Skip to content

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.