forked from toolshed/abra
chore: bump deps
This commit is contained in:
40
vendor/github.com/charmbracelet/bubbletea/.golangci-soft.yml
generated
vendored
40
vendor/github.com/charmbracelet/bubbletea/.golangci-soft.yml
generated
vendored
@ -1,40 +0,0 @@
|
||||
run:
|
||||
tests: false
|
||||
issues-exit-code: 0
|
||||
|
||||
issues:
|
||||
include:
|
||||
- EXC0001
|
||||
- EXC0005
|
||||
- EXC0011
|
||||
- EXC0012
|
||||
- EXC0013
|
||||
|
||||
max-issues-per-linter: 0
|
||||
max-same-issues: 0
|
||||
|
||||
linters:
|
||||
enable:
|
||||
- exhaustive
|
||||
- goconst
|
||||
- godot
|
||||
- godox
|
||||
- mnd
|
||||
- gomoddirectives
|
||||
- goprintffuncname
|
||||
- misspell
|
||||
- nakedret
|
||||
- nestif
|
||||
- noctx
|
||||
- nolintlint
|
||||
- prealloc
|
||||
- wrapcheck
|
||||
|
||||
# disable default linters, they are already enabled in .golangci.yml
|
||||
disable:
|
||||
- errcheck
|
||||
- gosimple
|
||||
- govet
|
||||
- ineffassign
|
||||
- staticcheck
|
||||
- unused
|
40
vendor/github.com/charmbracelet/bubbletea/.golangci.yml
generated
vendored
40
vendor/github.com/charmbracelet/bubbletea/.golangci.yml
generated
vendored
@ -1,24 +1,22 @@
|
||||
version: "2"
|
||||
run:
|
||||
tests: false
|
||||
|
||||
issues:
|
||||
include:
|
||||
- EXC0001
|
||||
- EXC0005
|
||||
- EXC0011
|
||||
- EXC0012
|
||||
- EXC0013
|
||||
|
||||
max-issues-per-linter: 0
|
||||
max-same-issues: 0
|
||||
|
||||
linters:
|
||||
enable:
|
||||
- bodyclose
|
||||
- gofumpt
|
||||
- goimports
|
||||
- exhaustive
|
||||
- goconst
|
||||
- godot
|
||||
- gomoddirectives
|
||||
- goprintffuncname
|
||||
- gosec
|
||||
- misspell
|
||||
- nakedret
|
||||
- nestif
|
||||
- nilerr
|
||||
- noctx
|
||||
- nolintlint
|
||||
- prealloc
|
||||
- revive
|
||||
- rowserrcheck
|
||||
- sqlclosecheck
|
||||
@ -26,3 +24,17 @@ linters:
|
||||
- unconvert
|
||||
- unparam
|
||||
- whitespace
|
||||
- wrapcheck
|
||||
exclusions:
|
||||
generated: lax
|
||||
presets:
|
||||
- common-false-positives
|
||||
issues:
|
||||
max-issues-per-linter: 0
|
||||
max-same-issues: 0
|
||||
formatters:
|
||||
enable:
|
||||
- gofumpt
|
||||
- goimports
|
||||
exclusions:
|
||||
generated: lax
|
||||
|
8
vendor/github.com/charmbracelet/bubbletea/README.md
generated
vendored
8
vendor/github.com/charmbracelet/bubbletea/README.md
generated
vendored
@ -1,11 +1,15 @@
|
||||
# Bubble Tea
|
||||
|
||||
<p>
|
||||
<a href="https://stuff.charm.sh/bubbletea/bubbletea-4k.png"><img src="https://github.com/charmbracelet/bubbletea/assets/25087/108d4fdb-d554-4910-abed-2a5f5586a60e" width="313" alt="Bubble Tea Title Treatment"></a><br>
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://stuff.charm.sh/bubbletea/bubble-tea-v2-light.png" width="308">
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://stuff.charm.sh/bubbletea/bubble-tea-v2-dark.png" width="312">
|
||||
<img src="https://stuff.charm.sh/bubbletea/bubble-tea-v2-light.png" width="308" />
|
||||
</picture>
|
||||
<br>
|
||||
<a href="https://github.com/charmbracelet/bubbletea/releases"><img src="https://img.shields.io/github/release/charmbracelet/bubbletea.svg" alt="Latest Release"></a>
|
||||
<a href="https://pkg.go.dev/github.com/charmbracelet/bubbletea?tab=doc"><img src="https://godoc.org/github.com/charmbracelet/bubbletea?status.svg" alt="GoDoc"></a>
|
||||
<a href="https://github.com/charmbracelet/bubbletea/actions"><img src="https://github.com/charmbracelet/bubbletea/actions/workflows/build.yml/badge.svg" alt="Build Status"></a>
|
||||
<a href="https://www.phorm.ai/query?projectId=a0e324b6-b706-4546-b951-6671ea60c13f"><img src="https://stuff.charm.sh/misc/phorm-badge.svg" alt="phorm.ai"></a>
|
||||
</p>
|
||||
|
||||
The fun, functional and stateful way to build terminal apps. A Go framework
|
||||
|
14
vendor/github.com/charmbracelet/bubbletea/Taskfile.yaml
generated
vendored
Normal file
14
vendor/github.com/charmbracelet/bubbletea/Taskfile.yaml
generated
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
# https://taskfile.dev
|
||||
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
lint:
|
||||
desc: Run lint
|
||||
cmds:
|
||||
- golangci-lint run
|
||||
|
||||
test:
|
||||
desc: Run tests
|
||||
cmds:
|
||||
- go test ./... {{.CLI_ARGS}}
|
4
vendor/github.com/charmbracelet/bubbletea/exec.go
generated
vendored
4
vendor/github.com/charmbracelet/bubbletea/exec.go
generated
vendored
@ -114,6 +114,7 @@ func (p *Program) exec(c ExecCommand, fn ExecCallback) {
|
||||
|
||||
// Execute system command.
|
||||
if err := c.Run(); err != nil {
|
||||
p.renderer.resetLinesRendered()
|
||||
_ = p.RestoreTerminal() // also try to restore the terminal.
|
||||
if fn != nil {
|
||||
go p.Send(fn(err))
|
||||
@ -121,6 +122,9 @@ func (p *Program) exec(c ExecCommand, fn ExecCallback) {
|
||||
return
|
||||
}
|
||||
|
||||
// Maintain the existing output from the command
|
||||
p.renderer.resetLinesRendered()
|
||||
|
||||
// Have the program re-capture input.
|
||||
err := p.RestoreTerminal()
|
||||
if fn != nil {
|
||||
|
7
vendor/github.com/charmbracelet/bubbletea/inputreader_other.go
generated
vendored
7
vendor/github.com/charmbracelet/bubbletea/inputreader_other.go
generated
vendored
@ -4,11 +4,16 @@
|
||||
package tea
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/muesli/cancelreader"
|
||||
)
|
||||
|
||||
func newInputReader(r io.Reader, _ bool) (cancelreader.CancelReader, error) {
|
||||
return cancelreader.NewReader(r)
|
||||
cr, err := cancelreader.NewReader(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("bubbletea: error creating cancel reader: %w", err)
|
||||
}
|
||||
return cr, nil
|
||||
}
|
||||
|
2
vendor/github.com/charmbracelet/bubbletea/inputreader_windows.go
generated
vendored
2
vendor/github.com/charmbracelet/bubbletea/inputreader_windows.go
generated
vendored
@ -67,6 +67,8 @@ func newInputReader(r io.Reader, enableMouse bool) (cancelreader.CancelReader, e
|
||||
func (r *conInputReader) Cancel() bool {
|
||||
r.setCanceled()
|
||||
|
||||
// Warning: These cancel methods do not reliably work on console input
|
||||
// and should not be counted on.
|
||||
return windows.CancelIoEx(r.conin, nil) == nil || windows.CancelIo(r.conin) == nil
|
||||
}
|
||||
|
||||
|
2
vendor/github.com/charmbracelet/bubbletea/key.go
generated
vendored
2
vendor/github.com/charmbracelet/bubbletea/key.go
generated
vendored
@ -622,7 +622,7 @@ func detectOneMsg(b []byte, canHaveMoreData bool) (w int, msg Msg) {
|
||||
case '<':
|
||||
if matchIndices := mouseSGRRegex.FindSubmatchIndex(b[3:]); matchIndices != nil {
|
||||
// SGR mouse events length is the length of the match plus the length of the escape sequence
|
||||
mouseEventSGRLen := matchIndices[1] + 3 //nolint:gomnd
|
||||
mouseEventSGRLen := matchIndices[1] + 3 //nolint:mnd
|
||||
return mouseEventSGRLen, MouseMsg(parseSGRMouseEvent(b))
|
||||
}
|
||||
}
|
||||
|
5
vendor/github.com/charmbracelet/bubbletea/key_sequences.go
generated
vendored
5
vendor/github.com/charmbracelet/bubbletea/key_sequences.go
generated
vendored
@ -119,13 +119,12 @@ func detectBracketedPaste(input []byte) (hasBp bool, width int, msg Msg) {
|
||||
}
|
||||
|
||||
// detectReportFocus detects a focus report sequence.
|
||||
// nolint: gomnd
|
||||
func detectReportFocus(input []byte) (hasRF bool, width int, msg Msg) {
|
||||
switch {
|
||||
case bytes.Equal(input, []byte("\x1b[I")):
|
||||
return true, 3, FocusMsg{}
|
||||
return true, 3, FocusMsg{} //nolint:mnd
|
||||
case bytes.Equal(input, []byte("\x1b[O")):
|
||||
return true, 3, BlurMsg{}
|
||||
return true, 3, BlurMsg{} //nolint:mnd
|
||||
}
|
||||
return false, 0, nil
|
||||
}
|
||||
|
103
vendor/github.com/charmbracelet/bubbletea/key_windows.go
generated
vendored
103
vendor/github.com/charmbracelet/bubbletea/key_windows.go
generated
vendored
@ -7,6 +7,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/erikgeiser/coninput"
|
||||
localereader "github.com/mattn/go-localereader"
|
||||
@ -25,14 +26,10 @@ func readConInputs(ctx context.Context, msgsch chan<- Msg, con *conInputReader)
|
||||
var ps coninput.ButtonState // keep track of previous mouse state
|
||||
var ws coninput.WindowBufferSizeEventRecord // keep track of the last window size event
|
||||
for {
|
||||
events, err := coninput.ReadNConsoleInputs(con.conin, 16)
|
||||
events, err := peekAndReadConsInput(con)
|
||||
if err != nil {
|
||||
if con.isCanceled() {
|
||||
return cancelreader.ErrCanceled
|
||||
}
|
||||
return fmt.Errorf("read coninput events: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
for _, event := range events {
|
||||
var msgs []Msg
|
||||
switch e := event.Unwrap().(type) {
|
||||
@ -87,13 +84,57 @@ func readConInputs(ctx context.Context, msgsch chan<- Msg, con *conInputReader)
|
||||
if err != nil {
|
||||
return fmt.Errorf("coninput context error: %w", err)
|
||||
}
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Peek for new input in a tight loop and then read the input.
|
||||
// windows.CancelIo* does not work reliably so peek first and only use the data if
|
||||
// the console input is not cancelled.
|
||||
func peekAndReadConsInput(con *conInputReader) ([]coninput.InputRecord, error) {
|
||||
events, err := peekConsInput(con)
|
||||
if err != nil {
|
||||
return events, err
|
||||
}
|
||||
events, err = coninput.ReadNConsoleInputs(con.conin, intToUint32OrDie(len(events)))
|
||||
if con.isCanceled() {
|
||||
return events, cancelreader.ErrCanceled
|
||||
}
|
||||
if err != nil {
|
||||
return events, fmt.Errorf("read coninput events: %w", err)
|
||||
}
|
||||
return events, nil
|
||||
}
|
||||
|
||||
// Convert i to unit32 or panic if it cannot be converted. Check satisifes lint G115.
|
||||
func intToUint32OrDie(i int) uint32 {
|
||||
if i < 0 {
|
||||
panic("cannot convert numEvents " + fmt.Sprint(i) + " to uint32")
|
||||
}
|
||||
return uint32(i)
|
||||
}
|
||||
|
||||
// Keeps peeking until there is data or the input is cancelled.
|
||||
func peekConsInput(con *conInputReader) ([]coninput.InputRecord, error) {
|
||||
for {
|
||||
events, err := coninput.PeekNConsoleInputs(con.conin, 16)
|
||||
if con.isCanceled() {
|
||||
return events, cancelreader.ErrCanceled
|
||||
}
|
||||
if err != nil {
|
||||
return events, fmt.Errorf("peek coninput events: %w", err)
|
||||
}
|
||||
if len(events) > 0 {
|
||||
return events, nil
|
||||
}
|
||||
// Sleep for a bit to avoid busy waiting.
|
||||
time.Sleep(16 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
func mouseEventButton(p, s coninput.ButtonState) (button MouseButton, action MouseAction) {
|
||||
btn := p ^ s
|
||||
action = MouseActionPress
|
||||
@ -114,7 +155,7 @@ func mouseEventButton(p, s coninput.ButtonState) (button MouseButton, action Mou
|
||||
case s&coninput.FROM_LEFT_4TH_BUTTON_PRESSED > 0:
|
||||
button = MouseButtonForward
|
||||
}
|
||||
return
|
||||
return button, action
|
||||
}
|
||||
|
||||
switch {
|
||||
@ -147,7 +188,7 @@ func mouseEvent(p coninput.ButtonState, e coninput.MouseEventRecord) MouseMsg {
|
||||
if ev.Action == MouseActionRelease {
|
||||
ev.Type = MouseRelease
|
||||
}
|
||||
switch ev.Button {
|
||||
switch ev.Button { //nolint:exhaustive
|
||||
case MouseButtonLeft:
|
||||
ev.Type = MouseLeft
|
||||
case MouseButtonMiddle:
|
||||
@ -190,7 +231,7 @@ func keyType(e coninput.KeyEventRecord) KeyType {
|
||||
shiftPressed := e.ControlKeyState.Contains(coninput.SHIFT_PRESSED)
|
||||
ctrlPressed := e.ControlKeyState.Contains(coninput.LEFT_CTRL_PRESSED | coninput.RIGHT_CTRL_PRESSED)
|
||||
|
||||
switch code {
|
||||
switch code { //nolint:exhaustive
|
||||
case coninput.VK_RETURN:
|
||||
return KeyEnter
|
||||
case coninput.VK_BACK:
|
||||
@ -276,6 +317,46 @@ func keyType(e coninput.KeyEventRecord) KeyType {
|
||||
return KeyPgDown
|
||||
case coninput.VK_DELETE:
|
||||
return KeyDelete
|
||||
case coninput.VK_F1:
|
||||
return KeyF1
|
||||
case coninput.VK_F2:
|
||||
return KeyF2
|
||||
case coninput.VK_F3:
|
||||
return KeyF3
|
||||
case coninput.VK_F4:
|
||||
return KeyF4
|
||||
case coninput.VK_F5:
|
||||
return KeyF5
|
||||
case coninput.VK_F6:
|
||||
return KeyF6
|
||||
case coninput.VK_F7:
|
||||
return KeyF7
|
||||
case coninput.VK_F8:
|
||||
return KeyF8
|
||||
case coninput.VK_F9:
|
||||
return KeyF9
|
||||
case coninput.VK_F10:
|
||||
return KeyF10
|
||||
case coninput.VK_F11:
|
||||
return KeyF11
|
||||
case coninput.VK_F12:
|
||||
return KeyF12
|
||||
case coninput.VK_F13:
|
||||
return KeyF13
|
||||
case coninput.VK_F14:
|
||||
return KeyF14
|
||||
case coninput.VK_F15:
|
||||
return KeyF15
|
||||
case coninput.VK_F16:
|
||||
return KeyF16
|
||||
case coninput.VK_F17:
|
||||
return KeyF17
|
||||
case coninput.VK_F18:
|
||||
return KeyF18
|
||||
case coninput.VK_F19:
|
||||
return KeyF19
|
||||
case coninput.VK_F20:
|
||||
return KeyF20
|
||||
default:
|
||||
switch {
|
||||
case e.ControlKeyState.Contains(coninput.LEFT_CTRL_PRESSED) && e.ControlKeyState.Contains(coninput.RIGHT_ALT_PRESSED):
|
||||
@ -348,7 +429,7 @@ func keyType(e coninput.KeyEventRecord) KeyType {
|
||||
return KeyCtrlUnderscore
|
||||
}
|
||||
|
||||
switch code {
|
||||
switch code { //nolint:exhaustive
|
||||
case coninput.VK_OEM_4:
|
||||
return KeyCtrlOpenBracket
|
||||
case coninput.VK_OEM_6:
|
||||
|
2
vendor/github.com/charmbracelet/bubbletea/logging.go
generated
vendored
2
vendor/github.com/charmbracelet/bubbletea/logging.go
generated
vendored
@ -33,7 +33,7 @@ type LogOptionsSetter interface {
|
||||
|
||||
// LogToFileWith does allows to call LogToFile with a custom LogOptionsSetter.
|
||||
func LogToFileWith(path string, prefix string, log LogOptionsSetter) (*os.File, error) {
|
||||
f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0o600) //nolint:gomnd
|
||||
f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0o600) //nolint:mnd
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error opening file for logging: %w", err)
|
||||
}
|
||||
|
2
vendor/github.com/charmbracelet/bubbletea/mouse.go
generated
vendored
2
vendor/github.com/charmbracelet/bubbletea/mouse.go
generated
vendored
@ -172,7 +172,7 @@ const (
|
||||
func parseSGRMouseEvent(buf []byte) MouseEvent {
|
||||
str := string(buf[3:])
|
||||
matches := mouseSGRRegex.FindStringSubmatch(str)
|
||||
if len(matches) != 5 { //nolint:gomnd
|
||||
if len(matches) != 5 { //nolint:mnd
|
||||
// Unreachable, we already checked the regex in `detectOneMsg`.
|
||||
panic("invalid mouse event")
|
||||
}
|
||||
|
1
vendor/github.com/charmbracelet/bubbletea/nil_renderer.go
generated
vendored
1
vendor/github.com/charmbracelet/bubbletea/nil_renderer.go
generated
vendored
@ -26,3 +26,4 @@ func (n nilRenderer) setWindowTitle(_ string) {}
|
||||
func (n nilRenderer) reportFocus() bool { return false }
|
||||
func (n nilRenderer) enableReportFocus() {}
|
||||
func (n nilRenderer) disableReportFocus() {}
|
||||
func (n nilRenderer) resetLinesRendered() {}
|
||||
|
2
vendor/github.com/charmbracelet/bubbletea/options.go
generated
vendored
2
vendor/github.com/charmbracelet/bubbletea/options.go
generated
vendored
@ -19,7 +19,7 @@ type ProgramOption func(*Program)
|
||||
// cancelled it will exit with an error ErrProgramKilled.
|
||||
func WithContext(ctx context.Context) ProgramOption {
|
||||
return func(p *Program) {
|
||||
p.ctx = ctx
|
||||
p.externalCtx = ctx
|
||||
}
|
||||
}
|
||||
|
||||
|
3
vendor/github.com/charmbracelet/bubbletea/renderer.go
generated
vendored
3
vendor/github.com/charmbracelet/bubbletea/renderer.go
generated
vendored
@ -79,6 +79,9 @@ type renderer interface {
|
||||
|
||||
// disableReportFocus stops reporting focus events to the program.
|
||||
disableReportFocus()
|
||||
|
||||
// resetLinesRendered ensures exec output remains on screen on exit
|
||||
resetLinesRendered()
|
||||
}
|
||||
|
||||
// repaintMsg forces a full repaint.
|
||||
|
4
vendor/github.com/charmbracelet/bubbletea/standard_renderer.go
generated
vendored
4
vendor/github.com/charmbracelet/bubbletea/standard_renderer.go
generated
vendored
@ -545,6 +545,10 @@ func (r *standardRenderer) clearIgnoredLines() {
|
||||
r.ignoreLines = nil
|
||||
}
|
||||
|
||||
func (r *standardRenderer) resetLinesRendered() {
|
||||
r.linesRendered = 0
|
||||
}
|
||||
|
||||
// insertTop effectively scrolls up. It inserts lines at the top of a given
|
||||
// area designated to be a scrollable region, pushing everything else down.
|
||||
// This is roughly how ncurses does it.
|
||||
|
119
vendor/github.com/charmbracelet/bubbletea/tea.go
generated
vendored
119
vendor/github.com/charmbracelet/bubbletea/tea.go
generated
vendored
@ -27,6 +27,9 @@ import (
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
// ErrProgramPanic is returned by [Program.Run] when the program recovers from a panic.
|
||||
var ErrProgramPanic = errors.New("program experienced a panic")
|
||||
|
||||
// ErrProgramKilled is returned by [Program.Run] when the program gets killed.
|
||||
var ErrProgramKilled = errors.New("program was killed")
|
||||
|
||||
@ -147,6 +150,12 @@ type Program struct {
|
||||
|
||||
inputType inputType
|
||||
|
||||
// externalCtx is a context that was passed in via WithContext, otherwise defaulting
|
||||
// to ctx.Background() (in case it was not), the internal context is derived from it.
|
||||
externalCtx context.Context
|
||||
|
||||
// ctx is the programs's internal context for signalling internal teardown.
|
||||
// It is built and derived from the externalCtx in NewProgram().
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
|
||||
@ -243,11 +252,11 @@ func NewProgram(model Model, opts ...ProgramOption) *Program {
|
||||
|
||||
// A context can be provided with a ProgramOption, but if none was provided
|
||||
// we'll use the default background context.
|
||||
if p.ctx == nil {
|
||||
p.ctx = context.Background()
|
||||
if p.externalCtx == nil {
|
||||
p.externalCtx = context.Background()
|
||||
}
|
||||
// Initialize context and teardown channel.
|
||||
p.ctx, p.cancel = context.WithCancel(p.ctx)
|
||||
p.ctx, p.cancel = context.WithCancel(p.externalCtx)
|
||||
|
||||
// if no output was set, set it to stdout
|
||||
if p.output == nil {
|
||||
@ -346,7 +355,11 @@ func (p *Program) handleCommands(cmds chan Cmd) chan struct{} {
|
||||
go func() {
|
||||
// Recover from panics.
|
||||
if !p.startupOptions.has(withoutCatchPanics) {
|
||||
defer p.recoverFromPanic()
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
p.recoverFromGoPanic(r)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
msg := cmd() // this can be long.
|
||||
@ -422,7 +435,7 @@ func (p *Program) eventLoop(model Model, cmds chan Cmd) (Model, error) {
|
||||
// work.
|
||||
if runtime.GOOS == "windows" && !p.mouseMode {
|
||||
p.mouseMode = true
|
||||
p.initCancelReader(true) //nolint:errcheck
|
||||
p.initCancelReader(true) //nolint:errcheck,gosec
|
||||
}
|
||||
|
||||
case disableMouseMsg:
|
||||
@ -433,7 +446,7 @@ func (p *Program) eventLoop(model Model, cmds chan Cmd) (Model, error) {
|
||||
// mouse events.
|
||||
if runtime.GOOS == "windows" && p.mouseMode {
|
||||
p.mouseMode = false
|
||||
p.initCancelReader(true) //nolint:errcheck
|
||||
p.initCancelReader(true) //nolint:errcheck,gosec
|
||||
}
|
||||
|
||||
case showCursorMsg:
|
||||
@ -460,7 +473,11 @@ func (p *Program) eventLoop(model Model, cmds chan Cmd) (Model, error) {
|
||||
|
||||
case BatchMsg:
|
||||
for _, cmd := range msg {
|
||||
cmds <- cmd
|
||||
select {
|
||||
case <-p.ctx.Done():
|
||||
return model, nil
|
||||
case cmds <- cmd:
|
||||
}
|
||||
}
|
||||
continue
|
||||
|
||||
@ -483,7 +500,7 @@ func (p *Program) eventLoop(model Model, cmds chan Cmd) (Model, error) {
|
||||
})
|
||||
}
|
||||
|
||||
//nolint:errcheck
|
||||
//nolint:errcheck,gosec
|
||||
g.Wait() // wait for all commands from batch msg to finish
|
||||
continue
|
||||
}
|
||||
@ -506,7 +523,13 @@ func (p *Program) eventLoop(model Model, cmds chan Cmd) (Model, error) {
|
||||
|
||||
var cmd Cmd
|
||||
model, cmd = model.Update(msg) // run update
|
||||
cmds <- cmd // process command (if any)
|
||||
|
||||
select {
|
||||
case <-p.ctx.Done():
|
||||
return model, nil
|
||||
case cmds <- cmd: // process command (if any)
|
||||
}
|
||||
|
||||
p.renderer.write(model.View()) // send view to renderer
|
||||
}
|
||||
}
|
||||
@ -515,11 +538,15 @@ func (p *Program) eventLoop(model Model, cmds chan Cmd) (Model, error) {
|
||||
// Run initializes the program and runs its event loops, blocking until it gets
|
||||
// terminated by either [Program.Quit], [Program.Kill], or its signal handler.
|
||||
// Returns the final model.
|
||||
func (p *Program) Run() (Model, error) {
|
||||
func (p *Program) Run() (returnModel Model, returnErr error) {
|
||||
p.handlers = channelHandlers{}
|
||||
cmds := make(chan Cmd)
|
||||
p.errs = make(chan error)
|
||||
p.finished = make(chan struct{}, 1)
|
||||
p.errs = make(chan error, 1)
|
||||
|
||||
p.finished = make(chan struct{})
|
||||
defer func() {
|
||||
close(p.finished)
|
||||
}()
|
||||
|
||||
defer p.cancel()
|
||||
|
||||
@ -568,7 +595,12 @@ func (p *Program) Run() (Model, error) {
|
||||
|
||||
// Recover from panics.
|
||||
if !p.startupOptions.has(withoutCatchPanics) {
|
||||
defer p.recoverFromPanic()
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
returnErr = fmt.Errorf("%w: %w", ErrProgramKilled, ErrProgramPanic)
|
||||
p.recoverFromPanic(r)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// If no renderer is set use the standard one.
|
||||
@ -645,11 +677,27 @@ func (p *Program) Run() (Model, error) {
|
||||
|
||||
// Run event loop, handle updates and draw.
|
||||
model, err := p.eventLoop(model, cmds)
|
||||
killed := p.ctx.Err() != nil || err != nil
|
||||
if killed && err == nil {
|
||||
err = fmt.Errorf("%w: %s", ErrProgramKilled, p.ctx.Err())
|
||||
|
||||
if err == nil && len(p.errs) > 0 {
|
||||
err = <-p.errs // Drain a leftover error in case eventLoop crashed
|
||||
}
|
||||
if err == nil {
|
||||
|
||||
killed := p.externalCtx.Err() != nil || p.ctx.Err() != nil || err != nil
|
||||
if killed {
|
||||
if err == nil && p.externalCtx.Err() != nil {
|
||||
// Return also as context error the cancellation of an external context.
|
||||
// This is the context the user knows about and should be able to act on.
|
||||
err = fmt.Errorf("%w: %w", ErrProgramKilled, p.externalCtx.Err())
|
||||
} else if err == nil && p.ctx.Err() != nil {
|
||||
// Return only that the program was killed (not the internal mechanism).
|
||||
// The user does not know or need to care about the internal program context.
|
||||
err = ErrProgramKilled
|
||||
} else {
|
||||
// Return that the program was killed and also the error that caused it.
|
||||
err = fmt.Errorf("%w: %w", ErrProgramKilled, err)
|
||||
}
|
||||
} else {
|
||||
// Graceful shutdown of the program (not killed):
|
||||
// Ensure we rendered the final state of the model.
|
||||
p.renderer.write(model.View())
|
||||
}
|
||||
@ -704,11 +752,11 @@ func (p *Program) Quit() {
|
||||
p.Send(Quit())
|
||||
}
|
||||
|
||||
// Kill stops the program immediately and restores the former terminal state.
|
||||
// Kill signals the program to stop immediately and restore the former terminal state.
|
||||
// The final render that you would normally see when quitting will be skipped.
|
||||
// [program.Run] returns a [ErrProgramKilled] error.
|
||||
func (p *Program) Kill() {
|
||||
p.shutdown(true)
|
||||
p.cancel()
|
||||
}
|
||||
|
||||
// Wait waits/blocks until the underlying Program finished shutting down.
|
||||
@ -717,7 +765,11 @@ func (p *Program) Wait() {
|
||||
}
|
||||
|
||||
// shutdown performs operations to free up resources and restore the terminal
|
||||
// to its original state.
|
||||
// to its original state. It is called once at the end of the program's lifetime.
|
||||
//
|
||||
// This method should not be called to signal the program to be killed/shutdown.
|
||||
// Doing so can lead to race conditions with the eventual call at the program's end.
|
||||
// As alternatives, the [Quit] or [Kill] convenience methods should be used instead.
|
||||
func (p *Program) shutdown(kill bool) {
|
||||
p.cancel()
|
||||
|
||||
@ -744,19 +796,30 @@ func (p *Program) shutdown(kill bool) {
|
||||
}
|
||||
|
||||
_ = p.restoreTerminalState()
|
||||
if !kill {
|
||||
p.finished <- struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
// recoverFromPanic recovers from a panic, prints the stack trace, and restores
|
||||
// the terminal to a usable state.
|
||||
func (p *Program) recoverFromPanic() {
|
||||
if r := recover(); r != nil {
|
||||
p.shutdown(true)
|
||||
fmt.Printf("Caught panic:\n\n%s\n\nRestoring terminal...\n\n", r)
|
||||
debug.PrintStack()
|
||||
func (p *Program) recoverFromPanic(r interface{}) {
|
||||
select {
|
||||
case p.errs <- ErrProgramPanic:
|
||||
default:
|
||||
}
|
||||
p.shutdown(true) // Ok to call here, p.Run() cannot do it anymore.
|
||||
fmt.Printf("Caught panic:\n\n%s\n\nRestoring terminal...\n\n", r)
|
||||
debug.PrintStack()
|
||||
}
|
||||
|
||||
// recoverFromGoPanic recovers from a goroutine panic, prints a stack trace and
|
||||
// signals for the program to be killed and terminal restored to a usable state.
|
||||
func (p *Program) recoverFromGoPanic(r interface{}) {
|
||||
select {
|
||||
case p.errs <- ErrProgramPanic:
|
||||
default:
|
||||
}
|
||||
p.cancel()
|
||||
fmt.Printf("Caught goroutine panic:\n\n%s\n\nRestoring terminal...\n\n", r)
|
||||
debug.PrintStack()
|
||||
}
|
||||
|
||||
// ReleaseTerminal restores the original terminal state and cancels the input
|
||||
|
4
vendor/github.com/charmbracelet/bubbletea/tty.go
generated
vendored
4
vendor/github.com/charmbracelet/bubbletea/tty.go
generated
vendored
@ -52,7 +52,7 @@ func (p *Program) restoreTerminalState() error {
|
||||
p.renderer.exitAltScreen()
|
||||
|
||||
// give the terminal a moment to catch up
|
||||
time.Sleep(time.Millisecond * 10) //nolint:gomnd
|
||||
time.Sleep(time.Millisecond * 10) //nolint:mnd
|
||||
}
|
||||
}
|
||||
|
||||
@ -109,7 +109,7 @@ func (p *Program) readLoop() {
|
||||
func (p *Program) waitForReadLoop() {
|
||||
select {
|
||||
case <-p.readLoopDone:
|
||||
case <-time.After(500 * time.Millisecond): //nolint:gomnd
|
||||
case <-time.After(500 * time.Millisecond): //nolint:mnd
|
||||
// The read loop hangs, which means the input
|
||||
// cancelReader's cancel function has returned true even
|
||||
// though it was not able to cancel the read.
|
||||
|
8
vendor/github.com/charmbracelet/bubbletea/tty_windows.go
generated
vendored
8
vendor/github.com/charmbracelet/bubbletea/tty_windows.go
generated
vendored
@ -19,7 +19,7 @@ func (p *Program) initInput() (err error) {
|
||||
p.ttyInput = f
|
||||
p.previousTtyInputState, err = term.MakeRaw(p.ttyInput.Fd())
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("error making raw: %w", err)
|
||||
}
|
||||
|
||||
// Enable VT input
|
||||
@ -38,7 +38,7 @@ func (p *Program) initInput() (err error) {
|
||||
p.ttyOutput = f
|
||||
p.previousOutputState, err = term.GetState(f.Fd())
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("error getting state: %w", err)
|
||||
}
|
||||
|
||||
var mode uint32
|
||||
@ -51,14 +51,14 @@ func (p *Program) initInput() (err error) {
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
// Open the Windows equivalent of a TTY.
|
||||
func openInputTTY() (*os.File, error) {
|
||||
f, err := os.OpenFile("CONIN$", os.O_RDWR, 0o644)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("error opening file: %w", err)
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
Reference in New Issue
Block a user