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 .
|
||||
|
||||
.PHONY: test
|
||||
test: ## Run the tests with race condition checking.
|
||||
@go test -race .
|
||||
test: ## Run tests with race condition checking.
|
||||
@go test -race ./...
|
||||
|
||||
.PHONY: bench
|
||||
bench: ## Run benchmarks.
|
||||
@go test -bench=. -run=- -benchmem ./...
|
||||
|
||||
.PHONY: coverage
|
||||
cover: ## Run the tests.
|
||||
|
112
document.go
112
document.go
@ -1,10 +1,11 @@
|
||||
package prompt
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
"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)
|
||||
}
|
||||
@ -292,7 +293,7 @@ func (d *Document) lineStartIndexes() []int {
|
||||
// the first character on that line.
|
||||
func (d *Document) findLineStartIndex(index int) (pos int, lineStartIndex int) {
|
||||
indexes := d.lineStartIndexes()
|
||||
pos = bisectRight(indexes, index) - 1
|
||||
pos = bisect.Right(indexes, index) - 1
|
||||
lineStartIndex = indexes[pos]
|
||||
return
|
||||
}
|
||||
@ -432,102 +433,3 @@ func (d *Document) leadingWhitespaceInCurrentLine() (margin string) {
|
||||
margin = d.CurrentLine()[:len(d.CurrentLine())-len(trimmed)]
|
||||
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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
"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) {
|
||||
patterns := []struct {
|
||||
document *Document
|
||||
@ -1040,18 +1253,3 @@ func TestDocument_GetEndOfLinePosition(t *testing.T) {
|
||||
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