move input handling, including menu and prompt, to the Client class

This commit is contained in:
Maxime Coste 2012-09-03 14:22:02 +02:00
parent d5f5f0989d
commit b08d8719e6
6 changed files with 392 additions and 320 deletions

279
src/client.cc Normal file
View File

@ -0,0 +1,279 @@
#include "client.hh"
#include "context.hh"
#include "register_manager.hh"
#include <unordered_map>
namespace Kakoune
{
extern std::unordered_map<Key, std::function<void (Context& context)>> keymap;
class Client::NormalMode : public Client::Mode
{
public:
NormalMode(Client& client)
: Client::Mode(client)
{
}
void on_key(const Key& key, Context& context) override
{
if (key.modifiers == Key::Modifiers::None and isdigit(key.key))
m_count = m_count * 10 + key.key - '0';
else
{
auto it = keymap.find(key);
if (it != keymap.end())
{
context.numeric_param(m_count);
// it's important to do that before calling the command,
// as we may die during the command execution.
m_count = 0;
it->second(context);
}
else
m_count = 0;
}
}
private:
int m_count = 0;
};
class Client::MenuMode : public Client::Mode
{
public:
MenuMode(Client& client, const memoryview<String>& choices, MenuCallback callback)
: Client::Mode(client),
m_callback(callback), m_choice_count(choices.size()), m_selected(0)
{
client.show_menu(choices);
}
void on_key(const Key& key, Context& context) override
{
if (key == Key(Key::Modifiers::Control, 'n') or
key == Key(Key::Modifiers::None, 'j'))
{
m_client.menu_ctrl(MenuCommand::SelectNext);
m_selected = std::min(m_selected+1, m_choice_count-1);
}
if (key == Key(Key::Modifiers::Control, 'p') or
key == Key(Key::Modifiers::None, 'k'))
{
m_client.menu_ctrl(MenuCommand::SelectPrev);
m_selected = std::max(m_selected-1, 0);
}
if (key == Key(Key::Modifiers::Control, 'm'))
{
// save callback as reset_normal_mode will delete this
MenuCallback callback = std::move(m_callback);
int selected = m_selected;
m_client.reset_normal_mode();
callback(selected, context);
}
if (key == Key(Key::Modifiers::None, 27))
{
m_client.reset_normal_mode();
}
if (key.modifiers == Key::Modifiers::None and
key.key >= '0' and key.key <= '9')
{
m_client.menu_ctrl(MenuCommand::Close);
// save callback as reset_normal_mode will delete this
MenuCallback callback = std::move(m_callback);
m_client.reset_normal_mode();
callback(key.key - '0' - 1, context);
}
}
private:
MenuCallback m_callback;
int m_selected;
int m_choice_count;
};
class Client::PromptMode : public Client::Mode
{
public:
PromptMode(Client& client, const String& prompt, Completer completer, PromptCallback callback)
: Client::Mode(client),
m_prompt(prompt), m_completer(completer), m_callback(callback), m_cursor_pos(0)
{
m_history_it = ms_history[m_prompt].end();
m_client.print_status(m_prompt, m_prompt.length());
}
void on_key(const Key& key, Context& context) override
{
std::vector<String>& history = ms_history[m_prompt];
if (key == Key(Key::Modifiers::Control, 'm')) // enter
{
std::vector<String>::iterator it;
while ((it = find(history, m_result)) != history.end())
history.erase(it);
history.push_back(m_result);
m_client.print_status("");
// save callback as reset_normal_mode will delete this
PromptCallback callback = std::move(m_callback);
String result = std::move(m_result);
m_client.reset_normal_mode();
callback(result, context);
return;
}
else if (key == Key(Key::Modifiers::None, 27))
{
m_client.print_status("");
m_client.reset_normal_mode();
return;
}
else if (key == Key(Key::Modifiers::Control, 'p'))
{
if (m_history_it != history.begin())
{
if (m_history_it == history.end())
m_saved_result = m_result;
--m_history_it;
m_result = *m_history_it;
m_cursor_pos = m_result.length();
}
}
else if (key == Key(Key::Modifiers::Control, 'n'))
{
if (m_history_it != history.end())
{
++m_history_it;
if (m_history_it != history.end())
m_result = *m_history_it;
else
m_result = m_saved_result;
m_cursor_pos = m_result.length();
}
}
else if (key == Key(Key::Modifiers::Control, 'b'))
{
if (m_cursor_pos > 0)
--m_cursor_pos;
}
else if (key == Key(Key::Modifiers::Control, 'f'))
{
if (m_cursor_pos < m_result.length())
++m_cursor_pos;
}
else if (key == Key(Key::Modifiers::Control, 'g')) // backspace
{
if (m_cursor_pos != 0)
{
m_result = m_result.substr(0, m_cursor_pos - 1)
+ m_result.substr(m_cursor_pos, String::npos);
--m_cursor_pos;
}
m_client.menu_ctrl(MenuCommand::Close);
m_current_completion = -1;
}
else if (key == Key(Key::Modifiers::Control, 'r'))
{
Key k = m_client.get_key();
String reg = RegisterManager::instance()[k.key].values(context)[0];
m_client.menu_ctrl(MenuCommand::Close);
m_current_completion = -1;
m_result = m_result.substr(0, m_cursor_pos) + reg + m_result.substr(m_cursor_pos, String::npos);
m_cursor_pos += reg.length();
}
else if (key == Key(Key::Modifiers::Control, 'i')) // tab
{
if (m_current_completion == -1)
{
m_completions = m_completer(context, m_result, m_cursor_pos);
if (m_completions.candidates.empty())
return;
m_client.menu_ctrl(MenuCommand::Close);
m_client.show_menu(m_completions.candidates);
m_text_before_completion = m_result.substr(m_completions.start,
m_completions.end - m_completions.start);
}
else
m_client.menu_ctrl(MenuCommand::SelectNext);
++m_current_completion;
String completion;
if (m_current_completion >= m_completions.candidates.size())
{
if (m_current_completion == m_completions.candidates.size() and
std::find(m_completions.candidates.begin(), m_completions.candidates.end(), m_text_before_completion) == m_completions.candidates.end())
completion = m_text_before_completion;
else
{
m_current_completion = 0;
completion = m_completions.candidates[0];
m_client.menu_ctrl(MenuCommand::SelectFirst);
}
}
else
completion = m_completions.candidates[m_current_completion];
m_result = m_result.substr(0, m_completions.start) + completion;
m_cursor_pos = m_completions.start + completion.length();
}
else
{
m_client.menu_ctrl(MenuCommand::Close);
m_current_completion = -1;
m_result = m_result.substr(0, m_cursor_pos) + key.key + m_result.substr(m_cursor_pos, String::npos);
++m_cursor_pos;
}
m_client.print_status(m_prompt + m_result, m_prompt.length() + m_cursor_pos);
}
private:
PromptCallback m_callback;
Completer m_completer;
const String m_prompt;
CharCount m_cursor_pos;
Completions m_completions;
int m_current_completion = -1;
String m_text_before_completion;
String m_result;
String m_saved_result;
static std::unordered_map<String, std::vector<String>> ms_history;
std::vector<String>::iterator m_history_it;
};
std::unordered_map<String, std::vector<String>> Client::PromptMode::ms_history;
Client::Client()
: m_mode(new NormalMode(*this))
{
}
void Client::prompt(const String& prompt, Completer completer,
PromptCallback callback)
{
m_mode.reset(new PromptMode(*this, prompt, completer, callback));
}
void Client::menu(const memoryview<String>& choices,
MenuCallback callback)
{
m_mode.reset(new MenuMode(*this, choices, callback));
}
void Client::handle_next_input(Context& context)
{
m_mode->on_key(get_key(), context);
context.draw_ifn();
}
void Client::reset_normal_mode()
{
m_mode.reset(new NormalMode(*this));
}
}

