UI (fnord v0.9.17)

View Source

User interface functions for output, logging, and user interaction.

Context Warnings for Interactive UI

Interactive UI functions (confirm/1, choose/2, prompt/1) can cause deadlocks when called from certain contexts and must be wrapped appropriately.

GenServer Callbacks

Use UI.Queue.run_from_genserver/1 to prevent deadlocks:

def handle_call(:delete_item, _from, state) do
  confirmed = UI.Queue.run_from_genserver(fn ->
    UI.confirm("Delete this item?")
  end)
  # ...
end

Services.Globals.Spawn.async and Spawned Processes

Use UI.Queue.run_from_task/1 when tasks need to participate in an existing UI interaction:

task = Services.Globals.Spawn.async(fn ->
  UI.Queue.run_from_task(fn ->
    UI.confirm("Process this item?")
  end)
end)

Creating UI Components

Use UI.interact/1 to group multiple UI operations into a single atomic component:

def confirm_with_details(item) do
  UI.interact(fn ->
    UI.info("Item details: #{item.name}")
    UI.puts("Size: #{item.size}, Modified: #{item.date}")
    UI.confirm("Delete this item?")
  end)
end

Non-interactive functions (info/2, warn/2, error/2, puts/1, say/1) are safe to call directly from any context.

Interactive vs Non-Interactive Functions

Interactive (require context wrappers in GenServer/Task contexts):

Non-interactive (safe to call directly from any context):

Summary

Functions

Formats a string through the external FNORD_FORMATTER command, if configured. Skipped when quiet mode is active or stdout is not a TTY.

Execute a function as a single interaction unit. All UI calls within the function (puts, log, choose, prompt, etc.) will be treated as part of this interaction and execute immediately without queuing.

Open content in the user's editor and return the edited text.

Functions

async_stream(enumerable, fun, label \\ "Working", options \\ [])

begin_step(msg)

begin_step(msg, detail)

bold(text)

@spec bold(binary()) :: iodata()

box(contents, opts)

choose(label, options)

choose(label, options, timeout_ms, default)

choose_multi(label, options)

choose_multi(label, options, owl_opts)

clean_detail(detail)

colorize?()

confirm(msg)

@spec confirm(binary()) :: boolean()

confirm(msg, default)

@spec confirm(binary(), boolean()) :: boolean()

debug(msg)

debug(msg, detail)

end_step(msg)

end_step(msg, detail)

end_step_background(msg)

end_step_background(msg, detail)

error(msg)

error(msg, detail)

fatal(msg)

@spec fatal(binary()) :: no_return()

fatal(msg, detail)

@spec fatal(binary(), binary()) :: no_return()

feedback(atom, name, msg)

feedback_assistant(name, msg)

feedback_user(msg)

flush()

format(input)

Formats a string through the external FNORD_FORMATTER command, if configured. Skipped when quiet mode is active or stdout is not a TTY.

info(msg)

info(msg, detail)

interact(fun)

Execute a function as a single interaction unit. All UI calls within the function (puts, log, choose, prompt, etc.) will be treated as part of this interaction and execute immediately without queuing.

This is useful for composite TUI components that combine multiple UI elements.

iodata?(term)

is_tty?()

italicize(text)

@spec italicize(binary()) :: iodata()

log_usage(model, usage)

@spec log_usage(AI.Model.t(), non_neg_integer() | map()) :: :ok

mute(text)

@spec mute(binary()) :: iodata()

newline()

open_in_editor(content, opts \\ [])

@spec open_in_editor(
  String.t(),
  keyword()
) :: String.t()

Open content in the user's editor and return the edited text.

Respects ELIXIR_EDITOR, then EDITOR, falling back to vi.

Uses a Port with :nouse_stdio so the editor inherits the real terminal file descriptors - this avoids the hang that System.shell causes with TUI editors like vim/nvim (whose stdin would otherwise be BEAM's pipe).

printf_debug(item)

progress_bar_start(name, label, total)

progress_bar_update(name)

prompt(prompt, owl_opts \\ [])

puts(msg)

quiet?()

report_from(name, msg)

report_from(name, msg, detail)

report_step(msg)

report_step(msg, detail)

say(msg)

spin(processing, func)

stdout_tty?()

warn(msg)

warn(msg, detail)

warning_banner(msg)

@spec warning_banner(binary()) :: :ok