Merge branch 'json-ui'

This commit is contained in:
Maxime Coste 2016-03-09 23:29:46 +00:00
commit fcd3437fac
33 changed files with 942 additions and 224 deletions

46
doc/json_ui.asciidoc Normal file
View 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

View File

@ -57,7 +57,7 @@ Vector<std::pair<StringView, StringView>> AliasRegistry::flatten_aliases() const
res = m_parent->flatten_aliases(); res = m_parent->flatten_aliases();
for (auto& alias : m_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); res.emplace_back(alias.key, alias.value);
} }
return res; return res;

View File

@ -20,7 +20,8 @@ public:
using iterator = AliasMap::const_iterator; using iterator = AliasMap::const_iterator;
Vector<StringView> aliases_for(StringView command) const; 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: private:
friend class Scope; friend class Scope;

View File

@ -304,7 +304,7 @@ bool Buffer::undo()
--m_history_cursor; --m_history_cursor;
for (const Modification& modification : reversed(*m_history_cursor)) for (const Modification& modification : *m_history_cursor | reverse())
apply_modification(modification.inverse()); apply_modification(modification.inverse());
return true; return true;
} }

View File

@ -104,7 +104,8 @@ inline ByteCoord Buffer::back_coord() const
inline ByteCoord Buffer::end_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) inline BufferIterator::BufferIterator(const Buffer& buffer, ByteCoord coord)

View File

