Merge remote-tracking branch 'krobelus/bracketed-paste'

This commit is contained in:
Maxime Coste 2023-03-13 20:37:45 +11:00
commit f05ab99d4d
12 changed files with 163 additions and 26 deletions

View File

@ -60,6 +60,9 @@ Client::Client(std::unique_ptr<UserInterface>&& ui,
else else
m_pending_keys.push_back(key); m_pending_keys.push_back(key);
}); });
m_ui->set_on_paste([this](StringView content) {
context().input_handler().paste(content);
});
m_window->hooks().run_hook(Hook::WinDisplay, m_window->buffer().name(), context()); m_window->hooks().run_hook(Hook::WinDisplay, m_window->buffer().name(), context());

View File

@ -32,6 +32,7 @@ public:
InputMode& operator=(const InputMode&) = delete; InputMode& operator=(const InputMode&) = delete;
void handle_key(Key key) { RefPtr<InputMode> keep_alive{this}; on_key(key); } void handle_key(Key key) { RefPtr<InputMode> keep_alive{this}; on_key(key); }
virtual void paste(StringView content);
virtual void on_enabled() {} virtual void on_enabled() {}
virtual void on_disabled(bool temporary) {} virtual void on_disabled(bool temporary) {}
@ -71,6 +72,32 @@ private:
InputHandler& m_input_handler; InputHandler& m_input_handler;
}; };
void InputMode::paste(StringView content)
{
try
{
Buffer& buffer = context().buffer();
const bool linewise = not content.empty() and content.back() == '\n';
ScopedEdition edition{context()};
ScopedSelectionEdition selection_edition{context()};
context().selections().for_each([&buffer, content=std::move(content), linewise]
(size_t index, Selection& sel) {
auto& min = sel.min();
auto& max = sel.max();
BufferRange range =
buffer.insert(paste_pos(buffer, min, max, PasteMode::Insert, linewise), content);
min = range.begin;
max = range.end > range.begin ? buffer.char_prev(range.end) : range.begin;
}, false);
}
catch (Kakoune::runtime_error& error)
{
write_to_debug_buffer(format("Error: {}", error.what()));
context().print_status({error.what().str(), context().faces()["Error"] });
context().hooks().run_hook(Hook::RuntimeError, error.what(), context());
}
}
namespace InputModes namespace InputModes
{ {
@ -1015,6 +1042,17 @@ public:
m_idle_timer.set_next_date(Clock::now() + get_idle_timeout(context())); m_idle_timer.set_next_date(Clock::now() + get_idle_timeout(context()));
} }
void paste(StringView content) override
{
m_line_editor.insert(content);
clear_completions();
m_refresh_completion_pending = true;
display();
m_line_changed = true;
if (not (context().flags() & Context::Flags::Draft))
m_idle_timer.set_next_date(Clock::now() + get_idle_timeout(context()));
}
void set_prompt_face(Face face) void set_prompt_face(Face face)
{ {
if (face != m_prompt_face) if (face != m_prompt_face)
@ -1433,6 +1471,12 @@ public:
m_idle_timer.set_next_date(Clock::now() + get_idle_timeout(context())); m_idle_timer.set_next_date(Clock::now() + get_idle_timeout(context()));
} }
void paste(StringView content) override
{
insert(ConstArrayView{content});
m_idle_timer.set_next_date(Clock::now() + get_idle_timeout(context()));
}
DisplayLine mode_line() const override DisplayLine mode_line() const override
{ {
auto num_sel = context().selections().size(); auto num_sel = context().selections().size();
@ -1462,7 +1506,8 @@ private:
selections.sort_and_merge_overlapping(); selections.sort_and_merge_overlapping();
} }
void insert(ConstArrayView<String> strings) template<typename S>
void insert(ConstArrayView<S> strings)
{ {
m_completer.try_accept(); m_completer.try_accept();
context().selections().for_each([strings, &buffer=context().buffer()] context().selections().for_each([strings, &buffer=context().buffer()]
@ -1474,7 +1519,7 @@ private:
void insert(Codepoint key) void insert(Codepoint key)
{ {
String str{key}; String str{key};
insert(str); insert(ConstArrayView{str});
context().hooks().run_hook(Hook::InsertChar, str, context()); context().hooks().run_hook(Hook::InsertChar, str, context());
} }
@ -1644,6 +1689,11 @@ void InputHandler::repeat_last_insert()
kak_assert(dynamic_cast<InputModes::Normal*>(&current_mode()) != nullptr); kak_assert(dynamic_cast<InputModes::Normal*>(&current_mode()) != nullptr);
} }
void InputHandler::paste(StringView content)
{
current_mode().paste(content);
}
void InputHandler::prompt(StringView prompt, String initstr, String emptystr, void InputHandler::prompt(StringView prompt, String initstr, String emptystr,
Face prompt_face, PromptFlags flags, char history_register, Face prompt_face, PromptFlags flags, char history_register,
PromptCompleter completer, PromptCallback callback) PromptCompleter completer, PromptCallback callback)

View File

@ -72,6 +72,8 @@ public:
void insert(InsertMode mode, int count); void insert(InsertMode mode, int count);
// repeat last insert mode key sequence // repeat last insert mode key sequence
void repeat_last_insert(); void repeat_last_insert();
// insert a string without affecting the mode stack
void paste(StringView content);
// enter prompt mode, callback is called on each change, // enter prompt mode, callback is called on each change,
// abort or validation with corresponding PromptEvent value // abort or validation with corresponding PromptEvent value

View File

@ -211,6 +211,11 @@ void JsonUI::set_on_key(OnKeyCallback callback)
m_on_key = std::move(callback); m_on_key = std::move(callback);
} }
void JsonUI::set_on_paste(OnPasteCallback callback)
{
m_on_paste = std::move(callback);
}
void JsonUI::eval_json(const Value& json) void JsonUI::eval_json(const Value& json)
{ {
if (not json.is_a<JsonObject>()) if (not json.is_a<JsonObject>())

View File

@ -46,6 +46,7 @@ public:
DisplayCoord dimensions() override; DisplayCoord dimensions() override;
void set_on_key(OnKeyCallback callback) override; void set_on_key(OnKeyCallback callback) override;
void set_on_paste(OnPasteCallback callback) override;
void set_ui_options(const Options& options) override; void set_ui_options(const Options& options) override;
private: private:
@ -54,6 +55,7 @@ private:
FDWatcher m_stdin_watcher; FDWatcher m_stdin_watcher;
OnKeyCallback m_on_key; OnKeyCallback m_on_key;
OnPasteCallback m_on_paste;
Vector<Key, MemoryDomain::Client> m_pending_keys; Vector<Key, MemoryDomain::Client> m_pending_keys;
DisplayCoord m_dimensions; DisplayCoord m_dimensions;
String m_requests; String m_requests;

View File

@ -626,6 +626,7 @@ std::unique_ptr<UserInterface> make_ui(UIType ui_type)
void set_cursor(CursorMode, DisplayCoord) override {} void set_cursor(CursorMode, DisplayCoord) override {}
void refresh(bool) override {} void refresh(bool) override {}
void set_on_key(OnKeyCallback) override {} void set_on_key(OnKeyCallback) override {}
void set_on_paste(OnPasteCallback) override {}
void set_ui_options(const Options&) override {} void set_ui_options(const Options&) override {}
}; };

View File

@ -657,13 +657,6 @@ void change(Context& context, NormalParams params)
enter_insert_mode<InsertMode::Replace>(context, params); enter_insert_mode<InsertMode::Replace>(context, params);
} }
enum class PasteMode
{
Append,
Insert,
Replace
};
BufferCoord paste_pos(Buffer& buffer, BufferCoord min, BufferCoord max, PasteMode mode, bool linewise) BufferCoord paste_pos(Buffer& buffer, BufferCoord min, BufferCoord max, PasteMode mode, bool linewise)
{ {
switch (mode) switch (mode)

View File

@ -10,6 +10,7 @@
namespace Kakoune namespace Kakoune
{ {
class Buffer;
class Context; class Context;
struct no_selections_remaining : runtime_error struct no_selections_remaining : runtime_error
@ -40,6 +41,15 @@ struct KeyInfo
String build_autoinfo_for_mapping(const Context& context, KeymapMode mode, String build_autoinfo_for_mapping(const Context& context, KeymapMode mode,
ConstArrayView<KeyInfo> built_ins); ConstArrayView<KeyInfo> built_ins);
enum class PasteMode
{
Append,
Insert,
Replace
};
BufferCoord paste_pos(Buffer& buffer, BufferCoord min, BufferCoord max, PasteMode mode, bool linewise);
} }
#endif // normal_hh_INCLUDED #endif // normal_hh_INCLUDED

View File

@ -43,6 +43,7 @@ enum class MessageType : uint8_t
SetOptions, SetOptions,
Exit, Exit,
Key, Key,
Paste,
}; };
class MsgWriter class MsgWriter
@ -413,6 +414,9 @@ public:
void set_on_key(OnKeyCallback callback) override void set_on_key(OnKeyCallback callback) override
{ m_on_key = std::move(callback); } { m_on_key = std::move(callback); }
void set_on_paste(OnPasteCallback callback) override
{ m_on_paste = std::move(callback); }
void set_ui_options(const Options& options) override; void set_ui_options(const Options& options) override;
void exit(int status); void exit(int status);
@ -430,6 +434,7 @@ private:
MsgReader m_reader; MsgReader m_reader;
DisplayCoord m_dimensions; DisplayCoord m_dimensions;
OnKeyCallback m_on_key; OnKeyCallback m_on_key;
OnPasteCallback m_on_paste;
RemoteBuffer m_send_buffer; RemoteBuffer m_send_buffer;
}; };
@ -479,17 +484,25 @@ RemoteUI::RemoteUI(int socket, DisplayCoord dimensions)
if (not m_reader.ready()) if (not m_reader.ready())
continue; continue;
if (m_reader.type() != MessageType::Key) if (m_reader.type() == MessageType::Key)
{
auto key = m_reader.read<Key>();
m_reader.reset();
if (key.modifiers == Key::Modifiers::Resize)
m_dimensions = key.coord();
m_on_key(key);
}
else if (m_reader.type() == MessageType::Paste)
{
auto content = m_reader.read<String>();
m_reader.reset();
m_on_paste(content);
}
else
{ {
m_socket_watcher.close_fd(); m_socket_watcher.close_fd();
return; return;
} }
auto key = m_reader.read<Key>();
m_reader.reset();
if (key.modifiers == Key::Modifiers::Resize)
m_dimensions = key.coord();
m_on_key(key);
} }
} }
catch (const disconnected& err) catch (const disconnected& err)
@ -660,6 +673,11 @@ RemoteClient::RemoteClient(StringView session, StringView name, std::unique_ptr<
msg.write(key); msg.write(key);
m_socket_watcher->events() |= FdEvents::Write; m_socket_watcher->events() |= FdEvents::Write;
}); });
m_ui->set_on_paste([this](StringView content){
MsgWriter msg(m_send_buffer, MessageType::Paste);
msg.write(content);
m_socket_watcher->events() |= FdEvents::Write;
});
m_socket_watcher.reset(new FDWatcher{sock, FdEvents::Read | FdEvents::Write, EventMode::Urgent, m_socket_watcher.reset(new FDWatcher{sock, FdEvents::Read | FdEvents::Write, EventMode::Urgent,
[this, reader = MsgReader{}](FDWatcher& watcher, FdEvents events, EventMode) mutable { [this, reader = MsgReader{}](FDWatcher& watcher, FdEvents events, EventMode) mutable {

View File

@ -1,5 +1,6 @@
#include "terminal_ui.hh" #include "terminal_ui.hh"
#include "buffer_utils.hh"
#include "display_buffer.hh" #include "display_buffer.hh"
#include "event_manager.hh" #include "event_manager.hh"
#include "exception.hh" #include "exception.hh"
@ -683,12 +684,16 @@ Optional<Key> TerminalUI::get_next_key()
return resize(dimensions()); return resize(dimensions());
} }
static auto get_char = []() -> Optional<unsigned char> { static auto get_char = [this]() -> Optional<unsigned char> {
if (not fd_readable(STDIN_FILENO)) if (not fd_readable(STDIN_FILENO))
return {}; return {};
if (unsigned char c = 0; read(STDIN_FILENO, &c, 1) == 1) if (unsigned char c = 0; read(STDIN_FILENO, &c, 1) == 1)
{
if (m_paste_buffer)
m_paste_buffer->push_back(c);
return c; return c;
}
stdin_closed = 1; stdin_closed = 1;
return {}; return {};
@ -700,7 +705,7 @@ Optional<Key> TerminalUI::get_next_key()
static constexpr auto control = [](char c) { return c & 037; }; static constexpr auto control = [](char c) { return c & 037; };
auto convert = [this](Codepoint c) -> Codepoint { static auto convert = [this](Codepoint c) -> Codepoint {
if (c == control('m') or c == control('j')) if (c == control('m') or c == control('j'))
return Key::Return; return Key::Return;
if (c == control('i')) if (c == control('i'))
@ -715,7 +720,7 @@ Optional<Key> TerminalUI::get_next_key()
return Key::Escape; return Key::Escape;
return c; return c;
}; };
auto parse_key = [&convert](unsigned char c) -> Key { static auto parse_key = [](unsigned char c) -> Key {
if (Codepoint cp = convert(c); cp > 255) if (Codepoint cp = convert(c); cp > 255)
return Key{cp}; return Key{cp};
// Special case: you can type NUL with Ctrl-2 or Ctrl-Shift-2 or // Special case: you can type NUL with Ctrl-2 or Ctrl-Shift-2 or
@ -743,7 +748,7 @@ Optional<Key> TerminalUI::get_next_key()
return Key{utf8::codepoint(CharIterator{c}, Sentinel{})}; return Key{utf8::codepoint(CharIterator{c}, Sentinel{})};
}; };
auto parse_mask = [](int mask) { static auto parse_mask = [](int mask) {
Key::Modifiers mod = Key::Modifiers::None; Key::Modifiers mod = Key::Modifiers::None;
if (mask & 1) if (mask & 1)
mod |= Key::Modifiers::Shift; mod |= Key::Modifiers::Shift;
@ -754,7 +759,16 @@ Optional<Key> TerminalUI::get_next_key()
return mod; return mod;
}; };
auto parse_csi = [this, &convert, &parse_mask]() -> Optional<Key> { enum class PasteEvent { Begin, End };
struct KeyOrPasteEvent {
KeyOrPasteEvent() = default;
KeyOrPasteEvent(Key key) : key(key) {}
KeyOrPasteEvent(Optional<Key> key) : key(key) {}
KeyOrPasteEvent(PasteEvent paste) : paste(paste) {}
const Optional<Key> key;
const Optional<PasteEvent> paste;
};
auto parse_csi = [this]() -> KeyOrPasteEvent {
auto next_char = [] { return get_char().value_or((unsigned char)0xff); }; auto next_char = [] { return get_char().value_or((unsigned char)0xff); };
int params[16][4] = {}; int params[16][4] = {};
auto c = next_char(); auto c = next_char();
@ -819,7 +833,7 @@ Optional<Key> TerminalUI::get_next_key()
{ {
if (params[0][0] == 2026) if (params[0][0] == 2026)
m_synchronized.supported = (params[1][0] == 1 or params[1][0] == 2); m_synchronized.supported = (params[1][0] == 1 or params[1][0] == 2);
return {Key::Invalid}; return Key{Key::Invalid};
} }
switch (params[0][0]) switch (params[0][0])
{ {
@ -863,6 +877,10 @@ Optional<Key> TerminalUI::get_next_key()
return Key{Key::Modifiers::Shift, Key::F7 + params[0][0] - 31}; // rxvt style return Key{Key::Modifiers::Shift, Key::F7 + params[0][0] - 31}; // rxvt style
case 33: case 34: case 33: case 34:
return Key{Key::Modifiers::Shift, Key::F9 + params[0][0] - 33}; // rxvt style return Key{Key::Modifiers::Shift, Key::F9 + params[0][0] - 33}; // rxvt style
case 200:
return PasteEvent::Begin;
case 201:
return PasteEvent::End;
} }
return {}; return {};
case 'u': case 'u':
@ -899,7 +917,7 @@ Optional<Key> TerminalUI::get_next_key()
return {}; return {};
}; };
auto parse_ss3 = [&parse_mask]() -> Optional<Key> { static auto parse_ss3 = []() -> Optional<Key> {
int raw_mask = 0; int raw_mask = 0;
char code = '0'; char code = '0';
do { do {
@ -944,17 +962,40 @@ Optional<Key> TerminalUI::get_next_key()
} }
}; };
if (m_paste_buffer)
{
if (*c == 27 and get_char() == '[' and parse_csi().paste == PasteEvent::End)
{
m_paste_buffer->resize(m_paste_buffer->length() - "\033[201~"_str.length(), '\0');
m_on_paste(*m_paste_buffer);
m_paste_buffer.reset();
}
return get_next_key();
}
if (*c != 27) if (*c != 27)
return parse_key(*c); return parse_key(*c);
if (auto next = get_char()) if (auto next = get_char())
{ {
if (*next == '[') // potential CSI
return parse_csi().value_or(alt('['));
if (*next == 'O') // potential SS3 if (*next == 'O') // potential SS3
return parse_ss3().value_or(alt('O')); return parse_ss3().value_or(alt('O'));
return alt(parse_key(*next)); if (*next != '[')
return alt(parse_key(*next));
// potential CSI
KeyOrPasteEvent csi = parse_csi();
if (csi.paste == PasteEvent::Begin)
{
m_paste_buffer = String{};
return get_next_key();
}
if (csi.paste == PasteEvent::End) // Unmatched bracketed paste sequence.
return {};
if (csi.key)
return *csi.key;
return alt('[');
} }
return Key{Key::Escape}; return Key{Key::Escape};
} }
@ -1395,6 +1436,11 @@ void TerminalUI::set_on_key(OnKeyCallback callback)
EventManager::instance().force_signal(0); EventManager::instance().force_signal(0);
} }
void TerminalUI::set_on_paste(OnPasteCallback callback)
{
m_on_paste = std::move(callback);
}
DisplayCoord TerminalUI::dimensions() DisplayCoord TerminalUI::dimensions()
{ {
return m_dimensions; return m_dimensions;
@ -1422,6 +1468,7 @@ void TerminalUI::setup_terminal()
"\033[?25l" // hide cursor "\033[?25l" // hide cursor
"\033=" // set application keypad mode, so the keypad keys send unique codes "\033=" // set application keypad mode, so the keypad keys send unique codes
"\033[?2026$p" // query support for synchronize output "\033[?2026$p" // query support for synchronize output
"\033[?2004h" // force enable bracketed-paste events
); );
} }
@ -1435,6 +1482,7 @@ void TerminalUI::restore_terminal()
"\033[>4;0m" "\033[>4;0m"
"\033[?1004l" "\033[?1004l"
"\033[?1049l" "\033[?1049l"
"\033[?2004l"
"\033[m" // set the terminal output back to default colours and style "\033[m" // set the terminal output back to default colours and style
); );
} }

