/* 
 * mxCmdSZ.c --
 *
 *	This file contains the top-level command procedures for
 *	all Mx commands whose names begin with the letters s-z.
 *	Some of these procedure are also used by Tx.
 *
 * Copyright (C) 1987, 1988 Regents of the University of California 
 * Permission to use, copy, modify, and distribute this
 * software and its documentation for any purpose and without
 * fee is hereby granted, provided that the above copyright
 * notice appear in all copies.  The University of California
 * makes no representations about the suitability of this
 * software for any purpose.  It is provided "as is" without
 * express or implied warranty.
 */

#ifndef lint
static char rcsid[] = "$Header: /sprite/src/lib/mx/RCS/mxCmdSZ.c,v 1.26 90/04/19 11:36:07 ouster Exp $ SPRITE (Berkeley)";
#endif not lint

#include <stdio.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <ctype.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/file.h>
#include "mxInt.h"
#include "regex.h"


/*
 *----------------------------------------------------------------------
 *
 * Mx_SearchCmd --
 *
 *	This is the top-level procedure that implements the
 *	"search" command.  See the man page for details on what it
 *	does and what it returns.
 *
 * Results:
 *	See the man page.
 *
 * Side effects:
 *	See the man page.
 *
 *----------------------------------------------------------------------
 */

int
Mx_SearchCmd(mxwPtr, interp, argc, argv)
    register MxWindow *mxwPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    char *pattern;
    Mx_Position start, dummy, newFirst, newLast;
    int forward;
    int result;
    int regExp=1;		/* 1 if doing regular expression matching */
    char *errStr;		/* Holds returned error string */
    Mx_Position fileEnd;	/* Holds end of file position */
    char *value;

    if (argc > 3) {
	sprintf(interp->result,
		"too many args: should be \"%.50s forward|backward [pattern]\"",
		argv[0]);
	return TCL_ERROR;
    }

    value = Tcl_GetVar(mxwPtr->interp, "noRegExps", 1);
    if ((value != NULL) && (*value == '1')) {
	regExp = 0;
    }

    if (argc == 1) {
	forward = 1;
    } else {
	int length;

	length = strlen(argv[1]);
	if (strncmp(argv[1], "forward", length) == 0) {
	    forward = 1;
	} else {
	    forward = 0;
	    if (strncmp(argv[1], "backward", length) != 0) {
		sprintf(interp->result, "bad \"%.50s\" direction \"%.50s\": must be forward or backward\"",
			argv[0], argv[1]);
		return TCL_ERROR;
	    }
	}
    }

    /*
     * Figure out what to search for.
     */

    if (argc == 3) {
	pattern = argv[2];
    } else {
	if ((mxwPtr->searchWindow == 0) || (*mxwPtr->searchString == 0)) {
	    return Tcl_Eval(mxwPtr->interp, "focus search", 0, (char **) 0);
	}
	pattern = mxwPtr->searchString;
    }

    /*
     * Do the search.
     */

    if (regExp) {
	errStr = Mx_CompileRegExp(pattern);
	if (errStr != NULL) {
	    sprintf(interp->result, "bad \"%.50s\" pattern: %.50s",
		    argv[0], errStr);
	    return TCL_ERROR;
	}
    }

    result = MxGetSelRange(mxwPtr, &start, &dummy);
    if (result != TCL_OK) {
	Tcl_Return(interp, (char *) NULL, TCL_STATIC);
	start = mxwPtr->fileInfoPtr->caretFirst;
    }
    Sx_SelectionClear(mxwPtr->display);
    XFlush(mxwPtr->display);

    fileEnd = Mx_EndOfFile(mxwPtr->fileInfoPtr->file);
    if (regExp) {
	if (!Mx_SearchRegExp(mxwPtr->fileInfoPtr->file,
		Mx_Offset(mxwPtr->fileInfoPtr->file, start, forward?1:-1),
		forward?fileEnd:Mx_ZeroPosition, &newFirst, &newLast)) {
	    if (!Mx_SearchRegExp(mxwPtr->fileInfoPtr->file,
		    forward?Mx_ZeroPosition:fileEnd,
		    start, &newFirst, &newLast)) {
		interp->result = "couldn't find matching pattern";
		return TCL_ERROR;
	    }
	}
    }
    else {
	if (!Mx_SearchPattern(mxwPtr->fileInfoPtr->file,
		Mx_Offset(mxwPtr->fileInfoPtr->file, start, forward?1:-1),
		forward?fileEnd:Mx_ZeroPosition,pattern,
		&newFirst, &newLast)) {
	    if (!Mx_SearchPattern(mxwPtr->fileInfoPtr->file,
		    forward?Mx_ZeroPosition:fileEnd,
		    start, pattern, &newFirst, &newLast)) {
		interp->result = "couldn't find matching pattern";
		return TCL_ERROR;
	    }
	}
    }
    MxSelectionSet(mxwPtr, newFirst, newLast);
    if (!(mxwPtr->flags & TX_WINDOW)) {
	MxCaretSetPosition(mxwPtr, newFirst, 0);
    }
    MxGetInWindow(mxwPtr, newFirst, mxwPtr->heightLines/2, 0);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * Mx_SeeCmd --
 *
 *	This is the top-level procedure that implements the
 *	"see" command.  See the man page for details on what it
 *	does and what it returns.
 *
 * Results:
 *	See the man page.
 *
 * Side effects:
 *	See the man page.
 *
 *----------------------------------------------------------------------
 */

