mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 12:32:43 +00:00 
			
		
		
		
	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.
This commit is contained in:
		
							parent
							
								
									1b347298f1
								
							
						
					
					
						commit
						be519022c3
					
				
					 11 changed files with 707 additions and 571 deletions
				
			
		|  | @ -398,6 +398,10 @@ | ||||||
| #cmakedefine01 WASM_BINPARSER_DEBUG | #cmakedefine01 WASM_BINPARSER_DEBUG | ||||||
| #endif  | #endif  | ||||||
| 
 | 
 | ||||||
|  | #ifndef ESCAPE_SEQUENCE_DEBUG | ||||||
|  | #cmakedefine01 ESCAPE_SEQUENCE_DEBUG | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
| #ifndef WINDOWMANAGER_DEBUG | #ifndef WINDOWMANAGER_DEBUG | ||||||
| #cmakedefine01 WINDOWMANAGER_DEBUG | #cmakedefine01 WINDOWMANAGER_DEBUG | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|  | @ -278,9 +278,13 @@ set(ELF_SOURCES | ||||||
|     ../Userland/Libraries/LibELF/Validation.cpp |     ../Userland/Libraries/LibELF/Validation.cpp | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | generate_state_machine(../Userland/Libraries/LibVT/StateMachine.txt ../Userland/Libraries/LibVT/EscapeSequenceStateMachine.h) | ||||||
|  | 
 | ||||||
| set(VT_SOURCES | set(VT_SOURCES | ||||||
|     ../Userland/Libraries/LibVT/Terminal.cpp |     ../Userland/Libraries/LibVT/Terminal.cpp | ||||||
|     ../Userland/Libraries/LibVT/Line.cpp |     ../Userland/Libraries/LibVT/Line.cpp | ||||||
|  |     ../Userland/Libraries/LibVT/EscapeSequenceStateMachine.h | ||||||
|  |     ../Userland/Libraries/LibVT/EscapeSequenceParser.cpp | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| set(KEYBOARD_SOURCES | set(KEYBOARD_SOURCES | ||||||
|  |  | ||||||
|  | @ -159,6 +159,7 @@ set(STORAGE_DEVICE_DEBUG ON) | ||||||
| set(TCP_DEBUG ON) | set(TCP_DEBUG ON) | ||||||
| set(TERMCAP_DEBUG ON) | set(TERMCAP_DEBUG ON) | ||||||
| set(TERMINAL_DEBUG ON) | set(TERMINAL_DEBUG ON) | ||||||
|  | set(ESCAPE_SEQUENCE_DEBUG ON) | ||||||
| set(UCI_DEBUG ON) | set(UCI_DEBUG ON) | ||||||
| set(UDP_DEBUG ON) | set(UDP_DEBUG ON) | ||||||
| set(UHCI_VERBOSE_DEBUG ON) | set(UHCI_VERBOSE_DEBUG ON) | ||||||
|  |  | ||||||
|  | @ -154,3 +154,15 @@ function(embed_resource target section file) | ||||||
|     ) |     ) | ||||||
|     target_sources("${target}" PRIVATE "${asm_file}") |     target_sources("${target}" PRIVATE "${asm_file}") | ||||||
| endfunction() | endfunction() | ||||||
|  | 
 | ||||||
|  | function(generate_state_machine source header) | ||||||
|  |     set(source ${CMAKE_CURRENT_SOURCE_DIR}/${source}) | ||||||
|  |     add_custom_command( | ||||||
|  |         OUTPUT ${header} | ||||||
|  | 	COMMAND ${write_if_different} ${header} ${CMAKE_BINARY_DIR}/Userland/DevTools/StateMachineGenerator/StateMachineGenerator ${source} > ${header} | ||||||
|  |         VERBATIM | ||||||
|  |         DEPENDS StateMachineGenerator | ||||||
|  |         MAIN_DEPENDENCY ${source} | ||||||
|  |     ) | ||||||
|  |     get_filename_component(output_name ${header} NAME) | ||||||
|  | endfunction() | ||||||
|  |  | ||||||
|  | @ -211,18 +211,15 @@ parse_state_machine(StringView input) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void output_header(const StateMachine&, SourceGenerator&); | void output_header(const StateMachine&, SourceGenerator&); | ||||||
| void output_cpp(const StateMachine&, SourceGenerator&); |  | ||||||
| 
 | 
 | ||||||
| int main(int argc, char** argv) | int main(int argc, char** argv) | ||||||
| { | { | ||||||
|     Core::ArgsParser args_parser; |     Core::ArgsParser args_parser; | ||||||
|     const char* path = nullptr; |     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.add_positional_argument(path, "Path to parser description", "input", Core::ArgsParser::Required::Yes); | ||||||
|     args_parser.parse(argc, argv); |     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()) { |     if (file_or_error.is_error()) { | ||||||
|         fprintf(stderr, "Cannot open %s\n", path); |         fprintf(stderr, "Cannot open %s\n", path); | ||||||
|     } |     } | ||||||
|  | @ -232,10 +229,7 @@ int main(int argc, char** argv) | ||||||
| 
 | 
 | ||||||
|     StringBuilder builder; |     StringBuilder builder; | ||||||
|     SourceGenerator generator { builder }; |     SourceGenerator generator { builder }; | ||||||
|     if (header_mode) |  | ||||||
|     output_header(*state_machine, generator); |     output_header(*state_machine, generator); | ||||||
|     else |  | ||||||
|         output_cpp(*state_machine, generator); |  | ||||||
|     outln("{}", generator.as_string_view()); |     outln("{}", generator.as_string_view()); | ||||||
|     return 0; |     return 0; | ||||||
| } | } | ||||||
|  | @ -340,9 +334,66 @@ public: | ||||||
| 
 | 
 | ||||||
|     typedef Function<void(Action, u8)> Handler; |     typedef Function<void(Action, u8)> Handler; | ||||||
| 
 | 
 | ||||||
|     @class_name@(Handler); |     @class_name@(Handler handler) | ||||||
|  |     : m_handler(move(handler)) | ||||||
|  |     { | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     void advance(u8); |     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; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         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: | private: | ||||||
|     enum class State : u8 { |     enum class State : u8 { | ||||||
|  | @ -370,7 +421,21 @@ private: | ||||||
| 
 | 
 | ||||||
|     Handler m_handler; |     Handler m_handler; | ||||||
| 
 | 
 | ||||||
|     StateTransition lookup_state_transition(u8); |     ALWAYS_INLINE StateTransition 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 != State::_Anywhere || anywhere_state.action != Action::_Ignore) | ||||||
|  |             return anywhere_state; | ||||||
|  |         else | ||||||
|  | )~~~"); | ||||||
|  |     } | ||||||
|  |     generator.append(R"~~~( | ||||||
|  |             return STATE_TRANSITION_TABLE[(u8)m_state][byte]; | ||||||
|  |     } | ||||||
| )~~~"); | )~~~"); | ||||||
| 
 | 
 | ||||||
