From 581b498a0f04b5a7ec5096bb1472aad315bef18a Mon Sep 17 00:00:00 2001 From: makeworld Date: Wed, 1 Jul 2020 13:39:13 -0400 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Open=20link=20in=20new=20tab=20-=20?= =?UTF-8?q?fixes=20#27?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 3 ++ README.md | 1 + display/display.go | 91 +++++++++++++++++++++++++++------------------- display/help.go | 41 ++++++++++++++++++--- display/private.go | 29 ++++++++++++--- 5 files changed, 116 insertions(+), 49 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7133c5f..9fb94f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Paging, using d and u, as well as Page Up and Page Down (#19) - Esc can exit link highlighting mode (#24) - Selected link URL is displayed in the bottom bar (#24) +- Pressing Ctrl-T with a link selected opens it in a new tab (#27) +- Writing `new:N` in the bottom bar will open link number N in a new tab (#27) ### Changed - Bottom bar now says `URL/Num./Search: ` when space is pressed @@ -22,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Performance when loading very long cached pages improved (#26) - Doesn't crash when wrapping certain complex lines (#20) - Input fields are always in focus when they appear (#5) +- Reloading the new tab page doesn't cause an error popup ## [1.1.0] - 2020-06-24 ### Added diff --git a/README.md b/README.md index 77e3266..6350aa7 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,7 @@ Features in *italics* are in the master branch, but not in the latest release. - [ ] Download pages and arbitrary data - [ ] Emoji favicons - See `gemini://mozz.us/files/rfc_gemini_favicon.gmi` for details +- [ ] Stream support - [ ] Full mouse support - [ ] Table of contents for pages - [ ] Full client certificate UX within the client diff --git a/display/display.go b/display/display.go index 748bf94..abf7ca1 100644 --- a/display/display.go +++ b/display/display.go @@ -24,6 +24,10 @@ var tabViews = make(map[int]*cview.TextView) var termW 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 var bottomBar = cview.NewInputField(). SetFieldBackgroundColor(tcell.ColorWhite). @@ -91,34 +95,7 @@ func Init() { App.Draw() }) - // Populate help table - helpTable.SetDoneFunc(func(key tcell.Key) { - if key == tcell.KeyEsc { - tabPages.SwitchToPage(strconv.Itoa(curTab)) - } - }) - rows := strings.Count(helpCells, "\n") + 1 - cells := strings.Split( - strings.ReplaceAll(helpCells, "\n", "|"), - "|") - cell := 0 - for r := 0; r < rows; r++ { - for c := 0; c < 2; c++ { - var tableCell *cview.TableCell - if c == 0 { - tableCell = cview.NewTableCell(cells[cell]). - SetAttributes(tcell.AttrBold). - SetExpansion(1). - SetAlign(cview.AlignCenter) - } else { - tableCell = cview.NewTableCell(" " + cells[cell]). - SetExpansion(2) - } - helpTable.SetCell(r, c, tableCell) - cell++ - } - } - tabPages.AddPage("help", helpTable, true, false) + helpInit() if viper.GetBool("a-general.color") { bottomBar.SetLabelColor(tcell.ColorGreen) @@ -142,7 +119,7 @@ func Init() { App.SetFocus(tabViews[curTab]) return } - if query == ".." && !strings.HasPrefix(query, "about:") { + if query == ".." && tabHasContent() { // Go up a directory parsed, err := url.Parse(tabMap[curTab].Url) if err != nil { @@ -168,22 +145,44 @@ func Init() { i, err := strconv.Atoi(query) if err != nil { - // It's a full URL or search term - // Detect if it's a search or URL - if strings.Contains(query, " ") || (!strings.Contains(query, "//") && !strings.Contains(query, ".") && !strings.HasPrefix(query, "about:")) { - URL(viper.GetString("a-general.search") + "?" + pathEscape(query)) + if strings.HasPrefix(query, "new:") && len(query) > 4 { + // They're trying to open a link number in a new tab + i, err = strconv.Atoi(query[4:]) + if err != nil { + return + } + if i <= len(tabMap[curTab].Links) && i > 0 { + // Open new tab and load link + oldTab := curTab + NewTab() + // Resolve and follow link manually + prevParsed, _ := url.Parse(tabMap[oldTab].Url) + nextParsed, err := url.Parse(tabMap[oldTab].Links[i-1]) + if err != nil { + Error("URL Error", "link URL could not be parsed") + return + } + URL(prevParsed.ResolveReference(nextParsed).String()) + return + } } else { - // Full URL - URL(query) + // It's a full URL or search term + // Detect if it's a search or URL + if strings.Contains(query, " ") || (!strings.Contains(query, "//") && !strings.Contains(query, ".") && !strings.HasPrefix(query, "about:")) { + URL(viper.GetString("a-general.search") + "?" + pathEscape(query)) + } else { + // Full URL + URL(query) + } + return } - return } if i <= len(tabMap[curTab].Links) && i > 0 { // It's a valid link number followLink(tabMap[curTab].Url, tabMap[curTab].Links[i-1]) return } - // Invalid link number + // Invalid link number, don't do anything bottomBar.SetText(tabMap[curTab].Url) App.SetFocus(tabViews[curTab]) @@ -229,7 +228,15 @@ func Init() { switch event.Key() { case tcell.KeyCtrlT: - NewTab() + if selectedLink != "" { + next, err := resolveRelLink(tabMap[curTab].Url, selectedLink) + if err != nil { + Error("URL Error", err.Error()) + return nil + } + NewTab() + URL(next) + } return nil case tcell.KeyCtrlW: CloseTab() @@ -360,6 +367,7 @@ func NewTab() { tabViews[curTab].Highlight("") bottomBar.SetLabel("") bottomBar.SetText(tabMap[curTab].Url) + selectedLink = "" } currentSelection := tabViews[curTab].GetHighlights() @@ -369,6 +377,7 @@ func NewTab() { 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 @@ -377,6 +386,7 @@ func NewTab() { // 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 @@ -393,6 +403,7 @@ func NewTab() { // Display link URL in bottomBar bottomBar.SetLabel("[::b]Link: [::-]") bottomBar.SetText(tabMap[curTab].Links[index]) + selectedLink = tabMap[curTab].Links[index] } }) @@ -494,6 +505,10 @@ func SwitchTab(tab int) { } func Reload() { + if !tabHasContent() { + return + } + cache.Remove(tabMap[curTab].Url) tabMap[curTab].LeftMargin = 0 // Redo left margin go handleURL(tabMap[curTab].Url) diff --git a/display/help.go b/display/help.go index aad2ac2..948a604 100644 --- a/display/help.go +++ b/display/help.go @@ -1,6 +1,7 @@ package display import ( + "strconv" "strings" "github.com/gdamore/tcell" @@ -13,16 +14,16 @@ Esc|Leave the help Arrow keys, h/j/k/l|Scroll and move a page. Tab|Navigate to the next item in a popup. Shift-Tab|Navigate to the previous item in a popup. -b, Alt-Left|Go back a page -f, Alt-Right|Go forward a page +b, Alt-Left|Go back in the history +f, Alt-Right|Go forward in the history g|Go to top of document G|Go to bottom of document -spacebar|Open bar at the bottom - type a URL, link number, or search term. You can also type two dots (..) to go up a directory in the URL. +spacebar|Open bar at the bottom - type a URL, link number, or search term. You can also type two dots (..) to go up a directory in the URL, as well as new:N to open link number N in a new tab instead of the current one. Enter|On a page this will start link highlighting. Press Tab and Shift-Tab to pick different links. Press Enter again to go to one, or Esc to stop. Shift-NUMBER|Go to a specific tab. Shift-0, )|Go to the last tab. Ctrl-H|Go home -Ctrl-T|New tab +Ctrl-T|New tab, or if a link is selected, this will open the link in a new tab. Ctrl-W|Close tab. For now, only the right-most tab can be closed. Ctrl-R, R|Reload a page, discarding the cached version. Ctrl-B|View bookmarks @@ -32,7 +33,6 @@ q, Ctrl-Q, Ctrl-C|Quit var helpTable = cview.NewTable(). SetSelectable(false, false). - SetFixed(1, 2). SetBorders(true). SetBordersColor(tcell.ColorGray) @@ -42,3 +42,34 @@ func Help() { tabPages.SwitchToPage("help") App.Draw() } + +func helpInit() { + // Populate help table + helpTable.SetDoneFunc(func(key tcell.Key) { + if key == tcell.KeyEsc { + tabPages.SwitchToPage(strconv.Itoa(curTab)) + } + }) + rows := strings.Count(helpCells, "\n") + 1 + cells := strings.Split( + strings.ReplaceAll(helpCells, "\n", "|"), + "|") + cell := 0 + for r := 0; r < rows; r++ { + for c := 0; c < 2; c++ { + var tableCell *cview.TableCell + if c == 0 { + tableCell = cview.NewTableCell(cells[cell]). + SetAttributes(tcell.AttrBold). + SetAlign(cview.AlignCenter) + } else { + tableCell = cview.NewTableCell(" " + cells[cell]) + } + helpTable.SetCell(r, c, tableCell) + cell++ + } + } + tabPages.AddPage("help", helpTable, true, false) +} + +// TODO: Wrap cell text so it's not offscreen diff --git a/display/private.go b/display/private.go index 0beffc8..f144f09 100644 --- a/display/private.go +++ b/display/private.go @@ -1,6 +1,7 @@ package display import ( + "errors" "net/url" "os/exec" "strings" @@ -97,6 +98,22 @@ func applyScroll() { tabViews[curTab].ScrollTo(tabMap[curTab].Row, tabMap[curTab].Column) } +// 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 +// to the user. +func resolveRelLink(prev, next string) (string, error) { + if !tabHasContent() { + return next, nil + } + + prevParsed, _ := url.Parse(prev) + nextParsed, err := url.Parse(next) + if err != nil { + return "", errors.New("link URL could not be parsed") + } + return prevParsed.ResolveReference(nextParsed).String(), nil +} + // 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. func followLink(prev, next string) { @@ -113,14 +130,12 @@ func followLink(prev, next string) { } if tabHasContent() { - saveScroll() // Likely called later on anyway, here just in case - prevParsed, _ := url.Parse(prev) - nextParsed, err := url.Parse(next) + saveScroll() // Likely called later on, it's here just in case + nextURL, err := resolveRelLink(prev, next) if err != nil { - Error("URL Error", "Link URL could not be parsed") + Error("URL Error", err.Error()) return } - nextURL := prevParsed.ResolveReference(nextParsed).String() go func() { final, displayed := handleURL(nextURL) if displayed { @@ -161,7 +176,9 @@ func setLeftMargin(p *structs.Page) { // Old margin needs to be removed, new one added lines := strings.Split(p.Content, "\n") for i := range lines { - shifted += strings.Repeat(" ", lM) + lines[i][p.LeftMargin:] + "\n" + if lines[i] != "" { + shifted += strings.Repeat(" ", lM) + lines[i][p.LeftMargin:] + "\n" + } } } p.Content = shifted