1
0
mirror of https://github.com/makew0rld/amfora.git synced 2024-09-25 22:55:55 -04:00

🚧 Everything uses tab struct, no compile errors, untested

This commit is contained in:
makeworld 2020-07-04 20:58:46 -04:00
parent 44bf54c12f
commit d3d47a344d
7 changed files with 226 additions and 273 deletions

View File

@ -13,6 +13,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### Changed
- Pages are rewrapped dynamically, whenever the terminal size changes (#33) - Pages are rewrapped dynamically, whenever the terminal size changes (#33)
### Fixed
- Many potential loading race conditions eliminated
## [1.2.0] - 2020-07-02 ## [1.2.0] - 2020-07-02
### Added ### Added
- Alt-Left and Alt-Right for history navigation (#23) - Alt-Left and Alt-Right for history navigation (#23)

View File

@ -1,9 +1,6 @@
# Notes # Notes
- Simplify into one struct - URL for each tab should not be stored as a string - in the current code there's lots of reparsing the URL
- All the maps and stuff could be replaced with a `tab` struct
- And then just one single map of tab number to `tab`
- URL for each tab should not be stored as a string - in the current code there's lots of reparsing the URL
## Upstream Bugs ## Upstream Bugs
- Wrapping messes up on brackets - Wrapping messes up on brackets

View File

@ -113,27 +113,27 @@ func Bookmarks() {
Width: termW, Width: termW,
Mediatype: structs.TextGemini, Mediatype: structs.TextGemini,
} }
setPage(&page) setPage(curTab, &page)
} }
// addBookmark goes through the process of adding a bookmark for the current page. // addBookmark goes through the process of adding a bookmark for the current page.
// It is the high-level way of doing it. It should be called in a goroutine. // It is the high-level way of doing it. It should be called in a goroutine.
// It can also be called to edit an existing bookmark. // It can also be called to edit an existing bookmark.
func addBookmark() { func addBookmark() {
if !strings.HasPrefix(tabMap[curTab].Url, "gemini://") { if !strings.HasPrefix(tabs[curTab].page.Url, "gemini://") {
// Can't make bookmarks for other kinds of URLs // Can't make bookmarks for other kinds of URLs
return return
} }
name, exists := bookmarks.Get(tabMap[curTab].Url) name, exists := bookmarks.Get(tabs[curTab].page.Url)
// Open a bookmark modal with the current name of the bookmark, if it exists // Open a bookmark modal with the current name of the bookmark, if it exists
newName, action := openBkmkModal(name, exists) newName, action := openBkmkModal(name, exists)
switch action { switch action {
case 1: case 1:
// Add/change the bookmark // Add/change the bookmark
bookmarks.Set(tabMap[curTab].Url, newName) bookmarks.Set(tabs[curTab].page.Url, newName)
case -1: case -1:
bookmarks.Remove(tabMap[curTab].Url) bookmarks.Remove(tabs[curTab].page.Url)
} }
// Other case is action = 0, meaning "Cancel", so nothing needs to happen // Other case is action = 0, meaning "Cancel", so nothing needs to happen
} }

View File

