lipgloss styles
This commit is contained in:
parent
865c6dc230
commit
b787ef92df
|
@ -252,7 +252,7 @@ func fetch(state *BrowserState, u string, tlsConf *tls.Config) (*sliderule.Respo
|
||||||
var tofuErr *TOFUViolation
|
var tofuErr *TOFUViolation
|
||||||
|
|
||||||
if errors.As(err, &tofuErr) {
|
if errors.As(err, &tofuErr) {
|
||||||
writeError(err.Error())
|
state.Printer.PrintError(err.Error())
|
||||||
state.Readline.SetPrompt("Trust new certificate instead (y/n)? [n] ")
|
state.Readline.SetPrompt("Trust new certificate instead (y/n)? [n] ")
|
||||||
line, err := state.Readline.Readline()
|
line, err := state.Readline.Readline()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -284,7 +284,7 @@ func upload(state *BrowserState, u string, body io.Reader, tlsConf *tls.Config)
|
||||||
response, err := sliderule.NewClient(tlsConf).Upload(ctx, u, body)
|
response, err := sliderule.NewClient(tlsConf).Upload(ctx, u, body)
|
||||||
var tofuErr *TOFUViolation
|
var tofuErr *TOFUViolation
|
||||||
if errors.As(err, &tofuErr) {
|
if errors.As(err, &tofuErr) {
|
||||||
writeError(err.Error())
|
state.Printer.PrintError(err.Error())
|
||||||
state.Readline.SetPrompt("Trust new certificate instead (y/n)? [n] ")
|
state.Readline.SetPrompt("Trust new certificate instead (y/n)? [n] ")
|
||||||
line, err := state.Readline.Readline()
|
line, err := state.Readline.Readline()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -20,7 +20,7 @@ type Command struct {
|
||||||
func ParseCommand(line string) (*Command, error) {
|
func ParseCommand(line string) (*Command, error) {
|
||||||
line = strings.TrimSpace(line)
|
line = strings.TrimSpace(line)
|
||||||
if line == "" {
|
if line == "" {
|
||||||
return &Command{Name: "print"}, nil
|
return &Command{Name: "default"}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd, rest, _ := strings.Cut(line, " ")
|
cmd, rest, _ := strings.Cut(line, " ")
|
||||||
|
@ -404,6 +404,8 @@ func RunCommand(cmd *Command, state *BrowserState) error {
|
||||||
return Outline(state)
|
return Outline(state)
|
||||||
case "pipe":
|
case "pipe":
|
||||||
return Pipe(state, cmd.Args[0])
|
return Pipe(state, cmd.Args[0])
|
||||||
|
case "default":
|
||||||
|
return HandleResource(state)
|
||||||
case "print":
|
case "print":
|
||||||
return Print(state)
|
return Print(state)
|
||||||
case "links":
|
case "links":
|
||||||
|
|
52
handlers.go
52
handlers.go
|
@ -8,6 +8,7 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/charmbracelet/lipgloss"
|
||||||
"tildegit.org/tjp/sliderule"
|
"tildegit.org/tjp/sliderule"
|
||||||
"tildegit.org/tjp/sliderule/gemini"
|
"tildegit.org/tjp/sliderule/gemini"
|
||||||
"tildegit.org/tjp/sliderule/gemini/gemtext"
|
"tildegit.org/tjp/sliderule/gemini/gemtext"
|
||||||
|
@ -121,7 +122,7 @@ func parseGophermapDoc(body []byte, softWrap int) (string, []Link, error) {
|
||||||
Text: item.Display,
|
Text: item.Display,
|
||||||
Target: fmtGopherURL(item.Type, item.Selector, item.Hostname, item.Port),
|
Target: fmtGopherURL(item.Type, item.Selector, item.Hostname, item.Port),
|
||||||
})
|
})
|
||||||
if _, err := b.WriteString(fmt.Sprintf("[%d]%s %s%s%s\n", i, padding(i, width), linkStyle, item.Display, ansiClear)); err != nil {
|
if _, err := b.WriteString(fmt.Sprintf("[%d]%s %s\n", i, padding(i, width), linkStyle.Render(item.Display))); err != nil {
|
||||||
return "", nil, err
|
return "", nil, err
|
||||||
}
|
}
|
||||||
i += 1
|
i += 1
|
||||||
|
@ -131,16 +132,15 @@ func parseGophermapDoc(body []byte, softWrap int) (string, []Link, error) {
|
||||||
return b.String(), l, nil
|
return b.String(), l, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
var (
|
||||||
ansiClear = "\x1b[0m"
|
linkStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("33"))
|
||||||
linkStyle = "\x1b[38;5;33m"
|
promptStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("39"))
|
||||||
promptStyle = "\x1b[38;5;39m"
|
quoteStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("208")).Italic(true)
|
||||||
quoteStyle = "\x1b[38;5;208m\x1b[3m"
|
rawStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("249"))
|
||||||
rawStyle = "\x1b[38;5;249m"
|
h1Style = lipgloss.NewStyle().Foreground(lipgloss.Color("154")).Bold(true).Underline(true)
|
||||||
h1Style = "\x1b[38;5;154m\x1b[1m\x1b[4m"
|
h2Style = lipgloss.NewStyle().Foreground(lipgloss.Color("50")).Underline(true)
|
||||||
h2Style = "\x1b[38;5;50m\x1b[4m"
|
h3Style = lipgloss.NewStyle().Foreground(lipgloss.Color("6")).Underline(true)
|
||||||
h3Style = "\x1b[38;5;6m\x1b[4m"
|
listStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("3"))
|
||||||
listStyle = "\x1b[38;5;3m"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func parseGemtextDoc(body []byte, softWrap int) (string, []Link, error) {
|
func parseGemtextDoc(body []byte, softWrap int) (string, []Link, error) {
|
||||||
|
@ -183,22 +183,30 @@ func parseGemtextDoc(body []byte, softWrap int) (string, []Link, error) {
|
||||||
if len(label) == 0 {
|
if len(label) == 0 {
|
||||||
label = ll.URL()
|
label = ll.URL()
|
||||||
}
|
}
|
||||||
if _, err := b.WriteString(fmt.Sprintf("[%d]%s %s%s%s\n", i, padding(i, width), linkStyle, label, ansiClear)); err != nil {
|
for j, line := range fold(label, softWrap) {
|
||||||
return "", nil, err
|
var prefix string
|
||||||
|
if j == 0 {
|
||||||
|
prefix = fmt.Sprintf("[%d]%s ", i, padding(i, width))
|
||||||
|
} else {
|
||||||
|
prefix = strings.Repeat(" ", width+3)
|
||||||
|
}
|
||||||
|
if _, err := fmt.Fprintf(&b, "%s%s\n", prefix, linkStyle.Render(line)); err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
i += 1
|
i += 1
|
||||||
case gemtext.LineTypeQuote:
|
case gemtext.LineTypeQuote:
|
||||||
q := item.(gemtext.QuoteLine)
|
q := item.(gemtext.QuoteLine)
|
||||||
for _, line := range fold(q.Body(), softWrap-1) {
|
for _, line := range fold(q.Body(), softWrap-1) {
|
||||||
line = strings.TrimSpace(line)
|
line = strings.TrimSpace(line)
|
||||||
if _, err := b.WriteString(textpad + "> " + quoteStyle + line + ansiClear + "\n"); err != nil {
|
if _, err := b.WriteString(textpad + "> " + quoteStyle.Render(line) + "\n"); err != nil {
|
||||||
return "", nil, err
|
return "", nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case gemtext.LineTypePreformatToggle:
|
case gemtext.LineTypePreformatToggle:
|
||||||
case gemtext.LineTypePreformattedText:
|
case gemtext.LineTypePreformattedText:
|
||||||
for _, line := range fold(item.String(), softWrap) {
|
for _, line := range fold(item.String(), softWrap) {
|
||||||
if _, err := b.WriteString(textpad + rawStyle + line + ansiClear + "\n"); err != nil {
|
if _, err := b.WriteString(textpad + rawStyle.Render(line) + "\n"); err != nil {
|
||||||
return "", nil, err
|
return "", nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -210,19 +218,19 @@ func parseGemtextDoc(body []byte, softWrap int) (string, []Link, error) {
|
||||||
fallthrough
|
fallthrough
|
||||||
case gemtext.LineTypeHeading1:
|
case gemtext.LineTypeHeading1:
|
||||||
hLevel += 1
|
hLevel += 1
|
||||||
var color string
|
var style lipgloss.Style
|
||||||
switch hLevel {
|
switch hLevel {
|
||||||
case 1:
|
case 1:
|
||||||
color = h1Style
|
style = h1Style
|
||||||
case 2:
|
case 2:
|
||||||
color = h2Style
|
style = h2Style
|
||||||
case 3:
|
case 3:
|
||||||
color = h3Style
|
style = h3Style
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, line := range fold(item.String(), softWrap) {
|
for _, line := range fold(item.String(), softWrap) {
|
||||||
line = strings.TrimRight(line, "\r\n")
|
line = strings.TrimRight(line, "\r\n")
|
||||||
if _, err := b.WriteString(textpad + color + line + ansiClear + "\n"); err != nil {
|
if _, err := b.WriteString(textpad + style.Render(line) + "\n"); err != nil {
|
||||||
return "", nil, err
|
return "", nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -233,7 +241,7 @@ func parseGemtextDoc(body []byte, softWrap int) (string, []Link, error) {
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
lpad = "* "
|
lpad = "* "
|
||||||
}
|
}
|
||||||
if _, err := b.WriteString(textpad + listStyle + lpad + line + ansiClear + "\n"); err != nil {
|
if _, err := b.WriteString(textpad + lpad + listStyle.Render(line) + "\n"); err != nil {
|
||||||
return "", nil, err
|
return "", nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -307,5 +315,5 @@ func numberWidth(i int) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
func padding(num int, width int) string {
|
func padding(num int, width int) string {
|
||||||
return string(bytes.Repeat([]byte{' '}, width-numberWidth(num)))
|
return strings.Repeat(" ", width-numberWidth(num))
|
||||||
}
|
}
|
||||||
|
|
30
main.go
30
main.go
|
@ -2,19 +2,18 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/chzyer/readline"
|
"github.com/chzyer/readline"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
cmdMode = flag.String("c", "", "")
|
cmdMode = flag.String("c", "", "")
|
||||||
helpMode = flag.Bool("h", false, "")
|
helpMode = flag.Bool("h", false, "")
|
||||||
quietMode = flag.Bool("q", false, "")
|
quietMode = flag.Bool("q", false, "")
|
||||||
|
promptMode = flag.Bool("p", false, "")
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@ -44,8 +43,11 @@ func main() {
|
||||||
state.Quiet = true
|
state.Quiet = true
|
||||||
}
|
}
|
||||||
|
|
||||||
runInteractivePrompt(state, flag.Args())
|
if *promptMode {
|
||||||
// runTUI(state, flag.Args())
|
runInteractivePrompt(state, flag.Args())
|
||||||
|
} else {
|
||||||
|
runTUI(state, flag.Args())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildReadline(prompt string, conf *Config) (*readline.Instance, error) {
|
func buildReadline(prompt string, conf *Config) (*readline.Instance, error) {
|
||||||
|
@ -91,7 +93,7 @@ func buildInitialState() (*BrowserState, error) {
|
||||||
}
|
}
|
||||||
state.Identities = idents
|
state.Identities = idents
|
||||||
|
|
||||||
rl, err := buildReadline(Prompt, conf)
|
rl, err := buildReadline(prompt(), conf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -105,12 +107,12 @@ func runInteractivePrompt(state *BrowserState, args []string) {
|
||||||
|
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
if err := Go(state, args[0]); err != nil {
|
if err := Go(state, args[0]); err != nil {
|
||||||
writeError(err.Error())
|
state.Printer.PrintError(err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
state.Readline.SetPrompt(Prompt)
|
state.Readline.SetPrompt(prompt())
|
||||||
line, err := state.Readline.Readline()
|
line, err := state.Readline.Readline()
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
break
|
break
|
||||||
|
@ -120,7 +122,7 @@ func runInteractivePrompt(state *BrowserState, args []string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := handleCmdLine(state, line); err != nil {
|
if err := handleCmdLine(state, line); err != nil {
|
||||||
writeError(err.Error())
|
state.Printer.PrintError(err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -136,8 +138,10 @@ func handleCmdLine(state *BrowserState, line string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
const Prompt = promptStyle + "X-1" + ansiClear + "> "
|
func prompt() string {
|
||||||
|
return promptStyle.Render("X-1") + "> "
|
||||||
|
}
|
||||||
|
|
||||||
func writeError(msg string) {
|
func writeError(msg string) {
|
||||||
fmt.Fprintf(os.Stdout, "\x1b[31m%s\x1b[0m\n", msg)
|
_ = PromptPrinter{}.PrintError(msg)
|
||||||
}
|
}
|
||||||
|
|
14
state.go
14
state.go
|
@ -3,10 +3,12 @@ package main
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
|
||||||
|
"github.com/charmbracelet/lipgloss"
|
||||||
"github.com/chzyer/readline"
|
"github.com/chzyer/readline"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -69,16 +71,17 @@ func NewBrowserState(conf *Config) *BrowserState {
|
||||||
type Printer interface {
|
type Printer interface {
|
||||||
PrintModal(*BrowserState, []byte) error
|
PrintModal(*BrowserState, []byte) error
|
||||||
PrintPage(*BrowserState, string) error
|
PrintPage(*BrowserState, string) error
|
||||||
|
PrintError(string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type PromptPrinter struct{}
|
type PromptPrinter struct{}
|
||||||
|
|
||||||
func (_ PromptPrinter) PrintModal(state *BrowserState, contents []byte) error {
|
func (PromptPrinter) PrintModal(state *BrowserState, contents []byte) error {
|
||||||
_, err := os.Stdout.Write(contents)
|
_, err := os.Stdout.Write(contents)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_ PromptPrinter) PrintPage(state *BrowserState, body string) error {
|
func (PromptPrinter) PrintPage(state *BrowserState, body string) error {
|
||||||
if state.Quiet {
|
if state.Quiet {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -105,3 +108,10 @@ func (_ PromptPrinter) PrintPage(state *BrowserState, body string) error {
|
||||||
return errors.New("invalid 'pager' value in configuration")
|
return errors.New("invalid 'pager' value in configuration")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var promptErrorStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("196")).Bold(true)
|
||||||
|
|
||||||
|
func (PromptPrinter) PrintError(msg string) error {
|
||||||
|
_, err := fmt.Println(promptErrorStyle.Render(msg))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
3
tls.go
3
tls.go
|
@ -99,8 +99,7 @@ func createIdentity(state *BrowserState, name string) (*tls.Config, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
snLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
|
||||||
serialNumber, err := rand.Int(rand.Reader, snLimit)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
197
tui.go
197
tui.go
|
@ -1,80 +1,177 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
"github.com/charmbracelet/bubbles/textinput"
|
||||||
"github.com/charmbracelet/bubbles/viewport"
|
"github.com/charmbracelet/bubbles/viewport"
|
||||||
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
|
"github.com/charmbracelet/lipgloss"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TUIModel struct {
|
|
||||||
State *BrowserState
|
|
||||||
Viewport viewport.Model
|
|
||||||
inited bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewTUIModel(state *BrowserState) *TUIModel {
|
|
||||||
return &TUIModel{State: state}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (model *TUIModel) Init() tea.Cmd {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (model *TUIModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
||||||
switch msg := msg.(type) {
|
|
||||||
case tea.KeyMsg:
|
|
||||||
switch msg.String() {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
var cmd tea.Cmd
|
|
||||||
model.Viewport, cmd = model.Viewport.Update(msg)
|
|
||||||
|
|
||||||
return model, cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func (model *TUIModel) View() string {
|
|
||||||
return model.Viewport.View()
|
|
||||||
}
|
|
||||||
|
|
||||||
func runTUI(state *BrowserState, args []string) {
|
func runTUI(state *BrowserState, args []string) {
|
||||||
model := NewTUIModel(state)
|
model := NewMainModel(state)
|
||||||
state.Printer = (*TUIPrinter)(model)
|
state.Printer = (*TUIPrinter)(model)
|
||||||
|
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
if err := Go(state, args[0]); err != nil {
|
if err := Go(state, args[0]); err != nil {
|
||||||
writeError(err.Error())
|
state.Printer.PrintError(err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
p := tea.NewProgram(model, tea.WithAltScreen())
|
p := tea.NewProgram(model, tea.WithAltScreen())
|
||||||
if _, err := p.Run(); err != nil {
|
if _, err := p.Run(); err != nil {
|
||||||
writeError(err.Error())
|
state.Printer.PrintError(err.Error())
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type TUIPrinter TUIModel
|
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 {
|
func (p *TUIPrinter) PrintModal(state *BrowserState, contents []byte) error {
|
||||||
(*TUIModel)(p).Viewport.SetContent(string(contents))
|
(*MainModel)(p).Viewport.SetContent(strings.TrimSuffix(string(contents), "\n"))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *TUIPrinter) PrintPage(state *BrowserState, body string) error {
|
func (p *TUIPrinter) PrintPage(state *BrowserState, body string) error {
|
||||||
(*TUIModel)(p).Viewport.SetContent(body)
|
(*MainModel)(p).Viewport.SetContent(strings.TrimSuffix(body, "\n"))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *TUIPrinter) PrintError(msg string) error {
|
||||||
|
(*MainModel)(p).ErrorMsg = msg
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue