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:
Ali Mohammad Pur 2021-05-24 16:30:44 +04:30 committed by Ali Mohammad Pur
parent e54d96d53e
commit e318f12263
2 changed files with 64 additions and 7 deletions

View file

@ -42,8 +42,15 @@ Configuration Configuration::from_config(const StringView& libname)
// Read behaviour options.
auto refresh = config_file->read_entry("behaviour", "refresh", "lazy");
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");
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"))
configuration.set(Configuration::Lazy);
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;
get_terminal_size();
if (m_configuration.enable_bracketed_paste)
fprintf(stderr, "\x1b[?2004h");
if (m_num_columns != old_cols || m_num_lines != old_lines)
m_refresh_needed = true;
@ -844,13 +854,8 @@ void Editor::handle_read_event()
m_state = InputState::CSIExpectFinal;
[[fallthrough]];
case InputState::CSIExpectFinal: {
m_state = InputState::Free;
if (!(code_point >= 0x40 && code_point <= 0x7f)) {
dbgln("LibLine: Invalid CSI: {:02x} ({:c})", code_point, code_point);
continue;
}
csi_final = code_point;
m_state = m_previous_free_state;
auto is_in_paste = m_state == InputState::Paste;
for (auto& parameter : String::copy(csi_parameter_bytes).split(';')) {
if (auto value = parameter.to_uint(); value.has_value())
csi_parameters.append(value.value());
@ -864,6 +869,25 @@ void Editor::handle_read_event()
param2 = csi_parameters[1];
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') {
// 'reverse tab'
reverse_tab = true;
@ -905,6 +929,18 @@ void Editor::handle_read_event()
m_search_offset = 0;
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
// ^[[6~: page down
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.
insert(code_point);
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:
m_previous_free_state = InputState::Free;
if (code_point == 27) {
m_callback_machine.key_pressed(*this, code_point);
// Note that this should also deal with explicitly registered keys

View file

@ -61,6 +61,11 @@ struct Configuration {
NoSignalHandlers,
};
enum Flags : u32 {
None = 0,
BracketedPaste = 1,
};
struct DefaultTextEditor {
String command;
};
@ -81,6 +86,10 @@ struct Configuration {
void set(SignalHandler mode) { m_signal_mode = mode; }
void set(const KeyBinding& binding) { keybindings.append(binding); }
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");
@ -89,6 +98,7 @@ struct Configuration {
OperationMode operation_mode { OperationMode::Unset };
Vector<KeyBinding> keybindings;
String m_default_text_editor {};
bool enable_bracketed_paste { false };
};
#define ENUMERATE_EDITOR_INTERNAL_FUNCTIONS(M) \
@ -450,12 +460,14 @@ private:
enum class InputState {
Free,
Verbatim,
Paste,
GotEscape,
CSIExpectParameter,
CSIExpectIntermediate,
CSIExpectFinal,
};
InputState m_state { InputState::Free };
InputState m_previous_free_state { InputState::Free };
struct Spans {
HashMap<u32, HashMap<u32, Style>> m_spans_starting;