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_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());
@ -50,54 +55,31 @@ Client::~Client()
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
{
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)
write_to_debug_buffer(format("Client '{}' got key '{}'",
context().name(), key_to_str(*key)));
context().name(), key_to_str(key)));
if (*key == ctrl('c'))
killpg(getpgrp(), SIGINT);
else if (*key == Key::FocusIn)
if (key == Key::FocusIn)
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());
else if (key->modifiers == Key::Modifiers::Resize)
else if (key.modifiers == Key::Modifiers::Resize)
{
m_window->set_dimensions(m_ui->dimensions());
force_redraw();
}
else
m_input_handler.handle_key(*key);
m_input_handler.handle_key(key);
}
}
catch (Kakoune::runtime_error& error)

View File

@ -34,8 +34,7 @@ public:
Client(Client&&) = delete;
// handle all the keys currently available in the user interface
void handle_available_input(EventMode mode);
void process_pending_inputs();
void menu_show(Vector<DisplayLine> choices, BufferCoord anchor, MenuStyle style);
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;
}
void ClientManager::handle_pending_inputs() const
void ClientManager::process_pending_inputs() const
{
for (auto& client : m_clients)
client->handle_available_input(EventMode::Pending);
client->process_pending_inputs();
}
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 redraw_clients() const;
void handle_pending_inputs() const;
void process_pending_inputs() const;
Client* get_client_ifp(StringView name);
Client& get_client(StringView name);

View File

@ -18,7 +18,6 @@ enum class EventMode
{
Normal,
Urgent,
Pending
};
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);
}
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,
DisplayCoord anchor, Face fg, Face bg,
@ -231,11 +219,6 @@ void JsonUI::refresh(bool 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)
{
// rpc_call("set_ui_options", options);
@ -246,6 +229,11 @@ DisplayCoord JsonUI::dimensions()
return m_dimensions;
}
void JsonUI::set_on_key(OnKeyCallback callback)
{
m_on_key = std::move(callback);
}
using JsonArray = Vector<Value>;
using JsonObject = IdMap<Value>;
@ -384,7 +372,7 @@ void JsonUI::eval_json(const Value& json)
for (auto& key_val : params)
{
for (auto& key : parse_keys(key_val.as<String>()))
m_pending_keys.push_back(key);
m_on_key(key);
}
}
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>()};
m_dimensions = dim;
m_pending_keys.push_back(resize(dim));
m_on_key(resize(dim));
}
else
throw runtime_error("unknown method");
@ -413,6 +401,9 @@ void JsonUI::parse_requests(EventMode mode)
m_requests += StringView{buf, buf + size};
}
if (not m_on_key)
return;
while (not m_requests.empty())
{
const char* pos = nullptr;
@ -435,9 +426,6 @@ void JsonUI::parse_requests(EventMode mode)
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{[]()

View File

@ -26,9 +26,6 @@ public:
const DisplayLine& mode_line,
const Face& default_face) override;
bool is_key_available() override;
Key get_key() override;
void menu_show(ConstArrayView<DisplayLine> items,
DisplayCoord anchor, Face fg, Face bg,
MenuStyle style) override;
@ -42,18 +39,16 @@ public:
void refresh(bool force) override;
void set_input_callback(InputCallback callback) override;
void set_ui_options(const Options& options) override;
DisplayCoord dimensions() override;
void set_on_key(OnKeyCallback callback) override;
void set_ui_options(const Options& options) override;
private:
void parse_requests(EventMode mode);
void eval_json(const Value& value);
InputCallback m_input_callback;
FDWatcher m_stdin_watcher;
OnKeyCallback m_on_key;
Vector<Key, MemoryDomain::Client> m_pending_keys;
DisplayCoord m_dimensions;
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_status(const DisplayLine&, const DisplayLine&, const Face&) override {}
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 set_input_callback(InputCallback) override {}
void set_on_key(OnKeyCallback callback) override {}
void set_ui_options(const Options&) override {}
};
@ -587,7 +585,7 @@ int run_server(StringView session, StringView init_command,
{
client_manager.redraw_clients();
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_window_trash();
buffer_manager.clear_buffer_trash();

View File

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

View File

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

View File

@ -324,11 +324,10 @@ public:
void refresh(bool force) override;
bool is_key_available() override;
Key get_key() override;
DisplayCoord dimensions() override;
DisplayCoord dimensions() override { return m_dimensions; }
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;
@ -336,46 +335,45 @@ public:
Client* client() const { return m_client.get(); }
private:
FDWatcher m_socket_watcher;
MsgReader m_reader;
DisplayCoord m_dimensions;
InputCallback m_input_callback;
FDWatcher m_socket_watcher;
MsgReader m_reader;
DisplayCoord m_dimensions;
OnKeyCallback m_on_key;
SafePtr<Client> m_client;
Optional<Key> m_pending_key;
};
RemoteUI::RemoteUI(int socket, DisplayCoord dimensions)
: m_socket_watcher(socket, [this](FDWatcher& watcher, EventMode mode) {
const int sock = watcher.fd();
bool disconnect = false;
try
{
while (fd_readable(sock) and not m_reader.ready())
while (fd_readable(sock))
{
m_reader.read_available(sock);
if (m_reader.ready() and not m_pending_key)
{
if (m_reader.type() == MessageType::Key)
if (not m_reader.ready())
continue;
if (m_reader.type() != MessageType::Key)
{
m_pending_key = m_reader.read<Key>();
m_reader.reset();
ClientManager::instance().remove_client(*m_client, false);
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)
{
write_to_debug_buffer(format("Error while reading remote message: {}", err.what()));
disconnect = true;
}
if (disconnect)
ClientManager::instance().remove_client(*m_client, false);
else if (m_pending_key and m_input_callback)
m_input_callback(mode);
}
}),
m_dimensions(dimensions)
{
@ -460,31 +458,6 @@ void RemoteUI::set_ui_options(const Options& 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)
{
sockaddr_un addr;
@ -527,7 +500,10 @@ RemoteClient::RemoteClient(StringView session, std::unique_ptr<UserInterface>&&
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;
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)
{
int sock = connect_to(session);

View File

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

View File

@ -33,7 +33,7 @@ enum class InfoStyle
enum class EventMode;
using InputCallback = std::function<void(EventMode mode)>;
using OnKeyCallback = std::function<void(Key key)>;
class UserInterface
{
@ -60,12 +60,10 @@ public:
const Face& default_face) = 0;
virtual DisplayCoord dimensions() = 0;
virtual bool is_key_available() = 0;
virtual Key get_key() = 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>;
virtual void set_ui_options(const Options& options) = 0;