Properly syntax highlight commands that get entered too fast

This fixes the following problem: if a command is entered while the
previous command is still executing, fish will see it all at once and
execute it before syntax highlighting as a chance to start. So the
command will appear wrong on the terminal. Fix this by detecting this
case and performing a fast no-io highlight.

An example of how to reproduce this:
run `sleep 3` and then type `echo foo` while the sleep is still running.
This commit is contained in:
ridiculousfish 2021-03-28 12:34:50 -07:00
parent 4e4852c40a
commit a5ea8570ae
2 changed files with 36 additions and 20 deletions

View file

@ -21,6 +21,7 @@ Interactive improvements
- When there are multiple completion candidates, fish inserts their shared prefix. This prefix was computed in a case-insensitive way, resulting in wrong case in the completion pager. This was fixed by only inserting prefixes with matching case (:issue:`7744`).
- Commands that wrap ``cd`` (using ``complete --wraps cd``) get the same completions as ``cd`` (:issue:`4693`).
- Arguments longer than 1024 characters no longer trigger excessive CPU usage on Mac (:issue:`7837`).
- Commands entered before the previous command finishes will now be properly syntax highlighted.
- Fish now automatically creates ``config.fish`` and the configuration directories in ``$XDG_CONFIG_HOME/fish`` (by default ``~/.config/fish``) if they do not already exist.
- ``__fish_prepend_sudo`` now toggles sudo even when it took the commandline from history instead of only adding it.

View file

@ -2500,29 +2500,42 @@ void reader_data_t::super_highlight_me_plenty() {
}
void reader_data_t::finish_highlighting_before_exec() {
// Early-out if highlighting is not OK.
if (!conf.highlight_ok) return;
if (in_flight_highlight_request.empty()) return;
// We have an in-flight highlight request scheduled.
// Wait for its completion to run, but not forever.
namespace sc = std::chrono;
auto now = sc::steady_clock::now();
auto deadline = now + sc::milliseconds(kHighlightTimeoutForExecutionMs);
while (now < deadline) {
long timeout_usec = sc::duration_cast<sc::microseconds>(deadline - now).count();
iothread_service_main_with_timeout(timeout_usec);
// Decide if our current highlighting is OK. If not we will do a quick highlight without I/O.
bool current_highlight_ok = false;
if (in_flight_highlight_request.empty()) {
// There is no in-flight highlight request. Two possibilities:
// 1: The user hit return after highlighting finished, so current highlighting is correct.
// 2: The user hit return before highlighting started, so current highlighting is stale.
// We can distinguish these based on what we last rendered.
current_highlight_ok = (this->rendered_layout.text == command_line.text());
} else if (in_flight_highlight_request == command_line.text()) {
// The user hit return while our in-flight highlight request was still processing the text.
// Wait for its completion to run, but not forever.
namespace sc = std::chrono;
auto now = sc::steady_clock::now();
auto deadline = now + sc::milliseconds(kHighlightTimeoutForExecutionMs);
while (now < deadline) {
long timeout_usec = sc::duration_cast<sc::microseconds>(deadline - now).count();
iothread_service_main_with_timeout(timeout_usec);
// Note iothread_service_main_with_timeout will reentrantly modify us,
// by invoking a completion.
if (in_flight_highlight_request.empty()) break;
now = sc::steady_clock::now();
// Note iothread_service_main_with_timeout will reentrantly modify us,
// by invoking a completion.
if (in_flight_highlight_request.empty()) break;
now = sc::steady_clock::now();
}
// If our in_flight_highlight_request is now empty, it means it completed and we highlighted
// successfully.
current_highlight_ok = in_flight_highlight_request.empty();
}
if (!in_flight_highlight_request.empty()) {
// We did not complete before the deadline.
// Give up and highlight without I/O.
const editable_line_t *el = &command_line;
auto highlight_no_io = get_highlight_performer(parser(), el->text(), false /* io not ok */);
if (!current_highlight_ok) {
// We need to do a quick highlight without I/O.
auto highlight_no_io =
get_highlight_performer(parser(), command_line.text(), false /* io not ok */);
this->highlight_complete(highlight_no_io());
}
}
@ -3993,8 +4006,10 @@ maybe_t<wcstring> reader_data_t::readline(int nchars_or_0) {
if (this->is_repaint_needed() || conf.in != STDIN_FILENO)
this->layout_and_repaint(L"prepare to execute");
// Finish any outstanding syntax highlighting (but do not wait forever).
finish_highlighting_before_exec();
// Finish syntax highlighting (but do not wait forever).
if (rls.finished) {
finish_highlighting_before_exec();
}
// Emit a newline so that the output is on the line after the command.
// But do not emit a newline if the cursor has wrapped onto a new line all its own - see #6826.