2163 lines
		
	
	
		
			54 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			2163 lines
		
	
	
		
			54 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /* GTK - The GIMP Toolkit
 | |
|  * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
 | |
|  *
 | |
|  * This library is free software; you can redistribute it and/or
 | |
|  * modify it under the terms of the GNU Library General Public
 | |
|  * License as published by the Free Software Foundation; either
 | |
|  * version 2 of the License, or (at your option) any later version.
 | |
|  *
 | |
|  * This library is distributed in the hope that it will be useful,
 | |
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 | |
|  * Library General Public License for more details.
 | |
|  *
 | |
|  * You should have received a copy of the GNU Library General Public
 | |
|  * License along with this library; if not, write to the Free
 | |
|  * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 | |
|  */
 | |
| #include <stdio.h>
 | |
| #include <sys/types.h>
 | |
| #include <sys/stat.h>
 | |
| #include <sys/param.h>
 | |
| #include <dirent.h>
 | |
| #include <stdlib.h>
 | |
| #include <unistd.h>
 | |
| #include <string.h>
 | |
| #include <errno.h>
 | |
| #include <pwd.h>
 | |
| #include "fnmatch.h"
 | |
| 
 | |
| #include "gdk/gdkkeysyms.h"
 | |
| #include "gtkbutton.h"
 | |
| #include "gtkentry.h"
 | |
| #include "gtkfilesel.h"
 | |
| #include "gtkhbox.h"
 | |
| #include "gtklabel.h"
 | |
| #include "gtklist.h"
 | |
| #include "gtklistitem.h"
 | |
| #include "gtkmain.h"
 | |
| #include "gtkscrolledwindow.h"
 | |
| #include "gtksignal.h"
 | |
| #include "gtkvbox.h"
 | |
| 
 | |
| 
 | |
| #define DIR_LIST_WIDTH   160
 | |
| #define DIR_LIST_HEIGHT  175
 | |
| #define FILE_LIST_WIDTH  160
 | |
| #define FILE_LIST_HEIGHT 175
 | |
| 
 | |
| 
 | |
| typedef struct _CompletionState    CompletionState;
 | |
| typedef struct _CompletionDir      CompletionDir;
 | |
| typedef struct _CompletionDirSent  CompletionDirSent;
 | |
| typedef struct _CompletionDirEntry CompletionDirEntry;
 | |
| typedef struct _CompletionUserDir  CompletionUserDir;
 | |
| typedef struct _PossibleCompletion PossibleCompletion;
 | |
| 
 | |
| /* Non-external file completion decls and structures */
 | |
| 
 | |
| /* A contant telling PRCS how many directories to cache.  Its actually
 | |
|  * kept in a list, so the geometry isn't important. */
 | |
| #define CMPL_DIRECTORY_CACHE_SIZE 10
 | |
| 
 | |
| /* A constant used to determine whether a substring was an exact
 | |
|  * match by first_diff_index()
 | |
|  */
 | |
| #define PATTERN_MATCH -1
 | |
| /* The arguments used by all fnmatch() calls below
 | |
|  */
 | |
| #define FNMATCH_FLAGS (FNM_PATHNAME | FNM_PERIOD)
 | |
| 
 | |
| #define CMPL_ERRNO_TOO_LONG ((1<<16)-1)
 | |
| 
 | |
| /* This structure contains all the useful information about a directory
 | |
|  * for the purposes of filename completion.  These structures are cached
 | |
|  * in the CompletionState struct.  CompletionDir's are reference counted.
 | |
|  */
 | |
| struct _CompletionDirSent
 | |
| {
 | |
|   ino_t inode;
 | |
|   time_t mtime;
 | |
| 
 | |
|   gint entry_count;
 | |
|   gchar *name_buffer; /* memory segment containing names of all entries */
 | |
| 
 | |
|   struct _CompletionDirEntry *entries;
 | |
| };
 | |
| 
 | |
| struct _CompletionDir
 | |
| {
 | |
|   CompletionDirSent *sent;
 | |
| 
 | |
|   gchar *fullname;
 | |
|   gint fullname_len;
 | |
| 
 | |
|   struct _CompletionDir *cmpl_parent;
 | |
|   gint cmpl_index;
 | |
|   gchar *cmpl_text;
 | |
| };
 | |
| 
 | |
| /* This structure contains pairs of directory entry names with a flag saying
 | |
|  * whether or not they are a valid directory.  NOTE: This information is used
 | |
|  * to provide the caller with information about whether to update its completions
 | |
|  * or try to open a file.  Since directories are cached by the directory mtime,
 | |
|  * a symlink which points to an invalid file (which will not be a directory),
 | |
|  * will not be reevaluated if that file is created, unless the containing
 | |
|  * directory is touched.  I consider this case to be worth ignoring (josh).
 | |
|  */
 | |
| struct _CompletionDirEntry
 | |
| {
 | |
|   gint is_dir;
 | |
|   gchar *entry_name;
 | |
| };
 | |
| 
 | |
| struct _CompletionUserDir
 | |
| {
 | |
|   gchar *login;
 | |
|   gchar *homedir;
 | |
| };
 | |
| 
 | |
| struct _PossibleCompletion
 | |
| {
 | |
|   /* accessible fields, all are accessed externally by functions
 | |
|    * declared above
 | |
|    */
 | |
|   gchar *text;
 | |
|   gint is_a_completion;
 | |
|   gint is_directory;
 | |
| 
 | |
|   /* Private fields
 | |
|    */
 | |
|   gint text_alloc;
 | |
| };
 | |
| 
 | |
| struct _CompletionState
 | |
| {
 | |
|   gint last_valid_char;
 | |
|   gchar *updated_text;
 | |
|   gint updated_text_len;
 | |
|   gint updated_text_alloc;
 | |
|   gint re_complete;
 | |
| 
 | |
|   gchar *user_dir_name_buffer;
 | |
|   gint user_directories_len;
 | |
|   gchar *user_home_dir;
 | |
| 
 | |
|   gchar *last_completion_text;
 | |
| 
 | |
|   gint user_completion_index; /* if >= 0, currently completing ~user */
 | |
| 
 | |
|   struct _CompletionDir *completion_dir; /* directory completing from */
 | |
|   struct _CompletionDir *active_completion_dir;
 | |
| 
 | |
|   struct _PossibleCompletion the_completion;
 | |
| 
 | |
|   struct _CompletionDir *reference_dir; /* initial directory */
 | |
| 
 | |
|   GList* directory_storage;
 | |
|   GList* directory_sent_storage;
 | |
| 
 | |
|   struct _CompletionUserDir *user_directories;
 | |
| };
 | |
| 
 | |
| 
 | |
| /* File completion functions which would be external, were they used
 | |
|  * outside of this file.
 | |
|  */
 | |
| 
 | |
| static CompletionState*    cmpl_init_state        (void);
 | |
| static void                cmpl_free_state        (CompletionState *cmpl_state);
 | |
| static gint                cmpl_state_okay        (CompletionState* cmpl_state);
 | |
| static gchar*              cmpl_strerror          (gint);
 | |
| 
 | |
| static PossibleCompletion* cmpl_completion_matches(gchar           *text_to_complete,
 | |
| 						   gchar          **remaining_text,
 | |
| 						   CompletionState *cmpl_state);
 | |
| 
 | |
| /* Returns a name for consideration, possibly a completion, this name
 | |
|  * will be invalid after the next call to cmpl_next_completion.
 | |
|  */
 | |
| static char*               cmpl_this_completion   (PossibleCompletion*);
 | |
| 
 | |
| /* True if this completion matches the given text.  Otherwise, this
 | |
|  * output can be used to have a list of non-completions.
 | |
|  */
 | |
| static gint                cmpl_is_a_completion   (PossibleCompletion*);
 | |
| 
 | |
| /* True if the completion is a directory
 | |
|  */
 | |
| static gint                cmpl_is_directory      (PossibleCompletion*);
 | |
| 
 | |
| /* Obtains the next completion, or NULL
 | |
|  */
 | |
| static PossibleCompletion* cmpl_next_completion   (CompletionState*);
 | |
| 
 | |
| /* Updating completions: the return value of cmpl_updated_text() will
 | |
|  * be text_to_complete completed as much as possible after the most
 | |
|  * recent call to cmpl_completion_matches.  For the present
 | |
|  * application, this is the suggested replacement for the user's input
 | |
|  * string.  You must CALL THIS AFTER ALL cmpl_text_completions have
 | |
|  * been received.
 | |
|  */
 | |
| static gchar*              cmpl_updated_text       (CompletionState* cmpl_state);
 | |
| 
 | |
| /* After updating, to see if the completion was a directory, call
 | |
|  * this.  If it was, you should consider re-calling completion_matches.
 | |
|  */
 | |
| static gint                cmpl_updated_dir        (CompletionState* cmpl_state);
 | |
| 
 | |
| /* Current location: if using file completion, return the current
 | |
|  * directory, from which file completion begins.  More specifically,
 | |
|  * the cwd concatenated with all exact completions up to the last
 | |
|  * directory delimiter('/').
 | |
|  */
 | |
| static gchar*              cmpl_reference_position (CompletionState* cmpl_state);
 | |
| 
 | |
| /* backing up: if cmpl_completion_matches returns NULL, you may query
 | |
|  * the index of the last completable character into cmpl_updated_text.
 | |
|  */
 | |
| static gint                cmpl_last_valid_char    (CompletionState* cmpl_state);
 | |
| 
 | |
| /* When the user selects a non-directory, call cmpl_completion_fullname
 | |
|  * to get the full name of the selected file.
 | |
|  */
 | |
| static gchar*              cmpl_completion_fullname (gchar*, CompletionState* cmpl_state);
 | |
| 
 | |
| 
 | |
| /* Directory operations. */
 | |
| static CompletionDir* open_ref_dir         (gchar* text_to_complete,
 | |
| 					    gchar** remaining_text,
 | |
| 					    CompletionState* cmpl_state);
 | |
| static CompletionDir* open_dir             (gchar* dir_name,
 | |
| 					    CompletionState* cmpl_state);
 | |
| static CompletionDir* open_user_dir        (gchar* text_to_complete,
 | |
| 					    CompletionState *cmpl_state);
 | |
| static CompletionDir* open_relative_dir    (gchar* dir_name, CompletionDir* dir,
 | |
| 					    CompletionState *cmpl_state);
 | |
| static CompletionDirSent* open_new_dir         (gchar* dir_name, struct stat* sbuf);
 | |
| static gint           correct_dir_fullname (CompletionDir* cmpl_dir);
 | |
| static gint           correct_parent       (CompletionDir* cmpl_dir,
 | |
| 					    struct stat *sbuf);
 | |
| static gchar*         find_parent_dir_fullname    (gchar* dirname);
 | |
| static CompletionDir* attach_dir           (CompletionDirSent* sent,
 | |
| 					    gchar* dir_name,
 | |
| 					    CompletionState *cmpl_state);
 | |
| static void           free_dir_sent (CompletionDirSent* sent);
 | |
| static void           free_dir      (CompletionDir  *dir);
 | |
| static void           prune_memory_usage(CompletionState *cmpl_state);
 | |
| 
 | |
| /* Completion operations */
 | |
| static PossibleCompletion* attempt_homedir_completion(gchar* text_to_complete,
 | |
| 						      CompletionState *cmpl_state);
 | |
| static PossibleCompletion* attempt_file_completion(CompletionState *cmpl_state);
 | |
| static CompletionDir* find_completion_dir(gchar* text_to_complete,
 | |
| 					  gchar** remaining_text,
 | |
| 					  CompletionState* cmpl_state);
 | |
