bubbletea/pager/pager.go

202 lines
3.1 KiB
Go
Raw Normal View History

package pager
import (
"errors"
"os"
"strings"
"github.com/charmbracelet/boba"
"golang.org/x/crypto/ssh/terminal"
)
// MSG
type terminalSizeMsg struct {
width int
height int
}
type errMsg error
// MODEL
type State int
const (
StateInit State = iota
StateReady
)
type Model struct {
Err error
Standalone bool
State State
Width int
Height int
Y int
lines []string
}
2020-05-13 16:06:15 -04:00
func (m *Model) PageUp() {
m.Y = max(0, m.Y-m.Height)
}
func (m *Model) PageDown() {
m.Y = min(len(m.lines)-m.Height, m.Y+m.Height)
}
// Content adds text content to the model
func (m *Model) Content(s string) {
s = strings.TrimSpace(s)
s = strings.Replace(s, "\r\n", "\n", -1) // normalize line endings
m.lines = strings.Split(s, "\n")
}
func NewModel() Model {
return Model{
State: StateInit,
}
}
// INIT
func Init(initialContent string) func() (boba.Model, boba.Cmd) {
m := NewModel()
m.Standalone = true
m.Content(initialContent)
return func() (boba.Model, boba.Cmd) {
return m, getTerminalSize
}
}
// UPDATE
func Update(msg boba.Msg, model boba.Model) (boba.Model, boba.Cmd) {
m, ok := model.(Model)
if !ok {
return Model{
Err: errors.New("could not perform assertion on model in update in pager; are you sure you passed the correct model?"),
}, nil
}
switch msg := msg.(type) {
case boba.KeyMsg:
switch msg.String() {
case "q":
fallthrough
case "ctrl+c":
if m.Standalone {
return m, boba.Quit
}
// Up one page
case "b":
m.Y = max(0, m.Y-m.Height)
return m, nil
// Down one page
case "space":
fallthrough
case "f":
m.Y = min(len(m.lines)-m.Height, m.Y+m.Height)
return m, nil
// Up half page
case "u":
m.Y = max(0, m.Y-m.Height/2)
return m, nil
// Down half page
case "d":
m.Y = min(len(m.lines)-m.Height, m.Y+m.Height/2)
return m, nil
// Up one line
case "up":
fallthrough
case "k":
m.Y = max(0, m.Y-1)
return m, nil
// Down one line
case "down":
fallthrough
case "j":
m.Y = min(len(m.lines)-m.Height, m.Y+1)
return m, nil
// Re-render
case "ctrl+l":
return m, getTerminalSize
}
case errMsg:
m.Err = msg
return m, nil
case terminalSizeMsg:
m.Width = msg.width
m.Height = msg.height
m.State = StateReady
return m, nil
}
return model, nil
}
// VIEW
func View(model boba.Model) string {
m, ok := model.(Model)
if !ok {
return "could not perform assertion on model in view in pager; are you sure you passed the correct model?"
}
if m.Err != nil {
return m.Err.Error()
}
if len(m.lines) == 0 {
return "(Buffer empty)"
}
if m.State == StateReady {
// Render viewport
top := max(0, m.Y)
bottom := min(len(m.lines), m.Y+m.Height)
lines := m.lines[top:bottom]
return "\n" + strings.Join(lines, "\n")
}
return ""
}
// CMD
func getTerminalSize() boba.Msg {
w, h, err := terminal.GetSize(int(os.Stdout.Fd()))
if err != nil {
return errMsg(err)
}
return terminalSizeMsg{w, h}
}
// ETC
func min(a, b int) int {
if a < b {
return a
}
return b
}
func max(a, b int) int {
if a > b {
return a
}
return b
}