nanite/widgets/pager/pager.go
2025-10-11 20:20:36 -03:00

171 lines
2.9 KiB
Go

// This is a "fork" of Vaxis' pager widget that adds Unicode line breaking.
// See <https://git.sr.ht/~rockorager/vaxis/tree/v0.15.0/item/widgets/pager/pager.go>.
package pager
import (
"strings"
"git.sr.ht/~rockorager/vaxis"
"github.com/rivo/uniseg"
)
const (
WrapFast = iota
WrapWords
)
var defaultFill = vaxis.Character{Grapheme: " "}
type Model struct {
Segments []vaxis.Segment
lines []*line
Fill vaxis.Cell
Offset int
WrapMode int
width int
}
type line struct {
characters []vaxis.Cell
}
func (l *line) append(t vaxis.Cell) {
l.characters = append(l.characters, t)
}
func (m *Model) Draw(win vaxis.Window) {
w, h := win.Size()
if w != m.width {
m.width = w
m.Layout()
}
if len(m.lines)-m.Offset < h {
m.Offset = len(m.lines) - h
}
if m.Offset < 0 {
m.Offset = 0
}
if m.Fill.Grapheme == "" {
m.Fill.Character = defaultFill
}
win.Fill(m.Fill)
for row, l := range m.lines {
if row < m.Offset {
continue
}
if (row - m.Offset) >= h {
return
}
col := 0
for _, cell := range l.characters {
win.SetCell(col, row-m.Offset, cell)
col += cell.Width
}
}
}
func (m *Model) layoutFast() {
l := &line{}
col := 0
for _, seg := range m.Segments {
for _, char := range vaxis.Characters(seg.Text) {
if strings.ContainsRune(char.Grapheme, '\n') {
m.lines = append(m.lines, l)
l = &line{}
col = 0
continue
}
cell := vaxis.Cell{
Character: char,
Style: seg.Style,
}
l.append(cell)
col += char.Width
if col >= m.width {
m.lines = append(m.lines, l)
l = &line{}
col = 0
}
}
}
}
func (m *Model) layoutSlow() {
cols := m.width
col := 0
l := &line{}
var (
state = -1
segment string
)
for _, seg := range m.Segments {
rest := seg.Text
for len(rest) > 0 {
segment, rest, _, state = uniseg.FirstLineSegmentInString(rest, state)
chars := vaxis.Characters(segment)
total := 0
for _, char := range chars {
total += char.Width
}
switch {
case total > cols:
case total+col > cols:
m.lines = append(m.lines, l)
l = &line{}
col = 0
default:
}
for _, char := range chars {
if uniseg.HasTrailingLineBreakInString(char.Grapheme) {
m.lines = append(m.lines, l)
l = &line{}
col = 0
continue
}
cell := vaxis.Cell{
Character: char,
Style: seg.Style,
}
l.append(cell)
col += char.Width
if col >= cols {
m.lines = append(m.lines, l)
l = &line{}
col = 0
}
}
}
}
}
func (m *Model) Layout() {
m.lines = []*line{}
switch m.WrapMode {
case WrapFast:
m.layoutFast()
case WrapWords:
m.layoutSlow()
}
}
// Scrolls the pager down n lines, if it can
func (m *Model) ScrollDown() {
m.Offset += 1
}
func (m *Model) ScrollUp() {
m.Offset -= 1
}
func (m *Model) ScrollDownN(n int) {
m.Offset += n
}
func (m *Model) ScrollUpN(n int) {
m.Offset -= n
}