mirror of
https://github.com/Maks1mS/bubbles.git
synced 2025-10-19 00:49:54 +03:00
Compare commits
11 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
7d1c04164e | ||
|
97020cd0d2 | ||
|
5dbcf95877 | ||
|
f58fead10d | ||
|
703de11da4 | ||
|
03461d6804 | ||
|
4445acbace | ||
|
bd161e8ded | ||
|
d9716a97f6 | ||
|
a0fe547fdb | ||
|
1cb36774ed |
13
.github/workflows/build.yml
vendored
13
.github/workflows/build.yml
vendored
@@ -4,14 +4,14 @@ jobs:
|
|||||||
test:
|
test:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.13.x, 1.14.x, 1.15.x]
|
go-version: [~1.13, ^1]
|
||||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
env:
|
env:
|
||||||
GO111MODULE: "on"
|
GO111MODULE: "on"
|
||||||
steps:
|
steps:
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@v1
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go-version }}
|
go-version: ${{ matrix.go-version }}
|
||||||
|
|
||||||
@@ -26,12 +26,3 @@ jobs:
|
|||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: go test ./...
|
run: go test ./...
|
||||||
|
|
||||||
- name: Coverage
|
|
||||||
env:
|
|
||||||
COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
run: |
|
|
||||||
go test -race -covermode atomic -coverprofile=profile.cov ./...
|
|
||||||
GO111MODULE=off go get github.com/mattn/goveralls
|
|
||||||
$(go env GOPATH)/bin/goveralls -coverprofile=profile.cov -service=github
|
|
||||||
if: matrix.go-version == '1.15.x' && matrix.os == 'ubuntu-latest'
|
|
||||||
|
28
.github/workflows/coverage.yml
vendored
Normal file
28
.github/workflows/coverage.yml
vendored
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
name: coverage
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
coverage:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
go-version: [^1]
|
||||||
|
os: [ubuntu-latest]
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
env:
|
||||||
|
GO111MODULE: "on"
|
||||||
|
steps:
|
||||||
|
- name: Install Go
|
||||||
|
uses: actions/setup-go@v2
|
||||||
|
with:
|
||||||
|
go-version: ${{ matrix.go-version }}
|
||||||
|
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Coverage
|
||||||
|
env:
|
||||||
|
COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
go test -race -covermode atomic -coverprofile=profile.cov ./...
|
||||||
|
GO111MODULE=off go get github.com/mattn/goveralls
|
||||||
|
$(go env GOPATH)/bin/goveralls -coverprofile=profile.cov -service=github
|
@@ -1,9 +1,10 @@
|
|||||||
Bubbles
|
Bubbles
|
||||||
=======
|
=======
|
||||||
|
|
||||||
|
[](https://github.com/charmbracelet/bubbles/releases)
|
||||||
|
[](https://pkg.go.dev/github.com/charmbracelet/bubbles)
|
||||||
[](https://github.com/charmbracelet/bubbles/actions)
|
[](https://github.com/charmbracelet/bubbles/actions)
|
||||||
[](http://goreportcard.com/report/charmbracelet/bubbles)
|
[](http://goreportcard.com/report/charmbracelet/bubbles)
|
||||||
[](https://pkg.go.dev/github.com/charmbracelet/bubbles)
|
|
||||||
|
|
||||||
Some components for [Bubble Tea](https://github.com/charmbracelet/bubbletea) applications.
|
Some components for [Bubble Tea](https://github.com/charmbracelet/bubbletea) applications.
|
||||||
|
|
||||||
@@ -32,7 +33,7 @@ pasting, in-place scrolling when the value exceeds the width of the element and
|
|||||||
the common, and many customization options.
|
the common, and many customization options.
|
||||||
|
|
||||||
* [Example code, one field](https://github.com/charmbracelet/tea/tree/master/examples/textinput/main.go)
|
* [Example code, one field](https://github.com/charmbracelet/tea/tree/master/examples/textinput/main.go)
|
||||||
* [Example code, many fields](https://github.com/charmbracelet/tea/tree/master/examples/textinput/main.go)
|
* [Example code, many fields](https://github.com/charmbracelet/tea/tree/master/examples/textinputs/main.go)
|
||||||
|
|
||||||
|
|
||||||
## Paginator
|
## Paginator
|
||||||
|
@@ -40,8 +40,8 @@ type Model struct {
|
|||||||
// used for other things beyond navigating sets. Note that it both returns the
|
// used for other things beyond navigating sets. Note that it both returns the
|
||||||
// number of total pages and alters the model.
|
// number of total pages and alters the model.
|
||||||
func (m *Model) SetTotalPages(items int) int {
|
func (m *Model) SetTotalPages(items int) int {
|
||||||
if items == 0 {
|
if items < 1 {
|
||||||
return 0
|
return m.TotalPages
|
||||||
}
|
}
|
||||||
n := items / m.PerPage
|
n := items / m.PerPage
|
||||||
if items%m.PerPage > 0 {
|
if items%m.PerPage > 0 {
|
||||||
|
@@ -11,7 +11,7 @@ import (
|
|||||||
"github.com/muesli/termenv"
|
"github.com/muesli/termenv"
|
||||||
)
|
)
|
||||||
|
|
||||||
const defaultBlinkSpeed = time.Millisecond * 600
|
const defaultBlinkSpeed = time.Millisecond * 530
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// EchoNormal displays text as is. This is the default behavior.
|
// EchoNormal displays text as is. This is the default behavior.
|
||||||
@@ -91,7 +91,7 @@ func (m *Model) SetValue(s string) {
|
|||||||
m.value = runes
|
m.value = runes
|
||||||
}
|
}
|
||||||
if m.pos > len(m.value) {
|
if m.pos > len(m.value) {
|
||||||
m.pos = len(m.value)
|
m.SetCursor(len(m.value))
|
||||||
}
|
}
|
||||||
m.handleOverflow()
|
m.handleOverflow()
|
||||||
}
|
}
|
||||||
@@ -105,19 +105,18 @@ func (m Model) Value() string {
|
|||||||
// out of bounds the cursor will be moved to the start or end accordingly.
|
// out of bounds the cursor will be moved to the start or end accordingly.
|
||||||
func (m *Model) SetCursor(pos int) {
|
func (m *Model) SetCursor(pos int) {
|
||||||
m.pos = clamp(pos, 0, len(m.value))
|
m.pos = clamp(pos, 0, len(m.value))
|
||||||
|
m.blink = false
|
||||||
m.handleOverflow()
|
m.handleOverflow()
|
||||||
}
|
}
|
||||||
|
|
||||||
// CursorStart moves the cursor to the start of the field.
|
// CursorStart moves the cursor to the start of the field.
|
||||||
func (m *Model) CursorStart() {
|
func (m *Model) CursorStart() {
|
||||||
m.pos = 0
|
m.SetCursor(0)
|
||||||
m.handleOverflow()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CursorEnd moves the cursor to the end of the field.
|
// CursorEnd moves the cursor to the end of the field.
|
||||||
func (m *Model) CursorEnd() {
|
func (m *Model) CursorEnd() {
|
||||||
m.pos = len(m.value)
|
m.SetCursor(len(m.value))
|
||||||
m.handleOverflow()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Focused returns the focus state on the model.
|
// Focused returns the focus state on the model.
|
||||||
@@ -140,8 +139,7 @@ func (m *Model) Blur() {
|
|||||||
// Reset sets the input to its default state with no input.
|
// Reset sets the input to its default state with no input.
|
||||||
func (m *Model) Reset() {
|
func (m *Model) Reset() {
|
||||||
m.value = nil
|
m.value = nil
|
||||||
m.pos = 0
|
m.SetCursor(0)
|
||||||
m.blink = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Paste pastes the contents of the clipboard into the text area (if supported).
|
// Paste pastes the contents of the clipboard into the text area (if supported).
|
||||||
@@ -152,7 +150,10 @@ func (m *Model) Paste() {
|
|||||||
}
|
}
|
||||||
paste := []rune(pasteString)
|
paste := []rune(pasteString)
|
||||||
|
|
||||||
availSpace := m.CharLimit - len(m.value)
|
var availSpace int
|
||||||
|
if m.CharLimit > 0 {
|
||||||
|
availSpace = m.CharLimit - len(m.value)
|
||||||
|
}
|
||||||
|
|
||||||
// If the char limit's been reached cancel
|
// If the char limit's been reached cancel
|
||||||
if m.CharLimit > 0 && availSpace <= 0 {
|
if m.CharLimit > 0 && availSpace <= 0 {
|
||||||
@@ -161,7 +162,7 @@ func (m *Model) Paste() {
|
|||||||
|
|
||||||
// If there's not enough space to paste the whole thing cut the pasted
|
// If there's not enough space to paste the whole thing cut the pasted
|
||||||
// runes down so they'll fit
|
// runes down so they'll fit
|
||||||
if availSpace < len(paste) {
|
if m.CharLimit > 0 && availSpace < len(paste) {
|
||||||
paste = paste[:len(paste)-availSpace]
|
paste = paste[:len(paste)-availSpace]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,15 +175,20 @@ func (m *Model) Paste() {
|
|||||||
// Insert pasted runes
|
// Insert pasted runes
|
||||||
for _, r := range paste {
|
for _, r := range paste {
|
||||||
head = append(head, r)
|
head = append(head, r)
|
||||||
availSpace--
|
|
||||||
m.pos++
|
m.pos++
|
||||||
if m.CharLimit > 0 && availSpace <= 0 {
|
if m.CharLimit > 0 {
|
||||||
break
|
availSpace--
|
||||||
|
if availSpace <= 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Put it all back together
|
// Put it all back together
|
||||||
m.value = append(head, tail...)
|
m.value = append(head, tail...)
|
||||||
|
|
||||||
|
// Reset blink state and run overflow checks
|
||||||
|
m.SetCursor(m.pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If a max width is defined, perform some logic to treat the visible area
|
// If a max width is defined, perform some logic to treat the visible area
|
||||||
@@ -250,6 +256,67 @@ func (m *Model) colorPlaceholder(s string) string {
|
|||||||
String()
|
String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// deleteWordLeft deletes the word left to the cursor.
|
||||||
|
func (m *Model) deleteWordLeft() {
|
||||||
|
if m.pos == 0 || len(m.value) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
i := m.pos
|
||||||
|
m.SetCursor(m.pos - 1)
|
||||||
|
for unicode.IsSpace(m.value[m.pos]) {
|
||||||
|
// ignore series of whitespace before cursor
|
||||||
|
m.SetCursor(m.pos - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
for m.pos > 0 {
|
||||||
|
if !unicode.IsSpace(m.value[m.pos]) {
|
||||||
|
m.SetCursor(m.pos - 1)
|
||||||
|
} else {
|
||||||
|
if m.pos > 0 {
|
||||||
|
// keep the previous space
|
||||||
|
m.SetCursor(m.pos + 1)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if i > len(m.value) {
|
||||||
|
m.value = m.value[:m.pos]
|
||||||
|
} else {
|
||||||
|
m.value = append(m.value[:m.pos], m.value[i:]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// deleteWordRight deletes the word right to the cursor.
|
||||||
|
func (m *Model) deleteWordRight() {
|
||||||
|
if m.pos >= len(m.value) || len(m.value) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
i := m.pos
|
||||||
|
m.SetCursor(m.pos + 1)
|
||||||
|
for unicode.IsSpace(m.value[m.pos]) {
|
||||||
|
// ignore series of whitespace after cursor
|
||||||
|
m.SetCursor(m.pos + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
for m.pos < len(m.value) {
|
||||||
|
if !unicode.IsSpace(m.value[m.pos]) {
|
||||||
|
m.SetCursor(m.pos + 1)
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.pos > len(m.value) {
|
||||||
|
m.value = m.value[:i]
|
||||||
|
} else {
|
||||||
|
m.value = append(m.value[:i], m.value[m.pos:]...)
|
||||||
|
}
|
||||||
|
m.SetCursor(i)
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Model) wordLeft() {
|
func (m *Model) wordLeft() {
|
||||||
if m.pos == 0 || len(m.value) == 0 {
|
if m.pos == 0 || len(m.value) == 0 {
|
||||||
return
|
return
|
||||||
@@ -259,7 +326,7 @@ func (m *Model) wordLeft() {
|
|||||||
|
|
||||||
for i >= 0 {
|
for i >= 0 {
|
||||||
if unicode.IsSpace(m.value[i]) {
|
if unicode.IsSpace(m.value[i]) {
|
||||||
m.pos--
|
m.SetCursor(m.pos - 1)
|
||||||
i--
|
i--
|
||||||
} else {
|
} else {
|
||||||
break
|
break
|
||||||
@@ -268,7 +335,7 @@ func (m *Model) wordLeft() {
|
|||||||
|
|
||||||
for i >= 0 {
|
for i >= 0 {
|
||||||
if !unicode.IsSpace(m.value[i]) {
|
if !unicode.IsSpace(m.value[i]) {
|
||||||
m.pos--
|
m.SetCursor(m.pos - 1)
|
||||||
i--
|
i--
|
||||||
} else {
|
} else {
|
||||||
break
|
break
|
||||||
@@ -285,7 +352,7 @@ func (m *Model) wordRight() {
|
|||||||
|
|
||||||
for i < len(m.value) {
|
for i < len(m.value) {
|
||||||
if unicode.IsSpace(m.value[i]) {
|
if unicode.IsSpace(m.value[i]) {
|
||||||
m.pos++
|
m.SetCursor(m.pos + 1)
|
||||||
i++
|
i++
|
||||||
} else {
|
} else {
|
||||||
break
|
break
|
||||||
@@ -294,7 +361,7 @@ func (m *Model) wordRight() {
|
|||||||
|
|
||||||
for i < len(m.value) {
|
for i < len(m.value) {
|
||||||
if !unicode.IsSpace(m.value[i]) {
|
if !unicode.IsSpace(m.value[i]) {
|
||||||
m.pos++
|
m.SetCursor(m.pos + 1)
|
||||||
i++
|
i++
|
||||||
} else {
|
} else {
|
||||||
break
|
break
|
||||||
@@ -351,10 +418,14 @@ func Update(msg tea.Msg, m Model) (Model, tea.Cmd) {
|
|||||||
case tea.KeyMsg:
|
case tea.KeyMsg:
|
||||||
switch msg.Type {
|
switch msg.Type {
|
||||||
case tea.KeyBackspace: // delete character before cursor
|
case tea.KeyBackspace: // delete character before cursor
|
||||||
if len(m.value) > 0 {
|
if msg.Alt {
|
||||||
m.value = append(m.value[:max(0, m.pos-1)], m.value[m.pos:]...)
|
m.deleteWordLeft()
|
||||||
if m.pos > 0 {
|
} else {
|
||||||
m.pos--
|
if len(m.value) > 0 {
|
||||||
|
m.value = append(m.value[:max(0, m.pos-1)], m.value[m.pos:]...)
|
||||||
|
if m.pos > 0 {
|
||||||
|
m.SetCursor(m.pos - 1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case tea.KeyLeft:
|
case tea.KeyLeft:
|
||||||
@@ -363,7 +434,7 @@ func Update(msg tea.Msg, m Model) (Model, tea.Cmd) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
if m.pos > 0 {
|
if m.pos > 0 {
|
||||||
m.pos--
|
m.SetCursor(m.pos - 1)
|
||||||
}
|
}
|
||||||
case tea.KeyRight:
|
case tea.KeyRight:
|
||||||
if msg.Alt { // alt+right arrow, forward one word
|
if msg.Alt { // alt+right arrow, forward one word
|
||||||
@@ -371,8 +442,10 @@ func Update(msg tea.Msg, m Model) (Model, tea.Cmd) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
if m.pos < len(m.value) {
|
if m.pos < len(m.value) {
|
||||||
m.pos++
|
m.SetCursor(m.pos + 1)
|
||||||
}
|
}
|
||||||
|
case tea.KeyCtrlW: // ^W, delete word left of cursor
|
||||||
|
m.deleteWordLeft()
|
||||||
case tea.KeyCtrlF: // ^F, forward one character
|
case tea.KeyCtrlF: // ^F, forward one character
|
||||||
fallthrough
|
fallthrough
|
||||||
case tea.KeyCtrlB: // ^B, back one charcter
|
case tea.KeyCtrlB: // ^B, back one charcter
|
||||||
@@ -387,16 +460,19 @@ func Update(msg tea.Msg, m Model) (Model, tea.Cmd) {
|
|||||||
m.CursorEnd()
|
m.CursorEnd()
|
||||||
case tea.KeyCtrlK: // ^K, kill text after cursor
|
case tea.KeyCtrlK: // ^K, kill text after cursor
|
||||||
m.value = m.value[:m.pos]
|
m.value = m.value[:m.pos]
|
||||||
m.pos = len(m.value)
|
m.SetCursor(len(m.value))
|
||||||
case tea.KeyCtrlU: // ^U, kill text before cursor
|
case tea.KeyCtrlU: // ^U, kill text before cursor
|
||||||
m.value = m.value[m.pos:]
|
m.value = m.value[m.pos:]
|
||||||
m.pos = 0
|
m.SetCursor(0)
|
||||||
m.offset = 0
|
m.offset = 0
|
||||||
case tea.KeyCtrlV: // ^V paste
|
case tea.KeyCtrlV: // ^V paste
|
||||||
m.Paste()
|
m.Paste()
|
||||||
case tea.KeyRune: // input a regular character
|
case tea.KeyRune: // input a regular character
|
||||||
|
|
||||||
if msg.Alt {
|
if msg.Alt {
|
||||||
|
if msg.Rune == 'd' { // alt+d, delete word right of cursor
|
||||||
|
m.deleteWordRight()
|
||||||
|
break
|
||||||
|
}
|
||||||
if msg.Rune == 'b' { // alt+b, back one word
|
if msg.Rune == 'b' { // alt+b, back one word
|
||||||
m.wordLeft()
|
m.wordLeft()
|
||||||
break
|
break
|
||||||
@@ -410,7 +486,7 @@ func Update(msg tea.Msg, m Model) (Model, tea.Cmd) {
|
|||||||
// Input a regular character
|
// Input a regular character
|
||||||
if m.CharLimit <= 0 || len(m.value) < m.CharLimit {
|
if m.CharLimit <= 0 || len(m.value) < m.CharLimit {
|
||||||
m.value = append(m.value[:m.pos], append([]rune{msg.Rune}, m.value[m.pos:]...)...)
|
m.value = append(m.value[:m.pos], append([]rune{msg.Rune}, m.value[m.pos:]...)...)
|
||||||
m.pos++
|
m.SetCursor(m.pos + 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -77,7 +77,7 @@ func (m *Model) SetContent(s string) {
|
|||||||
func (m Model) visibleLines() (lines []string) {
|
func (m Model) visibleLines() (lines []string) {
|
||||||
if len(m.lines) > 0 {
|
if len(m.lines) > 0 {
|
||||||
top := max(0, m.YOffset)
|
top := max(0, m.YOffset)
|
||||||
bottom := min(len(m.lines), m.YOffset+m.Height)
|
bottom := clamp(m.YOffset+m.Height, top, len(m.lines))
|
||||||
lines = m.lines[top:bottom]
|
lines = m.lines[top:bottom]
|
||||||
}
|
}
|
||||||
return lines
|
return lines
|
||||||
@@ -125,7 +125,7 @@ func (m *Model) HalfViewDown() (lines []string) {
|
|||||||
|
|
||||||
if len(m.lines) > 0 {
|
if len(m.lines) > 0 {
|
||||||
top := max(m.YOffset+m.Height/2, 0)
|
top := max(m.YOffset+m.Height/2, 0)
|
||||||
bottom := min(m.YOffset+m.Height, len(m.lines)-1)
|
bottom := clamp(m.YOffset+m.Height, top, len(m.lines)-1)
|
||||||
lines = m.lines[top:bottom]
|
lines = m.lines[top:bottom]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,7 +145,7 @@ func (m *Model) HalfViewUp() (lines []string) {
|
|||||||
|
|
||||||
if len(m.lines) > 0 {
|
if len(m.lines) > 0 {
|
||||||
top := max(m.YOffset, 0)
|
top := max(m.YOffset, 0)
|
||||||
bottom := min(m.YOffset+m.Height/2, len(m.lines)-1)
|
bottom := clamp(m.YOffset+m.Height/2, top, len(m.lines)-1)
|
||||||
lines = m.lines[top:bottom]
|
lines = m.lines[top:bottom]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,7 +171,7 @@ func (m *Model) LineDown(n int) (lines []string) {
|
|||||||
|
|
||||||
if len(m.lines) > 0 {
|
if len(m.lines) > 0 {
|
||||||
top := max(m.YOffset+m.Height-n, 0)
|
top := max(m.YOffset+m.Height-n, 0)
|
||||||
bottom := min(m.YOffset+m.Height, len(m.lines)-1)
|
bottom := clamp(m.YOffset+m.Height, top, len(m.lines)-1)
|
||||||
lines = m.lines[top:bottom]
|
lines = m.lines[top:bottom]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,7 +193,7 @@ func (m *Model) LineUp(n int) (lines []string) {
|
|||||||
|
|
||||||
if len(m.lines) > 0 {
|
if len(m.lines) > 0 {
|
||||||
top := max(0, m.YOffset)
|
top := max(0, m.YOffset)
|
||||||
bottom := min(m.YOffset+n, len(m.lines)-1)
|
bottom := clamp(m.YOffset+n, top, len(m.lines)-1)
|
||||||
lines = m.lines[top:bottom]
|
lines = m.lines[top:bottom]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,7 +210,7 @@ func (m *Model) GotoTop() (lines []string) {
|
|||||||
|
|
||||||
if len(m.lines) > 0 {
|
if len(m.lines) > 0 {
|
||||||
top := m.YOffset
|
top := m.YOffset
|
||||||
bottom := min(m.YOffset+m.Height, len(m.lines)-1)
|
bottom := clamp(m.YOffset+m.Height, top, len(m.lines)-1)
|
||||||
lines = m.lines[top:bottom]
|
lines = m.lines[top:bottom]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,7 +245,7 @@ func Sync(m Model) tea.Cmd {
|
|||||||
// TODO: we should probably use m.visibleLines() rather than these two
|
// TODO: we should probably use m.visibleLines() rather than these two
|
||||||
// expressions.
|
// expressions.
|
||||||
top := max(m.YOffset, 0)
|
top := max(m.YOffset, 0)
|
||||||
bottom := min(m.YOffset+m.Height, len(m.lines)-1)
|
bottom := clamp(m.YOffset+m.Height, 0, len(m.lines)-1)
|
||||||
|
|
||||||
return tea.SyncScrollArea(
|
return tea.SyncScrollArea(
|
||||||
m.lines[top:bottom],
|
m.lines[top:bottom],
|
||||||
@@ -376,6 +376,10 @@ func View(m Model) string {
|
|||||||
|
|
||||||
// ETC
|
// ETC
|
||||||
|
|
||||||
|
func clamp(v, low, high int) int {
|
||||||
|
return min(high, max(low, v))
|
||||||
|
}
|
||||||
|
|
||||||
func min(a, b int) int {
|
func min(a, b int) int {
|
||||||
if a < b {
|
if a < b {
|
||||||
return a
|
return a
|
||||||
|
Reference in New Issue
Block a user