mcabber/mcabber/compl.c
changeset 2042 0cb8ea02e472
parent 2035 dac609275117
child 2107 1bd9978ed5d0
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... */