diff --git a/src/client.cc b/src/client.cc index 3a4f44ed..eb099b6f 100644 --- a/src/client.cc +++ b/src/client.cc @@ -4,12 +4,12 @@ #include "context.hh" #include "buffer_manager.hh" #include "buffer_utils.hh" -#include "user_interface.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 @@ -31,8 +31,11 @@ Client::Client(std::unique_ptr&& ui, 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); }); } Client::~Client() @@ -154,19 +157,43 @@ void Client::change_buffer(Buffer& buffer) context().selections_write_only() = std::move(ws.selections); context().set_window(*m_window); - m_window->set_dimensions(ui().dimensions()); + m_window->set_dimensions(m_ui->dimensions()); 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(); - UserInterface& ui = context().ui(); const bool needs_redraw = window.needs_redraw(context()); if (needs_redraw) - ui.draw(window.update_display_buffer(context()), get_face("Default")); + { + auto window_pos = window.position(); + m_ui->draw(window.update_display_buffer(context()), get_face("Default")); + + // window moved, reanchor eventual menu and info + if (window_pos != window.position()) + { + if (not m_menu.items.empty() and m_menu.style == MenuStyle::Inline) + { + m_ui->menu_show(m_menu.items, window.display_position(m_menu.anchor), + get_face("MenuForeground"), get_face("MenuBackground"), m_menu.style); + m_ui->menu_select(m_menu.selected); + } + if (not m_info.content.empty() and is_inline(m_info.style)) + m_ui->info_show(m_info.title, m_info.content, + window.display_position(m_info.anchor), + get_face("Information"), m_info.style); + } + } DisplayLine mode_line = generate_mode_line(); if (needs_redraw or @@ -176,10 +203,10 @@ void Client::redraw_ifn() m_mode_line = std::move(mode_line); m_status_line = m_pending_status_line; - ui.draw_status(m_status_line, m_mode_line, get_face("StatusLine")); + m_ui->draw_status(m_status_line, m_mode_line, get_face("StatusLine")); } - ui.refresh(); + m_ui->refresh(); } void Client::force_redraw() @@ -277,4 +304,36 @@ void Client::on_option_changed(const Option& option) m_ui->set_ui_options(option.get()); } +void Client::menu_show(Vector choices, ByteCoord anchor, MenuStyle style) +{ + m_menu = Menu{ std::move(choices), anchor, style, -1 }; + CharCoord ui_anchor = style == MenuStyle::Inline ? context().window().display_position(anchor) : CharCoord{}; + m_ui->menu_show(m_menu.items, ui_anchor, get_face("MenuForeground"), get_face("MenuBackground"), style); +} + +void Client::menu_select(int selected) +{ + m_menu.selected = selected; + m_ui->menu_select(selected); +} + +void Client::menu_hide() +{ + m_menu = Menu{}; + m_ui->menu_hide(); +} + +void Client::info_show(String title, String content, ByteCoord anchor, InfoStyle style) +{ + m_info = Info{ std::move(title), std::move(content), anchor, style }; + CharCoord ui_anchor = is_inline(style) ? context().window().display_position(anchor) : CharCoord{}; + m_ui->info_show(m_info.title, m_info.content, ui_anchor, get_face("Information"), style); +} + +void Client::info_hide() +{ + m_info = Info{}; + m_ui->info_hide(); +} + } diff --git a/src/client.hh b/src/client.hh index 7a0c8571..f36c8756 100644 --- a/src/client.hh +++ b/src/client.hh @@ -8,11 +8,11 @@ #include "utils.hh" #include "option_manager.hh" #include "enum.hh" +#include "user_interface.hh" namespace Kakoune { -class UserInterface; class Window; class String; struct Key; @@ -34,13 +34,20 @@ public: // handle all the keys currently available in the user interface void handle_available_input(EventMode mode); + void menu_show(Vector choices, ByteCoord anchor, MenuStyle style); + void menu_select(int selected); + void menu_hide(); + + void info_show(String title, String content, ByteCoord anchor, InfoStyle style); + void info_hide(); + void print_status(DisplayLine status_line); + CharCoord dimensions() const { return m_ui->dimensions(); } + void force_redraw(); void redraw_ifn(); - UserInterface& ui() const { return *m_ui; } - void check_if_buffer_needs_reloading(); Context& context() { return m_input_handler.context(); } @@ -78,6 +85,22 @@ private: DisplayLine m_pending_status_line; DisplayLine m_mode_line; + struct Menu + { + Vector items; + ByteCoord anchor; + MenuStyle style; + int selected; + } m_menu; + + struct Info + { + String title; + String content; + ByteCoord anchor; + InfoStyle style; + } m_info; + Vector m_pending_keys; bool m_buffer_reload_dialog_opened = false; diff --git a/src/client_manager.cc b/src/client_manager.cc index f742809d..675d2e66 100644 --- a/src/client_manager.cc +++ b/src/client_manager.cc @@ -6,7 +6,6 @@ #include "event_manager.hh" #include "face_registry.hh" #include "file.hh" -#include "user_interface.hh" #include "window.hh" namespace Kakoune @@ -59,10 +58,6 @@ Client* ClientManager::create_client(std::unique_ptr&& ui, return nullptr; } - client->ui().set_input_callback([client](EventMode mode) { - client->handle_available_input(mode); - }); - return client; } diff --git a/src/commands.cc b/src/commands.cc index abe95c1c..40b0b976 100644 --- a/src/commands.cc +++ b/src/commands.cc @@ -22,7 +22,6 @@ #include "remote.hh" #include "shell_manager.hh" #include "string.hh" -#include "user_interface.hh" #include "window.hh" #include @@ -1522,7 +1521,7 @@ const CommandDesc menu_cmd = { select_cmds.push_back(parser[i+2]); } - context.input_handler().menu(choices, + context.input_handler().menu(std::move(choices), [=](int choice, MenuEvent event, Context& context) { ScopedSetBool disable_history{context.history_disabled()}; @@ -1571,20 +1570,24 @@ const CommandDesc info_cmd = { CommandCompleter{}, [](const ParametersParser& parser, Context& context, const ShellContext&) { - context.ui().info_hide(); + if (not context.has_client()) + return; + + context.client().info_hide(); if (parser.positional_count() > 0) { InfoStyle style = InfoStyle::Prompt; - CharCoord pos; + ByteCoord pos; if (auto anchor = parser.get_switch("anchor")) { auto dot = find(*anchor, '.'); if (dot == anchor->end()) throw runtime_error("expected . for anchor"); - ByteCoord coord{str_to_int({anchor->begin(), dot})-1, + + pos = ByteCoord{str_to_int({anchor->begin(), dot})-1, str_to_int({dot+1, anchor->end()})-1}; - pos = context.window().display_position(coord); style = InfoStyle::Inline; + if (auto placement = parser.get_switch("placement")) { if (*placement == "above") @@ -1596,7 +1599,7 @@ const CommandDesc info_cmd = { } } auto title = parser.get_switch("title").value_or(StringView{}); - context.ui().info_show(title, parser[0], pos, get_face("Information"), style); + context.client().info_show(title.str(), parser[0], pos, style); } } }; diff --git a/src/context.cc b/src/context.cc index d45a6aa9..a79f7790 100644 --- a/src/context.cc +++ b/src/context.cc @@ -2,7 +2,6 @@ #include "alias_registry.hh" #include "client.hh" -#include "user_interface.hh" #include "register_manager.hh" #include "window.hh" @@ -49,13 +48,6 @@ Client& Context::client() const return *m_client; } -UserInterface& Context::ui() const -{ - if (not has_ui()) - throw runtime_error("no user interface in context"); - return client().ui(); -} - Scope& Context::scope() const { if (has_window()) @@ -75,8 +67,6 @@ void Context::set_window(Window& window) { kak_assert(&window.buffer() == &buffer()); m_window.reset(&window); - if (has_ui()) - m_window->set_dimensions(ui().dimensions()); } void Context::print_status(DisplayLine status) const diff --git a/src/context.hh b/src/context.hh index 2574abbb..0bbbaa98 100644 --- a/src/context.hh +++ b/src/context.hh @@ -107,9 +107,6 @@ public: InputHandler& input_handler() const; bool has_input_handler() const { return (bool)m_input_handler; } - UserInterface& ui() const; - bool has_ui() const { return has_client(); } - SelectionList& selections(); const SelectionList& selections() const; Vector selections_content() const; diff --git a/src/input_handler.cc b/src/input_handler.cc index f3f6f710..1f307f67 100644 --- a/src/input_handler.cc +++ b/src/input_handler.cc @@ -10,7 +10,6 @@ #include "regex.hh" #include "register_manager.hh" #include "unordered_map.hh" -#include "user_interface.hh" #include "utf8.hh" #include "window.hh" @@ -244,8 +243,8 @@ public: pop_mode(); context().print_status({}); - if (context().has_ui()) - context().ui().info_hide(); + if (context().has_client()) + context().client().info_hide(); do_restore_hooks = true; auto it = std::lower_bound(keymap.begin(), keymap.end(), key, @@ -254,9 +253,9 @@ public: if (it != keymap.end() and it->key == key) { auto autoinfo = context().options()["autoinfo"].get(); - if (autoinfo & AutoInfo::Normal and context().has_ui()) - context().ui().info_show(key_to_str(key), it->docstring, CharCoord{}, - get_face("Information"), InfoStyle::Prompt); + if (autoinfo & AutoInfo::Normal and context().has_client()) + context().client().info_show(key_to_str(key), it->docstring.str(), + {}, InfoStyle::Prompt); // reset m_params now to be reentrant NormalParams params = m_params; @@ -490,17 +489,16 @@ private: class Menu : public InputMode { public: - Menu(InputHandler& input_handler, ConstArrayView choices, + Menu(InputHandler& input_handler, Vector choices, MenuCallback callback) : InputMode(input_handler), m_callback(callback), m_choices(choices.begin(), choices.end()), m_selected(m_choices.begin()) { - if (not context().has_ui()) + if (not context().has_client()) return; - context().ui().menu_show(choices, CharCoord{}, get_face("MenuForeground"), - get_face("MenuBackground"), MenuStyle::Prompt); - context().ui().menu_select(0); + context().client().menu_show(std::move(choices), {}, MenuStyle::Prompt); + context().client().menu_select(0); } void on_key(Key key) override @@ -517,8 +515,8 @@ public: if (key == ctrl('m')) { - if (context().has_ui()) - context().ui().menu_hide(); + if (context().has_client()) + context().client().menu_hide(); context().print_status(DisplayLine{}); pop_mode(); int selected = m_selected - m_choices.begin(); @@ -536,8 +534,8 @@ public: } else { - if (context().has_ui()) - context().ui().menu_hide(); + if (context().has_client()) + context().client().menu_hide(); pop_mode(); int selected = m_selected - m_choices.begin(); m_callback(selected, MenuEvent::Abort, context()); @@ -576,10 +574,10 @@ public: select(it); } - if (m_edit_filter and context().has_ui()) + if (m_edit_filter and context().has_client()) { auto prompt = "filter:"_str; - auto width = context().ui().dimensions().column - prompt.char_length(); + auto width = context().client().dimensions().column - prompt.char_length(); auto display_line = m_filter_editor.build_display_line(width); display_line.insert(display_line.begin(), { prompt, get_face("Prompt") }); context().print_status(display_line); @@ -604,8 +602,8 @@ private: { m_selected = it; int selected = m_selected - m_choices.begin(); - if (context().has_ui()) - context().ui().menu_select(selected); + if (context().has_client()) + context().client().menu_select(selected); m_callback(selected, MenuEvent::Select, context()); } @@ -671,8 +669,8 @@ public: if (not context().history_disabled()) history_push(history, line); context().print_status(DisplayLine{}); - if (context().has_ui()) - context().ui().menu_hide(); + if (context().has_client()) + context().client().menu_hide(); pop_mode(); // call callback after pop_mode so that callback // may change the mode @@ -684,8 +682,8 @@ public: if (not context().history_disabled()) history_push(history, line); context().print_status(DisplayLine{}); - if (context().has_ui()) - context().ui().menu_hide(); + if (context().has_client()) + context().client().menu_hide(); pop_mode(); m_callback(line, PromptEvent::Abort, context()); return; @@ -811,8 +809,8 @@ public: } const String& completion = candidates[m_current_completion]; - if (context().has_ui()) - context().ui().menu_select(m_current_completion); + if (context().has_client()) + context().client().menu_select(m_current_completion); m_line_editor.insert_from(line.char_count_to(m_completions.start), completion); @@ -872,15 +870,12 @@ private: const String& line = m_line_editor.line(); m_completions = m_completer(context(), flags, line, line.byte_count_to(m_line_editor.cursor_pos())); - if (context().has_ui() and not m_completions.candidates.empty()) + if (context().has_client() and not m_completions.candidates.empty()) { Vector items; for (auto& candidate : m_completions.candidates) items.push_back({ candidate, {} }); - context().ui().menu_show(items, CharCoord{}, - get_face("MenuForeground"), - get_face("MenuBackground"), - MenuStyle::Prompt); + context().client().menu_show(items, {}, MenuStyle::Prompt); } } catch (runtime_error&) {} } @@ -889,16 +884,16 @@ private: { m_current_completion = -1; m_completions.candidates.clear(); - if (context().has_ui()) - context().ui().menu_hide(); + if (context().has_client()) + context().client().menu_hide(); } void display() { - if (not context().has_ui()) + if (not context().has_client()) return; - auto width = context().ui().dimensions().column - m_prompt.char_length(); + auto width = context().client().dimensions().column - m_prompt.char_length(); auto display_line = m_line_editor.build_display_line(width); display_line.insert(display_line.begin(), { m_prompt, m_prompt_face }); context().print_status(display_line); @@ -1350,9 +1345,9 @@ void InputHandler::set_prompt_face(Face prompt_face) prompt->set_prompt_face(prompt_face); } -void InputHandler::menu(ConstArrayView choices, MenuCallback callback) +void InputHandler::menu(Vector choices, MenuCallback callback) { - push_mode(new InputModes::Menu(*this, choices, callback)); + push_mode(new InputModes::Menu(*this, std::move(choices), callback)); } void InputHandler::on_next_key(KeymapMode keymap_mode, KeyCallback callback) @@ -1420,12 +1415,17 @@ DisplayLine InputHandler::mode_line() const bool show_auto_info_ifn(StringView title, StringView info, AutoInfo mask, const Context& context) { if (not (context.options()["autoinfo"].get() & mask) or - not context.has_ui()) + not context.has_client()) return false; - Face face = get_face("Information"); - context.ui().info_show(title, info, CharCoord{}, face, InfoStyle::Prompt); + context.client().info_show(title.str(), info.str(), {}, InfoStyle::Prompt); return true; } +void hide_auto_info_ifn(const Context& context, bool hide) +{ + if (hide) + context.client().info_hide(); +} + } diff --git a/src/input_handler.hh b/src/input_handler.hh index e29114ab..8719ca15 100644 --- a/src/input_handler.hh +++ b/src/input_handler.hh @@ -8,8 +8,8 @@ #include "keys.hh" #include "string.hh" #include "utils.hh" -#include "user_interface.hh" #include "safe_ptr.hh" +#include "display_buffer.hh" namespace Kakoune { @@ -32,7 +32,6 @@ using PromptCallback = std::function; using KeyCallback = std::function; class InputMode; -class DisplayLine; enum class InsertMode : unsigned; enum class KeymapMode : char; @@ -62,7 +61,7 @@ public: // abort or validation with corresponding MenuEvent value // returns to normal mode after validation if callback does // not change the mode itself - void menu(ConstArrayView choices, MenuCallback callback); + void menu(Vector choices, MenuCallback callback); // execute callback on next keypress and returns to normal mode // if callback does not change the mode itself @@ -124,6 +123,7 @@ constexpr Array, 3> enum_desc(AutoInfo) } bool show_auto_info_ifn(StringView title, StringView info, AutoInfo mask, const Context& context); +void hide_auto_info_ifn(const Context& context, bool hide); template void on_next_key_with_autoinfo(const Context& context, KeymapMode keymap_mode, Cmd cmd, @@ -132,11 +132,11 @@ void on_next_key_with_autoinfo(const Context& context, KeymapMode keymap_mode, C const bool hide = show_auto_info_ifn(title, info, AutoInfo::OnKey, context); context.input_handler().on_next_key( keymap_mode, [hide,cmd](Key key, Context& context) mutable { - if (hide) - context.ui().info_hide(); + hide_auto_info_ifn(context, hide); cmd(key, context); }); } + } #endif // input_handler_hh_INCLUDED diff --git a/src/insert_completer.cc b/src/insert_completer.cc index 494fcd30..5a4464bb 100644 --- a/src/insert_completer.cc +++ b/src/insert_completer.cc @@ -2,12 +2,12 @@ #include "buffer_manager.hh" #include "buffer_utils.hh" +#include "client.hh" #include "context.hh" #include "display_buffer.hh" #include "face_registry.hh" #include "file.hh" #include "regex.hh" -#include "user_interface.hh" #include "window.hh" #include "word_db.hh" #include "utf8_iterator.hh" @@ -364,14 +364,14 @@ void InsertCompleter::select(int offset, Vector& keystrokes) m_completions.end = cursor_pos; m_completions.begin = buffer.advance(cursor_pos, -candidate.completion.length()); m_completions.timestamp = buffer.timestamp(); - if (m_context.has_ui()) + if (m_context.has_client()) { - m_context.ui().menu_select(m_current_candidate); + m_context.client().menu_select(m_current_candidate); if (not candidate.docstring.empty()) - m_context.ui().info_show(candidate.completion, candidate.docstring, CharCoord{}, - get_face("Information"), InfoStyle::MenuDoc); + m_context.client().info_show(candidate.completion, candidate.docstring, + {}, InfoStyle::MenuDoc); else - m_context.ui().info_hide(); + m_context.client().info_hide(); } for (auto i = 0_byte; i < prefix_len; ++i) @@ -395,10 +395,10 @@ void InsertCompleter::reset() { m_completions = InsertCompletion{}; m_explicit_completer = nullptr; - if (m_context.has_ui()) + if (m_context.has_client()) { - m_context.ui().menu_hide(); - m_context.ui().info_hide(); + m_context.client().menu_hide(); + m_context.client().info_hide(); } } @@ -435,19 +435,16 @@ bool InsertCompleter::setup_ifn() void InsertCompleter::menu_show() { - if (not m_context.has_ui()) + if (not m_context.has_client()) return; - CharCoord menu_pos = m_context.window().display_position(m_completions.begin); Vector menu_entries; for (auto& candidate : m_completions.candidates) menu_entries.push_back(candidate.menu_entry); - m_context.ui().menu_show(menu_entries, menu_pos, - get_face("MenuForeground"), - get_face("MenuBackground"), - MenuStyle::Inline); - m_context.ui().menu_select(m_current_candidate); + m_context.client().menu_show(std::move(menu_entries), m_completions.begin, + MenuStyle::Inline); + m_context.client().menu_select(m_current_candidate); } void InsertCompleter::on_option_changed(const Option& opt) diff --git a/src/main.cc b/src/main.cc index 1640c504..4c725a3b 100644 --- a/src/main.cc +++ b/src/main.cc @@ -265,6 +265,7 @@ struct convert_to_client_mode }; static Client* local_client = nullptr; +static UserInterface* local_ui = nullptr; static bool convert_to_client_pending = false; pid_t fork_server_to_background() @@ -309,6 +310,8 @@ std::unique_ptr create_local_ui(bool dummy_ui) { LocalUI() { + kak_assert(not local_ui); + local_ui = this; m_old_sighup = set_signal_handler(SIGHUP, [](int) { ClientManager::instance().remove_client(*local_client, false); }); @@ -318,7 +321,7 @@ std::unique_ptr create_local_ui(bool dummy_ui) *ClientManager::instance().begin() == local_client) { // Suspend normally if we are the only client - auto current = set_signal_handler(SIGTSTP, static_cast(local_client->ui()).m_old_sigtstp); + auto current = set_signal_handler(SIGTSTP, static_cast(local_ui)->m_old_sigtstp); sigset_t unblock_sigtstp, old_mask; sigemptyset(&unblock_sigtstp); @@ -341,6 +344,7 @@ std::unique_ptr create_local_ui(bool dummy_ui) set_signal_handler(SIGHUP, m_old_sighup); set_signal_handler(SIGTSTP, m_old_sigtstp); local_client = nullptr; + local_ui = nullptr; if (not convert_to_client_pending and not ClientManager::instance().empty()) { diff --git a/src/normal.cc b/src/normal.cc index 0702ca62..eaa33159 100644 --- a/src/normal.cc +++ b/src/normal.cc @@ -16,7 +16,6 @@ #include "selectors.hh" #include "shell_manager.hh" #include "string.hh" -#include "user_interface.hh" #include "window.hh" namespace Kakoune @@ -346,20 +345,19 @@ void command(Context& context, NormalParams params) return CommandManager::instance().complete(context, flags, cmd_line, pos); }, [params](StringView cmdline, PromptEvent event, Context& context) { - if (context.has_ui()) + if (context.has_client()) { - context.ui().info_hide(); + context.client().info_hide(); auto autoinfo = context.options()["autoinfo"].get(); if (event == PromptEvent::Change and autoinfo & AutoInfo::Command) { - Face face = get_face("Information"); if (cmdline.length() == 1 and is_horizontal_blank(cmdline[0_byte])) - context.ui().info_show("prompt", "commands preceded by a blank wont be saved to history", - CharCoord{}, face, InfoStyle::Prompt); + context.client().info_show("prompt", "commands preceded by a blank wont be saved to history", + {}, InfoStyle::Prompt); auto info = CommandManager::instance().command_info(context, cmdline); if (not info.first.empty() and not info.second.empty()) - context.ui().info_show(info.first, info.second, CharCoord{}, face, InfoStyle::Prompt); + context.client().info_show(info.first, info.second, {}, InfoStyle::Prompt); } } if (event == PromptEvent::Validate) @@ -586,8 +584,8 @@ void regex_prompt(Context& context, const String prompt, T func) [=](StringView str, PromptEvent event, Context& context) mutable { try { - if (event != PromptEvent::Change and context.has_ui()) - context.ui().info_hide(); + if (event != PromptEvent::Change and context.has_client()) + context.client().info_hide(); const bool incsearch = context.options()["incsearch"].get(); if (incsearch) diff --git a/src/window.cc b/src/window.cc index 4ebd9e2c..ef4d3270 100644 --- a/src/window.cc +++ b/src/window.cc @@ -5,7 +5,7 @@ #include "highlighter.hh" #include "hook_manager.hh" #include "input_handler.hh" -#include "user_interface.hh" +#include "client.hh" #include #include @@ -79,7 +79,7 @@ Window::Setup Window::build_setup(const Context& context) const selections.push_back({sel.cursor(), sel.anchor()}); return { m_position, - context.ui().dimensions(), + context.client().dimensions(), context.buffer().timestamp(), context.selections().main_index(), std::move(selections) }; @@ -90,7 +90,7 @@ bool Window::needs_redraw(const Context& context) const auto& selections = context.selections(); if (m_position != m_last_setup.position or - context.ui().dimensions() != m_last_setup.dimensions or + context.client().dimensions() != m_last_setup.dimensions or context.buffer().timestamp() != m_last_setup.timestamp or selections.main_index() != m_last_setup.main_selection or selections.size() != m_last_setup.selections.size()) @@ -111,7 +111,7 @@ const DisplayBuffer& Window::update_display_buffer(const Context& context) DisplayBuffer::LineList& lines = m_display_buffer.lines(); lines.clear(); - m_dimensions = context.ui().dimensions(); + m_dimensions = context.client().dimensions(); if (m_dimensions == CharCoord{0,0}) return m_display_buffer;