Git Product home page Git Product logo

mordant's Introduction

Mordant is a multiplatform library for rendering styled text in the terminal. You can use it to add color and style to text, create tables, draw animations, and more.

Mordant has:

  • Easy colorful ANSI output with automatic detection of terminal capabilities
  • Markdown rendering directly to the terminal
  • Widgets for laying out terminal output, including lists, tables, panels, and more
  • Support for animating any widget, like progress bars and dashboards

Documentation

The full documentation can be found on the website.

Installation

Mordant is distributed through Maven Central.

dependencies {
    implementation("com.github.ajalt.mordant:mordant:2.5.0")

    // optional extensions for running animations with coroutines
    implementation("com.github.ajalt.mordant:mordant-coroutines:2.5.0")
}
If you're using Maven instead of Gradle, use <artifactId>mordant-jvm</artifactId>

Snapshots

Snapshot builds are also available

You'll need to add the Sonatype snapshots repository:

repositories {
    maven {
        url = uri("https://oss.sonatype.org/content/repositories/snapshots/")
    }
}

License

Copyright 2018 AJ Alt

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

mordant's People

Contributors

ajalt avatar asemy avatar chase22 avatar drewcarlson avatar goooler avatar hepptho avatar hubvd avatar jakobkmar avatar kokorins avatar martinbonnin avatar mikehearn avatar morki avatar simonmarquis avatar sschuberth avatar suminb avatar ydubreuil avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

mordant's Issues

Mordant 2.0 terminal capabilities

I have some markdown with a link in a table cell.

It works great on iTerm2 but not on the Apple Terminal (the entire URL is shown).

Not sure this is a Mordant issue. If not, it would be nice to have a function that says if a component is not supported on the current terminal.

IntelliJ detection misfires if the program has a command line containing the word "idea"

This line in TerminalDetection.kt:

getJavaProperty("sun.java.command")?.contains("idea", ignoreCase = true) == true

will cause terminal corruption if you run a program with the word "idea" anywhere in the command line arguments. (yup, debugging this one was fun ๐Ÿ˜† ). I filed JetBrains/jediterm#253 to request a specific env var to be set, but for now, the sun.java.command check should probably be removed. The others look fairly specific but this one is too broad and will cause obscure bugs for any tool that is used on files in the default IntelliJ project directory (e.g. ~/IdeaProjects).

Colour not being detected

Using intellij, just a simple function to output some colour.
JDK11, Intellij 2018.3, Mordant 1.2.0

image

image

Animation issue

Hello AJ,

nice to see this project is rocking!

Anyway, I quickly tried the animation sample and after a few seconds I get:

Exception in thread "Thread-0" java.lang.IllegalStateException: Shutdown in progress
	at java.base/java.lang.ApplicationShutdownHooks.remove(ApplicationShutdownHooks.java:82)
	at java.base/java.lang.Runtime.removeShutdownHook(Runtime.java:244)
	at com.github.ajalt.mordant.internal.JvmTerminalCursor.show(MppImpl.kt:92)
	at com.github.ajalt.mordant.internal.JvmTerminalCursor.hide$lambda-3$lambda-2(MppImpl.kt:102)
	at java.base/java.lang.Thread.run(Thread.java:829)

Also, I noticed I have to use mordant-jvm also on kotlin scripts, you may want to add that to the readme

Using colormath in conjunction with mordant (?)

I'm currently struggling a bit to use some functionality of the colormath library in a project together with mordant. From the buildscripts it seems that mordant does not directly depend on colormath, but includes its API for interoperability. This leads me to believe that using mordant and colormath in conjunction a thing.

Since mordant only depends on the API, it does not pull in the colormath package, which means that I have to include it myself in my buildscript to use functionality from there. When I do that, though, I get java.lang.NoClassDefFoundErrors for various colormath classes at runtime (RGB for example).

For example:

Exception in thread "main" java.lang.NoClassDefFoundError: com/github/ajalt/colormath/RGB
	at com.github.ajalt.mordant.rendering.Theme.<clinit>(Theme.kt:13)
	at com.github.ajalt.mordant.terminal.Terminal.<init>(Terminal.kt:24)
	at [my code]
Caused by: java.lang.ClassNotFoundException: com.github.ajalt.colormath.RGB
	at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
	at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520)
	... 4 more

I am therefore not entirely sure whether I am doing something wrong. Maybe the versions don't match?

In any case I'd be thankful for some insight as to how using colormath and mordant together is done correctly.

Thanks!

Version Data:

Kotlin/JVM 1.6.10 (JVM target 17, also tested with 11)
mordant 2.0.0-beta2
colormath 3.2.0
OpenJDK 17.0.2-8
Gradle 7.3
Operating System: Windows 11 Pro x86_64

Progress bar: length must be 2 or greater, was 1

See russellbanks/Komac#102:

GitHub runners sometimes produce this error with progress bars:

   Exception in thread "Timer-0" java.lang.IllegalArgumentException: length must be 2 or greater, was 1
  	at com.github.ajalt.colormath.transform.InterpolateKt.sequence(Interpolate.kt:158)
  	at com.github.ajalt.mordant.widgets.ProgressBar.makeComplete(ProgressBar.kt:107)
  	at com.github.ajalt.mordant.widgets.ProgressBar.render(ProgressBar.kt:86)
  	at com.github.ajalt.mordant.widgets.Padded.render(Padding.kt:99)
  	at com.github.ajalt.mordant.table.TableRenderer.<init>(Table.kt:246)
  	at com.github.ajalt.mordant.table.TableImpl.render(Table.kt:121)
  	at com.github.ajalt.mordant.rendering.Widget$DefaultImpls.render$default(Widget.kt:7)
  	at com.github.ajalt.mordant.animation.Animation.update(Animation.kt:76)
  	at com.github.ajalt.mordant.animation.ProgressAnimation.tick(ProgressAnimation.kt:164)
  	at com.github.ajalt.mordant.animation.ProgressAnimation.access$tick(ProgressAnimation.kt:91)
  	at com.github.ajalt.mordant.animation.ProgressAnimation$start$1.invoke(ProgressAnimation.kt:154)
  	at com.github.ajalt.mordant.animation.ProgressAnimation$start$1.invoke(ProgressAnimation.kt:153)
  	at com.github.ajalt.mordant.animation.JvmTicker$start$$inlined$timer$default$1.run(Timer.kt:149)
  	at java.base/java.util.TimerThread.mainLoop(Timer.java:566)
  	at java.base/java.util.TimerThread.run(Timer.java:516)
