1 /* Completion facility functions
2 
3    Copyright (c) 1997-2014 Free Software Foundation, Inc.
4 
5    This file is part of GNU Zile.
6 
7    GNU Zile is free software; you can redistribute it and/or modify it
8    under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 3, or (at your option)
10    any later version.
11 
12    GNU Zile is distributed in the hope that it will be useful, but
13    WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15    General Public License for more details.
16 
17    You should have received a copy of the GNU General Public License
18    along with GNU Zile; see the file COPYING.  If not, write to the
19    Free Software Foundation, Fifth Floor, 51 Franklin Street, Boston,
20    MA 02111-1301, USA.  */
21 
22 #include <config.h>
23 
24 #include <sys/stat.h>
25 #include <assert.h>
26 #include <dirent.h>
27 #include <stdarg.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <unistd.h>
31 #include "dirname.h"
32 #include "gl_linked_list.h"
33 
34 #include "main.h"
35 #include "extern.h"
36 
37 
38 /*
39  * Structure
40  */
41 struct Completion
42 {
43 #define FIELD(ty, name) ty name;
44 #define FIELD_STR(name) char *name;
45 #include "completion.h"
46 #undef FIELD
47 #undef FIELD_STR
48 };
49 
50 #define FIELD(ty, field)                        \
51   GETTER (Completion, completion, ty, field)    \
52   SETTER (Completion, completion, ty, field)
53 
54 #define FIELD_STR(field)                                  \
55   GETTER (Completion, completion, char *, field)          \
56   STR_SETTER (Completion, completion, field)
57 
58 #include "completion.h"
59 #undef FIELD
60 #undef FIELD_STR
61 
62 /*
63  * List methods for completions and matches
64  */
65 int
completion_strcmp(const void * p1,const void * p2)66 completion_strcmp (const void *p1, const void *p2)
67 {
68   return strcmp ((const char *) p1, (const char *) p2);
69 }
70 
71 static bool
completion_STREQ(const void * p1,const void * p2)72 completion_STREQ (const void *p1, const void *p2)
73 {
74   return STREQ ((const char *) p1, (const char *) p2);
75 }
76 
77 /*
78  * Allocate a new completion structure.
79  */
80 Completion
completion_new(bool fileflag)81 completion_new (bool fileflag)
82 {
83   Completion cp = (Completion) XZALLOC (struct Completion);
84 
85   cp->completions = gl_list_create_empty (GL_LINKED_LIST,
86                                           completion_STREQ, NULL,
87                                           NULL, false);
88   cp->matches = gl_list_create_empty (GL_LINKED_LIST,
89                                       completion_STREQ, NULL,
90                                       NULL, false);
91 
92   if (fileflag)
93     {
94       cp->path = astr_new ();
95       cp->flags |= CFLAG_FILENAME;
96     }
97 
98   return cp;
99 }
100 
101 /*
102  * Scroll completions up.
103  */
104 void
completion_scroll_up(void)105 completion_scroll_up (void)
106 {
107   Window old_wp = cur_wp;
108   Window wp = find_window ("*Completions*");
109   assert (wp != NULL);
110   set_current_window (wp);
111   if (FUNCALL (scroll_up) == leNIL)
112     {
113       FUNCALL (beginning_of_buffer);
114       window_resync (cur_wp);
115     }
116   set_current_window (old_wp);
117 
118   term_redisplay ();
119 }
120 
121 /*
122  * Scroll completions down.
123  */
124 void
completion_scroll_down(void)125 completion_scroll_down (void)
126 {
127   Window old_wp = cur_wp;
128   Window wp = find_window ("*Completions*");
129   assert (wp != NULL);
130   set_current_window (wp);
131   if (FUNCALL (scroll_down) == leNIL)
132     {
133       FUNCALL (end_of_buffer);
134       window_resync (cur_wp);
135     }
136   set_current_window (old_wp);
137 
138   term_redisplay ();
139 }
140 
141 /*
142  * Calculate the maximum length of the completions.
143  */
144 static size_t
calculate_max_length(gl_list_t l)145 calculate_max_length (gl_list_t l)
146 {
147   size_t maxlen = 0;
148   for (size_t i = 0; i < gl_list_size (l); i++)
149     maxlen = MAX (strlen ((const char *) gl_list_get_at (l, i)), maxlen);
150   return maxlen;
151 }
152 
153 /*
154  * Print the list of completions in a set of columns.
155  */
156 static void
write_completion(va_list ap)157 write_completion (va_list ap)
158 {
159   gl_list_t l = va_arg (ap, gl_list_t);
160   size_t max = calculate_max_length (l) + 5;
161   size_t numcols = (get_window_ewidth (cur_wp) - 1) / max;
162 
163   bprintf ("Possible completions are:\n");
164   for (size_t i = 0, col = 0; i < gl_list_size (l); i++)
165     {
166       bprintf ("%-*s", (int) max, (const char *) gl_list_get_at (l, i));
167 
168       col = (col + 1) % numcols;
169       if (col == 0)
170         insert_newline ();
171     }
172 }
173 
174 /*
175  * Popup the completion window.
176  */
177 static void
popup_completion(Completion cp,int allflag)178 popup_completion (Completion cp, int allflag)
179 {
180   cp->flags |= CFLAG_POPPEDUP;
181   if (get_window_next (head_wp) == NULL)
182     cp->flags |= CFLAG_CLOSE;
183 
184   write_temp_buffer ("*Completions*", true, write_completion, allflag ? cp->completions : cp->matches);
185 
186   if (!(cp->flags & CFLAG_CLOSE))
187     cp->old_bp = cur_bp;
188 
189   term_redisplay ();
190 }
191 
192 /*
193  * Reread directory for completions.
194  */
195 static int
completion_readdir(Completion cp,astr path)196 completion_readdir (Completion cp, astr path)
197 {
198   cp->completions = gl_list_create_empty (GL_LINKED_LIST,
199                                           completion_STREQ, NULL,
200                                           NULL, false);
201 
202   if (!expand_path (path))
203     return false;
204 
205   /* Split up path with dirname and basename, unless it ends in `/',
206      in which case it's considered to be entirely dirname. */
207   astr pdir;
208   if (astr_get (path, astr_len (path) - 1) != '/')
209     {
210       pdir = astr_new_cstr (dir_name (astr_cstr (path)));
211       if (!STREQ (astr_cstr (pdir), "/"))
212         astr_cat_char (pdir, '/');
213       astr_cpy_cstr (path, base_name (astr_cstr (path)));
214     }
215   else
216     {
217       pdir = astr_cpy (astr_new (), path);
218       astr_truncate (path, 0);
219     }
220 
221   DIR *dir = opendir (astr_cstr (pdir));
222   if (dir != NULL)
223     {
224       for (struct dirent *d = readdir (dir); d != NULL; d = readdir (dir))
225         {
226           astr as = astr_new_cstr (d->d_name);
227           struct stat st;
228           if (stat (xasprintf ("%s%s", astr_cstr (pdir), d->d_name), &st) == 0 &&
229               S_ISDIR (st.st_mode))
230             astr_cat_char (as, '/');
231           gl_sortedlist_add (cp->completions, completion_strcmp,
232                              xstrdup (astr_cstr (as)));
233         }
234       closedir (dir);
235 
236       cp->path = compact_path (pdir);
237     }
238 
239   return dir != NULL;
240 }
241 
242 /*
243  * Match completions.
244  */
245 completion_code
completion_try(Completion cp,astr search,bool popup_when_complete)246 completion_try (Completion cp, astr search, bool popup_when_complete)
247 {
248   cp->matches = gl_list_create_empty (GL_LINKED_LIST, completion_STREQ, NULL, NULL, false);
249 
250   if (cp->flags & CFLAG_FILENAME)
251     if (!completion_readdir (cp, search))
252       return completion_notmatched;
253 
254   size_t ssize = astr_len (search);
255   if (ssize == 0)
256     {
257       cp->match = xstrdup ((const char *) gl_list_get_at (cp->completions, 0));
258       if (gl_list_size (cp->completions) > 1)
259         {
260           cp->matchsize = 0;
261           popup_completion (cp, true);
262           return completion_nonunique;
263         }
264       else
265         {
266           cp->matchsize = strlen (cp->match);
267           return completion_matched;
268         }
269     }
270 
271   size_t fullmatches = 0;
272   for (size_t i = 0; i < gl_list_size (cp->completions); i++)
273     {
274       const char *s = (const char *) gl_list_get_at (cp->completions, i);
275       if (!strncmp (s, astr_cstr (search), ssize))
276         {
277           gl_sortedlist_add (cp->matches, completion_strcmp, s);
278           if (STREQ (s, astr_cstr (search)))
279             ++fullmatches;
280         }
281     }
282 
283   if (gl_list_size (cp->matches) == 0)
284     return completion_notmatched;
285   else if (gl_list_size (cp->matches) == 1)
286     {
287       cp->match = xstrdup ((const char *) gl_list_get_at (cp->matches, 0));
288       cp->matchsize = strlen (cp->match);
289       return completion_matched;
290     }
291   else if (gl_list_size (cp->matches) > 1 && fullmatches == 1)
292     {
293       cp->match = xstrdup ((const char *) gl_list_get_at (cp->matches, 0));
294       cp->matchsize = strlen (cp->match);
295       if (popup_when_complete)
296         popup_completion (cp, false);
297       return completion_matchednonunique;
298     }
299 
300   for (size_t j = ssize;; ++j)
301     {
302       char c = ((const char *) gl_list_get_at (cp->matches, 0))[j];
303       for (size_t i = 1; i < gl_list_size (cp->matches); ++i)
304         {
305           if (((const char *)(gl_list_get_at (cp->matches, i)))[j] != c)
306             {
307               cp->match = xstrdup ((const char *) gl_list_get_at (cp->matches, 0));
308               cp->matchsize = j;
309               popup_completion (cp, false);
310               return completion_nonunique;
311             }
312         }
313     }
314 
315   abort ();
316 }
317