/* 
 * mxCmdAM.c --
 *
 *	This file contains the top-level command procedures for
 *	all Mx commands whose names begin with the letters a-m.
 *	Some of these commands are also shared with Tx.
 *
 * Copyright (C) 1986 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/mxCmdAM.c,v 1.15 89/07/17 09:47:45 ouster Exp $ SPRITE (Berkeley)";
#endif not lint

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

/*
 * Global variables shared between Mx_BindCmd and BindingProc
 * below.  The array holds pointers to all of the individual bindings.
 */

static int bindingCount;		/* No. of bindings found so far. */
static int bindingSpace;		/* No. of slots in bindings. */
static char **bindings;			/* Pointers to binding strings. */
extern int BindingProc();		/* Forward reference. */


/*
 *----------------------------------------------------------------------
 *
 * Mx_BindCmd --
 *
 *	This is the top-level procedure that implements the
 *	"bind" 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_BindCmd(mxwPtr, interp, argc, argv)
    register MxWindow *mxwPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    if (argc >= 4) {
	sprintf(interp->result,
		"too many args: should be \"%.50s [sequence [command]]\"",
		argv[0]);
	return TCL_ERROR;
    }

    if (argc == 2) {
	Tcl_Return(interp, Cmd_BindingGet(mxwPtr->cmdTable, argv[1]),
		TCL_STATIC);
    } else if (argc == 3) {
	if (*argv[2] == 0) {
	    Cmd_BindingDelete(mxwPtr->cmdTable, argv[1]);
	} else {
	    Cmd_BindingCreate(mxwPtr->cmdTable, argv[1], argv[2]);
	}
    } else {
	bindingCount = 0;
	bindingSpace = 250;
	bindings = (char **) malloc((unsigned) bindingSpace*(sizeof(char *)));
	(void) Cmd_EnumBindings(mxwPtr->cmdTable, BindingProc,
		(ClientData) 0);
	interp->result = Tcl_Merge(bindingCount, bindings);
	interp->dynamic = 1;
	free((char *) bindings);
    }
    return TCL_OK;
}

	/* ARGSUSED */
int
BindingProc(clientData, sequence, command)
    ClientData clientData;		/* Ignored. */
    char *sequence;			/* Binding. */
    char *command;			/* Ignored. */
{
    /*
     * Expand the array if it has run out of space.
     */

    if (bindingCount == bindingSpace) {
	char **newArray;

	bindingSpace *= 2;
	newArray = (char **) malloc((unsigned) bindingSpace*(sizeof(char *)));
	bcopy((char *) bindings, (char *) newArray,
		bindingCount*sizeof(char *));
	free((char *) bindings);
	bindings = newArray;
    }
    bindings[bindingCount] = sequence;
    bindingCount++;
    return 1;
}

/*
 *----------------------------------------------------------------------
 *
 * Mx_CaretCmd --
 *
 *	This is the top-level procedure that implements the
 *	"caret" 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_CaretCmd(mxwPtr, interp, argc, argv)
    register MxWindow *mxwPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    Mx_Position position;
    int result, length;

    if (argc == 2) {
	result = MxGetMark(mxwPtr, argv[1], &position);
	if (result != TCL_OK) {
	    return result;
	}
	MxCaretSetPosition(mxwPtr, position, 0);
    } else if (argc == 3) {
	if (strcmp(argv[1], "display") != 0) {
	    caretBadArg:
	    sprintf(interp->result,
		    "bad arg: should be \"%.50s %s\"",
		    argv[0], "display block|caret|off|viblock");
	    return TCL_ERROR;
	}
	length = strlen(argv[2]);
	if (strncmp(argv[2], "caret", length) == 0) {
	    mxwPtr->cursorMode = CARET;
	} else if (strncmp(argv[2], "block", length) == 0) {
	    mxwPtr->cursorMode = BLOCK;
	} else if (strncmp(argv[2], "off", length) == 0) {
	    mxwPtr->cursorMode = OFF;
	} else if (strncmp(argv[2], "viblock", length) == 0) {
	    mxwPtr->cursorMode = VIBLOCK;
	} else {
	    goto caretBadArg;
	}
    } else {
	sprintf(interp->result, "%s \"%.50s mark\" or \"%.50s display type\"",
		"wrong # args: should be", argv[0], argv[0]);
	return TCL_ERROR;
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * Mx_CleanCmd --
 *
 *	This is the top-level procedure that implements the
 *	"clean" 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_CleanCmd(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 \"%.50s\"",
		argv[0]);
	return TCL_ERROR;
    }
    Mx_MarkClean(mxwPtr->fileInfoPtr->file);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * Mx_ColumnCmd --
 *
 *	This is the top-level procedure that implements the
 *	"column" 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_ColumnCmd(mxwPtr, interp, argc, argv)
    MxWindow *mxwPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    Mx_Position mark;
    int result, length, column;
    register char *p;

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

    /*
     * Scan the line to determine the column number corresponding to
     * the mark.
     */

    p = Mx_GetLine(mxwPtr->fileInfoPtr->file, mark.lineIndex, &length);
    if (p == NULL) {
	interp->result = "Mx_GetLine returned bad line to Mx_ColumnCmd";
	return TCL_ERROR;
    }
    for (column = 0; (*p != 0) && (mark.charIndex > 0);
	    p++, mark.charIndex--) {
	if (*p == '\t') {
	    column = (column+8) & ~07;
	} else if (iscntrl(*p)) {
	    column += 2;
	} else {
	    column += 1;
	}
    }
    sprintf(interp->result, "%d", column);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * Mx_ControlCmd --
 *
 *	This is the top-level procedure that implements the
 *	"control" 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_ControlCmd(mxwPtr, interp, argc, argv)
    MxWindow *mxwPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    register char *src, *dst, c;
    int length;
    char *limit;
    int format;
