diff --git a/README.md b/README.md index 1fe6577..eb81570 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ Features: - history - comments (comment character is `#`) - variables +- help screen uses comfortable internal pager ## Demo diff --git a/calc.go b/calc.go index 433f3b2..dd00f56 100644 --- a/calc.go +++ b/calc.go @@ -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) } diff --git a/pager.go b/pager.go new file mode 100644 index 0000000..42b4367 --- /dev/null +++ b/pager.go @@ -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) + } +}