View File

@ -4,13 +4,13 @@
#include "keys.hh" #include "keys.hh"
#include "completion.hh" #include "completion.hh"
#include "utils.hh" #include "utils.hh"
#include "string.hh"
namespace Kakoune namespace Kakoune
{ {
class Editor; class Editor;
class Window; class Window;
class String;
class Context; class Context;
enum class MenuCommand enum class MenuCommand
@ -21,19 +21,48 @@ enum class MenuCommand
Close, Close,
}; };
using MenuCallback = std::function<void (int, Context&)>;
using PromptCallback = std::function<void (const String&, Context&)>;
class Client : public SafeCountable class Client : public SafeCountable
{ {
public: public:
Client();
virtual ~Client() {} virtual ~Client() {}
virtual void draw_window(Window& window) = 0; virtual void draw_window(Window& window) = 0;
virtual void print_status(const String& status) = 0; virtual void print_status(const String& status,
virtual String prompt(const String& prompt, const Context& context, CharCount cursor_pos = -1) = 0;
Completer completer = complete_nothing) = 0;
void prompt(const String& prompt, Completer completer,
PromptCallback callback);
void menu(const memoryview<String>& choices,
MenuCallback callback);
void handle_next_input(Context& context);
virtual Key get_key() = 0; virtual Key get_key() = 0;
private:
virtual void show_menu(const memoryview<String>& choices) = 0; virtual void show_menu(const memoryview<String>& choices) = 0;
virtual void menu_ctrl(MenuCommand command) = 0; virtual void menu_ctrl(MenuCommand command) = 0;
void reset_normal_mode();
class Mode
{
public:
Mode(Client& client) : m_client(client) {}
virtual ~Mode() {}
virtual void on_key(const Key& key, Context& context) = 0;
protected:
Client& m_client;
};
std::unique_ptr<Mode> m_mode;
class NormalMode;
class MenuMode;
class PromptMode;
}; };
struct prompt_aborted {}; struct prompt_aborted {};

