From 3aff20f43bcf1d15d67b3be82a49914393792e9f Mon Sep 17 00:00:00 2001 From: makeworld Date: Thu, 2 Jul 2020 23:55:24 -0400 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=A7=20Dynamic=20wrapping=20#33?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is the majority of the work for this issue completed. --- CHANGELOG.md | 4 ++++ amfora.go | 2 +- cache/cache_test.go | 10 --------- display/bookmarks.go | 10 +++++++-- display/display.go | 25 +++++++++------------ display/private.go | 49 +++++++++++++++++++++-------------------- renderer/page.go | 18 ++++++++++++--- renderer/renderer.go | 15 +++++++++++-- structs/structs.go | 15 +++++++------ structs/structs_test.go | 3 ++- 10 files changed, 87 insertions(+), 64 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fc86e1b..f24bfd3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] +### Changed +- Pages are rewrapped dynamically, whenever the terminal size changes (#33) + ## [1.2.0] - 2020-07-02 ### Added - Alt-Left and Alt-Right for history navigation (#23) diff --git a/amfora.go b/amfora.go index c23d7e3..b5937d5 100644 --- a/amfora.go +++ b/amfora.go @@ -8,7 +8,7 @@ import ( "github.com/makeworld-the-better-one/amfora/display" ) -var version = "1.2.0" +var version = "1.3.0-unreleased" func main() { // err := logger.Init() diff --git a/cache/cache_test.go b/cache/cache_test.go index c0542c1..734ffcd 100644 --- a/cache/cache_test.go +++ b/cache/cache_test.go @@ -70,13 +70,3 @@ func TestGet(t *testing.T) { t.Error("page urls don't match") } } - -func TestQueryString(t *testing.T) { - // Pages with URLs with query strings don't get added - reset() - Add(&queryPage) - _, ok := Get(queryPage.Url) - if ok { - t.Fatal("Get should not find the page, because it had query string") - } -} diff --git a/display/bookmarks.go b/display/bookmarks.go index e371d68..106d864 100644 --- a/display/bookmarks.go +++ b/display/bookmarks.go @@ -104,8 +104,14 @@ func Bookmarks() { rawContent += fmt.Sprintf("=> %s %s\r\n", keys[i], m[keys[i]]) } // Render and display - content, links := renderer.RenderGemini(rawContent, textWidth()) - page := structs.Page{Content: content, Links: links, Url: "about:bookmarks"} + content, links := renderer.RenderGemini(rawContent, textWidth(), leftMargin()) + page := structs.Page{ + Raw: rawContent, + Content: content, + Links: links, + Url: "about:bookmarks", + Width: termW, + } setPage(&page) } diff --git a/display/display.go b/display/display.go index 2b728d5..3bba092 100644 --- a/display/display.go +++ b/display/display.go @@ -81,16 +81,8 @@ var App = cview.NewApplication(). termW = width termH = height - // Shift new tabs created before app startup, when termW == 0 - // XXX: This is hacky but works. The biggest issue is that there will sometimes be a tiny flash - // of the old not shifted tab on startup. - if tabMap[curTab] == &newTabPage { - renderedNewTabContent, _ = renderer.RenderGemini(newTabContent, textWidth()) - newTabPage.Content = renderedNewTabContent - newTabPage.LeftMargin = 0 - setLeftMargin(tabMap[curTab]) - tabViews[curTab].SetText(tabMap[curTab].Content) - } + // Make sure the current tab content is reformatted when the terminal size changes + reformatAndDisplayPage(tabMap[curTab]) }) func Init() { @@ -201,8 +193,14 @@ func Init() { }) // Render the default new tab content ONCE and store it for later - renderedNewTabContent, newTabLinks = renderer.RenderGemini(newTabContent, textWidth()) - newTabPage = structs.Page{Content: renderedNewTabContent, Links: newTabLinks, Url: "about:newtab"} + renderedNewTabContent, newTabLinks = renderer.RenderGemini(newTabContent, textWidth(), leftMargin()) + newTabPage = structs.Page{ + Raw: newTabContent, + Content: renderedNewTabContent, + Links: newTabLinks, + Url: "about:newtab", + Width: termW, + } modalInit() @@ -361,7 +359,6 @@ func NewTab() { curTab = NumTabs() tabMap[curTab] = &newTabPage - setLeftMargin(tabMap[curTab]) tabViews[curTab] = cview.NewTextView(). SetDynamicColors(true). SetRegions(true). @@ -508,6 +505,7 @@ func SwitchTab(tab int) { } curTab = tab % NumTabs() + reformatAndDisplayPage(tabMap[curTab]) tabPages.SwitchToPage(strconv.Itoa(curTab)) tabRow.Highlight(strconv.Itoa(curTab)).ScrollToHighlight() @@ -524,7 +522,6 @@ func Reload() { } cache.Remove(tabMap[curTab].Url) - tabMap[curTab].LeftMargin = 0 // Redo left margin go handleURL(tabMap[curTab].Url) } diff --git a/display/private.go b/display/private.go index d6c92af..849bd6b 100644 --- a/display/private.go +++ b/display/private.go @@ -159,36 +159,36 @@ func followLink(prev, next string) { }() } -func setLeftMargin(p *structs.Page) { - lM := leftMargin() - if lM != p.LeftMargin { - // Left margin needs to be added or changed - - var shifted string - - if p.LeftMargin < 1 { - // The page content doesn't have a margin yet - lines := strings.Split(p.Content, "\n") - for i := range lines { - shifted += strings.Repeat(" ", lM) + lines[i] + "\n" - } - } else { - // 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" - } - } - p.Content = shifted - p.LeftMargin = lM +// reformatPage will take the raw page content and reformat it according to the current terminal dimensions. +// It should be called when the terminal size changes. +// It will not waste resources if the passed page is already fitted to the current terminal width, and can be +// called safely even when the page might be already formatted properly. +func reformatPage(p *structs.Page) { + if p.Width == termW { + // No changes to make + return } + // Links are not recorded because they won't change + rendered, _ := renderer.RenderGemini(p.Raw, textWidth(), leftMargin()) + p.Content = rendered + p.Width = termW +} + +// reformatAndDisplayPage is for reformatting a page that is already being displayed. +// setPage should be used when a page is being loaded for the first time. +func reformatAndDisplayPage(p *structs.Page) { + saveScroll() + reformatPage(tabMap[curTab]) + tabViews[curTab].SetText(tabMap[curTab].Content) + applyScroll() // Go back to where you were, roughly } // setPage displays a Page on the current tab. func setPage(p *structs.Page) { saveScroll() // Save the scroll of the previous page - setLeftMargin(p) + // Make sure the page content is fitted to the terminal every time it's displayed + reformatPage(p) // Change page on screen tabMap[curTab] = p @@ -289,7 +289,8 @@ func handleURL(u string) (string, bool) { return "", false } if renderer.CanDisplay(res) { - page, err := renderer.MakePage(u, res, textWidth()) + page, err := renderer.MakePage(u, res, textWidth(), leftMargin()) + page.Width = termW if err != nil { Error("Page Error", "Issuing creating page: "+err.Error()) // Set the bar back to original URL diff --git a/renderer/page.go b/renderer/page.go index 7dee59a..3df7805 100644 --- a/renderer/page.go +++ b/renderer/page.go @@ -46,7 +46,9 @@ func CanDisplay(res *gemini.Response) bool { return err == nil && enc != nil } -func MakePage(url string, res *gemini.Response, width int) (*structs.Page, error) { +// 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) (*structs.Page, error) { if !CanDisplay(res) { return nil, errors.New("not valid content for a Page") } @@ -76,17 +78,27 @@ func MakePage(url string, res *gemini.Response, width int) (*structs.Page, error } if mediatype == "text/gemini" { - rendered, links := RenderGemini(utfText, width) + rendered, links := RenderGemini(utfText, width, leftMargin) return &structs.Page{ Url: url, + Raw: utfText, Content: rendered, Links: links, }, nil } else if strings.HasPrefix(mediatype, "text/") { // Treated as plaintext + + // Add left margin + var shifted string + lines := strings.Split(utfText, "\n") + for i := range lines { + shifted += strings.Repeat(" ", leftMargin) + lines[i] + "\n" + } + return &structs.Page{ Url: url, - Content: utfText, + Raw: utfText, + Content: shifted, Links: []string{}, }, nil } diff --git a/renderer/renderer.go b/renderer/renderer.go index 88cd128..15bccb7 100644 --- a/renderer/renderer.go +++ b/renderer/renderer.go @@ -21,7 +21,7 @@ import ( // // Since this only works on non-preformatted blocks, RenderGemini // should always be used instead. -func convertRegularGemini(s string, numLinks int, width int) (string, []string) { +func convertRegularGemini(s string, numLinks, width int) (string, []string) { links := make([]string, 0) lines := strings.Split(s, "\n") wrappedLines := make([]string, 0) // Final result @@ -162,7 +162,10 @@ func convertRegularGemini(s string, numLinks int, width int) (string, []string) // RenderGemini converts text/gemini into a cview displayable format. // It also returns a slice of link URLs. -func RenderGemini(s string, width int) (string, []string) { +// +// width is the number of columns to wrap to. +// leftMargin is the number of blank spaces to prepend to each line. +func RenderGemini(s string, width, leftMargin int) (string, []string) { s = cview.Escape(s) if viper.GetBool("a-general.color") { s = cview.TranslateANSI(s) @@ -206,5 +209,13 @@ func RenderGemini(s string, width int) (string, []string) { 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 + } + return rendered, links } diff --git a/structs/structs.go b/structs/structs.go index 6de5872..bbda661 100644 --- a/structs/structs.go +++ b/structs/structs.go @@ -2,17 +2,18 @@ package structs // Page is for storing UTF-8 text/gemini pages, as well as text/plain pages. type Page struct { - Url string - Content string // The processed content, NOT raw. Uses cview colour tags. All link/link texts must have region tags. - Links []string // URLs, for each region in the content. - Row int // Scroll position - Column int // ditto - LeftMargin int // <1 when the content is unmodified. Otherwise it indicates how many spaces have been prepended to each line + Url string + Raw string // The raw response, as received over the network + Content string // The processed content, NOT raw. Uses cview colour tags. All link/link texts must have region tags. It will also have a left margin. + Links []string // URLs, for each region in the content. + Row int // Scroll position + Column int // ditto + Width int // The width of the terminal at the time when the Content was set. This is to know when reformatting should happen. } // Size returns an approx. size of a Page in bytes. func (p *Page) Size() int { - b := len(p.Content) + len(p.Url) + b := len(p.Raw) + len(p.Content) + len(p.Url) for i := range p.Links { b += len(p.Links[i]) } diff --git a/structs/structs_test.go b/structs/structs_test.go index 3290a56..fe18f40 100644 --- a/structs/structs_test.go +++ b/structs/structs_test.go @@ -9,8 +9,9 @@ import ( func TestSize(t *testing.T) { p := Page{ Url: "12345", + Raw: "12345", Content: "12345", Links: []string{"1", "2", "3", "4", "5"}, } - assert.Equal(t, 15, p.Size(), "sizes should be equal") + assert.Equal(t, 20, p.Size(), "sizes should be equal") }