Add MultiRegionHighlighter

MultiRegionHighlighter provides a way to segment the buffer in
logical regions using the common left-most rule for finding the
next region. It then provides highlighting groups for each region
that can be filled.
This commit is contained in:
Maxime Coste 2014-06-11 23:29:23 +01:00
parent 0faf7ff5e6
commit dac5e89e14
3 changed files with 224 additions and 24 deletions

View File

@ -40,19 +40,21 @@ def -hidden _cpp_indent_on_closing_curly_brace %[
] ]
defhl cpp defhl cpp
addhl -def-group cpp regex "\<(this|true|false|NULL|nullptr|)\>|\<-?\d+[fdiu]?|'((\\.)?|[^'\\])'" 0:value
addhl -def-group cpp regex "\<(void|int|char|unsigned|float|bool|size_t)\>" 0:type
addhl -def-group cpp regex "\<(while|for|if|else|do|switch|case|default|goto|break|continue|return|using|try|catch|throw|new|delete|and|or|not|operator|explicit)\>" 0:keyword
addhl -def-group cpp regex "\<(const|mutable|auto|namespace|inline|static|volatile|class|struct|enum|union|public|protected|private|template|typedef|virtual|friend|extern|typename|override|final)\>" 0:attribute
addhl -def-group cpp regex "^\h*?#.*?(?<!\\)$" 0:macro
addhl -def-group cpp region string %{(?<!')"} %{(?<!\\)(\\\\)*"} addhl -def-group cpp multi_region -default code root \
addhl -def-group cpp/string/content fill string string %{(?<!')"} %{(?<!\\)(\\\\)*"} '' \
comment /\* \*/ ''
addhl -def-group cpp region comment /\* \*/ addhl -def-group cpp/root/string fill string
addhl -def-group cpp/comment/content fill comment addhl -def-group cpp/root/comment fill comment
addhl -def-group cpp regex "(//[^\n]*\n)" 0:comment addhl -def-group cpp/root/code regex "\<(this|true|false|NULL|nullptr|)\>|\<-?\d+[fdiu]?|'((\\.)?|[^'\\])'" 0:value
addhl -def-group cpp/root/code regex "\<(void|int|char|unsigned|float|bool|size_t)\>" 0:type
addhl -def-group cpp/root/code regex "\<(while|for|if|else|do|switch|case|default|goto|break|continue|return|using|try|catch|throw|new|delete|and|or|not|operator|explicit)\>" 0:keyword
addhl -def-group cpp/root/code regex "\<(const|mutable|auto|namespace|inline|static|volatile|class|struct|enum|union|public|protected|private|template|typedef|virtual|friend|extern|typename|override|final)\>" 0:attribute
addhl -def-group cpp/root/code regex "^\h*?#.*?(?<!\\)$" 0:macro
addhl -def-group cpp/root/code regex "(//[^\n]*\n)" 0:comment
hook global WinSetOption filetype=cpp %[ hook global WinSetOption filetype=cpp %[
addhl ref cpp addhl ref cpp

View File

@ -3,22 +3,22 @@ hook global BufCreate (.*/)?(kakrc|.*.kak) %{
} }
defhl kakrc defhl kakrc
addhl -def-group kakrc regex \<(hook|rmhooks|defhl|addhl|rmhl|add|exec|eval|source|runtime|def|decl|echo|edit|set)\> 0:keyword addhl -def-group kakrc multi_region -default kakrc root \
addhl -def-group kakrc regex \<(default|black|red|green|yellow|blue|magenta|cyan|white)\> 0:value comment (^|\h)\K\# \n '' \
addhl -def-group kakrc regex (?<=\<hook)\h+((global|buffer|window)|(\S+))\h+(\S+)\h+(\H+) 2:attribute 3:error 4:identifier 5:string double_string %{(^|\h)"} %{(?<!\\)(\\\\)*"} '' \
addhl -def-group kakrc regex (?<=\<set)\h+((global|buffer|window)|(\S+))\h+(\S+)\h+(\S+) 2:attribute 3:error 4:identifier 5:value single_string %{(^|\h)'} %{(?<!\\)(\\\\)*'} '' \
addhl -def-group kakrc regex (?<=\<regex)\h+(\S+) 1:string shell '%sh\{' '\}' '\{'
addhl -def-group kakrc region double_string %{(^|\h)"} %{(?<!\\)(\\\\)*"} addhl -def-group kakrc/root/kakrc regex \<(hook|rmhooks|defhl|addhl|rmhl|add|exec|eval|source|runtime|def|decl|echo|edit|set)\> 0:keyword
addhl -def-group kakrc/double_string/content fill string addhl -def-group kakrc/root/kakrc regex \<(default|black|red|green|yellow|blue|magenta|cyan|white)\> 0:value
addhl -def-group kakrc/root/kakrc regex (?<=\<hook)\h+((global|buffer|window)|(\S+))\h+(\S+)\h+(\H+) 2:attribute 3:error 4:identifier 5:string
addhl -def-group kakrc/root/kakrc regex (?<=\<set)\h+((global|buffer|window)|(\S+))\h+(\S+)\h+(\S+) 2:attribute 3:error 4:identifier 5:value
addhl -def-group kakrc/root/kakrc regex (?<=\<regex)\h+(\S+) 1:string
addhl -def-group kakrc region single_string %{(^|\h)'} %{(?<!\\)(\\\\)*'} addhl -def-group kakrc/root/double_string fill string
addhl -def-group kakrc/single_string/content fill string addhl -def-group kakrc/root/single_string fill string
addhl -def-group kakrc/root/comment fill comment
addhl -def-group kakrc regex (^|\h)\#[^\n]*\n 0:comment addhl -def-group kakrc/root/shell ref sh
addhl -def-group kakrc region shell '%sh\{' '\}' '\{'
addhl -def-group kakrc/shell/content ref sh
hook global WinSetOption filetype=kak %{ addhl ref kakrc } hook global WinSetOption filetype=kak %{ addhl ref kakrc }
hook global WinSetOption filetype=(?!kak).* %{ rmhl kakrc } hook global WinSetOption filetype=(?!kak).* %{ rmhl kakrc }

View File

@ -12,6 +12,7 @@
#include "string.hh" #include "string.hh"
#include "utf8.hh" #include "utf8.hh"
#include "utf8_iterator.hh" #include "utf8_iterator.hh"
#include "parameters_parser.hh"
#include <sstream> #include <sstream>
#include <locale> #include <locale>
@ -951,6 +952,202 @@ HighlighterAndId region_factory(HighlighterParameters params)
} }
} }
struct MultiRegionHighlighter
{
public:
using NamedRegionDescList = std::vector<std::pair<String, RegionDesc>>;
MultiRegionHighlighter(NamedRegionDescList regions, String default_group)
: m_regions{std::move(regions)}, m_default_group{std::move(default_group)}
{
if (m_regions.empty())
throw runtime_error("at least one region must be defined");
for (auto& region : m_regions)
if (region.second.m_begin.empty() or region.second.m_end.empty())
throw runtime_error("invalid regex for region highlighter");
}
void operator()(HierachicalHighlighter::GroupMap groups, const Context& context,
HighlightFlags flags, DisplayBuffer& display_buffer)
{
if (flags != HighlightFlags::Highlight)
return;
auto range = display_buffer.range();
const auto& buffer = context.buffer();
auto& regions = update_cache_ifn(buffer);
auto begin = std::lower_bound(regions.begin(), regions.end(), range.first,
[](const Region& r, ByteCoord c) { return r.end < c; });
auto end = std::lower_bound(begin, regions.end(), range.second,
[](const Region& r, ByteCoord c) { return r.begin < c; });
auto correct = [&](ByteCoord c) -> ByteCoord {
if (buffer[c.line].length() == c.column)
return {c.line+1, 0};
return c;
};
auto default_group_it = groups.find(m_default_group);
const bool apply_default = default_group_it != groups.end();
auto last_begin = range.first;
for (; begin != end; ++begin)
{
if (apply_default and last_begin < begin->begin)
apply_highlighter(context, flags, display_buffer,
correct(last_begin), correct(begin->begin),
default_group_it->second);
auto it = groups.find(begin->group);
if (it == groups.end())
continue;
apply_highlighter(context, flags, display_buffer,
correct(begin->begin), correct(begin->end),
it->second);
last_begin = begin->end;
}
if (apply_default and last_begin < range.second)
apply_highlighter(context, flags, display_buffer,
correct(last_begin), range.second,
default_group_it->second);
}
private:
const NamedRegionDescList m_regions;
const String m_default_group;
struct Region
{
ByteCoord begin;
ByteCoord end;
StringView group;
};
using RegionList = std::vector<Region>;
struct Cache
{
size_t timestamp = 0;
std::vector<RegionMatches> matches;
RegionList regions;
};
BufferSideCache<Cache> m_cache;
using RegionAndMatch = std::pair<size_t, MatchList::const_iterator>;
// find the begin closest to pos in all matches
RegionAndMatch find_next_begin(const Cache& cache, ByteCoord pos) const
{
RegionAndMatch res{0, cache.matches[0].find_next_begin(pos)};
for (size_t i = 1; i < cache.matches.size(); ++i)
{
const auto& matches = cache.matches[i];
auto it = matches.find_next_begin(pos);
if (it != matches.begin_matches.end() and
(res.second == cache.matches[res.first].begin_matches.end() or
it->begin_coord() < res.second->begin_coord()))
res = RegionAndMatch{i, it};
}
return res;
}
const RegionList& update_cache_ifn(const Buffer& buffer)
{
Cache& cache = m_cache.get(buffer);
const size_t buf_timestamp = buffer.timestamp();
if (cache.timestamp == buf_timestamp)
return cache.regions;
if (cache.timestamp == 0)
{
cache.matches.resize(m_regions.size());
for (size_t i = 0; i < m_regions.size(); ++i)
cache.matches[i] = m_regions[i].second.find_matches(buffer);
}
else
{
auto modifs = compute_line_modifications(buffer, cache.timestamp);
for (size_t i = 0; i < m_regions.size(); ++i)
m_regions[i].second.update_matches(buffer, modifs, cache.matches[i]);
}
cache.regions.clear();
for (auto begin = find_next_begin(cache, ByteCoord{-1, 0}),
end = RegionAndMatch{ 0, cache.matches[0].begin_matches.end() };
begin != end; )
{
const RegionMatches& matches = cache.matches[begin.first];
auto& named_region = m_regions[begin.first];
auto beg_it = begin.second;
auto end_it = matches.find_matching_end(beg_it);
if (end_it == matches.end_matches.end())
{
cache.regions.push_back({ {beg_it->line, beg_it->begin},
buffer.end_coord(),
named_region.first });
break;
}
else
{
cache.regions.push_back({ beg_it->begin_coord(),
end_it->end_coord(),
named_region.first });
begin = find_next_begin(cache, end_it->end_coord());
}
}
cache.timestamp = buf_timestamp;
return cache.regions;
}
};
HighlighterAndId multi_region_factory(HighlighterParameters params)
{
try
{
static const ParameterDesc param_desc{
SwitchMap{ { "default", { true, "" } } },
ParameterDesc::Flags::SwitchesOnlyAtStart,
5};
ParametersParser parser{params, param_desc};
if ((parser.positional_count() % 4) != 1)
throw runtime_error("wrong parameter count, expect <id> (<group name> <begin> <end> <recurse>)+");
MultiRegionHighlighter::NamedRegionDescList regions;
id_map<HighlighterGroup> groups;
for (size_t i = 1; i < parser.positional_count(); i += 4)
{
if (parser[i].empty() or parser[i+1].empty() or parser[i+2].empty())
throw runtime_error("group id, begin and end must not be empty");
Regex begin{parser[i+1], Regex::nosubs | Regex::optimize };
Regex end{parser[i+2], Regex::nosubs | Regex::optimize };
Regex recurse;
if (not parser[i+3].empty())
recurse = Regex{parser[i+3], Regex::nosubs | Regex::optimize };
regions.push_back({ parser[i], {std::move(begin), std::move(end), std::move(recurse)} });
groups.append({ parser[i], HighlighterGroup{} });
}
String default_group;
if (parser.has_option("default"))
{
default_group = parser.option_value("default");
groups.append({ default_group, HighlighterGroup{} });
}
return {parser[0],
HierachicalHighlighter(
MultiRegionHighlighter(std::move(regions), std::move(default_group)), std::move(groups))};
}
catch (boost::regex_error& err)
{
throw runtime_error(String("regex error: ") + err.what());
}
}
} }
void register_highlighters() void register_highlighters()
@ -968,6 +1165,7 @@ void register_highlighters()
registry.register_func("flag_lines", flag_lines_factory); registry.register_func("flag_lines", flag_lines_factory);
registry.register_func("ref", reference_factory); registry.register_func("ref", reference_factory);
registry.register_func("region", RegionHighlight::region_factory); registry.register_func("region", RegionHighlight::region_factory);
registry.register_func("multi_region", RegionHighlight::multi_region_factory);
} }
} }