move input handling, including menu and prompt, to the Client class
This commit is contained in:
parent
d5f5f0989d
commit
b08d8719e6
279
src/client.cc
Normal file
279
src/client.cc
Normal 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));
|
||||
}
|
||||
|
||||
}
|
|
@ -4,13 +4,13 @@
|
|||
#include "keys.hh"
|
||||
#include "completion.hh"
|
||||
#include "utils.hh"
|
||||
#include "string.hh"
|
||||
|
||||
namespace Kakoune
|
||||
{
|
||||
|
||||
class Editor;
|
||||
class Window;
|
||||
class String;
|
||||
class Context;
|
||||
|
||||
enum class MenuCommand
|
||||
|
@ -21,19 +21,48 @@ enum class MenuCommand
|
|||
Close,
|
||||
};
|
||||
|
||||
using MenuCallback = std::function<void (int, Context&)>;
|
||||
using PromptCallback = std::function<void (const String&, Context&)>;
|
||||
|
||||
class Client : public SafeCountable
|
||||
{
|
||||
public:
|
||||
Client();
|
||||
virtual ~Client() {}
|
||||
virtual void draw_window(Window& window) = 0;
|
||||
virtual void print_status(const String& status,
|
||||
CharCount cursor_pos = -1) = 0;
|
||||
|
||||
virtual void draw_window(Window& window) = 0;
|
||||
virtual void print_status(const String& status) = 0;
|
||||
virtual String prompt(const String& prompt, const Context& context,
|
||||
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;
|
||||
|
||||
private:
|
||||
|
||||
virtual void show_menu(const memoryview<String>& choices) = 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 {};
|
||||
|
|
107
src/commands.cc
107
src/commands.cc
|
@ -660,84 +660,44 @@ private:
|
|||
class BatchClient : public Client
|
||||
{
|
||||
public:
|
||||
BatchClient(const KeyList& keys, Client* previous_client)
|
||||
BatchClient(const KeyList& keys)
|
||||
: m_keys(keys), m_pos(0)
|
||||
{
|
||||
m_previous_client = previous_client;
|
||||
}
|
||||
|
||||
String prompt(const String&, const Context&, Completer)
|
||||
{
|
||||
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()
|
||||
Key get_key() override
|
||||
{
|
||||
if (m_pos >= m_keys.size())
|
||||
throw runtime_error("no more characters");
|
||||
return m_keys[m_pos++];
|
||||
}
|
||||
|
||||
void print_status(const String& status)
|
||||
{
|
||||
m_previous_client->print_status(status);
|
||||
}
|
||||
void print_status(const String& status, CharCount cursor_pos) override {}
|
||||
void draw_window(Window& window) override {}
|
||||
|
||||
void draw_window(Window& window)
|
||||
{
|
||||
m_previous_client->draw_window(window);
|
||||
}
|
||||
void show_menu(const memoryview<String>&) override {}
|
||||
void menu_ctrl(MenuCommand) override {}
|
||||
|
||||
bool has_key_left() const { return m_pos < m_keys.size(); }
|
||||
|
||||
void show_menu(const memoryview<String>&) {}
|
||||
void menu_ctrl(MenuCommand) {}
|
||||
|
||||
private:
|
||||
const KeyList& m_keys;
|
||||
size_t m_pos;
|
||||
Client* m_previous_client;
|
||||
};
|
||||
|
||||
void exec_keys(const KeyList& keys, Context& context)
|
||||
{
|
||||
BatchClient batch_client(keys, context.has_client() ? &context.client()
|
||||
: nullptr);
|
||||
BatchClient batch_client(keys);
|
||||
|
||||
RegisterRestorer quote('"', context);
|
||||
RegisterRestorer slash('/', context);
|
||||
|
||||
scoped_edition edition(context.editor());
|
||||
|
||||
int count = 0;
|
||||
Context new_context(batch_client);
|
||||
new_context.change_editor(context.editor());
|
||||
while (batch_client.has_key_left())
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
batch_client.handle_next_input(new_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);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
ParametersParser parser(params, { { "auto-single", false } });
|
||||
|
@ -804,12 +725,18 @@ void menu(const CommandParameters& params, Context& context)
|
|||
}
|
||||
|
||||
std::vector<String> choices;
|
||||
std::vector<String> commands;
|
||||
for (int i = 0; i < count; i += 2)
|
||||
{
|
||||
choices.push_back(parser[i]);
|
||||
commands.push_back(parser[i+1]);
|
||||
}
|
||||
|
||||
int i = menu_select(choices, context.client()) + 1;
|
||||
if (i > 0 and i < (count / 2) + 1)
|
||||
CommandManager::instance().execute(parser[(i-1)*2+1], context);
|
||||
context.client().menu(choices,
|
||||
[=](int choice, Context& context) {
|
||||
if (choice >= 0 and choice < commands.size())
|
||||
CommandManager::instance().execute(commands[choice], context);
|
||||
});
|
||||
}
|
||||
|
||||
void try_catch(const CommandParameters& params, Context& context)
|
||||
|
|
110
src/main.cc
110
src/main.cc
|
@ -165,48 +165,39 @@ void do_go(Context& context)
|
|||
|
||||
void do_command(Context& context)
|
||||
{
|
||||
try
|
||||
{
|
||||
auto cmdline = context.client().prompt(
|
||||
":", context, std::bind(&CommandManager::complete,
|
||||
&CommandManager::instance(),
|
||||
_1, _2, _3));
|
||||
|
||||
CommandManager::instance().execute(cmdline, context);
|
||||
}
|
||||
catch (prompt_aborted&) {}
|
||||
context.client().prompt(
|
||||
":", std::bind(&CommandManager::complete, &CommandManager::instance(), _1, _2, _3),
|
||||
[](const String& cmdline, Context& context) { CommandManager::instance().execute(cmdline, context); });
|
||||
}
|
||||
|
||||
void do_pipe(Context& context)
|
||||
{
|
||||
try
|
||||
{
|
||||
auto cmdline = context.client().prompt("|", context, complete_nothing);
|
||||
context.client().prompt("|", complete_nothing,
|
||||
[](const String& cmdline, Context& context)
|
||||
{
|
||||
Editor& editor = context.editor();
|
||||
std::vector<String> strings;
|
||||
for (auto& sel : const_cast<const Editor&>(context.editor()).selections())
|
||||
strings.push_back(ShellManager::instance().pipe(String(sel.begin(), sel.end()),
|
||||
cmdline, context, {}));
|
||||
editor.replace(strings);
|
||||
});
|
||||
|
||||
Editor& editor = context.editor();
|
||||
std::vector<String> strings;
|
||||
for (auto& sel : const_cast<const Editor&>(context.editor()).selections())
|
||||
strings.push_back(ShellManager::instance().pipe(String(sel.begin(), sel.end()),
|
||||
cmdline, context, {}));
|
||||
editor.replace(strings);
|
||||
}
|
||||
catch (prompt_aborted&) {}
|
||||
}
|
||||
|
||||
template<bool append>
|
||||
void do_search(Context& context)
|
||||
{
|
||||
try
|
||||
{
|
||||
String ex = context.client().prompt("/", context);
|
||||
if (ex.empty())
|
||||
ex = RegisterManager::instance()['/'].values(context)[0];
|
||||
else
|
||||
RegisterManager::instance()['/'] = ex;
|
||||
context.client().prompt("/", complete_nothing,
|
||||
[](const String& str, Context& context) {
|
||||
String ex = str;
|
||||
if (ex.empty())
|
||||
ex = RegisterManager::instance()['/'].values(context)[0];
|
||||
else
|
||||
RegisterManager::instance()['/'] = ex;
|
||||
|
||||
context.editor().select(std::bind(select_next_match, _1, ex), append);
|
||||
}
|
||||
catch (prompt_aborted&) {}
|
||||
context.editor().select(std::bind(select_next_match, _1, ex), append);
|
||||
});
|
||||
}
|
||||
|
||||
template<bool append>
|
||||
|
@ -271,22 +262,16 @@ void do_paste(Context& context)
|
|||
|
||||
void do_select_regex(Context& context)
|
||||
{
|
||||
try
|
||||
{
|
||||
String ex = context.client().prompt("select: ", context);
|
||||
context.editor().multi_select(std::bind(select_all_matches, _1, ex));
|
||||
}
|
||||
catch (prompt_aborted&) {}
|
||||
context.client().prompt("select: ", complete_nothing,
|
||||
[](const String& ex, Context& context)
|
||||
{ context.editor().multi_select(std::bind(select_all_matches, _1, ex)); });
|
||||
}
|
||||
|
||||
void do_split_regex(Context& context)
|
||||
{
|
||||
try
|
||||
{
|
||||
String ex = context.client().prompt("split: ", context);
|
||||
context.editor().multi_select(std::bind(split_selection, _1, ex));
|
||||
}
|
||||
catch (prompt_aborted&) {}
|
||||
context.client().prompt("select: ", complete_nothing,
|
||||
[](const String& ex, Context& context)
|
||||
{ context.editor().multi_select(std::bind(split_selection, _1, ex)); });
|
||||
}
|
||||
|
||||
void do_join(Context& context)
|
||||
|
@ -444,32 +429,6 @@ std::unordered_map<Key, std::function<void (Context& context)>> keymap =
|
|||
|
||||
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[])
|
||||
{
|
||||
EventManager event_manager;
|
||||
|
@ -536,11 +495,20 @@ int main(int argc, char* argv[])
|
|||
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();
|
||||
while(not quit_requested)
|
||||
event_manager.handle_next_events();
|
||||
{
|
||||
try
|
||||
{
|
||||
event_manager.handle_next_events();
|
||||
}
|
||||
catch (Kakoune::runtime_error& error)
|
||||
{
|
||||
context.print_status(error.description());
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Kakoune::exception& error)
|
||||
{
|
||||
|
|
166
src/ncurses.cc
166
src/ncurses.cc
|
@ -169,159 +169,29 @@ Key NCursesClient::get_key()
|
|||
return Key(modifiers, c);
|
||||
}
|
||||
|
||||
String NCursesClient::prompt(const String& text, const Context& context, Completer completer)
|
||||
{
|
||||
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)
|
||||
void NCursesClient::print_status(const String& status, CharCount cursor_pos)
|
||||
{
|
||||
int x,y;
|
||||
getmaxyx(stdscr, y, x);
|
||||
move(y-1, 0);
|
||||
clrtoeol();
|
||||
addstr(status.c_str());
|
||||
if (cursor_pos == -1)
|
||||
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();
|
||||
}
|
||||
|
||||
|
|
|
@ -18,14 +18,13 @@ public:
|
|||
NCursesClient(const NCursesClient&) = delete;
|
||||
NCursesClient& operator=(const NCursesClient&) = delete;
|
||||
|
||||
void draw_window(Window& window);
|
||||
void print_status(const String& status);
|
||||
void draw_window(Window& window) override;
|
||||
void print_status(const String& status, CharCount cursor_pos) override;
|
||||
|
||||
String prompt(const String& prompt, const Context& context, Completer completer);
|
||||
Key get_key();
|
||||
Key get_key() override;
|
||||
|
||||
void show_menu(const memoryview<String>& choices);
|
||||
void menu_ctrl(MenuCommand command);
|
||||
void show_menu(const memoryview<String>& choices) override;
|
||||
void menu_ctrl(MenuCommand command) override;
|
||||
private:
|
||||
MENU* m_menu;
|
||||
std::vector<ITEM*> m_items;
|
||||
|
|
Loading…
Reference in New Issue
Block a user