summaryrefslogtreecommitdiff
path: root/Userland/Libraries/LibLine
diff options
context:
space:
mode:
authorAli Mohammad Pur <ali.mpfard@gmail.com>2021-07-19 23:12:28 +0430
committerAli Mohammad Pur <Ali.mpfard@gmail.com>2021-07-20 11:55:42 +0430
commit31840866798a1e369e9824689c46d9e7ee0e7dbd (patch)
tree71a9c0c78068fc9029adea9aba438f93a2803faa /Userland/Libraries/LibLine
parent0f6654fef289421bafaacbae1aa5c04bf8292bd8 (diff)
downloadserenity-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/LibLine')
-rw-r--r--Userland/Libraries/LibLine/Editor.cpp162
-rw-r--r--Userland/Libraries/LibLine/Editor.h2
-rw-r--r--Userland/Libraries/LibLine/InternalFunctions.cpp12
-rw-r--r--Userland/Libraries/LibLine/VT.h14
-rw-r--r--Userland/Libraries/LibLine/XtermSuggestionDisplay.cpp46
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;
}