diff --git a/src/normal.cc b/src/normal.cc index 4a6588c6..ce588bb2 100644 --- a/src/normal.cc +++ b/src/normal.cc @@ -40,33 +40,49 @@ void select(Context& context, T func) if (mode == SelectMode::Append) { auto& sel = selections.main(); - auto res = func(buffer, sel); - if (res.captures().empty()) - res.captures() = sel.captures(); - selections.push_back(res); - selections.set_main_index(selections.size() - 1); + if (auto res = func(buffer, sel)) + { + if (res->captures().empty()) + res->captures() = sel.captures(); + selections.push_back(std::move(*res)); + selections.set_main_index(selections.size() - 1); + } } else { - for (auto& sel : selections) + Vector to_remove; + for (int i = 0; i < (int)selections.size(); ++i) { + auto& sel = selections[i]; auto res = func(buffer, sel); + if (not res) + { + to_remove.push_back(i); + continue; + } + if (mode == SelectMode::Extend) - sel.merge_with(res); + sel.merge_with(*res); else { - sel.anchor() = res.anchor(); - sel.cursor() = res.cursor(); + sel.anchor() = res->anchor(); + sel.cursor() = res->cursor(); } - if (not res.captures().empty()) - sel.captures() = std::move(res.captures()); + if (not res->captures().empty()) + sel.captures() = std::move(res->captures()); } + + if (to_remove.size() == selections.size()) + throw runtime_error{"no selections remaining"}; + for (auto& i : to_remove | reverse()) + selections.remove(i); } + selections.sort_and_merge_overlapping(); selections.check_invariant(); } -template +template (*func)(const Buffer&, const Selection&)> void select(Context& context, NormalParams) { select(context, func); @@ -1053,7 +1069,7 @@ void select_object(Context& context, NormalParams params) static constexpr struct ObjectType { Codepoint key; - Selection (*func)(const Buffer&, const Selection&, int, ObjectFlags); + Optional (*func)(const Buffer&, const Selection&, int, ObjectFlags); } selectors[] = { { 'w', select_word }, { 'W', select_word }, diff --git a/src/selectors.cc b/src/selectors.cc index 43c376e1..85af2958 100644 --- a/src/selectors.cc +++ b/src/selectors.cc @@ -37,17 +37,18 @@ Selection utf8_range(const Utf8Iterator& first, const Utf8Iterator& last) } template -Selection select_to_next_word(const Buffer& buffer, const Selection& selection) +Optional +select_to_next_word(const Buffer& buffer, const Selection& selection) { Utf8Iterator begin{buffer.iterator_at(selection.cursor()), buffer}; if (begin+1 == buffer.end()) - return selection; + return {}; if (categorize(*begin) != categorize(*(begin+1))) ++begin; if (not skip_while(begin, buffer.end(), [](Codepoint c) { return is_eol(c); })) - return selection; + return {}; Utf8Iterator end = begin+1; if (word_type == Word and is_punctuation(*begin)) @@ -59,21 +60,21 @@ Selection select_to_next_word(const Buffer& buffer, const Selection& selection) return utf8_range(begin, end-1); } -template Selection select_to_next_word(const Buffer&, const Selection&); -template Selection select_to_next_word(const Buffer&, const Selection&); +template Optional select_to_next_word(const Buffer&, const Selection&); +template Optional select_to_next_word(const Buffer&, const Selection&); template -Selection select_to_next_word_end(const Buffer& buffer, const Selection& selection) +Optional select_to_next_word_end(const Buffer& buffer, const Selection& selection) { Utf8Iterator begin{buffer.iterator_at(selection.cursor()), buffer}; if (begin+1 == buffer.end()) - return selection; + return {}; if (categorize(*begin) != categorize(*(begin+1))) ++begin; if (not skip_while(begin, buffer.end(), [](Codepoint c) { return is_eol(c); })) - return selection; + return {}; Utf8Iterator end = begin; skip_while(end, buffer.end(), is_horizontal_blank); @@ -84,15 +85,16 @@ Selection select_to_next_word_end(const Buffer& buffer, const Selection& selecti return utf8_range(begin, end-1); } -template Selection select_to_next_word_end(const Buffer&, const Selection&); -template Selection select_to_next_word_end(const Buffer&, const Selection&); +template Optional select_to_next_word_end(const Buffer&, const Selection&); +template Optional select_to_next_word_end(const Buffer&, const Selection&); template -Selection select_to_previous_word(const Buffer& buffer, const Selection& selection) +Optional +select_to_previous_word(const Buffer& buffer, const Selection& selection) { Utf8Iterator begin{buffer.iterator_at(selection.cursor()), buffer}; if (begin == buffer.begin()) - return selection; + return {}; if (categorize(*begin) != categorize(*(begin-1))) --begin; @@ -108,19 +110,20 @@ Selection select_to_previous_word(const Buffer& buffer, const Selection& selecti return utf8_range(begin, with_end ? end : end+1); } -template Selection select_to_previous_word(const Buffer&, const Selection&); -template Selection select_to_previous_word(const Buffer&, const Selection&); +template Optional select_to_previous_word(const Buffer&, const Selection&); +template Optional select_to_previous_word(const Buffer&, const Selection&); template -Selection select_word(const Buffer& buffer, const Selection& selection, - int count, ObjectFlags flags) +Optional +select_word(const Buffer& buffer, const Selection& selection, + int count, ObjectFlags flags) { Utf8Iterator first{buffer.iterator_at(selection.cursor()), buffer}; if (not is_word(*first) and ((flags & ObjectFlags::Inner) or not skip_while(first, buffer.end(), [](Codepoint c) { return not is_word(c); }))) - return selection; + return {}; Utf8Iterator last = first; if (flags & ObjectFlags::ToBegin) @@ -139,10 +142,11 @@ Selection select_word(const Buffer& buffer, const Selection& selection, return (flags & ObjectFlags::ToEnd) ? utf8_range(first, last) : utf8_range(last, first); } -template Selection select_word(const Buffer&, const Selection&, int, ObjectFlags); -template Selection select_word(const Buffer&, const Selection&, int, ObjectFlags); +template Optional select_word(const Buffer&, const Selection&, int, ObjectFlags); +template Optional select_word(const Buffer&, const Selection&, int, ObjectFlags); -Selection select_line(const Buffer& buffer, const Selection& selection) +Optional +select_line(const Buffer& buffer, const Selection& selection) { Utf8Iterator first{buffer.iterator_at(selection.cursor()), buffer}; if (*first == '\n' and first + 1 != buffer.end()) @@ -158,7 +162,8 @@ Selection select_line(const Buffer& buffer, const Selection& selection) } template -Selection select_to_line_end(const Buffer& buffer, const Selection& selection) +Optional +select_to_line_end(const Buffer& buffer, const Selection& selection) { BufferCoord begin = selection.cursor(); LineCount line = begin.line; @@ -168,20 +173,22 @@ Selection select_to_line_end(const Buffer& buffer, const Selection& selection) end = begin; return target_eol({only_move ? end : begin, end}); } -template Selection select_to_line_end(const Buffer&, const Selection&); -template Selection select_to_line_end(const Buffer&, const Selection&); +template Optional select_to_line_end(const Buffer&, const Selection&); +template Optional select_to_line_end(const Buffer&, const Selection&); template -Selection select_to_line_begin(const Buffer& buffer, const Selection& selection) +Optional +select_to_line_begin(const Buffer& buffer, const Selection& selection) { BufferCoord begin = selection.cursor(); BufferCoord end = begin.line; - return {only_move ? end : begin, end}; + return Selection{only_move ? end : begin, end}; } -template Selection select_to_line_begin(const Buffer&, const Selection&); -template Selection select_to_line_begin(const Buffer&, const Selection&); +template Optional select_to_line_begin(const Buffer&, const Selection&); +template Optional select_to_line_begin(const Buffer&, const Selection&); -Selection select_to_first_non_blank(const Buffer& buffer, const Selection& selection) +Optional +select_to_first_non_blank(const Buffer& buffer, const Selection& selection) { auto it = buffer.iterator_at(selection.cursor().line); skip_while(it, buffer.iterator_at(selection.cursor().line+1), @@ -189,7 +196,8 @@ Selection select_to_first_non_blank(const Buffer& buffer, const Selection& selec return {it.coord()}; } -Selection select_matching(const Buffer& buffer, const Selection& selection) +Optional +select_matching(const Buffer& buffer, const Selection& selection) { Vector matching_pairs = { '(', ')', '{', '}', '[', ']', '<', '>' }; Utf8Iterator it{buffer.iterator_at(selection.cursor()), buffer}; @@ -202,7 +210,7 @@ Selection select_matching(const Buffer& buffer, const Selection& selection) ++it; } if (match == matching_pairs.end()) - return selection; + return {}; Utf8Iterator begin = it; @@ -236,7 +244,7 @@ Selection select_matching(const Buffer& buffer, const Selection& selection) --it; } } - return selection; + return {}; } template @@ -333,9 +341,10 @@ find_surrounding(const Container& container, Iterator pos, opening, closing, flags, init_level); } -Selection select_surrounding(const Buffer& buffer, const Selection& selection, - StringView opening, StringView closing, int level, - ObjectFlags flags) +Optional +select_surrounding(const Buffer& buffer, const Selection& selection, + StringView opening, StringView closing, int level, + ObjectFlags flags) { const bool nestable = opening != closing; auto pos = selection.cursor(); @@ -344,7 +353,7 @@ Selection select_surrounding(const Buffer& buffer, const Selection& selection, if (auto res = find_surrounding(buffer, buffer.iterator_at(pos), opening, closing, flags, level)) return utf8_range(res->first, res->second); - return selection; + return {}; } auto c = buffer.byte_at(pos); @@ -355,22 +364,23 @@ Selection select_surrounding(const Buffer& buffer, const Selection& selection, auto res = find_surrounding(buffer, buffer.iterator_at(pos), opening, closing, flags, level); if (not res) - return selection; + return {}; Selection sel = utf8_range(res->first, res->second); - if (flags == (ObjectFlags::ToBegin | ObjectFlags::ToEnd) and - sel.min() == selection.min() and sel.max() == selection.max()) - { - if (auto res_parent = find_surrounding(buffer, buffer.iterator_at(pos), - opening, closing, flags, level+1)) - return utf8_range(res_parent->first, res_parent->second); - } - return sel; + if (flags != (ObjectFlags::ToBegin | ObjectFlags::ToEnd) or + sel.min() != selection.min() or sel.max() != selection.max()) + return sel; + + if (auto res_parent = find_surrounding(buffer, buffer.iterator_at(pos), + opening, closing, flags, level+1)) + return utf8_range(res_parent->first, res_parent->second); + return {}; } -Selection select_to(const Buffer& buffer, const Selection& selection, - Codepoint c, int count, bool inclusive) +Optional +select_to(const Buffer& buffer, const Selection& selection, + Codepoint c, int count, bool inclusive) { Utf8Iterator begin{buffer.iterator_at(selection.cursor()), buffer}; Utf8Iterator end = begin; @@ -379,15 +389,16 @@ Selection select_to(const Buffer& buffer, const Selection& selection, ++end; skip_while(end, buffer.end(), [c](Codepoint cur) { return cur != c; }); if (end == buffer.end()) - return selection; + return {}; } while (--count > 0); return utf8_range(begin, inclusive ? end : end-1); } -Selection select_to_reverse(const Buffer& buffer, const Selection& selection, - Codepoint c, int count, bool inclusive) +Optional +select_to_reverse(const Buffer& buffer, const Selection& selection, + Codepoint c, int count, bool inclusive) { Utf8Iterator begin{buffer.iterator_at(selection.cursor()), buffer}; Utf8Iterator end = begin; @@ -396,14 +407,16 @@ Selection select_to_reverse(const Buffer& buffer, const Selection& selection, --end; if (skip_while_reverse(end, buffer.begin(), [c](Codepoint cur) { return cur != c; })) - return selection; + return {}; } while (--count > 0); return utf8_range(begin, inclusive ? end : end+1); } -Selection select_number(const Buffer& buffer, const Selection& selection, int count, ObjectFlags flags) +Optional +select_number(const Buffer& buffer, const Selection& selection, + int count, ObjectFlags flags) { auto is_number = [&](char c) { return (c >= '0' and c <= '9') or @@ -414,7 +427,7 @@ Selection select_number(const Buffer& buffer, const Selection& selection, int co BufferIterator last = first; if (not is_number(*first) and *first != '-') - return selection; + return {}; if (flags & ObjectFlags::ToBegin) { @@ -437,7 +450,9 @@ Selection select_number(const Buffer& buffer, const Selection& selection, int co : Selection{last.coord(), first.coord()}; } -Selection select_sentence(const Buffer& buffer, const Selection& selection, int count, ObjectFlags flags) +Optional +select_sentence(const Buffer& buffer, const Selection& selection, + int count, ObjectFlags flags) { auto is_end_of_sentence = [](char c) { return c == '.' or c == ';' or c == '!' or c == '?'; @@ -502,7 +517,9 @@ Selection select_sentence(const Buffer& buffer, const Selection& selection, int : Selection{last.coord(), first.coord()}; } -Selection select_paragraph(const Buffer& buffer, const Selection& selection, int count, ObjectFlags flags) +Optional +select_paragraph(const Buffer& buffer, const Selection& selection, + int count, ObjectFlags flags) { BufferIterator first = buffer.iterator_at(selection.cursor()); @@ -555,7 +572,9 @@ Selection select_paragraph(const Buffer& buffer, const Selection& selection, int : Selection{last.coord(), first.coord()}; } -Selection select_whitespaces(const Buffer& buffer, const Selection& selection, int count, ObjectFlags flags) +Optional +select_whitespaces(const Buffer& buffer, const Selection& selection, + int count, ObjectFlags flags) { auto is_whitespace = [&](char c) { return c == ' ' or c == '\t' or @@ -563,6 +582,10 @@ Selection select_whitespaces(const Buffer& buffer, const Selection& selection, i }; BufferIterator first = buffer.iterator_at(selection.cursor()); BufferIterator last = first; + + if (not is_whitespace(*first)) + return {}; + if (flags & ObjectFlags::ToBegin) { if (is_whitespace(*first)) @@ -584,7 +607,9 @@ Selection select_whitespaces(const Buffer& buffer, const Selection& selection, i : Selection{last.coord(), first.coord()}; } -Selection select_indent(const Buffer& buffer, const Selection& selection, int count, ObjectFlags flags) +Optional +select_indent(const Buffer& buffer, const Selection& selection, + int count, ObjectFlags flags) { auto get_indent = [](StringView str, int tabstop) { CharCount indent = 0; @@ -641,8 +666,9 @@ Selection select_indent(const Buffer& buffer, const Selection& selection, int co return Selection{begin_line, {end_line, buffer[end_line].length() - 1}}; } -Selection select_argument(const Buffer& buffer, const Selection& selection, - int level, ObjectFlags flags) +Optional +select_argument(const Buffer& buffer, const Selection& selection, + int level, ObjectFlags flags) { enum Class { None, Opening, Closing, Delimiter }; auto classify = [](Codepoint c) { @@ -724,11 +750,13 @@ Selection select_argument(const Buffer& buffer, const Selection& selection, --end; if (flags & ObjectFlags::ToBegin and not (flags & ObjectFlags::ToEnd)) - return {pos.coord(), begin.coord()}; - return {(flags & ObjectFlags::ToBegin ? begin : pos).coord(), end.coord()}; + return Selection{pos.coord(), begin.coord()}; + return Selection{(flags & ObjectFlags::ToBegin ? begin : pos).coord(), + end.coord()}; } -Selection select_lines(const Buffer& buffer, const Selection& selection) +Optional +select_lines(const Buffer& buffer, const Selection& selection) { BufferCoord anchor = selection.anchor(); BufferCoord cursor = selection.cursor(); @@ -741,7 +769,8 @@ Selection select_lines(const Buffer& buffer, const Selection& selection) return target_eol({anchor, cursor}); } -Selection trim_partial_lines(const Buffer& buffer, const Selection& selection) +Optional +trim_partial_lines(const Buffer& buffer, const Selection& selection) { BufferCoord anchor = selection.anchor(); BufferCoord cursor = selection.cursor(); @@ -753,14 +782,14 @@ Selection trim_partial_lines(const Buffer& buffer, const Selection& selection) if (to_line_end.column != buffer[to_line_end.line].length()-1) { if (to_line_end.line == 0) - return selection; + return {}; auto prev_line = to_line_end.line-1; to_line_end = BufferCoord{ prev_line, buffer[prev_line].length()-1 }; } if (to_line_start > to_line_end) - return selection; + return {}; return target_eol({anchor, cursor}); } @@ -772,9 +801,9 @@ void select_buffer(SelectionList& selections) } template -bool find_match_in_buffer(const Buffer& buffer, const BufferIterator pos, - MatchResults& matches, - const Regex& ex, bool& wrapped) +static bool find_match_in_buffer(const Buffer& buffer, const BufferIterator pos, + MatchResults& matches, + const Regex& ex, bool& wrapped) { wrapped = false; if (direction == Forward) diff --git a/src/selectors.hh b/src/selectors.hh index 737550d2..e0bbb5b5 100644 --- a/src/selectors.hh +++ b/src/selectors.hh @@ -18,29 +18,40 @@ inline Selection keep_direction(Selection res, const Selection& ref) } template -Selection select_to_next_word(const Buffer& buffer, const Selection& selection); +Optional +select_to_next_word(const Buffer& buffer, const Selection& selection); template -Selection select_to_next_word_end(const Buffer& buffer, const Selection& selection); +Optional +select_to_next_word_end(const Buffer& buffer, const Selection& selection); template -Selection select_to_previous_word(const Buffer& buffer, const Selection& selection); +Optional +select_to_previous_word(const Buffer& buffer, const Selection& selection); -Selection select_line(const Buffer& buffer, const Selection& selection); -Selection select_matching(const Buffer& buffer, const Selection& selection); +Optional +select_line(const Buffer& buffer, const Selection& selection); -Selection select_to(const Buffer& buffer, const Selection& selection, +Optional +select_matching(const Buffer& buffer, const Selection& selection); + +Optional +select_to(const Buffer& buffer, const Selection& selection, Codepoint c, int count, bool inclusive); -Selection select_to_reverse(const Buffer& buffer, const Selection& selection, - Codepoint c, int count, bool inclusive); +Optional +select_to_reverse(const Buffer& buffer, const Selection& selection, + Codepoint c, int count, bool inclusive); template -Selection select_to_line_end(const Buffer& buffer, const Selection& selection); +Optional +select_to_line_end(const Buffer& buffer, const Selection& selection); template -Selection select_to_line_begin(const Buffer& buffer, const Selection& selection); +Optional +select_to_line_begin(const Buffer& buffer, const Selection& selection); -Selection select_to_first_non_blank(const Buffer& buffer, const Selection& selection); +Optional +select_to_first_non_blank(const Buffer& buffer, const Selection& selection); enum class ObjectFlags { @@ -52,47 +63,53 @@ enum class ObjectFlags template<> struct WithBitOps : std::true_type {}; template -Selection select_word(const Buffer& buffer, const Selection& selection, - int count, ObjectFlags flags); +Optional +select_word(const Buffer& buffer, const Selection& selection, + int count, ObjectFlags flags); -Selection select_number(const Buffer& buffer, const Selection& selection, - int count, ObjectFlags flags); +Optional +select_number(const Buffer& buffer, const Selection& selection, + int count, ObjectFlags flags); -Selection select_sentence(const Buffer& buffer, const Selection& selection, - int count, ObjectFlags flags); +Optional +select_sentence(const Buffer& buffer, const Selection& selection, + int count, ObjectFlags flags); -Selection select_paragraph(const Buffer& buffer, const Selection& selection, - int count, ObjectFlags flags); +Optional +select_paragraph(const Buffer& buffer, const Selection& selection, + int count, ObjectFlags flags); -Selection select_whitespaces(const Buffer& buffer, const Selection& selection, - int count, ObjectFlags flags); +Optional +select_whitespaces(const Buffer& buffer, const Selection& selection, + int count, ObjectFlags flags); -Selection select_indent(const Buffer& buffer, const Selection& selection, - int count, ObjectFlags flags); +Optional +select_indent(const Buffer& buffer, const Selection& selection, + int count, ObjectFlags flags); -Selection select_argument(const Buffer& buffer, const Selection& selection, - int level, ObjectFlags flags); +Optional +select_argument(const Buffer& buffer, const Selection& selection, + int level, ObjectFlags flags); -Selection select_lines(const Buffer& buffer, const Selection& selection); +Optional +select_lines(const Buffer& buffer, const Selection& selection); -Selection trim_partial_lines(const Buffer& buffer, const Selection& selection); +Optional +trim_partial_lines(const Buffer& buffer, const Selection& selection); void select_buffer(SelectionList& selections); enum Direction { Forward, Backward }; template -bool find_match_in_buffer(const Buffer& buffer, const BufferIterator pos, - MatchResults& matches, - const Regex& ex, bool& wrapped); - -template -Selection find_next_match(const Buffer& buffer, const Selection& sel, const Regex& regex, bool& wrapped); +Selection find_next_match(const Buffer& buffer, const Selection& sel, + const Regex& regex, bool& wrapped); void select_all_matches(SelectionList& selections, const Regex& regex, int capture = 0); void split_selections(SelectionList& selections, const Regex& separator_regex, int capture = 0); -Selection select_surrounding(const Buffer& buffer, const Selection& selection, +Optional +select_surrounding(const Buffer& buffer, const Selection& selection, StringView opening, StringView closing, int level, ObjectFlags flags); diff --git a/test/unit/object/drop-non-whitespace/cmd b/test/unit/object/drop-non-whitespace/cmd new file mode 100644 index 00000000..b100ed0c --- /dev/null +++ b/test/unit/object/drop-non-whitespace/cmd @@ -0,0 +1 @@ + diff --git a/test/unit/object/drop-non-whitespace/in b/test/unit/object/drop-non-whitespace/in new file mode 100644 index 00000000..ec6eb00e --- /dev/null +++ b/test/unit/object/drop-non-whitespace/in @@ -0,0 +1,3 @@ +wo%(r)d +foo %( ) bar +baz%( )qux diff --git a/test/unit/object/drop-non-whitespace/selections b/test/unit/object/drop-non-whitespace/selections new file mode 100644 index 00000000..8c68ee72 --- /dev/null +++ b/test/unit/object/drop-non-whitespace/selections @@ -0,0 +1 @@ + :