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

View File

@ -32,6 +32,7 @@ public:
InputMode& operator=(const InputMode&) = delete;
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_disabled(bool temporary) {}
@ -71,6 +72,32 @@ private:
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
{
@ -1015,6 +1042,17 @@ public:
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)
{
if (face != m_prompt_face)
@ -1433,6 +1471,12 @@ public:
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
{
auto num_sel = context().selections().size();
@ -1462,7 +1506,8 @@ private:
selections.sort_and_merge_overlapping();
}
void insert(ConstArrayView<String> strings)
template<typename S>
void insert(ConstArrayView<S> strings)
{
m_completer.try_accept();
context().selections().for_each([strings, &buffer=context().buffer()]
@ -1474,7 +1519,7 @@ private:
void insert(Codepoint key)
{
String str{key};
insert(str);
insert(ConstArrayView{str});
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);
}
void InputHandler::paste(StringView content)
{
current_mode().paste(content);
}
void InputHandler::prompt(StringView prompt, String initstr, String emptystr,
Face prompt_face, PromptFlags flags, char history_register,
PromptCompleter completer, PromptCallback callback)

View File

@ -72,6 +72,8 @@ public:
void insert(InsertMode mode, int count);
// repeat last insert mode key sequence
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,
// 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);
}
void JsonUI::set_on_paste(OnPasteCallback callback)
{
m_on_paste = std::move(callback);
}
void JsonUI::eval_json(const Value& json)
{
if (not json.is_a<JsonObject>())

View File

@ -46,6 +46,7 @@ public:
DisplayCoord dimensions() override;
void set_on_key(OnKeyCallback callback) override;
void set_on_paste(OnPasteCallback callback) override;
void set_ui_options(const Options& options) override;
private:
@ -54,6 +55,7 @@ private:
FDWatcher m_stdin_watcher;
OnKeyCallback m_on_key;
OnPasteCallback m_on_paste;
Vector<Key, MemoryDomain::Client> m_pending_keys;
DisplayCoord m_dimensions;
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 refresh(bool) override {}
void set_on_key(OnKeyCallback) override {}
void set_on_paste(OnPasteCallback) 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);
}
enum class PasteMode
{
Append,
Insert,
Replace
};
BufferCoord paste_pos(Buffer& buffer, BufferCoord min, BufferCoord max, PasteMode mode, bool linewise)
{
switch (mode)

View File

@ -10,6 +10,7 @@
namespace Kakoune
{
class Buffer;
class Context;
struct no_selections_remaining : runtime_error
@ -40,6 +41,15 @@ struct KeyInfo
String build_autoinfo_for_mapping(const Context& context, KeymapMode mode,
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

View File

@ -43,6 +43,7 @@ enum class MessageType : uint8_t
SetOptions,
Exit,
Key,
Paste,
};
class MsgWriter
@ -413,6 +414,9 @@ public:
void set_on_key(OnKeyCallback callback) override
{ 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 exit(int status);
@ -430,6 +434,7 @@ private:
MsgReader m_reader;
DisplayCoord m_dimensions;
OnKeyCallback m_on_key;
OnPasteCallback m_on_paste;
RemoteBuffer m_send_buffer;
};
@ -479,18 +484,26 @@ RemoteUI::RemoteUI(int socket, DisplayCoord dimensions)
if (not m_reader.ready())
continue;
if (m_reader.type() != MessageType::Key)
if (m_reader.type() == MessageType::Key)
{
m_socket_watcher.close_fd();
return;
}
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();
return;
}
}
}
catch (const disconnected& err)
{
@ -660,6 +673,11 @@ RemoteClient::RemoteClient(StringView session, StringView name, std::unique_ptr<
msg.write(key);
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,
[this, reader = MsgReader{}](FDWatcher& watcher, FdEvents events, EventMode) mutable {

View File

@ -1,5 +1,6 @@
#include "terminal_ui.hh"
#include "buffer_utils.hh"
#include "display_buffer.hh"
#include "event_manager.hh"
#include "exception.hh"
@ -683,12 +684,16 @@ Optional<Key> TerminalUI::get_next_key()
return resize(dimensions());
}
static auto get_char = []() -> Optional<unsigned char> {
static auto get_char = [this]() -> Optional<unsigned char> {
if (not fd_readable(STDIN_FILENO))
return {};
if (unsigned char c = 0; read(STDIN_FILENO, &c, 1) == 1)
{
if (m_paste_buffer)
m_paste_buffer->push_back(c);
return c;
}
stdin_closed = 1;
return {};
@ -700,7 +705,7 @@ Optional<Key> TerminalUI::get_next_key()
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'))
return Key::Return;
if (c == control('i'))
@ -715,7 +720,7 @@ Optional<Key> TerminalUI::get_next_key()
return Key::Escape;
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)
return Key{cp};
// 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{})};
};
auto parse_mask = [](int mask) {
static auto parse_mask = [](int mask) {
Key::Modifiers mod = Key::Modifiers::None;
if (mask & 1)
mod |= Key::Modifiers::Shift;
@ -754,7 +759,16 @@ Optional<Key> TerminalUI::get_next_key()
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); };
int params[16][4] = {};
auto c = next_char();
@ -819,7 +833,7 @@ Optional<Key> TerminalUI::get_next_key()
{
if (params[0][0] == 2026)
m_synchronized.supported = (params[1][0] == 1 or params[1][0] == 2);
return {Key::Invalid};
return Key{Key::Invalid};
}
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
case 33: case 34:
return Key{Key::Modifiers::Shift, Key::F9 + params[0][0] - 33}; // rxvt style
case 200:
return PasteEvent::Begin;
case 201:
return PasteEvent::End;
}
return {};
case 'u':
@ -899,7 +917,7 @@ Optional<Key> TerminalUI::get_next_key()
return {};
};
auto parse_ss3 = [&parse_mask]() -> Optional<Key> {
static auto parse_ss3 = []() -> Optional<Key> {
int raw_mask = 0;
char code = '0';
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)
return parse_key(*c);
if (auto next = get_char())
{
if (*next == '[') // potential CSI
return parse_csi().value_or(alt('['));
if (*next == 'O') // potential SS3
return parse_ss3().value_or(alt('O'));
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};
}
@ -1395,6 +1436,11 @@ void TerminalUI::set_on_key(OnKeyCallback callback)
EventManager::instance().force_signal(0);
}
void TerminalUI::set_on_paste(OnPasteCallback callback)
{
m_on_paste = std::move(callback);
}
DisplayCoord TerminalUI::dimensions()
{
return m_dimensions;
@ -1422,6 +1468,7 @@ void TerminalUI::setup_terminal()
"\033[?25l" // hide cursor
"\033=" // set application keypad mode, so the keypad keys send unique codes
"\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[?1004l"
"\033[?1049l"
"\033[?2004l"
"\033[m" // set the terminal output back to default colours and style
);
}

View File

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

View File

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