1 /* SPDX-License-Identifier: BSD-3-Clause
2 * Copyright 2018, Olivier MATZ <zer0@droids-corp.org>
11 #include <ecoli_utils.h>
12 #include <ecoli_malloc.h>
13 #include <ecoli_string.h>
14 #include <ecoli_editline.h>
15 #include <ecoli_dict.h>
16 #include <ecoli_node.h>
17 #include <ecoli_parse.h>
18 #include <ecoli_complete.h>
24 const struct ec_node *node;
28 /* used by qsort below */
30 strcasecmp_cb(const void *p1, const void *p2)
32 return strcasecmp(*(char * const *)p1, *(char * const *)p2);
35 /* Show the matches as a multi-columns list */
37 ec_editline_print_cols(struct ec_editline *editline,
38 char const * const *matches, size_t n)
40 size_t max_strlen = 0, len, i, j, ncols;
43 char **matches_copy = NULL;
46 if (el_get(editline->el, EL_GETFP, 1, &f))
53 if (el_get(editline->el, EL_GETTC, "li", &height, (void *)0))
55 if (el_get(editline->el, EL_GETTC, "co", &width, (void *)0))
58 /* duplicate the matches table, and sort it */
59 matches_copy = calloc(n, sizeof(const char *));
60 if (matches_copy == NULL)
62 memcpy(matches_copy, matches, sizeof(const char *) * n);
63 qsort(matches_copy, n, sizeof(char *), strcasecmp_cb);
65 /* get max string length */
66 for (i = 0; i < n; i++) {
67 len = strlen(matches_copy[i]);
72 /* write the columns */
73 ncols = width / (max_strlen + 4);
76 for (i = 0; i < n; i+= ncols) {
77 for (j = 0; j < ncols; j++) {
84 fprintf(f, "%s%-*s", space,
85 (int)max_strlen, matches[i+j]);
94 /* Show the helps on editline output */
96 ec_editline_print_helps(struct ec_editline *editline,
97 const struct ec_editline_help *helps, size_t len)
102 if (el_get(editline->el, EL_GETFP, 1, &out))
105 for (i = 0; i < len; i++) {
106 if (fprintf(out, "%-20s %s\n",
107 helps[i].desc, helps[i].help) < 0)
115 ec_editline_free_helps(struct ec_editline_help *helps, size_t len)
121 for (i = 0; i < len; i++) {
122 ec_free(helps[i].desc);
123 ec_free(helps[i].help);
129 ec_editline_set_prompt(struct ec_editline *editline, const char *prompt)
133 if (prompt != NULL) {
139 ec_free(editline->prompt);
140 editline->prompt = copy;
146 prompt_cb(EditLine *el)
148 struct ec_editline *editline;
151 if (el_get(el, EL_CLIENTDATA, &clientdata))
153 editline = clientdata;
155 if (editline == NULL)
158 return editline->prompt;
162 ec_editline(const char *name, FILE *f_in, FILE *f_out, FILE *f_err,
165 struct ec_editline *editline = NULL;
168 if (f_in == NULL || f_out == NULL || f_err == NULL) {
173 editline = ec_calloc(1, sizeof(*editline));
174 if (editline == NULL)
177 el = el_init(name, f_in, f_out, f_err);
182 /* save editline pointer as user data */
183 if (el_set(el, EL_CLIENTDATA, editline))
186 /* install default editline signals */
187 if (el_set(el, EL_SIGNAL, 1))
190 if (el_set(el, EL_PREP_TERM, 0))
193 /* use emacs bindings */
194 if (el_set(el, EL_EDITOR, "emacs"))
196 if (el_set(el, EL_BIND, "^W", "ed-delete-prev-word", NULL))
199 /* ask terminal to not send signals */
200 if (flags & EC_EDITLINE_DISABLE_SIGNALS) {
201 if (el_set(el, EL_SETTY, "-d", "-isig", NULL))
206 editline->prompt = ec_strdup("> ");
207 if (editline->prompt == NULL)
209 if (el_set(el, EL_PROMPT, prompt_cb))
213 if ((flags & EC_EDITLINE_DISABLE_HISTORY) == 0) {
214 if (ec_editline_set_history(
215 editline, EC_EDITLINE_HISTORY_SIZE) < 0)
219 /* register completion callback */
220 if ((flags & EC_EDITLINE_DISABLE_COMPLETION) == 0) {
221 if (el_set(el, EL_ADDFN, "ed-complete", "Complete buffer",
222 ec_editline_complete))
224 if (el_set(el, EL_BIND, "^I", "ed-complete", NULL))
226 if (el_set(el, EL_BIND, "?", "ed-complete", NULL))
233 ec_editline_free(editline);
237 void ec_editline_free(struct ec_editline *editline)
239 if (editline == NULL)
241 if (editline->el != NULL)
242 el_end(editline->el);
243 if (editline->history != NULL)
244 history_end(editline->history);
245 ec_free(editline->prompt);
249 EditLine *ec_editline_get_el(struct ec_editline *editline)
254 const struct ec_node *
255 ec_editline_get_node(struct ec_editline *editline)
257 return editline->node;
261 ec_editline_set_node(struct ec_editline *editline, const struct ec_node *node)
263 editline->node = node;
266 int ec_editline_set_history(struct ec_editline *editline,
269 EditLine *el = editline->el;
271 if (editline->history != NULL)
272 history_end(editline->history);
277 editline->history = history_init();
278 if (editline->history == NULL)
280 if (history(editline->history, &editline->histev, H_SETSIZE,
283 if (history(editline->history, &editline->histev, H_SETUNIQUE, 1))
285 if (el_set(el, EL_HIST, history, editline->history))
292 if (editline->history != NULL) {
293 history_end(editline->history);
294 editline->history = NULL;
299 void ec_editline_free_completions(char **matches, size_t len)
303 // XXX use ec_malloc/ec_free() instead for consistency
306 for (i = 0; i < len; i++)
312 ec_editline_get_completions(const struct ec_comp *cmpl, char ***matches_out)
314 struct ec_comp_item *item;
315 char **matches = NULL;
318 EC_COMP_FOREACH(item, cmpl, EC_COMP_FULL | EC_COMP_PARTIAL) {
321 tmp = realloc(matches, (count + 1) * sizeof(char *));
325 matches[count] = strdup(ec_comp_item_get_display(item));
326 if (matches[count] == NULL)
331 *matches_out = matches;
335 ec_editline_free_completions(matches, count);
341 ec_editline_append_chars(const struct ec_comp *cmpl)
343 struct ec_comp_item *item;
348 EC_COMP_FOREACH(item, cmpl, EC_COMP_FULL | EC_COMP_PARTIAL) {
349 append = ec_comp_item_get_completion(item);
351 ret = ec_strdup(append);
355 n = ec_strcmp_count(ret, append);
368 /* this function builds the help string */
369 static int get_node_help(const struct ec_comp_item *item,
370 struct ec_editline_help *help)
372 const struct ec_comp_group *grp;
373 const struct ec_pnode *pstate;
374 const struct ec_node *node;
375 const char *node_help = NULL;
376 char *node_desc = NULL;
381 grp = ec_comp_item_get_grp(item);
383 for (pstate = ec_comp_group_get_pstate(grp); pstate != NULL;
384 pstate = ec_pnode_get_parent(pstate)) {
385 node = ec_pnode_get_node(pstate);
386 if (node_help == NULL)
387 node_help = ec_dict_get(ec_node_attrs(node), "help");
388 if (node_desc == NULL) {
389 node_desc = ec_node_desc(node);
390 if (node_desc == NULL)
395 if (node_desc == NULL)
397 if (node_help == NULL)
400 help->desc = node_desc;
401 help->help = ec_strdup(node_help);
402 if (help->help == NULL)
415 ec_editline_get_helps(const struct ec_editline *editline, const char *line,
416 const char *full_line, struct ec_editline_help **helps_out)
418 const struct ec_comp_group *grp, *prev_grp = NULL;
419 struct ec_comp_item *item;
420 struct ec_comp *cmpl = NULL;
421 struct ec_pnode *parse = NULL;
422 unsigned int count = 0;
423 struct ec_editline_help *helps = NULL;
427 /* check if the current line matches */
428 parse = ec_parse(editline->node, full_line);
429 if (ec_pnode_matches(parse))
431 ec_pnode_free(parse);
434 /* complete at current cursor position */
435 cmpl = ec_complete(editline->node, line);
436 if (cmpl == NULL) //XXX log error
439 helps = ec_calloc(1, sizeof(*helps));
443 helps[0].desc = ec_strdup("<return>");
444 if (helps[0].desc == NULL)
446 helps[0].help = ec_strdup("Validate command.");
447 if (helps[0].help == NULL)
451 /* let's display one contextual help per node */
452 EC_COMP_FOREACH(item, cmpl,
453 EC_COMP_UNKNOWN | EC_COMP_FULL | EC_COMP_PARTIAL) {
454 struct ec_editline_help *tmp = NULL;
456 /* keep one help per group, skip other items */
457 grp = ec_comp_item_get_grp(item);
463 tmp = ec_realloc(helps, (count + 1) * sizeof(*helps));
467 if (get_node_help(item, &helps[count]) < 0)
478 ec_pnode_free(parse);
482 ec_free(helps[count].desc);
483 ec_free(helps[count].help);
492 ec_editline_complete(EditLine *el, int c)
494 struct ec_editline *editline;
495 const LineInfo *line_info;
496 int ret = CC_REFRESH;
497 struct ec_comp *cmpl = NULL;
504 if (el_get(el, EL_GETFP, 1, &out))
506 if (el_get(el, EL_GETFP, 1, &err))
511 if (el_get(el, EL_CLIENTDATA, &clientdata)) {
512 fprintf(err, "completion failure: no client data\n");
515 editline = clientdata;
518 line_info = el_line(el);
519 if (line_info == NULL) {
520 fprintf(err, "completion failure: no line info\n");
524 len = line_info->cursor - line_info->buffer;
525 if (ec_asprintf(&line, "%.*s", len, line_info->buffer) < 0) {
526 fprintf(err, "completion failure: no memory\n");
530 if (editline->node == NULL) {
531 fprintf(err, "completion failure: no ec_node\n");
535 cmpl = ec_complete(editline->node, line);
539 append = ec_editline_append_chars(cmpl);
542 struct ec_editline_help *helps = NULL;
545 count = ec_editline_get_helps(editline, line, line_info->buffer,
549 if (ec_editline_print_helps(editline, helps, count) < 0) {
550 fprintf(err, "completion failure: cannot show help\n");
551 ec_editline_free_helps(helps, count);
555 ec_editline_free_helps(helps, count);
557 } else if (append == NULL || strcmp(append, "") == 0) {
558 char **matches = NULL;
561 count = ec_editline_get_completions(cmpl, &matches);
563 fprintf(err, "completion failure: cannot get completions\n");
567 if (ec_editline_print_cols(
569 EC_CAST(matches, char **,
570 char const * const *),
572 fprintf(err, "completion failure: cannot print\n");
573 ec_editline_free_completions(matches, count);
577 ec_editline_free_completions(matches, count);
580 if (el_insertstr(el, append) < 0) {
581 fprintf(err, "completion failure: cannot insert\n");
584 if (ec_comp_count(cmpl, EC_COMP_FULL) +
585 ec_comp_count(cmpl, EC_COMP_PARTIAL) == 1) {
586 if (el_insertstr(el, " ") < 0) {
587 fprintf(err, "completion failure: cannot insert space\n");
608 ec_editline_gets(struct ec_editline *editline)
610 EditLine *el = editline->el;
611 char *line_copy = NULL;
615 line = el_gets(el, &count);
619 line_copy = ec_strdup(line);
620 if (line_copy == NULL)
623 line_copy[strlen(line_copy) - 1] = '\0'; //XXX needed because of sh_lex bug?
625 if (editline->history != NULL && !ec_str_is_space(line_copy)) {
626 history(editline->history, &editline->histev,
638 ec_editline_parse(struct ec_editline *editline, const struct ec_node *node)
641 struct ec_pnode *parse = NULL;
643 /* XXX add sh_lex automatically? This node is required, parse and
644 * complete are based on it. */
646 ec_editline_set_node(editline, node);
648 line = ec_editline_gets(editline);
652 parse = ec_parse(node, line);
661 ec_pnode_free(parse);