View File

@ -660,84 +660,44 @@ private:
class BatchClient : public Client class BatchClient : public Client
{ {
public: public:
BatchClient(const KeyList& keys, Client* previous_client) BatchClient(const KeyList& keys)
: m_keys(keys), m_pos(0) : m_keys(keys), m_pos(0)
{ {
m_previous_client = previous_client;
} }
String prompt(const String&, const Context&, Completer) Key get_key() override
{
size_t begin = m_pos;
while (m_pos < m_keys.size() and m_keys[m_pos].key != '\n')
++m_pos;
String result;
for (size_t i = begin; i < m_pos; ++i)
result += String() + m_keys[i].key;
++m_pos;
return result;
}
Key get_key()
{ {
if (m_pos >= m_keys.size()) if (m_pos >= m_keys.size())
throw runtime_error("no more characters"); throw runtime_error("no more characters");
return m_keys[m_pos++]; return m_keys[m_pos++];
} }
void print_status(const String& status) void print_status(const String& status, CharCount cursor_pos) override {}
{ void draw_window(Window& window) override {}
m_previous_client->print_status(status);
}
void draw_window(Window& window) void show_menu(const memoryview<String>&) override {}
{ void menu_ctrl(MenuCommand) override {}
m_previous_client->draw_window(window);
}
bool has_key_left() const { return m_pos < m_keys.size(); } bool has_key_left() const { return m_pos < m_keys.size(); }
void show_menu(const memoryview<String>&) {}
void menu_ctrl(MenuCommand) {}
private: private:
const KeyList& m_keys; const KeyList& m_keys;
size_t m_pos; size_t m_pos;
Client* m_previous_client;
}; };
void exec_keys(const KeyList& keys, Context& context) void exec_keys(const KeyList& keys, Context& context)
{ {
BatchClient batch_client(keys, context.has_client() ? &context.client() BatchClient batch_client(keys);
: nullptr);
RegisterRestorer quote('"', context); RegisterRestorer quote('"', context);
RegisterRestorer slash('/', context); RegisterRestorer slash('/', context);
scoped_edition edition(context.editor()); scoped_edition edition(context.editor());
int count = 0;
Context new_context(batch_client); Context new_context(batch_client);
new_context.change_editor(context.editor()); new_context.change_editor(context.editor());
while (batch_client.has_key_left()) while (batch_client.has_key_left())
{ batch_client.handle_next_input(new_context);
Key key = batch_client.get_key();
if (key.modifiers == Key::Modifiers::None and isdigit(key.key))
count = count * 10 + key.key - '0';
else
{
auto it = keymap.find(key);
if (it != keymap.end())
{
new_context.numeric_param(count);
it->second(new_context);
}
count = 0;
}
}
} }
void exec_string(const CommandParameters& params, Context& context) void exec_string(const CommandParameters& params, Context& context)
@ -750,45 +710,6 @@ void exec_string(const CommandParameters& params, Context& context)
exec_keys(keys, context); exec_keys(keys, context);
} }
int menu_select(const memoryview<String>& choices, Client& client)
{
int selected = 0;
client.show_menu(choices);
while (true)
{
Key key = client.get_key();
if (key == Key(Key::Modifiers::Control, 'n') or
key == Key(Key::Modifiers::None, 'j'))
{
client.menu_ctrl(MenuCommand::SelectNext);
selected = std::min(selected+1, (int)choices.size()-1);
}
if (key == Key(Key::Modifiers::Control, 'p') or
key == Key(Key::Modifiers::None, 'k'))
{
client.menu_ctrl(MenuCommand::SelectPrev);
selected = std::max(selected-1, 0);
}
if (key == Key(Key::Modifiers::Control, 'm'))
{
client.menu_ctrl(MenuCommand::Close);
return selected;
}
if (key == Key(Key::Modifiers::None, 27))
{
client.menu_ctrl(MenuCommand::Close);
return -1;
}
if (key.modifiers == Key::Modifiers::None and
key.key >= '0' and key.key <= '9')
{
client.menu_ctrl(MenuCommand::Close);
return key.key - '0' - 1;
}
}
return 0;
}
void menu(const CommandParameters& params, Context& context) void menu(const CommandParameters& params, Context& context)
{ {
ParametersParser parser(params, { { "auto-single", false } }); ParametersParser parser(params, { { "auto-single", false } });
@ -804,12 +725,18 @@ void menu(const CommandParameters& params, Context& context)
} }
std::vector<String> choices; std::vector<String> choices;
std::vector<String> commands;
for (int i = 0; i < count; i += 2) for (int i = 0; i < count; i += 2)
{
choices.push_back(parser[i]); choices.push_back(parser[i]);
commands.push_back(parser[i+1]);
}
int i = menu_select(choices, context.client()) + 1; context.client().menu(choices,
if (i > 0 and i < (count / 2) + 1) [=](int choice, Context& context) {
CommandManager::instance().execute(parser[(i-1)*2+1], context); if (choice >= 0 and choice < commands.size())
CommandManager::instance().execute(commands[choice], context);
});
} }
void try_catch(const CommandParameters& params, Context& context) void try_catch(const CommandParameters& params, Context& context)

