Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Visually wide character support #74

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 83 additions & 28 deletions edit.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@

package gocui

import "errors"
import (
"errors"

"github.com/mattn/go-runewidth"
)

const maxInt = int(^uint(0) >> 1)

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -123,35 +128,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
Expand Down Expand Up @@ -191,10 +235,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 {
Expand Down Expand Up @@ -272,19 +315,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.
Expand Down
91 changes: 61 additions & 30 deletions view.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"io"
"strings"

"github.com/mattn/go-runewidth"
"github.com/nsf/termbox-go"
)

Expand Down Expand Up @@ -297,27 +298,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 {
vline := viewLine{linesX: 0, linesY: i, line: line[:maxX]}
v.viewLines = append(v.viewLines, vline)
}
// Append remaining lines
for n := maxX; 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)
}
}
Expand Down Expand Up @@ -356,7 +344,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++
}
Expand Down Expand Up @@ -412,21 +400,18 @@ func (v *View) clearRunes() {
// 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)
}

// 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
Expand Down Expand Up @@ -478,3 +463,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")
}