fun getDownloadProgressBar(url: Url, terminal: Terminal): ProgressAnimation {
    return terminal.progressAnimation {
        url.getFileName()?.let { text(it) }
        percentage()
        progressBar()
        completed()
        speed("B/s")
        timeRemaining()
    }
}
getDownloadProgressBar(url, this).run {
    start()
    prepareGet(url).execute { httpResponse ->
        val channel: ByteReadChannel = httpResponse.body()
        while (!channel.isClosedForRead) {
            val packet = channel.readRemaining(DEFAULT_BUFFER_SIZE.toLong())
            while (packet.isNotEmpty) {
                file.appendBytes(packet.readBytes())
                update(file.length(), httpResponse.contentLength())
            }
        }
    }
    clear()
}

Support animations in non-interactive consoles

Some IDEs have partial ANSI support. They know how to interpret color codes but can't change the cursor position. Fortunately, they understand carriage returns (CR). So, it's possible for them to support some animations if we don't print line feeds.

The logic behind the animation loop seems to be: do a carriage return, then move the cursor up by one line and finally print the animation ending with a line feed. What if we don't print that line feed?

No border between rows, or bottom border when borders = Borders.LEFT_RIGHT

I am trying to get this table layout:

+-----------------+-------------+----------+
| Column A        | Column B    | Column C |
+-----------------+-------------+----------+
| Lorem Ipsum...  | 123         | A        |
| Lorem Ipsum...  | 123         | B        |
| Lorem Ipsum...  | 123         | C        |
+-----------------+-------------+----------+

i.e I don't want borders between the rows, as in the default:

+-----------------+-------------+----------+
| Column A        | Column B    | Column C |
+-----------------+-------------+----------+
| Lorem Ipsum...  | 123         | A        |
+-----------------+-------------+----------+
| Lorem Ipsum...  | 123         | B        |
+-----------------+-------------+----------+
| Lorem Ipsum...  | 123         | C        |
+-----------------+-------------+----------+

So I used borders = Borders.LEFT_RIGHT:

borderType = BorderType.ASCII
header {
    row("Column A", "Column B", "Column C")
}
body {
    borders = Borders.LEFT_RIGHT
    for (thing in things) {
        ...
    }
}

What I get now is:

+-----------------+-------------+----------+
| Column A        | Column B    | Column C |
+-----------------+-------------+----------+
| Lorem Ipsum...  | 123         | A        |
| Lorem Ipsum...  | 123         | B        |
| Lorem Ipsum...  | 123         | C        |

i.e the price is the bottom border.

The only way I found to overcome this is to do something really nasty:

borderType = BorderType.ASCII
header {
    row("Column A", "Column B", "Column C")
}
body {
    borders = Borders.LEFT_RIGHT
    for ((idx, thing) in things.withIndex()) {
        row {
            cells(...)
            if (idx == things.lastIndex) {
                borders = Borders.LEFT_RIGHT_BOTTOM
            }
        }
    }
}

Which I assume isn't what we want to demand from the user of the library...

So either I'm missing the really simple way to do this, or it's missing.

Module classpath missing

Bug Report

Importing the library on a project running KMM version 1.7.10 causes the Task compileCommonMainKotlinMetadata to fail.

According to Kotlin docs projects running Kotlin v1.6.20 come with a hierarchical project structure. I suspect this causes compatibility issues with newer projects.

Versions

kotlin("multiplatform") version "1.7.10"
implementation("com.github.ajalt.mordant:mordant:2.0.0-beta7") // pulls colormath:3.2.0

Error

Cannot access 'com.github.ajalt.colormath.Color' which is a supertype of 'com.github.ajalt.mordant.rendering.TextColors'. Check your module classpath for missing or conflicting dependencies

Possible solution

  • Upgrade KMM to version >1.6.20
  • Include configuration described in the docs Kotlin docs

Temporary fix for newer projects

Temporary disable the compileCommonMainKotlinMetadata task directly in your build.gradle.kts

tasks.matching { it.name == "compileCommonMainKotlinMetadata" }.all {
    enabled = false
}

Protected strings such as URLs

If you render a widget containing an URL/URI โ€” depending on the whitespace setting โ€” it might be wrapped
which makes IntelliJ no longer detect it.

Consequently, you can no longer click on it.

It would be great if there was a means to protect certain strings, e.g. by a regular expression.

A grid with a single column and fixed width n only hold n-1 characters

The following code should render a grid with a single cell (fixed 80 width)
and its content of 80 1-width characters in a single line.

Terminal().render(
                    grid {
                        cellBorders = NONE
                        padding(0, 0, 0, 0)
                        column(0) {
                            width = ColumnWidth.Fixed(80)
                        }
                        row {
                            whitespace = PRE_LINE
                            verticalAlign = TOP
                            overflowWrap = BREAK_WORD
                            cell("Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod.")
                        }
                    }
                )

Expected

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod.

Actual

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy
eirmod.

Removing one character from the content string makes the grid widget consume only a single line.

Race on Terminal.interceptors

Terminal.interceptors should only be modified whilst holding Terminal.lock but the lock isn't taken in Terminal.addInterceptor or Terminal.removeInterceptor.

I had a quick go at fixing this but you can't use synchronized() in non-JVM code. Actually I'm not sure how you're meant to do locking like this in MPP code.

Exception in thread "Thread-0" java.util.ConcurrentModificationException
        at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1013)
        at java.base/java.util.ArrayList$Itr.next(ArrayList.java:967)
        at com.github.ajalt.mordant.internal.MppImplKt.sendInterceptedPrintRequest(MppImpl.kt:160)
        at com.github.ajalt.mordant.terminal.Terminal.sendPrintRequest(Terminal.kt:269)
        at com.github.ajalt.mordant.terminal.Terminal.rawPrint(Terminal.kt:265)
        at com.github.ajalt.mordant.terminal.Terminal.print(Terminal.kt:194)
        at com.github.ajalt.mordant.terminal.PrintTerminalCursor.show(TerminalCursor.kt:158)
        at com.github.ajalt.mordant.internal.JvmTerminalCursor.show(MppImpl.kt:113)
        at hydraulic.factory.progress.FancyTerminalParallelProgressTracker.close(FancyTerminalParallelProgressTracker.kt:390)
        at hydraulic.factory.progress.FancyTerminalParallelProgressTracker.shutdownHook$lambda-0(FancyTerminalParallelProgressTracker.kt:46)
        at java.base/java.lang.Thread.run(Thread.java:833)