int
Mx_SeeCmd(mxwPtr, interp, argc, argv)
    register MxWindow *mxwPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    Mx_Position position;
    int result;

    if ((argc != 2) && (argc != 3)) {
	sprintf(interp->result,
		"wrong # args: should be \"%.50s mark [top|center|bottom]\"",
		argv[0]);
	return TCL_ERROR;
    }
    result = MxGetMark(mxwPtr, argv[1], &position);
    if (result != TCL_OK) {
	return result;
    }
    if (argc == 3) {
	int length, index;

	length = strlen(argv[2]);
	if (strncmp(argv[2], "top", length) == 0) {
	    index = 0;
	} else if (strncmp(argv[2], "center", length) == 0) {
	    index = mxwPtr->heightLines/2;
	} else if (strncmp(argv[2], "bottom", length) == 0) {
	    index = mxwPtr->heightLines-1;
	} else {
	    sprintf(interp->result, "bad \"%.50s\" position \"%.50s\": should be top, center, or bottom\"",
		    argv[0], argv[2]);
	    return TCL_ERROR;
	}
	MxGetInWindow(mxwPtr, position, index, 1);
    } else {
	MxGetInWindow(mxwPtr, position, mxwPtr->heightLines/2, 0);
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * Mx_SelectionCmd --
 *
 *	This is the top-level procedure that implements the
 *	"selection" command.  See the man page for details on what it
 *	does and what it returns.
 *
 * Results:
 *	See the man page.
 *
 * Side effects:
 *	See the man page.
 *
 *----------------------------------------------------------------------
 */

int
Mx_SelectionCmd(mxwPtr, interp, argc, argv)
    register MxWindow *mxwPtr;
    register Tcl_Interp *interp;
    int argc;
    char **argv;
{
    int result, length;

    if (argc > 1) {
	length = strlen(argv[1]);
    }
    if ((argc == 1) || (strncmp(argv[1], "get", length)) == 0) {
#	define MAX_SEL_SIZE 100000
	static char *selection = NULL;	/* malloc'ed space for selection.
					 * Grows dynamically to handle any
					 * selection up to MAX_SEL_SIZE
					 * bytes. */
	static int curSelSize = 0;	/* Current size of selection. */
	int count;
	char type[SX_FORMAT_SIZE];

	if (argc > 2) {
	    sprintf(interp->result, "too many args: should be \"%.50s [get]\"",
		    argv[0]);
	    return TCL_ERROR;
	}

	if (curSelSize == 0) {
	    curSelSize = 500;
	    selection = (char *) malloc((unsigned) curSelSize);
	}
    
	/*
	 * It may take several tries to get the whole selection.  In each
	 * try, fill up the buffer.  If it all fits in the buffer then
	 * return it.  Otherwise allocate a larger buffer and try again,
	 * until eventually it all fits.
	 */
    
	while (1) {
	    count = Sx_SelectionGet(mxwPtr->display, "text", 0, curSelSize-1,
		    selection, type);
	    if (count == -1) {
		interp->result =
			"tried to use selection, but nothing's selected";
		return TCL_ERROR;
	    }
	    if (strcmp(type, "text") != 0) {
		sprintf(interp->result, "selection has type \"%.50s\", which I don't know how to handle",
			type);
		return TCL_ERROR;
	    }
	    if (count < (curSelSize-1)) {
		selection[count] = 0;
		break;
	    }
	    if (curSelSize == MAX_SEL_SIZE) {
		sprintf(interp->result,
			"can't handle selections larger than %d bytes",
			MAX_SEL_SIZE);
		return TCL_ERROR;
	    }
	    free((char *) selection);
	    curSelSize *= 2;
	    if (curSelSize > MAX_SEL_SIZE) {
		curSelSize = MAX_SEL_SIZE;
	    }
	    selection = malloc((unsigned) curSelSize);
	}
	interp->result = selection;
	return TCL_OK;
    }
    if (strncmp(argv[1], "here", length) == 0) {
	if (mxwPtr->fileInfoPtr == MxSelectedFile) {
	    interp->result = "1";
	} else {
	    interp->result = "0";
	}
	return TCL_OK;
    }
    if (strncmp(argv[1], "clear", length) == 0) {
	MxSelectionClear(mxwPtr);
        return TCL_OK;
    }
    if (strncmp(argv[1], "set", length) == 0) {
	Mx_Position first, last;

	if ((argc != 3) && (argc != 4)) {
	    sprintf(interp->result,
		    "wrong # args: should be \"%.50s set mark1 [mark2]\"",
			    argv[0]);
	    return TCL_ERROR;
	}
	result = MxGetMark(mxwPtr, argv[2], &first);
	if (result != TCL_OK) {
	    return result;
	}
	if (argc == 3) {
	    last = first;
	} else {
	    result = MxGetMark(mxwPtr, argv[3], &last);
	    if (result != TCL_OK) {
		return result;
	    }
	}
	if (MX_POS_LEQ(first, last)) {
	    MxSelectionSet(mxwPtr, first, last);
	} else {
	    MxSelectionSet(mxwPtr, last, first);
	}
	return TCL_OK;
    }
    sprintf(interp->result,
	    "bad \"%.50s\" option \"%.50s\": must be clear, get, here, or set",
	    argv[0], argv[1]);
    return TCL_ERROR;
}

/*
 *----------------------------------------------------------------------
 *
 * Mx_SendCmd --
 *
 *	This is the top-level procedure that implements the
 *	"send" command.  See the man page for details on what it
 *	does and what it returns.
 *
 * Results:
 *	See the man page.
 *
 * Side effects:
 *	See the man page.
 *
 *----------------------------------------------------------------------
 */

int
Mx_SendCmd(mxwPtr, interp, argc, argv)
    register MxWindow *mxwPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    MxWindow *mxwPtr2;
    int window, result;

    if (argc != 3) {
	sprintf(interp->result,
		"wrong # args: should be \"%.50s window command\"",
		argv[0]);
	return TCL_ERROR;
    }

    if (sscanf(argv[1], "0x%x", &window) != 1) {
	badId:
	sprintf(interp->result, "\"%.50s\" got bad window id \"%.50s\"",
		argv[0], argv[1]);
	return TCL_ERROR;
    }
    mxwPtr2 = MxGetMxWindow(mxwPtr->display, (Window) window);
    if (mxwPtr2 == NULL) {
	goto badId;
    }
    result = Tcl_Eval(mxwPtr2->interp, argv[2], 0, (char **) 0);
    Tcl_Return(interp, mxwPtr2->interp->result, TCL_VOLATILE);
    return result;
}

