-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathprompt.go
242 lines (204 loc) · 9.78 KB
/
prompt.go
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
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
/*
Package selection implements a selection prompt that allows users to to select
one of the pre-defined choices. It also offers customizable appreance and key
map as well as optional support for pagination, filtering.
*/
package selection
import (
"fmt"
"github.com/cnlubo/myssh/common"
"github.com/mattn/go-runewidth"
"io"
"os"
"strconv"
"strings"
"text/template"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
)
const (
// DefaultFilterPrompt is the default prompt for the filter input when
// filtering is enabled.
DefaultFilterPrompt = "Filter:"
// DefaultFilterPlaceholder is printed by default when no filter text was
// entered yet.
DefaultFilterPlaceholder = "Type to filter choices"
// accentColor = termenv.ANSI256Color(32)
defaultHeader = "Use the arrow keys to navigate: ↓ ↑ → ←"
DefaultCursor = "»"
)
func DefaultSelectedChoiceStyle(m *Model, obj *Choice, gdIndex int) string {
return common.RenderedText("["+strconv.Itoa(obj.Index+1)+"] "+obj.String, "green")
}
func DefaultUnselectedChoiceStyle(m *Model, obj *Choice, gdIndex int) string {
return common.GenSpaces(runewidth.StringWidth(m.Cursor)+1) + common.RenderedText(strconv.Itoa(obj.Index+1)+". "+obj.String, "blue")
}
// DefaultFinalChoiceStyle is the default style for final choices.
func DefaultFinalChoiceStyle(c *Choice) string {
// return termenv.String(c.String).Foreground(accentColor).String()
return common.RenderedText(c.String, "green")
}
// Selection represents a configurable selection prompt.
type Selection struct {
// Choices represent all selectable choices of the selection. Slices of
// arbitrary types can be converted to a slice of choices using the helper
// selection.Choices.
Choices []*Choice
// Cursor cursor rendering style
Cursor string
// Prompt holds the the prompt text or question that is to be answered by
// one of the choices.
Prompt string
// FilterPrompt is the prompt for the filter if filtering is enabled.
FilterPrompt string
// Filter is a function that decides whether a given choice should be
// displayed based on the text entered by the user into the filter input
// field. If Filter is nil, filtering will be disabled. By default the
// filter FilterContainsCaseInsensitive is used.
Filter func(filterText string, choice *Choice) bool
// FilterPlaceholder holds the text that is displayed in the filter input
// field when no text was entered by the user yet. If empty, the
// DefaultFilterPlaceholder is used. If Filter is nil, filtering is disabled
// and FilterPlaceholder does nothing.
FilterPlaceholder string
// PageSize is the number of choices that are displayed at once. If PageSize
// is smaller than the number of choices, pagination is enabled. If PageSize
// is 0, pagenation is disabled. Regardless of the value of PageSize,
// pagination is always enabled when the prompt does not fit the terminal.
PageSize int
// Template holds the display template. A custom template can be used to
// completely customize the appearance of the selection prompt. If empty,
// the DefaultTemplate is used. The following variables and functions are
// available:
//
// * Prompt string: The configured prompt.
// * IsFiltered bool: Whether or not filtering is enabled.
// * FilterPrompt string: The configured filter prompt.
// * FilterInput string: The view of the filter input model.
// * Choices []*Choice: The choices on the current page.
// * NChoices int: The number of choices on the current page.
// * SelectedIndex int: The index that is currently selected.
// * PageSize int: The configured page size.
// * IsPaged bool: Whether pagination is currently active.
// * AllChoices []*Choice: All configured choices.
// * NAllChoices int: The number of configured choices.
// * TerminalWidth int: The width of the terminal.
// * Selected(*Choice) string: The configured SelectedChoiceStyle.
// * Unselected(*Choice) string: The configured UnselectedChoiceStyle.
// * IsScrollDownHintPosition(idx int) bool: Returns whether
// the scroll down hint shoud be displayed at the given index.
// * IsScrollUpHintPosition(idx int) bool: Returns whether the
// scroll up hint shoud be displayed at the given index).
// * promptkit.UtilFuncMap: Handy helper functions.
// * termenv TemplateFuncs (see https://github.com/muesli/termenv).
// * The functions specified in ExtendedTemplateFuncs.
Template string
// ResultTemplate is rendered as soon as a choice has been selected.
// It is intended to permanently indicate the result of the prompt when the
// selection itself has disappeared. This template is only rendered in the
// Run() method and NOT when the selection prompt is used as a model. The
// following variables and functions are available:
//
// * FinalChoice: The choice that was selected by the user.
// * Prompt string: The configured prompt.
// * AllChoices []*Choice: All configured choices.
// * NAllChoices int: The number of configured choices.
// * TerminalWidth int: The width of the terminal.
// * Final(*Choice) string: The configured FinalChoiceStyle.
// * promptkit.UtilFuncMap: Handy helper functions.
// * termenv TemplateFuncs (see https://github.com/muesli/termenv).
// * The functions specified in ExtendedTemplateFuncs.
ResultTemplate string
// ExtendedTemplateFuncs can be used to add additional functions to the
// evaluation scope of the templates.
ExtendedTemplateFuncs template.FuncMap
// Styles of the filter input field. These will be applied as inline styles.
//
// For an introduction to styling with Lip Gloss see:
// https://github.com/charmbracelet/lipgloss
FilterInputTextStyle lipgloss.Style
FilterInputBackgroundStyle lipgloss.Style
FilterInputPlaceholderStyle lipgloss.Style
FilterInputCursorStyle lipgloss.Style
// SelectedChoice style allows to customize the appearance of the currently
// selected choice. By default DefaultSelectedChoiceStyle is used. If it is
// nil, no style will be applied and the plain string representation of the
// choice will be used. This style will be available as the template
// function Selected. Custom templates may or may not use this function.
SelectedChoiceStyle func(m *Model, obj *Choice, gdIndex int) string
// SelectedChoiceStyle func(*Choice) string
// UnselectedChoiceStyle style allows to customize the appearance of the
// currently unselected choice. By default it is nil, such that no style
// will be applied and the plain string representation of the choice will be
// used.This style will be available as the template function Unselected.
// Custom templates may or may not use this function.
// UnselectedChoiceStyle func(*Choice) string
UnselectedChoiceStyle func(m *Model, obj *Choice, gdIndex int) string
// FinalChoiceStyle style allows to customize the appearance of the choice
// that was ultimately chosen. By default DefaultFinalChoiceStyle is used.
// If it is nil, no style will be applied and the plain string
// representation of the choice will be used. This style will be available
// as the template function Final. Custom templates may or may not use this
// function.
FinalChoiceStyle func(*Choice) string
// KeyMap determines with which keys the selection prompt is controlled. By
// default, DefaultKeyMap is used.
KeyMap *KeyMap
// WrapMode decides which way the prompt view is wrapped if it does not fit
// the terminal. It can be a WrapMode provided by promptkit or a custom
// function. By default it is promptkit.WordWrap. It can also be nil which
// disables wrapping and likely causes output glitches.
WrapMode common.WrapMode
// Output is the output writer, by default os.Stdout is used.
Output io.Writer
// Input is the input reader, by default, os.Stdin is used.
Input io.Reader
//// ColorProfile determines how colors are rendered. By default, the terminal
//// is queried.
//ColorProfile termenv.Profile
}
// New creates a new selection prompt.
func New(prompt string, choices []*Choice) *Selection {
return &Selection{
Choices: choices,
Prompt: prompt,
Cursor: DefaultCursor,
FilterPrompt: DefaultFilterPrompt,
Template: DefaultTemplate,
ResultTemplate: DefaultResultTemplate,
Filter: FilterContainsCaseInsensitive,
FilterInputPlaceholderStyle: lipgloss.NewStyle().Foreground(lipgloss.Color("240")),
SelectedChoiceStyle: DefaultSelectedChoiceStyle,
UnselectedChoiceStyle: DefaultUnselectedChoiceStyle,
FinalChoiceStyle: DefaultFinalChoiceStyle,
KeyMap: NewDefaultKeyMap(),
FilterPlaceholder: DefaultFilterPlaceholder,
ExtendedTemplateFuncs: template.FuncMap{},
WrapMode: common.Truncate,
Output: os.Stdout,
Input: os.Stdin,
}
}
// RunPrompt executes the selection prompt.
func (s *Selection) RunPrompt() (*Choice, error) {
err := validateKeyMap(s.KeyMap)
if err != nil {
return nil, fmt.Errorf("insufficient key map: %w", err)
}
m := NewModel(s)
p := tea.NewProgram(m, tea.WithOutput(s.Output), tea.WithInput(s.Input))
if err := p.Start(); err != nil {
return nil, fmt.Errorf("running prompt: %w", err)
}
return m.Value()
}
// FilterContainsCaseInsensitive returns true if the string representation of
// the choice contains the filter string without regard for capitalization.
func FilterContainsCaseInsensitive(filter string, choice *Choice) bool {
return strings.Contains(strings.ToLower(choice.String), strings.ToLower(filter))
}
// FilterContainsCaseSensitive returns true if the string representation of the
// choice contains the filter string respecting capitalization.
func FilterContainsCaseSensitive(filter string, choice *Choice) bool {
return strings.Contains(choice.String, filter)
}