Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

textarea(v2): highlight, formatter, suggestions #657

Draft
wants to merge 23 commits into
base: v2-exp
Choose a base branch
from
Draft
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
8c3085a
feat(textarea): use a real cursor position
aymanbagabas Nov 13, 2024
c350e79
fix(textarea): reset real cursor position on Reset
aymanbagabas Nov 13, 2024
804c370
fix(textarea): respect double-width characters in real cursor position
aymanbagabas Nov 13, 2024
eb9b6c5
chore: merge branch 'v2-exp' into v2-real-textarea
aymanbagabas Nov 26, 2024
7073d1a
wip: area
caarlos0 Sep 5, 2024
2ec013b
wip
caarlos0 Sep 11, 2024
8789a98
wip
caarlos0 Sep 11, 2024
ca9cbbc
wip
caarlos0 Sep 11, 2024
4755f88
fix: comp
caarlos0 Sep 12, 2024
56f7197
fix: move to last line on accept
caarlos0 Sep 12, 2024
4c214e0
feat: formatter
caarlos0 Sep 12, 2024
afbb7bc
feat(textarea): add SetOffset and CursorPosition methods
aymanbagabas Oct 28, 2024
c288adf
fix: fmt
caarlos0 Nov 1, 2024
d020289
wip
aymanbagabas Nov 26, 2024
6abd576
chore(merge): branch 'v2-exp' into v2-area
meowgorithm Jan 22, 2025
3ba296c
(v2) Revert cursor position from v2-area (#709)
aymanbagabas Jan 23, 2025
88778ca
chore(merge): branch 'v2-exp' into v2-area-textarea-api-updates
meowgorithm Jan 31, 2025
644c7b3
chore(textarea): move completion check
meowgorithm Feb 1, 2025
eec8e1c
Merge branch 'v2-exp' into v2-area
caarlos0 Feb 4, 2025
4a3501c
chore: merge branch 'v2-exp' into v2-area
aymanbagabas Feb 4, 2025
55a73b9
Merge remote-tracking branch 'origin/v2-exp' into v2-area
caarlos0 Feb 5, 2025
1588e33
fix: improve suggestion navigation on multiline input
caarlos0 Feb 6, 2025
06f1729
Merge remote-tracking branch 'origin/v2-exp' into v2-area
caarlos0 Feb 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
253 changes: 221 additions & 32 deletions textarea/textarea.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"crypto/sha256"
"fmt"
"image/color"
"reflect"
"strconv"
"strings"
"time"
Expand Down Expand Up @@ -66,36 +67,41 @@
CapitalizeWordForward key.Binding

TransposeCharacterBackward key.Binding

AcceptSuggestion key.Binding
NextSuggestion key.Binding
PrevSuggestion key.Binding
}

