Repository: ajalt/mordant
Pull request #215 (2024-08-24 17:56:09)

Move markdown to a separate module


Patch:
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index d57a21f2..199c920f 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -40,7 +40,8 @@ jobs:
         run: >-
             ./gradlew
             :mordant:check
-            :extensions:mordant-coroutines:check
+            :mordant-coroutines:check
+            :mordant-markdown:check
             :test:graalvm:nativeTest
             ${{matrix.EXTRA_GRADLE_ARGS}}
             --stacktrace
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e6c7dea1..f7014593 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,10 +7,12 @@
 - Added new terminal implementation that uses the [Foreign Function and Memory (FFM) API](https://openjdk.java.net/jeps/419) added in JDK 22.
 - Split the library up into modules, so you can produce smaller JVM artifacts by only using the parts you need.
 - Added support for raw mode and input events to JS and wasmJS targets when running on node.js.
+- Added tvOS and watchOS native targets to all modules except the new `mordant-markdown` module.
 
 ### Changed
 - **Breaking Change** Moved `Terminal.info.width` and `height` to `Terminal.size.width` and `height`.
 - **Breaking Change** `TerminalInterface.info` is now a method with parameters instead of a property.
+- **Breaking Change** Moved `Markdown` widget to separate `mordant-markdown` module, which is not included by default. If you use markdown rendering, you need to add that module to you dependencies.
 
 ### Removed
 - Removed constructor overloads for `Terminal`. There is now one constructor with all default parameters. 
diff --git a/buildSrc/src/main/kotlin/mordant-native-conventions.gradle.kts b/buildSrc/src/main/kotlin/mordant-native-conventions.gradle.kts
index 9e1e99ca..44bf8aee 100644
--- a/buildSrc/src/main/kotlin/mordant-native-conventions.gradle.kts
+++ b/buildSrc/src/main/kotlin/mordant-native-conventions.gradle.kts
@@ -1,37 +1,14 @@
 plugins {
     kotlin("multiplatform")
+    id("mordant-native-core-conventions")
 }
 
 kotlin {
-    linuxX64()
-    linuxArm64()
-    macosX64()
-    macosArm64()
-    mingwX64()
-
-    iosX64()
-    iosArm64()
-    iosSimulatorArm64()
-// Not all targets are supported by the markdown library
-//    tvosX64()
-//    tvosArm64()
+    // Add targets not supported by the markdown library
+    tvosX64()
+    tvosArm64()
     tvosSimulatorArm64()
-//    watchosArm32()
-//    watchosArm64()
-//    watchosDeviceArm64()
-//    watchosX64()
-    watchosSimulatorArm64()
-
-    applyDefaultHierarchyTemplate()
-
-    // https://kotlinlang.org/docs/multiplatform-hierarchy.html#see-the-full-hierarchy-template
-    sourceSets {
-        val posixMain by creating { dependsOn(nativeMain.get()) }
-        linuxMain.get().dependsOn(posixMain)
-        appleMain.get().dependsOn(posixMain)
-        val appleNonDesktopMain by creating { dependsOn(appleMain.get()) }
-        for (target in listOf(iosMain, tvosMain, watchosMain)) {
-            target.get().dependsOn(appleNonDesktopMain)
-        }
-    }
+    watchosArm32()
+    watchosArm64()
+    watchosX64()
 }
diff --git a/buildSrc/src/main/kotlin/mordant-native-core-conventions.gradle.kts b/buildSrc/src/main/kotlin/mordant-native-core-conventions.gradle.kts
new file mode 100644
index 00000000..9e1e99ca
--- /dev/null
+++ b/buildSrc/src/main/kotlin/mordant-native-core-conventions.gradle.kts
@@ -0,0 +1,37 @@
+plugins {
+    kotlin("multiplatform")
+}
+
+kotlin {
+    linuxX64()
+    linuxArm64()
+    macosX64()
+    macosArm64()
+    mingwX64()
+
+    iosX64()
+    iosArm64()
+    iosSimulatorArm64()
+// Not all targets are supported by the markdown library
+//    tvosX64()
+//    tvosArm64()
+    tvosSimulatorArm64()
+//    watchosArm32()
+//    watchosArm64()
+//    watchosDeviceArm64()
+//    watchosX64()
+    watchosSimulatorArm64()
+
+    applyDefaultHierarchyTemplate()
+
+    // https://kotlinlang.org/docs/multiplatform-hierarchy.html#see-the-full-hierarchy-template
+    sourceSets {
+        val posixMain by creating { dependsOn(nativeMain.get()) }
+        linuxMain.get().dependsOn(posixMain)
+        appleMain.get().dependsOn(posixMain)
+        val appleNonDesktopMain by creating { dependsOn(appleMain.get()) }
+        for (target in listOf(iosMain, tvosMain, watchosMain)) {
+            target.get().dependsOn(appleNonDesktopMain)
+        }
+    }
+}
diff --git a/docs/guide.md b/docs/guide.md
index 53bf4bf0..3e4a9d4d 100644
--- a/docs/guide.md
+++ b/docs/guide.md
@@ -1,7 +1,44 @@
 # Getting Started
 
-You can get the library using any maven-compatible build system.
-Installation instructions can be found in the [README][README].
+Mordant is split into a number of modules to allow you to pick the functionality you need. On JVM,
+there are several modules that implement `TerminalInterface` using different dependencies.
+
+=== "All-in-one gradle dependencies"
+    ```kotlin
+    // Adds all JVM interface modules
+    implementation("com.github.ajalt.mordant:mordant:$mordantVersion")
+    
+    // optional extensions for running animations with coroutines
+    implementation("com.github.ajalt.mordant:mordant-coroutines:$mordantVersion")
+    
+    // optional widget for rendering Markdown
+    implementation("com.github.ajalt.mordant:mordant-markdown:$mordantVersion")
+    ```
+
+=== "Individual JVM gradle dependencies"
+    ```kotlin
+    // This modules doesn't include any JVM interface modules, so you'll need one or more if you're 
+    // targeting JVM
+    implementation("com.github.ajalt.mordant:mordant-core:$mordantVersion")
+    
+    // This module uses the Java Foreign Function and Memory API. It requires JDK 22+, and you must 
+    // add `--enable-native-access=ALL-UNNAMED` to your `java` command line arguments.
+    implementation("com.github.ajalt.mordant:mordant-jvm-ffm:$mordantVersion")
+    
+    // This module uses the Java Native Access library. It supports all versions of Java, but 
+    // requires linking to a bundled native library, so it increases your JAR size.
+    implementation("com.github.ajalt.mordant:mordant-jvm-jna:$mordantVersion")
+    
+    // This module uses the GraalVM Native Image FFI interface. This only supports Graal Native Image.
+    implementation("com.github.ajalt.mordant:mordant-jvm-graal-ffi:$mordantVersion")
+    
+    // optional extensions for running animations with coroutines
+    implementation("com.github.ajalt.mordant:mordant-coroutines:$mordantVersion")
+    
+    // optional widget for rendering Markdown
+    implementation("com.github.ajalt.mordant:mordant-markdown:$mordantVersion")
+    ```
+
 
 ## Text Colors and Styles
 
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 76b8ca6f..9151f546 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -3,7 +3,7 @@ kotlin = "2.0.10"
 coroutines = "1.8.1"
 
 [libraries]
-colormath = "com.github.ajalt.colormath:colormath:3.5.0"
+colormath = "com.github.ajalt.colormath:colormath:3.6.0"
 markdown = "org.jetbrains:markdown:0.7.3"
 jna-core = "net.java.dev.jna:jna:5.14.0"
 
diff --git a/mordant-coroutines/README.md b/mordant-coroutines/README.md
new file mode 100644
index 00000000..187fad12
--- /dev/null
+++ b/mordant-coroutines/README.md
@@ -0,0 +1,7 @@
+# Mordant Coroutines
+
+This module adds extensions to running animations with coroutines.
+
+```kotlin
+implementation("com.github.ajalt.mordant:mordant-coroutines:$mordantVersion")
+```
diff --git a/extensions/mordant-coroutines/api/mordant-coroutines.api b/mordant-coroutines/api/mordant-coroutines.api
similarity index 100%
rename from extensions/mordant-coroutines/api/mordant-coroutines.api
rename to mordant-coroutines/api/mordant-coroutines.api
diff --git a/extensions/mordant-coroutines/build.gradle.kts b/mordant-coroutines/build.gradle.kts
similarity index 100%
rename from extensions/mordant-coroutines/build.gradle.kts
rename to mordant-coroutines/build.gradle.kts
diff --git a/extensions/mordant-coroutines/gradle.properties b/mordant-coroutines/gradle.properties
similarity index 100%
rename from extensions/mordant-coroutines/gradle.properties
rename to mordant-coroutines/gradle.properties
diff --git a/extensions/mordant-coroutines/src/commonMain/kotlin/com/github/ajalt/mordant/animation/coroutines/CoroutineAnimator.kt b/mordant-coroutines/src/commonMain/kotlin/com/github/ajalt/mordant/animation/coroutines/CoroutineAnimator.kt
similarity index 100%
rename from extensions/mordant-coroutines/src/commonMain/kotlin/com/github/ajalt/mordant/animation/coroutines/CoroutineAnimator.kt
rename to mordant-coroutines/src/commonMain/kotlin/com/github/ajalt/mordant/animation/coroutines/CoroutineAnimator.kt
diff --git a/extensions/mordant-coroutines/src/commonMain/kotlin/com/github/ajalt/mordant/input/coroutines/ReceiveEventsFlow.kt b/mordant-coroutines/src/commonMain/kotlin/com/github/ajalt/mordant/input/coroutines/ReceiveEventsFlow.kt
similarity index 100%
rename from extensions/mordant-coroutines/src/commonMain/kotlin/com/github/ajalt/mordant/input/coroutines/ReceiveEventsFlow.kt
rename to mordant-coroutines/src/commonMain/kotlin/com/github/ajalt/mordant/input/coroutines/ReceiveEventsFlow.kt
diff --git a/extensions/mordant-coroutines/src/commonTest/kotlin/com/github/ajalt/mordant/animation/coroutines/CoroutinesAnimatorTest.kt b/mordant-coroutines/src/commonTest/kotlin/com/github/ajalt/mordant/animation/coroutines/CoroutinesAnimatorTest.kt
similarity index 100%
rename from extensions/mordant-coroutines/src/commonTest/kotlin/com/github/ajalt/mordant/animation/coroutines/CoroutinesAnimatorTest.kt
rename to mordant-coroutines/src/commonTest/kotlin/com/github/ajalt/mordant/animation/coroutines/CoroutinesAnimatorTest.kt
diff --git a/mordant-jvm-ffm/README.md b/mordant-jvm-ffm/README.md
new file mode 100644
index 00000000..da796e03
--- /dev/null
+++ b/mordant-jvm-ffm/README.md
@@ -0,0 +1,10 @@
+# Mordant JVM FFM
+
+This is a JVM-only module that adds a `TerminalInterface` implementation that uses the Java Foreign
+Functions and Memory APIs. It requires JDK 22 or newer, and, like all usages of FFM, requires that
+you add `--enable-native-access=ALL-UNNAMED` to your `java` command line arguments or
+`Enable-Native-Access: ALL-UNNAMED` to the manifest of your executable JAR.
+
+```kotlin
+implementation("com.github.ajalt.mordant:mordant-jvm-ffm:$mordantVersion")
+```
diff --git a/mordant-jvm-graal-ffi/README.md b/mordant-jvm-graal-ffi/README.md
new file mode 100644
index 00000000..ee55dec7
--- /dev/null
+++ b/mordant-jvm-graal-ffi/README.md
@@ -0,0 +1,8 @@
+# Mordant JVM GraalVM FFI
+
+This is a JVM-only module that adds a `TerminalInterface` implementation for use with GraalVM Native
+Image. It doesn't support regular JRE runtimes.
+
+```kotlin
+implementation("com.github.ajalt.mordant:mordant-jvm-graal-ffi:$mordantVersion")
+```
diff --git a/mordant-jvm-jna/README.md b/mordant-jvm-jna/README.md
new file mode 100644
index 00000000..85a80ca8
--- /dev/null
+++ b/mordant-jvm-jna/README.md
@@ -0,0 +1,11 @@
+# Mordant JVM JNA
+
+This is a JVM-only module that adds a `TerminalInterface` implementation that uses [JNA]. This
+module supports all JDK versions, but links to a bundled native library, so it increases your JAR
+size.
+
+```kotlin
+implementation("com.github.ajalt.mordant:mordant-jvm-jna:$mordantVersion")
+```
+
+[JNA]: https://github.com/java-native-access/jna
diff --git a/mordant-markdown/README.md b/mordant-markdown/README.md
new file mode 100644
index 00000000..4544b97a
--- /dev/null
+++ b/mordant-markdown/README.md
@@ -0,0 +1,3 @@
+# Mordant Markdown
+
+This module adds a `Markdown` widget that renders markdown.
diff --git a/mordant-markdown/api/mordant-markdown.api b/mordant-markdown/api/mordant-markdown.api
new file mode 100644
index 00000000..06b44cfb
--- /dev/null
+++ b/mordant-markdown/api/mordant-markdown.api
@@ -0,0 +1,7 @@
+public final class com/github/ajalt/mordant/markdown/Markdown : com/github/ajalt/mordant/rendering/Widget {
+	public fun <init> (Ljava/lang/String;ZLjava/lang/Boolean;)V
+	public synthetic fun <init> (Ljava/lang/String;ZLjava/lang/Boolean;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+	public fun measure (Lcom/github/ajalt/mordant/terminal/Terminal;I)Lcom/github/ajalt/mordant/rendering/WidthRange;
+	public fun render (Lcom/github/ajalt/mordant/terminal/Terminal;I)Lcom/github/ajalt/mordant/rendering/Lines;
+}
+
diff --git a/mordant-markdown/build.gradle.kts b/mordant-markdown/build.gradle.kts
new file mode 100644
index 00000000..b696bc02
--- /dev/null
+++ b/mordant-markdown/build.gradle.kts
@@ -0,0 +1,21 @@
+plugins {
+    id("mordant-kotlin-conventions")
+    id("mordant-js-conventions")
+    // Need core here pending https://github.com/JetBrains/markdown/pull/159
+    id("mordant-native-core-conventions")
+    id("mordant-publishing-conventions")
+}
+
+kotlin {
+    jvm() // need to list explicitly since we aren't using mpp-conventions for now
+    sourceSets {
+        commonMain.dependencies {
+            api(project(":mordant"))
+            implementation(libs.markdown)
+        }
+        commonTest.dependencies {
+            implementation(kotlin("test"))
+            implementation(libs.kotest)
+        }
+    }
+}
diff --git a/mordant-markdown/gradle.properties b/mordant-markdown/gradle.properties
new file mode 100644
index 00000000..059375b7
--- /dev/null
+++ b/mordant-markdown/gradle.properties
@@ -0,0 +1,2 @@
+POM_ARTIFACT_ID=mordant-markdown
+POM_NAME=Mordant Markdown Rendering
diff --git a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/markdown/BlockQuote.kt b/mordant-markdown/src/commonMain/kotlin/com/github/ajalt/mordant/markdown/BlockQuote.kt
similarity index 100%
rename from mordant/src/commonMain/kotlin/com/github/ajalt/mordant/markdown/BlockQuote.kt
rename to mordant-markdown/src/commonMain/kotlin/com/github/ajalt/mordant/markdown/BlockQuote.kt
diff --git a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/markdown/Markdown.kt b/mordant-markdown/src/commonMain/kotlin/com/github/ajalt/mordant/markdown/Markdown.kt
similarity index 100%
rename from mordant/src/commonMain/kotlin/com/github/ajalt/mordant/markdown/Markdown.kt
rename to mordant-markdown/src/commonMain/kotlin/com/github/ajalt/mordant/markdown/Markdown.kt
diff --git a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/markdown/MarkdownRenderer.kt b/mordant-markdown/src/commonMain/kotlin/com/github/ajalt/mordant/markdown/MarkdownRenderer.kt
similarity index 100%
rename from mordant/src/commonMain/kotlin/com/github/ajalt/mordant/markdown/MarkdownRenderer.kt
rename to mordant-markdown/src/commonMain/kotlin/com/github/ajalt/mordant/markdown/MarkdownRenderer.kt
diff --git a/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/markdown/MarkdownTest.kt b/mordant-markdown/src/commonTest/kotlin/com/github/ajalt/mordant/markdown/MarkdownTest.kt
similarity index 100%
rename from mordant/src/commonTest/kotlin/com/github/ajalt/mordant/markdown/MarkdownTest.kt
rename to mordant-markdown/src/commonTest/kotlin/com/github/ajalt/mordant/markdown/MarkdownTest.kt
diff --git a/mordant-omnibus/README.md b/mordant-omnibus/README.md
new file mode 100644
index 00000000..e15688f8
--- /dev/null
+++ b/mordant-omnibus/README.md
@@ -0,0 +1,7 @@
+# Mordant Omnibus
+
+This module is a convenience for depending on `mordant-core` and all of the `mordant-jvm` modules.
+
+```kotlin
+implementation("com.github.ajalt.mordant:mordant:$mordantVersion")
+```
diff --git a/mordant/README.md b/mordant/README.md
new file mode 100644
index 00000000..32de5023
--- /dev/null
+++ b/mordant/README.md
@@ -0,0 +1,7 @@
+# Mordant Core
+
+This is the core mordant module that implements most Mordant functionality. All other modules depend on this.
+
+```kotlin
+implementation("com.github.ajalt.mordant:mordant-core:$mordantVersion")
+```
diff --git a/mordant/api/mordant.api b/mordant/api/mordant.api
index 68aecd55..ab08c3c4 100644
--- a/mordant/api/mordant.api
+++ b/mordant/api/mordant.api
@@ -393,13 +393,6 @@ public final class com/github/ajalt/mordant/input/ReceiveEventsKt {
 	public static synthetic fun receiveMouseEvents$default (Lcom/github/ajalt/mordant/terminal/Terminal;Lcom/github/ajalt/mordant/input/MouseTracking;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ljava/lang/Object;
 }
 
-public final class com/github/ajalt/mordant/markdown/Markdown : com/github/ajalt/mordant/rendering/Widget {
-	public fun <init> (Ljava/lang/String;ZLjava/lang/Boolean;)V
-	public synthetic fun <init> (Ljava/lang/String;ZLjava/lang/Boolean;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
-	public fun measure (Lcom/github/ajalt/mordant/terminal/Terminal;I)Lcom/github/ajalt/mordant/rendering/WidthRange;
-	public fun render (Lcom/github/ajalt/mordant/terminal/Terminal;I)Lcom/github/ajalt/mordant/rendering/Lines;
-}
-
 public final class com/github/ajalt/mordant/platform/MultiplatformSystem {
 	public static final field INSTANCE Lcom/github/ajalt/mordant/platform/MultiplatformSystem;
 	public final fun exitProcess (I)V
diff --git a/mordant/build.gradle.kts b/mordant/build.gradle.kts
index 0ff74dcc..5153aa2c 100644
--- a/mordant/build.gradle.kts
+++ b/mordant/build.gradle.kts
@@ -11,7 +11,6 @@ kotlin {
         }
         commonMain.dependencies {
             api(libs.colormath)
-            implementation(libs.markdown)
         }
         commonTest.dependencies {
             implementation(kotlin("test"))
diff --git a/mordant/src/appleMain/kotlin/com/github/ajalt/mordant/terminal/terminalinterface/TerminalInterface.native.apple.kt b/mordant/src/appleMain/kotlin/com/github/ajalt/mordant/terminal/terminalinterface/TerminalInterface.native.apple.kt
index 7015e59d..eb2fb36a 100644
--- a/mordant/src/appleMain/kotlin/com/github/ajalt/mordant/terminal/terminalinterface/TerminalInterface.native.apple.kt
+++ b/mordant/src/appleMain/kotlin/com/github/ajalt/mordant/terminal/terminalinterface/TerminalInterface.native.apple.kt
@@ -26,6 +26,10 @@ internal class TerminalInterfaceNativeApple : TerminalInterfaceNativePosix() {
         IEXTEN = IEXTEN.convert(),
     )
 
+    override fun readIntoBuffer(c: ByteVar): Long {
+        return read(platform.posix.STDIN_FILENO, c.ptr, 1u).convert()
+    }
+
     override fun getTerminalSize(): Size? = memScoped {
         val size = alloc<winsize>()
         if (ioctl(STDIN_FILENO, TIOCGWINSZ.toULong(), size) < 0) {
diff --git a/mordant/src/linuxMain/kotlin/com/github/ajalt/mordant/terminal/terminalinterface/TerminalInterface.native.linux.kt b/mordant/src/linuxMain/kotlin/com/github/ajalt/mordant/terminal/terminalinterface/TerminalInterface.native.linux.kt
index 0232d8ba..3a39cdcb 100644
--- a/mordant/src/linuxMain/kotlin/com/github/ajalt/mordant/terminal/terminalinterface/TerminalInterface.native.linux.kt
+++ b/mordant/src/linuxMain/kotlin/com/github/ajalt/mordant/terminal/terminalinterface/TerminalInterface.native.linux.kt
@@ -26,6 +26,10 @@ internal object TerminalInterfaceNativeLinux : TerminalInterfaceNativePosix() {
         IEXTEN = IEXTEN.convert(),
     )
 
+    override fun readIntoBuffer(c: ByteVar): Long {
+        return read(platform.posix.STDIN_FILENO, c.ptr, 1u).convert()
+    }
+
     override fun getTerminalSize(): Size? = memScoped {
         val size = alloc<winsize>()
         if (ioctl(STDIN_FILENO, TIOCGWINSZ.toULong(), size) < 0) {
diff --git a/mordant/src/posixMain/kotlin/com/github/ajalt/mordant/terminal/terminalinterface/TerminalInterface.native.posix.kt b/mordant/src/posixMain/kotlin/com/github/ajalt/mordant/terminal/terminalinterface/TerminalInterface.native.posix.kt
index 8a897e90..7ef03f59 100644
--- a/mordant/src/posixMain/kotlin/com/github/ajalt/mordant/terminal/terminalinterface/TerminalInterface.native.posix.kt
+++ b/mordant/src/posixMain/kotlin/com/github/ajalt/mordant/terminal/terminalinterface/TerminalInterface.native.posix.kt
@@ -1,7 +1,9 @@
 package com.github.ajalt.mordant.terminal.terminalinterface
 
-import kotlinx.cinterop.*
-import platform.posix.read
+import kotlinx.cinterop.ByteVar
+import kotlinx.cinterop.alloc
+import kotlinx.cinterop.memScoped
+import kotlinx.cinterop.value
 import kotlin.time.ComparableTimeMark
 import kotlin.time.Duration
 
@@ -13,10 +15,13 @@ internal abstract class TerminalInterfaceNativePosix : TerminalInterfacePosix()
     override fun readRawByte(t0: ComparableTimeMark, timeout: Duration): Char = memScoped {
         do {
             val c = alloc<ByteVar>()
-            val read = read(platform.posix.STDIN_FILENO, c.ptr, 1u)
+            val read = readIntoBuffer(c)
             if (read < 0) throw RuntimeException("Error reading from stdin")
             if (read > 0) return c.value.toInt().toChar()
         } while (t0.elapsedNow() < timeout)
         throw RuntimeException("Timeout reading from stdin (timeout=$timeout)")
     }
+
+    // `read` has different byte widths on linux and apple
+    protected abstract fun readIntoBuffer(c: ByteVar): Long
 }
diff --git a/samples/drawing/build.gradle.kts b/samples/drawing/build.gradle.kts
index 88a0734b..9bac1621 100644
--- a/samples/drawing/build.gradle.kts
+++ b/samples/drawing/build.gradle.kts
@@ -5,7 +5,7 @@ plugins {
 kotlin {
     sourceSets {
         commonMain.dependencies {
-            implementation(project(":extensions:mordant-coroutines"))
+            implementation(project(":mordant-coroutines"))
         }
     }
 }
diff --git a/samples/markdown/build.gradle.kts b/samples/markdown/build.gradle.kts
index 8e682dfe..2098c3a2 100644
--- a/samples/markdown/build.gradle.kts
+++ b/samples/markdown/build.gradle.kts
@@ -1,3 +1,11 @@
 plugins {
     id("mordant-jvm-sample-conventions")
 }
+
+kotlin {
+    sourceSets {
+        jvmMain.dependencies {
+            implementation(project(":mordant-markdown"))
+        }
+    }
+}
diff --git a/samples/progress/build.gradle.kts b/samples/progress/build.gradle.kts
index e3eebd37..9616404b 100644
--- a/samples/progress/build.gradle.kts
+++ b/samples/progress/build.gradle.kts
@@ -5,7 +5,7 @@ plugins {
 kotlin {
     sourceSets {
         jvmMain.dependencies {
-            implementation(project(":extensions:mordant-coroutines"))
+            implementation(project(":mordant-coroutines"))
         }
     }
 }
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 6dff601f..d530e0bc 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -6,7 +6,8 @@ include(
     "mordant-jvm-jna",
     "mordant-jvm-ffm",
     "mordant-jvm-graal-ffi",
-    "extensions:mordant-coroutines",
+    "mordant-coroutines",
+    "mordant-markdown",
     "samples:detection",
     "samples:drawing",
     "samples:hexviewer",


Test patch:
diff --git a/test/graalvm/build.gradle.kts b/test/graalvm/build.gradle.kts
index cd8f71fc..cedb7e06 100644
--- a/test/graalvm/build.gradle.kts
+++ b/test/graalvm/build.gradle.kts
@@ -9,6 +9,7 @@ tasks.test {
 
 dependencies {
     implementation(project(":mordant-omnibus"))
+    implementation(project(":mordant-markdown"))
     testImplementation(kotlin("test"))
 }
 
diff --git a/test/proguard/build.gradle.kts b/test/proguard/build.gradle.kts
index b1004292..f5247595 100644
--- a/test/proguard/build.gradle.kts
+++ b/test/proguard/build.gradle.kts
@@ -10,13 +10,16 @@ repositories {
 val r8: Configuration by configurations.creating
 
 dependencies {
-    implementation(project(":mordant"))
+    implementation(project(":mordant-omnibus"))
+    implementation(project(":mordant-markdown"))
+    implementation(project(":mordant-coroutines"))
     r8(libs.r8)
 }
 
 
 val fatJar by tasks.register<Jar>("fatJar") {
     archiveClassifier = "fat"
+    duplicatesStrategy = DuplicatesStrategy.INCLUDE
 
     from(sourceSets.main.get().output)
 
diff --git a/test/proguard/src/main/kotlin/R8SmokeTest.kt b/test/proguard/src/main/kotlin/R8SmokeTest.kt
index c06a1cf4..798173ba 100644
--- a/test/proguard/src/main/kotlin/R8SmokeTest.kt
+++ b/test/proguard/src/main/kotlin/R8SmokeTest.kt
@@ -1,5 +1,6 @@
 package com.github.ajalt.mordant.main
 
+import com.github.ajalt.mordant.animation.coroutines.animateInCoroutine
 import com.github.ajalt.mordant.animation.progress.animateOnThread
 import com.github.ajalt.mordant.animation.progress.execute
 import com.github.ajalt.mordant.markdown.Markdown
@@ -7,16 +8,19 @@ import com.github.ajalt.mordant.rendering.AnsiLevel
 import com.github.ajalt.mordant.terminal.Terminal
 import com.github.ajalt.mordant.widgets.progress.progressBar
 import com.github.ajalt.mordant.widgets.progress.progressBarLayout
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
 
-fun main(args: Array<String>) {
+suspend fun main(args: Array<String>) = coroutineScope{
     // make sure that the terminal detection doesn't crash.
     Terminal()
 
     // make sure animations and markdown don't crash.
     val t = Terminal(interactive = true, ansiLevel = AnsiLevel.TRUECOLOR)
-    val animation = progressBarLayout { progressBar() }.animateOnThread(t, total = 1)
-    animation.execute()
+    val animation = progressBarLayout { progressBar() }.animateInCoroutine(t, total = 1)
+    launch { animation.execute() }
     t.print(Markdown("- Your args: **${args.asList()}**"))
-    Thread.sleep(100)
+    delay(100)
     animation.update { completed = 1 }
 }