diff --git a/src/client_manager.cc b/src/client_manager.cc index 946650f4..657e6d93 100644 --- a/src/client_manager.cc +++ b/src/client_manager.cc @@ -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() + '\''); diff --git a/src/commands.cc b/src/commands.cc index e0f9e535..7c692745 100644 --- a/src/commands.cc +++ b/src/commands.cc @@ -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); diff --git a/src/context.cc b/src/context.cc index 35e28731..6e94b174 100644 --- a/src/context.cc +++ b/src/context.cc @@ -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; +} + } diff --git a/src/context.hh b/src/context.hh index cda7a812..58e7380c 100644 --- a/src/context.hh +++ b/src/context.hh @@ -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 m_editor; safe_ptr m_input_handler; safe_ptr 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 diff --git a/src/editor.cc b/src/editor.cc index d7bf52e0..12c65096 100644 --- a/src/editor.cc +++ b/src/editor.cc @@ -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 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 Editor::selections_content() const { std::vector 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; -} - } diff --git a/src/editor.hh b/src/editor.hh index 5a468279..d98feecc 100644 --- a/src/editor.hh +++ b/src/editor.hh @@ -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 strings, - InsertMode mode = InsertMode::Insert); - const SelectionList& selections() const { return m_selections; } SelectionList& selections() { return m_selections; } std::vector 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 diff --git a/src/input_handler.cc b/src/input_handler.cc index 1b8e16d7..03c9f497 100644 --- a/src/input_handler.cc +++ b/src/input_handler.cc @@ -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 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; }; diff --git a/src/normal.cc b/src/normal.cc index aab5a029..1b922275 100644 --- a/src/normal.cc +++ b/src/normal.cc @@ -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 +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 +void insert(Buffer& buffer, SelectionList& selections, const String& str) +{ + for (auto& sel : selections) + { + auto pos = prepare_insert(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 +void insert(Buffer& buffer, SelectionList& selections, memoryview strings) +{ + if (strings.empty()) + return; + for (size_t i = 0; i < selections.size(); ++i) + { + auto& sel = selections[i]; + auto pos = prepare_insert(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 -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(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 void for_each_char(Context& context, int) { + ScopedEdition edition(context); Editor& editor = context.editor(); std::vector 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(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 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(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(context, param); + enter_insert_mode(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 +template 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(context.buffer(), context.selections(), strings); + else + insert(context.buffer(), context.selections(), strings); } template @@ -619,17 +705,17 @@ void join_select_spaces(Context& context, int) { select(select_whole_lines)(context, 0); select(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(buffer, selections, " "); } void join(Context& context, int param) @@ -666,21 +752,18 @@ void indent(Context& context, int) CharCount indent_width = context.options()["indentwidth"].get(); 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(buffer, sels, indent); } template @@ -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 @@ -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(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 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 }, - { 'I', insert }, - { 'a', insert }, - { 'A', insert }, - { 'o', insert }, - { 'O', insert }, + { 'i', enter_insert_mode }, + { 'I', enter_insert_mode }, + { 'a', enter_insert_mode }, + { 'A', enter_insert_mode }, + { 'o', enter_insert_mode }, + { 'O', enter_insert_mode }, { 'r', replace_with_char }, { 'g', goto_commands }, @@ -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()); } }, diff --git a/src/unit_tests.cc b/src/unit_tests.cc index d3cda91d..8bd0c5fc 100644 --- a/src/unit_tests.cc +++ b/src/unit_tests.cc @@ -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(); }