a3d92ccde09384fa103e2f07bbf0de2008572247
[runtime.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 "settings.h"
26
27 #include "chatnets.h"
28 #include "servers.h"
29 #include "servers-setup.h"
30 #include "channels.h"
31 #include "channels-setup.h"
32 #include "queries.h"
33 #include "nicklist.h"
34
35 #include "completion.h"
36 #include "window-items.h"
37
38 static int keep_privates_count, keep_publics_count;
39 static int completion_lowercase;
40 static const char *completion_char, *cmdchars;
41 static GSList *global_lastmsgs;
42 static int completion_auto, completion_strict;
43
44 #define SERVER_LAST_MSG_ADD(server, nick) \
45         last_msg_add(&((MODULE_SERVER_REC *) MODULE_DATA(server))->lastmsgs, \
46                      nick, TRUE, keep_privates_count)
47
48 #define CHANNEL_LAST_MSG_ADD(channel, nick, own) \
49         last_msg_add(&((MODULE_CHANNEL_REC *) MODULE_DATA(channel))->lastmsgs, \
50                      nick, own, keep_publics_count)
51
52 static LAST_MSG_REC *last_msg_find(GSList *list, const char *nick)
53 {
54         while (list != NULL) {
55                 LAST_MSG_REC *rec = list->data;
56
57                 if (g_strcasecmp(rec->nick, nick) == 0)
58                         return rec;
59                 list = list->next;
60         }
61
62         return NULL;
63 }
64
65 static void last_msg_dec_owns(GSList *list)
66 {
67         LAST_MSG_REC *rec;
68
69         while (list != NULL) {
70                 rec = list->data;
71                 if (rec->own) rec->own--;
72
73                 list = list->next;
74         }
75 }
76
77 static void last_msg_add(GSList **list, const char *nick, int own, int max)
78 {
79         LAST_MSG_REC *rec;
80
81         rec = last_msg_find(*list, nick);
82         if (rec != NULL) {
83                 /* msg already exists, update it */
84                 *list = g_slist_remove(*list, rec);
85                 if (own)
86                         rec->own = max;
87                 else if (rec->own)
88                         rec->own--;
89         } else {
90                 rec = g_new(LAST_MSG_REC, 1);
91                 rec->nick = g_strdup(nick);
92
93                 if ((int)g_slist_length(*list) == max) {
94                         *list = g_slist_remove(*list,
95                                                g_slist_last(*list)->data);
96                 }
97
98                 rec->own = own ? max : 0;
99         }
100         rec->time = time(NULL);
101
102         last_msg_dec_owns(*list);
103
104         *list = g_slist_prepend(*list, rec);
105 }
106
107 static void last_msg_destroy(GSList **list, LAST_MSG_REC *rec)
108 {
109         *list = g_slist_remove(*list, rec);
110
111         g_free(rec->nick);
112         g_free(rec);
113 }
114
115 void completion_last_message_add(const char *nick)
116 {
117         g_return_if_fail(nick != NULL);
118
119         last_msg_add(&global_lastmsgs, nick, TRUE, keep_privates_count);
120 }
121
122 void completion_last_message_remove(const char *nick)
123 {
124         LAST_MSG_REC *rec;
125
126         g_return_if_fail(nick != NULL);
127
128         rec = last_msg_find(global_lastmsgs, nick);
129         if (rec != NULL) last_msg_destroy(&global_lastmsgs, rec);
130 }
131
132 void completion_last_message_rename(const char *oldnick, const char *newnick)
133 {
134         LAST_MSG_REC *rec;
135
136         g_return_if_fail(oldnick != NULL);
137         g_return_if_fail(newnick != NULL);
138
139         rec = last_msg_find(global_lastmsgs, oldnick);
140         if (rec != NULL) {
141                 g_free(rec->nick);
142                 rec->nick = g_strdup(newnick);
143         }
144 }
145
146 static void sig_message_public(SERVER_REC *server, const char *msg,
147                                const char *nick, const char *address,
148                                const char *target)
149 {
150         CHANNEL_REC *channel;
151         int own;
152
153         channel = channel_find(server, target);
154         if (channel != NULL) {
155                 own = nick_match_msg(channel, msg, server->nick);
156                 CHANNEL_LAST_MSG_ADD(channel, nick, own);
157         }
158 }
159
160 static void sig_message_join(SERVER_REC *server, const char *channel,
161                              const char *nick, const char *address)
162 {
163         CHANNEL_REC *chanrec;
164
165         chanrec = channel_find(server, channel);
166         if (chanrec != NULL)
167                 CHANNEL_LAST_MSG_ADD(chanrec, nick, FALSE);
168 }
169
170 static void sig_message_private(SERVER_REC *server, const char *msg,
171                                 const char *nick, const char *address)
172 {
173         g_return_if_fail(server != NULL);
174         g_return_if_fail(nick != NULL);
175
176         SERVER_LAST_MSG_ADD(server, nick);
177 }
178
179 static void sig_message_own_public(SERVER_REC *server, const char *msg,
180                                    const char *target, const char *origtarget)
181 {
182         CHANNEL_REC *channel;
183         NICK_REC *nick;
184         char *p, *msgnick;
185
186         g_return_if_fail(server != NULL);
187         g_return_if_fail(msg != NULL);
188         if (target == NULL) return;
189
190         channel = channel_find(server, target);
191         if (channel == NULL)
192                 return;
193
194         /* channel msg - if first word in line is nick,
195            add it to lastmsgs */
196         p = strchr(msg, ' ');
197         if (p != NULL && p != msg) {
198                 msgnick = g_strndup(msg, (int) (p-msg));
199                 nick = nicklist_find(channel, msgnick);
200                 if (nick == NULL && msgnick[1] != '\0') {
201                         /* probably ':' or ',' or some other
202                            char after nick, try without it */
203                         msgnick[strlen(msgnick)-1] = '\0';
204                         nick = nicklist_find(channel, msgnick);
205                 }
206                 g_free(msgnick);
207                 if (nick != NULL && nick != channel->ownnick)
208                         CHANNEL_LAST_MSG_ADD(channel, nick->nick, TRUE);
209         }
210 }
211
212 static void sig_message_own_private(SERVER_REC *server, const char *msg,
213                                     const char *target, const char *origtarget)
214 {
215         g_return_if_fail(server != NULL);
216         g_return_if_fail(target != NULL);
217
218         if (target != NULL && query_find(server, target) == NULL)
219                 SERVER_LAST_MSG_ADD(server, target);
220 }
221
222 static void sig_nick_removed(CHANNEL_REC *channel, NICK_REC *nick)
223 {
224         MODULE_CHANNEL_REC *mchannel;
225         LAST_MSG_REC *rec;
226
227         mchannel = MODULE_DATA(channel);
228         rec = last_msg_find(mchannel->lastmsgs, nick->nick);
229         if (rec != NULL) last_msg_destroy(&mchannel->lastmsgs, rec);
230 }
231
232 static void sig_nick_changed(CHANNEL_REC *channel, NICK_REC *nick,
233                              const char *oldnick)
234 {
235         MODULE_CHANNEL_REC *mchannel;
236         LAST_MSG_REC *rec;
237
238         mchannel = MODULE_DATA(channel);
239         rec = last_msg_find(mchannel->lastmsgs, oldnick);
240         if (rec != NULL) {
241                 g_free(rec->nick);
242                 rec->nick = g_strdup(nick->nick);
243         }
244 }
245
246 static int last_msg_cmp(LAST_MSG_REC *m1, LAST_MSG_REC *m2)
247 {
248         return m1->time < m2->time ? 1 : -1;
249 }
250
251 /* Complete /MSG from specified server, or from
252    global_lastmsgs if server is NULL */
253 static void completion_msg_server(GSList **list, SERVER_REC *server,
254                                   const char *nick, const char *prefix)
255 {
256         LAST_MSG_REC *msg;
257         GSList *tmp;
258         int len;
259
260         g_return_if_fail(nick != NULL);
261
262         len = strlen(nick);
263         tmp = server == NULL ? global_lastmsgs :
264                 ((MODULE_SERVER_REC *) MODULE_DATA(server))->lastmsgs;
265         for (; tmp != NULL; tmp = tmp->next) {
266                 LAST_MSG_REC *rec = tmp->data;
267
268                 if (len != 0 && g_strncasecmp(rec->nick, nick, len) != 0)
269                         continue;
270
271                 msg = g_new(LAST_MSG_REC, 1);
272                 msg->time = rec->time;
273                 msg->nick = prefix == NULL || *prefix == '\0' ?
274                         g_strdup(rec->nick) :
275                         g_strconcat(prefix, " ", rec->nick, NULL);
276                 *list = g_slist_insert_sorted(*list, msg,
277                                               (GCompareFunc) last_msg_cmp);
278         }
279 }
280
281 /* convert list of LAST_MSG_REC's to list of char* nicks. */
282 static GList *convert_msglist(GSList *msglist)
283 {
284         GList *list;
285
286         list = NULL;
287         while (msglist != NULL) {
288                 LAST_MSG_REC *rec = msglist->data;
289
290                 list = g_list_append(list, rec->nick);
291                 msglist = g_slist_remove(msglist, rec);
292                 g_free(rec);
293         }
294
295         return list;
296 }
297
298 /* Complete /MSG - if `find_server' is NULL, complete nicks from all servers */
299 static GList *completion_msg(SERVER_REC *win_server,
300                              SERVER_REC *find_server,
301                              const char *nick, const char *prefix)
302 {
303         GSList *tmp, *list;
304         char *newprefix;
305
306         g_return_val_if_fail(nick != NULL, NULL);
307         if (servers == NULL) return NULL;
308
309         list = NULL;
310         if (find_server != NULL) {
311                 completion_msg_server(&list, find_server, nick, prefix);
312                 return convert_msglist(list);
313         }
314
315         completion_msg_server(&list, NULL, nick, prefix);
316         for (tmp = servers; tmp != NULL; tmp = tmp->next) {
317                 SERVER_REC *rec = tmp->data;
318
319                 if (rec == win_server)
320                         newprefix = g_strdup(prefix);
321                 else {
322                         newprefix = prefix == NULL ?
323                                 g_strdup_printf("-%s", rec->tag) :
324                                 g_strdup_printf("%s -%s", prefix, rec->tag);
325                 }
326
327                 completion_msg_server(&list, rec, nick, newprefix);
328                 g_free_not_null(newprefix);
329         }
330
331         return convert_msglist(list);
332 }
333
334 static void complete_from_nicklist(GList **outlist, CHANNEL_REC *channel,
335                                    const char *nick, const char *suffix)
336 {
337         MODULE_CHANNEL_REC *mchannel;
338         GSList *tmp;
339         GList *ownlist;
340         char *str;
341         int len;
342
343         /* go through the last x nicks who have said something in the channel.
344            nicks of all the "own messages" are placed before others */
345         ownlist = NULL;
346         len = strlen(nick);
347         mchannel = MODULE_DATA(channel);
348         for (tmp = mchannel->lastmsgs; tmp != NULL; tmp = tmp->next) {
349                 LAST_MSG_REC *rec = tmp->data;
350
351                 if (g_strncasecmp(rec->nick, nick, len) == 0 &&
352                     glist_find_icase_string(*outlist, rec->nick) == NULL) {
353                         str = g_strconcat(rec->nick, suffix, NULL);
354                         if (completion_lowercase) g_strdown(str);
355                         if (rec->own)
356                                 ownlist = g_list_append(ownlist, str);
357                         else
358                                 *outlist = g_list_append(*outlist, str);
359                 }
360         }
361
362         *outlist = g_list_concat(ownlist, *outlist);
363 }
364
365 static GList *completion_nicks_nonstrict(CHANNEL_REC *channel,
366                                          const char *nick,
367                                          const char *suffix)
368 {
369         GSList *nicks, *tmp;
370         GList *list;
371         char *tnick, *str, *in, *out;
372         int len, str_len, tmplen;
373
374         g_return_val_if_fail(channel != NULL, NULL);
375
376         list = NULL;
377
378         /* get all nicks from current channel, strip non alnum chars,
379            compare again and add to completion list on matching */
380         len = strlen(nick);
381         nicks = nicklist_getnicks(channel);
382
383         str_len = 80; str = g_malloc(str_len+1);
384         for (tmp = nicks; tmp != NULL; tmp = tmp->next) {
385                 NICK_REC *rec = tmp->data;
386
387                 tmplen = strlen(rec->nick);
388                 if (tmplen > str_len) {
389                         str_len = tmplen*2;
390                         str = g_realloc(str, str_len+1);
391                 }
392
393                 /* remove non alnum chars from nick */
394                 in = rec->nick; out = str;
395                 while (*in != '\0') {
396                         if (i_isalnum(*in))
397                                 *out++ = *in;
398                         in++;
399                 }
400                 *out = '\0';
401
402                 /* add to list if 'cleaned' nick matches */
403                 if (g_strncasecmp(str, nick, len) == 0) {
404                         tnick = g_strconcat(rec->nick, suffix, NULL);
405                         if (completion_lowercase)
406                                 g_strdown(tnick);
407
408                         if (glist_find_icase_string(list, tnick) == NULL)
409                                 list = g_list_append(list, tnick);
410                         else
411                                 g_free(tnick);
412                 }
413
414         }
415         g_free(str);
416         g_slist_free(nicks);
417
418         return list;
419 }
420
421 static GList *completion_channel_nicks(CHANNEL_REC *channel, const char *nick,
422                                        const char *suffix)
423 {
424         GSList *nicks, *tmp;
425         GList *list;
426         char *str;
427         int len;
428
429         g_return_val_if_fail(channel != NULL, NULL);
430         g_return_val_if_fail(nick != NULL, NULL);
431         if (*nick == '\0') return NULL;
432
433         if (suffix != NULL && *suffix == '\0')
434                 suffix = NULL;
435
436         /* put first the nicks who have recently said something */
437         list = NULL;
438         complete_from_nicklist(&list, channel, nick, suffix);
439
440         /* and add the rest of the nicks too */
441         len = strlen(nick);
442         nicks = nicklist_getnicks(channel);
443         for (tmp = nicks; tmp != NULL; tmp = tmp->next) {
444                 NICK_REC *rec = tmp->data;
445
446                 if (g_strncasecmp(rec->nick, nick, len) == 0 &&
447                     rec != channel->ownnick) {
448                         str = g_strconcat(rec->nick, suffix, NULL);
449                         if (completion_lowercase)
450                                 g_strdown(str);
451                         if (glist_find_icase_string(list, str) == NULL)
452                                 list = g_list_append(list, str);
453                         else
454                                 g_free(str);
455                 }
456         }
457         g_slist_free(nicks);
458
459         /* remove non alphanum chars from nick and search again in case
460            list is still NULL ("foo<tab>" would match "_foo_" f.e.) */
461         if (!completion_strict)
462                 list = g_list_concat(list, completion_nicks_nonstrict(channel, nick, suffix));
463         return list;
464 }
465
466 /* append all strings in list2 to list1 that already aren't there and
467    free list2 */
468 static GList *completion_joinlist(GList *list1, GList *list2)
469 {
470         GList *old;
471
472         old = list2;
473         while (list2 != NULL) {
474                 if (!glist_find_icase_string(list1, list2->data))
475                         list1 = g_list_append(list1, list2->data);
476                 else
477                         g_free(list2->data);
478
479                 list2 = list2->next;
480         }
481
482         g_list_free(old);
483         return list1;
484 }
485
486 GList *completion_get_channels(SERVER_REC *server, const char *word)
487 {
488         GList *list;
489         GSList *tmp;
490         int len;
491
492         g_return_val_if_fail(word != NULL, NULL);
493         g_return_val_if_fail(*word != '\0', NULL);
494
495         len = strlen(word);
496         list = NULL;
497
498         /* first get the joined channels */
499         tmp = server == NULL ? NULL : server->channels;
500         for (; tmp != NULL; tmp = tmp->next) {
501                 CHANNEL_REC *rec = tmp->data;
502
503                 if (g_strncasecmp(rec->name, word, len) == 0)
504                         list = g_list_append(list, g_strdup(rec->name));
505         }
506
507         /* get channels from setup */
508         for (tmp = setupchannels; tmp != NULL; tmp = tmp->next) {
509                 CHANNEL_SETUP_REC *rec = tmp->data;
510
511                 if (g_strncasecmp(rec->name, word, len) == 0 &&
512                     glist_find_icase_string(list, rec->name) == NULL)
513                         list = g_list_append(list, g_strdup(rec->name));
514
515         }
516
517         return list;
518 }
519
520 static void complete_window_nicks(GList **list, WINDOW_REC *window,
521                                   const char *word, const char *linestart)
522 {
523         CHANNEL_REC *channel;
524         GList *tmplist;
525         GSList *tmp;
526         const char *nicksuffix;
527
528         nicksuffix = *linestart != '\0' ? NULL : completion_char;
529
530         channel = CHANNEL(window->active);
531
532         /* first the active channel */
533         if (channel != NULL) {
534                 tmplist = completion_channel_nicks(channel, word, nicksuffix);
535                 *list = completion_joinlist(*list, tmplist);
536         }
537
538         if (nicksuffix != NULL) {
539                 /* completing nick at the start of line - probably answering
540                    to some other nick, don't even try to complete from
541                    non-active channels */
542                 return;
543         }
544
545         /* then the rest */
546         for (tmp = window->items; tmp != NULL; tmp = tmp->next) {
547                 channel = CHANNEL(tmp->data);
548                 if (channel != NULL && tmp->data != window->active) {
549                         tmplist = completion_channel_nicks(channel, word,
550                                                            nicksuffix);
551                         *list = completion_joinlist(*list, tmplist);
552                 }
553         }
554 }
555
556 static void sig_complete_word(GList **list, WINDOW_REC *window,
557                               const char *word, const char *linestart,
558                               int *want_space)
559 {
560         SERVER_REC *server;
561         CHANNEL_REC *channel;
562         QUERY_REC *query;
563         char *prefix;
564
565         g_return_if_fail(list != NULL);
566         g_return_if_fail(window != NULL);
567         g_return_if_fail(word != NULL);
568         g_return_if_fail(linestart != NULL);
569
570         server = window->active_server;
571         if (server == NULL && servers != NULL)
572                 server = servers->data;
573
574         if (server != NULL && server_ischannel(server, word)) {
575                 /* probably completing a channel name */
576                 *list = completion_get_channels(window->active_server, word);
577                 return;
578         }
579
580         server = window->active_server;
581         if (server == NULL || !server->connected)
582                 return;
583
584         if (*linestart == '\0' && *word == '\0') {
585                 /* pressed TAB at the start of line - add /MSG */
586                 prefix = g_strdup_printf("%cmsg", *cmdchars);
587                 *list = completion_msg(server, NULL, "", prefix);
588                 if (*list == NULL)
589                         *list = g_list_append(*list, g_strdup(prefix));
590                 g_free(prefix);
591
592                 signal_stop();
593                 return;
594         }
595
596         channel = CHANNEL(window->active);
597         query = QUERY(window->active);
598         if (channel == NULL && query != NULL &&
599             g_strncasecmp(word, query->name, strlen(word)) == 0) {
600                 /* completion in query */
601                 *list = g_list_append(*list, g_strdup(query->name));
602         } else if (channel != NULL) {
603                 /* nick completion .. we could also be completing a nick
604                    after /MSG from nicks in channel */
605                 complete_window_nicks(list, window, word, linestart);
606         }
607
608         if (*list != NULL) signal_stop();
609 }
610
611 static SERVER_REC *line_get_server(const char *line)
612 {
613         SERVER_REC *server;
614         char *tag, *ptr;
615
616         g_return_val_if_fail(line != NULL, NULL);
617         if (*line != '-') return NULL;
618
619         /* -option found - should be server tag */
620         tag = g_strdup(line+1);
621         ptr = strchr(tag, ' ');
622         if (ptr != NULL) *ptr = '\0';
623
624         server = server_find_tag(tag);
625
626         g_free(tag);
627         return server;
628 }
629
630 static void sig_complete_msg(GList **list, WINDOW_REC *window,
631                              const char *word, const char *line,
632                              int *want_space)
633 {
634         SERVER_REC *server, *msgserver;
635
636         g_return_if_fail(list != NULL);
637         g_return_if_fail(word != NULL);
638         g_return_if_fail(line != NULL);
639
640         server = window->active_server;
641         if (server == NULL || !server->connected)
642                 return;
643
644         msgserver = line_get_server(line);
645         *list = completion_msg(server, msgserver, word, NULL);
646         if (*list != NULL) signal_stop();
647 }
648
649 static void sig_erase_complete_msg(WINDOW_REC *window, const char *word,
650                                    const char *line)
651 {
652         SERVER_REC *server;
653         MODULE_SERVER_REC *mserver;
654         GSList *tmp;
655
656         server = line_get_server(line);
657         if (server == NULL){
658                 server = window->active_server;
659                 if (server == NULL)
660                         return;
661         }
662
663         if (*word == '\0')
664                 return;
665
666         /* check from global list */
667         completion_last_message_remove(word);
668
669         /* check from server specific list */
670         if (server != NULL) {
671                 mserver = MODULE_DATA(server);
672                 for (tmp = mserver->lastmsgs; tmp != NULL; tmp = tmp->next) {
673                         LAST_MSG_REC *rec = tmp->data;
674
675                         if (g_strcasecmp(rec->nick, word) == 0) {
676                                 last_msg_destroy(&mserver->lastmsgs, rec);
677                                 break;
678                         }
679                 }
680
681         }
682 }
683
684 GList *completion_get_chatnets(const char *word)
685 {
686         GList *list;
687         GSList *tmp;
688         int len;
689
690         g_return_val_if_fail(word != NULL, NULL);
691
692         len = strlen(word);
693         list = NULL;
694
695         for (tmp = chatnets; tmp != NULL; tmp = tmp->next) {
696                 CHATNET_REC *rec = tmp->data;
697
698                 if (g_strncasecmp(rec->name, word, len) == 0)
699                         list = g_list_append(list, g_strdup(rec->name));
700         }
701
702         return list;
703 }
704
705 GList *completion_get_servers(const char *word)
706 {
707         GList *list;
708         GSList *tmp;
709         int len;
710
711         g_return_val_if_fail(word != NULL, NULL);
712
713         len = strlen(word);
714         list = NULL;
715
716         for (tmp = setupservers; tmp != NULL; tmp = tmp->next) {
717                 SERVER_SETUP_REC *rec = tmp->data;
718
719                 if (g_strncasecmp(rec->address, word, len) == 0) 
720                         list = g_list_append(list, g_strdup(rec->address));
721         }
722
723         return list;
724 }
725
726 static void sig_complete_connect(GList **list, WINDOW_REC *window,
727                                  const char *word, const char *line, 
728                                  int *want_space)
729 {
730         g_return_if_fail(list != NULL);
731         g_return_if_fail(word != NULL);
732
733         *list = completion_get_chatnets(word);
734         *list = g_list_concat(*list, completion_get_servers(word));
735         if (*list != NULL) signal_stop();
736 }
737
738 static void sig_complete_topic(GList **list, WINDOW_REC *window,
739                                const char *word, const char *line,
740                                int *want_space)
741 {
742         const char *topic;
743
744         g_return_if_fail(list != NULL);
745         g_return_if_fail(word != NULL);
746
747         if (*word == '\0' && IS_CHANNEL(window->active)) {
748                 topic = CHANNEL(window->active)->topic;
749                 if (topic != NULL) {
750                         *list = g_list_append(NULL, g_strdup(topic));
751                         signal_stop();
752                 }
753         }
754 }
755
756 /* expand \n, \t and \\ */
757 static char *expand_escapes(const char *line, SERVER_REC *server,
758                             WI_ITEM_REC *item)
759 {
760         char *ptr, *ret;
761         int chr;
762
763         ret = ptr = g_malloc(strlen(line)+1);
764         for (; *line != '\0'; line++) {
765                 if (*line != '\\') {
766                         *ptr++ = *line;
767                         continue;
768                 }
769
770                 line++;
771                 if (*line == '\0') {
772                         *ptr++ = '\\';
773                         break;
774                 }
775
776                 chr = expand_escape(&line);
777                 if (chr == '\r' || chr == '\n') {
778                         /* newline .. we need to send another "send text"
779                            event to handle it (or actually the text before
780                            the newline..) */
781                         if (ret != ptr) {
782                                 *ptr = '\0';
783                                 signal_emit("send text", 3, ret, server, item);
784                                 ptr = ret;
785                         }
786                 } else if (chr != -1) {
787                         /* escaping went ok */
788                         *ptr++ = chr;
789                 } else {
790                         /* unknown escape, add it as-is */
791                         *ptr++ = '\\';
792                         *ptr++ = *line;
793                 }
794         }
795
796         *ptr = '\0';
797         return ret;
798 }
799
800 static char *auto_complete(CHANNEL_REC *channel, const char *line)
801 {
802         GList *comp;
803         const char *p;
804         char *nick, *ret;
805
806         p = strstr(line, completion_char);
807         if (p == NULL)
808                 return NULL;
809
810         nick = g_strndup(line, (int) (p-line));
811
812         ret = NULL;
813         if (nicklist_find(channel, nick) == NULL) {
814                 /* not an exact match, use the first possible completion */
815                 comp = completion_channel_nicks(channel, nick, NULL);
816                 if (comp != NULL) {
817                         ret = g_strconcat(comp->data, p, NULL);
818                         g_list_foreach(comp, (GFunc) g_free, NULL);
819                         g_list_free(comp);
820                 }
821         }
822
823         g_free(nick);
824
825         return ret;
826 }
827
828 static void event_text(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
829 {
830         char *line, *str;
831
832         g_return_if_fail(data != NULL);
833         if (item == NULL) return;
834
835         line = settings_get_bool("expand_escapes") ?
836                 expand_escapes(data, server, item) : g_strdup(data);
837
838         /* check for automatic nick completion */
839         if (completion_auto && IS_CHANNEL(item)) {
840                 str = auto_complete(CHANNEL(item), line);
841                 if (str != NULL) {
842                         g_free(line);
843                         line = str;
844                 }
845         }
846
847         str = g_strdup_printf(IS_CHANNEL(item) ? "-channel %s %s" :
848                               IS_QUERY(item) ? "-nick %s %s" : "%s %s",
849                               item->name, line);
850
851         signal_emit("command msg", 3, str, server, item);
852
853         g_free(str);
854         g_free(line);
855
856         signal_stop();
857 }
858
859 static void sig_server_disconnected(SERVER_REC *server)
860 {
861         MODULE_SERVER_REC *mserver;
862
863         g_return_if_fail(server != NULL);
864
865         mserver = MODULE_DATA(server);
866         while (mserver->lastmsgs)
867                 last_msg_destroy(&mserver->lastmsgs, mserver->lastmsgs->data);
868 }
869
870 static void sig_channel_destroyed(CHANNEL_REC *channel)
871 {
872         MODULE_CHANNEL_REC *mchannel;
873
874         g_return_if_fail(channel != NULL);
875
876         mchannel = MODULE_DATA(channel);
877         while (mchannel->lastmsgs != NULL) {
878                 last_msg_destroy(&mchannel->lastmsgs,
879                                  mchannel->lastmsgs->data);
880         }
881 }
882
883 static void read_settings(void)
884 {
885         keep_privates_count = settings_get_int("completion_keep_privates");
886         keep_publics_count = settings_get_int("completion_keep_publics");
887         completion_lowercase = settings_get_bool("completion_nicks_lowercase");
888         completion_char = settings_get_str("completion_char");
889         cmdchars = settings_get_str("cmdchars");
890         completion_auto = settings_get_bool("completion_auto");
891         completion_strict = settings_get_bool("completion_strict");
892
893         if (*completion_char == '\0') {
894                 /* this would break.. */
895                 completion_auto = FALSE;
896         }
897 }
898
899 void chat_completion_init(void)
900 {
901         settings_add_str("completion", "completion_char", ":");
902         settings_add_bool("completion", "completion_auto", FALSE);
903         settings_add_int("completion", "completion_keep_publics", 50);
904         settings_add_int("completion", "completion_keep_privates", 10);
905         settings_add_bool("completion", "expand_escapes", FALSE);
906         settings_add_bool("completion", "completion_nicks_lowercase", FALSE);
907         settings_add_bool("completion", "completion_strict", FALSE);
908
909         read_settings();
910         signal_add("complete word", (SIGNAL_FUNC) sig_complete_word);
911         signal_add("complete command msg", (SIGNAL_FUNC) sig_complete_msg);
912         signal_add("complete command query", (SIGNAL_FUNC) sig_complete_msg);
913         signal_add("complete erase command msg", (SIGNAL_FUNC) sig_erase_complete_msg);
914         signal_add("complete erase command query", (SIGNAL_FUNC) sig_erase_complete_msg);
915         signal_add("complete command connect", (SIGNAL_FUNC) sig_complete_connect);
916         signal_add("complete command server", (SIGNAL_FUNC) sig_complete_connect);
917         signal_add("complete command topic", (SIGNAL_FUNC) sig_complete_topic);
918         signal_add("message public", (SIGNAL_FUNC) sig_message_public);
919         signal_add("message join", (SIGNAL_FUNC) sig_message_join);
920         signal_add("message private", (SIGNAL_FUNC) sig_message_private);
921         signal_add("message own_public", (SIGNAL_FUNC) sig_message_own_public);
922         signal_add("message own_private", (SIGNAL_FUNC) sig_message_own_private);
923         signal_add("nicklist remove", (SIGNAL_FUNC) sig_nick_removed);
924         signal_add("nicklist changed", (SIGNAL_FUNC) sig_nick_changed);
925         signal_add("send text", (SIGNAL_FUNC) event_text);
926         signal_add("server disconnected", (SIGNAL_FUNC) sig_server_disconnected);
927         signal_add("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed);
928         signal_add("setup changed", (SIGNAL_FUNC) read_settings);
929 }
930
931 void chat_completion_deinit(void)
932 {
933         while (global_lastmsgs != NULL)
934                 last_msg_destroy(&global_lastmsgs, global_lastmsgs->data);
935
936         signal_remove("complete word", (SIGNAL_FUNC) sig_complete_word);
937         signal_remove("complete command msg", (SIGNAL_FUNC) sig_complete_msg);
938         signal_remove("complete command query", (SIGNAL_FUNC) sig_complete_msg);
939         signal_remove("complete erase command msg", (SIGNAL_FUNC) sig_erase_complete_msg);
940         signal_remove("complete erase command query", (SIGNAL_FUNC) sig_erase_complete_msg);
941         signal_remove("complete command connect", (SIGNAL_FUNC) sig_complete_connect);
942         signal_remove("complete command server", (SIGNAL_FUNC) sig_complete_connect);
943         signal_remove("complete command topic", (SIGNAL_FUNC) sig_complete_topic);
944         signal_remove("message public", (SIGNAL_FUNC) sig_message_public);
945         signal_remove("message join", (SIGNAL_FUNC) sig_message_join);
946         signal_remove("message private", (SIGNAL_FUNC) sig_message_private);
947         signal_remove("message own_public", (SIGNAL_FUNC) sig_message_own_public);
948         signal_remove("message own_private", (SIGNAL_FUNC) sig_message_own_private);
949         signal_remove("nicklist remove", (SIGNAL_FUNC) sig_nick_removed);
950         signal_remove("nicklist changed", (SIGNAL_FUNC) sig_nick_changed);
951         signal_remove("send text", (SIGNAL_FUNC) event_text);
952         signal_remove("server disconnected", (SIGNAL_FUNC) sig_server_disconnected);
953         signal_remove("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed);
954         signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
955 }