Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix quadratic runtime when updating region highlighter matches #4717

Merged
merged 2 commits into from
Sep 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
98 changes: 53 additions & 45 deletions src/highlighters.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2210,60 +2210,68 @@ struct RegionsHighlighter : public Highlighter
m_regexes.insert({key, Regex{str, flags}});
}

void add_matches(const Buffer& buffer, LineRange range, Cache& cache) const
class MatchAdder
{
for (auto& [key, regex] : m_regexes)
cache.matches[key];

struct Matcher
public:
MatchAdder(RegionsHighlighter& region, const Buffer& buffer, Cache& cache) : m_buffer(buffer)
{
RegexMatchList& matches;
const Regex& regex;
size_t pivot = matches.size();
ThreadedRegexVM<const char*, RegexMode::Forward | RegexMode::Search> vm{*regex.impl()};
};
Vector<Matcher> matchers;
for (auto& [key, regex] : m_regexes)
matchers.push_back(Matcher{cache.matches.get(key), regex});
for (auto& [key, regex] : region.m_regexes)
cache.matches[key];
for (auto& [key, regex] : region.m_regexes)
m_matchers.push_back(Matcher{cache.matches.get(key), regex});
}

for (auto line = range.begin; line < range.end; ++line)
~MatchAdder()
{
const StringView l = buffer[line];
const auto flags = RegexExecFlags::NotEndOfLine; // buffer line already ends with \n
// Move new matches into position.
for (auto& [matches, regex, pivot, vm] : m_matchers)
std::inplace_merge(matches.begin(), matches.begin() + pivot, matches.end(),
[](const auto& lhs, const auto& rhs) { return lhs.line < rhs.line; });
}

for (auto& [matches, regex, pivot, vm] : matchers)
void add(LineRange range)
{
for (auto line = range.begin; line < range.end; ++line)
{
auto extra_flags = RegexExecFlags::None;
auto pos = l.begin();
while (vm.exec(pos, l.end(), l.begin(), l.end(), flags | extra_flags))
const StringView l = m_buffer[line];
const auto flags = RegexExecFlags::NotEndOfLine; // buffer line already ends with \n

for (auto& [matches, regex, pivot, vm] : m_matchers)
{
ConstArrayView<const char*> captures = vm.captures();
const bool with_capture = regex.mark_count() > 0 and captures[2] != nullptr and
captures[1] - captures[0] < std::numeric_limits<uint16_t>::max();
matches.push_back({
line,
(int)(captures[0] - l.begin()),
(int)(captures[1] - l.begin()),
(uint16_t)(with_capture ? captures[2] - captures[0] : 0),
(uint16_t)(with_capture ? captures[3] - captures[2] : 0)
});
pos = captures[1];

extra_flags = (captures[0] == captures[1]) ? RegexExecFlags::NotInitialNull : RegexExecFlags::None;
auto extra_flags = RegexExecFlags::None;
auto pos = l.begin();
while (vm.exec(pos, l.end(), l.begin(), l.end(), flags | extra_flags))
{
ConstArrayView<const char*> captures = vm.captures();
const bool with_capture = regex.mark_count() > 0 and captures[2] != nullptr and
captures[1] - captures[0] < std::numeric_limits<uint16_t>::max();
matches.push_back({
line,
(int)(captures[0] - l.begin()),
(int)(captures[1] - l.begin()),
(uint16_t)(with_capture ? captures[2] - captures[0] : 0),
(uint16_t)(with_capture ? captures[3] - captures[2] : 0)
});
pos = captures[1];

extra_flags = (captures[0] == captures[1]) ? RegexExecFlags::NotInitialNull : RegexExecFlags::None;
}
}
}
}

for (auto& [matches, regex, pivot, vm] : matchers)
private:
struct Matcher
{
auto pos = std::lower_bound(matches.begin(), matches.begin() + pivot, range.begin,
[](const RegexMatch& m, LineCount l) { return m.line < l; });
kak_assert(pos == matches.begin() + pivot or pos->line >= range.end); // We should not have had matches for range
RegexMatchList& matches;
const Regex& regex;
size_t pivot = matches.size();
ThreadedRegexVM<const char*, RegexMode::Forward | RegexMode::Search> vm{*regex.impl()};
};

// Move new matches into position.
std::rotate(pos, matches.begin() + pivot, matches.end());
}
}
const Buffer& m_buffer;
Vector<Matcher> m_matchers;
};

void update_changed_lines(const Buffer& buffer, ConstArrayView<LineModification> modifs, Cache& cache)
{
Expand Down Expand Up @@ -2299,7 +2307,6 @@ struct RegionsHighlighter : public Highlighter
}
}


bool update_matches(Cache& cache, const Buffer& buffer, LineRange range)
{
const size_t buffer_timestamp = buffer.timestamp();
Expand All @@ -2315,7 +2322,7 @@ struct RegionsHighlighter : public Highlighter
add_regex(region->m_recurse, region->match_capture());
}

add_matches(buffer, range, cache);
MatchAdder{*this, buffer, cache}.add(range);
cache.ranges.reset(range);
cache.buffer_timestamp = buffer_timestamp;
cache.regions_timestamp = m_regions_timestamp;
Expand All @@ -2333,10 +2340,11 @@ struct RegionsHighlighter : public Highlighter
modified = true;
}

cache.ranges.add_range(range, [&, this](const LineRange& range) {
MatchAdder matches{*this, buffer, cache};
cache.ranges.add_range(range, [&](const LineRange& range) {
if (range.begin == range.end)
return;
add_matches(buffer, range, cache);
matches.add(range);
modified = true;
});
return modified;
Expand Down
13 changes: 7 additions & 6 deletions src/line_modification.cc
Original file line number Diff line number Diff line change
Expand Up @@ -147,26 +147,27 @@ void LineRangeSet::update(ConstArrayView<LineModification> modifs)

void LineRangeSet::add_range(LineRange range, FunctionRef<void (LineRange)> on_new_range)
{
auto it = std::lower_bound(begin(), end(), range.begin,
[](LineRange range, LineCount line) { return range.end < line; });
if (it == end() or it->begin > range.end)
auto insert_at = std::lower_bound(begin(), end(), range.begin,
[](LineRange range, LineCount line) { return range.end < line; });
if (insert_at == end() or insert_at->begin > range.end)
on_new_range(range);
else
{
auto pos = range.begin;
while (it != end() and it->begin <= range.end)
auto it = insert_at;
for (; it != end() and it->begin <= range.end; ++it)
{
if (pos < it->begin)
on_new_range({pos, it->begin});

range = LineRange{std::min(range.begin, it->begin), std::max(range.end, it->end)};
pos = it->end;
it = erase(it);
}
insert_at = erase(insert_at, it);
if (pos < range.end)
on_new_range({pos, range.end});
}
insert(it, range);
insert(insert_at, range);
}

void LineRangeSet::remove_range(LineRange range)
Expand Down