Merge pull request #113 from c-bata/refactor-documents
Refactor a Document component
This commit is contained in:
commit
fe8fa91b20
8
Makefile
8
Makefile
@ -21,8 +21,12 @@ lint: ## Run golint and go vet.
|
|||||||
@go vet .
|
@go vet .
|
||||||
|
|
||||||
.PHONY: test
|
.PHONY: test
|
||||||
test: ## Run the tests with race condition checking.
|
test: ## Run tests with race condition checking.
|
||||||
@go test -race .
|
@go test -race ./...
|
||||||
|
|
||||||
|
.PHONY: bench
|
||||||
|
bench: ## Run benchmarks.
|
||||||
|
@go test -bench=. -run=- -benchmem ./...
|
||||||
|
|
||||||
.PHONY: coverage
|
.PHONY: coverage
|
||||||
cover: ## Run the tests.
|
cover: ## Run the tests.
|
||||||
|
112
document.go
112
document.go
@ -1,10 +1,11 @@
|
|||||||
package prompt
|
package prompt
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sort"
|
|
||||||
"strings"
|
"strings"
|
||||||
"unicode/utf8"
|
"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"
|
runewidth "github.com/mattn/go-runewidth"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -133,7 +134,7 @@ func (d *Document) FindStartOfPreviousWord() int {
|
|||||||
// The only difference is to ignore contiguous spaces.
|
// The only difference is to ignore contiguous spaces.
|
||||||
func (d *Document) FindStartOfPreviousWordWithSpace() int {
|
func (d *Document) FindStartOfPreviousWordWithSpace() int {
|
||||||
x := d.TextBeforeCursor()
|
x := d.TextBeforeCursor()
|
||||||
end := lastIndexByteNot(x, ' ')
|
end := istrings.LastIndexNotByte(x, ' ')
|
||||||
if end == -1 {
|
if end == -1 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@ -168,7 +169,7 @@ func (d *Document) FindStartOfPreviousWordUntilSeparatorIgnoreNextToCursor(sep s
|
|||||||
}
|
}
|
||||||
|
|
||||||
x := d.TextBeforeCursor()
|
x := d.TextBeforeCursor()
|
||||||
end := lastIndexAnyNot(x, sep)
|
end := istrings.LastIndexNotAny(x, sep)
|
||||||
if end == -1 {
|
if end == -1 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@ -195,7 +196,7 @@ func (d *Document) FindEndOfCurrentWord() int {
|
|||||||
func (d *Document) FindEndOfCurrentWordWithSpace() int {
|
func (d *Document) FindEndOfCurrentWordWithSpace() int {
|
||||||
x := d.TextAfterCursor()
|
x := d.TextAfterCursor()
|
||||||
|
|
||||||
start := indexByteNot(x, ' ')
|
start := istrings.IndexNotByte(x, ' ')
|
||||||
if start == -1 {
|
if start == -1 {
|
||||||
return len(x)
|
return len(x)
|
||||||
}
|
}
|
||||||
@ -232,7 +233,7 @@ func (d *Document) FindEndOfCurrentWordUntilSeparatorIgnoreNextToCursor(sep stri
|
|||||||
|
|
||||||
x := d.TextAfterCursor()
|
x := d.TextAfterCursor()
|
||||||
|
|
||||||
start := indexAnyNot(x, sep)
|
start := istrings.IndexNotAny(x, sep)
|
||||||
if start == -1 {
|
if start == -1 {
|
||||||
return len(x)
|
return len(x)
|
||||||
}
|
}
|
||||||
@ -292,7 +293,7 @@ func (d *Document) lineStartIndexes() []int {
|
|||||||
// the first character on that line.
|
// the first character on that line.
|
||||||
func (d *Document) findLineStartIndex(index int) (pos int, lineStartIndex int) {
|
func (d *Document) findLineStartIndex(index int) (pos int, lineStartIndex int) {
|
||||||
indexes := d.lineStartIndexes()
|
indexes := d.lineStartIndexes()
|
||||||
pos = bisectRight(indexes, index) - 1
|
pos = bisect.Right(indexes, index) - 1
|
||||||
lineStartIndex = indexes[pos]
|
lineStartIndex = indexes[pos]
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -432,102 +433,3 @@ func (d *Document) leadingWhitespaceInCurrentLine() (margin string) {
|
|||||||
margin = d.CurrentLine()[:len(d.CurrentLine())-len(trimmed)]
|
margin = d.CurrentLine()[:len(d.CurrentLine())-len(trimmed)]
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// bisectRight to Locate the insertion point for v in a to maintain sorted order.
|
|
||||||
func bisectRight(a []int, v int) int {
|
|
||||||
return bisectRightRange(a, v, 0, len(a))
|
|
||||||
}
|
|
||||||
|
|
||||||
func bisectRightRange(a []int, v int, lo, hi int) int {
|
|
||||||
s := a[lo:hi]
|
|
||||||
return sort.Search(len(s), func(i int) bool {
|
|
||||||
return s[i] > v
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
228
document_test.go
228
document_test.go
@ -1,11 +1,224 @@
|
|||||||
package prompt
|
package prompt
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func ExampleDocument_CurrentLine() {
|
||||||
|
d := &Document{
|
||||||
|
Text: `Hello! my name is c-bata.
|
||||||
|
This is a example of Document component.
|
||||||
|
This component has texts displayed in terminal and cursor position.
|
||||||
|
`,
|
||||||
|
cursorPosition: len(`Hello! my name is c-bata.
|
||||||
|
This is a exam`),
|
||||||
|
}
|
||||||
|
fmt.Println(d.CurrentLine())
|
||||||
|
// Output:
|
||||||
|
// This is a example of Document component.
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleDocument_DisplayCursorPosition() {
|
||||||
|
d := &Document{
|
||||||
|
Text: `Hello! my name is c-bata.`,
|
||||||
|
cursorPosition: len(`Hello`),
|
||||||
|
}
|
||||||
|
fmt.Println("DisplayCursorPosition", d.DisplayCursorPosition())
|
||||||
|
// Output:
|
||||||
|
// DisplayCursorPosition 5
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleDocument_CursorPositionRow() {
|
||||||
|
d := &Document{
|
||||||
|
Text: `Hello! my name is c-bata.
|
||||||
|
This is a example of Document component.
|
||||||
|
This component has texts displayed in terminal and cursor position.
|
||||||
|
`,
|
||||||
|
cursorPosition: len(`Hello! my name is c-bata.
|
||||||
|
This is a exam`),
|
||||||
|
}
|
||||||
|
fmt.Println("CursorPositionRow", d.CursorPositionRow())
|
||||||
|
// Output:
|
||||||
|
// CursorPositionRow 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleDocument_CursorPositionCol() {
|
||||||
|
d := &Document{
|
||||||
|
Text: `Hello! my name is c-bata.
|
||||||
|
This is a example of Document component.
|
||||||
|
This component has texts displayed in terminal and cursor position.
|
||||||
|
`,
|
||||||
|
cursorPosition: len(`Hello! my name is c-bata.
|
||||||
|
This is a exam`),
|
||||||
|
}
|
||||||
|
fmt.Println("CursorPositionCol", d.CursorPositionCol())
|
||||||
|
// Output:
|
||||||
|
// CursorPositionCol 14
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleDocument_TextBeforeCursor() {
|
||||||
|
d := &Document{
|
||||||
|
Text: `Hello! my name is c-bata.
|
||||||
|
This is a example of Document component.
|
||||||
|
This component has texts displayed in terminal and cursor position.
|
||||||
|
`,
|
||||||
|
cursorPosition: len(`Hello! my name is c-bata.
|
||||||
|
This is a exam`),
|
||||||
|
}
|
||||||
|
fmt.Println(d.TextBeforeCursor())
|
||||||
|
// Output:
|
||||||
|
// Hello! my name is c-bata.
|
||||||
|
// This is a exam
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleDocument_TextAfterCursor() {
|
||||||
|
d := &Document{
|
||||||
|
Text: `Hello! my name is c-bata.
|
||||||
|
This is a example of Document component.
|
||||||
|
This component has texts displayed in terminal and cursor position.
|
||||||
|
`,
|
||||||
|
cursorPosition: len(`Hello! my name is c-bata.
|
||||||
|
This is a exam`),
|
||||||
|
}
|
||||||
|
fmt.Println(d.TextAfterCursor())
|
||||||
|
// Output:
|
||||||
|
// ple of Document component.
|
||||||
|
// This component has texts displayed in terminal and cursor position.
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleDocument_DisplayCursorPosition_withJapanese() {
|
||||||
|
d := &Document{
|
||||||
|
Text: `こんにちは、芝田 将です。`,
|
||||||
|
cursorPosition: 3,
|
||||||
|
}
|
||||||
|
fmt.Println("DisplayCursorPosition", d.DisplayCursorPosition())
|
||||||
|
// Output:
|
||||||
|
// DisplayCursorPosition 6
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleDocument_CurrentLineBeforeCursor() {
|
||||||
|
d := &Document{
|
||||||
|
Text: `Hello! my name is c-bata.
|
||||||
|
This is a example of Document component.
|
||||||
|
This component has texts displayed in terminal and cursor position.
|
||||||
|
`,
|
||||||
|
cursorPosition: len(`Hello! my name is c-bata.
|
||||||
|
This is a exam`),
|
||||||
|
}
|
||||||
|
fmt.Println(d.CurrentLineBeforeCursor())
|
||||||
|
// Output:
|
||||||
|
// This is a exam
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleDocument_CurrentLineAfterCursor() {
|
||||||
|
d := &Document{
|
||||||
|
Text: `Hello! my name is c-bata.
|
||||||
|
This is a example of Document component.
|
||||||
|
This component has texts displayed in terminal and cursor position.
|
||||||
|
`,
|
||||||
|
cursorPosition: len(`Hello! my name is c-bata.
|
||||||
|
This is a exam`),
|
||||||
|
}
|
||||||
|
fmt.Println(d.CurrentLineAfterCursor())
|
||||||
|
// Output:
|
||||||
|
// ple of Document component.
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleDocument_GetWordBeforeCursor() {
|
||||||
|
d := &Document{
|
||||||
|
Text: `Hello! my name is c-bata.
|
||||||
|
This is a example of Document component.
|
||||||
|
`,
|
||||||
|
cursorPosition: len(`Hello! my name is c-bata.
|
||||||
|
This is a exam`),
|
||||||
|
}
|
||||||
|
fmt.Println(d.GetWordBeforeCursor())
|
||||||
|
// Output:
|
||||||
|
// exam
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleDocument_GetWordAfterCursor() {
|
||||||
|
d := &Document{
|
||||||
|
Text: `Hello! my name is c-bata.
|
||||||
|
This is a example of Document component.
|
||||||
|
`,
|
||||||
|
cursorPosition: len(`Hello! my name is c-bata.
|
||||||
|
This is a exam`),
|
||||||
|
}
|
||||||
|
fmt.Println(d.GetWordAfterCursor())
|
||||||
|
// Output:
|
||||||
|
// ple
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleDocument_GetWordBeforeCursorWithSpace() {
|
||||||
|
d := &Document{
|
||||||
|
Text: `Hello! my name is c-bata.
|
||||||
|
This is a example of Document component.
|
||||||
|
`,
|
||||||
|
cursorPosition: len(`Hello! my name is c-bata.
|
||||||
|
This is a example `),
|
||||||
|
}
|
||||||
|
fmt.Println(d.GetWordBeforeCursorWithSpace())
|
||||||
|
// Output:
|
||||||
|
// example
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleDocument_GetWordAfterCursorWithSpace() {
|
||||||
|
d := &Document{
|
||||||
|
Text: `Hello! my name is c-bata.
|
||||||
|
This is a example of Document component.
|
||||||
|
`,
|
||||||
|
cursorPosition: len(`Hello! my name is c-bata.
|
||||||
|
This is a`),
|
||||||
|
}
|
||||||
|
fmt.Println(d.GetWordAfterCursorWithSpace())
|
||||||
|
// Output:
|
||||||
|
// example
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleDocument_GetWordBeforeCursorUntilSeparator() {
|
||||||
|
d := &Document{
|
||||||
|
Text: `hello,i am c-bata`,
|
||||||
|
cursorPosition: len(`hello,i am c`),
|
||||||
|
}
|
||||||
|
fmt.Println(d.GetWordBeforeCursorUntilSeparator(","))
|
||||||
|
// Output:
|
||||||
|
// i am c
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleDocument_GetWordAfterCursorUntilSeparator() {
|
||||||
|
d := &Document{
|
||||||
|
Text: `hello,i am c-bata,thank you for using go-prompt`,
|
||||||
|
cursorPosition: len(`hello,i a`),
|
||||||
|
}
|
||||||
|
fmt.Println(d.GetWordAfterCursorUntilSeparator(","))
|
||||||
|
// Output:
|
||||||
|
// m c-bata
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleDocument_GetWordBeforeCursorUntilSeparatorIgnoreNextToCursor() {
|
||||||
|
d := &Document{
|
||||||
|
Text: `hello,i am c-bata,thank you for using go-prompt`,
|
||||||
|
cursorPosition: len(`hello,i am c-bata,`),
|
||||||
|
}
|
||||||
|
fmt.Println(d.GetWordBeforeCursorUntilSeparatorIgnoreNextToCursor(","))
|
||||||
|
// Output:
|
||||||
|
// i am c-bata,
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleDocument_GetWordAfterCursorUntilSeparatorIgnoreNextToCursor() {
|
||||||
|
d := &Document{
|
||||||
|
Text: `hello,i am c-bata,thank you for using go-prompt`,
|
||||||
|
cursorPosition: len(`hello`),
|
||||||
|
}
|
||||||
|
fmt.Println(d.GetWordAfterCursorUntilSeparatorIgnoreNextToCursor(","))
|
||||||
|
// Output:
|
||||||
|
// ,i am c-bata
|
||||||
|
}
|
||||||
|
|
||||||
func TestDocument_DisplayCursorPosition(t *testing.T) {
|
func TestDocument_DisplayCursorPosition(t *testing.T) {
|
||||||
patterns := []struct {
|
patterns := []struct {
|
||||||
document *Document
|
document *Document
|
||||||
@ -1040,18 +1253,3 @@ func TestDocument_GetEndOfLinePosition(t *testing.T) {
|
|||||||
t.Errorf("Should be %#v, got %#v", ex, ac)
|
t.Errorf("Should be %#v, got %#v", ex, ac)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBisectRight(t *testing.T) {
|
|
||||||
// Thanks!! https://play.golang.org/p/y9NRj_XVIW
|
|
||||||
in := []int{1, 2, 3, 3, 3, 6, 7}
|
|
||||||
|
|
||||||
r := bisectRight(in, 0)
|
|
||||||
if r != 0 {
|
|
||||||
t.Errorf("number 0 should inserted at 0 position, but got %d", r)
|
|
||||||
}
|
|
||||||
|
|
||||||
r = bisectRight(in, 4)
|
|
||||||
if r != 5 {
|
|
||||||
t.Errorf("number 4 should inserted at 5 position, but got %d", r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
15
internal/bisect/bisect.go
Normal file
15
internal/bisect/bisect.go
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package bisect
|
||||||
|
|
||||||
|
import "sort"
|
||||||
|
|
||||||
|
// Right to locate the insertion point for v in a to maintain sorted order.
|
||||||
|
func Right(a []int, v int) int {
|
||||||
|
return bisectRightRange(a, v, 0, len(a))
|
||||||
|
}
|
||||||
|
|
||||||
|
func bisectRightRange(a []int, v int, lo, hi int) int {
|
||||||
|
s := a[lo:hi]
|
||||||
|
return sort.Search(len(s), func(i int) bool {
|
||||||
|
return s[i] > v
|
||||||
|
})
|
||||||
|
}
|
50
internal/bisect/bisect_test.go
Normal file
50
internal/bisect/bisect_test.go
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
package bisect_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/c-bata/go-prompt/internal/bisect"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Example() {
|
||||||
|
in := []int{1, 2, 3, 3, 3, 6, 7}
|
||||||
|
fmt.Println("Insertion position for 0 in the slice is", bisect.Right(in, 0))
|
||||||
|
fmt.Println("Insertion position for 4 in the slice is", bisect.Right(in, 4))
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// Insertion position for 0 in the slice is 0
|
||||||
|
// Insertion position for 4 in the slice is 5
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBisectRight(t *testing.T) {
|
||||||
|
// Thanks!! https://play.golang.org/p/y9NRj_XVIW
|
||||||
|
in := []int{1, 2, 3, 3, 3, 6, 7}
|
||||||
|
|
||||||
|
r := bisect.Right(in, 0)
|
||||||
|
if r != 0 {
|
||||||
|
t.Errorf("number 0 should inserted at 0 position, but got %d", r)
|
||||||
|
}
|
||||||
|
|
||||||
|
r = bisect.Right(in, 4)
|
||||||
|
if r != 5 {
|
||||||
|
t.Errorf("number 4 should inserted at 5 position, but got %d", r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkRight(b *testing.B) {
|
||||||
|
rand.Seed(0)
|
||||||
|
|
||||||
|
for _, l := range []int{10, 1e2, 1e3, 1e4} {
|
||||||
|
x := rand.Perm(l)
|
||||||
|
insertion := rand.Int()
|
||||||
|
|
||||||
|
b.Run(fmt.Sprintf("arrayLength=%d", l), func(b *testing.B) {
|
||||||
|
b.ResetTimer()
|
||||||
|
for n := 0; n < b.N; n++ {
|
||||||
|
bisect.Right(x, insertion)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
110
internal/strings/strings.go
Normal file
110
internal/strings/strings.go
Normal 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
|
||||||
|
}
|
47
internal/strings/strings_test.go
Normal file
47
internal/strings/strings_test.go
Normal 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
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user