Move insert and erase to normal.cc, and move edition management to context

This commit is contained in:
Maxime Coste 2013-12-15 18:07:51 +00:00
parent e369b60258
commit 7267b8281f
9 changed files with 200 additions and 227 deletions

View File

@ -109,7 +109,7 @@ void ClientManager::ensure_no_client_uses_buffer(Buffer& buffer)
if (&client->context().buffer() != &buffer)
continue;
if (client->context().editor().is_editing())
if (client->context().is_editing())
throw runtime_error("client '" + client->context().name() + "' is inserting in '" +
buffer.display_name() + '\'');

View File

@ -573,6 +573,11 @@ void context_wrap(CommandParameters params, Context& context, Func func)
DynamicSelectionList sels{editor.buffer(), editor.selections()};
auto restore_sels = on_scope_end([&]{ editor.selections() = std::move(sels); });
// We do not want this draft context to commit undo groups if the real one is
// going to commit the whole thing later
if (real_context->is_editing())
input_handler.context().disable_undo_handling();
if (parser.has_option("itersel"))
{
for (auto& sel : sels)
@ -804,7 +809,7 @@ void exec_keys(const KeyList& keys, Context& context)
RegisterRestorer quote('"', context);
RegisterRestorer slash('/', context);
scoped_edition edition(context.editor());
ScopedEdition edition(context);
for (auto& key : keys)
context.input_handler().handle_key(key);

View File

@ -187,4 +187,18 @@ const SelectionList& Context::selections() const
return editor().selections();
}
void Context::begin_edition()
{
++m_edition_level;
}
void Context::end_edition()
{
kak_assert(m_edition_level > 0);
if (m_edition_level == 1)
buffer().commit_undo_group();
--m_edition_level;
}
}

View File

@ -70,7 +70,15 @@ public:
const String& name() const { return m_name; }
void set_name(String name) { m_name = std::move(name); }
bool is_editing() const { return m_edition_level!= 0; }
void disable_undo_handling() { ++m_edition_level; }
private:
void begin_edition();
void end_edition();
int m_edition_level = 0;
friend struct ScopedEdition;
safe_ptr<Editor> m_editor;
safe_ptr<InputHandler> m_input_handler;
safe_ptr<Client> m_client;
@ -82,5 +90,19 @@ private:
JumpList::iterator m_current_jump = m_jump_list.begin();
};
struct ScopedEdition
{
ScopedEdition(Context& context)
: m_context(context)
{ m_context.begin_edition(); }
~ScopedEdition()
{ m_context.end_edition(); }
Context& context() const { return m_context; }
private:
Context& m_context;
};
}
#endif // context_hh_INCLUDED

View File