Adding log appender

Hey, is it possible to inject/add a log appender for writing the messages in a log file?

Widgets don't compute correct width for Emoji sequences

If you render a Widget, e.g. a grid, emoji sequences like

  • regional letters to make up flags, e.g. ๐Ÿ‡ฉ๐Ÿ‡ช
  • emojis with skin tone modifier, e.g. ๐Ÿ‘จ๐Ÿพโ€๐Ÿฆฑ
  • ZWJ (ZERO WIDTH JOINER) joined emojis , e.g. ๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ
    seem to render with an incorrectly rendered width.

Sample:

 Terminal().render(
                    grid {
                        cellBorders = NONE
                        it.forEachIndexed { i, (_, maxWidth) ->
                            column(i) {
                                width = ColumnWidth.Fixed(maxWidth + this@BlockRenderer.style.layout.gap)
                                if (i > 0) padding(0, this@BlockRenderer.style.layout.gap)
                            }
                        }
                        row {
                            whitespace = PRE_LINE
                            verticalAlign = TOP
                            overflowWrap = BREAK_WORD
                            cellsFrom(it.map { it.first.toString() + "๐Ÿ‡ฉ๐Ÿ‡ช๐Ÿ‘จ๐Ÿพโ€๐Ÿฆฑ๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ" })
                        }
                    }
                )

Above code renders as:

LOREM IPSUM DOLOR SIT AMET, CONSETETUR       LOREM IPSUM DOLOR SIT AMET,
SADIPSCING ELITR, SED DIAM NONUMY            CONSETETUR SADIPSCING ELITR,
EIRMOD.๐Ÿ‡ฉ๐Ÿ‡ช๐Ÿ‘จ๐Ÿพโ€๐Ÿฆฑ๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ                      SED DIAM NONUMY
                                             EIRMOD.๐Ÿ‡ฉ๐Ÿ‡ช๐Ÿ‘จ๐Ÿพโ€๐Ÿฆฑ๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ

whereas the words "SED DIAM NONUMY" of the second column appear too much to the left.

Can we support Kotlin/Native for this library?

As the title said, can we make this library fit KMPP?, Then I can use this library to develop a command-line tool that could run without JVM on any Platform, Like MacOSX Linux and Windows. ๐Ÿ˜„

Is there a way to display several progress bars?

Hi!

I really enjoy your code, you are super talented and I learned many cool Kotlin tricks reading your code :)

My issue is:
I want to present several progress bars. Simple use case will be downloading two (or more) files simultaneously - trying to display a progress-bar per file

