Simplify greatly UI input handling

This round trip through an input callback expected to call
is_key_available and get_key was overcomplicated, just send the
keys as they arrive, the client is already buffering due to urgent
event mode.
This commit is contained in:
Maxime Coste 2016-11-29 21:35:53 +00:00
parent 2fd1414b05
commit 3a81260917
13 changed files with 106 additions and 186 deletions

View File

@ -37,7 +37,12 @@ Client::Client(std::unique_ptr<UserInterface>&& ui,
m_window->options().register_watcher(*this); m_window->options().register_watcher(*this);
m_ui->set_ui_options(m_window->options()["ui_options"].get<UserInterface::Options>()); m_ui->set_ui_options(m_window->options()["ui_options"].get<UserInterface::Options>());
m_ui->set_input_callback([this](EventMode mode) { handle_available_input(mode); }); m_ui->set_on_key([this](Key key) {
if (key == ctrl('c'))
killpg(getpgrp(), SIGINT);
else
m_pending_keys.push_back(key);
});
m_window->hooks().run_hook("WinDisplay", m_window->buffer().name(), context()); m_window->hooks().run_hook("WinDisplay", m_window->buffer().name(), context());
@ -50,54 +55,31 @@ Client::~Client()
m_window->set_client(nullptr); m_window->set_client(nullptr);
} }
Optional<Key> Client::get_next_key(EventMode mode) void Client::process_pending_inputs()
{ {
if (not m_pending_keys.empty())
{
Key key = m_pending_keys.front();
m_pending_keys.erase(m_pending_keys.begin());
return key;
}
if (mode != EventMode::Pending and m_ui->is_key_available())
return m_ui->get_key();
return {};
}
void Client::handle_available_input(EventMode mode)
{
if (mode == EventMode::Urgent)
{
Key key = m_ui->get_key();
if (key == ctrl('c'))
killpg(getpgrp(), SIGINT);
else
m_pending_keys.push_back(key);
return;
}
try try
{ {
const bool debug_keys = (bool)(context().options()["debug"].get<DebugFlags>() & DebugFlags::Keys); const bool debug_keys = (bool)(context().options()["debug"].get<DebugFlags>() & DebugFlags::Keys);
while (Optional<Key> key = get_next_key(mode)) // steal keys as we might receive new keys while handling them.
Vector<Key, MemoryDomain::Client> keys = std::move(m_pending_keys);
for (auto& key : keys)
{ {
if (debug_keys) if (debug_keys)
write_to_debug_buffer(format("Client '{}' got key '{}'", write_to_debug_buffer(format("Client '{}' got key '{}'",
context().name(), key_to_str(*key))); context().name(), key_to_str(key)));
if (*key == ctrl('c')) if (key == Key::FocusIn)
killpg(getpgrp(), SIGINT);
else if (*key == Key::FocusIn)
context().hooks().run_hook("FocusIn", context().name(), context()); context().hooks().run_hook("FocusIn", context().name(), context());
else if (*key == Key::FocusOut) else if (key == Key::FocusOut)
context().hooks().run_hook("FocusOut", context().name(), context()); context().hooks().run_hook("FocusOut", context().name(), context());
else if (key->modifiers == Key::Modifiers::Resize) else if (key.modifiers == Key::Modifiers::Resize)
{ {
m_window->set_dimensions(m_ui->dimensions()); m_window->set_dimensions(m_ui->dimensions());
force_redraw(); force_redraw();
} }
else else
m_input_handler.handle_key(*key); m_input_handler.handle_key(key);
} }
} }
catch (Kakoune::runtime_error& error) catch (Kakoune::runtime_error& error)

View File

@ -34,8 +34,7 @@ public:
Client(Client&&) = delete; Client(Client&&) = delete;
// handle all the keys currently available in the user interface void process_pending_inputs();
void handle_available_input(EventMode mode);
void menu_show(Vector<DisplayLine> choices, BufferCoord anchor, MenuStyle style); void menu_show(Vector<DisplayLine> choices, BufferCoord anchor, MenuStyle style);
void menu_select(int selected); void menu_select(int selected);

View File

@ -61,10 +61,10 @@ Client* ClientManager::create_client(std::unique_ptr<UserInterface>&& ui,
return contains(m_clients, client) ? client : nullptr; return contains(m_clients, client) ? client : nullptr;
} }
void ClientManager::handle_pending_inputs() const void ClientManager::process_pending_inputs() const
{ {
for (auto& client : m_clients) for (auto& client : m_clients)
client->handle_available_input(EventMode::Pending); client->process_pending_inputs();
} }
void ClientManager::remove_client(Client& client, bool graceful) void ClientManager::remove_client(Client& client, bool graceful)

View File

@ -34,7 +34,7 @@ public:
void add_free_window(std::unique_ptr<Window>&& window, SelectionList selections); void add_free_window(std::unique_ptr<Window>&& window, SelectionList selections);
void redraw_clients() const; void redraw_clients() const;
void handle_pending_inputs() const; void process_pending_inputs() const;
Client* get_client_ifp(StringView name); Client* get_client_ifp(StringView name);
Client& get_client(StringView name); Client& get_client(StringView name);

View File

@ -18,7 +18,6 @@ enum class EventMode
{ {
Normal, Normal,
Urgent, Urgent,
Pending
}; };
class FDWatcher class FDWatcher

View File

@ -184,18 +184,6 @@ void JsonUI::draw_status(const DisplayLine& status_line,
rpc_call("draw_status", status_line, mode_line, default_face); rpc_call("draw_status", status_line, mode_line, default_face);
} }
bool JsonUI::is_key_available()
{
return not m_pending_keys.empty();
}
Key JsonUI::get_key()
{
kak_assert(not m_pending_keys.empty());
Key key = m_pending_keys.front();
m_pending_keys.erase(m_pending_keys.begin());
return key;
}
void JsonUI::menu_show(ConstArrayView<DisplayLine> items, void JsonUI::menu_show(ConstArrayView<DisplayLine> items,
DisplayCoord anchor, Face fg, Face bg, DisplayCoord anchor, Face fg, Face bg,
@ -231,11 +219,6 @@ void JsonUI::refresh(bool force)
rpc_call("refresh", force); rpc_call("refresh", force);
} }
void JsonUI::set_input_callback(InputCallback callback)
{
m_input_callback = std::move(callback);
}
void JsonUI::set_ui_options(const Options& options) void JsonUI::set_ui_options(const Options& options)
{ {
// rpc_call("set_ui_options", options); // rpc_call("set_ui_options", options);
@ -246,6 +229,11 @@ DisplayCoord JsonUI::dimensions()
return m_dimensions; return m_dimensions;
} }
void JsonUI::set_on_key(OnKeyCallback callback)
{
m_on_key = std::move(callback);
}
using JsonArray = Vector<Value>; using JsonArray = Vector<Value>;
using JsonObject = IdMap<Value>; using JsonObject = IdMap<Value>;
@ -384,7 +372,7 @@ void JsonUI::eval_json(const Value& json)
for (auto& key_val : params) for (auto& key_val : params)
{ {
for (auto& key : parse_keys(key_val.as<String>())) for (auto& key : parse_keys(key_val.as<String>()))
m_pending_keys.push_back(key); m_on_key(key);
} }
} }
else if (method == "resize") else if (method == "resize")
@ -394,7 +382,7 @@ void JsonUI::eval_json(const Value& json)
DisplayCoord dim{params[0].as<int>(), params[1].as<int>()}; DisplayCoord dim{params[0].as<int>(), params[1].as<int>()};
m_dimensions = dim; m_dimensions = dim;
m_pending_keys.push_back(resize(dim)); m_on_key(resize(dim));
} }
else else
throw runtime_error("unknown method"); throw runtime_error("unknown method");
@ -413,6 +401,9 @@ void JsonUI::parse_requests(EventMode mode)
m_requests += StringView{buf, buf + size}; m_requests += StringView{buf, buf + size};
} }
if (not m_on_key)
return;
while (not m_requests.empty()) while (not m_requests.empty())
{ {
const char* pos = nullptr; const char* pos = nullptr;
@ -435,9 +426,6 @@ void JsonUI::parse_requests(EventMode mode)
m_requests = String{pos, m_requests.end()}; m_requests = String{pos, m_requests.end()};
} }
while (m_input_callback and not m_pending_keys.empty())
m_input_callback(mode);
} }
UnitTest test_json_parser{[]() UnitTest test_json_parser{[]()

View File

@ -26,9 +26,6 @@ public:
const DisplayLine& mode_line, const DisplayLine& mode_line,
const Face& default_face) override; const Face& default_face) override;
bool is_key_available() override;
Key get_key() override;
void menu_show(ConstArrayView<DisplayLine> items, void menu_show(ConstArrayView<DisplayLine> items,
DisplayCoord anchor, Face fg, Face bg, DisplayCoord anchor, Face fg, Face bg,
MenuStyle style) override; MenuStyle style) override;
@ -42,18 +39,16 @@ public:
void refresh(bool force) override; void refresh(bool force) override;
void set_input_callback(InputCallback callback) override;
void set_ui_options(const Options& options) override;
DisplayCoord dimensions() override; DisplayCoord dimensions() override;
void set_on_key(OnKeyCallback callback) override;
void set_ui_options(const Options& options) override;
private: private:
void parse_requests(EventMode mode); void parse_requests(EventMode mode);
void eval_json(const Value& value); void eval_json(const Value& value);
InputCallback m_input_callback;
FDWatcher m_stdin_watcher; FDWatcher m_stdin_watcher;
OnKeyCallback m_on_key;
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

