detached 34b0481

perf(daemon): avoid copying cached styles per popup

commit
34b0481e11c95bca3955509a49fdca3c1f016a85
author
brice · · 2026-07-18 10:14 UTC
parent
f5267fd
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
3 files · +34 -29
M internal/usecase/daemon/floating_render.go +14 -12
@@ -86,15 +86,16 @@ func calculateContentFloatingGeometry(content domain.Size, cfg domain.FloatingCo
8686 }
8787
8888 type floatingComposeInput struct {
89- baseFrame renderer.Frame
90- baseDamage []renderer.Damage
91- floating capturedFloatingRenderState
92- content domain.Rect
93- layout capturedTabLayout
94- theme themeui.Theme
95- styles themeui.Styles
96- cache composeCacheInput
97- full bool
89+ baseFrame renderer.Frame
90+ baseDamage []renderer.Damage
91+ floating capturedFloatingRenderState
92+ content domain.Rect
93+ layout capturedTabLayout
94+ theme themeui.Theme
95+ borderMuted renderer.Style
96+ borderActive renderer.Style
97+ cache composeCacheInput
98+ full bool
9899 }
99100
100101 func composeCapturedFloatingFrame(input floatingComposeInput) (renderer.Frame, []renderer.Damage) {
@@ -104,7 +105,8 @@ func composeCapturedFloatingFrame(input floatingComposeInput) (renderer.Frame, [
104105 content := input.content
105106 layoutSnap := input.layout
106107 theme := input.theme
107- styles := input.styles
108+ borderMuted := input.borderMuted
109+ borderActive := input.borderActive
108110 cache := input.cache
109111 full := input.full
110112
@@ -119,9 +121,9 @@ func composeCapturedFloatingFrame(input floatingComposeInput) (renderer.Frame, [
119121 }
120122 popupChanged := !cache.valid || cache.floatingGeneration != floating.generation || cache.floatingGeometry != geometry || cache.floatingFocused != floating.focused
121123 titleChanged := popupChanged || cache.floatingTitleGeneration != floating.titleGeneration
122- border := styles.BorderMuted
124+ border := borderMuted
123125 if floating.focused {
124- border = styles.BorderActive
126+ border = borderActive
125127 }
126128 drawFloatingBorder(frame, geometry.Bounds, floating.title, border)
127129 if full || popupChanged {
@@ -86,15 +86,16 @@ func calculateContentFloatingGeometry(content domain.Size, cfg domain.FloatingCo
86 }86 }
87 87
88 type floatingComposeInput struct {88 type floatingComposeInput struct {
89- baseFrame renderer.Frame89+ baseFrame renderer.Frame
90- baseDamage []renderer.Damage90+ baseDamage []renderer.Damage
91- floating capturedFloatingRenderState91+ floating capturedFloatingRenderState
92- content domain.Rect92+ content domain.Rect
93- layout capturedTabLayout93+ layout capturedTabLayout
94- theme themeui.Theme94+ theme themeui.Theme
95- styles themeui.Styles95+ borderMuted renderer.Style
96- cache composeCacheInput96+ borderActive renderer.Style
97- full bool97+ cache composeCacheInput
98+ full bool
98 }99 }
99 100
100 func composeCapturedFloatingFrame(input floatingComposeInput) (renderer.Frame, []renderer.Damage) {101 func composeCapturedFloatingFrame(input floatingComposeInput) (renderer.Frame, []renderer.Damage) {
@@ -104,7 +105,8 @@ func composeCapturedFloatingFrame(input floatingComposeInput) (renderer.Frame, [
104 content := input.content105 content := input.content
105 layoutSnap := input.layout106 layoutSnap := input.layout
106 theme := input.theme107 theme := input.theme
107- styles := input.styles108+ borderMuted := input.borderMuted
109+ borderActive := input.borderActive
108 cache := input.cache110 cache := input.cache
109 full := input.full111 full := input.full
110 112
@@ -119,9 +121,9 @@ func composeCapturedFloatingFrame(input floatingComposeInput) (renderer.Frame, [
119 }121 }
120 popupChanged := !cache.valid || cache.floatingGeneration != floating.generation || cache.floatingGeometry != geometry || cache.floatingFocused != floating.focused122 popupChanged := !cache.valid || cache.floatingGeneration != floating.generation || cache.floatingGeometry != geometry || cache.floatingFocused != floating.focused
121 titleChanged := popupChanged || cache.floatingTitleGeneration != floating.titleGeneration123 titleChanged := popupChanged || cache.floatingTitleGeneration != floating.titleGeneration
122- border := styles.BorderMuted124+ border := borderMuted
123 if floating.focused {125 if floating.focused {
124- border = styles.BorderActive126+ border = borderActive
125 }127 }
126 drawFloatingBorder(frame, geometry.Bounds, floating.title, border)128 drawFloatingBorder(frame, geometry.Bounds, floating.title, border)
127 if full || popupChanged {129 if full || popupChanged {
M internal/usecase/daemon/floating_render_test.go +10 -8
@@ -199,9 +199,10 @@ func TestComposeCapturedFloatingFrameUsesSemanticBorderWithoutTintingPaneCells(t
199199 floating: capturedFloatingRenderState{
200200 visible: true, focused: true, pane: capturedPaneRenderState{frame: pane}, geometry: geometry, title: "float", generation: 1,
201201 },
202- content: content,
203- styles: themeui.Styles{BorderActive: active, BorderMuted: muted},
204- full: true,
202+ content: content,
203+ borderActive: active,
204+ borderMuted: muted,
205+ full: true,
205206 })
206207 bounds := geometry.translate(content.X, content.Y)
207208 require.Equal(t, active, frame.At(bounds.Bounds.X, bounds.Bounds.Y).Style, "focused floating border must use BorderActive")
@@ -220,11 +221,12 @@ func TestComposeCapturedFloatingFrameUsesMutedBorderWhenUnfocused(t *testing.T)
220221 active, muted := renderer.Style{Foreground: 2}, renderer.Style{Foreground: 4}
221222
222223 frame, _ := composeCapturedFloatingFrame(floatingComposeInput{
223- baseFrame: base,
224- floating: capturedFloatingRenderState{visible: true, pane: capturedPaneRenderState{frame: renderer.NewFrame(4, 1)}, geometry: geometry, title: "float", generation: 1},
225- content: content,
226- styles: themeui.Styles{BorderActive: active, BorderMuted: muted},
227- full: true,
224+ baseFrame: base,
225+ floating: capturedFloatingRenderState{visible: true, pane: capturedPaneRenderState{frame: renderer.NewFrame(4, 1)}, geometry: geometry, title: "float", generation: 1},
226+ content: content,
227+ borderActive: active,
228+ borderMuted: muted,
229+ full: true,
228230 })
229231 bounds := geometry.translate(content.X, content.Y)
230232 require.Equal(t, muted, frame.At(bounds.Bounds.X, bounds.Bounds.Y).Style)
@@ -199,9 +199,10 @@ func TestComposeCapturedFloatingFrameUsesSemanticBorderWithoutTintingPaneCells(t
199 floating: capturedFloatingRenderState{199 floating: capturedFloatingRenderState{
200 visible: true, focused: true, pane: capturedPaneRenderState{frame: pane}, geometry: geometry, title: "float", generation: 1,200 visible: true, focused: true, pane: capturedPaneRenderState{frame: pane}, geometry: geometry, title: "float", generation: 1,
201 },201 },
202- content: content,202+ content: content,
203- styles: themeui.Styles{BorderActive: active, BorderMuted: muted},203+ borderActive: active,
204- full: true,204+ borderMuted: muted,
205+ full: true,
205 })206 })
206 bounds := geometry.translate(content.X, content.Y)207 bounds := geometry.translate(content.X, content.Y)
207 require.Equal(t, active, frame.At(bounds.Bounds.X, bounds.Bounds.Y).Style, "focused floating border must use BorderActive")208 require.Equal(t, active, frame.At(bounds.Bounds.X, bounds.Bounds.Y).Style, "focused floating border must use BorderActive")
@@ -220,11 +221,12 @@ func TestComposeCapturedFloatingFrameUsesMutedBorderWhenUnfocused(t *testing.T)
220 active, muted := renderer.Style{Foreground: 2}, renderer.Style{Foreground: 4}221 active, muted := renderer.Style{Foreground: 2}, renderer.Style{Foreground: 4}
221 222
222 frame, _ := composeCapturedFloatingFrame(floatingComposeInput{223 frame, _ := composeCapturedFloatingFrame(floatingComposeInput{
223- baseFrame: base,224+ baseFrame: base,
224- floating: capturedFloatingRenderState{visible: true, pane: capturedPaneRenderState{frame: renderer.NewFrame(4, 1)}, geometry: geometry, title: "float", generation: 1},225+ floating: capturedFloatingRenderState{visible: true, pane: capturedPaneRenderState{frame: renderer.NewFrame(4, 1)}, geometry: geometry, title: "float", generation: 1},
225- content: content,226+ content: content,
226- styles: themeui.Styles{BorderActive: active, BorderMuted: muted},227+ borderActive: active,
227- full: true,228+ borderMuted: muted,
229+ full: true,
228 })230 })
229 bounds := geometry.translate(content.X, content.Y)231 bounds := geometry.translate(content.X, content.Y)
230 require.Equal(t, muted, frame.At(bounds.Bounds.X, bounds.Bounds.Y).Style)232 require.Equal(t, muted, frame.At(bounds.Bounds.X, bounds.Bounds.Y).Style)
M internal/usecase/daemon/render_pipeline.go +10 -9
@@ -130,15 +130,16 @@ func composeFrame(state capturedRenderState, in composeCacheInput, scratchIn ...
130130 if state.floating.visible {
131131 var floatingDamage []renderer.Damage
132132 frame, floatingDamage = composeCapturedFloatingFrame(floatingComposeInput{
133- baseFrame: baseFrame,
134- baseDamage: damage,
135- floating: state.floating,
136- content: content,
137- layout: state.layout,
138- theme: state.theme,
139- styles: styles,
140- cache: in,
141- full: full || state.overlays.active(),
133+ baseFrame: baseFrame,
134+ baseDamage: damage,
135+ floating: state.floating,
136+ content: content,
137+ layout: state.layout,
138+ theme: state.theme,
139+ borderMuted: styles.BorderMuted,
140+ borderActive: styles.BorderActive,
141+ cache: in,
142+ full: full || state.overlays.active(),
142143 })
143144 damage = floatingDamage
144145 }
@@ -130,15 +130,16 @@ func composeFrame(state capturedRenderState, in composeCacheInput, scratchIn ...
130 if state.floating.visible {130 if state.floating.visible {
131 var floatingDamage []renderer.Damage131 var floatingDamage []renderer.Damage
132 frame, floatingDamage = composeCapturedFloatingFrame(floatingComposeInput{132 frame, floatingDamage = composeCapturedFloatingFrame(floatingComposeInput{
133- baseFrame: baseFrame,133+ baseFrame: baseFrame,
134- baseDamage: damage,134+ baseDamage: damage,
135- floating: state.floating,135+ floating: state.floating,
136- content: content,136+ content: content,
137- layout: state.layout,137+ layout: state.layout,
138- theme: state.theme,138+ theme: state.theme,
139- styles: styles,139+ borderMuted: styles.BorderMuted,
140- cache: in,140+ borderActive: styles.BorderActive,
141- full: full || state.overlays.active(),141+ cache: in,
142+ full: full || state.overlays.active(),
142 })143 })
143 damage = floatingDamage144 damage = floatingDamage
144 }145 }

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 12

Tags 11

↑↓ moveenter openesc close