Mercurial > hg
view mcabber/mcabber/compl.c @ 2290:71ec41732035
Fix small memory leak
'servername' was not freed (and jidtodisp() was unnecessary.
(Spotted by franky)
author | Mikael Berthe <mikael@lilotux.net> |
---|---|
date | Wed, 21 Dec 2016 20:43:58 +0100 |
parents | f5402d705f67 |
children | ffd0e57e9563 |
line wrap: on
line source
/* * compl.c -- Completion system * * Copyright (C) 2005-2014 Mikael Berthe <mikael@lilotux.net> * Copyright (C) 2009-2014 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 * the Free Software Foundation; either version 2 of the License, or (at * your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see <http://www.gnu.org/licenses/>. */ /* Usage, basically: * - new_completion(); // 1. Initialization * - complete(); // 2. 1st completion * - cancel_completion(); // 3a. 2nd completion / cancel previous * - complete(); // 3b. 2nd completion / complete * ... * - done_completion(); // n. finished -- free allocated areas * */ #include <string.h> #include "compl.h" #include "utf8.h" #include "roster.h" #include "events.h" #include "settings.h" #include "logprint.h" // Completion structure typedef struct { GList *list; // list of matches guint len_prefix; // length of text already typed by the user guint len_compl; // length of the last completion GList *next; // pointer to next completion to try } compl; typedef GSList *(*compl_handler_t) (void); // XXX userdata? *dynlist? // Category structure typedef struct { guint flags; GSList *words; compl_handler_t dynamic; } category; #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); } 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; 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, 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); register_builtin_cat(COMPL_CARBONS, NULL); } #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. guint compl_new_category(guint flags) { 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 compl) { GSList *wel; if (!compl) { scr_log_print(LPRINT_DEBUG, "Error: compl_del_category() - " "Invalid category (0)."); return; } 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 // new_completion(prefix, compl_cat, suffix) // . prefix = beginning of the word, typed by the user // . compl_cat = pointer to a completion category list (list of *char) // . suffix = string to append to all completion possibilities (i.e. ":") // Set the InputCompl pointer to an allocated compl structure. // done_completion() must be called when finished. // Returns the number of possible completions. 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... scr_log_print(LPRINT_DEBUG, "Warning: new_completion() - " "Previous completion exists!"); done_completion(); } if (settings_opt_get_int("completion_ignore_case")) cmp = &strncasecmp; else cmp = &strncmp; c = g_new0(compl, 1); // Build the list of matches for (sl_cat = compl_cat; sl_cat; sl_cat = g_slist_next(sl_cat)) { char *word = sl_cat->data; if (!cmp(prefix, word, len)) { if (strlen(word) != len) { gchar *compval; if (suffix) compval = g_strdup_printf("%s%s", word+len, suffix); else compval = g_strdup(word+len); // for a bit of efficiency, will reverse order afterwards c->list = g_list_prepend(c->list, compval); ret_len ++; } } } c->list = g_list_reverse(c->list); c->next = NULL; InputCompl = c; return ret_len; } // done_completion(); void done_completion(void) { GList *clp; if (!InputCompl) return; // Free the current completion list for (clp = InputCompl->list; clp; clp = g_list_next(clp)) g_free(clp->data); g_list_free(InputCompl->list); g_free(InputCompl); InputCompl = NULL; } // cancel_completion() // Returns the number of chars to delete to cancel the completion guint cancel_completion(void) { if (!InputCompl) return 0; return InputCompl->len_compl; } // Returns pointer to text to insert, NULL if no completion. const char *complete(gboolean fwd) { compl* c = InputCompl; char *r; if (!InputCompl) return NULL; if (!c->next) { if (fwd) c->next = c->list; // back to the beginning else c->next = g_list_last(c->list); // back to the ending } else { if (fwd) c->next = g_list_next(c->next); else c->next = g_list_previous(c->next); } if (!c->next) { c->next = NULL; c->len_compl = 0; return NULL; } r = (char*)c->next->data; if (!utf8_mode) { c->len_compl = strlen(r); } else { char *wc; c->len_compl = 0; for (wc = r; *wc; wc = next_char(wc)) c->len_compl++; } return r; } /* 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) { char *nword; if (!categ) { scr_log_print(LPRINT_DEBUG, "Error: compl_add_category_word() - " "Invalid category (0)."); return; } categ--; 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 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 (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; 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) { GSList *wel; char *nword; if (!categ) { scr_log_print(LPRINT_DEBUG, "Error: compl_del_category_word() - " "Invalid category (0)."); return; } categ--; 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 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 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 } } g_free (nword); } // compl_get_category_list() // Returns a slist of all words in the specified categorie. // Iff this function sets *dynlist to TRUE, then the caller must free the // whole list after use. GSList *compl_get_category_list(guint categ, guint *dynlist) { if (!categ) { scr_log_print(LPRINT_DEBUG, "Error: compl_get_category_list() - " "Invalid category (0)."); return NULL; } categ --; 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; } 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... */