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