diff options
author | Junegunn Choi <junegunn.c@gmail.com> | 2017-08-09 23:25:32 +0900 |
---|---|---|
committer | Junegunn Choi <junegunn.c@gmail.com> | 2017-08-09 23:28:47 +0900 |
commit | e85a8a68d0248a6edfb6ef63c5edb4bcbe18f954 (patch) | |
tree | 0831c315a1022f08eaa2331f58015fb5c61007c5 /src | |
parent | dc55e68524a84544417c1ce290df447c4f0b1d60 (diff) |
Allow escaping meta characters with backslashes
One can escape meta characters in extended-search mode with backslashes.
Prefixes:
\'
\!
\^
Suffix:
\$
Term separator:
\<SPACE>
To keep things simple, we are not going to support escaping of escaped
sequences (e.g. \\') for matching them literally.
Since this is a breaking change, we will bump the minor version.
Close #444
Diffstat (limited to 'src')
-rw-r--r-- | src/pattern.go | 41 | ||||
-rw-r--r-- | src/pattern_test.go | 12 | ||||
-rw-r--r-- | src/terminal.go | 10 |
3 files changed, 41 insertions, 22 deletions
diff --git a/src/pattern.go b/src/pattern.go index 47cabf78..34329306 100644 --- a/src/pattern.go +++ b/src/pattern.go @@ -53,13 +53,15 @@ type Pattern struct { } var ( - _patternCache map[string]*Pattern - _splitRegex *regexp.Regexp - _cache ChunkCache + _patternCache map[string]*Pattern + _splitRegex *regexp.Regexp + _escapedPrefixRegex *regexp.Regexp + _cache ChunkCache ) func init() { - _splitRegex = regexp.MustCompile("\\s+") + _splitRegex = regexp.MustCompile(" +") + _escapedPrefixRegex = regexp.MustCompile("^\\\\['!^]") clearPatternCache() clearChunkCache() } @@ -80,7 +82,10 @@ func BuildPattern(fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case, var asString string if extended { - asString = strings.Trim(string(runes), " ") + asString = strings.TrimLeft(string(runes), " ") + for strings.HasSuffix(asString, " ") && !strings.HasSuffix(asString, "\\ ") { + asString = asString[:len(asString)-1] + } } else { asString = string(runes) } @@ -140,12 +145,13 @@ func BuildPattern(fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case, } func parseTerms(fuzzy bool, caseMode Case, normalize bool, str string) []termSet { + str = strings.Replace(str, "\\ ", "\t", -1) tokens := _splitRegex.Split(str, -1) sets := []termSet{} set := termSet{} switchSet := false for _, token := range tokens { - typ, inv, text := termFuzzy, false, token + typ, inv, text := termFuzzy, false, strings.Replace(token, "\t", " ", -1) lowerText := strings.ToLower(text) caseSensitive := caseMode == CaseRespect || caseMode == CaseSmart && text != lowerText @@ -167,6 +173,15 @@ func parseTerms(fuzzy bool, caseMode Case, normalize bool, str string) []termSet text = text[1:] } + if strings.HasSuffix(text, "$") { + if strings.HasSuffix(text, "\\$") { + text = text[:len(text)-2] + "$" + } else { + typ = termSuffix + text = text[:len(text)-1] + } + } + if strings.HasPrefix(text, "'") { // Flip exactness if fuzzy && !inv { @@ -177,16 +192,16 @@ func parseTerms(fuzzy bool, caseMode Case, normalize bool, str string) []termSet text = text[1:] } } else if strings.HasPrefix(text, "^") { - if strings.HasSuffix(text, "$") { + if typ == termSuffix { typ = termEqual - text = text[1 : len(text)-1] } else { typ = termPrefix - text = text[1:] } - } else if strings.HasSuffix(text, "$") { - typ = termSuffix - text = text[:len(text)-1] + text = text[1:] + } + + if _escapedPrefixRegex.MatchString(text) { + text = text[1:] } if len(text) > 0 { @@ -236,7 +251,7 @@ func (p *Pattern) CacheKey() string { cacheableTerms = append(cacheableTerms, string(termSet[0].text)) } } - return strings.Join(cacheableTerms, " ") + return strings.Join(cacheableTerms, "\t") } // Match returns the list of matches Items in the given Chunk diff --git a/src/pattern_test.go b/src/pattern_test.go index 9d56ff9f..efb1ef2d 100644 --- a/src/pattern_test.go +++ b/src/pattern_test.go @@ -165,15 +165,15 @@ func TestCacheKey(t *testing.T) { t.Errorf("Expected: %s, actual: %s", expected, pat.CacheKey()) } if pat.cacheable != cacheable { - t.Errorf("Expected: %s, actual: %s (%s)", cacheable, pat.cacheable, patStr) + t.Errorf("Expected: %t, actual: %t (%s)", cacheable, pat.cacheable, patStr) } clearPatternCache() } test(false, "foo !bar", "foo !bar", true) test(false, "foo | bar !baz", "foo | bar !baz", true) - test(true, "foo bar baz", "foo bar baz", true) + test(true, "foo bar baz", "foo\tbar\tbaz", true) test(true, "foo !bar", "foo", false) - test(true, "foo !bar baz", "foo baz", false) + test(true, "foo !bar baz", "foo\tbaz", false) test(true, "foo | bar baz", "baz", false) test(true, "foo | bar | baz", "", false) test(true, "foo | bar !baz", "", false) @@ -192,11 +192,11 @@ func TestCacheable(t *testing.T) { } clearPatternCache() } - test(true, "foo bar", "foo bar", true) - test(true, "foo 'bar", "foo bar", false) + test(true, "foo bar", "foo\tbar", true) + test(true, "foo 'bar", "foo\tbar", false) test(true, "foo !bar", "foo", false) - test(false, "foo bar", "foo bar", true) + test(false, "foo bar", "foo\tbar", true) test(false, "foo 'bar", "foo", false) test(false, "foo '", "foo", true) test(false, "foo 'bar", "foo", false) diff --git a/src/terminal.go b/src/terminal.go index 8c7c1a8d..5c66f44f 100644 --- a/src/terminal.go +++ b/src/terminal.go @@ -281,9 +281,13 @@ func defaultKeymap() map[int][]action { return keymap } +func trimQuery(query string) []rune { + return []rune(strings.Replace(query, "\t", " ", -1)) +} + // NewTerminal returns new Terminal object func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal { - input := []rune(opts.Query) + input := trimQuery(opts.Query) var header []string if opts.Reverse { header = opts.Header @@ -1694,13 +1698,13 @@ func (t *Terminal) Loop() { case actPreviousHistory: if t.history != nil { t.history.override(string(t.input)) - t.input = []rune(t.history.previous()) + t.input = trimQuery(t.history.previous()) t.cx = len(t.input) } case actNextHistory: if t.history != nil { t.history.override(string(t.input)) - t.input = []rune(t.history.next()) + t.input = trimQuery(t.history.next()) t.cx = len(t.input) } case actSigStop: |