@ -6,7 +6,6 @@ import (
"path" "path"
"strconv" "strconv"
"strings" "strings"
"sync"
"github.com/gdamore/tcell" "github.com/gdamore/tcell"
"github.com/makeworld-the-better-one/amfora/cache" "github.com/makeworld-the-better-one/amfora/cache"
@ -16,19 +15,13 @@ import (
"gitlab.com/tslocum/cview" "gitlab.com/tslocum/cview"
) )
var curTab = -1 // What number tab is currently visible, -1 means there are no tabs at all var tabs []*tab // Slice of all the current browser tabs
var tabMap = make(map[int]*structs.Page) // Map of tab number to page var curTab = -1 // What tab is currently visible - index for the tabs slice (-1 means there are no tabs)
// Holds the actual tab primitives
var tabViews = make(map[int]*cview.TextView)
// Terminal dimensions // Terminal dimensions
var termW int var termW int
var termH int var termH int
// The link currently selected when in link selection mode
// Set to "" when not in that mode
var selectedLink string
// The user input and URL display bar at the bottom // The user input and URL display bar at the bottom
var bottomBar = cview.NewInputField(). var bottomBar = cview.NewInputField().
SetFieldBackgroundColor(tcell.ColorWhite). SetFieldBackgroundColor(tcell.ColorWhite).
@ -74,8 +67,6 @@ var renderedNewTabContent string
var newTabLinks []string var newTabLinks []string
var newTabPage *structs.Page var newTabPage *structs.Page
var reformatMuts = make(map[int]*sync.Mutex) // Mutex for each tab
var App = cview.NewApplication(). var App = cview.NewApplication().
EnableMouse(false). EnableMouse(false).
SetRoot(layout, true). SetRoot(layout, true).
@ -86,10 +77,10 @@ var App = cview.NewApplication().
// Make sure the current tab content is reformatted when the terminal size changes // Make sure the current tab content is reformatted when the terminal size changes
go func(tab int) { go func(tab int) {
reformatMuts[tab].Lock() // Only one reformat job per tab tabs[tab].reformatMut.Lock() // Only one reformat job per tab
defer reformatMuts[tab].Unlock() defer tabs[tab].reformatMut.Unlock()
// Use the current tab, but don't affect other tabs if the user switches tabs // Use the current tab, but don't affect other tabs if the user switches tabs
reformatPageAndSetView(tab, tabMap[tab]) reformatPageAndSetView(tab, tabs[tab].page)
}(curTab) }(curTab)
}) })
@ -109,6 +100,8 @@ func Init() {
bottomBar.SetDoneFunc(func(key tcell.Key) { bottomBar.SetDoneFunc(func(key tcell.Key) {
defer bottomBar.SetLabel("") defer bottomBar.SetLabel("")
tab := curTab
switch key { switch key {
case tcell.KeyEnter: case tcell.KeyEnter:
// Figure out whether it's a URL, link number, or search // Figure out whether it's a URL, link number, or search
@ -118,21 +111,23 @@ func Init() {
if strings.TrimSpace(query) == "" { if strings.TrimSpace(query) == "" {
// Ignore // Ignore
bottomBar.SetText(tabMap[curTab].Url) bottomBar.SetText(tabs[tab].page.Url)
App.SetFocus(tabViews[curTab]) tabs[tab].saveBottomBar() // Store new bottomBar text
App.SetFocus(tabs[tab].view)
return return
} }
if query == ".." && tabHasContent() { if query == ".." && tabs[tab].hasContent() {
// Go up a directory // Go up a directory
parsed, err := url.Parse(tabMap[curTab].Url) parsed, err := url.Parse(tabs[tab].page.Url)
if err != nil { if err != nil {
// This shouldn't occur // This shouldn't occur
return return
} }
if parsed.Path == "/" { if parsed.Path == "/" {
// Can't go up further // Can't go up further
bottomBar.SetText(tabMap[curTab].Url) bottomBar.SetText(tabs[tab].page.Url)
App.SetFocus(tabViews[curTab]) tabs[tab].saveBottomBar()
App.SetFocus(tabs[tab].view)
return return
} }
@ -154,13 +149,13 @@ func Init() {
if err != nil { if err != nil {
return return
} }
if i <= len(tabMap[curTab].Links) && i > 0 { if i <= len(tabs[tab].page.Links) && i > 0 {
// Open new tab and load link // Open new tab and load link
oldTab := curTab oldTab := tab
NewTab() NewTab()
// Resolve and follow link manually // Resolve and follow link manually
prevParsed, _ := url.Parse(tabMap[oldTab].Url) prevParsed, _ := url.Parse(tabs[oldTab].page.Url)
nextParsed, err := url.Parse(tabMap[oldTab].Links[i-1]) nextParsed, err := url.Parse(tabs[oldTab].page.Links[i-1])
if err != nil { if err != nil {
Error("URL Error", "link URL could not be parsed") Error("URL Error", "link URL could not be parsed")
return return
@ -183,19 +178,21 @@ func Init() {
return return
} }
} }
if i <= len(tabMap[curTab].Links) && i > 0 { if i <= len(tabs[tab].page.Links) && i > 0 {
// It's a valid link number // It's a valid link number
followLink(tabMap[curTab].Url, tabMap[curTab].Links[i-1]) followLink(tab, tabs[tab].page.Url, tabs[tab].page.Links[i-1])
return return
} }
// Invalid link number, don't do anything // Invalid link number, don't do anything
bottomBar.SetText(tabMap[curTab].Url) bottomBar.SetText(tabs[tab].page.Url)
App.SetFocus(tabViews[curTab]) tabs[tab].saveBottomBar()
App.SetFocus(tabs[tab].view)
case tcell.KeyEscape: case tcell.KeyEscape:
// Set back to what it was // Set back to what it was
bottomBar.SetText(tabMap[curTab].Url) bottomBar.SetText(tabs[tab].page.Url)
App.SetFocus(tabViews[curTab]) tabs[tab].saveBottomBar()
App.SetFocus(tabs[tab].view)
} }
// Other potential keys are Tab and Backtab, they are ignored // Other potential keys are Tab and Backtab, they are ignored
}) })
@ -241,16 +238,16 @@ func Init() {
switch event.Key() { switch event.Key() {
case tcell.KeyCtrlT: case tcell.KeyCtrlT:
if selectedLink == "" { if tabs[curTab].mode == modeLinkSelect {
NewTab() next, err := resolveRelLink(curTab, tabs[curTab].page.Url, tabs[curTab].selected)
} else {
next, err := resolveRelLink(tabMap[curTab].Url, selectedLink)
if err != nil { if err != nil {
Error("URL Error", err.Error()) Error("URL Error", err.Error())
return nil return nil
} }
NewTab() NewTab()
URL(next) URL(next)
} else {
NewTab()
} }
return nil return nil
case tcell.KeyCtrlW: case tcell.KeyCtrlW:
@ -267,16 +264,16 @@ func Init() {
return nil return nil
case tcell.KeyCtrlB: case tcell.KeyCtrlB:
Bookmarks() Bookmarks()
addToHist("about:bookmarks") tabs[curTab].addToHistory("about:bookmarks")
return nil return nil
case tcell.KeyCtrlD: case tcell.KeyCtrlD:
go addBookmark() go addBookmark()
return nil return nil
case tcell.KeyPgUp: case tcell.KeyPgUp:
pageUp() tabs[curTab].pageUp()
return nil return nil
case tcell.KeyPgDn: case tcell.KeyPgDn:
pageDown() tabs[curTab].pageDown()
return nil return nil
case tcell.KeyRune: case tcell.KeyRune:
// Regular key was sent // Regular key was sent
@ -285,6 +282,7 @@ func Init() {
// Space starts typing, like Bombadillo // Space starts typing, like Bombadillo
bottomBar.SetLabel("[::b]URL/Num./Search: [::-]") bottomBar.SetLabel("[::b]URL/Num./Search: [::-]")
bottomBar.SetText("") bottomBar.SetText("")
// Don't save bottom bar, so that whenever you switch tabs, it's not in that mode
App.SetFocus(bottomBar) App.SetFocus(bottomBar)
return nil return nil
case "q": case "q":
@ -303,10 +301,10 @@ func Init() {
Help() Help()
return nil return nil
case "u": case "u":
pageUp() tabs[curTab].pageUp()
return nil return nil
case "d": case "d":
pageDown() tabs[curTab].pageDown()
return nil return nil
// Shift+NUMBER keys, for switching to a specific tab // Shift+NUMBER keys, for switching to a specific tab
@ -355,87 +353,31 @@ func Stop() {
// NewTab opens a new tab and switches to it, displaying the // NewTab opens a new tab and switches to it, displaying the
// the default empty content because there's no URL. // the default empty content because there's no URL.
func NewTab() { func NewTab() {
// Create TextView in tabViews and change curTab // Create TextView and change curTab
// Set the textView options, and the changed func to App.Draw() // Set the TextView options, and the changed func to App.Draw()
// SetDoneFunc to do link highlighting // SetDoneFunc to do link highlighting
// Add view to pages and switch to it // Add view to pages and switch to it
// But first, turn off link selecting mode in the current tab // Process current tab before making a new one
if curTab > -1 { if curTab > -1 {
tabViews[curTab].Highlight("") // Turn off link selecting mode in the current tab
tabs[curTab].view.Highlight("")
// Save bottomBar state
tabs[curTab].saveBottomBar()
} }
selectedLink = ""
curTab = NumTabs() curTab = NumTabs()
reformatPage(newTabPage) reformatPage(newTabPage)
tabMap[curTab] = newTabPage
reformatMuts[curTab] = &sync.Mutex{}
tabViews[curTab] = cview.NewTextView().
SetDynamicColors(true).
SetRegions(true).
SetScrollable(true).
SetWrap(false).
SetText(tabMap[curTab].Content).
ScrollToBeginning().
SetChangedFunc(func() {
App.Draw()
}).
SetDoneFunc(func(key tcell.Key) {
// Altered from: https://gitlab.com/tslocum/cview/-/blob/master/demos/textview/main.go
// Handles being able to select and "click" links with the enter and tab keys
if key == tcell.KeyEsc { tabs[curTab] = makeNewTab()
// Stop highlighting tabs[curTab].page = newTabPage
tabViews[curTab].Highlight("")
bottomBar.SetLabel("")
bottomBar.SetText(tabMap[curTab].Url)
selectedLink = ""
}
currentSelection := tabViews[curTab].GetHighlights()
numSelections := len(tabMap[curTab].Links)
if key == tcell.KeyEnter {
if len(currentSelection) > 0 && len(tabMap[curTab].Links) > 0 {
// A link was selected, "click" it and load the page it's for
bottomBar.SetLabel("")
selectedLink = ""
linkN, _ := strconv.Atoi(currentSelection[0])
followLink(tabMap[curTab].Url, tabMap[curTab].Links[linkN])
return
} else {
tabViews[curTab].Highlight("0").ScrollToHighlight()
// Display link URL in bottomBar
bottomBar.SetLabel("[::b]Link: [::-]")
bottomBar.SetText(tabMap[curTab].Links[0])
selectedLink = tabMap[curTab].Links[0]
}
} else if len(currentSelection) > 0 {
// There's still a selection, but a different key was pressed, not Enter
index, _ := strconv.Atoi(currentSelection[0])
if key == tcell.KeyTab {
index = (index + 1) % numSelections
} else if key == tcell.KeyBacktab {
index = (index - 1 + numSelections) % numSelections
} else {
return
}
tabViews[curTab].Highlight(strconv.Itoa(index)).ScrollToHighlight()
// Display link URL in bottomBar
bottomBar.SetLabel("[::b]Link: [::-]")
bottomBar.SetText(tabMap[curTab].Links[index])
selectedLink = tabMap[curTab].Links[index]
}
})
tabHist[curTab] = []string{}
// Can't go backwards, but this isn't the first page either. // Can't go backwards, but this isn't the first page either.
// The first page will be the next one the user goes to. // The first page will be the next one the user goes to.
tabHistPos[curTab] = -1 tabs[curTab].history.pos = -1
tabPages.AddAndSwitchToPage(strconv.Itoa(curTab), tabViews[curTab], true) tabPages.AddAndSwitchToPage(strconv.Itoa(curTab), tabs[curTab].view, true)
App.SetFocus(tabViews[curTab]) App.SetFocus(tabs[curTab].view)
// Add tab number to the actual place where tabs are show on the screen // Add tab number to the actual place where tabs are show on the screen
// Tab regions are 0-indexed but text displayed on the screen starts at 1 // Tab regions are 0-indexed but text displayed on the screen starts at 1
@ -448,6 +390,7 @@ func NewTab() {
bottomBar.SetLabel("") bottomBar.SetLabel("")
bottomBar.SetText("") bottomBar.SetText("")
tabs[curTab].saveBottomBar()
// Draw just in case // Draw just in case
App.Draw() App.Draw()
@ -470,13 +413,8 @@ func CloseTab() {
return return
} }
delete(tabMap, curTab) tabs = tabs[:len(tabs)-1]
tabPages.RemovePage(strconv.Itoa(curTab)) tabPages.RemovePage(strconv.Itoa(curTab))
delete(tabViews, curTab)
delete(reformatMuts, curTab)
delete(tabHist, curTab)
delete(tabHistPos, curTab)
if curTab <= 0 { if curTab <= 0 {
curTab = NumTabs() - 1 curTab = NumTabs() - 1
@ -498,8 +436,8 @@ func CloseTab() {
} }
tabRow.Highlight(strconv.Itoa(curTab)).ScrollToHighlight() tabRow.Highlight(strconv.Itoa(curTab)).ScrollToHighlight()
bottomBar.SetLabel("") // Set previous tab's bottomBar state
bottomBar.SetText(tabMap[curTab].Url) tabs[curTab].applyBottomBar()
// Just in case // Just in case
App.Draw() App.Draw()
@ -516,25 +454,37 @@ func SwitchTab(tab int) {
tab = NumTabs() - 1 tab = NumTabs() - 1
} }
// Save current tab attributes
if curTab > -1 {
// Save bottomBar state
tabs[curTab].saveBottomBar()
}
curTab = tab % NumTabs() curTab = tab % NumTabs()
reformatPageAndSetView(curTab, tabMap[curTab])
// Display tab
reformatPageAndSetView(curTab, tabs[curTab].page)
tabPages.SwitchToPage(strconv.Itoa(curTab)) tabPages.SwitchToPage(strconv.Itoa(curTab))
tabRow.Highlight(strconv.Itoa(curTab)).ScrollToHighlight() tabRow.Highlight(strconv.Itoa(curTab)).ScrollToHighlight()
tabs[curTab].applyBottomBar()
bottomBar.SetLabel("")
bottomBar.SetText(tabMap[curTab].Url)
// Just in case // Just in case
App.Draw() App.Draw()
} }
func Reload() { func Reload() {
if !tabHasContent() { if !tabs[curTab].hasContent() {
return return
} }
cache.Remove(tabMap[curTab].Url) cache.Remove(tabs[curTab].page.Url)
go handleURL(tabMap[curTab].Url) go func(tab int) {
handleURL(tab, tabs[tab].page.Url) // goURL is not used bc history shouldn't be added to
if tab == curTab {
// Display the bottomBar state that handleURL set
tabs[tab].applyBottomBar()
}
}(curTab)
} }
// URL loads and handles the provided URL for the current tab. // URL loads and handles the provided URL for the current tab.
@ -544,11 +494,11 @@ func URL(u string) {
if u == "about:bookmarks" { if u == "about:bookmarks" {
Bookmarks() Bookmarks()
addToHist("about:bookmarks") tabs[curTab].addToHistory("about:bookmarks")
return return
} }
if u == "about:newtab" { if u == "about:newtab" {
setPage(newTabPage) setPage(curTab, newTabPage)
return return
} }
if strings.HasPrefix(u, "about:") { if strings.HasPrefix(u, "about:") {
@ -556,14 +506,9 @@ func URL(u string) {
return return
} }
go func() { go goURL(curTab, u)
final, displayed := handleURL(u)
if displayed {
addToHist(final)
}
}()
} }
func NumTabs() int { func NumTabs() int {
return len(tabViews) return len(tabs)
} }

View File

@ -1,44 +1,33 @@
package display package display
// Tab number mapped to list of URLs ordered from first to most recent.
var tabHist = make(map[int][]string)
// Tab number mapped to where in its history you are.
// The value is a valid index of the string slice above.
var tabHistPos = make(map[int]int)
// addToHist adds the given URL to history.
// It assumes the URL is currently being loaded and displayed on the page.
func addToHist(u string) {
if tabHistPos[curTab] < len(tabHist[curTab])-1 {
// We're somewhere in the middle of the history instead, with URLs ahead and behind.
// The URLs ahead need to be removed so this new URL is the most recent item in the history
tabHist[curTab] = tabHist[curTab][:tabHistPos[curTab]+1]
}
tabHist[curTab] = append(tabHist[curTab], u)
tabHistPos[curTab]++
}
func histForward() { func histForward() {
if tabHistPos[curTab] >= len(tabHist[curTab])-1 { if tabs[curTab].history.pos >= len(tabs[curTab].history.urls)-1 {
// Already on the most recent URL in the history // Already on the most recent URL in the history
return return
} }
tabHistPos[curTab]++ tabs[curTab].history.pos++
go func() { go func(tab int) {
handleURL(tabHist[curTab][tabHistPos[curTab]]) handleURL(tab, tabs[tab].history.urls[tabs[tab].history.pos]) // Load that position in history
applyScroll() tabs[tab].applyScroll()
}() if tab == curTab {
// Display the bottomBar state that handleURL set
tabs[tab].applyBottomBar()
}
}(curTab)
} }
func histBack() { func histBack() {
if tabHistPos[curTab] <= 0 { if tabs[curTab].history.pos <= 0 {
// First tab in history // First tab in history
return return
} }
tabHistPos[curTab]-- tabs[curTab].history.pos--
go func() { go func(tab int) {
handleURL(tabHist[curTab][tabHistPos[curTab]]) handleURL(tab, tabs[tab].history.urls[tabs[tab].history.pos]) // Load that position in history
applyScroll() tabs[tab].applyScroll()
}() if tab == curTab {
// Display the bottomBar state that handleURL set
tabs[tab].applyBottomBar()
}
}(curTab)
} }

View File

@ -18,18 +18,6 @@ import (
// This file contains the functions that aren't part of the public API. // This file contains the functions that aren't part of the public API.
// pageUp scrolls up 75% of the height of the terminal, like Bombadillo.
func pageUp() {
row, col := tabViews[curTab].GetScrollOffset()
tabViews[curTab].ScrollTo(row-(termH/4)*3, col)
}
// pageDown scrolls down 75% of the height of the terminal, like Bombadillo.
func pageDown() {
row, col := tabViews[curTab].GetScrollOffset()
tabViews[curTab].ScrollTo(row+(termH/4)*3, col)
}
func leftMargin() int { func leftMargin() int {
return int(float64(termW) * viper.GetFloat64("a-general.left_margin")) return int(float64(termW) * viper.GetFloat64("a-general.left_margin"))
} }
@ -60,50 +48,11 @@ func queryEscape(path string) string {
return strings.ReplaceAll(url.PathEscape(path), "+", "%2B") return strings.ReplaceAll(url.PathEscape(path), "+", "%2B")
} }
// tabHasContent returns true when the current tab has a page being displayed.
// The most likely situation where false would be returned is when the default
// new tab content is being displayed.
func tabHasContent() bool {
if curTab < 0 {
return false
}
if len(tabViews) < curTab {
// There isn't a TextView for the current tab number
return false
}
if tabMap[curTab].Url == "" {
// Likely the default content page
return false
}
if strings.HasPrefix(tabMap[curTab].Url, "about:") {
return false
}
_, ok := tabMap[curTab]
return ok // If there's a page, return true
}
// saveScroll saves where in the page the user was.
// It should be used whenever moving from one page to another.
func saveScroll() {
// It will also be saved in the cache because the cache uses the same pointer
row, col := tabViews[curTab].GetScrollOffset()
tabMap[curTab].Row = row
tabMap[curTab].Column = col
}
// applyScroll applies the saved scroll values to the current page and tab.
// It should only be used when going backward and forward, not when
// loading a new page (that might have scroll vals cached anyway).
func applyScroll() {
tabViews[curTab].ScrollTo(tabMap[curTab].Row, tabMap[curTab].Column)
}
// resolveRelLink returns an absolute link for the given absolute link and relative one. // resolveRelLink returns an absolute link for the given absolute link and relative one.
// It also returns an error if it could not resolve the links, which should be displayed // It also returns an error if it could not resolve the links, which should be displayed
// to the user. // to the user.
func resolveRelLink(prev, next string) (string, error) { func resolveRelLink(tab int, prev, next string) (string, error) {
if !tabHasContent() { if !tabs[tab].hasContent() {
return next, nil return next, nil
} }
@ -117,12 +66,13 @@ func resolveRelLink(prev, next string) (string, error) {
// followLink should be used when the user "clicks" a link on a page. // followLink should be used when the user "clicks" a link on a page.
// Not when a URL is opened on a new tab for the first time. // Not when a URL is opened on a new tab for the first time.
func followLink(prev, next string) { // It will handle setting the bottomBar.
func followLink(tab int, prev, next string) {
// Copied from URL() // Copied from URL()
if next == "about:bookmarks" { if next == "about:bookmarks" {
Bookmarks() Bookmarks()
addToHist("about:bookmarks") tabs[tab].addToHistory("about:bookmarks")
return return
} }
if strings.HasPrefix(next, "about:") { if strings.HasPrefix(next, "about:") {
@ -130,19 +80,14 @@ func followLink(prev, next string) {
return return
} }
if tabHasContent() { if tabs[tab].hasContent() {
saveScroll() // Likely called later on, it's here just in case tabs[tab].saveScroll() // Likely called later on, it's here just in case
nextURL, err := resolveRelLink(prev, next) nextURL, err := resolveRelLink(tab, prev, next)
if err != nil { if err != nil {
Error("URL Error", err.Error()) Error("URL Error", err.Error())
return return
} }
go func() { go goURL(tab, nextURL)
final, displayed := handleURL(nextURL)
if displayed {
addToHist(final)
}
}()
return return
} }
// No content on current tab, so the "prev" URL is not valid. // No content on current tab, so the "prev" URL is not valid.
@ -152,12 +97,7 @@ func followLink(prev, next string) {
Error("URL Error", "Link URL could not be parsed") Error("URL Error", "Link URL could not be parsed")
return return
} }
go func() { go goURL(tab, next)
final, displayed := handleURL(next)
if displayed {
addToHist(final)
}
}()
} }
// reformatPage will take the raw page content and reformat it according to the current terminal dimensions. // reformatPage will take the raw page content and reformat it according to the current terminal dimensions.
@ -187,29 +127,48 @@ func reformatPage(p *structs.Page) {
// reformatPageAndSetView is for reformatting a page that is already being displayed. // reformatPageAndSetView is for reformatting a page that is already being displayed.
// setPage should be used when a page is being loaded for the first time. // setPage should be used when a page is being loaded for the first time.
func reformatPageAndSetView(tab int, p *structs.Page) { func reformatPageAndSetView(tab int, p *structs.Page) {
saveScroll() tabs[tab].saveScroll()
reformatPage(p) reformatPage(p)
tabViews[tab].SetText(p.Content) tabs[tab].view.SetText(p.Content)
applyScroll() // Go back to where you were, roughly tabs[tab].applyScroll() // Go back to where you were, roughly
} }
// setPage displays a Page on the current tab. // setPage displays a Page on the passed tab number.
func setPage(p *structs.Page) { // The bottomBar is not actually changed in this func
saveScroll() // Save the scroll of the previous page func setPage(tab int, p *structs.Page) {
tabs[tab].saveScroll() // Save the scroll of the previous page
// Make sure the page content is fitted to the terminal every time it's displayed // Make sure the page content is fitted to the terminal every time it's displayed
reformatPage(p) reformatPage(p)
// Change page on screen // Change page on screen
tabMap[curTab] = p tabs[tab].page = p
tabViews[curTab].SetText(p.Content) tabs[tab].view.SetText(p.Content)
tabViews[curTab].Highlight("") // Turn off highlights tabs[tab].view.Highlight("") // Turn off highlights
tabViews[curTab].ScrollToBeginning() tabs[tab].view.ScrollToBeginning()
// Setup display // Setup display
App.SetFocus(tabViews[curTab]) App.SetFocus(tabs[tab].view)
bottomBar.SetLabel("")
bottomBar.SetText(p.Url) // Save bottom bar for the tab - TODO: other funcs will apply/display it
tabs[tab].barLabel = ""
tabs[tab].barText = p.Url
}
// goURL is like handleURL, but takes care of history and the bottomBar.
// It should be preferred over handleURL in most cases.
// It has no return values to be processed.
//
// It should be called in a goroutine.
func goURL(tab int, u string) {
final, displayed := handleURL(tab, u)
if displayed {
tabs[tab].addToHistory(final)
}
if tab == curTab {
// Display the bottomBar state that handleURL set
tabs[tab].applyBottomBar()
}
} }
// handleURL displays whatever action is needed for the provided URL, // handleURL displays whatever action is needed for the provided URL,
@ -222,13 +181,12 @@ func setPage(p *structs.Page) {
// The second returned item is a bool indicating if page content was displayed. // The second returned item is a bool indicating if page content was displayed.
// It returns false for Errors, other protocols, etc. // It returns false for Errors, other protocols, etc.
// //
// TODO: Add tab number param - now the func only saves the values like the content // The bottomBar is not actually changed in this func, except during loading.
// and bottom bar. // The func that calls this one should apply the bottomBar values if necessary.
// TODO: Some other func that constantly updates bottom bar values func handleURL(tab int, u string) (string, bool) {
func handleURL(u string) (string, bool) {
defer App.Draw() // Just in case defer App.Draw() // Just in case
App.SetFocus(tabViews[curTab]) App.SetFocus(tabs[tab].view)
// To allow linking to the bookmarks page, and history browsing // To allow linking to the bookmarks page, and history browsing
if u == "about:bookmarks" { if u == "about:bookmarks" {
@ -241,7 +199,7 @@ func handleURL(u string) (string, bool) {
parsed, err := url.Parse(u) parsed, err := url.Parse(u)
if err != nil { if err != nil {
Error("URL Error", err.Error()) Error("URL Error", err.Error())
bottomBar.SetText(tabMap[curTab].Url) tabs[tab].barText = tabs[tab].page.Url
return "", false return "", false
} }
@ -264,12 +222,12 @@ func handleURL(u string) (string, bool) {
Error("HTTP Error", "Error executing custom browser command: "+err.Error()) Error("HTTP Error", "Error executing custom browser command: "+err.Error())
} }
} }
bottomBar.SetText(tabMap[curTab].Url) tabs[tab].barText = tabs[tab].page.Url
return "", false return "", false
} }
if !strings.HasPrefix(u, "gemini") { if !strings.HasPrefix(u, "gemini") {
Error("Protocol Error", "Only gemini and HTTP are supported. URL was "+u) Error("Protocol Error", "Only gemini and HTTP are supported. URL was "+u)
bottomBar.SetText(tabMap[curTab].Url) tabs[tab].barText = tabs[tab].page.Url
return "", false return "", false
} }
// Gemini URL // Gemini URL
@ -277,11 +235,12 @@ func handleURL(u string) (string, bool) {
// Load page from cache if possible // Load page from cache if possible
page, ok := cache.Get(u) page, ok := cache.Get(u)
if ok { if ok {
setPage(page) setPage(tab, page)
return u, true return u, true
} }
// Otherwise download it // Otherwise download it
bottomBar.SetText("Loading...") bottomBar.SetText("Loading...")
tabs[tab].barText = "Loading..." // Save it too, in case the tab switches during loading
App.Draw() App.Draw()
res, err := client.Fetch(u) res, err := client.Fetch(u)
@ -293,13 +252,13 @@ func handleURL(u string) (string, bool) {
} else { } else {
// They don't want to continue // They don't want to continue
// Set the bar back to original URL // Set the bar back to original URL
bottomBar.SetText(tabMap[curTab].Url) tabs[tab].barText = tabs[tab].page.Url
return "", false return "", false
} }
} else if err != nil { } else if err != nil {
Error("URL Fetch Error", err.Error()) Error("URL Fetch Error", err.Error())
// Set the bar back to original URL // Set the bar back to original URL
bottomBar.SetText(tabMap[curTab].Url) tabs[tab].barText = tabs[tab].page.Url
return "", false return "", false
} }
if renderer.CanDisplay(res) { if renderer.CanDisplay(res) {
@ -308,18 +267,19 @@ func handleURL(u string) (string, bool) {
if err != nil { if err != nil {
Error("Page Error", "Issuing creating page: "+err.Error()) Error("Page Error", "Issuing creating page: "+err.Error())
// Set the bar back to original URL // Set the bar back to original URL
bottomBar.SetText(tabMap[curTab].Url) tabs[tab].barText = tabs[tab].page.Url
return "", false return "", false
} }
cache.Add(page) cache.Add(page)
setPage(page) setPage(tab, page)
return u, true return u, true
} }
// Not displayable // Not displayable
// Could be a non 20 (or 21) status code, or a different kind of document // Could be a non 20 (or 21) status code, or a different kind of document
// Set the bar back to original URL // Set the bar back to original URL
bottomBar.SetText(tabMap[curTab].Url) bottomBar.SetText(tabs[curTab].page.Url)
tabs[tab].barText = tabs[curTab].page.Url
App.Draw() App.Draw()
// Handle each status code // Handle each status code
@ -335,7 +295,7 @@ func handleURL(u string) (string, bool) {
Error("Input Error", "URL for that input would be too long.") Error("Input Error", "URL for that input would be too long.")
return "", false return "", false
} }
return handleURL(parsed.String()) return handleURL(tab, parsed.String())
} }
return "", false return "", false
case 30: case 30:
@ -345,13 +305,12 @@ func handleURL(u string) (string, bool) {
return "", false return "", false
} }
redir := parsed.ResolveReference(parsedMeta).String() redir := parsed.ResolveReference(parsedMeta).String()
if YesNo("Follow redirect?\n" + redir) { if YesNo("Follow redirect?\n" + redir) {
return handleURL(redir) return handleURL(tab, redir)
} }
return "", false return "", false
case 40: case 40:
Error("Temporary Failure", cview.Escape(res.Meta)) // Escaped just in case, to not allow malicious meta strings Error("Temporary Failure", cview.Escape(res.Meta))
return "", false return "", false
case 50: case 50:
Error("Permanent Failure", cview.Escape(res.Meta)) Error("Permanent Failure", cview.Escape(res.Meta))

View File

@ -1,9 +1,11 @@
package display package display
import ( import (
"strconv"
"strings" "strings"
"sync" "sync"
"github.com/gdamore/tcell"
"github.com/makeworld-the-better-one/amfora/structs" "github.com/makeworld-the-better-one/amfora/structs"
"gitlab.com/tslocum/cview" "gitlab.com/tslocum/cview"
) )
@ -29,13 +31,14 @@ type tab struct {
history *tabHistory history *tabHistory
reformatMut *sync.Mutex // Mutex for reformatting, so there's only one reformat job at once reformatMut *sync.Mutex // Mutex for reformatting, so there's only one reformat job at once
selected string // The current text or link selected selected string // The current text or link selected
selectedID string // The cview region ID for the selected text/link
barLabel string // The bottomBar label for the tab barLabel string // The bottomBar label for the tab
barText string // The bottomBar text for the tab barText string // The bottomBar text for the tab
} }
// makeNewTab initializes an tab struct with no content. // makeNewTab initializes an tab struct with no content.
func makeNewTab() *tab { func makeNewTab() *tab {
return &tab{ t := tab{
page: &structs.Page{}, page: &structs.Page{},
view: cview.NewTextView(). view: cview.NewTextView().
SetDynamicColors(true). SetDynamicColors(true).
@ -46,8 +49,65 @@ func makeNewTab() *tab {
App.Draw() App.Draw()
}), }),
mode: modeOff, mode: modeOff,
history: &tabHistory{},
reformatMut: &sync.Mutex{}, reformatMut: &sync.Mutex{},
} }
t.view.SetDoneFunc(func(key tcell.Key) {
// Altered from: https://gitlab.com/tslocum/cview/-/blob/master/demos/textview/main.go
// Handles being able to select and "click" links with the enter and tab keys
tab := curTab // Don't let it change in the middle of the code
defer tabs[tab].saveBottomBar()
if key == tcell.KeyEsc {
// Stop highlighting
tabs[tab].view.Highlight("")
bottomBar.SetLabel("")
bottomBar.SetText(tabs[tab].page.Url)
tabs[tab].mode = modeOff
}
currentSelection := tabs[tab].view.GetHighlights()
numSelections := len(tabs[tab].page.Links)
if key == tcell.KeyEnter {
if len(currentSelection) > 0 && len(tabs[tab].page.Links) > 0 {
// A link was selected, "click" it and load the page it's for
bottomBar.SetLabel("")
tabs[tab].mode = modeOff
linkN, _ := strconv.Atoi(currentSelection[0])
followLink(tab, tabs[tab].page.Url, tabs[tab].page.Links[linkN])
return
} else {
tabs[tab].view.Highlight("0").ScrollToHighlight()
// Display link URL in bottomBar
bottomBar.SetLabel("[::b]Link: [::-]")
bottomBar.SetText(tabs[tab].page.Links[0])
tabs[tab].selected = tabs[tab].page.Links[0]
tabs[tab].selectedID = "0"
}
} else if len(currentSelection) > 0 {
// There's still a selection, but a different key was pressed, not Enter
index, _ := strconv.Atoi(currentSelection[0])
if key == tcell.KeyTab {
index = (index + 1) % numSelections
} else if key == tcell.KeyBacktab {
index = (index - 1 + numSelections) % numSelections
} else {
return
}
tabs[tab].view.Highlight(strconv.Itoa(index)).ScrollToHighlight()
// Display link URL in bottomBar
bottomBar.SetLabel("[::b]Link: [::-]")
bottomBar.SetText(tabs[tab].page.Links[index])
tabs[tab].selected = tabs[tab].page.Links[index]
tabs[tab].selectedID = currentSelection[0]
}
})
return &t
} }
// addToHistory adds the given URL to history. // addToHistory adds the given URL to history.