summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorJunegunn Choi <junegunn.c@gmail.com>2017-08-09 23:25:32 +0900
committerJunegunn Choi <junegunn.c@gmail.com>2017-08-09 23:28:47 +0900
commite85a8a68d0248a6edfb6ef63c5edb4bcbe18f954 (patch)
tree0831c315a1022f08eaa2331f58015fb5c61007c5 /src
parentdc55e68524a84544417c1ce290df447c4f0b1d60 (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.go41
-rw-r--r--src/pattern_test.go12
-rw-r--r--src/terminal.go10
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: