Repository: ajalt/mordant (kotlin)

Issue:
Prevent Animation from clearing the screen
Hey,

I'm using `Terminal.textAnimation` in my application, but it when the animation starts it clears all text that I printed before. Is there a way to prevent this from happening?

Thanks!

Pull request:
Fixes #110

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 187487f05..4490ce30e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,9 @@
 # Changelog
 
 ## Unreleased
+### Fixed
+- Fixed exception thrown in environments that are missing required shared native libraries.
+- Fixed animations clearing too much space when the animation changes size while running. [(#110)](https://github.com/ajalt/mordant/issues/110)
 
 ## 2.1.0
 ### Added
diff --git a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/animation/Animation.kt b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/animation/Animation.kt
index 0e99789e9..ef96816d1 100644
--- a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/animation/Animation.kt
+++ b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/animation/Animation.kt
@@ -110,10 +110,11 @@ abstract class Animation<T>(
         val width = rendered.width
         // To avoid flickering don't clear the screen if the render will completely cover the last frame
         needsClear = size?.let { (h, w) -> height < h || width < w } ?: false
-        size = height to width
         text = terminal.render(rendered)
         // Print an empty renderable to trigger our interceptor, which will add the rendered text
         terminal.print(EmptyWidget)
+        // Update the size now that the old frame has been cleared
+        size = height to width
     }
 
     private fun getCursorMoves(clearScreen: Boolean): String? {
diff --git a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/internal/MppH.kt b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/internal/MppH.kt
index ce982eb9f..eee04830c 100644
--- a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/internal/MppH.kt
+++ b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/internal/MppH.kt
@@ -19,8 +19,6 @@ internal expect fun stdoutInteractive(): Boolean
 
 internal expect fun stdinInteractive(): Boolean
 
-internal expect fun stderrInteractive(): Boolean
-
 internal expect fun codepointSequence(string: String): Sequence<Int>
 
 internal expect fun makePrintingTerminalCursor(terminal: Terminal): TerminalCursor
diff --git a/mordant/src/jsMain/kotlin/com/github/ajalt/mordant/internal/MppImpl.kt b/mordant/src/jsMain/kotlin/com/github/ajalt/mordant/internal/MppImpl.kt
index 579983e0a..bb381d012 100644
--- a/mordant/src/jsMain/kotlin/com/github/ajalt/mordant/internal/MppImpl.kt
+++ b/mordant/src/jsMain/kotlin/com/github/ajalt/mordant/internal/MppImpl.kt
@@ -95,7 +95,6 @@ internal actual fun getTerminalSize(): Pair<Int, Int>? = impls.getTerminalSize()
 internal actual fun getEnv(key: String): String? = impls.readEnvvar(key)
 internal actual fun stdoutInteractive(): Boolean = impls.stdoutInteractive()
 internal actual fun stdinInteractive(): Boolean = impls.stdinInteractive()
-internal actual fun stderrInteractive(): Boolean = impls.stderrInteractive()
 internal actual fun printStderr(message: String, newline: Boolean) = impls.printStderr(message, newline)
 
 // hideInput is not currently implemented
diff --git a/mordant/src/jvmMain/kotlin/com/github/ajalt/mordant/internal/MppImpl.kt b/mordant/src/jvmMain/kotlin/com/github/ajalt/mordant/internal/MppImpl.kt
index f33247ffb..84b586a16 100644
--- a/mordant/src/jvmMain/kotlin/com/github/ajalt/mordant/internal/MppImpl.kt
+++ b/mordant/src/jvmMain/kotlin/com/github/ajalt/mordant/internal/MppImpl.kt
@@ -92,15 +92,18 @@ internal actual fun sendInterceptedPrintRequest(
 internal actual inline fun synchronizeJvm(lock: Any, block: () -> Unit) = synchronized(lock, block)
 
 private val impls: JnaMppImpls = System.getProperty("os.name").let { os ->
-    when {
-        os.startsWith("Windows") -> Win32MppImpls()
-        os == "Linux" -> LinuxMppImpls()
-        os == "Mac OS X" -> MacosMppImpls()
-        else -> FallbackJnaMppImpls()
+    try {
+        when {
+            os.startsWith("Windows") -> Win32MppImpls()
+            os == "Linux" -> LinuxMppImpls()
+            os == "Mac OS X" -> MacosMppImpls()
+            else -> FallbackJnaMppImpls()
+        }
+    } catch (e: UnsatisfiedLinkError) {
+        FallbackJnaMppImpls()
     }
 }
 
 internal actual fun stdoutInteractive(): Boolean = impls.stdoutInteractive()
 internal actual fun stdinInteractive(): Boolean = impls.stdinInteractive()
-internal actual fun stderrInteractive(): Boolean = impls.stderrInteractive()
 internal actual fun getTerminalSize(): Pair<Int, Int>? = impls.getTerminalSize()
diff --git a/mordant/src/nativeMain/kotlin/com/github/ajalt/mordant/internal/MppImpl.kt b/mordant/src/nativeMain/kotlin/com/github/ajalt/mordant/internal/MppImpl.kt
index e48554ffd..2ca9a6260 100644
--- a/mordant/src/nativeMain/kotlin/com/github/ajalt/mordant/internal/MppImpl.kt
+++ b/mordant/src/nativeMain/kotlin/com/github/ajalt/mordant/internal/MppImpl.kt
@@ -29,8 +29,6 @@ internal actual fun stdoutInteractive(): Boolean = isatty(STDOUT_FILENO) != 0
 
 internal actual fun stdinInteractive(): Boolean = isatty(STDIN_FILENO) != 0
 
-internal actual fun stderrInteractive(): Boolean = isatty(STDERR_FILENO) != 0
-
 internal actual fun codepointSequence(string: String): Sequence<Int> = sequence {
     var i = 0
     while (i < string.length) {


Test patch:
diff --git a/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/animation/AnimationTest.kt b/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/animation/AnimationTest.kt
index 4dbd11ea9..fd4a165b8 100644
--- a/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/animation/AnimationTest.kt
+++ b/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/animation/AnimationTest.kt
@@ -38,6 +38,20 @@ class AnimationTest {
         rec.output() shouldBe "${moves}<2>"
     }
 
+    @Test
+    @JsName("animation_size_change")
+    fun `animation size change`() {
+        val a = t.textAnimation<String> { it }
+        a.update("1")
+        rec.output() shouldBe "1\n"
+
+        // update
+        rec.clearOutput()
+        a.update("2\n3")
+        val moves = t.cursor.getMoves { startOfLine(); up(1) }
+        rec.output() shouldBe "${moves}2\n3\n"
+    }
+
     @Test
     fun crClearsLine() {
         val rec = TerminalRecorder(width = 24, crClearsLine = true)