I tried several approaches utilizing progressAnimation, one terminal with multiple progress bars, and several terminals with progressAnimation each. Digging a little into the code I realized that this is not the way that I should use your code
Sadly my attempts failed miserably :(

My workaround is not using the progress bars at all and using styled text instead - but I feel like there is possibly a better solution

Can you please hint to me about what should I do?

Thanks a bunch!!

(p.s. I don't mind having both progress bars at the same line but I would prefer them one per line)

Handle images with reference full/short links

![img][1]

[1]: img.jpg

is not rendered properly. Render image link handles only INLINE_LINK type of references.
MarkdownRenderer.kt:357 renderImageLink()

Code is failing with

java.lang.NullPointerException
        at com.github.ajalt.mordant.markdown.MarkdownRendererKt.firstChildOfType(MarkdownRenderer.kt:383)
        at com.github.ajalt.mordant.markdown.MarkdownRendererKt.access$firstChildOfType(MarkdownRenderer.kt:1)
        at com.github.ajalt.mordant.markdown.MarkdownRenderer.renderImageLink(MarkdownRenderer.kt:358)
        at com.github.ajalt.mordant.markdown.MarkdownRenderer.parseInlines(MarkdownRenderer.kt:226)
        at com.github.ajalt.mordant.markdown.MarkdownRenderer.innerInlines(MarkdownRenderer.kt:279)
        at com.github.ajalt.mordant.markdown.MarkdownRenderer.innerInlines$default(MarkdownRenderer.kt:277)
        at com.github.ajalt.mordant.markdown.MarkdownRenderer.parseStructure(MarkdownRenderer.kt:133)
        at com.github.ajalt.mordant.markdown.MarkdownRenderer.parseFile(MarkdownRenderer.kt:86)
        at com.github.ajalt.mordant.markdown.MarkdownRenderer.render(MarkdownRenderer.kt:80)

Idea: more generalization for ptys

I updated to latest Mordant today - looks good. I found the way stderr is handled a little odd. Why is it done as a sort of transformation of TerminalInterface/Terminal? It feels a bit like Terminal should just be a wrapper around an arbitrary byte stream and maybe a set of environment variables, rather than special cased for stderr/stdout, as that way you can easily connect it to things like a telnet or ssh session. Note that this would require modifying StdoutTerminalInterface.

TerminalDetection not detecting IntelliJ

The TerminalDetection computes AnsiLevel.NONE although I'm running my unit tests
inside of IntelliJ (Build #IU-221.6008.13, built on July 19, 2022).

The runningInIdeaJavaAgent method does not match.
On my mac ManagementFactory.getRuntimeMXBean() returns the following elements

0 = "-Dorg.gradle.internal.worker.tmpdir=/Users/bkahlert/Development/com.bkahlert/kommons/build/tmp/jvmTest/work"
1 = "-Dorg.gradle.native=false"
2 = "-agentlib:jdwp=transport=dt_socket,server=n,suspend=y,address=127.0.0.1:56426"
3 = "-javaagent:/Users/bkahlert/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-coroutines-core-jvm/1.6.1/97fd74ccf54a863d221956ffcd21835e168e2aaa/kotlinx-coroutines-core-jvm-1.6.1.jar"
4 = "--add-opens=java.base/java.util=ALL-UNNAMED"
5 = "--add-opens=java.base/java.lang=ALL-UNNAMED"
6 = "-Xms128m"
7 = "-Xmx512m"
8 = "-Dfile.encoding=UTF-8"
9 = "-Duser.country=US"
10 = "-Duser.language=en"
11 = "-Duser.variant"
12 = "-ea"

System.getProperty("sun.java.command") reports "worker.org.gradle.process.internal.worker.GradleWorkerMain 'Gradle Test Executor 61'"

My environment reported by System.getenv() does neither contain IDEA_INITIAL_DIRECTORY, __INTELLIJ_COMMAND_HISTFILE__ nor TERMINAL_EMULATOR.

The following environment properties might help you:

...
"JAVA_MAIN_CLASS_56145" -> "worker.org.gradle.process.internal.worker.GradleWorkerMain"
"TOOLBOX_VERSION" -> "1.25.12424"
"__CFBundleIdentifier" -> "com.jetbrains.intellij"
"SHELL" -> "/bin/zsh"
"__CF_USER_TEXT_ENCODING" -> "0x40E34E6:0x0:0x0"

Support for scrolling regions

A really nice VTS feature that is missing, are Scrolling Margins.

Usage Idea

To keep usage in line with table I suggest something like this:

scrollingRegion {
  width = Int
  height = Int
  smooth = Boolean
  t.println("REALLY LONG TEXT...")
}

This would set the scrolling margins from the current line (which would need to be tracked or somehow got from DECXCPR) to the current line + height (and maybe there is a need to scroll some lines with SU, to make sure the height is respected).

After the block, the scrolling margin would be reset.

noteworthy things

  • DECSTBM is used to set top and bottom margins
  • DECLRMM needs to be set to be able to set left and right margins
  • DECSLRM is used to set left and right margins
  • DECSCLM can be used to set to smooth scrolling mode
  • DECSSCLS can be used to set the scroll speed
  • There should probably be a kind of failsafe, so the margins are reset if the code inside the block fails

Incorrect info.width on Windows terminals

First let me say this library and clikt are great, thanks for working on them.

I'm using this in a Kotlin Native project on Windows and getting some odd output for the terminal width. I saw the other ticket regarding max width so added the updateTerminalSize call to no avail.

Below are the results for the following code in various terminals:

        val terminal = Terminal()
        terminal.info.updateTerminalSize()
        println(terminal.info.width)
        terminal.print(
            table {
                header {
                    row("a", "b", "c", "d", "e")
                }

                body {
                    borders = TOM_BOTTOM
                    rowStyles(TextColors.white, TextColors.gray)

                    row {
                        cell("aaaaaaaaaaaaaaaaaa")
                        cell("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb")
                        cell("ccccccccccccccccccccccccccccccccccccccccccc")
                        cell("dddddddddddddddddddddddddddddddddddddddddddddddddddddddd")
                        cell("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee")
                    }
                }
            }
        )

Powershell in Windows Terminal:
image

CMD in Windows Terminal:
image

Powershell native:
image

Mordant 2.0 max table width

I have a table with 10 columns. The text gets always truncated even if I set OverflowWrap.NORMAL. It seems there is a table max-width that I am not able to override. Is there any way to have tables with a width that depends exclusively on the content?

Problems with MinGW Terminal

Hello :-) I'm the author of https://github.com/Framstag/taskdown

I use mordant (the latest beta) to add colored output in the terminal and (with the new beta) to print nicely formatted Markdown to the terminal.

This works perfectly under Linux and under Windows native, however it does not work under MinGW (under Windows). The problem occurs with the standard installation, but also with the integration inf MinGW in the new Windows terminal (calling bash.exe -l -i from the terminal configuration).

The problem is, that I use MinGW under Windows as preferred working environment.

I'm not sure what exactly the problem is, or if it is even a problem of mordant itself. IF I call my program with TERM=dumb (the problem disappears (unsurprisingly)). I tried to set TERM to other values to trick the autodetection mechanism, but did not find a variant to resulted in working output.

DOS Shell:
grafik

MinGW bash:
grafik

I obviously need some help, and I'm willing to help you if you give me advice what to do.

Expose methods for performing raw print request

Problem

I'm implementing a full screen interactive tool which needs to manipulate the cursor position. My first attempt was using the animation feature to update the screen but I realised it doesn't fit my purpose because it is build to be rendered inline with other widgets by hooking into the print requests, which makes it not compatible with manipulating the cursor.

Ok, since my use case is much simpler than what Animation attempts to do I decided draw my animation on my own. Unfortunately this was not possible without changes the Mordant library since I need to send raw print requests to refresh the screen.

Suggestion

Expose sending raw print requests:

fun rawPrintln(message: String) {
    sendPrintRequest(PrintRequest(message, true))
}

fun rawPrint(message: String) {
    sendPrintRequest(PrintRequest(message, false))
}

Not working with gradle output?

Regular ANSI color codes work fine, but not ones from Mordant:

import com.github.ajalt.mordant.rendering.TextColors.red
import com.github.ajalt.mordant.terminal.Terminal

fun main() {
    val t = Terminal()
    t.println(red("This isn't red!"))
    println("\u001B[31mThis is red\u001B[0m")
}

gradle run output:
image

However, java -jar BuiltJar.jar works fine

I also have tried to put org.gradle.console=rich in my gradle.properties, but it doesn't seem to do anything

Option to ignore unexpected characters in markdown

I have an application where I query markdown from a third party API, and this is valid markdown syntax in most of the cases. Therefore I am rendering the returned markdown text using mordant, and this works in most of the cases.

But I encountered the following problem where the mordant MarkdownRenderer encounters an unexpected "token":

Exception in thread "main" java.lang.IllegalStateException: Unexpected token when parsing inlines: org.intellij.markdown.ast.LeafASTNode@5c63e02a; [Markdown:~:'~'}]
	at com.github.ajalt.mordant.markdown.MarkdownRenderer.parseInlines(MarkdownRenderer.kt:272)
	at com.github.ajalt.mordant.markdown.MarkdownRenderer.innerInlines(MarkdownRenderer.kt:280)
	at com.github.ajalt.mordant.markdown.MarkdownRenderer.innerInlines$default(MarkdownRenderer.kt:278)
	at com.github.ajalt.mordant.markdown.MarkdownRenderer.parseStructure(MarkdownRenderer.kt:134)
	at com.github.ajalt.mordant.markdown.MarkdownRenderer.parseStructure(MarkdownRenderer.kt:96)
	at com.github.ajalt.mordant.markdown.MarkdownRenderer.parseFile(MarkdownRenderer.kt:87)
	at com.github.ajalt.mordant.markdown.MarkdownRenderer.render(MarkdownRenderer.kt:81)
	at com.github.ajalt.mordant.markdown.Markdown.document(Markdown.kt:25)
	at com.github.ajalt.mordant.markdown.Markdown.render(Markdown.kt:31)
	at com.github.ajalt.mordant.rendering.Widget$DefaultImpls.render$default(Widget.kt:8)
	at com.github.ajalt.mordant.terminal.Terminal.render(Terminal.kt:242)
	at com.github.ajalt.mordant.terminal.Terminal.println(Terminal.kt:201)

The corresponding text was:

"![comparison.png](https://i.imgur.com/KarVVbr.png)\n\n_Frame rate comparison between vanilla Minecraft and Sodium at a render distance of 32 chunks. You can find a world download with this exact scene [here](https://cdn.discordapp.com/attachments/705601849188679712/731924127476219914/Demo.zip) for your own comparison against this reference. Mileage may vary depending on how powerful your hardware is._\n\n<br>\n\nSodium is a free and open-source rendering engine replacement for the Minecraft client that greatly improves frame rates, reduces micro-stutter, and fixes graphical issues in Minecraft. It boasts wide compatibility with the Fabric mod ecosystem when compared to other mods and doesn't compromise on how the game looks, giving you that authentic block game feel.\n\n<br>\n\nIf you're coming from Optifine, you can generally expect a significant improvement to performance over it, but you'll be missing some small features while the Fabric community builds other free and open-source alternatives. For a quick list of replacement features (such as zoom), take a look [here](https://gist.github.com/modmuss50/deff1658c4550ca8b16cb5d40ceaa468). **Sodium and Optifine are incompatible with one another. You must pick one.**\n\n<br>\n\nYou can find more comparisons for various hardware configurations, such as...\n\n- [Intel i5-7200U @ 2.5GHz / Intel HD 620](https://i.imgur.com/0JrlAuf.png) (37-&gt;69fps)\n- [Intel i7-3770 @ 4.0GHz / GTX 970 (user-submitted)](https://i.imgur.com/gthEOTt.png) (27-&gt;152fps)\n- [Intel i3-6100 / GTX 750 Ti (user-submitted)](https://i.imgur.com/tGuLKNN.png) (10-&gt;102fps)\n- [Intel i7-8700K @ 5.0GHz / RTX 2080 Ti (user-submitted)](https://i.imgur.com/Q9z0vbB.png) (87-&gt;368fps)\n- [AMD Ryzen 5 2600 / RX 580 (user-submitted)](https://i.imgur.com/6WHjQR9.png) (133-&gt;586fps)\n\nYou can even find some exotic and low-end systems running Sodium:\n\n- [Raspberry Pi 4B / 4GB Variant (user-submitted)](https://i.imgur.com/RIGL7xp.png) (17-&gt;36fps)\n- [AMD Athlon X2 QL-45 / ATI Radeon 4530 (user-submitted)](https://i.imgur.com/7skXO7M.png) (18-&gt;49fps)\n\n \n**Note:** Sodium is mostly stable at this point, but it does not yet contain support for the Fabric Rendering API, which a small number of mods currently use. If you try to use these mods with Sodium, your game may crash or behave unexpectedly.\n\n# Features\n\n- A modern OpenGL rendering pipeline for chunk rendering that takes advantage of multi-draw techniques, allowing for a significant reduction in CPU overhead (~90%) when rendering the world. This can make a huge difference to frame rates for most computers that are not bottle-necked by the GPU or other components. Even if your GPU can't keep up, you'll experience much more stable frame times thanks to the CPU being able to work on other rendering tasks while it waits.\n<br>\n\n- Vertex data for rendered chunks is made much more compact, allowing for video memory and bandwidth requirements to be cut by almost 40%.\n<br>\n\n- Nearby block updates now take advantage of multi-threading, greatly reducing lag spikes caused by chunks needing to be updated. ([before](https://streamable.com/lm5sp5), [after](https://streamable.com/nsdl0r))\n<br>\n\n- Chunk faces which are not visible (or facing away from the camera) are culled very early in the rendering process, eliminating a ton of geometry that would have to be processed on the GPU only to be immediately discarded. For integrated GPUs, this can greatly reduce memory bandwidth requirements and provide a modest speedup even when GPU-bound.\n<br>\n\n- Plentiful optimizations for chunk loading and block rendering, making chunk loading significantly faster and less damaging to frame rates. ([before](https://streamable.com/3taw22), [after](https://streamable.com/4pesh2))\n<br>\n\n- Many optimizations for vertex building and matrix transformations, speeding up block entity, mob, and item rendering significantly for when you get carried away placing too many chests in one room.\n<br>\n\n- Many improvements to how the game manages memory and allocates objects, which in turn reduces memory consumption and lag spikes caused by garbage collector activity.\n<br>\n\n- Many graphical fixes for smooth lighting effects, making the game run better while still applying a healthy amount of optimization. For example, take this [before and after](https://i.imgur.com/lYmlmgq.png) of a white concrete room in vanilla, or this [comparison while underwater](https://i.imgur.com/QQMuOTy.png).\n<br>\n\n- Smooth lighting for fluids and other special blocks. ([comparison](https://i.imgur.com/z9HBcvq.png))\n<br>\n\n- Smooth biome blending for blocks and fluids, providing greatly improved graphical quality that is significantly less computationally intensive.  ([comparison](https://i.imgur.com/Fud5oyF.png))\n<br>\n\n- Animated textures which are not visible in the world are not updated, speeding up texture updating on most hardware (especially AMD cards.)\n<br>\n\n... and much more, this list is still being written after the initial release.\n\n## Installation\n\nMake sure you have the latest version of [Fabric Loader](https://fabricmc.net/use/) installed. Afterwards, all you need to do is simply drop the mod into your mods folder. No other mods (not even the Fabric API!) are required in order to use Sodium. You do not need to create new worlds in order to take advantage of the mod.\n\n## Configuration\n\nSodium replaces the video settings screen with a new and improved user interface that contains all the bells and whistles for configuring Sodium. Out of the box, Sodium will enable all optimizations which are supported on your system.\n\n## Reporting Issues\n\nPlease use the [issue tracker](https://github.com/jellysquid3/sodium-fabric/issues) linked at the top of the page to report bugs, crashes, and other issues.\n\n## Common questions\n\n**Will you port to Minecraft 1.15 and older? What about Minecraft 1.8.9?** \n\nNo. Sodium is a modern mod designed for modern versions of Minecraft. Please stop asking.\n\n<br>\n\n**Will you add Forge support?**\n\nNo. Forge compatibility is not being considered. You can read my thoughts [here](https://gist.github.com/jellysquid3/629eb84a74ab326046faf971150dc6c3) on why that decision was made.\n\n<br>\n\n**Does Sodium support Optifine shader packs?**\n\nNo. However, experimentation is being done and it is possible in the future that Sodium could provide its own shader pack system. No promises are being made.\n\n<br>\n\n**Is Sodium a replacement for Optifine?**\n\nIf you need the best performance out of your game, it very well could be. The primary focus of Sodium is optimization and improving rendering quality right now, not features. If you're looking for all the other features of which Optifine provides, check out [this list](https://gist.github.com/LambdAurora/1f6a4a99af374ce500f250c6b42e8754) for some Fabric-based alternatives (such as zoom functionality) which are compatible with Sodium. "

Is it possible to add an option to ignore these unexpected characters / tokens?

`Terminal.print()` ignores new line feeds at the end of Strings

I'm trying to interface Mordant with an existing logging structure by implementing the Appendable interface and forwarding the methods to Mordant using the Terminal.print() method, however I'm noticing that new line feeds at the end of CharSequences/Strings are getting ignored. Somehow Mordant is stripping them.

Sample code:

class TerminalAppendable(private val terminal: Terminal): Appendable {

    override fun append(charSequence: CharSequence): Appendable {
        terminal.print(charSequence)
        return this
    }

    override fun append(charSequence: CharSequence, start: Int, end: Int): Appendable {
        terminal.print(charSequence.subSequence(start, end))
        return this
    }

    override fun append(char: Char): Appendable {
        terminal.print(char.toString())
        return this
    }
}
val appendable = TerminalAppendable(Terminal())
appendable.appendLine("Foo Bar")
appendable.appendLine("Lorem Ipsum")
// Result:   `Foo BarLorem Ipsum`
// Expected: `Foo Bar\nLorem Ipsum\n`

appendable.append("Foo Bar\n")
appendable.append("Lorem Ipsum\n")
// Result:   `Foo BarLorem Ipsum`
// Expected: `Foo Bar\nLorem Ipsum\n`

appendable.append("Foo\nBar\n")
appendable.append("Lorem\nIpsum\n")
// Result:   `Foo\nBarLorem\nIpsum`
// Expected: `Foo\nBar\nLorem\nIpsum\n`

ProgressBar deletes previous text in IntelliJ Terminal (Jedi)

Version: mordant-jvm:2.0.0-beta8
OS: MacOS 13.0.1

I've noticed, that my application with progress bar works fine in iTerm, but works strangely in IntelliJ Terminal (I've checked on IntelliJ IDEA 2022.3 RC). Even it works fine in IntelliJ Terminal but inside docker container with -tty.

