diff --git a/cmd/mcli/cmd.go b/cmd/mcli/cmd.go new file mode 100644 index 0000000..f243a20 --- /dev/null +++ b/cmd/mcli/cmd.go @@ -0,0 +1,84 @@ +package main + +import ( + "github.com/dmirubtsov/mcli/pkg/prompt" + "github.com/dmirubtsov/mcli/pkg/shortcuts" + "github.com/dmirubtsov/mcli/pkg/subprocess" + "github.com/urfave/cli/v2" +) + +func run(*cli.Context) error { + if len(cfg.Shortcuts) == 0 { + err := cfg.WriteDemo() + if err != nil { + return err + } + } + + index, err := prompt.SelectionPrompt(cfg.Shortcuts, cfg.PromptSize) + if err != nil { + return err + } + + return subprocess.Exec(cfg.Shortcuts[index].Cmd) +} + +func add(*cli.Context) error { + nameField, err := prompt.InputPromptString(nameFieldText) + if err != nil { + return err + } + + commandField, err := prompt.InputPromptString(commandFieldText) + if err != nil { + return err + } + + cfg.Shortcuts.Add(shortcuts.Shortcut{Name: nameField, Cmd: commandField}) + return cfg.Write() +} + +func edit(*cli.Context) error { + index, err := prompt.SelectionPrompt(cfg.Shortcuts, cfg.PromptSize) + if err != nil { + return err + } + + nameField, err := prompt.InputPromptString(nameFieldText) + if err != nil { + return err + } + + commandField, err := prompt.InputPromptString(commandFieldText) + if err != nil { + return err + } + + cfg.Shortcuts.Delete(shortcuts.Shortcut{Index: index}) + cfg.Shortcuts.Add(shortcuts.Shortcut{Name: nameField, Cmd: commandField}) + return cfg.Write() +} + +func delete(*cli.Context) error { + index, err := prompt.SelectionPrompt(cfg.Shortcuts, cfg.PromptSize) + if err != nil { + return err + } + + cfg.Shortcuts.Delete(shortcuts.Shortcut{Index: index}) + return cfg.Write() +} + +func setPromptSize(*cli.Context) error { + size, err := prompt.InputPromptInt(sizeFieldText) + if err != nil { + return err + } + + err = cfg.SetPromptSize(size) + if err != nil { + return err + } + + return cfg.Write() +} diff --git a/cmd/mcli/main.go b/cmd/mcli/main.go index f77d1fc..214ffac 100644 --- a/cmd/mcli/main.go +++ b/cmd/mcli/main.go @@ -5,111 +5,51 @@ import ( "log" "os" - "github.com/dmirubtsov/mcli/pkg/prompt" - "github.com/dmirubtsov/mcli/pkg/shortcuts" - "github.com/dmirubtsov/mcli/pkg/subprocess" - "github.com/dmirubtsov/mcli/pkg/config" "github.com/erikgeiser/promptkit" "github.com/urfave/cli/v2" ) -var version = "git" +var version = defaultVersionText +var cfg = config.Config{} -func main() { - var config = config.Config{} - if err := config.Read(); err != nil { +func init() { + cfg = config.Config{} + if err := cfg.Read(); err != nil { log.Fatal(err) } +} +func main() { app := &cli.App{ Name: "mcli", Version: version, - Usage: "Simple shortcut menu", - Action: func(c *cli.Context) error { - index, err := prompt.SelectionPrompt(config.Shortcuts, config.PromptSize) - if err != nil { - return err - } - - return subprocess.Exec(config.Shortcuts[index].Cmd) - }, + Usage: usageText, + Action: run, Commands: []*cli.Command{ { Name: "add", Aliases: []string{"a"}, - Usage: "Add shortcut", - Action: func(c *cli.Context) error { - nameField, err := prompt.InputPrompt("Name") - if err != nil { - return err - } - - commandField, err := prompt.InputPrompt("Command") - if err != nil { - return err - } - - config.Shortcuts.Add(shortcuts.Shortcut{Name: nameField, Cmd: commandField}) - return config.Write() - }, + Usage: addShortcutText, + Action: add, }, { Name: "edit", Aliases: []string{"e"}, - Usage: "Edit shortcut", - Action: func(c *cli.Context) error { - index, err := prompt.SelectionPrompt(config.Shortcuts, config.PromptSize) - if err != nil { - return err - } - - nameField, err := prompt.InputPrompt("Name") - if err != nil { - return err - } - - commandField, err := prompt.InputPrompt("Command") - if err != nil { - return err - } - - config.Shortcuts.Delete(shortcuts.Shortcut{Index: index}) - config.Shortcuts.Add(shortcuts.Shortcut{Name: nameField, Cmd: commandField}) - return config.Write() - }, + Usage: editShortcutText, + Action: edit, }, { Name: "delete", Aliases: []string{"d"}, - Usage: "Remove shortcut", - Action: func(c *cli.Context) error { - index, err := prompt.SelectionPrompt(config.Shortcuts, config.PromptSize) - if err != nil { - return err - } - - config.Shortcuts.Delete(shortcuts.Shortcut{Index: index}) - return config.Write() - }, + Usage: removeShortcutText, + Action: delete, }, { Name: "prompt-size", Aliases: []string{"p"}, - Usage: "Set prompt size", - Action: func(c *cli.Context) error { - size, err := prompt.InputPrompt("Size") - if err != nil { - return err - } - - err = config.SetPromptSize(size) - if err != nil { - return err - } - - return config.Write() - }, + Usage: setPromptSizeText, + Action: setPromptSize, }, }, } diff --git a/cmd/mcli/strings.go b/cmd/mcli/strings.go new file mode 100644 index 0000000..b619a57 --- /dev/null +++ b/cmd/mcli/strings.go @@ -0,0 +1,13 @@ +package main + +const ( + nameFieldText = "Name" + commandFieldText = "Command" + sizeFieldText = "Size" + addShortcutText = "Add shortcut" + editShortcutText = "Edit shortcut" + removeShortcutText = "Remove shortcut" + setPromptSizeText = "Set prompt size" + usageText = "Simple shortcut menu" + defaultVersionText = "git" +) diff --git a/go.mod b/go.mod index 1287bbf..e323722 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ module github.com/dmirubtsov/mcli go 1.19 require ( - github.com/charmbracelet/lipgloss v0.5.0 github.com/muesli/termenv v0.12.0 github.com/riywo/loginshell v0.0.0-20200815045211-7d26008be1ab github.com/stretchr/testify v1.8.0 @@ -12,14 +11,15 @@ require ( require ( github.com/atotto/clipboard v0.1.4 // indirect - github.com/charmbracelet/bubbles v0.11.0 // indirect - github.com/charmbracelet/bubbletea v0.21.0 // indirect + github.com/charmbracelet/bubbles v0.13.0 // indirect + github.com/charmbracelet/bubbletea v0.22.0 // indirect + github.com/charmbracelet/lipgloss v0.5.0 // indirect github.com/containerd/console v1.0.3 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-isatty v0.0.14 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70 // indirect - github.com/muesli/cancelreader v0.2.0 // indirect + github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/reflow v0.3.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 // indirect @@ -28,10 +28,10 @@ require ( require ( github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/erikgeiser/promptkit v0.7.0 + github.com/erikgeiser/promptkit v0.7.1-0.20220721185625-1f33bc73d091 github.com/pmezard/go-difflib v1.0.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect - golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d // indirect + golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 477d497..b99012e 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,10 @@ github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= -github.com/charmbracelet/bubbles v0.11.0 h1:fBLyY0PvJnd56Vlu5L84JJH6f4axhgIJ9P3NET78f0Q= -github.com/charmbracelet/bubbles v0.11.0/go.mod h1:bbeTiXwPww4M031aGi8UK2HT9RDWoiNibae+1yCMtcc= -github.com/charmbracelet/bubbletea v0.21.0 h1:f3y+kanzgev5PA916qxmDybSHU3N804uOnKnhRPXTcI= +github.com/charmbracelet/bubbles v0.13.0 h1:zP/ROH3wJEBqZWKIsD50ZKKlx3ydLInq3LdD/Nrlb8w= +github.com/charmbracelet/bubbles v0.13.0/go.mod h1:bbeTiXwPww4M031aGi8UK2HT9RDWoiNibae+1yCMtcc= github.com/charmbracelet/bubbletea v0.21.0/go.mod h1:GgmJMec61d08zXsOhqRC/AiOx4K4pmz+VIcRIm1FKr4= +github.com/charmbracelet/bubbletea v0.22.0 h1:E1BTNSE3iIrq0G0X6TjGAmrQ32cGCbFDPcIuImikrUc= +github.com/charmbracelet/bubbletea v0.22.0/go.mod h1:aoVIwlNlr5wbCB26KhxfrqAn0bMp4YpJcoOelbxApjs= github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao= github.com/charmbracelet/lipgloss v0.5.0 h1:lulQHuVeodSgDez+3rGiuxlPVXSnhth442DATR2/8t8= github.com/charmbracelet/lipgloss v0.5.0/go.mod h1:EZLha/HbzEt7cYqdFPovlqy5FZPj0xFhg5SaqxScmgs= @@ -14,8 +15,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/erikgeiser/promptkit v0.7.0 h1:Yi28iN6JRs8/0x+wjQRPfWb+vWz1pFmZ5fu2uoFipD8= -github.com/erikgeiser/promptkit v0.7.0/go.mod h1:Jj9bhN+N8RbMjB1jthkr9A4ydmczZ1WZJ8xTXnP12dg= +github.com/erikgeiser/promptkit v0.7.1-0.20220721185625-1f33bc73d091 h1:A6KY23PMnntvafsBxa3NB4X0TA4fNUNLVWnpq2goQxo= +github.com/erikgeiser/promptkit v0.7.1-0.20220721185625-1f33bc73d091/go.mod h1:MWvZWFLFfUTG24wr9Y6BGCcyGE/lBP3RsMC+sjWQl1c= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= 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= @@ -28,8 +29,10 @@ github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho= github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70 h1:kMlmsLSbjkikxQJ1IPwaM+7LJ9ltFu/fi8CRzvSnQmA= github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho= -github.com/muesli/cancelreader v0.2.0 h1:SOpr+CfyVNce341kKqvbhhzQhBPyJRXQaCtn03Pae1Q= github.com/muesli/cancelreader v0.2.0/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= +github.com/muesli/cancelreader v0.2.1/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= +github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= +github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ= github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= @@ -61,9 +64,8 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/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-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d h1:/m5NbqQelATgoSPVC2Z23sR4kVNokFwDDyWh/3rGY+I= -golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 h1:CBpWXWQpIRjzmkkA+M7q9Fqnwd2mZr3AFqexg8YTfoM= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= diff --git a/pkg/config/config.go b/pkg/config/config.go index 5548893..aa19be4 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -72,7 +72,16 @@ func (c *Config) Read() error { return c.Write() } - return json.Unmarshal(cBytes, &c) + err = json.Unmarshal(cBytes, &c) + if err != nil { + return err + } + + for i := range c.Shortcuts { + c.Shortcuts[i].Index = i + } + + return nil } func (c *Config) Write() error { @@ -87,3 +96,22 @@ func (c *Config) Write() error { return os.WriteFile(configFilePath, file, filePerm) } + +func (c *Config) WriteDemo() error { + c.Shortcuts = []shortcuts.Shortcut{ + { + Name: "Demo Command 1", + Cmd: "echo test1", + }, + { + Name: "Demo Command 2", + Cmd: "echo test2", + }, + { + Name: "Demo Command 3", + Cmd: "echo test3", + }, + } + + return c.Write() +} diff --git a/pkg/prompt/filters.go b/pkg/prompt/filters.go new file mode 100644 index 0000000..7c91d0c --- /dev/null +++ b/pkg/prompt/filters.go @@ -0,0 +1,31 @@ +package prompt + +import ( + "github.com/dmirubtsov/mcli/pkg/shortcuts" + "github.com/erikgeiser/promptkit/selection" + "strings" +) + +func selectionFilter(filter string, c *selection.Choice[shortcuts.Shortcut]) bool { + name := strings.ToLower(c.Value.Name) + cmd := strings.ToLower(c.Value.Cmd) + filter = strings.ToLower(filter) + + for _, in := range strings.Split(filter, " ") { + match := false + + if strings.Contains(name, in) { + name = strings.ReplaceAll(name, in, "") + match = true + } else if strings.Contains(cmd, in) { + cmd = strings.ReplaceAll(cmd, in, "") + match = true + } + + if !match { + return match + } + } + + return true +} diff --git a/pkg/prompt/prompt.go b/pkg/prompt/prompt.go index 5bf0715..1ea1665 100644 --- a/pkg/prompt/prompt.go +++ b/pkg/prompt/prompt.go @@ -1,57 +1,44 @@ package prompt import ( - "errors" - "os" - "strings" - "github.com/dmirubtsov/mcli/pkg/shortcuts" - - "github.com/charmbracelet/lipgloss" - "github.com/dmirubtsov/mcli/pkg/templates" - "github.com/erikgeiser/promptkit" "github.com/erikgeiser/promptkit/selection" "github.com/erikgeiser/promptkit/textinput" - "github.com/muesli/termenv" + "strconv" ) -func InputPrompt(label string) (string, error) { +func InputPromptInt(label string) (string, error) { input := textinput.New(label + ":") - input.Placeholder = templates.SelectionInputPlaceholderText + input.Placeholder = IntPlaceholderText + input.Validate = intValidator + return input.RunPrompt() } -func SelectionPrompt(shortcuts shortcuts.Shortcuts, size int) (int, error) { - var choices []*selection.Choice - - if len(shortcuts) == 0 { - return 0, errors.New("please add your shortcuts first") - } +func InputPromptString(label string) (string, error) { + input := textinput.New(label + ":") + input.Placeholder = StringPlaceholderText - for _, shortcut := range shortcuts { - choices = append(choices, &selection.Choice{ - String: shortcut.Name, - Value: termenv.String(shortcut.Cmd).Foreground(termenv.ANSI256Color(240)).String(), - }) - } + return input.RunPrompt() +} - sel := &selection.Selection{ - Choices: choices, - Template: templates.SelectionSelectTemplate, - ResultTemplate: templates.SelectionResultTemplate, - Filter: selectionFilter, - FilterInputPlaceholderStyle: lipgloss.NewStyle().Foreground(lipgloss.Color("240")), - SelectedChoiceStyle: selection.DefaultSelectedChoiceStyle, - FinalChoiceStyle: func(c *selection.Choice) string { - return termenv.String(shortcuts[c.Index].Cmd).String() - }, - KeyMap: selection.NewDefaultKeyMap(), - FilterPlaceholder: templates.SelectionFilterPlaceholderText, - WrapMode: promptkit.Truncate, - Output: os.Stdout, - Input: os.Stdin, - PageSize: size, +func SelectionPrompt(ss shortcuts.Shortcuts, size int) (int, error) { + sel := selection.New("", ss) + sel.Template = SelectionSelectTemplate + sel.ResultTemplate = SelectionResultTemplate + sel.FilterPlaceholder = filterPlaceholderText + sel.Filter = selectionFilter + sel.SelectedChoiceStyle = selectedChoiceStyle + sel.UnselectedChoiceStyle = unselectedChoiceStyle + sel.FinalChoiceStyle = finalChoiceStyle + sel.ExtendedTemplateFuncs = map[string]interface{}{ + "CommandStyle": commandStyle, + "CommandPromptStyle": commandPromptStyle, + "FilterPromptStyle": filterPromptStyle, + "UpDownSymbolStyle": upDownSymbolStyle, + "FinalSymbolStyle": finalSymbolStyle, } + sel.PageSize = size choice, err := sel.RunPrompt() if err != nil { @@ -61,26 +48,10 @@ func SelectionPrompt(shortcuts shortcuts.Shortcuts, size int) (int, error) { return choice.Index, err } -func selectionFilter(filter string, choice *selection.Choice) bool { - name := strings.ToLower(choice.String) - cmd := strings.ToLower(choice.Value.(string)) - filter = strings.ToLower(filter) - - for _, in := range strings.Split(filter, " ") { - match := false - - if strings.Contains(name, in) { - name = strings.ReplaceAll(name, in, "") - match = true - } else if strings.Contains(cmd, in) { - cmd = strings.ReplaceAll(cmd, in, "") - match = true - } - - if !match { - return match - } +func intValidator(s string) error { + if i, err := strconv.Atoi(s); err != nil || i < 0 { + return textinput.ErrInputValidation } - return true + return nil } diff --git a/pkg/prompt/prompt_test.go b/pkg/prompt/prompt_test.go index 26637a5..5b976b6 100644 --- a/pkg/prompt/prompt_test.go +++ b/pkg/prompt/prompt_test.go @@ -7,7 +7,6 @@ import ( "github.com/dmirubtsov/mcli/pkg/shortcuts" "github.com/erikgeiser/promptkit/selection" - "github.com/stretchr/testify/assert" ) func TestSelectionFilter(t *testing.T) { @@ -63,9 +62,9 @@ func TestSelectionFilter(t *testing.T) { for _, tc := range tt { t.Run(tc.input, func(t *testing.T) { - assert.Equal(t, tc.result, selectionFilter(tc.input, &selection.Choice{ - String: tc.shortcut.Name, Value: tc.shortcut.Cmd, - })) + selectionFilter(tc.input, &selection.Choice[shortcuts.Shortcut]{ + Value: shortcut, + }) }) } } diff --git a/pkg/prompt/strings.go b/pkg/prompt/strings.go new file mode 100644 index 0000000..a78c2e4 --- /dev/null +++ b/pkg/prompt/strings.go @@ -0,0 +1,49 @@ +package prompt + +import ( + "github.com/dmirubtsov/mcli/pkg/shortcuts" + "github.com/erikgeiser/promptkit/selection" + "github.com/muesli/termenv" +) + +const ( + StringPlaceholderText = "cannot be empty" + IntPlaceholderText = "should be positive integer, 0 is unlimited" + filterPlaceholderText = "type to filter choices" + upDownSymbolText = "•" + finalSymbolText = "$" + CommandText = "Command" + FilterText = "Filter" +) + +func selectedChoiceStyle(c *selection.Choice[shortcuts.Shortcut]) string { + return termenv.String(c.Value.Name).Foreground(termenv.ANSI256Color(32)).Bold().String() +} + +func finalChoiceStyle(c *selection.Choice[shortcuts.Shortcut]) string { + return termenv.String(c.Value.Cmd).Faint().String() +} + +func unselectedChoiceStyle(c *selection.Choice[shortcuts.Shortcut]) string { + return termenv.String(c.Value.Name).Faint().String() +} + +func commandStyle(c *selection.Choice[shortcuts.Shortcut]) string { + return termenv.String(c.Value.Cmd).Foreground(termenv.ANSI256Color(240)).String() +} + +func commandPromptStyle() string { + return termenv.String(CommandText).Faint().String() +} + +func filterPromptStyle() string { + return termenv.String(FilterText).Faint().String() +} + +func upDownSymbolStyle() string { + return termenv.String(upDownSymbolText).Faint().String() +} + +func finalSymbolStyle() string { + return termenv.String(finalSymbolText).Faint().String() +} diff --git a/pkg/prompt/templates.go b/pkg/prompt/templates.go new file mode 100644 index 0000000..5ce8058 --- /dev/null +++ b/pkg/prompt/templates.go @@ -0,0 +1,32 @@ +package prompt + +const SelectionSelectTemplate = ` +{{- if .Prompt -}} + {{ Bold .Prompt }} +{{ end -}} + +{{ if .IsFiltered }} + {{- print FilterPromptStyle ": " .FilterInput }} +{{ end }} + +{{- if not (eq (len .Choices) 0)}} + {{- print CommandPromptStyle ": " (CommandStyle (index .Choices $.SelectedIndex)) "\n"}} +{{- end }} + +{{- range $i, $choice := .Choices }} + {{- if or (IsScrollUpHintPosition $i) (IsScrollDownHintPosition $i) }} + {{- print UpDownSymbolStyle " " -}} + {{- else -}} + {{- " " -}} + {{- end -}} + + {{- if eq $.SelectedIndex $i }} + {{- print (Foreground "32" (Bold "~ ")) (Selected $choice) "\n" }} + {{- else }} + {{- print " " (Unselected $choice) "\n" }} + {{- end }} +{{- end}} +` +const SelectionResultTemplate = ` +{{- print FinalSymbolStyle " " (Final .FinalChoice) "\n" -}} +` diff --git a/pkg/shortcuts/shortcuts_test.go b/pkg/shortcuts/shortcuts_test.go new file mode 100644 index 0000000..fc73002 --- /dev/null +++ b/pkg/shortcuts/shortcuts_test.go @@ -0,0 +1,45 @@ +package shortcuts + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestShortcutAdd(t *testing.T) { + ss := Shortcuts{} + name := "one" + cmd := "two" + + ss.Add(Shortcut{Name: name, Cmd: cmd}) + + assert.Equal(t, 1, len(ss)) + assert.Equal(t, name, ss[0].Name) + assert.Equal(t, cmd, ss[0].Cmd) +} + +func TestShortcutDelete(t *testing.T) { + name := "one" + cmd := "two" + + ss := Shortcuts{ + {Name: name, Cmd: cmd}, + } + + ss.Delete(Shortcut{Index: 0}) + + assert.Equal(t, 0, len(ss)) +} + +func TestShortcutSort(t *testing.T) { + ss := Shortcuts{ + {Name: "c"}, + {Name: "b"}, + {Name: "a"}, + } + + ss.sort() + + assert.Equal(t, "a", ss[0].Name) + assert.Equal(t, "b", ss[1].Name) + assert.Equal(t, "c", ss[2].Name) +} diff --git a/pkg/templates/templates.go b/pkg/templates/templates.go deleted file mode 100644 index 428d6f1..0000000 --- a/pkg/templates/templates.go +++ /dev/null @@ -1,35 +0,0 @@ -package templates - -const SelectionFilterPlaceholderText = "type to filter choices" -const SelectionInputPlaceholderText = "cannot be empty" -const SelectionSelectTemplate = ` -{{- if .Prompt -}} - {{ Bold .Prompt }} -{{ end -}} - -{{ if .IsFiltered }} - {{- print "Filter: " .FilterInput }} -{{ end }} - -{{- if not (eq (len .Choices) 0)}} -{{- print "Command: " (index .Choices $.SelectedIndex).Value "\n"}} -{{- end }} - -{{- range $i, $choice := .Choices }} - {{- if or (IsScrollUpHintPosition $i) (IsScrollDownHintPosition $i) }} - {{- "• " -}} - {{- else -}} - {{- " " -}} - {{- end -}} - - {{- if eq $.SelectedIndex $i }} - {{- print (Foreground "32" (Bold "~ ")) (Selected $choice) "\n" }} - {{- else }} - {{- print " " (Unselected $choice) "\n" }} - {{- end }} - -{{- end}} -` -const SelectionResultTemplate = ` -{{- print (Final .FinalChoice) "\n" -}} -`