Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(v2) Bubble Tea API #1118

Draft
wants to merge 185 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
185 commits
Select commit Hold shift + click to select a range
13ffcad
refactor: reimplement the event loop with a sequence parser
aymanbagabas Aug 12, 2024
448eb82
fix(ci): skip CI for examples/tutorials running go1.18
aymanbagabas Aug 12, 2024
c629efb
feat: enable focus reporting
caarlos0 Aug 13, 2024
cb37f88
fix: rename events to messages
aymanbagabas Aug 13, 2024
7a49b33
refactor: define renderer execute to write queries to the terminal
aymanbagabas Aug 13, 2024
ea54605
feat: introduce ExecuteSequence to send the terminal arbitrary sequence
aymanbagabas Aug 13, 2024
5cf8c29
feat(example): add example to query the terminal
aymanbagabas Aug 13, 2024
76fc19d
feat: detect key input grapheme runes
aymanbagabas Aug 13, 2024
7782a60
Merge remote-tracking branch 'origin/input' into input-focus-blur
caarlos0 Aug 13, 2024
88a5cd2
fix: parse invalid utf8 sequences
aymanbagabas Aug 13, 2024
7567352
fix: mouse sequence enable/disable order
aymanbagabas Aug 13, 2024
3981b80
refactor: remove tea.ExecuteSequence
aymanbagabas Aug 13, 2024
0dd6210
fix: initialize the terminal before the renderer
aymanbagabas Aug 13, 2024
c2c195c
fix: special case modified f3 key and cursor pos report
aymanbagabas Aug 13, 2024
4247aac
refactor: flatten csi sequence parsing
aymanbagabas Aug 13, 2024
3075646
refactor: bracketed-paste active state
aymanbagabas Aug 13, 2024
a4cd456
Merge remote-tracking branch 'origin/input' into input-focus-blur
caarlos0 Aug 14, 2024
3e87bf8
feat: add Enable/Disable focus report commands
caarlos0 Aug 14, 2024
f2bdd36
fix: use KeyRunes to indicate text input
aymanbagabas Aug 14, 2024
eb2eee4
refactor: rename KeySym to KeyType and make KeyRunes the default
aymanbagabas Aug 15, 2024
0126caf
feat: add kitty keyboard options and settings
aymanbagabas Aug 13, 2024
bdb3237
fix: kitty: request protocol flags and rename flag methods
aymanbagabas Aug 14, 2024
03df14c
feat: kitty: support associated text keyboard enhancement
aymanbagabas Aug 15, 2024
4b7fef7
chore: add kitty/fixterms test cases
aymanbagabas Aug 15, 2024
a37d731
chore: add kitty keyboard command test case
aymanbagabas Aug 15, 2024
ec5b362
refactor: rename query kitty keyboard command
aymanbagabas Aug 15, 2024
4d2072b
fix: kitty: request protocol flags and rename flag methods
aymanbagabas Aug 14, 2024
e16559d
feat(examples): add print-key example
aymanbagabas Aug 14, 2024
d36b259
chore: add modifyOtherKeys test cases
aymanbagabas Aug 15, 2024
44d4973
feat: query and set terminal background, foreground, and cursor colors
aymanbagabas Aug 14, 2024
cdc2503
feat(example): add example to set-terminal-color's
aymanbagabas Aug 14, 2024
e6ad78c
chore: add bg/fg/cur terminal color command test cases
aymanbagabas Aug 15, 2024
8f6ac01
chore: color: add min/max todo note
aymanbagabas Aug 15, 2024
04f843d
feat: support setting and querying the terminal clipboard using OSC52
aymanbagabas Aug 15, 2024
26bc2cb
feat: query the terminal version string and primary device attrs
aymanbagabas Aug 15, 2024
e0865cf
refactor: simplify key modifier matching
aymanbagabas Aug 16, 2024
3f5fb9a
fix(lint): reorder key struct to fix fieldalignment
aymanbagabas Aug 19, 2024
c630d5e
fix: simplify kitty keyboard msg flags
aymanbagabas Aug 19, 2024
0c1a6a4
refactor: reimplement the event loop with a sequence parser (#1080)
aymanbagabas Aug 19, 2024
8e406ff
feat: add kitty keyboard options and settings (#1083)
aymanbagabas Aug 19, 2024
828ff70
feat: support xterm modifyOtherKeys keyboard protocol (#1084)
aymanbagabas Aug 19, 2024
19f916b
Merge branch 'master' into input-background-foreground
aymanbagabas Aug 19, 2024
c657bba
feat: query the terminal version string and primary device attrs (#1088)
aymanbagabas Aug 19, 2024
485e52e
feat: query and set terminal background, foreground, and cursor color…
aymanbagabas Aug 19, 2024
e25b4b5
Merge branch 'master' into input-clipboard
aymanbagabas Aug 19, 2024
7cebfb8
Merge branch 'master' into input-focus-blur
aymanbagabas Aug 19, 2024
6062461
feat: support setting and querying the terminal clipboard using OSC52…
aymanbagabas Aug 19, 2024
2158d73
Merge branch 'master' into input-focus-blur
aymanbagabas Aug 19, 2024
05e8ecb
feat: enable focus reporting (#1081)
aymanbagabas Aug 19, 2024
70853cb
feat: drop erikgeiser/coninput in favor of x/windows
aymanbagabas Aug 15, 2024
da83499
fix: windows: correctly parse upper/lower key
aymanbagabas Aug 16, 2024
ed7dc3d
feat: add support for win32-input-mode
aymanbagabas Aug 15, 2024
0aa699c
chore: bump github.com/charmbracelet/x/windows
aymanbagabas Aug 19, 2024
ffe0133
refactor: change kitty keyboard flag name for clarity
aymanbagabas Aug 19, 2024
8480d19
chore: bump charmbracelet/x
aymanbagabas Aug 19, 2024
3ef72f2
Fix Windows API & Add support for win32-input-mode (#1087)
aymanbagabas Aug 19, 2024
c0f9975
fix: lint errors
aymanbagabas Aug 19, 2024
2df9b54
chore: mark deprecated
aymanbagabas Aug 19, 2024
2635e2e
chore(examples): go mod tidy
aymanbagabas Aug 19, 2024
210358d
docs: add v2 todo code reminders
aymanbagabas Aug 20, 2024
370d248
refactor: remove kitty protocol flags
aymanbagabas Aug 20, 2024
39ea34c
refactor: unexport kitty keyboard settings and options
aymanbagabas Aug 20, 2024
ad68fc1
feat: query modify other keys
aymanbagabas Aug 20, 2024
5f7700e
refactor: unexport modify other keys
aymanbagabas Aug 20, 2024
c47c2b9
refactor: unexport win32 input mode
aymanbagabas Aug 20, 2024
e206b36
fix: use explicit names for kitty keyboard option
aymanbagabas Aug 20, 2024
6f7f9e6
feat: expose the renderer interface
aymanbagabas Aug 19, 2024
fa03b59
feat: add custom renderer option
aymanbagabas Aug 19, 2024
c385aba
feat: add renderer Resize and InsertAbove
aymanbagabas Aug 19, 2024
ae0c273
refactor: check the initial size during Run
aymanbagabas Aug 19, 2024
53d72b4
feat: add the ability to change the renderer output
aymanbagabas Aug 20, 2024
544a715
fix: simplify instantiating a new standard renderer
aymanbagabas Aug 20, 2024
0825f61
feat: export nil renderer
aymanbagabas Aug 20, 2024
fe54df7
docs(examples): add help view to table example
meowgorithm Aug 20, 2024
a6b9970
chore(deps): go mod tidy
meowgorithm Aug 20, 2024
1f88b9e
refactor: simplify renderer interface
aymanbagabas Aug 20, 2024
9660d7d
fix: keep track of terminal capabilities and gracefully turn them off
aymanbagabas Aug 20, 2024
c9f2a56
fix: screen test
aymanbagabas Aug 21, 2024
05f8399
feat: dry setting and retrieving terminal modes
aymanbagabas Aug 21, 2024
f31a5f3
refactor: we don't care about renderer render errors
aymanbagabas Aug 21, 2024
356c649
fix(examples): update testdata
aymanbagabas Aug 21, 2024
5693515
chore(docs): edit some comments in the renderer interface
meowgorithm Aug 21, 2024
1f5a28f
feat: expose the renderer interface (#1094)
aymanbagabas Aug 21, 2024
8635fb3
fix: hide cursor if needed after restore
aymanbagabas Aug 21, 2024
fe88dd5
fix: show the cursor on exit
aymanbagabas Aug 21, 2024
f757c10
chore(deps): bump github.com/charmbracelet/x/ansi from 0.2.1 to 0.2.2…
dependabot[bot] Aug 22, 2024
55fb63f
chore(deps): go mod tidy
meowgorithm Aug 22, 2024
5df8f28
fix(examples): keys shouldn't be routed to viewport in chat example
meowgorithm Aug 22, 2024
fa69e03
fix: lint issues (#1109)
aymanbagabas Aug 22, 2024
508be2e
fix: force query the terminal bg before running any programs
aymanbagabas Aug 22, 2024
833cc78
chore: go mod tidy
aymanbagabas Aug 22, 2024
b2e983a
feat: add mode 2027 grapheme clustering stubs (#1105)
aymanbagabas Aug 23, 2024
ad68c42
feat!: make Init return the model (#1112)
aymanbagabas Aug 23, 2024
537c80b
Merge branch 'main' into v2-exp
aymanbagabas Aug 30, 2024
6e060ca
refactor!: remove backwards compatibility
aymanbagabas Aug 22, 2024
7b87642
refactor!: use key/mouse msg interfaces
aymanbagabas Aug 22, 2024
16f706a
refactor: expose key codes and define key/mouse interfaces
aymanbagabas Aug 27, 2024
e8903bb
feat!: use KeyExtended to signify more than one rune
aymanbagabas Aug 27, 2024
2f14548
docs: add godoc examples
aymanbagabas Aug 27, 2024
0c8967d
fix(kitty): only use printables
aymanbagabas Aug 28, 2024
2f6637b
fix: windows driver build
aymanbagabas Aug 29, 2024
3faa9b3
feat: use kitty report alternate keys with enhanced keyboard
aymanbagabas Aug 28, 2024
033071b
feat: export kitty/xterm/win32 input commands and messages
aymanbagabas Aug 28, 2024
9d02251
fix(key): use the key text value when keycode is multirune
aymanbagabas Aug 28, 2024
8a75439
(v2) Use KeyMsg/MouseMsg interfaces (#1111)
aymanbagabas Aug 30, 2024
85c5adc
(v2) Export different input mode commands and messages (#1119)
aymanbagabas Aug 30, 2024
0985406
Merge branch 'main' into v2-exp
aymanbagabas Sep 11, 2024
ee120d5
chore: update examples to use v2-exp
aymanbagabas Sep 12, 2024
c86cd34
Merge branch 'main' into v2-exp
aymanbagabas Sep 12, 2024
88141ed
chore: update tutorials to use v2-exp
aymanbagabas Sep 12, 2024
73773e8
feat: recognize nested sequence msgs
aymanbagabas Sep 12, 2024
0fe006d
feat: use the new renderer interface
aymanbagabas Sep 12, 2024
614aa93
fix: renderer: don't return a Cmd on update
aymanbagabas Sep 12, 2024
516b7cd
fix: renderer: nilRenderer doesn't need to be exported
aymanbagabas Sep 12, 2024
4e118e9
fix: lint issues
aymanbagabas Sep 12, 2024
4967f6b
fix: unexport standardRenderer
aymanbagabas Sep 12, 2024
9636413
fix: use safeWriter to guard writing to output
aymanbagabas Sep 12, 2024
2ad1918
chore: go mod tidy
aymanbagabas Sep 12, 2024
695fbf3
fix: only shutdown the program once
aymanbagabas Sep 12, 2024
a6725ba
fix: implement RequestCapability and unexport and remove internal mes…
aymanbagabas Sep 12, 2024
eacd930
feat(examples): add request-capability example
aymanbagabas Sep 12, 2024
ceaed4d
fix: restore terminal colors
aymanbagabas Sep 12, 2024
4fa1f06
fix: terminal colors tests
aymanbagabas Sep 12, 2024
d1827e4
fix: ignore invalid XTGETTCAP responses
aymanbagabas Sep 12, 2024
1bd66e6
fix: ignore nil terminal colors
aymanbagabas Sep 12, 2024
45222df
fix: only reset terminal colors if they're changed
aymanbagabas Sep 13, 2024
7df684a
fix: handle Kitty keyboard protocol extensions to legacy sequences
aymanbagabas Sep 16, 2024
d77a935
chore(deps): bump github.com/charmbracelet/x/ansi from 0.2.3 to 0.3.1
aymanbagabas Sep 16, 2024
3f75169
chore(deps): bump charmbracelet/x/ansi from 0.3.1 to 0.3.2
aymanbagabas Sep 16, 2024
3650670
fix: reset terminal colors on exit using osc 110/111/112
aymanbagabas Sep 16, 2024
62e46fe
fix: unexport kitty, modifyOtherKeys, and windowsInputMode options
aymanbagabas Sep 16, 2024
a26ecc5
fix: implement String() method for MouseButton type
aymanbagabas Sep 16, 2024
8b51296
feat: support terminal color profiles
aymanbagabas Sep 16, 2024
01a9fc8
feat: add colorprofile example
aymanbagabas Sep 16, 2024
f31aa40
fix: revert go.mod version change
aymanbagabas Sep 17, 2024
14cb6b5
feat!: v2: update module path to github.com/charmbracelet/bubbletea/v2
aymanbagabas Sep 18, 2024
1678d85
feat: use bubbles/v2 and bubbletea/v2 in examples and tutorials
aymanbagabas Sep 18, 2024
7cb34be
(v2) feat: combine keyboard enhancements into a nicer API (#1152)
aymanbagabas Sep 18, 2024
3274e41
docs(tutorials): upgrade tuts to v2 (#1155)
meowgorithm Sep 18, 2024
d727a8c
fix: enabling alt screen buffer should also set the cursor visibility
aymanbagabas Sep 19, 2024
a7d49d0
chore: remove deprecated
aymanbagabas Sep 19, 2024
265996c
docs: update godoc examples to return model in Init
aymanbagabas Sep 19, 2024
d815192
chore: use bubbles/v2@v2-deprecated
aymanbagabas Sep 19, 2024
09d1678
fix: don't close output writer on stopRenderer
aymanbagabas Sep 20, 2024
a039baa
fix: properly store kitty keyboard flags
aymanbagabas Sep 20, 2024
8085d33
feat: add kitty keyboard uniform key layout support
aymanbagabas Sep 20, 2024
84b68c5
feat: examples: update the print-key example to show the key text
aymanbagabas Sep 20, 2024
c53a7c9
fix: kitty: use the correct case for key text
aymanbagabas Oct 7, 2024
e812e94
fix: examples: update examples to use tea.KeyPressMsg and tea.MouseCl…
aymanbagabas Oct 11, 2024
ee16c6e
chore: drop Lip Gloss v1 lockup workaround for Lip Gloss v2 prep
meowgorithm Oct 11, 2024
792918f
feat: trace i/o to a file
aymanbagabas Oct 11, 2024
a08b8d4
fix: kitty: support faulty c0 mappings (#1161)
aymanbagabas Oct 15, 2024
e8f7e58
Fix textinputs example (#1176)
dogwaterdev1 Oct 15, 2024
8a91939
fix: kitty: correctly handle printable characters with no modifiers
aymanbagabas Oct 15, 2024
fb09ffd
chore: go mod tidy and update teatest/v2
aymanbagabas Oct 16, 2024
ecec8dc
refactor: windows: clean up, tidy and improve windows key handling
aymanbagabas Sep 20, 2024
b36937a
refactor: windows: clean up and remove redundent code
aymanbagabas Oct 14, 2024
c35932f
fix(windows): test cases on windows
aymanbagabas Oct 15, 2024
943846c
fix(windows): properly return focus events
aymanbagabas Oct 15, 2024
af4f115
chore(windows): add driver test cases
aymanbagabas Oct 15, 2024
85d716d
fix: lint issues
aymanbagabas Oct 15, 2024
bde5d1b
fix: use lf instead of crlf line endings
aymanbagabas Oct 16, 2024
5d08fe9
refactor: windows: clean up, tidy and improve windows key handling (#…
aymanbagabas Oct 16, 2024
d26972f
Merge branch 'main' into v2-exp
aymanbagabas Oct 16, 2024
2c29ed8
fix: linting issues
aymanbagabas Oct 16, 2024
ad61ed6
feat: support changing the cursor style
aymanbagabas Oct 16, 2024
d95e275
feat(examples): add cursor style example
aymanbagabas Oct 16, 2024
9f6f5cd
chore: merge branch 'main' into v2-exp
aymanbagabas Oct 21, 2024
43b11c3
fix: make grapheme clustering opt-in
aymanbagabas Oct 22, 2024
e858f4a
chore: merge branch 'main' into v2-exp
aymanbagabas Oct 23, 2024
b3904a3
fix: windows: ensure the input buffer is empty
aymanbagabas Oct 21, 2024
f182f16
chore: merge branch 'main' into v2-exp
aymanbagabas Oct 23, 2024
baecd89
chore: merge branch 'main' into v2-exp
aymanbagabas Oct 24, 2024
a31857d
chore: merge branch 'main' into v2-exp
aymanbagabas Oct 24, 2024
3bd4650
chore: use verbs in background, foreground, and window request cmd na…
meowgorithm Oct 28, 2024
6498624
chore: merge branch 'v2-exp' into v2-colorprofile
aymanbagabas Oct 28, 2024
026625d
feat: use colorprofile writer in standard renderer
aymanbagabas Oct 28, 2024
5c21725
chore: go mod tidy
aymanbagabas Oct 28, 2024
952b69d
fix: use ANSI256 color profile in screen_test.go
aymanbagabas Oct 28, 2024
8e99020
fix: skip test that fails due to color profile
aymanbagabas Oct 28, 2024
94b7873
feat: support terminal color profiles (#1151)
aymanbagabas Oct 29, 2024
95aa2ca
chore: merge branch 'main' into v2-exp
aymanbagabas Oct 29, 2024
83a84e4
refactor: move input tracing to input parser and simplify tracing
aymanbagabas Oct 29, 2024
d7790f3
chore: bump ansi to v0.4.2
aymanbagabas Oct 29, 2024
13a18ac
fix: refactor: move input tracing to input parser and simplify tracing
aymanbagabas Oct 30, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 16 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ import (
"fmt"
"os"

tea "github.com/charmbracelet/bubbletea"
tea "github.com/charmbracelet/bubbletea/v2"
)
```

Expand All @@ -89,34 +89,27 @@ type model struct {
}
```

### Initialization
## Initialization

Next, we’ll define our application’s initial state. In this case, we’re defining
a function to return our initial model, however, we could just as easily define
the initial model as a variable elsewhere, too.
Next, we’ll define our application’s initial state in the `Init` method. `Init`
can return a `Cmd` that could perform some initial I/O. For now, we don't need
to do any I/O, so for the command, we'll just return `nil`, which translates to
"no command."

```go
func initialModel() model {
return model{
func (m model) Init() (tea.Model, tea.Cmd) {
m = {
// Our to-do list is a grocery list
choices: []string{"Buy carrots", "Buy celery", "Buy kohlrabi"},

// A map which indicates which choices are selected. We're using
// the map like a mathematical set. The keys refer to the indexes
// the map like a mathematical set. The keys refer to the indexes
// of the `choices` slice, above.
selected: make(map[int]struct{}),
}
}
```

Next, we define the `Init` method. `Init` can return a `Cmd` that could perform
some initial I/O. For now, we don't need to do any I/O, so for the command,
we'll just return `nil`, which translates to "no command."

```go
func (m model) Init() tea.Cmd {
// Just return `nil`, which means "no I/O right now, please."
return nil
return m, nil
}
```

Expand All @@ -137,15 +130,15 @@ tick, or a response from a server.
We usually figure out which type of `Msg` we received with a type switch, but
you could also use a type assertion.

For now, we'll just deal with `tea.KeyMsg` messages, which are automatically
sent to the update function when keys are pressed.
For now, we'll just deal with `tea.KeyPressMsg` messages, which are
automatically sent to the update function when keys are pressed.

```go
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {

// Is it a key press?
case tea.KeyMsg:
case tea.KeyPressMsg:

// Cool, what was the actual key pressed?
switch msg.String() {
Expand All @@ -166,9 +159,9 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.cursor++
}

// The "enter" key and the spacebar (a literal space) toggle
// the selected state for the item that the cursor is pointing at.
case "enter", " ":
// The "enter" key and the space bar (a literal space) toggle the
// selected state for the item that the cursor is pointing at.
case "enter", "space":
_, ok := m.selected[m.cursor]
if ok {
delete(m.selected, m.cursor)
Expand Down
2 changes: 1 addition & 1 deletion inputreader_other.go → cancelreader_other.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ import (
"github.com/muesli/cancelreader"
)

func newInputReader(r io.Reader) (cancelreader.CancelReader, error) {
func newCancelreader(r io.Reader) (cancelreader.CancelReader, error) {
return cancelreader.NewReader(r)
}
225 changes: 225 additions & 0 deletions cancelreader_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
//go:build windows
// +build windows

package tea

import (
"fmt"
"io"
"os"
"sync"
"time"

xwindows "github.com/charmbracelet/x/windows"
"github.com/muesli/cancelreader"
"golang.org/x/sys/windows"
)

type conInputReader struct {
cancelMixin

conin windows.Handle
cancelEvent windows.Handle

originalMode uint32

// blockingReadSignal is used to signal that a blocking read is in progress.
blockingReadSignal chan struct{}
}

var _ cancelreader.CancelReader = &conInputReader{}

func newCancelreader(r io.Reader) (cancelreader.CancelReader, error) {
fallback := func(io.Reader) (cancelreader.CancelReader, error) {
return cancelreader.NewReader(r)
}

var dummy uint32
if f, ok := r.(cancelreader.File); !ok || f.Fd() != os.Stdin.Fd() ||
// If data was piped to the standard input, it does not emit events
// anymore. We can detect this if the console mode cannot be set anymore,
// in this case, we fallback to the default cancelreader implementation.
windows.GetConsoleMode(windows.Handle(f.Fd()), &dummy) != nil {
return fallback(r)
}

conin, err := windows.GetStdHandle(windows.STD_INPUT_HANDLE)
if err != nil {
return fallback(r)
}

// Discard any pending input events.
if err := xwindows.FlushConsoleInputBuffer(conin); err != nil {
return fallback(r)
}

originalMode, err := prepareConsole(conin,
windows.ENABLE_MOUSE_INPUT,
windows.ENABLE_WINDOW_INPUT,
windows.ENABLE_EXTENDED_FLAGS,
)
if err != nil {
return nil, fmt.Errorf("failed to prepare console input: %w", err)
}

cancelEvent, err := windows.CreateEvent(nil, 0, 0, nil)
if err != nil {
return nil, fmt.Errorf("create stop event: %w", err)
}

return &conInputReader{
conin: conin,
cancelEvent: cancelEvent,
originalMode: originalMode,
blockingReadSignal: make(chan struct{}, 1),
}, nil
}

// Cancel implements cancelreader.CancelReader.
func (r *conInputReader) Cancel() bool {
r.setCanceled()

select {
case r.blockingReadSignal <- struct{}{}:
err := windows.SetEvent(r.cancelEvent)
if err != nil {
return false
}
<-r.blockingReadSignal
case <-time.After(100 * time.Millisecond):
// Read() hangs in a GetOverlappedResult which is likely due to
// WaitForMultipleObjects returning without input being available
// so we cannot cancel this ongoing read.
return false
}

return true
}

// Close implements cancelreader.CancelReader.
func (r *conInputReader) Close() error {
err := windows.CloseHandle(r.cancelEvent)
if err != nil {
return fmt.Errorf("closing cancel event handle: %w", err)
}

if r.originalMode != 0 {
err := windows.SetConsoleMode(r.conin, r.originalMode)
if err != nil {
return fmt.Errorf("reset console mode: %w", err)
}
}

return nil
}

// Read implements cancelreader.CancelReader.
func (r *conInputReader) Read(data []byte) (n int, err error) {
if r.isCanceled() {
return 0, cancelreader.ErrCanceled
}

err = waitForInput(r.conin, r.cancelEvent)
if err != nil {
return 0, err
}

if r.isCanceled() {
return 0, cancelreader.ErrCanceled
}

r.blockingReadSignal <- struct{}{}
n, err = overlappedReader(r.conin).Read(data)
<-r.blockingReadSignal

return
}

func prepareConsole(input windows.Handle, modes ...uint32) (originalMode uint32, err error) {
err = windows.GetConsoleMode(input, &originalMode)
if err != nil {
return 0, fmt.Errorf("get console mode: %w", err)
}

var newMode uint32
for _, mode := range modes {
newMode |= mode
}

err = windows.SetConsoleMode(input, newMode)
if err != nil {
return 0, fmt.Errorf("set console mode: %w", err)
}

return originalMode, nil
}

func waitForInput(conin, cancel windows.Handle) error {
event, err := windows.WaitForMultipleObjects([]windows.Handle{conin, cancel}, false, windows.INFINITE)
switch {
case windows.WAIT_OBJECT_0 <= event && event < windows.WAIT_OBJECT_0+2:
if event == windows.WAIT_OBJECT_0+1 {
return cancelreader.ErrCanceled
}

if event == windows.WAIT_OBJECT_0 {
return nil
}

return fmt.Errorf("unexpected wait object is ready: %d", event-windows.WAIT_OBJECT_0)
case windows.WAIT_ABANDONED <= event && event < windows.WAIT_ABANDONED+2:
return fmt.Errorf("abandoned")
case event == uint32(windows.WAIT_TIMEOUT):
return fmt.Errorf("timeout")
case event == windows.WAIT_FAILED:
return fmt.Errorf("failed")
default:
return fmt.Errorf("unexpected error: %w", err)
}
}

// cancelMixin represents a goroutine-safe cancelation status.
type cancelMixin struct {
unsafeCanceled bool
lock sync.Mutex
}

func (c *cancelMixin) setCanceled() {
c.lock.Lock()
defer c.lock.Unlock()

c.unsafeCanceled = true
}

func (c *cancelMixin) isCanceled() bool {
c.lock.Lock()
defer c.lock.Unlock()

return c.unsafeCanceled
}

type overlappedReader windows.Handle

// Read performs an overlapping read fom a windows.Handle.
func (r overlappedReader) Read(data []byte) (int, error) {
hevent, err := windows.CreateEvent(nil, 0, 0, nil)
if err != nil {
return 0, fmt.Errorf("create event: %w", err)
}

overlapped := windows.Overlapped{HEvent: hevent}

var n uint32

err = windows.ReadFile(windows.Handle(r), data, &n, &overlapped)
if err != nil && err != windows.ERROR_IO_PENDING {
return int(n), err
}

err = windows.GetOverlappedResult(windows.Handle(r), &overlapped, &n, true)
if err != nil {
return int(n), nil
}

return int(n), nil
}
69 changes: 69 additions & 0 deletions clipboard.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package tea

// ClipboardMsg is a clipboard read message event. This message is emitted when
// a terminal receives an OSC52 clipboard read message event.
type ClipboardMsg string

// String returns the string representation of the clipboard message.
func (e ClipboardMsg) String() string {
return string(e)
}

// PrimaryClipboardMsg is a primary clipboard read message event. This message
// is emitted when a terminal receives an OSC52 primary clipboard read message
// event. Primary clipboard selection is a feature present in X11 and Wayland
// only.
type PrimaryClipboardMsg string

// String returns the string representation of the primary clipboard message.
func (e PrimaryClipboardMsg) String() string {
return string(e)
}

// setClipboardMsg is an internal message used to set the system clipboard
// using OSC52.
type setClipboardMsg string

// SetClipboard produces a command that sets the system clipboard using OSC52.
// Note that OSC52 is not supported in all terminals.
func SetClipboard(s string) Cmd {
return func() Msg {
return setClipboardMsg(s)
}
}

// readClipboardMsg is an internal message used to read the system clipboard
// using OSC52.
type readClipboardMsg struct{}

// ReadClipboard produces a command that reads the system clipboard using OSC52.
// Note that OSC52 is not supported in all terminals.
func ReadClipboard() Msg {
return readClipboardMsg{}
}

// setPrimaryClipboardMsg is an internal message used to set the primary
// clipboard using OSC52.
type setPrimaryClipboardMsg string

// SetPrimaryClipboard produces a command that sets the primary clipboard using
// OSC52. Primary clipboard selection is a feature present in X11 and Wayland
// only.
// Note that OSC52 is not supported in all terminals.
func SetPrimaryClipboard(s string) Cmd {
return func() Msg {
return setPrimaryClipboardMsg(s)
}
}

// readPrimaryClipboardMsg is an internal message used to read the primary
// clipboard using OSC52.
type readPrimaryClipboardMsg struct{}

// ReadPrimaryClipboard produces a command that reads the primary clipboard
// using OSC52. Primary clipboard selection is a feature present in X11 and
// Wayland only.
// Note that OSC52 is not supported in all terminals.
func ReadPrimaryClipboard() Msg {
return readPrimaryClipboardMsg{}
}
Loading
Loading