@ -13,92 +13,9 @@ namespace Kakoune
Editor::Editor(Buffer& buffer)
: m_buffer(&buffer),
m_edition_level(0),
m_selections(buffer, {BufferCoord{}})
{}
void Editor::erase()
{
scoped_edition edition(*this);
for (auto& sel : m_selections)
{
Kakoune::erase(*m_buffer, sel);
avoid_eol(*m_buffer, sel);
}
}
static BufferIterator prepare_insert(Buffer& buffer, const Selection& sel,
InsertMode mode)
{
switch (mode)
{
case InsertMode::Insert:
return buffer.iterator_at(sel.min());
case InsertMode::Replace:
return Kakoune::erase(buffer, sel);
case InsertMode::Append:
{
// special case for end of lines, append to current line instead
auto pos = buffer.iterator_at(sel.max());
return *pos == '\n' ? pos : utf8::next(pos);
}
case InsertMode::InsertAtLineBegin:
return buffer.iterator_at(sel.min().line);
case InsertMode::AppendAtLineEnd:
return buffer.iterator_at({sel.max().line, buffer[sel.max().line].length() - 1});
case InsertMode::InsertAtNextLineBegin:
return buffer.iterator_at(sel.max().line+1);
case InsertMode::OpenLineBelow:
return buffer.insert(buffer.iterator_at(sel.max().line + 1), "\n");
case InsertMode::OpenLineAbove:
return buffer.insert(buffer.iterator_at(sel.min().line), "\n");
}
kak_assert(false);
return {};
}
void Editor::insert(const String& str, InsertMode mode)
{
scoped_edition edition(*this);
for (auto& sel : m_selections)
{
auto pos = prepare_insert(*m_buffer, sel, mode);
pos = m_buffer->insert(pos, str);
if (mode == InsertMode::Replace and pos != m_buffer->end())
{
sel.first() = pos.coord();
sel.last() = str.empty() ?
pos.coord() : (pos + str.byte_count_to(str.char_length() - 1)).coord();
}
avoid_eol(*m_buffer, sel);
}
check_invariant();
}
void Editor::insert(memoryview<String> strings, InsertMode mode)
{
scoped_edition edition(*this);
if (strings.empty())
return;
for (size_t i = 0; i < selections().size(); ++i)
{
auto& sel = m_selections[i];
auto pos = prepare_insert(*m_buffer, sel, mode);
const String& str = strings[std::min(i, strings.size()-1)];
pos = m_buffer->insert(pos, str);
if (mode == InsertMode::Replace and pos != m_buffer->end())
{
sel.first() = pos.coord();
sel.last() = (str.empty() ?
pos : pos + str.byte_count_to(str.char_length() - 1)).coord();
}
avoid_eol(*m_buffer, sel);
}
check_invariant();
}
std::vector<String> Editor::selections_content() const
{
std::vector<String> contents;
@ -182,18 +99,4 @@ void Editor::check_invariant() const
#endif
}
void Editor::begin_edition()
{
++m_edition_level;
}
void Editor::end_edition()
{
kak_assert(m_edition_level > 0);
if (m_edition_level == 1)
m_buffer->commit_undo_group();
--m_edition_level;
}
}

View File

@ -38,13 +38,6 @@ public:
Buffer& buffer() const { return *m_buffer; }
void erase();
void insert(const String& string,
InsertMode mode = InsertMode::Insert);
void insert(memoryview<String> strings,
InsertMode mode = InsertMode::Insert);
const SelectionList& selections() const { return m_selections; }
SelectionList& selections() { return m_selections; }
std::vector<String> selections_content() const;
@ -52,14 +45,9 @@ public:
bool undo();
bool redo();
bool is_editing() const { return m_edition_level!= 0; }
private:
friend struct scoped_edition;
friend class InputModes::Insert;
void begin_edition();
void end_edition();
int m_edition_level;
void check_invariant() const;
@ -67,20 +55,6 @@ private:
DynamicSelectionList m_selections;
};
struct scoped_edition
{
scoped_edition(Editor& editor)
: m_editor(editor)
{ m_editor.begin_edition(); }
~scoped_edition()
{ m_editor.end_edition(); }
Editor& editor() const { return m_editor; }
private:
Editor& m_editor;
};
}
#endif // editor_hh_INCLUDED

View File

