#include "client.hh" #include "face_registry.hh" #include "context.hh" #include "buffer_manager.hh" #include "buffer_utils.hh" #include "file.hh" #include "remote.hh" #include "client_manager.hh" #include "command_manager.hh" #include "event_manager.hh" #include "user_interface.hh" #include "window.hh" #include #include namespace Kakoune { Client::Client(std::unique_ptr&& ui, std::unique_ptr&& window, SelectionList selections, EnvVarMap env_vars, String name) : m_ui{std::move(ui)}, m_window{std::move(window)}, m_input_handler{std::move(selections), Context::Flags::None, std::move(name)}, m_env_vars(env_vars) { m_window->set_client(this); context().set_client(*this); context().set_window(*m_window); m_window->set_dimensions(m_ui->dimensions()); m_window->options().register_watcher(*this); m_ui->set_ui_options(m_window->options()["ui_options"].get()); m_ui->set_input_callback([this](EventMode mode) { handle_available_input(mode); }); force_redraw(); } Client::~Client() { m_window->options().unregister_watcher(*this); m_window->set_client(nullptr); } Optional Client::get_next_key(EventMode mode) { 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 { while (Optional key = get_next_key(mode)) { if (*key == ctrl('c')) killpg(getpgrp(), SIGINT); else if (*key == Key::FocusIn) context().hooks().run_hook("FocusIn", context().name(), context()); else if (*key == Key::FocusOut) context().hooks().run_hook("FocusOut", context().name(), context()); else if (key->modifiers == Key::Modifiers::Resize) { m_window->set_dimensions(m_ui->dimensions()); force_redraw(); } else m_input_handler.handle_key(*key); } } catch (Kakoune::runtime_error& error) { context().print_status({ error.what().str(), get_face("Error") }); context().hooks().run_hook("RuntimeError", error.what(), context()); } } void Client::print_status(DisplayLine status_line) { m_status_line = std::move(status_line); m_ui_pending |= StatusLine; } DisplayLine Client::generate_mode_line() const { DisplayLine modeline; try { const String& modelinefmt = context().options()["modelinefmt"].get(); modeline = parse_display_line(expand(modelinefmt, context())); } catch (runtime_error& err) { write_to_debug_buffer(format("Error while parsing modelinefmt: {}", err.what())); modeline.push_back({ "modelinefmt error, see *debug* buffer", get_face("Error") }); } Face info_face = get_face("Information"); if (context().buffer().is_modified()) modeline.push_back({ "[+]", info_face }); if (m_input_handler.is_recording()) modeline.push_back({ format("[recording ({})]", m_input_handler.recording_reg()), info_face }); if (context().buffer().flags() & Buffer::Flags::New) modeline.push_back({ "[new file]", info_face }); if (context().user_hooks_disabled()) modeline.push_back({ "[no-hooks]", info_face }); if (context().buffer().flags() & Buffer::Flags::Fifo) modeline.push_back({ "[fifo]", info_face }); modeline.push_back({ " " }); for (auto& atom : m_input_handler.mode_line()) modeline.push_back(std::move(atom)); modeline.push_back({ format(" - {}@[{}]", context().name(), Server::instance().session()) }); return modeline; } void Client::change_buffer(Buffer& buffer) { if (m_buffer_reload_dialog_opened) close_buffer_reload_dialog(); m_last_buffer = &m_window->buffer(); auto& client_manager = ClientManager::instance(); m_window->options().unregister_watcher(*this); m_window->set_client(nullptr); client_manager.add_free_window(std::move(m_window), std::move(context().selections())); WindowAndSelections ws = client_manager.get_free_window(buffer); m_window = std::move(ws.window); m_window->set_client(this); m_window->options().register_watcher(*this); m_ui->set_ui_options(m_window->options()["ui_options"].get()); context().selections_write_only() = std::move(ws.selections); context().set_window(*m_window); m_window->set_dimensions(m_ui->dimensions()); force_redraw(); m_window->hooks().run_hook("WinDisplay", buffer.name(), context()); } static bool is_inline(InfoStyle style) { return style == InfoStyle::Inline or style == InfoStyle::InlineAbove or style == InfoStyle::InlineBelow; } void Client::redraw_ifn() { Window& window = context().window(); if (window.needs_redraw(context())) m_ui_pending |= Draw; DisplayLine mode_line = generate_mode_line(); if (mode_line.atoms() != m_mode_line.atoms()) { m_ui_pending |= StatusLine; m_mode_line = std::move(mode_line); } if (m_ui_pending == 0) return; if (m_ui_pending & Draw) { m_ui->draw(window.update_display_buffer(context()), get_face("Default"), get_face("BufferPadding")); if (not m_menu.items.empty() and m_menu.style == MenuStyle::Inline and m_menu.ui_anchor != window.display_position(m_menu.anchor)) m_ui_pending |= (MenuShow | MenuSelect); if (not m_info.content.empty() and is_inline(m_info.style) and m_info.ui_anchor != window.display_position(m_info.anchor)) m_ui_pending |= InfoShow; } if (m_ui_pending & MenuShow) { m_menu.ui_anchor = m_menu.style == MenuStyle::Inline ? window.display_position(m_menu.anchor) : CharCoord{}; m_ui->menu_show(m_menu.items, m_menu.ui_anchor, get_face("MenuForeground"), get_face("MenuBackground"), m_menu.style); } if (m_ui_pending & MenuSelect) m_ui->menu_select(m_menu.selected); if (m_ui_pending & MenuHide) m_ui->menu_hide(); if (m_ui_pending & InfoShow) { m_info.ui_anchor = is_inline(m_info.style) ? window.display_position(m_info.anchor) : CharCoord{}; m_ui->info_show(m_info.title, m_info.content, m_info.ui_anchor, get_face("Information"), m_info.style); } if (m_ui_pending & InfoHide) m_ui->info_hide(); if (m_ui_pending & StatusLine) m_ui->draw_status(m_status_line, m_mode_line, get_face("StatusLine")); m_ui->refresh(m_ui_pending | Refresh); m_ui_pending = 0; } void Client::force_redraw() { m_ui_pending |= Refresh | Draw | StatusLine | (m_menu.items.empty() ? MenuHide : MenuShow | MenuSelect) | (m_info.content.empty() ? InfoHide : InfoShow); } void Client::reload_buffer() { Buffer& buffer = context().buffer(); reload_file_buffer(buffer); context().print_status({ format("'{}' reloaded", buffer.display_name()), get_face("Information") }); } void Client::on_buffer_reload_key(Key key) { auto& buffer = context().buffer(); if (key == 'y' or key == Key::Return) reload_buffer(); else if (key == 'n' or key == Key::Escape) { // reread timestamp in case the file was modified again buffer.set_fs_timestamp(get_fs_timestamp(buffer.name())); print_status({ format("'{}' kept", buffer.display_name()), get_face("Information") }); } else { print_status({ format("'{}' is not a valid choice", key_to_str(key)), get_face("Error") }); m_input_handler.on_next_key(KeymapMode::None, [this](Key key, Context&){ on_buffer_reload_key(key); }); return; } for (auto& client : ClientManager::instance()) { if (&client->context().buffer() == &buffer and client->m_buffer_reload_dialog_opened) client->close_buffer_reload_dialog(); } } void Client::close_buffer_reload_dialog() { kak_assert(m_buffer_reload_dialog_opened); m_buffer_reload_dialog_opened = false; info_hide(); m_input_handler.reset_normal_mode(); } void Client::check_if_buffer_needs_reloading() { if (m_buffer_reload_dialog_opened) return; Buffer& buffer = context().buffer(); auto reload = context().options()["autoreload"].get(); if (not (buffer.flags() & Buffer::Flags::File) or reload == Autoreload::No) return; const String& filename = buffer.name(); timespec ts = get_fs_timestamp(filename); if (ts == InvalidTime or ts == buffer.fs_timestamp()) return; if (reload == Autoreload::Ask) { StringView bufname = buffer.display_name(); info_show(format("reload '{}' ?", bufname), format("'{}' was modified externally\n" "press or y to reload, or n to keep", bufname), {}, InfoStyle::Prompt); m_buffer_reload_dialog_opened = true; m_input_handler.on_next_key(KeymapMode::None, [this](Key key, Context&){ on_buffer_reload_key(key); }); } else reload_buffer(); } StringView Client::get_env_var(StringView name) const { auto it = m_env_vars.find(name); if (it == m_env_vars.end()) return {}; return it->value; } void Client::on_option_changed(const Option& option) { if (option.name() == "ui_options") { m_ui->set_ui_options(option.get()); m_ui_pending |= Draw; } } void Client::menu_show(Vector choices, ByteCoord anchor, MenuStyle style) { m_menu = Menu{ std::move(choices), anchor, {}, style, -1 }; m_ui_pending |= MenuShow; m_ui_pending &= ~MenuHide; } void Client::menu_select(int selected) { m_menu.selected = selected; m_ui_pending |= MenuSelect; m_ui_pending &= ~MenuHide; } void Client::menu_hide() { m_menu = Menu{}; m_ui_pending |= MenuHide; m_ui_pending &= ~(MenuShow | MenuSelect); } void Client::info_show(String title, String content, ByteCoord anchor, InfoStyle style) { m_info = Info{ std::move(title), std::move(content), anchor, {}, style }; m_ui_pending |= InfoShow; m_ui_pending &= ~InfoHide; } void Client::info_hide() { m_info = Info{}; m_ui_pending |= InfoHide; m_ui_pending &= ~InfoShow; } }