diff --git a/doc/json_ui.asciidoc b/doc/json_ui.asciidoc new file mode 100644 index 00000000..c57deb0b --- /dev/null +++ b/doc/json_ui.asciidoc @@ -0,0 +1,46 @@ +Json-Rpc user interface +======================= + +Kakoune user interfaces can be implemented through the json-rpc 2.0 protocol. + +By launching kakoune in client mode with the `-j` option, the launched client +will write json-rpc requests on stdout and read json-rpc requests on stdin. +Errors will be reported on stderr (not in json format). + +Kakoune requests are always using positional parameters, never named, and +Kakoune wont be able to parse named parameters in requests. + +Here are the data structures used: + +* Color: a string, either a named color, or #rrggbb +* Attribute: one of {exclusive, underline, reverse, blink, bold, dim, italic} +* Face { Color fg; Color bg; Array attributes; } +* Atom { Face face; String contents; } +* Line : Array of Atom +* Coord { int line; int column } + +Here are the requests that can be written by the json ui on stdout: + +* draw(Array lines, Face default_face) +* draw_status(Line status_line, Line mode_line, + Face default_face) +* menu_show(Array items, Coord anchor, Face fg, Face bg, + String style) + style can be: + - prompt: display the menu as a prompt menu (anchor is ignored) + - inline: display the menu next to (above or below) the anchor coordinate +* menu_select(int selected) +* menu_hide() +* info_show(String title, String content, Coord anchor, Face face, String style) + style can be: + - prompt: display the info as a prompt info (anchor is ignored) + - inline: display the info next to (above or below) the anchor coordinate + - inlineAbove: display the info next to (above) the anchor coordinate + - inlineBelow: display the info next to (below) the anchor coordinate + - menuDoc: display the info next to the menu, as a documentation for it +* info_hide() + +The requests that the json ui can interpret on stdin are: + +* keys(String key1, String key2...): keystrokes +* resize(int x, int y): notify ui resize diff --git a/src/alias_registry.cc b/src/alias_registry.cc index 6e92c006..690cde54 100644 --- a/src/alias_registry.cc +++ b/src/alias_registry.cc @@ -57,7 +57,7 @@ Vector> AliasRegistry::flatten_aliases() const res = m_parent->flatten_aliases(); for (auto& alias : m_aliases) { - if (not contains(transformed(res, [](const std::pair& val) { return val.first; }), alias.key)) + if (not contains(res | transform([](const AliasDesc& val) { return val.first; }), alias.key)) res.emplace_back(alias.key, alias.value); } return res; diff --git a/src/alias_registry.hh b/src/alias_registry.hh index 7f688e08..5ff093bb 100644 --- a/src/alias_registry.hh +++ b/src/alias_registry.hh @@ -20,7 +20,8 @@ public: using iterator = AliasMap::const_iterator; Vector aliases_for(StringView command) const; - Vector> flatten_aliases() const; + using AliasDesc = std::pair; + Vector flatten_aliases() const; private: friend class Scope; diff --git a/src/buffer.cc b/src/buffer.cc index e140f333..c0e25f0a 100644 --- a/src/buffer.cc +++ b/src/buffer.cc @@ -304,7 +304,7 @@ bool Buffer::undo() --m_history_cursor; - for (const Modification& modification : reversed(*m_history_cursor)) + for (const Modification& modification : *m_history_cursor | reverse()) apply_modification(modification.inverse()); return true; } diff --git a/src/buffer.inl.hh b/src/buffer.inl.hh index d6af3d4a..8397be33 100644 --- a/src/buffer.inl.hh +++ b/src/buffer.inl.hh @@ -104,7 +104,8 @@ inline ByteCoord Buffer::back_coord() const inline ByteCoord Buffer::end_coord() const { - return { line_count() - 1, m_lines.back().length() }; + return m_lines.empty() ? + ByteCoord{0,0} : ByteCoord{ line_count() - 1, m_lines.back().length() }; } inline BufferIterator::BufferIterator(const Buffer& buffer, ByteCoord coord) diff --git a/src/client.cc b/src/client.cc index 7dd22f88..cf8b3e71 100644 --- a/src/client.cc +++ b/src/client.cc @@ -36,7 +36,7 @@ Client::Client(std::unique_ptr&& ui, m_ui->set_ui_options(m_window->options()["ui_options"].get()); m_ui->set_input_callback([this](EventMode mode) { handle_available_input(mode); }); - m_ui_dirty = true; + force_redraw(); } Client::~Client() @@ -101,7 +101,8 @@ void Client::handle_available_input(EventMode mode) void Client::print_status(DisplayLine status_line) { - m_pending_status_line = std::move(status_line); + m_status_line = std::move(status_line); + m_ui_pending |= StatusLine; } DisplayLine Client::generate_mode_line() const @@ -159,7 +160,7 @@ void Client::change_buffer(Buffer& buffer) context().selections_write_only() = std::move(ws.selections); context().set_window(*m_window); m_window->set_dimensions(m_ui->dimensions()); - m_ui_dirty = true; + force_redraw(); m_window->hooks().run_hook("WinDisplay", buffer.name(), context()); } @@ -174,9 +175,20 @@ static bool is_inline(InfoStyle style) void Client::redraw_ifn() { Window& window = context().window(); + if (window.needs_redraw(context())) + m_ui_pending |= Draw; - const bool needs_redraw = window.needs_redraw(context()); - if (needs_redraw) + 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) { auto window_pos = window.position(); m_ui->draw(window.update_display_buffer(context()), get_face("Default")); @@ -185,42 +197,47 @@ void Client::redraw_ifn() 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); - } + m_ui_pending |= (MenuShow | MenuSelect); 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); + m_ui_pending |= InfoShow; } - m_ui_dirty = true; } - DisplayLine mode_line = generate_mode_line(); - if (needs_redraw or - m_status_line.atoms() != m_pending_status_line.atoms() or - mode_line.atoms() != m_mode_line.atoms()) + if (m_ui_pending & MenuShow) { - m_mode_line = std::move(mode_line); - m_status_line = m_pending_status_line; + const CharCoord ui_anchor = m_menu.style == MenuStyle::Inline ? + window.display_position(m_menu.anchor) : CharCoord{}; + m_ui->menu_show(m_menu.items, 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) + { + const CharCoord ui_anchor = is_inline(m_info.style) ? + window.display_position(m_info.anchor) : CharCoord{}; + m_ui->info_show(m_info.title, m_info.content, 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_dirty = true; - } - if (m_ui_dirty) - { - m_ui_dirty = false; - m_ui->refresh(); - } + m_ui->refresh(m_ui_pending | Refresh); + m_ui_pending = 0; } void Client::force_redraw() { - if (m_window) - m_window->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() @@ -310,45 +327,43 @@ void Client::on_option_changed(const Option& option) if (option.name() == "ui_options") { m_ui->set_ui_options(option.get()); - m_ui_dirty = true; + m_ui_pending |= Draw; } } 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); - m_ui_dirty = true; + m_ui_pending |= MenuShow; + m_ui_pending &= ~MenuHide; } void Client::menu_select(int selected) { m_menu.selected = selected; - m_ui->menu_select(selected); - m_ui_dirty = true; + m_ui_pending |= MenuSelect; + m_ui_pending &= ~MenuHide; } void Client::menu_hide() { m_menu = Menu{}; - m_ui->menu_hide(); - m_ui_dirty = true; + 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 }; - 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); - m_ui_dirty = true; + m_ui_pending |= InfoShow; + m_ui_pending &= ~InfoHide; } void Client::info_hide() { m_info = Info{}; - m_ui->info_hide(); - m_ui_dirty = true; + m_ui_pending |= InfoHide; + m_ui_pending &= ~InfoShow; } } diff --git a/src/client.hh b/src/client.hh index b5d13b7d..3fa36907 100644 --- a/src/client.hh +++ b/src/client.hh @@ -74,7 +74,6 @@ private: DisplayLine generate_mode_line() const; - bool m_ui_dirty = false; std::unique_ptr m_ui; std::unique_ptr m_window; @@ -83,9 +82,21 @@ private: InputHandler m_input_handler; DisplayLine m_status_line; - DisplayLine m_pending_status_line; DisplayLine m_mode_line; + enum PendingUI : int + { + MenuShow = 1 << 0, + MenuSelect = 1 << 1, + MenuHide = 1 << 2, + InfoShow = 1 << 3, + InfoHide = 1 << 4, + StatusLine = 1 << 5, + Draw = 1 << 6, + Refresh = 1 << 7, + }; + int m_ui_pending = 0; + struct Menu { Vector items; diff --git a/src/client_manager.cc b/src/client_manager.cc index b625fdc5..aff88d93 100644 --- a/src/client_manager.cc +++ b/src/client_manager.cc @@ -87,7 +87,7 @@ void ClientManager::remove_client(Client& client, bool graceful) WindowAndSelections ClientManager::get_free_window(Buffer& buffer) { - auto it = find_if(reversed(m_free_windows), + auto it = find_if(m_free_windows | reverse(), [&](const WindowAndSelections& ws) { return &ws.window->buffer() == &buffer; }); @@ -177,10 +177,8 @@ void ClientManager::redraw_clients() const CandidateList ClientManager::complete_client_name(StringView prefix, ByteCount cursor_pos) const { - auto c = transformed(m_clients, - [](const std::unique_ptr& c) -> const String& - { return c->context().name(); }); - + auto c = m_clients | transform([](const std::unique_ptr& c) -> const String& + { return c->context().name(); }); return complete(prefix, cursor_pos, c); } diff --git a/src/command_manager.cc b/src/command_manager.cc index 8dfb5181..4902f068 100644 --- a/src/command_manager.cc +++ b/src/command_manager.cc @@ -512,6 +512,20 @@ CommandInfo CommandManager::command_info(const Context& context, StringView comm return res; } +Completions CommandManager::complete_command_name(const Context& context, + StringView query) const +{ + auto candidates = Kakoune::complete( + query, query.length(), concatenated( + m_commands + | filter([](const CommandMap::value_type& cmd) { return not (cmd.second.flags & CommandFlags::Hidden); }) + | transform([](const CommandMap::value_type& cmd) { return StringView{cmd.first}; }), + context.aliases().flatten_aliases() + | transform([](AliasRegistry::AliasDesc alias) { return alias.first; }))); + + return {0, query.length(), std::move(candidates)}; +} + Completions CommandManager::complete(const Context& context, CompletionFlags flags, StringView command_line, @@ -533,33 +547,16 @@ Completions CommandManager::complete(const Context& context, } } + const bool is_last_token = tok_idx == tokens.size(); // command name completion if (tokens.empty() or - (tok_idx == cmd_idx and (tok_idx == tokens.size() or + (tok_idx == cmd_idx and (is_last_token or tokens[tok_idx].type() == Token::Type::Raw or tokens[tok_idx].type() == Token::Type::RawQuoted))) { - const bool is_end_token = tok_idx == tokens.size(); - ByteCount cmd_start = is_end_token ? cursor_pos - : tokens[tok_idx].begin(); - Completions result(cmd_start, cursor_pos); - StringView prefix = command_line.substr(cmd_start, - cursor_pos - cmd_start); - - for (auto& command : m_commands) - { - if (command.second.flags & CommandFlags::Hidden) - continue; - if (prefix_match(command.first, prefix)) - result.candidates.push_back(command.first); - } - for (auto& alias : context.aliases().flatten_aliases()) - { - if (prefix_match(alias.first, prefix)) - result.candidates.push_back(alias.first.str()); - } - std::sort(result.candidates.begin(), result.candidates.end()); - return result; + auto cmd_start = is_last_token ? cursor_pos : tokens[tok_idx].begin(); + StringView query = command_line.substr(cmd_start, cursor_pos - cmd_start); + return offset_pos(complete_command_name(context, query), cmd_start); } kak_assert(not tokens.empty()); @@ -628,18 +625,7 @@ Completions CommandManager::complete(const Context& context, StringView prefix = params[token_to_complete].substr(0, pos_in_token); if (token_to_complete == 0) - { - CandidateList candidates; - for (auto& command : m_commands) - { - if (command.second.flags & CommandFlags::Hidden) - continue; - if (prefix_match(command.first, prefix)) - candidates.push_back(command.first); - } - std::sort(candidates.begin(), candidates.end()); - return {0, pos_in_token, std::move(candidates)}; - } + return complete_command_name(context, prefix); else { const String& command_name = params[0]; diff --git a/src/command_manager.hh b/src/command_manager.hh index dbc87e81..090ed0fb 100644 --- a/src/command_manager.hh +++ b/src/command_manager.hh @@ -130,6 +130,8 @@ private: const ShellContext& shell_context, CharCoord pos) const; + Completions complete_command_name(const Context& context, StringView query) const; + struct CommandDescriptor { Command command; diff --git a/src/commands.cc b/src/commands.cc index 3a8d6132..b376f081 100644 --- a/src/commands.cc +++ b/src/commands.cc @@ -543,7 +543,7 @@ Completions add_highlighter_completer( if (token_to_complete == 1 and params[0] == "-group") return complete_highlighter(context, params[1], pos_in_token, true); else if (token_to_complete == 0 or (token_to_complete == 2 and params[0] == "-group")) - return { 0_byte, arg.length(), complete(arg, pos_in_token, transformed(HighlighterRegistry::instance(), HighlighterRegistry::get_id)) }; + return { 0_byte, arg.length(), complete(arg, pos_in_token, HighlighterRegistry::instance() | transform(HighlighterRegistry::get_id)) }; return Completions{}; } @@ -626,7 +626,7 @@ const CommandDesc rm_highlighter_cmd = { [](const ParametersParser& parser, Context& context, const ShellContext&) { StringView path = parser[0]; - auto sep_it = find(reversed(path), '/'); + auto sep_it = find(path | reverse(), '/'); auto& group = sep_it != path.rend() ? get_highlighter(context, {path.begin(), sep_it.base()}) : context.window().highlighters(); diff --git a/src/containers.hh b/src/containers.hh index 6bf3bea6..9597a25b 100644 --- a/src/containers.hh +++ b/src/containers.hh @@ -8,11 +8,21 @@ namespace Kakoune { +template +struct ContainerView { Factory factory; }; + +template +auto operator| (Container&& container, ContainerView view) -> + decltype(view.factory(std::forward(container))) +{ + return view.factory(std::forward(container)); +} + template -struct ReversedContainer +struct ReverseView { using iterator = decltype(std::declval().rbegin()); - ReversedContainer(Container& container) : m_container(container) {} + ReverseView(Container& container) : m_container(container) {} iterator begin() { return m_container.rbegin(); } iterator end() { return m_container.rend(); } @@ -21,122 +31,196 @@ private: Container& m_container; }; -template -ReversedContainer reversed(Container&& container) +struct ReverseFactory { - return ReversedContainer(container); -} - -template -struct FilteredIterator : std::iterator -{ - FilteredIterator(Filter filter, Iterator it, Iterator end) - : m_it(std::move(it)), m_end(std::move(end)), m_filter(std::move(filter)) + template + ReverseView operator()(Container&& container) const { - do_filter(); + return {container}; } - - auto operator*() -> decltype(*std::declval()) { return *m_it; } - FilteredIterator& operator++() { ++m_it; do_filter(); return *this; } - FilteredIterator operator++(int) { auto copy = *this; ++(*this); return copy; } - - friend bool operator==(const FilteredIterator& lhs, const FilteredIterator& rhs) - { - return lhs.m_it == rhs.m_it; - } - - friend bool operator!=(const FilteredIterator& lhs, const FilteredIterator& rhs) - { - return not (lhs == rhs); - } - - Iterator base() const { return m_it; } - -private: - void do_filter() - { - while (m_it != m_end and not m_filter(*m_it)) - ++m_it; - } - - Iterator m_it; - Iterator m_end; - Filter m_filter; }; +inline ContainerView reverse() { return {}; } + template -struct FilteredContainer +struct FilterView { - using iterator = FilteredIterator())), Filter>; - FilteredContainer(Container& container, Filter filter) + using ContainerIt = decltype(begin(std::declval())); + + struct Iterator : std::iterator + { + Iterator(const FilterView& view, ContainerIt it, ContainerIt end) + : m_it{std::move(it)}, m_end{std::move(end)}, m_view{view} + { + do_filter(); + } + + auto operator*() -> decltype(*std::declval()) { return *m_it; } + Iterator& operator++() { ++m_it; do_filter(); return *this; } + Iterator operator++(int) { auto copy = *this; ++(*this); return copy; } + + friend bool operator==(const Iterator& lhs, const Iterator& rhs) + { + return lhs.m_it == rhs.m_it; + } + + friend bool operator!=(const Iterator& lhs, const Iterator& rhs) + { + return not (lhs == rhs); + } + + const ContainerIt& base() const { return m_it; } + + private: + void do_filter() + { + while (m_it != m_end and not m_view.m_filter(*m_it)) + ++m_it; + } + + ContainerIt m_it; + ContainerIt m_end; + const FilterView& m_view; + }; + + FilterView(Container& container, Filter filter) : m_container(container), m_filter(std::move(filter)) {} - iterator begin() const { return iterator(m_filter, m_container.begin(), m_container.end()); } - iterator end() const { return iterator(m_filter, m_container.end(), m_container.end()); } + Iterator begin() const { return {*this, m_container.begin(), m_container.end()}; } + Iterator end() const { return {*this, m_container.end(), m_container.end()}; } private: Container& m_container; Filter m_filter; }; -template -FilteredContainer filtered(Container&& container, Filter filter) +template +struct FilterFactory { - return FilteredContainer(container, std::move(filter)); -} + template + FilterView operator()(Container&& container) const { return {container, std::move(m_filter)}; } + + Filter m_filter; +}; + +template +inline ContainerView> filter(Filter f) { return {{std::move(f)}}; } template using TransformedResult = decltype(std::declval()(*std::declval())); -template -struct TransformedIterator : std::iterator>::type> -{ - TransformedIterator(Transform transform, Iterator it) - : m_it(std::move(it)), m_transform(std::move(transform)) {} - - auto operator*() -> TransformedResult { return m_transform(*m_it); } - TransformedIterator& operator++() { ++m_it; return *this; } - TransformedIterator operator++(int) { auto copy = *this; ++m_it; return copy; } - - friend bool operator==(const TransformedIterator& lhs, const TransformedIterator& rhs) - { - return lhs.m_it == rhs.m_it; - } - - friend bool operator!=(const TransformedIterator& lhs, const TransformedIterator& rhs) - { - return not (lhs == rhs); - } - - Iterator base() const { return m_it; } - -private: - Iterator m_it; - Transform m_transform; -}; - - template -struct TransformedContainer +struct TransformView { - using iterator = TransformedIterator())), Transform>; - TransformedContainer(Container& container, Transform transform) + using ContainerIt = decltype(begin(std::declval())); + + struct Iterator : std::iterator>::type> + { + Iterator(const TransformView& view, ContainerIt it) + : m_it{std::move(it)}, m_view{view} {} + + auto operator*() -> TransformedResult { return m_view.m_transform(*m_it); } + Iterator& operator++() { ++m_it; return *this; } + Iterator operator++(int) { auto copy = *this; ++m_it; return copy; } + + friend bool operator==(const Iterator& lhs, const Iterator& rhs) + { + return lhs.m_it == rhs.m_it; + } + + friend bool operator!=(const Iterator& lhs, const Iterator& rhs) + { + return not (lhs == rhs); + } + + ContainerIt base() const { return m_it; } + + private: + ContainerIt m_it; + const TransformView& m_view; + }; + + TransformView(Container& container, Transform transform) : m_container(container), m_transform(std::move(transform)) {} - iterator begin() const { return iterator(m_transform, m_container.begin()); } - iterator end() const { return iterator(m_transform, m_container.end()); } + Iterator begin() const { return {*this, m_container.begin()}; } + Iterator end() const { return {*this, m_container.end()}; } private: Container& m_container; Transform m_transform; }; -template -TransformedContainer transformed(Container&& container, Transform transform) +template +struct TransformFactory { - return TransformedContainer(container, std::move(transform)); + template + TransformView operator()(Container&& container) const { return {container, std::move(m_transform)}; } + + Transform m_transform; +}; + +template +inline ContainerView> transform(Transform t) { return {{std::move(t)}}; } + + + +template +struct ConcatView +{ + using ContainerIt1 = decltype(begin(std::declval())); + using ContainerIt2 = decltype(begin(std::declval())); + using ValueType = typename ContainerIt1::value_type; + + struct Iterator : std::iterator + { + static_assert(std::is_convertible::value, ""); + static_assert(std::is_convertible::value, ""); + + Iterator(ContainerIt1 it1, ContainerIt1 end1, ContainerIt2 it2) + : m_it1(std::move(it1)), m_end1(std::move(end1)), + m_it2(std::move(it2)) {} + + decltype(*std::declval()) operator*() { return is2() ? *m_it2 : *m_it1; } + Iterator& operator++() { if (is2()) ++m_it2; else ++m_it1; return *this; } + Iterator operator++(int) { auto copy = *this; ++*this; return copy; } + + friend bool operator==(const Iterator& lhs, const Iterator& rhs) + { + return lhs.m_it1 == rhs.m_it1 and lhs.m_end1 == rhs.m_end1 and + lhs.m_it2 == rhs.m_it2; + } + + friend bool operator!=(const Iterator& lhs, const Iterator& rhs) + { + return not (lhs == rhs); + } + + private: + bool is2() const { return m_it1 == m_end1; } + + ContainerIt1 m_it1; + ContainerIt1 m_end1; + ContainerIt2 m_it2; + }; + + ConcatView(Container1& container1, Container2& container2) + : m_container1(container1), m_container2(container2) {} + + Iterator begin() const { return {m_container1.begin(), m_container1.end(), m_container2.begin()}; } + Iterator end() const { return {m_container1.end(), m_container1.end(), m_container2.end()}; } + +private: + Container1& m_container1; + Container2& m_container2; +}; + +template +ConcatView concatenated(Container1&& container1, Container2&& container2) +{ + return {container1, container2}; } // Todo: move that into the following functions once we can remove the decltype diff --git a/src/face_registry.cc b/src/face_registry.cc index 3a5ac6e1..7da57e9c 100644 --- a/src/face_registry.cc +++ b/src/face_registry.cc @@ -95,9 +95,8 @@ CandidateList FaceRegistry::complete_alias_name(StringView prefix, ByteCount cursor_pos) const { return complete(prefix, cursor_pos, - transformed(m_aliases, - [](const AliasMap::value_type& v) -> const String& - { return v.first; })); + m_aliases | transform([](const AliasMap::value_type& v) -> const String& + { return v.first; })); } FaceRegistry::FaceRegistry() diff --git a/src/file.cc b/src/file.cc index 2d3e6a82..6cf9a735 100644 --- a/src/file.cc +++ b/src/file.cc @@ -63,7 +63,7 @@ String parse_filename(StringView filename) std::pair split_path(StringView path) { - auto it = find(reversed(path), '/'); + auto it = find(path | reverse(), '/'); if (it == path.rend()) return { {}, path }; const char* slash = it.base()-1; diff --git a/src/highlighter_group.cc b/src/highlighter_group.cc index d3755734..2d0a7a74 100644 --- a/src/highlighter_group.cc +++ b/src/highlighter_group.cc @@ -52,10 +52,9 @@ Completions HighlighterGroup::complete_child(StringView path, ByteCount cursor_p auto candidates = complete( path, cursor_pos, - transformed(filtered(m_highlighters, - [=](const HighlighterMap::Element& hl) - { return not group or hl.value->has_children(); }), - HighlighterMap::get_id)); + m_highlighters | filter([=](const HighlighterMap::Element& hl) + { return not group or hl.value->has_children(); }) + | transform(HighlighterMap::get_id)); return { 0, 0, std::move(candidates) }; } diff --git a/src/highlighters.cc b/src/highlighters.cc index 740320ac..871d549c 100644 --- a/src/highlighters.cc +++ b/src/highlighters.cc @@ -1303,7 +1303,7 @@ public: return offset_pos(hl.complete_child(path.substr(offset), cursor_pos - offset, group), offset); } - auto container = transformed(m_groups, decltype(m_groups)::get_id); + auto container = m_groups | transform(decltype(m_groups)::get_id); return { 0, 0, complete(path, cursor_pos, container) }; } diff --git a/src/hook_manager.cc b/src/hook_manager.cc index 81a00730..389c8504 100644 --- a/src/hook_manager.cc +++ b/src/hook_manager.cc @@ -31,7 +31,7 @@ CandidateList HookManager::complete_hook_group(StringView prefix, ByteCount pos_ CandidateList res; for (auto& list : m_hook) { - auto container = transformed(list.value, decltype(list.value)::get_id); + auto container = list.value | transform(decltype(list.value)::get_id); for (auto& c : complete(prefix, pos_in_token, container)) { if (!contains(res, c)) diff --git a/src/input_handler.cc b/src/input_handler.cc index 3871398d..b1e71ed3 100644 --- a/src/input_handler.cc +++ b/src/input_handler.cc @@ -763,7 +763,11 @@ public: CandidateList& candidates = m_completions.candidates; // first try, we need to ask our completer for completions if (candidates.empty()) + { refresh_completions(CompletionFlags::None); + if (candidates.size() > 1) + return; + } if (candidates.empty()) return; diff --git a/src/json_ui.cc b/src/json_ui.cc new file mode 100644 index 00000000..aea412b1 --- /dev/null +++ b/src/json_ui.cc @@ -0,0 +1,446 @@ +#include "json_ui.hh" + +#include "display_buffer.hh" +#include "keys.hh" +#include "file.hh" +#include "event_manager.hh" +#include "value.hh" +#include "unit_tests.hh" + +#include + +namespace Kakoune +{ + +template +String to_json(ArrayView array) +{ + String res; + for (auto& elem : array) + { + if (not res.empty()) + res += ", "; + res += to_json(elem); + } + return "[" + res + "]"; +} + +template +String to_json(const Vector& vec) { return to_json(ArrayView{vec}); } + +String to_json(int i) { return to_string(i); } +String to_json(bool b) { return b ? "true" : "false"; } +String to_json(StringView str) +{ + String res; + res.reserve(str.length() + 4); + res += '"'; + for (auto it = str.begin(), end = str.end(); it != end; ) + { + auto next = std::find_if(it, end, [](char c) { + return c == '\\' or c == '"' or (c >= 0 and c <= 0x1F); + }); + + res += StringView{it, next}; + if (next == end) + break; + + char buf[7] = {'\\', *next, 0}; + if (*next >= 0 and *next <= 0x1F) + sprintf(buf, "\\u%04x", *next); + + res += buf; + it = next+1; + } + res += '"'; + return res; +} + +String to_json(Color color) +{ + if (color.color == Kakoune::Color::RGB) + { + char buffer[10]; + sprintf(buffer, R"("#%02x%02x%02x")", color.r, color.g, color.b); + return buffer; + } + return to_json(color_to_str(color)); +} + +String to_json(Attribute attributes) +{ + struct { Attribute attr; StringView name; } + attrs[] { + { Attribute::Exclusive, "exclusive" }, + { Attribute::Underline, "underline" }, + { Attribute::Reverse, "reverse" }, + { Attribute::Blink, "blink" }, + { Attribute::Bold, "bold" }, + { Attribute::Dim, "dim" }, + { Attribute::Italic, "italic" }, + }; + + String res; + for (auto& attr : attrs) + { + if (not (attributes & attr.attr)) + continue; + + if (not res.empty()) + res += ", "; + res += to_json(attr.name); + } + return "[" + res + "]"; +} + +String to_json(Face face) +{ + return format(R"(\{ "fg": {}, "bg": {}, "attributes": {} })", + to_json(face.fg), to_json(face.bg), to_json(face.attributes)); +} + +String to_json(const DisplayAtom& atom) +{ + return format(R"(\{ "face": {}, "contents": {} })", to_json(atom.face), to_json(atom.content())); +} + +String to_json(const DisplayLine& line) +{ + return to_json(line.atoms()); +} + +String to_json(CharCoord coord) +{ + return format(R"(\{ "line": {}, "column": {} })", coord.line, coord.column); +} + +String to_json(MenuStyle style) +{ + switch (style) + { + case MenuStyle::Prompt: return R"("prompt")"; + case MenuStyle::Inline: return R"("inline")"; + } + return ""; +} + +String to_json(InfoStyle style) +{ + switch (style) + { + case InfoStyle::Prompt: return R"("prompt")"; + case InfoStyle::Inline: return R"("inline")"; + case InfoStyle::InlineAbove: return R"("inlineAbove")"; + case InfoStyle::InlineBelow: return R"("inlineBelow")"; + case InfoStyle::MenuDoc: return R"("menuDoc")"; + } + return ""; +} + +String concat() +{ + return ""; +} + +template +String concat(First&& first, Args&&... args) +{ + if (sizeof...(Args) != 0) + return to_json(first) + ", " + concat(args...); + return to_json(first); +} + +template +void rpc_call(StringView method, Args&&... args) +{ + auto q = format(R"(\{ "jsonrpc": "2.0", "method": "{}", "params": [{}] }{})", + method, concat(std::forward(args)...), "\n"); + + write_stdout(q); +} + +JsonUI::JsonUI() + : m_stdin_watcher{0, [this](FDWatcher&, EventMode mode) { + parse_requests(mode); + }}, m_dimensions{24, 80} +{ + set_signal_handler(SIGINT, SIG_DFL); +} + +void JsonUI::draw(const DisplayBuffer& display_buffer, + const Face& default_face) +{ + rpc_call("draw", display_buffer.lines(), default_face); +} + +void JsonUI::draw_status(const DisplayLine& status_line, + const DisplayLine& mode_line, + const Face& 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 items, + CharCoord anchor, Face fg, Face bg, + MenuStyle style) +{ + rpc_call("menu_show", items, anchor, fg, bg, style); +} + +void JsonUI::menu_select(int selected) +{ + rpc_call("menu_show", selected); +} + +void JsonUI::menu_hide() +{ + rpc_call("menu_hide"); +} + +void JsonUI::info_show(StringView title, StringView content, + CharCoord anchor, Face face, + InfoStyle style) +{ + rpc_call("info_show", title, content, anchor, face, style); +} + +void JsonUI::info_hide() +{ + rpc_call("info_hide"); +} + +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); +} + +CharCoord JsonUI::dimensions() +{ + return m_dimensions; +} + +using JsonArray = Vector; +using JsonObject = IdMap; + +static bool is_digit(char c) { return c >= '0' and c <= '9'; } + +std::tuple +parse_json(const char* pos, const char* end) +{ + using Result = std::tuple; + + if (not skip_while(pos, end, is_blank)) + return {}; + + if (is_digit(*pos)) + { + auto digit_end = pos; + skip_while(digit_end, end, is_digit); + return Result{ Value{str_to_int({pos, end})}, digit_end }; + } + if (end - pos > 4 and StringView{pos, pos+4} == "true") + return Result{ Value{true}, pos+4 }; + if (end - pos > 5 and StringView{pos, pos+5} == "false") + return Result{ Value{false}, pos+5 }; + if (*pos == '"') + { + String value; + bool escaped = false; + ++pos; + for (auto string_end = pos; string_end != end; ++string_end) + { + if (escaped) + { + escaped = false; + value += StringView{pos, string_end}; + value.back() = *string_end; + pos = string_end+1; + continue; + } + if (*string_end == '\\') + escaped = true; + if (*string_end == '"') + { + value += StringView{pos, string_end}; + return Result{std::move(value), string_end+1}; + } + } + return {}; + } + if (*pos == '[') + { + JsonArray array; + ++pos; + while (true) + { + Value element; + std::tie(element, pos) = parse_json(pos, end); + if (not element) + return {}; + array.push_back(std::move(element)); + if (not skip_while(pos, end, is_blank)) + return {}; + + if (*pos == ',') + ++pos; + else if (*pos == ']') + return Result{std::move(array), pos+1}; + else + throw runtime_error("unable to parse array, expected ',' or ']'"); + } + } + if (*pos == '{') + { + JsonObject object; + ++pos; + while (true) + { + Value name_value; + std::tie(name_value, pos) = parse_json(pos, end); + if (not name_value) + return {}; + + String& name = name_value.as(); + if (not skip_while(pos, end, is_blank)) + return {}; + if (*pos++ != ':') + throw runtime_error("expected :"); + + Value element; + std::tie(element, pos) = parse_json(pos, end); + if (not element) + return {}; + object.append({ std::move(name), std::move(element) }); + if (not skip_while(pos, end, is_blank)) + return {}; + + if (*pos == ',') + ++pos; + else if (*pos == '}') + return Result{std::move(object), pos+1}; + else + throw runtime_error("unable to parse object, expected ',' or '}'"); + } + } + throw runtime_error("Could not parse json"); +} + +void JsonUI::eval_json(const Value& json) +{ + const JsonObject& object = json.as(); + auto json_it = object.find("jsonrpc"); + if (json_it == object.end() or json_it->value.as() != "2.0") + throw runtime_error("invalid json rpc request"); + + auto method_it = object.find("method"); + if (method_it == object.end()) + throw runtime_error("invalid json rpc request (method missing)"); + StringView method = method_it->value.as(); + + auto params_it = object.find("params"); + if (params_it == object.end()) + throw runtime_error("invalid json rpc request (params missing)"); + const JsonArray& params = params_it->value.as(); + + if (method == "keys") + { + for (auto& key_val : params) + { + for (auto& key : parse_keys(key_val.as())) + m_pending_keys.push_back(key); + } + } + else if (method == "resize") + { + if (params.size() != 2) + throw runtime_error("resize expects 2 parameters"); + + CharCoord dim{params[0].as(), params[1].as()}; + m_dimensions = dim; + m_pending_keys.push_back(resize(dim)); + } + else + throw runtime_error("unknown method"); +} + +static bool stdin_ready() +{ + fd_set rfds; + FD_ZERO(&rfds); + FD_SET(0, &rfds); + + timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 0; + + return select(1, &rfds, nullptr, nullptr, &tv) == 1; +} + +void JsonUI::parse_requests(EventMode mode) +{ + constexpr size_t bufsize = 1024; + char buf[bufsize]; + while (stdin_ready()) + { + ssize_t size = read(0, buf, bufsize); + if (size == -1 or size == 0) + break; + + m_requests += StringView{buf, buf + size}; + } + + if (not m_requests.empty()) + { + Value json; + const char* pos; + std::tie(json, pos) = parse_json(m_requests.begin(), m_requests.end()); + if (json) + { + try + { + eval_json(json); + } + catch (runtime_error& error) + { + write_stderr(format("error while executing request '{}': '{}'", + StringView{m_requests.begin(), pos}, error.what())); + } + m_requests = String{pos, m_requests.end()}; + } + } + + while (not m_pending_keys.empty()) + m_input_callback(mode); +} + +UnitTest test_json_parser{[]() +{ + StringView json = R"({ "jsonrpc": "2.0", "method": "keys", "params": [ "b", "l", "a", "h" ] })"; + auto value = std::get<0>(parse_json(json.begin(), json.end())); + kak_assert(value); +}}; + +} diff --git a/src/json_ui.hh b/src/json_ui.hh new file mode 100644 index 00000000..563ff997 --- /dev/null +++ b/src/json_ui.hh @@ -0,0 +1,63 @@ +#ifndef json_ui_hh_INCLUDED +#define json_ui_hh_INCLUDED + +#include "user_interface.hh" +#include "event_manager.hh" +#include "coord.hh" + +namespace Kakoune +{ + +class Value; + +class JsonUI : public UserInterface +{ +public: + JsonUI(); + + JsonUI(const JsonUI&) = delete; + JsonUI& operator=(const JsonUI&) = delete; + + void draw(const DisplayBuffer& display_buffer, + const Face& default_face) override; + + void draw_status(const DisplayLine& status_line, + const DisplayLine& mode_line, + const Face& default_face) override; + + bool is_key_available() override; + Key get_key() override; + + void menu_show(ConstArrayView items, + CharCoord anchor, Face fg, Face bg, + MenuStyle style) override; + void menu_select(int selected) override; + void menu_hide() override; + + void info_show(StringView title, StringView content, + CharCoord anchor, Face face, + InfoStyle style) override; + void info_hide() override; + + void refresh(bool force) override; + + void set_input_callback(InputCallback callback) override; + + void set_ui_options(const Options& options) override; + + CharCoord dimensions() override; + +private: + void parse_requests(EventMode mode); + void eval_json(const Value& value); + + InputCallback m_input_callback; + FDWatcher m_stdin_watcher; + Vector m_pending_keys; + CharCoord m_dimensions; + String m_requests; +}; + +} + +#endif // json_ui_hh_INCLUDED diff --git a/src/main.cc b/src/main.cc index 11f6c11d..0a9e713e 100644 --- a/src/main.cc +++ b/src/main.cc @@ -14,6 +14,7 @@ #include "insert_completer.hh" #include "shared_string.hh" #include "ncurses_ui.hh" +#include "json_ui.hh" #include "parameters_parser.hh" #include "register_manager.hh" #include "remote.hh" @@ -60,9 +61,9 @@ void register_env_vars() }, { "buflist", false, [](StringView name, const Context& context) - { return join(transformed(BufferManager::instance(), - [](const SafePtr& b) - { return b->display_name(); }), ':'); } + { return join(BufferManager::instance() | + transform([](const SafePtr& b) + { return b->display_name(); }), ':'); } }, { "timestamp", false, [](StringView name, const Context& context) -> String @@ -261,6 +262,13 @@ struct convert_to_client_mode String buffer_name; }; +enum class UIType +{ + NCurses, + Json, + Dummy, +}; + static Client* local_client = nullptr; static UserInterface* local_ui = nullptr; static bool convert_to_client_pending = false; @@ -278,7 +286,7 @@ pid_t fork_server_to_background() return 0; } -std::unique_ptr create_local_ui(bool dummy_ui) +std::unique_ptr make_ui(UIType ui_type) { struct DummyUI : UserInterface { @@ -295,13 +303,24 @@ std::unique_ptr create_local_ui(bool dummy_ui) CharCoord dimensions() override { return {24,80}; } bool is_key_available() override { return false; } Key get_key() override { return Key::Invalid; } - void refresh() override {} + void refresh(bool) override {} void set_input_callback(InputCallback) override {} void set_ui_options(const Options&) override {} }; - if (dummy_ui) - return make_unique(); + switch (ui_type) + { + case UIType::NCurses: return make_unique(); + case UIType::Json: return make_unique(); + case UIType::Dummy: return make_unique(); + } + throw logic_error{}; +} + +std::unique_ptr create_local_ui(UIType ui_type) +{ + if (ui_type != UIType::NCurses) + return make_ui(ui_type); struct LocalUI : NCursesUI { @@ -406,13 +425,12 @@ void signal_handler(int signal) abort(); } -int run_client(StringView session, StringView init_command) +int run_client(StringView session, StringView init_command, UIType ui_type) { try { EventManager event_manager; - RemoteClient client{session, make_unique(), - get_env_vars(), init_command}; + RemoteClient client{session, make_ui(ui_type), get_env_vars(), init_command}; while (true) event_manager.handle_next_events(EventMode::Normal); } @@ -430,7 +448,7 @@ int run_client(StringView session, StringView init_command) } int run_server(StringView session, StringView init_command, - bool ignore_kakrc, bool daemon, bool dummy_ui, + bool ignore_kakrc, bool daemon, UIType ui_type, ConstArrayView files, LineCount target_line) { static bool terminate = false; @@ -502,7 +520,7 @@ int run_server(StringView session, StringView init_command, { // create buffers in reverse order so that the first given buffer // is the most recently created one. - for (auto& file : reversed(files)) + for (auto& file : files | reverse()) { try { @@ -526,7 +544,7 @@ int run_server(StringView session, StringView init_command, if (not daemon) { local_client = client_manager.create_client( - create_local_ui(dummy_ui), get_env_vars(), init_command); + create_local_ui(ui_type), get_env_vars(), init_command); if (local_client) { @@ -699,7 +717,7 @@ int main(int argc, char* argv[]) { "p", { true, "just send stdin as commands to the given session" } }, { "f", { true, "act as a filter, executing given keys on given files" } }, { "q", { false, "in filter mode, be quiet about errors applying keys" } }, - { "u", { false, "use a dummy user interface, for testing purposes" } }, + { "ui", { true, "set the type of user interface to use (ncurses, dummy, or json)" } }, { "l", { false, "list existing sessions" } } } }; try @@ -730,6 +748,17 @@ int main(int argc, char* argv[]) } auto init_command = parser.get_switch("e").value_or(StringView{}); + auto ui_name = parser.get_switch("ui").value_or("ncurses"); + UIType ui_type; + if (ui_name == "ncurses") ui_type = UIType::NCurses; + else if (ui_name == "json") ui_type = UIType::Json; + else if (ui_name == "dummy") ui_type = UIType::Dummy; + else + { + write_stderr(format("error: unknown ui type: '{}'", ui_name)); + return -1; + } + if (auto keys = parser.get_switch("f")) { @@ -754,7 +783,7 @@ int main(int argc, char* argv[]) for (auto name : parser) new_files += format("edit '{}';", escape(real_path(name), "'", '\\')); - return run_client(*server_session, new_files + init_command); + return run_client(*server_session, new_files + init_command, ui_type); } else { @@ -780,15 +809,14 @@ int main(int argc, char* argv[]) return run_server(session, init_command, (bool)parser.get_switch("n"), (bool)parser.get_switch("d"), - (bool)parser.get_switch("u"), - files, target_line); + ui_type, files, target_line); } catch (convert_to_client_mode& convert) { raise(SIGTSTP); return run_client(convert.session, format("try %^buffer '{}'^; echo converted to client only mode", - escape(convert.buffer_name, "'^", '\\'))); + escape(convert.buffer_name, "'^", '\\')), ui_type); } } } diff --git a/src/ncurses_ui.cc b/src/ncurses_ui.cc index 5309ecb8..308527f4 100644 --- a/src/ncurses_ui.cc +++ b/src/ncurses_ui.cc @@ -301,9 +301,12 @@ void NCursesUI::redraw() doupdate(); } -void NCursesUI::refresh() +void NCursesUI::refresh(bool force) { - if (m_dirty) + if (force) + redrawwin(m_window); + + if (m_dirty or force) redraw(); m_dirty = false; } @@ -491,11 +494,6 @@ Key NCursesUI::get_key() if (c > 0 and c < 27) { - if (c == control('l')) - { - redrawwin(m_window); - redraw(); - } if (c == control('z')) { raise(SIGTSTP); @@ -674,6 +672,10 @@ void NCursesUI::menu_show(ConstArrayView items, auto width = is_prompt ? maxsize.column : min(longest+1, maxsize.column); m_menu.create({line, anchor.column}, {height, width}); draw_menu(); + + if (m_info) + info_show(m_info.title, m_info.content, + m_info.anchor, m_info.face, m_info.style); } void NCursesUI::menu_select(int selected) diff --git a/src/ncurses_ui.hh b/src/ncurses_ui.hh index fdf1b6a7..870a3fc7 100644 --- a/src/ncurses_ui.hh +++ b/src/ncurses_ui.hh @@ -43,7 +43,7 @@ public: InfoStyle style) override; void info_hide() override; - void refresh() override; + void refresh(bool force) override; void set_input_callback(InputCallback callback) override; diff --git a/src/normal.cc b/src/normal.cc index 4d95ff53..33c813f6 100644 --- a/src/normal.cc +++ b/src/normal.cc @@ -1560,6 +1560,15 @@ void ensure_forward(Context& context, NormalParams) context.selections().check_invariant(); } +void force_redraw(Context& context, NormalParams) +{ + if (context.has_client()) + { + context.client().force_redraw(); + context.client().redraw_ifn(); + } +} + static NormalCmdDesc cmds[] = { { 'h', "move left", move }, @@ -1734,6 +1743,8 @@ static NormalCmdDesc cmds[] = { 'z', "restore selections", restore_selections }, { alt('z'), "append saved selections", restore_selections }, { 'Z', "save selections", save_selections }, + + { ctrl('l'), "force redraw", force_redraw }, }; KeyMap keymap = cmds; diff --git a/src/remote.cc b/src/remote.cc index e2756ef1..718d7f7d 100644 --- a/src/remote.cc +++ b/src/remote.cc @@ -255,7 +255,7 @@ public: const DisplayLine& mode_line, const Face& default_face) override; - void refresh() override; + void refresh(bool force) override; bool is_key_available() override; Key get_key() override; @@ -353,10 +353,11 @@ void RemoteUI::draw_status(const DisplayLine& status_line, msg.write(default_face); } -void RemoteUI::refresh() +void RemoteUI::refresh(bool force) { Message msg(m_socket_watcher.fd()); msg.write(RemoteUIMsg::Refresh); + msg.write(force); } void RemoteUI::set_ui_options(const Options& options) @@ -516,7 +517,7 @@ void RemoteClient::process_next_message() break; } case RemoteUIMsg::Refresh: - m_ui->refresh(); + m_ui->refresh(read(socket)); break; case RemoteUIMsg::SetOptions: m_ui->set_ui_options(read_idmap(socket)); diff --git a/src/selection.cc b/src/selection.cc index f24b0939..90580bdd 100644 --- a/src/selection.cc +++ b/src/selection.cc @@ -555,8 +555,8 @@ String selection_to_string(const Selection& selection) String selection_list_to_string(const SelectionList& selections) { - return join(transformed(selections, [](const Selection& s) - { return selection_to_string(s); }), + return join(selections | transform([](const Selection& s) + { return selection_to_string(s); }), ':', false); } diff --git a/src/selectors.cc b/src/selectors.cc index 662cd6b0..1ca42a28 100644 --- a/src/selectors.cc +++ b/src/selectors.cc @@ -128,7 +128,7 @@ find_surrounding(Iterator begin, Iterator end, { using RevIt = std::reverse_iterator; auto res = find_closing(RevIt{pos+1}, RevIt{begin}, - reversed(closing), reversed(opening), + closing | reverse(), opening | reverse(), init_level, nestable); if (not res) return {}; diff --git a/src/user_interface.hh b/src/user_interface.hh index 67553922..e8a30a4b 100644 --- a/src/user_interface.hh +++ b/src/user_interface.hh @@ -63,7 +63,7 @@ public: virtual bool is_key_available() = 0; virtual Key get_key() = 0; - virtual void refresh() = 0; + virtual void refresh(bool force) = 0; virtual void set_input_callback(InputCallback callback) = 0; diff --git a/test/highlight/regions/cmd b/test/highlight/regions/cmd new file mode 100644 index 00000000..db6cccd0 --- /dev/null +++ b/test/highlight/regions/cmd @@ -0,0 +1 @@ +:q diff --git a/test/highlight/regions/display b/test/highlight/regions/display new file mode 100644 index 00000000..8b839286 --- /dev/null +++ b/test/highlight/regions/display @@ -0,0 +1,5 @@ +{ "jsonrpc": "2.0", "method": "draw", "params": [[[{ "face": { "fg": "black", "bg": "white", "attributes": [] }, "contents": "\"" }, { "face": { "fg": "green", "bg": "default", "attributes": [] }, "contents": "abcdefgh\"" }, { "face": { "fg": "yellow", "bg": "default", "attributes": [] }, "contents": " hehe " }, { "face": { "fg": "red", "bg": "default", "attributes": [] }, "contents": "${ youhou{hihi} }" }, { "face": { "fg": "yellow", "bg": "default", "attributes": [] }, "contents": "\u000a" }, { "face": { "fg": "default", "bg": "default", "attributes": [] }, "contents": "" }]], { "fg": "default", "bg": "default", "attributes": [] }] } +{ "jsonrpc": "2.0", "method": "menu_hide", "params": [] } +{ "jsonrpc": "2.0", "method": "info_hide", "params": [] } +{ "jsonrpc": "2.0", "method": "draw_status", "params": [[], [{ "face": { "fg": "default", "bg": "default", "attributes": [] }, "contents": "out 1:1 " }, { "face": { "fg": "default", "bg": "default", "attributes": [] }, "contents": " " }, { "face": { "fg": "blue", "bg": "default", "attributes": [] }, "contents": "1 sel" }, { "face": { "fg": "default", "bg": "default", "attributes": [] }, "contents": " - unnamed0@[kak-test-highlight-regions]" }], { "fg": "cyan", "bg": "default", "attributes": [] }] } +{ "jsonrpc": "2.0", "method": "refresh", "params": [true] } diff --git a/test/highlight/regions/in b/test/highlight/regions/in new file mode 100644 index 00000000..8b76db01 --- /dev/null +++ b/test/highlight/regions/in @@ -0,0 +1 @@ +"abcdefgh" hehe ${ youhou{hihi} } diff --git a/test/highlight/regions/rc b/test/highlight/regions/rc new file mode 100644 index 00000000..4679889e --- /dev/null +++ b/test/highlight/regions/rc @@ -0,0 +1,7 @@ +addhl regions -default code regions_test \ + string %{"} %{(?/dev/null) + test_files=$(ls out selections state display 2>/dev/null) cd $work/$dir; indent="$(echo "${dir}/" | sed -e 's|[^/]*/\+| |g')" name=$(basename $PWD) @@ -26,6 +26,8 @@ main() { touch in; cp in out kak_commands=" set global autoreload yes + set global autoinfo '' + set global autoshowcompl false try %{ source rc } @@ -40,6 +42,7 @@ main() { quit! } exec '$(cat cmd | sed -e s/\'/\\\\\'/g)' + exec eval -buffer *debug* write debug nop %sh{ IFS== @@ -49,7 +52,9 @@ main() { write out quit! " - ${test}/../src/kak out -n -u -e "$kak_commands" + session="kak-test-$(printf '%s' "$dir" | sed -e 's+^\./++; s+/+-+g')" + rm -f /tmp/kakoune/$USER/$session + ${test}/../src/kak out -n -s "$session" -ui json -e "$kak_commands" > display retval=$? if [ $should_fail = 0 ]; then if [ $retval -ne 0 ]; then @@ -75,9 +80,11 @@ main() { echo "$line" | colorize $color normal done echo - echo "debug buffer:" | colorize yellow normal - cat debug - echo + if test -e debug; then + echo "debug buffer:" | colorize yellow normal + cat debug + echo + fi fi done fi