Skip to content
This repository has been archived by the owner on Oct 13, 2021. It is now read-only.

Commit

Permalink
Implement whitespace preserving wordwrap
Browse files Browse the repository at this point in the history
  • Loading branch information
marcusolsson committed Nov 18, 2017
1 parent 79540b1 commit be6863e
Show file tree
Hide file tree
Showing 7 changed files with 246 additions and 89 deletions.
13 changes: 8 additions & 5 deletions entry.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,15 @@ func (e *Entry) Draw(p *Painter) {
}
p.WithStyle(style, func(p *Painter) {
s := e.Size()
e.text.SetMaxWidth(s.X)

text := e.visibleText()

p.FillRect(0, 0, s.X, 1)
p.DrawText(0, 0, text)

if e.IsFocused() {
pos := e.text.CursorPos(s.X)
pos := e.text.CursorPos()
p.DrawCursor(pos.X-e.offset, 0)
}
})
Expand All @@ -56,6 +57,9 @@ func (e *Entry) OnKeyEvent(ev KeyEvent) {
return
}

screenWidth := e.Size().X
e.text.SetMaxWidth(screenWidth)

if ev.Key != KeyRune {
switch ev.Key {
case KeyEnter:
Expand Down Expand Up @@ -83,8 +87,7 @@ func (e *Entry) OnKeyEvent(ev KeyEvent) {
case KeyRight, KeyCtrlF:
e.text.MoveForward()

screenWidth := e.Size().X
isCursorTooFar := e.text.CursorPos(screenWidth).X >= screenWidth
isCursorTooFar := e.text.CursorPos().X >= screenWidth
isTextLeft := (e.text.Width() - e.offset) > (screenWidth - 1)

if isCursorTooFar && isTextLeft {
Expand All @@ -95,7 +98,7 @@ func (e *Entry) OnKeyEvent(ev KeyEvent) {
e.offset = 0
case KeyEnd, KeyCtrlE:
e.text.MoveToLineEnd()
left := e.text.Width() - (e.Size().X - 1)
left := e.text.Width() - (screenWidth - 1)
if left >= 0 {
e.offset = left
}
Expand All @@ -106,7 +109,7 @@ func (e *Entry) OnKeyEvent(ev KeyEvent) {
}

e.text.WriteRune(ev.Rune)
if e.text.CursorPos(e.Size().X).X >= e.Size().X {
if e.text.CursorPos().X >= screenWidth {
e.offset++
}
if e.onTextChange != nil {
Expand Down
8 changes: 5 additions & 3 deletions entry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,7 @@ func TestEntry_MoveToStartAndEnd(t *testing.T) {
e := NewEntry()
e.SetText("Lorem ipsum")
e.SetFocused(true)
e.text.SetMaxWidth(5)
e.offset = 6

surface := newTestSurface(5, 1)
Expand All @@ -397,7 +398,7 @@ func TestEntry_MoveToStartAndEnd(t *testing.T) {

want := "\nLorem\n"

if got := e.text.CursorPos(5); got.X != 0 {
if got := e.text.CursorPos(); got.X != 0 {
t.Errorf("cursor position should be %d, but was %d", 0, got.X)
}
if e.offset != 0 {
Expand All @@ -413,7 +414,7 @@ func TestEntry_MoveToStartAndEnd(t *testing.T) {

want := "\npsum \n"

if got := e.text.CursorPos(5); got.X != 11 {
if got := e.text.CursorPos(); got.X != 11 {
t.Errorf("cursor position should be %d, but was %d", 11, got.X)
}
if e.offset != 7 {
Expand All @@ -430,6 +431,7 @@ func TestEntry_OnKeyBackspaceEvent(t *testing.T) {
e := NewEntry()
e.SetText("Lorem ipsum")
e.SetFocused(true)
e.text.SetMaxWidth(5)
e.offset = 6

surface := newTestSurface(5, 1)
Expand All @@ -441,7 +443,7 @@ func TestEntry_OnKeyBackspaceEvent(t *testing.T) {

want := "\nm ips\n"

if got := e.text.CursorPos(5); got.X != 9 {
if got := e.text.CursorPos(); got.X != 9 {
t.Errorf("cursor position should be %d, but was %d", 9, got.X)
}
if e.offset != 4 {
Expand Down
51 changes: 31 additions & 20 deletions runebuf.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import (
"image"
"strings"

"github.com/marcusolsson/tui-go/wordwrap"
runewidth "github.com/mattn/go-runewidth"
wordwrap "github.com/mitchellh/go-wordwrap"
)

// RuneBuffer provides readline functionality for text widgets.
Expand All @@ -14,6 +14,13 @@ type RuneBuffer struct {
idx int

wordwrap bool

width int
}

// SetMaxWidth sets the maximum text width.
func (r *RuneBuffer) SetMaxWidth(w int) {
r.width = w
}

// Width returns the width of the rune buffer, taking into account for CJK.
Expand Down Expand Up @@ -55,35 +62,39 @@ func (r *RuneBuffer) Len() int {
}

// SplitByLine returns the lines for a given width.
func (r *RuneBuffer) SplitByLine(width int) []string {
var text string
if r.wordwrap {
text = wordwrap.WrapString(r.String(), uint(width))
} else {
text = r.String()
}
return strings.Split(text, "\n")
func (r *RuneBuffer) SplitByLine() []string {
return r.getSplitByLine(r.width)
}

func getSplitByLine(rs []rune, width int, wrap bool) []string {
func (r *RuneBuffer) getSplitByLine(w int) []string {
var text string
if wrap {
text = wordwrap.WrapString(string(rs), uint(width))
if r.wordwrap {
text = wordwrap.WrapString(r.String(), w)
} else {
text = string(rs)
text = r.String()
}
return strings.Split(text, "\n")
}

// CursorPos returns the coordinate for the cursor for a given width.
func (r *RuneBuffer) CursorPos(width int) image.Point {
if width == 0 {
func (r *RuneBuffer) CursorPos() image.Point {
if r.width == 0 {
return image.ZP
}

sp := getSplitByLine(r.buf[:r.idx], width, r.wordwrap)

return image.Pt(stringWidth(sp[len(sp)-1]), len(sp)-1)
sp := r.SplitByLine()
var x, y int
remaining := r.idx
for _, l := range sp {
if len(l) < remaining {
y++
remaining -= len(l) + 1
} else {
x = remaining
break
}
}
return image.Pt(x, y)
}

func (r *RuneBuffer) String() string {
Expand Down Expand Up @@ -120,7 +131,7 @@ func (r *RuneBuffer) MoveToLineStart() {
// MoveToLineEnd moves the cursor to the end of the current line.
func (r *RuneBuffer) MoveToLineEnd() {
for i := r.idx; i < len(r.buf)-1; i++ {
if r.buf[i+1] == '\n' {
if r.buf[i] == '\n' {
r.idx = i
return
}
Expand Down Expand Up @@ -156,5 +167,5 @@ func (r *RuneBuffer) Kill() {
}

func (r *RuneBuffer) heightForWidth(w int) int {
return len(r.SplitByLine(w))
return len(r.getSplitByLine(w))
}
Loading

0 comments on commit be6863e

Please sign in to comment.