<commit_before>#include "editor.hh"

#include "exception.hh"
#include "utils.hh"
#include "register.hh"
#include "register_manager.hh"

#include "utf8_iterator.hh"

#include <array>

namespace Kakoune
{

Editor::Editor(Buffer& buffer)
    : m_buffer(&buffer),
      m_edition_level(0),
      m_selections(buffer)
{
    m_selections.push_back(Selection(buffer.begin(), buffer.begin()));
}

void Editor::erase()
{
    scoped_edition edition(*this);
    for (auto& sel : m_selections)
    {
        m_buffer->erase(sel.begin(), sel.end());
        sel.avoid_eol();
    }
}

static BufferIterator prepare_insert(Buffer& buffer, const Selection& sel,
                                     InsertMode mode)
{
    switch (mode)
    {
    case InsertMode::Insert:
    case InsertMode::Replace:
        return sel.begin();
    case InsertMode::Append:
    {
        // special case for end of lines, append to current line instead
        auto pos = std::max(sel.first(), sel.last());
        if (pos.column() == buffer.line_length(pos.line()) - 1)
            return pos;
        else
            return pos+1;
    }
    case InsertMode::InsertAtLineBegin:
        return buffer.iterator_at_line_begin(sel.begin());
    case InsertMode::AppendAtLineEnd:
        return buffer.iterator_at_line_end(sel.end()-1)-1;
    case InsertMode::InsertAtNextLineBegin:
        return buffer.iterator_at_line_end(sel.end()-1);
    case InsertMode::OpenLineBelow:
    {
        LineCount line = (sel.end() - 1).line();
        buffer.insert(buffer.iterator_at_line_end(line), "\n");
        return buffer.iterator_at_line_begin(line + 1);
    }
    case InsertMode::OpenLineAbove:
    {
        auto pos = buffer.iterator_at_line_begin(sel.begin());
        buffer.insert(pos, "\n");
        return pos;
    }
    }
    assert(false);
    return BufferIterator{};
}

void Editor::insert(const String& string, InsertMode mode)
{
    scoped_edition edition(*this);

    if (mode == InsertMode::Replace)
    {
        // do not call Editor::erase as we do not want to avoid end of lines
        for (auto& sel : m_selections)
            m_buffer->erase(sel.begin(), sel.end());
    }

    for (auto& sel : m_selections)
    {
        BufferIterator pos = prepare_insert(*m_buffer, sel, mode);
        m_buffer->insert(pos, string);
        if (mode == InsertMode::Replace)
        {
            sel.first() = pos;
            sel.last()  = pos + (string.empty() ? 0 : string.length() - 1 );
        }
        sel.avoid_eol();
    }
}

void Editor::insert(const memoryview<String>& strings, InsertMode mode)
{
    scoped_edition edition(*this);
    if (mode == InsertMode::Replace)
    {
        // do not call Editor::erase as we do not want to avoid end of lines
        for (auto& sel : m_selections)
            m_buffer->erase(sel.begin(), sel.end());
    }
    if (strings.empty())
        return;

    for (size_t i = 0; i < selections().size(); ++i)
    {
        auto& sel = m_selections[i];
        BufferIterator pos = prepare_insert(*m_buffer, sel, mode);
        const String& str = strings[std::min(i, strings.size()-1)];
        m_buffer->insert(pos, str);
        if (mode == InsertMode::Replace)
        {
            sel.first() = pos;
            sel.last()  = pos + (str.empty() ? 0 : str.length() - 1);
        }
        sel.avoid_eol();
    }
}

std::vector<String> Editor::selections_content() const
{
    std::vector<String> contents;
    for (auto& sel : m_selections)
        contents.push_back(m_buffer->string(sel.begin(),
                                           sel.end()));
    return contents;
}

static void sort_and_merge_overlapping(SelectionList& selections)
{
    std::sort(selections.begin(), selections.end(),
              [](const Selection& lhs, const Selection& rhs)
              { return lhs.begin() < rhs.begin(); });
    for (size_t i = 0; i < selections.size()-1;)
    {
        if (overlaps(selections[i], selections[i+1]))
        {
            selections[i].merge_with(selections[i+1]);
            selections.erase(selections.begin() + i + 1);
        }
        else
           ++i;
    }
}

void Editor::move_selections(CharCount offset, SelectMode mode)
{
    assert(mode == SelectMode::Replace or mode == SelectMode::Extend);
    for (auto& sel : m_selections)
    {
        auto last = sel.last();
        auto limit = offset < 0 ? buffer().iterator_at_line_begin(last)
                                : utf8::previous(buffer().iterator_at_line_end(last));
        last = utf8::advance(last, limit, offset);
        sel = Selection(mode == SelectMode::Extend ? sel.first() : last, last);
        sel.avoid_eol();
    }
    sort_and_merge_overlapping(m_selections);
}

void Editor::move_selections(LineCount offset, SelectMode mode)
{
    assert(mode == SelectMode::Replace or mode == SelectMode::Extend);
    for (auto& sel : m_selections)
    {
        BufferCoord pos = sel.last().coord();
        pos.line += offset;
        BufferIterator last = utf8::finish(m_buffer->iterator_at(pos, true));
        sel = Selection(mode == SelectMode::Extend ? sel.first() : last, last);
        sel.avoid_eol();
    }
    sort_and_merge_overlapping(m_selections);
}

void Editor::clear_selections()
{
    BufferIterator pos = m_selections.back().last();

    if (*pos == '\n' and not pos.is_begin() and *utf8::previous(pos) != '\n')
        pos = utf8::previous(pos);
<commit_msg>Editor: preserve captures accross movements<commit_after>#include "editor.hh"

#include "exception.hh"
#include "utils.hh"
#include "register.hh"
#include "register_manager.hh"

#include "utf8_iterator.hh"

#include <array>

namespace Kakoune
{

Editor::Editor(Buffer& buffer)
    : m_buffer(&buffer),
      m_edition_level(0),
      m_selections(buffer)
{
    m_selections.push_back(Selection(buffer.begin(), buffer.begin()));
}

void Editor::erase()
{
    scoped_edition edition(*this);
    for (auto& sel : m_selections)
    {
        m_buffer->erase(sel.begin(), sel.end());
        sel.avoid_eol();
    }
}

static BufferIterator prepare_insert(Buffer& buffer, const Selection& sel,
                                     InsertMode mode)
{
    switch (mode)
    {
    case InsertMode::Insert:
    case InsertMode::Replace:
        return sel.begin();
    case InsertMode::Append:
    {
        // special case for end of lines, append to current line instead
        auto pos = std::max(sel.first(), sel.last());
        if (pos.column() == buffer.line_length(pos.line()) - 1)
            return pos;
        else
            return pos+1;
    }
    case InsertMode::InsertAtLineBegin:
        return buffer.iterator_at_line_begin(sel.begin());
    case InsertMode::AppendAtLineEnd:
        return buffer.iterator_at_line_end(sel.end()-1)-1;
    case InsertMode::InsertAtNextLineBegin:
        return buffer.iterator_at_line_end(sel.end()-1);
    case InsertMode::OpenLineBelow:
    {
        LineCount line = (sel.end() - 1).line();
        buffer.insert(buffer.iterator_at_line_end(line), "\n");
        return buffer.iterator_at_line_begin(line + 1);
    }
    case InsertMode::OpenLineAbove:
    {
        auto pos = buffer.iterator_at_line_begin(sel.begin());
        buffer.insert(pos, "\n");
        return pos;
    }
    }
    assert(false);
    return BufferIterator{};
}

void Editor::insert(const String& string, InsertMode mode)
{
    scoped_edition edition(*this);

    if (mode == InsertMode::Replace)
    {
        // do not call Editor::erase as we do not want to avoid end of lines
        for (auto& sel : m_selections)
            m_buffer->erase(sel.begin(), sel.end());
    }

    for (auto& sel : m_selections)
    {
        BufferIterator pos = prepare_insert(*m_buffer, sel, mode);
        m_buffer->insert(pos, string);
        if (mode == InsertMode::Replace)
        {
            sel.first() = pos;
            sel.last()  = pos + (string.empty() ? 0 : string.length() - 1 );
        }
        sel.avoid_eol();
    }
}

void Editor::insert(const memoryview<String>& strings, InsertMode mode)
{
    scoped_edition edition(*this);
    if (mode == InsertMode::Replace)
    {
        // do not call Editor::erase as we do not want to avoid end of lines
        for (auto& sel : m_selections)
            m_buffer->erase(sel.begin(), sel.end());
    }
    if (strings.empty())
        return;

    for (size_t i = 0; i < selections().size(); ++i)
    {
        auto& sel = m_selections[i];
        BufferIterator pos = prepare_insert(*m_buffer, sel, mode);
        const String& str = strings[std::min(i, strings.size()-1)];
        m_buffer->insert(pos, str);
        if (mode == InsertMode::Replace)
        {
            sel.first() = pos;
            sel.last()  = pos + (str.empty() ? 0 : str.length() - 1);
        }
        sel.avoid_eol();
    }
}

std::vector<String> Editor::selections_content() const
{
    std::vector<String> contents;
    for (auto& sel : m_selections)
        contents.push_back(m_buffer->string(sel.begin(),
                                           sel.end()));
    return contents;
}

static void sort_and_merge_overlapping(SelectionList& selections)
{
    std::sort(selections.begin(), selections.end(),
              [](const Selection& lhs, const Selection& rhs)
              { return lhs.begin() < rhs.begin(); });
    for (size_t i = 0; i < selections.size()-1;)
    {
        if (overlaps(selections[i], selections[i+1]))
        {
            selections[i].merge_with(selections[i+1]);
            selections.erase(selections.begin() + i + 1);
        }
        else
           ++i;
    }
}

void Editor::move_selections(CharCount offset, SelectMode mode)
{
    assert(mode == SelectMode::Replace or mode == SelectMode::Extend);
    for (auto& sel : m_selections)
    {
        auto last = sel.last();
        auto limit = offset < 0 ? buffer().iterator_at_line_begin(last)
                                : utf8::previous(buffer().iterator_at_line_end(last));
        last = utf8::advance(last, limit, offset);
        sel.first() = mode == SelectMode::Extend ? sel.first() : last;
        sel.last()  = last;
        sel.avoid_eol();
    }
    sort_and_merge_overlapping(m_selections);
}

void Editor::move_selections(LineCount offset, SelectMode mode)
{
    assert(mode == SelectMode::Replace or mode == SelectMode::Extend);
    for (auto& sel : m_selections)
    {
        BufferCoord pos = sel.last().coord();
        pos.line += offset;
        BufferIterator last = utf8::finish(m_buffer->iterator_at(pos, true));
        sel.first() = mode == SelectMode::Extend ? sel.first() : last;
        sel.last()  = last;
        sel.avoid_eol();
    }
    sort_and_merge_overlapping(m_selections);
}

void Editor::clear_selections()
{
    BufferIterator pos = m_selections.back().last();

    if (*pos == '\n' and not pos.is_begin() and *utf8::previous(pos) != '\n')
        pos = utf8::previous(pos);

{'fraction_non_alphanumeric': 0.14226595549016002, 'fraction_numerical': 0.004782048924038992, 'mean_word_length': 2.522837706511176, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 25, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 8, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6459161', 'n_tokens_mistral': 3446, 'n_tokens_neox': 3396, 'n_words': 724}