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