mirror of
https://github.com/RGBCube/serenity
synced 2025-07-25 12:07:45 +00:00
LibLine: Add bracketed paste mode support
This mode makes the editor insert all the "pasted" text into the buffer without interpreting it in any way.
This commit is contained in:
parent
e54d96d53e
commit
e318f12263
2 changed files with 64 additions and 7 deletions
|
@ -42,8 +42,15 @@ Configuration Configuration::from_config(const StringView& libname)
|
||||||
// Read behaviour options.
|
// Read behaviour options.
|
||||||
auto refresh = config_file->read_entry("behaviour", "refresh", "lazy");
|
auto refresh = config_file->read_entry("behaviour", "refresh", "lazy");
|
||||||
auto operation = config_file->read_entry("behaviour", "operation_mode");
|
auto operation = config_file->read_entry("behaviour", "operation_mode");
|
||||||
|
auto bracketed_paste = config_file->read_bool_entry("behaviour", "bracketed_paste", true);
|
||||||
auto default_text_editor = config_file->read_entry("behaviour", "default_text_editor");
|
auto default_text_editor = config_file->read_entry("behaviour", "default_text_editor");
|
||||||
|
|
||||||
|
Configuration::Flags flags { Configuration::Flags::None };
|
||||||
|
if (bracketed_paste)
|
||||||
|
flags = static_cast<Flags>(flags | Configuration::Flags::BracketedPaste);
|
||||||
|
|
||||||
|
configuration.set(flags);
|
||||||
|
|
||||||
if (refresh.equals_ignoring_case("lazy"))
|
if (refresh.equals_ignoring_case("lazy"))
|
||||||
configuration.set(Configuration::Lazy);
|
configuration.set(Configuration::Lazy);
|
||||||
else if (refresh.equals_ignoring_case("eager"))
|
else if (refresh.equals_ignoring_case("eager"))
|
||||||
|
@ -650,6 +657,9 @@ auto Editor::get_line(const String& prompt) -> Result<String, Editor::Error>
|
||||||
auto old_lines = m_num_lines;
|
auto old_lines = m_num_lines;
|
||||||
get_terminal_size();
|
get_terminal_size();
|
||||||
|
|
||||||
|
if (m_configuration.enable_bracketed_paste)
|
||||||
|
fprintf(stderr, "\x1b[?2004h");
|
||||||
|
|
||||||
if (m_num_columns != old_cols || m_num_lines != old_lines)
|
if (m_num_columns != old_cols || m_num_lines != old_lines)
|
||||||
m_refresh_needed = true;
|
m_refresh_needed = true;
|
||||||
|
|
||||||
|
@ -844,13 +854,8 @@ void Editor::handle_read_event()
|
||||||
m_state = InputState::CSIExpectFinal;
|
m_state = InputState::CSIExpectFinal;
|
||||||
[[fallthrough]];
|
[[fallthrough]];
|
||||||
case InputState::CSIExpectFinal: {
|
case InputState::CSIExpectFinal: {
|
||||||
m_state = InputState::Free;
|
m_state = m_previous_free_state;
|
||||||
if (!(code_point >= 0x40 && code_point <= 0x7f)) {
|
auto is_in_paste = m_state == InputState::Paste;
|
||||||
dbgln("LibLine: Invalid CSI: {:02x} ({:c})", code_point, code_point);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
csi_final = code_point;
|
|
||||||
|
|
||||||
for (auto& parameter : String::copy(csi_parameter_bytes).split(';')) {
|
for (auto& parameter : String::copy(csi_parameter_bytes).split(';')) {
|
||||||
if (auto value = parameter.to_uint(); value.has_value())
|
if (auto value = parameter.to_uint(); value.has_value())
|
||||||
csi_parameters.append(value.value());
|
csi_parameters.append(value.value());
|
||||||
|
@ -864,6 +869,25 @@ void Editor::handle_read_event()
|
||||||
param2 = csi_parameters[1];
|
param2 = csi_parameters[1];
|
||||||
unsigned modifiers = param2 ? param2 - 1 : 0;
|
unsigned modifiers = param2 ? param2 - 1 : 0;
|
||||||
|
|
||||||
|
if (is_in_paste && code_point != '~' && param1 != 201) {
|
||||||
|
// The only valid escape to process in paste mode is the stop-paste sequence.
|
||||||
|
// so treat everything else as part of the pasted data.
|
||||||
|
insert('\x1b');
|
||||||
|
insert('[');
|
||||||
|
insert(StringView { csi_parameter_bytes.data(), csi_parameter_bytes.size() });
|
||||||
|
insert(StringView { csi_intermediate_bytes.data(), csi_intermediate_bytes.size() });
|
||||||
|
insert(code_point);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!(code_point >= 0x40 && code_point <= 0x7f)) {
|
||||||
|
dbgln("LibLine: Invalid CSI: {:02x} ({:c})", code_point, code_point);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
csi_final = code_point;
|
||||||
|
csi_parameters.clear();
|
||||||
|
csi_parameter_bytes.clear();
|
||||||
|
csi_intermediate_bytes.clear();
|
||||||
|
|
||||||
if (csi_final == 'Z') {
|
if (csi_final == 'Z') {
|
||||||
// 'reverse tab'
|
// 'reverse tab'
|
||||||
reverse_tab = true;
|
reverse_tab = true;
|
||||||
|
@ -905,6 +929,18 @@ void Editor::handle_read_event()
|
||||||
m_search_offset = 0;
|
m_search_offset = 0;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (m_configuration.enable_bracketed_paste) {
|
||||||
|
// ^[[200~: start bracketed paste
|
||||||
|
// ^[[201~: end bracketed paste
|
||||||
|
if (!is_in_paste && param1 == 200) {
|
||||||
|
m_state = InputState::Paste;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (is_in_paste && param1 == 201) {
|
||||||
|
m_state = InputState::Free;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
// ^[[5~: page up
|
// ^[[5~: page up
|
||||||
// ^[[6~: page down
|
// ^[[6~: page down
|
||||||
dbgln("LibLine: Unhandled '~': {}", param1);
|
dbgln("LibLine: Unhandled '~': {}", param1);
|
||||||
|
@ -920,7 +956,16 @@ void Editor::handle_read_event()
|
||||||
// Verbatim mode will bypass all mechanisms and just insert the code point.
|
// Verbatim mode will bypass all mechanisms and just insert the code point.
|
||||||
insert(code_point);
|
insert(code_point);
|
||||||
continue;
|
continue;
|
||||||
|
case InputState::Paste:
|
||||||
|
if (code_point == 27) {
|
||||||
|
m_previous_free_state = InputState::Paste;
|
||||||
|
m_state = InputState::GotEscape;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
insert(code_point);
|
||||||
|
continue;
|
||||||
case InputState::Free:
|
case InputState::Free:
|
||||||
|
m_previous_free_state = InputState::Free;
|
||||||
if (code_point == 27) {
|
if (code_point == 27) {
|
||||||
m_callback_machine.key_pressed(*this, code_point);
|
m_callback_machine.key_pressed(*this, code_point);
|
||||||
// Note that this should also deal with explicitly registered keys
|
// Note that this should also deal with explicitly registered keys
|
||||||
|
|
|
@ -61,6 +61,11 @@ struct Configuration {
|
||||||
NoSignalHandlers,
|
NoSignalHandlers,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum Flags : u32 {
|
||||||
|
None = 0,
|
||||||
|
BracketedPaste = 1,
|
||||||
|
};
|
||||||
|
|
||||||
struct DefaultTextEditor {
|
struct DefaultTextEditor {
|
||||||
String command;
|
String command;
|
||||||
};
|
};
|
||||||
|
@ -81,6 +86,10 @@ struct Configuration {
|
||||||
void set(SignalHandler mode) { m_signal_mode = mode; }
|
void set(SignalHandler mode) { m_signal_mode = mode; }
|
||||||
void set(const KeyBinding& binding) { keybindings.append(binding); }
|
void set(const KeyBinding& binding) { keybindings.append(binding); }
|
||||||
void set(DefaultTextEditor editor) { m_default_text_editor = move(editor.command); }
|
void set(DefaultTextEditor editor) { m_default_text_editor = move(editor.command); }
|
||||||
|
void set(Flags flags)
|
||||||
|
{
|
||||||
|
enable_bracketed_paste = flags & Flags::BracketedPaste;
|
||||||
|
}
|
||||||
|
|
||||||
static Configuration from_config(const StringView& libname = "line");
|
static Configuration from_config(const StringView& libname = "line");
|
||||||
|
|
||||||
|
@ -89,6 +98,7 @@ struct Configuration {
|
||||||
OperationMode operation_mode { OperationMode::Unset };
|
OperationMode operation_mode { OperationMode::Unset };
|
||||||
Vector<KeyBinding> keybindings;
|
Vector<KeyBinding> keybindings;
|
||||||
String m_default_text_editor {};
|
String m_default_text_editor {};
|
||||||
|
bool enable_bracketed_paste { false };
|
||||||
};
|
};
|
||||||
|
|
||||||
#define ENUMERATE_EDITOR_INTERNAL_FUNCTIONS(M) \
|
#define ENUMERATE_EDITOR_INTERNAL_FUNCTIONS(M) \
|
||||||
|
@ -450,12 +460,14 @@ private:
|
||||||
enum class InputState {
|
enum class InputState {
|
||||||
Free,
|
Free,
|
||||||
Verbatim,
|
Verbatim,
|
||||||
|
Paste,
|
||||||
GotEscape,
|
GotEscape,
|
||||||
CSIExpectParameter,
|
CSIExpectParameter,
|
||||||
CSIExpectIntermediate,
|
CSIExpectIntermediate,
|
||||||
CSIExpectFinal,
|
CSIExpectFinal,
|
||||||
};
|
};
|
||||||
InputState m_state { InputState::Free };
|
InputState m_state { InputState::Free };
|
||||||
|
InputState m_previous_free_state { InputState::Free };
|
||||||
|
|
||||||
struct Spans {
|
struct Spans {
|
||||||
HashMap<u32, HashMap<u32, Style>> m_spans_starting;
|
HashMap<u32, HashMap<u32, Style>> m_spans_starting;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue