mirror of
https://github.com/SerenityOS/serenity.git
synced 2025-01-23 18:02:05 -05:00
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:
parent
258a49346d
commit
b58dbc29fc
3 changed files with 108 additions and 1 deletions
|
@ -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));
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue