Menu: support searching through choices with /
when hitting / while in a menu, a regex filter can be entered so that only entries matching it are selectable, <esc> disable filtering while a second <esc> close the menu as usual.
This commit is contained in:
parent
148466c659
commit
4be6882bd5
|
@ -132,7 +132,8 @@ public:
|
||||||
Menu(Context& context, const memoryview<String>& choices,
|
Menu(Context& context, const memoryview<String>& choices,
|
||||||
MenuCallback callback)
|
MenuCallback callback)
|
||||||
: ClientMode(context.client()),
|
: ClientMode(context.client()),
|
||||||
m_callback(callback), m_choice_count(choices.size()), m_selected(0)
|
m_callback(callback), m_choices(choices.begin(), choices.end()),
|
||||||
|
m_selected(m_choices.begin())
|
||||||
{
|
{
|
||||||
DisplayCoord menu_pos{ context.window().dimensions().line, 0_char };
|
DisplayCoord menu_pos{ context.window().dimensions().line, 0_char };
|
||||||
context.ui().menu_show(choices, menu_pos, MenuStyle::Prompt);
|
context.ui().menu_show(choices, menu_pos, MenuStyle::Prompt);
|
||||||
|
@ -140,53 +141,91 @@ public:
|
||||||
|
|
||||||
void on_key(const Key& key, Context& context) override
|
void on_key(const Key& key, Context& context) override
|
||||||
{
|
{
|
||||||
if (key == Key::Down or
|
auto match_filter = [this](const String& str) {
|
||||||
|
return boost::regex_match(str.begin(), str.end(), m_filter);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (key == Key(Key::Modifiers::Control, 'm'))
|
||||||
|
{
|
||||||
|
context.ui().menu_hide();
|
||||||
|
context.ui().print_status("");
|
||||||
|
// save callback as reset_normal_mode will delete this
|
||||||
|
MenuCallback callback = std::move(m_callback);
|
||||||
|
int selected = m_selected - m_choices.begin();
|
||||||
|
reset_normal_mode();
|
||||||
|
callback(selected, context);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (key == Key::Escape)
|
||||||
|
{
|
||||||
|
if (m_edit_filter)
|
||||||
|
{
|
||||||
|
m_edit_filter = false;
|
||||||
|
m_filter = boost::regex(".*");
|
||||||
|
m_filter_editor.reset("");
|
||||||
|
context.ui().print_status("");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
context.ui().menu_hide();
|
||||||
|
reset_normal_mode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (key == Key::Down or
|
||||||
key == Key(Key::Modifiers::Control, 'i') or
|
key == Key(Key::Modifiers::Control, 'i') or
|
||||||
key == Key(Key::Modifiers::Control, 'n') or
|
key == Key(Key::Modifiers::Control, 'n') or
|
||||||
key == Key(Key::Modifiers::None, 'j'))
|
key == Key(Key::Modifiers::None, 'j'))
|
||||||
{
|
{
|
||||||
if (++m_selected >= m_choice_count)
|
auto it = std::find_if(m_selected+1, m_choices.end(), match_filter);
|
||||||
m_selected = 0;
|
if (it == m_choices.end())
|
||||||
context.ui().menu_select(m_selected);
|
it = std::find_if(m_choices.begin(), m_selected+1, match_filter);
|
||||||
|
m_selected = it;
|
||||||
|
context.ui().menu_select(m_selected - m_choices.begin());
|
||||||
}
|
}
|
||||||
if (key == Key::Up or
|
else if (key == Key::Up or
|
||||||
key == Key::BackTab or
|
key == Key::BackTab or
|
||||||
key == Key(Key::Modifiers::Control, 'p') or
|
key == Key(Key::Modifiers::Control, 'p') or
|
||||||
key == Key(Key::Modifiers::None, 'k'))
|
key == Key(Key::Modifiers::None, 'k'))
|
||||||
{
|
{
|
||||||
if (--m_selected < 0)
|
ChoiceList::const_reverse_iterator selected(m_selected);
|
||||||
m_selected = m_choice_count-1;
|
auto it = std::find_if(selected, m_choices.rend(), match_filter);
|
||||||
context.ui().menu_select(m_selected);
|
if (it == m_choices.rend())
|
||||||
|
it = std::find_if(m_choices.rbegin(), selected, match_filter);
|
||||||
|
m_selected = it.base()-1;
|
||||||
|
context.ui().menu_select(m_selected - m_choices.begin());
|
||||||
}
|
}
|
||||||
if (key == Key(Key::Modifiers::Control, 'm'))
|
else if (key == '/' and not m_edit_filter)
|
||||||
{
|
{
|
||||||
context.ui().menu_hide();
|
m_edit_filter = true;
|
||||||
// save callback as reset_normal_mode will delete this
|
|
||||||
MenuCallback callback = std::move(m_callback);
|
|
||||||
int selected = m_selected;
|
|
||||||
reset_normal_mode();
|
|
||||||
callback(selected, context);
|
|
||||||
}
|
}
|
||||||
if (key == Key::Escape)
|
else if (m_edit_filter)
|
||||||
{
|
{
|
||||||
context.ui().menu_hide();
|
m_filter_editor.handle_key(key);
|
||||||
reset_normal_mode();
|
|
||||||
}
|
auto search = ".*" + m_filter_editor.line() + ".*";
|
||||||
if (key.modifiers == Key::Modifiers::None and
|
m_filter = boost::regex(search.begin(), search.end());
|
||||||
key.key >= '0' and key.key <= '9')
|
auto it = std::find_if(m_selected, m_choices.end(), match_filter);
|
||||||
{
|
if (it == m_choices.end())
|
||||||
context.ui().menu_hide();
|
it = std::find_if(m_choices.begin(), m_selected, match_filter);
|
||||||
// save callback as reset_normal_mode will delete this
|
m_selected = it;
|
||||||
MenuCallback callback = std::move(m_callback);
|
context.ui().menu_select(m_selected - m_choices.begin());
|
||||||
reset_normal_mode();
|
|
||||||
callback(key.key - '0' - 1, context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (m_edit_filter)
|
||||||
|
context.ui().print_status("/" + m_filter_editor.line(),
|
||||||
|
m_filter_editor.cursor_pos() + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
MenuCallback m_callback;
|
MenuCallback m_callback;
|
||||||
int m_selected;
|
|
||||||
int m_choice_count;
|
using ChoiceList = std::vector<String>;
|
||||||
|
const ChoiceList m_choices;
|
||||||
|
ChoiceList::const_iterator m_selected;
|
||||||
|
|
||||||
|
boost::regex m_filter = boost::regex(".*");
|
||||||
|
bool m_edit_filter = false;
|
||||||
|
LineEditor m_filter_editor;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Prompt : public ClientMode
|
class Prompt : public ClientMode
|
||||||
|
|
Loading…
Reference in New Issue
Block a user