mirror of
https://github.com/mawww/kakoune.git
synced 2024-11-25 10:32:19 +03:00
Merge branch 'json-ui'
This commit is contained in:
commit
fcd3437fac
46
doc/json_ui.asciidoc
Normal file
46
doc/json_ui.asciidoc
Normal file
@ -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<Attribute> 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<Line> lines, Face default_face)
|
||||
* draw_status(Line status_line, Line mode_line,
|
||||
Face default_face)
|
||||
* menu_show(Array<Line> 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
|
@ -57,7 +57,7 @@ Vector<std::pair<StringView, StringView>> AliasRegistry::flatten_aliases() const
|
||||
res = m_parent->flatten_aliases();
|
||||
for (auto& alias : m_aliases)
|
||||
{
|
||||
if (not contains(transformed(res, [](const std::pair<StringView, StringView>& 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;
|
||||
|
@ -20,7 +20,8 @@ public:
|
||||
using iterator = AliasMap::const_iterator;
|
||||
|
||||
Vector<StringView> aliases_for(StringView command) const;
|
||||
Vector<std::pair<StringView, StringView>> flatten_aliases() const;
|
||||
using AliasDesc = std::pair<StringView, StringView>;
|
||||
Vector<AliasDesc> flatten_aliases() const;
|
||||
|
||||
private:
|
||||
friend class Scope;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -36,7 +36,7 @@ Client::Client(std::unique_ptr<UserInterface>&& ui,
|
||||
|
||||
m_ui->set_ui_options(m_window->options()["ui_options"].get<UserInterface::Options>());
|
||||
m_ui->set_input_callback([this](EventMode mode) { handle_available_input(mode); });
|
||||
m_ui_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<UserInterface::Options>());
|
||||
m_ui_dirty = true;
|
||||
m_ui_pending |= Draw;
|
||||
}
|
||||
}
|
||||
|
||||
void Client::menu_show(Vector<DisplayLine> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -74,7 +74,6 @@ private:
|
||||
|
||||
DisplayLine generate_mode_line() const;
|
||||
|
||||
bool m_ui_dirty = false;
|
||||
std::unique_ptr<UserInterface> m_ui;
|
||||
std::unique_ptr<Window> 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<DisplayLine> items;
|
||||
|
@ -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<Client>& c) -> const String&
|
||||
{ return c->context().name(); });
|
||||
|
||||
auto c = m_clients | transform([](const std::unique_ptr<Client>& c) -> const String&
|
||||
{ return c->context().name(); });
|
||||
return complete(prefix, cursor_pos, c);
|
||||
}
|
||||
|
||||
|
@ -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];
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -8,11 +8,21 @@
|
||||
namespace Kakoune
|
||||
{
|
||||
|
||||
template<typename Factory>
|
||||
struct ContainerView { Factory factory; };
|
||||
|
||||
template<typename Container, typename Factory>
|
||||
auto operator| (Container&& container, ContainerView<Factory> view) ->
|
||||
decltype(view.factory(std::forward<Container>(container)))
|
||||
{
|
||||
return view.factory(std::forward<Container>(container));
|
||||
}
|
||||
|
||||
template<typename Container>
|
||||
struct ReversedContainer
|
||||
struct ReverseView
|
||||
{
|
||||
using iterator = decltype(std::declval<Container>().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<typename Container>
|
||||
ReversedContainer<Container> reversed(Container&& container)
|
||||
struct ReverseFactory
|
||||
{
|
||||
return ReversedContainer<Container>(container);
|
||||
}
|
||||
|
||||
template<typename Iterator, typename Filter>
|
||||
struct FilteredIterator : std::iterator<std::forward_iterator_tag,
|
||||
typename Iterator::value_type>
|
||||
{
|
||||
FilteredIterator(Filter filter, Iterator it, Iterator end)
|
||||
: m_it(std::move(it)), m_end(std::move(end)), m_filter(std::move(filter))
|
||||
template<typename Container>
|
||||
ReverseView<Container> operator()(Container&& container) const
|
||||
{
|
||||
do_filter();
|
||||
return {container};
|
||||
}
|
||||
|
||||
auto operator*() -> decltype(*std::declval<Iterator>()) { 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<ReverseFactory> reverse() { return {}; }
|
||||
|
||||
template<typename Container, typename Filter>
|
||||
struct FilteredContainer
|
||||
struct FilterView
|
||||
{
|
||||
using iterator = FilteredIterator<decltype(begin(std::declval<Container>())), Filter>;
|
||||
FilteredContainer(Container& container, Filter filter)
|
||||
using ContainerIt = decltype(begin(std::declval<Container>()));
|
||||
|
||||
struct Iterator : std::iterator<std::forward_iterator_tag,
|
||||
typename ContainerIt::value_type>
|
||||
{
|
||||
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<ContainerIt>()) { 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<typename Container, typename Filter>
|
||||
FilteredContainer<Container, Filter> filtered(Container&& container, Filter filter)
|
||||
template<typename Filter>
|
||||
struct FilterFactory
|
||||
{
|
||||
return FilteredContainer<Container, Filter>(container, std::move(filter));
|
||||
}
|
||||
template<typename Container>
|
||||
FilterView<Container, Filter> operator()(Container&& container) const { return {container, std::move(m_filter)}; }
|
||||
|
||||
Filter m_filter;
|
||||
};
|
||||
|
||||
template<typename Filter>
|
||||
inline ContainerView<FilterFactory<Filter>> filter(Filter f) { return {{std::move(f)}}; }
|
||||
|
||||
template<typename I, typename T>
|
||||
using TransformedResult = decltype(std::declval<T>()(*std::declval<I>()));
|
||||
|
||||
template<typename Iterator, typename Transform>
|
||||
struct TransformedIterator : std::iterator<std::forward_iterator_tag,
|
||||
typename std::remove_reference<TransformedResult<Iterator, Transform>>::type>
|
||||
{
|
||||
TransformedIterator(Transform transform, Iterator it)
|
||||
: m_it(std::move(it)), m_transform(std::move(transform)) {}
|
||||
|
||||
auto operator*() -> TransformedResult<Iterator, Transform> { 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<typename Container, typename Transform>
|
||||
struct TransformedContainer
|
||||
struct TransformView
|
||||
{
|
||||
using iterator = TransformedIterator<decltype(begin(std::declval<Container>())), Transform>;
|
||||
TransformedContainer(Container& container, Transform transform)
|
||||
using ContainerIt = decltype(begin(std::declval<Container>()));
|
||||
|
||||
struct Iterator : std::iterator<std::forward_iterator_tag,
|
||||
typename std::remove_reference<TransformedResult<ContainerIt, Transform>>::type>
|
||||
{
|
||||
Iterator(const TransformView& view, ContainerIt it)
|
||||
: m_it{std::move(it)}, m_view{view} {}
|
||||
|
||||
auto operator*() -> TransformedResult<ContainerIt, Transform> { 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<typename Container, typename Transform>
|
||||
TransformedContainer<Container, Transform> transformed(Container&& container, Transform transform)
|
||||
template<typename Transform>
|
||||
struct TransformFactory
|
||||
{
|
||||
return TransformedContainer<Container, Transform>(container, std::move(transform));
|
||||
template<typename Container>
|
||||
TransformView<Container, Transform> operator()(Container&& container) const { return {container, std::move(m_transform)}; }
|
||||
|
||||
Transform m_transform;
|
||||
};
|
||||
|
||||
template<typename Transform>
|
||||
inline ContainerView<TransformFactory<Transform>> transform(Transform t) { return {{std::move(t)}}; }
|
||||
|
||||
|
||||
|
||||
template<typename Container1, typename Container2>
|
||||
struct ConcatView
|
||||
{
|
||||
using ContainerIt1 = decltype(begin(std::declval<Container1>()));
|
||||
using ContainerIt2 = decltype(begin(std::declval<Container2>()));
|
||||
using ValueType = typename ContainerIt1::value_type;
|
||||
|
||||
struct Iterator : std::iterator<std::forward_iterator_tag, ValueType>
|
||||
{
|
||||
static_assert(std::is_convertible<typename ContainerIt1::value_type, ValueType>::value, "");
|
||||
static_assert(std::is_convertible<typename ContainerIt2::value_type, ValueType>::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<ContainerIt1>()) 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<typename Container1, typename Container2>
|
||||
ConcatView<Container1, Container2> concatenated(Container1&& container1, Container2&& container2)
|
||||
{
|
||||
return {container1, container2};
|
||||
}
|
||||
|
||||
// Todo: move that into the following functions once we can remove the decltype
|
||||
|
@ -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()
|
||||
|
@ -63,7 +63,7 @@ String parse_filename(StringView filename)
|
||||
|
||||
std::pair<StringView, StringView> 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;
|
||||
|
@ -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) };
|
||||
}
|
||||
|
@ -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) };
|
||||
}
|
||||
|
||||
|
@ -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))
|
||||
|
@ -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;
|
||||
|
446
src/json_ui.cc
Normal file
446
src/json_ui.cc
Normal file
@ -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 <utility>
|
||||
|
||||
namespace Kakoune
|
||||
{
|
||||
|
||||
template<typename T>
|
||||
String to_json(ArrayView<const T> array)
|
||||
{
|
||||
String res;
|
||||
for (auto& elem : array)
|
||||
{
|
||||
if (not res.empty())
|
||||
res += ", ";
|
||||
res += to_json(elem);
|
||||
}
|
||||
return "[" + res + "]";
|
||||
}
|
||||
|
||||
template<typename T, MemoryDomain D>
|
||||
String to_json(const Vector<T, D>& vec) { return to_json(ArrayView<const T>{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<typename First, typename... Args>
|
||||
String concat(First&& first, Args&&... args)
|
||||
{
|
||||
if (sizeof...(Args) != 0)
|
||||
return to_json(first) + ", " + concat(args...);
|
||||
return to_json(first);
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
void rpc_call(StringView method, Args&&... args)
|
||||
{
|
||||
auto q = format(R"(\{ "jsonrpc": "2.0", "method": "{}", "params": [{}] }{})",
|
||||
method, concat(std::forward<Args>(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<DisplayLine> 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<Value>;
|
||||
using JsonObject = IdMap<Value>;
|
||||
|
||||
static bool is_digit(char c) { return c >= '0' and c <= '9'; }
|
||||
|
||||
std::tuple<Value, const char*>
|
||||
parse_json(const char* pos, const char* end)
|
||||
{
|
||||
using Result = std::tuple<Value, const char*>;
|
||||
|
||||
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<String>();
|
||||
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<JsonObject>();
|
||||
auto json_it = object.find("jsonrpc");
|
||||
if (json_it == object.end() or json_it->value.as<String>() != "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<String>();
|
||||
|
||||
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<JsonArray>();
|
||||
|
||||
if (method == "keys")
|
||||
{
|
||||
for (auto& key_val : params)
|
||||
{
|
||||
for (auto& key : parse_keys(key_val.as<String>()))
|
||||
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<int>(), params[1].as<int>()};
|
||||
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);
|
||||
}};
|
||||
|
||||
}
|
63
src/json_ui.hh
Normal file
63
src/json_ui.hh
Normal file
@ -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<DisplayLine> 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<Key> m_pending_keys;
|
||||
CharCoord m_dimensions;
|
||||
String m_requests;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // json_ui_hh_INCLUDED
|
64
src/main.cc
64
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<Buffer>& b)
|
||||
{ return b->display_name(); }), ':'); }
|
||||
{ return join(BufferManager::instance() |
|
||||
transform([](const SafePtr<Buffer>& 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<UserInterface> create_local_ui(bool dummy_ui)
|
||||
std::unique_ptr<UserInterface> make_ui(UIType ui_type)
|
||||
{
|
||||
struct DummyUI : UserInterface
|
||||
{
|
||||
@ -295,13 +303,24 @@ std::unique_ptr<UserInterface> 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<DummyUI>();
|
||||
switch (ui_type)
|
||||
{
|
||||
case UIType::NCurses: return make_unique<NCursesUI>();
|
||||
case UIType::Json: return make_unique<JsonUI>();
|
||||
case UIType::Dummy: return make_unique<DummyUI>();
|
||||
}
|
||||
throw logic_error{};
|
||||
}
|
||||
|
||||
std::unique_ptr<UserInterface> 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<NCursesUI>(),
|
||||
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<StringView> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<DisplayLine> 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)
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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<CharCount, Backward> },
|
||||
@ -1734,6 +1743,8 @@ static NormalCmdDesc cmds[] =
|
||||
{ 'z', "restore selections", restore_selections<false> },
|
||||
{ alt('z'), "append saved selections", restore_selections<true> },
|
||||
{ 'Z', "save selections", save_selections },
|
||||
|
||||
{ ctrl('l'), "force redraw", force_redraw },
|
||||
};
|
||||
|
||||
KeyMap keymap = cmds;
|
||||
|
@ -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<bool>(socket));
|
||||
break;
|
||||
case RemoteUIMsg::SetOptions:
|
||||
m_ui->set_ui_options(read_idmap<String, MemoryDomain::Options>(socket));
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -128,7 +128,7 @@ find_surrounding(Iterator begin, Iterator end,
|
||||
{
|
||||
using RevIt = std::reverse_iterator<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 {};
|
||||
|
@ -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;
|
||||
|
||||
|
1
test/highlight/regions/cmd
Normal file
1
test/highlight/regions/cmd
Normal file
@ -0,0 +1 @@
|
||||
<c-l>:q<ret>
|
5
test/highlight/regions/display
Normal file
5
test/highlight/regions/display
Normal file
@ -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] }
|
1
test/highlight/regions/in
Normal file
1
test/highlight/regions/in
Normal file
@ -0,0 +1 @@
|
||||
"abcdefgh" hehe ${ youhou{hihi} }
|
7
test/highlight/regions/rc
Normal file
7
test/highlight/regions/rc
Normal file
@ -0,0 +1,7 @@
|
||||
addhl regions -default code regions_test \
|
||||
string %{"} %{(?<!\\)(\\\\)*"} '' \
|
||||
shell '\$\{' '\}' '\{'
|
||||
|
||||
addhl -group regions_test/code fill yellow
|
||||
addhl -group regions_test/string fill green
|
||||
addhl -group regions_test/shell fill red
|
17
test/run
17
test/run
@ -12,7 +12,7 @@ main() {
|
||||
trap "rm -R $work" EXIT
|
||||
for dir in $(find $dirs -type d | sort); do
|
||||
cd $test/$dir;
|
||||
test_files=$(ls out selections state 2>/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 <c-l>
|
||||
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
|
||||
|
Loading…
Reference in New Issue
Block a user