MingaEditor.UI.Face.Registry (Minga v0.1.0)

Copy Markdown View Source

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.

Summary

Types

Map of face name to face struct.

t()

Functions

Builds a registry from a theme's syntax map.

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

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

Returns all face names in the registry.

Creates an empty registry with only the default face.

Adds or replaces a face in the registry.

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

Pre-resolves all faces and populates the resolution cache.

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

Resolves a face with modifier composition.

Adds default LSP semantic token faces to the registry.

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

Types

face_map()

@type face_map() :: %{required(String.t()) => Minga.Core.Face.t()}

Map of face name to face struct.

t()

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

Functions

from_syntax(syntax)

@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(theme)

@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(registry, name)

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

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

names(registry)

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

Returns all face names in the registry.

new()

@spec new() :: t()

Creates an empty registry with only the default face.

put(reg, face)

@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(reg, name)

@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(reg)

@spec resolve_all(t()) :: t()

Pre-resolves all faces and populates the resolution cache.

style_for(reg, name)

@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(reg, base_name, modifiers)

@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(reg)

@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(reg, overrides)

@spec with_overrides(t(), %{required(String.t()) => 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