@ -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_ui_options(m_window->options()["ui_options"].get<UserInterface::Options>());
m_ui->set_input_callback([this](EventMode mode) { handle_available_input(mode); }); m_ui->set_input_callback([this](EventMode mode) { handle_available_input(mode); });
m_ui_dirty = true; force_redraw();
} }
Client::~Client() Client::~Client()
@ -101,7 +101,8 @@ void Client::handle_available_input(EventMode mode)
void Client::print_status(DisplayLine status_line) 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 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().selections_write_only() = std::move(ws.selections);
context().set_window(*m_window); context().set_window(*m_window);
m_window->set_dimensions(m_ui->dimensions()); m_window->set_dimensions(m_ui->dimensions());
m_ui_dirty = true; force_redraw();
m_window->hooks().run_hook("WinDisplay", buffer.name(), context()); m_window->hooks().run_hook("WinDisplay", buffer.name(), context());
} }
@ -174,9 +175,20 @@ static bool is_inline(InfoStyle style)
void Client::redraw_ifn() void Client::redraw_ifn()
{ {
Window& window = context().window(); Window& window = context().window();
if (window.needs_redraw(context()))
m_ui_pending |= Draw;
const bool needs_redraw = window.needs_redraw(context()); DisplayLine mode_line = generate_mode_line();
if (needs_redraw) 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(); auto window_pos = window.position();
m_ui->draw(window.update_display_buffer(context()), get_face("Default")); 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 (window_pos != window.position())
{ {
if (not m_menu.items.empty() and m_menu.style == MenuStyle::Inline) if (not m_menu.items.empty() and m_menu.style == MenuStyle::Inline)
{ m_ui_pending |= (MenuShow | MenuSelect);
m_ui->menu_show(m_menu.items, window.display_position(m_menu.anchor),
get_face("MenuForeground"), get_face("MenuBackground"), m_menu.style);
m_ui->menu_select(m_menu.selected);
}
if (not m_info.content.empty() and is_inline(m_info.style)) if (not m_info.content.empty() and is_inline(m_info.style))
m_ui->info_show(m_info.title, m_info.content, m_ui_pending |= InfoShow;
window.display_position(m_info.anchor), }
}
if (m_ui_pending & MenuShow)
{
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); get_face("Information"), m_info.style);
} }
m_ui_dirty = true; if (m_ui_pending & InfoHide)
} m_ui->info_hide();
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())
{
m_mode_line = std::move(mode_line);
m_status_line = m_pending_status_line;
if (m_ui_pending & StatusLine)
m_ui->draw_status(m_status_line, m_mode_line, get_face("StatusLine")); m_ui->draw_status(m_status_line, m_mode_line, get_face("StatusLine"));
m_ui_dirty = true;
}
if (m_ui_dirty) m_ui->refresh(m_ui_pending | Refresh);
{ m_ui_pending = 0;
m_ui_dirty = false;
m_ui->refresh();
}
} }
void Client::force_redraw() void Client::force_redraw()
{ {
if (m_window) m_ui_pending |= Refresh | Draw | StatusLine |
m_window->force_redraw(); (m_menu.items.empty() ? MenuHide : MenuShow | MenuSelect) |
(m_info.content.empty() ? InfoHide : InfoShow);
} }
void Client::reload_buffer() void Client::reload_buffer()
@ -310,45 +327,43 @@ void Client::on_option_changed(const Option& option)
if (option.name() == "ui_options") if (option.name() == "ui_options")
{ {
m_ui->set_ui_options(option.get<UserInterface::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) void Client::menu_show(Vector<DisplayLine> choices, ByteCoord anchor, MenuStyle style)
{ {
m_menu = Menu{ std::move(choices), anchor, style, -1 }; m_menu = Menu{ std::move(choices), anchor, style, -1 };
CharCoord ui_anchor = style == MenuStyle::Inline ? context().window().display_position(anchor) : CharCoord{}; m_ui_pending |= MenuShow;
m_ui->menu_show(m_menu.items, ui_anchor, get_face("MenuForeground"), get_face("MenuBackground"), style); m_ui_pending &= ~MenuHide;
m_ui_dirty = true;
} }
void Client::menu_select(int selected) void Client::menu_select(int selected)
{ {
m_menu.selected = selected; m_menu.selected = selected;
m_ui->menu_select(selected); m_ui_pending |= MenuSelect;
m_ui_dirty = true; m_ui_pending &= ~MenuHide;
} }
void Client::menu_hide() void Client::menu_hide()
{ {
m_menu = Menu{}; m_menu = Menu{};
m_ui->menu_hide(); m_ui_pending |= MenuHide;
m_ui_dirty = true; m_ui_pending &= ~(MenuShow | MenuSelect);
} }
void Client::info_show(String title, String content, ByteCoord anchor, InfoStyle style) void Client::info_show(String title, String content, ByteCoord anchor, InfoStyle style)
{ {
m_info = Info{ std::move(title), std::move(content), anchor, 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_pending |= InfoShow;
m_ui->info_show(m_info.title, m_info.content, ui_anchor, get_face("Information"), style); m_ui_pending &= ~InfoHide;
m_ui_dirty = true;
} }
void Client::info_hide() void Client::info_hide()
{ {
m_info = Info{}; m_info = Info{};
m_ui->info_hide(); m_ui_pending |= InfoHide;
m_ui_dirty = true; m_ui_pending &= ~InfoShow;
} }
} }

View File

@ -74,7 +74,6 @@ private:
DisplayLine generate_mode_line() const; DisplayLine generate_mode_line() const;
bool m_ui_dirty = false;
std::unique_ptr<UserInterface> m_ui; std::unique_ptr<UserInterface> m_ui;
std::unique_ptr<Window> m_window; std::unique_ptr<Window> m_window;
@ -83,9 +82,21 @@ private:
InputHandler m_input_handler; InputHandler m_input_handler;
DisplayLine m_status_line; DisplayLine m_status_line;
DisplayLine m_pending_status_line;
DisplayLine m_mode_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 struct Menu
{ {
Vector<DisplayLine> items; Vector<DisplayLine> items;

View File

@ -87,7 +87,7 @@ void ClientManager::remove_client(Client& client, bool graceful)
WindowAndSelections ClientManager::get_free_window(Buffer& buffer) 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) [&](const WindowAndSelections& ws)
{ return &ws.window->buffer() == &buffer; }); { return &ws.window->buffer() == &buffer; });
@ -177,10 +177,8 @@ void ClientManager::redraw_clients() const
CandidateList ClientManager::complete_client_name(StringView prefix, CandidateList ClientManager::complete_client_name(StringView prefix,
ByteCount cursor_pos) const ByteCount cursor_pos) const
{ {
auto c = transformed(m_clients, auto c = m_clients | transform([](const std::unique_ptr<Client>& c) -> const String&
[](const std::unique_ptr<Client>& c) -> const String&
{ return c->context().name(); }); { return c->context().name(); });
return complete(prefix, cursor_pos, c); return complete(prefix, cursor_pos, c);
} }

