Prepare to record selection changes as perceived by the user

To be able to undo selection changes, we want to record selections
from all commands that modify selections. Each such command will get
its own private copy of the selections object.

This copy will live until the command is finished executing.
All child commands that are run while the command is executing,
will also use the same copy, because to the user it's all just one
selection change anyway.

Add an RAII object in all places where we might modify selections.
The next commit will use this to create the private selections copy
in the constructor (if there is none) and remove redundant history
items in the destructor.

We could avoid the RAII object in some places but that seems worse.
For lifetimes that don't correspond to a lexical scope, we use a
std::unique_ptr. For lambdas that require conversion to std::function,
we use std::shared_ptr because we need something that's copyable.
This commit is contained in:
Johannes Altmanninger 2022-08-06 21:55:39 +02:00
parent 611bdebf3c
commit dd4ba2ee88
4 changed files with 91 additions and 15 deletions

View File

@ -2009,6 +2009,7 @@ void context_wrap(const ParametersParser& parser, Context& context, StringView d
ScopedSetBool disable_history(c.history_disabled()); ScopedSetBool disable_history(c.history_disabled());
ScopedEdition edition{c}; ScopedEdition edition{c};
ScopedSelectionEdition selection_edition{c};
if (parser.get_switch("itersel")) if (parser.get_switch("itersel"))
{ {
@ -2523,6 +2524,7 @@ const CommandDesc select_cmd = {
else if (parser.get_switch("display-column")) else if (parser.get_switch("display-column"))
column_type = ColumnType::DisplayColumn; column_type = ColumnType::DisplayColumn;
ColumnCount tabstop = context.options()["tabstop"].get<int>(); ColumnCount tabstop = context.options()["tabstop"].get<int>();
ScopedSelectionEdition selection_edition{context};
context.selections_write_only() = selection_list_from_strings(buffer, column_type, parser.positionals_from(0), timestamp, 0, tabstop); context.selections_write_only() = selection_list_from_strings(buffer, column_type, parser.positionals_from(0), timestamp, 0, tabstop);
} }
}; };

View File

@ -180,5 +180,19 @@ private:
SafePtr<Buffer> m_buffer; SafePtr<Buffer> m_buffer;
}; };
struct ScopedSelectionEdition
{
ScopedSelectionEdition(Context& context)
: m_context{context},
m_buffer{context.has_buffer() ? &context.buffer() : nullptr} {}
ScopedSelectionEdition(ScopedSelectionEdition&& other) : m_context{other.m_context}, m_buffer{other.m_buffer}
{ other.m_buffer = nullptr; }
~ScopedSelectionEdition() {}
private:
Context& m_context;
SafePtr<Buffer> m_buffer;
};
} }
#endif // context_hh_INCLUDED #endif // context_hh_INCLUDED

View File

