Skip to content

Commit

Permalink
Change faces alias to be a base that can be modified
Browse files Browse the repository at this point in the history
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
  • Loading branch information
mawww committed Apr 23, 2019
1 parent 17c5e7a commit 4e24ba8
Show file tree
Hide file tree
Showing 8 changed files with 88 additions and 52 deletions.
11 changes: 8 additions & 3 deletions doc/pages/faces.asciidoc
Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down
107 changes: 62 additions & 45 deletions src/face_registry.cc
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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");
it->value.face = merge_faces(it->value.face, spec.face);
it->value.base = spec.base;
return;
}

FaceOrAlias& face = m_faces[name];

for (auto* registry = this; registry != nullptr; registry = registry->m_parent.get())
while (it != m_faces.end() and not it->value.base.empty())
{
if (not registry->m_faces.contains(facedesc))
continue;
face.alias = facedesc.str(); // This is referencing another face
return;
if (it->value.base == name)
throw runtime_error("face cycle detected");
it = m_faces.find(it->value.base);
}

face.alias = "";
face.face = parse_face(facedesc);
m_faces[name] = std::move(spec);
}

void FaceRegistry::remove_face(StringView name)
Expand Down
8 changes: 5 additions & 3 deletions src/face_registry.hh
Expand Up @@ -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
{
Expand All @@ -41,6 +41,8 @@ public:
}

private:
Face resolve_spec(const FaceSpec& spec) const;

friend class Scope;
FaceRegistry();

Expand Down
2 changes: 1 addition & 1 deletion src/window.cc
Expand Up @@ -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;
}

Expand Down
1 change: 1 addition & 0 deletions test/highlight/face-override/cmd
@@ -0,0 +1 @@

1 change: 1 addition & 0 deletions test/highlight/face-override/in
@@ -0,0 +1 @@
foo
3 changes: 3 additions & 0 deletions test/highlight/face-override/rc
@@ -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 changes: 7 additions & 0 deletions test/highlight/face-override/ui-out
@@ -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] }

0 comments on commit 4e24ba8

Please sign in to comment.