diff options
| author | tjp <tjp@ctrl-c.club> | 2024-01-23 22:42:18 -0700 |
|---|---|---|
| committer | tjp <tjp@ctrl-c.club> | 2024-01-23 22:42:18 -0700 |
| commit | 865c6dc23099e129cb3ba9709b2926734144e605 (patch) | |
| tree | 6824a2de82e842ea79554b581ceb0db328235c46 | |
| parent | dd2a06c1e1391fe6242015330b7c61fa37fd67cc (diff) | |
Printer abstraction
| -rw-r--r-- | actions.go | 37 | ||||
| -rw-r--r-- | go.mod | 6 | ||||
| -rw-r--r-- | go.sum | 8 | ||||
| -rw-r--r-- | main.go | 20 | ||||
| -rw-r--r-- | state.go | 45 | ||||
| -rw-r--r-- | tui.go | 57 |
6 files changed, 122 insertions, 51 deletions
@@ -559,43 +559,20 @@ func parseURL(str string, state *BrowserState, defaultScheme string) (*url.URL, } func print(state *BrowserState) error { - if state.Quiet { - return nil - } - - defer func() { state.Modal = nil }() - - if state.Body == nil && state.Modal == nil { - return ErrMustBeOnAPage - } - out := []byte(state.Formatted) if state.Modal != nil { - out = state.Modal + defer func() { state.Modal = nil }() + return state.Printer.PrintModal(state, state.Modal) } - if state.Modal != nil || state.Pager == "never" { - _, err := os.Stdout.Write(out) - return err + if state.Body == nil { + return ErrMustBeOnAPage } - lessarg := []string{} - switch state.Pager { - case "auto": - lessarg = []string{"-F"} - fallthrough - case "always": - less, err := exec.LookPath("less") - if err != nil { - return err - } - cmd := exec.Command(less, lessarg...) - cmd.Stdin = bytes.NewBuffer(out) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - return cmd.Run() + if state.Quiet { + return nil } - return errors.New("invalid 'pager' value in configuration") + return state.Printer.PrintPage(state, state.Formatted) } func Print(state *BrowserState) error { @@ -10,21 +10,23 @@ require ( require ( github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/charmbracelet/bubbles v0.17.1 // indirect github.com/charmbracelet/bubbletea v0.25.0 // indirect + github.com/charmbracelet/lipgloss v0.9.1 // indirect github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect github.com/go-kit/log v0.2.1 // indirect github.com/go-logfmt/logfmt v0.5.1 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-isatty v0.0.18 // indirect github.com/mattn/go-localereader v0.0.1 // indirect - github.com/mattn/go-runewidth v0.0.14 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/reflow v0.3.0 // indirect github.com/muesli/termenv v0.15.2 // indirect github.com/rivo/uniseg v0.2.0 // indirect golang.org/x/sync v0.1.0 // indirect - golang.org/x/sys v0.7.0 // indirect + golang.org/x/sys v0.12.0 // indirect golang.org/x/term v0.6.0 // indirect golang.org/x/text v0.3.8 // indirect ) @@ -2,8 +2,12 @@ github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8 github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/charmbracelet/bubbles v0.17.1 h1:0SIyjOnkrsfDo88YvPgAWvZMwXe26TP6drRvmkjyUu4= +github.com/charmbracelet/bubbles v0.17.1/go.mod h1:9HxZWlkCqz2PRwsCbYl7a3KXvGzFaDHpYbSYMJ+nE3o= github.com/charmbracelet/bubbletea v0.25.0 h1:bAfwk7jRz7FKFl9RzlIULPkStffg5k6pNt5dywy4TcM= github.com/charmbracelet/bubbletea v0.25.0/go.mod h1:EN3QDR1T5ZdWmdfDzYcqOCAps45+QIJbLOBxmVNWNNg= +github.com/charmbracelet/lipgloss v0.9.1 h1:PNyd3jvaJbg4jRHKWXnCj1akQm4rh8dbEzN1p/u1KWg= +github.com/charmbracelet/lipgloss v0.9.1/go.mod h1:1mPmG4cxScwUQALAAnacHaigiiHB9Pmr+v1VEawJl6I= github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM= github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI= @@ -27,6 +31,8 @@ github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+Ei github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34= github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= @@ -50,6 +56,8 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= @@ -44,14 +44,8 @@ func main() { state.Quiet = true } - if urls := flag.Args(); len(urls) > 0 { - if err := Go(state, urls[0]); err != nil { - writeError(err.Error()) - } - } - - // runInteractivePrompt(state) - runTUI(state) + runInteractivePrompt(state, flag.Args()) + // runTUI(state, flag.Args()) } func buildReadline(prompt string, conf *Config) (*readline.Instance, error) { @@ -106,7 +100,15 @@ func buildInitialState() (*BrowserState, error) { return state, nil } -func runInteractivePrompt(state *BrowserState) { +func runInteractivePrompt(state *BrowserState, args []string) { + state.Printer = PromptPrinter{} + + if len(args) > 0 { + if err := Go(state, args[0]); err != nil { + writeError(err.Error()) + } + } + for { state.Readline.SetPrompt(Prompt) line, err := state.Readline.Readline() @@ -1,7 +1,11 @@ package main import ( + "bytes" + "errors" "net/url" + "os" + "os/exec" "github.com/chzyer/readline" ) @@ -21,6 +25,7 @@ type BrowserState struct { CurrentTour *Tour Readline *readline.Instance + Printer Printer } type History struct { @@ -60,3 +65,43 @@ func NewBrowserState(conf *Config) *BrowserState { state.CurrentTour = &state.DefaultTour return state } + +type Printer interface { + PrintModal(*BrowserState, []byte) error + PrintPage(*BrowserState, string) error +} + +type PromptPrinter struct{} + +func (_ PromptPrinter) PrintModal(state *BrowserState, contents []byte) error { + _, err := os.Stdout.Write(contents) + return err +} + +func (_ PromptPrinter) PrintPage(state *BrowserState, body string) error { + if state.Quiet { + return nil + } + + lessarg := []string{} + switch state.Pager { + case "auto": + lessarg = []string{"-F"} + fallthrough + case "always": + less, err := exec.LookPath("less") + if err != nil { + return err + } + cmd := exec.Command(less, lessarg...) + cmd.Stdin = bytes.NewBufferString(body) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + return cmd.Run() + case "never": + _, err := os.Stdout.WriteString(body) + return err + default: + return errors.New("invalid 'pager' value in configuration") + } +} @@ -4,40 +4,77 @@ import ( "os" tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/bubbles/viewport" ) type TUIModel struct { State *BrowserState + Viewport viewport.Model + inited bool } -func NewTUIModel(state *BrowserState) TUIModel { - return TUIModel{State: state} +func NewTUIModel(state *BrowserState) *TUIModel { + return &TUIModel{State: state} } -func (model TUIModel) Init() tea.Cmd { +func (model *TUIModel) Init() tea.Cmd { return nil } -func (model TUIModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +func (model *TUIModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.KeyMsg: switch msg.String() { - case "ctrl+c", "ctrl+d", "q": + case "ctrl+c", "q": return model, tea.Quit + case "g": + model.Viewport.GotoTop() + return model, nil + case "G": + model.Viewport.GotoBottom() + return model, nil } + case tea.WindowSizeMsg: + model.inited = true + model.Viewport.Width = msg.Width + model.Viewport.Height = msg.Height - 1 } - return model, nil + var cmd tea.Cmd + model.Viewport, cmd = model.Viewport.Update(msg) + + return model, cmd } -func (model TUIModel) View() string { - return "pardon our dust" +func (model *TUIModel) View() string { + return model.Viewport.View() } -func runTUI(state *BrowserState) { - p := tea.NewProgram(NewTUIModel(state)) +func runTUI(state *BrowserState, args []string) { + model := NewTUIModel(state) + state.Printer = (*TUIPrinter)(model) + + if len(args) > 0 { + if err := Go(state, args[0]); err != nil { + writeError(err.Error()) + } + } + + p := tea.NewProgram(model, tea.WithAltScreen()) if _, err := p.Run(); err != nil { writeError(err.Error()) os.Exit(1) } } + +type TUIPrinter TUIModel + +func (p *TUIPrinter) PrintModal(state *BrowserState, contents []byte) error { + (*TUIModel)(p).Viewport.SetContent(string(contents)) + return nil +} + +func (p *TUIPrinter) PrintPage(state *BrowserState, body string) error { + (*TUIModel)(p).Viewport.SetContent(body) + return nil +} |