@ -93,59 +93,67 @@ struct MouseHandler
Buffer& buffer = context.buffer(); Buffer& buffer = context.buffer();
BufferCoord cursor; BufferCoord cursor;
auto& selections = context.selections();
constexpr auto modifiers = Key::Modifiers::Control | Key::Modifiers::Alt | Key::Modifiers::Shift | Key::Modifiers::MouseButtonMask; constexpr auto modifiers = Key::Modifiers::Control | Key::Modifiers::Alt | Key::Modifiers::Shift | Key::Modifiers::MouseButtonMask;
switch ((key.modifiers & ~modifiers).value) switch ((key.modifiers & ~modifiers).value)
{ {
case Key::Modifiers::MousePress: case Key::Modifiers::MousePress:
switch (key.mouse_button()) switch (key.mouse_button())
{ {
case Key::MouseButton::Right: case Key::MouseButton::Right: {
m_dragging = false; m_dragging.reset();
cursor = context.window().buffer_coord(key.coord()); cursor = context.window().buffer_coord(key.coord());
ScopedSelectionEdition selection_edition{context};
auto& selections = context.selections();
if (key.modifiers & Key::Modifiers::Control) if (key.modifiers & Key::Modifiers::Control)
selections = {{selections.begin()->anchor(), cursor}}; selections = {{selections.begin()->anchor(), cursor}};
else else
selections.main() = {selections.main().anchor(), cursor}; selections.main() = {selections.main().anchor(), cursor};
selections.sort_and_merge_overlapping(); selections.sort_and_merge_overlapping();
return true; return true;
}
case Key::MouseButton::Left: case Key::MouseButton::Left: {
m_dragging = true; m_dragging.reset(new ScopedSelectionEdition{context});
m_anchor = context.window().buffer_coord(key.coord()); m_anchor = context.window().buffer_coord(key.coord());
if (not (key.modifiers & Key::Modifiers::Control)) if (not (key.modifiers & Key::Modifiers::Control))
context.selections_write_only() = { buffer, m_anchor}; context.selections_write_only() = { buffer, m_anchor};
else else
{ {
auto& selections = context.selections();
size_t main = selections.size(); size_t main = selections.size();
selections.push_back({m_anchor}); selections.push_back({m_anchor});
selections.set_main_index(main); selections.set_main_index(main);
selections.sort_and_merge_overlapping(); selections.sort_and_merge_overlapping();
} }
return true; return true;
}
default: return true; default: return true;
} }
case Key::Modifiers::MouseRelease: case Key::Modifiers::MouseRelease: {
if (not m_dragging) if (not m_dragging)
return true; return true;
m_dragging = false; auto& selections = context.selections();
cursor = context.window().buffer_coord(key.coord()); cursor = context.window().buffer_coord(key.coord());
selections.main() = {buffer.clamp(m_anchor), cursor}; selections.main() = {buffer.clamp(m_anchor), cursor};
selections.sort_and_merge_overlapping(); selections.sort_and_merge_overlapping();
m_dragging.reset();
return true; return true;
}
case Key::Modifiers::MousePos: case Key::Modifiers::MousePos: {
if (not m_dragging) if (not m_dragging)
return true; return true;
cursor = context.window().buffer_coord(key.coord()); cursor = context.window().buffer_coord(key.coord());
auto& selections = context.selections();
selections.main() = {buffer.clamp(m_anchor), cursor}; selections.main() = {buffer.clamp(m_anchor), cursor};
selections.sort_and_merge_overlapping(); selections.sort_and_merge_overlapping();
return true; return true;
}
case Key::Modifiers::Scroll: case Key::Modifiers::Scroll:
scroll_window(context, static_cast<int32_t>(key.key), m_dragging); scroll_window(context, static_cast<int32_t>(key.key), (bool)m_dragging);
return true; return true;
default: return false; default: return false;
@ -153,7 +161,7 @@ struct MouseHandler
} }
private: private:
bool m_dragging = false; std::unique_ptr<ScopedSelectionEdition> m_dragging;
BufferCoord m_anchor; BufferCoord m_anchor;
}; };
@ -1199,6 +1207,7 @@ public:
Insert(InputHandler& input_handler, InsertMode mode, int count) Insert(InputHandler& input_handler, InsertMode mode, int count)
: InputMode(input_handler), : InputMode(input_handler),
m_edition(context()), m_edition(context()),
m_selection_edition(context()),
m_completer(context()), m_completer(context()),
m_restore_cursor(mode == InsertMode::Append), m_restore_cursor(mode == InsertMode::Append),
m_auto_complete{context().options()["autocomplete"].get<AutoComplete>() & AutoComplete::Insert}, m_auto_complete{context().options()["autocomplete"].get<AutoComplete>() & AutoComplete::Insert},
@ -1549,6 +1558,7 @@ private:
} }
ScopedEdition m_edition; ScopedEdition m_edition;
ScopedSelectionEdition m_selection_edition;
InsertCompleter m_completer; InsertCompleter m_completer;
const bool m_restore_cursor; const bool m_restore_cursor;
bool m_auto_complete; bool m_auto_complete;
@ -1806,6 +1816,7 @@ void scroll_window(Context& context, LineCount offset, bool mouse_dragging)
win_pos.line = clamp(win_pos.line + offset, 0_line, line_count-1); win_pos.line = clamp(win_pos.line + offset, 0_line, line_count-1);
ScopedSelectionEdition selection_edition{context};
SelectionList& selections = context.selections(); SelectionList& selections = context.selections();
Selection& main_selection = selections.main(); Selection& main_selection = selections.main();
const BufferCoord anchor = main_selection.anchor(); const BufferCoord anchor = main_selection.anchor();