#define NONE		0
#define BACKSLASH	1
#define BINDING		2

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

    length = strlen(argv[1]);
    if (strncmp(argv[1], "make", length) == 0) {
	for (src = argv[2]; *src != 0; src++) {
	    c = *src;
	    if (c == '?') {
		*src = 0177;
	    } else {
		*src = c & 037;
		if (*src == 0) {
		    *src = c;
		}
	    }
	}
	Tcl_Return(interp, argv[2], TCL_VOLATILE);
	return TCL_OK;
    }
    format = NONE;
    if (length >= 2) {
	if (strncmp(argv[1], "backslash", length) == 0) {
	    format = BACKSLASH;
	}
	if (strncmp(argv[1], "binding", length) == 0) {
	    format = BINDING;
	}
    }
    if (format == NONE) {
	sprintf(interp->result,
		"bad \"%.50s\" option \"%.50s\": should be make, backslash, or binding",
		argv[0], argv[1]);
	return TCL_ERROR;
    }
    length = TCL_RESULT_SIZE;
    for (src = argv[2], dst = interp->result, limit = dst + length - 10;
	    *src != 0; src++) {
	if ((format == BINDING) && (dst != interp->result)) {
	    *dst = ' ';
	    dst++;
	}
	c = *src;
	if ((format == BINDING) && (dst != interp->result)) {
	    *dst = ' ';
	    dst++;
	}
	if (isascii(c) && isprint(c)) {
	    if ((c == ' ') && (format == BINDING)) {
		strcpy(dst, "SPACE");
		dst += 5;
	    } else {
		*dst = c;
		dst++;
	    }
	} else if (format == BACKSLASH) {
	    *dst = '\\';
	    dst++;
	    if (c == '\n') {
		*dst = 'n';
		dst++;
	    } else if (c == '\t') {
		*dst = 't';
		dst++;
	    } else if (c == '\b') {
		*dst = 'b';
		dst++;
	    } else {
		dst[0] = ((c&0300) >> 6) + '0';
		dst[1] = ((c&070) >> 3) + '0';
		dst[2] = (c&07) + '0';
		dst += 3;
	    }
	} else {
	    if (c & 0x80) {
		dst[0] = 'M';
		dst[1] = '-';
		dst += 2;
		c &= 0x177;
	    }
	    if (isprint(c)) {
		if (c == ' ') {
		    strcpy(dst, "SPACE");
		    dst += 5;
		} else {
		    *dst = c;
		    dst++;
		}
	    } else {
		if (c == 033) {
		    dst[0] = 'E';
		    dst[1] = 'S';
		    dst[2] = 'C';
		} else if (c == 0177) {
		    dst[0] = 'D';
		    dst[1] = 'E';
		    dst[2] = 'L';
		} else {
		    dst[0] = 'C';
		    dst[1] = '-';
		    if (c == 037) {
			dst[2] = '?';
		    } else {
			dst[2] = (c & 037) | 0140;
		    }
		}
		dst += 3;
	    }
	}

	/*
	 * If the static space for the result runs out, then allocate
	 * more space.  Be sure always to leave enough space for the
	 * longest possible next conversion, plus the terminating null
	 * character.
	 */

	if (dst >= limit) {
	    char *newResult;

	    length = 2*length;
	    newResult = (char *) malloc((unsigned) length);
	    strncpy(newResult, interp->result, dst-interp->result);
	    dst += newResult - interp->result;
	    limit = dst + length - 10;
	    if (interp->dynamic) {
		free((char *) interp->result);
	    }
	    interp->dynamic = 1;
	    interp->result = newResult;
	}
    }
    *dst = 0;
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * Mx_DeleteCmd --
 *
 *	This is the top-level procedure that implements the
 *	"delete" 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_DeleteCmd(mxwPtr, interp, argc, argv)
    register MxWindow *mxwPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    Mx_Position first, last, tmp, eof;
    int result;
    int changeView = 1;

    if ((argc < 2) || (argc > 4)) {
	sprintf(interp->result,
		"wrong # args: should be \"%.50s mark1 [mark2 [noviewchange]]\"",
		argv[0]);
	return TCL_ERROR;
    }
    result = MxGetMark(mxwPtr, argv[1], &first);
    if (result != TCL_OK) {
	return result;
    }
    if (argc == 2) {
	last = first;
    } else {
	result = MxGetMark(mxwPtr, argv[2], &last);
	if (result != TCL_OK) {
	    return result;
	}
    }
    if (argc == 4) {
	if (strncmp(argv[3], "noviewchange", strlen(argv[3])) == 0) {
	    changeView = 0;
	} else {
	    sprintf(interp->result,
		    "bad arg \"%.50s\" to \"%.50s\" (should be \"noviewchange\")",
		    argv[3], argv[0]);
	    return TCL_ERROR;
	}
    }

    /*
     * Get the earlier position into first.
     */
     
    if (MX_POS_LESS(last, first)) {
	tmp = first;
	first = last;
	last = tmp;
    }

    /*
     * Here's a special case to allow the last line(s) of a file to
     * be deleted:  if the end of the delete range is the end of
     * the file, and the beginning of the delete range is the
     * beginning of a line, move the beginning of the range back
     * one character.  But don't allow a situation to develop where
     * the last line of the file has no newline at the end.
     */

    eof = Mx_EndOfFile(mxwPtr->fileInfoPtr->file);
    if (MX_POS_EQUAL(last, eof) && (first.charIndex == 0)) {
	first = Mx_Offset(mxwPtr->fileInfoPtr->file, first, -1);
    }

    /*
     * Increment last, since Mx_ReplaceBytes wants the position of
     * the character AFTER the last one being deleted.
     */

    last = Mx_Offset(mxwPtr->fileInfoPtr->file, last, 1);

    if (MX_POS_LESS(first, last)) {
	Mx_ReplaceBytes(mxwPtr->fileInfoPtr->file, first, last, (char *) NULL);
	if (changeView) {
	    MxGetInWindow(mxwPtr, first, mxwPtr->heightLines/2, 0);
	}
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * Mx_ExtractCmd --
 *
 *	This is the top-level procedure that implements the
 *	"extract" 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_ExtractCmd(mxwPtr, interp, argc, argv)
    register MxWindow *mxwPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    Mx_Position first, last;
    int result, numBytes, lineLength, i;
    char *line, *p;
    Mx_File file = mxwPtr->fileInfoPtr->file;

    if ((argc != 2) && (argc != 3)) {
	sprintf(interp->result,
		"wrong # args: should be \"%.50s first [last]\"", argv[0]);
	return TCL_ERROR;
    }
    result = MxGetMark(mxwPtr, argv[1], &first);
    if (result != TCL_OK) {
	return result;
    }
    if (argc == 2) {
	last = first;
    } else {
	result = MxGetMark(mxwPtr, argv[2], &last);
	if (result != TCL_OK) {
	    return result;
	}
    }

    /*
     * Do this in two passes.  First, figure out how many bytes it
     * will take to store the extracted bytes.
     */

    numBytes = 0;
    for (i = first.lineIndex; i <= last.lineIndex; i++) {
	line = Mx_GetLine(file, i, &lineLength);
	if (line == NULL) {
	    continue;
        }
	numBytes += lineLength;
	if (i == first.lineIndex) {
	    if (first.charIndex >= lineLength) {
		first.charIndex = lineLength-1;
	    }
	    numBytes -= first.charIndex;
	}
	if (i == last.lineIndex) {
	    if (last.charIndex > lineLength) {
		last.charIndex = lineLength-1;
	    }
	    numBytes -= (lineLength - last.charIndex - 1);
	}
    }
    numBytes += 1;
    if (numBytes > TCL_RESULT_SIZE) {
	interp->result = malloc((unsigned) numBytes);
	interp->dynamic = 1;
    }

    /*
     * Pass 2: copy the bytes into the result area.
     */

    p = interp->result;
	line = Mx_GetLine(file, first.lineIndex, &lineLength);
    if (first.lineIndex == last.lineIndex) {
	i = last.charIndex + 1 - first.charIndex;
	strncpy(p, &line[first.charIndex], i);
	p += i;
    } else {
	strncpy(p, &line[first.charIndex], lineLength - first.charIndex);
	p += lineLength - first.charIndex;
	for (i = first.lineIndex + 1; i < last.lineIndex; i++) {
	    line = Mx_GetLine(file, i, &lineLength);
	    strncpy(p, line, lineLength);
	    p += lineLength;
	}
	line = Mx_GetLine(file, last.lineIndex, &lineLength);
	strncpy(p, line, last.charIndex + 1);
	p += last.charIndex + 1;
    }
    *p = 0;
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * Mx_FocusCmd --
 *
 *	This is the top-level procedure that implements the
 *	"focus" 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_FocusCmd(mxwPtr, interp, argc, argv)
    register MxWindow *mxwPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    int clear = 0;
    int length;
    Window w;

    if ((argc < 2) || (argc > 3)) {
	focusSyntax:
	sprintf(interp->result, "wrong #/type of args: should be \"%.50s command|file|replace|search [clear]\"",
		argv[0]);
	return TCL_ERROR;
    }
    if (argc == 3) {
	if (strncmp(argv[2], "clear", strlen(argv[2])) != 0) {
	    goto focusSyntax;
	}
	clear = 1;
    }

    length = strlen(argv[1]);
    if (strncmp(argv[1], "search", length) == 0) {
	MxMakeSearchWindow(mxwPtr);
	if (clear && (mxwPtr->searchString != NULL)
		&& (*mxwPtr->searchString != 0)) {
	    *mxwPtr->searchString = 0;
	    Sx_EntryMake(mxwPtr->display, mxwPtr->searchWindow,
		    mxwPtr->w, "Search: ",
		    mxwPtr->fontPtr, mxwPtr->foreground, mxwPtr->background,
		    mxwPtr->searchString, MAX_STRING_LENGTH);
	}
	w = mxwPtr->searchWindow;
    } else if ((strncmp(argv[1], "replace", length) == 0) &&
	    !(mxwPtr->flags & TX_WINDOW)) {
	MxMakeSearchWindow(mxwPtr);
	if (clear && (mxwPtr->replaceString != NULL)
		&& (*mxwPtr->replaceString != 0)) {
	    *mxwPtr->replaceString = 0;
	    Sx_EntryMake(mxwPtr->display, mxwPtr->replaceWindow,
		    mxwPtr->w, "Replace: ",
		    mxwPtr->fontPtr, mxwPtr->foreground, mxwPtr->background,
		    mxwPtr->replaceString, MAX_STRING_LENGTH);
	}
	w = mxwPtr->replaceWindow;
    } else if (strncmp(argv[1], "command", length) == 0) {
	MxMakeCmdWindow(mxwPtr);
	if (clear && (mxwPtr->cmdString != NULL)
		&& (*mxwPtr->cmdString != 0)) {
	    *mxwPtr->cmdString = 0;
	    Sx_EntryMake(mxwPtr->display, mxwPtr->cmdWindow,
		    mxwPtr->w, "Command: ",
		    mxwPtr->fontPtr, mxwPtr->foreground, mxwPtr->background,
		    mxwPtr->cmdString, MAX_STRING_LENGTH);
	}
	w = mxwPtr->cmdWindow;
    } else if (strncmp(argv[1], "file", length) == 0) {
	w = mxwPtr->fileWindow;
    } else {
	goto focusSyntax;
    }

    Sx_Focus(mxwPtr->display, w);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * Mx_GeometryCmd --
 *
 *	This is the top-level procedure that implements the
 *	"geometry" 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_GeometryCmd(mxwPtr, interp, argc, argv)
    register MxWindow *mxwPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    Drawable dummy1;
    int x, y, width, height, borderWidth, dummy2, flags;

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

    XGetGeometry(mxwPtr->display, mxwPtr->w, &dummy1, &x, &y, &width, &height,
	    &borderWidth, &dummy2);
    flags = XParseGeometry(argv[1], &x, &y, &width, &height);
    if (flags & WidthValue) {
	width = Sx_ScrollbarWidth() + 2
		+ width*mxwPtr->fontPtr->max_bounds.width;
    }
    if (flags & HeightValue) {
	height = 3*Sx_DefaultHeight(mxwPtr->display, mxwPtr->fontPtr) + 4
		+ height*(mxwPtr->fontPtr->ascent + mxwPtr->fontPtr->descent);
	if (mxwPtr->cmdWindow != 0) {
	    height += Sx_DefaultHeight(mxwPtr->display, mxwPtr->fontPtr) + 1;
	}
    }
    if ((flags & (XNegative|XValue)) == (XNegative|XValue)) {
	x = DisplayWidth(mxwPtr->display, DefaultScreen(mxwPtr->display))
		+ x - width - 2*borderWidth;
    }
    if ((flags & (YNegative|YValue)) == (YNegative|YValue)) {
	y = DisplayHeight(mxwPtr->display, DefaultScreen(mxwPtr->display))
		+ y - height - 2*borderWidth;
    }
    if (((flags & (XValue|YValue)) != (XValue|YValue))
	    && ((flags & (WidthValue|HeightValue))
	    != (WidthValue|HeightValue))) {
	sprintf(interp->result,
		"geometry specification \"%.50s\" doesn't make sense",
		argv[1]);
	return TCL_ERROR;
    }
    XMoveResizeWindow(mxwPtr->display, mxwPtr->w, x, y, width, height);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * Mx_IndentCmd --
 *
 *	This is the top-level procedure that implements the
 *	"indent" 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_IndentCmd(mxwPtr, interp, argc, argv)
    register MxWindow *mxwPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    Mx_Position first, last;
    int amount, result;
    char c, *amountString;

    if ((argc != 4) && (argc != 5)) {
	indentSyntax:
	sprintf(interp->result, "wrong #/type of args: should be \"%.50s mark1 mark2 [+|-] amount\"",
		argv[0]);
	return TCL_ERROR;
    }
    result = MxGetMark(mxwPtr, argv[1], &first);
    if (result != TCL_OK) {
	return result;
    }
    result = MxGetMark(mxwPtr, argv[2], &last);
    if (result != TCL_OK) {
	return result;
    }
    c = *argv[3];
    if (argc == 5) {
	if ((c != '+') && (c != '-')) {
	    goto indentSyntax;
	}
	amountString = argv[4];
    } else {
	amountString = argv[3];
    }
    if (!isdigit(*amountString)) {
	goto indentSyntax;
    }
    amount = atoi(amountString);

    for ( ; first.lineIndex <= last.lineIndex; first.lineIndex++) {
	int indent;

	indent = MxGetIndent(mxwPtr->fileInfoPtr->file, first.lineIndex);
	if (c == '+') {
	    indent += amount;
	} else if (c == '-') {
	    indent -= amount;
	    if (indent < 0) {
		indent = 0;
	    }
	} else {
	    indent = amount;
	}
	MxSetIndent(mxwPtr->fileInfoPtr->file, first.lineIndex, indent);
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * Mx_InsertCmd --
 *
 *	This is the top-level procedure that implements the
 *	"insert" 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_InsertCmd(mxwPtr, interp, argc, argv)
    register MxWindow *mxwPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    register MxFileInfo *fileInfoPtr = mxwPtr->fileInfoPtr;
    int lastLine, result;
    Mx_Position pos, pos2;
    Mx_Floater floater;

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

    if (argc == 2) {
	pos = fileInfoPtr->caretFirst;
    } else {
	result = MxGetMark(mxwPtr, argv[2], &pos);
	if (result != TCL_OK) {
	    return result;
	}
    }
    pos2 = pos;
    floater = Mx_FloaterCreate(fileInfoPtr->file, &pos, &pos2);
    lastLine = pos.lineIndex;
    Mx_ReplaceBytes(fileInfoPtr->file, pos, pos, argv[1]);
    MxMarkParens(fileInfoPtr, Mx_Offset(fileInfoPtr->file, pos, -1));

    /*
     * When a new line is inserted, clean up the indentation on the
     * insertion point's line.
     */
     
    if (pos.lineIndex != lastLine) {
	MxCleanIndent(fileInfoPtr->file, lastLine);
    }

    /*
     * Make sure that the end of the insertion is visible in the window.
     */

    MxGetInWindow(mxwPtr, pos, mxwPtr->heightLines - 1, 0);
    Mx_FloaterDelete(floater);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * Mx_MarkCmd --
 *
 *	This is the top-level procedure that implements the
 *	"mark" 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_MarkCmd(mxwPtr, interp, argc, argv)
    register MxWindow *mxwPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    Mx_Position src;
    int opLength;
    int count, i, result;
    int forward;
    char *term;
    static char *msg1 = "bad \"op\" argument to \"mark\":  must be \
char, column, line, forward, backward, search, or parenthesis";

    result = MxGetMark(mxwPtr, argv[1], &src);
    if (result != TCL_OK) {
	return result;
    }

    if (argc == 2) {
	goto markDone;
    }

    if (argc < 3) {
	sprintf(interp->result,
		"wrong # args: should be \"%.50s src [op [args]]\"",
		argv[0]);
	return TCL_ERROR;
    }
    opLength = strlen(argv[2]);
    if (strncmp(argv[2], "line", opLength) == 0) {
	if (argc != 4) {
	    lineError:
	    sprintf(interp->result,
		    "wrong #/type of args: should be \"%.50s src line index\"",
		    argv[0]);
	    return TCL_ERROR;
	}
	i = strtoul(argv[3], &term, 10);
	if (term == argv[3]) {
	    goto lineError;
	}
	src.lineIndex = i-1;
	goto markDone;
    }

    if ((strncmp(argv[2], "char", opLength) == 0)
	    && (opLength >= 2)) {
	if (argc != 4) {
	    charError:
	    sprintf(interp->result,
		    "wrong #/type args: should be \"%.50s src char index\"",
		    argv[0]);
	    return TCL_ERROR;
	}
	i = strtol(argv[3], &term, 10);
	if (term == argv[3]) {
	    goto charError;
	}
	src.charIndex = i;
	if (i < 0) {
	    if (Mx_GetLine(mxwPtr->fileInfoPtr->file,
		    src.lineIndex, &i) == NULL) {
		i = 0;
	    } else {
		i--;
	    }
	}
	src.charIndex = i;
	goto markDone;
    }

    if ((strncmp(argv[2], "column", opLength) == 0)
	    && (opLength >= 2)) {
	register char *p;
	int column;

	if (argc != 4) {
	    columnError:
	    sprintf(interp->result,
		    "wrong #/type args: should be \"%.50s src column index\"",
		    argv[0]);
	    return TCL_ERROR;
	}
	column = strtoul(argv[3], &term, 10);
	if (term == argv[3]) {
	    goto columnError;
	}
	p = Mx_GetLine(mxwPtr->fileInfoPtr->file, src.lineIndex, (int *) NULL);
	if (p == NULL) {
	    interp->result = "Mx_GetLine returned bad line to Mx_MarkCmd";
	    return TCL_ERROR;
	}
	for (src.charIndex = 0, i = 0; (*p != '\n'); p++, src.charIndex++) {
	    if (*p == '\t') {
		i = (i+8) & ~07;
	    } else if (iscntrl(*p)) {
		i += 2;
	    } else {
		i += 1;
	    }
	    if (i > column) {
		break;
	    }
	}
	goto markDone;
    }

    if (strncmp(argv[2], "parenthesis", opLength) == 0) {
	Mx_Position open1, open2, close1, close2;

	if (argc > 4) {
	    sprintf(interp->result, "wrong #/type args: should be \"%.50s src paren [varName]\"",
		    argv[0]);
	    return TCL_ERROR;
	}
	if (Mx_SearchParen(mxwPtr->fileInfoPtr->file, src, Mx_ZeroPosition,
		Mx_EndOfFile(mxwPtr->fileInfoPtr->file), mxOpenParens,
		mxCloseParens, &open1, &open2, &close1, &close2)) {
	    if (MX_POS_LEQ(open1, src) && (MX_POS_LEQ(src, open2))) {
		open1 = close1;
		open2 = close2;
	    }
	} else {
	    open1 = open2 = src;
	}
	if (argc == 4) {
	    sprintf(interp->result, "%d.%d", open2.lineIndex+1,
		    open2.charIndex);
	    Tcl_SetVar(mxwPtr->interp, argv[3], interp->result, 0);
	}
	src = open1;
	goto markDone;
    }

    if (strncmp(argv[2], "search", opLength) == 0) {
	int dirLength;
	Mx_Position first, last;
	
	if ((argc != 5) && (argc != 6)) {
	    searchSyntax:
	    sprintf(interp->result, "wrong #/type args: should be \"%.50s src search forward|backward pattern [varName]",
		    argv[0]);
	    return TCL_ERROR;
	}
	dirLength = strlen(argv[3]);
	if (strncmp(argv[3], "forward", dirLength) == 0) {
	    if (!Mx_SearchPattern(mxwPtr->fileInfoPtr->file, src,
		    Mx_EndOfFile(mxwPtr->fileInfoPtr->file), argv[4],
		    &first, &last)) {
		if (!Mx_SearchPattern(mxwPtr->fileInfoPtr->file,
			Mx_ZeroPosition, src, argv[4], &first, &last)) {
		    first = last = src;
		}
	    }
	} else {
	    if (strncmp(argv[3], "backward", dirLength) != 0) {
		goto searchSyntax;
	    }
	    if (!Mx_SearchPattern(mxwPtr->fileInfoPtr->file, src,
		    Mx_ZeroPosition, argv[4], &first, &last)) {
		if (!Mx_SearchPattern(mxwPtr->fileInfoPtr->file,
			Mx_EndOfFile(mxwPtr->fileInfoPtr->file), src,
			argv[4], &first, &last)) {
		    first = last = src;
		}
	    }
	}
	if (argc == 6) {
	    sprintf(interp->result, "%d.%d", last.lineIndex + 1,
		    last.charIndex);
	    Tcl_SetVar(mxwPtr->interp, argv[5], interp->result, 0);
	}
	src = first;
	goto markDone;
    }

    if (strncmp(argv[2], "forward", opLength) == 0) {
	forward = 1;
    } else {
	forward = 0;
	if (strncmp(argv[2], "backward", opLength) != 0) {
	    interp->result = msg1;
	    return TCL_ERROR;
	}
    }

    if (argc != 5) {
	forwError:
	sprintf(interp->result, "wrong # args: must be \"%.50s src forward|backward count chars|words|lines\"",
		argv[0]);
	return TCL_ERROR;
    }
    count = strtoul(argv[3], &term, 10);
    if (term == argv[4]) {
	goto forwError;
    }
    i = strlen(argv[4]);
    if (strncmp(argv[4], "chars", i) == 0) {
	if (!forward) {
	    count = -count;
	}
	src = Mx_Offset(mxwPtr->fileInfoPtr->file, src, count);
    } else if (strncmp(argv[4], "lines", i) == 0) {
	if (forward) {
	    src.lineIndex += count;
	} else {
	    src.lineIndex -= count;
	    if (src.lineIndex < 0) {
		src.lineIndex = 0;
	    }
	}
    } else if (strncmp(argv[4], "words", i) == 0) {
	if (forward) {
	    Mx_Position eof;

	    eof = Mx_EndOfFile(mxwPtr->fileInfoPtr->file);
	    for ( ; ((count > 0) && !MX_POS_EQUAL(src, eof)); count--) {
		(void) Mx_SearchMask(mxwPtr->fileInfoPtr->file,
			src, eof, mxWordMask, &src);
		src = Mx_Offset(mxwPtr->fileInfoPtr->file, src, 1);
	    }
	} else {
	    for ( ; ((count > 0) && !MX_POS_LEQ(src, Mx_ZeroPosition));
		    count--) {
		src = Mx_Offset(mxwPtr->fileInfoPtr->file, src, -1);
		(void) Mx_SearchMask(mxwPtr->fileInfoPtr->file, src,
			Mx_ZeroPosition, mxWordMask, &src);
	    }
	}
    } else {
	goto forwError;
    }
    markDone:
    sprintf(interp->result, "%d.%d", src.lineIndex+1, src.charIndex);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * Mx_MenuCmd --
 *
 *	This is the top-level procedure that implements the
 *	"menu" 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_MenuCmd(mxwPtr, interp, argc, argv)
    register MxWindow *mxwPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    MxMenuInfo *miPtr;
    int length;
    Sx_MenuEntry entries[SX_MAX_MENU_ENTRIES];

    extern char *GetString();		/* Forward references */
    extern unsigned long GetColor();

    if (argc < 2) {
	sprintf(interp->result, "wrong # args: must be \"%.50s option [args]\"",
		argv[0]);
	return TCL_ERROR;
    }
    length = strlen(argv[1]);
    if (strncmp(argv[1], "append", length) == 0) {
	Window window;
	XFontStruct *fontPtr;
	int numEntries;
	unsigned long fg, bg;

	if (argc != 8) {
	    sprintf(interp->result, "wrong # args: should be \"%.50s append name left center right color cmd\"",
		    argv[0]);
	    return TCL_ERROR;
	}
	window = Sx_MenuGetWindow(mxwPtr->display, mxwPtr->menuBar, argv[2]);
	if (window == NULL) {
	    goto menuNameBad;
	}
	numEntries = Sx_MenuGetInfo(mxwPtr->display, window, entries,
		&fontPtr, &fg, &bg);
	if (numEntries >= SX_MAX_MENU_ENTRIES) {
	    return TCL_OK;
	}
	entries[numEntries].leftText = GetString(argv[3]);
	entries[numEntries].centerText = GetString(argv[4]);
	entries[numEntries].rightText = GetString(argv[5]);
	entries[numEntries].background = GetColor(mxwPtr->display, argv[6]);
	entries[numEntries].proc = MxMenuProc;
	miPtr = (MxMenuInfo *) malloc((unsigned) (sizeof(MxMenuInfo) - 3 +
		strlen(argv[7])));
	miPtr->mxwPtr = mxwPtr;
	(void) strcpy(miPtr->command, argv[7]);
	entries[numEntries].clientData = (ClientData) miPtr;
	Sx_MenuCreate(mxwPtr->display, mxwPtr->menuBar, argv[2],
		numEntries+1, entries, fontPtr, fg, bg);
	return TCL_OK;
    }
    if (strncmp(argv[1], "create", length) == 0) {
	int numEntries, i, arg;

	numEntries = (argc - 3)/5;
	if ((argc-3) != (numEntries*5)) {
	    sprintf(interp->result, "wrong # args: should be \"%.50s create name [left center right color cmd] ...\"",
		    argv[0]);
	    return TCL_ERROR;
	}
	if (numEntries > SX_MAX_MENU_ENTRIES) {
	    sprintf(interp->result,
		    "can't create a menu with more than %d entries",
		    SX_MAX_MENU_ENTRIES);
	    return TCL_ERROR;
	}

	for (i = 0, arg = 3; i < numEntries; i++, arg += 5) {
	    entries[i].leftText = GetString(argv[arg]);
	    entries[i].centerText = GetString(argv[arg+1]);
	    entries[i].rightText = GetString(argv[arg+2]);
	    entries[i].background = GetColor(mxwPtr->display, argv[arg+3]);
	    entries[i].proc = MxMenuProc;
	    miPtr = (MxMenuInfo *) malloc((unsigned) (sizeof(MxMenuInfo) - 3 +
		    strlen(argv[arg+4])));
	    miPtr->mxwPtr = mxwPtr;
	    (void) strcpy(miPtr->command, argv[arg+4]);
	    entries[i].clientData = (ClientData) miPtr;
	}
	Sx_MenuCreate(mxwPtr->display, mxwPtr->menuBar, argv[2], numEntries,
		entries, mxwPtr->fontPtr, mxwPtr->foreground,
		mxwPtr->background);
	return TCL_OK;
    } else if (strncmp(argv[1], "delete", length) == 0) {
	Window window;
	int count;

	if (argc != 3) {
	    sprintf(interp->result,
		    "wrong # args: should be \"%.50s delete name\"",
		    argv[0]);
	    return TCL_ERROR;
	}
	window = Sx_MenuGetWindow(mxwPtr->display, mxwPtr->menuBar, argv[2]);
	if (window == NULL) {
	    goto menuNameBad;
	}
	count = Sx_MenuGetInfo(mxwPtr->display, window, entries,
		(XFontStruct **) NULL, (unsigned long *) NULL,
		(unsigned long *) NULL);
	for (count--; count >= 0; count--) {
	    free((char *) entries[count].clientData);
	}
	XDestroyWindow(mxwPtr->display, window);
	return TCL_OK;
    } else if (strncmp(argv[1], "info", length) == 0) {
	Window window;
	int count, i;
	char *names[SX_MAX_MENUS];
	char *entryStrings[SX_MAX_MENU_ENTRIES];
	char *pieces[4];

	if (argc == 2) {
	    count = Sx_MenuGetNames(mxwPtr->display, mxwPtr->menuBar, names,
		    (Window *) NULL);
	    interp->result = Tcl_Merge(count, names);
	    interp->dynamic = 1;
	    return TCL_OK;
	}
	if (argc != 3) {
	    sprintf(interp->result,
		    "wrong # args: should be \"%.50s info [name]\"",
		    argv[0]);
	    return TCL_ERROR;
	}
	window = Sx_MenuGetWindow(mxwPtr->display, mxwPtr->menuBar, argv[2]);
	if (window == NULL) {
	    goto menuNameBad;
	}
	count = Sx_MenuGetInfo(mxwPtr->display, window, entries,
		(XFontStruct **) NULL, (unsigned long *) NULL,
		(unsigned long *) NULL);
	for (i = 0; i < count; i++) {
	    pieces[0] = entries[i].leftText;
	    if (pieces[0] == NULL) {
		pieces[0] = "";
	    }
	    pieces[1] = entries[i].centerText;
	    if (pieces[1] == NULL) {
		pieces[1] = "";
	    }
	    pieces[2] = entries[i].rightText;
	    if (pieces[2] == NULL) {
		pieces[2] = "";
	    }
	    pieces[3] = ((MxMenuInfo *) entries[i].clientData)->command;
	    if (pieces[3] == NULL) {
		pieces[3] = "";
	    }
	    entryStrings[i] = Tcl_Merge(4, pieces);
	}
	interp->result = Tcl_Merge(count, entryStrings);
	interp->dynamic = 1;
	for (i = 0; i < count; i++) {
	    free(entryStrings[i]);
	}
	return TCL_OK;
    } else if (strncmp(argv[1], "modify", length) == 0) {
	Window window;
	Sx_MenuEntry entry;
	int index;

	if (argc != 9) {
	    sprintf(interp->result, "wrong # args: should be \"%.50s modify name index left center right color cmd\"",
		    argv[0]);
	    return TCL_ERROR;
	}
	window = Sx_MenuGetWindow(mxwPtr->display, mxwPtr->menuBar, argv[2]);
	if (window == NULL) {
	    goto menuNameBad;
	}
	index = atoi(argv[3]);
	if ((index < 0) || (index >= Sx_MenuGetInfo(mxwPtr->display, window,
		entries, (XFontStruct **) NULL, (unsigned long *) NULL,
		(unsigned long *) NULL))) {
	    sprintf(interp->result,
		    "there's no entry %d in menu \"%.50s\"",
		    index, argv[2]);
	    return TCL_ERROR;
	}
	free((char *) entries[index].clientData);
	entry.leftText = GetString(argv[4]);
	entry.centerText = GetString(argv[5]);
	entry.rightText = GetString(argv[6]);
	entry.background = GetColor(mxwPtr->display, argv[7]);
	entry.proc = MxMenuProc;
	miPtr = (MxMenuInfo *) malloc((unsigned) (sizeof(MxMenuInfo) - 3 +
		strlen(argv[8])));
	miPtr->mxwPtr = mxwPtr;
	(void) strcpy(miPtr->command, argv[8]);
	entry.clientData = (ClientData) miPtr;
	Sx_MenuReplaceEntry(mxwPtr->display, window, index, &entry);
	return TCL_OK;
    } else {
	sprintf(interp->result, "bad \"%.50s\" option: must be append, create, delete, or modify\"",
		argv[0]);
	return TCL_ERROR;
    }

    menuNameBad:
    sprintf(interp->result,
	    "there's no menu named \"%.50s\".", argv[2]);
    return TCL_ERROR;
}

/*
 * Local procedure used to turn empty or "-" strings into NULLs.
 */

char *
GetString(string)
    char *string;
{
    if ((string[0] == 0) || ((string[0] == '-') && (string[1] == 0))) {
	return NULL;
    }
    return string;
}

/*
 * Local procedure to turn a string into a color.  Return -1 if string
 * empty, otherwise translate to X color.
 */

unsigned long
GetColor(display, string)
    Display *display;		/* Connection to X server. */
    char *string;		/* Character color specification. */
{
    XColor screen, exact;

    if (GetString(string) == NULL) {
	return -1;
    }

    /*
    if (XParseColor(display, DefaultColormap(display, DefaultScreen(display)),
	    string, &screen) == 0) {
	return -1;
    }
    if (XAllocColor(display, DefaultColormap(display, DefaultScreen(display)),
	    &screen) == 0) {
	return -1;
    }
    */
    if (XAllocNamedColor(display,
	    DefaultColormap(display, DefaultScreen(display)), string,
	    &screen, &exact) != 0) {
	return -1;
    }
    return screen.pixel;
}

/*
 *----------------------------------------------------------------------
 *
 * Mx_MessageCmd --
 *
 *	This is the top-level procedure that implements the
 *	"message" 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_MessageCmd(mxwPtr, interp, argc, argv)
    register MxWindow *mxwPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    if (argc != 2) {
	sprintf(interp->result, "wrong # args: should be \"%.50s string\"",
		argv[0]);
	return TCL_ERROR;
    }

    MxOutputMsg(mxwPtr, argv[1]);
    return TCL_OK;
}
