diff --git a/src/env.cpp b/src/env.cpp index a65c1161d..5f94789b1 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -606,7 +606,7 @@ static void init_curses() { term_has_xn = tigetflag((char *)"xenl") == 1; // does terminal have the eat_newline_glitch update_fish_color_support(); // Invalidate the cached escape sequences since they may no longer be valid. - cached_esc_sequences.clear(); + cached_layouts.clear(); curses_initialized = true; } diff --git a/src/fish_tests.cpp b/src/fish_tests.cpp index 6d86c07d6..eacaed021 100644 --- a/src/fish_tests.cpp +++ b/src/fish_tests.cpp @@ -4333,27 +4333,27 @@ void test_maybe() { } void test_cached_esc_sequences() { - cached_esc_sequences_t seqs; - do_test(seqs.find_entry(L"abc") == 0); - seqs.add_entry(L"abc"); - seqs.add_entry(L"abc"); - do_test(seqs.size() == 1); - do_test(seqs.find_entry(L"abc") == 3); - do_test(seqs.find_entry(L"abcd") == 3); - do_test(seqs.find_entry(L"abcde") == 3); - do_test(seqs.find_entry(L"xabcde") == 0); - seqs.add_entry(L"ac"); - do_test(seqs.find_entry(L"abcd") == 3); - do_test(seqs.find_entry(L"acbd") == 2); - seqs.add_entry(L"wxyz"); - do_test(seqs.find_entry(L"abc") == 3); - do_test(seqs.find_entry(L"abcd") == 3); - do_test(seqs.find_entry(L"wxyz123") == 4); - do_test(seqs.find_entry(L"qwxyz123") == 0); - do_test(seqs.size() == 3); + layout_cache_t seqs; + do_test(seqs.find_escape_code(L"abc") == 0); + seqs.add_escape_code(L"abc"); + seqs.add_escape_code(L"abc"); + do_test(seqs.esc_cache_size() == 1); + do_test(seqs.find_escape_code(L"abc") == 3); + do_test(seqs.find_escape_code(L"abcd") == 3); + do_test(seqs.find_escape_code(L"abcde") == 3); + do_test(seqs.find_escape_code(L"xabcde") == 0); + seqs.add_escape_code(L"ac"); + do_test(seqs.find_escape_code(L"abcd") == 3); + do_test(seqs.find_escape_code(L"acbd") == 2); + seqs.add_escape_code(L"wxyz"); + do_test(seqs.find_escape_code(L"abc") == 3); + do_test(seqs.find_escape_code(L"abcd") == 3); + do_test(seqs.find_escape_code(L"wxyz123") == 4); + do_test(seqs.find_escape_code(L"qwxyz123") == 0); + do_test(seqs.esc_cache_size() == 3); seqs.clear(); - do_test(seqs.size() == 0); - do_test(seqs.find_entry(L"abcd") == 0); + do_test(seqs.esc_cache_size() == 0); + do_test(seqs.find_escape_code(L"abcd") == 0); } /// Main test. diff --git a/src/screen.cpp b/src/screen.cpp index 3b5950e01..a0ef2c51a 100644 --- a/src/screen.cpp +++ b/src/screen.cpp @@ -81,7 +81,7 @@ class scoped_buffer_t { // Singleton of the cached escape sequences seen in prompts and similar strings. // Note this is deliberately exported so that init_curses can clear it. -cached_esc_sequences_t cached_esc_sequences; +layout_cache_t cached_layouts; /// Tests if the specified narrow character sequence is present at the specified position of the /// specified wide character string. All of \c seq must match, but str may be longer than seq. @@ -269,7 +269,7 @@ size_t escape_code_length(const wchar_t *code) { assert(code != NULL); if (*code != L'\e') return 0; - size_t esc_seq_len = cached_esc_sequences.find_entry(code); + size_t esc_seq_len = cached_layouts.find_escape_code(code); if (esc_seq_len) return esc_seq_len; bool found = is_color_escape_seq(code, &esc_seq_len); @@ -279,17 +279,10 @@ size_t escape_code_length(const wchar_t *code) { if (!found) found = is_single_byte_escape_seq(code, &esc_seq_len); if (!found) found = is_csi_style_escape_seq(code, &esc_seq_len); if (!found) found = is_two_byte_escape_seq(code, &esc_seq_len); - if (found) cached_esc_sequences.add_entry(wcstring(code, esc_seq_len)); + if (found) cached_layouts.add_escape_code(wcstring(code, esc_seq_len)); return esc_seq_len; } -// Information about the layout of a prompt. -struct prompt_layout_t { - size_t line_count; // how many lines the prompt consumes - size_t max_line_width; // width of the longest line - size_t last_line_width; // width of the last line -}; - // These are used by `calc_prompt_layout()` to avoid redundant calculations. static const wchar_t *cached_left_prompt = wcsdup(L""); static const wchar_t *cached_right_prompt = wcsdup(L""); diff --git a/src/screen.h b/src/screen.h index b7897bf5f..cb3d0bd81 100644 --- a/src/screen.h +++ b/src/screen.h @@ -16,9 +16,10 @@ #include #include +#include #include -#include #include +#include #include #include "common.h" @@ -199,45 +200,60 @@ bool screen_force_clear_to_end(); /// Returns the length of an escape code. Exposed for testing purposes only. size_t escape_code_length(const wchar_t *code); +// Information about the layout of a prompt. +struct prompt_layout_t { + size_t line_count; // how many lines the prompt consumes + size_t max_line_width; // width of the longest line + size_t last_line_width; // width of the last line +}; + // Maintain a mapping of escape sequences to their length for fast lookup. -class cached_esc_sequences_t { +class layout_cache_t { private: // Cached escape sequences we've already detected in the prompt and similar strings, ordered // lexicographically. - std::vector cache_; + std::vector esc_cache_; + + // LRU-list of prompts and their layouts. + // Use a list so we can promote to the front on a cache hit. + using prompt_layout_pair_t = std::pair; + std::list prompt_cache_; public: - /// \return the size of the cache. - size_t size() const { return cache_.size(); } + /// \return the size of the escape code cache. + size_t esc_cache_size() const { return esc_cache_.size(); } /// Insert the entry \p str in its sorted position, if it is not already present in the cache. - void add_entry(wcstring str) { - auto where = std::upper_bound(cache_.begin(), cache_.end(), str); - if (where == cache_.begin() || where[-1] != str) { - cache_.emplace(where, std::move(str)); + void add_escape_code(wcstring str) { + auto where = std::upper_bound(esc_cache_.begin(), esc_cache_.end(), str); + if (where == esc_cache_.begin() || where[-1] != str) { + esc_cache_.emplace(where, std::move(str)); } } /// \return the length of a string that matches a prefix of \p entry. - size_t find_entry(const wchar_t *entry) const { + size_t find_escape_code(const wchar_t *entry) const { // Do a binary search and see if the escape code right before our entry is a prefix of our // entry. Note this assumes that escape codes are prefix-free: no escape code is a prefix of // another one. This seems like a safe assumption. - auto where = std::upper_bound(cache_.begin(), cache_.end(), entry); + auto where = std::upper_bound(esc_cache_.begin(), esc_cache_.end(), entry); // 'where' is now the first element that is greater than entry. Thus where-1 is the last // element that is less than or equal to entry. - if (where != cache_.begin()) { + if (where != esc_cache_.begin()) { const wcstring &candidate = where[-1]; if (string_prefixes_string(candidate.c_str(), entry)) return candidate.size(); } return 0; } - void clear() { cache_.clear(); } + void clear() { + esc_cache_.clear(); + prompt_cache_.clear(); + } }; // Singleton that is exposed so that the cache can be invalidated when terminal related variables // change by calling `cached_esc_sequences.clear()`. -extern cached_esc_sequences_t cached_esc_sequences; +extern layout_cache_t cached_layouts; #endif