|     auto table_generator = generator.fork(); |     auto table_generator = generator.fork(); | ||||||
|  | @ -385,111 +450,3 @@ private: | ||||||
| )~~~"); | )~~~"); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 |  | ||||||
| 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@ { |  | ||||||
| )~~~"); |  | ||||||
|     } |  | ||||||
|     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; |  | ||||||
| )~~~"); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     generator.append(R"~~~( |  | ||||||
|         default: |  | ||||||
|             break; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     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; |  | ||||||
| )~~~"); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     generator.append(R"~~~( |  | ||||||
|         default: |  | ||||||
|             break; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| )~~~"); |  | ||||||
| 
 |  | ||||||
|     if (machine.namespaces.has_value()) { |  | ||||||
|         generator.append(R"~~~( |  | ||||||
| } // end namespace
 |  | ||||||
| )~~~"); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  | @ -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 | set(SOURCES | ||||||
|     Line.cpp |     Line.cpp | ||||||
|     Terminal.cpp |     Terminal.cpp | ||||||
|     TerminalWidget.cpp |     TerminalWidget.cpp | ||||||
|  |     EscapeSequenceParser.cpp | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| serenity_lib(LibVT vt) | serenity_lib(LibVT vt) | ||||||
|  |  | ||||||
							
								
								
									
										162
									
								
								Userland/Libraries/LibVT/EscapeSequenceParser.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										162
									
								
								Userland/Libraries/LibVT/EscapeSequenceParser.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -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'<EFBFBD>'); | ||||||
|  |         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; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | } | ||||||
							
								
								
									
										77
									
								
								Userland/Libraries/LibVT/EscapeSequenceParser.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								Userland/Libraries/LibVT/EscapeSequenceParser.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -3,7 +3,7 @@ | ||||||
| // The description of the state machine is taken from https://vt100.net/emu/dec_ansi_parser | // The description of the state machine is taken from https://vt100.net/emu/dec_ansi_parser | ||||||
| // with added support for UTF-8 parsing | // with added support for UTF-8 parsing | ||||||
| 
 | 
 | ||||||
