mirror of
https://github.com/mum4k/termdash.git
synced 2025-05-03 22:17:06 +08:00
Removing Text's dependency on wrap.Needed.
It was rune based and thus incompatible with word wrapping.
This commit is contained in:
parent
54c5dff63e
commit
61aca3fb62
@ -34,7 +34,11 @@ func (m Mode) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// modeNames maps Mode values to human readable names.
|
// 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 (
|
const (
|
||||||
// Never is the default wrapping mode, which disables line wrapping.
|
// Never is the default wrapping mode, which disables line wrapping.
|
||||||
@ -49,11 +53,11 @@ const (
|
|||||||
AtWords
|
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.
|
// position on the canvas that has the specified width.
|
||||||
// This will always return false if no options are provided, since the default
|
// This will always return false if no options are provided, since the default
|
||||||
// behavior is to not wrap the text.
|
// 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' {
|
if r == '\n' {
|
||||||
// Don't wrap for newline characters as they aren't printed on the
|
// Don't wrap for newline characters as they aren't printed on the
|
||||||
// canvas, i.e. they take no horizontal space.
|
// 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
|
// input text or when the canvas width and configuration requires line
|
||||||
// wrapping.
|
// wrapping.
|
||||||
type lineScanner struct {
|
type lineScanner struct {
|
||||||
// scanner lexes the input text.
|
// scanner is a lexer of the input text.
|
||||||
scanner *scanner.Scanner
|
scanner *scanner.Scanner
|
||||||
|
|
||||||
// width is the width of the canvas the text will be drawn on.
|
// 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:
|
case tok == scanner.Ident:
|
||||||
return scanLineBreak
|
return scanLineBreak
|
||||||
|
|
||||||
case Needed(tok, ls.posX, ls.width, ls.mode):
|
case needed(tok, ls.posX, ls.width, ls.mode):
|
||||||
return scanLineWrap
|
return scanLineWrap
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -93,9 +93,9 @@ func TestNeeded(t *testing.T) {
|
|||||||
|
|
||||||
for _, tc := range tests {
|
for _, tc := range tests {
|
||||||
t.Run(tc.desc, func(t *testing.T) {
|
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 {
|
if got != tc.want {
|
||||||
t.Errorf("Needed => got %v, want %v", got, tc.want)
|
t.Errorf("needed => got %v, want %v", got, tc.want)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -60,7 +60,13 @@ type Text struct {
|
|||||||
contentChanged bool
|
contentChanged bool
|
||||||
// lines stores the starting locations in bytes of all the lines in the
|
// 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.
|
// buffer. I.e. positions of newline characters and of any calculated line wraps.
|
||||||
|
// The indexes in this slice are the line numbers.
|
||||||
lines []int
|
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 protects the Text widget.
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
@ -98,6 +104,7 @@ func (t *Text) reset() {
|
|||||||
t.lastWidth = 0
|
t.lastWidth = 0
|
||||||
t.contentChanged = true
|
t.contentChanged = true
|
||||||
t.lines = nil
|
t.lines = nil
|
||||||
|
t.lineStartToIdx = map[int]int{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write writes text for the widget to display. Multiple calls append
|
// 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
|
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.
|
// draw draws the text context on the canvas starting at the specified line.
|
||||||
func (t *Text) draw(text string, cvs *canvas.Canvas) error {
|
func (t *Text) draw(text string, cvs *canvas.Canvas) error {
|
||||||
var cur image.Point // Tracks the current drawing position on the canvas.
|
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
|
return err
|
||||||
}
|
}
|
||||||
startPos := t.lines[fromLine]
|
startPos := t.lines[fromLine]
|
||||||
|
var drawnScrollUp bool // Indicates if a scroll up marker was drawn.
|
||||||
for i, r := range text {
|
for i, r := range text {
|
||||||
if i < startPos {
|
if i < startPos {
|
||||||
continue
|
continue
|
||||||
@ -196,11 +214,18 @@ func (t *Text) draw(text string, cvs *canvas.Canvas) error {
|
|||||||
if scrlUp {
|
if scrlUp {
|
||||||
cur = image.Point{0, cur.Y + 1} // Move to the next line.
|
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.
|
startPos = t.lines[fromLine+1] // Skip one line of text, the marker replaced it.
|
||||||
|
drawnScrollUp = true
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Line wrapping.
|
// 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.
|
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
|
// The previous text preprocessing (line wrapping) is invalidated when
|
||||||
// new text is added or the width of the canvas changed.
|
// new text is added or the width of the canvas changed.
|
||||||
t.lines = wrap.Lines(text, width, t.opts.wrapMode)
|
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
|
t.lastWidth = width
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user