--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/completion-sorting.diff Wed Oct 17 03:07:22 2012 +0300
@@ -0,0 +1,470 @@
+# HG changeset patch
+# Parent 3c7cebacae165329047f6859c26224e9c8a67807
+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
+ * [todo] Test ordering
+ * [todo] Bump API (compatibly)
+ * [todo] Reallocate array, when need more completions
+ * [todo] Design and publish interface to dynamic completion cbs
+
+diff -r 3c7cebacae16 mcabber/mcabber/compl.c
+--- a/mcabber/mcabber/compl.c Wed Oct 17 02:33:43 2012 +0300
++++ b/mcabber/mcabber/compl.c Wed Oct 17 03:00:30 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,49 +47,96 @@
+ 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;
+
+-#ifdef MODULES_ENABLE
+-static guint64 registered_cats;
++// Dynamic completions callbacks
++static GSList *compl_dyn_group (void)
++{
++ return compl_list(ROSTER_TYPE_GROUP);
++}
+
+-static inline void register_builtin_cat(guint c) {
+- registered_cats |= 1UL << (c-1);
++static GSList *compl_dyn_user (void)
++{
++ return compl_list(ROSTER_TYPE_USER);
++}
++
++static GSList *compl_dyn_resource (void)
++{
++ return buddy_getresources_locale(NULL);
++}
++
++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;
++ if (dynamic != NULL) {
++ Categories[c-1].flags |= COMPL_CAT_DYNAMIC;
++ Categories[c-1].dynamic = dynamic;
++ }
+ }
+
+ void compl_init_system(void)
+ {
++#ifdef MODULES_ENABLE
++ num_categories = 64; // XXX
++#else
++ num_categories = COMPL_MODULE;
++#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);
+ }
+
++#ifdef MODULES_ENABLE
+ // compl_new_category()
+ // Reserves id for new completion category.
+ // Returns 0, if no more categories can be allocated.
+@@ -97,32 +144,68 @@
+ // as it is likely to change in future.
+ guint compl_new_category(void)
+ {
+- 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;
++ for (; i < num_categories; i++) {
++ if (!(Categories[i].flags & COMPL_CAT_ACTIVE)) {
++ Categories[i].flags = COMPL_CAT_ACTIVE;
++ Categories[i].words = NULL;
++ return i+1;
++ }
+ }
++ // XXX realloc
++ scr_log_print(LPRINT_LOGNORM, "Warning: run out of completion categories!");
++ return 0;
+ }
+
+ // 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) {
++ if (!compl) {
+ scr_log_print(LPRINT_LOGNORM, "Error: compl_del_category() - "
+- "Invalid category.");
++ "Invalid category.");
+ 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;
++ // XXX free words
++}
++
++// compl_set_sorting_order(category,order)
++// Sets sorting order for given category.
++// In future can be merged with new_category, set more flags,
++// maybe even set dynamic callback.
++void compl_set_flags(guint compl, guint new_flags)
++{
++ if (!compl) {
++ scr_log_print(LPRINT_LOGNORM, "Error: compl_set_flags() - "
++ "Invalid category.");
++ return;
++ }
++ compl--;
++ if (compl < num_categories) {
++ guint flags = Categories[compl].flags;
++ if (flags & COMPL_CAT_BUILTIN)
++ scr_log_print(LPRINT_DEBUG, "Error: compl_set_flags() - "
++ "Invalid category.");
++ else if (!(flags & COMPL_CAT_ACTIVE))
++ scr_log_print(LPRINT_DEBUG, "Error: compl_set_flags() - "
++ "Invalid category.");
++ else
++ Categories[compl].flags = (flags & ~COMPL_CAT_USERFLAGS)
++ | ((new_flags << 2) & COMPL_CAT_USERFLAGS);
++ } else
++ scr_log_print(LPRINT_DEBUG, "Error: compl_set_flags() - "
++ "Invalid category.");
+ }
+ #endif
+
+@@ -222,13 +305,30 @@
+
+ /* 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; // XXX
++}
++
++static gint compl_sort_prepend(gconstpointer a, gconstpointer b)
++{
++ return -1; // XXX
++}
++
+ // 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) {
+@@ -236,21 +336,15 @@
+ "Invalid category.");
+ 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_LOGNORM, "Error: compl_add_category_word() - "
++ "Invalid category.");
++ 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,20 +356,28 @@
+ 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 *sl_elt;
+ char *nword;
+
+ if (!categ) {
+@@ -285,16 +387,13 @@
+ }
+
+ 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_LOGNORM, "Error: compl_del_category_word() - "
++ "Invalid category.");
++ 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++)
+@@ -306,11 +405,12 @@
+ nword = g_strdup(word);
+ }
+
+- sl_elt = cat->words;
++ sl_elt = Categories[categ].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);
++ Categories[categ].words = g_slist_delete_link
++ (Categories[categ].words, sl_elt);
+ break; // Only remove first occurence
+ }
+ sl_elt = g_slist_next(sl_elt);
+@@ -323,48 +423,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.");
+ return NULL;
+ }
+
+- *dynlist = FALSE;
+- cat_flags = 1UL << (categ - 1);
++ categ --;
+
+- // 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;
+-
+- // 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_LOGNORM, "Error: compl_get_category_list() - "
++ "Invalid category.");
++ 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... */
+diff -r 3c7cebacae16 mcabber/mcabber/compl.h
+--- a/mcabber/mcabber/compl.h Wed Oct 17 02:33:43 2012 +0300
++++ b/mcabber/mcabber/compl.h Wed Oct 17 03:00:30 2012 +0300
+@@ -28,10 +28,17 @@
+ #define COMPL_OTRPOLICY 21
+ #define COMPL_MODULE 22
+
++void compl_init_system(void); /* private */
++
+ #ifdef MODULES_ENABLE
+-void compl_init_system(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(void);
+ void compl_del_category(guint id);
++void compl_set_flags(guint id, guint flags);
+ #endif
+
+ void compl_add_category_word(guint categ, const gchar *command);
+diff -r 3c7cebacae16 mcabber/mcabber/main.c
+--- a/mcabber/mcabber/main.c Wed Oct 17 02:33:43 2012 +0300
++++ b/mcabber/mcabber/main.c Wed Oct 17 03:00:30 2012 +0300
+@@ -368,13 +368,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 */