1From jwe@che.utexas.edu Wed Sep 21 17:23:40 1994
2Flags: 10
3Return-Path: jwe@che.utexas.edu
4Received: from po.CWRU.Edu (root@po.CWRU.Edu [129.22.4.2]) by odin.INS.CWRU.Edu with ESMTP (8.6.8.1+cwru/CWRU-2.1-ins)
5	id RAA04010; Wed, 21 Sep 1994 17:23:39 -0400 (from jwe@che.utexas.edu for <chet@odin.INS.CWRU.Edu>)
6Received: from life.ai.mit.edu (life.ai.mit.edu [128.52.32.80]) by po.CWRU.Edu with SMTP (8.6.8.1+cwru/CWRU-2.2)
7	id RAA02121; Wed, 21 Sep 1994 17:23:28 -0400 (from jwe@che.utexas.edu for <chet@po.cwru.edu>)
8Received: from schoch.che.utexas.edu by life.ai.mit.edu (4.1/AI-4.10) for chet@po.cwru.edu id AA09989; Wed, 21 Sep 94 17:23:17 EDT
9Received: from localhost (jwe@localhost) by schoch.che.utexas.edu (8.6.8.1/8.6) with SMTP id QAA05737; Wed, 21 Sep 1994 16:22:01 -0500
10Message-Id: <199409212122.QAA05737@schoch.che.utexas.edu>
11To: march@tudor.com
12Cc: bug-bash@prep.ai.mit.edu
13Subject: Re: Completion feature possible?
14In-Reply-To: Your message of 21 Sep 94 13:30:22 EDT
15Date: Wed, 21 Sep 94 16:22:00 EDT
16From: John Eaton <jwe@che.utexas.edu>
17
18Gregory F. March <march@tudor.com> wrote:
19
20: I was having a discussion about MH with one of my friends the other
21: day and I got to thinking that the +folder/subfolder scheme for naming
22: mail folders is a real pain because completion doesn't work on
23: them. Someone then mentioned that zsh (I think) has the ability to
24: specify how to complete (I guess where to look for the files) for
25: different prefixes. Bash right now knows about '@', '~', and '$' (any
26: others?). It would be really helpful if one could define something
27: like:
28: 
29: 	completion '+' "$HOME/Mail"
30: 
31: in a config file someplace. Would this be easy? Is there a list of
32: TODO item that someone might want to add this to?
33
34It would be nice to have a general completion feature like this.
35
36Until that happens, maybe you will find the following patch useful.
37It makes MH folder name completion work with bash.  The diffs are
38relative to version 1.14.2.
39
40I realize that changes to readline.c and and complete.c are not good
41since they add some MH-specific stuff to the readline code and not to
42bash, but when I first wrote this, I had no idea what else to do.
43
44Chet, would you consider adding this if it were cleaned up a bit?
45Made optional with cpp conditionals?
46
47This feature has been very useful to me for the last several years
48(since about 1.05 or 1.06, I think).
49
50Thanks,
51
52--
53John W. Eaton      | 4.3BSD is not perfect.  -- Leffler, et al. (1989).
54jwe@che.utexas.edu |
55
56
57-------------------------------cut here-------------------------------
58diff -rc bash-1.14.2/bashline.c bash-1.14.2.local/bashline.c
59*** bash-1.14.2/bashline.c	Wed Aug  3 09:32:45 1994
60--- bash-1.14.2.local/bashline.c	Wed Sep 21 15:39:04 1994
61***************
62*** 58,63 ****
63--- 58,64 ----
64  static char *hostname_completion_function ();
65  static char *command_word_completion_function ();
66  static char *command_subst_completion_function ();
67+ static char *mh_folder_completion_function ();
68  
69  static void snarf_hosts_from_file (), add_host_name ();
70  static void sort_hostname_list ();
71***************
72*** 90,95 ****
73--- 91,98 ----
74    bash_complete_username_internal (),
75    bash_complete_hostname (), bash_possible_hostname_completions (),
76    bash_complete_hostname_internal (),
77+   bash_complete_mh_folder (), bash_possible_mh_folder_completions (),
78+   bash_complete_mh_folder_internal (),
79    bash_complete_variable (), bash_possible_variable_completions (),
80    bash_complete_variable_internal (),
81    bash_complete_command (), bash_possible_command_completions (),
82***************
83*** 134,140 ****
84    rl_terminal_name = get_string_value ("TERM");
85    rl_instream = stdin;
86    rl_outstream = stderr;
87!   rl_special_prefixes = "$@";
88  
89    /* Allow conditional parsing of the ~/.inputrc file. */
90    rl_readline_name = "Bash";
91--- 137,143 ----
92    rl_terminal_name = get_string_value ("TERM");
93    rl_instream = stdin;
94    rl_outstream = stderr;
95!   rl_special_prefixes = "$@+";
96  
97    /* Allow conditional parsing of the ~/.inputrc file. */
98    rl_readline_name = "Bash";
99***************
100*** 193,198 ****
101--- 196,207 ----
102    rl_bind_key_in_map ('@', bash_possible_hostname_completions,
103  		      emacs_ctlx_keymap);
104  
105+   rl_add_defun ("complete-mh-folder", bash_complete_mh_folder, META('+'));
106+   rl_add_defun ("possible-mh-folder-completions",
107+ 		bash_possible_mh_folder_completions, -1);
108+   rl_bind_key_in_map ('+', bash_possible_mh_folder_completions,
109+ 		      emacs_ctlx_keymap);
110+ 
111    rl_add_defun ("complete-variable", bash_complete_variable, -1);
112    rl_bind_key_in_map ('$', bash_complete_variable, emacs_meta_keymap);
113    rl_add_defun ("possible-variable-completions",
114***************
115*** 656,661 ****
116--- 665,677 ----
117    if (!matches && *text == '@')
118      matches = completion_matches (text, hostname_completion_function);
119  
120+   /* Another one.  Why not?  If the word starts in '+', then look for
121+      matching mh folders for completion first. */
122+   if (!matches && *text == '+')
123+     {
124+       matches = completion_matches (text, mh_folder_completion_function);
125+     }
126+ 
127    /* And last, (but not least) if this word is in a command position, then
128       complete over possible command names, including aliases, functions,
129       and command names. */
130***************
131*** 1077,1082 ****
132--- 1093,1185 ----
133      return ((char *)NULL);
134  }
135  
136+ /* How about a completion function for mh folders? */
137+ static char *
138+ mh_folder_completion_function (text, state)
139+      int state;
140+      char *text;
141+ {
142+   extern int rl_filename_completion_desired;
143+ 
144+   extern char *get_mh_path ();
145+ 
146+   static char *mh_path = (char *)NULL;
147+   static int len;
148+   static int istate;
149+   static char *val;
150+   char *hint;
151+ 
152+   static char *mh_folder_hint = (char *)NULL;
153+ 
154+   /* If we don't have any state, make some. */
155+   if (!state)
156+     {
157+       val = (char *)NULL;
158+ 
159+       if (mh_path)
160+ 	free (mh_path);
161+ 
162+       mh_path = get_mh_path ();
163+       if (!mh_path && !(hint[1] == '/' || hint[1] == '.'))
164+ 	return ((char *)NULL);
165+ 
166+       len = strlen (mh_path);
167+     }
168+ 
169+   if (mh_folder_hint)
170+     free (mh_folder_hint);
171+ 
172+   hint = text;
173+   if (*hint == '+')
174+     hint++;
175+ 
176+   mh_folder_hint = (char *)xmalloc (2 + len + strlen (hint));
177+   if (*hint == '/' || *hint == '.') {
178+     len = -1;
179+     sprintf (mh_folder_hint, "%s", hint);
180+   } else
181+     sprintf (mh_folder_hint, "%s/%s", mh_path, hint);
182+ 
183+   istate = (val != (char *)NULL);
184+ 
185+  again:
186+   val = filename_completion_function (mh_folder_hint, istate);
187+   istate = 1;
188+ 
189+   if (!val)
190+     {
191+       return ((char *)NULL);
192+     }
193+   else
194+     {
195+       char *ptr = val + len + 1, *temp;
196+       struct stat sb;
197+       int status = stat (val, &sb);
198+ 
199+       if (status != 0)
200+ 	return ((char *)NULL);
201+ 
202+       if ((sb.st_mode & S_IFDIR) == S_IFDIR)
203+ 	{
204+ 	  temp = (char *)xmalloc (2 + strlen (ptr));
205+ 	  *temp = '+';
206+ 	  strcpy (temp + 1, ptr);
207+ 
208+ 	  free (val);
209+ 	  val = "";
210+ 
211+ 	  rl_filename_completion_desired = 1;
212+ 
213+ 	  return (temp);
214+ 	}
215+       else
216+ 	{
217+ 	  free (val);
218+ 	}
219+       goto again;
220+     }
221+ }
222+ 
223  /* History and alias expand the line. */
224  static char *
225  history_expand_line_internal (line)
226***************
227*** 1628,1633 ****
228--- 1731,1773 ----
229  {
230    bash_specific_completion
231      (what_to_do, (Function *)username_completion_function);
232+ }
233+ 
234+ static void
235+ bash_complete_mh_folder (ignore, ignore2)
236+      int ignore, ignore2;
237+ {
238+   bash_complete_mh_folder_internal (TAB);
239+ }
240+ 
241+ static void
242+ bash_possible_mh_folder_completions (ignore, ignore2)
243+      int ignore, ignore2;
244+ {
245+   bash_complete_mh_folder_internal ('?');
246+ }
247+ 
248+ static void
249+ bash_complete_mh_folder_internal (what_to_do)
250+      int what_to_do;
251+ {
252+   Function  *orig_func;
253+   CPPFunction *orig_attempt_func;
254+   char *orig_rl_completer_word_break_characters;
255+   extern char *rl_completer_word_break_characters;
256+ 
257+   orig_func = rl_completion_entry_function;
258+   orig_attempt_func = rl_attempted_completion_function;
259+   orig_rl_completer_word_break_characters = rl_completer_word_break_characters;
260+   rl_completion_entry_function = (Function *)mh_folder_completion_function;
261+   rl_attempted_completion_function = (CPPFunction *)NULL;
262+   rl_completer_word_break_characters = " \t\n\"\'";
263+ 
264+   rl_complete_internal (what_to_do);
265+ 
266+   rl_completion_entry_function = orig_func;
267+   rl_attempted_completion_function = orig_attempt_func;
268+   rl_completer_word_break_characters = orig_rl_completer_word_break_characters;
269  }
270  
271  static void
272Only in bash-1.14.2.local: bashline.c.orig
273diff -rc bash-1.14.2/lib/readline/complete.c bash-1.14.2.local/lib/readline/complete.c
274*** bash-1.14.2/lib/readline/complete.c	Tue Jul 26 12:59:57 1994
275--- bash-1.14.2.local/lib/readline/complete.c	Wed Sep 21 15:41:19 1994
276***************
277*** 733,751 ****
278  	      if (rl_filename_completion_desired)
279  		{
280  		  struct stat finfo;
281! 		  char *filename = tilde_expand (matches[0]);
282  
283! 		  if ((stat (filename, &finfo) == 0) && S_ISDIR (finfo.st_mode))
284  		    {
285! 		      if (rl_line_buffer[rl_point] != '/')
286! 			rl_insert_text ("/");
287  		    }
288! 		  else
289  		    {
290! 		      if (rl_point == rl_end)
291! 			rl_insert_text (temp_string);
292  		    }
293- 		  free (filename);
294  		}
295  	      else
296  		{
297--- 733,768 ----
298  	      if (rl_filename_completion_desired)
299  		{
300  		  struct stat finfo;
301! 		  char *tilde_expand ();
302! 		  char *plus_expand ();
303! 		  char *filename = (char *) NULL;
304  
305! 		  switch (*matches[0])
306  		    {
307! 		    case '+':
308! 		      filename = plus_expand (matches[0]);
309! 		      break;
310! 		    case '~':
311! 		    default:
312! 		      filename = tilde_expand (matches[0]);
313! 		      break;
314  		    }
315! 
316! 		  if (filename)
317  		    {
318! 		      if ((stat (filename, &finfo) == 0)
319! 			  && S_ISDIR (finfo.st_mode))
320! 			{
321! 			  if (rl_line_buffer[rl_point] != '/')
322! 			    rl_insert_text ("/");
323! 			}
324! 		      else
325! 			{
326! 			  if (rl_point == rl_end)
327! 			    rl_insert_text (temp_string);
328! 			}
329! 		      free (filename);
330  		    }
331  		}
332  	      else
333  		{
334Only in bash-1.14.2.local/lib/readline: diffs
335diff -rc bash-1.14.2/lib/readline/readline.c bash-1.14.2.local/lib/readline/readline.c
336*** bash-1.14.2/lib/readline/readline.c	Fri Aug 12 12:47:46 1994
337--- bash-1.14.2.local/lib/readline/readline.c	Wed Sep 21 15:36:07 1994
338***************
339*** 23,28 ****
340--- 23,29 ----
341  #define READLINE_LIBRARY
342  
343  #include <stdio.h>
344+ #include <string.h>
345  #include <sys/types.h>
346  #include <fcntl.h>
347  #if !defined (NO_SYS_FILE)
348***************
349*** 3518,3523 ****
350--- 3519,3616 ----
351  }
352  
353  #endif /* TEST */
354+ 
355+ #define cr_whitespace(c) ((c) == '\r' || (c) == '\n' || whitespace(c))
356+ 
357+ char *
358+ get_mh_path ()
359+ {
360+   static FILE *fp = (FILE *)NULL;
361+   char buf[512];      /* XXX */
362+   char profile[512];  /* XXX */
363+   char *bp;
364+   char *temp_home;
365+   char *temp_path;
366+ 
367+   temp_home = (char *)getenv ("HOME");
368+   if (!temp_home)
369+     return ((char *)NULL);
370+ 
371+   strcpy (profile, temp_home);
372+   strcat (profile, "/.mh_profile");
373+ 
374+   if (fp)
375+     fclose (fp);
376+ 
377+   fp = fopen (profile, "r");
378+   if (fp == (FILE *)NULL)
379+     return ((char *)NULL);
380+ 
381+   while (fgets (buf, 512, fp) != (char *)NULL)  /* XXX */
382+     {
383+       if ((bp = strstr (buf, "Path:")) != (char *)NULL)
384+ 	{
385+ 	  bp += 5;
386+ 	  while (whitespace (*bp))
387+ 	    bp++;
388+ 
389+ 	  if (*bp == '\0')
390+ 	    return ((char *)NULL);
391+ 
392+ 	  temp_path = (char *)xmalloc (3 + strlen (bp) + strlen (temp_home));
393+ 
394+ 	  strcpy (temp_path, temp_home);
395+ 	  strcat (temp_path, "/");
396+ 	  strcat (temp_path, bp);
397+ 
398+ 	  bp = temp_path;
399+ 
400+ 	  while (!(cr_whitespace (*bp)))
401+ 	    bp++;
402+ 
403+ 	  *bp = '\0';
404+ 
405+ 	  return temp_path;
406+ 	}
407+     }
408+ 
409+   return ((char *)NULL);
410+ }
411+ 
412+ /* Expand FILENAME if it begins with a plus.  This always returns
413+    a new string. */
414+ char *
415+ plus_expand (filename)
416+      char *filename;
417+ {
418+   static char *dirname = (char *)NULL;
419+ 
420+   if (filename && *filename == '+')
421+     {
422+       char *mh_path = get_mh_path ();
423+ 
424+       if (filename[1] == '/' || filename[1] == '.')
425+ 	{
426+ 	  dirname = (char *)xmalloc (1 + strlen (filename));
427+ 
428+ 	  strcpy(dirname, filename+1);
429+ 
430+ 	  return dirname;
431+ 	}
432+ 
433+       if (mh_path)
434+ 	{
435+ 	  dirname = (char *)xmalloc (1 + strlen (filename) + strlen (mh_path));
436+ 
437+ 	  strcpy (dirname, mh_path);
438+ 	  strcat (dirname, "/");
439+ 	  strcat (dirname, filename+1);
440+ 
441+ 	  return dirname;
442+ 	}
443+     }
444+   return (char *)NULL;
445+ }
446  
447  
448  /*
449
450