You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
389 lines
11 KiB
389 lines
11 KiB
/* |
|
* 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_t; |
|
|
|
typedef GSList *(*compl_handler_t) (void); // XXX userdata? *dynlist? |
|
|
|
// Category structure |
|
typedef struct { |
|
guint flags; |
|
GSList *words; |
|
compl_handler_t dynamic; |
|
} category_t; |
|
|
|
#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_t *InputCompl; |
|
static category_t *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; |
|
Categories = g_new0(category_t, 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_CARBONS, NULL); |
|
} |
|
|
|
// 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_t *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_t, 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_t *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... */
|
|
|