Mercurial > ~mikael > mcabber > hg
comparison mcabber/mcabber/compl.c @ 2042:0cb8ea02e472
Make completion sorting order configurable
* Use allocated plain array for categories
* Use callbacks for dynamic completions (private for now)
* Add compl_set_flags() to allow user to set completion order
* Bump api to 24-24
author | Myhailo Danylenko <isbear@ukrpost.net> |
---|---|
date | Sat, 20 Oct 2012 18:29:49 +0300 |
parents | dac609275117 |
children | 1bd9978ed5d0 |
comparison
equal
deleted
inserted
replaced
2041:e8f2db654e67 | 2042:0cb8ea02e472 |
---|---|
1 /* | 1 /* |
2 * compl.c -- Completion system | 2 * compl.c -- Completion system |
3 * | 3 * |
4 * Copyright (C) 2005-2010 Mikael Berthe <mikael@lilotux.net> | 4 * Copyright (C) 2005-2010 Mikael Berthe <mikael@lilotux.net> |
5 * Copyright (C) 2009,2010 Myhailo Danylenko <isbear@ukrpost.net> | 5 * Copyright (C) 2009-2012 Myhailo Danylenko <isbear@ukrpost.net> |
6 * | 6 * |
7 * This program is free software; you can redistribute it and/or modify | 7 * This program is free software; you can redistribute it and/or modify |
8 * it under the terms of the GNU General Public License as published by | 8 * it under the terms of the GNU General Public License as published by |
9 * the Free Software Foundation; either version 2 of the License, or (at | 9 * the Free Software Foundation; either version 2 of the License, or (at |
10 * your option) any later version. | 10 * your option) any later version. |
45 guint len_prefix; // length of text already typed by the user | 45 guint len_prefix; // length of text already typed by the user |
46 guint len_compl; // length of the last completion | 46 guint len_compl; // length of the last completion |
47 GSList *next; // pointer to next completion to try | 47 GSList *next; // pointer to next completion to try |
48 } compl; | 48 } compl; |
49 | 49 |
50 typedef GSList *(*compl_handler_t) (void); // XXX userdata? *dynlist? | |
51 | |
50 // Category structure | 52 // Category structure |
51 typedef struct { | 53 typedef struct { |
52 guint64 flag; | 54 guint flags; |
53 GSList *words; | 55 GSList *words; |
56 compl_handler_t dynamic; | |
54 } category; | 57 } category; |
55 | 58 |
56 static GSList *Categories; | 59 #define COMPL_CAT_BUILTIN 0x01 |
60 #define COMPL_CAT_ACTIVE 0x02 | |
61 #define COMPL_CAT_DYNAMIC 0x04 | |
62 #define COMPL_CAT_REVERSE 0x10 | |
63 #define COMPL_CAT_NOSORT 0x20 | |
64 | |
65 #define COMPL_CAT_USERFLAGS 0x30 | |
66 | |
57 static compl *InputCompl; | 67 static compl *InputCompl; |
58 | 68 static category *Categories; |
69 static guint num_categories; | |
70 | |
71 // Dynamic completions callbacks | |
72 static GSList *compl_dyn_group (void) | |
73 { | |
74 return compl_list(ROSTER_TYPE_GROUP); | |
75 } | |
76 | |
77 static GSList *compl_dyn_user (void) | |
78 { | |
79 return compl_list(ROSTER_TYPE_USER); | |
80 } | |
81 | |
82 static GSList *compl_dyn_resource (void) | |
83 { | |
84 return buddy_getresources_locale(NULL); | |
85 } | |
86 | |
87 static GSList *compl_dyn_events (void) | |
88 { | |
89 GSList *compl = evs_geteventslist(); | |
90 GSList *cel; | |
91 for (cel = compl; cel; cel = cel->next) | |
92 cel->data = g_strdup(cel->data); | |
93 compl = g_slist_append(compl, g_strdup("list")); | |
94 return compl; | |
95 } | |
96 | |
97 static inline void register_builtin_cat(guint c, compl_handler_t dynamic) { | |
98 Categories[c-1].flags = COMPL_CAT_BUILTIN | COMPL_CAT_ACTIVE; | |
99 Categories[c-1].words = NULL; | |
100 Categories[c-1].dynamic = dynamic; | |
101 if (dynamic != NULL) { | |
102 Categories[c-1].flags |= COMPL_CAT_DYNAMIC; | |
103 } | |
104 } | |
105 | |
106 void compl_init_system(void) | |
107 { | |
108 num_categories = COMPL_MAX_ID; | |
59 #ifdef MODULES_ENABLE | 109 #ifdef MODULES_ENABLE |
60 static guint64 registered_cats; | 110 num_categories = ((num_categories / 16) + 1) * 16; |
61 | 111 #endif |
62 static inline void register_builtin_cat(guint c) { | 112 Categories = g_new0(category, num_categories); |
63 registered_cats |= 1UL << (c-1); | 113 |
64 } | |
65 | |
66 void compl_init_system(void) | |
67 { | |
68 // Builtin completion categories: | 114 // Builtin completion categories: |
69 register_builtin_cat(COMPL_CMD); | 115 register_builtin_cat(COMPL_CMD, NULL); |
70 register_builtin_cat(COMPL_JID); | 116 register_builtin_cat(COMPL_JID, compl_dyn_user); |
71 register_builtin_cat(COMPL_URLJID); | 117 register_builtin_cat(COMPL_URLJID, NULL); |
72 register_builtin_cat(COMPL_NAME); | 118 register_builtin_cat(COMPL_NAME, NULL); |
73 register_builtin_cat(COMPL_STATUS); | 119 register_builtin_cat(COMPL_STATUS, NULL); |
74 register_builtin_cat(COMPL_FILENAME); | 120 register_builtin_cat(COMPL_FILENAME, NULL); |
75 register_builtin_cat(COMPL_ROSTER); | 121 register_builtin_cat(COMPL_ROSTER, NULL); |
76 register_builtin_cat(COMPL_BUFFER); | 122 register_builtin_cat(COMPL_BUFFER, NULL); |
77 register_builtin_cat(COMPL_GROUP); | 123 register_builtin_cat(COMPL_GROUP, NULL); |
78 register_builtin_cat(COMPL_GROUPNAME); | 124 register_builtin_cat(COMPL_GROUPNAME, compl_dyn_group); |
79 register_builtin_cat(COMPL_MULTILINE); | 125 register_builtin_cat(COMPL_MULTILINE, NULL); |
80 register_builtin_cat(COMPL_ROOM); | 126 register_builtin_cat(COMPL_ROOM, NULL); |
81 register_builtin_cat(COMPL_RESOURCE); | 127 register_builtin_cat(COMPL_RESOURCE, compl_dyn_resource); |
82 register_builtin_cat(COMPL_AUTH); | 128 register_builtin_cat(COMPL_AUTH, NULL); |
83 register_builtin_cat(COMPL_REQUEST); | 129 register_builtin_cat(COMPL_REQUEST, NULL); |
84 register_builtin_cat(COMPL_EVENTS); | 130 register_builtin_cat(COMPL_EVENTS, NULL); |
85 register_builtin_cat(COMPL_EVENTSID); | 131 register_builtin_cat(COMPL_EVENTSID, compl_dyn_events); |
86 register_builtin_cat(COMPL_PGP); | 132 register_builtin_cat(COMPL_PGP, NULL); |
87 register_builtin_cat(COMPL_COLOR); | 133 register_builtin_cat(COMPL_COLOR, NULL); |
88 register_builtin_cat(COMPL_OTR); | 134 register_builtin_cat(COMPL_OTR, NULL); |
89 register_builtin_cat(COMPL_OTRPOLICY); | 135 register_builtin_cat(COMPL_OTRPOLICY, NULL); |
90 register_builtin_cat(COMPL_MODULE); | 136 register_builtin_cat(COMPL_MODULE, NULL); |
91 } | 137 } |
92 | 138 |
93 // compl_new_category() | 139 #ifdef MODULES_ENABLE |
140 // compl_new_category(flags) | |
94 // Reserves id for new completion category. | 141 // Reserves id for new completion category. |
142 // Flags determine word sorting order. | |
95 // Returns 0, if no more categories can be allocated. | 143 // Returns 0, if no more categories can be allocated. |
96 // Note, that user should not make any assumptions about id nature, | 144 guint compl_new_category(guint flags) |
97 // as it is likely to change in future. | 145 { |
98 guint compl_new_category(void) | 146 guint i; |
99 { | 147 for (i = 0; i < num_categories; i++) |
100 const guint maxcat = 8 * sizeof (registered_cats); | 148 if (!(Categories[i].flags & COMPL_CAT_ACTIVE)) |
101 guint i = 0; | 149 break; |
102 while ((registered_cats >> i) & 1 && i < maxcat) | 150 if (i >= num_categories ) { |
103 i++; | 151 guint j; |
104 if (i >= maxcat) | 152 if (num_categories > G_MAXUINT - 16) { |
105 return 0; | 153 scr_log_print(LPRINT_LOGNORM, "Warning: Too many " |
106 else { | 154 "completion categories!"); |
107 guint64 id = 1 << i; | 155 return 0; |
108 registered_cats |= id; | 156 } |
109 return i+1; | 157 num_categories += 16; |
110 } | 158 Categories = g_renew(category, Categories, num_categories); |
159 for (j = i+1; j < num_categories; j++) | |
160 Categories[j].flags = 0; | |
161 } | |
162 Categories[i].flags = COMPL_CAT_ACTIVE | (flags & COMPL_CAT_USERFLAGS); | |
163 Categories[i].words = NULL; | |
164 return i+1; | |
111 } | 165 } |
112 | 166 |
113 // compl_del_category(id) | 167 // compl_del_category(id) |
114 // Frees reserved id for category. | 168 // Frees reserved id for category. |
115 // Note, that for now it not validates its input, so, be careful | 169 // Note, that for now it not validates its input, so, be careful |
116 // and specify exactly what you get from compl_new_category. | 170 // and specify exactly what you get from compl_new_category. |
117 void compl_del_category(guint id) | 171 void compl_del_category(guint compl) |
118 { | 172 { |
119 if (!id) { | 173 GSList *wel; |
120 scr_log_print(LPRINT_LOGNORM, "Error: compl_del_category() - " | 174 |
121 "Invalid category."); | 175 if (!compl) { |
122 return; | 176 scr_log_print(LPRINT_DEBUG, "Error: compl_del_category() - " |
123 } | 177 "Invalid category (0)."); |
124 id--; | 178 return; |
125 registered_cats &= ~(1<<id); | 179 } |
180 | |
181 compl--; | |
182 | |
183 if ((compl >= num_categories) || | |
184 (Categories[compl].flags & COMPL_CAT_BUILTIN)) { | |
185 scr_log_print(LPRINT_DEBUG, "Error: compl_del_category() " | |
186 "Invalid category."); | |
187 return; | |
188 } | |
189 | |
190 Categories[compl].flags = 0; | |
191 for (wel = Categories[compl].words; wel; wel = g_slist_next (wel)) | |
192 g_free (wel -> data); | |
193 g_slist_free (Categories[compl].words); | |
126 } | 194 } |
127 #endif | 195 #endif |
128 | 196 |
129 // new_completion(prefix, compl_cat, suffix) | 197 // new_completion(prefix, compl_cat, suffix) |
130 // . prefix = beginning of the word, typed by the user | 198 // . prefix = beginning of the word, typed by the user |
134 // done_completion() must be called when finished. | 202 // done_completion() must be called when finished. |
135 // Returns the number of possible completions. | 203 // Returns the number of possible completions. |
136 guint new_completion(const char *prefix, GSList *compl_cat, const gchar *suffix) | 204 guint new_completion(const char *prefix, GSList *compl_cat, const gchar *suffix) |
137 { | 205 { |
138 compl *c; | 206 compl *c; |
207 guint ret_len = 0; | |
139 GSList *sl_cat; | 208 GSList *sl_cat; |
140 gint (*cmp)(const char *s1, const char *s2, size_t n); | 209 gint (*cmp)(const char *s1, const char *s2, size_t n); |
141 size_t len = strlen(prefix); | 210 size_t len = strlen(prefix); |
142 | 211 |
143 if (InputCompl) { // This should not happen, but hey... | 212 if (InputCompl) { // This should not happen, but hey... |
144 cancel_completion(); | 213 scr_log_print(LPRINT_DEBUG, "Warning: new_completion() - " |
214 "Previous completion exists!"); | |
215 done_completion(); | |
145 } | 216 } |
146 | 217 |
147 if (settings_opt_get_int("completion_ignore_case")) | 218 if (settings_opt_get_int("completion_ignore_case")) |
148 cmp = &strncasecmp; | 219 cmp = &strncasecmp; |
149 else | 220 else |
158 gchar *compval; | 229 gchar *compval; |
159 if (suffix) | 230 if (suffix) |
160 compval = g_strdup_printf("%s%s", word+len, suffix); | 231 compval = g_strdup_printf("%s%s", word+len, suffix); |
161 else | 232 else |
162 compval = g_strdup(word+len); | 233 compval = g_strdup(word+len); |
163 c->list = g_slist_insert_sorted(c->list, compval, | 234 // for a bit of efficiency, will reverse order afterwards |
164 (GCompareFunc)g_ascii_strcasecmp); | 235 c->list = g_slist_prepend(c->list, compval); |
236 ret_len ++; | |
165 } | 237 } |
166 } | 238 } |
167 } | 239 } |
168 c->next = c->list; | 240 c->next = c->list = g_slist_reverse (c->list); |
169 InputCompl = c; | 241 InputCompl = c; |
170 return g_slist_length(c->list); | 242 return ret_len; |
171 } | 243 } |
172 | 244 |
173 // done_completion(); | 245 // done_completion(); |
174 void done_completion(void) | 246 void done_completion(void) |
175 { | 247 { |
220 } | 292 } |
221 | 293 |
222 | 294 |
223 /* Categories functions */ | 295 /* Categories functions */ |
224 | 296 |
297 static gint compl_sort_forward(gconstpointer a, gconstpointer b) | |
298 { | |
299 return g_ascii_strcasecmp((const gchar *)a, (const gchar *)b); | |
300 } | |
301 | |
302 static gint compl_sort_reverse(gconstpointer a, gconstpointer b) | |
303 { | |
304 return -g_ascii_strcasecmp((const gchar *)a, (const gchar *)b); | |
305 } | |
306 | |
307 static gint compl_sort_append(gconstpointer a, gconstpointer b) | |
308 { | |
309 return 1; | |
310 } | |
311 | |
312 static gint compl_sort_prepend(gconstpointer a, gconstpointer b) | |
313 { | |
314 return -1; | |
315 } | |
316 | |
225 // compl_add_category_word(categ, command) | 317 // compl_add_category_word(categ, command) |
226 // Adds a keyword as a possible completion in category categ. | 318 // Adds a keyword as a possible completion in category categ. |
227 void compl_add_category_word(guint categ, const gchar *word) | 319 void compl_add_category_word(guint categ, const gchar *word) |
228 { | 320 { |
229 guint64 catv; | |
230 GSList *sl_cat; | |
231 category *cat; | |
232 char *nword; | 321 char *nword; |
233 | 322 |
234 if (!categ) { | 323 if (!categ) { |
235 scr_log_print(LPRINT_LOGNORM, "Error: compl_add_category_word() - " | 324 scr_log_print(LPRINT_DEBUG, "Error: compl_add_category_word() - " |
236 "Invalid category."); | 325 "Invalid category (0)."); |
237 return; | 326 return; |
238 } | 327 } |
239 | 328 |
240 categ--; | 329 categ--; |
241 catv = 1UL << categ; | 330 |
242 | 331 if ((categ >= num_categories) || |
243 // Look for category | 332 !(Categories[categ].flags & COMPL_CAT_ACTIVE)) { |
244 for (sl_cat=Categories; sl_cat; sl_cat = g_slist_next(sl_cat)) { | 333 scr_log_print(LPRINT_DEBUG, "Error: compl_add_category_word() - " |
245 if (catv == ((category*)sl_cat->data)->flag) | 334 "Category does not exist."); |
246 break; | 335 return; |
247 } | 336 } |
248 if (!sl_cat) { // Category not found, let's create it | |
249 cat = g_new0(category, 1); | |
250 cat->flag = catv; | |
251 Categories = g_slist_append(Categories, cat); | |
252 } else | |
253 cat = (category*)sl_cat->data; | |
254 | 337 |
255 // If word is not space-terminated, we add one trailing space | 338 // If word is not space-terminated, we add one trailing space |
256 for (nword = (char*)word; *nword; nword++) | 339 for (nword = (char*)word; *nword; nword++) |
257 ; | 340 ; |
258 if (nword > word) nword--; | 341 if (nword > word) nword--; |
260 nword = g_strdup_printf("%s ", word); | 343 nword = g_strdup_printf("%s ", word); |
261 } else { // word is fine | 344 } else { // word is fine |
262 nword = g_strdup(word); | 345 nword = g_strdup(word); |
263 } | 346 } |
264 | 347 |
265 if (g_slist_find_custom(cat->words, nword, (GCompareFunc)g_strcmp0) != NULL) | 348 if (g_slist_find_custom(Categories[categ].words, nword, |
266 return; | 349 (GCompareFunc)g_strcmp0) == NULL) { |
267 | 350 guint flags = Categories[categ].flags; |
268 cat->words = g_slist_insert_sorted(cat->words, nword, | 351 GCompareFunc comparator = compl_sort_forward; |
269 (GCompareFunc)g_ascii_strcasecmp); | 352 if (flags & COMPL_CAT_NOSORT) { |
353 if (flags & COMPL_CAT_REVERSE) | |
354 comparator = compl_sort_prepend; | |
355 else | |
356 comparator = compl_sort_append; | |
357 } else if (flags & COMPL_CAT_REVERSE) | |
358 comparator = compl_sort_reverse; | |
359 | |
360 Categories[categ].words = g_slist_insert_sorted | |
361 (Categories[categ].words, nword, comparator); | |
362 } | |
270 } | 363 } |
271 | 364 |
272 // compl_del_category_word(categ, command) | 365 // compl_del_category_word(categ, command) |
273 // Removes a keyword from category categ in completion list. | 366 // Removes a keyword from category categ in completion list. |
274 void compl_del_category_word(guint categ, const gchar *word) | 367 void compl_del_category_word(guint categ, const gchar *word) |
275 { | 368 { |
276 guint64 catv; | 369 GSList *wel; |
277 GSList *sl_cat, *sl_elt; | |
278 category *cat; | |
279 char *nword; | 370 char *nword; |
280 | 371 |
281 if (!categ) { | 372 if (!categ) { |
282 scr_log_print(LPRINT_LOGNORM, "Error: compl_del_category_word() - " | 373 scr_log_print(LPRINT_DEBUG, "Error: compl_del_category_word() - " |
283 "Invalid category."); | 374 "Invalid category (0)."); |
284 return; | 375 return; |
285 } | 376 } |
286 | 377 |
287 categ--; | 378 categ--; |
288 catv = 1UL << categ; | 379 |
289 | 380 if ((categ >= num_categories) || |
290 // Look for category | 381 !(Categories[categ].flags & COMPL_CAT_ACTIVE)) { |
291 for (sl_cat=Categories; sl_cat; sl_cat = g_slist_next(sl_cat)) { | 382 scr_log_print(LPRINT_DEBUG, "Error: compl_del_category_word() - " |
292 if (catv == ((category*)sl_cat->data)->flag) | 383 "Category does not exist."); |
293 break; | 384 return; |
294 } | 385 } |
295 if (!sl_cat) return; // Category not found, finished! | |
296 | |
297 cat = (category*)sl_cat->data; | |
298 | 386 |
299 // If word is not space-terminated, we add one trailing space | 387 // If word is not space-terminated, we add one trailing space |
300 for (nword = (char*)word; *nword; nword++) | 388 for (nword = (char*)word; *nword; nword++) |
301 ; | 389 ; |
302 if (nword > word) nword--; | 390 if (nword > word) nword--; |
303 if (*nword != ' ') { // Add a space | 391 if (*nword != ' ') // Add a space |
304 nword = g_strdup_printf("%s ", word); | 392 word = nword = g_strdup_printf("%s ", word); |
305 } else { // word is fine | 393 else |
306 nword = g_strdup(word); | 394 nword = NULL; |
307 } | 395 |
308 | 396 for (wel = Categories[categ].words; wel; wel = g_slist_next (wel)) { |
309 sl_elt = cat->words; | 397 if (!strcasecmp((char*)wel->data, word)) { |
310 while (sl_elt) { | 398 g_free(wel->data); |
311 if (!strcasecmp((char*)sl_elt->data, nword)) { | 399 Categories[categ].words = g_slist_delete_link |
312 g_free(sl_elt->data); | 400 (Categories[categ].words, wel); |
313 cat->words = g_slist_delete_link(cat->words, sl_elt); | |
314 break; // Only remove first occurence | 401 break; // Only remove first occurence |
315 } | 402 } |
316 sl_elt = g_slist_next(sl_elt); | 403 } |
317 } | 404 |
405 g_free (nword); | |
318 } | 406 } |
319 | 407 |
320 // compl_get_category_list() | 408 // compl_get_category_list() |
321 // Returns a slist of all words in the specified categorie. | 409 // Returns a slist of all words in the specified categorie. |
322 // Iff this function sets *dynlist to TRUE, then the caller must free the | 410 // Iff this function sets *dynlist to TRUE, then the caller must free the |
323 // whole list after use. | 411 // whole list after use. |
324 GSList *compl_get_category_list(guint categ, guint *dynlist) | 412 GSList *compl_get_category_list(guint categ, guint *dynlist) |
325 { | 413 { |
326 guint64 cat_flags; | |
327 GSList *sl_cat; | |
328 | |
329 if (!categ) { | 414 if (!categ) { |
330 scr_log_print(LPRINT_LOGNORM, "Error: compl_get_category_list() - " | 415 scr_log_print(LPRINT_DEBUG, "Error: compl_get_category_list() - " |
331 "Invalid category."); | 416 "Invalid category (0)."); |
332 return NULL; | 417 return NULL; |
333 } | 418 } |
334 | 419 |
335 *dynlist = FALSE; | 420 categ --; |
336 cat_flags = 1UL << (categ - 1); | 421 |
337 | 422 if ((categ > num_categories) || |
338 // Look for the category | 423 !(Categories[categ].flags & COMPL_CAT_ACTIVE)) { |
339 for (sl_cat=Categories; sl_cat; sl_cat = g_slist_next(sl_cat)) { | 424 scr_log_print(LPRINT_DEBUG, "Error: compl_get_category_list() - " |
340 if (cat_flags == ((category*)sl_cat->data)->flag) | 425 "Category does not exist."); |
341 break; | 426 return NULL; |
342 } | 427 } |
343 if (sl_cat) // Category was found, easy... | 428 |
344 return ((category*)sl_cat->data)->words; | 429 if (Categories[categ].flags & COMPL_CAT_DYNAMIC) { |
345 | 430 *dynlist = TRUE; |
346 // Handle dynamic SLists | 431 return (*Categories[categ].dynamic) (); |
347 *dynlist = TRUE; | 432 } else { |
348 if (categ == COMPL_GROUPNAME) { | 433 *dynlist = FALSE; |
349 return compl_list(ROSTER_TYPE_GROUP); | 434 return Categories[categ].words; |
350 } | 435 } |
351 if (categ == COMPL_JID) { | |
352 return compl_list(ROSTER_TYPE_USER); | |
353 } | |
354 if (categ == COMPL_RESOURCE) { | |
355 return buddy_getresources_locale(NULL); | |
356 } | |
357 if (categ == COMPL_EVENTSID) { | |
358 GSList *compl = evs_geteventslist(); | |
359 GSList *cel; | |
360 for (cel = compl; cel; cel = cel->next) | |
361 cel->data = g_strdup(cel->data); | |
362 compl = g_slist_append(compl, g_strdup("list")); | |
363 return compl; | |
364 } | |
365 | |
366 *dynlist = FALSE; | |
367 return NULL; | |
368 } | 436 } |
369 | 437 |
370 /* vim: set expandtab cindent cinoptions=>2\:2(0 sw=2 ts=2: For Vim users... */ | 438 /* vim: set expandtab cindent cinoptions=>2\:2(0 sw=2 ts=2: For Vim users... */ |