From ac0d188d9eca0d8e388f2f08af04dd46decbecd1 Mon Sep 17 00:00:00 2001 From: AJ Date: Sun, 25 Aug 2024 09:30:09 -0700 Subject: [PATCH 1/6] Add raw mode overrides to `TerminalRecorder` --- CHANGELOG.md | 1 + .../resources/META-INF/proguard/mordant.pro | 2 ++ .../ajalt/mordant/terminal/TerminalRecorder.kt | 15 +++++++++++++-- 3 files changed, 16 insertions(+), 2 deletions(-) rename {mordant-jvm-ffm => mordant-jvm-jna}/src/jvmMain/resources/META-INF/proguard/mordant.pro (79%) diff --git a/CHANGELOG.md b/CHANGELOG.md index f70145939..7147cd9f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - 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. +- Added ability to control raw mode with the `TerminalRecorder`. ### Changed - **Breaking Change** Moved `Terminal.info.width` and `height` to `Terminal.size.width` and `height`. diff --git a/mordant-jvm-ffm/src/jvmMain/resources/META-INF/proguard/mordant.pro b/mordant-jvm-jna/src/jvmMain/resources/META-INF/proguard/mordant.pro similarity index 79% rename from mordant-jvm-ffm/src/jvmMain/resources/META-INF/proguard/mordant.pro rename to mordant-jvm-jna/src/jvmMain/resources/META-INF/proguard/mordant.pro index 92ec51c90..0fa07b234 100644 --- a/mordant-jvm-ffm/src/jvmMain/resources/META-INF/proguard/mordant.pro +++ b/mordant-jvm-jna/src/jvmMain/resources/META-INF/proguard/mordant.pro @@ -1,5 +1,7 @@ # Keep rules for those who are using ProGuard. +-dontwarn org.graalvm.** +-dontwarn com.oracle.svm.core.annotate.** -keep class com.sun.jna.** { *; } -keep class * implements com.sun.jna.** { *; } -keepattributes RuntimeVisibleAnnotations,RuntimeVisibleParameterAnnotations,RuntimeVisibleTypeAnnotations,AnnotationDefault diff --git a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/terminal/TerminalRecorder.kt b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/terminal/TerminalRecorder.kt index add22fb97..4cacae5d4 100644 --- a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/terminal/TerminalRecorder.kt +++ b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/terminal/TerminalRecorder.kt @@ -44,10 +44,20 @@ class TerminalRecorder private constructor( * Lines of input to return from [readLineOrNull]. */ var inputLines: MutableList = mutableListOf() + + /** + * Input events to return when reading in raw mode + */ + var inputEvents: MutableList = mutableListOf() + + /** Whether raw mode is currently active */ + var rawModeActive: Boolean = false + private val stdout: StringBuilder = StringBuilder() private val stderr: StringBuilder = StringBuilder() private val output: StringBuilder = StringBuilder() + /** Clear [stdout], [stderr], and [output] */ fun clearOutput() { stdout.clear() stderr.clear() @@ -91,10 +101,11 @@ class TerminalRecorder private constructor( } override fun readInputEvent(timeout: Duration, mouseTracking: MouseTracking): InputEvent? { - TODO() + return inputEvents.removeFirstOrNull() } override fun enterRawMode(mouseTracking: MouseTracking): AutoCloseable { - TODO() + rawModeActive = true + return AutoCloseable { rawModeActive = false } } } From 1bda82f90098ed1b970c5fcb4314c0aeb5abc67d Mon Sep 17 00:00:00 2001 From: AJ Date: Sun, 25 Aug 2024 09:37:52 -0700 Subject: [PATCH 2/6] Combine test annotations, reformat all code --- .../coroutines/CoroutinesAnimatorTest.kt | 9 +- .../ajalt/mordant/markdown/MarkdownTest.kt | 171 ++++++----------- .../ajalt/mordant/animation/Animation.kt | 2 +- .../ajalt/mordant/internal/MppInternal.kt | 5 +- .../github/ajalt/mordant/internal/Parsing.kt | 34 +++- .../github/ajalt/mordant/rendering/Align.kt | 6 + .../ajalt/mordant/rendering/BorderType.kt | 8 +- .../ajalt/mordant/rendering/TextStyle.kt | 1 + .../ajalt/mordant/rendering/WidthRange.kt | 3 +- .../com/github/ajalt/mordant/table/Borders.kt | 1 + .../github/ajalt/mordant/table/TableCsv.kt | 2 + .../ajalt/mordant/terminal/TerminalColors.kt | 3 +- .../terminalinterface/PosixEventParser.kt | 2 +- .../TerminalInterface.windows.kt | 2 +- .../ajalt/mordant/widgets/DefinitionList.kt | 3 +- .../ajalt/mordant/widgets/HorizontalRule.kt | 6 +- .../github/ajalt/mordant/widgets/Padding.kt | 15 +- .../com/github/ajalt/mordant/widgets/Panel.kt | 9 +- .../github/ajalt/mordant/widgets/Spinner.kt | 7 +- .../com/github/ajalt/mordant/widgets/Text.kt | 33 +++- .../ajalt/mordant/animation/AnimationTest.kt | 15 +- .../progress/BaseProgressAnimationTest.kt | 9 +- .../mordant/input/SelectListAnimationTest.kt | 24 +-- .../mordant/rendering/TextAlignmentTest.kt | 66 ++++--- .../mordant/rendering/TextStyleOscTest.kt | 6 +- .../mordant/rendering/TextWhitespaceTest.kt | 3 +- .../rendering/internal/CellWidthTest.kt | 20 +- .../ajalt/mordant/table/LinearLayoutTest.kt | 12 +- .../ajalt/mordant/table/TableAlignmentTest.kt | 180 +++++++++--------- .../mordant/table/TableBorderStyleTest.kt | 56 ++++-- .../ajalt/mordant/table/TableBorderTest.kt | 48 +++-- .../mordant/table/TableColumnWidthTest.kt | 51 ++--- .../ajalt/mordant/table/TableCsvTest.kt | 18 +- .../github/ajalt/mordant/table/TableTest.kt | 72 +++---- .../mordant/terminal/HtmlRendererTest.kt | 6 +- .../ajalt/mordant/terminal/PromptTest.kt | 27 +-- .../mordant/terminal/TerminalColorsTest.kt | 36 ++-- .../mordant/terminal/TerminalCursorTest.kt | 18 +- .../ajalt/mordant/terminal/TerminalTest.kt | 9 +- .../mordant/widgets/DefinitionListTest.kt | 30 +-- .../widgets/DeprecatedProgressLayoutTest.kt | 18 +- .../mordant/widgets/HorizontalRuleTest.kt | 49 +++-- .../widgets/MultiProgressLayoutTest.kt | 12 +- .../ajalt/mordant/widgets/OrderedListTest.kt | 9 +- .../github/ajalt/mordant/widgets/PanelTest.kt | 30 +-- .../ajalt/mordant/widgets/ProgressBarTest.kt | 50 ++--- .../mordant/widgets/ProgressLayoutTest.kt | 72 +++---- .../ajalt/mordant/widgets/SelectListTest.kt | 12 +- .../github/ajalt/mordant/widgets/TextTest.kt | 38 ++-- .../mordant/widgets/UnorderedListTest.kt | 9 +- .../ajalt/mordant/widgets/ViewportTest.kt | 3 +- .../github/ajalt/mordant/internal/JsCompat.kt | 4 +- .../mordant/animation/ProgressAnimation.kt | 13 +- .../mordant/internal/MppInternal.mingw.kt | 3 +- test/proguard/src/main/kotlin/R8SmokeTest.kt | 4 +- 55 files changed, 627 insertions(+), 727 deletions(-) diff --git a/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 index 5c15a580b..003667f34 100644 --- a/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 @@ -64,8 +64,7 @@ class CoroutinesAnimatorTest { job.isActive shouldBe false } - @Test - @JsName("stop_and_clear") + @[Test JsName("stop_and_clear")] fun `stop and clear`() = runTest { val a = progressBarLayout(spacing = 0, textFps = 1) { completed() @@ -86,8 +85,7 @@ class CoroutinesAnimatorTest { vt.fullNormalizedOutput() shouldBe "$HIDE_CURSOR 0/10\r${CSI}0J$SHOW_CURSOR" } - @Test - @JsName("unit_animation") + @[Test JsName("unit_animation")] fun `unit animation`() = runTest { var i = 1 var fin = false @@ -111,8 +109,7 @@ class CoroutinesAnimatorTest { job.isActive shouldBe false } - @Test - @JsName("multi_progress_animation") + @[Test JsName("multi_progress_animation")] fun `multi progress animation`() = runTest { val layout = progressBarLayout { completed(fps = 1) } val animation = MultiProgressBarAnimation(t).animateInCoroutine() diff --git a/mordant-markdown/src/commonTest/kotlin/com/github/ajalt/mordant/markdown/MarkdownTest.kt b/mordant-markdown/src/commonTest/kotlin/com/github/ajalt/mordant/markdown/MarkdownTest.kt index 9350dbfc2..c9d04a1c0 100644 --- a/mordant-markdown/src/commonTest/kotlin/com/github/ajalt/mordant/markdown/MarkdownTest.kt +++ b/mordant-markdown/src/commonTest/kotlin/com/github/ajalt/mordant/markdown/MarkdownTest.kt @@ -37,8 +37,7 @@ class MarkdownTest { private val linkDest = Theme.Default.style("markdown.link.destination") private val imgAlt = Theme.Default.style("markdown.img.alt-text") - @Test - @JsName("default_style_is_colored") + @[Test JsName("default_style_is_colored")] fun `default style is colored`() = forAll( row(quote), row(quote), @@ -58,12 +57,10 @@ class MarkdownTest { @Test fun tilde() = doTest("~", "~") - @Test - @JsName("dollar_sign") + @[Test JsName("dollar_sign")] fun `dollar sign`() = doTest("$", "$") - @Test - @JsName("math_block") + @[Test JsName("math_block")] fun `math block`() = doTest( """ This expression uses `\${'$'}` to display a dollar sign: ${'$'}\sqrt{\${'$'}4}${'$'} @@ -77,8 +74,7 @@ ${codeBlock("\\sum_{k=1}^n a_k^2")} """ ) - @Test - @JsName("test_paragraphs") + @[Test JsName("test_paragraphs")] fun `test paragraphs`() = doTest( """ Paragraph one @@ -103,8 +99,7 @@ Paragraph three line breaks. """ ) - @Test - @JsName("test_paragraphs_wrapped") + @[Test JsName("test_paragraphs_wrapped")] fun `test paragraphs wrapped`() = doTest( """ Paragraph one @@ -123,8 +118,7 @@ line. """, width = 11 ) - @Test - @JsName("test_paragraphs_crlf") + @[Test JsName("test_paragraphs_crlf")] fun `test paragraphs crlf`() = doTest( """ Paragraph one @@ -141,8 +135,7 @@ Paragraph two wrapped line. """ ) - @Test - @JsName("test_quotes") + @[Test JsName("test_quotes")] fun `test quotes`() = doTest( """ This paragraph @@ -153,8 +146,7 @@ This paragraph has some "double quotes" and some 'single quotes'. """ ) - @Test - @JsName("test_emphasis") + @[Test JsName("test_emphasis")] fun `test emphasis`() = doTest( """ An *em span*. @@ -183,8 +175,7 @@ A ${strikethrough("strikethrough span")}. """ ) - @Test - @JsName("test_unordered_list") + @[Test JsName("test_unordered_list")] fun `test unordered list`() = doTest( """ - line 1 @@ -198,8 +189,7 @@ A ${strikethrough("strikethrough span")}. """ ) - @Test - @JsName("test_unordered_list_wrap") + @[Test JsName("test_unordered_list_wrap")] fun `test unordered list wrap`() = doTest( """ - line 1 @@ -215,8 +205,7 @@ A ${strikethrough("strikethrough span")}. ) // https://github.github.com/gfm/#example-306 - @Test - @JsName("test_unordered_list_nested") + @[Test JsName("test_unordered_list_nested")] fun `test unordered list nested`() = doTest( """ - a @@ -236,8 +225,7 @@ A ${strikethrough("strikethrough span")}. """ ) - @Test - @JsName("test_ordered_list") + @[Test JsName("test_ordered_list")] fun `test ordered list`() = doTest( """ 1. line 1 @@ -251,8 +239,7 @@ A ${strikethrough("strikethrough span")}. """ ) - @Test - @JsName("test_ordered_list_wrap") + @[Test JsName("test_ordered_list_wrap")] fun `test ordered list wrap`() = doTest( """ 1. line 1 @@ -267,8 +254,7 @@ A ${strikethrough("strikethrough span")}. """, width = 11 ) - @Test - @JsName("test_ordered_list_loose") + @[Test JsName("test_ordered_list_loose")] fun `test ordered list loose`() = doTest( """ 1. a @@ -283,8 +269,7 @@ A ${strikethrough("strikethrough span")}. """ ) - @Test - @JsName("test_ordered_list_nested") + @[Test JsName("test_ordered_list_nested")] fun `test ordered list nested`() = doTest( """ 1. a @@ -305,8 +290,7 @@ A ${strikethrough("strikethrough span")}. """ ) - @Test - @JsName("block_quote") + @[Test JsName("block_quote")] fun `block quote`() = doTest( """ > line 1 @@ -322,8 +306,7 @@ ${quote("▎ line 3")} // https://github.github.com/gfm/#example-206 - @Test - @JsName("block_quote_with_header") + @[Test JsName("block_quote_with_header")] fun `block quote with header`() = doTest( """ ># Foo @@ -338,8 +321,7 @@ ${quote("▎ bar baz")} ) // https://github.github.com/gfm/#example-208 - @Test - @JsName("indented_block_quote") + @[Test JsName("indented_block_quote")] fun `indented block quote`() = doTest( """ > # Foo @@ -355,8 +337,7 @@ ${quote("▎ bar baz")} @Suppress("MarkdownUnresolvedFileReference") - @Test - @JsName("visible_links") + @[Test JsName("visible_links")] fun `visible links`() = doTest( """ [a reference link][a link] @@ -405,8 +386,7 @@ ${linkDest("[a link]: example.com")} """ ) - @Test - @JsName("link_with_hyperlinks") + @[Test JsName("link_with_hyperlinks")] fun `link with hyperlinks`() = doTest( """ [a reference link][a link] @@ -459,8 +439,7 @@ ${linkText("www.example.com/url")} """.normalizeHyperlinks(), hyperlinks = true ) - @Test - @JsName("image_tags") + @[Test JsName("image_tags")] fun `image tags`() = doTest( """ ![an image]( example.png "a title" ) @@ -489,8 +468,7 @@ ${linkDest("[1]: example.png")} """ ) - @Test - @JsName("md_in_link_titles") + @[Test JsName("md_in_link_titles")] fun `md in link titles`() = doTest( """ [`code`](example.com/1) @@ -503,8 +481,7 @@ ${(linkText + hyperlink("example.com/2"))("🖼️ ${imgAlt("an image")}")} """.normalizeHyperlinks(), hyperlinks = true ) - @Test - @JsName("default_html") + @[Test JsName("default_html")] fun `default html`() = doTest( """

@@ -515,8 +492,7 @@ ${(linkText + hyperlink("example.com/2"))("🖼️ ${imgAlt("an image")}")} """ ) - @Test - @JsName("show_html") + @[Test JsName("show_html")] fun `show html`() = doTest( """

@@ -531,8 +507,7 @@ ${(linkText + hyperlink("example.com/2"))("🖼️ ${imgAlt("an image")}")} """, showHtml = true ) - @Test - @JsName("default_html_inline") + @[Test JsName("default_html_inline")] fun `default html inline`() = doTest( """ Hello world. @@ -541,8 +516,7 @@ Hello world. """ ) - @Test - @JsName("show_html_inline") + @[Test JsName("show_html_inline")] fun `show html inline`() = doTest( """ Hello world. @@ -551,8 +525,7 @@ Hello world. """, showHtml = true ) - @Test - @JsName("horizontal_rule") + @[Test JsName("horizontal_rule")] fun `horizontal rule`() = doTest( """ --- @@ -561,8 +534,7 @@ Hello world. """, width = 10 ) - @Test - @JsName("header_1_empty") + @[Test JsName("header_1_empty")] fun `header 1 empty`() = doTest( """ # @@ -572,8 +544,7 @@ Hello world. """, width = 19 ) - @Test - @JsName("header_1_custom_padding") + @[Test JsName("header_1_custom_padding")] fun `header 1 custom padding`() = doTest(""" # Header Text """, """ @@ -586,8 +557,7 @@ ${h1.colorOnly.colorOnly("═══ ${bold("Header Text")} ═══")} dimensions["markdown.header.padding"] = 2 }) - @Test - @JsName("header_1") + @[Test JsName("header_1")] fun `header 1`() = doTest( """ # Header Text @@ -598,8 +568,7 @@ ${h1.colorOnly("═══ ${bold("Header Text")} ═══")} """, width = 19 ) - @Test - @JsName("header_2") + @[Test JsName("header_2")] fun `header 2`() = doTest( """ ## Header Text @@ -610,8 +579,7 @@ ${h2.colorOnly("─── ${bold("Header Text")} ───")} """, width = 19 ) - @Test - @JsName("header_3") + @[Test JsName("header_3")] fun `header 3`() = doTest( """ ### Header Text @@ -622,8 +590,7 @@ ${h3.colorOnly(" ${(bold + underline)("Header Text")} ")} """, width = 19 ) - @Test - @JsName("header_4") + @[Test JsName("header_4")] fun `header 4`() = doTest( """ #### Header Text @@ -634,8 +601,7 @@ ${h4.colorOnly(" ${underline("Header Text")} ")} """, width = 19 ) - @Test - @JsName("header_5") + @[Test JsName("header_5")] fun `header 5`() = doTest( """ ##### Header Text @@ -646,8 +612,7 @@ ${h5.colorOnly(" ${italic("Header Text")} ")} """, width = 19 ) - @Test - @JsName("header_6") + @[Test JsName("header_6")] fun `header 6`() = doTest( """ ###### Header Text @@ -658,8 +623,7 @@ ${h6.colorOnly(" ${dim("Header Text")} ")} """, width = 19 ) - @Test - @JsName("adjacent_headers") + @[Test JsName("adjacent_headers")] fun `adjacent headers`() = doTest( """ # Header 1 @@ -677,8 +641,7 @@ ${h2.colorOnly("──── ${bold("Header 2")} ─────")} ) - @Test - @JsName("header_trailing_chars") + @[Test JsName("header_trailing_chars")] fun `header trailing chars`() = doTest( """ # Header Text ## @@ -689,8 +652,7 @@ ${h1.colorOnly("═══ ${bold("Header Text")} ═══")} """, width = 19 ) - @Test - @JsName("setext_h1") + @[Test JsName("setext_h1")] fun `setext h1`() = doTest( """ Header Text @@ -702,8 +664,7 @@ ${h1.colorOnly("═══ ${bold("Header Text")} ═══")} """, width = 19 ) - @Test - @JsName("setext_h2") + @[Test JsName("setext_h2")] fun `setext h2`() = doTest( """ Header Text @@ -715,8 +676,7 @@ ${h2.colorOnly("─── ${bold("Header Text")} ───")} """, width = 19 ) - @Test - @JsName("empty_code_span") + @[Test JsName("empty_code_span")] fun `empty code span`() = doTest( """ An `` empty code span. @@ -725,8 +685,7 @@ An `` empty code span. """ ) - @Test - @JsName("code_span") + @[Test JsName("code_span")] fun `code span`() = doTest( """ This `is ` a `code
` `span`. @@ -756,8 +715,7 @@ A ${code("span with a line break")}. //!"#${'$'}%&'()*+,-./:;<=>?@[\]^_`{|}~ //""") - @Test - @JsName("hard_line_breaks") + @[Test JsName("hard_line_breaks")] fun `hard line breaks`() = doTest( """ A hard @@ -794,8 +752,7 @@ LS ) // https://github.github.com/gfm/#example-205 - @Test - @JsName("header_only_table") + @[Test JsName("header_only_table")] fun `header only table`() = doTest( """ | abc | def | @@ -808,8 +765,7 @@ LS ) // https://github.github.com/gfm/#example-198 - @Test - @JsName("simple_table") + @[Test JsName("simple_table")] fun `simple table`() = doTest( """ | foo | bar | @@ -825,8 +781,7 @@ LS ) // https://github.github.com/gfm/#example-204 - @Test - @JsName("no_rectangular_table") + @[Test JsName("no_rectangular_table")] fun `non-rectangular table`() = doTest( """ | abc | def | @@ -844,8 +799,7 @@ LS """ ) - @Test - @JsName("table_alignment") + @[Test JsName("table_alignment")] fun `table alignment`() = doTest( """ | abc | def | ghi | @@ -863,8 +817,7 @@ LS """ ) - @Test - @JsName("table_with_empty_cell") + @[Test JsName("table_with_empty_cell")] fun `table with empty cell`() = doTest( """ | foo | @@ -879,8 +832,7 @@ LS """ ) - @Test - @JsName("table_with_empty_header_cell") + @[Test JsName("table_with_empty_header_cell")] fun `table with empty header cell`() = doTest( """ | | @@ -897,8 +849,7 @@ LS // https://github.github.com/gfm/#example-279 - @Test - @JsName("task_list") + @[Test JsName("task_list")] fun `task list`() = doTest( """ - [ ] foo @@ -909,8 +860,7 @@ LS """ ) - @Test - @JsName("indented_code_block") + @[Test JsName("indented_code_block")] fun `indented code block`() = doTest( """ foo { @@ -933,8 +883,7 @@ LS """ ) - @Test - @JsName("fenced_code_block") + @[Test JsName("fenced_code_block")] fun `fenced code block`() = doTest( """ ``` @@ -960,8 +909,7 @@ foo { ) // https://github.github.com/gfm/#example-113 - @Test - @JsName("fenced_code_block_with_info_string") + @[Test JsName("fenced_code_block_with_info_string")] fun `fenced code block with info string`() = doTest( """ ~~~~ ruby startline=3 ${'$'}%@#${'$'} @@ -978,8 +926,7 @@ end """ ) - @Test - @JsName("fenced_code_block_with_nbsp") + @[Test JsName("fenced_code_block_with_nbsp")] fun `fenced code block with nbsp`() { val nbsp = '\u00A0' doTest( @@ -995,8 +942,7 @@ foo${nbsp}bar baz ) } - @Test - @JsName("fenced_code_block_with_no_border_in_theme") + @[Test JsName("fenced_code_block_with_no_border_in_theme")] fun `fenced code block with no border in theme`() = doTest(""" ``` foo bar @@ -1007,8 +953,7 @@ ${codeBlock("foo bar")} flags["markdown.code.block.border"] = false }) - @Test - @JsName("indented_code_block_with_no_border_in_theme") + @[Test JsName("indented_code_block_with_no_border_in_theme")] fun `indented code block with no border in theme`() = doTest(""" foo bar """, """ @@ -1017,8 +962,7 @@ ${codeBlock("foo bar")} flags["markdown.code.block.border"] = false }) - @Test - @JsName("plain_theme") + @[Test JsName("plain_theme")] fun `plain theme`() = doTest( """ # H1 @@ -1036,8 +980,7 @@ link(c.com) """, theme = Theme.Plain, width = 15 ) - @Test - @JsName("ascii_theme") + @[Test JsName("ascii_theme")] fun `ascii theme`() = doTest( """ # H1 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 9e45af77c..f6a131f08 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 @@ -33,7 +33,7 @@ abstract class Animation( @Deprecated("This parameter is ignored; animations never print a trailing linebreak.") private val trailingLinebreak: Boolean = true, val terminal: Terminal, -): StoppableAnimation { +) : StoppableAnimation { private data class State( /** The length of each line of the last rendered widget */ val size: List? = null, diff --git a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/internal/MppInternal.kt b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/internal/MppInternal.kt index 0ae89af67..48d11081a 100644 --- a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/internal/MppInternal.kt +++ b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/internal/MppInternal.kt @@ -10,6 +10,7 @@ internal interface MppAtomicInt { internal interface MppAtomicRef { val value: T + /** @return true if the value was set */ fun compareAndSet(expected: T, newValue: T): Boolean fun getAndSet(newValue: T): T @@ -17,7 +18,7 @@ internal interface MppAtomicRef { /** Update the reference via spin lock, spinning forever until it succeeds. */ internal inline fun MppAtomicRef.update(block: T.() -> T): Pair { - while(true) { + while (true) { val old = value val newValue = block(old) if (compareAndSet(old, newValue)) return old to newValue @@ -54,6 +55,6 @@ internal expect fun readFileIfExists(filename: String): String? internal expect fun hasFileSystem(): Boolean -internal expect fun getStandardTerminalInterface() : TerminalInterface +internal expect fun getStandardTerminalInterface(): TerminalInterface internal val STANDARD_TERM_INTERFACE: TerminalInterface = getStandardTerminalInterface() diff --git a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/internal/Parsing.kt b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/internal/Parsing.kt index 86da09b14..21459b4b3 100644 --- a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/internal/Parsing.kt +++ b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/internal/Parsing.kt @@ -34,7 +34,10 @@ private fun parseAnsi(text: String, defaultStyle: TextStyle): List { var style = defaultStyle for (command in commands) { if (command.range.first > idxAfterLastCmd) { - parts += Chunk(text = text.substring(idxAfterLastCmd, command.range.first), style = style) + parts += Chunk( + text = text.substring(idxAfterLastCmd, command.range.first), + style = style + ) } idxAfterLastCmd = command.range.last + 1 style = updateStyle(style, defaultStyle, command.value) @@ -107,25 +110,39 @@ private fun splitLines(words: List): List { * If the [ansi] contains any reset codes, the reset style attributes will be set to their * corresponding value from [defaultStyle]. */ -internal fun updateStyle(existingStyle: TextStyle, defaultStyle: TextStyle, ansi: String): TextStyle { +internal fun updateStyle( + existingStyle: TextStyle, + defaultStyle: TextStyle, + ansi: String, +): TextStyle { if (ansi.startsWith(OSC)) return updateStyleWithOsc(ansi, existingStyle, defaultStyle) if (ansi.startsWith(CSI)) return updateStyleWithCsi(ansi, existingStyle, defaultStyle) return existingStyle // DSC, APC, etc. don't affect style, discard them } -private fun updateStyleWithOsc(ansi: String, existingStyle: TextStyle, defaultStyle: TextStyle): TextStyle { +private fun updateStyleWithOsc( + ansi: String, + existingStyle: TextStyle, + defaultStyle: TextStyle, +): TextStyle { if (!ansi.startsWith("${OSC}8")) return existingStyle // Only OSC 8 (hyperlinks) are supported val params = ansi.substring(3, ansi.length - 2).split(";") if (params.isEmpty()) return existingStyle // invalid ansi sequence val hyperlink = params.last().takeUnless { it.isBlank() } - val id = if (hyperlink == null) defaultStyle.hyperlinkId else params.find { it.startsWith("id=") }?.drop(3) + val id = + if (hyperlink == null) defaultStyle.hyperlinkId else params.find { it.startsWith("id=") } + ?.drop(3) return existingStyle.copy( hyperlink = hyperlink ?: defaultStyle.hyperlink, hyperlinkId = id ) } -private fun updateStyleWithCsi(ansi: String, existingStyle: TextStyle, defaultStyle: TextStyle): TextStyle { +private fun updateStyleWithCsi( + ansi: String, + existingStyle: TextStyle, + defaultStyle: TextStyle, +): TextStyle { if (!ansi.endsWith("m")) return existingStyle // SGR sequences end in "m", others don't affect style // SGR sequences only contains numbers. Anything else is malformed, so we skip it. @@ -160,10 +177,12 @@ private fun updateStyleWithCsi(ansi: String, existingStyle: TextStyle, defaultSt inverse = defaultStyle.inverse ?: false strikethrough = defaultStyle.strikethrough ?: false } + AnsiCodes.boldAndDimClose -> { bold = defaultStyle.bold ?: false dim = defaultStyle.dim ?: false } + AnsiCodes.italicClose -> italic = defaultStyle.italic ?: false AnsiCodes.underlineClose -> underline = defaultStyle.underline ?: false AnsiCodes.inverseClose -> inverse = defaultStyle.inverse ?: false @@ -174,15 +193,18 @@ private fun updateStyleWithCsi(ansi: String, existingStyle: TextStyle, defaultSt in AnsiCodes.fg16Range, in AnsiCodes.fg16BrightRange -> { color = Ansi16(code) } + in AnsiCodes.bg16Range, in AnsiCodes.bg16BrightRange -> { bgColor = Ansi16(code - AnsiCodes.fgBgOffset) } + AnsiCodes.fgColorSelector -> { val (c, consumed) = getAnsiColor(i + 1, codes) if (c == null) break // Unrecognized code format: stop parsing color = c i += consumed } + AnsiCodes.bgColorSelector -> { val (c, consumed) = getAnsiColor(i + 1, codes) if (c == null) break @@ -235,6 +257,7 @@ private fun getAnsiColor(i: Int, codes: List): Pair { Ansi256(codes[i + 1]) to 2 } } + AnsiCodes.selectorRgb -> { if (i + 3 > codes.lastIndex || codes[i + 1] !in 0..255 @@ -246,6 +269,7 @@ private fun getAnsiColor(i: Int, codes: List): Pair { RGB.from255(codes[i + 1], codes[i + 2], codes[i + 3]) to 4 } } + else -> null to 0 } } diff --git a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/rendering/Align.kt b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/rendering/Align.kt index 4f8d6c66a..5502ef2e0 100644 --- a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/rendering/Align.kt +++ b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/rendering/Align.kt @@ -3,16 +3,20 @@ package com.github.ajalt.mordant.rendering enum class TextAlign { /** Align text to the left side of the widget, and include trailing whitespace so that all lines are the lame length. */ LEFT, + /** Align text to the right side of the widget */ RIGHT, + /** Align text to the center of the widget */ CENTER, + /** * Align text to both sides of the widget, adding spaces between words. * * For Widgets other than `Text`, this is treated the same as [CENTER]. */ JUSTIFY, + /** Align text to the left side of the widget, and don't include trailing whitespace */ NONE } @@ -20,8 +24,10 @@ enum class TextAlign { enum class VerticalAlign { /** Align widgets vertically to the top of the layout */ TOP, + /** Align widgets vertically to the middle of the layout */ MIDDLE, + /** Align widgets vertically to the bottom of the layout */ BOTTOM } diff --git a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/rendering/BorderType.kt b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/rendering/BorderType.kt index 8470bf462..73efd38a1 100644 --- a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/rendering/BorderType.kt +++ b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/rendering/BorderType.kt @@ -43,7 +43,13 @@ class BorderTypeSection( private val array = arrayOf(" ", w, s, sw, e, ew, es, esw, n, nw, ns, nsw, ne, new, nes, nesw) - fun getCorner(n: Boolean, e: Boolean, s: Boolean, w: Boolean, textStyle: TextStyle = DEFAULT_STYLE): Span { + fun getCorner( + n: Boolean, + e: Boolean, + s: Boolean, + w: Boolean, + textStyle: TextStyle = DEFAULT_STYLE, + ): Span { val i = (if (n) 8 else 0) or (if (e) 4 else 0) or (if (s) 2 else 0) or (if (w) 1 else 0) return Span.word(array[i], textStyle) } diff --git a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/rendering/TextStyle.kt b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/rendering/TextStyle.kt index 1ce2cf428..12e471238 100644 --- a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/rendering/TextStyle.kt +++ b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/rendering/TextStyle.kt @@ -85,6 +85,7 @@ interface TextStyle { ) } } + operator fun plus(other: TextStyles): TextStyle = this + other.style } diff --git a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/rendering/WidthRange.kt b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/rendering/WidthRange.kt index 2c4dfb117..01d67684c 100644 --- a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/rendering/WidthRange.kt +++ b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/rendering/WidthRange.kt @@ -13,7 +13,8 @@ data class WidthRange(val min: Int, val max: Int) { operator fun plus(extra: Int) = if (extra == 0) this else WidthRange(min + extra, max + extra) operator fun plus(other: WidthRange) = WidthRange(min + other.min, max + other.max) - operator fun div(divisor: Int) = if (divisor == 1) this else WidthRange(min / divisor, max / divisor) + operator fun div(divisor: Int) = + if (divisor == 1) this else WidthRange(min / divisor, max / divisor) } internal fun Iterable.maxWidthRange( diff --git a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/table/Borders.kt b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/table/Borders.kt index cf634ae3f..59194c097 100644 --- a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/table/Borders.kt +++ b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/table/Borders.kt @@ -22,6 +22,7 @@ enum class Borders( LEFT_TOP_BOTTOM(left = true, top = true, right = false, bottom = true), LEFT_TOP_RIGHT(left = true, top = true, right = true, bottom = false), ALL(left = true, top = true, right = true, bottom = true), + @Suppress("unused") @Deprecated("Use TOP_BOTTOM", replaceWith = ReplaceWith("TOP_BOTTOM")) TOM_BOTTOM(left = false, top = true, right = false, bottom = true), diff --git a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/table/TableCsv.kt b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/table/TableCsv.kt index 996e1fdd5..1da1d45ea 100644 --- a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/table/TableCsv.kt +++ b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/table/TableCsv.kt @@ -68,6 +68,7 @@ fun Table.contentToCsv( require(escapeChar != null || !escapable.containsMatchIn(cell)) { "Content requires escaping, but no escapeChar set" } escapable.replace(quotesEscaped) { "$escapeChar${it.value}" } } + else -> quotesEscaped } @@ -77,6 +78,7 @@ fun Table.contentToCsv( (escapeChar == null || doubleQuote) && quoteChar in cell || escapable.containsMatchIn(cell) } + CsvQuoting.NONNUMERIC -> cell.any { it !in '0'..'9' } CsvQuoting.NONE -> false } diff --git a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/terminal/TerminalColors.kt b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/terminal/TerminalColors.kt index 2ae988bca..09cb72854 100644 --- a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/terminal/TerminalColors.kt +++ b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/terminal/TerminalColors.kt @@ -14,7 +14,8 @@ import com.github.ajalt.mordant.rendering.Theme * Strings decorated with these styles will be downsampled correctly even if they are printed * directly rather than through [Terminal.print]. */ -class TerminalColors internal constructor( // TODO(3.0): remove +class TerminalColors internal constructor( + // TODO(3.0): remove private val terminalInfo: TerminalInfo, private val theme: Theme, ) { diff --git a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/terminal/terminalinterface/PosixEventParser.kt b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/terminal/terminalinterface/PosixEventParser.kt index 740a1d71e..12663d506 100644 --- a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/terminal/terminalinterface/PosixEventParser.kt +++ b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/terminal/terminalinterface/PosixEventParser.kt @@ -13,7 +13,7 @@ private const val ESC = '\u001b' internal class PosixEventParser( - private val readRawByte: (t0: ComparableTimeMark, timeout: Duration) -> Char + private val readRawByte: (t0: ComparableTimeMark, timeout: Duration) -> Char, ) { /* Some patterns seen in terminal key escape codes, derived from combos seen diff --git a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/terminal/terminalinterface/TerminalInterface.windows.kt b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/terminal/terminalinterface/TerminalInterface.windows.kt index 2c0c99c54..702d8c34f 100644 --- a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/terminal/terminalinterface/TerminalInterface.windows.kt +++ b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/terminal/terminalinterface/TerminalInterface.windows.kt @@ -9,7 +9,7 @@ import kotlin.time.Duration import kotlin.time.TimeSource // TODO: docs -abstract class TerminalInterfaceWindows: StandardTerminalInterface() { +abstract class TerminalInterfaceWindows : StandardTerminalInterface() { private companion object { // https://learn.microsoft.com/en-us/windows/console/key-event-record-str const val RIGHT_ALT_PRESSED: UInt = 0x0001u diff --git a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/widgets/DefinitionList.kt b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/widgets/DefinitionList.kt index 94c940bb4..15b711021 100644 --- a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/widgets/DefinitionList.kt +++ b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/widgets/DefinitionList.kt @@ -58,7 +58,8 @@ private class DefinitionList( continue } val termLines = term.render(t, termWidth).lines - val descLines = desc.render(t, (width - termWidth - descriptionSpacing).coerceAtLeast(0)).lines + val descLines = + desc.render(t, (width - termWidth - descriptionSpacing).coerceAtLeast(0)).lines termLines.zip(descLines).mapTo(lines) { (t, d) -> flatLine(t, Span.space(descriptionSpacing + termWidth - t.lineWidth), d) } diff --git a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/widgets/HorizontalRule.kt b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/widgets/HorizontalRule.kt index 82f8569c5..5e1420add 100644 --- a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/widgets/HorizontalRule.kt +++ b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/widgets/HorizontalRule.kt @@ -62,11 +62,13 @@ class HorizontalRule internal constructor( val padding = titlePadding[t] val totalPadding = 2 * padding val minBarWidth = 4 + totalPadding // 2 for each of left bar and right bar - val renderedTitle = title.withAlign(TextAlign.NONE).render(t, (width - minBarWidth).coerceAtLeast(0)) + val renderedTitle = + title.withAlign(TextAlign.NONE).render(t, (width - minBarWidth).coerceAtLeast(0)) val lines = if (renderedTitle.isEmpty()) { listOf(rule(t, width)) } else { - val titleRuleLine = if (titleOverflowTop) renderedTitle.lines.last() else renderedTitle.lines.first() + val titleRuleLine = + if (titleOverflowTop) renderedTitle.lines.last() else renderedTitle.lines.first() val ruleWidth = width - titleRuleLine.sumOf { it.cellWidth } - totalPadding val leftRuleWidth = when (titleAlign) { TextAlign.LEFT -> 1 diff --git a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/widgets/Padding.kt b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/widgets/Padding.kt index 126c7be6d..6484ffae9 100644 --- a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/widgets/Padding.kt +++ b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/widgets/Padding.kt @@ -59,7 +59,13 @@ fun Widget.withPadding(padding: Padding, padEmptyLines: Boolean = true): Widget fun Widget.withPadding(all: Int, padEmptyLines: Boolean = true): Widget = Padded.get(this, Padding(all), padEmptyLines) -fun Widget.withPadding(top: Int, right: Int, bottom: Int, left: Int, padEmptyLines: Boolean = true): Widget = +fun Widget.withPadding( + top: Int, + right: Int, + bottom: Int, + left: Int, + padEmptyLines: Boolean = true, +): Widget = Padded.get(this, Padding(top, right, bottom, left), padEmptyLines) fun Widget.withPadding(padEmptyLines: Boolean = true, padding: Padding.Builder.() -> Unit): Widget = @@ -83,7 +89,12 @@ internal class Padded private constructor( fun get(content: Widget, padding: Padding, padEmptyLines: Boolean): Widget { return when { padding.isEmpty -> content - content is Padded -> Padded(content.content, content.padding + padding, padEmptyLines) + content is Padded -> Padded( + content.content, + content.padding + padding, + padEmptyLines + ) + else -> Padded(content, padding, padEmptyLines) } } diff --git a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/widgets/Panel.kt b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/widgets/Panel.kt index fb2e98ee6..ea1ef9d72 100644 --- a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/widgets/Panel.kt +++ b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/widgets/Panel.kt @@ -128,7 +128,8 @@ class Panel private constructor( override fun measure(t: Terminal, width: Int): WidthRange { val contentWidth = content.measure(t, maxContentWidth(width)) + borderWidth val titlePadding = titlePadding[t] - val titleWidth = title?.measure(t, maxTitleWidth(width, titlePadding))?.plus(borderWidth + titlePadding * 2) + val titleWidth = title?.measure(t, maxTitleWidth(width, titlePadding)) + ?.plus(borderWidth + titlePadding * 2) return listOf( if (expand) contentWidth.copy(min = contentWidth.max) else contentWidth, @@ -146,7 +147,8 @@ class Panel private constructor( else -> (measurement.max - borderWidth).coerceAtMost(maxContentWidth) } - val renderedContent = content.render(t, maxContentWidth).setSize(contentWidth, textAlign = LEFT) + val renderedContent = + content.render(t, maxContentWidth).setSize(contentWidth, textAlign = LEFT) val renderedTop = HorizontalRule( title ?: EmptyWidget, ThemeString.Explicit(borderType?.body?.ew ?: " "), @@ -173,7 +175,8 @@ class Panel private constructor( }) } - val lines = ArrayList(renderedContent.height + renderedTop.height + renderedBottom.height) + val lines = + ArrayList(renderedContent.height + renderedTop.height + renderedBottom.height) val b = borderType.body val vertical = listOf(Span.word(b.ns, borderStyle)) diff --git a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/widgets/Spinner.kt b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/widgets/Spinner.kt index d6c667fb6..dff33a60d 100644 --- a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/widgets/Spinner.kt +++ b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/widgets/Spinner.kt @@ -50,7 +50,12 @@ class Spinner( /** * Construct a [Spinner] from a string, where each character in the string is one frame. */ - constructor(frames: String, style: TextStyle = DEFAULT_STYLE, duration: Int = 1, initial: Int = 0) : + constructor( + frames: String, + style: TextStyle = DEFAULT_STYLE, + duration: Int = 1, + initial: Int = 0, + ) : this(frames.map { Text(style(it.toString())) }, duration, initial) private val _tick = MppAtomicInt(initial) diff --git a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/widgets/Text.kt b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/widgets/Text.kt index 6c4ba199a..ca3f30c8c 100644 --- a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/widgets/Text.kt +++ b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/widgets/Text.kt @@ -36,7 +36,14 @@ class Text internal constructor( internal fun withAlign(align: TextAlign, overflowWrap: OverflowWrap?): Text { return when { align == this.align && (overflowWrap == null || overflowWrap == this.overflowWrap) -> this - else -> Text(lines, whitespace, align, overflowWrap ?: this.overflowWrap, width, tabWidth) + else -> Text( + lines, + whitespace, + align, + overflowWrap ?: this.overflowWrap, + width, + tabWidth + ) } } @@ -45,7 +52,9 @@ class Text internal constructor( val lines = wrap(this.width ?: width, tabWidth ?: t.tabWidth, NONE, OverflowWrap.NORMAL) val max = lines.lines.maxOfOrNull { l -> l.sumOf { it.cellWidth } } ?: 0 val min = when { - whitespace.wrap -> lines.lines.maxOfOrNull { l -> l.maxOfOrNull { it.cellWidth } ?: 0 } ?: 0 + whitespace.wrap -> lines.lines.maxOfOrNull { l -> l.maxOfOrNull { it.cellWidth } ?: 0 } + ?: 0 + else -> max } return WidthRange(min, max) @@ -55,7 +64,12 @@ class Text internal constructor( return wrap(this.width ?: width, tabWidth ?: t.tabWidth, align, overflowWrap) } - private fun wrap(wrapWidth: Int, tabWidth: Int, align: TextAlign, overflowWrap: OverflowWrap): Lines { + private fun wrap( + wrapWidth: Int, + tabWidth: Int, + align: TextAlign, + overflowWrap: OverflowWrap, + ): Lines { if (wrapWidth <= 0 && overflowWrap != OverflowWrap.NORMAL) return EMPTY_LINES val lines = mutableListOf() @@ -119,6 +133,7 @@ class Text internal constructor( tabWidth - (width % tabWidth), piece.style ) else continue + else -> piece } @@ -135,12 +150,15 @@ class Text internal constructor( when (overflowWrap) { OverflowWrap.NORMAL -> { } + OverflowWrap.TRUNCATE -> { span = Span.word(span.text.take(wrapWidth), span.style) } + OverflowWrap.ELLIPSES -> { span = Span.word(span.text.take((wrapWidth - 1)) + "…", span.style) } + OverflowWrap.BREAK_WORD -> { span.text.chunked(wrapWidth).forEach { if (it.length == wrapWidth) { @@ -209,7 +227,11 @@ class Text internal constructor( if (halfExtra > 0) alignLineRight(line, halfExtra, endStyle) } - private fun justifyLine(line: MutableList, extraWidth: Int, endStyle: TextStyle): MutableList { + private fun justifyLine( + line: MutableList, + extraWidth: Int, + endStyle: TextStyle, + ): MutableList { val spaceCount = line.count { it.isWhitespace() } if (spaceCount == 0) { @@ -219,7 +241,8 @@ class Text internal constructor( val spaceSize = extraWidth / spaceCount var skipRemainder = spaceCount - extraWidth % spaceCount - val justifiedLine = ArrayList(line.size + skipRemainder + if (spaceSize > 0) spaceCount else 0) + val justifiedLine = + ArrayList(line.size + skipRemainder + if (spaceSize > 0) spaceCount else 0) for (span in line) { justifiedLine += span if (!span.isWhitespace()) continue 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 384e5fd20..041e5dae1 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 @@ -12,8 +12,7 @@ class AnimationTest { private val rec = TerminalRecorder(width = 24) private val t = Terminal(terminalInterface = rec) - @Test - @JsName("no_trailing_linebreak") + @[Test JsName("no_trailing_linebreak")] fun `no trailing linebreak`() { val a = t.textAnimation(trailingLinebreak = false) { "<$it>\n===" } a.update(1) @@ -26,8 +25,7 @@ class AnimationTest { rec.normalizedOutput() shouldBe "${moves}<2>\n===" } - @Test - @JsName("no_trailing_linebreak_single_line") + @[Test JsName("no_trailing_linebreak_single_line")] fun `no trailing linebreak single line`() { val a = t.textAnimation(trailingLinebreak = false) { "<$it>" } a.update(1) @@ -40,8 +38,7 @@ class AnimationTest { rec.normalizedOutput() shouldBe "${moves}<2>" } - @Test - @JsName("animation_size_change") + @[Test JsName("animation_size_change")] fun `animation size change`() { val a = t.textAnimation { it } a.update("1") @@ -67,8 +64,7 @@ class AnimationTest { rec.normalizedOutput() shouldBe "1\r2\n" } - @Test - @JsName("print_during_animation") + @[Test JsName("print_during_animation")] fun `print during animation`() { val a = t.textAnimation { "<$it>\n===" } a.update(1) @@ -118,8 +114,7 @@ class AnimationTest { rec.normalizedOutput() shouldBe "<4>\n===" } - @Test - @JsName("two_animations") + @[Test JsName("two_animations")] fun `two animations`() { val a = t.textAnimation { "" } val b = t.textAnimation { "" } diff --git a/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/animation/progress/BaseProgressAnimationTest.kt b/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/animation/progress/BaseProgressAnimationTest.kt index d745f294f..dfe54ee32 100644 --- a/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/animation/progress/BaseProgressAnimationTest.kt +++ b/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/animation/progress/BaseProgressAnimationTest.kt @@ -103,8 +103,7 @@ class BaseProgressAnimationTest : RenderingTest() { vt.latestOutput() shouldBe "text.txt| 0%|......| 0/200| ---.-/s|eta -:--:--" } - @Test - @JsName("task_visibility") + @[Test JsName("task_visibility")] fun `task visibility`() { val l = progressBarContextLayout(textFps = 1, animationFps = 1) { text { "Task $context" } @@ -137,8 +136,7 @@ class BaseProgressAnimationTest : RenderingTest() { vt.latestOutput() shouldBe "" } - @Test - @JsName("changing_text_cell") + @[Test JsName("changing_text_cell")] fun `changing text cell`() { val l = progressBarContextLayout( textFps = 1, animationFps = 1, alignColumns = false @@ -157,8 +155,7 @@ class BaseProgressAnimationTest : RenderingTest() { vt.normalizedOutput().visibleCrLf() shouldBe "====\n1111${moves}====\n22 ".visibleCrLf() } - @Test - @JsName("different_context_types") + @[Test JsName("different_context_types")] fun `different context types`() { val l1 = progressBarContextLayout { text { "int: $context" } } val l2 = progressBarContextLayout { text { "string: $context" } } diff --git a/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/input/SelectListAnimationTest.kt b/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/input/SelectListAnimationTest.kt index 79c8c6dbe..5e02b8971 100644 --- a/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/input/SelectListAnimationTest.kt +++ b/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/input/SelectListAnimationTest.kt @@ -24,8 +24,7 @@ class SelectListAnimationTest { private val esc = KeyboardEvent("Escape") private val x = KeyboardEvent("x") - @Test - @JsName("single_select_instructions") + @[Test JsName("single_select_instructions")] fun `single select instructions`() { val a = b.entries("a", "b") .title("title") @@ -68,8 +67,7 @@ class SelectListAnimationTest { } - @Test - @JsName("multi_select_instructions") + @[Test JsName("multi_select_instructions")] fun `multi select instructions`() { val a = b.entries("a", "b") .title("title") @@ -120,8 +118,7 @@ class SelectListAnimationTest { """ } - @Test - @JsName("cursor_movement") + @[Test JsName("cursor_movement")] fun `cursor movement`() { val a = b.entries("a", "b", "c") .createSingleSelectInputAnimation() @@ -169,8 +166,7 @@ class SelectListAnimationTest { """ } - @Test - @JsName("filtered_cursor_movement") + @[Test JsName("filtered_cursor_movement")] fun `filtered cursor movement`() { val a = b.entries("1", "ax", "2", "bx", "3", "cx", "4") .filterable(true) @@ -237,8 +233,7 @@ class SelectListAnimationTest { a.receiveEvent(enter) shouldBe InputReceiver.Status.Finished("bx") } - @Test - @JsName("filtered_to_empty") + @[Test JsName("filtered_to_empty")] fun `filtered to empty`() { val a = b.entries("a") .filterable(true) @@ -257,8 +252,7 @@ class SelectListAnimationTest { """ } - @Test - @JsName("always_show_descriptions") + @[Test JsName("always_show_descriptions")] fun `always show descriptions`() { b .addEntry("a", "adesc") @@ -273,8 +267,7 @@ class SelectListAnimationTest { """ } - @Test - @JsName("only_show_active_description") + @[Test JsName("only_show_active_description")] fun `only show active description`() { val a = b .addEntry("ax", "adesc") @@ -325,8 +318,7 @@ class SelectListAnimationTest { """ } - @Test - @JsName("filtering_multi_select") + @[Test JsName("filtering_multi_select")] fun `filtering multi select`() { val a = b.entries("ax", "b", "cx") .filterable(true) diff --git a/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/rendering/TextAlignmentTest.kt b/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/rendering/TextAlignmentTest.kt index c60b4c32d..dd1244dd7 100644 --- a/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/rendering/TextAlignmentTest.kt +++ b/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/rendering/TextAlignmentTest.kt @@ -10,71 +10,77 @@ import kotlin.test.Test class TextAlignmentTest : RenderingTest() { - @Test - @JsName("align_none") - fun `align none`() = doTest(NONE, 79, """ + @[Test JsName("align_none")] + fun `align none`() = doTest( + NONE, 79, """ ░one_word ░ ░two words ░3 whole words ░four words 4 4 ░5 5 5 5 5 - """) + """ + ) - @Test - @JsName("align_left") - fun `align left`() = doTest(LEFT, 15, """ + @[Test JsName("align_left")] + fun `align left`() = doTest( + LEFT, 15, """ ░one_word ░ ░ ░ ░two words ░ ░3 whole words ░ ░four words 4 4 ░ ░5 5 5 5 5 ░ - """) + """ + ) - @Test - @JsName("align_right") - fun `align right`() = doTest(RIGHT, 15, """ + @[Test JsName("align_right")] + fun `align right`() = doTest( + RIGHT, 15, """ ░ one_word░ ░ ░ ░ two words░ ░ 3 whole words░ ░ four words 4 4░ ░ 5 5 5 5 5░ - """) + """ + ) - @Test - @JsName("align_center") - fun `align center`() = doTest(CENTER, 15, """ + @[Test JsName("align_center")] + fun `align center`() = doTest( + CENTER, 15, """ ░ one_word ░ ░ ░ ░ two words ░ ░ 3 whole words ░ ░four words 4 4 ░ ░ 5 5 5 5 5 ░ - """) + """ + ) - @Test - @JsName("align_justify") - fun `align justify`() = doTest(JUSTIFY, 15, """ + @[Test JsName("align_justify")] + fun `align justify`() = doTest( + JUSTIFY, 15, """ ░ one_word ░ ░ ░ ░two words░ ░3 whole words░ ░four words 4 4░ ░5 5 5 5 5░ - """) + """ + ) - @Test - @JsName("align_justify_wide") - fun `align justify wide`() = doTest(JUSTIFY, 21, """ + @[Test JsName("align_justify_wide")] + fun `align justify wide`() = doTest( + JUSTIFY, 21, """ ░ one_word ░ ░ ░ ░two words░ ░3 whole words░ ░four words 4 4░ ░5 5 5 5 5░ - """) + """ + ) private fun doTest(align: TextAlign, width: Int, expected: String) { val ex = expected.trimMargin("░").lines().joinToString("\n") { (blue on white)(it) } @@ -86,10 +92,12 @@ class TextAlignmentTest : RenderingTest() { ░four words 4 4 ░5 5 5 5 5 """.trimMargin("░") - checkRender(Text( - (blue on white)(text), - whitespace = Whitespace.PRE_WRAP, - align = align, - ), ex, width = width) + checkRender( + Text( + (blue on white)(text), + whitespace = Whitespace.PRE_WRAP, + align = align, + ), ex, width = width + ) } } diff --git a/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/rendering/TextStyleOscTest.kt b/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/rendering/TextStyleOscTest.kt index 06ce2804a..73f04e61e 100644 --- a/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/rendering/TextStyleOscTest.kt +++ b/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/rendering/TextStyleOscTest.kt @@ -9,15 +9,13 @@ import kotlin.js.JsName import kotlin.test.Test class TextStyleOscTest { - @Test - @JsName("single_hyperlink") + @[Test JsName("single_hyperlink")] fun `single hyperlink`() = doTest( hyperlink("foo.com")("bar"), "<;id=1;foo.com>bar<;;>" ) - @Test - @JsName("nested_hyperlink") + @[Test JsName("nested_hyperlink")] fun `nested hyperlink`() = doTest( hyperlink("foo")("bar${hyperlink("baz")("qux")}qor"), "<;id=1;foo>bar<;id=2;baz>qux<;id=1;foo>qor<;;>" diff --git a/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/rendering/TextWhitespaceTest.kt b/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/rendering/TextWhitespaceTest.kt index c58b1295d..793dd3398 100644 --- a/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/rendering/TextWhitespaceTest.kt +++ b/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/rendering/TextWhitespaceTest.kt @@ -62,8 +62,7 @@ class TextWhitespaceTest : RenderingTest() { """ ) - @Test - @JsName("consecutive_whitespace_spans") + @[Test JsName("consecutive_whitespace_spans")] fun `consecutive whitespace spans`() { val line1 = Line(listOf("a", " ", " ").map { Span.word(it) }) val line2 = Line(listOf(" ", "b").map { Span.word(it) }) diff --git a/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/rendering/internal/CellWidthTest.kt b/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/rendering/internal/CellWidthTest.kt index b71d9850b..6b5a8b7c2 100644 --- a/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/rendering/internal/CellWidthTest.kt +++ b/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/rendering/internal/CellWidthTest.kt @@ -40,10 +40,22 @@ internal class CellWidthTest { row("en\u0303e", 3), row("👍🏿", 2), row("🇩🇪", 2), - row("\uD83D\uDC68\uD83C\uDFFE\u200D\uD83E\uDDB1", 2), // MAN, FITZPATRICK TYPE-5, ZWJ, CURLY HAIR - row("\uD83D\uDC69\u200D\uD83D\uDC67", 2), // Emoji_ZWJ_Sequence ; family: woman, girl (👩‍👧) - row("\uD83D\uDC69\u200D\uD83D\uDC67\u200D\uD83D\uDC66", 2), //Emoji_ZWJ_Sequence ; family: woman, girl, boy (👩‍👧‍👦) - row("\uD83D\uDC69\u200D\uD83D\uDC69\u200D\uD83D\uDC66\u200D\uD83D\uDC66", 2), // Emoji_ZWJ_Sequence ; family: woman, woman, boy, boy (👩‍👩‍👦‍👦) + row( + "\uD83D\uDC68\uD83C\uDFFE\u200D\uD83E\uDDB1", + 2 + ), // MAN, FITZPATRICK TYPE-5, ZWJ, CURLY HAIR + row( + "\uD83D\uDC69\u200D\uD83D\uDC67", + 2 + ), // Emoji_ZWJ_Sequence ; family: woman, girl (👩‍👧) + row( + "\uD83D\uDC69\u200D\uD83D\uDC67\u200D\uD83D\uDC66", + 2 + ), //Emoji_ZWJ_Sequence ; family: woman, girl, boy (👩‍👧‍👦) + row( + "\uD83D\uDC69\u200D\uD83D\uDC69\u200D\uD83D\uDC66\u200D\uD83D\uDC66", + 2 + ), // Emoji_ZWJ_Sequence ; family: woman, woman, boy, boy (👩‍👩‍👦‍👦) ) { str, width -> stringCellWidth(str) shouldBe width diff --git a/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/table/LinearLayoutTest.kt b/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/table/LinearLayoutTest.kt index 3bc74873a..7fc2bd37f 100644 --- a/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/table/LinearLayoutTest.kt +++ b/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/table/LinearLayoutTest.kt @@ -114,8 +114,7 @@ class LinearLayoutTest : RenderingTest() { """ ) - @Test - @JsName("verticalLayout_justify") + @[Test JsName("verticalLayout_justify")] fun `verticalLayout justify`() = checkRender( verticalLayout { align = TextAlign.JUSTIFY @@ -132,8 +131,7 @@ class LinearLayoutTest : RenderingTest() { """ ) - @Test - @JsName("verticalLayout_left_align") + @[Test JsName("verticalLayout_left_align")] fun `verticalLayout left align`() { class W(val w: Int) : Widget { override fun measure(t: Terminal, width: Int): WidthRange { @@ -169,8 +167,7 @@ class LinearLayoutTest : RenderingTest() { ) - @Test - @JsName("nesting_horizontalLayouts_in_verticalLayouts") + @[Test JsName("nesting_horizontalLayouts_in_verticalLayouts")] fun `nesting horizontalLayouts in verticalLayouts`() = checkRender( verticalLayout { cell(horizontalLayout { cells("1", "1") }) @@ -182,8 +179,7 @@ class LinearLayoutTest : RenderingTest() { """ ) - @Test - @JsName("nesting_horizontalLayouts_in_verticalLayouts_with_fixed_column_width") + @[Test JsName("nesting_horizontalLayouts_in_verticalLayouts_with_fixed_column_width")] fun `nesting horizontalLayouts in verticalLayouts with fixed column width`() = checkRender( verticalLayout { cell(horizontalLayout { diff --git a/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/table/TableAlignmentTest.kt b/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/table/TableAlignmentTest.kt index a022eb506..8ec6062de 100644 --- a/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/table/TableAlignmentTest.kt +++ b/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/table/TableAlignmentTest.kt @@ -11,9 +11,9 @@ import kotlin.js.JsName import kotlin.test.Test class TableAlignmentTest : RenderingTest() { - @Test - @JsName("top_none") - fun `top none`() = doTextTest(TOP, NONE, + @[Test JsName("top_none")] + fun `top none`() = doTextTest( + TOP, NONE, """ ┌─────────┬───┐ │ 1 2 │ · │ @@ -25,9 +25,9 @@ class TableAlignmentTest : RenderingTest() { """ ) - @Test - @JsName("top_left") - fun `top left`() = doTextTest(TOP, LEFT, + @[Test JsName("top_left")] + fun `top left`() = doTextTest( + TOP, LEFT, """ ┌─────────┬───┐ │ 1 2 │ · │ @@ -39,9 +39,9 @@ class TableAlignmentTest : RenderingTest() { """ ) - @Test - @JsName("top_center") - fun `top center`() = doTextTest(TOP, CENTER, + @[Test JsName("top_center")] + fun `top center`() = doTextTest( + TOP, CENTER, """ ┌─────────┬───┐ │ 1 2 │ · │ @@ -53,9 +53,9 @@ class TableAlignmentTest : RenderingTest() { """ ) - @Test - @JsName("top_justify") - fun `top justify`() = doTextTest(TOP, JUSTIFY, + @[Test JsName("top_justify")] + fun `top justify`() = doTextTest( + TOP, JUSTIFY, """ ┌─────────┬───┐ │ 1 2 │ · │ @@ -67,9 +67,9 @@ class TableAlignmentTest : RenderingTest() { """ ) - @Test - @JsName("top_right") - fun `top right`() = doTextTest(TOP, RIGHT, + @[Test JsName("top_right")] + fun `top right`() = doTextTest( + TOP, RIGHT, """ ┌─────────┬───┐ │ 1 2 │ · │ @@ -81,9 +81,9 @@ class TableAlignmentTest : RenderingTest() { """ ) - @Test - @JsName("middle_none") - fun `middle none`() = doTextTest(MIDDLE, NONE, + @[Test JsName("middle_none")] + fun `middle none`() = doTextTest( + MIDDLE, NONE, """ ┌─────────┬───┐ │ │ · │ @@ -95,9 +95,9 @@ class TableAlignmentTest : RenderingTest() { """ ) - @Test - @JsName("middle_left") - fun `middle left`() = doTextTest(MIDDLE, LEFT, + @[Test JsName("middle_left")] + fun `middle left`() = doTextTest( + MIDDLE, LEFT, """ ┌─────────┬───┐ │ │ · │ @@ -109,9 +109,9 @@ class TableAlignmentTest : RenderingTest() { """ ) - @Test - @JsName("middle_center") - fun `middle center`() = doTextTest(MIDDLE, CENTER, + @[Test JsName("middle_center")] + fun `middle center`() = doTextTest( + MIDDLE, CENTER, """ ┌─────────┬───┐ │ │ · │ @@ -123,9 +123,9 @@ class TableAlignmentTest : RenderingTest() { """ ) - @Test - @JsName("middle_justify") - fun `middle justify`() = doTextTest(MIDDLE, JUSTIFY, + @[Test JsName("middle_justify")] + fun `middle justify`() = doTextTest( + MIDDLE, JUSTIFY, """ ┌─────────┬───┐ │ │ · │ @@ -137,9 +137,9 @@ class TableAlignmentTest : RenderingTest() { """ ) - @Test - @JsName("middle_right") - fun `middle right`() = doTextTest(MIDDLE, RIGHT, + @[Test JsName("middle_right")] + fun `middle right`() = doTextTest( + MIDDLE, RIGHT, """ ┌─────────┬───┐ │ │ · │ @@ -151,9 +151,9 @@ class TableAlignmentTest : RenderingTest() { """ ) - @Test - @JsName("bottom_none") - fun `bottom none`() = doTextTest(BOTTOM, NONE, + @[Test JsName("bottom_none")] + fun `bottom none`() = doTextTest( + BOTTOM, NONE, """ ┌─────────┬───┐ │ │ · │ @@ -165,9 +165,9 @@ class TableAlignmentTest : RenderingTest() { """ ) - @Test - @JsName("bottom_left") - fun `bottom left`() = doTextTest(BOTTOM, LEFT, + @[Test JsName("bottom_left")] + fun `bottom left`() = doTextTest( + BOTTOM, LEFT, """ ┌─────────┬───┐ │ │ · │ @@ -179,9 +179,9 @@ class TableAlignmentTest : RenderingTest() { """ ) - @Test - @JsName("bottom_center") - fun `bottom center`() = doTextTest(BOTTOM, CENTER, + @[Test JsName("bottom_center")] + fun `bottom center`() = doTextTest( + BOTTOM, CENTER, """ ┌─────────┬───┐ │ │ · │ @@ -193,9 +193,9 @@ class TableAlignmentTest : RenderingTest() { """ ) - @Test - @JsName("bottom_justify") - fun `bottom justify`() = doTextTest(BOTTOM, JUSTIFY, + @[Test JsName("bottom_justify")] + fun `bottom justify`() = doTextTest( + BOTTOM, JUSTIFY, """ ┌─────────┬───┐ │ │ · │ @@ -207,9 +207,9 @@ class TableAlignmentTest : RenderingTest() { """ ) - @Test - @JsName("bottom_right") - fun `bottom right`() = doTextTest(BOTTOM, RIGHT, + @[Test JsName("bottom_right")] + fun `bottom right`() = doTextTest( + BOTTOM, RIGHT, """ ┌─────────┬───┐ │ │ · │ @@ -221,9 +221,9 @@ class TableAlignmentTest : RenderingTest() { """ ) - @Test - @JsName("top_none_widget") - fun `top none widget`() = doWidgetTest(TOP, NONE, + @[Test JsName("top_none_widget")] + fun `top none widget`() = doWidgetTest( + TOP, NONE, """ ┌──────┬───┐ │ × │ · │ @@ -235,9 +235,9 @@ class TableAlignmentTest : RenderingTest() { """ ) - @Test - @JsName("top_left_widget") - fun `top left widget`() = doWidgetTest(TOP, LEFT, + @[Test JsName("top_left_widget")] + fun `top left widget`() = doWidgetTest( + TOP, LEFT, """ ┌──────┬───┐ │ × │ · │ @@ -249,9 +249,9 @@ class TableAlignmentTest : RenderingTest() { """ ) - @Test - @JsName("top_center_widget") - fun `top center widget`() = doWidgetTest(TOP, CENTER, + @[Test JsName("top_center_widget")] + fun `top center widget`() = doWidgetTest( + TOP, CENTER, """ ┌──────┬───┐ │ × │ · │ @@ -263,9 +263,9 @@ class TableAlignmentTest : RenderingTest() { """ ) - @Test - @JsName("top_justify_widget") - fun `top justify widget`() = doWidgetTest(TOP, JUSTIFY, + @[Test JsName("top_justify_widget")] + fun `top justify widget`() = doWidgetTest( + TOP, JUSTIFY, """ ┌──────┬───┐ │ × │ · │ @@ -277,9 +277,9 @@ class TableAlignmentTest : RenderingTest() { """ ) - @Test - @JsName("top_right_widget") - fun `top right widget`() = doWidgetTest(TOP, RIGHT, + @[Test JsName("top_right_widget")] + fun `top right widget`() = doWidgetTest( + TOP, RIGHT, """ ┌──────┬───┐ │ × │ · │ @@ -291,9 +291,9 @@ class TableAlignmentTest : RenderingTest() { """ ) - @Test - @JsName("middle_none_widget") - fun `middle none widget`() = doWidgetTest(MIDDLE, NONE, + @[Test JsName("middle_none_widget")] + fun `middle none widget`() = doWidgetTest( + MIDDLE, NONE, """ ┌──────┬───┐ │ │ · │ @@ -305,9 +305,9 @@ class TableAlignmentTest : RenderingTest() { """ ) - @Test - @JsName("middle_left_widget") - fun `middle left widget`() = doWidgetTest(MIDDLE, LEFT, + @[Test JsName("middle_left_widget")] + fun `middle left widget`() = doWidgetTest( + MIDDLE, LEFT, """ ┌──────┬───┐ │ │ · │ @@ -319,9 +319,9 @@ class TableAlignmentTest : RenderingTest() { """ ) - @Test - @JsName("middle_center_widget") - fun `middle center widget`() = doWidgetTest(MIDDLE, CENTER, + @[Test JsName("middle_center_widget")] + fun `middle center widget`() = doWidgetTest( + MIDDLE, CENTER, """ ┌──────┬───┐ │ │ · │ @@ -333,9 +333,9 @@ class TableAlignmentTest : RenderingTest() { """ ) - @Test - @JsName("middle_justify_widget") - fun `middle justify widget`() = doWidgetTest(MIDDLE, JUSTIFY, + @[Test JsName("middle_justify_widget")] + fun `middle justify widget`() = doWidgetTest( + MIDDLE, JUSTIFY, """ ┌──────┬───┐ │ │ · │ @@ -347,9 +347,9 @@ class TableAlignmentTest : RenderingTest() { """ ) - @Test - @JsName("middle_right_widget") - fun `middle right widget`() = doWidgetTest(MIDDLE, RIGHT, + @[Test JsName("middle_right_widget")] + fun `middle right widget`() = doWidgetTest( + MIDDLE, RIGHT, """ ┌──────┬───┐ │ │ · │ @@ -361,9 +361,9 @@ class TableAlignmentTest : RenderingTest() { """ ) - @Test - @JsName("bottom_none_widget") - fun `bottom none widget`() = doWidgetTest(BOTTOM, NONE, + @[Test JsName("bottom_none_widget")] + fun `bottom none widget`() = doWidgetTest( + BOTTOM, NONE, """ ┌──────┬───┐ │ │ · │ @@ -375,9 +375,9 @@ class TableAlignmentTest : RenderingTest() { """ ) - @Test - @JsName("bottom_left_widget") - fun `bottom left widget`() = doWidgetTest(BOTTOM, LEFT, + @[Test JsName("bottom_left_widget")] + fun `bottom left widget`() = doWidgetTest( + BOTTOM, LEFT, """ ┌──────┬───┐ │ │ · │ @@ -389,9 +389,9 @@ class TableAlignmentTest : RenderingTest() { """ ) - @Test - @JsName("bottom_center_widget") - fun `bottom center widget`() = doWidgetTest(BOTTOM, CENTER, + @[Test JsName("bottom_center_widget")] + fun `bottom center widget`() = doWidgetTest( + BOTTOM, CENTER, """ ┌──────┬───┐ │ │ · │ @@ -403,9 +403,9 @@ class TableAlignmentTest : RenderingTest() { """ ) - @Test - @JsName("bottom_justify_widget") - fun `bottom justify widget`() = doWidgetTest(BOTTOM, JUSTIFY, + @[Test JsName("bottom_justify_widget")] + fun `bottom justify widget`() = doWidgetTest( + BOTTOM, JUSTIFY, """ ┌──────┬───┐ │ │ · │ @@ -417,9 +417,9 @@ class TableAlignmentTest : RenderingTest() { """ ) - @Test - @JsName("bottom_right_widget") - fun `bottom right widget`() = doWidgetTest(BOTTOM, RIGHT, + @[Test JsName("bottom_right_widget")] + fun `bottom right widget`() = doWidgetTest( + BOTTOM, RIGHT, """ ┌──────┬───┐ │ │ · │ diff --git a/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/table/TableBorderStyleTest.kt b/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/table/TableBorderStyleTest.kt index 8e8a0c7d5..a1b96eed1 100644 --- a/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/table/TableBorderStyleTest.kt +++ b/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/table/TableBorderStyleTest.kt @@ -8,7 +8,8 @@ import kotlin.test.Test class TableBorderStyleTest : RenderingTest() { @Test - fun square() = doTest(BorderType.SQUARE, """ + fun square() = doTest( + BorderType.SQUARE, """ ░┌───┬───┐ ┌───┬───┐ ╷ ╷ ╷ ╷░ ░│1 │2 │3 4 5 6 7 │8 │9 │10 │11 │12 │13 │░ ░├───┼───┤ ╶───────╴ └───┴───┘ ╵ ╵ ╵ ╵░ @@ -30,10 +31,12 @@ class TableBorderStyleTest : RenderingTest() { ░├───┼───┤ ╶───────╴ └───┴───┘ ╵ ╵ ╵ ╵░ ░│118│119│120 121 122 123 124 125 126 127 128 129 130 ░ ░└───┴───┘ ░ - """) + """ + ) @Test - fun rounded() = doTest(BorderType.ROUNDED, """ + fun rounded() = doTest( + BorderType.ROUNDED, """ ░╭───┬───╮ ╭───┬───╮ ╷ ╷ ╷ ╷░ ░│1 │2 │3 4 5 6 7 │8 │9 │10 │11 │12 │13 │░ ░├───┼───┤ ╶───────╴ ╰───┴───╯ ╵ ╵ ╵ ╵░ @@ -55,10 +58,12 @@ class TableBorderStyleTest : RenderingTest() { ░├───┼───┤ ╶───────╴ ╰───┴───╯ ╵ ╵ ╵ ╵░ ░│118│119│120 121 122 123 124 125 126 127 128 129 130 ░ ░╰───┴───╯ ░ - """) + """ + ) @Test - fun heavy() = doTest(BorderType.HEAVY, """ + fun heavy() = doTest( + BorderType.HEAVY, """ ░┏━━━┳━━━┓ ┏━━━┳━━━┓ ╻ ╻ ╻ ╻░ ░┃1 ┃2 ┃3 4 5 6 7 ┃8 ┃9 ┃10 ┃11 ┃12 ┃13 ┃░ ░┣━━━╋━━━┫ ╺━━━━━━━╸ ┗━━━┻━━━┛ ╹ ╹ ╹ ╹░ @@ -80,10 +85,12 @@ class TableBorderStyleTest : RenderingTest() { ░┣━━━╋━━━┫ ╺━━━━━━━╸ ┗━━━┻━━━┛ ╹ ╹ ╹ ╹░ ░┃118┃119┃120 121 122 123 124 125 126 127 128 129 130 ░ ░┗━━━┻━━━┛ ░ - """) + """ + ) @Test - fun double() = doTest(BorderType.DOUBLE, """ + fun double() = doTest( + BorderType.DOUBLE, """ ░╔═══╦═══╗ ╔═══╦═══╗ ░ ░║1 ║2 ║3 4 5 6 7 ║8 ║9 ║10 ║11 ║12 ║13 ║░ ░╠═══╬═══╣ ═══════ ╚═══╩═══╝ ░ @@ -105,10 +112,12 @@ class TableBorderStyleTest : RenderingTest() { ░╠═══╬═══╣ ═══════ ╚═══╩═══╝ ░ ░║118║119║120 121 122 123 124 125 126 127 128 129 130 ░ ░╚═══╩═══╝ ░ - """) + """ + ) @Test - fun heavy_head_foot() = doTest(BorderType.HEAVY_HEAD_FOOT, """ + fun heavy_head_foot() = doTest( + BorderType.HEAVY_HEAD_FOOT, """ ░┏━━━┳━━━┓ ┏━━━┳━━━┓ ╻ ╻ ╻ ╻░ ░┃1 ┃2 ┃3 4 5 6 7 ┃8 ┃9 ┃10 ┃11 ┃12 ┃13 ┃░ ░┣━━━╋━━━┫ ╺━━━━━━━╸ ┗━━━┻━━━┛ ╹ ╹ ╹ ╹░ @@ -130,10 +139,12 @@ class TableBorderStyleTest : RenderingTest() { ░┣━━━╋━━━┫ ╺━━━━━━━╸ ┗━━━┻━━━┛ ╹ ╹ ╹ ╹░ ░┃118┃119┃120 121 122 123 124 125 126 127 128 129 130 ░ ░┗━━━┻━━━┛ ░ - """) + """ + ) @Test - fun square_double_head_separator() = doTest(BorderType.SQUARE_DOUBLE_SECTION_SEPARATOR, """ + fun square_double_head_separator() = doTest( + BorderType.SQUARE_DOUBLE_SECTION_SEPARATOR, """ ░┌───┬───┐ ┌───┬───┐ ╷ ╷ ╷ ╷░ ░│1 │2 │3 4 5 6 7 │8 │9 │10 │11 │12 │13 │░ ░├───┼───┤ ╶───────╴ └───┴───┘ ╵ ╵ ╵ ╵░ @@ -155,10 +166,12 @@ class TableBorderStyleTest : RenderingTest() { ░├───┼───┤ ╶───────╴ └───┴───┘ ╵ ╵ ╵ ╵░ ░│118│119│120 121 122 123 124 125 126 127 128 129 130 ░ ░└───┴───┘ ░ - """) + """ + ) @Test - fun ascii() = doTest(BorderType.ASCII, """ + fun ascii() = doTest( + BorderType.ASCII, """ ░+---+---+ +---+---+ ░ ░|1 |2 |3 4 5 6 7 |8 |9 |10 |11 |12 |13 |░ ░+---+---+ ------- +---+---+ ░ @@ -180,10 +193,12 @@ class TableBorderStyleTest : RenderingTest() { ░+---+---+ ------- +---+---+ ░ ░|118|119|120 121 122 123 124 125 126 127 128 129 130 ░ ░+---+---+ ░ - """) + """ + ) @Test - fun ascii_double_section_separator() = doTest(BorderType.ASCII_DOUBLE_SECTION_SEPARATOR, """ + fun ascii_double_section_separator() = doTest( + BorderType.ASCII_DOUBLE_SECTION_SEPARATOR, """ ░+---+---+ +---+---+ ░ ░|1 |2 |3 4 5 6 7 |8 |9 |10 |11 |12 |13 |░ ░+---+---+ ------- +---+---+ ░ @@ -205,10 +220,12 @@ class TableBorderStyleTest : RenderingTest() { ░+---+---+ ------- +---+---+ ░ ░|118|119|120 121 122 123 124 125 126 127 128 129 130 ░ ░+---+---+ ░ - """) + """ + ) @Test - fun blank() = doTest(BorderType.BLANK, """ + fun blank() = doTest( + BorderType.BLANK, """ ░ ░ ░ 1 2 3 4 5 6 7 8 9 10 11 12 13 ░ ░ ░ @@ -230,7 +247,8 @@ class TableBorderStyleTest : RenderingTest() { ░ ░ ░ 118 119 120 121 122 123 124 125 126 127 128 129 130 ░ ░ ░ - """.trimMargin()) + """.trimMargin() + ) private fun doTest(borderType: BorderType, expected: String) { checkRender(table { @@ -286,6 +304,6 @@ class TableBorderStyleTest : RenderingTest() { transitionRow(false) sectionRows() } - }, expected) + }, expected) } } diff --git a/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/table/TableBorderTest.kt b/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/table/TableBorderTest.kt index 25ac5a3c7..ea8af46f7 100644 --- a/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/table/TableBorderTest.kt +++ b/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/table/TableBorderTest.kt @@ -7,14 +7,16 @@ import kotlin.test.Test @Suppress("TestFunctionName") class TableBorderTest : RenderingTest() { @Test - fun NONE() = doTest(NONE, + fun NONE() = doTest( + NONE, """ ░ × """ ) @Test - fun BOTTOM() = doTest(BOTTOM, + fun BOTTOM() = doTest( + BOTTOM, """ ░ × ░─── @@ -22,14 +24,16 @@ class TableBorderTest : RenderingTest() { ) @Test - fun RIGHT() = doTest(RIGHT, + fun RIGHT() = doTest( + RIGHT, """ ░ × │ """ ) @Test - fun BOTTOM_RIGHT() = doTest(BOTTOM_RIGHT, + fun BOTTOM_RIGHT() = doTest( + BOTTOM_RIGHT, """ ░ × │ ░───┘ @@ -37,7 +41,8 @@ class TableBorderTest : RenderingTest() { ) @Test - fun TOP() = doTest(TOP, + fun TOP() = doTest( + TOP, """ ░─── ░ × @@ -45,7 +50,8 @@ class TableBorderTest : RenderingTest() { ) @Test - fun TOP_BOTTOM() = doTest(TOP_BOTTOM, + fun TOP_BOTTOM() = doTest( + TOP_BOTTOM, """ ░─── ░ × @@ -54,7 +60,8 @@ class TableBorderTest : RenderingTest() { ) @Test - fun TOP_RIGHT() = doTest(TOP_RIGHT, + fun TOP_RIGHT() = doTest( + TOP_RIGHT, """ ░───┐ ░ × │ @@ -62,7 +69,8 @@ class TableBorderTest : RenderingTest() { ) @Test - fun TOP_RIGHT_BOTTOM() = doTest(TOP_RIGHT_BOTTOM, + fun TOP_RIGHT_BOTTOM() = doTest( + TOP_RIGHT_BOTTOM, """ ░───┐ ░ × │ @@ -71,14 +79,16 @@ class TableBorderTest : RenderingTest() { ) @Test - fun LEFT() = doTest(LEFT, + fun LEFT() = doTest( + LEFT, """ ░│ × """ ) @Test - fun LEFT_BOTTOM() = doTest(LEFT_BOTTOM, + fun LEFT_BOTTOM() = doTest( + LEFT_BOTTOM, """ ░│ × ░└─── @@ -86,14 +96,16 @@ class TableBorderTest : RenderingTest() { ) @Test - fun LEFT_RIGHT() = doTest(LEFT_RIGHT, + fun LEFT_RIGHT() = doTest( + LEFT_RIGHT, """ ░│ × │ """ ) @Test - fun LEFT_RIGHT_BOTTOM() = doTest(LEFT_RIGHT_BOTTOM, + fun LEFT_RIGHT_BOTTOM() = doTest( + LEFT_RIGHT_BOTTOM, """ ░│ × │ ░└───┘ @@ -101,7 +113,8 @@ class TableBorderTest : RenderingTest() { ) @Test - fun LEFT_TOP() = doTest(LEFT_TOP, + fun LEFT_TOP() = doTest( + LEFT_TOP, """ ░┌─── ░│ × @@ -109,7 +122,8 @@ class TableBorderTest : RenderingTest() { ) @Test - fun LEFT_TOP_BOTTOM() = doTest(LEFT_TOP_BOTTOM, + fun LEFT_TOP_BOTTOM() = doTest( + LEFT_TOP_BOTTOM, """ ░┌─── ░│ × @@ -118,7 +132,8 @@ class TableBorderTest : RenderingTest() { ) @Test - fun LEFT_TOP_RIGHT() = doTest(LEFT_TOP_RIGHT, + fun LEFT_TOP_RIGHT() = doTest( + LEFT_TOP_RIGHT, """ ░┌───┐ ░│ × │ @@ -126,7 +141,8 @@ class TableBorderTest : RenderingTest() { ) @Test - fun ALL() = doTest(ALL, + fun ALL() = doTest( + ALL, """ ░┌───┐ ░│ × │ diff --git a/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/table/TableColumnWidthTest.kt b/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/table/TableColumnWidthTest.kt index f817e718f..a675332e3 100644 --- a/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/table/TableColumnWidthTest.kt +++ b/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/table/TableColumnWidthTest.kt @@ -11,36 +11,31 @@ import kotlin.js.JsName import kotlin.test.Test class TableColumnWidthTest : RenderingTest() { - @Test - @JsName("exact_min") + @[Test JsName("exact_min")] fun `exact min`() = doTest( 18, "|11 |22 foo|33|44|" ) - @Test - @JsName("expand_flex") + @[Test JsName("expand_flex")] fun `expand flex`() = doTest( 26, "|11 |22 foo|33 |44 |" ) - @Test - @JsName("expand_flex_partial_remainder") + @[Test JsName("expand_flex_partial_remainder")] fun `expand flex partial remainder`() = doTest( 27, "|11 |22 foo|33 |44 |" ) - @Test - @JsName("expand_flex_equal_remainder") + @[Test JsName("expand_flex_equal_remainder")] fun `expand flex equal remainder`() = doTest( 28, "|11 |22 foo|33 |44 |" ) - @Test - @JsName("shrink_auto_partial") + @[Test JsName("shrink_auto_partial")] fun `shrink auto partial`() = doTest( 16, """ @@ -49,8 +44,7 @@ class TableColumnWidthTest : RenderingTest() { """ ) - @Test - @JsName("shrink_auto_max") + @[Test JsName("shrink_auto_max")] fun `shrink auto max`() = doTest( 15, """ @@ -59,8 +53,7 @@ class TableColumnWidthTest : RenderingTest() { """ ) - @Test - @JsName("shrink_flex_past_min") + @[Test JsName("shrink_flex_past_min")] fun `shrink flex past min`() = doTest( 13, """ @@ -69,8 +62,7 @@ class TableColumnWidthTest : RenderingTest() { """ ) - @Test - @JsName("shrink_flex_completely") + @[Test JsName("shrink_flex_completely")] fun `shrink flex completely`() = doTest( 11, """ @@ -79,8 +71,7 @@ class TableColumnWidthTest : RenderingTest() { """ ) - @Test - @JsName("shrink_flex_and_partial_auto") + @[Test JsName("shrink_flex_and_partial_auto")] fun `shrink flex and partial auto`() = doTest( 9, """ @@ -89,8 +80,7 @@ class TableColumnWidthTest : RenderingTest() { """ ) - @Test - @JsName("shrink_auto_completely") + @[Test JsName("shrink_auto_completely")] fun `shrink auto completely`() = doTest( 8, """ @@ -98,8 +88,7 @@ class TableColumnWidthTest : RenderingTest() { """ ) - @Test - @JsName("shrink_fixed_partial") + @[Test JsName("shrink_fixed_partial")] fun `shrink fixed partial`() = doTest( 6, """ @@ -107,15 +96,13 @@ class TableColumnWidthTest : RenderingTest() { """ ) - @Test - @JsName("shrink_fixed_completely") + @[Test JsName("shrink_fixed_completely")] fun `shrink fixed completely`() = doTest( 0, "|||||" ) - @Test - @JsName("shrink_fixed_completely_with_row_borders") + @[Test JsName("shrink_fixed_completely_with_row_borders")] fun `shrink fixed completely with row borders`() = doTest( 0, """ @@ -143,22 +130,19 @@ class TableColumnWidthTest : RenderingTest() { }, expected.trimIndent(), width = tableWidth, trimMargin = false) } - @Test - @JsName("custom_priority_exact") + @[Test JsName("custom_priority_exact")] fun `custom priority exact`() = doCustomTest( 18, "|11 |22 foo|33|44|" ) - @Test - @JsName("custom_priority_expand") + @[Test JsName("custom_priority_expand")] fun `custom priority expand`() = doCustomTest( 20, "|11 |22 foo|33|44 |" ) - @Test - @JsName("custom_priority_shrink") + @[Test JsName("custom_priority_shrink")] fun `custom priority shrink`() = doCustomTest( 9, """ @@ -167,8 +151,7 @@ class TableColumnWidthTest : RenderingTest() { """ ) - @Test - @JsName("custom_priority_shrink_min") + @[Test JsName("custom_priority_shrink_min")] fun `custom priority shrink min`() = doCustomTest( 6, """ diff --git a/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/table/TableCsvTest.kt b/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/table/TableCsvTest.kt index ddef746b1..291e28abc 100644 --- a/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/table/TableCsvTest.kt +++ b/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/table/TableCsvTest.kt @@ -14,14 +14,12 @@ class TableCsvTest { return table { body { rowFrom(row) } } } - @Test - @JsName("empty_table") + @[Test JsName("empty_table")] fun `empty table`() { t(emptyList()).contentToCsv() shouldBe "\n" } - @Test - @JsName("single_cell_table") + @[Test JsName("single_cell_table")] fun `single cell table`() { t(listOf(1)).contentToCsv() shouldBe "1\n" } @@ -42,8 +40,7 @@ class TableCsvTest { ) shouldBe expected + "\n" } - @Test - @JsName("escape_error") + @[Test JsName("escape_error")] fun `escape error`() { shouldThrow { t(listOf("a", 1, "p,\"q\"")).contentToCsv( @@ -63,16 +60,14 @@ class TableCsvTest { t(row).contentToCsv(quoting = quoting) shouldBe expected + "\n" } - @Test - @JsName("quoting_none") + @[Test JsName("quoting_none")] fun `quoting none`() { shouldThrow { t(listOf("a", 1, "p,q")).contentToCsv(quoting = CsvQuoting.NONE) } } - @Test - @JsName("column_span") + @[Test JsName("column_span")] fun `column span`() { table { body { @@ -89,8 +84,7 @@ class TableCsvTest { """.trimMargin() } - @Test - @JsName("row_span") + @[Test JsName("row_span")] fun `row span`() { table { body { diff --git a/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/table/TableTest.kt b/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/table/TableTest.kt index 7f3d194f0..6555e16bb 100644 --- a/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/table/TableTest.kt +++ b/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/table/TableTest.kt @@ -15,8 +15,7 @@ import kotlin.js.JsName import kotlin.test.Test class TableTest : RenderingTest() { - @Test - @JsName("empty_table") + @[Test JsName("empty_table")] fun `empty table`() = doBodyTest( """ ░┌┐ @@ -25,8 +24,7 @@ class TableTest : RenderingTest() { """ ) {} - @Test - @JsName("empty_cell") + @[Test JsName("empty_cell")] fun `empty cell`() = doBodyTest( """ ░┌┐ @@ -38,8 +36,7 @@ class TableTest : RenderingTest() { row("") } - @Test - @JsName("border_top") + @[Test JsName("border_top")] fun `border top`() = doBodyTest( """ ░─── @@ -53,8 +50,7 @@ class TableTest : RenderingTest() { } } - @Test - @JsName("border_right") + @[Test JsName("border_right")] fun `border right`() = doBodyTest( """ ░ 1 │ @@ -67,8 +63,7 @@ class TableTest : RenderingTest() { } } - @Test - @JsName("border_bottom") + @[Test JsName("border_bottom")] fun `border bottom`() = doBodyTest( """ ░ 1 ░ @@ -82,8 +77,7 @@ class TableTest : RenderingTest() { } } - @Test - @JsName("border_left") + @[Test JsName("border_left")] fun `border left`() = doBodyTest( """ ░│ 1 ░ @@ -96,8 +90,7 @@ class TableTest : RenderingTest() { } } - @Test - @JsName("border_top_with_corners") + @[Test JsName("border_top_with_corners")] fun `border top with corners`() = doBodyTest( """ ░╶───╴ @@ -115,8 +108,7 @@ class TableTest : RenderingTest() { row(2) } - @Test - @JsName("border_right_with_corners") + @[Test JsName("border_right_with_corners")] fun `border right with corners`() = doBodyTest( """ ░┌───┐ ╷ @@ -132,8 +124,7 @@ class TableTest : RenderingTest() { } } - @Test - @JsName("border_bottom_with_corners") + @[Test JsName("border_bottom_with_corners")] fun `border bottom with corners`() = doBodyTest( """ ░┌───┐ @@ -151,8 +142,7 @@ class TableTest : RenderingTest() { } } - @Test - @JsName("border_left_with_corners") + @[Test JsName("border_left_with_corners")] fun `border left with corners`() = doBodyTest( """ ░╷ ┌───┐ @@ -168,8 +158,7 @@ class TableTest : RenderingTest() { } } - @Test - @JsName("inside_borders") + @[Test JsName("inside_borders")] fun `inside borders`() = doBodyTest( """ ░ 1 │ 2 ░ @@ -196,8 +185,7 @@ class TableTest : RenderingTest() { } - @Test - @JsName("empty_row") + @[Test JsName("empty_row")] fun `empty row`() = doBodyTest( """ ░┌───┐ @@ -214,8 +202,7 @@ class TableTest : RenderingTest() { row(2) } - @Test - @JsName("non_rectangular_table") + @[Test JsName("non_rectangular_table")] fun `non-rectangular table`() = doBodyTest( """ ░┌───┐ ░ @@ -235,8 +222,7 @@ class TableTest : RenderingTest() { row(5) } - @Test - @JsName("preformatted_text_content") + @[Test JsName("preformatted_text_content")] fun `preformatted text content`() = doBodyTest( """ ░┌────────────────┬─┐ @@ -262,8 +248,7 @@ class TableTest : RenderingTest() { row(3, 4) } - @Test - @JsName("wide_unicode_characters") + @[Test JsName("wide_unicode_characters")] fun `wide unicode characters`() = doBodyTest( """ ░┌──────────┐ @@ -284,8 +269,7 @@ class TableTest : RenderingTest() { row("1234") } - @Test - @JsName("striped_row_styles") + @[Test JsName("striped_row_styles")] fun `striped row styles`() = doBodyTest( """ ░┌─────┐ @@ -307,8 +291,7 @@ class TableTest : RenderingTest() { row("row 4") } - @Test - @JsName("row_and_column_span_no_borders") + @[Test JsName("row_and_column_span_no_borders")] fun `row and column span no borders`() = doBodyTest( """ ░span1 @@ -329,8 +312,7 @@ class TableTest : RenderingTest() { row(3, 4, 5) } - @Test - @JsName("row_and_column_span") + @[Test JsName("row_and_column_span")] fun `row and column span`() = doBodyTest( """ ░┌───────────┬───┐ @@ -358,8 +340,7 @@ class TableTest : RenderingTest() { row(4, 5, 6, 7) } - @Test - @JsName("nested_tables") + @[Test JsName("nested_tables")] fun `nested tables`() = doBodyTest( """ ░┌───────────┬───┐ @@ -387,8 +368,7 @@ class TableTest : RenderingTest() { } - @Test - @JsName("outer_border_none") + @[Test JsName("outer_border_none")] fun `outer border none`() = doTest( """ ░ 1 │ 2 │ 3 @@ -411,8 +391,7 @@ class TableTest : RenderingTest() { } } - @Test - @JsName("border_outer_all_inner_left_right") + @[Test JsName("border_outer_all_inner_left_right")] fun `border outer all inner left right`() = doTest( """ ░┌───┬───┬───┐ @@ -432,8 +411,7 @@ class TableTest : RenderingTest() { } } - @Test - @JsName("border_outer_all_inner_none") + @[Test JsName("border_outer_all_inner_none")] fun `border outer all inner none`() = doTest( """ ░┌───────────┐ @@ -460,8 +438,7 @@ class TableTest : RenderingTest() { } } - @Test - @JsName("section_column_builders") + @[Test JsName("section_column_builders")] fun `section column builders`() = doTest( """ ░┌─────┬─────┐ @@ -517,8 +494,7 @@ class TableTest : RenderingTest() { body { row(1, 2, 3) } } - @Test - @JsName("caption_widgets") + @[Test JsName("caption_widgets")] fun `caption widgets`() = doTest( """ ░! diff --git a/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/terminal/HtmlRendererTest.kt b/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/terminal/HtmlRendererTest.kt index 37dc6ed02..b9965afc7 100644 --- a/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/terminal/HtmlRendererTest.kt +++ b/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/terminal/HtmlRendererTest.kt @@ -16,8 +16,7 @@ class HtmlRendererTest { t.print(blue("blue blue")) } - @Test - @JsName("no_frame") + @[Test JsName("no_frame")] fun `no frame`() { vt.outputAsHtml(backgroundColor = null) shouldBe """ | @@ -29,8 +28,7 @@ class HtmlRendererTest { """.trimMargin() } - @Test - @JsName("frame_no_body_tag") + @[Test JsName("frame_no_body_tag")] fun `frame no body tag`() { vt.outputAsHtml(includeCodeTag = false, includeBodyTag = false) shouldBe """ |
⏺ ⏺ ⏺ 
diff --git a/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/terminal/PromptTest.kt b/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/terminal/PromptTest.kt index 0e4933737..8897dad9a 100644 --- a/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/terminal/PromptTest.kt +++ b/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/terminal/PromptTest.kt @@ -16,16 +16,14 @@ class PromptTest { vt.output() shouldBe "pr: " } - @Test - @JsName("terminal_prompt") + @[Test JsName("terminal_prompt")] fun `terminal prompt`() { vt.inputLines = mutableListOf("answer") t.prompt("pr") shouldBe "answer" vt.output() shouldBe "pr: " } - @Test - @JsName("StringPrompt_with_default") + @[Test JsName("StringPrompt_with_default")] fun `StringPrompt with default`() { vt.inputLines = mutableListOf("") // showDefault should be inferred, but is disabled due to KT-59326 @@ -34,8 +32,7 @@ class PromptTest { vt.output() shouldBe "pr ${style("(def)")}: " } - @Test - @JsName("StringPrompt_with_choices") + @[Test JsName("StringPrompt_with_choices")] fun `StringPrompt with choices`() { vt.inputLines = mutableListOf("b") StringPrompt("pr", t, choices = listOf("a", "b")).ask() shouldBe "b" @@ -43,8 +40,7 @@ class PromptTest { vt.output() shouldBe "pr ${s("[a, b]")}: " } - @Test - @JsName("custom_Prompt") + @[Test JsName("custom_Prompt")] fun `custom Prompt`() { t.info.ansiLevel = AnsiLevel.NONE class IntPrompt : Prompt("pr", t) { @@ -63,8 +59,7 @@ class PromptTest { vt.output() shouldBe "before\npr: nope\nbefore\npr: " } - @Test - @JsName("StringPrompt_invalid_choices") + @[Test JsName("StringPrompt_invalid_choices")] fun `StringPrompt invalid choice`() { vt.inputLines = mutableListOf("bad", "a") StringPrompt("pr", t, choices = listOf("a", "b")).ask() shouldBe "a" @@ -74,8 +69,7 @@ class PromptTest { vt.output() shouldBe "$p${e("Invalid value, choose from [a, b]")}\n$p" } - @Test - @JsName("YesNoPrompt_no_default") + @[Test JsName("YesNoPrompt_no_default")] fun `YesNoPrompt no default`() { vt.inputLines = mutableListOf("Y") YesNoPrompt("pr", t).ask() shouldBe true @@ -83,8 +77,7 @@ class PromptTest { vt.output() shouldBe "pr ${style("[y/n]")}: " } - @Test - @JsName("YesNoPrompt_default") + @[Test JsName("YesNoPrompt_default")] fun `YesNoPrompt default`() { vt.inputLines = mutableListOf("") YesNoPrompt("pr", t, default = false).ask() shouldBe false @@ -92,16 +85,14 @@ class PromptTest { vt.output() shouldBe "pr ${style("[y/N]")}: " } - @Test - @JsName("ConfirmationPrompt_match") + @[Test JsName("ConfirmationPrompt_match")] fun `ConfirmationPrompt match`() { vt.inputLines = mutableListOf("a", "a") ConfirmationPrompt.createString("pr1", "pr2", t).ask() shouldBe "a" vt.output() shouldBe "pr1: pr2: " } - @Test - @JsName("ConfirmationPrompt_mismatch") + @[Test JsName("ConfirmationPrompt_mismatch")] fun `ConfirmationPrompt mismatch`() { vt.inputLines = mutableListOf("a", "b", "c", "c") ConfirmationPrompt.createString("pr1", "pr2", t).ask() shouldBe "c" diff --git a/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/terminal/TerminalColorsTest.kt b/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/terminal/TerminalColorsTest.kt index 30ca4c2fd..4bdaa8136 100644 --- a/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/terminal/TerminalColorsTest.kt +++ b/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/terminal/TerminalColorsTest.kt @@ -15,64 +15,55 @@ import kotlin.test.Test class TerminalColorsTest { private val c = Terminal(AnsiLevel.TRUECOLOR).colors - @Test - @JsName("empty_string") + @[Test JsName("empty_string")] fun `empty string`() = doTest( c.blue(""), "" ) - @Test - @JsName("one_level_nesting") + @[Test JsName("one_level_nesting")] fun `one level nesting`() = doTest( c.blue("A${c.red("B")}C"), "<34>A<31>B<34>C<39>" ) - @Test - @JsName("one_level_nesting_start_of_string") + @[Test JsName("one_level_nesting_start_of_string")] fun `one level nesting start of string`() = doTest( (c.blue on c.green)("${c.red("A")}B"), "<34;42>AB<39;49>" ) - @Test - @JsName("one_level_nesting_end_of_string") + @[Test JsName("one_level_nesting_end_of_string")] fun `one level nesting end of string`() = doTest( c.red("A${c.green("B")}"), "<31>A<32>B<39>" ) - @Test - @JsName("two_level_nesting") + @[Test JsName("two_level_nesting")] fun `two level nesting`() = doTest( c.blue("A${c.red("B${c.green("C")}D")}E"), "<34>A<31>B<32>C<31>D<34>E<39>" ) - @Test - @JsName("two_level_nesting_end_of_string") + @[Test JsName("two_level_nesting_end_of_string")] fun `two level nesting end of string`() = doTest( c.red("A${c.green("B${c.yellow("C")}")}"), "<31>A<32>B<33>C<39>" ) - @Test - @JsName("two_level_nesting_styles") + @[Test JsName("two_level_nesting_styles")] fun `two level nesting styles`() = doTest( c.red("A${c.bold("B${c.green("C")}D")}E"), "<31>A<1>B<32>C<31>D<22>E<39>" ) - @Test - @JsName("three_level_nesting_styles_and_bg") + @[Test JsName("three_level_nesting_styles_and_bg")] fun `three level nesting styles and bg`() = doTest( c.run { (red on red)("A${(green on green)("B${(yellow.bg + underline)("C")}D")}E") }, "<31;41>A<32;42>B<43;4>C<42;24>D<31;41>E<39;49>" ) - @Test - @JsName("all_ansi16_colors") + @[Test JsName("all_ansi16_colors")] fun `all ansi16 colors`() = forAll( row(c.black, 30), row(c.red, 31), @@ -94,8 +85,7 @@ class TerminalColorsTest { color.color!!.toAnsi16().code shouldBe code } - @Test - @JsName("all_24bit_colors") + @[Test JsName("all_24bit_colors")] fun `all 24bit colors`() = forAll( row(c.rgb("#ff00ff"), RGB("#ff00ff")), row(c.rgb(.11, .22, .33), RGB(.11, .22, .33)), @@ -109,8 +99,7 @@ class TerminalColorsTest { color.color shouldBe expected } - @Test - @JsName("all_styles") + @[Test JsName("all_styles")] fun `all styles`() { assertSoftly { c.bold.bold shouldBe true @@ -123,8 +112,7 @@ class TerminalColorsTest { } } - @Test - @JsName("all_colors_and_styles_downsampled") + @[Test JsName("all_colors_and_styles_downsampled")] fun `all colors and styles downsampled`() { val colorNone = Terminal(ansiLevel = AnsiLevel.NONE).colors val colorRgb = Terminal(ansiLevel = AnsiLevel.TRUECOLOR).colors diff --git a/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/terminal/TerminalCursorTest.kt b/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/terminal/TerminalCursorTest.kt index c33c4fb67..c93bf612f 100644 --- a/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/terminal/TerminalCursorTest.kt +++ b/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/terminal/TerminalCursorTest.kt @@ -16,8 +16,7 @@ private fun c(b: CursorMovements.() -> Unit): String { } class TerminalCursorTest { - @Test - @JsName("disabled_commands") + @[Test JsName("disabled_commands")] fun `disabled commands`() { val vt = TerminalRecorder(AnsiLevel.NONE) val t = Terminal(terminalInterface = vt) @@ -39,8 +38,7 @@ class TerminalCursorTest { } } - @Test - @JsName("cursor_directions_0_count") + @[Test JsName("cursor_directions_0_count")] fun `cursor directions 0 count`() = forAll( row(c { up(0) }), row(c { down(0) }), @@ -50,8 +48,7 @@ class TerminalCursorTest { actual shouldBe "" } - @Test - @JsName("cursor_show_and_hide") + @[Test JsName("cursor_show_and_hide")] fun `cursor show and hide`() { val vt = TerminalRecorder() val t = Terminal(terminalInterface = vt) @@ -62,8 +59,7 @@ class TerminalCursorTest { vt.output() shouldBe "$CSI?25h" } - @Test - @JsName("disabled_cursor_show_and_hide") + @[Test JsName("disabled_cursor_show_and_hide")] fun `disabled cursor show and hide`() { val vt = TerminalRecorder(AnsiLevel.NONE) val t = Terminal(terminalInterface = vt) @@ -73,8 +69,7 @@ class TerminalCursorTest { vt.output() shouldBe "" } - @Test - @JsName("cursor_commands") + @[Test JsName("cursor_commands")] fun `cursor commands`() = forAll( row(c { up(2) }, "${CSI}2A"), row(c { down(3) }, "${CSI}3B"), @@ -94,8 +89,7 @@ class TerminalCursorTest { actual shouldBe expected } - @Test - @JsName("cursor_commands_negative_count") + @[Test JsName("cursor_commands_negative_count")] fun `cursor commands negative count`() { forAll( row(c { up(-1) }, c { down(1) }), diff --git a/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/terminal/TerminalTest.kt b/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/terminal/TerminalTest.kt index 3175ef39c..6723f5d7d 100644 --- a/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/terminal/TerminalTest.kt +++ b/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/terminal/TerminalTest.kt @@ -44,7 +44,7 @@ class TerminalTest { @Test fun print() { t.print("1") - t.print("2", stderr=true) + t.print("2", stderr = true) t.print("3") vt.stdout() shouldBe "13" vt.stderr() shouldBe "2" @@ -54,7 +54,7 @@ class TerminalTest { @Test fun println() { t.println("1") - t.println("2", stderr=true) + t.println("2", stderr = true) t.println("3") vt.stdout() shouldBe "1\n3\n" vt.stderr() shouldBe "2\n" @@ -64,7 +64,7 @@ class TerminalTest { @Test fun rawPrint() { t.rawPrint(t.cursor.getMoves { left(1) }) - t.rawPrint(t.cursor.getMoves { up(1) }, stderr=true) + t.rawPrint(t.cursor.getMoves { up(1) }, stderr = true) t.rawPrint(t.cursor.getMoves { right(1) }) vt.stdout() shouldBe t.cursor.getMoves { left(1); right(1) } vt.stderr() shouldBe t.cursor.getMoves { up(1) } @@ -75,8 +75,7 @@ class TerminalTest { vt.output() shouldBe "\t" } - @Test - @JsName("print_customized") + @[Test JsName("print_customized")] fun `print customized`() { t.print(cyan("print with a wrap"), whitespace = Whitespace.NORMAL, align = TextAlign.RIGHT) vt.output() shouldBe """ diff --git a/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/widgets/DefinitionListTest.kt b/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/widgets/DefinitionListTest.kt index 29233a2a1..7ddac3946 100644 --- a/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/widgets/DefinitionListTest.kt +++ b/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/widgets/DefinitionListTest.kt @@ -8,8 +8,7 @@ import kotlin.js.JsName import kotlin.test.Test class DefinitionListTest : RenderingTest() { - @Test - @JsName("multiple_items_inline") + @[Test JsName("multiple_items_inline")] fun `multiple items inline`() = doTest( """ ░term 1: desc 1 @@ -33,8 +32,7 @@ class DefinitionListTest : RenderingTest() { descriptionSpacing = 4 } - @Test - @JsName("inline_desc_wrap") + @[Test JsName("inline_desc_wrap")] fun `inline desc wrap`() = doTest( """ ░term 1: Lorem ipsum @@ -48,8 +46,7 @@ class DefinitionListTest : RenderingTest() { inline = true } - @Test - @JsName("inline_desc_no_wrap_due_to_short_desc") + @[Test JsName("inline_desc_no_wrap_due_to_short_desc")] fun `inline desc no wrap due to short desc`() = doTest( """ ░term 1 lorem ipsum: 1 @@ -61,8 +58,7 @@ class DefinitionListTest : RenderingTest() { inline = true } - @Test - @JsName("inline_term_overflow") + @[Test JsName("inline_term_overflow")] fun `inline term overflow`() = doTest( """ ░term 1: desc 1 @@ -77,8 +73,7 @@ class DefinitionListTest : RenderingTest() { inline = true } - @Test - @JsName("inline_term_all_overflow") + @[Test JsName("inline_term_all_overflow")] fun `inline term all overflow`() = doTest( """ ░term 1 lorem ipsum: @@ -92,8 +87,7 @@ class DefinitionListTest : RenderingTest() { entry("term 2 lorem ipsum:", "dolor sit amet") } - @Test - @JsName("inline_term_overflow_wrap") + @[Test JsName("inline_term_overflow_wrap")] fun `inline term overflow wrap`() = doTest( """ ░term 1: desc 1 @@ -110,8 +104,7 @@ class DefinitionListTest : RenderingTest() { entry("term 3:", "desc 3") } - @Test - @JsName("inline_multi_line_term_and_desc") + @[Test JsName("inline_multi_line_term_and_desc")] fun `inline multi line term and desc`() = doTest( """ ░term 1: desc 1 @@ -133,8 +126,7 @@ class DefinitionListTest : RenderingTest() { } - @Test - @JsName("non_inline") + @[Test JsName("non_inline")] fun `non-inline`() = doTest( """ ░term 1: @@ -160,8 +152,7 @@ class DefinitionListTest : RenderingTest() { } } - @Test - @JsName("non_inline_custom_spacing") + @[Test JsName("non_inline_custom_spacing")] fun `non-inline custom spacing`() = doTest( """ ░term 1: @@ -182,8 +173,7 @@ class DefinitionListTest : RenderingTest() { entry("term 3:", "desc 3") } - @Test - @JsName("list_in_layout") + @[Test JsName("list_in_layout")] fun `list in layout`() { val widget = verticalLayout { cell(definitionList { diff --git a/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/widgets/DeprecatedProgressLayoutTest.kt b/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/widgets/DeprecatedProgressLayoutTest.kt index 47ef93063..a494eff30 100644 --- a/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/widgets/DeprecatedProgressLayoutTest.kt +++ b/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/widgets/DeprecatedProgressLayoutTest.kt @@ -16,36 +16,31 @@ class DeprecatedProgressLayoutTest : RenderingTest() { 0 ) - @Test - @JsName("no_progress") + @[Test JsName("no_progress")] fun `no progress`() = doTest( "text.txt| 0%|.........................| 0/0B| ---.-it/s", 0, 0 ) - @Test - @JsName("large_values") + @[Test JsName("large_values")] fun `large values`() = doTest( "text.txt| 50%|############>............|150.0/300.0MB|100.0Mit/s", 150_000_000, 300_000_000, 1.5, 100_000_000.0 ) - @Test - @JsName("short_eta") + @[Test JsName("short_eta")] fun `short eta`() = doTest( "text.txt| 50%|############>............| 1/2B| 4.0it/s", 1, 2, 3.0, 4.0 ) - @Test - @JsName("automatic_eta") + @[Test JsName("automatic_eta")] fun `automatic eta`() = doTest( "text.txt| 50%|############>............| 1/2B| 0.3it/s", 1, 2, 3.0 ) - @Test - @JsName("long_eta") + @[Test JsName("long_eta")] fun `long eta`() = doTest( "text.txt| 50%|############>............|150.0/300.0MB| 2.0it/s", 150_000_000, 300_000_000, 1.5, 2.0 @@ -72,8 +67,7 @@ class DeprecatedProgressLayoutTest : RenderingTest() { width = 3, ) - @Test - @JsName("no_pulse") + @[Test JsName("no_pulse")] fun `no pulse`() = checkRender( progressLayout { progressBar(showPulse = false) diff --git a/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/widgets/HorizontalRuleTest.kt b/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/widgets/HorizontalRuleTest.kt index 37736dd37..41e7bca13 100644 --- a/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/widgets/HorizontalRuleTest.kt +++ b/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/widgets/HorizontalRuleTest.kt @@ -10,41 +10,52 @@ import kotlin.js.JsName import kotlin.test.Test class HorizontalRuleTest : RenderingTest() { - @Test - @JsName("no_title") + @[Test JsName("no_title")] fun `no title`() { checkRender(HorizontalRule(), "──────────", width = 10) } - @Test - @JsName("multiple_character_rules") + @[Test JsName("multiple_character_rules")] fun `multiple character rules`() { - checkRender(HorizontalRule(title = "title", ruleCharacter = "1234"), "123412 title 1234123", width = 20) + checkRender( + HorizontalRule(title = "title", ruleCharacter = "1234"), + "123412 title 1234123", + width = 20 + ) } - @Test - @JsName("rule_with_whitespace") + @[Test JsName("rule_with_whitespace")] fun `rule with whitespace`() { checkRender(HorizontalRule(ruleCharacter = "- -"), "- -- -", width = 6) } - @Test - @JsName("title_align_left") + @[Test JsName("title_align_left")] fun `title align left`() { - checkRender(HorizontalRule("title", titleAlign = TextAlign.LEFT), "─ title ────", width = 12) + checkRender( + HorizontalRule("title", titleAlign = TextAlign.LEFT), + "─ title ────", + width = 12 + ) } - @Test - @JsName("title_align_right") + @[Test JsName("title_align_right")] fun `title align right`() { - checkRender(HorizontalRule("title", titleAlign = TextAlign.RIGHT), "──── title ─", width = 12) + checkRender( + HorizontalRule("title", titleAlign = TextAlign.RIGHT), + "──── title ─", + width = 12 + ) } - @Test - @JsName("multiline_title") + @[Test JsName("multiline_title")] fun `multiline title`() { checkRender( - HorizontalRule(title = Text("Multiline\nHeader Text", whitespace = Whitespace.PRE_WRAP)), + HorizontalRule( + title = Text( + "Multiline\nHeader Text", + whitespace = Whitespace.PRE_WRAP + ) + ), """ ░ Multiline ░ ░─── Header Text ───░ @@ -53,8 +64,7 @@ class HorizontalRuleTest : RenderingTest() { ) } - @Test - @JsName("styled_title_and_rule") + @[Test JsName("styled_title_and_rule")] fun `styled title and rule`() { checkRender( HorizontalRule(title = blue("title"), ruleStyle = blue), @@ -62,8 +72,7 @@ class HorizontalRuleTest : RenderingTest() { ) } - @Test - @JsName("themed_title_and_rule") + @[Test JsName("themed_title_and_rule")] fun `themed title and rule`() { checkRender( HorizontalRule(title = blue("title")), diff --git a/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/widgets/MultiProgressLayoutTest.kt b/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/widgets/MultiProgressLayoutTest.kt index a58f41826..ea28c65a1 100644 --- a/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/widgets/MultiProgressLayoutTest.kt +++ b/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/widgets/MultiProgressLayoutTest.kt @@ -22,8 +22,7 @@ class MultiProgressLayoutTest : RenderingTest() { """ ) - @Test - @JsName("indeterminate_unaligned") + @[Test JsName("indeterminate_unaligned")] fun `indeterminate unaligned`() = doTest( completed1 = 0, total1 = null, elapsed1 = null, speed1 = null, completed2 = 0, total2 = null, elapsed2 = null, speed2 = null, @@ -34,8 +33,7 @@ class MultiProgressLayoutTest : RenderingTest() { """ ) - @Test - @JsName("one_in_progress") + @[Test JsName("one_in_progress")] fun `one in progress`() = doTest( completed1 = 5, total1 = 10, elapsed1 = 5.0, speed1 = 1.0, completed2 = 0, total2 = null, elapsed2 = null, speed2 = null, @@ -45,8 +43,7 @@ class MultiProgressLayoutTest : RenderingTest() { """ ) - @Test - @JsName("two_finished") + @[Test JsName("two_finished")] fun `two finished`() = doTest( completed1 = 5, total1 = 10, elapsed1 = 5.0, speed1 = 1.0, completed2 = 20, total2 = 20, elapsed2 = 10.0, speed2 = 2.0, @@ -56,8 +53,7 @@ class MultiProgressLayoutTest : RenderingTest() { """ ) - @Test - @JsName("different_layouts") + @[Test JsName("different_layouts")] fun `different layouts`() { val t = TestTimeSource() val animTime = t.markNow() diff --git a/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/widgets/OrderedListTest.kt b/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/widgets/OrderedListTest.kt index 439d133a2..e7b90416e 100644 --- a/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/widgets/OrderedListTest.kt +++ b/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/widgets/OrderedListTest.kt @@ -5,8 +5,7 @@ import kotlin.js.JsName import kotlin.test.Test class OrderedListTest : RenderingTest() { - @Test - @JsName("vararg_string_constructor") + @[Test JsName("vararg_string_constructor")] fun `vararg string constructor`() = checkRender( OrderedList("one", "two", "three"), """ @@ -16,8 +15,7 @@ class OrderedListTest : RenderingTest() { """ ) - @Test - @JsName("vararg_widget_constructor") + @[Test JsName("vararg_widget_constructor")] fun `vararg widget constructor`() = checkRender( OrderedList(Text("one"), Text("two"), Text("three")), """ @@ -27,8 +25,7 @@ class OrderedListTest : RenderingTest() { """ ) - @Test - @JsName("empty_list") + @[Test JsName("empty_list")] fun `empty list`() = checkRender( OrderedList(emptyList()), "" diff --git a/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/widgets/PanelTest.kt b/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/widgets/PanelTest.kt index 0b6830c46..1f21fe841 100644 --- a/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/widgets/PanelTest.kt +++ b/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/widgets/PanelTest.kt @@ -10,8 +10,7 @@ import kotlin.test.Test class PanelTest : RenderingTest(width = 20) { - @Test - @JsName("no_expand") + @[Test JsName("no_expand")] fun `no expand`() = checkRender( Panel(Text("text"), expand = false), """ @@ -31,8 +30,7 @@ class PanelTest : RenderingTest(width = 20) { """ ) - @Test - @JsName("no_border") + @[Test JsName("no_border")] fun `no border`() = checkRender( Panel(Text("text\nline 2", whitespace = PRE), borderType = null), """ @@ -41,8 +39,7 @@ class PanelTest : RenderingTest(width = 20) { """ ) - @Test - @JsName("default_title") + @[Test JsName("default_title")] fun `default title`() = checkRender( Panel("text content", title = "title"), """ @@ -52,8 +49,7 @@ class PanelTest : RenderingTest(width = 20) { """ ) - @Test - @JsName("long_title") + @[Test JsName("long_title")] fun `long title`() = checkRender( Panel("content", title = "title title"), """ @@ -63,8 +59,7 @@ class PanelTest : RenderingTest(width = 20) { """ ) - @Test - @JsName("title_align_left") + @[Test JsName("title_align_left")] fun `title align left`() = checkRender( Panel("text content", title = "title", titleAlign = TextAlign.LEFT), """ @@ -74,8 +69,7 @@ class PanelTest : RenderingTest(width = 20) { """ ) - @Test - @JsName("title_align_right") + @[Test JsName("title_align_right")] fun `title align right`() = checkRender( Panel("text content", title = "title", titleAlign = TextAlign.RIGHT), """ @@ -85,8 +79,7 @@ class PanelTest : RenderingTest(width = 20) { """ ) - @Test - @JsName("bottom_title") + @[Test JsName("bottom_title")] fun `bottom title`() = checkRender( Panel("text content", bottomTitle = "title"), """ @@ -96,8 +89,7 @@ class PanelTest : RenderingTest(width = 20) { """ ) - @Test - @JsName("title_widgets") + @[Test JsName("title_widgets")] fun `title widgets`() = checkRender( Panel(Text("text content"), title = Text("foo\nbar"), bottomTitle = Text("foo\nbar")), """ @@ -109,8 +101,7 @@ class PanelTest : RenderingTest(width = 20) { """ ) - @Test - @JsName("bottom_align") + @[Test JsName("bottom_align")] fun `bottom align`() = checkRender( Panel( "my panel content", @@ -126,8 +117,7 @@ class PanelTest : RenderingTest(width = 20) { """ ) - @Test - @JsName("themed_panel") + @[Test JsName("themed_panel")] fun `themed panel`() = checkRender( Panel(green("text content"), title = blue("title")), """ diff --git a/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/widgets/ProgressBarTest.kt b/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/widgets/ProgressBarTest.kt index cac47bb8d..cdfbf4929 100644 --- a/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/widgets/ProgressBarTest.kt +++ b/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/widgets/ProgressBarTest.kt @@ -7,91 +7,75 @@ import kotlin.js.JsName import kotlin.test.Test class ProgressBarTest : RenderingTest() { - @Test - @JsName("_0_percent_complete") + @[Test JsName("_0_percent_complete")] fun `0 percent complete`() = doPercentTest(0, " ") - @Test - @JsName("_10_percent_complete") + @[Test JsName("_10_percent_complete")] fun `10 percent complete`() = doPercentTest(10, " ") - @Test - @JsName("_20_percent_complete") + @[Test JsName("_20_percent_complete")] fun `20 percent complete`() = doPercentTest(20, "#> ") - @Test - @JsName("_30_percent_complete") + @[Test JsName("_30_percent_complete")] fun `30 percent complete`() = doPercentTest(30, "#> ") - @Test - @JsName("_40_percent_complete") + @[Test JsName("_40_percent_complete")] fun `40 percent complete`() = doPercentTest(40, "##> ") - @Test - @JsName("_60_percent_complete") + @[Test JsName("_60_percent_complete")] fun `60 percent complete`() = doPercentTest(60, "###> ") - @Test - @JsName("_80_percent_complete") + @[Test JsName("_80_percent_complete")] fun `80 percent complete`() = doPercentTest(80, "####>") - @Test - @JsName("_99_percent_complete") + @[Test JsName("_99_percent_complete")] fun `99 percent complete`() = doPercentTest(99, "####>") - @Test - @JsName("_100_percent_complete") + @[Test JsName("_100_percent_complete")] fun `100 percent complete`() = doPercentTest(100, "#####") - @Test - @JsName("default_theme") + @[Test JsName("default_theme")] fun `default theme`() = doPercentTest( 40, "${CSI}38;2;97;175;239m━━${CSI}39m ${CSI}38;2;92;99;112m━━${CSI}39m", theme = Theme.Default ) - @Test - @JsName("pulse_initial") + @[Test JsName("pulse_initial")] fun `pulse initial`() = doPulseTest( pulsePosition = 0f, "${CSI}38;2;97;175;239m━━━━━━━━━━${CSI}39m" ) - @Test - @JsName("pulse_25") + @[Test JsName("pulse_25")] fun `pulse 25`() = doPulseTest( pulsePosition = .25f, "${CSI}38;2;97;175;239m━${CSI}38;2;251;253;255m━${CSI}38;2;207;230;251m━${CSI}38;2;107;180;240m━${CSI}38;2;97;175;239m━━━━━━${CSI}39m" ) - @Test - @JsName("pulse_50") + @[Test JsName("pulse_50")] fun `pulse 50`() = doPulseTest( pulsePosition = .50f, "${CSI}38;2;97;175;239m━━━${CSI}38;2;136;194;244m━${CSI}38;2;239;247;254m━━${CSI}38;2;136;194;244m━${CSI}38;2;97;175;239m━━━${CSI}39m" ) - @Test - @JsName("pulse_75") + @[Test JsName("pulse_75")] fun `pulse 75`() = doPulseTest( pulsePosition = .75f, "${CSI}38;2;97;175;239m━━━━━━${CSI}38;2;107;180;240m━${CSI}38;2;207;230;251m━${CSI}38;2;254;255;255m━${CSI}38;2;178;216;249m━${CSI}39m" ) - @Test - @JsName("pulse_100") + @[Test JsName("pulse_100")] fun `pulse 100`() = doPulseTest( pulsePosition = 1f, "${CSI}38;2;97;175;239m━━━━━━━━━━${CSI}39m" ) - @Test - @JsName("narrow_width") + @[Test JsName("narrow_width")] fun `narrow width`() = doPulseTest( pulsePosition = 1f, "${CSI}38;2;97;175;239m━${CSI}39m", - width = 1 + width = 1 ) private fun doPulseTest(pulsePosition: Float, expected: String, width: Int = 10) { diff --git a/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/widgets/ProgressLayoutTest.kt b/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/widgets/ProgressLayoutTest.kt index 5d6ebf6e0..4b5c7c723 100644 --- a/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/widgets/ProgressLayoutTest.kt +++ b/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/widgets/ProgressLayoutTest.kt @@ -26,71 +26,61 @@ class ProgressLayoutTest : RenderingTest() { private val indetermStyle = Theme.Default.style("progressbar.indeterminate") - @Test - @JsName("indeterminate_not_started") + @[Test JsName("indeterminate_not_started")] fun `indeterminate not started`() = doTest( "text.txt| 0%|#########| 0/---.-B| ---.-/s|eta -:--:--|-:--:--", 0, started = false ) - @Test - @JsName("indeterminate_started") + @[Test JsName("indeterminate_started")] fun `indeterminate started`() = doTest( "text.txt| 0%|#########| 0/---.-B| ---.-/s|eta -:--:--|0:00:00", 0 ) - @Test - @JsName("no_progress") + @[Test JsName("no_progress")] fun `no progress`() = doTest( "text.txt| 0%|.........| 0/0B| ---.-/s|eta -:--:--|0:00:00", 0, 0 ) - @Test - @JsName("large_values") + @[Test JsName("large_values")] fun `large values`() = doTest( "text.txt| 50%|####>....|150.0/300.0MB|100.0M/s|eta 0:00:01|4:10:33", 150_000_000, 300_000_000, 15033.0, 100_000_000.0 ) - @Test - @JsName("short_eta") + @[Test JsName("short_eta")] fun `short eta`() = doTest( "text.txt| 50%|####>....| 1/2B| 4.0/s|eta 0:00:00|0:00:03", 1, 2, 3.0, 4.0 ) - @Test - @JsName("long_eta") + @[Test JsName("long_eta")] fun `long eta`() = doTest( "text.txt| 50%|####>....|150.0/300.0MB| 2.0/s|eta -:--:--|0:00:01", 150_000_000, 300_000_000, 1.5, 2.0 ) - @Test - @JsName("zero_total") + @[Test JsName("zero_total")] fun `zero total`() = doTest( "text.txt| 0%|.........| 0/0B| ---.-/s|eta -:--:--|0:00:00", 0, 0 ) - @Test - @JsName("negative_completed_value") + @[Test JsName("negative_completed_value")] fun `negative completed value`() = doTest( "text.txt|-50%|.........| -1/2B| ---.-/s|eta -:--:--|0:00:00", -1, 2 ) - @Test - @JsName("completed_greater_than_total") + @[Test JsName("completed_greater_than_total")] fun `completed value greater than total`() = doTest( "text.txt|200%|#########| 10/5B| ---.-/s|eta -:--:--|0:00:00", 10, 5 ) - @Test - @JsName("default_pacing") + @[Test JsName("default_pacing")] fun `default spacing`() = checkRender( progressBarLayout { text("|") @@ -114,8 +104,7 @@ class ProgressLayoutTest : RenderingTest() { ) } - @Test - @JsName("custom_pulse_duration") + @[Test JsName("custom_pulse_duration")] fun `custom pulse duration`() { t += 0.5.seconds checkRender( @@ -127,8 +116,7 @@ class ProgressLayoutTest : RenderingTest() { ) } - @Test - @JsName("no_pulse") + @[Test JsName("no_pulse")] fun `no pulse`() { t += 1.seconds checkRender( @@ -140,8 +128,7 @@ class ProgressLayoutTest : RenderingTest() { ) } - @Test - @JsName("timeRemaining_compact") + @[Test JsName("timeRemaining_compact")] fun `timeRemaining compact`() { val l = progressBarLayout { timeRemaining(compact = true) @@ -157,22 +144,19 @@ class ProgressLayoutTest : RenderingTest() { ) } - @Test - @JsName("layout_no_cells") + @[Test JsName("layout_no_cells")] fun `layout with no cells`() { val layout = progressBarLayout { }.build(null, 0, start) checkRender(layout, "") } - @Test - @JsName("layout_no_states") + @[Test JsName("layout_no_states")] fun `layout with no states`() { val layout = MultiProgressBarWidgetMaker.build(emptyList()) checkRender(layout, "") } - @Test - @JsName("eta_and_remaining_compact") + @[Test JsName("eta_and_remaining_compact")] fun `eta and remaining compact`() = forAll( row(null, null, "--:--| eta --:--"), row(0.seconds, 1.seconds, "00:00| eta 00:01"), @@ -194,8 +178,7 @@ class ProgressLayoutTest : RenderingTest() { checkRender(layout, expected) } - @Test - @JsName("eta_and_remaining_finished") + @[Test JsName("eta_and_remaining_finished")] fun `eta and remaining finished`() { t += 1.hours val finishedTime = t.markNow() @@ -206,8 +189,7 @@ class ProgressLayoutTest : RenderingTest() { ) } - @Test - @JsName("eta_and_remaining_paused") + @[Test JsName("eta_and_remaining_paused")] fun `eta and remaining paused`() { t += 1.hours val pausedTime = t.markNow() @@ -216,8 +198,7 @@ class ProgressLayoutTest : RenderingTest() { checkRender(layout, "1:00:00|eta -:--:--") } - @Test - @JsName("eta_elapsedWhenFinished") + @[Test JsName("eta_elapsedWhenFinished")] fun `eta elapsedWhenFinished`() { val layout = etaLayout(elapsedWhenFinished = true) t += 1.hours @@ -267,8 +248,7 @@ class ProgressLayoutTest : RenderingTest() { checkRender(layout.build(null, 0, start), expected, trimMargin = false) } - @Test - @JsName("styled_marquee") + @[Test JsName("styled_marquee")] fun `styled marquee`() = forAll( row(0, red(" ")), row(1, red(" 1")), @@ -286,8 +266,7 @@ class ProgressLayoutTest : RenderingTest() { checkRender(layout.build(null, 0, start), expected, trimMargin = false) } - @Test - @JsName("completed_decimal_format") + @[Test JsName("completed_decimal_format")] fun `completed decimal format`() = forAll( row(0, 0, null,/* */" 0/---.-"), row(0, 1e1, 1e2,/* */" 10/100"), @@ -312,15 +291,13 @@ class ProgressLayoutTest : RenderingTest() { checkRender(widget, expected, trimMargin = false) } - @Test - @JsName("marquee_scrollWhenContentFits_false") + @[Test JsName("marquee_scrollWhenContentFits_false")] fun `marquee scrollWhenContentFits=false`() { val layout = progressBarLayout { marquee("123", width = 5) } checkRender(layout.build(null, 0, start), " 123", trimMargin = false) } - @Test - @JsName("marquee_scrollWhenContentFits_true") + @[Test JsName("marquee_scrollWhenContentFits_true")] fun `marquee scrollWhenContentFits=true`() { val start = setTime(2.seconds) val layout = progressBarLayout { marquee("123", width = 5, scrollWhenContentFits = true) } @@ -375,8 +352,7 @@ class ProgressLayoutTest : RenderingTest() { ) } - @Test - @JsName("use_as_builder") + @[Test JsName("use_as_builder")] fun `use as builder`() { val builder = ProgressLayoutBuilder() builder.text { "a$context" } diff --git a/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/widgets/SelectListTest.kt b/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/widgets/SelectListTest.kt index 6a9c3e382..db852796c 100644 --- a/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/widgets/SelectListTest.kt +++ b/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/widgets/SelectListTest.kt @@ -9,8 +9,7 @@ import kotlin.js.JsName import kotlin.test.Test class SelectListTest : RenderingTest() { - @Test - @JsName("no_optional_elements") + @[Test JsName("no_optional_elements")] fun `no optional elements`() = doTest( """ ░foo @@ -24,8 +23,7 @@ class SelectListTest : RenderingTest() { cursorMarker = "", ) - @Test - @JsName("no_selected_marker") + @[Test JsName("no_selected_marker")] fun `no selected marker`() = doTest( """ ░title @@ -41,8 +39,7 @@ class SelectListTest : RenderingTest() { selectedMarker = "", ) - @Test - @JsName("multi_selected_marker") + @[Test JsName("multi_selected_marker")] fun `multi selected marker`() = doTest( """ ░title @@ -61,8 +58,7 @@ class SelectListTest : RenderingTest() { captionBottom = Text("caption"), ) - @Test - @JsName("styles_with_descriptions") + @[Test JsName("styles_with_descriptions")] fun `styles with descriptions`() = doTest( """ ░ ${blue("•")} ${red("foo")} ░ diff --git a/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/widgets/TextTest.kt b/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/widgets/TextTest.kt index c20f1dcd5..d883cd120 100644 --- a/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/widgets/TextTest.kt +++ b/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/widgets/TextTest.kt @@ -23,14 +23,12 @@ import kotlin.test.Test class TextTest : RenderingTest() { - @Test - @JsName("trailing_line_break") + @[Test JsName("trailing_line_break")] fun `trailing line break`() { checkRender(Text("x\n"), "x\n", trimMargin = false) } - @Test - @JsName("override_width") + @[Test JsName("override_width")] fun `override width`() = checkRender( Text( """ @@ -44,8 +42,7 @@ class TextTest : RenderingTest() { """, width = 79 ) - @Test - @JsName("hard_line_breaks") + @[Test JsName("hard_line_breaks")] fun `hard line breaks`() = checkRender( Text( """ @@ -71,8 +68,7 @@ class TextTest : RenderingTest() { checkRender(Text(text, whitespace = PRE), expected, tabWidth = 4) } - @Test - @JsName("ansi_parsing") + @[Test JsName("ansi_parsing")] fun `ansi parsing`() = checkRender( Text( """ @@ -87,8 +83,7 @@ class TextTest : RenderingTest() { """, width = 79 ) - @Test - @JsName("ansi_parsing_256") + @[Test JsName("ansi_parsing_256")] fun `ansi parsing 256`() = checkRender( Text( (TextColors.color(Ansi256(111)) on TextColors.color(Ansi256(222)))("red") @@ -96,8 +91,7 @@ class TextTest : RenderingTest() { "${CSI}38;5;111;48;5;222mred${CSI}39;49m" ) - @Test - @JsName("ansi_parsing_truecolor") + @[Test JsName("ansi_parsing_truecolor")] fun `ansi parsing truecolor`() = checkRender( Text( (TextColors.rgb("#ff0000") on TextColors.rgb("#00ff00"))("red") @@ -105,8 +99,7 @@ class TextTest : RenderingTest() { "${CSI}38;2;255;0;0;48;2;0;255;0mred${CSI}39;49m" ) - @Test - @JsName("ansi_parsing_with_styles") + @[Test JsName("ansi_parsing_with_styles")] fun `ansi parsing with styles`() = checkRender( Text( """ @@ -124,20 +117,18 @@ class TextTest : RenderingTest() { """, width = 79 ) { it.normalizeHyperlinks() } - @Test - @JsName("replacing_whole_string_color") + @[Test JsName("replacing_whole_string_color")] fun `replacing whole string color`() = checkRender( Text((green on gray)((red on blue)("text"))), (green on gray)("text") ) - @Test - @JsName("ansi_bold_and_dim") + @[Test JsName("ansi_bold_and_dim")] fun `ansi bold and dim`() = checkRender( Text(" ${dim("dim${bold("bold")}dim")} ".visibleCrLf()), " ␛2mdim␛1mbold␛22;2mdim␛22m " - // bold not bold + // bold not bold ) @@ -150,15 +141,13 @@ class TextTest : RenderingTest() { checkRender(Text(style((red on blue)("text"))), expected("text")) } - @Test - @JsName("hyperlink_one_line") + @[Test JsName("hyperlink_one_line")] fun `hyperlink one line`() = doHyperlinkTest( "This is a link", "${OSC}8;id=1;https://example.com${ST}This is a link${OSC}8;;$ST" ) - @Test - @JsName("hyperlink_word_wrap") + @[Test JsName("hyperlink_word_wrap")] fun `hyperlink word wrap`() = doHyperlinkTest( "This is a link", """ @@ -168,8 +157,7 @@ class TextTest : RenderingTest() { width = 8 ) - @Test - @JsName("hyperlink_break_word") + @[Test JsName("hyperlink_break_word")] fun `hyperlink break word`() = doHyperlinkTest( "This_is_a_link", """ diff --git a/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/widgets/UnorderedListTest.kt b/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/widgets/UnorderedListTest.kt index ddf03f726..881696232 100644 --- a/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/widgets/UnorderedListTest.kt +++ b/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/widgets/UnorderedListTest.kt @@ -5,8 +5,7 @@ import kotlin.js.JsName import kotlin.test.Test class UnorderedListTest : RenderingTest() { - @Test - @JsName("vararg_string_constructor") + @[Test JsName("vararg_string_constructor")] fun `vararg string constructor`() = checkRender( UnorderedList("one", "two", "three"), """ @@ -16,8 +15,7 @@ class UnorderedListTest : RenderingTest() { """ ) - @Test - @JsName("vararg_widget_constructor") + @[Test JsName("vararg_widget_constructor")] fun `vararg widget constructor`() = checkRender( UnorderedList(Text("one"), Text("two"), Text("three")), """ @@ -27,8 +25,7 @@ class UnorderedListTest : RenderingTest() { """ ) - @Test - @JsName("empty_list") + @[Test JsName("empty_list")] fun `empty list`() = checkRender( UnorderedList(emptyList()), "" diff --git a/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/widgets/ViewportTest.kt b/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/widgets/ViewportTest.kt index 5d7153cac..a6125d9ca 100644 --- a/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/widgets/ViewportTest.kt +++ b/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/widgets/ViewportTest.kt @@ -45,8 +45,7 @@ class ViewportTest : RenderingTest(width = 20) { doTest(null, null, x, y, ex, "a\nb c") } - @Test - @JsName("scrolling_splits_span") + @[Test JsName("scrolling_splits_span")] fun `scrolling splits span`() { doTest(1, 1, 2, 0, red("Z"), "X${red("YZ")}") doTest(2, 1, 1, 0, "56", "4567") diff --git a/mordant/src/jsMain/kotlin/com/github/ajalt/mordant/internal/JsCompat.kt b/mordant/src/jsMain/kotlin/com/github/ajalt/mordant/internal/JsCompat.kt index c1196ba32..da658d93f 100644 --- a/mordant/src/jsMain/kotlin/com/github/ajalt/mordant/internal/JsCompat.kt +++ b/mordant/src/jsMain/kotlin/com/github/ajalt/mordant/internal/JsCompat.kt @@ -3,7 +3,7 @@ package com.github.ajalt.mordant.internal // https://github.com/iliakan/detect-node internal val isNode: Boolean = js( - "Object.prototype.toString.call(typeof process !== 'undefined' ? process : 0) === '[object process]'" + "Object.prototype.toString.call(typeof process !== 'undefined' ? process : 0) === '[object process]'" ) as Boolean /** Load module [mod], or throw an exception if not running on NodeJS */ @@ -36,7 +36,7 @@ internal fun nodeRequire(mod: String): dynamic { throw IllegalArgumentException("Module not available: $mod", e as? Throwable) } require( - imported != null && js("typeof imported !== 'undefined'").unsafeCast() + imported != null && js("typeof imported !== 'undefined'").unsafeCast() ) { "Module not available: $mod" } return imported } diff --git a/mordant/src/jvmMain/kotlin/com/github/ajalt/mordant/animation/ProgressAnimation.kt b/mordant/src/jvmMain/kotlin/com/github/ajalt/mordant/animation/ProgressAnimation.kt index 68fd16af9..8de10a9ad 100644 --- a/mordant/src/jvmMain/kotlin/com/github/ajalt/mordant/animation/ProgressAnimation.kt +++ b/mordant/src/jvmMain/kotlin/com/github/ajalt/mordant/animation/ProgressAnimation.kt @@ -96,7 +96,7 @@ class ProgressAnimation internal constructor( */ fun advance(amount: Long = 1) { inner.advance(amount) - if(!inner.finished) { + if (!inner.finished) { update() } } @@ -167,8 +167,11 @@ internal fun Terminal.progressAnimation( ) } val definition = ProgressBarDefinition(cells, origDef.spacing, origDef.alignColumns) - return ProgressAnimation(definition.animateOnThread(this, - timeSource = timeSource, - speedEstimateDuration = builder.historyLength.toDouble().seconds - )) + return ProgressAnimation( + definition.animateOnThread( + this, + timeSource = timeSource, + speedEstimateDuration = builder.historyLength.toDouble().seconds + ) + ) } diff --git a/mordant/src/mingwMain/kotlin/com/github/ajalt/mordant/internal/MppInternal.mingw.kt b/mordant/src/mingwMain/kotlin/com/github/ajalt/mordant/internal/MppInternal.mingw.kt index 8ad15cc4b..216ce2ec9 100644 --- a/mordant/src/mingwMain/kotlin/com/github/ajalt/mordant/internal/MppInternal.mingw.kt +++ b/mordant/src/mingwMain/kotlin/com/github/ajalt/mordant/internal/MppInternal.mingw.kt @@ -5,4 +5,5 @@ import com.github.ajalt.mordant.terminal.terminalinterface.TerminalInterfaceNati internal actual fun ttySetEcho(echo: Boolean) = TerminalInterfaceNativeWindows.ttySetEcho(echo) internal actual fun hasFileSystem(): Boolean = true -internal actual fun getStandardTerminalInterface(): TerminalInterface = TerminalInterfaceNativeWindows +internal actual fun getStandardTerminalInterface(): TerminalInterface = + TerminalInterfaceNativeWindows diff --git a/test/proguard/src/main/kotlin/R8SmokeTest.kt b/test/proguard/src/main/kotlin/R8SmokeTest.kt index 798173ba7..e75d6d636 100644 --- a/test/proguard/src/main/kotlin/R8SmokeTest.kt +++ b/test/proguard/src/main/kotlin/R8SmokeTest.kt @@ -1,8 +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 import com.github.ajalt.mordant.rendering.AnsiLevel import com.github.ajalt.mordant.terminal.Terminal @@ -12,7 +10,7 @@ import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch -suspend fun main(args: Array) = coroutineScope{ +suspend fun main(args: Array) = coroutineScope { // make sure that the terminal detection doesn't crash. Terminal() From 06ff4e005cf4ece293050db53f370a56606c2a38 Mon Sep 17 00:00:00 2001 From: AJ Date: Sun, 25 Aug 2024 10:16:39 -0700 Subject: [PATCH 3/6] Add InteractiveSelectListTest --- .../mordant/terminal/TerminalRecorder.kt | 5 +- .../input/InteractiveSelectListTest.kt | 63 +++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 mordant/src/commonTest/kotlin/com/github/ajalt/mordant/input/InteractiveSelectListTest.kt diff --git a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/terminal/TerminalRecorder.kt b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/terminal/TerminalRecorder.kt index 4cacae5d4..9ecaf853c 100644 --- a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/terminal/TerminalRecorder.kt +++ b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/terminal/TerminalRecorder.kt @@ -51,7 +51,7 @@ class TerminalRecorder private constructor( var inputEvents: MutableList = mutableListOf() /** Whether raw mode is currently active */ - var rawModeActive: Boolean = false + private var rawModeActive: Boolean = false private val stdout: StringBuilder = StringBuilder() private val stderr: StringBuilder = StringBuilder() @@ -105,6 +105,9 @@ class TerminalRecorder private constructor( } override fun enterRawMode(mouseTracking: MouseTracking): AutoCloseable { + if (!info.inputInteractive) { + throw RuntimeException("Cannot enter raw mode on a non-interactive terminal") + } rawModeActive = true return AutoCloseable { rawModeActive = false } } diff --git a/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/input/InteractiveSelectListTest.kt b/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/input/InteractiveSelectListTest.kt new file mode 100644 index 000000000..fbb92778d --- /dev/null +++ b/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/input/InteractiveSelectListTest.kt @@ -0,0 +1,63 @@ +package com.github.ajalt.mordant.input + +import com.github.ajalt.mordant.rendering.AnsiLevel +import com.github.ajalt.mordant.terminal.Terminal +import com.github.ajalt.mordant.terminal.TerminalRecorder +import com.github.ajalt.mordant.widgets.SelectList.Entry +import io.kotest.matchers.shouldBe +import io.kotest.matchers.string.shouldContain +import kotlin.js.JsName +import kotlin.test.Test + +class InteractiveSelectListTest { + private val rec = TerminalRecorder( + width = 24, ansiLevel = AnsiLevel.NONE, inputInteractive = true, outputInteractive = true + ) + private val t = Terminal(terminalInterface = rec) + + @[Test JsName("single_select_strings")] + fun `single select strings`() = doSingleSelectTest { + t.interactiveSelectList(listOf("a", "b", "c")) + } + + @[Test JsName("single_select_entries")] + fun `single select entries`() = doSingleSelectTest { + t.interactiveSelectList(listOf(Entry("a"), Entry("b"), Entry("c"))) + } + + @[Test JsName("multi_select_strings")] + fun `multi select strings`() = doMultiSelectTest { + t.interactiveMultiSelectList(listOf("a", "b", "c")) + } + + @[Test JsName("multi_select_entries")] + fun `multi select entries`() = doMultiSelectTest { + t.interactiveMultiSelectList(listOf(Entry("a"), Entry("b"), Entry("c"))) + } + + private fun doSingleSelectTest(runList: () -> String?) { + rec.inputEvents = mutableListOf(KeyboardEvent("ArrowDown"), KeyboardEvent("Enter")) + runList() shouldBe "b" + rec.stdout() shouldContain """ + ░❯ a + ░ b + ░ c + """.trimMargin("░") + } + + private fun doMultiSelectTest(runList: () -> List?) { + rec.inputEvents = mutableListOf( + KeyboardEvent("ArrowDown"), + KeyboardEvent("x"), + KeyboardEvent("ArrowDown"), + KeyboardEvent("x"), + KeyboardEvent("Enter"), + ) + runList() shouldBe listOf("b", "c") + rec.stdout() shouldContain """ + ░❯ • a + ░ • b + ░ • c + """.trimMargin("░") + } +} From 0e1dfa1c9fba7b3a2c4cf9e0e3d8e6abb1e7903b Mon Sep 17 00:00:00 2001 From: AJ Date: Sun, 25 Aug 2024 10:22:12 -0700 Subject: [PATCH 4/6] Add docs --- .../terminal/terminalinterface/TerminalInterface.posix.kt | 7 ++++++- .../terminalinterface/TerminalInterface.windows.kt | 4 +++- .../terminalinterface/TerminalInterface.jvm.posix.kt | 4 ---- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/terminal/terminalinterface/TerminalInterface.posix.kt b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/terminal/terminalinterface/TerminalInterface.posix.kt index 946288d0b..538b71ca9 100644 --- a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/terminal/terminalinterface/TerminalInterface.posix.kt +++ b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/terminal/terminalinterface/TerminalInterface.posix.kt @@ -7,7 +7,12 @@ import com.github.ajalt.mordant.terminal.StandardTerminalInterface import kotlin.time.ComparableTimeMark import kotlin.time.Duration -// TODO: docs +/** + * A terminal interface for POSIX systems. + * + * This class provides a base interface for POSIX systems like Linux and macOS that use termios for + * terminal configuration. + */ @Suppress("unused", "SpellCheckingInspection") abstract class TerminalInterfacePosix : StandardTerminalInterface() { protected companion object { diff --git a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/terminal/terminalinterface/TerminalInterface.windows.kt b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/terminal/terminalinterface/TerminalInterface.windows.kt index 702d8c34f..576919d36 100644 --- a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/terminal/terminalinterface/TerminalInterface.windows.kt +++ b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/terminal/terminalinterface/TerminalInterface.windows.kt @@ -8,7 +8,9 @@ import com.github.ajalt.mordant.terminal.StandardTerminalInterface import kotlin.time.Duration import kotlin.time.TimeSource -// TODO: docs +/** + * A base TerminalInterface implementation for Windows systems. + */ abstract class TerminalInterfaceWindows : StandardTerminalInterface() { private companion object { // https://learn.microsoft.com/en-us/windows/console/key-event-record-str diff --git a/mordant/src/jvmMain/kotlin/com/github/ajalt/mordant/terminal/terminalinterface/TerminalInterface.jvm.posix.kt b/mordant/src/jvmMain/kotlin/com/github/ajalt/mordant/terminal/terminalinterface/TerminalInterface.jvm.posix.kt index ade463342..cccd7b456 100644 --- a/mordant/src/jvmMain/kotlin/com/github/ajalt/mordant/terminal/terminalinterface/TerminalInterface.jvm.posix.kt +++ b/mordant/src/jvmMain/kotlin/com/github/ajalt/mordant/terminal/terminalinterface/TerminalInterface.jvm.posix.kt @@ -15,7 +15,3 @@ abstract class TerminalInterfaceJvmPosix : TerminalInterfacePosix() { throw RuntimeException("Timeout reading from stdin (timeout=$timeout)") } } - -/* -~/node/bin/node --require build/js/node_modules/source-map-support/register.js build/js/packages/mordant-samples-select/kotlin/mordant-samples-select.js - */ From be85c8d2951e1a91090af46b49b1f4900151de97 Mon Sep 17 00:00:00 2001 From: AJ Date: Wed, 28 Aug 2024 17:38:44 -0700 Subject: [PATCH 5/6] Handle utf8 input in posix terminal interface --- .../com/github/ajalt/mordant/internal/Utf8.kt | 24 +++++++++++++++++-- .../terminalinterface/PosixEventParser.kt | 24 ++++++++++++------- .../TerminalInterface.posix.kt | 2 +- .../TerminalInterface.jvm.posix.kt | 5 ++-- .../TerminalInterface.native.posix.kt | 4 ++-- 5 files changed, 44 insertions(+), 15 deletions(-) diff --git a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/internal/Utf8.kt b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/internal/Utf8.kt index 98662751e..552cd49b6 100644 --- a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/internal/Utf8.kt +++ b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/internal/Utf8.kt @@ -1,10 +1,13 @@ package com.github.ajalt.mordant.internal +import kotlin.time.ComparableTimeMark +import kotlin.time.Duration + /** Read bytes from a UTF-8 encoded stream, and return the next codepoint. */ internal fun readBytesAsUtf8(readByte: () -> Int): Int { val byte = readByte() - var byteLength = 0 - var codepoint = 0 + val byteLength: Int + var codepoint: Int when { byte and 0b1000_0000 == 0x00 -> { return byte // 1-byte character @@ -35,3 +38,20 @@ internal fun readBytesAsUtf8(readByte: () -> Int): Int { } return codepoint } + +/** Convert a unicode codepoint to a String. */ +internal fun codepointToString(codePoint: Int): String { + return when (codePoint) { + in 0..0xFFFF -> { + Char(codePoint).toString() + } + in 0x10000..0x10FFFF -> { + val highSurrogate = Char(((codePoint - 0x10000) shr 10) or 0xD800) + val lowSurrogate = Char(((codePoint - 0x10000) and 0x3FF) or 0xDC00) + highSurrogate.toString() + lowSurrogate.toString() + } + else -> { + throw IllegalArgumentException("Invalid code point: $codePoint") + } + } +} diff --git a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/terminal/terminalinterface/PosixEventParser.kt b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/terminal/terminalinterface/PosixEventParser.kt index 12663d506..6ca886fe7 100644 --- a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/terminal/terminalinterface/PosixEventParser.kt +++ b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/terminal/terminalinterface/PosixEventParser.kt @@ -3,6 +3,8 @@ package com.github.ajalt.mordant.terminal.terminalinterface import com.github.ajalt.mordant.input.InputEvent import com.github.ajalt.mordant.input.KeyboardEvent import com.github.ajalt.mordant.input.MouseEvent +import com.github.ajalt.mordant.internal.codepointSequence +import com.github.ajalt.mordant.internal.codepointToString import com.github.ajalt.mordant.internal.readBytesAsUtf8 import kotlin.time.ComparableTimeMark import kotlin.time.Duration @@ -13,7 +15,7 @@ private const val ESC = '\u001b' internal class PosixEventParser( - private val readRawByte: (t0: ComparableTimeMark, timeout: Duration) -> Char, + private val readRawByte: (t0: ComparableTimeMark, timeout: Duration) -> Int, ) { /* Some patterns seen in terminal key escape codes, derived from combos seen @@ -30,11 +32,16 @@ internal class PosixEventParser( var ch = ' ' fun read() { - ch = readRawByte(t0, timeout) + ch = readRawByte(t0, timeout).toChar() s.append(ch) } - read() + val first = codepointToString(readUtf8Int(t0, timeout)) + if (first.length > 1) { + return KeyboardEvent(first) // Got a utf8 char like an emoji + } else { + ch = first[0] + } if (ch == ESC) { escaped = true @@ -341,13 +348,13 @@ internal class PosixEventParser( private fun processMouseEvent(t0: ComparableTimeMark, timeout: Duration): InputEvent { // Mouse event coordinates are raw values, not decimal text, and they're sometimes utf-8 // encoded to fit larger values. - val cb = readUtf8Byte(t0, timeout) - val cx = readUtf8Byte(t0, timeout) - 33 + val cb = readUtf8Int(t0, timeout) + val cx = readUtf8Int(t0, timeout) - 33 // XXX: I've seen the terminal not send the third byte like `ESC [ M # W`, but I can't find // that pattern documented anywhere, so maybe it's an issue with the terminal emulator not // encoding utf8 correctly? val cy = runCatching { - readUtf8Byte(t0, timeout.coerceAtMost(1.milliseconds)) - 33 + readUtf8Int(t0, timeout.coerceAtMost(1.milliseconds)) - 33 }.getOrElse { 0 } val shift = (cb and 4) != 0 val alt = (cb and 8) != 0 @@ -368,7 +375,8 @@ internal class PosixEventParser( ) } - private fun readUtf8Byte(t0: ComparableTimeMark, timeout: Duration): Int { - return readBytesAsUtf8 { readRawByte(t0, timeout).code } + /** Read one utf-8 encoded codepoint from the input stream. */ + private fun readUtf8Int(t0: ComparableTimeMark, timeout: Duration): Int { + return readBytesAsUtf8 { readRawByte(t0, timeout) } } } diff --git a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/terminal/terminalinterface/TerminalInterface.posix.kt b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/terminal/terminalinterface/TerminalInterface.posix.kt index 538b71ca9..6237f8d34 100644 --- a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/terminal/terminalinterface/TerminalInterface.posix.kt +++ b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/terminal/terminalinterface/TerminalInterface.posix.kt @@ -93,7 +93,7 @@ abstract class TerminalInterfacePosix : StandardTerminalInterface() { abstract fun setStdinTermios(termios: Termios) abstract val termiosConstants: TermiosConstants protected abstract fun isatty(fd: Int): Boolean - protected abstract fun readRawByte(t0: ComparableTimeMark, timeout: Duration): Char + protected abstract fun readRawByte(t0: ComparableTimeMark, timeout: Duration): Int override fun stdoutInteractive(): Boolean = isatty(STDOUT_FILENO) override fun stdinInteractive(): Boolean = isatty(STDIN_FILENO) diff --git a/mordant/src/jvmMain/kotlin/com/github/ajalt/mordant/terminal/terminalinterface/TerminalInterface.jvm.posix.kt b/mordant/src/jvmMain/kotlin/com/github/ajalt/mordant/terminal/terminalinterface/TerminalInterface.jvm.posix.kt index cccd7b456..49351836f 100644 --- a/mordant/src/jvmMain/kotlin/com/github/ajalt/mordant/terminal/terminalinterface/TerminalInterface.jvm.posix.kt +++ b/mordant/src/jvmMain/kotlin/com/github/ajalt/mordant/terminal/terminalinterface/TerminalInterface.jvm.posix.kt @@ -7,10 +7,11 @@ import kotlin.time.Duration * A base class for terminal interfaces for JVM POSIX systems that uses `System.in` for input. */ abstract class TerminalInterfaceJvmPosix : TerminalInterfacePosix() { - override fun readRawByte(t0: ComparableTimeMark, timeout: Duration): Char { + override fun readRawByte(t0: ComparableTimeMark, timeout: Duration): Int { + Character.toString(1) do { val c = System.`in`.read() - if (c >= 0) return c.toChar() + if (c >= 0) return c } while (t0.elapsedNow() < timeout) throw RuntimeException("Timeout reading from stdin (timeout=$timeout)") } 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 7ef03f595..8c4c978f9 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 @@ -12,12 +12,12 @@ internal abstract class TerminalInterfaceNativePosix : TerminalInterfacePosix() return platform.posix.isatty(fd) != 0 } - override fun readRawByte(t0: ComparableTimeMark, timeout: Duration): Char = memScoped { + override fun readRawByte(t0: ComparableTimeMark, timeout: Duration): Int = memScoped { do { val c = alloc() val read = readIntoBuffer(c) if (read < 0) throw RuntimeException("Error reading from stdin") - if (read > 0) return c.value.toInt().toChar() + if (read > 0) return c.value.toInt() } while (t0.elapsedNow() < timeout) throw RuntimeException("Timeout reading from stdin (timeout=$timeout)") } From f82f01d168ad988e5f44e219aa36bea79d6fede0 Mon Sep 17 00:00:00 2001 From: AJ Date: Wed, 28 Aug 2024 18:42:18 -0700 Subject: [PATCH 6/6] Use TimeMarks for internal timeouts --- CHANGELOG.md | 1 + mordant/api/mordant.api | 18 ++++++----- .../com/github/ajalt/mordant/input/RawMode.kt | 20 ++++++------ .../com/github/ajalt/mordant/internal/Utf8.kt | 3 -- .../mordant/terminal/TerminalInterface.kt | 7 +++- .../mordant/terminal/TerminalRecorder.kt | 4 +-- .../terminalinterface/PosixEventParser.kt | 29 ++++++++--------- .../TerminalInterface.posix.kt | 9 +++--- .../TerminalInterface.windows.kt | 32 +++++++++++++++---- .../TerminalInterface.jsCommon.kt | 12 +++---- .../ajalt/mordant/internal/MppInternal.jvm.kt | 4 +-- .../TerminalInterface.jvm.posix.kt | 8 ++--- .../TerminalInterface.native.posix.kt | 7 ++-- 13 files changed, 85 insertions(+), 69 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7147cd9f4..3a15a16c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - 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. - Added ability to control raw mode with the `TerminalRecorder`. +- Added support for unicode input in raw mode. ### Changed - **Breaking Change** Moved `Terminal.info.width` and `height` to `Terminal.size.width` and `height`. diff --git a/mordant/api/mordant.api b/mordant/api/mordant.api index ab08c3c42..e2abc5f2e 100644 --- a/mordant/api/mordant.api +++ b/mordant/api/mordant.api @@ -1173,7 +1173,7 @@ public abstract class com/github/ajalt/mordant/terminal/StandardTerminalInterfac public fun enterRawMode (Lcom/github/ajalt/mordant/input/MouseTracking;)Ljava/lang/AutoCloseable; public fun getTerminalSize ()Lcom/github/ajalt/mordant/rendering/Size; public fun info (Lcom/github/ajalt/mordant/rendering/AnsiLevel;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;)Lcom/github/ajalt/mordant/terminal/TerminalInfo; - public fun readInputEvent-VtjQ1oo (JLcom/github/ajalt/mordant/input/MouseTracking;)Lcom/github/ajalt/mordant/input/InputEvent; + public fun readInputEvent (Lkotlin/time/TimeMark;Lcom/github/ajalt/mordant/input/MouseTracking;)Lcom/github/ajalt/mordant/input/InputEvent; public fun readLineOrNull (Z)Ljava/lang/String; public fun shouldAutoUpdateSize ()Z public fun stdinInteractive ()Z @@ -1311,7 +1311,7 @@ public abstract interface class com/github/ajalt/mordant/terminal/TerminalInterf public abstract fun enterRawMode (Lcom/github/ajalt/mordant/input/MouseTracking;)Ljava/lang/AutoCloseable; public abstract fun getTerminalSize ()Lcom/github/ajalt/mordant/rendering/Size; public abstract fun info (Lcom/github/ajalt/mordant/rendering/AnsiLevel;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;)Lcom/github/ajalt/mordant/terminal/TerminalInfo; - public abstract fun readInputEvent-VtjQ1oo (JLcom/github/ajalt/mordant/input/MouseTracking;)Lcom/github/ajalt/mordant/input/InputEvent; + public abstract fun readInputEvent (Lkotlin/time/TimeMark;Lcom/github/ajalt/mordant/input/MouseTracking;)Lcom/github/ajalt/mordant/input/InputEvent; public abstract fun readLineOrNull (Z)Ljava/lang/String; public abstract fun shouldAutoUpdateSize ()Z } @@ -1319,7 +1319,7 @@ public abstract interface class com/github/ajalt/mordant/terminal/TerminalInterf public final class com/github/ajalt/mordant/terminal/TerminalInterface$DefaultImpls { public static fun enterRawMode (Lcom/github/ajalt/mordant/terminal/TerminalInterface;Lcom/github/ajalt/mordant/input/MouseTracking;)Ljava/lang/AutoCloseable; public static fun getTerminalSize (Lcom/github/ajalt/mordant/terminal/TerminalInterface;)Lcom/github/ajalt/mordant/rendering/Size; - public static fun readInputEvent-VtjQ1oo (Lcom/github/ajalt/mordant/terminal/TerminalInterface;JLcom/github/ajalt/mordant/input/MouseTracking;)Lcom/github/ajalt/mordant/input/InputEvent; + public static fun readInputEvent (Lcom/github/ajalt/mordant/terminal/TerminalInterface;Lkotlin/time/TimeMark;Lcom/github/ajalt/mordant/input/MouseTracking;)Lcom/github/ajalt/mordant/input/InputEvent; public static fun shouldAutoUpdateSize (Lcom/github/ajalt/mordant/terminal/TerminalInterface;)Z } @@ -1333,12 +1333,14 @@ public final class com/github/ajalt/mordant/terminal/TerminalRecorder : com/gith public final fun clearOutput ()V public fun completePrintRequest (Lcom/github/ajalt/mordant/terminal/PrintRequest;)V public fun enterRawMode (Lcom/github/ajalt/mordant/input/MouseTracking;)Ljava/lang/AutoCloseable; + public final fun getInputEvents ()Ljava/util/List; public final fun getInputLines ()Ljava/util/List; public fun getTerminalSize ()Lcom/github/ajalt/mordant/rendering/Size; public fun info (Lcom/github/ajalt/mordant/rendering/AnsiLevel;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;)Lcom/github/ajalt/mordant/terminal/TerminalInfo; public final fun output ()Ljava/lang/String; - public fun readInputEvent-VtjQ1oo (JLcom/github/ajalt/mordant/input/MouseTracking;)Lcom/github/ajalt/mordant/input/InputEvent; + public fun readInputEvent (Lkotlin/time/TimeMark;Lcom/github/ajalt/mordant/input/MouseTracking;)Lcom/github/ajalt/mordant/input/InputEvent; public fun readLineOrNull (Z)Ljava/lang/String; + public final fun setInputEvents (Ljava/util/List;)V public final fun setInputLines (Ljava/util/List;)V public fun shouldAutoUpdateSize ()Z public final fun stderr ()Ljava/lang/String; @@ -1353,7 +1355,7 @@ public final class com/github/ajalt/mordant/terminal/YesNoPrompt : com/github/aj public abstract class com/github/ajalt/mordant/terminal/terminalinterface/TerminalInterfaceJvmPosix : com/github/ajalt/mordant/terminal/terminalinterface/TerminalInterfacePosix { public fun ()V - protected fun readRawByte-HG0u8IE (Lkotlin/time/ComparableTimeMark;J)C + protected fun readRawByte (Lkotlin/time/TimeMark;)I } public abstract class com/github/ajalt/mordant/terminal/terminalinterface/TerminalInterfacePosix : com/github/ajalt/mordant/terminal/StandardTerminalInterface { @@ -1366,8 +1368,8 @@ public abstract class com/github/ajalt/mordant/terminal/terminalinterface/Termin public abstract fun getStdinTermios ()Lcom/github/ajalt/mordant/terminal/terminalinterface/TerminalInterfacePosix$Termios; public abstract fun getTermiosConstants ()Lcom/github/ajalt/mordant/terminal/terminalinterface/TerminalInterfacePosix$TermiosConstants; protected abstract fun isatty (I)Z - public fun readInputEvent-VtjQ1oo (JLcom/github/ajalt/mordant/input/MouseTracking;)Lcom/github/ajalt/mordant/input/InputEvent; - protected abstract fun readRawByte-HG0u8IE (Lkotlin/time/ComparableTimeMark;J)C + public fun readInputEvent (Lkotlin/time/TimeMark;Lcom/github/ajalt/mordant/input/MouseTracking;)Lcom/github/ajalt/mordant/input/InputEvent; + protected abstract fun readRawByte (Lkotlin/time/TimeMark;)I public abstract fun setStdinTermios (Lcom/github/ajalt/mordant/terminal/terminalinterface/TerminalInterfacePosix$Termios;)V public fun stdinInteractive ()Z public fun stdoutInteractive ()Z @@ -1457,7 +1459,7 @@ public abstract class com/github/ajalt/mordant/terminal/terminalinterface/Termin public fun ()V public final fun enterRawMode (Lcom/github/ajalt/mordant/input/MouseTracking;)Ljava/lang/AutoCloseable; protected abstract fun getStdinConsoleMode-pVg5ArA ()I - public fun readInputEvent-VtjQ1oo (JLcom/github/ajalt/mordant/input/MouseTracking;)Lcom/github/ajalt/mordant/input/InputEvent; + public fun readInputEvent (Lkotlin/time/TimeMark;Lcom/github/ajalt/mordant/input/MouseTracking;)Lcom/github/ajalt/mordant/input/InputEvent; protected abstract fun readRawEvent (I)Lcom/github/ajalt/mordant/terminal/terminalinterface/TerminalInterfaceWindows$EventRecord; protected abstract fun setStdinConsoleMode-WZ4Q5Ns (I)V } diff --git a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/input/RawMode.kt b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/input/RawMode.kt index 278f7cf98..d9ab4fc85 100644 --- a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/input/RawMode.kt +++ b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/input/RawMode.kt @@ -55,7 +55,7 @@ class RawModeScope internal constructor( * @return The event, or `null` if no event was received before the timeout. */ fun readKeyOrNull(timeout: Duration = Duration.INFINITE): KeyboardEvent? { - return readEventsUntilTimeout(timeout) { it !is KeyboardEvent } as KeyboardEvent? + return readEventWithTimeout(timeout) { it !is KeyboardEvent } as KeyboardEvent? } /** @@ -75,7 +75,7 @@ class RawModeScope internal constructor( * @return The event, or `null` if no event was received before the timeout. */ fun readMouseOrNull(timeout: Duration = Duration.INFINITE): MouseEvent? { - return readEventsUntilTimeout(timeout) { it !is MouseEvent } as MouseEvent? + return readEventWithTimeout(timeout) { it !is MouseEvent } as MouseEvent? } /** @@ -95,20 +95,18 @@ class RawModeScope internal constructor( * @return The event, or `null` if no event was received before the timeout. */ fun readEventOrNull(timeout: Duration = Duration.INFINITE): InputEvent? { - return readEventsUntilTimeout(timeout) { false } + return readEventWithTimeout(timeout) { false } } - private inline fun readEventsUntilTimeout( + private inline fun readEventWithTimeout( timeout: Duration, skip: (InputEvent) -> Boolean, ): InputEvent? { - val t0 = TimeSource.Monotonic.markNow() + val t = TimeSource.Monotonic.markNow() + timeout do { - val event = terminal.terminalInterface - .readInputEvent(timeout - t0.elapsedNow(), mouseTracking) ?: continue - if (event !is MouseEvent || mouseTracking != MouseTracking.Off || skip(event)) { - return event - } - } while (t0.elapsedNow() < timeout) + val event = terminal.terminalInterface.readInputEvent(t, mouseTracking) ?: continue + if (event is MouseEvent && mouseTracking == MouseTracking.Off || skip(event)) continue + return event + } while (t.hasNotPassedNow()) return null } diff --git a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/internal/Utf8.kt b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/internal/Utf8.kt index 552cd49b6..053467257 100644 --- a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/internal/Utf8.kt +++ b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/internal/Utf8.kt @@ -1,8 +1,5 @@ package com.github.ajalt.mordant.internal -import kotlin.time.ComparableTimeMark -import kotlin.time.Duration - /** Read bytes from a UTF-8 encoded stream, and return the next codepoint. */ internal fun readBytesAsUtf8(readByte: () -> Int): Int { val byte = readByte() diff --git a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/terminal/TerminalInterface.kt b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/terminal/TerminalInterface.kt index f061e9902..4b1f7cc64 100644 --- a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/terminal/TerminalInterface.kt +++ b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/terminal/TerminalInterface.kt @@ -5,6 +5,7 @@ import com.github.ajalt.mordant.input.MouseTracking import com.github.ajalt.mordant.rendering.AnsiLevel import com.github.ajalt.mordant.rendering.Size import kotlin.time.Duration +import kotlin.time.TimeMark interface TerminalInterface { @@ -48,8 +49,12 @@ interface TerminalInterface { * * You would return `null` if you receive an event that should be ignored, like a mouse * event when mouse tracking is off. + * + * @param timeout The point in time that this call should block to while waiting for an event. + * If the timeout is in the past, this method should not block. + * @param mouseTracking The current mouse tracking mode. */ - fun readInputEvent(timeout: Duration, mouseTracking: MouseTracking): InputEvent? { + fun readInputEvent(timeout: TimeMark, mouseTracking: MouseTracking): InputEvent? { throw NotImplementedError("Reading input events is not supported on this terminal") } diff --git a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/terminal/TerminalRecorder.kt b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/terminal/TerminalRecorder.kt index 9ecaf853c..93e986292 100644 --- a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/terminal/TerminalRecorder.kt +++ b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/terminal/TerminalRecorder.kt @@ -4,7 +4,7 @@ import com.github.ajalt.mordant.input.InputEvent import com.github.ajalt.mordant.input.MouseTracking import com.github.ajalt.mordant.rendering.AnsiLevel import com.github.ajalt.mordant.rendering.Size -import kotlin.time.Duration +import kotlin.time.TimeMark /** * A [TerminalInterface] that records all output and allows you to provide input. @@ -100,7 +100,7 @@ class TerminalRecorder private constructor( ) } - override fun readInputEvent(timeout: Duration, mouseTracking: MouseTracking): InputEvent? { + override fun readInputEvent(timeout: TimeMark, mouseTracking: MouseTracking): InputEvent? { return inputEvents.removeFirstOrNull() } diff --git a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/terminal/terminalinterface/PosixEventParser.kt b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/terminal/terminalinterface/PosixEventParser.kt index 6ca886fe7..f4d2a5395 100644 --- a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/terminal/terminalinterface/PosixEventParser.kt +++ b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/terminal/terminalinterface/PosixEventParser.kt @@ -3,40 +3,37 @@ package com.github.ajalt.mordant.terminal.terminalinterface import com.github.ajalt.mordant.input.InputEvent import com.github.ajalt.mordant.input.KeyboardEvent import com.github.ajalt.mordant.input.MouseEvent -import com.github.ajalt.mordant.internal.codepointSequence import com.github.ajalt.mordant.internal.codepointToString import com.github.ajalt.mordant.internal.readBytesAsUtf8 -import kotlin.time.ComparableTimeMark -import kotlin.time.Duration import kotlin.time.Duration.Companion.milliseconds +import kotlin.time.TimeMark import kotlin.time.TimeSource private const val ESC = '\u001b' internal class PosixEventParser( - private val readRawByte: (t0: ComparableTimeMark, timeout: Duration) -> Int, + private val readRawByte: (timeout: TimeMark) -> Int, ) { /* Some patterns seen in terminal key escape codes, derived from combos seen at https://github.com/nodejs/node/blob/main/lib/internal/readline/utils.js */ - fun readInputEvent(timeout: Duration): InputEvent { - val t0 = TimeSource.Monotonic.markNow() + fun readInputEvent(timeout: TimeMark): InputEvent { var ctrl = false var alt = false var shift = false var escaped = false var name: String? = null val s = StringBuilder() - var ch = ' ' + var ch: Char fun read() { - ch = readRawByte(t0, timeout).toChar() + ch = readRawByte(timeout).toChar() s.append(ch) } - val first = codepointToString(readUtf8Int(t0, timeout)) + val first = codepointToString(readUtf8Int(timeout)) if (first.length > 1) { return KeyboardEvent(first) // Got a utf8 char like an emoji } else { @@ -86,7 +83,7 @@ internal class PosixEventParser( read() } else if (ch == 'M') { // mouse event - return processMouseEvent(t0, timeout) + return processMouseEvent(timeout) } val cmdStart = s.length - 1 @@ -345,16 +342,16 @@ internal class PosixEventParser( ) } - private fun processMouseEvent(t0: ComparableTimeMark, timeout: Duration): InputEvent { + private fun processMouseEvent(timeout: TimeMark): InputEvent { // Mouse event coordinates are raw values, not decimal text, and they're sometimes utf-8 // encoded to fit larger values. - val cb = readUtf8Int(t0, timeout) - val cx = readUtf8Int(t0, timeout) - 33 + val cb = readUtf8Int(timeout) + val cx = readUtf8Int(timeout) - 33 // XXX: I've seen the terminal not send the third byte like `ESC [ M # W`, but I can't find // that pattern documented anywhere, so maybe it's an issue with the terminal emulator not // encoding utf8 correctly? val cy = runCatching { - readUtf8Int(t0, timeout.coerceAtMost(1.milliseconds)) - 33 + readUtf8Int(TimeSource.Monotonic.markNow() + 1.milliseconds) - 33 }.getOrElse { 0 } val shift = (cb and 4) != 0 val alt = (cb and 8) != 0 @@ -376,7 +373,7 @@ internal class PosixEventParser( } /** Read one utf-8 encoded codepoint from the input stream. */ - private fun readUtf8Int(t0: ComparableTimeMark, timeout: Duration): Int { - return readBytesAsUtf8 { readRawByte(t0, timeout) } + private fun readUtf8Int(timeout: TimeMark): Int { + return readBytesAsUtf8 { readRawByte(timeout) } } } diff --git a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/terminal/terminalinterface/TerminalInterface.posix.kt b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/terminal/terminalinterface/TerminalInterface.posix.kt index 6237f8d34..0cb59dd66 100644 --- a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/terminal/terminalinterface/TerminalInterface.posix.kt +++ b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/terminal/terminalinterface/TerminalInterface.posix.kt @@ -4,8 +4,7 @@ import com.github.ajalt.mordant.input.InputEvent import com.github.ajalt.mordant.input.MouseTracking import com.github.ajalt.mordant.internal.CSI import com.github.ajalt.mordant.terminal.StandardTerminalInterface -import kotlin.time.ComparableTimeMark -import kotlin.time.Duration +import kotlin.time.TimeMark /** * A terminal interface for POSIX systems. @@ -93,7 +92,7 @@ abstract class TerminalInterfacePosix : StandardTerminalInterface() { abstract fun setStdinTermios(termios: Termios) abstract val termiosConstants: TermiosConstants protected abstract fun isatty(fd: Int): Boolean - protected abstract fun readRawByte(t0: ComparableTimeMark, timeout: Duration): Int + protected abstract fun readRawByte(timeout: TimeMark): Int override fun stdoutInteractive(): Boolean = isatty(STDOUT_FILENO) override fun stdinInteractive(): Boolean = isatty(STDIN_FILENO) @@ -127,8 +126,8 @@ abstract class TerminalInterfacePosix : StandardTerminalInterface() { } } - override fun readInputEvent(timeout: Duration, mouseTracking: MouseTracking): InputEvent? { - return PosixEventParser { t0, t -> readRawByte(t0, t) }.readInputEvent(timeout) + override fun readInputEvent(timeout: TimeMark, mouseTracking: MouseTracking): InputEvent? { + return PosixEventParser { t -> readRawByte(t) }.readInputEvent(timeout) } } diff --git a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/terminal/terminalinterface/TerminalInterface.windows.kt b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/terminal/terminalinterface/TerminalInterface.windows.kt index 576919d36..123997211 100644 --- a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/terminal/terminalinterface/TerminalInterface.windows.kt +++ b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/terminal/terminalinterface/TerminalInterface.windows.kt @@ -5,8 +5,7 @@ import com.github.ajalt.mordant.input.KeyboardEvent import com.github.ajalt.mordant.input.MouseEvent import com.github.ajalt.mordant.input.MouseTracking import com.github.ajalt.mordant.terminal.StandardTerminalInterface -import kotlin.time.Duration -import kotlin.time.TimeSource +import kotlin.time.TimeMark /** * A base TerminalInterface implementation for Windows systems. @@ -75,10 +74,14 @@ abstract class TerminalInterfaceWindows : StandardTerminalInterface() { return AutoCloseable { setStdinConsoleMode(originalMode) } } - override fun readInputEvent(timeout: Duration, mouseTracking: MouseTracking): InputEvent? { - val t0 = TimeSource.Monotonic.markNow() - val dwMilliseconds = (timeout - t0.elapsedNow()).inWholeMilliseconds - .coerceIn(0, Int.MAX_VALUE.toLong()).toInt() + override fun readInputEvent(timeout: TimeMark, mouseTracking: MouseTracking): InputEvent? { + val elapsed = timeout.elapsedNow() + val dwMilliseconds = when { + elapsed.isInfinite() -> Int.MAX_VALUE + elapsed.isPositive() -> 0 // positive elapsed is in the past + else -> -(elapsed.inWholeMilliseconds).coerceAtMost(Int.MAX_VALUE.toLong()).toInt() + } + return when (val event = readRawEvent(dwMilliseconds)) { null -> null is EventRecord.Key -> processKeyEvent(event) @@ -96,6 +99,23 @@ abstract class TerminalInterfaceWindows : StandardTerminalInterface() { else shiftVcodeToKey(virtualName) } + event.uChar in Char.MIN_SURROGATE..Char.MAX_SURROGATE -> { + // We got a surrogate pair, so we need to read the next char to get the full + // codepoint. Skip any key up events that might be in the queue. + var nextEvent: EventRecord? + do { + nextEvent = readRawEvent(0) + } while (nextEvent != null + && (nextEvent !is EventRecord.Key || !nextEvent.bKeyDown) + ) + if (nextEvent !is EventRecord.Key) { + event.uChar.toString() + } else { + charArrayOf(event.uChar, nextEvent.uChar).concatToString() + } + } + + virtualName != null -> virtualName event.uChar.code != 0 -> event.uChar.toString() else -> "Unidentified" diff --git a/mordant/src/jsCommonMain/kotlin/com/github/ajalt/mordant/terminal/terminalinterface/TerminalInterface.jsCommon.kt b/mordant/src/jsCommonMain/kotlin/com/github/ajalt/mordant/terminal/terminalinterface/TerminalInterface.jsCommon.kt index 3c9233bc2..36f95ac76 100644 --- a/mordant/src/jsCommonMain/kotlin/com/github/ajalt/mordant/terminal/terminalinterface/TerminalInterface.jsCommon.kt +++ b/mordant/src/jsCommonMain/kotlin/com/github/ajalt/mordant/terminal/terminalinterface/TerminalInterface.jsCommon.kt @@ -8,7 +8,7 @@ import com.github.ajalt.mordant.terminal.PrintTerminalCursor import com.github.ajalt.mordant.terminal.StandardTerminalInterface import com.github.ajalt.mordant.terminal.Terminal import com.github.ajalt.mordant.terminal.TerminalCursor -import kotlin.time.Duration +import kotlin.time.TimeMark internal abstract class TerminalInterfaceJsCommon : StandardTerminalInterface() { abstract fun readEnvvar(key: String): String? @@ -39,14 +39,14 @@ internal abstract class TerminalInterfaceNode : TerminalInterfaceJsComm abstract fun bufferToString(buffer: BufferT): String abstract fun readSync(fd: Int, buffer: BufferT, offset: Int, len: Int): Int - override fun readInputEvent(timeout: Duration, mouseTracking: MouseTracking): InputEvent? { - return PosixEventParser { t0, t -> + override fun readInputEvent(timeout: TimeMark, mouseTracking: MouseTracking): InputEvent? { + return PosixEventParser { t -> val buf = allocBuffer(1) do { - val c = readByteWithBuf(buf)?.let { it[0] } + val c = readByteWithBuf(buf)?.let { it[0].code } if (c != null) return@PosixEventParser c - } while (t0.elapsedNow() < t) - throw RuntimeException("Timeout reading from stdin (timeout=$timeout)") + } while (t.hasNotPassedNow()) + throw RuntimeException("Timeout reading from stdin (timeout=${timeout.elapsedNow()})") }.readInputEvent(timeout) } diff --git a/mordant/src/jvmMain/kotlin/com/github/ajalt/mordant/internal/MppInternal.jvm.kt b/mordant/src/jvmMain/kotlin/com/github/ajalt/mordant/internal/MppInternal.jvm.kt index 6be7a6029..3ab786e0b 100644 --- a/mordant/src/jvmMain/kotlin/com/github/ajalt/mordant/internal/MppInternal.jvm.kt +++ b/mordant/src/jvmMain/kotlin/com/github/ajalt/mordant/internal/MppInternal.jvm.kt @@ -12,7 +12,7 @@ import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicReference import kotlin.io.path.readText import kotlin.system.exitProcess -import kotlin.time.Duration +import kotlin.time.TimeMark private class JvmAtomicRef(value: T) : MppAtomicRef { private val ref = AtomicReference(value) @@ -165,7 +165,7 @@ private object DumbTerminalInterface : StandardTerminalInterface() { throw UnsupportedOperationException("Cannot enter raw mode on this system") } - override fun readInputEvent(timeout: Duration, mouseTracking: MouseTracking): InputEvent? { + override fun readInputEvent(timeout: TimeMark, mouseTracking: MouseTracking): InputEvent? { throw UnsupportedOperationException("Cannot read input on this system") } } diff --git a/mordant/src/jvmMain/kotlin/com/github/ajalt/mordant/terminal/terminalinterface/TerminalInterface.jvm.posix.kt b/mordant/src/jvmMain/kotlin/com/github/ajalt/mordant/terminal/terminalinterface/TerminalInterface.jvm.posix.kt index 49351836f..01264fb1d 100644 --- a/mordant/src/jvmMain/kotlin/com/github/ajalt/mordant/terminal/terminalinterface/TerminalInterface.jvm.posix.kt +++ b/mordant/src/jvmMain/kotlin/com/github/ajalt/mordant/terminal/terminalinterface/TerminalInterface.jvm.posix.kt @@ -1,18 +1,16 @@ package com.github.ajalt.mordant.terminal.terminalinterface -import kotlin.time.ComparableTimeMark -import kotlin.time.Duration +import kotlin.time.TimeMark /** * A base class for terminal interfaces for JVM POSIX systems that uses `System.in` for input. */ abstract class TerminalInterfaceJvmPosix : TerminalInterfacePosix() { - override fun readRawByte(t0: ComparableTimeMark, timeout: Duration): Int { - Character.toString(1) + override fun readRawByte(timeout: TimeMark): Int { do { val c = System.`in`.read() if (c >= 0) return c - } while (t0.elapsedNow() < timeout) + } while (timeout.hasNotPassedNow()) throw RuntimeException("Timeout reading from stdin (timeout=$timeout)") } } 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 8c4c978f9..5464e624d 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 @@ -4,21 +4,20 @@ import kotlinx.cinterop.ByteVar import kotlinx.cinterop.alloc import kotlinx.cinterop.memScoped import kotlinx.cinterop.value -import kotlin.time.ComparableTimeMark -import kotlin.time.Duration +import kotlin.time.TimeMark internal abstract class TerminalInterfaceNativePosix : TerminalInterfacePosix() { override fun isatty(fd: Int): Boolean { return platform.posix.isatty(fd) != 0 } - override fun readRawByte(t0: ComparableTimeMark, timeout: Duration): Int = memScoped { + override fun readRawByte(timeout: TimeMark): Int = memScoped { do { val c = alloc() val read = readIntoBuffer(c) if (read < 0) throw RuntimeException("Error reading from stdin") if (read > 0) return c.value.toInt() - } while (t0.elapsedNow() < timeout) + } while (timeout.hasNotPassedNow()) throw RuntimeException("Timeout reading from stdin (timeout=$timeout)") }