1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-26 03:37:43 +00:00

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 :^)
This commit is contained in:
Ali Mohammad Pur 2021-07-19 23:12:28 +04:30 committed by Ali Mohammad Pur
parent 0f6654fef2
commit 3184086679
5 changed files with 126 additions and 110 deletions

View file

@ -8,8 +8,10 @@
#include "Editor.h" #include "Editor.h"
#include <AK/CharacterTypes.h> #include <AK/CharacterTypes.h>
#include <AK/Debug.h> #include <AK/Debug.h>
#include <AK/FileStream.h>
#include <AK/GenericLexer.h> #include <AK/GenericLexer.h>
#include <AK/JsonObject.h> #include <AK/JsonObject.h>
#include <AK/MemoryStream.h>
#include <AK/ScopeGuard.h> #include <AK/ScopeGuard.h>
#include <AK/ScopedValueRollback.h> #include <AK/ScopedValueRollback.h>
#include <AK/StringBuilder.h> #include <AK/StringBuilder.h>
@ -578,11 +580,13 @@ void Editor::interrupted()
return; return;
m_finish = false; m_finish = false;
reposition_cursor(true); {
if (m_suggestion_display->cleanup()) OutputFileStream stderr_stream { stderr };
reposition_cursor(true); reposition_cursor(stderr_stream, true);
fprintf(stderr, "\n"); if (m_suggestion_display->cleanup())
fflush(stderr); reposition_cursor(stderr_stream, true);
stderr_stream.write("\n"sv.bytes());
}
m_buffer.clear(); m_buffer.clear();
m_chars_touched_in_the_middle = buffer().size(); m_chars_touched_in_the_middle = buffer().size();
m_is_editing = false; m_is_editing = false;
@ -619,9 +623,11 @@ void Editor::handle_resize_event(bool reset_origin)
set_origin(m_origin_row, 1); 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); m_suggestion_display->redisplay(m_suggestion_manager, m_num_lines, m_num_columns);
reposition_cursor(); reposition_cursor(stderr_stream);
if (m_is_searching) if (m_is_searching)
m_search_editor->resized(); m_search_editor->resized();
@ -630,9 +636,11 @@ void Editor::handle_resize_event(bool reset_origin)
void Editor::really_quit_event_loop() void Editor::really_quit_event_loop()
{ {
m_finish = false; m_finish = false;
reposition_cursor(true); {
fprintf(stderr, "\n"); OutputFileStream stderr_stream { stderr };
fflush(stderr); reposition_cursor(stderr_stream, true);
stderr_stream.write("\n"sv.bytes());
}
auto string = line(); auto string = line();
m_buffer.clear(); m_buffer.clear();
m_chars_touched_in_the_middle = buffer().size(); m_chars_touched_in_the_middle = buffer().size();
@ -693,11 +701,14 @@ auto Editor::get_line(const String& prompt) -> Result<String, Editor::Error>
reset(); reset();
strip_styles(true); strip_styles(true);
auto prompt_lines = max(current_prompt_metrics().line_metrics.size(), 1ul) - 1; {
for (size_t i = 0; i < prompt_lines; ++i) OutputFileStream stderr_stream { stderr };
putc('\n', 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(); set_origin();
@ -1089,7 +1100,8 @@ void Editor::handle_read_event()
for (auto& view : completion_result.insert) for (auto& view : completion_result.insert)
insert(view); insert(view);
reposition_cursor(); OutputFileStream stderr_stream { stderr };
reposition_cursor(stderr_stream);
if (completion_result.style_to_apply.has_value()) { if (completion_result.style_to_apply.has_value()) {
// Apply the style of the last suggestion. // Apply the style of the last suggestion.
@ -1111,7 +1123,7 @@ void Editor::handle_read_event()
if (m_times_tab_pressed > 1) { if (m_times_tab_pressed > 1) {
if (m_suggestion_manager.count() > 0) { if (m_suggestion_manager.count() > 0) {
if (m_suggestion_display->cleanup()) if (m_suggestion_display->cleanup())
reposition_cursor(); reposition_cursor(stderr_stream);
m_suggestion_display->set_initial_prompt_lines(m_prompt_lines_at_suggestion_initiation); 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, // We probably have some suggestions drawn,
// let's clean them up. // let's clean them up.
if (m_suggestion_display->cleanup()) { if (m_suggestion_display->cleanup()) {
reposition_cursor(); OutputFileStream stderr_stream { stderr };
reposition_cursor(stderr_stream);
m_refresh_needed = true; m_refresh_needed = true;
} }
m_suggestion_manager.reset(); m_suggestion_manager.reset();
@ -1239,15 +1252,26 @@ void Editor::cleanup()
if (new_lines < shown_lines) if (new_lines < shown_lines)
m_extra_forward_lines = max(shown_lines - new_lines, m_extra_forward_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; 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; m_extra_forward_lines = 0;
reposition_cursor(); reposition_cursor(stderr_stream);
}; };
void Editor::refresh_display() 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; auto has_cleaned_up = false;
// Someone changed the window size, figure it out // Someone changed the window size, figure it out
// and react to it, we might need to redraw. // 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 (m_origin_row + current_num_lines > m_num_lines) {
if (current_num_lines > m_num_lines) { if (current_num_lines > m_num_lines) {
for (size_t i = 0; i < m_num_lines; ++i) for (size_t i = 0; i < m_num_lines; ++i)
putc('\n', stderr); output_stream.write("\n"sv.bytes());
m_origin_row = 0; m_origin_row = 0;
} else { } else {
auto old_origin_row = m_origin_row; auto old_origin_row = m_origin_row;
m_origin_row = m_num_lines - current_num_lines + 1; m_origin_row = m_num_lines - current_num_lines + 1;
for (size_t i = 0; i < old_origin_row - m_origin_row; ++i) 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. // Do not call hook on pure cursor movement.
if (m_cached_prompt_valid && !m_refresh_needed && m_pending_chars.size() == 0) { if (m_cached_prompt_valid && !m_refresh_needed && m_pending_chars.size() == 0) {
// Probably just moving around. // Probably just moving around.
reposition_cursor(); reposition_cursor(output_stream);
m_cached_buffer_metrics = actual_rendered_string_metrics(buffer_view()); m_cached_buffer_metrics = actual_rendered_string_metrics(buffer_view());
m_drawn_end_of_line_offset = m_buffer.size(); m_drawn_end_of_line_offset = m_buffer.size();
return; return;
@ -1298,15 +1321,12 @@ void Editor::refresh_display()
if (!m_refresh_needed && m_cursor == m_buffer.size()) { if (!m_refresh_needed && m_cursor == m_buffer.size()) {
// Just write the characters out and continue, // Just write the characters out and continue,
// no need to refresh the entire line. // no need to refresh the entire line.
char null = 0; output_stream.write(m_pending_chars);
m_pending_chars.append(&null, 1);
fputs((char*)m_pending_chars.data(), stderr);
m_pending_chars.clear(); m_pending_chars.clear();
m_drawn_cursor = m_cursor; m_drawn_cursor = m_cursor;
m_drawn_end_of_line_offset = m_buffer.size(); m_drawn_end_of_line_offset = m_buffer.size();
m_cached_buffer_metrics = actual_rendered_string_metrics(buffer_view()); m_cached_buffer_metrics = actual_rendered_string_metrics(buffer_view());
m_drawn_spans = m_current_spans; m_drawn_spans = m_current_spans;
fflush(stderr);
return; return;
} }
} }
@ -1328,11 +1348,11 @@ void Editor::refresh_display()
style.unify_with(applicable_style.value); style.unify_with(applicable_style.value);
// Disable any style that should be turned off. // 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. // Reapply styles for overlapping spans that include this one.
style = find_applicable_style(i); style = find_applicable_style(i);
VT::apply_style(style, true); VT::apply_style(style, output_stream, true);
} }
if (starts.size() || anchored_starts.size()) { if (starts.size() || anchored_starts.size()) {
Style style; Style style;
@ -1344,11 +1364,11 @@ void Editor::refresh_display()
style.unify_with(applicable_style.value); style.unify_with(applicable_style.value);
// Set new styles. // 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; StringBuilder builder;
auto c = m_buffer[i]; auto c = m_buffer[i];
bool should_print_masked = is_ascii_control(c) && c != '\n'; bool should_print_masked = is_ascii_control(c) && c != '\n';
@ -1361,26 +1381,26 @@ void Editor::refresh_display()
builder.append(Utf32View { &c, 1 }); builder.append(Utf32View { &c, 1 });
if (should_print_masked) 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) 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) // If there have been no changes to previous sections of the line (style or text)
// just append the new text with the appropriate styles. // 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)) { 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); 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) { for (size_t i = m_drawn_end_of_line_offset; i < m_buffer.size(); ++i) {
apply_styles(i); apply_styles(i);
print_character_at(i); print_character_at(i);
} }
VT::apply_style(Style::reset_style()); VT::apply_style(Style::reset_style(), output_stream);
m_pending_chars.clear(); m_pending_chars.clear();
m_refresh_needed = false; m_refresh_needed = false;
m_cached_buffer_metrics = actual_rendered_string_metrics(buffer_view()); m_cached_buffer_metrics = actual_rendered_string_metrics(buffer_view());
@ -1416,18 +1436,18 @@ void Editor::refresh_display()
if (!has_cleaned_up) { if (!has_cleaned_up) {
cleanup(); 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; StringBuilder builder;
for (size_t i = 0; i < m_buffer.size(); ++i) { for (size_t i = 0; i < m_buffer.size(); ++i) {
apply_styles(i); apply_styles(i);
print_character_at(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_pending_chars.clear();
m_refresh_needed = false; m_refresh_needed = false;
@ -1437,8 +1457,7 @@ void Editor::refresh_display()
m_drawn_end_of_line_offset = m_buffer.size(); m_drawn_end_of_line_offset = m_buffer.size();
m_cached_prompt_valid = true; m_cached_prompt_valid = true;
reposition_cursor(); reposition_cursor(output_stream);
fflush(stderr);
} }
void Editor::strip_styles(bool strip_anchored) void Editor::strip_styles(bool strip_anchored)
@ -1454,7 +1473,7 @@ void Editor::strip_styles(bool strip_anchored)
m_refresh_needed = true; 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 cursor = m_cursor;
auto saved_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); ensure_free_lines_from_origin(line);
VERIFY(column + m_origin_column <= m_num_columns); 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; 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); stream.write(String::formatted("\033[{};{}H", row, col).bytes());
fflush(stderr);
} }
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'; char x_op = 'A', y_op = 'D';
@ -1495,9 +1513,9 @@ void VT::move_relative(int row, int col)
col = -col; col = -col;
if (row > 0) if (row > 0)
fprintf(stderr, "\033[%d%c", row, x_op); stream.write(String::formatted("\033[{}{}", row, x_op).bytes());
if (col > 0) 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 Style Editor::find_applicable_style(size_t offset) const
@ -1623,52 +1641,52 @@ String Style::to_string() const
return builder.build(); 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) { if (is_starting) {
fprintf(stderr, stream.write(String::formatted("\033[{};{};{}m{}{}{}",
"\033[%d;%d;%dm%s%s%s",
style.bold() ? 1 : 22, style.bold() ? 1 : 22,
style.underline() ? 4 : 24, style.underline() ? 4 : 24,
style.italic() ? 3 : 23, style.italic() ? 3 : 23,
style.background().to_vt_escape().characters(), style.background().to_vt_escape(),
style.foreground().to_vt_escape().characters(), style.foreground().to_vt_escape(),
style.hyperlink().to_vt_escape(true).characters()); style.hyperlink().to_vt_escape(true))
.bytes());
} else { } 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) { if (count_below + count_above == 0) {
fputs("\033[2K", stderr); stream.write("\033[2K"sv.bytes());
} else { } else {
// Go down count_below lines. // Go down count_below lines.
if (count_below > 0) 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. // Then clear lines going upwards.
for (size_t i = count_below + count_above; i > 0; --i) for (size_t i = count_below + count_above; i > 0; --i) {
fputs(i == 1 ? "\033[2K" : "\033[2K\033[A", stderr); 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); stream.write("\033[s"sv.bytes());
fflush(stderr);
} }
void VT::restore_cursor() void VT::restore_cursor(OutputStream& stream)
{ {
fputs("\033[u", stderr); stream.write("\033[u"sv.bytes());
fflush(stderr);
} }
void VT::clear_to_end_of_line() void VT::clear_to_end_of_line(OutputStream& stream)
{ {
fputs("\033[K", stderr); stream.write("\033[K"sv.bytes());
fflush(stderr);
} }
StringMetrics Editor::actual_rendered_string_metrics(const StringView& string) StringMetrics Editor::actual_rendered_string_metrics(const StringView& string)

View file

@ -383,7 +383,7 @@ private:
} }
void recalculate_origin(); void recalculate_origin();
void reposition_cursor(bool to_end = false); void reposition_cursor(OutputStream&, bool to_end = false);
struct CodepointRange { struct CodepointRange {
size_t start { 0 }; size_t start { 0 };

View file

@ -342,12 +342,13 @@ void Editor::enter_search()
auto& search_string = search_string_result.value(); auto& search_string = search_string_result.value();
// Manually cleanup the search line. // 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 search_metrics = actual_rendered_string_metrics(search_string);
auto metrics = actual_rendered_string_metrics(search_prompt); 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_refresh_needed = true;
m_cached_prompt_valid = false; m_cached_prompt_valid = false;
@ -432,8 +433,9 @@ void Editor::go_end()
void Editor::clear_screen() void Editor::clear_screen()
{ {
fprintf(stderr, "\033[3J\033[H\033[2J"); // Clear screen. warn("\033[3J\033[H\033[2J");
VT::move_absolute(1, 1); OutputFileStream stream { stderr };
VT::move_absolute(1, 1, stream);
set_origin(1, 1); set_origin(1, 1);
m_refresh_needed = true; m_refresh_needed = true;
m_cached_prompt_valid = false; m_cached_prompt_valid = false;

View file

@ -12,13 +12,13 @@
namespace Line { namespace Line {
namespace VT { namespace VT {
void save_cursor(); void save_cursor(OutputStream&);
void restore_cursor(); void restore_cursor(OutputStream&);
void clear_to_end_of_line(); void clear_to_end_of_line(OutputStream&);
void clear_lines(size_t count_above, size_t count_below = 0); void clear_lines(size_t count_above, size_t count_below, OutputStream&);
void move_relative(int x, int y); void move_relative(int x, int y, OutputStream&);
void move_absolute(u32 x, u32 y); void move_absolute(u32 x, u32 y, OutputStream&);
void apply_style(const Style&, bool is_starting = true); void apply_style(const Style&, OutputStream&, bool is_starting = true);
} }
} }

View file

@ -5,6 +5,7 @@
*/ */
#include <AK/BinarySearch.h> #include <AK/BinarySearch.h>
#include <AK/FileStream.h>
#include <AK/Function.h> #include <AK/Function.h>
#include <AK/StringBuilder.h> #include <AK/StringBuilder.h>
#include <LibLine/SuggestionDisplay.h> #include <LibLine/SuggestionDisplay.h>
@ -17,6 +18,8 @@ void XtermSuggestionDisplay::display(const SuggestionManager& manager)
{ {
did_display(); did_display();
OutputFileStream stderr_stream { stderr };
size_t longest_suggestion_length = 0; size_t longest_suggestion_length = 0;
size_t longest_suggestion_byte_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 num_printed = 0;
size_t lines_used = 1; size_t lines_used = 1;
VT::save_cursor(); VT::save_cursor(stderr_stream);
VT::clear_lines(0, m_lines_used_for_last_suggestions); VT::clear_lines(0, m_lines_used_for_last_suggestions, stderr_stream);
VT::restore_cursor(); VT::restore_cursor(stderr_stream);
auto spans_entire_line { false }; auto spans_entire_line { false };
Vector<StringMetrics::LineMetrics> lines; Vector<StringMetrics::LineMetrics> lines;
@ -45,14 +48,13 @@ void XtermSuggestionDisplay::display(const SuggestionManager& manager)
// We should make enough space for the biggest entry in // We should make enough space for the biggest entry in
// the suggestion list to fit in the prompt line. // the suggestion list to fit in the prompt line.
auto start = max_line_count - m_prompt_lines_at_suggestion_initiation; auto start = max_line_count - m_prompt_lines_at_suggestion_initiation;
for (size_t i = start; i < max_line_count; ++i) { for (size_t i = start; i < max_line_count; ++i)
fputc('\n', stderr); stderr_stream.write("\n"sv.bytes());
}
lines_used += max_line_count; lines_used += max_line_count;
longest_suggestion_length = 0; 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()) { if (m_pages.is_empty()) {
size_t num_printed = 0; 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()); auto page_index = fit_to_page_boundary(manager.next_index());
manager.set_start_index(m_pages[page_index].start); manager.set_start_index(m_pages[page_index].start);
manager.for_each_suggestion([&](auto& suggestion, auto index) { manager.for_each_suggestion([&](auto& suggestion, auto index) {
size_t next_column = num_printed + suggestion.text_view.length() + longest_suggestion_length + 2; size_t next_column = num_printed + suggestion.text_view.length() + longest_suggestion_length + 2;
if (next_column > m_num_columns) { if (next_column > m_num_columns) {
auto lines = (suggestion.text_view.length() + m_num_columns - 1) / m_num_columns; auto lines = (suggestion.text_view.length() + m_num_columns - 1) / m_num_columns;
lines_used += lines; lines_used += lines;
fputc('\n', stderr); stderr_stream.write("\n"sv.bytes());
num_printed = 0; 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. // Only apply color to the selection if something is *actually* added to the buffer.
if (manager.is_current_suggestion_complete() && index == manager.next_index()) { if (manager.is_current_suggestion_complete() && index == manager.next_index()) {
VT::apply_style({ Style::Foreground(Style::XtermColor::Blue) }); VT::apply_style({ Style::Foreground(Style::XtermColor::Blue) }, stderr_stream);
fflush(stderr);
} }
if (spans_entire_line) { if (spans_entire_line) {
num_printed += m_num_columns; num_printed += m_num_columns;
fprintf(stderr, "%s", suggestion.text_string.characters()); stderr_stream.write(suggestion.text_string.bytes());
} else { } 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; num_printed += longest_suggestion_length + 2;
} }
if (manager.is_current_suggestion_complete() && index == manager.next_index()) { if (manager.is_current_suggestion_complete() && index == manager.next_index())
VT::apply_style(Style::reset_style()); VT::apply_style(Style::reset_style(), stderr_stream);
fflush(stderr);
}
return IterationDecision::Continue; return IterationDecision::Continue;
}); });
@ -140,17 +138,14 @@ void XtermSuggestionDisplay::display(const SuggestionManager& manager)
if (string.length() > m_num_columns - 1) { if (string.length() > m_num_columns - 1) {
// This would overflow into the next line, so just don't print an indicator. // This would overflow into the next line, so just don't print an indicator.
fflush(stderr);
return; return;
} }
VT::move_absolute(m_origin_row + lines_used, m_num_columns - string.length() - 1); VT::move_absolute(m_origin_row + lines_used, m_num_columns - string.length() - 1, stderr_stream);
VT::apply_style({ Style::Background(Style::XtermColor::Green) }); VT::apply_style({ Style::Background(Style::XtermColor::Green) }, stderr_stream);
fputs(string.characters(), stderr); stderr_stream.write(string.bytes());
VT::apply_style(Style::reset_style()); VT::apply_style(Style::reset_style(), stderr_stream);
} }
fflush(stderr);
} }
bool XtermSuggestionDisplay::cleanup() bool XtermSuggestionDisplay::cleanup()
@ -158,7 +153,8 @@ bool XtermSuggestionDisplay::cleanup()
did_cleanup(); did_cleanup();
if (m_lines_used_for_last_suggestions) { 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; m_lines_used_for_last_suggestions = 0;
return true; return true;
} }