changeset 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 e8f2db654e67
children 8c51c684dd2f
files mcabber/mcabber/api.h mcabber/mcabber/compl.c mcabber/mcabber/compl.h mcabber/mcabber/main.c mcabber/modules/beep/beep.c
diffstat 5 files changed, 220 insertions(+), 141 deletions(-) [+]
line wrap: on
line diff
--- a/mcabber/mcabber/api.h	Thu Nov 22 00:59:19 2012 +0200
+++ b/mcabber/mcabber/api.h	Sat Oct 20 18:29:49 2012 +0300
@@ -4,12 +4,15 @@
 #include <glib.h>
 #include <mcabber/config.h> // For MCABBER_BRANCH
 
-#define MCABBER_API_VERSION 23
-#define MCABBER_API_MIN     21
+#define MCABBER_API_VERSION 24
+#define MCABBER_API_MIN     24
 
 #define MCABBER_BRANCH_DEV  1
 
+// cmd_add returns gpointer id
 #define MCABBER_API_HAVE_CMD_ID 1
+// compl_new_category accepts flags argument
+#define MCABBER_API_HAVE_COMPL_FLAGS 1
 
 extern const gchar *mcabber_branch;
 extern const guint mcabber_api_version;
--- a/mcabber/mcabber/compl.c	Thu Nov 22 00:59:19 2012 +0200
+++ b/mcabber/mcabber/compl.c	Sat Oct 20 18:29:49 2012 +0300
@@ -2,7 +2,7 @@
  * compl.c      -- Completion system
  *
  * Copyright (C) 2005-2010 Mikael Berthe <mikael@lilotux.net>
- * Copyright (C) 2009,2010 Myhailo Danylenko <isbear@ukrpost.net>
+ * Copyright (C) 2009-2012 Myhailo Danylenko <isbear@ukrpost.net>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -47,82 +47,150 @@
   GSList *next;         // pointer to next completion to try
 } compl;
 
+typedef GSList *(*compl_handler_t) (void); // XXX userdata? *dynlist?
+
 // Category structure
 typedef struct {
-  guint64 flag;
+  guint flags;
   GSList *words;
+  compl_handler_t dynamic;
 } category;
 
-static GSList *Categories;
+#define COMPL_CAT_BUILTIN   0x01
+#define COMPL_CAT_ACTIVE    0x02
+#define COMPL_CAT_DYNAMIC   0x04
+#define COMPL_CAT_REVERSE   0x10
+#define COMPL_CAT_NOSORT    0x20
+
+#define COMPL_CAT_USERFLAGS 0x30
+
 static compl *InputCompl;
+static category *Categories;
+static guint num_categories;
+
+// Dynamic completions callbacks
+static GSList *compl_dyn_group (void)
+{
+  return compl_list(ROSTER_TYPE_GROUP);
+}
+
+static GSList *compl_dyn_user (void)
+{
+  return compl_list(ROSTER_TYPE_USER);
+}
 
-#ifdef MODULES_ENABLE
-static guint64 registered_cats;
+static GSList *compl_dyn_resource (void)
+{
+  return buddy_getresources_locale(NULL);
+}
 
