X-Git-Url: http://git.droids-corp.org/?a=blobdiff_plain;f=src%2Feditline.c;fp=src%2Feditline.c;h=449aebb5b56923b8be96c3dbecd77a3a5d4c4716;hb=18d03456d96f7a086a2ccc82ce97fcf056848d90;hp=0000000000000000000000000000000000000000;hpb=a1571d413d2acac5d4a4fbdf2e50b2d1a6da3aa6;p=protos%2Flibecoli.git diff --git a/src/editline.c b/src/editline.c new file mode 100644 index 0000000..449aebb --- /dev/null +++ b/src/editline.c @@ -0,0 +1,544 @@ +/* + * Copyright 2018 6WIND S.A. + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "string_utils.h" +#include "editline.h" + +#define NC_CLI_HISTORY_SIZE 128 + +struct nc_cli_editline { + EditLine *editline; + History *history; + HistEvent histev; + bool break_received; + nc_cli_editline_complete_t complete; + FILE *null_out; + bool interactive; + bool incomplete_line; + char *full_line; + char *(*prompt_cb)(EditLine *); +}; + +struct nc_cli_editline *nc_cli_el; + +static int check_quotes(const char *str) +{ + char quote = 0; + size_t i = 0; + + while (str[i] != '\0') { + if (quote == 0) { + if (str[i] == '"' || str[i] == '\'') { + quote = str[i]; + } + i++; + continue; + } else { + if (str[i] == quote) { + i++; + quote = 0; + } else if (str[i] == '\\' && str[i+1] == quote) { + i += 2; + } else { + i++; + } + continue; + } + } + + return quote; +} + +static int +editline_break(EditLine *editline, int c) +{ + struct nc_cli_editline *el; + void *ptr; + + (void)c; + + if (el_get(editline, EL_CLIENTDATA, &ptr)) + return CC_ERROR; + + el = ptr; + el->break_received = true; + nc_cli_printf(el, "\n"); + + return CC_EOF; +} + +static int +editline_suspend(EditLine *editline, int c) +{ + (void)editline; + (void)c; + + kill(getpid(), SIGSTOP); + + return CC_NORM; +} + +int +editline_get_screen_size(struct nc_cli_editline *el, size_t *rows, size_t *cols) +{ + int w, h; + + if (rows != NULL) { + if (el_get(el->editline, EL_GETTC, "li", &h, (void *)0)) + return -1; + *rows = h; + } + if (cols != NULL) { + if (el_get(el->editline, EL_GETTC, "co", &w, (void *)0)) + return -1; + *cols = w; + } + return 0; +} + +FILE * +nc_cli_editline_get_file(struct nc_cli_editline *el, int num) +{ + FILE *f; + + if (el == NULL) + return NULL; + if (num > 2) + return NULL; + if (el_get(el->editline, EL_GETFP, num, &f)) + return NULL; + + return f; +} + +/* match the prototype expected by qsort() */ +static int +strcasecmp_cb(const void *p1, const void *p2) +{ + return strcasecmp(*(char * const *)p1, *(char * const *)p2); +} + +/* Show the matches as a multi-columns list */ +int +nc_cli_editline_print_cols(struct nc_cli_editline *el, + char const * const *matches, size_t n) +{ + size_t max_strlen = 0, len, i, j, ncols; + size_t width, height; + const char *space; + char **matches_copy = NULL; + + nc_cli_printf(nc_cli_el, "\n"); + if (n == 0) + return 0; + + if (editline_get_screen_size(el, &height, &width) < 0) + width = 80; + + /* duplicate the matches table, and sort it */ + matches_copy = calloc(n, sizeof(const char *)); + if (matches_copy == NULL) + return -1; + memcpy(matches_copy, matches, sizeof(const char *) * n); + qsort(matches_copy, n, sizeof(char *), strcasecmp_cb); + + /* get max string length */ + for (i = 0; i < n; i++) { + len = strlen(matches_copy[i]); + if (len > max_strlen) + max_strlen = len; + } + + /* write the columns */ + ncols = width / (max_strlen + 4); + if (ncols == 0) + ncols = 1; + for (i = 0; i < n; i+= ncols) { + for (j = 0; j < ncols; j++) { + if (i + j >= n) + break; + if (j == 0) + space = ""; + else + space = " "; + nc_cli_printf(nc_cli_el, "%s%-*s", space, + (int)max_strlen, matches[i+j]); + } + nc_cli_printf(nc_cli_el, "\n"); + } + + free(matches_copy); + return 0; +} + +static int +editline_complete(EditLine *editline, int c) +{ + enum nc_cli_editline_complete_status ret; + const LineInfo *line_info; + struct nc_cli_editline *el; + int len; + char *line; + void *ptr; + + if (el_get(editline, EL_CLIENTDATA, &ptr)) + return CC_ERROR; + + el = ptr; + + if (el->complete == NULL) + return CC_NORM; + + line_info = el_line(editline); + if (line_info == NULL) + return CC_ERROR; + + len = line_info->cursor - line_info->buffer; + if (asprintf(&line, "%s%.*s", el->full_line ? : "", len, + line_info->buffer) < 0) + return CC_ERROR; + + if (c == '?' && check_quotes(line) != 0) { + free(line); + el_insertstr(editline, "?"); + return CC_REFRESH; + } + + ret = el->complete(c, line); + free(line); + + if (ret == ERROR) + return CC_ERROR; + else if (ret == REDISPLAY) + return CC_REDISPLAY; + else + return CC_REFRESH; +} + +static bool is_blank_string(const char *s) +{ + while (*s) { + if (!isspace(*s)) + return false; + s++; + } + return true; +} + +static char * +multiline_prompt_cb(EditLine *e) +{ + (void)e; + return strdup("... "); +} + +const char * +nc_cli_editline_edit(struct nc_cli_editline *el) +{ + const char *line; + int count; + + assert(el->editline != NULL); + + if (el->incomplete_line == false) { + free(el->full_line); + el->full_line = NULL; + } + + el->break_received = false; + + if (el->incomplete_line) + el_set(el->editline, EL_PROMPT, multiline_prompt_cb); + else + el_set(el->editline, EL_PROMPT, el->prompt_cb); + + line = el_gets(el->editline, &count); + + if (line == NULL && el->break_received) { + free(el->full_line); + el->full_line = NULL; + el->incomplete_line = false; + return ""; /* abort current line */ + } + + if (line == NULL || astrcat(&el->full_line, line) < 0) { + free(el->full_line); + el->full_line = NULL; + el->incomplete_line = false; + return NULL; /* error / eof */ + } + + if (check_quotes(el->full_line) != 0) { + el->incomplete_line = true; + return ""; + } + + el->incomplete_line = false; + if (el->history != NULL && !is_blank_string(el->full_line)) + history(el->history, &el->histev, + H_ENTER, el->full_line); + + return el->full_line; +} + +int +nc_cli_editline_register_complete(struct nc_cli_editline *el, + nc_cli_editline_complete_t complete) +{ + const char *name; + + if (el_set(el->editline, EL_ADDFN, "ed-complete", + "Complete buffer", + editline_complete)) + return -1; + + if (complete != NULL) + name = "ed-complete"; + else + name = "ed-unassigned"; + if (el_set(el->editline, EL_BIND, "^I", name, NULL)) + return -1; + + if (complete != NULL) + name = "ed-complete"; + else + name = "ed-insert"; + if (el_set(el->editline, EL_BIND, "?", name, NULL)) + return -1; + + el->complete = complete; + + return 0; +} + +int +nc_cli_editline_insert_str(struct nc_cli_editline *el, const char *str) +{ + return el_insertstr(el->editline, str); +} + +int nc_cli_printf(struct nc_cli_editline *el, const char *format, ...) +{ + FILE *out = nc_cli_editline_get_file(el, 1); + va_list ap; + int ret; + + if (out == NULL) + out = stdout; + + va_start(ap, format); + ret = vfprintf(out, format, ap); + va_end(ap); + + return ret; +} + +int nc_cli_eprintf(struct nc_cli_editline *el, const char *format, ...) +{ + FILE *out = nc_cli_editline_get_file(el, 2); + va_list ap; + int ret; + + if (out == NULL) + out = stderr; + + va_start(ap, format); + ret = vfprintf(out, format, ap); + va_end(ap); + + return ret; +} + +bool +nc_cli_editline_is_interactive(struct nc_cli_editline *el) +{ + return el->interactive; +} + +bool +nc_cli_editline_is_running(struct nc_cli_editline *el) +{ + return el == nc_cli_el; +} + +void +nc_cli_editline_start(struct nc_cli_editline *el) +{ + nc_cli_el = el; +} + +void +nc_cli_editline_stop(struct nc_cli_editline *el) +{ + if (el == nc_cli_el) + nc_cli_el = NULL; +} + +int +nc_cli_editline_getc(struct nc_cli_editline *el) +{ + char c; + + if (el->interactive == false) + return -1; + + if (el_getc(el->editline, &c) != 1) + return -1; + + return c; +} + +int +nc_cli_editline_resize(struct nc_cli_editline *el) +{ + el_resize(el->editline); + return 0; +} + +int nc_cli_editline_set_prompt_cb(struct nc_cli_editline *el, + char *(*prompt_cb)(EditLine *el)) +{ + el->prompt_cb = prompt_cb; + return 0; +} + +int +nc_cli_editline_mask_interrupts(bool do_mask) +{ + const char *setty = do_mask ? "-isig" : "+isig"; + + if (nc_cli_el == NULL) + return -1; + + if (nc_cli_el->interactive == false) + return 0; + + if (el_set(nc_cli_el->editline, EL_SETTY, "-d", setty, NULL)) + return -1; + + return 0; +} + +struct nc_cli_editline * +nc_cli_editline_init(FILE *f_in, FILE *f_out, FILE *f_err, bool interactive, + char *(*prompt_cb)(EditLine *el)) +{ + struct nc_cli_editline *el = NULL; + + el = calloc(1, sizeof(*el)); + if (el == NULL) + goto fail; + if (f_in == NULL) { + errno = EINVAL; + goto fail; + } + if (f_out == NULL || f_err == NULL) { + el->null_out = fopen("/dev/null", "w"); + if (el->null_out == NULL) + goto fail; + } + if (f_out == NULL) + f_out = el->null_out; + if (f_err == NULL) + f_err = el->null_out; + + el->interactive = interactive; + el->prompt_cb = prompt_cb; + + el->editline = el_init("nc-cli", f_in, f_out, f_err); + if (el->editline == NULL) + goto fail; + + if (el_set(el->editline, EL_SIGNAL, 1)) + goto fail; + + if (el_set(el->editline, EL_CLIENTDATA, el)) + goto fail; + + if (el_set(el->editline, EL_PROMPT, prompt_cb)) + goto fail; + + if (interactive == false) + goto end; + + if (el_set(el->editline, EL_PREP_TERM, 0)) + goto fail; + + if (el_set(el->editline, EL_EDITOR, "emacs")) + goto fail; + + if (el_set(el->editline, EL_SETTY, "-d", "-isig", NULL)) + goto fail; + if (el_set(el->editline, EL_ADDFN, "ed-break", + "Break and flush the buffer", + editline_break)) + goto fail; + if (el_set(el->editline, EL_BIND, "^C", "ed-break", NULL)) + goto fail; + if (el_set(el->editline, EL_ADDFN, "ed-suspend", + "Suspend the terminal", + editline_suspend)) + goto fail; + if (el_set(el->editline, EL_BIND, "^Z", "ed-suspend", NULL)) + goto fail; + if (el_set(el->editline, EL_BIND, "^W", "ed-delete-prev-word", NULL)) + goto fail; + + el->history = history_init(); + if (!el->history) + goto fail; + if (history(el->history, &el->histev, H_SETSIZE, + NC_CLI_HISTORY_SIZE) < 0) + goto fail; + if (history(el->history, &el->histev, + H_SETUNIQUE, 1)) + goto fail; + if (el_set(el->editline, EL_HIST, history, + el->history)) + goto fail; + +end: + return el; + +fail: + if (el != NULL) { + if (el->null_out != NULL) + fclose(el->null_out); + if (el->history != NULL) + history_end(el->history); + if (el->editline != NULL) + el_end(el->editline); + free(el); + } + return NULL; +} + +void +nc_cli_editline_free(struct nc_cli_editline *el) +{ + if (el == NULL) + return; + nc_cli_editline_stop(el); + if (el->null_out != NULL) + fclose(el->null_out); + if (el->history != NULL) + history_end(el->history); + el_end(el->editline); + free(el->full_line); + free(el); +}