Added SILC Thread Queue API
[crypto.git] / apps / irssi / src / fe-common / core / chat-completion.c
index 3cbcbf02258f14d52c16cfb80ade3d4739cec21f..dea7360568443d35ead76320055bfa361428f680 100644 (file)
 #include "signals.h"
 #include "commands.h"
 #include "misc.h"
+#include "levels.h"
+#include "lib-config/iconfig.h"
 #include "settings.h"
 
 #include "chatnets.h"
+#include "servers.h"
 #include "servers-setup.h"
 #include "channels.h"
 #include "channels-setup.h"
@@ -32,6 +35,7 @@
 #include "nicklist.h"
 
 #include "completion.h"
+#include "chat-completion.h"
 #include "window-items.h"
 
 static int keep_privates_count, keep_publics_count;
@@ -73,10 +77,21 @@ static void last_msg_dec_owns(GSList *list)
        }
 }
 
+static void last_msg_destroy(GSList **list, LAST_MSG_REC *rec)
+{
+       *list = g_slist_remove(*list, rec);
+
+       g_free(rec->nick);
+       g_free(rec);
+}
+
 static void last_msg_add(GSList **list, const char *nick, int own, int max)
 {
        LAST_MSG_REC *rec;
 
+       if (max <= 0)
+               return;
+
        rec = last_msg_find(*list, nick);
        if (rec != NULL) {
                /* msg already exists, update it */
@@ -89,9 +104,8 @@ static void last_msg_add(GSList **list, const char *nick, int own, int max)
                rec = g_new(LAST_MSG_REC, 1);
                rec->nick = g_strdup(nick);
 
-               if ((int)g_slist_length(*list) == max) {
-                       *list = g_slist_remove(*list,
-                                              g_slist_last(*list)->data);
+               while ((int)g_slist_length(*list) >= max) {
+                       last_msg_destroy(list, g_slist_last(*list)->data);
                }
 
                rec->own = own ? max : 0;
@@ -103,14 +117,6 @@ static void last_msg_add(GSList **list, const char *nick, int own, int max)
        *list = g_slist_prepend(*list, rec);
 }
 
-static void last_msg_destroy(GSList **list, LAST_MSG_REC *rec)
-{
-       *list = g_slist_remove(*list, rec);
-
-       g_free(rec->nick);
-       g_free(rec);
-}
-
 void completion_last_message_add(const char *nick)
 {
        g_return_if_fail(nick != NULL);
@@ -156,6 +162,16 @@ static void sig_message_public(SERVER_REC *server, const char *msg,
        }
 }
 
+static void sig_message_join(SERVER_REC *server, const char *channel,
+                            const char *nick, const char *address)
+{
+       CHANNEL_REC *chanrec;
+
+       chanrec = channel_find(server, channel);
+       if (chanrec != NULL)
+               CHANNEL_LAST_MSG_ADD(chanrec, nick, FALSE);
+}
+
 static void sig_message_private(SERVER_REC *server, const char *msg,
                                const char *nick, const char *address)
 {
@@ -202,7 +218,6 @@ static void sig_message_own_private(SERVER_REC *server, const char *msg,
                                    const char *target, const char *origtarget)
 {
        g_return_if_fail(server != NULL);
-       g_return_if_fail(target != NULL);
 
        if (target != NULL && query_find(server, target) == NULL)
                SERVER_LAST_MSG_ADD(server, target);
@@ -285,7 +300,7 @@ static GList *convert_msglist(GSList *msglist)
 }
 
 /* Complete /MSG - if `find_server' is NULL, complete nicks from all servers */
-static GList *completion_msg(SERVER_REC *win_server,
+GList *completion_msg(SERVER_REC *win_server,
                             SERVER_REC *find_server,
                             const char *nick, const char *prefix)
 {
@@ -305,7 +320,7 @@ static GList *completion_msg(SERVER_REC *win_server,
        for (tmp = servers; tmp != NULL; tmp = tmp->next) {
                SERVER_REC *rec = tmp->data;
 
-               if (rec == win_server)
+               if (servers->next == NULL && rec == win_server)
                        newprefix = g_strdup(prefix);
                else {
                        newprefix = prefix == NULL ?
@@ -382,7 +397,7 @@ static GList *completion_nicks_nonstrict(CHANNEL_REC *channel,
                /* remove non alnum chars from nick */
                in = rec->nick; out = str;
                while (*in != '\0') {
-                       if (isalnum(*in))
+                       if (i_isalnum(*in))
                                *out++ = *in;
                         in++;
                }
@@ -472,6 +487,32 @@ static GList *completion_joinlist(GList *list1, GList *list2)
        return list1;
 }
 
+GList *completion_get_servertags(const char *word)
+{
+       GList *list;
+       GSList *tmp;
+       int len;
+
+       g_return_val_if_fail(word != NULL, NULL);
+
+       len = strlen(word);
+       list = NULL;
+
+       for (tmp = servers; tmp != NULL; tmp = tmp->next) {
+               SERVER_REC *rec = tmp->data;
+
+               if (g_strncasecmp(rec->tag, word, len) == 0) {
+                       if (rec == active_win->active_server)
+                               list = g_list_prepend(list, g_strdup(rec->tag));
+                       else
+                               list = g_list_append(list, g_strdup(rec->tag));
+               }
+
+       }
+
+       return list;
+}
+
 GList *completion_get_channels(SERVER_REC *server, const char *word)
 {
        GList *list;
@@ -479,7 +520,6 @@ GList *completion_get_channels(SERVER_REC *server, const char *word)
        int len;
 
        g_return_val_if_fail(word != NULL, NULL);
-       g_return_val_if_fail(*word != '\0', NULL);
 
        len = strlen(word);
        list = NULL;
@@ -489,7 +529,9 @@ GList *completion_get_channels(SERVER_REC *server, const char *word)
        for (; tmp != NULL; tmp = tmp->next) {
                CHANNEL_REC *rec = tmp->data;
 
-               if (g_strncasecmp(rec->name, word, len) == 0)
+               if (g_strncasecmp(rec->visible_name, word, len) == 0)
+                       list = g_list_append(list, g_strdup(rec->visible_name));
+               else if (g_strncasecmp(rec->name, word, len) == 0)
                        list = g_list_append(list, g_strdup(rec->name));
        }
 
@@ -506,6 +548,36 @@ GList *completion_get_channels(SERVER_REC *server, const char *word)
        return list;
 }
 
+GList *completion_get_aliases(const char *word)
+{
+       CONFIG_NODE *node;
+       GList *list;
+       GSList *tmp;
+       int len;
+
+       g_return_val_if_fail(word != NULL, NULL);
+
+       len = strlen(word);
+       list = NULL;
+
+       /* get the list of all aliases */
+       node = iconfig_node_traverse("aliases", FALSE);
+       tmp = node == NULL ? NULL : config_node_first(node->value);
+       for (; tmp != NULL; tmp = config_node_next(tmp)) {
+               node = tmp->data;
+
+               if (node->type != NODE_TYPE_KEY)
+                       continue;
+
+               if (len != 0 && g_strncasecmp(node->key, word, len) != 0)
+                       continue;
+
+               list = g_list_append(list, g_strdup(node->key));
+       }
+       
+       return list;
+}
+
 static void complete_window_nicks(GList **list, WINDOW_REC *window,
                                   const char *word, const char *linestart)
 {
@@ -543,7 +615,8 @@ static void complete_window_nicks(GList **list, WINDOW_REC *window,
 }
 
 static void sig_complete_word(GList **list, WINDOW_REC *window,
-                             const char *word, const char *linestart)
+                             const char *word, const char *linestart,
+                             int *want_space)
 {
        SERVER_REC *server;
        CHANNEL_REC *channel;
@@ -559,7 +632,7 @@ static void sig_complete_word(GList **list, WINDOW_REC *window,
        if (server == NULL && servers != NULL)
                server = servers->data;
 
-       if (server != NULL && server->ischannel(word)) {
+       if (server != NULL && server_ischannel(server, word)) {
                /* probably completing a channel name */
                *list = completion_get_channels(window->active_server, word);
                 return;
@@ -590,7 +663,10 @@ static void sig_complete_word(GList **list, WINDOW_REC *window,
        } else if (channel != NULL) {
                /* nick completion .. we could also be completing a nick
                   after /MSG from nicks in channel */
-                complete_window_nicks(list, window, word, linestart);
+               complete_window_nicks(list, window, word, linestart);
+       } else if (window->level & MSGLEVEL_MSGS) {
+               /* msgs window, complete /MSG nicks */
+                *list = g_list_concat(completion_msg(server, NULL, word, NULL), *list);
        }
 
        if (*list != NULL) signal_stop();
@@ -634,6 +710,41 @@ static void sig_complete_msg(GList **list, WINDOW_REC *window,
        if (*list != NULL) signal_stop();
 }
 
+static void sig_erase_complete_msg(WINDOW_REC *window, const char *word,
+                                  const char *line)
+{
+       SERVER_REC *server;
+       MODULE_SERVER_REC *mserver;
+        GSList *tmp;
+
+       server = line_get_server(line);
+       if (server == NULL){
+               server = window->active_server;
+               if (server == NULL)
+                        return;
+       }
+
+       if (*word == '\0')
+               return;
+
+        /* check from global list */
+       completion_last_message_remove(word);
+
+       /* check from server specific list */
+       if (server != NULL) {
+               mserver = MODULE_DATA(server);
+               for (tmp = mserver->lastmsgs; tmp != NULL; tmp = tmp->next) {
+                       LAST_MSG_REC *rec = tmp->data;
+
+                       if (g_strcasecmp(rec->nick, word) == 0) {
+                               last_msg_destroy(&mserver->lastmsgs, rec);
+                                break;
+                       }
+               }
+
+       }
+}
+
 GList *completion_get_chatnets(const char *word)
 {
        GList *list;
@@ -676,6 +787,36 @@ GList *completion_get_servers(const char *word)
        return list;
 }
 
+GList *completion_get_targets(const char *word)
+{
+       CONFIG_NODE *node;
+       GList *list;
+       GSList *tmp;
+       int len;
+
+       g_return_val_if_fail(word != NULL, NULL);
+
+       len = strlen(word);
+       list = NULL;
+
+       /* get the list of all conversion targets */
+       node = iconfig_node_traverse("conversions", FALSE);
+       tmp = node == NULL ? NULL : config_node_first(node->value);
+       for (; tmp != NULL; tmp = config_node_next(tmp)) {
+               node = tmp->data;
+
+               if (node->type != NODE_TYPE_KEY)
+                       continue;
+
+               if (len != 0 && g_strncasecmp(node->key, word, len) != 0)
+                       continue;
+
+               list = g_list_append(list, g_strdup(node->key));
+       }
+       
+       return list;
+}
+
 static void sig_complete_connect(GList **list, WINDOW_REC *window,
                                 const char *word, const char *line, 
                                 int *want_space)
@@ -688,11 +829,136 @@ static void sig_complete_connect(GList **list, WINDOW_REC *window,
        if (*list != NULL) signal_stop();
 }
 
+static void sig_complete_tag(GList **list, WINDOW_REC *window,
+                            const char *word, const char *line,
+                            int *want_space)
+{
+       g_return_if_fail(list != NULL);
+       g_return_if_fail(word != NULL);
+
+       *list = completion_get_servertags(word);
+       if (*list != NULL) signal_stop();
+}
+
+static void sig_complete_topic(GList **list, WINDOW_REC *window,
+                              const char *word, const char *line,
+                              int *want_space)
+{
+       const char *topic;
+
+       g_return_if_fail(list != NULL);
+       g_return_if_fail(word != NULL);
+
+       if (*word == '\0' && IS_CHANNEL(window->active)) {
+               topic = CHANNEL(window->active)->topic;
+               if (topic != NULL) {
+                       *list = g_list_append(NULL, g_strdup(topic));
+                        signal_stop();
+               }
+       }
+}
+
+static void sig_complete_away(GList **list, WINDOW_REC *window,
+                              const char *word, const char *line,
+                              int *want_space)
+{
+       const char *reason;
+
+       g_return_if_fail(list != NULL);
+       g_return_if_fail(word != NULL);
+
+        *want_space = FALSE;
+
+       if (*word == '\0' && window->active_server != NULL) {
+               reason = SERVER(window->active_server)->away_reason;
+               if (reason != NULL) {
+                       *list = g_list_append(NULL, g_strdup(reason));
+                       signal_stop();
+               }
+       }
+}
+
+static void sig_complete_unalias(GList **list, WINDOW_REC *window,
+                               const char *word, const char *line,
+                               int *want_space)
+{
+       g_return_if_fail(list != NULL);
+       g_return_if_fail(word != NULL);
+
+       *list = completion_get_aliases(word);
+       if (*list != NULL) signal_stop();
+}
+
+static void sig_complete_alias(GList **list, WINDOW_REC *window,
+                               const char *word, const char *line,
+                               int *want_space)
+{
+       const char *definition;
+       
+       g_return_if_fail(list != NULL);
+       g_return_if_fail(word != NULL);
+       g_return_if_fail(line != NULL);
+
+       if (*line != '\0') {
+               if ((definition = alias_find(line)) != NULL) {
+                       *list = g_list_append(NULL, g_strdup(definition));
+                       signal_stop();
+               }
+       } else {        
+               *list = completion_get_aliases(word);
+               if (*list != NULL) signal_stop();
+       }
+}
+
+static void sig_complete_channel(GList **list, WINDOW_REC *window,
+                                const char *word, const char *line,
+                                int *want_space)
+{
+       g_return_if_fail(list != NULL);
+       g_return_if_fail(word != NULL);
+
+       *list = completion_get_channels(NULL, word);
+       if (*list != NULL) signal_stop();
+}
+
+static void sig_complete_server(GList **list, WINDOW_REC *window,
+                               const char *word, const char *line,
+                               int *want_space)
+{
+       g_return_if_fail(list != NULL);
+       g_return_if_fail(word != NULL);
+
+       *list = completion_get_servers(word);
+       if (*list != NULL) signal_stop();
+}
+
+static void sig_complete_target(GList **list, WINDOW_REC *window,
+                               const char *word, const char *line,
+                               int *want_space)
+{
+       const char *definition;
+       
+       g_return_if_fail(list != NULL);
+       g_return_if_fail(word != NULL);
+       g_return_if_fail(line != NULL);
+
+       if (*line != '\0') {
+               if ((definition = iconfig_get_str("conversions", line ,NULL)) != NULL) {
+                       *list = g_list_append(NULL, g_strdup(definition));
+                       signal_stop();
+               }
+       } else {        
+               *list = completion_get_targets(word);
+               if (*list != NULL) signal_stop();
+       }
+}
+
 /* expand \n, \t and \\ */
 static char *expand_escapes(const char *line, SERVER_REC *server,
                            WI_ITEM_REC *item)
 {
        char *ptr, *ret;
+        int chr;
 
        ret = ptr = g_malloc(strlen(line)+1);
        for (; *line != '\0'; line++) {
@@ -707,25 +973,23 @@ static char *expand_escapes(const char *line, SERVER_REC *server,
                        break;
                }
 
-               switch (*line) {
-               case 'n':
+               chr = expand_escape(&line);
+               if (chr == '\r' || chr == '\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, ret, server, item);
-                       ptr = ret;
-                       break;
-               case 't':
-                       *ptr++ = '\t';
-                       break;
-               case '\\':
-                       *ptr++ = '\\';
-                       break;
-               default:
+                       if (ret != ptr) {
+                               *ptr = '\0';
+                               signal_emit("send text", 3, ret, server, item);
+                               ptr = ret;
+                       }
+               } else if (chr != -1) {
+                        /* escaping went ok */
+                       *ptr++ = chr;
+               } else {
+                        /* unknown escape, add it as-is */
                        *ptr++ = '\\';
                        *ptr++ = *line;
-                       break;
                }
        }
 
@@ -733,48 +997,75 @@ static char *expand_escapes(const char *line, SERVER_REC *server,
        return ret;
 }
 
-static void event_text(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
+static char *auto_complete(CHANNEL_REC *channel, const char *line)
 {
-       CHANNEL_REC *channel;
        GList *comp;
-       char *line, *str, *ptr, comp_char;
+       const char *p;
+        char *nick, *ret;
+
+       p = strstr(line, completion_char);
+       if (p == NULL)
+               return NULL;
+
+        nick = g_strndup(line, (int) (p-line));
+
+        ret = NULL;
+       if (nicklist_find(channel, nick) == NULL) {
+                /* not an exact match, use the first possible completion */
+               comp = completion_channel_nicks(channel, nick, NULL);
+               if (comp != NULL) {
+                       ret = g_strconcat(comp->data, p, NULL);
+                       g_list_foreach(comp, (GFunc) g_free, NULL);
+                       g_list_free(comp);
+               }
+       }
+
+       g_free(nick);
+
+        return ret;
+}
+
+static void event_text(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
+{
+       char *line, *str, *target;
 
        g_return_if_fail(data != NULL);
-       if (item == NULL) return;
+
+       if (item == NULL)
+               return;
+
+       if (*data == '\0') {
+               /* empty line, forget it. */
+                signal_stop();
+               return;
+       }
 
        line = settings_get_bool("expand_escapes") ?
                expand_escapes(data, server, item) : g_strdup(data);
-       comp_char = *completion_char;
 
        /* check for automatic nick completion */
-        ptr = NULL;
-       comp = NULL;
-       channel = CHANNEL(item);
-
-       if (completion_auto && channel != NULL && comp_char != '\0') {
-               ptr = strchr(line, comp_char);
-               if (ptr != NULL) {
-                       *ptr++ = '\0';
-                       if (nicklist_find(channel, line) == NULL) {
-                               comp = completion_channel_nicks(channel,
-                                                               line, NULL);
-                       }
+       if (completion_auto && IS_CHANNEL(item)) {
+               str = auto_complete(CHANNEL(item), line);
+               if (str != NULL) {
+                       g_free(line);
+                        line = str;
                }
        }
 
-       str = g_strdup_printf(ptr == NULL ? "%s %s" : "%s %s%c%s", item->name,
-                             comp != NULL ? (char *) comp->data : line,
-                             comp_char, ptr);
+       /* the nick is quoted in case it contains '-' character. also
+          spaces should work too now :) The nick is also escaped in case
+          it contains '\' characters */
+       target = escape_string(window_item_get_target(item));
+       str = g_strdup_printf(IS_CHANNEL(item) ? "-channel \"%s\" %s" :
+                             IS_QUERY(item) ? "-nick \"%s\" %s" : "%s %s",
+                             target, line);
+       g_free(target);
+
        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();
 }
 
@@ -811,6 +1102,11 @@ static void read_settings(void)
        cmdchars = settings_get_str("cmdchars");
        completion_auto = settings_get_bool("completion_auto");
        completion_strict = settings_get_bool("completion_strict");
+
+       if (*completion_char == '\0') {
+                /* this would break.. */
+               completion_auto = FALSE;
+       }
 }
 
 void chat_completion_init(void)
@@ -819,16 +1115,33 @@ void chat_completion_init(void)
        settings_add_bool("completion", "completion_auto", FALSE);
        settings_add_int("completion", "completion_keep_publics", 50);
        settings_add_int("completion", "completion_keep_privates", 10);
-       settings_add_bool("completion", "expand_escapes", FALSE);
        settings_add_bool("completion", "completion_nicks_lowercase", FALSE);
        settings_add_bool("completion", "completion_strict", FALSE);
 
+       settings_add_bool("lookandfeel", "expand_escapes", FALSE);
+
        read_settings();
        signal_add("complete word", (SIGNAL_FUNC) sig_complete_word);
        signal_add("complete command msg", (SIGNAL_FUNC) sig_complete_msg);
+       signal_add("complete command query", (SIGNAL_FUNC) sig_complete_msg);
+       signal_add("complete command action", (SIGNAL_FUNC) sig_complete_msg);
+       signal_add("complete erase command msg", (SIGNAL_FUNC) sig_erase_complete_msg);
+       signal_add("complete erase command query", (SIGNAL_FUNC) sig_erase_complete_msg);
+       signal_add("complete erase command action", (SIGNAL_FUNC) sig_erase_complete_msg);
        signal_add("complete command connect", (SIGNAL_FUNC) sig_complete_connect);
        signal_add("complete command server", (SIGNAL_FUNC) sig_complete_connect);
+       signal_add("complete command disconnect", (SIGNAL_FUNC) sig_complete_tag);
+       signal_add("complete command reconnect", (SIGNAL_FUNC) sig_complete_tag);
+       signal_add("complete command topic", (SIGNAL_FUNC) sig_complete_topic);
+       signal_add("complete command away", (SIGNAL_FUNC) sig_complete_away);
+       signal_add("complete command unalias", (SIGNAL_FUNC) sig_complete_unalias);
+       signal_add("complete command alias", (SIGNAL_FUNC) sig_complete_alias);
+       signal_add("complete command window item move", (SIGNAL_FUNC) sig_complete_channel);
+       signal_add("complete command server add", (SIGNAL_FUNC) sig_complete_server);
+       signal_add("complete command server remove", (SIGNAL_FUNC) sig_complete_server);
+       signal_add("complete command recode remove", (SIGNAL_FUNC) sig_complete_target);
        signal_add("message public", (SIGNAL_FUNC) sig_message_public);
+       signal_add("message join", (SIGNAL_FUNC) sig_message_join);
        signal_add("message private", (SIGNAL_FUNC) sig_message_private);
        signal_add("message own_public", (SIGNAL_FUNC) sig_message_own_public);
        signal_add("message own_private", (SIGNAL_FUNC) sig_message_own_private);
@@ -847,9 +1160,25 @@ void chat_completion_deinit(void)
 
        signal_remove("complete word", (SIGNAL_FUNC) sig_complete_word);
        signal_remove("complete command msg", (SIGNAL_FUNC) sig_complete_msg);
+       signal_remove("complete command query", (SIGNAL_FUNC) sig_complete_msg);
+       signal_remove("complete command action", (SIGNAL_FUNC) sig_complete_msg);
+       signal_remove("complete erase command msg", (SIGNAL_FUNC) sig_erase_complete_msg);
+       signal_remove("complete erase command query", (SIGNAL_FUNC) sig_erase_complete_msg);
+       signal_remove("complete erase command action", (SIGNAL_FUNC) sig_erase_complete_msg);
        signal_remove("complete command connect", (SIGNAL_FUNC) sig_complete_connect);
        signal_remove("complete command server", (SIGNAL_FUNC) sig_complete_connect);
+       signal_remove("complete command disconnect", (SIGNAL_FUNC) sig_complete_tag);
+       signal_remove("complete command reconnect", (SIGNAL_FUNC) sig_complete_tag);
+       signal_remove("complete command topic", (SIGNAL_FUNC) sig_complete_topic);
+       signal_remove("complete command away", (SIGNAL_FUNC) sig_complete_away);
+       signal_remove("complete command unalias", (SIGNAL_FUNC) sig_complete_unalias);
+       signal_remove("complete command alias", (SIGNAL_FUNC) sig_complete_alias);
+       signal_remove("complete command window item move", (SIGNAL_FUNC) sig_complete_channel);
+       signal_remove("complete command server add", (SIGNAL_FUNC) sig_complete_server);
+       signal_remove("complete command server remove", (SIGNAL_FUNC) sig_complete_server);
+       signal_remove("complete command recode remove", (SIGNAL_FUNC) sig_complete_target);
        signal_remove("message public", (SIGNAL_FUNC) sig_message_public);
+       signal_remove("message join", (SIGNAL_FUNC) sig_message_join);
        signal_remove("message private", (SIGNAL_FUNC) sig_message_private);
        signal_remove("message own_public", (SIGNAL_FUNC) sig_message_own_public);
        signal_remove("message own_private", (SIGNAL_FUNC) sig_message_own_private);