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... */