在 Nim 中使用 ANSI 转义码 —— std/terminal、\e、十进制 \27
Nim 的标准库 `std/terminal` 是任何编译型语言中最全面的、开箱即用的终端模块 —— 一次 import 就拥有 SGR 颜色(`setForegroundColor(fgRed)`)、文本样式(`setStyle({styleBright, styleUnderscore})`)、通过 `Color(...)` 的 256-palette、truecolor(需要显式 `enableTrueColors()` 启用)、光标定位、清屏、终端大小查询、raw-mode 切换、isatty 检查 —— 全在一个地方。多数应用的依赖列表到此为止。 字符串字面量形式:Nim 接受 `"\e"`(ESC 命名转义)、`"\x1b"`(十六进制,恰好 2 位)、`"\u001b"`(4 位 Unicode)、`"\u{1b}"`(变长 Unicode)。**Nim 的妙处** —— 也是本页的主要 SERP 差异化 —— 在于 `"\NNN"` 是**十进制**,不像所有 C 族语言那样是八进制。Nim 中 `"\27"` 是字节 27(ESC);同一字面量在 C、Crystal、Python、Ruby 中是字节 `\002` 后跟字符 `'7'`。若你正从其他语言移植 ANSI 代码而 `"\033[31m"` 抛出解析错误,修复方法是 `"\27[31m"`(十进制)或 `"\x1b[31m"`(十六进制)或 —— 最可读 —— `"\e[31m"`。 `std/terminal` 之外选用:**`std/colors`**(标准库颜色名表 —— `colWhite`、`colAliceBlue`,完整 HTML/CSS 调色板作为 `Color` 常量,搭配 `enableTrueColors()` 从命名常量发出 `38;2;R;G;B`);**`chronicles`**(status-im 的结构化日志库 —— Nim 规范日志库,开箱即用 ANSI 着色主题 / 级别 / 时间戳格式化,被所有 Nim 以太坊 / 区块链栈使用);**`illwill`**(johnnovak 的无 curses TUI 库 —— Nim 对 ncurses 的现代回答,自带 ANSI 后端,与 `std/terminal` 干净组合用于非全屏输出)。 能力门控:`std/terminal` 的 `isatty(stdout)` 在 stdout 重定向时返回 `false`。搭配 `getEnv("NO_COLOR")` 处理通用退出、`getEnv("FORCE_COLOR")` 处理强制启用。Windows VT 模式:`std/terminal` 在首次使用任何颜色辅助时自动用 `ENABLE_VIRTUAL_TERMINAL_PROCESSING` 调用 `SetConsoleMode`,所以你不需要在颜色调用周围加 `when defined(windows)` 门 —— Nim 处理它。Windows 10 1709 之前的构建(无 VT 支持的 Conhost)回退到直接 Win32 console-API 颜色调用,对你的代码透明。
推荐库
- std/terminal (stdlib)
任何编译型语言中最开箱即用的终端标准库。SGR(`setForegroundColor(fgRed)`)、样式(`setStyle({styleBright, styleUnderscore})`)、256-palette + truecolor(在 `enableTrueColors()` 之后)、光标(`setCursorPos(x, y)`、`cursorUp(n)`)、屏幕(`eraseScreen`、`eraseLine`)、raw-mode(`getch`)、终端大小(`terminalWidth()`、`terminalHeight()`、`terminalSize()`),以及 `isatty(stdout)` —— 全在一个 import 里。首次颜色调用时自动启用 Windows VT。`styledWrite(stdout, fgRed, "error")` 和 `styledEcho(fgRed, styleBright, "error")` 是规范的单行写法。
- std/colors (stdlib)
标准库颜色名表 —— 每个 HTML/CSS 命名颜色作为 `Color` 常量(`colWhite`、`colAliceBlue`、`colCornflowerBlue`,全部 147 个 X11/CSS3 名称)。配合 `enableTrueColors()` + `setForegroundColor(stdout, Color(...))` 从可读的名称发出 `38;2;R;G;B` 而不是裸 RGB 三元组。还暴露 `parseColor("#ff8000")` 解析十六进制字符串以及 `mix(a, b, t)` 插值 —— 生成渐变着色输出时有用。
- chronicles
Status-im 的结构化日志库 —— Nim 规范日志库,被 nimbus-eth1/2、waku、libp2p 以及多数 Nim 以太坊 / 区块链栈使用。开箱即用为级别(`debug` 青、`info` 绿、`warn` 黄、`error` 红、`fatal` 紫)、主题、时间戳、键 / 值对提供 ANSI 着色。编译期主题过滤意味着禁用主题零运行时成本。`info "started", port = 8080, ssl = true` 在 TTY 上产生彩色结构化行,重定向时产生纯 JSON —— 自动检测能力。
- illwill
无 curses TUI 库 —— Nim 对 ncurses 的现代回答。纯 Nim,无原生依赖(在 POSIX 上通过 raw ANSI 字节 + termios,在 Windows 上通过 SetConsoleMode 与终端通信)。提供 `TerminalBuffer`、双缓冲绘制循环、带修饰键的键盘轮询、颜色网格。需要重绘 UI(文件浏览器、编辑器、仪表板)但不想要 ncurses C 绑定脆弱性时选它。与 `std/terminal` 干净组合用于非全屏区段(如进入全屏模式前的状态栏)。
常用写法
# Nim accepts four ESC forms in "..." literals.
# Pick whichever your team reads fastest — all four
# compile to the same single byte (0x1b = 27).
#
# "\e" — ESC named escape (most readable)
# "\27" — \NNN is DECIMAL (the Nim twist!)
# "\x1b" — hex (exactly 2 digits)
# "\u001b" — Unicode 4-digit
#
# THE NIM TWIST: \NNN is DECIMAL — not octal like C,
# Crystal, Python, Ruby, etc. Nim's "\27" is byte 27.
# C's "\27" is byte \002 followed by character '7'.
# Porting from C? "\033" won't compile — use "\27"
# (decimal), "\x1b" (hex), or — best — "\e".
stdout.write "\e[1;31merror:\e[0m permission denied\n"
stdout.write "\27[33mwarn:\27[0m deprecated flag\n"
stdout.write "\x1b[32mok:\x1b[0m 142 tests passed\n"
stdout.write "\u001b[36minfo:\u001b[0m skipping cache\n"
# echo writes to stdout and adds a newline:
echo "\e[38;2;255;128;0morange truecolor\e[0m"
echo "\e[38;5;196m256-palette bright red\e[0m"
# Reusable helper — no stdlib needed:
proc esc(sgr, text: string): string =
"\e[" & sgr & "m" & text & "\e[0m"
echo esc("1;31", "FATAL"), " server crashed"
echo esc("32", "ok:"), " 142 tests passed"
echo esc("38;2;255;128;0", "truecolor orange")# Ships with every Nim install.
import std/terminal
# Basic SGR — fgRed/fgGreen/fgYellow/fgBlue/fgMagenta/fgCyan/fgWhite,
# bgRed/bgGreen/...; styleBright/styleDim/styleItalic/
# styleUnderscore/styleBlink/styleReverse/styleHidden/
# styleStrikethrough.
setForegroundColor(fgRed)
setStyle({styleBright})
stdout.write "error: "
resetAttributes()
echo "permission denied"
# styledEcho — one-liner, auto-reset, accepts an
# interleaved sequence of styles + strings:
styledEcho(fgGreen, styleBright, "ok: ", resetStyle, "142 tests passed")
styledEcho(fgYellow, "warn: ", resetStyle, "deprecated flag")
styledEcho(bgYellow, fgBlack, " CAUTION ", resetStyle, " brakes wet")
# 256-palette — Color() with a 0..255 index requires
# enableTrueColors() first (it switches stdout to the
# extended-color back end):
enableTrueColors()
for n in countup(0, 255, 16):
setForegroundColor(stdout, Color(n shl 16 or n shl 8 or n))
stdout.write n, " "
resetAttributes()
echo ""
# Truecolor — Color() built from 0xRRGGBB:
setForegroundColor(stdout, Color(0xFF8000)) # orange
echo "hot orange truecolor"
setForegroundColor(stdout, Color(0x006E82)) # deep teal
setStyle({styleBright})
echo "deep teal bold"
resetAttributes()
# Cursor + screen helpers — same module:
hideCursor()
setCursorPos(0, 0)
eraseScreen()
showCursor()# std/colors — 147 HTML/CSS named colours as Color
# constants; combine with std/terminal for readable output.
import std/[terminal, colors]
enableTrueColors()
# Named CSS colours as readable constants:
setForegroundColor(stdout, colCornflowerBlue)
echo "cornflower blue heading"
setForegroundColor(stdout, colTomato)
echo "tomato red error"
setForegroundColor(stdout, colMediumSeaGreen)
echo "medium sea green ok"
# parseColor() — read hex strings at runtime
# (e.g. from config files):
setForegroundColor(stdout, parseColor("#ff8000"))
echo "from hex string"
# mix() — interpolate between two colours,
# useful for gradient-coloured output:
let a = colRed
let b = colYellow
for t in 0..10:
setForegroundColor(stdout, mix(a, b, t.float / 10.0))
stdout.write "█"
resetAttributes()
echo ""
# ─── chronicles structured logging ───
# nimble install chronicles
import chronicles
# Compile-time topic filter — disabled topics cost zero
# at runtime. Enable via -d:chronicles_enabled_topics=...
logScope:
topics = "api"
service = "users"
info "request received", method = "GET", path = "/users/42"
warn "slow query", ms = 1240, table = "users"
error "auth failed", user_id = 42, reason = "expired_token"
# Output on a TTY:
# INF 2026-05-19 22:01:15.123 request received topics="api" ...
# Output piped to a file: plain JSON, no escapes.
# Auto-detection — no config needed.# Capability gating in Nim:
# isatty(stdout) — std/terminal, returns false when
# stdout is piped/redirected
# getEnv("NO_COLOR") — universal opt-out env var
# getEnv("FORCE_COLOR") — universal opt-in env var
#
# std/terminal auto-enables Windows VT on first colour call,
# so you do NOT need a "when defined(windows)" gate around
# your setForegroundColor calls — Nim handles it.
import std/[terminal, os]
proc ansiCapable(): bool =
if existsEnv("NO_COLOR"): return false
if existsEnv("FORCE_COLOR"): return true
isatty(stdout)
proc styled(text, sgr: string): string =
if ansiCapable(): "\e[" & sgr & "m" & text & "\e[0m"
else: text
echo styled("OK", "32")
echo styled("FAIL", "1;31")
# ─── illwill — pure-Nim TUI, no ncurses ───
# nimble install illwill
import illwill
proc exitProc() {.noconv.} =
illwillDeinit()
showCursor()
quit(0)
illwillInit(fullscreen = true)
setControlCHook(exitProc)
hideCursor()
var tb = newTerminalBuffer(terminalWidth(), terminalHeight())
while true:
tb.clear()
tb.write(2, 1, fgYellow, "illwill demo — q to quit",
resetStyle)
tb.write(2, 3, fgGreen, "✓ ok ", resetStyle, "142 tests passed")
tb.write(2, 4, fgRed, "✗ FAIL ", resetStyle, "3 tests failed")
tb.write(2, 5, fgCyan, "ℹ info ", resetStyle, "skipping cache")
tb.display()
let key = getKey()
case key
of Key.Q, Key.Escape: exitProc()
else: discard
sleep(20)