diff options
author | Daniel Bertalan <dani@danielbertalan.dev> | 2021-05-08 20:37:43 +0200 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2021-05-16 11:50:56 +0200 |
commit | be519022c3326f7d5cec964b6d3b72d419b0f522 (patch) | |
tree | e8d4698840276142443a0d0b1c99022bc6ccb2cf /Userland | |
parent | 1b347298f118e4e78e7cef41096623913c9e640e (diff) | |
download | serenity-be519022c3326f7d5cec964b6d3b72d419b0f522.zip |
LibVT: Implement new ANSI escape sequence parser
This commit replaces the former, hand-written parser with a new one that
can be generated automatically according to a state change diagram.
The new `EscapeSequenceParser` class provides a more ergonomic interface
to dealing with escape sequences. This interface has been inspired by
Alacritty's [vte library](https://github.com/alacritty/vte/).
I tried to avoid changing the application logic inside the `Terminal`
class. While this code has not been thoroughly tested, I can't find
regressions in the basic command line utilities or `vttest`.
`Terminal` now displays nicer debug messages when it encounters an
unknown escape sequence. Defensive programming and bounds checks have
been added where we access parameters, and as a result, we can now
endure 4-5 seconds of `cat /dev/urandom`. :D
We generate EscapeSequenceStateMachine.h when building the in-kernel
LibVT, and we assume that the file is already in place when the userland
library is being built. This will probably cause problems later on, but
I can't find a way to do it nicely.
Diffstat (limited to 'Userland')
-rw-r--r-- | Userland/DevTools/StateMachineGenerator/main.cpp | 189 | ||||
-rw-r--r-- | Userland/Libraries/LibVT/CMakeLists.txt | 4 | ||||
-rw-r--r-- | Userland/Libraries/LibVT/EscapeSequenceParser.cpp | 162 | ||||
-rw-r--r-- | Userland/Libraries/LibVT/EscapeSequenceParser.h | 77 | ||||
-rw-r--r-- | Userland/Libraries/LibVT/StateMachine.txt | 2 | ||||
-rw-r--r-- | Userland/Libraries/LibVT/Terminal.cpp | 705 | ||||
-rw-r--r-- | Userland/Libraries/LibVT/Terminal.h | 106 |
7 files changed, 680 insertions, 565 deletions
diff --git a/Userland/DevTools/StateMachineGenerator/main.cpp b/Userland/DevTools/StateMachineGenerator/main.cpp index 46b4e02d7c..d2f5f02892 100644 --- a/Userland/DevTools/StateMachineGenerator/main.cpp +++ b/Userland/DevTools/StateMachineGenerator/main.cpp @@ -211,18 +211,15 @@ parse_state_machine(StringView input) } void output_header(const StateMachine&, SourceGenerator&); -void output_cpp(const StateMachine&, SourceGenerator&); int main(int argc, char** argv) { Core::ArgsParser args_parser; const char* path = nullptr; - bool header_mode = false; - args_parser.add_option(header_mode, "Generate .h file", "header", 'H'); args_parser.add_positional_argument(path, "Path to parser description", "input", Core::ArgsParser::Required::Yes); args_parser.parse(argc, argv); - auto file_or_error = Core::File::open(path, Core::IODevice::ReadOnly); + auto file_or_error = Core::File::open(path, Core::OpenMode::ReadOnly); if (file_or_error.is_error()) { fprintf(stderr, "Cannot open %s\n", path); } @@ -232,10 +229,7 @@ int main(int argc, char** argv) StringBuilder builder; SourceGenerator generator { builder }; - if (header_mode) - output_header(*state_machine, generator); - else - output_cpp(*state_machine, generator); + output_header(*state_machine, generator); outln("{}", generator.as_string_view()); return 0; } @@ -340,9 +334,66 @@ public: typedef Function<void(Action, u8)> Handler; - @class_name@(Handler); + @class_name@(Handler handler) + : m_handler(move(handler)) + { + } + + void advance(u8 byte) + { + auto next_state = lookup_state_transition(byte); + bool state_will_change = next_state.new_state != m_state && next_state.new_state != State::_Anywhere; + + // only run exit directive if state is being changed + if (state_will_change) { + switch (m_state) { +)~~~"); + for (auto s : machine.states) { + auto state_generator = generator.fork(); + if (s.exit_action.has_value()) { + state_generator.set("state_name", s.name); + state_generator.set("action", s.exit_action.value()); + state_generator.append(R"~~~( + case State::@state_name@: + m_handler(Action::@action@, byte); + break; +)~~~"); + } + } + generator.append(R"~~~( + default: + break; + } + } - void advance(u8); + if (next_state.action != Action::_Ignore) + m_handler(next_state.action, byte); + m_state = next_state.new_state; + + // only run entry directive if state is being changed + if (state_will_change) + { + switch (next_state.new_state) + { +)~~~"); + for (auto state : machine.states) { + auto state_generator = generator.fork(); + if (state.entry_action.has_value()) { + state_generator.set("state_name", state.name); + state_generator.set("action", state.entry_action.value()); + state_generator.append(R"~~~( + case State::@state_name@: + m_handler(Action::@action@, byte); + break; +)~~~"); + } + } + generator.append(R"~~~( + default: + break; + } + } + } private: enum class State : u8 { @@ -370,121 +421,27 @@ private: Handler m_handler; - StateTransition lookup_state_transition(u8); -)~~~"); - - auto table_generator = generator.fork(); - generate_lookup_table(machine, table_generator); - generator.append(R"~~~( -}; // end @class_name@ -)~~~"); - - if (machine.namespaces.has_value()) { - generator.append(R"~~~( -} // end namespace -)~~~"); - } -} - -void output_cpp(const StateMachine& machine, SourceGenerator& generator) -{ - VERIFY(!machine.name.is_empty()); - generator.set("class_name", machine.name); - generator.set("state_count", String::number(machine.states.size() + 1)); - - generator.append(R"~~~( -#include "@class_name@.h" -#include <AK/Function.h> -#include <AK/Types.h> -)~~~"); - if (machine.namespaces.has_value()) { - generator.set("namespace", machine.namespaces.value()); - generator.append(R"~~~( -namespace @namespace@ { + ALWAYS_INLINE StateTransition lookup_state_transition(u8 byte) + { + VERIFY((u8)m_state < @state_count@); )~~~"); - } - generator.append(R"~~~( -@class_name@::@class_name@(Handler handler) - : m_handler(move(handler)) -{ -} - -ALWAYS_INLINE @class_name@::StateTransition @class_name@::lookup_state_transition(u8 byte) -{ - VERIFY((u8)m_state < @state_count@); - )~~~"); if (machine.anywhere.has_value()) { generator.append(R"~~~( - auto anywhere_state = STATE_TRANSITION_TABLE[0][byte]; - if (anywhere_state.new_state != @class_name@::State::_Anywhere || anywhere_state.action != @class_name@::Action::_Ignore) - return anywhere_state; - else -)~~~"); - } - generator.append(R"~~~( - return STATE_TRANSITION_TABLE[(u8)m_state][byte]; -} -)~~~"); - - generator.append(R"~~~( - -void @class_name@::advance(u8 byte) -{ - auto next_state = lookup_state_transition(byte); - bool state_will_change = next_state.new_state != m_state && next_state.new_state != @class_name@::State::_Anywhere; - - // only run exit directive if state is being changed - if (state_will_change) - { - switch (m_state) - { -)~~~"); - for (auto s : machine.states) { - auto state_generator = generator.fork(); - if (s.exit_action.has_value()) { - state_generator.set("state_name", s.name); - state_generator.set("action", s.exit_action.value()); - state_generator.append(R"~~~( - case @class_name@::State::@state_name@: - m_handler(Action::@action@, byte); - break; + auto anywhere_state = STATE_TRANSITION_TABLE[0][byte]; + if (anywhere_state.new_state != State::_Anywhere || anywhere_state.action != Action::_Ignore) + return anywhere_state; + else )~~~"); - } } generator.append(R"~~~( - default: - break; - } + return STATE_TRANSITION_TABLE[(u8)m_state][byte]; } - - if (next_state.action != @class_name@::Action::_Ignore) - m_handler(next_state.action, byte); - m_state = next_state.new_state; - - // only run entry directive if state is being changed - if (state_will_change) - { - switch (next_state.new_state) - { )~~~"); - for (auto state : machine.states) { - auto state_generator = generator.fork(); - if (state.entry_action.has_value()) { - state_generator.set("state_name", state.name); - state_generator.set("action", state.entry_action.value()); - state_generator.append(R"~~~( - case @class_name@::State::@state_name@: - m_handler(Action::@action@, byte); - break; -)~~~"); - } - } + + auto table_generator = generator.fork(); + generate_lookup_table(machine, table_generator); generator.append(R"~~~( - default: - break; - } - } -} +}; // end @class_name@ )~~~"); if (machine.namespaces.has_value()) { diff --git a/Userland/Libraries/LibVT/CMakeLists.txt b/Userland/Libraries/LibVT/CMakeLists.txt index eeca322d28..a5e3d6d729 100644 --- a/Userland/Libraries/LibVT/CMakeLists.txt +++ b/Userland/Libraries/LibVT/CMakeLists.txt @@ -1,7 +1,11 @@ +# FIXME: this assumes that EscapeSequenceStateMachine.h has been +# already generated when the kernel was built. This will probably +# mess builds up later on. set(SOURCES Line.cpp Terminal.cpp TerminalWidget.cpp + EscapeSequenceParser.cpp ) serenity_lib(LibVT vt) diff --git a/Userland/Libraries/LibVT/EscapeSequenceParser.cpp b/Userland/Libraries/LibVT/EscapeSequenceParser.cpp new file mode 100644 index 0000000000..dc2fd1e411 --- /dev/null +++ b/Userland/Libraries/LibVT/EscapeSequenceParser.cpp @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2021, the SerenityOS developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <AK/Format.h> +#include <AK/Types.h> +#include <LibVT/EscapeSequenceParser.h> +#include <LibVT/EscapeSequenceStateMachine.h> + +namespace VT { +EscapeSequenceParser::EscapeSequenceParser(EscapeSequenceExecutor& executor) + : m_executor(executor) + , m_state_machine([this](auto action, auto byte) { perform_action(action, byte); }) +{ +} + +EscapeSequenceParser::~EscapeSequenceParser() +{ +} + +Vector<EscapeSequenceParser::OscParameter> EscapeSequenceParser::osc_parameters() const +{ + VERIFY(m_osc_raw.size() >= m_osc_parameter_indexes.last()); + Vector<EscapeSequenceParser::OscParameter> params; + size_t prev_idx = 0; + for (auto end_idx : m_osc_parameter_indexes) { + // If the parameter is empty, we take an out of bounds index as the beginning of the Span. + // This should not be a problem as we won't dereference the 0-length Span that's created. + // Using &m_osc_raw[prev_idx] to get the start pointer checks whether we're out of bounds, + // so we would crash. + params.append({ m_osc_raw.data() + prev_idx, end_idx - prev_idx }); + prev_idx = end_idx; + } + return params; +} + +void EscapeSequenceParser::perform_action(EscapeSequenceStateMachine::Action action, u8 byte) +{ + auto advance_utf8 = [&](u8 byte) { + u32 new_codepoint = m_code_point; + new_codepoint <<= 6; + new_codepoint |= byte & 0x3f; + return new_codepoint; + }; + + switch (action) { + case EscapeSequenceStateMachine::Action::_Ignore: + break; + case EscapeSequenceStateMachine::Action::Print: + m_executor.emit_code_point((u32)byte); + break; + case EscapeSequenceStateMachine::Action::PrintUTF8: + m_executor.emit_code_point(advance_utf8(byte)); + break; + case EscapeSequenceStateMachine::Action::Execute: + m_executor.execute_control_code(byte); + break; + case EscapeSequenceStateMachine::Action::Hook: + if (m_param_vector.size() == MAX_PARAMETERS) + m_ignoring = true; + else + m_param_vector.append(m_param); + m_executor.dcs_hook(m_param_vector, intermediates(), m_ignoring, byte); + break; + case EscapeSequenceStateMachine::Action::Put: + m_executor.receive_dcs_char(byte); + break; + case EscapeSequenceStateMachine::Action::BeginUTF8: + if ((byte & 0xe0) == 0xc0) { + m_code_point = byte & 0x1f; + } else if ((byte & 0xf0) == 0xe0) { + m_code_point = byte & 0x0f; + } else if ((byte & 0xf8) == 0xf0) { + m_code_point = byte & 0x07; + } else { + dbgln("Invalid character was parsed as UTF-8 initial byte {:02x}", byte); + VERIFY_NOT_REACHED(); + } + break; + case EscapeSequenceStateMachine::Action::AdvanceUTF8: + VERIFY((byte & 0xc0) == 0x80); + m_code_point = advance_utf8(byte); + break; + case EscapeSequenceStateMachine::Action::FailUTF8: + m_executor.emit_code_point(U'�'); + break; + case EscapeSequenceStateMachine::Action::OscStart: + m_osc_raw.clear(); + m_osc_parameter_indexes.clear(); + break; + case EscapeSequenceStateMachine::Action::OscPut: + if (byte == ';') { + if (m_osc_parameter_indexes.size() == MAX_OSC_PARAMETERS) { + dbgln("EscapeSequenceParser::perform_action: shenanigans! OSC sequence has too many parameters"); + } else { + m_osc_parameter_indexes.append(m_osc_raw.size()); + } + } else { + m_osc_raw.append(byte); + } + break; + case EscapeSequenceStateMachine::Action::OscEnd: + if (m_osc_parameter_indexes.size() == MAX_OSC_PARAMETERS) { + dbgln("EscapeSequenceParser::perform_action: shenanigans! OSC sequence has too many parameters"); + } else { + m_osc_parameter_indexes.append(m_osc_raw.size()); + } + m_executor.execute_osc_sequence(osc_parameters(), byte); + break; + case EscapeSequenceStateMachine::Action::Unhook: + m_executor.execute_dcs_sequence(); + break; + case EscapeSequenceStateMachine::Action::CsiDispatch: + if (m_param_vector.size() > MAX_PARAMETERS) { + dbgln("EscapeSequenceParser::perform_action: shenanigans! CSI sequence has too many parameters"); + m_ignoring = true; + } else { + m_param_vector.append(m_param); + } + + m_executor.execute_csi_sequence(m_param_vector, intermediates(), m_ignoring, byte); + break; + + case EscapeSequenceStateMachine::Action::EscDispatch: + m_executor.execute_escape_sequence(intermediates(), m_ignoring, byte); + break; + case EscapeSequenceStateMachine::Action::Collect: + if (m_intermediate_idx == MAX_INTERMEDIATES) { + dbgln("EscapeSequenceParser::perform_action: shenanigans! escape sequence has too many intermediates"); + m_ignoring = true; + } else { + m_intermediates[m_intermediate_idx++] = byte; + } + break; + case EscapeSequenceStateMachine::Action::Param: + if (m_param_vector.size() == MAX_PARAMETERS) { + dbgln("EscapeSequenceParser::perform_action: shenanigans! escape sequence has too many parameters"); + m_ignoring = true; + } else { + if (byte == ';') { + m_param_vector.append(m_param); + m_param = 0; + } else if (byte == ':') { + dbgln("EscapeSequenceParser::perform_action: subparameters are not yet implemented"); + } else { + m_param *= 10; + m_param += (byte - '0'); + } + } + break; + case EscapeSequenceStateMachine::Action::Clear: + m_intermediate_idx = 0; + m_ignoring = false; + + m_param = 0; + m_param_vector.clear_with_capacity(); + break; + } +} +} diff --git a/Userland/Libraries/LibVT/EscapeSequenceParser.h b/Userland/Libraries/LibVT/EscapeSequenceParser.h new file mode 100644 index 0000000000..14ec984698 --- /dev/null +++ b/Userland/Libraries/LibVT/EscapeSequenceParser.h @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2021, the SerenityOS developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <AK/Debug.h> +#include <AK/Platform.h> +#include <AK/Span.h> +#include <AK/Types.h> +#include <AK/Vector.h> +#include <LibVT/EscapeSequenceStateMachine.h> + +namespace VT { +class EscapeSequenceExecutor { +public: + virtual ~EscapeSequenceExecutor() { } + + using Parameters = Span<const unsigned>; + using Intermediates = Span<const u8>; + using OscParameter = Span<const u8>; + using OscParameters = Span<const OscParameter>; + + virtual void emit_code_point(u32) = 0; + virtual void execute_control_code(u8) = 0; + virtual void execute_escape_sequence(Intermediates intermediates, bool ignore, u8 last_byte) = 0; + virtual void execute_csi_sequence(Parameters parameters, Intermediates intermediates, bool ignore, u8 last_byte) = 0; + virtual void execute_osc_sequence(OscParameters parameters, u8 last_byte) = 0; + virtual void dcs_hook(Parameters parameters, Intermediates intermediates, bool ignore, u8 last_byte) = 0; + virtual void receive_dcs_char(u8 byte) = 0; + virtual void execute_dcs_sequence() = 0; +}; + +class EscapeSequenceParser { +public: + explicit EscapeSequenceParser(EscapeSequenceExecutor&); + ~EscapeSequenceParser(); + + ALWAYS_INLINE void on_input(u8 byte) + { + dbgln_if(ESCAPE_SEQUENCE_DEBUG, "on_input {:02x}", byte); + m_state_machine.advance(byte); + } + +private: + static constexpr size_t MAX_INTERMEDIATES = 2; + static constexpr size_t MAX_PARAMETERS = 16; + static constexpr size_t MAX_OSC_PARAMETERS = 16; + + using Intermediates = EscapeSequenceExecutor::Intermediates; + using OscParameter = EscapeSequenceExecutor::OscParameter; + + void perform_action(EscapeSequenceStateMachine::Action, u8); + + EscapeSequenceExecutor& m_executor; + EscapeSequenceStateMachine m_state_machine; + + u32 m_code_point { 0 }; + + u8 m_intermediates[MAX_INTERMEDIATES]; + u8 m_intermediate_idx { 0 }; + + Intermediates intermediates() const { return { m_intermediates, m_intermediate_idx }; } + Vector<OscParameter> osc_parameters() const; + + Vector<unsigned, 4> m_param_vector; + unsigned m_param { 0 }; + + Vector<u8> m_osc_parameter_indexes; + Vector<u8, 16> m_osc_raw; + + bool m_ignoring { false }; +}; + +} diff --git a/Userland/Libraries/LibVT/StateMachine.txt b/Userland/Libraries/LibVT/StateMachine.txt index ffa300ec01..1cd20ce8b4 100644 --- a/Userland/Libraries/LibVT/StateMachine.txt +++ b/Userland/Libraries/LibVT/StateMachine.txt @@ -3,7 +3,7 @@ // The description of the state machine is taken from https://vt100.net/emu/dec_ansi_parser // with added support for UTF-8 parsing -@name VTParserStateMachine +@name EscapeSequenceStateMachine @namespace VT @begin Ground diff --git a/Userland/Libraries/LibVT/Terminal.cpp b/Userland/Libraries/LibVT/Terminal.cpp index 49c71a7edf..e06f57c1ed 100644 --- a/Userland/Libraries/LibVT/Terminal.cpp +++ b/Userland/Libraries/LibVT/Terminal.cpp @@ -4,6 +4,7 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include "Terminal.h" #include <AK/Debug.h> #include <AK/StringBuilder.h> #include <AK/StringView.h> @@ -13,6 +14,7 @@ namespace VT { Terminal::Terminal(TerminalClient& client) : m_client(client) + , m_parser(*this) { } @@ -37,22 +39,7 @@ void Terminal::clear_including_history() m_client.terminal_history_changed(); } -inline bool is_valid_parameter_character(u8 ch) -{ - return ch >= 0x30 && ch <= 0x3f; -} - -inline bool is_valid_intermediate_character(u8 ch) -{ - return ch >= 0x20 && ch <= 0x2f; -} - -inline bool is_valid_final_character(u8 ch) -{ - return ch >= 0x40 && ch <= 0x7e; -} - -void Terminal::alter_mode(bool should_set, bool question_param, const ParamVector& params) +void Terminal::alter_mode(bool should_set, bool question_param, Parameters params) { int mode = 2; if (params.size() > 0) { @@ -60,9 +47,9 @@ void Terminal::alter_mode(bool should_set, bool question_param, const ParamVecto } if (!question_param) { switch (mode) { - // FIXME: implement *something* for this + // FIXME: implement *something* for this default: - unimplemented_escape(); + dbgln("Terminal::alter_mode: Unimplemented mode {} (set={})", mode, should_set); break; } } else { @@ -70,7 +57,7 @@ void Terminal::alter_mode(bool should_set, bool question_param, const ParamVecto case 3: { // 80/132-column mode (DECCOLM) unsigned new_columns = should_set ? 80 : 132; - dbgln("Setting {}-column mode", new_columns); + dbgln_if(TERMINAL_DEBUG, "Setting {}-column mode", new_columns); set_size(new_columns, rows()); clear(); break; @@ -84,23 +71,33 @@ void Terminal::alter_mode(bool should_set, bool question_param, const ParamVecto dbgln("Terminal: Show Cursor escapecode received. Not needed: ignored."); break; default: - dbgln("Set Mode: Unimplemented mode {}", mode); + dbgln("Terminal::alter_mode: Unimplemented private mode {}", mode); break; } } } -void Terminal::RM(bool question_param, const ParamVector& params) +void Terminal::RM(Parameters params) { + bool question_param = false; + if (params.size() > 0 && params[0] == '?') { + question_param = true; + params = params.slice(1); + } alter_mode(true, question_param, params); } -void Terminal::SM(bool question_param, const ParamVector& params) +void Terminal::SM(Parameters params) { + bool question_param = false; + if (params.size() > 0 && params[0] == '?') { + question_param = true; + params = params.slice(1); + } alter_mode(false, question_param, params); } -void Terminal::SGR(const ParamVector& params) +void Terminal::SGR(Parameters params) { if (params.is_empty()) { m_current_attribute.reset(); @@ -215,25 +212,26 @@ void Terminal::SGR(const ParamVector& params) } } -void Terminal::SCOSC(const ParamVector&) +void Terminal::SCOSC() { m_saved_cursor_row = m_cursor_row; m_saved_cursor_column = m_cursor_column; + m_saved_attribute = m_current_attribute; } -void Terminal::SCORC(const ParamVector&) +void Terminal::SCORC(Parameters) { set_cursor(m_saved_cursor_row, m_saved_cursor_column); } -void Terminal::XTERM_WM(const ParamVector& params) +void Terminal::XTERM_WM(Parameters params) { if (params.size() < 1) return; dbgln("FIXME: XTERM_WM: Ps: {} (param count: {})", params[0], params.size()); } -void Terminal::DECSTBM(const ParamVector& params) +void Terminal::DECSTBM(Parameters params) { unsigned top = 1; unsigned bottom = m_rows; @@ -250,7 +248,7 @@ void Terminal::DECSTBM(const ParamVector& params) set_cursor(0, 0); } -void Terminal::CUP(const ParamVector& params) +void Terminal::CUP(Parameters params) { // CUP – Cursor Position unsigned row = 1; @@ -262,7 +260,7 @@ void Terminal::CUP(const ParamVector& params) set_cursor(row - 1, col - 1); } -void Terminal::HVP(const ParamVector& params) +void Terminal::HVP(Parameters params) { unsigned row = 1; unsigned col = 1; @@ -273,7 +271,7 @@ void Terminal::HVP(const ParamVector& params) set_cursor(row - 1, col - 1); } -void Terminal::CUU(const ParamVector& params) +void Terminal::CUU(Parameters params) { int num = 1; if (params.size() >= 1) @@ -286,7 +284,7 @@ void Terminal::CUU(const ParamVector& params) set_cursor(new_row, m_cursor_column); } -void Terminal::CUD(const ParamVector& params) +void Terminal::CUD(Parameters params) { int num = 1; if (params.size() >= 1) @@ -299,7 +297,7 @@ void Terminal::CUD(const ParamVector& params) set_cursor(new_row, m_cursor_column); } -void Terminal::CUF(const ParamVector& params) +void Terminal::CUF(Parameters params) { int num = 1; if (params.size() >= 1) @@ -312,7 +310,7 @@ void Terminal::CUF(const ParamVector& params) set_cursor(m_cursor_row, new_column); } -void Terminal::CUB(const ParamVector& params) +void Terminal::CUB(Parameters params) { int num = 1; if (params.size() >= 1) @@ -325,7 +323,7 @@ void Terminal::CUB(const ParamVector& params) set_cursor(m_cursor_row, new_column); } -void Terminal::CHA(const ParamVector& params) +void Terminal::CHA(Parameters params) { int new_column = 1; if (params.size() >= 1) @@ -335,7 +333,7 @@ void Terminal::CHA(const ParamVector& params) set_cursor(m_cursor_row, new_column); } -void Terminal::REP(const ParamVector& params) +void Terminal::REP(Parameters params) { if (params.size() < 1) return; @@ -344,7 +342,7 @@ void Terminal::REP(const ParamVector& params) put_character_at(m_cursor_row, m_cursor_column++, m_last_code_point); } -void Terminal::VPA(const ParamVector& params) +void Terminal::VPA(Parameters params) { int new_row = 1; if (params.size() >= 1) @@ -354,7 +352,7 @@ void Terminal::VPA(const ParamVector& params) set_cursor(new_row, m_cursor_column); } -void Terminal::ECH(const ParamVector& params) +void Terminal::ECH(Parameters params) { // Erase characters (without moving cursor) int num = 1; @@ -368,7 +366,7 @@ void Terminal::ECH(const ParamVector& params) } } -void Terminal::EL(const ParamVector& params) +void Terminal::EL(Parameters params) { int mode = 0; if (params.size() >= 1) @@ -393,12 +391,12 @@ void Terminal::EL(const ParamVector& params) } break; default: - unimplemented_escape(); + unimplemented_csi_sequence(params, {}, 'K'); break; } } -void Terminal::ED(const ParamVector& params) +void Terminal::ED(Parameters params) { int mode = 0; if (params.size() >= 1) @@ -432,12 +430,12 @@ void Terminal::ED(const ParamVector& params) clear(); break; default: - unimplemented_escape(); + unimplemented_csi_sequence(params, {}, 'J'); break; } } -void Terminal::SU(const ParamVector& params) +void Terminal::SU(Parameters params) { int count = 1; if (params.size() >= 1) @@ -447,7 +445,7 @@ void Terminal::SU(const ParamVector& params) scroll_up(); } -void Terminal::SD(const ParamVector& params) +void Terminal::SD(Parameters params) { int count = 1; if (params.size() >= 1) @@ -457,7 +455,7 @@ void Terminal::SD(const ParamVector& params) scroll_down(); } -void Terminal::IL(const ParamVector& params) +void Terminal::IL(Parameters params) { int count = 1; if (params.size() >= 1) @@ -474,12 +472,12 @@ void Terminal::IL(const ParamVector& params) m_need_full_flush = true; } -void Terminal::DA(const ParamVector&) +void Terminal::DA(Parameters) { emit_string("\033[?1;0c"); } -void Terminal::DL(const ParamVector& params) +void Terminal::DL(Parameters params) { int count = 1; if (params.size() >= 1) @@ -502,7 +500,7 @@ void Terminal::DL(const ParamVector& params) } } -void Terminal::DCH(const ParamVector& params) +void Terminal::DCH(Parameters params) { int num = 1; if (params.size() >= 1) @@ -524,165 +522,6 @@ void Terminal::DCH(const ParamVector& params) line.set_dirty(true); } -void Terminal::execute_xterm_command() -{ - ParamVector numeric_params; - auto param_string = String::copy(m_xterm_parameters); - auto params = param_string.split(';', true); - m_xterm_parameters.clear_with_capacity(); - for (auto& parampart : params) - numeric_params.append(parampart.to_uint().value_or(0)); - - while (params.size() < 3) { - params.append(String::empty()); - numeric_params.append(0); - } - - m_final = '@'; - - if (numeric_params.is_empty()) { - dbgln("Empty Xterm params?"); - return; - } - - switch (numeric_params[0]) { - case 0: - case 1: - case 2: - m_client.set_window_title(params[1]); - break; - case 8: - if (params[2].is_empty()) { - m_current_attribute.href = String(); - m_current_attribute.href_id = String(); - } else { - m_current_attribute.href = params[2]; - // FIXME: Respect the provided ID - m_current_attribute.href_id = String::number(m_next_href_id++); - } - break; - case 9: - m_client.set_window_progress(numeric_params[1], numeric_params[2]); - break; - default: - unimplemented_xterm_escape(); - break; - } -} - -void Terminal::execute_escape_sequence(u8 final) -{ - bool question_param = false; - m_final = final; - ParamVector params; - - if (m_parameters.size() > 0 && m_parameters[0] == '?') { - question_param = true; - m_parameters.remove(0); - } - auto paramparts = String::copy(m_parameters).split(';'); - for (auto& parampart : paramparts) { - auto value = parampart.to_uint(); - if (!value.has_value()) { - // FIXME: Should we do something else? - m_parameters.clear_with_capacity(); - m_intermediates.clear_with_capacity(); - return; - } - params.append(value.value()); - } - - switch (final) { - case 'A': - CUU(params); - break; - case 'B': - CUD(params); - break; - case 'C': - CUF(params); - break; - case 'D': - CUB(params); - break; - case 'H': - CUP(params); - break; - case 'J': - ED(params); - break; - case 'K': - EL(params); - break; - case 'M': - DL(params); - break; - case 'P': - DCH(params); - break; - case 'S': - SU(params); - break; - case 'T': - SD(params); - break; - case 'L': - IL(params); - break; - case 'G': - CHA(params); - break; - case 'X': - ECH(params); - break; - case 'b': - REP(params); - break; - case 'd': - VPA(params); - break; - case 'm': - SGR(params); - break; - case 's': - SCOSC(params); - break; - case 'u': - SCORC(params); - break; - case 't': - XTERM_WM(params); - break; - case 'r': - DECSTBM(params); - break; - case 'l': - RM(question_param, params); - break; - case 'h': - SM(question_param, params); - break; - case 'c': - DA(params); - break; - case 'f': - HVP(params); - break; - case 'n': - DSR(params); - break; - case '@': - ICH(params); - break; - default: - dbgln("Terminal::execute_escape_sequence: Unhandled final '{:c}'", final); - break; - } - - m_parameters.clear_with_capacity(); - m_intermediates.clear_with_capacity(); -} - void Terminal::newline() { u16 new_row = m_cursor_row; @@ -690,9 +529,13 @@ void Terminal::newline() scroll_up(); } else { ++new_row; - } + }; set_cursor(new_row, 0); } +void Terminal::carriage_return() +{ + set_cursor(m_cursor_row, 0); +} void Terminal::scroll_up() { @@ -748,6 +591,7 @@ void Terminal::put_character_at(unsigned row, unsigned column, u32 code_point) void Terminal::NEL() { newline(); + carriage_return(); } void Terminal::IND() @@ -760,7 +604,7 @@ void Terminal::RI() CUU({}); } -void Terminal::DSR(const ParamVector& params) +void Terminal::DSR(Parameters params) { if (params.size() == 1 && params[0] == 5) { // Device status @@ -773,7 +617,7 @@ void Terminal::DSR(const ParamVector& params) } } -void Terminal::ICH(const ParamVector& params) +void Terminal::ICH(Parameters params) { int num = 0; if (params.size() >= 1) { @@ -795,153 +639,44 @@ void Terminal::ICH(const ParamVector& params) line.set_dirty(true); } -void Terminal::on_input(u8 ch) +void Terminal::on_input(u8 byte) { - dbgln_if(TERMINAL_DEBUG, "Terminal::on_input: {:#02x} ({:c}), fg={}, bg={}\n", ch, ch, m_current_attribute.foreground_color, m_current_attribute.background_color); - - auto fail_utf8_parse = [this] { - m_parser_state = Normal; - on_code_point(U'�'); - }; - - auto advance_utf8_parse = [this, ch] { - m_parser_code_point <<= 6; - m_parser_code_point |= ch & 0x3f; - if (m_parser_state == UTF8Needs1Byte) { - on_code_point(m_parser_code_point); - m_parser_state = Normal; - } else { - m_parser_state = (ParserState)(m_parser_state + 1); - } - }; - - switch (m_parser_state) { - case GotEscape: - if (ch == '[') { - m_parser_state = ExpectParameter; - } else if (ch == '(') { - m_swallow_current = true; - m_parser_state = ExpectParameter; - } else if (ch == ']') { - m_parser_state = ExpectXtermParameter; - m_xterm_parameters.clear_with_capacity(); - } else if (ch == '#') { - m_parser_state = ExpectHashtagDigit; - } else if (ch == 'D') { - IND(); - m_parser_state = Normal; - return; - } else if (ch == 'M') { - RI(); - m_parser_state = Normal; - return; - } else if (ch == 'E') { - NEL(); - m_parser_state = Normal; - return; - } else { - dbgln("Unexpected character in GotEscape '{}'", (char)ch); - m_parser_state = Normal; - } - return; - case ExpectHashtagDigit: - if (ch >= '0' && ch <= '9') { - execute_hashtag(ch); - m_parser_state = Normal; - } - return; - case ExpectXtermParameter: - if (ch == 27) { - m_parser_state = ExpectStringTerminator; - return; - } - if (ch == 7) { - execute_xterm_command(); - m_parser_state = Normal; - return; - } - m_xterm_parameters.append(ch); - return; - case ExpectStringTerminator: - if (ch == '\\') - execute_xterm_command(); - else - dbgln("Unexpected string terminator: {:#02x}", ch); - m_parser_state = Normal; - return; - case ExpectParameter: - if (is_valid_parameter_character(ch)) { - m_parameters.append(ch); - return; - } - m_parser_state = ExpectIntermediate; - [[fallthrough]]; - case ExpectIntermediate: - if (is_valid_intermediate_character(ch)) { - m_intermediates.append(ch); - return; - } - m_parser_state = ExpectFinal; - [[fallthrough]]; - case ExpectFinal: - if (is_valid_final_character(ch)) { - m_parser_state = Normal; - if (!m_swallow_current) - execute_escape_sequence(ch); - m_swallow_current = false; - return; - } - m_parser_state = Normal; - m_swallow_current = false; - return; - case UTF8Needs1Byte: - case UTF8Needs2Bytes: - case UTF8Needs3Bytes: - if ((ch & 0xc0) != 0x80) { - fail_utf8_parse(); - } else { - advance_utf8_parse(); - } - return; + m_parser.on_input(byte); +} - case Normal: - if (!(ch & 0x80)) - break; - if ((ch & 0xe0) == 0xc0) { - m_parser_state = UTF8Needs1Byte; - m_parser_code_point = ch & 0x1f; - return; - } - if ((ch & 0xf0) == 0xe0) { - m_parser_state = UTF8Needs2Bytes; - m_parser_code_point = ch & 0x0f; - return; - } - if ((ch & 0xf8) == 0xf0) { - m_parser_state = UTF8Needs3Bytes; - m_parser_code_point = ch & 0x07; - return; - } - fail_utf8_parse(); +void Terminal::emit_code_point(u32 code_point) +{ + auto new_column = m_cursor_column + 1; + if (new_column < columns()) { + put_character_at(m_cursor_row, m_cursor_column, code_point); + set_cursor(m_cursor_row, new_column); return; } + if (m_stomp) { + m_stomp = false; + carriage_return(); + newline(); + put_character_at(m_cursor_row, m_cursor_column, code_point); + set_cursor(m_cursor_row, 1); + } else { + // Curious: We wait once on the right-hand side + m_stomp = true; + put_character_at(m_cursor_row, m_cursor_column, code_point); + } +} - switch (ch) { - case '\0': - return; - case '\033': - m_parser_state = GotEscape; - m_swallow_current = false; +void Terminal::execute_control_code(u8 code) +{ + switch (code) { + case '\a': + m_client.beep(); return; - case 8: // Backspace + case '\b': if (m_cursor_column) { set_cursor(m_cursor_row, m_cursor_column - 1); return; } return; - case '\a': - m_client.beep(); - return; case '\t': { for (unsigned i = m_cursor_column + 1; i < columns(); ++i) { if (m_horizontal_tabs[i]) { @@ -951,37 +686,211 @@ void Terminal::on_input(u8 ch) } return; } - case '\r': - set_cursor(m_cursor_row, 0); - return; case '\n': newline(); return; + case '\r': + carriage_return(); + return; + default: + unimplemented_control_code(code); } +} - on_code_point(ch); +void Terminal::execute_escape_sequence(Intermediates intermediates, bool ignore, u8 last_byte) +{ + // FIXME: Handle it somehow? + if (ignore) + dbgln("Escape sequence has its ignore flag set."); + + if (intermediates.size() == 0) { + switch (last_byte) { + case 'D': + IND(); + return; + case 'E': + NEL(); + return; + case 'M': + RI(); + return; + } + } else if (intermediates[0] == '#') { + switch (last_byte) { + case '8': + // Confidence Test - Fill screen with E's + for (size_t row = 0; row < m_rows; ++row) { + for (size_t column = 0; column < m_columns; ++column) { + put_character_at(row, column, 'E'); + } + } + return; + } + } + unimplemented_escape_sequence(intermediates, last_byte); } -void Terminal::on_code_point(u32 code_point) +void Terminal::execute_csi_sequence(Parameters parameters, Intermediates intermediates, bool ignore, u8 last_byte) { - auto new_column = m_cursor_column + 1; - if (new_column < columns()) { - put_character_at(m_cursor_row, m_cursor_column, code_point); - set_cursor(m_cursor_row, new_column); - return; + // FIXME: Handle it somehow? + if (ignore) + dbgln("CSI sequence has its ignore flag set."); + + switch (last_byte) { + case '@': + ICH(parameters); + break; + case 'A': + CUU(parameters); + break; + case 'B': + CUD(parameters); + break; + case 'C': + CUF(parameters); + break; + case 'D': + CUB(parameters); + break; + case 'G': + CHA(parameters); + break; + case 'H': + CUP(parameters); + break; + case 'J': + ED(parameters); + break; + case 'K': + EL(parameters); + break; + case 'L': + IL(parameters); + break; + case 'M': + DL(parameters); + break; + case 'P': + DCH(parameters); + break; + case 'S': + SU(parameters); + break; + case 'T': + SD(parameters); + break; + case 'X': + ECH(parameters); + break; + case 'b': + REP(parameters); + break; + case 'd': + VPA(parameters); + break; + case 'm': + SGR(parameters); + break; + case 's': + SCOSC(); + break; + case 'u': + SCORC(parameters); + break; + case 't': + XTERM_WM(parameters); + break; + case 'r': + DECSTBM(parameters); + break; + case 'l': + RM(parameters); + break; + case 'h': + SM(parameters); + break; + case 'c': + DA(parameters); + break; + case 'f': + HVP(parameters); + break; + case 'n': + DSR(parameters); + break; + default: + unimplemented_csi_sequence(parameters, intermediates, last_byte); } - if (m_stomp) { - m_stomp = false; - newline(); - put_character_at(m_cursor_row, m_cursor_column, code_point); - set_cursor(m_cursor_row, 1); +} + +void Terminal::execute_osc_sequence(OscParameters parameters, u8 last_byte) +{ + auto stringview_ify = [&](size_t param_idx) { + return StringView((const char*)(¶meters[param_idx][0]), parameters[param_idx].size()); + }; + + if (parameters.size() > 0 && !parameters[0].is_empty()) { + auto command_number = stringview_ify(0).to_uint(); + if (command_number.has_value()) { + switch (command_number.value()) { + case 0: + case 1: + case 2: + if (parameters[1].is_empty()) + dbgln("Attempted to set window title without any parameters"); + else + m_client.set_window_title(stringview_ify(1)); + // FIXME: the split breaks titles containing semicolons. + // Should we expose the raw OSC string from the parser? Or join by semicolon? + break; + case 8: + if (parameters.size() < 2) { + dbgln("Attempted to set href but gave too few parameters"); + } else if (parameters[2].is_empty()) { + m_current_attribute.href = String(); + m_current_attribute.href_id = String(); + } else { + m_current_attribute.href = stringview_ify(2); + // FIXME: Respect the provided ID + m_current_attribute.href_id = String::number(m_next_href_id++); + } + break; + case 9: + if (parameters.size() < 2 || parameters[1].is_empty() || parameters[2].is_empty()) + dbgln("Atttempted to set window progress but gave too few parameters"); + else + m_client.set_window_progress(stringview_ify(1).to_int().value_or(0), stringview_ify(2).to_int().value_or(0)); + break; + default: + unimplemented_osc_sequence(parameters, last_byte); + } + } else { + unimplemented_osc_sequence(parameters, last_byte); + } } else { - // Curious: We wait once on the right-hand side - m_stomp = true; - put_character_at(m_cursor_row, m_cursor_column, code_point); + unimplemented_osc_sequence(parameters, last_byte); } } +void Terminal::dcs_hook(Parameters parameters, Intermediates intermediates, bool ignore, u8 last_byte) +{ + dbgln("Received DCS parameters, but we don't support it yet"); + (void)parameters; + (void)last_byte; + (void)intermediates; + (void)ignore; +} + +void Terminal::receive_dcs_char(u8 byte) +{ + dbgln_if(TERMINAL_DEBUG, "DCS string character {:c}", byte); + (void)byte; +} + +void Terminal::execute_dcs_sequence() +{ +} + void Terminal::inject_string(const StringView& str) { for (size_t i = 0; i < str.length(); ++i) @@ -1078,26 +987,58 @@ void Terminal::handle_key_press(KeyCode key, u32 code_point, u8 flags) emit_string(sb.to_string()); } -void Terminal::unimplemented_escape() +void Terminal::unimplemented_control_code(u8 code) +{ + dbgln("Unimplemented control code {:02x}", code); +} + +void Terminal::unimplemented_escape_sequence(Intermediates intermediates, u8 last_byte) { StringBuilder builder; - builder.appendff("Unimplemented escape: {:c}", m_final); - if (!m_parameters.is_empty()) { - builder.append(", parameters:"); - for (size_t i = 0; i < m_parameters.size(); ++i) - builder.append((char)m_parameters[i]); + builder.appendff("Unimplemented escape sequence {:c}", last_byte); + if (!intermediates.is_empty()) { + builder.append(", intermediates: "); + for (size_t i = 0; i < intermediates.size(); ++i) + builder.append((char)intermediates[i]); } - if (!m_intermediates.is_empty()) { + dbgln("{}", builder.string_view()); +} + +void Terminal::unimplemented_csi_sequence(Parameters parameters, Intermediates intermediates, u8 last_byte) +{ + StringBuilder builder; + builder.appendff("Unimplemented CSI sequence: {:c}", last_byte); + if (!parameters.is_empty()) { + builder.append(", parameters: ["); + for (size_t i = 0; i < parameters.size(); ++i) + builder.appendff("{}{}", (i == 0) ? "" : ", ", parameters[i]); + builder.append("]"); + } + if (!intermediates.is_empty()) { builder.append(", intermediates:"); - for (size_t i = 0; i < m_intermediates.size(); ++i) - builder.append((char)m_intermediates[i]); + for (size_t i = 0; i < intermediates.size(); ++i) + builder.append((char)intermediates[i]); } dbgln("{}", builder.string_view()); } -void Terminal::unimplemented_xterm_escape() +void Terminal::unimplemented_osc_sequence(OscParameters parameters, u8 last_byte) { - dbgln("Unimplemented xterm escape: {:c}", m_final); + StringBuilder builder; + builder.appendff("Unimplemented OSC sequence parameters: (bel_terminated={}) [ ", last_byte == '\a'); + bool first = true; + for (auto parameter : parameters) { + if (!first) + builder.append(", "); + builder.append("["); + for (auto character : parameter) + builder.append((char)character); + builder.append("]"); + first = false; + } + + builder.append(" ]"); + dbgln("{}", builder.string_view()); } void Terminal::set_size(u16 columns, u16 rows) @@ -1145,22 +1086,6 @@ void Terminal::invalidate_cursor() m_lines[m_cursor_row].set_dirty(true); } -void Terminal::execute_hashtag(u8 hashtag) -{ - switch (hashtag) { - case '8': - // Confidence Test - Fill screen with E's - for (size_t row = 0; row < m_rows; ++row) { - for (size_t column = 0; column < m_columns; ++column) { - put_character_at(row, column, 'E'); - } - } - break; - default: - dbgln("Unknown hashtag: '{}'", (char)hashtag); - } -} - Attribute Terminal::attribute_at(const Position& position) const { if (!position.is_valid()) diff --git a/Userland/Libraries/LibVT/Terminal.h b/Userland/Libraries/LibVT/Terminal.h index 71984ab888..bf23e7cc9b 100644 --- a/Userland/Libraries/LibVT/Terminal.h +++ b/Userland/Libraries/LibVT/Terminal.h @@ -11,6 +11,7 @@ #include <AK/String.h> #include <AK/Vector.h> #include <Kernel/API/KeyCode.h> +#include <LibVT/EscapeSequenceParser.h> #include <LibVT/Line.h> #include <LibVT/Position.h> @@ -28,7 +29,7 @@ public: virtual void emit(const u8*, size_t) = 0; }; -class Terminal { +class Terminal : public EscapeSequenceExecutor { public: explicit Terminal(TerminalClient&); ~Terminal(); @@ -106,68 +107,78 @@ public: Attribute attribute_at(const Position&) const; private: - typedef Vector<unsigned, 4> ParamVector; - - void on_code_point(u32); + // ^EscapeSequenceExecutor + virtual void emit_code_point(u32) override; + virtual void execute_control_code(u8) override; + virtual void execute_escape_sequence(Intermediates intermediates, bool ignore, u8 last_byte) override; + virtual void execute_csi_sequence(Parameters parameters, Intermediates intermediates, bool ignore, u8 last_byte) override; + virtual void execute_osc_sequence(OscParameters parameters, u8 last_byte) override; + virtual void dcs_hook(Parameters parameters, Intermediates intermediates, bool ignore, u8 last_byte) override; + virtual void receive_dcs_char(u8 byte) override; + virtual void execute_dcs_sequence() override; void scroll_up(); void scroll_down(); void newline(); + void carriage_return(); + void set_cursor(unsigned row, unsigned column); void put_character_at(unsigned row, unsigned column, u32 ch); void set_window_title(const String&); - void unimplemented_escape(); - void unimplemented_xterm_escape(); + void unimplemented_control_code(u8); + void unimplemented_escape_sequence(Intermediates, u8 last_byte); + void unimplemented_csi_sequence(Parameters, Intermediates, u8 last_byte); + void unimplemented_osc_sequence(OscParameters, u8 last_byte); void emit_string(const StringView&); - void alter_mode(bool should_set, bool question_param, const ParamVector&); + void alter_mode(bool should_set, bool question_param, Parameters); // CUU – Cursor Up - void CUU(const ParamVector&); + void CUU(Parameters); // CUD – Cursor Down - void CUD(const ParamVector&); + void CUD(Parameters); // CUF – Cursor Forward - void CUF(const ParamVector&); + void CUF(Parameters); // CUB – Cursor Backward - void CUB(const ParamVector&); + void CUB(Parameters); // CUP - Cursor Position - void CUP(const ParamVector&); + void CUP(Parameters); // ED - Erase in Display - void ED(const ParamVector&); + void ED(Parameters); // EL - Erase in Line - void EL(const ParamVector&); + void EL(Parameters); // SGR – Select Graphic Rendition - void SGR(const ParamVector&); + void SGR(Parameters); // Save Current Cursor Position - void SCOSC(const ParamVector&); + void SCOSC(); // Restore Saved Cursor Position - void SCORC(const ParamVector&); + void SCORC(Parameters); // DECSTBM – Set Top and Bottom Margins ("Scrolling Region") - void DECSTBM(const ParamVector&); + void DECSTBM(Parameters); // RM – Reset Mode - void RM(bool question_param, const ParamVector&); + void RM(Parameters); // SM – Set Mode - void SM(bool question_param, const ParamVector&); + void SM(Parameters); // DA - Device Attributes - void DA(const ParamVector&); + void DA(Parameters); // HVP – Horizontal and Vertical Position - void HVP(const ParamVector&); + void HVP(Parameters); // NEL - Next Line void NEL(); @@ -179,43 +190,45 @@ private: void RI(); // DSR - Device Status Reports - void DSR(const ParamVector&); + void DSR(Parameters); // ICH - Insert Character - void ICH(const ParamVector&); + void ICH(Parameters); // SU - Scroll Up (called "Pan Down" in VT510) - void SU(const ParamVector&); + void SU(Parameters); // SD - Scroll Down (called "Pan Up" in VT510) - void SD(const ParamVector&); + void SD(Parameters); // IL - Insert Line - void IL(const ParamVector&); + void IL(Parameters); // DCH - Delete Character - void DCH(const ParamVector&); + void DCH(Parameters); // DL - Delete Line - void DL(const ParamVector&); + void DL(Parameters); // CHA - Cursor Horizontal Absolute - void CHA(const ParamVector&); + void CHA(Parameters); // REP - Repeat - void REP(const ParamVector&); + void REP(Parameters); // VPA - Vertical Line Position Absolute - void VPA(const ParamVector&); + void VPA(Parameters); // ECH - Erase Character - void ECH(const ParamVector&); + void ECH(Parameters); // FIXME: Find the right names for these. - void XTERM_WM(const ParamVector&); + void XTERM_WM(Parameters); TerminalClient& m_client; + EscapeSequenceParser m_parser; + size_t m_history_start = 0; NonnullOwnPtrVector<Line> m_history; void add_line_to_history(NonnullOwnPtr<Line>&& line) @@ -248,34 +261,11 @@ private: bool m_stomp { false }; Attribute m_current_attribute; + Attribute m_saved_attribute; u32 m_next_href_id { 0 }; - void execute_escape_sequence(u8 final); - void execute_xterm_command(); - void execute_hashtag(u8); - - enum ParserState { - Normal, - GotEscape, - ExpectParameter, - ExpectIntermediate, - ExpectFinal, - ExpectHashtagDigit, - ExpectXtermParameter, - ExpectStringTerminator, - UTF8Needs3Bytes, - UTF8Needs2Bytes, - UTF8Needs1Byte, - }; - - ParserState m_parser_state { Normal }; - u32 m_parser_code_point { 0 }; - Vector<u8> m_parameters; - Vector<u8> m_intermediates; - Vector<u8> m_xterm_parameters; Vector<bool> m_horizontal_tabs; - u8 m_final { 0 }; u32 m_last_code_point { 0 }; size_t m_max_history_lines { 1024 }; }; |