diff --git a/CHANGELOG.md b/CHANGELOG.md index 35a86ae93..0382ad6e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -64,6 +64,7 @@ - If fish_mode_prompt exists, vi-mode will only execute it on mode-switch instead of the entire prompt. This should make it much more responsive with slow prompts (#5783). - The path-component bindings (like ctrl-w) now also stop at ":" and "@" because those are used to denote user and host in ssh-likes (#5841). - `read` no longer keeps a history, making it suitable for operations that shouldn't end up there, like password entry (#5904). +- When syntax highlighting a string with an unclosed quote, only the quote itself will be shown as an error, instead of the whole argument. ### For distributors and developers - The autotools-based build system and legacy Xcode build systems have been removed, leaving only the CMake build system. All distributors and developers must migrate to the CMake build. diff --git a/src/fish_tests.cpp b/src/fish_tests.cpp index 0d531aad1..ebaa42f2c 100644 --- a/src/fish_tests.cpp +++ b/src/fish_tests.cpp @@ -4474,7 +4474,16 @@ static void test_highlighting() { highlight_tests.push_back({ {L"echo", highlight_role_t::command}, - {L"'single_quote", highlight_role_t::error}, + {L"'", highlight_role_t::error}, + {L"single_quote", highlight_role_t::quote}, + {L"$stuff", highlight_role_t::quote}, + }); + + highlight_tests.push_back({ + {L"echo", highlight_role_t::command}, + {L"\"", highlight_role_t::error}, + {L"double_quote", highlight_role_t::quote}, + {L"$stuff", highlight_role_t::operat}, }); highlight_tests.push_back({ diff --git a/src/highlight.cpp b/src/highlight.cpp index a79a17b4c..3149f97aa 100644 --- a/src/highlight.cpp +++ b/src/highlight.cpp @@ -537,6 +537,7 @@ static void color_string_internal(const wcstring &buffstr, highlight_spec_t base } enum { e_unquoted, e_single_quoted, e_double_quoted } mode = e_unquoted; + maybe_t unclosed_quote_offset; int bracket_count = 0; for (size_t in_pos = 0; in_pos < buff_len; in_pos++) { const wchar_t c = buffstr.at(in_pos); @@ -671,11 +672,13 @@ static void color_string_internal(const wcstring &buffstr, highlight_spec_t base } case L'\'': { colors[in_pos] = highlight_role_t::quote; + unclosed_quote_offset = in_pos; mode = e_single_quoted; break; } case L'\"': { colors[in_pos] = highlight_role_t::quote; + unclosed_quote_offset = in_pos; mode = e_double_quoted; break; } @@ -700,6 +703,7 @@ static void color_string_internal(const wcstring &buffstr, highlight_spec_t base } } } else if (c == L'\'') { + unclosed_quote_offset = none(); mode = e_unquoted; } break; @@ -713,6 +717,7 @@ static void color_string_internal(const wcstring &buffstr, highlight_spec_t base } switch (c) { case L'"': { + unclosed_quote_offset = none(); mode = e_unquoted; break; } @@ -743,6 +748,11 @@ static void color_string_internal(const wcstring &buffstr, highlight_spec_t base } } } + + // Error on unclosed quotes. + if (unclosed_quote_offset) { + colors[*unclosed_quote_offset] = highlight_role_t::error; + } } /// Syntax highlighter helper. @@ -793,7 +803,9 @@ class highlighter_t { working_directory(std::move(wd)), color_array(str.size()) { // Parse the tree. - parse_tree_from_string(buff, parse_flag_continue_after_error | parse_flag_include_comments, + parse_tree_from_string(buff, + parse_flag_continue_after_error | parse_flag_include_comments | + parse_flag_accept_incomplete_tokens, &this->parse_tree, NULL); }