From 21ed67f7d9c7b1a24d70c3ca428e06737bdc804a Mon Sep 17 00:00:00 2001 From: Shantanu Date: Mon, 13 May 2024 18:55:34 -0700 Subject: [PATCH] cmd/ask: allow users to select a command and copy it to the clipboard --- cmd/ask.go | 52 +++++++++++++++++++++++++++--- cmd/component/list/ask_delegate.go | 8 ++--- cmd/component/list/list.go | 6 +++- 3 files changed, 56 insertions(+), 10 deletions(-) diff --git a/cmd/ask.go b/cmd/ask.go index 9bf1c02..7eaa0ee 100644 --- a/cmd/ask.go +++ b/cmd/ask.go @@ -1,14 +1,17 @@ package cmd import ( + "errors" "fmt" "os" "runtime" "strings" + "github.com/atotto/clipboard" tea "github.com/charmbracelet/bubbletea" "github.com/getsavvyinc/savvy-cli/client" "github.com/getsavvyinc/savvy-cli/cmd/component" + "github.com/getsavvyinc/savvy-cli/cmd/component/list" "github.com/getsavvyinc/savvy-cli/display" "github.com/spf13/cobra" ) @@ -65,25 +68,66 @@ var askCmd = &cobra.Command{ Runbook: *runbook, }) - m, err := newDisplayCommandsModel(rb) + m, err := newAskCommandsModel(rb) if err != nil { display.ErrorWithSupportCTA(err) os.Exit(1) } p := tea.NewProgram(m, tea.WithOutput(programOutput), tea.WithAltScreen()) - if _, err := p.Run(); err != nil { + result, err := p.Run() + if err != nil { // TODO: fail gracefully and provide users a link to view the runbook display.ErrorWithSupportCTA(fmt.Errorf("could not display runbook: %w", err)) os.Exit(1) } - if rb.URL != "" { - display.Success("View and edit your runbook online at: " + rb.URL) + + if m, ok := result.(*askCommands); ok { + selectedCommand := m.l.SelectedCommand() + if selectedCommand == "" { + return + } + if err := clipboard.WriteAll(selectedCommand); err != nil { + display.Info(selectedCommand) + return + } + display.Info(fmt.Sprintf("Copied to clipboard: %s", selectedCommand)) } return }, } +type askCommands struct { + l list.Model +} + +func newAskCommandsModel(runbook *component.Runbook) (*askCommands, error) { + if runbook == nil { + return nil, errors.New("runbook is empty") + } + + listItems := toItems(runbook.Steps) + l := list.NewModelWithDelegate(listItems, runbook.Title, runbook.URL, list.NewAskDelegate()) + return &askCommands{l: l}, nil +} +func (dc *askCommands) Init() tea.Cmd { + // Just return `nil`, which means "no I/O right now, please." + dc.l.Init() + return nil +} + +func (dc *askCommands) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + m, cmd := dc.l.Update(msg) + if m, ok := m.(list.Model); ok { + dc.l = m + } + return dc, cmd +} + +func (dc *askCommands) View() string { + return dc.l.View() +} + func init() { rootCmd.AddCommand(askCmd) } diff --git a/cmd/component/list/ask_delegate.go b/cmd/component/list/ask_delegate.go index ce3407a..bf899e0 100644 --- a/cmd/component/list/ask_delegate.go +++ b/cmd/component/list/ask_delegate.go @@ -85,11 +85,9 @@ func NewAskDelegate() list.DefaultDelegate { } func HandleSelectedCommand(selectedCommand string) tea.Cmd { - return tea.Sequence( - func() tea.Msg { - return SelectedCommandMsg{selectedCommand} - }, tea.Quit, - ) + return func() tea.Msg { + return SelectedCommandMsg{selectedCommand} + } } type SelectedCommandMsg struct { diff --git a/cmd/component/list/list.go b/cmd/component/list/list.go index c76b7b7..ed33e17 100644 --- a/cmd/component/list/list.go +++ b/cmd/component/list/list.go @@ -105,7 +105,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, nil case SelectedCommandMsg: m.selectedCommand = msg.Command - return m, nil + return m, tea.Quit } var cmd tea.Cmd @@ -116,3 +116,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { func (m Model) View() string { return docStyle.Render(m.list.View()) } + +func (m Model) SelectedCommand() string { + return m.selectedCommand +}