/*
 *----------------------------------------------------------------------
 *
 * Mx_SwitchCmd --
 *
 *	This is the top-level procedure that implements the
 *	"switch" command.  See the man page for details on what it
 *	does and what it returns.
 *
 * Results:
 *	See the man page.
 *
 * Side effects:
 *	See the man page.
 *
 *----------------------------------------------------------------------
 */

int
Mx_SwitchCmd(mxwPtr, interp, argc, argv)
    register MxWindow *mxwPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    int result;
    char *fileName;

    if (argc != 2) {
	sprintf(interp->result, "wrong # args: should be \"%.50s fileName\"",
		argv[0]);
	return TCL_ERROR;
    }

    fileName = Tcl_TildeSubst(interp, argv[1]);
    if (fileName == NULL) {
	return TCL_ERROR;
    }
    if (strcmp(fileName, mxwPtr->fileInfoPtr->name) == 0) {
	/*
	 * SetupFileInfo won't reopen a file that's already
	 * open, so we give a message here.
	 */
	if (mxwPtr->fileInfoPtr->lastMod !=
		MxLastMod(mxwPtr->fileInfoPtr->name)) {
	    MxOutputMsg(mxwPtr, "Warning: current file has been modified");
	}
	return TCL_OK;
    }
    result = MxCheckWritten(mxwPtr, 0);
    if (result != TCL_OK) {
	return result;
    }
    return MxSwitchFile(mxwPtr, fileName);
}	    

