Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
aymanbagabas committed Sep 14, 2023
1 parent d55cfec commit 806d3d4
Show file tree
Hide file tree
Showing 12 changed files with 1,054 additions and 5 deletions.
Empty file.
6 changes: 6 additions & 0 deletions examples/altscreen-toggle/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"fmt"
"log"
"os"

tea "github.com/charmbracelet/bubbletea"
Expand Down Expand Up @@ -66,6 +67,11 @@ func (m model) View() string {
}

func main() {
f, err := tea.LogToFile("log.txt", "")
if err != nil {
log.Fatal(err)
}
defer f.Close()
if _, err := tea.NewProgram(model{}).Run(); err != nil {
fmt.Println("Error running program:", err)
os.Exit(1)
Expand Down
7 changes: 6 additions & 1 deletion examples/fullscreen/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@ type model int
type tickMsg time.Time

func main() {
p := tea.NewProgram(model(5), tea.WithAltScreen())
f, err := tea.LogToFile("log.txt", "")
if err != nil {
log.Fatal(err)
}
defer f.Close()
p := tea.NewProgram(model(50), tea.WithAltScreen(), tea.WithMouseAllMotion())
if _, err := p.Run(); err != nil {
log.Fatal(err)
}
Expand Down
14 changes: 13 additions & 1 deletion examples/glamour/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"fmt"
"log"
"os"

"github.com/charmbracelet/bubbles/viewport"
Expand Down Expand Up @@ -91,6 +92,12 @@ func (e example) Init() tea.Cmd {

func (e example) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.WindowSizeMsg:
e.viewport.Width = msg.Width
e.viewport.Height = msg.Height - 2
var cmd tea.Cmd
e.viewport, cmd = e.viewport.Update(msg)
return e, cmd
case tea.KeyMsg:
switch msg.String() {
case "q", "ctrl+c", "esc":
Expand All @@ -114,13 +121,18 @@ func (e example) helpView() string {
}

func main() {
f, err := tea.LogToFile("log.txt", "")
if err != nil {
log.Fatal(err)
}
defer f.Close()
model, err := newExample()
if err != nil {
fmt.Println("Could not initialize Bubble Tea model:", err)
os.Exit(1)
}

if _, err := tea.NewProgram(model).Run(); err != nil {
if _, err := tea.NewProgram(model, tea.WithAltScreen()).Run(); err != nil {
fmt.Println("Bummer, there's been an error:", err)
os.Exit(1)
}
Expand Down
1 change: 1 addition & 0 deletions examples/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ require (
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
github.com/dlclark/regexp2 v1.4.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/gorilla/css v1.0.0 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect
Expand Down
3 changes: 3 additions & 0 deletions examples/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ 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/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
github.com/fogleman/ease v0.0.0-20170301025033-8da417bf1776 h1:VRIbnDWRmAh5yBdz+J6yFMF5vso1It6vn+WmM/5l7MA=
github.com/fogleman/ease v0.0.0-20170301025033-8da417bf1776/go.mod h1:9wvnDu3YOfxzWM9Cst40msBF1C2UdQgDv962oTxSuMs=
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
Expand Down Expand Up @@ -95,6 +97,7 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
Expand Down
837 changes: 837 additions & 0 deletions examples/log.txt

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ go 1.17

require (
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f
github.com/mattn/go-isatty v0.0.18
github.com/mattn/go-localereader v0.0.1
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b
github.com/muesli/cancelreader v0.2.2
github.com/muesli/reflow v0.3.0
github.com/muesli/termenv v0.15.2
golang.org/x/sync v0.1.0
golang.org/x/sys v0.7.0
golang.org/x/term v0.6.0
)

Expand All @@ -19,6 +21,5 @@ require (
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
golang.org/x/sys v0.7.0 // indirect
golang.org/x/text v0.3.8 // indirect
)
3 changes: 3 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiE
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY=
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
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.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
Expand Down Expand Up @@ -37,6 +39,7 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
Expand Down
12 changes: 10 additions & 2 deletions tea.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@ import (
"syscall"

"github.com/containerd/console"
"github.com/erikgeiser/coninput"
isatty "github.com/mattn/go-isatty"
"github.com/muesli/cancelreader"
"github.com/muesli/termenv"
"golang.org/x/sync/errgroup"
"golang.org/x/sys/windows"

Check failure on line 29 in tea.go

View workflow job for this annotation

GitHub Actions / lint-soft

could not import golang.org/x/sys/windows (-: build constraints exclude all Go files in /home/runner/go/pkg/mod/golang.org/x/sys@v0.7.0/windows) (typecheck)

Check failure on line 29 in tea.go

View workflow job for this annotation

GitHub Actions / lint

could not import golang.org/x/sys/windows (-: build constraints exclude all Go files in /home/runner/go/pkg/mod/golang.org/x/sys@v0.7.0/windows) (typecheck)
)

// ErrProgramKilled is returned by [Program.Run] when the program got killed.
Expand Down Expand Up @@ -164,6 +166,10 @@ type Program struct {
// below.
windowsStdin *os.File //nolint:golint,structcheck,unused

conInput windows.Handle
cancelEvent windows.Handle
inputEvents []coninput.InputRecord

filter func(Model, Msg) Msg

// fps is the frames per second we should set on the renderer, if
Expand All @@ -185,6 +191,7 @@ func NewProgram(model Model, opts ...ProgramOption) *Program {
p := &Program{
initialModel: model,
msgs: make(chan Msg),
inputEvents: make([]coninput.InputRecord, 4),
}

// Apply all options to the program.
Expand All @@ -208,8 +215,6 @@ func NewProgram(model Model, opts ...ProgramOption) *Program {
termenv.WithColorCache(true)(p.output)
}

p.restoreOutput, _ = termenv.EnableVirtualTerminalProcessing(p.output)

return p
}

Expand Down Expand Up @@ -524,6 +529,9 @@ func (p *Program) Run() (Model, error) {
// Process commands.
handlers.add(p.handleCommands(cmds))

// Handle Windows console input.
handlers.add(p.handleConInput())

Check failure on line 533 in tea.go

View workflow job for this annotation

GitHub Actions / lint-soft

p.handleConInput undefined (type *Program has no field or method handleConInput) (typecheck)

Check failure on line 533 in tea.go

View workflow job for this annotation

GitHub Actions / lint

p.handleConInput undefined (type *Program has no field or method handleConInput) (typecheck)

// Run event loop, handle updates and draw.
model, err := p.eventLoop(model, cmds)
killed := p.ctx.Err() != nil
Expand Down
164 changes: 164 additions & 0 deletions tea_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
package tea

import (
"fmt"
"log"

"github.com/erikgeiser/coninput"
"golang.org/x/sys/windows"
)

func enableWindowsConInput(p *Program) (func() error, error) {
con, err := windows.GetStdHandle(windows.STD_INPUT_HANDLE)
if err != nil {
return nil, fmt.Errorf("get stdin handle: %w", err)
}

p.conInput = con

var originalConsoleMode uint32

err = windows.GetConsoleMode(con, &originalConsoleMode)
if err != nil {
return nil, fmt.Errorf("get console mode: %w", err)
}

log.Println("Input mode:", coninput.DescribeInputMode(originalConsoleMode))

newConsoleMode := coninput.AddInputModes(
0,
windows.ENABLE_WINDOW_INPUT,
windows.ENABLE_MOUSE_INPUT,
// windows.ENABLE_PROCESSED_INPUT,
windows.ENABLE_EXTENDED_FLAGS,
// windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING,
)

log.Println("Setting mode to:", coninput.DescribeInputMode(newConsoleMode))

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

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

p.cancelEvent = cancelEvent

return func() error {
log.Println("Resetting input mode to:", coninput.DescribeInputMode(originalConsoleMode))

if err := windows.CloseHandle(p.cancelEvent); err != nil {
return fmt.Errorf("close stop event: %w", err)
}

resetErr := windows.SetConsoleMode(con, originalConsoleMode)
if err == nil && resetErr != nil {
return fmt.Errorf("reset console mode: %w", resetErr)
}

return nil
}, nil
}

func (p *Program) handleConInput() chan struct{} {
ch := make(chan struct{})

go func() {
defer func() {
windows.SetEvent(p.cancelEvent)
close(ch)
}()
for {
select {
case <-p.ctx.Done():
return
default:
if p.ctx.Err() != nil {
return
}

if err := waitForInput(p.conInput, p.cancelEvent); err != nil {
log.Printf("wait for input: %s", err)
return
}

n, err := coninput.ReadConsoleInput(p.conInput, p.inputEvents)
if err != nil {
log.Printf("read input events: %s", err)
return
}

log.Printf("Read %d events:\n", n)
for _, event := range p.inputEvents[:n] {
log.Println(" ", event)
switch e := event.Unwrap().(type) {
case coninput.WindowBufferSizeEventRecord:
p.msgs <- WindowSizeMsg{
Width: int(e.Size.X),
Height: int(e.Size.Y),
}
}
}

}
}
}()

return ch
}

var errCanceled = fmt.Errorf("read cancelled")

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 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", error(err))
}
}

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
}
9 changes: 9 additions & 0 deletions tty_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
package tea

import (
"log"
"os"

"github.com/containerd/console"
"github.com/muesli/termenv"
)

func (p *Program) initInput() error {
Expand All @@ -24,6 +26,13 @@ func (p *Program) initInput() error {
p.console = c
}

var err error
p.restoreOutput, err = enableWindowsConInput(p)
if err != nil {
log.Printf("Error enabling Windows console input: %s", err)
p.restoreOutput, _ = termenv.EnableVirtualTerminalProcessing(p.output)
}

return nil
}

Expand Down

0 comments on commit 806d3d4

Please sign in to comment.