1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-24 16:47:42 +00:00

LibLine: Use Core::EventLoop for outer read loop

This commit changes LibLine's internal structure to work in an event
loop, and as a result, also switches it to being a Core::Object.
This commit is contained in:
AnotherTest 2020-05-26 15:04:39 +04:30 committed by Andreas Kling
parent 8e6df3949d
commit 70a213a6ec
7 changed files with 583 additions and 517 deletions

View file

@ -44,7 +44,7 @@
#include <string.h> #include <string.h>
#include <unistd.h> #include <unistd.h>
static Line::Editor editor {}; RefPtr<Line::Editor> editor;
OwnPtr<DebugSession> g_debug_session; OwnPtr<DebugSession> g_debug_session;
@ -173,6 +173,8 @@ void print_help()
int main(int argc, char** argv) int main(int argc, char** argv)
{ {
editor = Line::Editor::construct();
if (pledge("stdio proc exec rpath tty sigaction", nullptr) < 0) { if (pledge("stdio proc exec rpath tty sigaction", nullptr) < 0) {
perror("pledge"); perror("pledge");
return 1; return 1;
@ -236,7 +238,7 @@ int main(int argc, char** argv)
} }
for (;;) { for (;;) {
auto command_result = editor.get_line("(sdb) "); auto command_result = editor->get_line("(sdb) ");
if (command_result.is_error()) if (command_result.is_error())
return DebugSession::DebugDecision::Detach; return DebugSession::DebugDecision::Detach;
@ -246,8 +248,8 @@ int main(int argc, char** argv)
bool success = false; bool success = false;
Optional<DebugSession::DebugDecision> decision; Optional<DebugSession::DebugDecision> decision;
if (command.is_empty() && !editor.history().is_empty()) { if (command.is_empty() && !editor->history().is_empty()) {
command = editor.history().last(); command = editor->history().last();
} }
if (command == "cont") { if (command == "cont") {
decision = DebugSession::DebugDecision::Continue; decision = DebugSession::DebugDecision::Continue;
@ -276,8 +278,8 @@ int main(int argc, char** argv)
if (success && !command.is_empty()) { if (success && !command.is_empty()) {
// Don't add repeated commands to history // Don't add repeated commands to history
if (editor.history().is_empty() || editor.history().last() != command) if (editor->history().is_empty() || editor->history().last() != command)
editor.add_to_history(command); editor->add_to_history(command);
} }
if (!success) { if (!success) {
print_help(); print_help();

View file

@ -25,9 +25,13 @@
*/ */
#include "Editor.h" #include "Editor.h"
#include <AK/JsonObject.h>
#include <AK/StringBuilder.h> #include <AK/StringBuilder.h>
#include <AK/Utf32View.h> #include <AK/Utf32View.h>
#include <AK/Utf8View.h> #include <AK/Utf8View.h>
#include <LibCore/Event.h>
#include <LibCore/EventLoop.h>
#include <LibCore/Notifier.h>
#include <ctype.h> #include <ctype.h>
#include <stdio.h> #include <stdio.h>
#include <sys/ioctl.h> #include <sys/ioctl.h>
@ -252,7 +256,7 @@ void Editor::initialize()
m_initialized = true; m_initialized = true;
} }
Result<String, Editor::Error> Editor::get_line(const String& prompt) auto Editor::get_line(const String& prompt) -> Result<String, Editor::Error>
{ {
initialize(); initialize();
m_is_editing = true; m_is_editing = true;
@ -279,13 +283,22 @@ Result<String, Editor::Error> Editor::get_line(const String& prompt)
strip_styles(true); strip_styles(true);
m_history_cursor = m_history.size(); m_history_cursor = m_history.size();
for (;;) {
refresh_display();
Core::EventLoop loop;
m_notifier = Core::Notifier::construct(STDIN_FILENO, Core::Notifier::Read);
add_child(*m_notifier);
m_notifier->on_ready_to_read = [&] {
handle_read_event();
if (m_always_refresh) if (m_always_refresh)
m_refresh_needed = true; m_refresh_needed = true;
refresh_display(); refresh_display();
if (m_input_error.has_value()) {
return m_input_error.value();
}
if (m_finish) { if (m_finish) {
m_finish = false; m_finish = false;
printf("\n"); printf("\n");
@ -294,8 +307,25 @@ Result<String, Editor::Error> Editor::get_line(const String& prompt)
m_buffer.clear(); m_buffer.clear();
m_is_editing = false; m_is_editing = false;
restore(); restore();
return string;
m_returned_line = string;
m_notifier->set_event_mask(Core::Notifier::None);
deferred_invoke([this](auto&) {
remove_child(*m_notifier);
m_notifier = nullptr;
Core::EventLoop::current().quit(0);
});
} }
};
loop.exec();
return m_input_error.has_value() ? Result<String, Editor::Error> { m_input_error.value() } : Result<String, Editor::Error> { m_returned_line };
}
void Editor::handle_read_event()
{
char keybuf[16]; char keybuf[16];
ssize_t nread = 0; ssize_t nread = 0;
@ -306,10 +336,10 @@ Result<String, Editor::Error> Editor::get_line(const String& prompt)
if (errno == EINTR) { if (errno == EINTR) {
if (!m_was_interrupted) { if (!m_was_interrupted) {
if (m_was_resized) if (m_was_resized)
continue; return;
finish(); finish();
continue; return;
} }
m_was_interrupted = false; m_was_interrupted = false;
@ -324,20 +354,25 @@ Result<String, Editor::Error> Editor::get_line(const String& prompt)
on_interrupt_handled(); on_interrupt_handled();
m_refresh_needed = true; m_refresh_needed = true;
continue; return;
} }
ScopedValueRollback errno_restorer(errno); ScopedValueRollback errno_restorer(errno);
perror("read failed"); perror("read failed");
return Error::ReadFailure; m_input_error = Error::ReadFailure;
finish();
return;
} }
m_incomplete_data.append(keybuf, nread); m_incomplete_data.append(keybuf, nread);
nread = m_incomplete_data.size(); nread = m_incomplete_data.size();
if (nread == 0) if (nread == 0) {
return Error::Empty; m_input_error = Error::Empty;
finish();
return;
}
auto reverse_tab = false; auto reverse_tab = false;
auto ctrl_held = false; auto ctrl_held = false;
@ -700,7 +735,13 @@ Result<String, Editor::Error> Editor::get_line(const String& prompt)
for (auto codepoint : m_buffer) for (auto codepoint : m_buffer)
m_pre_search_buffer.append(codepoint); m_pre_search_buffer.append(codepoint);
m_pre_search_cursor = m_cursor; m_pre_search_cursor = m_cursor;
m_search_editor = make<Editor>(Configuration { Configuration::Eager, m_configuration.split_mechanism }); // Has anyone seen 'Inception'?
// Disable our own notifier so as to avoid interfering with the search editor.
m_notifier->set_enabled(false);
m_search_editor = Editor::construct(Configuration { Configuration::Eager, m_configuration.split_mechanism }); // Has anyone seen 'Inception'?
add_child(*m_search_editor);
m_search_editor->on_display_refresh = [this](Editor& search_editor) { m_search_editor->on_display_refresh = [this](Editor& search_editor) {
StringBuilder builder; StringBuilder builder;
builder.append(Utf32View { search_editor.buffer().data(), search_editor.buffer().size() }); builder.append(Utf32View { search_editor.buffer().data(), search_editor.buffer().size() });
@ -760,13 +801,19 @@ Result<String, Editor::Error> Editor::get_line(const String& prompt)
auto search_prompt = "\x1b[32msearch:\x1b[0m "; auto search_prompt = "\x1b[32msearch:\x1b[0m ";
auto search_string_result = m_search_editor->get_line(search_prompt); auto search_string_result = m_search_editor->get_line(search_prompt);
remove_child(*m_search_editor);
m_search_editor = nullptr; m_search_editor = nullptr;
m_is_searching = false; m_is_searching = false;
m_search_offset = 0; m_search_offset = 0;
// Re-enable the notifier after discarding the search editor.
m_notifier->set_enabled(true);
if (search_string_result.is_error()) { if (search_string_result.is_error()) {
// Somethine broke, fail // Somethine broke, fail
return search_string_result; m_input_error = search_string_result.error();
finish();
return;
} }
auto& search_string = search_string_result.value(); auto& search_string = search_string_result.value();
@ -796,14 +843,15 @@ Result<String, Editor::Error> Editor::get_line(const String& prompt)
if (m_buffer.is_empty()) { if (m_buffer.is_empty()) {
printf("<EOF>\n"); printf("<EOF>\n");
if (!m_always_refresh) { if (!m_always_refresh) {
return Error::Eof; m_input_error = Error::Eof;
finish();
continue;
} }
} }
continue; continue;
} }
// ^E // ^E
if (codepoint == 0x05) { if (codepoint == 0x05) {
m_cursor = m_buffer.size(); m_cursor = m_buffer.size();
continue; continue;
} }
@ -822,7 +870,6 @@ Result<String, Editor::Error> Editor::get_line(const String& prompt)
m_incomplete_data.take_first(); m_incomplete_data.take_first();
} }
} }
}
bool Editor::search(const StringView& phrase, bool allow_empty, bool from_beginning) bool Editor::search(const StringView& phrase, bool allow_empty, bool from_beginning)
{ {
@ -1304,6 +1351,7 @@ Vector<size_t, 2> Editor::vt_dsr()
auto nread = read(0, buf, 16); auto nread = read(0, buf, 16);
if (nread < 0) { if (nread < 0) {
m_input_error = Error::ReadFailure; m_input_error = Error::ReadFailure;
finish();
break; break;
} }
@ -1330,10 +1378,12 @@ Vector<size_t, 2> Editor::vt_dsr()
} }
dbg() << "Error while reading DSR: " << strerror(errno); dbg() << "Error while reading DSR: " << strerror(errno);
m_input_error = Error::ReadFailure; m_input_error = Error::ReadFailure;
finish();
return { 1, 1 }; return { 1, 1 };
} }
if (nread == 0) { if (nread == 0) {
m_input_error = Error::Empty; m_input_error = Error::Empty;
finish();
dbg() << "Terminal DSR issue; received no response"; dbg() << "Terminal DSR issue; received no response";
return { 1, 1 }; return { 1, 1 };
} }

