detached f367b16

feat(favicon): preload cached favicons in stacked pane title bars

commit
f367b1694c73d319189fc9bcbbbdae894d5c347f
author
bnema · · 2025-12-16 22:03 UTC
parent
2067abe
signature
unsigned
3 files · +53 -1
M internal/ui/cache/favicon_cache.go +24 -1
@@ -124,12 +124,35 @@ func (fc *FaviconCache) Get(domain string) *gdk.Texture {
124124 }
125125
126126 // GetByURL retrieves a favicon texture by extracting the domain from the URL.
127-// Returns nil if not found.
127+// Returns nil if not found in memory cache.
128128 func (fc *FaviconCache) GetByURL(pageURL string) *gdk.Texture {
129129 domain := extractDomain(pageURL)
130130 return fc.Get(domain)
131131 }
132132
133+// GetFromCacheByURL checks both memory and disk cache for a favicon.
134+// Returns nil if not found. Does not fetch from external sources.
135+// If found on disk, also populates memory cache.
136+func (fc *FaviconCache) GetFromCacheByURL(pageURL string) *gdk.Texture {
137+ domain := extractDomain(pageURL)
138+ if domain == "" {
139+ return nil
140+ }
141+
142+ // Check memory cache first
143+ if texture := fc.Get(domain); texture != nil {
144+ return texture
145+ }
146+
147+ // Check disk cache
148+ if texture := fc.loadFromDisk(domain); texture != nil {
149+ fc.Set(domain, texture)
150+ return texture
151+ }
152+
153+ return nil
154+}
155+
133156 // GetOrFetch checks the in-memory cache first, then queries the FaviconDatabase,
134157 // and falls back to Google's favicon API if not found.
135158 // The callback is invoked with the texture (or nil).
@@ -124,12 +124,35 @@ func (fc *FaviconCache) Get(domain string) *gdk.Texture {
124 }124 }
125 125
126 // GetByURL retrieves a favicon texture by extracting the domain from the URL.126 // GetByURL retrieves a favicon texture by extracting the domain from the URL.
127-// Returns nil if not found.127+// Returns nil if not found in memory cache.
128 func (fc *FaviconCache) GetByURL(pageURL string) *gdk.Texture {128 func (fc *FaviconCache) GetByURL(pageURL string) *gdk.Texture {
129 domain := extractDomain(pageURL)129 domain := extractDomain(pageURL)
130 return fc.Get(domain)130 return fc.Get(domain)
131 }131 }
132 132
133+// GetFromCacheByURL checks both memory and disk cache for a favicon.
134+// Returns nil if not found. Does not fetch from external sources.
135+// If found on disk, also populates memory cache.
136+func (fc *FaviconCache) GetFromCacheByURL(pageURL string) *gdk.Texture {
137+ domain := extractDomain(pageURL)
138+ if domain == "" {
139+ return nil
140+ }
141+
142+ // Check memory cache first
143+ if texture := fc.Get(domain); texture != nil {
144+ return texture
145+ }
146+
147+ // Check disk cache
148+ if texture := fc.loadFromDisk(domain); texture != nil {
149+ fc.Set(domain, texture)
150+ return texture
151+ }
152+
153+ return nil
154+}
155+
133 // GetOrFetch checks the in-memory cache first, then queries the FaviconDatabase,156 // GetOrFetch checks the in-memory cache first, then queries the FaviconDatabase,
134 // and falls back to Google's favicon API if not found.157 // and falls back to Google's favicon API if not found.
135 // The callback is invoked with the texture (or nil).158 // The callback is invoked with the texture (or nil).
M internal/ui/coordinator/content.go +27 -0
@@ -364,3 +364,30 @@ func (c *ContentCoordinator) SetNavigationOrigin(paneID entity.PaneID, url strin
364364 c.navOrigins[paneID] = url
365365 c.navOriginMu.Unlock()
366366 }
367+
368+// PreloadCachedFavicon checks the favicon cache and updates the stacked pane
369+// title bar immediately if a cached favicon exists for the URL.
370+// This provides instant favicon display without waiting for WebKit.
371+func (c *ContentCoordinator) PreloadCachedFavicon(ctx context.Context, paneID entity.PaneID, url string) {
372+ if c.faviconCache == nil || url == "" {
373+ return
374+ }
375+
376+ // Check memory and disk cache (no external fetch)
377+ texture := c.faviconCache.GetFromCacheByURL(url)
378+ if texture == nil {
379+ return
380+ }
381+
382+ // Update stacked pane favicon if applicable
383+ ws, wsView := c.getActiveWS()
384+ if wsView != nil {
385+ tr := wsView.TreeRenderer()
386+ if tr != nil {
387+ stackedView := tr.GetStackedViewForPane(string(paneID))
388+ if stackedView != nil {
389+ c.updateStackedPaneFavicon(ctx, ws, stackedView, paneID, texture)
390+ }
391+ }
392+ }
393+}
@@ -364,3 +364,30 @@ func (c *ContentCoordinator) SetNavigationOrigin(paneID entity.PaneID, url strin
364 c.navOrigins[paneID] = url364 c.navOrigins[paneID] = url
365 c.navOriginMu.Unlock()365 c.navOriginMu.Unlock()
366 }366 }
367+
368+// PreloadCachedFavicon checks the favicon cache and updates the stacked pane
369+// title bar immediately if a cached favicon exists for the URL.
370+// This provides instant favicon display without waiting for WebKit.
371+func (c *ContentCoordinator) PreloadCachedFavicon(ctx context.Context, paneID entity.PaneID, url string) {
372+ if c.faviconCache == nil || url == "" {
373+ return
374+ }
375+
376+ // Check memory and disk cache (no external fetch)
377+ texture := c.faviconCache.GetFromCacheByURL(url)
378+ if texture == nil {
379+ return
380+ }
381+
382+ // Update stacked pane favicon if applicable
383+ ws, wsView := c.getActiveWS()
384+ if wsView != nil {
385+ tr := wsView.TreeRenderer()
386+ if tr != nil {
387+ stackedView := tr.GetStackedViewForPane(string(paneID))
388+ if stackedView != nil {
389+ c.updateStackedPaneFavicon(ctx, ws, stackedView, paneID, texture)
390+ }
391+ }
392+ }
393+}
M internal/ui/coordinator/navigation.go +2 -0
@@ -57,6 +57,8 @@ func (c *NavigationCoordinator) Navigate(ctx context.Context, url string) error
5757 paneID = string(pane.Pane.ID)
5858 // Track original URL for cross-domain redirect favicon caching
5959 c.contentCoord.SetNavigationOrigin(pane.Pane.ID, url)
60+ // Pre-load cached favicon for instant display in stacked pane title bar
61+ c.contentCoord.PreloadCachedFavicon(ctx, pane.Pane.ID, url)
6062 }
6163 }
6264
@@ -57,6 +57,8 @@ func (c *NavigationCoordinator) Navigate(ctx context.Context, url string) error
57 paneID = string(pane.Pane.ID)57 paneID = string(pane.Pane.ID)
58 // Track original URL for cross-domain redirect favicon caching58 // Track original URL for cross-domain redirect favicon caching
59 c.contentCoord.SetNavigationOrigin(pane.Pane.ID, url)59 c.contentCoord.SetNavigationOrigin(pane.Pane.ID, url)
60+ // Pre-load cached favicon for instant display in stacked pane title bar
61+ c.contentCoord.PreloadCachedFavicon(ctx, pane.Pane.ID, url)
60 }62 }
61 }63 }
62 64

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close