Note
This library was created as part of the “Funktionale Programmierung in Lean” module as an examination requirement at the University of Applied Sciences Mittelhessen.
__ _ / / ___ ____ _ ____ _____ (_) / / / _ \ / __ `/ / __ \ / ___/ / / / /___/ __// /_/ / / / / / (__ ) / / /_____/\___/ \__,_/ /_/ /_/ /____/ /_/
A Lean 4 library for readable terminal output.
Leansi is a Lean 4 library for building terminal output from structured documents. It provides composable styling, color fallback (ANSI16/ANSI256/RGB), alignment, table-like layouts, terminal capability detection, and progress bars for CLI applications.
This repository is pinned to:
leanprover/lean4:v4.28.0
To build the library in this repository:
lake buildTo use Leansi from another Lean project, add it as a Lake dependency in lakefile.toml:
[[require]]
name = "leansi"
git = "git@github.com:schergen-org/Leansi.git"Then build your project as usual:
lake buildThe repository also contains an optional showcase executable:
lake exe exampleThe public entrypoint is the root module:
import leansi
open leansi
open leansi.Doc
def main : IO Unit := do
let msg : Doc Style := Doc.text "Leansi demo" |> bold |> bright_cyan
println msgDoc ann is the core document type. It keeps text structure separate from annotations so layout and rendering can happen later.
Use Doc.text, Doc.empty, and ++ to build documents:
let a := Doc.text "Hello"
let b := Doc.text " world"
let line : Doc Style := a ++ b
println lineStyle combinators live in the leansi.Doc namespace and operate on Doc Style values.
let styled :=
(Doc.text "bold" |> bold) ++ Doc.text ", " ++
(Doc.text "underline" |> underline) ++ Doc.text ", " ++
(Doc.text "italic" |> italic)
println styledAvailable style attributes include:
bolddimitalicunderlineblinkreversehiddenstrikethrough
Available color APIs include:
- ANSI16 foreground/background helpers such as
bright_red,cyan,bg_blue - ANSI256 via
fg_ansi_256 nandbg_ansi_256 n - RGB via
fg_rgb r g bandbg_rgb r g b
println (Doc.text "RGB" |> fg_rgb 100 150 200)
println (Doc.text "ANSI256" |> fg_ansi_256 54 |> bg_ansi_256 200)For direct terminal output, use:
println : Doc Style -> IO Unitprint : Doc Style -> IO Unit
These functions automatically detect terminal color support before rendering.
If you want a plain String instead of immediate IO, use the lower-level render API:
Doc.renderignores annotationsDoc.renderWithStylerenders a styled document with an explicitColorSupportrenderis a convenience wrapper forDoc Unit
detectColorSupport inspects common environment variables and caches the result.
Current detection behavior:
NO_COLORdisables colors entirelyTERM=dumbdisables colors entirelyCOLORTERM=truecolororCOLORTERM=24bitenables truecolorTERMcontaining256colorenables ANSI256- any other non-empty
TERMfalls back to ANSI16
let support ← detectColorSupport
println (Doc.text s!"Detected color support: {support}" |> bright_cyan)If the terminal supports fewer colors than requested, Leansi downscales automatically:
- truecolor -> ansi256 or ansi16
- ansi256 -> ansi16
- any color -> none when colors are disabled
Use alignDoc with Alignment.left, Alignment.right, Alignment.center, or Alignment.full.
let p := Doc.text "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
println (alignDoc 40 Alignment.left p)
println (alignDoc 40 Alignment.center p)
println (alignDoc 40 Alignment.right p)
println (alignDoc 40 Alignment.full p)Alignment.full performs simple space redistribution for terminal-friendly justification.
Layout provides composition helpers for CLI UIs:
Layout.hcatfor horizontal concatenationLayout.hcatSepfor horizontal concatenation with gapsLayout.vcatfor vertical stackingLayout.columnsfor fixed-width, table-like output
let row :=
Layout.columns [12, 18, 10] 2
[ Doc.text "Package" |> bold
, Doc.text "Component" |> bold
, Doc.text "State" |> bold
]
[Alignment.left, Alignment.center, Alignment.right]
println rowLayout.columns also handles overflow:
hideOverflow = trueclips cell content to column widthhideOverflow = falsewraps cell content into multiple linesuseMinRows = truetruncates the final output to the shortest columnuseMinRows = falsekeeps all rows up to the tallest column
Use getTerminalDimensions directly or Layout.fitToTerminal when you want terminal-aware alignment.
let banner ← Layout.fitToTerminal Alignment.center (Doc.text "Simple Layout Demo" |> bold)
println bannergetTerminalDimensions is best-effort:
- it first checks
LINESandCOLUMNS - on Linux/macOS it falls back to
stty size < /dev/tty - on Windows it queries PowerShell
- it can return
nonewhen no real TTY is attached, for example in CI or some IDE runs
Leansi includes a ready-to-use progress bar widget:
progressBarsimpleProgressBarProgressBarConfigProgressThreshold
let customConfig : ProgressBarConfig := {
width := 22
filled := '▓'
empty := '·'
brackets := none
thresholds := [
{ upperBound := 50, color := ColorLevel.truecolor (255, 100, 100) },
{ upperBound := 100, color := ColorLevel.truecolor (100, 255, 100) }
]
}
println (Doc.text "Upload: " ++ progressBar customConfig 65)- Structured
Doctrees instead of raw string concatenation. - Composable style helpers in
leansi.Doc. - ANSI16, ANSI256, and truecolor color requests.
- Automatic color fallback based on terminal support.
- Runtime terminal capability detection with caching.
- Alignment helpers for left, right, center, and full justification.
- Table-like column layout with wrapping or clipping.
- Best-effort terminal dimension detection.
- Progress bar widgets with configurable thresholds and visuals.
- Low-level rendering APIs for non-IO use cases.
Example.lean demonstrates:
- color support and downsampling
- the available style combinators
- left/center/right/full alignment
- terminal color and dimension detection
- layout composition with
Layout.vcatandLayout.columns - default and customized progress bars
Run it with:
lake exe example