diff --git a/Userland/Libraries/LibLine/Editor.cpp b/Userland/Libraries/LibLine/Editor.cpp index 208fad58ac..dea04f1616 100644 --- a/Userland/Libraries/LibLine/Editor.cpp +++ b/Userland/Libraries/LibLine/Editor.cpp @@ -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 | 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 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 diff --git a/Userland/Libraries/LibLine/Editor.h b/Userland/Libraries/LibLine/Editor.h index 2058e2914a..3a3ced8960 100644 --- a/Userland/Libraries/LibLine/Editor.h +++ b/Userland/Libraries/LibLine/Editor.h @@ -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 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> m_spans_starting;