/* Extended by Albert Fl"ugel, originally written by
 * Dan Heller and Paula Ferguson.  
 * Copyright 1994 O'Reilly & Associates, Inc.
 * Permission to use, copy, and modify this program without
 * restriction is hereby granted, as long as this copyright
 * notice appears in each copy of the program source code.
 * This program is freely distributable without licensing fees and
 * is provided without guarantee or warrantee expressed or implied.
 * This program is -not- in the public domain.
 */

/* editor.c -- create a full-blown Motif editor application complete
 * with a menubar, facilities to read and write files, text find
 * and replace, clipboard support and so forth.
 */
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#if !defined(sgi) && !defined(__QNX__) && !defined(linux)
#include <sys/mode.h>
#endif
#include <signal.h>

#include <Xm/Text.h>
#include <Xm/TextF.h>
#include <Xm/LabelG.h>
#include <Xm/Label.h>
#include <Xm/CascadeB.h>
#include <Xm/ToggleBG.h>
#include <Xm/PushBG.h>
#include <Xm/PushB.h>
#include <Xm/RowColumn.h>
#include <Xm/Protocols.h>
#include <Xm/MainW.h>
#include <Xm/PanedW.h>
#include <Xm/Form.h>
#include <Xm/FileSB.h>
#include <Xm/DialogS.h>
#include <Xm/ToggleBG.h>
#include <X11/Xos.h>

char	**extras_names = NULL;
char	**extras_cmds = NULL;
int	num_extras = 0;

Widget	text_edit, find_text, replace_text, lineno_text, text_output,
	toplevel, no_backup_button, autoindent_button, readonly_button,
	wraplines_button, find_menu, find_pair_dialog = NULL,
	find_pair_radio_box, find_pair_form, find_pair_right_form,
	curly_sel, round_sel, ccommsel, other_sel, tmp_widget,
	pat_one_text, pat_two_text, find_pair_pane;
char	*act_filename = NULL;

#define	TEXT_EXTRAS_FILE	".textedit_extras"

#define FILE_OPEN	0
#define FILE_SAVE 	1 
#define	FILE_SAVE_AS	2
#define	FILE_INSERT	3
#define	FILE_EMPTY	4
#define FILE_EXIT 	5

#define	EDIT_CUT	0
#define EDIT_COPY	1
#define EDIT_PASTE	2
#define	EDIT_UNDO	3
#define EDIT_CLEAR	4

#define SEARCH_FIND_NEXT	0
#define	SEARCH_FIND_PREVIOUS	1
#define	SEARCH_REPLACE		2
#define	SEARCH_FIND_AND_REPLACE	3
#define	SEARCH_REPLACE_AND_FIND	4
#define SEARCH_SHOW_ALL		5
#define SEARCH_REPLACE_ALL	6
#define SEARCH_FIND_PAIR	7
#define	SEARCH_CLEAR		9999

#define	VIEW_WHAT_LINE		0
#define	VIEW_GOTO_LINE		1

#define	OPTIONS_NO_BACKUP	0

#define	INSERT_ACTION	1
#define	DELETE_ACTION	2
#define	CUT_ACTION	3
#define	PASTE_ACTION	4
#define	BACKSP_ACTION	5
#define	CUTPASTE_ACTION	6

#define	OPENING_DISPLAY	1

#define	TEXTRECFILE	0x01
#define	EDITRECFILE	0x02

int	max_undos = 100;

struct edit_record	{
	XmTextPosition	ins_start, ins_end, del_start, del_end;
	char		*del_string;
	int		del_strlen;
	char		*ins_string;
	int		ins_strlen;
	char		type;
};

struct edit_record	act_edit = {
	0, 0, 0, 0, "", 0, "", 0, INSERT_ACTION
	};

struct edit_record	*all_edits;

int	act_edit_nr = 0;
int	first_edit_nr = 0;
int	total_edits = 0;
int	this_is_no_edit = 0;
int	sth_highlighted = 0;

int	motion_from = -1;
int	motion_to = -1;
char	moved = 0;

int	textpos_to_move_to = 0;
int	a_file_is_to_rm = 0;

XmTextBlock	act_ins_text;

Boolean	auto_indent = False;
Boolean	readonly = False;
Boolean	no_backup = False;
mode_t	filemode = 0;

char	textdumpfile[256], editdumpfile[256], recovercmd[256];
char	*textrecfile, *editrecfile;
int	recover = 0;
Boolean	readonly_forced = 0;
int	initial_line = -1;

int	exitflag = 0;

int	*gargc;
char	**gargv;

void	pre_modify_cb(Widget, XtPointer, XtPointer);
void	post_modify_cb(Widget, XtPointer, XtPointer);
void	motion_cb(Widget, XtPointer, XtPointer);
void	set_title();
void	auto_indent_cb();
void	wraplines_cb();
void	readonly_cb();
void	usage(char *);
void	save_on_hup(int);
void	safe_exit(int);
void	emergency_exit(int);
void	maybe_exit();
int	read_file(char *, char *, int);
void	set_wm_interactions();
void	goto_line();
void	find_sel();
void	repl_sel();
void	termproc();
void	get_options();
void	get_extras_menu();
void	file_cb(), edit_cb(), find_cb(), view_cb(), no_backup_cb();
void	extras_proc();

String	transl = "<Btn2Down>: copy-primary()";
String	linnotransl = "<Key>Return: GotoLine()";
String	findtransl = "<Key>Return: FindSelection()";
String	repltransl = "<Key>Return: ReplaceWith()";

XtActionsRec	actions[] = { {"GotoLine", goto_line},
			{"FindSelection", find_sel},
			{"ReplaceWith", repl_sel}, };

Atom		wm_delete_window;
Atom		wm_protocols;

static char *
strrstr(char * string, char * substr)
{
  int	len, sublen;
  char	*cptr;

  len = strlen(string);
  sublen = strlen(substr);

  if(len < sublen || len == 0 || sublen == 0)
    return(NULL);

  for(cptr = string + len - sublen; cptr >= string; cptr--)
    if(*cptr == substr[0])
      if(!strncmp(cptr, substr, sublen))
	return(cptr);

  return(NULL);
}

#define	NUM_LOCSTR	20

static XmString	*locstrs = NULL;
static	int	num_locstrs = 0;

static	XmString
xmstr(char * str)		/* a very useful hack */
{
  int		i;
  XmString	*new_strs;

  if(!str){
    for(i = 0; i < num_locstrs; i++)
      if(locstrs[i])
	XmStringFree(locstrs[i]);
    if(locstrs)
      free(locstrs);
    locstrs = NULL;
    num_locstrs = 0;
    return(NULL);
  }

  new_strs = (XmString *) malloc((num_locstrs + 1) * sizeof(XmString));
  if(!new_strs)
    return(NULL);

  memcpy(new_strs, locstrs, sizeof(XmString) * num_locstrs);
  if(locstrs)
    free(locstrs);
  locstrs = new_strs;
  locstrs[num_locstrs] = XmStringCreateLocalized(str);
  if(!locstrs[num_locstrs])
    return(NULL);

  num_locstrs++;
  return(locstrs[num_locstrs - 1]);
}


