mirror of
https://github.com/RGBCube/serenity
synced 2025-05-28 22:15:07 +00:00
LibLine: Change get_line to return a Result<String, Error>
This fixes a bunch of FIXME's in LibLine. Also handles the case where read() would read zero bytes in vt_dsr() and effectively block forever by erroring out. Fixes #2370
This commit is contained in:
parent
1e30ef239b
commit
bc9013f706
7 changed files with 87 additions and 22 deletions
|
@ -236,7 +236,13 @@ int main(int argc, char** argv)
|
||||||
}
|
}
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
auto command = editor.get_line("(sdb) ");
|
auto command_result = editor.get_line("(sdb) ");
|
||||||
|
|
||||||
|
if (command_result.is_error())
|
||||||
|
return DebugSession::DebugDecision::Detach;
|
||||||
|
|
||||||
|
auto& command = command_result.value();
|
||||||
|
|
||||||
bool success = false;
|
bool success = false;
|
||||||
Optional<DebugSession::DebugDecision> decision;
|
Optional<DebugSession::DebugDecision> decision;
|
||||||
|
|
||||||
|
|
|
@ -224,7 +224,7 @@ void Editor::suggest(size_t invariant_offset, size_t static_offset, Span::Mode o
|
||||||
m_suggestion_manager.set_suggestion_variants(internal_static_offset, internal_invariant_offset, 0);
|
m_suggestion_manager.set_suggestion_variants(internal_static_offset, internal_invariant_offset, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
String Editor::get_line(const String& prompt)
|
Result<String, Editor::Error> Editor::get_line(const String& prompt)
|
||||||
{
|
{
|
||||||
initialize();
|
initialize();
|
||||||
m_is_editing = true;
|
m_is_editing = true;
|
||||||
|
@ -239,6 +239,9 @@ String Editor::get_line(const String& prompt)
|
||||||
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");
|
||||||
|
@ -279,17 +282,18 @@ String Editor::get_line(const String& prompt)
|
||||||
m_refresh_needed = true;
|
m_refresh_needed = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ScopedValueRollback errno_restorer(errno);
|
||||||
perror("read failed");
|
perror("read failed");
|
||||||
// FIXME: exit()ing here is a bit off. Should communicate failure to caller somehow instead.
|
|
||||||
exit(2);
|
return Error::ReadFailure;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_incomplete_data.append(keybuf, nread);
|
m_incomplete_data.append(keybuf, nread);
|
||||||
nread = m_incomplete_data.size();
|
nread = m_incomplete_data.size();
|
||||||
|
|
||||||
// FIXME: exit()ing here is a bit off. Should communicate failure to caller somehow instead.
|
|
||||||
if (nread == 0)
|
if (nread == 0)
|
||||||
exit(0);
|
return Error::Empty;
|
||||||
|
|
||||||
auto reverse_tab = false;
|
auto reverse_tab = false;
|
||||||
auto ctrl_held = false;
|
auto ctrl_held = false;
|
||||||
|
@ -710,12 +714,19 @@ String Editor::get_line(const String& prompt)
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
|
|
||||||
auto search_prompt = "\x1b[32msearch:\x1b[0m ";
|
auto search_prompt = "\x1b[32msearch:\x1b[0m ";
|
||||||
auto search_string = m_search_editor->get_line(search_prompt);
|
auto search_string_result = m_search_editor->get_line(search_prompt);
|
||||||
|
|
||||||
m_search_editor = nullptr;
|
m_search_editor = nullptr;
|
||||||
m_is_searching = false;
|
m_is_searching = false;
|
||||||
m_search_offset = 0;
|
m_search_offset = 0;
|
||||||
|
|
||||||
|
if (search_string_result.is_error()) {
|
||||||
|
// Somethine broke, fail
|
||||||
|
return search_string_result;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& search_string = search_string_result.value();
|
||||||
|
|
||||||
// Manually cleanup the search line.
|
// Manually cleanup the search line.
|
||||||
reposition_cursor();
|
reposition_cursor();
|
||||||
auto search_string_codepoint_length = Utf8View { search_string }.length_in_codepoints();
|
auto search_string_codepoint_length = Utf8View { search_string }.length_in_codepoints();
|
||||||
|
@ -740,8 +751,9 @@ String Editor::get_line(const String& prompt)
|
||||||
if (codepoint == m_termios.c_cc[VEOF]) {
|
if (codepoint == m_termios.c_cc[VEOF]) {
|
||||||
if (m_buffer.is_empty()) {
|
if (m_buffer.is_empty()) {
|
||||||
printf("<EOF>\n");
|
printf("<EOF>\n");
|
||||||
if (!m_always_refresh) // This is a little off, but it'll do for now
|
if (!m_always_refresh) {
|
||||||
exit(0);
|
return Error::Eof;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -1246,11 +1258,22 @@ Vector<size_t, 2> Editor::vt_dsr()
|
||||||
(void)select(1, &readfds, nullptr, nullptr, &timeout);
|
(void)select(1, &readfds, nullptr, nullptr, &timeout);
|
||||||
if (FD_ISSET(0, &readfds)) {
|
if (FD_ISSET(0, &readfds)) {
|
||||||
auto nread = read(0, buf, 16);
|
auto nread = read(0, buf, 16);
|
||||||
|
if (nread < 0) {
|
||||||
|
m_input_error = Error::ReadFailure;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nread == 0)
|
||||||
|
break;
|
||||||
|
|
||||||
m_incomplete_data.append(buf, nread);
|
m_incomplete_data.append(buf, nread);
|
||||||
more_junk_to_read = true;
|
more_junk_to_read = true;
|
||||||
}
|
}
|
||||||
} while (more_junk_to_read);
|
} while (more_junk_to_read);
|
||||||
|
|
||||||
|
if (m_input_error.has_value())
|
||||||
|
return { 1, 1 };
|
||||||
|
|
||||||
fputs("\033[6n", stdout);
|
fputs("\033[6n", stdout);
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
|
|
||||||
|
@ -1262,9 +1285,11 @@ Vector<size_t, 2> Editor::vt_dsr()
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
dbg() << "Error while reading DSR: " << strerror(errno);
|
dbg() << "Error while reading DSR: " << strerror(errno);
|
||||||
|
m_input_error = Error::ReadFailure;
|
||||||
return { 1, 1 };
|
return { 1, 1 };
|
||||||
}
|
}
|
||||||
if (nread == 0) {
|
if (nread == 0) {
|
||||||
|
m_input_error = Error::Empty;
|
||||||
dbg() << "Terminal DSR issue; received no response";
|
dbg() << "Terminal DSR issue; received no response";
|
||||||
return { 1, 1 };
|
return { 1, 1 };
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,7 @@
|
||||||
#include <AK/HashMap.h>
|
#include <AK/HashMap.h>
|
||||||
#include <AK/NonnullOwnPtr.h>
|
#include <AK/NonnullOwnPtr.h>
|
||||||
#include <AK/QuickSort.h>
|
#include <AK/QuickSort.h>
|
||||||
|
#include <AK/Result.h>
|
||||||
#include <AK/String.h>
|
#include <AK/String.h>
|
||||||
#include <AK/Utf32View.h>
|
#include <AK/Utf32View.h>
|
||||||
#include <AK/Utf8View.h>
|
#include <AK/Utf8View.h>
|
||||||
|
@ -78,10 +79,16 @@ struct Configuration {
|
||||||
|
|
||||||
class Editor {
|
class Editor {
|
||||||
public:
|
public:
|
||||||
|
enum class Error {
|
||||||
|
ReadFailure,
|
||||||
|
Empty,
|
||||||
|
Eof,
|
||||||
|
};
|
||||||
|
|
||||||
explicit Editor(Configuration configuration = {});
|
explicit Editor(Configuration configuration = {});
|
||||||
~Editor();
|
~Editor();
|
||||||
|
|
||||||
String get_line(const String& prompt);
|
Result<String, Error> get_line(const String& prompt);
|
||||||
|
|
||||||
void initialize()
|
void initialize()
|
||||||
{
|
{
|
||||||
|
@ -209,6 +216,7 @@ private:
|
||||||
set_origin(0, 0);
|
set_origin(0, 0);
|
||||||
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
void refresh_display();
|
void refresh_display();
|
||||||
|
@ -276,8 +284,10 @@ private:
|
||||||
Vector<u32, 1024> m_pre_search_buffer;
|
Vector<u32, 1024> m_pre_search_buffer;
|
||||||
|
|
||||||
Vector<u32, 1024> m_buffer;
|
Vector<u32, 1024> m_buffer;
|
||||||
Vector<char, 512> m_incomplete_data;
|
|
||||||
ByteBuffer m_pending_chars;
|
ByteBuffer m_pending_chars;
|
||||||
|
Vector<char, 512> m_incomplete_data;
|
||||||
|
Optional<Error> m_input_error;
|
||||||
|
|
||||||
size_t m_cursor { 0 };
|
size_t m_cursor { 0 };
|
||||||
size_t m_drawn_cursor { 0 };
|
size_t m_drawn_cursor { 0 };
|
||||||
size_t m_inline_search_cursor { 0 };
|
size_t m_inline_search_cursor { 0 };
|
||||||
|
|
|
@ -1701,19 +1701,29 @@ Vector<Line::CompletionSuggestion> Shell::complete(const Line::Editor& editor)
|
||||||
return suggestions;
|
return suggestions;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Shell::read_single_line()
|
bool Shell::read_single_line()
|
||||||
{
|
{
|
||||||
auto line = editor.get_line(prompt());
|
auto line_result = editor.get_line(prompt());
|
||||||
|
|
||||||
|
if (line_result.is_error()) {
|
||||||
|
m_complete_line_builder.clear();
|
||||||
|
m_should_continue = ContinuationRequest::Nothing;
|
||||||
|
m_should_break_current_command = false;
|
||||||
|
Core::EventLoop::current().quit(line_result.error() == Line::Editor::Error::Eof ? 0 : 1);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& line = line_result.value();
|
||||||
|
|
||||||
if (m_should_break_current_command) {
|
if (m_should_break_current_command) {
|
||||||
m_complete_line_builder.clear();
|
m_complete_line_builder.clear();
|
||||||
m_should_continue = ContinuationRequest::Nothing;
|
m_should_continue = ContinuationRequest::Nothing;
|
||||||
m_should_break_current_command = false;
|
m_should_break_current_command = false;
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (line.is_empty())
|
if (line.is_empty())
|
||||||
return;
|
return true;
|
||||||
|
|
||||||
// FIXME: This might be a bit counter-intuitive, since we put nothing
|
// FIXME: This might be a bit counter-intuitive, since we put nothing
|
||||||
// between the two lines, even though the user has pressed enter
|
// between the two lines, even though the user has pressed enter
|
||||||
|
@ -1725,17 +1735,18 @@ void Shell::read_single_line()
|
||||||
m_should_continue = complete_or_exit_code.continuation;
|
m_should_continue = complete_or_exit_code.continuation;
|
||||||
|
|
||||||
if (!complete_or_exit_code.has_value())
|
if (!complete_or_exit_code.has_value())
|
||||||
return;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Shell::custom_event(Core::CustomEvent& event)
|
void Shell::custom_event(Core::CustomEvent& event)
|
||||||
{
|
{
|
||||||
if (event.custom_type() == ReadLine) {
|
if (event.custom_type() == ReadLine) {
|
||||||
read_single_line();
|
if (read_single_line())
|
||||||
Core::EventLoop::current().post_event(*this, make<Core::CustomEvent>(ShellEventType::ReadLine));
|
Core::EventLoop::current().post_event(*this, make<Core::CustomEvent>(ShellEventType::ReadLine));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -123,7 +123,7 @@ public:
|
||||||
bool should_read_more() const { return m_should_continue != ContinuationRequest::Nothing; }
|
bool should_read_more() const { return m_should_continue != ContinuationRequest::Nothing; }
|
||||||
void finish_command() { m_should_break_current_command = true; }
|
void finish_command() { m_should_break_current_command = true; }
|
||||||
|
|
||||||
void read_single_line();
|
bool read_single_line();
|
||||||
|
|
||||||
struct termios termios;
|
struct termios termios;
|
||||||
struct termios default_termios;
|
struct termios default_termios;
|
||||||
|
|
|
@ -67,6 +67,7 @@ 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 OwnPtr<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 String prompt_for_level(int level)
|
static String prompt_for_level(int level)
|
||||||
{
|
{
|
||||||
|
@ -85,7 +86,14 @@ String read_next_piece()
|
||||||
StringBuilder piece;
|
StringBuilder piece;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
String line = s_editor->get_line(prompt_for_level(s_repl_line_level));
|
auto line_result = s_editor->get_line(prompt_for_level(s_repl_line_level));
|
||||||
|
|
||||||
|
if (line_result.is_error()) {
|
||||||
|
s_fail_repl = true;
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& line = line_result.value();
|
||||||
s_editor->add_to_history(line);
|
s_editor->add_to_history(line);
|
||||||
|
|
||||||
piece.append(line);
|
piece.append(line);
|
||||||
|
@ -369,7 +377,7 @@ JS::Value ReplObject::load_file(JS::Interpreter& interpreter)
|
||||||
|
|
||||||
void repl(JS::Interpreter& interpreter)
|
void repl(JS::Interpreter& interpreter)
|
||||||
{
|
{
|
||||||
while (true) {
|
while (!s_fail_repl) {
|
||||||
String piece = read_next_piece();
|
String piece = read_next_piece();
|
||||||
if (piece.is_empty())
|
if (piece.is_empty())
|
||||||
continue;
|
continue;
|
||||||
|
|
|
@ -113,7 +113,12 @@ int run(Function<void(const char*, size_t)> fn)
|
||||||
Line::Editor editor;
|
Line::Editor editor;
|
||||||
editor.initialize();
|
editor.initialize();
|
||||||
for (;;) {
|
for (;;) {
|
||||||
auto line = editor.get_line("> ");
|
auto line_result = editor.get_line("> ");
|
||||||
|
|
||||||
|
if (line_result.is_error())
|
||||||
|
break;
|
||||||
|
auto& line = line_result.value();
|
||||||
|
|
||||||
if (line == ".wait") {
|
if (line == ".wait") {
|
||||||
loop.exec();
|
loop.exec();
|
||||||
} else {
|
} else {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue