mirror of
https://github.com/charmbracelet/gum
synced 2024-06-08 16:52:17 +02:00
Merge branch 'main' into feature/218/Adding_timeout_to_most_commands
This commit is contained in:
commit
2743d58f2a
|
@ -294,6 +294,8 @@ gum pager < README.md
|
|||
Display a spinner while running a script or command. The spinner will
|
||||
automatically stop after the given command exits.
|
||||
|
||||
To view or pipe the command's output, use the `--show-output` flag.
|
||||
|
||||
```bash
|
||||
gum spin --spinner dot --title "Buying Bubble Gum..." -- sleep 5
|
||||
```
|
||||
|
|
|
@ -84,6 +84,7 @@ func (o Options) Run() error {
|
|||
matchStyle: o.MatchStyle.ToLipgloss(),
|
||||
headerStyle: o.HeaderStyle.ToLipgloss(),
|
||||
textStyle: o.TextStyle.ToLipgloss(),
|
||||
cursorTextStyle: o.CursorTextStyle.ToLipgloss(),
|
||||
height: o.Height,
|
||||
selected: make(map[string]struct{}),
|
||||
limit: o.Limit,
|
||||
|
|
|
@ -42,6 +42,7 @@ type model struct {
|
|||
headerStyle lipgloss.Style
|
||||
matchStyle lipgloss.Style
|
||||
textStyle lipgloss.Style
|
||||
cursorTextStyle lipgloss.Style
|
||||
indicatorStyle lipgloss.Style
|
||||
selectedPrefixStyle lipgloss.Style
|
||||
unselectedPrefixStyle lipgloss.Style
|
||||
|
@ -61,6 +62,7 @@ func (m model) View() string {
|
|||
}
|
||||
|
||||
var s strings.Builder
|
||||
var lineTextStyle lipgloss.Style
|
||||
|
||||
// For reverse layout, if the number of matches is less than the viewport
|
||||
// height, we need to offset the matches so that the first match is at the
|
||||
|
@ -81,10 +83,14 @@ func (m model) View() string {
|
|||
|
||||
// If this is the current selected index, we add a small indicator to
|
||||
// represent it. Otherwise, simply pad the string.
|
||||
// The line's text style is set depending on whether or not the cursor
|
||||
// points to this line.
|
||||
if i == m.cursor {
|
||||
s.WriteString(m.indicatorStyle.Render(m.indicator))
|
||||
lineTextStyle = m.cursorTextStyle
|
||||
} else {
|
||||
s.WriteString(strings.Repeat(" ", lipgloss.Width(m.indicator)))
|
||||
lineTextStyle = m.textStyle
|
||||
}
|
||||
|
||||
// If there are multiple selections mark them, otherwise leave an empty space
|
||||
|
@ -106,7 +112,7 @@ func (m model) View() string {
|
|||
// index. If so, color the character to indicate a match.
|
||||
if mi < len(match.MatchedIndexes) && ci == match.MatchedIndexes[mi] {
|
||||
// Flush text buffer.
|
||||
s.WriteString(m.textStyle.Render(buf.String()))
|
||||
s.WriteString(lineTextStyle.Render(buf.String()))
|
||||
buf.Reset()
|
||||
|
||||
s.WriteString(m.matchStyle.Render(string(c)))
|
||||
|
@ -119,7 +125,7 @@ func (m model) View() string {
|
|||
}
|
||||
}
|
||||
// Flush text buffer.
|
||||
s.WriteString(m.textStyle.Render(buf.String()))
|
||||
s.WriteString(lineTextStyle.Render(buf.String()))
|
||||
|
||||
// We have finished displaying the match with all of it's matched
|
||||
// characters highlighted and the rest filled in.
|
||||
|
|
|
@ -8,27 +8,28 @@ import (
|
|||
|
||||
// Options is the customization options for the filter command.
|
||||
type Options struct {
|
||||
Indicator string `help:"Character for selection" default:"•" env:"GUM_FILTER_INDICATOR"`
|
||||
IndicatorStyle style.Styles `embed:"" prefix:"indicator." set:"defaultForeground=212" envprefix:"GUM_FILTER_INDICATOR_"`
|
||||
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"`
|
||||
Strict bool `help:"Only returns if anything matched. Otherwise return Filter" negatable:"true" default:"true" group:"Selection"`
|
||||
SelectedPrefix string `help:"Character to indicate selected items (hidden if limit is 1)" default:" ◉ " env:"GUM_FILTER_SELECTED_PREFIX"`
|
||||
SelectedPrefixStyle style.Styles `embed:"" prefix:"selected-indicator." set:"defaultForeground=212" envprefix:"GUM_FILTER_SELECTED_PREFIX_"`
|
||||
UnselectedPrefix string `help:"Character to indicate unselected items (hidden if limit is 1)" default:" ○ " env:"GUM_FILTER_UNSELECTED_PREFIX"`
|
||||
UnselectedPrefixStyle style.Styles `embed:"" prefix:"unselected-prefix." set:"defaultForeground=240" envprefix:"GUM_FILTER_UNSELECTED_PREFIX_"`
|
||||
HeaderStyle style.Styles `embed:"" prefix:"header." set:"defaultForeground=240" envprefix:"GUM_FILTER_HEADER_"`
|
||||
Header string `help:"Header value" default:"" env:"GUM_FILTER_HEADER"`
|
||||
TextStyle style.Styles `embed:"" prefix:"text." envprefix:"GUM_FILTER_TEXT_"`
|
||||
MatchStyle style.Styles `embed:"" prefix:"match." set:"defaultForeground=212" envprefix:"GUM_FILTER_MATCH_"`
|
||||
Placeholder string `help:"Placeholder value" default:"Filter..." env:"GUM_FILTER_PLACEHOLDER"`
|
||||
Prompt string `help:"Prompt to display" default:"> " env:"GUM_FILTER_PROMPT"`
|
||||
PromptStyle style.Styles `embed:"" prefix:"prompt." set:"defaultForeground=240" envprefix:"GUM_FILTER_PROMPT_"`
|
||||
Width int `help:"Input width" default:"20" env:"GUM_FILTER_WIDTH"`
|
||||
Height int `help:"Input height" default:"0" env:"GUM_FILTER_HEIGHT"`
|
||||
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:""`
|
||||
Indicator string `help:"Character for selection" default:"•" env:"GUM_FILTER_INDICATOR"`
|
||||
IndicatorStyle style.Styles `embed:"" prefix:"indicator." set:"defaultForeground=212" envprefix:"GUM_FILTER_INDICATOR_"`
|
||||
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"`
|
||||
Strict bool `help:"Only returns if anything matched. Otherwise return Filter" negatable:"true" default:"true" group:"Selection"`
|
||||
SelectedPrefix string `help:"Character to indicate selected items (hidden if limit is 1)" default:" ◉ " env:"GUM_FILTER_SELECTED_PREFIX"`
|
||||
SelectedPrefixStyle style.Styles `embed:"" prefix:"selected-indicator." set:"defaultForeground=212" envprefix:"GUM_FILTER_SELECTED_PREFIX_"`
|
||||
UnselectedPrefix string `help:"Character to indicate unselected items (hidden if limit is 1)" default:" ○ " env:"GUM_FILTER_UNSELECTED_PREFIX"`
|
||||
UnselectedPrefixStyle style.Styles `embed:"" prefix:"unselected-prefix." set:"defaultForeground=240" envprefix:"GUM_FILTER_UNSELECTED_PREFIX_"`
|
||||
HeaderStyle style.Styles `embed:"" prefix:"header." set:"defaultForeground=240" envprefix:"GUM_FILTER_HEADER_"`
|
||||
Header string `help:"Header value" default:"" env:"GUM_FILTER_HEADER"`
|
||||
TextStyle style.Styles `embed:"" prefix:"text." envprefix:"GUM_FILTER_TEXT_"`
|
||||
CursorTextStyle style.Styles `embed:"" prefix:"cursor-text." envprefix:"GUM_FILTER_CURSOR_TEXT_"`
|
||||
MatchStyle style.Styles `embed:"" prefix:"match." set:"defaultForeground=212" envprefix:"GUM_FILTER_MATCH_"`
|
||||
Placeholder string `help:"Placeholder value" default:"Filter..." env:"GUM_FILTER_PLACEHOLDER"`
|
||||
Prompt string `help:"Prompt to display" default:"> " env:"GUM_FILTER_PROMPT"`
|
||||
PromptStyle style.Styles `embed:"" prefix:"prompt." set:"defaultForeground=240" envprefix:"GUM_FILTER_PROMPT_"`
|
||||
Width int `help:"Input width" default:"20" env:"GUM_FILTER_WIDTH"`
|
||||
Height int `help:"Input height" default:"0" env:"GUM_FILTER_HEIGHT"`
|
||||
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:""`
|
||||
Sort bool `help:"Sort the results" default:"true" env:"GUM_FILTER_SORT" negatable:""`
|
||||
Timeout time.Duration `help:"Timeout until filter command aborts" default:"0" env:"GUM_FILTER_TIMEOUT"`
|
||||
Sort bool `help:"Sort the results" default:"true" env:"GUM_FILTER_SORT" negatable:""`
|
||||
}
|
||||
}
|
4
go.mod
4
go.mod
|
@ -3,13 +3,13 @@ module github.com/charmbracelet/gum
|
|||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/alecthomas/kong v0.7.1
|
||||
github.com/alecthomas/kong v0.8.0
|
||||
github.com/alecthomas/mango-kong v0.1.0
|
||||
github.com/charmbracelet/bubbles v0.16.1
|
||||
github.com/charmbracelet/bubbletea v0.24.2
|
||||
github.com/charmbracelet/glamour v0.6.1-0.20230531150759-6d5b52861a9d
|
||||
github.com/charmbracelet/lipgloss v0.7.2-0.20230316100548-06dd20ee5707
|
||||
github.com/mattn/go-isatty v0.0.18
|
||||
github.com/mattn/go-isatty v0.0.19
|
||||
github.com/muesli/reflow v0.3.0
|
||||
github.com/muesli/roff v0.1.0
|
||||
github.com/muesli/termenv v0.15.2-0.20230323153104-73a40463ff25
|
||||
|
|
6
go.sum
6
go.sum
|
@ -64,8 +64,8 @@ github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbf
|
|||
github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s=
|
||||
github.com/alecthomas/chroma/v2 v2.7.0 h1:hm1rY6c/Ob4eGclpQ7X/A3yhqBOZNUTk9q+yhyLIViI=
|
||||
github.com/alecthomas/chroma/v2 v2.7.0/go.mod h1:yrkMI9807G1ROx13fhe1v6PN2DDeaR73L3d+1nmYQtw=
|
||||
github.com/alecthomas/kong v0.7.1 h1:azoTh0IOfwlAX3qN9sHWTxACE2oV8Bg2gAwBsMwDQY4=
|
||||
github.com/alecthomas/kong v0.7.1/go.mod h1:n1iCIO2xS46oE8ZfYCNDqdR0b0wZNrXAIAqro/2132U=
|
||||
github.com/alecthomas/kong v0.8.0 h1:ryDCzutfIqJPnNn0omnrgHLbAggDQM2VWHikE1xqK7s=
|
||||
github.com/alecthomas/kong v0.8.0/go.mod h1:n1iCIO2xS46oE8ZfYCNDqdR0b0wZNrXAIAqro/2132U=
|
||||
github.com/alecthomas/mango-kong v0.1.0 h1:iFVfP1k1K4qpml3JUQmD5I8MCQYfIvsD9mRdrw7jJC4=
|
||||
github.com/alecthomas/mango-kong v0.1.0/go.mod h1:t+TYVdsONUolf/BwVcm+15eqcdAj15h4Qe9MMFAwwT4=
|
||||
github.com/alecthomas/repr v0.1.0 h1:ENn2e1+J3k09gyj2shc0dHr/yjaWSHRlrJ4DPMevDqE=
|
||||
|
@ -391,6 +391,8 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k
|
|||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
|
||||
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
||||
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"github.com/alecthomas/kong"
|
||||
"github.com/charmbracelet/bubbles/spinner"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/mattn/go-isatty"
|
||||
|
||||
"github.com/charmbracelet/gum/internal/exit"
|
||||
"github.com/charmbracelet/gum/style"
|
||||
|
@ -15,6 +16,8 @@ import (
|
|||
// Run provides a shell script interface for the spinner bubble.
|
||||
// https://github.com/charmbracelet/bubbles/spinner
|
||||
func (o Options) Run() error {
|
||||
isTTY := isatty.IsTerminal(os.Stdout.Fd())
|
||||
|
||||
s := spinner.New()
|
||||
s.Style = o.SpinnerStyle.ToLipgloss()
|
||||
s.Spinner = spinnerMap[o.Spinner]
|
||||
|
@ -25,6 +28,7 @@ func (o Options) Run() error {
|
|||
align: o.Align,
|
||||
timeout: o.Timeout,
|
||||
hasTimeout: o.Timeout > 0,
|
||||
showOutput: o.ShowOutput && isTTY,
|
||||
}
|
||||
p := tea.NewProgram(m, tea.WithOutput(os.Stderr))
|
||||
mm, err := p.Run()
|
||||
|
@ -34,15 +38,23 @@ func (o Options) Run() error {
|
|||
return fmt.Errorf("failed to run spin: %w", err)
|
||||
}
|
||||
|
||||
if o.ShowOutput {
|
||||
fmt.Fprint(os.Stdout, m.stdout)
|
||||
fmt.Fprint(os.Stderr, m.stderr)
|
||||
}
|
||||
|
||||
if m.aborted {
|
||||
return exit.ErrAborted
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to access stdout: %w", err)
|
||||
}
|
||||
|
||||
if o.ShowOutput {
|
||||
if !isTTY {
|
||||
_, err := os.Stdout.WriteString(m.stdout)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write to stdout: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
os.Exit(m.status)
|
||||
return nil
|
||||
}
|
||||
|
|
26
spin/spin.go
26
spin/spin.go
|
@ -34,14 +34,16 @@ type model struct {
|
|||
|
||||
status int
|
||||
stdout string
|
||||
stderr string
|
||||
timeout time.Duration
|
||||
hasTimeout bool
|
||||
showOutput bool
|
||||
}
|
||||
|
||||
var outbuf strings.Builder
|
||||
var errbuf strings.Builder
|
||||
|
||||
type finishCommandMsg struct {
|
||||
stdout string
|
||||
stderr string
|
||||
status int
|
||||
}
|
||||
|
||||
|
@ -53,7 +55,6 @@ func commandStart(command []string) tea.Cmd {
|
|||
}
|
||||
cmd := exec.Command(command[0], args...) //nolint:gosec
|
||||
|
||||
var outbuf, errbuf strings.Builder
|
||||
cmd.Stdout = &outbuf
|
||||
cmd.Stderr = &errbuf
|
||||
|
||||
|
@ -67,7 +68,6 @@ func commandStart(command []string) tea.Cmd {
|
|||
|
||||
return finishCommandMsg{
|
||||
stdout: outbuf.String(),
|
||||
stderr: errbuf.String(),
|
||||
status: status,
|
||||
}
|
||||
}
|
||||
|
@ -80,17 +80,18 @@ func (m model) Init() tea.Cmd {
|
|||
timeout.Init(m.timeout, nil),
|
||||
)
|
||||
}
|
||||
|
||||
func (m model) View() string {
|
||||
var str string
|
||||
if m.hasTimeout {
|
||||
str = timeout.Str(m.timeout)
|
||||
}
|
||||
|
||||
var header string
|
||||
if m.align == "left" {
|
||||
return m.spinner.View() + " " + m.title + str
|
||||
header = m.spinner.View() + " " + m.title
|
||||
} else {
|
||||
header = m.title + " " + m.spinner.View()
|
||||
}
|
||||
|
||||
return str + " " + m.title + " " + m.spinner.View()
|
||||
if !m.showOutput {
|
||||
return header
|
||||
}
|
||||
return header + errbuf.String() + "\n" + outbuf.String()
|
||||
}
|
||||
|
||||
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
|
@ -105,7 +106,6 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||
return m, timeout.Tick(msg.TimeoutValue, msg.Data)
|
||||
case finishCommandMsg:
|
||||
m.stdout = msg.stdout
|
||||
m.stderr = msg.stderr
|
||||
m.status = msg.status
|
||||
return m, tea.Quit
|
||||
case tea.KeyMsg:
|
||||
|
|
Loading…
Reference in a new issue