1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-02-09 13:47:11 +02:00

support unicode characters

This commit is contained in:
Jesse Duffield 2018-08-28 19:58:18 +10:00
parent 145cba34a0
commit 8c2b8cfb51
4 changed files with 148 additions and 60 deletions

6
Gopkg.lock generated
View File

@ -189,11 +189,11 @@
[[projects]] [[projects]]
branch = "master" branch = "master"
digest = "1:f774b11ae458cae2d10b94ef66ef00ba1c57f1971dd0e5534ac743cbe574f6d4" digest = "1:acbcdae312c37a8019e0f573a9be26499058d5e1244243655373d2fd97714658"
name = "github.com/jesseduffield/gocui" name = "github.com/jesseduffield/gocui"
packages = ["."] packages = ["."]
pruneopts = "NUT" pruneopts = "NUT"
revision = "7818a0f93387d1037cbd06f69323d9f8d068af7c" revision = "5d9e836837237cb3aadca51ecb37c7cf3345bfa4"
[[projects]] [[projects]]
digest = "1:ac6d01547ec4f7f673311b4663909269bfb8249952de3279799289467837c3cc" digest = "1:ac6d01547ec4f7f673311b4663909269bfb8249952de3279799289467837c3cc"
@ -614,8 +614,8 @@
"github.com/davecgh/go-spew/spew", "github.com/davecgh/go-spew/spew",
"github.com/fatih/color", "github.com/fatih/color",
"github.com/golang-collections/collections/stack", "github.com/golang-collections/collections/stack",
"github.com/jesseduffield/go-getter",
"github.com/heroku/rollrus", "github.com/heroku/rollrus",
"github.com/jesseduffield/go-getter",
"github.com/jesseduffield/gocui", "github.com/jesseduffield/gocui",
"github.com/kardianos/osext", "github.com/kardianos/osext",
"github.com/mgutz/str", "github.com/mgutz/str",

View File

@ -15,9 +15,8 @@ ZWJ https://en.wikipedia.org/wiki/Zero-width_joiner / https://unicode.org/
UNICODE ☆ 🤓 え 术 UNICODE ☆ 🤓 え 术
EOT EOT
git add charstest.txt git add charstest.txt
git commit -m "Test chars Œ¥ƒ👶👨‍👦☆ 🤓 え 术 commit" git commit -m "Test chars Œ¥ƒ👶👨‍👦☆ 🤓 え 术👩‍💻👩🏻‍💻👩🏽‍💻👩🏼‍💻👩🏾‍💻👩🏿‍💻👨‍💻👨🏻‍💻👨🏼‍💻👨🏽‍💻👨🏾‍💻👨🏿‍💻 commit"
echo "我喜歡編碼" >> charstest.txt echo "我喜歡編碼" >> charstest.txt
echo "நான் குறியீடு விரும்புகிறேன்" >> charstest.txt echo "நான் குறியீடு விரும்புகிறேன்" >> charstest.txt
git add charstest.txt git add charstest.txt
git commit -m "Test chars 我喜歡編碼 நான் குறியீடு விரும்புகிறேன் commit" git commit -m "Test chars 我喜歡編碼 நான் குறியீடு விரும்புகிறேன் commit"

View File

