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 *state;
374 const struct ec_node *node;
375 const char *node_help = NULL;
376 const char *node_desc = NULL;
381 grp = ec_comp_item_get_grp(item);
383 for (state = ec_comp_group_get_state(grp); state != NULL;
384 state = ec_pnode_get_parent(state)) {
385 node = ec_pnode_get_node(state);
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);
392 if (node_help == NULL)
394 if (node_desc == NULL)
397 help->desc = ec_strdup(node_desc);
398 if (help->desc == NULL)
401 help->help = ec_strdup(node_help);
402 if (help->help == NULL)
414 ec_editline_get_helps(const struct ec_editline *editline, const char *line,
415 const char *full_line, struct ec_editline_help **helps_out)
417 const struct ec_comp_group *grp, *prev_grp = NULL;
418 struct ec_comp_item *item;
419 struct ec_comp *cmpl = NULL;
420 struct ec_pnode *parse = NULL;
421 unsigned int count = 0;
422 struct ec_editline_help *helps = NULL;
426 /* check if the current line matches */
427 parse = ec_parse(editline->node, full_line);
428 if (ec_pnode_matches(parse))
430 ec_pnode_free(parse);
433 /* complete at current cursor position */
434 cmpl = ec_complete(editline->node, line);
435 if (cmpl == NULL) //XXX log error
438 helps = ec_calloc(1, sizeof(*helps));
442 helps[0].desc = ec_strdup("<return>");
443 if (helps[0].desc == NULL)
445 helps[0].help = ec_strdup("Validate command.");
446 if (helps[0].help == NULL)
450 /* let's display one contextual help per node */
451 EC_COMP_FOREACH(item, cmpl,
452 EC_COMP_UNKNOWN | EC_COMP_FULL | EC_COMP_PARTIAL) {
453 struct ec_editline_help *tmp = NULL;
455 /* keep one help per group, skip other items */
456 grp = ec_comp_item_get_grp(item);
462 tmp = ec_realloc(helps, (count + 1) * sizeof(*helps));
466 if (get_node_help(item, &helps[count]) < 0)
477 ec_pnode_free(parse);
481 ec_free(helps[count].desc);
482 ec_free(helps[count].help);
491 ec_editline_complete(EditLine *el, int c)
493 struct ec_editline *editline;
494 const LineInfo *line_info;
495 int ret = CC_REFRESH;
496 struct ec_comp *cmpl = NULL;
503 if (el_get(el, EL_GETFP, 1, &out))
505 if (el_get(el, EL_GETFP, 1, &err))
510 if (el_get(el, EL_CLIENTDATA, &clientdata)) {
511 fprintf(err, "completion failure: no client data\n");
514 editline = clientdata;
517 line_info = el_line(el);
518 if (line_info == NULL) {
519 fprintf(err, "completion failure: no line info\n");
523 len = line_info->cursor - line_info->buffer;
524 if (ec_asprintf(&line, "%.*s", len, line_info->buffer) < 0) {
525 fprintf(err, "completion failure: no memory\n");
529 if (editline->node == NULL) {
530 fprintf(err, "completion failure: no ec_node\n");
534 cmpl = ec_complete(editline->node, line);
538 append = ec_editline_append_chars(cmpl);
541 struct ec_editline_help *helps = NULL;
544 count = ec_editline_get_helps(editline, line, line_info->buffer,
548 if (ec_editline_print_helps(editline, helps, count) < 0) {
549 fprintf(err, "completion failure: cannot show help\n");
550 ec_editline_free_helps(helps, count);
554 ec_editline_free_helps(helps, count);
556 } else if (append == NULL || strcmp(append, "") == 0) {
557 char **matches = NULL;
560 count = ec_editline_get_completions(cmpl, &matches);
562 fprintf(err, "completion failure: cannot get completions\n");
566 if (ec_editline_print_cols(
568 EC_CAST(matches, char **,
569 char const * const *),
571 fprintf(err, "completion failure: cannot print\n");
572 ec_editline_free_completions(matches, count);
576 ec_editline_free_completions(matches, count);
579 if (el_insertstr(el, append) < 0) {
580 fprintf(err, "completion failure: cannot insert\n");
583 if (ec_comp_count(cmpl, EC_COMP_FULL) +
584 ec_comp_count(cmpl, EC_COMP_PARTIAL) == 1) {
585 if (el_insertstr(el, " ") < 0) {
586 fprintf(err, "completion failure: cannot insert space\n");
607 ec_editline_gets(struct ec_editline *editline)
609 EditLine *el = editline->el;
610 char *line_copy = NULL;
614 line = el_gets(el, &count);
618 line_copy = ec_strdup(line);
619 if (line_copy == NULL)
622 line_copy[strlen(line_copy) - 1] = '\0'; //XXX needed because of sh_lex bug?
624 if (editline->history != NULL && !ec_str_is_space(line_copy)) {
625 history(editline->history, &editline->histev,
637 ec_editline_parse(struct ec_editline *editline, const struct ec_node *node)
640 struct ec_pnode *parse = NULL;
642 /* XXX add sh_lex automatically? This node is required, parse and
643 * complete are based on it. */
645 ec_editline_set_node(editline, node);
647 line = ec_editline_gets(editline);
651 parse = ec_parse(node, line);
660 ec_pnode_free(parse);