MultiSelectors now transforms the whole selection
This commit is contained in:
parent
894ee0297e
commit
db048a0792
|
@ -282,26 +282,7 @@ struct nothing_selected : public runtime_error
|
||||||
|
|
||||||
void Editor::multi_select(const MultiSelector& selector)
|
void Editor::multi_select(const MultiSelector& selector)
|
||||||
{
|
{
|
||||||
SelectionList new_selections;
|
m_selections = selector(*m_buffer, std::move(m_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();
|
|
||||||
new_selections.set_main_index(new_selections.size() - 1);
|
|
||||||
new_selections.sort_and_merge_overlapping();
|
|
||||||
m_selections = std::move(new_selections);
|
|
||||||
check_invariant();
|
check_invariant();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@ class Editor : public SafeCountable
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
typedef std::function<Selection (const Buffer&, const Selection&)> Selector;
|
typedef std::function<Selection (const Buffer&, const Selection&)> Selector;
|
||||||
typedef std::function<SelectionList (const Buffer&, const Selection&)> MultiSelector;
|
typedef std::function<SelectionList (const Buffer&, SelectionList)> MultiSelector;
|
||||||
|
|
||||||
Editor(Buffer& buffer);
|
Editor(Buffer& buffer);
|
||||||
virtual ~Editor() {}
|
virtual ~Editor() {}
|
||||||
|
|
|
@ -321,7 +321,7 @@ void search(Context& context, int)
|
||||||
else if (str.empty() or not context.options()["incsearch"].get<bool>())
|
else if (str.empty() or not context.options()["incsearch"].get<bool>())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
context.editor().select(std::bind(select_next_match<direction>, _1, _2, ex), mode);
|
context.editor().multi_select(std::bind(select_next_match<direction, mode>, _1, _2, ex));
|
||||||
}
|
}
|
||||||
catch (boost::regex_error& err)
|
catch (boost::regex_error& err)
|
||||||
{
|
{
|
||||||
|
@ -351,7 +351,7 @@ void search_next(Context& context, int param)
|
||||||
{
|
{
|
||||||
Regex ex{str};
|
Regex ex{str};
|
||||||
do {
|
do {
|
||||||
context.editor().select(std::bind(select_next_match<direction>, _1, _2, ex), mode);
|
context.editor().multi_select(std::bind(select_next_match<direction, mode>, _1, _2, ex));
|
||||||
} while (--param > 0);
|
} while (--param > 0);
|
||||||
}
|
}
|
||||||
catch (boost::regex_error& err)
|
catch (boost::regex_error& err)
|
||||||
|
@ -496,16 +496,20 @@ void split_regex(Context& context, int)
|
||||||
|
|
||||||
void split_lines(Context& context, int)
|
void split_lines(Context& context, int)
|
||||||
{
|
{
|
||||||
context.editor().multi_select([](const Buffer& buffer, const Selection& sel) {
|
context.editor().multi_select([](const Buffer& buffer,
|
||||||
if (sel.first().line == sel.last().line)
|
SelectionList selections) {
|
||||||
return SelectionList{ sel };
|
|
||||||
SelectionList res;
|
SelectionList res;
|
||||||
auto min = sel.min();
|
for (auto& sel : selections)
|
||||||
auto max = sel.max();
|
{
|
||||||
res.push_back({min, {min.line, buffer[min.line].length()-1}});
|
if (sel.first().line == sel.last().line)
|
||||||
for (auto line = min.line+1; line < max.line; ++line)
|
return SelectionList{ sel };
|
||||||
res.push_back({line, {line, buffer[line].length()-1}});
|
auto min = sel.min();
|
||||||
res.push_back({max.line, max});
|
auto max = sel.max();
|
||||||
|
res.push_back({min, {min.line, buffer[min.line].length()-1}});
|
||||||
|
for (auto line = min.line+1; line < max.line; ++line)
|
||||||
|
res.push_back({line, {line, buffer[line].length()-1}});
|
||||||
|
res.push_back({max.line, max});
|
||||||
|
}
|
||||||
return res;
|
return res;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -515,9 +519,10 @@ void join_select_spaces(Context& context, int)
|
||||||
Editor& editor = context.editor();
|
Editor& editor = context.editor();
|
||||||
editor.select(select_whole_lines);
|
editor.select(select_whole_lines);
|
||||||
editor.select(select_to_eol, SelectMode::Extend);
|
editor.select(select_to_eol, SelectMode::Extend);
|
||||||
editor.multi_select([](const Buffer& buffer, const Selection& sel)
|
editor.multi_select([](const Buffer& buffer, SelectionList sel)
|
||||||
{
|
{
|
||||||
SelectionList res = select_all_matches(buffer, sel, Regex{"(\n\\h*)+"});
|
SelectionList res = select_all_matches(buffer, std::move(sel),
|
||||||
|
Regex{"(\n\\h*)+"});
|
||||||
// remove last end of line if selected
|
// remove last end of line if selected
|
||||||
kak_assert(std::is_sorted(res.begin(), res.end(),
|
kak_assert(std::is_sorted(res.begin(), res.end(),
|
||||||
[](const Selection& lhs, const Selection& rhs)
|
[](const Selection& lhs, const Selection& rhs)
|
||||||
|
@ -568,12 +573,15 @@ void indent(Context& context, int)
|
||||||
Editor& editor = context.editor();
|
Editor& editor = context.editor();
|
||||||
DynamicSelectionList sels{editor.buffer(), editor.selections()};
|
DynamicSelectionList sels{editor.buffer(), editor.selections()};
|
||||||
auto restore_sels = on_scope_end([&]{ editor.select((SelectionList)std::move(sels)); });
|
auto restore_sels = on_scope_end([&]{ editor.select((SelectionList)std::move(sels)); });
|
||||||
editor.multi_select([&indent](const Buffer& buf, const Selection& sel) {
|
editor.multi_select([&indent](const Buffer& buf, SelectionList selections) {
|
||||||
SelectionList res;
|
SelectionList res;
|
||||||
for (auto line = sel.min().line; line < sel.max().line+1; ++line)
|
for (auto& sel : selections)
|
||||||
{
|
{
|
||||||
if (indent_empty or buf[line].length() > 1)
|
for (auto line = sel.min().line; line < sel.max().line+1; ++line)
|
||||||
res.emplace_back(line, line);
|
{
|
||||||
|
if (indent_empty or buf[line].length() > 1)
|
||||||
|
res.emplace_back(line, line);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
});
|
});
|
||||||
|
@ -592,29 +600,32 @@ void deindent(Context& context, int)
|
||||||
DynamicSelectionList sels{editor.buffer(), editor.selections()};
|
DynamicSelectionList sels{editor.buffer(), editor.selections()};
|
||||||
auto restore_sels = on_scope_end([&]{ editor.select((SelectionList)std::move(sels)); });
|
auto restore_sels = on_scope_end([&]{ editor.select((SelectionList)std::move(sels)); });
|
||||||
|
|
||||||
editor.multi_select([indent_width,tabstop](const Buffer& buf, const Selection& sel) {
|
editor.multi_select([indent_width,tabstop](const Buffer& buf, SelectionList selections) {
|
||||||
SelectionList res;
|
SelectionList res;
|
||||||
for (auto line = sel.min().line; line < sel.max().line+1; ++line)
|
for (auto& sel : selections)
|
||||||
{
|
{
|
||||||
CharCount width = 0;
|
for (auto line = sel.min().line; line < sel.max().line+1; ++line)
|
||||||
auto& content = buf[line];
|
|
||||||
for (auto column = 0_byte; column < content.length(); ++column)
|
|
||||||
{
|
{
|
||||||
const char c = content[column];
|
CharCount width = 0;
|
||||||
if (c == '\t')
|
auto& content = buf[line];
|
||||||
width = (width / tabstop + 1) * tabstop;
|
for (auto column = 0_byte; column < content.length(); ++column)
|
||||||
else if (c == ' ')
|
|
||||||
++width;
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
if (deindent_incomplete and width != 0)
|
const char c = content[column];
|
||||||
res.emplace_back(line, BufferCoord{line, column-1});
|
if (c == '\t')
|
||||||
break;
|
width = (width / tabstop + 1) * tabstop;
|
||||||
}
|
else if (c == ' ')
|
||||||
if (width == indent_width)
|
++width;
|
||||||
{
|
else
|
||||||
res.emplace_back(line, BufferCoord{line, column});
|
{
|
||||||
break;
|
if (deindent_incomplete and width != 0)
|
||||||
|
res.emplace_back(line, BufferCoord{line, column-1});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (width == indent_width)
|
||||||
|
{
|
||||||
|
res.emplace_back(line, BufferCoord{line, column});
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
120
src/selectors.cc
120
src/selectors.cc
|
@ -597,107 +597,57 @@ Selection select_whole_buffer(const Buffer& buffer, const Selection&)
|
||||||
return Selection({0,0}, buffer.back_coord());
|
return Selection({0,0}, buffer.back_coord());
|
||||||
}
|
}
|
||||||
|
|
||||||
using MatchResults = boost::match_results<BufferIterator>;
|
SelectionList select_all_matches(const Buffer& buffer, SelectionList selections,
|
||||||
|
const Regex& regex)
|
||||||
static bool find_last_match(BufferIterator begin, const BufferIterator& end,
|
|
||||||
MatchResults& res, const Regex& regex)
|
|
||||||
{
|
{
|
||||||
MatchResults matches;
|
|
||||||
while (boost::regex_search(begin, end, matches, regex))
|
|
||||||
{
|
|
||||||
if (begin == matches[0].second)
|
|
||||||
break;
|
|
||||||
begin = matches[0].second;
|
|
||||||
res.swap(matches);
|
|
||||||
}
|
|
||||||
return not res.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
template<Direction direction>
|
|
||||||
bool find_match_in_buffer(const Buffer& buffer, const BufferIterator pos,
|
|
||||||
MatchResults& matches, const Regex& ex)
|
|
||||||
{
|
|
||||||
if (direction == Forward)
|
|
||||||
return (boost::regex_search(pos, buffer.end(), matches, ex) or
|
|
||||||
boost::regex_search(buffer.begin(), pos, matches, ex));
|
|
||||||
else
|
|
||||||
return (find_last_match(buffer.begin(), pos, matches, ex) or
|
|
||||||
find_last_match(pos, buffer.end(), matches, ex));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
template<Direction direction>
|
|
||||||
Selection select_next_match(const Buffer& buffer, const Selection& selection, const Regex& regex)
|
|
||||||
{
|
|
||||||
// regex matching do not use Utf8Iterator as boost::regex handle utf8
|
|
||||||
// decoding itself
|
|
||||||
BufferIterator begin = buffer.iterator_at(selection.last());
|
|
||||||
BufferIterator end = begin;
|
|
||||||
CaptureList captures;
|
|
||||||
|
|
||||||
MatchResults matches;
|
|
||||||
|
|
||||||
bool found = false;
|
|
||||||
if ((found = find_match_in_buffer<direction>(buffer, utf8::next(begin), matches, regex)))
|
|
||||||
{
|
|
||||||
begin = matches[0].first;
|
|
||||||
end = matches[0].second;
|
|
||||||
for (auto& match : matches)
|
|
||||||
captures.push_back(String(match.first, match.second));
|
|
||||||
}
|
|
||||||
if (not found or begin == buffer.end())
|
|
||||||
throw runtime_error("'" + regex.str() + "': no matches found");
|
|
||||||
|
|
||||||
end = (begin == end) ? end : utf8::previous(end);
|
|
||||||
if (direction == Backward)
|
|
||||||
std::swap(begin, end);
|
|
||||||
return Selection{begin.coord(), end.coord(), std::move(captures)};
|
|
||||||
}
|
|
||||||
template Selection select_next_match<Forward>(const Buffer&, const Selection&, const Regex&);
|
|
||||||
template Selection select_next_match<Backward>(const Buffer&, const Selection&, const Regex&);
|
|
||||||
|
|
||||||
SelectionList select_all_matches(const Buffer& buffer, const Selection& selection, const Regex& regex)
|
|
||||||
{
|
|
||||||
auto sel_end = utf8::next(buffer.iterator_at(selection.max()));
|
|
||||||
RegexIterator re_it(buffer.iterator_at(selection.min()), sel_end, regex);
|
|
||||||
RegexIterator re_end;
|
|
||||||
|
|
||||||
SelectionList result;
|
SelectionList result;
|
||||||
for (; re_it != re_end; ++re_it)
|
for (auto& sel : selections)
|
||||||
{
|
{
|
||||||
BufferIterator begin = (*re_it)[0].first;
|
auto sel_end = utf8::next(buffer.iterator_at(sel.max()));
|
||||||
BufferIterator end = (*re_it)[0].second;
|
RegexIterator re_it(buffer.iterator_at(sel.min()), sel_end, regex);
|
||||||
|
RegexIterator re_end;
|
||||||
|
|
||||||
if (begin == sel_end)
|
for (; re_it != re_end; ++re_it)
|
||||||
continue;
|
{
|
||||||
|
auto& begin = (*re_it)[0].first;
|
||||||
|
auto& end = (*re_it)[0].second;
|
||||||
|
|
||||||
CaptureList captures;
|
if (begin == sel_end)
|
||||||
for (auto& match : *re_it)
|
continue;
|
||||||
captures.push_back(String(match.first, match.second));
|
|
||||||
|
|
||||||
result.push_back(Selection(begin.coord(), (begin == end ? end : utf8::previous(end)).coord(),
|
CaptureList captures;
|
||||||
std::move(captures)));
|
for (auto& match : *re_it)
|
||||||
|
captures.emplace_back(match.first, match.second);
|
||||||
|
|
||||||
|
result.emplace_back(begin.coord(),
|
||||||
|
(begin == end ? end : utf8::previous(end)).coord(),
|
||||||
|
std::move(captures));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
SelectionList split_selection(const Buffer& buffer, const Selection& selection,
|
SelectionList split_selection(const Buffer& buffer, SelectionList selections,
|
||||||
const Regex& regex)
|
const Regex& regex)
|
||||||
{
|
{
|
||||||
auto begin = buffer.iterator_at(selection.min());
|
|
||||||
auto sel_end = utf8::next(buffer.iterator_at(selection.max()));
|
|
||||||
RegexIterator re_it(begin, sel_end, regex, boost::regex_constants::match_nosubs);
|
|
||||||
RegexIterator re_end;
|
|
||||||
|
|
||||||
SelectionList result;
|
SelectionList result;
|
||||||
for (; re_it != re_end; ++re_it)
|
for (auto& sel : selections)
|
||||||
{
|
{
|
||||||
BufferIterator end = (*re_it)[0].first;
|
auto begin = buffer.iterator_at(sel.min());
|
||||||
|
auto sel_end = utf8::next(buffer.iterator_at(sel.max()));
|
||||||
|
RegexIterator re_it(begin, sel_end, regex,
|
||||||
|
boost::regex_constants::match_nosubs);
|
||||||
|
RegexIterator re_end;
|
||||||
|
|
||||||
result.push_back(Selection(begin.coord(), (begin == end) ? end.coord() : utf8::previous(end).coord()));
|
for (; re_it != re_end; ++re_it)
|
||||||
begin = (*re_it)[0].second;
|
{
|
||||||
|
BufferIterator end = (*re_it)[0].first;
|
||||||
|
|
||||||
|
result.emplace_back(begin.coord(), (begin == end) ? end.coord() : utf8::previous(end).coord());
|
||||||
|
begin = (*re_it)[0].second;
|
||||||
|
}
|
||||||
|
result.emplace_back(begin.coord(), sel.max());
|
||||||
}
|
}
|
||||||
result.push_back(Selection(begin.coord(), selection.max()));
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
#include "selection.hh"
|
#include "selection.hh"
|
||||||
#include "unicode.hh"
|
#include "unicode.hh"
|
||||||
|
#include "editor.hh"
|
||||||
|
|
||||||
namespace Kakoune
|
namespace Kakoune
|
||||||
{
|
{
|
||||||
|
@ -58,14 +59,78 @@ Selection trim_partial_lines(const Buffer& buffer, const Selection& selection);
|
||||||
|
|
||||||
enum Direction { Forward, Backward };
|
enum Direction { Forward, Backward };
|
||||||
|
|
||||||
template<Direction direction>
|
using MatchResults = boost::match_results<BufferIterator>;
|
||||||
Selection select_next_match(const Buffer& buffer, const Selection& selection,
|
|
||||||
const Regex& regex);
|
|
||||||
|
|
||||||
SelectionList select_all_matches(const Buffer& buffer, const Selection& selection,
|
static bool find_last_match(BufferIterator begin, const BufferIterator& end,
|
||||||
|
MatchResults& res, const Regex& regex)
|
||||||
|
{
|
||||||
|
MatchResults matches;
|
||||||
|
while (boost::regex_search(begin, end, matches, regex))
|
||||||
|
{
|
||||||
|
if (begin == matches[0].second)
|
||||||
|
break;
|
||||||
|
begin = matches[0].second;
|
||||||
|
res.swap(matches);
|
||||||
|
}
|
||||||
|
return not res.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<Direction direction>
|
||||||
|
bool find_match_in_buffer(const Buffer& buffer, const BufferIterator pos,
|
||||||
|
MatchResults& matches, const Regex& ex)
|
||||||
|
{
|
||||||
|
if (direction == Forward)
|
||||||
|
return (boost::regex_search(pos, buffer.end(), matches, ex) or
|
||||||
|
boost::regex_search(buffer.begin(), pos, matches, ex));
|
||||||
|
else
|
||||||
|
return (find_last_match(buffer.begin(), pos, matches, ex) or
|
||||||
|
find_last_match(pos, buffer.end(), matches, ex));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<Direction direction, SelectMode mode>
|
||||||
|
SelectionList select_next_match(const Buffer& buffer, SelectionList selections,
|
||||||
|
const Regex& regex)
|
||||||
|
{
|
||||||
|
auto& sel = selections.main();
|
||||||
|
auto begin = buffer.iterator_at(sel.last());
|
||||||
|
auto end = begin;
|
||||||
|
CaptureList captures;
|
||||||
|
|
||||||
|
MatchResults matches;
|
||||||
|
|
||||||
|
bool found = false;
|
||||||
|
if ((found = find_match_in_buffer<direction>(buffer, utf8::next(begin), matches, regex)))
|
||||||
|
{
|
||||||
|
begin = matches[0].first;
|
||||||
|
end = matches[0].second;
|
||||||
|
for (auto& match : matches)
|
||||||
|
captures.emplace_back(match.first, match.second);
|
||||||
|
}
|
||||||
|
if (not found or begin == buffer.end())
|
||||||
|
throw runtime_error("'" + regex.str() + "': no matches found");
|
||||||
|
|
||||||
|
end = (begin == end) ? end : utf8::previous(end);
|
||||||
|
if (direction == Backward)
|
||||||
|
std::swap(begin, end);
|
||||||
|
|
||||||
|
Selection res{begin.coord(), end.coord(), std::move(captures)};
|
||||||
|
if (mode == SelectMode::Replace)
|
||||||
|
return SelectionList{ std::move(res) };
|
||||||
|
else if (mode == SelectMode::ReplaceMain)
|
||||||
|
sel = std::move(res);
|
||||||
|
else if (mode == SelectMode::Append)
|
||||||
|
{
|
||||||
|
selections.push_back(std::move(res));
|
||||||
|
selections.set_main_index(selections.size() - 1);
|
||||||
|
}
|
||||||
|
selections.sort_and_merge_overlapping();
|
||||||
|
return selections;
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectionList select_all_matches(const Buffer& buffer, SelectionList selection,
|
||||||
const Regex& regex);
|
const Regex& regex);
|
||||||
|
|
||||||
SelectionList split_selection(const Buffer& buffer, const Selection& selection,
|
SelectionList split_selection(const Buffer& buffer, SelectionList selection,
|
||||||
const Regex& separator_regex);
|
const Regex& separator_regex);
|
||||||
|
|
||||||
using CodepointPair = std::pair<Codepoint, Codepoint>;
|
using CodepointPair = std::pair<Codepoint, Codepoint>;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user