refactor(copy): centralize close client recovery
- commit
- 306f37ec41528b566ca8a245b806f6c42412f6ed
- author
- brice · · 2026-07-11 07:14 UTC
- parent
- 36fe4a5
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +22 -12
M internal/usecase/daemon/copymode_lifecycle_test.go +17 -0
| @@ -65,6 +65,23 @@ func TestCopyModeLifecycleClosePaneClearsRecoveredClientState(t *testing.T) { | |||
| 65 | 65 | require.Nil(t, copyTargetPane(ac.overlays)) | |
| 66 | 66 | } | |
| 67 | 67 | ||
| 68 | + | func TestCopyModeLifecycleCloseOnlyPaneInTabClearsRecoveredClientState(t *testing.T) { | |
| 69 | + | d, sess, ac, _, releases := newManualTabSession(t, 2) | |
| 70 | + | defer func() { | |
| 71 | + | for _, release := range releases { | |
| 72 | + | release() | |
| 73 | + | } | |
| 74 | + | }() | |
| 75 | + | tb := sess.activeTab() | |
| 76 | + | p := tb.focusedPane() | |
| 77 | + | d.enterCopyMode(sess, ac) | |
| 78 | + | require.True(t, ac.overlays.copyActive()) | |
| 79 | + | ||
| 80 | + | require.NoError(t, d.closePane(sess, tb, p.id, nil, false)) | |
| 81 | + | require.False(t, ac.overlays.copyActive()) | |
| 82 | + | require.Nil(t, copyTargetPane(ac.overlays)) | |
| 83 | + | } | |
| 84 | + | ||
| 68 | 85 | func TestCopyModeLifecycleRejectsPublicationForInactiveOrRemovedPane(t *testing.T) { | |
| 69 | 86 | for _, tc := range []struct { | |
| 70 | 87 | name string | |
| @@ -65,6 +65,23 @@ func TestCopyModeLifecycleClosePaneClearsRecoveredClientState(t *testing.T) { | |||||
| 65 | require.Nil(t, copyTargetPane(ac.overlays)) | 65 | require.Nil(t, copyTargetPane(ac.overlays)) | ||
| 66 | } | 66 | } | ||
| 67 | 67 | ||||
| 68 | + | func TestCopyModeLifecycleCloseOnlyPaneInTabClearsRecoveredClientState(t *testing.T) { | |||
| 69 | + | d, sess, ac, _, releases := newManualTabSession(t, 2) | |||
| 70 | + | defer func() { | |||
| 71 | + | for _, release := range releases { | |||
| 72 | + | release() | |||
| 73 | + | } | |||
| 74 | + | }() | |||
| 75 | + | tb := sess.activeTab() | |||
| 76 | + | p := tb.focusedPane() | |||
| 77 | + | d.enterCopyMode(sess, ac) | |||
| 78 | + | require.True(t, ac.overlays.copyActive()) | |||
| 79 | + | ||||
| 80 | + | require.NoError(t, d.closePane(sess, tb, p.id, nil, false)) | |||
| 81 | + | require.False(t, ac.overlays.copyActive()) | |||
| 82 | + | require.Nil(t, copyTargetPane(ac.overlays)) | |||
| 83 | + | } | |||
| 84 | + | ||||
| 68 | func TestCopyModeLifecycleRejectsPublicationForInactiveOrRemovedPane(t *testing.T) { | 85 | func TestCopyModeLifecycleRejectsPublicationForInactiveOrRemovedPane(t *testing.T) { | ||
| 69 | for _, tc := range []struct { | 86 | for _, tc := range []struct { | ||
| 70 | name string | 87 | name string | ||
M internal/usecase/daemon/pane_actions.go +5 -12
| @@ -226,6 +226,11 @@ func (d *Daemon) closePane(sess *session, tb *tab, id layout.PaneID, ac *attache | |||
| 226 | 226 | if tb == nil { | |
| 227 | 227 | return layout.ErrNotFound | |
| 228 | 228 | } | |
| 229 | + | if ac == nil { | |
| 230 | + | sess.mu.Lock() | |
| 231 | + | ac = sess.client | |
| 232 | + | sess.mu.Unlock() | |
| 233 | + | } | |
| 229 | 234 | tb.mu.Lock() | |
| 230 | 235 | p := tb.panes[id] | |
| 231 | 236 | if p == nil || tb.tree == nil || !layout.ContainsLeaf(tb.tree.Root, id) { | |
| @@ -234,11 +239,6 @@ func (d *Daemon) closePane(sess *session, tb *tab, id layout.PaneID, ac *attache | |||
| 234 | 239 | } | |
| 235 | 240 | if len(layout.LeafIDs(tb.tree.Root)) <= 1 { | |
| 236 | 241 | tb.mu.Unlock() | |
| 237 | - | if ac == nil { | |
| 238 | - | sess.mu.Lock() | |
| 239 | - | ac = sess.client | |
| 240 | - | sess.mu.Unlock() | |
| 241 | - | } | |
| 242 | 242 | if ac != nil { | |
| 243 | 243 | ac.overlays.clearCopyModeForPane(p) | |
| 244 | 244 | } | |
| @@ -253,13 +253,6 @@ func (d *Daemon) closePane(sess *session, tb *tab, id layout.PaneID, ac *attache | |||
| 253 | 253 | d.applyLayoutLocked(tb) | |
| 254 | 254 | tb.mu.Unlock() | |
| 255 | 255 | ||
| 256 | - | // A reap/normal close can be called without the client argument. Recover | |
| 257 | - | // the attached client after releasing tab.mu so copyMu never overlaps it. | |
| 258 | - | if ac == nil { | |
| 259 | - | sess.mu.Lock() | |
| 260 | - | ac = sess.client | |
| 261 | - | sess.mu.Unlock() | |
| 262 | - | } | |
| 263 | 256 | if ac != nil { | |
| 264 | 257 | ac.overlays.clearCopyModeForPane(p) | |
| 265 | 258 | } | |
| @@ -226,6 +226,11 @@ func (d *Daemon) closePane(sess *session, tb *tab, id layout.PaneID, ac *attache | |||||
| 226 | if tb == nil { | 226 | if tb == nil { | ||
| 227 | return layout.ErrNotFound | 227 | return layout.ErrNotFound | ||
| 228 | } | 228 | } | ||
| 229 | + | if ac == nil { | |||
| 230 | + | sess.mu.Lock() | |||
| 231 | + | ac = sess.client | |||
| 232 | + | sess.mu.Unlock() | |||
| 233 | + | } | |||
| 229 | tb.mu.Lock() | 234 | tb.mu.Lock() | ||
| 230 | p := tb.panes[id] | 235 | p := tb.panes[id] | ||
| 231 | if p == nil || tb.tree == nil || !layout.ContainsLeaf(tb.tree.Root, id) { | 236 | if p == nil || tb.tree == nil || !layout.ContainsLeaf(tb.tree.Root, id) { | ||
| @@ -234,11 +239,6 @@ func (d *Daemon) closePane(sess *session, tb *tab, id layout.PaneID, ac *attache | |||||
| 234 | } | 239 | } | ||
| 235 | if len(layout.LeafIDs(tb.tree.Root)) <= 1 { | 240 | if len(layout.LeafIDs(tb.tree.Root)) <= 1 { | ||
| 236 | tb.mu.Unlock() | 241 | tb.mu.Unlock() | ||
| 237 | - | if ac == nil { | |||
| 238 | - | sess.mu.Lock() | |||
| 239 | - | ac = sess.client | |||
| 240 | - | sess.mu.Unlock() | |||
| 241 | - | } | |||
| 242 | if ac != nil { | 242 | if ac != nil { | ||
| 243 | ac.overlays.clearCopyModeForPane(p) | 243 | ac.overlays.clearCopyModeForPane(p) | ||
| 244 | } | 244 | } | ||
| @@ -253,13 +253,6 @@ func (d *Daemon) closePane(sess *session, tb *tab, id layout.PaneID, ac *attache | |||||
| 253 | d.applyLayoutLocked(tb) | 253 | d.applyLayoutLocked(tb) | ||
| 254 | tb.mu.Unlock() | 254 | tb.mu.Unlock() | ||
| 255 | 255 | ||||
| 256 | - | // A reap/normal close can be called without the client argument. Recover | |||
| 257 | - | // the attached client after releasing tab.mu so copyMu never overlaps it. | |||
| 258 | - | if ac == nil { | |||
| 259 | - | sess.mu.Lock() | |||
| 260 | - | ac = sess.client | |||
| 261 | - | sess.mu.Unlock() | |||
| 262 | - | } | |||
| 263 | if ac != nil { | 256 | if ac != nil { | ||
| 264 | ac.overlays.clearCopyModeForPane(p) | 257 | ac.overlays.clearCopyModeForPane(p) | ||
| 265 | } | 258 | } | ||