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();
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;

View File

@ -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;

View File

@ -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;
}

View File

@ -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)

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_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;
}
}

View File

@ -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;

View File

@ -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);
}

View File

@ -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];

View File

@ -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;

View File

@ -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();

View File

@ -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

View File

@ -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()

View File

@ -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;

View File

@ -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) };
}

View File

@ -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) };
}

View File

@ -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))

View File

@ -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
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 "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);
}
}
}

View File

@ -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)

View File

@ -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;

View File

@ -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;

View File

@ -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));

View File

@ -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);
}

View File

@ -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 {};

View File

@ -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;

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
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