mirror of
https://github.com/charmbracelet/gum
synced 2024-06-15 20:15:08 +02:00
featiure: Adding timeout parameter to file, choose, input, filter, pager and spin
This commit is contained in:
parent
832c4fc917
commit
d2e9e37f89
|
@ -11,7 +11,9 @@
|
|||
package choose
|
||||
|
||||
import (
|
||||
"github.com/charmbracelet/gum/timeout"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/charmbracelet/bubbles/paginator"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
|
@ -37,6 +39,8 @@ type model struct {
|
|||
cursorStyle lipgloss.Style
|
||||
itemStyle lipgloss.Style
|
||||
selectedItemStyle lipgloss.Style
|
||||
hasTimeout bool
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
type item struct {
|
||||
|
@ -44,13 +48,21 @@ type item struct {
|
|||
selected bool
|
||||
}
|
||||
|
||||
func (m model) Init() tea.Cmd { return nil }
|
||||
func (m model) Init() tea.Cmd {
|
||||
return timeout.Init(m.timeout, nil)
|
||||
}
|
||||
|
||||
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
switch msg := msg.(type) {
|
||||
case tea.WindowSizeMsg:
|
||||
return m, nil
|
||||
|
||||
case timeout.TimeoutMsg:
|
||||
if msg.TimeoutValue <= 0 {
|
||||
m.quitting = true
|
||||
return m, tea.Quit
|
||||
}
|
||||
m.timeout = msg.TimeoutValue
|
||||
return m, timeout.Tick(msg.TimeoutValue, msg.Data)
|
||||
case tea.KeyMsg:
|
||||
start, end := m.paginator.GetSliceBounds(len(m.items))
|
||||
switch keypress := msg.String(); keypress {
|
||||
|
@ -110,6 +122,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||
m.aborted = true
|
||||
m.quitting = true
|
||||
return m, tea.Quit
|
||||
|
||||
case " ", "tab", "x":
|
||||
if m.limit == 1 {
|
||||
break // no op
|
||||
|
@ -145,6 +158,7 @@ func (m model) View() string {
|
|||
}
|
||||
|
||||
var s strings.Builder
|
||||
var timeoutStr string = ""
|
||||
|
||||
start, end := m.paginator.GetSliceBounds(len(m.items))
|
||||
for i, item := range m.items[start:end] {
|
||||
|
@ -155,7 +169,10 @@ func (m model) View() string {
|
|||
}
|
||||
|
||||
if item.selected {
|
||||
s.WriteString(m.selectedItemStyle.Render(m.selectedPrefix + item.text))
|
||||
if m.hasTimeout {
|
||||
timeoutStr = timeout.TimeoutStr(m.timeout)
|
||||
}
|
||||
s.WriteString(m.selectedItemStyle.Render(m.selectedPrefix + item.text + timeoutStr))
|
||||
} else if i == m.index%m.height {
|
||||
s.WriteString(m.cursorStyle.Render(m.cursorPrefix + item.text))
|
||||
} else {
|
||||
|
|
|
@ -106,6 +106,8 @@ func (o Options) Run() error {
|
|||
itemStyle: o.ItemStyle.ToLipgloss(),
|
||||
selectedItemStyle: o.SelectedItemStyle.ToLipgloss(),
|
||||
numSelected: currentSelected,
|
||||
hasTimeout: o.HasTimeout(),
|
||||
timeout: o.Timeout,
|
||||
}, tea.WithOutput(os.Stderr)).Run()
|
||||
|
||||
if err != nil {
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
package choose
|
||||
|
||||
import "github.com/charmbracelet/gum/style"
|
||||
import (
|
||||
"github.com/charmbracelet/gum/style"
|
||||
common "github.com/charmbracelet/gum/timeout"
|
||||
)
|
||||
|
||||
// Options is the customization options for the choose command.
|
||||
type Options struct {
|
||||
Options []string `arg:"" optional:"" help:"Options to choose from."`
|
||||
Options []string `arg:"" optional:"" help:"CmdOptions to choose from."`
|
||||
|
||||
Limit int `help:"Maximum number of options to pick" default:"1" group:"Selection"`
|
||||
NoLimit bool `help:"Pick unlimited number of options (ignores limit)" group:"Selection"`
|
||||
|
@ -13,8 +16,9 @@ type Options struct {
|
|||
CursorPrefix string `help:"Prefix to show on the cursor item (hidden if limit is 1)" default:"○ " env:"GUM_CHOOSE_CURSOR_PREFIX"`
|
||||
SelectedPrefix string `help:"Prefix to show on selected items (hidden if limit is 1)" default:"◉ " env:"GUM_CHOOSE_SELECTED_PREFIX"`
|
||||
UnselectedPrefix string `help:"Prefix to show on unselected items (hidden if limit is 1)" default:"○ " env:"GUM_CHOOSE_UNSELECTED_PREFIX"`
|
||||
Selected []string `help:"Options that should start as selected" default:"" env:"GUM_CHOOSE_SELECTED"`
|
||||
Selected []string `help:"CmdOptions that should start as selected" default:"" env:"GUM_CHOOSE_SELECTED"`
|
||||
CursorStyle style.Styles `embed:"" prefix:"cursor." set:"defaultForeground=212" envprefix:"GUM_CHOOSE_CURSOR_"`
|
||||
ItemStyle style.Styles `embed:"" prefix:"item." hidden:"" envprefix:"GUM_CHOOSE_ITEM_"`
|
||||
SelectedItemStyle style.Styles `embed:"" prefix:"selected." set:"defaultForeground=212" envprefix:"GUM_CHOOSE_SELECTED_"`
|
||||
common.CmdOptions // including timeout command options [Timeout,...]
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ func (o Options) Run() error {
|
|||
negative: o.Negative,
|
||||
confirmation: o.Default,
|
||||
timeout: o.Timeout,
|
||||
hasTimeout: o.Timeout > 0,
|
||||
hasTimeout: o.HasTimeout(),
|
||||
prompt: o.Prompt,
|
||||
selectedStyle: o.SelectedStyle.ToLipgloss(),
|
||||
unselectedStyle: o.UnselectedStyle.ToLipgloss(),
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
package confirm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/charmbracelet/gum/timeout"
|
||||
"time"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
|
@ -19,37 +19,22 @@ import (
|
|||
)
|
||||
|
||||
type model struct {
|
||||
prompt string
|
||||
affirmative string
|
||||
negative string
|
||||
quitting bool
|
||||
aborted bool
|
||||
hasTimeout bool
|
||||
timeout time.Duration
|
||||
|
||||
prompt string
|
||||
affirmative string
|
||||
negative string
|
||||
quitting bool
|
||||
aborted bool
|
||||
hasTimeout bool
|
||||
timeout time.Duration
|
||||
confirmation bool
|
||||
|
||||
// styles
|
||||
promptStyle lipgloss.Style
|
||||
selectedStyle lipgloss.Style
|
||||
unselectedStyle lipgloss.Style
|
||||
}
|
||||
|
||||
const tickInterval = time.Second
|
||||
|
||||
type tickMsg struct{}
|
||||
|
||||
func tick() tea.Cmd {
|
||||
return tea.Tick(tickInterval, func(time.Time) tea.Msg {
|
||||
return tickMsg{}
|
||||
})
|
||||
}
|
||||
|
||||
func (m model) Init() tea.Cmd {
|
||||
if m.timeout > 0 {
|
||||
return tick()
|
||||
}
|
||||
return nil
|
||||
return timeout.Init(m.timeout, m.confirmation)
|
||||
}
|
||||
|
||||
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
|
@ -81,14 +66,15 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||
m.confirmation = true
|
||||
return m, tea.Quit
|
||||
}
|
||||
case tickMsg:
|
||||
if m.timeout <= 0 {
|
||||
case timeout.TimeoutMsg:
|
||||
if msg.TimeoutValue <= 0 {
|
||||
m.quitting = true
|
||||
m.confirmation = false
|
||||
return m, tea.Quit
|
||||
}
|
||||
m.timeout -= tickInterval
|
||||
return m, tick()
|
||||
|
||||
m.timeout = msg.TimeoutValue
|
||||
return m, timeout.Tick(msg.TimeoutValue, msg.Data)
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
@ -98,18 +84,18 @@ func (m model) View() string {
|
|||
return ""
|
||||
}
|
||||
|
||||
var aff, neg, timeout string
|
||||
var aff, neg, timeoutStr string
|
||||
|
||||
if m.hasTimeout {
|
||||
timeout = fmt.Sprintf(" (%d)", max(0, int(m.timeout.Seconds())))
|
||||
timeoutStr = timeout.TimeoutStr(m.timeout)
|
||||
}
|
||||
|
||||
if m.confirmation {
|
||||
aff = m.selectedStyle.Render(m.affirmative)
|
||||
neg = m.unselectedStyle.Render(m.negative + timeout)
|
||||
neg = m.unselectedStyle.Render(m.negative + timeoutStr)
|
||||
} else {
|
||||
aff = m.unselectedStyle.Render(m.affirmative)
|
||||
neg = m.selectedStyle.Render(m.negative + timeout)
|
||||
neg = m.selectedStyle.Render(m.negative + timeoutStr)
|
||||
}
|
||||
|
||||
// If the option is intentionally empty, do not show it.
|
||||
|
@ -119,10 +105,3 @@ func (m model) View() string {
|
|||
|
||||
return lipgloss.JoinVertical(lipgloss.Center, m.promptStyle.Render(m.prompt), lipgloss.JoinHorizontal(lipgloss.Left, aff, neg))
|
||||
}
|
||||
|
||||
func max(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
|
|
@ -1,21 +1,20 @@
|
|||
package confirm
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/charmbracelet/gum/style"
|
||||
"github.com/charmbracelet/gum/timeout"
|
||||
)
|
||||
|
||||
// Options is the customization options for the confirm command.
|
||||
// Options is the customization timeout for the confirm command.
|
||||
type Options struct {
|
||||
Affirmative string `help:"The title of the affirmative action" default:"Yes"`
|
||||
Negative string `help:"The title of the negative action" default:"No"`
|
||||
Default bool `help:"Default confirmation action" default:"true"`
|
||||
Timeout time.Duration `help:"Timeout for confirmation" default:"0" env:"GUM_CONFIRM_TIMEOUT"`
|
||||
Prompt string `arg:"" help:"Prompt to display." default:"Are you sure?"`
|
||||
PromptStyle style.Styles `embed:"" prefix:"prompt." help:"The style of the prompt" set:"defaultMargin=1 0 0 0" envprefix:"GUM_CONFIRM_PROMPT_"`
|
||||
Default bool `help:"Default confirmation action" default:"true"`
|
||||
Affirmative string `help:"The title of the affirmative action" default:"Yes"`
|
||||
Negative string `help:"The title of the negative action" default:"No"`
|
||||
Prompt string `arg:"" help:"Prompt to display." default:"Are you sure?"`
|
||||
PromptStyle style.Styles `embed:"" prefix:"prompt." help:"The style of the prompt" set:"defaultMargin=1 0 0 0" envprefix:"GUM_CONFIRM_PROMPT_"`
|
||||
//nolint:staticcheck
|
||||
SelectedStyle style.Styles `embed:"" prefix:"selected." help:"The style of the selected action" set:"defaultBackground=212" set:"defaultForeground=230" set:"defaultPadding=0 3" set:"defaultMargin=1 1" envprefix:"GUM_CONFIRM_SELECTED_"`
|
||||
//nolint:staticcheck
|
||||
UnselectedStyle style.Styles `embed:"" prefix:"unselected." help:"The style of the unselected action" set:"defaultBackground=235" set:"defaultForeground=254" set:"defaultPadding=0 3" set:"defaultMargin=1 1" envprefix:"GUM_CONFIRM_UNSELECTED_"`
|
||||
UnselectedStyle style.Styles `embed:"" prefix:"unselected." help:"The style of the unselected action" set:"defaultBackground=235" set:"defaultForeground=254" set:"defaultPadding=0 3" set:"defaultMargin=1 1" envprefix:"GUM_CONFIRM_UNSELECTED_"`
|
||||
timeout.CmdOptions // including timeout command options [Timeout,...]
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package file
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/charmbracelet/gum/internal/exit"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
|
@ -48,6 +49,9 @@ func (o Options) Run() error {
|
|||
permissionStyle: o.PermissionsStyle.ToLipgloss().Inline(true),
|
||||
selectedStyle: o.SelectedStyle.ToLipgloss().Inline(true),
|
||||
fileSizeStyle: o.FileSizeStyle.ToLipgloss().Inline(true),
|
||||
timeout: o.Timeout,
|
||||
hasTimeout: o.HasTimeout(),
|
||||
aborted: false,
|
||||
}
|
||||
|
||||
tm, err := tea.NewProgram(&m, tea.WithOutput(os.Stderr)).Run()
|
||||
|
@ -56,7 +60,9 @@ func (o Options) Run() error {
|
|||
}
|
||||
|
||||
m = tm.(model)
|
||||
|
||||
if m.aborted {
|
||||
return exit.ErrAborted
|
||||
}
|
||||
if m.path == "" {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
|
19
file/file.go
19
file/file.go
|
@ -14,11 +14,13 @@ package file
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/charmbracelet/gum/timeout"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/gum/internal/stack"
|
||||
|
@ -30,6 +32,7 @@ const marginBottom = 5
|
|||
|
||||
type model struct {
|
||||
quitting bool
|
||||
aborted bool
|
||||
path string
|
||||
files []os.DirEntry
|
||||
showHidden bool
|
||||
|
@ -54,6 +57,8 @@ type model struct {
|
|||
permissionStyle lipgloss.Style
|
||||
selectedStyle lipgloss.Style
|
||||
fileSizeStyle lipgloss.Style
|
||||
timeout time.Duration
|
||||
hasTimeout bool
|
||||
}
|
||||
|
||||
type readDirMsg []os.DirEntry
|
||||
|
@ -89,13 +94,25 @@ func readDir(path string, showHidden bool) tea.Cmd {
|
|||
}
|
||||
|
||||
func (m model) Init() tea.Cmd {
|
||||
return readDir(m.path, m.showHidden)
|
||||
return tea.Batch(
|
||||
timeout.Init(m.timeout, nil),
|
||||
readDir(m.path, m.showHidden),
|
||||
)
|
||||
}
|
||||
|
||||
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
switch msg := msg.(type) {
|
||||
case readDirMsg:
|
||||
m.files = msg
|
||||
case timeout.TimeoutMsg:
|
||||
if msg.TimeoutValue <= 0 {
|
||||
m.quitting = true
|
||||
m.aborted = true
|
||||
return m, tea.Quit
|
||||
}
|
||||
m.timeout = msg.TimeoutValue
|
||||
return m, timeout.Tick(msg.TimeoutValue, msg.Data)
|
||||
|
||||
case tea.WindowSizeMsg:
|
||||
if m.autoHeight {
|
||||
m.height = msg.Height - marginBottom
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
package file
|
||||
|
||||
import "github.com/charmbracelet/gum/style"
|
||||
import (
|
||||
"github.com/charmbracelet/gum/style"
|
||||
"github.com/charmbracelet/gum/timeout"
|
||||
)
|
||||
|
||||
// Options are the options for the file command.
|
||||
type Options struct {
|
||||
|
@ -22,4 +25,5 @@ type Options struct {
|
|||
SelectedStyle style.Styles `embed:"" prefix:"selected." help:"The style to use for the selected item" set:"defaultBold=true" set:"defaultForeground=212" envprefix:"GUM_FILE_SELECTED_"`
|
||||
//nolint:staticcheck
|
||||
FileSizeStyle style.Styles `embed:"" prefix:"file-size." help:"The style to use for file sizes" set:"defaultWidth=8" set:"defaultAlign=right" set:"defaultForeground=240" envprefix:"GUM_FILE_FILE_SIZE_"`
|
||||
timeout.CmdOptions
|
||||
}
|
||||
|
|
|
@ -80,6 +80,8 @@ func (o Options) Run() error {
|
|||
limit: o.Limit,
|
||||
reverse: o.Reverse,
|
||||
fuzzy: o.Fuzzy,
|
||||
timeout: o.Timeout,
|
||||
hasTimeout: o.HasTimeout(),
|
||||
}, options...)
|
||||
|
||||
tm, err := p.Run()
|
||||
|
|
|
@ -11,7 +11,9 @@
|
|||
package filter
|
||||
|
||||
import (
|
||||
"github.com/charmbracelet/gum/timeout"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/charmbracelet/bubbles/textinput"
|
||||
"github.com/charmbracelet/bubbles/viewport"
|
||||
|
@ -43,9 +45,13 @@ type model struct {
|
|||
unselectedPrefixStyle lipgloss.Style
|
||||
reverse bool
|
||||
fuzzy bool
|
||||
timeout time.Duration
|
||||
hasTimeout bool
|
||||
}
|
||||
|
||||
func (m model) Init() tea.Cmd { return nil }
|
||||
func (m model) Init() tea.Cmd {
|
||||
return timeout.Init(m.timeout, nil)
|
||||
}
|
||||
func (m model) View() string {
|
||||
if m.quitting {
|
||||
return ""
|
||||
|
@ -130,6 +136,15 @@ func (m model) View() string {
|
|||
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
var cmd tea.Cmd
|
||||
switch msg := msg.(type) {
|
||||
case timeout.TimeoutMsg:
|
||||
if msg.TimeoutValue <= 0 {
|
||||
m.quitting = true
|
||||
m.aborted = true
|
||||
return m, tea.Quit
|
||||
}
|
||||
m.timeout = msg.TimeoutValue
|
||||
return m, timeout.Tick(msg.TimeoutValue, msg.Data)
|
||||
|
||||
case tea.WindowSizeMsg:
|
||||
if m.height == 0 || m.height > msg.Height {
|
||||
m.viewport.Height = msg.Height - lipgloss.Height(m.textinput.View())
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
package filter
|
||||
|
||||
import "github.com/charmbracelet/gum/style"
|
||||
import (
|
||||
"github.com/charmbracelet/gum/style"
|
||||
"github.com/charmbracelet/gum/timeout"
|
||||
)
|
||||
|
||||
// Options is the customization options for the filter command.
|
||||
type Options struct {
|
||||
|
@ -23,4 +26,5 @@ type Options struct {
|
|||
Value string `help:"Initial filter value" default:"" env:"GUM_FILTER_VALUE"`
|
||||
Reverse bool `help:"Display from the bottom of the screen" env:"GUM_FILTER_REVERSE"`
|
||||
Fuzzy bool `help:"Enable fuzzy matching" default:"true" env:"GUM_FILTER_FUZZY" negatable:""`
|
||||
timeout.CmdOptions
|
||||
}
|
||||
|
|
|
@ -41,6 +41,8 @@ func (o Options) Run() error {
|
|||
aborted: false,
|
||||
header: o.Header,
|
||||
headerStyle: o.HeaderStyle.ToLipgloss(),
|
||||
timeout: o.Timeout,
|
||||
hasTimeout: o.HasTimeout(),
|
||||
}, tea.WithOutput(os.Stderr))
|
||||
tm, err := p.Run()
|
||||
if err != nil {
|
||||
|
|
|
@ -10,7 +10,9 @@ package input
|
|||
import (
|
||||
"github.com/charmbracelet/bubbles/textinput"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/gum/timeout"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"time"
|
||||
)
|
||||
|
||||
type model struct {
|
||||
|
@ -19,24 +21,42 @@ type model struct {
|
|||
textinput textinput.Model
|
||||
quitting bool
|
||||
aborted bool
|
||||
timeout time.Duration
|
||||
hasTimeout bool
|
||||
}
|
||||
|
||||
func (m model) Init() tea.Cmd { return textinput.Blink }
|
||||
func (m model) Init() tea.Cmd {
|
||||
return tea.Batch(
|
||||
textinput.Blink,
|
||||
timeout.Init(m.timeout, nil),
|
||||
)
|
||||
}
|
||||
func (m model) View() string {
|
||||
if m.quitting {
|
||||
return ""
|
||||
}
|
||||
|
||||
var timeStr string = ""
|
||||
if m.hasTimeout {
|
||||
timeStr = timeout.TimeoutStr(m.timeout)
|
||||
}
|
||||
if m.header != "" {
|
||||
header := m.headerStyle.Render(m.header)
|
||||
return lipgloss.JoinVertical(lipgloss.Left, header, m.textinput.View())
|
||||
return lipgloss.JoinVertical(lipgloss.Left, header, m.textinput.View()+" "+timeStr)
|
||||
}
|
||||
|
||||
return m.textinput.View()
|
||||
return timeStr + " " + m.textinput.View()
|
||||
}
|
||||
|
||||
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
switch msg := msg.(type) {
|
||||
case timeout.TimeoutMsg:
|
||||
if msg.TimeoutValue <= 0 {
|
||||
m.quitting = true
|
||||
m.aborted = true
|
||||
return m, tea.Quit
|
||||
}
|
||||
m.timeout = msg.TimeoutValue
|
||||
return m, timeout.Tick(msg.TimeoutValue, msg.Data)
|
||||
case tea.KeyMsg:
|
||||
switch msg.String() {
|
||||
case "ctrl+c", "esc":
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
package input
|
||||
|
||||
import "github.com/charmbracelet/gum/style"
|
||||
import (
|
||||
"github.com/charmbracelet/gum/style"
|
||||
"github.com/charmbracelet/gum/timeout"
|
||||
)
|
||||
|
||||
// Options are the customization options for the input.
|
||||
type Options struct {
|
||||
|
@ -14,4 +17,5 @@ type Options struct {
|
|||
Password bool `help:"Mask input characters" default:"false"`
|
||||
Header string `help:"Header value" default:"" env:"GUM_INPUT_HEADER"`
|
||||
HeaderStyle style.Styles `embed:"" prefix:"header." set:"defaultForeground=240" envprefix:"GUM_INPUT_HEADER_"`
|
||||
timeout.CmdOptions
|
||||
}
|
||||
|
|
|
@ -35,6 +35,8 @@ func (o Options) Run() error {
|
|||
showLineNumbers: o.ShowLineNumbers,
|
||||
lineNumberStyle: o.LineNumberStyle.ToLipgloss(),
|
||||
softWrap: o.SoftWrap,
|
||||
timeout: o.Timeout,
|
||||
hasTimeout: o.HasTimeout(),
|
||||
}
|
||||
_, err := tea.NewProgram(model, tea.WithAltScreen()).Run()
|
||||
if err != nil {
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
package pager
|
||||
|
||||
import "github.com/charmbracelet/gum/style"
|
||||
import (
|
||||
"github.com/charmbracelet/gum/style"
|
||||
"github.com/charmbracelet/gum/timeout"
|
||||
)
|
||||
|
||||
// Options are the options for the pager.
|
||||
type Options struct {
|
||||
|
@ -11,4 +14,5 @@ type Options struct {
|
|||
ShowLineNumbers bool `help:"Show line numbers" default:"true"`
|
||||
LineNumberStyle style.Styles `embed:"" prefix:"line-number." help:"Style the line numbers" set:"defaultForeground=237" envprefix:"GUM_PAGER_LINE_NUMBER_"`
|
||||
SoftWrap bool `help:"Soft wrap lines" default:"false"`
|
||||
timeout.CmdOptions
|
||||
}
|
||||
|
|
|
@ -5,7 +5,9 @@ package pager
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/charmbracelet/gum/timeout"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/charmbracelet/bubbles/viewport"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
|
@ -20,14 +22,23 @@ type model struct {
|
|||
showLineNumbers bool
|
||||
lineNumberStyle lipgloss.Style
|
||||
softWrap bool
|
||||
timeout time.Duration
|
||||
hasTimeout bool
|
||||
}
|
||||
|
||||
func (m model) Init() tea.Cmd {
|
||||
return nil
|
||||
return timeout.Init(m.timeout, nil)
|
||||
}
|
||||
|
||||
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
switch msg := msg.(type) {
|
||||
case timeout.TimeoutMsg:
|
||||
if msg.TimeoutValue <= 0 {
|
||||
return m, tea.Quit
|
||||
}
|
||||
m.timeout = msg.TimeoutValue
|
||||
return m, timeout.Tick(msg.TimeoutValue, msg.Data)
|
||||
|
||||
case tea.WindowSizeMsg:
|
||||
m.viewport.Height = msg.Height - lipgloss.Height(m.helpStyle.Render("?")) - 1
|
||||
m.viewport.Width = msg.Width
|
||||
|
@ -84,5 +95,9 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||
}
|
||||
|
||||
func (m model) View() string {
|
||||
return m.viewport.View() + m.helpStyle.Render("\n ↑/↓: Navigate • q: Quit")
|
||||
var timeoutStr string
|
||||
if m.hasTimeout {
|
||||
timeoutStr = timeout.TimeoutStr(m.timeout) + " "
|
||||
}
|
||||
return m.viewport.View() + m.helpStyle.Render("\n"+timeoutStr+"↑/↓: Navigate • q: Quit")
|
||||
}
|
||||
|
|
|
@ -19,10 +19,12 @@ func (o Options) Run() error {
|
|||
s.Style = o.SpinnerStyle.ToLipgloss()
|
||||
s.Spinner = spinnerMap[o.Spinner]
|
||||
m := model{
|
||||
spinner: s,
|
||||
title: o.TitleStyle.ToLipgloss().Render(o.Title),
|
||||
command: o.Command,
|
||||
align: o.Align,
|
||||
spinner: s,
|
||||
title: o.TitleStyle.ToLipgloss().Render(o.Title),
|
||||
command: o.Command,
|
||||
align: o.Align,
|
||||
timeout: o.Timeout,
|
||||
hasTimeout: o.HasTimeout(),
|
||||
}
|
||||
p := tea.NewProgram(m, tea.WithOutput(os.Stderr))
|
||||
mm, err := p.Run()
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
package spin
|
||||
|
||||
import "github.com/charmbracelet/gum/style"
|
||||
import (
|
||||
"github.com/charmbracelet/gum/style"
|
||||
"github.com/charmbracelet/gum/timeout"
|
||||
)
|
||||
|
||||
// Options is the customization options for the spin command.
|
||||
type Options struct {
|
||||
|
@ -12,4 +15,5 @@ type Options struct {
|
|||
Title string `help:"Text to display to user while spinning" default:"Loading..." env:"GUM_SPIN_TITLE"`
|
||||
TitleStyle style.Styles `embed:"" prefix:"title." envprefix:"GUM_SPIN_TITLE_"`
|
||||
Align string `help:"Alignment of spinner with regard to the title" short:"a" type:"align" enum:"left,right" default:"left" env:"GUM_SPIN_ALIGN"`
|
||||
timeout.CmdOptions
|
||||
}
|
||||
|
|
29
spin/spin.go
29
spin/spin.go
|
@ -15,8 +15,10 @@
|
|||
package spin
|
||||
|
||||
import (
|
||||
"github.com/charmbracelet/gum/timeout"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/charmbracelet/bubbles/spinner"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
|
@ -29,9 +31,11 @@ type model struct {
|
|||
command []string
|
||||
aborted bool
|
||||
|
||||
status int
|
||||
stdout string
|
||||
stderr string
|
||||
status int
|
||||
stdout string
|
||||
stderr string
|
||||
timeout time.Duration
|
||||
hasTimeout bool
|
||||
}
|
||||
|
||||
type finishCommandMsg struct {
|
||||
|
@ -72,19 +76,32 @@ func (m model) Init() tea.Cmd {
|
|||
return tea.Batch(
|
||||
m.spinner.Tick,
|
||||
commandStart(m.command),
|
||||
timeout.Init(m.timeout, nil),
|
||||
)
|
||||
}
|
||||
func (m model) View() string {
|
||||
if m.align == "left" {
|
||||
return m.spinner.View() + " " + m.title
|
||||
var str string = ""
|
||||
if m.hasTimeout {
|
||||
str = timeout.TimeoutStr(m.timeout)
|
||||
}
|
||||
|
||||
return m.title + " " + m.spinner.View()
|
||||
if m.align == "left" {
|
||||
return m.spinner.View() + " " + m.title + str
|
||||
}
|
||||
|
||||
return str + " " + m.title + " " + m.spinner.View()
|
||||
}
|
||||
|
||||
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
var cmd tea.Cmd
|
||||
switch msg := msg.(type) {
|
||||
case timeout.TimeoutMsg:
|
||||
if msg.TimeoutValue <= 0 {
|
||||
m.status = 130
|
||||
return m, tea.Quit
|
||||
}
|
||||
m.timeout = msg.TimeoutValue
|
||||
return m, timeout.Tick(msg.TimeoutValue, msg.Data)
|
||||
case finishCommandMsg:
|
||||
m.stdout = msg.stdout
|
||||
m.stderr = msg.stderr
|
||||
|
|
63
timeout/options.go
Normal file
63
timeout/options.go
Normal file
|
@ -0,0 +1,63 @@
|
|||
package timeout
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"time"
|
||||
)
|
||||
|
||||
// CmdOptions Common Timeout Option
|
||||
type CmdOptions struct {
|
||||
Timeout time.Duration `help:"Timeout until command exits" default:"0" env:"GUM_CONFIRM_TIMEOUT"`
|
||||
}
|
||||
|
||||
// HasTimeout checks for a given timeout parameter
|
||||
func (o CmdOptions) HasTimeout() (hasTimeout bool) {
|
||||
return o.Timeout > 0
|
||||
}
|
||||
|
||||
// Tick interval
|
||||
const tickInterval = time.Second
|
||||
|
||||
// TimeoutMsg will be dispatched for every tick.
|
||||
// Containing current timeout value
|
||||
// and optional parameter to be used when handling the timeout msg
|
||||
type TimeoutMsg struct {
|
||||
TimeoutValue time.Duration
|
||||
Data interface{}
|
||||
}
|
||||
|
||||
// Init Start Timeout ticker using with timeout in seconds and optional data
|
||||
func Init(timeout time.Duration, data interface{}) tea.Cmd {
|
||||
if timeout > 0 {
|
||||
return Tick(timeout, data)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start ticker
|
||||
func Tick(timeoutValue time.Duration, data interface{}) tea.Cmd {
|
||||
return tea.Tick(tickInterval, func(time.Time) tea.Msg {
|
||||
// every tick checks if the timeout needs to be decremented
|
||||
// and send as message
|
||||
if timeoutValue >= 0 {
|
||||
timeoutValue -= tickInterval
|
||||
return TimeoutMsg{
|
||||
TimeoutValue: timeoutValue,
|
||||
Data: data,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func TimeoutStr(timeout time.Duration) string {
|
||||
return fmt.Sprintf(" (%d)", Max(0, int(timeout.Seconds())))
|
||||
}
|
||||
|
||||
func Max(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
Loading…
Reference in a new issue