Change faces alias to be a base that can be modified
Using <fg>,<bg>+<attr>@<base> will apply the given fg color, bg color and attributes on top of base dynamically. Simply giving <base> is a shorthand for default,default@<base>. Inspired by the discussion in #2862
This commit is contained in:
parent
17c5e7aa5f
commit
4e24ba86cc
|
@ -6,9 +6,9 @@ A 'face' refers how the specified text is displayed, it has a foreground
|
||||||
color, a background color, and some attributes. The value of a face has the
|
color, a background color, and some attributes. The value of a face has the
|
||||||
following format:
|
following format:
|
||||||
|
|
||||||
--------------------------------
|
---------------------------------------
|
||||||
fg_color[,bg_color][+attributes]
|
fg_color[,bg_color][+attributes][@base]
|
||||||
--------------------------------
|
---------------------------------------
|
||||||
|
|
||||||
'fg_color', 'bg_color'::
|
'fg_color', 'bg_color'::
|
||||||
a color whose value can be expressed in the following formats:
|
a color whose value can be expressed in the following formats:
|
||||||
|
@ -49,6 +49,11 @@ fg_color[,bg_color][+attributes]
|
||||||
final attributes, as final but only applies to face's
|
final attributes, as final but only applies to face's
|
||||||
attributes
|
attributes
|
||||||
|
|
||||||
|
'base'::
|
||||||
|
The base face on which this face applies, which can be any face name,
|
||||||
|
as long as a cycle is not introduced. A face can reference itself, in
|
||||||
|
which case it will apply on top of the parent scope version.
|
||||||
|
|
||||||
== Builtin faces
|
== Builtin faces
|
||||||
|
|
||||||
The following default faces are used by color schemes to highlight certain
|
The following default faces are used by color schemes to highlight certain
|
||||||
|
|
|
@ -7,43 +7,51 @@
|
||||||
namespace Kakoune
|
namespace Kakoune
|
||||||
{
|
{
|
||||||
|
|
||||||
static Face parse_face(StringView facedesc)
|
static FaceRegistry::FaceSpec parse_face(StringView facedesc)
|
||||||
{
|
{
|
||||||
constexpr StringView invalid_face_error = "invalid face description, expected <fg>[,<bg>][+<attr>]";
|
constexpr StringView invalid_face_error = "invalid face description, expected <fg>[,<bg>][+<attr>][@base] or just [base]";
|
||||||
|
if (all_of(facedesc, [](char c){ return is_word(c); }) and not is_color_name(facedesc))
|
||||||
|
return {Face{}, facedesc.str()};
|
||||||
|
|
||||||
auto bg_it = find(facedesc, ',');
|
auto bg_it = find(facedesc, ',');
|
||||||
auto attr_it = find(facedesc, '+');
|
auto attr_it = find(facedesc, '+');
|
||||||
|
auto base_it = find(facedesc, '@');
|
||||||
if (bg_it != facedesc.end()
|
if (bg_it != facedesc.end()
|
||||||
and (attr_it < bg_it or (bg_it + 1) == facedesc.end()))
|
and (attr_it < bg_it or (bg_it + 1) == facedesc.end()))
|
||||||
throw runtime_error(invalid_face_error.str());
|
throw runtime_error(invalid_face_error.str());
|
||||||
if (attr_it != facedesc.end()
|
if (attr_it != facedesc.end()
|
||||||
and (attr_it + 1) == facedesc.end())
|
and (attr_it + 1) == facedesc.end())
|
||||||
throw runtime_error(invalid_face_error.str());
|
throw runtime_error(invalid_face_error.str());
|
||||||
Face res;
|
|
||||||
res.fg = attr_it != facedesc.begin() ?
|
FaceRegistry::FaceSpec spec;
|
||||||
|
auto& face = spec.face;
|
||||||
|
face.fg = attr_it != facedesc.begin() ?
|
||||||
str_to_color({facedesc.begin(), std::min(attr_it, bg_it)}) : Color::Default;
|
str_to_color({facedesc.begin(), std::min(attr_it, bg_it)}) : Color::Default;
|
||||||
if (bg_it != facedesc.end())
|
if (bg_it != facedesc.end())
|
||||||
res.bg = bg_it+1 != attr_it ? str_to_color({bg_it+1, attr_it}) : Color::Default;
|
face.bg = bg_it+1 != attr_it ? str_to_color({bg_it+1, attr_it}) : Color::Default;
|
||||||
if (attr_it != facedesc.end())
|
if (attr_it != facedesc.end())
|
||||||
{
|
{
|
||||||
for (++attr_it; attr_it != facedesc.end(); ++attr_it)
|
for (++attr_it; attr_it != base_it; ++attr_it)
|
||||||
{
|
{
|
||||||
switch (*attr_it)
|
switch (*attr_it)
|
||||||
{
|
{
|
||||||
case 'u': res.attributes |= Attribute::Underline; break;
|
case 'u': face.attributes |= Attribute::Underline; break;
|
||||||
case 'r': res.attributes |= Attribute::Reverse; break;
|
case 'r': face.attributes |= Attribute::Reverse; break;
|
||||||
case 'b': res.attributes |= Attribute::Bold; break;
|
case 'b': face.attributes |= Attribute::Bold; break;
|
||||||
case 'B': res.attributes |= Attribute::Blink; break;
|
case 'B': face.attributes |= Attribute::Blink; break;
|
||||||
case 'd': res.attributes |= Attribute::Dim; break;
|
case 'd': face.attributes |= Attribute::Dim; break;
|
||||||
case 'i': res.attributes |= Attribute::Italic; break;
|
case 'i': face.attributes |= Attribute::Italic; break;
|
||||||
case 'f': res.attributes |= Attribute::FinalFg; break;
|
case 'f': face.attributes |= Attribute::FinalFg; break;
|
||||||
case 'g': res.attributes |= Attribute::FinalBg; break;
|
case 'g': face.attributes |= Attribute::FinalBg; break;
|
||||||
case 'a': res.attributes |= Attribute::FinalAttr; break;
|
case 'a': face.attributes |= Attribute::FinalAttr; break;
|
||||||
case 'F': res.attributes |= Attribute::Final; break;
|
case 'F': face.attributes |= Attribute::Final; break;
|
||||||
default: throw runtime_error(format("no such face attribute: '{}'", StringView{*attr_it}));
|
default: throw runtime_error(format("no such face attribute: '{}'", StringView{*attr_it}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return res;
|
if (base_it != facedesc.end())
|
||||||
|
spec.base = String{base_it+1, facedesc.end()};
|
||||||
|
return spec;
|
||||||
}
|
}
|
||||||
|
|
||||||
String to_string(Attribute attributes)
|
String to_string(Attribute attributes)
|
||||||
|
@ -79,16 +87,33 @@ String to_string(Face face)
|
||||||
|
|
||||||
Face FaceRegistry::operator[](StringView facedesc) const
|
Face FaceRegistry::operator[](StringView facedesc) const
|
||||||
{
|
{
|
||||||
auto it = m_faces.find(facedesc);
|
return resolve_spec(parse_face(facedesc));
|
||||||
if (it != m_faces.end())
|
|
||||||
{
|
|
||||||
if (it->value.alias.empty())
|
|
||||||
return it->value.face;
|
|
||||||
return operator[](it->value.alias);
|
|
||||||
}
|
}
|
||||||
if (m_parent)
|
|
||||||
return (*m_parent)[facedesc];
|
Face FaceRegistry::resolve_spec(const FaceSpec& spec) const
|
||||||
return parse_face(facedesc);
|
{
|
||||||
|
if (spec.base.empty())
|
||||||
|
return spec.face;
|
||||||
|
|
||||||
|
StringView base = spec.base;
|
||||||
|
Face face = spec.face;
|
||||||
|
for (auto* reg = this; reg != nullptr; reg = reg->m_parent.get())
|
||||||
|
{
|
||||||
|
auto it = reg->m_faces.find(base);
|
||||||
|
if (it == reg->m_faces.end())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (it->value.base.empty())
|
||||||
|
return merge_faces(it->value.face, face);
|
||||||
|
if (it->value.base != it->key)
|
||||||
|
return merge_faces(reg->resolve_spec(it->value), face);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
face = merge_faces(it->value.face, face);
|
||||||
|
base = it->value.base;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return face;
|
||||||
}
|
}
|
||||||
|
|
||||||
void FaceRegistry::add_face(StringView name, StringView facedesc, bool override)
|
void FaceRegistry::add_face(StringView name, StringView facedesc, bool override)
|
||||||
|
@ -97,33 +122,25 @@ void FaceRegistry::add_face(StringView name, StringView facedesc, bool override)
|
||||||
throw runtime_error(format("face '{}' already defined", name));
|
throw runtime_error(format("face '{}' already defined", name));
|
||||||
|
|
||||||
if (name.empty() or is_color_name(name) or
|
if (name.empty() or is_color_name(name) or
|
||||||
std::any_of(name.begin(), name.end(),
|
any_of(name, [](char c){ return not is_word(c); }))
|
||||||
[](char c){ return not is_word(c); }))
|
|
||||||
throw runtime_error(format("invalid face name: '{}'", name));
|
throw runtime_error(format("invalid face name: '{}'", name));
|
||||||
|
|
||||||
if (name == facedesc)
|
FaceSpec spec = parse_face(facedesc);
|
||||||
throw runtime_error(format("cannot alias face '{}' to itself", name));
|
auto it = m_faces.find(spec.base);
|
||||||
|
if (spec.base == name and it != m_faces.end())
|
||||||
for (auto it = m_faces.find(facedesc);
|
|
||||||
it != m_faces.end() and not it->value.alias.empty();
|
|
||||||
it = m_faces.find(it->value.alias))
|
|
||||||
{
|
{
|
||||||
if (it->value.alias == name)
|
it->value.face = merge_faces(it->value.face, spec.face);
|
||||||
throw runtime_error("face cycle detected");
|
it->value.base = spec.base;
|
||||||
}
|
|
||||||
|
|
||||||
FaceOrAlias& face = m_faces[name];
|
|
||||||
|
|
||||||
for (auto* registry = this; registry != nullptr; registry = registry->m_parent.get())
|
|
||||||
{
|
|
||||||
if (not registry->m_faces.contains(facedesc))
|
|
||||||
continue;
|
|
||||||
face.alias = facedesc.str(); // This is referencing another face
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
face.alias = "";
|
while (it != m_faces.end() and not it->value.base.empty())
|
||||||
face.face = parse_face(facedesc);
|
{
|
||||||
|
if (it->value.base == name)
|
||||||
|
throw runtime_error("face cycle detected");
|
||||||
|
it = m_faces.find(it->value.base);
|
||||||
|
}
|
||||||
|
m_faces[name] = std::move(spec);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FaceRegistry::remove_face(StringView name)
|
void FaceRegistry::remove_face(StringView name)
|
||||||
|
|
|
@ -20,12 +20,12 @@ public:
|
||||||
void add_face(StringView name, StringView facedesc, bool override = false);
|
void add_face(StringView name, StringView facedesc, bool override = false);
|
||||||
void remove_face(StringView name);
|
void remove_face(StringView name);
|
||||||
|
|
||||||
struct FaceOrAlias
|
struct FaceSpec
|
||||||
{
|
{
|
||||||
Face face = {};
|
Face face = {};
|
||||||
String alias = {};
|
String base = {};
|
||||||
};
|
};
|
||||||
using FaceMap = HashMap<String, FaceOrAlias, MemoryDomain::Faces>;
|
using FaceMap = HashMap<String, FaceSpec, MemoryDomain::Faces>;
|
||||||
|
|
||||||
auto flatten_faces() const
|
auto flatten_faces() const
|
||||||
{
|
{
|
||||||
|
@ -41,6 +41,8 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
Face resolve_spec(const FaceSpec& spec) const;
|
||||||
|
|
||||||
friend class Scope;
|
friend class Scope;
|
||||||
FaceRegistry();
|
FaceRegistry();
|
||||||
|
|
||||||
|
|
|
@ -78,7 +78,7 @@ static uint32_t compute_faces_hash(const FaceRegistry& faces)
|
||||||
{
|
{
|
||||||
uint32_t hash = 0;
|
uint32_t hash = 0;
|
||||||
for (auto&& face : faces.flatten_faces() | transform(&FaceRegistry::FaceMap::Item::value))
|
for (auto&& face : faces.flatten_faces() | transform(&FaceRegistry::FaceMap::Item::value))
|
||||||
hash = combine_hash(hash, face.alias.empty() ? hash_value(face.face) : hash_value(face.alias));
|
hash = combine_hash(hash, face.base.empty() ? hash_value(face.face) : hash_value(face.base));
|
||||||
return hash;
|
return hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
1
test/highlight/face-override/cmd
Normal file
1
test/highlight/face-override/cmd
Normal file
|
@ -0,0 +1 @@
|
||||||
|
|
1
test/highlight/face-override/in
Normal file
1
test/highlight/face-override/in
Normal file
|
@ -0,0 +1 @@
|
||||||
|
foo
|
3
test/highlight/face-override/rc
Normal file
3
test/highlight/face-override/rc
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
face global Foo default,blue+i
|
||||||
|
face buffer Foo red,default+u@Foo
|
||||||
|
add-highlighter window/ regex foo 0:Foo
|
7
test/highlight/face-override/ui-out
Normal file
7
test/highlight/face-override/ui-out
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
{ "jsonrpc": "2.0", "method": "set_ui_options", "params": [{}] }
|
||||||
|
{ "jsonrpc": "2.0", "method": "draw", "params": [[[{ "face": { "fg": "black", "bg": "white", "attributes": ["underline","italic"] }, "contents": "f" }, { "face": { "fg": "red", "bg": "blue", "attributes": ["underline","italic"] }, "contents": "oo" }, { "face": { "fg": "default", "bg": "default", "attributes": [] }, "contents": "\u000a" }]], { "fg": "default", "bg": "default", "attributes": [] }, { "fg": "blue", "bg": "default", "attributes": [] }] }
|
||||||
|
{ "jsonrpc": "2.0", "method": "menu_hide", "params": [] }
|
||||||
|
{ "jsonrpc": "2.0", "method": "info_hide", "params": [] }
|
||||||
|
{ "jsonrpc": "2.0", "method": "draw_status", "params": [[], [{ "face": { "fg": "default", "bg": "default", "attributes": [] }, "contents": "out 1:1 " }, { "face": { "fg": "black", "bg": "yellow", "attributes": [] }, "contents": "" }, { "face": { "fg": "default", "bg": "default", "attributes": [] }, "contents": " " }, { "face": { "fg": "blue", "bg": "default", "attributes": [] }, "contents": "1 sel" }, { "face": { "fg": "default", "bg": "default", "attributes": [] }, "contents": " - client0@[kak-tests]" }], { "fg": "cyan", "bg": "default", "attributes": [] }] }
|
||||||
|
{ "jsonrpc": "2.0", "method": "set_cursor", "params": ["buffer", { "line": 0, "column": 0 }] }
|
||||||
|
{ "jsonrpc": "2.0", "method": "refresh", "params": [true] }
|
Loading…
Reference in New Issue
Block a user