1 /* vifm
2 * Copyright (C) 2011 xaizek.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
17 */
18
19 #include "cmds.h"
20
21 #include <assert.h> /* assert() */
22 #include <ctype.h> /* isalpha() isdigit() isspace() */
23 #include <stddef.h> /* NULL size_t */
24 #include <stdio.h>
25 #include <stdlib.h> /* calloc() malloc() free() realloc() */
26 #include <string.h> /* strdup() */
27
28 #include "../compat/reallocarray.h"
29 #include "../utils/darray.h"
30 #include "../utils/macros.h"
31 #include "../utils/str.h"
32 #include "../utils/string_array.h"
33 #include "../utils/test_helpers.h"
34 #include "../utils/utils.h"
35 #include "completion.h"
36
37 #define MAX_CMD_RECURSION 16
38 #define MAX_CMD_NAME_LEN 256
39 #define INVALID_MARK -4096
40
41 typedef struct
42 {
43 cmd_t head;
44 cmd_add_t user_cmd_handler;
45 cmd_handler command_handler;
46 int udf_count;
47 }
48 inner_t;
49
50 /* List of characters, which are treated as range separators. */
51 static const char *RANGE_SEPARATORS = ",;";
52
53 static inner_t *inner;
54 static cmds_conf_t *cmds_conf;
55
56 static const char * correct_limit(const char cmd[], cmd_info_t *cmd_info);
57 static int udf_is_ambiguous(const char name[]);
58 static const char * parse_tail(cmd_t *cur, const char cmd[],
59 cmd_info_t *cmd_info);
60 static const char * get_cmd_name(const char cmd[], char buf[], size_t buf_len);
61 static void init_cmd_info(cmd_info_t *cmd_info);
62 static const char * skip_prefix_commands(const char cmd[]);
63 static cmd_t * find_cmd(const char name[]);
64 static const char * parse_range(const char cmd[], cmd_info_t *cmd_info);
65 static const char * parse_range_elem(const char cmd[], cmd_info_t *cmd_info,
66 char last_sep);
67 static int complete_cmd_args(cmd_t *cur, const char args[],
68 cmd_info_t *cmd_info, void *arg);
69 static void complete_cmd_name(const char cmd_name[], int user_only);
70 TSTATIC int add_builtin_cmd(const char name[], int abbr, const cmd_add_t *conf);
71 static int comclear_cmd(const cmd_info_t *cmd_info);
72 static int command_cmd(const cmd_info_t *cmd_info);
73 static void init_command_flags(cmd_t *cmd, int flags);
74 static const char * get_user_cmd_name(const char cmd[], char buf[],
75 size_t buf_len);
76 static int is_valid_udc_name(const char name[]);
77 static cmd_t * insert_cmd(cmd_t *after);
78 static int delcommand_cmd(const cmd_info_t *cmd_info);
79 TSTATIC char ** dispatch_line(const char args[], int *count, char sep,
80 int regexp, int quotes, int noescaping, int comments, int *last_arg,
81 int (**positions)[2]);
82 static int is_separator(char c, char sep);
83
84 void
vle_cmds_init(int udf,cmds_conf_t * conf)85 vle_cmds_init(int udf, cmds_conf_t *conf)
86 {
87 static cmd_add_t commands[] = {
88 {
89 .name = "comclear", .abbr = "comc", .handler = comclear_cmd,
90 .id = COMCLEAR_CMD_ID,
91 .descr = "remove all user-defined :commands",
92 .flags = 0,
93 .min_args = 0, .max_args = 0,
94 }, {
95 .name = "command", .abbr = "com", .handler = command_cmd,
96 .id = COMMAND_CMD_ID,
97 .descr = "display/define user-defined :command",
98 .flags = HAS_EMARK,
99 .min_args = 0, .max_args = NOT_DEF,
100 }, {
101 .name = "delcommand", .abbr = "delc", .handler = delcommand_cmd,
102 .id = DELCOMMAND_CMD_ID,
103 .descr = "undefine user-defined :command",
104 .flags = HAS_EMARK,
105 .min_args = 1, .max_args = 1,
106 }
107 };
108
109 cmds_conf = conf;
110 inner = conf->inner;
111
112 if(inner == NULL)
113 {
114 assert(conf->complete_args != NULL);
115 assert(conf->swap_range != NULL);
116 assert(conf->resolve_mark != NULL);
117 assert(conf->expand_macros != NULL);
118 assert(conf->expand_envvars != NULL);
119 assert(conf->post != NULL);
120 assert(conf->select_range != NULL);
121 assert(conf->skip_at_beginning != NULL);
122 conf->inner = calloc(1, sizeof(inner_t));
123 assert(conf->inner != NULL);
124 inner = conf->inner;
125
126 if(udf)
127 vle_cmds_add(commands, ARRAY_LEN(commands));
128 }
129 }
130
131 void
vle_cmds_reset(void)132 vle_cmds_reset(void)
133 {
134 cmd_t *cur = inner->head.next;
135
136 while(cur != NULL)
137 {
138 cmd_t *next = cur->next;
139 free(cur->cmd);
140 free(cur->name);
141 free(cur);
142 cur = next;
143 }
144
145 inner->head.next = NULL;
146 inner->user_cmd_handler.handler = NULL;
147
148 free(inner);
149 cmds_conf->inner = NULL;
150 }
151
152 int
vle_cmds_run(const char cmd[])153 vle_cmds_run(const char cmd[])
154 {
155 cmd_info_t cmd_info;
156 char cmd_name[MAX_CMD_NAME_LEN];
157 cmd_t *cur;
158 const char *args;
159 int execution_code;
160 size_t last_arg_len;
161 char *last_arg;
162 int last_end = 0;
163 cmds_conf_t *cc = cmds_conf;
164
165 init_cmd_info(&cmd_info);
166 cmd = parse_range(cmd, &cmd_info);
167 if(cmd == NULL)
168 {
169 if(cmd_info.end == INVALID_MARK)
170 return CMDS_ERR_INVALID_RANGE;
171 else
172 return CMDS_ERR_INVALID_CMD;
173 }
174
175 if(*cmd != '\0' && cmd_info.end < cmd_info.begin)
176 {
177 int t;
178
179 if(!cc->swap_range())
180 return CMDS_ERR_INVALID_RANGE;
181
182 t = cmd_info.end;
183 cmd_info.end = cmd_info.begin;
184 cmd_info.begin = t;
185 }
186
187 cmd = get_cmd_name(cmd, cmd_name, sizeof(cmd_name));
188 if(udf_is_ambiguous(cmd_name))
189 return CMDS_ERR_UDF_IS_AMBIGUOUS;
190
191 cur = find_cmd(cmd_name);
192 if(cur == NULL)
193 return CMDS_ERR_INVALID_CMD;
194
195 args = parse_tail(cur, cmd, &cmd_info);
196
197 cmd_info.raw_args = strdup(args);
198
199 /* Set background flag and remove background mark from raw arguments, when
200 * command supports backgrounding. */
201 last_arg = vle_cmds_last_arg(cmd_info.raw_args, cur->quote, &last_arg_len);
202 if(cur->bg && *last_arg == '&' && *vle_cmds_at_arg(last_arg + 1) == '\0')
203 {
204 cmd_info.bg = 1;
205 *last_arg = '\0';
206 }
207
208 if(cur->select)
209 {
210 cc->select_range(cur->id, &cmd_info);
211 }
212
213 if(cur->macros_for_cmd || cur->macros_for_shell)
214 {
215 cmd_info.args = cc->expand_macros(cmd_info.raw_args, cur->macros_for_shell,
216 &cmd_info.usr1, &cmd_info.usr2);
217 }
218 if(cur->envvars)
219 {
220 char *const p = cmd_info.args;
221 cmd_info.args = cc->expand_envvars(p ? p : cmd_info.raw_args);
222 free(p);
223 }
224 if(cmd_info.args == NULL)
225 {
226 cmd_info.args = strdup(cmd_info.raw_args);
227 }
228
229 cmd_info.argv = dispatch_line(cmd_info.args, &cmd_info.argc, cmd_info.sep,
230 cur->regexp, cur->quote, cur->noescaping, cur->comment, NULL,
231 &cmd_info.argvp);
232 if(cmd_info.argc > 0)
233 {
234 last_end = cmd_info.argvp[cmd_info.argc - 1][1];
235 }
236 cmd_info.args[last_end] = '\0';
237
238 /* TODO: extract a method that would check all these error conditions. */
239 if((cmd_info.begin != NOT_DEF || cmd_info.end != NOT_DEF) && !cur->range)
240 {
241 execution_code = CMDS_ERR_NO_RANGE_ALLOWED;
242 }
243 else if(cmd_info.argc < 0)
244 {
245 execution_code = CMDS_ERR_INVALID_ARG;
246 }
247 else if(cmd_info.emark && !cur->emark)
248 {
249 execution_code = CMDS_ERR_NO_BANG_ALLOWED;
250 }
251 else if(cmd_info.qmark && !cur->qmark)
252 {
253 execution_code = CMDS_ERR_NO_QMARK_ALLOWED;
254 }
255 else if(cmd_info.qmark && !cur->args_after_qmark && *cmd_info.args != '\0')
256 {
257 execution_code = CMDS_ERR_TRAILING_CHARS;
258 }
259 else if(cmd_info.argc < cur->min_args)
260 {
261 execution_code = CMDS_ERR_TOO_FEW_ARGS;
262 }
263 else if(cmd_info.argc > cur->max_args && cur->max_args != NOT_DEF)
264 {
265 execution_code = CMDS_ERR_TRAILING_CHARS;
266 }
267 else if(cur->passed > MAX_CMD_RECURSION)
268 {
269 execution_code = CMDS_ERR_LOOP;
270 }
271 else
272 {
273 ++cur->passed;
274
275 if(cur->type == USER_CMD)
276 {
277 cmd_info.user_cmd = cur->name;
278 cmd_info.user_action = cur->cmd;
279 execution_code = inner->user_cmd_handler.handler(&cmd_info);
280 }
281 else
282 {
283 execution_code = cur->handler(&cmd_info);
284 }
285
286 cc->post(cur->id);
287 if(--cur->passed == 0 && cur->deleted)
288 {
289 free(cur);
290 }
291 }
292
293 free(cmd_info.raw_args);
294 free(cmd_info.args);
295 free_string_array(cmd_info.argv, cmd_info.argc);
296 free(cmd_info.argvp);
297
298 return execution_code;
299 }
300
301 /* Applies limit shifts and ensures that its value is not out of bounds.
302 * Returns pointer to part of string after parsed piece. */
303 static const char *
correct_limit(const char cmd[],cmd_info_t * cmd_info)304 correct_limit(const char cmd[], cmd_info_t *cmd_info)
305 {
306 cmd_info->count = (cmd_info->end == NOT_DEF) ? 1 : (cmd_info->end + 1);
307
308 while(*cmd == '+' || *cmd == '-')
309 {
310 int n = 1;
311 int plus = *cmd == '+';
312 cmd++;
313 if(isdigit(*cmd))
314 {
315 char *p;
316 n = strtol(cmd, &p, 10);
317 cmd = p;
318 }
319
320 if(plus)
321 {
322 cmd_info->end += n;
323 cmd_info->count += n;
324 }
325 else
326 {
327 cmd_info->end -= n;
328 cmd_info->count -= n;
329 }
330 }
331
332 if(cmd_info->end < 0)
333 cmd_info->end = 0;
334 if(cmd_info->end > cmds_conf->end)
335 cmd_info->end = cmds_conf->end;
336
337 return cmd;
338 }
339
340 static int
udf_is_ambiguous(const char name[])341 udf_is_ambiguous(const char name[])
342 {
343 size_t len;
344 int count;
345 cmd_t *cur;
346
347 len = strlen(name);
348 count = 0;
349 cur = inner->head.next;
350 while(cur != NULL)
351 {
352 int cmp;
353
354 cmp = strncmp(cur->name, name, len);
355 if(cmp == 0)
356 {
357 if(cur->name[len] == '\0')
358 return 0;
359 if(cur->type == USER_CMD)
360 {
361 char c = cur->name[strlen(cur->name) - 1];
362 if(c != '!' && c != '?')
363 count++;
364 }
365 }
366 else if(cmp > 0)
367 {
368 break;
369 }
370
371 cur = cur->next;
372 }
373 return (count > 1);
374 }
375
376 static const char *
parse_tail(cmd_t * cur,const char cmd[],cmd_info_t * cmd_info)377 parse_tail(cmd_t *cur, const char cmd[], cmd_info_t *cmd_info)
378 {
379 if(*cmd == '!' && (!cur->cust_sep || cur->emark))
380 {
381 cmd_info->emark = 1;
382 cmd++;
383 }
384 else if(*cmd == '?' && (!cur->cust_sep || cur->qmark))
385 {
386 cmd_info->qmark = 1;
387 cmd++;
388 }
389
390 if(*cmd != '\0' && !isspace(*cmd))
391 {
392 if(cur->cust_sep)
393 {
394 cmd_info->sep = isspace(*cmd) ? ' ' : *cmd;
395 }
396 return cmd;
397 }
398
399 while(is_separator(*cmd, cmd_info->sep))
400 {
401 ++cmd;
402 }
403
404 return cmd;
405 }
406
407 int
vle_cmds_identify(const char cmd[])408 vle_cmds_identify(const char cmd[])
409 {
410 cmd_info_t info;
411 const cmd_t *const c = vle_cmds_parse(cmd, &info);
412 return (c == NULL ? -1 : c->id);
413 }
414
415 const char *
vle_cmds_args(const char cmd[])416 vle_cmds_args(const char cmd[])
417 {
418 cmd_info_t info = {};
419 (void)vle_cmds_parse(cmd, &info);
420 return info.raw_args;
421 }
422
423 /* Initializes command info structure with reasonable defaults. */
424 static void
init_cmd_info(cmd_info_t * cmd_info)425 init_cmd_info(cmd_info_t *cmd_info)
426 {
427 cmd_info->begin = NOT_DEF;
428 cmd_info->end = NOT_DEF;
429 cmd_info->count = NOT_DEF;
430 cmd_info->emark = 0;
431 cmd_info->qmark = 0;
432 cmd_info->raw_args = NULL;
433 cmd_info->args = NULL;
434 cmd_info->argc = 0;
435 cmd_info->argv = NULL;
436 cmd_info->user_cmd = NULL;
437 cmd_info->user_action = NULL;
438 cmd_info->sep = ' ';
439 cmd_info->bg = 0;
440 cmd_info->usr1 = 0;
441 cmd_info->usr2 = 0;
442 }
443
444 const cmd_t *
vle_cmds_parse(const char cmd[],cmd_info_t * info)445 vle_cmds_parse(const char cmd[], cmd_info_t *info)
446 {
447 cmd_info_t cmd_info;
448 char cmd_name[MAX_CMD_NAME_LEN + 1];
449 cmd_t *c;
450
451 init_cmd_info(&cmd_info);
452
453 cmd = parse_range(cmd, &cmd_info);
454 if(cmd == NULL)
455 {
456 return NULL;
457 }
458
459 cmd = get_cmd_name(cmd, cmd_name, sizeof(cmd_name));
460 c = find_cmd(cmd_name);
461 if(c == NULL)
462 {
463 return NULL;
464 }
465
466 cmd_info.raw_args = (char *)parse_tail(c, cmd, &cmd_info);
467
468 *info = cmd_info;
469 return c;
470 }
471
472 int
vle_cmds_complete(const char cmd[],void * arg)473 vle_cmds_complete(const char cmd[], void *arg)
474 {
475 cmd_info_t cmd_info;
476 const char *begin, *cmd_name_pos;
477 size_t prefix_len;
478
479 begin = cmd;
480 cmd = skip_prefix_commands(begin);
481 prefix_len = cmd - begin;
482
483 init_cmd_info(&cmd_info);
484
485 cmd_name_pos = parse_range(cmd, &cmd_info);
486 if(cmd_name_pos != NULL)
487 {
488 char cmd_name[MAX_CMD_NAME_LEN];
489 const char *args;
490 cmd_t *cur;
491
492 args = get_cmd_name(cmd_name_pos, cmd_name, sizeof(cmd_name));
493 cur = find_cmd(cmd_name);
494
495 if(*args == '\0' && strcmp(cmd_name, "!") != 0)
496 {
497 complete_cmd_name(cmd_name, 0);
498 prefix_len += cmd_name_pos - cmd;
499 }
500 else
501 {
502 prefix_len += args - cmd;
503 prefix_len += complete_cmd_args(cur, args, &cmd_info, arg);
504 }
505 }
506
507 return prefix_len;
508 }
509
510 /* Skips prefix commands (which can be followed by an arbitrary command) at the
511 * beginning of command-line. */
512 static const char *
skip_prefix_commands(const char cmd[])513 skip_prefix_commands(const char cmd[])
514 {
515 cmd_info_t cmd_info;
516 const char *cmd_name_pos;
517
518 init_cmd_info(&cmd_info);
519
520 cmd_name_pos = parse_range(cmd, &cmd_info);
521 if(cmd_name_pos != NULL)
522 {
523 char cmd_name[MAX_CMD_NAME_LEN];
524 const char *args;
525 cmd_t *cur;
526
527 args = get_cmd_name(cmd_name_pos, cmd_name, sizeof(cmd_name));
528 cur = find_cmd(cmd_name);
529 while(cur != NULL && *args != '\0')
530 {
531 int offset = cmds_conf->skip_at_beginning(cur->id, args);
532 if(offset >= 0)
533 {
534 int delta = (args - cmd) + offset;
535 cmd += delta;
536 init_cmd_info(&cmd_info);
537 cmd_name_pos = parse_range(cmd, &cmd_info);
538 if(cmd_name_pos == NULL)
539 {
540 break;
541 }
542 args = get_cmd_name(cmd_name_pos, cmd_name, sizeof(cmd_name));
543 }
544 else
545 {
546 break;
547 }
548 cur = find_cmd(cmd_name);
549 }
550 }
551 return cmd;
552 }
553
554 static cmd_t *
find_cmd(const char name[])555 find_cmd(const char name[])
556 {
557 cmd_t *cmd;
558
559 cmd = inner->head.next;
560 while(cmd != NULL && strcmp(cmd->name, name) < 0)
561 {
562 cmd = cmd->next;
563 }
564
565 if(cmd != NULL && strncmp(name, cmd->name, strlen(name)) != 0)
566 {
567 cmd = NULL;
568 }
569
570 return cmd;
571 }
572
573 /* Parses whole command range (e.g. "<val>;+<val>,,-<val>"). Returns advanced
574 * value of cmd when parsing is successful, otherwise NULL is returned. */
575 static const char *
parse_range(const char cmd[],cmd_info_t * cmd_info)576 parse_range(const char cmd[], cmd_info_t *cmd_info)
577 {
578 char last_sep;
579
580 cmd = vle_cmds_at_arg(cmd);
581
582 if(isalpha(*cmd) || *cmd == '!' || *cmd == '\0')
583 {
584 return cmd;
585 }
586
587 last_sep = '\0';
588 while(*cmd != '\0')
589 {
590 cmd_info->begin = cmd_info->end;
591
592 cmd = parse_range_elem(cmd, cmd_info, last_sep);
593 if(cmd == NULL)
594 {
595 return NULL;
596 }
597
598 cmd = correct_limit(cmd, cmd_info);
599
600 if(cmd_info->begin == NOT_DEF)
601 {
602 cmd_info->begin = cmd_info->end;
603 }
604
605 cmd = vle_cmds_at_arg(cmd);
606
607 if(!char_is_one_of(RANGE_SEPARATORS, *cmd))
608 {
609 break;
610 }
611
612 last_sep = *cmd;
613 cmd++;
614
615 cmd = vle_cmds_at_arg(cmd);
616 }
617
618 return cmd;
619 }
620
621 /* Parses single element of a command range (e.g. any of <el> in
622 * "<el>;<el>,<el>"). Returns advanced value of cmd when parsing is successful,
623 * otherwise NULL is returned. */
624 static const char *
parse_range_elem(const char cmd[],cmd_info_t * cmd_info,char last_sep)625 parse_range_elem(const char cmd[], cmd_info_t *cmd_info, char last_sep)
626 {
627 if(cmd[0] == '%')
628 {
629 cmd_info->begin = cmds_conf->begin;
630 cmd_info->end = cmds_conf->end;
631 cmd++;
632 }
633 else if(cmd[0] == '$')
634 {
635 cmd_info->end = cmds_conf->end;
636 cmd++;
637 }
638 else if(cmd[0] == '.')
639 {
640 cmd_info->end = cmds_conf->current;
641 cmd++;
642 }
643 else if(char_is_one_of(RANGE_SEPARATORS, *cmd))
644 {
645 cmd_info->end = cmds_conf->current;
646 }
647 else if(isalpha(*cmd))
648 {
649 cmd_info->end = cmds_conf->current;
650 }
651 else if(isdigit(*cmd))
652 {
653 char *p;
654 cmd_info->end = strtol(cmd, &p, 10) - 1;
655 if(cmd_info->end < cmds_conf->begin)
656 cmd_info->end = cmds_conf->begin;
657 cmd = p;
658 }
659 else if(*cmd == '\'')
660 {
661 char mark;
662 cmd++;
663 mark = *cmd++;
664 cmd_info->end = cmds_conf->resolve_mark(mark);
665 if(cmd_info->end < 0)
666 {
667 cmd_info->end = INVALID_MARK;
668 return NULL;
669 }
670 }
671 else if(*cmd == '+' || *cmd == '-')
672 {
673 /* Do nothing after semicolon, because in this case +/- are adjusting not
674 * base current cursor position, but base end of the range. */
675 if(last_sep != ';')
676 {
677 cmd_info->end = cmds_conf->current;
678 }
679 }
680 else
681 {
682 return NULL;
683 }
684
685 return cmd;
686 }
687
688 static const char *
get_cmd_name(const char cmd[],char buf[],size_t buf_len)689 get_cmd_name(const char cmd[], char buf[], size_t buf_len)
690 {
691 const char *t;
692 size_t len;
693
694 assert(buf_len != 0 && "The buffer is expected to be of size > 0.");
695
696 if(cmd[0] == '!')
697 {
698 strcpy(buf, "!");
699 cmd = vle_cmds_at_arg(cmd + 1);
700 return cmd;
701 }
702
703 t = cmd;
704 while(isalpha(*t))
705 t++;
706
707 len = MIN((size_t)(t - cmd), buf_len - 1);
708 strncpy(buf, cmd, len);
709 buf[len] = '\0';
710 if(*t == '?' || *t == '!')
711 {
712 int cmp;
713 cmd_t *cur;
714
715 cur = inner->head.next;
716 while(cur != NULL && (cmp = strncmp(cur->name, buf, len)) <= 0)
717 {
718 if(cmp == 0)
719 {
720 /* Complete match for a builtin with a custom separator. */
721 if(cur->cust_sep && cur->name[len] == '\0')
722 {
723 strncpy(buf, cur->name, buf_len);
724 break;
725 }
726 /* Check for user-defined command that ends with the char ('!' or
727 * '?', see above). */
728 if(cur->type == USER_CMD && cur->name[strlen(cur->name) - 1] == *t)
729 {
730 strncpy(buf, cur->name, buf_len);
731 break;
732 }
733 /* Or builtin abbreviation that supports the mark. */
734 if(cur->type == BUILTIN_ABBR &&
735 ((*t == '!' && cur->emark) || (*t == '?' && cur->qmark)))
736 {
737 strncpy(buf, cur->name, buf_len);
738 break;
739 }
740 }
741 cur = cur->next;
742 }
743
744 if(cur != NULL && cur->type == USER_CMD &&
745 strncmp(cur->name, buf, len) == 0)
746 {
747 /* For user-defined commands, the char is part of the name. */
748 ++t;
749 }
750 }
751
752 return t;
753 }
754
755 /* Returns offset at which completion was done. */
756 static int
complete_cmd_args(cmd_t * cur,const char args[],cmd_info_t * cmd_info,void * arg)757 complete_cmd_args(cmd_t *cur, const char args[], cmd_info_t *cmd_info,
758 void *arg)
759 {
760 const char *tmp_args = args;
761 int result = 0;
762
763 if(cur == NULL || (cur->id >= NO_COMPLETION_BOUNDARY && cur->id < 0))
764 return 0;
765
766 args = parse_tail(cur, tmp_args, cmd_info);
767 args = vle_cmds_at_arg(args);
768 result += args - tmp_args;
769
770 if(cur->id == COMMAND_CMD_ID || cur->id == DELCOMMAND_CMD_ID)
771 {
772 const char *arg;
773
774 arg = strrchr(args, ' ');
775 arg = (arg == NULL) ? args : (arg + 1);
776
777 complete_cmd_name(arg, 1);
778 result += arg - args;
779 }
780 else
781 {
782 int argc;
783 char **argv;
784 int (*argvp)[2];
785 int last_arg = 0;
786
787 argv = dispatch_line(args, &argc, ' ', 0, 1, 0, 0, &last_arg, &argvp);
788
789 cmd_info->args = (char *)args;
790 cmd_info->argc = argc;
791 cmd_info->argv = argv;
792 result += cmds_conf->complete_args(cur->id, cmd_info, last_arg, arg);
793
794 free_string_array(argv, argc);
795 free(argvp);
796 }
797 return result;
798 }
799
800 static void
complete_cmd_name(const char cmd_name[],int user_only)801 complete_cmd_name(const char cmd_name[], int user_only)
802 {
803 cmd_t *cur;
804 size_t len;
805
806 cur = inner->head.next;
807 while(cur != NULL && strcmp(cur->name, cmd_name) < 0)
808 cur = cur->next;
809
810 len = strlen(cmd_name);
811 while(cur != NULL && strncmp(cur->name, cmd_name, len) == 0)
812 {
813 if(cur->type == BUILTIN_ABBR)
814 ;
815 else if(cur->type != USER_CMD && user_only)
816 ;
817 else if(cur->name[0] == '\0')
818 ;
819 else if(cur->type == USER_CMD)
820 vle_compl_add_match(cur->name, cur->cmd);
821 else
822 vle_compl_add_match(cur->name, cur->descr);
823 cur = cur->next;
824 }
825
826 vle_compl_add_last_match(cmd_name);
827 }
828
829 void
vle_cmds_add(const cmd_add_t cmds[],int count)830 vle_cmds_add(const cmd_add_t cmds[], int count)
831 {
832 int i;
833 for(i = 0; i < count; ++i)
834 {
835 int ret_code;
836 assert(cmds[i].min_args >= 0);
837 assert(cmds[i].max_args == NOT_DEF ||
838 cmds[i].min_args <= cmds[i].max_args);
839 ret_code = add_builtin_cmd(cmds[i].name, 0, &cmds[i]);
840 assert(ret_code == 0);
841 if(cmds[i].abbr != NULL)
842 {
843 size_t full_len, short_len;
844 char buf[strlen(cmds[i].name) + 1];
845 assert(starts_with(cmds[i].name, cmds[i].abbr) &&
846 "Abbreviation is consistent with full command");
847 strcpy(buf, cmds[i].name);
848 full_len = strlen(buf);
849 short_len = strlen(cmds[i].abbr);
850 while(full_len > short_len)
851 {
852 buf[--full_len] = '\0';
853 ret_code = add_builtin_cmd(buf, 1, &cmds[i]);
854 assert(ret_code == 0);
855 }
856 }
857 (void)ret_code;
858 }
859 }
860
861 /* Returns non-zero on error */
862 TSTATIC int
add_builtin_cmd(const char name[],int abbr,const cmd_add_t * conf)863 add_builtin_cmd(const char name[], int abbr, const cmd_add_t *conf)
864 {
865 int cmp;
866 cmd_t *new;
867 cmd_t *cur = &inner->head;
868
869 if(strcmp(name, "<USERCMD>") == 0)
870 {
871 if(inner->user_cmd_handler.handler != NULL)
872 return -1;
873 inner->user_cmd_handler = *conf;
874 return 0;
875 }
876
877 if(strcmp(name, "!") != 0)
878 {
879 unsigned int i;
880 for(i = 0U; name[i] != '\0'; ++i)
881 {
882 if(!isalpha(name[i]))
883 {
884 return -1;
885 }
886 }
887 }
888
889 cmp = -1;
890 while(cur->next != NULL && (cmp = strcmp(cur->next->name, name)) < 0)
891 {
892 cur = cur->next;
893 }
894
895 /* Command with the same name already exists. */
896 if(cmp == 0)
897 {
898 if(strncmp(name, "command", strlen(name)) == 0)
899 {
900 inner->command_handler = conf->handler;
901 return 0;
902 }
903 return -1;
904 }
905
906 new = insert_cmd(cur);
907 if(new == NULL)
908 {
909 return -1;
910 }
911
912 new->name = strdup(name);
913 new->descr = conf->descr;
914 new->id = conf->id;
915 new->handler = conf->handler;
916 new->type = abbr ? BUILTIN_ABBR : BUILTIN_CMD;
917 new->passed = 0;
918 new->cmd = NULL;
919 new->min_args = conf->min_args;
920 new->max_args = conf->max_args;
921 new->deleted = 0;
922 init_command_flags(new, conf->flags);
923
924 return 0;
925 }
926
927 static int
comclear_cmd(const cmd_info_t * cmd_info)928 comclear_cmd(const cmd_info_t *cmd_info)
929 {
930 cmd_t *cur = &inner->head;
931
932 while(cur->next != NULL)
933 {
934 if(cur->next->type == USER_CMD)
935 {
936 cmd_t *this = cur->next;
937 cur->next = this->next;
938
939 free(this->cmd);
940 free(this->name);
941 if(this->passed == 0)
942 {
943 free(this);
944 }
945 else
946 {
947 this->deleted = 1;
948 }
949 }
950 else
951 {
952 cur = cur->next;
953 }
954 }
955 inner->udf_count = 0;
956 return 0;
957 }
958
959 static int
command_cmd(const cmd_info_t * cmd_info)960 command_cmd(const cmd_info_t *cmd_info)
961 {
962 int cmp;
963 char cmd_name[MAX_CMD_NAME_LEN];
964 const char *args;
965 cmd_t *new, *cur;
966 size_t len;
967 int has_emark, has_qmark;
968
969 if(cmd_info->argc < 2)
970 {
971 if(inner->command_handler != NULL)
972 return inner->command_handler(cmd_info);
973 else
974 return CMDS_ERR_TOO_FEW_ARGS;
975 }
976
977 args = get_user_cmd_name(cmd_info->args, cmd_name, sizeof(cmd_name));
978 args = vle_cmds_at_arg(args);
979 if(args[0] == '\0')
980 return CMDS_ERR_TOO_FEW_ARGS;
981 else if(!is_valid_udc_name(cmd_name))
982 return CMDS_ERR_INCORRECT_NAME;
983
984 len = strlen(cmd_name);
985 has_emark = (len > 0 && cmd_name[len - 1] == '!');
986 has_qmark = (len > 0 && cmd_name[len - 1] == '?');
987
988 cmp = -1;
989 cur = &inner->head;
990 while(cur->next != NULL && (cmp = strcmp(cur->next->name, cmd_name)) < 0)
991 {
992 if(has_emark && cur->next->type == BUILTIN_CMD && cur->next->emark &&
993 strncmp(cmd_name, cur->next->name, len - 1) == 0)
994 {
995 cmp = 0;
996 break;
997 }
998 if(has_qmark && cur->next->type == BUILTIN_CMD && cur->next->qmark &&
999 strncmp(cmd_name, cur->next->name, len - 1) == 0)
1000 {
1001 cmp = 0;
1002 break;
1003 }
1004 cur = cur->next;
1005 }
1006
1007 if(cmp == 0)
1008 {
1009 cur = cur->next;
1010 if(cur->type == BUILTIN_CMD)
1011 return CMDS_ERR_NO_BUILTIN_REDEFINE;
1012 if(!cmd_info->emark)
1013 return CMDS_ERR_NEED_BANG;
1014 free(cur->name);
1015 free(cur->cmd);
1016 new = cur;
1017 }
1018 else
1019 {
1020 if((new = insert_cmd(cur)) == NULL)
1021 {
1022 return CMDS_ERR_NO_MEM;
1023 }
1024 }
1025
1026 new->name = strdup(cmd_name);
1027 new->descr = NULL;
1028 new->id = USER_CMD_ID;
1029 new->type = USER_CMD;
1030 new->passed = 0;
1031 new->cmd = strdup(args);
1032 new->min_args = inner->user_cmd_handler.min_args;
1033 new->max_args = inner->user_cmd_handler.max_args;
1034 new->deleted = 0;
1035 init_command_flags(new, inner->user_cmd_handler.flags);
1036
1037 ++inner->udf_count;
1038 return 0;
1039 }
1040
1041 /* Initializes flag fields of *cmd from set of HAS_* flags. */
1042 static void
init_command_flags(cmd_t * cmd,int flags)1043 init_command_flags(cmd_t *cmd, int flags)
1044 {
1045 assert((flags & (HAS_RAW_ARGS | HAS_REGEXP_ARGS)) !=
1046 (HAS_RAW_ARGS | HAS_REGEXP_ARGS) && "Wrong flags combination.");
1047 assert((flags & (HAS_RAW_ARGS | HAS_QUOTED_ARGS)) !=
1048 (HAS_RAW_ARGS | HAS_QUOTED_ARGS) && "Wrong flags combination.");
1049 assert((flags & (HAS_QMARK_NO_ARGS | HAS_QMARK_WITH_ARGS)) !=
1050 (HAS_QMARK_NO_ARGS | HAS_QMARK_WITH_ARGS) && "Wrong flags combination.");
1051 assert((flags & (HAS_MACROS_FOR_CMD | HAS_MACROS_FOR_SHELL)) !=
1052 (HAS_MACROS_FOR_CMD | HAS_MACROS_FOR_SHELL) &&
1053 "Wrong flags combination.");
1054
1055 cmd->range = ((flags & HAS_RANGE) != 0);
1056 cmd->cust_sep = ((flags & HAS_CUST_SEP) != 0);
1057 cmd->emark = ((flags & HAS_EMARK) != 0);
1058 cmd->envvars = ((flags & HAS_ENVVARS) != 0);
1059 cmd->select = ((flags & HAS_SELECTION_SCOPE) != 0);
1060 cmd->bg = ((flags & HAS_BG_FLAG) != 0);
1061 cmd->regexp = ((flags & HAS_REGEXP_ARGS) != 0);
1062 cmd->quote = ((flags & HAS_QUOTED_ARGS) != 0);
1063 cmd->noescaping = ((flags & HAS_RAW_ARGS) != 0);
1064 cmd->comment = ((flags & HAS_COMMENT) != 0);
1065 cmd->qmark = ((flags & (HAS_QMARK_NO_ARGS | HAS_QMARK_WITH_ARGS)) != 0);
1066 cmd->args_after_qmark = ((flags & HAS_QMARK_WITH_ARGS) != 0);
1067 cmd->macros_for_cmd = ((flags & HAS_MACROS_FOR_CMD) != 0);
1068 cmd->macros_for_shell = ((flags & HAS_MACROS_FOR_SHELL) != 0);
1069 }
1070
1071 static const char *
get_user_cmd_name(const char cmd[],char buf[],size_t buf_len)1072 get_user_cmd_name(const char cmd[], char buf[], size_t buf_len)
1073 {
1074 const char *t;
1075 size_t len;
1076
1077 t = vle_cmds_past_arg(cmd);
1078
1079 len = MIN((size_t)(t - cmd), buf_len);
1080 strncpy(buf, cmd, len);
1081 buf[len] = '\0';
1082 return t;
1083 }
1084
1085 /* Checks that the name is a valid name for a user-defined command. */
1086 static int
is_valid_udc_name(const char name[])1087 is_valid_udc_name(const char name[])
1088 {
1089 assert(name[0] != '\0' && "Command name can't be empty");
1090
1091 if(strcmp(name, "!") == 0)
1092 return 0;
1093 if(strcmp(name, "?") == 0)
1094 return 0;
1095
1096 char cmd_name[MAX_CMD_NAME_LEN + 1];
1097 copy_str(cmd_name, sizeof(cmd_name), name);
1098
1099 while(name[0] != '\0')
1100 {
1101 if(!isalpha(name[0]))
1102 {
1103 if(name[1] != '\0')
1104 return 0;
1105 else if(name[0] != '!' && name[0] != '?')
1106 return 0;
1107 }
1108 name++;
1109 }
1110
1111 /* Builtins with custom separator have higher priority. Disallow registering
1112 * user-defined commands which will never be called. */
1113 if(name[-1] == '!' || name[-1] == '?')
1114 {
1115 cmd_name[strlen(cmd_name) - 1] = '\0';
1116 }
1117 const cmd_t *const c = find_cmd(cmd_name);
1118 if(c != NULL && c->cust_sep && strcmp(c->name, cmd_name) == 0)
1119 return 0;
1120
1121 return 1;
1122 }
1123
1124 /* Allocates new command node and inserts it after the specified one. Returns
1125 * new uninitialized node or NULL on error. */
1126 static cmd_t *
insert_cmd(cmd_t * after)1127 insert_cmd(cmd_t *after)
1128 {
1129 cmd_t *const new = malloc(sizeof(*new));
1130 if(new == NULL)
1131 {
1132 return NULL;
1133 }
1134
1135 new->next = after->next;
1136 after->next = new;
1137 return new;
1138 }
1139
1140 static int
delcommand_cmd(const cmd_info_t * cmd_info)1141 delcommand_cmd(const cmd_info_t *cmd_info)
1142 {
1143 int cmp;
1144 cmd_t *cur;
1145 cmd_t *cmd;
1146
1147 cmp = -1;
1148 cur = &inner->head;
1149 while(cur->next != NULL &&
1150 (cmp = strcmp(cur->next->name, cmd_info->argv[0])) < 0)
1151 cur = cur->next;
1152
1153 if(cur->next == NULL || cmp != 0)
1154 return CMDS_ERR_NO_SUCH_UDF;
1155
1156 cmd = cur->next;
1157 cur->next = cmd->next;
1158 free(cmd->name);
1159 free(cmd->cmd);
1160 free(cmd);
1161
1162 inner->udf_count--;
1163 return 0;
1164 }
1165
1166 char *
vle_cmds_last_arg(const char cmd[],int quotes,size_t * len)1167 vle_cmds_last_arg(const char cmd[], int quotes, size_t *len)
1168 {
1169 int argc;
1170 char **argv;
1171 int (*argvp)[2];
1172 int last_start = 0;
1173 int last_end = 0;
1174
1175 argv = dispatch_line(cmd, &argc, ' ', 0, quotes, 0, 0, NULL, &argvp);
1176
1177 if(argc > 0)
1178 {
1179 last_start = argvp[argc - 1][0];
1180 last_end = argvp[argc - 1][1];
1181 }
1182
1183 *len = last_end - last_start;
1184 free_string_array(argv, argc);
1185 free(argvp);
1186 return (char *)cmd + last_start;
1187 }
1188
1189 /* Splits argument string into array of strings. Non-zero noescaping means that
1190 * unquoted arguments don't have escaping. Returns NULL if no arguments are
1191 * found or an error occurred. Always sets *count (to negative value on
1192 * unmatched quotes and to zero on all other errors). */
1193 TSTATIC char **
dispatch_line(const char args[],int * count,char sep,int regexp,int quotes,int noescaping,int comments,int * last_pos,int (** positions)[2])1194 dispatch_line(const char args[], int *count, char sep, int regexp, int quotes,
1195 int noescaping, int comments, int *last_pos, int (**positions)[2])
1196 {
1197 char *cmdstr;
1198 int len;
1199 int i;
1200 int st;
1201 const char *args_beg;
1202 char **params;
1203 int (*argvp)[2] = NULL;
1204 DA_INSTANCE(argvp);
1205
1206 enum { BEGIN, NO_QUOTING, S_QUOTING, D_QUOTING, R_QUOTING, ARG, QARG } state;
1207
1208 if(last_pos != NULL)
1209 *last_pos = 0;
1210 *positions = NULL;
1211
1212 *count = 0;
1213 params = NULL;
1214
1215 args_beg = args;
1216 if(sep == ' ')
1217 {
1218 args = vle_cmds_at_arg(args);
1219 }
1220 cmdstr = strdup(args);
1221 len = strlen(cmdstr);
1222 for(i = 0, st = 0, state = BEGIN; i <= len; ++i)
1223 {
1224 const int prev_state = state;
1225 switch(state)
1226 {
1227 case BEGIN:
1228 if(sep == ' ' && cmdstr[i] == '\'' && quotes)
1229 {
1230 st = i + 1;
1231 state = S_QUOTING;
1232 }
1233 else if(cmdstr[i] == '"' && ((sep == ' ' && quotes) ||
1234 (comments && strchr(&cmdstr[i + 1], '"') == NULL)))
1235 {
1236 st = i + 1;
1237 state = D_QUOTING;
1238 }
1239 else if(sep == ' ' && cmdstr[i] == '/' && regexp)
1240 {
1241 st = i + 1;
1242 state = R_QUOTING;
1243 }
1244 else if(!is_separator(cmdstr[i], sep))
1245 {
1246 st = i;
1247 state = NO_QUOTING;
1248
1249 /* Omit escaped character from processign to account for possible case
1250 * when it's a separator, which would lead to breaking argument into
1251 * pieces. */
1252 if(!noescaping && cmdstr[i] == '\\' && cmdstr[i + 1] != '\0')
1253 {
1254 ++i;
1255 }
1256 }
1257 else if(sep != ' ' && i > 0 && is_separator(cmdstr[i - 1], sep))
1258 {
1259 st = i--;
1260 state = NO_QUOTING;
1261 }
1262 if(state != BEGIN && cmdstr[i] != '\0')
1263 {
1264 if(DA_EXTEND(argvp) != NULL)
1265 {
1266 DA_COMMIT(argvp);
1267 argvp[DA_SIZE(argvp) - 1U][0] = i;
1268 }
1269 }
1270 break;
1271 case NO_QUOTING:
1272 if(cmdstr[i] == '\0' || is_separator(cmdstr[i], sep))
1273 {
1274 state = ARG;
1275 }
1276 else if(!noescaping && cmdstr[i] == '\\' && cmdstr[i + 1] != '\0')
1277 {
1278 ++i;
1279 }
1280 break;
1281 case S_QUOTING:
1282 if(cmdstr[i] == '\'')
1283 {
1284 if(cmdstr[i + 1] == '\'')
1285 {
1286 ++i;
1287 }
1288 else
1289 {
1290 state = QARG;
1291 }
1292 }
1293 break;
1294 case D_QUOTING:
1295 if(cmdstr[i] == '"')
1296 {
1297 state = QARG;
1298 }
1299 else if(!noescaping && cmdstr[i] == '\\' && cmdstr[i + 1] != '\0')
1300 {
1301 ++i;
1302 }
1303 break;
1304 case R_QUOTING:
1305 if(cmdstr[i] == '/')
1306 {
1307 state = QARG;
1308 }
1309 else if(!noescaping && cmdstr[i] == '\\' && cmdstr[i + 1] == '/')
1310 {
1311 ++i;
1312 }
1313 break;
1314
1315 case ARG:
1316 case QARG:
1317 assert(0 && "Dispatch line state machine is broken");
1318 break;
1319 }
1320 if(state == ARG || state == QARG)
1321 {
1322 /* Found another argument. */
1323
1324 const int end = (args - args_beg) + ((state == ARG) ? i : (i + 1));
1325 char *last_arg;
1326 const char c = cmdstr[i];
1327 cmdstr[i] = '\0';
1328
1329 if(DA_SIZE(argvp) != 0U)
1330 {
1331 argvp[DA_SIZE(argvp) - 1U][1] = end;
1332 }
1333
1334 *count = add_to_string_array(¶ms, *count, &cmdstr[st]);
1335 if(*count == 0)
1336 {
1337 break;
1338 }
1339 last_arg = params[*count - 1];
1340
1341 cmdstr[i] = c;
1342 switch(prev_state)
1343 {
1344 case NO_QUOTING:
1345 if(!noescaping)
1346 {
1347 unescape(last_arg, sep != ' ');
1348 }
1349 break;
1350 case S_QUOTING: expand_squotes_escaping(last_arg); break;
1351 case D_QUOTING: expand_dquotes_escaping(last_arg); break;
1352 case R_QUOTING: unescape(last_arg, 1); break;
1353 }
1354 state = BEGIN;
1355 }
1356 }
1357
1358 if(comments && state == D_QUOTING && strchr(&cmdstr[st], '"') == NULL)
1359 {
1360 state = BEGIN;
1361 --st;
1362 if(DA_SIZE(argvp) != 0U)
1363 {
1364 DA_REMOVE(argvp, &argvp[DA_SIZE(argvp) - 1U]);
1365 }
1366 }
1367
1368 free(cmdstr);
1369
1370 if(*count == 0 || (size_t)*count != DA_SIZE(argvp) ||
1371 (state != BEGIN && state != NO_QUOTING) ||
1372 put_into_string_array(¶ms, *count, NULL) != *count + 1)
1373 {
1374 free(argvp);
1375 free_string_array(params, *count);
1376 *count = (state == S_QUOTING || state == D_QUOTING || state == R_QUOTING)
1377 ? -1 : 0;
1378 return NULL;
1379 }
1380
1381 if(last_pos != NULL)
1382 {
1383 *last_pos = (args - args_beg) + st;
1384 }
1385
1386 *positions = argvp;
1387 return params;
1388 }
1389
1390 char **
vle_cmds_list_udcs(void)1391 vle_cmds_list_udcs(void)
1392 {
1393 char **p;
1394 cmd_t *cur;
1395
1396 char **const list = reallocarray(NULL, inner->udf_count*2 + 1, sizeof(*list));
1397 if(list == NULL)
1398 {
1399 return NULL;
1400 }
1401
1402 p = list;
1403
1404 cur = inner->head.next;
1405 while(cur != NULL)
1406 {
1407 if(cur->type == USER_CMD)
1408 {
1409 *p++ = strdup(cur->name);
1410 *p++ = strdup(cur->cmd);
1411 }
1412 cur = cur->next;
1413 }
1414
1415 *p = NULL;
1416
1417 return list;
1418 }
1419
1420 char *
vle_cmds_print_udcs(const char beginning[])1421 vle_cmds_print_udcs(const char beginning[])
1422 {
1423 size_t len;
1424 cmd_t *cur;
1425 char *content = NULL;
1426 size_t content_len = 0;
1427
1428 cur = inner->head.next;
1429 len = strlen(beginning);
1430 while(cur != NULL)
1431 {
1432 void *ptr;
1433 size_t new_size;
1434
1435 if(strncmp(cur->name, beginning, len) != 0 || cur->type != USER_CMD)
1436 {
1437 cur = cur->next;
1438 continue;
1439 }
1440
1441 if(content == NULL)
1442 {
1443 content = strdup("Command -- Action");
1444 content_len = strlen(content);
1445 }
1446 new_size = content_len + 1 + strlen(cur->name) + 10 + strlen(cur->cmd) + 1;
1447 ptr = realloc(content, new_size);
1448 if(ptr != NULL)
1449 {
1450 content = ptr;
1451 content_len += sprintf(content + content_len, "\n%-*s %s", 10, cur->name,
1452 cur->cmd);
1453 }
1454 cur = cur->next;
1455 }
1456
1457 return content;
1458 }
1459
1460 char *
vle_cmds_past_arg(const char args[])1461 vle_cmds_past_arg(const char args[])
1462 {
1463 while(!is_separator(*args, ' ') && *args != '\0')
1464 {
1465 ++args;
1466 }
1467 return (char *)args;
1468 }
1469
1470 char *
vle_cmds_at_arg(const char args[])1471 vle_cmds_at_arg(const char args[])
1472 {
1473 while(is_separator(*args, ' '))
1474 {
1475 ++args;
1476 }
1477 return (char *)args;
1478 }
1479
1480 char *
vle_cmds_next_arg(const char args[])1481 vle_cmds_next_arg(const char args[])
1482 {
1483 args = vle_cmds_past_arg(args);
1484 return vle_cmds_at_arg(args);
1485 }
1486
1487 /* Checks whether character is command separator. Special case is space as a
1488 * separator, which means that any reasonable whitespace can be used as
1489 * separators (we don't won't to treat line feeds or similar characters as
1490 * separators to allow their appearance as part of arguments). Returns non-zero
1491 * if c is counted to be a separator, otherwise zero is returned. */
1492 static int
is_separator(char c,char sep)1493 is_separator(char c, char sep)
1494 {
1495 return (sep == ' ') ? (c == ' ' || c == '\t') : (c == sep);
1496 }
1497
1498 /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
1499 /* vim: set cinoptions+=t0 filetype=c : */
1500