@ -873,7 +873,7 @@ public:
Insert(InputHandler& input_handler, InsertMode mode)
: InputMode(input_handler),
m_insert_mode(mode),
m_edition(context().editor()),
m_edition(context()),
m_completer(context()),
m_idle_timer{Clock::now() + idle_timeout,
[this](Timer& timer) {
@ -982,8 +982,8 @@ public:
private:
void erase() const
{
auto& buffer = m_edition.editor().buffer();
for (auto& sel : m_edition.editor().selections())
auto& buffer = context().buffer();
for (auto& sel : context().selections())
{
if (sel.last() == BufferCoord{0,0})
continue;
@ -1007,8 +1007,8 @@ private:
void insert(memoryview<String> strings)
{
auto& buffer = m_edition.editor().buffer();
auto& selections = m_edition.editor().selections();
auto& buffer = context().buffer();
auto& selections = context().selections();
for (size_t i = 0; i < selections.size(); ++i)
{
size_t index = std::min(i, strings.size()-1);
@ -1020,18 +1020,18 @@ private:
void insert(Codepoint key)
{
auto str = codepoint_to_str(key);
auto& buffer = m_edition.editor().buffer();
for (auto& sel : m_edition.editor().selections())
auto& buffer = context().buffer();
for (auto& sel : context().selections())
buffer.insert(buffer.iterator_at(sel.last()), str);
context().hooks().run_hook("InsertChar", str, context());
}
void prepare(InsertMode mode)
{
Editor& editor = m_edition.editor();
Buffer& buffer = editor.buffer();
SelectionList& selections = context().selections();
Buffer& buffer = context().buffer();
for (auto& sel : editor.m_selections)
for (auto& sel : selections)
{
BufferCoord first, last;
switch (mode)
@ -1087,7 +1087,7 @@ private:
insert('\n');
if (mode == InsertMode::OpenLineAbove)
{
for (auto& sel : editor.m_selections)
for (auto& sel : selections)
{
// special case, the --first line above did nothing, so we need to compensate now
if (sel.first() == buffer.char_next({0,0}))
@ -1095,24 +1095,25 @@ private:
}
}
}
editor.m_selections.sort_and_merge_overlapping();
editor.check_invariant();
selections.sort_and_merge_overlapping();
selections.check_invariant();
buffer.check_invariant();
}
void on_replaced() override
{
for (auto& sel : m_edition.editor().m_selections)
for (auto& sel : context().selections())
{
if (m_insert_mode == InsertMode::Append and sel.last().column > 0)
sel.last() = m_edition.editor().buffer().char_prev(sel.last());
avoid_eol(m_edition.editor().buffer(), sel);
sel.last() = context().buffer().char_prev(sel.last());
avoid_eol(context().buffer(), sel);
}
}
enum class Mode { Default, Complete, InsertReg };
Mode m_mode = Mode::Default;
InsertMode m_insert_mode;
scoped_edition m_edition;
ScopedEdition m_edition;
BufferCompleter m_completer;
Timer m_idle_timer;
};

View File

@ -20,6 +20,89 @@
namespace Kakoune
{
void erase(Buffer& buffer, SelectionList& selections)
{
for (auto& sel : selections)
{
erase(buffer, sel);
avoid_eol(buffer, sel);
}
selections.check_invariant();
buffer.check_invariant();
}
template<InsertMode mode>
BufferIterator prepare_insert(Buffer& buffer, const Selection& sel)
{
switch (mode)
{
case InsertMode::Insert:
return buffer.iterator_at(sel.min());
case InsertMode::Replace:
return Kakoune::erase(buffer, sel);
case InsertMode::Append:
{
// special case for end of lines, append to current line instead
auto pos = buffer.iterator_at(sel.max());
return *pos == '\n' ? pos : utf8::next(pos);
}
case InsertMode::InsertAtLineBegin:
return buffer.iterator_at(sel.min().line);
case InsertMode::AppendAtLineEnd:
return buffer.iterator_at({sel.max().line, buffer[sel.max().line].length() - 1});
case InsertMode::InsertAtNextLineBegin:
return buffer.iterator_at(sel.max().line+1);
case InsertMode::OpenLineBelow:
return buffer.insert(buffer.iterator_at(sel.max().line + 1), "\n");
case InsertMode::OpenLineAbove:
return buffer.insert(buffer.iterator_at(sel.min().line), "\n");
}
kak_assert(false);
return {};
}
template<InsertMode mode>
void insert(Buffer& buffer, SelectionList& selections, const String& str)
{
for (auto& sel : selections)
{
auto pos = prepare_insert<mode>(buffer, sel);
pos = buffer.insert(pos, str);
if (mode == InsertMode::Replace and pos != buffer.end())
{
sel.first() = pos.coord();
sel.last() = str.empty() ?
pos.coord() : (pos + str.byte_count_to(str.char_length() - 1)).coord();
}
avoid_eol(buffer, sel);
}
selections.check_invariant();
buffer.check_invariant();
}
template<InsertMode mode>
void insert(Buffer& buffer, SelectionList& selections, memoryview<String> strings)
{
if (strings.empty())
return;
for (size_t i = 0; i < selections.size(); ++i)
{
auto& sel = selections[i];
auto pos = prepare_insert<mode>(buffer, sel);
const String& str = strings[std::min(i, strings.size()-1)];
pos = buffer.insert(pos, str);
if (mode == InsertMode::Replace and pos != buffer.end())
{
sel.first() = pos.coord();
sel.last() = (str.empty() ?
pos : pos + str.byte_count_to(str.char_length() - 1)).coord();
}
avoid_eol(buffer, sel);
}
selections.check_invariant();
buffer.check_invariant();
}
using namespace std::placeholders;
enum class SelectMode
@ -98,12 +181,12 @@ void select_coord(BufferCoord coord, SelectionList& selections)
}
template<InsertMode mode>
void insert(Context& context, int)
void enter_insert_mode(Context& context, int)
{
context.input_handler().insert(mode);
}
void repeat_insert(Context& context, int)
void repeat_last_insert(Context& context, int)
{
context.input_handler().repeat_last_insert();
}
@ -289,11 +372,11 @@ void replace_with_char(Context& context, int)
on_next_key_with_autoinfo(context, [](Key key, Context& context) {
if (not isprint(key.key))
return;
Editor& editor = context.editor();
SelectionList sels = context.selections();
auto restore_sels = on_scope_end([&]{ context.selections() = std::move(sels); });
select_all_matches(context.buffer(), context.selections(), Regex{"."});
editor.insert(codepoint_to_str(key.key), InsertMode::Replace);
ScopedEdition edition(context);
Buffer& buffer = context.buffer();
SelectionList selections = context.selections();
select_all_matches(buffer, selections, Regex{"."});
insert<InsertMode::Replace>(buffer, selections, codepoint_to_str(key.key));
}, "replace with char", "enter char to replace with\n");
}
@ -309,6 +392,7 @@ Codepoint swap_case(Codepoint cp)
template<Codepoint (*func)(Codepoint)>
void for_each_char(Context& context, int)
{
ScopedEdition edition(context);
Editor& editor = context.editor();
std::vector<String> sels = editor.selections_content();
for (auto& sel : sels)
@ -316,7 +400,7 @@ void for_each_char(Context& context, int)
for (auto& c : sel)
c = func(c);
}
editor.insert(sels, InsertMode::Replace);
insert<InsertMode::Replace>(context.buffer(), context.selections(), sels);
}
void command(Context& context, int)
@ -350,11 +434,12 @@ void pipe(Context& context, int)
if (real_cmd.empty())
return;
Editor& editor = context.editor();
Buffer& buffer = context.buffer();
SelectionList& selections = context.selections();
std::vector<String> strings;
for (auto& sel : context.selections())
for (auto& sel : selections)
{
auto str = content(context.buffer(), sel);
auto str = content(buffer, sel);
bool insert_eol = str.back() != '\n';
if (insert_eol)
str += '\n';
@ -364,7 +449,8 @@ void pipe(Context& context, int)
str = str.substr(0, str.length()-1);
strings.push_back(str);
}
editor.insert(strings, InsertMode::Replace);
ScopedEdition edition(context);
insert<InsertMode::Replace>(buffer, selections, strings);
});
}
@ -505,43 +591,43 @@ void cat_yank(Context& context, int)
void erase_selections(Context& context, int)
{
RegisterManager::instance()['"'] = context.editor().selections_content();
context.editor().erase();
ScopedEdition edition(context);
erase(context.buffer(), context.selections());
}
void change(Context& context, int param)
{
RegisterManager::instance()['"'] = context.editor().selections_content();
insert<InsertMode::Replace>(context, param);
enter_insert_mode<InsertMode::Replace>(context, param);
}
static InsertMode adapt_for_linewise(InsertMode mode)
constexpr InsertMode adapt_for_linewise(InsertMode mode)
{
if (mode == InsertMode::Append)
return InsertMode::InsertAtNextLineBegin;
if (mode == InsertMode::Insert)
return InsertMode::InsertAtLineBegin;
if (mode == InsertMode::Replace)
return InsertMode::Replace;
kak_assert(false);
return InsertMode::Insert;
return ((mode == InsertMode::Append) ?
InsertMode::InsertAtNextLineBegin :
((mode == InsertMode::Insert) ?
InsertMode::InsertAtLineBegin :
((mode == InsertMode::Replace) ?
InsertMode::Replace : InsertMode::Insert)));
}
template<InsertMode insert_mode>
template<InsertMode mode>
void paste(Context& context, int)
{
Editor& editor = context.editor();
auto strings = RegisterManager::instance()['"'].values(context);
InsertMode mode = insert_mode;
bool linewise = false;
for (auto& str : strings)
{
if (not str.empty() and str.back() == '\n')
{
mode = adapt_for_linewise(mode);
linewise = true;
break;
}
}
editor.insert(strings, mode);
if (linewise)
insert<adapt_for_linewise(mode)>(context.buffer(), context.selections(), strings);
else
insert<mode>(context.buffer(), context.selections(), strings);
}
template<typename T>
@ -619,17 +705,17 @@ void join_select_spaces(Context& context, int)
{
select(select_whole_lines)(context, 0);
select<SelectMode::Extend>(select_to_eol)(context, 0);
auto& editor = context.editor();
auto& buffer = context.buffer();
auto& selections = context.selections();
select_all_matches(buffer, context.selections(), Regex{"(\n\\h*)+"});
select_all_matches(buffer, selections, Regex{"(\n\\h*)+"});
// remove last end of line if selected
kak_assert(std::is_sorted(selections.begin(), selections.end(),
[](const Selection& lhs, const Selection& rhs)
{ return lhs.min() < rhs.min(); }));
if (not selections.empty() and selections.back().max() == buffer.back_coord())
selections.pop_back();
editor.insert(" ", InsertMode::Replace);
ScopedEdition edition(context);
insert<InsertMode::Replace>(buffer, selections, " ");
}
void join(Context& context, int param)
@ -666,21 +752,18 @@ void indent(Context& context, int)
CharCount indent_width = context.options()["indentwidth"].get<int>();
String indent = indent_width == 0 ? "\t" : String{' ', indent_width};
auto& editor = context.editor();
auto& buffer = context.buffer();
DynamicSelectionList sels{context.buffer(), context.selections()};
auto restore_sels = on_scope_end([&]{ context.selections() = std::move(sels); });
SelectionList res;
SelectionList sels;
for (auto& sel : context.selections())
{
for (auto line = sel.min().line; line < sel.max().line+1; ++line)
{
if (indent_empty or buffer[line].length() > 1)
res.emplace_back(line, line);
sels.emplace_back(line, line);
}
}
context.selections() = std::move(res);
editor.insert(indent, InsertMode::Insert);
ScopedEdition edition(context);
insert<InsertMode::Insert>(buffer, sels, indent);
}
template<bool deindent_incomplete = true>
@ -691,12 +774,8 @@ void deindent(Context& context, int)
if (indent_width == 0)
indent_width = tabstop;
auto& editor = context.editor();
auto& buffer = context.buffer();
DynamicSelectionList sels{context.buffer(), context.selections()};
auto restore_sels = on_scope_end([&]{ context.selections() = std::move(sels); });
SelectionList res;
SelectionList sels;
for (auto& sel : context.selections())
{
for (auto line = sel.min().line; line < sel.max().line+1; ++line)
@ -713,19 +792,19 @@ void deindent(Context& context, int)
else
{
if (deindent_incomplete and width != 0)
res.emplace_back(line, BufferCoord{line, column-1});
sels.emplace_back(line, BufferCoord{line, column-1});
break;
}
if (width == indent_width)
{
res.emplace_back(line, BufferCoord{line, column});
sels.emplace_back(line, BufferCoord{line, column});
break;
}
}
}
}
context.selections() = std::move(res);
editor.erase();
ScopedEdition edition(context);
erase(context.buffer(), sels);
}
template<ObjectFlags flags, SelectMode mode = SelectMode::Replace>
@ -827,8 +906,9 @@ void rotate_selections_content(Context& context, int count)
auto strings = editor.selections_content();
count = count % strings.size();
std::rotate(strings.begin(), strings.end()-count, strings.end());
editor.insert(strings, InsertMode::Replace);
context.selections().rotate_main(count);
ScopedEdition edition(context);
insert<InsertMode::Replace>(context.buffer(), context.selections(), strings);
}
enum class SelectFlags
@ -885,7 +965,7 @@ void replay_macro(Context& context, int count)
auto stop = on_scope_end([&]{ running_macros.erase(key.key); });
auto keys = parse_keys(reg_val[0]);
scoped_edition edition(context.editor());
ScopedEdition edition(context);
do { exec_keys(keys, context); } while (--count > 0);
}
}
@ -983,7 +1063,6 @@ void align(Context& context, int)
void align_indent(Context& context, int selection)
{
auto& editor = context.editor();
auto& buffer = context.buffer();
auto& selections = context.selections();
std::vector<LineCount> lines;
@ -1003,7 +1082,7 @@ void align_indent(Context& context, int selection)
++it;
const String indent{line.begin(), it};
scoped_edition edition{editor};
ScopedEdition edition{context};
for (auto& l : lines)
{
auto& line = buffer[l];
@ -1023,7 +1102,7 @@ public:
void operator() (Context& context, int count)
{
scoped_edition edition(context.editor());
ScopedEdition edition(context);
do { m_func(context, 0); } while(--count > 0);
}
private:
@ -1076,12 +1155,12 @@ KeyMap keymap =
{ 'd', erase_selections },
{ 'c', change },
{ 'i', insert<InsertMode::Insert> },
{ 'I', insert<InsertMode::InsertAtLineBegin> },
{ 'a', insert<InsertMode::Append> },
{ 'A', insert<InsertMode::AppendAtLineEnd> },
{ 'o', insert<InsertMode::OpenLineBelow> },
{ 'O', insert<InsertMode::OpenLineAbove> },
{ 'i', enter_insert_mode<InsertMode::Insert> },
{ 'I', enter_insert_mode<InsertMode::InsertAtLineBegin> },
{ 'a', enter_insert_mode<InsertMode::Append> },
{ 'A', enter_insert_mode<InsertMode::AppendAtLineEnd> },
{ 'o', enter_insert_mode<InsertMode::OpenLineBelow> },
{ 'O', enter_insert_mode<InsertMode::OpenLineAbove> },
{ 'r', replace_with_char },
{ 'g', goto_commands<SelectMode::Replace> },
@ -1099,7 +1178,7 @@ KeyMap keymap =
{ 'S', split_regex },
{ alt('s'), split_lines },
{ '.', repeat_insert },
{ '.', repeat_last_insert },
{ '%', [](Context& context, int) { select_whole_buffer(context.buffer(), context.selections()); } },

View File

@ -68,30 +68,6 @@ void test_undo_group_optimizer()
kak_assert(lines[i] == buffer[LineCount((int)i)]);
}
void test_editor()
{
using namespace std::placeholders;
Buffer buffer("test", Buffer::Flags::None, { "test\n", "\n", "youpi\n" });
Editor editor(buffer);
{
scoped_edition edition{editor};
select_whole_buffer(buffer, editor.selections());
select_all_matches(buffer, editor.selections(), Regex{"\\n\\h*"});
for (auto& sel : editor.selections())
{
kak_assert(buffer.byte_at(sel.min()) == '\n');
erase(buffer, sel);
}
}
editor.undo();
Selection sel{ 2_line, buffer.back_coord() };
editor.selections() = SelectionList{sel};
editor.insert("",InsertMode::Replace);
kak_assert(not buffer.is_end(editor.selections().main().first()));
}
void test_utf8()
{
String str = "maïs mélange bientôt";
@ -146,5 +122,4 @@ void run_unit_tests()
test_keys();
test_buffer();
test_undo_group_optimizer();
test_editor();
}