diff --git a/src/buffer_utils.cc b/src/buffer_utils.cc index 12ffae19..33b07b57 100644 --- a/src/buffer_utils.cc +++ b/src/buffer_utils.cc @@ -105,7 +105,10 @@ Buffer* create_fifo_buffer(String name, int fd, bool scroll) ValueId fifo_watcher_id = s_fifo_watcher_id; std::unique_ptr watcher( - new FDWatcher(fd, [buffer, scroll, fifo_watcher_id](FDWatcher& watcher) { + new FDWatcher(fd, [buffer, scroll, fifo_watcher_id](FDWatcher& watcher, EventMode mode) { + if (mode != EventMode::Normal) + return; + constexpr size_t buffer_size = 2048; // if we read data slower than it arrives in the fifo, limiting the // iteration number allows us to go back go back to the event loop and diff --git a/src/client.cc b/src/client.cc index ea6af7b9..39c3bc34 100644 --- a/src/client.cc +++ b/src/client.cc @@ -7,8 +7,12 @@ #include "file.hh" #include "remote.hh" #include "client_manager.hh" +#include "event_manager.hh" #include "window.hh" +#include +#include + namespace Kakoune { @@ -34,14 +38,44 @@ Client::~Client() m_window->options().unregister_watcher(*this); } -void Client::handle_available_input() +void Client::handle_available_input(EventMode mode) { - while (m_ui->is_key_available()) + if (mode == EventMode::Normal) { - m_input_handler.handle_key(m_ui->get_key()); - m_input_handler.clear_mode_trash(); + try + { + for (auto& key : m_pending_keys) + { + m_input_handler.handle_key(key); + m_input_handler.clear_mode_trash(); + } + m_pending_keys.clear(); + + while (m_ui->is_key_available()) + { + m_input_handler.handle_key(m_ui->get_key()); + m_input_handler.clear_mode_trash(); + } + context().window().forget_timestamp(); + } + catch (Kakoune::runtime_error& error) + { + context().print_status({ error.what(), get_face("Error") }); + context().hooks().run_hook("RuntimeError", error.what(), context()); + } + catch (Kakoune::client_removed&) + { + ClientManager::instance().remove_client(*this); + } + } + else + { + Key key = m_ui->get_key(); + if (key == ctrl('c')) + killpg(getpgrp(), SIGINT); + else + m_pending_keys.push_back(key); } - context().window().forget_timestamp(); } void Client::print_status(DisplayLine status_line) diff --git a/src/client.hh b/src/client.hh index ebbc96e8..00ae553a 100644 --- a/src/client.hh +++ b/src/client.hh @@ -14,6 +14,9 @@ namespace Kakoune class UserInterface; class Window; class String; +struct Key; + +enum class EventMode; class Client : public SafeCountable, public OptionManagerWatcher { @@ -28,7 +31,7 @@ public: Client(Client&&) = delete; // handle all the keys currently available in the user interface - void handle_available_input(); + void handle_available_input(EventMode mode); void print_status(DisplayLine status_line); @@ -64,6 +67,8 @@ private: DisplayLine m_status_line; DisplayLine m_pending_status_line; DisplayLine m_mode_line; + + std::vector m_pending_keys; }; } diff --git a/src/client_manager.cc b/src/client_manager.cc index 87abcf3e..4f99e844 100644 --- a/src/client_manager.cc +++ b/src/client_manager.cc @@ -50,26 +50,19 @@ Client* ClientManager::create_client(std::unique_ptr&& ui, return nullptr; } - client->ui().set_input_callback([client, this]() { - try - { - client->handle_available_input(); - } - catch (Kakoune::runtime_error& error) - { - client->context().print_status({ error.what(), get_face("Error") }); - client->context().hooks().run_hook("RuntimeError", error.what(), - client->context()); - } - catch (Kakoune::client_removed&) - { - ClientManager::instance().remove_client(*client); - } + client->ui().set_input_callback([client](EventMode mode) { + client->handle_available_input(mode); }); return client; } +void ClientManager::handle_available_inputs() const +{ + for (auto& client : m_clients) + client->handle_available_input(EventMode::Normal); +} + void ClientManager::remove_client(Client& client) { for (auto it = m_clients.begin(); it != m_clients.end(); ++it) diff --git a/src/client_manager.hh b/src/client_manager.hh index 2c2e01bc..9a3faf91 100644 --- a/src/client_manager.hh +++ b/src/client_manager.hh @@ -35,6 +35,7 @@ public: void redraw_clients() const; void clear_mode_trashes() const; + void handle_available_inputs() const; Client* get_client_ifp(StringView name); Client& get_client(StringView name); diff --git a/src/event_manager.cc b/src/event_manager.cc index 48e85f43..673f9403 100644 --- a/src/event_manager.cc +++ b/src/event_manager.cc @@ -16,8 +16,13 @@ FDWatcher::~FDWatcher() EventManager::instance().m_fd_watchers.erase(this); } -Timer::Timer(TimePoint date, Callback callback) - : m_date{date}, m_callback{std::move(callback)} +void FDWatcher::run(EventMode mode) +{ + m_callback(*this, mode); +} + +Timer::Timer(TimePoint date, Callback callback, EventMode mode) + : m_date{date}, m_callback{std::move(callback)}, m_mode(mode) { if (EventManager::has_instance()) EventManager::instance().m_timers.insert(this); @@ -29,10 +34,15 @@ Timer::~Timer() EventManager::instance().m_timers.erase(this); } -void Timer::run() +void Timer::run(EventMode mode) { - m_date = TimePoint::max(); - m_callback(*this); + if (mode & m_mode) + { + m_date = TimePoint::max(); + m_callback(*this); + } + else // try again a little later + m_date = Clock::now() + std::chrono::milliseconds{10}; } EventManager::EventManager() @@ -46,7 +56,7 @@ EventManager::~EventManager() kak_assert(m_timers.empty()); } -void EventManager::handle_next_events() +void EventManager::handle_next_events(EventMode mode) { std::vector events; events.reserve(m_fd_watchers.size()); @@ -76,7 +86,7 @@ void EventManager::handle_next_events() auto it = find_if(m_fd_watchers, [fd](FDWatcher* w) { return w->fd() == fd; }); if (it != m_fd_watchers.end()) - (*it)->run(); + (*it)->run(mode); } } @@ -84,7 +94,7 @@ void EventManager::handle_next_events() for (auto& timer : m_timers) { if (timer->next_date() <= now) - timer->run(); + timer->run(mode); } } diff --git a/src/event_manager.hh b/src/event_manager.hh index e14468ba..a88cd620 100644 --- a/src/event_manager.hh +++ b/src/event_manager.hh @@ -2,6 +2,7 @@ #define event_manager_hh_INCLUDED #include "utils.hh" +#include "flags.hh" #include #include @@ -9,20 +10,28 @@ namespace Kakoune { +enum class EventMode +{ + Normal = 1 << 0, + Urgent = 1 << 1 +}; + +template<> struct WithBitOps : std::true_type {}; + class FDWatcher { public: - using Callback = std::function; + using Callback = std::function; FDWatcher(int fd, Callback callback); ~FDWatcher(); int fd() const { return m_fd; } - void run() { m_callback(*this); } + void run(EventMode mode); private: FDWatcher(const FDWatcher&) = delete; - int m_fd; - Callback m_callback; + int m_fd; + Callback m_callback; }; using Clock = std::chrono::steady_clock; @@ -33,15 +42,17 @@ class Timer public: using Callback = std::function; - Timer(TimePoint date, Callback callback); + Timer(TimePoint date, Callback callback, + EventMode mode = EventMode::Normal); ~Timer(); TimePoint next_date() const { return m_date; } void set_next_date(TimePoint date) { m_date = date; } - void run(); + void run(EventMode mode); private: TimePoint m_date; + EventMode m_mode; Callback m_callback; }; @@ -56,7 +67,7 @@ public: EventManager(); ~EventManager(); - void handle_next_events(); + void handle_next_events(EventMode mode); // force the watchers associated with fd to be executed // on next handle_next_events call. diff --git a/src/main.cc b/src/main.cc index 5d3ad6dd..4bb0df86 100644 --- a/src/main.cc +++ b/src/main.cc @@ -305,7 +305,7 @@ int run_client(StringView session, StringView init_command) RemoteClient client{session, make_unique(), get_env_vars(), init_command}; while (true) - event_manager.handle_next_events(); + event_manager.handle_next_events(EventMode::Normal); } catch (peer_disconnected&) { @@ -407,7 +407,8 @@ int run_server(StringView session, StringView init_command, while (not terminate and (not client_manager.empty() or daemon)) { - event_manager.handle_next_events(); + event_manager.handle_next_events(EventMode::Normal); + client_manager.handle_available_inputs(); client_manager.clear_mode_trashes(); buffer_manager.clear_buffer_trash(); client_manager.redraw_clients(); diff --git a/src/ncurses.cc b/src/ncurses.cc index fe2ba1b1..3f06c557 100644 --- a/src/ncurses.cc +++ b/src/ncurses.cc @@ -219,8 +219,10 @@ void on_sigint(int) } NCursesUI::NCursesUI() - : m_stdin_watcher{0, [this](FDWatcher&){ if (m_input_callback) - m_input_callback(); }} + : m_stdin_watcher{0, [this](FDWatcher&, EventMode mode) { + if (m_input_callback) + m_input_callback(mode); + }} { initscr(); raw(); diff --git a/src/normal.cc b/src/normal.cc index 18c2fe8b..3c4528e0 100644 --- a/src/normal.cc +++ b/src/normal.cc @@ -18,9 +18,6 @@ #include "user_interface.hh" #include "window.hh" -#include -#include - namespace Kakoune { @@ -1400,8 +1397,6 @@ KeyMap keymap = { Key::PageUp, { "scroll one page up", scroll } }, { Key::PageDown, { "scroll one page down", scroll } }, - - { ctrl('c'), { "interupt", [](Context&, int) { killpg(getpgrp(), SIGINT); } } }, }; } diff --git a/src/remote.cc b/src/remote.cc index 9b8bab66..909f2c09 100644 --- a/src/remote.cc +++ b/src/remote.cc @@ -282,9 +282,9 @@ private: RemoteUI::RemoteUI(int socket) - : m_socket_watcher(socket, [this](FDWatcher&) { + : m_socket_watcher(socket, [this](FDWatcher&, EventMode mode) { if (m_input_callback) - m_input_callback(); + m_input_callback(mode); }) { write_debug("remote client connected: " + @@ -453,9 +453,9 @@ RemoteClient::RemoteClient(StringView session, std::unique_ptr&& msg.write(key); } - m_ui->set_input_callback([this]{ write_next_key(); }); + m_ui->set_input_callback([this](EventMode){ write_next_key(); }); - m_socket_watcher.reset(new FDWatcher{sock, [this](FDWatcher&){ process_available_messages(); }}); + m_socket_watcher.reset(new FDWatcher{sock, [this](FDWatcher&, EventMode){ process_available_messages(); }}); } void RemoteClient::process_available_messages() @@ -560,7 +560,10 @@ class Server::Accepter public: Accepter(int socket) : m_socket_watcher(socket, - [this](FDWatcher&) { handle_available_input(); }) + [this](FDWatcher&, EventMode mode) { + if (mode == EventMode::Normal) + handle_available_input(); + }) {} private: @@ -626,7 +629,7 @@ Server::Server(String session_name) if (listen(listen_sock, 4) == -1) throw runtime_error("unable to listen on socket "_str + addr.sun_path); - auto accepter = [this](FDWatcher& watcher) { + auto accepter = [this](FDWatcher& watcher, EventMode mode) { sockaddr_un client_addr; socklen_t client_addr_len = sizeof(sockaddr_un); int sock = accept(watcher.fd(), (sockaddr*) &client_addr, diff --git a/src/shell_manager.cc b/src/shell_manager.cc index 8ac683ef..ef12f50d 100644 --- a/src/shell_manager.cc +++ b/src/shell_manager.cc @@ -2,6 +2,7 @@ #include "context.hh" #include "debug.hh" +#include "event_manager.hh" #include "file.hh" #include @@ -59,25 +60,34 @@ String ShellManager::pipe(StringView input, write(write_pipe[1], input.data(), (int)input.length()); close(write_pipe[1]); - char buffer[1024]; - while (size_t size = read(read_pipe[0], buffer, 1024)) + String error; { - if (size == -1) - break; - output += String(buffer, buffer+size); - } - close(read_pipe[0]); + auto pipe_reader = [](String& output, bool& closed) { + return [&output, &closed](FDWatcher& watcher, EventMode) { + if (closed) + return; + const int fd = watcher.fd(); + char buffer[1024]; + size_t size = read(fd, buffer, 1024); + if (size <= 0) + { + close(fd); + closed = true; + } + output += String(buffer, buffer+size); + }; + }; - String errorout; - while (size_t size = read(error_pipe[0], buffer, 1024)) - { - if (size == -1) - break; - errorout += String(buffer, buffer+size); + bool stdout_closed = false, stderr_closed = false; + FDWatcher stdout_watcher{read_pipe[0], pipe_reader(output, stdout_closed)}; + FDWatcher stderr_watcher{error_pipe[0], pipe_reader(error, stderr_closed)}; + + while (not stdout_closed or not stderr_closed) + EventManager::instance().handle_next_events(EventMode::Urgent); } - close(error_pipe[0]); - if (not errorout.empty()) - write_debug("shell stderr: <<<\n" + errorout + ">>>"); + + if (not error.empty()) + write_debug("shell stderr: <<<\n" + error + ">>>"); waitpid(pid, exit_status, 0); if (exit_status) @@ -105,13 +115,7 @@ String ShellManager::pipe(StringView input, { auto& match = *it; - StringView name; - if (match[1].matched) - name = StringView(match[1].first, match[1].second); - else if (match[2].matched) - name = StringView(match[2].first, match[2].second); - else - kak_assert(false); + StringView name = StringView(match[1].first, match[1].second); kak_assert(name.length() > 0); auto local_var = env_vars.find(name); diff --git a/src/user_interface.hh b/src/user_interface.hh index 43d10377..b0884394 100644 --- a/src/user_interface.hh +++ b/src/user_interface.hh @@ -32,7 +32,9 @@ enum class InfoStyle MenuDoc }; -using InputCallback = std::function; +enum class EventMode; + +using InputCallback = std::function; class UserInterface : public SafeCountable {