diff --git a/src/editor.cc b/src/editor.cc index b3572333..2e39bacf 100644 --- a/src/editor.cc +++ b/src/editor.cc @@ -282,26 +282,7 @@ struct nothing_selected : public runtime_error 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(); - new_selections.set_main_index(new_selections.size() - 1); - new_selections.sort_and_merge_overlapping(); - m_selections = std::move(new_selections); + m_selections = selector(*m_buffer, std::move(m_selections)); check_invariant(); } diff --git a/src/editor.hh b/src/editor.hh index 4037a76e..be832da3 100644 --- a/src/editor.hh +++ b/src/editor.hh @@ -40,7 +40,7 @@ class Editor : public SafeCountable { public: typedef std::function Selector; - typedef std::function MultiSelector; + typedef std::function MultiSelector; Editor(Buffer& buffer); virtual ~Editor() {} diff --git a/src/normal.cc b/src/normal.cc index 0b50195d..08d4d990 100644 --- a/src/normal.cc +++ b/src/normal.cc @@ -321,7 +321,7 @@ void search(Context& context, int) else if (str.empty() or not context.options()["incsearch"].get()) return; - context.editor().select(std::bind(select_next_match, _1, _2, ex), mode); + context.editor().multi_select(std::bind(select_next_match, _1, _2, ex)); } catch (boost::regex_error& err) { @@ -351,7 +351,7 @@ void search_next(Context& context, int param) { Regex ex{str}; do { - context.editor().select(std::bind(select_next_match, _1, _2, ex), mode); + context.editor().multi_select(std::bind(select_next_match, _1, _2, ex)); } while (--param > 0); } catch (boost::regex_error& err) @@ -496,16 +496,20 @@ void split_regex(Context& context, int) void split_lines(Context& context, int) { - context.editor().multi_select([](const Buffer& buffer, const Selection& sel) { - if (sel.first().line == sel.last().line) - return SelectionList{ sel }; + context.editor().multi_select([](const Buffer& buffer, + SelectionList selections) { SelectionList res; - auto min = sel.min(); - 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}); + for (auto& sel : selections) + { + if (sel.first().line == sel.last().line) + return SelectionList{ sel }; + auto min = sel.min(); + 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; }); } @@ -515,9 +519,10 @@ void join_select_spaces(Context& context, int) Editor& editor = context.editor(); editor.select(select_whole_lines); 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 kak_assert(std::is_sorted(res.begin(), res.end(), [](const Selection& lhs, const Selection& rhs) @@ -568,12 +573,15 @@ void indent(Context& context, int) Editor& editor = context.editor(); DynamicSelectionList sels{editor.buffer(), editor.selections()}; 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; - for (auto line = sel.min().line; line < sel.max().line+1; ++line) + for (auto& sel : selections) { - if (indent_empty or buf[line].length() > 1) - res.emplace_back(line, line); + for (auto line = sel.min().line; line < sel.max().line+1; ++line) + { + if (indent_empty or buf[line].length() > 1) + res.emplace_back(line, line); + } } return res; }); @@ -592,29 +600,32 @@ void deindent(Context& context, int) DynamicSelectionList sels{editor.buffer(), editor.selections()}; 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; - for (auto line = sel.min().line; line < sel.max().line+1; ++line) + for (auto& sel : selections) { - CharCount width = 0; - auto& content = buf[line]; - for (auto column = 0_byte; column < content.length(); ++column) + for (auto line = sel.min().line; line < sel.max().line+1; ++line) { - const char c = content[column]; - if (c == '\t') - width = (width / tabstop + 1) * tabstop; - else if (c == ' ') - ++width; - else + CharCount width = 0; + auto& content = buf[line]; + for (auto column = 0_byte; column < content.length(); ++column) { - 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; + const char c = content[column]; + if (c == '\t') + width = (width / tabstop + 1) * tabstop; + else if (c == ' ') + ++width; + else + { + 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; + } } } } diff --git a/src/selectors.cc b/src/selectors.cc index 7d0c4192..2e71b014 100644 --- a/src/selectors.cc +++ b/src/selectors.cc @@ -597,107 +597,57 @@ Selection select_whole_buffer(const Buffer& buffer, const Selection&) return Selection({0,0}, buffer.back_coord()); } -using MatchResults = boost::match_results; - -static bool find_last_match(BufferIterator begin, const BufferIterator& end, - MatchResults& res, const Regex& regex) +SelectionList select_all_matches(const Buffer& buffer, SelectionList selections, + 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 -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 -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(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(const Buffer&, const Selection&, const Regex&); -template Selection select_next_match(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; - for (; re_it != re_end; ++re_it) + for (auto& sel : selections) { - BufferIterator begin = (*re_it)[0].first; - BufferIterator end = (*re_it)[0].second; + auto sel_end = utf8::next(buffer.iterator_at(sel.max())); + RegexIterator re_it(buffer.iterator_at(sel.min()), sel_end, regex); + RegexIterator re_end; - if (begin == sel_end) - continue; + for (; re_it != re_end; ++re_it) + { + auto& begin = (*re_it)[0].first; + auto& end = (*re_it)[0].second; - CaptureList captures; - for (auto& match : *re_it) - captures.push_back(String(match.first, match.second)); + if (begin == sel_end) + continue; - result.push_back(Selection(begin.coord(), (begin == end ? end : utf8::previous(end)).coord(), - std::move(captures))); + CaptureList 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; } -SelectionList split_selection(const Buffer& buffer, const Selection& selection, +SelectionList split_selection(const Buffer& buffer, SelectionList selections, 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; - 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())); - begin = (*re_it)[0].second; + for (; re_it != re_end; ++re_it) + { + 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; } diff --git a/src/selectors.hh b/src/selectors.hh index 47194f41..684562c3 100644 --- a/src/selectors.hh +++ b/src/selectors.hh @@ -3,6 +3,7 @@ #include "selection.hh" #include "unicode.hh" +#include "editor.hh" namespace Kakoune { @@ -58,14 +59,78 @@ Selection trim_partial_lines(const Buffer& buffer, const Selection& selection); enum Direction { Forward, Backward }; -template -Selection select_next_match(const Buffer& buffer, const Selection& selection, - const Regex& regex); +using MatchResults = boost::match_results; -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 +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 +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(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); -SelectionList split_selection(const Buffer& buffer, const Selection& selection, +SelectionList split_selection(const Buffer& buffer, SelectionList selection, const Regex& separator_regex); using CodepointPair = std::pair;