From ddf98661e4f16f75bb3deea26ec0c1e3bc651263 Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Sat, 12 Oct 2013 18:17:03 -0700 Subject: [PATCH] Adopt new parser in tab completions --- complete.cpp | 320 +++++++++++++++++++------------------------------ highlight.cpp | 9 +- parse_tree.cpp | 28 +++++ parse_tree.h | 9 ++ reader.cpp | 3 + 5 files changed, 169 insertions(+), 200 deletions(-) diff --git a/complete.cpp b/complete.cpp index 8df02b35a..e25042059 100644 --- a/complete.cpp +++ b/complete.cpp @@ -44,6 +44,7 @@ #include "parser_keywords.h" #include "wutil.h" #include "path.h" +#include "parse_tree.h" /* Completion description strings, mostly for different types of files, such as sockets, block devices, etc. @@ -1363,7 +1364,9 @@ struct local_options_t bool completer_t::complete_param(const wcstring &scmd_orig, const wcstring &spopt, const wcstring &sstr, bool use_switches) { - const wchar_t * const cmd_orig = scmd_orig.c_str(), * const popt = spopt.c_str(), * const str = sstr.c_str(); + const wchar_t * const cmd_orig = scmd_orig.c_str(); + const wchar_t * const popt = spopt.c_str(); + const wchar_t * const str = sstr.c_str(); bool use_common=1, use_files=1; @@ -1790,231 +1793,160 @@ bool completer_t::try_complete_user(const wcstring &str) return res; } -void complete(const wcstring &cmd, std::vector &comps, completion_request_flags_t flags, wcstring_list_t *commands_to_load) +void complete(const wcstring &cmd_with_subcmds, std::vector &comps, completion_request_flags_t flags, wcstring_list_t *commands_to_load) { + /* Determine the innermost subcommand */ + const wchar_t *cmdsubst_begin, *cmdsubst_end; + parse_util_cmdsubst_extent(cmd_with_subcmds.c_str(), cmd_with_subcmds.size(), &cmdsubst_begin, &cmdsubst_end); + assert(cmdsubst_begin != NULL && cmdsubst_end != NULL && cmdsubst_end >= cmdsubst_begin); + const wcstring cmd = wcstring(cmdsubst_begin, cmdsubst_end - cmdsubst_begin); + /* Make our completer */ completer_t completer(cmd, flags); - const wchar_t *tok_begin, *tok_end, *cmdsubst_begin, *cmdsubst_end, *prev_begin, *prev_end; - wcstring current_token, prev_token; wcstring current_command; - int on_command=0; - size_t pos; + const size_t pos = cmd.size(); bool done=false; - int use_command = 1; - int use_function = 1; - int use_builtin = 1; - int had_ddash = 0; + bool use_command = 1; + bool use_function = 1; + bool use_builtin = 1; // debug( 1, L"Complete '%ls'", cmd ); - size_t cursor_pos = cmd.size(); - const wchar_t *cmd_cstr = cmd.c_str(); - parse_util_cmdsubst_extent(cmd_cstr, cursor_pos, &cmdsubst_begin, &cmdsubst_end); - parse_util_token_extent(cmd_cstr, cursor_pos, &tok_begin, &tok_end, &prev_begin, &prev_end); - - if (!cmdsubst_begin) - done=1; - + const wchar_t *tok_begin = NULL, *prev_begin = NULL, *prev_end = NULL; + parse_util_token_extent(cmd_cstr, cmd.size(), &tok_begin, NULL, &prev_begin, &prev_end); /** If we are completing a variable name or a tilde expansion user name, we do that and return. No need for any other completions. */ + + const wcstring current_token = tok_begin; if (!done) { - wcstring tmp = tok_begin; - done = completer.try_complete_variable(tmp) || completer.try_complete_user(tmp); + done = completer.try_complete_variable(current_token) || completer.try_complete_user(current_token); } if (!done) { - pos = cursor_pos-(cmdsubst_begin-cmd_cstr); - - const wcstring buff = wcstring(cmdsubst_begin, cmdsubst_end-cmdsubst_begin); - - int had_cmd=0; - int end_loop=0; - - tokenizer_t tok(buff.c_str(), TOK_ACCEPT_UNFINISHED | TOK_SQUASH_ERRORS); - while (tok_has_next(&tok) && !end_loop) + //const size_t prev_token_len = (prev_begin ? prev_end - prev_begin : 0); + //const wcstring prev_token(prev_begin, prev_token_len); + + parse_node_tree_t tree; + parse_t::parse(cmd, parse_flag_continue_after_error | parse_flag_accept_incomplete_tokens, &tree, NULL); + + /* Find the plain statement that contains the position */ + const parse_node_t *plain_statement = tree.find_node_matching_source_location(symbol_plain_statement, pos, NULL); + if (plain_statement != NULL) { - switch (tok_last_type(&tok)) + assert(plain_statement->has_source() && plain_statement->type == symbol_plain_statement); + + /* Get the command node */ + const parse_node_t *cmd_node = tree.get_child(*plain_statement, 0, parse_token_type_string); + + /* Get the actual command string */ + if (cmd_node != NULL) + current_command = cmd_node->get_source(cmd); + + /* Check the decoration */ + switch (tree.decoration_for_plain_statement(*plain_statement)) { - - case TOK_STRING: + case parse_statement_decoration_none: + use_command = true; + use_function = false; + use_builtin = false; + break; + + case parse_statement_decoration_command: + use_command = true; + use_function = false; + use_builtin = false; + break; + + case parse_statement_decoration_builtin: + use_command = false; + use_function = false; + use_builtin = true; + break; + } + + if (cmd_node && cmd_node->location_in_or_at_end_of_source_range(pos)) + { + /* Complete command filename */ + completer.complete_cmd(current_token, use_function, use_builtin, use_command); + } + else + { + /* Get all the arguments */ + const parse_node_tree_t::parse_node_list_t all_arguments = tree.find_nodes(*plain_statement, symbol_argument); + + /* See whether we are in an argument. We may also be in a redirection, or nothing at all. */ + size_t matching_arg_index = -1; + for (size_t i=0; i < all_arguments.size(); i++) { - - const wcstring ncmd = tok_last(&tok); - int is_ddash = (ncmd == L"--") && ((tok_get_pos(&tok)+2) < (long)pos); - - if (!had_cmd) + const parse_node_t *node = all_arguments.at(i); + if (node->location_in_or_at_end_of_source_range(pos)) { - - if (parser_keywords_is_subcommand(ncmd)) - { - if (ncmd == L"builtin") - { - use_function = 0; - use_command = 0; - use_builtin = 1; - } - else if (ncmd == L"command") - { - use_command = 1; - use_function = 0; - use_builtin = 0; - } - break; - } - - - if (!is_ddash || - ((use_command && use_function && use_builtin))) - { - current_command = ncmd; - - size_t token_end = tok_get_pos(&tok) + ncmd.size(); - - on_command = (pos <= token_end); - had_cmd=1; - } - + matching_arg_index = i; + break; } - else - { - if (is_ddash) - { - had_ddash = 1; - } - } - - break; - } - - case TOK_END: - case TOK_PIPE: - case TOK_BACKGROUND: - { - had_cmd=0; - had_ddash = 0; - use_command = 1; - use_function = 1; - use_builtin = 1; - break; - } - - case TOK_ERROR: - { - end_loop=1; - break; } - default: + bool had_ddash = false; + wcstring current_argument, previous_argument; + if (matching_arg_index != (size_t)(-1)) { - break; + /* Get the current argument and the previous argument, if we have one */ + current_argument = all_arguments.at(matching_arg_index)->get_source(cmd); + + if (matching_arg_index > 0) + previous_argument = all_arguments.at(matching_arg_index - 1)->get_source(cmd); + + /* Check to see if we have a preceding double-dash */ + for (size_t i=0; i < matching_arg_index; i++) + { + if (all_arguments.at(i)->get_source(cmd) == L"--") + { + had_ddash = true; + break; + } + } } + + bool do_file = false; + + wcstring current_command_unescape = current_command; + wcstring previous_argument_unescape = previous_argument; + wcstring current_argument_unescape = current_argument; + + if (unescape_string(current_command_unescape, 0) && + unescape_string(previous_argument_unescape, 0) && + unescape_string(current_argument_unescape, UNESCAPE_INCOMPLETE)) + { + do_file = completer.complete_param(current_command_unescape, + previous_argument_unescape, + current_argument_unescape, + !had_ddash); + } + + /* If we have found no command specific completions at all, fall back to using file completions. */ + if (completer.empty()) + do_file = true; + + /* But if we are planning on loading commands, don't do file completions. + See https://github.com/fish-shell/fish-shell/issues/378 */ + if (commands_to_load != NULL && completer.has_commands_to_load()) + do_file = false; + + /* And if we're autosuggesting, and the token is empty, don't do file suggestions */ + if ((flags & COMPLETION_REQUEST_AUTOSUGGESTION) && current_argument_unescape.empty()) + do_file = false; + + /* This function wants the unescaped string */ + completer.complete_param_expand(current_token, do_file); } - - if (tok_get_pos(&tok) >= (long)pos) - { - end_loop=1; - } - - tok_next(&tok); - - } - - /* - Get the string to complete - */ - - current_token.assign(tok_begin, cursor_pos-(tok_begin-cmd_cstr)); - - if (prev_begin) - { - prev_token.assign(prev_begin, prev_end - prev_begin); - } - else - { - prev_token.clear(); - } - -// debug( 0, L"on_command: %d, %ls %ls\n", on_command, current_command, current_token ); - - /* - Check if we are using the 'command' or 'builtin' builtins - _and_ we are writing a switch instead of a command. In that - case, complete using the builtins completions, not using a - subcommand. - */ - - if ((on_command || current_token == L"--") && - string_prefixes_string(L"-", current_token) && - !(use_command && use_function && use_builtin)) - { - if (use_command == 0) - current_command = L"builtin"; - else - current_command = L"command"; - - had_cmd = 1; - on_command = 0; - } - - /* - Use command completions if in between commands - */ - if (!had_cmd) - { - on_command=1; - } - - - if (on_command) - { - /* Complete command filename */ - completer.complete_cmd(current_token, use_function, use_builtin, use_command); - } - else - { - bool do_file = false; - - wcstring current_command_unescape = current_command; - wcstring prev_token_unescape = prev_token; - wcstring current_token_unescape = current_token; - - if (unescape_string(current_command_unescape, 0) && - unescape_string(prev_token_unescape, 0) && - unescape_string(current_token_unescape, UNESCAPE_INCOMPLETE)) - { - do_file = completer.complete_param(current_command_unescape, - prev_token_unescape, - current_token_unescape, - !had_ddash); - } - - /* If we have found no command specific completions at - all, fall back to using file completions. - */ - if (completer.empty()) - do_file = true; - - /* But if we are planning on loading commands, don't do file completions. - See https://github.com/fish-shell/fish-shell/issues/378 */ - if (commands_to_load != NULL && completer.has_commands_to_load()) - do_file = false; - - /* And if we're autosuggesting, and the token is empty, don't do file suggestions */ - if ((flags & COMPLETION_REQUEST_AUTOSUGGESTION) && current_token_unescape.empty()) - do_file = false; - - /* - This function wants the unescaped string - */ - completer.complete_param_expand(current_token, do_file); } } diff --git a/highlight.cpp b/highlight.cpp index ffd5953c6..c4ad7d92e 100644 --- a/highlight.cpp +++ b/highlight.cpp @@ -713,8 +713,7 @@ static bool autosuggest_parse_command(const wcstring &buff, wcstring *out_expand /* Parse the buffer */ parse_node_tree_t parse_tree; - parse_t parser; - parser.parse(buff, parse_flag_continue_after_error | parse_flag_accept_incomplete_tokens, &parse_tree, NULL); + parse_t::parse(buff, parse_flag_continue_after_error | parse_flag_accept_incomplete_tokens, &parse_tree, NULL); /* Find the last statement */ const parse_node_t *last_statement = parse_tree.find_last_node_of_type(symbol_plain_statement, NULL); @@ -1709,8 +1708,7 @@ class highlighter_t { /* Parse the tree */ this->parse_tree.clear(); - parse_t parser; - parser.parse(buff, parse_flag_continue_after_error | parse_flag_include_comments, &this->parse_tree, NULL); + parse_t::parse(buff, parse_flag_continue_after_error | parse_flag_include_comments, &this->parse_tree, NULL); } /* Perform highlighting, returning an array of colors */ @@ -1920,8 +1918,7 @@ const highlighter_t::color_array_t & highlighter_t::highlight() /* Parse the buffer */ parse_node_tree_t parse_tree; - parse_t parser; - parser.parse(buff, parse_flag_continue_after_error | parse_flag_include_comments, &parse_tree, NULL); + parse_t::parse(buff, parse_flag_continue_after_error | parse_flag_include_comments, &parse_tree, NULL); #if 0 const wcstring dump = parse_dump_tree(parse_tree, buff); diff --git a/parse_tree.cpp b/parse_tree.cpp index 207458ef6..87e2b3dc0 100644 --- a/parse_tree.cpp +++ b/parse_tree.cpp @@ -1013,6 +1013,34 @@ const parse_node_t *parse_node_tree_t::find_last_node_of_type(parse_token_type_t return result; } +const parse_node_t *parse_node_tree_t::find_node_matching_source_location(parse_token_type_t type, size_t source_loc, const parse_node_t *parent) const +{ + const parse_node_t *result = NULL; + // Find nodes of the given type in the tree, working backwards + const size_t len = this->size(); + for (size_t idx=0; idx < len; idx++) + { + const parse_node_t &node = this->at(idx); + + /* Types must match */ + if (node.type != type) + continue; + + /* Must contain source location */ + if (! node.location_in_or_at_end_of_source_range(source_loc)) + continue; + + /* If a parent is given, it must be an ancestor */ + if (parent != NULL && node_has_ancestor(*this, node, *parent)) + continue; + + /* Found it */ + result = &node; + break; + } + return result; +} + bool parse_node_tree_t::argument_list_is_root(const parse_node_t &node) const { diff --git a/parse_tree.h b/parse_tree.h index c1bcbab96..62ffb622a 100644 --- a/parse_tree.h +++ b/parse_tree.h @@ -224,6 +224,12 @@ public: else return wcstring(str, this->source_start, this->source_length); } + + /* Returns whether the given location is within the source range or at its end */ + bool location_in_or_at_end_of_source_range(size_t loc) const + { + return has_source() && source_start <= loc && loc - source_start <= source_length; + } }; /* Statement decorations. This matches the order of productions in decorated_statement */ @@ -254,6 +260,9 @@ public: /* Finds the last node of a given type underneath a given node, or NULL if it could not be found. If parent is NULL, this finds the last node in the tree of that type. */ const parse_node_t *find_last_node_of_type(parse_token_type_t type, const parse_node_t *parent = NULL) const; + /* Finds a node containing the given source location */ + const parse_node_t *find_node_matching_source_location(parse_token_type_t type, size_t source_loc, const parse_node_t *parent) const; + /* Indicate if the given argument_list or arguments_or_redirections_list is a root list, or has a parent */ bool argument_list_is_root(const parse_node_t &node) const; diff --git a/reader.cpp b/reader.cpp index b59f8086c..6c1e4a52d 100644 --- a/reader.cpp +++ b/reader.cpp @@ -3187,6 +3187,9 @@ const wchar_t *reader_readline(void) /* Figure out the extent of the token within the command substitution. Note we pass cmdsub_begin here, not buff */ const wchar_t *token_begin, *token_end; parse_util_token_extent(cmdsub_begin, data->buff_pos - (cmdsub_begin-buff), &token_begin, &token_end, 0, 0); + + /* Hack: the token may extend past the end of the command substitution, e.g. in (echo foo) the last token is 'foo)'. Don't let that happen. */ + if (token_end > cmdsub_end) token_end = cmdsub_end; /* Figure out how many steps to get from the current position to the end of the current token. */ size_t end_of_token_offset = token_end - buff;