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 :^)
This commit is contained in:
AnotherTest 2021-04-17 21:41:38 +04:30 committed by Linus Groh
parent 258a49346d
commit b58dbc29fc
Notes: sideshowbarker 2024-07-18 19:29:40 +09:00
3 changed files with 108 additions and 1 deletions

View File

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

View File

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

View File

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