2020-06-18 16:54:48 -04:00
|
|
|
// Package renderer provides functions to convert various data into a cview primitive.
|
|
|
|
// Example objects include a Gemini response, and an error.
|
|
|
|
//
|
|
|
|
// Rendered lines always end with \r\n, in an effort to be Window compatible.
|
|
|
|
package renderer
|
|
|
|
|
|
|
|
import (
|
|
|
|
urlPkg "net/url"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/spf13/viper"
|
|
|
|
"gitlab.com/tslocum/cview"
|
|
|
|
)
|
|
|
|
|
|
|
|
// convertRegularGemini converts non-preformatted blocks of text/gemini
|
|
|
|
// into a cview-compatible format.
|
|
|
|
// It also returns a slice of link URLs.
|
|
|
|
// numLinks is the number of links that exist so far.
|
2020-06-21 16:53:12 -04:00
|
|
|
// width is the number of columns to wrap to.
|
2020-06-18 16:54:48 -04:00
|
|
|
//
|
2020-06-21 16:53:12 -04:00
|
|
|
// Since this only works on non-preformatted blocks, RenderGemini
|
2020-06-18 16:54:48 -04:00
|
|
|
// should always be used instead.
|
2020-06-21 16:53:12 -04:00
|
|
|
func convertRegularGemini(s string, numLinks int, width int) (string, []string) {
|
2020-06-18 16:54:48 -04:00
|
|
|
links := make([]string, 0)
|
|
|
|
lines := strings.Split(s, "\n")
|
|
|
|
wrappedLines := make([]string, 0) // Final result
|
|
|
|
|
|
|
|
for i := range lines {
|
|
|
|
lines[i] = strings.TrimRight(lines[i], " \r\t\n")
|
|
|
|
|
2020-06-24 12:01:09 -04:00
|
|
|
if strings.HasPrefix(lines[i], "#") {
|
2020-06-18 16:54:48 -04:00
|
|
|
// Headings
|
2020-06-24 12:01:09 -04:00
|
|
|
if viper.GetBool("a-general.color") {
|
|
|
|
if strings.HasPrefix(lines[i], "###") {
|
|
|
|
lines[i] = "[fuchsia::b]" + lines[i] + "[-::-]"
|
|
|
|
}
|
|
|
|
if strings.HasPrefix(lines[i], "##") {
|
|
|
|
lines[i] = "[lime::b]" + lines[i] + "[-::-]"
|
|
|
|
}
|
|
|
|
if strings.HasPrefix(lines[i], "#") {
|
|
|
|
lines[i] = "[red::b]" + lines[i] + "[-::-]"
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Just bold, no colors
|
|
|
|
if strings.HasPrefix(lines[i], "###") {
|
|
|
|
lines[i] = "[::b]" + lines[i] + "[::-]"
|
|
|
|
}
|
|
|
|
if strings.HasPrefix(lines[i], "##") {
|
|
|
|
lines[i] = "[::b]" + lines[i] + "[::-]"
|
|
|
|
}
|
|
|
|
if strings.HasPrefix(lines[i], "#") {
|
|
|
|
lines[i] = "[::b]" + lines[i] + "[::-]"
|
|
|
|
}
|
2020-06-18 16:54:48 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Links
|
|
|
|
} else if strings.HasPrefix(lines[i], "=>") && len([]rune(lines[i])) >= 3 {
|
|
|
|
// Trim whitespace and separate link from link text
|
|
|
|
|
|
|
|
lines[i] = strings.Trim(lines[i][2:], " \t") // Remove `=>` part too
|
|
|
|
delim := strings.IndexAny(lines[i], " \t") // Whitespace between link and link text
|
|
|
|
|
|
|
|
var url string
|
|
|
|
var linkText string
|
|
|
|
if delim == -1 {
|
|
|
|
// No link text
|
|
|
|
url = lines[i]
|
|
|
|
linkText = url
|
|
|
|
} else {
|
|
|
|
// There is link text
|
|
|
|
url = lines[i][:delim]
|
|
|
|
linkText = strings.Trim(lines[i][delim:], " \t")
|
|
|
|
}
|
|
|
|
|
|
|
|
if strings.TrimSpace(lines[i]) == "" || strings.TrimSpace(url) == "" {
|
|
|
|
// Link was just whitespace, reset it and move on
|
|
|
|
lines[i] = "=>"
|
|
|
|
wrappedLines = append(wrappedLines, lines[i])
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
links = append(links, url)
|
|
|
|
|
|
|
|
if viper.GetBool("a-general.color") {
|
2020-06-23 20:07:25 -04:00
|
|
|
pU, err := urlPkg.Parse(url)
|
|
|
|
if err == nil && (pU.Scheme == "" || pU.Scheme == "gemini" || pU.Scheme == "about") {
|
2020-06-18 16:54:48 -04:00
|
|
|
// A gemini link
|
|
|
|
// Add the link text in blue (in a region), and a gray link number to the left of it
|
|
|
|
lines[i] = `[silver::b][` + strconv.Itoa(numLinks+len(links)) + "[]" + "[-::-] " +
|
|
|
|
`[dodgerblue]["` + strconv.Itoa(numLinks+len(links)-1) + `"]` + linkText + `[""][-]`
|
|
|
|
} else {
|
|
|
|
// Not a gemini link, use purple instead
|
|
|
|
lines[i] = `[silver::b][` + strconv.Itoa(numLinks+len(links)) + "[]" + "[-::-] " +
|
|
|
|
`[#8700d7]["` + strconv.Itoa(numLinks+len(links)-1) + `"]` + linkText + `[""][-]`
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// No colours allowed
|
2020-06-24 12:01:09 -04:00
|
|
|
lines[i] = `[::b][` + strconv.Itoa(numLinks+len(links)) + "[][::-] " +
|
|
|
|
`["` + strconv.Itoa(numLinks+len(links)-1) + `"]` + linkText + `[""]`
|
2020-06-18 16:54:48 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Lists
|
|
|
|
} else if strings.HasPrefix(lines[i], "* ") {
|
|
|
|
if viper.GetBool("a-general.bullets") {
|
2020-06-27 11:22:45 -04:00
|
|
|
lines[i] = " \u2022" + lines[i][1:]
|
2020-06-18 16:54:48 -04:00
|
|
|
}
|
|
|
|
// Optionally list lines could be colored here too, if color is enabled
|
|
|
|
}
|
|
|
|
|
|
|
|
// Final processing of each line: wrapping
|
|
|
|
|
|
|
|
if strings.TrimSpace(lines[i]) == "" {
|
|
|
|
// Just add empty line without processing
|
|
|
|
wrappedLines = append(wrappedLines, "")
|
|
|
|
} else {
|
|
|
|
if (strings.HasPrefix(lines[i], "[silver::b]") && viper.GetBool("a-general.color")) || strings.HasPrefix(lines[i], "[::b]") {
|
|
|
|
// It's a link line, so don't wrap it
|
|
|
|
wrappedLines = append(wrappedLines, lines[i])
|
|
|
|
} else if strings.HasPrefix(lines[i], ">") {
|
|
|
|
// It's a quote line, add extra quote symbols to the start of each wrapped line
|
|
|
|
|
|
|
|
// Remove beginning quote and maybe space
|
|
|
|
lines[i] = strings.TrimPrefix(lines[i], ">")
|
|
|
|
lines[i] = strings.TrimPrefix(lines[i], " ")
|
|
|
|
|
2020-07-02 14:29:33 -04:00
|
|
|
// Text is also made italic, lower down in code
|
|
|
|
|
2020-06-29 12:54:36 -04:00
|
|
|
// Anonymous function to allow recovery from potential WordWrap panic
|
|
|
|
func() {
|
|
|
|
defer func() {
|
|
|
|
if r := recover(); r != nil {
|
|
|
|
// Add unwrapped line instead
|
2020-07-02 14:29:33 -04:00
|
|
|
wrappedLines = append(wrappedLines, "> [::i]"+lines[i]+"[::-]")
|
2020-06-29 12:54:36 -04:00
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
temp := cview.WordWrap(lines[i], width)
|
|
|
|
for i := range temp {
|
2020-07-02 14:29:33 -04:00
|
|
|
temp[i] = "> [::i]" + temp[i] + "[::-]"
|
2020-06-29 12:54:36 -04:00
|
|
|
}
|
|
|
|
wrappedLines = append(wrappedLines, temp...)
|
|
|
|
}()
|
2020-06-18 16:54:48 -04:00
|
|
|
} else {
|
2020-06-29 12:54:36 -04:00
|
|
|
// Anonymous function to allow recovery from potential WordWrap panic
|
|
|
|
func() {
|
|
|
|
defer func() {
|
|
|
|
if r := recover(); r != nil {
|
|
|
|
// Add unwrapped line instead
|
|
|
|
wrappedLines = append(wrappedLines, lines[i])
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
wrappedLines = append(wrappedLines, cview.WordWrap(lines[i], width)...)
|
|
|
|
}()
|
2020-06-18 16:54:48 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return strings.Join(wrappedLines, "\r\n"), links
|
|
|
|
}
|
|
|
|
|
2020-06-21 16:53:12 -04:00
|
|
|
// RenderGemini converts text/gemini into a cview displayable format.
|
2020-06-18 16:54:48 -04:00
|
|
|
// It also returns a slice of link URLs.
|
2020-06-21 16:53:12 -04:00
|
|
|
func RenderGemini(s string, width int) (string, []string) {
|
2020-06-18 16:54:48 -04:00
|
|
|
s = cview.Escape(s)
|
|
|
|
if viper.GetBool("a-general.color") {
|
|
|
|
s = cview.TranslateANSI(s)
|
|
|
|
}
|
|
|
|
lines := strings.Split(s, "\n")
|
|
|
|
|
|
|
|
links := make([]string, 0)
|
|
|
|
|
|
|
|
// Process and wrap non preformatted lines
|
|
|
|
rendered := "" // Final result
|
|
|
|
pre := false
|
|
|
|
buf := "" // Block of regular or preformatted lines
|
|
|
|
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
|
|
|
|
rendered += buf
|
|
|
|
} else {
|
|
|
|
// Not preformatted, regular text
|
2020-06-21 16:53:12 -04:00
|
|
|
ren, lks := convertRegularGemini(buf, len(links), width)
|
2020-06-18 16:54:48 -04:00
|
|
|
links = append(links, lks...)
|
|
|
|
rendered += ren
|
|
|
|
}
|
|
|
|
buf = "" // Clear buffer for next block
|
|
|
|
pre = !pre
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
// Lines always end with \r\n for Windows compatibility
|
|
|
|
buf += strings.TrimSuffix(lines[i], "\r") + "\r\n"
|
|
|
|
}
|
|
|
|
// Gone through all the lines, but there still is likely a block in the buffer
|
|
|
|
if pre {
|
|
|
|
// File ended without closing the preformatted block
|
|
|
|
rendered += buf
|
|
|
|
} else {
|
|
|
|
// Not preformatted, regular text
|
|
|
|
// Same code as in the loop above
|
2020-06-21 16:53:12 -04:00
|
|
|
ren, lks := convertRegularGemini(buf, len(links), width)
|
2020-06-18 16:54:48 -04:00
|
|
|
links = append(links, lks...)
|
|
|
|
rendered += ren
|
|
|
|
}
|
|
|
|
|
|
|
|
return rendered, links
|
|
|
|
}
|