View File

@ -512,6 +512,20 @@ CommandInfo CommandManager::command_info(const Context& context, StringView comm
return res; 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, Completions CommandManager::complete(const Context& context,
CompletionFlags flags, CompletionFlags flags,
StringView command_line, 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 // command name completion
if (tokens.empty() or 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::Raw or
tokens[tok_idx].type() == Token::Type::RawQuoted))) tokens[tok_idx].type() == Token::Type::RawQuoted)))
{ {
const bool is_end_token = tok_idx == tokens.size(); auto cmd_start = is_last_token ? cursor_pos : tokens[tok_idx].begin();
ByteCount cmd_start = is_end_token ? cursor_pos StringView query = command_line.substr(cmd_start, cursor_pos - cmd_start);
: tokens[tok_idx].begin(); return offset_pos(complete_command_name(context, query), cmd_start);
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;
} }
kak_assert(not tokens.empty()); 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); StringView prefix = params[token_to_complete].substr(0, pos_in_token);
if (token_to_complete == 0) if (token_to_complete == 0)
{ return complete_command_name(context, prefix);
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)};
}
else else
{ {
const String& command_name = params[0]; const String& command_name = params[0];

View File

@ -130,6 +130,8 @@ private:
const ShellContext& shell_context, const ShellContext& shell_context,
CharCoord pos) const; CharCoord pos) const;
Completions complete_command_name(const Context& context, StringView query) const;
struct CommandDescriptor struct CommandDescriptor
{ {
Command command; Command command;

View File

@ -543,7 +543,7 @@ Completions add_highlighter_completer(
if (token_to_complete == 1 and params[0] == "-group") if (token_to_complete == 1 and params[0] == "-group")
return complete_highlighter(context, params[1], pos_in_token, true); 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")) 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{}; return Completions{};
} }
@ -626,7 +626,7 @@ const CommandDesc rm_highlighter_cmd = {
[](const ParametersParser& parser, Context& context, const ShellContext&) [](const ParametersParser& parser, Context& context, const ShellContext&)
{ {
StringView path = parser[0]; StringView path = parser[0];
auto sep_it = find(reversed(path), '/'); auto sep_it = find(path | reverse(), '/');
auto& group = sep_it != path.rend() ? auto& group = sep_it != path.rend() ?
get_highlighter(context, {path.begin(), sep_it.base()}) get_highlighter(context, {path.begin(), sep_it.base()})
: context.window().highlighters(); : context.window().highlighters();

View File

@ -8,11 +8,21 @@
namespace Kakoune 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> template<typename Container>
struct ReversedContainer struct ReverseView
{ {
using iterator = decltype(std::declval<Container>().rbegin()); 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 begin() { return m_container.rbegin(); }
iterator end() { return m_container.rend(); } iterator end() { return m_container.rend(); }
@ -21,122 +31,196 @@ private:
Container& m_container; Container& m_container;
}; };
struct ReverseFactory
{
template<typename Container> template<typename Container>
ReversedContainer<Container> reversed(Container&& container) ReverseView<Container> operator()(Container&& container) const
{ {
return ReversedContainer<Container>(container); return {container};
} }
};
template<typename Iterator, typename Filter> inline ContainerView<ReverseFactory> reverse() { return {}; }
struct FilteredIterator : std::iterator<std::forward_iterator_tag,
typename Iterator::value_type> template<typename Container, typename Filter>
struct FilterView
{ {
FilteredIterator(Filter filter, Iterator it, Iterator end) using ContainerIt = decltype(begin(std::declval<Container>()));
: m_it(std::move(it)), m_end(std::move(end)), m_filter(std::move(filter))
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(); do_filter();
} }
auto operator*() -> decltype(*std::declval<Iterator>()) { return *m_it; } auto operator*() -> decltype(*std::declval<ContainerIt>()) { return *m_it; }
FilteredIterator& operator++() { ++m_it; do_filter(); return *this; } Iterator& operator++() { ++m_it; do_filter(); return *this; }
FilteredIterator operator++(int) { auto copy = *this; ++(*this); return copy; } Iterator operator++(int) { auto copy = *this; ++(*this); return copy; }
friend bool operator==(const FilteredIterator& lhs, const FilteredIterator& rhs) friend bool operator==(const Iterator& lhs, const Iterator& rhs)
{ {
return lhs.m_it == rhs.m_it; return lhs.m_it == rhs.m_it;
} }
friend bool operator!=(const FilteredIterator& lhs, const FilteredIterator& rhs) friend bool operator!=(const Iterator& lhs, const Iterator& rhs)
{ {
return not (lhs == rhs); return not (lhs == rhs);
} }
Iterator base() const { return m_it; } const ContainerIt& base() const { return m_it; }
private: private:
void do_filter() void do_filter()
{ {
while (m_it != m_end and not m_filter(*m_it)) while (m_it != m_end and not m_view.m_filter(*m_it))
++m_it; ++m_it;
} }
Iterator m_it; ContainerIt m_it;
Iterator m_end; ContainerIt m_end;
Filter m_filter; const FilterView& m_view;
}; };
template<typename Container, typename Filter> FilterView(Container& container, Filter filter)
struct FilteredContainer
{
using iterator = FilteredIterator<decltype(begin(std::declval<Container>())), Filter>;
FilteredContainer(Container& container, Filter filter)
: m_container(container), m_filter(std::move(filter)) {} : m_container(container), m_filter(std::move(filter)) {}
iterator begin() const { return iterator(m_filter, m_container.begin(), m_container.end()); } Iterator begin() const { return {*this, m_container.begin(), m_container.end()}; }
iterator end() const { return iterator(m_filter, m_container.end(), m_container.end()); } Iterator end() const { return {*this, m_container.end(), m_container.end()}; }
private: private:
Container& m_container; Container& m_container;
Filter m_filter; Filter m_filter;
}; };
template<typename Container, typename Filter> template<typename Filter>
FilteredContainer<Container, Filter> filtered(Container&& container, Filter 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> template<typename I, typename T>
using TransformedResult = decltype(std::declval<T>()(*std::declval<I>())); using TransformedResult = decltype(std::declval<T>()(*std::declval<I>()));
template<typename Iterator, typename Transform> template<typename Container, typename Transform>
struct TransformedIterator : std::iterator<std::forward_iterator_tag, struct TransformView
typename std::remove_reference<TransformedResult<Iterator, Transform>>::type>
{ {
TransformedIterator(Transform transform, Iterator it) using ContainerIt = decltype(begin(std::declval<Container>()));
: m_it(std::move(it)), m_transform(std::move(transform)) {}
auto operator*() -> TransformedResult<Iterator, Transform> { return m_transform(*m_it); } struct Iterator : std::iterator<std::forward_iterator_tag,
TransformedIterator& operator++() { ++m_it; return *this; } typename std::remove_reference<TransformedResult<ContainerIt, Transform>>::type>
TransformedIterator operator++(int) { auto copy = *this; ++m_it; return copy; } {
Iterator(const TransformView& view, ContainerIt it)
: m_it{std::move(it)}, m_view{view} {}
friend bool operator==(const TransformedIterator& lhs, const TransformedIterator& rhs) 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; return lhs.m_it == rhs.m_it;
} }
friend bool operator!=(const TransformedIterator& lhs, const TransformedIterator& rhs) friend bool operator!=(const Iterator& lhs, const Iterator& rhs)
{ {
return not (lhs == rhs); return not (lhs == rhs);
} }
Iterator base() const { return m_it; } ContainerIt base() const { return m_it; }
private: private:
Iterator m_it; ContainerIt m_it;
Transform m_transform; const TransformView& m_view;
}; };
TransformView(Container& container, Transform transform)
template<typename Container, typename Transform>
struct TransformedContainer
{
using iterator = TransformedIterator<decltype(begin(std::declval<Container>())), Transform>;
TransformedContainer(Container& container, Transform transform)
: m_container(container), m_transform(std::move(transform)) {} : m_container(container), m_transform(std::move(transform)) {}
iterator begin() const { return iterator(m_transform, m_container.begin()); } Iterator begin() const { return {*this, m_container.begin()}; }
iterator end() const { return iterator(m_transform, m_container.end()); } Iterator end() const { return {*this, m_container.end()}; }
private: private:
Container& m_container; Container& m_container;
Transform m_transform; Transform m_transform;
}; };
template<typename Container, typename Transform> template<typename Transform>
TransformedContainer<Container, Transform> transformed(Container&& container, Transform 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 // Todo: move that into the following functions once we can remove the decltype

View File

@ -95,8 +95,7 @@ CandidateList FaceRegistry::complete_alias_name(StringView prefix,
ByteCount cursor_pos) const ByteCount cursor_pos) const
{ {
return complete(prefix, cursor_pos, return complete(prefix, cursor_pos,
transformed(m_aliases, m_aliases | transform([](const AliasMap::value_type& v) -> const String&
[](const AliasMap::value_type& v) -> const String&
{ return v.first; })); { return v.first; }));
} }

