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:
parent
0f6654fef2
commit
3184086679
5 changed files with 126 additions and 110 deletions
|
@ -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)
|
||||||
|
|
|
@ -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 };
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue