diff --git a/src/fe-common/core/Makefile.am b/src/fe-common/core/Makefile.am index c8e24dad..093334ed 100644 --- a/src/fe-common/core/Makefile.am +++ b/src/fe-common/core/Makefile.am @@ -9,6 +9,7 @@ INCLUDES = \ libfe_common_core_la_SOURCES = \ autorun.c \ command-history.c \ + completion.c \ fe-common-core.c \ fe-core-commands.c \ fe-log.c \ @@ -27,6 +28,7 @@ libfe_common_core_la_SOURCES = \ noinst_HEADERS = \ command-history.h \ + completion.h \ fe-common-core.h \ hilight-text.h \ keyboard.h \ diff --git a/src/fe-common/core/completion.h b/src/fe-common/core/completion.h new file mode 100644 index 00000000..45ac1563 --- /dev/null +++ b/src/fe-common/core/completion.h @@ -0,0 +1,14 @@ +#ifndef __COMPLETION_H +#define __COMPLETION_H + +#include "window-items.h" + +/* automatic word completion - called when space/enter is pressed */ +char *auto_word_complete(const char *line, int *pos); +/* manual word completion - called when TAB is pressed */ +char *word_complete(WINDOW_REC *window, const char *line, int *pos); + +void completion_init(void); +void completion_deinit(void); + +#endif diff --git a/src/fe-common/core/fe-common-core.c b/src/fe-common/core/fe-common-core.c index 1fc1fbaf..a402e6ae 100644 --- a/src/fe-common/core/fe-common-core.c +++ b/src/fe-common/core/fe-common-core.c @@ -24,6 +24,7 @@ #include "hilight-text.h" #include "command-history.h" +#include "completion.h" #include "keyboard.h" #include "printtext.h" #include "themes.h" @@ -82,6 +83,7 @@ void fe_common_core_init(void) autorun_init(); hilight_text_init(); command_history_init(); + completion_init(); keyboard_init(); printtext_init(); fe_log_init(); @@ -100,6 +102,7 @@ void fe_common_core_deinit(void) autorun_deinit(); hilight_text_deinit(); command_history_deinit(); + completion_deinit(); keyboard_deinit(); printtext_deinit(); fe_log_deinit(); diff --git a/src/fe-common/irc/Makefile.am b/src/fe-common/irc/Makefile.am index ce3b1f5d..182cb9b0 100644 --- a/src/fe-common/irc/Makefile.am +++ b/src/fe-common/irc/Makefile.am @@ -12,7 +12,6 @@ INCLUDES = \ -DSYSCONFDIR=\""$(sysconfdir)"\" libfe_common_irc_la_SOURCES = \ - completion.c \ fe-channels.c \ fe-irc-commands.c \ fe-irc-server.c \ @@ -24,13 +23,13 @@ libfe_common_irc_la_SOURCES = \ fe-netsplit.c \ fe-query.c \ fe-common-irc.c \ + irc-completion.c \ irc-window-activity.c \ irc-hilight-text.c \ irc-modules.c \ module-formats.c noinst_HEADERS = \ - completion.h \ fe-common-irc.h \ fe-query.h \ irc-hilight-text.h \ diff --git a/src/fe-common/irc/completion.c b/src/fe-common/irc/completion.c deleted file mode 100644 index 637ad7fd..00000000 --- a/src/fe-common/irc/completion.c +++ /dev/null @@ -1,731 +0,0 @@ -/* - completion.c : irssi - - Copyright (C) 1999-2000 Timo Sirainen - - 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, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#include "module.h" -#include "signals.h" -#include "commands.h" -#include "misc.h" -#include "lib-config/iconfig.h" -#include "settings.h" - -#include "irc.h" -#include "server.h" -#include "channels.h" -#include "nicklist.h" - -#include "completion.h" -#include "window-items.h" - -typedef struct { - time_t time; - char *nick; -} COMPLETION_REC; - -#define replace_find(replace) \ - iconfig_list_find("replaces", "text", replace, "replace") - -#define completion_find(completion) \ - iconfig_list_find("completions", "short", completion, "long") - -static gint comptag; -static GList *complist; - -static COMPLETION_REC *nick_completion_find(GSList *list, gchar *nick) -{ - GSList *tmp; - - for (tmp = list; tmp != NULL; tmp = tmp->next) - { - COMPLETION_REC *rec = tmp->data; - - if (g_strcasecmp(rec->nick, nick) == 0) return rec; - } - - return NULL; -} - -static void completion_destroy(GSList **list, COMPLETION_REC *rec) -{ - *list = g_slist_remove(*list, rec); - - g_free(rec->nick); - g_free(rec); -} - -static COMPLETION_REC *nick_completion_create(GSList **list, time_t time, gchar *nick) -{ - COMPLETION_REC *rec; - - rec = nick_completion_find(*list, nick); - if (rec != NULL) - { - /* remove the old one */ - completion_destroy(list, rec); - } - - rec = g_new(COMPLETION_REC, 1); - *list = g_slist_prepend(*list, rec); - - rec->time = time; - rec->nick = g_strdup(nick); - return rec; -} - -static void completion_checklist(GSList **list, gint timeout, time_t t) -{ - GSList *tmp, *next; - - for (tmp = *list; tmp != NULL; tmp = next) - { - COMPLETION_REC *rec = tmp->data; - - next = tmp->next; - if (t-rec->time > timeout) - completion_destroy(list, rec); - } -} - -static gint completion_timeout(void) -{ - GSList *tmp, *link; - time_t t; - gint len; - - t = time(NULL); - for (tmp = servers; tmp != NULL; tmp = tmp->next) - { - IRC_SERVER_REC *rec = tmp->data; - - len = g_slist_length(rec->lastmsgs); - if (len > 0 && len >= settings_get_int("completion_keep_privates")) - { - link = g_slist_last(rec->lastmsgs); - g_free(link->data); - rec->lastmsgs = g_slist_remove_link(rec->lastmsgs, link); - g_slist_free_1(link); - } - } - - for (tmp = channels; tmp != NULL; tmp = tmp->next) - { - CHANNEL_REC *rec = tmp->data; - - completion_checklist(&rec->lastownmsgs, settings_get_int("completion_keep_ownpublics"), t); - completion_checklist(&rec->lastmsgs, settings_get_int("completion_keep_publics"), t); - } - - return 1; -} - -static void add_private_msg(IRC_SERVER_REC *server, gchar *nick) -{ - GSList *link; - - link = gslist_find_icase_string(server->lastmsgs, nick); - if (link != NULL) - { - g_free(link->data); - server->lastmsgs = g_slist_remove_link(server->lastmsgs, link); - g_slist_free_1(link); - } - server->lastmsgs = g_slist_prepend(server->lastmsgs, g_strdup(nick)); -} - -static void event_privmsg(gchar *data, IRC_SERVER_REC *server, gchar *nick) -{ - gchar *params, *target, *msg; - GSList **list; - - g_return_if_fail(server != NULL); - if (nick == NULL) return; /* from server */ - - params = event_get_params(data, 2 | PARAM_FLAG_GETREST, &target, &msg); - - if (ischannel(*target)) - { - /* channel message */ - CHANNEL_REC *channel; - - channel = channel_find(server, target); - if (channel == NULL) - { - g_free(params); - return; - } - - list = irc_nick_match(server->nick, msg) ? - &channel->lastownmsgs : - &channel->lastmsgs; - nick_completion_create(list, time(NULL), nick); - } - else - { - /* private message */ - add_private_msg(server, nick); - } - - g_free(params); -} - -static void cmd_msg(gchar *data, IRC_SERVER_REC *server) -{ - gchar *params, *target, *msg; - - g_return_if_fail(data != NULL); - - params = cmd_get_params(data, 2 | PARAM_FLAG_GETREST, &target, &msg); - if (*target != '\0' && *msg != '\0') - { - if (!ischannel(*target) && *target != '=' && server != NULL) - add_private_msg(server, target); - } - - g_free(params); -} - -static void complete_list(GList **outlist, GSList *list, gchar *nick) -{ - GSList *tmp; - gint len; - - len = strlen(nick); - for (tmp = list; tmp != NULL; tmp = tmp->next) - { - COMPLETION_REC *rec = tmp->data; - - if (g_strncasecmp(rec->nick, nick, len) == 0 && - glist_find_icase_string(*outlist, rec->nick) == NULL) - *outlist = g_list_append(*outlist, g_strdup(rec->nick)); - } -} - -static GList *completion_getlist(CHANNEL_REC *channel, gchar *nick) -{ - GSList *nicks, *tmp; - GList *list; - gint len; - - g_return_val_if_fail(channel != NULL, NULL); - g_return_val_if_fail(nick != NULL, NULL); - if (*nick == '\0') return NULL; - - list = NULL; - complete_list(&list, channel->lastownmsgs, nick); - complete_list(&list, channel->lastmsgs, nick); - - len = strlen(nick); - nicks = nicklist_getnicks(channel); - for (tmp = nicks; tmp != NULL; tmp = tmp->next) - { - NICK_REC *rec = tmp->data; - - if (g_strncasecmp(rec->nick, nick, len) == 0 && - glist_find_icase_string(list, rec->nick) == NULL && - g_strcasecmp(rec->nick, channel->server->nick) != 0) - list = g_list_append(list, g_strdup(rec->nick)); - } - g_slist_free(nicks); - - return list; -} - -static GList *completion_getmsglist(IRC_SERVER_REC *server, gchar *nick) -{ - GSList *tmp; - GList *list; - gint len; - - list = NULL; len = strlen(nick); - for (tmp = server->lastmsgs; tmp != NULL; tmp = tmp->next) - { - if (len == 0 || g_strncasecmp(tmp->data, nick, len) == 0) - list = g_list_append(list, g_strdup(tmp->data)); - } - - return list; -} - -/* expand \n, \t and \\ */ -static char *expand_escapes(const char *line, IRC_SERVER_REC *server, WI_IRC_REC *item) -{ - char *ptr, *ret; - - ret = ptr = g_malloc(strlen(line)+1); - while (*line != '\0') { - if (*line != '\\') - *ptr++ = *line; - else { - line++; - if (*line == '\0') { - *ptr++ = '\\'; - break; - } - - switch (*line) { - case 'n': - /* newline .. we need to send another "send text" event to handle it (or actually the text before the newline..) */ - *ptr = '\0'; - signal_emit("send text", 3, line, server, item); - ptr = ret; - break; - case 't': - *ptr++ = '\t'; - break; - case '\\': - *ptr++ = '\\'; - break; - default: - *ptr++ = '\\'; - *ptr++ = *line; - break; - } - } - line++; - } - - *ptr = '\0'; - return ret; -} - -static void event_text(gchar *line, IRC_SERVER_REC *server, WI_IRC_REC *item) -{ - CHANNEL_REC *channel; - GList *comp; - gchar *str, *ptr; - - g_return_if_fail(line != NULL); - - if (!irc_item_check(item)) - return; - - line = settings_get_bool("expand_escapes") ? - expand_escapes(line, server, item) : g_strdup(line); - - /* check for nick completion */ - if (settings_get_bool("completion_disable_auto") || *settings_get_str("completion_char") == '\0') - { - ptr = NULL; - comp = NULL; - } - else - { - ptr = strchr(line, *settings_get_str("completion_char")); - if (ptr != NULL) *ptr++ = '\0'; - - channel = irc_item_channel(item); - - comp = ptr == NULL || channel == NULL || - nicklist_find(channel, line) != NULL ? NULL : - completion_getlist(channel, line); - } - - /* message to channel */ - if (ptr == NULL) - str = g_strdup_printf("%s %s", item->name, line); - else - { - str = g_strdup_printf("%s %s%s%s", item->name, - comp != NULL ? (gchar *) comp->data : line, - settings_get_str("completion_char"), ptr); - } - signal_emit("command msg", 3, str, server, item); - - g_free(str); - g_free(line); - - if (comp != NULL) - { - g_list_foreach(comp, (GFunc) g_free, NULL); - g_list_free(comp); - } - - signal_stop(); -} - -static GList *completion_joinlist(GList *list1, GList *list2) -{ - while (list2 != NULL) - { - if (!glist_find_icase_string(list1, list2->data)) - list1 = g_list_append(list1, list2->data); - else - g_free(list2->data); - - list2 = list2->next; - } - g_list_free(list2); - return list1; -} - -static GList *free_list_if_old(GList *list, const char *word) -{ - int len; - - len = strlen(word); - if (list != NULL && (strlen(list->data) != len || g_strncasecmp(word, list->data, len) != 0)) { - list = g_list_first(list); - g_list_foreach(list, (GFunc) g_free, NULL); - g_list_free(list); - list = NULL; - } - return list; -} - -static GList *completion_commands(GList *list, const char *word) -{ - GSList *tmp; - - for (tmp = commands; tmp != NULL; tmp = tmp->next) { - COMMAND_REC *rec = tmp->data; - if (rec == NULL) continue; - - if (g_strncasecmp(rec->cmd, word, strlen(word)) == 0) { - list = g_list_append(list, g_strdup(rec->cmd)); - } - } - return list; -} - -static GList *completion_variables(GList *list, const char *word) -{ - GSList *sets, *tmp; - - sets = settings_get_sorted(); - - for (tmp = sets; tmp != NULL; tmp = tmp->next) { - SETTINGS_REC *rec = tmp->data; - if (rec == NULL) continue; - - if (g_strncasecmp(rec->key, word, strlen(word)) == 0) { - list = g_list_append(list, g_strdup(rec->key)); - } - } - g_slist_free(sets); - return list; -} - -char *auto_completion(const char *line, int *pos) -{ - const char *replace; - gchar *word, *ret; - gint spos, epos, n, wordpos; - GString *result; - - g_return_val_if_fail(line != NULL, NULL); - g_return_val_if_fail(pos != NULL, NULL); - - spos = *pos; - - /* get the word we are completing.. */ - while (spos > 0 && isspace((gint) line[spos-1])) spos--; - epos = spos; - while (spos > 0 && !isspace((gint) line[spos-1])) spos--; - while (line[epos] != '\0' && !isspace((gint) line[epos])) epos++; - - word = g_strdup(line+spos); - word[epos-spos] = '\0'; - - /* word position in line */ - wordpos = 0; - for (n = 0; n < spos; ) - { - while (n < spos && isspace((gint) line[n])) n++; - while (n < spos && !isspace((gint) line[n])) n++; - if (n < spos) wordpos++; - } - - result = g_string_new(line); - g_string_erase(result, spos, epos-spos); - - /* check for words in autocompletion list */ - replace = replace_find(word); g_free(word); - if (replace != NULL) - { - *pos = spos+strlen(replace); - - g_string_insert(result, spos, replace); - ret = result->str; - g_string_free(result, FALSE); - return ret; - } - - g_string_free(result, TRUE); - return NULL; -} - -#define issplit(a) ((a) == ',' || (a) == ' ') - -char *completion_line(WINDOW_REC *window, const char *line, int *pos) -{ - static gboolean msgcomp = FALSE; - const char *completion; - CHANNEL_REC *channel; - SERVER_REC *server; - gchar *word, *ret; - gint spos, epos, len, n, wordpos; - gboolean msgcompletion; - GString *result; - - g_return_val_if_fail(window != NULL, NULL); - g_return_val_if_fail(line != NULL, NULL); - g_return_val_if_fail(pos != NULL, NULL); - - spos = *pos; - - /* get the word we are completing.. */ - while (spos > 0 && issplit((gint) line[spos-1])) spos--; - epos = spos; - if (line[epos] == ',') epos++; - while (spos > 0 && !issplit((gint) line[spos-1])) spos--; - while (line[epos] != '\0' && !issplit((gint) line[epos])) epos++; - - word = g_strdup(line+spos); - word[epos-spos] = '\0'; - - /* word position in line */ - wordpos = 0; - for (n = 0; n < spos; ) - { - while (n < spos && issplit((gint) line[n])) n++; - while (n < spos && !issplit((gint) line[n])) n++; - if (n < spos) wordpos++; - } - - completion = NULL; - - if (wordpos == 0 && line[0] == '/') { - /* we are completing a command, skip the slash */ - spos++; - - complist = free_list_if_old(complist, word+1); - if (complist == NULL) { - complist = completion_commands(complist, word+1); - } else { - complist = complist->next == NULL ? g_list_first(complist) : complist->next; - } - - g_free(word); - if (complist == NULL) return NULL; - - completion = complist->data; - } - - if (wordpos == 1 && g_strncasecmp(line, "/set ", 5) == 0) { - /* we are completing a variable */ - complist = free_list_if_old(complist, word); - if (complist == NULL) { - complist = completion_variables(complist, word); - } else { - complist = complist->next == NULL ? g_list_first(complist) : complist->next; - } - - g_free(word); - if (complist == NULL) return NULL; - - completion = complist->data; - } - - if (completion != NULL) { - result = g_string_new(line); - g_string_erase(result, spos, epos-spos); - - *pos = spos+strlen(completion); - g_string_insert(result, spos, completion); - ret = result->str; - g_string_free(result, FALSE); - return ret; - } - - server = window->active == NULL ? window->active_server : window->active->server; - msgcompletion = server != NULL && - (*line == '\0' || ((wordpos == 0 || wordpos == 1) && g_strncasecmp(line, "/msg ", 5) == 0)); - - if (msgcompletion && wordpos == 0 && issplit((gint) line[epos])) - { - /* /msg */ - *word = '\0'; epos++; spos = epos; wordpos = 1; - } - - /* are we completing the same nick as last time? - if not, forget the old completion.. */ - len = strlen(word)-(msgcomp == FALSE && word[strlen(word)-1] == *settings_get_str("completion_char")); - if (complist != NULL && (strlen(complist->data) != len || g_strncasecmp(complist->data, word, len) != 0)) - { - complist = g_list_first(complist); - g_list_foreach(complist, (GFunc) g_free, NULL); - g_list_free(complist); - - complist = NULL; - } - - result = g_string_new(line); - g_string_erase(result, spos, epos-spos); - - /* check for words in completion list */ - completion = completion_find(word); - if (completion != NULL) - { - g_free(word); - *pos = spos+strlen(completion); - - g_string_insert(result, spos, completion); - ret = result->str; - g_string_free(result, FALSE); - return ret; - } - - channel = irc_item_channel(window->active); - if (complist == NULL && !msgcompletion && channel == NULL) - { - /* don't try nick completion */ - g_free(word); - g_string_free(result, TRUE); - return NULL; - } - - if (complist == NULL) - { - /* start new nick completion */ - complist = channel == NULL ? NULL : completion_getlist(channel, word); - - if (!msgcompletion) - { - /* nick completion in channel */ - msgcomp = FALSE; - } - else - { - GList *tmpcomp; - - /* /msg completion */ - msgcomp = TRUE; - - /* first get the list of msg nicks and then nicks from current - channel. */ - tmpcomp = complist; - complist = completion_getmsglist((IRC_SERVER_REC *) server, word); - complist = completion_joinlist(complist, tmpcomp); - if (*line == '\0') - { - /* completion in empty line -> /msg */ - g_free(word); - g_string_free(result, TRUE); - - if (complist == NULL) - ret = g_strdup("/msg "); - else - ret = g_strdup_printf("/msg %s ", (gchar *) complist->data); - *pos = strlen(ret); - return ret; - } - } - - if (complist == NULL) - { - g_free(word); - g_string_free(result, TRUE); - return NULL; - } - } - else - { - /* continue the last completion */ - complist = complist->next == NULL ? g_list_first(complist) : complist->next; - } - g_free(word); - - /* insert the nick.. */ - g_string_insert(result, spos, complist->data); - *pos = spos+strlen(complist->data); - - if (!msgcomp && wordpos == 0) - { - /* insert completion character */ - g_string_insert(result, *pos, settings_get_str("completion_char")); - *pos += strlen(settings_get_str("completion_char")); - } - if (msgcomp || wordpos == 0) - { - if (!issplit((gint) result->str[*pos])) - { - /* insert space */ - g_string_insert(result, *pos, " "); - } - (*pos)++; - } - - ret = result->str; - g_string_free(result, FALSE); - return ret; -} - -static void completion_deinit_server(IRC_SERVER_REC *server) -{ - g_return_if_fail(server != NULL); - - g_slist_foreach(server->lastmsgs, (GFunc) g_free, NULL); - g_slist_free(server->lastmsgs); -} - -static void completion_deinit_channel(CHANNEL_REC *channel) -{ - g_return_if_fail(channel != NULL); - - while (channel->lastmsgs != NULL) - completion_destroy(&channel->lastmsgs, channel->lastmsgs->data); - while (channel->lastownmsgs != NULL) - completion_destroy(&channel->lastownmsgs, channel->lastownmsgs->data); - g_slist_free(channel->lastmsgs); - g_slist_free(channel->lastownmsgs); -} - -void completion_init(void) -{ - settings_add_str("completion", "completion_char", ":"); - settings_add_bool("completion", "completion_disable_auto", FALSE); - settings_add_int("completion", "completion_keep_publics", 180); - settings_add_int("completion", "completion_keep_ownpublics", 360); - settings_add_int("completion", "completion_keep_privates", 10); - settings_add_bool("completion", "expand_escapes", FALSE); - - signal_add("event privmsg", (SIGNAL_FUNC) event_privmsg); - signal_add("send text", (SIGNAL_FUNC) event_text); - signal_add("server disconnected", (SIGNAL_FUNC) completion_deinit_server); - signal_add("channel destroyed", (SIGNAL_FUNC) completion_deinit_channel); - command_bind("msg", NULL, (SIGNAL_FUNC) cmd_msg); - - comptag = g_timeout_add(1000, (GSourceFunc) completion_timeout, NULL); - complist = NULL; -} - -void completion_deinit(void) -{ - complist = g_list_first(complist); - g_list_foreach(complist, (GFunc) g_free, NULL); - g_list_free(complist); - - g_source_remove(comptag); - - signal_remove("event privmsg", (SIGNAL_FUNC) event_privmsg); - signal_remove("send text", (SIGNAL_FUNC) event_text); - signal_remove("server disconnected", (SIGNAL_FUNC) completion_deinit_server); - signal_remove("channel destroyed", (SIGNAL_FUNC) completion_deinit_channel); - command_unbind("msg", (SIGNAL_FUNC) cmd_msg); -} diff --git a/src/fe-common/irc/completion.h b/src/fe-common/irc/completion.h deleted file mode 100644 index e7dfd076..00000000 --- a/src/fe-common/irc/completion.h +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef __COMPLETION_H -#define __COMPLETION_H - -#include "window-items.h" - -char *completion_line(WINDOW_REC *window, const char *line, int *pos); -char *auto_completion(const char *line, int *pos); - -void completion_init(void); -void completion_deinit(void); - -#endif diff --git a/src/fe-common/irc/fe-common-irc.c b/src/fe-common/irc/fe-common-irc.c index 7125dab5..83f20899 100644 --- a/src/fe-common/irc/fe-common-irc.c +++ b/src/fe-common/irc/fe-common-irc.c @@ -64,6 +64,9 @@ void fe_query_deinit(void); void irc_window_activity_init(void); void irc_window_activity_deinit(void); +void irc_completion_init(void); +void irc_completion_deinit(void); + void fe_netsplit_init(void); void fe_netsplit_deinit(void); @@ -111,7 +114,7 @@ void fe_common_irc_init(void) fe_ignore_init(); fe_netsplit_init(); fe_query_init(); - completion_init(); + irc_completion_init(); irc_window_activity_init(); fe_irc_modules_init(); @@ -131,7 +134,7 @@ void fe_common_irc_deinit(void) fe_ignore_deinit(); fe_netsplit_deinit(); fe_query_deinit(); - completion_deinit(); + irc_completion_deinit(); irc_window_activity_deinit(); theme_unregister(); diff --git a/src/fe-common/irc/irc-completion.c b/src/fe-common/irc/irc-completion.c new file mode 100644 index 00000000..8422c981 --- /dev/null +++ b/src/fe-common/irc/irc-completion.c @@ -0,0 +1,507 @@ +/* + completion.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" +#include "commands.h" +#include "misc.h" +#include "lib-config/iconfig.h" +#include "settings.h" + +#include "irc.h" +#include "server.h" +#include "channels.h" +#include "nicklist.h" + +#include "completion.h" +#include "window-items.h" + +static int complete_tag; + +typedef struct { + time_t time; + char *nick; +} NICK_COMPLETION_REC; + +static void nick_completion_destroy(GSList **list, NICK_COMPLETION_REC *rec) +{ + *list = g_slist_remove(*list, rec); + + g_free(rec->nick); + g_free(rec); +} + +static void nick_completion_remove_old(GSList **list, int timeout, time_t now) +{ + GSList *tmp, *next; + + for (tmp = *list; tmp != NULL; tmp = next) { + NICK_COMPLETION_REC *rec = tmp->data; + + next = tmp->next; + if (now-rec->time > timeout) + nick_completion_destroy(list, rec); + } +} + +static int nick_completion_timeout(void) +{ + GSList *tmp, *link; + time_t now; + int len; + + now = time(NULL); + for (tmp = servers; tmp != NULL; tmp = tmp->next) { + IRC_SERVER_REC *rec = tmp->data; + + len = g_slist_length(rec->lastmsgs); + if (len > 0 && len >= settings_get_int("completion_keep_privates")) { + link = g_slist_last(rec->lastmsgs); + g_free(link->data); + rec->lastmsgs = g_slist_remove(rec->lastmsgs, link->data); + } + } + + for (tmp = channels; tmp != NULL; tmp = tmp->next) { + CHANNEL_REC *rec = tmp->data; + + nick_completion_remove_old(&rec->lastownmsgs, settings_get_int("completion_keep_ownpublics"), now); + nick_completion_remove_old(&rec->lastmsgs, settings_get_int("completion_keep_publics"), now); + } + + return 1; +} + +static NICK_COMPLETION_REC *nick_completion_find(GSList *list, const char *nick) +{ + GSList *tmp; + + for (tmp = list; tmp != NULL; tmp = tmp->next) { + NICK_COMPLETION_REC *rec = tmp->data; + + if (g_strcasecmp(rec->nick, nick) == 0) + return rec; + } + + return NULL; +} + +static NICK_COMPLETION_REC *nick_completion_create(GSList **list, time_t time, const char *nick) +{ + NICK_COMPLETION_REC *rec; + + rec = nick_completion_find(*list, nick); + if (rec != NULL) { + /* remove the old one */ + nick_completion_destroy(list, rec); + } + + rec = g_new(NICK_COMPLETION_REC, 1); + *list = g_slist_prepend(*list, rec); + + rec->time = time; + rec->nick = g_strdup(nick); + return rec; +} + +static void add_private_msg(IRC_SERVER_REC *server, const char *nick) +{ + GSList *link; + + link = gslist_find_icase_string(server->lastmsgs, nick); + if (link != NULL) { + g_free(link->data); + server->lastmsgs = g_slist_remove(server->lastmsgs, link->data); + } + server->lastmsgs = g_slist_prepend(server->lastmsgs, g_strdup(nick)); +} + +static void event_privmsg(const char *data, IRC_SERVER_REC *server, const char *nick) +{ + char *params, *target, *msg; + GSList **list; + + g_return_if_fail(server != NULL); + if (nick == NULL) return; /* from server */ + + params = event_get_params(data, 2 | PARAM_FLAG_GETREST, &target, &msg); + + if (ischannel(*target)) { + /* channel message */ + CHANNEL_REC *channel; + + channel = channel_find(server, target); + if (channel == NULL) { + g_free(params); + return; + } + + list = irc_nick_match(server->nick, msg) ? + &channel->lastownmsgs : + &channel->lastmsgs; + nick_completion_create(list, time(NULL), nick); + } else { + /* private message */ + add_private_msg(server, nick); + } + + g_free(params); +} + +static void cmd_msg(const char *data, IRC_SERVER_REC *server) +{ + char *params, *target, *msg; + + g_return_if_fail(data != NULL); + + params = cmd_get_params(data, 2 | PARAM_FLAG_GETREST, &target, &msg); + if (*target != '\0' && *msg != '\0') { + if (!ischannel(*target) && *target != '=' && server != NULL) + add_private_msg(server, target); + } + + g_free(params); +} + +static void sig_nick_removed(CHANNEL_REC *channel, NICK_REC *nick) +{ + NICK_COMPLETION_REC *rec; + + rec = nick_completion_find(channel->lastownmsgs, nick->nick); + if (rec != NULL) nick_completion_destroy(&channel->lastownmsgs, rec); + + rec = nick_completion_find(channel->lastmsgs, nick->nick); + if (rec != NULL) nick_completion_destroy(&channel->lastmsgs, rec); +} + +static void sig_nick_changed(CHANNEL_REC *channel, NICK_REC *nick, const char *oldnick) +{ + NICK_COMPLETION_REC *rec; + + rec = nick_completion_find(channel->lastownmsgs, oldnick); + if (rec != NULL) { + g_free(rec->nick); + rec->nick = g_strdup(nick->nick); + } + + rec = nick_completion_find(channel->lastmsgs, oldnick); + if (rec != NULL) { + g_free(rec->nick); + rec->nick = g_strdup(nick->nick); + } +} + +static GList *completion_msg(IRC_SERVER_REC *server, const char *nick, const char *prefix) +{ + GSList *tmp; + GList *list; + int len; + + list = NULL; len = strlen(nick); + for (tmp = server->lastmsgs; tmp != NULL; tmp = tmp->next) { + if (len == 0 || g_strncasecmp(tmp->data, nick, len) == 0) { + if (prefix == NULL || *prefix == '\0') + list = g_list_append(list, g_strdup(tmp->data)); + else + list = g_list_append(list, g_strconcat(prefix, " ", tmp->data, NULL)); + } + } + + return list; +} + +static void complete_from_nicklist(GList **outlist, GSList *list, + const char *nick, const char *prefix) +{ + GSList *tmp; + int len; + + len = strlen(nick); + for (tmp = list; tmp != NULL; tmp = tmp->next) { + NICK_COMPLETION_REC *rec = tmp->data; + + if (g_strncasecmp(rec->nick, nick, len) == 0 && + glist_find_icase_string(*outlist, rec->nick) == NULL) { + if (prefix == NULL || *prefix == '\0') + *outlist = g_list_append(*outlist, g_strdup(rec->nick)); + else + *outlist = g_list_append(*outlist, g_strconcat(rec->nick, prefix, NULL)); + } + } +} + +static GList *completion_channel_nicks(CHANNEL_REC *channel, const char *nick, const char *prefix) +{ + GSList *nicks, *tmp; + GList *list; + int len; + + g_return_val_if_fail(channel != NULL, NULL); + g_return_val_if_fail(nick != NULL, NULL); + if (*nick == '\0') return NULL; + + /* put first the nicks who have recently said something [to you] */ + list = NULL; + complete_from_nicklist(&list, channel->lastownmsgs, nick, prefix); + complete_from_nicklist(&list, channel->lastmsgs, nick, prefix); + + /* and add the rest of the nicks too */ + len = strlen(nick); + nicks = nicklist_getnicks(channel); + for (tmp = nicks; tmp != NULL; tmp = tmp->next) { + NICK_REC *rec = tmp->data; + + if (g_strncasecmp(rec->nick, nick, len) == 0 && + glist_find_icase_string(list, rec->nick) == NULL && + g_strcasecmp(rec->nick, channel->server->nick) != 0) { + if (prefix == NULL || *prefix == '\0') + list = g_list_append(list, g_strdup(rec->nick)); + else + list = g_list_append(list, g_strconcat(rec->nick, prefix, NULL)); + } + } + g_slist_free(nicks); + + return list; +} + +static GList *completion_joinlist(GList *list1, GList *list2) +{ + GList *old; + + old = list2; + while (list2 != NULL) { + if (!glist_find_icase_string(list1, list2->data)) + list1 = g_list_append(list1, list2->data); + else + g_free(list2->data); + + list2 = list2->next; + } + + g_list_free(old); + return list1; +} + +static void sig_word_complete(WINDOW_REC *window, const char *word, + const char *linestart, GList **list) +{ + IRC_SERVER_REC *server; + CHANNEL_REC *channel; + GList *tmplist; + const char *cmdchars, *nickprefix; + char *prefix; + + g_return_if_fail(word != NULL); + + server = window->active_server; + if (server == NULL || !server->connected) + return; + + channel = irc_item_channel(window->active); + + /* check for /MSG completion */ + cmdchars = settings_get_str("cmdchars"); + if (*word == '\0' || (*linestart == '\0' && strchr(cmdchars, *word) != NULL && + g_strcasecmp(word+1, "msg") == 0)) { + /* pressed TAB at the start of line - add /MSG + ... or ... trying to complete /MSG command */ + prefix = g_strdup_printf("%cmsg", *cmdchars); + *list = completion_msg(server, "", prefix); + if (*list == NULL) *list = g_list_append(*list, g_strdup(prefix)); + g_free(prefix); + + signal_stop(); + return; + } + + if (strchr(cmdchars, *linestart) != NULL && + g_strcasecmp(linestart+1, "msg") == 0) { + /* completing /MSG nick */ + *list = completion_msg(server, word, NULL); + } + + /* nick completion .. we could also be completing a nick after /MSG + from nicks in channel */ + if (channel == NULL) + return; + + nickprefix = *linestart != '\0' ? NULL : + settings_get_str("completion_char"); + + tmplist = completion_channel_nicks(channel, word, nickprefix); + *list = completion_joinlist(*list, tmplist); + + if (*list != NULL) signal_stop(); +} + +/* expand \n, \t and \\ - FIXME: this doesn't work right */ +static char *expand_escapes(const char *line, IRC_SERVER_REC *server, WI_IRC_REC *item) +{ + char *ptr, *ret; + + ret = ptr = g_malloc(strlen(line)+1); + while (*line != '\0') { + if (*line != '\\') + *ptr++ = *line; + else { + line++; + if (*line == '\0') { + *ptr++ = '\\'; + break; + } + + switch (*line) { + case 'n': + /* newline .. we need to send another "send text" event to handle it (or actually the text before the newline..) */ + *ptr = '\0'; + signal_emit("send text", 3, line, server, item); + ptr = ret; + break; + case 't': + *ptr++ = '\t'; + break; + case '\\': + *ptr++ = '\\'; + break; + default: + *ptr++ = '\\'; + *ptr++ = *line; + break; + } + } + line++; + } + + *ptr = '\0'; + return ret; +} + +static void event_text(gchar *line, IRC_SERVER_REC *server, WI_IRC_REC *item) +{ + CHANNEL_REC *channel; + GList *comp; + gchar *str, *ptr; + + g_return_if_fail(line != NULL); + + if (!irc_item_check(item)) + return; + + /* FIXME: this really should go to fe-common/core. */ + line = settings_get_bool("expand_escapes") ? + expand_escapes(line, server, item) : g_strdup(line); + + /* check for nick completion */ + if (settings_get_bool("completion_disable_auto") || *settings_get_str("completion_char") == '\0') + { + ptr = NULL; + comp = NULL; + } + else + { + ptr = strchr(line, *settings_get_str("completion_char")); + if (ptr != NULL) *ptr++ = '\0'; + + channel = irc_item_channel(item); + + comp = ptr == NULL || channel == NULL || + nicklist_find(channel, line) != NULL ? NULL : + completion_channel_nicks(channel, line, NULL); + } + + /* message to channel */ + if (ptr == NULL) + str = g_strdup_printf("%s %s", item->name, line); + else + { + str = g_strdup_printf("%s %s%s%s", item->name, + comp != NULL ? (gchar *) comp->data : line, + settings_get_str("completion_char"), ptr); + } + signal_emit("command msg", 3, str, server, item); + + g_free(str); + g_free(line); + + if (comp != NULL) + { + g_list_foreach(comp, (GFunc) g_free, NULL); + g_list_free(comp); + } + + signal_stop(); +} + +static void completion_deinit_server(IRC_SERVER_REC *server) +{ + g_return_if_fail(server != NULL); + + g_slist_foreach(server->lastmsgs, (GFunc) g_free, NULL); + g_slist_free(server->lastmsgs); +} + +static void completion_deinit_channel(CHANNEL_REC *channel) +{ + g_return_if_fail(channel != NULL); + + while (channel->lastmsgs != NULL) + nick_completion_destroy(&channel->lastmsgs, channel->lastmsgs->data); + while (channel->lastownmsgs != NULL) + nick_completion_destroy(&channel->lastownmsgs, channel->lastownmsgs->data); + + g_slist_free(channel->lastmsgs); + g_slist_free(channel->lastownmsgs); +} + +void irc_completion_init(void) +{ + settings_add_str("completion", "completion_char", ":"); + settings_add_bool("completion", "completion_disable_auto", FALSE); + settings_add_int("completion", "completion_keep_publics", 180); + settings_add_int("completion", "completion_keep_ownpublics", 360); + settings_add_int("completion", "completion_keep_privates", 10); + settings_add_bool("completion", "expand_escapes", FALSE); + + complete_tag = g_timeout_add(1000, (GSourceFunc) nick_completion_timeout, NULL); + + signal_add("word complete", (SIGNAL_FUNC) sig_word_complete); + signal_add("event privmsg", (SIGNAL_FUNC) event_privmsg); + signal_add("command msg", (SIGNAL_FUNC) cmd_msg); + signal_add("nicklist remove", (SIGNAL_FUNC) sig_nick_removed); + signal_add("nicklist changed", (SIGNAL_FUNC) sig_nick_changed); + signal_add("send text", (SIGNAL_FUNC) event_text); + signal_add("server disconnected", (SIGNAL_FUNC) completion_deinit_server); + signal_add("channel destroyed", (SIGNAL_FUNC) completion_deinit_channel); +} + +void irc_completion_deinit(void) +{ + g_source_remove(complete_tag); + + signal_remove("word complete", (SIGNAL_FUNC) sig_word_complete); + signal_remove("event privmsg", (SIGNAL_FUNC) event_privmsg); + signal_remove("command msg", (SIGNAL_FUNC) cmd_msg); + signal_remove("nicklist remove", (SIGNAL_FUNC) sig_nick_removed); + signal_remove("nicklist changed", (SIGNAL_FUNC) sig_nick_changed); + signal_remove("send text", (SIGNAL_FUNC) event_text); + signal_remove("server disconnected", (SIGNAL_FUNC) completion_deinit_server); + signal_remove("channel destroyed", (SIGNAL_FUNC) completion_deinit_channel); +} diff --git a/src/fe-text/gui-readline.c b/src/fe-text/gui-readline.c index b68ec01e..bee0d285 100644 --- a/src/fe-text/gui-readline.c +++ b/src/fe-text/gui-readline.c @@ -317,7 +317,7 @@ static void sig_completion(void) pos = gui_entry_get_pos(); - line = completion_line(active_win, gui_entry_get_text(), &pos); + line = word_complete(active_win, gui_entry_get_text(), &pos); if (line != NULL) { gui_entry_set_text(line); gui_entry_set_pos(pos); @@ -332,7 +332,7 @@ static void sig_replace(void) pos = gui_entry_get_pos(); - line = auto_completion(gui_entry_get_text(), &pos); + line = auto_word_complete(gui_entry_get_text(), &pos); if (line != NULL) { gui_entry_set_text(line); gui_entry_set_pos(pos);