xaizek / libvle (License: GPLv3+) (since 2019-04-21)
Library for building Vim-like applications.
Commit 1f3a0cd26a9833098f2a4a8b6f7eaba6e3ea6db8

Import engine/cmds and engine/completion
Author: xaizek
Author date (UTC): 2022-02-07 10:41
Committer name: xaizek
Committer date (UTC): 2022-02-07 10:41
Parent(s): 1a23cc48f87656b617c276d053948de7fcbd09ec
Signing key: 99DC5E4DB05F6BE2
Tree: 51f5120d4cb77587b63e38e861960c56b41fad29
File Lines added Lines deleted
engine/cmds.c 1682 0
engine/cmds.h 331 0
engine/completion.c 327 0
engine/completion.h 91 0
utils/darray.h 119 0
utils/str.c 81 11
utils/str.h 21 0
utils/string_array.c 44 21
utils/string_array.h 16 26
utils/test_helpers.h 11 9
utils/utils.c 106 0
utils/utils.h 15 7
File engine/cmds.c added (mode: 100644) (index 0000000..dca5975)
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> /* isalnum() 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 custom_cmd_count; /* Number of non-builtin commands. */
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 cmd_t * find_cmd_advance(cmd_t *cmd, const char name[]);
65 static int find_cmd_match(cmd_t *cmd, const char name[]);
66 static const char * parse_range(const char cmd[], cmd_info_t *cmd_info);
67 static const char * parse_range_elem(const char cmd[], cmd_info_t *cmd_info,
68 char last_sep);
69 static int complete_cmd_args(cmd_t *cur, const char args[],
70 cmd_info_t *cmd_info, void *arg);
71 static void complete_cmd_name(const char cmd_name[], int user_only);
72 TSTATIC int add_builtin_cmd(const char name[], CMD_TYPE type,
73 const cmd_add_t *conf);
74 static int is_builtin_like_name_ok(const char name[]);
75 static int comclear_cmd(const cmd_info_t *cmd_info);
76 static void remove_commands(CMD_TYPE type);
77 static int command_cmd(const cmd_info_t *cmd_info);
78 static void init_command_flags(cmd_t *cmd, int flags);
79 static const char * get_user_cmd_name(const char cmd[], char buf[],
80 size_t buf_len);
81 static int is_valid_udc_name(const char name[]);
82 static cmd_t * insert_cmd(cmd_t *after);
83 static int delcommand_cmd(const cmd_info_t *cmd_info);
84 TSTATIC char ** dispatch_line(const char args[], int *count, char sep,
85 int regexp, int quotes, int noescaping, int comments, int *last_arg,
86 int (**positions)[2]);
87 static int is_separator(char c, char sep);
88
89 void
90 vle_cmds_init(int udf, cmds_conf_t *conf)
91 {
92 static cmd_add_t commands[] = {
93 {
94 .name = "comclear", .abbr = "comc", .handler = comclear_cmd,
95 .id = COMCLEAR_CMD_ID,
96 .descr = "remove all user-defined :commands",
97 .flags = 0,
98 .min_args = 0, .max_args = 0,
99 }, {
100 .name = "command", .abbr = "com", .handler = command_cmd,
101 .id = COMMAND_CMD_ID,
102 .descr = "display/define user-defined :command",
103 .flags = HAS_EMARK,
104 .min_args = 0, .max_args = NOT_DEF,
105 }, {
106 .name = "delcommand", .abbr = "delc", .handler = delcommand_cmd,
107 .id = DELCOMMAND_CMD_ID,
108 .descr = "undefine user-defined :command",
109 .flags = HAS_EMARK,
110 .min_args = 1, .max_args = 1,
111 }
112 };
113
114 cmds_conf = conf;
115 inner = conf->inner;
116
117 if(inner == NULL)
118 {
119 assert(conf->complete_line != NULL);
120 assert(conf->complete_args != NULL);
121 assert(conf->swap_range != NULL);
122 assert(conf->resolve_mark != NULL);
123 assert(conf->expand_macros != NULL);
124 assert(conf->expand_envvars != NULL);
125 assert(conf->post != NULL);
126 assert(conf->select_range != NULL);
127 assert(conf->skip_at_beginning != NULL);
128 conf->inner = calloc(1, sizeof(inner_t));
129 assert(conf->inner != NULL);
130 inner = conf->inner;
131
132 if(udf)
133 vle_cmds_add(commands, ARRAY_LEN(commands));
134 }
135 }
136
137 void
138 vle_cmds_reset(void)
139 {
140 if(inner == NULL)
141 {
142 return;
143 }
144
145 cmd_t *cur = inner->head.next;
146
147 while(cur != NULL)
148 {
149 cmd_t *next = cur->next;
150 free(cur->cmd);
151 free(cur->name);
152 free(cur);
153 cur = next;
154 }
155
156 inner->head.next = NULL;
157 inner->user_cmd_handler.handler = NULL;
158
159 free(inner);
160 inner = NULL;
161 cmds_conf->inner = NULL;
162 }
163
164 void
165 vle_cmds_clear(void)
166 {
167 remove_commands(USER_CMD);
168 remove_commands(FOREIGN_CMD);
169 }
170
171 int
172 vle_cmds_run(const char cmd[])
173 {
174 cmd_info_t cmd_info;
175 char cmd_name[MAX_CMD_NAME_LEN + 1];
176 cmd_t *cur;
177 const char *args;
178 int execution_code;
179 size_t last_arg_len;
180 char *last_arg;
181 int last_end = 0;
182 cmds_conf_t *cc = cmds_conf;
183
184 init_cmd_info(&cmd_info);
185 cmd = parse_range(cmd, &cmd_info);
186 if(cmd == NULL)
187 {
188 if(cmd_info.end == INVALID_MARK)
189 return CMDS_ERR_INVALID_RANGE;
190 else
191 return CMDS_ERR_INVALID_CMD;
192 }
193
194 if(*cmd != '\0' && cmd_info.end < cmd_info.begin)
195 {
196 int t;
197
198 if(!cc->swap_range())
199 return CMDS_ERR_INVALID_RANGE;
200
201 t = cmd_info.end;
202 cmd_info.end = cmd_info.begin;
203 cmd_info.begin = t;
204 }
205
206 cmd = get_cmd_name(cmd, cmd_name, sizeof(cmd_name));
207 if(udf_is_ambiguous(cmd_name))
208 return CMDS_ERR_UDF_IS_AMBIGUOUS;
209
210 cur = find_cmd(cmd_name);
211 if(cur == NULL)
212 return CMDS_ERR_INVALID_CMD;
213
214 args = parse_tail(cur, cmd, &cmd_info);
215
216 cmd_info.raw_args = strdup(args);
217
218 /* Set background flag and remove background mark from raw arguments, when
219 * command supports backgrounding. */
220 last_arg = vle_cmds_last_arg(cmd_info.raw_args, cur->quote, &last_arg_len);
221 if(cur->bg && *last_arg == '&' && *vle_cmds_at_arg(last_arg + 1) == '\0')
222 {
223 cmd_info.bg = 1;
224 *last_arg = '\0';
225 }
226
227 if(cur->select)
228 {
229 cc->select_range(cur->id, &cmd_info);
230 }
231
232 if(cur->macros_for_cmd || cur->macros_for_shell)
233 {
234 cmd_info.args = cc->expand_macros(cmd_info.raw_args, cur->macros_for_shell,
235 &cmd_info.usr1, &cmd_info.usr2);
236 }
237 if(cur->envvars)
238 {
239 char *const p = cmd_info.args;
240 cmd_info.args = cc->expand_envvars(p ? p : cmd_info.raw_args);
241 free(p);
242 }
243 if(cmd_info.args == NULL)
244 {
245 cmd_info.args = strdup(cmd_info.raw_args);
246 }
247
248 cmd_info.argv = dispatch_line(cmd_info.args, &cmd_info.argc, cmd_info.sep,
249 cur->regexp, cur->quote, cur->noescaping, cur->comment, NULL,
250 &cmd_info.argvp);
251 if(cmd_info.argc > 0)
252 {
253 last_end = cmd_info.argvp[cmd_info.argc - 1][1];
254 }
255 cmd_info.args[last_end] = '\0';
256
257 /* TODO: extract a method that would check all these error conditions. */
258 if((cmd_info.begin != NOT_DEF || cmd_info.end != NOT_DEF) && !cur->range)
259 {
260 execution_code = CMDS_ERR_NO_RANGE_ALLOWED;
261 }
262 else if(cmd_info.argc < 0)
263 {
264 execution_code = CMDS_ERR_INVALID_ARG;
265 }
266 else if(cmd_info.emark && !cur->emark)
267 {
268 execution_code = CMDS_ERR_NO_BANG_ALLOWED;
269 }
270 else if(cmd_info.qmark && !cur->qmark)
271 {
272 execution_code = CMDS_ERR_NO_QMARK_ALLOWED;
273 }
274 else if(cmd_info.qmark && !cur->args_after_qmark && *cmd_info.args != '\0')
275 {
276 execution_code = CMDS_ERR_TRAILING_CHARS;
277 }
278 else if(cmd_info.argc < cur->min_args)
279 {
280 execution_code = CMDS_ERR_TOO_FEW_ARGS;
281 }
282 else if(cmd_info.argc > cur->max_args && cur->max_args != NOT_DEF)
283 {
284 execution_code = CMDS_ERR_TRAILING_CHARS;
285 }
286 else if(cur->passed > MAX_CMD_RECURSION)
287 {
288 execution_code = CMDS_ERR_LOOP;
289 }
290 else
291 {
292 ++cur->passed;
293
294 if(cur->type == USER_CMD)
295 {
296 cmd_info.user_cmd = cur->name;
297 cmd_info.user_action = cur->cmd;
298 execution_code = inner->user_cmd_handler.handler(&cmd_info);
299 }
300 else
301 {
302 cmd_info.user_data = cur->user_data;
303 execution_code = cur->handler(&cmd_info);
304 }
305
306 cc->post(cur->id);
307 if(--cur->passed == 0 && cur->deleted)
308 {
309 free(cur);
310 }
311 }
312
313 free(cmd_info.raw_args);
314 free(cmd_info.args);
315 free_string_array(cmd_info.argv, cmd_info.argc);
316 free(cmd_info.argvp);
317
318 return execution_code;
319 }
320
321 /* Applies limit shifts and ensures that its value is not out of bounds.
322 * Returns pointer to part of string after parsed piece. */
323 static const char *
324 correct_limit(const char cmd[], cmd_info_t *cmd_info)
325 {
326 cmd_info->count = (cmd_info->end == NOT_DEF) ? 1 : (cmd_info->end + 1);
327
328 while(*cmd == '+' || *cmd == '-')
329 {
330 int n = 1;
331 int plus = *cmd == '+';
332 cmd++;
333 if(isdigit(*cmd))
334 {
335 char *p;
336 n = strtol(cmd, &p, 10);
337 cmd = p;
338 }
339
340 if(plus)
341 {
342 cmd_info->end += n;
343 cmd_info->count += n;
344 }
345 else
346 {
347 cmd_info->end -= n;
348 cmd_info->count -= n;
349 }
350 }
351
352 if(cmd_info->end < 0)
353 cmd_info->end = 0;
354 if(cmd_info->end > cmds_conf->end)
355 cmd_info->end = cmds_conf->end;
356
357 return cmd;
358 }
359
360 static int
361 udf_is_ambiguous(const char name[])
362 {
363 size_t len;
364 int count;
365 cmd_t *cur;
366
367 len = strlen(name);
368 count = 0;
369 cur = inner->head.next;
370 while(cur != NULL)
371 {
372 int cmp;
373
374 cmp = strncmp(cur->name, name, len);
375 if(cmp == 0)
376 {
377 if(cur->name[len] == '\0')
378 return 0;
379 if(cur->type == USER_CMD)
380 {
381 char c = cur->name[strlen(cur->name) - 1];
382 if(c != '!' && c != '?')
383 count++;
384 }
385 }
386 else if(cmp > 0)
387 {
388 break;
389 }
390
391 cur = cur->next;
392 }
393 return (count > 1);
394 }
395
396 static const char *
397 parse_tail(cmd_t *cur, const char cmd[], cmd_info_t *cmd_info)
398 {
399 if(*cmd == '!' && (!cur->cust_sep || cur->emark))
400 {
401 cmd_info->emark = 1;
402 cmd++;
403 }
404 else if(*cmd == '?' && (!cur->cust_sep || cur->qmark))
405 {
406 cmd_info->qmark = 1;
407 cmd++;
408 }
409
410 if(*cmd != '\0' && !isspace(*cmd))
411 {
412 if(cur->cust_sep)
413 {
414 cmd_info->sep = isspace(*cmd) ? ' ' : *cmd;
415 }
416 return cmd;
417 }
418
419 while(is_separator(*cmd, cmd_info->sep))
420 {
421 ++cmd;
422 }
423
424 return cmd;
425 }
426
427 int
428 vle_cmds_identify(const char cmd[])
429 {
430 cmd_info_t info;
431 const cmd_t *const c = vle_cmds_parse(cmd, &info);
432 return (c == NULL ? -1 : c->id);
433 }
434
435 const char *
436 vle_cmds_args(const char cmd[])
437 {
438 cmd_info_t info = {};
439 (void)vle_cmds_parse(cmd, &info);
440 return info.raw_args;
441 }
442
443 /* Initializes command info structure with reasonable defaults. */
444 static void
445 init_cmd_info(cmd_info_t *cmd_info)
446 {
447 cmd_info->begin = NOT_DEF;
448 cmd_info->end = NOT_DEF;
449 cmd_info->count = NOT_DEF;
450 cmd_info->emark = 0;
451 cmd_info->qmark = 0;
452 cmd_info->raw_args = NULL;
453 cmd_info->args = NULL;
454 cmd_info->argc = 0;
455 cmd_info->argv = NULL;
456 cmd_info->user_cmd = NULL;
457 cmd_info->user_action = NULL;
458 cmd_info->user_data = NULL;
459 cmd_info->sep = ' ';
460 cmd_info->bg = 0;
461 cmd_info->usr1 = 0;
462 cmd_info->usr2 = 0;
463 }
464
465 const cmd_t *
466 vle_cmds_parse(const char cmd[], cmd_info_t *info)
467 {
468 cmd_info_t cmd_info;
469 char cmd_name[MAX_CMD_NAME_LEN + 1];
470 cmd_t *c;
471
472 init_cmd_info(&cmd_info);
473
474 cmd = parse_range(cmd, &cmd_info);
475 if(cmd == NULL)
476 {
477 return NULL;
478 }
479
480 cmd = get_cmd_name(cmd, cmd_name, sizeof(cmd_name));
481 c = find_cmd(cmd_name);
482 if(c == NULL)
483 {
484 return NULL;
485 }
486
487 cmd_info.raw_args = (char *)parse_tail(c, cmd, &cmd_info);
488
489 *info = cmd_info;
490 return c;
491 }
492
493 int
494 vle_cmds_complete(const char cmd[], void *arg)
495 {
496 cmd_info_t cmd_info;
497 const char *begin, *cmd_name_pos;
498 size_t prefix_len;
499
500 begin = cmd;
501 cmd = skip_prefix_commands(begin);
502 prefix_len = cmd - begin;
503
504 init_cmd_info(&cmd_info);
505
506 cmd_name_pos = parse_range(cmd, &cmd_info);
507 if(cmd_name_pos != NULL)
508 {
509 char cmd_name[MAX_CMD_NAME_LEN + 1];
510 const char *args;
511 cmd_t *cur;
512
513 args = get_cmd_name(cmd_name_pos, cmd_name, sizeof(cmd_name));
514 cur = find_cmd(cmd_name);
515
516 if(*args == '\0' && strcmp(cmd_name, "!") != 0)
517 {
518 complete_cmd_name(cmd_name, 0);
519
520 if(vle_compl_get_count() == 0)
521 {
522 prefix_len += cmds_conf->complete_line(cmd_name, arg);
523 }
524 else
525 {
526 vle_compl_add_last_match(cmd_name);
527 prefix_len += cmd_name_pos - cmd;
528 }
529 }
530 else if(cur == NULL || cur->name[0] == '\0')
531 {
532 /* Handle empty command specially here. */
533 prefix_len += cmds_conf->complete_line(cmd, arg);
534 }
535 else
536 {
537 prefix_len += args - cmd;
538 cmd_info.user_data = cur->user_data;
539 prefix_len += complete_cmd_args(cur, args, &cmd_info, arg);
540 }
541 }
542
543 return prefix_len;
544 }
545
546 /* Skips prefix commands (which can be followed by an arbitrary command) at the
547 * beginning of command-line. */
548 static const char *
549 skip_prefix_commands(const char cmd[])
550 {
551 cmd_info_t cmd_info;
552 const char *cmd_name_pos;
553
554 init_cmd_info(&cmd_info);
555
556 cmd_name_pos = parse_range(cmd, &cmd_info);
557 if(cmd_name_pos != NULL)
558 {
559 char cmd_name[MAX_CMD_NAME_LEN + 1];
560 const char *args;
561 cmd_t *cur;
562
563 args = get_cmd_name(cmd_name_pos, cmd_name, sizeof(cmd_name));
564 cur = find_cmd(cmd_name);
565 while(cur != NULL && *args != '\0')
566 {
567 int offset = cmds_conf->skip_at_beginning(cur->id, args);
568 if(offset >= 0)
569 {
570 int delta = (args - cmd) + offset;
571 cmd += delta;
572 init_cmd_info(&cmd_info);
573 cmd_name_pos = parse_range(cmd, &cmd_info);
574 if(cmd_name_pos == NULL)
575 {
576 break;
577 }
578 args = get_cmd_name(cmd_name_pos, cmd_name, sizeof(cmd_name));
579 }
580 else
581 {
582 break;
583 }
584 cur = find_cmd(cmd_name);
585 }
586 }
587 return cmd;
588 }
589
590 /* Looks up a command by its name. */
591 static cmd_t *
592 find_cmd(const char name[])
593 {
594 cmd_t *const cmd = find_cmd_advance(inner->head.next, name);
595 return (find_cmd_match(cmd, name) ? cmd : NULL);
596 }
597
598 /* Advances to the first command whose name is not less than the parameter.
599 * Returns advanced values (could be unchanged or NULL). */
600 static cmd_t *
601 find_cmd_advance(cmd_t *cmd, const char name[])
602 {
603 while(cmd != NULL && strcmp(cmd->name, name) < 0)
604 {
605 cmd = cmd->next;
606 }
607 return cmd;
608 }
609
610 /* Checks that command search was a success. Returns non-zero if so, otherwise
611 * zero is returned. */
612 static int
613 find_cmd_match(cmd_t *cmd, const char name[])
614 {
615 if(cmd == NULL)
616 {
617 return 0;
618 }
619
620 if(name[0] == '\0')
621 {
622 return (cmd->name[0] == '\0');
623 }
624
625 return (strncmp(name, cmd->name, strlen(name)) == 0);
626 }
627
628 /* Parses whole command range (e.g. "<val>;+<val>,,-<val>"). Returns advanced
629 * value of cmd when parsing is successful, otherwise NULL is returned. */
630 static const char *
631 parse_range(const char cmd[], cmd_info_t *cmd_info)
632 {
633 char last_sep;
634
635 cmd = vle_cmds_at_arg(cmd);
636
637 if(isalpha(*cmd) || *cmd == '!' || *cmd == '\0')
638 {
639 return cmd;
640 }
641
642 last_sep = '\0';
643 while(*cmd != '\0')
644 {
645 cmd_info->begin = cmd_info->end;
646
647 cmd = parse_range_elem(cmd, cmd_info, last_sep);
648 if(cmd == NULL)
649 {
650 return NULL;
651 }
652
653 cmd = correct_limit(cmd, cmd_info);
654
655 if(cmd_info->begin == NOT_DEF)
656 {
657 cmd_info->begin = cmd_info->end;
658 }
659
660 cmd = vle_cmds_at_arg(cmd);
661
662 if(!char_is_one_of(RANGE_SEPARATORS, *cmd))
663 {
664 break;
665 }
666
667 last_sep = *cmd;
668 cmd++;
669
670 cmd = vle_cmds_at_arg(cmd);
671 }
672
673 return cmd;
674 }
675
676 /* Parses single element of a command range (e.g. any of <el> in
677 * "<el>;<el>,<el>"). Returns advanced value of cmd when parsing is successful,
678 * otherwise NULL is returned. */
679 static const char *
680 parse_range_elem(const char cmd[], cmd_info_t *cmd_info, char last_sep)
681 {
682 if(cmd[0] == '%')
683 {
684 cmd_info->begin = cmds_conf->begin;
685 cmd_info->end = cmds_conf->end;
686 cmd++;
687 }
688 else if(cmd[0] == '$')
689 {
690 cmd_info->end = cmds_conf->end;
691 cmd++;
692 }
693 else if(cmd[0] == '.')
694 {
695 cmd_info->end = cmds_conf->current;
696 cmd++;
697 }
698 else if(char_is_one_of(RANGE_SEPARATORS, *cmd))
699 {
700 cmd_info->end = cmds_conf->current;
701 }
702 else if(isalpha(*cmd))
703 {
704 cmd_info->end = cmds_conf->current;
705 }
706 else if(isdigit(*cmd))
707 {
708 char *p;
709 cmd_info->end = strtol(cmd, &p, 10) - 1;
710 if(cmd_info->end < cmds_conf->begin)
711 cmd_info->end = cmds_conf->begin;
712 cmd = p;
713 }
714 else if(*cmd == '\'')
715 {
716 char mark;
717 cmd++;
718 mark = *cmd++;
719 cmd_info->end = cmds_conf->resolve_mark(mark);
720 if(cmd_info->end < 0)
721 {
722 cmd_info->end = INVALID_MARK;
723 return NULL;
724 }
725 }
726 else if(*cmd == '+' || *cmd == '-')
727 {
728 /* Do nothing after semicolon, because in this case +/- are adjusting not
729 * base current cursor position, but base end of the range. */
730 if(last_sep != ';')
731 {
732 cmd_info->end = cmds_conf->current;
733 }
734 }
735 else
736 {
737 return NULL;
738 }
739
740 return cmd;
741 }
742
743 static const char *
744 get_cmd_name(const char cmd[], char buf[], size_t buf_len)
745 {
746 assert(buf_len != 0 && "The buffer is expected to be of size > 0.");
747
748 if(cmd[0] == '!')
749 {
750 strcpy(buf, "!");
751 cmd = vle_cmds_at_arg(cmd + 1);
752 return cmd;
753 }
754
755 cmd_t *c = inner->head.next;
756
757 const char *t = cmd;
758 if(isalpha(*t))
759 ++t;
760 while(isalnum(*t))
761 {
762 if(isdigit(*t))
763 {
764 size_t len = MIN((size_t)(t - cmd), buf_len - 1);
765 copy_str(buf, len + 1, cmd);
766
767 c = find_cmd_advance(c, buf);
768 if(find_cmd_match(c, buf) && c->name[len] != *t)
769 {
770 break;
771 }
772 }
773 ++t;
774 }
775
776 size_t len = MIN((size_t)(t - cmd), buf_len - 1);
777 strncpy(buf, cmd, len);
778 buf[len] = '\0';
779 if(*t == '?' || *t == '!')
780 {
781 int cmp;
782 cmd_t *cur;
783
784 cur = inner->head.next;
785 while(cur != NULL && (cmp = strncmp(cur->name, buf, len)) <= 0)
786 {
787 if(cmp == 0)
788 {
789 /* Complete match for a builtin with a custom separator. */
790 if(cur->cust_sep && cur->name[len] == '\0')
791 {
792 strncpy(buf, cur->name, buf_len);
793 break;
794 }
795 /* Check for user-defined command that ends with the char ('!' or
796 * '?', see above). */
797 if(cur->type == USER_CMD && cur->name[strlen(cur->name) - 1] == *t)
798 {
799 strncpy(buf, cur->name, buf_len);
800 break;
801 }
802 /* Or builtin abbreviation that supports the mark. */
803 if(cur->type == BUILTIN_ABBR &&
804 ((*t == '!' && cur->emark) || (*t == '?' && cur->qmark)))
805 {
806 strncpy(buf, cur->name, buf_len);
807 break;
808 }
809 }
810 cur = cur->next;
811 }
812
813 if(cur != NULL && cur->type == USER_CMD &&
814 strncmp(cur->name, buf, len) == 0)
815 {
816 /* For user-defined commands, the char is part of the name. */
817 ++t;
818 }
819 }
820
821 return t;
822 }
823
824 /* Completes command arguments. Returns offset at which completion was done. */
825 static int
826 complete_cmd_args(cmd_t *cur, const char args[], cmd_info_t *cmd_info,
827 void *arg)
828 {
829 const char *tmp_args = args;
830 int result = 0;
831
832 if(cur->id >= NO_COMPLETION_BOUNDARY && cur->id < 0)
833 return 0;
834
835 args = parse_tail(cur, tmp_args, cmd_info);
836 args = vle_cmds_at_arg(args);
837 result += args - tmp_args;
838
839 if(cur->id == COMMAND_CMD_ID || cur->id == DELCOMMAND_CMD_ID)
840 {
841 const char *arg;
842
843 arg = strrchr(args, ' ');
844 arg = (arg == NULL) ? args : (arg + 1);
845
846 complete_cmd_name(arg, 1);
847 vle_compl_add_last_match(arg);
848
849 result += arg - args;
850 }
851 else
852 {
853 int argc;
854 char **argv;
855 int (*argvp)[2];
856 int last_arg = 0;
857
858 argv = dispatch_line(args, &argc, ' ', 0, 1, 0, 0, &last_arg, &argvp);
859
860 cmd_info->args = (char *)args;
861 cmd_info->argc = argc;
862 cmd_info->argv = argv;
863 result += cmds_conf->complete_args(cur->id, cmd_info, last_arg, arg);
864
865 free_string_array(argv, argc);
866 free(argvp);
867 }
868 return result;
869 }
870
871 static void
872 complete_cmd_name(const char cmd_name[], int user_only)
873 {
874 cmd_t *cur;
875 size_t len;
876
877 cur = inner->head.next;
878 while(cur != NULL && strcmp(cur->name, cmd_name) < 0)
879 cur = cur->next;
880
881 len = strlen(cmd_name);
882 while(cur != NULL && strncmp(cur->name, cmd_name, len) == 0)
883 {
884 if(cur->type == BUILTIN_ABBR)
885 ;
886 else if(cur->type != USER_CMD && user_only)
887 ;
888 else if(cur->name[0] == '\0')
889 ;
890 else if(cur->type == USER_CMD && cur->descr == NULL)
891 vle_compl_add_match(cur->name, cur->cmd);
892 else
893 vle_compl_add_match(cur->name, cur->descr);
894 cur = cur->next;
895 }
896 }
897
898 void
899 vle_cmds_add(const cmd_add_t cmds[], int count)
900 {
901 int i;
902 for(i = 0; i < count; ++i)
903 {
904 int ret_code;
905 assert(cmds[i].min_args >= 0);
906 assert(cmds[i].max_args == NOT_DEF ||
907 cmds[i].min_args <= cmds[i].max_args);
908 ret_code = add_builtin_cmd(cmds[i].name, BUILTIN_CMD, &cmds[i]);
909 assert(ret_code == 0);
910 if(cmds[i].abbr != NULL)
911 {
912 size_t full_len, short_len;
913 char buf[strlen(cmds[i].name) + 1];
914 assert(starts_with(cmds[i].name, cmds[i].abbr) &&
915 "Abbreviation is consistent with full command");
916 strcpy(buf, cmds[i].name);
917 full_len = strlen(buf);
918 short_len = strlen(cmds[i].abbr);
919 while(full_len > short_len)
920 {
921 buf[--full_len] = '\0';
922 ret_code = add_builtin_cmd(buf, BUILTIN_ABBR, &cmds[i]);
923 assert(ret_code == 0);
924 }
925 }
926 (void)ret_code;
927 }
928 }
929
930 int
931 vle_cmds_add_foreign(const cmd_add_t *cmd)
932 {
933 if(cmd->min_args < 0)
934 {
935 return 1;
936 }
937
938 if(cmd->max_args != NOT_DEF && cmd->min_args > cmd->max_args)
939 {
940 return 1;
941 }
942
943 if(!is_valid_udc_name(cmd->name))
944 {
945 return CMDS_ERR_INCORRECT_NAME;
946 }
947
948 int failure = (add_builtin_cmd(cmd->name, FOREIGN_CMD, cmd) != 0);
949 if(!failure)
950 {
951 ++inner->custom_cmd_count;
952 }
953 return failure;
954 }
955
956 /* Returns non-zero on error */
957 TSTATIC int
958 add_builtin_cmd(const char name[], CMD_TYPE type, const cmd_add_t *conf)
959 {
960 int cmp;
961 cmd_t *new;
962 cmd_t *cur = &inner->head;
963
964 if(strcmp(name, "<USERCMD>") == 0)
965 {
966 if(inner->user_cmd_handler.handler != NULL)
967 return -1;
968 inner->user_cmd_handler = *conf;
969 return 0;
970 }
971
972 if(!is_builtin_like_name_ok(name))
973 {
974 return -1;
975 }
976
977 cmp = -1;
978 while(cur->next != NULL && (cmp = strcmp(cur->next->name, name)) < 0)
979 {
980 cur = cur->next;
981 }
982
983 /* Command with the same name already exists. */
984 if(cmp == 0)
985 {
986 if(strncmp(name, "command", strlen(name)) == 0)
987 {
988 inner->command_handler = conf->handler;
989 return 0;
990 }
991 return -1;
992 }
993
994 new = insert_cmd(cur);
995 if(new == NULL)
996 {
997 return -1;
998 }
999
1000 new->name = strdup(name);
1001 new->descr = conf->descr;
1002 new->id = conf->id;
1003 new->user_data = conf->user_data;
1004 new->handler = conf->handler;
1005 new->type = type;
1006 new->passed = 0;
1007 new->cmd = NULL;
1008 new->min_args = conf->min_args;
1009 new->max_args = conf->max_args;
1010 new->deleted = 0;
1011 init_command_flags(new, conf->flags);
1012
1013 return 0;
1014 }
1015
1016 /* Checks validity of a name for a builtin-like command. Returns non-zero if
1017 * it's valid and zero otherwise. */
1018 static int
1019 is_builtin_like_name_ok(const char name[])
1020 {
1021 if(name[0] == '\0' || strcmp(name, "!") == 0)
1022 {
1023 return 1;
1024 }
1025
1026 if(!isalpha(name[0]))
1027 {
1028 return 0;
1029 }
1030
1031 int i;
1032 for(i = 1; name[i] != '\0'; ++i)
1033 {
1034 if(!isalnum(name[i]))
1035 {
1036 return 0;
1037 }
1038 }
1039
1040 return 1;
1041 }
1042
1043 /* Implements :comclear builtin command provided by this unit. */
1044 static int
1045 comclear_cmd(const cmd_info_t *cmd_info)
1046 {
1047 remove_commands(USER_CMD);
1048 return 0;
1049 }
1050
1051 /* Removes commands of the specified type. */
1052 static void
1053 remove_commands(CMD_TYPE type)
1054 {
1055 cmd_t *cur = &inner->head;
1056
1057 while(cur->next != NULL)
1058 {
1059 if(cur->next->type == type)
1060 {
1061 cmd_t *this = cur->next;
1062 cur->next = this->next;
1063
1064 free(this->cmd);
1065 free(this->name);
1066 if(this->passed == 0)
1067 {
1068 free(this);
1069 }
1070 else
1071 {
1072 this->deleted = 1;
1073 }
1074 }
1075 else
1076 {
1077 cur = cur->next;
1078 }
1079 }
1080 inner->custom_cmd_count = 0;
1081 }
1082
1083 /* Implements :command builtin command mostly provided by this unit. */
1084 static int
1085 command_cmd(const cmd_info_t *cmd_info)
1086 {
1087 if(cmd_info->argc < 2)
1088 {
1089 if(inner->command_handler == NULL)
1090 {
1091 return CMDS_ERR_TOO_FEW_ARGS;
1092 }
1093 return inner->command_handler(cmd_info);
1094 }
1095
1096 char name[MAX_CMD_NAME_LEN + 1];
1097 const char *body = get_user_cmd_name(cmd_info->args, name, sizeof(name));
1098 body = vle_cmds_at_arg(body);
1099
1100 return vle_cmds_add_user(name, body, NULL, cmd_info->emark);
1101 }
1102
1103 int
1104 vle_cmds_add_user(const char name[], const char body[], const char descr[],
1105 int overwrite)
1106 {
1107 if(body[0] == '\0')
1108 {
1109 return CMDS_ERR_TOO_FEW_ARGS;
1110 }
1111 if(!is_valid_udc_name(name))
1112 {
1113 return CMDS_ERR_INCORRECT_NAME;
1114 }
1115
1116 size_t len = strlen(name);
1117 int has_emark = (len > 0 && name[len - 1] == '!');
1118 int has_qmark = (len > 0 && name[len - 1] == '?');
1119
1120 int cmp = -1;
1121 cmd_t *cur = &inner->head;
1122 while(cur->next != NULL && (cmp = strcmp(cur->next->name, name)) < 0)
1123 {
1124 int builtin_like = (cur->next->type == BUILTIN_CMD)
1125 || (cur->next->type == FOREIGN_CMD);
1126 if(has_emark && builtin_like && cur->next->emark &&
1127 strncmp(name, cur->next->name, len - 1) == 0)
1128 {
1129 cmp = 0;
1130 break;
1131 }
1132 if(has_qmark && builtin_like && cur->next->qmark &&
1133 strncmp(name, cur->next->name, len - 1) == 0)
1134 {
1135 cmp = 0;
1136 break;
1137 }
1138 cur = cur->next;
1139 }
1140
1141 cmd_t *new;
1142 if(cmp == 0)
1143 {
1144 cur = cur->next;
1145 if(cur->type == BUILTIN_CMD || cur->type == FOREIGN_CMD)
1146 return CMDS_ERR_NO_BUILTIN_REDEFINE;
1147 if(!overwrite)
1148 return CMDS_ERR_NEED_BANG;
1149 free(cur->name);
1150 free(cur->cmd);
1151 new = cur;
1152 }
1153 else
1154 {
1155 if((new = insert_cmd(cur)) == NULL)
1156 {
1157 return CMDS_ERR_NO_MEM;
1158 }
1159 }
1160
1161 new->name = strdup(name);
1162 new->descr = descr;
1163 new->id = USER_CMD_ID;
1164 new->type = USER_CMD;
1165 new->passed = 0;
1166 new->cmd = strdup(body);
1167 new->min_args = inner->user_cmd_handler.min_args;
1168 new->max_args = inner->user_cmd_handler.max_args;
1169 new->deleted = 0;
1170 init_command_flags(new, inner->user_cmd_handler.flags);
1171
1172 ++inner->custom_cmd_count;
1173 return 0;
1174 }
1175
1176 /* Initializes flag fields of *cmd from set of HAS_* flags. */
1177 static void
1178 init_command_flags(cmd_t *cmd, int flags)
1179 {
1180 assert((flags & (HAS_RAW_ARGS | HAS_REGEXP_ARGS)) !=
1181 (HAS_RAW_ARGS | HAS_REGEXP_ARGS) && "Wrong flags combination.");
1182 assert((flags & (HAS_RAW_ARGS | HAS_QUOTED_ARGS)) !=
1183 (HAS_RAW_ARGS | HAS_QUOTED_ARGS) && "Wrong flags combination.");
1184 assert((flags & (HAS_QMARK_NO_ARGS | HAS_QMARK_WITH_ARGS)) !=
1185 (HAS_QMARK_NO_ARGS | HAS_QMARK_WITH_ARGS) && "Wrong flags combination.");
1186 assert((flags & (HAS_MACROS_FOR_CMD | HAS_MACROS_FOR_SHELL)) !=
1187 (HAS_MACROS_FOR_CMD | HAS_MACROS_FOR_SHELL) &&
1188 "Wrong flags combination.");
1189
1190 cmd->range = ((flags & HAS_RANGE) != 0);
1191 cmd->cust_sep = ((flags & HAS_CUST_SEP) != 0);
1192 cmd->emark = ((flags & HAS_EMARK) != 0);
1193 cmd->envvars = ((flags & HAS_ENVVARS) != 0);
1194 cmd->select = ((flags & HAS_SELECTION_SCOPE) != 0);
1195 cmd->bg = ((flags & HAS_BG_FLAG) != 0);
1196 cmd->regexp = ((flags & HAS_REGEXP_ARGS) != 0);
1197 cmd->quote = ((flags & HAS_QUOTED_ARGS) != 0);
1198 cmd->noescaping = ((flags & HAS_RAW_ARGS) != 0);
1199 cmd->comment = ((flags & HAS_COMMENT) != 0);
1200 cmd->qmark = ((flags & (HAS_QMARK_NO_ARGS | HAS_QMARK_WITH_ARGS)) != 0);
1201 cmd->args_after_qmark = ((flags & HAS_QMARK_WITH_ARGS) != 0);
1202 cmd->macros_for_cmd = ((flags & HAS_MACROS_FOR_CMD) != 0);
1203 cmd->macros_for_shell = ((flags & HAS_MACROS_FOR_SHELL) != 0);
1204 }
1205
1206 static const char *
1207 get_user_cmd_name(const char cmd[], char buf[], size_t buf_len)
1208 {
1209 const char *t;
1210 size_t len;
1211
1212 t = vle_cmds_past_arg(cmd);
1213
1214 len = MIN((size_t)(t - cmd), buf_len - 1);
1215 strncpy(buf, cmd, len);
1216 buf[len] = '\0';
1217 return t;
1218 }
1219
1220 /* Checks that the name is a valid name for a user-defined command. */
1221 static int
1222 is_valid_udc_name(const char name[])
1223 {
1224 assert(name[0] != '\0' && "Command name can't be empty");
1225
1226 if(strcmp(name, "!") == 0)
1227 return 0;
1228 if(strcmp(name, "?") == 0)
1229 return 0;
1230 if(!isalpha(name[0]))
1231 return 0;
1232 if(strlen(name) >= MAX_CMD_NAME_LEN)
1233 return 0;
1234
1235 char cmd_name[MAX_CMD_NAME_LEN];
1236 char *p = cmd_name;
1237
1238 cmd_t *cmd = inner->head.next;
1239
1240 while(name[0] != '\0')
1241 {
1242 *p++ = *name;
1243
1244 if(!isalnum(name[0]))
1245 {
1246 if(name[1] != '\0')
1247 return 0;
1248 else if(name[0] != '!' && name[0] != '?')
1249 return 0;
1250 }
1251 else if(isdigit(name[1]))
1252 {
1253 *p = '\0';
1254 cmd = find_cmd_advance(cmd, cmd_name);
1255
1256 if(find_cmd_match(cmd, cmd_name))
1257 {
1258 if(cmd->type == BUILTIN_CMD || cmd->type == BUILTIN_ABBR)
1259 {
1260 return 0;
1261 }
1262
1263 const int name_len = p - cmd_name;
1264 if(cmd->name[name_len] == '\0' || isalpha(cmd->name[name_len]))
1265 {
1266 return 0;
1267 }
1268 }
1269 }
1270
1271 ++name;
1272 }
1273
1274 *p = '\0';
1275
1276 /* Builtins with custom separator have higher priority. Disallow registering
1277 * user-defined commands which will never be called. */
1278 if(name[-1] == '!' || name[-1] == '?')
1279 {
1280 cmd_name[strlen(cmd_name) - 1] = '\0';
1281 }
1282
1283 cmd = find_cmd_advance(cmd, cmd_name);
1284 if(find_cmd_match(cmd, cmd_name))
1285 {
1286 if(cmd->cust_sep && strcmp(cmd->name, cmd_name) == 0)
1287 return 0;
1288 if(isdigit(cmd->name[strlen(cmd_name)]))
1289 return 0;
1290 }
1291
1292 return 1;
1293 }
1294
1295 /* Allocates new command node and inserts it after the specified one. Returns
1296 * new uninitialized node or NULL on error. */
1297 static cmd_t *
1298 insert_cmd(cmd_t *after)
1299 {
1300 cmd_t *const new = malloc(sizeof(*new));
1301 if(new == NULL)
1302 {
1303 return NULL;
1304 }
1305
1306 new->next = after->next;
1307 after->next = new;
1308 return new;
1309 }
1310
1311 /* Implements :command builtin command provided by this unit. */
1312 static int
1313 delcommand_cmd(const cmd_info_t *cmd_info)
1314 {
1315 return vle_cmds_del_user(cmd_info->argv[0]);
1316 }
1317
1318 int
1319 vle_cmds_del_user(const char name[])
1320 {
1321 int cmp;
1322 cmd_t *cur;
1323 cmd_t *cmd;
1324
1325 cmp = -1;
1326 cur = &inner->head;
1327 while(cur->next != NULL && (cmp = strcmp(cur->next->name, name)) < 0)
1328 cur = cur->next;
1329
1330 if(cur->next == NULL || cmp != 0)
1331 return CMDS_ERR_NO_SUCH_UDF;
1332
1333 cmd = cur->next;
1334 cur->next = cmd->next;
1335 free(cmd->name);
1336 free(cmd->cmd);
1337 free(cmd);
1338
1339 inner->custom_cmd_count--;
1340 return 0;
1341 }
1342
1343 char *
1344 vle_cmds_last_arg(const char cmd[], int quotes, size_t *len)
1345 {
1346 int argc;
1347 char **argv;
1348 int (*argvp)[2];
1349 int last_start = 0;
1350 int last_end = 0;
1351
1352 argv = dispatch_line(cmd, &argc, ' ', 0, quotes, 0, 0, NULL, &argvp);
1353
1354 if(argc > 0)
1355 {
1356 last_start = argvp[argc - 1][0];
1357 last_end = argvp[argc - 1][1];
1358 }
1359
1360 *len = last_end - last_start;
1361 free_string_array(argv, argc);
1362 free(argvp);
1363 return (char *)cmd + last_start;
1364 }
1365
1366 /* Splits argument string into array of strings. Non-zero noescaping means that
1367 * unquoted arguments don't have escaping. Returns NULL if no arguments are
1368 * found or an error occurred. Always sets *count (to negative value on
1369 * unmatched quotes and to zero on all other errors). */
1370 TSTATIC char **
1371 dispatch_line(const char args[], int *count, char sep, int regexp, int quotes,
1372 int noescaping, int comments, int *last_pos, int (**positions)[2])
1373 {
1374 char *cmdstr;
1375 int len;
1376 int i;
1377 int st;
1378 const char *args_beg;
1379 char **params;
1380 int (*argvp)[2] = NULL;
1381 DA_INSTANCE(argvp);
1382
1383 enum { BEGIN, NO_QUOTING, S_QUOTING, D_QUOTING, R_QUOTING, ARG, QARG } state;
1384
1385 if(last_pos != NULL)
1386 *last_pos = 0;
1387 *positions = NULL;
1388
1389 *count = 0;
1390 params = NULL;
1391
1392 args_beg = args;
1393 if(sep == ' ')
1394 {
1395 args = vle_cmds_at_arg(args);
1396 }
1397 cmdstr = strdup(args);
1398 len = strlen(cmdstr);
1399 for(i = 0, st = 0, state = BEGIN; i <= len; ++i)
1400 {
1401 const int prev_state = state;
1402 switch(state)
1403 {
1404 case BEGIN:
1405 if(sep == ' ' && cmdstr[i] == '\'' && quotes)
1406 {
1407 st = i + 1;
1408 state = S_QUOTING;
1409 }
1410 else if(cmdstr[i] == '"' && ((sep == ' ' && quotes) ||
1411 (comments && strchr(&cmdstr[i + 1], '"') == NULL)))
1412 {
1413 st = i + 1;
1414 state = D_QUOTING;
1415 }
1416 else if(sep == ' ' && cmdstr[i] == '/' && regexp)
1417 {
1418 st = i + 1;
1419 state = R_QUOTING;
1420 }
1421 else if(!is_separator(cmdstr[i], sep))
1422 {
1423 st = i;
1424 state = NO_QUOTING;
1425
1426 /* Omit escaped character from processign to account for possible case
1427 * when it's a separator, which would lead to breaking argument into
1428 * pieces. */
1429 if(!noescaping && cmdstr[i] == '\\' && cmdstr[i + 1] != '\0')
1430 {
1431 ++i;
1432 }
1433 }
1434 else if(sep != ' ' && i > 0 && is_separator(cmdstr[i - 1], sep))
1435 {
1436 st = i--;
1437 state = NO_QUOTING;
1438 }
1439 if(state != BEGIN && cmdstr[i] != '\0')
1440 {
1441 if(DA_EXTEND(argvp) != NULL)
1442 {
1443 DA_COMMIT(argvp);
1444 argvp[DA_SIZE(argvp) - 1U][0] = i;
1445 }
1446 }
1447 break;
1448 case NO_QUOTING:
1449 if(cmdstr[i] == '\0' || is_separator(cmdstr[i], sep))
1450 {
1451 state = ARG;
1452 }
1453 else if(!noescaping && cmdstr[i] == '\\' && cmdstr[i + 1] != '\0')
1454 {
1455 ++i;
1456 }
1457 break;
1458 case S_QUOTING:
1459 if(cmdstr[i] == '\'')
1460 {
1461 if(cmdstr[i + 1] == '\'')
1462 {
1463 ++i;
1464 }
1465 else
1466 {
1467 state = QARG;
1468 }
1469 }
1470 break;
1471 case D_QUOTING:
1472 if(cmdstr[i] == '"')
1473 {
1474 state = QARG;
1475 }
1476 else if(!noescaping && cmdstr[i] == '\\' && cmdstr[i + 1] != '\0')
1477 {
1478 ++i;
1479 }
1480 break;
1481 case R_QUOTING:
1482 if(cmdstr[i] == '/')
1483 {
1484 state = QARG;
1485 }
1486 else if(!noescaping && cmdstr[i] == '\\' && cmdstr[i + 1] == '/')
1487 {
1488 ++i;
1489 }
1490 break;
1491
1492 case ARG:
1493 case QARG:
1494 assert(0 && "Dispatch line state machine is broken");
1495 break;
1496 }
1497 if(state == ARG || state == QARG)
1498 {
1499 /* Found another argument. */
1500
1501 const int end = (args - args_beg) + ((state == ARG) ? i : (i + 1));
1502 char *last_arg;
1503 const char c = cmdstr[i];
1504 cmdstr[i] = '\0';
1505
1506 if(DA_SIZE(argvp) != 0U)
1507 {
1508 argvp[DA_SIZE(argvp) - 1U][1] = end;
1509 }
1510
1511 *count = add_to_string_array(&params, *count, &cmdstr[st]);
1512 if(*count == 0)
1513 {
1514 break;
1515 }
1516 last_arg = params[*count - 1];
1517
1518 cmdstr[i] = c;
1519 switch(prev_state)
1520 {
1521 case NO_QUOTING:
1522 if(!noescaping)
1523 {
1524 unescape(last_arg, sep != ' ');
1525 }
1526 break;
1527 case S_QUOTING: expand_squotes_escaping(last_arg); break;
1528 case D_QUOTING: expand_dquotes_escaping(last_arg); break;
1529 case R_QUOTING: unescape(last_arg, 1); break;
1530 }
1531 state = BEGIN;
1532 }
1533 }
1534
1535 if(comments && state == D_QUOTING && strchr(&cmdstr[st], '"') == NULL)
1536 {
1537 state = BEGIN;
1538 --st;
1539 if(DA_SIZE(argvp) != 0U)
1540 {
1541 DA_REMOVE(argvp, &argvp[DA_SIZE(argvp) - 1U]);
1542 }
1543 }
1544
1545 free(cmdstr);
1546
1547 if(*count == 0 || (size_t)*count != DA_SIZE(argvp) ||
1548 (state != BEGIN && state != NO_QUOTING) ||
1549 put_into_string_array(&params, *count, NULL) != *count + 1)
1550 {
1551 free(argvp);
1552 free_string_array(params, *count);
1553 *count = (state == S_QUOTING || state == D_QUOTING || state == R_QUOTING)
1554 ? -1 : 0;
1555 return NULL;
1556 }
1557
1558 if(last_pos != NULL)
1559 {
1560 *last_pos = (args - args_beg) + st;
1561 }
1562
1563 *positions = argvp;
1564 return params;
1565 }
1566
1567 char **
1568 vle_cmds_list_udcs(void)
1569 {
1570 char **p;
1571 cmd_t *cur;
1572
1573 char **const list = reallocarray(NULL, inner->custom_cmd_count*2 + 1,
1574 sizeof(*list));
1575 if(list == NULL)
1576 {
1577 return NULL;
1578 }
1579
1580 p = list;
1581
1582 cur = inner->head.next;
1583 while(cur != NULL)
1584 {
1585 if(cur->type == USER_CMD)
1586 {
1587 *p++ = strdup(cur->name);
1588 *p++ = strdup(cur->descr == NULL ? cur->cmd : cur->descr);
1589 }
1590 else if(cur->type == FOREIGN_CMD)
1591 {
1592 *p++ = strdup(cur->name);
1593 *p++ = strdup(cur->descr);
1594 }
1595 cur = cur->next;
1596 }
1597
1598 *p = NULL;
1599
1600 return list;
1601 }
1602
1603 char *
1604 vle_cmds_print_udcs(const char beginning[])
1605 {
1606 size_t len;
1607 cmd_t *cur;
1608 char *content = NULL;
1609 size_t content_len = 0;
1610
1611 cur = inner->head.next;
1612 len = strlen(beginning);
1613 while(cur != NULL)
1614 {
1615 void *ptr;
1616 size_t new_size;
1617
1618 if(strncmp(cur->name, beginning, len) != 0 || cur->type != USER_CMD)
1619 {
1620 cur = cur->next;
1621 continue;
1622 }
1623
1624 if(content == NULL)
1625 {
1626 content = strdup("Command -- Action");
1627 content_len = strlen(content);
1628 }
1629 new_size = content_len + 1 + strlen(cur->name) + 10 + strlen(cur->cmd) + 1;
1630 ptr = realloc(content, new_size);
1631 if(ptr != NULL)
1632 {
1633 content = ptr;
1634 content_len += sprintf(content + content_len, "\n%-*s %s", 10, cur->name,
1635 cur->cmd);
1636 }
1637 cur = cur->next;
1638 }
1639
1640 return content;
1641 }
1642
1643 char *
1644 vle_cmds_past_arg(const char args[])
1645 {
1646 while(!is_separator(*args, ' ') && *args != '\0')
1647 {
1648 ++args;
1649 }
1650 return (char *)args;
1651 }
1652
1653 char *
1654 vle_cmds_at_arg(const char args[])
1655 {
1656 while(is_separator(*args, ' '))
1657 {
1658 ++args;
1659 }
1660 return (char *)args;
1661 }
1662
1663 char *
1664 vle_cmds_next_arg(const char args[])
1665 {
1666 args = vle_cmds_past_arg(args);
1667 return vle_cmds_at_arg(args);
1668 }
1669
1670 /* Checks whether character is command separator. Special case is space as a
1671 * separator, which means that any reasonable whitespace can be used as
1672 * separators (we don't won't to treat line feeds or similar characters as
1673 * separators to allow their appearance as part of arguments). Returns non-zero
1674 * if c is counted to be a separator, otherwise zero is returned. */
1675 static int
1676 is_separator(char c, char sep)
1677 {
1678 return (sep == ' ') ? (c == ' ' || c == '\t') : (c == sep);
1679 }
1680
1681 /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
1682 /* vim: set cinoptions+=t0 filetype=c : */
File engine/cmds.h added (mode: 100644) (index 0000000..3dc1142)
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 #ifndef VIFM__ENGINE__CMDS_H__
20 #define VIFM__ENGINE__CMDS_H__
21
22 #include <stddef.h> /* size_t */
23
24 #include "../utils/test_helpers.h"
25
26 #ifdef __cplusplus
27 extern "C"
28 {
29 #endif
30
31 /* Error codes returned by vle_cmds_run(). */
32 enum
33 {
34 /* Error codes are negative integers. */
35 CMDS_ERR_LOOP = -128, /* Too deep recursion of commands. */
36 CMDS_ERR_NO_MEM, /* Not enough memory. */
37 CMDS_ERR_TOO_FEW_ARGS, /* Not enough arguments. */
38 CMDS_ERR_TRAILING_CHARS, /* Too many arguments. */
39 CMDS_ERR_INCORRECT_NAME, /* Bad name for a user-defined command. */
40 CMDS_ERR_NEED_BANG, /* Need enforcing to succeed. */
41 CMDS_ERR_NO_BUILTIN_REDEFINE, /* Can't shadow builtin command. */
42 CMDS_ERR_INVALID_CMD, /* Unknown command name. */
43 CMDS_ERR_NO_BANG_ALLOWED, /* The command doesn't support "!". */
44 CMDS_ERR_NO_RANGE_ALLOWED, /* The command doesn't take range. */
45 CMDS_ERR_NO_QMARK_ALLOWED, /* The command doesn't support "?". */
46 CMDS_ERR_INVALID_RANGE, /* Bad range. */
47 CMDS_ERR_NO_SUCH_UDF, /* Unknown name of a user-defined command. */
48 CMDS_ERR_UDF_IS_AMBIGUOUS, /* Calling user-defined command is ambiguous. */
49 CMDS_ERR_INVALID_ARG, /* Missing closing quote. */
50 CMDS_ERR_CUSTOM, /* Error defined by the client. */
51 };
52
53 /* Constants related to command ids. */
54 enum
55 {
56 /* Builtin commands have negative ids. */
57 USER_CMD_ID = -256, /* <USERCMD> */
58 COMCLEAR_CMD_ID, /* :comc[lear] */
59 COMMAND_CMD_ID, /* :com[mand] */
60 DELCOMMAND_CMD_ID, /* :delc[ommand] */
61
62 /* Commands with ids in range [NO_COMPLETION_BOUNDARY; 0) are not
63 * completed. */
64 NO_COMPLETION_BOUNDARY,
65 };
66
67 /* Special values. */
68 enum
69 {
70 /* Undefined maximum number of arguments for a command. */
71 NOT_DEF = -8192,
72 };
73
74 /* Command type. */
75 typedef enum
76 {
77 BUILTIN_ABBR, /* Abbreviated version of a builtin command (like `:com`). */
78 BUILTIN_CMD, /* Builtin command. */
79 FOREIGN_CMD, /* Externally registered builtin-like command. */
80 USER_CMD, /* User-defined command. */
81 }
82 CMD_TYPE;
83
84 /* Detailed information resulted from command parsing, which is passed to
85 * command handler (cmd_handler). */
86 typedef struct cmd_info_t
87 {
88 int begin, end; /* Parsed range of the command. They are either valid and
89 define range with both boundaries included or equal to
90 NOT_DEF if no range was given. */
91 int count; /* Parsed [count] of the command. */
92 int emark, qmark, bg;
93 char sep;
94 int usr1, usr2;
95
96 char *raw_args; /* Arguments as they were passed in. */
97 char *args; /* Arguments after macro and envvar expansions. */
98 int argc; /* Number of arguments. */
99 char **argv; /* Values of arguments. */
100 int (*argvp)[2]; /* Start/end positions of arguments in args. */
101
102 /* For user defined commands. */
103 const char *user_cmd; /* Name of user defined command. */
104 const char *user_action; /* Body of user defined command. */
105
106 void *user_data; /* User data associated with the command or NULL. */
107 }
108 cmd_info_t;
109
110 /* Type of command handler. Shouldn't return negative numbers unless it's one
111 * of CMDS_ERR_* constants. Either way the return value will be the return
112 * value of vle_cmds_run() function. */
113 typedef int (*cmd_handler)(const cmd_info_t *cmd_info);
114
115 /* Description of a command or an abbreviation. */
116 typedef struct cmd_t
117 {
118 char *name; /* Name of the command. */
119 const char *descr; /* Brief description of the command. */
120 int id; /* Numeric identifier, positive, USER_CMD_ID or -1. */
121 CMD_TYPE type; /* Type of command described by this structure. */
122 int passed; /* Number of times this command was recursively called. */
123
124 void *user_data; /* User data associated with the command or NULL. */
125 cmd_handler handler; /* Handler for builtin commands. */
126 char *cmd; /* Command-line for user-defined commands. */
127
128 int min_args, max_args; /* Min and max number of arguments, can be NOT_DEF. */
129
130 unsigned int deleted : 1; /* Whether this command was deleted. */
131 unsigned int range : 1; /* Handles ranges. */
132 unsigned int cust_sep : 1; /* Custom separator of arguments. */
133 unsigned int emark : 1; /* Supports emark flag. */
134 unsigned int envvars : 1; /* Expand environment variables. */
135 unsigned int select : 1; /* Select files in a range. */
136 unsigned int bg : 1; /* Bg (can have " &" at the end). */
137 unsigned int noescaping : 1; /* Don't process \-escaping in unquoted
138 args. */
139 unsigned int regexp : 1; /* Process /.../-arguments. */
140 unsigned int quote : 1; /* Process '- and "-quoted args. */
141 unsigned int comment : 1; /* Trailing comment is allowed. */
142 unsigned int qmark : 1; /* No args after qmark. */
143 unsigned int args_after_qmark : 1; /* Args after qmark are allowed. */
144 unsigned int macros_for_cmd : 1; /* Expand macros w/o special escaping. */
145 unsigned int macros_for_shell : 1; /* Expand macros with shell escaping. */
146 unsigned int : 0; /* Padding. */
147
148 struct cmd_t *next; /* Pointer to the next structure or NULL. */
149 }
150 cmd_t;
151
152 /* Possible flags for cmd_add_t::flags field. */
153 enum
154 {
155 HAS_RANGE = 0x0001, /* Handles ranges. */
156 HAS_CUST_SEP = 0x0002, /* Custom separator of arguments. */
157 HAS_EMARK = 0x0004, /* Supports emark flag. */
158 HAS_ENVVARS = 0x0008, /* Expand environment variables. */
159 HAS_SELECTION_SCOPE = 0x0010, /* Select files in a range. */
160 HAS_BG_FLAG = 0x0020, /* Background (can have " &" at the end). */
161 HAS_COMMENT = 0x0040, /* Trailing comment is allowed. */
162
163 /* HAS_RAW_ARGS flag can't be combined with either of the other two, but those
164 * two can be specified at the same time. */
165 HAS_RAW_ARGS = 0x0080, /* No special processing of arguments. */
166 HAS_REGEXP_ARGS = 0x0100, /* Process /.../-arguments. */
167 HAS_QUOTED_ARGS = 0x0200, /* Process '- and "-quoted args. */
168
169 /* Must be at most one of these. */
170 HAS_QMARK_NO_ARGS = 0x0400, /* No args after qmark. */
171 HAS_QMARK_WITH_ARGS = 0x0800, /* Args after qmark are allowed. */
172
173 /* Must be at most one of these. */
174 HAS_MACROS_FOR_CMD = 0x1000, /* Expand macros without special escaping. */
175 HAS_MACROS_FOR_SHELL = 0x2000, /* Expand macros with shell escaping. */
176 };
177
178 /* New commands specification for vle_cmds_add(). */
179 typedef struct
180 {
181 const char *name; /* Full command name. */
182 const char *abbr; /* Command prefix (can be NULL). */
183 const char *descr; /* Brief description (stored as a pointer). */
184 void *user_data; /* User data for the handler or NULL. */
185 int id; /* Command id. Doesn't need to be unique. Negative
186 value means absence of arg completion. Use, for
187 example, -1 for all commands without completion. */
188 cmd_handler handler; /* Function invoked to run the command. */
189 int min_args, max_args; /* Minimum and maximum bounds on number of args. */
190 int flags; /* Set of HAS_* flags. */
191 }
192 cmd_add_t;
193
194 /* Configuration structure that's passed in from the outside. */
195 typedef struct
196 {
197 void *inner; /* Should be NULL on first call of vle_cmds_init(). This is a
198 pointer to internal data. */
199
200 int begin; /* The lowest valid number of the range. */
201 int current; /* Current position between [begin; end]. */
202 int end; /* The highest valid number of the range. */
203
204 /* Whole line completion function. arg is a user supplied value, which is
205 * passed through. The functions should return completion offset. */
206 int (*complete_line)(const char cmd_line[], void *arg);
207
208 /* Argument completion function. arg is a user supplied value, which is
209 * passed through. The functions must not modify any strings passed to it.
210 * The functions should return completion offset. */
211 int (*complete_args)(int id, const cmd_info_t *cmd_info, int arg_pos,
212 void *arg);
213
214 /* Asks user whether bounds of an inverted range should be swapped. Should
215 * return non-zero if so and zero otherwise. */
216 int (*swap_range)(void);
217 /* Resolves name of the mark to a position. Should return corresponding
218 * position or value < 0 on error. */
219 int (*resolve_mark)(char mark);
220 /* Expands macros in the passed string. Should return newly allocated
221 * memory. */
222 char * (*expand_macros)(const char str[], int for_shell, int *usr1,
223 int *usr2);
224 /* Expands environment variables in the passed string. Should return newly
225 * allocated memory. */
226 char * (*expand_envvars)(const char str[]);
227 /* Called after successful processing of a command. */
228 void (*post)(int id);
229 /* Called for commands with HAS_SELECTION_SCOPE flag. */
230 void (*select_range)(int id, const cmd_info_t *cmd_info);
231 /* Called to determine whether a command at the front should be skipped for
232 * the purposes of completion. Should return < 0 to do nothing, x to skip
233 * command name and x chars. */
234 int (*skip_at_beginning)(int id, const char args[]);
235 }
236 cmds_conf_t;
237
238 /* Initializes previously uninitialized instance of the unit and sets it as the
239 * current one. The udf argument specifies whether user-defined commands are
240 * allowed. The structure pointed to by cmds_conf_t should be filled before
241 * calling this function. */
242 void vle_cmds_init(int udf, cmds_conf_t *cmds_conf);
243
244 /* Resets state of the unit. */
245 void vle_cmds_reset(void);
246
247 /* Clears (partially resets) state of the unit. */
248 void vle_cmds_clear(void);
249
250 /* Executes a command. Returns one of CMDS_ERR_* codes or code returned by the
251 * command handler. */
252 int vle_cmds_run(const char cmd[]);
253
254 /* Parses command to fetch command and retrieve id associated with it. Returns
255 * the id, -1 on error and USER_CMD_ID for all user defined commands. */
256 int vle_cmds_identify(const char cmd[]);
257
258 /* Parses cmd to find beginning of arguments. Returns pointer within the cmd or
259 * NULL if command is unknown or command-line is invalid. */
260 const char * vle_cmds_args(const char cmd[]);
261
262 /* Breaks down passed command into its constituent parts. Returns pointer to
263 * command's description or NULL on error. */
264 const cmd_t * vle_cmds_parse(const char cmd[], cmd_info_t *info);
265
266 /* Performs completion of the command, either just command name or arguments of
267 * some command. Returns offset in cmd, where completion elements should be
268 * pasted. */
269 int vle_cmds_complete(const char cmd[], void *arg);
270
271 /* Registers all commands in the array pointed to by cmds of length at least
272 * count. */
273 void vle_cmds_add(const cmd_add_t cmds[], int count);
274
275 /* Registers a foreign builtin-like command. Returns non-zero on error,
276 * otherwise zero is returned. */
277 int vle_cmds_add_foreign(const cmd_add_t *cmd);
278
279 /* Adds a new or updates an existing user command. Non-zero overwrite parameter
280 * enables updating of existing command. Returns error code or zero on
281 * success. */
282 int vle_cmds_add_user(const char name[], const char body[], const char descr[],
283 int overwrite);
284
285 /* Removes a user command if one exists. Returns error code or zero on
286 * success. */
287 int vle_cmds_del_user(const char name[]);
288
289 /* Finds the first character of the last argument in cmd. Returns pointer to
290 * it. */
291 char * vle_cmds_last_arg(const char cmd[], int quotes, size_t *len);
292
293 /* Lists user-defined commands as name-value pairs each in separate item of the
294 * array. Last pair element is followed by a NULL. */
295 char ** vle_cmds_list_udcs(void);
296
297 /* Prints a table that includes commands that start with the given prefix into
298 * a multiline string (all lines except for the last one has new line
299 * character). Returns the string or NULL if there are no command with that
300 * prefix. */
301 char * vle_cmds_print_udcs(const char beginning[]);
302
303 /* Skips at most one argument of the string. Returns pointer to the next
304 * character after that argument, if any, otherwise pointer to
305 * null-character. */
306 char * vle_cmds_past_arg(const char args[]);
307
308 /* Skips argument separators in the string. Returns pointer to the first
309 * character of the first argument, if any, otherwise pointer to
310 * null-character. */
311 char * vle_cmds_at_arg(const char args[]);
312
313 /* Advances to the next argument in the string. Returns pointer to the next
314 * argument, if any, otherwise pointer to null-character. */
315 char * vle_cmds_next_arg(const char args[]);
316
317 TSTATIC_DEFS(
318 int add_builtin_cmd(const char name[], CMD_TYPE type, const cmd_add_t *conf);
319 char ** dispatch_line(const char args[], int *count, char sep, int regexp,
320 int quotes, int noescaping, int comments, int *last_arg,
321 int (**positions)[2]);
322 )
323
324 #ifdef __cplusplus
325 }
326 #endif
327
328 #endif /* VIFM__ENGINE__CMDS_H__ */
329
330 /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
331 /* vim: set cinoptions+=t0 filetype=c : */
File engine/completion.c added (mode: 100644) (index 0000000..9ab89d7)
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 "completion.h"
20
21 #include <assert.h> /* assert() */
22 #include <stddef.h> /* NULL size_t */
23 #include <string.h> /* strdup() */
24
25 #include "../compat/reallocarray.h"
26 #include "../utils/darray.h"
27 #include "../utils/macros.h"
28 #include "../utils/str.h"
29 #include "../utils/utils.h"
30
31 static enum
32 {
33 NOT_STARTED,
34 FILLING_LIST,
35 COMPLETING
36 }
37 state = NOT_STARTED;
38
39 /* List of completion items. */
40 static vle_compl_t *items;
41 /* Declarations to enable use of DA_* on items. */
42 static DA_INSTANCE(items);
43 static int curr = -1;
44 static int group_begin;
45 static int order;
46
47 /* Function called . */
48 static vle_compl_add_path_hook_f add_path_hook = &strdup;
49
50 static int add_match(char match[], const char descr[]);
51 static void group_unique_sort(size_t start_index, size_t len);
52 static int sorter(const void *first, const void *second);
53 static void remove_duplicates(vle_compl_t arr[], size_t count);
54
55 void
56 vle_compl_reset(void)
57 {
58 size_t i;
59
60 for(i = 0U; i < DA_SIZE(items); ++i)
61 {
62 free(items[i].text);
63 free(items[i].descr);
64 }
65 DA_REMOVE_ALL(items);
66
67 state = NOT_STARTED;
68 curr = -1;
69 group_begin = 0;
70 order = 0;
71 }
72
73 int
74 vle_compl_add_match(const char match[], const char descr[])
75 {
76 char *const match_dup = strdup(match);
77 const int result = vle_compl_put_match(match_dup, descr);
78 if(result != 0)
79 {
80 free(match_dup);
81 }
82 return result;
83 }
84
85 int
86 vle_compl_put_match(char match[], const char descr[])
87 {
88 return add_match(match, descr);
89 }
90
91 int
92 vle_compl_add_path_match(const char path[])
93 {
94 char *const match = add_path_hook(path);
95 return add_match(match, "");
96 }
97
98 int
99 vle_compl_put_path_match(char path[])
100 {
101 if(add_path_hook == &strdup)
102 {
103 return add_match(path, "");
104 }
105 else
106 {
107 const int result = vle_compl_add_path_match(path);
108 free(path);
109 return result;
110 }
111 }
112
113 /* Adds new match to the list of matches. Becomes an owner of memory pointed to
114 * by the match. Errors if match is NULL. Returns zero on success, otherwise
115 * non-zero is returned. */
116 static int
117 add_match(char match[], const char descr[])
118 {
119 vle_compl_t *item;
120
121 assert(state != COMPLETING);
122
123 if(match == NULL)
124 {
125 return -1;
126 }
127
128 item = DA_EXTEND(items);
129 if(item == NULL)
130 {
131 return 1;
132 }
133
134 item->text = match;
135 item->descr = strdup(descr);
136 if(item->descr == NULL)
137 {
138 return 1;
139 }
140
141 DA_COMMIT(items);
142
143 state = FILLING_LIST;
144 return 0;
145 }
146
147 int
148 vle_compl_add_last_match(const char origin[])
149 {
150 return vle_compl_add_match(origin, "");
151 }
152
153 int
154 vle_compl_add_last_path_match(const char origin[])
155 {
156 return vle_compl_add_path_match(origin);
157 }
158
159 void
160 vle_compl_finish_group(void)
161 {
162 const size_t n_group_items = DA_SIZE(items) - group_begin;
163 group_unique_sort(group_begin, n_group_items);
164 }
165
166 void
167 vle_compl_unite_groups(void)
168 {
169 group_unique_sort(0, DA_SIZE(items));
170 }
171
172 /* Sorts items of the list in range [start_index, start_index + len) with
173 * deduplication. Updates number of list elements and next group beginning. */
174 static void
175 group_unique_sort(size_t start_index, size_t len)
176 {
177 vle_compl_t *const group_start = items + start_index;
178
179 assert(state != COMPLETING);
180
181 safe_qsort(group_start, len, sizeof(*group_start), &sorter);
182 remove_duplicates(group_start, len);
183 group_begin = DA_SIZE(items);
184 }
185
186 /* qsort() comparison criterion implementation. Returns standard < 0, = 0,
187 * > 0.*/
188 static int
189 sorter(const void *first, const void *second)
190 {
191 const char *const stra = ((const vle_compl_t *)first)->text;
192 const char *const strb = ((const vle_compl_t *)second)->text;
193 const size_t lena = strlen(stra);
194 const size_t lenb = strlen(strb);
195
196 if(strcmp(stra, "./") == 0 || strcmp(strb, "./") == 0)
197 {
198 return 1;
199 }
200
201 if(stra[lena - 1] == '/' && strb[lenb - 1] == '/')
202 {
203 size_t len = MIN(lena - 1, lenb - 1);
204 /* Compare case sensitive strings even on Windows. */
205 if(strncmp(stra, strb, len) == 0)
206 {
207 return lena - lenb;
208 }
209 }
210
211 /* Compare case sensitive strings even on Windows. */
212 return strcmp(stra, strb);
213 }
214
215 char *
216 vle_compl_next(void)
217 {
218 assert(state != NOT_STARTED && "Invalid unit state.");
219 state = COMPLETING;
220
221 if(curr == -1)
222 {
223 const size_t n_group_items = DA_SIZE(items) - group_begin;
224 remove_duplicates(items + group_begin, n_group_items);
225 }
226
227 if(DA_SIZE(items) == 2)
228 {
229 const int idx = (curr == -1) ? 0 : curr;
230 curr = 0;
231 return strdup(items[idx].text);
232 }
233
234 if(!order)
235 {
236 /* Straight order. */
237 curr = (curr + 1) % DA_SIZE(items);
238 }
239 else
240 {
241 /* Reverse order. */
242 if(curr == -1)
243 {
244 curr = DA_SIZE(items) - 2U;
245 }
246 else
247 {
248 --curr;
249 }
250 if(curr < 0)
251 {
252 curr = DA_SIZE(items) - 1U;
253 }
254 }
255 return strdup(items[curr].text);
256 }
257
258 /* Removes series of consecutive duplicates. */
259 static void
260 remove_duplicates(vle_compl_t arr[], size_t count)
261 {
262 size_t i, j;
263 j = 1U;
264 for(i = j; i < count; ++i)
265 {
266 /* Compare case sensitive strings even on Windows. */
267 if(strcmp(arr[i].text, arr[j - 1].text) == 0)
268 {
269 free(arr[i].text);
270 free(arr[i].descr);
271 continue;
272 }
273 arr[j++] = arr[i];
274 }
275 if(count != 0U)
276 {
277 DA_REMOVE_AFTER(items, &arr[j]);
278 }
279 }
280
281 int
282 vle_compl_get_count(void)
283 {
284 return DA_SIZE(items);
285 }
286
287 void
288 vle_compl_set_order(int reversed)
289 {
290 order = reversed;
291 }
292
293 const vle_compl_t *
294 vle_compl_get_items(void)
295 {
296 return items;
297 }
298
299 int
300 vle_compl_get_pos(void)
301 {
302 return curr;
303 }
304
305 void
306 vle_compl_rewind(void)
307 {
308 assert(state == COMPLETING && "Invalid unit state.");
309
310 if(DA_SIZE(items) == 2)
311 {
312 curr = 1;
313 }
314 else if(DA_SIZE(items) > 2)
315 {
316 curr = DA_SIZE(items) - 2;
317 }
318 }
319
320 void
321 vle_compl_set_add_path_hook(vle_compl_add_path_hook_f hook)
322 {
323 add_path_hook = (hook == NULL) ? &strdup : hook;
324 }
325
326 /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
327 /* vim: set cinoptions+=t0 filetype=c : */
File engine/completion.h added (mode: 100644) (index 0000000..05bd4bf)
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 #ifndef VIFM__ENGINE__COMPLETION_H__
20 #define VIFM__ENGINE__COMPLETION_H__
21
22 /* Single completion item. */
23 typedef struct
24 {
25 char *text; /* Item text. */
26 char *descr; /* Description of the item. */
27 }
28 vle_compl_t;
29
30 /* Match addition hook function signature. Must return newly allocated
31 * string. */
32 typedef char * (*vle_compl_add_path_hook_f)(const char match[]);
33
34 /* Adds raw match as completion match. Returns zero on success, otherwise
35 * non-zero is returned. */
36 int vle_compl_add_match(const char match[], const char descr[]);
37
38 /* Puts raw match as completion match, takes ownership of the match string.
39 * Returns zero on success, otherwise non-zero is returned. */
40 int vle_compl_put_match(char match[], const char descr[]);
41
42 /* Adds path as completion match. Path is preprocessed with path add hook.
43 * Returns zero on success, otherwise non-zero is returned. */
44 int vle_compl_add_path_match(const char path[]);
45
46 /* Puts path as completion match, takes ownership of the match string. Path is
47 * preprocessed with path add hook. Returns zero on success, otherwise non-zero
48 * is returned. */
49 int vle_compl_put_path_match(char path[]);
50
51 /* Adds original input to the completion, should be called after all matches are
52 * registered with vle_compl_add_match(). Returns zero on success, otherwise
53 * non-zero is returned. */
54 int vle_compl_add_last_match(const char origin[]);
55
56 /* Adds original path path input to the completion, should be called after all
57 * matches are registered with vle_compl_add_path_match(). Returns zero on
58 * success, otherwise non-zero is returned. */
59 int vle_compl_add_last_path_match(const char origin[]);
60
61 void vle_compl_finish_group(void);
62
63 /* Squashes all existing completion groups into one. Performs resorting and
64 * deduplication of resulting single group. */
65 void vle_compl_unite_groups(void);
66
67 void vle_compl_reset(void);
68
69 /* Returns copy of the string or NULL. */
70 char * vle_compl_next(void);
71
72 int vle_compl_get_count(void);
73
74 void vle_compl_set_order(int reversed);
75
76 /* Retrieves list of completion items. Returns the list of size
77 * vle_compl_get_count(). The array is managed by the unit. */
78 const vle_compl_t * vle_compl_get_items(void);
79
80 int vle_compl_get_pos(void);
81
82 /* Go to the last item (probably to user input). */
83 void vle_compl_rewind(void);
84
85 /* Sets match addition hook. NULL value resets hook. */
86 void vle_compl_set_add_path_hook(vle_compl_add_path_hook_f hook);
87
88 #endif /* VIFM__ENGINE__COMPLETION_H__ */
89
90 /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
91 /* vim: set cinoptions+=t0 filetype=c : */
File utils/darray.h added (mode: 100644) (index 0000000..ceaf776)
1 /* vifm
2 * Copyright (C) 2015 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 #ifndef VIFM__UTILS__DARRAY_H__
20 #define VIFM__UTILS__DARRAY_H__
21
22 /* Simple macros that wrap common operations on dynamic arrays. */
23
24 #include <assert.h> /* assert() */
25 #include <stddef.h> /* ptrdiff_t size_t */
26 #include <stdlib.h> /* free() */
27 #include <string.h> /* memcpy() */
28
29 #include "../compat/reallocarray.h"
30
31 /* Instantiates additional data for dynamic array implementation. Usage
32 * example:
33 * static int *array;
34 * static DA_INSTANCE(array); */
35 #define DA_INSTANCE(da) DA_INSTANCE_FIELD(da) = 0U
36
37 /* DA_INSTANCE equivalent for structures. */
38 #define DA_INSTANCE_FIELD(da) size_t da##_count__
39
40 /* Obtains lvalue of array size, its type is size_t. */
41 #define DA_SIZE(da) *(&da##_count__)
42
43 /* Extends array capacity (not size) by at least one more element. Returns
44 * pointer to the element or NULL on memory allocation error. Once element data
45 * is successfully initialized DA_COMMIT should be used to update size. */
46 #define DA_EXTEND(da) \
47 ({ \
48 typeof(da) last = NULL; \
49 void *const ptr = reallocarray(da, da##_count__ + 1U, sizeof(*da)); \
50 if(ptr != NULL) \
51 { \
52 da = ptr; \
53 last = &da[da##_count__]; \
54 } \
55 last; \
56 })
57
58 /* Increments array size, to be used in pair with DA_EXTEND. Usage example:
59 * char **const string = DA_EXTEND(strings);
60 * if(string != NULL)
61 * {
62 * *string = strdup(str);
63 * if(*string != NULL)
64 * {
65 * DA_COMMIT(strings);
66 * }
67 * } */
68 #define DA_COMMIT(a) do { ++a##_count__; } while(0)
69
70 /* Removes item specified by pointer. */
71 #define DA_REMOVE(da, item) \
72 do \
73 { \
74 const typeof(da) it = item; \
75 size_t i; \
76 assert(it >= da && "Wrong item pointer."); \
77 assert(it - da < (ptrdiff_t)da##_count__ && "Wrong item pointer."); \
78 for(i = it - da + 1U; i < da##_count__; ++i) \
79 { \
80 memcpy(&da[i - 1], &da[i], sizeof(*da)); \
81 } \
82 if(--da##_count__ == 0) \
83 { \
84 free(da); \
85 da = NULL; \
86 } \
87 } \
88 while(0)
89
90 /* Removes all elements starting from and including the item. */
91 #define DA_REMOVE_AFTER(da, item) \
92 do \
93 { \
94 const typeof(da) it = item; \
95 assert(it >= da && "Wrong item pointer."); \
96 assert(it - da <= (ptrdiff_t)da##_count__ && "Wrong item pointer."); \
97 da##_count__ = it - da; \
98 if(da##_count__ == 0) \
99 { \
100 free(da); \
101 da = NULL; \
102 } \
103 } \
104 while(0)
105
106 /* Empties the array freeing allocated memory. */
107 #define DA_REMOVE_ALL(da) \
108 do \
109 { \
110 da##_count__ = 0; \
111 free(da); \
112 da = NULL; \
113 } \
114 while(0)
115
116 #endif /* VIFM__UTILS__DARRAY_H__ */
117
118 /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
119 /* vim: set cinoptions+=t0 filetype=c : */
File utils/str.c changed (mode: 100644) (index 57094b5..f015ad1)
19 19
20 20 #include "str.h" #include "str.h"
21 21
22 #include <ctype.h> /* tolower() isspace() */
23 #include <limits.h> /* INT_MAX INT_MIN LONG_MAX LONG_MIN */
24 #include <stdarg.h> /* va_list va_start() va_copy() va_end() */
22 #include <stdarg.h> /* va_list va_start() va_end() */
25 23 #include <stddef.h> /* NULL size_t wchar_t */ #include <stddef.h> /* NULL size_t wchar_t */
26 #include <stdio.h> /* snprintf() */
27 #include <stdlib.h> /* free() malloc() mbstowcs() memmove() memset() realloc()
28 strtol() wcstombs() */
29 #include <string.h> /* strdup() strncmp() strlen() strcmp() strchr() strrchr()
30 strncpy() */
31 #include <wchar.h> /* wint_t vswprintf() */
32 #include <wctype.h> /* iswprint() iswupper() towlower() towupper() */
24 #include <string.h> /* strncmp() strlen() strchr() */
25 #include <wchar.h> /* vswprintf() */
33 26
34 27 #include "../compat/reallocarray.h" #include "../compat/reallocarray.h"
35 #include "macros.h"
28
29 static size_t copy_substr(char dst[], size_t dst_len, const char src[],
30 char terminator);
36 31
37 32 wchar_t * wchar_t *
38 33 vifm_wcsdup(const wchar_t ws[]) vifm_wcsdup(const wchar_t ws[])
 
... ... vifm_wcsdup(const wchar_t ws[])
47 42 return result; return result;
48 43 } }
49 44
45 int
46 starts_with(const char str[], const char prefix[])
47 {
48 const size_t prefix_len = strlen(prefix);
49 return starts_withn(str, prefix, prefix_len);
50 }
51
52 int
53 starts_withn(const char str[], const char prefix[], size_t prefix_len)
54 {
55 return strncmp(str, prefix, prefix_len) == 0;
56 }
57
50 58 int int
51 59 vifm_swprintf(wchar_t str[], size_t len, const wchar_t format[], ...) vifm_swprintf(wchar_t str[], size_t len, const wchar_t format[], ...)
52 60 { {
 
... ... vifm_swprintf(wchar_t str[], size_t len, const wchar_t format[], ...)
66 74 return result; return result;
67 75 } }
68 76
77 size_t
78 copy_str(char dst[], size_t dst_len, const char src[])
79 {
80 /* XXX: shouldn't we return "strlen(src)" instead of "0U"? */
81 return (dst == src) ? 0U : copy_substr(dst, dst_len, src, '\0');
82 }
83
84 /* Copies characters from the string pointed to by src and terminated by the
85 * terminator to piece of memory of size dst_len pointed to by dst. Ensures
86 * that copied string ends with null character. Does nothing for zero
87 * dst_len. Returns number of characters written, including terminating null
88 * character. */
89 static size_t
90 copy_substr(char dst[], size_t dst_len, const char src[], char terminator)
91 {
92 char *past_end;
93
94 if(dst_len == 0U)
95 {
96 return 0U;
97 }
98
99 past_end = memccpy(dst, src, terminator, dst_len);
100 if(past_end == NULL)
101 {
102 dst[dst_len - 1] = '\0';
103 return dst_len;
104 }
105 else
106 {
107 past_end[-1] = '\0';
108 return past_end - dst;
109 }
110 }
111
112 int
113 char_is_one_of(const char *list, char c)
114 {
115 return c != '\0' && strchr(list, c) != NULL;
116 }
117
118 void
119 unescape(char s[], int regexp)
120 {
121 char *p;
122
123 p = s;
124 while(s[0] != '\0')
125 {
126 if(s[0] == '\\' && (!regexp || s[1] == '/'))
127 {
128 ++s;
129 }
130 *p++ = s[0];
131 if(s[0] != '\0')
132 {
133 ++s;
134 }
135 }
136 *p = '\0';
137 }
138
69 139 /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */ /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
70 140 /* vim: set cinoptions+=t0 filetype=c : */ /* vim: set cinoptions+=t0 filetype=c : */
File utils/str.h changed (mode: 100644) (index ac523b6..55f2b71)
43 43 * error. */ * error. */
44 44 wchar_t * vifm_wcsdup(const wchar_t ws[]); wchar_t * vifm_wcsdup(const wchar_t ws[]);
45 45
46 /* Checks whether str starts with the given prefix. Returns non-zero if it's
47 * so, otherwise zero is returned. */
48 int starts_with(const char str[], const char prefix[]);
49
50 /* Checks whether str starts with the given prefix of specified length. Returns
51 * non-zero if it's so, otherwise zero is returned. */
52 int starts_withn(const char str[], const char prefix[], size_t prefix_len);
53
46 54 /* A wrapper of swprintf() functions to make its differences on various /* A wrapper of swprintf() functions to make its differences on various
47 55 * platforms transparently in other parts of the program. */ * platforms transparently in other parts of the program. */
48 56 int vifm_swprintf(wchar_t str[], size_t len, const wchar_t format[], ...); int vifm_swprintf(wchar_t str[], size_t len, const wchar_t format[], ...);
49 57
58 /* Copies characters from the string pointed to by str to piece of memory of
59 * size dst_len pointed to by dst. Ensures that copied string ends with null
60 * character. Does nothing for zero dst_len. Returns number of characters
61 * written, including terminating null character. */
62 size_t copy_str(char dst[], size_t dst_len, const char src[]);
63
64 /* Checks if the c is one of characters in the list string. c cannot be '\0'. */
65 int char_is_one_of(const char *list, char c);
66
67 /* Unescapes string in place (removes extra slashes). regexp flag narrows set
68 * of unescaped characters to "/". */
69 void unescape(char s[], int regexp);
70
50 71 #endif /* VIFM__UTILS__STR_H__ */ #endif /* VIFM__UTILS__STR_H__ */
51 72
52 73 /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */ /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
File utils/string_array.c copied from file engine/mode.c (similarity 50%) (mode: 100644) (index 3e8dcf2..b287b55)
1 1 /* vifm /* vifm
2 * Copyright (C) 2014 xaizek.
2 * Copyright (C) 2011 xaizek.
3 3 * *
4 4 * This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
5 5 * it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
 
16 16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
17 17 */ */
18 18
19 #include "mode.h"
19 #include "string_array.h"
20 20
21 static vle_mode_t current_mode;
22 static vle_mode_t primary_mode;
21 #include <stddef.h> /* NULL size_t */
22 #include <stdlib.h> /* free() */
23 #include <string.h> /* strdup() */
23 24
24 vle_mode_t
25 vle_mode_get(void)
26 {
27 return current_mode;
28 }
25 #include "../compat/reallocarray.h"
29 26
30 27 int int
31 vle_mode_is(vle_mode_t mode)
28 add_to_string_array(char ***array, int len, const char item[])
32 29 { {
33 return current_mode == mode;
30 char **p = reallocarray(*array, len + 1, sizeof(*p));
31 if(p == NULL)
32 {
33 return len;
34 }
35 *array = p;
36
37 if(item == NULL)
38 {
39 p[len++] = NULL;
40 }
41 else if((p[len] = strdup(item)) != NULL)
42 {
43 ++len;
44 }
45
46 return len;
34 47 } }
35 48
36 vle_mode_t
37 vle_mode_get_primary(void)
49 int
50 put_into_string_array(char ***array, int len, char item[])
38 51 { {
39 return primary_mode;
52 char **const arr = reallocarray(*array, len + 1, sizeof(char *));
53 if(arr != NULL)
54 {
55 *array = arr;
56 arr[len++] = item;
57 }
58 return len;
40 59 } }
41 60
42 int
43 vle_primary_mode_is(vle_mode_t mode)
61 void
62 free_string_array(char *array[], size_t len)
44 63 { {
45 return primary_mode == mode;
64 if(array != NULL)
65 {
66 free_strings(array, len);
67 free(array);
68 }
46 69 } }
47 70
48 71 void void
49 vle_mode_set(vle_mode_t mode, VleModeType type)
72 free_strings(char *array[], size_t len)
50 73 { {
51 current_mode = mode;
52 if(type == VMT_PRIMARY)
74 size_t i;
75 for(i = 0; i < len; ++i)
53 76 { {
54 primary_mode = mode;
77 free(array[i]);
55 78 } }
56 79 } }
57 80
File utils/string_array.h copied from file utils/str.h (similarity 50%) (mode: 100644) (index ac523b6..c79ad7c)
1 1 /* vifm /* vifm
2 * Copyright (C) 2001 Ken Steen.
3 2 * Copyright (C) 2011 xaizek. * Copyright (C) 2011 xaizek.
4 3 * *
5 4 * This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
 
17 16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
18 17 */ */
19 18
20 #ifndef VIFM__UTILS__STR_H__
21 #define VIFM__UTILS__STR_H__
19 #ifndef VIFM__UTILS__STRING_ARRAY_H__
20 #define VIFM__UTILS__STRING_ARRAY_H__
22 21
23 #include <inttypes.h> /* PRIu64 */
24 #include <stddef.h> /* size_t wchar_t */
22 #include <stddef.h> /* size_t */
25 23
26 #include "macros.h"
24 /* Adds copy of a string to a string array. Input pointer can be NULL. Returns
25 * new length of the array, which is unchanged on allocation failure. */
26 int add_to_string_array(char ***array, int len, const char item[]);
27 27
28 #if defined(_WIN32) && !defined(_WIN64)
29 #ifdef BROKEN_SWPRINTF
30 #define WPRINTF_WSTR L"s"
31 #else
32 #define WPRINTF_WSTR L"ls"
33 #endif
34 #define PRINTF_ULL PRIu64
35 #else
36 #define WPRINTF_WSTR L"ls"
37 #define PRINTF_ULL "llu"
38 #endif
28 /* Puts pointer into string array without making a copy. Reallocates *array.
29 * item can be NULL. Returns new size of the array, which can be equal to len
30 * on reallocation failure. */
31 int put_into_string_array(char **array[], int len, char item[]);
39 32
40 /* Various string functions. */
33 /* Frees memory of all array items and from the array itself. Does nothing for
34 * NULL arrays. */
35 void free_string_array(char *array[], size_t len);
41 36
42 /* Duplicates a wide-character string. Returns pointer to new string or NULL on
43 * error. */
44 wchar_t * vifm_wcsdup(const wchar_t ws[]);
37 /* Frees memory of all array items, but not from the array itself. */
38 void free_strings(char *array[], size_t len);
45 39
46 /* A wrapper of swprintf() functions to make its differences on various
47 * platforms transparently in other parts of the program. */
48 int vifm_swprintf(wchar_t str[], size_t len, const wchar_t format[], ...);
49
50 #endif /* VIFM__UTILS__STR_H__ */
40 #endif /* VIFM__UTILS__STRING_ARRAY_H__ */
51 41
52 42 /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */ /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
53 43 /* vim: set cinoptions+=t0 filetype=c : */ /* vim: set cinoptions+=t0 filetype=c : */
File utils/test_helpers.h copied from file compat/reallocarray.h (similarity 68%) (mode: 100644) (index bc7fc6b..797c052)
1 1 /* vifm /* vifm
2 * Copyright (C) 2015 xaizek.
2 * Copyright (C) 2012 xaizek.
3 3 * *
4 4 * This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
5 5 * it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
 
16 16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
17 17 */ */
18 18
19 #ifndef VIFM__COMPAT__REALLOCARRAY_H__
20 #define VIFM__COMPAT__REALLOCARRAY_H__
19 #ifndef VIFM__UTILS__TEST_HELPERS_H__
20 #define VIFM__UTILS__TEST_HELPERS_H__
21 21
22 #include <stddef.h> /* size_t */
22 #ifdef TEST
23 #define TSTATIC
24 #define TSTATIC_DEFS(x) x
25 #else /* TEST */
26 #define TSTATIC
27 #define TSTATIC_DEFS(x)
28 #endif /* TEST */
23 29
24 /* Almost the same as realloc(), but with a different way of specifying size
25 * and checks for integer overflow in the calculation of nmemb * size. */
26 void * reallocarray(void *optr, size_t nmemb, size_t size);
27
28 #endif /* VIFM__COMPAT__REALLOCARRAY_H__ */
30 #endif /* VIFM__UTILS__TEST_HELPERS_H__ */
29 31
30 32 /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */ /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
31 33 /* vim: set cinoptions+=t0 filetype=c : */ /* vim: set cinoptions+=t0 filetype=c : */
File utils/utils.c added (mode: 100644) (index 0000000..4670233)
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 "utils.h"
20
21 #include <stddef.h> /* size_t */
22 #include <stdlib.h> /* qsort() */
23
24 #define LOG_ERROR_MSG(...)
25
26 void
27 expand_squotes_escaping(char s[])
28 {
29 char *p;
30 int sq_found;
31
32 p = s++;
33 sq_found = *p == '\'';
34 while(*p != '\0')
35 {
36 if(*s == '\'' && sq_found)
37 {
38 sq_found = 0;
39 }
40 else
41 {
42 *++p = *s;
43 sq_found = *s == '\'';
44 }
45 s++;
46 }
47 }
48
49 void
50 expand_dquotes_escaping(char s[])
51 {
52 static const char table[] =
53 /* 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f */
54 /* 00 */ "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
55 /* 10 */ "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"
56 /* 20 */ "\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f"
57 /* 30 */ "\x00\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f"
58 /* 40 */ "\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f"
59 /* 50 */ "\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f"
60 /* 60 */ "\x60\x07\x0b\x63\x64\x65\x0c\x67\x68\x69\x6a\x6b\x6c\x6d\x0a\x6f"
61 /* 70 */ "\x70\x71\x0d\x73\x09\x75\x0b\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f"
62 /* 80 */ "\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f"
63 /* 90 */ "\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f"
64 /* a0 */ "\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf"
65 /* b0 */ "\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf"
66 /* c0 */ "\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf"
67 /* d0 */ "\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf"
68 /* e0 */ "\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef"
69 /* f0 */ "\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff";
70
71 char *str = s;
72 char *p;
73
74 p = s;
75 while(*s != '\0')
76 {
77 if(*s != '\\')
78 {
79 *p++ = *s++;
80 continue;
81 }
82 s++;
83 if(*s == '\0')
84 {
85 LOG_ERROR_MSG("Escaped eol in \"%s\"", str);
86 break;
87 }
88 *p++ = table[(int)*s++];
89 }
90 *p = '\0';
91 }
92
93 void
94 safe_qsort(void *base, size_t nmemb, size_t size,
95 int (*compar)(const void *, const void *))
96 {
97 if(nmemb != 0U)
98 {
99 /* Even when there are no entries, qsort() shouldn't be called with a NULL
100 * parameter. It isn't allowed by the standard. */
101 qsort(base, nmemb, size, compar);
102 }
103 }
104
105 /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
106 /* vim: set cinoptions+=t0 filetype=c : */
File utils/utils.h copied from file compat/reallocarray.h (similarity 59%) (mode: 100644) (index bc7fc6b..efb0e16)
1 1 /* vifm /* vifm
2 * Copyright (C) 2015 xaizek.
2 * Copyright (C) 2001 Ken Steen.
3 * Copyright (C) 2011 xaizek.
3 4 * *
4 5 * This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
5 6 * it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
 
16 17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
17 18 */ */
18 19
19 #ifndef VIFM__COMPAT__REALLOCARRAY_H__
20 #define VIFM__COMPAT__REALLOCARRAY_H__
20 #ifndef VIFM__UTILS__UTILS_H__
21 #define VIFM__UTILS__UTILS_H__
21 22
22 23 #include <stddef.h> /* size_t */ #include <stddef.h> /* size_t */
23 24
24 /* Almost the same as realloc(), but with a different way of specifying size
25 * and checks for integer overflow in the calculation of nmemb * size. */
26 void * reallocarray(void *optr, size_t nmemb, size_t size);
25 /* Expands double ' sequences from single quoted string in place. */
26 void expand_squotes_escaping(char s[]);
27 27
28 #endif /* VIFM__COMPAT__REALLOCARRAY_H__ */
28 /* Expands escape sequences from double quoted string (e.g. "\n") in place. */
29 void expand_dquotes_escaping(char s[]);
30
31 /* Wrapper around qsort() that allows base to be NULL when nmemb is 0 by
32 * skipping call to qsort(). */
33 void safe_qsort(void *base, size_t nmemb, size_t size,
34 int (*compar)(const void *, const void *));
35
36 #endif /* VIFM__UTILS__UTILS_H__ */
29 37
30 38 /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */ /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
31 39 /* vim: set cinoptions+=t0 filetype=c : */ /* vim: set cinoptions+=t0 filetype=c : */
Hints

Before first commit, do not forget to setup your git environment:
git config --global user.name "your_name_here"
git config --global user.email "your@email_here"

Clone this repository using HTTP(S):
git clone https://code.reversed.top/user/xaizek/libvle

Clone this repository using ssh (do not forget to upload a key first):
git clone ssh://rocketgit@code.reversed.top/user/xaizek/libvle

You are allowed to anonymously push to this repository.
This means that your pushed commits will automatically be transformed into a pull request:
... clone the repository ...
... make some changes and some commits ...
git push origin master