# `MingaEditor.UI.Face.Registry`
[🔗](https://github.com/jsmestad/minga/blob/main/lib/minga_editor/ui/face/registry.ex#L1)

Named face storage and lookup with cached inheritance resolution.

The registry holds all defined faces and caches their resolved
(fully-inherited) forms. When a face is looked up, the registry
returns the resolved version with no `nil` fields.

## Building a registry

A registry is built from a theme's syntax map using `from_theme/1`,
which converts every `capture_name => style` entry into a `Face`
struct with proper inheritance wired by dotted-name convention.

## Resolution caching

`resolve_all/1` pre-computes every face's resolved form and stores
it alongside the raw faces. Subsequent `resolve/2` calls return the
cached result in O(1).

## Buffer-local overrides

`with_overrides/2` merges buffer-local face overrides (e.g., from
face remapping) on top of the base registry. The overrides are
merged attribute-by-attribute, and the resolution cache is rebuilt.

# `face_map`

```elixir
@type face_map() :: %{required(String.t()) =&gt; Minga.Core.Face.t()}
```

Map of face name to face struct.

# `t`

```elixir
@type t() :: %MingaEditor.UI.Face.Registry{faces: face_map(), resolved: face_map()}
```

# `from_syntax`

```elixir
@spec from_syntax(MingaEditor.UI.Theme.syntax()) :: t()
```

Builds a registry from a theme's syntax map.

Converts each `capture_name => style_keyword_list` entry into a Face
struct. Inheritance is inferred from dotted names: `"keyword.function"`
inherits from `"keyword"`, which inherits from `"default"`.

The registry is pre-resolved after building, so all lookups return
fully resolved faces.

## Examples

    iex> syntax = %{"keyword" => [fg: 0xC678DD, bold: true], "keyword.function" => [fg: 0xC678DD]}
    iex> reg = Registry.from_syntax(syntax)
    iex> face = Registry.resolve(reg, "keyword.function")
    iex> face.bold
    true

# `from_theme`

```elixir
@spec from_theme(MingaEditor.UI.Theme.t()) :: t()
```

Builds a registry from a full `MingaEditor.UI.Theme.t()` struct.

Uses the theme's syntax map and sets the default face's fg/bg from
the theme's editor colors.

# `get`

```elixir
@spec get(t(), String.t()) :: Minga.Core.Face.t() | nil
```

Looks up a face by name. Returns the raw (unresolved) face or nil.

# `names`

```elixir
@spec names(t()) :: [String.t()]
```

Returns all face names in the registry.

# `new`

```elixir
@spec new() :: t()
```

Creates an empty registry with only the default face.

# `put`

```elixir
@spec put(t(), Minga.Core.Face.t()) :: t()
```

Adds or replaces a face in the registry.

The resolution cache is invalidated for any face that could be
affected (the face itself and all descendants). Call `resolve_all/1`
after batch updates to rebuild the cache.

# `resolve`

```elixir
@spec resolve(t(), String.t()) :: Minga.Core.Face.t()
```

Resolves a face by name, returning the fully-inherited version.

If the face is in the resolution cache, returns it directly.
If not found by exact name, falls back through the dotted name
hierarchy (e.g., `"keyword.function.builtin"` tries
`"keyword.function"`, then `"keyword"`, then `"default"`).

Always returns a face (falls back to default).

# `resolve_all`

```elixir
@spec resolve_all(t()) :: t()
```

Pre-resolves all faces and populates the resolution cache.

# `style_for`

```elixir
@spec style_for(t(), String.t()) :: Minga.Core.Face.t()
```

Resolves a face by name and returns a `Face.t()`.

This is the main entry point for the render pipeline, replacing
`Theme.style_for_capture/2`. Returns a fully resolved face struct.
For composite capture names (e.g., "@lsp.type.variable+deprecated"),
modifier attributes are composed on top of the base face.

Always returns a face (falls back to default).

# `style_for_with_modifiers`

```elixir
@spec style_for_with_modifiers(t(), String.t(), [String.t()]) :: Minga.Core.Face.t()
```

Resolves a face with modifier composition.

Takes a base capture name and a list of modifier names, resolves each,
and merges modifier attributes on top of the base face. This is how
`@lsp.mod.deprecated` adds strikethrough to whatever the type's color
is, rather than replacing it.

Returns a `Face.t()` with the base face's colors and the modifier
face's decorative attributes composed.

# `with_lsp_defaults`

```elixir
@spec with_lsp_defaults(t()) :: t()
```

Adds default LSP semantic token faces to the registry.

Maps `@lsp.type.*` captures to their closest tree-sitter equivalents
so semantic tokens render with sensible colors even without explicit
theme support. Themes can override these by defining faces for
`@lsp.type.variable`, `@lsp.mod.deprecated`, etc.

Also adds modifier faces:
- `@lsp.mod.deprecated` gets strikethrough
- `@lsp.mod.readonly` inherits from the type face (no visual change by default)

# `with_overrides`

```elixir
@spec with_overrides(t(), %{required(String.t()) =&gt; keyword()}) :: t()
```

Merges buffer-local face overrides on top of the base registry.

Each override is a `{face_name, attribute_overrides}` pair where
attribute_overrides is a keyword list of face fields to override.
Returns a new registry with the overrides merged and the resolution
cache rebuilt.

## Examples

    iex> reg = Registry.from_syntax(%{"keyword" => [fg: 0xC678DD, bold: true]})
    iex> reg = Registry.with_overrides(reg, %{"keyword" => [fg: 0xFF0000]})
    iex> face = Registry.resolve(reg, "keyword")
    iex> face.fg
    0xFF0000
    iex> face.bold
    true

---

*Consult [api-reference.md](api-reference.md) for complete listing*