| @name VTParserStateMachine | @name EscapeSequenceStateMachine | ||||||
| @namespace VT | @namespace VT | ||||||
| @begin Ground | @begin Ground | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -4,6 +4,7 @@ | ||||||
|  * SPDX-License-Identifier: BSD-2-Clause |  * SPDX-License-Identifier: BSD-2-Clause | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
|  | #include "Terminal.h" | ||||||
| #include <AK/Debug.h> | #include <AK/Debug.h> | ||||||
| #include <AK/StringBuilder.h> | #include <AK/StringBuilder.h> | ||||||
| #include <AK/StringView.h> | #include <AK/StringView.h> | ||||||
|  | @ -13,6 +14,7 @@ namespace VT { | ||||||
| 
 | 
 | ||||||
| Terminal::Terminal(TerminalClient& client) | Terminal::Terminal(TerminalClient& client) | ||||||
|     : m_client(client) |     : m_client(client) | ||||||
|  |     , m_parser(*this) | ||||||
| { | { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -37,22 +39,7 @@ void Terminal::clear_including_history() | ||||||
|     m_client.terminal_history_changed(); |     m_client.terminal_history_changed(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| inline bool is_valid_parameter_character(u8 ch) | void Terminal::alter_mode(bool should_set, bool question_param, Parameters params) | ||||||
| { |  | ||||||
|     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) |  | ||||||
| { | { | ||||||
|     int mode = 2; |     int mode = 2; | ||||||
|     if (params.size() > 0) { |     if (params.size() > 0) { | ||||||
|  | @ -62,7 +49,7 @@ void Terminal::alter_mode(bool should_set, bool question_param, const ParamVecto | ||||||
|         switch (mode) { |         switch (mode) { | ||||||
|         // FIXME: implement *something* for this
 |         // FIXME: implement *something* for this
 | ||||||
|         default: |         default: | ||||||
|             unimplemented_escape(); |             dbgln("Terminal::alter_mode: Unimplemented mode {} (set={})", mode, should_set); | ||||||
|             break; |             break; | ||||||
|         } |         } | ||||||
|     } else { |     } else { | ||||||
|  | @ -70,7 +57,7 @@ void Terminal::alter_mode(bool should_set, bool question_param, const ParamVecto | ||||||
|         case 3: { |         case 3: { | ||||||
|             // 80/132-column mode (DECCOLM)
 |             // 80/132-column mode (DECCOLM)
 | ||||||
|             unsigned new_columns = should_set ? 80 : 132; |             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()); |             set_size(new_columns, rows()); | ||||||
|             clear(); |             clear(); | ||||||
|             break; |             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."); |                 dbgln("Terminal: Show Cursor escapecode received. Not needed: ignored."); | ||||||
|             break; |             break; | ||||||
|         default: |         default: | ||||||
|             dbgln("Set Mode: Unimplemented mode {}", mode); |             dbgln("Terminal::alter_mode: Unimplemented private mode {}", mode); | ||||||
|             break; |             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); |     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); |     alter_mode(false, question_param, params); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Terminal::SGR(const ParamVector& params) | void Terminal::SGR(Parameters params) | ||||||
| { | { | ||||||
|     if (params.is_empty()) { |     if (params.is_empty()) { | ||||||
|         m_current_attribute.reset(); |         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_row = m_cursor_row; | ||||||
|     m_saved_cursor_column = m_cursor_column; |     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); |     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) |     if (params.size() < 1) | ||||||
|         return; |         return; | ||||||
|     dbgln("FIXME: XTERM_WM: Ps: {} (param count: {})", params[0], params.size()); |     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 top = 1; | ||||||
|     unsigned bottom = m_rows; |     unsigned bottom = m_rows; | ||||||
|  | @ -250,7 +248,7 @@ void Terminal::DECSTBM(const ParamVector& params) | ||||||
|     set_cursor(0, 0); |     set_cursor(0, 0); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Terminal::CUP(const ParamVector& params) | void Terminal::CUP(Parameters params) | ||||||
| { | { | ||||||
|     // CUP – Cursor Position
 |     // CUP – Cursor Position
 | ||||||
|     unsigned row = 1; |     unsigned row = 1; | ||||||
|  | @ -262,7 +260,7 @@ void Terminal::CUP(const ParamVector& params) | ||||||
|     set_cursor(row - 1, col - 1); |     set_cursor(row - 1, col - 1); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Terminal::HVP(const ParamVector& params) | void Terminal::HVP(Parameters params) | ||||||
| { | { | ||||||
|     unsigned row = 1; |     unsigned row = 1; | ||||||
|     unsigned col = 1; |     unsigned col = 1; | ||||||
|  | @ -273,7 +271,7 @@ void Terminal::HVP(const ParamVector& params) | ||||||
|     set_cursor(row - 1, col - 1); |     set_cursor(row - 1, col - 1); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Terminal::CUU(const ParamVector& params) | void Terminal::CUU(Parameters params) | ||||||
| { | { | ||||||
|     int num = 1; |     int num = 1; | ||||||
|     if (params.size() >= 1) |     if (params.size() >= 1) | ||||||
|  | @ -286,7 +284,7 @@ void Terminal::CUU(const ParamVector& params) | ||||||
|     set_cursor(new_row, m_cursor_column); |     set_cursor(new_row, m_cursor_column); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Terminal::CUD(const ParamVector& params) | void Terminal::CUD(Parameters params) | ||||||
| { | { | ||||||
|     int num = 1; |     int num = 1; | ||||||
|     if (params.size() >= 1) |     if (params.size() >= 1) | ||||||
|  | @ -299,7 +297,7 @@ void Terminal::CUD(const ParamVector& params) | ||||||
|     set_cursor(new_row, m_cursor_column); |     set_cursor(new_row, m_cursor_column); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Terminal::CUF(const ParamVector& params) | void Terminal::CUF(Parameters params) | ||||||
| { | { | ||||||
|     int num = 1; |     int num = 1; | ||||||
|     if (params.size() >= 1) |     if (params.size() >= 1) | ||||||
|  | @ -312,7 +310,7 @@ void Terminal::CUF(const ParamVector& params) | ||||||
|     set_cursor(m_cursor_row, new_column); |     set_cursor(m_cursor_row, new_column); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Terminal::CUB(const ParamVector& params) | void Terminal::CUB(Parameters params) | ||||||
| { | { | ||||||
|     int num = 1; |     int num = 1; | ||||||
|     if (params.size() >= 1) |     if (params.size() >= 1) | ||||||
|  | @ -325,7 +323,7 @@ void Terminal::CUB(const ParamVector& params) | ||||||
|     set_cursor(m_cursor_row, new_column); |     set_cursor(m_cursor_row, new_column); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Terminal::CHA(const ParamVector& params) | void Terminal::CHA(Parameters params) | ||||||
| { | { | ||||||
|     int new_column = 1; |     int new_column = 1; | ||||||
|     if (params.size() >= 1) |     if (params.size() >= 1) | ||||||
|  | @ -335,7 +333,7 @@ void Terminal::CHA(const ParamVector& params) | ||||||
|     set_cursor(m_cursor_row, new_column); |     set_cursor(m_cursor_row, new_column); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Terminal::REP(const ParamVector& params) | void Terminal::REP(Parameters params) | ||||||
| { | { | ||||||
|     if (params.size() < 1) |     if (params.size() < 1) | ||||||
|         return; |         return; | ||||||
|  | @ -344,7 +342,7 @@ void Terminal::REP(const ParamVector& params) | ||||||
|         put_character_at(m_cursor_row, m_cursor_column++, m_last_code_point); |         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; |     int new_row = 1; | ||||||
|     if (params.size() >= 1) |     if (params.size() >= 1) | ||||||
|  | @ -354,7 +352,7 @@ void Terminal::VPA(const ParamVector& params) | ||||||
|     set_cursor(new_row, m_cursor_column); |     set_cursor(new_row, m_cursor_column); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Terminal::ECH(const ParamVector& params) | void Terminal::ECH(Parameters params) | ||||||
| { | { | ||||||
|     // Erase characters (without moving cursor)
 |     // Erase characters (without moving cursor)
 | ||||||
|     int num = 1; |     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; |     int mode = 0; | ||||||
|     if (params.size() >= 1) |     if (params.size() >= 1) | ||||||
|  | @ -393,12 +391,12 @@ void Terminal::EL(const ParamVector& params) | ||||||
|         } |         } | ||||||
|         break; |         break; | ||||||
|     default: |     default: | ||||||
|         unimplemented_escape(); |         unimplemented_csi_sequence(params, {}, 'K'); | ||||||
|         break; |         break; | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Terminal::ED(const ParamVector& params) | void Terminal::ED(Parameters params) | ||||||
| { | { | ||||||
|     int mode = 0; |     int mode = 0; | ||||||
|     if (params.size() >= 1) |     if (params.size() >= 1) | ||||||
|  | @ -432,12 +430,12 @@ void Terminal::ED(const ParamVector& params) | ||||||
|         clear(); |         clear(); | ||||||
|         break; |         break; | ||||||
|     default: |     default: | ||||||
|         unimplemented_escape(); |         unimplemented_csi_sequence(params, {}, 'J'); | ||||||
|         break; |         break; | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Terminal::SU(const ParamVector& params) | void Terminal::SU(Parameters params) | ||||||
| { | { | ||||||
|     int count = 1; |     int count = 1; | ||||||
|     if (params.size() >= 1) |     if (params.size() >= 1) | ||||||
|  | @ -447,7 +445,7 @@ void Terminal::SU(const ParamVector& params) | ||||||
|         scroll_up(); |         scroll_up(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Terminal::SD(const ParamVector& params) | void Terminal::SD(Parameters params) | ||||||
| { | { | ||||||
|     int count = 1; |     int count = 1; | ||||||
|     if (params.size() >= 1) |     if (params.size() >= 1) | ||||||
|  | @ -457,7 +455,7 @@ void Terminal::SD(const ParamVector& params) | ||||||
|         scroll_down(); |         scroll_down(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Terminal::IL(const ParamVector& params) | void Terminal::IL(Parameters params) | ||||||
| { | { | ||||||
|     int count = 1; |     int count = 1; | ||||||
|     if (params.size() >= 1) |     if (params.size() >= 1) | ||||||
|  | @ -474,12 +472,12 @@ void Terminal::IL(const ParamVector& params) | ||||||
|     m_need_full_flush = true; |     m_need_full_flush = true; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Terminal::DA(const ParamVector&) | void Terminal::DA(Parameters) | ||||||
| { | { | ||||||
|     emit_string("\033[?1;0c"); |     emit_string("\033[?1;0c"); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Terminal::DL(const ParamVector& params) | void Terminal::DL(Parameters params) | ||||||
| { | { | ||||||
|     int count = 1; |     int count = 1; | ||||||
|     if (params.size() >= 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; |     int num = 1; | ||||||
|     if (params.size() >= 1) |     if (params.size() >= 1) | ||||||
|  | @ -524,165 +522,6 @@ void Terminal::DCH(const ParamVector& params) | ||||||
|     line.set_dirty(true); |     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() | void Terminal::newline() | ||||||
| { | { | ||||||
|     u16 new_row = m_cursor_row; |     u16 new_row = m_cursor_row; | ||||||
|  | @ -690,9 +529,13 @@ void Terminal::newline() | ||||||
|         scroll_up(); |         scroll_up(); | ||||||
|     } else { |     } else { | ||||||
|         ++new_row; |         ++new_row; | ||||||
|     } |     }; | ||||||
|     set_cursor(new_row, 0); |     set_cursor(new_row, 0); | ||||||
| } | } | ||||||
|  | void Terminal::carriage_return() | ||||||
|  | { | ||||||
|  |     set_cursor(m_cursor_row, 0); | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| void Terminal::scroll_up() | void Terminal::scroll_up() | ||||||
| { | { | ||||||
|  | @ -748,6 +591,7 @@ void Terminal::put_character_at(unsigned row, unsigned column, u32 code_point) | ||||||
| void Terminal::NEL() | void Terminal::NEL() | ||||||
| { | { | ||||||
|     newline(); |     newline(); | ||||||
|  |     carriage_return(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Terminal::IND() | void Terminal::IND() | ||||||
|  | @ -760,7 +604,7 @@ void Terminal::RI() | ||||||
|     CUU({}); |     CUU({}); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Terminal::DSR(const ParamVector& params) | void Terminal::DSR(Parameters params) | ||||||
| { | { | ||||||
|     if (params.size() == 1 && params[0] == 5) { |     if (params.size() == 1 && params[0] == 5) { | ||||||
|         // Device status
 |         // 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; |     int num = 0; | ||||||
|     if (params.size() >= 1) { |     if (params.size() >= 1) { | ||||||
|  | @ -795,153 +639,44 @@ void Terminal::ICH(const ParamVector& params) | ||||||
|     line.set_dirty(true); |     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); |     m_parser.on_input(byte); | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
|     auto fail_utf8_parse = [this] { | void Terminal::emit_code_point(u32 code_point) | ||||||
|         m_parser_state = Normal; | { | ||||||
|         on_code_point(U'<EFBFBD>'); |     auto new_column = m_cursor_column + 1; | ||||||
|     }; |     if (new_column < columns()) { | ||||||
| 
 |         put_character_at(m_cursor_row, m_cursor_column, code_point); | ||||||
|     auto advance_utf8_parse = [this, ch] { |         set_cursor(m_cursor_row, new_column); | ||||||
|         m_parser_code_point <<= 6; |         return; | ||||||
|         m_parser_code_point |= ch & 0x3f; |     } | ||||||
|         if (m_parser_state == UTF8Needs1Byte) { |     if (m_stomp) { | ||||||
|             on_code_point(m_parser_code_point); |         m_stomp = false; | ||||||
|             m_parser_state = Normal; |         carriage_return(); | ||||||
|  |         newline(); | ||||||
|  |         put_character_at(m_cursor_row, m_cursor_column, code_point); | ||||||
|  |         set_cursor(m_cursor_row, 1); | ||||||
|     } else { |     } else { | ||||||
|             m_parser_state = (ParserState)(m_parser_state + 1); |         // Curious: We wait once on the right-hand side
 | ||||||
|  |         m_stomp = true; | ||||||
|  |         put_character_at(m_cursor_row, m_cursor_column, code_point); | ||||||
|     } |     } | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     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; |  | ||||||
| 
 |  | ||||||
|     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(); |  | ||||||
|         return; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|     switch (ch) { | void Terminal::execute_control_code(u8 code) | ||||||
|     case '\0': | { | ||||||
|  |     switch (code) { | ||||||
|  |     case '\a': | ||||||
|  |         m_client.beep(); | ||||||
|         return; |         return; | ||||||
|     case '\033': |     case '\b': | ||||||
|         m_parser_state = GotEscape; |  | ||||||
|         m_swallow_current = false; |  | ||||||
|         return; |  | ||||||
|     case 8: // Backspace
 |  | ||||||
|         if (m_cursor_column) { |         if (m_cursor_column) { | ||||||
|             set_cursor(m_cursor_row, m_cursor_column - 1); |             set_cursor(m_cursor_row, m_cursor_column - 1); | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|         return; |         return; | ||||||
|     case '\a': |  | ||||||
|         m_client.beep(); |  | ||||||
|         return; |  | ||||||
|     case '\t': { |     case '\t': { | ||||||
|         for (unsigned i = m_cursor_column + 1; i < columns(); ++i) { |         for (unsigned i = m_cursor_column + 1; i < columns(); ++i) { | ||||||
|             if (m_horizontal_tabs[i]) { |             if (m_horizontal_tabs[i]) { | ||||||
|  | @ -951,36 +686,210 @@ void Terminal::on_input(u8 ch) | ||||||
|         } |         } | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|     case '\r': |  | ||||||
|         set_cursor(m_cursor_row, 0); |  | ||||||
|         return; |  | ||||||
|     case '\n': |     case '\n': | ||||||
|         newline(); |         newline(); | ||||||
|         return; |         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) | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void Terminal::on_code_point(u32 code_point) |  | ||||||
| { | { | ||||||
|     auto new_column = m_cursor_column + 1; |     // FIXME: Handle it somehow?
 | ||||||
|     if (new_column < columns()) { |     if (ignore) | ||||||
|         put_character_at(m_cursor_row, m_cursor_column, code_point); |         dbgln("Escape sequence has its ignore flag set."); | ||||||
|         set_cursor(m_cursor_row, new_column); | 
 | ||||||
|  |     if (intermediates.size() == 0) { | ||||||
|  |         switch (last_byte) { | ||||||
|  |         case 'D': | ||||||
|  |             IND(); | ||||||
|  |             return; | ||||||
|  |         case 'E': | ||||||
|  |             NEL(); | ||||||
|  |             return; | ||||||
|  |         case 'M': | ||||||
|  |             RI(); | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|     if (m_stomp) { |     } else if (intermediates[0] == '#') { | ||||||
|         m_stomp = false; |         switch (last_byte) { | ||||||
|         newline(); |         case '8': | ||||||
|         put_character_at(m_cursor_row, m_cursor_column, code_point); |             // Confidence Test - Fill screen with E's
 | ||||||
|         set_cursor(m_cursor_row, 1); |             for (size_t row = 0; row < m_rows; ++row) { | ||||||
|     } else { |                 for (size_t column = 0; column < m_columns; ++column) { | ||||||
|         // Curious: We wait once on the right-hand side
 |                     put_character_at(row, column, 'E'); | ||||||
|         m_stomp = true; |  | ||||||
|         put_character_at(m_cursor_row, m_cursor_column, code_point); |  | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     unimplemented_escape_sequence(intermediates, last_byte); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void Terminal::execute_csi_sequence(Parameters parameters, Intermediates intermediates, bool ignore, u8 last_byte) | ||||||
|  | { | ||||||
|  |     // 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); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 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 { | ||||||
|  |         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) | void Terminal::inject_string(const StringView& str) | ||||||
| { | { | ||||||
|  | @ -1078,26 +987,58 @@ void Terminal::handle_key_press(KeyCode key, u32 code_point, u8 flags) | ||||||
|     emit_string(sb.to_string()); |     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; |     StringBuilder builder; | ||||||
|     builder.appendff("Unimplemented escape: {:c}", m_final); |     builder.appendff("Unimplemented escape sequence {:c}", last_byte); | ||||||
|     if (!m_parameters.is_empty()) { |     if (!intermediates.is_empty()) { | ||||||
|         builder.append(", parameters:"); |  | ||||||
|         for (size_t i = 0; i < m_parameters.size(); ++i) |  | ||||||
|             builder.append((char)m_parameters[i]); |  | ||||||
|     } |  | ||||||
|     if (!m_intermediates.is_empty()) { |  | ||||||
|         builder.append(", intermediates: "); |         builder.append(", intermediates: "); | ||||||
|         for (size_t i = 0; i < m_intermediates.size(); ++i) |         for (size_t i = 0; i < intermediates.size(); ++i) | ||||||
|             builder.append((char)m_intermediates[i]); |             builder.append((char)intermediates[i]); | ||||||
|     } |     } | ||||||
|     dbgln("{}", builder.string_view()); |     dbgln("{}", builder.string_view()); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Terminal::unimplemented_xterm_escape() | void Terminal::unimplemented_csi_sequence(Parameters parameters, Intermediates intermediates, u8 last_byte) | ||||||
| { | { | ||||||
|     dbgln("Unimplemented xterm escape: {:c}", m_final); |     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 < intermediates.size(); ++i) | ||||||
|  |             builder.append((char)intermediates[i]); | ||||||
|  |     } | ||||||
|  |     dbgln("{}", builder.string_view()); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void Terminal::unimplemented_osc_sequence(OscParameters parameters, u8 last_byte) | ||||||
|  | { | ||||||
|  |     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) | void Terminal::set_size(u16 columns, u16 rows) | ||||||
|  | @ -1145,22 +1086,6 @@ void Terminal::invalidate_cursor() | ||||||
|     m_lines[m_cursor_row].set_dirty(true); |     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 | Attribute Terminal::attribute_at(const Position& position) const | ||||||
| { | { | ||||||
|     if (!position.is_valid()) |     if (!position.is_valid()) | ||||||
|  |  | ||||||
|  | @ -11,6 +11,7 @@ | ||||||
| #include <AK/String.h> | #include <AK/String.h> | ||||||
| #include <AK/Vector.h> | #include <AK/Vector.h> | ||||||
| #include <Kernel/API/KeyCode.h> | #include <Kernel/API/KeyCode.h> | ||||||
|  | #include <LibVT/EscapeSequenceParser.h> | ||||||
| #include <LibVT/Line.h> | #include <LibVT/Line.h> | ||||||
| #include <LibVT/Position.h> | #include <LibVT/Position.h> | ||||||
| 
 | 
 | ||||||
|  | @ -28,7 +29,7 @@ public: | ||||||
|     virtual void emit(const u8*, size_t) = 0; |     virtual void emit(const u8*, size_t) = 0; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| class Terminal { | class Terminal : public EscapeSequenceExecutor { | ||||||
| public: | public: | ||||||
|     explicit Terminal(TerminalClient&); |     explicit Terminal(TerminalClient&); | ||||||
|     ~Terminal(); |     ~Terminal(); | ||||||
|  | @ -106,68 +107,78 @@ public: | ||||||
|     Attribute attribute_at(const Position&) const; |     Attribute attribute_at(const Position&) const; | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|     typedef Vector<unsigned, 4> ParamVector; |     // ^EscapeSequenceExecutor
 | ||||||
| 
 |     virtual void emit_code_point(u32) override; | ||||||
|     void on_code_point(u32); |     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_up(); | ||||||
|     void scroll_down(); |     void scroll_down(); | ||||||
|     void newline(); |     void newline(); | ||||||
|  |     void carriage_return(); | ||||||
|  | 
 | ||||||
|     void set_cursor(unsigned row, unsigned column); |     void set_cursor(unsigned row, unsigned column); | ||||||
|     void put_character_at(unsigned row, unsigned column, u32 ch); |     void put_character_at(unsigned row, unsigned column, u32 ch); | ||||||
|     void set_window_title(const String&); |     void set_window_title(const String&); | ||||||
| 
 | 
 | ||||||
|     void unimplemented_escape(); |     void unimplemented_control_code(u8); | ||||||
|     void unimplemented_xterm_escape(); |     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 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
 |     // CUU – Cursor Up
 | ||||||
|     void CUU(const ParamVector&); |     void CUU(Parameters); | ||||||
| 
 | 
 | ||||||
|     // CUD – Cursor Down
 |     // CUD – Cursor Down
 | ||||||
|     void CUD(const ParamVector&); |     void CUD(Parameters); | ||||||
| 
 | 
 | ||||||
|     // CUF – Cursor Forward
 |     // CUF – Cursor Forward
 | ||||||
|     void CUF(const ParamVector&); |     void CUF(Parameters); | ||||||
| 
 | 
 | ||||||
|     // CUB – Cursor Backward
 |     // CUB – Cursor Backward
 | ||||||
|     void CUB(const ParamVector&); |     void CUB(Parameters); | ||||||
| 
 | 
 | ||||||
|     // CUP - Cursor Position
 |     // CUP - Cursor Position
 | ||||||
|     void CUP(const ParamVector&); |     void CUP(Parameters); | ||||||
| 
 | 
 | ||||||
|     // ED - Erase in Display
 |     // ED - Erase in Display
 | ||||||
|     void ED(const ParamVector&); |     void ED(Parameters); | ||||||
| 
 | 
 | ||||||
|     // EL - Erase in Line
 |     // EL - Erase in Line
 | ||||||
|     void EL(const ParamVector&); |     void EL(Parameters); | ||||||
| 
 | 
 | ||||||
|     // SGR – Select Graphic Rendition
 |     // SGR – Select Graphic Rendition
 | ||||||
|     void SGR(const ParamVector&); |     void SGR(Parameters); | ||||||
| 
 | 
 | ||||||
|     // Save Current Cursor Position
 |     // Save Current Cursor Position
 | ||||||
|     void SCOSC(const ParamVector&); |     void SCOSC(); | ||||||
| 
 | 
 | ||||||
|     // Restore Saved Cursor Position
 |     // Restore Saved Cursor Position
 | ||||||
|     void SCORC(const ParamVector&); |     void SCORC(Parameters); | ||||||
| 
 | 
 | ||||||
|     // DECSTBM – Set Top and Bottom Margins ("Scrolling Region")
 |     // DECSTBM – Set Top and Bottom Margins ("Scrolling Region")
 | ||||||
|     void DECSTBM(const ParamVector&); |     void DECSTBM(Parameters); | ||||||
| 
 | 
 | ||||||
|     // RM – Reset Mode
 |     // RM – Reset Mode
 | ||||||
|     void RM(bool question_param, const ParamVector&); |     void RM(Parameters); | ||||||
| 
 | 
 | ||||||
|     // SM – Set Mode
 |     // SM – Set Mode
 | ||||||
|     void SM(bool question_param, const ParamVector&); |     void SM(Parameters); | ||||||
| 
 | 
 | ||||||
|     // DA - Device Attributes
 |     // DA - Device Attributes
 | ||||||
|     void DA(const ParamVector&); |     void DA(Parameters); | ||||||
| 
 | 
 | ||||||
|     // HVP – Horizontal and Vertical Position
 |     // HVP – Horizontal and Vertical Position
 | ||||||
|     void HVP(const ParamVector&); |     void HVP(Parameters); | ||||||
| 
 | 
 | ||||||
|     // NEL - Next Line
 |     // NEL - Next Line
 | ||||||
|     void NEL(); |     void NEL(); | ||||||
|  | @ -179,43 +190,45 @@ private: | ||||||
|     void RI(); |     void RI(); | ||||||
| 
 | 
 | ||||||
|     // DSR - Device Status Reports
 |     // DSR - Device Status Reports
 | ||||||
|     void DSR(const ParamVector&); |     void DSR(Parameters); | ||||||
| 
 | 
 | ||||||
|     // ICH - Insert Character
 |     // ICH - Insert Character
 | ||||||
|     void ICH(const ParamVector&); |     void ICH(Parameters); | ||||||
| 
 | 
 | ||||||
|     // SU - Scroll Up (called "Pan Down" in VT510)
 |     // SU - Scroll Up (called "Pan Down" in VT510)
 | ||||||
|     void SU(const ParamVector&); |     void SU(Parameters); | ||||||
| 
 | 
 | ||||||
|     // SD - Scroll Down (called "Pan Up" in VT510)
 |     // SD - Scroll Down (called "Pan Up" in VT510)
 | ||||||
|     void SD(const ParamVector&); |     void SD(Parameters); | ||||||
| 
 | 
 | ||||||
|     // IL - Insert Line
 |     // IL - Insert Line
 | ||||||
|     void IL(const ParamVector&); |     void IL(Parameters); | ||||||
| 
 | 
 | ||||||
|     // DCH - Delete Character
 |     // DCH - Delete Character
 | ||||||
|     void DCH(const ParamVector&); |     void DCH(Parameters); | ||||||
| 
 | 
 | ||||||
|     // DL - Delete Line
 |     // DL - Delete Line
 | ||||||
|     void DL(const ParamVector&); |     void DL(Parameters); | ||||||
| 
 | 
 | ||||||
|     // CHA - Cursor Horizontal Absolute
 |     // CHA - Cursor Horizontal Absolute
 | ||||||
|     void CHA(const ParamVector&); |     void CHA(Parameters); | ||||||
| 
 | 
 | ||||||
|     // REP - Repeat
 |     // REP - Repeat
 | ||||||
|     void REP(const ParamVector&); |     void REP(Parameters); | ||||||
| 
 | 
 | ||||||
|     // VPA - Vertical Line Position Absolute
 |     // VPA - Vertical Line Position Absolute
 | ||||||
|     void VPA(const ParamVector&); |     void VPA(Parameters); | ||||||
| 
 | 
 | ||||||
|     // ECH - Erase Character
 |     // ECH - Erase Character
 | ||||||
|     void ECH(const ParamVector&); |     void ECH(Parameters); | ||||||
| 
 | 
 | ||||||
|     // FIXME: Find the right names for these.
 |     // FIXME: Find the right names for these.
 | ||||||
|     void XTERM_WM(const ParamVector&); |     void XTERM_WM(Parameters); | ||||||
| 
 | 
 | ||||||
|     TerminalClient& m_client; |     TerminalClient& m_client; | ||||||
| 
 | 
 | ||||||
|  |     EscapeSequenceParser m_parser; | ||||||
|  | 
 | ||||||
|     size_t m_history_start = 0; |     size_t m_history_start = 0; | ||||||
|     NonnullOwnPtrVector<Line> m_history; |     NonnullOwnPtrVector<Line> m_history; | ||||||
|     void add_line_to_history(NonnullOwnPtr<Line>&& line) |     void add_line_to_history(NonnullOwnPtr<Line>&& line) | ||||||
|  | @ -248,34 +261,11 @@ private: | ||||||
|     bool m_stomp { false }; |     bool m_stomp { false }; | ||||||
| 
 | 
 | ||||||
|     Attribute m_current_attribute; |     Attribute m_current_attribute; | ||||||
|  |     Attribute m_saved_attribute; | ||||||
| 
 | 
 | ||||||
|     u32 m_next_href_id { 0 }; |     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; |     Vector<bool> m_horizontal_tabs; | ||||||
|     u8 m_final { 0 }; |  | ||||||
|     u32 m_last_code_point { 0 }; |     u32 m_last_code_point { 0 }; | ||||||
|     size_t m_max_history_lines { 1024 }; |     size_t m_max_history_lines { 1024 }; | ||||||
| }; | }; | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Daniel Bertalan
						Daniel Bertalan