diff --git a/example/readline-demo/readline-demo.go b/example/readline-demo/readline-demo.go index d80e59e..e6c0a53 100644 --- a/example/readline-demo/readline-demo.go +++ b/example/readline-demo/readline-demo.go @@ -68,6 +68,8 @@ func main() { AutoComplete: completer, InterruptPrompt: "^C", EOFPrompt: "exit", + + HistorySearchFold: true, }) if err != nil { panic(err) diff --git a/history.go b/history.go index 042f2f5..b154aed 100644 --- a/history.go +++ b/history.go @@ -153,7 +153,7 @@ func (o *opHistory) FindBck(isNewSearch bool, rs []rune, start int) (int, *list. item = item[:start] } } - idx := runes.IndexAllBck(item, rs) + idx := runes.IndexAllBckEx(item, rs, o.cfg.HistorySearchFold) if idx < 0 { continue } @@ -178,7 +178,7 @@ func (o *opHistory) FindFwd(isNewSearch bool, rs []rune, start int) (int, *list. continue } } - idx := runes.IndexAll(item, rs) + idx := runes.IndexAllEx(item, rs, o.cfg.HistorySearchFold) if idx < 0 { continue } diff --git a/readline.go b/readline.go index 1e67f51..1e232fb 100644 --- a/readline.go +++ b/readline.go @@ -34,6 +34,8 @@ type Config struct { // specify the max length of historys, it's 500 by default, set it to -1 to disable history HistoryLimit int DisableAutoSaveHistory bool + // enable case-insensitive history searching + HistorySearchFold bool // AutoCompleter will called once user press TAB AutoComplete AutoCompleter diff --git a/runes.go b/runes.go index 8ed54ce..a669bc4 100644 --- a/runes.go +++ b/runes.go @@ -3,6 +3,7 @@ package readline import ( "bytes" "unicode" + "unicode/utf8" ) var runes = Runes{} @@ -10,6 +11,42 @@ var TabWidth = 4 type Runes struct{} +func (Runes) EqualRune(a, b rune, fold bool) bool { + if a == b { + return true + } + if !fold { + return false + } + if a > b { + a, b = b, a + } + if b < utf8.RuneSelf && 'A' <= a && a <= 'Z' { + if b == a+'a'-'A' { + return true + } + } + return false +} + +func (r Runes) EqualRuneFold(a, b rune) bool { + return r.EqualRune(a, b, true) +} + +func (r Runes) EqualFold(a, b []rune) bool { + if len(a) != len(b) { + return false + } + for i := 0; i < len(a); i++ { + if r.EqualRuneFold(a[i], b[i]) { + continue + } + return false + } + + return true +} + func (Runes) Equal(a, b []rune) bool { if len(a) != len(b) { return false @@ -22,12 +59,11 @@ func (Runes) Equal(a, b []rune) bool { return true } -// Search in runes from end to front -func (Runes) IndexAllBck(r, sub []rune) int { +func (rs Runes) IndexAllBckEx(r, sub []rune, fold bool) int { for i := len(r) - len(sub); i >= 0; i-- { found := true for j := 0; j < len(sub); j++ { - if r[i+j] != sub[j] { + if !rs.EqualRune(r[i+j], sub[j], fold) { found = false break } @@ -39,15 +75,24 @@ func (Runes) IndexAllBck(r, sub []rune) int { return -1 } +// Search in runes from end to front +func (rs Runes) IndexAllBck(r, sub []rune) int { + return rs.IndexAllBckEx(r, sub, false) +} + // Search in runes from front to end -func (Runes) IndexAll(r, sub []rune) int { +func (rs Runes) IndexAll(r, sub []rune) int { + return rs.IndexAllEx(r, sub, false) +} + +func (rs Runes) IndexAllEx(r, sub []rune, fold bool) int { for i := 0; i < len(r); i++ { found := true if len(r[i:]) < len(sub) { return -1 } for j := 0; j < len(sub); j++ { - if r[i+j] != sub[j] { + if !rs.EqualRune(r[i+j], sub[j], fold) { found = false break } @@ -128,6 +173,13 @@ func (Runes) Copy(r []rune) []rune { return n } +func (Runes) HasPrefixFold(r, prefix []rune) bool { + if len(r) < len(prefix) { + return false + } + return runes.EqualFold(r[:len(prefix)], prefix) +} + func (Runes) HasPrefix(r, prefix []rune) bool { if len(r) < len(prefix) { return false