mirror of
https://github.com/fish-shell/fish-shell
synced 2024-12-26 12:53:13 +00:00
Adopt new parser in tab completions
This commit is contained in:
parent
cbd8a27a6d
commit
ddf98661e4
5 changed files with 169 additions and 200 deletions
264
complete.cpp
264
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,215 +1793,145 @@ bool completer_t::try_complete_user(const wcstring &str)
|
|||
return res;
|
||||
}
|
||||
|
||||
void complete(const wcstring &cmd, std::vector<completion_t> &comps, completion_request_flags_t flags, wcstring_list_t *commands_to_load)
|
||||
void complete(const wcstring &cmd_with_subcmds, std::vector<completion_t> &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 size_t prev_token_len = (prev_begin ? prev_end - prev_begin : 0);
|
||||
//const wcstring prev_token(prev_begin, prev_token_len);
|
||||
|
||||
const wcstring buff = wcstring(cmdsubst_begin, cmdsubst_end-cmdsubst_begin);
|
||||
parse_node_tree_t tree;
|
||||
parse_t::parse(cmd, parse_flag_continue_after_error | parse_flag_accept_incomplete_tokens, &tree, NULL);
|
||||
|
||||
int had_cmd=0;
|
||||
int end_loop=0;
|
||||
/* 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)
|
||||
{
|
||||
assert(plain_statement->has_source() && plain_statement->type == symbol_plain_statement);
|
||||
|
||||
tokenizer_t tok(buff.c_str(), TOK_ACCEPT_UNFINISHED | TOK_SQUASH_ERRORS);
|
||||
while (tok_has_next(&tok) && !end_loop)
|
||||
{
|
||||
switch (tok_last_type(&tok))
|
||||
{
|
||||
/* Get the command node */
|
||||
const parse_node_t *cmd_node = tree.get_child(*plain_statement, 0, parse_token_type_string);
|
||||
|
||||
case TOK_STRING:
|
||||
{
|
||||
/* Get the actual command string */
|
||||
if (cmd_node != NULL)
|
||||
current_command = cmd_node->get_source(cmd);
|
||||
|
||||
const wcstring ncmd = tok_last(&tok);
|
||||
int is_ddash = (ncmd == L"--") && ((tok_get_pos(&tok)+2) < (long)pos);
|
||||
/* Check the decoration */
|
||||
switch (tree.decoration_for_plain_statement(*plain_statement))
|
||||
{
|
||||
case parse_statement_decoration_none:
|
||||
use_command = true;
|
||||
use_function = false;
|
||||
use_builtin = false;
|
||||
break;
|
||||
|
||||
if (!had_cmd)
|
||||
{
|
||||
case parse_statement_decoration_command:
|
||||
use_command = true;
|
||||
use_function = false;
|
||||
use_builtin = false;
|
||||
break;
|
||||
|
||||
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;
|
||||
}
|
||||
case parse_statement_decoration_builtin:
|
||||
use_command = false;
|
||||
use_function = false;
|
||||
use_builtin = true;
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
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:
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
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 parse_node_t *node = all_arguments.at(i);
|
||||
if (node->location_in_or_at_end_of_source_range(pos))
|
||||
{
|
||||
matching_arg_index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool had_ddash = false;
|
||||
wcstring current_argument, previous_argument;
|
||||
if (matching_arg_index != (size_t)(-1))
|
||||
{
|
||||
/* 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 prev_token_unescape = prev_token;
|
||||
wcstring current_token_unescape = current_token;
|
||||
wcstring previous_argument_unescape = previous_argument;
|
||||
wcstring current_argument_unescape = current_argument;
|
||||
|
||||
if (unescape_string(current_command_unescape, 0) &&
|
||||
unescape_string(prev_token_unescape, 0) &&
|
||||
unescape_string(current_token_unescape, UNESCAPE_INCOMPLETE))
|
||||
unescape_string(previous_argument_unescape, 0) &&
|
||||
unescape_string(current_argument_unescape, UNESCAPE_INCOMPLETE))
|
||||
{
|
||||
do_file = completer.complete_param(current_command_unescape,
|
||||
prev_token_unescape,
|
||||
current_token_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 we have found no command specific completions at all, fall back to using file completions. */
|
||||
if (completer.empty())
|
||||
do_file = true;
|
||||
|
||||
|
@ -2008,15 +1941,14 @@ void complete(const wcstring &cmd, std::vector<completion_t> &comps, completion_
|
|||
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())
|
||||
if ((flags & COMPLETION_REQUEST_AUTOSUGGESTION) && current_argument_unescape.empty())
|
||||
do_file = false;
|
||||
|
||||
/*
|
||||
This function wants the unescaped string
|
||||
*/
|
||||
/* This function wants the unescaped string */
|
||||
completer.complete_param_expand(current_token, do_file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
comps = completer.get_completions();
|
||||
completer.get_commands_to_load(commands_to_load);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -3188,6 +3188,9 @@ const wchar_t *reader_readline(void)
|
|||
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;
|
||||
|
||||
|
|
Loading…
Reference in a new issue