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