Refactor RegionsHighlighters to define each region as a separate command

This commit is contained in:
Maxime Coste 2018-06-28 19:50:23 +10:00
parent c9cdae4364
commit b0ccf97b44
4 changed files with 217 additions and 138 deletions

View File

@ -136,17 +136,15 @@ evaluate-commands %sh{
fi
printf %s\\n '
add-highlighter shared/FT regions -default code -match-capture \
string %{MAYBEAT(?<!QUOTE)(?<!QUOTE\\)"} %{(?<!\\)(?:\\\\)*"} "" \
string %{R"([^(]*)\(} %{\)([^")]*)"} "" \
comment /\* \*/ "" \
comment // $ "" \
disabled ^\h*?#\h*if\h+(?:0|FALSE)\b "#\h*(?:else|elif|endif)" "#\h*if(?:def)?" \
macro %{^\h*?\K#} %{(?<!\\)\n} ""
add-highlighter shared/FT regions
add-highlighter shared/FT/code default-region group
add-highlighter shared/FT/string region %{MAYBEAT(?<!QUOTE)(?<!QUOTE\\)"} %{(?<!\\)(?:\\\\)*"} "" fill string
add-highlighter shared/FT/raw_string region %{R"([^(]*)\(} %{\)([^")]*)"} "" fill string
add-highlighter shared/FT/comment region /\* \*/ "" fill comment
add-highlighter shared/FT/line_comment region // $ "" fill comment
add-highlighter shared/FT/disabled region ^\h*?#\h*if\h+(?:0|FALSE)\b "#\h*(?:else|elif|endif)" "#\h*if(?:def)?" fill rgb:666666
add-highlighter shared/FT/macro region %{^\h*?\K#} %{(?<!\\)\n} "" group
add-highlighter shared/FT/string/fill fill string
add-highlighter shared/FT/comment/fill fill comment
add-highlighter shared/FT/disabled/fill fill rgb:666666
add-highlighter shared/FT/macro/fill fill meta
add-highlighter shared/FT/macro/include regex ^\h*#include\h+(\S*) 1:module
' | sed -e "s/FT/${ft}/g; s/QUOTE/'/g; s/MAYBEAT/${maybe_at}/;"

View File

@ -11,18 +11,19 @@ hook global BufCreate (.*/)?(kakrc|.*.kak) %{
# Highlighters & Completion
# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
add-highlighter shared/kakrc regions -default code \
comment (^|\h)\K# $ '' \
double_string %{(^|\h)\K"} %{"(?!")} %{(?<!")("")+(?!")} \
single_string %{(^|\h)\K'} %{'(?!')} %{(?<!')('')+(?!')} \
shell '(^|\h)\K%?%sh\{' '\}' '\{' \
shell '(^|\h)\K%?%sh\(' '\)' '\(' \
shell '(^|\h)\K%?%sh\[' '\]' '\[' \
shell '(^|\h)\K%?%sh<' '>' '<' \
shell '(^|\h)\K-shell-(completion|candidates)\h+%\{' '\}' '\{' \
shell '(^|\h)\K-shell-(completion|candidates)\h+%\(' '\)' '\(' \
shell '(^|\h)\K-shell-(completion|candidates)\h+%\[' '\]' '\[' \
shell '(^|\h)\K-shell-(completion|candidates)\h+%<' '>' '<'
add-highlighter shared/kakrc regions
add-highlighter shared/kakrc/code default-region group
add-highlighter shared/kakrc/comment region (^|\h)\K# $ '' fill comment
add-highlighter shared/kakrc/double_string region %{(^|\h)\K"} %{"(?!")} %{(?<!")("")+(?!")} group
add-highlighter shared/kakrc/single_string region %{(^|\h)\K'} %{'(?!')} %{(?<!')('')+(?!')} group
add-highlighter shared/kakrc/shell1 region '(^|\h)\K%?%sh\{' '\}' '\{' ref sh
add-highlighter shared/kakrc/shell2 region '(^|\h)\K%?%sh\(' '\)' '\(' ref sh
add-highlighter shared/kakrc/shell3 region '(^|\h)\K%?%sh\[' '\]' '\[' ref sh
add-highlighter shared/kakrc/shell4 region '(^|\h)\K%?%sh<' '>' '<' ref sh
add-highlighter shared/kakrc/shell5 region '(^|\h)\K-shell-(completion|candidates)\h+%\{' '\}' '\{' ref sh
add-highlighter shared/kakrc/shell6 region '(^|\h)\K-shell-(completion|candidates)\h+%\(' '\)' '\(' ref sh
add-highlighter shared/kakrc/shell7 region '(^|\h)\K-shell-(completion|candidates)\h+%\[' '\]' '\[' ref sh
add-highlighter shared/kakrc/shell8 region '(^|\h)\K-shell-(completion|candidates)\h+%<' '>' '<' ref sh
evaluate-commands %sh{
# Grammar
@ -61,8 +62,6 @@ add-highlighter shared/kakrc/double_string/fill fill string
add-highlighter shared/kakrc/double_string/escape regex '""' 0:default+b
add-highlighter shared/kakrc/single_string/fill fill string
add-highlighter shared/kakrc/single_string/escape regex "''" 0:default+b
add-highlighter shared/kakrc/comment/fill fill comment
add-highlighter shared/kakrc/shell/sh ref sh
# Commands
# ‾‾‾‾‾‾‾‾

View File

@ -2,32 +2,32 @@ hook global BufCreate .*\.(z|ba|c|k|mk)?sh(rc|_profile)? %{
set-option buffer filetype sh
}
add-highlighter shared/sh regions -default code -match-capture \
double_string %{(?<!\\)(?:\\\\)*\K"} %{(?<!\\)(?:\\\\)*"} '' \
single_string %{(?<!\\)(?:\\\\)*\K'} %{'} '' \
comment '(?<!\$)#' '$' '' \
heredoc '<<-?(\w+)' '^\t*(\w+)$' ''
add-highlighter shared/sh regions
add-highlighter shared/sh/code default-region group
add-highlighter shared/sh/double_string region %{(?<!\\)(?:\\\\)*\K"} %{(?<!\\)(?:\\\\)*"} '' group
add-highlighter shared/sh/single_string region %{(?<!\\)(?:\\\\)*\K'} %{'} '' fill string
add-highlighter shared/sh/comment region '(?<!\$)#' '$' '' fill comment
add-highlighter shared/sh/heredoc region -match-capture '<<-?(\w+)' '^\t*(\w+)$' '' fill string
add-highlighter shared/sh/double_string/fill fill string
add-highlighter shared/sh/single_string/fill fill string
add-highlighter shared/sh/comment/fill fill comment
add-highlighter shared/sh/heredoc/fill fill string
evaluate-commands %sh{
# Grammar
keywords="alias|bind|builtin|caller|case|cd|command|coproc|declare|do|done"
keywords="${keywords}|echo|elif|else|enable|esac|exit|fi|for|function|help"
keywords="${keywords}|if|in|let|local|logout|mapfile|printf|read|readarray"
keywords="${keywords}|readonly|return|select|set|shift|source|test|then"
keywords="${keywords}|time|type|typeset|ulimit|unalias|until|while|break|continue"
keywords="alias bind builtin caller case cd command coproc declare do done
echo elif else enable esac exit fi for function help
if in let local logout mapfile printf read readarray
readonly return select set shift source test then
time type typeset ulimit unalias until while break continue"
join() { printf "%s" "$1" | tr -s ' \n' "$2"; }
# Add the language's grammar to the static completion list
printf %s\\n "hook global WinSetOption filetype=sh %{
set-option window static_words ${keywords}
}" | tr '|' ' '
set-option window static_words $(join "${keywords}" ' ')
}"
# Highlight keywords
printf %s "add-highlighter shared/sh/code/keywords regex \b(${keywords})\b 0:keyword"
printf %s "add-highlighter shared/sh/code/keywords regex \b($(join ${keywords} '|'))\b 0:keyword"
}
add-highlighter shared/sh/code/operators regex [\[\]\(\)&|]{1,2} 0:operator

View File

@ -1728,60 +1728,17 @@ struct RegionMatches
}
};
struct RegionDesc
{
String m_name;
Regex m_begin;
Regex m_end;
Regex m_recurse;
bool m_match_capture;
RegionMatches find_matches(const Buffer& buffer) const
{
RegionMatches res;
Kakoune::find_matches(buffer, res.begin_matches, m_begin, m_match_capture);
Kakoune::find_matches(buffer, res.end_matches, m_end, m_match_capture);
if (not m_recurse.empty())
Kakoune::find_matches(buffer, res.recurse_matches, m_recurse, m_match_capture);
return res;
}
void update_matches(const Buffer& buffer,
ConstArrayView<LineModification> modifs,
RegionMatches& matches) const
{
Kakoune::update_matches(buffer, modifs, matches.begin_matches, m_begin, m_match_capture);
Kakoune::update_matches(buffer, modifs, matches.end_matches, m_end, m_match_capture);
if (not m_recurse.empty())
Kakoune::update_matches(buffer, modifs, matches.recurse_matches, m_recurse, m_match_capture);
}
};
struct RegionsHighlighter : public Highlighter
{
public:
using RegionDescList = Vector<RegionDesc, MemoryDomain::Highlight>;
RegionsHighlighter(RegionDescList regions, String default_group)
: Highlighter{HighlightPass::Colorize},
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)
{
m_groups.insert({region.m_name, HighlighterGroup{HighlightPass::Colorize}});
if (region.m_begin.empty() or region.m_end.empty())
throw runtime_error("invalid regex for region highlighter");
}
if (not m_default_group.empty())
m_groups.insert({m_default_group, HighlighterGroup{HighlightPass::Colorize}});
}
RegionsHighlighter()
: Highlighter{HighlightPass::Colorize} {}
void do_highlight(HighlightContext context, DisplayBuffer& display_buffer, BufferRange range) override
{
if (m_regions.empty())
return;
auto display_range = display_buffer.range();
const auto& buffer = context.context.buffer();
auto& regions = get_regions_for_range(buffer, range);
@ -1796,8 +1753,8 @@ public:
return c;
};
auto default_group_it = m_groups.find(m_default_group);
const bool apply_default = default_group_it != m_groups.end();
auto default_region_it = m_regions.find(m_default_region);
const bool apply_default = default_region_it != m_regions.end();
auto last_begin = (begin == regions.begin()) ?
BufferCoord{0,0} : (begin-1)->end;
@ -1807,20 +1764,20 @@ public:
if (apply_default and last_begin < begin->begin)
apply_highlighter(context, display_buffer,
correct(last_begin), correct(begin->begin),
default_group_it->value);
*default_region_it->value);
auto it = m_groups.find(begin->group);
if (it == m_groups.end())
auto it = m_regions.find(begin->region);
if (it == m_regions.end())
continue;
apply_highlighter(context, display_buffer,
correct(begin->begin), correct(begin->end),
it->value);
*it->value);
last_begin = begin->end;
}
if (apply_default and last_begin < display_range.end)
apply_highlighter(context, display_buffer,
correct(last_begin), range.end,
default_group_it->value);
*default_region_it->value);
}
bool has_children() const override { return true; }
@ -1829,13 +1786,31 @@ public:
{
auto sep_it = find(path, '/');
StringView id(path.begin(), sep_it);
auto it = m_groups.find(id);
if (it == m_groups.end())
auto it = m_regions.find(id);
if (it == m_regions.end())
throw child_not_found(format("no such id: {}", id));
if (sep_it == path.end())
return it->value;
return *it->value;
else
return it->value.get_child({sep_it+1, path.end()});
return it->value->get_child({sep_it+1, path.end()});
}
void add_child(String name, std::unique_ptr<Highlighter>&& hl) override
{
if (not dynamic_cast<RegionHighlighter*>(hl.get()))
throw runtime_error{"only region highlighter can be added as child of a regions highlighter"};
if (m_regions.contains(name))
throw runtime_error{format("duplicate id: '{}'", name)};
std::unique_ptr<RegionHighlighter> region_hl{dynamic_cast<RegionHighlighter*>(hl.release())};
if (region_hl->is_default())
{
if (not m_default_region.empty())
throw runtime_error{"default region already defined"};
m_default_region = name;
}
m_regions.insert({std::move(name), std::move(region_hl)});
}
Completions complete_child(StringView path, ByteCount cursor_pos, bool group) const override
@ -1848,51 +1823,146 @@ public:
return offset_pos(hl.complete_child(path.substr(offset), cursor_pos - offset, group), offset);
}
auto container = m_groups | transform(&decltype(m_groups)::Item::key);
auto container = m_regions | transform(&decltype(m_regions)::Item::key);
return { 0, 0, complete(path, cursor_pos, container) };
}
static std::unique_ptr<Highlighter> create(HighlighterParameters params)
{
if (not params.empty())
throw runtime_error{"unexpected parameters"};
return std::make_unique<RegionsHighlighter>();
}
static std::unique_ptr<Highlighter> create_region(HighlighterParameters params)
{
static const ParameterDesc param_desc{
{ { "default", { true, "" } }, { "match-capture", { false, "" } } },
ParameterDesc::Flags::SwitchesOnlyAtStart, 5
{ { "match-capture", { false, "" } } },
ParameterDesc::Flags::SwitchesOnlyAtStart, 4
};
ParametersParser parser{params, param_desc};
if ((parser.positional_count() % 4) != 0)
throw runtime_error("wrong parameter count, expected (<group name> <begin> <end> <recurse>)+");
const bool match_capture = (bool)parser.get_switch("match-capture");
RegionsHighlighter::RegionDescList regions;
for (size_t i = 0; 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");
if (parser[0].empty() or parser[1].empty())
throw runtime_error("begin and end must not be empty");
const RegexCompileFlags flags = match_capture ?
RegexCompileFlags::Optimize : RegexCompileFlags::NoSubs | RegexCompileFlags::Optimize;
regions.push_back({ parser[i],
Regex{parser[i+1], flags}, Regex{parser[i+2], flags},
parser[i+3].empty() ? Regex{} : Regex{parser[i+3], flags},
match_capture });
auto delegate = HighlighterRegistry::instance()[parser[3]].factory(parser.positionals_from(4));
return std::make_unique<RegionHighlighter>(std::move(delegate), Regex{parser[0], flags}, Regex{parser[1], flags}, parser[2].empty() ? Regex{} : Regex{parser[2], flags}, match_capture);
}
auto default_group = parser.get_switch("default").value_or(StringView{}).str();
return std::make_unique<RegionsHighlighter>(std::move(regions), default_group);
static std::unique_ptr<Highlighter> create_default_region(HighlighterParameters params)
{
static const ParameterDesc param_desc{ {}, ParameterDesc::Flags::SwitchesOnlyAtStart, 1 };
ParametersParser parser{params, param_desc};
auto delegate = HighlighterRegistry::instance()[parser[0]].factory(parser.positionals_from(1));
return std::make_unique<RegionHighlighter>(std::move(delegate));
}
private:
const RegionDescList m_regions;
const String m_default_group;
HashMap<String, HighlighterGroup, MemoryDomain::Highlight> m_groups;
struct RegionHighlighter : public Highlighter
{
RegionHighlighter(std::unique_ptr<Highlighter>&& delegate,
Regex begin, Regex end, Regex recurse,
bool match_capture)
: Highlighter{delegate->passes()},
m_delegate{std::move(delegate)},
m_begin{std::move(begin)}, m_end{std::move(end)}, m_recurse{std::move(recurse)},
m_match_capture{match_capture}
{
}
RegionHighlighter(std::unique_ptr<Highlighter>&& delegate)
: Highlighter{delegate->passes()}, m_delegate{std::move(delegate)}, m_default{true}
{
}
bool has_children() const override
{
return m_delegate->has_children();
}
Highlighter& get_child(StringView path) override
{
return m_delegate->get_child(path);
}
void add_child(String name, std::unique_ptr<Highlighter>&& hl) override
{
return m_delegate->add_child(name, std::move(hl));
}
void remove_child(StringView id) override
{
return m_delegate->remove_child(id);
}
Completions complete_child(StringView path, ByteCount cursor_pos, bool group) const override
{
return m_delegate->complete_child(path, cursor_pos, group);
}
void fill_unique_ids(Vector<StringView>& unique_ids) const override
{
return m_delegate->fill_unique_ids(unique_ids);
}
void do_highlight(HighlightContext context, DisplayBuffer& display_buffer, BufferRange range) override
{
return m_delegate->highlight(context, display_buffer, range);
}
RegionMatches find_matches(const Buffer& buffer) const
{
RegionMatches res;
if (m_default)
return res;
Kakoune::find_matches(buffer, res.begin_matches, m_begin, m_match_capture);
Kakoune::find_matches(buffer, res.end_matches, m_end, m_match_capture);
if (not m_recurse.empty())
Kakoune::find_matches(buffer, res.recurse_matches, m_recurse, m_match_capture);
return res;
}
void update_matches(const Buffer& buffer,
ConstArrayView<LineModification> modifs,
RegionMatches& matches) const
{
if (m_default)
return;
Kakoune::update_matches(buffer, modifs, matches.begin_matches, m_begin, m_match_capture);
Kakoune::update_matches(buffer, modifs, matches.end_matches, m_end, m_match_capture);
if (not m_recurse.empty())
Kakoune::update_matches(buffer, modifs, matches.recurse_matches, m_recurse, m_match_capture);
}
bool match_capture() const { return m_match_capture; }
bool is_default() const { return m_default; }
private:
std::unique_ptr<Highlighter> m_delegate;
Regex m_begin;
Regex m_end;
Regex m_recurse;
bool m_match_capture = false;
bool m_default = false;
};
HashMap<String, std::unique_ptr<RegionHighlighter>, MemoryDomain::Highlight> m_regions;
String m_default_region;
struct Region
{
BufferCoord begin;
BufferCoord end;
StringView group;
StringView region;
};
using RegionList = Vector<Region, MemoryDomain::Highlight>;
@ -1932,13 +2002,13 @@ private:
{
cache.matches.resize(m_regions.size());
for (size_t i = 0; i < m_regions.size(); ++i)
cache.matches[i] = m_regions[i].find_matches(buffer);
cache.matches[i] = m_regions.item(i).value->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].update_matches(buffer, modifs, cache.matches[i]);
m_regions.item(i).value->update_matches(buffer, modifs, cache.matches[i]);
}
cache.regions.clear();
@ -1955,24 +2025,23 @@ private:
begin != end; )
{
const RegionMatches& matches = cache.matches[begin.first];
auto& region = m_regions[begin.first];
auto& region = m_regions.item(begin.first);
auto beg_it = begin.second;
auto end_it = matches.find_matching_end(beg_it->end_coord(),
region.m_match_capture ? beg_it->capture
: Optional<StringView>{});
region.value->match_capture() ? beg_it->capture : Optional<StringView>{});
if (end_it == matches.end_matches.end() or end_it->end_coord() >= range.end)
{
regions.push_back({ {beg_it->line, beg_it->begin},
range.end,
region.m_name });
region.key });
break;
}
else
{
regions.push_back({ beg_it->begin_coord(),
end_it->end_coord(),
region.m_name });
region.key });
auto end_coord = end_it->end_coord();
// With empty begin and end matches (for example if the regexes
@ -2034,8 +2103,8 @@ void register_highlighters()
registry.insert({
"group",
{ create_highlighter_group,
"Parameters: [-passes <passes>] <group name>\n"
"Creates a named group that can contain other highlighters,\n"
"Parameters: [-passes <passes>]\n"
"Creates a group that can contain other highlighters,\n"
"<passes> is a flags(colorize|move|wrap) defaulting to colorize\n"
"which specify what kind of highlighters can be put in the group" } });
registry.insert({
@ -2083,13 +2152,26 @@ void register_highlighters()
registry.insert({
"regions",
{ RegionsHighlighter::create,
"Parameters: [-default <default group>] [-match-capture] <name> {<region name> <begin> <end> <recurse>}..."
"Split the highlighting into regions defined by the <begin>, <end> and <recurse> regex\n"
"The region <region name> starts at <begin> match, end at <end> match that does not\n"
"close a <recurse> match. In between region is the <default group>.\n"
"Highlighting a region is done by adding highlighters into the different <region name> subgroups.\n"
"If -match-capture is specified, then regions end/recurse matches must have the same \\1\n"
"capture content as the begin to be considered"} });
"Parameters: None"
"Holds child region highlighters and segments the buffer in ranges based on those regions\n"
"definitions. The regions highlighter finds the next region to start by finding which\n"
"of its child region has the leftmost starting point from current position. In between\n"
"regions, the default-region child highlighter is applied (if such a child exists)" } });
registry.insert({
"region",
{ RegionsHighlighter::create_region,
"Parameters: [-match-capture] <begin> <end> <recurse> <type> <params>...\n."
"Define a region for a regions highlighter, and apply the given delegate\n"
"highlighter as defined by <type> and eventual <params>...\n"
"The region starts at <begin> match and ends at the first <end> match that\n"
"does not close a <recurse> match.\n"
"If -match-capture is specified, then regions end/recurse matches must have\n"
"the same \\1 capture content as the begin match to be considered"} });
registry.insert({
"default-region",
{ RegionsHighlighter::create_default_region,
"Parameters: <delegate_type> <delegate_params>...\n"
"Define the default region of a regions highlighter" } });
}
}