mirror of
https://github.com/makeworld-the-better-one/amfora.git
synced 2024-09-11 06:45:30 +03:00
🚧 Dynamic wrapping #33
This is the majority of the work for this issue completed.
This commit is contained in:
parent
4d6656adf8
commit
3aff20f43b
@ -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)
|
||||
|
@ -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()
|
||||
|
10
cache/cache_test.go
vendored
10
cache/cache_test.go
vendored
@ -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")
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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])
|
||||
}
|
||||
|
@ -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")
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user