diff --git a/src/commands.cc b/src/commands.cc index 75ccecc2..0a4ec97d 100644 --- a/src/commands.cc +++ b/src/commands.cc @@ -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>()] { + 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 diff --git a/src/context.cc b/src/context.cc index 08a0b544..d969ffd0 100644 --- a/src/context.cc +++ b/src/context.cc @@ -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); } } diff --git a/src/input_handler.cc b/src/input_handler.cc index 750ec1d4..5bda3324 100644 --- a/src/input_handler.cc +++ b/src/input_handler.cc @@ -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::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; - static HashMap 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 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) diff --git a/src/input_handler.hh b/src/input_handler.hh index 0dc98b70..e9528a07 100644 --- a/src/input_handler.hh +++ b/src/input_handler.hh @@ -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); diff --git a/src/main.cc b/src/main.cc index b4622aea..53df2492 100644 --- a/src/main.cc +++ b/src/main.cc @@ -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()); + for (auto c : StringView{"/|:\\"}) + register_manager.add_register(c, std::make_unique()); + using StringList = Vector; register_manager.add_register('%', make_dyn_reg( diff --git a/src/normal.cc b/src/normal.cc index 2fb66e49..51308e7c 100644 --- a/src/normal.cc +++ b/src/normal.cc @@ -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 -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 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, prompt.str(), saved_reg[main_index], - [reg, count, saved_reg] + regex_prompt(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 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 void use_selection_as_search_pattern(Context& context, NormalParams params) { - Vector patterns; - auto& sels = context.selections(); const auto& buffer = context.buffer(); - for (auto& sel : sels) - { - const auto beg = sel.min(), end = buffer.char_next(sel.max()); - patterns.push_back(format("{}{}{}", - smart and is_bow(buffer, beg) ? "\\b" : "", - escape(buffer.string(beg, end), "^$\\.*+?()[]{}|", '\\'), - smart and is_eow(buffer, end) ? "\\b" : "")); - } + auto& sel = context.selections().main(); + const auto beg = sel.min(), end = buffer.char_next(sel.max()); + String pattern = format("{}{}{}", + smart and is_bow(buffer, beg) ? "\\b" : "", + escape(buffer.string(beg, end), "^$\\.*+?()[]{}|", '\\'), + 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 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 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>(); - 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); diff --git a/src/register_manager.hh b/src/register_manager.hh index 4337f257..c463ad3a 100644 --- a/src/register_manager.hh +++ b/src/register_manager.hh @@ -20,6 +20,15 @@ public: virtual void set(Context& context, ConstArrayView values) = 0; virtual ConstArrayView get(const Context& context) = 0; + virtual const String& get_main(const Context& context, size_t main_index) = 0; + + struct RestoreInfo + { + std::vector 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(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 data{new String[m_content.size()]}; + //std::copy_n(m_content.data(), m_content.size(), data.get()); + auto content = get(context); + std::vector 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 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 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 std::unique_ptr make_dyn_reg(Func func) { @@ -93,6 +157,14 @@ public: { return ConstArrayView(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