17 Commits

Author SHA1 Message Date
lorenries
1d489252fe fix(list): disable quit keybinding while filtering 2022-02-08 14:42:03 -05:00
treilik
06358c35f9 Add bubblelister and bubbleboxer to "additional bubbles" in the README (#113) 2022-02-06 07:04:52 +01:00
mirko
005233b529 Improve insert item documentation (#115) 2022-02-03 02:58:08 +01:00
Ayman Bagabas
18d25458da fix(list): DisableQuitKeybinding is ignored after updating the list (#108) 2022-01-27 13:09:53 -05:00
Ayman Bagabas
db97ac515d feat: sync bubbles with git.charm.sh 2022-01-24 17:08:23 -05:00
Christian Muehlhaeuser
200f95759b Fix key binding documentation
Fixes #105.
2022-01-24 15:09:43 +01:00
Christian Rocha
7ecce3fb97 Ignore width/height settings in viewport's style settings
The Lip Gloss width and height settings compete with the main
width/height settings and can result in funny rendering and generally
cause confusion.
2022-01-20 13:23:58 -05:00
Christian Rocha
746834a7ce Add safety check in textinput's clamp 2022-01-20 13:23:58 -05:00
Christian Rocha
fd306528f9 Rename var in deleteWordRight in textinput for additional clarity 2022-01-20 13:23:58 -05:00
Christian Rocha
a4dc540f3d Re-add panic guard in deleteWordLeft in textinput, just in case 2022-01-20 13:23:58 -05:00
IllusionMan1212
151d1026dd fix(textinput): use old cursor pos and simplify logic 2022-01-19 18:31:55 -05:00
Christian Rocha
daba232df4 Fix a bug where viewport y-offset could be negative 2022-01-19 12:32:05 -05:00
Christian Rocha
7a6d306889 Fix off-by-one error in viewport's GotoBottom()
This was most likely introduced by the fix for rendering final lines in
0ac5ecdf81.
2022-01-19 12:32:05 -05:00
Christian Rocha
6c015a2aa8 Remove an unused method 2022-01-19 12:31:50 -05:00
Christian Rocha
764fd321f6 Cleanup an old var 2022-01-19 12:31:50 -05:00
Christian Rocha
091c915462 Fix various godoc comments 2022-01-19 12:31:50 -05:00
Christian Rocha
505a16d057 Bump lipgloss and atotto/clipboard to their latest versions 2022-01-14 13:40:18 -05:00
10 changed files with 82 additions and 43 deletions

12
.github/workflows/soft-serve.yml vendored Normal file
View File

@@ -0,0 +1,12 @@
name: soft-serve
on:
push:
branches:
- master
jobs:
soft-serve:
uses: charmbracelet/meta/.github/workflows/soft-serve.yml@main
secrets:
ssh-key: "${{ secrets.CHARM_SOFT_SERVE_KEY }}"

View File

@@ -149,8 +149,8 @@ var DefaultKeyMap = KeyMap{
key.WithHelp("↑/k", "move up"), // corresponding help text key.WithHelp("↑/k", "move up"), // corresponding help text
), ),
Down: key.NewBinding( Down: key.NewBinding(
WithKeys("j", "down"), key.WithKeys("j", "down"),
WithHelp("↓/j", "move down"), key.WithHelp("↓/j", "move down"),
), ),
} }
@@ -178,6 +178,11 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
* [mritd/bubbles](https://github.com/mritd/bubbles): Some general-purpose * [mritd/bubbles](https://github.com/mritd/bubbles): Some general-purpose
bubbles. Inputs with validation, menu selection, a modified progressbar, and bubbles. Inputs with validation, menu selection, a modified progressbar, and
so on. so on.
* [bubblelister](https://github.com/treilik/bubblelister): An alternate list
that is scrollable without pagination and has the ability to contain other
bubbles as list items.
* [bubbleboxer](https://github.com/treilik/bubbleboxer): Layout multiple bubbles
side-by-side in a layout-tree.
If youve built a Bubble you think should be listed here, If youve built a Bubble you think should be listed here,
[let us know](mailto:vt100@charm.sh). [let us know](mailto:vt100@charm.sh).

4
go.mod
View File

@@ -3,10 +3,10 @@ module github.com/charmbracelet/bubbles
go 1.13 go 1.13
require ( require (
github.com/atotto/clipboard v0.1.2 github.com/atotto/clipboard v0.1.4
github.com/charmbracelet/bubbletea v0.19.3 github.com/charmbracelet/bubbletea v0.19.3
github.com/charmbracelet/harmonica v0.1.0 github.com/charmbracelet/harmonica v0.1.0
github.com/charmbracelet/lipgloss v0.3.0 github.com/charmbracelet/lipgloss v0.4.0
github.com/kylelemons/godebug v1.1.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 github.com/lucasb-eyer/go-colorful v1.2.0
github.com/mattn/go-runewidth v0.0.13 github.com/mattn/go-runewidth v0.0.13

10
go.sum
View File

@@ -1,18 +1,17 @@
github.com/atotto/clipboard v0.1.2 h1:YZCtFu5Ie8qX2VmVTBnrqLSiU9XOWwqNRmdT3gIQzbY= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.2/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/charmbracelet/bubbletea v0.19.3 h1:OKeO/Y13rQQqt4snX+lePB0QrnW80UdrMNolnCcmoAw= github.com/charmbracelet/bubbletea v0.19.3 h1:OKeO/Y13rQQqt4snX+lePB0QrnW80UdrMNolnCcmoAw=
github.com/charmbracelet/bubbletea v0.19.3/go.mod h1:VuXF2pToRxDUHcBUcPmCRUHRvFATM4Ckb/ql1rBl3KA= github.com/charmbracelet/bubbletea v0.19.3/go.mod h1:VuXF2pToRxDUHcBUcPmCRUHRvFATM4Ckb/ql1rBl3KA=
github.com/charmbracelet/harmonica v0.1.0 h1:lFKeSd6OAckQ/CEzPVd2mqj+YMEubQ/3FM2IYY3xNm0= github.com/charmbracelet/harmonica v0.1.0 h1:lFKeSd6OAckQ/CEzPVd2mqj+YMEubQ/3FM2IYY3xNm0=
github.com/charmbracelet/harmonica v0.1.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao= github.com/charmbracelet/harmonica v0.1.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
github.com/charmbracelet/lipgloss v0.3.0 h1:5MysOD6sHr4RP4jkZNWGVIul5GKoOsP12NgbgXPvAlA= github.com/charmbracelet/lipgloss v0.4.0 h1:768h64EFkGUr8V5yAKV7/Ta0NiVceiPaV+PphaW1K9g=
github.com/charmbracelet/lipgloss v0.3.0/go.mod h1:VkhdBS2eNAmRkTwRKLJCFhCOVkjntMusBDxv7TXahuk= github.com/charmbracelet/lipgloss v0.4.0/go.mod h1:vmdkHvce7UzX6xkyf4cca8WlwdQ5RQr8fzta+xl7BOM=
github.com/containerd/console v1.0.2 h1:Pi6D+aZXM+oUw1czuKgH5IJ+y0jhYcwBJfx5/Ghn9dE= github.com/containerd/console v1.0.2 h1:Pi6D+aZXM+oUw1czuKgH5IJ+y0jhYcwBJfx5/Ghn9dE=
github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ= github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA= github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA=
github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
@@ -24,7 +23,6 @@ github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBc
github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ= github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ=
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
github.com/muesli/termenv v0.8.1/go.mod h1:kzt/D/4a88RoheZmwfqorY3A+tnsSMA9HJC/fQSFKo0=
github.com/muesli/termenv v0.9.0 h1:wnbOaGz+LUR3jNT0zOzinPnyDaCZUQRZj9GxK8eRVl8= github.com/muesli/termenv v0.9.0 h1:wnbOaGz+LUR3jNT0zOzinPnyDaCZUQRZj9GxK8eRVl8=
github.com/muesli/termenv v0.9.0/go.mod h1:R/LzAKf+suGs4IsO95y7+7DpFHO0KABgnZqtlyx2mBw= github.com/muesli/termenv v0.9.0/go.mod h1:R/LzAKf+suGs4IsO95y7+7DpFHO0KABgnZqtlyx2mBw=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=

View File

@@ -165,7 +165,12 @@ func (m Model) FullHelpView(groups [][]key.Binding) string {
} }
var ( var (
// Linter note: at this time we don't think it's worth the additional
// code complexity involved in preallocating this slice.
//
//nolint:prealloc
out []string out []string
totalWidth int totalWidth int
sep = m.Styles.FullSeparator.Render(m.FullSeparator) sep = m.Styles.FullSeparator.Render(m.FullSeparator)
sepWidth = lipgloss.Width(sep) sepWidth = lipgloss.Width(sep)

View File

@@ -13,8 +13,8 @@
// key.WithHelp("↑/k", "move up"), // corresponding help text // key.WithHelp("↑/k", "move up"), // corresponding help text
// ), // ),
// Down: key.NewBinding( // Down: key.NewBinding(
// WithKeys("j", "down"), // key.WithKeys("j", "down"),
// WithHelp("↓/j", "move down"), // key.WithHelp("↓/j", "move down"),
// ), // ),
// } // }
// //

View File

@@ -67,14 +67,8 @@ func (f filteredItems) items() []Item {
return agg return agg
} }
func (f filteredItems) matches() [][]int { // FilterMatchesMsg contains data about items matched during filtering. The
agg := make([][]int, len(f)) // message should be routed to Update for processing.
for i, v := range f {
agg[i] = v.matches
}
return agg
}
type FilterMatchesMsg []filteredItem type FilterMatchesMsg []filteredItem
type statusMessageTimeoutMsg struct{} type statusMessageTimeoutMsg struct{}
@@ -113,6 +107,8 @@ type Model struct {
// Key mappings for navigating the list. // Key mappings for navigating the list.
KeyMap KeyMap KeyMap KeyMap
disableQuitKeybindings bool
// Additional key mappings for the short and full help views. This allows // Additional key mappings for the short and full help views. This allows
// you to add additional key mappings to the help menu without // you to add additional key mappings to the help menu without
// re-implementing the help component. Of course, you can also disable the // re-implementing the help component. Of course, you can also disable the
@@ -330,7 +326,8 @@ func (m *Model) SetItem(index int, item Item) tea.Cmd {
return cmd return cmd
} }
// Insert an item at the given index. This returns a command. // Insert an item at the given index. If index is out of the upper bound, the
// item will be appended. This returns a command.
func (m *Model) InsertItem(index int, item Item) tea.Cmd { func (m *Model) InsertItem(index int, item Item) tea.Cmd {
var cmd tea.Cmd var cmd tea.Cmd
m.items = insertItemIntoSlice(m.items, item, index) m.items = insertItemIntoSlice(m.items, item, index)
@@ -526,6 +523,7 @@ func (m *Model) StopSpinner() {
// Helper for disabling the keybindings used for quitting, incase you want to // Helper for disabling the keybindings used for quitting, incase you want to
// handle this elsewhere in your application. // handle this elsewhere in your application.
func (m *Model) DisableQuitKeybindings() { func (m *Model) DisableQuitKeybindings() {
m.disableQuitKeybindings = true
m.KeyMap.Quit.SetEnabled(false) m.KeyMap.Quit.SetEnabled(false)
m.KeyMap.ForceQuit.SetEnabled(false) m.KeyMap.ForceQuit.SetEnabled(false)
} }
@@ -608,7 +606,7 @@ func (m *Model) updateKeybindings() {
m.KeyMap.ClearFilter.SetEnabled(false) m.KeyMap.ClearFilter.SetEnabled(false)
m.KeyMap.CancelWhileFiltering.SetEnabled(true) m.KeyMap.CancelWhileFiltering.SetEnabled(true)
m.KeyMap.AcceptWhileFiltering.SetEnabled(m.FilterInput.Value() != "") m.KeyMap.AcceptWhileFiltering.SetEnabled(m.FilterInput.Value() != "")
m.KeyMap.Quit.SetEnabled(true) m.KeyMap.Quit.SetEnabled(false)
m.KeyMap.ShowFullHelp.SetEnabled(false) m.KeyMap.ShowFullHelp.SetEnabled(false)
m.KeyMap.CloseFullHelp.SetEnabled(false) m.KeyMap.CloseFullHelp.SetEnabled(false)
@@ -628,7 +626,7 @@ func (m *Model) updateKeybindings() {
m.KeyMap.ClearFilter.SetEnabled(m.filterState == FilterApplied) m.KeyMap.ClearFilter.SetEnabled(m.filterState == FilterApplied)
m.KeyMap.CancelWhileFiltering.SetEnabled(false) m.KeyMap.CancelWhileFiltering.SetEnabled(false)
m.KeyMap.AcceptWhileFiltering.SetEnabled(false) m.KeyMap.AcceptWhileFiltering.SetEnabled(false)
m.KeyMap.Quit.SetEnabled(true) m.KeyMap.Quit.SetEnabled(!m.disableQuitKeybindings)
if m.Help.ShowAll { if m.Help.ShowAll {
m.KeyMap.ShowFullHelp.SetEnabled(true) m.KeyMap.ShowFullHelp.SetEnabled(true)

View File

@@ -98,6 +98,11 @@ func WithWidth(w int) Option {
} }
} }
// WithSpringOptions sets the initial frequency and damping options for the
// progressbar's built-in spring-based animation. Frequency corresponds to
// speed, and damping to bounciness. For details see:
//
// https://github.com/charmbracelet/harmonica
func WithSpringOptions(frequency, damping float64) Option { func WithSpringOptions(frequency, damping float64) Option {
return func(m *Model) { return func(m *Model) {
m.SetSpringOptions(frequency, damping) m.SetSpringOptions(frequency, damping)
@@ -213,7 +218,8 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// SetSpringOptions sets the frequency and damping for the current spring. // SetSpringOptions sets the frequency and damping for the current spring.
// Frequency corresponds to speed, and damping to bounciness. For details see: // Frequency corresponds to speed, and damping to bounciness. For details see:
// https://github.com/charmbracelet/harmonica. //
// https://github.com/charmbracelet/harmonica
func (m *Model) SetSpringOptions(frequency, damping float64) { func (m *Model) SetSpringOptions(frequency, damping float64) {
m.spring = harmonica.NewSpring(harmonica.FPS(fps), frequency, damping) m.spring = harmonica.NewSpring(harmonica.FPS(fps), frequency, damping)
} }

View File

@@ -62,7 +62,7 @@ const (
// seen for password fields on the command line. // seen for password fields on the command line.
EchoNone EchoNone
// EchoOnEdit // EchoOnEdit.
) )
// blinkCtx manages cursor blinking. // blinkCtx manages cursor blinking.
@@ -234,7 +234,7 @@ func (m *Model) cursorStart() bool {
return m.setCursor(0) return m.setCursor(0)
} }
// CursorEnd moves the cursor to the end of the input field // CursorEnd moves the cursor to the end of the input field.
func (m *Model) CursorEnd() { func (m *Model) CursorEnd() {
m.cursorEnd() m.cursorEnd()
} }
@@ -412,7 +412,11 @@ func (m *Model) deleteWordLeft() bool {
return m.deleteBeforeCursor() return m.deleteBeforeCursor()
} }
i := m.pos // Linter note: it's critical that we acquire the initial cursor position
// here prior to altering it via SetCursor() below. As such, moving this
// call into the corresponding if clause does not apply here.
oldPos := m.pos //nolint:ifshort
blink := m.setCursor(m.pos - 1) blink := m.setCursor(m.pos - 1)
for unicode.IsSpace(m.value[m.pos]) { for unicode.IsSpace(m.value[m.pos]) {
if m.pos <= 0 { if m.pos <= 0 {
@@ -434,10 +438,10 @@ func (m *Model) deleteWordLeft() bool {
} }
} }
if i > len(m.value) { if oldPos > len(m.value) {
m.value = m.value[:m.pos] m.value = m.value[:m.pos]
} else { } else {
m.value = append(m.value[:m.pos], m.value[i:]...) m.value = append(m.value[:m.pos], m.value[oldPos:]...)
} }
return blink return blink
@@ -455,7 +459,7 @@ func (m *Model) deleteWordRight() bool {
return m.deleteAfterCursor() return m.deleteAfterCursor()
} }
i := m.pos oldPos := m.pos
m.setCursor(m.pos + 1) m.setCursor(m.pos + 1)
for unicode.IsSpace(m.value[m.pos]) { for unicode.IsSpace(m.value[m.pos]) {
// ignore series of whitespace after cursor // ignore series of whitespace after cursor
@@ -475,12 +479,12 @@ func (m *Model) deleteWordRight() bool {
} }
if m.pos > len(m.value) { if m.pos > len(m.value) {
m.value = m.value[:i] m.value = m.value[:oldPos]
} else { } else {
m.value = append(m.value[:i], m.value[m.pos:]...) m.value = append(m.value[:oldPos], m.value[m.pos:]...)
} }
return m.setCursor(i) return m.setCursor(oldPos)
} }
// wordLeft moves the cursor one word to the left. Returns whether or not the // wordLeft moves the cursor one word to the left. Returns whether or not the
@@ -796,6 +800,9 @@ func Paste() tea.Msg {
} }
func clamp(v, low, high int) int { func clamp(v, low, high int) int {
if high < low {
low, high = high, low
}
return min(high, max(low, v)) return min(high, max(low, v))
} }

View File

@@ -76,13 +76,13 @@ func (m Model) AtTop() bool {
// AtBottom returns whether or not the viewport is at or past the very bottom // AtBottom returns whether or not the viewport is at or past the very bottom
// position. // position.
func (m Model) AtBottom() bool { func (m Model) AtBottom() bool {
return m.YOffset >= len(m.lines)-m.Height return m.YOffset >= m.maxYOffset()
} }
// PastBottom returns whether or not the viewport is scrolled beyond the last // PastBottom returns whether or not the viewport is scrolled beyond the last
// line. This can happen when adjusting the viewport height. // line. This can happen when adjusting the viewport height.
func (m Model) PastBottom() bool { func (m Model) PastBottom() bool {
return m.YOffset > len(m.lines)-m.Height return m.YOffset > m.maxYOffset()
} }
// ScrollPercent returns the amount scrolled as a float between 0 and 1. // ScrollPercent returns the amount scrolled as a float between 0 and 1.
@@ -108,6 +108,12 @@ func (m *Model) SetContent(s string) {
} }
} }
// maxYOffset returns the maximum possible value of the y-offset based on the
// viewport's content and set height.
func (m Model) maxYOffset() int {
return max(0, len(m.lines)-m.Height)
}
// visibleLines returns the lines that should currently be visible in the // visibleLines returns the lines that should currently be visible in the
// viewport. // viewport.
func (m Model) visibleLines() (lines []string) { func (m Model) visibleLines() (lines []string) {
@@ -131,7 +137,7 @@ func (m Model) scrollArea() (top, bottom int) {
// SetYOffset sets the Y offset. // SetYOffset sets the Y offset.
func (m *Model) SetYOffset(n int) { func (m *Model) SetYOffset(n int) {
m.YOffset = clamp(n, 0, len(m.lines)-m.Height) m.YOffset = clamp(n, 0, m.maxYOffset())
} }
// ViewDown moves the view down by the number of lines in the viewport. // ViewDown moves the view down by the number of lines in the viewport.
@@ -213,12 +219,10 @@ func (m *Model) GotoTop() (lines []string) {
// GotoBottom sets the viewport to the bottom position. // GotoBottom sets the viewport to the bottom position.
func (m *Model) GotoBottom() (lines []string) { func (m *Model) GotoBottom() (lines []string) {
m.SetYOffset(len(m.lines) - 1 - m.Height) m.SetYOffset(m.maxYOffset())
return m.visibleLines() return m.visibleLines()
} }
// COMMANDS
// Sync tells the renderer where the viewport will be located and requests // Sync tells the renderer where the viewport will be located and requests
// a render of the current state of the viewport. It should be called for the // a render of the current state of the viewport. It should be called for the
// first render and after a window resize. // first render and after a window resize.
@@ -354,12 +358,16 @@ func (m Model) View() string {
extraLines = strings.Repeat("\n", max(0, m.Height-len(lines))) extraLines = strings.Repeat("\n", max(0, m.Height-len(lines)))
} }
return m.Style.Render(strings.Join(lines, "\n") + extraLines) return m.Style.Copy().
UnsetWidth().
UnsetHeight().
Render(strings.Join(lines, "\n") + extraLines)
} }
// ETC
func clamp(v, low, high int) int { func clamp(v, low, high int) int {
if high < low {
low, high = high, low
}
return min(high, max(low, v)) return min(high, max(low, v))
} }