| static PossibleCompletion* append_completion_text(gchar* text,
 | |
| 						  CompletionState* cmpl_state);
 | |
| static gint get_pwdb(CompletionState* cmpl_state);
 | |
| static gint first_diff_index(gchar* pat, gchar* text);
 | |
| static gint compare_user_dir(const void* a, const void* b);
 | |
| static gint compare_cmpl_dir(const void* a, const void* b);
 | |
| static void update_cmpl(PossibleCompletion* poss,
 | |
| 			CompletionState* cmpl_state);
 | |
| 
 | |
| static void gtk_file_selection_class_init    (GtkFileSelectionClass *klass);
 | |
| static void gtk_file_selection_init          (GtkFileSelection      *filesel);
 | |
| static void gtk_file_selection_destroy       (GtkObject             *object);
 | |
| static gint gtk_file_selection_key_press     (GtkWidget             *widget,
 | |
| 					      GdkEventKey           *event,
 | |
| 					      gpointer               user_data);
 | |
| static gint gtk_file_selection_file_button   (GtkWidget             *widget,
 | |
| 					      GdkEventButton        *event,
 | |
| 					      gpointer               user_data);
 | |
| static gint gtk_file_selection_dir_button    (GtkWidget             *widget,
 | |
| 					      GdkEventButton        *event,
 | |
| 					      gpointer               user_data);
 | |
| static void gtk_file_selection_file_list_changed  (GtkList          *gtklist,
 | |
| 					           gpointer	     func_data);
 | |
| static void gtk_file_selection_dir_list_changed   (GtkList     	    *gtklist,
 | |
| 					           gpointer	     func_data);
 | |
| static void gtk_file_selection_populate      (GtkFileSelection      *fs,
 | |
| 					      gchar                 *rel_path,
 | |
| 					      gint                   try_complete);
 | |
| static void gtk_file_selection_abort         (GtkFileSelection      *fs);
 | |
| static void gtk_file_selection_free_filename (GtkWidget             *widget,
 | |
| 					      gpointer               client_data);
 | |
| 
 | |
| 
 | |
| static GtkWindowClass *parent_class = NULL;
 | |
| 
 | |
| static gchar *list_changed_key = "_gtk_selection_changed_handler_key";
 | |
| 
 | |
| /* Saves errno when something cmpl does fails. */
 | |
| static gint cmpl_errno;
 | |
| 
 | |
| 
 | |
| guint
 | |
| gtk_file_selection_get_type ()
 | |
| {
 | |
|   static guint file_selection_type = 0;
 | |
| 
 | |
|   if (!file_selection_type)
 | |
|     {
 | |
|       GtkTypeInfo filesel_info =
 | |
|       {
 | |
| 	"GtkFileSelection",
 | |
| 	sizeof (GtkFileSelection),
 | |
| 	sizeof (GtkFileSelectionClass),
 | |
| 	(GtkClassInitFunc) gtk_file_selection_class_init,
 | |
| 	(GtkObjectInitFunc) gtk_file_selection_init,
 | |
| 	(GtkArgFunc) NULL,
 | |
|       };
 | |
| 
 | |
|       file_selection_type = gtk_type_unique (gtk_window_get_type (), &filesel_info);
 | |
|     }
 | |
| 
 | |
|   return file_selection_type;
 | |
| }
 | |
| 
 | |
| static void
 | |
| gtk_file_selection_class_init (GtkFileSelectionClass *class)
 | |
| {
 | |
|   GtkObjectClass *object_class;
 | |
| 
 | |
|   object_class = (GtkObjectClass*) class;
 | |
| 
 | |
|   parent_class = gtk_type_class (gtk_window_get_type ());
 | |
| 
 | |
|   object_class->destroy = gtk_file_selection_destroy;
 | |
| }
 | |
| 
 | |
| static void
 | |
| gtk_file_selection_init (GtkFileSelection *filesel)
 | |
| {
 | |
|   GtkWidget *dir_vbox;
 | |
|   GtkWidget *file_vbox;
 | |
|   GtkWidget *entry_vbox;
 | |
|   GtkWidget *listbox;
 | |
|   GtkWidget *label;
 | |
|   GtkWidget *list_hbox;
 | |
|   GtkWidget *action_area;
 | |
|   gint key;
 | |
| 
 | |
|   filesel->cmpl_state = cmpl_init_state ();
 | |
| 
 | |
|   /*  The dialog-sized vertical box  */
 | |
|   filesel->main_vbox = gtk_vbox_new (FALSE, 10);
 | |
|   gtk_container_border_width (GTK_CONTAINER (filesel), 10);
 | |
|   gtk_container_add (GTK_CONTAINER (filesel), filesel->main_vbox);
 | |
|   gtk_widget_show (filesel->main_vbox);
 | |
| 
 | |
|   /*  The horizontal box containing the directory and file listboxes  */
 | |
|   list_hbox = gtk_hbox_new (TRUE, 5);
 | |
|   gtk_box_pack_start (GTK_BOX (filesel->main_vbox), list_hbox, TRUE, TRUE, 0);
 | |
|   gtk_widget_show (list_hbox);
 | |
| 
 | |
| 
 | |
|   /* The directories listbox  */
 | |
|   dir_vbox = gtk_vbox_new (FALSE, 2);
 | |
|   gtk_box_pack_start (GTK_BOX (list_hbox), dir_vbox, TRUE, TRUE, 0);
 | |
|   gtk_widget_show (dir_vbox);
 | |
| 
 | |
|   label = gtk_label_new ("Directories");
 | |
|   gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
 | |
|   gtk_box_pack_start (GTK_BOX (dir_vbox), label, FALSE, FALSE, 0);
 | |
|   gtk_widget_show (label);
 | |
| 
 | |
|   listbox = gtk_scrolled_window_new (NULL, NULL);
 | |
|   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (listbox),
 | |
| 				  GTK_POLICY_AUTOMATIC,
 | |
| 				  GTK_POLICY_ALWAYS);
 | |
|   gtk_box_pack_start (GTK_BOX (dir_vbox), listbox, TRUE, TRUE, 0);
 | |
|   gtk_widget_set_usize (listbox, DIR_LIST_WIDTH, DIR_LIST_HEIGHT);
 | |
|   gtk_widget_show (listbox);
 | |
| 
 | |
|   filesel->dir_list = gtk_list_new ();
 | |
|   gtk_list_set_selection_mode (GTK_LIST (filesel->dir_list), GTK_SELECTION_BROWSE);
 | |
|   gtk_signal_connect (GTK_OBJECT (filesel->dir_list), "button_press_event",
 | |
| 		      (GtkSignalFunc) gtk_file_selection_dir_button, filesel);
 | |
|   key = gtk_signal_connect (GTK_OBJECT (filesel->dir_list),
 | |
| 			    "selection_changed",
 | |
| 			    (GtkSignalFunc) gtk_file_selection_dir_list_changed,
 | |
| 			    filesel);
 | |
|   gtk_object_set_data (GTK_OBJECT (filesel->dir_list), list_changed_key, (gpointer) key);
 | |
|   gtk_container_add (GTK_CONTAINER (listbox), filesel->dir_list);
 | |
|   gtk_widget_show (filesel->dir_list);
 | |
| 
 | |
| 
 | |
|   /* The files listbox  */
 | |
|   file_vbox = gtk_vbox_new (FALSE, 2);
 | |
|   gtk_box_pack_start (GTK_BOX (list_hbox), file_vbox, TRUE, TRUE, 0);
 | |
|   gtk_widget_show (file_vbox);
 | |
| 
 | |
|   label = gtk_label_new ("Files");
 | |
|   gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
 | |
|   gtk_box_pack_start (GTK_BOX (file_vbox), label, FALSE, FALSE, 0);
 | |
|   gtk_widget_show (label);
 | |
| 
 | |
|   listbox = gtk_scrolled_window_new (NULL, NULL);
 | |
|   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (listbox),
 | |
| 				  GTK_POLICY_AUTOMATIC,
 | |
| 				  GTK_POLICY_ALWAYS);
 | |
|   gtk_box_pack_start (GTK_BOX (file_vbox), listbox, TRUE, TRUE, 0);
 | |
|   gtk_widget_set_usize (listbox, FILE_LIST_WIDTH, FILE_LIST_HEIGHT);
 | |
|   gtk_widget_show (listbox);
 | |
| 
 | |
|   filesel->file_list = gtk_list_new ();
 | |
|   gtk_list_set_selection_mode (GTK_LIST (filesel->file_list), GTK_SELECTION_SINGLE);
 | |
|   gtk_signal_connect (GTK_OBJECT (filesel->file_list), "button_press_event",
 | |
| 		      (GtkSignalFunc) gtk_file_selection_file_button, filesel);
 | |
|   key = gtk_signal_connect (GTK_OBJECT (filesel->file_list),
 | |
| 			    "selection_changed",
 | |
| 			    (GtkSignalFunc) gtk_file_selection_file_list_changed,
 | |
| 			    filesel);
 | |
|   gtk_object_set_data (GTK_OBJECT (filesel->file_list), list_changed_key, (gpointer) key);
 | |
|   gtk_container_add (GTK_CONTAINER (listbox), filesel->file_list);
 | |
|   gtk_widget_show (filesel->file_list);
 | |
| 
 | |
| 
 | |
|   /*  The action area  */
 | |
|   action_area = gtk_hbox_new (TRUE, 10);
 | |
|   gtk_box_pack_end (GTK_BOX (filesel->main_vbox), action_area, FALSE, FALSE, 0);
 | |
|   gtk_widget_show (action_area);
 | |
| 
 | |
|   /*  The OK button  */
 | |
|   filesel->ok_button = gtk_button_new_with_label ("OK");
 | |
|   GTK_WIDGET_SET_FLAGS (filesel->ok_button, GTK_CAN_DEFAULT);
 | |
|   gtk_box_pack_start (GTK_BOX (action_area), filesel->ok_button, TRUE, TRUE, 0);
 | |
|   gtk_widget_grab_default (filesel->ok_button);
 | |
|   gtk_widget_show (filesel->ok_button);
 | |
| 
 | |
|   /*  The Cancel button  */
 | |
|   filesel->cancel_button = gtk_button_new_with_label ("Cancel");
 | |
|   GTK_WIDGET_SET_FLAGS (filesel->cancel_button, GTK_CAN_DEFAULT);
 | |
|   gtk_box_pack_start (GTK_BOX (action_area), filesel->cancel_button, TRUE, TRUE, 0);
 | |
|   gtk_widget_show (filesel->cancel_button);
 | |
| 
 | |
|   /*  The Help button  */
 | |
|   filesel->help_button = gtk_button_new_with_label ("Help");
 | |
|   GTK_WIDGET_SET_FLAGS (filesel->help_button, GTK_CAN_DEFAULT);
 | |
|   gtk_box_pack_start (GTK_BOX (action_area), filesel->help_button, TRUE, TRUE, 0);
 | |
|   gtk_widget_show (filesel->help_button);
 | |
| 
 | |
| 
 | |
|   /*  The selection entry widget  */
 | |
|   entry_vbox = gtk_vbox_new (FALSE, 2);
 | |
|   gtk_box_pack_end (GTK_BOX (filesel->main_vbox), entry_vbox, FALSE, FALSE, 0);
 | |
|   gtk_widget_show (entry_vbox);
 | |
| 
 | |
|   filesel->selection_text = label = gtk_label_new ("");
 | |
|   gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
 | |
|   gtk_box_pack_start (GTK_BOX (entry_vbox), label, FALSE, FALSE, 0);
 | |
