1
0
mirror of https://github.com/mum4k/termdash.git synced 2025-05-01 22:17:51 +08:00

Removing Text's dependency on wrap.Needed.

It was rune based and thus incompatible with word wrapping.
This commit is contained in:
Jakub Sobon 2019-02-25 00:33:27 -05:00
parent 54c5dff63e
commit 61aca3fb62
No known key found for this signature in database
GPG Key ID: F2451A77FB05D3B7
3 changed files with 41 additions and 8 deletions

View File

@ -34,7 +34,11 @@ func (m Mode) String() string {
}
// modeNames maps Mode values to human readable names.
var modeNames = map[Mode]string{}
var modeNames = map[Mode]string{
Never: "WrapModeNever",
AtRunes: "WrapModeAtRunes",
AtWords: "WrapModeAtWords",
}
const (
// Never is the default wrapping mode, which disables line wrapping.
@ -49,11 +53,11 @@ const (
AtWords
)
// Needed returns true if wrapping is needed for the rune at the horizontal
// needed returns true if wrapping is needed for the rune at the horizontal
// position on the canvas that has the specified width.
// This will always return false if no options are provided, since the default
// behavior is to not wrap the text.
func Needed(r rune, posX, width int, m Mode) bool {
func needed(r rune, posX, width int, m Mode) bool {
if r == '\n' {
// Don't wrap for newline characters as they aren't printed on the
// canvas, i.e. they take no horizontal space.
@ -82,7 +86,7 @@ func Lines(text string, width int, m Mode) []int {
// input text or when the canvas width and configuration requires line
// wrapping.
type lineScanner struct {
// scanner lexes the input text.
// scanner is a lexer of the input text.
scanner *scanner.Scanner
// width is the width of the canvas the text will be drawn on.
@ -142,7 +146,7 @@ func scanLine(ls *lineScanner) scannerState {
case tok == scanner.Ident:
return scanLineBreak
case Needed(tok, ls.posX, ls.width, ls.mode):
case needed(tok, ls.posX, ls.width, ls.mode):
return scanLineWrap
default:

View File

@ -93,9 +93,9 @@ func TestNeeded(t *testing.T) {
for _, tc := range tests {
t.Run(tc.desc, func(t *testing.T) {
got := Needed(tc.r, tc.posX, tc.width, tc.mode)
got := needed(tc.r, tc.posX, tc.width, tc.mode)
if got != tc.want {
t.Errorf("Needed => got %v, want %v", got, tc.want)
t.Errorf("needed => got %v, want %v", got, tc.want)
}
})
}

View File

@ -60,7 +60,13 @@ type Text struct {
contentChanged bool
// lines stores the starting locations in bytes of all the lines in the
// buffer. I.e. positions of newline characters and of any calculated line wraps.
// The indexes in this slice are the line numbers.
lines []int
// lineStartToIdx maps the rune positions where line starts are to indexes,
// the line numbers.
// This is the same data as in lines, but available for quick lookup based
// on character index.
lineStartToIdx map[int]int
// mu protects the Text widget.
mu sync.Mutex
@ -98,6 +104,7 @@ func (t *Text) reset() {
t.lastWidth = 0
t.contentChanged = true
t.lines = nil
t.lineStartToIdx = map[int]int{}
}
// Write writes text for the widget to display. Multiple calls append
@ -173,6 +180,16 @@ func (t *Text) drawScrollDown(cvs *canvas.Canvas, cur image.Point, fromLine int)
return false, nil
}
// isLineStart asserts whether a rune from the text at the specified position
// should be placed on a new line.
// Argument fromLine indicates the starting line we are drawing the text from
// and is needed, because this function must return false for the very first
// line drawn. The first line is already a new line.
func (t *Text) isLineStart(pos, fromLine int) bool {
idx, ok := t.lineStartToIdx[pos]
return ok && idx != fromLine
}
// draw draws the text context on the canvas starting at the specified line.
func (t *Text) draw(text string, cvs *canvas.Canvas) error {
var cur image.Point // Tracks the current drawing position on the canvas.
@ -183,6 +200,7 @@ func (t *Text) draw(text string, cvs *canvas.Canvas) error {
return err
}
startPos := t.lines[fromLine]
var drawnScrollUp bool // Indicates if a scroll up marker was drawn.
for i, r := range text {
if i < startPos {
continue
@ -196,11 +214,18 @@ func (t *Text) draw(text string, cvs *canvas.Canvas) error {
if scrlUp {
cur = image.Point{0, cur.Y + 1} // Move to the next line.
startPos = t.lines[fromLine+1] // Skip one line of text, the marker replaced it.
drawnScrollUp = true
continue
}
// Line wrapping.
if r == '\n' || wrap.Needed(r, cur.X, cvs.Area().Dx(), t.opts.wrapMode) {
fr := fromLine
if drawnScrollUp {
// The scroll marker inserted a line so we are off-by-one when
// looking up new lines.
fr++
}
if t.isLineStart(i, fr) {
cur = image.Point{0, cur.Y + 1} // Move to the next line.
}
@ -255,6 +280,10 @@ func (t *Text) Draw(cvs *canvas.Canvas) error {
// The previous text preprocessing (line wrapping) is invalidated when
// new text is added or the width of the canvas changed.
t.lines = wrap.Lines(text, width, t.opts.wrapMode)
t.lineStartToIdx = map[int]int{}
for idx, start := range t.lines {
t.lineStartToIdx[start] = idx
}
}
t.lastWidth = width