added bubbletea pager

This commit is contained in:
2025-08-08 12:20:59 +02:00
parent d430a45384
commit f977b56815
3 changed files with 138 additions and 19 deletions

View File

@@ -23,6 +23,7 @@ Features:
- history
- comments (comment character is `#`)
- variables
- help screen uses comfortable internal pager
## Demo

38
calc.go
View File

@@ -78,6 +78,12 @@ erf erfc erfcinv erfinv exp exp2 expm1 floor gamma ilogb j0 j1 log
log10 log1p log2 logb pow round roundtoeven sin sinh tan tanh trunc y0
y1 copysign dim hypot
Converter functions:
cm-to-inch yards-to-meters bytes-to-kilobytes
inch-to-cm meters-to-yards bytes-to-megabytes
gallons-to-liters miles-to-kilometers bytes-to-gigabytes
liters-to-gallons kilometers-to-miles bytes-to-terabytes
Batch functions:
sum sum of all values (alias: +)
max max of all values
@@ -579,46 +585,40 @@ func sortcommands(hash Commands) []string {
}
func (c *Calc) PrintHelp() {
fmt.Println("Available configuration commands:")
output := "Available configuration commands:\n"
for _, name := range sortcommands(c.SettingsCommands) {
fmt.Printf("%-20s %s\n", name, c.SettingsCommands[name].Help)
output += fmt.Sprintf("%-20s %s\n", name, c.SettingsCommands[name].Help)
}
fmt.Println()
fmt.Println("Available show commands:")
output += "\nAvailable show commands:\n"
for _, name := range sortcommands(c.ShowCommands) {
fmt.Printf("%-20s %s\n", name, c.ShowCommands[name].Help)
output += fmt.Sprintf("%-20s %s\n", name, c.ShowCommands[name].Help)
}
fmt.Println()
fmt.Println("Available stack manipulation commands:")
output += "\nAvailable stack manipulation commands:\n"
for _, name := range sortcommands(c.StackCommands) {
fmt.Printf("%-20s %s\n", name, c.StackCommands[name].Help)
output += fmt.Sprintf("%-20s %s\n", name, c.StackCommands[name].Help)
}
fmt.Println()
fmt.Println("Other commands:")
output += "\nOther commands:\n"
for _, name := range sortcommands(c.Commands) {
fmt.Printf("%-20s %s\n", name, c.Commands[name].Help)
output += fmt.Sprintf("%-20s %s\n", name, c.Commands[name].Help)
}
fmt.Println()
fmt.Println(Help)
output += "\n" + Help
// append lua functions, if any
if len(LuaFuncs) > 0 {
fmt.Println("Lua functions:")
output += "\nLua functions:\n"
for name, function := range LuaFuncs {
fmt.Printf("%-20s %s\n", name, function.help)
output += fmt.Sprintf("%-20s %s\n", name, function.help)
}
}
Pager(output)
}

118
pager.go Normal file
View File

@@ -0,0 +1,118 @@
package main
// 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"
)
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)
}()
)
type model struct {
content string
ready bool
viewport viewport.Model
}
func (m model) Init() tea.Cmd {
return nil
}
func (m model) 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(m.content)
m.ready = true
} else {
m.viewport.Width = msg.Width
m.viewport.Height = msg.Height - verticalMarginHeight
}
}
// 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 model) View() string {
if !m.ready {
return "\n Initializing..."
}
return fmt.Sprintf("%s\n%s\n%s", m.headerView(), m.viewport.View(), m.footerView())
}
func (m model) headerView() string {
title := titleStyle.Render("RPN Help Overview")
line := strings.Repeat("─", max(0, m.viewport.Width-lipgloss.Width(title)))
return lipgloss.JoinHorizontal(lipgloss.Center, title, line)
}
func (m model) 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(message string) {
p := tea.NewProgram(
model{content: message},
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 {
fmt.Println("could not run pager:", err)
os.Exit(1)
}
}