#include "editor.hh" #include "exception.hh" #include "register.hh" #include "register_manager.hh" #include "utf8_iterator.hh" #include "utils.hh" #include namespace Kakoune { Editor::Editor(Buffer& buffer) : m_buffer(&buffer), m_edition_level(0), m_selections(buffer) { m_selections.push_back(Selection({}, {})); m_main_sel = 0; } static void avoid_eol(const Buffer& buffer, BufferCoord& coord) { const auto column = coord.column; const auto& line = buffer[coord.line]; if (column != 0 and column == line.length() - 1) coord.column = line.byte_count_to(line.char_length() - 2); } static void avoid_eol(const Buffer& buffer, Range& sel) { avoid_eol(buffer, sel.first()); avoid_eol(buffer, sel.last()); } 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; for (auto& sel : m_selections) contents.push_back(m_buffer->string(sel.min(), m_buffer->char_next(sel.max()))); return contents; } static bool compare_selections(const Selection& lhs, const Selection& rhs) { return lhs.min() < rhs.min(); } template void merge_overlapping(SelectionList& selections, size_t& main_selection, OverlapsFunc overlaps) { kak_assert(std::is_sorted(selections.begin(), selections.end(), compare_selections)); for (size_t i = 0; i+1 < selections.size() and selections.size() > 1;) { if (overlaps(selections[i], selections[i+1])) { selections[i].merge_with(selections[i+1]); selections.erase(selections.begin() + i + 1); if (i + 1 <= main_selection) --main_selection; } else ++i; } } void sort_and_merge_overlapping(SelectionList& selections, size_t& main_selection) { if (selections.size() == 1) return; const auto& main = selections[main_selection]; const auto main_begin = main.min(); main_selection = std::count_if(selections.begin(), selections.end(), [&](const Selection& sel) { auto begin = sel.min(); if (begin == main_begin) return &sel < &main; else return begin < main_begin; }); std::stable_sort(selections.begin(), selections.end(), compare_selections); merge_overlapping(selections, main_selection, overlaps); } BufferCoord Editor::offset_coord(BufferCoord coord, CharCount offset) { auto& line = buffer()[coord.line]; auto character = std::max(0_char, std::min(line.char_count_to(coord.column) + offset, line.char_length() - 2)); return {coord.line, line.byte_count_to(character)}; } void Editor::move_selections(CharCount offset, SelectMode mode) { kak_assert(mode == SelectMode::Replace or mode == SelectMode::Extend); for (auto& sel : m_selections) { auto last = offset_coord(sel.last(), offset); sel.first() = mode == SelectMode::Extend ? sel.first() : last; sel.last() = last; avoid_eol(*m_buffer, sel); } sort_and_merge_overlapping(m_selections, m_main_sel); } BufferCoord Editor::offset_coord(BufferCoord coord, LineCount offset) { auto character = (*m_buffer)[coord.line].char_count_to(coord.column); auto line = clamp(coord.line + offset, 0_line, m_buffer->line_count()-1); auto& content = (*m_buffer)[line]; character = std::max(0_char, std::min(character, content.char_length() - 2)); return {line, content.byte_count_to(character)}; } void Editor::move_selections(LineCount offset, SelectMode mode) { kak_assert(mode == SelectMode::Replace or mode == SelectMode::Extend); for (auto& sel : m_selections) { auto pos = offset_coord(sel.last(), offset); sel.first() = mode == SelectMode::Extend ? sel.first() : pos; sel.last() = pos; avoid_eol(*m_buffer, sel); } sort_and_merge_overlapping(m_selections, m_main_sel); } void Editor::clear_selections() { auto& sel = m_selections[m_main_sel]; auto& pos = sel.last(); avoid_eol(*m_buffer, pos); sel.first() = pos; m_selections.erase(m_selections.begin(), m_selections.begin() + m_main_sel); m_selections.erase(m_selections.begin() + 1, m_selections.end()); m_main_sel = 0; check_invariant(); } void Editor::flip_selections() { for (auto& sel : m_selections) std::swap(sel.first(), sel.last()); check_invariant(); } void Editor::keep_selection(int index) { if (index < m_selections.size()) { size_t real_index = (index + m_main_sel + 1) % m_selections.size(); m_selections = SelectionList{ std::move(m_selections[real_index]) }; m_main_sel = 0; } check_invariant(); } void Editor::remove_selection(int index) { if (m_selections.size() > 1 and index < m_selections.size()) { size_t real_index = (index + m_main_sel + 1) % m_selections.size(); m_selections.erase(m_selections.begin() + real_index); if (real_index <= m_main_sel) --m_main_sel; } check_invariant(); } void Editor::select(const Selection& selection, SelectMode mode) { if (mode == SelectMode::Replace) { m_selections = SelectionList{ selection }; m_main_sel = 0; } else if (mode == SelectMode::Extend) { m_selections[m_main_sel].merge_with(selection); m_selections = SelectionList{ std::move(m_selections[m_main_sel]) }; m_main_sel = 0; } else if (mode == SelectMode::Append) { m_main_sel = m_selections.size(); m_selections.push_back(selection); sort_and_merge_overlapping(m_selections, m_main_sel); } else kak_assert(false); check_invariant(); } void Editor::select(SelectionList selections) { if (selections.empty()) throw runtime_error("no selections"); m_selections = std::move(selections); m_main_sel = m_selections.size() - 1; check_invariant(); } void Editor::select(const Selector& selector, SelectMode mode) { if (mode == SelectMode::Append) { auto& sel = m_selections[m_main_sel]; auto res = selector(*m_buffer, sel); if (res.captures().empty()) res.captures() = sel.captures(); m_main_sel = m_selections.size(); m_selections.push_back(res); } else if (mode == SelectMode::ReplaceMain) { auto& sel = m_selections[m_main_sel]; auto res = selector(*m_buffer, sel); sel.first() = res.first(); sel.last() = res.last(); if (not res.captures().empty()) sel.captures() = std::move(res.captures()); } else { for (auto& sel : m_selections) { auto res = selector(*m_buffer, sel); if (mode == SelectMode::Extend) sel.merge_with(res); else { sel.first() = res.first(); sel.last() = res.last(); } if (not res.captures().empty()) sel.captures() = std::move(res.captures()); } } sort_and_merge_overlapping(m_selections, m_main_sel); check_invariant(); } struct nothing_selected : public runtime_error { nothing_selected() : runtime_error("nothing was selected") {} }; void Editor::multi_select(const MultiSelector& selector) { SelectionList new_selections; for (auto& sel : m_selections) { SelectionList res = selector(*m_buffer, sel); new_selections.reserve(new_selections.size() + res.size()); for (auto& new_sel : res) { // preserve captures when selectors captures nothing. if (new_sel.captures().empty()) new_selections.emplace_back(new_sel.first(), new_sel.last(), sel.captures()); else new_selections.push_back(std::move(new_sel)); } } if (new_selections.empty()) throw nothing_selected(); m_main_sel = new_selections.size() - 1; sort_and_merge_overlapping(new_selections, m_main_sel); m_selections = std::move(new_selections); check_invariant(); } class ModifiedRangesListener : public BufferChangeListener_AutoRegister { public: ModifiedRangesListener(Buffer& buffer) : BufferChangeListener_AutoRegister(buffer) {} void on_insert(const Buffer& buffer, BufferCoord begin, BufferCoord end) { m_ranges.update_insert(buffer, begin, end); auto it = std::upper_bound(m_ranges.begin(), m_ranges.end(), begin, [](BufferCoord c, const Selection& sel) { return c < sel.min(); }); m_ranges.emplace(it, begin, buffer.char_prev(end)); } void on_erase(const Buffer& buffer, BufferCoord begin, BufferCoord end) { m_ranges.update_erase(buffer, begin, end); auto pos = std::min(begin, buffer.back_coord()); auto it = std::upper_bound(m_ranges.begin(), m_ranges.end(), pos, [](BufferCoord c, const Selection& sel) { return c < sel.min(); }); m_ranges.emplace(it, pos, pos); } SelectionList& ranges() { return m_ranges; } private: SelectionList m_ranges; }; inline bool touches(const Buffer& buffer, const Range& lhs, const Range& rhs) { return lhs.min() <= rhs.min() ? buffer.char_next(lhs.max()) >= rhs.min() : lhs.min() <= buffer.char_next(rhs.max()); } bool Editor::undo() { using namespace std::placeholders; ModifiedRangesListener listener(buffer()); bool res = m_buffer->undo(); if (res and not listener.ranges().empty()) { m_selections = std::move(listener.ranges()); m_main_sel = m_selections.size() - 1; merge_overlapping(m_selections, m_main_sel, std::bind(touches, std::ref(buffer()), _1, _2)); } check_invariant(); return res; } bool Editor::redo() { using namespace std::placeholders; ModifiedRangesListener listener(buffer()); bool res = m_buffer->redo(); if (res and not listener.ranges().empty()) { m_selections = std::move(listener.ranges()); m_main_sel = m_selections.size() - 1; merge_overlapping(m_selections, m_main_sel, std::bind(touches, std::ref(buffer()), _1, _2)); } check_invariant(); return res; } void Editor::check_invariant() const { #ifdef KAK_DEBUG kak_assert(not m_selections.empty()); kak_assert(m_main_sel < m_selections.size()); m_selections.check_invariant(); buffer().check_invariant(); kak_assert(std::is_sorted(m_selections.begin(), m_selections.end(), compare_selections)); #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; } IncrementalInserter::IncrementalInserter(Editor& editor, InsertMode mode) : m_editor(editor), m_edition(editor), m_mode(mode) { Buffer& buffer = *editor.m_buffer; for (auto& sel : m_editor.m_selections) { BufferCoord first, last; switch (mode) { case InsertMode::Insert: first = sel.max(); last = sel.min(); break; case InsertMode::Replace: first = last = Kakoune::erase(buffer, sel).coord(); break; case InsertMode::Append: first = sel.min(); last = sel.max(); // special case for end of lines, append to current line instead if (last.column != buffer[last.line].length() - 1) last = buffer.char_next(last); break; case InsertMode::OpenLineBelow: case InsertMode::AppendAtLineEnd: first = last = BufferCoord{sel.max().line, buffer[sel.max().line].length() - 1}; break; case InsertMode::OpenLineAbove: case InsertMode::InsertAtLineBegin: first = sel.min().line; if (mode == InsertMode::OpenLineAbove) first = buffer.char_prev(first); else { auto first_non_blank = buffer.iterator_at(first); while (*first_non_blank == ' ' or *first_non_blank == '\t') ++first_non_blank; if (*first_non_blank != '\n') first = first_non_blank.coord(); } last = first; break; case InsertMode::InsertAtNextLineBegin: kak_assert(false); // not implemented break; } if (buffer.is_end(first)) first = buffer.char_prev(first); if (buffer.is_end(last)) last = buffer.char_prev(last); sel.first() = first; sel.last() = last; } if (mode == InsertMode::OpenLineBelow or mode == InsertMode::OpenLineAbove) { insert("\n"); if (mode == InsertMode::OpenLineAbove) { for (auto& sel : m_editor.m_selections) { // special case, the --first line above did nothing, so we need to compensate now if (sel.first() == buffer.char_next({0,0})) sel.first() = sel.last() = BufferCoord{0,0}; } } } sort_and_merge_overlapping(editor.m_selections, editor.m_main_sel); editor.check_invariant(); } IncrementalInserter::~IncrementalInserter() { for (auto& sel : m_editor.m_selections) { if (m_mode == InsertMode::Append and sel.last().column > 0) sel.last() = m_editor.buffer().char_prev(sel.last()); avoid_eol(m_editor.buffer(), sel); } } void IncrementalInserter::insert(String content) { auto& buffer = m_editor.buffer(); for (auto& sel : m_editor.m_selections) { m_editor.filters()(buffer, sel, content); buffer.insert(buffer.iterator_at(sel.last()), content); } } void IncrementalInserter::insert(memoryview strings) { auto& buffer = m_editor.buffer(); for (size_t i = 0; i < m_editor.m_selections.size(); ++i) { size_t index = std::min(i, strings.size()-1); buffer.insert(buffer.iterator_at(m_editor.m_selections[i].last()), strings[index]); } } void IncrementalInserter::erase() { auto& buffer = m_editor.buffer(); for (auto& sel : m_editor.m_selections) { auto pos = buffer.iterator_at(sel.last()); buffer.erase(pos-1, pos); } } void IncrementalInserter::move_cursors(CharCount move) { m_editor.move_selections(move, SelectMode::Replace); } void IncrementalInserter::move_cursors(LineCount move) { m_editor.move_selections(move, SelectMode::Replace); } }