kakoune/src/face_registry.cc
Maxime Coste 6a6e71dc0f Highlight cursors differently when they lie on an end of line
When on an end of line, certain behaviours can be surprising, for
example delete will join the following line (which makes sense, and
is consistent, but hard to predict if we do not know the cursor is
on and end of line).

As Kakoune is moving more and more towards treating end of lines
as any other character, making it clear when the cursor lies on
them seems like a good way to reduce surprise.
2018-02-24 21:32:01 +11:00

159 lines
5.8 KiB
C++

#include "face_registry.hh"
#include "exception.hh"
#include "ranges.hh"
#include "string_utils.hh"
namespace Kakoune
{
static Face parse_face(StringView facedesc)
{
constexpr StringView invalid_face_error = "invalid face description, expected <fg>[,<bg>][+<attr>]";
auto bg_it = find(facedesc, ',');
auto attr_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() ?
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;
if (attr_it != facedesc.end())
{
for (++attr_it; attr_it != facedesc.end(); ++attr_it)
{
switch (*attr_it)
{
case 'e': res.attributes |= Attribute::Exclusive; break;
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;
default: throw runtime_error(format("unknown face attribute '{}'", StringView{*attr_it}));
}
}
}
return res;
}
String to_string(Attribute attributes)
{
if (attributes == Attribute::Normal)
return "";
struct Attr { Attribute attr; StringView name; }
attrs[] {
{ Attribute::Exclusive, "e" },
{ Attribute::Underline, "u" },
{ Attribute::Reverse, "r" },
{ Attribute::Blink, "B" },
{ Attribute::Bold, "b" },
{ Attribute::Dim, "d" },
{ Attribute::Italic, "i" },
};
auto filteredAttrs = attrs |
filter([=](const Attr& a) { return attributes & a.attr; }) |
transform([](const Attr& a) { return a.name; });
return accumulate(filteredAttrs, "+"_str, std::plus<>{});
}
String to_string(Face face)
{
return format("{},{}{}", face.fg, face.bg, face.attributes);
}
Face FaceRegistry::operator[](StringView facedesc)
{
auto it = m_aliases.find(facedesc);
while (it != m_aliases.end())
{
if (it->value.alias.empty())
return it->value.face;
it = m_aliases.find(it->value.alias);
}
return parse_face(facedesc);
}
void FaceRegistry::register_alias(StringView name, StringView facedesc,
bool override)
{
if (not override and m_aliases.find(name) != m_aliases.end())
throw runtime_error(format("alias '{}' already defined", name));
if (name.empty() or is_color_name(name) or
std::any_of(name.begin(), name.end(),
[](char c){ return not isalnum(c); }))
throw runtime_error(format("invalid alias name: '{}'", name));
if (name == facedesc)
throw runtime_error(format("cannot alias face '{}' to itself", name));
FaceOrAlias& alias = m_aliases[name];
auto it = m_aliases.find(facedesc);
if (it != m_aliases.end())
{
while (it != m_aliases.end())
{
if (it->value.alias.empty())
break;
if (it->value.alias == name)
throw runtime_error("face cycle detected");
it = m_aliases.find(it->value.alias);
}
alias.alias = facedesc.str();
}
else
{
alias.alias = "";
alias.face = parse_face(facedesc);
}
}
CandidateList FaceRegistry::complete_alias_name(StringView prefix,
ByteCount cursor_pos) const
{
return complete(prefix, cursor_pos,
m_aliases | transform(std::mem_fn(&AliasMap::Item::key)));
}
FaceRegistry::FaceRegistry()
: m_aliases{
{ "Default", {Face{ Color::Default, Color::Default }} },
{ "PrimarySelection", {Face{ Color::White, Color::Blue }} },
{ "SecondarySelection", {Face{ Color::Black, Color::Blue }} },
{ "PrimaryCursor", {Face{ Color::Black, Color::White }} },
{ "SecondaryCursor", {Face{ Color::Black, Color::White }} },
{ "PrimaryCursorEol", {Face{ Color::Black, Color::Cyan }} },
{ "SecondaryCursorEol", {Face{ Color::Black, Color::Cyan }} },
{ "LineNumbers", {Face{ Color::Default, Color::Default }} },
{ "LineNumberCursor", {Face{ Color::Default, Color::Default, Attribute::Reverse }} },
{ "LineNumbersWrapped", {Face{ Color::Default, Color::Default, Attribute::Italic }} },
{ "MenuForeground", {Face{ Color::White, Color::Blue }} },
{ "MenuBackground", {Face{ Color::Blue, Color::White }} },
{ "MenuInfo", {Face{ Color::Cyan, Color::Default }} },
{ "Information", {Face{ Color::Black, Color::Yellow }} },
{ "Error", {Face{ Color::Black, Color::Red }} },
{ "StatusLine", {Face{ Color::Cyan, Color::Default }} },
{ "StatusLineMode", {Face{ Color::Yellow, Color::Default }} },
{ "StatusLineInfo", {Face{ Color::Blue, Color::Default }} },
{ "StatusLineValue", {Face{ Color::Green, Color::Default }} },
{ "StatusCursor", {Face{ Color::Black, Color::Cyan }} },
{ "Prompt", {Face{ Color::Yellow, Color::Default }} },
{ "MatchingChar", {Face{ Color::Default, Color::Default, Attribute::Bold }} },
{ "BufferPadding", {Face{ Color::Blue, Color::Default }} },
{ "Whitespace", {Face{ Color::Default, Color::Default }} },
}
{}
}