跳到主要内容
ansicode
Elixir

在 Elixir 中使用 ANSI 转义码

Elixir 标准库提供 `IO.ANSI` 模块 —— 每个命名 SGR 和少量光标 / 屏幕辅助都是返回其字节形式的函数(`IO.ANSI.red()` 返回 `"\e[31m"`)。规范用法是 `IO.ANSI.format/2`,它接受混合原子(命名转义)与二进制(你的文本)的列表,返回适合 `IO.puts/1` 或 `IO.write/1` 的 iolist —— 例如 `IO.puts(IO.ANSI.format([:bright, :red, "error: ", :reset, "permission denied"]))`。`IO.ANSI.enabled?/0` 是内置于标准库的能力门控:遵守 `:elixir` 应用环境 `:ansi_enabled`(`iex` 在 tty 上设为 true、在管道上设为 false)以及 `NO_COLOR` 环境变量。 超出零散 print 的场景:**Bunt**(savonarola/bunt)为 `IO.ANSI` 加糖,支持字符串内的命名颜色标签 —— `Bunt.puts([:red, "error: ", :default_color, "permission denied"])` 或行内形式 `Bunt.puts("[red]error:[/red] permission denied")`。**Owl**(fuelen/owl)是现代输出工具集(盒子、表格、进度条、多行 spinner、ANSI 超链接)—— 被 Phoenix 生成的 CLI 任务、`mix` 扩展、`livebook` 启动输出使用。完整全屏 TUI 请用 **Ratatouille**(ndreynolds/ratatouille),Elm 风格的 `model / update / view` 架构经 termbox2 渲染为 ANSI。

推荐库

  • IO.ANSI

    标准库模块 —— `IO.ANSI.red/0`、`IO.ANSI.bright/0`、`IO.ANSI.format/2`(将原子 + 二进制混合为 iolist)、`IO.ANSI.enabled?/0`(能力门控,遵守 `:elixir` 的 `:ansi_enabled` 应用环境 + `NO_COLOR`)。所有转义生成器都是返回二进制的纯函数 —— 零运行时开销、零依赖。

  • Bunt

    基于 IO.ANSI 的标签字符串语法糖 —— `Bunt.puts("[red]error:[/red] permission denied")` 或 `Bunt.puts([:red, "error", :reset])`。新增命名颜色别名(`:chartreuse`、`:burlywood3` 等)以更精细地控制 SGR-256。`credo`、`ex_doc` 以及众多 `mix` 任务 UI 都在用。

  • Owl

    现代输出工具集 —— 盒子(`Owl.Box.new/2`)、表格(`Owl.Table.new/2`)、进度条(`Owl.ProgressBar`)、多行 spinner、ANSI 超链接(`Owl.Data.tag/2`)。被 Phoenix 生成的 CLI 任务、`mix livebook.install` 以及近期的 Elixir 工具 UI 使用。

  • Ratatouille

    全屏 TUI 框架 —— Elm 风格的 `model / update / view` 架构,经内置的 `termbox2` NIF 渲染为 ANSI。view 函数返回元素树,运行时做 diff 并绘制。被 `mix` 任务仪表板、日志查看器、内部开发工具使用。

常用写法

直接 IO.puts —— \e 字面量在 Elixir 字符串中可用
# Elixir string literals do support \e (= \x1b). All three forms are
# byte-equivalent: \e, \x1b, \u{1b}. IO.puts is byte-clean.
IO.puts("\e[1;31merror:\e[0m permission denied")
IO.puts("\x1b[1;32mok:\x1b[0m all 142 tests passed")
规范的 IO.ANSI.format —— 原子 + 二进制 → iolist
IO.puts(IO.ANSI.format([:bright, :red, "error: ", :reset, "permission denied"]))
IO.puts(IO.ANSI.format([:green, "ok: ", :reset, "all 142 tests passed"]))

# IO.ANSI.format/2 takes an optional second arg: emit_codes? boolean.
# Pass IO.ANSI.enabled? so the same call works in pipes / CI / NO_COLOR.
IO.puts(IO.ANSI.format([:red, "error"], IO.ANSI.enabled?()))
能力门控 —— IO.ANSI.enabled? + NO_COLOR + FORCE_COLOR
defmodule MyApp.Color do
  # IO.ANSI.enabled? already honours :ansi_enabled + NO_COLOR. Layer
  # FORCE_COLOR on top for CI runs that want colour despite no tty.
  def enabled? do
    cond do
      System.get_env("NO_COLOR") -> false
      System.get_env("FORCE_COLOR") -> true
      true -> IO.ANSI.enabled?()
    end
  end

  def paint(chunks) when is_list(chunks) do
    IO.ANSI.format(chunks, enabled?())
  end
end

IO.puts(MyApp.Color.paint([:bright, :red, "error: ", :reset, "no perm"]))
Truecolor + Owl 盒子
# Truecolor — IO.ANSI has no helper for SGR \x1b[38;2;r;g;b m, but you
# can splice raw bytes into the format list. Or use Owl, which accepts
# RGB tuples directly.

# Hand-rolled truecolor:
fg_rgb = fn r, g, b -> "\e[38;2;#{r};#{g};#{b}m" end
IO.puts([fg_rgb.(203, 166, 247), "lavender truecolor", IO.ANSI.reset()])

# Owl box with truecolor border:
#   {:owl, "~> 0.12"} in mix.exs deps
"Build complete — 142 tests, 0 failures"
|> Owl.Box.new(border_style: :double, title: " status ", padding: 1)
|> Owl.IO.puts()

相关序列

其他语言