瀏覽代碼

LibLine: Add support for ^X^E

This keybind opens the current buffer in an editor (determined by
EDITOR from the env, or the default_text_editor key in the config file,
and set to /bin/TextEditor by default), and later reads the file back
into the buffer.
Pretty handy :^)
AnotherTest 4 年之前
父節點
當前提交
b58dbc29fc

+ 9 - 0
Userland/Libraries/LibLine/Editor.cpp

@@ -62,6 +62,7 @@ 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 default_text_editor = config_file->read_entry("behaviour", "default_text_editor");
 
 
     if (refresh.equals_ignoring_case("lazy"))
     if (refresh.equals_ignoring_case("lazy"))
         configuration.set(Configuration::Lazy);
         configuration.set(Configuration::Lazy);
@@ -77,6 +78,11 @@ Configuration Configuration::from_config(const StringView& libname)
     else
     else
         configuration.set(Configuration::OperationMode::Unset);
         configuration.set(Configuration::OperationMode::Unset);
 
 
+    if (!default_text_editor.is_empty())
+        configuration.set(DefaultTextEditor { move(default_text_editor) });
+    else
+        configuration.set(DefaultTextEditor { "/bin/TextEditor" });
+
     // Read keybinds.
     // Read keybinds.
 
 
     for (auto& binding_key : config_file->keys("keybinds")) {
     for (auto& binding_key : config_file->keys("keybinds")) {
@@ -168,6 +174,9 @@ void Editor::set_default_keybinds()
     register_key_input_callback(ctrl('T'), EDITOR_INTERNAL_FUNCTION(transpose_characters));
     register_key_input_callback(ctrl('T'), EDITOR_INTERNAL_FUNCTION(transpose_characters));
     register_key_input_callback('\n', EDITOR_INTERNAL_FUNCTION(finish));
     register_key_input_callback('\n', EDITOR_INTERNAL_FUNCTION(finish));
 
 
+    // ^X^E: Edit in external editor
+    register_key_input_callback(Vector<Key> { ctrl('X'), ctrl('E') }, EDITOR_INTERNAL_FUNCTION(edit_in_external_editor));
+
     // ^[.: alt-.: insert last arg of previous command (similar to `!$`)
     // ^[.: alt-.: insert last arg of previous command (similar to `!$`)
     register_key_input_callback(Key { '.', Key::Alt }, EDITOR_INTERNAL_FUNCTION(insert_last_words));
     register_key_input_callback(Key { '.', Key::Alt }, EDITOR_INTERNAL_FUNCTION(insert_last_words));
     register_key_input_callback(Key { 'b', Key::Alt }, EDITOR_INTERNAL_FUNCTION(cursor_left_word));
     register_key_input_callback(Key { 'b', Key::Alt }, EDITOR_INTERNAL_FUNCTION(cursor_left_word));

+ 8 - 1
Userland/Libraries/LibLine/Editor.h

@@ -81,6 +81,10 @@ struct Configuration {
         NoSignalHandlers,
         NoSignalHandlers,
     };
     };
 
 
+    struct DefaultTextEditor {
+        String command;
+    };
+
     Configuration()
     Configuration()
     {
     {
     }
     }
@@ -96,6 +100,7 @@ struct Configuration {
     void set(OperationMode mode) { operation_mode = mode; }
     void set(OperationMode mode) { operation_mode = mode; }
     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); }
 
 
     static Configuration from_config(const StringView& libname = "line");
     static Configuration from_config(const StringView& libname = "line");
 
 
@@ -103,6 +108,7 @@ struct Configuration {
     SignalHandler m_signal_mode { SignalHandler::WithSignalHandlers };
     SignalHandler m_signal_mode { SignalHandler::WithSignalHandlers };
     OperationMode operation_mode { OperationMode::Unset };
     OperationMode operation_mode { OperationMode::Unset };
     Vector<KeyBinding> keybindings;
     Vector<KeyBinding> keybindings;
+    String m_default_text_editor {};
 };
 };
 
 
 #define ENUMERATE_EDITOR_INTERNAL_FUNCTIONS(M) \
 #define ENUMERATE_EDITOR_INTERNAL_FUNCTIONS(M) \