/*
 *----------------------------------------------------------------------
 *
 * Mx_TaginfoCmd --
 *
 *	This is the top-level procedure that implements the
 *	"taginfo" command.  See the man page for details on what it
 *	does and what it returns.
 *
 * Results:
 *	See the man page.
 *
 * Side effects:
 *	See the man page.
 *
 *----------------------------------------------------------------------
 */

int
Mx_TaginfoCmd(mxwPtr, interp, argc, argv)
    register MxWindow *mxwPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    char *ptrs[2];
    int result;
    int regExp = 1;		/* 1 if regular expression pattern matching */
    char *value;

    value = Tcl_GetVar(mxwPtr->interp, "noRegExps", 1);
    if ((value != NULL) && (*value == '1')) {
	regExp = 0;
    }

    if (argc != 2) {
	sprintf(interp->result, "wrong # args: should be \"%.50s name\"",
		argv[0]);
	return TCL_ERROR;
    }

    result = Mx_GetTag(argv[1], Tcl_GetVar(mxwPtr->interp, "tagFiles", 1),
	    regExp, &ptrs[0], &ptrs[1], interp);
    if (result != TCL_OK) {
	return result;
    }
    interp->result = Tcl_Merge(2, ptrs);
    interp->dynamic = 1;
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * Mx_UndoCmd --
 *
 *	This is the top-level procedure that implements the
 *	"undo" command.  See the man page for details on what it
 *	does and what it returns.
 *
 * Results:
 *	See the man page.
 *
 * Side effects:
 *	See the man page.
 *
 *----------------------------------------------------------------------
 */

int
Mx_UndoCmd(mxwPtr, interp, argc, argv)
    register MxWindow *mxwPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    if (mxwPtr->fileInfoPtr->log == NULL) {
	interp->result = "can't undo: no undo info is kept for this file";
	return TCL_ERROR;
    }

    if (argc == 1) {
	return Undo_Last(mxwPtr->fileInfoPtr->log, interp, mxwPtr);
    } else if ((argc == 2) && (strcmp(argv[1], "more") == 0)) {
	return Undo_More(mxwPtr->fileInfoPtr->log, interp, mxwPtr);
    } else if ((argc == 3) && (strcmp(argv[1], "recover") == 0)) {
	return Undo_Recover(mxwPtr->fileInfoPtr->file, argv[2], interp);
    }
    sprintf(interp->result, "wrong args: must be \"%.50s\", or \"%.50s more\", or \"%.50s recover file\"",
	    argv[0], argv[0], argv[0]);
    return TCL_ERROR;
}

/*
 *----------------------------------------------------------------------
 *
 * Mx_UpdateCmd --
 *
 *	This is the top-level procedure that implements the
 *	"update" command.  See the man page for details on what it
 *	does and what it returns.
 *
 * Results:
 *	See the man page.
 *
 * Side effects:
 *	See the man page.
 *
 *----------------------------------------------------------------------
 */

	/* ARGSUSED */
int
Mx_UpdateCmd(mxwPtr, interp, argc, argv)
    register MxWindow *mxwPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    if (argc != 1) {
	sprintf(interp->result, "too many args: should be just \"%.50s\"",
		argv[0]);
	return TCL_ERROR;
    }
    Mx_Update();
    XFlush(mxwPtr->display);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * Mx_WriteCmd --
 *
 *	This is the top-level procedure that implements the
 *	"write" command.  See the man page for details on what it
 *	does and what it returns.
 *
 * Results:
 *	See the man page.
 *
 * Side effects:
 *	See the man page.
 *
 *----------------------------------------------------------------------
 */

