178 lines
4.2 KiB
Go
178 lines
4.2 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/charmbracelet/bubbles/textinput"
|
|
"github.com/charmbracelet/bubbles/viewport"
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
"github.com/charmbracelet/lipgloss"
|
|
)
|
|
|
|
func runTUI(state *BrowserState, args []string) {
|
|
model := NewMainModel(state)
|
|
state.Printer = (*TUIPrinter)(model)
|
|
|
|
if len(args) > 0 {
|
|
if err := Go(state, args[0]); err != nil {
|
|
state.Printer.PrintError(err.Error())
|
|
}
|
|
}
|
|
|
|
p := tea.NewProgram(model, tea.WithAltScreen())
|
|
if _, err := p.Run(); err != nil {
|
|
state.Printer.PrintError(err.Error())
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
var (
|
|
hdrStyle = lipgloss.NewStyle().Background(lipgloss.Color("56")).Bold(true)
|
|
ftrStyle = lipgloss.NewStyle().Background(lipgloss.Color("56"))
|
|
errStyle = lipgloss.NewStyle().Background(lipgloss.Color("196")).Bold(true)
|
|
)
|
|
|
|
type MainModel struct {
|
|
State *BrowserState
|
|
Viewport viewport.Model
|
|
Prompt *textinput.Model
|
|
ErrorMsg string
|
|
}
|
|
|
|
func NewMainModel(state *BrowserState) *MainModel {
|
|
return &MainModel{
|
|
State: state,
|
|
Viewport: viewport.Model{
|
|
HighPerformanceRendering: true,
|
|
YPosition: 2,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (model *MainModel) Init() tea.Cmd {
|
|
return nil
|
|
}
|
|
|
|
func (model *MainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
if model.Prompt != nil {
|
|
return model.updatePrompt(msg)
|
|
}
|
|
|
|
m := model
|
|
var cmds []tea.Cmd
|
|
|
|
switch msg := msg.(type) {
|
|
case tea.KeyMsg:
|
|
model.ErrorMsg = ""
|
|
|
|
switch msg.String() {
|
|
case "ctrl+c", "q":
|
|
cmds = append(cmds, tea.Quit)
|
|
case "ctrl+l":
|
|
cmds = append(cmds, viewport.Sync(model.Viewport))
|
|
case "g":
|
|
lines := model.Viewport.GotoTop()
|
|
cmds = append(cmds, viewport.ViewUp(model.Viewport, lines))
|
|
case "G":
|
|
lines := model.Viewport.GotoBottom()
|
|
cmds = append(cmds, viewport.ViewDown(model.Viewport, lines))
|
|
case ":":
|
|
p := textinput.New()
|
|
model.Prompt = &p
|
|
cmds = append(cmds, p.Focus())
|
|
}
|
|
case tea.WindowSizeMsg:
|
|
model.Viewport.Width = msg.Width
|
|
model.Viewport.Height = msg.Height - 2
|
|
hdrStyle = hdrStyle.Width(msg.Width)
|
|
cmds = append(cmds, viewport.Sync(model.Viewport))
|
|
}
|
|
|
|
var cmd tea.Cmd
|
|
vp, cmd := model.Viewport.Update(msg)
|
|
model.Viewport = vp
|
|
cmds = append(cmds, cmd)
|
|
|
|
return m, tea.Batch(cmds...)
|
|
}
|
|
|
|
func (model *MainModel) updatePrompt(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
if keymsg, ok := msg.(tea.KeyMsg); ok {
|
|
model.ErrorMsg = ""
|
|
|
|
switch keymsg.String() {
|
|
case "enter":
|
|
cmd, err := ParseCommand(model.Prompt.Value())
|
|
model.Prompt = nil
|
|
if err != nil {
|
|
model.State.Printer.PrintError(err.Error())
|
|
return model, nil
|
|
}
|
|
if err := RunCommand(cmd, model.State); err != nil {
|
|
model.State.Printer.PrintError(err.Error())
|
|
return model, nil
|
|
}
|
|
return model, viewport.Sync(model.Viewport)
|
|
}
|
|
}
|
|
|
|
p, cmd := model.Prompt.Update(msg)
|
|
model.Prompt = &p
|
|
return model, cmd
|
|
}
|
|
|
|
func (model *MainModel) View() string {
|
|
return model.viewHeader() + "\n" + model.Viewport.View() + "\n" + model.viewFooter()
|
|
}
|
|
|
|
func (model *MainModel) viewHeader() string {
|
|
hdrLine := " Welcome to X-1"
|
|
if model.State.Url != nil {
|
|
hdrLine = " " + model.State.Url.String()
|
|
}
|
|
return hdrStyle.Render(hdrLine)
|
|
}
|
|
|
|
func (model *MainModel) viewFooter() string {
|
|
if model.ErrorMsg != "" {
|
|
return errStyle.Render(model.ErrorMsg)
|
|
}
|
|
|
|
var footerLine string
|
|
if model.Prompt != nil {
|
|
footerLine = ftrStyle.Width(hdrStyle.GetWidth()).Render(model.Prompt.View())
|
|
} else {
|
|
footerLine = ftrStyle.Render(" ") + ftrStyle.Copy().Italic(true).Render(model.State.DocType)
|
|
pct := ftrStyle.Render(fmt.Sprintf("%3.f%% ", model.Viewport.ScrollPercent()*100))
|
|
footerLine += ftrStyle.Render(strings.Repeat(" ", max(0, model.Viewport.Width-lipgloss.Width(footerLine)-lipgloss.Width(pct))))
|
|
footerLine += pct
|
|
}
|
|
return footerLine
|
|
}
|
|
|
|
func max(a, b int) int {
|
|
if a < b {
|
|
return b
|
|
}
|
|
return a
|
|
}
|
|
|
|
type TUIPrinter MainModel
|
|
|
|
func (p *TUIPrinter) PrintModal(state *BrowserState, contents []byte) error {
|
|
(*MainModel)(p).Viewport.SetContent(strings.TrimSuffix(string(contents), "\n"))
|
|
return nil
|
|
}
|
|
|
|
func (p *TUIPrinter) PrintPage(state *BrowserState, body string) error {
|
|
(*MainModel)(p).Viewport.SetContent(strings.TrimSuffix(body, "\n"))
|
|
return nil
|
|
}
|
|
|
|
func (p *TUIPrinter) PrintError(msg string) error {
|
|
(*MainModel)(p).ErrorMsg = msg
|
|
return nil
|
|
}
|