mirror of
https://github.com/RGBCube/serenity
synced 2025-07-24 18:17:34 +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
|
@ -396,6 +396,10 @@
|
||||||
|
|
||||||
#ifndef WASM_BINPARSER_DEBUG
|
#ifndef WASM_BINPARSER_DEBUG
|
||||||
#cmakedefine01 WASM_BINPARSER_DEBUG
|
#cmakedefine01 WASM_BINPARSER_DEBUG
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef ESCAPE_SEQUENCE_DEBUG
|
||||||
|
#cmakedefine01 ESCAPE_SEQUENCE_DEBUG
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef WINDOWMANAGER_DEBUG
|
#ifndef WINDOWMANAGER_DEBUG
|
||||||
|
|
|
@ -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) {
|
||||||
|
@ -60,9 +47,9 @@ void Terminal::alter_mode(bool should_set, bool question_param, const ParamVecto
|
||||||
}
|
}
|
||||||
if (!question_param) {
|
if (!question_param) {
|
||||||
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;
|
|
||||||
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;
|
|
||||||
|
|
||||||
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;
|
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) {
|
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,37 +686,211 @@ 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::on_code_point(u32 code_point)
|
void Terminal::execute_escape_sequence(Intermediates intermediates, bool ignore, u8 last_byte)
|
||||||
{
|
{
|
||||||
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);
|
|
||||||
return;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (m_stomp) {
|
unimplemented_escape_sequence(intermediates, last_byte);
|
||||||
m_stomp = false;
|
}
|
||||||
newline();
|
|
||||||
put_character_at(m_cursor_row, m_cursor_column, code_point);
|
void Terminal::execute_csi_sequence(Parameters parameters, Intermediates intermediates, bool ignore, u8 last_byte)
|
||||||
set_cursor(m_cursor_row, 1);
|
{
|
||||||
|
// 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 {
|
} else {
|
||||||
// Curious: We wait once on the right-hand side
|
unimplemented_osc_sequence(parameters, last_byte);
|
||||||
m_stomp = true;
|
|
||||||
put_character_at(m_cursor_row, m_cursor_column, code_point);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
{
|
{
|
||||||
for (size_t i = 0; i < str.length(); ++i)
|
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());
|
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:");
|
builder.append(", intermediates: ");
|
||||||
for (size_t i = 0; i < m_parameters.size(); ++i)
|
for (size_t i = 0; i < intermediates.size(); ++i)
|
||||||
builder.append((char)m_parameters[i]);
|
builder.append((char)intermediates[i]);
|
||||||
}
|
|
||||||
if (!m_intermediates.is_empty()) {
|
|
||||||
builder.append(", intermediates:");
|
|
||||||
for (size_t i = 0; i < m_intermediates.size(); ++i)
|
|
||||||
builder.append((char)m_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