Преглед изворни кода

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.
Ali Mohammad Pur пре 4 година
родитељ
комит
e318f12263
2 измењених фајлова са 64 додато и 7 уклоњено
  1. 52 7
      Userland/Libraries/LibLine/Editor.cpp
  2. 12 0
      Userland/Libraries/LibLine/Editor.h

+ 52 - 7
Userland/Libraries/LibLine/Editor.cpp

@@ -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;
-            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(';')) {
             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

+ 12 - 0
Userland/Libraries/LibLine/Editor.h

@@ -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;