diff options
author | Ali Mohammad Pur <ali.mpfard@gmail.com> | 2021-07-19 23:12:28 +0430 |
---|---|---|
committer | Ali Mohammad Pur <Ali.mpfard@gmail.com> | 2021-07-20 11:55:42 +0430 |
commit | 31840866798a1e369e9824689c46d9e7ee0e7dbd (patch) | |
tree | 71a9c0c78068fc9029adea9aba438f93a2803faa /Userland/Libraries | |
parent | 0f6654fef289421bafaacbae1aa5c04bf8292bd8 (diff) | |
download | serenity-31840866798a1e369e9824689c46d9e7ee0e7dbd.zip |
LibLine: Avoid excessive write() syscalls when refreshing the display
Previously, we were generating the display update one character at a
time, and writing them one at a time to stderr, which is not buffered,
doing so caused one syscall per character printed which is s l o w (TM)
This commit makes LibLine write the update contents into a buffer, and
flush it after all the update is generated :^)
Diffstat (limited to 'Userland/Libraries')
-rw-r--r-- | Userland/Libraries/LibLine/Editor.cpp | 162 | ||||
-rw-r--r-- | Userland/Libraries/LibLine/Editor.h | 2 | ||||
-rw-r--r-- | Userland/Libraries/LibLine/InternalFunctions.cpp | 12 | ||||
-rw-r--r-- | Userland/Libraries/LibLine/VT.h | 14 | ||||
-rw-r--r-- | Userland/Libraries/LibLine/XtermSuggestionDisplay.cpp | 46 |
5 files changed, 126 insertions, 110 deletions
diff --git a/Userland/Libraries/LibLine/Editor.cpp b/Userland/Libraries/LibLine/Editor.cpp index dca044a227..57790728e0 100644 --- a/Userland/Libraries/LibLine/Editor.cpp +++ b/Userland/Libraries/LibLine/Editor.cpp @@ -8,8 +8,10 @@ #include "Editor.h" #include <AK/CharacterTypes.h> #include <AK/Debug.h> +#include <AK/FileStream.h> #include <AK/GenericLexer.h> #include <AK/JsonObject.h> +#include <AK/MemoryStream.h> #include <AK/ScopeGuard.h> #include <AK/ScopedValueRollback.h> #include <AK/StringBuilder.h> @@ -578,11 +580,13 @@ void Editor::interrupted() return; m_finish = false; - reposition_cursor(true); - if (m_suggestion_display->cleanup()) - reposition_cursor(true); - fprintf(stderr, "\n"); - fflush(stderr); + { + OutputFileStream stderr_stream { stderr }; + reposition_cursor(stderr_stream, true); + if (m_suggestion_display->cleanup()) + reposition_cursor(stderr_stream, true); + stderr_stream.write("\n"sv.bytes()); + } m_buffer.clear(); m_chars_touched_in_the_middle = buffer().size(); m_is_editing = false; @@ -619,9 +623,11 @@ void Editor::handle_resize_event(bool reset_origin) set_origin(m_origin_row, 1); - reposition_cursor(true); + OutputFileStream stderr_stream { stderr }; + + reposition_cursor(stderr_stream, true); m_suggestion_display->redisplay(m_suggestion_manager, m_num_lines, m_num_columns); - reposition_cursor(); + reposition_cursor(stderr_stream); if (m_is_searching) m_search_editor->resized(); @@ -630,9 +636,11 @@ void Editor::handle_resize_event(bool reset_origin) void Editor::really_quit_event_loop() { m_finish = false; - reposition_cursor(true); - fprintf(stderr, "\n"); - fflush(stderr); + { + OutputFileStream stderr_stream { stderr }; + reposition_cursor(stderr_stream, true); + stderr_stream.write("\n"sv.bytes()); + } auto string = line(); m_buffer.clear(); m_chars_touched_in_the_middle = buffer().size(); @@ -693,11 +701,14 @@ auto Editor::get_line(const String& prompt) -> Result<String, Editor::Error> reset(); strip_styles(true); - auto prompt_lines = max(current_prompt_metrics().line_metrics.size(), 1ul) - 1; - for (size_t i = 0; i < prompt_lines; ++i) - putc('\n', stderr); + { + OutputFileStream stderr_stream { stderr }; + auto prompt_lines = max(current_prompt_metrics().line_metrics.size(), 1ul) - 1; + for (size_t i = 0; i < prompt_lines; ++i) + stderr_stream.write("\n"sv.bytes()); - VT::move_relative(-prompt_lines, 0); + VT::move_relative(-static_cast<int>(prompt_lines), 0, stderr_stream); + } set_origin(); @@ -1089,7 +1100,8 @@ void Editor::handle_read_event() for (auto& view : completion_result.insert) insert(view); - reposition_cursor(); + OutputFileStream stderr_stream { stderr }; + reposition_cursor(stderr_stream); if (completion_result.style_to_apply.has_value()) { // Apply the style of the last suggestion. @@ -1111,7 +1123,7 @@ void Editor::handle_read_event() if (m_times_tab_pressed > 1) { if (m_suggestion_manager.count() > 0) { if (m_suggestion_display->cleanup()) - reposition_cursor(); + reposition_cursor(stderr_stream); m_suggestion_display->set_initial_prompt_lines(m_prompt_lines_at_suggestion_initiation); @@ -1166,7 +1178,8 @@ void Editor::cleanup_suggestions() // We probably have some suggestions drawn, // let's clean them up. if (m_suggestion_display->cleanup()) { - reposition_cursor(); + OutputFileStream stderr_stream { stderr }; + reposition_cursor(stderr_stream); m_refresh_needed = true; } m_suggestion_manager.reset(); @@ -1239,15 +1252,26 @@ void Editor::cleanup() if (new_lines < shown_lines) m_extra_forward_lines = max(shown_lines - new_lines, m_extra_forward_lines); - reposition_cursor(true); + OutputFileStream stderr_stream { stderr }; + reposition_cursor(stderr_stream, true); auto current_line = num_lines() - 1; - VT::clear_lines(current_line, m_extra_forward_lines); + VT::clear_lines(current_line, m_extra_forward_lines, stderr_stream); m_extra_forward_lines = 0; - reposition_cursor(); + reposition_cursor(stderr_stream); }; void Editor::refresh_display() { + DuplexMemoryStream output_stream; + ScopeGuard flush_stream { + [&] { + auto buffer = output_stream.copy_into_contiguous_buffer(); + if (buffer.is_empty()) + return; + fwrite(buffer.data(), sizeof(char), buffer.size(), stderr); + } + }; + auto has_cleaned_up = false; // Someone changed the window size, figure it out // and react to it, we might need to redraw. @@ -1272,20 +1296,19 @@ void Editor::refresh_display() if (m_origin_row + current_num_lines > m_num_lines) { if (current_num_lines > m_num_lines) { for (size_t i = 0; i < m_num_lines; ++i) - putc('\n', stderr); + output_stream.write("\n"sv.bytes()); m_origin_row = 0; } else { auto old_origin_row = m_origin_row; m_origin_row = m_num_lines - current_num_lines + 1; for (size_t i = 0; i < old_origin_row - m_origin_row; ++i) - putc('\n', stderr); + output_stream.write("\n"sv.bytes()); } - fflush(stderr); } // Do not call hook on pure cursor movement. if (m_cached_prompt_valid && !m_refresh_needed && m_pending_chars.size() == 0) { // Probably just moving around. - reposition_cursor(); + reposition_cursor(output_stream); m_cached_buffer_metrics = actual_rendered_string_metrics(buffer_view()); m_drawn_end_of_line_offset = m_buffer.size(); return; @@ -1298,15 +1321,12 @@ void Editor::refresh_display() if (!m_refresh_needed && m_cursor == m_buffer.size()) { // Just write the characters out and continue, // no need to refresh the entire line. - char null = 0; - m_pending_chars.append(&null, 1); - fputs((char*)m_pending_chars.data(), stderr); + output_stream.write(m_pending_chars); m_pending_chars.clear(); m_drawn_cursor = m_cursor; m_drawn_end_of_line_offset = m_buffer.size(); m_cached_buffer_metrics = actual_rendered_string_metrics(buffer_view()); m_drawn_spans = m_current_spans; - fflush(stderr); return; } } @@ -1328,11 +1348,11 @@ void Editor::refresh_display() style.unify_with(applicable_style.value); // Disable any style that should be turned off. - VT::apply_style(style, false); + VT::apply_style(style, output_stream, false); // Reapply styles for overlapping spans that include this one. style = find_applicable_style(i); - VT::apply_style(style, true); + VT::apply_style(style, output_stream, true); } if (starts.size() || anchored_starts.size()) { Style style; @@ -1344,11 +1364,11 @@ void Editor::refresh_display() style.unify_with(applicable_style.value); // Set new styles. - VT::apply_style(style, true); + VT::apply_style(style, output_stream, true); } }; - auto print_character_at = [this](size_t i) { + auto print_character_at = [&](size_t i) { StringBuilder builder; auto c = m_buffer[i]; bool should_print_masked = is_ascii_control(c) && c != '\n'; @@ -1361,26 +1381,26 @@ void Editor::refresh_display() builder.append(Utf32View { &c, 1 }); if (should_print_masked) - fputs("\033[7m", stderr); + output_stream.write("\033[7m"sv.bytes()); - fputs(builder.to_string().characters(), stderr); + output_stream.write(builder.string_view().bytes()); if (should_print_masked) - fputs("\033[27m", stderr); + output_stream.write("\033[27m"sv.bytes()); }; // If there have been no changes to previous sections of the line (style or text) // just append the new text with the appropriate styles. if (!m_always_refresh && m_cached_prompt_valid && m_chars_touched_in_the_middle == 0 && m_drawn_spans.contains_up_to_offset(m_current_spans, m_drawn_cursor)) { auto initial_style = find_applicable_style(m_drawn_end_of_line_offset); - VT::apply_style(initial_style); + VT::apply_style(initial_style, output_stream); for (size_t i = m_drawn_end_of_line_offset; i < m_buffer.size(); ++i) { apply_styles(i); print_character_at(i); } - VT::apply_style(Style::reset_style()); + VT::apply_style(Style::reset_style(), output_stream); m_pending_chars.clear(); m_refresh_needed = false; m_cached_buffer_metrics = actual_rendered_string_metrics(buffer_view()); @@ -1416,18 +1436,18 @@ void Editor::refresh_display() if (!has_cleaned_up) { cleanup(); } - VT::move_absolute(m_origin_row, m_origin_column); + VT::move_absolute(m_origin_row, m_origin_column, output_stream); - fputs(m_new_prompt.characters(), stderr); + output_stream.write(m_new_prompt.bytes()); - VT::clear_to_end_of_line(); + VT::clear_to_end_of_line(output_stream); StringBuilder builder; for (size_t i = 0; i < m_buffer.size(); ++i) { apply_styles(i); print_character_at(i); } - VT::apply_style(Style::reset_style()); // don't bleed to EOL + VT::apply_style(Style::reset_style(), output_stream); // don't bleed to EOL m_pending_chars.clear(); m_refresh_needed = false; @@ -1437,8 +1457,7 @@ void Editor::refresh_display() m_drawn_end_of_line_offset = m_buffer.size(); m_cached_prompt_valid = true; - reposition_cursor(); - fflush(stderr); + reposition_cursor(output_stream); } void Editor::strip_styles(bool strip_anchored) @@ -1454,7 +1473,7 @@ void Editor::strip_styles(bool strip_anchored) m_refresh_needed = true; } -void Editor::reposition_cursor(bool to_end) +void Editor::reposition_cursor(OutputStream& stream, bool to_end) { auto cursor = m_cursor; auto saved_cursor = m_cursor; @@ -1470,18 +1489,17 @@ void Editor::reposition_cursor(bool to_end) ensure_free_lines_from_origin(line); VERIFY(column + m_origin_column <= m_num_columns); - VT::move_absolute(line + m_origin_row, column + m_origin_column); + VT::move_absolute(line + m_origin_row, column + m_origin_column, stream); m_cursor = saved_cursor; } -void VT::move_absolute(u32 row, u32 col) +void VT::move_absolute(u32 row, u32 col, OutputStream& stream) { - fprintf(stderr, "\033[%d;%dH", row, col); - fflush(stderr); + stream.write(String::formatted("\033[{};{}H", row, col).bytes()); } -void VT::move_relative(int row, int col) +void VT::move_relative(int row, int col, OutputStream& stream) { char x_op = 'A', y_op = 'D'; @@ -1495,9 +1513,9 @@ void VT::move_relative(int row, int col) col = -col; if (row > 0) - fprintf(stderr, "\033[%d%c", row, x_op); + stream.write(String::formatted("\033[{}{}", row, x_op).bytes()); if (col > 0) - fprintf(stderr, "\033[%d%c", col, y_op); + stream.write(String::formatted("\033[{}{}", col, y_op).bytes()); } Style Editor::find_applicable_style(size_t offset) const @@ -1623,52 +1641,52 @@ String Style::to_string() const return builder.build(); } -void VT::apply_style(const Style& style, bool is_starting) +void VT::apply_style(const Style& style, OutputStream& stream, bool is_starting) { if (is_starting) { - fprintf(stderr, - "\033[%d;%d;%dm%s%s%s", + stream.write(String::formatted("\033[{};{};{}m{}{}{}", style.bold() ? 1 : 22, style.underline() ? 4 : 24, style.italic() ? 3 : 23, - style.background().to_vt_escape().characters(), - style.foreground().to_vt_escape().characters(), - style.hyperlink().to_vt_escape(true).characters()); + style.background().to_vt_escape(), + style.foreground().to_vt_escape(), + style.hyperlink().to_vt_escape(true)) + .bytes()); } else { - fprintf(stderr, "%s", style.hyperlink().to_vt_escape(false).characters()); + stream.write(style.hyperlink().to_vt_escape(false).bytes()); } } -void VT::clear_lines(size_t count_above, size_t count_below) +void VT::clear_lines(size_t count_above, size_t count_below, OutputStream& stream) { if (count_below + count_above == 0) { - fputs("\033[2K", stderr); + stream.write("\033[2K"sv.bytes()); } else { // Go down count_below lines. if (count_below > 0) - fprintf(stderr, "\033[%dB", (int)count_below); + stream.write(String::formatted("\033[{}B", count_below).bytes()); // Then clear lines going upwards. - for (size_t i = count_below + count_above; i > 0; --i) - fputs(i == 1 ? "\033[2K" : "\033[2K\033[A", stderr); + for (size_t i = count_below + count_above; i > 0; --i) { + stream.write("\033[2K"sv.bytes()); + if (i != 1) + stream.write("\033[A"sv.bytes()); + } } } -void VT::save_cursor() +void VT::save_cursor(OutputStream& stream) { - fputs("\033[s", stderr); - fflush(stderr); + stream.write("\033[s"sv.bytes()); } -void VT::restore_cursor() +void VT::restore_cursor(OutputStream& stream) { - fputs("\033[u", stderr); - fflush(stderr); + stream.write("\033[u"sv.bytes()); } -void VT::clear_to_end_of_line() +void VT::clear_to_end_of_line(OutputStream& stream) { - fputs("\033[K", stderr); - fflush(stderr); + stream.write("\033[K"sv.bytes()); } StringMetrics Editor::actual_rendered_string_metrics(const StringView& string) diff --git a/Userland/Libraries/LibLine/Editor.h b/Userland/Libraries/LibLine/Editor.h index 82ab418fd9..bf32b1bbef 100644 --- a/Userland/Libraries/LibLine/Editor.h +++ b/Userland/Libraries/LibLine/Editor.h @@ -383,7 +383,7 @@ private: } void recalculate_origin(); - void reposition_cursor(bool to_end = false); + void reposition_cursor(OutputStream&, bool to_end = false); struct CodepointRange { size_t start { 0 }; diff --git a/Userland/Libraries/LibLine/InternalFunctions.cpp b/Userland/Libraries/LibLine/InternalFunctions.cpp index 834335d46c..20a8edb6d2 100644 --- a/Userland/Libraries/LibLine/InternalFunctions.cpp +++ b/Userland/Libraries/LibLine/InternalFunctions.cpp @@ -342,12 +342,13 @@ void Editor::enter_search() auto& search_string = search_string_result.value(); // Manually cleanup the search line. - reposition_cursor(); + OutputFileStream stderr_stream { stderr }; + reposition_cursor(stderr_stream); auto search_metrics = actual_rendered_string_metrics(search_string); auto metrics = actual_rendered_string_metrics(search_prompt); - VT::clear_lines(0, metrics.lines_with_addition(search_metrics, m_num_columns) + search_end_row - m_origin_row - 1); + VT::clear_lines(0, metrics.lines_with_addition(search_metrics, m_num_columns) + search_end_row - m_origin_row - 1, stderr_stream); - reposition_cursor(); + reposition_cursor(stderr_stream); m_refresh_needed = true; m_cached_prompt_valid = false; @@ -432,8 +433,9 @@ void Editor::go_end() void Editor::clear_screen() { - fprintf(stderr, "\033[3J\033[H\033[2J"); // Clear screen. - VT::move_absolute(1, 1); + warn("\033[3J\033[H\033[2J"); + OutputFileStream stream { stderr }; + VT::move_absolute(1, 1, stream); set_origin(1, 1); m_refresh_needed = true; m_cached_prompt_valid = false; diff --git a/Userland/Libraries/LibLine/VT.h b/Userland/Libraries/LibLine/VT.h index 681edf0353..da5332dc29 100644 --- a/Userland/Libraries/LibLine/VT.h +++ b/Userland/Libraries/LibLine/VT.h @@ -12,13 +12,13 @@ namespace Line { namespace VT { -void save_cursor(); -void restore_cursor(); -void clear_to_end_of_line(); -void clear_lines(size_t count_above, size_t count_below = 0); -void move_relative(int x, int y); -void move_absolute(u32 x, u32 y); -void apply_style(const Style&, bool is_starting = true); +void save_cursor(OutputStream&); +void restore_cursor(OutputStream&); +void clear_to_end_of_line(OutputStream&); +void clear_lines(size_t count_above, size_t count_below, OutputStream&); +void move_relative(int x, int y, OutputStream&); +void move_absolute(u32 x, u32 y, OutputStream&); +void apply_style(const Style&, OutputStream&, bool is_starting = true); } } diff --git a/Userland/Libraries/LibLine/XtermSuggestionDisplay.cpp b/Userland/Libraries/LibLine/XtermSuggestionDisplay.cpp index a691bba5df..6e5a39bde0 100644 --- a/Userland/Libraries/LibLine/XtermSuggestionDisplay.cpp +++ b/Userland/Libraries/LibLine/XtermSuggestionDisplay.cpp @@ -5,6 +5,7 @@ */ #include <AK/BinarySearch.h> +#include <AK/FileStream.h> #include <AK/Function.h> #include <AK/StringBuilder.h> #include <LibLine/SuggestionDisplay.h> @@ -17,6 +18,8 @@ void XtermSuggestionDisplay::display(const SuggestionManager& manager) { did_display(); + OutputFileStream stderr_stream { stderr }; + size_t longest_suggestion_length = 0; size_t longest_suggestion_byte_length = 0; @@ -30,9 +33,9 @@ void XtermSuggestionDisplay::display(const SuggestionManager& manager) size_t num_printed = 0; size_t lines_used = 1; - VT::save_cursor(); - VT::clear_lines(0, m_lines_used_for_last_suggestions); - VT::restore_cursor(); + VT::save_cursor(stderr_stream); + VT::clear_lines(0, m_lines_used_for_last_suggestions, stderr_stream); + VT::restore_cursor(stderr_stream); auto spans_entire_line { false }; Vector<StringMetrics::LineMetrics> lines; @@ -45,14 +48,13 @@ void XtermSuggestionDisplay::display(const SuggestionManager& manager) // We should make enough space for the biggest entry in // the suggestion list to fit in the prompt line. auto start = max_line_count - m_prompt_lines_at_suggestion_initiation; - for (size_t i = start; i < max_line_count; ++i) { - fputc('\n', stderr); - } + for (size_t i = start; i < max_line_count; ++i) + stderr_stream.write("\n"sv.bytes()); lines_used += max_line_count; longest_suggestion_length = 0; } - VT::move_absolute(max_line_count + m_origin_row, 1); + VT::move_absolute(max_line_count + m_origin_row, 1, stderr_stream); if (m_pages.is_empty()) { size_t num_printed = 0; @@ -89,14 +91,13 @@ void XtermSuggestionDisplay::display(const SuggestionManager& manager) auto page_index = fit_to_page_boundary(manager.next_index()); manager.set_start_index(m_pages[page_index].start); - manager.for_each_suggestion([&](auto& suggestion, auto index) { size_t next_column = num_printed + suggestion.text_view.length() + longest_suggestion_length + 2; if (next_column > m_num_columns) { auto lines = (suggestion.text_view.length() + m_num_columns - 1) / m_num_columns; lines_used += lines; - fputc('\n', stderr); + stderr_stream.write("\n"sv.bytes()); num_printed = 0; } @@ -107,22 +108,19 @@ void XtermSuggestionDisplay::display(const SuggestionManager& manager) // Only apply color to the selection if something is *actually* added to the buffer. if (manager.is_current_suggestion_complete() && index == manager.next_index()) { - VT::apply_style({ Style::Foreground(Style::XtermColor::Blue) }); - fflush(stderr); + VT::apply_style({ Style::Foreground(Style::XtermColor::Blue) }, stderr_stream); } if (spans_entire_line) { num_printed += m_num_columns; - fprintf(stderr, "%s", suggestion.text_string.characters()); + stderr_stream.write(suggestion.text_string.bytes()); } else { - fprintf(stderr, "%-*s", static_cast<int>(longest_suggestion_byte_length) + 2, suggestion.text_string.characters()); + stderr_stream.write(String::formatted("{: <{}}", suggestion.text_string, longest_suggestion_byte_length + 2).bytes()); num_printed += longest_suggestion_length + 2; } - if (manager.is_current_suggestion_complete() && index == manager.next_index()) { - VT::apply_style(Style::reset_style()); - fflush(stderr); - } + if (manager.is_current_suggestion_complete() && index == manager.next_index()) + VT::apply_style(Style::reset_style(), stderr_stream); return IterationDecision::Continue; }); @@ -140,17 +138,14 @@ void XtermSuggestionDisplay::display(const SuggestionManager& manager) if (string.length() > m_num_columns - 1) { // This would overflow into the next line, so just don't print an indicator. - fflush(stderr); return; } - VT::move_absolute(m_origin_row + lines_used, m_num_columns - string.length() - 1); - VT::apply_style({ Style::Background(Style::XtermColor::Green) }); - fputs(string.characters(), stderr); - VT::apply_style(Style::reset_style()); + VT::move_absolute(m_origin_row + lines_used, m_num_columns - string.length() - 1, stderr_stream); + VT::apply_style({ Style::Background(Style::XtermColor::Green) }, stderr_stream); + stderr_stream.write(string.bytes()); + VT::apply_style(Style::reset_style(), stderr_stream); } - - fflush(stderr); } bool XtermSuggestionDisplay::cleanup() @@ -158,7 +153,8 @@ bool XtermSuggestionDisplay::cleanup() did_cleanup(); if (m_lines_used_for_last_suggestions) { - VT::clear_lines(0, m_lines_used_for_last_suggestions); + OutputFileStream stderr_stream { stderr }; + VT::clear_lines(0, m_lines_used_for_last_suggestions, stderr_stream); m_lines_used_for_last_suggestions = 0; return true; } |