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