int
Mx_WriteCmd(mxwPtr, interp, argc, argv)
    register MxWindow *mxwPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    int result;
    char *name;
    Mx_Position eof;
    char msg[200];

    if (argc > 2)  {
	sprintf(interp->result, "too many args: should be \"%.50s [filename]\"",
		argv[0]);
	return TCL_ERROR;
    }
    if (argc == 1) {
	if (*mxwPtr->fileInfoPtr->name == 0) {
	    sprintf(interp->result, "must provide \"name\" arg to \"%.50s\", since the file doesn't have a name yet",
		    argv[0]);
	    return TCL_ERROR;
	}
	name = Tcl_TildeSubst(interp, mxwPtr->fileInfoPtr->name);
	if (name == NULL) {
	    return TCL_ERROR;
	}
    } else {
	char *p;

	/*
	 * Don't allow weird characters in file names.
	 */

	for (p = argv[1]; *p != NULL; p++) {
	    if (!isascii(*p) || iscntrl(*p)) {
		interp->result = "file name contains non-printing characters";
		return TCL_ERROR;
	    }
	}
	name = Tcl_TildeSubst(interp, argv[1]);
	if (name == NULL) {
	    return TCL_ERROR;
	}

	/*
	 * Don't overwrite an existing file without permission.
	 */

	if (strcmp(mxwPtr->fileInfoPtr->name, name) != 0) {
	    if (access(name, F_OK) == 0) {
		int option;

		sprintf(msg, "File \"%.50s\" already exists.  %s",
			name,  "Do you want to overwrite it?");
		option = Sx_Notify(mxwPtr->display, mxwPtr->w, -1, -1, 0, msg,
			mxwPtr->fontPtr, 1, "Go ahead", "Don't write",
			(char *) NULL);
		if (option != 0) {
		    return TCL_OK;
		}
	    }
	}
    }

    /* Check if file has changed since we read it in */

    if (!strcmp(mxwPtr->fileInfoPtr->name, name)) {
	if (mxwPtr->fileInfoPtr->lastMod != MxLastMod(name)) {
	    sprintf(msg, "File \"%.50s\" has been modified since %s",
		    name, "it was read in.  Do you want to overwrite it?");
	    if (Sx_Notify(mxwPtr->display, mxwPtr->w, -1, -1, 0, msg,
		    mxwPtr->fontPtr, 1, "Go ahead", "Don't write",
		    (char *) NULL)) {
		interp->result = "skipped command at your request";
		return TCL_ERROR;
	    }
	}
    }

    /*
     * Get rid of trailing indentation on the current line;  otherwise
     * the next time the user points into the window the indentation will
     * get modified and the file will suddenly appear modified.
     */

    sprintf(msg, "writing \"%.50s\" ...", name);
    MxOutputMsg(mxwPtr, msg);
    XFlush(mxwPtr->display);
    MxCleanIndent(mxwPtr->fileInfoPtr->file,
	    mxwPtr->fileInfoPtr->caretFirst.lineIndex);
    result = Mx_FileWrite(mxwPtr->fileInfoPtr->file, name, interp);
    if (result != TCL_OK) {
	(void) Sx_Notify(mxwPtr->display, mxwPtr->w, -1, -1, 0,
		interp->result, mxwPtr->fontPtr, 1, "Continue", (char *) NULL);
	Tcl_Return(interp, "couldn't write file", TCL_STATIC);
	return result;
    }
    mxwPtr->fileInfoPtr->lastMod = MxLastMod(mxwPtr->fileInfoPtr->name);

    if (strcmp(name, mxwPtr->fileInfoPtr->name) == 0) {
	Undo_SetVersion(mxwPtr->fileInfoPtr->log, name);
	mxwPtr->fileInfoPtr->lastMod = MxLastMod(name);
    }
    eof = Mx_EndOfFile(mxwPtr->fileInfoPtr->file);
    sprintf(interp->result, "wrote \"%.50s\": %d lines", name,
	    eof.lineIndex+1);
    return TCL_OK;
}
