Skip to content

Commit

Permalink
feat: better and faster link highlighting
Browse files Browse the repository at this point in the history
Performance optimization
  • Loading branch information
TypicalAM authored Jul 24, 2023
2 parents 9255327 + 9a7d7b7 commit d67c438
Show file tree
Hide file tree
Showing 6 changed files with 121 additions and 68 deletions.
4 changes: 2 additions & 2 deletions internal/model/browser/browser.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m.createNewTab(msg)

case backend.NewItemMsg:
bg := lipgloss.NewStyle().Width(m.width).Height((m.height)).Render(m.View())
bg := m.View()
width := m.width / 2
height := 17

Expand All @@ -194,7 +194,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m.downloadItem(msg)

case backend.MakeChoiceMsg:
bg := lipgloss.NewStyle().Width(m.width).Height((m.height)).Render(m.View())
bg := m.View()
width := m.width / 2
m.popup = popup.NewChoice(m.style.colors, bg, width, msg.Question, msg.Default)

Expand Down
61 changes: 34 additions & 27 deletions internal/model/popup/popup.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"strings"

tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/muesli/ansi"
)

Expand All @@ -15,13 +14,12 @@ type Popup interface {

// Default is a default popup window.
type Default struct {
prefix string
suffix string
ogSection []string
section []string
textAbove string
textBelow string
rowPrefix []string
rowSuffix []string
width int
height int
startCol int
}

// New creates a new default popup window.
Expand All @@ -33,21 +31,37 @@ func New(bgRaw string, width, height int) Default {
startRow := (bgHeight - height) / 2
startCol := (bgWidth - width) / 2

ogSection := make([]string, height)
section := make([]string, height)
rowPrefix := make([]string, height)
rowSuffix := make([]string, height)

for i, text := range bg[startRow : startRow+height] {
popupStart := findPrintIndex(text, startCol)
popupEnd := findPrintIndex(text, startCol+width)

if popupStart != -1 {
rowPrefix[i] = text[:popupStart]
} else {
rowPrintable := ansi.PrintableRuneWidth(text)
rowPrefix[i] = text + strings.Repeat(" ", startCol-rowPrintable)
}

if popupEnd != -1 {
rowSuffix[i] = text[popupEnd:]
} else {
rowSuffix[i] = ""
}
}

prefix := strings.Join(bg[:startRow], "\n")
suffix := strings.Join(bg[startRow+height:], "\n")
copy(ogSection, bg[startRow:startRow+height])

return Default{
ogSection: ogSection,
section: section,
rowPrefix: rowPrefix,
rowSuffix: rowSuffix,
width: width,
height: height,
prefix: prefix,
suffix: suffix,
startCol: startCol,
textAbove: prefix,
textBelow: suffix,
}
}

Expand All @@ -57,19 +71,12 @@ func (p Default) Overlay(text string) string {
lines := strings.Split(text, "\n")

// Overlay the background with the styled text.
// TODO: Use a string builder
for i, text := range p.ogSection {
p.section[i] = text[:findPrintableIndex(text, p.startCol)] +
lines[i] +
text[findPrintableIndex(text, p.startCol+p.width):]
output := make([]string, len(lines))
for i := 0; i < len(lines); i++ {
output[i] = p.rowPrefix[i] + lines[i] + p.rowSuffix[i]
}

return lipgloss.JoinVertical(
lipgloss.Top,
p.prefix,
strings.Join(p.section, "\n"),
p.suffix,
)
return p.textAbove + "\n" + strings.Join(output, "\n") + "\n" + p.textBelow
}

// Width returns the width of the popup window.
Expand All @@ -82,8 +89,8 @@ func (p Default) Height() int {
return p.height
}

// findPrintableIndex finds the index of the last printable rune at the given index.
func findPrintableIndex(str string, index int) int {
// findPrintIndex finds the print index, that is what string index corresponds to the given printable rune index.
func findPrintIndex(str string, index int) int {
for i := len(str) - 1; i >= 0; i-- {
if ansi.PrintableRuneWidth(str[:i]) == index {
return i
Expand Down
3 changes: 1 addition & 2 deletions internal/model/simplelist/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"github.com/charmbracelet/bubbles/list"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/muesli/reflow/ansi"
)

// Item is an item in the list
Expand Down Expand Up @@ -142,7 +141,7 @@ func (m Model) View() string {

if m.showDesc {
if item, ok := m.items[i].(list.DefaultItem); ok {
if ansi.PrintableRuneWidth(item.Description()) != 0 {
if len(item.Description()) != 0 {
sections = append(sections, m.style.styleDescription(item.Description()))
} else {
sections = append(sections, "")
Expand Down
34 changes: 27 additions & 7 deletions internal/model/tab/feed/feed.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ func (k Keymap) FullHelp() [][]key.Binding {
type Model struct {
list list.Model
fetcher backend.Fetcher
tr *glamour.TermRenderer
colorTr *glamour.TermRenderer
noColorTr *glamour.TermRenderer
colors *theme.Colors
selector *selector
title string
Expand Down Expand Up @@ -263,9 +264,9 @@ func (m Model) loadTab(items []list.Item, articleContents []string) (tab.Tab, te
m.list.DisableQuitKeybindings()

m.viewport = viewport.New(m.style.viewportWidth, m.height)

m.articleContent = articleContents
termRenderer, err := glamour.NewTermRenderer(

colorTr, err := glamour.NewTermRenderer(
glamour.WithStyles(m.colors.MarkdownStyle),
glamour.WithWordWrap(m.style.viewportWidth-2),
)
Expand All @@ -276,8 +277,20 @@ func (m Model) loadTab(items []list.Item, articleContents []string) (tab.Tab, te
return m, nil
}

noColorTr, err := glamour.NewTermRenderer(
glamour.WithStyles(glamour.NoTTYStyleConfig),
glamour.WithWordWrap(m.style.viewportWidth-2),
)

if err != nil {
m.errShown = true
m.loaded = false
return m, nil
}

// Locked and loaded
m.tr = termRenderer
m.colorTr = colorTr
m.noColorTr = noColorTr
m.loaded = true
return m, nil
}
Expand All @@ -293,14 +306,21 @@ func (m Model) updateViewport() (tab.Tab, tea.Cmd) {
return m, nil
}

text, err := m.tr.Render(m.articleContent[m.list.Index()])
rawText := m.articleContent[m.list.Index()]
styledText, err := m.colorTr.Render(rawText)
if err != nil {
m.viewport.SetContent(fmt.Sprintf("We have encountered an error styling the content: %s", err))
return m, nil
}

noColorText, err := m.noColorTr.Render(rawText)
if err != nil {
m.viewport.SetContent(fmt.Sprintf("We have encountered an error styling the content: %s", err))
return m, nil
}

m.selector.newArticle(text)
m.viewport.SetContent(text)
m.selector.newArticle(&rawText, &noColorText)
m.viewport.SetContent(styledText)
m.viewport.SetYOffset(0)
return m, nil
}
Expand Down
83 changes: 54 additions & 29 deletions internal/model/tab/feed/selector.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package feed
import (
"errors"
"os/exec"
"regexp"
"runtime"
"strings"

Expand All @@ -12,13 +11,10 @@ import (
"mvdan.cc/xurls/v2"
)

// TODO: Move from this monstrosity to a custom written regex (this is gonna be fun)
var ansiRe = regexp.MustCompile("[\u001B\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))")

// selector allows us to select links from a feed and open them in the browser
type selector struct {
linkStyle lipgloss.Style
article string
article *string
urls []string
indices [][]int
selection int
Expand All @@ -35,31 +31,61 @@ func newSelector(colors *theme.Colors) *selector {
}

// newArticle finds the URLs for this article
func (s *selector) newArticle(content string) {
rx := xurls.Relaxed()

s.article = content
func (s *selector) newArticle(rawText, styledText *string) {
s.article = styledText
s.selection = 0
s.active = false

rawIndices := rx.FindAllStringIndex(content, -1)
rx := xurls.Strict()
urlsToIndex := rx.FindAllString(*rawText, -1)

// map the url to their possible linebreak indices
urlsMap := make(map[string][]int)
for _, url := range urlsToIndex {
if !strings.ContainsRune(url, '-') {
urlsMap[url] = append(urlsMap[url], len(url)-1)
continue
}

for i := 0; i < len(url); i++ {
if url[i] == '-' {
urlsMap[url] = append(urlsMap[url], i)
}
}

urlsMap[url] = append(urlsMap[url], len(url)-1)
}

// Fix the newline issues
s.indices = make([][]int, 0)
s.urls = make([]string, 0)
s.indices = make([][]int, 0)

for url, indices := range urlsMap {
s.urls = append(s.urls, url)
// Check if the entire url fits in one line
start := strings.Index(*styledText, url[:indices[len(indices)-1]])
if start != -1 {
s.indices = append(s.indices, []int{start, start + len(url)})
continue
}

// Link highlighting is stupid, so we have to do this
for i := 0; i < len(rawIndices); i++ {
str := s.article[rawIndices[i][0]:rawIndices[i][1]]
if str[len(str)-1] == '-' {
s.indices = append(s.indices, []int{rawIndices[i][0], rawIndices[i+1][1]})
urlNoAnsi := ansiRe.ReplaceAllString(s.article[rawIndices[i][0]:rawIndices[i+1][1]], "")
urlStripped := strings.ReplaceAll(strings.ReplaceAll(urlNoAnsi, " ", ""), "\n", "")
s.urls = append(s.urls, urlStripped)
i++
} else {
s.indices = append(s.indices, rawIndices[i])
s.urls = append(s.urls, s.article[rawIndices[i][0]:rawIndices[i][1]])
// Let's check on the - character on which the url is broken down
for i := len(indices) - 2; i >= 0; i-- {
start = strings.Index(*styledText, url[:indices[i]])
if start == -1 {
continue
}

// The line is broken down on index, let's search where it ends on the next line
end := 0
for j := start + indices[i]; j < len(*styledText); j++ {
if (*styledText)[j] == url[indices[i]+1] {
end = j + len(url) - indices[i] - 1
break
}
}

s.indices = append(s.indices, []int{start, end})
break
}
}
}
Expand All @@ -75,12 +101,11 @@ func (s *selector) cycle() string {
}

start, end := s.indices[s.selection][0], s.indices[s.selection][1]
linkText := s.article[start:end]
b.WriteString(s.article[:start])
b.WriteString((*s.article)[:start])
linkText := (*s.article)[start:end]

// This is tricky
if strings.ContainsRune(s.article[start:end], '\n') {
linkText = ansiRe.ReplaceAllString(linkText, "")
if strings.ContainsRune(linkText, '\n') {
newLine := strings.IndexRune(linkText, '\n')
lastSpace := strings.LastIndex(linkText, " ")

Expand All @@ -91,7 +116,7 @@ func (s *selector) cycle() string {
b.WriteString(s.linkStyle.Render(linkText))
}

b.WriteString(s.article[end:])
b.WriteString((*s.article)[end:])
return b.String()
}

Expand Down
4 changes: 3 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package main

import "github.com/TypicalAM/goread/cmd/goread"
import (
"github.com/TypicalAM/goread/cmd/goread"
)

var (
version = "dev"
Expand Down

0 comments on commit d67c438

Please sign in to comment.