1
0
mirror of https://github.com/makew0rld/amfora.git synced 2024-06-15 19:15:24 +00:00

cview update (#107)

Co-authored-by: makeworld <25111343+makeworld-the-better-one@users.noreply.github.com>
Co-authored-by: Stephen Robinson <stephen@drsudo.net>
Co-authored-by: Trevor Slocum <trevor@rocketnine.space>
Co-authored-by: Stephen Robinson <sudobash1@users.noreply.github.com>
This commit is contained in:
makeworld 2021-02-17 14:17:13 -05:00 committed by GitHub
parent 332aa6af9f
commit 9198572f34
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 625 additions and 549 deletions

View File

@ -24,6 +24,6 @@ jobs:
uses: golangci/golangci-lint-action@v2
with:
# Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
version: v1.31
version: v1.35
# Optional: show only new issues if it's a pull request. The default value is `false`.
only-new-issues: true

View File

@ -8,8 +8,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- **Media type handlers** - open non-text files in another application (#121, #134)
- Ability to set custom keybindings in config (#135)
- Added scrollbar, by default only appears on pages that go off-screen (#89, #107)
- More internal about pages, see `about:about` (#160, 187)
### Changed
- Update cview to `d776e728ef6d2a9990a5cd86a70b31f0678613e2` for large performance and feature updates (#107)
- Update to tcell v2 (dependency of cview)
### Fixed
- Don't use cache when URL is typed in bottom bar (#159)
- Fix downloading of pages that are too large or timed out
@ -18,6 +23,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Handle empty META string (#176)
- Whitespace around the URL entered in the bottom bar is stripped (#184)
- Don't break visiting IPv6 hosts when port 1965 is specified (#195)
- More reliable start, no more flash of unindented text, or text that stays unindented (#107)
- Pages with ANSI resets don't use the terminal's default text and background colors (#107)
- ANSI documents don't leak color into the left margin (#107)
- Rendering very long documents is now ~96% faster, excluding gemtext parsing (#26, #107)
- Due to that same change, less memory is used per-page (#26, #107)
## [1.7.2] - 2020-12-21

View File

@ -4,17 +4,9 @@
- URL for each tab should not be stored as a string - in the current code there's lots of reparsing the URL
## Upstream Bugs
- Wrapping messes up on brackets
- Filed [issue 23](https://gitlab.com/tslocum/cview/-/issues/23)
- Wrapping panics on strings with brackets and Asian characters
- Filed cview [issue 27](https://gitlab.com/tslocum/cview/-/issues/27)
- The panicking was reported and fixed in Amfora [issue 20](https://github.com/makeworld-the-better-one/amfora/issues/20), but the lines are now just not wrapped
- Text background not reset on ANSI pages
- Filed [issue 25](https://gitlab.com/tslocum/cview/-/issues/25)
- Modal styling messed up when wrapped - example occurence is the error modal for a long unsupported scheme URL
- Filed [issue 26](https://gitlab.com/tslocum/cview/-/issues/26)
- Add some bold back into modal text after this is fixed
- Bookmark keys aren't deleted, just set to `""`
- Waiting on [this viper PR](https://github.com/spf13/viper/pull/519) to be merged
- Help table cells aren't dynamically wrapped
- Filed [issue 29](https://gitlab.com/tslocum/cview/-/issues/29)
- [cview.Styles not being used](https://gitlab.com/tslocum/cview/-/issues/47) - issue is circumvented in Amfora
- [ANSI conversion is messed up](https://gitlab.com/tslocum/cview/-/issues/48)
- [WordWrap is broken in some cases](https://gitlab.com/tslocum/cview/-/issues/27#note_475438483) - close #156 if this is fixed
- [Prevent panic when reformatting](https://gitlab.com/tslocum/cview/-/issues/50) - can't reliably reproduce or debug

View File

@ -178,7 +178,7 @@ You can also check out [all the issues with the bug label](https://github.com/ma
## Libraries
Amfora ❤️ open source!
- [cview](https://gitlab.com/tslocum/cview/) for the TUI
- My [cview fork](https://gitlab.com/makeworld-the-better-one/cview/) for the TUI - pull request [here](https://gitlab.com/tslocum/cview/-/merge_requests/12)
- It's a fork of [tview](https://github.com/rivo/tview) with PRs merged and active support
- It uses [tcell](https://github.com/gdamore/tcell) for low level terminal operations
- [Viper](https://github.com/spf13/viper) for configuration and TOFU storing

View File

@ -52,14 +52,19 @@ func main() {
client.Init()
// Initialize lower-level cview app
if err = display.App.Init(); err != nil {
panic(err)
}
// Initialize Amfora's settings
display.Init(version, commit, builtBy)
display.NewTab()
display.NewTab() // Open extra tab and close it to fully initialize the app and wrapping
display.CloseTab()
if len(os.Args[1:]) > 0 {
display.URL(os.Args[1])
}
// Start
if err = display.App.Run(); err != nil {
panic(err)
}

View File

@ -11,7 +11,7 @@ import (
"runtime"
"strings"
"github.com/gdamore/tcell"
"github.com/gdamore/tcell/v2"
"github.com/makeworld-the-better-one/amfora/cache"
homedir "github.com/mitchellh/go-homedir"
"github.com/rkoesters/xdg/basedir"
@ -55,6 +55,10 @@ type MediaHandler struct {
var MediaHandlers = make(map[string]MediaHandler)
// Controlled by "a-general.scrollbar" in config
// Defaults to ScrollBarAuto on an invalid value
var ScrollBar cview.ScrollBarVisibility
func Init() error {
// *** Set paths ***
@ -204,6 +208,7 @@ func Init() error {
viper.SetDefault("a-general.page_max_size", 2097152)
viper.SetDefault("a-general.page_max_time", 10)
viper.SetDefault("a-general.emoji_favicons", false)
viper.SetDefault("a-general.scrollbar", "auto")
viper.SetDefault("keybindings.bind_reload", []string{"R", "Ctrl-R"})
viper.SetDefault("keybindings.bind_home", "Backspace")
viper.SetDefault("keybindings.bind_bookmarks", "Ctrl-B")
@ -392,5 +397,15 @@ func Init() error {
}
}
// Parse scrollbar options
switch viper.GetString("a-general.scrollbar") {
case "never":
ScrollBar = cview.ScrollBarNever
case "always":
ScrollBar = cview.ScrollBarAlways
default:
ScrollBar = cview.ScrollBarAuto
}
return nil
}

View File

@ -73,6 +73,10 @@ page_max_time = 10
# Whether to replace tab numbers with emoji favicons, which are cached.
emoji_favicons = false
# When a scrollbar appears. "never", "auto", and "always" are the only valid values.
# "auto" means the scrollbar only appears when the page is longer than the window.
scrollbar = "auto"
[auth]
# Authentication settings
@ -301,6 +305,7 @@ entries_per_page = 20
# bottombar_label: The color of the prompt that appears when you press space
# bottombar_text: The color of the text you type
# bottombar_bg
# scrollbar: The scrollbar that appears on the right for long pages
# hdg_1
# hdg_2

View File

@ -3,7 +3,7 @@ package config
import (
"strings"
"github.com/gdamore/tcell"
"github.com/gdamore/tcell/v2"
"github.com/spf13/viper"
)

View File

@ -4,7 +4,7 @@ import (
"fmt"
"sync"
"github.com/gdamore/tcell"
"github.com/gdamore/tcell/v2"
)
// Functions to allow themeing configuration.
@ -21,6 +21,7 @@ var theme = map[string]tcell.Color{
"bottombar_label": tcell.Color30,
"bottombar_text": tcell.ColorBlack,
"bottombar_bg": tcell.ColorWhite,
"scrollbar": tcell.ColorWhite,
// Modals
"btn_bg": tcell.ColorNavy, // All modal buttons
@ -74,7 +75,7 @@ func SetColor(key string, color tcell.Color) {
func GetColor(key string) tcell.Color {
themeMu.RLock()
defer themeMu.RUnlock()
return theme[key]
return theme[key].TrueColor()
}
// GetColorString returns a string that can be used in a cview color tag,
@ -83,5 +84,5 @@ func GetColor(key string) tcell.Color {
func GetColorString(key string) string {
themeMu.RLock()
defer themeMu.RUnlock()
return fmt.Sprintf("#%06x", theme[key].Hex())
return fmt.Sprintf("#%06x", theme[key].TrueColor().Hex())
}

View File

@ -70,6 +70,10 @@ page_max_time = 10
# Whether to replace tab numbers with emoji favicons, which are cached.
emoji_favicons = false
# When a scrollbar appears. "never", "auto", and "always" are the only valid values.
# "auto" means the scrollbar only appears when the page is longer than the window.
scrollbar = "auto"
[auth]
# Authentication settings
@ -298,6 +302,7 @@ entries_per_page = 20
# bottombar_label: The color of the prompt that appears when you press space
# bottombar_text: The color of the text you type
# bottombar_bg
# scrollbar: The scrollbar that appears on the right for long pages
# hdg_1
# hdg_2

View File

@ -34,7 +34,7 @@ func aboutInit(version, commit, builtBy string) {
}
func createAboutPage(url string, content string) structs.Page {
renderContent, links := renderer.RenderGemini(content, textWidth(), leftMargin(), false)
renderContent, links := renderer.RenderGemini(content, textWidth(), false)
return structs.Page{
Raw: content,
Content: renderContent,

View File

@ -2,9 +2,8 @@ package display
import (
"fmt"
"strconv"
"github.com/gdamore/tcell"
"github.com/gdamore/tcell/v2"
"github.com/makeworld-the-better-one/amfora/bookmarks"
"github.com/makeworld-the-better-one/amfora/config"
"github.com/makeworld-the-better-one/amfora/renderer"
@ -21,37 +20,44 @@ var bkmkCh = make(chan int) // 1, 0, -1 for add/update, cancel, and remove
var bkmkModalText string // The current text of the input field in the modal
func bkmkInit() {
panels.AddPanel("bkmk", bkmkModal, false, false)
m := bkmkModal
if viper.GetBool("a-general.color") {
bkmkModal.SetBackgroundColor(config.GetColor("bkmk_modal_bg")).
SetButtonBackgroundColor(config.GetColor("btn_bg")).
SetButtonTextColor(config.GetColor("btn_text")).
SetTextColor(config.GetColor("bkmk_modal_text"))
bkmkModal.GetForm().
SetLabelColor(config.GetColor("bkmk_modal_label")).
SetFieldBackgroundColor(config.GetColor("bkmk_modal_field_bg")).
SetFieldTextColor(config.GetColor("bkmk_modal_field_text"))
bkmkModal.GetFrame().
SetBorderColor(config.GetColor("bkmk_modal_text")).
SetTitleColor(config.GetColor("bkmk_modal_text"))
m.SetBackgroundColor(config.GetColor("bkmk_modal_bg"))
m.SetButtonBackgroundColor(config.GetColor("btn_bg"))
m.SetButtonTextColor(config.GetColor("btn_text"))
m.SetTextColor(config.GetColor("bkmk_modal_text"))
form := m.GetForm()
form.SetLabelColor(config.GetColor("bkmk_modal_label"))
form.SetFieldBackgroundColor(config.GetColor("bkmk_modal_field_bg"))
form.SetFieldTextColor(config.GetColor("bkmk_modal_field_text"))
form.SetButtonBackgroundColorFocused(config.GetColor("btn_text"))
form.SetButtonTextColorFocused(config.GetColor("btn_bg"))
frame := m.GetFrame()
frame.SetBorderColor(config.GetColor("bkmk_modal_text"))
frame.SetTitleColor(config.GetColor("bkmk_modal_text"))
} else {
bkmkModal.SetBackgroundColor(tcell.ColorBlack).
SetButtonBackgroundColor(tcell.ColorWhite).
SetButtonTextColor(tcell.ColorBlack).
SetTextColor(tcell.ColorWhite)
bkmkModal.GetForm().
SetLabelColor(tcell.ColorWhite).
SetFieldBackgroundColor(tcell.ColorWhite).
SetFieldTextColor(tcell.ColorBlack)
bkmkModal.GetFrame().
SetBorderColor(tcell.ColorWhite).
SetTitleColor(tcell.ColorWhite)
m.SetBackgroundColor(tcell.ColorBlack)
m.SetButtonBackgroundColor(tcell.ColorWhite)
m.SetButtonTextColor(tcell.ColorBlack)
m.SetTextColor(tcell.ColorWhite)
form := m.GetForm()
form.SetLabelColor(tcell.ColorWhite)
form.SetFieldBackgroundColor(tcell.ColorWhite)
form.SetFieldTextColor(tcell.ColorBlack)
form.SetButtonBackgroundColorFocused(tcell.ColorBlack)
form.SetButtonTextColorFocused(tcell.ColorWhite)
frame := m.GetFrame()
frame.SetBorderColor(tcell.ColorWhite)
frame.SetTitleColor(tcell.ColorWhite)
}
bkmkModal.SetBorder(true)
bkmkModal.GetFrame().
SetTitleAlign(cview.AlignCenter).
SetTitle(" Add Bookmark ")
bkmkModal.SetDoneFunc(func(buttonIndex int, buttonLabel string) {
m.SetBorder(true)
frame := m.GetFrame()
frame.SetTitleAlign(cview.AlignCenter)
frame.SetTitle(" Add Bookmark ")
m.SetDoneFunc(func(buttonIndex int, buttonLabel string) {
switch buttonLabel {
case "Add":
bkmkCh <- 1
@ -97,13 +103,13 @@ func openBkmkModal(name string, exists bool, favicon string) (string, int) {
bkmkModalText = text
})
tabPages.ShowPage("bkmk")
tabPages.SendToFront("bkmk")
panels.ShowPanel("bkmk")
panels.SendToFront("bkmk")
App.SetFocus(bkmkModal)
App.Draw()
action := <-bkmkCh
tabPages.SwitchToPage(strconv.Itoa(curTab))
panels.HidePanel("bkmk")
App.SetFocus(tabs[curTab].view)
App.Draw()
@ -120,7 +126,7 @@ func Bookmarks(t *tab) {
bkmkPageRaw += fmt.Sprintf("=> %s %s\r\n", keys[i], m[keys[i]])
}
// Render and display
content, links := renderer.RenderGemini(bkmkPageRaw, textWidth(), leftMargin(), false)
content, links := renderer.RenderGemini(bkmkPageRaw, textWidth(), false)
page := structs.Page{
Raw: bkmkPageRaw,
Content: content,

View File

@ -6,8 +6,9 @@ import (
"regexp"
"strconv"
"strings"
"sync"
"github.com/gdamore/tcell"
"github.com/gdamore/tcell/v2"
"github.com/makeworld-the-better-one/amfora/cache"
"github.com/makeworld-the-better-one/amfora/config"
"github.com/makeworld-the-better-one/amfora/renderer"
@ -39,76 +40,92 @@ var hasSpaceisURL = regexp.MustCompile(`[^ ]+\.[^ ].*/.`)
// The only pages that don't confine to this scheme are those named after modals,
// which are used to draw modals on top the current tab.
// Ex: "info", "error", "input", "yesno"
var tabPages = cview.NewPages()
var panels = cview.NewPanels()
// The tabs at the top with titles
var tabRow = cview.NewTextView().
SetDynamicColors(true).
SetRegions(true).
SetScrollable(true).
SetWrap(false).
SetHighlightedFunc(func(added, removed, remaining []string) {
// There will always only be one string in added - never multiple highlights
// Remaining should always be empty
i, _ := strconv.Atoi(added[0])
tabPages.SwitchToPage(strconv.Itoa(i)) // Tab names are just numbers, zero-indexed
})
// Tabbed viewer for primitives
// Panels are named as strings of tab numbers - so the textview for the first tab
// is held in the page named "0".
var browser = cview.NewTabbedPanels()
// Root layout
var layout = cview.NewFlex().
SetDirection(cview.FlexRow)
var layout = cview.NewFlex()
var newTabPage structs.Page
var App = cview.NewApplication().
EnableMouse(false).
SetRoot(layout, true).
SetAfterResizeFunc(func(width int, height int) {
// Global mutex for changing the size of the left margin on all tabs.
var reformatMu = sync.Mutex{}
var App = cview.NewApplication()
func Init(version, commit, builtBy string) {
aboutInit(version, commit, builtBy)
App.EnableMouse(false)
App.SetRoot(layout, true)
App.SetAfterResizeFunc(func(width int, height int) {
// Store for calculations
termW = width
termH = height
// Make sure the current tab content is reformatted when the terminal size changes
go func(t *tab) {
t.reformatMu.Lock() // Only one reformat job per tab
defer t.reformatMu.Unlock()
// Use the current tab, but don't affect other tabs if the user switches tabs
reformatPageAndSetView(t, t.page)
reformatMu.Lock() // Only allow one reformat job at a time
for i := range tabs {
// Overwrite all tabs with a new, differently sized, left margin
browser.AddTab(strconv.Itoa(i), makeTabLabel(strconv.Itoa(i+1)), makeContentLayout(tabs[i].view))
if tabs[i] == t {
// Reformat page ASAP, in the middle of loop
reformatPageAndSetView(t, t.page)
}
}
App.Draw()
reformatMu.Unlock()
}(tabs[curTab])
})
func Init(version, commit, builtBy string) {
aboutInit(version, commit, builtBy)
tabRow.SetChangedFunc(func() {
App.Draw()
})
panels.AddPanel("browser", browser, true, true)
helpInit()
layout.
AddItem(tabRow, 1, 1, false).
AddItem(nil, 1, 1, false). // One line of empty space above the page
AddItem(tabPages, 0, 1, true).
AddItem(nil, 1, 1, false). // One line of empty space before bottomBar
AddItem(bottomBar, 1, 1, false)
layout.SetDirection(cview.FlexRow)
layout.AddItem(panels, 0, 1, true)
layout.AddItem(bottomBar, 1, 1, false)
if viper.GetBool("a-general.color") {
layout.SetBackgroundColor(config.GetColor("bg"))
tabRow.SetBackgroundColor(config.GetColor("bg"))
bottomBar.SetBackgroundColor(config.GetColor("bottombar_bg"))
bottomBar.
SetLabelColor(config.GetColor("bottombar_label")).
SetFieldBackgroundColor(config.GetColor("bottombar_bg")).
SetFieldTextColor(config.GetColor("bottombar_text"))
bottomBar.SetLabelColor(config.GetColor("bottombar_label"))
bottomBar.SetFieldBackgroundColor(config.GetColor("bottombar_bg"))
bottomBar.SetFieldTextColor(config.GetColor("bottombar_text"))
browser.SetTabBackgroundColor(config.GetColor("bg"))
browser.SetTabBackgroundColorFocused(config.GetColor("tab_num"))
browser.SetTabTextColor(config.GetColor("tab_num"))
browser.SetTabTextColorFocused(config.GetColor("bg"))
browser.SetTabSwitcherDivider(
"",
fmt.Sprintf("[%s:%s]|[-]", config.GetColorString("tab_divider"), config.GetColorString("bg")),
fmt.Sprintf("[%s:%s]|[-]", config.GetColorString("tab_divider"), config.GetColorString("bg")),
)
browser.Switcher.SetBackgroundColor(config.GetColor("bg"))
} else {
bottomBar.SetBackgroundColor(tcell.ColorWhite)
bottomBar.
SetLabelColor(tcell.ColorBlack).
SetFieldBackgroundColor(tcell.ColorWhite).
SetFieldTextColor(tcell.ColorBlack)
bottomBar.SetLabelColor(tcell.ColorBlack)
bottomBar.SetFieldBackgroundColor(tcell.ColorWhite)
bottomBar.SetFieldTextColor(tcell.ColorBlack)
browser.SetTabBackgroundColor(tcell.ColorBlack)
browser.SetTabBackgroundColorFocused(tcell.ColorWhite)
browser.SetTabTextColor(tcell.ColorWhite)
browser.SetTabTextColorFocused(tcell.ColorBlack)
browser.SetTabSwitcherDivider(
"",
"[#ffffff:#000000]|[-]",
"[#ffffff:#000000]|[-]",
)
}
bottomBar.SetDoneFunc(func(key tcell.Key) {
tab := curTab
@ -230,7 +247,7 @@ func Init(version, commit, builtBy string) {
// Render the default new tab content ONCE and store it for later
// This code is repeated in Reload()
newTabContent := getNewTabContent()
renderedNewTabContent, newTabLinks := renderer.RenderGemini(newTabContent, textWidth(), leftMargin(), false)
renderedNewTabContent, newTabLinks := renderer.RenderGemini(newTabContent, textWidth(), false)
newTabPage = structs.Page{
Raw: newTabContent,
Content: renderedNewTabContent,
@ -426,23 +443,10 @@ func NewTab() {
tabs[curTab].addToHistory("about:newtab")
tabs[curTab].history.pos = 0 // Manually set as first page
tabPages.AddAndSwitchToPage(strconv.Itoa(curTab), tabs[curTab].view, true)
browser.AddTab(strconv.Itoa(curTab), makeTabLabel(strconv.Itoa(curTab+1)), makeContentLayout(tabs[curTab].view))
browser.SetCurrentTab(strconv.Itoa(curTab))
App.SetFocus(tabs[curTab].view)
// 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
if viper.GetBool("a-general.color") {
fmt.Fprintf(tabRow, `["%d"][%s] %d [%s][""]|`,
curTab,
config.GetColorString("tab_num"),
curTab+1,
config.GetColorString("tab_divider"),
)
} else {
fmt.Fprintf(tabRow, `["%d"] %d [""]|`, curTab, curTab+1)
}
tabRow.Highlight(strconv.Itoa(curTab)).ScrollToHighlight()
bottomBar.SetLabel("")
bottomBar.SetText("")
tabs[curTab].saveBottomBar()
@ -469,7 +473,7 @@ func CloseTab() {
}
tabs = tabs[:len(tabs)-1]
tabPages.RemovePage(strconv.Itoa(curTab))
browser.RemoveTab(strconv.Itoa(curTab))
if curTab <= 0 {
curTab = NumTabs() - 1
@ -477,8 +481,7 @@ func CloseTab() {
curTab--
}
tabPages.SwitchToPage(strconv.Itoa(curTab)) // Go to previous page
rewriteTabRow()
browser.SetCurrentTab(strconv.Itoa(curTab)) // Go to previous page
// Restore previous tab's state
tabs[curTab].applyAll()
@ -510,8 +513,7 @@ func SwitchTab(tab int) {
// Display tab
reformatPageAndSetView(tabs[curTab], tabs[curTab].page)
tabPages.SwitchToPage(strconv.Itoa(curTab))
tabRow.Highlight(strconv.Itoa(curTab)).ScrollToHighlight()
browser.SetCurrentTab(strconv.Itoa(curTab))
tabs[curTab].applyAll()
App.SetFocus(tabs[curTab].view)
@ -525,7 +527,7 @@ func Reload() {
// Re-render new tab, similar to Init()
newTabContent := getNewTabContent()
tmpTermW := termW
renderedNewTabContent, newTabLinks := renderer.RenderGemini(newTabContent, textWidth(), leftMargin(), false)
renderedNewTabContent, newTabLinks := renderer.RenderGemini(newTabContent, textWidth(), false)
newTabPage = structs.Page{
Raw: newTabContent,
Content: renderedNewTabContent,

View File

@ -14,7 +14,7 @@ import (
"strings"
"time"
"github.com/gdamore/tcell"
"github.com/gdamore/tcell/v2"
"github.com/makeworld-the-better-one/amfora/config"
"github.com/makeworld-the-better-one/amfora/structs"
"github.com/makeworld-the-better-one/amfora/sysopen"
@ -24,9 +24,8 @@ import (
"gitlab.com/tslocum/cview"
)
// For choosing between download and opening - copy of YesNo basically
var dlChoiceModal = cview.NewModal().
AddButtons([]string{"Open", "Download", "Cancel"})
// For choosing between download and the portal - copy of YesNo basically
var dlChoiceModal = cview.NewModal()
// Channel to indicate what choice they made using the button text
var dlChoiceCh = make(chan string)
@ -34,52 +33,70 @@ var dlChoiceCh = make(chan string)
var dlModal = cview.NewModal()
func dlInit() {
panels.AddPanel("dl", dlModal, false, false)
panels.AddPanel("dlChoice", dlChoiceModal, false, false)
dlm := dlModal
chm := dlChoiceModal
if viper.GetBool("a-general.color") {
dlChoiceModal.SetButtonBackgroundColor(config.GetColor("btn_bg")).
SetButtonTextColor(config.GetColor("btn_text")).
SetBackgroundColor(config.GetColor("dl_choice_modal_bg")).
SetTextColor(config.GetColor("dl_choice_modal_text"))
dlChoiceModal.GetFrame().
SetBorderColor(config.GetColor("dl_choice_modal_text")).
SetTitleColor(config.GetColor("dl_choice_modal_text"))
chm.SetButtonBackgroundColor(config.GetColor("btn_bg"))
chm.SetButtonTextColor(config.GetColor("btn_text"))
chm.SetBackgroundColor(config.GetColor("dl_choice_modal_bg"))
chm.SetTextColor(config.GetColor("dl_choice_modal_text"))
form := chm.GetForm()
form.SetButtonBackgroundColorFocused(config.GetColor("btn_text"))
form.SetButtonTextColorFocused(config.GetColor("btn_bg"))
frame := chm.GetFrame()
frame.SetBorderColor(config.GetColor("dl_choice_modal_text"))
frame.SetTitleColor(config.GetColor("dl_choice_modal_text"))
dlModal.SetButtonBackgroundColor(config.GetColor("btn_bg")).
SetButtonTextColor(config.GetColor("btn_text")).
SetBackgroundColor(config.GetColor("dl_modal_bg")).
SetTextColor(config.GetColor("dl_modal_text"))
dlModal.GetFrame().
SetBorderColor(config.GetColor("dl_modal_text")).
SetTitleColor(config.GetColor("dl_modal_text"))
dlm.SetButtonBackgroundColor(config.GetColor("btn_bg"))
dlm.SetButtonTextColor(config.GetColor("btn_text"))
dlm.SetBackgroundColor(config.GetColor("dl_modal_bg"))
dlm.SetTextColor(config.GetColor("dl_modal_text"))
form = dlm.GetForm()
form.SetButtonBackgroundColorFocused(config.GetColor("btn_text"))
form.SetButtonTextColorFocused(config.GetColor("btn_bg"))
frame = dlm.GetFrame()
frame.SetBorderColor(config.GetColor("dl_modal_text"))
frame.SetTitleColor(config.GetColor("dl_modal_text"))
} else {
dlChoiceModal.SetButtonBackgroundColor(tcell.ColorWhite).
SetButtonTextColor(tcell.ColorBlack).
SetBackgroundColor(tcell.ColorBlack).
SetTextColor(tcell.ColorWhite)
dlChoiceModal.SetBorderColor(tcell.ColorWhite)
dlChoiceModal.GetFrame().SetTitleColor(tcell.ColorWhite)
chm.SetButtonBackgroundColor(tcell.ColorWhite)
chm.SetButtonTextColor(tcell.ColorBlack)
chm.SetBackgroundColor(tcell.ColorBlack)
chm.SetTextColor(tcell.ColorWhite)
chm.SetBorderColor(tcell.ColorWhite)
chm.GetFrame().SetTitleColor(tcell.ColorWhite)
form := chm.GetForm()
form.SetButtonBackgroundColorFocused(tcell.ColorBlack)
form.SetButtonTextColorFocused(tcell.ColorWhite)
dlModal.SetButtonBackgroundColor(tcell.ColorWhite).
SetButtonTextColor(tcell.ColorBlack).
SetBackgroundColor(tcell.ColorBlack).
SetTextColor(tcell.ColorWhite)
dlModal.GetFrame().
SetBorderColor(tcell.ColorWhite).
SetTitleColor(tcell.ColorWhite)
dlm.SetButtonBackgroundColor(tcell.ColorWhite)
dlm.SetButtonTextColor(tcell.ColorBlack)
dlm.SetBackgroundColor(tcell.ColorBlack)
dlm.SetTextColor(tcell.ColorWhite)
form = dlm.GetForm()
form.SetButtonBackgroundColorFocused(tcell.ColorBlack)
form.SetButtonTextColorFocused(tcell.ColorWhite)
frame := dlm.GetFrame()
frame.SetBorderColor(tcell.ColorWhite)
frame.SetTitleColor(tcell.ColorWhite)
}
dlChoiceModal.SetBorder(true)
dlChoiceModal.GetFrame().SetTitleAlign(cview.AlignCenter)
dlChoiceModal.SetDoneFunc(func(buttonIndex int, buttonLabel string) {
chm.AddButtons([]string{"Open", "Download", "Cancel"})
chm.SetBorder(true)
chm.GetFrame().SetTitleAlign(cview.AlignCenter)
chm.SetDoneFunc(func(buttonIndex int, buttonLabel string) {
dlChoiceCh <- buttonLabel
})
dlModal.SetBorder(true)
dlModal.GetFrame().
SetTitleAlign(cview.AlignCenter).
SetTitle(" Download ")
dlModal.SetDoneFunc(func(buttonIndex int, buttonLabel string) {
dlm.SetBorder(true)
frame := dlm.GetFrame()
frame.SetTitleAlign(cview.AlignCenter)
frame.SetTitle(" Download ")
dlm.SetDoneFunc(func(buttonIndex int, buttonLabel string) {
if buttonLabel == "Ok" {
tabPages.SwitchToPage(strconv.Itoa(curTab))
panels.HidePanel("dl")
App.SetFocus(tabs[curTab].view)
App.Draw()
}
@ -124,27 +141,29 @@ func dlChoice(text, u string, resp *gemini.Response) {
choice = "Open"
} else {
dlChoiceModal.SetText(text)
tabPages.ShowPage("dlChoice")
tabPages.SendToFront("dlChoice")
panels.ShowPanel("dlChoice")
panels.SendToFront("dlChoice")
App.SetFocus(dlChoiceModal)
App.Draw()
choice = <-dlChoiceCh
}
if choice == "Download" {
tabPages.HidePage("dlChoice")
panels.HidePanel("dlChoice")
App.Draw()
downloadURL(config.DownloadsDir, u, resp)
resp.Body.Close() // Only close when the file is downloaded
return
}
if choice == "Open" {
tabPages.HidePage("dlChoice")
panels.HidePanel("dlChoice")
App.Draw()
open(u, resp)
return
}
tabPages.SwitchToPage(strconv.Itoa(curTab))
// They chose the "Cancel" button
panels.HidePanel("dlChoice")
App.SetFocus(tabs[curTab].view)
App.Draw()
}
@ -180,9 +199,11 @@ func open(u string, resp *gemini.Response) {
if path == "" {
return
}
tabPages.SwitchToPage(strconv.Itoa(curTab))
panels.HidePanel("dl")
App.SetFocus(tabs[curTab].view)
App.Draw()
if mediaHandler.Cmd == nil {
// Open with system default viewer
_, err := sysopen.Open(path)
@ -246,15 +267,15 @@ func downloadURL(dir, u string, resp *gemini.Response) string {
// Display
dlModal.ClearButtons()
dlModal.AddButtons([]string{"Downloading..."})
tabPages.ShowPage("dl")
tabPages.SendToFront("dl")
panels.ShowPanel("dl")
panels.SendToFront("dl")
App.SetFocus(dlModal)
App.Draw()
_, err = io.Copy(io.MultiWriter(f, bar), resp.Body)
done = true
if err != nil {
tabPages.HidePage("dl")
panels.HidePanel("dl")
Error("Download Error", err.Error())
f.Close()
os.Remove(savePath) // Remove partial file

View File

@ -59,7 +59,7 @@ func handleFile(u string) (*structs.Page, bool) {
}
if mimetype == "text/gemini" {
rendered, links := renderer.RenderGemini(string(content), textWidth(), leftMargin(), false)
rendered, links := renderer.RenderGemini(string(content), textWidth(), false)
page = &structs.Page{
Mediatype: structs.TextGemini,
URL: u,
@ -73,7 +73,7 @@ func handleFile(u string) (*structs.Page, bool) {
Mediatype: structs.TextPlain,
URL: u,
Raw: string(content),
Content: renderer.RenderPlainText(string(content), leftMargin()),
Content: renderer.RenderPlainText(string(content)),
Links: []string{},
Width: termW,
}
@ -107,7 +107,7 @@ func createDirectoryListing(u string) (*structs.Page, bool) {
content += fmt.Sprintf("=> %s%s %s%s\n", f.Name(), separator, f.Name(), separator)
}
rendered, links := renderer.RenderGemini(content, textWidth(), leftMargin(), false)
rendered, links := renderer.RenderGemini(content, textWidth(), false)
page = &structs.Page{
Mediatype: structs.TextGemini,
URL: u,

View File

@ -9,6 +9,7 @@ import (
"net/url"
"os/exec"
"path"
"strconv"
"strings"
"github.com/makeworld-the-better-one/amfora/cache"
@ -90,12 +91,12 @@ func handleOther(u string) {
}
// handleFavicon handles getting and displaying a favicon.
// `old` is the previous favicon for the tab.
func handleFavicon(t *tab, host, old string) {
func handleFavicon(t *tab, host string) {
defer func() {
// Update display if needed
if t.page.Favicon != old && isValidTab(t) {
rewriteTabRow()
if t.page.Favicon != "" && isValidTab(t) {
browser.SetTabLabel(strconv.Itoa(tabNumber(t)), makeTabLabel(t.page.Favicon))
App.Draw()
}
}()
@ -117,7 +118,6 @@ func handleFavicon(t *tab, host, old string) {
}
if fav != "" {
t.page.Favicon = fav
rewriteTabRow()
return
}
@ -389,7 +389,7 @@ func handleURL(t *tab, u string, numRedirects int) (string, bool) {
res.Body = rr.NewRestartReader(res.Body)
if renderer.CanDisplay(res) {
page, err := renderer.MakePage(u, res, textWidth(), leftMargin(), usingProxy)
page, err := renderer.MakePage(u, res, textWidth(), usingProxy)
// Rendering may have taken a while, make sure tab is still valid
if !isValidTab(t) {
return ret("", false)

View File

@ -2,72 +2,69 @@ package display
import (
"fmt"
"strconv"
"strings"
"text/tabwriter"
"github.com/gdamore/tcell"
"github.com/gdamore/tcell/v2"
"github.com/makeworld-the-better-one/amfora/config"
"gitlab.com/tslocum/cview"
)
var helpCells = strings.TrimSpace(`
?|Bring up this help. You can scroll!
Enter|Close this help page
Esc|Close this help page or any active modal popups
Arrow keys, h/j/k/l|Scroll and move a page.
%s|Go up a page in document
%s|Go down a page in document
g|Go to top of document
G|Go to bottom of document
Tab|Navigate to the next item in a popup.
Shift-Tab|Navigate to the previous item in a popup.
%s|Go back in the history
%s|Go forward in the history
%s|Open bar at the bottom - type a URL, link number, search term.
|You can also type two dots (..) to go up a directory in the URL.
|Typing new:N will open link number N in a new tab
|instead of the current one.
%s|Go to links 1-10 respectively.
%s|Edit current URL
Enter, Tab|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.
%s|Go to a specific tab. (Default: Shift-NUMBER)
%s|Go to the last tab.
%s|Previous tab
%s|Next tab
%s|Go home
%s|New tab, or if a link is selected,
|this will open the link in a new tab.
%s|Close tab. For now, only the right-most tab can be closed.
%s|Reload a page, discarding the cached version.
|This can also be used if you resize your terminal.
%s|View bookmarks
%s|Add, change, or remove a bookmark for the current page.
%s|Save the current page to your downloads.
%s|View subscriptions
%s|Add or update a subscription
%s|Quit
`)
var helpCells = strings.TrimSpace(
"?\tBring up this help. You can scroll!\n" +
"Esc\tLeave the help\n" +
"Arrow keys, h/j/k/l\tScroll and move a page.\n" +
"%s\tGo up a page in document\n" +
"%s\tGo down a page in document\n" +
"g\tGo to top of document\n" +
"G\tGo to bottom of document\n" +
"Tab\tNavigate to the next item in a popup.\n" +
"Shift-Tab\tNavigate to the previous item in a popup.\n" +
"%s\tGo back in the history\n" +
"%s\tGo forward in the history\n" +
"%s\tOpen bar at the bottom - type a URL, link number, search term.\n" +
"\tYou can also type two dots (..) to go up a directory in the URL.\n" +
"\tTyping new:N will open link number N in a new tab\n" +
"\tinstead of the current one.\n" +
"%s\tGo to links 1-10 respectively.\n" +
"%s\tEdit current URL\n" +
"Enter, Tab\tOn a page this will start link highlighting.\n" +
"\tPress Tab and Shift-Tab to pick different links.\n" +
"\tPress Enter again to go to one, or Esc to stop.\n" +
"%s\tGo to a specific tab. (Default: Shift-NUMBER)\n" +
"%s\tGo to the last tab.\n" +
"%s\tPrevious tab\n" +
"%s\tNext tab\n" +
"%s\tGo home\n" +
"%s\tNew tab, or if a link is selected,\n" +
"\tthis will open the link in a new tab.\n" +
"%s\tClose tab. For now, only the right-most tab can be closed.\n" +
"%s\tReload a page, discarding the cached version.\n" +
"\tThis can also be used if you resize your terminal.\n" +
"%s\tView bookmarks\n" +
"%s\tAdd, change, or remove a bookmark for the current page.\n" +
"%s\tSave the current page to your downloads.\n" +
"%s\tView subscriptions\n" +
"%s\tAdd or update a subscription\n" +
"%s\tQuit\n")
var helpTable = cview.NewTable().
SetSelectable(false, false).
SetBorders(false).
SetScrollBarVisibility(cview.ScrollBarNever)
var helpTable = cview.NewTextView()
// Help displays the help and keybindings.
func Help() {
helpTable.ScrollToBeginning()
tabPages.SwitchToPage("help")
panels.ShowPanel("help")
panels.SendToFront("help")
App.SetFocus(helpTable)
App.Draw()
}
func helpInit() {
// Populate help table
helpTable.SetBackgroundColor(config.GetColor("bg"))
helpTable.SetPadding(0, 0, 1, 1)
helpTable.SetDoneFunc(func(key tcell.Key) {
if key == tcell.KeyEsc || key == tcell.KeyEnter {
tabPages.SwitchToPage(strconv.Itoa(curTab))
panels.HidePanel("help")
App.SetFocus(tabs[curTab].view)
App.Draw()
}
@ -102,34 +99,16 @@ func helpInit() {
config.GetKeyBinding(config.CmdQuit),
)
rows := strings.Count(helpCells, "\n") + 1
cells := strings.Split(
strings.ReplaceAll(helpCells, "\n", "|"),
"|")
cell := 0
extraRows := 0 // Rows continued from the previous, without spacing
for r := 0; r < rows; r++ {
for c := 0; c < 2; c++ {
var tableCell *cview.TableCell
if c == 0 {
// First column, the keybinding
tableCell = cview.NewTableCell(" " + cells[cell]).
SetAttributes(tcell.AttrBold).
SetAlign(cview.AlignLeft)
} else {
tableCell = cview.NewTableCell(" " + cells[cell])
}
if c == 0 && cells[cell] == "" || (cell > 0 && cells[cell-1] == "" && c == 1) {
// The keybinding column for this row was blank, meaning the explanation
// column is continued from the previous row.
// The row should be added without any spacing rows
helpTable.SetCell(((2*r)-extraRows/2)-1, c, tableCell)
extraRows++
} else {
helpTable.SetCell((2*r)-extraRows/2, c, tableCell) // Every other row, for readability
}
cell++
lines := strings.Split(helpCells, "\n")
w := tabwriter.NewWriter(helpTable, 0, 8, 2, ' ', 0)
for i, line := range lines {
if i > 0 && line[0] != '\t' {
fmt.Fprintln(w, "\t")
}
fmt.Fprintln(w, line)
}
tabPages.AddPage("help", helpTable, true, false)
w.Flush()
panels.AddPanel("help", helpTable, true, false)
}

View File

@ -2,12 +2,11 @@ package display
import (
"fmt"
"strconv"
"strings"
"time"
humanize "github.com/dustin/go-humanize"
"github.com/gdamore/tcell"
"github.com/gdamore/tcell/v2"
"github.com/makeworld-the-better-one/amfora/config"
"github.com/spf13/viper"
"gitlab.com/tslocum/cview"
@ -16,103 +15,133 @@ import (
// This file contains code for the popups / modals used in the display.
// The bookmark modal is in bookmarks.go
var infoModal = cview.NewModal().
AddButtons([]string{"Ok"})
var infoModal = cview.NewModal()
var errorModal = cview.NewModal().
AddButtons([]string{"Ok"})
var errorModal = cview.NewModal()
var inputModal = cview.NewModal()
var inputCh = make(chan string)
var inputModalText string // The current text of the input field in the modal
var yesNoModal = cview.NewModal().
AddButtons([]string{"Yes", "No"})
var yesNoModal = cview.NewModal()
// Channel to receive yesNo answer on
var yesNoCh = make(chan bool)
func modalInit() {
tabPages.AddPage("info", infoModal, false, false).
AddPage("error", errorModal, false, false).
AddPage("input", inputModal, false, false).
AddPage("yesno", yesNoModal, false, false).
AddPage("bkmk", bkmkModal, false, false).
AddPage("dlChoice", dlChoiceModal, false, false).
AddPage("dl", dlModal, false, false)
infoModal.AddButtons([]string{"Ok"})
errorModal.AddButtons([]string{"Ok"})
yesNoModal.AddButtons([]string{"Yes", "No"})
panels.AddPanel("info", infoModal, false, false)
panels.AddPanel("error", errorModal, false, false)
panels.AddPanel("input", inputModal, false, false)
panels.AddPanel("yesno", yesNoModal, false, false)
// Color setup
if viper.GetBool("a-general.color") {
infoModal.SetBackgroundColor(config.GetColor("info_modal_bg")).
SetButtonBackgroundColor(config.GetColor("btn_bg")).
SetButtonTextColor(config.GetColor("btn_text")).
SetTextColor(config.GetColor("info_modal_text"))
infoModal.GetFrame().
SetBorderColor(config.GetColor("info_modal_text")).
SetTitleColor(config.GetColor("info_modal_text"))
m := infoModal
m.SetBackgroundColor(config.GetColor("info_modal_bg"))
m.SetButtonBackgroundColor(config.GetColor("btn_bg"))
m.SetButtonTextColor(config.GetColor("btn_text"))
m.SetTextColor(config.GetColor("info_modal_text"))
form := m.GetForm()
form.SetButtonBackgroundColorFocused(config.GetColor("btn_text"))
form.SetButtonTextColorFocused(config.GetColor("btn_bg"))
frame := m.GetFrame()
frame.SetBorderColor(config.GetColor("info_modal_text"))
frame.SetTitleColor(config.GetColor("info_modal_text"))
errorModal.SetBackgroundColor(config.GetColor("error_modal_bg")).
SetButtonBackgroundColor(config.GetColor("btn_bg")).
SetButtonTextColor(config.GetColor("btn_text")).
SetTextColor(config.GetColor("error_modal_text"))
errorModal.GetFrame().
SetBorderColor(config.GetColor("error_modal_text")).
SetTitleColor(config.GetColor("error_modal_text"))
m = errorModal
m.SetBackgroundColor(config.GetColor("error_modal_bg"))
m.SetButtonBackgroundColor(config.GetColor("btn_bg"))
m.SetButtonTextColor(config.GetColor("btn_text"))
m.SetTextColor(config.GetColor("error_modal_text"))
form = m.GetForm()
form.SetButtonBackgroundColorFocused(config.GetColor("btn_text"))
form.SetButtonTextColorFocused(config.GetColor("btn_bg"))
frame = errorModal.GetFrame()
frame.SetBorderColor(config.GetColor("error_modal_text"))
frame.SetTitleColor(config.GetColor("error_modal_text"))
inputModal.SetBackgroundColor(config.GetColor("input_modal_bg")).
SetButtonBackgroundColor(config.GetColor("btn_bg")).
SetButtonTextColor(config.GetColor("btn_text")).
SetTextColor(config.GetColor("input_modal_text"))
inputModal.GetFrame().
SetBorderColor(config.GetColor("input_modal_text")).
SetTitleColor(config.GetColor("input_modal_text"))
inputModal.GetForm().
SetFieldBackgroundColor(config.GetColor("input_modal_field_bg")).
SetFieldTextColor(config.GetColor("input_modal_field_text"))
m = inputModal
m.SetBackgroundColor(config.GetColor("input_modal_bg"))
m.SetButtonBackgroundColor(config.GetColor("btn_bg"))
m.SetButtonTextColor(config.GetColor("btn_text"))
m.SetTextColor(config.GetColor("input_modal_text"))
frame = inputModal.GetFrame()
frame.SetBorderColor(config.GetColor("input_modal_text"))
frame.SetTitleColor(config.GetColor("input_modal_text"))
form = inputModal.GetForm()
form.SetFieldBackgroundColor(config.GetColor("input_modal_field_bg"))
form.SetFieldTextColor(config.GetColor("input_modal_field_text"))
form.SetButtonBackgroundColorFocused(config.GetColor("btn_text"))
form.SetButtonTextColorFocused(config.GetColor("btn_bg"))
yesNoModal.SetButtonBackgroundColor(config.GetColor("btn_bg")).
SetButtonTextColor(config.GetColor("btn_text"))
m = yesNoModal
m.SetButtonBackgroundColor(config.GetColor("btn_bg"))
m.SetButtonTextColor(config.GetColor("btn_text"))
form = m.GetForm()
form.SetButtonBackgroundColorFocused(config.GetColor("btn_text"))
form.SetButtonTextColorFocused(config.GetColor("btn_bg"))
} else {
infoModal.SetBackgroundColor(tcell.ColorBlack).
SetButtonBackgroundColor(tcell.ColorWhite).
SetButtonTextColor(tcell.ColorBlack).
SetTextColor(tcell.ColorWhite)
infoModal.GetFrame().
SetBorderColor(tcell.ColorWhite).
SetTitleColor(tcell.ColorWhite)
m := infoModal
m.SetBackgroundColor(tcell.ColorBlack)
m.SetButtonBackgroundColor(tcell.ColorWhite)
m.SetButtonTextColor(tcell.ColorBlack)
m.SetTextColor(tcell.ColorWhite)
form := m.GetForm()
form.SetButtonBackgroundColorFocused(tcell.ColorBlack)
form.SetButtonTextColorFocused(tcell.ColorWhite)
frame := infoModal.GetFrame()
frame.SetBorderColor(tcell.ColorWhite)
frame.SetTitleColor(tcell.ColorWhite)
errorModal.SetBackgroundColor(tcell.ColorBlack).
SetButtonBackgroundColor(tcell.ColorWhite).
SetButtonTextColor(tcell.ColorBlack).
SetTextColor(tcell.ColorWhite)
errorModal.GetFrame().
SetBorderColor(tcell.ColorWhite).
SetTitleColor(tcell.ColorWhite)
m = errorModal
m.SetBackgroundColor(tcell.ColorBlack)
m.SetButtonBackgroundColor(tcell.ColorWhite)
m.SetButtonTextColor(tcell.ColorBlack)
m.SetTextColor(tcell.ColorWhite)
form = m.GetForm()
form.SetButtonBackgroundColorFocused(tcell.ColorBlack)
form.SetButtonTextColorFocused(tcell.ColorWhite)
frame = errorModal.GetFrame()
frame.SetBorderColor(tcell.ColorWhite)
frame.SetTitleColor(tcell.ColorWhite)
inputModal.SetBackgroundColor(tcell.ColorBlack).
SetButtonBackgroundColor(tcell.ColorWhite).
SetButtonTextColor(tcell.ColorBlack).
SetTextColor(tcell.ColorWhite)
inputModal.GetFrame().
SetBorderColor(tcell.ColorWhite).
SetTitleColor(tcell.ColorWhite)
inputModal.GetForm().
SetFieldBackgroundColor(tcell.ColorWhite).
SetFieldTextColor(tcell.ColorBlack)
m = inputModal
m.SetBackgroundColor(tcell.ColorBlack)
m.SetButtonBackgroundColor(tcell.ColorWhite)
m.SetButtonTextColor(tcell.ColorBlack)
m.SetTextColor(tcell.ColorWhite)
frame = inputModal.GetFrame()
frame.SetBorderColor(tcell.ColorWhite)
frame.SetTitleColor(tcell.ColorWhite)
form = inputModal.GetForm()
form.SetFieldBackgroundColor(tcell.ColorWhite)
form.SetFieldTextColor(tcell.ColorBlack)
form.SetButtonBackgroundColorFocused(tcell.ColorBlack)
form.SetButtonTextColorFocused(tcell.ColorWhite)
// YesNo background color is changed in funcs
yesNoModal.SetButtonBackgroundColor(tcell.ColorWhite).
SetButtonTextColor(tcell.ColorBlack)
m = yesNoModal
m.SetButtonBackgroundColor(tcell.ColorWhite)
m.SetButtonTextColor(tcell.ColorBlack)
form = m.GetForm()
form.SetButtonBackgroundColorFocused(tcell.ColorBlack)
form.SetButtonTextColorFocused(tcell.ColorWhite)
}
// Modal functions that can't be added up above, because they return the wrong type
infoModal.SetBorder(true)
infoModal.GetFrame().
SetTitleAlign(cview.AlignCenter).
SetTitle(" Info ")
frame := infoModal.GetFrame()
frame.SetTitleAlign(cview.AlignCenter)
frame.SetTitle(" Info ")
infoModal.SetDoneFunc(func(buttonIndex int, buttonLabel string) {
tabPages.SwitchToPage(strconv.Itoa(curTab))
panels.HidePanel("info")
App.SetFocus(tabs[curTab].view)
App.Draw()
})
@ -120,15 +149,15 @@ func modalInit() {
errorModal.SetBorder(true)
errorModal.GetFrame().SetTitleAlign(cview.AlignCenter)
errorModal.SetDoneFunc(func(buttonIndex int, buttonLabel string) {
tabPages.SwitchToPage(strconv.Itoa(curTab))
panels.HidePanel("error")
App.SetFocus(tabs[curTab].view)
App.Draw()
})
inputModal.SetBorder(true)
inputModal.GetFrame().
SetTitleAlign(cview.AlignCenter).
SetTitle(" Input ")
frame = inputModal.GetFrame()
frame.SetTitleAlign(cview.AlignCenter)
frame.SetTitle(" Input ")
inputModal.SetDoneFunc(func(buttonIndex int, buttonLabel string) {
if buttonLabel == "Send" {
inputCh <- inputModalText
@ -167,8 +196,8 @@ func Error(title, text string) {
errorModal.GetFrame().SetTitle(title)
errorModal.SetText(text)
tabPages.ShowPage("error")
tabPages.SendToFront("error")
panels.ShowPanel("error")
panels.SendToFront("error")
App.SetFocus(errorModal)
App.Draw()
}
@ -176,8 +205,8 @@ func Error(title, text string) {
// Info displays some info on the screen in a modal.
func Info(s string) {
infoModal.SetText(s)
tabPages.ShowPage("info")
tabPages.SendToFront("info")
panels.ShowPanel("info")
panels.SendToFront("info")
App.SetFocus(infoModal)
App.Draw()
}
@ -198,14 +227,14 @@ func Input(prompt string) (string, bool) {
})
inputModal.SetText(prompt + " ")
tabPages.ShowPage("input")
tabPages.SendToFront("input")
panels.ShowPanel("input")
panels.SendToFront("input")
App.SetFocus(inputModal)
App.Draw()
resp := <-inputCh
tabPages.SwitchToPage(strconv.Itoa(curTab))
panels.HidePanel("input")
App.SetFocus(tabs[curTab].view)
App.Draw()
@ -218,29 +247,29 @@ func Input(prompt string) (string, bool) {
// YesNo displays a modal asking a yes-or-no question.
func YesNo(prompt string) bool {
if viper.GetBool("a-general.color") {
yesNoModal.
SetBackgroundColor(config.GetColor("yesno_modal_bg")).
SetTextColor(config.GetColor("yesno_modal_text"))
yesNoModal.GetFrame().
SetBorderColor(config.GetColor("yesno_modal_text")).
SetTitleColor(config.GetColor("yesno_modal_text"))
m := yesNoModal
m.SetBackgroundColor(config.GetColor("yesno_modal_bg"))
m.SetTextColor(config.GetColor("yesno_modal_text"))
frame := yesNoModal.GetFrame()
frame.SetBorderColor(config.GetColor("yesno_modal_text"))
frame.SetTitleColor(config.GetColor("yesno_modal_text"))
} else {
yesNoModal.
SetBackgroundColor(tcell.ColorBlack).
SetTextColor(tcell.ColorWhite)
yesNoModal.GetFrame().
SetBorderColor(tcell.ColorWhite).
SetTitleColor(tcell.ColorWhite)
m := yesNoModal
m.SetBackgroundColor(tcell.ColorBlack)
m.SetTextColor(tcell.ColorWhite)
frame := yesNoModal.GetFrame()
frame.SetBorderColor(tcell.ColorWhite)
frame.SetTitleColor(tcell.ColorWhite)
}
yesNoModal.GetFrame().SetTitle("")
yesNoModal.SetText(prompt)
tabPages.ShowPage("yesno")
tabPages.SendToFront("yesno")
panels.ShowPanel("yesno")
panels.SendToFront("yesno")
App.SetFocus(yesNoModal)
App.Draw()
resp := <-yesNoCh
tabPages.SwitchToPage(strconv.Itoa(curTab))
panels.HidePanel("yesno")
App.SetFocus(tabs[curTab].view)
App.Draw()
return resp
@ -251,36 +280,34 @@ func YesNo(prompt string) bool {
func Tofu(host string, expiry time.Time) bool {
// Reuses yesNoModal, with error color
m := yesNoModal
frame := yesNoModal.GetFrame()
if viper.GetBool("a-general.color") {
yesNoModal.
SetBackgroundColor(config.GetColor("tofu_modal_bg")).
SetTextColor(config.GetColor("tofu_modal_text"))
yesNoModal.GetFrame().
SetBorderColor(config.GetColor("tofu_modal_text")).
SetTitleColor(config.GetColor("tofu_modal_text"))
m.SetBackgroundColor(config.GetColor("tofu_modal_bg"))
m.SetTextColor(config.GetColor("tofu_modal_text"))
frame.SetBorderColor(config.GetColor("tofu_modal_text"))
frame.SetTitleColor(config.GetColor("tofu_modal_text"))
} else {
yesNoModal.
SetBackgroundColor(tcell.ColorBlack).
SetTextColor(tcell.ColorWhite)
yesNoModal.
SetBorderColor(tcell.ColorWhite).
SetTitleColor(tcell.ColorWhite)
m.SetBackgroundColor(tcell.ColorBlack)
m.SetTextColor(tcell.ColorWhite)
m.SetBorderColor(tcell.ColorWhite)
m.SetTitleColor(tcell.ColorWhite)
}
yesNoModal.GetFrame().SetTitle(" TOFU ")
yesNoModal.SetText(
frame.SetTitle(" TOFU ")
m.SetText(
//nolint:lll
fmt.Sprintf("%s's certificate has changed, possibly indicating an security issue. The certificate would have expired %s. Are you sure you want to continue? ",
host,
humanize.Time(expiry),
),
)
tabPages.ShowPage("yesno")
tabPages.SendToFront("yesno")
panels.ShowPanel("yesno")
panels.SendToFront("yesno")
App.SetFocus(yesNoModal)
App.Draw()
resp := <-yesNoCh
tabPages.SwitchToPage(strconv.Itoa(curTab))
panels.HidePanel("yesno")
App.SetFocus(tabs[curTab].view)
App.Draw()
return resp

View File

@ -1,15 +1,12 @@
package display
import (
"fmt"
"net/url"
"strconv"
"strings"
"github.com/makeworld-the-better-one/amfora/config"
"github.com/makeworld-the-better-one/amfora/renderer"
"github.com/makeworld-the-better-one/amfora/structs"
"github.com/spf13/viper"
)
// This file contains the functions that aren't part of the public API.
@ -68,11 +65,11 @@ func reformatPage(p *structs.Page) {
strings.HasPrefix(p.URL, "file") {
proxied = false
}
rendered, _ = renderer.RenderGemini(p.Raw, textWidth(), leftMargin(), proxied)
rendered, _ = renderer.RenderGemini(p.Raw, textWidth(), proxied)
case structs.TextPlain:
rendered = renderer.RenderPlainText(p.Raw, leftMargin())
rendered = renderer.RenderPlainText(p.Raw)
case structs.TextAnsi:
rendered = renderer.RenderANSI(p.Raw, leftMargin())
rendered = renderer.RenderANSI(p.Raw)
default:
// Rendering this type is not implemented
return
@ -92,6 +89,8 @@ func reformatPageAndSetView(t *tab, p *structs.Page) {
reformatPage(p)
t.view.SetText(p.Content)
t.applyScroll() // Go back to where you were, roughly
App.Draw()
}
// setPage displays a Page on the passed tab number.
@ -107,20 +106,23 @@ func setPage(t *tab, p *structs.Page) {
// Make sure the page content is fitted to the terminal every time it's displayed
reformatPage(p)
oldFav := t.page.Favicon
t.page = p
go func() {
parsed, _ := url.Parse(p.URL)
handleFavicon(t, parsed.Host, oldFav)
}()
// Change page on screen
t.view.SetText(p.Content)
t.view.Highlight("") // Turn off highlights, other funcs may restore if necessary
t.view.ScrollToBeginning()
// Set tab number in case a favicon from before overwrote it
tabNum := tabNumber(t)
browser.SetTabLabel(strconv.Itoa(tabNum), makeTabLabel(strconv.Itoa(tabNum+1)))
App.Draw()
go func() {
parsed, _ := url.Parse(p.URL)
handleFavicon(t, parsed.Host)
}()
// Setup display
App.SetFocus(t.view)
@ -144,32 +146,3 @@ func goURL(t *tab, u string) {
t.applyBottomBar()
}
}
// rewriteTabRow clears the tabRow and writes all the tabs number/favicons into it.
func rewriteTabRow() {
tabRow.Clear()
if viper.GetBool("a-general.color") {
for i := 0; i < NumTabs(); i++ {
char := strconv.Itoa(i + 1)
if tabs[i].page.Favicon != "" {
char = tabs[i].page.Favicon
}
fmt.Fprintf(tabRow, `["%d"][%s] %s [%s][""]|`,
i,
config.GetColorString("tab_num"),
char,
config.GetColorString("tab_divider"),
)
}
} else {
for i := 0; i < NumTabs(); i++ {
char := strconv.Itoa(i + 1)
if tabs[i].page.Favicon != "" {
char = tabs[i].page.Favicon
}
fmt.Fprintf(tabRow, `["%d"] %s [""]|`, i, char)
}
}
tabRow.Highlight(strconv.Itoa(curTab)).ScrollToHighlight()
App.Draw()
}

View File

@ -9,7 +9,7 @@ import (
"strings"
"time"
"github.com/gdamore/tcell"
"github.com/gdamore/tcell/v2"
"github.com/makeworld-the-better-one/amfora/cache"
"github.com/makeworld-the-better-one/amfora/config"
"github.com/makeworld-the-better-one/amfora/renderer"
@ -149,7 +149,7 @@ func Subscriptions(t *tab, u string) string {
}
}
content, links := renderer.RenderGemini(rawPage, textWidth(), leftMargin(), false)
content, links := renderer.RenderGemini(rawPage, textWidth(), false)
page := structs.Page{
Raw: rawPage,
Content: content,
@ -191,7 +191,7 @@ func ManageSubscriptions(t *tab, u string) {
)
}
content, links := renderer.RenderGemini(rawPage, textWidth(), leftMargin(), false)
content, links := renderer.RenderGemini(rawPage, textWidth(), false)
page := structs.Page{
Raw: rawPage,
Content: content,
@ -230,19 +230,19 @@ func openSubscriptionModal(validFeed, subscribed bool) bool {
// Reuses yesNoModal
if viper.GetBool("a-general.color") {
yesNoModal.
SetBackgroundColor(config.GetColor("subscription_modal_bg")).
SetTextColor(config.GetColor("subscription_modal_text"))
yesNoModal.GetFrame().
SetBorderColor(config.GetColor("subscription_modal_text")).
SetTitleColor(config.GetColor("subscription_modal_text"))
m := yesNoModal
m.SetBackgroundColor(config.GetColor("subscription_modal_bg"))
m.SetTextColor(config.GetColor("subscription_modal_text"))
frame := yesNoModal.GetFrame()
frame.SetBorderColor(config.GetColor("subscription_modal_text"))
frame.SetTitleColor(config.GetColor("subscription_modal_text"))
} else {
yesNoModal.
SetBackgroundColor(tcell.ColorBlack).
SetTextColor(tcell.ColorWhite)
yesNoModal.GetFrame().
SetBorderColor(tcell.ColorWhite).
SetTitleColor(tcell.ColorWhite)
m := yesNoModal
m.SetBackgroundColor(tcell.ColorBlack)
m.SetTextColor(tcell.ColorWhite)
frame := yesNoModal.GetFrame()
frame.SetBorderColor(tcell.ColorWhite)
frame.SetTitleColor(tcell.ColorWhite)
}
if validFeed {
yesNoModal.GetFrame().SetTitle("Feed Subscription")
@ -260,13 +260,13 @@ func openSubscriptionModal(validFeed, subscribed bool) bool {
}
}
tabPages.ShowPage("yesno")
tabPages.SendToFront("yesno")
panels.ShowPanel("yesno")
panels.SendToFront("yesno")
App.SetFocus(yesNoModal)
App.Draw()
resp := <-yesNoCh
tabPages.SwitchToPage(strconv.Itoa(curTab))
panels.HidePanel("yesno")
App.SetFocus(tabs[curTab].view)
App.Draw()
return resp

View File

@ -3,9 +3,9 @@ package display
import (
"strconv"
"strings"
"sync"
"github.com/gdamore/tcell"
"github.com/gdamore/tcell/v2"
"github.com/makeworld-the-better-one/amfora/config"
"github.com/makeworld-the-better-one/amfora/structs"
"gitlab.com/tslocum/cview"
)
@ -24,33 +24,34 @@ type tabHistory struct {
// tab hold the information needed for each browser tab.
type tab struct {
page *structs.Page
view *cview.TextView
history *tabHistory
mode tabMode
reformatMu *sync.Mutex // Mutex for reformatting, so there's only one reformat job at once
barLabel string // The bottomBar label for the tab
barText string // The bottomBar text for the tab
page *structs.Page
view *cview.TextView
history *tabHistory
mode tabMode
barLabel string // The bottomBar label for the tab
barText string // The bottomBar text for the tab
}
// makeNewTab initializes an tab struct with no content.
func makeNewTab() *tab {
t := tab{
page: &structs.Page{Mode: structs.ModeOff},
view: cview.NewTextView().
SetDynamicColors(true).
SetRegions(true).
SetScrollable(true).
SetWrap(false).
SetChangedFunc(func() {
App.Draw()
}),
history: &tabHistory{},
reformatMu: &sync.Mutex{},
mode: tabModeDone,
page: &structs.Page{Mode: structs.ModeOff},
view: cview.NewTextView(),
history: &tabHistory{},
mode: tabModeDone,
}
t.view.SetDynamicColors(true)
t.view.SetRegions(true)
t.view.SetScrollable(true)
t.view.SetWrap(false)
t.view.SetScrollBarVisibility(config.ScrollBar)
t.view.SetScrollBarColor(config.GetColor("scrollbar"))
t.view.SetChangedFunc(func() {
App.Draw()
})
t.view.SetDoneFunc(func(key tcell.Key) {
// Altered from: https://gitlab.com/tslocum/cview/-/blob/master/demos/textview/main.go
// Altered from:
// https://gitlab.com/tslocum/cview/-/blob/1f765c8695c3f4b35dae57f469d3aee0b1adbde7/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
@ -89,7 +90,8 @@ func makeNewTab() *tab {
// They've started link highlighting
tabs[tab].page.Mode = structs.ModeLinkSelect
tabs[tab].view.Highlight("0").ScrollToHighlight()
tabs[tab].view.Highlight("0")
tabs[tab].view.ScrollToHighlight()
// Display link URL in bottomBar
bottomBar.SetLabel("[::b]Link: [::-]")
bottomBar.SetText(tabs[tab].page.Links[0])
@ -109,7 +111,8 @@ func makeNewTab() *tab {
} else {
return
}
tabs[tab].view.Highlight(strconv.Itoa(index)).ScrollToHighlight()
tabs[tab].view.Highlight(strconv.Itoa(index))
tabs[tab].view.ScrollToHighlight()
// Display link URL in bottomBar
bottomBar.SetLabel("[::b]Link: [::-]")
bottomBar.SetText(tabs[tab].page.Links[index])

View File

@ -13,6 +13,43 @@ import (
// This file contains funcs that are small, self-contained utilities.
// makeContentLayout returns a flex that contains the given TextView
// along with the current correct left margin, as well as a single empty
// line at the top, for a top margin.
func makeContentLayout(tv *cview.TextView) *cview.Flex {
// Create horizontal flex with the left margin as an empty space
horiz := cview.NewFlex()
horiz.SetDirection(cview.FlexColumn)
horiz.AddItem(nil, leftMargin(), 0, false)
horiz.AddItem(tv, 0, 1, true)
// Create a vertical flex with the other one and a top margin
vert := cview.NewFlex()
vert.SetDirection(cview.FlexRow)
vert.AddItem(nil, 1, 0, false)
vert.AddItem(horiz, 0, 1, true)
return vert
}
// makeTabLabel takes a string and adds spacing to it, making it
// suitable for display as a tab label.
func makeTabLabel(s string) string {
return " " + s + " "
}
// tabNumber gets the index of the tab in the tabs slice. It returns -1
// if the tab is not in that slice.
func tabNumber(t *tab) int {
tempTabs := tabs
for i := range tempTabs {
if tempTabs[i] == t {
return i
}
}
return -1
}
// escapeMeta santizes a META string for use within a cview modal.
func escapeMeta(meta string) string {
return cview.Escape(strings.ReplaceAll(meta, "\n", ""))
@ -20,13 +57,7 @@ func escapeMeta(meta string) string {
// isValidTab indicates whether the passed tab is still being used, even if it's not currently displayed.
func isValidTab(t *tab) bool {
tempTabs := tabs
for i := range tempTabs {
if tempTabs[i] == t {
return true
}
}
return false
return tabNumber(t) != -1
}
func leftMargin() int {

10
go.mod
View File

@ -5,7 +5,7 @@ go 1.14
require (
github.com/dustin/go-humanize v1.0.0
github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/gdamore/tcell v1.3.1-0.20200608133353-cb1e5d6fa606
github.com/gdamore/tcell/v2 v2.1.1-0.20210125004847-19e17097d8fe
github.com/google/go-cmp v0.5.0 // indirect
github.com/makeworld-the-better-one/go-gemini v0.11.0
github.com/makeworld-the-better-one/go-isemoji v1.1.0
@ -19,12 +19,12 @@ require (
github.com/spf13/cast v1.3.1 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.7.0
github.com/spf13/viper v1.7.1
github.com/stretchr/testify v1.6.1
gitlab.com/tslocum/cview v1.4.8-0.20200713214710-cc7796c4ca44
golang.org/x/text v0.3.5-0.20201208001344-75a595aef632
gitlab.com/tslocum/cview v1.5.4-0.20210207045010-d776e728ef6d
golang.org/x/text v0.3.5
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
gopkg.in/ini.v1 v1.57.0 // indirect
gopkg.in/ini.v1 v1.62.0 // indirect
)
replace github.com/mmcdole/gofeed => github.com/makeworld-the-better-one/gofeed v1.1.1-0.20201123002655-c0c6354134fe

45
go.sum
View File

@ -14,7 +14,7 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/PuerkitoBio/goquery v1.5.1 h1:PSPBGne8NIUWw+/7vFBV+kG2J/5MOjbzc7154OaKCSE=
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
@ -50,9 +50,9 @@ github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWo
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/gdamore/tcell v1.3.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebKS4zMM=
github.com/gdamore/tcell v1.3.1-0.20200608133353-cb1e5d6fa606 h1:Y00kKKKYVyn7InlCMRcnZbwcjHFIsgkjU0Bn1F5re4o=
github.com/gdamore/tcell v1.3.1-0.20200608133353-cb1e5d6fa606/go.mod h1:vxEiSDZdW3L+Uhjii9c3375IlDmR05bzxY404ZVSMo0=
github.com/gdamore/tcell/v2 v2.0.0-dev/go.mod h1:vSVL/GV5mCSlPC6thFP5kfOFdM9MGZcalipmpTxTgQA=
github.com/gdamore/tcell/v2 v2.1.1-0.20210125004847-19e17097d8fe h1:D4zQq/0ep0XCtgkmA+dUvQNYMoiW2+2336rdlAucr10=
github.com/gdamore/tcell/v2 v2.1.1-0.20210125004847-19e17097d8fe/go.mod h1:cTTuF84Dlj/RqmaCIV5p4w8uG1zWdk0SF6oBpwHp4fU=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
@ -128,9 +128,10 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s=
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/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/makeworld-the-better-one/go-gemini v0.11.0 h1:MNGiULJFvcqls9oCy40tE897hDeKvNmEK9i5kRucgQk=
@ -144,11 +145,11 @@ github.com/makeworld-the-better-one/progressbar/v3 v3.3.5-0.20201220005701-b036c
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
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/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
@ -192,8 +193,9 @@ github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY=
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/rkoesters/xdg v0.0.0-20181125232953-edd15b846f9b h1:8NiY6v9/IlFU8osj1L7kqzRbrG6e3izRQQjGze1Q1R0=
github.com/rkoesters/xdg v0.0.0-20181125232953-edd15b846f9b/go.mod h1:T1HolqzmdHnJIH6p7A9LDuvYGQgEHx9ijX3vKgDKU60=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
@ -221,8 +223,8 @@ github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM=
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk=
github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@ -235,10 +237,10 @@ github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/urfave/cli v1.22.3/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
gitlab.com/tslocum/cbind v0.1.1 h1:JXXtxMWHgWLvoF+QkrvcNvOQ59juy7OE1RhT7hZfdt0=
gitlab.com/tslocum/cbind v0.1.1/go.mod h1:rX7vkl0pUSg/yy427MmD1FZAf99S7WwpUlxF/qTpPqk=
gitlab.com/tslocum/cview v1.4.8-0.20200713214710-cc7796c4ca44 h1:YddMqXJ6jI3SkP8Nfxc+S2pcvI5o8mmXmHL2D9hkwQI=
gitlab.com/tslocum/cview v1.4.8-0.20200713214710-cc7796c4ca44/go.mod h1:QctoEJaR2AqZTy0KKo12P1ZjHgQJyVkAXaeDanBBhlE=
gitlab.com/tslocum/cbind v0.1.4 h1:cbZXPPcieXspk8cShoT6efz7HAT8yMNQcofYWNizis4=
gitlab.com/tslocum/cbind v0.1.4/go.mod h1:RvwYE3auSjBNlCmWeGspzn+jdLUVQ8C2QGC+0nP9ChI=
gitlab.com/tslocum/cview v1.5.4-0.20210207045010-d776e728ef6d h1:ck3gAnCoraAI2doDfH2MZsz+DxVpvNwnaXa453jH5aI=
gitlab.com/tslocum/cview v1.5.4-0.20210207045010-d776e728ef6d/go.mod h1:lCEqP/zDhBihNbyiEn59LgOCk09ejefHaS7kNZ57Nmc=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
@ -315,19 +317,22 @@ golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200610111108-226ff32320da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201013132646-2da7054afaeb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201113135734-0a15ea8d9b02/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf h1:MZ2shdL+ZM/XzY3ZGOnh4Nlpnxz5GSOhOmtHo3iPU6M=
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5-0.20201208001344-75a595aef632 h1:clKlpQ6BheG1zIRhU2SPRAXpLgol/tqWVEeRkjpsaDI=
golang.org/x/text v0.3.5-0.20201208001344-75a595aef632/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@ -378,8 +383,8 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogR
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.57.0 h1:9unxIsFcTt4I55uWluz+UmL95q4kdJ0buvQ1ZIqVQww=
gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@ -69,7 +69,7 @@ func CanDisplay(res *gemini.Response) bool {
// MakePage creates a formatted, rendered Page from the given network response and params.
// You must set the Page.Width value yourself.
func MakePage(url string, res *gemini.Response, width, leftMargin int, proxied bool) (*structs.Page, error) {
func MakePage(url string, res *gemini.Response, width int, proxied bool) (*structs.Page, error) {
if !CanDisplay(res) {
return nil, ErrCantDisplay
}
@ -112,7 +112,7 @@ func MakePage(url string, res *gemini.Response, width, leftMargin int, proxied b
}
if mediatype == "text/gemini" {
rendered, links := RenderGemini(utfText, width, leftMargin, proxied)
rendered, links := RenderGemini(utfText, width, proxied)
return &structs.Page{
Mediatype: structs.TextGemini,
RawMediatype: mediatype,
@ -130,7 +130,7 @@ func MakePage(url string, res *gemini.Response, width, leftMargin int, proxied b
RawMediatype: mediatype,
URL: url,
Raw: utfText,
Content: RenderANSI(utfText, leftMargin),
Content: RenderANSI(utfText),
Links: []string{},
MadeAt: time.Now(),
}, nil
@ -142,7 +142,7 @@ func MakePage(url string, res *gemini.Response, width, leftMargin int, proxied b
RawMediatype: mediatype,
URL: url,
Raw: utfText,
Content: RenderPlainText(utfText, leftMargin),
Content: RenderPlainText(utfText),
Links: []string{},
MadeAt: time.Now(),
}, nil

View File

@ -21,31 +21,27 @@ var ansiRegex = regexp.MustCompile(`\x1b\[[0-9;]*m`)
// RenderANSI renders plain text pages containing ANSI codes.
// Practically, it is used for the text/x-ansi.
func RenderANSI(s string, leftMargin int) string {
func RenderANSI(s string) string {
s = cview.Escape(s)
if viper.GetBool("a-general.color") && viper.GetBool("a-general.ansi") {
s = cview.TranslateANSI(s)
// The TranslateANSI function injects tags like [-:-:-]
// but this will reset the background to use the user's terminal color.
// These tags need to be replaced with resets that use the theme color.
s = strings.ReplaceAll(s, "[-:-:-]",
fmt.Sprintf("[-:%s:-]", config.GetColorString("bg")))
} else {
s = ansiRegex.ReplaceAllString(s, "")
}
var shifted string
lines := strings.Split(s, "\n")
for i := range lines {
shifted += strings.Repeat(" ", leftMargin) + lines[i] + "\n"
}
return shifted
return s
}
// RenderPlainText should be used to format plain text pages.
func RenderPlainText(s string, leftMargin int) string {
var shifted string
lines := strings.Split(cview.Escape(s), "\n")
for i := range lines {
shifted += strings.Repeat(" ", leftMargin) +
"[" + config.GetColorString("regular_text") + "]" + lines[i] + "[-]" +
"\n"
}
return shifted
func RenderPlainText(s string) string {
// It used to add a left margin, now this is done elsewhere.
// The function is kept for convenience and in case rendering
// is needed in the future.
return s
}
// wrapLine wraps a line to the provided width, and adds the provided prefix and suffix to each wrapped line.
@ -278,7 +274,7 @@ func convertRegularGemini(s string, numLinks, width int, proxied bool) (string,
//
// proxied is whether the request is through the gemini:// scheme.
// If it's not a gemini:// page, set this to true.
func RenderGemini(s string, width, leftMargin int, proxied bool) (string, []string) {
func RenderGemini(s string, width int, proxied bool) (string, []string) {
s = cview.Escape(s)
lines := strings.Split(s, "\n")
@ -289,30 +285,52 @@ func RenderGemini(s string, width, leftMargin int, proxied bool) (string, []stri
rendered := "" // Final result
pre := false
buf := "" // Block of regular or preformatted lines
// processPre is for rendering preformatted blocks
processPre := func() {
// Support ANSI color codes in preformatted blocks - see #59
if viper.GetBool("a-general.color") && viper.GetBool("a-general.ansi") {
buf = cview.TranslateANSI(buf)
// The TranslateANSI function injects tags like [-:-:-]
// but this will reset the background to use the user's terminal color.
// These tags need to be replaced with resets that use the theme color.
buf = strings.ReplaceAll(buf, "[-:-:-]",
fmt.Sprintf("[%s:%s:-]", config.GetColorString("preformatted_text"), config.GetColorString("bg")))
} else {
buf = ansiRegex.ReplaceAllString(buf, "")
}
// The final newline is removed (and re-added) to prevent background glitches
// where the terminal background color slips through. This only happens on
// preformatted blocks with ANSI characters.
//
// Lines are modified below to always end with \r\n
buf = strings.TrimSuffix(buf, "\r\n")
rendered += fmt.Sprintf("[%s]", config.GetColorString("preformatted_text")) +
buf + fmt.Sprintf("[%s:%s:-]\r\n", config.GetColorString("regular_text"), config.GetColorString("bg"))
}
// processRegular processes non-preformatted sections
processRegular := func() {
// ANSI not allowed in regular text - see #59
buf = ansiRegex.ReplaceAllString(buf, "")
ren, lks := convertRegularGemini(buf, len(links), width, proxied)
links = append(links, lks...)
rendered += ren
}
for i := range lines {
if strings.HasPrefix(lines[i], "```") {
if pre {
// In a preformatted block, so add the text as is
// Don't add the current line with backticks
processPre()
// Support ANSI color codes in preformatted blocks - see #59
if viper.GetBool("a-general.color") && viper.GetBool("a-general.ansi") {
buf = cview.TranslateANSI(buf)
} else {
buf = ansiRegex.ReplaceAllString(buf, "")
}
rendered += fmt.Sprintf("[%s]", config.GetColorString("preformatted_text")) +
buf + "[-]"
} else {
// Not preformatted, regular text
// ANSI not allowed in regular text - see #59
buf = ansiRegex.ReplaceAllString(buf, "")
ren, lks := convertRegularGemini(buf, len(links), width, proxied)
links = append(links, lks...)
rendered += ren
processRegular()
}
buf = "" // Clear buffer for next block
pre = !pre
@ -324,32 +342,10 @@ func RenderGemini(s string, width, leftMargin int, proxied bool) (string, []stri
// Gone through all the lines, but there still is likely a block in the buffer
if pre {
// File ended without closing the preformatted block
// Same code as in the loop above
if viper.GetBool("a-general.color") && viper.GetBool("a-general.ansi") {
buf = cview.TranslateANSI(buf)
} else {
buf = ansiRegex.ReplaceAllString(buf, "")
}
rendered += fmt.Sprintf("[%s]", config.GetColorString("preformatted_text")) +
buf + "[-]"
processPre()
} else {
// Not preformatted, regular text
// Same code as in the loop above
buf = ansiRegex.ReplaceAllString(buf, "")
ren, lks := convertRegularGemini(buf, len(links), width, proxied)
links = append(links, lks...)
rendered += ren
}
if leftMargin > 0 {
renLines := strings.Split(rendered, "\n")
for i := range renLines {
renLines[i] = strings.Repeat(" ", leftMargin) + renLines[i]
}
return strings.Join(renLines, "\n"), links
processRegular()
}
return rendered, links

View File

@ -374,9 +374,9 @@ func updateAll() {
defer wg.Done()
for j := range jobs {
if j[0] == "feed" {
updateFeed(j[1]) //nolint:errcheck
updateFeed(j[1])
} else if j[0] == "page" {
updatePage(j[1]) //nolint:errcheck
updatePage(j[1])
}
}
}