Merge Irssi 0.8.16-rc1
[silc.git] / apps / irssi / src / fe-common / core / completion.c
1 /*
2  completion.c : irssi
3
4     Copyright (C) 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 along
17     with this program; if not, write to the Free Software Foundation, Inc.,
18     51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 */
20
21 #include "module.h"
22 #include "module-formats.h"
23 #include "signals.h"
24 #include "commands.h"
25 #include "levels.h"
26 #include "misc.h"
27 #include "lib-config/iconfig.h"
28 #include "settings.h"
29
30 #include "completion.h"
31 #include "printtext.h"
32
33 static GList *complist; /* list of commands we're currently completing */
34 static char *last_line;
35 static int last_want_space, last_line_pos;
36
37 #define isseparator_notspace(c) \
38         ((c) == ',')
39
40 #define isseparator(c) \
41         ((c) == ' ' || isseparator_notspace(c))
42
43 void chat_completion_init(void);
44 void chat_completion_deinit(void);
45
46 static const char *completion_find(const char *key, int automatic)
47 {
48         CONFIG_NODE *node;
49
50         node = iconfig_node_traverse("completions", FALSE);
51         if (node == NULL || node->type != NODE_TYPE_BLOCK)
52                 return NULL;
53
54         node = config_node_section(node, key, -1);
55         if (node == NULL)
56                 return NULL;
57
58         if (automatic && !config_node_get_bool(node, "auto", FALSE))
59                 return NULL;
60
61         return config_node_get_str(node, "value", NULL);
62 }
63
64 /* Return whole word at specified position in string */
65 static char *get_word_at(const char *str, int pos, char **startpos)
66 {
67         const char *start, *end;
68
69         g_return_val_if_fail(str != NULL, NULL);
70         g_return_val_if_fail(pos >= 0, NULL);
71
72         /* get previous word if char at `pos' is space */
73         start = str+pos;
74         while (start > str && isseparator(start[-1])) start--;
75
76         end = start;
77         while (start > str && !isseparator(start[-1])) start--;
78         while (*end != '\0' && !isseparator(*end)) end++;
79         while (*end != '\0' && isseparator_notspace(*end)) end++;
80
81         *startpos = (char *) start;
82         return g_strndup(start, (int) (end-start));
83 }
84
85 /* automatic word completion - called when space/enter is pressed */
86 char *auto_word_complete(const char *line, int *pos)
87 {
88         GString *result;
89         const char *replace;
90         char *word, *wordstart, *ret;
91         int startpos;
92
93         g_return_val_if_fail(line != NULL, NULL);
94         g_return_val_if_fail(pos != NULL, NULL);
95
96         word = get_word_at(line, *pos, &wordstart);
97         startpos = (int) (wordstart-line);
98
99         result = g_string_new(line);
100         g_string_erase(result, startpos, strlen(word));
101
102         /* check for words in autocompletion list */
103         replace = completion_find(word, TRUE);
104         if (replace == NULL) {
105                 ret = NULL;
106                 g_string_free(result, TRUE);
107         } else {
108                 *pos = startpos+strlen(replace);
109
110                 g_string_insert(result, startpos, replace);
111                 ret = result->str;
112                 g_string_free(result, FALSE);
113         }
114
115         g_free(word);
116         return ret;
117 }
118
119 static void free_completions(void)
120 {
121         complist = g_list_first(complist);
122
123         g_list_foreach(complist, (GFunc) g_free, NULL);
124         g_list_free(complist);
125         complist = NULL;
126
127         g_free_and_null(last_line);
128 }
129
130 /* manual word completion - called when TAB is pressed */
131 char *word_complete(WINDOW_REC *window, const char *line, int *pos, int erase, int backward)
132 {
133         static int startpos = 0, wordlen = 0;
134         int old_startpos, old_wordlen;
135
136         GString *result;
137         char *word, *wordstart, *linestart, *ret;
138         int continue_complete, want_space;
139
140         g_return_val_if_fail(line != NULL, NULL);
141         g_return_val_if_fail(pos != NULL, NULL);
142
143         continue_complete = complist != NULL && *pos == last_line_pos &&
144                 strcmp(line, last_line) == 0;
145
146         if (erase && !continue_complete)
147                 return NULL;
148
149         old_startpos = startpos;
150         old_wordlen = wordlen;
151
152         if (!erase && continue_complete) {
153                 word = NULL;
154                 linestart = NULL;
155         } else {
156                 /* get the word we want to complete */
157                 word = get_word_at(line, *pos, &wordstart);
158                 startpos = (int) (wordstart-line);
159                 wordlen = strlen(word);
160
161                 /* get the start of line until the word we're completing */
162                 if (isseparator(*line)) {
163                         /* empty space at the start of line */
164                         if (wordstart == line)
165                                 wordstart += strlen(wordstart);
166                 } else {
167                         while (wordstart > line && isseparator(wordstart[-1]))
168                                 wordstart--;
169                 }
170                 linestart = g_strndup(line, (int) (wordstart-line));
171
172                 /* completions usually add space after the word, that makes
173                    things a bit harder. When continuing a completion
174                    "/msg nick1 "<tab> we have to cycle to nick2, etc.
175                    BUT if we start completion with "/msg "<tab>, we don't
176                    want to complete the /msg word, but instead complete empty
177                    word with /msg being in linestart. */
178                 if (!erase && *pos > 0 && line[*pos-1] == ' ' &&
179                     (*linestart == '\0' || wordstart[-1] != ' ')) {
180                         char *old;
181
182                         old = linestart;
183                         linestart = *linestart == '\0' ?
184                                 g_strdup(word) :
185                                 g_strconcat(linestart, " ", word, NULL);
186                         g_free(old);
187
188                         g_free(word);
189                         word = g_strdup("");
190                         startpos = strlen(linestart)+1;
191                         wordlen = 0;
192                 }
193
194         }
195
196         if (erase) {
197                 signal_emit("complete erase", 3, window, word, linestart);
198
199                 /* jump to next completion */
200                 startpos = old_startpos;
201                 wordlen = old_wordlen;
202         }
203
204         if (continue_complete) {
205                 /* complete from old list */
206                 if (backward)
207                         complist = complist->prev != NULL ? complist->prev :
208                                 g_list_last(complist);
209                 else
210                         complist = complist->next != NULL ? complist->next :
211                                 g_list_first(complist);
212                 want_space = last_want_space;
213         } else {
214                 /* get new completion list */
215                 free_completions();
216
217                 want_space = TRUE;
218                 signal_emit("complete word", 5, &complist, window, word, linestart, &want_space);
219                 last_want_space = want_space;
220         }
221
222         g_free(linestart);
223         g_free(word);
224
225         if (complist == NULL)
226                 return NULL;
227
228         /* word completed */
229         *pos = startpos+strlen(complist->data);
230
231         /* replace the word in line - we need to return
232            a full new line */
233         result = g_string_new(line);
234         g_string_erase(result, startpos, wordlen);
235         g_string_insert(result, startpos, complist->data);
236
237         if (want_space) {
238                 if (!isseparator(result->str[*pos]))
239                         g_string_insert_c(result, *pos, ' ');
240                 (*pos)++;
241         }
242
243         wordlen = strlen(complist->data);
244         last_line_pos = *pos;
245         g_free_not_null(last_line);
246         last_line = g_strdup(result->str);
247
248         ret = result->str;
249         g_string_free(result, FALSE);
250         return ret;
251 }
252
253 #define IS_CURRENT_DIR(dir) \
254         ((dir)[0] == '.' && ((dir)[1] == '\0' || (dir)[1] == G_DIR_SEPARATOR))
255
256 #define USE_DEFAULT_PATH(path, default_path) \
257         ((!g_path_is_absolute(path) || IS_CURRENT_DIR(path)) && \
258          default_path != NULL)
259
260 static GList *list_add_file(GList *list, const char *name, const char *default_path)
261 {
262         struct stat statbuf;
263         char *fname;
264
265         g_return_val_if_fail(name != NULL, NULL);
266
267         fname = convert_home(name);
268         if (USE_DEFAULT_PATH(fname, default_path)) {
269                 g_free(fname);
270                 fname = g_strconcat(default_path, G_DIR_SEPARATOR_S,
271                                     name, NULL);
272         }
273         if (stat(fname, &statbuf) == 0) {
274                 list = g_list_append(list, !S_ISDIR(statbuf.st_mode) ? g_strdup(name) :
275                                      g_strconcat(name, G_DIR_SEPARATOR_S, NULL));
276         }
277
278         g_free(fname);
279         return list;
280 }
281
282 GList *filename_complete(const char *path, const char *default_path)
283 {
284         GList *list;
285         DIR *dirp;
286         struct dirent *dp;
287         const char *basename;
288         char *realpath, *dir, *name;
289         int len;
290
291         g_return_val_if_fail(path != NULL, NULL);
292
293         list = NULL;
294
295         /* get directory part of the path - expand ~/ */
296         realpath = convert_home(path);
297         if (USE_DEFAULT_PATH(realpath, default_path)) {
298                 g_free(realpath);
299                 realpath = g_strconcat(default_path, G_DIR_SEPARATOR_S,
300                                        path, NULL);
301         }
302
303         /* open directory for reading */
304         dir = g_path_get_dirname(realpath);
305         dirp = opendir(dir);
306         g_free(dir);
307         g_free(realpath);
308
309         if (dirp == NULL)
310                 return NULL;
311
312         dir = g_path_get_dirname(path);
313         if (*dir == G_DIR_SEPARATOR && dir[1] == '\0') {
314                 /* completing file in root directory */
315                 *dir = '\0';
316         } else if (IS_CURRENT_DIR(dir) && !IS_CURRENT_DIR(path)) {
317                 /* completing file in default_path
318                    (path not set, and leave it that way) */
319                 g_free_and_null(dir);
320         }
321
322         basename = g_basename(path);
323         len = strlen(basename);
324
325         /* add all files in directory to completion list */
326         while ((dp = readdir(dirp)) != NULL) {
327                 if (dp->d_name[0] == '.') {
328                         if (dp->d_name[1] == '\0' ||
329                             (dp->d_name[1] == '.' && dp->d_name[2] == '\0'))
330                                 continue; /* skip . and .. */
331
332                         if (basename[0] != '.')
333                                 continue;
334                 }
335
336                 if (len == 0 || strncmp(dp->d_name, basename, len) == 0) {
337                         name = dir == NULL ? g_strdup(dp->d_name) :
338                                 g_strdup_printf("%s"G_DIR_SEPARATOR_S"%s", dir, dp->d_name);
339                         list = list_add_file(list, name, default_path);
340                         g_free(name);
341                 }
342         }
343         closedir(dirp);
344
345         g_free_not_null(dir);
346         return list;
347 }
348
349 static GList *completion_get_settings(const char *key, SettingType type)
350 {
351         GList *complist;
352         GSList *tmp, *sets;
353         int len;
354
355         g_return_val_if_fail(key != NULL, NULL);
356
357         sets = settings_get_sorted();
358
359         len = strlen(key);
360         complist = NULL;
361         for (tmp = sets; tmp != NULL; tmp = tmp->next) {
362                 SETTINGS_REC *rec = tmp->data;
363
364                 if ((type == -1 || rec->type == type) &&
365                     g_strncasecmp(rec->key, key, len) == 0)
366                         complist = g_list_insert_sorted(complist, g_strdup(rec->key), (GCompareFunc) g_istr_cmp);
367         }
368         g_slist_free(sets);
369         return complist;
370 }
371
372 static GList *completion_get_aliases(const char *alias, char cmdchar)
373 {
374         CONFIG_NODE *node;
375         GList *complist;
376         GSList *tmp;
377         char *word;
378         int len;
379
380         g_return_val_if_fail(alias != NULL, NULL);
381
382         /* get list of aliases from mainconfig */
383         node = iconfig_node_traverse("aliases", FALSE);
384         tmp = node == NULL ? NULL : config_node_first(node->value);
385
386         len = strlen(alias);
387         complist = NULL;
388         for (; tmp != NULL; tmp = config_node_next(tmp)) {
389                 CONFIG_NODE *node = tmp->data;
390
391                 if (node->type != NODE_TYPE_KEY)
392                         continue;
393
394                 if (g_strncasecmp(node->key, alias, len) == 0) {
395                         word = g_strdup_printf("%c%s", cmdchar, node->key);
396                         /* add matching alias to completion list, aliases will
397                            be appended after command completions and kept in
398                            uppercase to show it's an alias */
399                         if (glist_find_icase_string(complist, word) == NULL)
400                                 complist = g_list_insert_sorted(complist, word, (GCompareFunc) g_istr_cmp);
401                         else
402                                 g_free(word);
403                 }
404         }
405         return complist;
406 }
407
408 static GList *completion_get_commands(const char *cmd, char cmdchar)
409 {
410         GList *complist;
411         GSList *tmp;
412         char *word;
413         int len;
414
415         g_return_val_if_fail(cmd != NULL, NULL);
416
417         len = strlen(cmd);
418         complist = NULL;
419         for (tmp = commands; tmp != NULL; tmp = tmp->next) {
420                 COMMAND_REC *rec = tmp->data;
421
422                 if (strchr(rec->cmd, ' ') != NULL)
423                         continue;
424
425                 if (g_strncasecmp(rec->cmd, cmd, len) == 0) {
426                         word = cmdchar == '\0' ? g_strdup(rec->cmd) :
427                                 g_strdup_printf("%c%s", cmdchar, rec->cmd);
428                         if (glist_find_icase_string(complist, word) == NULL)
429                                 complist = g_list_insert_sorted(complist, word, (GCompareFunc) g_istr_cmp);
430                         else
431                                 g_free(word);
432                 }
433         }
434         return complist;
435 }
436
437 static GList *completion_get_subcommands(const char *cmd)
438 {
439         GList *complist;
440         GSList *tmp;
441         char *spacepos;
442         int len, skip;
443
444         g_return_val_if_fail(cmd != NULL, NULL);
445
446         /* get the number of chars to skip at the start of command. */
447         spacepos = strrchr(cmd, ' ');
448         skip = spacepos == NULL ? strlen(cmd)+1 :
449                 ((int) (spacepos-cmd) + 1);
450
451         len = strlen(cmd);
452         complist = NULL;
453         for (tmp = commands; tmp != NULL; tmp = tmp->next) {
454                 COMMAND_REC *rec = tmp->data;
455
456                 if ((int)strlen(rec->cmd) < len)
457                         continue;
458
459                 if (strchr(rec->cmd+len, ' ') != NULL)
460                         continue;
461
462                 if (g_strncasecmp(rec->cmd, cmd, len) == 0)
463                         complist = g_list_insert_sorted(complist, g_strdup(rec->cmd+skip), (GCompareFunc) g_istr_cmp);
464         }
465         return complist;
466 }
467
468 static GList *completion_get_options(const char *cmd, const char *option)
469 {
470         COMMAND_REC *rec;
471         GList *list;
472         char **tmp;
473         int len;
474
475         g_return_val_if_fail(cmd != NULL, NULL);
476         g_return_val_if_fail(option != NULL, NULL);
477
478         rec = command_find(cmd);
479         if (rec == NULL || rec->options == NULL) return NULL;
480
481         list = NULL;
482         len = strlen(option);
483         for (tmp = rec->options; *tmp != NULL; tmp++) {
484                 const char *optname = *tmp + iscmdtype(**tmp);
485
486                 if (len == 0 || g_strncasecmp(optname, option, len) == 0)
487                         list = g_list_append(list, g_strconcat("-", optname, NULL));
488         }
489
490         return list;
491 }
492
493 /* split the line to command and arguments */
494 static char *line_get_command(const char *line, char **args, int aliases)
495 {
496         const char *ptr, *cmdargs;
497         char *cmd, *checkcmd;
498
499         g_return_val_if_fail(line != NULL, NULL);
500         g_return_val_if_fail(args != NULL, NULL);
501
502         cmd = checkcmd = NULL; *args = "";
503         cmdargs = NULL; ptr = line;
504
505         do {
506                 ptr = strchr(ptr, ' ');
507                 if (ptr == NULL) {
508                         checkcmd = g_strdup(line);
509                         cmdargs = "";
510                 } else {
511                         checkcmd = g_strndup(line, (int) (ptr-line));
512
513                         while (*ptr == ' ') ptr++;
514                         cmdargs = ptr;
515                 }
516
517                 if (aliases ? !alias_find(checkcmd) :
518                     !command_find(checkcmd)) {
519                         /* not found, use the previous */
520                         g_free(checkcmd);
521                         break;
522                 }
523
524                 /* found, check if it has subcommands */
525                 g_free_not_null(cmd);
526                 if (!aliases)
527                         cmd = checkcmd;
528                 else {
529                         cmd = g_strdup(alias_find(checkcmd));
530                         g_free(checkcmd);
531                 }
532                 *args = (char *) cmdargs;
533         } while (ptr != NULL);
534
535         if (cmd != NULL)
536                 ascii_strdown(cmd);
537         return cmd;
538 }
539
540 static char *expand_aliases(const char *line)
541 {
542         char *cmd, *args, *ret;
543
544         g_return_val_if_fail(line != NULL, NULL);
545
546         cmd = line_get_command(line, &args, TRUE);
547         if (cmd == NULL) return g_strdup(line);
548         if (*args == '\0') return cmd;
549
550         ret = g_strconcat(cmd, " ", args, NULL);
551         g_free(cmd);
552         return ret;
553 }
554
555 static void sig_complete_word(GList **list, WINDOW_REC *window,
556                               const char *word, const char *linestart,
557                               int *want_space)
558 {
559         const char *newword, *cmdchars;
560         char *signal, *cmd, *args, *line;
561
562         g_return_if_fail(list != NULL);
563         g_return_if_fail(word != NULL);
564         g_return_if_fail(linestart != NULL);
565
566         /* check against "completion words" list */
567         newword = completion_find(word, FALSE);
568         if (newword != NULL) {
569                 *list = g_list_append(*list, g_strdup(newword));
570
571                 signal_stop();
572                 return;
573         }
574
575         if (*linestart != '\0' && (*word == '/' || *word == '~')) {
576                 /* quite likely filename completion */
577                 *list = g_list_concat(*list, filename_complete(word, NULL));
578                 if (*list != NULL) {
579                         *want_space = FALSE;
580                         signal_stop();
581                         return;
582                 }
583         }
584
585         /* command completion? */
586         cmdchars = settings_get_str("cmdchars");
587         if (*word != '\0' && *linestart == '\0' && strchr(cmdchars, *word)) {
588                 /* complete /command */
589                 *list = completion_get_commands(word+1, *word);
590
591                 /* complete aliases, too */
592                 *list = g_list_concat(*list,
593                                       completion_get_aliases(word+1, *word));
594
595                 if (*list != NULL) signal_stop();
596                 return;
597         }
598
599         /* check only for /command completions from now on */
600         if (*linestart == '\0')
601                 return;
602
603         cmdchars = strchr(cmdchars, *linestart);
604         if (cmdchars == NULL) return;
605
606         /* check if there's aliases */
607         line = linestart[1] == *cmdchars ? g_strdup(linestart+2) :
608                 expand_aliases(linestart+1);
609
610         cmd = line_get_command(line, &args, FALSE);
611         if (cmd == NULL) {
612                 g_free(line);
613                 return;
614         }
615
616         /* we're completing -option? */
617         if (*word == '-') {
618                 *list = completion_get_options(cmd, word+1);
619                 if (*list != NULL) signal_stop();
620                 g_free(cmd);
621                 g_free(line);
622                 return;
623         }
624
625         /* complete parameters */
626         signal = g_strconcat("complete command ", cmd, NULL);
627         signal_emit(signal, 5, list, window, word, args, want_space);
628
629         if (command_have_sub(line)) {
630                 /* complete subcommand */
631                 g_free(cmd);
632                 cmd = g_strconcat(line, " ", word, NULL);
633                 *list = g_list_concat(completion_get_subcommands(cmd), *list);
634         }
635
636         if (*list != NULL) signal_stop();
637         g_free(signal);
638         g_free(cmd);
639         g_free(line);
640 }
641
642 static void sig_complete_erase(WINDOW_REC *window, const char *word,
643                                const char *linestart)
644 {
645         const char *cmdchars;
646         char *line, *cmd, *args, *signal;
647
648         if (*linestart == '\0')
649                 return;
650
651         /* we only want to check for commands */
652         cmdchars = settings_get_str("cmdchars");
653         cmdchars = strchr(cmdchars, *linestart);
654         if (cmdchars == NULL)
655                 return;
656
657         /* check if there's aliases */
658         line = linestart[1] == *cmdchars ? g_strdup(linestart+2) :
659                 expand_aliases(linestart+1);
660
661         cmd = line_get_command(line, &args, FALSE);
662         if (cmd == NULL) {
663                 g_free(line);
664                 return;
665         }
666
667         signal = g_strconcat("complete erase command ", cmd, NULL);
668         signal_emit(signal, 3, window, word, args);
669
670         g_free(signal);
671         g_free(cmd);
672         g_free(line);
673 }
674
675 static void sig_complete_set(GList **list, WINDOW_REC *window,
676                              const char *word, const char *line, int *want_space)
677 {
678         g_return_if_fail(list != NULL);
679         g_return_if_fail(word != NULL);
680         g_return_if_fail(line != NULL);
681
682         if (*line == '\0' ||
683             !strcmp("-clear", line) || !strcmp("-default", line))
684                 *list = completion_get_settings(word, -1);
685         else if (*line != '\0' && *word == '\0') {
686                 SETTINGS_REC *rec = settings_get_record(line);
687                 if (rec != NULL) {
688                         char *value = settings_get_print(rec);
689                         if (value != NULL)
690                                 *list = g_list_append(*list, value);
691                 }
692         }
693
694         if (*list != NULL) signal_stop();
695 }
696
697 static void sig_complete_toggle(GList **list, WINDOW_REC *window,
698                                 const char *word, const char *line, int *want_space)
699 {
700         g_return_if_fail(list != NULL);
701         g_return_if_fail(word != NULL);
702         g_return_if_fail(line != NULL);
703
704         if (*line != '\0') return;
705
706         *list = completion_get_settings(word, SETTING_TYPE_BOOLEAN);
707         if (*list != NULL) signal_stop();
708 }
709
710 /* first argument of command is file name - complete it */
711 static void sig_complete_filename(GList **list, WINDOW_REC *window,
712                                   const char *word, const char *line, int *want_space)
713 {
714         g_return_if_fail(list != NULL);
715         g_return_if_fail(word != NULL);
716         g_return_if_fail(line != NULL);
717
718         if (*line != '\0') return;
719
720         *list = filename_complete(word, NULL);
721         if (*list != NULL) {
722                 *want_space = FALSE;
723                 signal_stop();
724         }
725 }
726
727 /* first argument of command is .. command :) (/HELP command) */
728 static void sig_complete_command(GList **list, WINDOW_REC *window,
729                                   const char *word, const char *line, int *want_space)
730 {
731         char *cmd;
732
733         g_return_if_fail(list != NULL);
734         g_return_if_fail(word != NULL);
735         g_return_if_fail(line != NULL);
736
737         if (*line == '\0') {
738                 /* complete base command */
739                 *list = completion_get_commands(word, '\0');
740         } else if (command_have_sub(line)) {
741                 /* complete subcommand */
742                 cmd = g_strconcat(line, " ", word, NULL);
743                 *list = completion_get_subcommands(cmd);
744                 g_free(cmd);
745         }
746
747         if (*list != NULL) signal_stop();
748 }
749
750 static void cmd_completion(const char *data)
751 {
752         GHashTable *optlist;
753         CONFIG_NODE *node;
754         GSList *tmp;
755         char *key, *value;
756         void *free_arg;
757         int len;
758
759         if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_OPTIONS |
760                             PARAM_FLAG_GETREST,
761                             "completion", &optlist, &key, &value))
762                 return;
763
764         node = iconfig_node_traverse("completions", *value != '\0');
765         if (node != NULL && node->type != NODE_TYPE_BLOCK) {
766                 /* FIXME: remove after 0.8.5 */
767                 iconfig_node_remove(mainconfig->mainnode, node);
768                 node = iconfig_node_traverse("completions", *value != '\0');
769         }
770
771         if (node == NULL || (node->value == NULL && *value == '\0')) {
772                 printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
773                             TXT_NO_COMPLETIONS);
774                 cmd_params_free(free_arg);
775                 return;
776         }
777
778         if (g_hash_table_lookup(optlist, "delete") != NULL && *key != '\0') {
779                 printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
780                             TXT_COMPLETION_REMOVED, key);
781
782                 iconfig_set_str("completions", key, NULL);
783                 signal_emit("completion removed", 1, key);
784         } else if (*key != '\0' && *value != '\0') {
785                 int automatic = g_hash_table_lookup(optlist, "auto") != NULL;
786
787                 node = config_node_section(node, key, NODE_TYPE_BLOCK);
788                 iconfig_node_set_str(node, "value", value);
789                 if (automatic)
790                         iconfig_node_set_bool(node, "auto", TRUE);
791                 else
792                         iconfig_node_set_str(node, "auto", NULL);
793
794                 printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP,
795                             TXT_COMPLETION_LINE,
796                             key, value, automatic ? "yes" : "no");
797
798                 signal_emit("completion added", 1, key);
799         } else {
800                 printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP,
801                             TXT_COMPLETION_HEADER);
802
803                 len = strlen(key);
804                 for (tmp = node->value; tmp != NULL; tmp = tmp->next) {
805                         node = tmp->data;
806
807                         if (len == 0 ||
808                             g_strncasecmp(node->key, key, len) == 0) {
809                                 printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP,
810                                             TXT_COMPLETION_LINE, node->key,
811                                             config_node_get_str(node, "value", ""),
812                                             config_node_get_bool(node, "auto", FALSE) ? "yes" : "no");
813                         }
814                 }
815
816                 printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP,
817                             TXT_COMPLETION_FOOTER);
818         }
819
820         cmd_params_free(free_arg);
821 }
822
823 void completion_init(void)
824 {
825         complist = NULL;
826         last_line = NULL; last_line_pos = -1;
827
828         chat_completion_init();
829
830         command_bind("completion", NULL, (SIGNAL_FUNC) cmd_completion);
831
832         signal_add_first("complete word", (SIGNAL_FUNC) sig_complete_word);
833         signal_add_first("complete erase", (SIGNAL_FUNC) sig_complete_erase);
834         signal_add("complete command set", (SIGNAL_FUNC) sig_complete_set);
835         signal_add("complete command toggle", (SIGNAL_FUNC) sig_complete_toggle);
836         signal_add("complete command load", (SIGNAL_FUNC) sig_complete_filename);
837         signal_add("complete command cat", (SIGNAL_FUNC) sig_complete_filename);
838         signal_add("complete command save", (SIGNAL_FUNC) sig_complete_filename);
839         signal_add("complete command reload", (SIGNAL_FUNC) sig_complete_filename);
840         signal_add("complete command rawlog open", (SIGNAL_FUNC) sig_complete_filename);
841         signal_add("complete command rawlog save", (SIGNAL_FUNC) sig_complete_filename);
842         signal_add("complete command help", (SIGNAL_FUNC) sig_complete_command);
843
844         command_set_options("completion", "auto delete");
845 }
846
847 void completion_deinit(void)
848 {
849         free_completions();
850
851         chat_completion_deinit();
852
853         command_unbind("completion", (SIGNAL_FUNC) cmd_completion);
854
855         signal_remove("complete word", (SIGNAL_FUNC) sig_complete_word);
856         signal_remove("complete erase", (SIGNAL_FUNC) sig_complete_erase);
857         signal_remove("complete command set", (SIGNAL_FUNC) sig_complete_set);
858         signal_remove("complete command toggle", (SIGNAL_FUNC) sig_complete_toggle);
859         signal_remove("complete command load", (SIGNAL_FUNC) sig_complete_filename);
860         signal_remove("complete command cat", (SIGNAL_FUNC) sig_complete_filename);
861         signal_remove("complete command save", (SIGNAL_FUNC) sig_complete_filename);
862         signal_remove("complete command reload", (SIGNAL_FUNC) sig_complete_filename);
863         signal_remove("complete command rawlog open", (SIGNAL_FUNC) sig_complete_filename);
864         signal_remove("complete command rawlog save", (SIGNAL_FUNC) sig_complete_filename);
865         signal_remove("complete command help", (SIGNAL_FUNC) sig_complete_command);
866 }