View File

@ -63,7 +63,7 @@ String parse_filename(StringView filename)
std::pair<StringView, StringView> split_path(StringView path) std::pair<StringView, StringView> split_path(StringView path)
{ {
auto it = find(reversed(path), '/'); auto it = find(path | reverse(), '/');
if (it == path.rend()) if (it == path.rend())
return { {}, path }; return { {}, path };
const char* slash = it.base()-1; const char* slash = it.base()-1;

View File

@ -52,10 +52,9 @@ Completions HighlighterGroup::complete_child(StringView path, ByteCount cursor_p
auto candidates = complete( auto candidates = complete(
path, cursor_pos, path, cursor_pos,
transformed(filtered(m_highlighters, m_highlighters | filter([=](const HighlighterMap::Element& hl)
[=](const HighlighterMap::Element& hl) { return not group or hl.value->has_children(); })
{ return not group or hl.value->has_children(); }), | transform(HighlighterMap::get_id));
HighlighterMap::get_id));
return { 0, 0, std::move(candidates) }; return { 0, 0, std::move(candidates) };
} }

View File

@ -1303,7 +1303,7 @@ public:
return offset_pos(hl.complete_child(path.substr(offset), cursor_pos - offset, group), offset); 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) }; return { 0, 0, complete(path, cursor_pos, container) };
} }

