imported.
[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         (isspace((int) (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)
117 {
118         static int startpos = 0, wordlen = 0;
119
120         GString *result;
121         char *word, *wordstart, *linestart, *ret;
122         int want_space;
123
124         g_return_val_if_fail(line != NULL, NULL);
125         g_return_val_if_fail(pos != NULL, NULL);
126
127         if (complist != NULL && *pos == last_line_pos &&
128             strcmp(line, last_line) == 0) {
129                 /* complete from old list */
130                 complist = complist->next != NULL ? complist->next :
131                         g_list_first(complist);
132                 want_space = last_want_space;
133         } else {
134                 /* get new completion list */
135                 free_completions();
136
137                 /* get the word we want to complete */
138                 word = get_word_at(line, *pos, &wordstart);
139                 startpos = (int) (wordstart-line);
140                 wordlen = strlen(word);
141
142                 /* get the start of line until the word we're completing */
143                 if (isseparator(*line)) {
144                         /* empty space at the start of line */
145                         if (wordstart == line)
146                                 wordstart += strlen(wordstart);
147                 } else {
148                         while (wordstart > line && isseparator(wordstart[-1]))
149                                 wordstart--;
150                 }
151                 linestart = g_strndup(line, (int) (wordstart-line));
152
153                 /* completions usually add space after the word, that makes
154                    things a bit harder. When continuing a completion
155                    "/msg nick1 "<tab> we have to cycle to nick2, etc.
156                    BUT if we start completion with "/msg "<tab>, we don't
157                    want to complete the /msg word, but instead complete empty
158                    word with /msg being in linestart. */
159                 if (*pos > 0 && line[*pos-1] == ' ') {
160                         char *old;
161
162                         old = linestart;
163                         linestart = *linestart == '\0' ?
164                                 g_strdup(word) :
165                                 g_strconcat(linestart, " ", word, NULL);
166                         g_free(old);
167
168                         g_free(word);
169                         word = g_strdup("");
170                         startpos = strlen(linestart)+1;
171                         wordlen = 0;
172                 }
173
174                 want_space = TRUE;
175                 signal_emit("complete word", 5, &complist, window, word, linestart, &want_space);
176                 last_want_space = want_space;
177
178                 g_free(linestart);
179                 g_free(word);
180         }
181
182         if (complist == NULL)
183                 return NULL;
184
185         /* word completed */
186         *pos = startpos+strlen(complist->data);
187
188         /* replace the word in line - we need to return
189            a full new line */
190         result = g_string_new(line);
191         g_string_erase(result, startpos, wordlen);
192         g_string_insert(result, startpos, complist->data);
193
194         if (want_space) {
195                 if (!isseparator(result->str[*pos]))
196                         g_string_insert_c(result, *pos, ' ');
197                 (*pos)++;
198         }
199
200         wordlen = strlen(complist->data);
201         last_line_pos = *pos;
202         g_free_not_null(last_line);
203         last_line = g_strdup(result->str);
204
205         ret = result->str;
206         g_string_free(result, FALSE);
207         return ret;
208 }
209
210 GList *list_add_file(GList *list, const char *name)
211 {
212         struct stat statbuf;
213         char *fname;
214
215         g_return_val_if_fail(name != NULL, NULL);
216
217         fname = convert_home(name);
218         if (stat(fname, &statbuf) == 0) {
219                 list = g_list_append(list, !S_ISDIR(statbuf.st_mode) ? g_strdup(name) :
220                                      g_strconcat(name, G_DIR_SEPARATOR_S, NULL));
221         }
222
223         g_free(fname);
224         return list;
225 }
226
227 GList *filename_complete(const char *path)
228 {
229         GList *list;
230         DIR *dirp;
231         struct dirent *dp;
232         char *realpath, *dir, *basename, *name;
233         int len;
234
235         g_return_val_if_fail(path != NULL, NULL);
236
237         list = NULL;
238
239         /* get directory part of the path - expand ~/ */
240         realpath = convert_home(path);
241         dir = g_dirname(realpath);
242         g_free(realpath);
243
244         /* open directory for reading */
245         dirp = opendir(dir);
246         g_free(dir);
247         if (dirp == NULL) return NULL;
248
249         dir = g_dirname(path);
250         if (*dir == G_DIR_SEPARATOR && dir[1] == '\0')
251                 *dir = '\0'; /* completing file in root directory */
252         basename = g_basename(path);
253         len = strlen(basename);
254
255         /* add all files in directory to completion list */
256         while ((dp = readdir(dirp)) != NULL) {
257                 if (dp->d_name[0] == '.') {
258                         if (dp->d_name[1] == '\0' ||
259                             (dp->d_name[1] == '.' && dp->d_name[2] == '\0'))
260                                 continue; /* skip . and .. */
261
262                         if (basename[0] != '.')
263                                 continue;
264                 }
265
266                 if (len == 0 || strncmp(dp->d_name, basename, len) == 0) {
267                         name = g_strdup_printf("%s"G_DIR_SEPARATOR_S"%s", dir, dp->d_name);
268                         list = list_add_file(list, name);
269                         g_free(name);
270                 }
271         }
272         closedir(dirp);
273
274         g_free(dir);
275         return list;
276 }
277
278 static GList *completion_get_settings(const char *key)
279 {
280         GList *complist;
281         GSList *tmp, *sets;
282         int len;
283
284         g_return_val_if_fail(key != NULL, NULL);
285
286         sets = settings_get_sorted();
287
288         len = strlen(key);
289         complist = NULL;
290         for (tmp = sets; tmp != NULL; tmp = tmp->next) {
291                 SETTINGS_REC *rec = tmp->data;
292
293                 if (g_strncasecmp(rec->key, key, len) == 0)
294                         complist = g_list_insert_sorted(complist, g_strdup(rec->key), (GCompareFunc) g_istr_cmp);
295         }
296         g_slist_free(sets);
297         return complist;
298 }
299
300 static GList *completion_get_bool_settings(const char *key)
301 {
302         GList *complist;
303         GSList *tmp, *sets;
304         int len;
305
306         g_return_val_if_fail(key != NULL, NULL);
307
308         sets = settings_get_sorted();
309
310         len = strlen(key);
311         complist = NULL;
312         for (tmp = sets; tmp != NULL; tmp = tmp->next) {
313                 SETTINGS_REC *rec = tmp->data;
314
315                 if (rec->type == SETTING_TYPE_BOOLEAN &&
316                     g_strncasecmp(rec->key, key, len) == 0)
317                         complist = g_list_insert_sorted(complist, g_strdup(rec->key), (GCompareFunc) g_istr_cmp);
318         }
319         g_slist_free(sets);
320         return complist;
321 }
322
323 static GList *completion_get_aliases(const char *alias, char cmdchar)
324 {
325         CONFIG_NODE *node;
326         GList *complist;
327         GSList *tmp;
328         char *word;
329         int len;
330
331         g_return_val_if_fail(alias != NULL, NULL);
332
333         /* get list of aliases from mainconfig */
334         node = iconfig_node_traverse("aliases", FALSE);
335         tmp = node == NULL ? NULL : node->value;
336
337         len = strlen(alias);
338         complist = NULL;
339         for (; tmp != NULL; tmp = tmp->next) {
340                 CONFIG_NODE *node = tmp->data;
341
342                 if (node->type != NODE_TYPE_KEY)
343                         continue;
344
345                 if (g_strncasecmp(node->key, alias, len) == 0) {
346                         word = g_strdup_printf("%c%s", cmdchar, node->key);
347                         /* add matching alias to completion list, aliases will
348                            be appended after command completions and kept in
349                            uppercase to show it's an alias */
350                         if (glist_find_icase_string(complist, word) == NULL)
351                                 complist = g_list_insert_sorted(complist, word, (GCompareFunc) g_istr_cmp);
352                         else
353                                 g_free(word);
354                 }
355         }
356         return complist;
357 }
358
359 static GList *completion_get_commands(const char *cmd, char cmdchar)
360 {
361         GList *complist;
362         GSList *tmp;
363         char *word;
364         int len;
365
366         g_return_val_if_fail(cmd != NULL, NULL);
367
368         len = strlen(cmd);
369         complist = NULL;
370         for (tmp = commands; tmp != NULL; tmp = tmp->next) {
371                 COMMAND_REC *rec = tmp->data;
372
373                 if (strchr(rec->cmd, ' ') != NULL)
374                         continue;
375
376                 if (g_strncasecmp(rec->cmd, cmd, len) == 0) {
377                         word = cmdchar == '\0' ? g_strdup(rec->cmd) :
378                                 g_strdup_printf("%c%s", cmdchar, rec->cmd);
379                         if (glist_find_icase_string(complist, word) == NULL)
380                                 complist = g_list_insert_sorted(complist, word, (GCompareFunc) g_istr_cmp);
381                         else
382                                 g_free(word);
383                 }
384         }
385         return complist;
386 }
387
388 static GList *completion_get_subcommands(const char *cmd)
389 {
390         GList *complist;
391         GSList *tmp;
392         char *spacepos;
393         int len, skip;
394
395         g_return_val_if_fail(cmd != NULL, NULL);
396
397         /* get the number of chars to skip at the start of command. */
398         spacepos = strrchr(cmd, ' ');
399         skip = spacepos == NULL ? strlen(cmd)+1 :
400                 ((int) (spacepos-cmd) + 1);
401
402         len = strlen(cmd);
403         complist = NULL;
404         for (tmp = commands; tmp != NULL; tmp = tmp->next) {
405                 COMMAND_REC *rec = tmp->data;
406
407                 if ((int)strlen(rec->cmd) < len)
408                         continue;
409
410                 if (strchr(rec->cmd+len, ' ') != NULL)
411                         continue;
412
413                 if (g_strncasecmp(rec->cmd, cmd, len) == 0)
414                         complist = g_list_insert_sorted(complist, g_strdup(rec->cmd+skip), (GCompareFunc) g_istr_cmp);
415         }
416         return complist;
417 }
418
419 GList *completion_get_options(const char *cmd, const char *option)
420 {
421         COMMAND_REC *rec;
422         GList *list;
423         char **tmp;
424         int len;
425
426         g_return_val_if_fail(cmd != NULL, NULL);
427         g_return_val_if_fail(option != NULL, NULL);
428
429         rec = command_find(cmd);
430         if (rec == NULL || rec->options == NULL) return NULL;
431
432         list = NULL;
433         len = strlen(option);
434         for (tmp = rec->options; *tmp != NULL; tmp++) {
435                 const char *optname = *tmp + iscmdtype(**tmp);
436
437                 if (len == 0 || g_strncasecmp(optname, option, len) == 0)
438                         list = g_list_append(list, g_strconcat("-", optname, NULL));
439         }
440
441         return list;
442 }
443
444 /* split the line to command and arguments */
445 static char *line_get_command(const char *line, char **args, int aliases)
446 {
447         const char *ptr, *cmdargs;
448         char *cmd, *checkcmd;
449
450         g_return_val_if_fail(line != NULL, NULL);
451         g_return_val_if_fail(args != NULL, NULL);
452
453         cmd = checkcmd = NULL; *args = "";
454         cmdargs = NULL; ptr = line;
455
456         do {
457                 ptr = strchr(ptr, ' ');
458                 if (ptr == NULL) {
459                         checkcmd = g_strdup(line);
460                         cmdargs = "";
461                 } else {
462                         checkcmd = g_strndup(line, (int) (ptr-line));
463
464                         while (isspace(*ptr)) ptr++;
465                         cmdargs = ptr;
466                 }
467
468                 if (aliases ? !alias_find(checkcmd) :
469                     !command_find(checkcmd)) {
470                         /* not found, use the previous */
471                         g_free(checkcmd);
472                         break;
473                 }
474
475                 /* found, check if it has subcommands */
476                 g_free_not_null(cmd);
477                 if (!aliases)
478                         cmd = checkcmd;
479                 else {
480                         cmd = g_strdup(alias_find(checkcmd));
481                         g_free(checkcmd);
482                 }
483                 *args = (char *) cmdargs;
484         } while (ptr != NULL);
485
486         return cmd;
487 }
488
489 static char *expand_aliases(const char *line)
490 {
491         char *cmd, *args, *ret;
492
493         g_return_val_if_fail(line != NULL, NULL);
494
495         cmd = line_get_command(line, &args, TRUE);
496         if (cmd == NULL) return g_strdup(line);
497         if (*args == '\0') return cmd;
498
499         ret = g_strconcat(cmd, " ", args, NULL);
500         g_free(cmd);
501         return ret;
502 }
503
504 static void sig_complete_word(GList **list, WINDOW_REC *window,
505                               const char *word, const char *linestart, int *want_space)
506 {
507         const char *newword, *cmdchars;
508         char *signal, *cmd, *args, *line;
509
510         g_return_if_fail(list != NULL);
511         g_return_if_fail(word != NULL);
512         g_return_if_fail(linestart != NULL);
513
514         /* check against "completion words" list */
515         newword = completion_find(word);
516         if (newword != NULL) {
517                 *list = g_list_append(*list, g_strdup(newword));
518
519                 signal_stop();
520                 return;
521         }
522
523         /* command completion? */
524         cmdchars = settings_get_str("cmdchars");
525         if (strchr(cmdchars, *word) && *linestart == '\0') {
526                 /* complete /command */
527                 *list = completion_get_commands(word+1, *word);
528
529                 /* complete aliases, too */
530                 *list = g_list_concat(*list,
531                                       completion_get_aliases(word+1, *word));
532
533                 if (*list != NULL) signal_stop();
534                 return;
535         }
536
537         /* check only for /command completions from now on */
538         cmdchars = strchr(cmdchars, *linestart);
539         if (cmdchars == NULL) return;
540
541         /* check if there's aliases */
542         line = linestart[1] == *cmdchars ? g_strdup(linestart+2) :
543                 expand_aliases(linestart+1);
544
545         cmd = line_get_command(line, &args, FALSE);
546         if (cmd == NULL) {
547                 g_free(line);
548                 return;
549         }
550
551         /* we're completing -option? */
552         if (*word == '-') {
553                 *list = completion_get_options(cmd, word+1);
554                 g_free(cmd);
555                 g_free(line);
556                 return;
557         }
558
559         /* complete parameters */
560         signal = g_strconcat("complete command ", cmd, NULL);
561         signal_emit(signal, 5, list, window, word, args, want_space);
562
563         if (command_have_sub(line)) {
564                 /* complete subcommand */
565                 g_free(cmd);
566                 cmd = g_strconcat(line, " ", word, NULL);
567                 *list = g_list_concat(completion_get_subcommands(cmd), *list);
568
569                 if (*list != NULL) signal_stop();
570         }
571
572         g_free(signal);
573         g_free(cmd);
574
575         g_free(line);
576 }
577
578 static void sig_complete_set(GList **list, WINDOW_REC *window,
579                              const char *word, const char *line, int *want_space)
580 {
581         g_return_if_fail(list != NULL);
582         g_return_if_fail(word != NULL);
583         g_return_if_fail(line != NULL);
584
585         if (*line != '\0') return;
586
587         *list = completion_get_settings(word);
588         if (*list != NULL) signal_stop();
589 }
590
591 static void sig_complete_toggle(GList **list, WINDOW_REC *window,
592                                 const char *word, const char *line, int *want_space)
593 {
594         g_return_if_fail(list != NULL);
595         g_return_if_fail(word != NULL);
596         g_return_if_fail(line != NULL);
597
598         if (*line != '\0') return;
599
600         *list = completion_get_bool_settings(word);
601         if (*list != NULL) signal_stop();
602 }
603
604 /* first argument of command is file name - complete it */
605 static void sig_complete_filename(GList **list, WINDOW_REC *window,
606                                   const char *word, const char *line, int *want_space)
607 {
608         g_return_if_fail(list != NULL);
609         g_return_if_fail(word != NULL);
610         g_return_if_fail(line != NULL);
611
612         if (*line != '\0') return;
613
614         *list = filename_complete(word);
615         if (*list != NULL) {
616                 *want_space = FALSE;
617                 signal_stop();
618         }
619 }
620
621 /* first argument of command is .. command :) (/HELP command) */
622 static void sig_complete_command(GList **list, WINDOW_REC *window,
623                                   const char *word, const char *line, int *want_space)
624 {
625         char *cmd;
626
627         g_return_if_fail(list != NULL);
628         g_return_if_fail(word != NULL);
629         g_return_if_fail(line != NULL);
630
631         if (*line == '\0') {
632                 /* complete base command */
633                 *list = completion_get_commands(word, '\0');
634         } else if (command_have_sub(line)) {
635                 /* complete subcommand */
636                 cmd = g_strconcat(line, " ", word, NULL);
637                 *list = completion_get_subcommands(cmd);
638                 g_free(cmd);
639         }
640
641         if (*list != NULL) signal_stop();
642 }
643
644 void completion_init(void)
645 {
646         complist = NULL;
647         last_line = NULL; last_line_pos = -1;
648
649         chat_completion_init();
650
651         signal_add_first("complete word", (SIGNAL_FUNC) sig_complete_word);
652         signal_add("complete command set", (SIGNAL_FUNC) sig_complete_set);
653         signal_add("complete command toggle", (SIGNAL_FUNC) sig_complete_toggle);
654         signal_add("complete command cat", (SIGNAL_FUNC) sig_complete_filename);
655         signal_add("complete command run", (SIGNAL_FUNC) sig_complete_filename);
656         signal_add("complete command save", (SIGNAL_FUNC) sig_complete_filename);
657         signal_add("complete command reload", (SIGNAL_FUNC) sig_complete_filename);
658         signal_add("complete command rawlog open", (SIGNAL_FUNC) sig_complete_filename);
659         signal_add("complete command rawlog save", (SIGNAL_FUNC) sig_complete_filename);
660         signal_add("complete command help", (SIGNAL_FUNC) sig_complete_command);
661 }
662
663 void completion_deinit(void)
664 {
665         free_completions();
666
667         chat_completion_deinit();
668
669         signal_remove("complete word", (SIGNAL_FUNC) sig_complete_word);
670         signal_remove("complete command set", (SIGNAL_FUNC) sig_complete_set);
671         signal_remove("complete command toggle", (SIGNAL_FUNC) sig_complete_toggle);
672         signal_remove("complete command cat", (SIGNAL_FUNC) sig_complete_filename);
673         signal_remove("complete command run", (SIGNAL_FUNC) sig_complete_filename);
674         signal_remove("complete command save", (SIGNAL_FUNC) sig_complete_filename);
675         signal_remove("complete command reload", (SIGNAL_FUNC) sig_complete_filename);
676         signal_remove("complete command rawlog open", (SIGNAL_FUNC) sig_complete_filename);
677         signal_remove("complete command rawlog save", (SIGNAL_FUNC) sig_complete_filename);
678         signal_remove("complete command help", (SIGNAL_FUNC) sig_complete_command);
679 }
680
681