I think the problem is in the detection of ij terminal:

val ij = isIntellijConsole() // intellij console is interactive, even through System.console == null

However, for me, ij terminal has System.console != null.

So, when I unset env variables from com.github.ajalt.mordant.terminal.TerminalDetection#hasIdeaEnvvar my program starts working as expected.

I've created small reproduce project: https://github.com/zinoviy23/terminal-kotlin-test
There is a small script run.sh. When it executed in ij terminal like ./run.sh, progress bar deletes previous lines. But without ij terminal environment variables it works fine: ./run.sh -e.

Screen clearing optimization breaks when printing during an animation

If we print something using the Terminal object's print methods during an animation, it all works correctly in 2.0.0-beta2 because the animation uses an interceptor to clear the screen, print, then re-render. In 2.0.0-beta4 this has broken, presumably due to the new optimization (which works great, the flicker seems to be gone). The problem is that the printing is done, but the screen wasn't cleared first, so parts of the animation fill the rest of the line after the message:

image

info.updateTerminalSize() can crash on MacOS Ventura

Firstly let me thank you for this amazing lib AND cliktcommand. They are super useful :)

However, apart from not really working (as you can see from the video, all the tables get squashed to nothing), the jvm updateTerminalSize function causes the JVM to crash on MacOs Ventura:

