Added split package to help with completions
This commit is contained in:
parent
b6eca8eafa
commit
66dd2b1b11
|
@ -0,0 +1,118 @@
|
|||
// Package split provides a simple string splitting utility for use with
|
||||
// CompleteMessage.
|
||||
package split
|
||||
|
||||
import (
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
var asciiSpace = [256]uint8{'\t': 1, '\n': 1, '\v': 1, '\f': 1, '\r': 1, ' ': 1}
|
||||
|
||||
// SpaceIndexed returns a splitted string with the current index that
|
||||
// CompleteMessage wants. The text is the entire input string and the offset is
|
||||
// where the cursor currently is.
|
||||
func SpaceIndexed(text string, offset int) ([]string, int) {
|
||||
// First count the fields.
|
||||
// This is an exact count if s is ASCII, otherwise it is an approximation.
|
||||
n := 0
|
||||
wasSpace := 1
|
||||
// setBits is used to track which bits are set in the bytes of s.
|
||||
setBits := uint8(0)
|
||||
for i := 0; i < len(text); i++ {
|
||||
r := text[i]
|
||||
setBits |= r
|
||||
isSpace := int(asciiSpace[r])
|
||||
n += wasSpace & ^isSpace
|
||||
wasSpace = isSpace
|
||||
}
|
||||
|
||||
if setBits >= utf8.RuneSelf {
|
||||
// Some runes in the input string are not ASCII.
|
||||
return spaceIndexedRunes([]rune(text), offset)
|
||||
}
|
||||
|
||||
// ASCII fast path
|
||||
a := make([]string, n)
|
||||
na := 0
|
||||
fieldStart := 0
|
||||
i := 0
|
||||
j := n - 1 // last by default
|
||||
|
||||
// Skip spaces in the front of the input.
|
||||
for i < len(text) && asciiSpace[text[i]] != 0 {
|
||||
i++
|
||||
}
|
||||
|
||||
fieldStart = i
|
||||
|
||||
for i < len(text) {
|
||||
if asciiSpace[text[i]] == 0 {
|
||||
i++
|
||||
continue
|
||||
}
|
||||
|
||||
a[na] = text[fieldStart:i]
|
||||
if fieldStart <= offset && offset <= i {
|
||||
j = na
|
||||
}
|
||||
|
||||
na++
|
||||
i++
|
||||
|
||||
// Skip spaces in between fields.
|
||||
for i < len(text) && asciiSpace[text[i]] != 0 {
|
||||
i++
|
||||
}
|
||||
fieldStart = i
|
||||
}
|
||||
if fieldStart < len(text) { // Last field might end at EOF.
|
||||
a[na] = text[fieldStart:]
|
||||
}
|
||||
|
||||
return a, j
|
||||
}
|
||||
|
||||
func spaceIndexedRunes(runes []rune, offset int) ([]string, int) {
|
||||
// A span is used to record a slice of s of the form s[start:end].
|
||||
// The start index is inclusive and the end index is exclusive.
|
||||
type span struct{ start, end int }
|
||||
|
||||
spans := make([]span, 0, 16)
|
||||
|
||||
// Find the field start and end indices.
|
||||
wasField := false
|
||||
fromIndex := 0
|
||||
for i, rune := range runes {
|
||||
if unicode.IsSpace(rune) {
|
||||
if wasField {
|
||||
spans = append(spans, span{start: fromIndex, end: i})
|
||||
wasField = false
|
||||
}
|
||||
} else {
|
||||
if !wasField {
|
||||
fromIndex = i
|
||||
wasField = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Last field might end at EOF.
|
||||
if wasField {
|
||||
spans = append(spans, span{fromIndex, len(runes)})
|
||||
}
|
||||
|
||||
// Create strings from recorded field indices.
|
||||
a := make([]string, 0, len(spans))
|
||||
j := len(spans) - 1 // assume last
|
||||
|
||||
for i, span := range spans {
|
||||
a = append(a, string(runes[span.start:span.end]))
|
||||
|
||||
if span.start <= offset && offset <= span.end {
|
||||
j = i
|
||||
}
|
||||
}
|
||||
|
||||
return a, j
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
package split
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestSpaceIndexed(t *testing.T) {
|
||||
var tests = []struct {
|
||||
input string
|
||||
offset int
|
||||
output []string
|
||||
index int
|
||||
}{{
|
||||
input: "bruhemus momentus lorem ipsum",
|
||||
offset: 13, // ^
|
||||
output: []string{"bruhemus", "momentus", "lorem", "ipsum"},
|
||||
index: 1,
|
||||
}, {
|
||||
input: "Yoohoo! My name's Astolfo! I belong to the Rider-class! And, and... uhm, nice " +
|
||||
"to meet you!",
|
||||
offset: 37, // ^
|
||||
output: []string{
|
||||
"Yoohoo!", "My", "name's", "Astolfo!", "I", "belong", "to", "the", "Rider-class!",
|
||||
"And,", "and...", "uhm,", "nice", "to", "meet", "you!"},
|
||||
index: 6,
|
||||
}, {
|
||||
input: "sorry, what were you typing?",
|
||||
offset: len("sorry, what were you typing?") - 1,
|
||||
output: []string{"sorry,", "what", "were", "you", "typing?"},
|
||||
index: 4,
|
||||
}, {
|
||||
input: "zeroed out input",
|
||||
offset: 0,
|
||||
output: []string{"zeroed", "out", "input"},
|
||||
index: 0,
|
||||
}, {
|
||||
input: "に ほ ん ご",
|
||||
offset: 3,
|
||||
output: []string{"に", "ほ", "ん", "ご"},
|
||||
index: 1,
|
||||
}}
|
||||
|
||||
for _, test := range tests {
|
||||
a, j := SpaceIndexed(test.input, test.offset)
|
||||
if !strsleq(a, test.output) {
|
||||
t.Error("Mismatch output (input/got/expected)", test.input, a, test.output)
|
||||
}
|
||||
if j != test.index {
|
||||
t.Error("Mismatch index (input/got/expected)", test.input, j, test.index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const benchstr = "Alright, Master! I'm your blade, your edge and your arrow! You've placed " +
|
||||
"so much trust in me, despite how weak I am - I'll do everything in my power to not " +
|
||||
"disappoint you!"
|
||||
const benchcursor = 32 // arbitrary
|
||||
|
||||
func BenchmarkSpaceIndexed(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
SpaceIndexed(benchstr, benchcursor)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSpaceIndexedLong(b *testing.B) {
|
||||
const benchstr = benchstr + benchstr + benchstr + benchstr + benchstr + benchstr
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
SpaceIndexed(benchstr, benchcursor)
|
||||
}
|
||||
}
|
||||
|
||||
// same as benchstr but w/ a horizontal line (outside ascii)
|
||||
const benchstr8 = "Alright, Master! I'm your blade, your edge and your arrow! You've placed " +
|
||||
"so much trust in me, despite how weak I am ― I'll do everything in my power to not " +
|
||||
"disappoint you!"
|
||||
|
||||
func BenchmarkSpaceIndexedUTF8(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
SpaceIndexed(benchstr8, benchcursor)
|
||||
}
|
||||
}
|
||||
|
||||
func strsleq(s1, s2 []string) bool {
|
||||
if len(s1) != len(s2) {
|
||||
return false
|
||||
}
|
||||
for i := 0; i < len(s1); i++ {
|
||||
if s1[i] != s2[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
Loading…
Reference in New Issue