diff --git a/src/builtin_complete.cpp b/src/builtin_complete.cpp index b7d2d5f4f..2d6f9552c 100644 --- a/src/builtin_complete.cpp +++ b/src/builtin_complete.cpp @@ -390,8 +390,8 @@ int builtin_complete(parser_t &parser, io_streams_t &streams, wchar_t **argv) { if (!streams.out_is_redirected && isatty(STDOUT_FILENO)) { std::vector colors; size_t len = repr.size(); - highlight_shell(repr, colors, len, operation_context_t::globals()); - streams.out.append(str2wcstring(colorize(repr, colors))); + highlight_shell(repr, colors, len, parser.context()); + streams.out.append(str2wcstring(colorize(repr, colors, parser.vars()))); } else { streams.out.append(repr); } diff --git a/src/builtin_functions.cpp b/src/builtin_functions.cpp index 4e36c0d2b..72bbd782e 100644 --- a/src/builtin_functions.cpp +++ b/src/builtin_functions.cpp @@ -262,9 +262,8 @@ static int report_function_metadata(const wchar_t *funcname, bool verbose, io_st append_format(comment, L"# Defined in %ls @ line %d\n", path, line_number); if (!streams.out_is_redirected && isatty(STDOUT_FILENO)) { std::vector colors; - highlight_shell(comment, colors, comment.size(), - operation_context_t{nullptr, env_stack_t::globals(), no_cancel}); - streams.out.append(str2wcstring(colorize(comment, colors))); + highlight_shell(comment, colors, comment.size(), parser.context()); + streams.out.append(str2wcstring(colorize(comment, colors, parser.vars()))); } else { streams.out.append(comment); } @@ -441,8 +440,8 @@ int builtin_functions(parser_t &parser, io_streams_t &streams, wchar_t **argv) { if (!streams.out_is_redirected && isatty(STDOUT_FILENO)) { std::vector colors; - highlight_shell(def, colors, def.size(), operation_context_t::globals()); - streams.out.append(str2wcstring(colorize(def, colors))); + highlight_shell(def, colors, def.size(), parser.context()); + streams.out.append(str2wcstring(colorize(def, colors, parser.vars()))); } else { streams.out.append(def); } diff --git a/src/fish_indent.cpp b/src/fish_indent.cpp index c6068c9be..1074e2f1b 100644 --- a/src/fish_indent.cpp +++ b/src/fish_indent.cpp @@ -987,7 +987,7 @@ int main(int argc, char *argv[]) { break; } case output_type_ansi: { - colored_output = colorize(output_wtext, colors); + colored_output = colorize(output_wtext, colors, env_stack_t::globals()); break; } case output_type_html: { diff --git a/src/highlight.cpp b/src/highlight.cpp index b179b9f00..53cbedd52 100644 --- a/src/highlight.cpp +++ b/src/highlight.cpp @@ -342,9 +342,9 @@ static bool statement_get_expanded_command(const wcstring &src, return err == expand_result_t::ok; } -rgb_color_t highlight_get_color(const highlight_spec_t &highlight, bool is_background) { - // TODO: rationalize this principal_vars. - const auto &vars = env_stack_t::principal(); +rgb_color_t highlight_color_resolver_t::resolve_spec_uncached(const highlight_spec_t &highlight, + bool is_background, + const environment_t &vars) const { rgb_color_t result = rgb_color_t::normal(); highlight_role_t role = is_background ? highlight.background : highlight.foreground; @@ -377,6 +377,20 @@ rgb_color_t highlight_get_color(const highlight_spec_t &highlight, bool is_backg return result; } +rgb_color_t highlight_color_resolver_t::resolve_spec(const highlight_spec_t &highlight, + bool is_background, + const environment_t &vars) { + auto &cache = is_background ? bg_cache_ : fg_cache_; + auto p = cache.insert(std::make_pair(highlight, rgb_color_t{})); + auto iter = p.first; + bool did_insert = p.second; + if (did_insert) { + // Insertion happened, meaning the cache needs to be populated. + iter->second = resolve_spec_uncached(highlight, is_background, vars); + } + return iter->second; +} + static bool command_is_valid(const wcstring &cmd, enum statement_decoration_t decoration, const wcstring &working_directory, const environment_t &vars); @@ -1288,17 +1302,17 @@ highlighter_t::color_array_t highlighter_t::highlight() { return std::move(color_array); } -/// Given a string and list of colors of the same size, return the string with ANSI escape sequences -/// representing the colors. -std::string colorize(const wcstring &text, const std::vector &colors) { +std::string colorize(const wcstring &text, const std::vector &colors, + const environment_t &vars) { assert(colors.size() == text.size()); + highlight_color_resolver_t rv; outputter_t outp; highlight_spec_t last_color = highlight_role_t::normal; for (size_t i = 0; i < text.size(); i++) { highlight_spec_t color = colors.at(i); if (color != last_color) { - outp.set_color(highlight_get_color(color, false), rgb_color_t::normal()); + outp.set_color(rv.resolve_spec(color, false, vars), rgb_color_t::normal()); last_color = color; } outp.writech(text.at(i)); diff --git a/src/highlight.h b/src/highlight.h index 3bdb077cc..6a873f90c 100644 --- a/src/highlight.h +++ b/src/highlight.h @@ -5,6 +5,7 @@ #include #include +#include #include #include "color.h" @@ -69,10 +70,22 @@ struct highlight_spec_t { } }; +template <> +struct std::hash { + std::size_t operator()(const highlight_spec_t &v) const { + size_t vals[4] = {static_cast(v.foreground), static_cast(v.background), + v.valid_path, v.force_underline}; + return (vals[0] << 0) + (vals[1] << 6) + (vals[2] << 12) + (vals[3] << 18); + } +}; + class history_item_t; class operation_context_t; -std::string colorize(const wcstring &text, const std::vector &colors); +/// Given a string and list of colors of the same size, return the string with ANSI escape sequences +/// representing the colors. +std::string colorize(const wcstring &text, const std::vector &colors, + const environment_t &vars); /// Perform syntax highlighting for the shell commands in buff. The result is stored in the color /// array as a color_code from the HIGHLIGHT_ enum for each character in buff. @@ -87,8 +100,20 @@ std::string colorize(const wcstring &text, const std::vector & void highlight_shell(const wcstring &buffstr, std::vector &color, size_t pos, const operation_context_t &ctx, bool io_ok = false); -/// \return an RGB color for a given highlight spec. -rgb_color_t highlight_get_color(const highlight_spec_t &highlight, bool is_background); +/// highlight_color_resolver_t resolves highlight specs (like "a command") to actual RGB colors. +/// It maintains a cache with no invalidation mechanism. The lifetime of these should typically be +/// one screen redraw. +struct highlight_color_resolver_t { + /// \return an RGB color for a given highlight spec. + rgb_color_t resolve_spec(const highlight_spec_t &highlight, bool is_background, + const environment_t &vars); + + private: + std::unordered_map fg_cache_; + std::unordered_map bg_cache_; + rgb_color_t resolve_spec_uncached(const highlight_spec_t &highlight, bool is_background, + const environment_t &vars) const; +}; /// 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 diff --git a/src/screen.cpp b/src/screen.cpp index 7a6b1d46e..d0ab6489e 100644 --- a/src/screen.cpp +++ b/src/screen.cpp @@ -604,13 +604,6 @@ static void s_move(screen_t *s, int new_x, int new_y) { s->actual.cursor.y = new_y; } -/// Set the pen color for the terminal. -static void s_set_color(screen_t *s, const environment_t &vars, highlight_spec_t c) { - UNUSED(s); - UNUSED(vars); - s->outp().set_color(highlight_get_color(c, false), highlight_get_color(c, true)); -} - /// Convert a wide character to a multibyte string and append it to the buffer. static void s_write_char(screen_t *s, wchar_t c, size_t width) { scoped_buffer_t outp(*s); @@ -689,8 +682,17 @@ static void invalidate_soft_wrap(screen_t *scr) { scr->soft_wrap_location = none /// Update the screen to match the desired output. static void s_update(screen_t *scr, const wcstring &left_prompt, const wcstring &right_prompt) { - layout_cache_t &cached_layouts = layout_cache_t::shared; + // TODO: this should be passed in. const environment_t &vars = env_stack_t::principal(); + + // Helper function to set a resolved color, using the caching resolver. + highlight_color_resolver_t color_resolver{}; + auto set_color = [&](highlight_spec_t c) { + scr->outp().set_color(color_resolver.resolve_spec(c, false, vars), + color_resolver.resolve_spec(c, true, vars)); + }; + + layout_cache_t &cached_layouts = layout_cache_t::shared; const scoped_buffer_t buffering(*scr); // Determine size of left and right prompt. Note these have already been truncated. @@ -761,7 +763,7 @@ static void s_update(screen_t *scr, const wcstring &left_prompt, const wcstring if (shared_prefix < o_line.indentation) { if (o_line.indentation > s_line.indentation && !has_cleared_screen && clr_eol && clr_eos) { - s_set_color(scr, vars, highlight_spec_t{}); + set_color(highlight_spec_t{}); s_move(scr, 0, static_cast(i)); s_write_mbs(scr, should_clear_screen_this_line ? clr_eos : clr_eol); has_cleared_screen = should_clear_screen_this_line; @@ -821,7 +823,7 @@ static void s_update(screen_t *scr, const wcstring &left_prompt, const wcstring // wrapping. if (should_clear_screen_this_line && !has_cleared_screen && (done || j + 1 == static_cast(screen_width))) { - s_set_color(scr, vars, highlight_spec_t{}); + set_color(highlight_spec_t{}); s_move(scr, current_width, static_cast(i)); s_write_mbs(scr, clr_eos); has_cleared_screen = true; @@ -830,7 +832,7 @@ static void s_update(screen_t *scr, const wcstring &left_prompt, const wcstring perform_any_impending_soft_wrap(scr, current_width, static_cast(i)); s_move(scr, current_width, static_cast(i)); - s_set_color(scr, vars, o_line.color_at(j)); + set_color(o_line.color_at(j)); auto width = fish_wcwidth_min_0(o_line.char_at(j)); s_write_char(scr, o_line.char_at(j), width); current_width += width; @@ -857,7 +859,7 @@ static void s_update(screen_t *scr, const wcstring &left_prompt, const wcstring } } if (clear_remainder && clr_eol) { - s_set_color(scr, vars, highlight_spec_t{}); + set_color(highlight_spec_t{}); s_move(scr, current_width, static_cast(i)); s_write_mbs(scr, clr_eol); } @@ -865,7 +867,7 @@ static void s_update(screen_t *scr, const wcstring &left_prompt, const wcstring // Output any rprompt if this is the first line. if (i == 0 && right_prompt_width > 0) { //!OCLINT(Use early exit/continue) s_move(scr, static_cast(screen_width - right_prompt_width), static_cast(i)); - s_set_color(scr, vars, highlight_spec_t{}); + set_color(highlight_spec_t{}); s_write_str(scr, right_prompt.c_str()); scr->actual.cursor.x += right_prompt_width; @@ -886,7 +888,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. if (!has_cleared_screen && need_clear_screen && clr_eol) { - s_set_color(scr, vars, highlight_spec_t{}); + set_color(highlight_spec_t{}); for (size_t i = scr->desired.line_count(); i < lines_with_stuff; i++) { s_move(scr, 0, static_cast(i)); s_write_mbs(scr, clr_eol); @@ -894,7 +896,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_set_color(scr, vars, highlight_spec_t{}); + set_color(highlight_spec_t{}); // We have now synced our actual screen against our desired screen. Note that this is a big // assignment!