@@ -130,7 +136,8 @@ struct Configuration {
     M(erase_alnum_word_forwards)               \
     M(erase_alnum_word_forwards)               \
     M(capitalize_word)                         \
     M(capitalize_word)                         \
     M(lowercase_word)                          \
     M(lowercase_word)                          \
-    M(uppercase_word)
+    M(uppercase_word)                          \
+    M(edit_in_external_editor)
 
 
 #define EDITOR_INTERNAL_FUNCTION(name) \
 #define EDITOR_INTERNAL_FUNCTION(name) \
     [](auto& editor) { editor.name();  return false; }
     [](auto& editor) { editor.name();  return false; }

+ 91 - 0
Userland/Libraries/LibLine/InternalFunctions.cpp

@@ -24,12 +24,16 @@
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
  */
 
 
+#include <AK/ScopeGuard.h>
 #include <AK/ScopedValueRollback.h>
 #include <AK/ScopedValueRollback.h>
 #include <AK/StringBuilder.h>
 #include <AK/StringBuilder.h>
 #include <AK/TemporaryChange.h>
 #include <AK/TemporaryChange.h>
+#include <LibCore/File.h>
 #include <LibLine/Editor.h>
 #include <LibLine/Editor.h>
 #include <ctype.h>
 #include <ctype.h>
 #include <stdio.h>
 #include <stdio.h>
+#include <sys/wait.h>
+#include <unistd.h>
 
 
 namespace {
 namespace {
 constexpr u32 ctrl(char c) { return c & 0x3f; }
 constexpr u32 ctrl(char c) { return c & 0x3f; }
@@ -522,4 +526,91 @@ void Editor::uppercase_word()
     case_change_word(CaseChangeOp::Uppercase);
     case_change_word(CaseChangeOp::Uppercase);
 }
 }
 
 
+void Editor::edit_in_external_editor()
+{
+    const auto* editor_command = getenv("EDITOR");
+    if (!editor_command)
+        editor_command = m_configuration.m_default_text_editor.characters();
+
+    char file_path[] = "/tmp/line-XXXXXX";
+    auto fd = mkstemp(file_path);
+
+    if (fd < 0) {
+        perror("mktemp");
+        return;
+    }
+
+    {
+        auto* fp = fdopen(fd, "rw");
+        if (!fp) {
+            perror("fdopen");
+            return;
+        }
+
+        StringBuilder builder;
+        builder.append(Utf32View { m_buffer.data(), m_buffer.size() });
+        auto view = builder.string_view();
+        size_t remaining_size = view.length();
+
+        while (remaining_size > 0)
+            remaining_size = fwrite(view.characters_without_null_termination() - remaining_size, sizeof(char), remaining_size, fp);
+
+        fclose(fp);
+    }
+
+    ScopeGuard remove_temp_file_guard {
+        [fd, file_path] {
+            close(fd);
+            unlink(file_path);
+        }
+    };
+
+    Vector<const char*> args { editor_command, file_path, nullptr };
+    auto pid = vfork();
+
+    if (pid == -1) {
+        perror("vfork");
+        return;
+    }
+
+    if (pid == 0) {
+        execvp(editor_command, const_cast<char* const*>(args.data()));
+        perror("execv");
+        _exit(126);
+    } else {
+        int wstatus = 0;
+        do {
+            waitpid(pid, &wstatus, 0);
+        } while (errno == EINTR);
+
+        if (!(WIFEXITED(wstatus) && WEXITSTATUS(wstatus) == 0))
+            return;
+    }
+
+    {
+        auto file_or_error = Core::File::open(file_path, Core::IODevice::OpenMode::ReadOnly);
+        if (file_or_error.is_error())
+            return;
+
+        auto file = file_or_error.release_value();
+        auto contents = file->read_all();
+        StringView data { contents };
+        while (data.ends_with('\n'))
+            data = data.substring_view(0, data.length() - 1);
+
+        m_cursor = 0;
+        m_chars_touched_in_the_middle = m_buffer.size();
+        m_buffer.clear_with_capacity();
+        m_refresh_needed = true;
+
+        Utf8View view { data };
+        if (view.validate()) {
+            for (auto cp : view)
+                insert(cp);
+        } else {
+            for (auto ch : data)
+                insert(ch);
+        }
+    }
+}
 }
 }