mirror of
https://github.com/fish-shell/fish-shell
synced 2025-01-15 22:44:01 +00:00
Cache resolved colors when outputting to the screen
Prior to this change, fish would "resolve" highlight specs to rgb colors right before use. This requires a series of variable lookups; profiling showed 30% of draw time was spent here. Switch to caching these (within a single redraw only).
This commit is contained in:
parent
87d049edd8
commit
6eab9275d0
6 changed files with 73 additions and 33 deletions
|
@ -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<highlight_spec_t> 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);
|
||||
}
|
||||
|
|
|
@ -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<highlight_spec_t> 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<highlight_spec_t> 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);
|
||||
}
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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<highlight_spec_t> &colors) {
|
||||
std::string colorize(const wcstring &text, const std::vector<highlight_spec_t> &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));
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "color.h"
|
||||
|
@ -69,10 +70,22 @@ struct highlight_spec_t {
|
|||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct std::hash<highlight_spec_t> {
|
||||
std::size_t operator()(const highlight_spec_t &v) const {
|
||||
size_t vals[4] = {static_cast<uint32_t>(v.foreground), static_cast<uint32_t>(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<highlight_spec_t> &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<highlight_spec_t> &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<highlight_spec_t> &
|
|||
void highlight_shell(const wcstring &buffstr, std::vector<highlight_spec_t> &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<highlight_spec_t, rgb_color_t> fg_cache_;
|
||||
std::unordered_map<highlight_spec_t, rgb_color_t> 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
|
||||
|
|
|
@ -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<int>(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<size_t>(screen_width))) {
|
||||
s_set_color(scr, vars, highlight_spec_t{});
|
||||
set_color(highlight_spec_t{});
|
||||
s_move(scr, current_width, static_cast<int>(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<int>(i));
|
||||
s_move(scr, current_width, static_cast<int>(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<int>(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<int>(screen_width - right_prompt_width), static_cast<int>(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<int>(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!
|
||||
|
|
Loading…
Reference in a new issue