Merged with Irssi 0.8.6.
[silc.git] / apps / irssi / src / fe-common / core / chat-completion.c
1 /*
2  chat-completion.c : irssi
3
4     Copyright (C) 1999-2000 Timo Sirainen
5
6     This program is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     This program is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with this program; if not, write to the Free Software
18     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19 */
20
21 #include "module.h"
22 #include "signals.h"
23 #include "commands.h"
24 #include "misc.h"
25 #include "levels.h"
26 #include "lib-config/iconfig.h"
27 #include "settings.h"
28
29 #include "chatnets.h"
30 #include "servers.h"
31 #include "servers-setup.h"
32 #include "channels.h"
33 #include "channels-setup.h"
34 #include "queries.h"
35 #include "nicklist.h"
36
37 #include "completion.h"
38 #include "chat-completion.h"
39 #include "window-items.h"
40
41 static int keep_privates_count, keep_publics_count;
42 static int completion_lowercase;
43 static const char *completion_char, *cmdchars;
44 static GSList *global_lastmsgs;
45 static int completion_auto, completion_strict;
46
47 #define SERVER_LAST_MSG_ADD(server, nick) \
48         last_msg_add(&((MODULE_SERVER_REC *) MODULE_DATA(server))->lastmsgs, \
49                      nick, TRUE, keep_privates_count)
50
51 #define CHANNEL_LAST_MSG_ADD(channel, nick, own) \
52         last_msg_add(&((MODULE_CHANNEL_REC *) MODULE_DATA(channel))->lastmsgs, \
53                      nick, own, keep_publics_count)
54
55 static LAST_MSG_REC *last_msg_find(GSList *list, const char *nick)
56 {
57         while (list != NULL) {
58                 LAST_MSG_REC *rec = list->data;
59
60                 if (g_strcasecmp(rec->nick, nick) == 0)
61                         return rec;
62                 list = list->next;
63         }
64
65         return NULL;
66 }
67
68 static void last_msg_dec_owns(GSList *list)
69 {
70         LAST_MSG_REC *rec;
71
72         while (list != NULL) {
73                 rec = list->data;
74                 if (rec->own) rec->own--;
75
76                 list = list->next;
77         }
78 }
79
80 static void last_msg_add(GSList **list, const char *nick, int own, int max)
81 {
82         LAST_MSG_REC *rec;
83
84         rec = last_msg_find(*list, nick);
85         if (rec != NULL) {
86                 /* msg already exists, update it */
87                 *list = g_slist_remove(*list, rec);
88                 if (own)
89                         rec->own = max;
90                 else if (rec->own)
91                         rec->own--;
92         } else {
93                 rec = g_new(LAST_MSG_REC, 1);
94                 rec->nick = g_strdup(nick);
95
96                 if ((int)g_slist_length(*list) == max) {
97                         *list = g_slist_remove(*list,
98                                                g_slist_last(*list)->data);
99                 }
100
101                 rec->own = own ? max : 0;
102         }
103         rec->time = time(NULL);
104
105         last_msg_dec_owns(*list);
106
107         *list = g_slist_prepend(*list, rec);
108 }
109
110 static void last_msg_destroy(GSList **list, LAST_MSG_REC *rec)
111 {
112         *list = g_slist_remove(*list, rec);
113
114         g_free(rec->nick);
115         g_free(rec);
116 }
117
118 void completion_last_message_add(const char *nick)
119 {
120         g_return_if_fail(nick != NULL);
121
122         last_msg_add(&global_lastmsgs, nick, TRUE, keep_privates_count);
123 }
124
125 void completion_last_message_remove(const char *nick)
126 {
127         LAST_MSG_REC *rec;
128
129         g_return_if_fail(nick != NULL);
130
131         rec = last_msg_find(global_lastmsgs, nick);
132         if (rec != NULL) last_msg_destroy(&global_lastmsgs, rec);
133 }
134
135 void completion_last_message_rename(const char *oldnick, const char *newnick)
136 {
137         LAST_MSG_REC *rec;
138
139         g_return_if_fail(oldnick != NULL);
140         g_return_if_fail(newnick != NULL);
141
142         rec = last_msg_find(global_lastmsgs, oldnick);
143         if (rec != NULL) {
144                 g_free(rec->nick);
145                 rec->nick = g_strdup(newnick);
146         }
147 }
148
149 static void sig_message_public(SERVER_REC *server, const char *msg,
150                                const char *nick, const char *address,
151                                const char *target)
152 {
153         CHANNEL_REC *channel;
154         int own;
155
156         channel = channel_find(server, target);
157         if (channel != NULL) {
158                 own = nick_match_msg(channel, msg, server->nick);
159                 CHANNEL_LAST_MSG_ADD(channel, nick, own);
160         }
161 }
162
163 static void sig_message_join(SERVER_REC *server, const char *channel,
164                              const char *nick, const char *address)
165 {
166         CHANNEL_REC *chanrec;
167
168         chanrec = channel_find(server, channel);
169         if (chanrec != NULL)
170                 CHANNEL_LAST_MSG_ADD(chanrec, nick, FALSE);
171 }
172
173 static void sig_message_private(SERVER_REC *server, const char *msg,
174                                 const char *nick, const char *address)
175 {
176         g_return_if_fail(server != NULL);
177         g_return_if_fail(nick != NULL);
178
179         SERVER_LAST_MSG_ADD(server, nick);
180 }
181
182 static void sig_message_own_public(SERVER_REC *server, const char *msg,
183                                    const char *target, const char *origtarget)
184 {
185         CHANNEL_REC *channel;
186         NICK_REC *nick;
187         char *p, *msgnick;
188
189         g_return_if_fail(server != NULL);
190         g_return_if_fail(msg != NULL);
191         if (target == NULL) return;
192
193         channel = channel_find(server, target);
194         if (channel == NULL)
195                 return;
196
197         /* channel msg - if first word in line is nick,
198            add it to lastmsgs */
199         p = strchr(msg, ' ');
200         if (p != NULL && p != msg) {
201                 msgnick = g_strndup(msg, (int) (p-msg));
202                 nick = nicklist_find(channel, msgnick);
203                 if (nick == NULL && msgnick[1] != '\0') {
204                         /* probably ':' or ',' or some other
205                            char after nick, try without it */
206                         msgnick[strlen(msgnick)-1] = '\0';
207                         nick = nicklist_find(channel, msgnick);
208                 }
209                 g_free(msgnick);
210                 if (nick != NULL && nick != channel->ownnick)
211                         CHANNEL_LAST_MSG_ADD(channel, nick->nick, TRUE);
212         }
213 }
214
215 static void sig_message_own_private(SERVER_REC *server, const char *msg,
216                                     const char *target, const char *origtarget)
217 {
218         g_return_if_fail(server != NULL);
219         g_return_if_fail(target != NULL);
220
221         if (target != NULL && query_find(server, target) == NULL)
222                 SERVER_LAST_MSG_ADD(server, target);
223 }
224
225 static void sig_nick_removed(CHANNEL_REC *channel, NICK_REC *nick)
226 {
227         MODULE_CHANNEL_REC *mchannel;
228         LAST_MSG_REC *rec;
229
230         mchannel = MODULE_DATA(channel);
231         rec = last_msg_find(mchannel->lastmsgs, nick->nick);
232         if (rec != NULL) last_msg_destroy(&mchannel->lastmsgs, rec);
233 }
234
235 static void sig_nick_changed(CHANNEL_REC *channel, NICK_REC *nick,
236                              const char *oldnick)
237 {
238         MODULE_CHANNEL_REC *mchannel;
239         LAST_MSG_REC *rec;
240
241         mchannel = MODULE_DATA(channel);
242         rec = last_msg_find(mchannel->lastmsgs, oldnick);
243         if (rec != NULL) {
244                 g_free(rec->nick);
245                 rec->nick = g_strdup(nick->nick);
246         }
247 }
248
249 static int last_msg_cmp(LAST_MSG_REC *m1, LAST_MSG_REC *m2)
250 {
251         return m1->time < m2->time ? 1 : -1;
252 }
253
254 /* Complete /MSG from specified server, or from
255    global_lastmsgs if server is NULL */
256 static void completion_msg_server(GSList **list, SERVER_REC *server,
257                                   const char *nick, const char *prefix)
258 {
259         LAST_MSG_REC *msg;
260         GSList *tmp;
261         int len;
262
263         g_return_if_fail(nick != NULL);
264
265         len = strlen(nick);
266         tmp = server == NULL ? global_lastmsgs :
267                 ((MODULE_SERVER_REC *) MODULE_DATA(server))->lastmsgs;
268         for (; tmp != NULL; tmp = tmp->next) {
269                 LAST_MSG_REC *rec = tmp->data;
270
271                 if (len != 0 && g_strncasecmp(rec->nick, nick, len) != 0)
272                         continue;
273
274                 msg = g_new(LAST_MSG_REC, 1);
275                 msg->time = rec->time;
276                 msg->nick = prefix == NULL || *prefix == '\0' ?
277                         g_strdup(rec->nick) :
278                         g_strconcat(prefix, " ", rec->nick, NULL);
279                 *list = g_slist_insert_sorted(*list, msg,
280                                               (GCompareFunc) last_msg_cmp);
281         }
282 }
283
284 /* convert list of LAST_MSG_REC's to list of char* nicks. */
285 static GList *convert_msglist(GSList *msglist)
286 {
287         GList *list;
288
289         list = NULL;
290         while (msglist != NULL) {
291                 LAST_MSG_REC *rec = msglist->data;
292
293                 list = g_list_append(list, rec->nick);
294                 msglist = g_slist_remove(msglist, rec);
295                 g_free(rec);
296         }
297
298         return list;
299 }
300
301 /* Complete /MSG - if `find_server' is NULL, complete nicks from all servers */
302 GList *completion_msg(SERVER_REC *win_server,
303                              SERVER_REC *find_server,
304                              const char *nick, const char *prefix)
305 {
306         GSList *tmp, *list;
307         char *newprefix;
308
309         g_return_val_if_fail(nick != NULL, NULL);
310         if (servers == NULL) return NULL;
311
312         list = NULL;
313         if (find_server != NULL) {
314                 completion_msg_server(&list, find_server, nick, prefix);
315                 return convert_msglist(list);
316         }
317
318         completion_msg_server(&list, NULL, nick, prefix);
319         for (tmp = servers; tmp != NULL; tmp = tmp->next) {
320                 SERVER_REC *rec = tmp->data;
321
322                 if (servers->next == NULL && rec == win_server)
323                         newprefix = g_strdup(prefix);
324                 else {
325                         newprefix = prefix == NULL ?
326                                 g_strdup_printf("-%s", rec->tag) :
327                                 g_strdup_printf("%s -%s", prefix, rec->tag);
328                 }
329
330                 completion_msg_server(&list, rec, nick, newprefix);
331                 g_free_not_null(newprefix);
332         }
333
334         return convert_msglist(list);
335 }
336
337 static void complete_from_nicklist(GList **outlist, CHANNEL_REC *channel,
338                                    const char *nick, const char *suffix)
339 {
340         MODULE_CHANNEL_REC *mchannel;
341         GSList *tmp;
342         GList *ownlist;
343         char *str;
344         int len;
345
346         /* go through the last x nicks who have said something in the channel.
347            nicks of all the "own messages" are placed before others */
348         ownlist = NULL;
349         len = strlen(nick);
350         mchannel = MODULE_DATA(channel);
351         for (tmp = mchannel->lastmsgs; tmp != NULL; tmp = tmp->next) {
352                 LAST_MSG_REC *rec = tmp->data;
353
354                 if (g_strncasecmp(rec->nick, nick, len) == 0 &&
355                     glist_find_icase_string(*outlist, rec->nick) == NULL) {
356                         str = g_strconcat(rec->nick, suffix, NULL);
357                         if (completion_lowercase) g_strdown(str);
358                         if (rec->own)
359                                 ownlist = g_list_append(ownlist, str);
360                         else
361                                 *outlist = g_list_append(*outlist, str);
362                 }
363         }
364
365         *outlist = g_list_concat(ownlist, *outlist);
366 }
367
368 static GList *completion_nicks_nonstrict(CHANNEL_REC *channel,
369                                          const char *nick,
370                                          const char *suffix)
371 {
372         GSList *nicks, *tmp;
373         GList *list;
374         char *tnick, *str, *in, *out;
375         int len, str_len, tmplen;
376
377         g_return_val_if_fail(channel != NULL, NULL);
378
379         list = NULL;
380
381         /* get all nicks from current channel, strip non alnum chars,
382            compare again and add to completion list on matching */
383         len = strlen(nick);
384         nicks = nicklist_getnicks(channel);
385
386         str_len = 80; str = g_malloc(str_len+1);
387         for (tmp = nicks; tmp != NULL; tmp = tmp->next) {
388                 NICK_REC *rec = tmp->data;
389
390                 tmplen = strlen(rec->nick);
391                 if (tmplen > str_len) {
392                         str_len = tmplen*2;
393                         str = g_realloc(str, str_len+1);
394                 }
395
396                 /* remove non alnum chars from nick */
397                 in = rec->nick; out = str;
398                 while (*in != '\0') {
399                         if (i_isalnum(*in))
400                                 *out++ = *in;
401                         in++;
402                 }
403                 *out = '\0';
404
405                 /* add to list if 'cleaned' nick matches */
406                 if (g_strncasecmp(str, nick, len) == 0) {
407                         tnick = g_strconcat(rec->nick, suffix, NULL);
408                         if (completion_lowercase)
409                                 g_strdown(tnick);
410
411                         if (glist_find_icase_string(list, tnick) == NULL)
412                                 list = g_list_append(list, tnick);
413                         else
414                                 g_free(tnick);
415                 }
416
417         }
418         g_free(str);
419         g_slist_free(nicks);
420
421         return list;
422 }
423
424 static GList *completion_channel_nicks(CHANNEL_REC *channel, const char *nick,
425                                        const char *suffix)
426 {
427         GSList *nicks, *tmp;
428         GList *list;
429         char *str;
430         int len;
431
432         g_return_val_if_fail(channel != NULL, NULL);
433         g_return_val_if_fail(nick != NULL, NULL);
434         if (*nick == '\0') return NULL;
435
436         if (suffix != NULL && *suffix == '\0')
437                 suffix = NULL;
438
439         /* put first the nicks who have recently said something */
440         list = NULL;
441         complete_from_nicklist(&list, channel, nick, suffix);
442
443         /* and add the rest of the nicks too */
444         len = strlen(nick);
445         nicks = nicklist_getnicks(channel);
446         for (tmp = nicks; tmp != NULL; tmp = tmp->next) {
447                 NICK_REC *rec = tmp->data;
448
449                 if (g_strncasecmp(rec->nick, nick, len) == 0 &&
450                     rec != channel->ownnick) {
451                         str = g_strconcat(rec->nick, suffix, NULL);
452                         if (completion_lowercase)
453                                 g_strdown(str);
454                         if (glist_find_icase_string(list, str) == NULL)
455                                 list = g_list_append(list, str);
456                         else
457                                 g_free(str);
458                 }
459         }
460         g_slist_free(nicks);
461
462         /* remove non alphanum chars from nick and search again in case
463            list is still NULL ("foo<tab>" would match "_foo_" f.e.) */
464         if (!completion_strict)
465                 list = g_list_concat(list, completion_nicks_nonstrict(channel, nick, suffix));
466         return list;
467 }
468
469 /* append all strings in list2 to list1 that already aren't there and
470    free list2 */
471 static GList *completion_joinlist(GList *list1, GList *list2)
472 {
473         GList *old;
474
475         old = list2;
476         while (list2 != NULL) {
477                 if (!glist_find_icase_string(list1, list2->data))
478                         list1 = g_list_append(list1, list2->data);
479                 else
480                         g_free(list2->data);
481
482                 list2 = list2->next;
483         }
484
485         g_list_free(old);
486         return list1;
487 }
488
489 GList *completion_get_servertags(const char *word)
490 {
491         GList *list;
492         GSList *tmp;
493         int len;
494
495         g_return_val_if_fail(word != NULL, NULL);
496
497         len = strlen(word);
498         list = NULL;
499
500         for (tmp = servers; tmp != NULL; tmp = tmp->next) {
501                 SERVER_REC *rec = tmp->data;
502
503                 if (g_strncasecmp(rec->tag, word, len) == 0) {
504                         if (rec == active_win->active_server)
505                                 list = g_list_prepend(list, g_strdup(rec->tag));
506                         else
507                                 list = g_list_append(list, g_strdup(rec->tag));
508                 }
509
510         }
511
512         return list;
513 }
514
515 GList *completion_get_channels(SERVER_REC *server, const char *word)
516 {
517         GList *list;
518         GSList *tmp;
519         int len;
520
521         g_return_val_if_fail(word != NULL, NULL);
522
523         len = strlen(word);
524         list = NULL;
525
526         /* first get the joined channels */
527         tmp = server == NULL ? NULL : server->channels;
528         for (; tmp != NULL; tmp = tmp->next) {
529                 CHANNEL_REC *rec = tmp->data;
530
531                 if (g_strncasecmp(rec->visible_name, word, len) == 0)
532                         list = g_list_append(list, g_strdup(rec->visible_name));
533                 else if (g_strncasecmp(rec->name, word, len) == 0)
534                         list = g_list_append(list, g_strdup(rec->name));
535         }
536
537         /* get channels from setup */
538         for (tmp = setupchannels; tmp != NULL; tmp = tmp->next) {
539                 CHANNEL_SETUP_REC *rec = tmp->data;
540
541                 if (g_strncasecmp(rec->name, word, len) == 0 &&
542                     glist_find_icase_string(list, rec->name) == NULL)
543                         list = g_list_append(list, g_strdup(rec->name));
544
545         }
546
547         return list;
548 }
549
550 GList *completion_get_aliases(const char *word)
551 {
552         CONFIG_NODE *node;
553         GList *list;
554         GSList *tmp;
555         int len;
556
557         g_return_val_if_fail(word != NULL, NULL);
558
559         len = strlen(word);
560         list = NULL;
561
562         /* get the list of all aliases */
563         node = iconfig_node_traverse("aliases", FALSE);
564         tmp = node == NULL ? NULL : config_node_first(node->value);
565         for (; tmp != NULL; tmp = config_node_next(tmp)) {
566                 node = tmp->data;
567
568                 if (node->type != NODE_TYPE_KEY)
569                         continue;
570
571                 if (len != 0 && g_strncasecmp(node->key, word, len) != 0)
572                         continue;
573
574                 list = g_list_append(list, g_strdup(node->key));
575         }
576         
577         return list;
578 }
579
580 static void complete_window_nicks(GList **list, WINDOW_REC *window,
581                                   const char *word, const char *linestart)
582 {
583         CHANNEL_REC *channel;
584         GList *tmplist;
585         GSList *tmp;
586         const char *nicksuffix;
587
588         nicksuffix = *linestart != '\0' ? NULL : completion_char;
589
590         channel = CHANNEL(window->active);
591
592         /* first the active channel */
593         if (channel != NULL) {
594                 tmplist = completion_channel_nicks(channel, word, nicksuffix);
595                 *list = completion_joinlist(*list, tmplist);
596         }
597
598         if (nicksuffix != NULL) {
599                 /* completing nick at the start of line - probably answering
600                    to some other nick, don't even try to complete from
601                    non-active channels */
602                 return;
603         }
604
605         /* then the rest */
606         for (tmp = window->items; tmp != NULL; tmp = tmp->next) {
607                 channel = CHANNEL(tmp->data);
608                 if (channel != NULL && tmp->data != window->active) {
609                         tmplist = completion_channel_nicks(channel, word,
610                                                            nicksuffix);
611                         *list = completion_joinlist(*list, tmplist);
612                 }
613         }
614 }
615
616 static void sig_complete_word(GList **list, WINDOW_REC *window,
617                               const char *word, const char *linestart,
618                               int *want_space)
619 {
620         SERVER_REC *server;
621         CHANNEL_REC *channel;
622         QUERY_REC *query;
623         char *prefix;
624
625         g_return_if_fail(list != NULL);
626         g_return_if_fail(window != NULL);
627         g_return_if_fail(word != NULL);
628         g_return_if_fail(linestart != NULL);
629
630         server = window->active_server;
631         if (server == NULL && servers != NULL)
632                 server = servers->data;
633
634         if (server != NULL && server_ischannel(server, word)) {
635                 /* probably completing a channel name */
636                 *list = completion_get_channels(window->active_server, word);
637                 return;
638         }
639
640         server = window->active_server;
641         if (server == NULL || !server->connected)
642                 return;
643
644         if (*linestart == '\0' && *word == '\0') {
645                 /* pressed TAB at the start of line - add /MSG */
646                 prefix = g_strdup_printf("%cmsg", *cmdchars);
647                 *list = completion_msg(server, NULL, "", prefix);
648                 if (*list == NULL)
649                         *list = g_list_append(*list, g_strdup(prefix));
650                 g_free(prefix);
651
652                 signal_stop();
653                 return;
654         }
655
656         channel = CHANNEL(window->active);
657         query = QUERY(window->active);
658         if (channel == NULL && query != NULL &&
659             g_strncasecmp(word, query->name, strlen(word)) == 0) {
660                 /* completion in query */
661                 *list = g_list_append(*list, g_strdup(query->name));
662         } else if (channel != NULL) {
663                 /* nick completion .. we could also be completing a nick
664                    after /MSG from nicks in channel */
665                 complete_window_nicks(list, window, word, linestart);
666         } else if (window->level & MSGLEVEL_MSGS) {
667                 /* msgs window, complete /MSG nicks */
668                 *list = g_list_concat(completion_msg(server, NULL, word, NULL), *list);
669         }
670
671         if (*list != NULL) signal_stop();
672 }
673
674 static SERVER_REC *line_get_server(const char *line)
675 {
676         SERVER_REC *server;
677         char *tag, *ptr;
678
679         g_return_val_if_fail(line != NULL, NULL);
680         if (*line != '-') return NULL;
681
682         /* -option found - should be server tag */
683         tag = g_strdup(line+1);
684         ptr = strchr(tag, ' ');
685         if (ptr != NULL) *ptr = '\0';
686
687         server = server_find_tag(tag);
688
689         g_free(tag);
690         return server;
691 }
692
693 static void sig_complete_msg(GList **list, WINDOW_REC *window,
694                              const char *word, const char *line,
695                              int *want_space)
696 {
697         SERVER_REC *server, *msgserver;
698
699         g_return_if_fail(list != NULL);
700         g_return_if_fail(word != NULL);
701         g_return_if_fail(line != NULL);
702
703         server = window->active_server;
704         if (server == NULL || !server->connected)
705                 return;
706
707         msgserver = line_get_server(line);
708         *list = completion_msg(server, msgserver, word, NULL);
709         if (*list != NULL) signal_stop();
710 }
711
712 static void sig_erase_complete_msg(WINDOW_REC *window, const char *word,
713                                    const char *line)
714 {
715         SERVER_REC *server;
716         MODULE_SERVER_REC *mserver;
717         GSList *tmp;
718
719         server = line_get_server(line);
720         if (server == NULL){
721                 server = window->active_server;
722                 if (server == NULL)
723                         return;
724         }
725
726         if (*word == '\0')
727                 return;
728
729         /* check from global list */
730         completion_last_message_remove(word);
731
732         /* check from server specific list */
733         if (server != NULL) {
734                 mserver = MODULE_DATA(server);
735                 for (tmp = mserver->lastmsgs; tmp != NULL; tmp = tmp->next) {
736                         LAST_MSG_REC *rec = tmp->data;
737
738                         if (g_strcasecmp(rec->nick, word) == 0) {
739                                 last_msg_destroy(&mserver->lastmsgs, rec);
740                                 break;
741                         }
742                 }
743
744         }
745 }
746
747 GList *completion_get_chatnets(const char *word)
748 {
749         GList *list;
750         GSList *tmp;
751         int len;
752
753         g_return_val_if_fail(word != NULL, NULL);
754
755         len = strlen(word);
756         list = NULL;
757
758         for (tmp = chatnets; tmp != NULL; tmp = tmp->next) {
759                 CHATNET_REC *rec = tmp->data;
760
761                 if (g_strncasecmp(rec->name, word, len) == 0)
762                         list = g_list_append(list, g_strdup(rec->name));
763         }
764
765         return list;
766 }
767
768 GList *completion_get_servers(const char *word)
769 {
770         GList *list;
771         GSList *tmp;
772         int len;
773
774         g_return_val_if_fail(word != NULL, NULL);
775
776         len = strlen(word);
777         list = NULL;
778
779         for (tmp = setupservers; tmp != NULL; tmp = tmp->next) {
780                 SERVER_SETUP_REC *rec = tmp->data;
781
782                 if (g_strncasecmp(rec->address, word, len) == 0) 
783                         list = g_list_append(list, g_strdup(rec->address));
784         }
785
786         return list;
787 }
788
789 static void sig_complete_connect(GList **list, WINDOW_REC *window,
790                                  const char *word, const char *line, 
791                                  int *want_space)
792 {
793         g_return_if_fail(list != NULL);
794         g_return_if_fail(word != NULL);
795
796         *list = completion_get_chatnets(word);
797         *list = g_list_concat(*list, completion_get_servers(word));
798         if (*list != NULL) signal_stop();
799 }
800
801 static void sig_complete_tag(GList **list, WINDOW_REC *window,
802                              const char *word, const char *line,
803                              int *want_space)
804 {
805         g_return_if_fail(list != NULL);
806         g_return_if_fail(word != NULL);
807
808         *list = completion_get_servertags(word);
809         if (*list != NULL) signal_stop();
810 }
811
812 static void sig_complete_topic(GList **list, WINDOW_REC *window,
813                                const char *word, const char *line,
814                                int *want_space)
815 {
816         const char *topic;
817
818         g_return_if_fail(list != NULL);
819         g_return_if_fail(word != NULL);
820
821         if (*word == '\0' && IS_CHANNEL(window->active)) {
822                 topic = CHANNEL(window->active)->topic;
823                 if (topic != NULL) {
824                         *list = g_list_append(NULL, g_strdup(topic));
825                         signal_stop();
826                 }
827         }
828 }
829
830 static void sig_complete_away(GList **list, WINDOW_REC *window,
831                                const char *word, const char *line,
832                                int *want_space)
833 {
834         const char *reason;
835
836         g_return_if_fail(list != NULL);
837         g_return_if_fail(word != NULL);
838
839         *want_space = FALSE;
840
841         if (*word == '\0' && window->active_server != NULL) {
842                 reason = SERVER(window->active_server)->away_reason;
843                 if (reason != NULL) {
844                         *list = g_list_append(NULL, g_strdup(reason));
845                         signal_stop();
846                 }
847         }
848 }
849
850 static void sig_complete_unalias(GList **list, WINDOW_REC *window,
851                                 const char *word, const char *line,
852                                 int *want_space)
853 {
854         g_return_if_fail(list != NULL);
855         g_return_if_fail(word != NULL);
856
857         *list = completion_get_aliases(word);
858         if (*list != NULL) signal_stop();
859 }
860
861 static void sig_complete_alias(GList **list, WINDOW_REC *window,
862                                 const char *word, const char *line,
863                                 int *want_space)
864 {
865         const char *definition;
866         
867         g_return_if_fail(list != NULL);
868         g_return_if_fail(word != NULL);
869         g_return_if_fail(line != NULL);
870
871         if (*line != '\0') {
872                 if ((definition = alias_find(line)) != NULL) {
873                         *list = g_list_append(NULL, g_strdup(definition));
874                         signal_stop();
875                 }
876         } else {        
877                 *list = completion_get_aliases(word);
878                 if (*list != NULL) signal_stop();
879         }
880 }
881
882
883 static void sig_complete_channel(GList **list, WINDOW_REC *window,
884                                  const char *word, const char *line,
885                                  int *want_space)
886 {
887         g_return_if_fail(list != NULL);
888         g_return_if_fail(word != NULL);
889
890         *list = completion_get_channels(NULL, word);
891         if (*list != NULL) signal_stop();
892 }
893
894 static void sig_complete_server(GList **list, WINDOW_REC *window,
895                                 const char *word, const char *line,
896                                 int *want_space)
897 {
898         g_return_if_fail(list != NULL);
899         g_return_if_fail(word != NULL);
900
901         *list = completion_get_servers(word);
902         if (*list != NULL) signal_stop();
903 }
904
905 /* expand \n, \t and \\ */
906 static char *expand_escapes(const char *line, SERVER_REC *server,
907                             WI_ITEM_REC *item)
908 {
909         char *ptr, *ret;
910         int chr;
911
912         ret = ptr = g_malloc(strlen(line)+1);
913         for (; *line != '\0'; line++) {
914                 if (*line != '\\') {
915                         *ptr++ = *line;
916                         continue;
917                 }
918
919                 line++;
920                 if (*line == '\0') {
921                         *ptr++ = '\\';
922                         break;
923                 }
924
925                 chr = expand_escape(&line);
926                 if (chr == '\r' || chr == '\n') {
927                         /* newline .. we need to send another "send text"
928                            event to handle it (or actually the text before
929                            the newline..) */
930                         if (ret != ptr) {
931                                 *ptr = '\0';
932                                 signal_emit("send text", 3, ret, server, item);
933                                 ptr = ret;
934                         }
935                 } else if (chr != -1) {
936                         /* escaping went ok */
937                         *ptr++ = chr;
938                 } else {
939                         /* unknown escape, add it as-is */
940                         *ptr++ = '\\';
941                         *ptr++ = *line;
942                 }
943         }
944
945         *ptr = '\0';
946         return ret;
947 }
948
949 static char *auto_complete(CHANNEL_REC *channel, const char *line)
950 {
951         GList *comp;
952         const char *p;
953         char *nick, *ret;
954
955         p = strstr(line, completion_char);
956         if (p == NULL)
957                 return NULL;
958
959         nick = g_strndup(line, (int) (p-line));
960
961         ret = NULL;
962         if (nicklist_find(channel, nick) == NULL) {
963                 /* not an exact match, use the first possible completion */
964                 comp = completion_channel_nicks(channel, nick, NULL);
965                 if (comp != NULL) {
966                         ret = g_strconcat(comp->data, p, NULL);
967                         g_list_foreach(comp, (GFunc) g_free, NULL);
968                         g_list_free(comp);
969                 }
970         }
971
972         g_free(nick);
973
974         return ret;
975 }
976
977 static void event_text(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
978 {
979         char *line, *str, *target;
980
981         g_return_if_fail(data != NULL);
982
983         if (item == NULL)
984                 return;
985
986         if (*data == '\0') {
987                 /* empty line, forget it. */
988                 signal_stop();
989                 return;
990         }
991
992         line = settings_get_bool("expand_escapes") ?
993                 expand_escapes(data, server, item) : g_strdup(data);
994
995         /* check for automatic nick completion */
996         if (completion_auto && IS_CHANNEL(item)) {
997                 str = auto_complete(CHANNEL(item), line);
998                 if (str != NULL) {
999                         g_free(line);
1000                         line = str;
1001                 }
1002         }
1003
1004         /* the nick is quoted in case it contains '-' character. also
1005            spaces should work too now :) The nick is also escaped in case
1006            it contains '\' characters */
1007         target = escape_string(window_item_get_target(item));
1008         str = g_strdup_printf(IS_CHANNEL(item) ? "-channel \"%s\" %s" :
1009                               IS_QUERY(item) ? "-nick \"%s\" %s" : "%s %s",
1010                               target, line);
1011         g_free(target);
1012
1013         signal_emit("command msg", 3, str, server, item);
1014
1015         g_free(str);
1016         g_free(line);
1017
1018         signal_stop();
1019 }
1020
1021 static void sig_server_disconnected(SERVER_REC *server)
1022 {
1023         MODULE_SERVER_REC *mserver;
1024
1025         g_return_if_fail(server != NULL);
1026
1027         mserver = MODULE_DATA(server);
1028         while (mserver->lastmsgs)
1029                 last_msg_destroy(&mserver->lastmsgs, mserver->lastmsgs->data);
1030 }
1031
1032 static void sig_channel_destroyed(CHANNEL_REC *channel)
1033 {
1034         MODULE_CHANNEL_REC *mchannel;
1035
1036         g_return_if_fail(channel != NULL);
1037
1038         mchannel = MODULE_DATA(channel);
1039         while (mchannel->lastmsgs != NULL) {
1040                 last_msg_destroy(&mchannel->lastmsgs,
1041                                  mchannel->lastmsgs->data);
1042         }
1043 }
1044
1045 static void read_settings(void)
1046 {
1047         keep_privates_count = settings_get_int("completion_keep_privates");
1048         keep_publics_count = settings_get_int("completion_keep_publics");
1049         completion_lowercase = settings_get_bool("completion_nicks_lowercase");
1050         completion_char = settings_get_str("completion_char");
1051         cmdchars = settings_get_str("cmdchars");
1052         completion_auto = settings_get_bool("completion_auto");
1053         completion_strict = settings_get_bool("completion_strict");
1054
1055         if (*completion_char == '\0') {
1056                 /* this would break.. */
1057                 completion_auto = FALSE;
1058         }
1059 }
1060
1061 void chat_completion_init(void)
1062 {
1063         settings_add_str("completion", "completion_char", ":");
1064         settings_add_bool("completion", "completion_auto", FALSE);
1065         settings_add_int("completion", "completion_keep_publics", 50);
1066         settings_add_int("completion", "completion_keep_privates", 10);
1067         settings_add_bool("completion", "completion_nicks_lowercase", FALSE);
1068         settings_add_bool("completion", "completion_strict", FALSE);
1069
1070         settings_add_bool("lookandfeel", "expand_escapes", FALSE);
1071
1072         read_settings();
1073         signal_add("complete word", (SIGNAL_FUNC) sig_complete_word);
1074         signal_add("complete command msg", (SIGNAL_FUNC) sig_complete_msg);
1075         signal_add("complete command query", (SIGNAL_FUNC) sig_complete_msg);
1076         signal_add("complete command action", (SIGNAL_FUNC) sig_complete_msg);
1077         signal_add("complete erase command msg", (SIGNAL_FUNC) sig_erase_complete_msg);
1078         signal_add("complete erase command query", (SIGNAL_FUNC) sig_erase_complete_msg);
1079         signal_add("complete erase command action", (SIGNAL_FUNC) sig_erase_complete_msg);
1080         signal_add("complete command connect", (SIGNAL_FUNC) sig_complete_connect);
1081         signal_add("complete command server", (SIGNAL_FUNC) sig_complete_connect);
1082         signal_add("complete command disconnect", (SIGNAL_FUNC) sig_complete_tag);
1083         signal_add("complete command reconnect", (SIGNAL_FUNC) sig_complete_tag);
1084         signal_add("complete command topic", (SIGNAL_FUNC) sig_complete_topic);
1085         signal_add("complete command away", (SIGNAL_FUNC) sig_complete_away);
1086         signal_add("complete command unalias", (SIGNAL_FUNC) sig_complete_unalias);
1087         signal_add("complete command alias", (SIGNAL_FUNC) sig_complete_alias);
1088         signal_add("complete command window item move", (SIGNAL_FUNC) sig_complete_channel);
1089         signal_add("complete command server add", (SIGNAL_FUNC) sig_complete_server);
1090         signal_add("complete command server remove", (SIGNAL_FUNC) sig_complete_server);
1091         signal_add("message public", (SIGNAL_FUNC) sig_message_public);
1092         signal_add("message join", (SIGNAL_FUNC) sig_message_join);
1093         signal_add("message private", (SIGNAL_FUNC) sig_message_private);
1094         signal_add("message own_public", (SIGNAL_FUNC) sig_message_own_public);
1095         signal_add("message own_private", (SIGNAL_FUNC) sig_message_own_private);
1096         signal_add("nicklist remove", (SIGNAL_FUNC) sig_nick_removed);
1097         signal_add("nicklist changed", (SIGNAL_FUNC) sig_nick_changed);
1098         signal_add("send text", (SIGNAL_FUNC) event_text);
1099         signal_add("server disconnected", (SIGNAL_FUNC) sig_server_disconnected);
1100         signal_add("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed);
1101         signal_add("setup changed", (SIGNAL_FUNC) read_settings);
1102 }
1103
1104 void chat_completion_deinit(void)
1105 {
1106         while (global_lastmsgs != NULL)
1107                 last_msg_destroy(&global_lastmsgs, global_lastmsgs->data);
1108
1109         signal_remove("complete word", (SIGNAL_FUNC) sig_complete_word);
1110         signal_remove("complete command msg", (SIGNAL_FUNC) sig_complete_msg);
1111         signal_remove("complete command query", (SIGNAL_FUNC) sig_complete_msg);
1112         signal_remove("complete command action", (SIGNAL_FUNC) sig_complete_msg);
1113         signal_remove("complete erase command msg", (SIGNAL_FUNC) sig_erase_complete_msg);
1114         signal_remove("complete erase command query", (SIGNAL_FUNC) sig_erase_complete_msg);
1115         signal_remove("complete erase command action", (SIGNAL_FUNC) sig_erase_complete_msg);
1116         signal_remove("complete command connect", (SIGNAL_FUNC) sig_complete_connect);
1117         signal_remove("complete command server", (SIGNAL_FUNC) sig_complete_connect);
1118         signal_remove("complete command disconnect", (SIGNAL_FUNC) sig_complete_tag);
1119         signal_remove("complete command reconnect", (SIGNAL_FUNC) sig_complete_tag);
1120         signal_remove("complete command topic", (SIGNAL_FUNC) sig_complete_topic);
1121         signal_remove("complete command away", (SIGNAL_FUNC) sig_complete_away);
1122         signal_remove("complete command unalias", (SIGNAL_FUNC) sig_complete_unalias);
1123         signal_remove("complete command alias", (SIGNAL_FUNC) sig_complete_alias);
1124         signal_remove("complete command window item move", (SIGNAL_FUNC) sig_complete_channel);
1125         signal_remove("complete command server add", (SIGNAL_FUNC) sig_complete_server);
1126         signal_remove("complete command server remove", (SIGNAL_FUNC) sig_complete_server);
1127         signal_remove("message public", (SIGNAL_FUNC) sig_message_public);
1128         signal_remove("message join", (SIGNAL_FUNC) sig_message_join);
1129         signal_remove("message private", (SIGNAL_FUNC) sig_message_private);
1130         signal_remove("message own_public", (SIGNAL_FUNC) sig_message_own_public);
1131         signal_remove("message own_private", (SIGNAL_FUNC) sig_message_own_private);
1132         signal_remove("nicklist remove", (SIGNAL_FUNC) sig_nick_removed);
1133         signal_remove("nicklist changed", (SIGNAL_FUNC) sig_nick_changed);
1134         signal_remove("send text", (SIGNAL_FUNC) event_text);
1135         signal_remove("server disconnected", (SIGNAL_FUNC) sig_server_disconnected);
1136         signal_remove("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed);
1137         signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
1138 }