@ -332,10 +332,8 @@ std::unique_ptr<UserInterface> make_ui(UIType ui_type)
void draw(const DisplayBuffer&, const Face&, const Face&) override {} void draw(const DisplayBuffer&, const Face&, const Face&) override {}
void draw_status(const DisplayLine&, const DisplayLine&, const Face&) override {} void draw_status(const DisplayLine&, const DisplayLine&, const Face&) override {}
DisplayCoord dimensions() override { return {24,80}; } DisplayCoord dimensions() override { return {24,80}; }
bool is_key_available() override { return false; }
Key get_key() override { return Key::Invalid; }
void refresh(bool) override {} void refresh(bool) override {}
void set_input_callback(InputCallback) override {} void set_on_key(OnKeyCallback callback) override {}
void set_ui_options(const Options&) override {} void set_ui_options(const Options&) override {}
}; };
@ -587,7 +585,7 @@ int run_server(StringView session, StringView init_command,
{ {
client_manager.redraw_clients(); client_manager.redraw_clients();
event_manager.handle_next_events(EventMode::Normal); event_manager.handle_next_events(EventMode::Normal);
client_manager.handle_pending_inputs(); client_manager.process_pending_inputs();
client_manager.clear_client_trash(); client_manager.clear_client_trash();
client_manager.clear_window_trash(); client_manager.clear_window_trash();
buffer_manager.clear_buffer_trash(); buffer_manager.clear_buffer_trash();

View File

@ -220,8 +220,11 @@ void on_term_resize(int)
NCursesUI::NCursesUI() NCursesUI::NCursesUI()
: m_stdin_watcher{0, [this](FDWatcher&, EventMode mode) { : m_stdin_watcher{0, [this](FDWatcher&, EventMode mode) {
if (m_input_callback) if (not m_on_key)
m_input_callback(mode); return;
while (auto key = get_next_key())
m_on_key(*key);
}}, }},
m_assistant(assistant_clippy), m_assistant(assistant_clippy),
m_colors{ m_colors{
@ -485,26 +488,19 @@ void NCursesUI::on_sighup()
m_window = nullptr; m_window = nullptr;
} }
bool NCursesUI::is_key_available() Optional<Key> NCursesUI::get_next_key()
{ {
if (not m_window) if (not m_window)
return false; return {};
check_resize(); check_resize();
wtimeout(m_window, 0); wtimeout(m_window, 0);
const int c = wgetch(m_window); const int c = wgetch(m_window);
if (c != ERR)
ungetch(c);
wtimeout(m_window, -1); wtimeout(m_window, -1);
return c != ERR;
}
Key NCursesUI::get_key() if (c == ERR)
{ return {};
check_resize();
const int c = wgetch(m_window);
if (c == KEY_MOUSE) if (c == KEY_MOUSE)
{ {
@ -530,21 +526,21 @@ Key NCursesUI::get_key()
return res | Key::Modifiers::MousePos; return res | Key::Modifiers::MousePos;
}; };
return { get_modifiers(ev.bstate), return Key{ get_modifiers(ev.bstate),
encode_coord({ ev.y - (m_status_on_top ? 1 : 0), ev.x }) }; encode_coord({ ev.y - (m_status_on_top ? 1 : 0), ev.x }) };
} }
} }
if (c > 0 and c < 27) if (c > 0 and c < 27)
{ {
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'))
return Key::Tab; return {Key::Tab};
if (c == control('z')) if (c == control('z'))
{ {
raise(SIGTSTP); raise(SIGTSTP);
return Key::Invalid; return {};
} }
return ctrl(Codepoint(c) - 1 + 'a'); return ctrl(Codepoint(c) - 1 + 'a');
} }
@ -557,8 +553,8 @@ Key NCursesUI::get_key()
const Codepoint csi_val = wgetch(m_window); const Codepoint csi_val = wgetch(m_window);
switch (csi_val) switch (csi_val)
{ {
case 'I': return Key::FocusIn; case 'I': return {Key::FocusIn};
case 'O': return Key::FocusOut; case 'O': return {Key::FocusOut};
default: break; // nothing default: break; // nothing
} }
} }
@ -570,28 +566,28 @@ Key NCursesUI::get_key()
return alt(new_c); return alt(new_c);
} }
else else
return Key::Escape; return {Key::Escape};
} }
else switch (c) else switch (c)
{ {
case KEY_BACKSPACE: case 127: return Key::Backspace; case KEY_BACKSPACE: case 127: return {Key::Backspace};
case KEY_DC: return Key::Delete; case KEY_DC: return {Key::Delete};
case KEY_UP: return Key::Up; case KEY_UP: return {Key::Up};
case KEY_DOWN: return Key::Down; case KEY_DOWN: return {Key::Down};
case KEY_LEFT: return Key::Left; case KEY_LEFT: return {Key::Left};
case KEY_RIGHT: return Key::Right; case KEY_RIGHT: return {Key::Right};
case KEY_PPAGE: return Key::PageUp; case KEY_PPAGE: return {Key::PageUp};
case KEY_NPAGE: return Key::PageDown; case KEY_NPAGE: return {Key::PageDown};
case KEY_HOME: return Key::Home; case KEY_HOME: return {Key::Home};
case KEY_END: return Key::End; case KEY_END: return {Key::End};
case KEY_BTAB: return Key::BackTab; case KEY_BTAB: return {Key::BackTab};
case KEY_RESIZE: return resize(m_dimensions); case KEY_RESIZE: return resize(m_dimensions);
} }
for (int i = 0; i < 12; ++i) for (int i = 0; i < 12; ++i)
{ {
if (c == KEY_F(i+1)) if (c == KEY_F(i+1))
return Key::F1 + i; return {Key::F1 + i};
} }
if (c >= 0 and c < 256) if (c >= 0 and c < 256)
@ -607,9 +603,10 @@ Key NCursesUI::get_key()
WINDOW* window; WINDOW* window;
}; };
return utf8::codepoint(getch_iterator{m_window}, getch_iterator{m_window}); return Key{utf8::codepoint(getch_iterator{m_window},
getch_iterator{m_window})};
} }
return Key::Invalid; return {};
} }
template<typename T> template<typename T>
@ -954,16 +951,16 @@ void NCursesUI::mark_dirty(const Window& win)
wredrawln(m_window, (int)win.pos.line, (int)win.size.line); wredrawln(m_window, (int)win.pos.line, (int)win.size.line);
} }
void NCursesUI::set_on_key(OnKeyCallback callback)
{
m_on_key = std::move(callback);
}
DisplayCoord NCursesUI::dimensions() DisplayCoord NCursesUI::dimensions()
{ {
return m_dimensions; return m_dimensions;
} }
void NCursesUI::set_input_callback(InputCallback callback)
{
m_input_callback = std::move(callback);
}
void NCursesUI::abort() void NCursesUI::abort()
{ {
endwin(); endwin();

View File

@ -30,9 +30,6 @@ public:
const DisplayLine& mode_line, const DisplayLine& mode_line,
const Face& default_face) override; const Face& default_face) override;
bool is_key_available() override;
Key get_key() override;
void menu_show(ConstArrayView<DisplayLine> items, void menu_show(ConstArrayView<DisplayLine> items,
DisplayCoord anchor, Face fg, Face bg, DisplayCoord anchor, Face fg, Face bg,
MenuStyle style) override; MenuStyle style) override;
@ -46,11 +43,10 @@ public:
void refresh(bool force) override; void refresh(bool force) override;
void set_input_callback(InputCallback callback) override;
void set_ui_options(const Options& options) override;
DisplayCoord dimensions() override; DisplayCoord dimensions() override;
void set_on_key(OnKeyCallback callback) override;
void set_ui_options(const Options& options) override;
static void abort(); static void abort();
@ -74,6 +70,8 @@ private:
ColumnCount col_index, ColumnCount max_column, ColumnCount col_index, ColumnCount max_column,
const Face& default_face); const Face& default_face);
Optional<Key> get_next_key();
NCursesWin* m_window = nullptr; NCursesWin* m_window = nullptr;
DisplayCoord m_dimensions; DisplayCoord m_dimensions;
@ -119,8 +117,8 @@ private:
InfoStyle style; InfoStyle style;
} m_info; } m_info;
FDWatcher m_stdin_watcher; FDWatcher m_stdin_watcher;
InputCallback m_input_callback; OnKeyCallback m_on_key;
bool m_status_on_top = false; bool m_status_on_top = false;
ConstArrayView<StringView> m_assistant; ConstArrayView<StringView> m_assistant;

