{"id":13537257,"url":"https://github.com/varabyte/kotter","last_synced_at":"2026-05-07T01:05:44.500Z","repository":{"id":40592153,"uuid":"397769020","full_name":"varabyte/kotter","owner":"varabyte","description":"A declarative, Kotlin-idiomatic API for writing dynamic console applications.","archived":false,"fork":false,"pushed_at":"2025-02-07T22:39:29.000Z","size":2292,"stargazers_count":612,"open_issues_count":12,"forks_count":20,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-02T04:35:03.312Z","etag":null,"topics":["ansi","ansi-colors","console","console-colors","hacktoberfest","kotlin","kotlin-jvm","kotlin-linux","kotlin-macos","kotlin-mingw","kotlin-multiplatform","reactive","terminal","terminal-input"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/varabyte.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"ko_fi":"bitspittle"}},"created_at":"2021-08-19T00:24:16.000Z","updated_at":"2025-03-29T15:00:32.000Z","dependencies_parsed_at":"2024-01-16T15:41:16.005Z","dependency_job_id":"2c321a00-d8cf-4398-9bb6-499762ede21b","html_url":"https://github.com/varabyte/kotter","commit_stats":null,"previous_names":[],"tags_count":25,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/varabyte%2Fkotter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/varabyte%2Fkotter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/varabyte%2Fkotter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/varabyte%2Fkotter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/varabyte","download_url":"https://codeload.github.com/varabyte/kotter/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253990157,"owners_count":21995770,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ansi","ansi-colors","console","console-colors","hacktoberfest","kotlin","kotlin-jvm","kotlin-linux","kotlin-macos","kotlin-mingw","kotlin-multiplatform","reactive","terminal","terminal-input"],"created_at":"2024-08-01T09:00:56.900Z","updated_at":"2026-05-07T01:05:44.476Z","avatar_url":"https://github.com/varabyte.png","language":"Kotlin","funding_links":["https://ko-fi.com/bitspittle"],"categories":["Libraries"],"sub_categories":["Command Line Interface"],"readme":"![version: 1.3.0](https://img.shields.io/badge/kotter-v1.3.0-blue)\n![kotter tests](https://github.com/varabyte/kotter/actions/workflows/gradle-test.yml/badge.svg?branch=main)\n![kotter coverage](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/bitspittle/1fab2b6fd23489bdea3f5d1f38e4dcb2/raw/kotter-coverage-badge.json)\n\u003ca href=\"https://varabyte.github.io/kotter\"\u003e\n![kotter docs](https://img.shields.io/badge/docs-grey?logo=readthedocs)\n\u003c/a\u003e\n\u003cbr\u003e\n![kotlin compatibility jvm](https://img.shields.io/badge/kotlin_[jvm]-1.8+-lightgray?logo=kotlin)\n![kotlin compatibility k/n](https://img.shields.io/badge/kotlin_[native]-1.9+-lightgray?logo=kotlin)\n\u003cbr\u003e\n\u003ca href=\"https://discord.gg/5NZ2GKV5Cs\"\u003e\n  \u003cimg alt=\"Varabyte Discord\" src=\"https://img.shields.io/discord/886036660767305799.svg?label=\u0026logo=discord\u0026logoColor=ffffff\u0026color=7389D8\u0026labelColor=6A7EC2\" /\u003e\n\u003c/a\u003e\n[![Bluesky](https://img.shields.io/badge/Bluesky-0285FF?logo=bluesky\u0026logoColor=fff)](https://bsky.app/profile/bitspittle.bsky.social)\n\n# Kotter 🦦\n\n```kotlin\nsession {\n  var wantsToLearn by liveVarOf(false)\n  section {\n    text(\"Would you like to learn \"); cyan { text(\"Kotter\") }; textLine(\"? (Y/n)\")\n    text(\"\u003e \"); input(Completions(\"yes\", \"no\"))\n\n    if (wantsToLearn) {\n      yellow(isBright = true) { p { textLine(\"\"\"\\(^o^)/\"\"\") } }\n    }\n  }.runUntilInputEntered {\n    onInputEntered { wantsToLearn = \"yes\".startsWith(input.lowercase()) }\n  }\n}\n```\n\n![Code sample in action](https://github.com/varabyte/media/raw/main/kotter/screencasts/kotter-input.gif)\n\n*See also: [the game of life](examples/life), [snake](examples/snake), [sliding tiles](examples/sliding), [doom fire](examples/doomfire), and [Wordle](examples/wordle) implemented in Kotter!*\n\n---\n\nKotter (a **KOT**lin **TER**minal library) aims to be a relatively thin, declarative, Kotlin-idiomatic API that provides\nuseful functionality for writing delightful console applications. It strives to keep things simple, providing a solution\na bit more opinionated than making raw `println` calls but way less featured than something like _Java Curses_.\n\nSpecifically, this library helps with:\n\n* Setting colors and text decorations (e.g. underline, bold)\n* Handling user input\n* Creating timers and animations\n* Seamlessly repainting terminal text when values change\n\nKotter is multiplatform, supporting JVM and native targets.\n\nThe next sections deal with setting Kotter up, but you may wish to jump straight\n[to the usage section ▼](#-usage) to immediately start learning about this library.\n\n## 🐘 Gradle\n\n### 🎯 Dependency\n\nKotter supports JVM and native targets.\n\n\u003e [!TIP]\n\u003e If you're not sure what you want, start with a JVM project. That target is far easier to distribute. It also means\n\u003e your project will have access to a very broad ecosystem of Kotlin and Java libraries.\n\u003e\n\u003e In case it affects your decision, you can read more about [distributing Kotter applications ▼](#-distributing-your-application)\n\u003e later in this document.\n\n#### JVM\n\n```kotlin\n// build.gradle.kts (kotlin script)\nplugins {\n    kotlin(\"jvm\")\n    application\n}\n\nrepositories {\n    mavenCentral()\n}\n\ndependencies {\n    implementation(\"com.varabyte.kotter:kotter:1.3.0\")\n    testImplementation(\"com.varabyte.kotterx:kotter-test-support:1.3.0\")\n}\n\napplication {\n    applicationDefaultJvmArgs = listOf(\n        // JDK24 started reporting warnings for libraries that use restricted native methods, at least one which Kotter\n        // uses indirectly (via jline/jansi). It looks like this:\n        //\n        // WARNING: A restricted method in java.lang.System has been called\n        // WARNING: java.lang.System::loadLibrary has been called by ...\n        // WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module\n        // WARNING: Restricted methods will be blocked in a future release unless native access is enabled\n        //\n        // The best solution we have for now is to disable the warning by explicitly enabling access.\n        // We also suggest the IgnoreUnrecognizedVMOptions flag here to allow kotter applications to be able to compile\n        // with JDKs older than JDK24. You can remove it if you are intentionally using JDK24+.\n        // See also: https://docs.oracle.com/en/java/javase/24/docs/api/java.base/java/lang/doc-files/RestrictedMethods.html\n        // And also: https://github.com/jline/jline3/issues/1067\n        \"-XX:+IgnoreUnrecognizedVMOptions\",\n        \"--enable-native-access=ALL-UNNAMED\",\n    )\n    // The following assumes a top-level `main.kt` file in your project; adjust as needed otherwise\n    mainClass.set(\"MainKt\")\n}\n```\n\n#### Multiplatform\n\nMultiplatform can be useful if you want to distribute binaries to users without requiring they have Java installed on\ntheir machine.\n\n```kotlin\n// build.gradle.kts (kotlin script)\nplugins {\n    kotlin(\"multiplatform\")\n}\n\nrepositories {\n    mavenCentral()\n}\n\nkotlin {\n    // Choose the targets you care about.\n    // Note: You will need the right machine to build each one; otherwise, the target is disabled automatically\n    listOf(\n        linuxX64(), // Linux\n        mingwX64(), // Windows\n        macosArm64(), // Mac M1\n        macosX64(), // Mac Legacy\n    ).forEach { nativeTarget -\u003e\n        nativeTarget.apply {\n            binaries {\n                executable {\n                    entryPoint = \"main\"\n                }\n            }\n        }\n    }\n\n    sourceSets {\n        val commonMain by getting {\n            dependencies {\n                implementation(\"com.varabyte.kotter:kotter:1.3.0\")\n            }\n        }\n        val commonTest by getting {\n            dependencies {\n                implementation(\"com.varabyte.kotterx:kotter-test-support:1.3.0\")\n            }\n        }\n    }\n}\n```\n\n\u003e [!NOTE]\n\u003e Building native binaries is a little tricky, as you may need different host machines in order to build the various\n\u003e binaries. For example, [here is Kotter's CI workflow](https://github.com/varabyte/kotter/blob/main/.github/workflows/publish.yml)\n\u003e which runs on both Linux and Mac targets to build platform-specific Kotter artifacts.\n\n#### Testing snapshots\n\nMost users won't ever need to run a Kotter snapshot, so feel free to skip over this section! However, occasionally, bug\nfixes and new features will be available for testing for a short period before they are released.\n\nIf you ever file a bug with Kotter and are asked to test a fix using a snapshot, you must add an entry for the sonatype\nsnapshots repository to your `repositories` block in order to allow Gradle to find it:\n\n```diff\n// build.gradle.kts\n\nrepositories {\n  mavenCentral()\n+ maven(\"https://central.sonatype.com/repository/maven-snapshots/\") {\n+   mavenContent {\n+     includeGroup(\"com.varabyte.kotter\")\n+     includeGroup(\"com.varabyte.kotterx\")\n+     snapshotsOnly()\n+   }\n+ }\n}\n```\n\n### 🚥 Running examples\n\nIf you've cloned this repository, examples are located under the [examples](examples) folder.\n\n#### JVM\n\nMost of the examples (except `examples/native`) target the JVM. To try one of them, you can navigate into it on the\ncommand line and run it via Gradle.\n\n```bash\n$ cd examples/life\n$ ../../gradlew run\n```\n\nHowever, because Gradle itself has taken over the terminal to do its own fancy command line magic, the example will\nactually open up and run inside a virtual terminal.\n\nIf you want to run the program directly inside your system terminal, which is hopefully the way most users will see your\napplication, you should use the `installDist` task to accomplish this:\n\n```bash\n$ cd examples/life\n$ ../../gradlew installDist\n$ cd build/install/life/bin\n$ ./life\n```\n\n\u003e [!WARNING]\n\u003e If your terminal does not support features needed by Kotter, which could happen on legacy machines for example, then\n\u003e this still may end up running inside a virtual terminal.\n\n#### Multiplatform\n\nUnlike the JVM target, native targets do not have a virtual terminal fallback. So be sure you **do not** use any of the\nGradle run tasks (e.g. `runDebugExecutabule...`). This will also fail if you try to run your program through the IDE via\nthe green \"play\" arrow.\n\nInstead, you should link your executable and then run it directly.\n\nFor example, on Linux:\n\n```bash\n$ cd examples/native\n$ ../../gradlew linkDebugExecutableLinuxX64\n$ ./build/bin/linuxX64/debugExecutable/native.kexe\n```\n\n## 📖 Usage\n\n### 👶 Basics\n\nThe following is equivalent to `println(\"Hello, World\")`. In this simple case, it's definitely overkill!\n\n```kotlin\nsession {\n  section { textLine(\"Hello, World\") }.run()\n}\n```\n\n`section { ... }` defines a `Section` which, on its own, is inert. It needs to be run to output text to the\nconsole. Above, we use the `run` method to trigger this. The method blocks until the render (i.e. text printing to the\nconsole) is finished (which, in the above case, will be almost instant).\n\n`session { ... }` sets the outer scope for your whole program. While we're just calling it with default arguments here,\nyou can also pass in parameters that apply to the entire application.\n\nWhile the above simple case is a bit verbose for what it's doing, Kotter starts to show its strength when doing\nbackground work (or other async tasks like waiting for user input) during which time the section block may render\nseveral times. We'll see many examples throughout this document later.\n\nA Kotter `session` can contain one or more `section`s. Your own app may only ever contain a single `section` and that's\nfine! But if you have multiple `section`s, it will feel to the user like your app has a current, active area, following\na history of text paragraphs from previous interactions that no longer change.\n\n### 🎨 Text Effects\n\nYou can call color methods directly, which remain in effect until the next color method is called:\n\n```kotlin\nsection {\n  green(layer = BG)\n  red() // defaults to FG layer if no layer specified\n  textLine(\"Red on green\")\n  blue()\n  textLine(\"Blue on green\")\n}.run()\n```\n\n![Code sample in action](https://github.com/varabyte/media/raw/main/kotter/images/kotter-text-ex-1.png)\n\nIf you only want the color effect to live for a limited time, you can use scoped helper versions that handle\nclearing colors for you automatically at the end of their block:\n\n```kotlin\nsection {\n  green(layer = BG) {\n    red {\n      textLine(\"Red on green\")\n    }\n    textLine(\"Default on green\")\n    blue {\n      textLine(\"Blue on green\")\n    }\n  }\n}.run()\n```\n\n![Code sample in action](https://github.com/varabyte/media/raw/main/kotter/images/kotter-text-ex-2.png)\n\nIf the user's terminal supports truecolor mode, you can specify rgb (or hsv) values directly:\n\n```kotlin\nsection {\n  rgb(0xFFFF00) { textLine(\"Yellow!\") }\n  hsv(35, 1.0f, 1.0f) { textLine(\"Orange!\") }\n}.run()\n```\n\n![Code sample in action](https://github.com/varabyte/media/raw/main/kotter/images/kotter-text-ex-3.png)\n\n\u003e [!NOTE]\n\u003e If truecolor is not supported, terminals may attempt to emulate it by falling back to a nearby color, which may look\n\u003e decent! However, to be safe, you may want to avoid rendering smooth gradient color changes, as they may come out\n\u003e clumped for some users.\n\nVarious text effects (like bold) are also available:\n\n```kotlin\nsection {\n  bold {\n    textLine(\"Title\")\n  }\n\n  p {\n    textLine(\"A paragraph is content auto-surrounded by newlines\")\n  }\n\n  p {\n    text(\"This paragraph has an \")\n    underline { text(\"underlined\") }\n    textLine(\" word in it\")\n  }\n}.run()\n```\n\n![Code sample in action](https://github.com/varabyte/media/raw/main/kotter/images/kotter-text-ex-4.png)\n\n\u003e [!NOTE]\n\u003e Italics functionality is not currently exposed, as it is not a standard feature and is inconsistently supported across\n\u003e terminals.\n\nYou can also define links:\n\n```kotlin\nsection {\n  text(\"Would you like to \")\n  link(\"https://github.com/varabyte/kotter\", \"learn Kotter\")\n  textLine(\"?\")\n}\n```\n\n![Code sample in action](https://github.com/varabyte/media/raw/main/kotter/images/kotter-text-ex-5.png)\n\nalthough keep in mind that this feature is not guaranteed to work on every terminal. In that case, it will simply render\nas normal text.\n\n### 🪆 State and scopedState\n\nTo reduce the chance of introducing unexpected bugs later, state changes (like colors) will be localized to the current\n`section` block only:\n\n```kotlin\nsection {\n  blue(BG)\n  red()\n  text(\"This text is red on blue\")\n}.run()\n\nsection {\n  text(\"This text is rendered using default colors\")\n}.run()\n```\n\n![Code sample in action](https://github.com/varabyte/media/raw/main/kotter/images/kotter-scope-ex-1.png)\n\nWithin a section, you can also use the `scopedState` method. This creates a new scope within which any state will be\nautomatically discarded after it ends.\n\n```kotlin\nsection {\n  scopedState {\n    red()\n    blue(BG)\n    underline()\n    textLine(\"Underlined red on blue\")\n  }\n  text(\"Text without color or decorations\")\n}.run()\n```\n\n![Code sample in action](https://github.com/varabyte/media/raw/main/kotter/images/kotter-scope-ex-2.png)\n\n\u003e [!NOTE]\n\u003e Scoped text effect methods (like `red { ... }`) work by calling `scopedState` for you under the hood.\n\n### 🎬 Rerendering sections\n\nThe `section` block is designed to be run one _or more_ times. That is, you can write logic inside it which may not get\nexecuted on the first run but will be on a followup run.\n\nHere, instead of just calling `run()`, we create a `run` block, having it update a variable that is also referenced by\nthe `section` block. This example will render the section twice - once when `run` is first called and again when it\ncalls `rerender`:\n\n```kotlin\nvar result: Int? = null\nsection {\n  text(\"Calculating... \")\n  if (result != null) {\n    text(\"Done! Result = $result\")\n  }\n}.run {\n  result = doNetworkFetchAndExpensiveCalculation()\n  rerender()\n}\n```\n\n![Code sample in action](https://github.com/varabyte/media/raw/main/kotter/screencasts/kotter-calculating.gif)\n\nThe `run` block runs as a suspend function, so you can call other suspend methods from within it.\n\nYour program will be blocked until the run block has finished (or, if it has triggered a rerender, until the last\nrerender finishes).\n\n#### LiveVar\n\nIn our example above, the `run` block calls a `rerender` method, which you can call to request another render pass:\n\n```kotlin\n/* ... */\nrun {\n  result = doNetworkFetchAndExpensiveCalculation()\n  rerender()\n}\n```\n\nHowever, remembering to call `rerender` yourself is potentially fragile and could be a source of bugs in the future when\ntrying to figure out why your console isn't updating.\n\nFor this purpose, Kotter provides the `LiveVar` class, which, when modified, will automatically request a rerender.\nAn example will demonstrate this in action shortly.\n\nTo create a `LiveVar`, simply change a normal variable declaration line like:\n\n```kotlin\nsession {\n  var result: Int? = null\n  /* ... */\n}\n```\n\nto:\n\n```kotlin\nsession {\n  var result by liveVarOf\u003cInt?\u003e(null)\n  /* ... */\n}\n```\n\n\u003e [!IMPORTANT]\n\u003e The `liveVarOf` method is provided by the `session` block. For many remaining examples, we'll elide the\n\u003e `session` boilerplate, but that doesn't mean you can omit it in your own program!\n\nLet's apply `liveVarOf` to our earlier example in order to remove the `rerender` call:\n\n```kotlin\nvar result by liveVarOf\u003cInt?\u003e(null)\nsection {\n  /* ... no changes ... */\n}.run {\n  result = doNetworkFetchAndExpensiveCalculation()\n}\n```\n\nAnd done! Fewer lines and less error pone.\n\nHere's another example, showing how you can use `run` and a `LiveVar` to render a progress bar:\n\n```kotlin\n// Prints something like: [****------]\nval BAR_LENGTH = 10\nvar numFilledSegments by liveVarOf(0)\nsection {\n  text(\"[\")\n  for (i in 0 until BAR_LENGTH) {\n    text(if (i \u003c numFilledSegments) \"*\" else \"-\")\n  }\n  text(\"]\")\n}.run {\n  var percent = 0\n  while (percent \u003c 100) {\n    delay(Random.nextLong(10, 100))\n    percent += Random.nextInt(1,5)\n    numFilledSegments = ((percent / 100f) * BAR_LENGTH).roundToInt()\n  }\n}\n```\n\n![Code sample in action](https://github.com/varabyte/media/raw/main/kotter/screencasts/kotter-progress.gif)\n\n#### LiveList\n\nSimilar to `LiveVar`, a `LiveList` is a reactive primitive which, when modified by having elements added to or\nremoved from it, causes a rerender to happen automatically.\n\nYou don't need to use the `by` keyword with `LiveList`. Instead, within a `session`, just assign a variable to the\nresult of the `liveListOf` method:\n\n```kotlin\nval fileWalker = FileWalker(\".\") // This class doesn't exist but just pretend for this example...\nval fileMatches = liveListOf\u003cString\u003e()\nsection {\n  textLine(\"Matches found so far:\")\n  if (fileMatches.isNotEmpty()) {\n    for (match in fileMatches) {\n      textLine(\" - $match\")\n    }\n  }\n  else {\n    textLine(\"No matches so far...\")\n  }\n}.run {\n  fileWalker.findFiles(\"*.txt\") { file -\u003e\n    fileMatches += file.name\n  }\n}\n```\n\n![Code sample in action](https://github.com/varabyte/media/raw/main/kotter/screencasts/kotter-files.gif)\n\nThe `LiveList` class is thread safe, but you can still run into trouble if you access multiple values on the list one\nafter the other, as a lock is released between each check. It's always possible that modifying the first property will\nkick off a new render which will start before the additional values are set, in other words.\n\nTo handle this, you can use the `LiveList#withWriteLock` method:\n\n```kotlin\nval fileWalker = FileWalker(\".\")\nval last10Matches = liveListOf\u003cString\u003e()\nsection {\n  /* ... */\n}.run {\n  fileWalker.findFiles(\"*.txt\") { file -\u003e\n    last10Matches.withWriteLock {\n      add(file.name)\n      if (size \u003e 10) { removeAt(0) }\n    }\n  }\n}\n```\n\nThe general rule of thumb is: use `withWriteLock` if you want to modify more than one property from the list at the same\ntime within your `run` block.\n\n\u003e [!NOTE]\n\u003e You don't have to worry about locking within a `section { ... }` block. Data access is already locked for you in that\n\u003e context.\n\n#### Other Collections\n\nIn addition to `LiveList`, Kotter also provides `LiveMap` and `LiveSet`. There's no need to extensively document these\nclasses here as much of the earlier `LiveList` section applies to them as well. It's just the data structure that is\ndifferent.\n\nYou can create these classes using `liveMapOf(...)` and `liveSetOf(...)`, respectfully.\n\n#### Signals and waiting\n\nA common pattern is for the `run` block to wait for some sort of signal before finishing, e.g. in response to some\nevent. You could always use a general threading trick for this, such as a `CountDownLatch` or a\n`CompletableDeffered\u003cUnit\u003e` to stop the block from finishing until you're ready:\n\n```kotlin\nval fileDownloader = FileDownloader(\"...\")\nsection {\n  /* ... */\n}.run {\n  val finished = CompletableDeffered\u003cUnit\u003e()\n  fileDownloader.onFinished += { finished.complete(Unit) }\n  fileDownloader.start()\n  finished.await()\n}\n```\n\nbut, for convenience, Kotter provides the `signal` and `waitForSignal` methods, which do this for you.\n\n```kotlin\nval fileDownloader = FileDownloader(\"...\")\nsection {\n  /* ... */\n}.run {\n  fileDownloader.onFinished += { signal() }\n  fileDownloader.start()\n  waitForSignal()\n}\n```\n\nThese methods are enough in most cases.\n\n\u003e [!NOTE]\n\u003e If you call `signal` before you reach `waitForSignal`, then `waitForSignal` will just pass through without stopping.\n\nThere's also a convenience `runUntilSignal` method you can use, within which you don't need to call `waitForSignal`\nyourself, since this case is so common:\n\n```kotlin\nval fileDownloader = FileDownloader(\"...\")\nsection {\n  /* ... */\n}.runUntilSignal {\n  fileDownloader.onFinished += { signal() }\n  fileDownloader.start()\n}\n```\n\n### ⌨️ User input\n\n#### Typed input\n\nKotter consumes keypresses, so as the user types into the console, nothing will show up unless you intentionally print\nit. You can easily do this using the `input` method, which handles listening to kepresses and adding text into your\nsection at that location:\n\n```kotlin\nsection {\n  // `input` is a method that inserts any user input typed so far in place where it is called.\n  // Your section block will automatically rerender when its value changes.\n  text(\"Please enter your name: \"); input()\n}.run { /* ... */ }\n```\n\n![Code sample in action](https://github.com/varabyte/media/raw/main/kotter/screencasts/kotter-enter-name.gif)\n\nThe input method automatically adds a cursor for you. It also handles keys like LEFT/RIGHT and HOME/END, moving the\ncursor back and forth between the bounds of the input string.\n\nYou can intercept input as it is typed using the `onInputChanged` event:\n\n```kotlin\nsection {\n  text(\"Please enter your name: \"); input()\n}.run {\n  onInputChanged {\n    input = input.toUpperCase()\n  }\n  /* ... */\n}\n```\n\nYou can also use the `rejectInput` method to return your input to the previous (presumably valid) state.\n\n```kotlin\nsection {\n  text(\"Please enter your name: \"); input()\n}.run {\n  onInputChanged {\n    if (input.any { !it.isLetter() }) { rejectInput() }\n    // Would also work: input = input.filter { it.isLetter() }\n    // although often `rejectInput()` specifies your intention more clearly\n  }\n  /* ... */\n}\n```\n\nTo handle when the user presses the _ENTER_ key, use the `onInputEntered` event. You can use it in conjunction with the\n`onInputChanged` event we just discussed:\n\n```kotlin\nvar name = \"\"\nsection {\n  text(\"Please enter your name: \"); input()\n}.runUntilSignal {\n  onInputChanged { if (input.any { !it.isLetter() }) { rejectInput() } }\n  onInputEntered { name = input; signal() }\n}\n```\n\nAbove, we've indicated that we want to close the section when the user presses _ENTER_. Since this is actually a fairly\ncommon case, Kotter provides `runUntilInputEntered` for your convenience. Using it, we can simplify the above example a\nbit, typing fewer characters for identical behavior and expressing clearer intention:\n\n```kotlin\nvar name = \"\"\nsection {\n  text(\"Please enter your name: \"); input()\n}.runUntilInputEntered {\n  onInputChanged { if (input.any { !it.isLetter() }) { rejectInput() } }\n  onInputEntered { name = input }\n}\n```\n\n#### Input Completions\n\nYou can pass in an `InputCompleter` implementation to `input` that can generate suggestions based on the current input.\nThe user can press RIGHT at any time to autocomplete any suggestions shown to them.\n\nHere's the interface (with some parts elided for simplicity):\n\n```kotlin\ninterface InputCompleter {\n    fun complete(input: String): String?\n}\n\ninput(object : InputCompleter {\n    override fun complete(input: String): String? { /* ... */ }\n})\n```\n\nPerhaps you have a database of names in your program? You can use it to provide suggestions. If your implementation\nreturns null, that means no suggestion was found:\n\n```kotlin\nobject : InputCompleter {\n  override fun complete(input: String): String? {\n      return names\n          .firstOrNull { it.startsWith(input) }\n          ?.let { it.drop(input.length) }\n    // NOTE:      ^^^^^^^^^^^^^^^^^^^^\n    // Don't return the whole word; just the part that comes after the user's input so far.\n  }\n}\n```\n\nKotter provides a very useful implementation out of the box, called `Completions`, which lets you specify a list of\nvalues that will be autocompleted as long as the user's input matches one of them.\n\n```kotlin\nsection {\n  text(\"Continue? \"); input(Completions(\"yes\", \"no\"))\n}.run()\n```\n\nOrder matters! If nothing is typed, the first completion will be suggested. If multiple values match, the one earliest\nin the list will be suggested.\n\n#### Altering the input's appearance\n\nThere are two callbacks you can pass into `input` to affect how it looks, `viewMap` (for altering the character that\nappears) and `customFormat` (for applying rendering effects). Both callbacks operate character by character, exposed in\nthe callback as `ch`. You can also query the whole text string in (`text`) along with the current index (`index`) in\ncase those help you with the current logic.\n\n##### `viewMap`\n\n`viewMap` intercepts an incoming character and outputs a new character which will get rendered in its place. This is a\nvisual change only! The `onInputEntered` callback will still be triggered with the original input without the view\nmapping applied.\n\nIt is commonly used to mask something like a password field:\n\n```kotlin\nvar password = \"\"\nsection {\n  text(\"Password: \"); input(viewMap = { '*' })\n}.runUntilInputEntered {\n  onInputEntered { password = input }\n}\n\n// \"password\" will be set to the actual password; user will only ever see \"*\"s\n```\n\n![Code sample in action](https://github.com/varabyte/media/raw/main/kotter/screencasts/kotter-password.gif)\n\n##### `customFormat`\n\n`customFormat` lets you change the color or text effects of the input. Within the callback, you can call methods\n`color(...)`, `bold()`, `underline()`, and `strikethrough()` to apply each effect, respectively.\n\nIf you want to highlight valid characters and/or emphasize invalid characters in your input, `customFormat` is the way\nto go:\n\n```kotlin\nsection {\n  text(\"PIN: \"); input(customFormat = { if (ch.isDigit()) green() else red() })\n}.runUntilInputEntered {\n  onInputEntered { if (input.any { !it.isDigit() }) rejectInput() }\n}\n```\n\n![Code sample in action](https://github.com/varabyte/media/raw/main/kotter/screencasts/kotter-pin.gif)\n\n#### Multiline Input\n\nOccasionally, you may want to allow users to type long-form text, with newlines. `input` is quite useful but it\nintentionally doesn't allow newlines. That's because pressing ENTER fires the `onInputEntered` event.\n\nIf you need longer form input, you can reach to `multilineInput`. Users must terminate their input by sending an EOF\nsignal (generated by pressing CTRL-D), since ENTER is now used for newlines.\n\n\u003e [!NOTE]\n\u003e Unfortunately, SHIFT+ENTER, although it is commonly used for handling newlines in most modern editors, is unavailable\n\u003e to us as consoles don't reveal meta-key (e.g. CTRL, SHIFT) states. As console application developers, we're\n\u003e essentially blind to them. CTRL-D is the traditional way to close input streams in many CLIs, because the system\n\u003e translates those keystrokes into an EOF signal, which is all the applications see.\n\nMultiline inputs allow the user to navigate text typed in by pressing the arrow, home, end, page up, and page down keys.\nIn order to support this feature, multiline inputs always start on a new line and any text following it will also appear\non a new line after the input block.\n\n```kotlin\nsection {\n  black(isBright = true) { text(\"Send a text message (press CTRL-D when finished)\") }\n  multilineInput()\n}.runUntilInputEntered {\n  onInputEntered { sendMessage(input.trim()) }\n}\n```\n![Code sample in action](https://github.com/varabyte/media/raw/main/kotter/screencasts/kotter-multiline.gif)\n\n#### Keypresses\n\nIf you're interested in specific keypresses and not simply input that's been typed in, you can register a listener to\nthe `onKeyPressed` event:\n\n```kotlin\nsection {\n  textLine(\"Press Q to quit\")\n  /* ... */\n}.run {\n  var quit = false\n  onKeyPressed {\n    when(key) {\n      Keys.Q -\u003e quit = true\n    }\n  }\n\n  while (!quit) {\n    delay(16)\n    /* ... */\n  }\n}\n```\n\nFor convenience, there's also a `runUntilKeyPressed` method you can use to help with patterns like the above. It can be\nnice, for example, to let the user press _Q_ to quit your application:\n\n```kotlin\nsection {\n  textLine(\"Press Q to quit\")\n  /* ... */\n}.runUntilKeyPressed(Keys.Q) {\n  while (true) {\n    delay(16)\n    /* ... */\n  }\n}\n```\n\n### ⏳ Timers\n\nKotter can manage a set of timers for you. Use the `addTimer` method in your `run` block to add some:\n\n```kotlin\nsection {\n  /* ... */\n}.runUntilSignal {\n  addTimer(500.milliseconds) {\n    println(\"500ms passed!\")\n    signal()\n  }\n}\n```\n\nYou can create a repeating timer by passing in `repeat = true` to the method. And if you want to stop it from repeating\nat some point, set `repeat = false` inside the timer block when it is triggered:\n\n```kotlin\nval BLINK_TOTAL_LEN = 5.seconds\nval BLINK_LEN = 250.milliseconds\nvar blinkOn by liveVarOf(false)\nsection {\n  scopedState {\n    if (blinkOn) invert()\n    textLine(\"This line will blink for ${BLINK_TOTAL_LEN.toSeconds()} seconds\")\n  }\n\n}.run {\n  var blinkCount = BLINK_TOTAL_LEN.toMillis() / BLINK_LEN.toMillis()\n  addTimer(BLINK_LEN, repeat = true) {\n    blinkOn = !blinkOn\n    blinkCount--\n    if (blinkCount == 0L) {\n      repeat = false\n    }\n  }\n  /* ... */\n}\n```\n\n![Code sample in action](https://github.com/varabyte/media/raw/main/kotter/screencasts/kotter-blink.gif)\n\nWith timers running, it's possible your `run` block will exit while things are in a state you didn't intend (e.g. in the\nabove example with the blink effect still on). You should use the `onFinishing` event to handle this case:\n\n```kotlin\nvar blinkOn by liveVarOf(false)\nsection {\n  /* ... */\n}.onFinishing {\n  blinkOn = false // Because user might press Q while the blinking state was on\n}.runUntilKeyPressed(Keys.Q) {\n  addTimer(250.milliseconds, repeat = true) { blinkOn = !blinkOn }\n  /* ... */\n}\n```\n\n\u003e [!IMPORTANT]\n\u003e Unlike all the other events we discussed earlier, `onFinishing` is registered directly against the underlying\n\u003e `section` and not inside the `run` block, because it is actually triggered AFTER the run pass is finished but before\n\u003e the block is torn down.\n\n`onFinishing` will only run after all timers are stopped, so you don't have to worry about setting a value that an\nerrant timer will clobber later.\n\n### 🎥 Animations\n\nAnimations make a huge difference for how the user experiences your application, so Kotter strives to make it trivial to\nadd them into your program.\n\n#### Text Animation\n\nYou can easily create quick animations by calling `textAnimOf`:\n\n```kotlin\nvar finished = false\nval spinnerAnim = textAnimOf(listOf(\"\\\\\", \"|\", \"/\", \"-\"), 125.milliseconds)\nval thinkingAnim = textAnimOf(listOf(\"\", \".\", \"..\", \"...\"), 500.milliseconds)\nsection {\n  if (!finished) { text(spinnerAnim) } else { text(\"✓\") }\n  text(\" Searching for files\")\n  if (!finished) { text(thinkingAnim) } else { text(\"... Done!\") }\n}.run {\n  doExpensiveFileSearching()\n  finished = true\n}\n```\n\n![Code sample in action](https://github.com/varabyte/media/raw/main/kotter/screencasts/kotter-spinner.gif)\n\nWhen you reference an animation in a render for the first time, it kickstarts a timer automatically for you. In other\nwords, all you have to do is treat your animation instance as if it were a string, and Kotter takes care of the rest!\n\n#### Text animation templates\n\nIf you have an animation that you want to share in a bunch of places, you can create a template for it and instantiate\ninstances from the template. `TextAnim.Template` takes exactly the same arguments as the `textAnimOf` method:\n\n```kotlin\nval SPINNER_TEMPATE = TextAnim.Template(listOf(\"\\\\\", \"|\", \"/\", \"-\"), 250.milliseconds)\n\nval spinners = (1..10).map { textAnimOf(SPINNER_TEMPLATE) }\n/* ... */\n```\n\n#### Render animations\n\nIf you need a bit more power than text animations, you can use a render animation instead. You create one with a\ncallback that is given a frame index and access to the current render scope. You can interpret the frame index however\nyou want and use the render scope to call any of Kotter's text rendering methods that you need.\n\nDeclare a render animation using the `renderAnimOf` method and then invoke the result inside your render block:\n\n```kotlin\nval exampleAnim = renderAnimOf(numFrames = 5, 250.milliseconds) { i -\u003e /* ... */ }\nsection {\n  // Call your render animation passing in the section block (i.e. `this`) as a parameter\n  exampleAnim(this)\n  /* ... */\n}\n```\n\nFor example, let's say we want to rotate through a list of colors and apply those to some text. Text animations only\ndeal with raw text and don't have access to text effects like colors and styles. Therefore, we need to use a render\nanimation instead, giving us access to the `color(Color)` method:\n\n```kotlin\n// Note: `Color` is a Kotter enum that enumerates all the standard colors it supports\n\nval colorAnim = renderAnimOf(Color.entries.size, 250.milliseconds) { i -\u003e\n  color(Color.entries[i])\n}\nsection {\n  colorAnim(this) // Side-effect: sets the color for this section\n  text(\"RAINBOW\")\n}.runUntilSignal { /* ... */ }\n```\n\n![Code sample in action](https://github.com/varabyte/media/raw/main/kotter/screencasts/kotter-rainbow.gif)\n\n#### One-shot animations\n\nBoth text and render animations can be created with a `looping` parameter set to false, if you only want them to run\nonce and stop:\n\n```kotlin\nval arrow = \"=============\u003e\"\n\nval wipeRightAnim = renderAnimOf(\n  arrow.length + 1, // `length + 1` because empty string is also a frame\n  40.milliseconds,\n  looping = false\n) { frameIndex -\u003e\n  textLine(arrow.take(frameIndex))\n}\n\nsection {\n  text(\"Go this way: \"); wipeRightAnim(this)\n}.runUntilSignal {\n  // Give the animation time to complete:\n  addTimer(wipeRightAnim.totalDuration) { signal() }\n}\n```\n\n![Code sample in action](https://github.com/varabyte/media/raw/main/kotter/screencasts/kotter-one-shot.gif)\n\nYou can restart a one-shot animation by setting its `currFrame` property back to 0.\n\n### 📥 Offscreen\n\nOccasionally, when you want to render some marked up text, you'll wish you could measure it first, for example allowing\nyou to pad both sides of each line with spaces to center everything, or putting the right count of \"=\" characters above\nand below a block of text to give it a sort of header effect. But by the time you've rendered something out, then it's\ntoo late to measure it!\n\n`offscreen` to the rescue. You can think of `offscreen` as a temporary buffer to render to, after which you can both\nquery it and control when it actually renders to the screen.\n\n`offscreen` returns a buffer, which is a read-only view of the content. You can query its raw text or line widths,\nfor example. To render it, you need to call `offscreen.createRenderer` and then use `renderer.renderNextRow` to render\nout each line at a time.\n\n\u003e [!CAUTION]\n\u003e Note that a line's _width_ may be different from its string length, as many symbols, such as emojis and Asian\n\u003e characters, may be built out of many combined characters or take up double the space of a regular Latin letter.\n\nHere, we use `offscreen` to render the header effect described above:\n\n```kotlin\nsection {\n  val buffer = offscreen {\n    textLine(\"Multi-line\"); textLine(\"Header\"); textLine(\"Example\")\n  }\n  val headerWidth = buffer.lineWidths.maxOrNull() ?: 0\n  val renderer = buffer.createRenderer()\n  repeat(headerWidth) { text('=') }; textLine()\n  while (renderer.hasNextRow()) {\n    renderer.renderNextRow()\n    textLine()\n  }\n  repeat(headerWidth) { text('=') }; textLine()\n}.run()\n```\n\n![Offscreen header example](https://github.com/varabyte/media/raw/main/kotter/images/offscreen-header-example.png)\n\n\u003e [!NOTE]\n\u003e Although you usually won't need to, you can create multiple renderers per offscreen buffer, each which manages its own\n\u003e state for what row to render out next.\n\nOne nice thing about the offscreen buffer is it manages its own local state, and while it originally inherits its parent\nscope's state, any changes you make within the offscreen buffer will be remembered to its end.\n\nThis is easier seen than described. The following example:\n\n```kotlin\nsection {\n  val buffer = offscreen {\n    textLine(\"Inherited color (red)\")\n    cyan()\n    textLine(\"Local color (cyan)\")\n    textLine(\"Still blue\")\n  }\n\n  val renderer = buffer.createRenderer()\n  red()\n  while (renderer.hasNextRow()) {\n    text(\"red -- \"); renderer.renderNextRow(); textLine(\" -- red\")\n  }\n}.run()\n```\n\nwill render:\n\n![Offscreen local state example](https://github.com/varabyte/media/raw/main/kotter/images/offscreen-local-state.png)\n\nThe driving motivation for adding offscreen buffers was to be able to easily add borders around any block of text, where\nthe border might be a different color than its contents. So when this functionality went in, we also added the\n`bordered` method ([link to example](https://github.com/varabyte/kotter/tree/main/examples/border)).\n\n![Bordered example](https://github.com/varabyte/media/raw/main/kotter/images/border-example.png)\n\nIf you want to implement your own utility method that uses `offscreen` under the hood, you can check\n[bordered's implementation](https://github.com/varabyte/kotter/blob/main/kotter/src/main/kotlin/com/varabyte/kotterx/decorations/BorderSupport.kt)\nyourself to see how it delegates to `offscreen`, padding each row with the right number of spaces so that the border sides all line up.\n\n### 📤 Aside\n\nYou can actually make one-off render requests directly inside a `run` block:\n\n```kotlin\nsection {\n    /* ... */\n}.run {\n    aside {\n        textLine(\"Hello from an aside block\")\n    }\n}\n```\n\nwhich will output text directly before the active section.\n\nIn order to understand aside blocks, you should start to think of Kotter output as two parts -- some static history, and\na dynamic, active area at the bottom. The static history will never change, while the active area will be written and\ncleared and rewritten over and over and over again as needed.\n\nIn general, a section is active *until* it is finished running, at which point it becomes static history, and the next\nsection becomes active. You can almost think about *consuming* an active section, which freezes it after one final\nrender, at which point it becomes static.\n\nIn fact, it's a common pattern to get static instructions out of the way first, in its own section, so we don't waste\ntime rerendering them over and over in the main block:\n\n```kotlin\nsession {\n  // The following instructions are static, just render them immediately\n  section {\n    textLine(\"Press arrow keys to move\")\n    textLine(\"Press R to restart\")\n    textLine(\"Press Q to quit\")\n    textLine()\n  }.run()\n\n  section {\n    /* ... constantly rerendered lines ... */\n  }.runUntilKeyPressed(Keys.Q) { /* ... */ }\n}\n```\n\nOccasionally, however, you want to generate static history *while* a block is still active.\n\nLet's revisit an example from above, our `FileWalker` demo which searched a list of files and added every matching\nresult to a list. We can, instead, put a spinner in the active section and use the `aside` block to output matches:\n\n```kotlin\nval fileWalker = FileWalker(\".\")\nvar isFinished by liveVarOf(false)\nval searchingAnim = textAnimOf(listOf(\"\", \".\", \"..\", \"...\"), 500.milliseconds)\nsection {\n  textLine()\n  if (!isFinished) {\n      textLine(\"Searching$searchingAnim\")\n  }\n  else {\n      textLine(\"Finished searching\")\n  }\n}.run {\n  aside {\n    textLine(\"Matches found so far:\")\n    textLine()\n  }\n\n  fileWalker.findFiles(\"*.txt\") { file -\u003e\n    aside { textLine(\" - ${file.name}\") }\n  }\n  isFinished = true\n}\n```\n\n![Code sample in action](https://github.com/varabyte/media/raw/main/kotter/screencasts/kotter-aside.gif)\n\nAsides are very useful if you have some long-running process that generates text as a side effect. You could imagine\na compiler spitting out warnings and errors as it continues to process more code, or a test runner reporting failures\nas it continues to run more tests. In fact, Kotter provides a [fake compiler example](examples/compiler) that you can\nreference.\n\n### 🪟 Grids\n\nKotter provides support for creating arbitrarily sized grids with multiple rows and columns.\n\nWith Kotter's approach to grids, you specify the number of columns explicitly; rows are auto added as you declare new\ngrid cells.\n\n```kotlin\nsection {\n  // A grid with two columns, each with space for 6 characters\n  grid(Cols(6, 6), characters = GridCharacters.CURVED) {\n    cell { // Auto set to row=0, col=0\n      textLine(\"Cell1\")\n    }\n    cell { // Auto set to row=0, col=1\n      textLine(\"Cell2\")\n    }\n\n    // Third cell in a grid with two columns creates a new row\n    cell { // Auto set to row=1, col=0\n      textLine(\"Cell3\")\n    }\n    cell { // Auto set to row=1, col=1\n      textLine(\"Cell4\")\n    }\n\n    // You can explicitly set the row and column if you want. Rows and columns\n    // are 0-indexed.\n    cell(row = 2, col = 1) { // Jump over cell row=2,col=0\n      textLine(\"Cell6\")\n    }\n  }\n}.run()\n```\n\n![Simple grid example](https://github.com/varabyte/media/raw/main/kotter/images/kotter-grid-simple.png)\n\n\u003e [!TIP]\n\u003e There is also a `Cols.uniform` method for when you want to create multiple columns of the same width. For example,\n\u003e instead of `Cols(6, 6)` which we used above, you could also call `Cols.uniform(2, width = 6)`. It is a bit more\n\u003e verbose but may express intention more clearly.\n\nYou can check out the [grid example](examples/grid/src/main/kotlin/main.kt) for a more comprehensive example.\n\n#### Fit- and star-sized columns\n\nFixed width columns are useful, but Kotter also provides even more functionality via fit- and star-sized columns.\n\nA **fit-sized column** will check all cells it contains and choose a width that fits all of them.\n\n![Fit grid example](https://github.com/varabyte/media/raw/main/kotter/images/kotter-grid-fit-sized.png)\n\nA **star-sized column** will be sized dynamically based on how much space is remaining (more on this in a bit). If you\nhave multiple star-sized columns, then space will be divided between them based on their ratio with each other.\n\nFor example, if you have one star-sized column set to \"2\" and another set to \"1\", then the first column will be twice as\nwide as the second. If you have two star-sized columns both set to \"2\" then they will share the remaining space equally.\n\n![Star grid example](https://github.com/varabyte/media/raw/main/kotter/images/kotter-grid-star-sized.png)\n\nTo determine \"remaining space\", the `grid` method accepts a `targetWidth` parameter. If you don't have any star-sized\ncolumns, the `targetWidth` value does nothing. If you do, then the grid will subtract all fixed and fit width values\nfrom it and share any remaining space between the star-sized columns.\n\nFor a trivial example, say you have a two-column grid with `targetWidth` set to 10. The first column is fixed to 4, and\nthe second column is set to star-sized. The star-sized column will then receive 6 characters of space.\n\nIf you do not set the `targetWidth` at all, then all star-sized columns will shrink to size 1.\n\n#### Building column specifications\n\nEarlier, we used `Cols(6, 6)`, a convenience constructor that accepts only integer values indicating fixed column\nwidths. But for more control, you can construct the `Cols` class using a builder block:\n\n```kotlin\ngrid(Cols { fit(); fixed(10); star() }, targetWidth = 80) {\n  /* ... */\n}\n```\n\n#### Column properties\n\nIn addition to their base value, columns have a few properties you can set: *minimum value*, *maximum value*, and\n*justification*.\n\nHere's an example of setting all three properties:\n\n```kotlin\ngrid(\n  Cols {\n    fit(maxValue = 10)\n    fixed(10, justification = Justification.CENTER)\n    star(minValue = 5)\n  },\n  targetWidth = 80\n) {\n  /* ... */\n}\n```\n\nThe above means that the first column will be fit-sized but will never exceed 10 characters. The second column is fixed\nto 10 characters, and its contents will be centered. The final column is star-sized, but it will never be less than 5\ncharacters.\n\n#### Spanning multiple rows and columns\n\nYou can declare that a cell should span multiple rows and/or columns by setting the `rowSpan` and `colSpan` parameters.\nIf either `rowSpan` or `colSpan` are not specified, then they default to 1.\n\n\u003e [!CAUTION]\n\u003e If `colSpan` is set to a value that would cause the cell to go out of bounds of the number of columns in this grid, an\n\u003e exception is thrown.\n\nA few examples should help illustrate this:\n\n```kotlin\n// Spanning multiple columns\n\ngrid(Cols(3, 3, 3), characters = GridCharacters.CURVED) {\n  cell(row = 0, col = 0, colSpan = 3)\n  cell(row = 2) // Force three rows to be created\n}\n```\n\n![Col spanning grid example](https://github.com/varabyte/media/raw/main/kotter/images/kotter-grid-col-span.png)\n\n```kotlin\n// Spanning multiple rows\n\ngrid(Cols(3, 3, 3), characters = GridCharacters.CURVED) {\n  cell(row = 0, col = 0, rowSpan = 3)\n}\n```\n\n![Row spanning grid example](https://github.com/varabyte/media/raw/main/kotter/images/kotter-grid-row-span.png)\n\n```kotlin\n// Spanning both\n\ngrid(Cols(3, 3, 3, 3), characters = GridCharacters.CURVED) {\n  cell(row = 1, col = 1, rowSpan = 2, colSpan = 2)\n  cell(row = 3) // Force four rows to be created\n}\n```\n\n![Spanning both rows and cols grid example](https://github.com/varabyte/media/raw/main/kotter/images/kotter-grid-both-span.png)\n\n\u003e [!CAUTION]\n\u003e A cell spanning columns will inherit its justification from its left-most cell. Additionally, any cell that spans\n\u003e multiple columns will not be included in any fit-size calculations.\n\n#### Auto-layout of cells\n\nWhen you declare a `cell` block without specifying a row or column, it will automatically be placed in the next empty\nslot after the last cell that was declared. This goes from left-to-right, top-to-bottom.\n\nFor example:\n\n```kotlin\ngrid(Cols(1, 1, 1)) {\n  cell(row = 1) { text(\"1\") } // declared cell at row=1, col=0\n  cell(row = 0) { text(\"2\") } // declared cell at row=0, col=0\n  cell { text(\"3\") } // next empty slot is row=0, col=1\n  cell { text(\"4\") }\n  cell { text(\"5\") }\n}\n\n// +-+-+-+\n// |2|3|4|\n// +-+-+-+\n// |1|5| |\n// +-+-+-+\n```\n\nWhile the above example feels forced (it should be pretty rare to intentionally register cells out of order), the way\ncells flow is intuitive when used in conjunction with row spans:\n\n```kotlin\ngrid(Cols(1, 1, 1)) {\n  cell(rowSpan = 2) { text(\"1\") } // declared cell at row=0, col=0\n  cell { text(\"2\") }\n  cell { text(\"3\") }\n  cell { text(\"4\") }\n}\n\n// +-+-+-+\n// |1|2|3|\n// | +-+-+\n// | |4| |\n// +-+-+-+\n```\n\nFinally, here's an example of how cells flow following a cell spanning multiple columns:\n\n```kotlin\ngrid(Cols(1, 1, 1)) {\n  cell(colSpan = 2) { text(\"1\") }\n  cell { text(\"2\") }\n  cell { text(\"3\") }\n}\n\n// +---+-+\n// |1  |2|\n// +-+-+-+\n// |3| | |\n// +-+-+-+\n```\n\n### 🪝 Shutdown Hook\n\nTerminal applications can be forcefully interrupted if the user presses CTRL-C. Some apps may want to handle this.\n\nWhile you can register hooks for such an event directly with the underlying platform, Kotter offers an additional\nAPI for this: `addShutdownHook`.\n\nYou can register a handler insider your run block, like so:\n\n```kotlin\nsection { /* ... */ }.run {\n  addShutdownHook { /* this will get called only if the user presses ctrl-c */ }\n}\n```\n\nUnlike a shutdown hook registered with the system, Kotter's managed shutdown hook will additionally try to give the\nrender block one more chance to run before the program exits. You can take advantage of this to send a message to the\nuser:\n\n```kotlin\nvar emergencyShutdown by liveVarOf(false)\nsection {\n  /* ... */\n  if (emergencyShutdown) {\n    yellow {\n      textLine(\"This program is exiting NOW because the user pressed CTRL-C.\")\n      textLine(\"We sent a request to shut down the server but could not confirm it was received.\")\n      textLine(\"Consider running `./stop-server.sh` later to make sure it actually stopped.\")\n    }\n  }\n}.run {\n  addShutdownHook {\n    sendServerShutdownRequestAsync()\n    emergencyShutdown = true\n  }\n}\n```\n\nIt's important that you never run any long-running logic inside a shutdown hook. If your program continues to run for\ntoo long after an interrupt request, the system may just halt your program anyway.\n\nFinally, you should not rely on shutdown hooks actually getting run. They don't get triggered if the system exits\nnormally, the program crashes, or if the process gets aggressively halted by the OS (perhaps because things were taking\ntoo long to shut down, or maybe the user issued a kill command from the terminal).\n\n### 📐 Text Metrics\n\nUnicode is hard! But if you want to use non-Latin characters in your application, Kotter tries to handle it correctly.\n\nThe class that supports this functionality is exposed from the Kotter session via the `textMetrics` property, so you can\nuse it as well, anytime you need to measure text that may contain complex Unicode characters.\n\nKotter's grids and bordered text areas use text metrics so that their borders still line up correctly when containing\nemojis or double-width, non-Latin characters. And the `offscreen` buffer uses it internally and exposes the final render\nwidth values via the `lineWidths` property.\n\n```kotlin\nclass TextMetrics {\n  fun renderWidthOf(str: CharSequence): Int\n  fun graphemeClusterLengthAt(str: CharSequence, index: Int): Int\n  fun graphemeStartIndex(str: CharSequence, index: Int): Int\n  fun isEmoji(codePoint: Int): Boolean\n}\n```\n\n\u003e [!NOTE]\n\u003e There is also a `renderWidthOf` method provided that takes a single `Char` value, but the `String` version is\n\u003e recommended as sometimes the render width of a character can change based on surrounding context, which is why we\n\u003e elide it here. But you can use it if you know what you're doing!\n\nA _grapheme_ is the final symbol that gets rendered to the screen. A _grapheme cluster_ is the underlying characters\nwhich are associated with the resulting grapheme. For basic text, the grapheme is often one-to-one with the underlying\ncharacter, but with emoji, that's often not the case. For example, the emoji 👨‍👩‍👧‍👦 is built up from 11 underlying\ncharacters! Use `graphemeCusterLengthAt` with an index pointing at the beginning of a grapheme cluster to get this\nvalue.\n\nYou can also use `graphemeStartIndex` to return the start of a cluster given any index inside said cluster. It is not\nexpected that most users will ever need this, but it can be useful if you want to iterate a Unicode string backwards one\ngrapheme at a time.\n\nWhen a grapheme is rendered, it takes up either 1 or 2 spaces in the terminal grid. This is important to know when\nmeasuring out how much text will fit inside a container. Use `renderWidthOf` to get this value.\n\nBringing it all together, if you wanted to iterate a text string that might contain some Unicode, the skeleton of your\nloop would generally look like this:\n\n```kotlin\n// Iterating forward\nvar currIndex = 0\nvar currWidth = 0\nwhile (currIndex \u003c str.length) {\n    val graphemeLen = textMetrics.graphemeClusterLengthAt(str, currIndex)\n    val grapheme = textMetrics.substring(currIndex, currIndex + graphemeLen)\n    // Do something with the grapheme here\n    currWidth += textMetrics.renderWidthOf(grapheme)\n    currIndex += nextGraphemeLen\n}\n\n// Iterating backwards\nvar currIndex = string.length - 1\nvar currWidth = 0\nwhile (currIndex \u003e= 0) {\n    val graphemeStart = textMetrics.graphemeStartIndex(str, currIndex)\n    val graphemeLen = textMetrics.graphemeClusterLengthAt(str, graphemeStart)\n    val grapheme = textMetrics.substring(currIndex, graphemeStart + graphemeLen)\n    // Do something with the grapheme here\n    currWidth += textMetrics.renderWidthOf(grapheme)\n    currIndex = graphemeStart - 1\n}\n```\n\n#### Truncating text\n\nKotter extends the core `TextMetrics` class with a useful method that lets you truncate text to ensure it fits within a\ntarget render width:\n\n```kotlin\nfun TextMetrics.truncateToWidth(\n  text: String,\n  maxWidth: Int,\n  truncateAt: TruncateAt = TruncateAt.END,\n  ellipsis: String = \"\"\n): String\n```\n\nBy default, the method will just truncate your string if it doesn't fit, but you can also provide an optional ellipsis\nvalue. If present, the method will more \"gently\" cut off the end of the text (after securing any additional space needed\nfor the ellipsis value itself)\n\n```kotlin\n// Truncate with 1-width ellipsis character\ntextMetrics.truncateToWidth(\"Hello world\", maxWidth = 8, ellipsis = \"…\")\n// Returns: \"Hello w…\"\n\n// Truncate with 3-width ellipsis character\ntextMetrics.truncateToWidth(\"Hello world\", maxWidth = 8, ellipsis = \"...\")\n// Returns: \"Hello...\"\n```\n\nYou can also specify that the truncation should happen at the start or middle of the string:\n\n```kotlin\ntextMetrics.truncateToWidth(\"Hello world\", maxWidth = 8, TruncateAt.END, ellipsis = \"…\")\n// Returns: \"Hello w…\"\ntextMetrics.truncateToWidth(\"Hello world\", maxWidth = 8, TruncateAt.START, ellipsis = \"…\")\n// Returns: \"…o world\"\ntextMetrics.truncateToWidth(\"Hello world\", maxWidth = 8, TruncateAt.MIDDLE, ellipsis = \"…\")\n// Returns: \"Hell…rld\"\n```\n\nEllipsis presets are provided for your convenience:\n\n```kotlin\nobject EllipsisPresets {\n    const val NONE = \"\"\n    const val SYMBOL = \"…\"\n    const val PERIODS = \"...\"\n}\n\ntextMetrics.truncateToWidth(\"Hello world\", maxWidth = 8, ellipsis = EllipsisPresets.SYMBOL)\n```\n\n## 🎓 Advanced\n\n### 🔨 \"Extending\" Kotter\n\nKotter aims to provide all the primitives you need to write dynamic, interactive console applications, such as\n`textLine`, `input`, `offscreen`, `aside`, `onKeyPressed`, etc.\n\nBut we may have missed _your_ use case, or maybe you just want to refactor out some logic to share across `section`s.\nThis is totally doable, but it requires writing extension methods against the correct receiving classes. At this point,\nwe need to discuss the framework in a bit more detail than beginners need to know.\n\nFor reference, you should also look at the [extend](examples/extend) sample project, which was written to demonstrate\nsome of the concepts that will be discussed here.\n\n#### Scopes\n\nBefore continuing, let's look at the overview of a Kotter application. The following may look a bit complex at first\nglance, but don't worry as the remaining subsections will break it down:\n\n```\n┌───────── session {\n│ ┌─┬─────   section {\n│ │ │           ...\n│ │ 3a┌───      offscreen {\n│ │ │ 3b           ...\n│ │ │ └───      }\n│ │ └─────   }.onFinished {\n1 2             ...\n│ │ ┌─────   }.run {\n│ │ │           ...\n│ │ 4 ┌───      aside {\n│ │ │ 3c           ...\n│ │ │ └───      }\n│ └─┴─────   }\n└───────── }\n```\n\n**1 - `Session`**\n\n```\n┌─ session {\n│    section {\n│      ...\n│    }.run {\n│      ...\n│    }\n└─ }\n```\n\nThe top level of your whole application. `Session` owns a `data: ConcurrentScopedData` field which we'll talk more about\na little later. However, it's worth understanding that `data` lives inside a session, although many scopes additionally\nexpose it themselves. Any time you see some Kotter code interacting with a `data` field, it is really pointing back to\nthis singular one.\n\n`Session` is the scope you need when you want to call `liveVarOf` or `liveListOf`, or even to declare a `section`:\n\n```kotlin\nfun Session.firstSection() {\n  var name by liveVarOf(\"\")\n  var age by liveVarOf(18)\n  section { /* ... */ }.run { /* ... */ }\n}\n\nfun Session.secondSection() { /* ... */ }\nfun Session.thirdSection() { /* ... */ }\n\n// Later ...\n\nsession {\n  firstSection()\n  secondSection()\n  thirdSection()\n}\n```\n\n**2 - `Section`**\n\n```\n┌─ section {\n│    ...\n│  }.run {\n│    ...\n└─ }\n```\n\nThe `section { ... }` block receives a `RenderScope` and the `run { ... }` block receives a `RunScope`. These are\ndiscussed next.\n\n**3 - `RenderScope`**\n\n```\n3a\n┌─ section {\n│    ...\n└─ }\n\n3b\n┌─ offscreen {\n│    ...\n└─ }\n\n3c\n┌─ aside {\n│    ...\n└─ }\n```\n\nThis scope represents a render pass. This is the scope that owns `textLine`, `red`, `green`, `bold`, `underline`, and\nother text rendering methods.\n\nThis can be a useful scope for extracting out a common text rendering pattern. For example, let's say you wanted to\ndisplay a bunch of terminal commands and their arguments, and you want to highlight the command a particular color,\ne.g. cyan:\n\n```kotlin\nsection {\n  cyan { text(\"cd\") }; textLine(\" /path/to/code\")\n  cyan { text(\"git\") }; textLine(\" init\")\n}\n```\n\nThat can get a bit repetitive. Plus, if you decide to change the color later, or maybe bold the command part, you'd have\nto do it all over the place. Also, putting the logic behind a function can help you express your intention more clearly.\n\nLet's refactor it!\n\n```kotlin\nfun RenderScope.shellCommand(command: String, arg: String) {\n  cyan { text(command) }; textLine(\" $arg\")\n}\n\nsection {\n  shellCommand(\"cd\", \"/path/to/code\")\n  shellCommand(\"git\", \"init\")\n}\n```\n\nMuch better!\n\nOn its surface, the concept of a `RenderScope` seems pretty straightforward, but the gotcha is that Kotter offers a few\nseparate areas that accept render blocks. In addition to the main rendering block, there are also `offscreen` and\n`aside` blocks, which allow rendering to different targets (these are discussed earlier in the README in case you aren't\nfamiliar with them here).\n\nOccasionally, you may want to define an extension method that *only* applies to one of the three blocks (usually the\nmain one). In order to narrow down the place your helper method will appear in, you can use `MainRenderScope` (3a),\n`OffscreenRenderScope` (3b), and/or `AsideRenderScope` (3c) as your method receiver instead of just `RenderScope`.\n\nFor example, the `input()` method doesn't make sense in an `offscreen` or `aside` context (as those aren't interactive).\nTherefore, its definition looks like `fun MainRenderScope.input(...) { ... }`\n\n**4 - `RunScope`**\n\n```\n   section {\n     ...\n┌─ }.run {\n│    ...\n└─ }\n```\n\n`RunScope` is used for `run` blocks. It's useful for extracting logic that deals with handling user input or\nlong-running tasks. Functions like `onKeyPressed`, `onInputChanged`, `addTimer` etc. are defined on top of this scope.\n\n```kotlin\nfun RunScope.exec(vararg command: String) {\n  val process = Runtime.getRuntime().exec(*command)\n  process.waitFor()\n}\n\nsection {\n    textLine(\"Please wait, cloning the repo...\")\n}.run {\n  exec(\"git\", \"clone\", \"https://github.com/varabyte/kotter.git\")\n  /* ... */\n}\n```\n\nAs the `run` method is a suspend function, you may declare your `RunScope` extending methods suspend as well:\n\n```kotlin\nsuspend fun RunScope.doLongRunningTask() { /* ... */ }\n\nsection {\n    textLine(\"Please wait...\")\n}.run {\n    doLongRunningTask()\n}\n```\n\n**`SectionScope`**\n\nTo close off all this scope discussion, it's worth mentioning that a `SectionScope` interface exists. It is the base\ninterface to both `RenderScope` AND a `RunScope`, and using it can allow you to define the occasional helper method that\ncan be called from both of them.\n\nIt's not expected that most users will ever use this, but it can be a way to write a common getter that both the render\nblock and run block can use (perhaps for data that is set by the run block elsewhere).\n\n#### ConcurrentScopedData\n\nThe one thing that all scopes have in common is they expose access to a session's `data` field. OK, but what is it?\n\n`ConcurrentScopedData` is a thread-safe hashmap, where the keys are always of type `ConcurrentScopedData.Key\u003cT\u003e`, and\nsuch keys are associated with a `ConcurrentScopedData.Lifecycle` (meaning that any data you register into the map will\nalways be released when some parent lifecycle ends, unless you remove it yourself manually first).\n\nKotter itself manages four lifecycles: `Session.Lifecycle`, `Section.Lifecycle`, `MainRenderScope.Lifecycle`, and\n`RunScope.Lifecycle` (each associated with the scopes discussed above).\n\n\u003e [!NOTE]\n\u003e No lifecycles are provided for `offscreen` or `aside` blocks at the moment. Feel free to open up an issue with a\n\u003e use-case requiring additional lifecycles if you run into one. So far, the `RunScope.Lifecycle` has been effective\n\u003e enough for our needs.\n\nKeep in mind that the `MainRenderScope.Lifecycle` dies after a *single* render pass. Almost always you want to tie data\nto `Section.Lifecycle`, as it survives across multiple runs.\n\nNothing prevents you from defining your own lifecycle - just be sure to call `data.start(...)` and `data.stop(...)` with\nit.\n\n```kotlin\n  object MyLifecycle : ConcurrentScopedData.Lifecycle\n  private val MySetting = MyLifecycle.createKey\u003cBoolean\u003e()\n\n  try {\n    data.start(MyLifecycle)\n    data[MySetting] = true\n    /* ... */\n  } finally {\n    data.stop(MyLifecycle) // Side effect: Removes MySetting from data\n  }\n```\n\nLifecycles can be defined as subordinate to other lifecycles, so if you create a lifecycle that is tied to the\n`RunScope` lifecycle for example, then you don't need to explicitly call `stop` yourself (but you still need to call\n`start`).\n\n```kotlin\n  object MyLifecycle : ConcurrentScopedData.Lifecycle {\n    override val parent = RunScope.Lifecycle\n  }\n\n  section { /* ... */ }.run {\n    data.start(MyLifecycle) // Will be stopped when the run block finishes\n  }\n```\n\n\nYou can review the `ConcurrentScopedData` class for its full list of documented API calls, but the three common ways to\nadd values to it are:\n\n* always overwrite: `data[key] = value`\n* add if first time: `data.tryPut(key, value) // returns true if added, false otherwise`\n* add if first time but always run some follow-up logic:\u003cbr\u003e\n  `data.putIfAbsent(key, provideInitialValue = { value }) { ... logic using value ... }`\u003cbr\u003e\n  * This is essentially a shortcut for calling `tryPut` and then getting the value, but doing so in a lock-safe manner\n    that ensures no one else grabs the thread from you in between.\n\nBy having a session own and expose such a data structure, it makes it possible for anyone to write their own extension\nmethods on top of Kotter, using data as a way to manage long-lived state. For example, `input()`, which may get called\nmany times in a row as the section rerenders, can distinguish the first time it is called from later calls based on\nwhether some value is present in the data store or not.\n\nTo close this section, we just wanted to say that it was very tempting at first to create a bunch of hardcoded functions\nbaked inside `Section`, `MainRenderScope`, etc., with access to some private state, but implementing everything through\n`ConcurrentScopedData` plus extension methods ensured that we were using the same tools as users.\n\nSo go forth, and extend Kotter!\n\n### 🧪 Testing\n\nKotter includes a separate library that provides useful testing utilities, called `kotter-test-support`. You can review\nthe [Gradle section▲](#-gradle) from earlier to see how to include it in your project.\n\nThe library comes with its [own README](kotterx/kotter-test-support/README.md) which goes into more detail about how\nto write Kotter unit tests.\n\n### 🧵 Thread Affinity\n\nSections are rendered sequentially on a single render thread. Anytime you see a `section { ... }`, no matter which\nthread it is called from, a single thread ultimately handles it. However, if you use two threads to attempt to run one\nsection while another is already running, an exception is thrown.\n\nThe `run` block runs in place on the thread that called it. In this way, progress is prevented until the run logic\nfinishes running.\n\nI made the decision to lock section rendering down so that:\n\n* I don't have to worry about multiple sections `println`ing at the same time - who likes clobbered text?\n* Kotter handles repainting by moving the terminal cursor around, which would fail horribly if multiple sections tried\ndoing this at the same time.\n* Kotter embraces the idea of a dynamic, active section preceded by a bunch of static history. If two dynamic blocks\nwanted to be active at the same time, what would that even mean?\n\nIn practice, I expect this decision won't be an issue for most users. Command line apps are expected to have a main flow\nanyway -- ask the user a question, do some work, then ask another question, etc. It is expected that a user won't ever\neven need to call `section` from more than one thread. It is hoped that the\n\n```kotlin\nsession {\n  section { /* ... render thread ... */ }.run { /* ... current thread ... */ }\n  section { /* ... render thread ... */ }.run { /* ... current thread ... */ }\n  section { /* ... render thread ... */ }.run { /* ... current thread ... */ }\n}\n```\n\npattern (just calling `section`s one after another on a single thread) is powerful enough for most (all?) cases.\n\n### 🖥️  Virtual Terminal\n\n\u003e [!IMPORTANT]\n\u003e The virtual terminal is only supported for JVM targets. Kotlin/Native targets don't implement this feature.\n\nIt's not guaranteed that your program will be called in a terminal that allows interactivity. Kotter, when first run,\nrequests a specific configuration from your system that may be rejected.\n\nFor example, pressing the debug button on your project inside IntelliJ as well as calling `./gradlew run` are two very\ncommon scenarios! In those cases, IntelliJ/Gradle are already consuming the terminal's interactivity themselves, so when\nyour program tries to run, it does so inside a more limited environment.\n\nKotter can (and by default is setup to) detect this case and open up a virtual terminal instead. This fallback gives\nyour application better cross-platform support.\n\nTo modify your program to ALWAYS open the virtual terminal, you can set the `terminal` parameter in `session` like so:\n\n```kotlin\nsession(terminal = VirtualTerminal.create()) {\n  section { /* ... */ }\n  /* ... */\n}\n```\n\nor you can chain multiple factory methods together using the `firstSuccess` method, which will try to start each\nterminal type in turn:\n\n```kotlin\nsession(\n  terminal = listOf(\n    { SystemTerminal() },\n    { VirtualTerminal.create(title = \"My App\", terminalSize = Dimension(30, 30)) },\n  ).firstSuccess()\n) {\n  /* ... */\n}\n```\n\n#### Cross-platform emoji support\n\nOur team produces the `com.varabyte.kotterx:twemoji` artifact, a dependency which provides rendering support for emoji\ncharacters using SVGs provided by the [Twemoji (Twitter Emoji)](https://github.com/twitter/twemoji) project.\n\n\u003e [!IMPORTANT]\n\u003e Above, we mentioned that the virtual terminal is a JVM-only concept. As such, this dependency is only provided for the\n\u003e JVM target.\n\nThe benefit of using an emoji library instead of system fonts is a consistent look and feel whether you are using Mac,\nWindows, or Linux. It also seems to provide more complete emoji support than the fonts do.\n\nAll you have to do is add a dependency on the Kotterx `twemoji` coordinate in you're build script, and it will\nautomatically be picked up by the virtual terminal if it opens up.\n\n```kotlin\n// JVM project\nplugins {\n  kotlin(\"jvm\")\n}\n\ndependencies {\n  implementation(\"com.varabyte.kotter:kotter:1.3.0\")\n  implementation(\"com.varabyte.kotterx:twemoji:1.3.0\")\n}\n\n// Mulitplatform project\nplugins {\n  kotlin(\"multiplatform\")\n}\n\nkotlin {\n  jvm()\n  // optionally other targets\n\n  sourceSets {\n    commonMain {\n      dependencies {\n        implementation(\"com.varabyte.kotter:kotter:1.3.0\")\n      }\n    }\n    jvmMain {\n      dependencies {\n        implementation(\"com.varabyte.kotterx:twemoji:1.3.0\")\n      }\n    }\n  }\n}\n```\n\nOn the left is a screenshot of my virtual terminal running on MacOS using the default system font; on the right,\nTwemoji. Some reports indicate that the left side looks even more dismal on Windows.\n\n![Emojis provided by the system](https://github.com/varabyte/media/raw/main/kotter/images/kotter-emoji-system.png)\n![Emojies from the twemoji project](https://github.com/varabyte/media/raw/main/kotter/images/kotter-emoji-twemoji.png)\n\nThe biggest drawback for including this dependency is it adds about 4MB of data to your application bundle.\n\nWe don't include this as part of core Kotter since it's assumed most users will want to write an application that\ntargets a system terminal and hopefully will never even open up the virtual terminal, so why pay the memory cost?\nAdditionally, allowing an external module to control emoji rendering opens us up to supporting alternate emoji sets in\nthe future.\n\n### 🔙 Fallback in Non-Interactive Terminals\n\nKotter is designed to *only* run within a rich, interactive terminal environment, such as a console with ANSI support\nor inside a virtual terminal.\n\nHowever, there may be cases that Kotter won't be able to run. For example:\n\n1. You're trying to run Kotter in an environment that is both non-interactive AND doesn't have any graphical system\n   enabled (like ssh-ing to some remote machine).\n2. You are in a non-interactive environment, and you constructed your session in a way that excludes using a\n   `VirtualTerminal`.\n3. You are in a non-interactive environment, and you are using Kotlin/Native (which does not provide a `VirtualTerminal`\n   implementation).\n\nIf you run a console app in such a non-interactive environment, calls like `println` and `readLine` still work, but any\nattempts to move the cursor, erase previous lines, and many other ANSI commands which Kotter builds on top of are not\nsupported.\n\nSuch cases are increasingly rare on modern machines, so you may just decide to ignore them and crash!\n\nHowever, if you're very determined, you could consider writing some parts of your program twice, once with Kotter using\nall its fancy bells and whistles, and a second time providing a much simpler presentation, limited to printing\ninformation and asking the occasional question.\n\nTo accomplish this, you can use the following code structure:\n\n```kotlin\nprivate fun Session.runKotterLogic() { /* ... */ }\nprivate fun runFallbackLogic() { /* ... */ }\n\nfun main() {\n    var kotterStarted = false\n    try {\n        session {\n            kotterStarted = true\n            runKotterLogic()\n        }\n    } catch (ex: Exception) {\n        if (!kotterStarted) {\n            runFallbackLogic()\n        } else {\n            // This exception came from after startup, when the user was\n            // interacting with Kotter. Crashing with an informative stack\n            // is probably the best thing we can do at this point.\n            throw ex\n        }\n    }\n}\n```\n\nFor a concrete example, imagine you are writing a file downloading app. You can have the Kotter version show\nanimated progress bars, but if you end up in the fallback zone, you can simply print \"10%... 20%... 30%...\" instead.\nBoth sections could delegate to some downloader class that did all the heavy lifting -- you should\nabsolutely share as much non-UI code as you can!\n\n### 📦 Distributing Your Application\n\nYou finished your Kotter application. Congratulations!! 🎉\n\nNow what? How do you get your amazing program to your users?\n\nLet's explore a few options.\n\n#### Package your JVM application as a zip (or tar) file\n\n**Pros:**\n* Trivial to do.\n* Easy to share.\n* Access to the whole JVM ecosystem.\n\n**Cons:**\n* User must extract files on their machine, a step they aren't used to worrying about.\n* User must have Java installed.\n\nIf your application targets the JVM, you can easily build zip and tar files of your project using the `assembleDist`\ntask:\n\n```bash\n$ cd yourkotterapp\n$ ./assembleDist\n$ cd build/distributions\n$ ls\nyourkotterapp-version.tar  yourkotterapp-version.zip\n```\n\nYou can ask your user to download either file, extract it, and then run the program under the bin folder:\n\n```bash\n$ unzip yourkotterapp-version.zip\n$ ./yourkotterapp-version/bin/yourkotterapp\n```\n\n---\n\n#### Publish your JVM application to a package manager\n\n**Pros:**\n* Trivial for the user to install (although this may install Java as a side effect).\n* Access to the whole JVM ecosystem.\n\n**Cons:**\n* Everyone has their favorite package manager, and you won't be able to satisfy everyone.\n* Some package managers take a lot of effort to set up.\n* Some users won't want to use a package manager, so you'll need to include other options anyway.\n\nFor a concrete example, let's consider Homebrew, a very popular package manager. They're one of the easier ones to\nsupport -- you can create a custom repo that declares a manifest ([example here](https://github.com/varabyte/homebrew-tap/blob/main/Formula/kobweb.rb)).\nOnce set up, a user can install / update your software simply by running: `brew install yourkotterapp`\n\nNotice how that manifest declares JDK11 as a dependency. You'll need to figure out how to do that with each package\nmanager you decide to support.\n\n\u003e [!NOTE]\n\u003e I have a project that uses [JReleaser](https://jreleaser.org/guide/latest/reference/packagers/index.html)\n\u003e to publish my program to several package managers with a single Gradle publish task. If you decide to try JReleaser in\n\u003e your own project, you can review\n\u003e [my jreleaser block in this build.gradle.kts](https://github.com/varabyte/kobweb/blob/a8910bf5168a3e27be88ab49fce8b0a86322caac/cli/kobweb/build.gradle.kts#L49).\n\n---\n\n#### Build a Kotlin/Native target\n\n**Pros:**\n* No Java required on the user's machine.\n* Potentially small final binary size.\n* Relatively easy to set up.\n\n**Cons:**\n* Will require multiple host machines if you want to build binaries for all platform targets.\n* No access to the broader JVM ecosystem.\n* Native targets don't provide a virtual terminal.\n\nConfiguring a Kotter/Native application is relatively easy but outside the scope of this document. Start with\n[the official docs](https://kotlinlang.org/docs/native-overview.html) and review the\n[native example](https://github.com/varabyte/kotter/tree/main/examples/native) for guidance.\n\nOnce your Kotlin/Native project is set up, you can build it using `./gradlew linkDebugExecutable[Host]` (or\n`./gradlew linkReleaseExecutable[Host]`) which puts a binary under `./build/bin/[host]/debugExecutable/native.exe`\n(or `native.kexe` on Linux).\n\nUnless you already have Mac, Windows, and Linux machines at home, you may want to use a CI to build binaries for you.\nHow to do this is also outside the scope of this document, but I personally use GitHub Actions to handle this, creating\na workflow that runs on several machines, publishing artifacts based on which runner is active. You can read more about\n[GitHub CI strategies here](https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs).\n\nFor example, you could set up a workflow to link various binaries into executables whenever a new commit is pushed to\nthe main branch. You could then use the [upload artifact](https://github.com/actions/upload-artifact) action to push\nbinaries to [a location you can download from](https://github.com/actions/upload-artifact#where-does-the-upload-go)\nlater.\n\n\u003e [!NOTE]\n\u003e For reference, you may wish to refer to [Kotter's publishing workflow script.](https://github.com/varabyte/kotter/blob/main/.github/workflows/publish.yml)\n\u003e It doesn't use the upload action, but you can see how we run multiple target hosts in order to build all the different\n\u003e flavors of artifacts.\n\nOnce built, you can share your binaries with your users, either from cloud storage somewhere or by publishing it via a\npackage manager (as [discussed above ▲](#publish-your-jvm-application-to-a-package-manager)).\n\n---\n\n#### Use `jlink / jpackage` to convert your JVM project into a binary\n\n**Pros:**\n* No Java required on the user's machine.\n* Access to the whole JVM ecosystem.\n\n**Cons:**\n* Requires JDK14 or newer.\n* Will require multiple host machines if you want to build binaries for all platform targets.\n* May be complicated to set up.\n* May require your CI have access to two different JDKs, one for compiling your code and one for running the jpackage\n  step, in case you are intentionally compiling your code with an older JDK version.\n\n\u003e [!WARNING]\n\u003e This section is incomplete as I have not found time to try out these tools yet. However, any readers familiar with\n\u003e them are welcome to [contact me](mailto:bitspittle@gmail.com) with information so that I can update this section.\n\n`jlink`, introduced in Java 9, allows you to assemble modules into a custom runtime image, which can significantly\nreduce the final size of a runtime you'd want to ship (as you'd be excluding a bunch of standard library code you don't\nneed). [Official docs here](https://docs.oracle.com/javase/9/tools/jlink.htm).\n\n`jpackage`, introduced in Java 14, allows you to bundle a JVM program plus a runtime (e.g. produced by `jlink`) into\na final binary + installer, one per platform. [Official docs here](https://docs.oracle.com/en/java/javase/14/jpackage/index.html).\n\nJReleaser, discussed in a previous section, exposes support for [jlink](https://jreleaser.org/guide/early-access/reference/assemble/jlink.html)\nand [jpackage](https://jreleaser.org/guide/early-access/reference/assemble/jpackage.html) configurations.\n\nIf you don't need access to the JVM library ecosystem, using [Kotlin/Native ▲](#build-a-kotlinnative-target) is probably\neasier.\n\n---\n\n#### Build your JVM application with GraalVM Native Image\n\n**Pros:**\n* No Java required on the user's machine.\n* Great flexibility. User can use the entire JVM library ecosystem while still producing a binary that can run anywhere.\n* Potentially small final binary size (after using UPX).\n\n**Cons:**\n* Will require multiple host machines if you want to build binaries for all platform targets.\n* GraalVM can be annoying to install.\n* GraalVM can be very fussy.\n* Can be frustrating to chase down runtime exceptions caused by a misconfigured compile.\n\n\u003e [!WARNING]\n\u003e This section is incomplete as my own experimentation fell a bit short with it. However, GraalVM is a very promising\n\u003e technology, so if anyone reading this knows how to get this solution to work, please [contact me](mailto:bitspittle@gmail.com)\n\u003e and I can update this section.\n\n[GraalVM](https://www.graalvm.org/) is a high-performance JDK distribution, while\n[GraalVM Native Image](https://www.graalvm.org/latest/reference-manual/native-image/) is an ahead-of-time compiler for\nJava programs. What this means to you is that you can build a Java application jar and then target it with native image\nto convert it into a standalone binary.\n\nYou may be able to further minify your GraalVM output with [UPX](https://upx.github.io/), which may be able to shrink\nyour final binary size dramatically.\n\n\u003e [!IMPORTANT]\n\u003e If you decide to try using GraalVM on your project, you should strongly consider excluding the virtual terminal by\n\u003e overriding the default `terminal` parameter when creating a session:\n\u003e\n\u003e ```kotlin\n\u003e session(terminal = SystemTerminal.create()) { /* ... */ }\n\u003e ```\n\u003e\n\u003e This allows GraalVM to strip out all Swing code, which is otherwise very tricky to configure.\n\n---\n\n### 🤷 Why Not Compose / Mosaic?\n\nKotter's API is inspired by Compose, which astute readers may have already noticed -- it has a core block which gets\nrerun for you automatically as necessary without you having to worry about it, and special state variables which, when\nmodified, automatically \"recompose\" the current console block. Why not just use Compose directly?\n\nIn fact, this is exactly what [Jake Wharton's Mosaic](https://github.com/JakeWharton/mosaic) is doing. Actually, I tried\nusing it first but ultimately decided against it before deciding to write Kotter, for the following reasons:\n\n* Compose is tightly tied to the current Kotlin compiler version, which means if you are targeting a particular\nversion of the Kotlin language, you can easily see the dreaded error message: `This version (x.y.z) of the Compose\nCompiler requires Kotlin version a.b.c but you appear to be using Kotlin version d.e.f which is not known to be\ncompatible.`\n  * Using Kotlin v1.3 or older for some reason? You're out of luck.\n  * Want to upgrade Kotlin without updating Kotter? You're out of luck.\n  * Want to update Kotter without also upgrading Kotlin? You might be out of luck.\n\n* Compose is great for rendering a whole, interactive UI, but console printing is often two parts: the active part that\nthe user is interacting with, and the history, which is static. To support this with Compose, you'd need to manage the\nhistory list yourself and keep appending to it, which would be a waste of render cycles and memory when you could just\nlean on the console to do it. It was while thinking about an API that addressed this limitation that I envisioned\nKotter.\n  * For a concrete example, see the [compiler demo](examples/compiler). A compiler might generate hundreds (or\n    thousands!) of history lines. We definitely don't want to rerender all of those every frame.\n\n* Compose encourages using a set of powerful layout primitives, namely `Box`, `Column`, and `Row`, with margins and\n  shapes and layers. Command line apps don't really need this level of power, however.\n\n* Compose has a lot of strengths built around, well, composing methods! But for a simple CLI library, being able to\n  focus on simple render blocks that don't nest at all allowed a more pared down API to emerge.\n\n* Compose does a lot of nice tricks due to the fact it is ultimately a compiler plugin, but it is interesting to see\n  what the API could look like with no magic at all (although, admittedly, with some features sacrificed).\n\n* Mosaic doesn't support input well yet (at the time of writing this README, but maybe this has changed in the future).\n  For example, compare [Mosaic](https://github.com/JakeWharton/mosaic/blob/fd213711ce2b828a6436a61d6d345692222bdb95/samples/robot/src/main/kotlin/example/robot.kt#L45)\n  to [Kotter](https://github.com/varabyte/kotter/blob/main/examples/mosaic/robot/src/main/kotlin/main.kt#L27).\n\n#### Mosaic comparison\n\nMosaic and Kotter programs look very similar, but they are organized slightly differently. Instead of Compose, where you\nhave a single code block where values, layout, and logic are combined (with judicious use of `remember` and\n`LaunchedEffect`), in Kotter you tend to have three separate areas for these concepts: before a section, a section\nblock, and a run block.\n\n```kotlin\n// Mosaic\nrunMosaic {\n  var count by remember { mutableStateOf(0) }\n  Text(\"The count is: $count\")\n\n  LaunchedEffect(null) {\n    for (i in 1..20) {\n      delay(250)\n      count++\n    }\n  }\n}\n\n// Kotter\nsession {\n  var count by liveVarOf(0)\n  section {\n    textLine(\"The count is: $count\")\n  }.run {\n    for (i in 1..20) {\n      delay(250)\n      count++\n    }\n  }\n}\n```\n\nComparisons with Mosaic are included in the [examples/mosaic](examples/mosaic) folder.\n\n### 🧶 How About Mordant?\n\n[Mordant](https://github.com/ajalt/mordant) is another Kotlin console API. And honestly, it's awesome. It's worth a\nlook!\n\nMordant provides an API where you instantiate a `Terminal` object and issue commands to it directly. It's very clean!\nThe library also provides markdown support and builders for complex tables, which are really nice features that don't\ncurrently exist in Kotter. It has a few other opinionated components, such as an animated progress bar and an input\nprompter that requires the answer be one of a few choices.\n\nIf you are mostly rendering output text, Mordant may honestly result in more streamlined code. It also handles falling\nback better if you run it inside a terminal that does not support interactive mode. (Kotter will open a virtual terminal\nif it can, or crash if it can't).\n\nYou may still prefer using Kotter for cases where you plan to have a lot of interactive elements, such as several\nanimations running side by side in parallel, or if you want keypress handling, or if you want to want the ability to\nmanage timers, or if you want to show interactive prompts with rich auto-completion behavior.\n\nAdditionally, Kotter's split between the `section` block and `run` blocks benefit more and more in increasingly complex\nscenarios. It can be nice to have a clear separation of the rendering logic from background computation logic.\n\nStill, for concreteness, let's take a few examples from Mordant's README and show them side-by-side with equivalent\nKotter implementations:\n\n**Declaring basic styles**\n```kotlin\n// Mordant\nval t = Terminal()\nt.println(\"${red(\"red\")} ${white(\"white\")} and ${blue(\"blue\")}\")\n\n// Kotter\nsection {\n    red { text(\"red\") }; text(' ')\n    white { text(\"white\")}; text(\" and \")\n    blue { textLine(\"blue\") }\n}.run()\n```\n\n**Nesting styles**\n```kotlin\n// Mordant\nt.println(white(\"You ${(blue on yellow)(\"can ${(black + strikethrough)(\"nest\")} styles\")} arbitrarily\"))\n\n// Kotter\nsection {\n    white {\n        text(\"You \")\n        blue { yellow(BG) {\n            text(\"can \")\n            black { strikethrough {\n                text(\"nest\")\n            }}\n            text(\" styles\")\n        }}\n        textLine(\" arbitrarily\")\n    }\n}.run()\n```\n\n**Reusing styles**\n```kotlin\n// Mordant\nval style = (bold + white + underline)\nt.println(style(\"You can save styles\"))\nt.println(style(\"to reuse\"))\n\n// Kotter\nfun RenderScope.withStyle(block: () -\u003e Unit) {\n  bold { white { underline { block() }}}\n}\nsection {\n  withStyle { textLine(\"You can refactor styles\") }\n  withStyle { textLine(\"to reuse\") }\n}.run()\n```\n\n**Animating a horizontal bar**\n```kotlin\n// Mordant\nval t = Terminal()\nval a = t.textAnimation\u003cInt\u003e { frame -\u003e\n  (1..50).joinToString(\"\") {\n    val hue = (frame + it) * 3 % 360\n    t.colors.hsv(hue, 1, 1)(\"━\")\n  }\n}\n\nt.cursor.hide(showOnExit = true)\nrepeat(120) {\n  a.update(it)\n  Thread.sleep(25)\n}\n\n// Kotter\nval barAnim = renderAnimOf(numFrames = 120, 25.milliseconds) { frame -\u003e\n    for (i in 1..50) {\n        val hue = ((frame + i) * 3) % 360\n        hsv(hue, 1f, 1f) { text(\"━\") }\n    }\n}\nsection {\n    barAnim(this)\n}.runFor(3.seconds)\n```\n\n**Prompting for input**\n```kotlin\n// Mordant\nval t = Terminal()\nval response = t.prompt(\"Choose a size\", choices=listOf(\"small\", \"large\"))\nt.println(\"You chose: $response\")\n\n// Kotter\nval choices = listOf(\"small\", \"large\")\nvar choice: String = \"\"\nsection {\n    text(\"Choose a size [${choices.joinToString()}]: \")\n    input(Completions(*choices))\n}.runUntilInputEntered {\n    onInputEntered {\n        if (input !in choices) rejectInput() else choice = input\n    }\n}\nsection {\n    text(\"You chose: $choice\")\n}.run()\n```\n\nThe above samples definitely look really nice in Mordant, and if those cases capture the main sort of functionality you\nwere planning to use in your own app, Mordant may be the better API for your project.\n\nMeanwhile, for examples that respond to user input like [snake](examples/snake), or which do a lot of clearing /\nrepainting like [doomfire](examples/doomfire), or which query for input in the middle of a bunch of other text like\n[wordle](examples/wordle), Kotter may be the better choice in those cases.\n\nAnd finally, it's possible to use Kotter and Mordant together. For example, referring back to the\n[fallback section above ▲](#-fallback-in-non-interactive-terminals), you can use Mordant in the fallback block, since\nit provides a friendlier API than raw `println`/`readLine` calls.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvarabyte%2Fkotter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvarabyte%2Fkotter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvarabyte%2Fkotter/lists"}