<commit_before>#include "face_registry.hh"

#include "exception.hh"
#include "ranges.hh"
#include "string_utils.hh"

namespace Kakoune
{

static FaceRegistry::FaceSpec parse_face(StringView facedesc)
{
    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());

    auto colors_end = std::min(attr_it, base_it);

    FaceRegistry::FaceSpec spec;
    auto& face = spec.face;
    face.fg = colors_end != facedesc.begin() ?
        str_to_color({facedesc.begin(), std::min(bg_it, colors_end)}) : Color::Default;
    if (bg_it != facedesc.end())
        face.bg = bg_it+1 != attr_it ? str_to_color({bg_it+1, colors_end}) : Color::Default;
    if (attr_it != facedesc.end())
    {
        for (++attr_it; attr_it != base_it; ++attr_it)
        {
            switch (*attr_it)
            {
                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}));
            }
        }
    }
    if (base_it != facedesc.end())
        spec.base = String{base_it+1, facedesc.end()};
    return spec;
}

String to_string(Attribute attributes)
{
    if (attributes == Attribute::Normal)
        return "";

    struct Attr { Attribute attr; StringView name; }
    attrs[] {
        { Attribute::Underline, "u" },
        { Attribute::Reverse, "r" },
        { Attribute::Blink, "B" },
        { Attribute::Bold, "b" },
        { Attribute::Dim, "d" },
        { Attribute::Italic, "i" },
        { Attribute::Final, "F" },
        { Attribute::FinalFg, "f" },
        { Attribute::FinalBg, "g" },
        { Attribute::FinalAttr, "a" },
    };

    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) const
{
    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())
    {
        auto it = reg->m_faces.find(base);<commit_msg>Fix face attributes to string conversion with F shorthand<commit_after>#include "face_registry.hh"

#include "exception.hh"
#include "ranges.hh"
#include "string_utils.hh"

namespace Kakoune
{

static FaceRegistry::FaceSpec parse_face(StringView facedesc)
{
    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());

    auto colors_end = std::min(attr_it, base_it);

    FaceRegistry::FaceSpec spec;
    auto& face = spec.face;
    face.fg = colors_end != facedesc.begin() ?
        str_to_color({facedesc.begin(), std::min(bg_it, colors_end)}) : Color::Default;
    if (bg_it != facedesc.end())
        face.bg = bg_it+1 != attr_it ? str_to_color({bg_it+1, colors_end}) : Color::Default;
    if (attr_it != facedesc.end())
    {
        for (++attr_it; attr_it != base_it; ++attr_it)
        {
            switch (*attr_it)
            {
                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}));
            }
        }
    }
    if (base_it != facedesc.end())
        spec.base = String{base_it+1, facedesc.end()};
    return spec;
}

String to_string(Attribute attributes)
{
    if (attributes == Attribute::Normal)
        return "";

    struct Attr { Attribute attr; StringView name; }
    attrs[] {
        { Attribute::Underline, "u" },
        { Attribute::Reverse, "r" },
        { Attribute::Blink, "B" },
        { Attribute::Bold, "b" },
        { Attribute::Dim, "d" },
        { Attribute::Italic, "i" },
        { Attribute::Final, "F" },
        { Attribute::FinalFg, "f" },
        { Attribute::FinalBg, "g" },
        { Attribute::FinalAttr, "a" },
    };

    auto filteredAttrs = attrs |
                         filter([&](const Attr& a) {
                             if ((attributes & a.attr) != a.attr)
                                 return false;
                             attributes &= ~a.attr;
                             return true;
                         }) | 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) const
{
    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())
    {
        auto it = reg->m_faces.find(base);
{'fraction_non_alphanumeric': 0.15750430520598754, 'fraction_numerical': 0.0013246787653993907, 'mean_word_length': 2.5646836638338053, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 15, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 1, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5408193', 'n_tokens_mistral': 2376, 'n_tokens_neox': 2316, 'n_words': 532}