summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMaxime Coste <frrrwww@gmail.com>2016-03-09 23:29:46 +0000
committerMaxime Coste <frrrwww@gmail.com>2016-03-09 23:29:46 +0000
commitfcd3437fac01ecb942d3fd9116d1d1a3c455c59e (patch)
tree479798e1738023100c58b9cb9ef13838bb08b85d /src
parent2d494ccb8e72782a0a015c8890d39ab00d5ce4aa (diff)
parent517abf5e74636052007db0938067aee7bf658965 (diff)
Merge branch 'json-ui'
Diffstat (limited to 'src')
-rw-r--r--src/alias_registry.cc2
-rw-r--r--src/alias_registry.hh3
-rw-r--r--src/buffer.cc2
-rw-r--r--src/buffer.inl.hh3
-rw-r--r--src/client.cc97
-rw-r--r--src/client.hh15
-rw-r--r--src/client_manager.cc8
-rw-r--r--src/command_manager.cc54
-rw-r--r--src/command_manager.hh2
-rw-r--r--src/commands.cc4
-rw-r--r--src/containers.hh242
-rw-r--r--src/face_registry.cc5
-rw-r--r--src/file.cc2
-rw-r--r--src/highlighter_group.cc7
-rw-r--r--src/highlighters.cc2
-rw-r--r--src/hook_manager.cc2
-rw-r--r--src/input_handler.cc4
-rw-r--r--src/json_ui.cc446
-rw-r--r--src/json_ui.hh63
-rw-r--r--src/main.cc64
-rw-r--r--src/ncurses_ui.cc16
-rw-r--r--src/ncurses_ui.hh2
-rw-r--r--src/normal.cc11
-rw-r--r--src/remote.cc7
-rw-r--r--src/selection.cc4
-rw-r--r--src/selectors.cc2
-rw-r--r--src/user_interface.hh2
27 files changed, 861 insertions, 210 deletions
diff --git a/src/alias_registry.cc b/src/alias_registry.cc
index 6e92c006..690cde54 100644
--- a/src/alias_registry.cc
+++ b/src/alias_registry.cc
@@ -57,7 +57,7 @@ Vector<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;
diff --git a/src/alias_registry.hh b/src/alias_registry.hh
index 7f688e08..5ff093bb 100644
--- a/src/alias_registry.hh
+++ b/src/alias_registry.hh
@@ -20,7 +20,8 @@ public:
using iterator = AliasMap::const_iterator;
Vector<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;
diff --git a/src/buffer.cc b/src/buffer.cc
index e140f333..c0e25f0a 100644
--- a/src/buffer.cc
+++ b/src/buffer.cc
@@ -304,7 +304,7 @@ bool Buffer::undo()
--m_history_cursor;
- for (const Modification& modification : reversed(*m_history_cursor))
+ for (const Modification& modification : *m_history_cursor | reverse())
apply_modification(modification.inverse());
return true;
}
diff --git a/src/buffer.inl.hh b/src/buffer.inl.hh
index d6af3d4a..8397be33 100644
--- a/src/buffer.inl.hh
+++ b/src/buffer.inl.hh
@@ -104,7 +104,8 @@ inline ByteCoord Buffer::back_coord() const
inline ByteCoord Buffer::end_coord() const
{
- return { line_count() - 1, m_lines.back().length() };
+ return m_lines.empty() ?
+ ByteCoord{0,0} : ByteCoord{ line_count() - 1, m_lines.back().length() };
}
inline BufferIterator::BufferIterator(const Buffer& buffer, ByteCoord coord)
diff --git a/src/client.cc b/src/client.cc
index 7dd22f88..cf8b3e71 100644
--- a/src/client.cc
+++ b/src/client.cc
@@ -36,7 +36,7 @@ Client::Client(std::unique_ptr<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;
-
- m_ui->draw_status(m_status_line, m_mode_line, get_face("StatusLine"));
- m_ui_dirty = true;
+ 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_dirty)
+ if (m_ui_pending & InfoShow)
{
- m_ui_dirty = false;
- m_ui->refresh();
+ 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->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;
}
}
diff --git a/src/client.hh b/src/client.hh
index b5d13b7d..3fa36907 100644
--- a/src/client.hh
+++ b/src/client.hh
@@ -74,7 +74,6 @@ private:
DisplayLine generate_mode_line() const;
- bool m_ui_dirty = false;
std::unique_ptr<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;
diff --git a/src/client_manager.cc b/src/client_manager.cc
index b625fdc5..aff88d93 100644
--- a/src/client_manager.cc
+++ b/src/client_manager.cc
@@ -87,7 +87,7 @@ void ClientManager::remove_client(Client& client, bool graceful)
WindowAndSelections ClientManager::get_free_window(Buffer& buffer)
{
- auto it = find_if(reversed(m_free_windows),
+ auto it = find_if(m_free_windows | reverse(),
[&](const WindowAndSelections& ws)
{ return &ws.window->buffer() == &buffer; });
@@ -177,10 +177,8 @@ void ClientManager::redraw_clients() const
CandidateList ClientManager::complete_client_name(StringView prefix,
ByteCount cursor_pos) const
{
- auto c = transformed(m_clients,
- [](const std::unique_ptr<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);
}
diff --git a/src/command_manager.cc b/src/command_manager.cc
index 8dfb5181..4902f068 100644
--- a/src/command_manager.cc
+++ b/src/command_manager.cc
@@ -512,6 +512,20 @@ CommandInfo CommandManager::command_info(const Context& context, StringView comm
return res;
}
+Completions CommandManager::complete_command_name(const Context& context,
+ StringView query) const
+{
+ auto candidates = Kakoune::complete(
+ query, query.length(), concatenated(
+ m_commands
+ | filter([](const CommandMap::value_type& cmd) { return not (cmd.second.flags & CommandFlags::Hidden); })
+ | transform([](const CommandMap::value_type& cmd) { return StringView{cmd.first}; }),
+ context.aliases().flatten_aliases()
+ | transform([](AliasRegistry::AliasDesc alias) { return alias.first; })));
+
+ return {0, query.length(), std::move(candidates)};
+}
+
Completions CommandManager::complete(const Context& context,
CompletionFlags flags,
StringView command_line,
@@ -533,33 +547,16 @@ Completions CommandManager::complete(const Context& context,
}
}
+ const bool is_last_token = tok_idx == tokens.size();
// command name completion
if (tokens.empty() or
- (tok_idx == cmd_idx and (tok_idx == tokens.size() or
+ (tok_idx == cmd_idx and (is_last_token or
tokens[tok_idx].type() == Token::Type::Raw or
tokens[tok_idx].type() == Token::Type::RawQuoted)))
{
- const bool is_end_token = tok_idx == tokens.size();
- ByteCount cmd_start = is_end_token ? cursor_pos
- : tokens[tok_idx].begin();
- Completions result(cmd_start, cursor_pos);
- StringView prefix = command_line.substr(cmd_start,
- cursor_pos - cmd_start);
-
- for (auto& command : m_commands)
- {
- if (command.second.flags & CommandFlags::Hidden)
- continue;
- if (prefix_match(command.first, prefix))
- result.candidates.push_back(command.first);
- }
- for (auto& alias : context.aliases().flatten_aliases())
- {
- if (prefix_match(alias.first, prefix))
- result.candidates.push_back(alias.first.str());
- }
- std::sort(result.candidates.begin(), result.candidates.end());
- return result;
+ auto cmd_start = is_last_token ? cursor_pos : tokens[tok_idx].begin();
+ StringView query = command_line.substr(cmd_start, cursor_pos - cmd_start);
+ return offset_pos(complete_command_name(context, query), cmd_start);
}
kak_assert(not tokens.empty());
@@ -628,18 +625,7 @@ Completions CommandManager::complete(const Context& context,
StringView prefix = params[token_to_complete].substr(0, pos_in_token);
if (token_to_complete == 0)
- {
- CandidateList candidates;
- for (auto& command : m_commands)
- {
- if (command.second.flags & CommandFlags::Hidden)
- continue;
- if (prefix_match(command.first, prefix))
- candidates.push_back(command.first);
- }
- std::sort(candidates.begin(), candidates.end());
- return {0, pos_in_token, std::move(candidates)};
- }
+ return complete_command_name(context, prefix);
else
{
const String& command_name = params[0];
diff --git a/src/command_manager.hh b/src/command_manager.hh
index dbc87e81..090ed0fb 100644
--- a/src/command_manager.hh
+++ b/src/command_manager.hh
@@ -130,6 +130,8 @@ private:
const ShellContext& shell_context,
CharCoord pos) const;
+ Completions complete_command_name(const Context& context, StringView query) const;
+
struct CommandDescriptor
{
Command command;
diff --git a/src/commands.cc b/src/commands.cc
index 3a8d6132..b376f081 100644
--- a/src/commands.cc
+++ b/src/commands.cc
@@ -543,7 +543,7 @@ Completions add_highlighter_completer(
if (token_to_complete == 1 and params[0] == "-group")
return complete_highlighter(context, params[1], pos_in_token, true);
else if (token_to_complete == 0 or (token_to_complete == 2 and params[0] == "-group"))
- return { 0_byte, arg.length(), complete(arg, pos_in_token, transformed(HighlighterRegistry::instance(), HighlighterRegistry::get_id)) };
+ return { 0_byte, arg.length(), complete(arg, pos_in_token, HighlighterRegistry::instance() | transform(HighlighterRegistry::get_id)) };
return Completions{};
}
@@ -626,7 +626,7 @@ const CommandDesc rm_highlighter_cmd = {
[](const ParametersParser& parser, Context& context, const ShellContext&)
{
StringView path = parser[0];
- auto sep_it = find(reversed(path), '/');
+ auto sep_it = find(path | reverse(), '/');
auto& group = sep_it != path.rend() ?
get_highlighter(context, {path.begin(), sep_it.base()})
: context.window().highlighters();
diff --git a/src/containers.hh b/src/containers.hh
index 6bf3bea6..9597a25b 100644
--- a/src/containers.hh
+++ b/src/containers.hh
@@ -8,11 +8,21 @@
namespace Kakoune
{
+template<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)
-{
- return ReversedContainer<Container>(container);
-}
-
-template<typename Iterator, typename Filter>
-struct FilteredIterator : std::iterator<std::forward_iterator_tag,
- typename Iterator::value_type>
+struct ReverseFactory
{
- 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; }
+inline ContainerView<ReverseFactory> reverse() { return {}; }
- friend bool operator==(const FilteredIterator& lhs, const FilteredIterator& rhs)
- {
- return lhs.m_it == rhs.m_it;
- }
+template<typename Container, typename Filter>
+struct FilterView
+{
+ using ContainerIt = decltype(begin(std::declval<Container>()));
- friend bool operator!=(const FilteredIterator& lhs, const FilteredIterator& rhs)
+ struct Iterator : std::iterator<std::forward_iterator_tag,
+ typename ContainerIt::value_type>
{
- return not (lhs == rhs);
- }
+ 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 base() const { return m_it; }
+ Iterator begin() const { return {*this, m_container.begin(), m_container.end()}; }
+ Iterator end() const { return {*this, m_container.end(), m_container.end()}; }
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;
+ Container& m_container;
+ Filter m_filter;
};
-template<typename Container, typename Filter>
-struct FilteredContainer
+template<typename Filter>
+struct FilterFactory
{
- using iterator = FilteredIterator<decltype(begin(std::declval<Container>())), Filter>;
- FilteredContainer(Container& container, Filter filter)
- : m_container(container), m_filter(std::move(filter)) {}
+ template<typename Container>
+ FilterView<Container, Filter> operator()(Container&& container) const { return {container, std::move(m_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()); }
-
-private:
- Container& m_container;
Filter m_filter;
};
-template<typename Container, typename Filter>
-FilteredContainer<Container, Filter> filtered(Container&& container, Filter filter)
-{
- return FilteredContainer<Container, Filter>(container, std::move(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>
+template<typename Container, typename Transform>
+struct TransformView
{
- 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; }
+ using ContainerIt = decltype(begin(std::declval<Container>()));
- friend bool operator==(const TransformedIterator& lhs, const TransformedIterator& rhs)
+ struct Iterator : std::iterator<std::forward_iterator_tag,
+ typename std::remove_reference<TransformedResult<ContainerIt, Transform>>::type>
{
- return lhs.m_it == rhs.m_it;
- }
+ Iterator(const TransformView& view, ContainerIt it)
+ : m_it{std::move(it)}, m_view{view} {}
- friend bool operator!=(const TransformedIterator& lhs, const TransformedIterator& rhs)
- {
- return not (lhs == 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;
+ }
+
+ 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 base() const { return m_it; }
+ Iterator begin() const { return {*this, m_container.begin()}; }
+ Iterator end() const { return {*this, m_container.end()}; }
private:
- Iterator m_it;
+ Container& m_container;
Transform m_transform;
};
+template<typename Transform>
+struct TransformFactory
+{
+ template<typename Container>
+ TransformView<Container, Transform> operator()(Container&& container) const { return {container, std::move(m_transform)}; }
-template<typename Container, typename Transform>
-struct TransformedContainer
+ 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 iterator = TransformedIterator<decltype(begin(std::declval<Container>())), Transform>;
- TransformedContainer(Container& container, Transform transform)
- : m_container(container), m_transform(std::move(transform)) {}
+ 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;
+ }
- iterator begin() const { return iterator(m_transform, m_container.begin()); }
- iterator end() const { return iterator(m_transform, m_container.end()); }
+ 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:
- Container& m_container;
- Transform m_transform;
+ Container1& m_container1;
+ Container2& m_container2;
};
-template<typename Container, typename Transform>
-TransformedContainer<Container, Transform> transformed(Container&& container, Transform transform)
+template<typename Container1, typename Container2>
+ConcatView<Container1, Container2> concatenated(Container1&& container1, Container2&& container2)
{
- return TransformedContainer<Container, Transform>(container, std::move(transform));
+ return {container1, container2};
}
// Todo: move that into the following functions once we can remove the decltype
diff --git a/src/face_registry.cc b/src/face_registry.cc
index 3a5ac6e1..7da57e9c 100644
--- a/src/face_registry.cc
+++ b/src/face_registry.cc
@@ -95,9 +95,8 @@ CandidateList FaceRegistry::complete_alias_name(StringView prefix,
ByteCount cursor_pos) const
{
return complete(prefix, cursor_pos,
- transformed(m_aliases,
- [](const AliasMap::value_type& v) -> const String&
- { return v.first; }));
+ m_aliases | transform([](const AliasMap::value_type& v) -> const String&
+ { return v.first; }));
}
FaceRegistry::FaceRegistry()
diff --git a/src/file.cc b/src/file.cc
index 2d3e6a82..6cf9a735 100644
--- a/src/file.cc
+++ b/src/file.cc
@@ -63,7 +63,7 @@ String parse_filename(StringView filename)
std::pair<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;
diff --git a/src/highlighter_group.cc b/src/highlighter_group.cc
index d3755734..2d0a7a74 100644
--- a/src/highlighter_group.cc
+++ b/src/highlighter_group.cc
@@ -52,10 +52,9 @@ Completions HighlighterGroup::complete_child(StringView path, ByteCount cursor_p
auto candidates = complete(
path, cursor_pos,
- transformed(filtered(m_highlighters,
- [=](const HighlighterMap::Element& hl)
- { return not group or hl.value->has_children(); }),
- HighlighterMap::get_id));
+ m_highlighters | filter([=](const HighlighterMap::Element& hl)
+ { return not group or hl.value->has_children(); })
+ | transform(HighlighterMap::get_id));
return { 0, 0, std::move(candidates) };
}
diff --git a/src/highlighters.cc b/src/highlighters.cc
index 740320ac..871d549c 100644
--- a/src/highlighters.cc
+++ b/src/highlighters.cc
@@ -1303,7 +1303,7 @@ public:
return offset_pos(hl.complete_child(path.substr(offset), cursor_pos - offset, group), offset);
}
- auto container = transformed(m_groups, decltype(m_groups)::get_id);
+ auto container = m_groups | transform(decltype(m_groups)::get_id);
return { 0, 0, complete(path, cursor_pos, container) };
}
diff --git a/src/hook_manager.cc b/src/hook_manager.cc
index 81a00730..389c8504 100644
--- a/src/hook_manager.cc
+++ b/src/hook_manager.cc
@@ -31,7 +31,7 @@ CandidateList HookManager::complete_hook_group(StringView prefix, ByteCount pos_
CandidateList res;
for (auto& list : m_hook)
{
- auto container = transformed(list.value, decltype(list.value)::get_id);
+ auto container = list.value | transform(decltype(list.value)::get_id);
for (auto& c : complete(prefix, pos_in_token, container))
{
if (!contains(res, c))
diff --git a/src/input_handler.cc b/src/input_handler.cc
index 3871398d..b1e71ed3 100644
--- a/src/input_handler.cc
+++ b/src/input_handler.cc
@@ -763,7 +763,11 @@ public:
CandidateList& candidates = m_completions.candidates;
// first try, we need to ask our completer for completions
if (candidates.empty())
+ {
refresh_completions(CompletionFlags::None);
+ if (candidates.size() > 1)
+ return;
+ }
if (candidates.empty())
return;
diff --git a/src/json_ui.cc b/src/json_ui.cc
new file mode 100644
index 00000000..aea412b1
--- /dev/null
+++ b/src/json_ui.cc
@@ -0,0 +1,446 @@
+#include "json_ui.hh"
+
+#include "display_buffer.hh"
+#include "keys.hh"
+#include "file.hh"
+#include "event_manager.hh"
+#include "value.hh"
+#include "unit_tests.hh"
+
+#include <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);
+}};
+
+}
diff --git a/src/json_ui.hh b/src/json_ui.hh
new file mode 100644
index 00000000..563ff997
--- /dev/null
+++ b/src/json_ui.hh
@@ -0,0 +1,63 @@
+#ifndef json_ui_hh_INCLUDED
+#define json_ui_hh_INCLUDED
+
+#include "user_interface.hh"
+#include "event_manager.hh"
+#include "coord.hh"
+
+namespace Kakoune
+{
+
+class Value;
+
+class JsonUI : public UserInterface
+{
+public:
+ JsonUI();
+
+ JsonUI(const JsonUI&) = delete;
+ JsonUI& operator=(const JsonUI&) = delete;
+
+ void draw(const DisplayBuffer& display_buffer,
+ const Face& default_face) override;
+
+ void draw_status(const DisplayLine& status_line,
+ const DisplayLine& mode_line,
+ const Face& default_face) override;
+
+ bool is_key_available() override;
+ Key get_key() override;
+
+ void menu_show(ConstArrayView<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
diff --git a/src/main.cc b/src/main.cc
index 11f6c11d..0a9e713e 100644
--- a/src/main.cc
+++ b/src/main.cc
@@ -14,6 +14,7 @@
#include "insert_completer.hh"
#include "shared_string.hh"
#include "ncurses_ui.hh"
+#include "json_ui.hh"
#include "parameters_parser.hh"
#include "register_manager.hh"
#include "remote.hh"
@@ -60,9 +61,9 @@ void register_env_vars()
}, {
"buflist", false,
[](StringView name, const Context& context)
- { return join(transformed(BufferManager::instance(),
- [](const SafePtr<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);
}
}
}
diff --git a/src/ncurses_ui.cc b/src/ncurses_ui.cc
index 5309ecb8..308527f4 100644
--- a/src/ncurses_ui.cc
+++ b/src/ncurses_ui.cc
@@ -301,9 +301,12 @@ void NCursesUI::redraw()
doupdate();
}
-void NCursesUI::refresh()
+void NCursesUI::refresh(bool force)
{
- if (m_dirty)
+ if (force)
+ redrawwin(m_window);
+
+ if (m_dirty or force)
redraw();
m_dirty = false;
}
@@ -491,11 +494,6 @@ Key NCursesUI::get_key()
if (c > 0 and c < 27)
{
- if (c == control('l'))
- {
- redrawwin(m_window);
- redraw();
- }
if (c == control('z'))
{
raise(SIGTSTP);
@@ -674,6 +672,10 @@ void NCursesUI::menu_show(ConstArrayView<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)
diff --git a/src/ncurses_ui.hh b/src/ncurses_ui.hh
index fdf1b6a7..870a3fc7 100644
--- a/src/ncurses_ui.hh
+++ b/src/ncurses_ui.hh
@@ -43,7 +43,7 @@ public:
InfoStyle style) override;
void info_hide() override;
- void refresh() override;
+ void refresh(bool force) override;
void set_input_callback(InputCallback callback) override;
diff --git a/src/normal.cc b/src/normal.cc
index 4d95ff53..33c813f6 100644
--- a/src/normal.cc
+++ b/src/normal.cc
@@ -1560,6 +1560,15 @@ void ensure_forward(Context& context, NormalParams)
context.selections().check_invariant();
}
+void force_redraw(Context& context, NormalParams)
+{
+ if (context.has_client())
+ {
+ context.client().force_redraw();
+ context.client().redraw_ifn();
+ }
+}
+
static NormalCmdDesc cmds[] =
{
{ 'h', "move left", move<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;
diff --git a/src/remote.cc b/src/remote.cc
index e2756ef1..718d7f7d 100644
--- a/src/remote.cc
+++ b/src/remote.cc
@@ -255,7 +255,7 @@ public:
const DisplayLine& mode_line,
const Face& default_face) override;
- void refresh() override;
+ void refresh(bool force) override;
bool is_key_available() override;
Key get_key() override;
@@ -353,10 +353,11 @@ void RemoteUI::draw_status(const DisplayLine& status_line,
msg.write(default_face);
}
-void RemoteUI::refresh()
+void RemoteUI::refresh(bool force)
{
Message msg(m_socket_watcher.fd());
msg.write(RemoteUIMsg::Refresh);
+ msg.write(force);
}
void RemoteUI::set_ui_options(const Options& options)
@@ -516,7 +517,7 @@ void RemoteClient::process_next_message()
break;
}
case RemoteUIMsg::Refresh:
- m_ui->refresh();
+ m_ui->refresh(read<bool>(socket));
break;
case RemoteUIMsg::SetOptions:
m_ui->set_ui_options(read_idmap<String, MemoryDomain::Options>(socket));
diff --git a/src/selection.cc b/src/selection.cc
index f24b0939..90580bdd 100644
--- a/src/selection.cc
+++ b/src/selection.cc
@@ -555,8 +555,8 @@ String selection_to_string(const Selection& selection)
String selection_list_to_string(const SelectionList& selections)
{
- return join(transformed(selections, [](const Selection& s)
- { return selection_to_string(s); }),
+ return join(selections | transform([](const Selection& s)
+ { return selection_to_string(s); }),
':', false);
}
diff --git a/src/selectors.cc b/src/selectors.cc
index 662cd6b0..1ca42a28 100644
--- a/src/selectors.cc
+++ b/src/selectors.cc
@@ -128,7 +128,7 @@ find_surrounding(Iterator begin, Iterator end,
{
using RevIt = std::reverse_iterator<Iterator>;
auto res = find_closing(RevIt{pos+1}, RevIt{begin},
- reversed(closing), reversed(opening),
+ closing | reverse(), opening | reverse(),
init_level, nestable);
if (not res)
return {};
diff --git a/src/user_interface.hh b/src/user_interface.hh
index 67553922..e8a30a4b 100644
--- a/src/user_interface.hh
+++ b/src/user_interface.hh
@@ -63,7 +63,7 @@ public:
virtual bool is_key_available() = 0;
virtual Key get_key() = 0;
- virtual void refresh() = 0;
+ virtual void refresh(bool force) = 0;
virtual void set_input_callback(InputCallback callback) = 0;