๐Ÿ“ฆ sleepyfran / duets

๐Ÿ“„ ChoicePrompt.fs ยท 188 lines
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188[<AutoOpen>]
module Duets.Cli.Components.ChoicePrompt

open Duets.Common
open Spectre.Console
open Duets.Cli.Text

let private showSelection choice =
    showSeparator None

    Generic.choiceSelection choice |> showMessage

    showSeparator None

let private createChoicePrompt title optionTextFn (choices: 'a seq) =
    let mutable selectionPrompt = SelectionPrompt<'a>()

    selectionPrompt.Title <- title
    selectionPrompt <- selectionPrompt.AddChoices(choices)
    selectionPrompt <- selectionPrompt.UseConverter(fun c -> optionTextFn c)
    selectionPrompt

/// <summary>
/// Shows the user a list of options to choose from, forcing them to choose one
/// in order to continue.
/// </summary>
/// <param name="title">Title of the prompt</param>
/// <param name="optionTextFn">
/// Function to transform an item from the choice sequence into text
/// </param>
/// <param name="choices">Sequence of options to show to the user</param>
/// <returns>The selected item</returns>
let showChoicePrompt<'a> title optionTextFn (choices: 'a seq) =
    createChoicePrompt title optionTextFn choices
    |> AnsiConsole.Prompt
    |> Pipe.tap (optionTextFn >> showSelection)

/// <summary>
/// Shows the user a list of options to choose from, with a text field to
/// be able to search. Forcing them to choose one in order to continue.
/// </summary>
/// <param name="title">Title of the prompt</param>
/// <param name="optionTextFn">
/// Function to transform an item from the choice sequence into text
/// </param>
/// <param name="choices">Sequence of options to show to the user</param>
/// <returns>The selected item</returns>
let showSearchableChoicePrompt<'a> title optionTextFn (choices: 'a seq) =
    let mutable selectionPrompt = createChoicePrompt title optionTextFn choices
    selectionPrompt.SearchEnabled <- true

    selectionPrompt
    |> AnsiConsole.Prompt
    |> Pipe.tap (optionTextFn >> showSelection)

let private optionalTextFn optionTextFn backText opt =
    match opt with
    | Some item -> optionTextFn item
    | None -> backText


let private createOptionalChoicePrompt title backText optionTextFn choices =
    choices
    |> Seq.map Some
    |> fun options -> Seq.append options [ None ]
    |> createChoicePrompt title (optionalTextFn optionTextFn backText)

/// <summary>
/// Shows the user a list of options to choose from, with one extra option that
/// acts as an alternative choice to not select any of the items.
/// </summary>
/// <param name="title">Title of the prompt</param>
/// <param name="backText">
/// Text for the <i>cancel</i> or <i>back</i> option
/// </param>
/// <param name="optionTextFn">
/// Function to transform an item from the choice sequence into text
/// </param>
/// <param name="choices">Sequence of options to show to the user</param>
/// <returns>
/// An item wrapped in <i>Some</i> if the user selected an item or <i>None</i>
/// if they selected the <i>cancel</i> option.
/// </returns>
let showOptionalChoicePrompt title backText optionTextFn choices =
    createOptionalChoicePrompt title backText optionTextFn choices
    |> AnsiConsole.Prompt
    |> Pipe.tap (optionalTextFn optionTextFn backText >> showSelection)

/// <summary>
/// Shows the user a list of options to choose from, with the option to cancel
/// the selection by pressing Esc.
/// </summary>
/// <param name="title">Title of the prompt</param>
/// <param name="cancelOptionText">
/// Text for the <i>cancel</i> or <i>back</i> option
/// </param>
/// <param name="optionTextFn">
/// Function to transform an item from the choice sequence into text
/// </param>
/// <param name="choices">Sequence of options to show to the user</param>
/// <returns>
/// An item wrapped in <i>Some</i> if the user selected an item or <i>None</i>
/// if they selected the <i>cancel</i> option.
/// </returns>
let showCancellableChoicePrompt
    title
    cancelOptionText
    optionTextFn
    (choices: 'a seq)
    =
    let mutable selectionPrompt = createChoicePrompt title optionTextFn choices

    selectionPrompt.AbortPlaceholderText <-
        $"Press Esc to {cancelOptionText}" |> Styles.faded

    match AnsiConsole.TryPrompt(selectionPrompt) with
    | true, item ->
        item |> optionTextFn |> showSelection
        Some item
    | false, _ ->
        showSelection cancelOptionText
        None

/// <summary>
/// Shows the user a list of options to choose from, with the option to cancel
/// the selection by pressing Esc.
/// </summary>
/// <param name="title">Title of the prompt</param>
/// <param name="cancelOptionText">
/// Text for the <i>cancel</i> or <i>back</i> option
/// </param>
/// <param name="optionTextFn">
/// Function to transform an item from the choice sequence into text
/// </param>
/// <param name="choices">Sequence of options to show to the user</param>
/// <returns>
/// An item wrapped in <i>Some</i> if the user selected an item or <i>None</i>
/// if they selected the <i>cancel</i> option.
/// </returns>
let showSearchableOptionalChoicePrompt
    title
    cancelOptionText
    optionTextFn
    (choices: 'a seq)
    =
    let mutable selectionPrompt = createChoicePrompt title optionTextFn choices

    selectionPrompt.SearchEnabled <- true

    selectionPrompt.AbortPlaceholderText <-
        $"Press Esc to {cancelOptionText}" |> Styles.faded

    match AnsiConsole.TryPrompt(selectionPrompt) with
    | true, item ->
        item |> optionTextFn |> showSelection
        Some item
    | false, _ ->
        showSelection cancelOptionText
        None

/// <summary>
/// Shows the user a list of options to choose from, allowing them to choose
/// one or multiple items.
/// </summary>
/// <param name="title">Title of the prompt</param>
/// <param name="optionTextFn">
/// Function to transform an item from the choice sequence into text
/// </param>
/// <param name="choices">Sequence of options to show to the user</param>
/// <returns>The selected item(s)</returns>
let showMultiChoicePrompt<'a> title optionTextFn (choices: 'a seq) =
    let mutable multiSelectionPrompt = MultiSelectionPrompt<'a>()

    multiSelectionPrompt.Title <- title

    multiSelectionPrompt.MoreChoicesText <- Generic.multiChoiceMoreChoices

    multiSelectionPrompt.InstructionsText <- Generic.multiChoiceInstructions

    multiSelectionPrompt.Required <- true
    multiSelectionPrompt.PageSize <- 10
    multiSelectionPrompt <- multiSelectionPrompt.AddChoices(choices)

    multiSelectionPrompt <-
        multiSelectionPrompt.UseConverter(fun c -> optionTextFn c)

    AnsiConsole.Prompt(multiSelectionPrompt) |> List.ofSeq