2 * Copyright 2018 6WIND S.A.
19 #include "string_utils.h"
22 #define NC_CLI_HISTORY_SIZE 128
24 struct nc_cli_editline {
29 nc_cli_editline_complete_t complete;
34 char *(*prompt_cb)(EditLine *);
37 struct nc_cli_editline *nc_cli_el;
39 static int check_quotes(const char *str)
44 while (str[i] != '\0') {
46 if (str[i] == '"' || str[i] == '\'') {
52 if (str[i] == quote) {
55 } else if (str[i] == '\\' && str[i+1] == quote) {
68 editline_break(EditLine *editline, int c)
70 struct nc_cli_editline *el;
75 if (el_get(editline, EL_CLIENTDATA, &ptr))
79 el->break_received = true;
80 nc_cli_printf(el, "\n");
86 editline_suspend(EditLine *editline, int c)
91 kill(getpid(), SIGSTOP);
97 editline_get_screen_size(struct nc_cli_editline *el, size_t *rows, size_t *cols)
102 if (el_get(el->editline, EL_GETTC, "li", &h, (void *)0))
107 if (el_get(el->editline, EL_GETTC, "co", &w, (void *)0))
115 nc_cli_editline_get_file(struct nc_cli_editline *el, int num)
123 if (el_get(el->editline, EL_GETFP, num, &f))
129 /* match the prototype expected by qsort() */
131 strcasecmp_cb(const void *p1, const void *p2)
133 return strcasecmp(*(char * const *)p1, *(char * const *)p2);
136 /* Show the matches as a multi-columns list */
138 nc_cli_editline_print_cols(struct nc_cli_editline *el,
139 char const * const *matches, size_t n)
141 size_t max_strlen = 0, len, i, j, ncols;
142 size_t width, height;
144 char **matches_copy = NULL;
146 nc_cli_printf(nc_cli_el, "\n");
150 if (editline_get_screen_size(el, &height, &width) < 0)
153 /* duplicate the matches table, and sort it */
154 matches_copy = calloc(n, sizeof(const char *));
155 if (matches_copy == NULL)
157 memcpy(matches_copy, matches, sizeof(const char *) * n);
158 qsort(matches_copy, n, sizeof(char *), strcasecmp_cb);
160 /* get max string length */
161 for (i = 0; i < n; i++) {
162 len = strlen(matches_copy[i]);
163 if (len > max_strlen)
167 /* write the columns */
168 ncols = width / (max_strlen + 4);
171 for (i = 0; i < n; i+= ncols) {
172 for (j = 0; j < ncols; j++) {
179 nc_cli_printf(nc_cli_el, "%s%-*s", space,
180 (int)max_strlen, matches[i+j]);
182 nc_cli_printf(nc_cli_el, "\n");
190 editline_complete(EditLine *editline, int c)
192 enum nc_cli_editline_complete_status ret;
193 const LineInfo *line_info;
194 struct nc_cli_editline *el;
199 if (el_get(editline, EL_CLIENTDATA, &ptr))
204 if (el->complete == NULL)
207 line_info = el_line(editline);
208 if (line_info == NULL)
211 len = line_info->cursor - line_info->buffer;
212 if (asprintf(&line, "%s%.*s", el->full_line ? : "", len,
213 line_info->buffer) < 0)
216 if (c == '?' && check_quotes(line) != 0) {
218 el_insertstr(editline, "?");
222 ret = el->complete(c, line);
227 else if (ret == REDISPLAY)
233 static bool is_blank_string(const char *s)
244 multiline_prompt_cb(EditLine *e)
247 return strdup("... ");
251 nc_cli_editline_edit(struct nc_cli_editline *el)
256 assert(el->editline != NULL);
258 if (el->incomplete_line == false) {
260 el->full_line = NULL;
263 el->break_received = false;
265 if (el->incomplete_line)
266 el_set(el->editline, EL_PROMPT, multiline_prompt_cb);
268 el_set(el->editline, EL_PROMPT, el->prompt_cb);
270 line = el_gets(el->editline, &count);
272 if (line == NULL && el->break_received) {
274 el->full_line = NULL;
275 el->incomplete_line = false;
276 return ""; /* abort current line */
279 if (line == NULL || astrcat(&el->full_line, line) < 0) {
281 el->full_line = NULL;
282 el->incomplete_line = false;
283 return NULL; /* error / eof */
286 if (check_quotes(el->full_line) != 0) {
287 el->incomplete_line = true;
291 el->incomplete_line = false;
292 if (el->history != NULL && !is_blank_string(el->full_line))
293 history(el->history, &el->histev,
294 H_ENTER, el->full_line);
296 return el->full_line;
300 nc_cli_editline_register_complete(struct nc_cli_editline *el,
301 nc_cli_editline_complete_t complete)
305 if (el_set(el->editline, EL_ADDFN, "ed-complete",
310 if (complete != NULL)
311 name = "ed-complete";
313 name = "ed-unassigned";
314 if (el_set(el->editline, EL_BIND, "^I", name, NULL))
317 if (complete != NULL)
318 name = "ed-complete";
321 if (el_set(el->editline, EL_BIND, "?", name, NULL))
324 el->complete = complete;
330 nc_cli_editline_insert_str(struct nc_cli_editline *el, const char *str)
332 return el_insertstr(el->editline, str);
335 int nc_cli_printf(struct nc_cli_editline *el, const char *format, ...)
337 FILE *out = nc_cli_editline_get_file(el, 1);
344 va_start(ap, format);
345 ret = vfprintf(out, format, ap);
351 int nc_cli_eprintf(struct nc_cli_editline *el, const char *format, ...)
353 FILE *out = nc_cli_editline_get_file(el, 2);
360 va_start(ap, format);
361 ret = vfprintf(out, format, ap);
368 nc_cli_editline_is_interactive(struct nc_cli_editline *el)
370 return el->interactive;
374 nc_cli_editline_is_running(struct nc_cli_editline *el)
376 return el == nc_cli_el;
380 nc_cli_editline_start(struct nc_cli_editline *el)
386 nc_cli_editline_stop(struct nc_cli_editline *el)
393 nc_cli_editline_getc(struct nc_cli_editline *el)
397 if (el->interactive == false)
400 if (el_getc(el->editline, &c) != 1)
407 nc_cli_editline_resize(struct nc_cli_editline *el)
409 el_resize(el->editline);
413 int nc_cli_editline_set_prompt_cb(struct nc_cli_editline *el,
414 char *(*prompt_cb)(EditLine *el))
416 el->prompt_cb = prompt_cb;
421 nc_cli_editline_mask_interrupts(bool do_mask)
423 const char *setty = do_mask ? "-isig" : "+isig";
425 if (nc_cli_el == NULL)
428 if (nc_cli_el->interactive == false)
431 if (el_set(nc_cli_el->editline, EL_SETTY, "-d", setty, NULL))
437 struct nc_cli_editline *
438 nc_cli_editline_init(FILE *f_in, FILE *f_out, FILE *f_err, bool interactive,
439 char *(*prompt_cb)(EditLine *el))
441 struct nc_cli_editline *el = NULL;
443 el = calloc(1, sizeof(*el));
450 if (f_out == NULL || f_err == NULL) {
451 el->null_out = fopen("/dev/null", "w");
452 if (el->null_out == NULL)
456 f_out = el->null_out;
458 f_err = el->null_out;
460 el->interactive = interactive;
461 el->prompt_cb = prompt_cb;
463 el->editline = el_init("nc-cli", f_in, f_out, f_err);
464 if (el->editline == NULL)
467 if (el_set(el->editline, EL_SIGNAL, 1))
470 if (el_set(el->editline, EL_CLIENTDATA, el))
473 if (el_set(el->editline, EL_PROMPT, prompt_cb))
476 if (interactive == false)
479 if (el_set(el->editline, EL_PREP_TERM, 0))
482 if (el_set(el->editline, EL_EDITOR, "emacs"))
485 if (el_set(el->editline, EL_SETTY, "-d", "-isig", NULL))
487 if (el_set(el->editline, EL_ADDFN, "ed-break",
488 "Break and flush the buffer",
491 if (el_set(el->editline, EL_BIND, "^C", "ed-break", NULL))
493 if (el_set(el->editline, EL_ADDFN, "ed-suspend",
494 "Suspend the terminal",
497 if (el_set(el->editline, EL_BIND, "^Z", "ed-suspend", NULL))
499 if (el_set(el->editline, EL_BIND, "^W", "ed-delete-prev-word", NULL))
502 el->history = history_init();
505 if (history(el->history, &el->histev, H_SETSIZE,
506 NC_CLI_HISTORY_SIZE) < 0)
508 if (history(el->history, &el->histev,
511 if (el_set(el->editline, EL_HIST, history,
520 if (el->null_out != NULL)
521 fclose(el->null_out);
522 if (el->history != NULL)
523 history_end(el->history);
524 if (el->editline != NULL)
525 el_end(el->editline);
532 nc_cli_editline_free(struct nc_cli_editline *el)
536 nc_cli_editline_stop(el);
537 if (el->null_out != NULL)
538 fclose(el->null_out);
539 if (el->history != NULL)
540 history_end(el->history);
541 el_end(el->editline);