Home | History | Annotate | Line # | Download | only in dist
      1 /* $XConsortium: commands.c,v 1.33 91/10/21 14:32:18 eswu Exp $ */
      2 
      3 /*
      4  *			  COPYRIGHT 1987
      5  *		   DIGITAL EQUIPMENT CORPORATION
      6  *		       MAYNARD, MASSACHUSETTS
      7  *			ALL RIGHTS RESERVED.
      8  *
      9  * THE INFORMATION IN THIS SOFTWARE IS SUBJECT TO CHANGE WITHOUT NOTICE AND
     10  * SHOULD NOT BE CONSTRUED AS A COMMITMENT BY DIGITAL EQUIPMENT CORPORATION.
     11  * DIGITAL MAKES NO REPRESENTATIONS ABOUT THE SUITABILITY OF THIS SOFTWARE FOR
     12  * ANY PURPOSE.  IT IS SUPPLIED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY.
     13  *
     14  * IF THE SOFTWARE IS MODIFIED IN A MANNER CREATING DERIVATIVE COPYRIGHT RIGHTS,
     15  * APPROPRIATE LEGENDS MAY BE PLACED ON THE DERIVATIVE WORK IN ADDITION TO THAT
     16  * SET FORTH ABOVE.
     17  *
     18  *
     19  * Permission to use, copy, modify, and distribute this software and its
     20  * documentation for any purpose and without fee is hereby granted, provided
     21  * that the above copyright notice appear in all copies and that both that
     22  * copyright notice and this permission notice appear in supporting
     23  * documentation, and that the name of Digital Equipment Corporation not be
     24  * used in advertising or publicity pertaining to distribution of the software
     25  * without specific, written prior permission.
     26  */
     27 /* $XFree86: xc/programs/xedit/commands.c,v 1.29tsi Exp $ */
     28 
     29 #include <X11/Xfuncs.h>
     30 #include <X11/Xos.h>
     31 #include "xedit.h"
     32 #include <stdlib.h>
     33 #include <stdio.h>
     34 #include <limits.h>
     35 #include <string.h>
     36 #include <dirent.h>
     37 #include <pwd.h>
     38 #include <sys/stat.h>
     39 #include <X11/Xmu/SysUtil.h>
     40 #include <X11/IntrinsicP.h>
     41 #include <X11/Xaw/TextSrcP.h>
     42 
     43 /* Turn a NULL pointer string into an empty string */
     44 #define NULLSTR(x) (((x)!=NULL)?(x):(""))
     45 
     46 #define Error(x) { printf x ; exit(EXIT_FAILURE); }
     47 #define Assertion(expr, msg) { if (!(expr)) { Error msg } }
     48 #define Log(x)   { if (True) printf x; }
     49 
     50 void ResetSourceChanged(xedit_flist_item*);
     51 static void ResetDC(Widget, XtPointer, XtPointer);
     52 
     53 static void AddDoubleClickCallback(Widget, Bool);
     54 static Bool ReallyDoLoad(char*, char*);
     55 static char *makeBackupName(String, String, unsigned);
     56 
     57 /*
     58  * External
     59  */
     60 extern void _XawTextShowPosition(TextWidget);
     61 
     62 extern Widget scratch, texts[3], labels[3];
     63 
     64 #define DC_UNSAVED	(1 << 0)
     65 #define DC_LOADED	(1 << 1)
     66 #define DC_CLOBBER	(1 << 2)
     67 #define DC_KILL		(1 << 3)
     68 #define DC_SAVE		(1 << 4)
     69 #define DC_NEWER	(1 << 5)
     70 static int dc_state;
     71 
     72 static void
     73 AddDoubleClickCallback(Widget w, Bool state)
     74 {
     75     if (state)
     76 	XtAddCallback(w, XtNcallback, ResetDC, NULL);
     77     else
     78 	XtRemoveCallback(w, XtNcallback, ResetDC, NULL);
     79 }
     80 
     81 /*	Function Name: ResetDC
     82  *	Description: Resets the double click flag.
     83  *	Arguments: w - the text widget.
     84  *                 junk, garbage - *** NOT USED ***
     85  *	Returns: none.
     86  */
     87 
     88 /* ARGSUSED */
     89 static void
     90 ResetDC(Widget w, XtPointer junk, XtPointer garbage)
     91 {
     92     AddDoubleClickCallback(w, FALSE);
     93 }
     94 
     95 /*ARGSUSED*/
     96 void
     97 QuitAction(Widget w, XEvent *event, String *params, Cardinal *num_params)
     98 {
     99     DoQuit(w, NULL, NULL);
    100 }
    101 
    102 /*ARGSUSED*/
    103 void
    104 DoQuit(Widget w, XtPointer client_data, XtPointer call_data)
    105 {
    106     unsigned i;
    107     Bool source_changed = False;
    108 
    109     if (!(dc_state & DC_UNSAVED)) {
    110 	for (i = 0; i < flist.num_itens; i++)
    111 	    if (flist.itens[i]->flags & CHANGED_BIT) {
    112 		source_changed = True;
    113 		break;
    114 	    }
    115     }
    116     if (!source_changed) {
    117 	XeditLispCleanUp();
    118 	exit(0);
    119     }
    120 
    121     XeditPrintf("Unsaved changes. Save them, or Quit again.\n");
    122     Feep();
    123     dc_state |= DC_UNSAVED;
    124     AddDoubleClickCallback(XawTextGetSource(textwindow), True);
    125 }
    126 
    127 static char *
    128 makeBackupName(String buf, String filename, unsigned len)
    129 {
    130     if (app_resources.backupNamePrefix
    131 	&& strlen(app_resources.backupNamePrefix)) {
    132 	if (strchr(app_resources.backupNamePrefix, '/'))
    133 	    XmuSnprintf(buf, len, "%s%s%s", app_resources.backupNamePrefix,
    134 			filename, app_resources.backupNameSuffix);
    135 	else {
    136 	    char fname[BUFSIZ];
    137 	    char *name, ch;
    138 
    139 	    strncpy(fname, filename, sizeof(fname) - 1);
    140 	    fname[sizeof(fname) - 1] = '\0';
    141 	    if ((name = strrchr(fname, '/')) != NULL)
    142 		++name;
    143 	    else
    144 		name = filename;
    145 	    ch = *name;
    146 	    *name = '\0';
    147 	    ++name;
    148 	    XmuSnprintf(buf, len, "%s%s%c%s%s",
    149 			fname, app_resources.backupNamePrefix, ch, name,
    150 			app_resources.backupNameSuffix);
    151 	}
    152     }
    153     else
    154 	XmuSnprintf(buf, len, "%s%s",
    155 		    filename, app_resources.backupNameSuffix);
    156 
    157     return (strcmp(filename, buf) ? buf : NULL);
    158 }
    159 
    160 /*ARGSUSED*/
    161 void
    162 SaveFile(Widget w, XEvent *event, String *params, Cardinal *num_params)
    163 {
    164     if (line_edit) {
    165 	/* Don't try to save buffer with regex string.
    166 	 * Call CancelFindFile() to leave line_edit mode.
    167 	 */
    168 	XeditPrintf("Save: Leaving line edit mode -- nothing saved.\n");
    169 	CancelFindFile(w, event, params, num_params);
    170 	Feep();
    171     }
    172     else
    173 	DoSave(w, NULL, NULL);
    174 }
    175 
    176 /*ARGSUSED*/
    177 void
    178 DoSave(Widget w, XtPointer client_data, XtPointer call_data)
    179 {
    180     String name = GetString(filenamewindow);
    181     String filename = ResolveName(name);
    182     FileAccess file_access;
    183     xedit_flist_item *item;
    184     Boolean exists;
    185     Widget source = XawTextGetSource(textwindow);
    186     char buffer[BUFSIZ];
    187     struct stat st;
    188     static const char *nothing_saved = " -- nothing saved.\n";
    189 
    190     if (!filename) {
    191 	XmuSnprintf(buffer, sizeof(buffer), "%s%s",
    192 		    "Save: Can't resolve pathname",  nothing_saved);
    193 	goto error;
    194     }
    195     else if (*name == '\0') {
    196 	XmuSnprintf(buffer, sizeof(buffer), "%s%s",
    197 		    "Save: No filename specified", nothing_saved);
    198 	goto error;
    199     }
    200 
    201     item = FindTextSource(NULL, filename);
    202     if (item != NULL && item->source != source) {
    203 	if (!(dc_state & DC_LOADED)) {
    204 	    XmuSnprintf(buffer, sizeof(buffer), "%s%s%s%s",
    205 			"Save: file ", name, " is already loaded, "
    206 			"Save again to unload it", nothing_saved);
    207 	    Feep();
    208 	    dc_state |= DC_LOADED;
    209 	    AddDoubleClickCallback(XawTextGetSource(textwindow), True);
    210 	    goto error;
    211 	}
    212 	else {
    213 	    KillTextSource(item);
    214 	    item = FindTextSource(source = XawTextGetSource(textwindow), NULL);
    215 	    dc_state &= ~DC_LOADED;
    216 	}
    217     }
    218     else if (item && !(item->flags & CHANGED_BIT)) {
    219 	if (!(dc_state & DC_SAVE)) {
    220 	    XmuSnprintf(buffer, sizeof(buffer), "%s%s",
    221 			"Save: No changes need to be saved, "
    222 			"save again to override", nothing_saved);
    223 	    Feep();
    224 	    dc_state |= DC_SAVE;
    225 	    AddDoubleClickCallback(XawTextGetSource(textwindow), True);
    226 	    goto error;
    227 	}
    228 	else
    229 	    dc_state &= ~DC_SAVE;
    230     }
    231 
    232     file_access = CheckFilePermissions(filename, &exists);
    233     if (exists) {
    234 	if (stat(filename, &st) != 0) {
    235 	    XmuSnprintf(buffer, sizeof(buffer), "%s%s%s",
    236 			"Save: cannot stat ", name, nothing_saved);
    237 	    goto error;
    238 	}
    239 	else if (!S_ISREG(st.st_mode)) {
    240 	    XmuSnprintf(buffer, sizeof(buffer), "%s%s%s%s",
    241 			"Save: file ", name, "is not a regular file",
    242 			nothing_saved);
    243 	    goto error;
    244 	}
    245     }
    246 
    247     if (!item || strcmp(item->filename, filename)) {
    248 	if (file_access == WRITE_OK && exists) {
    249 	    if (!(dc_state & DC_CLOBBER)) {
    250 		XmuSnprintf(buffer, sizeof(buffer), "%s%s%s%s",
    251 			    "Save: file ", name, " already exists, "
    252 			    "save again to override", nothing_saved);
    253 		Feep();
    254 		dc_state |= DC_CLOBBER;
    255 		AddDoubleClickCallback(XawTextGetSource(textwindow), True);
    256 		goto error;
    257 	    }
    258 	    else
    259 		dc_state &= ~DC_CLOBBER;
    260 	}
    261 	if (!item)
    262 	    item = FindTextSource(source, NULL);
    263     }
    264 
    265     if (item && item->mtime && exists && item->mtime < st.st_mtime) {
    266 	if (!(dc_state & DC_NEWER)) {
    267 	    XmuSnprintf(buffer, sizeof(buffer), "%s%s",
    268 			"Save: Newer file exists, "
    269 			"save again to override", nothing_saved);
    270 	    Feep();
    271 	    dc_state |= DC_NEWER;
    272 	    AddDoubleClickCallback(XawTextGetSource(textwindow), True);
    273 	    goto error;
    274 	}
    275 	else
    276 	    dc_state &= DC_NEWER;
    277     }
    278 
    279     if (app_resources.enableBackups && exists) {
    280 	char backup_file[BUFSIZ];
    281 
    282 	if (makeBackupName(backup_file, filename, sizeof(backup_file)) == NULL
    283 	    || rename(filename, backup_file) != 0) {
    284 	    XeditPrintf("Error backing up file: %s\n", filename);
    285 	}
    286     }
    287 
    288     switch (file_access = MaybeCreateFile(filename)) {
    289 	case NO_READ:
    290 	case READ_OK:
    291 	    XeditPrintf("File %s could not be opened for writing.\n", name);
    292 	    Feep();
    293 	    break;
    294 	case WRITE_OK:
    295 	    if (XawAsciiSaveAsFile(source, filename)) {
    296 		int i;
    297 		Arg args[1];
    298 
    299 		XmuSnprintf(buffer, sizeof(buffer),
    300 			    "%s       Read - Write", name);
    301 		XtSetArg(args[0], XtNlabel, buffer);
    302 		for (i = 0; i < 3; i++)
    303 		    if (XawTextGetSource(texts[i]) == source)
    304 			XtSetValues(labels[i], args, 1);
    305 
    306 		XeditPrintf("Saved file: %s\n", name);
    307 
    308 		if (item && item->source != scratch) {
    309 		    XtSetArg(args[0], XtNlabel, filename);
    310 		    XtSetValues(item->sme, args, 1);
    311 
    312 		    XtSetArg(args[0], XtNeditType, XawtextEdit);
    313 		    XtSetValues(item->source, args, 1);
    314 
    315 		    XtFree(item->name);
    316 		    XtFree(item->filename);
    317 		    item->name = XtNewString(name);
    318 		    item->filename = XtNewString(filename);
    319 		    item->flags = EXISTS_BIT;
    320 		}
    321 		else {
    322 		    item = flist.itens[0];
    323 		    XtRemoveCallback(scratch, XtNcallback, SourceChanged,
    324 				     (XtPointer)item);
    325 		    item->source = scratch =
    326 		    XtVaCreateWidget("textSource", international ?
    327 				     multiSrcObjectClass :
    328 				     asciiSrcObjectClass,
    329 				     topwindow,
    330 				     XtNtype, XawAsciiFile,
    331 				     XtNeditType, XawtextEdit,
    332 				     NULL, NULL);
    333 		    ResetSourceChanged(item);
    334 		    XtAddCallback(scratch, XtNcallback, SourceChanged,
    335 				  (XtPointer)item);
    336 
    337 		    item = AddTextSource(source, name, filename, EXISTS_BIT,
    338 					 file_access);
    339 		    XtAddCallback(item->source, XtNcallback, SourceChanged,
    340 				  (XtPointer)item);
    341 		}
    342 
    343 		/* Keep file protection mode */
    344 		if (item->mode)
    345 		    chmod(filename, item->mode);
    346 
    347 		/* Remember time of last modification */
    348 		if (stat(filename, &st) == 0)
    349 		    item->mtime = st.st_mtime;
    350 
    351 		item->flags |= EXISTS_BIT;
    352 		ResetSourceChanged(item);
    353 	    }
    354 	    else {
    355 		XeditPrintf("Error saving file: %s\n",  name);
    356 		Feep();
    357 	    }
    358 	    break;
    359 	default:
    360 	    Feep();
    361 	    break;
    362     }
    363 
    364     return;
    365 error:
    366     XeditPrintf("%s", buffer);
    367     Feep();
    368 }
    369 
    370 /*ARGSUSED*/
    371 void
    372 DoLoad(Widget w, XtPointer client_data, XtPointer call_data)
    373 {
    374     if (ReallyDoLoad(GetString(filenamewindow), ResolveName(NULL))) {
    375         SwitchDirWindow(False);
    376         XtSetKeyboardFocus(topwindow, textwindow);
    377     }
    378 }
    379 
    380 Bool
    381 LoadFileInTextwindow(char *name, char *resolved_name)
    382 {
    383     return (ReallyDoLoad(name, resolved_name));
    384 }
    385 
    386 static Bool
    387 ReallyDoLoad(char *name, char *filename)
    388 {
    389     Arg args[5];
    390     Cardinal num_args = 0;
    391     xedit_flist_item *item;
    392     Widget source = XawTextGetSource(textwindow);
    393 
    394     if (!filename) {
    395 	XeditPrintf("Load: Can't resolve pathname.\n");
    396 	Feep();
    397 	return (False);
    398     }
    399     else if (*name == '\0') {
    400 	XeditPrintf("Load: No file specified.\n");
    401 	Feep();
    402     }
    403     if ((item = FindTextSource(NULL, filename)) != NULL) {
    404 	SwitchTextSource(item);
    405 	return (True);
    406     }
    407     else {
    408 	struct stat st;
    409 
    410 	if (stat(filename, &st) == 0 && !S_ISREG(st.st_mode)) {
    411 	    if (S_ISDIR(st.st_mode)) {
    412 		char path[BUFSIZ + 1];
    413 
    414 		strncpy(path, filename, sizeof(path) - 2);
    415 		path[sizeof(path) - 2] = '\0';
    416 		if (*path) {
    417 		    if (path[strlen(path) - 1] != '/')
    418 			strcat(path, "/");
    419 		}
    420 		else
    421 		    strcpy(path, "./");
    422 		XtSetArg(args[0], XtNlabel, "");
    423 		XtSetValues(dirlabel, args, 1);
    424 		SwitchDirWindow(True);
    425 		DirWindowCB(dirwindow, path, NULL);
    426 		return (False);
    427 	    }
    428 	}
    429     }
    430 
    431     {
    432 	Boolean exists;
    433 	int flags;
    434 	FileAccess file_access;
    435 
    436 	switch( file_access = CheckFilePermissions(filename, &exists) ) {
    437 	case NO_READ:
    438 	    if (exists)
    439 		XeditPrintf("File %s, %s", name,
    440 			    "exists, and could not be opened for reading.\n");
    441 	    else
    442 		XeditPrintf("File %s %s %s",  name,
    443 			    "does not exist, and",
    444 			    "the directory could not be opened for writing.\n");
    445 
    446 	    Feep();
    447 	    return (False);
    448 	case READ_OK:
    449 	    XtSetArg(args[num_args], XtNeditType, XawtextRead); num_args++;
    450 	    XeditPrintf("File %s opened READ ONLY.\n", name);
    451 	    break;
    452 	case WRITE_OK:
    453 	    XtSetArg(args[num_args], XtNeditType, XawtextEdit); num_args++;
    454 	    XeditPrintf("File %s opened read - write.\n", name);
    455 	    break;
    456 	default:
    457 	    Feep();
    458 	    return (False);
    459 	}
    460 
    461 	if (exists) {
    462 	    flags = EXISTS_BIT;
    463 	    XtSetArg(args[num_args], XtNstring, filename); num_args++;
    464 	}
    465 	else {
    466 	    flags = 0;
    467 	    XtSetArg(args[num_args], XtNstring, NULL); num_args++;
    468 	}
    469 
    470 	source = XtVaCreateWidget("textSource", international ?
    471 				  multiSrcObjectClass :
    472 				  asciiSrcObjectClass,
    473 				  topwindow,
    474 				  XtNtype, XawAsciiFile,
    475 				  XtNeditType, XawtextEdit,
    476 				  NULL, NULL);
    477 	XtSetValues(source, args, num_args);
    478 
    479 	item = AddTextSource(source, name, filename, flags, file_access);
    480 	XtAddCallback(item->source, XtNcallback, SourceChanged,
    481 		      (XtPointer)item);
    482 	if (exists && file_access == WRITE_OK) {
    483 	    struct stat st;
    484 
    485 	    if (stat(item->filename, &st) == 0) {
    486 		item->mode = st.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO);
    487 		item->mtime = st.st_mtime;
    488 	    }
    489 	}
    490 	SwitchTextSource(item);
    491 	ResetSourceChanged(item);
    492     }
    493 
    494     return (True);
    495 }
    496 
    497 /*	Function Name: SourceChanged
    498  *	Description: A callback routine called when the source has changed.
    499  *	Arguments: w - the text source that has changed.
    500  *		   client_data - xedit_flist_item associated with text buffer.
    501  *                 call_data - NULL is unchanged
    502  *	Returns: none.
    503  */
    504 /*ARGSUSED*/
    505 void
    506 SourceChanged(Widget w, XtPointer client_data, XtPointer call_data)
    507 {
    508     xedit_flist_item *item = (xedit_flist_item*)client_data;
    509     Bool changed = (Bool)(long)call_data;
    510 
    511     if (changed) {
    512 	if (item->flags & CHANGED_BIT)
    513 	    return;
    514 	item->flags |= CHANGED_BIT;
    515     }
    516     else {
    517 	if (item->flags & CHANGED_BIT)
    518 	    ResetSourceChanged(item);
    519 	return;
    520     }
    521 
    522     if (flist.pixmap) {
    523 	Arg args[1];
    524 	Cardinal num_args;
    525 	int i;
    526 
    527 	num_args = 0;
    528 	XtSetArg(args[num_args], XtNleftBitmap, flist.pixmap);	++num_args;
    529 	XtSetValues(item->sme, args, num_args);
    530 
    531 	for (i = 0; i < 3; i++)
    532 	    if (XawTextGetSource(texts[i]) == item->source)
    533 		XtSetValues(labels[i], args, num_args);
    534     }
    535 }
    536 
    537 /*	Function Name: ResetSourceChanged.
    538  *	Description: Sets the source changed to FALSE, and
    539  *                   registers a callback to set it to TRUE when
    540  *                   the source has changed.
    541  *	Arguments: item - item with widget to register the callback on.
    542  *	Returns: none.
    543  */
    544 
    545 void
    546 ResetSourceChanged(xedit_flist_item *item)
    547 {
    548     Arg args[1];
    549     Cardinal num_args;
    550     int i;
    551 
    552     num_args = 0;
    553     XtSetArg(args[num_args], XtNleftBitmap, None);	++num_args;
    554     XtSetValues(item->sme, args, num_args);
    555 
    556     dc_state = 0;
    557     for (i = 0; i < 3; i++) {
    558 	if (XawTextGetSource(texts[i]) == item->source)
    559 	    XtSetValues(labels[i], args, num_args);
    560 	AddDoubleClickCallback(XawTextGetSource(texts[i]), False);
    561     }
    562 
    563     num_args = 0;
    564     XtSetArg(args[num_args], XtNsourceChanged, False);	++num_args;
    565     XtSetValues(item->source, args, num_args);
    566 
    567     item->flags &= ~CHANGED_BIT;
    568 }
    569 
    570 /*ARGSUSED*/
    571 void
    572 KillFile(Widget w, XEvent *event, String *params, Cardinal *num_params)
    573 {
    574     xedit_flist_item *item = FindTextSource(XawTextGetSource(textwindow), NULL);
    575 
    576     if (item->source == scratch) {
    577 	Feep();
    578 	return;
    579     }
    580 
    581     if (item->flags & CHANGED_BIT) {
    582 	if (!(dc_state & DC_KILL)) {
    583 	    XeditPrintf("Kill: Unsaved changes. Kill again to override.\n");
    584 	    Feep();
    585 	    dc_state |= DC_KILL;
    586 	    AddDoubleClickCallback(XawTextGetSource(textwindow), True);
    587 	    return;
    588 	}
    589 	dc_state &= ~DC_KILL;
    590     }
    591     KillTextSource(item);
    592 }
    593 
    594 /*ARGSUSED*/
    595 void
    596 FindFile(Widget w, XEvent *event, String *params, Cardinal *num_params)
    597 {
    598     char *string;
    599     char *slash;
    600     XawTextBlock block;
    601     XawTextPosition end = XawTextSourceScan(XawTextGetSource(filenamewindow),
    602 					    0, XawstAll, XawsdRight, 1, True);
    603 
    604     slash = NULL;
    605     if (!line_edit) {
    606 	string = GetString(filenamewindow);
    607 	if (string)
    608 	    slash = strrchr(string, '/');
    609     }
    610     else {
    611 	string = "";
    612 	line_edit = False;
    613     }
    614 
    615     block.firstPos = 0;
    616     block.format = FMT8BIT;
    617     block.ptr = string;
    618     block.length = slash ? slash - string + 1 : 0;
    619 
    620     if (block.length != end)
    621 	XawTextReplace(filenamewindow, 0, end, &block);
    622     XawTextSetInsertionPoint(filenamewindow, end);
    623     XtSetKeyboardFocus(topwindow, filenamewindow);
    624 }
    625 
    626 /*ARGSUSED*/
    627 void
    628 LoadFile(Widget w, XEvent *event, String *params, Cardinal *num_params)
    629 {
    630     if (line_edit)
    631 	LineEdit(textwindow);
    632     else if (ReallyDoLoad(GetString(filenamewindow), ResolveName(NULL))) {
    633 	SwitchDirWindow(False);
    634 	XtSetKeyboardFocus(topwindow, textwindow);
    635     }
    636 }
    637 
    638 /*ARGSUSED*/
    639 void
    640 CancelFindFile(Widget w, XEvent *event, String *params, Cardinal *num_params)
    641 {
    642     Arg args[1];
    643     xedit_flist_item *item;
    644 
    645     XtSetKeyboardFocus(topwindow, textwindow);
    646 
    647     item = FindTextSource(XawTextGetSource(textwindow), NULL);
    648 
    649     if (item->source != scratch)
    650 	XtSetArg(args[0], XtNstring, item->name);
    651     else
    652 	XtSetArg(args[0], XtNstring, NULL);
    653 
    654     XtSetValues(filenamewindow, args, 1);
    655     /* XXX This probably should be done by the TextWidget, i.e. notice
    656      * if the cursor became inivisible due to an horizontal scroll */
    657     _XawTextShowPosition((TextWidget)filenamewindow);
    658 
    659    if (XtIsManaged(XtParent(dirwindow)))
    660 	SwitchDirWindow(False);
    661 
    662     line_edit = False;
    663 }
    664 
    665 static int
    666 compar(_Xconst void *a, _Xconst void *b)
    667 {
    668     return (strcmp(*(char **)a, *(char **)b));
    669 }
    670 
    671 /*ARGSUSED*/
    672 void
    673 FileCompletion(Widget w, XEvent *event, String *params, Cardinal *num_params)
    674 {
    675     XawTextBlock block;
    676     String text;
    677     int length;
    678     char **matches, *save, *dir_name, *file_name, match[257];
    679     unsigned n_matches, len, mlen, buflen;
    680     DIR *dir;
    681     Bool changed, slash = False, has_dot = False;
    682 #define	SM_NEVER	0
    683 #define SM_HINT		1
    684 #define SM_ALWAYS	2
    685     int show_matches;
    686 
    687     text = GetString(filenamewindow);
    688 
    689     if (!text) {
    690 	Feep();
    691 	return;
    692     }
    693     else if (line_edit) {
    694 	Feep();
    695 	line_edit = 0;
    696     }
    697 
    698     {
    699 	XawTextPosition pos = XawTextGetInsertionPoint(w);
    700 	char *cslash = strchr(&text[pos], '/'), *cdot = strchr(&text[pos], '.');
    701 
    702 	if (cslash != NULL || cdot != NULL) {
    703 	    if (cslash != NULL && (cdot == NULL || cdot > cslash)) {
    704 		length = cslash - text;
    705 		slash = True;
    706 	    }
    707 	    else {
    708 		length = cdot - text;
    709 		has_dot = True;
    710 	    }
    711 	}
    712 	else
    713 	    length = strlen(text);
    714     }
    715 
    716     if (*num_params == 1 && length == strlen(text)) {
    717 	switch (params[0][0]) {
    718 	case 'n':		/* Never */
    719 	case 'N':
    720 	    show_matches = SM_NEVER;
    721 	    break;
    722 	case 'h':		/* Hint */
    723 	case 'H':
    724 	    show_matches = SM_HINT;
    725 	    break;
    726 	case 'a':		/* Always */
    727 	case 'A':
    728 	    show_matches = SM_ALWAYS;
    729 	    break;
    730 	default:
    731 	    show_matches = SM_NEVER;
    732 	    XtAppWarning(XtWidgetToApplicationContext(w),
    733 			 "Bad argument to file-completion, "
    734 			 "must be Never, Hint or Always");
    735 	    break;
    736 	}
    737     }
    738     else
    739 	show_matches = SM_NEVER;
    740 
    741     matches = NULL;
    742     n_matches = buflen = 0;
    743     save = XtMalloc(length + 1);
    744     memmove(save, text, length);
    745     save[length] = '\0';
    746 
    747     if (save[0] == '~' && save[1]) {
    748 	char *slash2 = strchr(save, '/');
    749 
    750 	if (slash2) {
    751 	    struct passwd *pw;
    752 	    char home[BUFSIZ];
    753 	    char *name;
    754 	    int slen = strlen(save), diff = slash2 - save;
    755 
    756 	    *slash2 = '\0';
    757 	    name = save + 1;
    758 	    if (strlen(name) != 0)
    759 		pw = getpwnam(name);
    760 	    else
    761 		pw = getpwuid(getuid());
    762 
    763 	    if (pw) {
    764 		char fname[BUFSIZ];
    765 		int hlen;
    766 
    767 		strncpy(home, pw->pw_dir, sizeof(home) - 1);
    768 		home[sizeof(home) - 1] = '\0';
    769 		hlen = strlen(home);
    770 		strncpy(fname, slash2 + 1, sizeof(fname) - 1);
    771 		fname[sizeof(fname) - 1] = '\0';
    772 		save = XtRealloc(save, slen - diff + hlen + 2);
    773 		(void)memmove(save, home, hlen);
    774 		save[hlen] = '/';
    775 		strcpy(&save[hlen + 1], fname);
    776 
    777 		/* expand directory */
    778 		block.length = strlen(save);
    779 		block.ptr = save;
    780 		block.firstPos = 0;
    781 		block.format = FMT8BIT;
    782 		XawTextReplace(filenamewindow, 0, length, &block);
    783 		XawTextSetInsertionPoint(filenamewindow, length = block.length);
    784 	    }
    785 	    else
    786 		*slash2 = '/';
    787 	}
    788     }
    789 
    790     if ((file_name = strrchr(save, '/')) != NULL) {
    791 	*file_name = '\0';
    792 	++file_name;
    793 	dir_name = save;
    794 	if (!file_name[0])
    795 	    slash = True;
    796 	if (!dir_name[0])
    797 	    dir_name = "/";
    798     }
    799     else {
    800 	dir_name = ".";
    801 	file_name = save;
    802     }
    803     len = strlen(file_name);
    804 
    805     if ((dir = opendir(dir_name)) != NULL) {
    806 	char path[BUFSIZ], *pptr;
    807 	struct dirent *ent;
    808 	int isdir = 0, first = 1, bytes;
    809 
    810 	XmuSnprintf(path, sizeof(path), "%s/", dir_name);
    811 	pptr = path + strlen(path);
    812 	bytes = sizeof(path) - (pptr - path) - 1;
    813 
    814 	mlen = 0;
    815 	match[0] = '\0';
    816 	while ((ent = readdir(dir)) != NULL) {
    817 	    unsigned d_namlen = strlen(ent->d_name);
    818 
    819 	    if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0)
    820 		continue;
    821 	    if (d_namlen >= len && strncmp(ent->d_name, file_name, len) == 0) {
    822 		char *tmp = &(ent->d_name[len]), *mat = match;
    823 		struct stat st;
    824 		Bool is_dir = FALSE;
    825 
    826 		strncpy(pptr, ent->d_name, bytes);
    827 		pptr[bytes] = '\0';
    828 		if (stat(path, &st) != 0)
    829 		    /* Should check errno, may be a broken symbolic link
    830 		     * a directory with r-- permission, etc */
    831 		    continue;
    832 		else if (first || show_matches != SM_NEVER) {
    833 		    is_dir = S_ISDIR(st.st_mode);
    834 		}
    835 
    836 		if (first) {
    837 		    strncpy(match, tmp, sizeof(match) - 1);
    838 		    match[sizeof(match) - 2] = '\0';
    839 		    mlen = strlen(match);
    840 		    first = 0;
    841 		    isdir = is_dir;
    842 		}
    843 		else {
    844 		    while (*tmp && *mat && *tmp++ == *mat)
    845 			++mat;
    846 		    if (mlen > mat - match) {
    847 			mlen = mat - match;
    848 			match[mlen] = '\0';
    849 		    }
    850 		}
    851 		if (show_matches != SM_NEVER) {
    852 		    matches = (char **)XtRealloc((char*)matches, sizeof(char**)
    853 						 * (n_matches + 1));
    854 		    buflen += d_namlen + 1;
    855 		    if (is_dir) {
    856 			matches[n_matches] = XtMalloc(d_namlen + 2);
    857 			strcpy(matches[n_matches], ent->d_name);
    858 			strcat(matches[n_matches], "/");
    859 			++buflen;
    860 		    }
    861 		    else
    862 			matches[n_matches] = XtNewString(ent->d_name);
    863 		}
    864 		else if (mlen == 0 && n_matches >= 1) {
    865 		    ++n_matches;
    866 		    break;
    867 		}
    868 		++n_matches;
    869 	    }
    870 	}
    871 
    872 	closedir(dir);
    873 	changed = mlen != 0;
    874 
    875 	if (first || n_matches) {
    876 	    Bool free_matches = True, add_slash = n_matches == 1 && isdir && !slash;
    877 
    878 	    if (mlen && has_dot && match[mlen - 1] == '.')
    879 		--mlen;
    880 
    881 	    if (mlen || add_slash) {
    882 		XawTextPosition pos;
    883 
    884 		block.firstPos = 0;
    885 		block.format = FMT8BIT;
    886 		if (mlen) {
    887 		    pos = length;
    888 		    block.length = mlen;
    889 		    block.ptr = match;
    890 		    XawTextReplace(filenamewindow, pos, pos, &block);
    891 		    XawTextSetInsertionPoint(filenamewindow, pos + block.length);
    892 		}
    893 		if (add_slash) {
    894 		    XawTextPosition actual = XawTextGetInsertionPoint(w);
    895 
    896 		    pos = XawTextSourceScan(XawTextGetSource(w), 0, XawstAll,
    897 					    XawsdRight, 1, True);
    898 		    block.length = 1;
    899 		    block.ptr = "/";
    900 		    XawTextReplace(filenamewindow, pos, pos, &block);
    901 		    if (actual == pos)
    902 			XawTextSetInsertionPoint(filenamewindow, pos + 1);
    903 		}
    904 	    }
    905 	    else if (n_matches != 1 || isdir) {
    906 		if (show_matches == SM_NEVER)
    907 		    Feep();
    908 	    }
    909 
    910 	    if (show_matches != SM_NEVER) {
    911 		if (show_matches == SM_ALWAYS || (!changed && n_matches != 1)) {
    912 		    char **list = NULL, *label;
    913 		    int n_list;
    914 		    Arg args[2];
    915 
    916 		    XtSetArg(args[0], XtNlist, &list);
    917 		    XtSetArg(args[1], XtNnumberStrings, &n_list);
    918 		    XtGetValues(dirwindow, args, 2);
    919 
    920 		    matches = (char **)XtRealloc((char*)matches, sizeof(char**)
    921 						 * (n_matches + 2));
    922 		    matches[n_matches++] = XtNewString("./");
    923 		    matches[n_matches++] = XtNewString("../");
    924 		    qsort(matches, n_matches, sizeof(char*), compar);
    925 		    XtSetArg(args[0], XtNlist, matches);
    926 		    XtSetArg(args[1], XtNnumberStrings, n_matches);
    927 		    XtSetValues(dirwindow, args, 2);
    928 		    if (n_list > 0
    929 			&& (n_list != 1 || list[0] != XtName(dirwindow))) {
    930 			while (--n_list > -1)
    931 			    XtFree(list[n_list]);
    932 			XtFree((char*)list);
    933 		    }
    934 
    935 		    label = ResolveName(dir_name);
    936 		    XtSetArg(args[0], XtNlabel, label);
    937 		    XtSetValues(dirlabel, args, 1);
    938 		    SwitchDirWindow(True);
    939 		    free_matches = False;
    940 		}
    941 	    }
    942 	    if (free_matches && matches) {
    943 		while (--n_matches > -1)
    944 		    XtFree(matches[n_matches]);
    945 		XtFree((char*)matches);
    946 	    }
    947 	}
    948 	else
    949 	    Feep();
    950     }
    951     else
    952 	Feep();
    953 
    954     XtFree(save);
    955 }
    956 
    957 /*ARGSUSED*/
    958 void
    959 DirWindowCB(Widget w, XtPointer user_data, XtPointer call_data)
    960 {
    961     XawListReturnStruct *file_info = (XawListReturnStruct *)call_data;
    962     char *dir_name, *string, path[BUFSIZ];
    963     Arg args[2];
    964 
    965     if (file_info == NULL)
    966 	string = (char *)user_data;
    967     else
    968 	string = file_info->string;
    969 
    970     XtSetArg(args[0], XtNlabel, &dir_name);
    971     XtGetValues(dirlabel, args, 1);
    972     if (*dir_name == '\0') {
    973 	strncpy(path, string, sizeof(path) - 1);
    974 	path[sizeof(path) - 1] = '\0';
    975     }
    976     else if (strcmp(dir_name, "/") == 0)
    977 	XmuSnprintf(path, sizeof(path), "/%s", string);
    978     else
    979 	XmuSnprintf(path, sizeof(path), "%s/%s", dir_name, string);
    980 
    981     if (*string && string[strlen(string) - 1] == '/') {
    982 	DIR *dir;
    983 
    984 	if ((dir = opendir(path)) != NULL) {
    985 	    struct dirent *ent;
    986 	    struct stat st;
    987 	    unsigned d_namlen;
    988 	    Bool isdir;
    989 	    char **entries = NULL, **list = NULL;
    990 	    int n_entries = 0, n_list = 0;
    991 	    char *label, *pptr = path + strlen(path);
    992 	    int bytes = sizeof(path) - (pptr - path) - 1;
    993 
    994 	    while ((ent = readdir(dir)) != NULL) {
    995 		d_namlen = strlen(ent->d_name);
    996 		strncpy(pptr, ent->d_name, bytes);
    997 		pptr[bytes] = '\0';
    998 		if (stat(path, &st) != 0)
    999 		    /* Should check errno, may be a broken symbolic link
   1000 		     * a directory with r-- permission, etc */
   1001 		    continue;
   1002 		else
   1003 		    isdir = S_ISDIR(st.st_mode);
   1004 
   1005 		entries = (char **)XtRealloc((char*)entries, sizeof(char*)
   1006 					     * (n_entries + 1));
   1007 		if (isdir) {
   1008 		    entries[n_entries] = XtMalloc(d_namlen + 2);
   1009 		    strcpy(entries[n_entries], ent->d_name);
   1010 		    strcat(entries[n_entries], "/");
   1011 		}
   1012 		else
   1013 		    entries[n_entries] = XtNewString(ent->d_name);
   1014 		++n_entries;
   1015 	    }
   1016 	    closedir(dir);
   1017 
   1018 	    XtSetArg(args[0], XtNlist, &list);
   1019 	    XtSetArg(args[1], XtNnumberStrings, &n_list);
   1020 	    XtGetValues(dirwindow, args, 2);
   1021 
   1022 	    if (n_entries == 0) {
   1023 		entries = (char**)XtMalloc(sizeof(char*) * 2);
   1024 		/* Directory has read but not execute permission? */
   1025 		entries[n_entries++] = XtNewString("./");
   1026 		entries[n_entries++] = XtNewString("../");
   1027 	    }
   1028 	    qsort(entries, n_entries, sizeof(char*), compar);
   1029 	    XtSetArg(args[0], XtNlist, entries);
   1030 	    XtSetArg(args[1], XtNnumberStrings, n_entries);
   1031 	    XtSetValues(dirwindow, args, 2);
   1032 	    if (n_list > 0
   1033 		&& (n_list != 1 || list[0] != XtName(dirwindow))) {
   1034 		while (--n_list > -1)
   1035 		    XtFree(list[n_list]);
   1036 		XtFree((char*)list);
   1037 	    }
   1038 
   1039 	    *pptr = '\0';
   1040 	    if ((label = ResolveName(path)) == NULL) {
   1041 		Feep();
   1042 		label = path;
   1043 	    }
   1044 	    XtSetArg(args[0], XtNlabel, label);
   1045 	    XtSetValues(dirlabel, args, 1);
   1046 
   1047 	    strncpy(path, label, sizeof(path) - 2);
   1048 	    if (*path && path[strlen(path) - 1] != '/')
   1049 		strcat(path, "/");
   1050 	    XtSetArg(args[0], XtNstring, path);
   1051 	    XtSetValues(filenamewindow, args, 1);
   1052 	    XtSetKeyboardFocus(topwindow, filenamewindow);
   1053 	    XawTextSetInsertionPoint(filenamewindow, strlen(path));
   1054 	}
   1055 	else
   1056 	    Feep();
   1057     }
   1058     else {
   1059 	(void)ReallyDoLoad(path, ResolveName(path));
   1060 	SwitchDirWindow(False);
   1061 	XtSetKeyboardFocus(topwindow, textwindow);
   1062     }
   1063 }
   1064