no-mistakes(review): fix(daemon): propagate themes to floating panes
- commit
- 04d2afbd1226a6bb334166bc1ee82f9892ddaf6c
- author
- brice · · 2026-07-10 11:40 UTC
- parent
- 789f8b1
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
6 files · +81 -21
M internal/usecase/daemon/config.go +16 -8
| @@ -232,13 +232,21 @@ func (d *Daemon) reapplyThemeAllSessions() { | |||
| 232 | 232 | d.mu.Unlock() | |
| 233 | 233 | ||
| 234 | 234 | for _, sess := range sessions { | |
| 235 | - | sess.mu.Lock() | |
| 236 | - | ac := sess.client | |
| 237 | - | sess.mu.Unlock() | |
| 238 | - | var clientTheme theme.Theme | |
| 239 | - | if ac != nil { | |
| 240 | - | clientTheme = ac.getClientTheme() | |
| 241 | - | } | |
| 242 | - | d.applyHostTheme(sess, ac, d.effectiveTheme(clientTheme), ac == nil) | |
| 235 | + | d.reapplyThemeSession(sess) | |
| 236 | + | } | |
| 237 | + | } | |
| 238 | + | ||
| 239 | + | func (d *Daemon) reapplyThemeSession(sess *session) { | |
| 240 | + | sess.mu.Lock() | |
| 241 | + | ac := sess.client | |
| 242 | + | sess.mu.Unlock() | |
| 243 | + | if ac == nil { | |
| 244 | + | d.applyHostTheme(sess, nil, d.effectiveTheme(theme.Theme{}), true) | |
| 245 | + | return | |
| 243 | 246 | } | |
| 247 | + | ac.sendMu.Lock() | |
| 248 | + | defer ac.sendMu.Unlock() | |
| 249 | + | sess.themeMu.Lock() | |
| 250 | + | defer sess.themeMu.Unlock() | |
| 251 | + | d.applyHostThemeLocked(sess, ac, d.effectiveTheme(ac.getClientTheme()), false) | |
| 244 | 252 | } | |
| @@ -232,13 +232,21 @@ func (d *Daemon) reapplyThemeAllSessions() { | |||||
| 232 | d.mu.Unlock() | 232 | d.mu.Unlock() | ||
| 233 | 233 | ||||
| 234 | for _, sess := range sessions { | 234 | for _, sess := range sessions { | ||
| 235 | - | sess.mu.Lock() | 235 | + | d.reapplyThemeSession(sess) |
| 236 | - | ac := sess.client | 236 | + | } |
| 237 | - | sess.mu.Unlock() | 237 | + | } |
| 238 | - | var clientTheme theme.Theme | 238 | + | |
| 239 | - | if ac != nil { | 239 | + | func (d *Daemon) reapplyThemeSession(sess *session) { |
| 240 | - | clientTheme = ac.getClientTheme() | 240 | + | sess.mu.Lock() |
| 241 | - | } | 241 | + | ac := sess.client |
| 242 | - | d.applyHostTheme(sess, ac, d.effectiveTheme(clientTheme), ac == nil) | 242 | + | sess.mu.Unlock() |
| 243 | + | if ac == nil { | |||
| 244 | + | d.applyHostTheme(sess, nil, d.effectiveTheme(theme.Theme{}), true) | |||
| 245 | + | return | |||
| 243 | } | 246 | } | ||
| 247 | + | ac.sendMu.Lock() | |||
| 248 | + | defer ac.sendMu.Unlock() | |||
| 249 | + | sess.themeMu.Lock() | |||
| 250 | + | defer sess.themeMu.Unlock() | |||
| 251 | + | d.applyHostThemeLocked(sess, ac, d.effectiveTheme(ac.getClientTheme()), false) | |||
| 244 | } | 252 | } | ||
M internal/usecase/daemon/floating.go +1 -0
| @@ -339,6 +339,7 @@ func (d *Daemon) installFloating(sess *session, tb *tab, p *pane, generation uin | |||
| 339 | 339 | closeFloatingPane(p) | |
| 340 | 340 | return | |
| 341 | 341 | } | |
| 342 | + | d.reapplyThemeSession(sess) | |
| 342 | 343 | d.startPaneGoroutines(sess, tb, p) | |
| 343 | 344 | if visible { | |
| 344 | 345 | d.resizeActiveFloating(tb) | |
| @@ -339,6 +339,7 @@ func (d *Daemon) installFloating(sess *session, tb *tab, p *pane, generation uin | |||||
| 339 | closeFloatingPane(p) | 339 | closeFloatingPane(p) | ||
| 340 | return | 340 | return | ||
| 341 | } | 341 | } | ||
| 342 | + | d.reapplyThemeSession(sess) | |||
| 342 | d.startPaneGoroutines(sess, tb, p) | 343 | d.startPaneGoroutines(sess, tb, p) | ||
| 343 | if visible { | 344 | if visible { | ||
| 344 | d.resizeActiveFloating(tb) | 345 | d.resizeActiveFloating(tb) | ||
M internal/usecase/daemon/floating_test.go +9 -2
| @@ -17,6 +17,7 @@ import ( | |||
| 17 | 17 | "github.com/bnema/vev/internal/ports" | |
| 18 | 18 | portsmocks "github.com/bnema/vev/internal/ports/mocks" | |
| 19 | 19 | "github.com/bnema/vev/internal/usecase/layout" | |
| 20 | + | themeui "github.com/bnema/vev/internal/usecase/theme" | |
| 20 | 21 | ) | |
| 21 | 22 | ||
| 22 | 23 | func TestFloatingSlotTransitions(t *testing.T) { | |
| @@ -134,7 +135,7 @@ func TestFloatingLifecycleCapturesLaunchBeforeOpenAndDoesNotHoldTabLock(t *testi | |||
| 134 | 135 | tb := newTabWithStableID("t_stable", "p_normal", newBlockingPanePTY(t), domain.Size{Cols: 100, Rows: 40}) | |
| 135 | 136 | tb.ctx, tb.cancel = context.WithCancel(t.Context()) | |
| 136 | 137 | sess := &session{name: "work", cwd: cwd, tabs: []*tab{tb}, ctx: t.Context()} | |
| 137 | - | d.ApplyConfig(domain.Config{Floating: domain.FloatingConfig{Command: "btop --utf", Width: 50, Height: 50}}) | |
| 138 | + | d.ApplyConfig(domain.Config{Theme: domain.ThemeDark, Floating: domain.FloatingConfig{Command: "btop --utf", Width: 50, Height: 50}}) | |
| 138 | 139 | d.ensureFloatingWarm(sess, tb) | |
| 139 | 140 | // Open has started while this goroutine owns tab.mu: an external factory | |
| 140 | 141 | // call under that lock would deadlock this channel-controlled test. | |
| @@ -145,13 +146,19 @@ func TestFloatingLifecycleCapturesLaunchBeforeOpenAndDoesNotHoldTabLock(t *testi | |||
| 145 | 146 | t.Fatal("floating Open waited for tab.mu") | |
| 146 | 147 | } | |
| 147 | 148 | tb.mu.Unlock() | |
| 148 | - | d.ApplyConfig(domain.Config{Floating: domain.FloatingConfig{Command: "later", Width: 90, Height: 90}}) | |
| 149 | + | d.ApplyConfig(domain.Config{Theme: domain.ThemeDark, Floating: domain.FloatingConfig{Command: "later", Width: 90, Height: 90}}) | |
| 149 | 150 | close(allowOpen) | |
| 150 | 151 | select { | |
| 151 | 152 | case <-readerStarted: // install started exactly one reader after the async Open | |
| 152 | 153 | case <-time.After(time.Second): | |
| 153 | 154 | t.Fatal("floating pane was not installed") | |
| 154 | 155 | } | |
| 156 | + | tb.mu.Lock() | |
| 157 | + | floating := tb.floating.pane | |
| 158 | + | tb.mu.Unlock() | |
| 159 | + | require.NotNil(t, floating) | |
| 160 | + | assertPaneDefaultColors(t, floating, themeui.BuiltinDark.Foreground, themeui.BuiltinDark.Background) | |
| 161 | + | assertPaneColorScheme(t, floating, false) | |
| 155 | 162 | require.Equal(t, "/bin/custom-shell", gotCommand) | |
| 156 | 163 | require.Equal(t, []string{"-lc", "btop --utf"}, gotArgs) | |
| 157 | 164 | require.Equal(t, cwd, gotDir) | |
| @@ -17,6 +17,7 @@ import ( | |||||
| 17 | "github.com/bnema/vev/internal/ports" | 17 | "github.com/bnema/vev/internal/ports" | ||
| 18 | portsmocks "github.com/bnema/vev/internal/ports/mocks" | 18 | portsmocks "github.com/bnema/vev/internal/ports/mocks" | ||
| 19 | "github.com/bnema/vev/internal/usecase/layout" | 19 | "github.com/bnema/vev/internal/usecase/layout" | ||
| 20 | + | themeui "github.com/bnema/vev/internal/usecase/theme" | |||
| 20 | ) | 21 | ) | ||
| 21 | 22 | ||||
| 22 | func TestFloatingSlotTransitions(t *testing.T) { | 23 | func TestFloatingSlotTransitions(t *testing.T) { | ||
| @@ -134,7 +135,7 @@ func TestFloatingLifecycleCapturesLaunchBeforeOpenAndDoesNotHoldTabLock(t *testi | |||||
| 134 | tb := newTabWithStableID("t_stable", "p_normal", newBlockingPanePTY(t), domain.Size{Cols: 100, Rows: 40}) | 135 | tb := newTabWithStableID("t_stable", "p_normal", newBlockingPanePTY(t), domain.Size{Cols: 100, Rows: 40}) | ||
| 135 | tb.ctx, tb.cancel = context.WithCancel(t.Context()) | 136 | tb.ctx, tb.cancel = context.WithCancel(t.Context()) | ||
| 136 | sess := &session{name: "work", cwd: cwd, tabs: []*tab{tb}, ctx: t.Context()} | 137 | sess := &session{name: "work", cwd: cwd, tabs: []*tab{tb}, ctx: t.Context()} | ||
| 137 | - | d.ApplyConfig(domain.Config{Floating: domain.FloatingConfig{Command: "btop --utf", Width: 50, Height: 50}}) | 138 | + | d.ApplyConfig(domain.Config{Theme: domain.ThemeDark, Floating: domain.FloatingConfig{Command: "btop --utf", Width: 50, Height: 50}}) |
| 138 | d.ensureFloatingWarm(sess, tb) | 139 | d.ensureFloatingWarm(sess, tb) | ||
| 139 | // Open has started while this goroutine owns tab.mu: an external factory | 140 | // Open has started while this goroutine owns tab.mu: an external factory | ||
| 140 | // call under that lock would deadlock this channel-controlled test. | 141 | // call under that lock would deadlock this channel-controlled test. | ||
| @@ -145,13 +146,19 @@ func TestFloatingLifecycleCapturesLaunchBeforeOpenAndDoesNotHoldTabLock(t *testi | |||||
| 145 | t.Fatal("floating Open waited for tab.mu") | 146 | t.Fatal("floating Open waited for tab.mu") | ||
| 146 | } | 147 | } | ||
| 147 | tb.mu.Unlock() | 148 | tb.mu.Unlock() | ||
| 148 | - | d.ApplyConfig(domain.Config{Floating: domain.FloatingConfig{Command: "later", Width: 90, Height: 90}}) | 149 | + | d.ApplyConfig(domain.Config{Theme: domain.ThemeDark, Floating: domain.FloatingConfig{Command: "later", Width: 90, Height: 90}}) |
| 149 | close(allowOpen) | 150 | close(allowOpen) | ||
| 150 | select { | 151 | select { | ||
| 151 | case <-readerStarted: // install started exactly one reader after the async Open | 152 | case <-readerStarted: // install started exactly one reader after the async Open | ||
| 152 | case <-time.After(time.Second): | 153 | case <-time.After(time.Second): | ||
| 153 | t.Fatal("floating pane was not installed") | 154 | t.Fatal("floating pane was not installed") | ||
| 154 | } | 155 | } | ||
| 156 | + | tb.mu.Lock() | |||
| 157 | + | floating := tb.floating.pane | |||
| 158 | + | tb.mu.Unlock() | |||
| 159 | + | require.NotNil(t, floating) | |||
| 160 | + | assertPaneDefaultColors(t, floating, themeui.BuiltinDark.Foreground, themeui.BuiltinDark.Background) | |||
| 161 | + | assertPaneColorScheme(t, floating, false) | |||
| 155 | require.Equal(t, "/bin/custom-shell", gotCommand) | 162 | require.Equal(t, "/bin/custom-shell", gotCommand) | ||
| 156 | require.Equal(t, []string{"-lc", "btop --utf"}, gotArgs) | 163 | require.Equal(t, []string{"-lc", "btop --utf"}, gotArgs) | ||
| 157 | require.Equal(t, cwd, gotDir) | 164 | require.Equal(t, cwd, gotDir) | ||
M internal/usecase/daemon/session.go +1 -0
| @@ -55,6 +55,7 @@ type session struct { | |||
| 55 | 55 | cancel context.CancelFunc | |
| 56 | 56 | ||
| 57 | 57 | mu sync.Mutex // guards tabs, active, client, clipFiles, and clipboard queue state | |
| 58 | + | themeMu sync.Mutex | |
| 58 | 59 | tabs []*tab | |
| 59 | 60 | active int | |
| 60 | 61 | client *attachedClient | |
| @@ -55,6 +55,7 @@ type session struct { | |||||
| 55 | cancel context.CancelFunc | 55 | cancel context.CancelFunc | ||
| 56 | 56 | ||||
| 57 | mu sync.Mutex // guards tabs, active, client, clipFiles, and clipboard queue state | 57 | mu sync.Mutex // guards tabs, active, client, clipFiles, and clipboard queue state | ||
| 58 | + | themeMu sync.Mutex | |||
| 58 | tabs []*tab | 59 | tabs []*tab | ||
| 59 | active int | 60 | active int | ||
| 60 | client *attachedClient | 61 | client *attachedClient | ||
M internal/usecase/daemon/status_test.go +46 -11
| @@ -155,6 +155,31 @@ func TestStatusApplyThemeStoresClientAndPropagatesScreens(t *testing.T) { | |||
| 155 | 155 | assertSessionColorScheme(t, sess, true) | |
| 156 | 156 | } | |
| 157 | 157 | ||
| 158 | + | func TestApplyThemePropagatesToFloatingPane(t *testing.T) { | |
| 159 | + | p, releasePTY := newBlockingPTY(t) | |
| 160 | + | defer releasePTY() | |
| 161 | + | d, sess, ac, _ := newManualSessionWithPTYs(t, p) | |
| 162 | + | tb := sess.activeTab() | |
| 163 | + | floating := newPane("floating", nil, domain.Size{Cols: 20, Rows: 5}) | |
| 164 | + | installTestFloating(tb, floating, true) | |
| 165 | + | clientTheme := ports.Theme{ | |
| 166 | + | HasForeground: true, | |
| 167 | + | Foreground: renderer.RGB{R: 1, G: 2, B: 3}, | |
| 168 | + | HasBackground: true, | |
| 169 | + | Background: renderer.RGB{R: 4, G: 5, B: 6}, | |
| 170 | + | SchemeKnown: true, | |
| 171 | + | Light: true, | |
| 172 | + | } | |
| 173 | + | ||
| 174 | + | d.applyTheme(sess, ac, clientTheme) | |
| 175 | + | assertPaneDefaultColors(t, floating, clientTheme.Foreground, clientTheme.Background) | |
| 176 | + | assertPaneColorScheme(t, floating, true) | |
| 177 | + | ||
| 178 | + | d.ApplyConfig(domain.Config{Theme: domain.ThemeDark}) | |
| 179 | + | assertPaneDefaultColors(t, floating, themeui.BuiltinDark.Foreground, themeui.BuiltinDark.Background) | |
| 180 | + | assertPaneColorScheme(t, floating, false) | |
| 181 | + | } | |
| 182 | + | ||
| 158 | 183 | func TestApplyThemeForcedBuiltinThemePropagatesToChromeAndPanes(t *testing.T) { | |
| 159 | 184 | tests := []struct { | |
| 160 | 185 | name string | |
| @@ -297,26 +322,36 @@ func TestApplyThemeAutoUnknownDoesNotClobberPaneColorScheme(t *testing.T) { | |||
| 297 | 322 | func assertSessionDefaultColors(t *testing.T, sess *session, fg, bg renderer.RGB) { | |
| 298 | 323 | t.Helper() | |
| 299 | 324 | for _, tb := range sess.tabs { | |
| 300 | - | var got []byte | |
| 301 | - | tb.focusedPane().screen.OnResponse = func(b []byte) { got = append(got, b...) } | |
| 302 | - | tb.focusedPane().screen.Write([]byte("\x1b]10;?\a\x1b]11;?\a")) | |
| 303 | - | require.True(t, strings.Contains(string(got), formatOSCColor(fg)), string(got)) | |
| 304 | - | require.True(t, strings.Contains(string(got), formatOSCColor(bg)), string(got)) | |
| 325 | + | assertPaneDefaultColors(t, tb.focusedPane(), fg, bg) | |
| 305 | 326 | } | |
| 306 | 327 | } | |
| 307 | 328 | ||
| 329 | + | func assertPaneDefaultColors(t *testing.T, p *pane, fg, bg renderer.RGB) { | |
| 330 | + | t.Helper() | |
| 331 | + | var got []byte | |
| 332 | + | p.screen.OnResponse = func(b []byte) { got = append(got, b...) } | |
| 333 | + | p.screen.Write([]byte("\x1b]10;?\a\x1b]11;?\a")) | |
| 334 | + | require.True(t, strings.Contains(string(got), formatOSCColor(fg)), string(got)) | |
| 335 | + | require.True(t, strings.Contains(string(got), formatOSCColor(bg)), string(got)) | |
| 336 | + | } | |
| 337 | + | ||
| 308 | 338 | func assertSessionColorScheme(t *testing.T, sess *session, light bool) { | |
| 339 | + | t.Helper() | |
| 340 | + | for _, tb := range sess.tabs { | |
| 341 | + | assertPaneColorScheme(t, tb.focusedPane(), light) | |
| 342 | + | } | |
| 343 | + | } | |
| 344 | + | ||
| 345 | + | func assertPaneColorScheme(t *testing.T, p *pane, light bool) { | |
| 309 | 346 | t.Helper() | |
| 310 | 347 | want := "\x1b[?997;1n" | |
| 311 | 348 | if light { | |
| 312 | 349 | want = "\x1b[?997;2n" | |
| 313 | 350 | } | |
| 314 | - | for _, tb := range sess.tabs { | |
| 315 | - | var got []byte | |
| 316 | - | tb.focusedPane().screen.OnResponse = func(b []byte) { got = append(got, b...) } | |
| 317 | - | tb.focusedPane().screen.Write([]byte("\x1b[?996n")) | |
| 318 | - | require.Equal(t, want, string(got)) | |
| 319 | - | } | |
| 351 | + | var got []byte | |
| 352 | + | p.screen.OnResponse = func(b []byte) { got = append(got, b...) } | |
| 353 | + | p.screen.Write([]byte("\x1b[?996n")) | |
| 354 | + | require.Equal(t, want, string(got)) | |
| 320 | 355 | } | |
| 321 | 356 | ||
| 322 | 357 | func assertSessionColorSchemeUnknown(t *testing.T, sess *session) { | |
| @@ -155,6 +155,31 @@ func TestStatusApplyThemeStoresClientAndPropagatesScreens(t *testing.T) { | |||||
| 155 | assertSessionColorScheme(t, sess, true) | 155 | assertSessionColorScheme(t, sess, true) | ||
| 156 | } | 156 | } | ||
| 157 | 157 | ||||
| 158 | + | func TestApplyThemePropagatesToFloatingPane(t *testing.T) { | |||
| 159 | + | p, releasePTY := newBlockingPTY(t) | |||
| 160 | + | defer releasePTY() | |||
| 161 | + | d, sess, ac, _ := newManualSessionWithPTYs(t, p) | |||
| 162 | + | tb := sess.activeTab() | |||
| 163 | + | floating := newPane("floating", nil, domain.Size{Cols: 20, Rows: 5}) | |||
| 164 | + | installTestFloating(tb, floating, true) | |||
| 165 | + | clientTheme := ports.Theme{ | |||
| 166 | + | HasForeground: true, | |||
| 167 | + | Foreground: renderer.RGB{R: 1, G: 2, B: 3}, | |||
| 168 | + | HasBackground: true, | |||
| 169 | + | Background: renderer.RGB{R: 4, G: 5, B: 6}, | |||
| 170 | + | SchemeKnown: true, | |||
| 171 | + | Light: true, | |||
| 172 | + | } | |||
| 173 | + | ||||
| 174 | + | d.applyTheme(sess, ac, clientTheme) | |||
| 175 | + | assertPaneDefaultColors(t, floating, clientTheme.Foreground, clientTheme.Background) | |||
| 176 | + | assertPaneColorScheme(t, floating, true) | |||
| 177 | + | ||||
| 178 | + | d.ApplyConfig(domain.Config{Theme: domain.ThemeDark}) | |||
| 179 | + | assertPaneDefaultColors(t, floating, themeui.BuiltinDark.Foreground, themeui.BuiltinDark.Background) | |||
| 180 | + | assertPaneColorScheme(t, floating, false) | |||
| 181 | + | } | |||
| 182 | + | ||||
| 158 | func TestApplyThemeForcedBuiltinThemePropagatesToChromeAndPanes(t *testing.T) { | 183 | func TestApplyThemeForcedBuiltinThemePropagatesToChromeAndPanes(t *testing.T) { | ||
| 159 | tests := []struct { | 184 | tests := []struct { | ||
| 160 | name string | 185 | name string | ||
| @@ -297,26 +322,36 @@ func TestApplyThemeAutoUnknownDoesNotClobberPaneColorScheme(t *testing.T) { | |||||
| 297 | func assertSessionDefaultColors(t *testing.T, sess *session, fg, bg renderer.RGB) { | 322 | func assertSessionDefaultColors(t *testing.T, sess *session, fg, bg renderer.RGB) { | ||
| 298 | t.Helper() | 323 | t.Helper() | ||
| 299 | for _, tb := range sess.tabs { | 324 | for _, tb := range sess.tabs { | ||
| 300 | - | var got []byte | 325 | + | assertPaneDefaultColors(t, tb.focusedPane(), fg, bg) |
| 301 | - | tb.focusedPane().screen.OnResponse = func(b []byte) { got = append(got, b...) } | |||
| 302 | - | tb.focusedPane().screen.Write([]byte("\x1b]10;?\a\x1b]11;?\a")) | |||
| 303 | - | require.True(t, strings.Contains(string(got), formatOSCColor(fg)), string(got)) | |||
| 304 | - | require.True(t, strings.Contains(string(got), formatOSCColor(bg)), string(got)) | |||
| 305 | } | 326 | } | ||
| 306 | } | 327 | } | ||
| 307 | 328 | ||||
| 329 | + | func assertPaneDefaultColors(t *testing.T, p *pane, fg, bg renderer.RGB) { | |||
| 330 | + | t.Helper() | |||
| 331 | + | var got []byte | |||
| 332 | + | p.screen.OnResponse = func(b []byte) { got = append(got, b...) } | |||
| 333 | + | p.screen.Write([]byte("\x1b]10;?\a\x1b]11;?\a")) | |||
| 334 | + | require.True(t, strings.Contains(string(got), formatOSCColor(fg)), string(got)) | |||
| 335 | + | require.True(t, strings.Contains(string(got), formatOSCColor(bg)), string(got)) | |||
| 336 | + | } | |||
| 337 | + | ||||
| 308 | func assertSessionColorScheme(t *testing.T, sess *session, light bool) { | 338 | func assertSessionColorScheme(t *testing.T, sess *session, light bool) { | ||
| 339 | + | t.Helper() | |||
| 340 | + | for _, tb := range sess.tabs { | |||
| 341 | + | assertPaneColorScheme(t, tb.focusedPane(), light) | |||
| 342 | + | } | |||
| 343 | + | } | |||
| 344 | + | ||||
| 345 | + | func assertPaneColorScheme(t *testing.T, p *pane, light bool) { | |||
| 309 | t.Helper() | 346 | t.Helper() | ||
| 310 | want := "\x1b[?997;1n" | 347 | want := "\x1b[?997;1n" | ||
| 311 | if light { | 348 | if light { | ||
| 312 | want = "\x1b[?997;2n" | 349 | want = "\x1b[?997;2n" | ||
| 313 | } | 350 | } | ||
| 314 | - | for _, tb := range sess.tabs { | 351 | + | var got []byte |
| 315 | - | var got []byte | 352 | + | p.screen.OnResponse = func(b []byte) { got = append(got, b...) } |
| 316 | - | tb.focusedPane().screen.OnResponse = func(b []byte) { got = append(got, b...) } | 353 | + | p.screen.Write([]byte("\x1b[?996n")) |
| 317 | - | tb.focusedPane().screen.Write([]byte("\x1b[?996n")) | 354 | + | require.Equal(t, want, string(got)) |
| 318 | - | require.Equal(t, want, string(got)) | |||
| 319 | - | } | |||
| 320 | } | 355 | } | ||
| 321 | 356 | ||||
| 322 | func assertSessionColorSchemeUnknown(t *testing.T, sess *session) { | 357 | func assertSessionColorSchemeUnknown(t *testing.T, sess *session) { | ||
M internal/usecase/daemon/theme_resolve.go +8 -0
| @@ -7,7 +7,12 @@ func (d *Daemon) applyHostTheme(sess *session, ac *attachedClient, t theme.Theme | |||
| 7 | 7 | ac.sendMu.Lock() | |
| 8 | 8 | defer ac.sendMu.Unlock() | |
| 9 | 9 | } | |
| 10 | + | sess.themeMu.Lock() | |
| 11 | + | defer sess.themeMu.Unlock() | |
| 12 | + | return d.applyHostThemeLocked(sess, ac, t, clearUnknownScheme) | |
| 13 | + | } | |
| 10 | 14 | ||
| 15 | + | func (d *Daemon) applyHostThemeLocked(sess *session, ac *attachedClient, t theme.Theme, clearUnknownScheme bool) bool { | |
| 11 | 16 | sess.mu.Lock() | |
| 12 | 17 | if ac != nil { | |
| 13 | 18 | if sess.client != ac { | |
| @@ -28,6 +33,9 @@ func (d *Daemon) applyHostTheme(sess *session, ac *attachedClient, t theme.Theme | |||
| 28 | 33 | for _, tb := range tabs { | |
| 29 | 34 | tb.mu.Lock() | |
| 30 | 35 | panes := tb.panesSnapshot() | |
| 36 | + | if floating := tb.floating.pane; floating != nil { | |
| 37 | + | panes = append(panes, floating) | |
| 38 | + | } | |
| 31 | 39 | tb.mu.Unlock() | |
| 32 | 40 | for _, p := range panes { | |
| 33 | 41 | p.mu.Lock() | |
| @@ -7,7 +7,12 @@ func (d *Daemon) applyHostTheme(sess *session, ac *attachedClient, t theme.Theme | |||||
| 7 | ac.sendMu.Lock() | 7 | ac.sendMu.Lock() | ||
| 8 | defer ac.sendMu.Unlock() | 8 | defer ac.sendMu.Unlock() | ||
| 9 | } | 9 | } | ||
| 10 | + | sess.themeMu.Lock() | |||
| 11 | + | defer sess.themeMu.Unlock() | |||
| 12 | + | return d.applyHostThemeLocked(sess, ac, t, clearUnknownScheme) | |||
| 13 | + | } | |||
| 10 | 14 | ||||
| 15 | + | func (d *Daemon) applyHostThemeLocked(sess *session, ac *attachedClient, t theme.Theme, clearUnknownScheme bool) bool { | |||
| 11 | sess.mu.Lock() | 16 | sess.mu.Lock() | ||
| 12 | if ac != nil { | 17 | if ac != nil { | ||
| 13 | if sess.client != ac { | 18 | if sess.client != ac { | ||
| @@ -28,6 +33,9 @@ func (d *Daemon) applyHostTheme(sess *session, ac *attachedClient, t theme.Theme | |||||
| 28 | for _, tb := range tabs { | 33 | for _, tb := range tabs { | ||
| 29 | tb.mu.Lock() | 34 | tb.mu.Lock() | ||
| 30 | panes := tb.panesSnapshot() | 35 | panes := tb.panesSnapshot() | ||
| 36 | + | if floating := tb.floating.pane; floating != nil { | |||
| 37 | + | panes = append(panes, floating) | |||
| 38 | + | } | |||
| 31 | tb.mu.Unlock() | 39 | tb.mu.Unlock() | ||
| 32 | for _, p := range panes { | 40 | for _, p := range panes { | ||
| 33 | p.mu.Lock() | 41 | p.mu.Lock() | ||