diff --git a/doc_src/fish_indent_lexer.py b/doc_src/fish_indent_lexer.py index 727b62fbb..c81a06867 100644 --- a/doc_src/fish_indent_lexer.py +++ b/doc_src/fish_indent_lexer.py @@ -37,6 +37,7 @@ ROLE_TO_TOKEN = { "keyword": Keyword, "statement_terminator": Punctuation, "param": Name.Constant, + "option": Name.Literal, "comment": Comment, "match": DEFAULT, "search_match": DEFAULT, diff --git a/doc_src/interactive.rst b/doc_src/interactive.rst index 6c8104959..044a610bc 100644 --- a/doc_src/interactive.rst +++ b/doc_src/interactive.rst @@ -107,6 +107,7 @@ Variable Meaning ``fish_color_end`` process separators like ``;`` and ``&`` ``fish_color_error`` syntax errors ``fish_color_param`` ordinary command parameters +``fish_color_option`` options starting with "-", up to the first "--" parameter ``fish_color_comment`` comments like '# important' ``fish_color_selection`` selected text in vi visual mode ``fish_color_operator`` parameter expansion operators like ``*`` and ``~`` @@ -120,7 +121,10 @@ Variable Meaning ``fish_color_search_match`` history search matches and selected pager items (background only) ========================================== ===================================================================== -If a variable isn't set, fish usually tries ``$fish_color_normal``, except for ``$fish_color_keyword``, where it tries ``$fish_color_command`` first. +If a variable isn't set, fish usually tries ``$fish_color_normal``, except for: + +- ``$fish_color_keyword``, where it tries ``$fish_color_command`` first. +- ``$fish_color_option``, where it tries ``$fish_color_param`` first. .. _variables-color-pager: diff --git a/src/fish_indent.cpp b/src/fish_indent.cpp index 26830a134..55ffd6057 100644 --- a/src/fish_indent.cpp +++ b/src/fish_indent.cpp @@ -671,6 +671,7 @@ static const char *highlight_role_to_string(highlight_role_t role) { TEST_ROLE(keyword) TEST_ROLE(statement_terminator) TEST_ROLE(param) + TEST_ROLE(option) TEST_ROLE(comment) TEST_ROLE(search_match) TEST_ROLE(operat) @@ -777,6 +778,9 @@ static const wchar_t *html_class_name_for_color(highlight_spec_t spec) { case highlight_role_t::param: { return P(param); } + case highlight_role_t::option: { + return P(option); + } case highlight_role_t::comment: { return P(comment); } diff --git a/src/fish_tests.cpp b/src/fish_tests.cpp index 04058b47b..0de04338b 100644 --- a/src/fish_tests.cpp +++ b/src/fish_tests.cpp @@ -5244,8 +5244,8 @@ static void test_highlighting() { highlight_tests.push_back({ {L"cd", highlight_role_t::command}, - {L"--help", highlight_role_t::param}, - {L"-h", highlight_role_t::param}, + {L"--help", highlight_role_t::option}, + {L"-h", highlight_role_t::option}, {L"definitely_not_a_directory", highlight_role_t::error}, }); @@ -5479,6 +5479,13 @@ static void test_highlighting() { {L"# comment", highlight_role_t::comment}, }); + + highlight_tests.push_back({ + {L"echo", highlight_role_t::command}, + {L"--", highlight_role_t::option}, + {L"-s", highlight_role_t::param}, + }); + // Overlong paths don't crash (#7837). const wcstring overlong = get_overlong_path(); highlight_tests.push_back({ diff --git a/src/highlight.cpp b/src/highlight.cpp index 69948d16b..0ae4a37cd 100644 --- a/src/highlight.cpp +++ b/src/highlight.cpp @@ -51,6 +51,8 @@ static const wchar_t *get_highlight_var_name(highlight_role_t role) { return L"fish_color_end"; case highlight_role_t::param: return L"fish_color_param"; + case highlight_role_t::option: + return L"fish_color_option"; case highlight_role_t::comment: return L"fish_color_comment"; case highlight_role_t::search_match: @@ -111,6 +113,8 @@ static highlight_role_t get_fallback(highlight_role_t role) { return highlight_role_t::command; case highlight_role_t::statement_terminator: return highlight_role_t::normal; + case highlight_role_t::option: + return highlight_role_t::param; case highlight_role_t::param: return highlight_role_t::normal; case highlight_role_t::comment: @@ -539,7 +543,9 @@ static size_t color_variable(const wchar_t *in, size_t in_len, static void color_string_internal(const wcstring &buffstr, highlight_spec_t base_color, std::vector::iterator colors) { // Clarify what we expect. - assert((base_color == highlight_role_t::param || base_color == highlight_role_t::command) && + assert((base_color == highlight_role_t::param + || base_color == highlight_role_t::option + || base_color == highlight_role_t::command) && "Unexpected base color"); const size_t buff_len = buffstr.size(); std::fill(colors, colors + buff_len, base_color); @@ -799,7 +805,7 @@ class highlighter_t { // Color a command. void color_command(const ast::string_t &node); // Color a node as if it were an argument. - void color_as_argument(const ast::node_t &node); + void color_as_argument(const ast::node_t &node, bool options_allowed = true); // Colors the source range of a node with a given color. void color_node(const ast::node_t &node, highlight_spec_t color); // Colors a range with a given color. @@ -824,7 +830,7 @@ class highlighter_t { void visit(const ast::block_statement_t &block); // Visit an argument, perhaps knowing that our command is cd. - void visit(const ast::argument_t &arg, bool cmd_is_cd = false); + void visit(const ast::argument_t &arg, bool cmd_is_cd = false, bool options_allowed = true); // Default implementation is to just visit children. void visit(const ast::node_t &node) { visit_children(node); } @@ -867,7 +873,7 @@ void highlighter_t::color_command(const ast::string_t &node) { } // node does not necessarily have type symbol_argument here. -void highlighter_t::color_as_argument(const ast::node_t &node) { + void highlighter_t::color_as_argument(const ast::node_t &node, bool options_allowed) { auto source_range = node.source_range(); const wcstring arg_str = get_source(source_range); @@ -876,7 +882,11 @@ void highlighter_t::color_as_argument(const ast::node_t &node) { const color_array_t::iterator arg_colors = color_array.begin() + arg_start; // Color this argument without concern for command substitutions. - color_string_internal(arg_str, highlight_role_t::param, arg_colors); + if (options_allowed && arg_str[0] == L'-') { + color_string_internal(arg_str, highlight_role_t::option, arg_colors); + } else { + color_string_internal(arg_str, highlight_role_t::param, arg_colors); + } // Now do command substitutions. size_t cmdsub_cursor = 0, cmdsub_start = 0, cmdsub_end = 0; @@ -1001,8 +1011,8 @@ void highlighter_t::visit(const ast::semi_nl_t &semi_nl) { color_node(semi_nl, highlight_role_t::statement_terminator); } -void highlighter_t::visit(const ast::argument_t &arg, bool cmd_is_cd) { - color_as_argument(arg); +void highlighter_t::visit(const ast::argument_t &arg, bool cmd_is_cd, bool options_allowed) { + color_as_argument(arg, options_allowed); if (cmd_is_cd && io_ok) { // Mark this as an error if it's not 'help' and not a valid cd path. wcstring param = arg.source(this->buff); @@ -1065,6 +1075,8 @@ void highlighter_t::visit(const ast::decorated_statement_t &stmt) { // Except if our command is 'cd' we have special logic for how arguments are colored. bool is_cd = (expanded_cmd == L"cd"); bool is_set = (expanded_cmd == L"set"); + // If we have seen a "--" argument, color all options from then on as normal arguments. + bool have_dashdash = false; for (const ast::argument_or_redirection_t &v : stmt.args_or_redirs) { if (v.is_argument()) { if (is_set) { @@ -1074,7 +1086,8 @@ void highlighter_t::visit(const ast::decorated_statement_t &stmt) { is_set = false; } } - this->visit(v.argument(), is_cd); + this->visit(v.argument(), is_cd, !have_dashdash); + if (v.argument().source(this->buff) == L"--") have_dashdash = true; } else { this->visit(v.redirection()); } diff --git a/src/highlight.h b/src/highlight.h index ed1115078..621ce7943 100644 --- a/src/highlight.h +++ b/src/highlight.h @@ -20,6 +20,7 @@ enum class highlight_role_t : uint8_t { keyword, statement_terminator, // process separator param, // command parameter (argument) + option, // argument starting with "-", up to a "--" comment, // comment search_match, // search match operat, // operator