View File

@ -165,48 +165,39 @@ void do_go(Context& context)
void do_command(Context& context) void do_command(Context& context)
{ {
try context.client().prompt(
{ ":", std::bind(&CommandManager::complete, &CommandManager::instance(), _1, _2, _3),
auto cmdline = context.client().prompt( [](const String& cmdline, Context& context) { CommandManager::instance().execute(cmdline, context); });
":", context, std::bind(&CommandManager::complete,
&CommandManager::instance(),
_1, _2, _3));
CommandManager::instance().execute(cmdline, context);
}
catch (prompt_aborted&) {}
} }
void do_pipe(Context& context) void do_pipe(Context& context)
{ {
try context.client().prompt("|", complete_nothing,
[](const String& cmdline, Context& context)
{ {
auto cmdline = context.client().prompt("|", context, complete_nothing);
Editor& editor = context.editor(); Editor& editor = context.editor();
std::vector<String> strings; std::vector<String> strings;
for (auto& sel : const_cast<const Editor&>(context.editor()).selections()) for (auto& sel : const_cast<const Editor&>(context.editor()).selections())
strings.push_back(ShellManager::instance().pipe(String(sel.begin(), sel.end()), strings.push_back(ShellManager::instance().pipe(String(sel.begin(), sel.end()),
cmdline, context, {})); cmdline, context, {}));
editor.replace(strings); editor.replace(strings);
} });
catch (prompt_aborted&) {}
} }
template<bool append> template<bool append>
void do_search(Context& context) void do_search(Context& context)
{ {
try context.client().prompt("/", complete_nothing,
{ [](const String& str, Context& context) {
String ex = context.client().prompt("/", context); String ex = str;
if (ex.empty()) if (ex.empty())
ex = RegisterManager::instance()['/'].values(context)[0]; ex = RegisterManager::instance()['/'].values(context)[0];
else else
RegisterManager::instance()['/'] = ex; RegisterManager::instance()['/'] = ex;
context.editor().select(std::bind(select_next_match, _1, ex), append); context.editor().select(std::bind(select_next_match, _1, ex), append);
} });
catch (prompt_aborted&) {}
} }
template<bool append> template<bool append>
@ -271,22 +262,16 @@ void do_paste(Context& context)
void do_select_regex(Context& context) void do_select_regex(Context& context)
{ {
try context.client().prompt("select: ", complete_nothing,
{ [](const String& ex, Context& context)
String ex = context.client().prompt("select: ", context); { context.editor().multi_select(std::bind(select_all_matches, _1, ex)); });
context.editor().multi_select(std::bind(select_all_matches, _1, ex));
}
catch (prompt_aborted&) {}
} }
void do_split_regex(Context& context) void do_split_regex(Context& context)
{ {
try context.client().prompt("select: ", complete_nothing,
{ [](const String& ex, Context& context)
String ex = context.client().prompt("split: ", context); { context.editor().multi_select(std::bind(split_selection, _1, ex)); });
context.editor().multi_select(std::bind(split_selection, _1, ex));
}
catch (prompt_aborted&) {}
} }
void do_join(Context& context) void do_join(Context& context)
@ -444,32 +429,6 @@ std::unordered_map<Key, std::function<void (Context& context)>> keymap =
void run_unit_tests(); void run_unit_tests();
void manage_next_keypress(Context& context)
{
static int count = 0;
try
{
Key key = context.client().get_key();
if (key.modifiers == Key::Modifiers::None and isdigit(key.key))
count = count * 10 + key.key - '0';
else
{
auto it = keymap.find(key);
if (it != keymap.end())
{
context.numeric_param(count);
it->second(context);
context.draw_ifn();
}
count = 0;
}
}
catch (Kakoune::runtime_error& error)
{
context.print_status(error.description());
}
}
int main(int argc, char* argv[]) int main(int argc, char* argv[])
{ {
EventManager event_manager; EventManager event_manager;
@ -536,12 +495,21 @@ int main(int argc, char* argv[])
context.change_editor(*buffer->get_or_create_window()); context.change_editor(*buffer->get_or_create_window());
} }
event_manager.watch(0, [&](int) { manage_next_keypress(context); }); event_manager.watch(0, [&](int) { client.handle_next_input(context); });
context.draw_ifn(); context.draw_ifn();
while(not quit_requested) while(not quit_requested)
{
try
{
event_manager.handle_next_events(); event_manager.handle_next_events();
} }
catch (Kakoune::runtime_error& error)
{
context.print_status(error.description());
}
}
}
catch (Kakoune::exception& error) catch (Kakoune::exception& error)
{ {
puts("uncaught exception:\n"); puts("uncaught exception:\n");

View File

@ -169,159 +169,29 @@ Key NCursesClient::get_key()
return Key(modifiers, c); return Key(modifiers, c);
} }
String NCursesClient::prompt(const String& text, const Context& context, Completer completer) void NCursesClient::print_status(const String& status, CharCount cursor_pos)
{
curs_set(2);
auto restore_cursor = on_scope_end([]() { curs_set(0); });
int max_x, max_y;
getmaxyx(stdscr, max_y, max_x);
move(max_y-1, 0);
addstr(text.c_str());
clrtoeol();
CharCount cursor_pos = 0;
Completions completions;
int current_completion = -1;
String text_before_completion;
String result;
String saved_result;
static std::unordered_map<String, std::vector<String>> history_per_prompt;
std::vector<String>& history = history_per_prompt[text];
auto history_it = history.end();
while (true)
{
int c = getch();
switch (c)
{
case '\r':
{
std::vector<String>::iterator it;
while ((it = find(history, result)) != history.end())
history.erase(it);
history.push_back(result);
return result;
}
case KEY_UP:
if (history_it != history.begin())
{
if (history_it == history.end())
saved_result = result;
--history_it;
result = *history_it;
cursor_pos = result.length();
}
break;
case KEY_DOWN:
if (history_it != history.end())
{
++history_it;
if (history_it != history.end())
result = *history_it;
else
result = saved_result;
cursor_pos = result.length();
}
break;
case KEY_LEFT:
if (cursor_pos > 0)
--cursor_pos;
break;
case KEY_RIGHT:
if (cursor_pos < result.length())
++cursor_pos;
break;
case KEY_BACKSPACE:
if (cursor_pos != 0)
{
result = result.substr(0, cursor_pos - 1)
+ result.substr(cursor_pos, String::npos);
--cursor_pos;
}
menu_ctrl(MenuCommand::Close);
current_completion = -1;
break;
case CTRL('r'):
{
c = getch();
String reg = RegisterManager::instance()[c].values(context)[0];
menu_ctrl(MenuCommand::Close);
current_completion = -1;
result = result.substr(0, cursor_pos) + reg + result.substr(cursor_pos, String::npos);
cursor_pos += reg.length();
}
break;
case 27:
throw prompt_aborted();
case '\t':
{
if (current_completion == -1)
{
completions = completer(context, result, cursor_pos);
if (completions.candidates.empty())
break;
menu_ctrl(MenuCommand::Close);
show_menu(completions.candidates);
text_before_completion = result.substr(completions.start,
completions.end - completions.start);
}
else
menu_ctrl(MenuCommand::SelectNext);
++current_completion;
String completion;
if (current_completion >= completions.candidates.size())
{
if (current_completion == completions.candidates.size() and
std::find(completions.candidates.begin(), completions.candidates.end(), text_before_completion) == completions.candidates.end())
completion = text_before_completion;
else
{
current_completion = 0;
completion = completions.candidates[0];
menu_ctrl(MenuCommand::SelectFirst);
}
}
else
completion = completions.candidates[current_completion];
move(max_y-1, (int)text.length());
result = result.substr(0, completions.start) + completion;
cursor_pos = completions.start + completion.length();
break;
}
default:
menu_ctrl(MenuCommand::Close);
current_completion = -1;
result = result.substr(0, cursor_pos) + (char)c + result.substr(cursor_pos, String::npos);
++cursor_pos;
}
move(max_y - 1, (int)text.length());
clrtoeol();
addstr(result.c_str());
move(max_y - 1, (int)(text.length() + cursor_pos));
refresh();
}
menu_ctrl(MenuCommand::Close);
return result;
}
void NCursesClient::print_status(const String& status)
{ {
int x,y; int x,y;
getmaxyx(stdscr, y, x); getmaxyx(stdscr, y, x);
move(y-1, 0); move(y-1, 0);
clrtoeol(); clrtoeol();
if (cursor_pos == -1)
addstr(status.c_str()); addstr(status.c_str());
else if (cursor_pos < status.length())
{
addstr(status.substr(0, cursor_pos).c_str());
set_attribute(A_REVERSE, 1);
addch(status[cursor_pos]);
set_attribute(A_REVERSE, 0);
addstr(status.substr(cursor_pos+1, -1).c_str());
}
else
{
addstr(status.c_str());
set_attribute(A_REVERSE, 1);
addch(' ');
set_attribute(A_REVERSE, 0);
}
refresh(); refresh();
} }

View File

@ -18,14 +18,13 @@ public:
NCursesClient(const NCursesClient&) = delete; NCursesClient(const NCursesClient&) = delete;
NCursesClient& operator=(const NCursesClient&) = delete; NCursesClient& operator=(const NCursesClient&) = delete;
void draw_window(Window& window); void draw_window(Window& window) override;
void print_status(const String& status); void print_status(const String& status, CharCount cursor_pos) override;
String prompt(const String& prompt, const Context& context, Completer completer); Key get_key() override;
Key get_key();
void show_menu(const memoryview<String>& choices); void show_menu(const memoryview<String>& choices) override;
void menu_ctrl(MenuCommand command); void menu_ctrl(MenuCommand command) override;
private: private:
MENU* m_menu; MENU* m_menu;
std::vector<ITEM*> m_items; std::vector<ITEM*> m_items;