Use register to store prompt history

This commit is contained in:
Maxime Coste 2019-06-05 23:19:27 +10:00
parent a9e778fcc7
commit e613292568
7 changed files with 146 additions and 87 deletions

View File

@ -1739,10 +1739,10 @@ void context_wrap(const ParametersParser& parser, Context& context, StringView d
auto& register_manager = RegisterManager::instance();
auto make_register_restorer = [&](char c) {
return on_scope_end([&, c, save=register_manager[c].get(context) | gather<Vector<String>>()] {
return on_scope_end([&, c, save=register_manager[c].save(context)] {
try
{
RegisterManager::instance()[c].set(context, save);
RegisterManager::instance()[c].restore(context, save);
}
catch (runtime_error& err)
{
@ -2001,7 +2001,7 @@ const CommandDesc prompt_cmd = {
CapturedShellContext sc{shell_context};
context.input_handler().prompt(
parser[0], initstr.str(), {}, context.faces()["Prompt"],
flags, std::move(completer),
flags, '_', std::move(completer),
[=](StringView str, PromptEvent event, Context& context) mutable
{
if ((event == PromptEvent::Abort and on_abort.empty()) or

View File

@ -239,11 +239,8 @@ void Context::end_edition()
StringView Context::main_sel_register_value(StringView reg) const
{
auto strings = RegisterManager::instance()[reg].get(*this);
size_t index = m_selections ? (*m_selections).main_index() : 0;
if (strings.size() <= index)
index = strings.size() - 1;
return strings[index];
return RegisterManager::instance()[reg].get_main(*this, index);
}
}

View File

@ -751,11 +751,13 @@ class Prompt : public InputMode
public:
Prompt(InputHandler& input_handler, StringView prompt,
String initstr, String emptystr, Face face, PromptFlags flags,
PromptCompleter completer, PromptCallback callback)
char history_register, PromptCompleter completer, PromptCallback callback)
: InputMode(input_handler), m_callback(std::move(callback)), m_completer(std::move(completer)),
m_prompt(prompt.str()), m_prompt_face(face),
m_empty_text{std::move(emptystr)},
m_line_editor{context().faces()}, m_flags(flags),
m_history{RegisterManager::instance()[history_register]},
m_current_history{m_history.get(context()).size()},
m_auto_complete{context().options()["autocomplete"].get<AutoComplete>() & AutoComplete::Prompt},
m_idle_timer{TimePoint::max(), context().flags() & Context::Flags::Draft ?
Timer::Callback{} : [this](Timer&) {
@ -769,13 +771,11 @@ public:
context().hooks().run_hook(Hook::PromptIdle, "", context());
}}
{
m_history_it = ms_history[m_prompt].end();
m_line_editor.reset(std::move(initstr), m_empty_text);
}
void on_key(Key key) override
{
History& history = ms_history[m_prompt];
const String& line = m_line_editor.line();
if (key == Key::Return)
@ -790,7 +790,7 @@ public:
}
if (not context().history_disabled())
history_push(history, line);
history_push(line);
context().print_status(DisplayLine{});
if (context().has_client())
context().client().menu_hide();
@ -807,7 +807,7 @@ public:
else if (key == Key::Escape or key == ctrl('c'))
{
if (not context().history_disabled())
history_push(history, line);
history_push(line);
context().print_status(DisplayLine{});
if (context().has_client())
context().client().menu_hide();
@ -853,22 +853,25 @@ public:
}
else if (key == Key::Up or key == ctrl('p'))
{
if (m_history_it != history.begin())
if (m_current_history != 0)
{
if (m_history_it == history.end())
auto history = m_history.get(context());
// The history register might have been mutated in the mean time
m_current_history = std::min(history.size(), m_current_history);
if (m_current_history == history.size())
m_prefix = line;
auto it = m_history_it;
auto index = m_current_history;
// search for the previous history entry matching typed prefix
do
{
--it;
if (prefix_match(*it, m_prefix))
--index;
if (prefix_match(history[index], m_prefix))
{
m_history_it = it;
m_line_editor.reset(*it, m_empty_text);
m_current_history = index;
m_line_editor.reset(history[index], m_empty_text);
break;
}
} while (it != history.begin());
} while (index != 0);
clear_completions();
m_refresh_completion_pending = true;
@ -876,16 +879,19 @@ public:
}
else if (key == Key::Down or key == ctrl('n')) // next
{
if (m_history_it != history.end())
auto history = m_history.get(context());
// The history register might have been mutated in the mean time
m_current_history = std::min(history.size(), m_current_history);
if (m_current_history < history.size())
{
// search for the next history entry matching typed prefix
++m_history_it;
while (m_history_it != history.end() and
not prefix_match(*m_history_it, m_prefix))
++m_history_it;
++m_current_history;
while (m_current_history != history.size() and
not prefix_match(history[m_current_history], m_prefix))
++m_current_history;
if (m_history_it != history.end())
m_line_editor.reset(*m_history_it, m_empty_text);
if (m_current_history != history.size())
m_line_editor.reset(history[m_current_history], m_empty_text);
else
m_line_editor.reset(m_prefix, m_empty_text);
@ -1089,27 +1095,22 @@ private:
LineEditor m_line_editor;
bool m_line_changed = false;
PromptFlags m_flags;
Register& m_history;
size_t m_current_history;
bool m_auto_complete;
bool m_refresh_completion_pending = true;
Timer m_idle_timer;
using History = Vector<String, MemoryDomain::History>;
static HashMap<String, History, MemoryDomain::History> ms_history;
History::iterator m_history_it;
void history_push(History& history, StringView entry)
void history_push(StringView entry)
{
if (entry.empty() or
(m_flags & PromptFlags::DropHistoryEntriesWithBlankPrefix and
is_horizontal_blank(entry[0_byte])))
return;
history.erase(std::remove(history.begin(), history.end(), entry),
history.end());
history.push_back(entry.str());
m_history.set(context(), {entry.str()});
}
};
HashMap<String, Prompt::History, MemoryDomain::History> Prompt::ms_history;
class NextKey : public InputMode
{
@ -1592,11 +1593,12 @@ void InputHandler::repeat_last_insert()
}
void InputHandler::prompt(StringView prompt, String initstr, String emptystr,
Face prompt_face, PromptFlags flags,
Face prompt_face, PromptFlags flags, char history_register,
PromptCompleter completer, PromptCallback callback)
{
push_mode(new InputModes::Prompt(*this, prompt, std::move(initstr), std::move(emptystr),
prompt_face, flags, std::move(completer), std::move(callback)));
prompt_face, flags, history_register,
std::move(completer), std::move(callback)));
}
void InputHandler::set_prompt_face(Face prompt_face)

View File

@ -68,7 +68,7 @@ public:
// returns to normal mode after validation if callback does
// not change the mode itself
void prompt(StringView prompt, String initstr, String emptystr,
Face prompt_face, PromptFlags flags,
Face prompt_face, PromptFlags flags, char history_register,
PromptCompleter completer, PromptCallback callback);
void set_prompt_face(Face prompt_face);

View File

@ -280,9 +280,12 @@ void register_registers()
{
RegisterManager& register_manager = RegisterManager::instance();
for (auto c : "abcdefghijklmnopqrstuvwxyz/\"|^@:")
for (auto c : StringView{"abcdefghijklmnopqrstuvwxyz\"^@"})
register_manager.add_register(c, std::make_unique<StaticRegister>());
for (auto c : StringView{"/|:\\"})
register_manager.add_register(c, std::make_unique<HistoryRegister>());
using StringList = Vector<String, MemoryDomain::Registers>;
register_manager.add_register('%', make_dyn_reg(

View File

@ -456,6 +456,7 @@ void command(Context& context, EnvVarMap env_vars)
context.input_handler().prompt(
":", {}, context.main_sel_register_value(':').str(),
context.faces()["Prompt"], PromptFlags::DropHistoryEntriesWithBlankPrefix,
':',
[](const Context& context, CompletionFlags flags,
StringView cmd_line, ByteCount pos) {
return CommandManager::instance().complete(context, flags, cmd_line, pos);
@ -544,7 +545,7 @@ void pipe(Context& context, NormalParams)
const char* prompt = replace ? "pipe:" : "pipe-to:";
context.input_handler().prompt(
prompt, {}, context.main_sel_register_value("|").str(), context.faces()["Prompt"],
PromptFlags::DropHistoryEntriesWithBlankPrefix,
PromptFlags::DropHistoryEntriesWithBlankPrefix, '|',
shell_complete,
[](StringView cmdline, PromptEvent event, Context& context)
{
@ -625,7 +626,7 @@ void insert_output(Context& context, NormalParams)
const char* prompt = mode == InsertMode::Insert ? "insert-output:" : "append-output:";
context.input_handler().prompt(
prompt, {}, context.main_sel_register_value("|").str(), context.faces()["Prompt"],
PromptFlags::DropHistoryEntriesWithBlankPrefix,
PromptFlags::DropHistoryEntriesWithBlankPrefix, '|',
shell_complete,
[](StringView cmdline, PromptEvent event, Context& context)
{
@ -753,14 +754,15 @@ constexpr RegexCompileFlags direction_flags(RegexMode mode)
}
template<RegexMode mode = RegexMode::Forward, typename T>
void regex_prompt(Context& context, String prompt, String default_regex, T func)
void regex_prompt(Context& context, String prompt, char reg, T func)
{
static_assert(is_direction(mode));
DisplayCoord position = context.has_window() ? context.window().position() : DisplayCoord{};
SelectionList selections = context.selections();
auto default_regex = RegisterManager::instance()[reg].get_main(context, context.selections().main_index());
context.input_handler().prompt(
std::move(prompt), {}, default_regex, context.faces()["Prompt"],
PromptFlags::Search,
PromptFlags::Search, reg,
[](const Context& context, CompletionFlags, StringView regex, ByteCount pos) -> Completions {
auto current_word = [](StringView s) {
auto it = s.end();
@ -785,7 +787,7 @@ void regex_prompt(Context& context, String prompt, String default_regex, T func)
[&](auto&& m) { candidates.push_back(m.candidate().str()); return true; });
return {(int)(word.begin() - regex.begin()), pos, std::move(candidates) };
},
[=](StringView str, PromptEvent event, Context& context) mutable {
[=, func=T(std::move(func))](StringView str, PromptEvent event, Context& context) mutable {
try
{
if (event != PromptEvent::Change and context.has_client())
@ -879,16 +881,12 @@ void search(Context& context, NormalParams params)
const char reg = to_lower(params.reg ? params.reg : '/');
const int count = params.count;
auto reg_content = RegisterManager::instance()[reg].get(context);
Vector<String> saved_reg{reg_content.begin(), reg_content.end()};
const int main_index = std::min(context.selections().main_index(), saved_reg.size()-1);
regex_prompt<regex_mode>(context, prompt.str(), saved_reg[main_index],
[reg, count, saved_reg]
regex_prompt<regex_mode>(context, prompt.str(), reg,
[reg, count, saved_reg = RegisterManager::instance()[reg].save(context)]
(const Regex& regex, PromptEvent event, Context& context) {
if (event == PromptEvent::Abort)
{
RegisterManager::instance()[reg].set(context, saved_reg);
RegisterManager::instance()[reg].restore(context, saved_reg);
return;
}
RegisterManager::instance()[reg].set(context, regex.str());
@ -907,7 +905,7 @@ template<SelectMode mode, RegexMode regex_mode>
void search_next(Context& context, NormalParams params)
{
const char reg = to_lower(params.reg ? params.reg : '/');
StringView str = context.main_sel_register_value(reg);
StringView str = RegisterManager::instance()[reg].get(context).back();
if (not str.empty())
{
Regex regex{str, direction_flags(regex_mode)};
@ -942,25 +940,21 @@ void search_next(Context& context, NormalParams params)
template<bool smart>
void use_selection_as_search_pattern(Context& context, NormalParams params)
{
Vector<String> patterns;
auto& sels = context.selections();
const auto& buffer = context.buffer();
for (auto& sel : sels)
{
auto& sel = context.selections().main();
const auto beg = sel.min(), end = buffer.char_next(sel.max());
patterns.push_back(format("{}{}{}",
String pattern = format("{}{}{}",
smart and is_bow(buffer, beg) ? "\\b" : "",
escape(buffer.string(beg, end), "^$\\.*+?()[]{}|", '\\'),
smart and is_eow(buffer, end) ? "\\b" : ""));
}
smart and is_eow(buffer, end) ? "\\b" : "");
const char reg = to_lower(params.reg ? params.reg : '/');
context.print_status({
format("register '{}' set to '{}'", reg, fix_atom_text(patterns[sels.main_index()])),
format("register '{}' set to '{}'", reg, fix_atom_text(pattern)),
context.faces()["Information"] });
RegisterManager::instance()[reg].set(context, patterns);
RegisterManager::instance()[reg].set(context, {pattern});
// Hack, as Window do not take register state into account
if (context.has_window())
@ -973,15 +967,12 @@ void select_regex(Context& context, NormalParams params)
const int capture = params.count;
auto prompt = capture ? format("select (capture {}):", capture) : "select:"_str;
auto reg_content = RegisterManager::instance()[reg].get(context);
Vector<String> saved_reg{reg_content.begin(), reg_content.end()};
const int main_index = std::min(context.selections().main_index(), saved_reg.size()-1);
regex_prompt(context, std::move(prompt), saved_reg[main_index],
[reg, capture, saved_reg](Regex ex, PromptEvent event, Context& context) {
regex_prompt(context, std::move(prompt), reg,
[reg, capture, saved_reg = RegisterManager::instance()[reg].save(context)]
(Regex ex, PromptEvent event, Context& context) {
if (event == PromptEvent::Abort)
{
RegisterManager::instance()[reg].set(context, saved_reg);
RegisterManager::instance()[reg].restore(context, saved_reg);
return;
}
@ -1000,15 +991,12 @@ void split_regex(Context& context, NormalParams params)
const int capture = params.count;
auto prompt = capture ? format("split (on capture {}):", (int)capture) : "split:"_str;
auto reg_content = RegisterManager::instance()[reg].get(context);
Vector<String> saved_reg{reg_content.begin(), reg_content.end()};
const int main_index = std::min(context.selections().main_index(), saved_reg.size()-1);
regex_prompt(context, std::move(prompt), saved_reg[main_index],
[reg, capture, saved_reg](Regex ex, PromptEvent event, Context& context) {
regex_prompt(context, std::move(prompt), reg,
[reg, capture, saved_reg = RegisterManager::instance()[reg].save(context)]
(Regex ex, PromptEvent event, Context& context) {
if (event == PromptEvent::Abort)
{
RegisterManager::instance()[reg].set(context, saved_reg);
RegisterManager::instance()[reg].restore(context, saved_reg);
return;
}
@ -1102,16 +1090,13 @@ void keep(Context& context, NormalParams params)
constexpr StringView prompt = matching ? "keep matching:" : "keep not matching:";
const char reg = to_lower(params.reg ? params.reg : '/');
auto saved_reg = RegisterManager::instance()[reg].get(context) | gather<Vector<String>>();
const int main_index = std::min(context.selections().main_index(), saved_reg.size()-1);
regex_prompt(context, prompt.str(), saved_reg[main_index],
[saved_reg, reg]
regex_prompt(context, prompt.str(), reg,
[reg, saved_reg = RegisterManager::instance()[reg].save(context)]
(const Regex& regex, PromptEvent event, Context& context) {
if (event == PromptEvent::Abort)
{
RegisterManager::instance()[reg].set(context, saved_reg);
RegisterManager::instance()[reg].restore(context, saved_reg);
return;
}
if (not context.history_disabled())
@ -1144,7 +1129,7 @@ void keep_pipe(Context& context, NormalParams)
{
context.input_handler().prompt(
"keep pipe:", {}, {}, context.faces()["Prompt"],
PromptFlags::DropHistoryEntriesWithBlankPrefix, shell_complete,
PromptFlags::DropHistoryEntriesWithBlankPrefix, '|', shell_complete,
[](StringView cmdline, PromptEvent event, Context& context) {
if (event != PromptEvent::Validate)
return;
@ -1301,7 +1286,7 @@ void select_object(Context& context, NormalParams params)
context.input_handler().prompt(
"object desc:", {}, {}, context.faces()["Prompt"],
PromptFlags::None, complete_nothing,
PromptFlags::None, '_', complete_nothing,
[count,info](StringView cmdline, PromptEvent event, Context& context) {
if (event != PromptEvent::Change)
hide_auto_info_ifn(context, info);

View File

@ -20,6 +20,15 @@ public:
virtual void set(Context& context, ConstArrayView<String> values) = 0;
virtual ConstArrayView<String> get(const Context& context) = 0;
virtual const String& get_main(const Context& context, size_t main_index) = 0;
struct RestoreInfo
{
std::vector<String> data;
size_t size;
};
virtual RestoreInfo save(const Context&) = 0;
virtual void restore(Context&, const RestoreInfo&) = 0;
};
// static value register, which can be modified
@ -39,6 +48,25 @@ public:
else
return ConstArrayView<String>(m_content);
}
const String& get_main(const Context& context, size_t main_index) override
{
return get(context)[std::min(main_index, m_content.size() - 1)];
}
RestoreInfo save(const Context& context) override
{
//std::unique_ptr<String[]> data{new String[m_content.size()]};
//std::copy_n(m_content.data(), m_content.size(), data.get());
auto content = get(context);
std::vector<String> data{content.begin(), content.end()};
return {std::move(data), content.size()};
}
void restore(Context&, const RestoreInfo& info) override
{
m_content.assign(info.data.begin(), info.data.begin() + info.size);
}
protected:
Vector<String, MemoryDomain::Registers> m_content;
};
@ -63,11 +91,47 @@ public:
return StaticRegister::get(context);
}
void restore(Context& context, const RestoreInfo& info) override
{
set(context, info.data);
}
private:
Getter m_getter;
Setter m_setter;
};
// Register that is used to store some kind prompt history
class HistoryRegister : public StaticRegister
{
public:
void set(Context&, ConstArrayView<String> values) override
{
for (auto& entry : values)
{
m_content.erase(std::remove(m_content.begin(), m_content.end(), entry),
m_content.end());
m_content.push_back(entry);
}
}
const String& get_main(const Context&, size_t) override
{
return m_content.empty() ? String::ms_empty : m_content.back();
}
RestoreInfo save(const Context&) override
{
return {{}, m_content.size()};
}
void restore(Context&, const RestoreInfo& info) override
{
if (info.size < m_content.size())
m_content.resize(info.size);
}
};
template<typename Func>
std::unique_ptr<Register> make_dyn_reg(Func func)
{
@ -93,6 +157,14 @@ public:
{
return ConstArrayView<String>(String::ms_empty);
}
const String& get_main(const Context&, size_t) override
{
return String::ms_empty;
}
RestoreInfo save(const Context&) override { return {}; }
void restore(Context&, const RestoreInfo& info) override {}
};
class RegisterManager : public Singleton<RegisterManager>