main(argc, argv)
int argc;
char *argv[];
{
    XtAppContext  app_context;
    Widget	main_window, menubar, form, find_panel, dummy_widget;
    Arg		args[20];
    int		n = 0, i, j;
    XmString	open, save, exitstr, exit_acc, file, edit, cut,
		clear, copy, paste, find, next, replace, replace_all,
		open_acc, save_acc, save_as_acc, save_as, cut_acc,
		copy_acc, paste_acc, prev, find_next_acc,
		find_prev_acc, find_and_replace, replace_and_find,
		find_repl_acc, what_line, line_to_goto, what_line_acc,
		goto_line_acc, view, insert, empty, undo, undo_acc,
		tmpstr, option;
    char	buf[256], *cptr;
    Atom	WM_DELETE_WINDOW;
    XtTranslations	trsl;
    struct stat	statb;

    get_extras_menu();

    XtSetLanguageProc (NULL, NULL, NULL);

    strcpy(buf, argv[0]);
    cptr = strrchr(buf, '/');
    if(cptr)
	cptr++;
    else
	cptr = buf;

    if(cptr[0] >= 'a' && cptr[0] <= 'z')
	cptr[0] = cptr[0] - 'a' + 'A';

    atexit(termproc);
    exitflag = OPENING_DISPLAY;

    gargc = &argc;
    gargv = argv;

    toplevel = XtVaAppInitialize (&app_context, cptr,
        NULL, 0, &argc, argv, NULL, XmNdeleteResponse, XmDO_NOTHING,
	NULL);

    gargc = &argc;
    gargv = argv;

    exitflag = 0;

    XtAppAddActions(app_context, actions,
				sizeof(actions) / sizeof(*actions));

    set_title();

    XmRepTypeInstallTearOffModelConverter ();

    main_window = XtVaCreateWidget ("mainWindow",
        xmMainWindowWidgetClass, toplevel, NULL);

    /* Create a simple MenuBar that contains three menus */
    menubar = XmVaCreateSimpleMenuBar (main_window, "menubar",
        XmVaCASCADEBUTTON, xmstr("File"), 'l',
        XmVaCASCADEBUTTON, xmstr("Edit"), 'E',
        XmVaCASCADEBUTTON, xmstr("View"), 'i',
        XmVaCASCADEBUTTON, xmstr("Find"), 'f',
        NULL);

    /* First menu is the File menu -- callback is file_cb() */
    XmVaCreateSimplePulldownMenu (menubar, "fileMenu", 0, file_cb,
        XmVaPUSHBUTTON, xmstr("Open ..."), 'O',
			"Alt<Key>o", xmstr("Alt+O"),
        XmVaPUSHBUTTON, xmstr("Save ..."), 'S',
			"Alt<Key>s", xmstr("Alt+S"),
        XmVaPUSHBUTTON, xmstr("Save As ..."), 'A',
			"Alt<Key>a", xmstr("Alt+A"),
	XmVaPUSHBUTTON, xmstr("Insert ..."), 'I', NULL, NULL,
	XmVaPUSHBUTTON, xmstr("Empty Text"), 'E', NULL, NULL,
        /*XmVaSEPARATOR,*/
        XmVaPUSHBUTTON, xmstr("Exit"), 'x',
			"Alt<Key>F4"/*"Ctrl<Key>c"*/, xmstr("Alt+F4"),
        NULL);

    /* ...create the "Edit" menu --  callback is edit_cb() */
    XmVaCreateSimplePulldownMenu (menubar, "editMenu", 1, edit_cb,
        XmVaPUSHBUTTON, xmstr("Cut"), 't', "Alt<Key>x", xmstr("Alt+X"),
        XmVaPUSHBUTTON, xmstr("Copy"), 'C', "Alt<Key>c", xmstr("Alt+C"),
        XmVaPUSHBUTTON, xmstr("Paste"), 'P', "Alt<Key>v", xmstr("Alt+V"),
        XmVaPUSHBUTTON, xmstr("Undo"), 'U', "Alt<Key>z", xmstr("Alt+Z"),
/*        XmVaSEPARATOR,
        XmVaPUSHBUTTON, xmstr("Clear"), 'l', NULL, NULL,*/
        NULL);

    /* ...create the "View" menu --  callback is view_cb() */
    XmVaCreateSimplePulldownMenu (menubar, "viewMenu", 2, view_cb,
        XmVaPUSHBUTTON, xmstr("What Line ?"), 'W',
			"Alt<Key>w", xmstr("Alt+W"),
        XmVaPUSHBUTTON, xmstr("Goto Line"), 'G',
			"Alt<Key>g", xmstr("Alt+G"),
        NULL);

    /* create the "Find" menu -- callback is find_cb() */
    find_menu =
	XmVaCreateSimplePulldownMenu (menubar, "findMenu", 3, find_cb,
        XmVaPUSHBUTTON, xmstr("Find Next"), 'N',
			"Ctrl<Key>f", xmstr("Ctrl+F"),
        XmVaPUSHBUTTON, xmstr("Find Previous"), 'P',
			"Ctrl Shift<Key>f", xmstr("Ctrl+Shift+F"),
        XmVaPUSHBUTTON, xmstr("Replace"), 'R',
			"Ctrl <Key>r", xmstr("Ctrl+R"),
        XmVaPUSHBUTTON, xmstr("Find and Replace"), 'i', NULL, NULL,
        XmVaPUSHBUTTON, xmstr("Replace and Find"), 'e', NULL, NULL,
        XmVaPUSHBUTTON, xmstr("Show All"), 'A', NULL, NULL,
        XmVaPUSHBUTTON, xmstr("Replace All"), 'l', NULL, NULL,
        XmVaPUSHBUTTON, xmstr("Find Pair ..."), 'l', NULL, NULL,
        NULL);

    option = XmStringCreateLocalized("Options");

    dummy_widget = XmCreatePulldownMenu(menubar, "optionMenu", NULL, 0);
    XtVaCreateManagedWidget("Options",
	xmCascadeButtonWidgetClass,	menubar,
	XmNlabelString,			option,
	XmNmnemonic,			'p',
	XmNsubMenuId,			dummy_widget,
	NULL);
    XmStringFree(option);

#if 0
    tmpstr = XmStringCreateLocalized("Wrap Lines");
    wraplines_button = XtVaCreateManagedWidget("Wrap Lines",
		xmToggleButtonGadgetClass, dummy_widget, NULL);
    XmStringFree(tmpstr);
    XtAddCallback(wraplines_button, XmNvalueChangedCallback,
						wraplines_cb, (XtPointer) 0);
#endif
    tmpstr = XmStringCreateLocalized("Auto-Indent");
    autoindent_button = XtVaCreateManagedWidget("Auto-Indent",
		xmToggleButtonGadgetClass, dummy_widget, NULL);
    XmStringFree(tmpstr);
    XtAddCallback(autoindent_button, XmNvalueChangedCallback,
						auto_indent_cb, (XtPointer) 0);
    tmpstr = XmStringCreateLocalized("No Backup");
    no_backup_button = XtVaCreateManagedWidget("No Backup",
		xmToggleButtonGadgetClass, dummy_widget, NULL);
    XmStringFree(tmpstr);
    tmpstr = XmStringCreateLocalized("Read-Only");
    readonly_button = XtVaCreateManagedWidget("Read-Only",
		xmToggleButtonGadgetClass, dummy_widget, NULL);
    XmStringFree(tmpstr);
    XtAddCallback(readonly_button, XmNvalueChangedCallback,
						readonly_cb, (XtPointer) 0);

    if(num_extras > 0){
	dummy_widget = XmCreatePulldownMenu(menubar, "extrasMenu", NULL, 0);
	XtVaCreateManagedWidget("Extras",
		xmCascadeButtonWidgetClass,	menubar,
		XmNlabelString,			xmstr("Extras"),
		XmNsubMenuId,			dummy_widget,
		NULL);
	for(i = 0; i < num_extras; i++){
	  tmp_widget = XtVaCreateManagedWidget(extras_names[i],
			xmPushButtonGadgetClass, dummy_widget, NULL);
	  XtAddCallback(tmp_widget, XmNactivateCallback, extras_proc, (XtPointer) i);
	}
    }
    xmstr(NULL);

    XtManageChild (menubar);

    /* create a form work are */
    form = XtVaCreateWidget ("form",
        xmFormWidgetClass, main_window, NULL);

    /* create horizontal RowColumn inside the form */
    find_panel = XtVaCreateWidget ("findPanel",
        xmRowColumnWidgetClass, form,
        XmNorientation,     XmHORIZONTAL,
        XmNpacking,         XmPACK_TIGHT,
        XmNtopAttachment,   XmATTACH_FORM,
        XmNleftAttachment,  XmATTACH_FORM,
        XmNrightAttachment, XmATTACH_FORM,
        NULL);
    /* Create three TextField widgets with Labels... */
    XtVaCreateManagedWidget ("Search:",
        xmLabelGadgetClass, find_panel, NULL);
    find_text = XtVaCreateManagedWidget ("findText",
        xmTextFieldWidgetClass, find_panel, NULL);
    XtVaCreateManagedWidget (" Replace:",
        xmLabelGadgetClass, find_panel, NULL);
    replace_text = XtVaCreateManagedWidget ("replaceText",
        xmTextFieldWidgetClass, find_panel, NULL);
    XtVaCreateManagedWidget (" Line-Number:",
        xmLabelGadgetClass, find_panel, NULL);
    lineno_text = XtVaCreateManagedWidget ("linenoText",
        xmTextFieldWidgetClass, find_panel, NULL);
    XtManageChild (find_panel);

    text_output = XtVaCreateManagedWidget ("textOutput",
        xmTextFieldWidgetClass, form,
        XmNeditable,              False,
        XmNcursorPositionVisible, False,
        XmNshadowThickness,       0,
        XmNleftAttachment,        XmATTACH_FORM,
        XmNrightAttachment,       XmATTACH_FORM,
        XmNbottomAttachment,      XmATTACH_FORM,
        NULL);

    n = 0;
    XtSetArg (args[n], XmNrows,             10); n++;
    XtSetArg (args[n], XmNcolumns,          80); n++;
    XtSetArg (args[n], XmNeditMode,         XmMULTI_LINE_EDIT); n++;
    XtSetArg (args[n], XmNtopAttachment,    XmATTACH_WIDGET); n++;
    XtSetArg (args[n], XmNtopWidget,        find_panel); n++;
    XtSetArg (args[n], XmNleftAttachment,   XmATTACH_FORM); n++;
    XtSetArg (args[n], XmNrightAttachment,  XmATTACH_FORM); n++;
    XtSetArg (args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++;
    XtSetArg (args[n], XmNbottomWidget,     text_output); n++;
    text_edit = XmCreateScrolledText (form, "textEdit", args, n);
    XtAddCallback (text_edit, XmNmodifyVerifyCallback, pre_modify_cb, NULL);
    XtAddCallback (text_edit, XmNvalueChangedCallback, post_modify_cb, NULL);
    XtAddCallback (text_edit, XmNmotionVerifyCallback, motion_cb, NULL);
    XtManageChild (text_edit);

    trsl = XtParseTranslationTable(transl);
    XtOverrideTranslations(text_edit, trsl);

    trsl = XtParseTranslationTable(linnotransl);
    XtOverrideTranslations(lineno_text, trsl);

    trsl = XtParseTranslationTable(findtransl);
    XtOverrideTranslations(find_text, trsl);

    trsl = XtParseTranslationTable(repltransl);
    XtOverrideTranslations(replace_text, trsl);

    XtManageChild (form);
    XtManageChild (main_window);

    all_edits = (struct edit_record *)
		malloc(max_undos * sizeof(struct edit_record));

    for(i = 0; i < max_undos; i++){
	memcpy(all_edits + i, &act_edit, sizeof(act_edit));
	all_edits[i].ins_string = (char *) malloc(sizeof(char));
	all_edits[i].del_string = (char *) malloc(sizeof(char));
    }
    all_edits[0].type = PASTE_ACTION;

    get_options();

    if(act_filename){
	i = readonly;

	if(read_file(act_filename, buf, 0)){
	    cptr = act_filename + strlen(act_filename) - 1;
	    if(*cptr == '/'){
		sprintf(buf,
			"%s is the name of a directory. Filename ignored.",
				act_filename);
		act_filename = NULL;
	    }
	    else{
		j = strlen(act_filename);
		if(cptr = strrchr(act_filename, '/')){
		    *(cptr) = '\0';
		    if(!act_filename[0] && j < 2){
			sprintf(buf, "/ is a directory. Filename ignored.");
			act_filename = NULL;
		    }
		    else{
			if(*act_filename && stat(act_filename, &statb) == -1){
			    sprintf(buf, "The directory %s does not exist. Filename ignored.",
					act_filename);
			    act_filename = NULL;
			}
			else{
			    *cptr = '/';
			    j = stat(act_filename, &statb);
			    if(j != -1 && S_ISDIR(statb.st_mode)){
				sprintf(buf, "%s is a directory. Filename ignored.",
						act_filename);
				act_filename = NULL;
			    }
		    	}
		    }
		    *cptr = '/';
		}
	    }

	    if(act_filename)
		sprintf(buf,
			"%s not found. File will be created when saving.",
					act_filename);
	}

	set_title();

	XmTextSetString(text_output, buf);

	if(initial_line >= 0){
	    sprintf(buf, "%d", initial_line);
	    XmTextSetString(lineno_text, buf);
	    goto_line();
	}

	if(readonly_forced)
	    readonly = i;
    }

    if(recover){
	if(read_file(textrecfile, buf, 0)){
	    fprintf(stderr, "Recovering failed.\n");
	    exit(0);
	}
	else
	    a_file_is_to_rm |= TEXTRECFILE;

	if(load_edits()){
	    fprintf(stderr, "Recovering partially failed, sorry.\n");
	    act_edit_nr = first_edit_nr = total_edits = 0;
	}
	else
	    a_file_is_to_rm |= EDITRECFILE;

	if(act_filename)
	    if(!strcmp(act_filename, ""))
		act_filename = NULL;

	set_title();

	XmTextSetString(text_output, buf);

    }

    XtVaSetValues(autoindent_button, XmNset, auto_indent, NULL);
    XtVaSetValues(readonly_button, XmNset, readonly, NULL);
    XtVaSetValues(no_backup_button, XmNset, no_backup, NULL);

    tmpnam(textdumpfile);
    tmpnam(editdumpfile);
    strcat(textdumpfile, ".textedit.text");
    strcat(editdumpfile, ".textedit.edit");
    sprintf(recovercmd, "%s -recover %s %s &", argv[0], textdumpfile,
				editdumpfile);

    signal(SIGTERM, safe_exit);
    signal(SIGINT, safe_exit);
    signal(SIGBUS, emergency_exit);
    signal(SIGSEGV, emergency_exit);
    signal(SIGHUP, save_on_hup);

    XtRealizeWidget (toplevel);
    set_wm_interactions();
    XtAppMainLoop (app_context);
}

void
get_options()
{
    int	i;

    recover = 0;
    readonly_forced = False;
    if(act_filename)
	free(act_filename);
    act_filename = NULL;

    for(i = 1; i < *gargc; i++){
	if(! strcmp(gargv[i], "+ai"))
	    auto_indent = True;
	else if(! strcmp(gargv[i], "-ai"))
	    auto_indent = False;
	else if(! strcmp(gargv[i], "+ro")){
	    readonly_forced = True;
	    readonly = True;
	}
	else if(! strcmp(gargv[i], "-ro")){
	    readonly_forced = True;
	    readonly = False;
	}
	else if(! strcmp(gargv[i], "+nb"))
	    no_backup = True;
	else if(! strcmp(gargv[i], "-nb"))
	    no_backup = False;
	else if(! strcmp(gargv[i], "-recover")){
	    recover = 1;
	    textrecfile = gargv[i + 1];
	    editrecfile = gargv[i + 2];
	    i += 2;
	}
	else if(gargv[i][0] == '-')
	    usage(gargv[0]);
	else if(gargv[i][0] == '+'
		&& gargv[i][1] >= '0' && gargv[i][1] <= '9')
	    sscanf(gargv[i] + 1, "%d", &initial_line);
	else{
	    if(act_filename)
		usage(gargv[0]);
	    act_filename = strdup(gargv[i]);
	}
    }
}

void
reset_act_edit()
{
    act_edit.ins_start = act_edit.ins_end = act_edit.del_start
			= act_edit.del_end = 0;
    act_edit.type = PASTE_ACTION;
}

void
set_title()
{
    char	buf[256], *cptr;
    int		i;

    sprintf(buf, "Editor: %s %s", act_filename ?
				act_filename : "[No Filename]",
				total_edits > 0 ? "(edited)" : "");

    XtVaSetValues(toplevel, XmNtitle, buf, NULL);

    sprintf(buf, "%s", act_filename ?
				act_filename : "[No Filename]");

    for(cptr = buf + strlen(buf); *cptr != '/' && cptr >= buf; cptr--);
    if(*cptr == '/')
	cptr++;
    if(cptr < buf)
	cptr = buf;

    for(i = 0; *cptr; i++, cptr++)
	buf[i] = *cptr;
    buf[i] = '\0';
    for(; cptr >= buf; cptr--)
	*(cptr + 1) = *cptr;
    buf[0] = '(';

    cptr = buf + 1;
    if(total_edits > 0){
	cptr = buf;
	buf[i + 1] = ')';
	buf[i + 2] = '\0';
    }

    XtVaSetValues(toplevel, XmNiconName, cptr, NULL);
}

void
evaluate_change()
{
    char		act_action, *string, *text, *cptr;
    int			*ip, finish, textpos, i;
    struct edit_record	*er;

    text = NULL;

    if(act_edit.del_start < act_edit.del_end && act_edit.ins_start < act_edit.ins_end){
	act_action = CUTPASTE_ACTION;
	act_edit.ins_start -= act_edit.del_end - act_edit.del_start;
	act_edit.ins_end -= act_edit.del_end - act_edit.del_start;

	goto found_action;
    }
    if(act_edit.del_start == act_edit.del_end - 1){
	if(act_edit.ins_start != act_edit.del_start){
	    act_action = BACKSP_ACTION;
	    act_edit.ins_start--;
	    act_edit.ins_end--;
	}
	else{
	    act_action = DELETE_ACTION;
	}
    }
    else if(act_edit.del_start < act_edit.del_end){
	act_action = CUT_ACTION;
	act_edit.ins_start = act_edit.ins_end = act_edit.del_start;
    }
    if(act_edit.ins_end == act_edit.ins_start + 1){
	act_action = INSERT_ACTION;
    }
    else if(act_edit.ins_start < act_edit.ins_end){
	act_action = PASTE_ACTION;
    }

   found_action:
    act_edit.type = act_action;

    finish = 0;
    if(all_edits[act_edit_nr].ins_end != act_edit.ins_start
		|| act_action == BACKSP_ACTION){
	finish = 1;

	if(all_edits[act_edit_nr].ins_end - 1 == act_edit.ins_start
		&& act_action == BACKSP_ACTION
		&& (all_edits[act_edit_nr].type == DELETE_ACTION
			|| all_edits[act_edit_nr].type == BACKSP_ACTION))
	    finish = 0;
    }
    else if(act_action == PASTE_ACTION || act_action == CUT_ACTION
		|| act_action == CUTPASTE_ACTION)
	finish = 1;
    else{
	if(all_edits[act_edit_nr].type != act_action){
	    if((all_edits[act_edit_nr].type != DELETE_ACTION
		&& all_edits[act_edit_nr].type != BACKSP_ACTION) ||
		(act_action != DELETE_ACTION
			&& act_action != BACKSP_ACTION)){
		finish = 1;
	    }
	}
    }
    if(act_action == INSERT_ACTION)
	if(act_edit.ins_string[0] == '\n')
	    finish = 1;

    if(finish){
	act_edit_nr = (act_edit_nr + 1)/* % max_undos*/;

	if(act_edit_nr >= max_undos){
	    max_undos += 50;
	    all_edits = realloc(all_edits, max_undos * sizeof(*all_edits));
	    for(i = max_undos - 50; i < max_undos; i++){
		memset(all_edits + i, 0, sizeof(*all_edits));
		all_edits[i].ins_string = malloc(1);
		all_edits[i].del_string = malloc(1);
		all_edits[i].type = PASTE_ACTION;
	    }
	}

	if(act_edit_nr == first_edit_nr){
	    first_edit_nr = (first_edit_nr + 1) % max_undos;
	}

	total_edits++;

	er = &(all_edits[act_edit_nr]);

	er->ins_start = er->ins_end = er->del_start = er->del_end =
		act_edit.ins_start;
	er->del_strlen = er->ins_strlen = 0;
	strcpy(er->del_string, "");
    }
	  
    if(act_action == INSERT_ACTION){
	if(act_edit.ins_string[0] == '\n'){
	    int	num_inserts;

	    if(auto_indent){
		text = XmTextGetString(text_edit);

		textpos = (int) XmTextGetCursorPosition(text_edit);

		for(i = textpos; i > 0 && text[i - 1] != '\n'; i--);

		for(num_inserts = 0;
			isspace(text[i + num_inserts])
					&& text[i + num_inserts] != '\n';
			num_inserts++);

		act_ins_text->ptr = XtRealloc(act_ins_text->ptr, sizeof(char)
						* (num_inserts + 1));
		cptr = act_ins_text->ptr;
		memcpy(cptr + 1, text + i, num_inserts * sizeof(char));
		cptr[0] = '\n';

		act_ins_text->length = num_inserts + 1;

		act_edit.ins_end += num_inserts;
	    }
	}
    }

    switch(act_action){
      case DELETE_ACTION:
      case BACKSP_ACTION:
      case CUT_ACTION:
      case CUTPASTE_ACTION:
	if(! text)
	    text = XmTextGetString(text_edit);
	break;
    }

    er = &(all_edits[act_edit_nr]);
    ip = &(er->del_strlen);

    switch(act_action){
      case BACKSP_ACTION:
	string = (char *) malloc(sizeof(char) * (2 + *ip));
	strncpy(string + 1, er->del_string, *ip);
	string[0] = text[act_edit.del_start];
	string[*ip + 1] = '\0';

	(*ip)++;
	free(er->del_string);
	er->del_string = string;
	break;

      case DELETE_ACTION:
	string = (char *) malloc(sizeof(char) * (2 + *ip));
	strncpy(string, er->del_string, *ip);
	string[*ip] = text[act_edit.del_start];
	string[*ip + 1] = '\0';

	(*ip)++;
	free(er->del_string);
	er->del_string = string;
	break;

      case INSERT_ACTION:
	act_edit.ins_start = er->ins_start;
	break;

      case CUT_ACTION:
      case CUTPASTE_ACTION:
	*ip = act_edit.del_end - act_edit.del_start;
	string = (char *) malloc(sizeof(char) * (1 + *ip));
	strncpy(string, text + act_edit.del_start, *ip);
	string[*ip] = '\0';

	free(er->del_string);
	er->del_string = string;
	break;

    }

    er->ins_start = act_edit.ins_start;
    er->ins_end = act_edit.ins_end;
    er->del_start = act_edit.del_start;
    er->del_end = act_edit.del_end;
    er->type = act_action;

  getout:
    if(text)
	XtFree(text);
}

void
insert_text(char ** text, int textlength, char * string, int stringlen,
		int insert_pos)
{
    char	*newtext;

    newtext = (char *) malloc(sizeof(char) * (textlength + stringlen + 3));

    memcpy(newtext, *text, sizeof(char) * insert_pos);
    memcpy(newtext + insert_pos, string, sizeof(char) * stringlen);
    memcpy(newtext + insert_pos + stringlen, *text + insert_pos,
		sizeof(char) * (textlength - insert_pos));
    newtext[textlength + stringlen] = '\0';

    free(*text);
    *text = newtext;
}

void
delete_text(char ** text, int textlength, int delpos, int dellen)
{
    int	i;

    for(i = delpos; i < textlength - dellen; i++)
	(*text)[i] = (*text)[i + dellen];

    if(dellen > 0)
	(*text)[i] = '\0';
}

void
undo_actual()
{
    char	*text, buf[256];
    int		textlen;

    if(act_edit_nr == first_edit_nr && readonly)
	return;

    if(act_edit_nr == first_edit_nr){
	if(total_edits)
	    XmTextSetString(text_output, "Maximum number of unedits reached.");
	else{
	    XmTextSetString(text_output, "Nothing more to unedit.");
	}

	XBell(XtDisplay(toplevel), 50);

	return;
    }

    if(readonly){
	XBell(XtDisplay(toplevel), 50);

	sprintf(buf, "Text is read-only !  You may toggle this option.");

	XmTextSetString(text_output, buf);

	return;
    }

#if 0
    text = XmTextGetString(text_edit);
    textlen = (int) XmTextGetLastPosition(text_edit);
#endif

    this_is_no_edit = 1;

#if 0
    delete_text(&text, textlen, all_edits[act_edit_nr].ins_start,
				all_edits[act_edit_nr].ins_end -
				all_edits[act_edit_nr].ins_start);
    insert_text(&text, textlen, all_edits[act_edit_nr].del_string,
				all_edits[act_edit_nr].del_strlen,
				all_edits[act_edit_nr].del_start);

    XmTextSetString(text_edit, text);
#endif

    XmTextReplace(text_edit, all_edits[act_edit_nr].ins_start,
				all_edits[act_edit_nr].ins_end,
				all_edits[act_edit_nr].del_string);

    XmTextSetCursorPosition(text_edit, all_edits[act_edit_nr].del_end);
    XmTextSetInsertionPosition(text_edit, all_edits[act_edit_nr].del_end);

#if 0
    XFree(text);
#endif

    act_edit_nr--;
    if(act_edit_nr < 0)
	act_edit_nr = (act_edit_nr + max_undos) % max_undos;

    memcpy(&act_edit, all_edits + act_edit_nr, sizeof(act_edit));
    act_edit.type = PASTE_ACTION;

    total_edits--;

    if(total_edits == 0){
	set_title();
    }
}

void
pre_modify_cb(Widget w, XtPointer client_data, XtPointer call_data)
{
    XmTextVerifyPtr	text_call_data;
    XmTextPosition	pos;
    char		*str, buf[256];

    if(sth_highlighted){
	pos = XmTextGetLastPosition(text_edit);
	XmTextSetHighlight (text_edit, 0, pos, XmHIGHLIGHT_NORMAL);
	sth_highlighted = 0;
    }

    if(this_is_no_edit){
	this_is_no_edit = 0;
	return;
    }

    text_call_data = (XmTextVerifyPtr) call_data;

    if(readonly){
	text_call_data->doit = False;

	XBell(XtDisplay(toplevel), 50);

	sprintf(buf, "Text is read-only !  You may toggle this option.");

	XmTextSetString(text_output, buf);

	return;
    }

    act_ins_text = text_call_data->text;
    act_edit.ins_start = act_edit.ins_end = text_call_data->currInsert;
    if(text_call_data->text->length > 0){
	act_edit.ins_end = text_call_data->currInsert
				+ text_call_data->text->length;
    }
    act_edit.del_start = text_call_data->startPos;
    act_edit.del_end = text_call_data->endPos;
    act_edit.ins_string = text_call_data->text->ptr;

    if(moved)
	act_edit.ins_start = motion_to;

    moved = 0;

    evaluate_change();

    if(total_edits == 1){
	set_title();
    }
}

void
post_modify_cb(Widget w, XtPointer client_data, XtPointer call_data)
{
}

void
motion_cb(Widget w, XtPointer client_data, XtPointer call_data)
{
    XmTextVerifyPtr	text_call_data;
    XmTextPosition	pos;

    if(sth_highlighted){
	pos = XmTextGetLastPosition(text_edit);
	XmTextSetHighlight (text_edit, 0, pos, XmHIGHLIGHT_NORMAL);
	sth_highlighted = 0;
    }

    text_call_data = (XmTextVerifyPtr) call_data;

    motion_from = text_call_data->currInsert;
    motion_to = text_call_data->newInsert;
    moved = 1;

    if(a_file_is_to_rm){
	if(a_file_is_to_rm & EDITRECFILE)
	    unlink(editrecfile);
	if(a_file_is_to_rm & TEXTRECFILE)
	    unlink(textrecfile);

	a_file_is_to_rm = 0;
    }
}


int
read_file(char * filename, char * return_msg, int inserting)
{
    struct stat	statb;
    int		fault = 0, len;
    FILE	*fp;
    char	*text;
    XmTextPosition	insertion_position;

    strcpy(return_msg, "");

    /* make sure the file is a regular text file and open it */
    if (stat (filename, &statb) == -1)
	fault = 1;
    else if ((statb.st_mode & S_IFMT) != S_IFREG)
	fault = 1;
    else if(!(fp = fopen (filename, "r")))
	fault = 1;

    if(fault) {
        sprintf (return_msg, "Can't read %s.", filename);
        XmTextSetString (text_output, return_msg);
        return(1);
    }

    /* put the contents of the file in the Text widget by
     * allocating enough space for the entire file, reading the
     * file into the space, and using XmTextSetString() to show
     * the file.
     */
    len = statb.st_size;
    if (!(text = XtMalloc ((unsigned)(len+1)))) /* +1 for NULL */
        sprintf (return_msg, "%s: XtMalloc(%ld) failed", len, filename);
    else {
        if (fread (text, sizeof (char), len, fp) != len)
                sprintf (return_msg, "Warning: did not read entire file!");
        else
            sprintf (return_msg, "%s %ld bytes from %s.",
			inserting ? "Inserted" : "Loaded", len, filename);
        text[len] = 0; /* NULL-terminate */

	if(inserting){
	    insertion_position = XmTextGetInsertionPosition(text_edit);
	    XmTextInsert (text_edit, insertion_position, text);
	}
	else{
	    if(len > 0)
	      this_is_no_edit = 1;

 	    XmTextSetString (text_edit, text);
	    act_edit_nr = first_edit_nr = total_edits = 0;

	    reset_act_edit();

	    fclose(fp);

	    fp = fopen(filename, "a");
	    if(fp)
		fclose(fp);

	    readonly = (! fp);

	    fp = NULL;

	    XtVaSetValues(readonly_button, XmNset, readonly, NULL);

	    if(readonly)
		strcat(return_msg, " File is read-only.");
	}
    }

    if(fp)
	fclose(fp);

    filemode = statb.st_mode;

    return(0);
}

int
save_file(char * filename, char * return_msg)
{
    struct stat	statb;
    FILE	*fp;
    int		len;
    char	*text, buf[256];
    Boolean	file_there;

    if(file_there = (! stat(filename, &statb)))
	filemode = statb.st_mode;

    fp = fopen(filename, "a");

    if(!fp){
	strcpy(return_msg, "File is not writable, text not saved.");

	XBell(XtDisplay(toplevel), 50);

	return(1);
    }
    else
	fclose(fp);

    XtVaGetValues(no_backup_button, XmNset, &no_backup, NULL);

    if(! no_backup){
	sprintf(buf, "%s%%", filename);

	fp = fopen(buf, "r");

	if(fp){
	    fclose(fp);

	    fp = fopen(buf, "a");
	    if(fp)
		fclose(fp);
	    else{
		strcpy(return_msg,
			"Backup file is not writable, text not saved.");
		strcat(return_msg, " You toggle the backup option.");

		XBell(XtDisplay(toplevel), 50);

		return(1);
	    }
	}

	if(file_there){
	    if(rename(filename, buf)){
		strcpy(return_msg, "Cannot backup file, text not saved.");
		strcat(return_msg, " You may try to toggle the backup option.");

		XBell(XtDisplay(toplevel), 50);

		return(1);
	    }
	}
    }

    if (!(fp = fopen (filename, "w"))) {
	perror (filename);
	sprintf (return_msg, "Can't save to %s.", filename);
	XmTextSetString (text_output, return_msg);
	XtFree (filename);
	return(1);
    }
    /* saving -- get text from Text widget... */
    text = XmTextGetString (text_edit);
    len = XmTextGetLastPosition (text_edit);
    /* write it to file (check for error) */
    if (fwrite (text, sizeof (char), len, fp) != len)
	strcpy (return_msg, "Warning: did not write entire file!");
    else {
        /* make sure a newline terminates file */
        if (text[len-1] != '\n')
            fputc ('\n', fp);
        sprintf (return_msg, "Saved %ld bytes to %s.", len, filename);
    }
    fclose(fp);

    if(filemode)
	chmod(filename, filemode);

    act_edit_nr = first_edit_nr = total_edits = 0;

    reset_act_edit();

    return(0);
}

/* file_select_cb() -- callback routine for "OK" button in 
 * FileSelectionDialogs.
 */
void
file_select_cb(dialog, client_data, call_data)
Widget dialog;
XtPointer client_data;
XtPointer call_data;
{
    char	buf[256], *filename;
    long	len;
    int		reason = (int) client_data;
    XmFileSelectionBoxCallbackStruct *cbs =
		(XmFileSelectionBoxCallbackStruct *) call_data;

    if (!XmStringGetLtoR (cbs->value, XmFONTLIST_DEFAULT_TAG, &filename))
        return; /* must have been an internal error */

    if (*filename == '\0') {
        XtFree (filename);
        XBell (XtDisplay (text_edit), 50);
        XmTextSetString (text_output, "Choose a file.");
        return; /* nothing typed */
    }

    if (reason == FILE_SAVE_AS) {
	save_file(filename, buf);
    }
    else if(reason == FILE_SAVE) {
	fprintf(stderr, "Internal Error: sdferww.\n");
    }
    else if(reason == FILE_OPEN) {
	read_file(filename, buf, 0);
    }
    else {	 /* reason == FILE_INSERT */
	read_file(filename, buf, 1);
    }
    XmTextSetString (text_output, buf); /* purge output message */

    if(reason != FILE_INSERT){
	if(act_filename)
	    free(act_filename);
	act_filename = strdup(filename);
	if(! act_filename){
	    fprintf(stderr, "malloc failed.\n");
	}
    }

    /* free all allocated space. */
    XtFree (filename);
    XtUnmanageChild (dialog);

    set_title();
}

/* popdown_cb() -- callback routine for "Cancel" button. */
void
popdown_cb (w, client_data, call_data)
Widget w;
XtPointer client_data;
XtPointer call_data;
{
    XtUnmanageChild (w);
}

void
maybe_exit()
{
    if(total_edits){
	if(sth_highlighted == 25)
	    exit(0);

	XBell(XtDisplay(toplevel), 50);

	XmTextSetString(text_output, "Warning: Text has been modified, but not saved. Choose \"Exit\" again to really exit.");

	sth_highlighted = 25;

	return;
    }

    exit(0);
}

/* file_cb() -- a menu item from the "File" pulldown menu was selected */
void
file_cb(w, client_data, call_data)
Widget w;
XtPointer client_data;
XtPointer call_data;
{
    static Widget open_dialog = NULL, save_dialog = NULL, ins_dialog = NULL;
    Widget	dialog = NULL;
    XmString	button, title;
    int		reason = (int) client_data;
    char	buf[256];

    if (reason == FILE_EXIT){
	maybe_exit();
	return;
    }

    XmTextSetString (text_output, NULL);   /* clear message area */

    if(reason == FILE_OPEN && total_edits){
	XBell(XtDisplay(toplevel), 50);

	XmTextSetString(text_output,
		"Warning: Actual text has been modified and is not saved !");
    }

    if(reason == FILE_EMPTY){
	XmTextSetString(text_edit, "");
	return;
    }
    if (reason == FILE_OPEN && open_dialog)
        dialog = open_dialog;
    else if (reason == FILE_SAVE_AS && save_dialog)
        dialog = save_dialog;
    else if(reason == FILE_INSERT && ins_dialog)
	dialog = ins_dialog;
    else if(reason == FILE_SAVE){
	if(! act_filename)
	    reason = FILE_SAVE_AS;
	else if(total_edits == 0){
	    strcpy(buf, "File has not been modified, save operation ignored.");
	    XmTextSetString (text_output, buf);
	    return;
	}
	else{
	    strcpy(buf, "");
	    save_file(act_filename, buf);
	    XmTextSetString (text_output, buf);
	    set_title();
	    return;
	}
    }

    if (dialog) {
        XtManageChild (dialog);
        /* make sure that dialog is raised to top of window stack */
        XMapRaised (XtDisplay (dialog), XtWindow (XtParent (dialog)));
        return;
    }

    dialog = XmCreateFileSelectionDialog (text_edit, "Files", NULL, 0);
    XtAddCallback (dialog, XmNcancelCallback, popdown_cb, NULL);
    XtAddCallback (dialog, XmNokCallback, file_select_cb, (XtPointer *)reason);
    if (reason == FILE_OPEN) {
        button = XmStringCreateLocalized ("Open");
        title = XmStringCreateLocalized ("Open File");
        open_dialog = dialog;
    } 
    if(reason == FILE_SAVE_AS){
        button = XmStringCreateLocalized ("Save");
        title = XmStringCreateLocalized ("Save File");
        save_dialog = dialog;
    }
    if(reason == FILE_INSERT){
        button = XmStringCreateLocalized ("Insert");
        title = XmStringCreateLocalized ("Insert File");
        ins_dialog = dialog;
    } 

    XtVaSetValues (dialog,
        XmNokLabelString, button,
        XmNdialogTitle,   title,
        NULL);
    XmStringFree (button);
    XmStringFree (title);
    XtManageChild (dialog);
}

void
set_new_pos(
  char			*text,
  XmTextPosition	oldpos,
  XmTextPosition	newpos)
{
  int			i, j;
  short			rows;
  XmTextPosition	toppos;

  XtVaGetValues(text_edit,
	XmNrows,		&rows,
	XmNtopCharacter,	&toppos, NULL);

  if(toppos > newpos){
    i = 0;
    for(j = newpos; j <= toppos; j++)
	if(text[j] == '\n')
	  i++;
    XmTextScroll(text_edit, -i - rows / 5);
  }
  else{
    i = 0;
    for(j = toppos; j <= newpos; j++)
	if(text[j] == '\n')
	  i++;

    if(i > rows * 6 / 7 && i < rows * 3 / 2){
	XmTextScroll(text_edit, i - rows * 6 / 7);
    }
    if(i < rows / 7){
	XmTextScroll(text_edit, i - rows / 7);
    }
    if(i >= rows * 3 / 2){
	XmTextScroll(text_edit, i - rows / 2);
    }
  }

    XtVaSetValues(text_edit, XmNcursorPosition, newpos, NULL); 
}

void
find_sel()
{
  XmTextPosition	pos;

  pos = XmTextGetInsertionPosition(text_edit);

  XmTextSetSelection(text_edit, pos, pos, CurrentTime);

  find_cb(find_menu, (XtPointer) SEARCH_FIND_NEXT, (XtPointer) NULL);
}

void
repl_sel()
{
  find_cb(find_menu, (XtPointer) SEARCH_REPLACE, (XtPointer) NULL);
}

Widget
top_shell(Widget ch)
{
  while(ch && !XtIsWMShell(ch))
    ch = XtParent(ch);

  return(ch);
}

/* For interactions with the window-manager */
Atom		find_pair_wm_delete_window;
Atom		find_pair_wm_protocols;

int	find_pair_mode = 0;
char	*left_pat = NULL;
char	*right_pat = NULL;


/* window-delete handler */
static void
find_pair_client_msg_handler(
  Widget	w,
  XtPointer	closure,
  XEvent	*event)
{
  XClientMessageEvent	*c;

  c = (XClientMessageEvent *) event;

  if (event->type != ClientMessage ||
		c->message_type != find_pair_wm_protocols)
    return;

  find_pair_dialog = NULL;
}

static void
set_find_pair_mode(
  Widget	w,
  XtPointer	client_data,
  XtPointer	call_data)
{
  XmToggleButtonCallbackStruct	*state;

  state = (XmToggleButtonCallbackStruct *) call_data;

  if(state->set)
    find_pair_mode = (int) client_data;
}

static void
find_pair_proc(
  Widget	w,
  XtPointer	client_data,
  XtPointer	call_data)
{
  int	mode = (int) client_data;
  int	leftlen, rightlen, actpos, tlen, leftfound, rightfound;
  int	i, count;
  char	*text = NULL;
  char	*cptr, *rcptr, *lcptr;
  char	*left_pat_to_find, *right_pat_to_find;

  XmTextSetString (text_output, "");

  if(left_pat)
    XtFree(left_pat);
  if(right_pat)
    XtFree(right_pat);

  left_pat = XmTextGetString(pat_one_text);
  right_pat = XmTextGetString(pat_two_text);
  actpos = XmTextGetInsertionPosition(text_edit);
  text = XmTextGetString(text_edit);
  tlen = XmTextGetLastPosition(text_edit);

  switch(find_pair_mode){
    case 0:
	left_pat_to_find = "{";
	right_pat_to_find = "}";
	break;
    case 1:
	left_pat_to_find = "(";
	right_pat_to_find = ")";
	break;
    case 2:
	left_pat_to_find = "/*";
	right_pat_to_find = "*/";
	break;
    default:
	left_pat_to_find = (left_pat ? left_pat : "");
	right_pat_to_find = (right_pat ? right_pat : "");
  }

  leftlen = strlen(left_pat_to_find);
  rightlen = strlen(right_pat_to_find);

  if(!leftlen && mode != 2){
    XmTextSetString (text_output, "No left pattern specified");
    goto done_find_pair;
  }
  if(!rightlen && mode != 2){
    XmTextSetString (text_output, "No right pattern specified");
    goto done_find_pair;
  }

  switch(mode){
    case 0:
	cptr = strstr(text + actpos, left_pat_to_find);

	if(!cptr){
	  XmTextSetString (text_output, "Left Side not found");
	  goto done_find_pair;
	}

	leftfound = cptr - text;

	cptr += leftlen;
	count = 1;
	while(count > 0){
	  lcptr = strstr(cptr, left_pat_to_find);
	  rcptr = strstr(cptr, right_pat_to_find);

	  if(!rcptr){
	    XmTextSetString (text_output, "Pair not found");
	    goto done_find_pair;
	  }

	  if(lcptr){
	    if(lcptr < rcptr){
		count++;
		cptr = lcptr + leftlen;
	    }
	    else{
	      count--;
	      cptr = rcptr + rightlen;
	    }
	  }
	  else{
	    count--;
	    cptr = rcptr + rightlen;
	  }
	}

	rightfound = rcptr - text;

	XmTextSetSelection(text_edit,
			leftfound, rightfound + rightlen, CurrentTime);
	XmTextSetHighlight(text_edit, leftfound, rightfound + rightlen,
			XmHIGHLIGHT_SELECTED);
	if(strcmp(left_pat_to_find, right_pat_to_find)){
	  XmTextSetInsertionPosition(text_edit, rightfound);
	  XmTextSetCursorPosition(text_edit, rightfound);
	  XmTextSetInsertionPosition(text_edit, leftfound + 1);
	  XmTextSetCursorPosition(text_edit, leftfound + 1);
	}
	else{
	  XmTextSetInsertionPosition(text_edit, leftfound);
	  XmTextSetCursorPosition(text_edit, leftfound);
	  XmTextSetInsertionPosition(text_edit, rightfound + rightlen);
	  XmTextSetCursorPosition(text_edit, rightfound + rightlen);
	}
	sth_highlighted = 1;

	break;
    case 1:
	text[actpos] = '\0';
	cptr = strrstr(text, right_pat_to_find);

	if(!cptr){
	  XmTextSetString (text_output, "Right Side not found");
	  goto done_find_pair;
	}

	rightfound = cptr - text;

	*cptr = '\0';
	count = 1;
	while(count > 0){
	  lcptr = strrstr(text, left_pat_to_find);
	  rcptr = strrstr(text, right_pat_to_find);

	  if(!lcptr){
	    XmTextSetString (text_output, "Pair not found");
	    goto done_find_pair;
	  }

	  if(rcptr){
	    if(lcptr < rcptr){
		count++;
		cptr = rcptr;
		*cptr = '\0';
	    }
	    else{
		count--;
		cptr = lcptr;
		*cptr = '\0';
	    }
	  }
	  else{
	    count--;
	    cptr = lcptr;
	    *cptr = '\0';
	  }
	}

	leftfound = lcptr - text;

	XmTextSetSelection(text_edit,
			leftfound, rightfound + rightlen, CurrentTime);
	XmTextSetHighlight(text_edit, leftfound, rightfound + rightlen,
			XmHIGHLIGHT_SELECTED);
	if(strcmp(left_pat_to_find, right_pat_to_find)){
	  XmTextSetInsertionPosition(text_edit, leftfound);
	  XmTextSetCursorPosition(text_edit, leftfound);
	  XmTextSetInsertionPosition(text_edit, rightfound + rightlen - 1);
	  XmTextSetCursorPosition(text_edit, rightfound + rightlen - 1);
	}
	else{
	  XmTextSetInsertionPosition(text_edit, rightfound + rightlen);
	  XmTextSetCursorPosition(text_edit, rightfound + rightlen);
	  XmTextSetInsertionPosition(text_edit, leftfound);
	  XmTextSetCursorPosition(text_edit, leftfound);
	}
	sth_highlighted = 1;

	break;
    case 2:
	XtDestroyWidget(find_pair_dialog);
	find_pair_dialog = NULL;
	break;
  }

 done_find_pair:
  if(text)
    XtFree(text);
}

void
popup_find_pair_dialog()
{
  Position	x, y;
  Widget	pat_label, pat_one_label, action_form, find_forw_button,
		pat_two_label, find_backw_button, find_done_button;

  if(find_pair_dialog){
    XMapRaised (XtDisplay (find_pair_dialog), XtWindow (find_pair_dialog));

    return;
  }

    find_pair_dialog = XtVaCreatePopupShell("findPair",
		transientShellWidgetClass,	toplevel,
		XmNdeleteResponse,	XmDESTROY,
		XmNtitle,		"Find Pair",
		NULL);

    find_pair_pane = XtVaCreateManagedWidget("findPairPane",
		xmPanedWindowWidgetClass,	find_pair_dialog,
		NULL);
    find_pair_form = XtVaCreateManagedWidget("findPairForm",
		xmFormWidgetClass,		find_pair_pane,
		NULL);
    find_pair_radio_box = XtVaCreateManagedWidget("findPairSel",
		xmRowColumnWidgetClass,	find_pair_form,
		XmNradioAlwaysOne,	True,
		XmNradioBehavior,	True,
		XmNentryClass,		xmToggleButtonGadgetClass,
		XmNisHomogeneous,	True,
		XmNtraversalOn,		True,
		XmNnavigationType,	XmTAB_GROUP,
		NULL);

    curly_sel = XtVaCreateManagedWidget("curlySel",
		xmToggleButtonGadgetClass,	find_pair_radio_box,
		XmNset,				(find_pair_mode == 0),
		XmNlabelString,			xmstr("{ }"),
		NULL);
    XtAddCallback(curly_sel, XmNvalueChangedCallback, set_find_pair_mode, (XtPointer) 0);
    round_sel = XtVaCreateManagedWidget("roundSel",
		xmToggleButtonGadgetClass,	find_pair_radio_box,
		XmNset,				(find_pair_mode == 1),
		XmNlabelString,			xmstr("( )"),
		NULL);
    XtAddCallback(round_sel, XmNvalueChangedCallback, set_find_pair_mode, (XtPointer) 1);
    ccommsel = XtVaCreateManagedWidget("ccommsel",
		xmToggleButtonGadgetClass,	find_pair_radio_box,
		XmNset,				(find_pair_mode == 2),
		XmNlabelString,			xmstr("/* */"),
		NULL);
    XtAddCallback(ccommsel, XmNvalueChangedCallback, set_find_pair_mode, (XtPointer) 2);
    other_sel = XtVaCreateManagedWidget("other_sel",
		xmToggleButtonGadgetClass,	find_pair_radio_box,
		XmNset,				(find_pair_mode == 3),
		XmNlabelString,			xmstr("other"),
		NULL);
    XtAddCallback(other_sel, XmNvalueChangedCallback, set_find_pair_mode, (XtPointer) 3);

    pat_label = XtVaCreateManagedWidget("patLabel",
		xmLabelGadgetClass,		find_pair_form,
		XmNlabelString,		xmstr("Other Enclosing Pair:"),
		XmNleftAttachment,		XmATTACH_WIDGET,
		XmNleftWidget,			find_pair_radio_box,
		XmNleftOffset,			20,
		XmNtopAttachment,		XmATTACH_FORM,
		XmNtopOffset,			10,
		NULL),
    pat_one_label = XtVaCreateManagedWidget("patOneLabel",
		xmLabelGadgetClass,		find_pair_form,
		XmNlabelString,			xmstr("Left:  "),
		XmNleftAttachment,		XmATTACH_OPPOSITE_WIDGET,
		XmNleftWidget,			pat_label,
		XmNtopAttachment,		XmATTACH_WIDGET,
		XmNtopWidget,			pat_label,
		XmNtopOffset,			10,
		NULL),
    pat_one_text = XtVaCreateManagedWidget ("patOneText",
		xmTextFieldWidgetClass,		find_pair_form,
		XmNleftAttachment,		XmATTACH_WIDGET,
		XmNleftWidget,			pat_one_label,
		XmNtopAttachment,		XmATTACH_OPPOSITE_WIDGET,
		XmNtopWidget,			pat_one_label,
		XmNcolumns,			8,
		NULL);
    pat_two_label = XtVaCreateManagedWidget("patOneLabel",
		xmLabelGadgetClass,		find_pair_form,
		XmNlabelString,			xmstr("Right:"),
		XmNleftAttachment,		XmATTACH_OPPOSITE_WIDGET,
		XmNleftWidget,			pat_label,
		XmNtopAttachment,		XmATTACH_WIDGET,
		XmNtopWidget,			pat_one_text,
		NULL),
    pat_two_text = XtVaCreateManagedWidget ("patTwoText",
		xmTextFieldWidgetClass,	find_pair_form,
		XmNcolumns,			8,
		XmNleftAttachment,		XmATTACH_OPPOSITE_WIDGET,
		XmNleftWidget,			pat_one_text,
		XmNtopAttachment,		XmATTACH_WIDGET,
		XmNtopWidget,			pat_one_text,
		NULL);
    XmTextSetString(pat_one_text, (left_pat ? left_pat : ""));
    XmTextSetString(pat_two_text, (right_pat ? right_pat : ""));

  action_form = XtVaCreateManagedWidget("doFindPair",
		xmFormWidgetClass,		find_pair_pane,
		NULL);
  find_forw_button = XtVaCreateManagedWidget("findPairForw",
		xmPushButtonGadgetClass,	action_form,
		XmNlabelString,			xmstr("Forward"),
		NULL);
  XtAddCallback(find_forw_button, XmNactivateCallback, find_pair_proc, (XtPointer) 0);
  find_backw_button = XtVaCreateManagedWidget("findPairForw",
		xmPushButtonGadgetClass,	action_form,
		XmNlabelString,			xmstr("Backward"),
		XmNleftAttachment,		XmATTACH_WIDGET,
		XmNleftWidget,			find_forw_button,
		NULL);
  XtAddCallback(find_backw_button, XmNactivateCallback, find_pair_proc, (XtPointer) 1);
  find_done_button = XtVaCreateManagedWidget("findPairForw",
		xmPushButtonGadgetClass,	action_form,
		XmNlabelString,			xmstr("Done"),
		XmNleftAttachment,		XmATTACH_WIDGET,
		XmNleftWidget,			find_backw_button,
		NULL);
  xmstr(NULL);
  XtAddCallback(find_done_button, XmNactivateCallback, find_pair_proc, (XtPointer) 2);

  XtManageChild(find_pair_pane);

  XtVaGetValues(toplevel, XtNx, &x, XtNy, &y, NULL);
  XtVaSetValues(find_pair_dialog, XtNx, x + 100, XtNy, y + 10, NULL);
  XtPopup(find_pair_dialog, XtGrabNone);

/* get atoms */
  find_pair_wm_delete_window = XInternAtom(XtDisplay(find_pair_dialog),
		"WM_DELETE_WINDOW", False);
  find_pair_wm_protocols = XInternAtom(XtDisplay(find_pair_dialog),
			"WM_PROTOCOLS", False);

/* set protocol hints */
  XSetWMProtocols(XtDisplay(find_pair_dialog), XtWindow(find_pair_dialog),
			&find_pair_wm_delete_window, 1);

/* install event handler */
  XtAddEventHandler(find_pair_dialog, (EventMask) 0, True,
		(XtEventHandler) find_pair_client_msg_handler, NULL);
}

/* find_cb() -- a menu item from the "Search" pulldown menu selected */
void
find_cb(w, client_data, call_data)
Widget w;
XtPointer client_data;
XtPointer call_data;
{
    char	*find_pat = NULL, *string = NULL, *new_pat = NULL;
    char	buf[256], findbuf[1000];
    XmTextPosition pos, first, last, act_pos, toppos, oldpos, oldtop;
    int		len, nfound = 0, textlen, i, j;
    int		find_len, pattern_len, repl_len;
    int		reason = (int) client_data;
    Boolean	found = False, selected;
    XmTextDirection	find_dir;

    pos = 0;

    XmTextSetString (text_output, NULL);   /* clear message area */

    textlen = XmTextGetLastPosition (text_edit);
    pos = textlen;
    XmTextSetHighlight (text_edit, 0, pos, XmHIGHLIGHT_NORMAL);

    if (reason == SEARCH_CLEAR) {
        goto cleanup_find;
    }

    if(reason == SEARCH_FIND_PAIR){
	popup_find_pair_dialog();
        goto cleanup_find;
    }

    selected = XmTextGetSelectionPosition(text_edit, &first, &last);
    if(selected && first >= last)
	selected = 0;

    if (!(string = XmTextGetString (text_edit)) || !*string) {
        XmTextSetString (text_output, "No text to find.");
        goto cleanup_find;
    }
    if(!(find_pat = XmTextGetString (find_text)) || (!*find_pat && !selected)){
        XmTextSetString (text_output, "Specify a find pattern.");
        goto cleanup_find;
    }

    new_pat = XmTextGetString (replace_text);
    repl_len = pattern_len = strlen (new_pat);

    find_len = strlen (find_pat);

    oldpos = XmTextGetCursorPosition (text_edit);
    XtVaGetValues(text_edit, XmNtopCharacter, &oldtop, NULL);

    if(selected){
	find_len = last - first;
	if(find_len > 999){
	    XmTextSetString (text_output,
			"Cannot get whole selection for search.");
	    goto cleanup_find;
	}
	strncpy(findbuf, string + first, last - first);
	findbuf[last - first] = '\0';
	pos = first + (reason == SEARCH_FIND_PREVIOUS ? -1 : 1);
    }
    else{
	strcpy(findbuf, find_pat);
	pos = oldpos;
	if(reason == SEARCH_FIND_PREVIOUS)
		pos--;
    }

    if(reason == SEARCH_REPLACE || reason == SEARCH_REPLACE_AND_FIND){
	if(!selected){
	    XmTextSetString (text_output, "No selection to replace.");
	    goto cleanup_find;
	}
	XmTextReplace (text_edit, first, last, new_pat);
	if(reason == SEARCH_REPLACE){
	    sprintf(buf, "Replaced text at position %ld", first);
	    XmTextSetString (text_output, buf);
	    goto cleanup_find;
	}
	reason = SEARCH_FIND_NEXT;
    }

    if(pos < 0)
	    pos = textlen + 1;
    else if(pos > textlen + 1)
	    pos = 0;

    if (reason == SEARCH_FIND_NEXT || reason == SEARCH_FIND_PREVIOUS
		|| reason == SEARCH_FIND_AND_REPLACE) {
	if(reason == SEARCH_FIND_PREVIOUS)
	    find_dir = XmTEXT_BACKWARD;
	else
	    find_dir = XmTEXT_FORWARD;

	found = XmTextFindString (text_edit, pos, findbuf, 
           					find_dir, &pos);
	if (!found){
	    if(reason == SEARCH_FIND_PREVIOUS)
		pos = textlen + 1;
	    else
		pos = 0;

            found = XmTextFindString (text_edit, pos, findbuf, 
				find_dir, &pos);
	}
	if (found){
            nfound++;
	    set_new_pos(string, oldpos, pos + find_len);
	    if(reason == SEARCH_FIND_AND_REPLACE){
		XmTextReplace(text_edit, pos, pos + find_len, new_pat);
		XmTextSetCursorPosition(text_edit, pos + strlen(new_pat));
	    }
	    else{
		XmTextSetHighlight (text_edit, pos, pos + find_len,
					XmHIGHLIGHT_SELECTED);
		XmTextSetSelection(text_edit, pos, pos + find_len, CurrentTime);
	    }
	}
    }
    else { /* reason == SEARCH_SHOW_ALL || reason == SEARCH_REPLACE_ALL */
	pos = 0;
	act_pos = XmTextGetInsertionPosition(text_edit);
        do {
            found = XmTextFindString (text_edit, pos, findbuf,
						XmTEXT_FORWARD, &pos);
            if (found) {
                nfound++;
                if (reason == SEARCH_SHOW_ALL) {
                    XmTextSetHighlight (text_edit, pos, pos + find_len,
                        XmHIGHLIGHT_SELECTED);
		    sth_highlighted = 1;
		}
		else{
		    XmTextSetInsertionPosition(text_edit, pos + find_len);
		    XmTextSetCursorPosition(text_edit, pos + find_len);
                    XmTextReplace (text_edit, pos, pos + find_len, new_pat);
		    pos += repl_len - 1;
		}
                pos++;
	    }
	}
        while (found);
	XmTextSetInsertionPosition(text_edit, act_pos);
	XmTextSetCursorPosition(text_edit, act_pos);
    }		  

    if (nfound == 0)
        XmTextSetString (text_output, "Pattern not found.");
    else {
        switch (reason) {
            case SEARCH_FIND_NEXT :
            case SEARCH_FIND_PREVIOUS :
                sprintf (buf, "Pattern found at position %ld.", pos);
                /*XmTextSetInsertionPosition (text_edit, pos);*/
                break;
	    case SEARCH_FIND_AND_REPLACE:
		sprintf (buf, "Replaced Pattern at position %ld.", pos);
		break;
            case SEARCH_SHOW_ALL :
                sprintf (buf, "Found %d occurrences.", nfound);
                break;
            case SEARCH_REPLACE_ALL :
                sprintf (buf, "Made %d replacements.", nfound);
        }
        XmTextSetString (text_output, buf);
    }

  cleanup_find:
    if(string)
	XtFree (string);
    if(find_pat)
	XtFree (find_pat);
    if(new_pat)
	XtFree (new_pat);
}

/* edit_cb() -- the callback routine for the items in the edit menu */
void
edit_cb(widget, client_data, call_data)
Widget widget;  
XtPointer client_data;
XtPointer call_data;
{
    Boolean	result = True;
    int		reason = (int) client_data;
    XEvent	*event = ((XmPushButtonCallbackStruct *) call_data)->event;
    Time	when;

    XmTextSetString (text_output, NULL);   /* clear message area */

    if (event != NULL &&
        reason == EDIT_CUT || reason == EDIT_COPY || reason == EDIT_CLEAR) {
        switch (event->type) {
            case ButtonRelease :
	    case ButtonPress :
                when = event->xbutton.time;
                break;
            case KeyRelease :
	    case KeyPress :
                when = event->xkey.time;
                break;
            default:
                when = CurrentTime;
                break;
        }
    }

    switch (reason) {
        case EDIT_CUT : 
            result = XmTextCut (text_edit, when); 
            break;
        case EDIT_COPY : 
            result = XmTextCopy (text_edit, when); 
            break;
        case EDIT_PASTE : 
            result = XmTextPaste (text_edit);
	    break;
	case EDIT_UNDO:
	    undo_actual();
	    break;
        case EDIT_CLEAR : 
            XmTextClearSelection (text_edit, when); 
            break;
    }
    if (result == False)
        XmTextSetString (text_output, "There is no selection.");
}

unsigned long
find_line_idx(Widget textedit, unsigned long position)
{
    char *		text;
    unsigned long	len, i, lineno = 0;

    len = XmTextGetLastPosition (textedit);
    if(len < position){
	return(-1);
    }

    text = XmTextGetString (textedit);
    if(! text){
	XmTextSetString (text_output, "There is no Text.");
	return(-1);
    }

    for(i = 0; i < position; i++){
	if(text[i] == '\n')
	    lineno++;
    }

    XtFree(text);

    return(lineno);
}


/* view_cb() -- a menu item from the "View" pulldown menu selected */
void
view_cb(w, client_data, call_data)
Widget w;
XtPointer client_data;
XtPointer call_data;
{
    int			i, lineno, reason = (int) client_data;
    char		buf[100], *cptr, *text;
    XmTextPosition	first, last, cursorpos;
    Boolean		got_selection, got_cursor;
    Position		cursorline, selline, x;

    if(reason == VIEW_WHAT_LINE){
	got_selection = XmTextGetSelectionPosition(text_edit, &first, &last);
	if(first >= last)
	    got_selection = 0;
	if(got_selection){
	    selline = find_line_idx(text_edit, first) + 1;
	    got_selection = (selline > 0);
	}
	
	cursorpos = XmTextGetCursorPosition(text_edit);
	cursorline = find_line_idx(text_edit, cursorpos) + 1;
	got_cursor = (cursorline > 0);

	if(got_cursor && got_selection)
	    sprintf(buf, "Selection starts in line %lu, Cursor is in line %lu.",
			(unsigned long) selline, (unsigned long) cursorline);
	else if(got_cursor)
	    sprintf(buf, "Cursor is in line %lu.",
					(unsigned long) cursorline);
	else if(got_selection)
	    sprintf(buf, "Selection starts in line %lu.",
				(unsigned long) selline);
	else
	    sprintf(buf, "Strange: Can't get cursor position.");

	 XmTextSetString (text_output, buf);
    }
    if(reason == VIEW_GOTO_LINE){
	goto_line();
    }
}

void
goto_line()
{
  char		*cptr, buf[100], *text;
  int		i, lineno, cursorpos, j;
  XmTextPosition	oldpos;

    cptr = XmTextGetString(lineno_text);

    if(! cptr[0]){
        XtFree(cptr);
        return;
    }

    i = sscanf(cptr, "%d", &lineno);

    if(i < 1){
        sprintf(buf, "Cannot read a line number from \"%s\".", cptr);
        XtFree(cptr);
        XmTextSetString(text_output, buf);
        return;
    }

    XtFree(cptr);

    text = cptr = XmTextGetString(text_edit);
    cursorpos = 0;
    oldpos = XmTextGetCursorPosition(text_edit);

    for(i = 0; i < lineno - 1; i++){
        while(*cptr && *cptr != '\n'){
	    cptr++;
	    cursorpos++;
        }

        if(! *cptr){
	    sprintf(buf, "Text has only %d line%s.", i + 1, i ? "s" : "");
	    XmTextSetString (text_output, buf);
	    XtFree(text);
	    return;
        }

        cptr++;
        cursorpos++;
    }

    set_new_pos(text, oldpos, cursorpos);

    XtFree(text);

    XmTextSetString (text_output, "");

}

void
wraplines_cb(
  Widget	w,
  XtPointer	client_data,
  XtPointer	call_data)
{
    Boolean	wrap_lines;

    XtVaGetValues(wraplines_button, XmNset, &wrap_lines, NULL);

    XtVaSetValues(text_edit, XmNscrollHorizontal, wrap_lines,
			XmNwordWrap, True, NULL);
}

void
auto_indent_cb(
  Widget	w,
  XtPointer	client_data,
  XtPointer	call_data)
{
    XtVaGetValues(autoindent_button, XmNset, &auto_indent, NULL);
}

void
readonly_cb(
  Widget	w,
  XtPointer	client_data,
  XtPointer	call_data)
{
    XtVaGetValues(readonly_button, XmNset, &readonly, NULL);
}

void
save_on_hup(int sigc)
{
    char	buf[256], nam[256];

    signal(sigc, save_on_hup);

    if(act_filename)
	strcpy(nam, act_filename);
    else{
	tmpnam(nam);

	fprintf(stderr, "Got signal HUP, saved actual text as %s.\n", nam);
    }

    save_file(nam, buf);

    set_title();

    XmTextSetString(text_output, buf);
}

void
safe_exit(int sigc)
{
    char	buf[256];

    signal(sigc, safe_exit);

    if(! total_edits)
	exit(0);

    sprintf(buf, "Got signal %d, text has been modified, not exiting.",
			sigc);

    XmTextSetString(text_output, buf);

    XBell(XtDisplay(toplevel), 50);
}

int
dump_edits()
{
    FILE	*fp;
    int		i;

    fp = fopen(editdumpfile, "w");

    if(!fp)
	return(1);

    fprintf(fp, "%d %d %d %d %d %d %d %d \005%s\005\n",
		act_edit_nr, first_edit_nr,
		total_edits, motion_from, motion_to, moved, max_undos,
		(int) XmTextGetCursorPosition(text_edit),
		act_filename ? act_filename : "");

    for(i = 0; i < max_undos; i++){
	fprintf(fp, "%d %d %d %d \005%s\005 %d \005%s\005 %d %d\n",
		all_edits[i].ins_start, all_edits[i].ins_end,
		all_edits[i].del_start, all_edits[i].del_end,
		all_edits[i].del_string, all_edits[i].del_strlen,
		all_edits[i].ins_string, all_edits[i].ins_strlen,
		(int) all_edits[i].type);
    }

    fclose(fp);

    return(0);
}

int
load_edits()
{
    FILE	*fp;
    int		i, j, k, cpos;
    char	a, buf[256];

    fp = fopen(editrecfile, "r");

    if(!fp)
	return(1);

    if(fscanf(fp, "%d%d%d%d%d%d%d%d",
		&act_edit_nr, &first_edit_nr, &total_edits,
		&motion_from, &motion_to, &moved, &max_undos, &cpos)
				< 8){
	fclose(fp);
	return(1);
    }

    do{
	if(EOF == fscanf(fp, "%c", &a)){
	    fclose(fp);
	    return(1);
	}
    } while(a != '\005');

    k = -1;
    do{
	k++;

	if(EOF == fscanf(fp, "%c", buf + k)){
	    fclose(fp);
	    return(1);
	}
    } while(buf[k] != '\005');

    buf[k] = '\0';

    act_filename = strdup(buf);
    all_edits = (struct edit_record *)
		malloc(max_undos * sizeof(struct edit_record));
    if(!all_edits){
	fclose(fp);
	return(1);
    }

    for(i = 0; i < max_undos; i++){
	if(fscanf(fp, "%d%d%d%d", 
		&(all_edits[i].ins_start), &(all_edits[i].ins_end),
		&(all_edits[i].del_start), &(all_edits[i].del_end))
			< 4){
	    fclose(fp);
	    return(1);
	}

	do{
	    if(EOF == fscanf(fp, "%c", &a)){
		fclose(fp);
		return(1);
	    }

	} while(a != '\005');

	k = -1;
	do{
	    k++;

	    if(EOF == fscanf(fp, "%c", buf + k)){
		fclose(fp);
		return(1);
	    }
	} while(buf[k] != '\005');

	buf[k] = '\0';

	all_edits[i].del_string = strdup(buf);
	if(! all_edits[i].del_string){
	    fclose(fp);
	    return(1);
	}

	if(fscanf(fp, "%d", &(all_edits[i].del_strlen)) < 1){
	    fclose(fp);
	    return(1);
	}

	do{
	    if(EOF == fscanf(fp, "%c", &a)){
		fclose(fp);
		return(1);
	    }

	} while(a != '\005');

	k = -1;
	do{
	    k++;

	    if(EOF == fscanf(fp, "%c", buf + k)){
		fclose(fp);
		return(1);
	    }
	} while(buf[k] != '\005');

	buf[k] = '\0';

	all_edits[i].ins_string = strdup(buf);
	if(! all_edits[i].ins_string){
	    fclose(fp);
	    return(1);
	}

	if(fscanf(fp, "%d%d", &(all_edits[i].ins_strlen), &j) < 2){
	    fclose(fp);
	    return(1);
	}

	all_edits[i].type = (char) j;
    }

    fclose(fp);

    XmTextSetCursorPosition(text_edit, cpos);
    XmTextSetInsertionPosition(text_edit, cpos);

    return(0);
}

void
emergency_exit(int sigc)
{
    int		i, len;
    FILE	*fp;
    char	buf[256];
    char	*text;

    fprintf(stderr, "textedit panic: got signal %d !\n", sigc);
    fprintf(stderr, "Trying to save text and edits, so that you can\n");
    fprintf(stderr, "recover everything with the command:\n\n%s\n\n",
			recovercmd);
    fprintf(stderr, "(You can cut & paste the line above).\n");
    fprintf(stderr, "Trying to dump the text as %s...\n", textdumpfile);

    text = XmTextGetString(text_edit);
    len = XmTextGetLastPosition(text_edit);
    fp = fopen(textdumpfile, "w");
    i = (fp != NULL) && (fwrite(text, sizeof(char), len, fp) == len);
    fclose(fp);
    XtFree(text);

    fprintf(stderr, ! i ? "failed.\n" : "succeeded.\n");
    fprintf(stderr, "Trying to dump your modifications as %s...\n",
			editdumpfile);
    i = dump_edits();
    fprintf(stderr, i ? "failed.\n" : "succeeded.\n");
    fprintf(stderr, "Trying to auto-recover...\n");
    system(recovercmd);

    exit(0);
}

void
client_msg_handler(
  Widget	w,
  XtPointer	client_data,
  XtPointer	call_data)
{
  XmAnyCallbackStruct	*cbs;

  cbs = (XmAnyCallbackStruct *) call_data;

  if(cbs->reason != XmCR_WM_PROTOCOLS)
    return;

  maybe_exit();
}

void
set_wm_interactions()
{
  int	i;

  wm_delete_window = XmInternAtom(XtDisplay(toplevel), "WM_DELETE_WINDOW",
				False);
  XmAddWMProtocolCallback(toplevel, wm_delete_window, client_msg_handler,
				NULL);
}

void
usage(char * progname)
{
    fprintf(stderr, "usage: %s [ +|-ai ] [ +|-ro ] [ +|-nb ] [ <filename> ]\n",
		progname);
    fprintf(stderr, "            ai stands for auto-indentation,\n");
    fprintf(stderr, "            nb stands for no backup (no *%%-file),\n");
    fprintf(stderr, "            ro stands for read-only option.\n");

    exit(0);
}

void
termproc()
{
    get_options();

    if(!recover && !toplevel && exitflag == OPENING_DISPLAY){
	char	*altedit;

	altedit = getenv("ALTEDITOR");
	if(!altedit)
	    altedit = "vi";

	execlp(altedit, altedit, act_filename, NULL);
    }
}

void
get_extras_menu()
{
  char		*homeacc;
  FILE		*fp;
  char		*extras_file;
  char		linebuf[1000];
  char		*cptr, *cptr2, *cptr3;
  char		quoted;

  homeacc = getenv("HOME");
  if(!homeacc)
    return;

  extras_file = malloc(sizeof(char) *
			(strlen(homeacc) + 2 + strlen(TEXT_EXTRAS_FILE)));

  if(!extras_file)
    return;

  strcpy(extras_file, homeacc);
  strcat(extras_file, "/");
  strcat(extras_file, TEXT_EXTRAS_FILE);

  fp = fopen(extras_file, "r");
  if(!fp)
    return;

  for(;;){
    if(!fgets(linebuf, 999, fp))
	break;

    linebuf[999] = '\0';

    while(linebuf[strlen(linebuf) - 1] == '\n')
	linebuf[strlen(linebuf) - 1] = '\0';

    if(!extras_cmds)
	extras_cmds = malloc(sizeof(char*));
    else
	extras_cmds = realloc(extras_cmds,
				(num_extras + 1) * sizeof(char*));
    if(!extras_names)
	extras_names = malloc(sizeof(char*));
    else
	extras_names = realloc(extras_names,
				(num_extras + 1) * sizeof(char*));

    cptr = linebuf;
    while(*cptr && isspace(*cptr))
	cptr++;

    if(!*cptr)
	continue;

    cptr2 = cptr;
    quoted = 0;
    while(*cptr2 && (!isspace(*cptr2) || quoted)){
	if(*cptr2 == '\"'){
	  quoted = !quoted;
	  for(cptr3 = cptr2; *cptr3; cptr3++)
	    *cptr3 = *(cptr3 + 1);
	  cptr2--;
	}
	cptr2++;
    }

    if(!*cptr2){
	fprintf(stderr, "Line in %s has wrong format:\n%s\n",
			TEXT_EXTRAS_FILE, linebuf);
	continue;
    }

    *cptr2 = '\0';
    extras_names[num_extras] = strdup(cptr);

    if(!extras_names[num_extras]){
	fprintf(stderr, "Error: cannot allocate memory for extras.\n");
	exit(1);
    }

    cptr = cptr2 + 1;
    while(*cptr && isspace(*cptr))
	cptr++;

    if(!*cptr){
	fprintf(stderr, "Line in %s has wrong format:\n%s\n",
			TEXT_EXTRAS_FILE, linebuf);
	continue;
    }

    extras_cmds[num_extras] = strdup(cptr);
    if(!extras_cmds[num_extras]){
	fprintf(stderr, "Error: cannot allocate memory for extras.\n");
	exit(1);
    }

    num_extras++;
  }

  fclose(fp);
}

void
extras_proc(
  Widget	w,
  XtPointer	client_data,
  XtPointer	call_data)
{
  int	idx, namp, selected, i, pipe_in;
  FILE	*pp;
  char	buf[1000], cmd[1050];
  char	*text;
  XmTextPosition	first, last, cursorpos;
  struct stat	statb;

  idx = (int) client_data;

  if(extras_cmds[idx][0] == '|')
    pipe_in = 1;
  else
    pipe_in = 0;

  if(!pipe_in){
    sprintf(cmd, "%s > ", extras_cmds[idx]);
    namp = strlen(cmd);
    tmpnam(cmd + namp);
  }
  else{
    strcpy(cmd, extras_cmds[idx] + 1);
  }
  pp = popen(cmd, "w");
  if(!pp){
    sprintf(buf, "Error: cannot execute command %s", extras_cmds[idx]);
    XmTextSetString(text_output, buf);
    return;
  }

  first = last = 0;
  selected = XmTextGetSelectionPosition(text_edit, &first, &last);
  if(selected && first >= last)
	selected = 0;

  cursorpos = XmTextGetInsertionPosition(text_edit);

  text = XmTextGetString(text_edit);
  if(!selected){
    first = 0;
    last = XmTextGetLastPosition(text_edit);
  }

  fwrite(text + first, 1, last - first, pp);

  pclose(pp);
  XtFree(text);

  if(pipe_in){
    unlink(cmd + namp);
    return;
  }

  i = stat(cmd + namp, &statb);
  pp = fopen(cmd + namp, "r");
  if(i < 0 || !pp){
    if(pp)
	fclose(pp);
    sprintf(buf, "Error: cannot execute command %s", extras_cmds[idx]);
    XmTextSetString(text_output, buf);
    unlink(cmd + namp);
    return;
  }

  text = malloc(sizeof(char) * (statb.st_size + 1));
  if(!text){
    fprintf(stderr, "Error: cannot allocate memory.\n");
    unlink(cmd + namp);
    exit(9);
  }

  i = fread(text, 1, statb.st_size, pp);
  text[statb.st_size] = '\0';

  fclose(pp);
  unlink(cmd + namp);

  if(i < statb.st_size){
    sprintf(buf, "Error: cannot execute command %s", extras_cmds[idx]);
    XmTextSetString(text_output, buf);
    XtFree(text);
    return;
  }

  XmTextSetInsertionPosition(text_edit, last);
  XmTextSetCursorPosition(text_edit, last);
  XmTextReplace(text_edit, first, last, text);
  if(selected){
    XmTextSetInsertionPosition(text_edit, first + statb.st_size);
    XmTextSetCursorPosition(text_edit, first + statb.st_size);
  }
  else{
    XmTextSetInsertionPosition(text_edit, cursorpos);
    XmTextSetCursorPosition(text_edit, cursorpos);
  }

  XtFree(text);
}

