M go.mod => go.mod +10 -4
@@ 3,8 3,14 @@ module git.sr.ht/~rafael/gembro
go 1.15
require (
- github.com/charmbracelet/bubbles v0.7.6
- github.com/charmbracelet/bubbletea v0.13.1
- github.com/muesli/termenv v0.7.4
- golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3
+ github.com/charmbracelet/bubbles v0.10.2
+ github.com/charmbracelet/bubbletea v0.19.3
+ github.com/containerd/console v1.0.3 // indirect
+ github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f // indirect
+ github.com/mattn/go-isatty v0.0.14 // indirect
+ github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70 // indirect
+ github.com/muesli/termenv v0.9.0
+ golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce // indirect
+ golang.org/x/net v0.0.0-20220121210141-e204ce36a2ba
+ golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect
)
M go.sum => go.sum +51 -0
@@ 1,37 1,71 @@
github.com/atotto/clipboard v0.1.2 h1:YZCtFu5Ie8qX2VmVTBnrqLSiU9XOWwqNRmdT3gIQzbY=
github.com/atotto/clipboard v0.1.2/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
+github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
+github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/charmbracelet/bubbles v0.7.6 h1:SCAp4ZEUf2tBNEsufo+Xxxu2dvbFhYSDPrX45toQZrM=
github.com/charmbracelet/bubbles v0.7.6/go.mod h1:0D4XRYK0tjo8JMvflz1obpVcOikNZSG46SFauoZj22s=
+github.com/charmbracelet/bubbles v0.10.2 h1:VK1Q7nnBMDFTlrMmvBgE9nidtU5udsIcZvFXvjE2Cfk=
+github.com/charmbracelet/bubbles v0.10.2/go.mod h1:jOA+DUF1rjZm7gZHcNyIVW+YrBPALKfpGVdJu8UiJsA=
github.com/charmbracelet/bubbletea v0.12.2/go.mod h1:3gZkYELUOiEUOp0bTInkxguucy/xRbGSOcbMs1geLxg=
github.com/charmbracelet/bubbletea v0.13.1 h1:huvX8mPaeMZ8DLulT50iEWRF+iitY5FNEDqDVLu69nM=
github.com/charmbracelet/bubbletea v0.13.1/go.mod h1:tp9tr9Dadh0PLhgiwchE5zZJXm5543JYjHG9oY+5qSg=
+github.com/charmbracelet/bubbletea v0.19.3 h1:OKeO/Y13rQQqt4snX+lePB0QrnW80UdrMNolnCcmoAw=
+github.com/charmbracelet/bubbletea v0.19.3/go.mod h1:VuXF2pToRxDUHcBUcPmCRUHRvFATM4Ckb/ql1rBl3KA=
+github.com/charmbracelet/harmonica v0.1.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
+github.com/charmbracelet/lipgloss v0.4.0 h1:768h64EFkGUr8V5yAKV7/Ta0NiVceiPaV+PphaW1K9g=
+github.com/charmbracelet/lipgloss v0.4.0/go.mod h1:vmdkHvce7UzX6xkyf4cca8WlwdQ5RQr8fzta+xl7BOM=
github.com/containerd/console v1.0.1 h1:u7SFAJyRqWcG6ogaMAx3KjSTy1e3hT9QxqX7Jco7dRc=
github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw=
+github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ=
+github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw=
+github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f/go.mod h1:nOFQdrUlIlx6M6ODdSpBj1NVA+VgLC6kmw60mkw34H4=
+github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac=
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
+github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
+github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
+github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
+github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
+github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg=
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
+github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
+github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
+github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
+github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho=
+github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70 h1:kMlmsLSbjkikxQJ1IPwaM+7LJ9ltFu/fi8CRzvSnQmA=
+github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho=
github.com/muesli/reflow v0.2.0/go.mod h1:qT22vjVmM9MIUeLgsVYe/Ye7eZlbv9dZjL3dVhUqLX8=
github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68 h1:y1p/ycavWjGT9FnmSjdbWUlLGvcxrY0Rw3ATltrxOhk=
github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ=
+github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
+github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
github.com/muesli/termenv v0.7.2/go.mod h1:ct2L5N2lmix82RaY3bMWwVu/jUFc9Ule0KGDCiKYPh8=
github.com/muesli/termenv v0.7.4 h1:/pBqvU5CpkY53tU0vVn+xgs2ZTX63aH5nY+SSps5Xa8=
github.com/muesli/termenv v0.7.4/go.mod h1:pZ7qY9l3F7e5xsAOS0zCew2tME+p7bWeBkotCEcIIcc=
+github.com/muesli/termenv v0.9.0 h1:wnbOaGz+LUR3jNT0zOzinPnyDaCZUQRZj9GxK8eRVl8=
+github.com/muesli/termenv v0.9.0/go.mod h1:R/LzAKf+suGs4IsO95y7+7DpFHO0KABgnZqtlyx2mBw=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
+github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E=
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce h1:Roh6XWxHFKrPgC/EQhVubSAGQ6Ozk6IdxHSzt1mR0EI=
+golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20220121210141-e204ce36a2ba h1:6u6sik+bn/y7vILcYkK3iwTBWN7WtBvB0+SZswQnbf8=
+golang.org/x/net v0.0.0-20220121210141-e204ce36a2ba/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ 39,5 73,22 @@ golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201020230747-6e5568b54d1a h1:e3IU37lwO4aq3uoRKINC7JikojFmE5gO7xhfxs8VC34=
golang.org/x/sys v0.0.0-20201020230747-6e5568b54d1a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0=
+golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210422114643-f5beecf764ed/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
M internal/history/history.go => internal/history/history.go +41 -14
@@ 7,9 7,14 @@ import (
"sync"
)
+type URL struct {
+ url string
+ scrollPos int
+}
+
type History struct {
sync.Mutex
- urls []string
+ urls []URL
pos int
}
@@ 19,37 24,47 @@ func (h *History) Add(surl string) {
if len(h.urls) == 0 && h.pos == 0 {
h.pos = -1
}
- h.urls = append(h.urls[:h.pos+1], surl)
+ h.urls = append(h.urls[:h.pos+1], URL{surl, 0})
h.pos = len(h.urls) - 1
}
-func (h *History) Back() (string, bool) {
+func (h *History) Back() (string, int, bool) {
h.Lock()
defer h.Unlock()
if h.pos > 0 {
h.pos--
- return h.urls[h.pos], true
+ u := h.urls[h.pos]
+ return u.url, u.scrollPos, true
+ }
+ return "", 0, false
+}
+
+func (h *History) UpdateScroll(pos int) {
+ if len(h.urls) == 0 {
+ return
}
- return "", false
+ h.urls[h.pos].scrollPos = pos
}
-func (h *History) Current() string {
+func (h *History) Current() (string, int) {
h.Lock()
defer h.Unlock()
if len(h.urls) == 0 {
- return ""
+ return "", 0
}
- return h.urls[h.pos]
+ u := h.urls[h.pos]
+ return u.url, u.scrollPos
}
-func (h *History) Forward() (string, bool) {
+func (h *History) Forward() (string, int, bool) {
h.Lock()
defer h.Unlock()
if h.pos < len(h.urls)-1 {
h.pos++
- return h.urls[h.pos], true
+ u := h.urls[h.pos]
+ return u.url, u.scrollPos, true
}
- return "", false
+ return "", 0, false
}
func (h *History) Status() string {
@@ 58,15 73,23 @@ func (h *History) Status() string {
return fmt.Sprintf("Count=%d, Pos=%d", len(h.urls), h.pos)
}
+type jsonURL struct {
+ URL string
+ ScrollPos int
+}
+
type jsonData struct {
- URLs []string
+ URLs []jsonURL
Pos int
}
func (h *History) ToJSON(out io.Writer) error {
h.Lock()
defer h.Unlock()
- j := jsonData{h.urls, h.pos}
+ j := jsonData{Pos: h.pos}
+ for _, u := range h.urls {
+ j.URLs = append(j.URLs, jsonURL{u.url, u.scrollPos})
+ }
return json.NewEncoder(out).Encode(&j)
}
@@ 81,7 104,11 @@ func FromJSON(in io.Reader) ([]*History, error) {
}
return nil, err
}
- hs = append(hs, &History{urls: j.URLs, pos: j.Pos})
+ h := &History{pos: j.Pos}
+ for _, u := range j.URLs {
+ h.urls = append(h.urls, URL{u.URL, u.ScrollPos})
+ }
+ hs = append(hs, h)
}
return hs, nil
}
M main.go => main.go +13 -6
@@ 317,7 317,7 @@ func (m model) openNewTab(url string, switchTo bool) (model, tea.Cmd) {
var cmd tea.Cmd
if len(m.tabs) < 9 {
m.sequenceID++
- m.tabs = append(m.tabs, NewTab(m.client, url, m.bookmarks, nil, m.sequenceID))
+ m.tabs = append(m.tabs, NewTab(m.client, url, 0, m.bookmarks, nil, m.sequenceID))
if switchTo {
cmd = fireEvent(SelectTabEvent{Tab: len(m.tabs) - 1})
}
@@ 327,6 327,7 @@ func (m model) openNewTab(url string, switchTo bool) (model, tea.Cmd) {
type LoadURLEvent struct {
URL string
+ ScrollPos int
AddHistory bool
}
@@ 355,7 356,9 @@ func (m model) saveHistory(path string) error {
}
defer f.Close()
for i := range m.tabs {
- if err := m.tabs[i].history.ToJSON(f); err != nil {
+ tab := &m.tabs[i]
+ tab.history.UpdateScroll(tab.viewport.viewport.YOffset)
+ if err := tab.history.ToJSON(f); err != nil {
return fmt.Errorf("could not write history: %w", err)
}
}
@@ 368,7 371,7 @@ func loadTabs(historyPath string, client *gemini.Client, bs *bookmark.Store, sta
if err != nil {
if os.IsNotExist(err) {
return []Tab{
- NewTab(client, startURL, bs, nil, seqID),
+ NewTab(client, startURL, 0, bs, nil, seqID),
}, seqID + 1, nil
}
return nil, 0, fmt.Errorf("could not load history file: %w", err)
@@ 377,15 380,19 @@ func loadTabs(historyPath string, client *gemini.Client, bs *bookmark.Store, sta
hs, err := history.FromJSON(f)
if err != nil {
- return nil, 0, fmt.Errorf("could not decode history: %w", err)
+ log.Printf("Incompatible history file. Ignoring it.")
+ return []Tab{
+ NewTab(client, startURL, 0, bs, nil, seqID),
+ }, seqID + 1, nil
}
var tabs []Tab
for _, h := range hs {
u := startURL
+ scrollPos := 0
if u == "" {
- u = h.Current()
+ u, scrollPos = h.Current()
}
- tab := NewTab(client, u, bs, h, seqID)
+ tab := NewTab(client, u, scrollPos, bs, h, seqID)
tabs = append(tabs, tab)
seqID++
}
M tab.go => tab.go +18 -16
@@ 57,7 57,7 @@ type Tab struct {
specialPages map[string]func(Tab) string
}
-func NewTab(client *gemini.Client, startURL string, bs *bookmark.Store, h *history.History, id tabID) Tab {
+func NewTab(client *gemini.Client, startURL string, scrollPos int, bs *bookmark.Store, h *history.History, id tabID) Tab {
ti := textinput.NewModel()
ti.Placeholder = ""
ti.CharLimit = 255
@@ 71,7 71,7 @@ func NewTab(client *gemini.Client, startURL string, bs *bookmark.Store, h *histo
client: client,
history: h,
input: NewInput(),
- viewport: NewViewport(startURL, h),
+ viewport: NewViewport(startURL, scrollPos, h),
message: Message{},
bookmarks: bs,
specialPages: map[string]func(Tab) string{
@@ 119,7 119,7 @@ func (tab Tab) Update(msg tea.Msg) (Tab, tea.Cmd) {
}
case messageForceCert:
if msg.Response {
- return tab.loadURL(msg.Payload, true, 1, true)
+ return tab.loadURL(msg.Payload, 0, true, 1, true)
}
}
case ShowMessageEvent:
@@ 131,9 131,9 @@ func (tab Tab) Update(msg tea.Msg) (Tab, tea.Cmd) {
switch msg.Type {
case inputQuery:
url := fmt.Sprintf("%s?%s", msg.Payload, neturl.QueryEscape(msg.Value))
- return tab.loadURL(url, true, 1, false)
+ return tab.loadURL(url, 0, true, 1, false)
case inputNav:
- return tab.loadURL(msg.Value, true, 1, false)
+ return tab.loadURL(msg.Value, 0, true, 1, false)
case inputBookmark:
if err := tab.bookmarks.Add(msg.Payload, msg.Value); err != nil {
log.Print(err)
@@ 146,14 146,14 @@ func (tab Tab) Update(msg tea.Msg) (Tab, tea.Cmd) {
case ShowInputEvent:
return tab.showInput(msg.Message, msg.Value, msg.Payload, msg.Type)
case LoadURLEvent:
- return tab.loadURL(msg.URL, msg.AddHistory, 1, false)
+ return tab.loadURL(msg.URL, msg.ScrollPos, msg.AddHistory, 1, false)
case GoBackEvent:
- if url, ok := tab.history.Back(); ok {
- return tab.loadURL(url, false, 1, false)
+ if url, pos, ok := tab.history.Back(); ok {
+ return tab.loadURL(url, pos, false, 1, false)
}
case GoForwardEvent:
- if url, ok := tab.history.Forward(); ok {
- return tab.loadURL(url, false, 1, false)
+ if url, pos, ok := tab.history.Forward(); ok {
+ return tab.loadURL(url, pos, false, 1, false)
}
case ToggleBookmarkEvent:
if tab.bookmarks.Contains(msg.URL) {
@@ 285,8 285,9 @@ type ServerResponse interface {
type GeminiResponse struct {
*gemini.Response
- level int
- tab tabID
+ level int
+ scrollPos int
+ tab tabID
}
func (gr GeminiResponse) Tab() tabID {
@@ 334,7 335,7 @@ func (tab Tab) handleResponse(resp ServerResponse) (Tab, tea.Cmd) {
if resp.level > 5 {
return tab.showMessage("Too many redirects. Welcome to the Web from Hell.", "", messagePlain, false)
}
- return tab.loadURL(resp.Header.Meta, true, resp.level+1, false)
+ return tab.loadURL(resp.Header.Meta, resp.scrollPos, true, resp.level+1, false)
case 4, 5, 6:
return tab.showMessage(fmt.Sprintf("Error: %s", resp.Header.Meta), "", messagePlain, false)
case 2:
@@ 344,7 345,7 @@ func (tab Tab) handleResponse(resp ServerResponse) (Tab, tea.Cmd) {
return tab, nil
}
tab.lastResponse = resp
- tab.viewport = tab.viewport.SetGeminiContent(body, resp.URL, resp.Header.Meta)
+ tab.viewport = tab.viewport.SetGeminiContent(body, resp.URL, resp.Header.Meta, resp.scrollPos)
return tab, nil
default:
log.Print(resp.Header)
@@ 354,7 355,7 @@ func (tab Tab) handleResponse(resp ServerResponse) (Tab, tea.Cmd) {
return tab, nil
}
-func (tab Tab) loadURL(url string, addHist bool, level int, skipVerify bool) (Tab, tea.Cmd) {
+func (tab Tab) loadURL(url string, scrollPos int, addHist bool, level int, skipVerify bool) (Tab, tea.Cmd) {
if !strings.Contains(url, "://") {
url = fmt.Sprintf("gemini://%s", url)
}
@@ 371,6 372,7 @@ func (tab Tab) loadURL(url string, addHist bool, level int, skipVerify bool) (Ta
tab.viewport.loading = true
cmd := func() tea.Msg {
+ tab.history.UpdateScroll(tab.viewport.viewport.YOffset)
defer cancel()
if isSpecial {
@@ 406,7 408,7 @@ func (tab Tab) loadURL(url string, addHist bool, level int, skipVerify bool) (Ta
if addHist && resp.Header.Status == 2 {
tab.history.Add(u.String())
}
- return GeminiResponse{Response: resp, level: level, tab: tab.id}
+ return GeminiResponse{Response: resp, level: level, tab: tab.id, scrollPos: scrollPos}
}
}
return tab, tea.Batch(cmd, spinner.Tick)
M viewport.go => viewport.go +13 -9
@@ 36,6 36,7 @@ type Viewport struct {
ready bool
loading bool
URL, MediaType string
+ startScroll int
title string
links text.Links
@@ 44,15 45,16 @@ type Viewport struct {
digits string
}
-func NewViewport(startURL string, h *history.History) Viewport {
+func NewViewport(startURL string, scrollPos int, h *history.History) Viewport {
s := spinner.NewModel()
s.Spinner = spinner.Points
// footerLead := "Back (RMB) Forward (->) Home (h) Bookmark (b) Download (d) Close tab (q) Quit (ctrl+c) "
return Viewport{
- URL: startURL,
- spinner: s,
- history: h,
- footer: NewFooter(buttonBack, buttonFwd, buttonHome, buttonBookmark, buttonDownload, buttonHelp, buttonQuit),
+ URL: startURL,
+ startScroll: scrollPos,
+ spinner: s,
+ history: h,
+ footer: NewFooter(buttonBack, buttonFwd, buttonHome, buttonBookmark, buttonDownload, buttonHelp, buttonQuit),
}
}
@@ 75,7 77,7 @@ func (v Viewport) SetGoperContent(data []byte, url string, typ byte) Viewport {
return v
}
-func (v Viewport) SetGeminiContent(content, url, mediaType string) Viewport {
+func (v Viewport) SetGeminiContent(content, url, mediaType string, scrollPos int) Viewport {
v.URL = url
v.MediaType = mediaType
u, _ := neturl.Parse(url)
@@ 95,7 97,7 @@ func (v Viewport) SetGeminiContent(content, url, mediaType string) Viewport {
}
v.viewport.SetContent(s)
- v.viewport.GotoTop()
+ v.viewport.SetYOffset(scrollPos)
return v
}
@@ 116,7 118,9 @@ func (v Viewport) Update(msg tea.Msg) (Viewport, tea.Cmd) {
if startURL == "" {
startURL = homeURL
}
- return v, fireEvent(LoadURLEvent{URL: startURL, AddHistory: v.history.Current() != startURL})
+ hist, _ := v.history.Current()
+ return v, fireEvent(LoadURLEvent{URL: startURL, ScrollPos: v.startScroll,
+ AddHistory: hist != startURL})
} else {
v.viewport.Width = msg.Width
v.viewport.Height = msg.Height - verticalMargins
@@ 205,7 209,7 @@ func (v Viewport) handleButtonClick(btn string) tea.Cmd {
Type: inputDownloadSrc})
case buttonGoto:
var val string
- if cur := v.history.Current(); cur != homeURL && cur != helpURL {
+ if cur, _ := v.history.Current(); cur != homeURL && cur != helpURL {
val = cur
}
return fireEvent(ShowInputEvent{Message: "Go to", Type: inputNav, Payload: "", Value: val})