132 lines
2.6 KiB
Go
132 lines
2.6 KiB
Go
package line
|
|
|
|
import (
|
|
"strings"
|
|
"unicode/utf8"
|
|
|
|
"code.chimeric.al/dwrz/wen/internal/editor/buffer/glyph"
|
|
)
|
|
|
|
type Line struct {
|
|
data string
|
|
}
|
|
|
|
func New(s string) *Line {
|
|
return &Line{data: s}
|
|
}
|
|
|
|
func (l *Line) DecodeRune(index int) (r rune, size int) {
|
|
return utf8.DecodeRuneInString(l.data[index:])
|
|
}
|
|
|
|
// FindGlyphIndex returns the byte index and display column (g) for a target
|
|
// display column. If target falls inside a multi-column rune, it returns the
|
|
// start of that rune. If target is past the end of the line, it returns the
|
|
// byte length and total display width of the line.
|
|
func (l *Line) FindGlyphIndex(target int) (index, g int) {
|
|
var li, lg int
|
|
for i, r := range l.data {
|
|
// We've reached the target.
|
|
if g == target {
|
|
|
|
return i, g
|
|
}
|
|
// We've gone too far.
|
|
// Return the preceding index and glyph.
|
|
if g > target {
|
|
return li, lg
|
|
}
|
|
|
|
// Otherwise, we haven't reached the target.
|
|
// Save this index and glyph.
|
|
li = i
|
|
lg = g
|
|
|
|
// Then increment the glyph.
|
|
g += glyph.Width(r)
|
|
}
|
|
// Target falls in-between the last glyph and the end of the line.
|
|
// Use the last glyph.
|
|
if target >= lg && target < g {
|
|
return li, lg
|
|
}
|
|
|
|
// We weren't able to find the glyph.
|
|
// Return the last possible index and glyph.
|
|
return len(l.data), g
|
|
}
|
|
|
|
// Length returns the byte length of the line's UTF-8 string.
|
|
func (l *Line) Length() int {
|
|
return len(l.data)
|
|
}
|
|
|
|
// Render returns the visible substring starting at display column offset and
|
|
// fitting within width display columns. Tabs are expanded to 8 spaces. If
|
|
// offset splits a multi-column rune, the remaining columns of that rune are
|
|
// padded with spaces.
|
|
func (l *Line) Render(offset, width int) string {
|
|
if offset < 0 || width <= 0 {
|
|
return ""
|
|
}
|
|
|
|
var (
|
|
text = strings.ReplaceAll(l.data, "\t", " ")
|
|
str strings.Builder
|
|
)
|
|
for _, r := range text {
|
|
if r == utf8.RuneError {
|
|
continue
|
|
}
|
|
|
|
rw := glyph.Width(r)
|
|
if offset > 0 {
|
|
offset -= rw
|
|
if offset < 0 {
|
|
// The offset split a wide rune.
|
|
// Pad the remaining column.
|
|
pad := -offset
|
|
if pad > width {
|
|
pad = width
|
|
}
|
|
for i := 0; i < pad; i++ {
|
|
str.WriteRune(' ')
|
|
}
|
|
width -= pad
|
|
offset = 0
|
|
}
|
|
continue
|
|
}
|
|
|
|
if width-rw < 0 {
|
|
break
|
|
}
|
|
|
|
str.WriteRune(r)
|
|
width -= rw
|
|
}
|
|
|
|
return str.String()
|
|
}
|
|
|
|
func (l *Line) Runes() []rune {
|
|
return []rune(l.data)
|
|
}
|
|
|
|
func (l *Line) String() string {
|
|
return l.data
|
|
}
|
|
|
|
// Width returns the terminal display column width of the line.
|
|
func (l *Line) Width() (count int) {
|
|
for _, r := range l.data {
|
|
rw := glyph.Width(r)
|
|
if r == utf8.RuneError {
|
|
continue
|
|
}
|
|
count += rw
|
|
}
|
|
|
|
return count
|
|
}
|