|   gtk_widget_show (label);
 | |
| 
 | |
|   filesel->selection_entry = gtk_entry_new ();
 | |
|   gtk_signal_connect (GTK_OBJECT (filesel->selection_entry), "key_press_event",
 | |
| 		      (GtkSignalFunc) gtk_file_selection_key_press, filesel);
 | |
|   gtk_signal_connect_object (GTK_OBJECT (filesel->selection_entry), "focus_in_event",
 | |
| 			     (GtkSignalFunc) gtk_widget_grab_default,
 | |
| 			     GTK_OBJECT (filesel->ok_button));
 | |
|   gtk_signal_connect_object (GTK_OBJECT (filesel->selection_entry), "activate",
 | |
|                              (GtkSignalFunc) gtk_button_clicked,
 | |
|                              GTK_OBJECT (filesel->ok_button));
 | |
|   gtk_box_pack_start (GTK_BOX (entry_vbox), filesel->selection_entry, TRUE, TRUE, 0);
 | |
|   gtk_widget_show (filesel->selection_entry);
 | |
| 
 | |
|   if (!cmpl_state_okay (filesel->cmpl_state))
 | |
|     {
 | |
|       gchar err_buf[256];
 | |
| 
 | |
|       sprintf (err_buf, "Directory unreadable: %s", cmpl_strerror (cmpl_errno));
 | |
| 
 | |
|       gtk_label_set (GTK_LABEL (filesel->selection_text), err_buf);
 | |
|     }
 | |
|   else
 | |
|     {
 | |
|       gtk_file_selection_populate (filesel, "", FALSE);
 | |
|     }
 | |
| 
 | |
|   gtk_widget_grab_focus (filesel->selection_entry);
 | |
| }
 | |
| 
 | |
| GtkWidget*
 | |
| gtk_file_selection_new (const gchar *title)
 | |
| {
 | |
|   GtkFileSelection *filesel;
 | |
| 
 | |
|   filesel = gtk_type_new (gtk_file_selection_get_type ());
 | |
|   gtk_window_set_title (GTK_WINDOW (filesel), title);
 | |
| 
 | |
|   return GTK_WIDGET (filesel);
 | |
| }
 | |
| 
 | |
| void
 | |
| gtk_file_selection_set_filename (GtkFileSelection *filesel,
 | |
| 				 const gchar      *filename)
 | |
| {
 | |
|   char  buf[MAXPATHLEN];
 | |
|   const char *name, *last_slash;
 | |
| 
 | |
|   g_return_if_fail (filesel != NULL);
 | |
|   g_return_if_fail (GTK_IS_FILE_SELECTION (filesel));
 | |
|   g_return_if_fail (filename != NULL);
 | |
| 
 | |
|   last_slash = strrchr (filename, '/');
 | |
| 
 | |
|   if (!last_slash)
 | |
|     {
 | |
|       buf[0] = 0;
 | |
|       name = filename;
 | |
|     }
 | |
|   else
 | |
|     {
 | |
|       gint len = MIN (MAXPATHLEN - 1, last_slash - filename + 1);
 | |
| 
 | |
|       strncpy (buf, filename, len);
 | |
|       buf[len] = 0;
 | |
| 
 | |
|       name = last_slash + 1;
 | |
|     }
 | |
| 
 | |
|   gtk_file_selection_populate (filesel, buf, FALSE);
 | |
| 
 | |
|   if (filesel->selection_entry)
 | |
|     gtk_entry_set_text (GTK_ENTRY (filesel->selection_entry), name);
 | |
| }
 | |
| 
 | |
| gchar*
 | |
| gtk_file_selection_get_filename (GtkFileSelection *filesel)
 | |
| {
 | |
|   static char nothing[2] = "";
 | |
|   char *text;
 | |
|   char *filename;
 | |
| 
 | |
|   g_return_val_if_fail (filesel != NULL, nothing);
 | |
|   g_return_val_if_fail (GTK_IS_FILE_SELECTION (filesel), nothing);
 | |
| 
 | |
|   text = gtk_entry_get_text (GTK_ENTRY (filesel->selection_entry));
 | |
|   if (text)
 | |
|     {
 | |
|       filename = cmpl_completion_fullname (text, filesel->cmpl_state);
 | |
|       return filename;
 | |
|     }
 | |
| 
 | |
|   return nothing;
 | |
| }
 | |
| 
 | |
| static void
 | |
| gtk_file_selection_destroy (GtkObject *object)
 | |
| {
 | |
|   GtkFileSelection *filesel;
 | |
| 
 | |
|   g_return_if_fail (object != NULL);
 | |
|   g_return_if_fail (GTK_IS_FILE_SELECTION (object));
 | |
| 
 | |
|   filesel = GTK_FILE_SELECTION (object);
 | |
| 
 | |
|   cmpl_free_state (filesel->cmpl_state);
 | |
| 
 | |
|   if (GTK_OBJECT_CLASS (parent_class)->destroy)
 | |
|     (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
 | |
| }
 | |
| 
 | |
| static gint
 | |
| gtk_file_selection_key_press (GtkWidget   *widget,
 | |
| 			      GdkEventKey *event,
 | |
| 			      gpointer     user_data)
 | |
| {
 | |
|   GtkFileSelection *fs;
 | |
|   char *text;
 | |
| 
 | |
|   g_return_val_if_fail (widget != NULL, FALSE);
 | |
|   g_return_val_if_fail (event != NULL, FALSE);
 | |
| 
 | |
|   if (event->keyval == GDK_Tab)
 | |
|     {
 | |
|       gtk_signal_emit_stop_by_name (GTK_OBJECT (widget), "key_press_event");
 | |
| 
 | |
|       fs = GTK_FILE_SELECTION (user_data);
 | |
|       text = gtk_entry_get_text (GTK_ENTRY (fs->selection_entry));
 | |
|       gtk_file_selection_populate (fs, text, TRUE);
 | |
| 
 | |
|       return TRUE;
 | |
|     }
 | |
| 
 | |
|   return FALSE;
 | |
| }
 | |
| 
 | |
| static gint
 | |
| gtk_file_selection_file_button (GtkWidget      *widget,
 | |
| 				GdkEventButton *event,
 | |
| 				gpointer        user_data)
 | |
| {
 | |
|   GtkFileSelection *fs;
 | |
|   GtkWidget *event_widget;
 | |
|   gchar *filename;
 | |
|   gboolean handled;
 | |
| 
 | |
|   g_return_val_if_fail (widget != NULL, FALSE);
 | |
|   g_return_val_if_fail (event != NULL, FALSE);
 | |
| 
 | |
|   fs = GTK_FILE_SELECTION (user_data);
 | |
|   g_return_val_if_fail (fs != NULL, FALSE);
 | |
|   g_return_val_if_fail (GTK_IS_FILE_SELECTION (fs), FALSE);
 | |
| 
 | |
|   event_widget = gtk_get_event_widget ((GdkEvent*) event);
 | |
|   handled = FALSE;
 | |
|   if (GTK_IS_LIST_ITEM (event_widget))
 | |
|     {
 | |
|       switch (event->type)
 | |
| 	{
 | |
| 	case GDK_BUTTON_PRESS:
 | |
| 	  filename = gtk_object_get_user_data (GTK_OBJECT (event_widget));
 | |
| 	  gtk_widget_grab_focus (fs->selection_entry);
 | |
| 	  gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), filename);
 | |
| 	  handled = TRUE;
 | |
| 	  break;
 | |
| 
 | |
| 	case GDK_2BUTTON_PRESS:
 | |
| 	  gtk_button_clicked (GTK_BUTTON (fs->ok_button));
 | |
| 	  handled = TRUE;
 | |
| 	  break;
 | |
| 
 | |
| 	default:
 | |
| 	  break;
 | |
| 	}
 | |
|     }
 | |
| 
 | |
|   return handled;
 | |
| }
 | |
| 
 | |
| static void
 | |
| gtk_file_selection_file_list_changed (GtkList          *list,
 | |
| 				      gpointer          func_data)
 | |
| {
 | |
|   GtkFileSelection *fs;
 | |
| 
 | |
|   g_return_if_fail (func_data != NULL);
 | |
|   g_return_if_fail (GTK_IS_FILE_SELECTION (func_data));
 | |
| 
 | |
|   fs = GTK_FILE_SELECTION (func_data);
 | |
| 
 | |
|   /* only act on an appropriate selection
 | |
|    */
 | |
|   if (list->selection && list->selection->data)
 | |
|     {
 | |
|       GtkListItem *item;
 | |
| 
 | |
|       item = list->selection->data;
 | |
|       
 | |
|       if (GTK_IS_LIST_ITEM (item))
 | |
| 	{
 | |
| 	  gchar *filename;
 | |
| 	  
 | |
| 	  filename = gtk_object_get_user_data (GTK_OBJECT (item));
 | |
| 	  gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), filename);
 | |
| 	}
 | |
|     }
 | |
| }
 | |
| 
 | |
| static gint
 | |
| gtk_file_selection_dir_button (GtkWidget      *widget,
 | |
| 			       GdkEventButton *event,
 | |
| 			       gpointer        user_data)
 | |
| {
 | |
|   GtkFileSelection *fs;
 | |
|   GtkWidget *event_widget;
 | |
|   gchar *filename;
 | |
|   gboolean handled;
 | |
| 
 | |
|   g_return_val_if_fail (widget != NULL, FALSE);
 | |
|   g_return_val_if_fail (GTK_IS_LIST (widget), FALSE);
 | |
|   g_return_val_if_fail (event != NULL, FALSE);
 | |
| 
 | |
|   fs = GTK_FILE_SELECTION (user_data);
 | |
|   g_return_val_if_fail (fs != NULL, FALSE);
 | |
|   g_return_val_if_fail (GTK_IS_FILE_SELECTION (fs), FALSE);
 | |
| 
 | |
|   event_widget = gtk_get_event_widget ((GdkEvent*) event);
 | |
|   handled = FALSE;
 | |
|   if (GTK_IS_LIST_ITEM (event_widget))
 | |
|     {
 | |
|       gint key;
 | |
| 
 | |
|       filename = gtk_object_get_user_data (GTK_OBJECT (event_widget));
 | |
| 
 | |
|       key = (gint) gtk_object_get_data (GTK_OBJECT (widget), list_changed_key);
 | |
| 
 | |
|       switch (event->type)
 | |
| 	{
 | |
| 	case GDK_BUTTON_PRESS:
 | |
| 
 | |
| 	  gtk_signal_handler_block (GTK_OBJECT (widget), key);
 | |
| 	  gtk_widget_activate (GTK_WIDGET (event_widget));
 | |
| 	  gtk_signal_handler_unblock (GTK_OBJECT (widget), key);
 | |
| 
 | |
| 	  gtk_widget_grab_focus (fs->selection_entry);
 | |
| 	  gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), filename);
 | |
| 	  handled = TRUE;
 | |
| 	  break;
 | |
| 
 | |
| 	case GDK_2BUTTON_PRESS:
 | |
| 	  gtk_file_selection_populate (fs, filename, FALSE);
 | |
| 	  handled = TRUE;
 | |
| 	  break;
 | |
| 
 | |
| 	default:
 | |
| 	  break;
 | |
| 	}
 | |
|     }
 | |
| 
 | |
|   return handled;
 | |
| }
 | |
| 
 | |
| static void
 | |
| gtk_file_selection_dir_list_changed (GtkList          *list,
 | |
| 				     gpointer          func_data)
 | |
