summaryrefslogtreecommitdiff
path: root/src/fe-common/irc/completion.c
diff options
context:
space:
mode:
authorTimo Sirainen <cras@irssi.org>2000-04-26 08:03:38 +0000
committercras <cras@dbcabf3a-b0e7-0310-adc4-f8d773084564>2000-04-26 08:03:38 +0000
commitc95034c6de1bf72536595e1e3431d8ec64b9880e (patch)
treee51aa4528257ed8aa9d53640649519f299aaf0c7 /src/fe-common/irc/completion.c
parentd01b094151705d433bc43cae9eeb304e6f110a17 (diff)
downloadirssi-c95034c6de1bf72536595e1e3431d8ec64b9880e.zip
..adding new files..
git-svn-id: http://svn.irssi.org/repos/irssi/trunk@171 dbcabf3a-b0e7-0310-adc4-f8d773084564
Diffstat (limited to 'src/fe-common/irc/completion.c')
-rw-r--r--src/fe-common/irc/completion.c628
1 files changed, 628 insertions, 0 deletions
diff --git a/src/fe-common/irc/completion.c b/src/fe-common/irc/completion.c
new file mode 100644
index 00000000..444a3a06
--- /dev/null
+++ b/src/fe-common/irc/completion.c
@@ -0,0 +1,628 @@
+/*
+ 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 (*msg == 1)
+ {
+ /* ignore ctcp messages */
+ g_free(params);
+ return;
+ }
+
+ if (ischannel(*target))
+ {
+ /* channel message */
+ CHANNEL_REC *channel;
+
+ channel = channel_find(server, target);
+ if (channel == NULL)
+ {
+ g_free(params);
+ return;
+ }
+
+ list = completion_msgtoyou((SERVER_REC *) server, 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);
+}
+
+int completion_msgtoyou(SERVER_REC *server, const char *msg)
+{
+ gchar *stripped, *nick;
+ gboolean ret;
+ gint len;
+
+ g_return_val_if_fail(msg != NULL, FALSE);
+
+ if (g_strncasecmp(msg, server->nick, strlen(server->nick)) == 0 &&
+ !isalnum((gint) msg[strlen(server->nick)])) return TRUE;
+
+ stripped = nick_strip(server->nick);
+ nick = nick_strip(msg);
+
+ len = strlen(stripped);
+ ret = *stripped != '\0' &&
+ g_strncasecmp(nick, stripped, len) == 0 &&
+ !isalnum((gint) nick[len]) &&
+ (guchar) nick[len] < 128;
+
+ g_free(nick);
+ g_free(stripped);
+ return ret;
+}
+
+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;
+}
+
+static void event_command(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;
+
+ if (strchr(settings_get_str("cmdchars"), *line) != NULL)
+ return;
+
+ line = 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;
+}
+
+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++;
+ }
+
+ 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 <tab> */
+ *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))
+ {
+ 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 <nick> */
+ 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);
+
+ signal_add("event privmsg", (SIGNAL_FUNC) event_privmsg);
+ signal_add("send command", (SIGNAL_FUNC) event_command);
+ 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)
+{
+ 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 command", (SIGNAL_FUNC) event_command);
+ 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);
+}