diff --git a/src/fish_tests.cpp b/src/fish_tests.cpp index 68b69cb20..16bbd6665 100644 --- a/src/fish_tests.cpp +++ b/src/fish_tests.cpp @@ -5732,19 +5732,19 @@ void test_layout_cache() { for (size_t i = 0; i < layout_cache_t::prompt_cache_max_size; i++) { wcstring input = std::to_wstring(i); do_test(!seqs.find_prompt_layout(input)); - seqs.add_prompt_layout({input, huge, input, {i, 0, 0}}); - do_test(seqs.find_prompt_layout(input)->layout.line_count == i); + seqs.add_prompt_layout({input, huge, input, {{}, i, 0}}); + do_test(seqs.find_prompt_layout(input)->layout.max_line_width == i); } size_t expected_evictee = 3; for (size_t i = 0; i < layout_cache_t::prompt_cache_max_size; i++) { if (i != expected_evictee) - do_test(seqs.find_prompt_layout(std::to_wstring(i))->layout.line_count == i); + do_test(seqs.find_prompt_layout(std::to_wstring(i))->layout.max_line_width == i); } - seqs.add_prompt_layout({L"whatever", huge, L"whatever", {100, 0, 0}}); + seqs.add_prompt_layout({L"whatever", huge, L"whatever", {{}, 100, 0}}); do_test(!seqs.find_prompt_layout(std::to_wstring(expected_evictee))); - do_test(seqs.find_prompt_layout(L"whatever", huge)->layout.line_count == 100); + do_test(seqs.find_prompt_layout(L"whatever", huge)->layout.max_line_width == 100); } void test_prompt_truncation() { @@ -5754,7 +5754,16 @@ void test_prompt_truncation() { /// Helper to return 'layout' formatted as a string for easy comparison. auto format_layout = [&] { - return format_string(L"%lu,%lu,%lu", (unsigned long)layout.line_count, + wcstring line_breaks = L""; + bool first = true; + for (const size_t line_break : layout.line_breaks) { + if (!first) { + line_breaks.push_back(L','); + } + line_breaks.append(format_string(L"%lu", (unsigned long)line_break)); + first = false; + } + return format_string(L"[%ls],%lu,%lu", line_breaks.c_str(), (unsigned long)layout.max_line_width, (unsigned long)layout.last_line_width); }; @@ -5766,12 +5775,22 @@ void test_prompt_truncation() { // No truncation. layout = cache.calc_prompt_layout(L"abcd", &trunc); - do_test(format_layout() == L"1,4,4"); + do_test(format_layout() == L"[],4,4"); do_test(trunc == L"abcd"); + // Line break calculation. + layout = cache.calc_prompt_layout(join({ + L"0123456789ABCDEF", // + L"012345", // + L"0123456789abcdef", // + L"xyz" // + }), + &trunc, 80); + do_test(format_layout() == L"[16,23,40],16,3"); + // Basic truncation. layout = cache.calc_prompt_layout(L"0123456789ABCDEF", &trunc, 8); - do_test(format_layout() == L"1,8,8"); + do_test(format_layout() == L"[],8,8"); do_test(trunc == ellipsis + L"9ABCDEF"); // Multiline truncation. @@ -5782,24 +5801,24 @@ void test_prompt_truncation() { L"xyz" // }), &trunc, 8); - do_test(format_layout() == L"4,8,3"); + do_test(format_layout() == L"[8,15,24],8,3"); do_test(trunc == join({ellipsis + L"9ABCDEF", L"012345", ellipsis + L"9abcdef", L"xyz"})); // Escape sequences are not truncated. layout = cache.calc_prompt_layout(L"\x1B]50;CurrentDir=test/foo\x07NOT_PART_OF_SEQUENCE", &trunc, 4); - do_test(format_layout() == L"1,4,4"); + do_test(format_layout() == L"[],4,4"); do_test(trunc == ellipsis + L"\x1B]50;CurrentDir=test/foo\x07NCE"); // Newlines in escape sequences are skipped. layout = cache.calc_prompt_layout(L"\x1B]50;CurrentDir=\ntest/foo\x07NOT_PART_OF_SEQUENCE", &trunc, 4); - do_test(format_layout() == L"1,4,4"); + do_test(format_layout() == L"[],4,4"); do_test(trunc == ellipsis + L"\x1B]50;CurrentDir=\ntest/foo\x07NCE"); // We will truncate down to one character if we have to. layout = cache.calc_prompt_layout(L"Yay", &trunc, 1); - do_test(format_layout() == L"1,1,1"); + do_test(format_layout() == L"[],1,1"); do_test(trunc == ellipsis); } diff --git a/src/screen.cpp b/src/screen.cpp index 20e8010a1..7d43ec2d0 100644 --- a/src/screen.cpp +++ b/src/screen.cpp @@ -368,7 +368,7 @@ prompt_layout_t layout_cache_t::calc_prompt_layout(const wcstring &prompt_str, size_t prompt_len = prompt_str.size(); const wchar_t *prompt = prompt_str.c_str(); - prompt_layout_t layout{1, 0, 0}; + prompt_layout_t layout = {{}, 0, 0}; wcstring trunc_prompt; size_t run_start = 0; @@ -390,7 +390,7 @@ prompt_layout_t layout_cache_t::calc_prompt_layout(const wcstring &prompt_str, wchar_t endc = prompt[run_end]; if (endc) { if (endc == L'\n' || endc == L'\f') { - layout.line_count += 1; + layout.line_breaks.push_back(trunc_prompt.size()); } trunc_prompt.push_back(endc); run_start = run_end + 1; @@ -408,10 +408,10 @@ prompt_layout_t layout_cache_t::calc_prompt_layout(const wcstring &prompt_str, static size_t calc_prompt_lines(const wcstring &prompt) { // Hack for the common case where there's no newline at all. I don't know if a newline can // appear in an escape sequence, so if we detect a newline we have to defer to - // calc_prompt_layout. + // calc_prompt_width_and_lines. size_t result = 1; if (prompt.find_first_of(L"\n\f") != wcstring::npos) { - result = layout_cache_t::shared.calc_prompt_layout(prompt).line_count; + result = layout_cache_t::shared.calc_prompt_layout(prompt).line_breaks.size() + 1; } return result; } @@ -737,19 +737,15 @@ static void s_update(screen_t *scr, const wcstring &left_prompt, const wcstring // Output the left prompt if it has changed. if (left_prompt != scr->actual_left_prompt) { s_move(scr, 0, 0); - wcstring::const_iterator line_start = left_prompt.begin(); - while (line_start < left_prompt.end()) { - auto line_end = std::find_if(line_start, left_prompt.end(), is_run_terminator); - s_write_str(scr, wcstring(line_start, line_end).c_str()); - if (line_end != left_prompt.end()) { - // Embedded run terminator. Emit clr_eol before the line break. - // TODO: we should be skipping line terminators inside escape sequences. - if (clr_eol) s_write_mbs(scr, clr_eol); - scr->outp().push_back(*line_end); - ++line_end; + size_t start = 0; + for (const size_t line_break : left_prompt_layout.line_breaks) { + s_write_str(scr, left_prompt.substr(start, line_break - start).c_str()); + if (clr_eol) { + s_write_mbs(scr, clr_eol); } - line_start = line_end; + start = line_break; } + s_write_str(scr, left_prompt.c_str() + start); scr->actual_left_prompt = left_prompt; scr->actual.cursor.x = static_cast(left_prompt_width); } diff --git a/src/screen.h b/src/screen.h index 2c1e0ddd4..a3b214be5 100644 --- a/src/screen.h +++ b/src/screen.h @@ -213,9 +213,9 @@ void screen_force_clear_to_end(); // Information about the layout of a prompt. struct prompt_layout_t { - size_t line_count; // number of line breaks when rendering the prompt - size_t max_line_width; // width of the longest line - size_t last_line_width; // width of the last line + std::vector line_breaks; // line breaks when rendering the prompt + 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 widths for fast lookup.