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 "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 print_status(const String& status,
|
||||||
|
CharCount cursor_pos = -1) = 0;
|
||||||
|
|
||||||
virtual void draw_window(Window& window) = 0;
|
void prompt(const String& prompt, Completer completer,
|
||||||
virtual void print_status(const String& status) = 0;
|
PromptCallback callback);
|
||||||
virtual String prompt(const String& prompt, const Context& context,
|
|
||||||
Completer completer = complete_nothing) = 0;
|
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 {};
|
||||||
|
|
107
src/commands.cc
107
src/commands.cc
|
@ -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)
|
||||||
|
|
110
src/main.cc
110
src/main.cc
|
@ -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();
|
||||||
|
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>
|
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,11 +495,20 @@ 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)
|
||||||
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)
|
catch (Kakoune::exception& error)
|
||||||
{
|
{
|
||||||
|
|
166
src/ncurses.cc
166
src/ncurses.cc
|
@ -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();
|
||||||
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();
|
refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user