Skip to content

Commit 5ef32c3

Browse files
committed
Make textarea work with a promptfunc
.. as discussed in charmbracelet/bubbles#211
1 parent fa2daf8 commit 5ef32c3

3 files changed

Lines changed: 96 additions & 54 deletions

File tree

editline/editline.go

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -310,10 +310,9 @@ func (m *Model) Focus() tea.Cmd {
310310
m.text.KeyMap = m.KeyMap.KeyMap
311311
m.text.Placeholder = m.Placeholder
312312
m.text.ShowLineNumbers = m.ShowLineNumbers
313-
m.text.Prompt = m.Prompt
314-
m.text.NextPrompt = m.NextPrompt
315313
m.text.FocusedStyle = m.FocusedStyle.Editor
316314
m.text.BlurredStyle = m.BlurredStyle.Editor
315+
m.updatePrompt()
317316
m.hctrl.pattern.PromptStyle = m.FocusedStyle.SearchInput.PromptStyle
318317
m.hctrl.pattern.TextStyle = m.FocusedStyle.SearchInput.TextStyle
319318
m.hctrl.pattern.BackgroundStyle = m.FocusedStyle.SearchInput.BackgroundStyle
@@ -443,6 +442,24 @@ func (m *Model) updateTextSz() (cmd tea.Cmd) {
443442
return cmd
444443
}
445444

445+
func (m *Model) updatePrompt() {
446+
prompt, nextPrompt := m.Prompt, m.NextPrompt
447+
if m.hidePrompt {
448+
prompt, nextPrompt = "", ""
449+
}
450+
promptWidth := max(rw.StringWidth(prompt), rw.StringWidth(nextPrompt))
451+
m.text.Prompt = ""
452+
m.text.SetPromptFunc(promptWidth, func(line int) string {
453+
if line == 0 {
454+
return prompt
455+
}
456+
return nextPrompt
457+
})
458+
// Recompute the width.
459+
m.text.SetWidth(m.maxWidth - 1)
460+
m.text.SetCursor(0)
461+
}
462+
446463
func (m *Model) saveValue() {
447464
m.hctrl.c.valueSaved = true
448465
m.hctrl.c.prevValue = m.text.Value()
@@ -574,16 +591,7 @@ func (m *Model) Update(imsg tea.Msg) (tea.Model, tea.Cmd) {
574591

575592
case key.Matches(msg, m.KeyMap.HideShowPrompt):
576593
m.hidePrompt = !m.hidePrompt
577-
if m.hidePrompt {
578-
m.text.Prompt = ""
579-
m.text.NextPrompt = ""
580-
} else {
581-
m.text.Prompt = m.Prompt
582-
m.text.NextPrompt = m.NextPrompt
583-
}
584-
// Recompute the width.
585-
m.text.SetWidth(m.maxWidth)
586-
m.text.SetCursor(0)
594+
m.updatePrompt()
587595
cmd = tea.Batch(cmd, m.updateTextSz())
588596
imsg = nil // consume message
589597

editline/internal/textarea/extra.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ func (m Model) LogicalHeight() int {
6767
func (m Model) Debug() string {
6868
var buf strings.Builder
6969
fmt.Fprintf(&buf, "focus: %v\n", m.focus)
70+
fmt.Fprintf(&buf, "promptWidth: %d\n", m.promptWidth)
7071
fmt.Fprintf(&buf, "width: %d, height: %d\n", m.width, m.height)
7172
fmt.Fprintf(&buf, "col: %d, row: %d\n", m.col, m.row)
7273
fmt.Fprintf(&buf, "lastCharOffset: %d\n", m.lastCharOffset)

editline/internal/textarea/textarea.go

Lines changed: 75 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,6 @@ type Style struct {
127127
LineNumber lipgloss.Style
128128
Placeholder lipgloss.Style
129129
Prompt lipgloss.Style
130-
NextPrompt lipgloss.Style
131130
Text lipgloss.Style
132131
}
133132

@@ -136,15 +135,28 @@ type Model struct {
136135
Err error
137136

138137
// General settings.
139-
Prompt string
140-
Placeholder string
141-
ShowLineNumbers bool
138+
139+
// Prompt is printed at the beginning of each line.
140+
//
141+
// When changing the value of Prompt after the model has been
142+
// initialized, ensure that SetWidth() gets called afterwards.
143+
//
144+
// See also SetPromptFunc().
145+
Prompt string
146+
147+
// Placeholder is the text displayed when the user
148+
// hasn't entered anything yet.
149+
Placeholder string
150+
151+
// ShowLineNumbers, if enabled, causes line numbers to be printed
152+
// after the prompt.
153+
ShowLineNumbers bool
154+
155+
// EndOfBufferCharacter is displayed at the end of the input.
142156
EndOfBufferCharacter rune
143-
KeyMap KeyMap
144157

145-
// NextPrompt, if set, is used for all lines
146-
// except the first.
147-
NextPrompt string
158+
// KeyMap encodes the keybindings recognized by the widget.
159+
KeyMap KeyMap
148160

149161
// Styling. FocusedStyle and BlurredStyle are used to style the textarea in
150162
// focused and blurred states.
@@ -163,6 +175,13 @@ type Model struct {
163175
// accept. If 0 or less, there's no limit.
164176
CharLimit int
165177

178+
// If promptFunc is set, it replaces Prompt as a generator for
179+
// prompt strings at the beginning of each line.
180+
promptFunc func(line int) string
181+
182+
// promptWidth is the width of the prompt.
183+
promptWidth int
184+
166185
// width is the maximum number of characters that can be displayed at once.
167186
// If 0 or less this setting is ignored.
168187
width int
@@ -245,7 +264,6 @@ func DefaultStyles() (Style, Style) {
245264
LineNumber: lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "249", Dark: "7"}),
246265
Placeholder: lipgloss.NewStyle().Foreground(lipgloss.Color("240")),
247266
Prompt: lipgloss.NewStyle().Foreground(lipgloss.Color("7")),
248-
NextPrompt: lipgloss.NewStyle().Foreground(lipgloss.Color("7")),
249267
Text: lipgloss.NewStyle(),
250268
}
251269
blurred := Style{
@@ -256,7 +274,6 @@ func DefaultStyles() (Style, Style) {
256274
LineNumber: lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "249", Dark: "7"}),
257275
Placeholder: lipgloss.NewStyle().Foreground(lipgloss.Color("240")),
258276
Prompt: lipgloss.NewStyle().Foreground(lipgloss.Color("7")),
259-
NextPrompt: lipgloss.NewStyle().Foreground(lipgloss.Color("7")),
260277
Text: lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "245", Dark: "7"}),
261278
}
262279

@@ -791,10 +808,26 @@ func (m *Model) SetWidth(w int) {
791808
// Account for base style borders and padding.
792809
inputWidth -= m.style.Base.GetHorizontalFrameSize()
793810

794-
inputWidth -= max(rw.StringWidth(m.Prompt), rw.StringWidth(m.NextPrompt))
811+
if m.promptFunc == nil {
812+
m.promptWidth = rw.StringWidth(m.Prompt)
813+
}
814+
815+
inputWidth -= m.promptWidth
795816
m.width = clamp(inputWidth, minWidth, maxWidth)
796817
}
797818

819+
// SetPromptFunc supersedes the Prompt field and sets a dynamic prompt
820+
// instead.
821+
// If the function returns a prompt that is shorter than the
822+
// specified promptWidth, it will be padded to the left.
823+
// If it returns a prompt that is longer, display artifacts
824+
// may occur; the caller is responsible for computing an adequate
825+
// promptWidth.
826+
func (m *Model) SetPromptFunc(promptWidth int, fn func(lineIdx int) string) {
827+
m.promptFunc = fn
828+
m.promptWidth = promptWidth
829+
}
830+
798831
// Height returns the current height of the textarea.
799832
func (m Model) Height() int {
800833
return m.height
@@ -929,11 +962,17 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
929962
case key.Matches(msg, m.KeyMap.ToggleOverwriteMode):
930963
m.overwrite = !m.overwrite
931964
default:
932-
if m.CharLimit > 0 && rw.StringWidth(m.Value())+len(msg.Runes) >= m.CharLimit {
933-
break
934-
}
935-
for _, r := range msg.Runes {
936-
m.InsertRune(r)
965+
if !m.overwrite {
966+
if m.CharLimit > 0 && rw.StringWidth(m.Value())+len(msg.Runes) >= m.CharLimit {
967+
break
968+
}
969+
for _, r := range msg.Runes {
970+
m.InsertRune(r)
971+
}
972+
} else {
973+
for _, r := range msg.Runes {
974+
m.overwriteRune(r)
975+
}
937976
}
938977
}
939978

@@ -974,11 +1013,7 @@ func (m Model) View() string {
9741013

9751014
var newLines int
9761015

977-
prompt, nextPrompt := m.getPromptStrings()
978-
prompt = m.style.Prompt.Render(prompt)
979-
nextPrompt = m.style.NextPrompt.Render(nextPrompt)
980-
981-
firstDisplayLine := true
1016+
displayLine := 0
9821017
for l, line := range m.value {
9831018
wrappedLines := wrap(line, m.width)
9841019

@@ -989,12 +1024,10 @@ func (m Model) View() string {
9891024
}
9901025

9911026
for wl, wrappedLine := range wrappedLines {
992-
selectedPrompt := nextPrompt
993-
if firstDisplayLine {
994-
selectedPrompt = prompt
995-
firstDisplayLine = false
996-
}
997-
s.WriteString(style.Render(selectedPrompt))
1027+
prompt := m.getPromptString(displayLine)
1028+
prompt = m.style.Prompt.Render(prompt)
1029+
s.WriteString(style.Render(prompt))
1030+
displayLine++
9981031

9991032
if m.ShowLineNumbers {
10001033
if wl == 0 {
@@ -1043,7 +1076,10 @@ func (m Model) View() string {
10431076
// Always show at least `m.Height` lines at all times.
10441077
// To do this we can simply pad out a few extra new lines in the view.
10451078
for i := 0; i < m.height; i++ {
1046-
s.WriteString(nextPrompt)
1079+
prompt := m.getPromptString(displayLine)
1080+
prompt = m.style.Prompt.Render(prompt)
1081+
s.WriteString(prompt)
1082+
displayLine++
10471083

10481084
if m.ShowLineNumbers {
10491085
lineNumber := m.style.EndOfBuffer.Render((fmt.Sprintf(m.lineNumberFormat, string(m.EndOfBufferCharacter))))
@@ -1056,20 +1092,17 @@ func (m Model) View() string {
10561092
return m.style.Base.Render(m.viewport.View())
10571093
}
10581094

1059-
func (m Model) getPromptStrings() (prompt, nextPrompt string) {
1095+
func (m Model) getPromptString(displayLine int) (prompt string) {
10601096
prompt = m.Prompt
1061-
nextPrompt = m.NextPrompt
1062-
if nextPrompt == "" {
1063-
return prompt, prompt
1097+
if m.promptFunc == nil {
1098+
return prompt
10641099
}
1100+
prompt = m.promptFunc(displayLine)
10651101
pl := rw.StringWidth(prompt)
1066-
npl := rw.StringWidth(nextPrompt)
1067-
if pl > npl {
1068-
nextPrompt = fmt.Sprintf("%*s", pl-npl, "") + nextPrompt
1069-
} else if npl > pl {
1070-
prompt = fmt.Sprintf("%*s", npl-pl, "") + prompt
1102+
if pl < m.promptWidth {
1103+
prompt = fmt.Sprintf("%*s%s", m.promptWidth-pl, "", prompt)
10711104
}
1072-
return prompt, nextPrompt
1105+
return prompt
10731106
}
10741107

10751108
// placeholderView returns the prompt and placeholder view, if any.
@@ -1080,8 +1113,7 @@ func (m Model) placeholderView() string {
10801113
style = m.style.Placeholder.Inline(true)
10811114
)
10821115

1083-
prompt, nextPrompt := m.getPromptStrings()
1084-
1116+
prompt := m.getPromptString(0)
10851117
prompt = m.style.Prompt.Render(prompt)
10861118
s.WriteString(m.style.CursorLine.Render(prompt))
10871119

@@ -1097,10 +1129,11 @@ func (m Model) placeholderView() string {
10971129
s.WriteString(m.style.CursorLine.Render(style.Render(p[1:] + strings.Repeat(" ", max(0, m.width-rw.StringWidth(p))))))
10981130

10991131
// The rest of the new lines
1100-
nextPrompt = m.style.NextPrompt.Render(nextPrompt)
11011132
for i := 1; i < m.height; i++ {
11021133
s.WriteRune('\n')
1103-
s.WriteString(nextPrompt)
1134+
prompt := m.getPromptString(i)
1135+
prompt = m.style.Prompt.Render(prompt)
1136+
s.WriteString(prompt)
11041137

11051138
if m.ShowLineNumbers {
11061139
eob := m.style.EndOfBuffer.Render((fmt.Sprintf(m.lineNumberFormat, string(m.EndOfBufferCharacter))))

0 commit comments

Comments
 (0)