1 /* vifm
2 * Copyright (C) 2011 xaizek.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
17 */
18
19 #include "completion.h"
20
21 #include <assert.h> /* assert() */
22 #include <stddef.h> /* NULL size_t */
23 #include <string.h> /* strdup() */
24
25 #include "../compat/reallocarray.h"
26 #include "../utils/darray.h"
27 #include "../utils/macros.h"
28 #include "../utils/str.h"
29 #include "../utils/utils.h"
30
31 static enum
32 {
33 NOT_STARTED,
34 FILLING_LIST,
35 COMPLETING
36 }
37 state = NOT_STARTED;
38
39 /* List of completion items. */
40 static vle_compl_t *items;
41 /* Declarations to enable use of DA_* on items. */
42 static DA_INSTANCE(items);
43 static int curr = -1;
44 static int group_begin;
45 static int order;
46
47 /* Function called . */
48 static vle_compl_add_path_hook_f add_path_hook = &strdup;
49
50 static int add_match(char match[], const char descr[]);
51 static void group_unique_sort(size_t start_index, size_t len);
52 static int sorter(const void *first, const void *second);
53 static void remove_duplicates(vle_compl_t arr[], size_t count);
54
55 void
vle_compl_reset(void)56 vle_compl_reset(void)
57 {
58 size_t i;
59
60 for(i = 0U; i < DA_SIZE(items); ++i)
61 {
62 free(items[i].text);
63 free(items[i].descr);
64 }
65 DA_REMOVE_ALL(items);
66
67 state = NOT_STARTED;
68 curr = -1;
69 group_begin = 0;
70 order = 0;
71 }
72
73 int
vle_compl_add_match(const char match[],const char descr[])74 vle_compl_add_match(const char match[], const char descr[])
75 {
76 char *const match_dup = strdup(match);
77 const int result = vle_compl_put_match(match_dup, descr);
78 if(result != 0)
79 {
80 free(match_dup);
81 }
82 return result;
83 }
84
85 int
vle_compl_put_match(char match[],const char descr[])86 vle_compl_put_match(char match[], const char descr[])
87 {
88 return add_match(match, descr);
89 }
90
91 int
vle_compl_add_path_match(const char path[])92 vle_compl_add_path_match(const char path[])
93 {
94 char *const match = add_path_hook(path);
95 return add_match(match, "");
96 }
97
98 int
vle_compl_put_path_match(char path[])99 vle_compl_put_path_match(char path[])
100 {
101 if(add_path_hook == &strdup)
102 {
103 return add_match(path, "");
104 }
105 else
106 {
107 const int result = vle_compl_add_path_match(path);
108 free(path);
109 return result;
110 }
111 }
112
113 /* Adds new match to the list of matches. Becomes an owner of memory pointed to
114 * by the match. Errors if match is NULL. Returns zero on success, otherwise
115 * non-zero is returned. */
116 static int
add_match(char match[],const char descr[])117 add_match(char match[], const char descr[])
118 {
119 vle_compl_t *item;
120
121 assert(state != COMPLETING);
122
123 if(match == NULL)
124 {
125 return -1;
126 }
127
128 item = DA_EXTEND(items);
129 if(item == NULL)
130 {
131 return 1;
132 }
133
134 item->text = match;
135 item->descr = strdup(descr);
136 if(item->descr == NULL)
137 {
138 return 1;
139 }
140
141 DA_COMMIT(items);
142
143 state = FILLING_LIST;
144 return 0;
145 }
146
147 int
vle_compl_add_last_match(const char origin[])148 vle_compl_add_last_match(const char origin[])
149 {
150 return vle_compl_add_match(origin, "");
151 }
152
153 int
vle_compl_add_last_path_match(const char origin[])154 vle_compl_add_last_path_match(const char origin[])
155 {
156 return vle_compl_add_path_match(origin);
157 }
158
159 void
vle_compl_finish_group(void)160 vle_compl_finish_group(void)
161 {
162 const size_t n_group_items = DA_SIZE(items) - group_begin;
163 group_unique_sort(group_begin, n_group_items);
164 }
165
166 void
vle_compl_unite_groups(void)167 vle_compl_unite_groups(void)
168 {
169 group_unique_sort(0, DA_SIZE(items));
170 }
171
172 /* Sorts items of the list in range [start_index, start_index + len) with
173 * deduplication. Updates number of list elements and next group beginning. */
174 static void
group_unique_sort(size_t start_index,size_t len)175 group_unique_sort(size_t start_index, size_t len)
176 {
177 vle_compl_t *const group_start = items + start_index;
178
179 assert(state != COMPLETING);
180
181 safe_qsort(group_start, len, sizeof(*group_start), &sorter);
182 remove_duplicates(group_start, len);
183 group_begin = DA_SIZE(items);
184 }
185
186 /* qsort() comparison criterion implementation. Returns standard < 0, = 0,
187 * > 0.*/
188 static int
sorter(const void * first,const void * second)189 sorter(const void *first, const void *second)
190 {
191 const char *const stra = ((const vle_compl_t *)first)->text;
192 const char *const strb = ((const vle_compl_t *)second)->text;
193 const size_t lena = strlen(stra);
194 const size_t lenb = strlen(strb);
195
196 if(strcmp(stra, "./") == 0 || strcmp(strb, "./") == 0)
197 {
198 return 1;
199 }
200
201 if(stra[lena - 1] == '/' && strb[lenb - 1] == '/')
202 {
203 size_t len = MIN(lena - 1, lenb - 1);
204 /* Compare case sensitive strings even on Windows. */
205 if(strncmp(stra, strb, len) == 0)
206 {
207 return lena - lenb;
208 }
209 }
210
211 /* Compare case sensitive strings even on Windows. */
212 return strcmp(stra, strb);
213 }
214
215 char *
vle_compl_next(void)216 vle_compl_next(void)
217 {
218 assert(state != NOT_STARTED && "Invalid unit state.");
219 state = COMPLETING;
220
221 if(curr == -1)
222 {
223 const size_t n_group_items = DA_SIZE(items) - group_begin;
224 remove_duplicates(items + group_begin, n_group_items);
225 }
226
227 if(DA_SIZE(items) == 2)
228 {
229 const int idx = (curr == -1) ? 0 : curr;
230 curr = 0;
231 return strdup(items[idx].text);
232 }
233
234 if(!order)
235 {
236 /* Straight order. */
237 curr = (curr + 1) % DA_SIZE(items);
238 }
239 else
240 {
241 /* Reverse order. */
242 if(curr == -1)
243 {
244 curr = DA_SIZE(items) - 2U;
245 }
246 else
247 {
248 --curr;
249 }
250 if(curr < 0)
251 {
252 curr = DA_SIZE(items) - 1U;
253 }
254 }
255 return strdup(items[curr].text);
256 }
257
258 /* Removes series of consecutive duplicates. */
259 static void
remove_duplicates(vle_compl_t arr[],size_t count)260 remove_duplicates(vle_compl_t arr[], size_t count)
261 {
262 size_t i, j;
263 j = 1U;
264 for(i = j; i < count; ++i)
265 {
266 /* Compare case sensitive strings even on Windows. */
267 if(strcmp(arr[i].text, arr[j - 1].text) == 0)
268 {
269 free(arr[i].text);
270 free(arr[i].descr);
271 continue;
272 }
273 arr[j++] = arr[i];
274 }
275 if(count != 0U)
276 {
277 DA_REMOVE_AFTER(items, &arr[j]);
278 }
279 }
280
281 int
vle_compl_get_count(void)282 vle_compl_get_count(void)
283 {
284 return DA_SIZE(items);
285 }
286
287 void
vle_compl_set_order(int reversed)288 vle_compl_set_order(int reversed)
289 {
290 order = reversed;
291 }
292
293 const vle_compl_t *
vle_compl_get_items(void)294 vle_compl_get_items(void)
295 {
296 return items;
297 }
298
299 int
vle_compl_get_pos(void)300 vle_compl_get_pos(void)
301 {
302 return curr;
303 }
304
305 void
vle_compl_rewind(void)306 vle_compl_rewind(void)
307 {
308 assert(state == COMPLETING && "Invalid unit state.");
309
310 if(DA_SIZE(items) == 2)
311 {
312 curr = 1;
313 }
314 else if(DA_SIZE(items) > 2)
315 {
316 curr = DA_SIZE(items) - 2;
317 }
318 }
319
320 void
vle_compl_set_add_path_hook(vle_compl_add_path_hook_f hook)321 vle_compl_set_add_path_hook(vle_compl_add_path_hook_f hook)
322 {
323 add_path_hook = (hook == NULL) ? &strdup : hook;
324 }
325
326 /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
327 /* vim: set cinoptions+=t0 filetype=c : */
328