View File

@ -31,7 +31,7 @@ CandidateList HookManager::complete_hook_group(StringView prefix, ByteCount pos_
CandidateList res; CandidateList res;
for (auto& list : m_hook) 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)) for (auto& c : complete(prefix, pos_in_token, container))
{ {
if (!contains(res, c)) if (!contains(res, c))

View File

@ -763,7 +763,11 @@ public:
CandidateList& candidates = m_completions.candidates; CandidateList& candidates = m_completions.candidates;
// first try, we need to ask our completer for completions // first try, we need to ask our completer for completions
if (candidates.empty()) if (candidates.empty())
{
refresh_completions(CompletionFlags::None); refresh_completions(CompletionFlags::None);
if (candidates.size() > 1)
return;
}
if (candidates.empty()) if (candidates.empty())
return; return;

446
src/json_ui.cc Normal file
View 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
View 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

View File

@ -14,6 +14,7 @@
#include "insert_completer.hh" #include "insert_completer.hh"
#include "shared_string.hh" #include "shared_string.hh"
#include "ncurses_ui.hh" #include "ncurses_ui.hh"
#include "json_ui.hh"
#include "parameters_parser.hh" #include "parameters_parser.hh"
#include "register_manager.hh" #include "register_manager.hh"
#include "remote.hh" #include "remote.hh"
@ -60,8 +61,8 @@ void register_env_vars()
}, { }, {
"buflist", false, "buflist", false,
[](StringView name, const Context& context) [](StringView name, const Context& context)
{ return join(transformed(BufferManager::instance(), { return join(BufferManager::instance() |
[](const SafePtr<Buffer>& b) transform([](const SafePtr<Buffer>& b)
{ return b->display_name(); }), ':'); } { return b->display_name(); }), ':'); }
}, { }, {
"timestamp", false, "timestamp", false,
@ -261,6 +262,13 @@ struct convert_to_client_mode
String buffer_name; String buffer_name;
}; };
enum class UIType
{
NCurses,
Json,
Dummy,
};
static Client* local_client = nullptr; static Client* local_client = nullptr;
static UserInterface* local_ui = nullptr; static UserInterface* local_ui = nullptr;
static bool convert_to_client_pending = false; static bool convert_to_client_pending = false;
@ -278,7 +286,7 @@ pid_t fork_server_to_background()
return 0; return 0;
} }
std::unique_ptr<UserInterface> create_local_ui(bool dummy_ui) std::unique_ptr<UserInterface> make_ui(UIType ui_type)
{ {
struct DummyUI : UserInterface struct DummyUI : UserInterface
{ {
@ -295,13 +303,24 @@ std::unique_ptr<UserInterface> create_local_ui(bool dummy_ui)
CharCoord dimensions() override { return {24,80}; } CharCoord dimensions() override { return {24,80}; }
bool is_key_available() override { return false; } bool is_key_available() override { return false; }
Key get_key() override { return Key::Invalid; } Key get_key() override { return Key::Invalid; }
void refresh() override {} void refresh(bool) override {}
void set_input_callback(InputCallback) override {} void set_input_callback(InputCallback) override {}
void set_ui_options(const Options&) override {} void set_ui_options(const Options&) override {}
}; };
if (dummy_ui) switch (ui_type)
return make_unique<DummyUI>(); {
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 struct LocalUI : NCursesUI
{ {
@ -406,13 +425,12 @@ void signal_handler(int signal)
abort(); abort();
} }
int run_client(StringView session, StringView init_command) int run_client(StringView session, StringView init_command, UIType ui_type)
{ {
try try
{ {
EventManager event_manager; EventManager event_manager;
RemoteClient client{session, make_unique<NCursesUI>(), RemoteClient client{session, make_ui(ui_type), get_env_vars(), init_command};
get_env_vars(), init_command};
while (true) while (true)
event_manager.handle_next_events(EventMode::Normal); 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, 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) ConstArrayView<StringView> files, LineCount target_line)
{ {
static bool terminate = false; 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 // create buffers in reverse order so that the first given buffer
// is the most recently created one. // is the most recently created one.
for (auto& file : reversed(files)) for (auto& file : files | reverse())
{ {
try try
{ {
@ -526,7 +544,7 @@ int run_server(StringView session, StringView init_command,
if (not daemon) if (not daemon)
{ {
local_client = client_manager.create_client( 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) if (local_client)
{ {
@ -699,7 +717,7 @@ int main(int argc, char* argv[])
{ "p", { true, "just send stdin as commands to the given session" } }, { "p", { true, "just send stdin as commands to the given session" } },
{ "f", { true, "act as a filter, executing given keys on given files" } }, { "f", { true, "act as a filter, executing given keys on given files" } },
{ "q", { false, "in filter mode, be quiet about errors applying keys" } }, { "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" } } } { "l", { false, "list existing sessions" } } }
}; };
try try
@ -730,6 +748,17 @@ int main(int argc, char* argv[])
} }
auto init_command = parser.get_switch("e").value_or(StringView{}); 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")) if (auto keys = parser.get_switch("f"))
{ {
@ -754,7 +783,7 @@ int main(int argc, char* argv[])
for (auto name : parser) for (auto name : parser)
new_files += format("edit '{}';", escape(real_path(name), "'", '\\')); 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 else
{ {
@ -780,15 +809,14 @@ int main(int argc, char* argv[])
return run_server(session, init_command, return run_server(session, init_command,
(bool)parser.get_switch("n"), (bool)parser.get_switch("n"),
(bool)parser.get_switch("d"), (bool)parser.get_switch("d"),
(bool)parser.get_switch("u"), ui_type, files, target_line);
files, target_line);
} }
catch (convert_to_client_mode& convert) catch (convert_to_client_mode& convert)
{ {
raise(SIGTSTP); raise(SIGTSTP);
return run_client(convert.session, return run_client(convert.session,
format("try %^buffer '{}'^; echo converted to client only mode", format("try %^buffer '{}'^; echo converted to client only mode",
escape(convert.buffer_name, "'^", '\\'))); escape(convert.buffer_name, "'^", '\\')), ui_type);
} }
} }
} }

View File

@ -301,9 +301,12 @@ void NCursesUI::redraw()
doupdate(); doupdate();
} }
void NCursesUI::refresh() void NCursesUI::refresh(bool force)
{ {
if (m_dirty) if (force)
redrawwin(m_window);
if (m_dirty or force)
redraw(); redraw();
m_dirty = false; m_dirty = false;
} }
@ -491,11 +494,6 @@ Key NCursesUI::get_key()
if (c > 0 and c < 27) if (c > 0 and c < 27)
{ {
if (c == control('l'))
{
redrawwin(m_window);
redraw();
}
if (c == control('z')) if (c == control('z'))
{ {
raise(SIGTSTP); raise(SIGTSTP);
@ -674,6 +672,10 @@ void NCursesUI::menu_show(ConstArrayView<DisplayLine> items,
auto width = is_prompt ? maxsize.column : min(longest+1, maxsize.column); auto width = is_prompt ? maxsize.column : min(longest+1, maxsize.column);
m_menu.create({line, anchor.column}, {height, width}); m_menu.create({line, anchor.column}, {height, width});
draw_menu(); 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) void NCursesUI::menu_select(int selected)

View File

@ -43,7 +43,7 @@ public:
InfoStyle style) override; InfoStyle style) override;
void info_hide() override; void info_hide() override;
void refresh() override; void refresh(bool force) override;
void set_input_callback(InputCallback callback) override; void set_input_callback(InputCallback callback) override;

View File

@ -1560,6 +1560,15 @@ void ensure_forward(Context& context, NormalParams)
context.selections().check_invariant(); 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[] = static NormalCmdDesc cmds[] =
{ {
{ 'h', "move left", move<CharCount, Backward> }, { 'h', "move left", move<CharCount, Backward> },
@ -1734,6 +1743,8 @@ static NormalCmdDesc cmds[] =
{ 'z', "restore selections", restore_selections<false> }, { 'z', "restore selections", restore_selections<false> },
{ alt('z'), "append saved selections", restore_selections<true> }, { alt('z'), "append saved selections", restore_selections<true> },
{ 'Z', "save selections", save_selections }, { 'Z', "save selections", save_selections },
{ ctrl('l'), "force redraw", force_redraw },
}; };
KeyMap keymap = cmds; KeyMap keymap = cmds;

View File

@ -255,7 +255,7 @@ public:
const DisplayLine& mode_line, const DisplayLine& mode_line,
const Face& default_face) override; const Face& default_face) override;
void refresh() override; void refresh(bool force) override;
bool is_key_available() override; bool is_key_available() override;
Key get_key() override; Key get_key() override;
@ -353,10 +353,11 @@ void RemoteUI::draw_status(const DisplayLine& status_line,
msg.write(default_face); msg.write(default_face);
} }
void RemoteUI::refresh() void RemoteUI::refresh(bool force)
{ {
Message msg(m_socket_watcher.fd()); Message msg(m_socket_watcher.fd());
msg.write(RemoteUIMsg::Refresh); msg.write(RemoteUIMsg::Refresh);
msg.write(force);
} }
void RemoteUI::set_ui_options(const Options& options) void RemoteUI::set_ui_options(const Options& options)
@ -516,7 +517,7 @@ void RemoteClient::process_next_message()
break; break;
} }
case RemoteUIMsg::Refresh: case RemoteUIMsg::Refresh:
m_ui->refresh(); m_ui->refresh(read<bool>(socket));
break; break;
case RemoteUIMsg::SetOptions: case RemoteUIMsg::SetOptions:
m_ui->set_ui_options(read_idmap<String, MemoryDomain::Options>(socket)); m_ui->set_ui_options(read_idmap<String, MemoryDomain::Options>(socket));

View File

@ -555,7 +555,7 @@ String selection_to_string(const Selection& selection)
String selection_list_to_string(const SelectionList& selections) String selection_list_to_string(const SelectionList& selections)
{ {
return join(transformed(selections, [](const Selection& s) return join(selections | transform([](const Selection& s)
{ return selection_to_string(s); }), { return selection_to_string(s); }),
':', false); ':', false);
} }

View File

@ -128,7 +128,7 @@ find_surrounding(Iterator begin, Iterator end,
{ {
using RevIt = std::reverse_iterator<Iterator>; using RevIt = std::reverse_iterator<Iterator>;
auto res = find_closing(RevIt{pos+1}, RevIt{begin}, auto res = find_closing(RevIt{pos+1}, RevIt{begin},
reversed(closing), reversed(opening), closing | reverse(), opening | reverse(),
init_level, nestable); init_level, nestable);
if (not res) if (not res)
return {}; return {};

View File

@ -63,7 +63,7 @@ public:
virtual bool is_key_available() = 0; virtual bool is_key_available() = 0;
virtual Key get_key() = 0; virtual Key get_key() = 0;
virtual void refresh() = 0; virtual void refresh(bool force) = 0;
virtual void set_input_callback(InputCallback callback) = 0; virtual void set_input_callback(InputCallback callback) = 0;

View File

@ -0,0 +1 @@
<c-l>:q<ret>

View 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] }

View File

@ -0,0 +1 @@
"abcdefgh" hehe ${ youhou{hihi} }

View 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

View File

@ -12,7 +12,7 @@ main() {
trap "rm -R $work" EXIT trap "rm -R $work" EXIT
for dir in $(find $dirs -type d | sort); do for dir in $(find $dirs -type d | sort); do
cd $test/$dir; 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; cd $work/$dir;
indent="$(echo "${dir}/" | sed -e 's|[^/]*/\+| |g')" indent="$(echo "${dir}/" | sed -e 's|[^/]*/\+| |g')"
name=$(basename $PWD) name=$(basename $PWD)
@ -26,6 +26,8 @@ main() {
touch in; cp in out touch in; cp in out
kak_commands=" kak_commands="
set global autoreload yes set global autoreload yes
set global autoinfo ''
set global autoshowcompl false
try %{ try %{
source rc source rc
} }
@ -40,6 +42,7 @@ main() {
quit! quit!
} }
exec '$(cat cmd | sed -e s/\'/\\\\\'/g)' exec '$(cat cmd | sed -e s/\'/\\\\\'/g)'
exec <c-l>
eval -buffer *debug* write debug eval -buffer *debug* write debug
nop %sh{ nop %sh{
IFS== IFS==
@ -49,7 +52,9 @@ main() {
write out write out
quit! 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=$? retval=$?
if [ $should_fail = 0 ]; then if [ $should_fail = 0 ]; then
if [ $retval -ne 0 ]; then if [ $retval -ne 0 ]; then
@ -75,10 +80,12 @@ main() {
echo "$line" | colorize $color normal echo "$line" | colorize $color normal
done done
echo echo
if test -e debug; then
echo "debug buffer:" | colorize yellow normal echo "debug buffer:" | colorize yellow normal
cat debug cat debug
echo echo
fi fi
fi
done done
fi fi
else else