mirror of
https://github.com/Maks1mS/bubbles.git
synced 2025-10-17 16:15:58 +03:00
Compare commits
24 Commits
v0.10.2
...
filter-tex
Author | SHA1 | Date | |
---|---|---|---|
|
ad8d2fac86 | ||
|
cd2593cfb7 | ||
|
e1871db6d3 | ||
|
2a8d463bd1 | ||
|
c214837839 | ||
|
f5ac64216b | ||
|
292a1dd7ba | ||
|
154cdbc53a | ||
|
430b7b5d36 | ||
|
00ec90b59f | ||
|
aa0744fd8d | ||
|
cf1fe5f9ce | ||
|
6c18900279 | ||
|
d897463138 | ||
|
88562515cf | ||
|
e349920524 | ||
|
64b9e0582f | ||
|
057f7b9a4d | ||
|
1d489252fe | ||
|
06358c35f9 | ||
|
005233b529 | ||
|
18d25458da | ||
|
db97ac515d | ||
|
200f95759b |
12
.github/workflows/soft-serve.yml
vendored
Normal file
12
.github/workflows/soft-serve.yml
vendored
Normal 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 }}"
|
27
README.md
27
README.md
@@ -149,8 +149,8 @@ var DefaultKeyMap = KeyMap{
|
||||
key.WithHelp("↑/k", "move up"), // corresponding help text
|
||||
),
|
||||
Down: key.NewBinding(
|
||||
WithKeys("j", "down"),
|
||||
WithHelp("↓/j", "move down"),
|
||||
key.WithKeys("j", "down"),
|
||||
key.WithHelp("↓/j", "move down"),
|
||||
),
|
||||
}
|
||||
|
||||
@@ -171,13 +171,28 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
|
||||
## Additional Bubbles
|
||||
|
||||
* [promptkit](https://github.com/erikgeiser/promptkit): A collection of common
|
||||
prompts for cases like selection, text input, and confirmation. Each prompt
|
||||
comes with sensible defaults, remappable keybindings, any many customization
|
||||
options.
|
||||
<!-- in alphabetical order by author -->
|
||||
|
||||
* [76creates/stickers](https://github.com/76creates/stickers): Responsive
|
||||
flexbox and table components.
|
||||
* [calyptia/go-bubble-table](https://github.com/calyptia/go-bubble-table): An
|
||||
interactive, customizable, scrollable table component.
|
||||
* [erikgeiser/promptkit](https://github.com/erikgeiser/promptkit): A collection
|
||||
of common prompts for cases like selection, text input, and confirmation.
|
||||
Each prompt comes with sensible defaults, remappable keybindings, any many
|
||||
customization options.
|
||||
* [evertras/bubble-table](https://github.com/Evertras/bubble-table): Interactive,
|
||||
customizable, paginated tables.
|
||||
* [knipferrc/teacup](https://github.com/knipferrc/teacup): Various handy
|
||||
bubbles and utilities for building Bubble Tea applications.
|
||||
* [mritd/bubbles](https://github.com/mritd/bubbles): Some general-purpose
|
||||
bubbles. Inputs with validation, menu selection, a modified progressbar, and
|
||||
so on.
|
||||
* [treilik/bubbleboxer](https://github.com/treilik/bubbleboxer): Layout
|
||||
multiple bubbles side-by-side in a layout-tree.
|
||||
* [treilik/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.
|
||||
|
||||
If you’ve built a Bubble you think should be listed here,
|
||||
[let us know](mailto:vt100@charm.sh).
|
||||
|
2
go.mod
2
go.mod
@@ -5,6 +5,7 @@ go 1.13
|
||||
require (
|
||||
github.com/atotto/clipboard v0.1.4
|
||||
github.com/charmbracelet/bubbletea v0.19.3
|
||||
github.com/charmbracelet/glamour v0.5.0
|
||||
github.com/charmbracelet/harmonica v0.1.0
|
||||
github.com/charmbracelet/lipgloss v0.4.0
|
||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||
@@ -12,5 +13,6 @@ require (
|
||||
github.com/mattn/go-runewidth v0.0.13
|
||||
github.com/muesli/reflow v0.3.0
|
||||
github.com/muesli/termenv v0.9.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/sahilm/fuzzy v0.1.0
|
||||
)
|
||||
|
40
go.sum
40
go.sum
@@ -1,23 +1,39 @@
|
||||
github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek=
|
||||
github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s=
|
||||
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
||||
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
||||
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
||||
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
||||
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/glamour v0.5.0 h1:wu15ykPdB7X6chxugG/NNfDUbyyrCLV9XBalj5wdu3g=
|
||||
github.com/charmbracelet/glamour v0.5.0/go.mod h1:9ZRtG19AUIzcTm7FGLGbq3D5WKQ5UyZBbQsMQN0XIqc=
|
||||
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/lipgloss v0.4.0 h1:768h64EFkGUr8V5yAKV7/Ta0NiVceiPaV+PphaW1K9g=
|
||||
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/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E=
|
||||
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
|
||||
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
|
||||
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/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/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-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
|
||||
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/microcosm-cc/bluemonday v1.0.17 h1:Z1a//hgsQ4yjC+8zEkV8IWySkXnsxmdSY642CTFQb5Y=
|
||||
github.com/microcosm-cc/bluemonday v1.0.17/go.mod h1:Z0r70sCuXHig8YpBzCc5eGHAap2K7e/u082ZUpDRRqM=
|
||||
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34=
|
||||
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho=
|
||||
github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ=
|
||||
@@ -25,8 +41,12 @@ 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/termenv v0.9.0 h1:wnbOaGz+LUR3jNT0zOzinPnyDaCZUQRZj9GxK8eRVl8=
|
||||
github.com/muesli/termenv v0.9.0/go.mod h1:R/LzAKf+suGs4IsO95y7+7DpFHO0KABgnZqtlyx2mBw=
|
||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
@@ -35,10 +55,28 @@ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI=
|
||||
github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.4 h1:zNWRjYUW32G9KirMXYHQHVNFkXvMI7LpgNW2AgYAoIs=
|
||||
github.com/yuin/goldmark v1.4.4/go.mod h1:rmuwmfZ0+bvzB24eSC//bk1R1Zp3hM0OXYv/G2LIilg=
|
||||
github.com/yuin/goldmark-emoji v1.0.1 h1:ctuWEyzGBwiucEqxzwe0SOYDXPAucOrE9NQC18Wa1os=
|
||||
github.com/yuin/goldmark-emoji v1.0.1/go.mod h1:2w1E6FEWLcDQkoTE+7HU6QF1F6SLlNGjRIBbIZQFqkQ=
|
||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q=
|
||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210422114643-f5beecf764ed h1:Ei4bQjjpYUsS4efOUz+5Nz++IVkHk87n2zBA0NxBWc0=
|
||||
golang.org/x/term v0.0.0-20210422114643-f5beecf764ed/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
@@ -164,11 +164,10 @@ func (m Model) FullHelpView(groups [][]key.Binding) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Linter note: at this time we don't think it's worth the additional
|
||||
// code complexity involved in preallocating this slice.
|
||||
//nolint:prealloc
|
||||
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
|
||||
|
||||
totalWidth int
|
||||
|
@@ -13,8 +13,8 @@
|
||||
// key.WithHelp("↑/k", "move up"), // corresponding help text
|
||||
// ),
|
||||
// Down: key.NewBinding(
|
||||
// WithKeys("j", "down"),
|
||||
// WithHelp("↓/j", "move down"),
|
||||
// key.WithKeys("j", "down"),
|
||||
// key.WithHelp("↓/j", "move down"),
|
||||
// ),
|
||||
// }
|
||||
//
|
||||
|
@@ -3,6 +3,7 @@ package list
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/charmbracelet/bubbles/key"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
@@ -86,6 +87,7 @@ type DefaultDelegate struct {
|
||||
UpdateFunc func(tea.Msg, *Model) tea.Cmd
|
||||
ShortHelpFunc func() []key.Binding
|
||||
FullHelpFunc func() [][]key.Binding
|
||||
height int
|
||||
spacing int
|
||||
}
|
||||
|
||||
@@ -94,14 +96,22 @@ func NewDefaultDelegate() DefaultDelegate {
|
||||
return DefaultDelegate{
|
||||
ShowDescription: true,
|
||||
Styles: NewDefaultItemStyles(),
|
||||
height: 2,
|
||||
spacing: 1,
|
||||
}
|
||||
}
|
||||
|
||||
// SetHeight sets delegate's preferred height.
|
||||
func (d *DefaultDelegate) SetHeight(i int) {
|
||||
d.height = i
|
||||
}
|
||||
|
||||
// Height returns the delegate's preferred height.
|
||||
// This has effect only if ShowDescription is true,
|
||||
// otherwise height is always 1.
|
||||
func (d DefaultDelegate) Height() int {
|
||||
if d.ShowDescription {
|
||||
return 2 //nolint:gomnd
|
||||
return d.height
|
||||
}
|
||||
return 1
|
||||
}
|
||||
@@ -139,11 +149,23 @@ func (d DefaultDelegate) Render(w io.Writer, m Model, index int, item Item) {
|
||||
return
|
||||
}
|
||||
|
||||
if m.width <= 0 {
|
||||
// short-circuit
|
||||
return
|
||||
}
|
||||
|
||||
// Prevent text from exceeding list width
|
||||
if m.width > 0 {
|
||||
textwidth := uint(m.width - s.NormalTitle.GetPaddingLeft() - s.NormalTitle.GetPaddingRight())
|
||||
title = truncate.StringWithTail(title, textwidth, ellipsis)
|
||||
desc = truncate.StringWithTail(desc, textwidth, ellipsis)
|
||||
textwidth := uint(m.width - s.NormalTitle.GetPaddingLeft() - s.NormalTitle.GetPaddingRight())
|
||||
title = truncate.StringWithTail(title, textwidth, ellipsis)
|
||||
if d.ShowDescription {
|
||||
var lines []string
|
||||
for i, line := range strings.Split(desc, "\n") {
|
||||
if i >= d.height-1 {
|
||||
break
|
||||
}
|
||||
lines = append(lines, truncate.StringWithTail(line, textwidth, ellipsis))
|
||||
}
|
||||
desc = strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
// Conditions
|
||||
|
54
list/list.go
54
list/list.go
@@ -71,6 +71,34 @@ func (f filteredItems) items() []Item {
|
||||
// message should be routed to Update for processing.
|
||||
type FilterMatchesMsg []filteredItem
|
||||
|
||||
// FilterFunc takes a term and a list of strings to search through
|
||||
// (defined by Item#FilterValue).
|
||||
// It should return a sorted list of ranks.
|
||||
type FilterFunc func(string, []string) []Rank
|
||||
|
||||
// Rank defines a rank for a given item.
|
||||
type Rank struct {
|
||||
// The index of the item in the original input.
|
||||
Index int
|
||||
// Indices of the actual word that were matched against the filter term.
|
||||
MatchedIndexes []int
|
||||
}
|
||||
|
||||
// DefaultFilter uses the sahilm/fuzzy to filter through the list.
|
||||
// This is set by default.
|
||||
func DefaultFilter(term string, targets []string) []Rank {
|
||||
var ranks fuzzy.Matches = fuzzy.Find(term, targets)
|
||||
sort.Stable(ranks)
|
||||
result := make([]Rank, len(ranks))
|
||||
for i, r := range ranks {
|
||||
result[i] = Rank{
|
||||
Index: r.Index,
|
||||
MatchedIndexes: r.MatchedIndexes,
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
type statusMessageTimeoutMsg struct{}
|
||||
|
||||
// FilterState describes the current filtering state on the model.
|
||||
@@ -107,6 +135,11 @@ type Model struct {
|
||||
// Key mappings for navigating the list.
|
||||
KeyMap KeyMap
|
||||
|
||||
// Filter is used to filter the list.
|
||||
Filter FilterFunc
|
||||
|
||||
disableQuitKeybindings bool
|
||||
|
||||
// Additional key mappings for the short and full help views. This allows
|
||||
// you to add additional key mappings to the help menu without
|
||||
// re-implementing the help component. Of course, you can also disable the
|
||||
@@ -171,6 +204,7 @@ func New(items []Item, delegate ItemDelegate, width, height int) Model {
|
||||
showHelp: true,
|
||||
filteringEnabled: true,
|
||||
KeyMap: DefaultKeyMap(),
|
||||
Filter: DefaultFilter,
|
||||
Styles: styles,
|
||||
Title: "List",
|
||||
FilterInput: filterInput,
|
||||
@@ -324,7 +358,8 @@ func (m *Model) SetItem(index int, item Item) tea.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 {
|
||||
var cmd tea.Cmd
|
||||
m.items = insertItemIntoSlice(m.items, item, index)
|
||||
@@ -520,6 +555,7 @@ func (m *Model) StopSpinner() {
|
||||
// Helper for disabling the keybindings used for quitting, incase you want to
|
||||
// handle this elsewhere in your application.
|
||||
func (m *Model) DisableQuitKeybindings() {
|
||||
m.disableQuitKeybindings = true
|
||||
m.KeyMap.Quit.SetEnabled(false)
|
||||
m.KeyMap.ForceQuit.SetEnabled(false)
|
||||
}
|
||||
@@ -602,7 +638,7 @@ func (m *Model) updateKeybindings() {
|
||||
m.KeyMap.ClearFilter.SetEnabled(false)
|
||||
m.KeyMap.CancelWhileFiltering.SetEnabled(true)
|
||||
m.KeyMap.AcceptWhileFiltering.SetEnabled(m.FilterInput.Value() != "")
|
||||
m.KeyMap.Quit.SetEnabled(true)
|
||||
m.KeyMap.Quit.SetEnabled(false)
|
||||
m.KeyMap.ShowFullHelp.SetEnabled(false)
|
||||
m.KeyMap.CloseFullHelp.SetEnabled(false)
|
||||
|
||||
@@ -622,7 +658,7 @@ func (m *Model) updateKeybindings() {
|
||||
m.KeyMap.ClearFilter.SetEnabled(m.filterState == FilterApplied)
|
||||
m.KeyMap.CancelWhileFiltering.SetEnabled(false)
|
||||
m.KeyMap.AcceptWhileFiltering.SetEnabled(false)
|
||||
m.KeyMap.Quit.SetEnabled(true)
|
||||
m.KeyMap.Quit.SetEnabled(!m.disableQuitKeybindings)
|
||||
|
||||
if m.Help.ShowAll {
|
||||
m.KeyMap.ShowFullHelp.SetEnabled(true)
|
||||
@@ -1000,7 +1036,10 @@ func (m Model) titleView() string {
|
||||
}
|
||||
}
|
||||
|
||||
return titleBarStyle.Render(view)
|
||||
if len(view) > 0 {
|
||||
return titleBarStyle.Render(view)
|
||||
}
|
||||
return view
|
||||
}
|
||||
|
||||
func (m Model) statusView() string {
|
||||
@@ -1078,7 +1117,7 @@ func (m Model) populatedView() string {
|
||||
if m.filterState == Filtering {
|
||||
return ""
|
||||
}
|
||||
m.Styles.NoItems.Render("No items found.")
|
||||
return m.Styles.NoItems.Render("No items found.")
|
||||
}
|
||||
|
||||
if len(items) > 0 {
|
||||
@@ -1129,11 +1168,8 @@ func filterItems(m Model) tea.Cmd {
|
||||
targets = append(targets, t.FilterValue())
|
||||
}
|
||||
|
||||
var ranks fuzzy.Matches = fuzzy.Find(m.FilterInput.Value(), targets)
|
||||
sort.Stable(ranks)
|
||||
|
||||
filterMatches := []filteredItem{}
|
||||
for _, r := range ranks {
|
||||
for _, r := range m.Filter(m.FilterInput.Value(), targets) {
|
||||
filterMatches = append(filterMatches, filteredItem{
|
||||
item: items[r.Index],
|
||||
matches: r.MatchedIndexes,
|
||||
|
82
pager/pager.go
Normal file
82
pager/pager.go
Normal file
@@ -0,0 +1,82 @@
|
||||
package pager
|
||||
|
||||
import (
|
||||
"github.com/charmbracelet/bubbles/viewport"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/glamour"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
/* TODO:
|
||||
- add rendered markdown
|
||||
- show status (similar to paginator?)
|
||||
- add search functionality - similar to neovim
|
||||
*/
|
||||
|
||||
const useHighPerformanceRenderer = false
|
||||
|
||||
type Model struct {
|
||||
content string
|
||||
ready bool
|
||||
viewport viewport.Model
|
||||
errors []error
|
||||
}
|
||||
|
||||
func New(content string) Model {
|
||||
return Model{content: content}
|
||||
}
|
||||
|
||||
func (m Model) Init() tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
var (
|
||||
cmd tea.Cmd
|
||||
cmds []tea.Cmd
|
||||
)
|
||||
switch msg := msg.(type) {
|
||||
case tea.WindowSizeMsg:
|
||||
if !m.ready {
|
||||
m.viewport = viewport.New(msg.Width, msg.Height)
|
||||
m.viewport.HighPerformanceRendering = useHighPerformanceRenderer
|
||||
renderedContent, err := m.renderContent(msg.Width)
|
||||
if err != nil {
|
||||
m.errors = append(m.errors, err)
|
||||
}
|
||||
m.viewport.SetContent(renderedContent)
|
||||
m.ready = true
|
||||
} else {
|
||||
m.viewport.Width = msg.Width
|
||||
m.viewport.Height = msg.Height
|
||||
}
|
||||
|
||||
if useHighPerformanceRenderer {
|
||||
cmds = append(cmds, viewport.Sync(m.viewport))
|
||||
}
|
||||
}
|
||||
m.viewport, cmd = m.viewport.Update(msg)
|
||||
cmds = append(cmds, cmd)
|
||||
return m, tea.Batch(cmds...)
|
||||
// TODO: scrolling
|
||||
// TODO: filtering
|
||||
}
|
||||
|
||||
func (m Model) renderContent(width int) (string, error) {
|
||||
r, err := glamour.NewTermRenderer(
|
||||
glamour.WithAutoStyle(),
|
||||
glamour.WithWordWrap(width),
|
||||
)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "could not init glamour renderer")
|
||||
}
|
||||
rendered, err := r.Render(m.content)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "could not render content")
|
||||
}
|
||||
return rendered, nil
|
||||
}
|
||||
|
||||
func (m Model) View() string {
|
||||
return m.viewport.View()
|
||||
}
|
@@ -144,8 +144,8 @@ type Model struct {
|
||||
// Members for animated transitions.
|
||||
spring harmonica.Spring
|
||||
springCustomized bool
|
||||
percent float64
|
||||
targetPercent float64
|
||||
percentShown float64 // percent currently displaying
|
||||
targetPercent float64 // percent to which we're animating
|
||||
velocity float64
|
||||
|
||||
// Gradient settings
|
||||
@@ -203,12 +203,12 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
}
|
||||
|
||||
// If we've more or less reached equilibrium, stop updating.
|
||||
dist := math.Abs(m.percent - m.targetPercent)
|
||||
dist := math.Abs(m.percentShown - m.targetPercent)
|
||||
if dist < 0.001 && m.velocity < 0.01 {
|
||||
return m, nil
|
||||
}
|
||||
|
||||
m.percent, m.velocity = m.spring.Update(m.percent, m.velocity, m.targetPercent)
|
||||
m.percentShown, m.velocity = m.spring.Update(m.percentShown, m.velocity, m.targetPercent)
|
||||
return m, m.nextFrame()
|
||||
|
||||
default:
|
||||
@@ -224,7 +224,7 @@ func (m *Model) SetSpringOptions(frequency, damping float64) {
|
||||
m.spring = harmonica.NewSpring(harmonica.FPS(fps), frequency, damping)
|
||||
}
|
||||
|
||||
// Percent returns the current percentage state of the model. This is only
|
||||
// Percent returns the current visible percentage on the model. This is only
|
||||
// relevant when you're animating the progress bar.
|
||||
//
|
||||
// If you're rendering with ViewAs you won't need this.
|
||||
@@ -261,7 +261,7 @@ func (m *Model) DecrPercent(v float64) tea.Cmd {
|
||||
// View renders the an animated progress bar in its current state. To render
|
||||
// a static progress bar based on your own calculations use ViewAs instead.
|
||||
func (m Model) View() string {
|
||||
return m.ViewAs(m.percent)
|
||||
return m.ViewAs(m.percentShown)
|
||||
}
|
||||
|
||||
// ViewAs renders the progress bar with a given percentage.
|
||||
|
@@ -1,13 +1,11 @@
|
||||
package spinner
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/muesli/reflow/ansi"
|
||||
)
|
||||
|
||||
// Internal ID management for text inputs. Necessary for blink integrity when
|
||||
@@ -74,7 +72,6 @@ var (
|
||||
// Model contains the state for the spinner. Use NewModel to create new models
|
||||
// rather than using Model as a struct literal.
|
||||
type Model struct {
|
||||
|
||||
// Spinner settings to use. See type Spinner.
|
||||
Spinner Spinner
|
||||
|
||||
@@ -85,65 +82,9 @@ type Model struct {
|
||||
// https://github.com/charmbracelet/lipgloss
|
||||
Style lipgloss.Style
|
||||
|
||||
// MinimumLifetime is the minimum amount of time the spinner can run. Any
|
||||
// logic around this can be implemented in view that implements this
|
||||
// spinner. If HideFor is set MinimumLifetime will be added on top of
|
||||
// HideFor. In other words, if HideFor is 100ms and MinimumLifetime is
|
||||
// 200ms then MinimumLifetime will expire after 300ms.
|
||||
//
|
||||
// MinimumLifetime is optional.
|
||||
//
|
||||
// This is considered experimental and may not appear in future versions of
|
||||
// this library.
|
||||
MinimumLifetime time.Duration
|
||||
|
||||
// HideFor can be used to wait to show the spinner until a certain amount
|
||||
// of time has passed. This can be useful for preventing flicking when load
|
||||
// times are very fast.
|
||||
// Optional.
|
||||
//
|
||||
// This is considered experimental and may not appear in future versions of
|
||||
// this library.
|
||||
HideFor time.Duration
|
||||
|
||||
frame int
|
||||
startTime time.Time
|
||||
id int
|
||||
tag int
|
||||
}
|
||||
|
||||
// Start resets resets the spinner start time. For use with MinimumLifetime and
|
||||
// MinimumStartTime. Optional.
|
||||
//
|
||||
// This function is optional and generally considered for advanced use only.
|
||||
// Most of the time your application logic will obviate the need for this
|
||||
// method.
|
||||
//
|
||||
// This is considered experimental and may not appear in future versions of
|
||||
// this library.
|
||||
func (m *Model) Start() {
|
||||
m.startTime = time.Now()
|
||||
}
|
||||
|
||||
// Finish sets the internal timer to a completed state so long as the spinner
|
||||
// isn't flagged to be showing. If it is showing, finish has no effect. The
|
||||
// idea here is that you call Finish if your operation has completed and, if
|
||||
// the spinner isn't showing yet (by virtue of HideFor) then Visible() doesn't
|
||||
// show the spinner at all.
|
||||
//
|
||||
// This is intended to work in conjunction with MinimumLifetime and
|
||||
// MinimumStartTime, is completely optional.
|
||||
//
|
||||
// This function is optional and generally considered for advanced use only.
|
||||
// Most of the time your application logic will obviate the need for this
|
||||
// method.
|
||||
//
|
||||
// This is considered experimental and may not appear in future versions of
|
||||
// this library.
|
||||
func (m *Model) Finish() {
|
||||
if m.hidden() {
|
||||
m.startTime = time.Time{}
|
||||
}
|
||||
frame int
|
||||
id int
|
||||
tag int
|
||||
}
|
||||
|
||||
// ID returns the spinner's unique ID.
|
||||
@@ -151,48 +92,6 @@ func (m Model) ID() int {
|
||||
return m.id
|
||||
}
|
||||
|
||||
// advancedMode returns whether or not the user is making use of HideFor and
|
||||
// MinimumLifetime properties.
|
||||
func (m Model) advancedMode() bool {
|
||||
return m.HideFor > 0 && m.MinimumLifetime > 0
|
||||
}
|
||||
|
||||
// hidden returns whether or not Model.HideFor is in effect.
|
||||
func (m Model) hidden() bool {
|
||||
if m.startTime.IsZero() {
|
||||
return false
|
||||
}
|
||||
if m.HideFor == 0 {
|
||||
return false
|
||||
}
|
||||
return m.startTime.Add(m.HideFor).After(time.Now())
|
||||
}
|
||||
|
||||
// finished returns whether the minimum lifetime of this spinner has been
|
||||
// exceeded.
|
||||
func (m Model) finished() bool {
|
||||
if m.startTime.IsZero() || m.MinimumLifetime == 0 {
|
||||
return true
|
||||
}
|
||||
return m.startTime.Add(m.HideFor).Add(m.MinimumLifetime).Before(time.Now())
|
||||
}
|
||||
|
||||
// Visible returns whether or not the view should be rendered. Works in
|
||||
// conjunction with Model.HideFor and Model.MinimumLifetimeReached. You should
|
||||
// use this method directly to determine whether or not to render this view in
|
||||
// the parent view and whether to continue sending spin messaging in the
|
||||
// parent update function.
|
||||
//
|
||||
// This function is optional and generally considered for advanced use only.
|
||||
// Most of the time your application logic will obviate the need for this
|
||||
// method.
|
||||
//
|
||||
// This is considered experimental and may not appear in future versions of
|
||||
// this library.
|
||||
func (m Model) Visible() bool {
|
||||
return !m.hidden() && !m.finished()
|
||||
}
|
||||
|
||||
// New returns a model with default values.
|
||||
func New() Model {
|
||||
return Model{
|
||||
@@ -250,16 +149,7 @@ func (m Model) View() string {
|
||||
return "(error)"
|
||||
}
|
||||
|
||||
frame := m.Spinner.Frames[m.frame]
|
||||
|
||||
// If we're using the fine-grained hide/show spinner rules and those rules
|
||||
// deem that the spinner should be hidden, draw an empty space in place of
|
||||
// the spinner.
|
||||
if m.advancedMode() && !m.Visible() {
|
||||
frame = strings.Repeat(" ", ansi.PrintableRuneWidth(frame))
|
||||
}
|
||||
|
||||
return m.Style.Render(frame)
|
||||
return m.Style.Render(m.Spinner.Frames[m.frame])
|
||||
}
|
||||
|
||||
// Tick is the command used to advance the spinner one frame. Use this command
|
||||
|
@@ -203,6 +203,11 @@ func (m Model) Cursor() int {
|
||||
return m.pos
|
||||
}
|
||||
|
||||
// Blink returns whether or not to draw the cursor.
|
||||
func (m Model) Blink() bool {
|
||||
return m.blink
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (m *Model) SetCursor(pos int) {
|
||||
|
@@ -22,21 +22,27 @@ func DefaultKeyMap() KeyMap {
|
||||
return KeyMap{
|
||||
PageDown: key.NewBinding(
|
||||
key.WithKeys("pgdown", spacebar, "f"),
|
||||
key.WithHelp("f/pgdn", "page down"),
|
||||
),
|
||||
PageUp: key.NewBinding(
|
||||
key.WithKeys("pgup", "b"),
|
||||
key.WithHelp("b/pgup", "page up"),
|
||||
),
|
||||
HalfPageUp: key.NewBinding(
|
||||
key.WithKeys("u", "ctrl+u"),
|
||||
key.WithHelp("u", "½ page up"),
|
||||
),
|
||||
HalfPageDown: key.NewBinding(
|
||||
key.WithKeys("d", "ctrl+d"),
|
||||
key.WithHelp("d", "½ page down"),
|
||||
),
|
||||
Up: key.NewBinding(
|
||||
key.WithKeys("up", "k"),
|
||||
key.WithHelp("↑/k", "up"),
|
||||
),
|
||||
Down: key.NewBinding(
|
||||
key.WithKeys("down", "j"),
|
||||
key.WithHelp("↓/j", "down"),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user