7 Commits

Author SHA1 Message Date
Christian Rocha
dbd40713ce Remove extraneous methods 2020-07-29 20:04:09 -04:00
Christian Rocha
54a0d84255 Add badges 2020-07-29 19:24:09 -04:00
Christian Rocha
004511e00f Add example GIFs 2020-07-29 19:20:41 -04:00
Christian Rocha
7fa53ea961 Link directly to example code and flesh out pagination component description 2020-07-29 16:38:59 -04:00
Christian Rocha
0cc5e71a63 Flesh out README and add Charm badge 2020-07-29 16:34:01 -04:00
Christian Rocha
f11ca377f4 Add clipboard paste support to textarea 2020-07-23 11:53:58 -04:00
Christian Rocha
10022c964c Fix slice out of bounds errors that could happen when deleting text 2020-07-21 18:22:18 -04:00
5 changed files with 111 additions and 32 deletions

View File

@@ -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
View File

@@ -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
View File

@@ -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=

View File

@@ -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]))

View File

@@ -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