| {
 | |
|   GtkFileSelection *fs;
 | |
|   
 | |
|   g_return_if_fail (func_data != NULL);
 | |
|   g_return_if_fail (GTK_IS_FILE_SELECTION (func_data));
 | |
|   
 | |
|   fs = GTK_FILE_SELECTION (func_data);
 | |
| 
 | |
|   /* only act on an appropriate selection
 | |
|    */
 | |
|   if (list->selection && list->selection->data)
 | |
|     {
 | |
|       GtkListItem *item;
 | |
| 
 | |
|       item = list->selection->data;
 | |
|       
 | |
|       if (GTK_IS_LIST_ITEM (item))
 | |
| 	{
 | |
| 	  gchar *filename;
 | |
| 	  
 | |
| 	  filename = gtk_object_get_user_data (GTK_OBJECT (item));
 | |
| 
 | |
| 	  if (filename)
 | |
| 	    gtk_file_selection_populate (fs, filename, FALSE);
 | |
| 	}
 | |
|     }
 | |
| }
 | |
| 
 | |
| static void
 | |
| gtk_file_selection_populate (GtkFileSelection *fs,
 | |
| 			     gchar            *rel_path,
 | |
| 			     gint              try_complete)
 | |
| {
 | |
|   CompletionState *cmpl_state;
 | |
|   PossibleCompletion* poss;
 | |
|   GList *dir_list = NULL;
 | |
|   GList *file_list = NULL;
 | |
|   GtkWidget *label;
 | |
|   gchar* filename;
 | |
|   gchar* rem_path = rel_path;
 | |
|   gchar* sel_text;
 | |
|   gint did_recurse = FALSE;
 | |
|   gint possible_count = 0;
 | |
|   gint selection_index = -1;
 | |
|   gint dir_changed_key;
 | |
|   
 | |
|   g_return_if_fail (fs != NULL);
 | |
|   g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
 | |
|   
 | |
|   dir_changed_key = (gint) gtk_object_get_data (GTK_OBJECT (fs->dir_list), list_changed_key);
 | |
|   gtk_signal_handler_block (GTK_OBJECT (fs->dir_list), dir_changed_key);
 | |
|   
 | |
|   cmpl_state = (CompletionState*) fs->cmpl_state;
 | |
|   poss = cmpl_completion_matches (rel_path, &rem_path, cmpl_state);
 | |
| 
 | |
|   if (!cmpl_state_okay (cmpl_state))
 | |
|     {
 | |
|       /* Something went wrong. */
 | |
|       gtk_file_selection_abort (fs);
 | |
|       gtk_signal_handler_unblock (GTK_OBJECT (fs->dir_list), dir_changed_key);
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|   g_assert (cmpl_state->reference_dir);
 | |
| 
 | |
|   /* Set the dir_list and file_list to be GLists of strdup'd
 | |
|    * filenames, including ./ and ../ */
 | |
|   dir_list = g_list_prepend (dir_list, g_strdup("./"));
 | |
|   dir_list = g_list_prepend (dir_list, g_strdup("../"));
 | |
| 
 | |
|   while (poss)
 | |
|     {
 | |
|       if (cmpl_is_a_completion (poss))
 | |
|         {
 | |
|           possible_count += 1;
 | |
| 
 | |
|           filename = g_strdup (cmpl_this_completion (poss));
 | |
| 
 | |
|           if (cmpl_is_directory (poss))
 | |
|             {
 | |
|               if (strcmp (filename, "./") != 0 &&
 | |
|                   strcmp (filename, "../") != 0)
 | |
|                 dir_list = g_list_prepend (dir_list, filename);
 | |
|             }
 | |
|           else
 | |
|             file_list = g_list_prepend (file_list, filename);
 | |
|         }
 | |
| 
 | |
|       poss = cmpl_next_completion (cmpl_state);
 | |
|     }
 | |
| 
 | |
|   /* File lists are set. */
 | |
| 
 | |
|   g_assert (cmpl_state->reference_dir);
 | |
| 
 | |
|   if (try_complete)
 | |
|     {
 | |
|       /* User is trying to complete filenames, so advance the user's input
 | |
|        * string to the updated_text, which is the common leading substring
 | |
|        * of all possible completions, and if its a directory attempt
 | |
|        * attempt completions in it. */
 | |
| 
 | |
|       if (cmpl_updated_text (cmpl_state)[0])
 | |
|         {
 | |
|           if (cmpl_updated_dir (cmpl_state))
 | |
|             {
 | |
|               gchar* dir_name = g_strdup (cmpl_updated_text (cmpl_state));
 | |
| 
 | |
|               did_recurse = TRUE;
 | |
| 
 | |
|               gtk_file_selection_populate (fs, dir_name, TRUE);
 | |
| 
 | |
|               g_free (dir_name);
 | |
|             }
 | |
|           else
 | |
|             {
 | |
| 	      if (fs->selection_entry)
 | |
| 		gtk_entry_set_text (GTK_ENTRY (fs->selection_entry),
 | |
| 				    cmpl_updated_text (cmpl_state));
 | |
|             }
 | |
|         }
 | |
|       else
 | |
|         {
 | |
|           selection_index = cmpl_last_valid_char (cmpl_state) -
 | |
|                             (strlen (rel_path) - strlen (rem_path));
 | |
| 	  if (fs->selection_entry)
 | |
| 	    gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), rem_path);
 | |
|         }
 | |
|     }
 | |
|   else
 | |
|     {
 | |
|       if (fs->selection_entry)
 | |
| 	gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), "");
 | |
|     }
 | |
| 
 | |
|   if (!did_recurse)
 | |
|     {
 | |
|       GList *file_label_list = NULL;
 | |
|       GList *dir_label_list = NULL;
 | |
| 
 | |
|       /* This reverses the lists. */
 | |
|       while (file_list)
 | |
|         {
 | |
|           label = gtk_list_item_new_with_label (file_list->data);
 | |
|           gtk_object_set_user_data (GTK_OBJECT (label), file_list->data);
 | |
|           gtk_widget_show (label);
 | |
| 
 | |
|           file_label_list = g_list_prepend (file_label_list, label);
 | |
|           file_list = file_list->next;
 | |
|         }
 | |
| 
 | |
|       while (dir_list)
 | |
|         {
 | |
|           label = gtk_list_item_new_with_label (dir_list->data);
 | |
|           gtk_object_set_user_data (GTK_OBJECT (label), dir_list->data);
 | |
|           gtk_widget_show (label);
 | |
| 
 | |
|           dir_label_list = g_list_prepend (dir_label_list, label);
 | |
|           dir_list = dir_list->next;
 | |
|         }
 | |
| 
 | |
|       gtk_container_disable_resize (GTK_CONTAINER (fs));
 | |
| 
 | |
|       if (fs->selection_entry)
 | |
| 	gtk_entry_set_position (GTK_ENTRY (fs->selection_entry), selection_index);
 | |
| 
 | |
|       if (fs->selection_entry)
 | |
| 	{
 | |
| 	  sel_text = g_new (char, strlen (cmpl_reference_position (cmpl_state)) +
 | |
| 			    sizeof ("Selection: "));
 | |
| 	  strcpy (sel_text, "Selection: ");
 | |
| 	  strcat (sel_text, cmpl_reference_position (cmpl_state));
 | |
| 
 | |
| 	  gtk_label_set (GTK_LABEL (fs->selection_text), sel_text);
 | |
| 	  g_free (sel_text);
 | |
| 	}
 | |
| 
 | |
|       if (fs->dir_list)
 | |
| 	{
 | |
| 	  gtk_container_foreach (GTK_CONTAINER (fs->dir_list),
 | |
| 				 gtk_file_selection_free_filename, NULL);
 | |
| 	  gtk_list_clear_items (GTK_LIST (fs->dir_list), 0, -1);
 | |
| 
 | |
| 	  if (dir_label_list)
 | |
| 	    gtk_list_append_items (GTK_LIST (fs->dir_list), dir_label_list);
 | |
| 	}
 | |
|       if (fs->file_list)
 | |
| 	{
 | |
| 	  gtk_container_foreach (GTK_CONTAINER (fs->file_list),
 | |
| 				 gtk_file_selection_free_filename, NULL);
 | |
| 	  gtk_list_clear_items (GTK_LIST (fs->file_list), 0, -1);
 | |
| 
 | |
| 	  if (file_label_list)
 | |
| 	    gtk_list_append_items (GTK_LIST (fs->file_list), file_label_list);
 | |
| 	}
 | |
| 
 | |
|       gtk_container_enable_resize (GTK_CONTAINER (fs));
 | |
|     }
 | |
|   else
 | |
|     {
 | |
|       GList *dir_list0 = dir_list;
 | |
|       GList *file_list0 = file_list;
 | |
| 
 | |
|       while (dir_list)
 | |
|         {
 | |
|           GList *tmp = dir_list;
 | |
|           dir_list = dir_list->next;
 | |
| 
 | |
|           if (tmp)
 | |
|             g_free (tmp->data);
 | |
|         }
 | |
| 
 | |
|       while (file_list)
 | |
|         {
 | |
|           GList *tmp = file_list;
 | |
|           file_list = file_list->next;
 | |
| 
 | |
|           if (tmp)
 | |
|             g_free (tmp->data);
 | |
|         }
 | |
| 
 | |
|       g_list_free (dir_list0);
 | |
|       g_list_free (file_list0);
 | |
|     }
 | |
| 
 | |
|   gtk_signal_handler_unblock (GTK_OBJECT (fs->dir_list), dir_changed_key);
 | |
| }
 | |
| 
 | |
| static void
 | |
| gtk_file_selection_abort (GtkFileSelection *fs)
 | |
| {
 | |
|   gchar err_buf[256];
 | |
| 
 | |
|   sprintf (err_buf, "Directory unreadable: %s", cmpl_strerror (cmpl_errno));
 | |
| 
 | |
|   /*  BEEP gdk_beep();  */
 | |
| 
 | |
|   if (fs->selection_entry)
 | |
|     gtk_label_set (GTK_LABEL (fs->selection_text), err_buf);
 | |
| }
 | |
| 
 | |
| static void
 | |
| gtk_file_selection_free_filename (GtkWidget *widget,
 | |
| 				  gpointer   client_data)
 | |
