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 }