Packages
fnord
0.8.43
0.9.40
0.9.39
0.9.38
0.9.37
0.9.36
0.9.35
0.9.34
0.9.33
0.9.32
0.9.31
0.9.30
0.9.29
0.9.28
0.9.27
0.9.26
0.9.25
0.9.24
0.9.23
0.9.22
0.9.21
0.9.20
0.9.19
0.9.18
0.9.17
0.9.16
0.9.15
0.9.14
0.9.13
0.9.12
0.9.11
0.9.10
0.9.9
0.9.8
0.9.7
0.9.6
0.9.5
0.9.4
0.9.3
0.9.2
0.9.1
0.9.0
0.8.99
0.8.98
0.8.97
0.8.96
0.8.95
0.8.94
0.8.93
0.8.92
0.8.91
0.8.90
0.8.89
0.8.88
0.8.87
0.8.86
0.8.85
0.8.84
0.8.83
0.8.82
0.8.81
0.8.80
0.8.79
0.8.78
0.8.77
0.8.76
0.8.75
0.8.74
0.8.73
0.8.72
0.8.71
0.8.70
0.8.69
0.8.68
0.8.67
0.8.66
0.8.65
0.8.64
0.8.63
0.8.62
0.8.61
0.8.60
0.8.59
0.8.58
0.8.57
0.8.56
0.8.55
0.8.54
0.8.53
0.8.52
0.8.51
0.8.50
0.8.49
0.8.48
0.8.47
0.8.46
0.8.45
0.8.44
0.8.43
0.8.42
0.8.41
0.8.40
0.8.39
0.8.38
0.8.37
0.8.36
0.8.35
0.8.34
0.8.33
0.8.32
0.8.31
0.8.30
0.8.29
0.8.27
0.8.26
0.8.25
0.8.24
0.8.23
0.8.22
0.8.21
0.8.20
0.8.19
0.8.18
0.8.17
0.8.16
0.8.15
0.8.14
0.8.13
0.8.12
0.8.11
0.8.1
0.8.0
0.7.24
0.7.23
0.7.22
0.7.21
0.7.20
0.7.19
0.7.18
0.7.17
0.7.16
0.7.15
0.7.14
0.7.13
0.7.12
0.7.11
0.7.10
0.7.9
0.7.8
0.7.7
0.7.6
0.7.5
0.7.3
0.7.2
0.7.1
0.7.0
0.6.9
0.6.8
0.6.7
0.6.6
0.6.5
0.6.4
0.6.3
0.6.1
0.6.0
0.5.9
0.5.8
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.44
0.4.43
0.4.42
0.4.41
0.4.40
0.4.39
0.4.38
0.4.37
0.4.36
0.4.35
0.4.34
0.4.33
0.4.32
0.4.30
0.4.29
0.4.28
0.4.27
0.4.26
0.4.25
0.4.24
0.4.23
0.4.22
0.4.21
0.4.20
0.4.19
0.4.18
0.4.17
0.4.16
0.4.15
0.4.14
0.4.13
0.4.12
0.4.11
0.4.10
0.4.9
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.0
0.2.0
0.1.0
AI code archaeology
Current section
Files
Jump to
Current section
Files
lib/ui/output/production.ex
defmodule UI.Output.Production do
@moduledoc """
Production implementation of UI.Output that uses UI.Queue and Owl.IO.
"""
require Logger
@behaviour UI.Output
@impl UI.Output
def puts(data) do
UI.Queue.puts(UI.Queue, :stdio, data)
end
@impl UI.Output
def log(level, data) do
UI.Queue.log(UI.Queue, level, data)
end
@impl UI.Output
def interact(fun) do
case UI.Queue.interact(UI.Queue, fun) do
{:ok, result} ->
result
{:error, {%{} = exception, _stacktrace}} ->
raise exception
{:error, {:exit, reason}} ->
exit(reason)
{:error, {:throw, value}} ->
throw(value)
{:error, {kind, value}} ->
# Fallback for other kinds
raise RuntimeError, "#{kind}: #{inspect(value)}"
end
end
@impl UI.Output
def choose(label, options) do
interact(fn ->
with_notification_timeout(
fn ->
flush()
Owl.IO.select(options, label: label)
end,
"Fnord is waiting for your selection: #{label}"
)
end)
end
@impl UI.Output
def choose(label, options, timeout_ms, default) do
interact(fn ->
# Display the selection prompt
task =
Task.async(fn ->
flush()
Owl.IO.select(options, label: label)
end)
# Phase A: wait for notification threshold
case Task.yield(task, 60_000) do
{:ok, selection} ->
selection
nil ->
# Send OS notification and continue waiting
Notifier.notify("Fnord", "Fnord is waiting for your selection: #{label}", [])
# Phase B: wait for auto window
case Task.yield(task, timeout_ms) do
{:ok, selection} ->
Notifier.dismiss("Fnord")
selection
nil ->
Task.shutdown(task, :brutal_kill)
Notifier.dismiss("Fnord")
# Note: would need UI.debug here, but that would create circular dependency
default
end
end
end)
end
@impl UI.Output
def prompt(prompt_text) do
prompt(prompt_text, [])
end
@impl UI.Output
def prompt(prompt_text, owl_opts) do
interact(fn ->
owl_opts = Keyword.put(owl_opts, :label, prompt_text)
with_notification_timeout(
fn ->
flush()
Owl.IO.input(owl_opts)
end,
"Fnord is waiting for your input: #{String.slice(prompt_text, 0..50)}"
)
end)
end
@impl UI.Output
def confirm(msg) do
confirm(msg, false)
end
@impl UI.Output
def confirm(msg, default) do
has_default = is_boolean(default)
cond do
is_tty?() ->
yes = if default == true, do: "Y", else: "y"
no = if default == false, do: "N", else: "n"
interact(fn ->
with_notification_timeout(
fn ->
flush()
IO.write(:stderr, UI.Formatter.format_output("#{msg} (#{yes}/#{no}) "))
case IO.gets("") do
"y\n" -> true
"Y\n" -> true
_ -> default
end
end,
"Fnord is waiting for your response to: #{msg}"
)
end)
has_default ->
default
true ->
Logger.warning(
"Confirmation requested without default, but session is not connected to a TTY."
)
false
end
end
@impl UI.Output
def newline do
puts("")
end
@impl UI.Output
def box(contents, opts) do
contents
|> Owl.Box.new(opts)
|> puts()
end
@impl UI.Output
def flush do
Logger.flush()
end
# Private helper functions
defp with_notification_timeout(func, notification_message) do
with_notification_timeout(func, notification_message, 60_000)
end
defp with_notification_timeout(func, notification_message, timeout_ms) do
# Start a task to execute the function with UI.Queue context preserved
task =
Task.async(fn ->
UI.Queue.run_from_task(func)
end)
# Start a timer for the notification
timer_ref =
Process.send_after(self(), {:notification_timeout, notification_message}, timeout_ms)
# Wait for the task to complete while handling timeout messages
result = wait_for_task_with_timeout(task, timer_ref)
# Clean up: cancel timer if still active
Process.cancel_timer(timer_ref)
result
end
defp wait_for_task_with_timeout(task, timer_ref) do
wait_for_task_with_timeout(task, timer_ref, false)
end
defp wait_for_task_with_timeout(task, timer_ref, notification_sent?) do
receive do
{:notification_timeout, message} ->
# Send notification but continue waiting for the task
Notifier.notify("Fnord", message, [])
wait_for_task_with_timeout(task, timer_ref, true)
after
100 ->
# Check if task completed
case Task.yield(task, 0) do
{:ok, result} ->
# If we sent a notification, try to dismiss it
if notification_sent? do
Notifier.dismiss("Fnord")
end
result
nil ->
# Keep waiting
wait_for_task_with_timeout(task, timer_ref, notification_sent?)
end
end
end
defp is_tty? do
:prim_tty.isatty(:stderr)
|> case do
true -> true
_ -> false
end
end
end