| {
 | |
|   g_return_if_fail (widget != NULL);
 | |
| 
 | |
|   g_free (gtk_object_get_user_data (GTK_OBJECT (widget)));
 | |
|   gtk_object_set_user_data (GTK_OBJECT (widget), NULL);
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /**********************************************************************/
 | |
| /*			  External Interface                          */
 | |
| /**********************************************************************/
 | |
| 
 | |
| /* The four completion state selectors
 | |
|  */
 | |
| static gchar*
 | |
| cmpl_updated_text (CompletionState* cmpl_state)
 | |
| {
 | |
|   return cmpl_state->updated_text;
 | |
| }
 | |
| 
 | |
| static gint
 | |
| cmpl_updated_dir (CompletionState* cmpl_state)
 | |
| {
 | |
|   return cmpl_state->re_complete;
 | |
| }
 | |
| 
 | |
| static gchar*
 | |
| cmpl_reference_position (CompletionState* cmpl_state)
 | |
| {
 | |
|   return cmpl_state->reference_dir->fullname;
 | |
| }
 | |
| 
 | |
| static gint
 | |
| cmpl_last_valid_char (CompletionState* cmpl_state)
 | |
| {
 | |
|   return cmpl_state->last_valid_char;
 | |
| }
 | |
| 
 | |
| static gchar*
 | |
| cmpl_completion_fullname (gchar* text, CompletionState* cmpl_state)
 | |
| {
 | |
|   if (text[0] == '/')
 | |
|     {
 | |
|       strcpy (cmpl_state->updated_text, text);
 | |
|     }
 | |
|   else if (text[0] == '~')
 | |
|     {
 | |
|       CompletionDir* dir;
 | |
|       char* slash;
 | |
| 
 | |
|       dir = open_user_dir (text, cmpl_state);
 | |
| 
 | |
|       if (!dir)
 | |
| 	{
 | |
| 	  /* spencer says just return ~something, so
 | |
| 	   * for now just do it. */
 | |
| 	  strcpy (cmpl_state->updated_text, text);
 | |
| 	}
 | |
|       else
 | |
| 	{
 | |
| 
 | |
| 	  strcpy (cmpl_state->updated_text, dir->fullname);
 | |
| 
 | |
| 	  slash = strchr (text, '/');
 | |
| 
 | |
| 	  if (slash)
 | |
| 	    strcat (cmpl_state->updated_text, slash);
 | |
| 	}
 | |
|     }
 | |
|   else
 | |
|     {
 | |
|       strcpy (cmpl_state->updated_text, cmpl_state->reference_dir->fullname);
 | |
|       strcat (cmpl_state->updated_text, "/");
 | |
|       strcat (cmpl_state->updated_text, text);
 | |
|     }
 | |
| 
 | |
|   return cmpl_state->updated_text;
 | |
| }
 | |
| 
 | |
| /* The three completion selectors
 | |
|  */
 | |
| static gchar*
 | |
| cmpl_this_completion (PossibleCompletion* pc)
 | |
| {
 | |
|   return pc->text;
 | |
| }
 | |
| 
 | |
| static gint
 | |
| cmpl_is_directory (PossibleCompletion* pc)
 | |
| {
 | |
|   return pc->is_directory;
 | |
| }
 | |
| 
 | |
| static gint
 | |
| cmpl_is_a_completion (PossibleCompletion* pc)
 | |
| {
 | |
|   return pc->is_a_completion;
 | |
| }
 | |
| 
 | |
| /**********************************************************************/
 | |
| /*	                 Construction, deletion                       */
 | |
| /**********************************************************************/
 | |
| 
 | |
| static CompletionState*
 | |
| cmpl_init_state (void)
 | |
| {
 | |
|   gchar getcwd_buf[2*MAXPATHLEN];
 | |
|   CompletionState *new_state;
 | |
| 
 | |
|   new_state = g_new (CompletionState, 1);
 | |
| 
 | |
|   if (!getcwd (getcwd_buf, MAXPATHLEN))
 | |
|     {
 | |
|       cmpl_errno = errno;
 | |
|       return NULL;
 | |
|     }
 | |
| 
 | |
|   new_state->reference_dir = NULL;
 | |
|   new_state->completion_dir = NULL;
 | |
|   new_state->active_completion_dir = NULL;
 | |
| 
 | |
|   if ((new_state->user_home_dir = getenv("HOME")) != NULL)
 | |
|     {
 | |
|       /* if this fails, get_pwdb will fill it in. */
 | |
|       new_state->user_home_dir = g_strdup(new_state->user_home_dir);
 | |
|     }
 | |
| 
 | |
|   new_state->directory_storage = NULL;
 | |
|   new_state->directory_sent_storage = NULL;
 | |
|   new_state->last_valid_char = 0;
 | |
|   new_state->updated_text = g_new (gchar, MAXPATHLEN);
 | |
|   new_state->updated_text_alloc = MAXPATHLEN;
 | |
|   new_state->the_completion.text = g_new (gchar, MAXPATHLEN);
 | |
|   new_state->the_completion.text_alloc = MAXPATHLEN;
 | |
|   new_state->user_dir_name_buffer = NULL;
 | |
|   new_state->user_directories = NULL;
 | |
| 
 | |
|   new_state->reference_dir =  open_dir (getcwd_buf, new_state);
 | |
| 
 | |
|   if (!new_state->reference_dir)
 | |
|     return NULL;
 | |
| 
 | |
|   return new_state;
 | |
| }
 | |
| 
 | |
| static void
 | |
| cmpl_free_dir_list(GList* dp0)
 | |
| {
 | |
|   GList *dp = dp0;
 | |
| 
 | |
|   while (dp) {
 | |
|     free_dir (dp->data);
 | |
|     dp = dp->next;
 | |
|   }
 | |
| 
 | |
|   g_list_free(dp0);
 | |
| }
 | |
| 
 | |
| static void
 | |
| cmpl_free_dir_sent_list(GList* dp0)
 | |
| {
 | |
|   GList *dp = dp0;
 | |
| 
 | |
|   while (dp) {
 | |
|     free_dir_sent (dp->data);
 | |
|     dp = dp->next;
 | |
|   }
 | |
| 
 | |
|   g_list_free(dp0);
 | |
| }
 | |
| 
 | |
| static void
 | |
| cmpl_free_state (CompletionState* cmpl_state)
 | |
| {
 | |
|   cmpl_free_dir_list(cmpl_state->directory_storage);
 | |
|   cmpl_free_dir_sent_list(cmpl_state->directory_sent_storage);
 | |
| 
 | |
|   if (cmpl_state->user_dir_name_buffer)
 | |
|     g_free (cmpl_state->user_dir_name_buffer);
 | |
|   if (cmpl_state->user_directories)
 | |
|     g_free (cmpl_state->user_directories);
 | |
|   if (cmpl_state->the_completion.text)
 | |
|     g_free (cmpl_state->the_completion.text);
 | |
|   if (cmpl_state->updated_text)
 | |
|     g_free (cmpl_state->updated_text);
 | |
| 
 | |
|   g_free (cmpl_state);
 | |
| }
 | |
| 
 | |
| static void
 | |
| free_dir(CompletionDir* dir)
 | |
| {
 | |
|   g_free(dir->fullname);
 | |
|   g_free(dir);
 | |
| }
 | |
| 
 | |
| static void
 | |
| free_dir_sent(CompletionDirSent* sent)
 | |
| {
 | |
|   g_free(sent->name_buffer);
 | |
|   g_free(sent->entries);
 | |
|   g_free(sent);
 | |
| }
 | |
| 
 | |
| static void
 | |
| prune_memory_usage(CompletionState *cmpl_state)
 | |
| {
 | |
|   GList* cdsl = cmpl_state->directory_sent_storage;
 | |
|   GList* cdl = cmpl_state->directory_storage;
 | |
|   GList* cdl0 = cdl;
 | |
|   gint len = 0;
 | |
| 
 | |
|   for(; cdsl && len < CMPL_DIRECTORY_CACHE_SIZE; len += 1)
 | |
|     cdsl = cdsl->next;
 | |
| 
 | |
|   if (cdsl) {
 | |
|     cmpl_free_dir_sent_list(cdsl->next);
 | |
|     cdsl->next = NULL;
 | |
|   }
 | |
| 
 | |
|   cmpl_state->directory_storage = NULL;
 | |
|   while (cdl) {
 | |
|     if (cdl->data == cmpl_state->reference_dir)
 | |
|       cmpl_state->directory_storage = g_list_prepend(NULL, cdl->data);
 | |
|     else
 | |
|       free_dir (cdl->data);
 | |
|     cdl = cdl->next;
 | |
|   }
 | |
| 
 | |
|   g_list_free(cdl0);
 | |
| }
 | |
| 
 | |
| /**********************************************************************/
 | |
| /*                        The main entrances.                         */
 | |
| /**********************************************************************/
 | |
| 
 | |
| static PossibleCompletion*
 | |
| cmpl_completion_matches (gchar* text_to_complete,
 | |
| 			 gchar** remaining_text,
 | |
| 			 CompletionState* cmpl_state)
 | |
| {
 | |
|   gchar* first_slash;
 | |
|   PossibleCompletion *poss;
 | |
| 
 | |
|   prune_memory_usage(cmpl_state);
 | |
| 
 | |
|   g_assert(text_to_complete);
 | |
| 
 | |
|   cmpl_state->user_completion_index = -1;
 | |
|   cmpl_state->last_completion_text = text_to_complete;
 | |
|   cmpl_state->the_completion.text[0] = 0;
 | |
|   cmpl_state->last_valid_char = 0;
 | |
|   cmpl_state->updated_text_len = -1;
 | |
|   cmpl_state->updated_text[0] = 0;
 | |
|   cmpl_state->re_complete = FALSE;
 | |
| 
 | |
|   first_slash = strchr(text_to_complete, '/');
 | |
| 
 | |
|   if(text_to_complete[0] == '~' && !first_slash)
 | |
|     {
 | |
|       /* Text starts with ~ and there is no slash, show all the
 | |
|        * home directory completions.
 | |
|        */
 | |
|       poss = attempt_homedir_completion(text_to_complete, cmpl_state);
 | |
| 
 | |
|       update_cmpl(poss, cmpl_state);
 | |
| 
 | |
|       return poss;
 | |
|     }
 | |
| 
 | |
|   cmpl_state->reference_dir =
 | |
|     open_ref_dir(text_to_complete, remaining_text, cmpl_state);
 | |
| 
 | |
|   if(!cmpl_state->reference_dir)
 | |
|     return NULL;
 | |
| 
 | |
|   cmpl_state->completion_dir =
 | |
|     find_completion_dir(*remaining_text, remaining_text, cmpl_state);
 | |
| 
 | |
|   cmpl_state->last_valid_char = *remaining_text - text_to_complete;
 | |
| 
 | |
|   if(!cmpl_state->completion_dir)
 | |
|     return NULL;
 | |
| 
 | |
|   cmpl_state->completion_dir->cmpl_index = -1;
 | |
|   cmpl_state->completion_dir->cmpl_parent = NULL;
 | |
|   cmpl_state->completion_dir->cmpl_text = *remaining_text;
 | |
| 
 | |
|   cmpl_state->active_completion_dir = cmpl_state->completion_dir;
 | |
| 
 | |
|   cmpl_state->reference_dir = cmpl_state->completion_dir;
 | |
| 
 | |
|   poss = attempt_file_completion(cmpl_state);
 | |
| 
 | |
|   update_cmpl(poss, cmpl_state);
 | |
| 
 | |
|   return poss;
 | |
| }
 | |
| 
 | |
| static PossibleCompletion*
 | |
| cmpl_next_completion (CompletionState* cmpl_state)
 | |
| {
 | |
|   PossibleCompletion* poss = NULL;
 | |
| 
 | |
|   cmpl_state->the_completion.text[0] = 0;
 | |
| 
 | |
|   if(cmpl_state->user_completion_index >= 0)
 | |
|     poss = attempt_homedir_completion(cmpl_state->last_completion_text, cmpl_state);
 | |
|   else
 | |
|     poss = attempt_file_completion(cmpl_state);
 | |
| 
 | |
|   update_cmpl(poss, cmpl_state);
 | |
| 
 | |
|   return poss;
 | |
| }
 | |
| 
 | |
| /**********************************************************************/
 | |
| /*			 Directory Operations                         */
 | |
| /**********************************************************************/
 | |
| 
 | |
| /* Open the directory where completion will begin from, if possible. */
 | |
| static CompletionDir*
 | |
| open_ref_dir(gchar* text_to_complete,
 | |
| 	     gchar** remaining_text,
 | |
| 	     CompletionState* cmpl_state)
 | |
| {
 | |
|   gchar* first_slash;
 | |
|   CompletionDir *new_dir;
 | |
| 
 | |
|   first_slash = strchr(text_to_complete, '/');
 | |
| 
 | |
|   if (text_to_complete[0] == '/' || !cmpl_state->reference_dir)
 | |
|     {
 | |
|       new_dir = open_dir("/", cmpl_state);
 | |
| 
 | |
|       if(new_dir)
 | |
| 	*remaining_text = text_to_complete + 1;
 | |
|     }
 | |
|   else if (text_to_complete[0] == '~')
 | |
|     {
 | |
|       new_dir = open_user_dir(text_to_complete, cmpl_state);
 | |
| 
 | |
|       if(new_dir)
 | |
| 	{
 | |
| 	  if(first_slash)
 | |
| 	    *remaining_text = first_slash + 1;
 | |
| 	  else
 | |
| 	    *remaining_text = text_to_complete + strlen(text_to_complete);
 | |
| 	}
 | |
|       else
 | |
| 	{
 | |
| 	  return NULL;
 | |
| 	}
 | |
|     }
 | |
|   else
 | |
|     {
 | |
|       *remaining_text = text_to_complete;
 | |
| 
 | |
|       new_dir = open_dir(cmpl_state->reference_dir->fullname, cmpl_state);
 | |
|     }
 | |
| 
 | |
|   if(new_dir)
 | |
|     {
 | |
|       new_dir->cmpl_index = -1;
 | |
|       new_dir->cmpl_parent = NULL;
 | |
|     }
 | |
| 
 | |
|   return new_dir;
 | |
| }
 | |
| 
 | |
| /* open a directory by user name */
 | |
| static CompletionDir*
 | |
| open_user_dir(gchar* text_to_complete,
 | |
| 	      CompletionState *cmpl_state)
 | |
| {
 | |
|   gchar *first_slash;
 | |
|   gint cmp_len;
 | |
| 
 | |
|   g_assert(text_to_complete && text_to_complete[0] == '~');
 | |
| 
 | |
|   first_slash = strchr(text_to_complete, '/');
 | |
| 
 | |
|   if (first_slash)
 | |
|     cmp_len = first_slash - text_to_complete - 1;
 | |
|   else
 | |
|     cmp_len = strlen(text_to_complete + 1);
 | |
| 
 | |
|   if(!cmp_len)
 | |
|     {
 | |
|       /* ~/ */
 | |
|       if (!cmpl_state->user_home_dir &&
 | |
| 	  !get_pwdb(cmpl_state))
 | |
| 	return NULL;
 | |
|       return open_dir(cmpl_state->user_home_dir, cmpl_state);
 | |
|     }
 | |
|   else
 | |
|     {
 | |
|       /* ~user/ */
 | |
|       char* copy = g_new(char, cmp_len + 1);
 | |
|       struct passwd *pwd;
 | |
|       strncpy(copy, text_to_complete + 1, cmp_len);
 | |
|       copy[cmp_len] = 0;
 | |
|       pwd = getpwnam(copy);
 | |
|       g_free(copy);
 | |
|       if (!pwd)
 | |
| 	{
 | |
| 	  cmpl_errno = errno;
 | |
| 	  return NULL;
 | |
| 	}
 | |
| 
 | |
|       return open_dir(pwd->pw_dir, cmpl_state);
 | |
|     }
 | |
| }
 | |
| 
 | |
| /* open a directory relative the the current relative directory */
 | |
| static CompletionDir*
 | |
| open_relative_dir(gchar* dir_name,
 | |
| 		  CompletionDir* dir,
 | |
| 		  CompletionState *cmpl_state)
 | |
| {
 | |
|   gchar path_buf[2*MAXPATHLEN];
 | |
| 
 | |
|   if(dir->fullname_len + strlen(dir_name) + 2 >= MAXPATHLEN)
 | |
|     {
 | |
|       cmpl_errno = CMPL_ERRNO_TOO_LONG;
 | |
|       return NULL;
 | |
|     }
 | |
| 
 | |
|   strcpy(path_buf, dir->fullname);
 | |
| 
 | |
|   if(dir->fullname_len > 1)
 | |
|     {
 | |
|       path_buf[dir->fullname_len] = '/';
 | |
|       strcpy(path_buf + dir->fullname_len + 1, dir_name);
 | |
|     }
 | |
|   else
 | |
|     {
 | |
|       strcpy(path_buf + dir->fullname_len, dir_name);
 | |
|     }
 | |
| 
 | |
|   return open_dir(path_buf, cmpl_state);
 | |
| }
 | |
| 
 | |
| /* after the cache lookup fails, really open a new directory */
 | |
| static CompletionDirSent*
 | |
| open_new_dir(gchar* dir_name, struct stat* sbuf)
 | |
| {
 | |
|   CompletionDirSent* sent;
 | |
|   DIR* directory;
 | |
|   gchar *buffer_ptr;
 | |
|   struct dirent *dirent_ptr;
 | |
|   gint buffer_size = 0;
 | |
|   gint entry_count = 0;
 | |
|   gint i;
 | |
|   struct stat ent_sbuf;
 | |
|   char path_buf[MAXPATHLEN*2];
 | |
|   gint path_buf_len;
 | |
| 
 | |
|   sent = g_new(CompletionDirSent, 1);
 | |
|   sent->mtime = sbuf->st_mtime;
 | |
|   sent->inode = sbuf->st_ino;
 | |
| 
 | |
|   path_buf_len = strlen(dir_name);
 | |
| 
 | |
|   if (path_buf_len > MAXPATHLEN)
 | |
|     {
 | |
|       cmpl_errno = CMPL_ERRNO_TOO_LONG;
 | |
|       return NULL;
 | |
|     }
 | |
| 
 | |
|   strcpy(path_buf, dir_name);
 | |
| 
 | |
|   directory = opendir(dir_name);
 | |
| 
 | |
|   if(!directory)
 | |
|     {
 | |
|       cmpl_errno = errno;
 | |
|       return NULL;
 | |
|     }
 | |
| 
 | |
|   while((dirent_ptr = readdir(directory)) != NULL)
 | |
|     {
 | |
|       int entry_len = strlen(dirent_ptr->d_name);
 | |
|       buffer_size += entry_len + 1;
 | |
|       entry_count += 1;
 | |
| 
 | |
|       if(path_buf_len + entry_len + 2 >= MAXPATHLEN)
 | |
| 	{
 | |
| 	  cmpl_errno = CMPL_ERRNO_TOO_LONG;
 | |
|  	  closedir(directory);
 | |
| 	  return NULL;
 | |
| 	}
 | |
|     }
 | |
| 
 | |
|   sent->name_buffer = g_new(gchar, buffer_size);
 | |
|   sent->entries = g_new(CompletionDirEntry, entry_count);
 | |
|   sent->entry_count = entry_count;
 | |
| 
 | |
|   buffer_ptr = sent->name_buffer;
 | |
| 
 | |
|   rewinddir(directory);
 | |
| 
 | |
|   for(i = 0; i < entry_count; i += 1)
 | |
|     {
 | |
|       dirent_ptr = readdir(directory);
 | |
| 
 | |
|       if(!dirent_ptr)
 | |
| 	{
 | |
| 	  cmpl_errno = errno;
 | |
| 	  closedir(directory);
 | |
| 	  return NULL;
 | |
| 	}
 | |
| 
 | |
|       strcpy(buffer_ptr, dirent_ptr->d_name);
 | |
|       sent->entries[i].entry_name = buffer_ptr;
 | |
|       buffer_ptr += strlen(dirent_ptr->d_name);
 | |
|       *buffer_ptr = 0;
 | |
|       buffer_ptr += 1;
 | |
| 
 | |
|       path_buf[path_buf_len] = '/';
 | |
|       strcpy(path_buf + path_buf_len + 1, dirent_ptr->d_name);
 | |
| 
 | |
|       if(stat(path_buf, &ent_sbuf) >= 0 && S_ISDIR(ent_sbuf.st_mode))
 | |
| 	sent->entries[i].is_dir = 1;
 | |
|       else
 | |
| 	/* stat may fail, and we don't mind, since it could be a
 | |
| 	 * dangling symlink. */
 | |
| 	sent->entries[i].is_dir = 0;
 | |
|     }
 | |
| 
 | |
|   qsort(sent->entries, sent->entry_count, sizeof(CompletionDirEntry), compare_cmpl_dir);
 | |
| 
 | |
|   closedir(directory);
 | |
| 
 | |
|   return sent;
 | |
| }
 | |
| 
 | |
| /* open a directory by absolute pathname */
 | |
| static CompletionDir*
 | |
| open_dir(gchar* dir_name, CompletionState* cmpl_state)
 | |
| {
 | |
|   struct stat sbuf;
 | |
|   CompletionDirSent *sent;
 | |
|   GList* cdsl;
 | |
| 
 | |
|   if(stat(dir_name, &sbuf) < 0)
 | |
|     {
 | |
|       cmpl_errno = errno;
 | |
|       return NULL;
 | |
|     }
 | |
| 
 | |
|   cdsl = cmpl_state->directory_sent_storage;
 | |
| 
 | |
|   while (cdsl)
 | |
|     {
 | |
|       sent = cdsl->data;
 | |
| 
 | |
|       if(sent->inode == sbuf.st_ino &&
 | |
| 	 sent->mtime == sbuf.st_mtime)
 | |
| 	return attach_dir(sent, dir_name, cmpl_state);
 | |
| 
 | |
|       cdsl = cdsl->next;
 | |
|     }
 | |
| 
 | |
|   sent = open_new_dir(dir_name, &sbuf);
 | |
| 
 | |
|   if (sent) {
 | |
|     cmpl_state->directory_sent_storage =
 | |
|       g_list_prepend(cmpl_state->directory_sent_storage, sent);
 | |
| 
 | |
|     return attach_dir(sent, dir_name, cmpl_state);
 | |
|   }
 | |
| 
 | |
|   return NULL;
 | |
| }
 | |
| 
 | |
| static CompletionDir*
 | |
| attach_dir(CompletionDirSent* sent, gchar* dir_name, CompletionState *cmpl_state)
 | |
| {
 | |
|   CompletionDir* new_dir;
 | |
| 
 | |
|   new_dir = g_new(CompletionDir, 1);
 | |
| 
 | |
|   cmpl_state->directory_storage =
 | |
|     g_list_prepend(cmpl_state->directory_storage, new_dir);
 | |
| 
 | |
|   new_dir->sent = sent;
 | |
|   new_dir->fullname = g_strdup(dir_name);
 | |
|   new_dir->fullname_len = strlen(dir_name);
 | |
| 
 | |
|   return new_dir;
 | |
| }
 | |
| 
 | |
| static gint
 | |
| correct_dir_fullname(CompletionDir* cmpl_dir)
 | |
| {
 | |
|   gint length = strlen(cmpl_dir->fullname);
 | |
|   struct stat sbuf;
 | |
| 
 | |
|   if (strcmp(cmpl_dir->fullname + length - 2, "/.") == 0)
 | |
|     cmpl_dir->fullname[length - 2] = 0;
 | |
|   else if (strcmp(cmpl_dir->fullname + length - 3, "/./") == 0)
 | |
|     cmpl_dir->fullname[length - 3] = 0;
 | |
|   else if (strcmp(cmpl_dir->fullname + length - 3, "/..") == 0)
 | |
|     {
 | |
|       if(length == 3)
 | |
| 	{
 | |
| 	  strcpy(cmpl_dir->fullname, "/");
 | |
| 	  cmpl_dir->fullname_len = 1;
 | |
| 	  return TRUE;
 | |
| 	}
 | |
| 
 | |
|       if(stat(cmpl_dir->fullname, &sbuf) < 0)
 | |
| 	{
 | |
| 	  cmpl_errno = errno;
 | |
| 	  return FALSE;
 | |
| 	}
 | |
| 
 | |
|       cmpl_dir->fullname[length - 2] = 0;
 | |
| 
 | |
|       if(!correct_parent(cmpl_dir, &sbuf))
 | |
| 	return FALSE;
 | |
|     }
 | |
|   else if (strcmp(cmpl_dir->fullname + length - 4, "/../") == 0)
 | |
|     {
 | |
|       if(length == 4)
 | |
| 	{
 | |
| 	  strcpy(cmpl_dir->fullname, "/");
 | |
| 	  cmpl_dir->fullname_len = 1;
 | |
| 	  return TRUE;
 | |
| 	}
 | |
| 
 | |
|       if(stat(cmpl_dir->fullname, &sbuf) < 0)
 | |
| 	{
 | |
| 	  cmpl_errno = errno;
 | |
| 	  return FALSE;
 | |
| 	}
 | |
| 
 | |
|       cmpl_dir->fullname[length - 3] = 0;
 | |
| 
 | |
|       if(!correct_parent(cmpl_dir, &sbuf))
 | |
| 	return FALSE;
 | |
|     }
 | |
| 
 | |
|   cmpl_dir->fullname_len = strlen(cmpl_dir->fullname);
 | |
| 
 | |
|   return TRUE;
 | |
| }
 | |
| 
 | |
| static gint
 | |
| correct_parent(CompletionDir* cmpl_dir, struct stat *sbuf)
 | |
| {
 | |
|   struct stat parbuf;
 | |
|   gchar *last_slash;
 | |
|   gchar *new_name;
 | |
|   gchar c = 0;
 | |
| 
 | |
|   last_slash = strrchr(cmpl_dir->fullname, '/');
 | |
| 
 | |
|   g_assert(last_slash);
 | |
| 
 | |
|   if(last_slash != cmpl_dir->fullname)
 | |
|     { /* last_slash[0] = 0; */ }
 | |
|   else
 | |
|     {
 | |
|       c = last_slash[1];
 | |
|       last_slash[1] = 0;
 | |
|     }
 | |
| 
 | |
|   if (stat(cmpl_dir->fullname, &parbuf) < 0)
 | |
|     {
 | |
|       cmpl_errno = errno;
 | |
|       return FALSE;
 | |
|     }
 | |
| 
 | |
|   if (parbuf.st_ino == sbuf->st_ino && parbuf.st_dev == sbuf->st_dev)
 | |
|     /* it wasn't a link */
 | |
|     return TRUE;
 | |
| 
 | |
|   if(c)
 | |
|     last_slash[1] = c;
 | |
|   /* else
 | |
|     last_slash[0] = '/'; */
 | |
| 
 | |
|   /* it was a link, have to figure it out the hard way */
 | |
| 
 | |
|   new_name = find_parent_dir_fullname(cmpl_dir->fullname);
 | |
| 
 | |
|   if (!new_name)
 | |
|     return FALSE;
 | |
| 
 | |
|   g_free(cmpl_dir->fullname);
 | |
| 
 | |
|   cmpl_dir->fullname = new_name;
 | |
| 
 | |
|   return TRUE;
 | |
| }
 | |
| 
 | |
| static gchar*
 | |
| find_parent_dir_fullname(gchar* dirname)
 | |
| {
 | |
|   gchar buffer[MAXPATHLEN];
 | |
|   gchar buffer2[MAXPATHLEN];
 | |
| 
 | |
|   if(!getcwd(buffer, MAXPATHLEN))
 | |
|     {
 | |
|       cmpl_errno = errno;
 | |
|       return NULL;
 | |
|     }
 | |
| 
 | |
|   if(chdir(dirname) != 0 || chdir("..") != 0)
 | |
|     {
 | |
|       cmpl_errno = errno;
 | |
|       return NULL;
 | |
|     }
 | |
| 
 | |
|   if(!getcwd(buffer2, MAXPATHLEN))
 | |
|     {
 | |
|       chdir(buffer);
 | |
|       cmpl_errno = errno;
 | |
| 
 | |
|       return NULL;
 | |
|     }
 | |
| 
 | |
|   if(chdir(buffer) != 0)
 | |
|     {
 | |
|       cmpl_errno = errno;
 | |
|       return NULL;
 | |
|     }
 | |
| 
 | |
|   return g_strdup(buffer2);
 | |
| }
 | |
| 
 | |
| /**********************************************************************/
 | |
| /*                        Completion Operations                       */
 | |
| /**********************************************************************/
 | |
| 
 | |
| static PossibleCompletion*
 | |
| attempt_homedir_completion(gchar* text_to_complete,
 | |
| 			   CompletionState *cmpl_state)
 | |
| {
 | |
|   gint index, length;
 | |
| 
 | |
|   if (!cmpl_state->user_dir_name_buffer &&
 | |
|       !get_pwdb(cmpl_state))
 | |
|     return NULL;
 | |
|   length = strlen(text_to_complete) - 1;
 | |
| 
 | |
|   cmpl_state->user_completion_index += 1;
 | |
| 
 | |
|   while(cmpl_state->user_completion_index < cmpl_state->user_directories_len)
 | |
|     {
 | |
|       index = first_diff_index(text_to_complete + 1,
 | |
| 			       cmpl_state->user_directories
 | |
| 			       [cmpl_state->user_completion_index].login);
 | |
| 
 | |
|       switch(index)
 | |
| 	{
 | |
| 	case PATTERN_MATCH:
 | |
| 	  break;
 | |
| 	default:
 | |
| 	  if(cmpl_state->last_valid_char < (index + 1))
 | |
| 	    cmpl_state->last_valid_char = index + 1;
 | |
| 	  cmpl_state->user_completion_index += 1;
 | |
| 	  continue;
 | |
| 	}
 | |
| 
 | |
|       cmpl_state->the_completion.is_a_completion = 1;
 | |
|       cmpl_state->the_completion.is_directory = 1;
 | |
| 
 | |
|       append_completion_text("~", cmpl_state);
 | |
| 
 | |
|       append_completion_text(cmpl_state->
 | |
| 			      user_directories[cmpl_state->user_completion_index].login,
 | |
| 			     cmpl_state);
 | |
| 
 | |
|       return append_completion_text("/", cmpl_state);
 | |
|     }
 | |
| 
 | |
|   if(text_to_complete[1] ||
 | |
|      cmpl_state->user_completion_index > cmpl_state->user_directories_len)
 | |
|     {
 | |
|       cmpl_state->user_completion_index = -1;
 | |
|       return NULL;
 | |
|     }
 | |
|   else
 | |
|     {
 | |
|       cmpl_state->user_completion_index += 1;
 | |
|       cmpl_state->the_completion.is_a_completion = 1;
 | |
|       cmpl_state->the_completion.is_directory = 1;
 | |
| 
 | |
|       return append_completion_text("~/", cmpl_state);
 | |
|     }
 | |
| }
 | |
| 
 | |
| /* returns the index (>= 0) of the first differing character,
 | |
|  * PATTERN_MATCH if the completion matches */
 | |
| static gint
 | |
| first_diff_index(gchar* pat, gchar* text)
 | |
| {
 | |
|   gint diff = 0;
 | |
| 
 | |
|   while(*pat && *text && *text == *pat)
 | |
|     {
 | |
|       pat += 1;
 | |
|       text += 1;
 | |
|       diff += 1;
 | |
|     }
 | |
| 
 | |
|   if(*pat)
 | |
|     return diff;
 | |
| 
 | |
|   return PATTERN_MATCH;
 | |
| }
 | |
| 
 | |
| static PossibleCompletion*
 | |
| append_completion_text(gchar* text, CompletionState* cmpl_state)
 | |
| {
 | |
|   gint len, i = 1;
 | |
| 
 | |
|   if(!cmpl_state->the_completion.text)
 | |
|     return NULL;
 | |
| 
 | |
|   len = strlen(text) + strlen(cmpl_state->the_completion.text) + 1;
 | |
| 
 | |
|   if(cmpl_state->the_completion.text_alloc > len)
 | |
|     {
 | |
|       strcat(cmpl_state->the_completion.text, text);
 | |
|       return &cmpl_state->the_completion;
 | |
|     }
 | |
| 
 | |
|   while(i < len) { i <<= 1; }
 | |
| 
 | |
|   cmpl_state->the_completion.text_alloc = i;
 | |
| 
 | |
|   cmpl_state->the_completion.text = (gchar*)g_realloc(cmpl_state->the_completion.text, i);
 | |
| 
 | |
|   if(!cmpl_state->the_completion.text)
 | |
|     return NULL;
 | |
|   else
 | |
|     {
 | |
|       strcat(cmpl_state->the_completion.text, text);
 | |
|       return &cmpl_state->the_completion;
 | |
|     }
 | |
| }
 | |
| 
 | |
| static CompletionDir*
 | |
| find_completion_dir(gchar* text_to_complete,
 | |
| 		    gchar** remaining_text,
 | |
| 		    CompletionState* cmpl_state)
 | |
| {
 | |
|   gchar* first_slash = strchr(text_to_complete, '/');
 | |
|   CompletionDir* dir = cmpl_state->reference_dir;
 | |
|   *remaining_text = text_to_complete;
 | |
| 
 | |
|   while(first_slash)
 | |
|     {
 | |
|       gint len = first_slash - *remaining_text;
 | |
|       gint found = 0;
 | |
|       gint found_index = -1;
 | |
|       gint i;
 | |
|       gchar* pat_buf = g_new (gchar, len + 1);
 | |
| 
 | |
|       strncpy(pat_buf, *remaining_text, len);
 | |
|       pat_buf[len] = 0;
 | |
| 
 | |
|       for(i = 0; i < dir->sent->entry_count; i += 1)
 | |
| 	{
 | |
| 	  if(dir->sent->entries[i].is_dir &&
 | |
| 	     fnmatch(pat_buf, dir->sent->entries[i].entry_name,
 | |
| 		     FNMATCH_FLAGS)!= FNM_NOMATCH)
 | |
| 	    {
 | |
| 	      if(found)
 | |
| 		{
 | |
| 		  g_free (pat_buf);
 | |
| 		  return dir;
 | |
| 		}
 | |
| 	      else
 | |
| 		{
 | |
| 		  found = 1;
 | |
| 		  found_index = i;
 | |
| 		}
 | |
| 	    }
 | |
| 	}
 | |
| 
 | |
|       if(found)
 | |
| 	{
 | |
| 	  CompletionDir* next = open_relative_dir(dir->sent->entries[found_index].entry_name,
 | |
| 						  dir, cmpl_state);
 | |
| 
 | |
| 	  if(!next)
 | |
| 	    {
 | |
| 	      g_free (pat_buf);
 | |
| 	      return NULL;
 | |
| 	    }
 | |
| 
 | |
| 	  next->cmpl_parent = dir;
 | |
| 
 | |
| 	  dir = next;
 | |
| 
 | |
| 	  if(!correct_dir_fullname(dir))
 | |
| 	    {
 | |
| 	      g_free(pat_buf);
 | |
| 	      return NULL;
 | |
| 	    }
 | |
| 
 | |
| 	  *remaining_text = first_slash + 1;
 | |
| 	  first_slash = strchr(*remaining_text, '/');
 | |
| 	}
 | |
|       else
 | |
| 	{
 | |
| 	  g_free (pat_buf);
 | |
| 	  return NULL;
 | |
| 	}
 | |
| 
 | |
|       g_free (pat_buf);
 | |
|     }
 | |
| 
 | |
|   return dir;
 | |
| }
 | |
| 
 | |
| static void
 | |
| update_cmpl(PossibleCompletion* poss, CompletionState* cmpl_state)
 | |
| {
 | |
|   gint cmpl_len;
 | |
| 
 | |
|   if(!poss || !cmpl_is_a_completion(poss))
 | |
|     return;
 | |
| 
 | |
|   cmpl_len = strlen(cmpl_this_completion(poss));
 | |
| 
 | |
|   if(cmpl_state->updated_text_alloc < cmpl_len + 1)
 | |
|     {
 | |
|       cmpl_state->updated_text =
 | |
| 	(gchar*)g_realloc(cmpl_state->updated_text,
 | |
| 			  cmpl_state->updated_text_alloc);
 | |
|       cmpl_state->updated_text_alloc = 2*cmpl_len;
 | |
|     }
 | |
| 
 | |
|   if(cmpl_state->updated_text_len < 0)
 | |
|     {
 | |
|       strcpy(cmpl_state->updated_text, cmpl_this_completion(poss));
 | |
|       cmpl_state->updated_text_len = cmpl_len;
 | |
|       cmpl_state->re_complete = cmpl_is_directory(poss);
 | |
|     }
 | |
|   else if(cmpl_state->updated_text_len == 0)
 | |
|     {
 | |
|       cmpl_state->re_complete = FALSE;
 | |
|     }
 | |
|   else
 | |
|     {
 | |
|       gint first_diff =
 | |
| 	first_diff_index(cmpl_state->updated_text,
 | |
| 			 cmpl_this_completion(poss));
 | |
| 
 | |
|       cmpl_state->re_complete = FALSE;
 | |
| 
 | |
|       if(first_diff == PATTERN_MATCH)
 | |
| 	return;
 | |
| 
 | |
|       if(first_diff > cmpl_state->updated_text_len)
 | |
| 	strcpy(cmpl_state->updated_text, cmpl_this_completion(poss));
 | |
| 
 | |
|       cmpl_state->updated_text_len = first_diff;
 | |
|       cmpl_state->updated_text[first_diff] = 0;
 | |
|     }
 | |
| }
 | |
| 
 | |
| static PossibleCompletion*
 | |
| attempt_file_completion(CompletionState *cmpl_state)
 | |
| {
 | |
|   gchar *pat_buf, *first_slash;
 | |
|   CompletionDir *dir = cmpl_state->active_completion_dir;
 | |
| 
 | |
|   dir->cmpl_index += 1;
 | |
| 
 | |
|   if(dir->cmpl_index == dir->sent->entry_count)
 | |
|     {
 | |
|       if(dir->cmpl_parent == NULL)
 | |
| 	{
 | |
| 	  cmpl_state->active_completion_dir = NULL;
 | |
| 
 | |
| 	  return NULL;
 | |
| 	}
 | |
|       else
 | |
| 	{
 | |
| 	  cmpl_state->active_completion_dir = dir->cmpl_parent;
 | |
| 
 | |
| 	  return attempt_file_completion(cmpl_state);
 | |
| 	}
 | |
|     }
 | |
| 
 | |
|   g_assert(dir->cmpl_text);
 | |
| 
 | |
|   first_slash = strchr(dir->cmpl_text, '/');
 | |
| 
 | |
|   if(first_slash)
 | |
|     {
 | |
|       gint len = first_slash - dir->cmpl_text;
 | |
| 
 | |
|       pat_buf = g_new (gchar, len + 1);
 | |
|       strncpy(pat_buf, dir->cmpl_text, len);
 | |
|       pat_buf[len] = 0;
 | |
|     }
 | |
|   else
 | |
|     {
 | |
|       gint len = strlen(dir->cmpl_text);
 | |
| 
 | |
|       pat_buf = g_new (gchar, len + 2);
 | |
|       strcpy(pat_buf, dir->cmpl_text);
 | |
|       strcpy(pat_buf + len, "*");
 | |
|     }
 | |
| 
 | |
|   if(first_slash)
 | |
|     {
 | |
|       if(dir->sent->entries[dir->cmpl_index].is_dir)
 | |
| 	{
 | |
| 	  if(fnmatch(pat_buf, dir->sent->entries[dir->cmpl_index].entry_name,
 | |
| 		     FNMATCH_FLAGS) != FNM_NOMATCH)
 | |
| 	    {
 | |
| 	      CompletionDir* new_dir;
 | |
| 
 | |
| 	      new_dir = open_relative_dir(dir->sent->entries[dir->cmpl_index].entry_name,
 | |
| 					  dir, cmpl_state);
 | |
| 
 | |
| 	      if(!new_dir)
 | |
| 		{
 | |
| 		  g_free (pat_buf);
 | |
| 		  return NULL;
 | |
| 		}
 | |
| 
 | |
| 	      new_dir->cmpl_parent = dir;
 | |
| 
 | |
| 	      new_dir->cmpl_index = -1;
 | |
| 	      new_dir->cmpl_text = first_slash + 1;
 | |
| 
 | |
| 	      cmpl_state->active_completion_dir = new_dir;
 | |
| 
 | |
| 	      g_free (pat_buf);
 | |
| 	      return attempt_file_completion(cmpl_state);
 | |
| 	    }
 | |
| 	  else
 | |
| 	    {
 | |
| 	      g_free (pat_buf);
 | |
| 	      return attempt_file_completion(cmpl_state);
 | |
| 	    }
 | |
| 	}
 | |
|       else
 | |
| 	{
 | |
| 	  g_free (pat_buf);
 | |
| 	  return attempt_file_completion(cmpl_state);
 | |
| 	}
 | |
|     }
 | |
|   else
 | |
|     {
 | |
|       if(dir->cmpl_parent != NULL)
 | |
| 	{
 | |
| 	  append_completion_text(dir->fullname +
 | |
| 				 strlen(cmpl_state->completion_dir->fullname) + 1,
 | |
| 				 cmpl_state);
 | |
| 	  append_completion_text("/", cmpl_state);
 | |
| 	}
 | |
| 
 | |
|       append_completion_text(dir->sent->entries[dir->cmpl_index].entry_name, cmpl_state);
 | |
| 
 | |
|       cmpl_state->the_completion.is_a_completion =
 | |
| 	(fnmatch(pat_buf, dir->sent->entries[dir->cmpl_index].entry_name,
 | |
| 		 FNMATCH_FLAGS) != FNM_NOMATCH);
 | |
| 
 | |
|       cmpl_state->the_completion.is_directory = dir->sent->entries[dir->cmpl_index].is_dir;
 | |
|       if(dir->sent->entries[dir->cmpl_index].is_dir)
 | |
| 	append_completion_text("/", cmpl_state);
 | |
| 
 | |
|       g_free (pat_buf);
 | |
|       return &cmpl_state->the_completion;
 | |
|     }
 | |
| }
 | |
| 
 | |
| 
 | |
| static gint
 | |
| get_pwdb(CompletionState* cmpl_state)
 | |
| {
 | |
|   struct passwd *pwd_ptr;
 | |
|   gchar* buf_ptr, *home_dir = NULL;
 | |
|   gint len = 0, i, count = 0;
 | |
| 
 | |
|   if(cmpl_state->user_dir_name_buffer)
 | |
|     return TRUE;
 | |
|   setpwent ();
 | |
| 
 | |
|   while ((pwd_ptr = getpwent()) != NULL)
 | |
|     {
 | |
|       len += strlen(pwd_ptr->pw_name);
 | |
|       len += strlen(pwd_ptr->pw_dir);
 | |
|       len += 2;
 | |
|       count += 1;
 | |
|     }
 | |
| 
 | |
|   if (!cmpl_state->user_home_dir)
 | |
|     {
 | |
|       /* the loser doesn't have $HOME set */
 | |
|       setpwent ();
 | |
| 
 | |
|       pwd_ptr = getpwuid(getuid());
 | |
|       if(!pwd_ptr)
 | |
| 	{
 | |
| 	  cmpl_errno = errno;
 | |
| 	  goto error;
 | |
| 	}
 | |
|       home_dir = pwd_ptr->pw_dir;
 | |
| 
 | |
|       len += strlen(home_dir);
 | |
|       len += 1;
 | |
|     }
 | |
| 
 | |
|   setpwent ();
 | |
| 
 | |
|   cmpl_state->user_dir_name_buffer = g_new(gchar, len);
 | |
|   cmpl_state->user_directories = g_new(CompletionUserDir, count);
 | |
|   cmpl_state->user_directories_len = count;
 | |
| 
 | |
|   buf_ptr = cmpl_state->user_dir_name_buffer;
 | |
| 
 | |
|   if (!cmpl_state->user_home_dir)
 | |
|     {
 | |
|       strcpy(buf_ptr, home_dir);
 | |
|       cmpl_state->user_home_dir = buf_ptr;
 | |
|       buf_ptr += strlen(buf_ptr);
 | |
|       buf_ptr += 1;
 | |
|     }
 | |
| 
 | |
|   for(i = 0; i < count; i += 1)
 | |
|     {
 | |
|       pwd_ptr = getpwent();
 | |
|       if(!pwd_ptr)
 | |
| 	{
 | |
| 	  cmpl_errno = errno;
 | |
| 	  goto error;
 | |
| 	}
 | |
| 
 | |
|       strcpy(buf_ptr, pwd_ptr->pw_name);
 | |
|       cmpl_state->user_directories[i].login = buf_ptr;
 | |
|       buf_ptr += strlen(buf_ptr);
 | |
|       buf_ptr += 1;
 | |
|       strcpy(buf_ptr, pwd_ptr->pw_dir);
 | |
|       cmpl_state->user_directories[i].homedir = buf_ptr;
 | |
|       buf_ptr += strlen(buf_ptr);
 | |
|       buf_ptr += 1;
 | |
|     }
 | |
| 
 | |
|   qsort(cmpl_state->user_directories,
 | |
| 	cmpl_state->user_directories_len,
 | |
| 	sizeof(CompletionUserDir),
 | |
| 	compare_user_dir);
 | |
| 
 | |
|   endpwent();
 | |
| 
 | |
|   return TRUE;
 | |
| 
 | |
| error:
 | |
| 
 | |
|   if(cmpl_state->user_dir_name_buffer)
 | |
|     g_free(cmpl_state->user_dir_name_buffer);
 | |
|   if(cmpl_state->user_directories)
 | |
|     g_free(cmpl_state->user_directories);
 | |
| 
 | |
|   cmpl_state->user_dir_name_buffer = NULL;
 | |
|   cmpl_state->user_directories = NULL;
 | |
| 
 | |
|   return FALSE;
 | |
| }
 | |
| 
 | |
| static gint
 | |
| compare_user_dir(const void* a, const void* b)
 | |
| {
 | |
|   return strcmp((((CompletionUserDir*)a))->login,
 | |
| 		(((CompletionUserDir*)b))->login);
 | |
| }
 | |
| 
 | |
| static gint
 | |
| compare_cmpl_dir(const void* a, const void* b)
 | |
| {
 | |
|   return strcmp((((CompletionDirEntry*)a))->entry_name,
 | |
| 		(((CompletionDirEntry*)b))->entry_name);
 | |
| }
 | |
| 
 | |
| static gint
 | |
| cmpl_state_okay(CompletionState* cmpl_state)
 | |
| {
 | |
|   return  cmpl_state && cmpl_state->reference_dir;
 | |
| }
 | |
| 
 | |
| static gchar*
 | |
| cmpl_strerror(gint err)
 | |
| {
 | |
|   if(err == CMPL_ERRNO_TOO_LONG)
 | |
|     return "Name too long";
 | |
|   else
 | |
|     return g_strerror (err);
 | |
| }
 | 
