From 430ab459d709dae03aca5d47f5b414f88f1097c5 Mon Sep 17 00:00:00 2001 From: Maas Lalani Date: Fri, 30 Sep 2022 14:30:52 -0400 Subject: [PATCH] feat(pager): gum pager for scrolling through long documents --- file/hidden_windows.go | 20 ++++++++++++++++++++ main.go | 14 ++++++++------ pager/command.go | 26 +++++++++++++------------- pager/options.go | 8 +++++--- pager/pager.go | 36 ++++++++++++++++++++++++++++++++---- style/options.go | 4 ++-- 6 files changed, 80 insertions(+), 28 deletions(-) create mode 100644 file/hidden_windows.go diff --git a/file/hidden_windows.go b/file/hidden_windows.go new file mode 100644 index 000000000..f44fdfcbb --- /dev/null +++ b/file/hidden_windows.go @@ -0,0 +1,20 @@ +//go:build windows + +package file + +import ( + "syscall" +) + +// IsHidden reports whether a file is hidden or not. +func IsHidden(file string) (bool, error) { + pointer, err := syscall.UTF16PtrFromString(file) + if err != nil { + return false, err + } + attributes, err := syscall.GetFileAttributes(pointer) + if err != nil { + return false, err + } + return attributes&syscall.FILE_ATTRIBUTE_HIDDEN != 0, nil +} diff --git a/main.go b/main.go index fbd815524..1d6f223cb 100644 --- a/main.go +++ b/main.go @@ -52,12 +52,14 @@ func main() { Summary: false, }), kong.Vars{ - "version": version, - "defaultBackground": "", - "defaultForeground": "", - "defaultMargin": "0 0", - "defaultPadding": "0 0", - "defaultUnderline": "false", + "version": version, + "defaultBorder": "none", + "defaultBorderForeground": "", + "defaultBackground": "", + "defaultForeground": "", + "defaultMargin": "0 0", + "defaultPadding": "0 0", + "defaultUnderline": "false", }, ) if err := ctx.Run(); err != nil { diff --git a/pager/command.go b/pager/command.go index 01388b3ae..77da6bac6 100644 --- a/pager/command.go +++ b/pager/command.go @@ -1,9 +1,10 @@ package pager import ( + "fmt" + "github.com/charmbracelet/bubbles/viewport" tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/glamour" "github.com/charmbracelet/gum/internal/stdin" ) @@ -15,24 +16,23 @@ func (o Options) Run() error { var err error if o.Content == "" { - o.Content, err = stdin.Read() + stdin, err := stdin.Read() if err != nil { return err } + if stdin != "" { + o.Content = stdin + } else { + return fmt.Errorf("provide some content to display") + } } - renderer, err := glamour.NewTermRenderer( - glamour.WithWordWrap(80), - ) - if err != nil { - return err - } - md, err := renderer.Render(o.Content) - vp.SetContent(md) - model := model{ - viewport: vp, - helpStyle: o.HelpStyle.ToLipgloss(), + viewport: vp, + helpStyle: o.HelpStyle.ToLipgloss(), + content: o.Content, + showLineNumbers: o.ShowLineNumbers, + lineNumberStyle: o.LineNumberStyle.ToLipgloss(), } if err != nil { return err diff --git a/pager/options.go b/pager/options.go index 808dbf546..9900d82f2 100644 --- a/pager/options.go +++ b/pager/options.go @@ -4,7 +4,9 @@ import "github.com/charmbracelet/gum/style" // Options are the options for the pager. type Options struct { - Style style.Styles `embed:"" help:"Style the pager" envprefix:"GUM_PAGER_"` - HelpStyle style.Styles `embed:"" prefix:"help." help:"Style the help text" set:"defaultForeground=241" envprefix:"GUM_PAGER_HELP_"` - Content string `arg:"" optional:"" help:"Display content to scroll"` + Style style.Styles `embed:"" help:"Style the pager" set:"defaultBorder=rounded" set:"defaultPadding=0 1" set:"defaultBorderForeground=212" envprefix:"GUM_PAGER_"` + HelpStyle style.Styles `embed:"" prefix:"help." help:"Style the help text" set:"defaultForeground=241" envprefix:"GUM_PAGER_HELP_"` + Content string `arg:"" optional:"" help:"Display content to scroll"` + ShowLineNumbers bool `help:"Show line numbers" default:"true"` + LineNumberStyle style.Styles `embed:"" prefix:"line-number." help:"Style the line numbers" set:"defaultForeground=237" envprefix:"GUM_PAGER_LINE_NUMBER_"` } diff --git a/pager/pager.go b/pager/pager.go index f82b0ca7d..f359a0e29 100644 --- a/pager/pager.go +++ b/pager/pager.go @@ -4,14 +4,21 @@ package pager import ( + "fmt" + "strings" + "github.com/charmbracelet/bubbles/viewport" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" + "github.com/mattn/go-runewidth" ) type model struct { - viewport viewport.Model - helpStyle lipgloss.Style + content string + viewport viewport.Model + helpStyle lipgloss.Style + showLineNumbers bool + lineNumberStyle lipgloss.Style } func (m model) Init() tea.Cmd { @@ -21,10 +28,31 @@ func (m model) Init() tea.Cmd { func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.WindowSizeMsg: + m.viewport.Height = msg.Height - 2 m.viewport.Width = msg.Width - m.viewport.Height = msg.Height - 1 + textStyle := lipgloss.NewStyle().Width(m.viewport.Width) + var text strings.Builder + for i, line := range strings.Split(m.content, "\n") { + line = strings.ReplaceAll(line, "\t", " ") + if m.showLineNumbers { + text.WriteString(m.lineNumberStyle.Render(fmt.Sprintf("%4d │ ", i+1))) + } + text.WriteString(textStyle.Render(runewidth.Truncate(line, m.viewport.Width, ""))) + text.WriteString("\n") + } + + diffHeight := m.viewport.Height - lipgloss.Height(text.String()) + if diffHeight > 0 && m.showLineNumbers { + remainingLines := " ~ │ " + strings.Repeat("\n ~ │ ", diffHeight-1) + text.WriteString(m.lineNumberStyle.Render(remainingLines)) + } + m.viewport.SetContent(text.String()) case tea.KeyMsg: switch msg.String() { + case "g": + m.viewport.GotoTop() + case "G": + m.viewport.GotoBottom() case "q", "ctrl+c", "esc": return m, tea.Quit } @@ -35,5 +63,5 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } func (m model) View() string { - return m.viewport.View() + "\n" + m.helpStyle.Render("↑/↓: Navigate • q: Quit") + return m.viewport.View() + m.helpStyle.Render("\n ↑/↓: Navigate • q: Quit") } diff --git a/style/options.go b/style/options.go index d71757569..97b730c4f 100644 --- a/style/options.go +++ b/style/options.go @@ -22,9 +22,9 @@ type Styles struct { Foreground string `help:"Foreground Color" default:"${defaultForeground}" group:"Style Flags" env:"FOREGROUND"` // Border - Border string `help:"Border Style" enum:"none,hidden,normal,rounded,thick,double" default:"none" group:"Style Flags" env:"BORDER"` + Border string `help:"Border Style" enum:"none,hidden,normal,rounded,thick,double" default:"${defaultBorder}" group:"Style Flags" env:"BORDER"` BorderBackground string `help:"Border Background Color" group:"Style Flags" env:"BORDER_BACKGROUND"` - BorderForeground string `help:"Border Foreground Color" group:"Style Flags" env:"BORDER_FOREGROUND"` + BorderForeground string `help:"Border Foreground Color" group:"Style Flags" default:"${defaultBorderForeground}" env:"BORDER_FOREGROUND"` // Layout Align string `help:"Text Alignment" enum:"left,center,right,bottom,middle,top" default:"left" group:"Style Flags" env:"ALIGN"`