Switch highlight_spec_t to a struct

Rather than a janky bitmask, use a real struct with real fields.
This commit is contained in:
ridiculousfish 2019-03-03 17:34:00 -08:00
parent d165d1df27
commit 717ac9a8d5
7 changed files with 422 additions and 377 deletions

View File

@ -275,7 +275,7 @@ static std::string ansi_colorize(const wcstring &text,
assert(colors.size() == text.size()); assert(colors.size() == text.size());
outputter_t outp; outputter_t outp;
highlight_spec_t last_color = highlight_spec_normal; highlight_spec_t last_color = highlight_role_t::normal;
for (size_t i = 0; i < text.size(); i++) { for (size_t i = 0; i < text.size(); i++) {
highlight_spec_t color = colors.at(i); highlight_spec_t color = colors.at(i);
if (color != last_color) { if (color != last_color) {
@ -292,47 +292,47 @@ static std::string ansi_colorize(const wcstring &text,
/// for the various colors. /// for the various colors.
static const wchar_t *html_class_name_for_color(highlight_spec_t spec) { static const wchar_t *html_class_name_for_color(highlight_spec_t spec) {
#define P(x) L"fish_color_" #x #define P(x) L"fish_color_" #x
switch (spec & HIGHLIGHT_SPEC_PRIMARY_MASK) { switch (spec.foreground) {
case highlight_spec_normal: { case highlight_role_t::normal: {
return P(normal); return P(normal);
} }
case highlight_spec_error: { case highlight_role_t::error: {
return P(error); return P(error);
} }
case highlight_spec_command: { case highlight_role_t::command: {
return P(command); return P(command);
} }
case highlight_spec_statement_terminator: { case highlight_role_t::statement_terminator: {
return P(statement_terminator); return P(statement_terminator);
} }
case highlight_spec_param: { case highlight_role_t::param: {
return P(param); return P(param);
} }
case highlight_spec_comment: { case highlight_role_t::comment: {
return P(comment); return P(comment);
} }
case highlight_spec_match: { case highlight_role_t::match: {
return P(match); return P(match);
} }
case highlight_spec_search_match: { case highlight_role_t::search_match: {
return P(search_match); return P(search_match);
} }
case highlight_spec_operator: { case highlight_role_t::operat: {
return P(operator); return P(operator);
} }
case highlight_spec_escape: { case highlight_role_t::escape: {
return P(escape); return P(escape);
} }
case highlight_spec_quote: { case highlight_role_t::quote: {
return P(quote); return P(quote);
} }
case highlight_spec_redirection: { case highlight_role_t::redirection: {
return P(redirection); return P(redirection);
} }
case highlight_spec_autosuggestion: { case highlight_role_t::autosuggestion: {
return P(autosuggestion); return P(autosuggestion);
} }
case highlight_spec_selection: { case highlight_role_t::selection: {
return P(selection); return P(selection);
} }
default: { return P(other); } default: { return P(other); }
@ -347,7 +347,7 @@ static std::string html_colorize(const wcstring &text,
assert(colors.size() == text.size()); assert(colors.size() == text.size());
wcstring html = L"<pre><code>"; wcstring html = L"<pre><code>";
highlight_spec_t last_color = highlight_spec_normal; highlight_spec_t last_color = highlight_role_t::normal;
for (size_t i = 0; i < text.size(); i++) { for (size_t i = 0; i < text.size(); i++) {
// Handle colors. // Handle colors.
highlight_spec_t color = colors.at(i); highlight_spec_t color = colors.at(i);

View File

@ -4242,9 +4242,9 @@ static void test_highlighting() {
// Here are the components of our source and the colors we expect those to be. // Here are the components of our source and the colors we expect those to be.
struct highlight_component_t { struct highlight_component_t {
const wchar_t *txt; const wchar_t *txt;
int color; highlight_spec_t color;
bool nospace; bool nospace;
highlight_component_t(const wchar_t *txt, int color, bool nospace = false) highlight_component_t(const wchar_t *txt, highlight_spec_t color, bool nospace = false)
: txt(txt), color(color), nospace(nospace) {} : txt(txt), color(color), nospace(nospace) {}
}; };
const bool ns = true; const bool ns = true;
@ -4252,182 +4252,184 @@ static void test_highlighting() {
using highlight_component_list_t = std::vector<highlight_component_t>; using highlight_component_list_t = std::vector<highlight_component_t>;
std::vector<highlight_component_list_t> highlight_tests; std::vector<highlight_component_list_t> highlight_tests;
highlight_tests.push_back( highlight_spec_t param_valid_path{highlight_role_t::param};
{{L"echo", highlight_spec_command}, param_valid_path.valid_path = true;
{L"test/fish_highlight_test/foo", highlight_spec_param | highlight_modifier_valid_path},
{L"&", highlight_spec_statement_terminator}}); highlight_tests.push_back({{L"echo", highlight_role_t::command},
{L"test/fish_highlight_test/foo", param_valid_path},
{L"&", highlight_role_t::statement_terminator}});
highlight_tests.push_back({ highlight_tests.push_back({
{L"command", highlight_spec_command}, {L"command", highlight_role_t::command},
{L"echo", highlight_spec_command}, {L"echo", highlight_role_t::command},
{L"abc", highlight_spec_param}, {L"abc", highlight_role_t::param},
{L"test/fish_highlight_test/foo", highlight_spec_param | highlight_modifier_valid_path}, {L"test/fish_highlight_test/foo", param_valid_path},
{L"&", highlight_spec_statement_terminator}, {L"&", highlight_role_t::statement_terminator},
}); });
highlight_tests.push_back({ highlight_tests.push_back({
{L"if command ls", highlight_spec_command}, {L"if command ls", highlight_role_t::command},
{L"; ", highlight_spec_statement_terminator}, {L"; ", highlight_role_t::statement_terminator},
{L"echo", highlight_spec_command}, {L"echo", highlight_role_t::command},
{L"abc", highlight_spec_param}, {L"abc", highlight_role_t::param},
{L"; ", highlight_spec_statement_terminator}, {L"; ", highlight_role_t::statement_terminator},
{L"/bin/definitely_not_a_command", highlight_spec_error}, {L"/bin/definitely_not_a_command", highlight_role_t::error},
{L"; ", highlight_spec_statement_terminator}, {L"; ", highlight_role_t::statement_terminator},
{L"end", highlight_spec_command}, {L"end", highlight_role_t::command},
}); });
// Verify that cd shows errors for non-directories. // Verify that cd shows errors for non-directories.
highlight_tests.push_back({ highlight_tests.push_back({
{L"cd", highlight_spec_command}, {L"cd", highlight_role_t::command},
{L"test/fish_highlight_test", highlight_spec_param | highlight_modifier_valid_path}, {L"test/fish_highlight_test", param_valid_path},
}); });
highlight_tests.push_back({ highlight_tests.push_back({
{L"cd", highlight_spec_command}, {L"cd", highlight_role_t::command},
{L"test/fish_highlight_test/foo", highlight_spec_error}, {L"test/fish_highlight_test/foo", highlight_role_t::error},
}); });
highlight_tests.push_back({ highlight_tests.push_back({
{L"cd", highlight_spec_command}, {L"cd", highlight_role_t::command},
{L"--help", highlight_spec_param}, {L"--help", highlight_role_t::param},
{L"-h", highlight_spec_param}, {L"-h", highlight_role_t::param},
{L"definitely_not_a_directory", highlight_spec_error}, {L"definitely_not_a_directory", highlight_role_t::error},
}); });
// Command substitutions. // Command substitutions.
highlight_tests.push_back({ highlight_tests.push_back({
{L"echo", highlight_spec_command}, {L"echo", highlight_role_t::command},
{L"param1", highlight_spec_param}, {L"param1", highlight_role_t::param},
{L"(", highlight_spec_operator}, {L"(", highlight_role_t::operat},
{L"ls", highlight_spec_command}, {L"ls", highlight_role_t::command},
{L"param2", highlight_spec_param}, {L"param2", highlight_role_t::param},
{L")", highlight_spec_operator}, {L")", highlight_role_t::operat},
{L"|", highlight_spec_statement_terminator}, {L"|", highlight_role_t::statement_terminator},
{L"cat", highlight_spec_command}, {L"cat", highlight_role_t::command},
}); });
// Redirections substitutions. // Redirections substitutions.
highlight_tests.push_back({ highlight_tests.push_back({
{L"echo", highlight_spec_command}, {L"echo", highlight_role_t::command},
{L"param1", highlight_spec_param}, {L"param1", highlight_role_t::param},
// Input redirection. // Input redirection.
{L"<", highlight_spec_redirection}, {L"<", highlight_role_t::redirection},
{L"/bin/echo", highlight_spec_redirection}, {L"/bin/echo", highlight_role_t::redirection},
// Output redirection to a valid fd. // Output redirection to a valid fd.
{L"1>&2", highlight_spec_redirection}, {L"1>&2", highlight_role_t::redirection},
// Output redirection to an invalid fd. // Output redirection to an invalid fd.
{L"2>&", highlight_spec_redirection}, {L"2>&", highlight_role_t::redirection},
{L"LOL", highlight_spec_error}, {L"LOL", highlight_role_t::error},
// Just a param, not a redirection. // Just a param, not a redirection.
{L"test/blah", highlight_spec_param}, {L"test/blah", highlight_role_t::param},
// Input redirection from directory. // Input redirection from directory.
{L"<", highlight_spec_redirection}, {L"<", highlight_role_t::redirection},
{L"test/", highlight_spec_error}, {L"test/", highlight_role_t::error},
// Output redirection to an invalid path. // Output redirection to an invalid path.
{L"3>", highlight_spec_redirection}, {L"3>", highlight_role_t::redirection},
{L"/not/a/valid/path/nope", highlight_spec_error}, {L"/not/a/valid/path/nope", highlight_role_t::error},
// Output redirection to directory. // Output redirection to directory.
{L"3>", highlight_spec_redirection}, {L"3>", highlight_role_t::redirection},
{L"test/nope/", highlight_spec_error}, {L"test/nope/", highlight_role_t::error},
// Redirections to overflow fd. // Redirections to overflow fd.
{L"99999999999999999999>&2", highlight_spec_error}, {L"99999999999999999999>&2", highlight_role_t::error},
{L"2>&", highlight_spec_redirection}, {L"2>&", highlight_role_t::redirection},
{L"99999999999999999999", highlight_spec_error}, {L"99999999999999999999", highlight_role_t::error},
// Output redirection containing a command substitution. // Output redirection containing a command substitution.
{L"4>", highlight_spec_redirection}, {L"4>", highlight_role_t::redirection},
{L"(", highlight_spec_operator}, {L"(", highlight_role_t::operat},
{L"echo", highlight_spec_command}, {L"echo", highlight_role_t::command},
{L"test/somewhere", highlight_spec_param}, {L"test/somewhere", highlight_role_t::param},
{L")", highlight_spec_operator}, {L")", highlight_role_t::operat},
// Just another param. // Just another param.
{L"param2", highlight_spec_param}, {L"param2", highlight_role_t::param},
}); });
highlight_tests.push_back({ highlight_tests.push_back({
{L"end", highlight_spec_error}, {L"end", highlight_role_t::error},
{L";", highlight_spec_statement_terminator}, {L";", highlight_role_t::statement_terminator},
{L"if", highlight_spec_command}, {L"if", highlight_role_t::command},
{L"end", highlight_spec_error}, {L"end", highlight_role_t::error},
}); });
highlight_tests.push_back({ highlight_tests.push_back({
{L"echo", highlight_spec_command}, {L"echo", highlight_role_t::command},
{L"'single_quote", highlight_spec_error}, {L"'single_quote", highlight_role_t::error},
}); });
highlight_tests.push_back({ highlight_tests.push_back({
{L"echo", highlight_spec_command}, {L"echo", highlight_role_t::command},
{L"$foo", highlight_spec_operator}, {L"$foo", highlight_role_t::operat},
{L"\"", highlight_spec_quote}, {L"\"", highlight_role_t::quote},
{L"$bar", highlight_spec_operator}, {L"$bar", highlight_role_t::operat},
{L"\"", highlight_spec_quote}, {L"\"", highlight_role_t::quote},
{L"$baz[", highlight_spec_operator}, {L"$baz[", highlight_role_t::operat},
{L"1 2..3", highlight_spec_param}, {L"1 2..3", highlight_role_t::param},
{L"]", highlight_spec_operator}, {L"]", highlight_role_t::operat},
}); });
highlight_tests.push_back({ highlight_tests.push_back({
{L"for", highlight_spec_command}, {L"for", highlight_role_t::command},
{L"i", highlight_spec_param}, {L"i", highlight_role_t::param},
{L"in", highlight_spec_command}, {L"in", highlight_role_t::command},
{L"1 2 3", highlight_spec_param}, {L"1 2 3", highlight_role_t::param},
{L";", highlight_spec_statement_terminator}, {L";", highlight_role_t::statement_terminator},
{L"end", highlight_spec_command}, {L"end", highlight_role_t::command},
}); });
highlight_tests.push_back({ highlight_tests.push_back({
{L"echo", highlight_spec_command}, {L"echo", highlight_role_t::command},
{L"$$foo[", highlight_spec_operator}, {L"$$foo[", highlight_role_t::operat},
{L"1", highlight_spec_param}, {L"1", highlight_role_t::param},
{L"][", highlight_spec_operator}, {L"][", highlight_role_t::operat},
{L"2", highlight_spec_param}, {L"2", highlight_role_t::param},
{L"]", highlight_spec_operator}, {L"]", highlight_role_t::operat},
{L"[3]", highlight_spec_param}, // two dollar signs, so last one is not an expansion {L"[3]", highlight_role_t::param}, // two dollar signs, so last one is not an expansion
}); });
highlight_tests.push_back({ highlight_tests.push_back({
{L"cat", highlight_spec_command}, {L"cat", highlight_role_t::command},
{L"/dev/null", highlight_spec_param}, {L"/dev/null", highlight_role_t::param},
{L"|", highlight_spec_statement_terminator}, {L"|", highlight_role_t::statement_terminator},
// This is bogus, but we used to use "less" here and that doesn't have to be installed. // This is bogus, but we used to use "less" here and that doesn't have to be installed.
{L"cat", highlight_spec_command}, {L"cat", highlight_role_t::command},
{L"2>", highlight_spec_redirection}, {L"2>", highlight_role_t::redirection},
}); });
highlight_tests.push_back({ highlight_tests.push_back({
{L"if", highlight_spec_command}, {L"if", highlight_role_t::command},
{L"true", highlight_spec_command}, {L"true", highlight_role_t::command},
{L"&&", highlight_spec_operator}, {L"&&", highlight_role_t::operat},
{L"false", highlight_spec_command}, {L"false", highlight_role_t::command},
{L";", highlight_spec_statement_terminator}, {L";", highlight_role_t::statement_terminator},
{L"or", highlight_spec_operator}, {L"or", highlight_role_t::operat},
{L"false", highlight_spec_command}, {L"false", highlight_role_t::command},
{L"||", highlight_spec_operator}, {L"||", highlight_role_t::operat},
{L"true", highlight_spec_command}, {L"true", highlight_role_t::command},
{L";", highlight_spec_statement_terminator}, {L";", highlight_role_t::statement_terminator},
{L"and", highlight_spec_operator}, {L"and", highlight_role_t::operat},
{L"not", highlight_spec_operator}, {L"not", highlight_role_t::operat},
{L"!", highlight_spec_operator}, {L"!", highlight_role_t::operat},
{L"true", highlight_spec_command}, {L"true", highlight_role_t::command},
{L";", highlight_spec_statement_terminator}, {L";", highlight_role_t::statement_terminator},
{L"end", highlight_spec_command}, {L"end", highlight_role_t::command},
}); });
highlight_tests.push_back({ highlight_tests.push_back({
{L"echo", highlight_spec_command}, {L"echo", highlight_role_t::command},
{L"%self", highlight_spec_operator}, {L"%self", highlight_role_t::operat},
{L"not%self", highlight_spec_param}, {L"not%self", highlight_role_t::param},
{L"self%not", highlight_spec_param}, {L"self%not", highlight_role_t::param},
}); });
auto &vars = parser_t::principal_parser().vars(); auto &vars = parser_t::principal_parser().vars();
@ -4435,21 +4437,21 @@ static void test_highlighting() {
vars.set(L"VARIABLE_IN_COMMAND", ENV_LOCAL, {L"a"}); vars.set(L"VARIABLE_IN_COMMAND", ENV_LOCAL, {L"a"});
vars.set(L"VARIABLE_IN_COMMAND2", ENV_LOCAL, {L"at"}); vars.set(L"VARIABLE_IN_COMMAND2", ENV_LOCAL, {L"at"});
highlight_tests.push_back( highlight_tests.push_back(
{{L"/bin/ca", highlight_spec_command, ns}, {L"*", highlight_spec_operator, ns}}); {{L"/bin/ca", highlight_role_t::command, ns}, {L"*", highlight_role_t::operat, ns}});
highlight_tests.push_back({{L"/bin/c", highlight_spec_command, ns}, highlight_tests.push_back({{L"/bin/c", highlight_role_t::command, ns},
{L"{$VARIABLE_IN_COMMAND}", highlight_spec_operator, ns}, {L"{$VARIABLE_IN_COMMAND}", highlight_role_t::operat, ns},
{L"*", highlight_spec_operator, ns}}); {L"*", highlight_role_t::operat, ns}});
highlight_tests.push_back({{L"/bin/c", highlight_spec_command, ns}, highlight_tests.push_back({{L"/bin/c", highlight_role_t::command, ns},
{L"{$VARIABLE_IN_COMMAND}", highlight_spec_operator, ns}, {L"{$VARIABLE_IN_COMMAND}", highlight_role_t::operat, ns},
{L"*", highlight_spec_operator, ns}}); {L"*", highlight_role_t::operat, ns}});
highlight_tests.push_back({{L"/bin/c", highlight_spec_command, ns}, highlight_tests.push_back({{L"/bin/c", highlight_role_t::command, ns},
{L"$VARIABLE_IN_COMMAND2", highlight_spec_operator, ns}}); {L"$VARIABLE_IN_COMMAND2", highlight_role_t::operat, ns}});
highlight_tests.push_back({{L"$EMPTY_VARIABLE", highlight_spec_error}}); highlight_tests.push_back({{L"$EMPTY_VARIABLE", highlight_role_t::error}});
highlight_tests.push_back({{L"\"$EMPTY_VARIABLE\"", highlight_spec_error}}); highlight_tests.push_back({{L"\"$EMPTY_VARIABLE\"", highlight_role_t::error}});
for (const highlight_component_list_t &components : highlight_tests) { for (const highlight_component_list_t &components : highlight_tests) {
// Generate the text. // Generate the text.
@ -4458,7 +4460,7 @@ static void test_highlighting() {
for (const highlight_component_t &comp : components) { for (const highlight_component_t &comp : components) {
if (!text.empty() && !comp.nospace) { if (!text.empty() && !comp.nospace) {
text.push_back(L' '); text.push_back(L' ');
expected_colors.push_back(0); expected_colors.push_back(highlight_spec_t{});
} }
text.append(comp.txt); text.append(comp.txt);
expected_colors.resize(text.size(), comp.color); expected_colors.resize(text.size(), comp.color);

View File

@ -39,71 +39,127 @@ namespace g = grammar;
#define CURSOR_POSITION_INVALID ((size_t)(-1)) #define CURSOR_POSITION_INVALID ((size_t)(-1))
/// Number of elements in the highlight_var array. static const wchar_t *get_highlight_var_name(highlight_role_t role) {
#define VAR_COUNT (sizeof(highlight_var) / sizeof(wchar_t *)) switch (role) {
static const wchar_t *const highlight_var[] = { case highlight_role_t::normal:
[highlight_spec_normal] = L"fish_color_normal", return L"fish_color_normal";
[highlight_spec_error] = L"fish_color_error", case highlight_role_t::error:
[highlight_spec_command] = L"fish_color_command", return L"fish_color_error";
[highlight_spec_statement_terminator] = L"fish_color_end", case highlight_role_t::command:
[highlight_spec_param] = L"fish_color_param", return L"fish_color_command";
[highlight_spec_comment] = L"fish_color_comment", case highlight_role_t::statement_terminator:
[highlight_spec_match] = L"fish_color_match", return L"fish_color_end";
[highlight_spec_search_match] = L"fish_color_search_match", case highlight_role_t::param:
[highlight_spec_operator] = L"fish_color_operator", return L"fish_color_param";
[highlight_spec_escape] = L"fish_color_escape", case highlight_role_t::comment:
[highlight_spec_quote] = L"fish_color_quote", return L"fish_color_comment";
[highlight_spec_redirection] = L"fish_color_redirection", case highlight_role_t::match:
[highlight_spec_autosuggestion] = L"fish_color_autosuggestion", return L"fish_color_match";
[highlight_spec_selection] = L"fish_color_selection", case highlight_role_t::search_match:
[highlight_spec_pager_progress] = L"fish_pager_color_progress", return L"fish_color_search_match";
[highlight_spec_pager_background] = L"fish_pager_color_background", case highlight_role_t::operat:
[highlight_spec_pager_prefix] = L"fish_pager_color_prefix", return L"fish_color_operator";
[highlight_spec_pager_completion] = L"fish_pager_color_completion", case highlight_role_t::escape:
[highlight_spec_pager_description] = L"fish_pager_color_description", return L"fish_color_escape";
[highlight_spec_pager_secondary_background] = L"fish_pager_color_secondary_background", case highlight_role_t::quote:
[highlight_spec_pager_secondary_prefix] = L"fish_pager_color_secondary_prefix", return L"fish_color_quote";
[highlight_spec_pager_secondary_completion] = L"fish_pager_color_secondary_completion", case highlight_role_t::redirection:
[highlight_spec_pager_secondary_description] = L"fish_pager_color_secondary_description", return L"fish_color_redirection";
[highlight_spec_pager_selected_background] = L"fish_pager_color_selected_background", case highlight_role_t::autosuggestion:
[highlight_spec_pager_selected_prefix] = L"fish_pager_color_selected_prefix", return L"fish_color_autosuggestion";
[highlight_spec_pager_selected_completion] = L"fish_pager_color_selected_completion", case highlight_role_t::selection:
[highlight_spec_pager_selected_description] = L"fish_pager_color_selected_description", return L"fish_color_selection";
}; case highlight_role_t::pager_progress:
static_assert(VAR_COUNT == HIGHLIGHT_SPEC_MAX, "Every color spec has a corresponding env var"); return L"fish_pager_color_progress";
case highlight_role_t::pager_background:
return L"fish_pager_color_background";
case highlight_role_t::pager_prefix:
return L"fish_pager_color_prefix";
case highlight_role_t::pager_completion:
return L"fish_pager_color_completion";
case highlight_role_t::pager_description:
return L"fish_pager_color_description";
case highlight_role_t::pager_secondary_background:
return L"fish_pager_color_secondary_background";
case highlight_role_t::pager_secondary_prefix:
return L"fish_pager_color_secondary_prefix";
case highlight_role_t::pager_secondary_completion:
return L"fish_pager_color_secondary_completion";
case highlight_role_t::pager_secondary_description:
return L"fish_pager_color_secondary_description";
case highlight_role_t::pager_selected_background:
return L"fish_pager_color_selected_background";
case highlight_role_t::pager_selected_prefix:
return L"fish_pager_color_selected_prefix";
case highlight_role_t::pager_selected_completion:
return L"fish_pager_color_selected_completion";
case highlight_role_t::pager_selected_description:
return L"fish_pager_color_selected_description";
}
DIE("invalid highlight role");
}
// Table used to fetch fallback highlights in case the specified one // Table used to fetch fallback highlights in case the specified one
// wasn't set. // wasn't set.
static const highlight_spec_t fallbacks[] = { static highlight_role_t get_fallback(highlight_role_t role) {
[highlight_spec_normal] = highlight_spec_normal, switch (role) {
[highlight_spec_error] = highlight_spec_normal, case highlight_role_t::normal:
[highlight_spec_command] = highlight_spec_normal, return highlight_role_t::normal;
[highlight_spec_statement_terminator] = highlight_spec_normal, case highlight_role_t::error:
[highlight_spec_param] = highlight_spec_normal, return highlight_role_t::normal;
[highlight_spec_comment] = highlight_spec_normal, case highlight_role_t::command:
[highlight_spec_match] = highlight_spec_normal, return highlight_role_t::normal;
[highlight_spec_search_match] = highlight_spec_normal, case highlight_role_t::statement_terminator:
[highlight_spec_operator] = highlight_spec_normal, return highlight_role_t::normal;
[highlight_spec_escape] = highlight_spec_normal, case highlight_role_t::param:
[highlight_spec_quote] = highlight_spec_normal, return highlight_role_t::normal;
[highlight_spec_redirection] = highlight_spec_normal, case highlight_role_t::comment:
[highlight_spec_autosuggestion] = highlight_spec_normal, return highlight_role_t::normal;
[highlight_spec_selection] = highlight_spec_normal, case highlight_role_t::match:
[highlight_spec_pager_progress] = highlight_spec_normal, return highlight_role_t::normal;
[highlight_spec_pager_background] = highlight_spec_normal, case highlight_role_t::search_match:
[highlight_spec_pager_prefix] = highlight_spec_normal, return highlight_role_t::normal;
[highlight_spec_pager_completion] = highlight_spec_normal, case highlight_role_t::operat:
[highlight_spec_pager_description] = highlight_spec_normal, return highlight_role_t::normal;
[highlight_spec_pager_secondary_background] = highlight_spec_pager_background, case highlight_role_t::escape:
[highlight_spec_pager_secondary_prefix] = highlight_spec_pager_prefix, return highlight_role_t::normal;
[highlight_spec_pager_secondary_completion] = highlight_spec_pager_completion, case highlight_role_t::quote:
[highlight_spec_pager_secondary_description] = highlight_spec_pager_description, return highlight_role_t::normal;
[highlight_spec_pager_selected_background] = highlight_spec_search_match, case highlight_role_t::redirection:
[highlight_spec_pager_selected_prefix] = highlight_spec_pager_prefix, return highlight_role_t::normal;
[highlight_spec_pager_selected_completion] = highlight_spec_pager_completion, case highlight_role_t::autosuggestion:
[highlight_spec_pager_selected_description] = highlight_spec_pager_description, return highlight_role_t::normal;
}; case highlight_role_t::selection:
static_assert(sizeof(fallbacks) / sizeof(fallbacks[0]) == HIGHLIGHT_SPEC_MAX, "No missing fallbacks"); return highlight_role_t::normal;
case highlight_role_t::pager_progress:
return highlight_role_t::normal;
case highlight_role_t::pager_background:
return highlight_role_t::normal;
case highlight_role_t::pager_prefix:
return highlight_role_t::normal;
case highlight_role_t::pager_completion:
return highlight_role_t::normal;
case highlight_role_t::pager_description:
return highlight_role_t::normal;
case highlight_role_t::pager_secondary_background:
return highlight_role_t::pager_background;
case highlight_role_t::pager_secondary_prefix:
return highlight_role_t::pager_prefix;
case highlight_role_t::pager_secondary_completion:
return highlight_role_t::pager_completion;
case highlight_role_t::pager_secondary_description:
return highlight_role_t::pager_description;
case highlight_role_t::pager_selected_background:
return highlight_role_t::search_match;
case highlight_role_t::pager_selected_prefix:
return highlight_role_t::pager_prefix;
case highlight_role_t::pager_selected_completion:
return highlight_role_t::pager_completion;
case highlight_role_t::pager_selected_description:
return highlight_role_t::pager_description;
}
DIE("invalid highlight role");
}
/// Determine if the filesystem containing the given fd is case insensitive for lookups regardless /// Determine if the filesystem containing the given fd is case insensitive for lookups regardless
/// of whether it preserves the case when saving a pathname. /// of whether it preserves the case when saving a pathname.
@ -292,30 +348,19 @@ static bool plain_statement_get_expanded_command(const wcstring &src,
return err == EXPAND_OK || err == EXPAND_WILDCARD_MATCH; return err == EXPAND_OK || err == EXPAND_WILDCARD_MATCH;
} }
rgb_color_t highlight_get_color(highlight_spec_t highlight, bool is_background) { rgb_color_t highlight_get_color(const highlight_spec_t &highlight, bool is_background) {
// TODO: rationalize this principal_vars. // TODO: rationalize this principal_vars.
const auto &vars = env_stack_t::principal(); const auto &vars = env_stack_t::principal();
rgb_color_t result = rgb_color_t::normal(); rgb_color_t result = rgb_color_t::normal();
highlight_role_t role = is_background ? highlight.background : highlight.foreground;
bool treat_as_background = is_background; auto var = vars.get(get_highlight_var_name(role));
if (!var) var = vars.get(get_highlight_var_name(get_fallback(role)));
// Get the primary variable. if (!var) var = vars.get(get_highlight_var_name(highlight_role_t::normal));
size_t idx = highlight_get_primary(highlight); if (var) result = parse_color(*var, is_background);
if (idx >= VAR_COUNT) {
return rgb_color_t::normal();
}
auto var = vars.get(highlight_var[idx]);
// debug( 1, L"%d -> %d -> %ls", highlight, idx, val );
if (!var) var = vars.get(highlight_var[fallbacks[idx]]);
if (!var) var = vars.get(highlight_var[0]);
if (var) result = parse_color(*var, treat_as_background);
// Handle modifiers. // Handle modifiers.
if (highlight & highlight_modifier_valid_path) { if (!is_background && highlight.valid_path) {
auto var2 = vars.get(L"fish_color_valid_path"); auto var2 = vars.get(L"fish_color_valid_path");
if (var2) { if (var2) {
rgb_color_t result2 = parse_color(*var2, is_background); rgb_color_t result2 = parse_color(*var2, is_background);
@ -331,7 +376,7 @@ rgb_color_t highlight_get_color(highlight_spec_t highlight, bool is_background)
} }
} }
if (highlight & highlight_modifier_force_underline) { if (!is_background && highlight.force_underline) {
result.set_underline(true); result.set_underline(true);
} }
@ -435,9 +480,9 @@ static size_t color_variable(const wchar_t *in, size_t in_len,
// Our color depends on the next char. // Our color depends on the next char.
wchar_t next = in[idx + 1]; wchar_t next = in[idx + 1];
if (next == L'$' || valid_var_name_char(next)) { if (next == L'$' || valid_var_name_char(next)) {
colors[idx] = highlight_spec_operator; colors[idx] = highlight_role_t::operat;
} else { } else {
colors[idx] = highlight_spec_error; colors[idx] = highlight_role_t::error;
} }
idx++; idx++;
dollar_count++; dollar_count++;
@ -445,7 +490,7 @@ static size_t color_variable(const wchar_t *in, size_t in_len,
// Handle a sequence of variable characters. // Handle a sequence of variable characters.
while (valid_var_name_char(in[idx])) { while (valid_var_name_char(in[idx])) {
colors[idx++] = highlight_spec_operator; colors[idx++] = highlight_role_t::operat;
} }
// Handle a slice, up to dollar_count of them. Note that we currently don't do any validation of // Handle a slice, up to dollar_count of them. Note that we currently don't do any validation of
@ -456,8 +501,8 @@ static size_t color_variable(const wchar_t *in, size_t in_len,
if (located == 1) { if (located == 1) {
size_t slice_begin_idx = slice_begin - in, slice_end_idx = slice_end - in; size_t slice_begin_idx = slice_begin - in, slice_end_idx = slice_end - in;
assert(slice_end_idx > slice_begin_idx); assert(slice_end_idx > slice_begin_idx);
colors[slice_begin_idx] = highlight_spec_operator; colors[slice_begin_idx] = highlight_role_t::operat;
colors[slice_end_idx] = highlight_spec_operator; colors[slice_end_idx] = highlight_role_t::operat;
idx = slice_end_idx + 1; idx = slice_end_idx + 1;
} else if (located == 0) { } else if (located == 0) {
// not a slice // not a slice
@ -468,7 +513,7 @@ static size_t color_variable(const wchar_t *in, size_t in_len,
// double-quoted string that doesn't happen. As such, color the variable + the slice // double-quoted string that doesn't happen. As such, color the variable + the slice
// start red. Coloring any more than that looks bad, unless we're willing to try and // start red. Coloring any more than that looks bad, unless we're willing to try and
// detect where the double-quoted string ends, and I'd rather not do that. // detect where the double-quoted string ends, and I'd rather not do that.
std::fill(colors, colors + idx + 1, (highlight_spec_t)highlight_spec_error); std::fill(colors, colors + idx + 1, highlight_role_t::error);
break; break;
} }
} }
@ -480,14 +525,14 @@ 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, static void color_string_internal(const wcstring &buffstr, highlight_spec_t base_color,
std::vector<highlight_spec_t>::iterator colors) { std::vector<highlight_spec_t>::iterator colors) {
// Clarify what we expect. // Clarify what we expect.
assert((base_color == highlight_spec_param || base_color == highlight_spec_command) && assert((base_color == highlight_role_t::param || base_color == highlight_role_t::command) &&
"Unexpected base color"); "Unexpected base color");
const size_t buff_len = buffstr.size(); const size_t buff_len = buffstr.size();
std::fill(colors, colors + buff_len, base_color); std::fill(colors, colors + buff_len, base_color);
// Hacky support for %self which must be an unquoted literal argument. // Hacky support for %self which must be an unquoted literal argument.
if (buffstr == PROCESS_EXPAND_SELF_STR) { if (buffstr == PROCESS_EXPAND_SELF_STR) {
std::fill_n(colors, wcslen(PROCESS_EXPAND_SELF_STR), highlight_spec_operator); std::fill_n(colors, wcslen(PROCESS_EXPAND_SELF_STR), highlight_role_t::operat);
return; return;
} }
@ -498,7 +543,7 @@ static void color_string_internal(const wcstring &buffstr, highlight_spec_t base
switch (mode) { switch (mode) {
case e_unquoted: { case e_unquoted: {
if (c == L'\\') { if (c == L'\\') {
int fill_color = highlight_spec_escape; // may be set to highlight_error auto fill_color = highlight_role_t::escape; // may be set to highlight_error
const size_t backslash_pos = in_pos; const size_t backslash_pos = in_pos;
size_t fill_end = backslash_pos; size_t fill_end = backslash_pos;
@ -508,7 +553,7 @@ static void color_string_internal(const wcstring &buffstr, highlight_spec_t base
if (escaped_char == L'\0') { if (escaped_char == L'\0') {
fill_end = in_pos; fill_end = in_pos;
fill_color = highlight_spec_error; fill_color = highlight_role_t::error;
} else if (wcschr(L"~%", escaped_char)) { } else if (wcschr(L"~%", escaped_char)) {
if (in_pos == 1) { if (in_pos == 1) {
fill_end = in_pos + 1; fill_end = in_pos + 1;
@ -571,7 +616,7 @@ static void color_string_internal(const wcstring &buffstr, highlight_spec_t base
fill_end = in_pos; fill_end = in_pos;
// It's an error if we exceeded the max value. // It's an error if we exceeded the max value.
if (res > max_val) fill_color = highlight_spec_error; if (res > max_val) fill_color = highlight_role_t::error;
// Subtract one from in_pos, so that the increment in the loop will move to // Subtract one from in_pos, so that the increment in the loop will move to
// the next character. // the next character.
@ -584,7 +629,7 @@ static void color_string_internal(const wcstring &buffstr, highlight_spec_t base
switch (c) { switch (c) {
case L'~': { case L'~': {
if (in_pos == 0) { if (in_pos == 0) {
colors[in_pos] = highlight_spec_operator; colors[in_pos] = highlight_role_t::operat;
} }
break; break;
} }
@ -598,39 +643,39 @@ static void color_string_internal(const wcstring &buffstr, highlight_spec_t base
} }
case L'?': { case L'?': {
if (!feature_test(features_t::qmark_noglob)) { if (!feature_test(features_t::qmark_noglob)) {
colors[in_pos] = highlight_spec_operator; colors[in_pos] = highlight_role_t::operat;
} }
break; break;
} }
case L'*': case L'*':
case L'(': case L'(':
case L')': { case L')': {
colors[in_pos] = highlight_spec_operator; colors[in_pos] = highlight_role_t::operat;
break; break;
} }
case L'{': { case L'{': {
colors[in_pos] = highlight_spec_operator; colors[in_pos] = highlight_role_t::operat;
bracket_count++; bracket_count++;
break; break;
} }
case L'}': { case L'}': {
colors[in_pos] = highlight_spec_operator; colors[in_pos] = highlight_role_t::operat;
bracket_count--; bracket_count--;
break; break;
} }
case L',': { case L',': {
if (bracket_count > 0) { if (bracket_count > 0) {
colors[in_pos] = highlight_spec_operator; colors[in_pos] = highlight_role_t::operat;
} }
break; break;
} }
case L'\'': { case L'\'': {
colors[in_pos] = highlight_spec_quote; colors[in_pos] = highlight_role_t::quote;
mode = e_single_quoted; mode = e_single_quoted;
break; break;
} }
case L'\"': { case L'\"': {
colors[in_pos] = highlight_spec_quote; colors[in_pos] = highlight_role_t::quote;
mode = e_double_quoted; mode = e_double_quoted;
break; break;
} }
@ -643,14 +688,14 @@ static void color_string_internal(const wcstring &buffstr, highlight_spec_t base
} }
// Mode 1 means single quoted string, i.e 'foo'. // Mode 1 means single quoted string, i.e 'foo'.
case e_single_quoted: { case e_single_quoted: {
colors[in_pos] = highlight_spec_quote; colors[in_pos] = highlight_role_t::quote;
if (c == L'\\') { if (c == L'\\') {
// backslash // backslash
if (in_pos + 1 < buff_len) { if (in_pos + 1 < buff_len) {
const wchar_t escaped_char = buffstr.at(in_pos + 1); const wchar_t escaped_char = buffstr.at(in_pos + 1);
if (escaped_char == L'\\' || escaped_char == L'\'') { if (escaped_char == L'\\' || escaped_char == L'\'') {
colors[in_pos] = highlight_spec_escape; // backslash colors[in_pos] = highlight_role_t::escape; // backslash
colors[in_pos + 1] = highlight_spec_escape; // escaped char colors[in_pos + 1] = highlight_role_t::escape; // escaped char
in_pos += 1; // skip over backslash in_pos += 1; // skip over backslash
} }
} }
@ -664,7 +709,7 @@ static void color_string_internal(const wcstring &buffstr, highlight_spec_t base
// Slices are colored in advance, past `in_pos`, and we don't want to overwrite // Slices are colored in advance, past `in_pos`, and we don't want to overwrite
// that. // that.
if (colors[in_pos] == base_color) { if (colors[in_pos] == base_color) {
colors[in_pos] = highlight_spec_quote; colors[in_pos] = highlight_role_t::quote;
} }
switch (c) { switch (c) {
case L'"': { case L'"': {
@ -676,8 +721,8 @@ static void color_string_internal(const wcstring &buffstr, highlight_spec_t base
if (in_pos + 1 < buff_len) { if (in_pos + 1 < buff_len) {
const wchar_t escaped_char = buffstr.at(in_pos + 1); const wchar_t escaped_char = buffstr.at(in_pos + 1);
if (wcschr(L"\\\"\n$", escaped_char)) { if (wcschr(L"\\\"\n$", escaped_char)) {
colors[in_pos] = highlight_spec_escape; // backslash colors[in_pos] = highlight_role_t::escape; // backslash
colors[in_pos + 1] = highlight_spec_escape; // escaped char colors[in_pos + 1] = highlight_role_t::escape; // escaped char
in_pos += 1; // skip over backslash in_pos += 1; // skip over backslash
} }
} }
@ -778,7 +823,7 @@ void highlighter_t::color_command(tnode_t<g::tok_string> node) {
// Get an iterator to the colors associated with the argument. // Get an iterator to the colors associated with the argument.
const size_t arg_start = source_range->start; const size_t arg_start = source_range->start;
const color_array_t::iterator colors = color_array.begin() + arg_start; const color_array_t::iterator colors = color_array.begin() + arg_start;
color_string_internal(cmd_str, highlight_spec_command, colors); color_string_internal(cmd_str, highlight_role_t::command, colors);
} }
// node does not necessarily have type symbol_argument here. // node does not necessarily have type symbol_argument here.
@ -793,7 +838,7 @@ void highlighter_t::color_argument(tnode_t<g::tok_string> node) {
const color_array_t::iterator arg_colors = color_array.begin() + arg_start; const color_array_t::iterator arg_colors = color_array.begin() + arg_start;
// Color this argument without concern for command substitutions. // Color this argument without concern for command substitutions.
color_string_internal(arg_str, highlight_spec_param, arg_colors); color_string_internal(arg_str, highlight_role_t::param, arg_colors);
// Now do command substitutions. // Now do command substitutions.
size_t cmdsub_cursor = 0, cmdsub_start = 0, cmdsub_end = 0; size_t cmdsub_cursor = 0, cmdsub_start = 0, cmdsub_end = 0;
@ -814,9 +859,9 @@ void highlighter_t::color_argument(tnode_t<g::tok_string> node) {
// Highlight the parens. The open paren must exist; the closed paren may not if it was // Highlight the parens. The open paren must exist; the closed paren may not if it was
// incomplete. // incomplete.
assert(cmdsub_start < arg_str.size()); assert(cmdsub_start < arg_str.size());
this->color_array.at(arg_subcmd_start) = highlight_spec_operator; this->color_array.at(arg_subcmd_start) = highlight_role_t::operat;
if (arg_subcmd_end < this->buff.size()) if (arg_subcmd_end < this->buff.size())
this->color_array.at(arg_subcmd_end) = highlight_spec_operator; this->color_array.at(arg_subcmd_end) = highlight_role_t::operat;
// Compute the cursor's position within the cmdsub. We must be past the open paren (hence >) // Compute the cursor's position within the cmdsub. We must be past the open paren (hence >)
// but can be at the end of the string or closed paren (hence <=). // but can be at the end of the string or closed paren (hence <=).
@ -886,7 +931,7 @@ void highlighter_t::color_arguments(const std::vector<tnode_t<g::argument>> &arg
string_prefixes_string(param, L"-h"); string_prefixes_string(param, L"-h");
if (!is_help && this->io_ok && if (!is_help && this->io_ok &&
!is_potential_cd_path(param, working_directory, vars, PATH_EXPAND_TILDE)) { !is_potential_cd_path(param, working_directory, vars, PATH_EXPAND_TILDE)) {
this->color_node(arg, highlight_spec_error); this->color_node(arg, highlight_role_t::error);
} }
} }
} }
@ -905,7 +950,7 @@ void highlighter_t::color_redirection(tnode_t<g::redirection> redirection_node)
redirection_type(redirection_node, this->buff, nullptr, &target); redirection_type(redirection_node, this->buff, nullptr, &target);
// We may get a missing redirection type if the redirection is invalid. // We may get a missing redirection type if the redirection is invalid.
auto hl = redirect_type ? highlight_spec_redirection : highlight_spec_error; auto hl = redirect_type ? highlight_role_t::redirection : highlight_role_t::error;
this->color_node(redir_prim, hl); this->color_node(redir_prim, hl);
// Check if the argument contains a command substitution. If so, highlight it as a param // Check if the argument contains a command substitution. If so, highlight it as a param
@ -999,7 +1044,7 @@ void highlighter_t::color_redirection(tnode_t<g::redirection> redirection_node)
} }
if (redir_target) { if (redir_target) {
auto hl = target_is_valid ? highlight_spec_redirection : highlight_spec_error; auto hl = target_is_valid ? highlight_role_t::redirection : highlight_role_t::error;
this->color_node(redir_target, hl); this->color_node(redir_target, hl);
} }
} }
@ -1080,7 +1125,7 @@ const highlighter_t::color_array_t &highlighter_t::highlight() {
if (length == 0) return color_array; if (length == 0) return color_array;
// Start out at zero. // Start out at zero.
std::fill(this->color_array.begin(), this->color_array.end(), 0); std::fill(this->color_array.begin(), this->color_array.end(), highlight_spec_t{});
// Walk the node tree. // Walk the node tree.
for (const parse_node_t &node : parse_tree) { for (const parse_node_t &node : parse_tree) {
@ -1094,15 +1139,15 @@ const highlighter_t::color_array_t &highlighter_t::highlight() {
case symbol_case_item: case symbol_case_item:
case symbol_decorated_statement: case symbol_decorated_statement:
case symbol_if_statement: { case symbol_if_statement: {
this->color_children(node, parse_token_type_string, highlight_spec_command); this->color_children(node, parse_token_type_string, highlight_role_t::command);
break; break;
} }
case symbol_switch_statement: { case symbol_switch_statement: {
tnode_t<g::switch_statement> switchn(&parse_tree, &node); tnode_t<g::switch_statement> switchn(&parse_tree, &node);
auto literal_switch = switchn.child<0>(); auto literal_switch = switchn.child<0>();
auto switch_arg = switchn.child<1>(); auto switch_arg = switchn.child<1>();
this->color_node(literal_switch, highlight_spec_command); this->color_node(literal_switch, highlight_role_t::command);
this->color_node(switch_arg, highlight_spec_param); this->color_node(switch_arg, highlight_role_t::param);
break; break;
} }
case symbol_for_header: { case symbol_for_header: {
@ -1110,8 +1155,8 @@ const highlighter_t::color_array_t &highlighter_t::highlight() {
// Color the 'for' and 'in' as commands. // Color the 'for' and 'in' as commands.
auto literal_for = fhead.child<0>(); auto literal_for = fhead.child<0>();
auto literal_in = fhead.child<2>(); auto literal_in = fhead.child<2>();
this->color_node(literal_for, highlight_spec_command); this->color_node(literal_for, highlight_role_t::command);
this->color_node(literal_in, highlight_spec_command); this->color_node(literal_in, highlight_role_t::command);
// Color the variable name as a parameter. // Color the variable name as a parameter.
this->color_argument(fhead.child<1>()); this->color_argument(fhead.child<1>());
@ -1120,22 +1165,22 @@ const highlighter_t::color_array_t &highlighter_t::highlight() {
case parse_token_type_andand: case parse_token_type_andand:
case parse_token_type_oror: case parse_token_type_oror:
this->color_node(node, highlight_spec_operator); this->color_node(node, highlight_role_t::operat);
break; break;
case symbol_not_statement: case symbol_not_statement:
this->color_children(node, parse_token_type_string, highlight_spec_operator); this->color_children(node, parse_token_type_string, highlight_role_t::operat);
break; break;
case symbol_job_decorator: case symbol_job_decorator:
this->color_node(node, highlight_spec_operator); this->color_node(node, highlight_role_t::operat);
break; break;
case parse_token_type_pipe: case parse_token_type_pipe:
case parse_token_type_background: case parse_token_type_background:
case parse_token_type_end: case parse_token_type_end:
case symbol_optional_background: { case symbol_optional_background: {
this->color_node(node, highlight_spec_statement_terminator); this->color_node(node, highlight_role_t::statement_terminator);
break; break;
} }
case symbol_plain_statement: { case symbol_plain_statement: {
@ -1166,7 +1211,7 @@ const highlighter_t::color_array_t &highlighter_t::highlight() {
} }
} }
if (!is_valid_cmd) { if (!is_valid_cmd) {
this->color_node(*cmd_node, highlight_spec_error); this->color_node(*cmd_node, highlight_role_t::error);
} else { } else {
this->color_command(cmd_node); this->color_command(cmd_node);
} }
@ -1190,16 +1235,16 @@ const highlighter_t::color_array_t &highlighter_t::highlight() {
break; break;
} }
case symbol_end_command: { case symbol_end_command: {
this->color_node(node, highlight_spec_command); this->color_node(node, highlight_role_t::command);
break; break;
} }
case parse_special_type_parse_error: case parse_special_type_parse_error:
case parse_special_type_tokenizer_error: { case parse_special_type_tokenizer_error: {
this->color_node(node, highlight_spec_error); this->color_node(node, highlight_role_t::error);
break; break;
} }
case parse_special_type_comment: { case parse_special_type_comment: {
this->color_node(node, highlight_spec_comment); this->color_node(node, highlight_role_t::comment);
break; break;
} }
default: { break; } default: { break; }
@ -1225,10 +1270,10 @@ const highlighter_t::color_array_t &highlighter_t::highlight() {
node_is_potential_path(buff, node, vars, working_directory)) { node_is_potential_path(buff, node, vars, working_directory)) {
// It is, underline it. // It is, underline it.
for (size_t i = node.source_start; i < node.source_start + node.source_length; i++) { for (size_t i = node.source_start; i < node.source_start + node.source_length; i++) {
// Don't color highlight_spec_error because it looks dorky. For example, // Don't color highlight_role_t::error because it looks dorky. For example,
// trying to cd into a non-directory would show an underline and also red. // trying to cd into a non-directory would show an underline and also red.
if (highlight_get_primary(this->color_array.at(i)) != highlight_spec_error) { if (this->color_array.at(i).foreground != highlight_role_t::error) {
this->color_array.at(i) |= highlight_modifier_valid_path; this->color_array.at(i).valid_path = true;
} }
} }
} }
@ -1295,10 +1340,8 @@ static void highlight_universal_internal(const wcstring &buffstr,
pos1 = lst.back(); pos1 = lst.back();
pos2 = str - buff; pos2 = str - buff;
if (pos1 == pos || pos2 == pos) { if (pos1 == pos || pos2 == pos) {
color.at(pos1) |= color.at(pos1).background = highlight_role_t::match;
highlight_make_background(highlight_spec_match); color.at(pos2).background = highlight_role_t::match;
color.at(pos2) |=
highlight_make_background(highlight_spec_match);
match_found = true; match_found = true;
} }
prev_q = *str == L'\"' ? L'\'' : L'\"'; prev_q = *str == L'\"' ? L'\'' : L'\"';
@ -1318,7 +1361,7 @@ static void highlight_universal_internal(const wcstring &buffstr,
str++; str++;
} }
if (!match_found) color.at(pos) = highlight_make_background(highlight_spec_error); if (!match_found) color.at(pos).background = highlight_role_t::error;
} }
// Highlight matching parenthesis. // Highlight matching parenthesis.
@ -1335,14 +1378,15 @@ static void highlight_universal_internal(const wcstring &buffstr,
if (test_char == dec_char) level--; if (test_char == dec_char) level--;
if (level == 0) { if (level == 0) {
long pos2 = i; long pos2 = i;
color.at(pos) |= highlight_spec_match << 16; color.at(pos).background = highlight_role_t::match;
color.at(pos2) |= highlight_spec_match << 16; color.at(pos2).background = highlight_role_t::match;
match_found = true; match_found = true;
break; break;
} }
} }
if (!match_found) color[pos] = highlight_make_background(highlight_spec_error); if (!match_found)
color.at(pos) = highlight_spec_t::make_background(highlight_role_t::error);
} }
} }
} }
@ -1352,6 +1396,6 @@ void highlight_universal(const wcstring &buff, std::vector<highlight_spec_t> &co
UNUSED(error); UNUSED(error);
UNUSED(vars); UNUSED(vars);
assert(buff.size() == color.size()); assert(buff.size() == color.size());
std::fill(color.begin(), color.end(), 0); std::fill(color.begin(), color.end(), highlight_spec_t{});
highlight_universal_internal(buff, color, pos); highlight_universal_internal(buff, color, pos);
} }

View File

@ -11,66 +11,64 @@
#include "common.h" #include "common.h"
#include "env.h" #include "env.h"
// Internally, we specify highlight colors using a set of bits. Each highlight_spec is a 32 bit /// Describes the role of a span of text.
// uint. We divide this into low 16 (foreground) and high 16 (background). Each half we further enum class highlight_role_t : uint8_t {
// subdivide into low 8 (primary) and high 8 (modifiers). The primary is not a bitmask; specify normal = 0, // normal text
// exactly one. The modifiers are a bitmask; specify any number. error, // error
enum { command, // command
// The following values are mutually exclusive; specify at most one. statement_terminator, // process separator
highlight_spec_normal = 0, // normal text param, // command parameter (argument)
highlight_spec_error, // error comment, // comment
highlight_spec_command, // command match, // matching parenthesis, etc.
highlight_spec_statement_terminator, // process separator search_match, // search match
highlight_spec_param, // command parameter (argument) operat, // operator
highlight_spec_comment, // comment escape, // escape sequences
highlight_spec_match, // matching parenthesis, etc. quote, // quoted string
highlight_spec_search_match, // search match redirection, // redirection
highlight_spec_operator, // operator autosuggestion, // autosuggestion
highlight_spec_escape, // escape sequences selection,
highlight_spec_quote, // quoted string
highlight_spec_redirection, // redirection
highlight_spec_autosuggestion, // autosuggestion
highlight_spec_selection,
// Pager support. // Pager support.
// NOTE: pager.cpp relies on these being in this order. // NOTE: pager.cpp relies on these being in this order.
highlight_spec_pager_progress, pager_progress,
highlight_spec_pager_background, pager_background,
highlight_spec_pager_prefix, pager_prefix,
highlight_spec_pager_completion, pager_completion,
highlight_spec_pager_description, pager_description,
highlight_spec_pager_secondary_background, pager_secondary_background,
highlight_spec_pager_secondary_prefix, pager_secondary_prefix,
highlight_spec_pager_secondary_completion, pager_secondary_completion,
highlight_spec_pager_secondary_description, pager_secondary_description,
highlight_spec_pager_selected_background, pager_selected_background,
highlight_spec_pager_selected_prefix, pager_selected_prefix,
highlight_spec_pager_selected_completion, pager_selected_completion,
highlight_spec_pager_selected_description, pager_selected_description,
// Used to double check a data structure in highlight.cpp
HIGHLIGHT_SPEC_MAX,
HIGHLIGHT_SPEC_PRIMARY_MASK = 0xFF,
// The following values are modifiers.
highlight_modifier_valid_path = 0x100,
highlight_modifier_force_underline = 0x200,
/* Very special value */
highlight_spec_invalid = 0xFFFF
}; };
typedef uint32_t highlight_spec_t;
inline highlight_spec_t highlight_get_primary(highlight_spec_t val) { /// Simply value type describing how a character should be highlighted..
return val & HIGHLIGHT_SPEC_PRIMARY_MASK; struct highlight_spec_t {
} highlight_role_t foreground{highlight_role_t::normal};
highlight_role_t background{highlight_role_t::normal};
bool valid_path{false};
bool force_underline{false};
inline highlight_spec_t highlight_make_background(highlight_spec_t val) { highlight_spec_t() = default;
assert(val >> 16 ==
0); // should have nothing in upper bits, otherwise this is already a background /* implicit */ highlight_spec_t(highlight_role_t fg,
return val << 16; highlight_role_t bg = highlight_role_t::normal)
} : foreground(fg), background(bg) {}
bool operator==(const highlight_spec_t &rhs) const {
return foreground == rhs.foreground && background == rhs.background &&
valid_path == rhs.valid_path && force_underline == rhs.force_underline;
}
bool operator!=(const highlight_spec_t &rhs) const { return !(*this == rhs); }
static highlight_spec_t make_background(highlight_role_t bg_role) {
return highlight_spec_t{highlight_role_t::normal, bg_role};
}
};
class history_item_t; class history_item_t;
@ -104,14 +102,8 @@ void highlight_shell_no_io(const wcstring &buffstr, std::vector<highlight_spec_t
void highlight_universal(const wcstring &buffstr, std::vector<highlight_spec_t> &color, size_t pos, void highlight_universal(const wcstring &buffstr, std::vector<highlight_spec_t> &color, size_t pos,
wcstring_list_t *error, const environment_t &vars); wcstring_list_t *error, const environment_t &vars);
/// Translate from HIGHLIGHT_* to FISH_COLOR_* according to environment variables. Defaults to /// \return an RGB color for a given highlight spec.
/// FISH_COLOR_NORMAL. rgb_color_t highlight_get_color(const highlight_spec_t &highlight, bool is_background);
///
/// Example:
///
/// If the environment variable FISH_FISH_COLOR_ERROR is set to 'red', a call to
/// highlight_get_color( highlight_error) will return FISH_COLOR_RED.
rgb_color_t highlight_get_color(highlight_spec_t highlight, bool is_background);
/// Given a command 'str' from the history, try to determine whether we ought to suggest it by /// Given a command 'str' from the history, try to determine whether we ought to suggest it by
/// specially recognizing the command. Returns true if we validated the command. If so, returns by /// specially recognizing the command. Returns true if we validated the command. If so, returns by

View File

@ -7,6 +7,7 @@
#include <algorithm> #include <algorithm>
#include <numeric> #include <numeric>
#include <type_traits>
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
@ -137,20 +138,25 @@ line_t pager_t::completion_print_item(const wcstring &prefix, const comp_t *c, s
assert(comp_width <= width); assert(comp_width <= width);
} }
int offset = selected auto modify_role = [=](highlight_role_t role) -> highlight_role_t {
? (highlight_spec_pager_selected_background - highlight_spec_pager_background) using uint_t = typename std::underlying_type<highlight_role_t>::type;
: (secondary uint_t base = static_cast<uint_t>(role);
? (highlight_spec_pager_secondary_background - highlight_spec_pager_background) if (selected) {
: 0); base += static_cast<uint_t>(highlight_role_t::pager_selected_background) -
highlight_spec_t bg_color = highlight_spec_pager_background + offset; static_cast<uint_t>(highlight_role_t::pager_background);
highlight_spec_t prefix_fg = highlight_spec_pager_prefix + offset; } else if (secondary) {
highlight_spec_t comp_fg = highlight_spec_pager_completion + offset; base += static_cast<uint_t>(highlight_role_t::pager_secondary_background) -
highlight_spec_t desc_fg = highlight_spec_pager_description + offset; static_cast<uint_t>(highlight_role_t::pager_background);
}
return static_cast<highlight_role_t>(base);
};
highlight_role_t bg_role = modify_role(highlight_role_t::pager_background);
highlight_spec_t bg = {highlight_role_t::normal, bg_role};
highlight_spec_t prefix_col = {modify_role(highlight_role_t::pager_prefix), bg_role};
highlight_spec_t comp_col = {modify_role(highlight_role_t::pager_completion), bg_role};
highlight_spec_t desc_col = {modify_role(highlight_role_t::pager_description), bg_role};
auto bg = highlight_make_background(bg_color);
auto prefix_col = prefix_fg | bg;
auto comp_col = comp_fg | bg;
auto desc_col = desc_fg | bg;
// Print the completion part // Print the completion part
size_t comp_remaining = comp_width; size_t comp_remaining = comp_width;
for (size_t i = 0; i < c->comp.size(); i++) { for (size_t i = 0; i < c->comp.size(); i++) {
@ -179,7 +185,7 @@ line_t pager_t::completion_print_item(const wcstring &prefix, const comp_t *c, s
} }
assert(desc_remaining >= 2); assert(desc_remaining >= 2);
auto paren_col = highlight_spec_pager_completion | bg; highlight_spec_t paren_col = {highlight_role_t::pager_completion, bg_role};
desc_remaining -= print_max(L"(", paren_col, 1, false, &line_data); desc_remaining -= print_max(L"(", paren_col, 1, false, &line_data);
desc_remaining -= print_max(c->desc, desc_col, desc_remaining - 1, false, &line_data); desc_remaining -= print_max(c->desc, desc_col, desc_remaining - 1, false, &line_data);
desc_remaining -= print_max(L")", paren_col, 1, false, &line_data); desc_remaining -= print_max(L")", paren_col, 1, false, &line_data);
@ -226,7 +232,7 @@ void pager_t::completion_print(size_t cols, const size_t *width_by_column, size_
// If there's more to come, append two spaces. // If there's more to come, append two spaces.
if (col + 1 < cols) { if (col + 1 < cols) {
line.append(PAGER_SPACER_STRING, 0); line.append(PAGER_SPACER_STRING, highlight_spec_t{});
} }
// Append this to the real line. // Append this to the real line.
@ -503,8 +509,8 @@ bool pager_t::completion_try_print(size_t cols, const wcstring &prefix, const co
if (!progress_text.empty()) { if (!progress_text.empty()) {
line_t &line = rendering->screen_data.add_line(); line_t &line = rendering->screen_data.add_line();
highlight_spec_t spec = highlight_spec_pager_progress | highlight_spec_t spec = {highlight_role_t::pager_progress,
highlight_make_background(highlight_spec_pager_progress); highlight_role_t::pager_progress};
print_max(progress_text, spec, term_width, true /* has_more */, &line); print_max(progress_text, spec, term_width, true /* has_more */, &line);
} }
@ -521,11 +527,14 @@ bool pager_t::completion_try_print(size_t cols, const wcstring &prefix, const co
line_t *search_field = &rendering->screen_data.insert_line_at_index(0); line_t *search_field = &rendering->screen_data.insert_line_at_index(0);
// We limit the width to term_width - 1. // We limit the width to term_width - 1.
highlight_spec_t underline{};
underline.force_underline = true;
size_t search_field_remaining = term_width - 1; size_t search_field_remaining = term_width - 1;
search_field_remaining -= print_max(SEARCH_FIELD_PROMPT, highlight_spec_normal, search_field_remaining -= print_max(SEARCH_FIELD_PROMPT, highlight_role_t::normal,
search_field_remaining, false, search_field);
search_field_remaining -= print_max(search_field_text, highlight_modifier_force_underline,
search_field_remaining, false, search_field); search_field_remaining, false, search_field);
search_field_remaining -=
print_max(search_field_text, underline, search_field_remaining, false, search_field);
return true; return true;
} }

View File

@ -617,10 +617,10 @@ void reader_data_t::repaint() {
if (len < 1) len = 1; if (len < 1) len = 1;
std::vector<highlight_spec_t> colors = this->colors; std::vector<highlight_spec_t> colors = this->colors;
colors.resize(len, highlight_spec_autosuggestion); colors.resize(len, highlight_role_t::autosuggestion);
if (sel_active) { if (sel_active) {
highlight_spec_t selection_color = highlight_make_background(highlight_spec_selection); highlight_spec_t selection_color = {highlight_role_t::normal, highlight_role_t::selection};
for (size_t i = sel_start_pos; i < std::min(len, sel_stop_pos); i++) { for (size_t i = sel_start_pos; i < std::min(len, sel_stop_pos); i++) {
colors[i] = selection_color; colors[i] = selection_color;
} }
@ -1433,7 +1433,7 @@ void reader_data_t::flash() {
struct timespec pollint; struct timespec pollint;
editable_line_t *el = &command_line; editable_line_t *el = &command_line;
for (size_t i = 0; i < el->position; i++) { for (size_t i = 0; i < el->position; i++) {
colors.at(i) = highlight_spec_search_match << 16; colors.at(i) = highlight_spec_t::make_background(highlight_role_t::search_match);
} }
repaint(); repaint();
@ -2025,7 +2025,7 @@ void reader_data_t::highlight_search() {
if (match_pos != wcstring::npos) { if (match_pos != wcstring::npos) {
size_t end = match_pos + needle.size(); size_t end = match_pos + needle.size();
for (size_t i = match_pos; i < end; i++) { for (size_t i = match_pos; i < end; i++) {
colors.at(i) |= (highlight_spec_search_match << 16); colors.at(i).background = highlight_role_t::search_match;
} }
} }
} }
@ -2059,7 +2059,7 @@ static std::function<highlight_result_t(void)> get_highlight_performer(
return {}; return {};
} }
s_thread_generation = generation_count; s_thread_generation = generation_count;
std::vector<highlight_spec_t> colors(text.size(), 0); std::vector<highlight_spec_t> colors(text.size(), highlight_spec_t{});
highlight_func(text, colors, match_highlight_pos, NULL /* error */, vars); highlight_func(text, colors, match_highlight_pos, NULL /* error */, vars);
return {std::move(colors), text}; return {std::move(colors), text};
}; };

View File

@ -376,7 +376,8 @@ static void s_check_status(screen_t *s) {
/// Appends a character to the end of the line that the output cursor is on. This function /// Appends a character to the end of the line that the output cursor is on. This function
/// automatically handles linebreaks and lines longer than the screen width. /// automatically handles linebreaks and lines longer than the screen width.
static void s_desired_append_char(screen_t *s, wchar_t b, int c, int indent, size_t prompt_width) { static void s_desired_append_char(screen_t *s, wchar_t b, highlight_spec_t c, int indent,
size_t prompt_width) {
int line_no = s->desired.cursor.y; int line_no = s->desired.cursor.y;
if (b == L'\n') { if (b == L'\n') {
@ -386,7 +387,7 @@ static void s_desired_append_char(screen_t *s, wchar_t b, int c, int indent, siz
s->desired.cursor.y++; s->desired.cursor.y++;
s->desired.cursor.x = 0; s->desired.cursor.x = 0;
for (size_t i = 0; i < prompt_width + indent * INDENT_STEP; i++) { for (size_t i = 0; i < prompt_width + indent * INDENT_STEP; i++) {
s_desired_append_char(s, L' ', 0, indent, prompt_width); s_desired_append_char(s, L' ', highlight_spec_t{}, indent, prompt_width);
} }
} else if (b == L'\r') { } else if (b == L'\r') {
line_t &current = s->desired.line(line_no); line_t &current = s->desired.line(line_no);
@ -512,10 +513,7 @@ static void s_move(screen_t *s, int new_x, int new_y) {
/// Set the pen color for the terminal. /// Set the pen color for the terminal.
static void s_set_color(screen_t *s, const environment_t &vars, highlight_spec_t c) { static void s_set_color(screen_t *s, const environment_t &vars, highlight_spec_t c) {
UNUSED(s); UNUSED(s);
s->outp().set_color(highlight_get_color(c, false), highlight_get_color(c, true));
unsigned int uc = (unsigned int)c;
s->outp().set_color(highlight_get_color(uc & 0xfff, false),
highlight_get_color((uc >> 16) & 0xffff, true));
} }
/// Convert a wide character to a multibyte string and append it to the buffer. /// Convert a wide character to a multibyte string and append it to the buffer.
@ -723,7 +721,7 @@ static void s_update(screen_t *scr, const wcstring &left_prompt, const wcstring
clear_remainder = prev_width > current_width; clear_remainder = prev_width > current_width;
} }
if (clear_remainder && clr_eol) { if (clear_remainder && clr_eol) {
s_set_color(scr, vars, 0xffffffff); s_set_color(scr, vars, highlight_spec_t{});
s_move(scr, current_width, (int)i); s_move(scr, current_width, (int)i);
s_write_mbs(scr, clr_eol); s_write_mbs(scr, clr_eol);
} }
@ -731,7 +729,7 @@ static void s_update(screen_t *scr, const wcstring &left_prompt, const wcstring
// Output any rprompt if this is the first line. // Output any rprompt if this is the first line.
if (i == 0 && right_prompt_width > 0) { //!OCLINT(Use early exit/continue) if (i == 0 && right_prompt_width > 0) { //!OCLINT(Use early exit/continue)
s_move(scr, (int)(screen_width - right_prompt_width), (int)i); s_move(scr, (int)(screen_width - right_prompt_width), (int)i);
s_set_color(scr, vars, 0xffffffff); s_set_color(scr, vars, highlight_spec_t{});
s_write_str(scr, right_prompt.c_str()); s_write_str(scr, right_prompt.c_str());
scr->actual.cursor.x += right_prompt_width; scr->actual.cursor.x += right_prompt_width;
@ -751,7 +749,7 @@ static void s_update(screen_t *scr, const wcstring &left_prompt, const wcstring
// Clear remaining lines (if any) if we haven't cleared the screen. // Clear remaining lines (if any) if we haven't cleared the screen.
if (!has_cleared_screen && scr->desired.line_count() < lines_with_stuff && clr_eol) { if (!has_cleared_screen && scr->desired.line_count() < lines_with_stuff && clr_eol) {
s_set_color(scr, vars, 0xffffffff); s_set_color(scr, vars, highlight_spec_t{});
for (size_t i = scr->desired.line_count(); i < lines_with_stuff; i++) { for (size_t i = scr->desired.line_count(); i < lines_with_stuff; i++) {
s_move(scr, 0, (int)i); s_move(scr, 0, (int)i);
s_write_mbs(scr, clr_eol); s_write_mbs(scr, clr_eol);
@ -759,7 +757,7 @@ static void s_update(screen_t *scr, const wcstring &left_prompt, const wcstring
} }
s_move(scr, scr->desired.cursor.x, scr->desired.cursor.y); s_move(scr, scr->desired.cursor.x, scr->desired.cursor.y);
s_set_color(scr, vars, 0xffffffff); s_set_color(scr, vars, highlight_spec_t{});
// We have now synced our actual screen against our desired screen. Note that this is a big // We have now synced our actual screen against our desired screen. Note that this is a big
// assignment! // assignment!
@ -999,13 +997,13 @@ void s_write(screen_t *s, const wcstring &left_prompt, const wcstring &right_pro
// Append spaces for the left prompt. // Append spaces for the left prompt.
for (size_t i = 0; i < layout.left_prompt_space; i++) { for (size_t i = 0; i < layout.left_prompt_space; i++) {
s_desired_append_char(s, L' ', 0, 0, layout.left_prompt_space); s_desired_append_char(s, L' ', highlight_spec_t{}, 0, layout.left_prompt_space);
} }
// If overflowing, give the prompt its own line to improve the situation. // If overflowing, give the prompt its own line to improve the situation.
size_t first_line_prompt_space = layout.left_prompt_space; size_t first_line_prompt_space = layout.left_prompt_space;
if (layout.prompts_get_own_line) { if (layout.prompts_get_own_line) {
s_desired_append_char(s, L'\n', 0, 0, 0); s_desired_append_char(s, L'\n', highlight_spec_t{}, 0, 0);
first_line_prompt_space = 0; first_line_prompt_space = 0;
} }