The completion source behind the expression-editing component: predicator's own grammar vocabulary, plus the datamodel paths a host declares.
This module is pure and touches no LiveView, the same split
StatifierUI.Live.State gives the ops panes. StatifierUI.Live.ExpressionInput
renders what completions/2 returns; everything about what an author can
be offered is decided here, where it is testable without a browser and
without Phoenix.
Two sources, and only two
A completion is either a declared datamodel path - supplied by the caller,
because only the host knows its own datamodel - or a lexeme of the
predicator grammar, read from Predicator.Vocabulary (px-15q). Nothing is
invented here. An operator this module offered that the lexer does not accept
would be a second, drifting copy of the grammar, which is precisely the
duplication Predicator.Vocabulary was published to prevent.
The grammar half degrades
Predicator.Vocabulary is newer than the predicator releases this package
can resolve, so its absence is a supported state rather than a broken one: a
host on an older predicator gets its declared paths and no grammar entries,
and the component renders a plain input with a path list. The module is
reached through Application.get_env(:statifier_ui, :predicator_vocabulary, Predicator.Vocabulary) and guarded with Code.ensure_loaded?/1, so nothing
raises and nothing warns at compile time.
Shape
Every completion is a map with four keys:
:label- what a completion list shows ("len(...)","contains"):insert- the text written into the source at the caret:kind-"path","function", or the px category of a lexeme ("comparison","logical", ...), which is what a list groups by:detail- one line of prose, ornilwhen the source carries none
Examples
iex> StatifierUI.Expression.completions(["order.total"])
...> |> Enum.find(&(&1.insert == "order.total"))
%{label: "order.total", insert: "order.total", kind: "path", detail: "declared path"}
Summary
Types
One offer: what to show, what to write, how to group it.
Functions
Every completion available to an expression field: the declared paths first, then the grammar.
The subset a native <datalist> can usefully offer: the word-shaped
completions.
Whether the resolved predicator exposes Predicator.Vocabulary.
Types
Functions
@spec completions( [String.t()], keyword() ) :: [completion()]
Every completion available to an expression field: the declared paths first, then the grammar.
candidates is the declared datamodel path list - what
StatifierBlocks.Datamodel.candidates/3 returns, arriving through the
expression_component seam as :candidates. Paths lead because they are the
ones an author cannot look up.
opts is passed through to Predicator.Vocabulary.functions/1, so a host
with its own Predicator.FunctionProvider modules offers exactly the
functions its own contexts will accept.
Examples
iex> StatifierUI.Expression.completions() |> Enum.any?(&(&1.insert == ">="))
true
iex> StatifierUI.Expression.completions([], builtins: false)
...> |> Enum.any?(&(&1.kind == "function"))
false
@spec datalist([completion()]) :: [completion()]
The subset a native <datalist> can usefully offer: the word-shaped
completions.
A <datalist> filters its options against the whole field value, and it
cannot insert at a caret. Offering "::" or "(" through one is noise, so
the no-JavaScript affordance carries paths, keywords, and function names and
leaves the symbol operators to the hook.
Examples
iex> StatifierUI.Expression.completions() |> StatifierUI.Expression.datalist()
...> |> Enum.any?(&(&1.insert == "::"))
false
@spec vocabulary_available?() :: boolean()
Whether the resolved predicator exposes Predicator.Vocabulary.
The component stamps this on the rendered input so a host - or a test - can tell "no grammar completions offered" from "grammar completions offered and none matched".
Examples
iex> is_boolean(StatifierUI.Expression.vocabulary_available?())
true