]> Pileus Git - ~andy/gtk/blob - gtk/gtkfilesel.c
Initial revision
[~andy/gtk] / gtk / gtkfilesel.c
1 /* GTK - The GIMP Toolkit
2  * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the Free
16  * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17  */
18 #include <stdio.h>
19 #include <sys/types.h>
20 #include <sys/stat.h>
21 #include <sys/param.h>
22 #include <dirent.h>
23 #include <stdlib.h>
24 #include <unistd.h>
25 #include <string.h>
26 #include <errno.h>
27 #include <pwd.h>
28 #include "fnmatch.h"
29
30 #include "gdk/gdkkeysyms.h"
31 #include "gtkbutton.h"
32 #include "gtkentry.h"
33 #include "gtkfilesel.h"
34 #include "gtkhbox.h"
35 #include "gtklabel.h"
36 #include "gtklist.h"
37 #include "gtklistitem.h"
38 #include "gtkmain.h"
39 #include "gtkscrolledwindow.h"
40 #include "gtksignal.h"
41 #include "gtkvbox.h"
42
43
44 #define DIR_LIST_WIDTH   160
45 #define DIR_LIST_HEIGHT  175
46 #define FILE_LIST_WIDTH  160
47 #define FILE_LIST_HEIGHT 175
48
49
50 typedef struct _CompletionState    CompletionState;
51 typedef struct _CompletionDir      CompletionDir;
52 typedef struct _CompletionDirSent  CompletionDirSent;
53 typedef struct _CompletionDirEntry CompletionDirEntry;
54 typedef struct _CompletionUserDir  CompletionUserDir;
55 typedef struct _PossibleCompletion PossibleCompletion;
56
57 /* Non-external file completion decls and structures */
58
59 /* A contant telling PRCS how many directories to cache.  Its actually
60  * kept in a list, so the geometry isn't important. */
61 #define CMPL_DIRECTORY_CACHE_SIZE 10
62
63 /* A constant used to determine whether a substring was an exact
64  * match by first_diff_index()
65  */
66 #define PATTERN_MATCH -1
67 /* The arguments used by all fnmatch() calls below
68  */
69 #define FNMATCH_FLAGS (FNM_PATHNAME | FNM_PERIOD)
70
71 #define CMPL_ERRNO_TOO_LONG ((1<<16)-1)
72
73 /* This structure contains all the useful information about a directory
74  * for the purposes of filename completion.  These structures are cached
75  * in the CompletionState struct.  CompletionDir's are reference counted.
76  */
77 struct _CompletionDirSent
78 {
79   ino_t inode;
80   time_t mtime;
81
82   gint entry_count;
83   gchar *name_buffer; /* memory segment containing names of all entries */
84
85   struct _CompletionDirEntry *entries;
86 };
87
88 struct _CompletionDir
89 {
90   CompletionDirSent *sent;
91
92   gchar *fullname;
93   gint fullname_len;
94
95   struct _CompletionDir *cmpl_parent;
96   gint cmpl_index;
97   gchar *cmpl_text;
98 };
99
100 /* This structure contains pairs of directory entry names with a flag saying
101  * whether or not they are a valid directory.  NOTE: This information is used
102  * to provide the caller with information about whether to update its completions
103  * or try to open a file.  Since directories are cached by the directory mtime,
104  * a symlink which points to an invalid file (which will not be a directory),
105  * will not be reevaluated if that file is created, unless the containing
106  * directory is touched.  I consider this case to be worth ignoring (josh).
107  */
108 struct _CompletionDirEntry
109 {
110   gint is_dir;
111   gchar *entry_name;
112 };
113
114 struct _CompletionUserDir
115 {
116   gchar *login;
117   gchar *homedir;
118 };
119
120 struct _PossibleCompletion
121 {
122   /* accessible fields, all are accessed externally by functions
123    * declared above
124    */
125   gchar *text;
126   gint is_a_completion;
127   gint is_directory;
128
129   /* Private fields
130    */
131   gint text_alloc;
132 };
133
134 struct _CompletionState
135 {
136   gint last_valid_char;
137   gchar *updated_text;
138   gint updated_text_len;
139   gint updated_text_alloc;
140   gint re_complete;
141
142   gchar *user_dir_name_buffer;
143   gint user_directories_len;
144   gchar *user_home_dir;
145
146   gchar *last_completion_text;
147
148   gint user_completion_index; /* if >= 0, currently completing ~user */
149
150   struct _CompletionDir *completion_dir; /* directory completing from */
151   struct _CompletionDir *active_completion_dir;
152
153   struct _PossibleCompletion the_completion;
154
155   struct _CompletionDir *reference_dir; /* initial directory */
156
157   GList* directory_storage;
158   GList* directory_sent_storage;
159
160   struct _CompletionUserDir *user_directories;
161 };
162
163
164 /* File completion functions which would be external, were they used
165  * outside of this file.
166  */
167
168 static CompletionState*    cmpl_init_state        (void);
169 static void                cmpl_free_state        (CompletionState *cmpl_state);
170 static gint                cmpl_state_okay        (CompletionState* cmpl_state);
171 static gchar*              cmpl_strerror          (gint);
172
173 static PossibleCompletion* cmpl_completion_matches(gchar           *text_to_complete,
174                                                    gchar          **remaining_text,
175                                                    CompletionState *cmpl_state);
176
177 /* Returns a name for consideration, possibly a completion, this name
178  * will be invalid after the next call to cmpl_next_completion.
179  */
180 static char*               cmpl_this_completion   (PossibleCompletion*);
181
182 /* True if this completion matches the given text.  Otherwise, this
183  * output can be used to have a list of non-completions.
184  */
185 static gint                cmpl_is_a_completion   (PossibleCompletion*);
186
187 /* True if the completion is a directory
188  */
189 static gint                cmpl_is_directory      (PossibleCompletion*);
190
191 /* Obtains the next completion, or NULL
192  */
193 static PossibleCompletion* cmpl_next_completion   (CompletionState*);
194
195 /* Updating completions: the return value of cmpl_updated_text() will
196  * be text_to_complete completed as much as possible after the most
197  * recent call to cmpl_completion_matches.  For the present
198  * application, this is the suggested replacement for the user's input
199  * string.  You must CALL THIS AFTER ALL cmpl_text_completions have
200  * been received.
201  */
202 static gchar*              cmpl_updated_text       (CompletionState* cmpl_state);
203
204 /* After updating, to see if the completion was a directory, call
205  * this.  If it was, you should consider re-calling completion_matches.
206  */
207 static gint                cmpl_updated_dir        (CompletionState* cmpl_state);
208
209 /* Current location: if using file completion, return the current
210  * directory, from which file completion begins.  More specifically,
211  * the cwd concatenated with all exact completions up to the last
212  * directory delimiter('/').
213  */
214 static gchar*              cmpl_reference_position (CompletionState* cmpl_state);
215
216 /* backing up: if cmpl_completion_matches returns NULL, you may query
217  * the index of the last completable character into cmpl_updated_text.
218  */
219 static gint                cmpl_last_valid_char    (CompletionState* cmpl_state);
220
221 /* When the user selects a non-directory, call cmpl_completion_fullname
222  * to get the full name of the selected file.
223  */
224 static gchar*              cmpl_completion_fullname (gchar*, CompletionState* cmpl_state);
225
226
227 /* Directory operations. */
228 static CompletionDir* open_ref_dir         (gchar* text_to_complete,
229                                             gchar** remaining_text,
230                                             CompletionState* cmpl_state);
231 static CompletionDir* open_dir             (gchar* dir_name,
232                                             CompletionState* cmpl_state);
233 static CompletionDir* open_user_dir        (gchar* text_to_complete,
234                                             CompletionState *cmpl_state);
235 static CompletionDir* open_relative_dir    (gchar* dir_name, CompletionDir* dir,
236                                             CompletionState *cmpl_state);
237 static CompletionDirSent* open_new_dir         (gchar* dir_name, struct stat* sbuf);
238 static gint           correct_dir_fullname (CompletionDir* cmpl_dir);
239 static gint           correct_parent       (CompletionDir* cmpl_dir,
240                                             struct stat *sbuf);
241 static gchar*         find_parent_dir_fullname    (gchar* dirname);
242 static CompletionDir* attach_dir           (CompletionDirSent* sent,
243                                             gchar* dir_name,
244                                             CompletionState *cmpl_state);
245 static void           free_dir_sent (CompletionDirSent* sent);
246 static void           free_dir      (CompletionDir  *dir);
247 static void           prune_memory_usage(CompletionState *cmpl_state);
248
249 /* Completion operations */
250 static PossibleCompletion* attempt_homedir_completion(gchar* text_to_complete,
251                                                       CompletionState *cmpl_state);
252 static PossibleCompletion* attempt_file_completion(CompletionState *cmpl_state);
253 static CompletionDir* find_completion_dir(gchar* text_to_complete,
254                                           gchar** remaining_text,
255                                           CompletionState* cmpl_state);
256 static PossibleCompletion* append_completion_text(gchar* text,
257                                                   CompletionState* cmpl_state);
258 static gint get_pwdb(CompletionState* cmpl_state);
259 static gint first_diff_index(gchar* pat, gchar* text);
260 static gint compare_user_dir(const void* a, const void* b);
261 static gint compare_cmpl_dir(const void* a, const void* b);
262 static void update_cmpl(PossibleCompletion* poss,
263                         CompletionState* cmpl_state);
264
265 static void gtk_file_selection_class_init    (GtkFileSelectionClass *klass);
266 static void gtk_file_selection_init          (GtkFileSelection      *filesel);
267 static void gtk_file_selection_destroy       (GtkObject             *object);
268 static gint gtk_file_selection_key_press     (GtkWidget             *widget,
269                                               GdkEventKey           *event,
270                                               gpointer               user_data);
271 static gint gtk_file_selection_file_button   (GtkWidget             *widget,
272                                               GdkEventButton        *event,
273                                               gpointer               user_data);
274 static gint gtk_file_selection_dir_button    (GtkWidget             *widget,
275                                               GdkEventButton        *event,
276                                               gpointer               user_data);
277 static void gtk_file_selection_file_list_changed  (GtkList          *gtklist,
278                                                    gpointer          func_data);
279 static void gtk_file_selection_dir_list_changed   (GtkList          *gtklist,
280                                                    gpointer          func_data);
281 static void gtk_file_selection_populate      (GtkFileSelection      *fs,
282                                               gchar                 *rel_path,
283                                               gint                   try_complete);
284 static void gtk_file_selection_abort         (GtkFileSelection      *fs);
285 static void gtk_file_selection_free_filename (GtkWidget             *widget,
286                                               gpointer               client_data);
287
288
289 static GtkWindowClass *parent_class = NULL;
290
291 static gchar *list_changed_key = "_gtk_selection_changed_handler_key";
292
293 /* Saves errno when something cmpl does fails. */
294 static gint cmpl_errno;
295
296
297 guint
298 gtk_file_selection_get_type ()
299 {
300   static guint file_selection_type = 0;
301
302   if (!file_selection_type)
303     {
304       GtkTypeInfo filesel_info =
305       {
306         "GtkFileSelection",
307         sizeof (GtkFileSelection),
308         sizeof (GtkFileSelectionClass),
309         (GtkClassInitFunc) gtk_file_selection_class_init,
310         (GtkObjectInitFunc) gtk_file_selection_init,
311         (GtkArgFunc) NULL,
312       };
313
314       file_selection_type = gtk_type_unique (gtk_window_get_type (), &filesel_info);
315     }
316
317   return file_selection_type;
318 }
319
320 static void
321 gtk_file_selection_class_init (GtkFileSelectionClass *class)
322 {
323   GtkObjectClass *object_class;
324
325   object_class = (GtkObjectClass*) class;
326
327   parent_class = gtk_type_class (gtk_window_get_type ());
328
329   object_class->destroy = gtk_file_selection_destroy;
330 }
331
332 static void
333 gtk_file_selection_init (GtkFileSelection *filesel)
334 {
335   GtkWidget *dir_vbox;
336   GtkWidget *file_vbox;
337   GtkWidget *entry_vbox;
338   GtkWidget *listbox;
339   GtkWidget *label;
340   GtkWidget *list_hbox;
341   GtkWidget *action_area;
342   gint key;
343
344   filesel->cmpl_state = cmpl_init_state ();
345
346   /*  The dialog-sized vertical box  */
347   filesel->main_vbox = gtk_vbox_new (FALSE, 10);
348   gtk_container_border_width (GTK_CONTAINER (filesel), 10);
349   gtk_container_add (GTK_CONTAINER (filesel), filesel->main_vbox);
350   gtk_widget_show (filesel->main_vbox);
351
352   /*  The horizontal box containing the directory and file listboxes  */
353   list_hbox = gtk_hbox_new (TRUE, 5);
354   gtk_box_pack_start (GTK_BOX (filesel->main_vbox), list_hbox, TRUE, TRUE, 0);
355   gtk_widget_show (list_hbox);
356
357
358   /* The directories listbox  */
359   dir_vbox = gtk_vbox_new (FALSE, 2);
360   gtk_box_pack_start (GTK_BOX (list_hbox), dir_vbox, TRUE, TRUE, 0);
361   gtk_widget_show (dir_vbox);
362
363   label = gtk_label_new ("Directories");
364   gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
365   gtk_box_pack_start (GTK_BOX (dir_vbox), label, FALSE, FALSE, 0);
366   gtk_widget_show (label);
367
368   listbox = gtk_scrolled_window_new (NULL, NULL);
369   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (listbox),
370                                   GTK_POLICY_AUTOMATIC,
371                                   GTK_POLICY_ALWAYS);
372   gtk_box_pack_start (GTK_BOX (dir_vbox), listbox, TRUE, TRUE, 0);
373   gtk_widget_set_usize (listbox, DIR_LIST_WIDTH, DIR_LIST_HEIGHT);
374   gtk_widget_show (listbox);
375
376   filesel->dir_list = gtk_list_new ();
377   gtk_list_set_selection_mode (GTK_LIST (filesel->dir_list), GTK_SELECTION_BROWSE);
378   gtk_signal_connect (GTK_OBJECT (filesel->dir_list), "button_press_event",
379                       (GtkSignalFunc) gtk_file_selection_dir_button, filesel);
380   key = gtk_signal_connect (GTK_OBJECT (filesel->dir_list),
381                             "selection_changed",
382                             (GtkSignalFunc) gtk_file_selection_dir_list_changed,
383                             filesel);
384   gtk_object_set_data (GTK_OBJECT (filesel->dir_list), list_changed_key, (gpointer) key);
385   gtk_container_add (GTK_CONTAINER (listbox), filesel->dir_list);
386   gtk_widget_show (filesel->dir_list);
387
388
389   /* The files listbox  */
390   file_vbox = gtk_vbox_new (FALSE, 2);
391   gtk_box_pack_start (GTK_BOX (list_hbox), file_vbox, TRUE, TRUE, 0);
392   gtk_widget_show (file_vbox);
393
394   label = gtk_label_new ("Files");
395   gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
396   gtk_box_pack_start (GTK_BOX (file_vbox), label, FALSE, FALSE, 0);
397   gtk_widget_show (label);
398
399   listbox = gtk_scrolled_window_new (NULL, NULL);
400   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (listbox),
401                                   GTK_POLICY_AUTOMATIC,
402                                   GTK_POLICY_ALWAYS);
403   gtk_box_pack_start (GTK_BOX (file_vbox), listbox, TRUE, TRUE, 0);
404   gtk_widget_set_usize (listbox, FILE_LIST_WIDTH, FILE_LIST_HEIGHT);
405   gtk_widget_show (listbox);
406
407   filesel->file_list = gtk_list_new ();
408   gtk_list_set_selection_mode (GTK_LIST (filesel->file_list), GTK_SELECTION_BROWSE);
409   gtk_signal_connect (GTK_OBJECT (filesel->file_list), "button_press_event",
410                       (GtkSignalFunc) gtk_file_selection_file_button, filesel);
411   key = gtk_signal_connect (GTK_OBJECT (filesel->file_list),
412                             "selection_changed",
413                             (GtkSignalFunc) gtk_file_selection_file_list_changed,
414                             filesel);
415   gtk_object_set_data (GTK_OBJECT (filesel->file_list), list_changed_key, (gpointer) key);
416   gtk_container_add (GTK_CONTAINER (listbox), filesel->file_list);
417   gtk_widget_show (filesel->file_list);
418
419
420   /*  The action area  */
421   action_area = gtk_hbox_new (TRUE, 10);
422   gtk_box_pack_end (GTK_BOX (filesel->main_vbox), action_area, FALSE, FALSE, 0);
423   gtk_widget_show (action_area);
424
425   /*  The OK button  */
426   filesel->ok_button = gtk_button_new_with_label ("OK");
427   GTK_WIDGET_SET_FLAGS (filesel->ok_button, GTK_CAN_DEFAULT);
428   gtk_box_pack_start (GTK_BOX (action_area), filesel->ok_button, TRUE, TRUE, 0);
429   gtk_widget_grab_default (filesel->ok_button);
430   gtk_widget_show (filesel->ok_button);
431
432   /*  The Cancel button  */
433   filesel->cancel_button = gtk_button_new_with_label ("Cancel");
434   GTK_WIDGET_SET_FLAGS (filesel->cancel_button, GTK_CAN_DEFAULT);
435   gtk_box_pack_start (GTK_BOX (action_area), filesel->cancel_button, TRUE, TRUE, 0);
436   gtk_widget_show (filesel->cancel_button);
437
438   /*  The Help button  */
439   filesel->help_button = gtk_button_new_with_label ("Help");
440   GTK_WIDGET_SET_FLAGS (filesel->help_button, GTK_CAN_DEFAULT);
441   gtk_box_pack_start (GTK_BOX (action_area), filesel->help_button, TRUE, TRUE, 0);
442   gtk_widget_show (filesel->help_button);
443
444
445   /*  The selection entry widget  */
446   entry_vbox = gtk_vbox_new (FALSE, 2);
447   gtk_box_pack_end (GTK_BOX (filesel->main_vbox), entry_vbox, FALSE, FALSE, 0);
448   gtk_widget_show (entry_vbox);
449
450   filesel->selection_text = label = gtk_label_new ("");
451   gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
452   gtk_box_pack_start (GTK_BOX (entry_vbox), label, FALSE, FALSE, 0);
453   gtk_widget_show (label);
454
455   filesel->selection_entry = gtk_entry_new ();
456   gtk_signal_connect (GTK_OBJECT (filesel->selection_entry), "key_press_event",
457                       (GtkSignalFunc) gtk_file_selection_key_press, filesel);
458   gtk_signal_connect_object (GTK_OBJECT (filesel->selection_entry), "focus_in_event",
459                              (GtkSignalFunc) gtk_widget_grab_default,
460                              GTK_OBJECT (filesel->ok_button));
461   gtk_signal_connect_object (GTK_OBJECT (filesel->selection_entry), "activate",
462                              (GtkSignalFunc) gtk_button_clicked,
463                              GTK_OBJECT (filesel->ok_button));
464   gtk_box_pack_start (GTK_BOX (entry_vbox), filesel->selection_entry, TRUE, TRUE, 0);
465   gtk_widget_show (filesel->selection_entry);
466
467   if (!cmpl_state_okay (filesel->cmpl_state))
468     {
469       gchar err_buf[256];
470
471       sprintf (err_buf, "Directory unreadable: %s", cmpl_strerror (cmpl_errno));
472
473       gtk_label_set (GTK_LABEL (filesel->selection_text), err_buf);
474     }
475   else
476     {
477       gtk_file_selection_populate (filesel, "", FALSE);
478     }
479
480   gtk_widget_grab_focus (filesel->selection_entry);
481 }
482
483 GtkWidget*
484 gtk_file_selection_new (const gchar *title)
485 {
486   GtkFileSelection *filesel;
487
488   filesel = gtk_type_new (gtk_file_selection_get_type ());
489   gtk_window_set_title (GTK_WINDOW (filesel), title);
490
491   return GTK_WIDGET (filesel);
492 }
493
494 void
495 gtk_file_selection_set_filename (GtkFileSelection *filesel,
496                                  const gchar      *filename)
497 {
498   char  buf[MAXPATHLEN];
499   const char *name, *last_slash;
500
501   g_return_if_fail (filesel != NULL);
502   g_return_if_fail (GTK_IS_FILE_SELECTION (filesel));
503   g_return_if_fail (filename != NULL);
504
505   last_slash = strrchr (filename, '/');
506
507   if (!last_slash)
508     {
509       buf[0] = 0;
510       name = filename;
511     }
512   else
513     {
514       gint len = MIN (MAXPATHLEN - 1, last_slash - filename + 1);
515
516       strncpy (buf, filename, len);
517       buf[len] = 0;
518
519       name = last_slash + 1;
520     }
521
522   gtk_file_selection_populate (filesel, buf, FALSE);
523
524   if (filesel->selection_entry)
525     gtk_entry_set_text (GTK_ENTRY (filesel->selection_entry), name);
526 }
527
528 gchar*
529 gtk_file_selection_get_filename (GtkFileSelection *filesel)
530 {
531   static char nothing[2] = "";
532   char *text;
533   char *filename;
534
535   g_return_val_if_fail (filesel != NULL, nothing);
536   g_return_val_if_fail (GTK_IS_FILE_SELECTION (filesel), nothing);
537
538   text = gtk_entry_get_text (GTK_ENTRY (filesel->selection_entry));
539   if (text)
540     {
541       filename = cmpl_completion_fullname (text, filesel->cmpl_state);
542       return filename;
543     }
544
545   return nothing;
546 }
547
548 static void
549 gtk_file_selection_destroy (GtkObject *object)
550 {
551   GtkFileSelection *filesel;
552
553   g_return_if_fail (object != NULL);
554   g_return_if_fail (GTK_IS_FILE_SELECTION (object));
555
556   filesel = GTK_FILE_SELECTION (object);
557
558   cmpl_free_state (filesel->cmpl_state);
559
560   if (GTK_OBJECT_CLASS (parent_class)->destroy)
561     (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
562 }
563
564 static gint
565 gtk_file_selection_key_press (GtkWidget   *widget,
566                               GdkEventKey *event,
567                               gpointer     user_data)
568 {
569   GtkFileSelection *fs;
570   char *text;
571
572   g_return_val_if_fail (widget != NULL, FALSE);
573   g_return_val_if_fail (event != NULL, FALSE);
574
575   if (event->keyval == GDK_Tab)
576     {
577       gtk_signal_emit_stop_by_name (GTK_OBJECT (widget), "key_press_event");
578
579       fs = GTK_FILE_SELECTION (user_data);
580       text = gtk_entry_get_text (GTK_ENTRY (fs->selection_entry));
581       gtk_file_selection_populate (fs, text, TRUE);
582
583       return TRUE;
584     }
585
586   return FALSE;
587 }
588
589 static gint
590 gtk_file_selection_file_button (GtkWidget      *widget,
591                                 GdkEventButton *event,
592                                 gpointer        user_data)
593 {
594   GtkFileSelection *fs;
595   GtkWidget *event_widget;
596   gchar *filename;
597   gboolean handled;
598
599   g_return_val_if_fail (widget != NULL, FALSE);
600   g_return_val_if_fail (event != NULL, FALSE);
601
602   fs = GTK_FILE_SELECTION (user_data);
603   g_return_val_if_fail (fs != NULL, FALSE);
604   g_return_val_if_fail (GTK_IS_FILE_SELECTION (fs), FALSE);
605
606   event_widget = gtk_get_event_widget ((GdkEvent*) event);
607   handled = FALSE;
608   if (GTK_IS_LIST_ITEM (event_widget))
609     {
610       switch (event->type)
611         {
612         case GDK_BUTTON_PRESS:
613           filename = gtk_object_get_user_data (GTK_OBJECT (event_widget));
614           gtk_widget_grab_focus (fs->selection_entry);
615           gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), filename);
616           handled = TRUE;
617           break;
618
619         case GDK_2BUTTON_PRESS:
620           gtk_button_clicked (GTK_BUTTON (fs->ok_button));
621           handled = TRUE;
622           break;
623
624         default:
625           break;
626         }
627     }
628
629   return handled;
630 }
631
632 static void
633 gtk_file_selection_file_list_changed (GtkList          *list,
634                                       gpointer          func_data)
635 {
636   GtkFileSelection *fs;
637
638   g_return_if_fail (func_data != NULL);
639   g_return_if_fail (GTK_IS_FILE_SELECTION (func_data));
640
641   fs = GTK_FILE_SELECTION (func_data);
642
643   /* only act on an appropriate selection
644    */
645   if (list->selection && list->selection->data)
646     {
647       GtkListItem *item;
648
649       item = list->selection->data;
650       
651       if (GTK_IS_LIST_ITEM (item))
652         {
653           gchar *filename;
654           
655           filename = gtk_object_get_user_data (GTK_OBJECT (item));
656           gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), filename);
657         }
658     }
659 }
660
661 static gint
662 gtk_file_selection_dir_button (GtkWidget      *widget,
663                                GdkEventButton *event,
664                                gpointer        user_data)
665 {
666   GtkFileSelection *fs;
667   GtkWidget *event_widget;
668   gchar *filename;
669   gboolean handled;
670
671   g_return_val_if_fail (widget != NULL, FALSE);
672   g_return_val_if_fail (GTK_IS_LIST (widget), FALSE);
673   g_return_val_if_fail (event != NULL, FALSE);
674
675   fs = GTK_FILE_SELECTION (user_data);
676   g_return_val_if_fail (fs != NULL, FALSE);
677   g_return_val_if_fail (GTK_IS_FILE_SELECTION (fs), FALSE);
678
679   event_widget = gtk_get_event_widget ((GdkEvent*) event);
680   handled = FALSE;
681   if (GTK_IS_LIST_ITEM (event_widget))
682     {
683       gint key;
684
685       filename = gtk_object_get_user_data (GTK_OBJECT (event_widget));
686
687       key = (gint) gtk_object_get_data (GTK_OBJECT (widget), list_changed_key);
688
689       switch (event->type)
690         {
691         case GDK_BUTTON_PRESS:
692
693           gtk_signal_handler_block (GTK_OBJECT (widget), key);
694           gtk_widget_activate (GTK_WIDGET (event_widget));
695           gtk_signal_handler_unblock (GTK_OBJECT (widget), key);
696
697           gtk_widget_grab_focus (fs->selection_entry);
698           gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), filename);
699           handled = TRUE;
700           break;
701
702         case GDK_2BUTTON_PRESS:
703           gtk_file_selection_populate (fs, filename, FALSE);
704           handled = TRUE;
705           break;
706
707         default:
708           break;
709         }
710     }
711
712   return handled;
713 }
714
715 static void
716 gtk_file_selection_dir_list_changed (GtkList          *list,
717                                      gpointer          func_data)
718 {
719   GtkFileSelection *fs;
720   
721   g_return_if_fail (func_data != NULL);
722   g_return_if_fail (GTK_IS_FILE_SELECTION (func_data));
723   
724   fs = GTK_FILE_SELECTION (func_data);
725
726   /* only act on an appropriate selection
727    */
728   if (list->selection && list->selection->data)
729     {
730       GtkListItem *item;
731
732       item = list->selection->data;
733       
734       if (GTK_IS_LIST_ITEM (item))
735         {
736           gchar *filename;
737           
738           filename = gtk_object_get_user_data (GTK_OBJECT (item));
739
740           if (filename)
741             gtk_file_selection_populate (fs, filename, FALSE);
742         }
743     }
744 }
745
746 static void
747 gtk_file_selection_populate (GtkFileSelection *fs,
748                              gchar            *rel_path,
749                              gint              try_complete)
750 {
751   CompletionState *cmpl_state;
752   PossibleCompletion* poss;
753   GList *dir_list = NULL;
754   GList *file_list = NULL;
755   GtkWidget *label;
756   gchar* filename;
757   gchar* rem_path = rel_path;
758   gchar* sel_text;
759   gint did_recurse = FALSE;
760   gint possible_count = 0;
761   gint selection_index = -1;
762   gint dir_changed_key;
763   
764   g_return_if_fail (fs != NULL);
765   g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
766   
767   dir_changed_key = (gint) gtk_object_get_data (GTK_OBJECT (fs->dir_list), list_changed_key);
768   gtk_signal_handler_block (GTK_OBJECT (fs->dir_list), dir_changed_key);
769   
770   cmpl_state = (CompletionState*) fs->cmpl_state;
771   poss = cmpl_completion_matches (rel_path, &rem_path, cmpl_state);
772
773   if (!cmpl_state_okay (cmpl_state))
774     {
775       /* Something went wrong. */
776       gtk_file_selection_abort (fs);
777       gtk_signal_handler_unblock (GTK_OBJECT (fs->dir_list), dir_changed_key);
778       return;
779     }
780
781   g_assert (cmpl_state->reference_dir);
782
783   /* Set the dir_list and file_list to be GLists of strdup'd
784    * filenames, including ./ and ../ */
785   dir_list = g_list_prepend (dir_list, g_strdup("./"));
786   dir_list = g_list_prepend (dir_list, g_strdup("../"));
787
788   while (poss)
789     {
790       if (cmpl_is_a_completion (poss))
791         {
792           possible_count += 1;
793
794           filename = g_strdup (cmpl_this_completion (poss));
795
796           if (cmpl_is_directory (poss))
797             {
798               if (strcmp (filename, "./") != 0 &&
799                   strcmp (filename, "../") != 0)
800                 dir_list = g_list_prepend (dir_list, filename);
801             }
802           else
803             file_list = g_list_prepend (file_list, filename);
804         }
805
806       poss = cmpl_next_completion (cmpl_state);
807     }
808
809   /* File lists are set. */
810
811   g_assert (cmpl_state->reference_dir);
812
813   if (try_complete)
814     {
815       /* User is trying to complete filenames, so advance the user's input
816        * string to the updated_text, which is the common leading substring
817        * of all possible completions, and if its a directory attempt
818        * attempt completions in it. */
819
820       if (cmpl_updated_text (cmpl_state)[0])
821         {
822           if (cmpl_updated_dir (cmpl_state))
823             {
824               gchar* dir_name = g_strdup (cmpl_updated_text (cmpl_state));
825
826               did_recurse = TRUE;
827
828               gtk_file_selection_populate (fs, dir_name, TRUE);
829
830               g_free (dir_name);
831             }
832           else
833             {
834               if (fs->selection_entry)
835                 gtk_entry_set_text (GTK_ENTRY (fs->selection_entry),
836                                     cmpl_updated_text (cmpl_state));
837             }
838         }
839       else
840         {
841           selection_index = cmpl_last_valid_char (cmpl_state) -
842                             (strlen (rel_path) - strlen (rem_path));
843           if (fs->selection_entry)
844             gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), rem_path);
845         }
846     }
847   else
848     {
849       if (fs->selection_entry)
850         gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), "");
851     }
852
853   if (!did_recurse)
854     {
855       GList *file_label_list = NULL;
856       GList *dir_label_list = NULL;
857
858       /* This reverses the lists. */
859       while (file_list)
860         {
861           label = gtk_list_item_new_with_label (file_list->data);
862           gtk_object_set_user_data (GTK_OBJECT (label), file_list->data);
863           gtk_widget_show (label);
864
865           file_label_list = g_list_prepend (file_label_list, label);
866           file_list = file_list->next;
867         }
868
869       while (dir_list)
870         {
871           label = gtk_list_item_new_with_label (dir_list->data);
872           gtk_object_set_user_data (GTK_OBJECT (label), dir_list->data);
873           gtk_widget_show (label);
874
875           dir_label_list = g_list_prepend (dir_label_list, label);
876           dir_list = dir_list->next;
877         }
878
879       gtk_container_disable_resize (GTK_CONTAINER (fs));
880
881       if (fs->selection_entry)
882         gtk_entry_set_position (GTK_ENTRY (fs->selection_entry), selection_index);
883
884       if (fs->selection_entry)
885         {
886           sel_text = g_new (char, strlen (cmpl_reference_position (cmpl_state)) +
887                             sizeof ("Selection: "));
888           strcpy (sel_text, "Selection: ");
889           strcat (sel_text, cmpl_reference_position (cmpl_state));
890
891           gtk_label_set (GTK_LABEL (fs->selection_text), sel_text);
892           g_free (sel_text);
893         }
894
895       if (fs->dir_list)
896         {
897           gtk_container_foreach (GTK_CONTAINER (fs->dir_list),
898                                  gtk_file_selection_free_filename, NULL);
899           gtk_list_clear_items (GTK_LIST (fs->dir_list), 0, -1);
900
901           if (dir_label_list)
902             gtk_list_append_items (GTK_LIST (fs->dir_list), dir_label_list);
903         }
904       if (fs->file_list)
905         {
906           gtk_container_foreach (GTK_CONTAINER (fs->file_list),
907                                  gtk_file_selection_free_filename, NULL);
908           gtk_list_clear_items (GTK_LIST (fs->file_list), 0, -1);
909
910           if (file_label_list)
911             gtk_list_append_items (GTK_LIST (fs->file_list), file_label_list);
912         }
913
914       gtk_container_enable_resize (GTK_CONTAINER (fs));
915     }
916   else
917     {
918       GList *dir_list0 = dir_list;
919       GList *file_list0 = file_list;
920
921       while (dir_list)
922         {
923           GList *tmp = dir_list;
924           dir_list = dir_list->next;
925
926           if (tmp)
927             g_free (tmp->data);
928         }
929
930       while (file_list)
931         {
932           GList *tmp = file_list;
933           file_list = file_list->next;
934
935           if (tmp)
936             g_free (tmp->data);
937         }
938
939       g_list_free (dir_list0);
940       g_list_free (file_list0);
941     }
942
943   gtk_signal_handler_unblock (GTK_OBJECT (fs->dir_list), dir_changed_key);
944 }
945
946 static void
947 gtk_file_selection_abort (GtkFileSelection *fs)
948 {
949   gchar err_buf[256];
950
951   sprintf (err_buf, "Directory unreadable: %s", cmpl_strerror (cmpl_errno));
952
953   /*  BEEP gdk_beep();  */
954
955   if (fs->selection_entry)
956     gtk_label_set (GTK_LABEL (fs->selection_text), err_buf);
957 }
958
959 static void
960 gtk_file_selection_free_filename (GtkWidget *widget,
961                                   gpointer   client_data)
962 {
963   g_return_if_fail (widget != NULL);
964
965   g_free (gtk_object_get_user_data (GTK_OBJECT (widget)));
966   gtk_object_set_user_data (GTK_OBJECT (widget), NULL);
967 }
968
969
970
971 /**********************************************************************/
972 /*                        External Interface                          */
973 /**********************************************************************/
974
975 /* The four completion state selectors
976  */
977 static gchar*
978 cmpl_updated_text (CompletionState* cmpl_state)
979 {
980   return cmpl_state->updated_text;
981 }
982
983 static gint
984 cmpl_updated_dir (CompletionState* cmpl_state)
985 {
986   return cmpl_state->re_complete;
987 }
988
989 static gchar*
990 cmpl_reference_position (CompletionState* cmpl_state)
991 {
992   return cmpl_state->reference_dir->fullname;
993 }
994
995 static gint
996 cmpl_last_valid_char (CompletionState* cmpl_state)
997 {
998   return cmpl_state->last_valid_char;
999 }
1000
1001 static gchar*
1002 cmpl_completion_fullname (gchar* text, CompletionState* cmpl_state)
1003 {
1004   if (text[0] == '/')
1005     {
1006       strcpy (cmpl_state->updated_text, text);
1007     }
1008   else if (text[0] == '~')
1009     {
1010       CompletionDir* dir;
1011       char* slash;
1012
1013       dir = open_user_dir (text, cmpl_state);
1014
1015       if (!dir)
1016         {
1017           /* spencer says just return ~something, so
1018            * for now just do it. */
1019           strcpy (cmpl_state->updated_text, text);
1020         }
1021       else
1022         {
1023
1024           strcpy (cmpl_state->updated_text, dir->fullname);
1025
1026           slash = strchr (text, '/');
1027
1028           if (slash)
1029             strcat (cmpl_state->updated_text, slash);
1030         }
1031     }
1032   else
1033     {
1034       strcpy (cmpl_state->updated_text, cmpl_state->reference_dir->fullname);
1035       strcat (cmpl_state->updated_text, "/");
1036       strcat (cmpl_state->updated_text, text);
1037     }
1038
1039   return cmpl_state->updated_text;
1040 }
1041
1042 /* The three completion selectors
1043  */
1044 static gchar*
1045 cmpl_this_completion (PossibleCompletion* pc)
1046 {
1047   return pc->text;
1048 }
1049
1050 static gint
1051 cmpl_is_directory (PossibleCompletion* pc)
1052 {
1053   return pc->is_directory;
1054 }
1055
1056 static gint
1057 cmpl_is_a_completion (PossibleCompletion* pc)
1058 {
1059   return pc->is_a_completion;
1060 }
1061
1062 /**********************************************************************/
1063 /*                       Construction, deletion                       */
1064 /**********************************************************************/
1065
1066 static CompletionState*
1067 cmpl_init_state (void)
1068 {
1069   gchar getcwd_buf[2*MAXPATHLEN];
1070   CompletionState *new_state;
1071
1072   new_state = g_new (CompletionState, 1);
1073
1074   if (!getcwd (getcwd_buf, MAXPATHLEN))
1075     {
1076       cmpl_errno = errno;
1077       return NULL;
1078     }
1079
1080   new_state->reference_dir = NULL;
1081   new_state->completion_dir = NULL;
1082   new_state->active_completion_dir = NULL;
1083
1084   if ((new_state->user_home_dir = getenv("HOME")) != NULL)
1085     {
1086       /* if this fails, get_pwdb will fill it in. */
1087       new_state->user_home_dir = g_strdup(new_state->user_home_dir);
1088     }
1089
1090   new_state->directory_storage = NULL;
1091   new_state->directory_sent_storage = NULL;
1092   new_state->last_valid_char = 0;
1093   new_state->updated_text = g_new (gchar, MAXPATHLEN);
1094   new_state->updated_text_alloc = MAXPATHLEN;
1095   new_state->the_completion.text = g_new (gchar, MAXPATHLEN);
1096   new_state->the_completion.text_alloc = MAXPATHLEN;
1097   new_state->user_dir_name_buffer = NULL;
1098   new_state->user_directories = NULL;
1099
1100   new_state->reference_dir =  open_dir (getcwd_buf, new_state);
1101
1102   if (!new_state->reference_dir)
1103     return NULL;
1104
1105   return new_state;
1106 }
1107
1108 static void
1109 cmpl_free_dir_list(GList* dp0)
1110 {
1111   GList *dp = dp0;
1112
1113   while (dp) {
1114     free_dir (dp->data);
1115     dp = dp->next;
1116   }
1117
1118   g_list_free(dp0);
1119 }
1120
1121 static void
1122 cmpl_free_dir_sent_list(GList* dp0)
1123 {
1124   GList *dp = dp0;
1125
1126   while (dp) {
1127     free_dir_sent (dp->data);
1128     dp = dp->next;
1129   }
1130
1131   g_list_free(dp0);
1132 }
1133
1134 static void
1135 cmpl_free_state (CompletionState* cmpl_state)
1136 {
1137   cmpl_free_dir_list(cmpl_state->directory_storage);
1138   cmpl_free_dir_sent_list(cmpl_state->directory_sent_storage);
1139
1140   if (cmpl_state->user_dir_name_buffer)
1141     g_free (cmpl_state->user_dir_name_buffer);
1142   if (cmpl_state->user_directories)
1143     g_free (cmpl_state->user_directories);
1144   if (cmpl_state->the_completion.text)
1145     g_free (cmpl_state->the_completion.text);
1146   if (cmpl_state->updated_text)
1147     g_free (cmpl_state->updated_text);
1148
1149   g_free (cmpl_state);
1150 }
1151
1152 static void
1153 free_dir(CompletionDir* dir)
1154 {
1155   g_free(dir->fullname);
1156   g_free(dir);
1157 }
1158
1159 static void
1160 free_dir_sent(CompletionDirSent* sent)
1161 {
1162   g_free(sent->name_buffer);
1163   g_free(sent->entries);
1164   g_free(sent);
1165 }
1166
1167 static void
1168 prune_memory_usage(CompletionState *cmpl_state)
1169 {
1170   GList* cdsl = cmpl_state->directory_sent_storage;
1171   GList* cdl = cmpl_state->directory_storage;
1172   GList* cdl0 = cdl;
1173   gint len = 0;
1174
1175   for(; cdsl && len < CMPL_DIRECTORY_CACHE_SIZE; len += 1)
1176     cdsl = cdsl->next;
1177
1178   if (cdsl) {
1179     cmpl_free_dir_sent_list(cdsl->next);
1180     cdsl->next = NULL;
1181   }
1182
1183   while (cdl) {
1184     if (cdl->data == cmpl_state->reference_dir)
1185       cmpl_state->directory_storage = g_list_prepend(NULL, cdl->data);
1186     else
1187       free_dir (cdl->data);
1188     cdl = cdl->next;
1189   }
1190
1191   g_list_free(cdl0);
1192 }
1193
1194 /**********************************************************************/
1195 /*                        The main entrances.                         */
1196 /**********************************************************************/
1197
1198 static PossibleCompletion*
1199 cmpl_completion_matches (gchar* text_to_complete,
1200                          gchar** remaining_text,
1201                          CompletionState* cmpl_state)
1202 {
1203   gchar* first_slash;
1204   PossibleCompletion *poss;
1205
1206   prune_memory_usage(cmpl_state);
1207
1208   g_assert(text_to_complete);
1209
1210   cmpl_state->user_completion_index = -1;
1211   cmpl_state->last_completion_text = text_to_complete;
1212   cmpl_state->the_completion.text[0] = 0;
1213   cmpl_state->last_valid_char = 0;
1214   cmpl_state->updated_text_len = -1;
1215   cmpl_state->updated_text[0] = 0;
1216   cmpl_state->re_complete = FALSE;
1217
1218   first_slash = strchr(text_to_complete, '/');
1219
1220   if(text_to_complete[0] == '~' && !first_slash)
1221     {
1222       /* Text starts with ~ and there is no slash, show all the
1223        * home directory completions.
1224        */
1225       poss = attempt_homedir_completion(text_to_complete, cmpl_state);
1226
1227       update_cmpl(poss, cmpl_state);
1228
1229       return poss;
1230     }
1231
1232   cmpl_state->reference_dir =
1233     open_ref_dir(text_to_complete, remaining_text, cmpl_state);
1234
1235   if(!cmpl_state->reference_dir)
1236     return NULL;
1237
1238   cmpl_state->completion_dir =
1239     find_completion_dir(*remaining_text, remaining_text, cmpl_state);
1240
1241   cmpl_state->last_valid_char = *remaining_text - text_to_complete;
1242
1243   if(!cmpl_state->completion_dir)
1244     return NULL;
1245
1246   cmpl_state->completion_dir->cmpl_index = -1;
1247   cmpl_state->completion_dir->cmpl_parent = NULL;
1248   cmpl_state->completion_dir->cmpl_text = *remaining_text;
1249
1250   cmpl_state->active_completion_dir = cmpl_state->completion_dir;
1251
1252   cmpl_state->reference_dir = cmpl_state->completion_dir;
1253
1254   poss = attempt_file_completion(cmpl_state);
1255
1256   update_cmpl(poss, cmpl_state);
1257
1258   return poss;
1259 }
1260
1261 static PossibleCompletion*
1262 cmpl_next_completion (CompletionState* cmpl_state)
1263 {
1264   PossibleCompletion* poss = NULL;
1265
1266   cmpl_state->the_completion.text[0] = 0;
1267
1268   if(cmpl_state->user_completion_index >= 0)
1269     poss = attempt_homedir_completion(cmpl_state->last_completion_text, cmpl_state);
1270   else
1271     poss = attempt_file_completion(cmpl_state);
1272
1273   update_cmpl(poss, cmpl_state);
1274
1275   return poss;
1276 }
1277
1278 /**********************************************************************/
1279 /*                       Directory Operations                         */
1280 /**********************************************************************/
1281
1282 /* Open the directory where completion will begin from, if possible. */
1283 static CompletionDir*
1284 open_ref_dir(gchar* text_to_complete,
1285              gchar** remaining_text,
1286              CompletionState* cmpl_state)
1287 {
1288   gchar* first_slash;
1289   CompletionDir *new_dir;
1290
1291   first_slash = strchr(text_to_complete, '/');
1292
1293   if (text_to_complete[0] == '/' || !cmpl_state->reference_dir)
1294     {
1295       new_dir = open_dir("/", cmpl_state);
1296
1297       if(new_dir)
1298         *remaining_text = text_to_complete + 1;
1299     }
1300   else if (text_to_complete[0] == '~')
1301     {
1302       new_dir = open_user_dir(text_to_complete, cmpl_state);
1303
1304       if(new_dir)
1305         {
1306           if(first_slash)
1307             *remaining_text = first_slash + 1;
1308           else
1309             *remaining_text = text_to_complete + strlen(text_to_complete);
1310         }
1311       else
1312         {
1313           return NULL;
1314         }
1315     }
1316   else
1317     {
1318       *remaining_text = text_to_complete;
1319
1320       new_dir = open_dir(cmpl_state->reference_dir->fullname, cmpl_state);
1321     }
1322
1323   if(new_dir)
1324     {
1325       new_dir->cmpl_index = -1;
1326       new_dir->cmpl_parent = NULL;
1327     }
1328
1329   return new_dir;
1330 }
1331
1332 /* open a directory by user name */
1333 static CompletionDir*
1334 open_user_dir(gchar* text_to_complete,
1335               CompletionState *cmpl_state)
1336 {
1337   gchar *first_slash;
1338   gint cmp_len;
1339
1340   g_assert(text_to_complete && text_to_complete[0] == '~');
1341
1342   first_slash = strchr(text_to_complete, '/');
1343
1344   if (first_slash)
1345     cmp_len = first_slash - text_to_complete - 1;
1346   else
1347     cmp_len = strlen(text_to_complete + 1);
1348
1349   if(!cmp_len)
1350     {
1351       /* ~/ */
1352       if (!cmpl_state->user_home_dir &&
1353           !get_pwdb(cmpl_state))
1354         return NULL;
1355       return open_dir(cmpl_state->user_home_dir, cmpl_state);
1356     }
1357   else
1358     {
1359       /* ~user/ */
1360       char* copy = g_new(char, cmp_len + 1);
1361       struct passwd *pwd;
1362       strncpy(copy, text_to_complete + 1, cmp_len);
1363       copy[cmp_len] = 0;
1364       pwd = getpwnam(copy);
1365       g_free(copy);
1366       if (!pwd)
1367         {
1368           cmpl_errno = errno;
1369           return NULL;
1370         }
1371
1372       return open_dir(pwd->pw_dir, cmpl_state);
1373     }
1374 }
1375
1376 /* open a directory relative the the current relative directory */
1377 static CompletionDir*
1378 open_relative_dir(gchar* dir_name,
1379                   CompletionDir* dir,
1380                   CompletionState *cmpl_state)
1381 {
1382   gchar path_buf[2*MAXPATHLEN];
1383
1384   if(dir->fullname_len + strlen(dir_name) + 2 >= MAXPATHLEN)
1385     {
1386       cmpl_errno = CMPL_ERRNO_TOO_LONG;
1387       return NULL;
1388     }
1389
1390   strcpy(path_buf, dir->fullname);
1391
1392   if(dir->fullname_len > 1)
1393     {
1394       path_buf[dir->fullname_len] = '/';
1395       strcpy(path_buf + dir->fullname_len + 1, dir_name);
1396     }
1397   else
1398     {
1399       strcpy(path_buf + dir->fullname_len, dir_name);
1400     }
1401
1402   return open_dir(path_buf, cmpl_state);
1403 }
1404
1405 /* after the cache lookup fails, really open a new directory */
1406 static CompletionDirSent*
1407 open_new_dir(gchar* dir_name, struct stat* sbuf)
1408 {
1409   CompletionDirSent* sent;
1410   DIR* directory;
1411   gchar *buffer_ptr;
1412   struct dirent *dirent_ptr;
1413   gint buffer_size = 0;
1414   gint entry_count = 0;
1415   gint i;
1416   struct stat ent_sbuf;
1417   char path_buf[MAXPATHLEN*2];
1418   gint path_buf_len;
1419
1420   sent = g_new(CompletionDirSent, 1);
1421   sent->mtime = sbuf->st_mtime;
1422   sent->inode = sbuf->st_ino;
1423
1424   path_buf_len = strlen(dir_name);
1425
1426   if (path_buf_len > MAXPATHLEN)
1427     {
1428       cmpl_errno = CMPL_ERRNO_TOO_LONG;
1429       return NULL;
1430     }
1431
1432   strcpy(path_buf, dir_name);
1433
1434   directory = opendir(dir_name);
1435
1436   if(!directory)
1437     {
1438       cmpl_errno = errno;
1439       return NULL;
1440     }
1441
1442   while((dirent_ptr = readdir(directory)) != NULL)
1443     {
1444       int entry_len = strlen(dirent_ptr->d_name);
1445       buffer_size += entry_len + 1;
1446       entry_count += 1;
1447
1448       if(path_buf_len + entry_len + 2 >= MAXPATHLEN)
1449         {
1450           cmpl_errno = CMPL_ERRNO_TOO_LONG;
1451           closedir(directory);
1452           return NULL;
1453         }
1454     }
1455
1456   sent->name_buffer = g_new(gchar, buffer_size);
1457   sent->entries = g_new(CompletionDirEntry, entry_count);
1458   sent->entry_count = entry_count;
1459
1460   buffer_ptr = sent->name_buffer;
1461
1462   rewinddir(directory);
1463
1464   for(i = 0; i < entry_count; i += 1)
1465     {
1466       dirent_ptr = readdir(directory);
1467
1468       if(!dirent_ptr)
1469         {
1470           cmpl_errno = errno;
1471           closedir(directory);
1472           return NULL;
1473         }
1474
1475       strcpy(buffer_ptr, dirent_ptr->d_name);
1476       sent->entries[i].entry_name = buffer_ptr;
1477       buffer_ptr += strlen(dirent_ptr->d_name);
1478       *buffer_ptr = 0;
1479       buffer_ptr += 1;
1480
1481       path_buf[path_buf_len] = '/';
1482       strcpy(path_buf + path_buf_len + 1, dirent_ptr->d_name);
1483
1484       if(stat(path_buf, &ent_sbuf) >= 0 && S_ISDIR(ent_sbuf.st_mode))
1485         sent->entries[i].is_dir = 1;
1486       else
1487         /* stat may fail, and we don't mind, since it could be a
1488          * dangling symlink. */
1489         sent->entries[i].is_dir = 0;
1490     }
1491
1492   qsort(sent->entries, sent->entry_count, sizeof(CompletionDirEntry), compare_cmpl_dir);
1493
1494   closedir(directory);
1495
1496   return sent;
1497 }
1498
1499 /* open a directory by absolute pathname */
1500 static CompletionDir*
1501 open_dir(gchar* dir_name, CompletionState* cmpl_state)
1502 {
1503   struct stat sbuf;
1504   CompletionDirSent *sent;
1505   GList* cdsl;
1506
1507   if(stat(dir_name, &sbuf) < 0)
1508     {
1509       cmpl_errno = errno;
1510       return NULL;
1511     }
1512
1513   cdsl = cmpl_state->directory_sent_storage;
1514
1515   while (cdsl)
1516     {
1517       sent = cdsl->data;
1518
1519       if(sent->inode == sbuf.st_ino &&
1520          sent->mtime == sbuf.st_mtime)
1521         return attach_dir(sent, dir_name, cmpl_state);
1522
1523       cdsl = cdsl->next;
1524     }
1525
1526   sent = open_new_dir(dir_name, &sbuf);
1527
1528   if (sent) {
1529     cmpl_state->directory_sent_storage =
1530       g_list_prepend(cmpl_state->directory_sent_storage, sent);
1531
1532     return attach_dir(sent, dir_name, cmpl_state);
1533   }
1534
1535   return NULL;
1536 }
1537
1538 static CompletionDir*
1539 attach_dir(CompletionDirSent* sent, gchar* dir_name, CompletionState *cmpl_state)
1540 {
1541   CompletionDir* new_dir;
1542
1543   new_dir = g_new(CompletionDir, 1);
1544
1545   cmpl_state->directory_storage =
1546     g_list_prepend(cmpl_state->directory_storage, new_dir);
1547
1548   new_dir->sent = sent;
1549   new_dir->fullname = g_strdup(dir_name);
1550   new_dir->fullname_len = strlen(dir_name);
1551
1552   return new_dir;
1553 }
1554
1555 static gint
1556 correct_dir_fullname(CompletionDir* cmpl_dir)
1557 {
1558   gint length = strlen(cmpl_dir->fullname);
1559   struct stat sbuf;
1560
1561   if (strcmp(cmpl_dir->fullname + length - 2, "/.") == 0)
1562     cmpl_dir->fullname[length - 2] = 0;
1563   else if (strcmp(cmpl_dir->fullname + length - 3, "/./") == 0)
1564     cmpl_dir->fullname[length - 3] = 0;
1565   else if (strcmp(cmpl_dir->fullname + length - 3, "/..") == 0)
1566     {
1567       if(length == 3)
1568         {
1569           strcpy(cmpl_dir->fullname, "/");
1570           cmpl_dir->fullname_len = 1;
1571           return TRUE;
1572         }
1573
1574       if(stat(cmpl_dir->fullname, &sbuf) < 0)
1575         {
1576           cmpl_errno = errno;
1577           return FALSE;
1578         }
1579
1580       cmpl_dir->fullname[length - 3] = 0;
1581
1582       if(!correct_parent(cmpl_dir, &sbuf))
1583         return FALSE;
1584     }
1585   else if (strcmp(cmpl_dir->fullname + length - 4, "/../") == 0)
1586     {
1587       if(length == 4)
1588         {
1589           strcpy(cmpl_dir->fullname, "/");
1590           cmpl_dir->fullname_len = 1;
1591           return TRUE;
1592         }
1593
1594       if(stat(cmpl_dir->fullname, &sbuf) < 0)
1595         {
1596           cmpl_errno = errno;
1597           return FALSE;
1598         }
1599
1600       cmpl_dir->fullname[length - 4] = 0;
1601
1602       if(!correct_parent(cmpl_dir, &sbuf))
1603         return FALSE;
1604     }
1605
1606   cmpl_dir->fullname_len = strlen(cmpl_dir->fullname);
1607
1608   return TRUE;
1609 }
1610
1611 static gint
1612 correct_parent(CompletionDir* cmpl_dir, struct stat *sbuf)
1613 {
1614   struct stat parbuf;
1615   gchar *last_slash;
1616   gchar *new_name;
1617   gchar c = 0;
1618
1619   last_slash = strrchr(cmpl_dir->fullname, '/');
1620
1621   g_assert(last_slash);
1622
1623   if(last_slash != cmpl_dir->fullname)
1624     last_slash[0] = 0;
1625   else
1626     {
1627       c = last_slash[1];
1628       last_slash[1] = 0;
1629     }
1630
1631   if (stat(cmpl_dir->fullname, &parbuf) < 0)
1632     {
1633       cmpl_errno = errno;
1634       return FALSE;
1635     }
1636
1637   if (parbuf.st_ino == sbuf->st_ino && parbuf.st_dev == sbuf->st_dev)
1638     /* it wasn't a link */
1639     return TRUE;
1640
1641   if(c)
1642     last_slash[1] = c;
1643   else
1644     last_slash[0] = '/';
1645
1646   /* it was a link, have to figure it out the hard way */
1647
1648   new_name = find_parent_dir_fullname(cmpl_dir->fullname);
1649
1650   if (!new_name)
1651     return FALSE;
1652
1653   g_free(cmpl_dir->fullname);
1654
1655   cmpl_dir->fullname = new_name;
1656
1657   return TRUE;
1658 }
1659
1660 static gchar*
1661 find_parent_dir_fullname(gchar* dirname)
1662 {
1663   gchar buffer[MAXPATHLEN];
1664   gchar buffer2[MAXPATHLEN];
1665
1666   if(!getcwd(buffer, MAXPATHLEN))
1667     {
1668       cmpl_errno = errno;
1669       return NULL;
1670     }
1671
1672   if(chdir(dirname) != 0 || chdir("..") != 0)
1673     {
1674       cmpl_errno = errno;
1675       return NULL;
1676     }
1677
1678   if(!getcwd(buffer2, MAXPATHLEN))
1679     {
1680       chdir(buffer);
1681       cmpl_errno = errno;
1682
1683       return NULL;
1684     }
1685
1686   if(chdir(buffer) != 0)
1687     {
1688       cmpl_errno = errno;
1689       return NULL;
1690     }
1691
1692   return g_strdup(buffer2);
1693 }
1694
1695 /**********************************************************************/
1696 /*                        Completion Operations                       */
1697 /**********************************************************************/
1698
1699 static PossibleCompletion*
1700 attempt_homedir_completion(gchar* text_to_complete,
1701                            CompletionState *cmpl_state)
1702 {
1703   gint index, length;
1704
1705   if (!cmpl_state->user_dir_name_buffer &&
1706       !get_pwdb(cmpl_state))
1707     return NULL;
1708   length = strlen(text_to_complete) - 1;
1709
1710   cmpl_state->user_completion_index += 1;
1711
1712   while(cmpl_state->user_completion_index < cmpl_state->user_directories_len)
1713     {
1714       index = first_diff_index(text_to_complete + 1,
1715                                cmpl_state->user_directories
1716                                [cmpl_state->user_completion_index].login);
1717
1718       switch(index)
1719         {
1720         case PATTERN_MATCH:
1721           break;
1722         default:
1723           if(cmpl_state->last_valid_char < (index + 1))
1724             cmpl_state->last_valid_char = index + 1;
1725           cmpl_state->user_completion_index += 1;
1726           continue;
1727         }
1728
1729       cmpl_state->the_completion.is_a_completion = 1;
1730       cmpl_state->the_completion.is_directory = 1;
1731
1732       append_completion_text("~", cmpl_state);
1733
1734       append_completion_text(cmpl_state->
1735                               user_directories[cmpl_state->user_completion_index].login,
1736                              cmpl_state);
1737
1738       return append_completion_text("/", cmpl_state);
1739     }
1740
1741   if(text_to_complete[1] ||
1742      cmpl_state->user_completion_index > cmpl_state->user_directories_len)
1743     {
1744       cmpl_state->user_completion_index = -1;
1745       return NULL;
1746     }
1747   else
1748     {
1749       cmpl_state->user_completion_index += 1;
1750       cmpl_state->the_completion.is_a_completion = 1;
1751       cmpl_state->the_completion.is_directory = 1;
1752
1753       return append_completion_text("~/", cmpl_state);
1754     }
1755 }
1756
1757 /* returns the index (>= 0) of the first differing character,
1758  * PATTERN_MATCH if the completion matches */
1759 static gint
1760 first_diff_index(gchar* pat, gchar* text)
1761 {
1762   gint diff = 0;
1763
1764   while(*pat && *text && *text == *pat)
1765     {
1766       pat += 1;
1767       text += 1;
1768       diff += 1;
1769     }
1770
1771   if(*pat)
1772     return diff;
1773
1774   return PATTERN_MATCH;
1775 }
1776
1777 static PossibleCompletion*
1778 append_completion_text(gchar* text, CompletionState* cmpl_state)
1779 {
1780   gint len, i = 1;
1781
1782   if(!cmpl_state->the_completion.text)
1783     return NULL;
1784
1785   len = strlen(text) + strlen(cmpl_state->the_completion.text) + 1;
1786
1787   if(cmpl_state->the_completion.text_alloc > len)
1788     {
1789       strcat(cmpl_state->the_completion.text, text);
1790       return &cmpl_state->the_completion;
1791     }
1792
1793   while(i < len) { i <<= 1; }
1794
1795   cmpl_state->the_completion.text_alloc = i;
1796
1797   cmpl_state->the_completion.text = (gchar*)g_realloc(cmpl_state->the_completion.text, i);
1798
1799   if(!cmpl_state->the_completion.text)
1800     return NULL;
1801   else
1802     {
1803       strcat(cmpl_state->the_completion.text, text);
1804       return &cmpl_state->the_completion;
1805     }
1806 }
1807
1808 static CompletionDir*
1809 find_completion_dir(gchar* text_to_complete,
1810                     gchar** remaining_text,
1811                     CompletionState* cmpl_state)
1812 {
1813   gchar* first_slash = strchr(text_to_complete, '/');
1814   CompletionDir* dir = cmpl_state->reference_dir;
1815   *remaining_text = text_to_complete;
1816
1817   while(first_slash)
1818     {
1819       gint len = first_slash - *remaining_text;
1820       gint found = 0;
1821       gint found_index = -1;
1822       gint i;
1823       gchar* pat_buf = g_new (gchar, len + 1);
1824
1825       strncpy(pat_buf, *remaining_text, len);
1826       pat_buf[len] = 0;
1827
1828       for(i = 0; i < dir->sent->entry_count; i += 1)
1829         {
1830           if(dir->sent->entries[i].is_dir &&
1831              fnmatch(pat_buf, dir->sent->entries[i].entry_name,
1832                      FNMATCH_FLAGS)!= FNM_NOMATCH)
1833             {
1834               if(found)
1835                 {
1836                   g_free (pat_buf);
1837                   return dir;
1838                 }
1839               else
1840                 {
1841                   found = 1;
1842                   found_index = i;
1843                 }
1844             }
1845         }
1846
1847       if(found)
1848         {
1849           CompletionDir* next = open_relative_dir(dir->sent->entries[found_index].entry_name,
1850                                                   dir, cmpl_state);
1851
1852           if(!next)
1853             {
1854               g_free (pat_buf);
1855               return NULL;
1856             }
1857
1858           next->cmpl_parent = dir;
1859
1860           dir = next;
1861
1862           if(!correct_dir_fullname(dir))
1863             {
1864               g_free(pat_buf);
1865               return NULL;
1866             }
1867
1868           *remaining_text = first_slash + 1;
1869           first_slash = strchr(*remaining_text, '/');
1870         }
1871       else
1872         {
1873           g_free (pat_buf);
1874           return NULL;
1875         }
1876
1877       g_free (pat_buf);
1878     }
1879
1880   return dir;
1881 }
1882
1883 static void
1884 update_cmpl(PossibleCompletion* poss, CompletionState* cmpl_state)
1885 {
1886   gint cmpl_len;
1887
1888   if(!poss || !cmpl_is_a_completion(poss))
1889     return;
1890
1891   cmpl_len = strlen(cmpl_this_completion(poss));
1892
1893   if(cmpl_state->updated_text_alloc < cmpl_len + 1)
1894     {
1895       cmpl_state->updated_text =
1896         (gchar*)g_realloc(cmpl_state->updated_text,
1897                           cmpl_state->updated_text_alloc);
1898       cmpl_state->updated_text_alloc = 2*cmpl_len;
1899     }
1900
1901   if(cmpl_state->updated_text_len < 0)
1902     {
1903       strcpy(cmpl_state->updated_text, cmpl_this_completion(poss));
1904       cmpl_state->updated_text_len = cmpl_len;
1905       cmpl_state->re_complete = cmpl_is_directory(poss);
1906     }
1907   else if(cmpl_state->updated_text_len == 0)
1908     {
1909       cmpl_state->re_complete = FALSE;
1910     }
1911   else
1912     {
1913       gint first_diff =
1914         first_diff_index(cmpl_state->updated_text,
1915                          cmpl_this_completion(poss));
1916
1917       cmpl_state->re_complete = FALSE;
1918
1919       if(first_diff == PATTERN_MATCH)
1920         return;
1921
1922       if(first_diff > cmpl_state->updated_text_len)
1923         strcpy(cmpl_state->updated_text, cmpl_this_completion(poss));
1924
1925       cmpl_state->updated_text_len = first_diff;
1926       cmpl_state->updated_text[first_diff] = 0;
1927     }
1928 }
1929
1930 static PossibleCompletion*
1931 attempt_file_completion(CompletionState *cmpl_state)
1932 {
1933   gchar *pat_buf, *first_slash;
1934   CompletionDir *dir = cmpl_state->active_completion_dir;
1935
1936   dir->cmpl_index += 1;
1937
1938   if(dir->cmpl_index == dir->sent->entry_count)
1939     {
1940       if(dir->cmpl_parent == NULL)
1941         {
1942           cmpl_state->active_completion_dir = NULL;
1943
1944           return NULL;
1945         }
1946       else
1947         {
1948           cmpl_state->active_completion_dir = dir->cmpl_parent;
1949
1950           return attempt_file_completion(cmpl_state);
1951         }
1952     }
1953
1954   g_assert(dir->cmpl_text);
1955
1956   first_slash = strchr(dir->cmpl_text, '/');
1957
1958   if(first_slash)
1959     {
1960       gint len = first_slash - dir->cmpl_text;
1961
1962       pat_buf = g_new (gchar, len + 1);
1963       strncpy(pat_buf, dir->cmpl_text, len);
1964       pat_buf[len] = 0;
1965     }
1966   else
1967     {
1968       gint len = strlen(dir->cmpl_text);
1969
1970       pat_buf = g_new (gchar, len + 2);
1971       strcpy(pat_buf, dir->cmpl_text);
1972       strcpy(pat_buf + len, "*");
1973     }
1974
1975   if(first_slash)
1976     {
1977       if(dir->sent->entries[dir->cmpl_index].is_dir)
1978         {
1979           if(fnmatch(pat_buf, dir->sent->entries[dir->cmpl_index].entry_name,
1980                      FNMATCH_FLAGS) != FNM_NOMATCH)
1981             {
1982               CompletionDir* new_dir;
1983
1984               new_dir = open_relative_dir(dir->sent->entries[dir->cmpl_index].entry_name,
1985                                           dir, cmpl_state);
1986
1987               if(!new_dir)
1988                 {
1989                   g_free (pat_buf);
1990                   return NULL;
1991                 }
1992
1993               new_dir->cmpl_parent = dir;
1994
1995               new_dir->cmpl_index = -1;
1996               new_dir->cmpl_text = first_slash + 1;
1997
1998               cmpl_state->active_completion_dir = new_dir;
1999
2000               g_free (pat_buf);
2001               return attempt_file_completion(cmpl_state);
2002             }
2003           else
2004             {
2005               g_free (pat_buf);
2006               return attempt_file_completion(cmpl_state);
2007             }
2008         }
2009       else
2010         {
2011           g_free (pat_buf);
2012           return attempt_file_completion(cmpl_state);
2013         }
2014     }
2015   else
2016     {
2017       if(dir->cmpl_parent != NULL)
2018         {
2019           append_completion_text(dir->fullname +
2020                                  strlen(cmpl_state->completion_dir->fullname) + 1,
2021                                  cmpl_state);
2022           append_completion_text("/", cmpl_state);
2023         }
2024
2025       append_completion_text(dir->sent->entries[dir->cmpl_index].entry_name, cmpl_state);
2026
2027       cmpl_state->the_completion.is_a_completion =
2028         (fnmatch(pat_buf, dir->sent->entries[dir->cmpl_index].entry_name,
2029                  FNMATCH_FLAGS) != FNM_NOMATCH);
2030
2031       cmpl_state->the_completion.is_directory = dir->sent->entries[dir->cmpl_index].is_dir;
2032       if(dir->sent->entries[dir->cmpl_index].is_dir)
2033         append_completion_text("/", cmpl_state);
2034
2035       g_free (pat_buf);
2036       return &cmpl_state->the_completion;
2037     }
2038 }
2039
2040
2041 static gint
2042 get_pwdb(CompletionState* cmpl_state)
2043 {
2044   struct passwd *pwd_ptr;
2045   gchar* buf_ptr, *home_dir = NULL;
2046   gint len = 0, i, count = 0;
2047
2048   if(cmpl_state->user_dir_name_buffer)
2049     return TRUE;
2050   setpwent ();
2051
2052   while ((pwd_ptr = getpwent()) != NULL)
2053     {
2054       len += strlen(pwd_ptr->pw_name);
2055       len += strlen(pwd_ptr->pw_dir);
2056       len += 2;
2057       count += 1;
2058     }
2059
2060   if (!cmpl_state->user_home_dir)
2061     {
2062       /* the loser doesn't have $HOME set */
2063       setpwent ();
2064
2065       pwd_ptr = getpwuid(getuid());
2066       if(!pwd_ptr)
2067         {
2068           cmpl_errno = errno;
2069           goto error;
2070         }
2071       home_dir = pwd_ptr->pw_dir;
2072
2073       len += strlen(home_dir);
2074       len += 1;
2075     }
2076
2077   setpwent ();
2078
2079   cmpl_state->user_dir_name_buffer = g_new(gchar, len);
2080   cmpl_state->user_directories = g_new(CompletionUserDir, count);
2081   cmpl_state->user_directories_len = count;
2082
2083   buf_ptr = cmpl_state->user_dir_name_buffer;
2084
2085   if (!cmpl_state->user_home_dir)
2086     {
2087       strcpy(buf_ptr, home_dir);
2088       cmpl_state->user_home_dir = buf_ptr;
2089       buf_ptr += strlen(buf_ptr);
2090       buf_ptr += 1;
2091     }
2092
2093   for(i = 0; i < count; i += 1)
2094     {
2095       pwd_ptr = getpwent();
2096       if(!pwd_ptr)
2097         {
2098           cmpl_errno = errno;
2099           goto error;
2100         }
2101
2102       strcpy(buf_ptr, pwd_ptr->pw_name);
2103       cmpl_state->user_directories[i].login = buf_ptr;
2104       buf_ptr += strlen(buf_ptr);
2105       buf_ptr += 1;
2106       strcpy(buf_ptr, pwd_ptr->pw_dir);
2107       cmpl_state->user_directories[i].homedir = buf_ptr;
2108       buf_ptr += strlen(buf_ptr);
2109       buf_ptr += 1;
2110     }
2111
2112   qsort(cmpl_state->user_directories,
2113         cmpl_state->user_directories_len,
2114         sizeof(CompletionUserDir),
2115         compare_user_dir);
2116
2117   endpwent();
2118
2119   return TRUE;
2120
2121 error:
2122
2123   if(cmpl_state->user_dir_name_buffer)
2124     g_free(cmpl_state->user_dir_name_buffer);
2125   if(cmpl_state->user_directories)
2126     g_free(cmpl_state->user_directories);
2127
2128   cmpl_state->user_dir_name_buffer = NULL;
2129   cmpl_state->user_directories = NULL;
2130
2131   return FALSE;
2132 }
2133
2134 static gint
2135 compare_user_dir(const void* a, const void* b)
2136 {
2137   return strcmp((((CompletionUserDir*)a))->login,
2138                 (((CompletionUserDir*)b))->login);
2139 }
2140
2141 static gint
2142 compare_cmpl_dir(const void* a, const void* b)
2143 {
2144   return strcmp((((CompletionDirEntry*)a))->entry_name,
2145                 (((CompletionDirEntry*)b))->entry_name);
2146 }
2147
2148 static gint
2149 cmpl_state_okay(CompletionState* cmpl_state)
2150 {
2151   return  cmpl_state && cmpl_state->reference_dir;
2152 }
2153
2154 static gchar*
2155 cmpl_strerror(gint err)
2156 {
2157   if(err == CMPL_ERRNO_TOO_LONG)
2158     return "Name too long";
2159   else
2160     return g_strerror (err);
2161 }