/* 
 * txWindow.c --
 *
 *	This procedure provides the top-level procedures for
 *	creating and deleting typescript windows, as well as the
 *	mouse event handler for those windows.  These are all
 *	routines that apply ONLY to Tx, and not to Mx.
 *
 * Copyright (C) 1986, 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/txWindow.c,v 1.46 90/03/29 11:16:58 ouster Exp $ SPRITE (Berkeley)";
#endif not lint


#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <bstring.h>
#include <ctype.h>
#include <errno.h>
#include <list.h>
#include <sgtty.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/file.h>
#include <sys/stat.h>
#include "mxInt.h"

/*
 * Library imports:
 */

char *getenv();

/*
 * Termcap definition from txSprite.c
 */
extern char *tcFormat, *tc1, *tc2, *tc3, *tc4, *tc5, *tc6, *tc7, *tc8;

/*
 * The context below maps from X window ids to MxWindow structures:
 */
static XContext txContext;

static int init = 0;

/*
 * Default cursor to use in Tx windows:
 */

#include "cursor.bits"
#include "cursMask.bits"
static Cursor txCursor;

/*
 * Head of list of all the MxFileInfo structures used for typescripts:
 */

static MxFileInfo *txFileListPtr = NULL;

/*
 * Procedures defined in this file but referenced before defined:
 */

static Mx_Position	TxBackspace();
static int		TxChildDied();
static void		TxDestroy();
static void		TxInit();
static void		TxInsertMultiple();
static void		TxMenuEntryProc();
static void		TxRawControlProc();
static void		TxStructureProc();

/*
 * The information below is used to create the command table.
 */
typedef struct {
    char *name;			/* Command name. */
    int (*proc)();		/* Procedure to process command. */
} CmdInfo;

static CmdInfo commands[] = {
    {"bind",		Mx_BindCmd},
    {"caret",		Mx_CaretCmd},
    {"control",		Mx_ControlCmd},
    {"delete",		Mx_DeleteCmd},
    {"escape",		Tx_EscapeCmd},
    {"extract",		Mx_ExtractCmd},
    {"focus",		Mx_FocusCmd},
    {"geometry",	Mx_GeometryCmd},
    {"insert", 		Tx_InsertCmd},
    {"mark",		Mx_MarkCmd},
    {"menu",		Mx_MenuCmd},
    {"message",		Mx_MessageCmd},
    {"mxopen",		Mx_OpenCmd},
    {"mxsend",		Mx_SendCmd},
    {"open",		Tx_OpenCmd},
    {"output",		Tx_OutputCmd},
    {"quit",		Tx_QuitCmd},
    {"quote",		Mx_QuoteCmd},
    {"search",		Mx_SearchCmd},
    {"see",		Mx_SeeCmd},
    {"selection",	Mx_SelectionCmd},
    {"send",		Tx_SendCmd},
    {"title",		Tx_TitleCmd},
    {"update",		Tx_UpdateCmd},
    {"vi",		Tx_ViCmd},
    {(char *) NULL, (int (*)()) NULL}
};

/*
 *----------------------------------------------------------------------
 *
 * Tx_Make --
 *
 *	Given a window, this procedure creates a set of subwindows
 *	to turn the window into a typescript window.
 *
 * Results:
 *	The return value is a Tcl return code (TCL_OK or TCL_ERROR).
 *	In the case of an error, interp->result is set to point to
 *	a string describing the error condition.  Interp->result
 *	should be initialized by the caller in the standard Tcl
 *	fashion.
 *
 * Side effects:
 *	Data structures are allocated and subwindows are created.
 *	Once the typescript is set up, Tx_Output may be called to
 *	output information to the typescript.  Whenever input becomes
 *	available from the typescript, the procedure inputProc will
 *	be called in the following fashion:
 *
 *	void
 *	inputProc(clientData, string)
 *	    ClientData clientData;
 *	    char *string;
 *	{
 *	}
 *
 *	In the call to inputProc, clientData will be the same as the
 * 	clientData argument to Tx_Make when the typescript was
 *	created.  String will point to a NULL-terminated string of
 *	input characters from the typescript.
 *
 *	Wnenever the size of a typescript window changes (more precisely,
 *	when the "main" window on a typescript changes size), sizeChangeProc
 *	is called with new information about the window:
 *
 *	void
 *	sizeChangeProc(clientData, sizeInfo)
 *	    ClientData clientData;
 *	    struct winsize *sizeInfo;
 *	{
 *	}
 *	
 *	ClientData means the same thing as for inputProc.  SizeInfo points
 *	to a structure giving the window's new size in the format needed
 *	for the TIOCSWINSZ ioctl.
 *
 *	Whenever the last window on a typescript is deleted, so that the
 *	typescript itself is about to go away, closeProc will be called
 *	as follows:
 *
 *	void
 *	closeProc(clientData)
 *	    ClientData clientData;
 *	{
 *	}
 *
 *	Once again, clientData is the same as the clientData argument to
 *	Tx_Make.
 *----------------------------------------------------------------------
 */

    /* ARGSUSED */
