Implement #242: Made the fuzzy filter configurable

This commit is contained in:
Eric F 2016-12-08 17:20:55 +11:00
parent 4c4b4535ed
commit 21179c2c38
7 changed files with 90 additions and 9 deletions

View file

@ -58,13 +58,15 @@ Not only can you select multiple lines one by one, you can select a range of lin
## Select Filters
Different types of filters are available. Default is case-insensitive filter, so lines with any case will match. You can toggle between IgnoreCase, CaseSensitive, SmartCase and RegExp filters.
Different types of filters are available. Default is case-insensitive filter, so lines with any case will match. You can toggle between IgnoreCase, CaseSensitive, SmartCase RegExp and Fuzzy filters.
The SmartCase filter uses case-*insensitive* matching when all of the queries are lower case, and case-*sensitive* matching otherwise.
The RegExp filter allows you to use any valid regular expression to match lines
![Executed `ps aux | peco`, then typed `google`, which matches the Chrome.app under IgnoreCase filter type. Whenyou change it to Regexp filter, this is no longer the case. But you can type `(?i)google` instead to toggle case-insensitive mode](http://peco.github.io/images/peco-demo-matcher.gif)
The Fuzzy filter allows you to find matches using partial patterns. For example, when searching for `ALongString`, you can enable the Fuzzy filter and search `ALS` to find it. The Fuzzy filter uses smart case search like the SmartCase filter. The Fuzzy filter is not enabled by default, but can be enabled using option `--enable-fuzzy`.
![Executed `ps aux | peco`, then typed `google`, which matches the Chrome.app under IgnoreCase filter type. When you change it to Regexp filter, this is no longer the case. But you can type `(?i)google` instead to toggle case-insensitive mode](http://peco.github.io/images/peco-demo-matcher.gif)
## Selectable Layout
@ -158,9 +160,13 @@ Changes how peco interprets incoming data. When this flag is set, you may insert
Specifies the initial line position upon start up. E.g. If you want to start out with the second line selected, set it to "1" (because the index is 0 based)
### --initial-filter `IgnoreCase|CaseSensitive|SmartCase|Regexp`
### --fuzzy-filter `enabled|disabled`
Specifies the initial filter to use upon start up. You should specify the name of the filter like `IgnoreCase`, `CaseSensitive`, `SmartCase` and `Regexp`. Default is `IgnoreCase`.
Make the Fuzzy filter available. The command line option can override the value in the configuration file. Default is `disabled`.
### --initial-filter `IgnoreCase|CaseSensitive|SmartCase|Regexp|Fuzzy`
Specifies the initial filter to use upon start up. You should specify the name of the filter like `IgnoreCase`, `CaseSensitive`, `SmartCase`, `Regexp` and `Fuzzy` (if enabled). Default is `IgnoreCase`.
### --prompt
@ -217,9 +223,13 @@ You can change the query line's prompt, which is `QUERY>` by default.
*InitialMatcher* has been deprecated. Please use `InitialFilter` instead.
### FuzzyFilter
Set to `enabled` to make the Fuzzy filter available. Default is `disabled`.
### InitialFilter
Specifies the filter name to start peco with. You should specify the name of the filter, such as `IgnoreCase`, `CaseSensitive`, `SmartCase` and `Regexp`
Specifies the filter name to start peco with. You should specify the name of the filter, such as `IgnoreCase`, `CaseSensitive`, `SmartCase`, `Regexp` and `Fuzzy` (if [enabled](#FuzzyFilter)).
### StickySelection
@ -473,7 +483,7 @@ For now, styles of following 5 items can be customized in `config.json`.
This is an experimental feature. Please note that some details of this specification may change
By default `peco` comes with `IgnoreCase`, `CaseSensitive`, `SmartCase` and `Regexp` filters, but since v0.1.3, it is possible to create your own custom filter.
By default `peco` comes with `IgnoreCase`, `CaseSensitive`, `SmartCase`, `Regexp` and `Fuzzy` filters, but since v0.1.3, it is possible to create your own custom filter.
The filter will be executed via `Command.Run()` as an external process, and it will be passed the query values in the command line, and the original unaltered buffer is passed via `os.Stdin`. Your filter must perform the matching, and print out to `os.Stdout` matched lines. You filter MAY be called multiple times if the buffer
given to peco is big enough. See `BufferThreshold` below.

View file

@ -25,6 +25,7 @@ func (c *Config) Init() error {
c.Style.Init()
c.Prompt = "QUERY>"
c.Layout = LayoutTypeTopDown
c.FuzzyFilter = OptionDisabled
return nil
}

View file

@ -47,6 +47,7 @@ func TestReadRC(t *testing.T) {
InitialMatcher: IgnoreCaseMatch,
Layout: DefaultLayoutType,
Prompt: "[peco]",
FuzzyFilter: "disabled",
Style: StyleSet{
Matched: Style{
fg: termbox.ColorCyan | termbox.AttrBold,

View file

@ -433,9 +433,8 @@ func NewFuzzyFilter() *RegexpFilter {
return []string{"i"}
})
rf.quotemeta = true
rf.name = "FuzzySearch"
rf.name = FuzzyFilter
rf.queryTrans = queryTransformerFunc(func(q string) string {
// Assume that all characters are runes
qr := []rune(q)
res := make([]rune, 5*len(qr))
i := 0

View file

@ -46,6 +46,11 @@ const (
RegexpMatch = "Regexp"
)
// Filter names, used in the config file
const (
FuzzyFilter = "Fuzzy"
)
// lineIDGenerator defines an interface for things that generate
// unique IDs for lines used within peco.
type lineIDGenerator interface {
@ -56,6 +61,11 @@ type idgen struct {
ch chan uint64
}
const (
OptionEnabled = "enabled"
OptionDisabled = "disabled"
)
// Peco is the global object containing everything required to run peco.
// It also contains the global state of the program.
type Peco struct {
@ -74,6 +84,7 @@ type Peco struct {
enableSep bool // Enable parsing on separators
filters FilterSet
idgen *idgen
enableFuzzy bool
initialFilter string
initialQuery string // populated if --query is specified
inputseq Inputseq // current key sequence (just the names)
@ -343,6 +354,7 @@ type Config struct {
Keymap map[string]string `json:"Keymap"`
Matcher string `json:"Matcher"` // Deprecated.
InitialMatcher string `json:"InitialMatcher"` // Use this instead of Matcher
FuzzyFilter string `json:"FuzzyFilter"`
InitialFilter string `json:"InitialFilter"`
Style StyleSet `json:"Style"`
Prompt string `json:"Prompt"`
@ -475,6 +487,7 @@ type CLIOptions struct {
OptEnableNullSep bool `long:"null" description:"expect NUL (\\0) as separator for target/output"`
OptInitialIndex int `long:"initial-index" description:"position of the initial index of the selection (0 base)"`
OptInitialMatcher string `long:"initial-matcher" description:"specify the default matcher (deprecated)"`
OptFuzzyFilter string `short:"z" long:"fuzzy-filter" description:"enable/disable the Fuzzy filter"`
OptInitialFilter string `long:"initial-filter" description:"specify the default filter"`
OptPrompt string `long:"prompt" description:"specify the prompt string"`
OptLayout string `long:"layout" description:"layout to be used 'top-down' or 'bottom-up'. default is 'top-down'"`

21
peco.go
View file

@ -500,6 +500,20 @@ func (p *Peco) ApplyConfig(opts CLIOptions) error {
p.bufferSize = opts.OptBufferSize
p.selectOneAndExit = opts.OptSelect1
p.initialQuery = opts.OptQuery
// Option EnableFuzzy is a string to allow overriding the value on the command line
fuzzyFilter := opts.OptFuzzyFilter
if len(fuzzyFilter) <= 0 {
fuzzyFilter = p.config.FuzzyFilter
}
if len(fuzzyFilter) > 0 {
if fuzzyFilter == OptionEnabled {
p.enableFuzzy = true
} else if fuzzyFilter == OptionDisabled {
p.enableFuzzy = false
} else {
return errors.Errorf("Unexpected value for FuzzyFilter option: %v (expected %v/%v)", fuzzyFilter, OptionEnabled, OptionDisabled)
}
}
p.initialFilter = opts.OptInitialFilter
if len(p.initialFilter) <= 0 {
p.initialFilter = p.config.InitialFilter
@ -507,6 +521,9 @@ func (p *Peco) ApplyConfig(opts CLIOptions) error {
if len(p.initialFilter) <= 0 {
p.initialFilter = opts.OptInitialMatcher
}
if len(p.initialFilter) > 0 && !p.enableFuzzy && p.initialFilter == FuzzyFilter {
return errors.New("Fuzzy filter is not enabled, can not set it to the initial filter.")
}
if err := p.populateCommandList(); err != nil {
return errors.Wrap(err, "failed to populate command list")
@ -566,7 +583,9 @@ func (p *Peco) populateFilters() error {
p.filters.Add(NewCaseSensitiveFilter())
p.filters.Add(NewSmartCaseFilter())
p.filters.Add(NewRegexpFilter())
p.filters.Add(NewFuzzyFilter())
if p.enableFuzzy {
p.filters.Add(NewFuzzyFilter())
}
for name, c := range p.config.CustomFilter {
f := NewExternalCmdFilter(name, c.Cmd, c.Args, c.BufferThreshold, p.idgen, p.enableSep)

View file

@ -197,6 +197,44 @@ func TestGHIssue331(t *testing.T) {
}
}
func TestConfigFuzzyFilter(t *testing.T) {
var opts CLIOptions
p := newPeco()
// Ensure that it's possible to enable the Fuzzy filter
opts.OptFuzzyFilter = "enabled"
if !assert.NoError(t, p.ApplyConfig(opts), "p.ApplyConfig should succeed") {
return
}
if !assert.Equal(t, true, p.enableFuzzy, "p.enableFuzzy should be equal to opts.OptEnableFuzzy") {
return
}
opts.OptFuzzyFilter = "abc"
if !assert.Error(t, p.ApplyConfig(opts), "p.ApplyConfig should not succeed") {
return
}
}
func TestConfigInitialFilterFuzzy(t *testing.T) {
var opts CLIOptions
p := newPeco()
// If Fuzzy is not enabled, initialFilter=Fuzzy should cause peco to fail
opts.OptFuzzyFilter = "disabled"
opts.OptInitialFilter = "Fuzzy"
if !assert.Error(t, p.ApplyConfig(opts), "p.ApplyConfig should not succeed") {
return
}
// If Fuzzy is enabled, it should be possible to set the initial filter to Fuzzy
opts.OptFuzzyFilter = "enabled"
if !assert.NoError(t, p.ApplyConfig(opts), "p.ApplyConfig should succeed") {
return
}
if !assert.Equal(t, true, p.enableFuzzy, "p.enableFuzzy should be equal to opts.OptEnableFuzzy") {
return
}
}
func TestApplyConfig(t *testing.T) {
// XXX We should add all the possible configurations that needs to be
// propagated to Peco from config