// DefaultKeyMap returns the default set of key bindings for navigating and acting
// upon the textarea.
func DefaultKeyMap() KeyMap {
return KeyMap{
CharacterForward: key.NewBinding(key.WithKeys("right", "ctrl+f"), key.WithHelp("right", "character forward")),
CharacterBackward: key.NewBinding(key.WithKeys("left", "ctrl+b"), key.WithHelp("left", "character backward")),
WordForward: key.NewBinding(key.WithKeys("alt+right", "alt+f"), key.WithHelp("alt+right", "word forward")),
WordBackward: key.NewBinding(key.WithKeys("alt+left", "alt+b"), key.WithHelp("alt+left", "word backward")),
LineNext: key.NewBinding(key.WithKeys("down", "ctrl+n"), key.WithHelp("down", "next line")),
LinePrevious: key.NewBinding(key.WithKeys("up", "ctrl+p"), key.WithHelp("up", "previous line")),
DeleteWordBackward: key.NewBinding(key.WithKeys("alt+backspace", "ctrl+w"), key.WithHelp("alt+backspace", "delete word backward")),
DeleteWordForward: key.NewBinding(key.WithKeys("alt+delete", "alt+d"), key.WithHelp("alt+delete", "delete word forward")),
DeleteAfterCursor: key.NewBinding(key.WithKeys("ctrl+k"), key.WithHelp("ctrl+k", "delete after cursor")),
DeleteBeforeCursor: key.NewBinding(key.WithKeys("ctrl+u"), key.WithHelp("ctrl+u", "delete before cursor")),
InsertNewline: key.NewBinding(key.WithKeys("enter", "ctrl+m"), key.WithHelp("enter", "insert newline")),
DeleteCharacterBackward: key.NewBinding(key.WithKeys("backspace", "ctrl+h"), key.WithHelp("backspace", "delete character backward")),
DeleteCharacterForward: key.NewBinding(key.WithKeys("delete", "ctrl+d"), key.WithHelp("delete", "delete character forward")),
LineStart: key.NewBinding(key.WithKeys("home", "ctrl+a"), key.WithHelp("home", "line start")),
LineEnd: key.NewBinding(key.WithKeys("end", "ctrl+e"), key.WithHelp("end", "line end")),
Paste: key.NewBinding(key.WithKeys("ctrl+v"), key.WithHelp("ctrl+v", "paste")),
InputBegin: key.NewBinding(key.WithKeys("alt+<", "ctrl+home"), key.WithHelp("alt+<", "input begin")),
InputEnd: key.NewBinding(key.WithKeys("alt+>", "ctrl+end"), key.WithHelp("alt+>", "input end")),

CapitalizeWordForward: key.NewBinding(key.WithKeys("alt+c"), key.WithHelp("alt+c", "capitalize word forward")),
LowercaseWordForward: key.NewBinding(key.WithKeys("alt+l"), key.WithHelp("alt+l", "lowercase word forward")),
UppercaseWordForward: key.NewBinding(key.WithKeys("alt+u"), key.WithHelp("alt+u", "uppercase word forward")),

CharacterForward: key.NewBinding(key.WithKeys("right", "ctrl+f"), key.WithHelp("right", "character forward")),
CharacterBackward: key.NewBinding(key.WithKeys("left", "ctrl+b"), key.WithHelp("left", "character backward")),
WordForward: key.NewBinding(key.WithKeys("alt+right", "alt+f"), key.WithHelp("alt+right", "word forward")),
WordBackward: key.NewBinding(key.WithKeys("alt+left", "alt+b"), key.WithHelp("alt+left", "word backward")),
LineNext: key.NewBinding(key.WithKeys("down"), key.WithHelp("down", "next line")),
LinePrevious: key.NewBinding(key.WithKeys("up"), key.WithHelp("up", "previous line")),
DeleteWordBackward: key.NewBinding(key.WithKeys("alt+backspace", "ctrl+w"), key.WithHelp("alt+backspace", "delete word backward")),
DeleteWordForward: key.NewBinding(key.WithKeys("alt+delete", "alt+d"), key.WithHelp("alt+delete", "delete word forward")),
DeleteAfterCursor: key.NewBinding(key.WithKeys("ctrl+k"), key.WithHelp("ctrl+k", "delete after cursor")),
DeleteBeforeCursor: key.NewBinding(key.WithKeys("ctrl+u"), key.WithHelp("ctrl+u", "delete before cursor")),
InsertNewline: key.NewBinding(key.WithKeys("enter", "ctrl+m"), key.WithHelp("enter", "insert newline")),
DeleteCharacterBackward: key.NewBinding(key.WithKeys("backspace", "ctrl+h"), key.WithHelp("backspace", "delete character backward")),
DeleteCharacterForward: key.NewBinding(key.WithKeys("delete", "ctrl+d"), key.WithHelp("delete", "delete character forward")),
LineStart: key.NewBinding(key.WithKeys("home", "ctrl+a"), key.WithHelp("home", "line start")),
LineEnd: key.NewBinding(key.WithKeys("end", "ctrl+e"), key.WithHelp("end", "line end")),
Paste: key.NewBinding(key.WithKeys("ctrl+v"), key.WithHelp("ctrl+v", "paste")),
InputBegin: key.NewBinding(key.WithKeys("alt+<", "ctrl+home"), key.WithHelp("alt+<", "input begin")),
InputEnd: key.NewBinding(key.WithKeys("alt+>", "ctrl+end"), key.WithHelp("alt+>", "input end")),
CapitalizeWordForward: key.NewBinding(key.WithKeys("alt+c"), key.WithHelp("alt+c", "capitalize word forward")),
LowercaseWordForward: key.NewBinding(key.WithKeys("alt+l"), key.WithHelp("alt+l", "lowercase word forward")),
UppercaseWordForward: key.NewBinding(key.WithKeys("alt+u"), key.WithHelp("alt+u", "uppercase word forward")),
TransposeCharacterBackward: key.NewBinding(key.WithKeys("ctrl+t"), key.WithHelp("ctrl+t", "transpose character backward")),
AcceptSuggestion: key.NewBinding(key.WithKeys("tab", "ctrl+y")),
NextSuggestion: key.NewBinding(key.WithKeys("down", "ctrl+n")),
PrevSuggestion: key.NewBinding(key.WithKeys("up", "ctrl+p")),
}
}