-static inline void register_builtin_cat(guint c) {
-  registered_cats |= 1UL << (c-1);
+static GSList *compl_dyn_events (void)
+{
+  GSList *compl = evs_geteventslist();
+  GSList *cel;
+  for (cel = compl; cel; cel = cel->next)
+    cel->data = g_strdup(cel->data);
+  compl = g_slist_append(compl, g_strdup("list"));
+  return compl;
+}
+
+static inline void register_builtin_cat(guint c, compl_handler_t dynamic) {
+  Categories[c-1].flags   = COMPL_CAT_BUILTIN | COMPL_CAT_ACTIVE;
+  Categories[c-1].words   = NULL;
+  Categories[c-1].dynamic = dynamic;
+  if (dynamic != NULL) {
+    Categories[c-1].flags |= COMPL_CAT_DYNAMIC;
+  }
 }
 
 void compl_init_system(void)
 {
+  num_categories = COMPL_MAX_ID;
+#ifdef MODULES_ENABLE
+  num_categories = ((num_categories / 16) + 1) * 16;
+#endif
+  Categories = g_new0(category, num_categories);
+
   // Builtin completion categories:
-  register_builtin_cat(COMPL_CMD);
-  register_builtin_cat(COMPL_JID);
-  register_builtin_cat(COMPL_URLJID);
-  register_builtin_cat(COMPL_NAME);
-  register_builtin_cat(COMPL_STATUS);
-  register_builtin_cat(COMPL_FILENAME);
-  register_builtin_cat(COMPL_ROSTER);
-  register_builtin_cat(COMPL_BUFFER);
-  register_builtin_cat(COMPL_GROUP);
-  register_builtin_cat(COMPL_GROUPNAME);
-  register_builtin_cat(COMPL_MULTILINE);
-  register_builtin_cat(COMPL_ROOM);
-  register_builtin_cat(COMPL_RESOURCE);
-  register_builtin_cat(COMPL_AUTH);
-  register_builtin_cat(COMPL_REQUEST);
-  register_builtin_cat(COMPL_EVENTS);
-  register_builtin_cat(COMPL_EVENTSID);
-  register_builtin_cat(COMPL_PGP);
-  register_builtin_cat(COMPL_COLOR);
-  register_builtin_cat(COMPL_OTR);
-  register_builtin_cat(COMPL_OTRPOLICY);
-  register_builtin_cat(COMPL_MODULE);
+  register_builtin_cat(COMPL_CMD, NULL);
+  register_builtin_cat(COMPL_JID, compl_dyn_user);
+  register_builtin_cat(COMPL_URLJID, NULL);
+  register_builtin_cat(COMPL_NAME, NULL);
+  register_builtin_cat(COMPL_STATUS, NULL);
+  register_builtin_cat(COMPL_FILENAME, NULL);
+  register_builtin_cat(COMPL_ROSTER, NULL);
+  register_builtin_cat(COMPL_BUFFER, NULL);
+  register_builtin_cat(COMPL_GROUP, NULL);
+  register_builtin_cat(COMPL_GROUPNAME, compl_dyn_group);
+  register_builtin_cat(COMPL_MULTILINE, NULL);
+  register_builtin_cat(COMPL_ROOM, NULL);
+  register_builtin_cat(COMPL_RESOURCE, compl_dyn_resource);
+  register_builtin_cat(COMPL_AUTH, NULL);
+  register_builtin_cat(COMPL_REQUEST, NULL);
+  register_builtin_cat(COMPL_EVENTS, NULL);
+  register_builtin_cat(COMPL_EVENTSID, compl_dyn_events);
+  register_builtin_cat(COMPL_PGP, NULL);
+  register_builtin_cat(COMPL_COLOR, NULL);
+  register_builtin_cat(COMPL_OTR, NULL);
+  register_builtin_cat(COMPL_OTRPOLICY, NULL);
+  register_builtin_cat(COMPL_MODULE, NULL);
 }
 
-//  compl_new_category()
+#ifdef MODULES_ENABLE
+//  compl_new_category(flags)
 // Reserves id for new completion category.
+// Flags determine word sorting order.
 // Returns 0, if no more categories can be allocated.
-// Note, that user should not make any assumptions about id nature,
-// as it is likely to change in future.
-guint compl_new_category(void)
+guint compl_new_category(guint flags)
 {
-  const guint maxcat = 8 * sizeof (registered_cats);
-  guint i = 0;
-  while ((registered_cats >> i) & 1 && i < maxcat)
-    i++;
-  if (i >= maxcat)
-    return 0;
-  else {
-    guint64 id = 1 << i;
-    registered_cats |= id;
-    return i+1;
+  guint i;
+  for (i = 0; i < num_categories; i++)
+    if (!(Categories[i].flags & COMPL_CAT_ACTIVE))
+      break;
+  if (i >= num_categories ) {
+    guint j;
+    if (num_categories > G_MAXUINT - 16) {
+      scr_log_print(LPRINT_LOGNORM, "Warning: Too many "
+                    "completion categories!");
+      return 0;
+    }
+    num_categories += 16;
+    Categories = g_renew(category, Categories, num_categories);
+    for (j = i+1; j < num_categories; j++)
+      Categories[j].flags = 0;
   }
+  Categories[i].flags = COMPL_CAT_ACTIVE | (flags & COMPL_CAT_USERFLAGS);
+  Categories[i].words = NULL;
+  return i+1;
 }
 
 //  compl_del_category(id)
 // Frees reserved id for category.
 // Note, that for now it not validates its input, so, be careful
 // and specify exactly what you get from compl_new_category.
-void compl_del_category(guint id)
+void compl_del_category(guint compl)
 {
-  if (!id) {
-    scr_log_print(LPRINT_LOGNORM, "Error: compl_del_category() - "
-                  "Invalid category.");
+  GSList *wel;
+
+  if (!compl) {
+    scr_log_print(LPRINT_DEBUG, "Error: compl_del_category() - "
+                                "Invalid category (0).");
     return;
   }
-  id--;
-  registered_cats &= ~(1<<id);
+
+  compl--;
+
+  if ((compl >= num_categories) ||
+      (Categories[compl].flags & COMPL_CAT_BUILTIN)) {
+    scr_log_print(LPRINT_DEBUG, "Error: compl_del_category() "
+                                "Invalid category.");
+    return;
+  }
+
+  Categories[compl].flags = 0;
+  for (wel = Categories[compl].words; wel; wel = g_slist_next (wel))
+    g_free (wel -> data);
+  g_slist_free (Categories[compl].words);
 }
 #endif
 
@@ -136,12 +204,15 @@
 guint new_completion(const char *prefix, GSList *compl_cat, const gchar *suffix)
 {
   compl *c;
+  guint  ret_len = 0;
   GSList *sl_cat;
   gint (*cmp)(const char *s1, const char *s2, size_t n);
   size_t len = strlen(prefix);
 
   if (InputCompl) { // This should not happen, but hey...
-    cancel_completion();
+    scr_log_print(LPRINT_DEBUG, "Warning: new_completion() - "
+                                "Previous completion exists!");
+    done_completion();
   }
 
   if (settings_opt_get_int("completion_ignore_case"))
@@ -160,14 +231,15 @@
           compval = g_strdup_printf("%s%s", word+len, suffix);
         else
           compval = g_strdup(word+len);
-        c->list = g_slist_insert_sorted(c->list, compval,
-                                        (GCompareFunc)g_ascii_strcasecmp);
+        // for a bit of efficiency, will reverse order afterwards
+        c->list = g_slist_prepend(c->list, compval);
+        ret_len ++;
       }
     }
   }
-  c->next = c->list;
+  c->next = c->list = g_slist_reverse (c->list);
   InputCompl = c;
-  return g_slist_length(c->list);
+  return ret_len;
 }
 
 //  done_completion();