int 
Tx_Make(interp, display, window, infoPtr, inputProc, sizeChangeProc,
	closeProc, clientData)
    Tcl_Interp *interp;			/* Tcl interpreter used to return
					 * an error string. */
    Display *display;			/* Connection to X server. */
    Window window;			/* Window to use for edit display. */
    register Tx_WindowInfo *infoPtr;	/* Describes configuration for Tx
					 * window. */
    void (*inputProc)();		/* Procedure to call when input becomes
					 * available from typescript.   This
					 * argument, and sizeChangeProc and
					 * closeProc and clientData, are
					 * ignored if this isn't the first
					 * window on the typescript. */
    void (*sizeChangeProc)();		/* Procedure to call when size of
					 * typescript changes.   NULL means
					 * no callback. */
    void (*closeProc)();		/* Procedure to call when the last
					 * window for this typescript is
					 * deleted.   NULL means no callback. */
    ClientData clientData;		/* Arbitrary argument to pass to
					 * inputProc and closeProc. */
{
    register MxWindow *mxwPtr;
    int titleHeight, i, result;
    MxFileInfo *fileInfoPtr;
    CmdInfo *cmd;
    char *s, string[200], msg[300], *rightTitle;
    caddr_t data;
    XGCValues gcValues;

    if (!init) {
	TxInit(display);
    }

    /*
     * Create the MxWindow structure and register it in the table
     * of all typescripts.  If there was already an MxWindow here,
     * delete it first, then create a new one.
     */

    if (XFindContext(display, window, txContext, &data) == 0) {
	mxwPtr = (MxWindow *) data;
	TxDestroy(mxwPtr);
    }
    mxwPtr = (MxWindow *) malloc(sizeof(MxWindow));
    bzero((char *) mxwPtr, sizeof(MxWindow));
    XSaveContext(display, window, txContext, (caddr_t) mxwPtr);

    /*
     * Create an MxFileInfo structure if this is a new typescript.
     */

    if (infoPtr->source != NULL) {
	MxWindow *mxwPtr2;

	if (XFindContext(display, infoPtr->source, txContext, &data) != 0) {
	    Sx_Panic(display,
		    "Tx_Make: source referred to non-existent typescript.");
	}
	mxwPtr2 = (MxWindow *) data;
	fileInfoPtr = mxwPtr2->fileInfoPtr;
    } else {
	fileInfoPtr = (MxFileInfo *) malloc(sizeof(MxFileInfo));
	bzero((char *) fileInfoPtr, sizeof(MxFileInfo));
	fileInfoPtr->file = Mx_FileLoad((char *) NULL);
	fileInfoPtr->name = NULL;
	fileInfoPtr->lastMod = 0;
	fileInfoPtr->log = NULL;
	fileInfoPtr->hlPtr = NULL;
	fileInfoPtr->mxwPtr = NULL;
	fileInfoPtr->nextPtr = txFileListPtr;
	txFileListPtr = fileInfoPtr;
	fileInfoPtr->flags = 0;
	fileInfoPtr->caretFirst = Mx_ZeroPosition;
	fileInfoPtr->caretLast = Mx_ZeroPosition;
	(void) Mx_FloaterCreate(fileInfoPtr->file,
	    &fileInfoPtr->caretFirst, &fileInfoPtr->caretLast);
	(void) Mx_SpyCreate(fileInfoPtr->file, MX_BEFORE,
		MxCaretRedisplayText, (ClientData) fileInfoPtr);
	fileInfoPtr->openParenPtr = NULL;
	fileInfoPtr->closeParenPtr = NULL;
	fileInfoPtr->inputProc = inputProc;
	fileInfoPtr->sizeChangeProc = sizeChangeProc;
	fileInfoPtr->closeProc = closeProc;
	fileInfoPtr->clientData = clientData;
	fileInfoPtr->viLines = -1;
    }
    mxwPtr->nextPtr = fileInfoPtr->mxwPtr;
    fileInfoPtr->mxwPtr = mxwPtr;
    mxwPtr->fileInfoPtr = fileInfoPtr;

    /*
     * Initialize information used for displaying:
     */

    mxwPtr->fontPtr = infoPtr->fontPtr;
    mxwPtr->foreground = infoPtr->foreground;
    mxwPtr->background = infoPtr->background;
    mxwPtr->border = infoPtr->border;
    mxwPtr->titleForeground = infoPtr->titleForeground;
    mxwPtr->titleBackground = infoPtr->titleBackground;
    mxwPtr->titleStripe = infoPtr->titleStripe;
    mxwPtr->sbForeground = infoPtr->sbForeground;
    mxwPtr->sbBackground = infoPtr->sbBackground;
    mxwPtr->sbElevator = infoPtr->sbElevator;
    mxwPtr->cursor = txCursor;
    gcValues.foreground = mxwPtr->foreground;
    gcValues.background = mxwPtr->background;
    gcValues.font = mxwPtr->fontPtr->fid;
    mxwPtr->textGc = XCreateGC(display, window,
	    GCForeground|GCBackground|GCFont, &gcValues);
    mxwPtr->grayGc = None;
    mxwPtr->reverseGc = None;
    mxwPtr->iconPixmap = None;
    mxwPtr->cursorMode = VIBLOCK;

    /*
     * Initialize stuff related to command interpretation.
     */

    mxwPtr->cmdTable = Cmd_TableCreate();
    mxwPtr->interp = Tcl_CreateInterp();
    for (cmd = commands; cmd->name != NULL; cmd++) {
	Tcl_CreateCommand(mxwPtr->interp, cmd->name, cmd->proc,
		(ClientData) mxwPtr, (void (*)()) NULL);
    }
    for (i = 1; i <= 0177; i++) {
	char string[2];

	string[0] = i;
	string[1] = 0;
	Cmd_BindingCreate(mxwPtr->cmdTable, string, "!@");
    }

    mxwPtr->quoteFlag = 0;
    mxwPtr->cmdString = NULL;
    mxwPtr->searchString = NULL;
    mxwPtr->replaceString = NULL;
    mxwPtr->msgString = NULL;

    /*
     * Tx doesn't do any history tracing (only Mx does this).
     */

    mxwPtr->histEnabled = -1;

    /*
     * Initialize subwindow information.  The title is a bit tricky:
     * when opening an alternate window, must redo the title of the
     * parent if this is the first alternate, in order to distinguish
     * them.
     */

    titleHeight = Sx_DefaultHeight(display, infoPtr->fontPtr);
    mxwPtr->display = display;
    mxwPtr->w = window;
    if (infoPtr->source != NULL) {
	rightTitle = "Alternate";
    } else {
	rightTitle = NULL;
    }
    if (infoPtr->flags & TX_NO_TITLE) {
	mxwPtr->title = NULL;
    } else {
	mxwPtr->title = Sx_TitleCreate(display, mxwPtr->w, SX_TOP,
		titleHeight, 2, infoPtr->fontPtr,
		infoPtr->titleForeground, infoPtr->titleBackground,
		infoPtr->titleStripe, (char *) NULL, infoPtr->title,
		rightTitle);
    }
    Tcl_SetVar(mxwPtr->interp, "title", infoPtr->title, 1);
    mxwPtr->menuBar = Sx_CreatePacked(display, mxwPtr->w, SX_TOP,
	    titleHeight, 0, 1, mxwPtr->border, None,
	    mxwPtr->background);
    mxwPtr->msgWindow = Sx_CreatePacked(display, mxwPtr->w, SX_TOP,
	    titleHeight, 0, 1, mxwPtr->border, None,
	    mxwPtr->background);
    mxwPtr->searchContainer = NULL;
    mxwPtr->searchWindow = NULL;
    mxwPtr->replaceWindow = NULL;
    mxwPtr->cmdWindow = NULL;
    mxwPtr->scrollbar = Sx_ScrollbarCreate(display, mxwPtr->w,
	    SX_RIGHT, 1, mxwPtr->sbForeground, mxwPtr->sbBackground,
	    mxwPtr->sbElevator, mxwPtr->border, MxScroll,
	    (ClientData) mxwPtr);
    mxwPtr->fileWindow = Sx_CreatePacked(display, mxwPtr->w, SX_TOP,
	    0, 1, 0, mxwPtr->border, None, mxwPtr->background);
    
    /*
     * Initialize other, random, information about window.
     */
    
    mxwPtr->flags = TX_WINDOW;
    if (infoPtr->source == NULL) {
	mxwPtr->flags |= TX_MAIN_WINDOW;
    }
    sprintf(string, "%d", (int) mxwPtr->w);
    Tcl_SetVar(mxwPtr->interp, "windowId", string, 1);

    /*
     * Note: TX_VERSION should be defined with a "-D" switch in the Makefile.
     */

    Tcl_SetVar(mxwPtr->interp, "version", TX_VERSION, 1);

    mxwPtr->width = infoPtr->width - Sx_ScrollbarWidth() - 1;
    mxwPtr->height = infoPtr->height - 2*titleHeight - 2;
    if (mxwPtr->title != NULL) {
	mxwPtr->height -= titleHeight + 2;
    }
    MxSetupRedisplay(mxwPtr);
    XDefineCursor(display, mxwPtr->w, mxwPtr->cursor);
    XSetWindowBorder(display, mxwPtr->w, infoPtr->border);
    Sx_EnableFocus(display, mxwPtr->w, mxwPtr->fileWindow,
	    MxFocusProc, (ClientData) mxwPtr);
    (void) Sx_HandlerCreate(display, mxwPtr->fileWindow, KeyPressMask,
	    MxKeyProc, (ClientData) mxwPtr);
    (void) Sx_HandlerCreate(display, mxwPtr->fileWindow, ButtonPressMask
	    |ButtonReleaseMask|Button1MotionMask|Button3MotionMask,
	    MxMouseProc, (ClientData) mxwPtr);
    (void) Sx_HandlerCreate(display, mxwPtr->fileWindow, StructureNotifyMask,
	    TxStructureProc, (ClientData) mxwPtr);
    Sx_Focus(display, mxwPtr->fileWindow);
    XStoreName(mxwPtr->display, mxwPtr->w, infoPtr->title);
    XSetIconName(mxwPtr->display, mxwPtr->w, infoPtr->title);

    /*
     * Read the system's default .tx file, then read .tx files from
     * the home directory and the current directory, if they exist.
     */

    sprintf(string, "source %.150s/default.tx", TX_LIB_DIR);
    result = MxDoCmd(mxwPtr, string);
    if (result != TCL_OK) {
	sprintf(msg, "Error executing \"%.50s/default.tx\": %.200s",
		TX_LIB_DIR, mxwPtr->interp->result);
	(void) Sx_Notify(mxwPtr->display,  DefaultRootWindow(mxwPtr->display),
		-1, -1, 0, msg, mxwPtr->fontPtr, 1, "Quit", (char *) NULL);
	XDestroyWindow(mxwPtr->display, mxwPtr->w);
	mxwPtr->flags |= DESTROYED;
    }
    s = getenv("HOME");
    if (s != NULL) {
	sprintf(string, "%.150s/.tx", s);
	if (access(string, R_OK) == 0) {
	    sprintf(string, "source %.150s/.tx", s);
	    result = MxDoCmd(mxwPtr, string);
	}
    }
    if (result == TCL_OK) {
	if (access(".tx", R_OK) == 0) {
	    struct stat homeStat, cwdStat;

	    /*
	     * Don't process the .tx file in the current directory if
	     * the current directory is the same as the home directory:
	     * it will already have been processed above.
	     */

	    (void) stat(s, &homeStat);
	    (void) stat(".", &cwdStat);
	    if (bcmp((char *) &homeStat, (char *) &cwdStat,
		    sizeof(cwdStat))) {
		result = MxDoCmd(mxwPtr, "source .tx");
	    }
	}
    }

    /*
     * Display a message with the current version number, unless an
     * error occurred in one of the startup files.  We return TCL_OK
     * even if an error occurred, because the error is already
     * displayed in the new window:  no need to scare the parent.
     */

    if (result != TCL_OK) {
	return TCL_OK;
    }
    sprintf(msg, "Tx version %s", TX_VERSION);
    MxOutputMsg(mxwPtr, msg);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * Tx_Output --
 *
 *	Output characters into a typescript.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The characters in string get added to the typescript starting
 *	at the current cursor position.  The position of the cursor
 *	gets updated.  Cursor-motion characters embedded in the output
 *	string get processed specially, as does the bell character
 *	(^G).
 *
 * Design:
 *	Several programs like vi advance the cursor across a line by
 *	outputting a character identical to what's already in place.
 *	To avoid overhead for these operations, the code below tries
 *	to separate out actions that only move the cursor.  For these
 *	things, the cursor is moved without modifying the characters.
 *	In addition, the code tries to process characters in bunches,
 *	to avoid redisplay overhead.  It saves up a buffer-full of
 *	characters that can all be output together, and then inserts
 *	them in the underlying file in a single operation.
 *
 *----------------------------------------------------------------------
 */

void
Tx_Output(fileInfoPtr, string)
    register MxFileInfo *fileInfoPtr;	/* Where to output characters. */
    register char *string;		/* NULL-terminated string of characters
					 * to output to mxwPtr.  May contain
					 * cursor-motion characters: ^H, ^M,
					 * and ^J. */
{
#define BUFSIZE 2048
    char buffer[BUFSIZE];	/* Used to buffer up output characters. */
    register int next;		/* Next place in buffer to put character. */
    Mx_Position caret;		/* Within this procedure, this variable
				 * keeps track of where the caret's supposed
				 * to be.  At the end of the procedure, the
				 * actual caret gets updated to this
				 * position. */
    Mx_Position firstPos;	/* Position in file corresponding to
				 * buffer[0]. */
    register MxWindow *mxwPtr;
    register char c;

    /*
     * Remember which windows the caret was visible in, so that we
     * can keep the caret visible in them by scrolling later.
     */

    caret = fileInfoPtr->caretFirst;
    for (mxwPtr = fileInfoPtr->mxwPtr; mxwPtr != NULL;
	    mxwPtr = mxwPtr->nextPtr) {
	if ((mxwPtr->heightLines != 0)
		&& (caret.lineIndex >= mxwPtr->linePtr[0].position.lineIndex)
		&& (caret.lineIndex <=
		mxwPtr->linePtr[mxwPtr->heightLines-1].position.lineIndex)) {
	    mxwPtr->flags |= CARET_VISIBLE;
	}
    }
    mxwPtr = fileInfoPtr->mxwPtr;

    while (*string != 0) {
	int lineLength, viLines, toDelete;
	char *line;
	Mx_Position lastToReplace;

	/*
	 * Process the string in one or more clumps.  Each clump
	 * consists of:
	 * 1. one or more characters or control-chars, which move
	 *    the cursor but don't affect the contents of the file.
	 * 2. some characters to add to the file, potentially
	 *    replacing a portion of the existing file.
	 */

	/*
	 * Update cursor position for each character, until a character
	 * is found that modifies the file.  No modification occurs as
	 * long as the output character is a cursor control character
	 * or a duplicate of what's already in the file.
	 */
	 
	for (c = *string; c != 0; string++, c = *string) {
	    if (c < '\40') {
		if (c == '\15') {
		    caret.charIndex = 0;
		    continue;
		} else if (c == '\n') {
		    /*
		     * Newline is tricky in vi mode:  if it causes the cursor
		     * to move off the bottom of the "screen", add a new blank
		     * line and delete the topmost line (sort of like scrolling
		     * the screen up one line).
		     */

		    if (caret.lineIndex == (fileInfoPtr->viLines-1)) {
			TxReplaceLines(mxwPtr, fileInfoPtr->viLines,
				0, 1);
			TxReplaceLines(mxwPtr, 0, 1, 0);
		    } else {
			caret.lineIndex++;
		    }
		    continue;
		} else if (c == '\b') {
		    if (caret.charIndex > 0) {
			caret.charIndex -= 1;
			if (fileInfoPtr->viLines < 0) {
			    caret = TxBackspace(fileInfoPtr, caret);
			}
		    }
		    continue;
		} else if (c == '\7') {
		    XBell(mxwPtr->display, 50);
		    continue;
		}
	    }
	    
	    line = Mx_GetLine(fileInfoPtr->file, caret.lineIndex,
		    &lineLength);
	    if ((line == NULL) || (lineLength <= caret.charIndex)
		    || (line[caret.charIndex] != c)) {
		break;
	    }
	    caret.charIndex++;
	}

	/*
	 * Now find a range of characters that can all be output as a bunch,
	 * replacing what was already in the file.
	 */

	firstPos = caret;
	for (next = 0; (c != 0) && (next < BUFSIZE-1);
		string++, c = *string, next++) {
	    if (c < '\40') {
		/*
		 * The only cursor motion that can be handled here
		 * is a carriage-return immediately followed by a
		 * line-feed.  Even this can only be handled if there
		 * aren't any dangling characters left in the file at
		 * the end of the previous line.
		 */
		 
		if ((c == '\n') || (c == '\b') || (c == '\7')) {
		    break;
		}
		if (c == '\15') {
		    if (string[1] != '\n') {
			break;
		    }
		    line = Mx_GetLine(fileInfoPtr->file,
			    caret.lineIndex, &lineLength);
		    if ((line != NULL) && (lineLength > caret.charIndex)) {
			break;
		    }
		    string++;
		    buffer[next] = '\n';
		    caret.lineIndex++;
		    caret.charIndex = 0;
		    continue;
		}
	    }
	    buffer[next] = c;
	    caret.charIndex++;
	}

	/*
	 * Now output the contents of the buffer.  There are three potential
	 * problems.  First, the caret may have moved around a lot. In
	 * fact, the position to which it has moved may not even exist.
	 * Second, the new bytes have to replace any old bytes that are
	 * in the way.  Depending on where we are, there may or may not
	 * be old bytes in the way.  Third, the caret may have moved
	 * off the bottom of the "screen" (in vi mode).  When this happens,
	 * delete topmost lines in the typescript to simulate scrolling
	 * (also, don't replace lines outside the "screen" range).
	 */

	buffer[next] = 0;
	TxMakePosExist(fileInfoPtr->file, firstPos);
	if (Mx_GetLine(fileInfoPtr->file, caret.lineIndex,
		&lineLength) == NULL) {
	    lastToReplace = Mx_EndOfFile(fileInfoPtr->file);
	} else {
	    lastToReplace = caret;
	    if (lastToReplace.charIndex >= lineLength) {
		lastToReplace.charIndex = lineLength-1;
	    }
	}
	viLines = fileInfoPtr->viLines;
	if ((viLines > 0) && (caret.lineIndex >= viLines)) {
	    toDelete = caret.lineIndex - (viLines - 1);
	    lastToReplace.lineIndex = viLines;
	    lastToReplace.charIndex = 0;
	    lastToReplace = Mx_Offset(fileInfoPtr->file,
		    lastToReplace, -1);
	} else {
	    toDelete = 0;
	}
	Mx_ReplaceBytes(fileInfoPtr->file, firstPos,
		lastToReplace, buffer);
	if (toDelete != 0) {
	    TxReplaceLines(mxwPtr, 0, toDelete, 0);
	    caret.lineIndex -= toDelete;
	}
    }
    
    MxCaretSetPosition(mxwPtr, caret, 0);
    for (mxwPtr = fileInfoPtr->mxwPtr; mxwPtr != NULL;
	    mxwPtr = mxwPtr->nextPtr) {
	if (mxwPtr->flags & CARET_VISIBLE) {
	    mxwPtr->flags &= ~CARET_VISIBLE;
	    if (fileInfoPtr->viLines > 0) {
		MxGetInWindow(mxwPtr, Mx_ZeroPosition, 0, 1);
	    } else {
		MxGetInWindow(mxwPtr, caret, mxwPtr->heightLines-1, 0);
	    }
	}
    }

    if (fileInfoPtr->viLines >= 0) {
	return;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * Tx_Command --
 *
 *	Invoke a Tx command on a particular window.
 *
 * Results:
 *	Returns a Tcl result:  TCL_OK, TCL_ERROR, etc..
 *
 * Side effects:
 *	Whatever the command does:  could be almost arbitrary.
 *
 *----------------------------------------------------------------------
 */

int
Tx_Command(display, window, command)
    Display *display;		/* Connection to server. */
    Window window;		/* Window to which command pertains. */
    char *command;		/* Null-terminated command. */
{
    MxWindow *mxwPtr;
    caddr_t data;

    if (!init) {
	TxInit(display);
    }
    if (XFindContext(display, window, txContext, &data) != 0) {
	Sx_Panic(display, "Tx_Command: window isn't a typescript.");
    }
    mxwPtr = (MxWindow *) data;
    return MxDoCmd(mxwPtr, command);
}

/*
 *----------------------------------------------------------------------
 *
 * Tx_Update --
 *
 *	Update the screen.  If any changes have been made to any
 *	Tx window since the last time this procedure was called,
 *	they've probably not been displayed properly on the screen.
 *	This procedure corrects everything on the screen to reflect
 *	the current state of the world.  In addition, every once-
 *	in-a-while this routine checks the lengths of the windows
 *	and truncates all those that have more than a threshold
 *	number of lines.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Information on the screen gets redisplayed.  Windows with
 *	huge amounts of information in them get truncated.
 *
 *----------------------------------------------------------------------
 */

void
Tx_Update()
{
#define MAX_COUNT 100
#define MAX_LINES 2000
    register MxFileInfo *infoPtr;
    register MxWindow *mxwPtr;
    Mx_Position pos;
    static int count = 0;

    count += 1;
    if (count > MAX_COUNT) {
	count = 0;
    }
    for (infoPtr = txFileListPtr; infoPtr != NULL;
	    infoPtr = infoPtr->nextPtr) {

	if (infoPtr->viLines < 0) {
	    /*
	     * Check the typescript's length every once in a while
	     * and truncate it when it gets too long.
	     */
    
	    if (count == MAX_COUNT) {
		pos = Mx_EndOfFile(infoPtr->file);
		if (pos.lineIndex >= MAX_LINES) {
		    pos.lineIndex -= MAX_LINES;
		    pos.charIndex = 0;
		    Mx_ReplaceBytes(infoPtr->file, Mx_ZeroPosition, pos,
			    (char *) NULL);
		}
	    }
	}
	for (mxwPtr = infoPtr->mxwPtr; mxwPtr != NULL;
		mxwPtr = mxwPtr->nextPtr) {
	    if ((mxwPtr->flags & NEEDS_UPDATE)
		    && !(mxwPtr->flags & DESTROYED)) {
		MxUpdateWindow(mxwPtr);
		if (mxwPtr->flags & FOCUS_WINDOW) {
		    MxDisplayCaret(mxwPtr);
		}
	    }
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * TxMakePosExist --
 *
 *	Add newlines and spaces to a file to guarantee that a
 *	particular position exists.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The file gets modified.
 *
 *----------------------------------------------------------------------
 */

void
TxMakePosExist(file, position)
    Mx_File file;		/* File to modify (if necessary). */
    Mx_Position position;	/* Position to make exist. */
{
    int length, count;

    /*
     * First of all, make sure that the line exists.
     */

    if (Mx_GetLine(file, position.lineIndex, &length) == NULL) {
	Mx_Position eof;
	
	eof = Mx_EndOfFile(file);
	TxInsertMultiple(file, eof, position.lineIndex - eof.lineIndex,
		'\n');
	length = 1;
    }

    /*
     * Then add spaces to the end of the line, if needed.
     */

    count = position.charIndex + 1 - length;
    if (count > 0) {
	Mx_Position insert;
	
	insert.lineIndex = position.lineIndex;
	insert.charIndex = length-1;
	TxInsertMultiple(file, insert, count, ' ');
    }
}

/*
 *----------------------------------------------------------------------
 *
 * TxBackspace --
 *
 *	This procedure handles backspacing for Tx windows that are in
 *	normal typescript (not vi) mode.  The only reason that this is
 *	tricky is tabs: backspace must not go over the whole tab, but
 *	just one character position.
 *
 * Results:
 *	The return value is the position of the character that was
 *	backspaced over, which is the same as pos unless a tab was
 *	replaced with a bunch of spaces as part of the backspace
 *	operation.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static Mx_Position
TxBackspace(fileInfoPtr, pos)
    register MxFileInfo *fileInfoPtr;	/* File in which to backspace. */ 
    Mx_Position pos;			/* The character to be backspaced
					 * over. */
{
    char *line, c;
    int lineLength, i, count;
    static char spaces[] = "        ";

    /*
     * If the character being backspaced over isn't a tab, then everything's
     * easy:  just delete the character and return.  If the character being
     * backspaced over is a tab, replace it with just enough characters
     * to move the caret left one position.
     */

    line = Mx_GetLine(fileInfoPtr->file, pos.lineIndex, &lineLength);
    if ((line == NULL) || (lineLength <= pos.charIndex)) {
	return pos;
    }
    if (line[pos.charIndex] != '\t') {
	return pos;
    }
    count = 0;
    for (i = 0; i < pos.charIndex; i++) {
	c = line[i];
	if (isprint(c)) {
	    count += 1;
	} else if (c == '\t') {
	    count = (count + 8) & ~07;
	} else {
	    count += 2;
	}
    }
    count = 7 - (count & 07);
    spaces[count] = 0;
    Mx_ReplaceBytes(fileInfoPtr->file, pos,
	    Mx_Offset(fileInfoPtr->file, pos, 1), spaces);
    spaces[count] = ' ';
    pos.charIndex += count;
    return pos;
}

/*
 *----------------------------------------------------------------------
 *
 * TxInsertMultiple --
 *
 *	Insert many characters, all the same, at a given point in a
 *	file.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Count copies of the character c are added to file at position.
 *
 *----------------------------------------------------------------------
 */

static void
TxInsertMultiple(file, position, count, c)
    Mx_File file;		/* File to modify. */
    Mx_Position position;	/* Where to add the characters. */
    int count;			/* How many copies of the character to add. */
    char c;			/* Character to add count times over. */
{
#define MAXATONCE 128
    char buffer[MAXATONCE+1];
    int thisBatch;
    register char *p;

    while (count > 0) {
	thisBatch = count;
	if (thisBatch > MAXATONCE) {
	    thisBatch = MAXATONCE;
	}
	count -= thisBatch;
	for (p = buffer; thisBatch > 0; thisBatch--, p++) {
	    *p = c;
	}
	*p = 0;
	Mx_ReplaceBytes(file, position, position, buffer);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * TxGetMxWindow --
 *
 *	Given an X window id for a typescript window, returns the
 *	address of the MxWindow data structure for the window.
 *
 * Results:
 *	The return value is the address of the MxWindow corresponding
 *	to window, or NULL if there's no existing MxWindow for window.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

MxWindow *
TxGetMxWindow(display, window)
    Display *display;			/* Connection to server. */
    Window window;			/* X window id for Mx window. */
{
    caddr_t data;

    if (!init) {
	return NULL;
    }
    if (XFindContext(display, window, txContext, &data) != 0) {
	return NULL;
    }
    return (MxWindow *) data;
}

/*
 *----------------------------------------------------------------------
 *
 * TxDestroy --
 *
 *	Release all the Tx-related resources associated with a window.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Clean up the MxWindow structure and recycle its space.
 *	May kill child process executing in window, if one still exists.
 *
 *----------------------------------------------------------------------
 */

static void
TxDestroy(mxwPtr)
    register MxWindow *mxwPtr;		/* Window to clean up. */
{
    register MxFileInfo *fileInfoPtr;
    register MxWindow *mxwPtr2;

    XDeleteContext(mxwPtr->display, mxwPtr->w, txContext);
    XFreeGC(mxwPtr->display, mxwPtr->textGc);
    if (mxwPtr->grayGc != None) {
	XFreeGC(mxwPtr->display, mxwPtr->grayGc);
    }
    if (mxwPtr->reverseGc != None) {
	XFreeGC(mxwPtr->display, mxwPtr->reverseGc);
    }
    MxCleanupRedisplay(mxwPtr);
    Cmd_TableDelete(mxwPtr->cmdTable);
    Tcl_DeleteInterp(mxwPtr->interp);
    if (mxwPtr->cmdString != NULL) {
	free((char *) mxwPtr->cmdString);
    }
    if (mxwPtr->searchString != NULL) {
	free((char *) mxwPtr->searchString);
    }
    if (mxwPtr->replaceString != NULL) {
	free((char *) mxwPtr->replaceString);
    }
    if (mxwPtr->msgString != NULL) {
	free((char *) mxwPtr->msgString);
    }

    /*
     * Sx handlers need not be deleted:  Sx automatically deletes
     * them when the window goes away.
     */

    /*
     * Remove from list of windows on file.  If last window on file,
     * recycle file information also.
     */

    fileInfoPtr = mxwPtr->fileInfoPtr;
    if (fileInfoPtr->caretWindow == mxwPtr) {
	fileInfoPtr->caretWindow = NULL;
    }
    if (fileInfoPtr->mxwPtr == mxwPtr) {
	fileInfoPtr->mxwPtr = mxwPtr->nextPtr;
    } else {
	for (mxwPtr2 = fileInfoPtr->mxwPtr; mxwPtr2 != NULL;
		mxwPtr2 = mxwPtr2->nextPtr) {
	    if (mxwPtr2->nextPtr == mxwPtr) {
		mxwPtr2->nextPtr = mxwPtr->nextPtr;
		break;
	    }
	}
    }
    if (fileInfoPtr->mxwPtr == NULL) {
	if (fileInfoPtr == MxSelectedFile) {
	    Sx_SelectionClear(mxwPtr->display);
	}
	if (txFileListPtr == fileInfoPtr) {
	    txFileListPtr = fileInfoPtr->nextPtr;
	} else {
	    register MxFileInfo *fileInfoPtr2;
	    for (fileInfoPtr2 = txFileListPtr; fileInfoPtr2 != NULL;
		    fileInfoPtr2 = fileInfoPtr2->nextPtr) {
	        if (fileInfoPtr2->nextPtr == fileInfoPtr) {
		    fileInfoPtr2->nextPtr = fileInfoPtr->nextPtr;
		    break;
		}
	    }
	}
	while (fileInfoPtr->hlPtr != NULL) {
	    MxHighlightDelete(fileInfoPtr->hlPtr);
	}
	Mx_FileClose(fileInfoPtr->file);

	/*
	 * The spies and floaters on this file need not be deleted:
	 * they're deleted automatically when the file goes away.
	 */

	if (fileInfoPtr->closeProc != NULL) {
	    (*fileInfoPtr->closeProc)(fileInfoPtr->clientData);
	}
	free((char *) fileInfoPtr);
    } else {
	/*
	 * We may need to retitle existing windows.  If the deleted
	 * window used to be the primary window, find an alternate and
	 * make it the primary instead.  Then propagate its size to
	 * applications.
	 */

	for (mxwPtr2 = fileInfoPtr->mxwPtr; mxwPtr2 != NULL;
		mxwPtr2 = mxwPtr2->nextPtr) {
	    if (!(mxwPtr2->flags & DESTROYED)) {
		break;
	    }
	}
	if ((mxwPtr2 != NULL) && (mxwPtr->flags & TX_MAIN_WINDOW)) {
	    struct winsize sizeInfo;

	    mxwPtr2->flags |= TX_MAIN_WINDOW;
	    if (mxwPtr2->title != NULL) {
		Sx_TitleMake(mxwPtr2->display, mxwPtr2->title,
			mxwPtr2->fontPtr, mxwPtr2->titleForeground,
			mxwPtr2->titleBackground,
			mxwPtr2->titleStripe, (char *) NULL,
			Tcl_GetVar(mxwPtr2->interp, "title", 1),
			(char *) NULL);
	    }
	    sizeInfo.ws_row = mxwPtr2->heightLines;
	    sizeInfo.ws_col =
		    mxwPtr2->width/mxwPtr2->fontPtr->max_bounds.width;
	    sizeInfo.ws_xpixel = mxwPtr2->width;
	    sizeInfo.ws_ypixel = mxwPtr2->height;
	    (*fileInfoPtr->sizeChangeProc)(fileInfoPtr->clientData,
		    &sizeInfo);
	}
    }

    free((char *) mxwPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * TxStructureProc --
 *
 *	This procedure is invoked each time a Tx window is resized
 *	or deleted.  It resets the "termcap" variable to reflect the
 *	window's current size, or cleans things up if the window was
 *	just deleted.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The "termcap" variable may get modified.
 *
 *----------------------------------------------------------------------
 */

static void
TxStructureProc(mxwPtr, eventPtr)
    register MxWindow *mxwPtr;		/* Window that was resized. */
    XEvent *eventPtr;			/* What happened. */
{
    char tc[400];
    struct winsize sizeInfo;

    if (eventPtr->type == DestroyNotify) {
	TxDestroy(mxwPtr);
    } else if (eventPtr->type == ConfigureNotify) {
	sizeInfo.ws_row = eventPtr->xconfigure.height/
		(mxwPtr->fontPtr->ascent + mxwPtr->fontPtr->descent);
	sizeInfo.ws_col =
		eventPtr->xconfigure.width/mxwPtr->fontPtr->max_bounds.width;
	sizeInfo.ws_xpixel = eventPtr->xconfigure.width;
	sizeInfo.ws_ypixel = eventPtr->xconfigure.width;
	sprintf(tc, tcFormat, sizeInfo.ws_col, sizeInfo.ws_row,
		tc1, tc2, tc3, tc4, tc5, tc6, tc7, tc8);
	Tcl_SetVar(mxwPtr->interp, "termcap", tc, 1);
	if ((mxwPtr->flags & TX_MAIN_WINDOW)
		&& (mxwPtr->fileInfoPtr->sizeChangeProc != NULL)) {
	    (*mxwPtr->fileInfoPtr->sizeChangeProc)(
		    mxwPtr->fileInfoPtr->clientData, &sizeInfo);
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * TxInit --
 *
 *	Once-only initialization for Tx widgets.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Create the Tx context, cursors, etc.
 *
 *----------------------------------------------------------------------
 */

static void
TxInit(display)
    Display *display;		/* Connection to server. */
{
    static XColor black = {0, 0, 0, 0, 0};
    static XColor white = {0, ~0, ~0, ~0, 0};
    Pixmap source, mask;

    init = 1;
    txContext = XUniqueContext();

    source = XCreateBitmapFromData(display, DefaultRootWindow(display),
	    cursor_bits, cursor_width, cursor_height);
    mask = XCreateBitmapFromData(display, DefaultRootWindow(display),
	    cursMask_bits, cursMask_width, cursMask_height);
    txCursor = XCreatePixmapCursor(display, source, mask, &black, &white,
	    cursor_x_hot, cursor_y_hot);
    XFreePixmap(display, source);
    XFreePixmap(display, mask);
}

/*
 *----------------------------------------------------------------------
 *
 * Tx_WindowEventProc --
 *
 *	This procedure is invoked by the Fs dispatcher whenever the
 *	X connection has data waiting on it.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Read the event and dispatch using the Sx dispatcher.
 *
 *----------------------------------------------------------------------
 */

void
Tx_WindowEventProc(display)
    Display *display;			/* Connection to X server. */
{
    XEvent event;

    do {
	XNextEvent(display, &event);
	Sx_HandleEvent(&event);
    } while (QLength(display) > 0);
}