View File

@ -324,11 +324,10 @@ public:
void refresh(bool force) override; void refresh(bool force) override;
bool is_key_available() override; DisplayCoord dimensions() override { return m_dimensions; }
Key get_key() override;
DisplayCoord dimensions() override;
void set_input_callback(InputCallback callback) override; void set_on_key(OnKeyCallback callback) override
{ m_on_key = std::move(callback); }
void set_ui_options(const Options& options) override; void set_ui_options(const Options& options) override;
@ -336,46 +335,45 @@ public:
Client* client() const { return m_client.get(); } Client* client() const { return m_client.get(); }
private: private:
FDWatcher m_socket_watcher; FDWatcher m_socket_watcher;
MsgReader m_reader; MsgReader m_reader;
DisplayCoord m_dimensions; DisplayCoord m_dimensions;
InputCallback m_input_callback; OnKeyCallback m_on_key;
SafePtr<Client> m_client; SafePtr<Client> m_client;
Optional<Key> m_pending_key;
}; };
RemoteUI::RemoteUI(int socket, DisplayCoord dimensions) RemoteUI::RemoteUI(int socket, DisplayCoord dimensions)
: m_socket_watcher(socket, [this](FDWatcher& watcher, EventMode mode) { : m_socket_watcher(socket, [this](FDWatcher& watcher, EventMode mode) {
const int sock = watcher.fd(); const int sock = watcher.fd();
bool disconnect = false;
try try
{ {
while (fd_readable(sock) and not m_reader.ready()) while (fd_readable(sock))
{
m_reader.read_available(sock); m_reader.read_available(sock);
if (m_reader.ready() and not m_pending_key) if (not m_reader.ready())
{ continue;
if (m_reader.type() == MessageType::Key)
if (m_reader.type() != MessageType::Key)
{ {
m_pending_key = m_reader.read<Key>(); ClientManager::instance().remove_client(*m_client, false);
m_reader.reset(); return;
} }
else
disconnect = true; 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 remote_error& err) catch (const remote_error& err)
{ {
write_to_debug_buffer(format("Error while reading remote message: {}", err.what())); write_to_debug_buffer(format("Error while reading remote message: {}", err.what()));
disconnect = true;
}
if (disconnect)
ClientManager::instance().remove_client(*m_client, false); ClientManager::instance().remove_client(*m_client, false);
else if (m_pending_key and m_input_callback) }
m_input_callback(mode);
}), }),
m_dimensions(dimensions) m_dimensions(dimensions)
{ {
@ -460,31 +458,6 @@ void RemoteUI::set_ui_options(const Options& options)
msg.write(options); msg.write(options);
} }
bool RemoteUI::is_key_available()
{
return (bool)m_pending_key;
}
Key RemoteUI::get_key()
{
kak_assert(m_pending_key);
auto key = *m_pending_key;
m_pending_key.reset();
if (key.modifiers == Key::Modifiers::Resize)
m_dimensions = key.coord();
return key;
}
DisplayCoord RemoteUI::dimensions()
{
return m_dimensions;
}
void RemoteUI::set_input_callback(InputCallback callback)
{
m_input_callback = std::move(callback);
}
static sockaddr_un session_addr(StringView session) static sockaddr_un session_addr(StringView session)
{ {
sockaddr_un addr; sockaddr_un addr;
@ -527,7 +500,10 @@ RemoteClient::RemoteClient(StringView session, std::unique_ptr<UserInterface>&&
msg.write(env_vars); msg.write(env_vars);
} }
m_ui->set_input_callback([this](EventMode){ send_available_keys(); }); m_ui->set_on_key([this](Key key){
MsgWriter msg(m_socket_watcher->fd(), MessageType::Key);
msg.write(key);
});
MsgReader reader; MsgReader reader;
m_socket_watcher.reset(new FDWatcher{sock, [this, reader](FDWatcher& watcher, EventMode) mutable { m_socket_watcher.reset(new FDWatcher{sock, [this, reader](FDWatcher& watcher, EventMode) mutable {
@ -598,15 +574,6 @@ RemoteClient::RemoteClient(StringView session, std::unique_ptr<UserInterface>&&
}}); }});
} }
void RemoteClient::send_available_keys()
{
while (m_ui->is_key_available())
{
MsgWriter msg(m_socket_watcher->fd(), MessageType::Key);
msg.write(m_ui->get_key());
}
}
void send_command(StringView session, StringView command) void send_command(StringView session, StringView command)
{ {
int sock = connect_to(session); int sock = connect_to(session);

View File

@ -19,6 +19,7 @@ struct remote_error : runtime_error
class FDWatcher; class FDWatcher;
class UserInterface; class UserInterface;
struct Key;
// A remote client handle communication between a client running on the server // A remote client handle communication between a client running on the server
// and a user interface running on the local process. // and a user interface running on the local process.
@ -29,8 +30,6 @@ public:
const EnvVarMap& env_vars, StringView init_command); const EnvVarMap& env_vars, StringView init_command);
private: private:
void send_available_keys();
std::unique_ptr<UserInterface> m_ui; std::unique_ptr<UserInterface> m_ui;
std::unique_ptr<FDWatcher> m_socket_watcher; std::unique_ptr<FDWatcher> m_socket_watcher;
}; };

View File

@ -33,7 +33,7 @@ enum class InfoStyle
enum class EventMode; enum class EventMode;
using InputCallback = std::function<void(EventMode mode)>; using OnKeyCallback = std::function<void(Key key)>;
class UserInterface class UserInterface
{ {
@ -60,12 +60,10 @@ public:
const Face& default_face) = 0; const Face& default_face) = 0;
virtual DisplayCoord dimensions() = 0; virtual DisplayCoord dimensions() = 0;
virtual bool is_key_available() = 0;
virtual Key get_key() = 0;
virtual void refresh(bool force) = 0; virtual void refresh(bool force) = 0;
virtual void set_input_callback(InputCallback callback) = 0; virtual void set_on_key(OnKeyCallback callback) = 0;
using Options = IdMap<String, MemoryDomain::Options>; using Options = IdMap<String, MemoryDomain::Options>;
virtual void set_ui_options(const Options& options) = 0; virtual void set_ui_options(const Options& options) = 0;