initial commit

This commit is contained in:
2025-10-15 00:54:19 +02:00
parent 97c7383cf1
commit 0d4c44ee11
14 changed files with 1242 additions and 0 deletions

166
cmd/pager.go Normal file
View File

@@ -0,0 +1,166 @@
package cmd
// pager setup using bubbletea
// file shamlelessly copied from:
// https://github.com/charmbracelet/bubbletea/tree/main/examples/pager
import (
"fmt"
"os"
"strings"
"github.com/charmbracelet/bubbles/viewport"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/muesli/reflow/wordwrap"
"golang.org/x/term"
)
var (
titleStyle = func() lipgloss.Style {
b := lipgloss.RoundedBorder()
b.Right = "├"
return lipgloss.NewStyle().BorderStyle(b).Padding(0, 1)
}()
infoStyle = func() lipgloss.Style {
b := lipgloss.RoundedBorder()
b.Left = "┤"
return titleStyle.BorderStyle(b)
}()
viewstyle = lipgloss.NewStyle()
)
type Meta struct {
lines int
currentline int
initialprogress int
document string
}
type Doc struct {
content string
title string
ready bool
viewport viewport.Model
initialwidth int
meta *Meta
}
func (m Doc) Init() tea.Cmd {
return nil
}
func (m Doc) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var (
cmd tea.Cmd
cmds []tea.Cmd
)
switch msg := msg.(type) {
case tea.KeyMsg:
if k := msg.String(); k == "ctrl+c" || k == "q" || k == "esc" {
return m, tea.Quit
}
case tea.WindowSizeMsg:
headerHeight := lipgloss.Height(m.headerView())
footerHeight := lipgloss.Height(m.footerView())
verticalMarginHeight := headerHeight + footerHeight
if !m.ready {
// Since this program is using the full size of the viewport we
// need to wait until we've received the window dimensions before
// we can initialize the viewport. The initial dimensions come in
// quickly, though asynchronously, which is why we wait for them
// here.
m.viewport = viewport.New(msg.Width, msg.Height-verticalMarginHeight)
m.viewport.YPosition = headerHeight
m.viewport.SetContent(wordwrap.String(m.content, m.initialwidth))
m.viewport.ScrollDown(m.meta.initialprogress)
m.ready = true
} else {
m.viewport.Width = msg.Width
m.viewport.Height = msg.Height - verticalMarginHeight
m.viewport.SetContent(wordwrap.String(m.content, msg.Width))
}
}
// Handle keyboard and mouse events in the viewport
m.viewport, cmd = m.viewport.Update(msg)
cmds = append(cmds, cmd)
return m, tea.Batch(cmds...)
}
func (m Doc) View() string {
if !m.ready {
return "\n Initializing..."
}
// update current line for later saving
// FIXME: doesn't work correctly yet
m.meta.currentline = int(float64(m.meta.lines) * m.viewport.ScrollPercent())
return fmt.Sprintf("%s\n%s\n%s", m.headerView(), m.viewport.View(), m.footerView())
}
func (m Doc) headerView() string {
title := titleStyle.Render(m.title)
line := strings.Repeat("─", max(0, m.viewport.Width-lipgloss.Width(title)))
return lipgloss.JoinHorizontal(lipgloss.Center, title, line)
}
func (m Doc) footerView() string {
info := infoStyle.Render(fmt.Sprintf("%3.f%%", m.viewport.ScrollPercent()*100))
line := strings.Repeat("─", max(0, m.viewport.Width-lipgloss.Width(info)))
return lipgloss.JoinHorizontal(lipgloss.Center, line, info)
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
func Pager(conf *Config, title, message string) (int, error) {
width := 80
scrollto := 0
if term.IsTerminal(int(os.Stdout.Fd())) {
w, _, err := term.GetSize(0)
if err == nil {
width = w
}
}
if conf.StoreProgress {
scrollto = conf.InitialProgress
}
meta := Meta{
initialprogress: scrollto,
lines: len(strings.Split(message, "\r\n")),
}
p := tea.NewProgram(
Doc{content: message, title: title, initialwidth: width, meta: &meta},
tea.WithAltScreen(), // use the full size of the terminal in its "alternate screen buffer"
tea.WithMouseCellMotion(), // turn on mouse support so we can track the mouse wheel
)
if _, err := p.Run(); err != nil {
return 0, fmt.Errorf("could not run pager:", err)
}
if conf.Debug {
fmt.Printf("scrollto: %d, last: %d, diff: %d\n",
scrollto, meta.currentline, scrollto-meta.currentline)
}
return meta.currentline, nil
}