X-Git-Url: http://git.droids-corp.org/?a=blobdiff_plain;f=lib%2Fmain-readline.c;h=2c81c53c0f69e76d413910bdc1ede305077844cd;hb=1d655de6043b607f39888c1bb88f72d071f2d49a;hp=3bdc70322bd05d39e7706f3471cfeeb6f52190b1;hpb=ae2c4709444ee6fbbf388b853ab4039fcdde2e95;p=protos%2Flibecoli.git diff --git a/lib/main-readline.c b/lib/main-readline.c index 3bdc703..2c81c53 100644 --- a/lib/main-readline.c +++ b/lib/main-readline.c @@ -1,118 +1,346 @@ -/* - * Copyright (c) 2016, Olivier MATZ - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of the University of California, Berkeley nor the - * names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ */ +#define _GNU_SOURCE /* for asprintf */ #include #include +#include #include #include #include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include -static struct ec_tk *commands; +static struct ec_node *commands; -/* Set to a character describing the type of completion being attempted by - rl_complete_internal; available for use by application completion - functions. */ -extern int rl_completion_type; -/* Set to the last key used to invoke one of the completion functions */ -extern int rl_completion_invoking_key; - -int my_complete(int x, int y) +static char *my_completion_entry(const char *s, int state) { - (void)x; - (void)y; - - return 0; -} - -char *my_completion_entry(const char *s, int state) -{ - static struct ec_completed_tk *c; - static const struct ec_completed_tk_elt *elt; + static struct ec_comp *c; + static struct ec_comp_iter *iter; + const struct ec_comp_item *item; + enum ec_comp_type item_type; + const char *item_str, *item_display; (void)s; + /* Don't append a quote. Note: there are still some bugs when + * completing a quoted token. */ + rl_completion_suppress_quote = 1; + rl_completer_quote_characters = "\"'"; + if (state == 0) { - char *start; + char *line; - if (c != NULL) - ec_completed_tk_free(c); + ec_comp_free(c); + line = strdup(rl_line_buffer); + if (line == NULL) + return NULL; + line[rl_point] = '\0'; - start = strdup(rl_line_buffer); - if (start == NULL) + c = ec_node_complete(commands, line); + free(line); + if (c == NULL) return NULL; - start[rl_point] = '\0'; - c = ec_tk_complete(commands, start); - ec_completed_tk_iter_start(c); + ec_comp_iter_free(iter); + iter = ec_comp_iter(c, EC_COMP_FULL | EC_COMP_PARTIAL); + if (iter == NULL) + return NULL; } - elt = ec_completed_tk_iter_next(c); - if (elt == NULL) + item = ec_comp_iter_next(iter); + if (item == NULL) return NULL; - return strdup(elt->full); + item_str = ec_comp_item_get_str(item); + if (c->count_full == 1) { + + /* don't add the trailing space for partial completions */ + if (state == 0) { + item_type = ec_comp_item_get_type(item); + if (item_type == EC_COMP_FULL) + rl_completion_suppress_append = 0; + else + rl_completion_suppress_append = 1; + } + + return strdup(item_str); + } else if (rl_completion_type == '?') { + /* on second try only show the display part */ + item_display = ec_comp_item_get_display(item); + return strdup(item_display); + } + + return strdup(item_str); } -char **my_attempted_completion(const char *text, int start, int end) +static char **my_attempted_completion(const char *text, int start, int end) { (void)start; (void)end; - // XXX when it returns NULL, it completes with a file + + /* remove default file completion */ + rl_attempted_completion_over = 1; + return rl_completion_matches(text, my_completion_entry); } +/* this function builds the help string */ +static char *get_node_help(const struct ec_comp_item *item) +{ + const struct ec_comp_group *grp; + const struct ec_parse *state; + const struct ec_node *node; + char *help = NULL; + const char *node_help = NULL; + const char *node_desc = NULL; + + grp = ec_comp_item_get_grp(item); + state = grp->state; + for (state = grp->state; state != NULL; + state = ec_parse_get_parent(state)) { + node = ec_parse_get_node(state); + if (node_help == NULL) + node_help = ec_keyval_get(ec_node_attrs(node), "help"); + if (node_desc == NULL) + node_desc = ec_node_desc(node); + } + + if (node_help == NULL) + node_help = "-"; + if (node_desc == NULL) + return NULL; + + if (asprintf(&help, "%-20s %s", node_desc, node_help) < 0) + return NULL; + + return help; +} + +static int show_help(int ignore, int invoking_key) +{ + struct ec_comp_iter *iter = NULL; + const struct ec_comp_group *grp, *prev_grp = NULL; + const struct ec_comp_item *item; + struct ec_comp *c = NULL; + struct ec_parse *p = NULL; + char *line = NULL; + unsigned int count; + char **helps = NULL; + int match = 0; + int cols; + + (void)ignore; + (void)invoking_key; + + line = strdup(rl_line_buffer); + if (line == NULL) + goto fail; + + /* check if the current line matches */ + p = ec_node_parse(commands, line); + if (ec_parse_matches(p)) + match = 1; + ec_parse_free(p); + p = NULL; + + /* complete at current cursor position */ + line[rl_point] = '\0'; + c = ec_node_complete(commands, line); + free(line); + line = NULL; + if (c == NULL) + goto fail; + + /* let's display one contextual help per node */ + count = 0; + iter = ec_comp_iter(c, + EC_COMP_UNKNOWN | EC_COMP_FULL | EC_COMP_PARTIAL); + if (iter == NULL) + goto fail; + + /* strangely, rl_display_match_list() expects first index at 1 */ + helps = calloc(match + 1, sizeof(char *)); + if (helps == NULL) + goto fail; + if (match) + helps[1] = ""; + + while ((item = ec_comp_iter_next(iter)) != NULL) { + char **tmp; + + /* keep one help per group, skip other items */ + grp = ec_comp_item_get_grp(item); + if (grp == prev_grp) + continue; + + prev_grp = grp; + + tmp = realloc(helps, (count + match + 2) * sizeof(char *)); + if (tmp == NULL) + goto fail; + helps = tmp; + helps[count + match + 1] = get_node_help(item); + count++; + } + + ec_comp_iter_free(iter); + ec_comp_free(c); + /* ensure not more than 1 entry per line */ + rl_get_screen_size(NULL, &cols); + rl_display_match_list(helps, count + match, cols); + rl_forced_update_display(); + + return 0; + +fail: + ec_comp_iter_free(iter); + ec_parse_free(p); + free(line); + ec_comp_free(c); + if (helps != NULL) { + while (count--) + free(helps[count + match + 1]); + } + free(helps); + + return 1; +} + +static int create_commands(void) +{ + struct ec_node *cmdlist = NULL, *cmd = NULL; + + cmdlist = ec_node("or", EC_NO_ID); + if (cmdlist == NULL) + goto fail; + + + cmd = EC_NODE_SEQ(EC_NO_ID, + ec_node_str(EC_NO_ID, "hello"), + EC_NODE_OR("name", + ec_node_str("john", "john"), + ec_node_str(EC_NO_ID, "johnny"), + ec_node_str(EC_NO_ID, "mike") + ), + ec_node_option(EC_NO_ID, ec_node_int("int", 0, 10, 10)) + ); + if (cmd == NULL) + goto fail; + ec_keyval_set(ec_node_attrs(cmd), "help", + "say hello to someone several times", NULL); + ec_keyval_set(ec_node_attrs(ec_node_find(cmd, "john")), + "help", "specific help for john", NULL); + ec_keyval_set(ec_node_attrs(ec_node_find(cmd, "name")), + "help", "the name of the person", NULL); + ec_keyval_set(ec_node_attrs(ec_node_find(cmd, "int")), + "help", "an integer (0-10)", NULL); + if (ec_node_or_add(cmdlist, cmd) < 0) + goto fail; + + + cmd = EC_NODE_CMD(EC_NO_ID, "good morning name [count]", + EC_NODE_CMD("name", "bob|bobby|michael"), + ec_node_int("count", 0, 10, 10)); + if (cmd == NULL) + goto fail; + ec_keyval_set(ec_node_attrs(cmd), "help", + "say good morning to someone several times", NULL); + ec_keyval_set(ec_node_attrs(ec_node_find(cmd, "name")), "help", + "the person to greet", NULL); + ec_keyval_set(ec_node_attrs(ec_node_find(cmd, "count")), "help", + "how many times to greet (0-10)", NULL); + if (ec_node_or_add(cmdlist, cmd) < 0) + goto fail; + + + cmd = EC_NODE_CMD(EC_NO_ID, + "buy potatoes,carrots,pumpkins"); + if (cmd == NULL) + goto fail; + ec_keyval_set(ec_node_attrs(cmd), "help", + "buy some vegetables", NULL); + if (ec_node_or_add(cmdlist, cmd) < 0) + goto fail; + + + cmd = EC_NODE_CMD(EC_NO_ID, "eat vegetables", + ec_node_many("vegetables", + EC_NODE_OR(EC_NO_ID, + ec_node_str(EC_NO_ID, "potatoes"), + ec_node_once(EC_NO_ID, + ec_node_str(EC_NO_ID, "carrots")), + ec_node_once(EC_NO_ID, + ec_node_str(EC_NO_ID, "pumpkins"))), + 1, 0)); + if (cmd == NULL) + goto fail; + ec_keyval_set(ec_node_attrs(cmd), "help", + "eat vegetables (take some more potatoes)", NULL); + if (ec_node_or_add(cmdlist, cmd) < 0) + goto fail; + + + cmd = EC_NODE_SEQ(EC_NO_ID, + ec_node_str(EC_NO_ID, "bye") + ); + ec_keyval_set(ec_node_attrs(cmd), "help", "say bye", NULL); + if (ec_node_or_add(cmdlist, cmd) < 0) + goto fail; + + + cmd = EC_NODE_SEQ(EC_NO_ID, + ec_node_str(EC_NO_ID, "load"), + ec_node("file", EC_NO_ID) + ); + ec_keyval_set(ec_node_attrs(cmd), "help", "load a file", NULL); + if (ec_node_or_add(cmdlist, cmd) < 0) + goto fail; + + + commands = ec_node_sh_lex(EC_NO_ID, cmdlist); + if (commands == NULL) + goto fail; + + return 0; + + fail: + fprintf(stderr, "cannot initialize nodes\n"); + ec_node_free(cmdlist); + return -1; +} + int main(void) { - struct ec_parsed_tk *p; -// const char *name; + struct ec_parse *p; char *line; - commands = ec_tk_seq_new_list(NULL, - ec_tk_str_new(NULL, "hello"), - ec_tk_space_new(NULL), - ec_tk_or_new_list("name", - ec_tk_str_new(NULL, "john"), - ec_tk_str_new(NULL, "mike"), - EC_TK_ENDLIST), - EC_TK_ENDLIST); - if (commands == NULL) { - printf("cannot create token\n"); + if (ec_init() < 0) { + fprintf(stderr, "cannot init ecoli: %s\n", strerror(errno)); return 1; } - //rl_bind_key('\t', my_complete); + if (create_commands() < 0) + return 1; - //rl_completion_entry_function = my_completion_entry; + rl_bind_key('?', show_help); rl_attempted_completion_function = my_attempted_completion; while (1) { @@ -120,14 +348,14 @@ int main(void) if (line == NULL) break; - // XXX need a "parse_all" - p = ec_tk_parse(commands, line); - ec_parsed_tk_dump(stdout, p); + p = ec_node_parse(commands, line); + ec_parse_dump(stdout, p); add_history(line); - ec_parsed_tk_free(p); + ec_parse_free(p); } - ec_tk_free(commands); + ec_node_free(commands); return 0; + }