diff --git a/README.asciidoc b/README.asciidoc index cd490035..897415e1 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -515,9 +515,15 @@ By default, marks use the '^' register, but using the register can be set using `"` prefix. `Z` will save the current selections to the register. -`` will append the current selections to the register. +`` will combine the current selections to the register. `z` will restore the selections from the register. -`` will add the selections from the register to the existing ones. +`` will combine the selections from the register with the existing ones. + +When combining selections, kakoune will prompt for a combining mode: + +`+` will append selections from both lists into a single list +`<` will select the selection with the leftmost cursor for each pair +`>` will select the selection with the rightmost cursor for each pair Jump list ~~~~~~~~~ diff --git a/doc/manpages/keys.asciidoc b/doc/manpages/keys.asciidoc index 26aafd9d..ab5a7510 100644 --- a/doc/manpages/keys.asciidoc +++ b/doc/manpages/keys.asciidoc @@ -417,13 +417,22 @@ Marks use the *^* register by default. will save the current selections to the register **:: - will append the current selections to the register + will combine the current selections to the register *z*:: will restore the selections from the register **:: - will append the selections from the register to the existing ones + will combine the selections from the register to the existing ones + +When combining selections, kakoune will prompt for a combining mode: + +*+*:: + will append selections from both lists into a single list +*<*:: + will select the selection with the leftmost cursor for each pair +*>*:: + will select the selection with the rightmost cursor for each pair Macros ------ diff --git a/src/main.cc b/src/main.cc index 1dc5bf16..16a27f39 100644 --- a/src/main.cc +++ b/src/main.cc @@ -48,7 +48,9 @@ static const char* startup_info = " * The status line can be further customized.\n" " See `:doc options modelinefmt`.\n" " * The range-faces option type is now named range-specs\n" -" and can be used by the new replace-range highlighter.\n"; +" and can be used by the new replace-range highlighter.\n" +" * and are not append to/from register anymore but\n" +" combine from/to register.\n"; struct startup_error : runtime_error { diff --git a/src/normal.cc b/src/normal.cc index 6b2b568a..f8a6abf9 100644 --- a/src/normal.cc +++ b/src/normal.cc @@ -1531,67 +1531,111 @@ SelectionList read_selections_from_register(char reg, Context& context) return {buffer, std::move(sels), timestamp}; } -template +enum class CombineOp +{ + Append, + SelectLeftmostCursor, + SelectRightmostCursor, +}; + +CombineOp key_to_combine_op(Key key) +{ + switch (key.key) + { + case '+': return CombineOp::Append; + case '<': return CombineOp::SelectLeftmostCursor; + case '>': return CombineOp::SelectRightmostCursor; + } + throw runtime_error{format("unknown combine operator '{}'", key.key)}; +} + +const Selection& select_selection(const Selection& lhs, const Selection& rhs, CombineOp op) +{ + switch (op) + { + case CombineOp::SelectLeftmostCursor: return lhs.cursor() < rhs.cursor() ? lhs : rhs; + case CombineOp::SelectRightmostCursor: return lhs.cursor() < rhs.cursor() ? rhs : lhs; + default: kak_assert(false); return lhs; + } +} + +template +void combine_selections(Context& context, SelectionList list, Func func) +{ + if (&context.buffer() != &list.buffer()) + throw runtime_error{"cannot combine selections from different buffers"}; + + on_next_key_with_autoinfo(context, KeymapMode::None, + [func, list](Key key, Context& context) mutable { + const auto op = key_to_combine_op(key); + auto& sels = context.selections(); + list.update(); + if (op == CombineOp::Append) + { + const auto main_index = list.size() + sels.main_index(); + for (auto& sel : sels) + list.push_back(sel); + list.set_main_index(main_index); + list.sort_and_merge_overlapping(); + } + else + { + if (list.size() != sels.size()) + throw runtime_error{"The two selection lists dont have the same number of elements"}; + for (int i = 0; i < list.size(); ++i) + list[i] = select_selection(list[i], sels[i], op); + list.set_main_index(sels.main_index()); + } + func(context, std::move(list)); + }, "enter combining operator", + "'+': append lists\n" + "'<': select leftmost cursor\n" + "'>': select rightmost cursor\n"); +} + +template void save_selections(Context& context, NormalParams params) { const char reg = to_lower(params.reg ? params.reg : '^'); if (not is_basic_alpha(reg) and reg != '^') throw runtime_error("selections can only be saved to the '^' and alphabetic registers"); - auto gen_desc = [&] { - auto content = RegisterManager::instance()[reg].get(context); - const bool empty = content.size() == 1 and content[0].empty(); + auto content = RegisterManager::instance()[reg].get(context); + const bool empty = content.size() == 1 and content[0].empty(); - if (not add or empty) - return selection_list_to_string(context.selections()); - - auto selections = read_selections_from_register(reg, context); - if (&selections.buffer() != &context.buffer()) - throw runtime_error("cannot save selections from different buffers in the same register"); - selections.update(); - - for (auto& sel : context.selections()) - selections.push_back(sel); - selections.sort_and_merge_overlapping(); - return selection_list_to_string(selections); + auto save_to_reg = [reg](Context& context, const SelectionList& sels) { + String desc = format("{}@{}%{}", selection_list_to_string(sels), + context.buffer().name(), + context.buffer().timestamp()); + RegisterManager::instance()[reg].set(context, desc); + context.print_status({format("{} selections to register '{}'", combine ? "Combined" : "Saved", reg), get_face("Information")}); }; - String desc = format("{}@{}%{}", gen_desc(), - context.buffer().name(), - context.buffer().timestamp()); - - RegisterManager::instance()[reg].set(context, desc); - - context.print_status({format("{} selections to register '{}'", add ? "Added" : "Saved", reg), get_face("Information")}); + if (combine and not empty) + combine_selections(context, read_selections_from_register(reg, context), save_to_reg); + else + save_to_reg(context, context.selections()); } -template +template void restore_selections(Context& context, NormalParams params) { const char reg = to_lower(params.reg ? params.reg : '^'); - auto selections = read_selections_from_register(reg, context); + auto selections = read_selections_from_register(reg, context); - if (not add) + auto set_selections = [reg](Context& context, SelectionList sels) { + context.selections_write_only() = std::move(sels); + context.print_status({format("{} selections from register '{}'", combine ? "Combined" : "Restored", reg), get_face("Information")}); + }; + + if (not combine) { if (&selections.buffer() != &context.buffer()) context.change_buffer(selections.buffer()); + set_selections(context, std::move(selections)); } else - { - if (&selections.buffer() != &context.buffer()) - throw runtime_error("Cannot add selections from another buffer"); - - selections.update(); - int main_index = selections.size() + context.selections_write_only().main_index(); - for (auto& sel : context.selections()) - selections.push_back(std::move(sel)); - - selections.set_main_index(main_index); - selections.sort_and_merge_overlapping(); - } - - context.selections_write_only() = std::move(selections); - context.print_status({format("{} selections from register '{}'", add ? "Added" : "Restored", reg), get_face("Information")}); + combine_selections(context, std::move(selections), set_selections); } void undo(Context& context, NormalParams params) @@ -1957,9 +2001,9 @@ const HashMap keymap{ { {ctrl('d')}, {"scroll half a page down", scroll} }, { {'z'}, {"restore selections from register", restore_selections} }, - { {alt('z')}, {"append selections from register", restore_selections} }, + { {alt('z')}, {"combine selections from register", restore_selections} }, { {'Z'}, {"save selections to register", save_selections} }, - { {alt('Z')}, {"append selections to register", save_selections} }, + { {alt('Z')}, {"combine selections to register", save_selections} }, { {ctrl('l')}, {"force redraw", force_redraw} }, };