hs_err_pid22892.log

replay_pid77535.log

Crash log on console out:

#
# A fatal error has been detected by the Java Runtime Environment:
#
#  SIGSEGV (0xb) at pc=0x000000010469021c, pid=77535, tid=23299
#
# JRE version: OpenJDK Runtime Environment GraalVM CE 22.3.0 (17.0.5+8) (build 17.0.5+8-jvmci-22.3-b08)
# Java VM: OpenJDK 64-Bit Server VM GraalVM CE 22.3.0 (17.0.5+8-jvmci-22.3-b08, mixed mode, sharing, tiered, jvmci, jvmci compiler, compressed oops, compressed class ptrs, g1 gc, bsd-aarch64)
# Problematic frame:
# V  [libjvm.dylib+0x75021c]  MethodMatcher::matches(methodHandle const&) const+0x44
#
# No core dump will be written. Core dumps have been disabled. To enable core dumping, try "ulimit -c unlimited" before starting Java again
#
# An error report file with more information is saved as:
# /Users/mattcorby-eaglen/repos/binder/applications/connect-cli/hs_err_pid77535.log
> #
# Compiler replay data is saved as:
# /[redacted]/replay_pid77535.log
sigsegv.mov

All this command I'm running is calling the updateTerminalSize function in a CliktCommand underneath. I'm adapting the mordant terminal using the interfaces provided for Clikt, if you are wondering, and everything works fine in a linux docker environment.

Edit:

> java -version
openjdk version "17.0.5" 2022-10-18
OpenJDK Runtime Environment GraalVM CE 22.3.0 (build 17.0.5+8-jvmci-22.3-b08)
OpenJDK 64-Bit Server VM GraalVM CE 22.3.0 (build 17.0.5+8-jvmci-22.3-b08, mixed mode, sharing)

Second edit: added console crash message for GraalVm and replay log

Markdown table parsing fails on empty cells

Trying to print a markdown file that contains empty cells fails currently (mordant version 2.0.0-beta2).

fun main() {
    val t = Terminal()
    t.println(Markdown("""
        | Test |
        |------|
        |      |
    """.trimIndent()))
}

