Getting Started¶
Mordant is split into a number of modules to allow you to pick the functionality you need. On JVM,
there are several modules that implement TerminalInterface
using different dependencies.
// Adds all JVM interface modules
implementation("com.github.ajalt.mordant:mordant:$mordantVersion")
// optional extensions for running animations with coroutines
implementation("com.github.ajalt.mordant:mordant-coroutines:$mordantVersion")
// optional widget for rendering Markdown
implementation("com.github.ajalt.mordant:mordant-markdown:$mordantVersion")
// The `:mordant-core` module doesn't include any JVM interface modules, so you'll need one or
// more if you're targeting JVM. If you don't include any, features like raw mode and size
// detection won't work, but colors and styles still will.
implementation("com.github.ajalt.mordant:mordant-core:$mordantVersion")
// This module uses the Java Foreign Function and Memory API. It requires JDK 22+, and you must
// add `--enable-native-access=ALL-UNNAMED` to your `java` command line arguments.
implementation("com.github.ajalt.mordant:mordant-jvm-ffm:$mordantVersion")
// This module uses the Java Native Access library. It supports all versions of Java, but
// requires linking to a bundled native library, so it increases your JAR size.
implementation("com.github.ajalt.mordant:mordant-jvm-jna:$mordantVersion")
// This module uses the GraalVM Native Image FFI interface. This only supports Graal Native Image.
implementation("com.github.ajalt.mordant:mordant-jvm-graal-ffi:$mordantVersion")
// optional extensions for running animations with coroutines
implementation("com.github.ajalt.mordant:mordant-coroutines:$mordantVersion")
// optional widget for rendering Markdown
implementation("com.github.ajalt.mordant:mordant-markdown:$mordantVersion")
Text Colors and Styles¶
Create a Terminal instance, and import any enum entries you want from TextColors and
TextStyles. The println function on your Terminal
will detect your current terminal
capabilities and automatically downsample colors if necessary.
Use color.bg
to create a background color, or color1 on color2
to
combine a foreground and background.
import com.github.ajalt.mordant.rendering.TextColors.*
import com.github.ajalt.mordant.rendering.TextStyles.*
import com.github.ajalt.mordant.terminal.Terminal
import com.github.ajalt.mordant.rendering.TextColors.Companion.rgb
val t = Terminal()
t.println(brightRed("You can use any of the standard ANSI colors"))
val style = (bold + black + strikethrough)
t.println(
cyan("You ${(green on white)("can ${style("nest")} styles")} arbitrarily")
)
t.println(rgb("#b4eeb4")("You can also use true color and color spaces like HSL"))
You can use any of the standard ANSI colors You can nest styles arbitrarily You can also use true color and other color spaces like HSL
Terminal color support detection¶
By default, Terminal()
will try to detect ANSI support in the current stdout stream. If you’d
like to override the detection, you can pass a specific value to the Terminal constructor.
For example, to always output ANSI RGB color codes, even if stdout is currently directed to a file, you can do this:
Terminal(AnsiLevel.TRUECOLOR)
Text Wrapping and Alignment¶
Terminal.println
will preserve whitespace by default, but you can use the Text
widget
for more advanced formatting. You can use the Whitespace, TextAlign, and OverflowWrap enums to
format text. They behave similar to the CSS properties of the same names.
Text Wrapping¶
Pass one of the Whitespace values to the Text
constructor to control how whitespace is handled.
You can also set a width
to wrap the text to a specific width rather than the terminal width.
val text = """
This is a long line {
This line is indented
}
""".trimIndent()
for (entry in Whitespace.entries) {
terminal.println(
Panel(
content = Text(text, whitespace = entry, width = 17),
title = Text(entry.name)
)
)
}
╭──── NORMAL ────╮ │This is a long │ │line { This line│ │is indented } │ ╰────────────────╯ ╭────────────────── NOWRAP ───────────────────╮ │This is a long line { This line is indented }│ ╰─────────────────────────────────────────────╯ ╭────────── PRE ──────────╮ │This is a long line { │ │ This line is indented│ │} │ ╰─────────────────────────╯ ╭─── PRE_WRAP ───╮ │This is a long │ │line { │ │ This line is│ │indented │ │} │ ╰────────────────╯ ╭── PRE_LINE ──╮ │This is a long│ │line { │ │This line is │ │indented │ │} │ ╰──────────────╯
Tip
You can format styled text too.
Text(red("Hello, world!"), whitespace = NORMAL)
Text Alignment¶
You can use the TextAlign values to align or justify text.
for (entry in TextAlign.entries) {
terminal.println(
Text(
(black on blue)("align = ${entry.name}"),
align = entry, width = 20
),
)
}
align = LEFT align = RIGHT align = CENTER align = JUSTIFY align = NONE
Text Overflow¶
If you are wrapping text that has long words that exceed the line length by themselves, you can use the OverflowWrap enum to control how they are handled.
for (entry in OverflowWrap.entries) {
terminal.println(
Panel(
content = Text(
"overflow_wrap",
whitespace = Whitespace.NORMAL,
overflowWrap = entry,
width = 8
),
title = Text(entry.name)
)
)
}
╭── NORMAL ───╮ │overflow_wrap│ ╰─────────────╯ ╭ BREAK_WORD ─╮ │overflow │ │_wrap │ ╰─────────────╯ ╭─ TRUNCATE ──╮ │overflow │ ╰─────────────╯ ╭─ ELLIPSES ──╮ │overflo… │ ╰─────────────╯
Note
OverflowWrap
has no effect when used with Whitespace.PRE
or Whitespace.NOWRAP
.
Tables¶
Use the table DSL to define table widgets.
t.println(table {
header { row("Column 1", "Column 2") }
body { row("1", "2") }
})
┌──────────┬──────────┐ │ Column 1 │ Column 2 │ ├──────────┼──────────┤ │ 1 │ 2 │ └──────────┴──────────┘
Mordant gives you lots of customization for your tables, including striped row styles, row and column spans, and different border styles.
Percent Change 2020 2021 2022 2020-21 2021-21 ═════════════════════════════╤═════════════════════════════╤══════════════════ Average income before taxes │ $84,352 $87,432 $94,003 │ 3.7 7.5 ─────────────────────────────┼─────────────────────────────┼────────────────── Average annual expenditures │ $61,332 $66,928 $72,967 │ 9.1 9.0 ─────────────────────────────┼─────────────────────────────┼────────────────── Food │ 7,310 8,289 9,343 │ 13.4 12.7 ─────────────────────────────┼─────────────────────────────┼────────────────── Housing │ 21,417 22,624 24,298 │ 5.6 7.4 ─────────────────────────────┼─────────────────────────────┼────────────────── Apparel and services │ 1,434 1,754 1,945 │ 22.3 10.9 ─────────────────────────────┼─────────────────────────────┼────────────────── Transportation │ 9,826 10,961 12,295 │ 11.6 12.2 ─────────────────────────────┼─────────────────────────────┼────────────────── Healthcare │ 5,177 5,452 5,850 │ 5.3 7.3 ─────────────────────────────┼─────────────────────────────┼────────────────── Entertainment │ 2,909 3,568 3,458 │ 22.7 -3.1 ─────────────────────────────┼─────────────────────────────┼────────────────── Education │ 1,271 1,226 1,335 │ -3.5 8.9 ═════════════════════════════╪═════════╤═════════╤═════════╪══════════════════ Remaining income │ $23,020 │ $20,504 │ $21,036 │ via U.S. Bureau of Labor Statistics
table {
borderType = SQUARE_DOUBLE_SECTION_SEPARATOR
borderStyle = rgb("#4b25b9")
align = RIGHT
tableBorders = NONE
header {
style = brightRed + bold
row {
cellBorders = NONE
cells("", "", "", "")
cell("Percent Change") {
columnSpan = 2
align = CENTER
}
}
row("", "2020", "2021", "2022", "2020-21", "2021-21") { cellBorders = BOTTOM }
}
body {
style = green
column(0) {
align = LEFT
cellBorders = ALL
style = brightBlue
}
column(4) {
cellBorders = LEFT_BOTTOM
style = brightBlue
}
column(5) {
style = brightBlue
}
rowStyles(TextStyle(), dim.style)
cellBorders = TOP_BOTTOM
row("Average income before taxes", "$84,352", "$87,432", "$94,003", "3.7", "7.5")
row("Average annual expenditures", "$61,332", "$66,928", "$72,967", "9.1", "9.0")
row(" Food", "7,310", "8,289", "9,343", "13.4", "12.7")
row(" Housing", "21,417", "22,624", "24,298", "5.6", "7.4")
row(" Apparel and services", "1,434", "1,754", "1,945", "22.3", "10.9")
row(" Transportation", "9,826", "10,961", "12,295", "11.6", "12.2")
row(" Healthcare", "5,177", "5,452", "5,850", "5.3", "7.3")
row(" Entertainment", "2,909", "3,568", "3,458", "22.7", "-3.1")
row(" Education", "1,271", "1,226", "1,335", "-3.5", "8.9")
}
footer {
style(italic = true)
row {
cells("Remaining income", "$23,020", "$20,504", "$21,036")
}
}
captionBottom(dim("via U.S. Bureau of Labor Statistics"))
}
Layout¶
If you need to lay out multiple widgets or strings, you can use the grid builder, which has an API
similar to table
, but doesn’t apply styling by default
grid {
row("Grid Builder", "Supports", "Alignment")
row {
cell("Left") { align = LEFT }
cell("Center") { align = CENTER }
cell("Right") { align = RIGHT }
}
}
Grid Builder Supports Alignment Left Center Right
There are also the horizontalLayout and verticalLayout builders if you don’t need a full grid.
horizontalLayout {
cell("Spinner:")
cell(Spinner.Dots(initial = 2))
}
Spinner: ⠹
Controlling the cursor¶
You can show and hide the cursor, move it around, and clear parts of the screen with the cursor
property on Terminal
. If your terminal doesn’t support cursor movements (like when output is
redirected to a file) these commands are no-ops.
val t = Terminal()
t.cursor.move {
up(3)
startOfLine()
clearScreenAfterCursor()
}
t.cursor.hide(showOnExit = true)
Animations¶
You can animate any widget like a table with Terminal.animation, or any regular string with Terminal.textAnimation. For progress bar animations, see the docs on progress bars.
val terminal = Terminal()
val a = terminal.textAnimation<Int> { frame ->
(1..50).joinToString("") {
val hue = (frame + it) * 3 % 360
TextColors.hsv(hue, 1, 1)("━")
}
}
terminal.cursor.hide(showOnExit = true)
repeat(120) {
a.update(it)
Thread.sleep(25)
}
Tip
If you have an animation<Unit>
or textAnimation<Unit>
, you can refresh them automatically with
animateOnThread or animateOnCoroutine.
Prompting for input¶
You can ask the user to enter text and wait for a response with Terminal.prompt:
val t = Terminal()
val response = t.prompt("Choose a size", choices=listOf("small", "large"))
t.println("You chose: $response")
$ ./example
Choose a size [small, large]: small
You chose: small
You can customize the prompt behavior further or convert the response to other types creating a subclass of the Prompt class. Mordant includes StringPrompt, YesNoPrompt, and ConfirmationPrompt classes for common use cases.