Skip to content

Commit 4ca8b5c

Browse files
committed
textarea: support dynamic prompts
The dynamic prompt can be different on every line. See for example: [![asciicast](https://asciinema.org/a/iFBPBwoDZOzcoRJOgfmPk8ObH.svg)](https://asciinema.org/a/iFBPBwoDZOzcoRJOgfmPk8ObH)
1 parent fba1956 commit 4ca8b5c

File tree

1 file changed

+55
-4
lines changed

1 file changed

+55
-4
lines changed

textarea/textarea.go

+55-4
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,11 @@ type Model struct {
119119
// General settings.
120120

121121
// Prompt is printed at the beginning of each line.
122+
//
123+
// When changing the value of Prompt after the model has been
124+
// initialized, ensure that SetWidth() gets called afterwards.
125+
//
126+
// See also SetPromptFunc().
122127
Prompt string
123128

124129
// Placeholder is the text displayed when the user
@@ -152,6 +157,13 @@ type Model struct {
152157
// accept. If 0 or less, there's no limit.
153158
CharLimit int
154159

160+
// If promptFunc is set, it replaces Prompt as a generator for
161+
// prompt strings at the beginning of each line.
162+
promptFunc func(line int) string
163+
164+
// promptWidth is the width of the prompt.
165+
promptWidth int
166+
155167
// width is the maximum number of characters that can be displayed at once.
156168
// If 0 or less this setting is ignored.
157169
width int
@@ -694,10 +706,26 @@ func (m *Model) SetWidth(w int) {
694706
// Account for base style borders and padding.
695707
inputWidth -= m.style.Base.GetHorizontalFrameSize()
696708

697-
inputWidth -= rw.StringWidth(m.Prompt)
709+
if m.promptFunc == nil {
710+
m.promptWidth = rw.StringWidth(m.Prompt)
711+
}
712+
713+
inputWidth -= m.promptWidth
698714
m.width = clamp(inputWidth, minWidth, maxWidth)
699715
}
700716

717+
// SetPromptFunc supersedes the Prompt field and sets a dynamic prompt
718+
// instead.
719+
// If the function returns a prompt that is shorter than the
720+
// specified promptWidth, it will be padded to the left.
721+
// If it returns a prompt that is longer, display artifacts
722+
// may occur; the caller is responsible for computing an adequate
723+
// promptWidth.
724+
func (m *Model) SetPromptFunc(promptWidth int, fn func(lineIdx int) string) {
725+
m.promptFunc = fn
726+
m.promptWidth = promptWidth
727+
}
728+
701729
// Height returns the current height of the textarea.
702730
func (m Model) Height() int {
703731
return m.height
@@ -860,6 +888,7 @@ func (m Model) View() string {
860888

861889
var newLines int
862890

891+
displayLine := 0
863892
for l, line := range m.value {
864893
wrappedLines := wrap(line, m.width)
865894

@@ -870,7 +899,10 @@ func (m Model) View() string {
870899
}
871900

872901
for wl, wrappedLine := range wrappedLines {
873-
s.WriteString(style.Render(m.style.Prompt.Render(m.Prompt)))
902+
prompt := m.getPromptString(displayLine)
903+
prompt = m.style.Prompt.Render(prompt)
904+
s.WriteString(prompt)
905+
displayLine++
874906

875907
if m.ShowLineNumbers {
876908
if wl == 0 {
@@ -919,7 +951,10 @@ func (m Model) View() string {
919951
// Always show at least `m.Height` lines at all times.
920952
// To do this we can simply pad out a few extra new lines in the view.
921953
for i := 0; i < m.height; i++ {
922-
s.WriteString(m.style.Prompt.Render(m.Prompt))
954+
prompt := m.getPromptString(displayLine)
955+
prompt = m.style.Prompt.Render(prompt)
956+
s.WriteString(prompt)
957+
displayLine++
923958

924959
if m.ShowLineNumbers {
925960
lineNumber := m.style.EndOfBuffer.Render((fmt.Sprintf(m.lineNumberFormat, string(m.EndOfBufferCharacter))))
@@ -932,6 +967,19 @@ func (m Model) View() string {
932967
return m.style.Base.Render(m.viewport.View())
933968
}
934969

970+
func (m Model) getPromptString(displayLine int) (prompt string) {
971+
prompt = m.Prompt
972+
if m.promptFunc == nil {
973+
return prompt
974+
}
975+
prompt = m.promptFunc(displayLine)
976+
pl := rw.StringWidth(prompt)
977+
if pl < m.promptWidth {
978+
prompt = fmt.Sprintf("%*s%s", m.promptWidth-pl, "", prompt)
979+
}
980+
return prompt
981+
}
982+
935983
// placeholderView returns the prompt and placeholder view, if any.
936984
func (m Model) placeholderView() string {
937985
var (
@@ -940,7 +988,8 @@ func (m Model) placeholderView() string {
940988
style = m.style.Placeholder.Inline(true)
941989
)
942990

943-
prompt := m.style.Prompt.Render(m.Prompt)
991+
prompt := m.getPromptString(0)
992+
prompt = m.style.Prompt.Render(prompt)
944993
s.WriteString(m.style.CursorLine.Render(prompt))
945994

946995
if m.ShowLineNumbers {
@@ -957,6 +1006,8 @@ func (m Model) placeholderView() string {
9571006
// The rest of the new lines
9581007
for i := 1; i < m.height; i++ {
9591008
s.WriteRune('\n')
1009+
prompt := m.getPromptString(i)
1010+
prompt = m.style.Prompt.Render(prompt)
9601011
s.WriteString(prompt)
9611012

9621013
if m.ShowLineNumbers {

0 commit comments

Comments
 (0)