Expand Down Expand Up @@ -281,6 +287,18 @@
// there's no limit.
MaxWidth int

SyntaxHighlighter func(string) string
Formatter func(string) string

// Should the input suggest to complete
ShowSuggestions bool

// suggestions is a list of suggestions that may be used to complete the
// input.
suggestions [][][]rune
matchedSuggestions [][][]rune
currentSuggestionIndex int

// If promptFunc is set, it replaces Prompt as a generator for
// prompt strings at the beginning of each line.
promptFunc func(line int) string
Expand Down Expand Up @@ -343,10 +361,11 @@
virtualCursor: cur,
KeyMap: DefaultKeyMap(),

value: make([][]rune, minHeight, maxLines),
focus: false,
col: 0,
row: 0,
suggestions: [][][]rune{},
value: make([][]rune, minHeight, maxLines),
focus: false,
col: 0,
row: 0,

viewport: &vp,
}
Expand Down Expand Up @@ -429,6 +448,18 @@
m.InsertString(s)
}

// SetSuggestions sets the suggestions for the input.
func (m *Model) SetSuggestions(suggestions []string) {
m.suggestions = make([][][]rune, len(suggestions))
for i, s := range suggestions {
for _, line := range strings.Split(s, "\n") {
m.suggestions[i] = append(m.suggestions[i], []rune(line))
}
}

m.updateSuggestions()
}

// InsertString inserts a string at the cursor position.
func (m *Model) InsertString(s string) {
m.insertRunesFromUserInput([]rune(s))
Expand Down Expand Up @@ -525,6 +556,7 @@
// Finally add the tail at the end of the last line inserted.
m.value[m.row] = append(m.value[m.row], tail...)

m.format()
m.SetCursorColumn(m.col)
}