The parser does not expect to only encounter whitespace.

Exception in thread "main" java.lang.IllegalArgumentException: fromIndex(1) > toIndex(0)
	at java.util.ArrayList.subListRangeCheck(ArrayList.java:1014)
	at java.util.ArrayList.subList(ArrayList.java:1004)
	at com.github.ajalt.mordant.markdown.MarkdownRenderer.innerInlines(MarkdownRenderer.kt:279)
	at com.github.ajalt.mordant.markdown.MarkdownRenderer.access$innerInlines(MarkdownRenderer.kt:59)
	at com.github.ajalt.mordant.markdown.MarkdownRenderer$parseTableRow$1.invoke(MarkdownRenderer.kt:316)
	at com.github.ajalt.mordant.markdown.MarkdownRenderer$parseTableRow$1.invoke(MarkdownRenderer.kt:312)
	at com.github.ajalt.mordant.table.SectionBuilderInstance.row(TableDslInstances.kt:96)
	at com.github.ajalt.mordant.markdown.MarkdownRenderer.parseTableRow(MarkdownRenderer.kt:312)
	at com.github.ajalt.mordant.markdown.MarkdownRenderer.access$parseTableRow(MarkdownRenderer.kt:59)
	at com.github.ajalt.mordant.markdown.MarkdownRenderer$parseStructure$7$3.invoke(MarkdownRenderer.kt:174)
	at com.github.ajalt.mordant.markdown.MarkdownRenderer$parseStructure$7$3.invoke(MarkdownRenderer.kt:171)
	at com.github.ajalt.mordant.table.TableBuilderInstance.body(TableDslInstances.kt:64)
	at com.github.ajalt.mordant.markdown.MarkdownRenderer$parseStructure$7.invoke(MarkdownRenderer.kt:171)
	at com.github.ajalt.mordant.markdown.MarkdownRenderer$parseStructure$7.invoke(MarkdownRenderer.kt:159)
	at com.github.ajalt.mordant.table.TableDslKt.table(TableDsl.kt:202)
	at com.github.ajalt.mordant.markdown.MarkdownRenderer.parseStructure(MarkdownRenderer.kt:159)
	at com.github.ajalt.mordant.markdown.MarkdownRenderer.parseFile(MarkdownRenderer.kt:87)
	at com.github.ajalt.mordant.markdown.MarkdownRenderer.render(MarkdownRenderer.kt:81)
	at com.github.ajalt.mordant.markdown.Markdown.document(Markdown.kt:25)
	at com.github.ajalt.mordant.markdown.Markdown.render(Markdown.kt:31)
	at com.github.ajalt.mordant.rendering.Widget$DefaultImpls.render$default(Widget.kt:8)
	at com.github.ajalt.mordant.terminal.Terminal.render(Terminal.kt:242)
	at com.github.ajalt.mordant.terminal.Terminal.println(Terminal.kt:201)
	at main(Main.kt:10)

I currently have to work around this issue, by putting an invisible character into empty cells.

Animation could overdraw instead of clear to avoid flicker

When rendering an animation at high speed there is unfortunately some ugly flicker in most terminals. I suspect the cause is that Animation starts by clearing the space previously used. This is nice and simple but causes flicker. For a flicker-free experience, text should be emitted that covers the entire previous area so clearing isn't used at all.

Perhaps a simple optimization would be to notice when the size of the rendered widget hasn't changed and just skip the clear, or allow animations to opt-in to that when they know that their rendered widget will always overdraw everything anyway?

Mordant 2.0

Mordant 2.0 is a complete rewrite that adds support for markdown rendering, animations, and renderable components like lists and tables. Specifying colors and styles can now be done through static members of the TextColors and TextStyles enums. You don't need to pass around a TermColors instance everywhere; you just need to print the final strings with Terminal.print if you want the ANSI codes downsampled.

There's still a lot I'd like to do before a stable release.

  • Lots of docs two write, including an mkdocs site
  • Multiplatform support. The biggest blocker here is the intellij-markdown dependency. I can contribute native support there if necessary.
  • Better animation support, especially for periodic animations like spinners and progress bars. It would probably be helpful to add a DSL for progress updates that can animate a progress bar or spinner, automatically calculate time elapsed/remaining etc. This might be tricky on JS or Native. Maybe it would be best to add kotlinx.coroutines as a dependency rather than try to do concurrency manually.
  • Automatically update detected terminal dimensions when the window is resized.
  • Maybe get rid of Terminal.colors. It's the equivalent of Mordant 1's TermColors, but isn't necessary if you use Terminal.println. We could simplify a few things if we got rid of it. If we did, you could still use regular println instead of the Terminal version, you would just need to pass your stings through Terminal.render if you want your ANSI codes downsampled.

If anyone can try out the alpha, I'd love to hear your feedback on API, implementation, features, or anything else. You can comment on this issue, start a conversation in the #clikt channel on the kotlinlang slack, or DM me on ASG or kotlinlang slack.

please document platform constraints

i am new to mordant (and i love it)
but yesterday i tried to add a animation and a progress bar (because i saw them i nthe README)
just to discover that they are JVM only and not available in common code (we target jvm, linux and mingw)

something like the colored platform tags in the kotlin docs would help a lot with figuring outif a feature is available
tags

Demo Terminal.textAnimation with 256 colors produces an IllegalArgumentException

Version: 2.0.0-beta3

Running the demo animation on a 256-color terminal...

        val t = Terminal(ansiLevel = AnsiLevel.ANSI256)
        val a = t.textAnimation<Int> { frame ->
            (1..50).joinToString("") {
                val hue = (frame + it) * 3 % 360
                t.colors.hsv(hue, 100, 100)("โ”")
            }
        }

        t.cursor.hide(showOnExit = true)
        repeat(120) {
            a.update(it)
            Thread.sleep(25)
        }

...produces this exception:

Exception in thread "main" java.lang.IllegalArgumentException: Spans cannot contain ANSI codes
        at com.github.ajalt.mordant.rendering.Span$Companion.word(Span.kt:22)
        at com.github.ajalt.mordant.internal.ParsingKt.splitLines(Parsing.kt:90)
        at com.github.ajalt.mordant.internal.ParsingKt.parseText(Parsing.kt:23)
        at com.github.ajalt.mordant.widgets.Text.<init>(Text.kt:29)
        at sca.Application$run$$inlined$textAnimation$default$1.renderData(Animation.kt:89)
        at com.github.ajalt.mordant.animation.Animation.update(Animation.kt:60)
