From 8c2b8cfb517fc2c8a506a0a2480c5dacb2ec3a6d Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Tue, 28 Aug 2018 19:58:18 +1000 Subject: support unicode characters --- Gopkg.lock | 6 +- test/repos/unicode_characters.sh | 3 +- vendor/github.com/jesseduffield/gocui/edit.go | 111 +++++++++++++++++++------- vendor/github.com/jesseduffield/gocui/view.go | 88 +++++++++++++------- 4 files changed, 148 insertions(+), 60 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index 421c87726..e0df88b2d 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -189,11 +189,11 @@ [[projects]] branch = "master" - digest = "1:f774b11ae458cae2d10b94ef66ef00ba1c57f1971dd0e5534ac743cbe574f6d4" + digest = "1:acbcdae312c37a8019e0f573a9be26499058d5e1244243655373d2fd97714658" name = "github.com/jesseduffield/gocui" packages = ["."] pruneopts = "NUT" - revision = "7818a0f93387d1037cbd06f69323d9f8d068af7c" + revision = "5d9e836837237cb3aadca51ecb37c7cf3345bfa4" [[projects]] digest = "1:ac6d01547ec4f7f673311b4663909269bfb8249952de3279799289467837c3cc" @@ -614,8 +614,8 @@ "github.com/davecgh/go-spew/spew", "github.com/fatih/color", "github.com/golang-collections/collections/stack", - "github.com/jesseduffield/go-getter", "github.com/heroku/rollrus", + "github.com/jesseduffield/go-getter", "github.com/jesseduffield/gocui", "github.com/kardianos/osext", "github.com/mgutz/str", diff --git a/test/repos/unicode_characters.sh b/test/repos/unicode_characters.sh index fc4cddf1b..3ae129751 100755 --- a/test/repos/unicode_characters.sh +++ b/test/repos/unicode_characters.sh @@ -15,9 +15,8 @@ ZWJ https://en.wikipedia.org/wiki/Zero-width_joiner / https://unicode.org/ UNICODE ☆ 🤓 え 术 EOT git add charstest.txt -git commit -m "Test chars Œ¥ƒ👶👨‍👦☆ 🤓 え 术 commit" +git commit -m "Test chars Œ¥ƒ👶👨‍👦☆ 🤓 え 术👩‍💻👩🏻‍💻👩🏽‍💻👩🏼‍💻👩🏾‍💻👩🏿‍💻👨‍💻👨🏻‍💻👨🏼‍💻👨🏽‍💻👨🏾‍💻👨🏿‍💻 commit" echo "我喜歡編碼" >> charstest.txt echo "நான் குறியீடு விரும்புகிறேன்" >> charstest.txt git add charstest.txt git commit -m "Test chars 我喜歡編碼 நான் குறியீடு விரும்புகிறேன் commit" - diff --git a/vendor/github.com/jesseduffield/gocui/edit.go b/vendor/github.com/jesseduffield/gocui/edit.go index e1b19c20b..a5e6f690b 100644 --- a/vendor/github.com/jesseduffield/gocui/edit.go +++ b/vendor/github.com/jesseduffield/gocui/edit.go @@ -4,7 +4,11 @@ package gocui -import "errors" +import ( + "errors" + + "github.com/mattn/go-runewidth" +) const maxInt = int(^uint(0) >> 1) @@ -54,8 +58,9 @@ func simpleEditor(v *View, key Key, ch rune, mod Modifier) { // EditWrite writes a rune at the cursor position. func (v *View) EditWrite(ch rune) { + w := runewidth.RuneWidth(ch) v.writeRune(v.cx, v.cy, ch) - v.MoveCursor(1, 0, true) + v.moveCursor(w, 0, true) } // EditDelete deletes a rune at the cursor position. back determines the @@ -89,12 +94,12 @@ func (v *View) EditDelete(back bool) { v.MoveCursor(-1, 0, true) } } else { // wrapped line - v.deleteRune(len(v.viewLines[y-1].line)-1, v.cy-1) - v.MoveCursor(-1, 0, true) + n, _ := v.deleteRune(len(v.viewLines[y-1].line)-1, v.cy-1) + v.MoveCursor(-n, 0, true) } } else { // middle/end of the line - v.deleteRune(v.cx-1, v.cy) - v.MoveCursor(-1, 0, true) + n, _ := v.deleteRune(v.cx-1, v.cy) + v.MoveCursor(-n, 0, true) } } else { if x == len(v.viewLines[y].line) { // end of the line @@ -116,35 +121,74 @@ func (v *View) EditNewLine() { // MoveCursor moves the cursor taking into account the width of the line/view, // displacing the origin if necessary. func (v *View) MoveCursor(dx, dy int, writeMode bool) { + ox, oy := v.cx+v.ox, v.cy+v.oy + x, y := ox+dx, oy+dy + + if y < 0 || y >= len(v.viewLines) { + v.moveCursor(dx, dy, writeMode) + return + } + + // Removing newline. + if x < 0 { + var prevLen int + if y-1 >= 0 && y-1 < len(v.viewLines) { + prevLen = lineWidth(v.viewLines[y-1].line) + } + + v.MoveCursor(prevLen, -1, writeMode) + return + } + + line := v.viewLines[y].line + var col int + var prevCol int + for i := range line { + prevCol = col + col += runewidth.RuneWidth(line[i].chr) + if dx > 0 { + if x <= col { + x = col + break + } + continue + } + + if x < col { + x = prevCol + break + } + } + + v.moveCursor(x-ox, y-oy, writeMode) +} + +func (v *View) moveCursor(dx, dy int, writeMode bool) { maxX, maxY := v.Size() cx, cy := v.cx+dx, v.cy+dy x, y := v.ox+cx, v.oy+cy var curLineWidth, prevLineWidth int // get the width of the current line - if writeMode { - if v.Wrap { - curLineWidth = maxX - 1 - } else { - curLineWidth = maxInt - } - } else { + curLineWidth = maxInt + if v.Wrap { + curLineWidth = maxX - 1 + } + + if !writeMode { + curLineWidth = 0 if y >= 0 && y < len(v.viewLines) { - curLineWidth = len(v.viewLines[y].line) + curLineWidth = lineWidth(v.viewLines[y].line) if v.Wrap && curLineWidth >= maxX { curLineWidth = maxX - 1 } - } else { - curLineWidth = 0 } } // get the width of the previous line + prevLineWidth = 0 if y-1 >= 0 && y-1 < len(v.viewLines) { - prevLineWidth = len(v.viewLines[y-1].line) - } else { - prevLineWidth = 0 + prevLineWidth = lineWidth(v.viewLines[y-1].line) } - // adjust cursor's x position and view's x origin if x > curLineWidth { // move to next line if dx > 0 { // horizontal movement @@ -190,10 +234,9 @@ func (v *View) MoveCursor(dx, dy int, writeMode bool) { if !v.Wrap { // set origin so the EOL is visible nox := prevLineWidth - maxX + 1 if nox < 0 { - v.ox = 0 - } else { - v.ox = nox + nox = 0 } + v.ox = nox } v.cx = prevLineWidth } else { @@ -275,19 +318,31 @@ func (v *View) writeRune(x, y int, ch rune) error { // deleteRune removes a rune from the view's internal buffer, at the // position corresponding to the point (x, y). -func (v *View) deleteRune(x, y int) error { +// returns the amount of columns that where removed. +func (v *View) deleteRune(x, y int) (int, error) { v.tainted = true x, y, err := v.realPosition(x, y) if err != nil { - return err + return 0, err } if x < 0 || y < 0 || y >= len(v.lines) || x >= len(v.lines[y]) { - return errors.New("invalid point") + return 0, errors.New("invalid point") } - v.lines[y] = append(v.lines[y][:x], v.lines[y][x+1:]...) - return nil + + var tw int + for i := range v.lines[y] { + w := runewidth.RuneWidth(v.lines[y][i].chr) + tw += w + if tw > x { + v.lines[y] = append(v.lines[y][:i], v.lines[y][i+1:]...) + return w, nil + } + + } + + return 0, nil } // mergeLines merges the lines "y" and "y+1" if possible. diff --git a/vendor/github.com/jesseduffield/gocui/view.go b/vendor/github.com/jesseduffield/gocui/view.go index 462cb474e..0dd897aa4 100644 --- a/vendor/github.com/jesseduffield/gocui/view.go +++ b/vendor/github.com/jesseduffield/gocui/view.go @@ -10,6 +10,7 @@ import ( "io" "strings" + "github.com/mattn/go-runewidth" "github.com/nsf/termbox-go" ) @@ -312,24 +313,14 @@ func (v *View) draw() error { if v.tainted { v.viewLines = nil for i, line := range v.lines { + wrap := 0 if v.Wrap { - if len(line) < maxX { - vline := viewLine{linesX: 0, linesY: i, line: line} - v.viewLines = append(v.viewLines, vline) - continue - } else { - for n := 0; n <= len(line); n += maxX { - if len(line[n:]) <= maxX { - vline := viewLine{linesX: n, linesY: i, line: line[n:]} - v.viewLines = append(v.viewLines, vline) - } else { - vline := viewLine{linesX: n, linesY: i, line: line[n : n+maxX]} - v.viewLines = append(v.viewLines, vline) - } - } - } - } else { - vline := viewLine{linesX: 0, linesY: i, line: line} + wrap = maxX + } + + ls := lineWrap(line, wrap) + for j := range ls { + vline := viewLine{linesX: j, linesY: i, line: ls[j]} v.viewLines = append(v.viewLines, vline) } } @@ -368,7 +359,7 @@ func (v *View) draw() error { if err := v.setRune(x, y, c.chr, fgColor, bgColor); err != nil { return err } - x++ + x += runewidth.RuneWidth(c.chr) } y++ } @@ -438,11 +429,7 @@ func (v *View) BufferLines() []string { // Buffer returns a string with the contents of the view's internal // buffer. func (v *View) Buffer() string { - str := "" - for _, l := range v.lines { - str += lineType(l).String() + "\n" - } - return strings.Replace(str, "\x00", " ", -1) + return linesToString(v.lines) } // ViewBufferLines returns the lines in the view's internal @@ -460,11 +447,12 @@ func (v *View) ViewBufferLines() []string { // ViewBuffer returns a string with the contents of the view's buffer that is // shown to the user. func (v *View) ViewBuffer() string { - str := "" - for _, l := range v.viewLines { - str += lineType(l.line).String() + "\n" + lines := make([][]cell, len(v.viewLines)) + for i := range v.viewLines { + lines[i] = v.viewLines[i].line } - return strings.Replace(str, "\x00", " ", -1) + + return linesToString(lines) } // Line returns a string with the line of the view's internal buffer @@ -516,3 +504,49 @@ func (v *View) Word(x, y int) (string, error) { func indexFunc(r rune) bool { return r == ' ' || r == 0 } + +func lineWidth(line []cell) (n int) { + for i := range line { + n += runewidth.RuneWidth(line[i].chr) + } + + return +} + +func lineWrap(line []cell, columns int) [][]cell { + if columns == 0 { + return [][]cell{line} + } + + var n int + var offset int + lines := make([][]cell, 0, 1) + for i := range line { + rw := runewidth.RuneWidth(line[i].chr) + n += rw + if n > columns { + n = rw + lines = append(lines, line[offset:i-1]) + offset = i + } + } + + lines = append(lines, line[offset:]) + return lines +} + +func linesToString(lines [][]cell) string { + str := make([]string, len(lines)) + for i := range lines { + rns := make([]rune, 0, len(lines[i])) + line := lineType(lines[i]).String() + for _, c := range line { + if c != '\x00' { + rns = append(rns, c) + } + } + str[i] = string(rns) + } + + return strings.Join(str, "\n") +} -- cgit v1.2.3