Move some strings utilities to internal/strings

This commit is contained in:
c-bata 2018-12-09 16:35:08 +09:00
parent 24941a6503
commit 6916c5f66d
3 changed files with 162 additions and 91 deletions

View File

@ -5,6 +5,7 @@ import (
"unicode/utf8"
"github.com/c-bata/go-prompt/internal/bisect"
istrings "github.com/c-bata/go-prompt/internal/strings"
runewidth "github.com/mattn/go-runewidth"
)
@ -133,7 +134,7 @@ func (d *Document) FindStartOfPreviousWord() int {
// The only difference is to ignore contiguous spaces.
func (d *Document) FindStartOfPreviousWordWithSpace() int {
x := d.TextBeforeCursor()
end := lastIndexByteNot(x, ' ')
end := istrings.LastIndexNotByte(x, ' ')
if end == -1 {
return 0
}
@ -168,7 +169,7 @@ func (d *Document) FindStartOfPreviousWordUntilSeparatorIgnoreNextToCursor(sep s
}
x := d.TextBeforeCursor()
end := lastIndexAnyNot(x, sep)
end := istrings.LastIndexNotAny(x, sep)
if end == -1 {
return 0
}
@ -195,7 +196,7 @@ func (d *Document) FindEndOfCurrentWord() int {
func (d *Document) FindEndOfCurrentWordWithSpace() int {
x := d.TextAfterCursor()
start := indexByteNot(x, ' ')
start := istrings.IndexNotByte(x, ' ')
if start == -1 {
return len(x)
}
@ -232,7 +233,7 @@ func (d *Document) FindEndOfCurrentWordUntilSeparatorIgnoreNextToCursor(sep stri
x := d.TextAfterCursor()
start := indexAnyNot(x, sep)
start := istrings.IndexNotAny(x, sep)
if start == -1 {
return len(x)
}
@ -432,90 +433,3 @@ func (d *Document) leadingWhitespaceInCurrentLine() (margin string) {
margin = d.CurrentLine()[:len(d.CurrentLine())-len(trimmed)]
return
}
func indexByteNot(s string, c byte) int {
n := len(s)
for i := 0; i < n; i++ {
if s[i] != c {
return i
}
}
return -1
}
func lastIndexByteNot(s string, c byte) int {
for i := len(s) - 1; i >= 0; i-- {
if s[i] != c {
return i
}
}
return -1
}
type asciiSet [8]uint32
func (as *asciiSet) notContains(c byte) bool {
return (as[c>>5] & (1 << uint(c&31))) == 0
}
func makeASCIISet(chars string) (as asciiSet, ok bool) {
for i := 0; i < len(chars); i++ {
c := chars[i]
if c >= utf8.RuneSelf {
return as, false
}
as[c>>5] |= 1 << uint(c&31)
}
return as, true
}
func indexAnyNot(s, chars string) int {
if len(chars) > 0 {
if len(s) > 8 {
if as, isASCII := makeASCIISet(chars); isASCII {
for i := 0; i < len(s); i++ {
if as.notContains(s[i]) {
return i
}
}
return -1
}
}
for i := 0; i < len(s); {
// I don't know why strings.IndexAny doesn't add rune count here.
r, size := utf8.DecodeRuneInString(s[i:])
i += size
for _, c := range chars {
if r != c {
return i
}
}
}
}
return -1
}
func lastIndexAnyNot(s, chars string) int {
if len(chars) > 0 {
if len(s) > 8 {
if as, isASCII := makeASCIISet(chars); isASCII {
for i := len(s) - 1; i >= 0; i-- {
if as.notContains(s[i]) {
return i
}
}
return -1
}
}
for i := len(s); i > 0; {
r, size := utf8.DecodeLastRuneInString(s[:i])
i -= size
for _, c := range chars {
if r != c {
return i
}
}
}
}
return -1
}

110
internal/strings/strings.go Normal file
View File

@ -0,0 +1,110 @@
package strings
import "unicode/utf8"
// IndexNotByte is similar with strings.IndexByte but returns
// the index of the first instance of character except c in s.
// or -1 if s only contains c.
func IndexNotByte(s string, c byte) int {
n := len(s)
for i := 0; i < n; i++ {
if s[i] != c {
return i
}
}
return -1
}
// LastIndexByte is similar with strings.IndexByte but returns
// the index of the last instance of character except c in s,
// or -1 if s only contains c.
func LastIndexNotByte(s string, c byte) int {
for i := len(s) - 1; i >= 0; i-- {
if s[i] != c {
return i
}
}
return -1
}
type asciiSet [8]uint32
func (as *asciiSet) notContains(c byte) bool {
return (as[c>>5] & (1 << uint(c&31))) == 0
}
func makeASCIISet(chars string) (as asciiSet, ok bool) {
for i := 0; i < len(chars); i++ {
c := chars[i]
if c >= utf8.RuneSelf {
return as, false
}
as[c>>5] |= 1 << uint(c&31)
}
return as, true
}
// IndexNotAny is similar with strings.IndexAny but returns
// the index of the first instance of any Unicode code point from chars in s,
// or -1 if no Unicode code point from chars is present in s.
func IndexNotAny(s, chars string) int {
if len(chars) > 0 {
if len(s) > 8 {
if as, isASCII := makeASCIISet(chars); isASCII {
for i := 0; i < len(s); i++ {
if as.notContains(s[i]) {
return i
}
}
return -1
}
}
LabelFirstLoop:
for i, c := range s {
for j, m := range chars {
if c != m && j == len(chars)-1 {
return i
} else if c != m {
continue
} else {
continue LabelFirstLoop
}
}
}
}
return -1
}
// LastIndexAny returns the index of the last instance of any Unicode code
// point from chars in s, or -1 if no Unicode code point from chars is
// present in s.
func LastIndexNotAny(s, chars string) int {
if len(chars) > 0 {
if len(s) > 8 {
if as, isASCII := makeASCIISet(chars); isASCII {
for i := len(s) - 1; i >= 0; i-- {
if as.notContains(s[i]) {
return i
}
}
return -1
}
}
LabelFirstLoop:
for i := len(s); i > 0; {
r, size := utf8.DecodeLastRuneInString(s[:i])
i -= size
for j, m := range chars {
if r != m && j == len(chars)-1 {
return i
} else if r != m {
continue
} else {
continue LabelFirstLoop
}
}
}
}
return -1
}

View File

@ -0,0 +1,47 @@
package strings_test
import (
"fmt"
"github.com/c-bata/go-prompt/internal/strings"
)
func ExampleIndexNotByte() {
fmt.Println(strings.IndexNotByte("golang", 'g'))
fmt.Println(strings.IndexNotByte("golang", 'x'))
fmt.Println(strings.IndexNotByte("gggggg", 'g'))
// Output:
// 1
// 0
// -1
}
func ExampleLastIndexNotByte() {
fmt.Println(strings.LastIndexNotByte("golang", 'g'))
fmt.Println(strings.LastIndexNotByte("golang", 'x'))
fmt.Println(strings.LastIndexNotByte("gggggg", 'g'))
// Output:
// 4
// 5
// -1
}
func ExampleIndexNotAny() {
fmt.Println(strings.IndexNotAny("golang", "glo"))
fmt.Println(strings.IndexNotAny("golang", "gl"))
fmt.Println(strings.IndexNotAny("golang", "golang"))
// Output:
// 3
// 1
// -1
}
func ExampleLastIndexNotAny() {
fmt.Println(strings.LastIndexNotAny("golang", "agn"))
fmt.Println(strings.LastIndexNotAny("golang", "an"))
fmt.Println(strings.LastIndexNotAny("golang", "golang"))
// Output:
// 2
// 5
// -1
}