diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a1835e4..a25ee3b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # Changelog ## Unreleased +### Added +- Possibility to specify hidden return value in `Terminal.interactiveSelectList`, `Terminal.interactiveMultiSelectList`, and `InteractiveSelectListBuilder`. ## 3.0.1 ### Fixed - Fixed terminal size detection in the `mordant-jvm-ffm` module on macOS [(#238)](https://github.com/ajalt/mordant/issues/238) diff --git a/mordant/api/mordant.api b/mordant/api/mordant.api index 86f9075c..1f334889 100644 --- a/mordant/api/mordant.api +++ b/mordant/api/mordant.api @@ -212,10 +212,14 @@ public final class com/github/ajalt/mordant/input/InteractiveSelectListBuilder { public fun (Lcom/github/ajalt/mordant/terminal/Terminal;)V public final fun addEntry (Lcom/github/ajalt/mordant/widgets/SelectList$Entry;)Lcom/github/ajalt/mordant/input/InteractiveSelectListBuilder; public final fun addEntry (Ljava/lang/String;)Lcom/github/ajalt/mordant/input/InteractiveSelectListBuilder; + public final fun addEntry (Ljava/lang/String;Lcom/github/ajalt/mordant/rendering/Widget;)Lcom/github/ajalt/mordant/input/InteractiveSelectListBuilder; public final fun addEntry (Ljava/lang/String;Lcom/github/ajalt/mordant/rendering/Widget;Z)Lcom/github/ajalt/mordant/input/InteractiveSelectListBuilder; + public final fun addEntry (Ljava/lang/String;Lcom/github/ajalt/mordant/rendering/Widget;ZLjava/lang/String;)Lcom/github/ajalt/mordant/input/InteractiveSelectListBuilder; + public final fun addEntry (Ljava/lang/String;Ljava/lang/String;)Lcom/github/ajalt/mordant/input/InteractiveSelectListBuilder; public final fun addEntry (Ljava/lang/String;Ljava/lang/String;Z)Lcom/github/ajalt/mordant/input/InteractiveSelectListBuilder; - public static synthetic fun addEntry$default (Lcom/github/ajalt/mordant/input/InteractiveSelectListBuilder;Ljava/lang/String;Lcom/github/ajalt/mordant/rendering/Widget;ZILjava/lang/Object;)Lcom/github/ajalt/mordant/input/InteractiveSelectListBuilder; - public static synthetic fun addEntry$default (Lcom/github/ajalt/mordant/input/InteractiveSelectListBuilder;Ljava/lang/String;Ljava/lang/String;ZILjava/lang/Object;)Lcom/github/ajalt/mordant/input/InteractiveSelectListBuilder; + public final fun addEntry (Ljava/lang/String;Ljava/lang/String;ZLjava/lang/String;)Lcom/github/ajalt/mordant/input/InteractiveSelectListBuilder; + public static synthetic fun addEntry$default (Lcom/github/ajalt/mordant/input/InteractiveSelectListBuilder;Ljava/lang/String;Lcom/github/ajalt/mordant/rendering/Widget;ZLjava/lang/String;ILjava/lang/Object;)Lcom/github/ajalt/mordant/input/InteractiveSelectListBuilder; + public static synthetic fun addEntry$default (Lcom/github/ajalt/mordant/input/InteractiveSelectListBuilder;Ljava/lang/String;Ljava/lang/String;ZLjava/lang/String;ILjava/lang/Object;)Lcom/github/ajalt/mordant/input/InteractiveSelectListBuilder; public final fun clearOnExit (Z)Lcom/github/ajalt/mordant/input/InteractiveSelectListBuilder; public final fun createMultiSelectInputAnimation ()Lcom/github/ajalt/mordant/input/InputReceiverAnimation; public final fun createSingleSelectInputAnimation ()Lcom/github/ajalt/mordant/input/InputReceiverAnimation; @@ -436,10 +440,6 @@ public final class com/github/ajalt/mordant/rendering/Line : java/util/List, kot public synthetic fun add (Ljava/lang/Object;)Z public fun addAll (ILjava/util/Collection;)Z public fun addAll (Ljava/util/Collection;)Z - public fun addFirst (Lcom/github/ajalt/mordant/rendering/Span;)V - public synthetic fun addFirst (Ljava/lang/Object;)V - public fun addLast (Lcom/github/ajalt/mordant/rendering/Span;)V - public synthetic fun addLast (Ljava/lang/Object;)V public fun clear ()V public final fun component1 ()Ljava/util/List; public final fun component2 ()Lcom/github/ajalt/mordant/rendering/TextStyle; @@ -467,10 +467,6 @@ public final class com/github/ajalt/mordant/rendering/Line : java/util/List, kot public synthetic fun remove (I)Ljava/lang/Object; public fun remove (Ljava/lang/Object;)Z public fun removeAll (Ljava/util/Collection;)Z - public fun removeFirst ()Lcom/github/ajalt/mordant/rendering/Span; - public synthetic fun removeFirst ()Ljava/lang/Object; - public fun removeLast ()Lcom/github/ajalt/mordant/rendering/Span; - public synthetic fun removeLast ()Ljava/lang/Object; public fun replaceAll (Ljava/util/function/UnaryOperator;)V public fun retainAll (Ljava/util/Collection;)Z public fun set (ILcom/github/ajalt/mordant/rendering/Span;)Lcom/github/ajalt/mordant/rendering/Span; @@ -1573,19 +1569,26 @@ public final class com/github/ajalt/mordant/widgets/SelectList : com/github/ajal } public final class com/github/ajalt/mordant/widgets/SelectList$Entry { + public fun (Ljava/lang/String;)V + public fun (Ljava/lang/String;Lcom/github/ajalt/mordant/rendering/Widget;)V public fun (Ljava/lang/String;Lcom/github/ajalt/mordant/rendering/Widget;Z)V - public synthetic fun (Ljava/lang/String;Lcom/github/ajalt/mordant/rendering/Widget;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/String;Lcom/github/ajalt/mordant/rendering/Widget;ZLjava/lang/String;)V + public synthetic fun (Ljava/lang/String;Lcom/github/ajalt/mordant/rendering/Widget;ZLjava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/String;Ljava/lang/String;)V public fun (Ljava/lang/String;Ljava/lang/String;Z)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/String;Ljava/lang/String;ZLjava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;ZLjava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Ljava/lang/String; public final fun component2 ()Lcom/github/ajalt/mordant/rendering/Widget; public final fun component3 ()Z - public final fun copy (Ljava/lang/String;Lcom/github/ajalt/mordant/rendering/Widget;Z)Lcom/github/ajalt/mordant/widgets/SelectList$Entry; - public static synthetic fun copy$default (Lcom/github/ajalt/mordant/widgets/SelectList$Entry;Ljava/lang/String;Lcom/github/ajalt/mordant/rendering/Widget;ZILjava/lang/Object;)Lcom/github/ajalt/mordant/widgets/SelectList$Entry; + public final fun component4 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Lcom/github/ajalt/mordant/rendering/Widget;ZLjava/lang/String;)Lcom/github/ajalt/mordant/widgets/SelectList$Entry; + public static synthetic fun copy$default (Lcom/github/ajalt/mordant/widgets/SelectList$Entry;Ljava/lang/String;Lcom/github/ajalt/mordant/rendering/Widget;ZLjava/lang/String;ILjava/lang/Object;)Lcom/github/ajalt/mordant/widgets/SelectList$Entry; public fun equals (Ljava/lang/Object;)Z public final fun getDescription ()Lcom/github/ajalt/mordant/rendering/Widget; public final fun getSelected ()Z public final fun getTitle ()Ljava/lang/String; + public final fun getValue ()Ljava/lang/String; public fun hashCode ()I public fun toString ()Ljava/lang/String; } diff --git a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/input/InteractiveSelectList.kt b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/input/InteractiveSelectList.kt index d9c1597f..1699765a 100644 --- a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/input/InteractiveSelectList.kt +++ b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/input/InteractiveSelectList.kt @@ -7,7 +7,7 @@ import kotlin.jvm.JvmName /** * Display a list of items and allow the user to select one with the arrow keys and enter. * - * @return the selected item title, or null if the user canceled the selection + * @return the selected item value, or title if value is not specified, or null if the user canceled the selection */ inline fun Terminal.interactiveSelectList( block: InteractiveSelectListBuilder.() -> Unit, @@ -41,7 +41,7 @@ fun Terminal.interactiveSelectList( * * @param entries The list of items to select from * @param title The title to display above the list - * @return the selected item title, or null if the user canceled the selection + * @return the selected item value, or title if value is not specified, or null if the user canceled the selection */ @JvmName("interactiveSelectListEntry") fun Terminal.interactiveSelectList( @@ -57,7 +57,7 @@ fun Terminal.interactiveSelectList( /** * Display a list of items and allow the user to select zero or more with the arrow keys and enter. * - * @return the selected item titles, or null if the user canceled the selection + * @return the selected item value, or title if value is not specified, or null if the user canceled the selection */ inline fun Terminal.interactiveMultiSelectList( block: InteractiveSelectListBuilder.() -> Unit, @@ -73,7 +73,7 @@ inline fun Terminal.interactiveMultiSelectList( * * @param entries The list of items to select from * @param title The title to display above the list - * @return the selected item titles, or null if the user canceled the selection + * @return the selected item value, or title if value is not specified, or null if the user canceled the selection */ @JvmName("interactiveMultiSelectListEntry") fun Terminal.interactiveMultiSelectList( diff --git a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/input/SelectListAnimation.kt b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/input/SelectListAnimation.kt index 4fc853ed..9befdc10 100644 --- a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/input/SelectListAnimation.kt +++ b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/input/SelectListAnimation.kt @@ -12,6 +12,7 @@ import com.github.ajalt.mordant.terminal.Terminal import com.github.ajalt.mordant.widgets.SelectList import com.github.ajalt.mordant.widgets.Text import kotlin.jvm.JvmName +import kotlin.jvm.JvmOverloads private class SelectConfig( var entries: MutableList = mutableListOf(), @@ -77,21 +78,25 @@ class InteractiveSelectListBuilder(private val terminal: Terminal) { } /** Add an item to the list of items to select from */ + @JvmOverloads fun addEntry( title: String, description: String, selected: Boolean = false, + value: String? = null, ): InteractiveSelectListBuilder = apply { - config.entries += SelectList.Entry(title, description, selected) + config.entries += SelectList.Entry(title, description, selected, value) } /** Add an item to the list of items to select from */ + @JvmOverloads fun addEntry( title: String, description: Widget? = null, selected: Boolean = false, + value: String? = null ): InteractiveSelectListBuilder = apply { - config.entries += SelectList.Entry(title, description, selected) + config.entries += SelectList.Entry(title, description, selected, value) } /** Add an item to the list of items to select from */ @@ -99,11 +104,6 @@ class InteractiveSelectListBuilder(private val terminal: Terminal) { config.entries += entry } - /** Add an item to the list of items to select from */ - fun addEntry(entry: String): InteractiveSelectListBuilder = apply { - config.entries += SelectList.Entry(entry) - } - /** Set the title to display above the list */ fun title(title: String): InteractiveSelectListBuilder = apply { config.title = when { @@ -386,10 +386,10 @@ private class SelectInputAnimation( } key == keySubmit -> { - if (singleSelect) copy(finished = true, result = listOf(entry.title)) + if (singleSelect) copy(finished = true, result = listOf(entry.value ?: entry.title)) else copy( finished = true, - result = items.filter { it.selected }.map { it.title }) + result = items.filter { it.selected }.map { it.value ?: it.title }) } filtering && !key.alt && !key.ctrl -> { diff --git a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/widgets/SelectList.kt b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/widgets/SelectList.kt index e2448d06..b0cae866 100644 --- a/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/widgets/SelectList.kt +++ b/mordant/src/commonMain/kotlin/com/github/ajalt/mordant/widgets/SelectList.kt @@ -8,6 +8,7 @@ import com.github.ajalt.mordant.table.Borders import com.github.ajalt.mordant.table.table import com.github.ajalt.mordant.table.verticalLayout import com.github.ajalt.mordant.terminal.Terminal +import kotlin.jvm.JvmOverloads /** * A list widget with selectable items. @@ -56,19 +57,23 @@ class SelectList private constructor( unselectedMarkerStyle = ThemeStyle.of("select.unselected-marker", unselectedMarkerStyle), ) - data class Entry( + data class Entry @JvmOverloads constructor( /** The title of the entry. */ val title: String, /** An optional description of the entry. */ val description: Widget? = null, /** Whether this entry is marked as selected. */ val selected: Boolean = false, + /** Return this value instead of title if not null. */ + val value: String? = null, ) { - constructor(title: String, description: String?, selected: Boolean = false) + @JvmOverloads + constructor(title: String, description: String?, selected: Boolean = false, value: String? = null) : this( title = title, description = description?.let { Text(it, whitespace = Whitespace.PRE_WRAP) }, - selected = selected + selected = selected, + value = value ) } 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 index fbb92778..2a7a196e 100644 --- a/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/input/InteractiveSelectListTest.kt +++ b/mordant/src/commonTest/kotlin/com/github/ajalt/mordant/input/InteractiveSelectListTest.kt @@ -16,28 +16,38 @@ class InteractiveSelectListTest { private val t = Terminal(terminalInterface = rec) @[Test JsName("single_select_strings")] - fun `single select strings`() = doSingleSelectTest { + fun `single select strings`() = doSingleSelectTest({ t.interactiveSelectList(listOf("a", "b", "c")) - } + }, "b") @[Test JsName("single_select_entries")] - fun `single select entries`() = doSingleSelectTest { + fun `single select entries`() = doSingleSelectTest({ t.interactiveSelectList(listOf(Entry("a"), Entry("b"), Entry("c"))) - } + }, "b") + + @[Test JsName("single_select_entries_with_value")] + fun `single select entries with value`() = doSingleSelectTest({ + t.interactiveSelectList(listOf(Entry("a", value = "AA"), Entry("b", value = "BB"), Entry("c", value = "CC"))) + }, "BB") @[Test JsName("multi_select_strings")] - fun `multi select strings`() = doMultiSelectTest { + fun `multi select strings`() = doMultiSelectTest({ t.interactiveMultiSelectList(listOf("a", "b", "c")) - } + }, listOf("b", "c")) @[Test JsName("multi_select_entries")] - fun `multi select entries`() = doMultiSelectTest { + fun `multi select entries`() = doMultiSelectTest({ t.interactiveMultiSelectList(listOf(Entry("a"), Entry("b"), Entry("c"))) - } + }, listOf("b", "c")) + + @[Test JsName("multi_select_entries_with_values")] + fun `multi select entries with values`() = doMultiSelectTest({ + t.interactiveMultiSelectList(listOf(Entry("a", value = "AA"), Entry("b", value = "BB"), Entry("c", value = "CC"))) + }, listOf("BB", "CC")) - private fun doSingleSelectTest(runList: () -> String?) { + private fun doSingleSelectTest(runList: () -> String?, expected: String) { rec.inputEvents = mutableListOf(KeyboardEvent("ArrowDown"), KeyboardEvent("Enter")) - runList() shouldBe "b" + runList() shouldBe expected rec.stdout() shouldContain """ ░❯ a ░ b @@ -45,7 +55,7 @@ class InteractiveSelectListTest { """.trimMargin("░") } - private fun doMultiSelectTest(runList: () -> List?) { + private fun doMultiSelectTest(runList: () -> List?, expected: List) { rec.inputEvents = mutableListOf( KeyboardEvent("ArrowDown"), KeyboardEvent("x"), @@ -53,7 +63,7 @@ class InteractiveSelectListTest { KeyboardEvent("x"), KeyboardEvent("Enter"), ) - runList() shouldBe listOf("b", "c") + runList() shouldBe expected rec.stdout() shouldContain """ ░❯ • a ░ • b