@ -4,7 +4,11 @@
package gocui package gocui
import "errors" import (
"errors"
"github.com/mattn/go-runewidth"
)
const maxInt = int(^uint(0) >> 1) 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. // EditWrite writes a rune at the cursor position.
func (v *View) EditWrite(ch rune) { func (v *View) EditWrite(ch rune) {
w := runewidth.RuneWidth(ch)
v.writeRune(v.cx, v.cy, 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 // 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) v.MoveCursor(-1, 0, true)
} }
} else { // wrapped line } else { // wrapped line
v.deleteRune(len(v.viewLines[y-1].line)-1, v.cy-1) n, _ := v.deleteRune(len(v.viewLines[y-1].line)-1, v.cy-1)
v.MoveCursor(-1, 0, true) v.MoveCursor(-n, 0, true)
} }
} else { // middle/end of the line } else { // middle/end of the line
v.deleteRune(v.cx-1, v.cy) n, _ := v.deleteRune(v.cx-1, v.cy)
v.MoveCursor(-1, 0, true) v.MoveCursor(-n, 0, true)
} }
} else { } else {
if x == len(v.viewLines[y].line) { // end of the line 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, // MoveCursor moves the cursor taking into account the width of the line/view,
// displacing the origin if necessary. // displacing the origin if necessary.
func (v *View) MoveCursor(dx, dy int, writeMode bool) { 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() maxX, maxY := v.Size()
cx, cy := v.cx+dx, v.cy+dy cx, cy := v.cx+dx, v.cy+dy
x, y := v.ox+cx, v.oy+cy x, y := v.ox+cx, v.oy+cy
var curLineWidth, prevLineWidth int var curLineWidth, prevLineWidth int
// get the width of the current line // get the width of the current line
if writeMode { curLineWidth = maxInt
if v.Wrap { if v.Wrap {
curLineWidth = maxX - 1 curLineWidth = maxX - 1
} else { }
curLineWidth = maxInt
} if !writeMode {
} else { curLineWidth = 0
if y >= 0 && y < len(v.viewLines) { if y >= 0 && y < len(v.viewLines) {
curLineWidth = len(v.viewLines[y].line) curLineWidth = lineWidth(v.viewLines[y].line)
if v.Wrap && curLineWidth >= maxX { if v.Wrap && curLineWidth >= maxX {
curLineWidth = maxX - 1 curLineWidth = maxX - 1
} }
} else {
curLineWidth = 0
} }
} }
// get the width of the previous line // get the width of the previous line
prevLineWidth = 0
if y-1 >= 0 && y-1 < len(v.viewLines) { if y-1 >= 0 && y-1 < len(v.viewLines) {
prevLineWidth = len(v.viewLines[y-1].line) prevLineWidth = lineWidth(v.viewLines[y-1].line)
} else {
prevLineWidth = 0
} }
// adjust cursor's x position and view's x origin // adjust cursor's x position and view's x origin
if x > curLineWidth { // move to next line if x > curLineWidth { // move to next line
if dx > 0 { // horizontal movement 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 if !v.Wrap { // set origin so the EOL is visible
nox := prevLineWidth - maxX + 1 nox := prevLineWidth - maxX + 1
if nox < 0 { if nox < 0 {
v.ox = 0 nox = 0
} else {
v.ox = nox
} }
v.ox = nox
} }
v.cx = prevLineWidth v.cx = prevLineWidth
} else { } 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 // deleteRune removes a rune from the view's internal buffer, at the
// position corresponding to the point (x, y). // 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 v.tainted = true
x, y, err := v.realPosition(x, y) x, y, err := v.realPosition(x, y)
if err != nil { if err != nil {
return err return 0, err
} }
if x < 0 || y < 0 || y >= len(v.lines) || x >= len(v.lines[y]) { 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. // mergeLines merges the lines "y" and "y+1" if possible.

View File

@ -10,6 +10,7 @@ import (
"io" "io"
"strings" "strings"
"github.com/mattn/go-runewidth"
"github.com/nsf/termbox-go" "github.com/nsf/termbox-go"
) )
@ -312,24 +313,14 @@ func (v *View) draw() error {
if v.tainted { if v.tainted {
v.viewLines = nil v.viewLines = nil
for i, line := range v.lines { for i, line := range v.lines {
wrap := 0
if v.Wrap { if v.Wrap {
if len(line) < maxX { wrap = maxX
vline := viewLine{linesX: 0, linesY: i, line: line} }
v.viewLines = append(v.viewLines, vline)
continue ls := lineWrap(line, wrap)
} else { for j := range ls {
for n := 0; n <= len(line); n += maxX { vline := viewLine{linesX: j, linesY: i, line: ls[j]}
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}
v.viewLines = append(v.viewLines, vline) 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 { if err := v.setRune(x, y, c.chr, fgColor, bgColor); err != nil {
return err return err
} }
x++ x += runewidth.RuneWidth(c.chr)
} }
y++ y++
} }
@ -438,11 +429,7 @@ func (v *View) BufferLines() []string {
// Buffer returns a string with the contents of the view's internal // Buffer returns a string with the contents of the view's internal
// buffer. // buffer.
func (v *View) Buffer() string { func (v *View) Buffer() string {
str := "" return linesToString(v.lines)
for _, l := range v.lines {
str += lineType(l).String() + "\n"
}
return strings.Replace(str, "\x00", " ", -1)
} }
// ViewBufferLines returns the lines in the view's internal // 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 // ViewBuffer returns a string with the contents of the view's buffer that is
// shown to the user. // shown to the user.
func (v *View) ViewBuffer() string { func (v *View) ViewBuffer() string {
str := "" lines := make([][]cell, len(v.viewLines))
for _, l := range v.viewLines { for i := range v.viewLines {
str += lineType(l.line).String() + "\n" 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 // 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 { func indexFunc(r rune) bool {
return r == ' ' || r == 0 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")
}