[...]

It appears that an escape sequence containing a negative parameter is produced (escape character replaced by E for readability):
E[38;5;-313484m

Negative values seem to appear initially in the conversion of HSV(h=3.0, s=100.0, v=100.0, alpha=1.0) to SRGB in ajalt/colormath/model/HSV.kt (with h normalized to 0.05):

    override fun toSRGB(): RGB {
        if (s < 1e-7) return RGB(v, v, v, alpha)
        val v = v.toDouble()
        val h = (h.normalizeDeg() / 60.0)
        val s = s.toDouble()

        fun f(n: Int): Float {
            val k = (n + h) % 6
            return (v - v * s * minOf(k, 4 - k, 1.0).coerceAtLeast(0.0)).toFloat()
        }
        return SRGB(f(5), f(3), f(1), alpha)
    }

It returns SRGB(r=100.0, g=-9400.0 b=-9900.0 alpha=1.0).

I did not dive into the underlying math, but hopefully this helps.

Mordant 2.0 Tables DSL suggestion

Hello @ajalt!

I see that in Mordant 2.0 you want to add support for rendering tables, and you have also a nice DSL for it. Well, I have some suggestions to it.
I think that when table styling and properties are lying around with child components of a table it is not very easy to read. I think it is better to implement styling in function arguments, like in Jetpack Compose. So it will look like this:

table(
    align = BorderLocation.TopBottom,
    outerBorder = false
) {
    header(
        style = TableStyle(color = DefaultColors.Magenta, bold = true) 
    ) {
        row("", "Projected Cost", "Actual Cost", "Difference")
    }
    body(
         borders = BorderLocation.TopBottom
    ) {
        row("Food", "$400", "$200", "$200")
        row("Data", "$100", "$150", "-$50")
        row("Rent", "$800", "$800", "$0")
        row("Candles", "$0", "$3,600", "-$3,600")
        row("Utility", "$145", "$150", "-$5")
    }
    footer(
        style = TableStyle(bold = true)
    ) {
        row {
            cell(text = "Subtotal")
            cell(columnSpan = 3, text = "$-3,455")
        }
    }
    captionBottom("Budget courtesy @dril", TextStyle(dim = true))
}

I think it looks a lot better :D

What do you think about this?

Provide a way to disconnect animation TerminalInterceptor

I am working on a CLI with output similar to a docker pull, where I have several lines of status info that updates as operations progress. I am using a custom Animation subclass to handle rendering and updating this. Later, after this phase finishes, I want to continue printing other information, but leave the last state of the animation in place.

Currently, if I print new lines after the animations without first clearing them, the animations will re-print after the new lines.
What I would like to be able to do is to stop or disconnect the animation so that the last text it printed remains, and does not re-print or clear.

character not rendering properly in gitlab ci console

First of all, thanks for such awesome library. I am using this library to write a program that is running on gitlab CI. Here is the screenshot of the table that I was rendering.

image

After debugging I found that \u2500 is rendering a line. In my local machine (I am using mac with zsh) everything works fine but in CI ? appears instead of -. I tried to directly echo the unicode in CI with command echo "\u2500" but this didn't work, it just printed the text. Then I used echo -e "\u2500" and it rendered the -. Can you please help me?

Tables (and therefore almost all layouts) cannot be empty

This complicates items which are rendered by loops.

Consider

fun thing(strings: List<String>) = verticalLayout {
  for (string in strings) {
    cell("Hi $string!")
  }
}

This crashes, and so you have to do something like:

fun thing(strings: List<String>) = if (strings.isEmpty()) {
  Text("")
} else {
  verticalLayout {
    for (string in strings) {
      cell("Hi $string!")
    }
  }
}

This occurs for table, grid, and verticalLayout. Weirdly, horizontalLayout does not exhibit this behavior (presumably because it always adds a single row which is empty?).

Is there a technical reason to prevent empty layouts?

fun main() {
  val t = Terminal()
  println("Vertical")
  runCatching { t.println(verticalLayout { }) }.exceptionOrNull()?.printStackTrace(System.out)
  println("Horizontal")
  runCatching { t.println(horizontalLayout { }) }.exceptionOrNull()?.printStackTrace(System.out)
  println("Grid")
  runCatching { t.println(grid { }) }.exceptionOrNull()?.printStackTrace(System.out)
  println("Table")
  runCatching { t.println(table { }) }.exceptionOrNull()?.printStackTrace(System.out)
}
Vertical
java.lang.IllegalArgumentException: Table cannot be empty
	at com.github.ajalt.mordant.table.TableImpl.<init>(Table.kt:80)
	at com.github.ajalt.mordant.table.TableLayout.buildTable(TableLayout.kt:24)
	at com.github.ajalt.mordant.table.VerticalLayoutBuilderInstance.build(TableDslInstances.kt:223)
	at com.github.ajalt.mordant.table.TableDslKt.verticalLayout(TableDsl.kt:298)
	at com.jakewharton.mosaic.MordantKt.main(mordant.kt:12)
	at com.jakewharton.mosaic.MordantKt.main(mordant.kt)
Horizontal

Grid
java.lang.IllegalArgumentException: Table cannot be empty
	at com.github.ajalt.mordant.table.TableImpl.<init>(Table.kt:80)
	at com.github.ajalt.mordant.table.TableLayout.buildTable(TableLayout.kt:24)
	at com.github.ajalt.mordant.table.GridBuilderInstance.build(TableDslInstances.kt:149)
	at com.github.ajalt.mordant.table.TableDslKt.grid(TableDsl.kt:277)
	at com.jakewharton.mosaic.MordantKt.main(mordant.kt:16)
	at com.jakewharton.mosaic.MordantKt.main(mordant.kt)
Table
java.lang.IllegalArgumentException: Table cannot be empty
	at com.github.ajalt.mordant.table.TableImpl.<init>(Table.kt:80)
	at com.github.ajalt.mordant.table.TableLayout.buildTable(TableLayout.kt:24)
	at com.github.ajalt.mordant.table.TableDslKt.table(TableDsl.kt:254)
	at com.jakewharton.mosaic.MordantKt.main(mordant.kt:18)
	at com.jakewharton.mosaic.MordantKt.main(mordant.kt)

I am using 2.0.0-beta9.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.