diff --git a/go.mod b/go.mod index d925bb79..735c4790 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.18.2 github.com/ulikunitz/xz v0.5.11 - golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc + golang.org/x/exp v0.0.0-20240110193028-0dcbfd608b1e golang.org/x/sync v0.6.0 golang.org/x/term v0.16.0 ) diff --git a/go.sum b/go.sum index 381e0289..ab8e941c 100644 --- a/go.sum +++ b/go.sum @@ -87,8 +87,8 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc h1:ao2WRsKSzW6KuUY9IWPwWahcHCgR0s52IfwutMfEbdM= -golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= +golang.org/x/exp v0.0.0-20240110193028-0dcbfd608b1e h1:723BNChdd0c2Wk6WOE320qGBiPtYx0F0Bbm1kriShfE= +golang.org/x/exp v0.0.0-20240110193028-0dcbfd608b1e/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= diff --git a/oviewer/action.go b/oviewer/action.go index 6d550d80..8fc6ee59 100644 --- a/oviewer/action.go +++ b/oviewer/action.go @@ -17,10 +17,12 @@ import ( func (root *Root) toggleWrapMode() { m := root.Doc m.WrapMode = !m.WrapMode + // Move cursor to correct position - x, err := root.Doc.optimalX(m.columnCursor) + x, err := m.optimalX(m.columnCursor) if err != nil { root.setMessageLog(err.Error()) + return } // Move if off screen if x < m.x || x > m.x+(root.scr.vWidth-root.scr.startX) { diff --git a/oviewer/input.go b/oviewer/input.go index 6d0fb8be..182477db 100644 --- a/oviewer/input.go +++ b/oviewer/input.go @@ -115,10 +115,10 @@ func (input *Input) keyEvent(evKey *tcell.EventKey) bool { if input.cursorX <= 0 { return false } - pos := stringWidth(input.value, input.cursorX) + pos := countToCursor(input.value, input.cursorX) runes := []rune(input.value) input.value = string(runes[:pos]) - input.cursorX = runeWidth(input.value) + input.cursorX = stringWidth(input.value) next := pos + 1 for ; next < len(runes); next++ { if runewidth.RuneWidth(runes[next]) != 0 { @@ -127,7 +127,7 @@ func (input *Input) keyEvent(evKey *tcell.EventKey) bool { } input.value += string(runes[next:]) case tcell.KeyDelete: - pos := stringWidth(input.value, input.cursorX) + pos := countToCursor(input.value, input.cursorX) runes := []rune(input.value) dp := 1 if input.cursorX == 0 { @@ -147,27 +147,27 @@ func (input *Input) keyEvent(evKey *tcell.EventKey) bool { if input.cursorX <= 0 { return false } - pos := stringWidth(input.value, input.cursorX) + pos := countToCursor(input.value, input.cursorX) runes := []rune(input.value) - input.cursorX = runeWidth(string(runes[:pos])) + input.cursorX = stringWidth(string(runes[:pos])) if pos > 0 && runes[pos-1] == '\t' { input.cursorX-- } case tcell.KeyRight: - pos := stringWidth(input.value, input.cursorX+1) + pos := countToCursor(input.value, input.cursorX+1) runes := []rune(input.value) if len(runes) > pos { - input.cursorX = runeWidth(string(runes[:pos+1])) + input.cursorX = stringWidth(string(runes[:pos+1])) } case tcell.KeyTAB: - pos := stringWidth(input.value, input.cursorX+1) + pos := countToCursor(input.value, input.cursorX+1) runes := []rune(input.value) input.value = string(runes[:pos]) input.value += "\t" input.cursorX += 2 input.value += string(runes[pos:]) case tcell.KeyRune: - pos := stringWidth(input.value, input.cursorX+1) + pos := countToCursor(input.value, input.cursorX+1) runes := []rune(input.value) input.value = string(runes[:pos]) r := evKey.Rune() @@ -209,7 +209,7 @@ func (root *Root) inputPrevious() { input := root.input input.value = input.Event.Up(input.value) runes := []rune(input.value) - input.cursorX = runeWidth(string(runes)) + input.cursorX = stringWidth(string(runes)) } // inputNext searches the next history. @@ -217,36 +217,37 @@ func (root *Root) inputNext() { input := root.input input.value = input.Event.Down(input.value) runes := []rune(input.value) - input.cursorX = runeWidth(string(runes)) + input.cursorX = stringWidth(string(runes)) } -// stringWidth returns the number of characters in the input. -func stringWidth(str string, cursor int) int { +// stringWidth returns the number of widths of the input. +// Tab is 2 characters. +func stringWidth(str string) int { width := 0 - i := 0 for _, r := range str { width += runewidth.RuneWidth(r) if r == '\t' { width += 2 } - if width >= cursor { - return i - } - i++ } - return i + return width } -// runeWidth returns the number of widths of the input. -func runeWidth(str string) int { +// countToCursor returns the number of characters in the input. +func countToCursor(str string, cursor int) int { width := 0 + i := 0 for _, r := range str { width += runewidth.RuneWidth(r) if r == '\t' { width += 2 } + if width >= cursor { + return i + } + i++ } - return width + return i } // Eventer is a generic interface for inputs. diff --git a/oviewer/mouse.go b/oviewer/mouse.go index d48abe7a..655afa59 100644 --- a/oviewer/mouse.go +++ b/oviewer/mouse.go @@ -215,7 +215,7 @@ func (root *Root) getClipboard(_ context.Context) { return } - pos := stringWidth(input.value, input.cursorX+1) + pos := countToCursor(input.value, input.cursorX+1) runes := []rune(input.value) input.value = string(runes[:pos]) input.value += str diff --git a/oviewer/utils.go b/oviewer/utils.go index c48ff241..b9b90db0 100644 --- a/oviewer/utils.go +++ b/oviewer/utils.go @@ -4,6 +4,7 @@ import ( "regexp" "strings" + "github.com/mattn/go-runewidth" "golang.org/x/exp/constraints" ) @@ -90,11 +91,25 @@ func allStringIndex(s string, substr string) [][]int { return nil } var result [][]int + width := wordWidth(substr) for pos, offSet := strings.Index(s, substr), 0; pos != -1; { - s = s[pos+len(substr):] - result = append(result, []int{pos + offSet, pos + offSet + len(substr)}) - offSet += pos + len(substr) + s = s[pos+width:] + result = append(result, []int{pos + offSet, pos + offSet + width}) + offSet += pos + width pos = strings.Index(s, substr) } return result } + +// wordWidth returns the width of the word. +func wordWidth(str string) int { + width := 0 + for _, r := range str { + w := runewidth.RuneWidth(r) + if w == 0 { + w = 1 + } + width += w + } + return width +} diff --git a/oviewer/utils_test.go b/oviewer/utils_test.go index 63d401b2..7aed676e 100644 --- a/oviewer/utils_test.go +++ b/oviewer/utils_test.go @@ -422,6 +422,42 @@ func Test_allIndex(t *testing.T) { {3, 4}, }, }, + { + name: "test2", + args: args{ + s: "a|b|c", + substr: "|", + reg: nil, + }, + want: [][]int{ + {1, 2}, + {3, 4}, + }, + }, + { + name: "testTab", + args: args{ + s: "a b c", + substr: " ", + reg: nil, + }, + want: [][]int{ + {1, 2}, + {3, 4}, + }, + }, + { + name: "testUnicode", + args: args{ + s: "a│b│c", + substr: "│", + reg: nil, + }, + want: [][]int{ + {1, 2}, + {5, 6}, + }, + }, { name: "testRegex", args: args{