From b58dbc29fc8b1e5711a8548f8dc2fc66c77b5213 Mon Sep 17 00:00:00 2001 From: AnotherTest Date: Sat, 17 Apr 2021 21:41:38 +0430 Subject: 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 :^) --- Userland/Libraries/LibLine/Editor.cpp | 9 +++ Userland/Libraries/LibLine/Editor.h | 9 ++- Userland/Libraries/LibLine/InternalFunctions.cpp | 91 ++++++++++++++++++++++++ 3 files changed, 108 insertions(+), 1 deletion(-) diff --git a/Userland/Libraries/LibLine/Editor.cpp b/Userland/Libraries/LibLine/Editor.cpp index d029d837f1..e86d08bff0 100644 --- a/Userland/Libraries/LibLine/Editor.cpp +++ b/Userland/Libraries/LibLine/Editor.cpp @@ -62,6 +62,7 @@ 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 default_text_editor = config_file->read_entry("behaviour", "default_text_editor"); if (refresh.equals_ignoring_case("lazy")) configuration.set(Configuration::Lazy); @@ -77,6 +78,11 @@ Configuration Configuration::from_config(const StringView& libname) else 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. 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('\n', EDITOR_INTERNAL_FUNCTION(finish)); + // ^X^E: Edit in external editor + register_key_input_callback(Vector { ctrl('X'), ctrl('E') }, EDITOR_INTERNAL_FUNCTION(edit_in_external_editor)); + // ^[.: 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 { 'b', Key::Alt }, EDITOR_INTERNAL_FUNCTION(cursor_left_word)); diff --git a/Userland/Libraries/LibLine/Editor.h b/Userland/Libraries/LibLine/Editor.h index 083d3ceb91..23aea8d3c7 100644 --- a/Userland/Libraries/LibLine/Editor.h +++ b/Userland/Libraries/LibLine/Editor.h @@ -81,6 +81,10 @@ struct Configuration { NoSignalHandlers, }; + struct DefaultTextEditor { + String command; + }; + Configuration() { } @@ -96,6 +100,7 @@ struct Configuration { void set(OperationMode mode) { operation_mode = mode; } 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); } static Configuration from_config(const StringView& libname = "line"); @@ -103,6 +108,7 @@ struct Configuration { SignalHandler m_signal_mode { SignalHandler::WithSignalHandlers }; OperationMode operation_mode { OperationMode::Unset }; Vector keybindings; + String m_default_text_editor {}; }; #define ENUMERATE_EDITOR_INTERNAL_FUNCTIONS(M) \ @@ -130,7 +136,8 @@ struct Configuration { M(erase_alnum_word_forwards) \ M(capitalize_word) \ M(lowercase_word) \ - M(uppercase_word) + M(uppercase_word) \ + M(edit_in_external_editor) #define EDITOR_INTERNAL_FUNCTION(name) \ [](auto& editor) { editor.name(); return false; } diff --git a/Userland/Libraries/LibLine/InternalFunctions.cpp b/Userland/Libraries/LibLine/InternalFunctions.cpp index 015754c0b0..73c799603d 100644 --- a/Userland/Libraries/LibLine/InternalFunctions.cpp +++ b/Userland/Libraries/LibLine/InternalFunctions.cpp @@ -24,12 +24,16 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#include #include #include #include +#include #include #include #include +#include +#include namespace { constexpr u32 ctrl(char c) { return c & 0x3f; } @@ -522,4 +526,91 @@ void Editor::uppercase_word() 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 args { editor_command, file_path, nullptr }; + auto pid = vfork(); + + if (pid == -1) { + perror("vfork"); + return; + } + + if (pid == 0) { + execvp(editor_command, const_cast(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); + } + } +} } -- cgit v1.2.3