Skip to content

Commit 36483f4

Browse files
committed
feat(textarea) Add multiline placeholder
Add the capability to show a multiline placeholder. Some refactoring was required to improve readability and improve logic. End of line buffer character was only shown when line numbers were displayed which requires some verification whether this is the intended outcome. This change resolves this issue.
1 parent bd562f9 commit 36483f4

2 files changed

Lines changed: 458 additions & 19 deletions

File tree

textarea/textarea.go

Lines changed: 55 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package textarea
33
import (
44
"crypto/sha256"
55
"fmt"
6+
"strconv"
67
"strings"
78
"unicode"
89

@@ -14,6 +15,7 @@ import (
1415
"github.com/charmbracelet/bubbles/viewport"
1516
tea "github.com/charmbracelet/bubbletea"
1617
"github.com/charmbracelet/lipgloss"
18+
"github.com/charmbracelet/x/ansi"
1719
rw "github.com/mattn/go-runewidth"
1820
"github.com/rivo/uniseg"
1921
)
@@ -1170,36 +1172,71 @@ func (m Model) getPromptString(displayLine int) (prompt string) {
11701172
func (m Model) placeholderView() string {
11711173
var (
11721174
s strings.Builder
1173-
p = rw.Truncate(rw.Truncate(m.Placeholder, m.width, "..."), m.width, "")
1175+
p = m.Placeholder
11741176
style = m.style.Placeholder.Inline(true)
11751177
)
11761178

1177-
prompt := m.getPromptString(0)
1178-
prompt = m.style.Prompt.Render(prompt)
1179-
s.WriteString(m.style.CursorLine.Render(prompt))
1179+
// word wrap lines
1180+
pwordwrap := ansi.Wordwrap(p, m.width, "")
1181+
// wrap lines (handles lines that could not be word wrapped)
1182+
pwrap := ansi.Hardwrap(pwordwrap, m.width, true)
1183+
// split string by new lines
1184+
plines := strings.Split(strings.TrimSpace(pwrap), "\n")
11801185

1181-
if m.ShowLineNumbers {
1182-
s.WriteString(m.style.CursorLine.Render(m.style.CursorLineNumber.Render((fmt.Sprintf(m.lineNumberFormat, 1)))))
1183-
}
1184-
1185-
m.Cursor.TextStyle = m.style.Placeholder
1186-
m.Cursor.SetChar(string(p[0]))
1187-
s.WriteString(m.style.CursorLine.Render(m.Cursor.View()))
1188-
1189-
// The rest of the placeholder text
1190-
s.WriteString(m.style.CursorLine.Render(style.Render(p[1:] + strings.Repeat(" ", max(0, m.width-uniseg.StringWidth(p))))))
1186+
for i := 0; i < m.height; i++ {
1187+
lineStyle := m.style.Placeholder
1188+
lineNumberStyle := m.style.LineNumber
1189+
if len(plines) > i {
1190+
lineStyle = m.style.CursorLine
1191+
lineNumberStyle = m.style.CursorLineNumber
1192+
}
11911193

1192-
// The rest of the new lines
1193-
for i := 1; i < m.height; i++ {
1194-
s.WriteRune('\n')
1194+
// render prompt
11951195
prompt := m.getPromptString(i)
11961196
prompt = m.style.Prompt.Render(prompt)
1197-
s.WriteString(prompt)
1197+
s.WriteString(lineStyle.Render(prompt))
11981198

1199+
// when show line numbers enabled:
1200+
// - render line number for only the cursor line
1201+
// - indent other placeholder lines
1202+
// this is consistent with vim with line numbers enabled
11991203
if m.ShowLineNumbers {
1204+
var ln string
1205+
1206+
switch {
1207+
case i == 0:
1208+
ln = strconv.Itoa(i + 1)
1209+
fallthrough
1210+
case len(plines) > i:
1211+
s.WriteString(lineStyle.Render(lineNumberStyle.Render(fmt.Sprintf(m.lineNumberFormat, ln))))
1212+
default:
1213+
}
1214+
}
1215+
1216+
switch {
1217+
// first line
1218+
case i == 0:
1219+
// first character of first line as cursor with character
1220+
m.Cursor.TextStyle = m.style.Placeholder
1221+
m.Cursor.SetChar(string(plines[0][0]))
1222+
s.WriteString(lineStyle.Render(m.Cursor.View()))
1223+
1224+
// the rest of the first line
1225+
s.WriteString(lineStyle.Render(style.Render(plines[0][1:] + strings.Repeat(" ", max(0, m.width-uniseg.StringWidth(plines[0]))))))
1226+
// remaining lines
1227+
case len(plines) > i:
1228+
// current line placeholder text
1229+
if len(plines) > i {
1230+
s.WriteString(lineStyle.Render(style.Render(plines[i] + strings.Repeat(" ", max(0, m.width-uniseg.StringWidth(plines[i]))))))
1231+
}
1232+
default:
1233+
// end of line buffer character
12001234
eob := m.style.EndOfBuffer.Render(string(m.EndOfBufferCharacter))
12011235
s.WriteString(eob)
12021236
}
1237+
1238+
// terminate with new line
1239+
s.WriteRune('\n')
12031240
}
12041241

12051242
m.viewport.SetContent(s.String())

0 commit comments

Comments
 (0)