mirror of
https://github.com/zoriya/bubbles.git
synced 2026-08-05 04:36:07 +00:00
wip
Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
This commit is contained in:
+110
-4
@@ -3,6 +3,7 @@ package textarea
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
@@ -64,6 +65,10 @@ type KeyMap struct {
|
||||
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
|
||||
@@ -94,6 +99,10 @@ func DefaultKeyMap() KeyMap {
|
||||
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")),
|
||||
NextSuggestion: key.NewBinding(key.WithKeys("down", "ctrl+n")),
|
||||
PrevSuggestion: key.NewBinding(key.WithKeys("up", "ctrl+p")),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,6 +257,15 @@ type Model struct {
|
||||
|
||||
SyntaxHighlighter 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
|
||||
@@ -310,10 +328,11 @@ func New() Model {
|
||||
Cursor: 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,
|
||||
}
|
||||
@@ -369,6 +388,18 @@ func (m *Model) SetValue(s string) {
|
||||
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))
|
||||
@@ -982,6 +1013,15 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// Need to check for completion before, because key is configurable and might be double assigned
|
||||
keyMsg, ok := msg.(tea.KeyMsg)
|
||||
if ok && key.Matches(keyMsg, m.KeyMap.AcceptSuggestion) {
|
||||
if m.canAcceptSuggestion() {
|
||||
m.value = append(m.value, m.matchedSuggestions[m.currentSuggestionIndex][len(m.value):]...)
|
||||
m.CursorEnd()
|
||||
}
|
||||
}
|
||||
|
||||
// Used to determine if the cursor should blink.
|
||||
oldRow, oldCol := m.cursorLineNumber(), m.col
|
||||
|
||||
@@ -1083,11 +1123,19 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
|
||||
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))
|
||||
|
||||
@@ -1194,10 +1242,12 @@ func (m Model) View() string {
|
||||
if m.col >= len(line) && lineInfo.CharOffset >= m.width {
|
||||
m.Cursor.SetChar(" ")
|
||||
s.WriteString(m.Cursor.View())
|
||||
// XXX: suggestions
|
||||
} else {
|
||||
m.Cursor.SetChar(string(wrappedLine[lineInfo.ColumnOffset]))
|
||||
s.WriteString(style.Render(m.Cursor.View()))
|
||||
s.WriteString(style.Render(string(wrappedLine[lineInfo.ColumnOffset+1:])))
|
||||
// XXX: suggestions
|
||||
}
|
||||
} else {
|
||||
ln := string(wrappedLine)
|
||||
@@ -1425,6 +1475,62 @@ func (m *Model) splitLine(row, col int) {
|
||||
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
|
||||
}
|
||||
|
||||
func linesToString(lines [][]rune) string {
|
||||
var result []string
|
||||
for _, line := range lines {
|
||||
result = append(result, string(line))
|
||||
}
|
||||
return strings.Join(result, "\n")
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// Paste is a command for pasting from the clipboard into the text input.
|
||||
func Paste() tea.Msg {
|
||||
str, err := clipboard.ReadAll()
|
||||
|
||||
Reference in New Issue
Block a user