Skip to main content
ansicode
Nim

ANSI escape codes in Nim — std/terminal, \e, decimal \27

Nim's stdlib `std/terminal` is the most comprehensive batteries-included terminal module of any compiled language — a single import gives you SGR colour (`setForegroundColor(fgRed)`), text style (`setStyle({styleBright, styleUnderscore})`), 256-palette via `Color(...)`, truecolor (with an explicit `enableTrueColors()` opt-in), cursor positioning, screen erase, terminal-size queries, raw-mode toggle, and isatty all in one place. For most apps the dependency list ends right there. String-literal forms: Nim accepts `"\e"` (ESC named escape), `"\x1b"` (hex, exactly 2 digits), `"\u001b"` (4-digit Unicode), and `"\u{1b}"` (variable-width Unicode). **The Nim twist** — and the page's primary SERP differentiator — is that `"\NNN"` is **decimal**, not octal like every C-family language. `"\27"` in Nim is byte 27 (ESC); the same literal in C, Crystal, Python, or Ruby is byte `\002` followed by the character `'7'`. If you're porting ANSI code from another language and `"\033[31m"` is throwing a parse error, the fix is `"\27[31m"` (decimal) or `"\x1b[31m"` (hex) or — most readably — `"\e[31m"`. For everything beyond `std/terminal` reach for: **`std/colors`** (stdlib colour-name table — `colWhite`, `colAliceBlue`, full HTML/CSS palette as `Color` constants, paired with `enableTrueColors()` to emit `38;2;R;G;B` from the named constant); **`chronicles`** (status-im's structured logger — the canonical Nim logging library, ships ANSI colour topic / level / timestamp formatting out of the box, used by every Nim Ethereum/blockchain stack); **`illwill`** (johnnovak's curses-free TUI library — the modern Nim answer to ncurses, ships its own ANSI back end so it composes cleanly with `std/terminal` for non-fullscreen output). Capability gating: `isatty(stdout)` from `std/terminal` returns `false` when stdout is redirected. Pair with `getEnv("NO_COLOR")` for the universal opt-out and `getEnv("FORCE_COLOR")` for opt-in. Windows VT mode: `std/terminal` calls `SetConsoleMode` with `ENABLE_VIRTUAL_TERMINAL_PROCESSING` automatically on first use of any colour helper, so you don't need a `when defined(windows)` gate around your colour calls — Nim handles it. Pre-Windows-10-1709 builds (Conhost without VT support) fall back to direct Win32 console-API colour calls, transparent to your code.

Recommended libraries

  • std/terminal (stdlib)

    The most batteries-included terminal stdlib of any compiled language. SGR (`setForegroundColor(fgRed)`), style (`setStyle({styleBright, styleUnderscore})`), 256-palette + truecolor (after `enableTrueColors()`), cursor (`setCursorPos(x, y)`, `cursorUp(n)`), screen (`eraseScreen`, `eraseLine`), raw-mode (`getch`), terminal-size (`terminalWidth()`, `terminalHeight()`, `terminalSize()`), and `isatty(stdout)` — all in one import. Auto-enables Windows VT on first colour call. `styledWrite(stdout, fgRed, "error")` and `styledEcho(fgRed, styleBright, "error")` are the canonical one-liners.

  • std/colors (stdlib)

    Stdlib colour-name table — every HTML/CSS named colour as a `Color` constant (`colWhite`, `colAliceBlue`, `colCornflowerBlue`, ...all 147 X11/CSS3 names). Combine with `enableTrueColors()` + `setForegroundColor(stdout, Color(...))` to emit `38;2;R;G;B` from a readable name instead of raw RGB triples. Also exposes `parseColor("#ff8000")` for hex strings and `mix(a, b, t)` for interpolation — useful when generating gradient-coloured output.

  • chronicles

    Status-im's structured logger — the canonical Nim logging library, used by nimbus-eth1/2, waku, libp2p, and most Nim Ethereum/blockchain stacks. Ships ANSI colouring for level (`debug` cyan, `info` green, `warn` yellow, `error` red, `fatal` magenta), topic, timestamp, and key/value pairs out of the box. Compile-time topic filtering means zero runtime cost for disabled topics. `info "started", port = 8080, ssl = true` produces a coloured structured line on a TTY, plain JSON when redirected — capability auto-detect.

  • illwill

    Curses-free TUI library — the modern Nim answer to ncurses. Pure Nim, no native dependency (talks to the terminal via raw ANSI bytes + termios on POSIX / SetConsoleMode on Windows). Provides `TerminalBuffer`, double-buffered draw loop, keyboard polling with modifier keys, colour grids. Reach for it when you need a redrawing UI (file browsers, editors, dashboards) without the C-binding fragility of ncurses. Composes cleanly with `std/terminal` for non-fullscreen sections (e.g. status bar before entering full-screen mode).

Idiomatic patterns

Direct stdout.write with \e — and the \NNN-is-decimal Nim twist
# 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")
std/terminal — setForegroundColor, styledEcho, truecolor opt-in
# 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 + chronicles — named-colour table and structured logging
# 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 gate + illwill TUI — isatty, NO_COLOR, double-buffered draw
# 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)

Related sequences

Other languages