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
|
||||
following format:
|
||||
|
||||
--------------------------------
|
||||
fg_color[,bg_color][+attributes]
|
||||
--------------------------------
|
||||
---------------------------------------
|
||||
fg_color[,bg_color][+attributes][@base]
|
||||
---------------------------------------
|
||||
|
||||
'fg_color', 'bg_color'::
|
||||
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
|
||||
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
|
||||
|
||||
The following default faces are used by color schemes to highlight certain
|
||||
|
|
|
@ -7,43 +7,51 @@
|
|||
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 attr_it = find(facedesc, '+');
|
||||
auto base_it = find(facedesc, '@');
|
||||
if (bg_it != facedesc.end()
|
||||
and (attr_it < bg_it or (bg_it + 1) == facedesc.end()))
|
||||
throw runtime_error(invalid_face_error.str());
|
||||
if (attr_it != facedesc.end()
|
||||
and (attr_it + 1) == facedesc.end())
|
||||
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;
|
||||
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())
|
||||
{
|
||||
for (++attr_it; attr_it != facedesc.end(); ++attr_it)
|
||||
for (++attr_it; attr_it != base_it; ++attr_it)
|
||||
{
|
||||
switch (*attr_it)
|
||||
{
|
||||
case 'u': res.attributes |= Attribute::Underline; break;
|
||||
case 'r': res.attributes |= Attribute::Reverse; break;
|
||||
case 'b': res.attributes |= Attribute::Bold; break;
|
||||
case 'B': res.attributes |= Attribute::Blink; break;
|
||||
case 'd': res.attributes |= Attribute::Dim; break;
|
||||
case 'i': res.attributes |= Attribute::Italic; break;
|
||||
case 'f': res.attributes |= Attribute::FinalFg; break;
|
||||
case 'g': res.attributes |= Attribute::FinalBg; break;
|
||||
case 'a': res.attributes |= Attribute::FinalAttr; break;
|
||||
case 'F': res.attributes |= Attribute::Final; break;
|
||||
case 'u': face.attributes |= Attribute::Underline; break;
|
||||
case 'r': face.attributes |= Attribute::Reverse; break;
|
||||
case 'b': face.attributes |= Attribute::Bold; break;
|
||||
case 'B': face.attributes |= Attribute::Blink; break;
|
||||
case 'd': face.attributes |= Attribute::Dim; break;
|
||||
case 'i': face.attributes |= Attribute::Italic; break;
|
||||
case 'f': face.attributes |= Attribute::FinalFg; break;
|
||||
case 'g': face.attributes |= Attribute::FinalBg; break;
|
||||
case 'a': face.attributes |= Attribute::FinalAttr; break;
|
||||
case 'F': face.attributes |= Attribute::Final; break;
|
||||
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)
|
||||
|
@ -79,16 +87,33 @@ String to_string(Face face)
|
|||
|
||||
Face FaceRegistry::operator[](StringView facedesc) const
|
||||
{
|
||||
auto it = m_faces.find(facedesc);
|
||||
if (it != m_faces.end())
|
||||
return resolve_spec(parse_face(facedesc));
|
||||
}
|
||||
|
||||
Face FaceRegistry::resolve_spec(const FaceSpec& spec) const
|
||||
{
|
||||
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())
|
||||
{
|
||||
if (it->value.alias.empty())
|
||||
return it->value.face;
|
||||
return operator[](it->value.alias);
|
||||
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;
|
||||
}
|
||||
}
|
||||
if (m_parent)
|
||||
return (*m_parent)[facedesc];
|
||||
return parse_face(facedesc);
|
||||
return face;
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
if (name.empty() or is_color_name(name) or
|
||||
std::any_of(name.begin(), name.end(),
|
||||
[](char c){ return not is_word(c); }))
|
||||
any_of(name, [](char c){ return not is_word(c); }))
|
||||
throw runtime_error(format("invalid face name: '{}'", name));
|
||||
|
||||
if (name == facedesc)
|
||||
throw runtime_error(format("cannot alias face '{}' to itself", name));
|
||||
|
||||
for (auto it = m_faces.find(facedesc);
|
||||
it != m_faces.end() and not it->value.alias.empty();
|
||||
it = m_faces.find(it->value.alias))
|
||||
FaceSpec spec = parse_face(facedesc);
|
||||
auto it = m_faces.find(spec.base);
|
||||
if (spec.base == name and it != m_faces.end())
|
||||
{
|
||||
if (it->value.alias == name)
|
||||
throw runtime_error("face cycle detected");
|
||||
}
|
||||
|
||||
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
|
||||
it->value.face = merge_faces(it->value.face, spec.face);
|
||||
it->value.base = spec.base;
|
||||
return;
|
||||
}
|
||||
|
||||
face.alias = "";
|
||||
face.face = parse_face(facedesc);
|
||||
while (it != m_faces.end() and not it->value.base.empty())
|
||||
{
|
||||
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)
|
||||
|
|
|
@ -20,12 +20,12 @@ public:
|
|||
void add_face(StringView name, StringView facedesc, bool override = false);
|
||||
void remove_face(StringView name);
|
||||
|
||||
struct FaceOrAlias
|
||||
struct FaceSpec
|
||||
{
|
||||
Face face = {};
|
||||
String alias = {};
|
||||
String base = {};
|
||||
};
|
||||
using FaceMap = HashMap<String, FaceOrAlias, MemoryDomain::Faces>;
|
||||
using FaceMap = HashMap<String, FaceSpec, MemoryDomain::Faces>;
|
||||
|
||||
auto flatten_faces() const
|
||||
{
|
||||
|
@ -41,6 +41,8 @@ public:
|
|||
}
|
||||
|
||||
private:
|
||||
Face resolve_spec(const FaceSpec& spec) const;
|
||||
|
||||
friend class Scope;
|
||||
FaceRegistry();
|
||||
|
||||
|
|
|
@ -78,7 +78,7 @@ static uint32_t compute_faces_hash(const FaceRegistry& faces)
|
|||
{
|
||||
uint32_t hash = 0;
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
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