mirror of
https://github.com/fish-shell/fish-shell
synced 2025-01-27 20:25:12 +00:00
Equip syntax highlighting with a variant that does no disk I/O. Invoke
it after expanding an abbreviation, so that the expanded abbreviation appears with (some) syntax highlighting.
This commit is contained in:
parent
0325c1ba65
commit
31bf50b2d4
3 changed files with 86 additions and 29 deletions
|
@ -912,6 +912,9 @@ class highlighter_t
|
|||
/* Environment variables. Again, a reference member variable! */
|
||||
const env_vars_snapshot_t &vars;
|
||||
|
||||
/* Whether it's OK to do I/O */
|
||||
const bool io_ok;
|
||||
|
||||
/* Working directory */
|
||||
const wcstring working_directory;
|
||||
|
||||
|
@ -943,7 +946,7 @@ class highlighter_t
|
|||
public:
|
||||
|
||||
/* Constructor */
|
||||
highlighter_t(const wcstring &str, size_t pos, const env_vars_snapshot_t &ev, const wcstring &wd) : buff(str), cursor_pos(pos), vars(ev), working_directory(wd), color_array(str.size())
|
||||
highlighter_t(const wcstring &str, size_t pos, const env_vars_snapshot_t &ev, const wcstring &wd, bool can_do_io) : buff(str), cursor_pos(pos), vars(ev), io_ok(can_do_io), working_directory(wd), color_array(str.size())
|
||||
{
|
||||
/* Parse the tree */
|
||||
this->parse_tree.clear();
|
||||
|
@ -1010,7 +1013,7 @@ void highlighter_t::color_argument(const parse_node_t &node)
|
|||
}
|
||||
|
||||
/* Highlight it recursively. */
|
||||
highlighter_t cmdsub_highlighter(cmdsub_contents, cursor_subpos, this->vars, this->working_directory);
|
||||
highlighter_t cmdsub_highlighter(cmdsub_contents, cursor_subpos, this->vars, this->working_directory, this->io_ok);
|
||||
const color_array_t &subcolors = cmdsub_highlighter.highlight();
|
||||
|
||||
/* Copy out the subcolors back into our array */
|
||||
|
@ -1046,6 +1049,8 @@ void highlighter_t::color_arguments(const parse_node_t &list_node)
|
|||
{
|
||||
/* Hack: determine whether the parent is the cd command, so we can show errors for non-directories */
|
||||
bool cmd_is_cd = false;
|
||||
if (this->io_ok)
|
||||
{
|
||||
const parse_node_t *parent = this->parse_tree.get_parent(list_node, symbol_plain_statement);
|
||||
if (parent != NULL)
|
||||
{
|
||||
|
@ -1055,6 +1060,7 @@ void highlighter_t::color_arguments(const parse_node_t &list_node)
|
|||
cmd_is_cd = (cmd_str == L"cd");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Find all the arguments of this list */
|
||||
const parse_node_tree_t::parse_node_list_t nodes = this->parse_tree.find_nodes(list_node, symbol_argument);
|
||||
|
@ -1072,7 +1078,7 @@ void highlighter_t::color_arguments(const parse_node_t &list_node)
|
|||
if (expand_one(param, EXPAND_SKIP_CMDSUBST))
|
||||
{
|
||||
bool is_help = string_prefixes_string(param, L"--help") || string_prefixes_string(param, L"-h");
|
||||
if (!is_help && ! is_potential_cd_path(param, working_directory, PATH_EXPAND_TILDE, NULL))
|
||||
if (! is_help && this->io_ok && ! is_potential_cd_path(param, working_directory, PATH_EXPAND_TILDE, NULL))
|
||||
{
|
||||
this->color_node(*child, highlight_spec_error);
|
||||
}
|
||||
|
@ -1102,14 +1108,21 @@ void highlighter_t::color_redirection(const parse_node_t &redirection_node)
|
|||
if (parse_util_locate_cmdsubst(target.c_str(), NULL, NULL, true) != 0)
|
||||
{
|
||||
if (redirection_target != NULL)
|
||||
{
|
||||
this->color_argument(*redirection_target);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* No command substitution, so we can highlight the target file or fd. For example, disallow redirections into a non-existent directory */
|
||||
bool target_is_valid = true;
|
||||
|
||||
if (! expand_one(target, EXPAND_SKIP_CMDSUBST))
|
||||
if (! this->io_ok)
|
||||
{
|
||||
/* I/O is disallowed, so we don't have much hope of catching anything but gross errors. Assume it's valid. */
|
||||
target_is_valid = true;
|
||||
}
|
||||
else if (! expand_one(target, EXPAND_SKIP_CMDSUBST))
|
||||
{
|
||||
/* Could not be expanded */
|
||||
target_is_valid = false;
|
||||
|
@ -1280,7 +1293,11 @@ static bool command_is_valid(const wcstring &cmd, enum parse_statement_decoratio
|
|||
|
||||
const highlighter_t::color_array_t & highlighter_t::highlight()
|
||||
{
|
||||
// If we are doing I/O, we must be in a background thread
|
||||
if (io_ok)
|
||||
{
|
||||
ASSERT_IS_BACKGROUND_THREAD();
|
||||
}
|
||||
|
||||
const size_t length = buff.size();
|
||||
assert(this->buff.size() == this->color_array.size());
|
||||
|
@ -1346,7 +1363,7 @@ const highlighter_t::color_array_t & highlighter_t::highlight()
|
|||
|
||||
case symbol_plain_statement:
|
||||
{
|
||||
// Get the decoration from the parent
|
||||
/* Get the decoration from the parent */
|
||||
enum parse_statement_decoration_t decoration = parse_tree.decoration_for_plain_statement(node);
|
||||
|
||||
/* Color the command */
|
||||
|
@ -1354,6 +1371,14 @@ const highlighter_t::color_array_t & highlighter_t::highlight()
|
|||
if (cmd_node != NULL && cmd_node->has_source())
|
||||
{
|
||||
bool is_valid_cmd = false;
|
||||
if (! this->io_ok)
|
||||
{
|
||||
/* We cannot check if the command is invalid, so just assume it's valid */
|
||||
is_valid_cmd = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Check to see if the command is valid */
|
||||
wcstring cmd(buff, cmd_node->source_start, cmd_node->source_length);
|
||||
|
||||
/* Try expanding it. If we cannot, it's an error. */
|
||||
|
@ -1362,6 +1387,7 @@ const highlighter_t::color_array_t & highlighter_t::highlight()
|
|||
{
|
||||
is_valid_cmd = command_is_valid(cmd, decoration, working_directory, vars);
|
||||
}
|
||||
}
|
||||
this->color_node(*cmd_node, is_valid_cmd ? highlight_spec_command : highlight_spec_error);
|
||||
}
|
||||
}
|
||||
|
@ -1398,7 +1424,7 @@ const highlighter_t::color_array_t & highlighter_t::highlight()
|
|||
}
|
||||
}
|
||||
|
||||
if (this->cursor_pos <= this->buff.size())
|
||||
if (this->io_ok && this->cursor_pos <= this->buff.size())
|
||||
{
|
||||
/* If the cursor is over an argument, and that argument is a valid path, underline it */
|
||||
for (parse_node_tree_t::const_iterator iter = parse_tree.begin(); iter != parse_tree.end(); ++iter)
|
||||
|
@ -1438,7 +1464,17 @@ void highlight_shell(const wcstring &buff, std::vector<highlight_spec_t> &color,
|
|||
const wcstring working_directory = env_get_pwd_slash();
|
||||
|
||||
/* Highlight it! */
|
||||
highlighter_t highlighter(buff, pos, vars, working_directory);
|
||||
highlighter_t highlighter(buff, pos, vars, working_directory, true /* can do IO */);
|
||||
color = highlighter.highlight();
|
||||
}
|
||||
|
||||
void highlight_shell_no_io(const wcstring &buff, std::vector<highlight_spec_t> &color, size_t pos, wcstring_list_t *error, const env_vars_snapshot_t &vars)
|
||||
{
|
||||
/* Do something sucky and get the current working directory on this background thread. This should really be passed in. */
|
||||
const wcstring working_directory = env_get_pwd_slash();
|
||||
|
||||
/* Highlight it! */
|
||||
highlighter_t highlighter(buff, pos, vars, working_directory, false /* no IO allowed */);
|
||||
color = highlighter.highlight();
|
||||
}
|
||||
|
||||
|
|
|
@ -76,6 +76,11 @@ struct file_detection_context_t;
|
|||
*/
|
||||
void highlight_shell(const wcstring &buffstr, std::vector<highlight_spec_t> &color, size_t pos, wcstring_list_t *error, const env_vars_snapshot_t &vars);
|
||||
|
||||
/**
|
||||
Perform a non-blocking shell highlighting. The function will not do any I/O that may block. As a result, invalid commands may not be detected, etc.
|
||||
*/
|
||||
void highlight_shell_no_io(const wcstring &buffstr, std::vector<highlight_spec_t> &color, size_t pos, wcstring_list_t *error, const env_vars_snapshot_t &vars);
|
||||
|
||||
/**
|
||||
Perform syntax highlighting for the text in buff. Matching quotes and paranthesis are highlighted. The result is
|
||||
stored in the color array as a color_code from the HIGHLIGHT_ enum
|
||||
|
|
28
reader.cpp
28
reader.cpp
|
@ -429,7 +429,7 @@ static struct termios terminal_mode_on_startup;
|
|||
static struct termios terminal_mode_for_executing_programs;
|
||||
|
||||
|
||||
static void reader_super_highlight_me_plenty(int highlight_pos_adjust = 0);
|
||||
static void reader_super_highlight_me_plenty(int highlight_pos_adjust = 0, bool no_io = false);
|
||||
|
||||
/**
|
||||
Variable to keep track of forced exits - see \c reader_exit_forced();
|
||||
|
@ -2654,7 +2654,7 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
int threaded_highlight()
|
||||
int perform_highlight()
|
||||
{
|
||||
if (generation_count != s_generation_count)
|
||||
{
|
||||
|
@ -2710,7 +2710,7 @@ static void highlight_complete(background_highlight_context_t *ctx, int result)
|
|||
|
||||
static int threaded_highlight(background_highlight_context_t *ctx)
|
||||
{
|
||||
return ctx->threaded_highlight();
|
||||
return ctx->perform_highlight();
|
||||
}
|
||||
|
||||
|
||||
|
@ -2722,8 +2722,9 @@ static int threaded_highlight(background_highlight_context_t *ctx)
|
|||
|
||||
\param match_highlight_pos_adjust the adjustment to the position to use for bracket matching. This is added to the current cursor position and may be negative.
|
||||
\param error if non-null, any possible errors in the buffer are further descibed by the strings inserted into the specified arraylist
|
||||
\param no_io if true, do a highlight that does not perform I/O, synchronously. If false, perform an asynchronous highlight in the background, which may perform disk I/O.
|
||||
*/
|
||||
static void reader_super_highlight_me_plenty(int match_highlight_pos_adjust)
|
||||
static void reader_super_highlight_me_plenty(int match_highlight_pos_adjust, bool no_io)
|
||||
{
|
||||
const editable_line_t *el = &data->command_line;
|
||||
long match_highlight_pos = (long)el->position + match_highlight_pos_adjust;
|
||||
|
@ -2731,8 +2732,20 @@ static void reader_super_highlight_me_plenty(int match_highlight_pos_adjust)
|
|||
|
||||
reader_sanity_check();
|
||||
|
||||
background_highlight_context_t *ctx = new background_highlight_context_t(el->text, match_highlight_pos, data->highlight_function);
|
||||
highlight_function_t highlight_func = no_io ? highlight_shell_no_io : data->highlight_function;
|
||||
background_highlight_context_t *ctx = new background_highlight_context_t(el->text, match_highlight_pos, highlight_func);
|
||||
if (no_io)
|
||||
{
|
||||
// Highlighting without IO, we just do it
|
||||
// Note that highlight_complete deletes ctx.
|
||||
int result = ctx->perform_highlight();
|
||||
highlight_complete(ctx, result);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Highlighting including I/O proceeds in the background
|
||||
iothread_perform(threaded_highlight, highlight_complete, ctx);
|
||||
}
|
||||
highlight_search();
|
||||
|
||||
/* Here's a hack. Check to see if our autosuggestion still applies; if so, don't recompute it. Since the autosuggestion computation is asynchronous, this avoids "flashing" as you type into the autosuggestion. */
|
||||
|
@ -3452,8 +3465,11 @@ const wchar_t *reader_readline(void)
|
|||
if (abbreviation_expanded)
|
||||
{
|
||||
/* It's our reponsibility to rehighlight and repaint. But everything we do below triggers a repaint. */
|
||||
reader_super_highlight_me_plenty();
|
||||
command_test_result = data->test_func(el->text.c_str());
|
||||
|
||||
/* If the command is OK, then we're going to execute it. We still want to do syntax highlighting, but a synchronous variant that performs no I/O, so as not to block the user */
|
||||
bool skip_io = (command_test_result == 0);
|
||||
reader_super_highlight_me_plenty(0, skip_io);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue