Change <a-z>/<a-Z> to combine selections instead of appending

This commit is contained in:
Maxime Coste 2017-05-20 10:31:49 +01:00
parent 55d1d1020d
commit b440d9f537
4 changed files with 109 additions and 48 deletions

View File

@ -515,9 +515,15 @@ By default, marks use the '^' register, but using the register can be set
using `"<reg>` prefix.
`Z` will save the current selections to the register.
`<a-Z>` will append the current selections to the register.
`<a-Z>` will combine the current selections to the register.
`z` will restore the selections from the register.
`<a-z>` will add the selections from the register to the existing ones.
`<a-z>` 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
~~~~~~~~~

View File

@ -417,13 +417,22 @@ Marks use the *^* register by default.
will save the current selections to the register
*<a-Z>*::
will append the current selections to the register
will combine the current selections to the register
*z*::
will restore the selections from the register
*<a-z>*::
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
------

View File

@ -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"
" * <a-z> and <a-Z> are not append to/from register anymore but\n"
" combine from/to register.\n";
struct startup_error : runtime_error
{

View File

@ -1531,67 +1531,111 @@ SelectionList read_selections_from_register(char reg, Context& context)
return {buffer, std::move(sels), timestamp};
}
template<bool add>
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<typename Func>
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<bool combine>
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();
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);
};
String desc = format("{}@{}%{}", gen_desc(),
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")});
};
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<bool add>
template<bool combine>
void restore_selections(Context& context, NormalParams params)
{
const char reg = to_lower(params.reg ? params.reg : '^');
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<Key, NormalCmd> keymap{
{ {ctrl('d')}, {"scroll half a page down", scroll<Forward, true>} },
{ {'z'}, {"restore selections from register", restore_selections<false>} },
{ {alt('z')}, {"append selections from register", restore_selections<true>} },
{ {alt('z')}, {"combine selections from register", restore_selections<true>} },
{ {'Z'}, {"save selections to register", save_selections<false>} },
{ {alt('Z')}, {"append selections to register", save_selections<true>} },
{ {alt('Z')}, {"combine selections to register", save_selections<true>} },
{ {ctrl('l')}, {"force redraw", force_redraw} },
};