Merge remote-tracking branch 'krobelus/bracketed-paste'
This commit is contained in:
commit
f05ab99d4d
|
@ -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());
|
||||
|
||||
|
|
|
@ -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*>(¤t_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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>())
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {}
|
||||
};
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,17 +484,25 @@ RemoteUI::RemoteUI(int socket, DisplayCoord dimensions)
|
|||
if (not m_reader.ready())
|
||||
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();
|
||||
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)
|
||||
|
@ -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 {
|
||||
|
|
|
@ -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'));
|
||||
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};
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue
Block a user