summaryrefslogtreecommitdiff
path: root/actions.go
diff options
context:
space:
mode:
authortjp <tjp@ctrl-c.club>2024-01-17 08:55:42 -0700
committertjp <tjp@ctrl-c.club>2024-01-17 08:55:42 -0700
commitdd2a06c1e1391fe6242015330b7c61fa37fd67cc (patch)
tree8b07e964f40f70de898046e5435c8932768f2b21 /actions.go
parentdfebc9013b5414e9a3c5f940704e831c31ce35d2 (diff)
start of a bubbletea TUI
Diffstat (limited to 'actions.go')
-rw-r--r--actions.go148
1 files changed, 95 insertions, 53 deletions
diff --git a/actions.go b/actions.go
index c91e479..c11e677 100644
--- a/actions.go
+++ b/actions.go
@@ -32,13 +32,22 @@ var (
ErrCantMoveRelative = errors.New("next/previous only work after navigating to a link on a page")
ErrAlreadyAtTop = errors.New("already at the site root")
ErrInvalidNumericLink = errors.New("no link with that number")
- ErrInvalidLink = errors.New("that doesn't look like a valid URL")
ErrSaveNeedsFilename = errors.New("save requires a filename argument")
ErrInvalidMarkArgs = errors.New("mark what?")
ErrInvalidTourArgs = errors.New("tour what?")
ErrOnlyTextGemini = errors.New("that is only supported for text/gemini pages")
)
+func ErrInvalidLink(invalidURL string) error {
+ return invalidLinkErr(invalidURL)
+}
+
+type invalidLinkErr string
+
+func (ie invalidLinkErr) Error() string {
+ return fmt.Sprintf("that doesn't look like a valid URL: %s", string(ie))
+}
+
func About(_ *BrowserState) error {
_, err := fmt.Println(`
...
@@ -73,13 +82,13 @@ It was written by TJP and released to the public domain.
return err
}
-func Navigate(state *BrowserState, target *url.URL, navIndex int, conf *Config) error {
+func Navigate(state *BrowserState, target *url.URL, navIndex int) error {
if state.Url == nil || target.String() != state.Url.String() {
pushHistory(state, target, navIndex)
}
state.Modal = nil
- return Reload(state, conf)
+ return Reload(state)
}
func pushHistory(state *BrowserState, target *url.URL, navIndex int) {
@@ -91,6 +100,25 @@ func pushHistory(state *BrowserState, target *url.URL, navIndex int) {
NavIndex: navIndex,
}
hist.Forward = state.History
+
+ purgeOldHistory(state)
+}
+
+func purgeOldHistory(state *BrowserState) {
+ if state.Depth <= state.SavedHistoryDepth {
+ return
+ }
+
+ d := state.SavedHistoryDepth
+ h := state.History
+ for d > 0 {
+ h = h.Back
+ d -= 1
+ }
+
+ h.Body = nil
+ h.Formatted = ""
+ h.Links = nil
}
func gopherURL(u *url.URL) (string, sliderule.Status) {
@@ -103,7 +131,7 @@ func gopherURL(u *url.URL) (string, sliderule.Status) {
return clone.String(), sliderule.Status(itemType)
}
-func Reload(state *BrowserState, conf *Config) error {
+func Reload(state *BrowserState) error {
if state.Url == nil {
return ErrMustBeOnAPage
}
@@ -144,9 +172,9 @@ func Reload(state *BrowserState, conf *Config) error {
}
}
+ if state.Url.Scheme == "gemini" {
outer:
- for {
- if state.Url.Scheme == "gemini" {
+ for {
switch response.Status {
case gemini.StatusInput:
state.Readline.SetPrompt(response.Meta.(string) + " ")
@@ -178,23 +206,33 @@ outer:
default:
return fmt.Errorf("gemini response %s: %s", gemini.StatusName(response.Status), response.Meta.(string))
}
- } else {
- break
}
}
state.DocType = docType(state.Url, response)
+ state.Url = returnedURL(state.Url, response)
state.Body, err = io.ReadAll(response.Body)
if err != nil {
return err
}
- state.Formatted, state.Links, err = parseDoc(state.DocType, state.Body, conf)
+ state.Formatted, state.Links, err = parseDoc(state.DocType, state.Body, state.Config)
if err != nil {
return err
}
- return HandleResource(state, conf)
+ return HandleResource(state)
+}
+
+func returnedURL(requested *url.URL, response *sliderule.Response) *url.URL {
+ _, gopherType := gopherURL(requested)
+ if gopherType == 0 {
+ return response.Request.URL
+ }
+
+ u := *response.Request.URL
+ u.Path = "/" + string([]byte{byte(gopherType)}) + u.Path
+ return &u
}
func requestCtx(timeout time.Duration) (context.Context, context.CancelFunc) {
@@ -206,7 +244,7 @@ func requestCtx(timeout time.Duration) (context.Context, context.CancelFunc) {
}
func fetch(state *BrowserState, u string, tlsConf *tls.Config) (*sliderule.Response, error) {
- ctx, cancel := requestCtx(state.Timeout)
+ ctx, cancel := requestCtx(state.Timeout.Duration)
defer cancel()
tlsConf.ClientSessionCache = nil
@@ -229,7 +267,7 @@ func fetch(state *BrowserState, u string, tlsConf *tls.Config) (*sliderule.Respo
return nil, err
}
- ctx, cancel = requestCtx(state.Timeout)
+ ctx, cancel = requestCtx(state.Timeout.Duration)
defer cancel()
return sliderule.NewClient(tlsConf).Fetch(ctx, u)
} else if err != nil {
@@ -239,7 +277,7 @@ func fetch(state *BrowserState, u string, tlsConf *tls.Config) (*sliderule.Respo
}
func upload(state *BrowserState, u string, body io.Reader, tlsConf *tls.Config) (*sliderule.Response, error) {
- ctx, cancel := requestCtx(state.Timeout)
+ ctx, cancel := requestCtx(state.Timeout.Duration)
defer cancel()
tlsConf.ClientSessionCache = nil
@@ -261,7 +299,7 @@ func upload(state *BrowserState, u string, body io.Reader, tlsConf *tls.Config)
return nil, err
}
- ctx, cancel = requestCtx(state.Timeout)
+ ctx, cancel = requestCtx(state.Timeout.Duration)
defer cancel()
return sliderule.NewClient(tlsConf).Upload(ctx, u, body)
} else if err != nil {
@@ -327,21 +365,25 @@ func back(state *BrowserState) error {
if state.Back == nil {
return ErrNoPreviousHistory
}
- state.History = state.Back
state.Modal = nil
+ state.History = state.Back
+
+ if state.Body == nil {
+ return Reload(state)
+ }
return nil
}
-func Back(state *BrowserState, conf *Config, num int) error {
+func Back(state *BrowserState, num int) error {
for i := 0; i < num; i += 1 {
if err := back(state); err != nil {
return err
}
}
- return HandleResource(state, conf)
+ return HandleResource(state)
}
-func Forward(state *BrowserState, conf *Config, num int) error {
+func Forward(state *BrowserState, num int) error {
for i := 0; i < num; i += 1 {
if state.Forward == nil {
return ErrNoNextHistory
@@ -350,10 +392,10 @@ func Forward(state *BrowserState, conf *Config, num int) error {
}
state.Modal = nil
- return HandleResource(state, conf)
+ return HandleResource(state)
}
-func Next(state *BrowserState, conf *Config) error {
+func Next(state *BrowserState) error {
switch state.NavIndex {
case -1:
return ErrCantMoveRelative
@@ -369,10 +411,10 @@ func Next(state *BrowserState, conf *Config) error {
u := state.Url.ResolveReference(state.Links[index].Target)
- return Navigate(state, u, index, conf)
+ return Navigate(state, u, index)
}
-func Previous(state *BrowserState, conf *Config) error {
+func Previous(state *BrowserState) error {
switch state.NavIndex {
case -1:
return ErrCantMoveRelative
@@ -388,10 +430,10 @@ func Previous(state *BrowserState, conf *Config) error {
u := state.Url.ResolveReference(state.Links[index].Target)
- return Navigate(state, u, index, conf)
+ return Navigate(state, u, index)
}
-func Root(state *BrowserState, tilde bool, conf *Config) error {
+func Root(state *BrowserState, tilde bool) error {
if state.Url == nil {
return ErrMustBeOnAPage
}
@@ -411,10 +453,10 @@ func Root(state *BrowserState, tilde bool, conf *Config) error {
u.Path = base
}
- return Navigate(state, &u, -1, conf)
+ return Navigate(state, &u, -1)
}
-func Up(state *BrowserState, conf *Config) error {
+func Up(state *BrowserState) error {
if state.Url == nil {
return ErrMustBeOnAPage
}
@@ -429,16 +471,16 @@ func Up(state *BrowserState, conf *Config) error {
u.RawQuery = ""
u.Fragment = ""
- return Navigate(state, &u, -1, conf)
+ return Navigate(state, &u, -1)
}
-func Go(state *BrowserState, dest string, conf *Config) error {
- u, idx, err := parseURL(dest, state, conf.DefaultScheme)
+func Go(state *BrowserState, dest string) error {
+ u, idx, err := parseURL(dest, state, state.DefaultScheme)
if err != nil {
return err
}
- return Navigate(state, u, idx, conf)
+ return Navigate(state, u, idx)
}
func parseURL(str string, state *BrowserState, defaultScheme string) (*url.URL, int, error) {
@@ -458,7 +500,7 @@ func parseURL(str string, state *BrowserState, defaultScheme string) (*url.URL,
} else if strings.HasPrefix(str, "t:") {
i, err := strconv.Atoi(str[2:])
if err != nil {
- return nil, -1, ErrInvalidLink
+ return nil, -1, ErrInvalidLink(str)
}
if i < 0 || i >= len(state.CurrentTour.Links) {
@@ -468,10 +510,10 @@ func parseURL(str string, state *BrowserState, defaultScheme string) (*url.URL,
} else if strings.HasPrefix(str, "t[") {
idx := strings.IndexByte(str, ']')
if idx < 0 || idx >= len(str)-2 || str[idx+1] != ':' {
- return nil, -1, ErrInvalidLink
+ return nil, -1, ErrInvalidLink(str)
}
if i, err := strconv.Atoi(str[idx+2:]); err != nil {
- return nil, -1, ErrInvalidLink
+ return nil, -1, ErrInvalidLink(str)
} else {
_, tour, err := findTour(state, str[2:idx])
if err != nil {
@@ -499,7 +541,7 @@ func parseURL(str string, state *BrowserState, defaultScheme string) (*url.URL,
i = -1
u, err = url.Parse(str)
if err != nil {
- return nil, -1, ErrInvalidLink
+ return nil, -1, ErrInvalidLink(str)
}
if u.Scheme == "" {
u.Scheme = defaultScheme
@@ -510,7 +552,7 @@ func parseURL(str string, state *BrowserState, defaultScheme string) (*url.URL,
}
if u.Hostname() == "" {
- return nil, -1, ErrInvalidLink
+ return nil, -1, ErrInvalidLink(u.String())
}
return u, i, nil
@@ -563,12 +605,12 @@ func Print(state *BrowserState) error {
return print(state)
}
-func HandleResource(state *BrowserState, conf *Config) error {
+func HandleResource(state *BrowserState) error {
if state.Modal != nil {
return Print(state)
}
- if handler, ok := conf.Handlers[state.DocType]; ok {
+ if handler, ok := state.Handlers[state.DocType]; ok {
return Pipe(state, handler)
}
@@ -577,10 +619,10 @@ func HandleResource(state *BrowserState, conf *Config) error {
return print(state)
}
- return Save(state, path.Base(state.Url.Path), conf)
+ return Save(state, path.Base(state.Url.Path))
}
-func Outline(state *BrowserState, conf *Config) error {
+func Outline(state *BrowserState) error {
if state.Body == nil {
return ErrMustBeOnAPage
}
@@ -604,7 +646,7 @@ func Outline(state *BrowserState, conf *Config) error {
}
}
- formatted, _, err := parseGemtextDoc(b.Bytes(), conf.SoftWrap)
+ formatted, _, err := parseGemtextDoc(b.Bytes(), state.SoftWrap)
if err != nil {
return err
}
@@ -617,7 +659,7 @@ func Outline(state *BrowserState, conf *Config) error {
return Print(state)
}
-func Links(state *BrowserState, conf *Config) error {
+func Links(state *BrowserState) error {
if state.Links == nil {
return ErrMustBeOnAPage
}
@@ -626,7 +668,7 @@ func Links(state *BrowserState, conf *Config) error {
for _, link := range state.Links {
fmt.Fprintf(buf, "=> %s %s\n", link.Target.String(), link.Text)
}
- formatted, _, err := parseDoc("text/gemini", buf.Bytes(), conf)
+ formatted, _, err := parseDoc("text/gemini", buf.Bytes(), state.Config)
if err != nil {
return err
}
@@ -666,7 +708,7 @@ func HistoryCmd(state *BrowserState) error {
return Print(state)
}
-func Save(state *BrowserState, filename string, conf *Config) error {
+func Save(state *BrowserState, filename string) error {
if state.Body == nil {
return ErrMustBeOnAPage
}
@@ -674,7 +716,7 @@ func Save(state *BrowserState, filename string, conf *Config) error {
return ErrSaveNeedsFilename
}
- p := filepath.Join(conf.DownloadFolder, filename)
+ p := filepath.Join(state.DownloadFolder, filename)
_, err := os.Stat(p)
pbase := p
i := 1
@@ -691,12 +733,12 @@ func Save(state *BrowserState, filename string, conf *Config) error {
return Print(state)
}
-func Mark(state *BrowserState, args []string, conf *Config) error {
+func Mark(state *BrowserState, args []string) error {
switch args[0] {
case "add":
- return MarkAdd(state, conf, args[1], args[2])
+ return MarkAdd(state, args[1], args[2])
case "go":
- return MarkGo(state, conf, args[1])
+ return MarkGo(state, args[1])
case "list":
return MarkList(state)
case "delete":
@@ -706,27 +748,27 @@ func Mark(state *BrowserState, args []string, conf *Config) error {
return ErrInvalidMarkArgs
}
-func TourCmd(state *BrowserState, args []string, conf *Config) error {
+func TourCmd(state *BrowserState, args []string) error {
switch args[0] {
case "add":
if args[1] == "next" {
- return TourAddNext(state, conf, args[2:])
+ return TourAddNext(state, args[2:])
}
- return TourAdd(state, conf, args[1:])
+ return TourAdd(state, args[1:])
case "show":
return TourShow(state)
case "select":
return TourSelect(state, args[1])
case "next":
- return TourNext(state, conf)
+ return TourNext(state)
case "previous":
- return TourPrevious(state, conf)
+ return TourPrevious(state)
case "clear":
return TourClear(state)
case "list":
return TourList(state)
case "go":
- return TourGo(state, conf, args[1])
+ return TourGo(state, args[1])
}
return ErrInvalidTourArgs