View file

@ -38,6 +38,8 @@
#include <AK/Utf8View.h> #include <AK/Utf8View.h>
#include <AK/Vector.h> #include <AK/Vector.h>
#include <LibCore/DirIterator.h> #include <LibCore/DirIterator.h>
#include <LibCore/Notifier.h>
#include <LibCore/Object.h>
#include <LibLine/Span.h> #include <LibLine/Span.h>
#include <LibLine/Style.h> #include <LibLine/Style.h>
#include <LibLine/SuggestionDisplay.h> #include <LibLine/SuggestionDisplay.h>
@ -82,7 +84,9 @@ struct Configuration {
OperationMode operation_mode { OperationMode::Full }; OperationMode operation_mode { OperationMode::Full };
}; };
class Editor { class Editor : public Core::Object {
C_OBJECT(Editor);
public: public:
enum class Error { enum class Error {
ReadFailure, ReadFailure,
@ -90,7 +94,6 @@ public:
Eof, Eof,
}; };
explicit Editor(Configuration configuration = {});
~Editor(); ~Editor();
Result<String, Error> get_line(const String& prompt); Result<String, Error> get_line(const String& prompt);
@ -160,6 +163,8 @@ public:
bool is_editing() const { return m_is_editing; } bool is_editing() const { return m_is_editing; }
private: private:
explicit Editor(Configuration configuration = {});
struct KeyCallback { struct KeyCallback {
KeyCallback(Function<bool(Editor&)> cb) KeyCallback(Function<bool(Editor&)> cb)
: callback(move(cb)) : callback(move(cb))
@ -168,6 +173,8 @@ private:
Function<bool(Editor&)> callback; Function<bool(Editor&)> callback;
}; };
void handle_read_event();
Vector<size_t, 2> vt_dsr(); Vector<size_t, 2> vt_dsr();
void remove_at_index(size_t); void remove_at_index(size_t);
@ -208,6 +215,7 @@ private:
m_prompt_lines_at_suggestion_initiation = 0; m_prompt_lines_at_suggestion_initiation = 0;
m_refresh_needed = true; m_refresh_needed = true;
m_input_error.clear(); m_input_error.clear();
m_returned_line = String::empty();
} }
void refresh_display(); void refresh_display();
@ -266,7 +274,7 @@ private:
bool m_finish { false }; bool m_finish { false };
OwnPtr<Editor> m_search_editor; RefPtr<Editor> m_search_editor;
bool m_is_searching { false }; bool m_is_searching { false };
bool m_reset_buffer_on_search_end { true }; bool m_reset_buffer_on_search_end { true };
size_t m_search_offset { 0 }; size_t m_search_offset { 0 };
@ -278,6 +286,7 @@ private:
ByteBuffer m_pending_chars; ByteBuffer m_pending_chars;
Vector<char, 512> m_incomplete_data; Vector<char, 512> m_incomplete_data;
Optional<Error> m_input_error; Optional<Error> m_input_error;
String m_returned_line;
size_t m_cursor { 0 }; size_t m_cursor { 0 };
size_t m_drawn_cursor { 0 }; size_t m_drawn_cursor { 0 };
@ -336,6 +345,8 @@ private:
HashMap<u32, HashMap<u32, Style>> m_anchored_spans_starting; HashMap<u32, HashMap<u32, Style>> m_anchored_spans_starting;
HashMap<u32, HashMap<u32, Style>> m_anchored_spans_ending; HashMap<u32, HashMap<u32, Style>> m_anchored_spans_ending;
RefPtr<Core::Notifier> m_notifier;
bool m_initialized { false }; bool m_initialized { false };
bool m_refresh_needed { false }; bool m_refresh_needed { false };

View file

@ -55,7 +55,7 @@
// if we want to be more sh-like, we should do that some day // if we want to be more sh-like, we should do that some day
static constexpr bool HighlightVariablesInsideStrings = false; static constexpr bool HighlightVariablesInsideStrings = false;
static bool s_disable_hyperlinks = false; static bool s_disable_hyperlinks = false;
extern Line::Editor editor; extern RefPtr<Line::Editor> editor;
//#define SH_DEBUG //#define SH_DEBUG
@ -126,7 +126,7 @@ String Shell::prompt() const
}; };
auto the_prompt = build_prompt(); auto the_prompt = build_prompt();
auto prompt_length = editor.actual_rendered_string_length(the_prompt); auto prompt_length = editor->actual_rendered_string_length(the_prompt);
if (m_should_continue != ExitCodeOrContinuationRequest::Nothing) { if (m_should_continue != ExitCodeOrContinuationRequest::Nothing) {
const auto format_string = "\033[34m%.*-s\033[m"; const auto format_string = "\033[34m%.*-s\033[m";
@ -511,8 +511,8 @@ int Shell::builtin_disown(int argc, const char** argv)
int Shell::builtin_history(int, const char**) int Shell::builtin_history(int, const char**)
{ {
for (size_t i = 0; i < editor.history().size(); ++i) { for (size_t i = 0; i < editor->history().size(); ++i) {
printf("%6zu %s\n", i, editor.history()[i].characters()); printf("%6zu %s\n", i, editor->history()[i].characters());
} }
return 0; return 0;
} }
@ -1385,7 +1385,7 @@ void Shell::load_history()
while (history_file->can_read_line()) { while (history_file->can_read_line()) {
auto b = history_file->read_line(1024); auto b = history_file->read_line(1024);
// skip the newline and terminating bytes // skip the newline and terminating bytes
editor.add_to_history(String(reinterpret_cast<const char*>(b.data()), b.size() - 2)); editor->add_to_history(String(reinterpret_cast<const char*>(b.data()), b.size() - 2));
} }
} }
@ -1395,7 +1395,7 @@ void Shell::save_history()
if (file_or_error.is_error()) if (file_or_error.is_error())
return; return;
auto& file = *file_or_error.value(); auto& file = *file_or_error.value();
for (const auto& line : editor.history()) { for (const auto& line : editor->history()) {
file.write(line); file.write(line);
file.write("\n"); file.write("\n");
} }
@ -1484,7 +1484,7 @@ void Shell::cache_path()
quick_sort(cached_path); quick_sort(cached_path);
} }
void Shell::highlight(Line::Editor&) const void Shell::highlight(Line::Editor& editor) const
{ {
StringBuilder builder; StringBuilder builder;
if (m_should_continue == ExitCodeOrContinuationRequest::DoubleQuotedString) { if (m_should_continue == ExitCodeOrContinuationRequest::DoubleQuotedString) {
@ -1703,7 +1703,7 @@ Vector<Line::CompletionSuggestion> Shell::complete(const Line::Editor& editor)
bool Shell::read_single_line() bool Shell::read_single_line()
{ {
auto line_result = editor.get_line(prompt()); auto line_result = editor->get_line(prompt());
if (line_result.is_error()) { if (line_result.is_error()) {
m_complete_line_builder.clear(); m_complete_line_builder.clear();
@ -1737,7 +1737,7 @@ bool Shell::read_single_line()
if (!complete_or_exit_code.has_value()) if (!complete_or_exit_code.has_value())
return true; return true;
editor.add_to_history(m_complete_line_builder.build()); editor->add_to_history(m_complete_line_builder.build());
m_complete_line_builder.clear(); m_complete_line_builder.clear();
return true; return true;
} }

View file

@ -35,7 +35,7 @@
#include <string.h> #include <string.h>
#include <sys/wait.h> #include <sys/wait.h>
Line::Editor editor { Line::Configuration { Line::Configuration::UnescapedSpaces } }; RefPtr<Line::Editor> editor;
Shell* s_shell; Shell* s_shell;
void FileDescriptionCollector::collect() void FileDescriptionCollector::collect()
@ -60,11 +60,11 @@ int main(int argc, char** argv)
Core::EventLoop loop; Core::EventLoop loop;
signal(SIGINT, [](int) { signal(SIGINT, [](int) {
editor.interrupted(); editor->interrupted();
}); });
signal(SIGWINCH, [](int) { signal(SIGWINCH, [](int) {
editor.resized(); editor->resized();
}); });
signal(SIGHUP, [](int) { signal(SIGHUP, [](int) {
@ -92,18 +92,20 @@ int main(int argc, char** argv)
return 1; return 1;
} }
editor = Line::Editor::construct(Line::Configuration { Line::Configuration::UnescapedSpaces });
auto shell = Shell::construct(); auto shell = Shell::construct();
s_shell = shell.ptr(); s_shell = shell.ptr();
editor.initialize(); editor->initialize();
shell->termios = editor.termios(); shell->termios = editor->termios();
shell->default_termios = editor.default_termios(); shell->default_termios = editor->default_termios();
editor.on_display_refresh = [&](auto& editor) { editor->on_display_refresh = [&](auto& editor) {
editor.strip_styles(); editor.strip_styles();
shell->highlight(editor); shell->highlight(editor);
}; };
editor.on_tab_complete = [&](const Line::Editor& editor) { editor->on_tab_complete = [&](const Line::Editor& editor) {
return shell->complete(editor); return shell->complete(editor);
}; };
@ -128,13 +130,15 @@ int main(int argc, char** argv)
return 0; return 0;
} }
editor.on_interrupt_handled = [&] { editor->on_interrupt_handled = [&] {
if (!shell->should_read_more()) { if (!shell->should_read_more()) {
shell->finish_command(); shell->finish_command();
editor.finish(); editor->finish();
} }
}; };
shell->add_child(*editor);
Core::EventLoop::current().post_event(*shell, make<Core::CustomEvent>(Shell::ShellEventType::ReadLine)); Core::EventLoop::current().post_event(*shell, make<Core::CustomEvent>(Shell::ShellEventType::ReadLine));
return loop.exec(); return loop.exec();

View file

@ -65,7 +65,7 @@ private:
static bool s_dump_ast = false; static bool s_dump_ast = false;
static bool s_print_last_result = false; static bool s_print_last_result = false;
static OwnPtr<Line::Editor> s_editor; static RefPtr<Line::Editor> s_editor;
static int s_repl_line_level = 0; static int s_repl_line_level = 0;
static bool s_fail_repl = false; static bool s_fail_repl = false;
@ -510,7 +510,7 @@ int main(int argc, char** argv)
if (test_mode) if (test_mode)
enable_test_mode(*interpreter); enable_test_mode(*interpreter);
s_editor = make<Line::Editor>(); s_editor = Line::Editor::construct();
signal(SIGINT, [](int) { signal(SIGINT, [](int) {
if (!s_editor->is_editing()) if (!s_editor->is_editing())

View file

@ -110,10 +110,9 @@ Core::EventLoop loop;
int run(Function<void(const char*, size_t)> fn) int run(Function<void(const char*, size_t)> fn)
{ {
if (interactive) { if (interactive) {
Line::Editor editor; auto editor = Line::Editor::construct();
editor.initialize();
for (;;) { for (;;) {
auto line_result = editor.get_line("> "); auto line_result = editor->get_line("> ");
if (line_result.is_error()) if (line_result.is_error())
break; break;