summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnotherTest <ali.mpfard@gmail.com>2021-04-17 21:41:38 +0430
committerLinus Groh <mail@linusgroh.de>2021-04-17 22:10:35 +0200
commitb58dbc29fc8b1e5711a8548f8dc2fc66c77b5213 (patch)
treec4adeadfdaa2fb1eaaee90bc6058b4540b080f86
parent258a49346d164abac357ac356f3c85aace34d2df (diff)
downloadserenity-b58dbc29fc8b1e5711a8548f8dc2fc66c77b5213.zip
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 :^)
-rw-r--r--Userland/Libraries/LibLine/Editor.cpp9
-rw-r--r--Userland/Libraries/LibLine/Editor.h9
-rw-r--r--Userland/Libraries/LibLine/InternalFunctions.cpp91
3 files changed, 108 insertions, 1 deletions
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<Key> { 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<KeyBinding> 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 <AK/ScopeGuard.h>
#include <AK/ScopedValueRollback.h>
#include <AK/StringBuilder.h>
#include <AK/TemporaryChange.h>
+#include <LibCore/File.h>
#include <LibLine/Editor.h>
#include <ctype.h>
#include <stdio.h>
+#include <sys/wait.h>
+#include <unistd.h>
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<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);
+ }
+ }
+}
}