Expand Down Expand Up @@ -943,8 +975,8 @@
// repositionView repositions the view of the viewport based on the defined
// scrolling behavior.
func (m *Model) repositionView() {
min := m.viewport.YOffset

Check failure on line 978 in textarea/textarea.go

View workflow job for this annotation

GitHub Actions / lint / lint (ubuntu-latest)

redefines-builtin-id: redefinition of the built-in function min (revive)

Check failure on line 978 in textarea/textarea.go

View workflow job for this annotation

GitHub Actions / lint / lint (ubuntu-latest)

redefines-builtin-id: redefinition of the built-in function min (revive)
max := min + m.viewport.Height() - 1

Check failure on line 979 in textarea/textarea.go

View workflow job for this annotation

GitHub Actions / lint / lint (ubuntu-latest)

redefines-builtin-id: redefinition of the built-in function max (revive)

Check failure on line 979 in textarea/textarea.go

View workflow job for this annotation

GitHub Actions / lint / lint (ubuntu-latest)

redefines-builtin-id: redefinition of the built-in function max (revive)

if row := m.cursorLineNumber(); row < min {
m.viewport.LineUp(min - row)
Expand Down Expand Up @@ -1071,7 +1103,19 @@
switch msg := msg.(type) {
case tea.PasteMsg:
m.insertRunesFromUserInput([]rune(msg))

case tea.KeyPressMsg:
// We need to check for completion before checking other key matches,
// because the key is configurable and might be double assigned.
if key.Matches(msg, m.KeyMap.AcceptSuggestion) {
if m.canAcceptSuggestion() {
m.value = m.matchedSuggestions[m.currentSuggestionIndex]
m.format()
m.row = len(m.value) - 1
m.CursorEnd()
}
}

switch {
case key.Matches(msg, m.KeyMap.DeleteAfterCursor):
m.col = clamp(m.col, 0, len(m.value[m.row]))
Expand Down Expand Up @@ -1133,15 +1177,23 @@
case key.Matches(msg, m.KeyMap.CharacterForward):
m.characterRight()
case key.Matches(msg, m.KeyMap.LineNext):
m.CursorDown()
if m.row == 0 && len(m.value) == 1 {
m.nextSuggestion()
} else {
m.CursorDown()
}
case key.Matches(msg, m.KeyMap.WordForward):
m.wordRight()
case key.Matches(msg, m.KeyMap.Paste):
return m, Paste
case key.Matches(msg, m.KeyMap.CharacterBackward):
m.characterLeft(false /* insideLine */)
case key.Matches(msg, m.KeyMap.LinePrevious):
m.CursorUp()
if m.row == 0 && len(m.value) == 1 {
m.previousSuggestion()
} else {
m.CursorUp()
}
case key.Matches(msg, m.KeyMap.WordBackward):
m.wordLeft()
case key.Matches(msg, m.KeyMap.InputBegin):
Expand All @@ -1156,11 +1208,19 @@
m.capitalizeRight()
case key.Matches(msg, m.KeyMap.TransposeCharacterBackward):
m.transposeLeft()
case key.Matches(msg, m.KeyMap.NextSuggestion):
m.nextSuggestion()
case key.Matches(msg, m.KeyMap.PrevSuggestion):
m.previousSuggestion()

default:
m.insertRunesFromUserInput([]rune(msg.Text))
}

// Check again if can be completed
// because value might be something that does not match the completion prefix
m.updateSuggestions()

case pasteMsg:
m.insertRunesFromUserInput([]rune(msg))

Expand All @@ -1185,6 +1245,27 @@
return m, tea.Batch(cmds...)
}

func (m Model) suggestionView(offset int) string {
if !m.canAcceptSuggestion() {
return ""
}

value := linesToString(m.value)
suggestion := linesToString(m.matchedSuggestions[m.currentSuggestionIndex])
if len(value) >= len(suggestion) {
return ""
}

var lines []string

Check failure on line 1259 in textarea/textarea.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (ubuntu-latest)

Consider pre-allocating `lines` (prealloc)

Check failure on line 1259 in textarea/textarea.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (ubuntu-latest)

Consider pre-allocating `lines` (prealloc)

Check failure on line 1259 in textarea/textarea.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (macos-latest)

Consider pre-allocating `lines` (prealloc)

Check failure on line 1259 in textarea/textarea.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (windows-latest)

Consider pre-allocating `lines` (prealloc)

Check failure on line 1259 in textarea/textarea.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (macos-latest)

Consider pre-allocating `lines` (prealloc)

Check failure on line 1259 in textarea/textarea.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (windows-latest)

Consider pre-allocating `lines` (prealloc)
for _, line := range strings.Split(suggestion[len(m.Value())+offset:], "\n") {
lines = append(lines, m.activeStyle().Placeholder.Inline(true).Render(line))
}
if len(lines) > m.Height() {
m.SetHeight(len(lines) + 1)
}
return strings.Join(lines, "\n")
}

// View renders the text area in its current state.
func (m Model) View() string {
m.updateVirtualCursorStyle()
Expand Down Expand Up @@ -1219,7 +1300,7 @@
displayLine++

var ln string
if m.ShowLineNumbers { //nolint:nestif

Check failure on line 1303 in textarea/textarea.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (ubuntu-latest)

directive `//nolint:nestif` is unused for linter "nestif" (nolintlint)

Check failure on line 1303 in textarea/textarea.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (ubuntu-latest)

directive `//nolint:nestif` is unused for linter "nestif" (nolintlint)

Check failure on line 1303 in textarea/textarea.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (macos-latest)

directive `//nolint:nestif` is unused for linter "nestif" (nolintlint)

Check failure on line 1303 in textarea/textarea.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (windows-latest)

directive `//nolint:nestif` is unused for linter "nestif" (nolintlint)

Check failure on line 1303 in textarea/textarea.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (macos-latest)

directive `//nolint:nestif` is unused for linter "nestif" (nolintlint)

Check failure on line 1303 in textarea/textarea.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (windows-latest)

directive `//nolint:nestif` is unused for linter "nestif" (nolintlint)
if wl == 0 { // normal line
isCursorLine := m.row == l
s.WriteString(m.lineNumberView(l+1, isCursorLine))
Expand Down Expand Up @@ -1248,20 +1329,52 @@
wrappedLine = []rune(strings.TrimSuffix(string(wrappedLine), " "))
padding -= m.width - strwidth
}
if m.row == l && lineInfo.RowOffset == wl {

Check failure on line 1332 in textarea/textarea.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (ubuntu-latest)

`if m.row == l && lineInfo.RowOffset == wl` has complex nested blocks (complexity: 15) (nestif)

Check failure on line 1332 in textarea/textarea.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (ubuntu-latest)

`if m.row == l && lineInfo.RowOffset == wl` has complex nested blocks (complexity: 15) (nestif)

Check failure on line 1332 in textarea/textarea.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (macos-latest)

`if m.row == l && lineInfo.RowOffset == wl` has complex nested blocks (complexity: 15) (nestif)

Check failure on line 1332 in textarea/textarea.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (windows-latest)

`if m.row == l && lineInfo.RowOffset == wl` has complex nested blocks (complexity: 15) (nestif)

Check failure on line 1332 in textarea/textarea.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (macos-latest)

`if m.row == l && lineInfo.RowOffset == wl` has complex nested blocks (complexity: 15) (nestif)

Check failure on line 1332 in textarea/textarea.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (windows-latest)

`if m.row == l && lineInfo.RowOffset == wl` has complex nested blocks (complexity: 15) (nestif)
s.WriteString(style.Render(string(wrappedLine[:lineInfo.ColumnOffset])))
ln := string(wrappedLine[:lineInfo.ColumnOffset])
if m.SyntaxHighlighter == nil {
ln = style.Render(ln)
} else {
ln = m.SyntaxHighlighter(ln)
}
s.WriteString(ln)

if m.col >= len(line) && lineInfo.CharOffset >= m.width {
m.virtualCursor.SetChar(" ")
s.WriteString(m.virtualCursor.View())
// XXX: suggestions?
} else {
m.virtualCursor.SetChar(string(wrappedLine[lineInfo.ColumnOffset]))
if m.canAcceptSuggestion() && len(m.matchedSuggestions) > 0 {
suggestion := m.matchedSuggestions[m.currentSuggestionIndex]
if len(suggestion) >= m.row {
suggestion = suggestion[m.row:]
}
m.virtualCursor.TextStyle = m.activeStyle().Placeholder
if len(suggestion) > m.row && len(suggestion[m.row]) > m.col {
m.virtualCursor.SetChar(string(suggestion[m.row][m.col]))
}
}
s.WriteString(style.Render(m.virtualCursor.View()))
s.WriteString(style.Render(string(wrappedLine[lineInfo.ColumnOffset+1:])))
s.WriteString(m.suggestionView(1))
// XXX: suggestions
}
} else {
ln := string(wrappedLine)
if m.SyntaxHighlighter == nil {
ln = style.Render(ln)
} else {
ln = m.SyntaxHighlighter(ln)
}
s.WriteString(ln)
}

pad := strings.Repeat(" ", max(0, padding))
if m.SyntaxHighlighter == nil {
s.WriteString(style.Render(pad))
} else {
s.WriteString(style.Render(string(wrappedLine)))
s.WriteString(pad)
}
s.WriteString(style.Render(strings.Repeat(" ", max(0, padding))))
s.WriteRune('\n')
newLines++
}
Expand Down Expand Up @@ -1541,6 +1654,66 @@
m.row++
}

// canAcceptSuggestion returns whether there is an acceptable suggestion to
// autocomplete the current value.
func (m *Model) canAcceptSuggestion() bool {
return len(m.matchedSuggestions) > 0
}

// updateSuggestions refreshes the list of matching suggestions.
func (m *Model) updateSuggestions() {
if !m.ShowSuggestions {
return
}

if len(m.value) <= 0 || len(m.suggestions) <= 0 {
m.matchedSuggestions = [][][]rune{}
return
}

// TODO: this should be better

Check failure on line 1674 in textarea/textarea.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (ubuntu-latest)

textarea/textarea.go:1674: Line contains TODO/BUG/FIXME: "TODO: this should be better" (godox)

Check failure on line 1674 in textarea/textarea.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (ubuntu-latest)

textarea/textarea.go:1674: Line contains TODO/BUG/FIXME: "TODO: this should be better" (godox)

Check failure on line 1674 in textarea/textarea.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (macos-latest)

textarea/textarea.go:1674: Line contains TODO/BUG/FIXME: "TODO: this should be better" (godox)

Check failure on line 1674 in textarea/textarea.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (windows-latest)

\textarea\textarea.go:1674: Line contains TODO/BUG/FIXME: "TODO: this should be better" (godox)

Check failure on line 1674 in textarea/textarea.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (macos-latest)

textarea/textarea.go:1674: Line contains TODO/BUG/FIXME: "TODO: this should be better" (godox)

Check failure on line 1674 in textarea/textarea.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (windows-latest)

\textarea\textarea.go:1674: Line contains TODO/BUG/FIXME: "TODO: this should be better" (godox)
matches := [][][]rune{}
for _, s := range m.suggestions {
suggestion := linesToString(s)

if strings.HasPrefix(strings.ToLower(suggestion), strings.ToLower(linesToString(m.value))) {
matches = append(matches, s)
}
}
if !reflect.DeepEqual(matches, m.matchedSuggestions) {
m.currentSuggestionIndex = 0
}

m.matchedSuggestions = matches
}

// nextSuggestion selects the next suggestion.
func (m *Model) nextSuggestion() {
m.currentSuggestionIndex = (m.currentSuggestionIndex + 1)
if m.currentSuggestionIndex >= len(m.matchedSuggestions) {
m.currentSuggestionIndex = 0
}
}

// previousSuggestion selects the previous suggestion.
func (m *Model) previousSuggestion() {
m.currentSuggestionIndex = (m.currentSuggestionIndex - 1)
if m.currentSuggestionIndex < 0 {
m.currentSuggestionIndex = len(m.matchedSuggestions) - 1
}
}

func (m *Model) format() {
if m.Formatter == nil {
return
}
m.value = stringToLines(m.Formatter(linesToString(m.value)))
m.row = len(m.value) - 1
if m.col > len(m.value[m.row]) {
m.col = len(m.value[m.row]) - 1
}
}

// Paste is a command for pasting from the clipboard into the text input.
func Paste() tea.Msg {
str, err := clipboard.ReadAll()
Expand Down Expand Up @@ -1646,3 +1819,19 @@
}
return n
}

func stringToLines(s string) [][]rune {
var r [][]rune

Check failure on line 1824 in textarea/textarea.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (ubuntu-latest)

Consider pre-allocating `r` (prealloc)

Check failure on line 1824 in textarea/textarea.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (ubuntu-latest)

Consider pre-allocating `r` (prealloc)

Check failure on line 1824 in textarea/textarea.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (macos-latest)

Consider pre-allocating `r` (prealloc)

Check failure on line 1824 in textarea/textarea.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (windows-latest)

Consider pre-allocating `r` (prealloc)

Check failure on line 1824 in textarea/textarea.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (macos-latest)

Consider pre-allocating `r` (prealloc)

Check failure on line 1824 in textarea/textarea.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (windows-latest)

Consider pre-allocating `r` (prealloc)
for _, line := range strings.Split(s, "\n") {
r = append(r, []rune(line))
}
return r
}

func linesToString(lines [][]rune) string {
var result []string

Check failure on line 1832 in textarea/textarea.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (ubuntu-latest)

Consider pre-allocating `result` (prealloc)

Check failure on line 1832 in textarea/textarea.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (ubuntu-latest)

Consider pre-allocating `result` (prealloc)

Check failure on line 1832 in textarea/textarea.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (macos-latest)

Consider pre-allocating `result` (prealloc)

Check failure on line 1832 in textarea/textarea.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (windows-latest)

Consider pre-allocating `result` (prealloc)

Check failure on line 1832 in textarea/textarea.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (macos-latest)

Consider pre-allocating `result` (prealloc)

Check failure on line 1832 in textarea/textarea.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (windows-latest)

Consider pre-allocating `result` (prealloc)
for _, line := range lines {
result = append(result, string(line))
}
return strings.Join(result, "\n")
}
Loading