@@ -222,35 +294,46 @@
 
 /* Categories functions */
 
+static gint compl_sort_forward(gconstpointer a, gconstpointer b)
+{
+  return g_ascii_strcasecmp((const gchar *)a, (const gchar *)b);
+}
+
+static gint compl_sort_reverse(gconstpointer a, gconstpointer b)
+{
+  return -g_ascii_strcasecmp((const gchar *)a, (const gchar *)b);
+}
+
+static gint compl_sort_append(gconstpointer a, gconstpointer b)
+{
+  return 1;
+}
+
+static gint compl_sort_prepend(gconstpointer a, gconstpointer b)
+{
+  return -1;
+}
+
 //  compl_add_category_word(categ, command)
 // Adds a keyword as a possible completion in category categ.
 void compl_add_category_word(guint categ, const gchar *word)
 {
-  guint64 catv;
-  GSList *sl_cat;
-  category *cat;
   char *nword;
 
   if (!categ) {
-    scr_log_print(LPRINT_LOGNORM, "Error: compl_add_category_word() - "
-                  "Invalid category.");
+    scr_log_print(LPRINT_DEBUG, "Error: compl_add_category_word() - "
+                  "Invalid category (0).");
     return;
   }
 
   categ--;
-  catv = 1UL << categ;
 
-  // Look for category
-  for (sl_cat=Categories; sl_cat; sl_cat = g_slist_next(sl_cat)) {
-    if (catv == ((category*)sl_cat->data)->flag)
-      break;
+  if ((categ >= num_categories) ||
+      !(Categories[categ].flags & COMPL_CAT_ACTIVE)) {
+    scr_log_print(LPRINT_DEBUG, "Error: compl_add_category_word() - "
+                  "Category does not exist.");
+    return;
   }
-  if (!sl_cat) {   // Category not found, let's create it
-    cat = g_new0(category, 1);
-    cat->flag = catv;
-    Categories = g_slist_append(Categories, cat);
-  } else
-    cat = (category*)sl_cat->data;
 
   // If word is not space-terminated, we add one trailing space
   for (nword = (char*)word; *nword; nword++)
@@ -262,59 +345,64 @@
     nword = g_strdup(word);
   }
 
-  if (g_slist_find_custom(cat->words, nword, (GCompareFunc)g_strcmp0) != NULL)
-    return;
+  if (g_slist_find_custom(Categories[categ].words, nword,
+                          (GCompareFunc)g_strcmp0) == NULL) {
+    guint flags = Categories[categ].flags;
+    GCompareFunc comparator = compl_sort_forward;
+    if (flags & COMPL_CAT_NOSORT) {
+      if (flags & COMPL_CAT_REVERSE)
+        comparator = compl_sort_prepend;
+      else
+        comparator = compl_sort_append;
+    } else if (flags & COMPL_CAT_REVERSE)
+      comparator = compl_sort_reverse;
 
-  cat->words = g_slist_insert_sorted(cat->words, nword,
-                                     (GCompareFunc)g_ascii_strcasecmp);
+    Categories[categ].words = g_slist_insert_sorted
+                                  (Categories[categ].words, nword, comparator);
+  }
 }
 
 //  compl_del_category_word(categ, command)
 // Removes a keyword from category categ in completion list.
 void compl_del_category_word(guint categ, const gchar *word)
 {
-  guint64 catv;
-  GSList *sl_cat, *sl_elt;
-  category *cat;
+  GSList *wel;
   char *nword;
 
   if (!categ) {
-    scr_log_print(LPRINT_LOGNORM, "Error: compl_del_category_word() - "
-                  "Invalid category.");
+    scr_log_print(LPRINT_DEBUG, "Error: compl_del_category_word() - "
+                  "Invalid category (0).");
     return;
   }
 
   categ--;
-  catv = 1UL << categ;
 
-  // Look for category
-  for (sl_cat=Categories; sl_cat; sl_cat = g_slist_next(sl_cat)) {
-    if (catv == ((category*)sl_cat->data)->flag)
-      break;
+  if ((categ >= num_categories) ||
+      !(Categories[categ].flags & COMPL_CAT_ACTIVE)) {
+    scr_log_print(LPRINT_DEBUG, "Error: compl_del_category_word() - "
+                  "Category does not exist.");
+    return;
   }
-  if (!sl_cat) return;   // Category not found, finished!
-
-  cat = (category*)sl_cat->data;
 
   // If word is not space-terminated, we add one trailing space
   for (nword = (char*)word; *nword; nword++)
     ;
   if (nword > word) nword--;
-  if (*nword != ' ') {  // Add a space
-    nword = g_strdup_printf("%s ", word);
-  } else {              // word is fine
-    nword = g_strdup(word);
+  if (*nword != ' ')  // Add a space
+    word = nword = g_strdup_printf("%s ", word);
+  else
+    nword = NULL;
+
+  for (wel = Categories[categ].words; wel; wel = g_slist_next (wel)) {
+    if (!strcasecmp((char*)wel->data, word)) {
+      g_free(wel->data);
+      Categories[categ].words = g_slist_delete_link
+                                (Categories[categ].words, wel);
+      break; // Only remove first occurence
+    }
   }
 
-  sl_elt = cat->words;
-  while (sl_elt) {
-    if (!strcasecmp((char*)sl_elt->data, nword)) {
-      g_free(sl_elt->data);
-      cat->words = g_slist_delete_link(cat->words, sl_elt);
-      break; // Only remove first occurence
-    }
-    sl_elt = g_slist_next(sl_elt);
-  }
+  g_free (nword);
 }
 
 //  compl_get_category_list()
@@ -323,48 +411,28 @@
 // whole list after use.
 GSList *compl_get_category_list(guint categ, guint *dynlist)
 {
-  guint64 cat_flags;
-  GSList *sl_cat;
-
   if (!categ) {
-    scr_log_print(LPRINT_LOGNORM, "Error: compl_get_category_list() - "
-                  "Invalid category.");
+    scr_log_print(LPRINT_DEBUG, "Error: compl_get_category_list() - "
+                  "Invalid category (0).");
     return NULL;
   }
 
-  *dynlist = FALSE;
-  cat_flags = 1UL << (categ - 1);
-
-  // Look for the category
-  for (sl_cat=Categories; sl_cat; sl_cat = g_slist_next(sl_cat)) {
-    if (cat_flags == ((category*)sl_cat->data)->flag)
-      break;
-  }
-  if (sl_cat)       // Category was found, easy...
-    return ((category*)sl_cat->data)->words;
+  categ --;
 
-  // Handle dynamic SLists
-  *dynlist = TRUE;
-  if (categ == COMPL_GROUPNAME) {
-    return compl_list(ROSTER_TYPE_GROUP);
-  }
-  if (categ == COMPL_JID) {
-    return compl_list(ROSTER_TYPE_USER);
-  }
-  if (categ == COMPL_RESOURCE) {
-    return buddy_getresources_locale(NULL);
-  }
-  if (categ == COMPL_EVENTSID) {
-    GSList *compl = evs_geteventslist();
-    GSList *cel;
-    for (cel = compl; cel; cel = cel->next)
-      cel->data = g_strdup(cel->data);
-    compl = g_slist_append(compl, g_strdup("list"));
-    return compl;
+  if ((categ > num_categories) ||
+      !(Categories[categ].flags & COMPL_CAT_ACTIVE)) {
+    scr_log_print(LPRINT_DEBUG, "Error: compl_get_category_list() - "
+                  "Category does not exist.");
+    return NULL;
   }
 
-  *dynlist = FALSE;
-  return NULL;
+  if (Categories[categ].flags & COMPL_CAT_DYNAMIC) {
+    *dynlist = TRUE;
+    return (*Categories[categ].dynamic) ();
+  } else {
+    *dynlist = FALSE;
+    return Categories[categ].words;
+  }
 }
 
 /* vim: set expandtab cindent cinoptions=>2\:2(0 sw=2 ts=2:  For Vim users... */
--- a/mcabber/mcabber/compl.h	Thu Nov 22 00:59:19 2012 +0200
+++ b/mcabber/mcabber/compl.h	Sat Oct 20 18:29:49 2012 +0300
@@ -27,10 +27,18 @@
 #define COMPL_OTR         20
 #define COMPL_OTRPOLICY   21
 #define COMPL_MODULE      22
+/* private */
+#define COMPL_MAX_ID      22
+
+void compl_init_system(void); /* private */
 
 #ifdef MODULES_ENABLE
-void  compl_init_system(void);
-guint compl_new_category(void);
+#define COMPL_FLAGS_SORT     0x00
+#define COMPL_FLAGS_REVERSE  0x10
+#define COMPL_FLAGS_APPEND   0x20
+#define COMPL_FLAGS_PREPEND  0x30
+
+guint compl_new_category(guint flags);
 void  compl_del_category(guint id);
 #endif
 
--- a/mcabber/mcabber/main.c	Thu Nov 22 00:59:19 2012 +0200
+++ b/mcabber/mcabber/main.c	Sat Oct 20 18:29:49 2012 +0300
@@ -364,13 +364,13 @@
   }
 
   /* Initialize command system, roster and default key bindings */
+  compl_init_system();
   cmd_init();
   roster_init();
   settings_init();
   scr_init_bindings();
   caps_init();
 #ifdef MODULES_ENABLE
-  compl_init_system();
   modules_init();
 #endif
   /* Initialize charset */
--- a/mcabber/modules/beep/beep.c	Thu Nov 22 00:59:19 2012 +0200
+++ b/mcabber/modules/beep/beep.c	Sat Oct 20 18:29:49 2012 +0300
@@ -89,7 +89,7 @@
 static void beep_init(void)
 {
   /* Create completions */
-  beep_cid = compl_new_category();
+  beep_cid = compl_new_category(COMPL_FLAGS_SORT);
   if (beep_cid) {
     compl_add_category_word(beep_cid, "enable");
     compl_add_category_word(beep_cid, "disable");