mirror of
https://github.com/Maks1mS/bubbles.git
synced 2025-10-17 16:15:58 +03:00
Compare commits
5 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
9449cc7e41 | ||
|
58a177394e | ||
|
f016c31d83 | ||
|
74436326b9 | ||
|
158097df66 |
@@ -61,7 +61,6 @@ type Model struct {
|
||||
// General settings.
|
||||
Prompt string
|
||||
Placeholder string
|
||||
Cursor string
|
||||
BlinkSpeed time.Duration
|
||||
EchoMode EchoMode
|
||||
EchoCharacter rune
|
||||
@@ -140,7 +139,7 @@ func (m *Model) SetValue(s string) {
|
||||
m.value = runes
|
||||
}
|
||||
if m.pos == 0 || m.pos > len(m.value) {
|
||||
m.SetCursor(len(m.value))
|
||||
m.setCursor(len(m.value))
|
||||
}
|
||||
m.handleOverflow()
|
||||
}
|
||||
@@ -150,10 +149,21 @@ func (m Model) Value() string {
|
||||
return string(m.value)
|
||||
}
|
||||
|
||||
// SetCursor start moves the cursor to the given position. If the position is
|
||||
// Cursor returns the cursor position.
|
||||
func (m Model) Cursor() int {
|
||||
return m.pos
|
||||
}
|
||||
|
||||
// SetCursor moves the cursor to the given position. If the position is
|
||||
// out of bounds the cursor will be moved to the start or end accordingly.
|
||||
// Returns whether or nor the cursor timer should be reset.
|
||||
func (m *Model) SetCursor(pos int) bool {
|
||||
func (m *Model) SetCursor(pos int) {
|
||||
m.setCursor(pos)
|
||||
}
|
||||
|
||||
// setCursor moves the cursor to the given position and returns whether or not
|
||||
// the cursor blink should be reset. If the position is out of bounds the
|
||||
// cursor will be moved to the start or end accordingly.
|
||||
func (m *Model) setCursor(pos int) bool {
|
||||
m.pos = clamp(pos, 0, len(m.value))
|
||||
m.handleOverflow()
|
||||
|
||||
@@ -164,16 +174,26 @@ func (m *Model) SetCursor(pos int) bool {
|
||||
return m.cursorMode == cursorBlink
|
||||
}
|
||||
|
||||
// CursorStart moves the cursor to the start of the field. Returns whether or
|
||||
// not the curosr blink should be reset.
|
||||
func (m *Model) CursorStart() bool {
|
||||
return m.SetCursor(0)
|
||||
// CursorStart moves the cursor to the start of the input field.
|
||||
func (m *Model) CursorStart() {
|
||||
m.cursorStart()
|
||||
}
|
||||
|
||||
// CursorEnd moves the cursor to the end of the field. Returns whether or not
|
||||
// the cursor blink should be reset.
|
||||
func (m *Model) CursorEnd() bool {
|
||||
return m.SetCursor(len(m.value))
|
||||
// cursorStart moves the cursor to the start of the input field and returns
|
||||
// whether or not the curosr blink should be reset.
|
||||
func (m *Model) cursorStart() bool {
|
||||
return m.setCursor(0)
|
||||
}
|
||||
|
||||
// CursorEnd moves the cursor to the end of the input field
|
||||
func (m *Model) CursorEnd() {
|
||||
m.cursorEnd()
|
||||
}
|
||||
|
||||
// cursorEnd moves the cursor to the end of the input field and returns whether
|
||||
// or not
|
||||
func (m *Model) cursorEnd() bool {
|
||||
return m.setCursor(len(m.value))
|
||||
}
|
||||
|
||||
// Focused returns the focus state on the model.
|
||||
@@ -197,7 +217,7 @@ func (m *Model) Blur() {
|
||||
// or not the cursor blink should reset.
|
||||
func (m *Model) Reset() bool {
|
||||
m.value = nil
|
||||
return m.SetCursor(0)
|
||||
return m.setCursor(0)
|
||||
}
|
||||
|
||||
// handle a clipboard paste event, if supported. Returns whether or not the
|
||||
@@ -243,7 +263,7 @@ func (m *Model) handlePaste(v string) bool {
|
||||
m.value = append(head, tail...)
|
||||
|
||||
// Reset blink state if necessary and run overflow checks
|
||||
return m.SetCursor(m.pos)
|
||||
return m.setCursor(m.pos)
|
||||
}
|
||||
|
||||
// If a max width is defined, perform some logic to treat the visible area
|
||||
@@ -291,6 +311,22 @@ func (m *Model) handleOverflow() {
|
||||
}
|
||||
}
|
||||
|
||||
// deleteBeforeCursor deletes all text before the cursor. Returns whether or
|
||||
// not the cursor blink should be reset.
|
||||
func (m *Model) deleteBeforeCursor() bool {
|
||||
m.value = m.value[m.pos:]
|
||||
m.offset = 0
|
||||
return m.setCursor(0)
|
||||
}
|
||||
|
||||
// deleteAfterCursor deletes all text after the cursor. Returns whether or not
|
||||
// the cursor blink should be reset. If input is masked delete everything after
|
||||
// the cursor so as not to reveal word breaks in the masked input.
|
||||
func (m *Model) deleteAfterCursor() bool {
|
||||
m.value = m.value[:m.pos]
|
||||
return m.setCursor(len(m.value))
|
||||
}
|
||||
|
||||
// deleteWordLeft deletes the word left to the cursor. Returns whether or not
|
||||
// the cursor blink should be reset.
|
||||
func (m *Model) deleteWordLeft() bool {
|
||||
@@ -298,20 +334,24 @@ func (m *Model) deleteWordLeft() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
if m.EchoMode != EchoNormal {
|
||||
return m.deleteBeforeCursor()
|
||||
}
|
||||
|
||||
i := m.pos
|
||||
blink := m.SetCursor(m.pos - 1)
|
||||
blink := m.setCursor(m.pos - 1)
|
||||
for unicode.IsSpace(m.value[m.pos]) {
|
||||
// ignore series of whitespace before cursor
|
||||
blink = m.SetCursor(m.pos - 1)
|
||||
blink = m.setCursor(m.pos - 1)
|
||||
}
|
||||
|
||||
for m.pos > 0 {
|
||||
if !unicode.IsSpace(m.value[m.pos]) {
|
||||
blink = m.SetCursor(m.pos - 1)
|
||||
blink = m.setCursor(m.pos - 1)
|
||||
} else {
|
||||
if m.pos > 0 {
|
||||
// keep the previous space
|
||||
blink = m.SetCursor(m.pos + 1)
|
||||
blink = m.setCursor(m.pos + 1)
|
||||
}
|
||||
break
|
||||
}
|
||||
@@ -327,22 +367,27 @@ func (m *Model) deleteWordLeft() bool {
|
||||
}
|
||||
|
||||
// deleteWordRight deletes the word right to the cursor. Returns whether or not
|
||||
// the cursor blink should be reset.
|
||||
// the cursor blink should be reset. If input is masked delete everything after
|
||||
// the cursor so as not to reveal word breaks in the masked input.
|
||||
func (m *Model) deleteWordRight() bool {
|
||||
if m.pos >= len(m.value) || len(m.value) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
if m.EchoMode != EchoNormal {
|
||||
return m.deleteAfterCursor()
|
||||
}
|
||||
|
||||
i := m.pos
|
||||
m.SetCursor(m.pos + 1)
|
||||
m.setCursor(m.pos + 1)
|
||||
for unicode.IsSpace(m.value[m.pos]) {
|
||||
// ignore series of whitespace after cursor
|
||||
m.SetCursor(m.pos + 1)
|
||||
m.setCursor(m.pos + 1)
|
||||
}
|
||||
|
||||
for m.pos < len(m.value) {
|
||||
if !unicode.IsSpace(m.value[m.pos]) {
|
||||
m.SetCursor(m.pos + 1)
|
||||
m.setCursor(m.pos + 1)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
@@ -354,21 +399,26 @@ func (m *Model) deleteWordRight() bool {
|
||||
m.value = append(m.value[:i], m.value[m.pos:]...)
|
||||
}
|
||||
|
||||
return m.SetCursor(i)
|
||||
return m.setCursor(i)
|
||||
}
|
||||
|
||||
// wordLeft moves the cursor one word to the left. Returns whether or not the
|
||||
// cursor blink should be reset.
|
||||
// cursor blink should be reset. If input is masked, move input to the start
|
||||
// so as not to reveal word breaks in the masked input.
|
||||
func (m *Model) wordLeft() bool {
|
||||
if m.pos == 0 || len(m.value) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
if m.EchoMode != EchoNormal {
|
||||
return m.cursorStart()
|
||||
}
|
||||
|
||||
blink := false
|
||||
i := m.pos - 1
|
||||
for i >= 0 {
|
||||
if unicode.IsSpace(m.value[i]) {
|
||||
blink = m.SetCursor(m.pos - 1)
|
||||
blink = m.setCursor(m.pos - 1)
|
||||
i--
|
||||
} else {
|
||||
break
|
||||
@@ -377,7 +427,7 @@ func (m *Model) wordLeft() bool {
|
||||
|
||||
for i >= 0 {
|
||||
if !unicode.IsSpace(m.value[i]) {
|
||||
blink = m.SetCursor(m.pos - 1)
|
||||
blink = m.setCursor(m.pos - 1)
|
||||
i--
|
||||
} else {
|
||||
break
|
||||
@@ -388,17 +438,22 @@ func (m *Model) wordLeft() bool {
|
||||
}
|
||||
|
||||
// wordRight moves the cursor one word to the right. Returns whether or not the
|
||||
// cursor blink should be reset.
|
||||
// cursor blink should be reset. If the input is masked, move input to the end
|
||||
// so as not to reveal word breaks in the masked input.
|
||||
func (m *Model) wordRight() bool {
|
||||
if m.pos >= len(m.value) || len(m.value) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
if m.EchoMode != EchoNormal {
|
||||
return m.cursorEnd()
|
||||
}
|
||||
|
||||
blink := false
|
||||
i := m.pos
|
||||
for i < len(m.value) {
|
||||
if unicode.IsSpace(m.value[i]) {
|
||||
blink = m.SetCursor(m.pos + 1)
|
||||
blink = m.setCursor(m.pos + 1)
|
||||
i++
|
||||
} else {
|
||||
break
|
||||
@@ -407,7 +462,7 @@ func (m *Model) wordRight() bool {
|
||||
|
||||
for i < len(m.value) {
|
||||
if !unicode.IsSpace(m.value[i]) {
|
||||
blink = m.SetCursor(m.pos + 1)
|
||||
blink = m.setCursor(m.pos + 1)
|
||||
i++
|
||||
} else {
|
||||
break
|
||||
@@ -448,7 +503,7 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
|
||||
if len(m.value) > 0 {
|
||||
m.value = append(m.value[:max(0, m.pos-1)], m.value[m.pos:]...)
|
||||
if m.pos > 0 {
|
||||
resetBlink = m.SetCursor(m.pos - 1)
|
||||
resetBlink = m.setCursor(m.pos - 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -458,33 +513,30 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
|
||||
break
|
||||
}
|
||||
if m.pos > 0 { // left arrow, ^F, back one character
|
||||
resetBlink = m.SetCursor(m.pos - 1)
|
||||
resetBlink = m.setCursor(m.pos - 1)
|
||||
}
|
||||
case tea.KeyRight, tea.KeyCtrlF:
|
||||
if msg.Alt { // alt+right arrow, forward one word
|
||||
resetBlink = m.wordRight()
|
||||
break
|
||||
}
|
||||
if m.pos < len(m.value) { // right arrow, ^F, forward one word
|
||||
resetBlink = m.SetCursor(m.pos + 1)
|
||||
if m.pos < len(m.value) { // right arrow, ^F, forward one character
|
||||
resetBlink = m.setCursor(m.pos + 1)
|
||||
}
|
||||
case tea.KeyCtrlW: // ^W, delete word left of cursor
|
||||
resetBlink = m.deleteWordLeft()
|
||||
case tea.KeyHome, tea.KeyCtrlA: // ^A, go to beginning
|
||||
resetBlink = m.CursorStart()
|
||||
resetBlink = m.cursorStart()
|
||||
case tea.KeyDelete, tea.KeyCtrlD: // ^D, delete char under cursor
|
||||
if len(m.value) > 0 && m.pos < len(m.value) {
|
||||
m.value = append(m.value[:m.pos], m.value[m.pos+1:]...)
|
||||
}
|
||||
case tea.KeyCtrlE, tea.KeyEnd: // ^E, go to end
|
||||
resetBlink = m.CursorEnd()
|
||||
resetBlink = m.cursorEnd()
|
||||
case tea.KeyCtrlK: // ^K, kill text after cursor
|
||||
m.value = m.value[:m.pos]
|
||||
resetBlink = m.SetCursor(len(m.value))
|
||||
resetBlink = m.deleteAfterCursor()
|
||||
case tea.KeyCtrlU: // ^U, kill text before cursor
|
||||
m.value = m.value[m.pos:]
|
||||
resetBlink = m.SetCursor(0)
|
||||
m.offset = 0
|
||||
resetBlink = m.deleteBeforeCursor()
|
||||
case tea.KeyCtrlV: // ^V paste
|
||||
return m, Paste
|
||||
case tea.KeyRunes: // input regular characters
|
||||
@@ -506,7 +558,7 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
|
||||
// Input a regular character
|
||||
if m.CharLimit <= 0 || len(m.value) < m.CharLimit {
|
||||
m.value = append(m.value[:m.pos], append(msg.Runes, m.value[m.pos:]...)...)
|
||||
resetBlink = m.SetCursor(m.pos + len(msg.Runes))
|
||||
resetBlink = m.setCursor(m.pos + len(msg.Runes))
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -71,6 +71,10 @@ func (m Model) ScrollPercent() float64 {
|
||||
func (m *Model) SetContent(s string) {
|
||||
s = strings.Replace(s, "\r\n", "\n", -1) // normalize line endings
|
||||
m.lines = strings.Split(s, "\n")
|
||||
|
||||
if m.YOffset > len(m.lines)-1 {
|
||||
m.GotoBottom()
|
||||
}
|
||||
}
|
||||
|
||||
// Return the lines that should currently be visible in the viewport.
|
||||
|
Reference in New Issue
Block a user