Text-Objects: Use regex to select surroundings

Fixes #925
This commit is contained in:
Maxime Coste 2017-12-03 17:06:11 +08:00
parent c8f5935c48
commit 73a239d3be
3 changed files with 126 additions and 118 deletions

View File

@ -1171,7 +1171,9 @@ void select_object(Context& context, NormalParams params)
if (cp == 'c') if (cp == 'c')
{ {
const bool info = show_auto_info_ifn( const bool info = show_auto_info_ifn(
"Enter object desc", "format: <open text>,<close text>", "Enter object desc",
"format: <open regex>,<close regex>\n"
" escape commas with '\\'",
AutoInfo::Command, context); AutoInfo::Command, context);
context.input_handler().prompt( context.input_handler().prompt(
@ -1189,7 +1191,8 @@ void select_object(Context& context, NormalParams params)
select_and_set_last<mode>( select_and_set_last<mode>(
context, std::bind(select_surrounding, _1, _2, context, std::bind(select_surrounding, _1, _2,
params[0], params[1], Regex{params[0], RegexCompileFlags::Backward},
Regex{params[1], RegexCompileFlags::Backward},
count, flags)); count, flags));
}); });
return; return;
@ -1197,36 +1200,36 @@ void select_object(Context& context, NormalParams params)
static constexpr struct SurroundingPair static constexpr struct SurroundingPair
{ {
StringView opening; char opening;
StringView closing; char closing;
Codepoint name; char name;
} surrounding_pairs[] = { } surrounding_pairs[] = {
{ "(", ")", 'b' }, { '(', ')', 'b' },
{ "{", "}", 'B' }, { '{', '}', 'B' },
{ "[", "]", 'r' }, { '[', ']', 'r' },
{ "<", ">", 'a' }, { '<', '>', 'a' },
{ "\"", "\"", 'Q' }, { '"', '"', 'Q' },
{ "'", "'", 'q' }, { '\'', '\'', 'q' },
{ "`", "`", 'g' }, { '`', '`', 'g' },
}; };
auto pair_it = find_if(surrounding_pairs, auto pair_it = find_if(surrounding_pairs,
[cp](const SurroundingPair& s) { [cp](const SurroundingPair& s) {
return s.opening[0_char] == cp or return s.opening == cp or s.closing == cp or
s.closing[0_char] == cp or
(s.name != 0 and s.name == cp); (s.name != 0 and s.name == cp);
}); });
if (pair_it != std::end(surrounding_pairs)) if (pair_it != std::end(surrounding_pairs))
return select_and_set_last<mode>( return select_and_set_last<mode>(
context, std::bind(select_surrounding, _1, _2, context, std::bind(select_surrounding, _1, _2,
pair_it->opening, pair_it->closing, Regex{format("\\Q{}", pair_it->opening), RegexCompileFlags::Backward},
Regex{format("\\Q{}", pair_it->closing), RegexCompileFlags::Backward},
count, flags)); count, flags));
if (is_punctuation(cp) or cp == '_') if (is_punctuation(cp) or cp == '_')
{ {
auto utf8cp = to_string(cp); auto re = Regex{"\\Q" + to_string(cp), RegexCompileFlags::Backward};
return select_and_set_last<mode>( return select_and_set_last<mode>(
context, std::bind(select_surrounding, _1, _2, context, std::bind(select_surrounding, _1, _2,
utf8cp, utf8cp, count, flags)); re, re, count, flags));
} }
}, get_title(), }, get_title(),
build_autoinfo_for_mapping(context, KeymapMode::Object, build_autoinfo_for_mapping(context, KeymapMode::Object,

View File

@ -275,40 +275,55 @@ select_matching(const Context& context, const Selection& selection)
return {}; return {};
} }
template<typename Iterator, typename Container> template<typename Iterator>
Optional<Iterator> find_closing(Iterator pos, Iterator end, Optional<std::pair<Iterator, Iterator>>
Container opening, Container closing, find_opening(const Iterator& begin, Iterator pos,
int init_level, bool nestable) const Regex& opening, const Regex& closing,
int level, bool nestable)
{ {
const auto opening_len = opening.end() - opening.begin(); MatchResults<Iterator> res;
const auto closing_len = closing.end() - closing.begin(); if (backward_regex_search(begin, pos, res, closing) and
res[0].second == pos)
pos = res[0].first;
int level = nestable ? init_level : 0; for (auto match : RegexIterator<Iterator, MatchDirection::Backward>{begin, pos, opening})
if (end - pos >= opening_len and
std::equal(opening.begin(), opening.end(), pos))
pos += opening_len;
while (pos != end)
{ {
auto close = std::search(pos, end, closing.begin(), closing.end());
if (close == end)
return {};
if (nestable) if (nestable)
{ {
for (auto open = pos; open != close; open += opening_len) for (auto m : RegexIterator<Iterator, MatchDirection::Backward>{match[0].second, pos, closing})
{
open = std::search(open, close, opening.begin(), opening.end());
if (open == close)
break;
++level; ++level;
} }
if (not nestable or level == 0)
return match[0];
pos = match[0].first;
--level;
}
return {};
}
template<typename Iterator>
Optional<std::pair<Iterator, Iterator>>
find_closing(Iterator pos, const Iterator& end,
const Regex& opening, const Regex& closing,
int level, bool nestable)
{
MatchResults<Iterator> res;
if (regex_search(pos, end, res, opening) and
res[0].first == pos)
pos = res[0].second;
for (auto match : RegexIterator<Iterator, MatchDirection::Forward>{pos, end, closing})
{
if (nestable)
{
for (auto m : RegexIterator<Iterator, MatchDirection::Forward>{pos, match[0].first, opening})
++level;
} }
pos = close + closing_len; if (not nestable or level == 0)
if (level == 0) return match[0];
return pos-1; pos = match[0].second;
--level; --level;
} }
return {}; return {};
@ -317,84 +332,70 @@ Optional<Iterator> find_closing(Iterator pos, Iterator end,
template<typename Container, typename Iterator> template<typename Container, typename Iterator>
Optional<std::pair<Iterator, Iterator>> Optional<std::pair<Iterator, Iterator>>
find_surrounding(const Container& container, Iterator pos, find_surrounding(const Container& container, Iterator pos,
StringView opening, StringView closing, const Regex& opening, const Regex& closing,
ObjectFlags flags, int init_level) ObjectFlags flags, int level)
{ {
using std::begin; using std::end;
const bool to_begin = flags & ObjectFlags::ToBegin;
const bool to_end = flags & ObjectFlags::ToEnd;
const bool nestable = opening != closing; const bool nestable = opening != closing;
auto first = pos; // When onto the token of a non nestable block, consider it as an opening.
if (to_begin and opening != *pos) MatchResults<Iterator> matches;
{ if (not nestable and regex_search(pos, container.end(), matches, opening) and
using RevIt = std::reverse_iterator<Iterator>; matches[0].first == pos)
auto res = find_closing(RevIt{pos+1}, RevIt{begin(container)}, pos = matches[0].second;
closing | reverse(), opening | reverse(),
init_level, nestable);
if (not res)
return {};
first = res->base() - 1; auto first = pos;
if (flags & ObjectFlags::ToBegin)
{
// When positionned onto opening and searching to opening, search the parent one
if (nestable and first != container.begin() and not (flags & ObjectFlags::ToEnd) and
regex_search(first, container.end(), matches, opening) and matches[0].first == first)
first = utf8::previous(first, container.begin());
if (auto res = find_opening(container.begin(), first+1, opening, closing, level, nestable))
first = (flags & ObjectFlags::Inner) ? res->second : res->first;
else
return {};
} }
auto last = pos; auto last = pos;
if (to_end) if (flags & ObjectFlags::ToEnd)
{ {
auto res = find_closing(pos, end(container), opening, closing, // When positionned onto closing and searching to closing, search the parent one
init_level, nestable); auto next = utf8::next(last, container.end());
if (not res) if (nestable and next != container.end() and not (flags & ObjectFlags::ToBegin) and
backward_regex_search(container.begin(), next, matches, closing) and matches[0].second == next)
last = next;
if (auto res = find_closing(last, container.end(), opening, closing, level, nestable))
last = (flags & ObjectFlags::Inner) ? utf8::previous(res->first, container.begin())
: utf8::previous(res->second, container.begin());
else
return {}; return {};
last = *res;
} }
if (first > last)
last = first;
if (flags & ObjectFlags::Inner) return std::pair<Iterator, Iterator>{first, last};
{
if (to_begin and first != last)
first += (int)opening.length();
if (to_end and first != last)
last -= (int)closing.length();
}
return to_end ? std::pair<Iterator, Iterator>{first, last}
: std::pair<Iterator, Iterator>{last, first};
} }
Optional<Selection> Optional<Selection>
select_surrounding(const Context& context, const Selection& selection, select_surrounding(const Context& context, const Selection& selection,
StringView opening, StringView closing, int level, const Regex& opening, const Regex& closing, int level,
ObjectFlags flags) ObjectFlags flags)
{ {
auto& buffer = context.buffer(); auto& buffer = context.buffer();
const bool nestable = opening != closing; auto pos = buffer.iterator_at(selection.cursor());
auto pos = selection.cursor();
if (not nestable or flags & ObjectFlags::Inner)
{
if (auto res = find_surrounding(buffer, buffer.iterator_at(pos),
opening, closing, flags, level))
return utf8_range(res->first, res->second);
return {};
}
auto c = buffer.byte_at(pos); auto res = find_surrounding(buffer, pos, opening, closing, flags, level);
if ((flags == ObjectFlags::ToBegin and c == opening) or
(flags == ObjectFlags::ToEnd and c == closing))
++level;
auto res = find_surrounding(buffer, buffer.iterator_at(pos), // When we already had the full object selected, select its parent
opening, closing, flags, level); if (res and flags == (ObjectFlags::ToBegin | ObjectFlags::ToEnd) and
if (not res) res->first.coord() == selection.min() and res->second.coord() == selection.max())
return {}; res = find_surrounding(buffer, pos, opening, closing, flags, level+1);
Selection sel = utf8_range(res->first, res->second); if (res)
return (flags & ObjectFlags::ToEnd) ? utf8_range(res->first, res->second)
if (flags != (ObjectFlags::ToBegin | ObjectFlags::ToEnd) or : utf8_range(res->second, res->first);
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 {}; return {};
} }
@ -987,30 +988,34 @@ void split_selections(SelectionList& selections, const Regex& regex, int capture
UnitTest test_find_surrounding{[]() UnitTest test_find_surrounding{[]()
{ {
StringView s("[salut { toi[] }]"); StringView s = "{foo [bar { baz[] }]}";
auto check_equal = [&](const char* pos, StringView opening, StringView closing, auto check_equal = [&](const char* pos, StringView opening, StringView closing,
ObjectFlags flags, int init_level, StringView expected) { ObjectFlags flags, int level, StringView expected) {
auto res = find_surrounding(s, pos, opening, closing, flags, init_level); auto res = find_surrounding(s, pos,
Regex{"\\Q" + opening, RegexCompileFlags::Backward},
Regex{"\\Q" + closing, RegexCompileFlags::Backward},
flags, level);
kak_assert(res);
auto min = std::min(res->first, res->second), auto min = std::min(res->first, res->second),
max = std::max(res->first, res->second); max = std::max(res->first, res->second);
kak_assert(res and StringView{min, max+1} == expected); kak_assert(StringView{min, max+1} == expected);
}; };
check_equal(s.begin() + 10, '{', '}', ObjectFlags::ToBegin | ObjectFlags::ToEnd, 0, "{ toi[] }"); check_equal(s.begin() + 13, '{', '}', ObjectFlags::ToBegin | ObjectFlags::ToEnd, 0, "{ baz[] }");
check_equal(s.begin() + 10, '[', ']', ObjectFlags::ToBegin | ObjectFlags::ToEnd | ObjectFlags::Inner, 0, "salut { toi[] }"); check_equal(s.begin() + 13, '[', ']', ObjectFlags::ToBegin | ObjectFlags::ToEnd | ObjectFlags::Inner, 0, "bar { baz[] }");
check_equal(s.begin(), '[', ']', ObjectFlags::ToBegin | ObjectFlags::ToEnd, 0, "[salut { toi[] }]"); check_equal(s.begin() + 5, '[', ']', ObjectFlags::ToBegin | ObjectFlags::ToEnd, 0, "[bar { baz[] }]");
check_equal(s.begin()+7, '{', '}', ObjectFlags::ToBegin | ObjectFlags::ToEnd, 0, "{ toi[] }"); check_equal(s.begin() + 10, '{', '}', ObjectFlags::ToBegin | ObjectFlags::ToEnd, 0, "{ baz[] }");
check_equal(s.begin() + 12, '[', ']', ObjectFlags::ToBegin | ObjectFlags::ToEnd | ObjectFlags::Inner, 0, "]"); check_equal(s.begin() + 16, '[', ']', ObjectFlags::ToBegin | ObjectFlags::ToEnd | ObjectFlags::Inner, 0, "]");
check_equal(s.begin() + 14, '[', ']', ObjectFlags::ToBegin | ObjectFlags::ToEnd, 0, "[salut { toi[] }]"); check_equal(s.begin() + 18, '[', ']', ObjectFlags::ToBegin | ObjectFlags::ToEnd, 0, "[bar { baz[] }]");
check_equal(s.begin() + 1, '[', ']', ObjectFlags::ToBegin, 0, "[s"); check_equal(s.begin() + 6, '[', ']', ObjectFlags::ToBegin, 0, "[b");
s = "[]"; s = "[*][] foo";
check_equal(s.begin() + 1, '[', ']', ObjectFlags::ToBegin | ObjectFlags::ToEnd, 0, "[]"); kak_assert(not find_surrounding(s, s.begin() + 6,
Regex{"\\Q[", RegexCompileFlags::Backward},
Regex{"\\Q]", RegexCompileFlags::Backward},
ObjectFlags::ToBegin, 0));
s = "[*][] hehe"; s = "begin foo begin bar end end";
kak_assert(not find_surrounding(s, s.begin() + 6, '[', ']', ObjectFlags::ToBegin, 0));
s = "begin tchou begin tchaa end end";
check_equal(s.begin() + 6, "begin", "end", ObjectFlags::ToBegin | ObjectFlags::ToEnd, 0, s); check_equal(s.begin() + 6, "begin", "end", ObjectFlags::ToBegin | ObjectFlags::ToEnd, 0, s);
}}; }};

View File

@ -109,7 +109,7 @@ void split_selections(SelectionList& selections, const Regex& regex, int capture
Optional<Selection> Optional<Selection>
select_surrounding(const Context& context, const Selection& selection, select_surrounding(const Context& context, const Selection& selection,
StringView opening, StringView closing, int level, const Regex& opening, const Regex& closing, int level,
ObjectFlags flags); ObjectFlags flags);
} }