mirror of
https://github.com/Maks1mS/bubbles.git
synced 2025-10-17 16:15:58 +03:00
Compare commits
7 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
dbd40713ce | ||
|
54a0d84255 | ||
|
004511e00f | ||
|
7fa53ea961 | ||
|
0cc5e71a63 | ||
|
f11ca377f4 | ||
|
10022c964c |
73
README.md
73
README.md
@@ -1,34 +1,79 @@
|
||||
# Bubbles
|
||||
Bubbles
|
||||
=======
|
||||
|
||||
Some components for [Bubble Tea](https://github.com/charmbraclet/bubbletea):
|
||||
<p>
|
||||
<a href="https://pkg.go.dev/github.com/charmbracelet/bubbles?tab=doc"><img src="https://godoc.org/github.com/golang/gddo?status.svg" alt="GoDoc"></a>
|
||||
<a href="https://github.com/charmbracelet/bubbles/actions"><img src="https://github.com/charmbracelet/glow/workflows/build/badge.svg" alt="Build Status"></a>
|
||||
</p>
|
||||
|
||||
Some components for [Bubble Tea](https://github.com/charmbraclet/bubbletea) applications.
|
||||
|
||||
These components are used in production in [Glow][glow] and [Charm][charm].
|
||||
|
||||
[glow]: https://github.com/charmbraclet/glow
|
||||
[charm]: https://github.com/charmbraclet/charm
|
||||
|
||||
* Spinner
|
||||
* Text Input
|
||||
* Paginator
|
||||
* Viewport
|
||||
|
||||
## Spinner
|
||||
|
||||
A spinner, useful for indicating that some kind of invisible operation is
|
||||
happening. There are a couple default ones, but you can also pass your own
|
||||
”frames.”
|
||||
<img src="https://stuff.charm.sh/bubbles-examples/spinner.gif" width="400" alt="Spinner Example">
|
||||
|
||||
A spinner, useful for indicating that some kind an operation is happening.
|
||||
There are a couple default ones, but you can also pass your own ”frames.”
|
||||
|
||||
* [Example code](https://github.com/charmbracelet/tea/tree/master/examples/spinner/main.go)
|
||||
|
||||
|
||||
## Text Input
|
||||
|
||||
A text input field, akin to an `<input type="text">` in HTML.
|
||||
<img src="https://stuff.charm.sh/bubbles-examples/textinput.gif" width="400" alt="Text Input Example">
|
||||
|
||||
A text input field, akin to an `<input type="text">` in HTML. Supports unicode,
|
||||
pasting, in-place scrolling when the value exceeds the width of the element and
|
||||
the common, and many customization options.
|
||||
|
||||
* [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)
|
||||
|
||||
|
||||
## Paginator
|
||||
|
||||
<img src="https://stuff.charm.sh/bubbles-examples/pagination.gif" width="200" alt="Paginator Example">
|
||||
|
||||
A component for handling pagination logic and optionally drawing pagination UI.
|
||||
Supports "dot-style" pagination (similar to what you might see on iOS) and
|
||||
numeric page numbering, but you could also just use this component for the
|
||||
logic and visualize pagination however you like.
|
||||
|
||||
This component is used in [Glow][glow] to browse documents and [Charm][charm] to
|
||||
browse SSH keys.
|
||||
|
||||
|
||||
## Viewport
|
||||
|
||||
A viewport for vertically scrolling content which optionally includes standard
|
||||
<img src="https://stuff.charm.sh/bubbles-examples/viewport.gif" width="600" alt="Viewport Example">
|
||||
|
||||
A viewport for vertically scrolling content. Optionally includes standard
|
||||
pager keybindings and mouse wheel support. A high performance mode is available
|
||||
for applications which make use of the alterate screen buffer. This is
|
||||
generally only necessary when dealing with content with a very large amount of
|
||||
ANSI escape sequences.
|
||||
for applications which make use of the alterate screen buffer.
|
||||
|
||||
* [Example code](https://github.com/charmbracelet/tea/tree/master/examples/pager/main.go)
|
||||
|
||||
This compoent is well complimented with [Reflow][reflow] for ANSI-aware
|
||||
indenting and text wrapping.
|
||||
|
||||
[reflow]: https://github.com/muesli/reflow
|
||||
|
||||
|
||||
## License
|
||||
|
||||
[MIT](https://github.com/charmbracelet/teaparty/raw/master/LICENSE)
|
||||
|
||||
|
||||
***
|
||||
|
||||
A [Charm](https://charm.sh) project.
|
||||
|
||||
<img alt="the Charm logo" src="https://stuff.charm.sh/charm-badge.jpg" width="400">
|
||||
|
||||
Charm热爱开源!
|
||||
|
1
go.mod
1
go.mod
@@ -3,6 +3,7 @@ module github.com/charmbracelet/bubbles
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/atotto/clipboard v0.1.2 // indirect
|
||||
github.com/charmbracelet/bubbletea v0.9.1-0.20200713153904-2f53eeb54b90
|
||||
github.com/mattn/go-runewidth v0.0.9
|
||||
github.com/muesli/termenv v0.5.3-0.20200625163851-04b5c30e4c04
|
||||
|
2
go.sum
2
go.sum
@@ -1,3 +1,5 @@
|
||||
github.com/atotto/clipboard v0.1.2 h1:YZCtFu5Ie8qX2VmVTBnrqLSiU9XOWwqNRmdT3gIQzbY=
|
||||
github.com/atotto/clipboard v0.1.2/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
||||
github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f h1:5CjVwnuUcp5adK4gmY6i72gpVFVnZDP2h5TmPScB6u4=
|
||||
github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f/go.mod h1:nOFQdrUlIlx6M6ODdSpBj1NVA+VgLC6kmw60mkw34H4=
|
||||
github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac=
|
||||
|
@@ -5,6 +5,7 @@ import (
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"github.com/atotto/clipboard"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
rw "github.com/mattn/go-runewidth"
|
||||
"github.com/muesli/termenv"
|
||||
@@ -126,6 +127,47 @@ func (m *Model) Reset() {
|
||||
m.blink = false
|
||||
}
|
||||
|
||||
// Paste pastes the contents of the clipboard into the text area (if supported).
|
||||
func (m *Model) Paste() {
|
||||
pasteString, err := clipboard.ReadAll()
|
||||
if err != nil {
|
||||
m.Err = err
|
||||
}
|
||||
paste := []rune(pasteString)
|
||||
|
||||
availSpace := m.CharLimit - len(m.value)
|
||||
|
||||
// If the char limit's been reached cancel
|
||||
if m.CharLimit > 0 && availSpace <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// If there's not enough space to paste the whole thing cut the pasted
|
||||
// runes down so they'll fit
|
||||
if availSpace < len(paste) {
|
||||
paste = paste[:len(paste)-availSpace]
|
||||
}
|
||||
|
||||
// Stuff before and after the cursor
|
||||
head := m.value[:m.pos]
|
||||
tailSrc := m.value[m.pos:]
|
||||
tail := make([]rune, len(tailSrc))
|
||||
copy(tail, tailSrc)
|
||||
|
||||
// Insert pasted runes
|
||||
for _, r := range paste {
|
||||
head = append(head, r)
|
||||
availSpace--
|
||||
m.pos++
|
||||
if m.CharLimit > 0 && availSpace <= 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Put it all back together
|
||||
m.value = append(head, tail...)
|
||||
}
|
||||
|
||||
// If a max width is defined, perform some logic to treat the visible area
|
||||
// as a horizontally scrolling viewport.
|
||||
func (m *Model) handleOverflow() {
|
||||
@@ -282,8 +324,10 @@ func Update(msg tea.Msg, m Model) (Model, tea.Cmd) {
|
||||
fallthrough
|
||||
case tea.KeyDelete:
|
||||
if len(m.value) > 0 {
|
||||
m.value = append(m.value[:m.pos-1], m.value[m.pos:]...)
|
||||
m.pos--
|
||||
m.value = append(m.value[:max(0, m.pos-1)], m.value[m.pos:]...)
|
||||
if m.pos > 0 {
|
||||
m.pos--
|
||||
}
|
||||
}
|
||||
case tea.KeyLeft:
|
||||
if msg.Alt { // alt+left arrow, back one word
|
||||
@@ -320,6 +364,8 @@ func Update(msg tea.Msg, m Model) (Model, tea.Cmd) {
|
||||
m.value = m.value[m.pos:]
|
||||
m.pos = 0
|
||||
m.offset = 0
|
||||
case tea.KeyCtrlV: // ^V paste
|
||||
m.Paste()
|
||||
case tea.KeyRune: // input a regular character
|
||||
|
||||
if msg.Alt {
|
||||
@@ -366,7 +412,7 @@ func View(model tea.Model) string {
|
||||
}
|
||||
|
||||
value := m.value[m.offset:m.offsetRight]
|
||||
pos := m.pos - m.offset
|
||||
pos := max(0, m.pos-m.offset)
|
||||
|
||||
v := m.colorText(string(value[:pos]))
|
||||
|
||||
|
@@ -37,21 +37,6 @@ type Model struct {
|
||||
lines []string
|
||||
}
|
||||
|
||||
// TODO: do we really need this?
|
||||
func NewModel(width, height int) Model {
|
||||
return Model{
|
||||
Width: width,
|
||||
Height: height,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: do we really need this?
|
||||
func (m Model) SetSize(yPos, width, height int) {
|
||||
m.YPosition = yPos
|
||||
m.Width = width
|
||||
m.Height = height
|
||||
}
|
||||
|
||||
// AtTop returns whether or not the viewport is in the very top position.
|
||||
func (m Model) AtTop() bool {
|
||||
return m.YOffset <= 0
|
||||
|
Reference in New Issue
Block a user