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	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
127static char *
128makeBackupName(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*/
161void
162SaveFile(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*/
177void
178DoSave(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;
365error:
366    XeditPrintf("%s", buffer);
367    Feep();
368}
369
370/*ARGSUSED*/
371void
372DoLoad(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
380Bool
381LoadFileInTextwindow(char *name, char *resolved_name)
382{
383    return (ReallyDoLoad(name, resolved_name));
384}
385
386static Bool
387ReallyDoLoad(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*/
505void
506SourceChanged(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
545void
546ResetSourceChanged(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*/
571void
572KillFile(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*/
595void
596FindFile(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*/
627void
628LoadFile(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*/
639void
640CancelFindFile(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
665static int
666compar(_Xconst void *a, _Xconst void *b)
667{
668    return (strcmp(*(char **)a, *(char **)b));
669}
670
671/*ARGSUSED*/
672void
673FileCompletion(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*/
958void
959DirWindowCB(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