View File

@ -74,6 +74,7 @@ UnitTest test_merge_selection{[] {
template<SelectMode mode, typename T> template<SelectMode mode, typename T>
void select(Context& context, T func) void select(Context& context, T func)
{ {
ScopedSelectionEdition selection_edition{context};
auto& selections = context.selections(); auto& selections = context.selections();
if (mode == SelectMode::Append) if (mode == SelectMode::Append)
{ {
@ -143,6 +144,7 @@ template<SelectMode mode = SelectMode::Replace>
void select_coord(Context& context, BufferCoord coord) void select_coord(Context& context, BufferCoord coord)
{ {
Buffer& buffer = context.buffer(); Buffer& buffer = context.buffer();
ScopedSelectionEdition selection_edition{context};
SelectionList& selections = context.selections(); SelectionList& selections = context.selections();
coord = buffer.clamp(coord); coord = buffer.clamp(coord);
if (mode == SelectMode::Replace) if (mode == SelectMode::Replace)
@ -422,6 +424,7 @@ void replace_with_char(Context& context, NormalParams)
if (not cp or key == Key::Escape) if (not cp or key == Key::Escape)
return; return;
ScopedEdition edition(context); ScopedEdition edition(context);
ScopedSelectionEdition selection_edition{context};
Buffer& buffer = context.buffer(); Buffer& buffer = context.buffer();
context.selections().for_each([&](size_t index, Selection& sel) { context.selections().for_each([&](size_t index, Selection& sel) {
CharCount count = char_length(buffer, sel); CharCount count = char_length(buffer, sel);
@ -442,6 +445,7 @@ void for_each_codepoint(Context& context, NormalParams)
using Utf8It = utf8::iterator<BufferIterator>; using Utf8It = utf8::iterator<BufferIterator>;
ScopedEdition edition(context); ScopedEdition edition(context);
ScopedSelectionEdition selection_edition{context};
Buffer& buffer = context.buffer(); Buffer& buffer = context.buffer();
context.selections().for_each([&](size_t index, Selection& sel) { context.selections().for_each([&](size_t index, Selection& sel) {
@ -555,7 +559,8 @@ void pipe(Context& context, NormalParams params)
prompt, {}, default_command, context.faces()["Prompt"], prompt, {}, default_command, context.faces()["Prompt"],
PromptFlags::DropHistoryEntriesWithBlankPrefix, '|', PromptFlags::DropHistoryEntriesWithBlankPrefix, '|',
shell_complete, shell_complete,
[default_command](StringView cmdline, PromptEvent event, Context& context) [default_command, selection_edition=std::make_shared<ScopedSelectionEdition>(context)]
(StringView cmdline, PromptEvent event, Context& context)
{ {
if (event != PromptEvent::Validate) if (event != PromptEvent::Validate)
return; return;
@ -637,6 +642,7 @@ void erase_selections(Context& context, NormalParams params)
RegisterManager::instance()[reg].set(context, context.selections_content()); RegisterManager::instance()[reg].set(context, context.selections_content());
} }
ScopedEdition edition(context); ScopedEdition edition(context);
ScopedSelectionEdition selection_edition{context};
context.selections().erase(); context.selections().erase();
} }
@ -683,6 +689,7 @@ void paste(Context& context, NormalParams params)
auto& buffer = context.buffer(); auto& buffer = context.buffer();
ScopedEdition edition(context); ScopedEdition edition(context);
ScopedSelectionEdition selection_edition{context};
context.selections().for_each([&](size_t index, Selection& sel) { context.selections().for_each([&](size_t index, Selection& sel) {
auto& str = strings[std::min(strings.size()-1, index)]; auto& str = strings[std::min(strings.size()-1, index)];
auto& min = sel.min(); auto& min = sel.min();
@ -719,6 +726,7 @@ void paste_all(Context& context, NormalParams params)
Buffer& buffer = context.buffer(); Buffer& buffer = context.buffer();
Vector<Selection> result; Vector<Selection> result;
ScopedSelectionEdition selection_edition{context};
auto& selections = context.selections(); auto& selections = context.selections();
{ {
ScopedEdition edition(context); ScopedEdition edition(context);
@ -751,7 +759,8 @@ void insert_output(Context& context, NormalParams params)
prompt, {}, default_command, context.faces()["Prompt"], prompt, {}, default_command, context.faces()["Prompt"],
PromptFlags::DropHistoryEntriesWithBlankPrefix, '|', PromptFlags::DropHistoryEntriesWithBlankPrefix, '|',
shell_complete, shell_complete,
[default_command](StringView cmdline, PromptEvent event, Context& context) [default_command, selection_edition=std::make_shared<ScopedSelectionEdition>(context)]
(StringView cmdline, PromptEvent event, Context& context)
{ {
if (event != PromptEvent::Validate) if (event != PromptEvent::Validate)
return; return;
@ -827,7 +836,8 @@ void regex_prompt(Context& context, String prompt, char reg, T func)
[&](auto&& m) { candidates.push_back(m.candidate().str()); return true; }); [&](auto&& m) { candidates.push_back(m.candidate().str()); return true; });
return {(int)(word.begin() - regex.begin()), pos, std::move(candidates) }; return {(int)(word.begin() - regex.begin()), pos, std::move(candidates) };
}, },
[=, func=T(std::move(func))](StringView str, PromptEvent event, Context& context) mutable { [=, func=T(std::move(func)), selection_edition=std::make_shared<ScopedSelectionEdition>(context)]
(StringView str, PromptEvent event, Context& context) mutable {
try try
{ {
if (event != PromptEvent::Change and context.has_client()) if (event != PromptEvent::Change and context.has_client())
@ -947,6 +957,7 @@ void search_next(Context& context, NormalParams params)
if (not str.empty()) if (not str.empty())
{ {
Regex regex{str, direction_flags(regex_mode)}; Regex regex{str, direction_flags(regex_mode)};
ScopedSelectionEdition selection_edition{context};
auto& selections = context.selections(); auto& selections = context.selections();
bool main_wrapped = false; bool main_wrapped = false;
do { do {
@ -1048,6 +1059,7 @@ void split_regex(Context& context, NormalParams params)
void split_lines(Context& context, NormalParams params) void split_lines(Context& context, NormalParams params)
{ {
const LineCount count{params.count == 0 ? 1 : params.count}; const LineCount count{params.count == 0 ? 1 : params.count};
ScopedSelectionEdition selection_edition{context};
auto& selections = context.selections(); auto& selections = context.selections();
auto& buffer = context.buffer(); auto& buffer = context.buffer();
Vector<Selection> res; Vector<Selection> res;
@ -1074,6 +1086,7 @@ void split_lines(Context& context, NormalParams params)
void select_boundaries(Context& context, NormalParams) void select_boundaries(Context& context, NormalParams)
{ {
ScopedSelectionEdition selection_edition{context};
auto& selections = context.selections(); auto& selections = context.selections();
Vector<Selection> res; Vector<Selection> res;
for (auto& sel : selections) for (auto& sel : selections)
@ -1089,6 +1102,7 @@ void join_lines_select_spaces(Context& context, NormalParams)
{ {
auto& buffer = context.buffer(); auto& buffer = context.buffer();
Vector<Selection> selections; Vector<Selection> selections;
ScopedSelectionEdition selection_edition{context};
for (auto& sel : context.selections()) for (auto& sel : context.selections())
{ {
const LineCount min_line = sel.min().line; const LineCount min_line = sel.min().line;
@ -1112,6 +1126,7 @@ void join_lines_select_spaces(Context& context, NormalParams)
void join_lines(Context& context, NormalParams params) void join_lines(Context& context, NormalParams params)
{ {
ScopedSelectionEdition selection_edition{context};
SelectionList sels{context.selections()}; SelectionList sels{context.selections()};
auto restore_sels = on_scope_end([&]{ auto restore_sels = on_scope_end([&]{
sels.update(); sels.update();
@ -1129,7 +1144,8 @@ void keep(Context& context, NormalParams params)
const char reg = to_lower(params.reg ? params.reg : '/'); const char reg = to_lower(params.reg ? params.reg : '/');
regex_prompt(context, prompt.str(), reg, regex_prompt(context, prompt.str(), reg,
[reg, saved_reg = RegisterManager::instance()[reg].save(context)] [reg, saved_reg = RegisterManager::instance()[reg].save(context),
selection_edition=std::make_shared<ScopedSelectionEdition>(context)]
(const Regex& regex, PromptEvent event, Context& context) { (const Regex& regex, PromptEvent event, Context& context) {
RegisterManager::instance()[reg].restore(context, saved_reg); RegisterManager::instance()[reg].restore(context, saved_reg);
if (event == PromptEvent::Abort) if (event == PromptEvent::Abort)
@ -1166,7 +1182,8 @@ void keep_pipe(Context& context, NormalParams params)
context.input_handler().prompt( context.input_handler().prompt(
"keep pipe:", {}, default_command, context.faces()["Prompt"], "keep pipe:", {}, default_command, context.faces()["Prompt"],
PromptFlags::DropHistoryEntriesWithBlankPrefix, '|', shell_complete, PromptFlags::DropHistoryEntriesWithBlankPrefix, '|', shell_complete,
[default_command](StringView cmdline, PromptEvent event, Context& context) { [default_command, selection_edition=std::make_shared<ScopedSelectionEdition>(context)]
(StringView cmdline, PromptEvent event, Context& context) {
if (event != PromptEvent::Validate) if (event != PromptEvent::Validate)
return; return;
@ -1212,6 +1229,7 @@ void indent(Context& context, NormalParams params)
ScopedEdition edition(context); ScopedEdition edition(context);
auto& buffer = context.buffer(); auto& buffer = context.buffer();
LineCount last_line = 0; LineCount last_line = 0;
ScopedSelectionEdition selection_edition{context};
for (auto& sel : context.selections()) for (auto& sel : context.selections())
{ {
for (auto line = std::max(last_line, sel.min().line); line < sel.max().line+1; ++line) for (auto line = std::max(last_line, sel.min().line); line < sel.max().line+1; ++line)
@ -1236,6 +1254,7 @@ void deindent(Context& context, NormalParams params)
auto& buffer = context.buffer(); auto& buffer = context.buffer();
LineCount last_line = 0; LineCount last_line = 0;
ScopedSelectionEdition selection_edition{context};
for (auto& sel : context.selections()) for (auto& sel : context.selections())
{ {
for (auto line = std::max(sel.min().line, last_line); for (auto line = std::max(sel.min().line, last_line);
@ -1416,6 +1435,7 @@ void scroll(Context& context, NormalParams params)
template<Direction direction> template<Direction direction>
void copy_selections_on_next_lines(Context& context, NormalParams params) void copy_selections_on_next_lines(Context& context, NormalParams params)
{ {
ScopedSelectionEdition selection_edition{context};
auto& selections = context.selections(); auto& selections = context.selections();
auto& buffer = context.buffer(); auto& buffer = context.buffer();
const ColumnCount tabstop = context.options()["tabstop"].get<int>(); const ColumnCount tabstop = context.options()["tabstop"].get<int>();
@ -1469,6 +1489,7 @@ template<Direction direction>
void rotate_selections(Context& context, NormalParams params) void rotate_selections(Context& context, NormalParams params)
{ {
const int count = params.count ? params.count : 1; const int count = params.count ? params.count : 1;
ScopedSelectionEdition selection_edition{context};
auto& selections = context.selections(); auto& selections = context.selections();
const int index = selections.main_index(); const int index = selections.main_index();
const int num = selections.size(); const int num = selections.size();
@ -1486,6 +1507,7 @@ void rotate_selections_content(Context& context, NormalParams params)
if (group == 0 or group > (int)strings.size()) if (group == 0 or group > (int)strings.size())
group = (int)strings.size(); group = (int)strings.size();
count = count % group; count = count % group;
ScopedSelectionEdition selection_edition{context};
auto& selections = context.selections(); auto& selections = context.selections();
auto main = strings.begin() + selections.main_index(); auto main = strings.begin() + selections.main_index();
for (auto it = strings.begin(); it != strings.end(); ) for (auto it = strings.begin(); it != strings.end(); )
@ -1570,6 +1592,7 @@ void replay_macro(Context& context, NormalParams params)
auto keys = parse_keys(reg_val[0]); auto keys = parse_keys(reg_val[0]);
ScopedEdition edition(context); ScopedEdition edition(context);
ScopedSelectionEdition selection_edition{context};
do do
{ {
for (auto& key : keys) for (auto& key : keys)
@ -1587,6 +1610,7 @@ void jump(Context& context, NormalParams params)
Buffer* oldbuf = &context.buffer(); Buffer* oldbuf = &context.buffer();
Buffer& buffer = const_cast<Buffer&>(jump.buffer()); Buffer& buffer = const_cast<Buffer&>(jump.buffer());
ScopedSelectionEdition selection_edition{context};
if (&buffer != oldbuf) if (&buffer != oldbuf)
context.change_buffer(buffer); context.change_buffer(buffer);
context.selections_write_only() = jump; context.selections_write_only() = jump;
@ -1601,6 +1625,7 @@ void push_selections(Context& context, NormalParams)
void align(Context& context, NormalParams) void align(Context& context, NormalParams)
{ {
ScopedSelectionEdition selection_edition{context};
auto& selections = context.selections(); auto& selections = context.selections();
auto& buffer = context.buffer(); auto& buffer = context.buffer();
const ColumnCount tabstop = context.options()["tabstop"].get<int>(); const ColumnCount tabstop = context.options()["tabstop"].get<int>();
@ -1654,6 +1679,7 @@ void copy_indent(Context& context, NormalParams params)
{ {
int selection = params.count; int selection = params.count;
auto& buffer = context.buffer(); auto& buffer = context.buffer();
ScopedSelectionEdition selection_edition{context};
auto& selections = context.selections(); auto& selections = context.selections();
Vector<LineCount> lines; Vector<LineCount> lines;
for (auto sel : selections) for (auto sel : selections)
@ -1694,6 +1720,7 @@ void tabs_to_spaces(Context& context, NormalParams params)
const ColumnCount tabstop = params.count == 0 ? opt_tabstop : params.count; const ColumnCount tabstop = params.count == 0 ? opt_tabstop : params.count;
Vector<Selection> tabs; Vector<Selection> tabs;
Vector<String> spaces; Vector<String> spaces;
ScopedSelectionEdition selection_edition{context};
for (auto& sel : context.selections()) for (auto& sel : context.selections())
{ {
for (auto it = buffer.iterator_at(sel.min()), for (auto it = buffer.iterator_at(sel.min()),
@ -1718,6 +1745,7 @@ void spaces_to_tabs(Context& context, NormalParams params)
const ColumnCount opt_tabstop = context.options()["tabstop"].get<int>(); const ColumnCount opt_tabstop = context.options()["tabstop"].get<int>();
const ColumnCount tabstop = params.count == 0 ? opt_tabstop : params.count; const ColumnCount tabstop = params.count == 0 ? opt_tabstop : params.count;
Vector<Selection> spaces; Vector<Selection> spaces;
ScopedSelectionEdition selection_edition{context};
for (auto& sel : context.selections()) for (auto& sel : context.selections())
{ {
for (auto it = buffer.iterator_at(sel.min()), for (auto it = buffer.iterator_at(sel.min()),
@ -1751,6 +1779,7 @@ void spaces_to_tabs(Context& context, NormalParams params)
void trim_selections(Context& context, NormalParams) void trim_selections(Context& context, NormalParams)
{ {
auto& buffer = context.buffer(); auto& buffer = context.buffer();
ScopedSelectionEdition selection_edition{context};
auto& selections = context.selections(); auto& selections = context.selections();
Vector<int> to_remove; Vector<int> to_remove;
@ -1874,6 +1903,7 @@ void combine_selections(Context& context, SelectionList list, Func func, StringV
return; return;
const auto op = key_to_combine_op(key); const auto op = key_to_combine_op(key);
ScopedSelectionEdition selection_edition{context};
auto& sels = context.selections(); auto& sels = context.selections();
list.update(); list.update();
if (op == CombineOp::Append) if (op == CombineOp::Append)
@ -1927,7 +1957,10 @@ void save_selections(Context& context, NormalParams params)
}; };
if (combine and not empty) if (combine and not empty)
{
ScopedSelectionEdition selection_edition{context};
combine_selections(context, read_selections_from_register(reg, context), save_to_reg, "combine selections to register"); combine_selections(context, read_selections_from_register(reg, context), save_to_reg, "combine selections to register");
}
else else
save_to_reg(context, context.selections()); save_to_reg(context, context.selections());
} }
@ -1938,6 +1971,8 @@ void restore_selections(Context& context, NormalParams params)
const char reg = to_lower(params.reg ? params.reg : '^'); 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);
ScopedSelectionEdition selection_edition{context};
auto set_selections = [reg](Context& context, SelectionList sels) { auto set_selections = [reg](Context& context, SelectionList sels) {
auto size = sels.size(); auto size = sels.size();
context.selections_write_only() = std::move(sels); context.selections_write_only() = std::move(sels);
@ -2021,6 +2056,7 @@ void exec_user_mappings(Context& context, NormalParams params)
InputHandler::ScopedForceNormal force_normal{context.input_handler(), params}; InputHandler::ScopedForceNormal force_normal{context.input_handler(), params};
ScopedEdition edition(context); ScopedEdition edition(context);
ScopedSelectionEdition selection_edition{context};
for (auto& key : mapping.keys) for (auto& key : mapping.keys)
context.input_handler().handle_key(key); context.input_handler().handle_key(key);
}, "user mapping", }, "user mapping",
@ -2033,6 +2069,7 @@ void add_empty_line(Context& context, NormalParams params)
int count = std::max(params.count, 1); int count = std::max(params.count, 1);
String new_lines{'\n', CharCount{count}}; String new_lines{'\n', CharCount{count}};
auto& buffer = context.buffer(); auto& buffer = context.buffer();
ScopedSelectionEdition selection_edition{context};
auto& sels = context.selections(); auto& sels = context.selections();
ScopedEdition edition{context}; ScopedEdition edition{context};
for (int i = 0; i < sels.size(); ++i) for (int i = 0; i < sels.size(); ++i)
@ -2051,6 +2088,7 @@ public:
void operator() (Context& context, NormalParams params) void operator() (Context& context, NormalParams params)
{ {
ScopedEdition edition(context); ScopedEdition edition(context);
ScopedSelectionEdition selection_edition{context};
do { m_func(context, {0, params.reg}); } while(--params.count > 0); do { m_func(context, {0, params.reg}); } while(--params.count > 0);
} }
private: private:
@ -2061,6 +2099,7 @@ template<void (*func)(Context&, NormalParams)>
void repeated(Context& context, NormalParams params) void repeated(Context& context, NormalParams params)
{ {
ScopedEdition edition(context); ScopedEdition edition(context);
ScopedSelectionEdition selection_edition{context};
do { func(context, {0, params.reg}); } while(--params.count > 0); do { func(context, {0, params.reg}); } while(--params.count > 0);
} }
@ -2070,6 +2109,7 @@ void move_cursor(Context& context, NormalParams params)
kak_assert(mode == SelectMode::Replace or mode == SelectMode::Extend); kak_assert(mode == SelectMode::Replace or mode == SelectMode::Extend);
const Type offset{direction * std::max(params.count,1)}; const Type offset{direction * std::max(params.count,1)};
const ColumnCount tabstop = context.options()["tabstop"].get<int>(); const ColumnCount tabstop = context.options()["tabstop"].get<int>();
ScopedSelectionEdition selection_edition{context};
auto& selections = context.selections(); auto& selections = context.selections();
for (auto& sel : selections) for (auto& sel : selections)
{ {
@ -2083,11 +2123,13 @@ void move_cursor(Context& context, NormalParams params)
void select_whole_buffer(Context& context, NormalParams) void select_whole_buffer(Context& context, NormalParams)
{ {
auto& buffer = context.buffer(); auto& buffer = context.buffer();
ScopedSelectionEdition selection_edition{context};
context.selections_write_only() = SelectionList{buffer, {{0,0}, {buffer.back_coord(), max_column}}}; context.selections_write_only() = SelectionList{buffer, {{0,0}, {buffer.back_coord(), max_column}}};
} }
void keep_selection(Context& context, NormalParams p) void keep_selection(Context& context, NormalParams p)
{ {
ScopedSelectionEdition selection_edition{context};
auto& selections = context.selections(); auto& selections = context.selections();
const int index = p.count ? p.count-1 : selections.main_index(); const int index = p.count ? p.count-1 : selections.main_index();
if (index >= selections.size()) if (index >= selections.size())
@ -2099,6 +2141,7 @@ void keep_selection(Context& context, NormalParams p)
void remove_selection(Context& context, NormalParams p) void remove_selection(Context& context, NormalParams p)
{ {
ScopedSelectionEdition selection_edition{context};
auto& selections = context.selections(); auto& selections = context.selections();
const int index = p.count ? p.count-1 : selections.main_index(); const int index = p.count ? p.count-1 : selections.main_index();
if (index >= selections.size()) if (index >= selections.size())
@ -2112,12 +2155,14 @@ void remove_selection(Context& context, NormalParams p)
void clear_selections(Context& context, NormalParams) void clear_selections(Context& context, NormalParams)
{ {
ScopedSelectionEdition selection_edition{context};
for (auto& sel : context.selections()) for (auto& sel : context.selections())
sel.anchor() = sel.cursor(); sel.anchor() = sel.cursor();
} }
void flip_selections(Context& context, NormalParams) void flip_selections(Context& context, NormalParams)
{ {
ScopedSelectionEdition selection_edition{context};
for (auto& sel : context.selections()) for (auto& sel : context.selections())
{ {
const BufferCoord tmp = sel.anchor(); const BufferCoord tmp = sel.anchor();
@ -2129,6 +2174,7 @@ void flip_selections(Context& context, NormalParams)
void ensure_forward(Context& context, NormalParams) void ensure_forward(Context& context, NormalParams)
{ {
ScopedSelectionEdition selection_edition{context};
for (auto& sel : context.selections()) for (auto& sel : context.selections())
{ {
const BufferCoord min = sel.min(), max = sel.max(); const BufferCoord min = sel.min(), max = sel.max();
@ -2140,18 +2186,21 @@ void ensure_forward(Context& context, NormalParams)
void merge_consecutive(Context& context, NormalParams params) void merge_consecutive(Context& context, NormalParams params)
{ {
ScopedSelectionEdition selection_edition{context};
ensure_forward(context, params); ensure_forward(context, params);
context.selections().merge_consecutive(); context.selections().merge_consecutive();
} }
void merge_overlapping(Context& context, NormalParams params) void merge_overlapping(Context& context, NormalParams params)
{ {
ScopedSelectionEdition selection_edition{context};
ensure_forward(context, params); ensure_forward(context, params);
context.selections().merge_overlapping(); context.selections().merge_overlapping();
} }
void duplicate_selections(Context& context, NormalParams params) void duplicate_selections(Context& context, NormalParams params)
{ {
ScopedSelectionEdition selection_edition{context};
SelectionList& sels = context.selections(); SelectionList& sels = context.selections();
Vector<Selection> new_sels; Vector<Selection> new_sels;
const int count = params.count ? params.count : 2; const int count = params.count ? params.count : 2;