View File

@ -55,6 +55,7 @@ public:
DisplayCoord dimensions() override; DisplayCoord dimensions() override;
void set_on_key(OnKeyCallback callback) override; void set_on_key(OnKeyCallback callback) override;
void set_on_paste(OnPasteCallback callback) override;
void set_ui_options(const Options& options) override; void set_ui_options(const Options& options) override;
static void setup_terminal(); static void setup_terminal();
@ -137,6 +138,8 @@ private:
FDWatcher m_stdin_watcher; FDWatcher m_stdin_watcher;
OnKeyCallback m_on_key; OnKeyCallback m_on_key;
OnPasteCallback m_on_paste;
Optional<String> m_paste_buffer;
bool m_status_on_top = false; bool m_status_on_top = false;
ConstArrayView<StringView> m_assistant; ConstArrayView<StringView> m_assistant;

View File

@ -43,6 +43,7 @@ enum class CursorMode
}; };
using OnKeyCallback = std::function<void(Key key)>; using OnKeyCallback = std::function<void(Key key)>;
using OnPasteCallback = std::function<void(StringView content)>;
class UserInterface class UserInterface
{ {
@ -78,6 +79,7 @@ public:
virtual void refresh(bool force) = 0; virtual void refresh(bool force) = 0;
virtual void set_on_key(OnKeyCallback callback) = 0; virtual void set_on_key(OnKeyCallback callback) = 0;
virtual void set_on_paste(OnPasteCallback callback) = 0;
using Options = HashMap<String, String, MemoryDomain::Options>; using Options = HashMap<String, String, MemoryDomain::Options>;
virtual void set_ui_options(const Options& options) = 0; virtual void set_ui_options(const Options& options) = 0;