From 952884009c3d8e97a4ee2b63bc4174b5335da41b Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Mon, 16 Feb 2026 22:17:20 +0900 Subject: [PATCH] Use types for OnCancel values --- action.go | 2 +- config_test.go | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++ interface.go | 24 +++++++++++++++++---- peco.go | 11 +++++++--- peco_test.go | 2 +- 5 files changed, 88 insertions(+), 9 deletions(-) diff --git a/action.go b/action.go index 3639afc..8bbc083 100644 --- a/action.go +++ b/action.go @@ -392,7 +392,7 @@ func doCancel(ctx context.Context, state *Peco, e Event) { // peco.Cancel -> end program, exit with failure err := makeIgnorable(errors.New("user canceled")) - if state.onCancel == errorKey { + if state.onCancel == OnCancelError { err = setExitStatus(err, 1) } state.Exit(err) diff --git a/config_test.go b/config_test.go index b257fa6..afdc144 100644 --- a/config_test.go +++ b/config_test.go @@ -220,6 +220,64 @@ func TestLocateRcfileYAML(t *testing.T) { require.Equal(t, filepath.Join(pecoDir, "config.yaml"), file) } +func TestOnCancelBehavior(t *testing.T) { + t.Run("valid values via JSON", func(t *testing.T) { + for _, tc := range []struct { + input string + expected OnCancelBehavior + }{ + {`{"OnCancel":"success"}`, OnCancelSuccess}, + {`{"OnCancel":"error"}`, OnCancelError}, + {`{}`, ""}, // absent key stays at zero value; default applied later in ApplyConfig + } { + var cfg Config + require.NoError(t, cfg.Init()) + require.NoError(t, json.Unmarshal([]byte(tc.input), &cfg)) + require.Equal(t, tc.expected, cfg.OnCancel) + } + }) + + t.Run("valid values via YAML", func(t *testing.T) { + for _, tc := range []struct { + input string + expected OnCancelBehavior + }{ + {"OnCancel: success", OnCancelSuccess}, + {"OnCancel: error", OnCancelError}, + } { + var cfg Config + require.NoError(t, cfg.Init()) + require.NoError(t, yaml.Unmarshal([]byte(tc.input), &cfg)) + require.Equal(t, tc.expected, cfg.OnCancel) + } + }) + + t.Run("invalid value via JSON", func(t *testing.T) { + var cfg Config + require.NoError(t, cfg.Init()) + err := json.Unmarshal([]byte(`{"OnCancel":"bogus"}`), &cfg) + require.Error(t, err) + require.Contains(t, err.Error(), "bogus") + }) + + t.Run("invalid value via YAML", func(t *testing.T) { + var cfg Config + require.NoError(t, cfg.Init()) + err := yaml.Unmarshal([]byte("OnCancel: bogus"), &cfg) + require.Error(t, err) + require.Contains(t, err.Error(), "bogus") + }) + + t.Run("invalid CLI option rejected", func(t *testing.T) { + p := newPeco() + var opts CLIOptions + opts.OptOnCancel = "bogus" + err := p.ApplyConfig(opts) + require.Error(t, err) + require.Contains(t, err.Error(), "bogus") + }) +} + func TestReadFilenameYAML(t *testing.T) { dir := t.TempDir() yamlFile := filepath.Join(dir, "config.yaml") diff --git a/interface.go b/interface.go index 7db82a9..9d07978 100644 --- a/interface.go +++ b/interface.go @@ -1,6 +1,7 @@ package peco import ( + "fmt" "io" "sync" "time" @@ -15,11 +16,26 @@ import ( "github.com/peco/peco/pipeline" ) +// OnCancelBehavior specifies what happens when the user cancels peco. +type OnCancelBehavior string + const ( - successKey = "success" - errorKey = "error" + OnCancelSuccess OnCancelBehavior = "success" + OnCancelError OnCancelBehavior = "error" ) +func (o *OnCancelBehavior) UnmarshalText(b []byte) error { + switch s := string(b); s { + case "", "success": + *o = OnCancelSuccess + case "error": + *o = OnCancelError + default: + return fmt.Errorf("invalid OnCancel value %q: must be %q or %q", s, OnCancelSuccess, OnCancelError) + } + return nil +} + const ( ToLineAbove PagingRequestType = iota // ToLineAbove moves the selection to the line above ToScrollPageDown // ToScrollPageDown moves the selection to the next page @@ -84,7 +100,7 @@ type Peco struct { location Location maxScanBufferSize int mutex sync.Mutex - onCancel string + onCancel OnCancelBehavior printQuery bool prompt string query Query @@ -324,7 +340,7 @@ type Config struct { Prompt string `json:"Prompt" yaml:"Prompt"` Layout string `json:"Layout" yaml:"Layout"` Use256Color bool `json:"Use256Color" yaml:"Use256Color"` - OnCancel string `json:"OnCancel" yaml:"OnCancel"` + OnCancel OnCancelBehavior `json:"OnCancel" yaml:"OnCancel"` CustomMatcher map[string][]string `json:"CustomMatcher" yaml:"CustomMatcher"` CustomFilter map[string]CustomFilterConfig `json:"CustomFilter" yaml:"CustomFilter"` QueryExecutionDelay int `json:"QueryExecutionDelay" yaml:"QueryExecutionDelay"` diff --git a/peco.go b/peco.go index 18b0ad4..7f97600 100644 --- a/peco.go +++ b/peco.go @@ -644,9 +644,14 @@ func (p *Peco) ApplyConfig(opts CLIOptions) error { p.use256Color = p.config.Use256Color - p.onCancel = successKey - if opts.OptOnCancel == errorKey || p.config.OnCancel == errorKey { - p.onCancel = errorKey + p.onCancel = p.config.OnCancel + if p.onCancel == "" { + p.onCancel = OnCancelSuccess + } + if opts.OptOnCancel != "" { + if err := p.onCancel.UnmarshalText([]byte(opts.OptOnCancel)); err != nil { + return fmt.Errorf("invalid --on-cancel value: %w", err) + } } p.bufferSize = opts.OptBufferSize if v := opts.OptSelectionPrefix; len(v) > 0 { diff --git a/peco_test.go b/peco_test.go index 8aafa6a..3c3a5cf 100644 --- a/peco_test.go +++ b/peco_test.go @@ -398,7 +398,7 @@ func TestApplyConfig(t *testing.T) { return } - if !assert.Equal(t, opts.OptOnCancel, p.onCancel, "p.onCancel should be equal to opts.OptOnCancel") { + if !assert.Equal(t, OnCancelBehavior(opts.OptOnCancel), p.onCancel, "p.onCancel should be equal to opts.OptOnCancel") { return }