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