refactor(daemon): remove global active tab state
- commit
- 30c3dce9a1407d8a2af7132346cd5e331ee0982e
- author
- brice · · 2026-08-03 01:09 UTC
- committer
- brice · · 2026-08-03 20:53 UTC
- parent
- 409ae78
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
80 files · +579 -494
M internal/usecase/daemon/attachment_registry.go +37 -26
| @@ -404,12 +404,10 @@ func (s *session) tabIndexForAttachment(ac *attachedClient) (int, int) { | |||
| 404 | 404 | return 0, len(tabs) | |
| 405 | 405 | } | |
| 406 | 406 | ||
| 407 | - | func (s *session) tabForAttachmentOrActive(ac *attachedClient) *tab { | |
| 408 | - | if tb := s.tabForAttachment(ac); tb != nil { | |
| 409 | - | return tb | |
| 410 | - | } | |
| 411 | - | // Headless/session-wide callers have no attachment view. Use the first | |
| 412 | - | // shared tab rather than a session-wide active-tab singleton. | |
| 407 | + | // firstTab returns the first tab in the session's ordered tab collection. | |
| 408 | + | // It is a deterministic session-level default for headless work, never an | |
| 409 | + | // interactive attachment selection. | |
| 410 | + | func (s *session) firstTab() *tab { | |
| 413 | 411 | if s == nil { | |
| 414 | 412 | return nil | |
| 415 | 413 | } | |
| @@ -478,6 +476,20 @@ func (s *session) activateAttachmentViewLocked(ac *attachedClient, idx int) bool | |||
| 478 | 476 | return true | |
| 479 | 477 | } | |
| 480 | 478 | ||
| 479 | + | func (s *session) selectAttachmentTabLocked(ac *attachedClient, tabID domain.TabStableID) bool { | |
| 480 | + | if s == nil || ac == nil || !attachmentRegisteredLocked(s, ac) { | |
| 481 | + | return false | |
| 482 | + | } | |
| 483 | + | view := ac.viewSnapshot() | |
| 484 | + | view.tabID = tabID | |
| 485 | + | view.paneID = "" | |
| 486 | + | view = s.repairAttachmentViewLocked(ac, view) | |
| 487 | + | view.revision++ | |
| 488 | + | ac.publishView(view) | |
| 489 | + | s.invalidateViewsLocked() | |
| 490 | + | return view.tabID == tabID | |
| 491 | + | } | |
| 492 | + | ||
| 481 | 493 | func (s *session) selectAttachmentTab(ac *attachedClient, tabID domain.TabStableID) bool { | |
| 482 | 494 | if s == nil || ac == nil { | |
| 483 | 495 | return false | |
| @@ -486,28 +498,22 @@ func (s *session) selectAttachmentTab(ac *attachedClient, tabID domain.TabStable | |||
| 486 | 498 | _ = s.runMutation(func() error { | |
| 487 | 499 | s.mu.Lock() | |
| 488 | 500 | defer s.mu.Unlock() | |
| 489 | - | if _, ok := s.attachments[ac]; !ok { | |
| 490 | - | return nil | |
| 491 | - | } | |
| 492 | - | view := ac.viewSnapshot() | |
| 493 | - | view.tabID = tabID | |
| 494 | - | view.paneID = "" | |
| 495 | - | view = s.repairAttachmentViewLocked(ac, view) | |
| 496 | - | view.revision++ | |
| 497 | - | ac.publishView(view) | |
| 498 | - | changed = view.tabID == tabID | |
| 499 | - | s.invalidateViewsLocked() | |
| 501 | + | changed = s.selectAttachmentTabLocked(ac, tabID) | |
| 500 | 502 | return nil | |
| 501 | 503 | }) | |
| 502 | 504 | return changed | |
| 503 | 505 | } | |
| 504 | 506 | ||
| 505 | - | func (s *session) switchAttachmentRelative(ac *attachedClient, delta int) bool { | |
| 506 | - | position, count := s.tabIndexForAttachment(ac) | |
| 507 | - | if count < 2 { | |
| 507 | + | func (s *session) switchAttachmentTabForDispatch(ac *attachedClient, idx int) bool { | |
| 508 | + | if s == nil || ac == nil { | |
| 509 | + | return false | |
| 510 | + | } | |
| 511 | + | s.mu.Lock() | |
| 512 | + | defer s.mu.Unlock() | |
| 513 | + | if idx < 0 || idx >= len(s.tabs) { | |
| 508 | 514 | return false | |
| 509 | 515 | } | |
| 510 | - | return s.switchAttachmentTab(ac, (position+delta+count)%count) | |
| 516 | + | return s.selectAttachmentTabLocked(ac, domain.TabStableID(s.tabs[idx].stableID)) | |
| 511 | 517 | } | |
| 512 | 518 | ||
| 513 | 519 | func (s *session) switchAttachmentTab(ac *attachedClient, idx int) bool { | |
| @@ -515,17 +521,22 @@ func (s *session) switchAttachmentTab(ac *attachedClient, idx int) bool { | |||
| 515 | 521 | return false | |
| 516 | 522 | } | |
| 517 | 523 | s.mu.Lock() | |
| 518 | - | registered := false | |
| 519 | - | if s.attachments != nil { | |
| 520 | - | _, registered = s.attachments[ac] | |
| 521 | - | } | |
| 524 | + | registered := attachmentRegisteredLocked(s, ac) | |
| 522 | 525 | if registered && idx >= 0 && idx < len(s.tabs) { | |
| 523 | 526 | tabID := domain.TabStableID(s.tabs[idx].stableID) | |
| 524 | 527 | s.mu.Unlock() | |
| 525 | 528 | return s.selectAttachmentTab(ac, tabID) | |
| 526 | 529 | } | |
| 527 | 530 | s.mu.Unlock() | |
| 528 | - | return s.switchTab(idx) | |
| 531 | + | return false | |
| 532 | + | } | |
| 533 | + | ||
| 534 | + | func (s *session) switchAttachmentRelativeForDispatch(ac *attachedClient, delta int) bool { | |
| 535 | + | position, count := s.tabIndexForAttachment(ac) | |
| 536 | + | if count < 2 { | |
| 537 | + | return false | |
| 538 | + | } | |
| 539 | + | return s.switchAttachmentTabForDispatch(ac, (position+delta+count)%count) | |
| 529 | 540 | } | |
| 530 | 541 | ||
| 531 | 542 | // repairAttachmentViews repairs all live attachment targets after a shared | |
| @@ -404,12 +404,10 @@ func (s *session) tabIndexForAttachment(ac *attachedClient) (int, int) { | |||||
| 404 | return 0, len(tabs) | 404 | return 0, len(tabs) | ||
| 405 | } | 405 | } | ||
| 406 | 406 | ||||
| 407 | - | func (s *session) tabForAttachmentOrActive(ac *attachedClient) *tab { | 407 | + | // firstTab returns the first tab in the session's ordered tab collection. |
| 408 | - | if tb := s.tabForAttachment(ac); tb != nil { | 408 | + | // It is a deterministic session-level default for headless work, never an |
| 409 | - | return tb | 409 | + | // interactive attachment selection. |
| 410 | - | } | 410 | + | func (s *session) firstTab() *tab { |
| 411 | - | // Headless/session-wide callers have no attachment view. Use the first | |||
| 412 | - | // shared tab rather than a session-wide active-tab singleton. | |||
| 413 | if s == nil { | 411 | if s == nil { | ||
| 414 | return nil | 412 | return nil | ||
| 415 | } | 413 | } | ||
| @@ -478,6 +476,20 @@ func (s *session) activateAttachmentViewLocked(ac *attachedClient, idx int) bool | |||||
| 478 | return true | 476 | return true | ||
| 479 | } | 477 | } | ||
| 480 | 478 | ||||
| 479 | + | func (s *session) selectAttachmentTabLocked(ac *attachedClient, tabID domain.TabStableID) bool { | |||
| 480 | + | if s == nil || ac == nil || !attachmentRegisteredLocked(s, ac) { | |||
| 481 | + | return false | |||
| 482 | + | } | |||
| 483 | + | view := ac.viewSnapshot() | |||
| 484 | + | view.tabID = tabID | |||
| 485 | + | view.paneID = "" | |||
| 486 | + | view = s.repairAttachmentViewLocked(ac, view) | |||
| 487 | + | view.revision++ | |||
| 488 | + | ac.publishView(view) | |||
| 489 | + | s.invalidateViewsLocked() | |||
| 490 | + | return view.tabID == tabID | |||
| 491 | + | } | |||
| 492 | + | ||||
| 481 | func (s *session) selectAttachmentTab(ac *attachedClient, tabID domain.TabStableID) bool { | 493 | func (s *session) selectAttachmentTab(ac *attachedClient, tabID domain.TabStableID) bool { | ||
| 482 | if s == nil || ac == nil { | 494 | if s == nil || ac == nil { | ||
| 483 | return false | 495 | return false | ||
| @@ -486,28 +498,22 @@ func (s *session) selectAttachmentTab(ac *attachedClient, tabID domain.TabStable | |||||
| 486 | _ = s.runMutation(func() error { | 498 | _ = s.runMutation(func() error { | ||
| 487 | s.mu.Lock() | 499 | s.mu.Lock() | ||
| 488 | defer s.mu.Unlock() | 500 | defer s.mu.Unlock() | ||
| 489 | - | if _, ok := s.attachments[ac]; !ok { | 501 | + | changed = s.selectAttachmentTabLocked(ac, tabID) |
| 490 | - | return nil | |||
| 491 | - | } | |||
| 492 | - | view := ac.viewSnapshot() | |||
| 493 | - | view.tabID = tabID | |||
| 494 | - | view.paneID = "" | |||
| 495 | - | view = s.repairAttachmentViewLocked(ac, view) | |||
| 496 | - | view.revision++ | |||
| 497 | - | ac.publishView(view) | |||
| 498 | - | changed = view.tabID == tabID | |||
| 499 | - | s.invalidateViewsLocked() | |||
| 500 | return nil | 502 | return nil | ||
| 501 | }) | 503 | }) | ||
| 502 | return changed | 504 | return changed | ||
| 503 | } | 505 | } | ||
| 504 | 506 | ||||
| 505 | - | func (s *session) switchAttachmentRelative(ac *attachedClient, delta int) bool { | 507 | + | func (s *session) switchAttachmentTabForDispatch(ac *attachedClient, idx int) bool { |
| 506 | - | position, count := s.tabIndexForAttachment(ac) | 508 | + | if s == nil || ac == nil { |
| 507 | - | if count < 2 { | 509 | + | return false |
| 510 | + | } | |||
| 511 | + | s.mu.Lock() | |||
| 512 | + | defer s.mu.Unlock() | |||
| 513 | + | if idx < 0 || idx >= len(s.tabs) { | |||
| 508 | return false | 514 | return false | ||
| 509 | } | 515 | } | ||
| 510 | - | return s.switchAttachmentTab(ac, (position+delta+count)%count) | 516 | + | return s.selectAttachmentTabLocked(ac, domain.TabStableID(s.tabs[idx].stableID)) |
| 511 | } | 517 | } | ||
| 512 | 518 | ||||
| 513 | func (s *session) switchAttachmentTab(ac *attachedClient, idx int) bool { | 519 | func (s *session) switchAttachmentTab(ac *attachedClient, idx int) bool { | ||
| @@ -515,17 +521,22 @@ func (s *session) switchAttachmentTab(ac *attachedClient, idx int) bool { | |||||
| 515 | return false | 521 | return false | ||
| 516 | } | 522 | } | ||
| 517 | s.mu.Lock() | 523 | s.mu.Lock() | ||
| 518 | - | registered := false | 524 | + | registered := attachmentRegisteredLocked(s, ac) |
| 519 | - | if s.attachments != nil { | |||
| 520 | - | _, registered = s.attachments[ac] | |||
| 521 | - | } | |||
| 522 | if registered && idx >= 0 && idx < len(s.tabs) { | 525 | if registered && idx >= 0 && idx < len(s.tabs) { | ||
| 523 | tabID := domain.TabStableID(s.tabs[idx].stableID) | 526 | tabID := domain.TabStableID(s.tabs[idx].stableID) | ||
| 524 | s.mu.Unlock() | 527 | s.mu.Unlock() | ||
| 525 | return s.selectAttachmentTab(ac, tabID) | 528 | return s.selectAttachmentTab(ac, tabID) | ||
| 526 | } | 529 | } | ||
| 527 | s.mu.Unlock() | 530 | s.mu.Unlock() | ||
| 528 | - | return s.switchTab(idx) | 531 | + | return false |
| 532 | + | } | |||
| 533 | + | ||||
| 534 | + | func (s *session) switchAttachmentRelativeForDispatch(ac *attachedClient, delta int) bool { | |||
| 535 | + | position, count := s.tabIndexForAttachment(ac) | |||
| 536 | + | if count < 2 { | |||
| 537 | + | return false | |||
| 538 | + | } | |||
| 539 | + | return s.switchAttachmentTabForDispatch(ac, (position+delta+count)%count) | |||
| 529 | } | 540 | } | ||
| 530 | 541 | ||||
| 531 | // repairAttachmentViews repairs all live attachment targets after a shared | 542 | // repairAttachmentViews repairs all live attachment targets after a shared | ||
M internal/usecase/daemon/attachment_transition_prelocked_test.go +2 -2
| @@ -13,7 +13,7 @@ func TestValidateAttachmentTransitionPrelockedLeavesMembershipUntouchedOnFailure | |||
| 13 | 13 | ||
| 14 | 14 | d := newTestDaemon(t, nil, stubClock{}) | |
| 15 | 15 | source := &session{sessionCore: sessionCore{id: domain.SessionID("source")}} | |
| 16 | - | target := &session{sessionCore: sessionCore{id: domain.SessionID("target")}, active: 0} | |
| 16 | + | target := &session{sessionCore: sessionCore{id: domain.SessionID("target")}} | |
| 17 | 17 | next := &attachedClient{tr: &closeTrackingTransport{}} | |
| 18 | 18 | next.setSession(source) | |
| 19 | 19 | source.registerAttachmentLocked(next) | |
| @@ -58,5 +58,5 @@ func TestValidateAttachmentTransitionPrelockedLeavesMembershipUntouchedOnFailure | |||
| 58 | 58 | require.Same(t, source, next.currentSession()) | |
| 59 | 59 | require.Equal(t, uint64(0), next.roleGeneration.Load()) | |
| 60 | 60 | require.Equal(t, uint64(0), old.roleGeneration.Load()) | |
| 61 | - | require.Equal(t, 0, target.active) | |
| 61 | + | require.Equal(t, 0, testAttachmentTabIndex(target)) | |
| 62 | 62 | } | |
| @@ -13,7 +13,7 @@ func TestValidateAttachmentTransitionPrelockedLeavesMembershipUntouchedOnFailure | |||||
| 13 | 13 | ||||
| 14 | d := newTestDaemon(t, nil, stubClock{}) | 14 | d := newTestDaemon(t, nil, stubClock{}) | ||
| 15 | source := &session{sessionCore: sessionCore{id: domain.SessionID("source")}} | 15 | source := &session{sessionCore: sessionCore{id: domain.SessionID("source")}} | ||
| 16 | - | target := &session{sessionCore: sessionCore{id: domain.SessionID("target")}, active: 0} | 16 | + | target := &session{sessionCore: sessionCore{id: domain.SessionID("target")}} |
| 17 | next := &attachedClient{tr: &closeTrackingTransport{}} | 17 | next := &attachedClient{tr: &closeTrackingTransport{}} | ||
| 18 | next.setSession(source) | 18 | next.setSession(source) | ||
| 19 | source.registerAttachmentLocked(next) | 19 | source.registerAttachmentLocked(next) | ||
| @@ -58,5 +58,5 @@ func TestValidateAttachmentTransitionPrelockedLeavesMembershipUntouchedOnFailure | |||||
| 58 | require.Same(t, source, next.currentSession()) | 58 | require.Same(t, source, next.currentSession()) | ||
| 59 | require.Equal(t, uint64(0), next.roleGeneration.Load()) | 59 | require.Equal(t, uint64(0), next.roleGeneration.Load()) | ||
| 60 | require.Equal(t, uint64(0), old.roleGeneration.Load()) | 60 | require.Equal(t, uint64(0), old.roleGeneration.Load()) | ||
| 61 | - | require.Equal(t, 0, target.active) | 61 | + | require.Equal(t, 0, testAttachmentTabIndex(target)) |
| 62 | } | 62 | } | ||
M internal/usecase/daemon/attachment_transition_publish.go +1 -1
| @@ -165,7 +165,7 @@ func (d *Daemon) validateAttachmentTransitionPrelocked(req attachmentTransitionR | |||
| 165 | 165 | invalid = invalid || req.sourceToken == nil && source != req.target && req.expectedRole == attachmentActive && sourceCoordinator != nil && | |
| 166 | 166 | (sourceCoordinator.lease == nil || !sourceCoordinator.lease.active || sourceCoordinator.lease.attachment != req.next) | |
| 167 | 167 | invalid = invalid || !attachmentLifecycleCurrentLocked(req.target, req.expectedTargetLifecycle) | |
| 168 | - | invalid = invalid || req.activateTargetTab && !req.target.activateTargetLocked(req.targetTabIndex) | |
| 168 | + | invalid = invalid || req.activateTargetTab && !req.target.validTargetTabLocked(req.targetTabIndex) | |
| 169 | 169 | if invalid { | |
| 170 | 170 | publication.unlockCoordinators() | |
| 171 | 171 | return nil, errAttachmentTransition | |
| @@ -165,7 +165,7 @@ func (d *Daemon) validateAttachmentTransitionPrelocked(req attachmentTransitionR | |||||
| 165 | invalid = invalid || req.sourceToken == nil && source != req.target && req.expectedRole == attachmentActive && sourceCoordinator != nil && | 165 | invalid = invalid || req.sourceToken == nil && source != req.target && req.expectedRole == attachmentActive && sourceCoordinator != nil && | ||
| 166 | (sourceCoordinator.lease == nil || !sourceCoordinator.lease.active || sourceCoordinator.lease.attachment != req.next) | 166 | (sourceCoordinator.lease == nil || !sourceCoordinator.lease.active || sourceCoordinator.lease.attachment != req.next) | ||
| 167 | invalid = invalid || !attachmentLifecycleCurrentLocked(req.target, req.expectedTargetLifecycle) | 167 | invalid = invalid || !attachmentLifecycleCurrentLocked(req.target, req.expectedTargetLifecycle) | ||
| 168 | - | invalid = invalid || req.activateTargetTab && !req.target.activateTargetLocked(req.targetTabIndex) | 168 | + | invalid = invalid || req.activateTargetTab && !req.target.validTargetTabLocked(req.targetTabIndex) |
| 169 | if invalid { | 169 | if invalid { | ||
| 170 | publication.unlockCoordinators() | 170 | publication.unlockCoordinators() | ||
| 171 | return nil, errAttachmentTransition | 171 | return nil, errAttachmentTransition | ||
M internal/usecase/daemon/attention.go +1 -1
| @@ -59,7 +59,7 @@ func (d *Daemon) jumpAttentionForRole(sess *session, ac *attachedClient, token a | |||
| 59 | 59 | } | |
| 60 | 60 | if idx, ok := oldestAttentionTab(sess); ok { | |
| 61 | 61 | if sess.switchAttachmentTab(ac, idx) { | |
| 62 | - | d.activateTabAfterResizeForLease(sess, sess.tabForAttachmentOrActive(ac), false, ac, token.lease) | |
| 62 | + | d.activateTabAfterResizeForLease(sess, sess.tabForAttachment(ac), false, ac, token.lease) | |
| 63 | 63 | d.invalidateRender(sess, ac, true, "attention.go") | |
| 64 | 64 | } | |
| 65 | 65 | return nil | |
| @@ -59,7 +59,7 @@ func (d *Daemon) jumpAttentionForRole(sess *session, ac *attachedClient, token a | |||||
| 59 | } | 59 | } | ||
| 60 | if idx, ok := oldestAttentionTab(sess); ok { | 60 | if idx, ok := oldestAttentionTab(sess); ok { | ||
| 61 | if sess.switchAttachmentTab(ac, idx) { | 61 | if sess.switchAttachmentTab(ac, idx) { | ||
| 62 | - | d.activateTabAfterResizeForLease(sess, sess.tabForAttachmentOrActive(ac), false, ac, token.lease) | 62 | + | d.activateTabAfterResizeForLease(sess, sess.tabForAttachment(ac), false, ac, token.lease) |
| 63 | d.invalidateRender(sess, ac, true, "attention.go") | 63 | d.invalidateRender(sess, ac, true, "attention.go") | ||
| 64 | } | 64 | } | ||
| 65 | return nil | 65 | return nil | ||
M internal/usecase/daemon/attention_test.go +21 -21
| @@ -23,7 +23,7 @@ func TestNoteAttentionFlagsBackgroundTab(t *testing.T) { | |||
| 23 | 23 | d, sess, _, _, releases := newManualTabSession(t, 2) | |
| 24 | 24 | defer releases[0]() | |
| 25 | 25 | defer releases[1]() | |
| 26 | - | sess.active = 0 | |
| 26 | + | selectTestAttachmentTab(sess, 0) | |
| 27 | 27 | ||
| 28 | 28 | d.noteAttention(sess, sess.tabs[1]) | |
| 29 | 29 | ||
| @@ -37,7 +37,7 @@ func TestNoteAttentionFlagsBackgroundTab(t *testing.T) { | |||
| 37 | 37 | func TestNoteAttentionFlagsVisibleTabUntilPainted(t *testing.T) { | |
| 38 | 38 | d, sess, _, _, releases := newManualTabSession(t, 1) | |
| 39 | 39 | defer releases[0]() | |
| 40 | - | sess.active = 0 | |
| 40 | + | selectTestAttachmentTab(sess, 0) | |
| 41 | 41 | ||
| 42 | 42 | d.noteAttention(sess, sess.tabs[0]) | |
| 43 | 43 | ||
| @@ -50,7 +50,7 @@ func TestNoteAttentionFlagsVisibleTabUntilPainted(t *testing.T) { | |||
| 50 | 50 | func TestPaintDoesNotAckActiveAttentionOnBlankPulseFrame(t *testing.T) { | |
| 51 | 51 | d, sess, ac, sends, releases := newManualTabSession(t, 1) | |
| 52 | 52 | defer releases[0]() | |
| 53 | - | sess.active = 0 | |
| 53 | + | selectTestAttachmentTab(sess, 0) | |
| 54 | 54 | d.setAttentionFrame(0) | |
| 55 | 55 | d.noteAttention(sess, sess.tabs[0]) | |
| 56 | 56 | ||
| @@ -86,7 +86,7 @@ func TestPTYReaderActiveVisibleAgentNotificationRendersBellBeforeAck(t *testing. | |||
| 86 | 86 | t.Run(tt.name, func(t *testing.T) { | |
| 87 | 87 | pty := newScriptPTY([][]byte{tt.data}) | |
| 88 | 88 | d, sess, ac, sends := newManualSessionWithPTYs(t, pty) | |
| 89 | - | sess.active = 0 | |
| 89 | + | selectTestAttachmentTab(sess, 0) | |
| 90 | 90 | d.setAttentionFrame(1) | |
| 91 | 91 | ||
| 92 | 92 | d.sessWg.Add(1) | |
| @@ -119,7 +119,7 @@ func TestPTYReaderActiveVisibleAgentNotificationRendersBellBeforeAck(t *testing. | |||
| 119 | 119 | func TestNoteAttentionFlagsDetachedActiveTab(t *testing.T) { | |
| 120 | 120 | d, sess, ac, _, releases := newManualTabSession(t, 1) | |
| 121 | 121 | defer releases[0]() | |
| 122 | - | sess.active = 0 | |
| 122 | + | selectTestAttachmentTab(sess, 0) | |
| 123 | 123 | require.True(t, sess.detachIfCurrent(ac)) | |
| 124 | 124 | ||
| 125 | 125 | d.noteAttention(sess, sess.tabs[0]) | |
| @@ -135,7 +135,7 @@ func TestPTYReaderBellMarksBackgroundTab(t *testing.T) { | |||
| 135 | 135 | p2 := newScriptPTY([][]byte{[]byte("\a")}) | |
| 136 | 136 | d, sess, _, _ := newManualSessionWithPTYs(t, p1, p2) | |
| 137 | 137 | defer releasePTY1() | |
| 138 | - | sess.active = 0 | |
| 138 | + | selectTestAttachmentTab(sess, 0) | |
| 139 | 139 | ||
| 140 | 140 | d.sessWg.Add(1) | |
| 141 | 141 | go d.ptyReader(sess, sess.tabs[1], sess.tabs[1].focusedPane()) | |
| @@ -161,7 +161,7 @@ func TestNoteAttentionDoesNotBlockOnWedgedOtherClient(t *testing.T) { | |||
| 161 | 161 | d, sess, _, _, releases := newManualTabSession(t, 2) | |
| 162 | 162 | defer releases[0]() | |
| 163 | 163 | defer releases[1]() | |
| 164 | - | sess.active = 0 | |
| 164 | + | selectTestAttachmentTab(sess, 0) | |
| 165 | 165 | ||
| 166 | 166 | trW := portsmocks.NewMockTransport(t) | |
| 167 | 167 | block := make(chan struct{}) | |
| @@ -235,7 +235,7 @@ func TestAckAttentionClearsOnlyPaintedVisibleTab(t *testing.T) { | |||
| 235 | 235 | d, sess, ac, sends, releases := newManualTabSession(t, 2) | |
| 236 | 236 | defer releases[0]() | |
| 237 | 237 | defer releases[1]() | |
| 238 | - | sess.active = 0 | |
| 238 | + | selectTestAttachmentTab(sess, 0) | |
| 239 | 239 | sess.mu.Lock() | |
| 240 | 240 | sess.tabs[0].attention = true | |
| 241 | 241 | sess.tabs[0].attentionAt = time.Unix(1, 0) | |
| @@ -266,7 +266,7 @@ func TestPaintPreservesBackgroundAttention(t *testing.T) { | |||
| 266 | 266 | d, sess, ac, sends, releases := newManualTabSession(t, 2) | |
| 267 | 267 | defer releases[0]() | |
| 268 | 268 | defer releases[1]() | |
| 269 | - | sess.active = 0 | |
| 269 | + | selectTestAttachmentTab(sess, 0) | |
| 270 | 270 | sess.mu.Lock() | |
| 271 | 271 | sess.tabs[1].attention = true | |
| 272 | 272 | sess.tabs[1].attentionAt = time.Unix(2, 0) | |
| @@ -286,13 +286,13 @@ func TestSwitchTabClearsAttentionEndToEnd(t *testing.T) { | |||
| 286 | 286 | d, sess, ac, sends, releases := newManualTabSession(t, 2) | |
| 287 | 287 | defer releases[0]() | |
| 288 | 288 | defer releases[1]() | |
| 289 | - | sess.active = 0 | |
| 289 | + | selectTestAttachmentTab(sess, 0) | |
| 290 | 290 | sess.mu.Lock() | |
| 291 | 291 | sess.tabs[1].attention = true | |
| 292 | 292 | sess.tabs[1].attentionAt = time.Unix(2, 0) | |
| 293 | 293 | sess.mu.Unlock() | |
| 294 | 294 | ||
| 295 | - | require.True(t, sess.switchTab(1)) | |
| 295 | + | require.True(t, selectTestAttachmentTab(sess, 1)) | |
| 296 | 296 | d.paint(sess, ac, true, nil) | |
| 297 | 297 | data := mustOutputData(t, sends) | |
| 298 | 298 | // As above: the deferred all-clients repaint has nothing new to say about | |
| @@ -314,7 +314,7 @@ func TestCloseRingingTabClearsClientBar(t *testing.T) { | |||
| 314 | 314 | d, sess, ac, sends, releases := newManualTabSession(t, 2) | |
| 315 | 315 | defer releases[0]() | |
| 316 | 316 | defer releases[1]() | |
| 317 | - | sess.active = 0 | |
| 317 | + | selectTestAttachmentTab(sess, 0) | |
| 318 | 318 | sess.mu.Lock() | |
| 319 | 319 | ringing := sess.tabs[1] | |
| 320 | 320 | ringing.attention = true | |
| @@ -340,7 +340,7 @@ func TestAnimationRepaintConfinedToBarRows(t *testing.T) { | |||
| 340 | 340 | d, sess, ac, sends, releases := newManualTabSession(t, 2) | |
| 341 | 341 | defer releases[0]() | |
| 342 | 342 | defer releases[1]() | |
| 343 | - | sess.active = 0 | |
| 343 | + | selectTestAttachmentTab(sess, 0) | |
| 344 | 344 | sess.tabs[0].focusedPane().screen.Write([]byte("MIDSCREENMARKER")) | |
| 345 | 345 | sess.mu.Lock() | |
| 346 | 346 | sess.tabs[1].attention = true | |
| @@ -374,7 +374,7 @@ func TestComposeFrameAttentionFrameChangeDamagesOnlyBars(t *testing.T) { | |||
| 374 | 374 | d, sess, _, _, releases := newManualTabSession(t, 2) | |
| 375 | 375 | defer releases[0]() | |
| 376 | 376 | defer releases[1]() | |
| 377 | - | sess.active = 0 | |
| 377 | + | selectTestAttachmentTab(sess, 0) | |
| 378 | 378 | sess.mu.Lock() | |
| 379 | 379 | sess.tabs[1].attention = true | |
| 380 | 380 | sess.tabs[1].attentionAt = time.Unix(1, 0) | |
| @@ -471,7 +471,7 @@ func TestAltAJumpAttentionSelectsOldestLocalTab(t *testing.T) { | |||
| 471 | 471 | defer releases[0]() | |
| 472 | 472 | defer releases[1]() | |
| 473 | 473 | defer releases[2]() | |
| 474 | - | sess.active = 0 | |
| 474 | + | selectTestAttachmentTab(sess, 0) | |
| 475 | 475 | sess.mu.Lock() | |
| 476 | 476 | sess.tabs[1].attention = true | |
| 477 | 477 | sess.tabs[1].attentionAt = time.Unix(20, 0) | |
| @@ -481,8 +481,8 @@ func TestAltAJumpAttentionSelectsOldestLocalTab(t *testing.T) { | |||
| 481 | 481 | ||
| 482 | 482 | d.handleInput(sess, ac, []byte("\x1ba")) | |
| 483 | 483 | ||
| 484 | - | require.Equal(t, 2, activeTabIndex(sess)) | |
| 485 | - | requireFloatingInitialized(t, sess.activeTab()) | |
| 484 | + | require.Equal(t, 2, testAttachmentTabIndex(sess)) | |
| 485 | + | requireFloatingInitialized(t, testAttachmentTab(sess)) | |
| 486 | 486 | _ = mustOutputData(t, sends) | |
| 487 | 487 | } | |
| 488 | 488 | ||
| @@ -514,7 +514,7 @@ func TestJumpAttentionCrossesSessionsWhenNoLocalBells(t *testing.T) { | |||
| 514 | 514 | require.Same(t, sess2, ac.currentSession()) | |
| 515 | 515 | require.Contains(t, sess2.snapshotAttachments(), ac) | |
| 516 | 516 | require.Empty(t, sess1.snapshotAttachments()) | |
| 517 | - | require.Equal(t, 1, activeTabIndex(sess2)) | |
| 517 | + | require.Equal(t, 1, testAttachmentTabIndex(sess2)) | |
| 518 | 518 | _ = mustOutputData(t, sends) | |
| 519 | 519 | } | |
| 520 | 520 | ||
| @@ -522,12 +522,12 @@ func TestJumpAttentionNoopsWithNoBells(t *testing.T) { | |||
| 522 | 522 | d, sess, ac, sends, releases := newManualTabSession(t, 2) | |
| 523 | 523 | defer releases[0]() | |
| 524 | 524 | defer releases[1]() | |
| 525 | - | sess.active = 0 | |
| 525 | + | selectTestAttachmentTab(sess, 0) | |
| 526 | 526 | ||
| 527 | 527 | require.NoError(t, d.jumpAttention(sess, ac), "no target to jump to is not a failure") | |
| 528 | 528 | ||
| 529 | 529 | require.Same(t, sess, ac.currentSession()) | |
| 530 | - | require.Equal(t, 0, activeTabIndex(sess)) | |
| 530 | + | require.Equal(t, 0, testAttachmentTabIndex(sess)) | |
| 531 | 531 | select { | |
| 532 | 532 | case f := <-sends: | |
| 533 | 533 | t.Fatalf("unexpected frame on no-op jump: %v", f.Type) | |
| @@ -576,7 +576,7 @@ func TestAttentionAnimatorParksAdvancesAndResets(t *testing.T) { | |||
| 576 | 576 | defer releases[1]() | |
| 577 | 577 | clk := newManualAttentionClock() | |
| 578 | 578 | d.clock = clk | |
| 579 | - | sess.active = 0 | |
| 579 | + | selectTestAttachmentTab(sess, 0) | |
| 580 | 580 | ||
| 581 | 581 | ctx, cancel := context.WithCancel(context.Background()) | |
| 582 | 582 | done := make(chan struct{}) | |
| @@ -23,7 +23,7 @@ func TestNoteAttentionFlagsBackgroundTab(t *testing.T) { | |||||
| 23 | d, sess, _, _, releases := newManualTabSession(t, 2) | 23 | d, sess, _, _, releases := newManualTabSession(t, 2) | ||
| 24 | defer releases[0]() | 24 | defer releases[0]() | ||
| 25 | defer releases[1]() | 25 | defer releases[1]() | ||
| 26 | - | sess.active = 0 | 26 | + | selectTestAttachmentTab(sess, 0) |
| 27 | 27 | ||||
| 28 | d.noteAttention(sess, sess.tabs[1]) | 28 | d.noteAttention(sess, sess.tabs[1]) | ||
| 29 | 29 | ||||
| @@ -37,7 +37,7 @@ func TestNoteAttentionFlagsBackgroundTab(t *testing.T) { | |||||
| 37 | func TestNoteAttentionFlagsVisibleTabUntilPainted(t *testing.T) { | 37 | func TestNoteAttentionFlagsVisibleTabUntilPainted(t *testing.T) { | ||
| 38 | d, sess, _, _, releases := newManualTabSession(t, 1) | 38 | d, sess, _, _, releases := newManualTabSession(t, 1) | ||
| 39 | defer releases[0]() | 39 | defer releases[0]() | ||
| 40 | - | sess.active = 0 | 40 | + | selectTestAttachmentTab(sess, 0) |
| 41 | 41 | ||||
| 42 | d.noteAttention(sess, sess.tabs[0]) | 42 | d.noteAttention(sess, sess.tabs[0]) | ||
| 43 | 43 | ||||
| @@ -50,7 +50,7 @@ func TestNoteAttentionFlagsVisibleTabUntilPainted(t *testing.T) { | |||||
| 50 | func TestPaintDoesNotAckActiveAttentionOnBlankPulseFrame(t *testing.T) { | 50 | func TestPaintDoesNotAckActiveAttentionOnBlankPulseFrame(t *testing.T) { | ||
| 51 | d, sess, ac, sends, releases := newManualTabSession(t, 1) | 51 | d, sess, ac, sends, releases := newManualTabSession(t, 1) | ||
| 52 | defer releases[0]() | 52 | defer releases[0]() | ||
| 53 | - | sess.active = 0 | 53 | + | selectTestAttachmentTab(sess, 0) |
| 54 | d.setAttentionFrame(0) | 54 | d.setAttentionFrame(0) | ||
| 55 | d.noteAttention(sess, sess.tabs[0]) | 55 | d.noteAttention(sess, sess.tabs[0]) | ||
| 56 | 56 | ||||
| @@ -86,7 +86,7 @@ func TestPTYReaderActiveVisibleAgentNotificationRendersBellBeforeAck(t *testing. | |||||
| 86 | t.Run(tt.name, func(t *testing.T) { | 86 | t.Run(tt.name, func(t *testing.T) { | ||
| 87 | pty := newScriptPTY([][]byte{tt.data}) | 87 | pty := newScriptPTY([][]byte{tt.data}) | ||
| 88 | d, sess, ac, sends := newManualSessionWithPTYs(t, pty) | 88 | d, sess, ac, sends := newManualSessionWithPTYs(t, pty) | ||
| 89 | - | sess.active = 0 | 89 | + | selectTestAttachmentTab(sess, 0) |
| 90 | d.setAttentionFrame(1) | 90 | d.setAttentionFrame(1) | ||
| 91 | 91 | ||||
| 92 | d.sessWg.Add(1) | 92 | d.sessWg.Add(1) | ||
| @@ -119,7 +119,7 @@ func TestPTYReaderActiveVisibleAgentNotificationRendersBellBeforeAck(t *testing. | |||||
| 119 | func TestNoteAttentionFlagsDetachedActiveTab(t *testing.T) { | 119 | func TestNoteAttentionFlagsDetachedActiveTab(t *testing.T) { | ||
| 120 | d, sess, ac, _, releases := newManualTabSession(t, 1) | 120 | d, sess, ac, _, releases := newManualTabSession(t, 1) | ||
| 121 | defer releases[0]() | 121 | defer releases[0]() | ||
| 122 | - | sess.active = 0 | 122 | + | selectTestAttachmentTab(sess, 0) |
| 123 | require.True(t, sess.detachIfCurrent(ac)) | 123 | require.True(t, sess.detachIfCurrent(ac)) | ||
| 124 | 124 | ||||
| 125 | d.noteAttention(sess, sess.tabs[0]) | 125 | d.noteAttention(sess, sess.tabs[0]) | ||
| @@ -135,7 +135,7 @@ func TestPTYReaderBellMarksBackgroundTab(t *testing.T) { | |||||
| 135 | p2 := newScriptPTY([][]byte{[]byte("\a")}) | 135 | p2 := newScriptPTY([][]byte{[]byte("\a")}) | ||
| 136 | d, sess, _, _ := newManualSessionWithPTYs(t, p1, p2) | 136 | d, sess, _, _ := newManualSessionWithPTYs(t, p1, p2) | ||
| 137 | defer releasePTY1() | 137 | defer releasePTY1() | ||
| 138 | - | sess.active = 0 | 138 | + | selectTestAttachmentTab(sess, 0) |
| 139 | 139 | ||||
| 140 | d.sessWg.Add(1) | 140 | d.sessWg.Add(1) | ||
| 141 | go d.ptyReader(sess, sess.tabs[1], sess.tabs[1].focusedPane()) | 141 | go d.ptyReader(sess, sess.tabs[1], sess.tabs[1].focusedPane()) | ||
| @@ -161,7 +161,7 @@ func TestNoteAttentionDoesNotBlockOnWedgedOtherClient(t *testing.T) { | |||||
| 161 | d, sess, _, _, releases := newManualTabSession(t, 2) | 161 | d, sess, _, _, releases := newManualTabSession(t, 2) | ||
| 162 | defer releases[0]() | 162 | defer releases[0]() | ||
| 163 | defer releases[1]() | 163 | defer releases[1]() | ||
| 164 | - | sess.active = 0 | 164 | + | selectTestAttachmentTab(sess, 0) |
| 165 | 165 | ||||
| 166 | trW := portsmocks.NewMockTransport(t) | 166 | trW := portsmocks.NewMockTransport(t) | ||
| 167 | block := make(chan struct{}) | 167 | block := make(chan struct{}) | ||
| @@ -235,7 +235,7 @@ func TestAckAttentionClearsOnlyPaintedVisibleTab(t *testing.T) { | |||||
| 235 | d, sess, ac, sends, releases := newManualTabSession(t, 2) | 235 | d, sess, ac, sends, releases := newManualTabSession(t, 2) | ||
| 236 | defer releases[0]() | 236 | defer releases[0]() | ||
| 237 | defer releases[1]() | 237 | defer releases[1]() | ||
| 238 | - | sess.active = 0 | 238 | + | selectTestAttachmentTab(sess, 0) |
| 239 | sess.mu.Lock() | 239 | sess.mu.Lock() | ||
| 240 | sess.tabs[0].attention = true | 240 | sess.tabs[0].attention = true | ||
| 241 | sess.tabs[0].attentionAt = time.Unix(1, 0) | 241 | sess.tabs[0].attentionAt = time.Unix(1, 0) | ||
| @@ -266,7 +266,7 @@ func TestPaintPreservesBackgroundAttention(t *testing.T) { | |||||
| 266 | d, sess, ac, sends, releases := newManualTabSession(t, 2) | 266 | d, sess, ac, sends, releases := newManualTabSession(t, 2) | ||
| 267 | defer releases[0]() | 267 | defer releases[0]() | ||
| 268 | defer releases[1]() | 268 | defer releases[1]() | ||
| 269 | - | sess.active = 0 | 269 | + | selectTestAttachmentTab(sess, 0) |
| 270 | sess.mu.Lock() | 270 | sess.mu.Lock() | ||
| 271 | sess.tabs[1].attention = true | 271 | sess.tabs[1].attention = true | ||
| 272 | sess.tabs[1].attentionAt = time.Unix(2, 0) | 272 | sess.tabs[1].attentionAt = time.Unix(2, 0) | ||
| @@ -286,13 +286,13 @@ func TestSwitchTabClearsAttentionEndToEnd(t *testing.T) { | |||||
| 286 | d, sess, ac, sends, releases := newManualTabSession(t, 2) | 286 | d, sess, ac, sends, releases := newManualTabSession(t, 2) | ||
| 287 | defer releases[0]() | 287 | defer releases[0]() | ||
| 288 | defer releases[1]() | 288 | defer releases[1]() | ||
| 289 | - | sess.active = 0 | 289 | + | selectTestAttachmentTab(sess, 0) |
| 290 | sess.mu.Lock() | 290 | sess.mu.Lock() | ||
| 291 | sess.tabs[1].attention = true | 291 | sess.tabs[1].attention = true | ||
| 292 | sess.tabs[1].attentionAt = time.Unix(2, 0) | 292 | sess.tabs[1].attentionAt = time.Unix(2, 0) | ||
| 293 | sess.mu.Unlock() | 293 | sess.mu.Unlock() | ||
| 294 | 294 | ||||
| 295 | - | require.True(t, sess.switchTab(1)) | 295 | + | require.True(t, selectTestAttachmentTab(sess, 1)) |
| 296 | d.paint(sess, ac, true, nil) | 296 | d.paint(sess, ac, true, nil) | ||
| 297 | data := mustOutputData(t, sends) | 297 | data := mustOutputData(t, sends) | ||
| 298 | // As above: the deferred all-clients repaint has nothing new to say about | 298 | // As above: the deferred all-clients repaint has nothing new to say about | ||
| @@ -314,7 +314,7 @@ func TestCloseRingingTabClearsClientBar(t *testing.T) { | |||||
| 314 | d, sess, ac, sends, releases := newManualTabSession(t, 2) | 314 | d, sess, ac, sends, releases := newManualTabSession(t, 2) | ||
| 315 | defer releases[0]() | 315 | defer releases[0]() | ||
| 316 | defer releases[1]() | 316 | defer releases[1]() | ||
| 317 | - | sess.active = 0 | 317 | + | selectTestAttachmentTab(sess, 0) |
| 318 | sess.mu.Lock() | 318 | sess.mu.Lock() | ||
| 319 | ringing := sess.tabs[1] | 319 | ringing := sess.tabs[1] | ||
| 320 | ringing.attention = true | 320 | ringing.attention = true | ||
| @@ -340,7 +340,7 @@ func TestAnimationRepaintConfinedToBarRows(t *testing.T) { | |||||
| 340 | d, sess, ac, sends, releases := newManualTabSession(t, 2) | 340 | d, sess, ac, sends, releases := newManualTabSession(t, 2) | ||
| 341 | defer releases[0]() | 341 | defer releases[0]() | ||
| 342 | defer releases[1]() | 342 | defer releases[1]() | ||
| 343 | - | sess.active = 0 | 343 | + | selectTestAttachmentTab(sess, 0) |
| 344 | sess.tabs[0].focusedPane().screen.Write([]byte("MIDSCREENMARKER")) | 344 | sess.tabs[0].focusedPane().screen.Write([]byte("MIDSCREENMARKER")) | ||
| 345 | sess.mu.Lock() | 345 | sess.mu.Lock() | ||
| 346 | sess.tabs[1].attention = true | 346 | sess.tabs[1].attention = true | ||
| @@ -374,7 +374,7 @@ func TestComposeFrameAttentionFrameChangeDamagesOnlyBars(t *testing.T) { | |||||
| 374 | d, sess, _, _, releases := newManualTabSession(t, 2) | 374 | d, sess, _, _, releases := newManualTabSession(t, 2) | ||
| 375 | defer releases[0]() | 375 | defer releases[0]() | ||
| 376 | defer releases[1]() | 376 | defer releases[1]() | ||
| 377 | - | sess.active = 0 | 377 | + | selectTestAttachmentTab(sess, 0) |
| 378 | sess.mu.Lock() | 378 | sess.mu.Lock() | ||
| 379 | sess.tabs[1].attention = true | 379 | sess.tabs[1].attention = true | ||
| 380 | sess.tabs[1].attentionAt = time.Unix(1, 0) | 380 | sess.tabs[1].attentionAt = time.Unix(1, 0) | ||
| @@ -471,7 +471,7 @@ func TestAltAJumpAttentionSelectsOldestLocalTab(t *testing.T) { | |||||
| 471 | defer releases[0]() | 471 | defer releases[0]() | ||
| 472 | defer releases[1]() | 472 | defer releases[1]() | ||
| 473 | defer releases[2]() | 473 | defer releases[2]() | ||
| 474 | - | sess.active = 0 | 474 | + | selectTestAttachmentTab(sess, 0) |
| 475 | sess.mu.Lock() | 475 | sess.mu.Lock() | ||
| 476 | sess.tabs[1].attention = true | 476 | sess.tabs[1].attention = true | ||
| 477 | sess.tabs[1].attentionAt = time.Unix(20, 0) | 477 | sess.tabs[1].attentionAt = time.Unix(20, 0) | ||
| @@ -481,8 +481,8 @@ func TestAltAJumpAttentionSelectsOldestLocalTab(t *testing.T) { | |||||
| 481 | 481 | ||||
| 482 | d.handleInput(sess, ac, []byte("\x1ba")) | 482 | d.handleInput(sess, ac, []byte("\x1ba")) | ||
| 483 | 483 | ||||
| 484 | - | require.Equal(t, 2, activeTabIndex(sess)) | 484 | + | require.Equal(t, 2, testAttachmentTabIndex(sess)) |
| 485 | - | requireFloatingInitialized(t, sess.activeTab()) | 485 | + | requireFloatingInitialized(t, testAttachmentTab(sess)) |
| 486 | _ = mustOutputData(t, sends) | 486 | _ = mustOutputData(t, sends) | ||
| 487 | } | 487 | } | ||
| 488 | 488 | ||||
| @@ -514,7 +514,7 @@ func TestJumpAttentionCrossesSessionsWhenNoLocalBells(t *testing.T) { | |||||
| 514 | require.Same(t, sess2, ac.currentSession()) | 514 | require.Same(t, sess2, ac.currentSession()) | ||
| 515 | require.Contains(t, sess2.snapshotAttachments(), ac) | 515 | require.Contains(t, sess2.snapshotAttachments(), ac) | ||
| 516 | require.Empty(t, sess1.snapshotAttachments()) | 516 | require.Empty(t, sess1.snapshotAttachments()) | ||
| 517 | - | require.Equal(t, 1, activeTabIndex(sess2)) | 517 | + | require.Equal(t, 1, testAttachmentTabIndex(sess2)) |
| 518 | _ = mustOutputData(t, sends) | 518 | _ = mustOutputData(t, sends) | ||
| 519 | } | 519 | } | ||
| 520 | 520 | ||||
| @@ -522,12 +522,12 @@ func TestJumpAttentionNoopsWithNoBells(t *testing.T) { | |||||
| 522 | d, sess, ac, sends, releases := newManualTabSession(t, 2) | 522 | d, sess, ac, sends, releases := newManualTabSession(t, 2) | ||
| 523 | defer releases[0]() | 523 | defer releases[0]() | ||
| 524 | defer releases[1]() | 524 | defer releases[1]() | ||
| 525 | - | sess.active = 0 | 525 | + | selectTestAttachmentTab(sess, 0) |
| 526 | 526 | ||||
| 527 | require.NoError(t, d.jumpAttention(sess, ac), "no target to jump to is not a failure") | 527 | require.NoError(t, d.jumpAttention(sess, ac), "no target to jump to is not a failure") | ||
| 528 | 528 | ||||
| 529 | require.Same(t, sess, ac.currentSession()) | 529 | require.Same(t, sess, ac.currentSession()) | ||
| 530 | - | require.Equal(t, 0, activeTabIndex(sess)) | 530 | + | require.Equal(t, 0, testAttachmentTabIndex(sess)) |
| 531 | select { | 531 | select { | ||
| 532 | case f := <-sends: | 532 | case f := <-sends: | ||
| 533 | t.Fatalf("unexpected frame on no-op jump: %v", f.Type) | 533 | t.Fatalf("unexpected frame on no-op jump: %v", f.Type) | ||
| @@ -576,7 +576,7 @@ func TestAttentionAnimatorParksAdvancesAndResets(t *testing.T) { | |||||
| 576 | defer releases[1]() | 576 | defer releases[1]() | ||
| 577 | clk := newManualAttentionClock() | 577 | clk := newManualAttentionClock() | ||
| 578 | d.clock = clk | 578 | d.clock = clk | ||
| 579 | - | sess.active = 0 | 579 | + | selectTestAttachmentTab(sess, 0) |
| 580 | 580 | ||||
| 581 | ctx, cancel := context.WithCancel(context.Background()) | 581 | ctx, cancel := context.WithCancel(context.Background()) | ||
| 582 | done := make(chan struct{}) | 582 | done := make(chan struct{}) | ||
M internal/usecase/daemon/bar_refresh.go +9 -2
| @@ -125,7 +125,7 @@ func (d *Daemon) collectBarScriptContext(sess *session, anchor string) (barScrip | |||
| 125 | 125 | attachments := sess.snapshotAttachmentsLocked() | |
| 126 | 126 | ctx.PaneCWD = sess.cwd | |
| 127 | 127 | sess.mu.Unlock() | |
| 128 | - | tb := sess.tabForAttachmentOrActive(nil) | |
| 128 | + | tb := sess.firstTab() | |
| 129 | 129 | if len(attachments) == 0 || tb == nil { | |
| 130 | 130 | return ctx, env, false | |
| 131 | 131 | } | |
| @@ -412,7 +412,14 @@ func (d *Daemon) pokeSessionRender(sess *session) { | |||
| 412 | 412 | // A headless session can still supply a picker preview to an attachment | |
| 413 | 413 | // owned by another session. Its coordinator fans this wake out to those | |
| 414 | 414 | // preview subscribers even though no primary attachment is bound. | |
| 415 | - | for _, ac := range sess.snapshotAttachments() { | |
| 415 | + | attachments := sess.snapshotAttachments() | |
| 416 | + | if len(attachments) == 0 { | |
| 417 | + | if rc := sess.renderCoordinator(); rc != nil { | |
| 418 | + | rc.invalidate(renderInvalidation{class: invalidateUrgent, producer: "bar_refresh.go"}) | |
| 419 | + | } | |
| 420 | + | return | |
| 421 | + | } | |
| 422 | + | for _, ac := range attachments { | |
| 416 | 423 | d.invalidateRender(sess, ac, false, "bar_refresh.go") | |
| 417 | 424 | } | |
| 418 | 425 | } | |
| @@ -125,7 +125,7 @@ func (d *Daemon) collectBarScriptContext(sess *session, anchor string) (barScrip | |||||
| 125 | attachments := sess.snapshotAttachmentsLocked() | 125 | attachments := sess.snapshotAttachmentsLocked() | ||
| 126 | ctx.PaneCWD = sess.cwd | 126 | ctx.PaneCWD = sess.cwd | ||
| 127 | sess.mu.Unlock() | 127 | sess.mu.Unlock() | ||
| 128 | - | tb := sess.tabForAttachmentOrActive(nil) | 128 | + | tb := sess.firstTab() |
| 129 | if len(attachments) == 0 || tb == nil { | 129 | if len(attachments) == 0 || tb == nil { | ||
| 130 | return ctx, env, false | 130 | return ctx, env, false | ||
| 131 | } | 131 | } | ||
| @@ -412,7 +412,14 @@ func (d *Daemon) pokeSessionRender(sess *session) { | |||||
| 412 | // A headless session can still supply a picker preview to an attachment | 412 | // A headless session can still supply a picker preview to an attachment | ||
| 413 | // owned by another session. Its coordinator fans this wake out to those | 413 | // owned by another session. Its coordinator fans this wake out to those | ||
| 414 | // preview subscribers even though no primary attachment is bound. | 414 | // preview subscribers even though no primary attachment is bound. | ||
| 415 | - | for _, ac := range sess.snapshotAttachments() { | 415 | + | attachments := sess.snapshotAttachments() |
| 416 | + | if len(attachments) == 0 { | |||
| 417 | + | if rc := sess.renderCoordinator(); rc != nil { | |||
| 418 | + | rc.invalidate(renderInvalidation{class: invalidateUrgent, producer: "bar_refresh.go"}) | |||
| 419 | + | } | |||
| 420 | + | return | |||
| 421 | + | } | |||
| 422 | + | for _, ac := range attachments { | |||
| 416 | d.invalidateRender(sess, ac, false, "bar_refresh.go") | 423 | d.invalidateRender(sess, ac, false, "bar_refresh.go") | ||
| 417 | } | 424 | } | ||
| 418 | } | 425 | } | ||
M internal/usecase/daemon/bar_refresh_test.go +7 -8
| @@ -85,17 +85,16 @@ func TestBarScriptRefreshIntervalClampingTickAndLastGoodOnFailure(t *testing.T) | |||
| 85 | 85 | require.Equal(t, "bot2", state.bottomRight) | |
| 86 | 86 | } | |
| 87 | 87 | ||
| 88 | - | func TestBarScriptContextUsesActivePaneSessionAndClientCols(t *testing.T) { | |
| 88 | + | func TestBarScriptContextUsesFirstOrderedPaneAndClientCols(t *testing.T) { | |
| 89 | 89 | r := &fakeBarRunner{outs: []string{"top", "bottom"}} | |
| 90 | 90 | d := newBarRefreshTestDaemon(r, time.Second) | |
| 91 | 91 | sess := newBarRefreshTestSession() | |
| 92 | 92 | sess.name = "work" | |
| 93 | 93 | sess.cwd = "/repo" | |
| 94 | 94 | sess.registerAttachment(&attachedClient{size: domain.Size{Cols: 132, Rows: 40}}) | |
| 95 | - | sess.active = 1 | |
| 96 | - | sess.tabs[1].size = domain.Size{Cols: 132, Rows: 38} | |
| 97 | - | active := sess.tabs[1].focusedPane() | |
| 98 | - | active.stableID = "pane-active" | |
| 95 | + | sess.tabs[0].size = domain.Size{Cols: 132, Rows: 38} | |
| 96 | + | active := sess.tabs[0].focusedPane() | |
| 97 | + | active.stableID = "pane-default" | |
| 99 | 98 | active.pty = newScriptPTY(nil) | |
| 100 | 99 | d.procCwd = func(pid int) (string, error) { | |
| 101 | 100 | require.Equal(t, 4242, pid) | |
| @@ -107,8 +106,8 @@ func TestBarScriptContextUsesActivePaneSessionAndClientCols(t *testing.T) { | |||
| 107 | 106 | require.Len(t, r.calls, 2) | |
| 108 | 107 | for _, call := range r.calls { | |
| 109 | 108 | require.Equal(t, "work", call.Session) | |
| 110 | - | require.Equal(t, "tab-active", call.Tab) | |
| 111 | - | require.Equal(t, "pane-active", call.Pane) | |
| 109 | + | require.Equal(t, "tab-inactive", call.Tab) | |
| 110 | + | require.Equal(t, "pane-default", call.Pane) | |
| 112 | 111 | require.Equal(t, "/pane-repo", call.PaneCWD) | |
| 113 | 112 | require.Equal(t, 132, call.Cols) | |
| 114 | 113 | } | |
| @@ -587,7 +586,7 @@ func newBarRefreshTestSession() *session { | |||
| 587 | 586 | inactive := newTabWithStableID("tab-inactive", "pane-inactive", nil, domain.Size{Cols: 80, Rows: 22}) | |
| 588 | 587 | active := newTabWithStableID("tab-active", "pane-active", nil, domain.Size{Cols: 80, Rows: 22}) | |
| 589 | 588 | active.tree.Focus = layout.PaneID("pane-1") | |
| 590 | - | return &session{sessionCore: sessionCore{id: "s", name: "s"}, cwd: "/tmp", tabs: []*tab{inactive, active}, active: 0, ctx: context.Background()} | |
| 589 | + | return &session{sessionCore: sessionCore{id: "s", name: "s"}, cwd: "/tmp", tabs: []*tab{inactive, active}, ctx: context.Background()} | |
| 591 | 590 | } | |
| 592 | 591 | ||
| 593 | 592 | func waitBarRefreshIdle(t *testing.T, d *Daemon) { | |
| @@ -85,17 +85,16 @@ func TestBarScriptRefreshIntervalClampingTickAndLastGoodOnFailure(t *testing.T) | |||||
| 85 | require.Equal(t, "bot2", state.bottomRight) | 85 | require.Equal(t, "bot2", state.bottomRight) | ||
| 86 | } | 86 | } | ||
| 87 | 87 | ||||
| 88 | - | func TestBarScriptContextUsesActivePaneSessionAndClientCols(t *testing.T) { | 88 | + | func TestBarScriptContextUsesFirstOrderedPaneAndClientCols(t *testing.T) { |
| 89 | r := &fakeBarRunner{outs: []string{"top", "bottom"}} | 89 | r := &fakeBarRunner{outs: []string{"top", "bottom"}} | ||
| 90 | d := newBarRefreshTestDaemon(r, time.Second) | 90 | d := newBarRefreshTestDaemon(r, time.Second) | ||
| 91 | sess := newBarRefreshTestSession() | 91 | sess := newBarRefreshTestSession() | ||
| 92 | sess.name = "work" | 92 | sess.name = "work" | ||
| 93 | sess.cwd = "/repo" | 93 | sess.cwd = "/repo" | ||
| 94 | sess.registerAttachment(&attachedClient{size: domain.Size{Cols: 132, Rows: 40}}) | 94 | sess.registerAttachment(&attachedClient{size: domain.Size{Cols: 132, Rows: 40}}) | ||
| 95 | - | sess.active = 1 | 95 | + | sess.tabs[0].size = domain.Size{Cols: 132, Rows: 38} |
| 96 | - | sess.tabs[1].size = domain.Size{Cols: 132, Rows: 38} | 96 | + | active := sess.tabs[0].focusedPane() |
| 97 | - | active := sess.tabs[1].focusedPane() | 97 | + | active.stableID = "pane-default" |
| 98 | - | active.stableID = "pane-active" | |||
| 99 | active.pty = newScriptPTY(nil) | 98 | active.pty = newScriptPTY(nil) | ||
| 100 | d.procCwd = func(pid int) (string, error) { | 99 | d.procCwd = func(pid int) (string, error) { | ||
| 101 | require.Equal(t, 4242, pid) | 100 | require.Equal(t, 4242, pid) | ||
| @@ -107,8 +106,8 @@ func TestBarScriptContextUsesActivePaneSessionAndClientCols(t *testing.T) { | |||||
| 107 | require.Len(t, r.calls, 2) | 106 | require.Len(t, r.calls, 2) | ||
| 108 | for _, call := range r.calls { | 107 | for _, call := range r.calls { | ||
| 109 | require.Equal(t, "work", call.Session) | 108 | require.Equal(t, "work", call.Session) | ||
| 110 | - | require.Equal(t, "tab-active", call.Tab) | 109 | + | require.Equal(t, "tab-inactive", call.Tab) |
| 111 | - | require.Equal(t, "pane-active", call.Pane) | 110 | + | require.Equal(t, "pane-default", call.Pane) |
| 112 | require.Equal(t, "/pane-repo", call.PaneCWD) | 111 | require.Equal(t, "/pane-repo", call.PaneCWD) | ||
| 113 | require.Equal(t, 132, call.Cols) | 112 | require.Equal(t, 132, call.Cols) | ||
| 114 | } | 113 | } | ||
| @@ -587,7 +586,7 @@ func newBarRefreshTestSession() *session { | |||||
| 587 | inactive := newTabWithStableID("tab-inactive", "pane-inactive", nil, domain.Size{Cols: 80, Rows: 22}) | 586 | inactive := newTabWithStableID("tab-inactive", "pane-inactive", nil, domain.Size{Cols: 80, Rows: 22}) | ||
| 588 | active := newTabWithStableID("tab-active", "pane-active", nil, domain.Size{Cols: 80, Rows: 22}) | 587 | active := newTabWithStableID("tab-active", "pane-active", nil, domain.Size{Cols: 80, Rows: 22}) | ||
| 589 | active.tree.Focus = layout.PaneID("pane-1") | 588 | active.tree.Focus = layout.PaneID("pane-1") | ||
| 590 | - | return &session{sessionCore: sessionCore{id: "s", name: "s"}, cwd: "/tmp", tabs: []*tab{inactive, active}, active: 0, ctx: context.Background()} | 589 | + | return &session{sessionCore: sessionCore{id: "s", name: "s"}, cwd: "/tmp", tabs: []*tab{inactive, active}, ctx: context.Background()} |
| 591 | } | 590 | } | ||
| 592 | 591 | ||||
| 593 | func waitBarRefreshIdle(t *testing.T, d *Daemon) { | 592 | func waitBarRefreshIdle(t *testing.T, d *Daemon) { | ||
M internal/usecase/daemon/client.go +1 -1
| @@ -718,7 +718,7 @@ func (d *Daemon) firstPaintWithLease(sess *session, ac *attachedClient, clientSi | |||
| 718 | 718 | // Drained before the early return below so a session without an active tab | |
| 719 | 719 | // cannot swallow the queue. | |
| 720 | 720 | d.drainPendingForFirstPaint(sess, ac) | |
| 721 | - | tb := sess.tabForAttachmentOrActive(ac) | |
| 721 | + | tb := sess.tabForAttachment(ac) | |
| 722 | 722 | if tb == nil { | |
| 723 | 723 | return | |
| 724 | 724 | } | |
| @@ -718,7 +718,7 @@ func (d *Daemon) firstPaintWithLease(sess *session, ac *attachedClient, clientSi | |||||
| 718 | // Drained before the early return below so a session without an active tab | 718 | // Drained before the early return below so a session without an active tab | ||
| 719 | // cannot swallow the queue. | 719 | // cannot swallow the queue. | ||
| 720 | d.drainPendingForFirstPaint(sess, ac) | 720 | d.drainPendingForFirstPaint(sess, ac) | ||
| 721 | - | tb := sess.tabForAttachmentOrActive(ac) | 721 | + | tb := sess.tabForAttachment(ac) |
| 722 | if tb == nil { | 722 | if tb == nil { | ||
| 723 | return | 723 | return | ||
| 724 | } | 724 | } | ||
M internal/usecase/daemon/clipboard.go +6 -1
| @@ -147,7 +147,12 @@ func (d *Daemon) injectClipboardPathToTarget(sess *session, path string, token * | |||
| 147 | 147 | if token != nil { | |
| 148 | 148 | ac = token.ac | |
| 149 | 149 | } | |
| 150 | - | tb := sess.tabForAttachmentOrActive(ac) | |
| 150 | + | var tb *tab | |
| 151 | + | if ac != nil { | |
| 152 | + | tb = sess.tabForAttachment(ac) | |
| 153 | + | } else { | |
| 154 | + | tb = sess.firstTab() | |
| 155 | + | } | |
| 151 | 156 | if tb == nil { | |
| 152 | 157 | return | |
| 153 | 158 | } | |
| @@ -147,7 +147,12 @@ func (d *Daemon) injectClipboardPathToTarget(sess *session, path string, token * | |||||
| 147 | if token != nil { | 147 | if token != nil { | ||
| 148 | ac = token.ac | 148 | ac = token.ac | ||
| 149 | } | 149 | } | ||
| 150 | - | tb := sess.tabForAttachmentOrActive(ac) | 150 | + | var tb *tab |
| 151 | + | if ac != nil { | |||
| 152 | + | tb = sess.tabForAttachment(ac) | |||
| 153 | + | } else { | |||
| 154 | + | tb = sess.firstTab() | |||
| 155 | + | } | |||
| 151 | if tb == nil { | 156 | if tb == nil { | ||
| 152 | return | 157 | return | ||
| 153 | } | 158 | } | ||
M internal/usecase/daemon/clipboard_test.go +5 -5
| @@ -31,7 +31,7 @@ func TestInjectClipboardPathTargetsVisibleFloatingPane(t *testing.T) { | |||
| 31 | 31 | d, sess, _, _ := newManualSessionWithPTYs(t, normal) | |
| 32 | 32 | floating := newPane("floating", floatingPTY, domain.Size{Cols: 20, Rows: 5}) | |
| 33 | 33 | floating.screen.Write([]byte("\x1b[?2004h")) | |
| 34 | - | installTestFloating(sess.activeTab(), floating, true) | |
| 34 | + | installTestFloating(testAttachmentTab(sess), floating, true) | |
| 35 | 35 | ||
| 36 | 36 | d.injectClipboardPath(sess, "/tmp/image.png") | |
| 37 | 37 | ||
| @@ -211,7 +211,7 @@ func publishActiveClipboardCapability(d *Daemon, sess *session, ac *attachedClie | |||
| 211 | 211 | ||
| 212 | 212 | func clipboardOwnerLease(t *testing.T, sess *session) paneEffectLease { | |
| 213 | 213 | t.Helper() | |
| 214 | - | tb := sess.activeTab() | |
| 214 | + | tb := testAttachmentTab(sess) | |
| 215 | 215 | tb.mu.Lock() | |
| 216 | 216 | p := tb.terminalTargetLocked() | |
| 217 | 217 | tb.mu.Unlock() | |
| @@ -354,7 +354,7 @@ func TestQueuedClipboardAfterPaneMoveDoesNotSendToFormerOwner(t *testing.T) { | |||
| 354 | 354 | sess.mu.Unlock() | |
| 355 | 355 | d.forwardClipboardAsync(clipboardOwnerLease(t, sess), base64.StdEncoding.EncodeToString([]byte("queued"))) | |
| 356 | 356 | ||
| 357 | - | sourceTab := sess.activeTab() | |
| 357 | + | sourceTab := testAttachmentTab(sess) | |
| 358 | 358 | p := sourceTab.focusedPane() | |
| 359 | 359 | destination := &session{sessionCore: sessionCore{id: "destination", name: "destination"}} | |
| 360 | 360 | publishPaneOwner(p, destination, &tab{}, 0) | |
| @@ -422,7 +422,7 @@ func TestQueuedClipboardRevalidatesOwnerAfterWaitingForClientSendLock(t *testing | |||
| 422 | 422 | return ac.roleEffects.inFlightCount() == 1 | |
| 423 | 423 | }, time.Second, time.Millisecond, "clipboard send was not admitted before waiting on sendMu") | |
| 424 | 424 | ||
| 425 | - | sourceTab := sess.activeTab() | |
| 425 | + | sourceTab := testAttachmentTab(sess) | |
| 426 | 426 | p := sourceTab.focusedPane() | |
| 427 | 427 | destination := &session{sessionCore: sessionCore{id: "destination", name: "destination"}} | |
| 428 | 428 | publishPaneOwner(p, destination, &tab{}, 0) | |
| @@ -453,7 +453,7 @@ func TestClipboardSendErrorAfterPaneMoveDoesNotDetachFormerOwner(t *testing.T) { | |||
| 453 | 453 | }() | |
| 454 | 454 | awaitTestCompletion(t, oldTransport.started, "clipboard send did not start") | |
| 455 | 455 | ||
| 456 | - | sourceTab := sess.activeTab() | |
| 456 | + | sourceTab := testAttachmentTab(sess) | |
| 457 | 457 | p := sourceTab.focusedPane() | |
| 458 | 458 | destination := &session{sessionCore: sessionCore{id: "destination", name: "destination"}} | |
| 459 | 459 | publishPaneOwner(p, destination, &tab{}, 0) | |
| @@ -31,7 +31,7 @@ func TestInjectClipboardPathTargetsVisibleFloatingPane(t *testing.T) { | |||||
| 31 | d, sess, _, _ := newManualSessionWithPTYs(t, normal) | 31 | d, sess, _, _ := newManualSessionWithPTYs(t, normal) | ||
| 32 | floating := newPane("floating", floatingPTY, domain.Size{Cols: 20, Rows: 5}) | 32 | floating := newPane("floating", floatingPTY, domain.Size{Cols: 20, Rows: 5}) | ||
| 33 | floating.screen.Write([]byte("\x1b[?2004h")) | 33 | floating.screen.Write([]byte("\x1b[?2004h")) | ||
| 34 | - | installTestFloating(sess.activeTab(), floating, true) | 34 | + | installTestFloating(testAttachmentTab(sess), floating, true) |
| 35 | 35 | ||||
| 36 | d.injectClipboardPath(sess, "/tmp/image.png") | 36 | d.injectClipboardPath(sess, "/tmp/image.png") | ||
| 37 | 37 | ||||
| @@ -211,7 +211,7 @@ func publishActiveClipboardCapability(d *Daemon, sess *session, ac *attachedClie | |||||
| 211 | 211 | ||||
| 212 | func clipboardOwnerLease(t *testing.T, sess *session) paneEffectLease { | 212 | func clipboardOwnerLease(t *testing.T, sess *session) paneEffectLease { | ||
| 213 | t.Helper() | 213 | t.Helper() | ||
| 214 | - | tb := sess.activeTab() | 214 | + | tb := testAttachmentTab(sess) |
| 215 | tb.mu.Lock() | 215 | tb.mu.Lock() | ||
| 216 | p := tb.terminalTargetLocked() | 216 | p := tb.terminalTargetLocked() | ||
| 217 | tb.mu.Unlock() | 217 | tb.mu.Unlock() | ||
| @@ -354,7 +354,7 @@ func TestQueuedClipboardAfterPaneMoveDoesNotSendToFormerOwner(t *testing.T) { | |||||
| 354 | sess.mu.Unlock() | 354 | sess.mu.Unlock() | ||
| 355 | d.forwardClipboardAsync(clipboardOwnerLease(t, sess), base64.StdEncoding.EncodeToString([]byte("queued"))) | 355 | d.forwardClipboardAsync(clipboardOwnerLease(t, sess), base64.StdEncoding.EncodeToString([]byte("queued"))) | ||
| 356 | 356 | ||||
| 357 | - | sourceTab := sess.activeTab() | 357 | + | sourceTab := testAttachmentTab(sess) |
| 358 | p := sourceTab.focusedPane() | 358 | p := sourceTab.focusedPane() | ||
| 359 | destination := &session{sessionCore: sessionCore{id: "destination", name: "destination"}} | 359 | destination := &session{sessionCore: sessionCore{id: "destination", name: "destination"}} | ||
| 360 | publishPaneOwner(p, destination, &tab{}, 0) | 360 | publishPaneOwner(p, destination, &tab{}, 0) | ||
| @@ -422,7 +422,7 @@ func TestQueuedClipboardRevalidatesOwnerAfterWaitingForClientSendLock(t *testing | |||||
| 422 | return ac.roleEffects.inFlightCount() == 1 | 422 | return ac.roleEffects.inFlightCount() == 1 | ||
| 423 | }, time.Second, time.Millisecond, "clipboard send was not admitted before waiting on sendMu") | 423 | }, time.Second, time.Millisecond, "clipboard send was not admitted before waiting on sendMu") | ||
| 424 | 424 | ||||
| 425 | - | sourceTab := sess.activeTab() | 425 | + | sourceTab := testAttachmentTab(sess) |
| 426 | p := sourceTab.focusedPane() | 426 | p := sourceTab.focusedPane() | ||
| 427 | destination := &session{sessionCore: sessionCore{id: "destination", name: "destination"}} | 427 | destination := &session{sessionCore: sessionCore{id: "destination", name: "destination"}} | ||
| 428 | publishPaneOwner(p, destination, &tab{}, 0) | 428 | publishPaneOwner(p, destination, &tab{}, 0) | ||
| @@ -453,7 +453,7 @@ func TestClipboardSendErrorAfterPaneMoveDoesNotDetachFormerOwner(t *testing.T) { | |||||
| 453 | }() | 453 | }() | ||
| 454 | awaitTestCompletion(t, oldTransport.started, "clipboard send did not start") | 454 | awaitTestCompletion(t, oldTransport.started, "clipboard send did not start") | ||
| 455 | 455 | ||||
| 456 | - | sourceTab := sess.activeTab() | 456 | + | sourceTab := testAttachmentTab(sess) |
| 457 | p := sourceTab.focusedPane() | 457 | p := sourceTab.focusedPane() | ||
| 458 | destination := &session{sessionCore: sessionCore{id: "destination", name: "destination"}} | 458 | destination := &session{sessionCore: sessionCore{id: "destination", name: "destination"}} | ||
| 459 | publishPaneOwner(p, destination, &tab{}, 0) | 459 | publishPaneOwner(p, destination, &tab{}, 0) | ||
M internal/usecase/daemon/control.go +11 -4
| @@ -276,7 +276,10 @@ func resolveDaemonActionTargetForAttachment(sess *session, ac *attachedClient) d | |||
| 276 | 276 | if sess == nil { | |
| 277 | 277 | return daemonActionTarget{} | |
| 278 | 278 | } | |
| 279 | - | tb := sess.tabForAttachmentOrActive(ac) | |
| 279 | + | tb := sess.tabForAttachment(ac) | |
| 280 | + | if ac == nil { | |
| 281 | + | tb = sess.firstTab() | |
| 282 | + | } | |
| 280 | 283 | if tb == nil { | |
| 281 | 284 | return daemonActionTarget{session: sess} | |
| 282 | 285 | } | |
| @@ -364,8 +367,8 @@ func (d *Daemon) hasDaemonActionPaneTarget(target daemonActionTarget) bool { | |||
| 364 | 367 | } | |
| 365 | 368 | ||
| 366 | 369 | func (a daemonActions) switchRelative(sess *session, ac *attachedClient, delta int) error { | |
| 367 | - | if sess.switchAttachmentRelative(ac, delta) { | |
| 368 | - | a.d.activateTabAfterResizeForLease(sess, sess.tabForAttachmentOrActive(ac), false, ac, nil) | |
| 370 | + | if sess.switchAttachmentRelativeForDispatch(ac, delta) { | |
| 371 | + | a.d.activateTabAfterResizeForLease(sess, sess.tabForAttachment(ac), false, ac, nil) | |
| 369 | 372 | } | |
| 370 | 373 | return nil | |
| 371 | 374 | } | |
| @@ -689,7 +692,11 @@ func (e controlExec) ListPanes(asJSON bool) (string, error) { | |||
| 689 | 692 | } | |
| 690 | 693 | tb := e.tab | |
| 691 | 694 | if tb == nil { | |
| 692 | - | tb = e.sess.tabForAttachmentOrActive(e.target.attachment) | |
| 695 | + | if e.target.attachment == nil { | |
| 696 | + | tb = e.sess.firstTab() | |
| 697 | + | } else { | |
| 698 | + | tb = e.sess.tabForAttachment(e.target.attachment) | |
| 699 | + | } | |
| 693 | 700 | } | |
| 694 | 701 | if tb == nil { | |
| 695 | 702 | return "", errors.New("target session has no active tab") | |
| @@ -276,7 +276,10 @@ func resolveDaemonActionTargetForAttachment(sess *session, ac *attachedClient) d | |||||
| 276 | if sess == nil { | 276 | if sess == nil { | ||
| 277 | return daemonActionTarget{} | 277 | return daemonActionTarget{} | ||
| 278 | } | 278 | } | ||
| 279 | - | tb := sess.tabForAttachmentOrActive(ac) | 279 | + | tb := sess.tabForAttachment(ac) |
| 280 | + | if ac == nil { | |||
| 281 | + | tb = sess.firstTab() | |||
| 282 | + | } | |||
| 280 | if tb == nil { | 283 | if tb == nil { | ||
| 281 | return daemonActionTarget{session: sess} | 284 | return daemonActionTarget{session: sess} | ||
| 282 | } | 285 | } | ||
| @@ -364,8 +367,8 @@ func (d *Daemon) hasDaemonActionPaneTarget(target daemonActionTarget) bool { | |||||
| 364 | } | 367 | } | ||
| 365 | 368 | ||||
| 366 | func (a daemonActions) switchRelative(sess *session, ac *attachedClient, delta int) error { | 369 | func (a daemonActions) switchRelative(sess *session, ac *attachedClient, delta int) error { | ||
| 367 | - | if sess.switchAttachmentRelative(ac, delta) { | 370 | + | if sess.switchAttachmentRelativeForDispatch(ac, delta) { |
| 368 | - | a.d.activateTabAfterResizeForLease(sess, sess.tabForAttachmentOrActive(ac), false, ac, nil) | 371 | + | a.d.activateTabAfterResizeForLease(sess, sess.tabForAttachment(ac), false, ac, nil) |
| 369 | } | 372 | } | ||
| 370 | return nil | 373 | return nil | ||
| 371 | } | 374 | } | ||
| @@ -689,7 +692,11 @@ func (e controlExec) ListPanes(asJSON bool) (string, error) { | |||||
| 689 | } | 692 | } | ||
| 690 | tb := e.tab | 693 | tb := e.tab | ||
| 691 | if tb == nil { | 694 | if tb == nil { | ||
| 692 | - | tb = e.sess.tabForAttachmentOrActive(e.target.attachment) | 695 | + | if e.target.attachment == nil { |
| 696 | + | tb = e.sess.firstTab() | |||
| 697 | + | } else { | |||
| 698 | + | tb = e.sess.tabForAttachment(e.target.attachment) | |||
| 699 | + | } | |||
| 693 | } | 700 | } | ||
| 694 | if tb == nil { | 701 | if tb == nil { | ||
| 695 | return "", errors.New("target session has no active tab") | 702 | return "", errors.New("target session has no active tab") | ||
M internal/usecase/daemon/control_test.go +26 -28
| @@ -37,7 +37,7 @@ func TestPaletteAndControlShareExplicitDaemonActionTarget(t *testing.T) { | |||
| 37 | 37 | t.Run(tt.name, func(t *testing.T) { | |
| 38 | 38 | d := newTestDaemon(t, nil, stubClock{}) | |
| 39 | 39 | sess := addControlSession(d, "work", "t_work", "p_work") | |
| 40 | - | tb := sess.activeTab() | |
| 40 | + | tb := testAttachmentTab(sess) | |
| 41 | 41 | tb.mu.Lock() | |
| 42 | 42 | pane := tb.focusedPane() | |
| 43 | 43 | tb.mu.Unlock() | |
| @@ -78,9 +78,7 @@ func TestConsumeOrExpelControlSelfTargetsNonFocusedPane(t *testing.T) { | |||
| 78 | 78 | ||
| 79 | 79 | require.True(t, result.OK, result.Text) | |
| 80 | 80 | require.Empty(t, result.Output) | |
| 81 | - | h.session.mu.Lock() | |
| 82 | - | require.Zero(t, h.session.active, "--self must not change the active tab") | |
| 83 | - | h.session.mu.Unlock() | |
| 81 | + | require.Zero(t, testAttachmentTabIndex(h.session), "--self must not change the attachment tab") | |
| 84 | 82 | h.tab.mu.Lock() | |
| 85 | 83 | require.Equal(t, layout.PaneID("pane-3"), h.tab.tree.Focus, "a moved explicit target becomes focused") | |
| 86 | 84 | require.Equal(t, beforeGeneration+1, h.tab.layoutGeneration) | |
| @@ -240,7 +238,7 @@ func TestHandleCommandStableIDsResolveSessionWithoutSelectingTab(t *testing.T) { | |||
| 240 | 238 | second.ctx, second.cancel = context.WithCancel(d.serveCtx) | |
| 241 | 239 | sess.mu.Lock() | |
| 242 | 240 | sess.tabs = append(sess.tabs, second) | |
| 243 | - | sess.active = 0 | |
| 241 | + | selectTestAttachmentTabLocked(sess, 0) | |
| 244 | 242 | sess.mu.Unlock() | |
| 245 | 243 | ||
| 246 | 244 | result := sendCommand(t, d, ports.CommandRequest{ | |
| @@ -256,7 +254,7 @@ func TestHandleCommandStableIDsResolveSessionWithoutSelectingTab(t *testing.T) { | |||
| 256 | 254 | require.Empty(t, second.name) | |
| 257 | 255 | second.mu.Unlock() | |
| 258 | 256 | sess.mu.Lock() | |
| 259 | - | require.Zero(t, sess.active) | |
| 257 | + | require.Zero(t, testAttachmentTabIndexLocked(sess)) | |
| 260 | 258 | sess.mu.Unlock() | |
| 261 | 259 | } | |
| 262 | 260 | ||
| @@ -272,7 +270,7 @@ func TestHandleCommandStableIDsDoNotRedirectSplitFromCurrentFocus(t *testing.T) | |||
| 272 | 270 | second.ctx, second.cancel = context.WithCancel(d.serveCtx) | |
| 273 | 271 | sess.mu.Lock() | |
| 274 | 272 | sess.tabs = append(sess.tabs, second) | |
| 275 | - | sess.active = 0 | |
| 273 | + | selectTestAttachmentTabLocked(sess, 0) | |
| 276 | 274 | sess.mu.Unlock() | |
| 277 | 275 | ||
| 278 | 276 | result := sendCommand(t, d, ports.CommandRequest{ | |
| @@ -282,7 +280,7 @@ func TestHandleCommandStableIDsDoNotRedirectSplitFromCurrentFocus(t *testing.T) | |||
| 282 | 280 | ||
| 283 | 281 | require.True(t, result.OK, result.Text) | |
| 284 | 282 | sess.mu.Lock() | |
| 285 | - | activeIndex := sess.active | |
| 283 | + | activeIndex := testAttachmentTabIndexLocked(sess) | |
| 286 | 284 | sess.mu.Unlock() | |
| 287 | 285 | require.Zero(t, activeIndex) | |
| 288 | 286 | active.mu.Lock() | |
| @@ -309,7 +307,7 @@ func TestHandleCommandSelfTargetsNonActiveTabAndPane(t *testing.T) { | |||
| 309 | 307 | invoking.ctx, invoking.cancel = context.WithCancel(d.serveCtx) | |
| 310 | 308 | sess.mu.Lock() | |
| 311 | 309 | sess.tabs = append(sess.tabs, invoking) | |
| 312 | - | sess.active = 0 | |
| 310 | + | selectTestAttachmentTabLocked(sess, 0) | |
| 313 | 311 | sess.mu.Unlock() | |
| 314 | 312 | ||
| 315 | 313 | rename := sendCommand(t, d, ports.CommandRequest{ | |
| @@ -383,7 +381,7 @@ func TestResizeControlOneShotsTargetDetachedSessions(t *testing.T) { | |||
| 383 | 381 | sess := addControlSession(d, "work", "t_work", "p_work") | |
| 384 | 382 | require.True(t, sendCommand(t, d, ports.CommandRequest{Slug: "split-right", TargetSession: "work"}).OK) | |
| 385 | 383 | require.True(t, sendCommand(t, d, ports.CommandRequest{Slug: "split-down", TargetSession: "work"}).OK) | |
| 386 | - | tb := sess.activeTab() | |
| 384 | + | tb := testAttachmentTab(sess) | |
| 387 | 385 | tb.mu.Lock() | |
| 388 | 386 | generation := tb.layoutGeneration | |
| 389 | 387 | tb.mu.Unlock() | |
| @@ -422,7 +420,7 @@ func TestResizeControlNonSelfVEVLocatorUsesActiveTarget(t *testing.T) { | |||
| 422 | 420 | locator.ctx, locator.cancel = context.WithCancel(d.serveCtx) | |
| 423 | 421 | sess.mu.Lock() | |
| 424 | 422 | sess.tabs = append(sess.tabs, locator) | |
| 425 | - | sess.active = 0 | |
| 423 | + | selectTestAttachmentTabLocked(sess, 0) | |
| 426 | 424 | sess.mu.Unlock() | |
| 427 | 425 | active.mu.Lock() | |
| 428 | 426 | before := active.layoutGeneration | |
| @@ -439,7 +437,7 @@ func TestResizeControlNonSelfVEVLocatorUsesActiveTarget(t *testing.T) { | |||
| 439 | 437 | require.Zero(t, locator.layoutGeneration) | |
| 440 | 438 | locator.mu.Unlock() | |
| 441 | 439 | sess.mu.Lock() | |
| 442 | - | require.Zero(t, sess.active) | |
| 440 | + | require.Zero(t, testAttachmentTabIndexLocked(sess)) | |
| 443 | 441 | sess.mu.Unlock() | |
| 444 | 442 | } | |
| 445 | 443 | ||
| @@ -449,7 +447,7 @@ func TestResizeControlTooSmallUsesStableFailure(t *testing.T) { | |||
| 449 | 447 | t.Cleanup(func() { factory.close(); d.sessWg.Wait() }) | |
| 450 | 448 | sess := addControlSession(d, "work", "t_work", "p_work") | |
| 451 | 449 | require.True(t, sendCommand(t, d, ports.CommandRequest{Slug: "split-right", TargetSession: "work"}).OK) | |
| 452 | - | tb := sess.activeTab() | |
| 450 | + | tb := testAttachmentTab(sess) | |
| 453 | 451 | tb.mu.Lock() | |
| 454 | 452 | tb.size.Cols = 41 | |
| 455 | 453 | tb.bumpLayoutGenerationLocked() | |
| @@ -468,7 +466,7 @@ func TestHandleCommandSelfListPanesUsesInvokingTab(t *testing.T) { | |||
| 468 | 466 | invoking.ctx, invoking.cancel = context.WithCancel(d.serveCtx) | |
| 469 | 467 | sess.mu.Lock() | |
| 470 | 468 | sess.tabs = append(sess.tabs, invoking) | |
| 471 | - | sess.active = 0 | |
| 469 | + | selectTestAttachmentTabLocked(sess, 0) | |
| 472 | 470 | sess.mu.Unlock() | |
| 473 | 471 | ||
| 474 | 472 | result := sendCommand(t, d, ports.CommandRequest{ | |
| @@ -539,7 +537,7 @@ func TestCloseCommandsReportMutationOutcome(t *testing.T) { | |||
| 539 | 537 | second.ctx, second.cancel = context.WithCancel(d.serveCtx) | |
| 540 | 538 | sess.mu.Lock() | |
| 541 | 539 | sess.tabs = append(sess.tabs, second) | |
| 542 | - | sess.active = 1 | |
| 540 | + | selectTestAttachmentTabLocked(sess, 1) | |
| 543 | 541 | sess.mu.Unlock() | |
| 544 | 542 | ||
| 545 | 543 | result := sendCommand(t, d, ports.CommandRequest{Slug: "close-tab", TargetSession: "work"}) | |
| @@ -560,7 +558,7 @@ func TestHandleCommandHeadlessMutations(t *testing.T) { | |||
| 560 | 558 | verify func(*testing.T, *Daemon, *session) | |
| 561 | 559 | }{ | |
| 562 | 560 | {name: "split focused pane", slug: "split-right", verify: func(t *testing.T, _ *Daemon, sess *session) { | |
| 563 | - | tb := sess.activeTab() | |
| 561 | + | tb := testAttachmentTab(sess) | |
| 564 | 562 | tb.mu.Lock() | |
| 565 | 563 | defer tb.mu.Unlock() | |
| 566 | 564 | require.Len(t, tb.panes, 2) | |
| @@ -569,10 +567,10 @@ func TestHandleCommandHeadlessMutations(t *testing.T) { | |||
| 569 | 567 | sess.mu.Lock() | |
| 570 | 568 | defer sess.mu.Unlock() | |
| 571 | 569 | require.Len(t, sess.tabs, 2) | |
| 572 | - | require.Equal(t, 1, sess.active) | |
| 570 | + | require.Equal(t, 1, testAttachmentTabIndexLocked(sess)) | |
| 573 | 571 | }}, | |
| 574 | 572 | {name: "rename tab", slug: "rename-tab", args: []string{"logs"}, verify: func(t *testing.T, _ *Daemon, sess *session) { | |
| 575 | - | tb := sess.activeTab() | |
| 573 | + | tb := testAttachmentTab(sess) | |
| 576 | 574 | tb.mu.Lock() | |
| 577 | 575 | defer tb.mu.Unlock() | |
| 578 | 576 | require.Equal(t, "logs", tb.name) | |
| @@ -762,7 +760,7 @@ func TestHandleCommandSerializesSelfTargetOnNonActiveTab(t *testing.T) { | |||
| 762 | 760 | target.ctx, target.cancel = context.WithCancel(d.serveCtx) | |
| 763 | 761 | sess.mu.Lock() | |
| 764 | 762 | sess.tabs = append(sess.tabs, target) | |
| 765 | - | sess.active = 0 | |
| 763 | + | selectTestAttachmentTabLocked(sess, 0) | |
| 766 | 764 | sess.mu.Unlock() | |
| 767 | 765 | original := target.tree.Focus | |
| 768 | 766 | ||
| @@ -808,7 +806,7 @@ func TestHandleCommandSerializesSelfTargetOnNonActiveTab(t *testing.T) { | |||
| 808 | 806 | require.True(t, awaitCommandResult(t, secondSends).OK) | |
| 809 | 807 | ||
| 810 | 808 | sess.mu.Lock() | |
| 811 | - | require.Zero(t, sess.active, "self commands must not select the invoking tab") | |
| 809 | + | require.Zero(t, testAttachmentTabIndexLocked(sess), "self commands must not select the invoking tab") | |
| 812 | 810 | sess.mu.Unlock() | |
| 813 | 811 | target.mu.Lock() | |
| 814 | 812 | defer target.mu.Unlock() | |
| @@ -824,7 +822,7 @@ func TestHandleCommandMovePaneUsesActiveFocusedSourceWithSessionFlag(t *testing. | |||
| 824 | 822 | publishTiledPaneOwners(source, inactive) | |
| 825 | 823 | source.mu.Lock() | |
| 826 | 824 | source.tabs = append(source.tabs, inactive) | |
| 827 | - | source.active = 0 | |
| 825 | + | selectTestAttachmentTabLocked(source, 0) | |
| 828 | 826 | source.mu.Unlock() | |
| 829 | 827 | destination := addNamedMoveDestination(d, "dest", "t_dest", "p_dest") | |
| 830 | 828 | ||
| @@ -859,7 +857,7 @@ func TestHandleCommandMovePaneSelfUsesStableSourceIDs(t *testing.T) { | |||
| 859 | 857 | publishTiledPaneOwners(source, inactive) | |
| 860 | 858 | source.mu.Lock() | |
| 861 | 859 | source.tabs = append(source.tabs, inactive) | |
| 862 | - | source.active = 0 | |
| 860 | + | selectTestAttachmentTabLocked(source, 0) | |
| 863 | 861 | source.mu.Unlock() | |
| 864 | 862 | destination := addNamedMoveDestination(d, "dest", "t_dest", "p_dest") | |
| 865 | 863 | ||
| @@ -896,7 +894,7 @@ func TestHandleCommandMoveTabUsesActiveTabWithoutSelf(t *testing.T) { | |||
| 896 | 894 | publishTiledPaneOwners(source, second) | |
| 897 | 895 | source.mu.Lock() | |
| 898 | 896 | source.tabs = append(source.tabs, second) | |
| 899 | - | source.active = 1 | |
| 897 | + | selectTestAttachmentTabLocked(source, 1) | |
| 900 | 898 | source.mu.Unlock() | |
| 901 | 899 | destination := addNamedMoveDestination(d, "dest", "t_dest", "p_dest") | |
| 902 | 900 | ||
| @@ -923,7 +921,7 @@ func TestHandleCommandMoveTabSelfUsesStableTab(t *testing.T) { | |||
| 923 | 921 | publishTiledPaneOwners(source, second) | |
| 924 | 922 | source.mu.Lock() | |
| 925 | 923 | source.tabs = append(source.tabs, second) | |
| 926 | - | source.active = 0 | |
| 924 | + | selectTestAttachmentTabLocked(source, 0) | |
| 927 | 925 | source.mu.Unlock() | |
| 928 | 926 | destination := addNamedMoveDestination(d, "dest", "t_dest", "p_dest") | |
| 929 | 927 | ||
| @@ -1093,7 +1091,7 @@ func TestHandleCommandMovePaneStableIDsLocateSessionWithoutSelfRedirect(t *testi | |||
| 1093 | 1091 | publishTiledPaneOwners(source, inactive) | |
| 1094 | 1092 | source.mu.Lock() | |
| 1095 | 1093 | source.tabs = append(source.tabs, inactive) | |
| 1096 | - | source.active = 1 | |
| 1094 | + | selectTestAttachmentTabLocked(source, 1) | |
| 1097 | 1095 | source.mu.Unlock() | |
| 1098 | 1096 | destination := addNamedMoveDestination(d, "dest", "t_dest", "p_dest") | |
| 1099 | 1097 | ||
| @@ -1126,7 +1124,7 @@ func TestHandleCommandOppositeMoveCommandsDoNotDeadlock(t *testing.T) { | |||
| 1126 | 1124 | left.mu.Lock() | |
| 1127 | 1125 | left.name = "left" | |
| 1128 | 1126 | left.tabs[0].stableID = "left-moved" | |
| 1129 | - | left.active = 0 | |
| 1127 | + | selectTestAttachmentTabLocked(left, 0) | |
| 1130 | 1128 | left.mu.Unlock() | |
| 1131 | 1129 | ||
| 1132 | 1130 | right := addMoveTabTestSession(d, "right", "right-stays") | |
| @@ -1135,7 +1133,7 @@ func TestHandleCommandOppositeMoveCommandsDoNotDeadlock(t *testing.T) { | |||
| 1135 | 1133 | publishTiledPaneOwners(right, rightMoved) | |
| 1136 | 1134 | right.mu.Lock() | |
| 1137 | 1135 | right.tabs = append(right.tabs, rightMoved) | |
| 1138 | - | right.active = 1 | |
| 1136 | + | selectTestAttachmentTabLocked(right, 1) | |
| 1139 | 1137 | right.mu.Unlock() | |
| 1140 | 1138 | ||
| 1141 | 1139 | leftFrame := commandFrame(t, ports.CommandRequest{ | |
| @@ -1179,7 +1177,7 @@ func addNamedMoveDestination(d *Daemon, name, tabID, paneID string) *session { | |||
| 1179 | 1177 | tb := newTabWithStableID(tabID, paneID, newQuietPTY(), domain.Size{Cols: 80, Rows: 23}) | |
| 1180 | 1178 | tb.ctx, tb.cancel = context.WithCancel(ctx) | |
| 1181 | 1179 | sess := &session{sessionCore: sessionCore{id: domain.SessionID("sess-" + name), name: name, incarnation: domain.IncarnationID{5}, | |
| 1182 | - | ephemeral: true}, ctx: ctx, cancel: cancel, tabs: []*tab{tb}, active: 0, | |
| 1180 | + | ephemeral: true}, ctx: ctx, cancel: cancel, tabs: []*tab{tb}, | |
| 1183 | 1181 | } | |
| 1184 | 1182 | publishTiledPaneOwners(sess, tb) | |
| 1185 | 1183 | d.mu.Lock() | |
| @@ -37,7 +37,7 @@ func TestPaletteAndControlShareExplicitDaemonActionTarget(t *testing.T) { | |||||
| 37 | t.Run(tt.name, func(t *testing.T) { | 37 | t.Run(tt.name, func(t *testing.T) { | ||
| 38 | d := newTestDaemon(t, nil, stubClock{}) | 38 | d := newTestDaemon(t, nil, stubClock{}) | ||
| 39 | sess := addControlSession(d, "work", "t_work", "p_work") | 39 | sess := addControlSession(d, "work", "t_work", "p_work") | ||
| 40 | - | tb := sess.activeTab() | 40 | + | tb := testAttachmentTab(sess) |
| 41 | tb.mu.Lock() | 41 | tb.mu.Lock() | ||
| 42 | pane := tb.focusedPane() | 42 | pane := tb.focusedPane() | ||
| 43 | tb.mu.Unlock() | 43 | tb.mu.Unlock() | ||
| @@ -78,9 +78,7 @@ func TestConsumeOrExpelControlSelfTargetsNonFocusedPane(t *testing.T) { | |||||
| 78 | 78 | ||||
| 79 | require.True(t, result.OK, result.Text) | 79 | require.True(t, result.OK, result.Text) | ||
| 80 | require.Empty(t, result.Output) | 80 | require.Empty(t, result.Output) | ||
| 81 | - | h.session.mu.Lock() | 81 | + | require.Zero(t, testAttachmentTabIndex(h.session), "--self must not change the attachment tab") |
| 82 | - | require.Zero(t, h.session.active, "--self must not change the active tab") | |||
| 83 | - | h.session.mu.Unlock() | |||
| 84 | h.tab.mu.Lock() | 82 | h.tab.mu.Lock() | ||
| 85 | require.Equal(t, layout.PaneID("pane-3"), h.tab.tree.Focus, "a moved explicit target becomes focused") | 83 | require.Equal(t, layout.PaneID("pane-3"), h.tab.tree.Focus, "a moved explicit target becomes focused") | ||
| 86 | require.Equal(t, beforeGeneration+1, h.tab.layoutGeneration) | 84 | require.Equal(t, beforeGeneration+1, h.tab.layoutGeneration) | ||
| @@ -240,7 +238,7 @@ func TestHandleCommandStableIDsResolveSessionWithoutSelectingTab(t *testing.T) { | |||||
| 240 | second.ctx, second.cancel = context.WithCancel(d.serveCtx) | 238 | second.ctx, second.cancel = context.WithCancel(d.serveCtx) | ||
| 241 | sess.mu.Lock() | 239 | sess.mu.Lock() | ||
| 242 | sess.tabs = append(sess.tabs, second) | 240 | sess.tabs = append(sess.tabs, second) | ||
| 243 | - | sess.active = 0 | 241 | + | selectTestAttachmentTabLocked(sess, 0) |
| 244 | sess.mu.Unlock() | 242 | sess.mu.Unlock() | ||
| 245 | 243 | ||||
| 246 | result := sendCommand(t, d, ports.CommandRequest{ | 244 | result := sendCommand(t, d, ports.CommandRequest{ | ||
| @@ -256,7 +254,7 @@ func TestHandleCommandStableIDsResolveSessionWithoutSelectingTab(t *testing.T) { | |||||
| 256 | require.Empty(t, second.name) | 254 | require.Empty(t, second.name) | ||
| 257 | second.mu.Unlock() | 255 | second.mu.Unlock() | ||
| 258 | sess.mu.Lock() | 256 | sess.mu.Lock() | ||
| 259 | - | require.Zero(t, sess.active) | 257 | + | require.Zero(t, testAttachmentTabIndexLocked(sess)) |
| 260 | sess.mu.Unlock() | 258 | sess.mu.Unlock() | ||
| 261 | } | 259 | } | ||
| 262 | 260 | ||||
| @@ -272,7 +270,7 @@ func TestHandleCommandStableIDsDoNotRedirectSplitFromCurrentFocus(t *testing.T) | |||||
| 272 | second.ctx, second.cancel = context.WithCancel(d.serveCtx) | 270 | second.ctx, second.cancel = context.WithCancel(d.serveCtx) | ||
| 273 | sess.mu.Lock() | 271 | sess.mu.Lock() | ||
| 274 | sess.tabs = append(sess.tabs, second) | 272 | sess.tabs = append(sess.tabs, second) | ||
| 275 | - | sess.active = 0 | 273 | + | selectTestAttachmentTabLocked(sess, 0) |
| 276 | sess.mu.Unlock() | 274 | sess.mu.Unlock() | ||
| 277 | 275 | ||||
| 278 | result := sendCommand(t, d, ports.CommandRequest{ | 276 | result := sendCommand(t, d, ports.CommandRequest{ | ||
| @@ -282,7 +280,7 @@ func TestHandleCommandStableIDsDoNotRedirectSplitFromCurrentFocus(t *testing.T) | |||||
| 282 | 280 | ||||
| 283 | require.True(t, result.OK, result.Text) | 281 | require.True(t, result.OK, result.Text) | ||
| 284 | sess.mu.Lock() | 282 | sess.mu.Lock() | ||
| 285 | - | activeIndex := sess.active | 283 | + | activeIndex := testAttachmentTabIndexLocked(sess) |
| 286 | sess.mu.Unlock() | 284 | sess.mu.Unlock() | ||
| 287 | require.Zero(t, activeIndex) | 285 | require.Zero(t, activeIndex) | ||
| 288 | active.mu.Lock() | 286 | active.mu.Lock() | ||
| @@ -309,7 +307,7 @@ func TestHandleCommandSelfTargetsNonActiveTabAndPane(t *testing.T) { | |||||
| 309 | invoking.ctx, invoking.cancel = context.WithCancel(d.serveCtx) | 307 | invoking.ctx, invoking.cancel = context.WithCancel(d.serveCtx) | ||
| 310 | sess.mu.Lock() | 308 | sess.mu.Lock() | ||
| 311 | sess.tabs = append(sess.tabs, invoking) | 309 | sess.tabs = append(sess.tabs, invoking) | ||
| 312 | - | sess.active = 0 | 310 | + | selectTestAttachmentTabLocked(sess, 0) |
| 313 | sess.mu.Unlock() | 311 | sess.mu.Unlock() | ||
| 314 | 312 | ||||
| 315 | rename := sendCommand(t, d, ports.CommandRequest{ | 313 | rename := sendCommand(t, d, ports.CommandRequest{ | ||
| @@ -383,7 +381,7 @@ func TestResizeControlOneShotsTargetDetachedSessions(t *testing.T) { | |||||
| 383 | sess := addControlSession(d, "work", "t_work", "p_work") | 381 | sess := addControlSession(d, "work", "t_work", "p_work") | ||
| 384 | require.True(t, sendCommand(t, d, ports.CommandRequest{Slug: "split-right", TargetSession: "work"}).OK) | 382 | require.True(t, sendCommand(t, d, ports.CommandRequest{Slug: "split-right", TargetSession: "work"}).OK) | ||
| 385 | require.True(t, sendCommand(t, d, ports.CommandRequest{Slug: "split-down", TargetSession: "work"}).OK) | 383 | require.True(t, sendCommand(t, d, ports.CommandRequest{Slug: "split-down", TargetSession: "work"}).OK) | ||
| 386 | - | tb := sess.activeTab() | 384 | + | tb := testAttachmentTab(sess) |
| 387 | tb.mu.Lock() | 385 | tb.mu.Lock() | ||
| 388 | generation := tb.layoutGeneration | 386 | generation := tb.layoutGeneration | ||
| 389 | tb.mu.Unlock() | 387 | tb.mu.Unlock() | ||
| @@ -422,7 +420,7 @@ func TestResizeControlNonSelfVEVLocatorUsesActiveTarget(t *testing.T) { | |||||
| 422 | locator.ctx, locator.cancel = context.WithCancel(d.serveCtx) | 420 | locator.ctx, locator.cancel = context.WithCancel(d.serveCtx) | ||
| 423 | sess.mu.Lock() | 421 | sess.mu.Lock() | ||
| 424 | sess.tabs = append(sess.tabs, locator) | 422 | sess.tabs = append(sess.tabs, locator) | ||
| 425 | - | sess.active = 0 | 423 | + | selectTestAttachmentTabLocked(sess, 0) |
| 426 | sess.mu.Unlock() | 424 | sess.mu.Unlock() | ||
| 427 | active.mu.Lock() | 425 | active.mu.Lock() | ||
| 428 | before := active.layoutGeneration | 426 | before := active.layoutGeneration | ||
| @@ -439,7 +437,7 @@ func TestResizeControlNonSelfVEVLocatorUsesActiveTarget(t *testing.T) { | |||||
| 439 | require.Zero(t, locator.layoutGeneration) | 437 | require.Zero(t, locator.layoutGeneration) | ||
| 440 | locator.mu.Unlock() | 438 | locator.mu.Unlock() | ||
| 441 | sess.mu.Lock() | 439 | sess.mu.Lock() | ||
| 442 | - | require.Zero(t, sess.active) | 440 | + | require.Zero(t, testAttachmentTabIndexLocked(sess)) |
| 443 | sess.mu.Unlock() | 441 | sess.mu.Unlock() | ||
| 444 | } | 442 | } | ||
| 445 | 443 | ||||
| @@ -449,7 +447,7 @@ func TestResizeControlTooSmallUsesStableFailure(t *testing.T) { | |||||
| 449 | t.Cleanup(func() { factory.close(); d.sessWg.Wait() }) | 447 | t.Cleanup(func() { factory.close(); d.sessWg.Wait() }) | ||
| 450 | sess := addControlSession(d, "work", "t_work", "p_work") | 448 | sess := addControlSession(d, "work", "t_work", "p_work") | ||
| 451 | require.True(t, sendCommand(t, d, ports.CommandRequest{Slug: "split-right", TargetSession: "work"}).OK) | 449 | require.True(t, sendCommand(t, d, ports.CommandRequest{Slug: "split-right", TargetSession: "work"}).OK) | ||
| 452 | - | tb := sess.activeTab() | 450 | + | tb := testAttachmentTab(sess) |
| 453 | tb.mu.Lock() | 451 | tb.mu.Lock() | ||
| 454 | tb.size.Cols = 41 | 452 | tb.size.Cols = 41 | ||
| 455 | tb.bumpLayoutGenerationLocked() | 453 | tb.bumpLayoutGenerationLocked() | ||
| @@ -468,7 +466,7 @@ func TestHandleCommandSelfListPanesUsesInvokingTab(t *testing.T) { | |||||
| 468 | invoking.ctx, invoking.cancel = context.WithCancel(d.serveCtx) | 466 | invoking.ctx, invoking.cancel = context.WithCancel(d.serveCtx) | ||
| 469 | sess.mu.Lock() | 467 | sess.mu.Lock() | ||
| 470 | sess.tabs = append(sess.tabs, invoking) | 468 | sess.tabs = append(sess.tabs, invoking) | ||
| 471 | - | sess.active = 0 | 469 | + | selectTestAttachmentTabLocked(sess, 0) |
| 472 | sess.mu.Unlock() | 470 | sess.mu.Unlock() | ||
| 473 | 471 | ||||
| 474 | result := sendCommand(t, d, ports.CommandRequest{ | 472 | result := sendCommand(t, d, ports.CommandRequest{ | ||
| @@ -539,7 +537,7 @@ func TestCloseCommandsReportMutationOutcome(t *testing.T) { | |||||
| 539 | second.ctx, second.cancel = context.WithCancel(d.serveCtx) | 537 | second.ctx, second.cancel = context.WithCancel(d.serveCtx) | ||
| 540 | sess.mu.Lock() | 538 | sess.mu.Lock() | ||
| 541 | sess.tabs = append(sess.tabs, second) | 539 | sess.tabs = append(sess.tabs, second) | ||
| 542 | - | sess.active = 1 | 540 | + | selectTestAttachmentTabLocked(sess, 1) |
| 543 | sess.mu.Unlock() | 541 | sess.mu.Unlock() | ||
| 544 | 542 | ||||
| 545 | result := sendCommand(t, d, ports.CommandRequest{Slug: "close-tab", TargetSession: "work"}) | 543 | result := sendCommand(t, d, ports.CommandRequest{Slug: "close-tab", TargetSession: "work"}) | ||
| @@ -560,7 +558,7 @@ func TestHandleCommandHeadlessMutations(t *testing.T) { | |||||
| 560 | verify func(*testing.T, *Daemon, *session) | 558 | verify func(*testing.T, *Daemon, *session) | ||
| 561 | }{ | 559 | }{ | ||
| 562 | {name: "split focused pane", slug: "split-right", verify: func(t *testing.T, _ *Daemon, sess *session) { | 560 | {name: "split focused pane", slug: "split-right", verify: func(t *testing.T, _ *Daemon, sess *session) { | ||
| 563 | - | tb := sess.activeTab() | 561 | + | tb := testAttachmentTab(sess) |
| 564 | tb.mu.Lock() | 562 | tb.mu.Lock() | ||
| 565 | defer tb.mu.Unlock() | 563 | defer tb.mu.Unlock() | ||
| 566 | require.Len(t, tb.panes, 2) | 564 | require.Len(t, tb.panes, 2) | ||
| @@ -569,10 +567,10 @@ func TestHandleCommandHeadlessMutations(t *testing.T) { | |||||
| 569 | sess.mu.Lock() | 567 | sess.mu.Lock() | ||
| 570 | defer sess.mu.Unlock() | 568 | defer sess.mu.Unlock() | ||
| 571 | require.Len(t, sess.tabs, 2) | 569 | require.Len(t, sess.tabs, 2) | ||
| 572 | - | require.Equal(t, 1, sess.active) | 570 | + | require.Equal(t, 1, testAttachmentTabIndexLocked(sess)) |
| 573 | }}, | 571 | }}, | ||
| 574 | {name: "rename tab", slug: "rename-tab", args: []string{"logs"}, verify: func(t *testing.T, _ *Daemon, sess *session) { | 572 | {name: "rename tab", slug: "rename-tab", args: []string{"logs"}, verify: func(t *testing.T, _ *Daemon, sess *session) { | ||
| 575 | - | tb := sess.activeTab() | 573 | + | tb := testAttachmentTab(sess) |
| 576 | tb.mu.Lock() | 574 | tb.mu.Lock() | ||
| 577 | defer tb.mu.Unlock() | 575 | defer tb.mu.Unlock() | ||
| 578 | require.Equal(t, "logs", tb.name) | 576 | require.Equal(t, "logs", tb.name) | ||
| @@ -762,7 +760,7 @@ func TestHandleCommandSerializesSelfTargetOnNonActiveTab(t *testing.T) { | |||||
| 762 | target.ctx, target.cancel = context.WithCancel(d.serveCtx) | 760 | target.ctx, target.cancel = context.WithCancel(d.serveCtx) | ||
| 763 | sess.mu.Lock() | 761 | sess.mu.Lock() | ||
| 764 | sess.tabs = append(sess.tabs, target) | 762 | sess.tabs = append(sess.tabs, target) | ||
| 765 | - | sess.active = 0 | 763 | + | selectTestAttachmentTabLocked(sess, 0) |
| 766 | sess.mu.Unlock() | 764 | sess.mu.Unlock() | ||
| 767 | original := target.tree.Focus | 765 | original := target.tree.Focus | ||
| 768 | 766 | ||||
| @@ -808,7 +806,7 @@ func TestHandleCommandSerializesSelfTargetOnNonActiveTab(t *testing.T) { | |||||
| 808 | require.True(t, awaitCommandResult(t, secondSends).OK) | 806 | require.True(t, awaitCommandResult(t, secondSends).OK) | ||
| 809 | 807 | ||||
| 810 | sess.mu.Lock() | 808 | sess.mu.Lock() | ||
| 811 | - | require.Zero(t, sess.active, "self commands must not select the invoking tab") | 809 | + | require.Zero(t, testAttachmentTabIndexLocked(sess), "self commands must not select the invoking tab") |
| 812 | sess.mu.Unlock() | 810 | sess.mu.Unlock() | ||
| 813 | target.mu.Lock() | 811 | target.mu.Lock() | ||
| 814 | defer target.mu.Unlock() | 812 | defer target.mu.Unlock() | ||
| @@ -824,7 +822,7 @@ func TestHandleCommandMovePaneUsesActiveFocusedSourceWithSessionFlag(t *testing. | |||||
| 824 | publishTiledPaneOwners(source, inactive) | 822 | publishTiledPaneOwners(source, inactive) | ||
| 825 | source.mu.Lock() | 823 | source.mu.Lock() | ||
| 826 | source.tabs = append(source.tabs, inactive) | 824 | source.tabs = append(source.tabs, inactive) | ||
| 827 | - | source.active = 0 | 825 | + | selectTestAttachmentTabLocked(source, 0) |
| 828 | source.mu.Unlock() | 826 | source.mu.Unlock() | ||
| 829 | destination := addNamedMoveDestination(d, "dest", "t_dest", "p_dest") | 827 | destination := addNamedMoveDestination(d, "dest", "t_dest", "p_dest") | ||
| 830 | 828 | ||||
| @@ -859,7 +857,7 @@ func TestHandleCommandMovePaneSelfUsesStableSourceIDs(t *testing.T) { | |||||
| 859 | publishTiledPaneOwners(source, inactive) | 857 | publishTiledPaneOwners(source, inactive) | ||
| 860 | source.mu.Lock() | 858 | source.mu.Lock() | ||
| 861 | source.tabs = append(source.tabs, inactive) | 859 | source.tabs = append(source.tabs, inactive) | ||
| 862 | - | source.active = 0 | 860 | + | selectTestAttachmentTabLocked(source, 0) |
| 863 | source.mu.Unlock() | 861 | source.mu.Unlock() | ||
| 864 | destination := addNamedMoveDestination(d, "dest", "t_dest", "p_dest") | 862 | destination := addNamedMoveDestination(d, "dest", "t_dest", "p_dest") | ||
| 865 | 863 | ||||
| @@ -896,7 +894,7 @@ func TestHandleCommandMoveTabUsesActiveTabWithoutSelf(t *testing.T) { | |||||
| 896 | publishTiledPaneOwners(source, second) | 894 | publishTiledPaneOwners(source, second) | ||
| 897 | source.mu.Lock() | 895 | source.mu.Lock() | ||
| 898 | source.tabs = append(source.tabs, second) | 896 | source.tabs = append(source.tabs, second) | ||
| 899 | - | source.active = 1 | 897 | + | selectTestAttachmentTabLocked(source, 1) |
| 900 | source.mu.Unlock() | 898 | source.mu.Unlock() | ||
| 901 | destination := addNamedMoveDestination(d, "dest", "t_dest", "p_dest") | 899 | destination := addNamedMoveDestination(d, "dest", "t_dest", "p_dest") | ||
| 902 | 900 | ||||
| @@ -923,7 +921,7 @@ func TestHandleCommandMoveTabSelfUsesStableTab(t *testing.T) { | |||||
| 923 | publishTiledPaneOwners(source, second) | 921 | publishTiledPaneOwners(source, second) | ||
| 924 | source.mu.Lock() | 922 | source.mu.Lock() | ||
| 925 | source.tabs = append(source.tabs, second) | 923 | source.tabs = append(source.tabs, second) | ||
| 926 | - | source.active = 0 | 924 | + | selectTestAttachmentTabLocked(source, 0) |
| 927 | source.mu.Unlock() | 925 | source.mu.Unlock() | ||
| 928 | destination := addNamedMoveDestination(d, "dest", "t_dest", "p_dest") | 926 | destination := addNamedMoveDestination(d, "dest", "t_dest", "p_dest") | ||
| 929 | 927 | ||||
| @@ -1093,7 +1091,7 @@ func TestHandleCommandMovePaneStableIDsLocateSessionWithoutSelfRedirect(t *testi | |||||
| 1093 | publishTiledPaneOwners(source, inactive) | 1091 | publishTiledPaneOwners(source, inactive) | ||
| 1094 | source.mu.Lock() | 1092 | source.mu.Lock() | ||
| 1095 | source.tabs = append(source.tabs, inactive) | 1093 | source.tabs = append(source.tabs, inactive) | ||
| 1096 | - | source.active = 1 | 1094 | + | selectTestAttachmentTabLocked(source, 1) |
| 1097 | source.mu.Unlock() | 1095 | source.mu.Unlock() | ||
| 1098 | destination := addNamedMoveDestination(d, "dest", "t_dest", "p_dest") | 1096 | destination := addNamedMoveDestination(d, "dest", "t_dest", "p_dest") | ||
| 1099 | 1097 | ||||
| @@ -1126,7 +1124,7 @@ func TestHandleCommandOppositeMoveCommandsDoNotDeadlock(t *testing.T) { | |||||
| 1126 | left.mu.Lock() | 1124 | left.mu.Lock() | ||
| 1127 | left.name = "left" | 1125 | left.name = "left" | ||
| 1128 | left.tabs[0].stableID = "left-moved" | 1126 | left.tabs[0].stableID = "left-moved" | ||
| 1129 | - | left.active = 0 | 1127 | + | selectTestAttachmentTabLocked(left, 0) |
| 1130 | left.mu.Unlock() | 1128 | left.mu.Unlock() | ||
| 1131 | 1129 | ||||
| 1132 | right := addMoveTabTestSession(d, "right", "right-stays") | 1130 | right := addMoveTabTestSession(d, "right", "right-stays") | ||
| @@ -1135,7 +1133,7 @@ func TestHandleCommandOppositeMoveCommandsDoNotDeadlock(t *testing.T) { | |||||
| 1135 | publishTiledPaneOwners(right, rightMoved) | 1133 | publishTiledPaneOwners(right, rightMoved) | ||
| 1136 | right.mu.Lock() | 1134 | right.mu.Lock() | ||
| 1137 | right.tabs = append(right.tabs, rightMoved) | 1135 | right.tabs = append(right.tabs, rightMoved) | ||
| 1138 | - | right.active = 1 | 1136 | + | selectTestAttachmentTabLocked(right, 1) |
| 1139 | right.mu.Unlock() | 1137 | right.mu.Unlock() | ||
| 1140 | 1138 | ||||
| 1141 | leftFrame := commandFrame(t, ports.CommandRequest{ | 1139 | leftFrame := commandFrame(t, ports.CommandRequest{ | ||
| @@ -1179,7 +1177,7 @@ func addNamedMoveDestination(d *Daemon, name, tabID, paneID string) *session { | |||||
| 1179 | tb := newTabWithStableID(tabID, paneID, newQuietPTY(), domain.Size{Cols: 80, Rows: 23}) | 1177 | tb := newTabWithStableID(tabID, paneID, newQuietPTY(), domain.Size{Cols: 80, Rows: 23}) | ||
| 1180 | tb.ctx, tb.cancel = context.WithCancel(ctx) | 1178 | tb.ctx, tb.cancel = context.WithCancel(ctx) | ||
| 1181 | sess := &session{sessionCore: sessionCore{id: domain.SessionID("sess-" + name), name: name, incarnation: domain.IncarnationID{5}, | 1179 | sess := &session{sessionCore: sessionCore{id: domain.SessionID("sess-" + name), name: name, incarnation: domain.IncarnationID{5}, | ||
| 1182 | - | ephemeral: true}, ctx: ctx, cancel: cancel, tabs: []*tab{tb}, active: 0, | 1180 | + | ephemeral: true}, ctx: ctx, cancel: cancel, tabs: []*tab{tb}, |
| 1183 | } | 1181 | } | ||
| 1184 | publishTiledPaneOwners(sess, tb) | 1182 | publishTiledPaneOwners(sess, tb) | ||
| 1185 | d.mu.Lock() | 1183 | d.mu.Lock() | ||
M internal/usecase/daemon/copy_mouse_input.go +1 -1
| @@ -179,7 +179,7 @@ func (d *Daemon) handleFreshCopyPointer(sess *session, ac *attachedClient, ev mo | |||
| 179 | 179 | if !ok { | |
| 180 | 180 | return true | |
| 181 | 181 | } | |
| 182 | - | if !d.publishCopyMode(sess, ac, sess.tabForAttachmentOrActive(ac), pointer.pane, pointer.document, func(mode *scopy.Mode) { | |
| 182 | + | if !d.publishCopyMode(sess, ac, sess.tabForAttachment(ac), pointer.pane, pointer.document, func(mode *scopy.Mode) { | |
| 183 | 183 | mode.StartCharacterSelection(pointer.press) | |
| 184 | 184 | mode.ExtendCharacterSelection(mapped.pos) | |
| 185 | 185 | }, func(runtime *overlayRuntime, mode *scopy.Mode) { | |
| @@ -179,7 +179,7 @@ func (d *Daemon) handleFreshCopyPointer(sess *session, ac *attachedClient, ev mo | |||||
| 179 | if !ok { | 179 | if !ok { | ||
| 180 | return true | 180 | return true | ||
| 181 | } | 181 | } | ||
| 182 | - | if !d.publishCopyMode(sess, ac, sess.tabForAttachmentOrActive(ac), pointer.pane, pointer.document, func(mode *scopy.Mode) { | 182 | + | if !d.publishCopyMode(sess, ac, sess.tabForAttachment(ac), pointer.pane, pointer.document, func(mode *scopy.Mode) { |
| 183 | mode.StartCharacterSelection(pointer.press) | 183 | mode.StartCharacterSelection(pointer.press) | ||
| 184 | mode.ExtendCharacterSelection(mapped.pos) | 184 | mode.ExtendCharacterSelection(mapped.pos) | ||
| 185 | }, func(runtime *overlayRuntime, mode *scopy.Mode) { | 185 | }, func(runtime *overlayRuntime, mode *scopy.Mode) { | ||
M internal/usecase/daemon/copy_mouse_map_test.go +1 -1
| @@ -71,7 +71,7 @@ func TestCopyMouseGeometryUsesClientFrameExactlyOnce(t *testing.T) { | |||
| 71 | 71 | p, release := newBlockingPTY(t) | |
| 72 | 72 | defer release() | |
| 73 | 73 | d, sess, _, _ := newManualSessionWithPTYs(t, p) | |
| 74 | - | tb := sess.activeTab() | |
| 74 | + | tb := testAttachmentTab(sess) | |
| 75 | 75 | ||
| 76 | 76 | tb.mu.Lock() | |
| 77 | 77 | geometry, ok := hitTestCopyMouseGeometryLocked(tb, d.currentFloatingConfig(), 0, 1) | |
| @@ -71,7 +71,7 @@ func TestCopyMouseGeometryUsesClientFrameExactlyOnce(t *testing.T) { | |||||
| 71 | p, release := newBlockingPTY(t) | 71 | p, release := newBlockingPTY(t) | ||
| 72 | defer release() | 72 | defer release() | ||
| 73 | d, sess, _, _ := newManualSessionWithPTYs(t, p) | 73 | d, sess, _, _ := newManualSessionWithPTYs(t, p) | ||
| 74 | - | tb := sess.activeTab() | 74 | + | tb := testAttachmentTab(sess) |
| 75 | 75 | ||||
| 76 | tb.mu.Lock() | 76 | tb.mu.Lock() | ||
| 77 | geometry, ok := hitTestCopyMouseGeometryLocked(tb, d.currentFloatingConfig(), 0, 1) | 77 | geometry, ok := hitTestCopyMouseGeometryLocked(tb, d.currentFloatingConfig(), 0, 1) | ||
M internal/usecase/daemon/copymode.go +2 -2
| @@ -63,7 +63,7 @@ func (d *Daemon) copyWheel(sess *session, ac *attachedClient, delta int) { | |||
| 63 | 63 | d.invalidateRender(sess, ac, true, "copymode.go") | |
| 64 | 64 | } | |
| 65 | 65 | func (d *Daemon) enterCopyMode(sess *session, ac *attachedClient) { | |
| 66 | - | tb := sess.tabForAttachmentOrActive(ac) | |
| 66 | + | tb := sess.tabForAttachment(ac) | |
| 67 | 67 | if tb == nil { | |
| 68 | 68 | return | |
| 69 | 69 | } | |
| @@ -114,7 +114,7 @@ func (d *Daemon) publishCopyMode(sess *session, ac *attachedClient, tb *tab, p * | |||
| 114 | 114 | if d.beforeCopyModeRevalidate != nil { | |
| 115 | 115 | d.beforeCopyModeRevalidate() | |
| 116 | 116 | } | |
| 117 | - | valid := sess.tabForAttachmentOrActive(ac) == tb | |
| 117 | + | valid := sess.tabForAttachment(ac) == tb | |
| 118 | 118 | if valid { | |
| 119 | 119 | tb.mu.Lock() | |
| 120 | 120 | valid = tb.panes[p.id] == p || (tb.floating.state == floatingVisible && tb.floating.pane == p) | |
| @@ -63,7 +63,7 @@ func (d *Daemon) copyWheel(sess *session, ac *attachedClient, delta int) { | |||||
| 63 | d.invalidateRender(sess, ac, true, "copymode.go") | 63 | d.invalidateRender(sess, ac, true, "copymode.go") | ||
| 64 | } | 64 | } | ||
| 65 | func (d *Daemon) enterCopyMode(sess *session, ac *attachedClient) { | 65 | func (d *Daemon) enterCopyMode(sess *session, ac *attachedClient) { | ||
| 66 | - | tb := sess.tabForAttachmentOrActive(ac) | 66 | + | tb := sess.tabForAttachment(ac) |
| 67 | if tb == nil { | 67 | if tb == nil { | ||
| 68 | return | 68 | return | ||
| 69 | } | 69 | } | ||
| @@ -114,7 +114,7 @@ func (d *Daemon) publishCopyMode(sess *session, ac *attachedClient, tb *tab, p * | |||||
| 114 | if d.beforeCopyModeRevalidate != nil { | 114 | if d.beforeCopyModeRevalidate != nil { | ||
| 115 | d.beforeCopyModeRevalidate() | 115 | d.beforeCopyModeRevalidate() | ||
| 116 | } | 116 | } | ||
| 117 | - | valid := sess.tabForAttachmentOrActive(ac) == tb | 117 | + | valid := sess.tabForAttachment(ac) == tb |
| 118 | if valid { | 118 | if valid { | ||
| 119 | tb.mu.Lock() | 119 | tb.mu.Lock() | ||
| 120 | valid = tb.panes[p.id] == p || (tb.floating.state == floatingVisible && tb.floating.pane == p) | 120 | valid = tb.panes[p.id] == p || (tb.floating.state == floatingVisible && tb.floating.pane == p) | ||
M internal/usecase/daemon/copymode_lifecycle_test.go +11 -11
| @@ -45,8 +45,8 @@ func TestCopyModeLifecycleActivateTabClearsRuntime(t *testing.T) { | |||
| 45 | 45 | seedCopyInteractionLocked(ac.overlays, ac.overlays.copyPane, ac.overlays.copyDocument) | |
| 46 | 46 | ac.overlays.copyMu.Unlock() | |
| 47 | 47 | ||
| 48 | - | require.True(t, sess.switchTab(1)) | |
| 49 | - | d.activateTab(sess, sess.activeTab()) | |
| 48 | + | require.True(t, selectTestAttachmentTab(sess, 1)) | |
| 49 | + | d.activateTab(sess, testAttachmentTab(sess)) | |
| 50 | 50 | ||
| 51 | 51 | require.False(t, ac.overlays.copyActive()) | |
| 52 | 52 | ac.overlays.copyMu.Lock() | |
| @@ -80,7 +80,7 @@ func TestActivateTabAfterResizePathAndCopyModeLifecycle(t *testing.T) { | |||
| 80 | 80 | ||
| 81 | 81 | d.enterCopyMode(sess, ac) | |
| 82 | 82 | require.True(t, ac.overlays.copyActive()) | |
| 83 | - | d.activateTabAfterResizeForLease(sess, sess.activeTab(), true, ac, lease) | |
| 83 | + | d.activateTabAfterResizeForLease(sess, testAttachmentTab(sess), true, ac, lease) | |
| 84 | 84 | ||
| 85 | 85 | require.False(t, ac.overlays.copyActive()) | |
| 86 | 86 | require.Equal(t, tt.leaseBacked, <-path) | |
| @@ -90,7 +90,7 @@ func TestActivateTabAfterResizePathAndCopyModeLifecycle(t *testing.T) { | |||
| 90 | 90 | ||
| 91 | 91 | func TestCopyModeLifecycleClosePaneClearsRecoveredClientState(t *testing.T) { | |
| 92 | 92 | d, sess, _, _ := newSplitTestDaemon(t, domain.Size{Cols: 41, Rows: 10}) | |
| 93 | - | tb := sess.activeTab() | |
| 93 | + | tb := testAttachmentTab(sess) | |
| 94 | 94 | closingPTY := portsmocks.NewMockPTY(t) | |
| 95 | 95 | closingPTY.EXPECT().Close().Return(nil).Once() | |
| 96 | 96 | tb.tree = &layout.Tree{Root: &layout.Node{Kind: layout.Split, Dir: layout.Horizontal, Children: []*layout.Node{layout.NewLeaf("pane-1"), layout.NewLeaf("pane-2")}}, Focus: "pane-2"} | |
| @@ -126,7 +126,7 @@ func TestCopyModeLifecycleCloseOnlyPaneInTabClearsRecoveredClientState(t *testin | |||
| 126 | 126 | release() | |
| 127 | 127 | } | |
| 128 | 128 | }() | |
| 129 | - | tb := sess.activeTab() | |
| 129 | + | tb := testAttachmentTab(sess) | |
| 130 | 130 | p := tb.focusedPane() | |
| 131 | 131 | d.enterCopyMode(sess, ac) | |
| 132 | 132 | require.True(t, ac.overlays.copyActive()) | |
| @@ -143,7 +143,7 @@ func TestCopyModeLifecycleDoesNotRenderCandidateBeforeValidation(t *testing.T) { | |||
| 143 | 143 | release() | |
| 144 | 144 | } | |
| 145 | 145 | }() | |
| 146 | - | tb := sess.activeTab() | |
| 146 | + | tb := testAttachmentTab(sess) | |
| 147 | 147 | p := tb.focusedPane() | |
| 148 | 148 | p.mu.Lock() | |
| 149 | 149 | document := scopy.NewDocument(scopy.NewSnapshot(p.history, p.screen.Frame, p.screen.LineBounds(), nil), domain.DefaultWordSeparators) | |
| @@ -185,7 +185,7 @@ func TestCopyModeLifecycleDoesNotRenderCandidateBeforeValidation(t *testing.T) { | |||
| 185 | 185 | renderSnapshot := ac.overlays.SnapshotForRender() | |
| 186 | 186 | candidateWasRenderable := renderSnapshot.copyActive | |
| 187 | 187 | renderSnapshot.Unlock() | |
| 188 | - | require.True(t, sess.switchTab(1)) | |
| 188 | + | require.True(t, selectTestAttachmentTab(sess, 1)) | |
| 189 | 189 | close(resume) | |
| 190 | 190 | resumed = true | |
| 191 | 191 | ||
| @@ -225,7 +225,7 @@ func TestCopyModeLifecyclePublicationEpochRejectsReleaseAndPaneClose(t *testing. | |||
| 225 | 225 | release() | |
| 226 | 226 | } | |
| 227 | 227 | }() | |
| 228 | - | tb := sess.activeTab() | |
| 228 | + | tb := testAttachmentTab(sess) | |
| 229 | 229 | p := tb.focusedPane() | |
| 230 | 230 | ||
| 231 | 231 | // A real press is required: the motion publication must inherit this | |
| @@ -285,7 +285,7 @@ func TestCopyModeLifecycleFloatingCloseDuringPublicationDoesNotResurrect(t *test | |||
| 285 | 285 | release() | |
| 286 | 286 | } | |
| 287 | 287 | }() | |
| 288 | - | tb := sess.activeTab() | |
| 288 | + | tb := testAttachmentTab(sess) | |
| 289 | 289 | floating := newPane("floating", nil, domain.Size{Cols: 20, Rows: 5}) | |
| 290 | 290 | installTestFloating(tb, floating, true) | |
| 291 | 291 | tb.mu.Lock() | |
| @@ -356,7 +356,7 @@ func TestCopyModeLifecycleRejectsPublicationForInactiveOrRemovedPane(t *testing. | |||
| 356 | 356 | { | |
| 357 | 357 | name: "inactive tab", | |
| 358 | 358 | invalidate: func(sess *session, _ *tab, _ *pane) { | |
| 359 | - | require.True(t, sess.switchTab(1)) | |
| 359 | + | require.True(t, selectTestAttachmentTab(sess, 1)) | |
| 360 | 360 | }, | |
| 361 | 361 | }, | |
| 362 | 362 | } { | |
| @@ -367,7 +367,7 @@ func TestCopyModeLifecycleRejectsPublicationForInactiveOrRemovedPane(t *testing. | |||
| 367 | 367 | release() | |
| 368 | 368 | } | |
| 369 | 369 | }() | |
| 370 | - | tb := sess.activeTab() | |
| 370 | + | tb := testAttachmentTab(sess) | |
| 371 | 371 | p := tb.focusedPane() | |
| 372 | 372 | p.mu.Lock() | |
| 373 | 373 | document := scopy.NewDocument(scopy.NewSnapshot(p.history, p.screen.Frame, p.screen.LineBounds(), nil), domain.DefaultWordSeparators) | |
| @@ -45,8 +45,8 @@ func TestCopyModeLifecycleActivateTabClearsRuntime(t *testing.T) { | |||||
| 45 | seedCopyInteractionLocked(ac.overlays, ac.overlays.copyPane, ac.overlays.copyDocument) | 45 | seedCopyInteractionLocked(ac.overlays, ac.overlays.copyPane, ac.overlays.copyDocument) | ||
| 46 | ac.overlays.copyMu.Unlock() | 46 | ac.overlays.copyMu.Unlock() | ||
| 47 | 47 | ||||
| 48 | - | require.True(t, sess.switchTab(1)) | 48 | + | require.True(t, selectTestAttachmentTab(sess, 1)) |
| 49 | - | d.activateTab(sess, sess.activeTab()) | 49 | + | d.activateTab(sess, testAttachmentTab(sess)) |
| 50 | 50 | ||||
| 51 | require.False(t, ac.overlays.copyActive()) | 51 | require.False(t, ac.overlays.copyActive()) | ||
| 52 | ac.overlays.copyMu.Lock() | 52 | ac.overlays.copyMu.Lock() | ||
| @@ -80,7 +80,7 @@ func TestActivateTabAfterResizePathAndCopyModeLifecycle(t *testing.T) { | |||||
| 80 | 80 | ||||
| 81 | d.enterCopyMode(sess, ac) | 81 | d.enterCopyMode(sess, ac) | ||
| 82 | require.True(t, ac.overlays.copyActive()) | 82 | require.True(t, ac.overlays.copyActive()) | ||
| 83 | - | d.activateTabAfterResizeForLease(sess, sess.activeTab(), true, ac, lease) | 83 | + | d.activateTabAfterResizeForLease(sess, testAttachmentTab(sess), true, ac, lease) |
| 84 | 84 | ||||
| 85 | require.False(t, ac.overlays.copyActive()) | 85 | require.False(t, ac.overlays.copyActive()) | ||
| 86 | require.Equal(t, tt.leaseBacked, <-path) | 86 | require.Equal(t, tt.leaseBacked, <-path) | ||
| @@ -90,7 +90,7 @@ func TestActivateTabAfterResizePathAndCopyModeLifecycle(t *testing.T) { | |||||
| 90 | 90 | ||||
| 91 | func TestCopyModeLifecycleClosePaneClearsRecoveredClientState(t *testing.T) { | 91 | func TestCopyModeLifecycleClosePaneClearsRecoveredClientState(t *testing.T) { | ||
| 92 | d, sess, _, _ := newSplitTestDaemon(t, domain.Size{Cols: 41, Rows: 10}) | 92 | d, sess, _, _ := newSplitTestDaemon(t, domain.Size{Cols: 41, Rows: 10}) | ||
| 93 | - | tb := sess.activeTab() | 93 | + | tb := testAttachmentTab(sess) |
| 94 | closingPTY := portsmocks.NewMockPTY(t) | 94 | closingPTY := portsmocks.NewMockPTY(t) | ||
| 95 | closingPTY.EXPECT().Close().Return(nil).Once() | 95 | closingPTY.EXPECT().Close().Return(nil).Once() | ||
| 96 | tb.tree = &layout.Tree{Root: &layout.Node{Kind: layout.Split, Dir: layout.Horizontal, Children: []*layout.Node{layout.NewLeaf("pane-1"), layout.NewLeaf("pane-2")}}, Focus: "pane-2"} | 96 | tb.tree = &layout.Tree{Root: &layout.Node{Kind: layout.Split, Dir: layout.Horizontal, Children: []*layout.Node{layout.NewLeaf("pane-1"), layout.NewLeaf("pane-2")}}, Focus: "pane-2"} | ||
| @@ -126,7 +126,7 @@ func TestCopyModeLifecycleCloseOnlyPaneInTabClearsRecoveredClientState(t *testin | |||||
| 126 | release() | 126 | release() | ||
| 127 | } | 127 | } | ||
| 128 | }() | 128 | }() | ||
| 129 | - | tb := sess.activeTab() | 129 | + | tb := testAttachmentTab(sess) |
| 130 | p := tb.focusedPane() | 130 | p := tb.focusedPane() | ||
| 131 | d.enterCopyMode(sess, ac) | 131 | d.enterCopyMode(sess, ac) | ||
| 132 | require.True(t, ac.overlays.copyActive()) | 132 | require.True(t, ac.overlays.copyActive()) | ||
| @@ -143,7 +143,7 @@ func TestCopyModeLifecycleDoesNotRenderCandidateBeforeValidation(t *testing.T) { | |||||
| 143 | release() | 143 | release() | ||
| 144 | } | 144 | } | ||
| 145 | }() | 145 | }() | ||
| 146 | - | tb := sess.activeTab() | 146 | + | tb := testAttachmentTab(sess) |
| 147 | p := tb.focusedPane() | 147 | p := tb.focusedPane() | ||
| 148 | p.mu.Lock() | 148 | p.mu.Lock() | ||
| 149 | document := scopy.NewDocument(scopy.NewSnapshot(p.history, p.screen.Frame, p.screen.LineBounds(), nil), domain.DefaultWordSeparators) | 149 | document := scopy.NewDocument(scopy.NewSnapshot(p.history, p.screen.Frame, p.screen.LineBounds(), nil), domain.DefaultWordSeparators) | ||
| @@ -185,7 +185,7 @@ func TestCopyModeLifecycleDoesNotRenderCandidateBeforeValidation(t *testing.T) { | |||||
| 185 | renderSnapshot := ac.overlays.SnapshotForRender() | 185 | renderSnapshot := ac.overlays.SnapshotForRender() | ||
| 186 | candidateWasRenderable := renderSnapshot.copyActive | 186 | candidateWasRenderable := renderSnapshot.copyActive | ||
| 187 | renderSnapshot.Unlock() | 187 | renderSnapshot.Unlock() | ||
| 188 | - | require.True(t, sess.switchTab(1)) | 188 | + | require.True(t, selectTestAttachmentTab(sess, 1)) |
| 189 | close(resume) | 189 | close(resume) | ||
| 190 | resumed = true | 190 | resumed = true | ||
| 191 | 191 | ||||
| @@ -225,7 +225,7 @@ func TestCopyModeLifecyclePublicationEpochRejectsReleaseAndPaneClose(t *testing. | |||||
| 225 | release() | 225 | release() | ||
| 226 | } | 226 | } | ||
| 227 | }() | 227 | }() | ||
| 228 | - | tb := sess.activeTab() | 228 | + | tb := testAttachmentTab(sess) |
| 229 | p := tb.focusedPane() | 229 | p := tb.focusedPane() | ||
| 230 | 230 | ||||
| 231 | // A real press is required: the motion publication must inherit this | 231 | // A real press is required: the motion publication must inherit this | ||
| @@ -285,7 +285,7 @@ func TestCopyModeLifecycleFloatingCloseDuringPublicationDoesNotResurrect(t *test | |||||
| 285 | release() | 285 | release() | ||
| 286 | } | 286 | } | ||
| 287 | }() | 287 | }() | ||
| 288 | - | tb := sess.activeTab() | 288 | + | tb := testAttachmentTab(sess) |
| 289 | floating := newPane("floating", nil, domain.Size{Cols: 20, Rows: 5}) | 289 | floating := newPane("floating", nil, domain.Size{Cols: 20, Rows: 5}) | ||
| 290 | installTestFloating(tb, floating, true) | 290 | installTestFloating(tb, floating, true) | ||
| 291 | tb.mu.Lock() | 291 | tb.mu.Lock() | ||
| @@ -356,7 +356,7 @@ func TestCopyModeLifecycleRejectsPublicationForInactiveOrRemovedPane(t *testing. | |||||
| 356 | { | 356 | { | ||
| 357 | name: "inactive tab", | 357 | name: "inactive tab", | ||
| 358 | invalidate: func(sess *session, _ *tab, _ *pane) { | 358 | invalidate: func(sess *session, _ *tab, _ *pane) { | ||
| 359 | - | require.True(t, sess.switchTab(1)) | 359 | + | require.True(t, selectTestAttachmentTab(sess, 1)) |
| 360 | }, | 360 | }, | ||
| 361 | }, | 361 | }, | ||
| 362 | } { | 362 | } { | ||
| @@ -367,7 +367,7 @@ func TestCopyModeLifecycleRejectsPublicationForInactiveOrRemovedPane(t *testing. | |||||
| 367 | release() | 367 | release() | ||
| 368 | } | 368 | } | ||
| 369 | }() | 369 | }() | ||
| 370 | - | tb := sess.activeTab() | 370 | + | tb := testAttachmentTab(sess) |
| 371 | p := tb.focusedPane() | 371 | p := tb.focusedPane() | ||
| 372 | p.mu.Lock() | 372 | p.mu.Lock() | ||
| 373 | document := scopy.NewDocument(scopy.NewSnapshot(p.history, p.screen.Frame, p.screen.LineBounds(), nil), domain.DefaultWordSeparators) | 373 | document := scopy.NewDocument(scopy.NewSnapshot(p.history, p.screen.Frame, p.screen.LineBounds(), nil), domain.DefaultWordSeparators) | ||
M internal/usecase/daemon/copymode_mouse_test.go +5 -5
| @@ -26,7 +26,7 @@ func mouseCopyHarness(t *testing.T, rows ...string) (*Daemon, *session, *attache | |||
| 26 | 26 | d, sess, ac, _ := newManualSessionWithPTYs(t, p) | |
| 27 | 27 | clock := &manualMouseClock{now: time.Unix(1, 0)} | |
| 28 | 28 | d.clock = clock | |
| 29 | - | pane := sess.activeTab().focusedPane() | |
| 29 | + | pane := testAttachmentTab(sess).focusedPane() | |
| 30 | 30 | for i, row := range rows { | |
| 31 | 31 | copy(pane.screen.Frame.Row(i), testRow(row)) | |
| 32 | 32 | } | |
| @@ -41,7 +41,7 @@ func copyMouseInput(d *Daemon, sess *session, ac *attachedClient, raw string) { | |||
| 41 | 41 | func TestCopyDoubleClick(t *testing.T) { | |
| 42 | 42 | t.Run("same normalized position within interval selects word", func(t *testing.T) { | |
| 43 | 43 | d, sess, ac, clock := mouseCopyHarness(t, "a界 beta") | |
| 44 | - | pane := sess.activeTab().focusedPane() | |
| 44 | + | pane := testAttachmentTab(sess).focusedPane() | |
| 45 | 45 | d.exitCopyMode(ac) | |
| 46 | 46 | pane.mu.Lock() | |
| 47 | 47 | row := pane.screen.Frame.Row(0) | |
| @@ -324,7 +324,7 @@ func TestCopyWordSelectionYanksExactOSC52(t *testing.T) { | |||
| 324 | 324 | d, sess, ac, sends := newManualSessionWithPTYs(t, p) | |
| 325 | 325 | clock := &manualMouseClock{now: time.Unix(1, 0)} | |
| 326 | 326 | d.clock = clock | |
| 327 | - | copy(sess.activeTab().focusedPane().screen.Frame.Row(0), testRow("alpha beta")) | |
| 327 | + | copy(testAttachmentTab(sess).focusedPane().screen.Frame.Row(0), testRow("alpha beta")) | |
| 328 | 328 | d.enterCopyMode(sess, ac) | |
| 329 | 329 | mustOutputData(t, sends) | |
| 330 | 330 | ||
| @@ -348,11 +348,11 @@ func TestCopyPointerAndClickResetOnCopyExitAndReplacement(t *testing.T) { | |||
| 348 | 348 | {name: "escape", run: func(d *Daemon, _ *session, ac *attachedClient) { d.handleCopyInput(ac, []byte("\x1bx")) }}, | |
| 349 | 349 | {name: "yank", run: func(d *Daemon, _ *session, ac *attachedClient) { d.handleCopyInput(ac, []byte("y")) }}, | |
| 350 | 350 | {name: "replacement publication", run: func(d *Daemon, sess *session, ac *attachedClient) { | |
| 351 | - | p := sess.activeTab().focusedPane() | |
| 351 | + | p := testAttachmentTab(sess).focusedPane() | |
| 352 | 352 | p.mu.Lock() | |
| 353 | 353 | doc := scopy.NewDocument(scopy.NewSnapshot(p.history, p.screen.Frame, p.screen.LineBounds(), nil), domain.DefaultWordSeparators) | |
| 354 | 354 | p.mu.Unlock() | |
| 355 | - | require.True(t, d.publishCopyMode(sess, ac, sess.activeTab(), p, doc, nil, nil)) | |
| 355 | + | require.True(t, d.publishCopyMode(sess, ac, testAttachmentTab(sess), p, doc, nil, nil)) | |
| 356 | 356 | }}, | |
| 357 | 357 | } { | |
| 358 | 358 | t.Run(tc.name, func(t *testing.T) { | |
| @@ -26,7 +26,7 @@ func mouseCopyHarness(t *testing.T, rows ...string) (*Daemon, *session, *attache | |||||
| 26 | d, sess, ac, _ := newManualSessionWithPTYs(t, p) | 26 | d, sess, ac, _ := newManualSessionWithPTYs(t, p) | ||
| 27 | clock := &manualMouseClock{now: time.Unix(1, 0)} | 27 | clock := &manualMouseClock{now: time.Unix(1, 0)} | ||
| 28 | d.clock = clock | 28 | d.clock = clock | ||
| 29 | - | pane := sess.activeTab().focusedPane() | 29 | + | pane := testAttachmentTab(sess).focusedPane() |
| 30 | for i, row := range rows { | 30 | for i, row := range rows { | ||
| 31 | copy(pane.screen.Frame.Row(i), testRow(row)) | 31 | copy(pane.screen.Frame.Row(i), testRow(row)) | ||
| 32 | } | 32 | } | ||
| @@ -41,7 +41,7 @@ func copyMouseInput(d *Daemon, sess *session, ac *attachedClient, raw string) { | |||||
| 41 | func TestCopyDoubleClick(t *testing.T) { | 41 | func TestCopyDoubleClick(t *testing.T) { | ||
| 42 | t.Run("same normalized position within interval selects word", func(t *testing.T) { | 42 | t.Run("same normalized position within interval selects word", func(t *testing.T) { | ||
| 43 | d, sess, ac, clock := mouseCopyHarness(t, "a界 beta") | 43 | d, sess, ac, clock := mouseCopyHarness(t, "a界 beta") | ||
| 44 | - | pane := sess.activeTab().focusedPane() | 44 | + | pane := testAttachmentTab(sess).focusedPane() |
| 45 | d.exitCopyMode(ac) | 45 | d.exitCopyMode(ac) | ||
| 46 | pane.mu.Lock() | 46 | pane.mu.Lock() | ||
| 47 | row := pane.screen.Frame.Row(0) | 47 | row := pane.screen.Frame.Row(0) | ||
| @@ -324,7 +324,7 @@ func TestCopyWordSelectionYanksExactOSC52(t *testing.T) { | |||||
| 324 | d, sess, ac, sends := newManualSessionWithPTYs(t, p) | 324 | d, sess, ac, sends := newManualSessionWithPTYs(t, p) | ||
| 325 | clock := &manualMouseClock{now: time.Unix(1, 0)} | 325 | clock := &manualMouseClock{now: time.Unix(1, 0)} | ||
| 326 | d.clock = clock | 326 | d.clock = clock | ||
| 327 | - | copy(sess.activeTab().focusedPane().screen.Frame.Row(0), testRow("alpha beta")) | 327 | + | copy(testAttachmentTab(sess).focusedPane().screen.Frame.Row(0), testRow("alpha beta")) |
| 328 | d.enterCopyMode(sess, ac) | 328 | d.enterCopyMode(sess, ac) | ||
| 329 | mustOutputData(t, sends) | 329 | mustOutputData(t, sends) | ||
| 330 | 330 | ||||
| @@ -348,11 +348,11 @@ func TestCopyPointerAndClickResetOnCopyExitAndReplacement(t *testing.T) { | |||||
| 348 | {name: "escape", run: func(d *Daemon, _ *session, ac *attachedClient) { d.handleCopyInput(ac, []byte("\x1bx")) }}, | 348 | {name: "escape", run: func(d *Daemon, _ *session, ac *attachedClient) { d.handleCopyInput(ac, []byte("\x1bx")) }}, | ||
| 349 | {name: "yank", run: func(d *Daemon, _ *session, ac *attachedClient) { d.handleCopyInput(ac, []byte("y")) }}, | 349 | {name: "yank", run: func(d *Daemon, _ *session, ac *attachedClient) { d.handleCopyInput(ac, []byte("y")) }}, | ||
| 350 | {name: "replacement publication", run: func(d *Daemon, sess *session, ac *attachedClient) { | 350 | {name: "replacement publication", run: func(d *Daemon, sess *session, ac *attachedClient) { | ||
| 351 | - | p := sess.activeTab().focusedPane() | 351 | + | p := testAttachmentTab(sess).focusedPane() |
| 352 | p.mu.Lock() | 352 | p.mu.Lock() | ||
| 353 | doc := scopy.NewDocument(scopy.NewSnapshot(p.history, p.screen.Frame, p.screen.LineBounds(), nil), domain.DefaultWordSeparators) | 353 | doc := scopy.NewDocument(scopy.NewSnapshot(p.history, p.screen.Frame, p.screen.LineBounds(), nil), domain.DefaultWordSeparators) | ||
| 354 | p.mu.Unlock() | 354 | p.mu.Unlock() | ||
| 355 | - | require.True(t, d.publishCopyMode(sess, ac, sess.activeTab(), p, doc, nil, nil)) | 355 | + | require.True(t, d.publishCopyMode(sess, ac, testAttachmentTab(sess), p, doc, nil, nil)) |
| 356 | }}, | 356 | }}, | ||
| 357 | } { | 357 | } { | ||
| 358 | t.Run(tc.name, func(t *testing.T) { | 358 | t.Run(tc.name, func(t *testing.T) { | ||
M internal/usecase/daemon/copymode_test.go +8 -8
| @@ -108,7 +108,7 @@ func TestCopySearchModalGeometry(t *testing.T) { | |||
| 108 | 108 | func TestCopyModeDocumentCarriesPaneRowIDs(t *testing.T) { | |
| 109 | 109 | p, _ := newBlockingPTY(t) | |
| 110 | 110 | d, sess, ac, sends := newManualSessionWithPTYs(t, p) | |
| 111 | - | pane := sess.activeTab().focusedPane() | |
| 111 | + | pane := testAttachmentTab(sess).focusedPane() | |
| 112 | 112 | appendHistoryRow(t, pane.history, testRow("history")) | |
| 113 | 113 | pane.screen.Write([]byte("live")) | |
| 114 | 114 | historyID := pane.history.View().RowID(0) | |
| @@ -128,7 +128,7 @@ func TestComposeCopyClientFrameConcurrentPaneOutput(t *testing.T) { | |||
| 128 | 128 | p, release := newBlockingPTY(t) | |
| 129 | 129 | defer release() | |
| 130 | 130 | _, sess, _, _ := newManualSessionWithPTYs(t, p) | |
| 131 | - | tb := sess.activeTab() | |
| 131 | + | tb := testAttachmentTab(sess) | |
| 132 | 132 | pane := tb.focusedPane() | |
| 133 | 133 | pane.mu.Lock() | |
| 134 | 134 | snap := scopy.NewSnapshot(pane.history, pane.screen.Frame, pane.screen.LineBounds(), nil) | |
| @@ -162,7 +162,7 @@ func TestCopyModeFrameIncludesTopAndBottomChrome(t *testing.T) { | |||
| 162 | 162 | _, sess, _, _ := newManualSessionWithPTYs(t, p) | |
| 163 | 163 | defer release() | |
| 164 | 164 | sess.name = "work" | |
| 165 | - | tb := sess.activeTab() | |
| 165 | + | tb := testAttachmentTab(sess) | |
| 166 | 166 | tb.focusedPane().screen = vt.NewScreen(12, 3) | |
| 167 | 167 | tb.focusedPane().screen.Write([]byte("live")) | |
| 168 | 168 | snap := scopy.NewSnapshot(tb.focusedPane().history, tb.focusedPane().screen.Frame, tb.focusedPane().screen.LineBounds(), nil) | |
| @@ -441,7 +441,7 @@ func TestScrollbackEvictionFeedsCopyModeYank(t *testing.T) { | |||
| 441 | 441 | if sess == nil { | |
| 442 | 442 | return false | |
| 443 | 443 | } | |
| 444 | - | win := sess.activeTab() | |
| 444 | + | win := testAttachmentTab(sess) | |
| 445 | 445 | if win == nil { | |
| 446 | 446 | return false | |
| 447 | 447 | } | |
| @@ -662,7 +662,7 @@ func TestHandleCopyInputUsesImmutableSnapshotWithoutPaneLock(t *testing.T) { | |||
| 662 | 662 | p, release := newBlockingPTY(t) | |
| 663 | 663 | defer release() | |
| 664 | 664 | d, sess, ac, sends := newManualSessionWithPTYs(t, p) | |
| 665 | - | pane := sess.activeTab().focusedPane() | |
| 665 | + | pane := testAttachmentTab(sess).focusedPane() | |
| 666 | 666 | pane.screen.Write([]byte("live")) | |
| 667 | 667 | d.enterCopyMode(sess, ac) | |
| 668 | 668 | awaitFrame(t, sends, ports.MsgOutput) | |
| @@ -729,7 +729,7 @@ func installFloatingCopyFixture(t *testing.T, sess *session, size domain.Size) * | |||
| 729 | 729 | floatingPTY, releaseFloating := newBlockingPTY(t) | |
| 730 | 730 | t.Cleanup(releaseFloating) | |
| 731 | 731 | fp := newPane("floating", floatingPTY, size) | |
| 732 | - | installTestFloating(sess.activeTab(), fp, true) | |
| 732 | + | installTestFloating(testAttachmentTab(sess), fp, true) | |
| 733 | 733 | return fp | |
| 734 | 734 | } | |
| 735 | 735 | ||
| @@ -748,7 +748,7 @@ func TestCopyModeCapturesSourceAndRetainsItAcrossFocusMove(t *testing.T) { | |||
| 748 | 748 | normal, releaseNormal := newBlockingPTYWithWrites(t, normalWrites) | |
| 749 | 749 | defer releaseNormal() | |
| 750 | 750 | d, sess, ac, sends := newManualSessionWithPTYs(t, normal) | |
| 751 | - | tb := sess.activeTab() | |
| 751 | + | tb := testAttachmentTab(sess) | |
| 752 | 752 | second := newPane("pane-2", nil, domain.Size{Cols: 40, Rows: 5}) | |
| 753 | 753 | tb.mu.Lock() | |
| 754 | 754 | tb.tree.Root = &layout.Node{Kind: layout.Split, Dir: layout.Horizontal, Children: []*layout.Node{layout.NewLeaf("pane-1"), layout.NewLeaf("pane-2")}} | |
| @@ -856,7 +856,7 @@ func TestFloatingExitClearsCopyModeBeforeRepaint(t *testing.T) { | |||
| 856 | 856 | normal, releaseNormal := newBlockingPTY(t) | |
| 857 | 857 | defer releaseNormal() | |
| 858 | 858 | d, sess, ac, sends := newManualSessionWithPTYs(t, normal) | |
| 859 | - | tb := sess.activeTab() | |
| 859 | + | tb := testAttachmentTab(sess) | |
| 860 | 860 | fp := installFloatingCopyFixture(t, sess, domain.Size{Cols: 20, Rows: 3}) | |
| 861 | 861 | fp.screen.Write([]byte("flt-live")) | |
| 862 | 862 | ||
| @@ -108,7 +108,7 @@ func TestCopySearchModalGeometry(t *testing.T) { | |||||
| 108 | func TestCopyModeDocumentCarriesPaneRowIDs(t *testing.T) { | 108 | func TestCopyModeDocumentCarriesPaneRowIDs(t *testing.T) { | ||
| 109 | p, _ := newBlockingPTY(t) | 109 | p, _ := newBlockingPTY(t) | ||
| 110 | d, sess, ac, sends := newManualSessionWithPTYs(t, p) | 110 | d, sess, ac, sends := newManualSessionWithPTYs(t, p) | ||
| 111 | - | pane := sess.activeTab().focusedPane() | 111 | + | pane := testAttachmentTab(sess).focusedPane() |
| 112 | appendHistoryRow(t, pane.history, testRow("history")) | 112 | appendHistoryRow(t, pane.history, testRow("history")) | ||
| 113 | pane.screen.Write([]byte("live")) | 113 | pane.screen.Write([]byte("live")) | ||
| 114 | historyID := pane.history.View().RowID(0) | 114 | historyID := pane.history.View().RowID(0) | ||
| @@ -128,7 +128,7 @@ func TestComposeCopyClientFrameConcurrentPaneOutput(t *testing.T) { | |||||
| 128 | p, release := newBlockingPTY(t) | 128 | p, release := newBlockingPTY(t) | ||
| 129 | defer release() | 129 | defer release() | ||
| 130 | _, sess, _, _ := newManualSessionWithPTYs(t, p) | 130 | _, sess, _, _ := newManualSessionWithPTYs(t, p) | ||
| 131 | - | tb := sess.activeTab() | 131 | + | tb := testAttachmentTab(sess) |
| 132 | pane := tb.focusedPane() | 132 | pane := tb.focusedPane() | ||
| 133 | pane.mu.Lock() | 133 | pane.mu.Lock() | ||
| 134 | snap := scopy.NewSnapshot(pane.history, pane.screen.Frame, pane.screen.LineBounds(), nil) | 134 | snap := scopy.NewSnapshot(pane.history, pane.screen.Frame, pane.screen.LineBounds(), nil) | ||
| @@ -162,7 +162,7 @@ func TestCopyModeFrameIncludesTopAndBottomChrome(t *testing.T) { | |||||
| 162 | _, sess, _, _ := newManualSessionWithPTYs(t, p) | 162 | _, sess, _, _ := newManualSessionWithPTYs(t, p) | ||
| 163 | defer release() | 163 | defer release() | ||
| 164 | sess.name = "work" | 164 | sess.name = "work" | ||
| 165 | - | tb := sess.activeTab() | 165 | + | tb := testAttachmentTab(sess) |
| 166 | tb.focusedPane().screen = vt.NewScreen(12, 3) | 166 | tb.focusedPane().screen = vt.NewScreen(12, 3) | ||
| 167 | tb.focusedPane().screen.Write([]byte("live")) | 167 | tb.focusedPane().screen.Write([]byte("live")) | ||
| 168 | snap := scopy.NewSnapshot(tb.focusedPane().history, tb.focusedPane().screen.Frame, tb.focusedPane().screen.LineBounds(), nil) | 168 | snap := scopy.NewSnapshot(tb.focusedPane().history, tb.focusedPane().screen.Frame, tb.focusedPane().screen.LineBounds(), nil) | ||
| @@ -441,7 +441,7 @@ func TestScrollbackEvictionFeedsCopyModeYank(t *testing.T) { | |||||
| 441 | if sess == nil { | 441 | if sess == nil { | ||
| 442 | return false | 442 | return false | ||
| 443 | } | 443 | } | ||
| 444 | - | win := sess.activeTab() | 444 | + | win := testAttachmentTab(sess) |
| 445 | if win == nil { | 445 | if win == nil { | ||
| 446 | return false | 446 | return false | ||
| 447 | } | 447 | } | ||
| @@ -662,7 +662,7 @@ func TestHandleCopyInputUsesImmutableSnapshotWithoutPaneLock(t *testing.T) { | |||||
| 662 | p, release := newBlockingPTY(t) | 662 | p, release := newBlockingPTY(t) | ||
| 663 | defer release() | 663 | defer release() | ||
| 664 | d, sess, ac, sends := newManualSessionWithPTYs(t, p) | 664 | d, sess, ac, sends := newManualSessionWithPTYs(t, p) | ||
| 665 | - | pane := sess.activeTab().focusedPane() | 665 | + | pane := testAttachmentTab(sess).focusedPane() |
| 666 | pane.screen.Write([]byte("live")) | 666 | pane.screen.Write([]byte("live")) | ||
| 667 | d.enterCopyMode(sess, ac) | 667 | d.enterCopyMode(sess, ac) | ||
| 668 | awaitFrame(t, sends, ports.MsgOutput) | 668 | awaitFrame(t, sends, ports.MsgOutput) | ||
| @@ -729,7 +729,7 @@ func installFloatingCopyFixture(t *testing.T, sess *session, size domain.Size) * | |||||
| 729 | floatingPTY, releaseFloating := newBlockingPTY(t) | 729 | floatingPTY, releaseFloating := newBlockingPTY(t) | ||
| 730 | t.Cleanup(releaseFloating) | 730 | t.Cleanup(releaseFloating) | ||
| 731 | fp := newPane("floating", floatingPTY, size) | 731 | fp := newPane("floating", floatingPTY, size) | ||
| 732 | - | installTestFloating(sess.activeTab(), fp, true) | 732 | + | installTestFloating(testAttachmentTab(sess), fp, true) |
| 733 | return fp | 733 | return fp | ||
| 734 | } | 734 | } | ||
| 735 | 735 | ||||
| @@ -748,7 +748,7 @@ func TestCopyModeCapturesSourceAndRetainsItAcrossFocusMove(t *testing.T) { | |||||
| 748 | normal, releaseNormal := newBlockingPTYWithWrites(t, normalWrites) | 748 | normal, releaseNormal := newBlockingPTYWithWrites(t, normalWrites) | ||
| 749 | defer releaseNormal() | 749 | defer releaseNormal() | ||
| 750 | d, sess, ac, sends := newManualSessionWithPTYs(t, normal) | 750 | d, sess, ac, sends := newManualSessionWithPTYs(t, normal) | ||
| 751 | - | tb := sess.activeTab() | 751 | + | tb := testAttachmentTab(sess) |
| 752 | second := newPane("pane-2", nil, domain.Size{Cols: 40, Rows: 5}) | 752 | second := newPane("pane-2", nil, domain.Size{Cols: 40, Rows: 5}) | ||
| 753 | tb.mu.Lock() | 753 | tb.mu.Lock() | ||
| 754 | tb.tree.Root = &layout.Node{Kind: layout.Split, Dir: layout.Horizontal, Children: []*layout.Node{layout.NewLeaf("pane-1"), layout.NewLeaf("pane-2")}} | 754 | tb.tree.Root = &layout.Node{Kind: layout.Split, Dir: layout.Horizontal, Children: []*layout.Node{layout.NewLeaf("pane-1"), layout.NewLeaf("pane-2")}} | ||
| @@ -856,7 +856,7 @@ func TestFloatingExitClearsCopyModeBeforeRepaint(t *testing.T) { | |||||
| 856 | normal, releaseNormal := newBlockingPTY(t) | 856 | normal, releaseNormal := newBlockingPTY(t) | ||
| 857 | defer releaseNormal() | 857 | defer releaseNormal() | ||
| 858 | d, sess, ac, sends := newManualSessionWithPTYs(t, normal) | 858 | d, sess, ac, sends := newManualSessionWithPTYs(t, normal) | ||
| 859 | - | tb := sess.activeTab() | 859 | + | tb := testAttachmentTab(sess) |
| 860 | fp := installFloatingCopyFixture(t, sess, domain.Size{Cols: 20, Rows: 3}) | 860 | fp := installFloatingCopyFixture(t, sess, domain.Size{Cols: 20, Rows: 3}) | ||
| 861 | fp.screen.Write([]byte("flt-live")) | 861 | fp.screen.Write([]byte("flt-live")) | ||
| 862 | 862 | ||||
M internal/usecase/daemon/daemon.go +1 -1
| @@ -1140,7 +1140,7 @@ func (d *Daemon) handleHello(tr ports.Transport, f ports.Frame) { | |||
| 1140 | 1140 | return | |
| 1141 | 1141 | } | |
| 1142 | 1142 | if ac.proxied { | |
| 1143 | - | meta, ok := sess.sessionMetaSnapshot() | |
| 1143 | + | meta, ok := sess.sessionMetaSnapshotFor(ac) | |
| 1144 | 1144 | metaFrame, metaErr := frameSessionMeta(meta) | |
| 1145 | 1145 | if !ok || metaErr != nil || ac.sendExpectedTransportForRole(postWelcomeToken.transport, metaFrame, postWelcomeTicket) != nil { | |
| 1146 | 1146 | postWelcomeTicket.End() | |
| @@ -1140,7 +1140,7 @@ func (d *Daemon) handleHello(tr ports.Transport, f ports.Frame) { | |||||
| 1140 | return | 1140 | return | ||
| 1141 | } | 1141 | } | ||
| 1142 | if ac.proxied { | 1142 | if ac.proxied { | ||
| 1143 | - | meta, ok := sess.sessionMetaSnapshot() | 1143 | + | meta, ok := sess.sessionMetaSnapshotFor(ac) |
| 1144 | metaFrame, metaErr := frameSessionMeta(meta) | 1144 | metaFrame, metaErr := frameSessionMeta(meta) | ||
| 1145 | if !ok || metaErr != nil || ac.sendExpectedTransportForRole(postWelcomeToken.transport, metaFrame, postWelcomeTicket) != nil { | 1145 | if !ok || metaErr != nil || ac.sendExpectedTransportForRole(postWelcomeToken.transport, metaFrame, postWelcomeTicket) != nil { | ||
| 1146 | postWelcomeTicket.End() | 1146 | postWelcomeTicket.End() | ||
M internal/usecase/daemon/daemon_test.go +93 -2
| @@ -439,10 +439,101 @@ func newManualTabSession(t *testing.T, n int) (*Daemon, *session, *attachedClien | |||
| 439 | 439 | return d, sess, ac, sends, releases | |
| 440 | 440 | } | |
| 441 | 441 | ||
| 442 | - | func activeTabIndex(sess *session) int { | |
| 442 | + | func testAttachment(sess *session) *attachedClient { | |
| 443 | + | if sess == nil { | |
| 444 | + | return nil | |
| 445 | + | } | |
| 446 | + | attachments := sess.snapshotAttachments() | |
| 447 | + | if len(attachments) == 0 { | |
| 448 | + | return nil | |
| 449 | + | } | |
| 450 | + | return attachments[0] | |
| 451 | + | } | |
| 452 | + | ||
| 453 | + | func testAttachmentTabLocked(sess *session) *tab { | |
| 454 | + | if sess == nil { | |
| 455 | + | return nil | |
| 456 | + | } | |
| 457 | + | var ac *attachedClient | |
| 458 | + | for candidate := range sess.attachments { | |
| 459 | + | ac = candidate | |
| 460 | + | break | |
| 461 | + | } | |
| 462 | + | if ac == nil { | |
| 463 | + | if len(sess.tabs) == 0 { | |
| 464 | + | return nil | |
| 465 | + | } | |
| 466 | + | return sess.tabs[0] | |
| 467 | + | } | |
| 468 | + | view := ac.viewSnapshot() | |
| 469 | + | for _, tb := range sess.tabs { | |
| 470 | + | if domain.TabStableID(tb.stableID) == view.tabID { | |
| 471 | + | return tb | |
| 472 | + | } | |
| 473 | + | } | |
| 474 | + | return nil | |
| 475 | + | } | |
| 476 | + | ||
| 477 | + | func testAttachmentTab(sess *session) *tab { | |
| 478 | + | if ac := testAttachment(sess); ac != nil { | |
| 479 | + | return sess.tabForAttachment(ac) | |
| 480 | + | } | |
| 481 | + | return sess.firstTab() | |
| 482 | + | } | |
| 483 | + | ||
| 484 | + | func selectTestAttachmentTab(sess *session, idx int) bool { | |
| 485 | + | ac := testAttachment(sess) | |
| 486 | + | if ac == nil { | |
| 487 | + | return idx == 0 && testAttachmentTab(sess) != nil | |
| 488 | + | } | |
| 489 | + | return sess.switchAttachmentTab(ac, idx) | |
| 490 | + | } | |
| 491 | + | ||
| 492 | + | // selectTestAttachmentTabLocked is used only by fixtures that already hold | |
| 493 | + | // sess.mu; production navigation always goes through selectAttachmentTab. | |
| 494 | + | func selectTestAttachmentTabLocked(sess *session, idx int) bool { | |
| 495 | + | if sess == nil { | |
| 496 | + | return false | |
| 497 | + | } | |
| 498 | + | var ac *attachedClient | |
| 499 | + | for candidate := range sess.attachments { | |
| 500 | + | ac = candidate | |
| 501 | + | break | |
| 502 | + | } | |
| 503 | + | if ac == nil { | |
| 504 | + | return idx == 0 && len(sess.tabs) > 0 | |
| 505 | + | } | |
| 506 | + | return sess.activateAttachmentViewLocked(ac, idx) | |
| 507 | + | } | |
| 508 | + | ||
| 509 | + | func testAttachmentTabIndexLocked(sess *session) int { | |
| 510 | + | if sess == nil { | |
| 511 | + | return 0 | |
| 512 | + | } | |
| 513 | + | var ac *attachedClient | |
| 514 | + | for candidate := range sess.attachments { | |
| 515 | + | ac = candidate | |
| 516 | + | break | |
| 517 | + | } | |
| 518 | + | if ac == nil { | |
| 519 | + | return 0 | |
| 520 | + | } | |
| 521 | + | view := ac.viewSnapshot() | |
| 522 | + | for i, tb := range sess.tabs { | |
| 523 | + | if domain.TabStableID(tb.stableID) == view.tabID { | |
| 524 | + | return i | |
| 525 | + | } | |
| 526 | + | } | |
| 527 | + | return 0 | |
| 528 | + | } | |
| 529 | + | ||
| 530 | + | func testAttachmentTabIndex(sess *session) int { | |
| 531 | + | if sess == nil { | |
| 532 | + | return 0 | |
| 533 | + | } | |
| 443 | 534 | sess.mu.Lock() | |
| 444 | 535 | defer sess.mu.Unlock() | |
| 445 | - | return sess.active | |
| 536 | + | return testAttachmentTabIndexLocked(sess) | |
| 446 | 537 | } | |
| 447 | 538 | ||
| 448 | 539 | func testRow(text string) []renderer.Cell { | |
| @@ -439,10 +439,101 @@ func newManualTabSession(t *testing.T, n int) (*Daemon, *session, *attachedClien | |||||
| 439 | return d, sess, ac, sends, releases | 439 | return d, sess, ac, sends, releases | ||
| 440 | } | 440 | } | ||
| 441 | 441 | ||||
| 442 | - | func activeTabIndex(sess *session) int { | 442 | + | func testAttachment(sess *session) *attachedClient { |
| 443 | + | if sess == nil { | |||
| 444 | + | return nil | |||
| 445 | + | } | |||
| 446 | + | attachments := sess.snapshotAttachments() | |||
| 447 | + | if len(attachments) == 0 { | |||
| 448 | + | return nil | |||
| 449 | + | } | |||
| 450 | + | return attachments[0] | |||
| 451 | + | } | |||
| 452 | + | ||||
| 453 | + | func testAttachmentTabLocked(sess *session) *tab { | |||
| 454 | + | if sess == nil { | |||
| 455 | + | return nil | |||
| 456 | + | } | |||
| 457 | + | var ac *attachedClient | |||
| 458 | + | for candidate := range sess.attachments { | |||
| 459 | + | ac = candidate | |||
| 460 | + | break | |||
| 461 | + | } | |||
| 462 | + | if ac == nil { | |||
| 463 | + | if len(sess.tabs) == 0 { | |||
| 464 | + | return nil | |||
| 465 | + | } | |||
| 466 | + | return sess.tabs[0] | |||
| 467 | + | } | |||
| 468 | + | view := ac.viewSnapshot() | |||
| 469 | + | for _, tb := range sess.tabs { | |||
| 470 | + | if domain.TabStableID(tb.stableID) == view.tabID { | |||
| 471 | + | return tb | |||
| 472 | + | } | |||
| 473 | + | } | |||
| 474 | + | return nil | |||
| 475 | + | } | |||
| 476 | + | ||||
| 477 | + | func testAttachmentTab(sess *session) *tab { | |||
| 478 | + | if ac := testAttachment(sess); ac != nil { | |||
| 479 | + | return sess.tabForAttachment(ac) | |||
| 480 | + | } | |||
| 481 | + | return sess.firstTab() | |||
| 482 | + | } | |||
| 483 | + | ||||
| 484 | + | func selectTestAttachmentTab(sess *session, idx int) bool { | |||
| 485 | + | ac := testAttachment(sess) | |||
| 486 | + | if ac == nil { | |||
| 487 | + | return idx == 0 && testAttachmentTab(sess) != nil | |||
| 488 | + | } | |||
| 489 | + | return sess.switchAttachmentTab(ac, idx) | |||
| 490 | + | } | |||
| 491 | + | ||||
| 492 | + | // selectTestAttachmentTabLocked is used only by fixtures that already hold | |||
| 493 | + | // sess.mu; production navigation always goes through selectAttachmentTab. | |||
| 494 | + | func selectTestAttachmentTabLocked(sess *session, idx int) bool { | |||
| 495 | + | if sess == nil { | |||
| 496 | + | return false | |||
| 497 | + | } | |||
| 498 | + | var ac *attachedClient | |||
| 499 | + | for candidate := range sess.attachments { | |||
| 500 | + | ac = candidate | |||
| 501 | + | break | |||
| 502 | + | } | |||
| 503 | + | if ac == nil { | |||
| 504 | + | return idx == 0 && len(sess.tabs) > 0 | |||
| 505 | + | } | |||
| 506 | + | return sess.activateAttachmentViewLocked(ac, idx) | |||
| 507 | + | } | |||
| 508 | + | ||||
| 509 | + | func testAttachmentTabIndexLocked(sess *session) int { | |||
| 510 | + | if sess == nil { | |||
| 511 | + | return 0 | |||
| 512 | + | } | |||
| 513 | + | var ac *attachedClient | |||
| 514 | + | for candidate := range sess.attachments { | |||
| 515 | + | ac = candidate | |||
| 516 | + | break | |||
| 517 | + | } | |||
| 518 | + | if ac == nil { | |||
| 519 | + | return 0 | |||
| 520 | + | } | |||
| 521 | + | view := ac.viewSnapshot() | |||
| 522 | + | for i, tb := range sess.tabs { | |||
| 523 | + | if domain.TabStableID(tb.stableID) == view.tabID { | |||
| 524 | + | return i | |||
| 525 | + | } | |||
| 526 | + | } | |||
| 527 | + | return 0 | |||
| 528 | + | } | |||
| 529 | + | ||||
| 530 | + | func testAttachmentTabIndex(sess *session) int { | |||
| 531 | + | if sess == nil { | |||
| 532 | + | return 0 | |||
| 533 | + | } | |||
| 443 | sess.mu.Lock() | 534 | sess.mu.Lock() | ||
| 444 | defer sess.mu.Unlock() | 535 | defer sess.mu.Unlock() | ||
| 445 | - | return sess.active | 536 | + | return testAttachmentTabIndexLocked(sess) |
| 446 | } | 537 | } | ||
| 447 | 538 | ||||
| 448 | func testRow(text string) []renderer.Cell { | 539 | func testRow(text string) []renderer.Cell { | ||
M internal/usecase/daemon/floating.go +4 -1
| @@ -223,7 +223,10 @@ func (d *Daemon) toggleFloating(sess *session, ac *attachedClient) error { | |||
| 223 | 223 | if d == nil || sess == nil { | |
| 224 | 224 | return domain.UserErr(domain.NoticeFloatingSpawn, "couldn't open floating pane: no active session", nil) | |
| 225 | 225 | } | |
| 226 | - | tb := sess.tabForAttachmentOrActive(ac) | |
| 226 | + | tb := sess.tabForAttachment(ac) | |
| 227 | + | if ac == nil { | |
| 228 | + | tb = sess.firstTab() | |
| 229 | + | } | |
| 227 | 230 | if tb == nil { | |
| 228 | 231 | return domain.UserErr(domain.NoticeFloatingSpawn, "couldn't open floating pane: no active tab", layout.ErrNotFound) | |
| 229 | 232 | } | |
| @@ -223,7 +223,10 @@ func (d *Daemon) toggleFloating(sess *session, ac *attachedClient) error { | |||||
| 223 | if d == nil || sess == nil { | 223 | if d == nil || sess == nil { | ||
| 224 | return domain.UserErr(domain.NoticeFloatingSpawn, "couldn't open floating pane: no active session", nil) | 224 | return domain.UserErr(domain.NoticeFloatingSpawn, "couldn't open floating pane: no active session", nil) | ||
| 225 | } | 225 | } | ||
| 226 | - | tb := sess.tabForAttachmentOrActive(ac) | 226 | + | tb := sess.tabForAttachment(ac) |
| 227 | + | if ac == nil { | |||
| 228 | + | tb = sess.firstTab() | |||
| 229 | + | } | |||
| 227 | if tb == nil { | 230 | if tb == nil { | ||
| 228 | return domain.UserErr(domain.NoticeFloatingSpawn, "couldn't open floating pane: no active tab", layout.ErrNotFound) | 231 | return domain.UserErr(domain.NoticeFloatingSpawn, "couldn't open floating pane: no active tab", layout.ErrNotFound) | ||
| 229 | } | 232 | } | ||
M internal/usecase/daemon/floating_test.go +1 -1
| @@ -485,7 +485,7 @@ func TestFloatingEOFRepaintsVisibleSlotOnly(t *testing.T) { | |||
| 485 | 485 | t.Helper() | |
| 486 | 486 | normalPTY, releaseNormal := newBlockingPTY(t) | |
| 487 | 487 | d, sess, ac, sends := newManualSessionWithPTYs(t, normalPTY) | |
| 488 | - | tb := sess.activeTab() | |
| 488 | + | tb := testAttachmentTab(sess) | |
| 489 | 489 | require.NotNil(t, tb) | |
| 490 | 490 | tb.mu.Lock() | |
| 491 | 491 | normal := tb.focusedPane() | |
| @@ -485,7 +485,7 @@ func TestFloatingEOFRepaintsVisibleSlotOnly(t *testing.T) { | |||||
| 485 | t.Helper() | 485 | t.Helper() | ||
| 486 | normalPTY, releaseNormal := newBlockingPTY(t) | 486 | normalPTY, releaseNormal := newBlockingPTY(t) | ||
| 487 | d, sess, ac, sends := newManualSessionWithPTYs(t, normalPTY) | 487 | d, sess, ac, sends := newManualSessionWithPTYs(t, normalPTY) | ||
| 488 | - | tb := sess.activeTab() | 488 | + | tb := testAttachmentTab(sess) |
| 489 | require.NotNil(t, tb) | 489 | require.NotNil(t, tb) | ||
| 490 | tb.mu.Lock() | 490 | tb.mu.Lock() | ||
| 491 | normal := tb.focusedPane() | 491 | normal := tb.focusedPane() | ||
M internal/usecase/daemon/input.go +4 -4
| @@ -93,7 +93,7 @@ func (d *Daemon) handleMouse(ac *attachedClient, ev mouse.Event) { | |||
| 93 | 93 | invalidateRejectedLeftPointer(rt, ev) | |
| 94 | 94 | return | |
| 95 | 95 | } | |
| 96 | - | tb := sess.tabForAttachmentOrActive(ac) | |
| 96 | + | tb := sess.tabForAttachment(ac) | |
| 97 | 97 | if tb == nil { | |
| 98 | 98 | invalidateRejectedLeftPointer(rt, ev) | |
| 99 | 99 | return | |
| @@ -364,7 +364,7 @@ func (h daemonKeyHandler) Forward(data []byte) { | |||
| 364 | 364 | if owned { | |
| 365 | 365 | defer effect.End() | |
| 366 | 366 | } | |
| 367 | - | tb := sess.tabForAttachmentOrActive(h.ac) | |
| 367 | + | tb := sess.tabForAttachment(h.ac) | |
| 368 | 368 | if tb == nil { | |
| 369 | 369 | return | |
| 370 | 370 | } | |
| @@ -409,7 +409,7 @@ func (h daemonKeyHandler) Action(action keys.Action, _ []byte) { | |||
| 409 | 409 | case keys.ActionJumpAttention: | |
| 410 | 410 | if !proxiedJumpSearchesOtherSessions(h.ac.proxied) { | |
| 411 | 411 | if idx, ok := oldestAttentionTab(sess); ok && sess.switchAttachmentTab(h.ac, idx) { | |
| 412 | - | h.d.activateTabAfterResizeForLease(sess, sess.tabForAttachmentOrActive(h.ac), false, h.ac, nil) | |
| 412 | + | h.d.activateTabAfterResizeForLease(sess, sess.tabForAttachment(h.ac), false, h.ac, nil) | |
| 413 | 413 | h.d.invalidateRender(sess, h.ac, true, "input.go") | |
| 414 | 414 | } | |
| 415 | 415 | return | |
| @@ -458,7 +458,7 @@ func (h daemonKeyHandler) Action(action keys.Action, _ []byte) { | |||
| 458 | 458 | keys.ActionSwitchTab7, keys.ActionSwitchTab8, keys.ActionSwitchTab9: | |
| 459 | 459 | idx := int(action - keys.ActionSwitchTab1) | |
| 460 | 460 | if sess.switchAttachmentTab(h.ac, idx) { | |
| 461 | - | h.d.activateTabAfterResizeForLease(sess, sess.tabForAttachmentOrActive(h.ac), false, h.ac, nil) | |
| 461 | + | h.d.activateTabAfterResizeForLease(sess, sess.tabForAttachment(h.ac), false, h.ac, nil) | |
| 462 | 462 | h.d.invalidateRender(sess, h.ac, true, "input.go") | |
| 463 | 463 | } | |
| 464 | 464 | } | |
| @@ -93,7 +93,7 @@ func (d *Daemon) handleMouse(ac *attachedClient, ev mouse.Event) { | |||||
| 93 | invalidateRejectedLeftPointer(rt, ev) | 93 | invalidateRejectedLeftPointer(rt, ev) | ||
| 94 | return | 94 | return | ||
| 95 | } | 95 | } | ||
| 96 | - | tb := sess.tabForAttachmentOrActive(ac) | 96 | + | tb := sess.tabForAttachment(ac) |
| 97 | if tb == nil { | 97 | if tb == nil { | ||
| 98 | invalidateRejectedLeftPointer(rt, ev) | 98 | invalidateRejectedLeftPointer(rt, ev) | ||
| 99 | return | 99 | return | ||
| @@ -364,7 +364,7 @@ func (h daemonKeyHandler) Forward(data []byte) { | |||||
| 364 | if owned { | 364 | if owned { | ||
| 365 | defer effect.End() | 365 | defer effect.End() | ||
| 366 | } | 366 | } | ||
| 367 | - | tb := sess.tabForAttachmentOrActive(h.ac) | 367 | + | tb := sess.tabForAttachment(h.ac) |
| 368 | if tb == nil { | 368 | if tb == nil { | ||
| 369 | return | 369 | return | ||
| 370 | } | 370 | } | ||
| @@ -409,7 +409,7 @@ func (h daemonKeyHandler) Action(action keys.Action, _ []byte) { | |||||
| 409 | case keys.ActionJumpAttention: | 409 | case keys.ActionJumpAttention: | ||
| 410 | if !proxiedJumpSearchesOtherSessions(h.ac.proxied) { | 410 | if !proxiedJumpSearchesOtherSessions(h.ac.proxied) { | ||
| 411 | if idx, ok := oldestAttentionTab(sess); ok && sess.switchAttachmentTab(h.ac, idx) { | 411 | if idx, ok := oldestAttentionTab(sess); ok && sess.switchAttachmentTab(h.ac, idx) { | ||
| 412 | - | h.d.activateTabAfterResizeForLease(sess, sess.tabForAttachmentOrActive(h.ac), false, h.ac, nil) | 412 | + | h.d.activateTabAfterResizeForLease(sess, sess.tabForAttachment(h.ac), false, h.ac, nil) |
| 413 | h.d.invalidateRender(sess, h.ac, true, "input.go") | 413 | h.d.invalidateRender(sess, h.ac, true, "input.go") | ||
| 414 | } | 414 | } | ||
| 415 | return | 415 | return | ||
| @@ -458,7 +458,7 @@ func (h daemonKeyHandler) Action(action keys.Action, _ []byte) { | |||||
| 458 | keys.ActionSwitchTab7, keys.ActionSwitchTab8, keys.ActionSwitchTab9: | 458 | keys.ActionSwitchTab7, keys.ActionSwitchTab8, keys.ActionSwitchTab9: | ||
| 459 | idx := int(action - keys.ActionSwitchTab1) | 459 | idx := int(action - keys.ActionSwitchTab1) | ||
| 460 | if sess.switchAttachmentTab(h.ac, idx) { | 460 | if sess.switchAttachmentTab(h.ac, idx) { | ||
| 461 | - | h.d.activateTabAfterResizeForLease(sess, sess.tabForAttachmentOrActive(h.ac), false, h.ac, nil) | 461 | + | h.d.activateTabAfterResizeForLease(sess, sess.tabForAttachment(h.ac), false, h.ac, nil) |
| 462 | h.d.invalidateRender(sess, h.ac, true, "input.go") | 462 | h.d.invalidateRender(sess, h.ac, true, "input.go") | ||
| 463 | } | 463 | } | ||
| 464 | } | 464 | } | ||
M internal/usecase/daemon/input_test.go +40 -40
| @@ -197,7 +197,7 @@ func TestResizeActionAdaptersProduceEquivalentGeometry(t *testing.T) { | |||
| 197 | 197 | "pane-1": {}, "pane-2": {}, "pane-3": {}, | |
| 198 | 198 | } | |
| 199 | 199 | d, sess, ac, _ := newManualSessionWithPTYs(t, ptys["pane-1"]) | |
| 200 | - | tb := sess.activeTab() | |
| 200 | + | tb := testAttachmentTab(sess) | |
| 201 | 201 | p2 := newPane("pane-2", ptys["pane-2"], tb.size) | |
| 202 | 202 | p3 := newPane("pane-3", ptys["pane-3"], tb.size) | |
| 203 | 203 | tb.mu.Lock() | |
| @@ -401,7 +401,7 @@ func TestSwitchTabFirstFrameDoesNotReuseSamePaneIDCapture(t *testing.T) { | |||
| 401 | 401 | // Use the real key action: it requests the mandatory complete repaint for | |
| 402 | 402 | // the first target-tab frame. | |
| 403 | 403 | daemonKeyHandler{d: d, ac: ac}.Action(keys.ActionSwitchTab2, nil) | |
| 404 | - | require.Equal(t, 1, activeTabIndex(sess)) | |
| 404 | + | require.Equal(t, 1, testAttachmentTabIndex(sess)) | |
| 405 | 405 | ||
| 406 | 406 | second := awaitFrame(t, sends, ports.MsgOutput) | |
| 407 | 407 | secondOutput, err := ports.UnmarshalOutput(second.Payload) | |
| @@ -496,7 +496,7 @@ func TestAltFToggleRetainedFloatingPaneRepaintsImmediately(t *testing.T) { | |||
| 496 | 496 | floating := newPane("floating", floatingPTY, domain.Size{Cols: 20, Rows: 5}) | |
| 497 | 497 | floating.ctx = floatingCtx | |
| 498 | 498 | floating.screen.Write([]byte("popup-content")) | |
| 499 | - | installTestFloating(sess.activeTab(), floating, false) | |
| 499 | + | installTestFloating(testAttachmentTab(sess), floating, false) | |
| 500 | 500 | ||
| 501 | 501 | // Establish the client shadow while the retained popup is hidden. | |
| 502 | 502 | d.paint(sess, ac, true, nil) | |
| @@ -518,7 +518,7 @@ func TestAltFToggleRetainedFloatingPaneRepaintsImmediately(t *testing.T) { | |||
| 518 | 518 | d.handleInput(sess, ac, []byte("\x1bf")) | |
| 519 | 519 | mustApplyOutput(t, client, awaitFrame(t, sends, ports.MsgOutput)) | |
| 520 | 520 | require.NotContains(t, strings.Join(frameRows(client.Frame), "\n"), "popup-content") | |
| 521 | - | tb := sess.activeTab() | |
| 521 | + | tb := testAttachmentTab(sess) | |
| 522 | 522 | tb.mu.Lock() | |
| 523 | 523 | require.Equal(t, floatingHidden, tb.floating.state) | |
| 524 | 524 | require.Same(t, floating, tb.floating.pane) | |
| @@ -586,7 +586,7 @@ func TestFloatingKeyboardRoutesVisibleAndHiddenInput(t *testing.T) { | |||
| 586 | 586 | defer releaseNormal() | |
| 587 | 587 | defer releaseFloating() | |
| 588 | 588 | d, sess, ac, _ := newManualSessionWithPTYs(t, normal) | |
| 589 | - | installTestFloating(sess.activeTab(), newPane("floating", floating, domain.Size{Cols: 20, Rows: 5}), tc.visible) | |
| 589 | + | installTestFloating(testAttachmentTab(sess), newPane("floating", floating, domain.Size{Cols: 20, Rows: 5}), tc.visible) | |
| 590 | 590 | ||
| 591 | 591 | daemonKeyHandler{d: d, ac: ac}.Forward(tc.input) | |
| 592 | 592 | if tc.visible { | |
| @@ -608,7 +608,7 @@ func TestFloatingBracketedPasteAndGlobalActions(t *testing.T) { | |||
| 608 | 608 | defer releaseNormal() | |
| 609 | 609 | defer releaseFloating() | |
| 610 | 610 | d, sess, ac, _ := newManualSessionWithPTYs(t, normal) | |
| 611 | - | installTestFloating(sess.activeTab(), newPane("floating", floating, domain.Size{Cols: 20, Rows: 5}), true) | |
| 611 | + | installTestFloating(testAttachmentTab(sess), newPane("floating", floating, domain.Size{Cols: 20, Rows: 5}), true) | |
| 612 | 612 | ||
| 613 | 613 | paste := []byte("\x1b[200~paste\x1bf\x1b[201~") | |
| 614 | 614 | d.handleInput(sess, ac, paste) | |
| @@ -626,7 +626,7 @@ func TestFloatingStaysTerminalTargetAfterDirectionalFocus(t *testing.T) { | |||
| 626 | 626 | defer releaseNormal() | |
| 627 | 627 | defer releaseFloating() | |
| 628 | 628 | d, sess, ac, _ := newManualSessionWithPTYs(t, normal) | |
| 629 | - | tb := sess.activeTab() | |
| 629 | + | tb := testAttachmentTab(sess) | |
| 630 | 630 | second := newPane("pane-2", nil, domain.Size{Cols: 40, Rows: 5}) | |
| 631 | 631 | tb.mu.Lock() | |
| 632 | 632 | tb.size = domain.Size{Cols: 81, Rows: 5} | |
| @@ -658,7 +658,7 @@ func TestActionFocusPaneAtEdgeStaysSilent(t *testing.T) { | |||
| 658 | 658 | // the user as a notice instead of being silently discarded. | |
| 659 | 659 | func TestActionFocusPaneGenuineErrorReportsNoticeInternal(t *testing.T) { | |
| 660 | 660 | d, sess, ac, _ := newManualSessionWithPTYs(t, nil) | |
| 661 | - | tb := sess.activeTab() | |
| 661 | + | tb := testAttachmentTab(sess) | |
| 662 | 662 | tb.mu.Lock() | |
| 663 | 663 | tb.tree = nil | |
| 664 | 664 | tb.mu.Unlock() | |
| @@ -675,7 +675,7 @@ func TestActionFocusPaneGenuineErrorReportsNoticeInternal(t *testing.T) { | |||
| 675 | 675 | ||
| 676 | 676 | func TestFloatingVisibilityRemainsIndependentAcrossTabSwitches(t *testing.T) { | |
| 677 | 677 | d, sess, ac, _ := newManualSessionWithPTYs(t, nil) | |
| 678 | - | first := sess.activeTab() | |
| 678 | + | first := testAttachmentTab(sess) | |
| 679 | 679 | second := newTab(nil, first.size) | |
| 680 | 680 | installTestFloating(first, newPane("floating", nil, domain.Size{Cols: 20, Rows: 5}), true) | |
| 681 | 681 | installTestFloating(second, newPane("floating", nil, domain.Size{Cols: 20, Rows: 5}), false) | |
| @@ -699,7 +699,7 @@ func TestFloatingMouseTranslatesSGRToInnerCoordinates(t *testing.T) { | |||
| 699 | 699 | floatingPTY := portsmocks.NewMockPTY(t) | |
| 700 | 700 | floatingPTY.EXPECT().Write([]byte("\x1b[<0;1;1M")).Return(len("\x1b[<0;1;1M"), nil).Once() | |
| 701 | 701 | d, sess, ac, _ := newManualSessionWithPTYs(t, normal) | |
| 702 | - | tb := sess.activeTab() | |
| 702 | + | tb := testAttachmentTab(sess) | |
| 703 | 703 | tb.mu.Lock() | |
| 704 | 704 | tb.size = domain.Size{Cols: 100, Rows: 20} | |
| 705 | 705 | tb.mu.Unlock() | |
| @@ -716,7 +716,7 @@ func TestFloatingMouseIgnoresBorderAndOutside(t *testing.T) { | |||
| 716 | 716 | normal := portsmocks.NewMockPTY(t) | |
| 717 | 717 | floatingPTY := portsmocks.NewMockPTY(t) | |
| 718 | 718 | d, sess, ac, _ := newManualSessionWithPTYs(t, normal) | |
| 719 | - | tb := sess.activeTab() | |
| 719 | + | tb := testAttachmentTab(sess) | |
| 720 | 720 | tb.mu.Lock() | |
| 721 | 721 | tb.size = domain.Size{Cols: 100, Rows: 20} | |
| 722 | 722 | tb.mu.Unlock() | |
| @@ -736,7 +736,7 @@ func TestResponsiveDrawerFloatingMouseRoutesOnlyInnerClientCells(t *testing.T) { | |||
| 736 | 736 | floatingPTY := portsmocks.NewMockPTY(t) | |
| 737 | 737 | floatingPTY.EXPECT().Write([]byte("\x1b[<0;1;1M")).Return(len("\x1b[<0;1;1M"), nil).Once() | |
| 738 | 738 | d, sess, ac, _ := newManualSessionWithPTYs(t, normal) | |
| 739 | - | tb := sess.activeTab() | |
| 739 | + | tb := testAttachmentTab(sess) | |
| 740 | 740 | complete := domain.Size{Cols: 79, Rows: 25} | |
| 741 | 741 | content := tabSize(complete) | |
| 742 | 742 | geometry := calculateContentFloatingGeometry(content, d.currentFloatingConfig()) | |
| @@ -760,7 +760,7 @@ func TestResponsiveDrawerCopyMouseMapsInnerAndPinsDragAwayFromChrome(t *testing. | |||
| 760 | 760 | p, release := newBlockingPTY(t) | |
| 761 | 761 | defer release() | |
| 762 | 762 | d, sess, ac, _ := newManualSessionWithPTYs(t, p) | |
| 763 | - | tb := sess.activeTab() | |
| 763 | + | tb := testAttachmentTab(sess) | |
| 764 | 764 | complete := domain.Size{Cols: 79, Rows: 25} | |
| 765 | 765 | content := tabSize(complete) | |
| 766 | 766 | geometry := calculateContentFloatingGeometry(content, d.currentFloatingConfig()) | |
| @@ -960,12 +960,12 @@ func TestPaletteNextPreviousSwitchActiveTab(t *testing.T) { | |||
| 960 | 960 | release() | |
| 961 | 961 | } | |
| 962 | 962 | }() | |
| 963 | - | sess.active = tc.start | |
| 963 | + | selectTestAttachmentTab(sess, tc.start) | |
| 964 | 964 | ||
| 965 | 965 | d.handleInput(sess, ac, []byte("\x1b ")) | |
| 966 | 966 | d.handleInput(sess, ac, tc.query) | |
| 967 | 967 | ||
| 968 | - | require.Equal(t, tc.wantIndex, activeTabIndex(sess)) | |
| 968 | + | require.Equal(t, tc.wantIndex, testAttachmentTabIndex(sess)) | |
| 969 | 969 | }) | |
| 970 | 970 | } | |
| 971 | 971 | } | |
| @@ -1200,14 +1200,14 @@ func TestAltXClosesActiveTabAndSelectsRemaining(t *testing.T) { | |||
| 1200 | 1200 | p2, releasePTY2 := newBlockingPTY(t) | |
| 1201 | 1201 | p3, releasePTY3 := newBlockingPTYWithWrites(t, writes) | |
| 1202 | 1202 | d, sess, ac, _ := newManualSessionWithPTYs(t, p1, p2, p3) | |
| 1203 | - | sess.active = 1 | |
| 1203 | + | selectTestAttachmentTab(sess, 1) | |
| 1204 | 1204 | ||
| 1205 | 1205 | d.handleInput(sess, ac, []byte("\x1b ")) | |
| 1206 | 1206 | d.handleInput(sess, ac, []byte("CLT\r")) | |
| 1207 | 1207 | ||
| 1208 | 1208 | require.Equal(t, 1, sessionCount(d)) | |
| 1209 | 1209 | require.Len(t, sess.tabs, 2) | |
| 1210 | - | require.Equal(t, 1, activeTabIndex(sess), "closing middle tab selects the next remaining tab") | |
| 1210 | + | require.Equal(t, 1, testAttachmentTabIndex(sess), "closing middle tab selects the next remaining tab") | |
| 1211 | 1211 | d.handleInput(sess, ac, []byte("Z")) | |
| 1212 | 1212 | require.Eventually(t, func() bool { return len(writes) == 1 }, 2*time.Second, 5*time.Millisecond) | |
| 1213 | 1213 | requirePTYWrite(t, writes, []byte("Z")) | |
| @@ -1258,7 +1258,7 @@ func TestRNTOpensPromptAndRenamesActiveTab(t *testing.T) { | |||
| 1258 | 1258 | release() | |
| 1259 | 1259 | } | |
| 1260 | 1260 | }() | |
| 1261 | - | sess.active = 1 | |
| 1261 | + | selectTestAttachmentTab(sess, 1) | |
| 1262 | 1262 | ||
| 1263 | 1263 | d.handleInput(sess, ac, []byte("\x1b ")) | |
| 1264 | 1264 | awaitFrame(t, sends, ports.MsgOutput) | |
| @@ -1639,7 +1639,7 @@ func TestMouseWheelOverUnfocusedPaneDoesNotFocusAndForwardsChildMouse(t *testing | |||
| 1639 | 1639 | p2.EXPECT().Write([]byte("\x1b[<64;1;1M")).Return(len("\x1b[<64;1;1M"), nil).Once() | |
| 1640 | 1640 | d, sess, ac, _ := newManualSessionWithPTYs(t, p1) | |
| 1641 | 1641 | d.procComm = nil | |
| 1642 | - | tb := sess.activeTab() | |
| 1642 | + | tb := testAttachmentTab(sess) | |
| 1643 | 1643 | p2pane := newPane("pane-2", p2, domain.Size{Cols: 20, Rows: 5}) | |
| 1644 | 1644 | p2pane.screen.Write([]byte("\x1b[?1000h\x1b[?1006h")) | |
| 1645 | 1645 | tb.mu.Lock() | |
| @@ -1663,7 +1663,7 @@ func TestRejectedLeftReleaseInvalidatesFreshPointerBeforeMotion(t *testing.T) { | |||
| 1663 | 1663 | { | |
| 1664 | 1664 | name: "split divider", | |
| 1665 | 1665 | setup: func(t *testing.T, d *Daemon, sess *session, _ *attachedClient, _ *[]byte) []byte { | |
| 1666 | - | tb := sess.activeTab() | |
| 1666 | + | tb := testAttachmentTab(sess) | |
| 1667 | 1667 | p2 := newPane("pane-2", nil, domain.Size{Cols: 20, Rows: 10}) | |
| 1668 | 1668 | tb.mu.Lock() | |
| 1669 | 1669 | tb.size = domain.Size{Cols: 41, Rows: 10} | |
| @@ -1676,7 +1676,7 @@ func TestRejectedLeftReleaseInvalidatesFreshPointerBeforeMotion(t *testing.T) { | |||
| 1676 | 1676 | { | |
| 1677 | 1677 | name: "split title bar", | |
| 1678 | 1678 | setup: func(t *testing.T, d *Daemon, sess *session, _ *attachedClient, _ *[]byte) []byte { | |
| 1679 | - | tb := sess.activeTab() | |
| 1679 | + | tb := testAttachmentTab(sess) | |
| 1680 | 1680 | p2 := newPane("pane-2", nil, domain.Size{Cols: 20, Rows: 10}) | |
| 1681 | 1681 | tb.mu.Lock() | |
| 1682 | 1682 | tb.size = domain.Size{Cols: 41, Rows: 10} | |
| @@ -1689,7 +1689,7 @@ func TestRejectedLeftReleaseInvalidatesFreshPointerBeforeMotion(t *testing.T) { | |||
| 1689 | 1689 | { | |
| 1690 | 1690 | name: "floating exterior", | |
| 1691 | 1691 | setup: func(t *testing.T, d *Daemon, sess *session, _ *attachedClient, press *[]byte) []byte { | |
| 1692 | - | tb := sess.activeTab() | |
| 1692 | + | tb := testAttachmentTab(sess) | |
| 1693 | 1693 | floating := newPane("floating", nil, domain.Size{Cols: 20, Rows: 5}) | |
| 1694 | 1694 | installTestFloating(tb, floating, true) | |
| 1695 | 1695 | tb.mu.Lock() | |
| @@ -1749,7 +1749,7 @@ func TestPressAtFormerStackTitleRowIsTreatedAsExpandedContent(t *testing.T) { | |||
| 1749 | 1749 | p, release := newBlockingPTY(t) | |
| 1750 | 1750 | defer release() | |
| 1751 | 1751 | d, sess, ac, _ := newManualSessionWithPTYs(t, p) | |
| 1752 | - | tb := sess.activeTab() | |
| 1752 | + | tb := testAttachmentTab(sess) | |
| 1753 | 1753 | p2 := newPane("pane-2", nil, domain.Size{Cols: 20, Rows: 9}) | |
| 1754 | 1754 | p3 := newPane("pane-3", nil, domain.Size{Cols: 20, Rows: 1}) | |
| 1755 | 1755 | tb.mu.Lock() | |
| @@ -1821,7 +1821,7 @@ func TestMouseDividerAndTitleBarDoNotForwardBogusCoordinates(t *testing.T) { | |||
| 1821 | 1821 | p2.EXPECT().Resize(mock.Anything).Return(nil).Maybe() | |
| 1822 | 1822 | d, sess, ac, _ := newManualSessionWithPTYs(t, p1) | |
| 1823 | 1823 | d.procComm = nil | |
| 1824 | - | tb := sess.activeTab() | |
| 1824 | + | tb := testAttachmentTab(sess) | |
| 1825 | 1825 | tb.focusedPane().screen.Write([]byte("\x1b[?1000h\x1b[?1006h")) | |
| 1826 | 1826 | tb.mu.Lock() | |
| 1827 | 1827 | tb.size = tc.size | |
| @@ -1844,7 +1844,7 @@ func TestCopyModeDragOutsideSplitPaneClampsToPaneContent(t *testing.T) { | |||
| 1844 | 1844 | p2.EXPECT().Resize(domain.Size{Cols: 20, Rows: 10}).Return(nil).Maybe() | |
| 1845 | 1845 | d, sess, ac, sends := newManualSessionWithPTYs(t, p1) | |
| 1846 | 1846 | d.procComm = nil | |
| 1847 | - | tb := sess.activeTab() | |
| 1847 | + | tb := testAttachmentTab(sess) | |
| 1848 | 1848 | for i := range 10 { | |
| 1849 | 1849 | copy(tb.focusedPane().screen.Frame.Row(i), testRow(string(rune('a'+i)))) | |
| 1850 | 1850 | } | |
| @@ -1878,7 +1878,7 @@ func TestMouseHitTestFocusesPaneAndTranslatesSGRColumns(t *testing.T) { | |||
| 1878 | 1878 | p2.EXPECT().Write([]byte("\x1b[<0;1;1M")).Return(len("\x1b[<0;1;1M"), nil).Once() | |
| 1879 | 1879 | d, sess, ac, _ := newManualSessionWithPTYs(t, p1) | |
| 1880 | 1880 | d.procComm = nil | |
| 1881 | - | tb := sess.activeTab() | |
| 1881 | + | tb := testAttachmentTab(sess) | |
| 1882 | 1882 | p2pane := newPane("pane-2", p2, domain.Size{Cols: 20, Rows: 5}) | |
| 1883 | 1883 | p2pane.screen.Write([]byte("\x1b[?1000h\x1b[?1006h")) | |
| 1884 | 1884 | tb.mu.Lock() | |
| @@ -1905,7 +1905,7 @@ func TestMouseGatedWhileNoticesOverlayActive(t *testing.T) { | |||
| 1905 | 1905 | p2.EXPECT().Resize(domain.Size{Cols: 20, Rows: 5}).Return(nil).Maybe() | |
| 1906 | 1906 | d, sess, ac, sends := newManualSessionWithPTYs(t, p1) | |
| 1907 | 1907 | d.procComm = nil | |
| 1908 | - | tb := sess.activeTab() | |
| 1908 | + | tb := testAttachmentTab(sess) | |
| 1909 | 1909 | p2pane := newPane("pane-2", p2, domain.Size{Cols: 20, Rows: 5}) | |
| 1910 | 1910 | p2pane.screen.Write([]byte("\x1b[?1000h\x1b[?1006h")) | |
| 1911 | 1911 | tb.mu.Lock() | |
| @@ -1935,7 +1935,7 @@ func TestMouseCollapsedStackBarExpandsAndFocuses(t *testing.T) { | |||
| 1935 | 1935 | p2.EXPECT().Resize(domain.Size{Cols: 20, Rows: 4}).Return(nil).Maybe() | |
| 1936 | 1936 | d, sess, ac, _ := newManualSessionWithPTYs(t, p1) | |
| 1937 | 1937 | d.procComm = nil | |
| 1938 | - | tb := sess.activeTab() | |
| 1938 | + | tb := testAttachmentTab(sess) | |
| 1939 | 1939 | p2pane := newPane("pane-2", p2, domain.Size{Cols: 20, Rows: 3}) | |
| 1940 | 1940 | tb.mu.Lock() | |
| 1941 | 1941 | tb.size = domain.Size{Cols: 20, Rows: 5} | |
| @@ -1954,7 +1954,7 @@ func TestActiveCopyPressOnOtherPaneFocusesAndExitsBeforeFutureInput(t *testing.T | |||
| 1954 | 1954 | p1, release := newBlockingPTY(t) | |
| 1955 | 1955 | defer release() | |
| 1956 | 1956 | d, sess, ac, _ := newManualSessionWithPTYs(t, p1) | |
| 1957 | - | tb := sess.activeTab() | |
| 1957 | + | tb := testAttachmentTab(sess) | |
| 1958 | 1958 | p2 := newPane("pane-2", nil, domain.Size{Cols: 20, Rows: 10}) | |
| 1959 | 1959 | tb.mu.Lock() | |
| 1960 | 1960 | tb.size = domain.Size{Cols: 41, Rows: 10} | |
| @@ -2053,8 +2053,8 @@ func TestActiveCopyMouseIgnoresStalePointerResets(t *testing.T) { | |||
| 2053 | 2053 | name: "press-owned release rejects mapping", | |
| 2054 | 2054 | prepare: func(t *testing.T, d *Daemon, sess *session, ac *attachedClient) mouse.Event { | |
| 2055 | 2055 | t.Helper() | |
| 2056 | - | p := sess.activeTab().focusedPane() | |
| 2057 | - | tb := sess.activeTab() | |
| 2056 | + | p := testAttachmentTab(sess).focusedPane() | |
| 2057 | + | tb := testAttachmentTab(sess) | |
| 2058 | 2058 | tb.mu.Lock() | |
| 2059 | 2059 | geometry, ok := hitTestCopyMouseGeometryLocked(tb, d.currentFloatingConfig(), 1, 2) | |
| 2060 | 2060 | tb.mu.Unlock() | |
| @@ -2069,7 +2069,7 @@ func TestActiveCopyMouseIgnoresStalePointerResets(t *testing.T) { | |||
| 2069 | 2069 | name: "current geometry disappears", | |
| 2070 | 2070 | prepare: func(t *testing.T, _ *Daemon, sess *session, _ *attachedClient) mouse.Event { | |
| 2071 | 2071 | t.Helper() | |
| 2072 | - | tb := sess.activeTab() | |
| 2072 | + | tb := testAttachmentTab(sess) | |
| 2073 | 2073 | tb.mu.Lock() | |
| 2074 | 2074 | tb.tree = nil | |
| 2075 | 2075 | tb.mu.Unlock() | |
| @@ -2080,7 +2080,7 @@ func TestActiveCopyMouseIgnoresStalePointerResets(t *testing.T) { | |||
| 2080 | 2080 | name: "fresh press rejects document mapping", | |
| 2081 | 2081 | prepare: func(t *testing.T, _ *Daemon, sess *session, _ *attachedClient) mouse.Event { | |
| 2082 | 2082 | t.Helper() | |
| 2083 | - | tb := sess.activeTab() | |
| 2083 | + | tb := testAttachmentTab(sess) | |
| 2084 | 2084 | tb.mu.Lock() | |
| 2085 | 2085 | tb.size.Rows = 40 // valid layout coordinate, outside the old document. | |
| 2086 | 2086 | tb.mu.Unlock() | |
| @@ -2109,7 +2109,7 @@ func TestActiveCopyMouseIgnoresStalePointerResets(t *testing.T) { | |||
| 2109 | 2109 | } | |
| 2110 | 2110 | defer func() { d.beforeCopyMouseMap = nil }() | |
| 2111 | 2111 | ||
| 2112 | - | d.handleActiveCopyMouse(sess, ac, sess.activeTab(), ev) | |
| 2112 | + | d.handleActiveCopyMouse(sess, ac, testAttachmentTab(sess), ev) | |
| 2113 | 2113 | ||
| 2114 | 2114 | ac.overlays.copyMu.Lock() | |
| 2115 | 2115 | defer ac.overlays.copyMu.Unlock() | |
| @@ -2128,7 +2128,7 @@ func TestActiveCopyMouseRejectsViewportChangeAfterMappingSnapshot(t *testing.T) | |||
| 2128 | 2128 | p, release := newBlockingPTY(t) | |
| 2129 | 2129 | defer release() | |
| 2130 | 2130 | d, sess, ac, sends := newManualSessionWithPTYs(t, p) | |
| 2131 | - | pane := sess.activeTab().focusedPane() | |
| 2131 | + | pane := testAttachmentTab(sess).focusedPane() | |
| 2132 | 2132 | for range 8 { | |
| 2133 | 2133 | appendHistoryRow(t, pane.history, testRow("history")) | |
| 2134 | 2134 | } | |
| @@ -2162,13 +2162,13 @@ func TestFreshCopyPressUsesFrameAbsoluteHitTestGeometry(t *testing.T) { | |||
| 2162 | 2162 | { | |
| 2163 | 2163 | name: "mono", | |
| 2164 | 2164 | setup: func(_ *testing.T, _ *Daemon, sess *session) (*pane, mouse.Event) { | |
| 2165 | - | return sess.activeTab().focusedPane(), mouse.Event{Button: mouse.Left, Type: mouse.Press, Col: 1, Row: 1} | |
| 2165 | + | return testAttachmentTab(sess).focusedPane(), mouse.Event{Button: mouse.Left, Type: mouse.Press, Col: 1, Row: 1} | |
| 2166 | 2166 | }, | |
| 2167 | 2167 | }, | |
| 2168 | 2168 | { | |
| 2169 | 2169 | name: "split", | |
| 2170 | 2170 | setup: func(_ *testing.T, _ *Daemon, sess *session) (*pane, mouse.Event) { | |
| 2171 | - | tb := sess.activeTab() | |
| 2171 | + | tb := testAttachmentTab(sess) | |
| 2172 | 2172 | p2 := newPane("pane-2", nil, domain.Size{Cols: 20, Rows: 10}) | |
| 2173 | 2173 | tb.mu.Lock() | |
| 2174 | 2174 | tb.size = domain.Size{Cols: 41, Rows: 10} | |
| @@ -2182,8 +2182,8 @@ func TestFreshCopyPressUsesFrameAbsoluteHitTestGeometry(t *testing.T) { | |||
| 2182 | 2182 | name: "floating", | |
| 2183 | 2183 | setup: func(t *testing.T, d *Daemon, sess *session) (*pane, mouse.Event) { | |
| 2184 | 2184 | p := newPane("floating", nil, domain.Size{Cols: 20, Rows: 5}) | |
| 2185 | - | installTestFloating(sess.activeTab(), p, true) | |
| 2186 | - | tb := sess.activeTab() | |
| 2185 | + | installTestFloating(testAttachmentTab(sess), p, true) | |
| 2186 | + | tb := testAttachmentTab(sess) | |
| 2187 | 2187 | tb.mu.Lock() | |
| 2188 | 2188 | _, geometry, visible := tb.visibleFloatingSnapshotLocked(d.currentFloatingConfig()) | |
| 2189 | 2189 | tb.mu.Unlock() | |
| @@ -2197,7 +2197,7 @@ func TestFreshCopyPressUsesFrameAbsoluteHitTestGeometry(t *testing.T) { | |||
| 2197 | 2197 | defer release() | |
| 2198 | 2198 | d, sess, ac, _ := newManualSessionWithPTYs(t, p) | |
| 2199 | 2199 | wantPane, ev := tc.setup(t, d, sess) | |
| 2200 | - | tb := sess.activeTab() | |
| 2200 | + | tb := testAttachmentTab(sess) | |
| 2201 | 2201 | tb.mu.Lock() | |
| 2202 | 2202 | wantGeometry, ok := hitTestCopyMouseGeometryLocked(tb, d.currentFloatingConfig(), ev.Col, ev.Row) | |
| 2203 | 2203 | tb.mu.Unlock() | |
| @@ -197,7 +197,7 @@ func TestResizeActionAdaptersProduceEquivalentGeometry(t *testing.T) { | |||||
| 197 | "pane-1": {}, "pane-2": {}, "pane-3": {}, | 197 | "pane-1": {}, "pane-2": {}, "pane-3": {}, | ||
| 198 | } | 198 | } | ||
| 199 | d, sess, ac, _ := newManualSessionWithPTYs(t, ptys["pane-1"]) | 199 | d, sess, ac, _ := newManualSessionWithPTYs(t, ptys["pane-1"]) | ||
| 200 | - | tb := sess.activeTab() | 200 | + | tb := testAttachmentTab(sess) |
| 201 | p2 := newPane("pane-2", ptys["pane-2"], tb.size) | 201 | p2 := newPane("pane-2", ptys["pane-2"], tb.size) | ||
| 202 | p3 := newPane("pane-3", ptys["pane-3"], tb.size) | 202 | p3 := newPane("pane-3", ptys["pane-3"], tb.size) | ||
| 203 | tb.mu.Lock() | 203 | tb.mu.Lock() | ||
| @@ -401,7 +401,7 @@ func TestSwitchTabFirstFrameDoesNotReuseSamePaneIDCapture(t *testing.T) { | |||||
| 401 | // Use the real key action: it requests the mandatory complete repaint for | 401 | // Use the real key action: it requests the mandatory complete repaint for | ||
| 402 | // the first target-tab frame. | 402 | // the first target-tab frame. | ||
| 403 | daemonKeyHandler{d: d, ac: ac}.Action(keys.ActionSwitchTab2, nil) | 403 | daemonKeyHandler{d: d, ac: ac}.Action(keys.ActionSwitchTab2, nil) | ||
| 404 | - | require.Equal(t, 1, activeTabIndex(sess)) | 404 | + | require.Equal(t, 1, testAttachmentTabIndex(sess)) |
| 405 | 405 | ||||
| 406 | second := awaitFrame(t, sends, ports.MsgOutput) | 406 | second := awaitFrame(t, sends, ports.MsgOutput) | ||
| 407 | secondOutput, err := ports.UnmarshalOutput(second.Payload) | 407 | secondOutput, err := ports.UnmarshalOutput(second.Payload) | ||
| @@ -496,7 +496,7 @@ func TestAltFToggleRetainedFloatingPaneRepaintsImmediately(t *testing.T) { | |||||
| 496 | floating := newPane("floating", floatingPTY, domain.Size{Cols: 20, Rows: 5}) | 496 | floating := newPane("floating", floatingPTY, domain.Size{Cols: 20, Rows: 5}) | ||
| 497 | floating.ctx = floatingCtx | 497 | floating.ctx = floatingCtx | ||
| 498 | floating.screen.Write([]byte("popup-content")) | 498 | floating.screen.Write([]byte("popup-content")) | ||
| 499 | - | installTestFloating(sess.activeTab(), floating, false) | 499 | + | installTestFloating(testAttachmentTab(sess), floating, false) |
| 500 | 500 | ||||
| 501 | // Establish the client shadow while the retained popup is hidden. | 501 | // Establish the client shadow while the retained popup is hidden. | ||
| 502 | d.paint(sess, ac, true, nil) | 502 | d.paint(sess, ac, true, nil) | ||
| @@ -518,7 +518,7 @@ func TestAltFToggleRetainedFloatingPaneRepaintsImmediately(t *testing.T) { | |||||
| 518 | d.handleInput(sess, ac, []byte("\x1bf")) | 518 | d.handleInput(sess, ac, []byte("\x1bf")) | ||
| 519 | mustApplyOutput(t, client, awaitFrame(t, sends, ports.MsgOutput)) | 519 | mustApplyOutput(t, client, awaitFrame(t, sends, ports.MsgOutput)) | ||
| 520 | require.NotContains(t, strings.Join(frameRows(client.Frame), "\n"), "popup-content") | 520 | require.NotContains(t, strings.Join(frameRows(client.Frame), "\n"), "popup-content") | ||
| 521 | - | tb := sess.activeTab() | 521 | + | tb := testAttachmentTab(sess) |
| 522 | tb.mu.Lock() | 522 | tb.mu.Lock() | ||
| 523 | require.Equal(t, floatingHidden, tb.floating.state) | 523 | require.Equal(t, floatingHidden, tb.floating.state) | ||
| 524 | require.Same(t, floating, tb.floating.pane) | 524 | require.Same(t, floating, tb.floating.pane) | ||
| @@ -586,7 +586,7 @@ func TestFloatingKeyboardRoutesVisibleAndHiddenInput(t *testing.T) { | |||||
| 586 | defer releaseNormal() | 586 | defer releaseNormal() | ||
| 587 | defer releaseFloating() | 587 | defer releaseFloating() | ||
| 588 | d, sess, ac, _ := newManualSessionWithPTYs(t, normal) | 588 | d, sess, ac, _ := newManualSessionWithPTYs(t, normal) | ||
| 589 | - | installTestFloating(sess.activeTab(), newPane("floating", floating, domain.Size{Cols: 20, Rows: 5}), tc.visible) | 589 | + | installTestFloating(testAttachmentTab(sess), newPane("floating", floating, domain.Size{Cols: 20, Rows: 5}), tc.visible) |
| 590 | 590 | ||||
| 591 | daemonKeyHandler{d: d, ac: ac}.Forward(tc.input) | 591 | daemonKeyHandler{d: d, ac: ac}.Forward(tc.input) | ||
| 592 | if tc.visible { | 592 | if tc.visible { | ||
| @@ -608,7 +608,7 @@ func TestFloatingBracketedPasteAndGlobalActions(t *testing.T) { | |||||
| 608 | defer releaseNormal() | 608 | defer releaseNormal() | ||
| 609 | defer releaseFloating() | 609 | defer releaseFloating() | ||
| 610 | d, sess, ac, _ := newManualSessionWithPTYs(t, normal) | 610 | d, sess, ac, _ := newManualSessionWithPTYs(t, normal) | ||
| 611 | - | installTestFloating(sess.activeTab(), newPane("floating", floating, domain.Size{Cols: 20, Rows: 5}), true) | 611 | + | installTestFloating(testAttachmentTab(sess), newPane("floating", floating, domain.Size{Cols: 20, Rows: 5}), true) |
| 612 | 612 | ||||
| 613 | paste := []byte("\x1b[200~paste\x1bf\x1b[201~") | 613 | paste := []byte("\x1b[200~paste\x1bf\x1b[201~") | ||
| 614 | d.handleInput(sess, ac, paste) | 614 | d.handleInput(sess, ac, paste) | ||
| @@ -626,7 +626,7 @@ func TestFloatingStaysTerminalTargetAfterDirectionalFocus(t *testing.T) { | |||||
| 626 | defer releaseNormal() | 626 | defer releaseNormal() | ||
| 627 | defer releaseFloating() | 627 | defer releaseFloating() | ||
| 628 | d, sess, ac, _ := newManualSessionWithPTYs(t, normal) | 628 | d, sess, ac, _ := newManualSessionWithPTYs(t, normal) | ||
| 629 | - | tb := sess.activeTab() | 629 | + | tb := testAttachmentTab(sess) |
| 630 | second := newPane("pane-2", nil, domain.Size{Cols: 40, Rows: 5}) | 630 | second := newPane("pane-2", nil, domain.Size{Cols: 40, Rows: 5}) | ||
| 631 | tb.mu.Lock() | 631 | tb.mu.Lock() | ||
| 632 | tb.size = domain.Size{Cols: 81, Rows: 5} | 632 | tb.size = domain.Size{Cols: 81, Rows: 5} | ||
| @@ -658,7 +658,7 @@ func TestActionFocusPaneAtEdgeStaysSilent(t *testing.T) { | |||||
| 658 | // the user as a notice instead of being silently discarded. | 658 | // the user as a notice instead of being silently discarded. | ||
| 659 | func TestActionFocusPaneGenuineErrorReportsNoticeInternal(t *testing.T) { | 659 | func TestActionFocusPaneGenuineErrorReportsNoticeInternal(t *testing.T) { | ||
| 660 | d, sess, ac, _ := newManualSessionWithPTYs(t, nil) | 660 | d, sess, ac, _ := newManualSessionWithPTYs(t, nil) | ||
| 661 | - | tb := sess.activeTab() | 661 | + | tb := testAttachmentTab(sess) |
| 662 | tb.mu.Lock() | 662 | tb.mu.Lock() | ||
| 663 | tb.tree = nil | 663 | tb.tree = nil | ||
| 664 | tb.mu.Unlock() | 664 | tb.mu.Unlock() | ||
| @@ -675,7 +675,7 @@ func TestActionFocusPaneGenuineErrorReportsNoticeInternal(t *testing.T) { | |||||
| 675 | 675 | ||||
| 676 | func TestFloatingVisibilityRemainsIndependentAcrossTabSwitches(t *testing.T) { | 676 | func TestFloatingVisibilityRemainsIndependentAcrossTabSwitches(t *testing.T) { | ||
| 677 | d, sess, ac, _ := newManualSessionWithPTYs(t, nil) | 677 | d, sess, ac, _ := newManualSessionWithPTYs(t, nil) | ||
| 678 | - | first := sess.activeTab() | 678 | + | first := testAttachmentTab(sess) |
| 679 | second := newTab(nil, first.size) | 679 | second := newTab(nil, first.size) | ||
| 680 | installTestFloating(first, newPane("floating", nil, domain.Size{Cols: 20, Rows: 5}), true) | 680 | installTestFloating(first, newPane("floating", nil, domain.Size{Cols: 20, Rows: 5}), true) | ||
| 681 | installTestFloating(second, newPane("floating", nil, domain.Size{Cols: 20, Rows: 5}), false) | 681 | installTestFloating(second, newPane("floating", nil, domain.Size{Cols: 20, Rows: 5}), false) | ||
| @@ -699,7 +699,7 @@ func TestFloatingMouseTranslatesSGRToInnerCoordinates(t *testing.T) { | |||||
| 699 | floatingPTY := portsmocks.NewMockPTY(t) | 699 | floatingPTY := portsmocks.NewMockPTY(t) | ||
| 700 | floatingPTY.EXPECT().Write([]byte("\x1b[<0;1;1M")).Return(len("\x1b[<0;1;1M"), nil).Once() | 700 | floatingPTY.EXPECT().Write([]byte("\x1b[<0;1;1M")).Return(len("\x1b[<0;1;1M"), nil).Once() | ||
| 701 | d, sess, ac, _ := newManualSessionWithPTYs(t, normal) | 701 | d, sess, ac, _ := newManualSessionWithPTYs(t, normal) | ||
| 702 | - | tb := sess.activeTab() | 702 | + | tb := testAttachmentTab(sess) |
| 703 | tb.mu.Lock() | 703 | tb.mu.Lock() | ||
| 704 | tb.size = domain.Size{Cols: 100, Rows: 20} | 704 | tb.size = domain.Size{Cols: 100, Rows: 20} | ||
| 705 | tb.mu.Unlock() | 705 | tb.mu.Unlock() | ||
| @@ -716,7 +716,7 @@ func TestFloatingMouseIgnoresBorderAndOutside(t *testing.T) { | |||||
| 716 | normal := portsmocks.NewMockPTY(t) | 716 | normal := portsmocks.NewMockPTY(t) | ||
| 717 | floatingPTY := portsmocks.NewMockPTY(t) | 717 | floatingPTY := portsmocks.NewMockPTY(t) | ||
| 718 | d, sess, ac, _ := newManualSessionWithPTYs(t, normal) | 718 | d, sess, ac, _ := newManualSessionWithPTYs(t, normal) | ||
| 719 | - | tb := sess.activeTab() | 719 | + | tb := testAttachmentTab(sess) |
| 720 | tb.mu.Lock() | 720 | tb.mu.Lock() | ||
| 721 | tb.size = domain.Size{Cols: 100, Rows: 20} | 721 | tb.size = domain.Size{Cols: 100, Rows: 20} | ||
| 722 | tb.mu.Unlock() | 722 | tb.mu.Unlock() | ||
| @@ -736,7 +736,7 @@ func TestResponsiveDrawerFloatingMouseRoutesOnlyInnerClientCells(t *testing.T) { | |||||
| 736 | floatingPTY := portsmocks.NewMockPTY(t) | 736 | floatingPTY := portsmocks.NewMockPTY(t) | ||
| 737 | floatingPTY.EXPECT().Write([]byte("\x1b[<0;1;1M")).Return(len("\x1b[<0;1;1M"), nil).Once() | 737 | floatingPTY.EXPECT().Write([]byte("\x1b[<0;1;1M")).Return(len("\x1b[<0;1;1M"), nil).Once() | ||
| 738 | d, sess, ac, _ := newManualSessionWithPTYs(t, normal) | 738 | d, sess, ac, _ := newManualSessionWithPTYs(t, normal) | ||
| 739 | - | tb := sess.activeTab() | 739 | + | tb := testAttachmentTab(sess) |
| 740 | complete := domain.Size{Cols: 79, Rows: 25} | 740 | complete := domain.Size{Cols: 79, Rows: 25} | ||
| 741 | content := tabSize(complete) | 741 | content := tabSize(complete) | ||
| 742 | geometry := calculateContentFloatingGeometry(content, d.currentFloatingConfig()) | 742 | geometry := calculateContentFloatingGeometry(content, d.currentFloatingConfig()) | ||
| @@ -760,7 +760,7 @@ func TestResponsiveDrawerCopyMouseMapsInnerAndPinsDragAwayFromChrome(t *testing. | |||||
| 760 | p, release := newBlockingPTY(t) | 760 | p, release := newBlockingPTY(t) | ||
| 761 | defer release() | 761 | defer release() | ||
| 762 | d, sess, ac, _ := newManualSessionWithPTYs(t, p) | 762 | d, sess, ac, _ := newManualSessionWithPTYs(t, p) | ||
| 763 | - | tb := sess.activeTab() | 763 | + | tb := testAttachmentTab(sess) |
| 764 | complete := domain.Size{Cols: 79, Rows: 25} | 764 | complete := domain.Size{Cols: 79, Rows: 25} | ||
| 765 | content := tabSize(complete) | 765 | content := tabSize(complete) | ||
| 766 | geometry := calculateContentFloatingGeometry(content, d.currentFloatingConfig()) | 766 | geometry := calculateContentFloatingGeometry(content, d.currentFloatingConfig()) | ||
| @@ -960,12 +960,12 @@ func TestPaletteNextPreviousSwitchActiveTab(t *testing.T) { | |||||
| 960 | release() | 960 | release() | ||
| 961 | } | 961 | } | ||
| 962 | }() | 962 | }() | ||
| 963 | - | sess.active = tc.start | 963 | + | selectTestAttachmentTab(sess, tc.start) |
| 964 | 964 | ||||
| 965 | d.handleInput(sess, ac, []byte("\x1b ")) | 965 | d.handleInput(sess, ac, []byte("\x1b ")) | ||
| 966 | d.handleInput(sess, ac, tc.query) | 966 | d.handleInput(sess, ac, tc.query) | ||
| 967 | 967 | ||||
| 968 | - | require.Equal(t, tc.wantIndex, activeTabIndex(sess)) | 968 | + | require.Equal(t, tc.wantIndex, testAttachmentTabIndex(sess)) |
| 969 | }) | 969 | }) | ||
| 970 | } | 970 | } | ||
| 971 | } | 971 | } | ||
| @@ -1200,14 +1200,14 @@ func TestAltXClosesActiveTabAndSelectsRemaining(t *testing.T) { | |||||
| 1200 | p2, releasePTY2 := newBlockingPTY(t) | 1200 | p2, releasePTY2 := newBlockingPTY(t) | ||
| 1201 | p3, releasePTY3 := newBlockingPTYWithWrites(t, writes) | 1201 | p3, releasePTY3 := newBlockingPTYWithWrites(t, writes) | ||
| 1202 | d, sess, ac, _ := newManualSessionWithPTYs(t, p1, p2, p3) | 1202 | d, sess, ac, _ := newManualSessionWithPTYs(t, p1, p2, p3) | ||
| 1203 | - | sess.active = 1 | 1203 | + | selectTestAttachmentTab(sess, 1) |
| 1204 | 1204 | ||||
| 1205 | d.handleInput(sess, ac, []byte("\x1b ")) | 1205 | d.handleInput(sess, ac, []byte("\x1b ")) | ||
| 1206 | d.handleInput(sess, ac, []byte("CLT\r")) | 1206 | d.handleInput(sess, ac, []byte("CLT\r")) | ||
| 1207 | 1207 | ||||
| 1208 | require.Equal(t, 1, sessionCount(d)) | 1208 | require.Equal(t, 1, sessionCount(d)) | ||
| 1209 | require.Len(t, sess.tabs, 2) | 1209 | require.Len(t, sess.tabs, 2) | ||
| 1210 | - | require.Equal(t, 1, activeTabIndex(sess), "closing middle tab selects the next remaining tab") | 1210 | + | require.Equal(t, 1, testAttachmentTabIndex(sess), "closing middle tab selects the next remaining tab") |
| 1211 | d.handleInput(sess, ac, []byte("Z")) | 1211 | d.handleInput(sess, ac, []byte("Z")) | ||
| 1212 | require.Eventually(t, func() bool { return len(writes) == 1 }, 2*time.Second, 5*time.Millisecond) | 1212 | require.Eventually(t, func() bool { return len(writes) == 1 }, 2*time.Second, 5*time.Millisecond) | ||
| 1213 | requirePTYWrite(t, writes, []byte("Z")) | 1213 | requirePTYWrite(t, writes, []byte("Z")) | ||
| @@ -1258,7 +1258,7 @@ func TestRNTOpensPromptAndRenamesActiveTab(t *testing.T) { | |||||
| 1258 | release() | 1258 | release() | ||
| 1259 | } | 1259 | } | ||
| 1260 | }() | 1260 | }() | ||
| 1261 | - | sess.active = 1 | 1261 | + | selectTestAttachmentTab(sess, 1) |
| 1262 | 1262 | ||||
| 1263 | d.handleInput(sess, ac, []byte("\x1b ")) | 1263 | d.handleInput(sess, ac, []byte("\x1b ")) | ||
| 1264 | awaitFrame(t, sends, ports.MsgOutput) | 1264 | awaitFrame(t, sends, ports.MsgOutput) | ||
| @@ -1639,7 +1639,7 @@ func TestMouseWheelOverUnfocusedPaneDoesNotFocusAndForwardsChildMouse(t *testing | |||||
| 1639 | p2.EXPECT().Write([]byte("\x1b[<64;1;1M")).Return(len("\x1b[<64;1;1M"), nil).Once() | 1639 | p2.EXPECT().Write([]byte("\x1b[<64;1;1M")).Return(len("\x1b[<64;1;1M"), nil).Once() | ||
| 1640 | d, sess, ac, _ := newManualSessionWithPTYs(t, p1) | 1640 | d, sess, ac, _ := newManualSessionWithPTYs(t, p1) | ||
| 1641 | d.procComm = nil | 1641 | d.procComm = nil | ||
| 1642 | - | tb := sess.activeTab() | 1642 | + | tb := testAttachmentTab(sess) |
| 1643 | p2pane := newPane("pane-2", p2, domain.Size{Cols: 20, Rows: 5}) | 1643 | p2pane := newPane("pane-2", p2, domain.Size{Cols: 20, Rows: 5}) | ||
| 1644 | p2pane.screen.Write([]byte("\x1b[?1000h\x1b[?1006h")) | 1644 | p2pane.screen.Write([]byte("\x1b[?1000h\x1b[?1006h")) | ||
| 1645 | tb.mu.Lock() | 1645 | tb.mu.Lock() | ||
| @@ -1663,7 +1663,7 @@ func TestRejectedLeftReleaseInvalidatesFreshPointerBeforeMotion(t *testing.T) { | |||||
| 1663 | { | 1663 | { | ||
| 1664 | name: "split divider", | 1664 | name: "split divider", | ||
| 1665 | setup: func(t *testing.T, d *Daemon, sess *session, _ *attachedClient, _ *[]byte) []byte { | 1665 | setup: func(t *testing.T, d *Daemon, sess *session, _ *attachedClient, _ *[]byte) []byte { | ||
| 1666 | - | tb := sess.activeTab() | 1666 | + | tb := testAttachmentTab(sess) |
| 1667 | p2 := newPane("pane-2", nil, domain.Size{Cols: 20, Rows: 10}) | 1667 | p2 := newPane("pane-2", nil, domain.Size{Cols: 20, Rows: 10}) | ||
| 1668 | tb.mu.Lock() | 1668 | tb.mu.Lock() | ||
| 1669 | tb.size = domain.Size{Cols: 41, Rows: 10} | 1669 | tb.size = domain.Size{Cols: 41, Rows: 10} | ||
| @@ -1676,7 +1676,7 @@ func TestRejectedLeftReleaseInvalidatesFreshPointerBeforeMotion(t *testing.T) { | |||||
| 1676 | { | 1676 | { | ||
| 1677 | name: "split title bar", | 1677 | name: "split title bar", | ||
| 1678 | setup: func(t *testing.T, d *Daemon, sess *session, _ *attachedClient, _ *[]byte) []byte { | 1678 | setup: func(t *testing.T, d *Daemon, sess *session, _ *attachedClient, _ *[]byte) []byte { | ||
| 1679 | - | tb := sess.activeTab() | 1679 | + | tb := testAttachmentTab(sess) |
| 1680 | p2 := newPane("pane-2", nil, domain.Size{Cols: 20, Rows: 10}) | 1680 | p2 := newPane("pane-2", nil, domain.Size{Cols: 20, Rows: 10}) | ||
| 1681 | tb.mu.Lock() | 1681 | tb.mu.Lock() | ||
| 1682 | tb.size = domain.Size{Cols: 41, Rows: 10} | 1682 | tb.size = domain.Size{Cols: 41, Rows: 10} | ||
| @@ -1689,7 +1689,7 @@ func TestRejectedLeftReleaseInvalidatesFreshPointerBeforeMotion(t *testing.T) { | |||||
| 1689 | { | 1689 | { | ||
| 1690 | name: "floating exterior", | 1690 | name: "floating exterior", | ||
| 1691 | setup: func(t *testing.T, d *Daemon, sess *session, _ *attachedClient, press *[]byte) []byte { | 1691 | setup: func(t *testing.T, d *Daemon, sess *session, _ *attachedClient, press *[]byte) []byte { | ||
| 1692 | - | tb := sess.activeTab() | 1692 | + | tb := testAttachmentTab(sess) |
| 1693 | floating := newPane("floating", nil, domain.Size{Cols: 20, Rows: 5}) | 1693 | floating := newPane("floating", nil, domain.Size{Cols: 20, Rows: 5}) | ||
| 1694 | installTestFloating(tb, floating, true) | 1694 | installTestFloating(tb, floating, true) | ||
| 1695 | tb.mu.Lock() | 1695 | tb.mu.Lock() | ||
| @@ -1749,7 +1749,7 @@ func TestPressAtFormerStackTitleRowIsTreatedAsExpandedContent(t *testing.T) { | |||||
| 1749 | p, release := newBlockingPTY(t) | 1749 | p, release := newBlockingPTY(t) | ||
| 1750 | defer release() | 1750 | defer release() | ||
| 1751 | d, sess, ac, _ := newManualSessionWithPTYs(t, p) | 1751 | d, sess, ac, _ := newManualSessionWithPTYs(t, p) | ||
| 1752 | - | tb := sess.activeTab() | 1752 | + | tb := testAttachmentTab(sess) |
| 1753 | p2 := newPane("pane-2", nil, domain.Size{Cols: 20, Rows: 9}) | 1753 | p2 := newPane("pane-2", nil, domain.Size{Cols: 20, Rows: 9}) | ||
| 1754 | p3 := newPane("pane-3", nil, domain.Size{Cols: 20, Rows: 1}) | 1754 | p3 := newPane("pane-3", nil, domain.Size{Cols: 20, Rows: 1}) | ||
| 1755 | tb.mu.Lock() | 1755 | tb.mu.Lock() | ||
| @@ -1821,7 +1821,7 @@ func TestMouseDividerAndTitleBarDoNotForwardBogusCoordinates(t *testing.T) { | |||||
| 1821 | p2.EXPECT().Resize(mock.Anything).Return(nil).Maybe() | 1821 | p2.EXPECT().Resize(mock.Anything).Return(nil).Maybe() | ||
| 1822 | d, sess, ac, _ := newManualSessionWithPTYs(t, p1) | 1822 | d, sess, ac, _ := newManualSessionWithPTYs(t, p1) | ||
| 1823 | d.procComm = nil | 1823 | d.procComm = nil | ||
| 1824 | - | tb := sess.activeTab() | 1824 | + | tb := testAttachmentTab(sess) |
| 1825 | tb.focusedPane().screen.Write([]byte("\x1b[?1000h\x1b[?1006h")) | 1825 | tb.focusedPane().screen.Write([]byte("\x1b[?1000h\x1b[?1006h")) | ||
| 1826 | tb.mu.Lock() | 1826 | tb.mu.Lock() | ||
| 1827 | tb.size = tc.size | 1827 | tb.size = tc.size | ||
| @@ -1844,7 +1844,7 @@ func TestCopyModeDragOutsideSplitPaneClampsToPaneContent(t *testing.T) { | |||||
| 1844 | p2.EXPECT().Resize(domain.Size{Cols: 20, Rows: 10}).Return(nil).Maybe() | 1844 | p2.EXPECT().Resize(domain.Size{Cols: 20, Rows: 10}).Return(nil).Maybe() | ||
| 1845 | d, sess, ac, sends := newManualSessionWithPTYs(t, p1) | 1845 | d, sess, ac, sends := newManualSessionWithPTYs(t, p1) | ||
| 1846 | d.procComm = nil | 1846 | d.procComm = nil | ||
| 1847 | - | tb := sess.activeTab() | 1847 | + | tb := testAttachmentTab(sess) |
| 1848 | for i := range 10 { | 1848 | for i := range 10 { | ||
| 1849 | copy(tb.focusedPane().screen.Frame.Row(i), testRow(string(rune('a'+i)))) | 1849 | copy(tb.focusedPane().screen.Frame.Row(i), testRow(string(rune('a'+i)))) | ||
| 1850 | } | 1850 | } | ||
| @@ -1878,7 +1878,7 @@ func TestMouseHitTestFocusesPaneAndTranslatesSGRColumns(t *testing.T) { | |||||
| 1878 | p2.EXPECT().Write([]byte("\x1b[<0;1;1M")).Return(len("\x1b[<0;1;1M"), nil).Once() | 1878 | p2.EXPECT().Write([]byte("\x1b[<0;1;1M")).Return(len("\x1b[<0;1;1M"), nil).Once() | ||
| 1879 | d, sess, ac, _ := newManualSessionWithPTYs(t, p1) | 1879 | d, sess, ac, _ := newManualSessionWithPTYs(t, p1) | ||
| 1880 | d.procComm = nil | 1880 | d.procComm = nil | ||
| 1881 | - | tb := sess.activeTab() | 1881 | + | tb := testAttachmentTab(sess) |
| 1882 | p2pane := newPane("pane-2", p2, domain.Size{Cols: 20, Rows: 5}) | 1882 | p2pane := newPane("pane-2", p2, domain.Size{Cols: 20, Rows: 5}) | ||
| 1883 | p2pane.screen.Write([]byte("\x1b[?1000h\x1b[?1006h")) | 1883 | p2pane.screen.Write([]byte("\x1b[?1000h\x1b[?1006h")) | ||
| 1884 | tb.mu.Lock() | 1884 | tb.mu.Lock() | ||
| @@ -1905,7 +1905,7 @@ func TestMouseGatedWhileNoticesOverlayActive(t *testing.T) { | |||||
| 1905 | p2.EXPECT().Resize(domain.Size{Cols: 20, Rows: 5}).Return(nil).Maybe() | 1905 | p2.EXPECT().Resize(domain.Size{Cols: 20, Rows: 5}).Return(nil).Maybe() | ||
| 1906 | d, sess, ac, sends := newManualSessionWithPTYs(t, p1) | 1906 | d, sess, ac, sends := newManualSessionWithPTYs(t, p1) | ||
| 1907 | d.procComm = nil | 1907 | d.procComm = nil | ||
| 1908 | - | tb := sess.activeTab() | 1908 | + | tb := testAttachmentTab(sess) |
| 1909 | p2pane := newPane("pane-2", p2, domain.Size{Cols: 20, Rows: 5}) | 1909 | p2pane := newPane("pane-2", p2, domain.Size{Cols: 20, Rows: 5}) | ||
| 1910 | p2pane.screen.Write([]byte("\x1b[?1000h\x1b[?1006h")) | 1910 | p2pane.screen.Write([]byte("\x1b[?1000h\x1b[?1006h")) | ||
| 1911 | tb.mu.Lock() | 1911 | tb.mu.Lock() | ||
| @@ -1935,7 +1935,7 @@ func TestMouseCollapsedStackBarExpandsAndFocuses(t *testing.T) { | |||||
| 1935 | p2.EXPECT().Resize(domain.Size{Cols: 20, Rows: 4}).Return(nil).Maybe() | 1935 | p2.EXPECT().Resize(domain.Size{Cols: 20, Rows: 4}).Return(nil).Maybe() | ||
| 1936 | d, sess, ac, _ := newManualSessionWithPTYs(t, p1) | 1936 | d, sess, ac, _ := newManualSessionWithPTYs(t, p1) | ||
| 1937 | d.procComm = nil | 1937 | d.procComm = nil | ||
| 1938 | - | tb := sess.activeTab() | 1938 | + | tb := testAttachmentTab(sess) |
| 1939 | p2pane := newPane("pane-2", p2, domain.Size{Cols: 20, Rows: 3}) | 1939 | p2pane := newPane("pane-2", p2, domain.Size{Cols: 20, Rows: 3}) | ||
| 1940 | tb.mu.Lock() | 1940 | tb.mu.Lock() | ||
| 1941 | tb.size = domain.Size{Cols: 20, Rows: 5} | 1941 | tb.size = domain.Size{Cols: 20, Rows: 5} | ||
| @@ -1954,7 +1954,7 @@ func TestActiveCopyPressOnOtherPaneFocusesAndExitsBeforeFutureInput(t *testing.T | |||||
| 1954 | p1, release := newBlockingPTY(t) | 1954 | p1, release := newBlockingPTY(t) | ||
| 1955 | defer release() | 1955 | defer release() | ||
| 1956 | d, sess, ac, _ := newManualSessionWithPTYs(t, p1) | 1956 | d, sess, ac, _ := newManualSessionWithPTYs(t, p1) | ||
| 1957 | - | tb := sess.activeTab() | 1957 | + | tb := testAttachmentTab(sess) |
| 1958 | p2 := newPane("pane-2", nil, domain.Size{Cols: 20, Rows: 10}) | 1958 | p2 := newPane("pane-2", nil, domain.Size{Cols: 20, Rows: 10}) | ||
| 1959 | tb.mu.Lock() | 1959 | tb.mu.Lock() | ||
| 1960 | tb.size = domain.Size{Cols: 41, Rows: 10} | 1960 | tb.size = domain.Size{Cols: 41, Rows: 10} | ||
| @@ -2053,8 +2053,8 @@ func TestActiveCopyMouseIgnoresStalePointerResets(t *testing.T) { | |||||
| 2053 | name: "press-owned release rejects mapping", | 2053 | name: "press-owned release rejects mapping", | ||
| 2054 | prepare: func(t *testing.T, d *Daemon, sess *session, ac *attachedClient) mouse.Event { | 2054 | prepare: func(t *testing.T, d *Daemon, sess *session, ac *attachedClient) mouse.Event { | ||
| 2055 | t.Helper() | 2055 | t.Helper() | ||
| 2056 | - | p := sess.activeTab().focusedPane() | 2056 | + | p := testAttachmentTab(sess).focusedPane() |
| 2057 | - | tb := sess.activeTab() | 2057 | + | tb := testAttachmentTab(sess) |
| 2058 | tb.mu.Lock() | 2058 | tb.mu.Lock() | ||
| 2059 | geometry, ok := hitTestCopyMouseGeometryLocked(tb, d.currentFloatingConfig(), 1, 2) | 2059 | geometry, ok := hitTestCopyMouseGeometryLocked(tb, d.currentFloatingConfig(), 1, 2) | ||
| 2060 | tb.mu.Unlock() | 2060 | tb.mu.Unlock() | ||
| @@ -2069,7 +2069,7 @@ func TestActiveCopyMouseIgnoresStalePointerResets(t *testing.T) { | |||||
| 2069 | name: "current geometry disappears", | 2069 | name: "current geometry disappears", | ||
| 2070 | prepare: func(t *testing.T, _ *Daemon, sess *session, _ *attachedClient) mouse.Event { | 2070 | prepare: func(t *testing.T, _ *Daemon, sess *session, _ *attachedClient) mouse.Event { | ||
| 2071 | t.Helper() | 2071 | t.Helper() | ||
| 2072 | - | tb := sess.activeTab() | 2072 | + | tb := testAttachmentTab(sess) |
| 2073 | tb.mu.Lock() | 2073 | tb.mu.Lock() | ||
| 2074 | tb.tree = nil | 2074 | tb.tree = nil | ||
| 2075 | tb.mu.Unlock() | 2075 | tb.mu.Unlock() | ||
| @@ -2080,7 +2080,7 @@ func TestActiveCopyMouseIgnoresStalePointerResets(t *testing.T) { | |||||
| 2080 | name: "fresh press rejects document mapping", | 2080 | name: "fresh press rejects document mapping", | ||
| 2081 | prepare: func(t *testing.T, _ *Daemon, sess *session, _ *attachedClient) mouse.Event { | 2081 | prepare: func(t *testing.T, _ *Daemon, sess *session, _ *attachedClient) mouse.Event { | ||
| 2082 | t.Helper() | 2082 | t.Helper() | ||
| 2083 | - | tb := sess.activeTab() | 2083 | + | tb := testAttachmentTab(sess) |
| 2084 | tb.mu.Lock() | 2084 | tb.mu.Lock() | ||
| 2085 | tb.size.Rows = 40 // valid layout coordinate, outside the old document. | 2085 | tb.size.Rows = 40 // valid layout coordinate, outside the old document. | ||
| 2086 | tb.mu.Unlock() | 2086 | tb.mu.Unlock() | ||
| @@ -2109,7 +2109,7 @@ func TestActiveCopyMouseIgnoresStalePointerResets(t *testing.T) { | |||||
| 2109 | } | 2109 | } | ||
| 2110 | defer func() { d.beforeCopyMouseMap = nil }() | 2110 | defer func() { d.beforeCopyMouseMap = nil }() | ||
| 2111 | 2111 | ||||
| 2112 | - | d.handleActiveCopyMouse(sess, ac, sess.activeTab(), ev) | 2112 | + | d.handleActiveCopyMouse(sess, ac, testAttachmentTab(sess), ev) |
| 2113 | 2113 | ||||
| 2114 | ac.overlays.copyMu.Lock() | 2114 | ac.overlays.copyMu.Lock() | ||
| 2115 | defer ac.overlays.copyMu.Unlock() | 2115 | defer ac.overlays.copyMu.Unlock() | ||
| @@ -2128,7 +2128,7 @@ func TestActiveCopyMouseRejectsViewportChangeAfterMappingSnapshot(t *testing.T) | |||||
| 2128 | p, release := newBlockingPTY(t) | 2128 | p, release := newBlockingPTY(t) | ||
| 2129 | defer release() | 2129 | defer release() | ||
| 2130 | d, sess, ac, sends := newManualSessionWithPTYs(t, p) | 2130 | d, sess, ac, sends := newManualSessionWithPTYs(t, p) | ||
| 2131 | - | pane := sess.activeTab().focusedPane() | 2131 | + | pane := testAttachmentTab(sess).focusedPane() |
| 2132 | for range 8 { | 2132 | for range 8 { | ||
| 2133 | appendHistoryRow(t, pane.history, testRow("history")) | 2133 | appendHistoryRow(t, pane.history, testRow("history")) | ||
| 2134 | } | 2134 | } | ||
| @@ -2162,13 +2162,13 @@ func TestFreshCopyPressUsesFrameAbsoluteHitTestGeometry(t *testing.T) { | |||||
| 2162 | { | 2162 | { | ||
| 2163 | name: "mono", | 2163 | name: "mono", | ||
| 2164 | setup: func(_ *testing.T, _ *Daemon, sess *session) (*pane, mouse.Event) { | 2164 | setup: func(_ *testing.T, _ *Daemon, sess *session) (*pane, mouse.Event) { | ||
| 2165 | - | return sess.activeTab().focusedPane(), mouse.Event{Button: mouse.Left, Type: mouse.Press, Col: 1, Row: 1} | 2165 | + | return testAttachmentTab(sess).focusedPane(), mouse.Event{Button: mouse.Left, Type: mouse.Press, Col: 1, Row: 1} |
| 2166 | }, | 2166 | }, | ||
| 2167 | }, | 2167 | }, | ||
| 2168 | { | 2168 | { | ||
| 2169 | name: "split", | 2169 | name: "split", | ||
| 2170 | setup: func(_ *testing.T, _ *Daemon, sess *session) (*pane, mouse.Event) { | 2170 | setup: func(_ *testing.T, _ *Daemon, sess *session) (*pane, mouse.Event) { | ||
| 2171 | - | tb := sess.activeTab() | 2171 | + | tb := testAttachmentTab(sess) |
| 2172 | p2 := newPane("pane-2", nil, domain.Size{Cols: 20, Rows: 10}) | 2172 | p2 := newPane("pane-2", nil, domain.Size{Cols: 20, Rows: 10}) | ||
| 2173 | tb.mu.Lock() | 2173 | tb.mu.Lock() | ||
| 2174 | tb.size = domain.Size{Cols: 41, Rows: 10} | 2174 | tb.size = domain.Size{Cols: 41, Rows: 10} | ||
| @@ -2182,8 +2182,8 @@ func TestFreshCopyPressUsesFrameAbsoluteHitTestGeometry(t *testing.T) { | |||||
| 2182 | name: "floating", | 2182 | name: "floating", | ||
| 2183 | setup: func(t *testing.T, d *Daemon, sess *session) (*pane, mouse.Event) { | 2183 | setup: func(t *testing.T, d *Daemon, sess *session) (*pane, mouse.Event) { | ||
| 2184 | p := newPane("floating", nil, domain.Size{Cols: 20, Rows: 5}) | 2184 | p := newPane("floating", nil, domain.Size{Cols: 20, Rows: 5}) | ||
| 2185 | - | installTestFloating(sess.activeTab(), p, true) | 2185 | + | installTestFloating(testAttachmentTab(sess), p, true) |
| 2186 | - | tb := sess.activeTab() | 2186 | + | tb := testAttachmentTab(sess) |
| 2187 | tb.mu.Lock() | 2187 | tb.mu.Lock() | ||
| 2188 | _, geometry, visible := tb.visibleFloatingSnapshotLocked(d.currentFloatingConfig()) | 2188 | _, geometry, visible := tb.visibleFloatingSnapshotLocked(d.currentFloatingConfig()) | ||
| 2189 | tb.mu.Unlock() | 2189 | tb.mu.Unlock() | ||
| @@ -2197,7 +2197,7 @@ func TestFreshCopyPressUsesFrameAbsoluteHitTestGeometry(t *testing.T) { | |||||
| 2197 | defer release() | 2197 | defer release() | ||
| 2198 | d, sess, ac, _ := newManualSessionWithPTYs(t, p) | 2198 | d, sess, ac, _ := newManualSessionWithPTYs(t, p) | ||
| 2199 | wantPane, ev := tc.setup(t, d, sess) | 2199 | wantPane, ev := tc.setup(t, d, sess) | ||
| 2200 | - | tb := sess.activeTab() | 2200 | + | tb := testAttachmentTab(sess) |
| 2201 | tb.mu.Lock() | 2201 | tb.mu.Lock() | ||
| 2202 | wantGeometry, ok := hitTestCopyMouseGeometryLocked(tb, d.currentFloatingConfig(), ev.Col, ev.Row) | 2202 | wantGeometry, ok := hitTestCopyMouseGeometryLocked(tb, d.currentFloatingConfig(), ev.Col, ev.Row) | ||
| 2203 | tb.mu.Unlock() | 2203 | tb.mu.Unlock() | ||
M internal/usecase/daemon/move.go +0 -2
| @@ -78,7 +78,6 @@ func (d *Daemon) movePane(req movePaneRequest) (result error) { | |||
| 78 | 78 | sourceClient := admission.sourceClient | |
| 79 | 79 | destinationClient := admission.destinationClient | |
| 80 | 80 | sourceSnatched := admission.sourceSnatched | |
| 81 | - | sourceTabWasActive := admission.sourceTabWasActive | |
| 82 | 81 | sourceTabInitialGeneration := admission.sourceGeneration | |
| 83 | 82 | destinationTabInitialGeneration := admission.destinationGeneration | |
| 84 | 83 | finalSourceTab := admission.finalSourceTab | |
| @@ -215,7 +214,6 @@ func (d *Daemon) movePane(req movePaneRequest) (result error) { | |||
| 215 | 214 | rolesFrozen: sourceRolesFrozen, | |
| 216 | 215 | unlockDispatch: unlockDispatch, | |
| 217 | 216 | reservation: reservation, | |
| 218 | - | sourceTabWasActive: sourceTabWasActive, | |
| 219 | 217 | sourceTabRemoved: commit.sourceTabRemoved, | |
| 220 | 218 | sourceEmpty: commit.sourceEmpty, | |
| 221 | 219 | retiredParked: commit.retiredParked, | |
| @@ -78,7 +78,6 @@ func (d *Daemon) movePane(req movePaneRequest) (result error) { | |||||
| 78 | sourceClient := admission.sourceClient | 78 | sourceClient := admission.sourceClient | ||
| 79 | destinationClient := admission.destinationClient | 79 | destinationClient := admission.destinationClient | ||
| 80 | sourceSnatched := admission.sourceSnatched | 80 | sourceSnatched := admission.sourceSnatched | ||
| 81 | - | sourceTabWasActive := admission.sourceTabWasActive | |||
| 82 | sourceTabInitialGeneration := admission.sourceGeneration | 81 | sourceTabInitialGeneration := admission.sourceGeneration | ||
| 83 | destinationTabInitialGeneration := admission.destinationGeneration | 82 | destinationTabInitialGeneration := admission.destinationGeneration | ||
| 84 | finalSourceTab := admission.finalSourceTab | 83 | finalSourceTab := admission.finalSourceTab | ||
| @@ -215,7 +214,6 @@ func (d *Daemon) movePane(req movePaneRequest) (result error) { | |||||
| 215 | rolesFrozen: sourceRolesFrozen, | 214 | rolesFrozen: sourceRolesFrozen, | ||
| 216 | unlockDispatch: unlockDispatch, | 215 | unlockDispatch: unlockDispatch, | ||
| 217 | reservation: reservation, | 216 | reservation: reservation, | ||
| 218 | - | sourceTabWasActive: sourceTabWasActive, | |||
| 219 | sourceTabRemoved: commit.sourceTabRemoved, | 217 | sourceTabRemoved: commit.sourceTabRemoved, | ||
| 220 | sourceEmpty: commit.sourceEmpty, | 218 | sourceEmpty: commit.sourceEmpty, | ||
| 221 | retiredParked: commit.retiredParked, | 219 | retiredParked: commit.retiredParked, | ||
M internal/usecase/daemon/move_acceptance_test.go +4 -4
| @@ -89,7 +89,7 @@ func TestMovePaneAcceptanceRetainsSourceFocusAndDestinationActivity(t *testing.T | |||
| 89 | 89 | ||
| 90 | 90 | destinationActive := newTabWithStableID("destination-active", "active-pane", activePTY, domain.Size{Cols: 80, Rows: 23}) | |
| 91 | 91 | destinationTab := newTabWithStableID("destination-target", "destination-pane", destinationPTY, domain.Size{Cols: 80, Rows: 23}) | |
| 92 | - | destination := &session{sessionCore: sessionCore{id: "destination", name: "destination", ephemeral: true}, tabs: []*tab{destinationActive, destinationTab}, active: 0} | |
| 92 | + | destination := &session{sessionCore: sessionCore{id: "destination", name: "destination", ephemeral: true}, tabs: []*tab{destinationActive, destinationTab}} | |
| 93 | 93 | publishTiledPaneOwners(destination, destinationActive) | |
| 94 | 94 | publishTiledPaneOwners(destination, destinationTab) | |
| 95 | 95 | d.mu.Lock() | |
| @@ -140,7 +140,7 @@ func TestMovePaneAcceptanceRetainsSourceFocusAndDestinationActivity(t *testing.T | |||
| 140 | 140 | require.Same(t, remaining, remainingPane) | |
| 141 | 141 | require.Equal(t, layout.PaneID("pane-2"), sourceFocus, "source focus must use the valid fallback leaf") | |
| 142 | 142 | destination.mu.Lock() | |
| 143 | - | destinationActiveTab := destination.tabs[destination.active] | |
| 143 | + | destinationActiveTab := destination.tabs[testAttachmentTabIndexLocked(destination)] | |
| 144 | 144 | destination.mu.Unlock() | |
| 145 | 145 | require.Same(t, destinationActive, destinationActiveTab, "destination active tab must remain unchanged") | |
| 146 | 146 | destinationTab.mu.Lock() | |
| @@ -182,7 +182,7 @@ func TestMovePaneRejectsStaleIncarnationWithoutMutation(t *testing.T) { | |||
| 182 | 182 | sourceTab := source.tabs[0] | |
| 183 | 183 | sourceTab.stableID = "source-tab" | |
| 184 | 184 | moved := sourceTab.focusedPane() | |
| 185 | - | destination := &session{sessionCore: sessionCore{id: "destination", name: "destination", ephemeral: true}, tabs: []*tab{newTabWithStableID("destination-tab", "destination-pane", destinationPTY, domain.Size{Cols: 80, Rows: 23})}, active: 0} | |
| 185 | + | destination := &session{sessionCore: sessionCore{id: "destination", name: "destination", ephemeral: true}, tabs: []*tab{newTabWithStableID("destination-tab", "destination-pane", destinationPTY, domain.Size{Cols: 80, Rows: 23})}} | |
| 186 | 186 | destinationTab := destination.tabs[0] | |
| 187 | 187 | publishTiledPaneOwners(destination, destinationTab) | |
| 188 | 188 | d.mu.Lock() | |
| @@ -231,7 +231,7 @@ func TestMovePaneRejectsStaleLayoutGenerationBeforeMutation(t *testing.T) { | |||
| 231 | 231 | sourceTab := source.tabs[0] | |
| 232 | 232 | sourceTab.stableID = "source-tab" | |
| 233 | 233 | moved := sourceTab.focusedPane() | |
| 234 | - | destination := &session{sessionCore: sessionCore{id: "destination", name: "destination", ephemeral: true}, tabs: []*tab{newTabWithStableID("destination-tab", "destination-pane", destinationPTY, domain.Size{Cols: 80, Rows: 23})}, active: 0} | |
| 234 | + | destination := &session{sessionCore: sessionCore{id: "destination", name: "destination", ephemeral: true}, tabs: []*tab{newTabWithStableID("destination-tab", "destination-pane", destinationPTY, domain.Size{Cols: 80, Rows: 23})}} | |
| 235 | 235 | destinationTab := destination.tabs[0] | |
| 236 | 236 | publishTiledPaneOwners(destination, destinationTab) | |
| 237 | 237 | d.mu.Lock() | |
| @@ -89,7 +89,7 @@ func TestMovePaneAcceptanceRetainsSourceFocusAndDestinationActivity(t *testing.T | |||||
| 89 | 89 | ||||
| 90 | destinationActive := newTabWithStableID("destination-active", "active-pane", activePTY, domain.Size{Cols: 80, Rows: 23}) | 90 | destinationActive := newTabWithStableID("destination-active", "active-pane", activePTY, domain.Size{Cols: 80, Rows: 23}) | ||
| 91 | destinationTab := newTabWithStableID("destination-target", "destination-pane", destinationPTY, domain.Size{Cols: 80, Rows: 23}) | 91 | destinationTab := newTabWithStableID("destination-target", "destination-pane", destinationPTY, domain.Size{Cols: 80, Rows: 23}) | ||
| 92 | - | destination := &session{sessionCore: sessionCore{id: "destination", name: "destination", ephemeral: true}, tabs: []*tab{destinationActive, destinationTab}, active: 0} | 92 | + | destination := &session{sessionCore: sessionCore{id: "destination", name: "destination", ephemeral: true}, tabs: []*tab{destinationActive, destinationTab}} |
| 93 | publishTiledPaneOwners(destination, destinationActive) | 93 | publishTiledPaneOwners(destination, destinationActive) | ||
| 94 | publishTiledPaneOwners(destination, destinationTab) | 94 | publishTiledPaneOwners(destination, destinationTab) | ||
| 95 | d.mu.Lock() | 95 | d.mu.Lock() | ||
| @@ -140,7 +140,7 @@ func TestMovePaneAcceptanceRetainsSourceFocusAndDestinationActivity(t *testing.T | |||||
| 140 | require.Same(t, remaining, remainingPane) | 140 | require.Same(t, remaining, remainingPane) | ||
| 141 | require.Equal(t, layout.PaneID("pane-2"), sourceFocus, "source focus must use the valid fallback leaf") | 141 | require.Equal(t, layout.PaneID("pane-2"), sourceFocus, "source focus must use the valid fallback leaf") | ||
| 142 | destination.mu.Lock() | 142 | destination.mu.Lock() | ||
| 143 | - | destinationActiveTab := destination.tabs[destination.active] | 143 | + | destinationActiveTab := destination.tabs[testAttachmentTabIndexLocked(destination)] |
| 144 | destination.mu.Unlock() | 144 | destination.mu.Unlock() | ||
| 145 | require.Same(t, destinationActive, destinationActiveTab, "destination active tab must remain unchanged") | 145 | require.Same(t, destinationActive, destinationActiveTab, "destination active tab must remain unchanged") | ||
| 146 | destinationTab.mu.Lock() | 146 | destinationTab.mu.Lock() | ||
| @@ -182,7 +182,7 @@ func TestMovePaneRejectsStaleIncarnationWithoutMutation(t *testing.T) { | |||||
| 182 | sourceTab := source.tabs[0] | 182 | sourceTab := source.tabs[0] | ||
| 183 | sourceTab.stableID = "source-tab" | 183 | sourceTab.stableID = "source-tab" | ||
| 184 | moved := sourceTab.focusedPane() | 184 | moved := sourceTab.focusedPane() | ||
| 185 | - | destination := &session{sessionCore: sessionCore{id: "destination", name: "destination", ephemeral: true}, tabs: []*tab{newTabWithStableID("destination-tab", "destination-pane", destinationPTY, domain.Size{Cols: 80, Rows: 23})}, active: 0} | 185 | + | destination := &session{sessionCore: sessionCore{id: "destination", name: "destination", ephemeral: true}, tabs: []*tab{newTabWithStableID("destination-tab", "destination-pane", destinationPTY, domain.Size{Cols: 80, Rows: 23})}} |
| 186 | destinationTab := destination.tabs[0] | 186 | destinationTab := destination.tabs[0] | ||
| 187 | publishTiledPaneOwners(destination, destinationTab) | 187 | publishTiledPaneOwners(destination, destinationTab) | ||
| 188 | d.mu.Lock() | 188 | d.mu.Lock() | ||
| @@ -231,7 +231,7 @@ func TestMovePaneRejectsStaleLayoutGenerationBeforeMutation(t *testing.T) { | |||||
| 231 | sourceTab := source.tabs[0] | 231 | sourceTab := source.tabs[0] | ||
| 232 | sourceTab.stableID = "source-tab" | 232 | sourceTab.stableID = "source-tab" | ||
| 233 | moved := sourceTab.focusedPane() | 233 | moved := sourceTab.focusedPane() | ||
| 234 | - | destination := &session{sessionCore: sessionCore{id: "destination", name: "destination", ephemeral: true}, tabs: []*tab{newTabWithStableID("destination-tab", "destination-pane", destinationPTY, domain.Size{Cols: 80, Rows: 23})}, active: 0} | 234 | + | destination := &session{sessionCore: sessionCore{id: "destination", name: "destination", ephemeral: true}, tabs: []*tab{newTabWithStableID("destination-tab", "destination-pane", destinationPTY, domain.Size{Cols: 80, Rows: 23})}} |
| 235 | destinationTab := destination.tabs[0] | 235 | destinationTab := destination.tabs[0] | ||
| 236 | publishTiledPaneOwners(destination, destinationTab) | 236 | publishTiledPaneOwners(destination, destinationTab) | ||
| 237 | d.mu.Lock() | 237 | d.mu.Lock() | ||
M internal/usecase/daemon/move_atomicity_test.go +2 -3
| @@ -67,10 +67,10 @@ func readMoveCommitObservation(d *Daemon, source, destination *session, sourceTa | |||
| 67 | 67 | } | |
| 68 | 68 | ||
| 69 | 69 | func activeMoveTabLocked(sess *session) *tab { | |
| 70 | - | if sess == nil || sess.active < 0 || sess.active >= len(sess.tabs) { | |
| 70 | + | if sess == nil || testAttachmentTabIndexLocked(sess) < 0 || testAttachmentTabIndexLocked(sess) >= len(sess.tabs) { | |
| 71 | 71 | return nil | |
| 72 | 72 | } | |
| 73 | - | return sess.tabs[sess.active] | |
| 73 | + | return sess.tabs[testAttachmentTabIndexLocked(sess)] | |
| 74 | 74 | } | |
| 75 | 75 | ||
| 76 | 76 | func TestMovePaneCommitPointHidesPartialPublication(t *testing.T) { | |
| @@ -87,7 +87,6 @@ func TestMovePaneCommitPointHidesPartialPublication(t *testing.T) { | |||
| 87 | 87 | destination := &session{sessionCore: sessionCore{id: "destination", | |
| 88 | 88 | name: "destination", | |
| 89 | 89 | ephemeral: true}, tabs: []*tab{newTabWithStableID("destination-tab", "destination-pane", p2, domain.Size{Cols: 80, Rows: 23})}, | |
| 90 | - | active: 0, | |
| 91 | 90 | } | |
| 92 | 91 | destinationTab := destination.tabs[0] | |
| 93 | 92 | publishTiledPaneOwners(destination, destinationTab) | |
| @@ -67,10 +67,10 @@ func readMoveCommitObservation(d *Daemon, source, destination *session, sourceTa | |||||
| 67 | } | 67 | } | ||
| 68 | 68 | ||||
| 69 | func activeMoveTabLocked(sess *session) *tab { | 69 | func activeMoveTabLocked(sess *session) *tab { | ||
| 70 | - | if sess == nil || sess.active < 0 || sess.active >= len(sess.tabs) { | 70 | + | if sess == nil || testAttachmentTabIndexLocked(sess) < 0 || testAttachmentTabIndexLocked(sess) >= len(sess.tabs) { |
| 71 | return nil | 71 | return nil | ||
| 72 | } | 72 | } | ||
| 73 | - | return sess.tabs[sess.active] | 73 | + | return sess.tabs[testAttachmentTabIndexLocked(sess)] |
| 74 | } | 74 | } | ||
| 75 | 75 | ||||
| 76 | func TestMovePaneCommitPointHidesPartialPublication(t *testing.T) { | 76 | func TestMovePaneCommitPointHidesPartialPublication(t *testing.T) { | ||
| @@ -87,7 +87,6 @@ func TestMovePaneCommitPointHidesPartialPublication(t *testing.T) { | |||||
| 87 | destination := &session{sessionCore: sessionCore{id: "destination", | 87 | destination := &session{sessionCore: sessionCore{id: "destination", | ||
| 88 | name: "destination", | 88 | name: "destination", | ||
| 89 | ephemeral: true}, tabs: []*tab{newTabWithStableID("destination-tab", "destination-pane", p2, domain.Size{Cols: 80, Rows: 23})}, | 89 | ephemeral: true}, tabs: []*tab{newTabWithStableID("destination-tab", "destination-pane", p2, domain.Size{Cols: 80, Rows: 23})}, | ||
| 90 | - | active: 0, | |||
| 91 | } | 90 | } | ||
| 92 | destinationTab := destination.tabs[0] | 91 | destinationTab := destination.tabs[0] | ||
| 93 | publishTiledPaneOwners(destination, destinationTab) | 92 | publishTiledPaneOwners(destination, destinationTab) | ||
M internal/usecase/daemon/move_cleanup_test.go +2 -6
| @@ -26,9 +26,7 @@ func TestMovePaneReleasesResizeFencesBeforeAttachmentCleanup(t *testing.T) { | |||
| 26 | 26 | client.captureFrames = map[*pane]capturedPaneRenderState{moved: {}} | |
| 27 | 27 | client.sendMu.Unlock() | |
| 28 | 28 | ||
| 29 | - | destination := &session{sessionCore: sessionCore{id: "destination", name: "destination", ephemeral: true}, tabs: []*tab{newTabWithStableID("destination-tab", "destination-pane", destinationPTY, domain.Size{Cols: 80, Rows: 23})}, | |
| 30 | - | active: 0, | |
| 31 | - | } | |
| 29 | + | destination := &session{sessionCore: sessionCore{id: "destination", name: "destination", ephemeral: true}, tabs: []*tab{newTabWithStableID("destination-tab", "destination-pane", destinationPTY, domain.Size{Cols: 80, Rows: 23})}} | |
| 32 | 30 | destinationTab := destination.tabs[0] | |
| 33 | 31 | publishTiledPaneOwners(destination, destinationTab) | |
| 34 | 32 | d.mu.Lock() | |
| @@ -104,9 +102,7 @@ func TestMovePaneCleanupUsesCommitPointSourceAttachmentToken(t *testing.T) { | |||
| 104 | 102 | moved := sourceTab.focusedPane() | |
| 105 | 103 | require.NotNil(t, d.attachCoordinator(source, nil, displaced, true)) | |
| 106 | 104 | ||
| 107 | - | destination := &session{sessionCore: sessionCore{id: "destination", name: "destination", ephemeral: true}, tabs: []*tab{newTabWithStableID("destination-tab", "destination-pane", destinationPTY, domain.Size{Cols: 80, Rows: 23})}, | |
| 108 | - | active: 0, | |
| 109 | - | } | |
| 105 | + | destination := &session{sessionCore: sessionCore{id: "destination", name: "destination", ephemeral: true}, tabs: []*tab{newTabWithStableID("destination-tab", "destination-pane", destinationPTY, domain.Size{Cols: 80, Rows: 23})}} | |
| 110 | 106 | destinationTab := destination.tabs[0] | |
| 111 | 107 | publishTiledPaneOwners(destination, destinationTab) | |
| 112 | 108 | d.mu.Lock() | |
| @@ -26,9 +26,7 @@ func TestMovePaneReleasesResizeFencesBeforeAttachmentCleanup(t *testing.T) { | |||||
| 26 | client.captureFrames = map[*pane]capturedPaneRenderState{moved: {}} | 26 | client.captureFrames = map[*pane]capturedPaneRenderState{moved: {}} | ||
| 27 | client.sendMu.Unlock() | 27 | client.sendMu.Unlock() | ||
| 28 | 28 | ||||
| 29 | - | destination := &session{sessionCore: sessionCore{id: "destination", name: "destination", ephemeral: true}, tabs: []*tab{newTabWithStableID("destination-tab", "destination-pane", destinationPTY, domain.Size{Cols: 80, Rows: 23})}, | 29 | + | destination := &session{sessionCore: sessionCore{id: "destination", name: "destination", ephemeral: true}, tabs: []*tab{newTabWithStableID("destination-tab", "destination-pane", destinationPTY, domain.Size{Cols: 80, Rows: 23})}} |
| 30 | - | active: 0, | |||
| 31 | - | } | |||
| 32 | destinationTab := destination.tabs[0] | 30 | destinationTab := destination.tabs[0] | ||
| 33 | publishTiledPaneOwners(destination, destinationTab) | 31 | publishTiledPaneOwners(destination, destinationTab) | ||
| 34 | d.mu.Lock() | 32 | d.mu.Lock() | ||
| @@ -104,9 +102,7 @@ func TestMovePaneCleanupUsesCommitPointSourceAttachmentToken(t *testing.T) { | |||||
| 104 | moved := sourceTab.focusedPane() | 102 | moved := sourceTab.focusedPane() | ||
| 105 | require.NotNil(t, d.attachCoordinator(source, nil, displaced, true)) | 103 | require.NotNil(t, d.attachCoordinator(source, nil, displaced, true)) | ||
| 106 | 104 | ||||
| 107 | - | destination := &session{sessionCore: sessionCore{id: "destination", name: "destination", ephemeral: true}, tabs: []*tab{newTabWithStableID("destination-tab", "destination-pane", destinationPTY, domain.Size{Cols: 80, Rows: 23})}, | 105 | + | destination := &session{sessionCore: sessionCore{id: "destination", name: "destination", ephemeral: true}, tabs: []*tab{newTabWithStableID("destination-tab", "destination-pane", destinationPTY, domain.Size{Cols: 80, Rows: 23})}} |
| 108 | - | active: 0, | |||
| 109 | - | } | |||
| 110 | destinationTab := destination.tabs[0] | 106 | destinationTab := destination.tabs[0] | ||
| 111 | publishTiledPaneOwners(destination, destinationTab) | 107 | publishTiledPaneOwners(destination, destinationTab) | ||
| 112 | d.mu.Lock() | 108 | d.mu.Lock() | ||
M internal/usecase/daemon/move_gate_race_test.go +1 -3
| @@ -21,9 +21,7 @@ func newMoveGateRaceFixture(t *testing.T) (*Daemon, *session, *tab, *pane, *sess | |||
| 21 | 21 | moved := sourceTab.focusedPane() | |
| 22 | 22 | require.NotNil(t, d.attachCoordinator(source, nil, client, true)) | |
| 23 | 23 | ||
| 24 | - | destination := &session{sessionCore: sessionCore{id: "destination", name: "destination", ephemeral: true}, tabs: []*tab{newTabWithStableID("destination-tab", "destination-pane", destinationPTY, domain.Size{Cols: 80, Rows: 23})}, | |
| 25 | - | active: 0, | |
| 26 | - | } | |
| 24 | + | destination := &session{sessionCore: sessionCore{id: "destination", name: "destination", ephemeral: true}, tabs: []*tab{newTabWithStableID("destination-tab", "destination-pane", destinationPTY, domain.Size{Cols: 80, Rows: 23})}} | |
| 27 | 25 | destinationTab := destination.tabs[0] | |
| 28 | 26 | publishTiledPaneOwners(destination, destinationTab) | |
| 29 | 27 | d.mu.Lock() | |
| @@ -21,9 +21,7 @@ func newMoveGateRaceFixture(t *testing.T) (*Daemon, *session, *tab, *pane, *sess | |||||
| 21 | moved := sourceTab.focusedPane() | 21 | moved := sourceTab.focusedPane() | ||
| 22 | require.NotNil(t, d.attachCoordinator(source, nil, client, true)) | 22 | require.NotNil(t, d.attachCoordinator(source, nil, client, true)) | ||
| 23 | 23 | ||||
| 24 | - | destination := &session{sessionCore: sessionCore{id: "destination", name: "destination", ephemeral: true}, tabs: []*tab{newTabWithStableID("destination-tab", "destination-pane", destinationPTY, domain.Size{Cols: 80, Rows: 23})}, | 24 | + | destination := &session{sessionCore: sessionCore{id: "destination", name: "destination", ephemeral: true}, tabs: []*tab{newTabWithStableID("destination-tab", "destination-pane", destinationPTY, domain.Size{Cols: 80, Rows: 23})}} |
| 25 | - | active: 0, | |||
| 26 | - | } | |||
| 27 | destinationTab := destination.tabs[0] | 25 | destinationTab := destination.tabs[0] | ||
| 28 | publishTiledPaneOwners(destination, destinationTab) | 26 | publishTiledPaneOwners(destination, destinationTab) | ||
| 29 | d.mu.Lock() | 27 | d.mu.Lock() | ||
M internal/usecase/daemon/move_pane_admission.go +1 -11
| @@ -1,9 +1,6 @@ | |||
| 1 | 1 | package daemon | |
| 2 | 2 | ||
| 3 | - | import ( | |
| 4 | - | "github.com/bnema/vev/internal/domain" | |
| 5 | - | "github.com/bnema/vev/internal/usecase/layout" | |
| 6 | - | ) | |
| 3 | + | import "github.com/bnema/vev/internal/usecase/layout" | |
| 7 | 4 | ||
| 8 | 5 | // movePaneAdmission is an immutable snapshot of the exact live objects and | |
| 9 | 6 | // generations a move may later commit. Resize fences are acquired only after | |
| @@ -20,7 +17,6 @@ type movePaneAdmission struct { | |||
| 20 | 17 | sourceGeneration uint64 | |
| 21 | 18 | destinationGeneration uint64 | |
| 22 | 19 | finalSourceTab bool | |
| 23 | - | sourceTabWasActive bool | |
| 24 | 20 | } | |
| 25 | 21 | ||
| 26 | 22 | func (d *Daemon) snapshotMovePaneAdmission(req movePaneRequest, source, destination *session) (*movePaneAdmission, error) { | |
| @@ -57,11 +53,6 @@ func (d *Daemon) snapshotMovePaneAdmission(req movePaneRequest, source, destinat | |||
| 57 | 53 | return nil, errMoveStaleTarget | |
| 58 | 54 | } | |
| 59 | 55 | ||
| 60 | - | sourceTabWasActive := false | |
| 61 | - | if req.Client != nil { | |
| 62 | - | view := req.Client.viewSnapshot() | |
| 63 | - | sourceTabWasActive = view.tabID == domain.TabStableID(sourceTab.stableID) | |
| 64 | - | } | |
| 65 | 56 | return &movePaneAdmission{ | |
| 66 | 57 | source: source, | |
| 67 | 58 | destination: destination, | |
| @@ -75,6 +66,5 @@ func (d *Daemon) snapshotMovePaneAdmission(req movePaneRequest, source, destinat | |||
| 75 | 66 | destinationGeneration: destinationTab.layoutGeneration, | |
| 76 | 67 | finalSourceTab: len(source.tabs) == 1 && source.tabs[0] == sourceTab && | |
| 77 | 68 | sourceTab.tree != nil && sourceTab.tree.Root != nil && len(layout.LeafIDs(sourceTab.tree.Root)) == 1, | |
| 78 | - | sourceTabWasActive: sourceTabWasActive, | |
| 79 | 69 | }, nil | |
| 80 | 70 | } | |
| @@ -1,9 +1,6 @@ | |||||
| 1 | package daemon | 1 | package daemon | ||
| 2 | 2 | ||||
| 3 | - | import ( | 3 | + | import "github.com/bnema/vev/internal/usecase/layout" |
| 4 | - | "github.com/bnema/vev/internal/domain" | |||
| 5 | - | "github.com/bnema/vev/internal/usecase/layout" | |||
| 6 | - | ) | |||
| 7 | 4 | ||||
| 8 | // movePaneAdmission is an immutable snapshot of the exact live objects and | 5 | // movePaneAdmission is an immutable snapshot of the exact live objects and | ||
| 9 | // generations a move may later commit. Resize fences are acquired only after | 6 | // generations a move may later commit. Resize fences are acquired only after | ||
| @@ -20,7 +17,6 @@ type movePaneAdmission struct { | |||||
| 20 | sourceGeneration uint64 | 17 | sourceGeneration uint64 | ||
| 21 | destinationGeneration uint64 | 18 | destinationGeneration uint64 | ||
| 22 | finalSourceTab bool | 19 | finalSourceTab bool | ||
| 23 | - | sourceTabWasActive bool | |||
| 24 | } | 20 | } | ||
| 25 | 21 | ||||
| 26 | func (d *Daemon) snapshotMovePaneAdmission(req movePaneRequest, source, destination *session) (*movePaneAdmission, error) { | 22 | func (d *Daemon) snapshotMovePaneAdmission(req movePaneRequest, source, destination *session) (*movePaneAdmission, error) { | ||
| @@ -57,11 +53,6 @@ func (d *Daemon) snapshotMovePaneAdmission(req movePaneRequest, source, destinat | |||||
| 57 | return nil, errMoveStaleTarget | 53 | return nil, errMoveStaleTarget | ||
| 58 | } | 54 | } | ||
| 59 | 55 | ||||
| 60 | - | sourceTabWasActive := false | |||
| 61 | - | if req.Client != nil { | |||
| 62 | - | view := req.Client.viewSnapshot() | |||
| 63 | - | sourceTabWasActive = view.tabID == domain.TabStableID(sourceTab.stableID) | |||
| 64 | - | } | |||
| 65 | return &movePaneAdmission{ | 56 | return &movePaneAdmission{ | ||
| 66 | source: source, | 57 | source: source, | ||
| 67 | destination: destination, | 58 | destination: destination, | ||
| @@ -75,6 +66,5 @@ func (d *Daemon) snapshotMovePaneAdmission(req movePaneRequest, source, destinat | |||||
| 75 | destinationGeneration: destinationTab.layoutGeneration, | 66 | destinationGeneration: destinationTab.layoutGeneration, | ||
| 76 | finalSourceTab: len(source.tabs) == 1 && source.tabs[0] == sourceTab && | 67 | finalSourceTab: len(source.tabs) == 1 && source.tabs[0] == sourceTab && | ||
| 77 | sourceTab.tree != nil && sourceTab.tree.Root != nil && len(layout.LeafIDs(sourceTab.tree.Root)) == 1, | 68 | sourceTab.tree != nil && sourceTab.tree.Root != nil && len(layout.LeafIDs(sourceTab.tree.Root)) == 1, | ||
| 78 | - | sourceTabWasActive: sourceTabWasActive, | |||
| 79 | }, nil | 69 | }, nil | ||
| 80 | } | 70 | } | ||
M internal/usecase/daemon/move_pane_postcommit.go +3 -4
| @@ -36,9 +36,8 @@ type movePanePostcommitPlan struct { | |||
| 36 | 36 | reservation *moveLifecycleReservation | |
| 37 | 37 | oldTabCancel context.CancelFunc | |
| 38 | 38 | ||
| 39 | - | sourceTabWasActive bool | |
| 40 | - | sourceTabRemoved bool | |
| 41 | - | sourceEmpty bool | |
| 39 | + | sourceTabRemoved bool | |
| 40 | + | sourceEmpty bool | |
| 42 | 41 | ||
| 43 | 42 | retiredParked []parkedAttachmentRetirement | |
| 44 | 43 | retiredAttachments []detachedAttachmentSnapshot | |
| @@ -94,7 +93,7 @@ func (p movePanePostcommitPlan) execute(d *Daemon) { | |||
| 94 | 93 | if !moveSessionRetired(d, p.source) { | |
| 95 | 94 | sourceLayoutTab := p.sourceTab | |
| 96 | 95 | if p.sourceTabRemoved { | |
| 97 | - | sourceLayoutTab = p.source.tabForAttachmentOrActive(nil) | |
| 96 | + | sourceLayoutTab = p.source.firstTab() | |
| 98 | 97 | } | |
| 99 | 98 | if sourceLayoutTab != nil && sourceLayoutTab != p.destinationTab { | |
| 100 | 99 | d.applyTabLayout(p.source, sourceLayoutTab) | |
| @@ -36,9 +36,8 @@ type movePanePostcommitPlan struct { | |||||
| 36 | reservation *moveLifecycleReservation | 36 | reservation *moveLifecycleReservation | ||
| 37 | oldTabCancel context.CancelFunc | 37 | oldTabCancel context.CancelFunc | ||
| 38 | 38 | ||||
| 39 | - | sourceTabWasActive bool | 39 | + | sourceTabRemoved bool |
| 40 | - | sourceTabRemoved bool | 40 | + | sourceEmpty bool |
| 41 | - | sourceEmpty bool | |||
| 42 | 41 | ||||
| 43 | retiredParked []parkedAttachmentRetirement | 42 | retiredParked []parkedAttachmentRetirement | ||
| 44 | retiredAttachments []detachedAttachmentSnapshot | 43 | retiredAttachments []detachedAttachmentSnapshot | ||
| @@ -94,7 +93,7 @@ func (p movePanePostcommitPlan) execute(d *Daemon) { | |||||
| 94 | if !moveSessionRetired(d, p.source) { | 93 | if !moveSessionRetired(d, p.source) { | ||
| 95 | sourceLayoutTab := p.sourceTab | 94 | sourceLayoutTab := p.sourceTab | ||
| 96 | if p.sourceTabRemoved { | 95 | if p.sourceTabRemoved { | ||
| 97 | - | sourceLayoutTab = p.source.tabForAttachmentOrActive(nil) | 96 | + | sourceLayoutTab = p.source.firstTab() |
| 98 | } | 97 | } | ||
| 99 | if sourceLayoutTab != nil && sourceLayoutTab != p.destinationTab { | 98 | if sourceLayoutTab != nil && sourceLayoutTab != p.destinationTab { | ||
| 100 | d.applyTabLayout(p.source, sourceLayoutTab) | 99 | d.applyTabLayout(p.source, sourceLayoutTab) | ||
M internal/usecase/daemon/move_picker.go +2 -2
| @@ -60,7 +60,7 @@ func (d *Daemon) enterPickerForIntent(sess *session, ac *attachedClient, intent | |||
| 60 | 60 | if intent != pickerNavigate && !sess.capabilities().yieldsMoves() { | |
| 61 | 61 | return errSessionCannotYieldMoves | |
| 62 | 62 | } | |
| 63 | - | model := d.newPickerModel(sess, intent, source, picker.SourceFilter{}) | |
| 63 | + | model := d.newPickerModel(sess, ac, intent, source, picker.SourceFilter{}) | |
| 64 | 64 | if intent != pickerNavigate { | |
| 65 | 65 | if _, ok := model.Selected(); !ok { | |
| 66 | 66 | return errNoMoveDestination | |
| @@ -172,7 +172,7 @@ func (d *Daemon) refreshPickerOpts(ac *attachedClient, opts pickerRefreshOptions | |||
| 172 | 172 | } | |
| 173 | 173 | } | |
| 174 | 174 | rt.pickerMu.Unlock() | |
| 175 | - | model := d.newPickerModel(sess, intent, source, current) | |
| 175 | + | model := d.newPickerModel(sess, ac, intent, source, current) | |
| 176 | 176 | if rt.afterPickerRefreshBuild != nil { | |
| 177 | 177 | rt.afterPickerRefreshBuild(model) | |
| 178 | 178 | } | |
| @@ -60,7 +60,7 @@ func (d *Daemon) enterPickerForIntent(sess *session, ac *attachedClient, intent | |||||
| 60 | if intent != pickerNavigate && !sess.capabilities().yieldsMoves() { | 60 | if intent != pickerNavigate && !sess.capabilities().yieldsMoves() { | ||
| 61 | return errSessionCannotYieldMoves | 61 | return errSessionCannotYieldMoves | ||
| 62 | } | 62 | } | ||
| 63 | - | model := d.newPickerModel(sess, intent, source, picker.SourceFilter{}) | 63 | + | model := d.newPickerModel(sess, ac, intent, source, picker.SourceFilter{}) |
| 64 | if intent != pickerNavigate { | 64 | if intent != pickerNavigate { | ||
| 65 | if _, ok := model.Selected(); !ok { | 65 | if _, ok := model.Selected(); !ok { | ||
| 66 | return errNoMoveDestination | 66 | return errNoMoveDestination | ||
| @@ -172,7 +172,7 @@ func (d *Daemon) refreshPickerOpts(ac *attachedClient, opts pickerRefreshOptions | |||||
| 172 | } | 172 | } | ||
| 173 | } | 173 | } | ||
| 174 | rt.pickerMu.Unlock() | 174 | rt.pickerMu.Unlock() | ||
| 175 | - | model := d.newPickerModel(sess, intent, source, current) | 175 | + | model := d.newPickerModel(sess, ac, intent, source, current) |
| 176 | if rt.afterPickerRefreshBuild != nil { | 176 | if rt.afterPickerRefreshBuild != nil { | ||
| 177 | rt.afterPickerRefreshBuild(model) | 177 | rt.afterPickerRefreshBuild(model) | ||
| 178 | } | 178 | } | ||
M internal/usecase/daemon/move_picker_test.go +2 -2
| @@ -35,7 +35,7 @@ func setupMovePickerSessionsWithClock(t *testing.T, clock ports.Clock, extraDest | |||
| 35 | 35 | releases = append(releases, releaseDest) | |
| 36 | 36 | destinationTab := newTabWithStableID("destination-tab", "destination-pane", destPTY, domain.Size{Cols: 80, Rows: 23}) | |
| 37 | 37 | publishTiledPaneOwners(source, sourceTab) | |
| 38 | - | destination := &session{sessionCore: sessionCore{id: "destination", name: "destination", incarnation: domain.IncarnationID{2}, ephemeral: true}, ctx: source.ctx, cancel: func() {}, tabs: []*tab{destinationTab}, active: 0} | |
| 38 | + | destination := &session{sessionCore: sessionCore{id: "destination", name: "destination", incarnation: domain.IncarnationID{2}, ephemeral: true}, ctx: source.ctx, cancel: func() {}, tabs: []*tab{destinationTab}} | |
| 39 | 39 | publishTiledPaneOwners(destination, destinationTab) | |
| 40 | 40 | for range extraDestinationTabs { | |
| 41 | 41 | extraPTY, releaseExtra := newBlockingPTY(t) | |
| @@ -306,7 +306,7 @@ func TestMovePickerRefreshCloseKeepsReplacementPicker(t *testing.T) { | |||
| 306 | 306 | t.Fatal("timed out waiting for move-picker refresh rebuild") | |
| 307 | 307 | } | |
| 308 | 308 | ||
| 309 | - | replacement := d.newPickerModel(source, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{}) | |
| 309 | + | replacement := d.newPickerModel(source, nil, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{}) | |
| 310 | 310 | d.publishPicker(source, ac, replacement, pickerNavigate, moveSourceLocator{}) | |
| 311 | 311 | close(allowClose) | |
| 312 | 312 | select { | |
| @@ -35,7 +35,7 @@ func setupMovePickerSessionsWithClock(t *testing.T, clock ports.Clock, extraDest | |||||
| 35 | releases = append(releases, releaseDest) | 35 | releases = append(releases, releaseDest) | ||
| 36 | destinationTab := newTabWithStableID("destination-tab", "destination-pane", destPTY, domain.Size{Cols: 80, Rows: 23}) | 36 | destinationTab := newTabWithStableID("destination-tab", "destination-pane", destPTY, domain.Size{Cols: 80, Rows: 23}) | ||
| 37 | publishTiledPaneOwners(source, sourceTab) | 37 | publishTiledPaneOwners(source, sourceTab) | ||
| 38 | - | destination := &session{sessionCore: sessionCore{id: "destination", name: "destination", incarnation: domain.IncarnationID{2}, ephemeral: true}, ctx: source.ctx, cancel: func() {}, tabs: []*tab{destinationTab}, active: 0} | 38 | + | destination := &session{sessionCore: sessionCore{id: "destination", name: "destination", incarnation: domain.IncarnationID{2}, ephemeral: true}, ctx: source.ctx, cancel: func() {}, tabs: []*tab{destinationTab}} |
| 39 | publishTiledPaneOwners(destination, destinationTab) | 39 | publishTiledPaneOwners(destination, destinationTab) | ||
| 40 | for range extraDestinationTabs { | 40 | for range extraDestinationTabs { | ||
| 41 | extraPTY, releaseExtra := newBlockingPTY(t) | 41 | extraPTY, releaseExtra := newBlockingPTY(t) | ||
| @@ -306,7 +306,7 @@ func TestMovePickerRefreshCloseKeepsReplacementPicker(t *testing.T) { | |||||
| 306 | t.Fatal("timed out waiting for move-picker refresh rebuild") | 306 | t.Fatal("timed out waiting for move-picker refresh rebuild") | ||
| 307 | } | 307 | } | ||
| 308 | 308 | ||||
| 309 | - | replacement := d.newPickerModel(source, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{}) | 309 | + | replacement := d.newPickerModel(source, nil, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{}) |
| 310 | d.publishPicker(source, ac, replacement, pickerNavigate, moveSourceLocator{}) | 310 | d.publishPicker(source, ac, replacement, pickerNavigate, moveSourceLocator{}) | ||
| 311 | close(allowClose) | 311 | close(allowClose) | ||
| 312 | select { | 312 | select { | ||
M internal/usecase/daemon/move_stale_destination_test.go +1 -1
| @@ -18,7 +18,7 @@ func TestMovePaneRejectsStaleDestinationIncarnationWithoutMutation(t *testing.T) | |||
| 18 | 18 | sourceTab := source.tabs[0] | |
| 19 | 19 | sourceTab.stableID = "source-tab" | |
| 20 | 20 | moved := sourceTab.focusedPane() | |
| 21 | - | destination := &session{sessionCore: sessionCore{id: "destination", name: "destination", ephemeral: true}, tabs: []*tab{newTabWithStableID("destination-tab", "destination-pane", destinationPTY, domain.Size{Cols: 80, Rows: 23})}, active: 0} | |
| 21 | + | destination := &session{sessionCore: sessionCore{id: "destination", name: "destination", ephemeral: true}, tabs: []*tab{newTabWithStableID("destination-tab", "destination-pane", destinationPTY, domain.Size{Cols: 80, Rows: 23})}} | |
| 22 | 22 | destinationTab := destination.tabs[0] | |
| 23 | 23 | publishTiledPaneOwners(destination, destinationTab) | |
| 24 | 24 | d.mu.Lock() | |
| @@ -18,7 +18,7 @@ func TestMovePaneRejectsStaleDestinationIncarnationWithoutMutation(t *testing.T) | |||||
| 18 | sourceTab := source.tabs[0] | 18 | sourceTab := source.tabs[0] | ||
| 19 | sourceTab.stableID = "source-tab" | 19 | sourceTab.stableID = "source-tab" | ||
| 20 | moved := sourceTab.focusedPane() | 20 | moved := sourceTab.focusedPane() | ||
| 21 | - | destination := &session{sessionCore: sessionCore{id: "destination", name: "destination", ephemeral: true}, tabs: []*tab{newTabWithStableID("destination-tab", "destination-pane", destinationPTY, domain.Size{Cols: 80, Rows: 23})}, active: 0} | 21 | + | destination := &session{sessionCore: sessionCore{id: "destination", name: "destination", ephemeral: true}, tabs: []*tab{newTabWithStableID("destination-tab", "destination-pane", destinationPTY, domain.Size{Cols: 80, Rows: 23})}} |
| 22 | destinationTab := destination.tabs[0] | 22 | destinationTab := destination.tabs[0] | ||
| 23 | publishTiledPaneOwners(destination, destinationTab) | 23 | publishTiledPaneOwners(destination, destinationTab) | ||
| 24 | d.mu.Lock() | 24 | d.mu.Lock() | ||
M internal/usecase/daemon/move_tab.go +0 -1
| @@ -185,7 +185,6 @@ func (d *Daemon) moveTab(req moveTabRequest) (result error) { | |||
| 185 | 185 | unlockDispatch: unlockDispatch, | |
| 186 | 186 | reservation: reservation, | |
| 187 | 187 | oldTabCancel: commit.oldTabCancel, | |
| 188 | - | sourceTabWasActive: !commit.sourceEmpty, | |
| 189 | 188 | sourceTabRemoved: true, | |
| 190 | 189 | sourceEmpty: commit.sourceEmpty, | |
| 191 | 190 | retiredParked: commit.retiredParked, | |
| @@ -185,7 +185,6 @@ func (d *Daemon) moveTab(req moveTabRequest) (result error) { | |||||
| 185 | unlockDispatch: unlockDispatch, | 185 | unlockDispatch: unlockDispatch, | ||
| 186 | reservation: reservation, | 186 | reservation: reservation, | ||
| 187 | oldTabCancel: commit.oldTabCancel, | 187 | oldTabCancel: commit.oldTabCancel, | ||
| 188 | - | sourceTabWasActive: !commit.sourceEmpty, | |||
| 189 | sourceTabRemoved: true, | 188 | sourceTabRemoved: true, | ||
| 190 | sourceEmpty: commit.sourceEmpty, | 189 | sourceEmpty: commit.sourceEmpty, | ||
| 191 | retiredParked: commit.retiredParked, | 190 | retiredParked: commit.retiredParked, | ||
M internal/usecase/daemon/move_tab_test.go +9 -9
| @@ -27,13 +27,13 @@ func TestMoveTabPreservesWholeTabAndActivatesLogicalNeighbor(t *testing.T) { | |||
| 27 | 27 | moved.name = "preserved-name" | |
| 28 | 28 | remaining := source.tabs[1] | |
| 29 | 29 | remaining.stableID = "remaining-tab" | |
| 30 | - | source.active = 0 | |
| 30 | + | selectTestAttachmentTab(source, 0) | |
| 31 | 31 | ||
| 32 | 32 | destinationCtx, destinationCancel := context.WithCancel(d.serveCtx) | |
| 33 | 33 | t.Cleanup(destinationCancel) | |
| 34 | 34 | destinationTab := newTabWithStableID("destination-tab", "destination-pane", destinationPTY, domain.Size{Cols: 100, Rows: 30}) | |
| 35 | 35 | destinationTab.ctx, destinationTab.cancel = context.WithCancel(destinationCtx) | |
| 36 | - | destination := &session{sessionCore: sessionCore{id: "destination", name: "destination", incarnation: domain.IncarnationID{9}, ephemeral: destinationEphemeral}, ctx: destinationCtx, cancel: destinationCancel, tabs: []*tab{destinationTab}, active: 0} | |
| 36 | + | destination := &session{sessionCore: sessionCore{id: "destination", name: "destination", incarnation: domain.IncarnationID{9}, ephemeral: destinationEphemeral}, ctx: destinationCtx, cancel: destinationCancel, tabs: []*tab{destinationTab}} | |
| 37 | 37 | publishTiledPaneOwners(destination, destinationTab) | |
| 38 | 38 | ||
| 39 | 39 | floating := newPaneWithStableID("floating", "floating-stable", floatingPTY, domain.Size{Cols: 20, Rows: 8}) | |
| @@ -53,7 +53,7 @@ func TestMoveTabPreservesWholeTabAndActivatesLogicalNeighbor(t *testing.T) { | |||
| 53 | 53 | oldWorkerCtx := moved.ctx | |
| 54 | 54 | oldTree := moved.tree | |
| 55 | 55 | oldPanes := moved.panes | |
| 56 | - | beforeDestinationActive := destination.tabs[destination.active] | |
| 56 | + | beforeDestinationActive := destination.tabs[testAttachmentTabIndex(destination)] | |
| 57 | 57 | ||
| 58 | 58 | err := d.moveTab(moveTabRequest{ | |
| 59 | 59 | Source: moveSessionLocator{ID: source.id, Incarnation: source.incarnation}, | |
| @@ -63,10 +63,10 @@ func TestMoveTabPreservesWholeTabAndActivatesLogicalNeighbor(t *testing.T) { | |||
| 63 | 63 | require.NoError(t, err) | |
| 64 | 64 | ||
| 65 | 65 | require.Equal(t, []*tab{remaining}, source.tabs) | |
| 66 | - | require.Zero(t, source.active) | |
| 67 | - | require.Same(t, remaining, source.tabs[source.active]) | |
| 66 | + | require.Zero(t, testAttachmentTabIndex(source)) | |
| 67 | + | require.Same(t, remaining, source.tabs[testAttachmentTabIndex(source)]) | |
| 68 | 68 | require.Equal(t, []*tab{destinationTab, moved}, destination.tabs) | |
| 69 | - | require.Same(t, beforeDestinationActive, destination.tabs[destination.active], "moved tab stays in the background") | |
| 69 | + | require.Same(t, beforeDestinationActive, destination.tabs[testAttachmentTabIndex(destination)], "moved tab stays in the background") | |
| 70 | 70 | require.Equal(t, destinationTab.size, moved.size, "destination geometry becomes authoritative") | |
| 71 | 71 | require.Equal(t, "preserved-name", moved.name) | |
| 72 | 72 | require.Same(t, oldTree, moved.tree) | |
| @@ -115,7 +115,7 @@ func TestMoveTabFinalSourceVisibleFloatingPreservesRuntimeAndFollowsClient(t *te | |||
| 115 | 115 | Destination: moveSessionLocator{ID: destination.id, Incarnation: destination.incarnation}, | |
| 116 | 116 | })) | |
| 117 | 117 | ||
| 118 | - | require.Same(t, moved, destination.tabs[destination.active]) | |
| 118 | + | require.Same(t, moved, destination.tabs[testAttachmentTabIndex(destination)]) | |
| 119 | 119 | require.Contains(t, destination.snapshotAttachments(), client) | |
| 120 | 120 | require.Same(t, destination, client.currentSession()) | |
| 121 | 121 | moved.mu.Lock() | |
| @@ -162,7 +162,7 @@ func TestMoveTabFinalSourceClientFollowsAndActivatesMovedTab(t *testing.T) { | |||
| 162 | 162 | require.Contains(t, destination.snapshotAttachments(), client) | |
| 163 | 163 | require.Equal(t, attachmentActive, destination.attachmentRole(client)) | |
| 164 | 164 | require.Equal(t, []*tab{oldDestinationActive, moved}, destination.tabs) | |
| 165 | - | require.Same(t, moved, destination.tabs[destination.active], "final-source follower activates the moved tab") | |
| 165 | + | require.Same(t, moved, destination.tabs[testAttachmentTabIndex(destination)], "final-source follower activates the moved tab") | |
| 166 | 166 | require.True(t, rebased) | |
| 167 | 167 | } | |
| 168 | 168 | ||
| @@ -572,7 +572,7 @@ func addMoveTabTestSession(d *Daemon, id domain.SessionID, tabID string) *sessio | |||
| 572 | 572 | ctx, cancel := context.WithCancel(d.serveCtx) | |
| 573 | 573 | tb := newTabWithStableID(tabID, tabID+"-pane", newQuietPTY(), domain.Size{Cols: 80, Rows: 23}) | |
| 574 | 574 | tb.ctx, tb.cancel = context.WithCancel(ctx) | |
| 575 | - | sess := &session{sessionCore: sessionCore{id: id, name: string(id), incarnation: domain.IncarnationID{5}, ephemeral: true}, ctx: ctx, cancel: cancel, tabs: []*tab{tb}, active: 0} | |
| 575 | + | sess := &session{sessionCore: sessionCore{id: id, name: string(id), incarnation: domain.IncarnationID{5}, ephemeral: true}, ctx: ctx, cancel: cancel, tabs: []*tab{tb}} | |
| 576 | 576 | publishTiledPaneOwners(sess, tb) | |
| 577 | 577 | d.mu.Lock() | |
| 578 | 578 | d.sessions[sess.id] = sess | |
| @@ -27,13 +27,13 @@ func TestMoveTabPreservesWholeTabAndActivatesLogicalNeighbor(t *testing.T) { | |||||
| 27 | moved.name = "preserved-name" | 27 | moved.name = "preserved-name" | ||
| 28 | remaining := source.tabs[1] | 28 | remaining := source.tabs[1] | ||
| 29 | remaining.stableID = "remaining-tab" | 29 | remaining.stableID = "remaining-tab" | ||
| 30 | - | source.active = 0 | 30 | + | selectTestAttachmentTab(source, 0) |
| 31 | 31 | ||||
| 32 | destinationCtx, destinationCancel := context.WithCancel(d.serveCtx) | 32 | destinationCtx, destinationCancel := context.WithCancel(d.serveCtx) | ||
| 33 | t.Cleanup(destinationCancel) | 33 | t.Cleanup(destinationCancel) | ||
| 34 | destinationTab := newTabWithStableID("destination-tab", "destination-pane", destinationPTY, domain.Size{Cols: 100, Rows: 30}) | 34 | destinationTab := newTabWithStableID("destination-tab", "destination-pane", destinationPTY, domain.Size{Cols: 100, Rows: 30}) | ||
| 35 | destinationTab.ctx, destinationTab.cancel = context.WithCancel(destinationCtx) | 35 | destinationTab.ctx, destinationTab.cancel = context.WithCancel(destinationCtx) | ||
| 36 | - | destination := &session{sessionCore: sessionCore{id: "destination", name: "destination", incarnation: domain.IncarnationID{9}, ephemeral: destinationEphemeral}, ctx: destinationCtx, cancel: destinationCancel, tabs: []*tab{destinationTab}, active: 0} | 36 | + | destination := &session{sessionCore: sessionCore{id: "destination", name: "destination", incarnation: domain.IncarnationID{9}, ephemeral: destinationEphemeral}, ctx: destinationCtx, cancel: destinationCancel, tabs: []*tab{destinationTab}} |
| 37 | publishTiledPaneOwners(destination, destinationTab) | 37 | publishTiledPaneOwners(destination, destinationTab) | ||
| 38 | 38 | ||||
| 39 | floating := newPaneWithStableID("floating", "floating-stable", floatingPTY, domain.Size{Cols: 20, Rows: 8}) | 39 | floating := newPaneWithStableID("floating", "floating-stable", floatingPTY, domain.Size{Cols: 20, Rows: 8}) | ||
| @@ -53,7 +53,7 @@ func TestMoveTabPreservesWholeTabAndActivatesLogicalNeighbor(t *testing.T) { | |||||
| 53 | oldWorkerCtx := moved.ctx | 53 | oldWorkerCtx := moved.ctx | ||
| 54 | oldTree := moved.tree | 54 | oldTree := moved.tree | ||
| 55 | oldPanes := moved.panes | 55 | oldPanes := moved.panes | ||
| 56 | - | beforeDestinationActive := destination.tabs[destination.active] | 56 | + | beforeDestinationActive := destination.tabs[testAttachmentTabIndex(destination)] |
| 57 | 57 | ||||
| 58 | err := d.moveTab(moveTabRequest{ | 58 | err := d.moveTab(moveTabRequest{ | ||
| 59 | Source: moveSessionLocator{ID: source.id, Incarnation: source.incarnation}, | 59 | Source: moveSessionLocator{ID: source.id, Incarnation: source.incarnation}, | ||
| @@ -63,10 +63,10 @@ func TestMoveTabPreservesWholeTabAndActivatesLogicalNeighbor(t *testing.T) { | |||||
| 63 | require.NoError(t, err) | 63 | require.NoError(t, err) | ||
| 64 | 64 | ||||
| 65 | require.Equal(t, []*tab{remaining}, source.tabs) | 65 | require.Equal(t, []*tab{remaining}, source.tabs) | ||
| 66 | - | require.Zero(t, source.active) | 66 | + | require.Zero(t, testAttachmentTabIndex(source)) |
| 67 | - | require.Same(t, remaining, source.tabs[source.active]) | 67 | + | require.Same(t, remaining, source.tabs[testAttachmentTabIndex(source)]) |
| 68 | require.Equal(t, []*tab{destinationTab, moved}, destination.tabs) | 68 | require.Equal(t, []*tab{destinationTab, moved}, destination.tabs) | ||
| 69 | - | require.Same(t, beforeDestinationActive, destination.tabs[destination.active], "moved tab stays in the background") | 69 | + | require.Same(t, beforeDestinationActive, destination.tabs[testAttachmentTabIndex(destination)], "moved tab stays in the background") |
| 70 | require.Equal(t, destinationTab.size, moved.size, "destination geometry becomes authoritative") | 70 | require.Equal(t, destinationTab.size, moved.size, "destination geometry becomes authoritative") | ||
| 71 | require.Equal(t, "preserved-name", moved.name) | 71 | require.Equal(t, "preserved-name", moved.name) | ||
| 72 | require.Same(t, oldTree, moved.tree) | 72 | require.Same(t, oldTree, moved.tree) | ||
| @@ -115,7 +115,7 @@ func TestMoveTabFinalSourceVisibleFloatingPreservesRuntimeAndFollowsClient(t *te | |||||
| 115 | Destination: moveSessionLocator{ID: destination.id, Incarnation: destination.incarnation}, | 115 | Destination: moveSessionLocator{ID: destination.id, Incarnation: destination.incarnation}, | ||
| 116 | })) | 116 | })) | ||
| 117 | 117 | ||||
| 118 | - | require.Same(t, moved, destination.tabs[destination.active]) | 118 | + | require.Same(t, moved, destination.tabs[testAttachmentTabIndex(destination)]) |
| 119 | require.Contains(t, destination.snapshotAttachments(), client) | 119 | require.Contains(t, destination.snapshotAttachments(), client) | ||
| 120 | require.Same(t, destination, client.currentSession()) | 120 | require.Same(t, destination, client.currentSession()) | ||
| 121 | moved.mu.Lock() | 121 | moved.mu.Lock() | ||
| @@ -162,7 +162,7 @@ func TestMoveTabFinalSourceClientFollowsAndActivatesMovedTab(t *testing.T) { | |||||
| 162 | require.Contains(t, destination.snapshotAttachments(), client) | 162 | require.Contains(t, destination.snapshotAttachments(), client) | ||
| 163 | require.Equal(t, attachmentActive, destination.attachmentRole(client)) | 163 | require.Equal(t, attachmentActive, destination.attachmentRole(client)) | ||
| 164 | require.Equal(t, []*tab{oldDestinationActive, moved}, destination.tabs) | 164 | require.Equal(t, []*tab{oldDestinationActive, moved}, destination.tabs) | ||
| 165 | - | require.Same(t, moved, destination.tabs[destination.active], "final-source follower activates the moved tab") | 165 | + | require.Same(t, moved, destination.tabs[testAttachmentTabIndex(destination)], "final-source follower activates the moved tab") |
| 166 | require.True(t, rebased) | 166 | require.True(t, rebased) | ||
| 167 | } | 167 | } | ||
| 168 | 168 | ||||
| @@ -572,7 +572,7 @@ func addMoveTabTestSession(d *Daemon, id domain.SessionID, tabID string) *sessio | |||||
| 572 | ctx, cancel := context.WithCancel(d.serveCtx) | 572 | ctx, cancel := context.WithCancel(d.serveCtx) | ||
| 573 | tb := newTabWithStableID(tabID, tabID+"-pane", newQuietPTY(), domain.Size{Cols: 80, Rows: 23}) | 573 | tb := newTabWithStableID(tabID, tabID+"-pane", newQuietPTY(), domain.Size{Cols: 80, Rows: 23}) | ||
| 574 | tb.ctx, tb.cancel = context.WithCancel(ctx) | 574 | tb.ctx, tb.cancel = context.WithCancel(ctx) | ||
| 575 | - | sess := &session{sessionCore: sessionCore{id: id, name: string(id), incarnation: domain.IncarnationID{5}, ephemeral: true}, ctx: ctx, cancel: cancel, tabs: []*tab{tb}, active: 0} | 575 | + | sess := &session{sessionCore: sessionCore{id: id, name: string(id), incarnation: domain.IncarnationID{5}, ephemeral: true}, ctx: ctx, cancel: cancel, tabs: []*tab{tb}} |
| 576 | publishTiledPaneOwners(sess, tb) | 576 | publishTiledPaneOwners(sess, tb) | ||
| 577 | d.mu.Lock() | 577 | d.mu.Lock() | ||
| 578 | d.sessions[sess.id] = sess | 578 | d.sessions[sess.id] = sess | ||
M internal/usecase/daemon/move_test.go +5 -9
| @@ -57,7 +57,7 @@ func TestMovePaneMutationCrossSession(t *testing.T) { | |||
| 57 | 57 | movedCtx, movedCancel := context.WithCancel(d.paneProcessCtx) | |
| 58 | 58 | moved.ctx, moved.cancel = movedCtx, movedCancel | |
| 59 | 59 | ||
| 60 | - | destination := &session{sessionCore: sessionCore{id: "destination", name: "destination", ephemeral: true}, tabs: []*tab{newTab(p2, domain.Size{Cols: 80, Rows: 23})}, active: 0} | |
| 60 | + | destination := &session{sessionCore: sessionCore{id: "destination", name: "destination", ephemeral: true}, tabs: []*tab{newTab(p2, domain.Size{Cols: 80, Rows: 23})}} | |
| 61 | 61 | destinationTab := destination.tabs[0] | |
| 62 | 62 | destinationTab.stableID = "destination-tab" | |
| 63 | 63 | publishTiledPaneOwners(destination, destinationTab) | |
| @@ -97,7 +97,7 @@ func TestMovePaneFinalSourceClientFollowsDestination(t *testing.T) { | |||
| 97 | 97 | require.NotNil(t, rc) | |
| 98 | 98 | var rebased atomic.Bool | |
| 99 | 99 | client.renderStages.handoffRebase = func() { rebased.Store(true) } | |
| 100 | - | destination := &session{sessionCore: sessionCore{id: "destination", name: "destination"}, tabs: []*tab{newTab(p2, domain.Size{Cols: 80, Rows: 23})}, active: 0} | |
| 100 | + | destination := &session{sessionCore: sessionCore{id: "destination", name: "destination"}, tabs: []*tab{newTab(p2, domain.Size{Cols: 80, Rows: 23})}} | |
| 101 | 101 | destinationTab := destination.tabs[0] | |
| 102 | 102 | destinationTab.stableID = "destination-tab" | |
| 103 | 103 | publishTiledPaneOwners(destination, destinationTab) | |
| @@ -117,7 +117,7 @@ func TestMovePaneFinalSourceClientFollowsDestination(t *testing.T) { | |||
| 117 | 117 | require.Same(t, destination, client.currentSession()) | |
| 118 | 118 | require.Contains(t, destination.snapshotAttachments(), client) | |
| 119 | 119 | require.Equal(t, attachmentActive, destination.attachmentRole(client)) | |
| 120 | - | require.Same(t, destinationTab, destination.tabs[destination.active]) | |
| 120 | + | require.Same(t, destinationTab, destination.tabs[testAttachmentTabIndex(destination)]) | |
| 121 | 121 | require.True(t, rebased.Load(), "final-source follower must rebase output before its first paint") | |
| 122 | 122 | require.NoError(t, moved.ctx.Err(), "transferred pane process must remain alive") | |
| 123 | 123 | } | |
| @@ -140,9 +140,7 @@ func TestMovePaneRetiresEmptySourceWithoutClosingTransferredResources(t *testing | |||
| 140 | 140 | movedTab.stableID = "source-tab" | |
| 141 | 141 | ||
| 142 | 142 | destinationPTY := newQuietPTY() | |
| 143 | - | destination := &session{sessionCore: sessionCore{id: "destination", name: "destination", ephemeral: true}, tabs: []*tab{newTabWithStableID("destination-tab", "destination-pane", destinationPTY, domain.Size{Cols: 80, Rows: 23})}, | |
| 144 | - | active: 0, | |
| 145 | - | } | |
| 143 | + | destination := &session{sessionCore: sessionCore{id: "destination", name: "destination", ephemeral: true}, tabs: []*tab{newTabWithStableID("destination-tab", "destination-pane", destinationPTY, domain.Size{Cols: 80, Rows: 23})}} | |
| 146 | 144 | publishTiledPaneOwners(destination, destination.tabs[0]) | |
| 147 | 145 | d.mu.Lock() | |
| 148 | 146 | d.sessions[destination.id] = destination | |
| @@ -190,9 +188,7 @@ func TestMovePaneRetiresSourceParkedClients(t *testing.T) { | |||
| 190 | 188 | parked.initOverlays() | |
| 191 | 189 | require.True(t, d.parkAttachment(source, parked)) | |
| 192 | 190 | destinationPTY := newQuietPTY() | |
| 193 | - | destination := &session{sessionCore: sessionCore{id: "destination", name: "destination", ephemeral: true}, tabs: []*tab{newTabWithStableID("destination-tab", "destination-pane", destinationPTY, domain.Size{Cols: 80, Rows: 23})}, | |
| 194 | - | active: 0, | |
| 195 | - | } | |
| 191 | + | destination := &session{sessionCore: sessionCore{id: "destination", name: "destination", ephemeral: true}, tabs: []*tab{newTabWithStableID("destination-tab", "destination-pane", destinationPTY, domain.Size{Cols: 80, Rows: 23})}} | |
| 196 | 192 | publishTiledPaneOwners(destination, destination.tabs[0]) | |
| 197 | 193 | d.mu.Lock() | |
| 198 | 194 | d.sessions[destination.id] = destination | |
| @@ -57,7 +57,7 @@ func TestMovePaneMutationCrossSession(t *testing.T) { | |||||
| 57 | movedCtx, movedCancel := context.WithCancel(d.paneProcessCtx) | 57 | movedCtx, movedCancel := context.WithCancel(d.paneProcessCtx) | ||
| 58 | moved.ctx, moved.cancel = movedCtx, movedCancel | 58 | moved.ctx, moved.cancel = movedCtx, movedCancel | ||
| 59 | 59 | ||||
| 60 | - | destination := &session{sessionCore: sessionCore{id: "destination", name: "destination", ephemeral: true}, tabs: []*tab{newTab(p2, domain.Size{Cols: 80, Rows: 23})}, active: 0} | 60 | + | destination := &session{sessionCore: sessionCore{id: "destination", name: "destination", ephemeral: true}, tabs: []*tab{newTab(p2, domain.Size{Cols: 80, Rows: 23})}} |
| 61 | destinationTab := destination.tabs[0] | 61 | destinationTab := destination.tabs[0] | ||
| 62 | destinationTab.stableID = "destination-tab" | 62 | destinationTab.stableID = "destination-tab" | ||
| 63 | publishTiledPaneOwners(destination, destinationTab) | 63 | publishTiledPaneOwners(destination, destinationTab) | ||
| @@ -97,7 +97,7 @@ func TestMovePaneFinalSourceClientFollowsDestination(t *testing.T) { | |||||
| 97 | require.NotNil(t, rc) | 97 | require.NotNil(t, rc) | ||
| 98 | var rebased atomic.Bool | 98 | var rebased atomic.Bool | ||
| 99 | client.renderStages.handoffRebase = func() { rebased.Store(true) } | 99 | client.renderStages.handoffRebase = func() { rebased.Store(true) } | ||
| 100 | - | destination := &session{sessionCore: sessionCore{id: "destination", name: "destination"}, tabs: []*tab{newTab(p2, domain.Size{Cols: 80, Rows: 23})}, active: 0} | 100 | + | destination := &session{sessionCore: sessionCore{id: "destination", name: "destination"}, tabs: []*tab{newTab(p2, domain.Size{Cols: 80, Rows: 23})}} |
| 101 | destinationTab := destination.tabs[0] | 101 | destinationTab := destination.tabs[0] | ||
| 102 | destinationTab.stableID = "destination-tab" | 102 | destinationTab.stableID = "destination-tab" | ||
| 103 | publishTiledPaneOwners(destination, destinationTab) | 103 | publishTiledPaneOwners(destination, destinationTab) | ||
| @@ -117,7 +117,7 @@ func TestMovePaneFinalSourceClientFollowsDestination(t *testing.T) { | |||||
| 117 | require.Same(t, destination, client.currentSession()) | 117 | require.Same(t, destination, client.currentSession()) | ||
| 118 | require.Contains(t, destination.snapshotAttachments(), client) | 118 | require.Contains(t, destination.snapshotAttachments(), client) | ||
| 119 | require.Equal(t, attachmentActive, destination.attachmentRole(client)) | 119 | require.Equal(t, attachmentActive, destination.attachmentRole(client)) | ||
| 120 | - | require.Same(t, destinationTab, destination.tabs[destination.active]) | 120 | + | require.Same(t, destinationTab, destination.tabs[testAttachmentTabIndex(destination)]) |
| 121 | require.True(t, rebased.Load(), "final-source follower must rebase output before its first paint") | 121 | require.True(t, rebased.Load(), "final-source follower must rebase output before its first paint") | ||
| 122 | require.NoError(t, moved.ctx.Err(), "transferred pane process must remain alive") | 122 | require.NoError(t, moved.ctx.Err(), "transferred pane process must remain alive") | ||
| 123 | } | 123 | } | ||
| @@ -140,9 +140,7 @@ func TestMovePaneRetiresEmptySourceWithoutClosingTransferredResources(t *testing | |||||
| 140 | movedTab.stableID = "source-tab" | 140 | movedTab.stableID = "source-tab" | ||
| 141 | 141 | ||||
| 142 | destinationPTY := newQuietPTY() | 142 | destinationPTY := newQuietPTY() | ||
| 143 | - | destination := &session{sessionCore: sessionCore{id: "destination", name: "destination", ephemeral: true}, tabs: []*tab{newTabWithStableID("destination-tab", "destination-pane", destinationPTY, domain.Size{Cols: 80, Rows: 23})}, | 143 | + | destination := &session{sessionCore: sessionCore{id: "destination", name: "destination", ephemeral: true}, tabs: []*tab{newTabWithStableID("destination-tab", "destination-pane", destinationPTY, domain.Size{Cols: 80, Rows: 23})}} |
| 144 | - | active: 0, | |||
| 145 | - | } | |||
| 146 | publishTiledPaneOwners(destination, destination.tabs[0]) | 144 | publishTiledPaneOwners(destination, destination.tabs[0]) | ||
| 147 | d.mu.Lock() | 145 | d.mu.Lock() | ||
| 148 | d.sessions[destination.id] = destination | 146 | d.sessions[destination.id] = destination | ||
| @@ -190,9 +188,7 @@ func TestMovePaneRetiresSourceParkedClients(t *testing.T) { | |||||
| 190 | parked.initOverlays() | 188 | parked.initOverlays() | ||
| 191 | require.True(t, d.parkAttachment(source, parked)) | 189 | require.True(t, d.parkAttachment(source, parked)) | ||
| 192 | destinationPTY := newQuietPTY() | 190 | destinationPTY := newQuietPTY() | ||
| 193 | - | destination := &session{sessionCore: sessionCore{id: "destination", name: "destination", ephemeral: true}, tabs: []*tab{newTabWithStableID("destination-tab", "destination-pane", destinationPTY, domain.Size{Cols: 80, Rows: 23})}, | 191 | + | destination := &session{sessionCore: sessionCore{id: "destination", name: "destination", ephemeral: true}, tabs: []*tab{newTabWithStableID("destination-tab", "destination-pane", destinationPTY, domain.Size{Cols: 80, Rows: 23})}} |
| 194 | - | active: 0, | |||
| 195 | - | } | |||
| 196 | publishTiledPaneOwners(destination, destination.tabs[0]) | 192 | publishTiledPaneOwners(destination, destination.tabs[0]) | ||
| 197 | d.mu.Lock() | 193 | d.mu.Lock() | ||
| 198 | d.sessions[destination.id] = destination | 194 | d.sessions[destination.id] = destination | ||
M internal/usecase/daemon/navigation_overflow_test.go +10 -10
| @@ -58,7 +58,7 @@ func TestKeyboardHorizontalOverflowLandsOnFacingEdge(t *testing.T) { | |||
| 58 | 58 | t.Run(tt.name, func(t *testing.T) { | |
| 59 | 59 | d, sess, ac, _ := newManualSessionWithPTYs(t, nil, nil) | |
| 60 | 60 | sess.mu.Lock() | |
| 61 | - | sess.active = tt.start | |
| 61 | + | selectTestAttachmentTabLocked(sess, tt.start) | |
| 62 | 62 | target := sess.tabs[tt.wantActive] | |
| 63 | 63 | source := sess.tabs[tt.start] | |
| 64 | 64 | sess.mu.Unlock() | |
| @@ -71,11 +71,11 @@ func TestKeyboardHorizontalOverflowLandsOnFacingEdge(t *testing.T) { | |||
| 71 | 71 | ||
| 72 | 72 | daemonKeyHandler{d: d, ac: ac}.Action(tt.action, nil) | |
| 73 | 73 | ||
| 74 | - | require.Equal(t, tt.wantActive, activeTabIndex(sess)) | |
| 74 | + | require.Equal(t, tt.wantActive, testAttachmentTabIndex(sess)) | |
| 75 | 75 | target.mu.Lock() | |
| 76 | 76 | require.Equal(t, tt.wantFocus, target.tree.Focus) | |
| 77 | 77 | target.mu.Unlock() | |
| 78 | - | require.NotSame(t, source, sess.activeTab()) | |
| 78 | + | require.NotSame(t, source, testAttachmentTab(sess)) | |
| 79 | 79 | }) | |
| 80 | 80 | } | |
| 81 | 81 | } | |
| @@ -93,7 +93,7 @@ func TestKeyboardVerticalOverflowSwitchesOnlyAcrossAlphabeticalLiveSessions(t *t | |||
| 93 | 93 | target.tree = &layout.Tree{Root: &layout.Node{Kind: layout.Split, Dir: layout.Horizontal, Children: []*layout.Node{layout.NewLeaf("pane-1"), layout.NewLeaf("pane-2")}}, Focus: focus} | |
| 94 | 94 | target.panes["pane-2"] = newPane("pane-2", nil, domain.Size{Cols: 20, Rows: 10}) | |
| 95 | 95 | target.mu.Unlock() | |
| 96 | - | return &session{sessionCore: sessionCore{id: domain.SessionID(id), name: name}, ctx: t.Context(), cancel: func() {}, tabs: tabs, active: active} | |
| 96 | + | return &session{sessionCore: sessionCore{id: domain.SessionID(id), name: name}, ctx: t.Context(), cancel: func() {}, tabs: tabs} | |
| 97 | 97 | } | |
| 98 | 98 | charlie := newSession("live-charlie", "charlie", 1, "pane-2") | |
| 99 | 99 | echo := newSession("live-echo", "echo", 1, "pane-2") | |
| @@ -130,7 +130,7 @@ func TestKeyboardVerticalOverflowSwitchesOnlyAcrossAlphabeticalLiveSessions(t *t | |||
| 130 | 130 | } | |
| 131 | 131 | ||
| 132 | 132 | for _, target := range []*session{charlie, echo} { | |
| 133 | - | require.Equal(t, 1, activeTabIndex(target), "switch preserves the target active tab") | |
| 133 | + | require.Equal(t, 1, testAttachmentTabIndex(target), "switch preserves the target active tab") | |
| 134 | 134 | target.tabs[1].mu.Lock() | |
| 135 | 135 | require.Equal(t, layout.PaneID("pane-2"), target.tabs[1].tree.Focus, "switch preserves the target pane focus") | |
| 136 | 136 | target.tabs[1].mu.Unlock() | |
| @@ -208,7 +208,7 @@ func TestVerticalOverflowRejectsStaleSourceTab(t *testing.T) { | |||
| 208 | 208 | alpha.mu.Lock() | |
| 209 | 209 | alpha.name = "alpha" | |
| 210 | 210 | expectedSource := alpha.tabs[0] | |
| 211 | - | alpha.active = 1 | |
| 211 | + | selectTestAttachmentTabLocked(alpha, 1) | |
| 212 | 212 | alpha.mu.Unlock() | |
| 213 | 213 | charlie := &session{sessionCore: sessionCore{id: "live-charlie", name: "charlie"}, ctx: t.Context(), cancel: func() {}, tabs: []*tab{newTab(nil, domain.Size{Cols: 41, Rows: 10})}} | |
| 214 | 214 | d.mu.Lock() | |
| @@ -351,7 +351,7 @@ func TestKeyboardHorizontalOverflowRespectsDefaultsWallsFailedEntryAndFloating(t | |||
| 351 | 351 | ||
| 352 | 352 | daemonKeyHandler{d: d, ac: ac}.Action(tt.dir, nil) | |
| 353 | 353 | ||
| 354 | - | require.Equal(t, 0, activeTabIndex(sess)) | |
| 354 | + | require.Equal(t, 0, testAttachmentTabIndex(sess)) | |
| 355 | 355 | }) | |
| 356 | 356 | } | |
| 357 | 357 | } | |
| @@ -363,7 +363,7 @@ func TestCommitTabOverflowRevalidatesTabPointerIdentities(t *testing.T) { | |||
| 363 | 363 | }{ | |
| 364 | 364 | {name: "source no longer active", mutate: func(sess *session, _ tabOverflowCandidate) { | |
| 365 | 365 | sess.mu.Lock() | |
| 366 | - | sess.active = 1 | |
| 366 | + | selectTestAttachmentTabLocked(sess, 1) | |
| 367 | 367 | sess.mu.Unlock() | |
| 368 | 368 | }}, | |
| 369 | 369 | {name: "target entry replaced", mutate: func(sess *session, candidate tabOverflowCandidate) { | |
| @@ -382,10 +382,10 @@ func TestCommitTabOverflowRevalidatesTabPointerIdentities(t *testing.T) { | |||
| 382 | 382 | candidate, ok := d.prepareTabOverflow(sess, sess.tabs[0], layout.Right, domain.Rect{Width: 80, Height: 23}, 1) | |
| 383 | 383 | require.True(t, ok) | |
| 384 | 384 | tt.mutate(sess, candidate) | |
| 385 | - | activeBeforeCommit := activeTabIndex(sess) | |
| 385 | + | activeBeforeCommit := testAttachmentTabIndex(sess) | |
| 386 | 386 | ||
| 387 | 387 | require.False(t, d.commitTabOverflow(sess, candidate)) | |
| 388 | - | require.Equal(t, activeBeforeCommit, activeTabIndex(sess)) | |
| 388 | + | require.Equal(t, activeBeforeCommit, testAttachmentTabIndex(sess)) | |
| 389 | 389 | }) | |
| 390 | 390 | } | |
| 391 | 391 | } | |
| @@ -58,7 +58,7 @@ func TestKeyboardHorizontalOverflowLandsOnFacingEdge(t *testing.T) { | |||||
| 58 | t.Run(tt.name, func(t *testing.T) { | 58 | t.Run(tt.name, func(t *testing.T) { | ||
| 59 | d, sess, ac, _ := newManualSessionWithPTYs(t, nil, nil) | 59 | d, sess, ac, _ := newManualSessionWithPTYs(t, nil, nil) | ||
| 60 | sess.mu.Lock() | 60 | sess.mu.Lock() | ||
| 61 | - | sess.active = tt.start | 61 | + | selectTestAttachmentTabLocked(sess, tt.start) |
| 62 | target := sess.tabs[tt.wantActive] | 62 | target := sess.tabs[tt.wantActive] | ||
| 63 | source := sess.tabs[tt.start] | 63 | source := sess.tabs[tt.start] | ||
| 64 | sess.mu.Unlock() | 64 | sess.mu.Unlock() | ||
| @@ -71,11 +71,11 @@ func TestKeyboardHorizontalOverflowLandsOnFacingEdge(t *testing.T) { | |||||
| 71 | 71 | ||||
| 72 | daemonKeyHandler{d: d, ac: ac}.Action(tt.action, nil) | 72 | daemonKeyHandler{d: d, ac: ac}.Action(tt.action, nil) | ||
| 73 | 73 | ||||
| 74 | - | require.Equal(t, tt.wantActive, activeTabIndex(sess)) | 74 | + | require.Equal(t, tt.wantActive, testAttachmentTabIndex(sess)) |
| 75 | target.mu.Lock() | 75 | target.mu.Lock() | ||
| 76 | require.Equal(t, tt.wantFocus, target.tree.Focus) | 76 | require.Equal(t, tt.wantFocus, target.tree.Focus) | ||
| 77 | target.mu.Unlock() | 77 | target.mu.Unlock() | ||
| 78 | - | require.NotSame(t, source, sess.activeTab()) | 78 | + | require.NotSame(t, source, testAttachmentTab(sess)) |
| 79 | }) | 79 | }) | ||
| 80 | } | 80 | } | ||
| 81 | } | 81 | } | ||
| @@ -93,7 +93,7 @@ func TestKeyboardVerticalOverflowSwitchesOnlyAcrossAlphabeticalLiveSessions(t *t | |||||
| 93 | target.tree = &layout.Tree{Root: &layout.Node{Kind: layout.Split, Dir: layout.Horizontal, Children: []*layout.Node{layout.NewLeaf("pane-1"), layout.NewLeaf("pane-2")}}, Focus: focus} | 93 | target.tree = &layout.Tree{Root: &layout.Node{Kind: layout.Split, Dir: layout.Horizontal, Children: []*layout.Node{layout.NewLeaf("pane-1"), layout.NewLeaf("pane-2")}}, Focus: focus} | ||
| 94 | target.panes["pane-2"] = newPane("pane-2", nil, domain.Size{Cols: 20, Rows: 10}) | 94 | target.panes["pane-2"] = newPane("pane-2", nil, domain.Size{Cols: 20, Rows: 10}) | ||
| 95 | target.mu.Unlock() | 95 | target.mu.Unlock() | ||
| 96 | - | return &session{sessionCore: sessionCore{id: domain.SessionID(id), name: name}, ctx: t.Context(), cancel: func() {}, tabs: tabs, active: active} | 96 | + | return &session{sessionCore: sessionCore{id: domain.SessionID(id), name: name}, ctx: t.Context(), cancel: func() {}, tabs: tabs} |
| 97 | } | 97 | } | ||
| 98 | charlie := newSession("live-charlie", "charlie", 1, "pane-2") | 98 | charlie := newSession("live-charlie", "charlie", 1, "pane-2") | ||
| 99 | echo := newSession("live-echo", "echo", 1, "pane-2") | 99 | echo := newSession("live-echo", "echo", 1, "pane-2") | ||
| @@ -130,7 +130,7 @@ func TestKeyboardVerticalOverflowSwitchesOnlyAcrossAlphabeticalLiveSessions(t *t | |||||
| 130 | } | 130 | } | ||
| 131 | 131 | ||||
| 132 | for _, target := range []*session{charlie, echo} { | 132 | for _, target := range []*session{charlie, echo} { | ||
| 133 | - | require.Equal(t, 1, activeTabIndex(target), "switch preserves the target active tab") | 133 | + | require.Equal(t, 1, testAttachmentTabIndex(target), "switch preserves the target active tab") |
| 134 | target.tabs[1].mu.Lock() | 134 | target.tabs[1].mu.Lock() | ||
| 135 | require.Equal(t, layout.PaneID("pane-2"), target.tabs[1].tree.Focus, "switch preserves the target pane focus") | 135 | require.Equal(t, layout.PaneID("pane-2"), target.tabs[1].tree.Focus, "switch preserves the target pane focus") | ||
| 136 | target.tabs[1].mu.Unlock() | 136 | target.tabs[1].mu.Unlock() | ||
| @@ -208,7 +208,7 @@ func TestVerticalOverflowRejectsStaleSourceTab(t *testing.T) { | |||||
| 208 | alpha.mu.Lock() | 208 | alpha.mu.Lock() | ||
| 209 | alpha.name = "alpha" | 209 | alpha.name = "alpha" | ||
| 210 | expectedSource := alpha.tabs[0] | 210 | expectedSource := alpha.tabs[0] | ||
| 211 | - | alpha.active = 1 | 211 | + | selectTestAttachmentTabLocked(alpha, 1) |
| 212 | alpha.mu.Unlock() | 212 | alpha.mu.Unlock() | ||
| 213 | charlie := &session{sessionCore: sessionCore{id: "live-charlie", name: "charlie"}, ctx: t.Context(), cancel: func() {}, tabs: []*tab{newTab(nil, domain.Size{Cols: 41, Rows: 10})}} | 213 | charlie := &session{sessionCore: sessionCore{id: "live-charlie", name: "charlie"}, ctx: t.Context(), cancel: func() {}, tabs: []*tab{newTab(nil, domain.Size{Cols: 41, Rows: 10})}} | ||
| 214 | d.mu.Lock() | 214 | d.mu.Lock() | ||
| @@ -351,7 +351,7 @@ func TestKeyboardHorizontalOverflowRespectsDefaultsWallsFailedEntryAndFloating(t | |||||
| 351 | 351 | ||||
| 352 | daemonKeyHandler{d: d, ac: ac}.Action(tt.dir, nil) | 352 | daemonKeyHandler{d: d, ac: ac}.Action(tt.dir, nil) | ||
| 353 | 353 | ||||
| 354 | - | require.Equal(t, 0, activeTabIndex(sess)) | 354 | + | require.Equal(t, 0, testAttachmentTabIndex(sess)) |
| 355 | }) | 355 | }) | ||
| 356 | } | 356 | } | ||
| 357 | } | 357 | } | ||
| @@ -363,7 +363,7 @@ func TestCommitTabOverflowRevalidatesTabPointerIdentities(t *testing.T) { | |||||
| 363 | }{ | 363 | }{ | ||
| 364 | {name: "source no longer active", mutate: func(sess *session, _ tabOverflowCandidate) { | 364 | {name: "source no longer active", mutate: func(sess *session, _ tabOverflowCandidate) { | ||
| 365 | sess.mu.Lock() | 365 | sess.mu.Lock() | ||
| 366 | - | sess.active = 1 | 366 | + | selectTestAttachmentTabLocked(sess, 1) |
| 367 | sess.mu.Unlock() | 367 | sess.mu.Unlock() | ||
| 368 | }}, | 368 | }}, | ||
| 369 | {name: "target entry replaced", mutate: func(sess *session, candidate tabOverflowCandidate) { | 369 | {name: "target entry replaced", mutate: func(sess *session, candidate tabOverflowCandidate) { | ||
| @@ -382,10 +382,10 @@ func TestCommitTabOverflowRevalidatesTabPointerIdentities(t *testing.T) { | |||||
| 382 | candidate, ok := d.prepareTabOverflow(sess, sess.tabs[0], layout.Right, domain.Rect{Width: 80, Height: 23}, 1) | 382 | candidate, ok := d.prepareTabOverflow(sess, sess.tabs[0], layout.Right, domain.Rect{Width: 80, Height: 23}, 1) | ||
| 383 | require.True(t, ok) | 383 | require.True(t, ok) | ||
| 384 | tt.mutate(sess, candidate) | 384 | tt.mutate(sess, candidate) | ||
| 385 | - | activeBeforeCommit := activeTabIndex(sess) | 385 | + | activeBeforeCommit := testAttachmentTabIndex(sess) |
| 386 | 386 | ||||
| 387 | require.False(t, d.commitTabOverflow(sess, candidate)) | 387 | require.False(t, d.commitTabOverflow(sess, candidate)) | ||
| 388 | - | require.Equal(t, activeBeforeCommit, activeTabIndex(sess)) | 388 | + | require.Equal(t, activeBeforeCommit, testAttachmentTabIndex(sess)) |
| 389 | }) | 389 | }) | ||
| 390 | } | 390 | } | ||
| 391 | } | 391 | } | ||
M internal/usecase/daemon/navigation_role_handoff_test.go +1 -1
| @@ -136,7 +136,7 @@ func TestNavigationHandoffsDropReplacedInitiatorWithoutMutation(t *testing.T) { | |||
| 136 | 136 | source.mu.Unlock() | |
| 137 | 137 | target.mu.Lock() | |
| 138 | 138 | require.Empty(t, target.snapshotAttachmentsLocked(), "stale handoff attached to the target") | |
| 139 | - | require.Zero(t, target.active, "stale handoff changed target tab focus") | |
| 139 | + | require.Zero(t, testAttachmentTabIndexLocked(target), "stale handoff changed target tab focus") | |
| 140 | 140 | target.mu.Unlock() | |
| 141 | 141 | if tt.check != nil { | |
| 142 | 142 | tt.check(t, d, source, target) | |
| @@ -136,7 +136,7 @@ func TestNavigationHandoffsDropReplacedInitiatorWithoutMutation(t *testing.T) { | |||||
| 136 | source.mu.Unlock() | 136 | source.mu.Unlock() | ||
| 137 | target.mu.Lock() | 137 | target.mu.Lock() | ||
| 138 | require.Empty(t, target.snapshotAttachmentsLocked(), "stale handoff attached to the target") | 138 | require.Empty(t, target.snapshotAttachmentsLocked(), "stale handoff attached to the target") | ||
| 139 | - | require.Zero(t, target.active, "stale handoff changed target tab focus") | 139 | + | require.Zero(t, testAttachmentTabIndexLocked(target), "stale handoff changed target tab focus") |
| 140 | target.mu.Unlock() | 140 | target.mu.Unlock() | ||
| 141 | if tt.check != nil { | 141 | if tt.check != nil { | ||
| 142 | tt.check(t, d, source, target) | 142 | tt.check(t, d, source, target) | ||
M internal/usecase/daemon/navigation_role_incarnation_test.go +1 -1
| @@ -74,7 +74,7 @@ func TestNavigationHandoffRejectsResumedOrReboundInitiatorIncarnation(t *testing | |||
| 74 | 74 | require.Same(t, source, ac.currentSession()) | |
| 75 | 75 | target.mu.Lock() | |
| 76 | 76 | require.Empty(t, target.snapshotAttachmentsLocked()) | |
| 77 | - | require.Zero(t, target.active) | |
| 77 | + | require.Zero(t, testAttachmentTabIndexLocked(target)) | |
| 78 | 78 | target.mu.Unlock() | |
| 79 | 79 | }) | |
| 80 | 80 | } | |
| @@ -74,7 +74,7 @@ func TestNavigationHandoffRejectsResumedOrReboundInitiatorIncarnation(t *testing | |||||
| 74 | require.Same(t, source, ac.currentSession()) | 74 | require.Same(t, source, ac.currentSession()) | ||
| 75 | target.mu.Lock() | 75 | target.mu.Lock() | ||
| 76 | require.Empty(t, target.snapshotAttachmentsLocked()) | 76 | require.Empty(t, target.snapshotAttachmentsLocked()) | ||
| 77 | - | require.Zero(t, target.active) | 77 | + | require.Zero(t, testAttachmentTabIndexLocked(target)) |
| 78 | target.mu.Unlock() | 78 | target.mu.Unlock() | ||
| 79 | }) | 79 | }) | ||
| 80 | } | 80 | } | ||
M internal/usecase/daemon/output_state_sync_test.go +3 -3
| @@ -285,7 +285,7 @@ func TestResizeGrowthFirstFrameIncludesConcurrentPTYRedraw(t *testing.T) { | |||
| 285 | 285 | p := newResizeReaderPTY([]byte("\x1b[1;81H" + strings.Repeat("B", 40))) | |
| 286 | 286 | d, sess, ac, sends := newManualSessionWithPTYs(t, p) | |
| 287 | 287 | ac.output.maxOutstanding = tc.window | |
| 288 | - | pane := sess.activeTab().focusedPane() | |
| 288 | + | pane := testAttachmentTab(sess).focusedPane() | |
| 289 | 289 | p.applying = func() bool { | |
| 290 | 290 | pane.mu.Lock() | |
| 291 | 291 | defer pane.mu.Unlock() | |
| @@ -299,7 +299,7 @@ func TestResizeGrowthFirstFrameIncludesConcurrentPTYRedraw(t *testing.T) { | |||
| 299 | 299 | ||
| 300 | 300 | d.sessWg.Add(1) | |
| 301 | 301 | pane.onExit = func() {} | |
| 302 | - | go d.ptyReader(sess, sess.activeTab(), pane) | |
| 302 | + | go d.ptyReader(sess, testAttachmentTab(sess), pane) | |
| 303 | 303 | defer func() { p.close(); d.sessWg.Wait() }() | |
| 304 | 304 | ||
| 305 | 305 | // The resize deadline drives coordinator prepare/apply/commit. No test | |
| @@ -387,7 +387,7 @@ func TestResizeBurstFlushesOnlyLatestGeometry(t *testing.T) { | |||
| 387 | 387 | awaitTestCompletion(t, latestDone, "latest resize callback did not complete") | |
| 388 | 388 | awaitFrame(t, sends, ports.MsgOutput) | |
| 389 | 389 | require.Equal(t, domain.Size{Cols: 120, Rows: 24}, ac.size) | |
| 390 | - | require.Equal(t, 120, sess.activeTab().focusedPane().screen.Frame.Width) | |
| 390 | + | require.Equal(t, 120, testAttachmentTab(sess).focusedPane().screen.Frame.Width) | |
| 391 | 391 | requireNoOutputFrame(t, sends) | |
| 392 | 392 | } | |
| 393 | 393 | ||
| @@ -285,7 +285,7 @@ func TestResizeGrowthFirstFrameIncludesConcurrentPTYRedraw(t *testing.T) { | |||||
| 285 | p := newResizeReaderPTY([]byte("\x1b[1;81H" + strings.Repeat("B", 40))) | 285 | p := newResizeReaderPTY([]byte("\x1b[1;81H" + strings.Repeat("B", 40))) | ||
| 286 | d, sess, ac, sends := newManualSessionWithPTYs(t, p) | 286 | d, sess, ac, sends := newManualSessionWithPTYs(t, p) | ||
| 287 | ac.output.maxOutstanding = tc.window | 287 | ac.output.maxOutstanding = tc.window | ||
| 288 | - | pane := sess.activeTab().focusedPane() | 288 | + | pane := testAttachmentTab(sess).focusedPane() |
| 289 | p.applying = func() bool { | 289 | p.applying = func() bool { | ||
| 290 | pane.mu.Lock() | 290 | pane.mu.Lock() | ||
| 291 | defer pane.mu.Unlock() | 291 | defer pane.mu.Unlock() | ||
| @@ -299,7 +299,7 @@ func TestResizeGrowthFirstFrameIncludesConcurrentPTYRedraw(t *testing.T) { | |||||
| 299 | 299 | ||||
| 300 | d.sessWg.Add(1) | 300 | d.sessWg.Add(1) | ||
| 301 | pane.onExit = func() {} | 301 | pane.onExit = func() {} | ||
| 302 | - | go d.ptyReader(sess, sess.activeTab(), pane) | 302 | + | go d.ptyReader(sess, testAttachmentTab(sess), pane) |
| 303 | defer func() { p.close(); d.sessWg.Wait() }() | 303 | defer func() { p.close(); d.sessWg.Wait() }() | ||
| 304 | 304 | ||||
| 305 | // The resize deadline drives coordinator prepare/apply/commit. No test | 305 | // The resize deadline drives coordinator prepare/apply/commit. No test | ||
| @@ -387,7 +387,7 @@ func TestResizeBurstFlushesOnlyLatestGeometry(t *testing.T) { | |||||
| 387 | awaitTestCompletion(t, latestDone, "latest resize callback did not complete") | 387 | awaitTestCompletion(t, latestDone, "latest resize callback did not complete") | ||
| 388 | awaitFrame(t, sends, ports.MsgOutput) | 388 | awaitFrame(t, sends, ports.MsgOutput) | ||
| 389 | require.Equal(t, domain.Size{Cols: 120, Rows: 24}, ac.size) | 389 | require.Equal(t, domain.Size{Cols: 120, Rows: 24}, ac.size) | ||
| 390 | - | require.Equal(t, 120, sess.activeTab().focusedPane().screen.Frame.Width) | 390 | + | require.Equal(t, 120, testAttachmentTab(sess).focusedPane().screen.Frame.Width) |
| 391 | requireNoOutputFrame(t, sends) | 391 | requireNoOutputFrame(t, sends) | ||
| 392 | } | 392 | } | ||
| 393 | 393 | ||||
M internal/usecase/daemon/palette.go +4 -1
| @@ -672,7 +672,10 @@ func (e paletteExec) RenameSession() error { | |||
| 672 | 672 | } | |
| 673 | 673 | ||
| 674 | 674 | func (e paletteExec) RenameTab() error { | |
| 675 | - | tb := e.sess.tabForAttachmentOrActive(e.ac) | |
| 675 | + | tb := e.sess.tabForAttachment(e.ac) | |
| 676 | + | if e.ac == nil { | |
| 677 | + | tb = e.sess.firstTab() | |
| 678 | + | } | |
| 676 | 679 | if tb == nil { | |
| 677 | 680 | return nil | |
| 678 | 681 | } | |
| @@ -672,7 +672,10 @@ func (e paletteExec) RenameSession() error { | |||||
| 672 | } | 672 | } | ||
| 673 | 673 | ||||
| 674 | func (e paletteExec) RenameTab() error { | 674 | func (e paletteExec) RenameTab() error { | ||
| 675 | - | tb := e.sess.tabForAttachmentOrActive(e.ac) | 675 | + | tb := e.sess.tabForAttachment(e.ac) |
| 676 | + | if e.ac == nil { | |||
| 677 | + | tb = e.sess.firstTab() | |||
| 678 | + | } | |||
| 676 | if tb == nil { | 679 | if tb == nil { | ||
| 677 | return nil | 680 | return nil | ||
| 678 | } | 681 | } | ||
M internal/usecase/daemon/palette_test.go +6 -6
| @@ -109,8 +109,8 @@ func TestPaletteOpenTypeEnterRunAndEscClose(t *testing.T) { | |||
| 109 | 109 | ||
| 110 | 110 | d.handleInput(sess, ac, []byte("NXT\r")) | |
| 111 | 111 | require.False(t, ac.overlays.paletteActive()) | |
| 112 | - | require.Equal(t, 1, activeTabIndex(sess)) | |
| 113 | - | requireFloatingInitialized(t, sess.activeTab()) | |
| 112 | + | require.Equal(t, 1, testAttachmentTabIndex(sess)) | |
| 113 | + | requireFloatingInitialized(t, testAttachmentTab(sess)) | |
| 114 | 114 | awaitFrame(t, sends, ports.MsgOutput) | |
| 115 | 115 | ||
| 116 | 116 | d.handleInput(sess, ac, []byte("\x1b ")) | |
| @@ -440,7 +440,7 @@ func TestPaletteFuzzySelectedStaticCommandExecutes(t *testing.T) { | |||
| 440 | 440 | d.handleInput(sess, ac, []byte("next\r")) | |
| 441 | 441 | ||
| 442 | 442 | require.False(t, ac.overlays.paletteActive()) | |
| 443 | - | require.Equal(t, 1, activeTabIndex(sess)) | |
| 443 | + | require.Equal(t, 1, testAttachmentTabIndex(sess)) | |
| 444 | 444 | awaitFrame(t, sends, ports.MsgOutput) | |
| 445 | 445 | } | |
| 446 | 446 | ||
| @@ -639,7 +639,7 @@ func TestPaletteFLTExecutesFloatingToggle(t *testing.T) { | |||
| 639 | 639 | p, release := newBlockingPTY(t) | |
| 640 | 640 | defer release() | |
| 641 | 641 | d, sess, ac, sends := newManualSessionWithPTYs(t, p) | |
| 642 | - | tb := sess.activeTab() | |
| 642 | + | tb := testAttachmentTab(sess) | |
| 643 | 643 | installTestFloating(tb, newPane("floating", nil, domain.Size{Cols: 20, Rows: 5}), false) | |
| 644 | 644 | ||
| 645 | 645 | d.handleInput(sess, ac, []byte("\x1b ")) | |
| @@ -1125,9 +1125,9 @@ func TestPaletteExecMethods(t *testing.T) { | |||
| 1125 | 1125 | exec := paletteExec{d: d, sess: sess, ac: ac} | |
| 1126 | 1126 | ||
| 1127 | 1127 | require.NoError(t, exec.NextTab()) | |
| 1128 | - | require.Equal(t, 1, activeTabIndex(sess)) | |
| 1128 | + | require.Equal(t, 1, testAttachmentTabIndex(sess)) | |
| 1129 | 1129 | require.NoError(t, exec.PrevTab()) | |
| 1130 | - | require.Equal(t, 0, activeTabIndex(sess)) | |
| 1130 | + | require.Equal(t, 0, testAttachmentTabIndex(sess)) | |
| 1131 | 1131 | sess.ephemeral = true | |
| 1132 | 1132 | require.NoError(t, exec.RenameSession()) | |
| 1133 | 1133 | require.True(t, ac.overlays.promptActive()) | |
| @@ -109,8 +109,8 @@ func TestPaletteOpenTypeEnterRunAndEscClose(t *testing.T) { | |||||
| 109 | 109 | ||||
| 110 | d.handleInput(sess, ac, []byte("NXT\r")) | 110 | d.handleInput(sess, ac, []byte("NXT\r")) | ||
| 111 | require.False(t, ac.overlays.paletteActive()) | 111 | require.False(t, ac.overlays.paletteActive()) | ||
| 112 | - | require.Equal(t, 1, activeTabIndex(sess)) | 112 | + | require.Equal(t, 1, testAttachmentTabIndex(sess)) |
| 113 | - | requireFloatingInitialized(t, sess.activeTab()) | 113 | + | requireFloatingInitialized(t, testAttachmentTab(sess)) |
| 114 | awaitFrame(t, sends, ports.MsgOutput) | 114 | awaitFrame(t, sends, ports.MsgOutput) | ||
| 115 | 115 | ||||
| 116 | d.handleInput(sess, ac, []byte("\x1b ")) | 116 | d.handleInput(sess, ac, []byte("\x1b ")) | ||
| @@ -440,7 +440,7 @@ func TestPaletteFuzzySelectedStaticCommandExecutes(t *testing.T) { | |||||
| 440 | d.handleInput(sess, ac, []byte("next\r")) | 440 | d.handleInput(sess, ac, []byte("next\r")) | ||
| 441 | 441 | ||||
| 442 | require.False(t, ac.overlays.paletteActive()) | 442 | require.False(t, ac.overlays.paletteActive()) | ||
| 443 | - | require.Equal(t, 1, activeTabIndex(sess)) | 443 | + | require.Equal(t, 1, testAttachmentTabIndex(sess)) |
| 444 | awaitFrame(t, sends, ports.MsgOutput) | 444 | awaitFrame(t, sends, ports.MsgOutput) | ||
| 445 | } | 445 | } | ||
| 446 | 446 | ||||
| @@ -639,7 +639,7 @@ func TestPaletteFLTExecutesFloatingToggle(t *testing.T) { | |||||
| 639 | p, release := newBlockingPTY(t) | 639 | p, release := newBlockingPTY(t) | ||
| 640 | defer release() | 640 | defer release() | ||
| 641 | d, sess, ac, sends := newManualSessionWithPTYs(t, p) | 641 | d, sess, ac, sends := newManualSessionWithPTYs(t, p) | ||
| 642 | - | tb := sess.activeTab() | 642 | + | tb := testAttachmentTab(sess) |
| 643 | installTestFloating(tb, newPane("floating", nil, domain.Size{Cols: 20, Rows: 5}), false) | 643 | installTestFloating(tb, newPane("floating", nil, domain.Size{Cols: 20, Rows: 5}), false) | ||
| 644 | 644 | ||||
| 645 | d.handleInput(sess, ac, []byte("\x1b ")) | 645 | d.handleInput(sess, ac, []byte("\x1b ")) | ||
| @@ -1125,9 +1125,9 @@ func TestPaletteExecMethods(t *testing.T) { | |||||
| 1125 | exec := paletteExec{d: d, sess: sess, ac: ac} | 1125 | exec := paletteExec{d: d, sess: sess, ac: ac} | ||
| 1126 | 1126 | ||||
| 1127 | require.NoError(t, exec.NextTab()) | 1127 | require.NoError(t, exec.NextTab()) | ||
| 1128 | - | require.Equal(t, 1, activeTabIndex(sess)) | 1128 | + | require.Equal(t, 1, testAttachmentTabIndex(sess)) |
| 1129 | require.NoError(t, exec.PrevTab()) | 1129 | require.NoError(t, exec.PrevTab()) | ||
| 1130 | - | require.Equal(t, 0, activeTabIndex(sess)) | 1130 | + | require.Equal(t, 0, testAttachmentTabIndex(sess)) |
| 1131 | sess.ephemeral = true | 1131 | sess.ephemeral = true | ||
| 1132 | require.NoError(t, exec.RenameSession()) | 1132 | require.NoError(t, exec.RenameSession()) | ||
| 1133 | require.True(t, ac.overlays.promptActive()) | 1133 | require.True(t, ac.overlays.promptActive()) | ||
M internal/usecase/daemon/pane_actions_test.go +24 -24
| @@ -78,7 +78,7 @@ func TestLayoutRetryIsBoundedDeduplicatedAndCanceled(t *testing.T) { | |||
| 78 | 78 | d, sess, _, _ := newManualSessionWithPTYs(t, pty) | |
| 79 | 79 | clock := &layoutRetryClock{timers: make(chan *layoutRetryTimer, 16)} | |
| 80 | 80 | d.clock = clock | |
| 81 | - | tb := sess.activeTab() | |
| 81 | + | tb := testAttachmentTab(sess) | |
| 82 | 82 | tb.mu.Lock() | |
| 83 | 83 | tb.size = domain.Size{Cols: 100, Rows: 20} | |
| 84 | 84 | tb.bumpLayoutGenerationLocked() | |
| @@ -118,7 +118,7 @@ func TestLayoutRetryIsBoundedDeduplicatedAndCanceled(t *testing.T) { | |||
| 118 | 118 | d, sess, _, _ := newManualSessionWithPTYs(t, pty) | |
| 119 | 119 | clock := &layoutRetryClock{timers: make(chan *layoutRetryTimer, 16)} | |
| 120 | 120 | d.clock = clock | |
| 121 | - | tb := sess.activeTab() | |
| 121 | + | tb := testAttachmentTab(sess) | |
| 122 | 122 | tb.mu.Lock() | |
| 123 | 123 | tb.size = domain.Size{Cols: 100, Rows: 20} | |
| 124 | 124 | tb.bumpLayoutGenerationLocked() | |
| @@ -145,7 +145,7 @@ func TestLayoutRetryIsBoundedDeduplicatedAndCanceled(t *testing.T) { | |||
| 145 | 145 | func TestFocusDirAtDoesNotApplyUncommittedCandidate(t *testing.T) { | |
| 146 | 146 | pty := &transactionalResizePTY{} | |
| 147 | 147 | d, sess, _, _ := newManualSessionWithPTYs(t, pty) | |
| 148 | - | tb := sess.activeTab() | |
| 148 | + | tb := testAttachmentTab(sess) | |
| 149 | 149 | second := newPane("pane-2", nil, domain.Size{Cols: 80, Rows: 23}) | |
| 150 | 150 | tb.mu.Lock() | |
| 151 | 151 | tb.panes[second.id] = second | |
| @@ -164,7 +164,7 @@ func TestLayoutApplicationRetriesOnlyAcceptedFailedGeometry(t *testing.T) { | |||
| 164 | 164 | d, sess, _, _ := newManualSessionWithPTYs(t, pty) | |
| 165 | 165 | clock := &layoutRetryClock{timers: make(chan *layoutRetryTimer, 16)} | |
| 166 | 166 | d.clock = clock | |
| 167 | - | tb, p := sess.activeTab(), sess.activeTab().focusedPane() | |
| 167 | + | tb, p := testAttachmentTab(sess), testAttachmentTab(sess).focusedPane() | |
| 168 | 168 | tb.mu.Lock() | |
| 169 | 169 | tb.size = domain.Size{Cols: 100, Rows: 20} | |
| 170 | 170 | tb.bumpLayoutGenerationLocked() | |
| @@ -206,7 +206,7 @@ func TestDelayedRetryUsesFreshLayoutAfterPaneResize(t *testing.T) { | |||
| 206 | 206 | failed := &transactionalResizePTY{errs: []error{errors.New("first resize fails")}} | |
| 207 | 207 | d, sess, ac, _ := newManualSessionWithPTYs(t, failed) | |
| 208 | 208 | d.clock = clock.clock | |
| 209 | - | tb := sess.activeTab() | |
| 209 | + | tb := testAttachmentTab(sess) | |
| 210 | 210 | first := tb.focusedPane() | |
| 211 | 211 | second := newPane("pane-2", nil, domain.Size{Cols: 80, Rows: 23}) | |
| 212 | 212 | tb.mu.Lock() | |
| @@ -260,7 +260,7 @@ func TestDelayedRetryUsesFreshLayoutAfterPaneResize(t *testing.T) { | |||
| 260 | 260 | ||
| 261 | 261 | func TestLayoutApplicationDoesNotHoldTabLock(t *testing.T) { | |
| 262 | 262 | d, sess, pty, _ := newSplitTestDaemon(t, domain.Size{Cols: 41, Rows: 10}) | |
| 263 | - | tb := sess.activeTab() | |
| 263 | + | tb := testAttachmentTab(sess) | |
| 264 | 264 | tb.focusedPane().rect = domain.Rect{Width: 20, Height: 10} | |
| 265 | 265 | entered := make(chan struct{}) | |
| 266 | 266 | release := make(chan struct{}) | |
| @@ -290,7 +290,7 @@ func TestLayoutApplicationDoesNotHoldTabLock(t *testing.T) { | |||
| 290 | 290 | ||
| 291 | 291 | func TestLayoutApplicationRejectsStalePaneIdentity(t *testing.T) { | |
| 292 | 292 | d, sess, oldPTY, _ := newSplitTestDaemon(t, domain.Size{Cols: 41, Rows: 10}) | |
| 293 | - | tb := sess.activeTab() | |
| 293 | + | tb := testAttachmentTab(sess) | |
| 294 | 294 | oldPane := tb.focusedPane() | |
| 295 | 295 | oldPane.rect = domain.Rect{Width: 20, Height: 10} | |
| 296 | 296 | oldPane.screen.Resize(20, 10) | |
| @@ -341,7 +341,7 @@ func TestLayoutApplicationRejectsStaleCloseFocusAndSize(t *testing.T) { | |||
| 341 | 341 | first, entered, release := newBlockedPTY() | |
| 342 | 342 | second := &transactionalResizePTY{} | |
| 343 | 343 | d, sess, _, _ := newManualSessionWithPTYs(t, first) | |
| 344 | - | tb := sess.activeTab() | |
| 344 | + | tb := testAttachmentTab(sess) | |
| 345 | 345 | p1 := tb.focusedPane() | |
| 346 | 346 | p2 := newPane("pane-2", second, domain.Size{Cols: 80, Rows: 23}) | |
| 347 | 347 | tb.mu.Lock() | |
| @@ -383,7 +383,7 @@ func TestLayoutApplicationRejectsStaleCloseFocusAndSize(t *testing.T) { | |||
| 383 | 383 | first, entered, release := newBlockedPTY() | |
| 384 | 384 | second := &transactionalResizePTY{} | |
| 385 | 385 | d, sess, _, _ := newManualSessionWithPTYs(t, first) | |
| 386 | - | tb := sess.activeTab() | |
| 386 | + | tb := testAttachmentTab(sess) | |
| 387 | 387 | p1 := tb.focusedPane() | |
| 388 | 388 | p2 := newPane("pane-2", second, domain.Size{Cols: 80, Rows: 23}) | |
| 389 | 389 | tb.mu.Lock() | |
| @@ -417,7 +417,7 @@ func TestLayoutApplicationRejectsStaleCloseFocusAndSize(t *testing.T) { | |||
| 417 | 417 | t.Run("client size rejects and retries a blocked plan", func(t *testing.T) { | |
| 418 | 418 | pty, entered, release := newBlockedPTY() | |
| 419 | 419 | d, sess, _, _ := newManualSessionWithPTYs(t, pty) | |
| 420 | - | tb := sess.activeTab() | |
| 420 | + | tb := testAttachmentTab(sess) | |
| 421 | 421 | p := tb.focusedPane() | |
| 422 | 422 | p.rect = domain.Rect{Width: 20, Height: 10} | |
| 423 | 423 | ||
| @@ -476,7 +476,7 @@ func TestSplitPaneCreatesFocusedShellInRequestedPosition(t *testing.T) { | |||
| 476 | 476 | ||
| 477 | 477 | require.NoError(t, d.splitPane(sess, nil, tt.dir)) | |
| 478 | 478 | ||
| 479 | - | tb := sess.activeTab() | |
| 479 | + | tb := testAttachmentTab(sess) | |
| 480 | 480 | tb.mu.Lock() | |
| 481 | 481 | defer tb.mu.Unlock() | |
| 482 | 482 | require.Equal(t, layout.PaneID("pane-2"), tb.tree.Focus) | |
| @@ -492,7 +492,7 @@ func TestSplitPaneCreatesFocusedShellInRequestedPosition(t *testing.T) { | |||
| 492 | 492 | ||
| 493 | 493 | func TestSplitPaneRightFromStackSplitsWholeStack(t *testing.T) { | |
| 494 | 494 | d, sess, _, factory := newSplitTestDaemon(t, domain.Size{Cols: 41, Rows: 4}) | |
| 495 | - | tb := sess.activeTab() | |
| 495 | + | tb := testAttachmentTab(sess) | |
| 496 | 496 | stackPTY := portsmocks.NewMockPTY(t) | |
| 497 | 497 | stackPane := newPane("pane-2", stackPTY, domain.Size{Cols: 41, Rows: 2}) | |
| 498 | 498 | tb.panes[stackPane.id] = stackPane | |
| @@ -520,7 +520,7 @@ func TestSplitPaneOpenErrorRollsBackTreeAndPaneMap(t *testing.T) { | |||
| 520 | 520 | cause := errors.New("open failed") | |
| 521 | 521 | factory.EXPECT().Open(mock.Anything, "/bin/sh", []string(nil), mock.Anything, "/work", domain.Size{Cols: 20, Rows: 10}).Return(nil, cause).Once() | |
| 522 | 522 | ||
| 523 | - | tb := sess.activeTab() | |
| 523 | + | tb := testAttachmentTab(sess) | |
| 524 | 524 | tb.mu.Lock() | |
| 525 | 525 | beforeRoot, beforeFocus, beforeNext, beforePaneCount := tb.tree.Root.Leaf, tb.tree.Focus, tb.nextPaneID, len(tb.panes) | |
| 526 | 526 | tb.mu.Unlock() | |
| @@ -547,7 +547,7 @@ func TestSplitPaneOpenErrorRollsBackTreeAndPaneMap(t *testing.T) { | |||
| 547 | 547 | ||
| 548 | 548 | func TestApplyLayoutResizesPTYsAndScreens(t *testing.T) { | |
| 549 | 549 | d, sess, oldPTY, _ := newSplitTestDaemon(t, domain.Size{Cols: 41, Rows: 10}) | |
| 550 | - | tb := sess.activeTab() | |
| 550 | + | tb := testAttachmentTab(sess) | |
| 551 | 551 | newPTY := portsmocks.NewMockPTY(t) | |
| 552 | 552 | newPane := newPane("pane-2", newPTY, domain.Size{Cols: 41, Rows: 10}) | |
| 553 | 553 | tb.tree = &layout.Tree{Root: &layout.Node{Kind: layout.Split, Dir: layout.Horizontal, Children: []*layout.Node{layout.NewLeaf("pane-1"), layout.NewLeaf("pane-2")}}, Focus: "pane-2"} | |
| @@ -564,7 +564,7 @@ func TestApplyLayoutResizesPTYsAndScreens(t *testing.T) { | |||
| 564 | 564 | ||
| 565 | 565 | func TestResizeReflowsAllPanes(t *testing.T) { | |
| 566 | 566 | d, sess, oldPTY, _ := newSplitTestDaemon(t, domain.Size{Cols: 41, Rows: 10}) | |
| 567 | - | tb := sess.activeTab() | |
| 567 | + | tb := testAttachmentTab(sess) | |
| 568 | 568 | newPTY := portsmocks.NewMockPTY(t) | |
| 569 | 569 | tb.tree = &layout.Tree{Root: &layout.Node{Kind: layout.Split, Dir: layout.Horizontal, Children: []*layout.Node{layout.NewLeaf("pane-1"), layout.NewLeaf("pane-2")}}, Focus: "pane-2"} | |
| 570 | 570 | tb.panes["pane-2"] = newPane("pane-2", newPTY, domain.Size{Cols: 20, Rows: 10}) | |
| @@ -596,7 +596,7 @@ func TestFocusDirAtReturnsDepartingSpanAndMapsNoNeighbor(t *testing.T) { | |||
| 596 | 596 | for _, tt := range tests { | |
| 597 | 597 | t.Run(tt.name, func(t *testing.T) { | |
| 598 | 598 | d, sess, _, _ := newSplitTestDaemon(t, domain.Size{Cols: 41, Rows: 10}) | |
| 599 | - | tb := sess.activeTab() | |
| 599 | + | tb := testAttachmentTab(sess) | |
| 600 | 600 | target := tb.panes["pane-1"] | |
| 601 | 601 | tb.tree = &layout.Tree{Root: &layout.Node{Kind: layout.Split, Dir: layout.Horizontal, Children: []*layout.Node{layout.NewLeaf("pane-1"), layout.NewLeaf("pane-2")}}, Focus: "pane-1"} | |
| 602 | 602 | tb.panes["pane-1"].rect = domain.Rect{Width: 20, Height: 10} | |
| @@ -614,7 +614,7 @@ func TestFocusDirAtReturnsDepartingSpanAndMapsNoNeighbor(t *testing.T) { | |||
| 614 | 614 | ||
| 615 | 615 | func TestFocusDirMovesFocusAndExitsCopyMode(t *testing.T) { | |
| 616 | 616 | d, sess, oldPTY, _ := newSplitTestDaemon(t, domain.Size{Cols: 41, Rows: 10}) | |
| 617 | - | tb := sess.activeTab() | |
| 617 | + | tb := testAttachmentTab(sess) | |
| 618 | 618 | newPTY := portsmocks.NewMockPTY(t) | |
| 619 | 619 | tb.tree = &layout.Tree{Root: &layout.Node{Kind: layout.Split, Dir: layout.Horizontal, Children: []*layout.Node{layout.NewLeaf("pane-1"), layout.NewLeaf("pane-2")}}, Focus: "pane-1"} | |
| 620 | 620 | tb.panes["pane-2"] = newPane("pane-2", newPTY, domain.Size{Cols: 20, Rows: 10}) | |
| @@ -635,7 +635,7 @@ func TestFocusDirMovesFocusAndExitsCopyMode(t *testing.T) { | |||
| 635 | 635 | ||
| 636 | 636 | func TestCloseFocusedPaneRemovesPaneReflowsAndIsIdempotent(t *testing.T) { | |
| 637 | 637 | d, sess, oldPTY, _ := newSplitTestDaemon(t, domain.Size{Cols: 41, Rows: 10}) | |
| 638 | - | tb := sess.activeTab() | |
| 638 | + | tb := testAttachmentTab(sess) | |
| 639 | 639 | closingPTY := portsmocks.NewMockPTY(t) | |
| 640 | 640 | tb.tree = &layout.Tree{Root: &layout.Node{Kind: layout.Split, Dir: layout.Horizontal, Children: []*layout.Node{layout.NewLeaf("pane-1"), layout.NewLeaf("pane-2")}}, Focus: "pane-2"} | |
| 641 | 641 | tb.panes["pane-2"] = newPane("pane-2", closingPTY, domain.Size{Cols: 20, Rows: 10}) | |
| @@ -654,7 +654,7 @@ func TestCloseFocusedPaneRemovesPaneReflowsAndIsIdempotent(t *testing.T) { | |||
| 654 | 654 | ||
| 655 | 655 | func TestReapPaneSharesClosePathAndIsIdempotent(t *testing.T) { | |
| 656 | 656 | d, sess, oldPTY, _ := newSplitTestDaemon(t, domain.Size{Cols: 41, Rows: 10}) | |
| 657 | - | tb := sess.activeTab() | |
| 657 | + | tb := testAttachmentTab(sess) | |
| 658 | 658 | reapedPTY := portsmocks.NewMockPTY(t) | |
| 659 | 659 | reaped := newPane("pane-2", reapedPTY, domain.Size{Cols: 20, Rows: 10}) | |
| 660 | 660 | tb.tree = &layout.Tree{Root: &layout.Node{Kind: layout.Split, Dir: layout.Horizontal, Children: []*layout.Node{layout.NewLeaf("pane-1"), layout.NewLeaf("pane-2")}}, Focus: "pane-2"} | |
| @@ -673,7 +673,7 @@ func TestReapPaneSharesClosePathAndIsIdempotent(t *testing.T) { | |||
| 673 | 673 | ||
| 674 | 674 | func TestClosePaneKeepsTabOpenWhenPendingSpawnLeafExists(t *testing.T) { | |
| 675 | 675 | d, sess, oldPTY, _ := newSplitTestDaemon(t, domain.Size{Cols: 41, Rows: 10}) | |
| 676 | - | tb := sess.activeTab() | |
| 676 | + | tb := testAttachmentTab(sess) | |
| 677 | 677 | tb.tree = &layout.Tree{Root: &layout.Node{Kind: layout.Split, Dir: layout.Horizontal, Children: []*layout.Node{layout.NewLeaf("pane-1"), layout.NewLeaf("pane-2")}}, Focus: "pane-2"} | |
| 678 | 678 | oldPTY.EXPECT().Close().Return(nil).Once() | |
| 679 | 679 | ||
| @@ -705,7 +705,7 @@ func TestStackPaneCreatesStackAndToggleRestoresSplit(t *testing.T) { | |||
| 705 | 705 | factory.EXPECT().Open(mock.Anything, "/bin/sh", []string(nil), mock.Anything, "/work", domain.Size{Cols: 20, Rows: 4}).Return(newPTY, nil).Once() | |
| 706 | 706 | ||
| 707 | 707 | require.NoError(t, d.stackPane(sess, nil)) | |
| 708 | - | tb := sess.activeTab() | |
| 708 | + | tb := testAttachmentTab(sess) | |
| 709 | 709 | require.Equal(t, layout.Stack, tb.tree.Root.Kind) | |
| 710 | 710 | require.Equal(t, layout.PaneID("pane-2"), tb.tree.Focus) | |
| 711 | 711 | require.Equal(t, layout.PaneID("pane-2"), tb.tree.Root.Expanded) | |
| @@ -728,7 +728,7 @@ func TestStackFocusWalkExpandsAndOverflowRefuses(t *testing.T) { | |||
| 728 | 728 | ||
| 729 | 729 | require.NoError(t, d.focusDir(sess, nil, layout.Up, nil)) | |
| 730 | 730 | ||
| 731 | - | tb := sess.activeTab() | |
| 731 | + | tb := testAttachmentTab(sess) | |
| 732 | 732 | require.Equal(t, layout.PaneID("pane-1"), tb.tree.Focus) | |
| 733 | 733 | require.Equal(t, layout.PaneID("pane-1"), tb.tree.Root.Expanded) | |
| 734 | 734 | thirdPTY := portsmocks.NewMockPTY(t) | |
| @@ -773,7 +773,7 @@ func TestCloseOriginalPaneLeavesSurvivorFunctional(t *testing.T) { | |||
| 773 | 773 | factory.EXPECT().Open(mock.Anything, "/bin/sh", []string(nil), mock.Anything, "/work", domain.Size{Cols: 20, Rows: 10}).Return(newPTY, nil).Once() | |
| 774 | 774 | ||
| 775 | 775 | require.NoError(t, d.splitPane(sess, nil, layout.Right)) | |
| 776 | - | tb := sess.activeTab() | |
| 776 | + | tb := testAttachmentTab(sess) | |
| 777 | 777 | tb.mu.Lock() | |
| 778 | 778 | tb.tree.Focus = "pane-1" | |
| 779 | 779 | tb.mu.Unlock() | |
| @@ -844,7 +844,7 @@ func TestSplitPaneUsesExactAuthoritativeEnvironment(t *testing.T) { | |||
| 844 | 844 | ).Once() | |
| 845 | 845 | ||
| 846 | 846 | require.NoError(t, d.splitPane(sess, nil, layout.Right)) | |
| 847 | - | tb := sess.activeTab() | |
| 847 | + | tb := testAttachmentTab(sess) | |
| 848 | 848 | tb.mu.Lock() | |
| 849 | 849 | paneID := tb.panes["pane-2"].stableID | |
| 850 | 850 | tabID := tb.stableID | |
| @@ -78,7 +78,7 @@ func TestLayoutRetryIsBoundedDeduplicatedAndCanceled(t *testing.T) { | |||||
| 78 | d, sess, _, _ := newManualSessionWithPTYs(t, pty) | 78 | d, sess, _, _ := newManualSessionWithPTYs(t, pty) | ||
| 79 | clock := &layoutRetryClock{timers: make(chan *layoutRetryTimer, 16)} | 79 | clock := &layoutRetryClock{timers: make(chan *layoutRetryTimer, 16)} | ||
| 80 | d.clock = clock | 80 | d.clock = clock | ||
| 81 | - | tb := sess.activeTab() | 81 | + | tb := testAttachmentTab(sess) |
| 82 | tb.mu.Lock() | 82 | tb.mu.Lock() | ||
| 83 | tb.size = domain.Size{Cols: 100, Rows: 20} | 83 | tb.size = domain.Size{Cols: 100, Rows: 20} | ||
| 84 | tb.bumpLayoutGenerationLocked() | 84 | tb.bumpLayoutGenerationLocked() | ||
| @@ -118,7 +118,7 @@ func TestLayoutRetryIsBoundedDeduplicatedAndCanceled(t *testing.T) { | |||||
| 118 | d, sess, _, _ := newManualSessionWithPTYs(t, pty) | 118 | d, sess, _, _ := newManualSessionWithPTYs(t, pty) | ||
| 119 | clock := &layoutRetryClock{timers: make(chan *layoutRetryTimer, 16)} | 119 | clock := &layoutRetryClock{timers: make(chan *layoutRetryTimer, 16)} | ||
| 120 | d.clock = clock | 120 | d.clock = clock | ||
| 121 | - | tb := sess.activeTab() | 121 | + | tb := testAttachmentTab(sess) |
| 122 | tb.mu.Lock() | 122 | tb.mu.Lock() | ||
| 123 | tb.size = domain.Size{Cols: 100, Rows: 20} | 123 | tb.size = domain.Size{Cols: 100, Rows: 20} | ||
| 124 | tb.bumpLayoutGenerationLocked() | 124 | tb.bumpLayoutGenerationLocked() | ||
| @@ -145,7 +145,7 @@ func TestLayoutRetryIsBoundedDeduplicatedAndCanceled(t *testing.T) { | |||||
| 145 | func TestFocusDirAtDoesNotApplyUncommittedCandidate(t *testing.T) { | 145 | func TestFocusDirAtDoesNotApplyUncommittedCandidate(t *testing.T) { | ||
| 146 | pty := &transactionalResizePTY{} | 146 | pty := &transactionalResizePTY{} | ||
| 147 | d, sess, _, _ := newManualSessionWithPTYs(t, pty) | 147 | d, sess, _, _ := newManualSessionWithPTYs(t, pty) | ||
| 148 | - | tb := sess.activeTab() | 148 | + | tb := testAttachmentTab(sess) |
| 149 | second := newPane("pane-2", nil, domain.Size{Cols: 80, Rows: 23}) | 149 | second := newPane("pane-2", nil, domain.Size{Cols: 80, Rows: 23}) | ||
| 150 | tb.mu.Lock() | 150 | tb.mu.Lock() | ||
| 151 | tb.panes[second.id] = second | 151 | tb.panes[second.id] = second | ||
| @@ -164,7 +164,7 @@ func TestLayoutApplicationRetriesOnlyAcceptedFailedGeometry(t *testing.T) { | |||||
| 164 | d, sess, _, _ := newManualSessionWithPTYs(t, pty) | 164 | d, sess, _, _ := newManualSessionWithPTYs(t, pty) | ||
| 165 | clock := &layoutRetryClock{timers: make(chan *layoutRetryTimer, 16)} | 165 | clock := &layoutRetryClock{timers: make(chan *layoutRetryTimer, 16)} | ||
| 166 | d.clock = clock | 166 | d.clock = clock | ||
| 167 | - | tb, p := sess.activeTab(), sess.activeTab().focusedPane() | 167 | + | tb, p := testAttachmentTab(sess), testAttachmentTab(sess).focusedPane() |
| 168 | tb.mu.Lock() | 168 | tb.mu.Lock() | ||
| 169 | tb.size = domain.Size{Cols: 100, Rows: 20} | 169 | tb.size = domain.Size{Cols: 100, Rows: 20} | ||
| 170 | tb.bumpLayoutGenerationLocked() | 170 | tb.bumpLayoutGenerationLocked() | ||
| @@ -206,7 +206,7 @@ func TestDelayedRetryUsesFreshLayoutAfterPaneResize(t *testing.T) { | |||||
| 206 | failed := &transactionalResizePTY{errs: []error{errors.New("first resize fails")}} | 206 | failed := &transactionalResizePTY{errs: []error{errors.New("first resize fails")}} | ||
| 207 | d, sess, ac, _ := newManualSessionWithPTYs(t, failed) | 207 | d, sess, ac, _ := newManualSessionWithPTYs(t, failed) | ||
| 208 | d.clock = clock.clock | 208 | d.clock = clock.clock | ||
| 209 | - | tb := sess.activeTab() | 209 | + | tb := testAttachmentTab(sess) |
| 210 | first := tb.focusedPane() | 210 | first := tb.focusedPane() | ||
| 211 | second := newPane("pane-2", nil, domain.Size{Cols: 80, Rows: 23}) | 211 | second := newPane("pane-2", nil, domain.Size{Cols: 80, Rows: 23}) | ||
| 212 | tb.mu.Lock() | 212 | tb.mu.Lock() | ||
| @@ -260,7 +260,7 @@ func TestDelayedRetryUsesFreshLayoutAfterPaneResize(t *testing.T) { | |||||
| 260 | 260 | ||||
| 261 | func TestLayoutApplicationDoesNotHoldTabLock(t *testing.T) { | 261 | func TestLayoutApplicationDoesNotHoldTabLock(t *testing.T) { | ||
| 262 | d, sess, pty, _ := newSplitTestDaemon(t, domain.Size{Cols: 41, Rows: 10}) | 262 | d, sess, pty, _ := newSplitTestDaemon(t, domain.Size{Cols: 41, Rows: 10}) | ||
| 263 | - | tb := sess.activeTab() | 263 | + | tb := testAttachmentTab(sess) |
| 264 | tb.focusedPane().rect = domain.Rect{Width: 20, Height: 10} | 264 | tb.focusedPane().rect = domain.Rect{Width: 20, Height: 10} | ||
| 265 | entered := make(chan struct{}) | 265 | entered := make(chan struct{}) | ||
| 266 | release := make(chan struct{}) | 266 | release := make(chan struct{}) | ||
| @@ -290,7 +290,7 @@ func TestLayoutApplicationDoesNotHoldTabLock(t *testing.T) { | |||||
| 290 | 290 | ||||
| 291 | func TestLayoutApplicationRejectsStalePaneIdentity(t *testing.T) { | 291 | func TestLayoutApplicationRejectsStalePaneIdentity(t *testing.T) { | ||
| 292 | d, sess, oldPTY, _ := newSplitTestDaemon(t, domain.Size{Cols: 41, Rows: 10}) | 292 | d, sess, oldPTY, _ := newSplitTestDaemon(t, domain.Size{Cols: 41, Rows: 10}) | ||
| 293 | - | tb := sess.activeTab() | 293 | + | tb := testAttachmentTab(sess) |
| 294 | oldPane := tb.focusedPane() | 294 | oldPane := tb.focusedPane() | ||
| 295 | oldPane.rect = domain.Rect{Width: 20, Height: 10} | 295 | oldPane.rect = domain.Rect{Width: 20, Height: 10} | ||
| 296 | oldPane.screen.Resize(20, 10) | 296 | oldPane.screen.Resize(20, 10) | ||
| @@ -341,7 +341,7 @@ func TestLayoutApplicationRejectsStaleCloseFocusAndSize(t *testing.T) { | |||||
| 341 | first, entered, release := newBlockedPTY() | 341 | first, entered, release := newBlockedPTY() | ||
| 342 | second := &transactionalResizePTY{} | 342 | second := &transactionalResizePTY{} | ||
| 343 | d, sess, _, _ := newManualSessionWithPTYs(t, first) | 343 | d, sess, _, _ := newManualSessionWithPTYs(t, first) | ||
| 344 | - | tb := sess.activeTab() | 344 | + | tb := testAttachmentTab(sess) |
| 345 | p1 := tb.focusedPane() | 345 | p1 := tb.focusedPane() | ||
| 346 | p2 := newPane("pane-2", second, domain.Size{Cols: 80, Rows: 23}) | 346 | p2 := newPane("pane-2", second, domain.Size{Cols: 80, Rows: 23}) | ||
| 347 | tb.mu.Lock() | 347 | tb.mu.Lock() | ||
| @@ -383,7 +383,7 @@ func TestLayoutApplicationRejectsStaleCloseFocusAndSize(t *testing.T) { | |||||
| 383 | first, entered, release := newBlockedPTY() | 383 | first, entered, release := newBlockedPTY() | ||
| 384 | second := &transactionalResizePTY{} | 384 | second := &transactionalResizePTY{} | ||
| 385 | d, sess, _, _ := newManualSessionWithPTYs(t, first) | 385 | d, sess, _, _ := newManualSessionWithPTYs(t, first) | ||
| 386 | - | tb := sess.activeTab() | 386 | + | tb := testAttachmentTab(sess) |
| 387 | p1 := tb.focusedPane() | 387 | p1 := tb.focusedPane() | ||
| 388 | p2 := newPane("pane-2", second, domain.Size{Cols: 80, Rows: 23}) | 388 | p2 := newPane("pane-2", second, domain.Size{Cols: 80, Rows: 23}) | ||
| 389 | tb.mu.Lock() | 389 | tb.mu.Lock() | ||
| @@ -417,7 +417,7 @@ func TestLayoutApplicationRejectsStaleCloseFocusAndSize(t *testing.T) { | |||||
| 417 | t.Run("client size rejects and retries a blocked plan", func(t *testing.T) { | 417 | t.Run("client size rejects and retries a blocked plan", func(t *testing.T) { | ||
| 418 | pty, entered, release := newBlockedPTY() | 418 | pty, entered, release := newBlockedPTY() | ||
| 419 | d, sess, _, _ := newManualSessionWithPTYs(t, pty) | 419 | d, sess, _, _ := newManualSessionWithPTYs(t, pty) | ||
| 420 | - | tb := sess.activeTab() | 420 | + | tb := testAttachmentTab(sess) |
| 421 | p := tb.focusedPane() | 421 | p := tb.focusedPane() | ||
| 422 | p.rect = domain.Rect{Width: 20, Height: 10} | 422 | p.rect = domain.Rect{Width: 20, Height: 10} | ||
| 423 | 423 | ||||
| @@ -476,7 +476,7 @@ func TestSplitPaneCreatesFocusedShellInRequestedPosition(t *testing.T) { | |||||
| 476 | 476 | ||||
| 477 | require.NoError(t, d.splitPane(sess, nil, tt.dir)) | 477 | require.NoError(t, d.splitPane(sess, nil, tt.dir)) | ||
| 478 | 478 | ||||
| 479 | - | tb := sess.activeTab() | 479 | + | tb := testAttachmentTab(sess) |
| 480 | tb.mu.Lock() | 480 | tb.mu.Lock() | ||
| 481 | defer tb.mu.Unlock() | 481 | defer tb.mu.Unlock() | ||
| 482 | require.Equal(t, layout.PaneID("pane-2"), tb.tree.Focus) | 482 | require.Equal(t, layout.PaneID("pane-2"), tb.tree.Focus) | ||
| @@ -492,7 +492,7 @@ func TestSplitPaneCreatesFocusedShellInRequestedPosition(t *testing.T) { | |||||
| 492 | 492 | ||||
| 493 | func TestSplitPaneRightFromStackSplitsWholeStack(t *testing.T) { | 493 | func TestSplitPaneRightFromStackSplitsWholeStack(t *testing.T) { | ||
| 494 | d, sess, _, factory := newSplitTestDaemon(t, domain.Size{Cols: 41, Rows: 4}) | 494 | d, sess, _, factory := newSplitTestDaemon(t, domain.Size{Cols: 41, Rows: 4}) | ||
| 495 | - | tb := sess.activeTab() | 495 | + | tb := testAttachmentTab(sess) |
| 496 | stackPTY := portsmocks.NewMockPTY(t) | 496 | stackPTY := portsmocks.NewMockPTY(t) | ||
| 497 | stackPane := newPane("pane-2", stackPTY, domain.Size{Cols: 41, Rows: 2}) | 497 | stackPane := newPane("pane-2", stackPTY, domain.Size{Cols: 41, Rows: 2}) | ||
| 498 | tb.panes[stackPane.id] = stackPane | 498 | tb.panes[stackPane.id] = stackPane | ||
| @@ -520,7 +520,7 @@ func TestSplitPaneOpenErrorRollsBackTreeAndPaneMap(t *testing.T) { | |||||
| 520 | cause := errors.New("open failed") | 520 | cause := errors.New("open failed") | ||
| 521 | factory.EXPECT().Open(mock.Anything, "/bin/sh", []string(nil), mock.Anything, "/work", domain.Size{Cols: 20, Rows: 10}).Return(nil, cause).Once() | 521 | factory.EXPECT().Open(mock.Anything, "/bin/sh", []string(nil), mock.Anything, "/work", domain.Size{Cols: 20, Rows: 10}).Return(nil, cause).Once() | ||
| 522 | 522 | ||||
| 523 | - | tb := sess.activeTab() | 523 | + | tb := testAttachmentTab(sess) |
| 524 | tb.mu.Lock() | 524 | tb.mu.Lock() | ||
| 525 | beforeRoot, beforeFocus, beforeNext, beforePaneCount := tb.tree.Root.Leaf, tb.tree.Focus, tb.nextPaneID, len(tb.panes) | 525 | beforeRoot, beforeFocus, beforeNext, beforePaneCount := tb.tree.Root.Leaf, tb.tree.Focus, tb.nextPaneID, len(tb.panes) | ||
| 526 | tb.mu.Unlock() | 526 | tb.mu.Unlock() | ||
| @@ -547,7 +547,7 @@ func TestSplitPaneOpenErrorRollsBackTreeAndPaneMap(t *testing.T) { | |||||
| 547 | 547 | ||||
| 548 | func TestApplyLayoutResizesPTYsAndScreens(t *testing.T) { | 548 | func TestApplyLayoutResizesPTYsAndScreens(t *testing.T) { | ||
| 549 | d, sess, oldPTY, _ := newSplitTestDaemon(t, domain.Size{Cols: 41, Rows: 10}) | 549 | d, sess, oldPTY, _ := newSplitTestDaemon(t, domain.Size{Cols: 41, Rows: 10}) | ||
| 550 | - | tb := sess.activeTab() | 550 | + | tb := testAttachmentTab(sess) |
| 551 | newPTY := portsmocks.NewMockPTY(t) | 551 | newPTY := portsmocks.NewMockPTY(t) | ||
| 552 | newPane := newPane("pane-2", newPTY, domain.Size{Cols: 41, Rows: 10}) | 552 | newPane := newPane("pane-2", newPTY, domain.Size{Cols: 41, Rows: 10}) | ||
| 553 | tb.tree = &layout.Tree{Root: &layout.Node{Kind: layout.Split, Dir: layout.Horizontal, Children: []*layout.Node{layout.NewLeaf("pane-1"), layout.NewLeaf("pane-2")}}, Focus: "pane-2"} | 553 | tb.tree = &layout.Tree{Root: &layout.Node{Kind: layout.Split, Dir: layout.Horizontal, Children: []*layout.Node{layout.NewLeaf("pane-1"), layout.NewLeaf("pane-2")}}, Focus: "pane-2"} | ||
| @@ -564,7 +564,7 @@ func TestApplyLayoutResizesPTYsAndScreens(t *testing.T) { | |||||
| 564 | 564 | ||||
| 565 | func TestResizeReflowsAllPanes(t *testing.T) { | 565 | func TestResizeReflowsAllPanes(t *testing.T) { | ||
| 566 | d, sess, oldPTY, _ := newSplitTestDaemon(t, domain.Size{Cols: 41, Rows: 10}) | 566 | d, sess, oldPTY, _ := newSplitTestDaemon(t, domain.Size{Cols: 41, Rows: 10}) | ||
| 567 | - | tb := sess.activeTab() | 567 | + | tb := testAttachmentTab(sess) |
| 568 | newPTY := portsmocks.NewMockPTY(t) | 568 | newPTY := portsmocks.NewMockPTY(t) | ||
| 569 | tb.tree = &layout.Tree{Root: &layout.Node{Kind: layout.Split, Dir: layout.Horizontal, Children: []*layout.Node{layout.NewLeaf("pane-1"), layout.NewLeaf("pane-2")}}, Focus: "pane-2"} | 569 | tb.tree = &layout.Tree{Root: &layout.Node{Kind: layout.Split, Dir: layout.Horizontal, Children: []*layout.Node{layout.NewLeaf("pane-1"), layout.NewLeaf("pane-2")}}, Focus: "pane-2"} | ||
| 570 | tb.panes["pane-2"] = newPane("pane-2", newPTY, domain.Size{Cols: 20, Rows: 10}) | 570 | tb.panes["pane-2"] = newPane("pane-2", newPTY, domain.Size{Cols: 20, Rows: 10}) | ||
| @@ -596,7 +596,7 @@ func TestFocusDirAtReturnsDepartingSpanAndMapsNoNeighbor(t *testing.T) { | |||||
| 596 | for _, tt := range tests { | 596 | for _, tt := range tests { | ||
| 597 | t.Run(tt.name, func(t *testing.T) { | 597 | t.Run(tt.name, func(t *testing.T) { | ||
| 598 | d, sess, _, _ := newSplitTestDaemon(t, domain.Size{Cols: 41, Rows: 10}) | 598 | d, sess, _, _ := newSplitTestDaemon(t, domain.Size{Cols: 41, Rows: 10}) | ||
| 599 | - | tb := sess.activeTab() | 599 | + | tb := testAttachmentTab(sess) |
| 600 | target := tb.panes["pane-1"] | 600 | target := tb.panes["pane-1"] | ||
| 601 | tb.tree = &layout.Tree{Root: &layout.Node{Kind: layout.Split, Dir: layout.Horizontal, Children: []*layout.Node{layout.NewLeaf("pane-1"), layout.NewLeaf("pane-2")}}, Focus: "pane-1"} | 601 | tb.tree = &layout.Tree{Root: &layout.Node{Kind: layout.Split, Dir: layout.Horizontal, Children: []*layout.Node{layout.NewLeaf("pane-1"), layout.NewLeaf("pane-2")}}, Focus: "pane-1"} | ||
| 602 | tb.panes["pane-1"].rect = domain.Rect{Width: 20, Height: 10} | 602 | tb.panes["pane-1"].rect = domain.Rect{Width: 20, Height: 10} | ||
| @@ -614,7 +614,7 @@ func TestFocusDirAtReturnsDepartingSpanAndMapsNoNeighbor(t *testing.T) { | |||||
| 614 | 614 | ||||
| 615 | func TestFocusDirMovesFocusAndExitsCopyMode(t *testing.T) { | 615 | func TestFocusDirMovesFocusAndExitsCopyMode(t *testing.T) { | ||
| 616 | d, sess, oldPTY, _ := newSplitTestDaemon(t, domain.Size{Cols: 41, Rows: 10}) | 616 | d, sess, oldPTY, _ := newSplitTestDaemon(t, domain.Size{Cols: 41, Rows: 10}) | ||
| 617 | - | tb := sess.activeTab() | 617 | + | tb := testAttachmentTab(sess) |
| 618 | newPTY := portsmocks.NewMockPTY(t) | 618 | newPTY := portsmocks.NewMockPTY(t) | ||
| 619 | tb.tree = &layout.Tree{Root: &layout.Node{Kind: layout.Split, Dir: layout.Horizontal, Children: []*layout.Node{layout.NewLeaf("pane-1"), layout.NewLeaf("pane-2")}}, Focus: "pane-1"} | 619 | tb.tree = &layout.Tree{Root: &layout.Node{Kind: layout.Split, Dir: layout.Horizontal, Children: []*layout.Node{layout.NewLeaf("pane-1"), layout.NewLeaf("pane-2")}}, Focus: "pane-1"} | ||
| 620 | tb.panes["pane-2"] = newPane("pane-2", newPTY, domain.Size{Cols: 20, Rows: 10}) | 620 | tb.panes["pane-2"] = newPane("pane-2", newPTY, domain.Size{Cols: 20, Rows: 10}) | ||
| @@ -635,7 +635,7 @@ func TestFocusDirMovesFocusAndExitsCopyMode(t *testing.T) { | |||||
| 635 | 635 | ||||
| 636 | func TestCloseFocusedPaneRemovesPaneReflowsAndIsIdempotent(t *testing.T) { | 636 | func TestCloseFocusedPaneRemovesPaneReflowsAndIsIdempotent(t *testing.T) { | ||
| 637 | d, sess, oldPTY, _ := newSplitTestDaemon(t, domain.Size{Cols: 41, Rows: 10}) | 637 | d, sess, oldPTY, _ := newSplitTestDaemon(t, domain.Size{Cols: 41, Rows: 10}) | ||
| 638 | - | tb := sess.activeTab() | 638 | + | tb := testAttachmentTab(sess) |
| 639 | closingPTY := portsmocks.NewMockPTY(t) | 639 | closingPTY := portsmocks.NewMockPTY(t) | ||
| 640 | tb.tree = &layout.Tree{Root: &layout.Node{Kind: layout.Split, Dir: layout.Horizontal, Children: []*layout.Node{layout.NewLeaf("pane-1"), layout.NewLeaf("pane-2")}}, Focus: "pane-2"} | 640 | tb.tree = &layout.Tree{Root: &layout.Node{Kind: layout.Split, Dir: layout.Horizontal, Children: []*layout.Node{layout.NewLeaf("pane-1"), layout.NewLeaf("pane-2")}}, Focus: "pane-2"} | ||
| 641 | tb.panes["pane-2"] = newPane("pane-2", closingPTY, domain.Size{Cols: 20, Rows: 10}) | 641 | tb.panes["pane-2"] = newPane("pane-2", closingPTY, domain.Size{Cols: 20, Rows: 10}) | ||
| @@ -654,7 +654,7 @@ func TestCloseFocusedPaneRemovesPaneReflowsAndIsIdempotent(t *testing.T) { | |||||
| 654 | 654 | ||||
| 655 | func TestReapPaneSharesClosePathAndIsIdempotent(t *testing.T) { | 655 | func TestReapPaneSharesClosePathAndIsIdempotent(t *testing.T) { | ||
| 656 | d, sess, oldPTY, _ := newSplitTestDaemon(t, domain.Size{Cols: 41, Rows: 10}) | 656 | d, sess, oldPTY, _ := newSplitTestDaemon(t, domain.Size{Cols: 41, Rows: 10}) | ||
| 657 | - | tb := sess.activeTab() | 657 | + | tb := testAttachmentTab(sess) |
| 658 | reapedPTY := portsmocks.NewMockPTY(t) | 658 | reapedPTY := portsmocks.NewMockPTY(t) | ||
| 659 | reaped := newPane("pane-2", reapedPTY, domain.Size{Cols: 20, Rows: 10}) | 659 | reaped := newPane("pane-2", reapedPTY, domain.Size{Cols: 20, Rows: 10}) | ||
| 660 | tb.tree = &layout.Tree{Root: &layout.Node{Kind: layout.Split, Dir: layout.Horizontal, Children: []*layout.Node{layout.NewLeaf("pane-1"), layout.NewLeaf("pane-2")}}, Focus: "pane-2"} | 660 | tb.tree = &layout.Tree{Root: &layout.Node{Kind: layout.Split, Dir: layout.Horizontal, Children: []*layout.Node{layout.NewLeaf("pane-1"), layout.NewLeaf("pane-2")}}, Focus: "pane-2"} | ||
| @@ -673,7 +673,7 @@ func TestReapPaneSharesClosePathAndIsIdempotent(t *testing.T) { | |||||
| 673 | 673 | ||||
| 674 | func TestClosePaneKeepsTabOpenWhenPendingSpawnLeafExists(t *testing.T) { | 674 | func TestClosePaneKeepsTabOpenWhenPendingSpawnLeafExists(t *testing.T) { | ||
| 675 | d, sess, oldPTY, _ := newSplitTestDaemon(t, domain.Size{Cols: 41, Rows: 10}) | 675 | d, sess, oldPTY, _ := newSplitTestDaemon(t, domain.Size{Cols: 41, Rows: 10}) | ||
| 676 | - | tb := sess.activeTab() | 676 | + | tb := testAttachmentTab(sess) |
| 677 | tb.tree = &layout.Tree{Root: &layout.Node{Kind: layout.Split, Dir: layout.Horizontal, Children: []*layout.Node{layout.NewLeaf("pane-1"), layout.NewLeaf("pane-2")}}, Focus: "pane-2"} | 677 | tb.tree = &layout.Tree{Root: &layout.Node{Kind: layout.Split, Dir: layout.Horizontal, Children: []*layout.Node{layout.NewLeaf("pane-1"), layout.NewLeaf("pane-2")}}, Focus: "pane-2"} | ||
| 678 | oldPTY.EXPECT().Close().Return(nil).Once() | 678 | oldPTY.EXPECT().Close().Return(nil).Once() | ||
| 679 | 679 | ||||
| @@ -705,7 +705,7 @@ func TestStackPaneCreatesStackAndToggleRestoresSplit(t *testing.T) { | |||||
| 705 | factory.EXPECT().Open(mock.Anything, "/bin/sh", []string(nil), mock.Anything, "/work", domain.Size{Cols: 20, Rows: 4}).Return(newPTY, nil).Once() | 705 | factory.EXPECT().Open(mock.Anything, "/bin/sh", []string(nil), mock.Anything, "/work", domain.Size{Cols: 20, Rows: 4}).Return(newPTY, nil).Once() | ||
| 706 | 706 | ||||
| 707 | require.NoError(t, d.stackPane(sess, nil)) | 707 | require.NoError(t, d.stackPane(sess, nil)) | ||
| 708 | - | tb := sess.activeTab() | 708 | + | tb := testAttachmentTab(sess) |
| 709 | require.Equal(t, layout.Stack, tb.tree.Root.Kind) | 709 | require.Equal(t, layout.Stack, tb.tree.Root.Kind) | ||
| 710 | require.Equal(t, layout.PaneID("pane-2"), tb.tree.Focus) | 710 | require.Equal(t, layout.PaneID("pane-2"), tb.tree.Focus) | ||
| 711 | require.Equal(t, layout.PaneID("pane-2"), tb.tree.Root.Expanded) | 711 | require.Equal(t, layout.PaneID("pane-2"), tb.tree.Root.Expanded) | ||
| @@ -728,7 +728,7 @@ func TestStackFocusWalkExpandsAndOverflowRefuses(t *testing.T) { | |||||
| 728 | 728 | ||||
| 729 | require.NoError(t, d.focusDir(sess, nil, layout.Up, nil)) | 729 | require.NoError(t, d.focusDir(sess, nil, layout.Up, nil)) | ||
| 730 | 730 | ||||
| 731 | - | tb := sess.activeTab() | 731 | + | tb := testAttachmentTab(sess) |
| 732 | require.Equal(t, layout.PaneID("pane-1"), tb.tree.Focus) | 732 | require.Equal(t, layout.PaneID("pane-1"), tb.tree.Focus) | ||
| 733 | require.Equal(t, layout.PaneID("pane-1"), tb.tree.Root.Expanded) | 733 | require.Equal(t, layout.PaneID("pane-1"), tb.tree.Root.Expanded) | ||
| 734 | thirdPTY := portsmocks.NewMockPTY(t) | 734 | thirdPTY := portsmocks.NewMockPTY(t) | ||
| @@ -773,7 +773,7 @@ func TestCloseOriginalPaneLeavesSurvivorFunctional(t *testing.T) { | |||||
| 773 | factory.EXPECT().Open(mock.Anything, "/bin/sh", []string(nil), mock.Anything, "/work", domain.Size{Cols: 20, Rows: 10}).Return(newPTY, nil).Once() | 773 | factory.EXPECT().Open(mock.Anything, "/bin/sh", []string(nil), mock.Anything, "/work", domain.Size{Cols: 20, Rows: 10}).Return(newPTY, nil).Once() | ||
| 774 | 774 | ||||
| 775 | require.NoError(t, d.splitPane(sess, nil, layout.Right)) | 775 | require.NoError(t, d.splitPane(sess, nil, layout.Right)) | ||
| 776 | - | tb := sess.activeTab() | 776 | + | tb := testAttachmentTab(sess) |
| 777 | tb.mu.Lock() | 777 | tb.mu.Lock() | ||
| 778 | tb.tree.Focus = "pane-1" | 778 | tb.tree.Focus = "pane-1" | ||
| 779 | tb.mu.Unlock() | 779 | tb.mu.Unlock() | ||
| @@ -844,7 +844,7 @@ func TestSplitPaneUsesExactAuthoritativeEnvironment(t *testing.T) { | |||||
| 844 | ).Once() | 844 | ).Once() | ||
| 845 | 845 | ||||
| 846 | require.NoError(t, d.splitPane(sess, nil, layout.Right)) | 846 | require.NoError(t, d.splitPane(sess, nil, layout.Right)) | ||
| 847 | - | tb := sess.activeTab() | 847 | + | tb := testAttachmentTab(sess) |
| 848 | tb.mu.Lock() | 848 | tb.mu.Lock() | ||
| 849 | paneID := tb.panes["pane-2"].stableID | 849 | paneID := tb.panes["pane-2"].stableID | ||
| 850 | tabID := tb.stableID | 850 | tabID := tb.stableID | ||
M internal/usecase/daemon/pane_lifetime_test.go +2 -2
| @@ -223,7 +223,7 @@ func TestPaneProcessLifetimeIsDaemonRooted(t *testing.T) { | |||
| 223 | 223 | setup: func(t *testing.T, d *Daemon) (*pane, func(), func()) { | |
| 224 | 224 | sess, err := createSessionForTest(d, "work", true, "/tmp", domain.Size{Cols: 80, Rows: 24}, terminalEnv{}, nil) | |
| 225 | 225 | require.NoError(t, err) | |
| 226 | - | return sess.activeTab().focusedPane(), sess.cancel, func() { | |
| 226 | + | return testAttachmentTab(sess).focusedPane(), sess.cancel, func() { | |
| 227 | 227 | _ = d.killSession(sess, ports.ReasonSessionKilled, false) | |
| 228 | 228 | } | |
| 229 | 229 | }, | |
| @@ -239,7 +239,7 @@ func TestPaneProcessLifetimeIsDaemonRooted(t *testing.T) { | |||
| 239 | 239 | d.sessions[sess.id] = sess | |
| 240 | 240 | d.mu.Unlock() | |
| 241 | 241 | require.NoError(t, d.createTab(sess, domain.Size{Cols: 80, Rows: 24})) | |
| 242 | - | created := sess.activeTab() | |
| 242 | + | created := testAttachmentTab(sess) | |
| 243 | 243 | return created.focusedPane(), cancelSession, created.closeAllPanes | |
| 244 | 244 | }, | |
| 245 | 245 | }, | |
| @@ -223,7 +223,7 @@ func TestPaneProcessLifetimeIsDaemonRooted(t *testing.T) { | |||||
| 223 | setup: func(t *testing.T, d *Daemon) (*pane, func(), func()) { | 223 | setup: func(t *testing.T, d *Daemon) (*pane, func(), func()) { | ||
| 224 | sess, err := createSessionForTest(d, "work", true, "/tmp", domain.Size{Cols: 80, Rows: 24}, terminalEnv{}, nil) | 224 | sess, err := createSessionForTest(d, "work", true, "/tmp", domain.Size{Cols: 80, Rows: 24}, terminalEnv{}, nil) | ||
| 225 | require.NoError(t, err) | 225 | require.NoError(t, err) | ||
| 226 | - | return sess.activeTab().focusedPane(), sess.cancel, func() { | 226 | + | return testAttachmentTab(sess).focusedPane(), sess.cancel, func() { |
| 227 | _ = d.killSession(sess, ports.ReasonSessionKilled, false) | 227 | _ = d.killSession(sess, ports.ReasonSessionKilled, false) | ||
| 228 | } | 228 | } | ||
| 229 | }, | 229 | }, | ||
| @@ -239,7 +239,7 @@ func TestPaneProcessLifetimeIsDaemonRooted(t *testing.T) { | |||||
| 239 | d.sessions[sess.id] = sess | 239 | d.sessions[sess.id] = sess | ||
| 240 | d.mu.Unlock() | 240 | d.mu.Unlock() | ||
| 241 | require.NoError(t, d.createTab(sess, domain.Size{Cols: 80, Rows: 24})) | 241 | require.NoError(t, d.createTab(sess, domain.Size{Cols: 80, Rows: 24})) | ||
| 242 | - | created := sess.activeTab() | 242 | + | created := testAttachmentTab(sess) |
| 243 | return created.focusedPane(), cancelSession, created.closeAllPanes | 243 | return created.focusedPane(), cancelSession, created.closeAllPanes | ||
| 244 | }, | 244 | }, | ||
| 245 | }, | 245 | }, | ||
M internal/usecase/daemon/pane_owner_reader_test.go +4 -4
| @@ -74,7 +74,7 @@ func (p *pane) clearOwnerForTest() { | |||
| 74 | 74 | func TestPTYReaderRoutesEffectsToOwnerPublishedForEachRead(t *testing.T) { | |
| 75 | 75 | pty := newOwnerRoutingPTY() | |
| 76 | 76 | d, source, _, _ := newManualSessionWithPTYs(t, pty) | |
| 77 | - | pane := source.activeTab().focusedPane() | |
| 77 | + | pane := testAttachmentTab(source).focusedPane() | |
| 78 | 78 | pane.onExit = func() {} | |
| 79 | 79 | source.snapEligible.Store(true) | |
| 80 | 80 | ||
| @@ -97,14 +97,14 @@ func TestPTYReaderRoutesEffectsToOwnerPublishedForEachRead(t *testing.T) { | |||
| 97 | 97 | destinationCoordinator := d.attachCoordinator(destination, nil, destinationAttachment, true) | |
| 98 | 98 | destinationCoordinator.opts.onInvalidate = func(invalidation renderInvalidation) { destinationInvalidations <- invalidation } | |
| 99 | 99 | ||
| 100 | - | d.startPaneGoroutines(source, source.activeTab(), pane) | |
| 100 | + | d.startPaneGoroutines(source, testAttachmentTab(source), pane) | |
| 101 | 101 | pty.steps <- channelPTYStep{data: []byte("source\a")} | |
| 102 | 102 | <-pty.processed | |
| 103 | 103 | require.Len(t, sourceInvalidations, 1) | |
| 104 | 104 | require.Empty(t, destinationInvalidations) | |
| 105 | 105 | require.Equal(t, uint64(1), source.snapshotGeneration) | |
| 106 | 106 | require.Zero(t, destination.snapshotGeneration) | |
| 107 | - | require.True(t, source.activeTab().attention) | |
| 107 | + | require.True(t, testAttachmentTab(source).attention) | |
| 108 | 108 | ||
| 109 | 109 | installTiledPaneOwnerForTest(destination, destinationTab, pane) | |
| 110 | 110 | pty.steps <- channelPTYStep{data: []byte("destination\a")} | |
| @@ -124,7 +124,7 @@ func TestPTYReaderRoutesEffectsToOwnerPublishedForEachRead(t *testing.T) { | |||
| 124 | 124 | func TestPTYReaderExitReapsExactlyCurrentOwnerAfterPublication(t *testing.T) { | |
| 125 | 125 | pty := newOwnerRoutingPTY() | |
| 126 | 126 | d, source, _, _ := newManualSessionWithPTYs(t, pty) | |
| 127 | - | sourceTab := source.activeTab() | |
| 127 | + | sourceTab := testAttachmentTab(source) | |
| 128 | 128 | moved := sourceTab.focusedPane() | |
| 129 | 129 | ||
| 130 | 130 | destinationTab := newTab(newQuietPTY(), domain.Size{Cols: 80, Rows: 23}) | |
| @@ -74,7 +74,7 @@ func (p *pane) clearOwnerForTest() { | |||||
| 74 | func TestPTYReaderRoutesEffectsToOwnerPublishedForEachRead(t *testing.T) { | 74 | func TestPTYReaderRoutesEffectsToOwnerPublishedForEachRead(t *testing.T) { | ||
| 75 | pty := newOwnerRoutingPTY() | 75 | pty := newOwnerRoutingPTY() | ||
| 76 | d, source, _, _ := newManualSessionWithPTYs(t, pty) | 76 | d, source, _, _ := newManualSessionWithPTYs(t, pty) | ||
| 77 | - | pane := source.activeTab().focusedPane() | 77 | + | pane := testAttachmentTab(source).focusedPane() |
| 78 | pane.onExit = func() {} | 78 | pane.onExit = func() {} | ||
| 79 | source.snapEligible.Store(true) | 79 | source.snapEligible.Store(true) | ||
| 80 | 80 | ||||
| @@ -97,14 +97,14 @@ func TestPTYReaderRoutesEffectsToOwnerPublishedForEachRead(t *testing.T) { | |||||
| 97 | destinationCoordinator := d.attachCoordinator(destination, nil, destinationAttachment, true) | 97 | destinationCoordinator := d.attachCoordinator(destination, nil, destinationAttachment, true) | ||
| 98 | destinationCoordinator.opts.onInvalidate = func(invalidation renderInvalidation) { destinationInvalidations <- invalidation } | 98 | destinationCoordinator.opts.onInvalidate = func(invalidation renderInvalidation) { destinationInvalidations <- invalidation } | ||
| 99 | 99 | ||||
| 100 | - | d.startPaneGoroutines(source, source.activeTab(), pane) | 100 | + | d.startPaneGoroutines(source, testAttachmentTab(source), pane) |
| 101 | pty.steps <- channelPTYStep{data: []byte("source\a")} | 101 | pty.steps <- channelPTYStep{data: []byte("source\a")} | ||
| 102 | <-pty.processed | 102 | <-pty.processed | ||
| 103 | require.Len(t, sourceInvalidations, 1) | 103 | require.Len(t, sourceInvalidations, 1) | ||
| 104 | require.Empty(t, destinationInvalidations) | 104 | require.Empty(t, destinationInvalidations) | ||
| 105 | require.Equal(t, uint64(1), source.snapshotGeneration) | 105 | require.Equal(t, uint64(1), source.snapshotGeneration) | ||
| 106 | require.Zero(t, destination.snapshotGeneration) | 106 | require.Zero(t, destination.snapshotGeneration) | ||
| 107 | - | require.True(t, source.activeTab().attention) | 107 | + | require.True(t, testAttachmentTab(source).attention) |
| 108 | 108 | ||||
| 109 | installTiledPaneOwnerForTest(destination, destinationTab, pane) | 109 | installTiledPaneOwnerForTest(destination, destinationTab, pane) | ||
| 110 | pty.steps <- channelPTYStep{data: []byte("destination\a")} | 110 | pty.steps <- channelPTYStep{data: []byte("destination\a")} | ||
| @@ -124,7 +124,7 @@ func TestPTYReaderRoutesEffectsToOwnerPublishedForEachRead(t *testing.T) { | |||||
| 124 | func TestPTYReaderExitReapsExactlyCurrentOwnerAfterPublication(t *testing.T) { | 124 | func TestPTYReaderExitReapsExactlyCurrentOwnerAfterPublication(t *testing.T) { | ||
| 125 | pty := newOwnerRoutingPTY() | 125 | pty := newOwnerRoutingPTY() | ||
| 126 | d, source, _, _ := newManualSessionWithPTYs(t, pty) | 126 | d, source, _, _ := newManualSessionWithPTYs(t, pty) | ||
| 127 | - | sourceTab := source.activeTab() | 127 | + | sourceTab := testAttachmentTab(source) |
| 128 | moved := sourceTab.focusedPane() | 128 | moved := sourceTab.focusedPane() | ||
| 129 | 129 | ||||
| 130 | destinationTab := newTab(newQuietPTY(), domain.Size{Cols: 80, Rows: 23}) | 130 | destinationTab := newTab(newQuietPTY(), domain.Size{Cols: 80, Rows: 23}) | ||
M internal/usecase/daemon/pane_sync_owner_test.go +2 -2
| @@ -27,8 +27,8 @@ func newPaneSyncOwnerFixture(t *testing.T) paneSyncOwnerFixture { | |||
| 27 | 27 | size := domain.Size{Cols: 80, Rows: 23} | |
| 28 | 28 | sourceTab := newTab(newQuietPTY(), size) | |
| 29 | 29 | targetTab := newTab(newQuietPTY(), size) | |
| 30 | - | source := &session{sessionCore: sessionCore{id: "sync-source", attachments: map[*attachedClient]struct{}{&attachedClient{}: {}}}, tabs: []*tab{sourceTab}, active: 0} | |
| 31 | - | destination := &session{sessionCore: sessionCore{id: "sync-destination", attachments: map[*attachedClient]struct{}{&attachedClient{}: {}}}, tabs: []*tab{targetTab}, active: 0} | |
| 30 | + | source := &session{sessionCore: sessionCore{id: "sync-source", attachments: map[*attachedClient]struct{}{&attachedClient{}: {}}}, tabs: []*tab{sourceTab}} | |
| 31 | + | destination := &session{sessionCore: sessionCore{id: "sync-destination", attachments: map[*attachedClient]struct{}{&attachedClient{}: {}}}, tabs: []*tab{targetTab}} | |
| 32 | 32 | d := &Daemon{sessions: map[domain.SessionID]attachmentSession{source.id: source, destination.id: destination}} | |
| 33 | 33 | p := sourceTab.focusedPane() | |
| 34 | 34 | publishPaneOwner(p, source, sourceTab, 0) | |
| @@ -27,8 +27,8 @@ func newPaneSyncOwnerFixture(t *testing.T) paneSyncOwnerFixture { | |||||
| 27 | size := domain.Size{Cols: 80, Rows: 23} | 27 | size := domain.Size{Cols: 80, Rows: 23} | ||
| 28 | sourceTab := newTab(newQuietPTY(), size) | 28 | sourceTab := newTab(newQuietPTY(), size) | ||
| 29 | targetTab := newTab(newQuietPTY(), size) | 29 | targetTab := newTab(newQuietPTY(), size) | ||
| 30 | - | source := &session{sessionCore: sessionCore{id: "sync-source", attachments: map[*attachedClient]struct{}{&attachedClient{}: {}}}, tabs: []*tab{sourceTab}, active: 0} | 30 | + | source := &session{sessionCore: sessionCore{id: "sync-source", attachments: map[*attachedClient]struct{}{&attachedClient{}: {}}}, tabs: []*tab{sourceTab}} |
| 31 | - | destination := &session{sessionCore: sessionCore{id: "sync-destination", attachments: map[*attachedClient]struct{}{&attachedClient{}: {}}}, tabs: []*tab{targetTab}, active: 0} | 31 | + | destination := &session{sessionCore: sessionCore{id: "sync-destination", attachments: map[*attachedClient]struct{}{&attachedClient{}: {}}}, tabs: []*tab{targetTab}} |
| 32 | d := &Daemon{sessions: map[domain.SessionID]attachmentSession{source.id: source, destination.id: destination}} | 32 | d := &Daemon{sessions: map[domain.SessionID]attachmentSession{source.id: source, destination.id: destination}} | ||
| 33 | p := sourceTab.focusedPane() | 33 | p := sourceTab.focusedPane() | ||
| 34 | publishPaneOwner(p, source, sourceTab, 0) | 34 | publishPaneOwner(p, source, sourceTab, 0) | ||
M internal/usecase/daemon/pane_titles.go +1 -1
| @@ -151,7 +151,7 @@ func (d *Daemon) refreshPaneTitleCached(sess *session, id layout.PaneID, force b | |||
| 151 | 151 | if sess == nil { | |
| 152 | 152 | return fallback | |
| 153 | 153 | } | |
| 154 | - | tb := sess.tabForAttachmentOrActive(nil) | |
| 154 | + | tb := sess.firstTab() | |
| 155 | 155 | if tb == nil { | |
| 156 | 156 | return fallback | |
| 157 | 157 | } | |
| @@ -151,7 +151,7 @@ func (d *Daemon) refreshPaneTitleCached(sess *session, id layout.PaneID, force b | |||||
| 151 | if sess == nil { | 151 | if sess == nil { | ||
| 152 | return fallback | 152 | return fallback | ||
| 153 | } | 153 | } | ||
| 154 | - | tb := sess.tabForAttachmentOrActive(nil) | 154 | + | tb := sess.firstTab() |
| 155 | if tb == nil { | 155 | if tb == nil { | ||
| 156 | return fallback | 156 | return fallback | ||
| 157 | } | 157 | } | ||
M internal/usecase/daemon/pane_titles_test.go +3 -3
| @@ -78,7 +78,7 @@ func TestRefreshPaneTitleUsesForegroundProcessComm(t *testing.T) { | |||
| 78 | 78 | title := d.refreshPaneTitle(sess, "pane-1") | |
| 79 | 79 | require.Equal(t, "vim", title) | |
| 80 | 80 | ||
| 81 | - | p := sess.activeTab().focusedPane() | |
| 81 | + | p := testAttachmentTab(sess).focusedPane() | |
| 82 | 82 | p.mu.Lock() | |
| 83 | 83 | p.screen.Write([]byte("\x1b]2;project/main.go\a")) | |
| 84 | 84 | p.refreshTerminalTitleLocked() | |
| @@ -142,7 +142,7 @@ func TestRefreshFloatingPaneTitleUsesShellFallbackForEmptyCommand(t *testing.T) | |||
| 142 | 142 | ||
| 143 | 143 | func TestRefreshPaneTitleUpdatesNormalPaneShellFallback(t *testing.T) { | |
| 144 | 144 | d, sess, _, _ := newManualSessionWithPTYs(t, nil) | |
| 145 | - | p := sess.activeTab().focusedPane() | |
| 145 | + | p := testAttachmentTab(sess).focusedPane() | |
| 146 | 146 | ||
| 147 | 147 | d.shell = "/usr/bin/fish" | |
| 148 | 148 | require.Equal(t, "fish", d.refreshPaneTitle(sess, "pane-1")) | |
| @@ -170,7 +170,7 @@ func TestRefreshPaneTitleLookupFailureKeepsProcessNameEmpty(t *testing.T) { | |||
| 170 | 170 | d := newTestDaemon(t, nil, clk) | |
| 171 | 171 | d.shell = "/usr/bin/fish" | |
| 172 | 172 | d.procComm = func(int) (string, error) { return "", errors.New("unused") } | |
| 173 | - | p := sess.activeTab().focusedPane() | |
| 173 | + | p := testAttachmentTab(sess).focusedPane() | |
| 174 | 174 | ||
| 175 | 175 | p.mu.Lock() | |
| 176 | 176 | p.screen.Write([]byte("\x1b]2;terminal\a")) | |
| @@ -78,7 +78,7 @@ func TestRefreshPaneTitleUsesForegroundProcessComm(t *testing.T) { | |||||
| 78 | title := d.refreshPaneTitle(sess, "pane-1") | 78 | title := d.refreshPaneTitle(sess, "pane-1") | ||
| 79 | require.Equal(t, "vim", title) | 79 | require.Equal(t, "vim", title) | ||
| 80 | 80 | ||||
| 81 | - | p := sess.activeTab().focusedPane() | 81 | + | p := testAttachmentTab(sess).focusedPane() |
| 82 | p.mu.Lock() | 82 | p.mu.Lock() | ||
| 83 | p.screen.Write([]byte("\x1b]2;project/main.go\a")) | 83 | p.screen.Write([]byte("\x1b]2;project/main.go\a")) | ||
| 84 | p.refreshTerminalTitleLocked() | 84 | p.refreshTerminalTitleLocked() | ||
| @@ -142,7 +142,7 @@ func TestRefreshFloatingPaneTitleUsesShellFallbackForEmptyCommand(t *testing.T) | |||||
| 142 | 142 | ||||
| 143 | func TestRefreshPaneTitleUpdatesNormalPaneShellFallback(t *testing.T) { | 143 | func TestRefreshPaneTitleUpdatesNormalPaneShellFallback(t *testing.T) { | ||
| 144 | d, sess, _, _ := newManualSessionWithPTYs(t, nil) | 144 | d, sess, _, _ := newManualSessionWithPTYs(t, nil) | ||
| 145 | - | p := sess.activeTab().focusedPane() | 145 | + | p := testAttachmentTab(sess).focusedPane() |
| 146 | 146 | ||||
| 147 | d.shell = "/usr/bin/fish" | 147 | d.shell = "/usr/bin/fish" | ||
| 148 | require.Equal(t, "fish", d.refreshPaneTitle(sess, "pane-1")) | 148 | require.Equal(t, "fish", d.refreshPaneTitle(sess, "pane-1")) | ||
| @@ -170,7 +170,7 @@ func TestRefreshPaneTitleLookupFailureKeepsProcessNameEmpty(t *testing.T) { | |||||
| 170 | d := newTestDaemon(t, nil, clk) | 170 | d := newTestDaemon(t, nil, clk) | ||
| 171 | d.shell = "/usr/bin/fish" | 171 | d.shell = "/usr/bin/fish" | ||
| 172 | d.procComm = func(int) (string, error) { return "", errors.New("unused") } | 172 | d.procComm = func(int) (string, error) { return "", errors.New("unused") } | ||
| 173 | - | p := sess.activeTab().focusedPane() | 173 | + | p := testAttachmentTab(sess).focusedPane() |
| 174 | 174 | ||||
| 175 | p.mu.Lock() | 175 | p.mu.Lock() | ||
| 176 | p.screen.Write([]byte("\x1b]2;terminal\a")) | 176 | p.screen.Write([]byte("\x1b]2;terminal\a")) | ||
M internal/usecase/daemon/performance_benchmark_test.go +1 -1
| @@ -1565,7 +1565,7 @@ func performanceFullWidthRow(width, tabIndex, row int) string { | |||
| 1565 | 1565 | ||
| 1566 | 1566 | func (f *performanceFixture) findActivePane() *pane { | |
| 1567 | 1567 | f.t.Helper() | |
| 1568 | - | tb := f.sess.activeTab() | |
| 1568 | + | tb := testAttachmentTab(f.sess) | |
| 1569 | 1569 | require.NotNil(f.t, tb) | |
| 1570 | 1570 | tb.mu.Lock() | |
| 1571 | 1571 | defer tb.mu.Unlock() | |
| @@ -1565,7 +1565,7 @@ func performanceFullWidthRow(width, tabIndex, row int) string { | |||||
| 1565 | 1565 | ||||
| 1566 | func (f *performanceFixture) findActivePane() *pane { | 1566 | func (f *performanceFixture) findActivePane() *pane { | ||
| 1567 | f.t.Helper() | 1567 | f.t.Helper() | ||
| 1568 | - | tb := f.sess.activeTab() | 1568 | + | tb := testAttachmentTab(f.sess) |
| 1569 | require.NotNil(f.t, tb) | 1569 | require.NotNil(f.t, tb) | ||
| 1570 | tb.mu.Lock() | 1570 | tb.mu.Lock() | ||
| 1571 | defer tb.mu.Unlock() | 1571 | defer tb.mu.Unlock() | ||
M internal/usecase/daemon/picker.go +18 -8
| @@ -52,7 +52,7 @@ func (d *Daemon) togglePickerSort() { | |||
| 52 | 52 | // publishes its model, including an empty one; only move entry can fail for a | |
| 53 | 53 | // missing destination. | |
| 54 | 54 | func (d *Daemon) enterPicker(sess attachmentSession, ac *attachedClient) { | |
| 55 | - | model := d.newPickerModel(sess, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{}) | |
| 55 | + | model := d.newPickerModel(sess, ac, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{}) | |
| 56 | 56 | d.publishPicker(sess, ac, model, pickerNavigate, moveSourceLocator{}) | |
| 57 | 57 | } | |
| 58 | 58 | ||
| @@ -83,7 +83,7 @@ func (d *Daemon) publishPicker(sess attachmentSession, ac *attachedClient, model | |||
| 83 | 83 | ||
| 84 | 84 | // pickerViews captures one canonical lifecycle/tab snapshot. It intentionally | |
| 85 | 85 | // knows nothing about picker intent; picker.New owns all destination policy. | |
| 86 | - | func (d *Daemon) pickerViews(cur attachmentSession) ([]picker.SessionView, picker.SourceFilter) { | |
| 86 | + | func (d *Daemon) pickerViews(cur attachmentSession, ac *attachedClient) ([]picker.SessionView, picker.SourceFilter) { | |
| 87 | 87 | d.mu.Lock() | |
| 88 | 88 | sessions := make([]attachmentSession, 0, len(d.sessions)) | |
| 89 | 89 | proxies := make(map[domain.SessionID]*proxySession) | |
| @@ -119,13 +119,23 @@ func (d *Daemon) pickerViews(cur attachmentSession) ([]picker.SessionView, picke | |||
| 119 | 119 | live := make([]liveSnapshot, 0, len(sessions)) | |
| 120 | 120 | var current picker.SourceFilter | |
| 121 | 121 | for _, entry := range sessions { | |
| 122 | + | if entry == cur && ac != nil { | |
| 123 | + | if local, ok := localSession(entry); ok { | |
| 124 | + | local.repairAttachmentView(ac) | |
| 125 | + | } | |
| 126 | + | } | |
| 122 | 127 | snap := entry.snapshotView(opts) | |
| 123 | 128 | if entry == cur { | |
| 124 | 129 | if proxy, ok := entry.(*proxySession); ok { | |
| 125 | 130 | key := proxy.key | |
| 126 | 131 | current = picker.SourceFilter{Session: snap.id, Incarnation: snap.incarnation, RemoteKey: &key} | |
| 127 | - | } else if snap.active >= 0 && snap.active < len(snap.tabs) { | |
| 128 | - | current = picker.SourceFilter{Session: snap.id, Incarnation: snap.incarnation, TabID: snap.tabs[snap.active].id} | |
| 132 | + | } else if _, ok := localSession(entry); ok && ac != nil { | |
| 133 | + | view := ac.viewSnapshot() | |
| 134 | + | if view.tabID != "" { | |
| 135 | + | current = picker.SourceFilter{Session: snap.id, Incarnation: snap.incarnation, TabID: view.tabID} | |
| 136 | + | } | |
| 137 | + | } else if len(snap.tabs) > 0 { | |
| 138 | + | current = picker.SourceFilter{Session: snap.id, Incarnation: snap.incarnation, TabID: snap.tabs[snap.defaultTab].id} | |
| 129 | 139 | } | |
| 130 | 140 | } | |
| 131 | 141 | live = append(live, liveSnapshot{entry: entry, view: snap}) | |
| @@ -201,8 +211,8 @@ func (d *Daemon) pickerViews(cur attachmentSession) ([]picker.SessionView, picke | |||
| 201 | 211 | return views, current | |
| 202 | 212 | } | |
| 203 | 213 | ||
| 204 | - | func (d *Daemon) newPickerModel(cur attachmentSession, intent pickerIntent, source moveSourceLocator, current picker.SourceFilter) *picker.Model { | |
| 205 | - | views, attachedCurrent := d.pickerViews(cur) | |
| 214 | + | func (d *Daemon) newPickerModel(cur attachmentSession, ac *attachedClient, intent pickerIntent, source moveSourceLocator, current picker.SourceFilter) *picker.Model { | |
| 215 | + | views, attachedCurrent := d.pickerViews(cur, ac) | |
| 206 | 216 | if intent == pickerNavigate && current == (picker.SourceFilter{}) { | |
| 207 | 217 | current = attachedCurrent | |
| 208 | 218 | } | |
| @@ -877,12 +887,12 @@ func (d *Daemon) switchToTargetGuardedForRole(from *session, ac *attachedClient, | |||
| 877 | 887 | if sourceToken != nil { | |
| 878 | 888 | fresh, admitted := ac.beginRoleEffect(transition.published) | |
| 879 | 889 | if admitted { | |
| 880 | - | d.activateTabAfterResizeForLease(from, from.tabForAttachmentOrActive(ac), false, ac, transition.published.lease) | |
| 890 | + | d.activateTabAfterResizeForLease(from, from.tabForAttachment(ac), false, ac, transition.published.lease) | |
| 881 | 891 | d.invalidateRender(from, ac, true, "picker.go") | |
| 882 | 892 | fresh.End() | |
| 883 | 893 | } | |
| 884 | 894 | } else { | |
| 885 | - | d.activateTabAfterResizeForLease(from, from.tabForAttachmentOrActive(ac), false, ac, nil) | |
| 895 | + | d.activateTabAfterResizeForLease(from, from.tabForAttachment(ac), false, ac, nil) | |
| 886 | 896 | d.invalidateRender(from, ac, true, "picker.go") | |
| 887 | 897 | } | |
| 888 | 898 | } else { | |
| @@ -52,7 +52,7 @@ func (d *Daemon) togglePickerSort() { | |||||
| 52 | // publishes its model, including an empty one; only move entry can fail for a | 52 | // publishes its model, including an empty one; only move entry can fail for a | ||
| 53 | // missing destination. | 53 | // missing destination. | ||
| 54 | func (d *Daemon) enterPicker(sess attachmentSession, ac *attachedClient) { | 54 | func (d *Daemon) enterPicker(sess attachmentSession, ac *attachedClient) { | ||
| 55 | - | model := d.newPickerModel(sess, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{}) | 55 | + | model := d.newPickerModel(sess, ac, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{}) |
| 56 | d.publishPicker(sess, ac, model, pickerNavigate, moveSourceLocator{}) | 56 | d.publishPicker(sess, ac, model, pickerNavigate, moveSourceLocator{}) | ||
| 57 | } | 57 | } | ||
| 58 | 58 | ||||
| @@ -83,7 +83,7 @@ func (d *Daemon) publishPicker(sess attachmentSession, ac *attachedClient, model | |||||
| 83 | 83 | ||||
| 84 | // pickerViews captures one canonical lifecycle/tab snapshot. It intentionally | 84 | // pickerViews captures one canonical lifecycle/tab snapshot. It intentionally | ||
| 85 | // knows nothing about picker intent; picker.New owns all destination policy. | 85 | // knows nothing about picker intent; picker.New owns all destination policy. | ||
| 86 | - | func (d *Daemon) pickerViews(cur attachmentSession) ([]picker.SessionView, picker.SourceFilter) { | 86 | + | func (d *Daemon) pickerViews(cur attachmentSession, ac *attachedClient) ([]picker.SessionView, picker.SourceFilter) { |
| 87 | d.mu.Lock() | 87 | d.mu.Lock() | ||
| 88 | sessions := make([]attachmentSession, 0, len(d.sessions)) | 88 | sessions := make([]attachmentSession, 0, len(d.sessions)) | ||
| 89 | proxies := make(map[domain.SessionID]*proxySession) | 89 | proxies := make(map[domain.SessionID]*proxySession) | ||
| @@ -119,13 +119,23 @@ func (d *Daemon) pickerViews(cur attachmentSession) ([]picker.SessionView, picke | |||||
| 119 | live := make([]liveSnapshot, 0, len(sessions)) | 119 | live := make([]liveSnapshot, 0, len(sessions)) | ||
| 120 | var current picker.SourceFilter | 120 | var current picker.SourceFilter | ||
| 121 | for _, entry := range sessions { | 121 | for _, entry := range sessions { | ||
| 122 | + | if entry == cur && ac != nil { | |||
| 123 | + | if local, ok := localSession(entry); ok { | |||
| 124 | + | local.repairAttachmentView(ac) | |||
| 125 | + | } | |||
| 126 | + | } | |||
| 122 | snap := entry.snapshotView(opts) | 127 | snap := entry.snapshotView(opts) | ||
| 123 | if entry == cur { | 128 | if entry == cur { | ||
| 124 | if proxy, ok := entry.(*proxySession); ok { | 129 | if proxy, ok := entry.(*proxySession); ok { | ||
| 125 | key := proxy.key | 130 | key := proxy.key | ||
| 126 | current = picker.SourceFilter{Session: snap.id, Incarnation: snap.incarnation, RemoteKey: &key} | 131 | current = picker.SourceFilter{Session: snap.id, Incarnation: snap.incarnation, RemoteKey: &key} | ||
| 127 | - | } else if snap.active >= 0 && snap.active < len(snap.tabs) { | 132 | + | } else if _, ok := localSession(entry); ok && ac != nil { |
| 128 | - | current = picker.SourceFilter{Session: snap.id, Incarnation: snap.incarnation, TabID: snap.tabs[snap.active].id} | 133 | + | view := ac.viewSnapshot() |
| 134 | + | if view.tabID != "" { | |||
| 135 | + | current = picker.SourceFilter{Session: snap.id, Incarnation: snap.incarnation, TabID: view.tabID} | |||
| 136 | + | } | |||
| 137 | + | } else if len(snap.tabs) > 0 { | |||
| 138 | + | current = picker.SourceFilter{Session: snap.id, Incarnation: snap.incarnation, TabID: snap.tabs[snap.defaultTab].id} | |||
| 129 | } | 139 | } | ||
| 130 | } | 140 | } | ||
| 131 | live = append(live, liveSnapshot{entry: entry, view: snap}) | 141 | live = append(live, liveSnapshot{entry: entry, view: snap}) | ||
| @@ -201,8 +211,8 @@ func (d *Daemon) pickerViews(cur attachmentSession) ([]picker.SessionView, picke | |||||
| 201 | return views, current | 211 | return views, current | ||
| 202 | } | 212 | } | ||
| 203 | 213 | ||||
| 204 | - | func (d *Daemon) newPickerModel(cur attachmentSession, intent pickerIntent, source moveSourceLocator, current picker.SourceFilter) *picker.Model { | 214 | + | func (d *Daemon) newPickerModel(cur attachmentSession, ac *attachedClient, intent pickerIntent, source moveSourceLocator, current picker.SourceFilter) *picker.Model { |
| 205 | - | views, attachedCurrent := d.pickerViews(cur) | 215 | + | views, attachedCurrent := d.pickerViews(cur, ac) |
| 206 | if intent == pickerNavigate && current == (picker.SourceFilter{}) { | 216 | if intent == pickerNavigate && current == (picker.SourceFilter{}) { | ||
| 207 | current = attachedCurrent | 217 | current = attachedCurrent | ||
| 208 | } | 218 | } | ||
| @@ -877,12 +887,12 @@ func (d *Daemon) switchToTargetGuardedForRole(from *session, ac *attachedClient, | |||||
| 877 | if sourceToken != nil { | 887 | if sourceToken != nil { | ||
| 878 | fresh, admitted := ac.beginRoleEffect(transition.published) | 888 | fresh, admitted := ac.beginRoleEffect(transition.published) | ||
| 879 | if admitted { | 889 | if admitted { | ||
| 880 | - | d.activateTabAfterResizeForLease(from, from.tabForAttachmentOrActive(ac), false, ac, transition.published.lease) | 890 | + | d.activateTabAfterResizeForLease(from, from.tabForAttachment(ac), false, ac, transition.published.lease) |
| 881 | d.invalidateRender(from, ac, true, "picker.go") | 891 | d.invalidateRender(from, ac, true, "picker.go") | ||
| 882 | fresh.End() | 892 | fresh.End() | ||
| 883 | } | 893 | } | ||
| 884 | } else { | 894 | } else { | ||
| 885 | - | d.activateTabAfterResizeForLease(from, from.tabForAttachmentOrActive(ac), false, ac, nil) | 895 | + | d.activateTabAfterResizeForLease(from, from.tabForAttachment(ac), false, ac, nil) |
| 886 | d.invalidateRender(from, ac, true, "picker.go") | 896 | d.invalidateRender(from, ac, true, "picker.go") | ||
| 887 | } | 897 | } | ||
| 888 | } else { | 898 | } else { | ||
M internal/usecase/daemon/picker_test.go +23 -23
| @@ -79,7 +79,7 @@ func TestPickerViewsAddsBellSuffixForAttention(t *testing.T) { | |||
| 79 | 79 | d.sessions[current.id] = current | |
| 80 | 80 | d.sessions[ringing.id] = ringing | |
| 81 | 81 | ||
| 82 | - | views, currentSelection := d.pickerViews(current) | |
| 82 | + | views, currentSelection := d.pickerViews(current, nil) | |
| 83 | 83 | ||
| 84 | 84 | require.Equal(t, picker.SourceFilter{Session: current.id}, currentSelection) | |
| 85 | 85 | require.Len(t, views, 2) | |
| @@ -104,7 +104,7 @@ func TestPickerViewsIncludesEphemeralSessions(t *testing.T) { | |||
| 104 | 104 | d.sessions[current.id] = current | |
| 105 | 105 | d.sessions[ephemeral.id] = ephemeral | |
| 106 | 106 | ||
| 107 | - | views, _ := d.pickerViews(current) | |
| 107 | + | views, _ := d.pickerViews(current, nil) | |
| 108 | 108 | ||
| 109 | 109 | require.Len(t, views, 2) | |
| 110 | 110 | var ephemeralView picker.SessionView | |
| @@ -132,7 +132,7 @@ func TestPickerViewsOrdersByMRUWithEphemeralInterleaved(t *testing.T) { | |||
| 132 | 132 | d.stopped["halted-old"] = stoppedSession{name: "halted-old", createdAt: 10, lastUsedSeq: 1} | |
| 133 | 133 | d.stopped["halted-new"] = stoppedSession{name: "halted-new", createdAt: 11, lastUsedSeq: 2} | |
| 134 | 134 | ||
| 135 | - | views, _ := d.pickerViews(recent) | |
| 135 | + | views, _ := d.pickerViews(recent, nil) | |
| 136 | 136 | ||
| 137 | 137 | names := make([]string, 0, len(views)) | |
| 138 | 138 | for _, v := range views { | |
| @@ -152,7 +152,7 @@ func TestPickerViewsMRUTieBreaksAlphabetically(t *testing.T) { | |||
| 152 | 152 | d.sessions[b.id] = b | |
| 153 | 153 | d.sessions[a.id] = a | |
| 154 | 154 | ||
| 155 | - | views, _ := d.pickerViews(a) | |
| 155 | + | views, _ := d.pickerViews(a, nil) | |
| 156 | 156 | ||
| 157 | 157 | require.Equal(t, "alpha", views[0].Name) | |
| 158 | 158 | require.Equal(t, "bravo", views[1].Name) | |
| @@ -172,7 +172,7 @@ func TestPickerViewsGroupedModePutsNamedBeforeEphemeral(t *testing.T) { | |||
| 172 | 172 | d.sessions[named2.id] = named2 | |
| 173 | 173 | d.stopped["halted"] = stoppedSession{name: "halted", createdAt: 9, lastUsedSeq: 9} | |
| 174 | 174 | ||
| 175 | - | views, _ := d.pickerViews(named) | |
| 175 | + | views, _ := d.pickerViews(named, nil) | |
| 176 | 176 | ||
| 177 | 177 | names := make([]string, 0, len(views)) | |
| 178 | 178 | for _, v := range views { | |
| @@ -283,7 +283,7 @@ func TestPickerViewsCarryNamedLifecycleIdentity(t *testing.T) { | |||
| 283 | 283 | d.sessions[active.id] = active | |
| 284 | 284 | d.stopped["stopped"] = stoppedSession{name: "stopped", createdAt: 24} | |
| 285 | 285 | ||
| 286 | - | views, _ := d.pickerViews(active) | |
| 286 | + | views, _ := d.pickerViews(active, nil) | |
| 287 | 287 | ||
| 288 | 288 | require.Len(t, views, 2) | |
| 289 | 289 | require.NotNil(t, views[0].ExpectedCreatedAt) | |
| @@ -294,7 +294,7 @@ func TestPickerViewsCarryNamedLifecycleIdentity(t *testing.T) { | |||
| 294 | 294 | ||
| 295 | 295 | func TestPickerCanonicalViewsLeaveMoveFilteringToModel(t *testing.T) { | |
| 296 | 296 | d := newTestDaemon(t, nil, stubClock{}) | |
| 297 | - | source := &session{sessionCore: sessionCore{id: "source", name: "source", incarnation: domain.IncarnationID{1}}, active: 0, | |
| 297 | + | source := &session{sessionCore: sessionCore{id: "source", name: "source", incarnation: domain.IncarnationID{1}}, | |
| 298 | 298 | tabs: []*tab{ | |
| 299 | 299 | {stableID: "source-tab", panes: map[layout.PaneID]*pane{}}, | |
| 300 | 300 | {stableID: "sibling-tab", panes: map[layout.PaneID]*pane{}}, | |
| @@ -310,7 +310,7 @@ func TestPickerCanonicalViewsLeaveMoveFilteringToModel(t *testing.T) { | |||
| 310 | 310 | PaneID: "source-pane", | |
| 311 | 311 | } | |
| 312 | 312 | ||
| 313 | - | views, current := d.pickerViews(source) | |
| 313 | + | views, current := d.pickerViews(source, nil) | |
| 314 | 314 | require.Len(t, views, 3, "canonical capture includes active and stopped lifecycles") | |
| 315 | 315 | require.Equal(t, picker.SourceFilter{Session: source.id, Incarnation: source.incarnation, TabID: "source-tab"}, current) | |
| 316 | 316 | var sourceView picker.SessionView | |
| @@ -322,14 +322,14 @@ func TestPickerCanonicalViewsLeaveMoveFilteringToModel(t *testing.T) { | |||
| 322 | 322 | require.Equal(t, []picker.TabEntry{{TabID: "source-tab", Name: "1"}, {TabID: "sibling-tab", Name: "2"}}, sourceView.Tabs, | |
| 323 | 323 | "daemon capture does not remove the move source") | |
| 324 | 324 | ||
| 325 | - | paneModel := d.newPickerModel(source, pickerMovePane, sourceLocator, picker.SourceFilter{ | |
| 325 | + | paneModel := d.newPickerModel(source, nil, pickerMovePane, sourceLocator, picker.SourceFilter{ | |
| 326 | 326 | Session: source.id, Incarnation: source.incarnation, TabID: "sibling-tab", | |
| 327 | 327 | }) | |
| 328 | 328 | paneTarget, ok := paneModel.Selected() | |
| 329 | 329 | require.True(t, ok) | |
| 330 | 330 | require.Equal(t, domain.TabStableID("sibling-tab"), paneTarget.TabID) | |
| 331 | 331 | ||
| 332 | - | tabModel := d.newPickerModel(source, pickerMoveTab, sourceLocator, picker.SourceFilter{}) | |
| 332 | + | tabModel := d.newPickerModel(source, nil, pickerMoveTab, sourceLocator, picker.SourceFilter{}) | |
| 333 | 333 | tabTarget, ok := tabModel.Selected() | |
| 334 | 334 | require.True(t, ok) | |
| 335 | 335 | require.Equal(t, destination.id, tabTarget.Session) | |
| @@ -445,7 +445,7 @@ func TestPickerMoveTabPreviewsDestinationActiveTabWithoutActivatingIt(t *testing | |||
| 445 | 445 | sourceSession.id, sourceSession.name, sourceSession.incarnation = "source", "source", domain.IncarnationID{1} | |
| 446 | 446 | delete(d.sessions, domain.SessionID("manual")) | |
| 447 | 447 | d.sessions[sourceSession.id] = sourceSession | |
| 448 | - | target := &session{sessionCore: sessionCore{id: "destination", name: "destination", incarnation: domain.IncarnationID{2}, ephemeral: true}, active: 1, | |
| 448 | + | target := &session{sessionCore: sessionCore{id: "destination", name: "destination", incarnation: domain.IncarnationID{2}, ephemeral: true}, | |
| 449 | 449 | ctx: sourceSession.ctx, cancel: func() {}, | |
| 450 | 450 | tabs: []*tab{ | |
| 451 | 451 | newTestTabWithContext(p2, sourceSession.ctx, sourceSession.cancel), | |
| @@ -471,7 +471,7 @@ func TestPickerMoveTabPreviewsDestinationActiveTabWithoutActivatingIt(t *testing | |||
| 471 | 471 | require.Equal(t, target.name, selected.Name) | |
| 472 | 472 | require.Equal(t, -1, selected.TabIndex) | |
| 473 | 473 | require.Nil(t, selected.ExpectedCreatedAt) | |
| 474 | - | require.Equal(t, 1, activeTabIndex(target), "preview must not mutate destination focus") | |
| 474 | + | require.Equal(t, 1, testAttachmentTabIndex(target), "preview must not mutate destination focus") | |
| 475 | 475 | d.closePicker(ac) | |
| 476 | 476 | } | |
| 477 | 477 | ||
| @@ -575,7 +575,7 @@ func TestPickerRejectsRecreatedEphemeralTargetFromStaleSelection(t *testing.T) { | |||
| 575 | 575 | original := &session{sessionCore: sessionCore{id: "ephemeral-1", name: "2", ephemeral: true}, ctx: ctx, cancel: cancel, tabs: []*tab{{}}} | |
| 576 | 576 | d.sessions[original.id] = original | |
| 577 | 577 | ||
| 578 | - | views, _ := d.pickerViews(current) | |
| 578 | + | views, _ := d.pickerViews(current, nil) | |
| 579 | 579 | var originalView picker.SessionView | |
| 580 | 580 | for _, view := range views { | |
| 581 | 581 | if view.ID == original.id { | |
| @@ -604,7 +604,7 @@ func TestSameSessionSwitchRejectsReplacedClient(t *testing.T) { | |||
| 604 | 604 | current.registerAttachment(&attachedClient{}) | |
| 605 | 605 | ||
| 606 | 606 | require.Error(t, d.switchToTarget(current, ac, picker.Target{Session: current.id, TabIndex: 1})) | |
| 607 | - | require.Equal(t, 0, current.active) | |
| 607 | + | require.Equal(t, 0, testAttachmentTabIndex(current)) | |
| 608 | 608 | } | |
| 609 | 609 | ||
| 610 | 610 | func TestPickerViewsComposesFocusedPaneTitleWithAttentionSuffix(t *testing.T) { | |
| @@ -626,7 +626,7 @@ func TestPickerViewsComposesFocusedPaneTitleWithAttentionSuffix(t *testing.T) { | |||
| 626 | 626 | sess.tabs[1].attention = true | |
| 627 | 627 | sess.mu.Unlock() | |
| 628 | 628 | ||
| 629 | - | views, _ := d.pickerViews(sess) | |
| 629 | + | views, _ := d.pickerViews(sess, nil) | |
| 630 | 630 | ||
| 631 | 631 | require.Len(t, views, 1) | |
| 632 | 632 | require.Equal(t, []picker.TabEntry{ | |
| @@ -653,7 +653,7 @@ func TestPickerViewsOmitsTerminalTitleWhenTabsConfigDisabled(t *testing.T) { | |||
| 653 | 653 | ||
| 654 | 654 | d.ApplyConfig(domain.Config{Tabs: domain.TabsConfig{TerminalTitle: false}}) | |
| 655 | 655 | ||
| 656 | - | views, _ := d.pickerViews(sess) | |
| 656 | + | views, _ := d.pickerViews(sess, nil) | |
| 657 | 657 | ||
| 658 | 658 | require.Len(t, views, 1) | |
| 659 | 659 | require.Equal(t, []picker.TabEntry{ | |
| @@ -844,8 +844,8 @@ func TestPickerSameSessionNavigationSwitchAndEscClose(t *testing.T) { | |||
| 844 | 844 | awaitFrame(t, sends, ports.MsgOutput) | |
| 845 | 845 | d.handleInput(sess, ac, []byte("\r")) | |
| 846 | 846 | ||
| 847 | - | require.Equal(t, 1, activeTabIndex(sess)) | |
| 848 | - | requireFloatingInitialized(t, sess.activeTab()) | |
| 847 | + | require.Equal(t, 1, testAttachmentTabIndex(sess)) | |
| 848 | + | requireFloatingInitialized(t, testAttachmentTab(sess)) | |
| 849 | 849 | awaitFrame(t, sends, ports.MsgOutput) | |
| 850 | 850 | d.enterPicker(sess, ac) | |
| 851 | 851 | awaitFrame(t, sends, ports.MsgOutput) | |
| @@ -886,7 +886,7 @@ func TestPickerSplitArrowNavigatesWithoutExiting(t *testing.T) { | |||
| 886 | 886 | } | |
| 887 | 887 | require.True(t, ac.overlays.pickerActive()) | |
| 888 | 888 | d.handleInput(sess, ac, []byte("\r")) | |
| 889 | - | require.Equal(t, tc.wantActive, activeTabIndex(sess)) | |
| 889 | + | require.Equal(t, tc.wantActive, testAttachmentTabIndex(sess)) | |
| 890 | 890 | }) | |
| 891 | 891 | } | |
| 892 | 892 | } | |
| @@ -1549,7 +1549,7 @@ func TestResumeStoppedAndSwitchInheritsTerminalEnv(t *testing.T) { | |||
| 1549 | 1549 | require.Contains(t, opens[1], "COLORTERM=truecolor") | |
| 1550 | 1550 | require.Contains(t, opens[1], "TERM_PROGRAM=vev") | |
| 1551 | 1551 | require.Eventually(t, func() bool { | |
| 1552 | - | tb := got.activeTab() | |
| 1552 | + | tb := testAttachmentTab(got) | |
| 1553 | 1553 | if tb == nil { | |
| 1554 | 1554 | return false | |
| 1555 | 1555 | } | |
| @@ -1640,7 +1640,8 @@ func TestPickerNavigationRefreshAfterDeleteSelectsReplacingRow(t *testing.T) { | |||
| 1640 | 1640 | t.Run(tt.name, func(t *testing.T) { | |
| 1641 | 1641 | d, current, ac, _, currentReleases := newManualTabSession(t, 2) | |
| 1642 | 1642 | defer releaseAll(currentReleases) | |
| 1643 | - | current.id, current.name, current.active = "current", tt.currentName, 1 | |
| 1643 | + | current.id, current.name = "current", tt.currentName | |
| 1644 | + | selectTestAttachmentTab(current, 1) | |
| 1644 | 1645 | current.tabs[0].stableID, current.tabs[1].stableID = "current-first", "current-active" | |
| 1645 | 1646 | delete(d.sessions, domain.SessionID("manual")) | |
| 1646 | 1647 | d.sessions[current.id] = current | |
| @@ -1811,8 +1812,7 @@ func TestPickerKillStoppedSessionPersistDeleteFailureSurfacesNoticeAndKeepsEntry | |||
| 1811 | 1812 | require.True(t, stopped.purging, "an uncertain deletion stays fenced from restore") | |
| 1812 | 1813 | ||
| 1813 | 1814 | from.mu.Lock() | |
| 1814 | - | activeTab := from.tabs[from.active] | |
| 1815 | - | activeTabID := activeTab.stableID | |
| 1815 | + | activeTabID := testAttachmentTabLocked(from).stableID | |
| 1816 | 1816 | from.mu.Unlock() | |
| 1817 | 1817 | ac.overlays.pickerMu.Lock() | |
| 1818 | 1818 | selected, selectedOK := ac.overlays.picker.Selected() | |
| @@ -79,7 +79,7 @@ func TestPickerViewsAddsBellSuffixForAttention(t *testing.T) { | |||||
| 79 | d.sessions[current.id] = current | 79 | d.sessions[current.id] = current | ||
| 80 | d.sessions[ringing.id] = ringing | 80 | d.sessions[ringing.id] = ringing | ||
| 81 | 81 | ||||
| 82 | - | views, currentSelection := d.pickerViews(current) | 82 | + | views, currentSelection := d.pickerViews(current, nil) |
| 83 | 83 | ||||
| 84 | require.Equal(t, picker.SourceFilter{Session: current.id}, currentSelection) | 84 | require.Equal(t, picker.SourceFilter{Session: current.id}, currentSelection) | ||
| 85 | require.Len(t, views, 2) | 85 | require.Len(t, views, 2) | ||
| @@ -104,7 +104,7 @@ func TestPickerViewsIncludesEphemeralSessions(t *testing.T) { | |||||
| 104 | d.sessions[current.id] = current | 104 | d.sessions[current.id] = current | ||
| 105 | d.sessions[ephemeral.id] = ephemeral | 105 | d.sessions[ephemeral.id] = ephemeral | ||
| 106 | 106 | ||||
| 107 | - | views, _ := d.pickerViews(current) | 107 | + | views, _ := d.pickerViews(current, nil) |
| 108 | 108 | ||||
| 109 | require.Len(t, views, 2) | 109 | require.Len(t, views, 2) | ||
| 110 | var ephemeralView picker.SessionView | 110 | var ephemeralView picker.SessionView | ||
| @@ -132,7 +132,7 @@ func TestPickerViewsOrdersByMRUWithEphemeralInterleaved(t *testing.T) { | |||||
| 132 | d.stopped["halted-old"] = stoppedSession{name: "halted-old", createdAt: 10, lastUsedSeq: 1} | 132 | d.stopped["halted-old"] = stoppedSession{name: "halted-old", createdAt: 10, lastUsedSeq: 1} | ||
| 133 | d.stopped["halted-new"] = stoppedSession{name: "halted-new", createdAt: 11, lastUsedSeq: 2} | 133 | d.stopped["halted-new"] = stoppedSession{name: "halted-new", createdAt: 11, lastUsedSeq: 2} | ||
| 134 | 134 | ||||
| 135 | - | views, _ := d.pickerViews(recent) | 135 | + | views, _ := d.pickerViews(recent, nil) |
| 136 | 136 | ||||
| 137 | names := make([]string, 0, len(views)) | 137 | names := make([]string, 0, len(views)) | ||
| 138 | for _, v := range views { | 138 | for _, v := range views { | ||
| @@ -152,7 +152,7 @@ func TestPickerViewsMRUTieBreaksAlphabetically(t *testing.T) { | |||||
| 152 | d.sessions[b.id] = b | 152 | d.sessions[b.id] = b | ||
| 153 | d.sessions[a.id] = a | 153 | d.sessions[a.id] = a | ||
| 154 | 154 | ||||
| 155 | - | views, _ := d.pickerViews(a) | 155 | + | views, _ := d.pickerViews(a, nil) |
| 156 | 156 | ||||
| 157 | require.Equal(t, "alpha", views[0].Name) | 157 | require.Equal(t, "alpha", views[0].Name) | ||
| 158 | require.Equal(t, "bravo", views[1].Name) | 158 | require.Equal(t, "bravo", views[1].Name) | ||
| @@ -172,7 +172,7 @@ func TestPickerViewsGroupedModePutsNamedBeforeEphemeral(t *testing.T) { | |||||
| 172 | d.sessions[named2.id] = named2 | 172 | d.sessions[named2.id] = named2 | ||
| 173 | d.stopped["halted"] = stoppedSession{name: "halted", createdAt: 9, lastUsedSeq: 9} | 173 | d.stopped["halted"] = stoppedSession{name: "halted", createdAt: 9, lastUsedSeq: 9} | ||
| 174 | 174 | ||||
| 175 | - | views, _ := d.pickerViews(named) | 175 | + | views, _ := d.pickerViews(named, nil) |
| 176 | 176 | ||||
| 177 | names := make([]string, 0, len(views)) | 177 | names := make([]string, 0, len(views)) | ||
| 178 | for _, v := range views { | 178 | for _, v := range views { | ||
| @@ -283,7 +283,7 @@ func TestPickerViewsCarryNamedLifecycleIdentity(t *testing.T) { | |||||
| 283 | d.sessions[active.id] = active | 283 | d.sessions[active.id] = active | ||
| 284 | d.stopped["stopped"] = stoppedSession{name: "stopped", createdAt: 24} | 284 | d.stopped["stopped"] = stoppedSession{name: "stopped", createdAt: 24} | ||
| 285 | 285 | ||||
| 286 | - | views, _ := d.pickerViews(active) | 286 | + | views, _ := d.pickerViews(active, nil) |
| 287 | 287 | ||||
| 288 | require.Len(t, views, 2) | 288 | require.Len(t, views, 2) | ||
| 289 | require.NotNil(t, views[0].ExpectedCreatedAt) | 289 | require.NotNil(t, views[0].ExpectedCreatedAt) | ||
| @@ -294,7 +294,7 @@ func TestPickerViewsCarryNamedLifecycleIdentity(t *testing.T) { | |||||
| 294 | 294 | ||||
| 295 | func TestPickerCanonicalViewsLeaveMoveFilteringToModel(t *testing.T) { | 295 | func TestPickerCanonicalViewsLeaveMoveFilteringToModel(t *testing.T) { | ||
| 296 | d := newTestDaemon(t, nil, stubClock{}) | 296 | d := newTestDaemon(t, nil, stubClock{}) | ||
| 297 | - | source := &session{sessionCore: sessionCore{id: "source", name: "source", incarnation: domain.IncarnationID{1}}, active: 0, | 297 | + | source := &session{sessionCore: sessionCore{id: "source", name: "source", incarnation: domain.IncarnationID{1}}, |
| 298 | tabs: []*tab{ | 298 | tabs: []*tab{ | ||
| 299 | {stableID: "source-tab", panes: map[layout.PaneID]*pane{}}, | 299 | {stableID: "source-tab", panes: map[layout.PaneID]*pane{}}, | ||
| 300 | {stableID: "sibling-tab", panes: map[layout.PaneID]*pane{}}, | 300 | {stableID: "sibling-tab", panes: map[layout.PaneID]*pane{}}, | ||
| @@ -310,7 +310,7 @@ func TestPickerCanonicalViewsLeaveMoveFilteringToModel(t *testing.T) { | |||||
| 310 | PaneID: "source-pane", | 310 | PaneID: "source-pane", | ||
| 311 | } | 311 | } | ||
| 312 | 312 | ||||
| 313 | - | views, current := d.pickerViews(source) | 313 | + | views, current := d.pickerViews(source, nil) |
| 314 | require.Len(t, views, 3, "canonical capture includes active and stopped lifecycles") | 314 | require.Len(t, views, 3, "canonical capture includes active and stopped lifecycles") | ||
| 315 | require.Equal(t, picker.SourceFilter{Session: source.id, Incarnation: source.incarnation, TabID: "source-tab"}, current) | 315 | require.Equal(t, picker.SourceFilter{Session: source.id, Incarnation: source.incarnation, TabID: "source-tab"}, current) | ||
| 316 | var sourceView picker.SessionView | 316 | var sourceView picker.SessionView | ||
| @@ -322,14 +322,14 @@ func TestPickerCanonicalViewsLeaveMoveFilteringToModel(t *testing.T) { | |||||
| 322 | require.Equal(t, []picker.TabEntry{{TabID: "source-tab", Name: "1"}, {TabID: "sibling-tab", Name: "2"}}, sourceView.Tabs, | 322 | require.Equal(t, []picker.TabEntry{{TabID: "source-tab", Name: "1"}, {TabID: "sibling-tab", Name: "2"}}, sourceView.Tabs, | ||
| 323 | "daemon capture does not remove the move source") | 323 | "daemon capture does not remove the move source") | ||
| 324 | 324 | ||||
| 325 | - | paneModel := d.newPickerModel(source, pickerMovePane, sourceLocator, picker.SourceFilter{ | 325 | + | paneModel := d.newPickerModel(source, nil, pickerMovePane, sourceLocator, picker.SourceFilter{ |
| 326 | Session: source.id, Incarnation: source.incarnation, TabID: "sibling-tab", | 326 | Session: source.id, Incarnation: source.incarnation, TabID: "sibling-tab", | ||
| 327 | }) | 327 | }) | ||
| 328 | paneTarget, ok := paneModel.Selected() | 328 | paneTarget, ok := paneModel.Selected() | ||
| 329 | require.True(t, ok) | 329 | require.True(t, ok) | ||
| 330 | require.Equal(t, domain.TabStableID("sibling-tab"), paneTarget.TabID) | 330 | require.Equal(t, domain.TabStableID("sibling-tab"), paneTarget.TabID) | ||
| 331 | 331 | ||||
| 332 | - | tabModel := d.newPickerModel(source, pickerMoveTab, sourceLocator, picker.SourceFilter{}) | 332 | + | tabModel := d.newPickerModel(source, nil, pickerMoveTab, sourceLocator, picker.SourceFilter{}) |
| 333 | tabTarget, ok := tabModel.Selected() | 333 | tabTarget, ok := tabModel.Selected() | ||
| 334 | require.True(t, ok) | 334 | require.True(t, ok) | ||
| 335 | require.Equal(t, destination.id, tabTarget.Session) | 335 | require.Equal(t, destination.id, tabTarget.Session) | ||
| @@ -445,7 +445,7 @@ func TestPickerMoveTabPreviewsDestinationActiveTabWithoutActivatingIt(t *testing | |||||
| 445 | sourceSession.id, sourceSession.name, sourceSession.incarnation = "source", "source", domain.IncarnationID{1} | 445 | sourceSession.id, sourceSession.name, sourceSession.incarnation = "source", "source", domain.IncarnationID{1} | ||
| 446 | delete(d.sessions, domain.SessionID("manual")) | 446 | delete(d.sessions, domain.SessionID("manual")) | ||
| 447 | d.sessions[sourceSession.id] = sourceSession | 447 | d.sessions[sourceSession.id] = sourceSession | ||
| 448 | - | target := &session{sessionCore: sessionCore{id: "destination", name: "destination", incarnation: domain.IncarnationID{2}, ephemeral: true}, active: 1, | 448 | + | target := &session{sessionCore: sessionCore{id: "destination", name: "destination", incarnation: domain.IncarnationID{2}, ephemeral: true}, |
| 449 | ctx: sourceSession.ctx, cancel: func() {}, | 449 | ctx: sourceSession.ctx, cancel: func() {}, | ||
| 450 | tabs: []*tab{ | 450 | tabs: []*tab{ | ||
| 451 | newTestTabWithContext(p2, sourceSession.ctx, sourceSession.cancel), | 451 | newTestTabWithContext(p2, sourceSession.ctx, sourceSession.cancel), | ||
| @@ -471,7 +471,7 @@ func TestPickerMoveTabPreviewsDestinationActiveTabWithoutActivatingIt(t *testing | |||||
| 471 | require.Equal(t, target.name, selected.Name) | 471 | require.Equal(t, target.name, selected.Name) | ||
| 472 | require.Equal(t, -1, selected.TabIndex) | 472 | require.Equal(t, -1, selected.TabIndex) | ||
| 473 | require.Nil(t, selected.ExpectedCreatedAt) | 473 | require.Nil(t, selected.ExpectedCreatedAt) | ||
| 474 | - | require.Equal(t, 1, activeTabIndex(target), "preview must not mutate destination focus") | 474 | + | require.Equal(t, 1, testAttachmentTabIndex(target), "preview must not mutate destination focus") |
| 475 | d.closePicker(ac) | 475 | d.closePicker(ac) | ||
| 476 | } | 476 | } | ||
| 477 | 477 | ||||
| @@ -575,7 +575,7 @@ func TestPickerRejectsRecreatedEphemeralTargetFromStaleSelection(t *testing.T) { | |||||
| 575 | original := &session{sessionCore: sessionCore{id: "ephemeral-1", name: "2", ephemeral: true}, ctx: ctx, cancel: cancel, tabs: []*tab{{}}} | 575 | original := &session{sessionCore: sessionCore{id: "ephemeral-1", name: "2", ephemeral: true}, ctx: ctx, cancel: cancel, tabs: []*tab{{}}} | ||
| 576 | d.sessions[original.id] = original | 576 | d.sessions[original.id] = original | ||
| 577 | 577 | ||||
| 578 | - | views, _ := d.pickerViews(current) | 578 | + | views, _ := d.pickerViews(current, nil) |
| 579 | var originalView picker.SessionView | 579 | var originalView picker.SessionView | ||
| 580 | for _, view := range views { | 580 | for _, view := range views { | ||
| 581 | if view.ID == original.id { | 581 | if view.ID == original.id { | ||
| @@ -604,7 +604,7 @@ func TestSameSessionSwitchRejectsReplacedClient(t *testing.T) { | |||||
| 604 | current.registerAttachment(&attachedClient{}) | 604 | current.registerAttachment(&attachedClient{}) | ||
| 605 | 605 | ||||
| 606 | require.Error(t, d.switchToTarget(current, ac, picker.Target{Session: current.id, TabIndex: 1})) | 606 | require.Error(t, d.switchToTarget(current, ac, picker.Target{Session: current.id, TabIndex: 1})) | ||
| 607 | - | require.Equal(t, 0, current.active) | 607 | + | require.Equal(t, 0, testAttachmentTabIndex(current)) |
| 608 | } | 608 | } | ||
| 609 | 609 | ||||
| 610 | func TestPickerViewsComposesFocusedPaneTitleWithAttentionSuffix(t *testing.T) { | 610 | func TestPickerViewsComposesFocusedPaneTitleWithAttentionSuffix(t *testing.T) { | ||
| @@ -626,7 +626,7 @@ func TestPickerViewsComposesFocusedPaneTitleWithAttentionSuffix(t *testing.T) { | |||||
| 626 | sess.tabs[1].attention = true | 626 | sess.tabs[1].attention = true | ||
| 627 | sess.mu.Unlock() | 627 | sess.mu.Unlock() | ||
| 628 | 628 | ||||
| 629 | - | views, _ := d.pickerViews(sess) | 629 | + | views, _ := d.pickerViews(sess, nil) |
| 630 | 630 | ||||
| 631 | require.Len(t, views, 1) | 631 | require.Len(t, views, 1) | ||
| 632 | require.Equal(t, []picker.TabEntry{ | 632 | require.Equal(t, []picker.TabEntry{ | ||
| @@ -653,7 +653,7 @@ func TestPickerViewsOmitsTerminalTitleWhenTabsConfigDisabled(t *testing.T) { | |||||
| 653 | 653 | ||||
| 654 | d.ApplyConfig(domain.Config{Tabs: domain.TabsConfig{TerminalTitle: false}}) | 654 | d.ApplyConfig(domain.Config{Tabs: domain.TabsConfig{TerminalTitle: false}}) | ||
| 655 | 655 | ||||
| 656 | - | views, _ := d.pickerViews(sess) | 656 | + | views, _ := d.pickerViews(sess, nil) |
| 657 | 657 | ||||
| 658 | require.Len(t, views, 1) | 658 | require.Len(t, views, 1) | ||
| 659 | require.Equal(t, []picker.TabEntry{ | 659 | require.Equal(t, []picker.TabEntry{ | ||
| @@ -844,8 +844,8 @@ func TestPickerSameSessionNavigationSwitchAndEscClose(t *testing.T) { | |||||
| 844 | awaitFrame(t, sends, ports.MsgOutput) | 844 | awaitFrame(t, sends, ports.MsgOutput) | ||
| 845 | d.handleInput(sess, ac, []byte("\r")) | 845 | d.handleInput(sess, ac, []byte("\r")) | ||
| 846 | 846 | ||||
| 847 | - | require.Equal(t, 1, activeTabIndex(sess)) | 847 | + | require.Equal(t, 1, testAttachmentTabIndex(sess)) |
| 848 | - | requireFloatingInitialized(t, sess.activeTab()) | 848 | + | requireFloatingInitialized(t, testAttachmentTab(sess)) |
| 849 | awaitFrame(t, sends, ports.MsgOutput) | 849 | awaitFrame(t, sends, ports.MsgOutput) | ||
| 850 | d.enterPicker(sess, ac) | 850 | d.enterPicker(sess, ac) | ||
| 851 | awaitFrame(t, sends, ports.MsgOutput) | 851 | awaitFrame(t, sends, ports.MsgOutput) | ||
| @@ -886,7 +886,7 @@ func TestPickerSplitArrowNavigatesWithoutExiting(t *testing.T) { | |||||
| 886 | } | 886 | } | ||
| 887 | require.True(t, ac.overlays.pickerActive()) | 887 | require.True(t, ac.overlays.pickerActive()) | ||
| 888 | d.handleInput(sess, ac, []byte("\r")) | 888 | d.handleInput(sess, ac, []byte("\r")) | ||
| 889 | - | require.Equal(t, tc.wantActive, activeTabIndex(sess)) | 889 | + | require.Equal(t, tc.wantActive, testAttachmentTabIndex(sess)) |
| 890 | }) | 890 | }) | ||
| 891 | } | 891 | } | ||
| 892 | } | 892 | } | ||
| @@ -1549,7 +1549,7 @@ func TestResumeStoppedAndSwitchInheritsTerminalEnv(t *testing.T) { | |||||
| 1549 | require.Contains(t, opens[1], "COLORTERM=truecolor") | 1549 | require.Contains(t, opens[1], "COLORTERM=truecolor") | ||
| 1550 | require.Contains(t, opens[1], "TERM_PROGRAM=vev") | 1550 | require.Contains(t, opens[1], "TERM_PROGRAM=vev") | ||
| 1551 | require.Eventually(t, func() bool { | 1551 | require.Eventually(t, func() bool { | ||
| 1552 | - | tb := got.activeTab() | 1552 | + | tb := testAttachmentTab(got) |
| 1553 | if tb == nil { | 1553 | if tb == nil { | ||
| 1554 | return false | 1554 | return false | ||
| 1555 | } | 1555 | } | ||
| @@ -1640,7 +1640,8 @@ func TestPickerNavigationRefreshAfterDeleteSelectsReplacingRow(t *testing.T) { | |||||
| 1640 | t.Run(tt.name, func(t *testing.T) { | 1640 | t.Run(tt.name, func(t *testing.T) { | ||
| 1641 | d, current, ac, _, currentReleases := newManualTabSession(t, 2) | 1641 | d, current, ac, _, currentReleases := newManualTabSession(t, 2) | ||
| 1642 | defer releaseAll(currentReleases) | 1642 | defer releaseAll(currentReleases) | ||
| 1643 | - | current.id, current.name, current.active = "current", tt.currentName, 1 | 1643 | + | current.id, current.name = "current", tt.currentName |
| 1644 | + | selectTestAttachmentTab(current, 1) | |||
| 1644 | current.tabs[0].stableID, current.tabs[1].stableID = "current-first", "current-active" | 1645 | current.tabs[0].stableID, current.tabs[1].stableID = "current-first", "current-active" | ||
| 1645 | delete(d.sessions, domain.SessionID("manual")) | 1646 | delete(d.sessions, domain.SessionID("manual")) | ||
| 1646 | d.sessions[current.id] = current | 1647 | d.sessions[current.id] = current | ||
| @@ -1811,8 +1812,7 @@ func TestPickerKillStoppedSessionPersistDeleteFailureSurfacesNoticeAndKeepsEntry | |||||
| 1811 | require.True(t, stopped.purging, "an uncertain deletion stays fenced from restore") | 1812 | require.True(t, stopped.purging, "an uncertain deletion stays fenced from restore") | ||
| 1812 | 1813 | ||||
| 1813 | from.mu.Lock() | 1814 | from.mu.Lock() | ||
| 1814 | - | activeTab := from.tabs[from.active] | 1815 | + | activeTabID := testAttachmentTabLocked(from).stableID |
| 1815 | - | activeTabID := activeTab.stableID | |||
| 1816 | from.mu.Unlock() | 1816 | from.mu.Unlock() | ||
| 1817 | ac.overlays.pickerMu.Lock() | 1817 | ac.overlays.pickerMu.Lock() | ||
| 1818 | selected, selectedOK := ac.overlays.picker.Selected() | 1818 | selected, selectedOK := ac.overlays.picker.Selected() | ||
M internal/usecase/daemon/proxied_contract_test.go +8 -8
| @@ -57,9 +57,9 @@ func TestProxiedHandshakeOrdersWelcomeMetadataAndBaseZeroContent(t *testing.T) { | |||
| 57 | 57 | ||
| 58 | 58 | sess := firstSession(d) | |
| 59 | 59 | require.NotNil(t, sess) | |
| 60 | - | sess.activeTab().mu.Lock() | |
| 61 | - | actualContentSize := sess.activeTab().size | |
| 62 | - | sess.activeTab().mu.Unlock() | |
| 60 | + | testAttachmentTab(sess).mu.Lock() | |
| 61 | + | actualContentSize := testAttachmentTab(sess).size | |
| 62 | + | testAttachmentTab(sess).mu.Unlock() | |
| 63 | 63 | require.Equal(t, contentSize, actualContentSize, "proxied geometry must not subtract chrome a second time") | |
| 64 | 64 | ||
| 65 | 65 | release() | |
| @@ -94,9 +94,9 @@ func TestOrdinaryHandshakeRetainsChromeAndNoMetadata(t *testing.T) { | |||
| 94 | 94 | ||
| 95 | 95 | sess := firstSession(d) | |
| 96 | 96 | require.NotNil(t, sess) | |
| 97 | - | sess.activeTab().mu.Lock() | |
| 98 | - | actualContentSize := sess.activeTab().size | |
| 99 | - | sess.activeTab().mu.Unlock() | |
| 97 | + | testAttachmentTab(sess).mu.Lock() | |
| 98 | + | actualContentSize := testAttachmentTab(sess).size | |
| 99 | + | testAttachmentTab(sess).mu.Unlock() | |
| 100 | 100 | require.Equal(t, tabSize(viewport), actualContentSize) | |
| 101 | 101 | requireNoOutputFrame(t, sends) | |
| 102 | 102 | ||
| @@ -143,7 +143,7 @@ func TestProxiedRemotePaintSendsMetadataBeforeScreen(t *testing.T) { | |||
| 143 | 143 | func TestProxiedPaintPublishesAbsoluteVisibleCursorStyle(t *testing.T) { | |
| 144 | 144 | d, sess, ac, sends := newManualSessionWithPTYs(t, newQuietPTY()) | |
| 145 | 145 | ac.proxied = true | |
| 146 | - | pane := sess.activeTab().focusedPane() | |
| 146 | + | pane := testAttachmentTab(sess).focusedPane() | |
| 147 | 147 | pane.mu.Lock() | |
| 148 | 148 | pane.screen.Write([]byte("\x1b[3;6H\x1b[0 q")) | |
| 149 | 149 | pane.mu.Unlock() | |
| @@ -209,7 +209,7 @@ func TestProxiedTransactionalResizeUsesReceivedContentGeometry(t *testing.T) { | |||
| 209 | 209 | ||
| 210 | 210 | want := domain.Size{Cols: 100, Rows: 12} | |
| 211 | 211 | require.True(t, d.requestTransactionalResizeForLease(sess, ac, lease, want, true)) | |
| 212 | - | tb := sess.activeTab() | |
| 212 | + | tb := testAttachmentTab(sess) | |
| 213 | 213 | tb.mu.Lock() | |
| 214 | 214 | require.Equal(t, want, tb.size) | |
| 215 | 215 | tb.mu.Unlock() | |
| @@ -57,9 +57,9 @@ func TestProxiedHandshakeOrdersWelcomeMetadataAndBaseZeroContent(t *testing.T) { | |||||
| 57 | 57 | ||||
| 58 | sess := firstSession(d) | 58 | sess := firstSession(d) | ||
| 59 | require.NotNil(t, sess) | 59 | require.NotNil(t, sess) | ||
| 60 | - | sess.activeTab().mu.Lock() | 60 | + | testAttachmentTab(sess).mu.Lock() |
| 61 | - | actualContentSize := sess.activeTab().size | 61 | + | actualContentSize := testAttachmentTab(sess).size |
| 62 | - | sess.activeTab().mu.Unlock() | 62 | + | testAttachmentTab(sess).mu.Unlock() |
| 63 | require.Equal(t, contentSize, actualContentSize, "proxied geometry must not subtract chrome a second time") | 63 | require.Equal(t, contentSize, actualContentSize, "proxied geometry must not subtract chrome a second time") | ||
| 64 | 64 | ||||
| 65 | release() | 65 | release() | ||
| @@ -94,9 +94,9 @@ func TestOrdinaryHandshakeRetainsChromeAndNoMetadata(t *testing.T) { | |||||
| 94 | 94 | ||||
| 95 | sess := firstSession(d) | 95 | sess := firstSession(d) | ||
| 96 | require.NotNil(t, sess) | 96 | require.NotNil(t, sess) | ||
| 97 | - | sess.activeTab().mu.Lock() | 97 | + | testAttachmentTab(sess).mu.Lock() |
| 98 | - | actualContentSize := sess.activeTab().size | 98 | + | actualContentSize := testAttachmentTab(sess).size |
| 99 | - | sess.activeTab().mu.Unlock() | 99 | + | testAttachmentTab(sess).mu.Unlock() |
| 100 | require.Equal(t, tabSize(viewport), actualContentSize) | 100 | require.Equal(t, tabSize(viewport), actualContentSize) | ||
| 101 | requireNoOutputFrame(t, sends) | 101 | requireNoOutputFrame(t, sends) | ||
| 102 | 102 | ||||
| @@ -143,7 +143,7 @@ func TestProxiedRemotePaintSendsMetadataBeforeScreen(t *testing.T) { | |||||
| 143 | func TestProxiedPaintPublishesAbsoluteVisibleCursorStyle(t *testing.T) { | 143 | func TestProxiedPaintPublishesAbsoluteVisibleCursorStyle(t *testing.T) { | ||
| 144 | d, sess, ac, sends := newManualSessionWithPTYs(t, newQuietPTY()) | 144 | d, sess, ac, sends := newManualSessionWithPTYs(t, newQuietPTY()) | ||
| 145 | ac.proxied = true | 145 | ac.proxied = true | ||
| 146 | - | pane := sess.activeTab().focusedPane() | 146 | + | pane := testAttachmentTab(sess).focusedPane() |
| 147 | pane.mu.Lock() | 147 | pane.mu.Lock() | ||
| 148 | pane.screen.Write([]byte("\x1b[3;6H\x1b[0 q")) | 148 | pane.screen.Write([]byte("\x1b[3;6H\x1b[0 q")) | ||
| 149 | pane.mu.Unlock() | 149 | pane.mu.Unlock() | ||
| @@ -209,7 +209,7 @@ func TestProxiedTransactionalResizeUsesReceivedContentGeometry(t *testing.T) { | |||||
| 209 | 209 | ||||
| 210 | want := domain.Size{Cols: 100, Rows: 12} | 210 | want := domain.Size{Cols: 100, Rows: 12} | ||
| 211 | require.True(t, d.requestTransactionalResizeForLease(sess, ac, lease, want, true)) | 211 | require.True(t, d.requestTransactionalResizeForLease(sess, ac, lease, want, true)) | ||
| 212 | - | tb := sess.activeTab() | 212 | + | tb := testAttachmentTab(sess) |
| 213 | tb.mu.Lock() | 213 | tb.mu.Lock() | ||
| 214 | require.Equal(t, want, tb.size) | 214 | require.Equal(t, want, tb.size) | ||
| 215 | tb.mu.Unlock() | 215 | tb.mu.Unlock() | ||
M internal/usecase/daemon/proxy_input_test.go +9 -9
| @@ -514,7 +514,7 @@ func TestProxyAttachedCommandRejectsTargetsAndExecutesExactActiveSession(t *test | |||
| 514 | 514 | publishTiledPaneOwners(sess, second) | |
| 515 | 515 | sess.mu.Lock() | |
| 516 | 516 | sess.tabs = append(sess.tabs, second) | |
| 517 | - | sess.active = 0 | |
| 517 | + | selectTestAttachmentTabLocked(sess, 0) | |
| 518 | 518 | sess.mu.Unlock() | |
| 519 | 519 | d.attachCoordinator(sess, nil, ac, true) | |
| 520 | 520 | ||
| @@ -532,13 +532,13 @@ func TestProxyAttachedCommandRejectsTargetsAndExecutesExactActiveSession(t *test | |||
| 532 | 532 | result := send(ports.CommandRequest{Version: ports.ProtocolVersion, RequestID: 1, Attached: true, Slug: "next-tab"}) | |
| 533 | 533 | require.True(t, result.OK) | |
| 534 | 534 | sess.mu.Lock() | |
| 535 | - | require.Equal(t, 1, sess.active) | |
| 535 | + | require.Equal(t, 1, testAttachmentTabIndexLocked(sess)) | |
| 536 | 536 | sess.mu.Unlock() | |
| 537 | 537 | ||
| 538 | 538 | result = send(ports.CommandRequest{Version: ports.ProtocolVersion, RequestID: 2, Attached: true, Slug: "previous-tab", TargetSession: "other"}) | |
| 539 | 539 | require.False(t, result.OK) | |
| 540 | 540 | sess.mu.Lock() | |
| 541 | - | require.Equal(t, 1, sess.active, "target override must not execute against any session") | |
| 541 | + | require.Equal(t, 1, testAttachmentTabIndexLocked(sess), "target override must not execute against any session") | |
| 542 | 542 | sess.mu.Unlock() | |
| 543 | 543 | ||
| 544 | 544 | result = send(ports.CommandRequest{Version: ports.ProtocolVersion, RequestID: 3, Attached: true, Slug: "rename-tab"}) | |
| @@ -669,7 +669,7 @@ func TestRemotePickerEnterRoutesStructuredKeyThroughProxyOwnership(t *testing.T) | |||
| 669 | 669 | Sessions: []ports.RemoteCatalogSession{{Name: key.Name, State: "running"}}, | |
| 670 | 670 | }}) | |
| 671 | 671 | ||
| 672 | - | model := d.newPickerModel(local, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{Session: key.ID(), RemoteKey: &key}) | |
| 672 | + | model := d.newPickerModel(local, nil, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{Session: key.ID(), RemoteKey: &key}) | |
| 673 | 673 | d.publishPicker(local, ac, model, pickerNavigate, moveSourceLocator{}) | |
| 674 | 674 | target, selectable := model.Selected() | |
| 675 | 675 | require.True(t, selectable) | |
| @@ -694,7 +694,7 @@ func TestRemotePickerSwitchesFromProxyBackToLocalSession(t *testing.T) { | |||
| 694 | 694 | require.True(t, d.registerSessionLocked(local)) | |
| 695 | 695 | d.mu.Unlock() | |
| 696 | 696 | ||
| 697 | - | model := d.newPickerModel(proxy, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{Session: local.id, TabID: domain.TabStableID(tb.stableID)}) | |
| 697 | + | model := d.newPickerModel(proxy, nil, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{Session: local.id, TabID: domain.TabStableID(tb.stableID)}) | |
| 698 | 698 | d.publishPicker(proxy, ac, model, pickerNavigate, moveSourceLocator{}) | |
| 699 | 699 | target, selectable := model.Selected() | |
| 700 | 700 | require.True(t, selectable) | |
| @@ -734,7 +734,7 @@ func TestRemotePickerRejectsReplacedLocalLifecycle(t *testing.T) { | |||
| 734 | 734 | require.True(t, d.registerSessionLocked(local)) | |
| 735 | 735 | d.mu.Unlock() | |
| 736 | 736 | ||
| 737 | - | model := d.newPickerModel(proxy, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{Session: local.id, TabID: domain.TabStableID(tb.stableID)}) | |
| 737 | + | model := d.newPickerModel(proxy, nil, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{Session: local.id, TabID: domain.TabStableID(tb.stableID)}) | |
| 738 | 738 | target, selectable := model.Selected() | |
| 739 | 739 | require.True(t, selectable) | |
| 740 | 740 | d.afterRoleEffectsFrozen = func() { | |
| @@ -759,7 +759,7 @@ func TestRemotePickerRejectsReplacedLocalIncarnation(t *testing.T) { | |||
| 759 | 759 | require.True(t, d.registerSessionLocked(local)) | |
| 760 | 760 | d.mu.Unlock() | |
| 761 | 761 | ||
| 762 | - | model := d.newPickerModel(proxy, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{Session: local.id, Incarnation: local.incarnation, TabID: domain.TabStableID(tb.stableID)}) | |
| 762 | + | model := d.newPickerModel(proxy, nil, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{Session: local.id, Incarnation: local.incarnation, TabID: domain.TabStableID(tb.stableID)}) | |
| 763 | 763 | target, selectable := model.Selected() | |
| 764 | 764 | require.True(t, selectable) | |
| 765 | 765 | d.afterRoleEffectsFrozen = func() { | |
| @@ -786,7 +786,7 @@ func TestRemotePickerRejectsReplacedLocalTab(t *testing.T) { | |||
| 786 | 786 | require.True(t, d.registerSessionLocked(local)) | |
| 787 | 787 | d.mu.Unlock() | |
| 788 | 788 | ||
| 789 | - | model := d.newPickerModel(proxy, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{Session: local.id, TabID: domain.TabStableID(second.stableID)}) | |
| 789 | + | model := d.newPickerModel(proxy, nil, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{Session: local.id, TabID: domain.TabStableID(second.stableID)}) | |
| 790 | 790 | target, selectable := model.Selected() | |
| 791 | 791 | require.True(t, selectable) | |
| 792 | 792 | require.Equal(t, domain.TabStableID(second.stableID), target.TabID) | |
| @@ -822,7 +822,7 @@ func TestRemotePickerCanReselectWarmProxyAfterReturningLocal(t *testing.T) { | |||
| 822 | 822 | require.NoError(t, d.switchToTargetForRole(proxyToken, picker.Target{Session: local.id, TabIndex: 0}, sessionHandoffGuard{}, "picker-select")) | |
| 823 | 823 | require.Same(t, local, ac.currentSession()) | |
| 824 | 824 | ||
| 825 | - | model := d.newPickerModel(local, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{Session: key.ID(), RemoteKey: &key}) | |
| 825 | + | model := d.newPickerModel(local, nil, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{Session: key.ID(), RemoteKey: &key}) | |
| 826 | 826 | d.publishPicker(local, ac, model, pickerNavigate, moveSourceLocator{}) | |
| 827 | 827 | target, selectable := model.Selected() | |
| 828 | 828 | require.True(t, selectable) | |
| @@ -514,7 +514,7 @@ func TestProxyAttachedCommandRejectsTargetsAndExecutesExactActiveSession(t *test | |||||
| 514 | publishTiledPaneOwners(sess, second) | 514 | publishTiledPaneOwners(sess, second) | ||
| 515 | sess.mu.Lock() | 515 | sess.mu.Lock() | ||
| 516 | sess.tabs = append(sess.tabs, second) | 516 | sess.tabs = append(sess.tabs, second) | ||
| 517 | - | sess.active = 0 | 517 | + | selectTestAttachmentTabLocked(sess, 0) |
| 518 | sess.mu.Unlock() | 518 | sess.mu.Unlock() | ||
| 519 | d.attachCoordinator(sess, nil, ac, true) | 519 | d.attachCoordinator(sess, nil, ac, true) | ||
| 520 | 520 | ||||
| @@ -532,13 +532,13 @@ func TestProxyAttachedCommandRejectsTargetsAndExecutesExactActiveSession(t *test | |||||
| 532 | result := send(ports.CommandRequest{Version: ports.ProtocolVersion, RequestID: 1, Attached: true, Slug: "next-tab"}) | 532 | result := send(ports.CommandRequest{Version: ports.ProtocolVersion, RequestID: 1, Attached: true, Slug: "next-tab"}) | ||
| 533 | require.True(t, result.OK) | 533 | require.True(t, result.OK) | ||
| 534 | sess.mu.Lock() | 534 | sess.mu.Lock() | ||
| 535 | - | require.Equal(t, 1, sess.active) | 535 | + | require.Equal(t, 1, testAttachmentTabIndexLocked(sess)) |
| 536 | sess.mu.Unlock() | 536 | sess.mu.Unlock() | ||
| 537 | 537 | ||||
| 538 | result = send(ports.CommandRequest{Version: ports.ProtocolVersion, RequestID: 2, Attached: true, Slug: "previous-tab", TargetSession: "other"}) | 538 | result = send(ports.CommandRequest{Version: ports.ProtocolVersion, RequestID: 2, Attached: true, Slug: "previous-tab", TargetSession: "other"}) | ||
| 539 | require.False(t, result.OK) | 539 | require.False(t, result.OK) | ||
| 540 | sess.mu.Lock() | 540 | sess.mu.Lock() | ||
| 541 | - | require.Equal(t, 1, sess.active, "target override must not execute against any session") | 541 | + | require.Equal(t, 1, testAttachmentTabIndexLocked(sess), "target override must not execute against any session") |
| 542 | sess.mu.Unlock() | 542 | sess.mu.Unlock() | ||
| 543 | 543 | ||||
| 544 | result = send(ports.CommandRequest{Version: ports.ProtocolVersion, RequestID: 3, Attached: true, Slug: "rename-tab"}) | 544 | result = send(ports.CommandRequest{Version: ports.ProtocolVersion, RequestID: 3, Attached: true, Slug: "rename-tab"}) | ||
| @@ -669,7 +669,7 @@ func TestRemotePickerEnterRoutesStructuredKeyThroughProxyOwnership(t *testing.T) | |||||
| 669 | Sessions: []ports.RemoteCatalogSession{{Name: key.Name, State: "running"}}, | 669 | Sessions: []ports.RemoteCatalogSession{{Name: key.Name, State: "running"}}, | ||
| 670 | }}) | 670 | }}) | ||
| 671 | 671 | ||||
| 672 | - | model := d.newPickerModel(local, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{Session: key.ID(), RemoteKey: &key}) | 672 | + | model := d.newPickerModel(local, nil, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{Session: key.ID(), RemoteKey: &key}) |
| 673 | d.publishPicker(local, ac, model, pickerNavigate, moveSourceLocator{}) | 673 | d.publishPicker(local, ac, model, pickerNavigate, moveSourceLocator{}) | ||
| 674 | target, selectable := model.Selected() | 674 | target, selectable := model.Selected() | ||
| 675 | require.True(t, selectable) | 675 | require.True(t, selectable) | ||
| @@ -694,7 +694,7 @@ func TestRemotePickerSwitchesFromProxyBackToLocalSession(t *testing.T) { | |||||
| 694 | require.True(t, d.registerSessionLocked(local)) | 694 | require.True(t, d.registerSessionLocked(local)) | ||
| 695 | d.mu.Unlock() | 695 | d.mu.Unlock() | ||
| 696 | 696 | ||||
| 697 | - | model := d.newPickerModel(proxy, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{Session: local.id, TabID: domain.TabStableID(tb.stableID)}) | 697 | + | model := d.newPickerModel(proxy, nil, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{Session: local.id, TabID: domain.TabStableID(tb.stableID)}) |
| 698 | d.publishPicker(proxy, ac, model, pickerNavigate, moveSourceLocator{}) | 698 | d.publishPicker(proxy, ac, model, pickerNavigate, moveSourceLocator{}) | ||
| 699 | target, selectable := model.Selected() | 699 | target, selectable := model.Selected() | ||
| 700 | require.True(t, selectable) | 700 | require.True(t, selectable) | ||
| @@ -734,7 +734,7 @@ func TestRemotePickerRejectsReplacedLocalLifecycle(t *testing.T) { | |||||
| 734 | require.True(t, d.registerSessionLocked(local)) | 734 | require.True(t, d.registerSessionLocked(local)) | ||
| 735 | d.mu.Unlock() | 735 | d.mu.Unlock() | ||
| 736 | 736 | ||||
| 737 | - | model := d.newPickerModel(proxy, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{Session: local.id, TabID: domain.TabStableID(tb.stableID)}) | 737 | + | model := d.newPickerModel(proxy, nil, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{Session: local.id, TabID: domain.TabStableID(tb.stableID)}) |
| 738 | target, selectable := model.Selected() | 738 | target, selectable := model.Selected() | ||
| 739 | require.True(t, selectable) | 739 | require.True(t, selectable) | ||
| 740 | d.afterRoleEffectsFrozen = func() { | 740 | d.afterRoleEffectsFrozen = func() { | ||
| @@ -759,7 +759,7 @@ func TestRemotePickerRejectsReplacedLocalIncarnation(t *testing.T) { | |||||
| 759 | require.True(t, d.registerSessionLocked(local)) | 759 | require.True(t, d.registerSessionLocked(local)) | ||
| 760 | d.mu.Unlock() | 760 | d.mu.Unlock() | ||
| 761 | 761 | ||||
| 762 | - | model := d.newPickerModel(proxy, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{Session: local.id, Incarnation: local.incarnation, TabID: domain.TabStableID(tb.stableID)}) | 762 | + | model := d.newPickerModel(proxy, nil, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{Session: local.id, Incarnation: local.incarnation, TabID: domain.TabStableID(tb.stableID)}) |
| 763 | target, selectable := model.Selected() | 763 | target, selectable := model.Selected() | ||
| 764 | require.True(t, selectable) | 764 | require.True(t, selectable) | ||
| 765 | d.afterRoleEffectsFrozen = func() { | 765 | d.afterRoleEffectsFrozen = func() { | ||
| @@ -786,7 +786,7 @@ func TestRemotePickerRejectsReplacedLocalTab(t *testing.T) { | |||||
| 786 | require.True(t, d.registerSessionLocked(local)) | 786 | require.True(t, d.registerSessionLocked(local)) | ||
| 787 | d.mu.Unlock() | 787 | d.mu.Unlock() | ||
| 788 | 788 | ||||
| 789 | - | model := d.newPickerModel(proxy, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{Session: local.id, TabID: domain.TabStableID(second.stableID)}) | 789 | + | model := d.newPickerModel(proxy, nil, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{Session: local.id, TabID: domain.TabStableID(second.stableID)}) |
| 790 | target, selectable := model.Selected() | 790 | target, selectable := model.Selected() | ||
| 791 | require.True(t, selectable) | 791 | require.True(t, selectable) | ||
| 792 | require.Equal(t, domain.TabStableID(second.stableID), target.TabID) | 792 | require.Equal(t, domain.TabStableID(second.stableID), target.TabID) | ||
| @@ -822,7 +822,7 @@ func TestRemotePickerCanReselectWarmProxyAfterReturningLocal(t *testing.T) { | |||||
| 822 | require.NoError(t, d.switchToTargetForRole(proxyToken, picker.Target{Session: local.id, TabIndex: 0}, sessionHandoffGuard{}, "picker-select")) | 822 | require.NoError(t, d.switchToTargetForRole(proxyToken, picker.Target{Session: local.id, TabIndex: 0}, sessionHandoffGuard{}, "picker-select")) | ||
| 823 | require.Same(t, local, ac.currentSession()) | 823 | require.Same(t, local, ac.currentSession()) | ||
| 824 | 824 | ||||
| 825 | - | model := d.newPickerModel(local, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{Session: key.ID(), RemoteKey: &key}) | 825 | + | model := d.newPickerModel(local, nil, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{Session: key.ID(), RemoteKey: &key}) |
| 826 | d.publishPicker(local, ac, model, pickerNavigate, moveSourceLocator{}) | 826 | d.publishPicker(local, ac, model, pickerNavigate, moveSourceLocator{}) | ||
| 827 | target, selectable := model.Selected() | 827 | target, selectable := model.Selected() | ||
| 828 | require.True(t, selectable) | 828 | require.True(t, selectable) | ||
M internal/usecase/daemon/proxy_lifecycle_test.go +2 -2
| @@ -581,7 +581,7 @@ func TestReplacedProxyCannotBeSelectedOrReattachedAfterSwitchAway(t *testing.T) | |||
| 581 | 581 | require.True(t, ok) | |
| 582 | 582 | require.Equal(t, proxyWarmDuration, warmTimer.delay) | |
| 583 | 583 | ||
| 584 | - | views, _ := d.pickerViews(local) | |
| 584 | + | views, _ := d.pickerViews(local, nil) | |
| 585 | 585 | var expiredView picker.SessionView | |
| 586 | 586 | for _, view := range views { | |
| 587 | 587 | if view.RemoteKey != nil && *view.RemoteKey == key { | |
| @@ -597,7 +597,7 @@ func TestReplacedProxyCannotBeSelectedOrReattachedAfterSwitchAway(t *testing.T) | |||
| 597 | 597 | require.Equal(t, key.Display()+" [expired]", proxy.lifecycleDisplayName()) | |
| 598 | 598 | ||
| 599 | 599 | d.enterPicker(local, ac) | |
| 600 | - | forced := d.newPickerModel(local, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{ | |
| 600 | + | forced := d.newPickerModel(local, nil, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{ | |
| 601 | 601 | Session: key.ID(), RemoteKey: &key, | |
| 602 | 602 | }) | |
| 603 | 603 | ac.overlays.pickerMu.Lock() | |
| @@ -581,7 +581,7 @@ func TestReplacedProxyCannotBeSelectedOrReattachedAfterSwitchAway(t *testing.T) | |||||
| 581 | require.True(t, ok) | 581 | require.True(t, ok) | ||
| 582 | require.Equal(t, proxyWarmDuration, warmTimer.delay) | 582 | require.Equal(t, proxyWarmDuration, warmTimer.delay) | ||
| 583 | 583 | ||||
| 584 | - | views, _ := d.pickerViews(local) | 584 | + | views, _ := d.pickerViews(local, nil) |
| 585 | var expiredView picker.SessionView | 585 | var expiredView picker.SessionView | ||
| 586 | for _, view := range views { | 586 | for _, view := range views { | ||
| 587 | if view.RemoteKey != nil && *view.RemoteKey == key { | 587 | if view.RemoteKey != nil && *view.RemoteKey == key { | ||
| @@ -597,7 +597,7 @@ func TestReplacedProxyCannotBeSelectedOrReattachedAfterSwitchAway(t *testing.T) | |||||
| 597 | require.Equal(t, key.Display()+" [expired]", proxy.lifecycleDisplayName()) | 597 | require.Equal(t, key.Display()+" [expired]", proxy.lifecycleDisplayName()) | ||
| 598 | 598 | ||||
| 599 | d.enterPicker(local, ac) | 599 | d.enterPicker(local, ac) | ||
| 600 | - | forced := d.newPickerModel(local, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{ | 600 | + | forced := d.newPickerModel(local, nil, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{ |
| 601 | Session: key.ID(), RemoteKey: &key, | 601 | Session: key.ID(), RemoteKey: &key, | ||
| 602 | }) | 602 | }) | ||
| 603 | ac.overlays.pickerMu.Lock() | 603 | ac.overlays.pickerMu.Lock() | ||
M internal/usecase/daemon/proxy_session.go +2 -2
| @@ -148,7 +148,7 @@ func (p *proxySession) snapshotView(opts viewOptions) sessionView { | |||
| 148 | 148 | expired: expired, | |
| 149 | 149 | createdAt: p.createdAt, | |
| 150 | 150 | mruAt: p.mruAt.Load(), | |
| 151 | - | active: int(meta.Active), | |
| 151 | + | defaultTab: 0, | |
| 152 | 152 | tabCount: len(meta.Tabs), | |
| 153 | 153 | cannotAcceptMoves: true, | |
| 154 | 154 | } | |
| @@ -191,7 +191,7 @@ func (p *proxySession) statusSegments(_ bool) statusSnapshot { | |||
| 191 | 191 | return snapshot | |
| 192 | 192 | } | |
| 193 | 193 | ||
| 194 | - | func (p *proxySession) activateTargetLocked(tabIndex int) bool { | |
| 194 | + | func (p *proxySession) validTargetTabLocked(tabIndex int) bool { | |
| 195 | 195 | if tabIndex < 0 { | |
| 196 | 196 | return true | |
| 197 | 197 | } | |
| @@ -148,7 +148,7 @@ func (p *proxySession) snapshotView(opts viewOptions) sessionView { | |||||
| 148 | expired: expired, | 148 | expired: expired, | ||
| 149 | createdAt: p.createdAt, | 149 | createdAt: p.createdAt, | ||
| 150 | mruAt: p.mruAt.Load(), | 150 | mruAt: p.mruAt.Load(), | ||
| 151 | - | active: int(meta.Active), | 151 | + | defaultTab: 0, |
| 152 | tabCount: len(meta.Tabs), | 152 | tabCount: len(meta.Tabs), | ||
| 153 | cannotAcceptMoves: true, | 153 | cannotAcceptMoves: true, | ||
| 154 | } | 154 | } | ||
| @@ -191,7 +191,7 @@ func (p *proxySession) statusSegments(_ bool) statusSnapshot { | |||||
| 191 | return snapshot | 191 | return snapshot | ||
| 192 | } | 192 | } | ||
| 193 | 193 | ||||
| 194 | - | func (p *proxySession) activateTargetLocked(tabIndex int) bool { | 194 | + | func (p *proxySession) validTargetTabLocked(tabIndex int) bool { |
| 195 | if tabIndex < 0 { | 195 | if tabIndex < 0 { | ||
| 196 | return true | 196 | return true | ||
| 197 | } | 197 | } | ||
M internal/usecase/daemon/remote_picker_test.go +22 -22
| @@ -83,7 +83,7 @@ func TestRemotePickerMerge(t *testing.T) { | |||
| 83 | 83 | d.remoteCatalog.status["learned"] = remoteHostFresh | |
| 84 | 84 | d.remoteCatalog.mu.Unlock() | |
| 85 | 85 | ||
| 86 | - | views, _ := d.pickerViews(local) | |
| 86 | + | views, _ := d.pickerViews(local, nil) | |
| 87 | 87 | ||
| 88 | 88 | require.Equal(t, []string{"local", "work@pinned", "alpha@pinned", "alpha@learned", "stopped"}, pickerViewNames(views)) | |
| 89 | 89 | require.False(t, views[0].Stopped) | |
| @@ -113,7 +113,7 @@ func TestRemotePickerDedupeUsesExactKeyAndKeepsLiveID(t *testing.T) { | |||
| 113 | 113 | }, | |
| 114 | 114 | }}) | |
| 115 | 115 | ||
| 116 | - | views, _ := d.pickerViews(nil) | |
| 116 | + | views, _ := d.pickerViews(nil, nil) | |
| 117 | 117 | ||
| 118 | 118 | require.Len(t, views, 1) | |
| 119 | 119 | require.Equal(t, key.ID(), views[0].ID) | |
| @@ -132,7 +132,7 @@ func TestRemotePickerSnapshotsDoNotRetainProxyLock(t *testing.T) { | |||
| 132 | 132 | } | |
| 133 | 133 | d.sessions[key.ID()] = proxy | |
| 134 | 134 | ||
| 135 | - | views, _ := d.pickerViews(nil) | |
| 135 | + | views, _ := d.pickerViews(nil, nil) | |
| 136 | 136 | ||
| 137 | 137 | require.Len(t, views, 1) | |
| 138 | 138 | require.True(t, proxy.mu.TryLock(), "picker construction must release the proxy leaf lock") | |
| @@ -162,7 +162,7 @@ func TestRemotePickerAvailabilityUsesCachedRowsForFailures(t *testing.T) { | |||
| 162 | 162 | d.remoteCatalog.status["arch"] = test.status | |
| 163 | 163 | d.remoteCatalog.mu.Unlock() | |
| 164 | 164 | ||
| 165 | - | views, _ := d.pickerViews(nil) | |
| 165 | + | views, _ := d.pickerViews(nil, nil) | |
| 166 | 166 | ||
| 167 | 167 | require.Len(t, views, 1) | |
| 168 | 168 | require.Equal(t, test.want, views[0].RemoteAvailability) | |
| @@ -182,7 +182,7 @@ func TestRemotePickerStaleDetailUsesLastSuccessfulFetch(t *testing.T) { | |||
| 182 | 182 | d.remoteCatalog.failure["arch"] = failedAt | |
| 183 | 183 | d.remoteCatalog.mu.Unlock() | |
| 184 | 184 | ||
| 185 | - | views, _ := d.pickerViews(nil) | |
| 185 | + | views, _ := d.pickerViews(nil, nil) | |
| 186 | 186 | ||
| 187 | 187 | require.Len(t, views, 1) | |
| 188 | 188 | require.Equal(t, "stale since "+fetchedAt.Format(time.RFC3339), views[0].RemoteDetail) | |
| @@ -206,7 +206,7 @@ func TestRemotePickerNoCacheHostFailuresRemainVisible(t *testing.T) { | |||
| 206 | 206 | d.remoteCatalog.failure["arch"] = time.Unix(50, 0) | |
| 207 | 207 | d.remoteCatalog.mu.Unlock() | |
| 208 | 208 | ||
| 209 | - | views, _ := d.pickerViews(nil) | |
| 209 | + | views, _ := d.pickerViews(nil, nil) | |
| 210 | 210 | ||
| 211 | 211 | require.Len(t, views, 1) | |
| 212 | 212 | require.Equal(t, "arch", views[0].Name) | |
| @@ -233,7 +233,7 @@ func TestRemotePickerGroupedSortTreatsProxiesAsLiveSessions(t *testing.T) { | |||
| 233 | 233 | d.sessions[proxy.id] = proxy | |
| 234 | 234 | d.sessions[named.id] = named | |
| 235 | 235 | ||
| 236 | - | views, _ := d.pickerViews(nil) | |
| 236 | + | views, _ := d.pickerViews(nil, nil) | |
| 237 | 237 | ||
| 238 | 238 | require.Equal(t, []string{"named", "work@arch", "1"}, pickerViewNames(views)) | |
| 239 | 239 | } | |
| @@ -539,7 +539,7 @@ func addRemoteRefreshPickerOwner(t *testing.T, d *Daemon, id domain.SessionID) ( | |||
| 539 | 539 | ac.initOverlays() | |
| 540 | 540 | tb := newTab(newQuietPTY(), domain.Size{Cols: 80, Rows: 22}) | |
| 541 | 541 | tb.stableID = string(id) + "-tab" | |
| 542 | - | sess := &session{sessionCore: sessionCore{id: id, name: string(id), attachments: map[*attachedClient]struct{}{ac: {}}}, tabs: []*tab{tb}, active: 0} | |
| 542 | + | sess := &session{sessionCore: sessionCore{id: id, name: string(id), attachments: map[*attachedClient]struct{}{ac: {}}}, tabs: []*tab{tb}} | |
| 543 | 543 | publishTiledPaneOwners(sess, tb) | |
| 544 | 544 | ac.setSession(sess) | |
| 545 | 545 | d.sessions[id] = sess | |
| @@ -555,7 +555,7 @@ func TestRemoteRefreshUpdatesAllOpenPickersPreservingSelection(t *testing.T) { | |||
| 555 | 555 | ||
| 556 | 556 | for _, owner := range []*attachedClient{first, second} { | |
| 557 | 557 | owner.overlays.pickerMu.Lock() | |
| 558 | - | owner.overlays.picker = d.newPickerModel(firstSession, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{Session: "second", TabID: "second-tab"}) | |
| 558 | + | owner.overlays.picker = d.newPickerModel(firstSession, nil, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{Session: "second", TabID: "second-tab"}) | |
| 559 | 559 | owner.overlays.pickerIntent = pickerNavigate | |
| 560 | 560 | owner.overlays.pickerGeneration++ | |
| 561 | 561 | instance := remotePickerInstance{ac: owner, generation: owner.overlays.pickerGeneration, model: owner.overlays.picker} | |
| @@ -600,7 +600,7 @@ func TestRemotePickerTeardownLifecycle(t *testing.T) { | |||
| 600 | 600 | d := newRemotePickerDaemon(nil) | |
| 601 | 601 | sess, ac, _ := addRemoteRefreshPickerOwner(t, d, "owner") | |
| 602 | 602 | ac.resumeCapable = test.resumeCapable | |
| 603 | - | model := d.newPickerModel(sess, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{}) | |
| 603 | + | model := d.newPickerModel(sess, nil, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{}) | |
| 604 | 604 | d.publishPicker(sess, ac, model, pickerNavigate, moveSourceLocator{}) | |
| 605 | 605 | ac.overlays.pickerMu.Lock() | |
| 606 | 606 | generation := ac.overlays.pickerGeneration | |
| @@ -639,7 +639,7 @@ func TestParkedPickerExpiryClosesCapturedGenerationAndCancelsRefresh(t *testing. | |||
| 639 | 639 | d.clock = clock | |
| 640 | 640 | sess, ac, _ := addRemoteRefreshPickerOwner(t, d, "owner") | |
| 641 | 641 | ac.resumeCapable = true | |
| 642 | - | d.publishPicker(sess, ac, d.newPickerModel(sess, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{}), pickerNavigate, moveSourceLocator{}) | |
| 642 | + | d.publishPicker(sess, ac, d.newPickerModel(sess, nil, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{}), pickerNavigate, moveSourceLocator{}) | |
| 643 | 643 | request := receiveRemotePicker(t, catalog.requests, "catalog request") | |
| 644 | 644 | ||
| 645 | 645 | require.True(t, d.parkAttachment(sess, ac)) | |
| @@ -659,7 +659,7 @@ func TestParkedPickerRetirementClosesCapturedGenerationOnly(t *testing.T) { | |||
| 659 | 659 | d := newRemotePickerDaemon(nil) | |
| 660 | 660 | sess, ac, _ := addRemoteRefreshPickerOwner(t, d, "owner") | |
| 661 | 661 | ac.resumeCapable = true | |
| 662 | - | d.publishPicker(sess, ac, d.newPickerModel(sess, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{}), pickerNavigate, moveSourceLocator{}) | |
| 662 | + | d.publishPicker(sess, ac, d.newPickerModel(sess, nil, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{}), pickerNavigate, moveSourceLocator{}) | |
| 663 | 663 | d.refreshPickerOpts(ac, pickerRefreshOptions{preserveSelection: true, nearestRow: -1}) | |
| 664 | 664 | ac.overlays.pickerMu.Lock() | |
| 665 | 665 | refreshed := ac.overlays.picker | |
| @@ -686,10 +686,10 @@ func TestParkedPickerTokenReplacementRetiresPreviousGeneration(t *testing.T) { | |||
| 686 | 686 | firstSession, first, _ := addRemoteRefreshPickerOwner(t, d, "first") | |
| 687 | 687 | secondSession, second, _ := addRemoteRefreshPickerOwner(t, d, "second") | |
| 688 | 688 | first.resumeCapable, second.resumeCapable = true, true | |
| 689 | - | d.publishPicker(firstSession, first, d.newPickerModel(firstSession, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{}), pickerNavigate, moveSourceLocator{}) | |
| 689 | + | d.publishPicker(firstSession, first, d.newPickerModel(firstSession, nil, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{}), pickerNavigate, moveSourceLocator{}) | |
| 690 | 690 | require.True(t, d.parkAttachment(firstSession, first)) | |
| 691 | 691 | token := first.resumeToken | |
| 692 | - | d.publishPicker(secondSession, second, d.newPickerModel(secondSession, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{}), pickerNavigate, moveSourceLocator{}) | |
| 692 | + | d.publishPicker(secondSession, second, d.newPickerModel(secondSession, nil, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{}), pickerNavigate, moveSourceLocator{}) | |
| 693 | 693 | second.resumeToken = token | |
| 694 | 694 | require.True(t, d.parkAttachment(secondSession, second)) | |
| 695 | 695 | ||
| @@ -706,7 +706,7 @@ func TestParkedPickerResumePreservesGeneration(t *testing.T) { | |||
| 706 | 706 | sess, ac, _ := addRemoteRefreshPickerOwner(t, d, "owner") | |
| 707 | 707 | ac.resumeCapable = true | |
| 708 | 708 | ac.clientID = [16]byte{1, 2, 3, 4} | |
| 709 | - | d.publishPicker(sess, ac, d.newPickerModel(sess, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{}), pickerNavigate, moveSourceLocator{}) | |
| 709 | + | d.publishPicker(sess, ac, d.newPickerModel(sess, nil, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{}), pickerNavigate, moveSourceLocator{}) | |
| 710 | 710 | ac.overlays.pickerMu.Lock() | |
| 711 | 711 | model, generation := ac.overlays.picker, ac.overlays.pickerGeneration | |
| 712 | 712 | ac.overlays.pickerMu.Unlock() | |
| @@ -728,7 +728,7 @@ func TestStaleParkedPickerExpiryPreservesNewGeneration(t *testing.T) { | |||
| 728 | 728 | d := newRemotePickerDaemon(nil) | |
| 729 | 729 | sess, ac, _ := addRemoteRefreshPickerOwner(t, d, "owner") | |
| 730 | 730 | ac.resumeCapable = true | |
| 731 | - | d.publishPicker(sess, ac, d.newPickerModel(sess, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{}), pickerNavigate, moveSourceLocator{}) | |
| 731 | + | d.publishPicker(sess, ac, d.newPickerModel(sess, nil, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{}), pickerNavigate, moveSourceLocator{}) | |
| 732 | 732 | require.True(t, d.parkAttachment(sess, ac)) | |
| 733 | 733 | token := ac.resumeToken | |
| 734 | 734 | d.mu.Lock() | |
| @@ -736,7 +736,7 @@ func TestStaleParkedPickerExpiryPreservesNewGeneration(t *testing.T) { | |||
| 736 | 736 | d.mu.Unlock() | |
| 737 | 737 | require.NotNil(t, parked) | |
| 738 | 738 | ||
| 739 | - | newer := d.newPickerModel(sess, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{}) | |
| 739 | + | newer := d.newPickerModel(sess, nil, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{}) | |
| 740 | 740 | d.publishPicker(sess, ac, newer, pickerNavigate, moveSourceLocator{}) | |
| 741 | 741 | d.expireParked(token, parked) | |
| 742 | 742 | ac.overlays.pickerMu.Lock() | |
| @@ -752,7 +752,7 @@ func TestRemoteRefreshPreservesRemoteCursorAcrossCacheToProxyUpgrade(t *testing. | |||
| 752 | 752 | Host: "arch", FetchedAt: time.Unix(10, 0), Sessions: []ports.RemoteCatalogSession{{Name: key.Name, State: "running"}}, | |
| 753 | 753 | }}) | |
| 754 | 754 | owner.overlays.pickerMu.Lock() | |
| 755 | - | owner.overlays.picker = d.newPickerModel(ownerSession, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{Session: key.ID(), RemoteKey: &key}) | |
| 755 | + | owner.overlays.picker = d.newPickerModel(ownerSession, nil, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{Session: key.ID(), RemoteKey: &key}) | |
| 756 | 756 | owner.overlays.pickerIntent = pickerNavigate | |
| 757 | 757 | before, ok := owner.overlays.picker.Cursor() | |
| 758 | 758 | _, beforeSelectable := owner.overlays.picker.Selected() | |
| @@ -864,7 +864,7 @@ func TestRemoteRefreshCannotStartAfterLastPickerCloseWins(t *testing.T) { | |||
| 864 | 864 | func TestRemotePickerStaleAfterCloseCannotRemoveReopenedPicker(t *testing.T) { | |
| 865 | 865 | d := newRemotePickerDaemon(nil) | |
| 866 | 866 | sess, ac, _ := addRemoteRefreshPickerOwner(t, d, "owner") | |
| 867 | - | first := d.newPickerModel(sess, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{}) | |
| 867 | + | first := d.newPickerModel(sess, nil, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{}) | |
| 868 | 868 | d.publishPicker(sess, ac, first, pickerNavigate, moveSourceLocator{}) | |
| 869 | 869 | ||
| 870 | 870 | staleClose := d.pickerListInputState(ac) | |
| @@ -872,7 +872,7 @@ func TestRemotePickerStaleAfterCloseCannotRemoveReopenedPicker(t *testing.T) { | |||
| 872 | 872 | staleClose.closeLocked() | |
| 873 | 873 | ac.overlays.pickerMu.Unlock() | |
| 874 | 874 | ||
| 875 | - | reopened := d.newPickerModel(sess, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{}) | |
| 875 | + | reopened := d.newPickerModel(sess, nil, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{}) | |
| 876 | 876 | d.publishPicker(sess, ac, reopened, pickerNavigate, moveSourceLocator{}) | |
| 877 | 877 | staleClose.afterClose() | |
| 878 | 878 | ||
| @@ -888,7 +888,7 @@ func TestRemotePickerStaleAfterCloseCannotRemoveReopenedPicker(t *testing.T) { | |||
| 888 | 888 | func TestRemotePickerStaleRefreshCannotOverwriteReopenedModel(t *testing.T) { | |
| 889 | 889 | d := newRemotePickerDaemon(nil) | |
| 890 | 890 | sess, ac, _ := addRemoteRefreshPickerOwner(t, d, "owner") | |
| 891 | - | first := d.newPickerModel(sess, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{}) | |
| 891 | + | first := d.newPickerModel(sess, nil, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{}) | |
| 892 | 892 | d.publishPicker(sess, ac, first, pickerNavigate, moveSourceLocator{}) | |
| 893 | 893 | ||
| 894 | 894 | rebuildReached := make(chan struct{}) | |
| @@ -906,7 +906,7 @@ func TestRemotePickerStaleRefreshCannotOverwriteReopenedModel(t *testing.T) { | |||
| 906 | 906 | receiveRemotePickerClose(t, rebuildReached, "picker rebuild") | |
| 907 | 907 | ||
| 908 | 908 | d.closePicker(ac) | |
| 909 | - | reopened := d.newPickerModel(sess, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{}) | |
| 909 | + | reopened := d.newPickerModel(sess, nil, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{}) | |
| 910 | 910 | d.publishPicker(sess, ac, reopened, pickerNavigate, moveSourceLocator{}) | |
| 911 | 911 | close(allowPublication) | |
| 912 | 912 | require.NoError(t, receiveRemotePicker(t, publicationWait, "picker publication release")) | |
| @@ -83,7 +83,7 @@ func TestRemotePickerMerge(t *testing.T) { | |||||
| 83 | d.remoteCatalog.status["learned"] = remoteHostFresh | 83 | d.remoteCatalog.status["learned"] = remoteHostFresh | ||
| 84 | d.remoteCatalog.mu.Unlock() | 84 | d.remoteCatalog.mu.Unlock() | ||
| 85 | 85 | ||||
| 86 | - | views, _ := d.pickerViews(local) | 86 | + | views, _ := d.pickerViews(local, nil) |
| 87 | 87 | ||||
| 88 | require.Equal(t, []string{"local", "work@pinned", "alpha@pinned", "alpha@learned", "stopped"}, pickerViewNames(views)) | 88 | require.Equal(t, []string{"local", "work@pinned", "alpha@pinned", "alpha@learned", "stopped"}, pickerViewNames(views)) | ||
| 89 | require.False(t, views[0].Stopped) | 89 | require.False(t, views[0].Stopped) | ||
| @@ -113,7 +113,7 @@ func TestRemotePickerDedupeUsesExactKeyAndKeepsLiveID(t *testing.T) { | |||||
| 113 | }, | 113 | }, | ||
| 114 | }}) | 114 | }}) | ||
| 115 | 115 | ||||
| 116 | - | views, _ := d.pickerViews(nil) | 116 | + | views, _ := d.pickerViews(nil, nil) |
| 117 | 117 | ||||
| 118 | require.Len(t, views, 1) | 118 | require.Len(t, views, 1) | ||
| 119 | require.Equal(t, key.ID(), views[0].ID) | 119 | require.Equal(t, key.ID(), views[0].ID) | ||
| @@ -132,7 +132,7 @@ func TestRemotePickerSnapshotsDoNotRetainProxyLock(t *testing.T) { | |||||
| 132 | } | 132 | } | ||
| 133 | d.sessions[key.ID()] = proxy | 133 | d.sessions[key.ID()] = proxy | ||
| 134 | 134 | ||||
| 135 | - | views, _ := d.pickerViews(nil) | 135 | + | views, _ := d.pickerViews(nil, nil) |
| 136 | 136 | ||||
| 137 | require.Len(t, views, 1) | 137 | require.Len(t, views, 1) | ||
| 138 | require.True(t, proxy.mu.TryLock(), "picker construction must release the proxy leaf lock") | 138 | require.True(t, proxy.mu.TryLock(), "picker construction must release the proxy leaf lock") | ||
| @@ -162,7 +162,7 @@ func TestRemotePickerAvailabilityUsesCachedRowsForFailures(t *testing.T) { | |||||
| 162 | d.remoteCatalog.status["arch"] = test.status | 162 | d.remoteCatalog.status["arch"] = test.status | ||
| 163 | d.remoteCatalog.mu.Unlock() | 163 | d.remoteCatalog.mu.Unlock() | ||
| 164 | 164 | ||||
| 165 | - | views, _ := d.pickerViews(nil) | 165 | + | views, _ := d.pickerViews(nil, nil) |
| 166 | 166 | ||||
| 167 | require.Len(t, views, 1) | 167 | require.Len(t, views, 1) | ||
| 168 | require.Equal(t, test.want, views[0].RemoteAvailability) | 168 | require.Equal(t, test.want, views[0].RemoteAvailability) | ||
| @@ -182,7 +182,7 @@ func TestRemotePickerStaleDetailUsesLastSuccessfulFetch(t *testing.T) { | |||||
| 182 | d.remoteCatalog.failure["arch"] = failedAt | 182 | d.remoteCatalog.failure["arch"] = failedAt | ||
| 183 | d.remoteCatalog.mu.Unlock() | 183 | d.remoteCatalog.mu.Unlock() | ||
| 184 | 184 | ||||
| 185 | - | views, _ := d.pickerViews(nil) | 185 | + | views, _ := d.pickerViews(nil, nil) |
| 186 | 186 | ||||
| 187 | require.Len(t, views, 1) | 187 | require.Len(t, views, 1) | ||
| 188 | require.Equal(t, "stale since "+fetchedAt.Format(time.RFC3339), views[0].RemoteDetail) | 188 | require.Equal(t, "stale since "+fetchedAt.Format(time.RFC3339), views[0].RemoteDetail) | ||
| @@ -206,7 +206,7 @@ func TestRemotePickerNoCacheHostFailuresRemainVisible(t *testing.T) { | |||||
| 206 | d.remoteCatalog.failure["arch"] = time.Unix(50, 0) | 206 | d.remoteCatalog.failure["arch"] = time.Unix(50, 0) | ||
| 207 | d.remoteCatalog.mu.Unlock() | 207 | d.remoteCatalog.mu.Unlock() | ||
| 208 | 208 | ||||
| 209 | - | views, _ := d.pickerViews(nil) | 209 | + | views, _ := d.pickerViews(nil, nil) |
| 210 | 210 | ||||
| 211 | require.Len(t, views, 1) | 211 | require.Len(t, views, 1) | ||
| 212 | require.Equal(t, "arch", views[0].Name) | 212 | require.Equal(t, "arch", views[0].Name) | ||
| @@ -233,7 +233,7 @@ func TestRemotePickerGroupedSortTreatsProxiesAsLiveSessions(t *testing.T) { | |||||
| 233 | d.sessions[proxy.id] = proxy | 233 | d.sessions[proxy.id] = proxy | ||
| 234 | d.sessions[named.id] = named | 234 | d.sessions[named.id] = named | ||
| 235 | 235 | ||||
| 236 | - | views, _ := d.pickerViews(nil) | 236 | + | views, _ := d.pickerViews(nil, nil) |
| 237 | 237 | ||||
| 238 | require.Equal(t, []string{"named", "work@arch", "1"}, pickerViewNames(views)) | 238 | require.Equal(t, []string{"named", "work@arch", "1"}, pickerViewNames(views)) | ||
| 239 | } | 239 | } | ||
| @@ -539,7 +539,7 @@ func addRemoteRefreshPickerOwner(t *testing.T, d *Daemon, id domain.SessionID) ( | |||||
| 539 | ac.initOverlays() | 539 | ac.initOverlays() | ||
| 540 | tb := newTab(newQuietPTY(), domain.Size{Cols: 80, Rows: 22}) | 540 | tb := newTab(newQuietPTY(), domain.Size{Cols: 80, Rows: 22}) | ||
| 541 | tb.stableID = string(id) + "-tab" | 541 | tb.stableID = string(id) + "-tab" | ||
| 542 | - | sess := &session{sessionCore: sessionCore{id: id, name: string(id), attachments: map[*attachedClient]struct{}{ac: {}}}, tabs: []*tab{tb}, active: 0} | 542 | + | sess := &session{sessionCore: sessionCore{id: id, name: string(id), attachments: map[*attachedClient]struct{}{ac: {}}}, tabs: []*tab{tb}} |
| 543 | publishTiledPaneOwners(sess, tb) | 543 | publishTiledPaneOwners(sess, tb) | ||
| 544 | ac.setSession(sess) | 544 | ac.setSession(sess) | ||
| 545 | d.sessions[id] = sess | 545 | d.sessions[id] = sess | ||
| @@ -555,7 +555,7 @@ func TestRemoteRefreshUpdatesAllOpenPickersPreservingSelection(t *testing.T) { | |||||
| 555 | 555 | ||||
| 556 | for _, owner := range []*attachedClient{first, second} { | 556 | for _, owner := range []*attachedClient{first, second} { | ||
| 557 | owner.overlays.pickerMu.Lock() | 557 | owner.overlays.pickerMu.Lock() | ||
| 558 | - | owner.overlays.picker = d.newPickerModel(firstSession, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{Session: "second", TabID: "second-tab"}) | 558 | + | owner.overlays.picker = d.newPickerModel(firstSession, nil, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{Session: "second", TabID: "second-tab"}) |
| 559 | owner.overlays.pickerIntent = pickerNavigate | 559 | owner.overlays.pickerIntent = pickerNavigate | ||
| 560 | owner.overlays.pickerGeneration++ | 560 | owner.overlays.pickerGeneration++ | ||
| 561 | instance := remotePickerInstance{ac: owner, generation: owner.overlays.pickerGeneration, model: owner.overlays.picker} | 561 | instance := remotePickerInstance{ac: owner, generation: owner.overlays.pickerGeneration, model: owner.overlays.picker} | ||
| @@ -600,7 +600,7 @@ func TestRemotePickerTeardownLifecycle(t *testing.T) { | |||||
| 600 | d := newRemotePickerDaemon(nil) | 600 | d := newRemotePickerDaemon(nil) | ||
| 601 | sess, ac, _ := addRemoteRefreshPickerOwner(t, d, "owner") | 601 | sess, ac, _ := addRemoteRefreshPickerOwner(t, d, "owner") | ||
| 602 | ac.resumeCapable = test.resumeCapable | 602 | ac.resumeCapable = test.resumeCapable | ||
| 603 | - | model := d.newPickerModel(sess, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{}) | 603 | + | model := d.newPickerModel(sess, nil, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{}) |
| 604 | d.publishPicker(sess, ac, model, pickerNavigate, moveSourceLocator{}) | 604 | d.publishPicker(sess, ac, model, pickerNavigate, moveSourceLocator{}) | ||
| 605 | ac.overlays.pickerMu.Lock() | 605 | ac.overlays.pickerMu.Lock() | ||
| 606 | generation := ac.overlays.pickerGeneration | 606 | generation := ac.overlays.pickerGeneration | ||
| @@ -639,7 +639,7 @@ func TestParkedPickerExpiryClosesCapturedGenerationAndCancelsRefresh(t *testing. | |||||
| 639 | d.clock = clock | 639 | d.clock = clock | ||
| 640 | sess, ac, _ := addRemoteRefreshPickerOwner(t, d, "owner") | 640 | sess, ac, _ := addRemoteRefreshPickerOwner(t, d, "owner") | ||
| 641 | ac.resumeCapable = true | 641 | ac.resumeCapable = true | ||
| 642 | - | d.publishPicker(sess, ac, d.newPickerModel(sess, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{}), pickerNavigate, moveSourceLocator{}) | 642 | + | d.publishPicker(sess, ac, d.newPickerModel(sess, nil, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{}), pickerNavigate, moveSourceLocator{}) |
| 643 | request := receiveRemotePicker(t, catalog.requests, "catalog request") | 643 | request := receiveRemotePicker(t, catalog.requests, "catalog request") | ||
| 644 | 644 | ||||
| 645 | require.True(t, d.parkAttachment(sess, ac)) | 645 | require.True(t, d.parkAttachment(sess, ac)) | ||
| @@ -659,7 +659,7 @@ func TestParkedPickerRetirementClosesCapturedGenerationOnly(t *testing.T) { | |||||
| 659 | d := newRemotePickerDaemon(nil) | 659 | d := newRemotePickerDaemon(nil) | ||
| 660 | sess, ac, _ := addRemoteRefreshPickerOwner(t, d, "owner") | 660 | sess, ac, _ := addRemoteRefreshPickerOwner(t, d, "owner") | ||
| 661 | ac.resumeCapable = true | 661 | ac.resumeCapable = true | ||
| 662 | - | d.publishPicker(sess, ac, d.newPickerModel(sess, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{}), pickerNavigate, moveSourceLocator{}) | 662 | + | d.publishPicker(sess, ac, d.newPickerModel(sess, nil, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{}), pickerNavigate, moveSourceLocator{}) |
| 663 | d.refreshPickerOpts(ac, pickerRefreshOptions{preserveSelection: true, nearestRow: -1}) | 663 | d.refreshPickerOpts(ac, pickerRefreshOptions{preserveSelection: true, nearestRow: -1}) | ||
| 664 | ac.overlays.pickerMu.Lock() | 664 | ac.overlays.pickerMu.Lock() | ||
| 665 | refreshed := ac.overlays.picker | 665 | refreshed := ac.overlays.picker | ||
| @@ -686,10 +686,10 @@ func TestParkedPickerTokenReplacementRetiresPreviousGeneration(t *testing.T) { | |||||
| 686 | firstSession, first, _ := addRemoteRefreshPickerOwner(t, d, "first") | 686 | firstSession, first, _ := addRemoteRefreshPickerOwner(t, d, "first") | ||
| 687 | secondSession, second, _ := addRemoteRefreshPickerOwner(t, d, "second") | 687 | secondSession, second, _ := addRemoteRefreshPickerOwner(t, d, "second") | ||
| 688 | first.resumeCapable, second.resumeCapable = true, true | 688 | first.resumeCapable, second.resumeCapable = true, true | ||
| 689 | - | d.publishPicker(firstSession, first, d.newPickerModel(firstSession, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{}), pickerNavigate, moveSourceLocator{}) | 689 | + | d.publishPicker(firstSession, first, d.newPickerModel(firstSession, nil, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{}), pickerNavigate, moveSourceLocator{}) |
| 690 | require.True(t, d.parkAttachment(firstSession, first)) | 690 | require.True(t, d.parkAttachment(firstSession, first)) | ||
| 691 | token := first.resumeToken | 691 | token := first.resumeToken | ||
| 692 | - | d.publishPicker(secondSession, second, d.newPickerModel(secondSession, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{}), pickerNavigate, moveSourceLocator{}) | 692 | + | d.publishPicker(secondSession, second, d.newPickerModel(secondSession, nil, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{}), pickerNavigate, moveSourceLocator{}) |
| 693 | second.resumeToken = token | 693 | second.resumeToken = token | ||
| 694 | require.True(t, d.parkAttachment(secondSession, second)) | 694 | require.True(t, d.parkAttachment(secondSession, second)) | ||
| 695 | 695 | ||||
| @@ -706,7 +706,7 @@ func TestParkedPickerResumePreservesGeneration(t *testing.T) { | |||||
| 706 | sess, ac, _ := addRemoteRefreshPickerOwner(t, d, "owner") | 706 | sess, ac, _ := addRemoteRefreshPickerOwner(t, d, "owner") | ||
| 707 | ac.resumeCapable = true | 707 | ac.resumeCapable = true | ||
| 708 | ac.clientID = [16]byte{1, 2, 3, 4} | 708 | ac.clientID = [16]byte{1, 2, 3, 4} | ||
| 709 | - | d.publishPicker(sess, ac, d.newPickerModel(sess, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{}), pickerNavigate, moveSourceLocator{}) | 709 | + | d.publishPicker(sess, ac, d.newPickerModel(sess, nil, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{}), pickerNavigate, moveSourceLocator{}) |
| 710 | ac.overlays.pickerMu.Lock() | 710 | ac.overlays.pickerMu.Lock() | ||
| 711 | model, generation := ac.overlays.picker, ac.overlays.pickerGeneration | 711 | model, generation := ac.overlays.picker, ac.overlays.pickerGeneration | ||
| 712 | ac.overlays.pickerMu.Unlock() | 712 | ac.overlays.pickerMu.Unlock() | ||
| @@ -728,7 +728,7 @@ func TestStaleParkedPickerExpiryPreservesNewGeneration(t *testing.T) { | |||||
| 728 | d := newRemotePickerDaemon(nil) | 728 | d := newRemotePickerDaemon(nil) | ||
| 729 | sess, ac, _ := addRemoteRefreshPickerOwner(t, d, "owner") | 729 | sess, ac, _ := addRemoteRefreshPickerOwner(t, d, "owner") | ||
| 730 | ac.resumeCapable = true | 730 | ac.resumeCapable = true | ||
| 731 | - | d.publishPicker(sess, ac, d.newPickerModel(sess, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{}), pickerNavigate, moveSourceLocator{}) | 731 | + | d.publishPicker(sess, ac, d.newPickerModel(sess, nil, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{}), pickerNavigate, moveSourceLocator{}) |
| 732 | require.True(t, d.parkAttachment(sess, ac)) | 732 | require.True(t, d.parkAttachment(sess, ac)) | ||
| 733 | token := ac.resumeToken | 733 | token := ac.resumeToken | ||
| 734 | d.mu.Lock() | 734 | d.mu.Lock() | ||
| @@ -736,7 +736,7 @@ func TestStaleParkedPickerExpiryPreservesNewGeneration(t *testing.T) { | |||||
| 736 | d.mu.Unlock() | 736 | d.mu.Unlock() | ||
| 737 | require.NotNil(t, parked) | 737 | require.NotNil(t, parked) | ||
| 738 | 738 | ||||
| 739 | - | newer := d.newPickerModel(sess, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{}) | 739 | + | newer := d.newPickerModel(sess, nil, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{}) |
| 740 | d.publishPicker(sess, ac, newer, pickerNavigate, moveSourceLocator{}) | 740 | d.publishPicker(sess, ac, newer, pickerNavigate, moveSourceLocator{}) | ||
| 741 | d.expireParked(token, parked) | 741 | d.expireParked(token, parked) | ||
| 742 | ac.overlays.pickerMu.Lock() | 742 | ac.overlays.pickerMu.Lock() | ||
| @@ -752,7 +752,7 @@ func TestRemoteRefreshPreservesRemoteCursorAcrossCacheToProxyUpgrade(t *testing. | |||||
| 752 | Host: "arch", FetchedAt: time.Unix(10, 0), Sessions: []ports.RemoteCatalogSession{{Name: key.Name, State: "running"}}, | 752 | Host: "arch", FetchedAt: time.Unix(10, 0), Sessions: []ports.RemoteCatalogSession{{Name: key.Name, State: "running"}}, | ||
| 753 | }}) | 753 | }}) | ||
| 754 | owner.overlays.pickerMu.Lock() | 754 | owner.overlays.pickerMu.Lock() | ||
| 755 | - | owner.overlays.picker = d.newPickerModel(ownerSession, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{Session: key.ID(), RemoteKey: &key}) | 755 | + | owner.overlays.picker = d.newPickerModel(ownerSession, nil, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{Session: key.ID(), RemoteKey: &key}) |
| 756 | owner.overlays.pickerIntent = pickerNavigate | 756 | owner.overlays.pickerIntent = pickerNavigate | ||
| 757 | before, ok := owner.overlays.picker.Cursor() | 757 | before, ok := owner.overlays.picker.Cursor() | ||
| 758 | _, beforeSelectable := owner.overlays.picker.Selected() | 758 | _, beforeSelectable := owner.overlays.picker.Selected() | ||
| @@ -864,7 +864,7 @@ func TestRemoteRefreshCannotStartAfterLastPickerCloseWins(t *testing.T) { | |||||
| 864 | func TestRemotePickerStaleAfterCloseCannotRemoveReopenedPicker(t *testing.T) { | 864 | func TestRemotePickerStaleAfterCloseCannotRemoveReopenedPicker(t *testing.T) { | ||
| 865 | d := newRemotePickerDaemon(nil) | 865 | d := newRemotePickerDaemon(nil) | ||
| 866 | sess, ac, _ := addRemoteRefreshPickerOwner(t, d, "owner") | 866 | sess, ac, _ := addRemoteRefreshPickerOwner(t, d, "owner") | ||
| 867 | - | first := d.newPickerModel(sess, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{}) | 867 | + | first := d.newPickerModel(sess, nil, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{}) |
| 868 | d.publishPicker(sess, ac, first, pickerNavigate, moveSourceLocator{}) | 868 | d.publishPicker(sess, ac, first, pickerNavigate, moveSourceLocator{}) | ||
| 869 | 869 | ||||
| 870 | staleClose := d.pickerListInputState(ac) | 870 | staleClose := d.pickerListInputState(ac) | ||
| @@ -872,7 +872,7 @@ func TestRemotePickerStaleAfterCloseCannotRemoveReopenedPicker(t *testing.T) { | |||||
| 872 | staleClose.closeLocked() | 872 | staleClose.closeLocked() | ||
| 873 | ac.overlays.pickerMu.Unlock() | 873 | ac.overlays.pickerMu.Unlock() | ||
| 874 | 874 | ||||
| 875 | - | reopened := d.newPickerModel(sess, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{}) | 875 | + | reopened := d.newPickerModel(sess, nil, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{}) |
| 876 | d.publishPicker(sess, ac, reopened, pickerNavigate, moveSourceLocator{}) | 876 | d.publishPicker(sess, ac, reopened, pickerNavigate, moveSourceLocator{}) | ||
| 877 | staleClose.afterClose() | 877 | staleClose.afterClose() | ||
| 878 | 878 | ||||
| @@ -888,7 +888,7 @@ func TestRemotePickerStaleAfterCloseCannotRemoveReopenedPicker(t *testing.T) { | |||||
| 888 | func TestRemotePickerStaleRefreshCannotOverwriteReopenedModel(t *testing.T) { | 888 | func TestRemotePickerStaleRefreshCannotOverwriteReopenedModel(t *testing.T) { | ||
| 889 | d := newRemotePickerDaemon(nil) | 889 | d := newRemotePickerDaemon(nil) | ||
| 890 | sess, ac, _ := addRemoteRefreshPickerOwner(t, d, "owner") | 890 | sess, ac, _ := addRemoteRefreshPickerOwner(t, d, "owner") | ||
| 891 | - | first := d.newPickerModel(sess, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{}) | 891 | + | first := d.newPickerModel(sess, nil, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{}) |
| 892 | d.publishPicker(sess, ac, first, pickerNavigate, moveSourceLocator{}) | 892 | d.publishPicker(sess, ac, first, pickerNavigate, moveSourceLocator{}) | ||
| 893 | 893 | ||||
| 894 | rebuildReached := make(chan struct{}) | 894 | rebuildReached := make(chan struct{}) | ||
| @@ -906,7 +906,7 @@ func TestRemotePickerStaleRefreshCannotOverwriteReopenedModel(t *testing.T) { | |||||
| 906 | receiveRemotePickerClose(t, rebuildReached, "picker rebuild") | 906 | receiveRemotePickerClose(t, rebuildReached, "picker rebuild") | ||
| 907 | 907 | ||||
| 908 | d.closePicker(ac) | 908 | d.closePicker(ac) | ||
| 909 | - | reopened := d.newPickerModel(sess, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{}) | 909 | + | reopened := d.newPickerModel(sess, nil, pickerNavigate, moveSourceLocator{}, picker.SourceFilter{}) |
| 910 | d.publishPicker(sess, ac, reopened, pickerNavigate, moveSourceLocator{}) | 910 | d.publishPicker(sess, ac, reopened, pickerNavigate, moveSourceLocator{}) | ||
| 911 | close(allowPublication) | 911 | close(allowPublication) | ||
| 912 | require.NoError(t, receiveRemotePicker(t, publicationWait, "picker publication release")) | 912 | require.NoError(t, receiveRemotePicker(t, publicationWait, "picker publication release")) | ||
M internal/usecase/daemon/render.go +1 -1
| @@ -319,7 +319,7 @@ func (d *Daemon) paint(entry attachmentSession, ac *attachedClient, reset bool, | |||
| 319 | 319 | } | |
| 320 | 320 | var tb *tab | |
| 321 | 321 | if local { | |
| 322 | - | tb = sess.tabForAttachmentOrActive(ac) | |
| 322 | + | tb = sess.tabForAttachment(ac) | |
| 323 | 323 | if tb == nil { | |
| 324 | 324 | return | |
| 325 | 325 | } | |
| @@ -319,7 +319,7 @@ func (d *Daemon) paint(entry attachmentSession, ac *attachedClient, reset bool, | |||||
| 319 | } | 319 | } | ||
| 320 | var tb *tab | 320 | var tb *tab | ||
| 321 | if local { | 321 | if local { | ||
| 322 | - | tb = sess.tabForAttachmentOrActive(ac) | 322 | + | tb = sess.tabForAttachment(ac) |
| 323 | if tb == nil { | 323 | if tb == nil { | ||
| 324 | return | 324 | return | ||
| 325 | } | 325 | } | ||
M internal/usecase/daemon/render_capture.go +1 -1
| @@ -219,7 +219,7 @@ func captureLocalPrimaryRenderState( | |||
| 219 | 219 | } | |
| 220 | 220 | tb, _ := sess.paneForAttachment(ac) | |
| 221 | 221 | if tb == nil { | |
| 222 | - | tb = sess.tabForAttachmentOrActive(ac) | |
| 222 | + | tb = sess.tabForAttachment(ac) | |
| 223 | 223 | } | |
| 224 | 224 | if tb == nil { | |
| 225 | 225 | return nil, false | |
| @@ -219,7 +219,7 @@ func captureLocalPrimaryRenderState( | |||||
| 219 | } | 219 | } | ||
| 220 | tb, _ := sess.paneForAttachment(ac) | 220 | tb, _ := sess.paneForAttachment(ac) | ||
| 221 | if tb == nil { | 221 | if tb == nil { | ||
| 222 | - | tb = sess.tabForAttachmentOrActive(ac) | 222 | + | tb = sess.tabForAttachment(ac) |
| 223 | } | 223 | } | ||
| 224 | if tb == nil { | 224 | if tb == nil { | ||
| 225 | return nil, false | 225 | return nil, false | ||
M internal/usecase/daemon/render_coordinator_test.go +3 -3
| @@ -1116,7 +1116,7 @@ func TestProducerInvalidations(t *testing.T) { | |||
| 1116 | 1116 | name: "floating toggle to visible", | |
| 1117 | 1117 | run: func(t *testing.T, d *Daemon, sess *session, ac *attachedClient) { | |
| 1118 | 1118 | fp := newPaneWithStableID(layout.PaneID("floating"), "float-producer", newQuietPTY(), domain.Size{Cols: 40, Rows: 10}) | |
| 1119 | - | tb := sess.activeTab() | |
| 1119 | + | tb := testAttachmentTab(sess) | |
| 1120 | 1120 | tb.mu.Lock() | |
| 1121 | 1121 | tb.floating.pane = fp | |
| 1122 | 1122 | tb.floating.state = floatingHidden | |
| @@ -1134,7 +1134,7 @@ func TestProducerInvalidations(t *testing.T) { | |||
| 1134 | 1134 | ac.proxied = true | |
| 1135 | 1135 | sess.tabs[1].attention = true | |
| 1136 | 1136 | daemonKeyHandler{d: d, ac: ac}.Action(keys.ActionJumpAttention, nil) | |
| 1137 | - | require.Equal(t, 1, activeTabIndex(sess)) | |
| 1137 | + | require.Equal(t, 1, testAttachmentTabIndex(sess)) | |
| 1138 | 1138 | }, | |
| 1139 | 1139 | }, | |
| 1140 | 1140 | { | |
| @@ -1275,7 +1275,7 @@ func TestConcurrentPaintInitializesOverlayUnderSendOwnership(t *testing.T) { | |||
| 1275 | 1275 | func TestStartPaneGoroutinesAccountsForOneReader(t *testing.T) { | |
| 1276 | 1276 | p, release := newBlockingPTY(t) | |
| 1277 | 1277 | d, sess, _, _ := newManualSessionWithPTYs(t, p) | |
| 1278 | - | tb := sess.activeTab() | |
| 1278 | + | tb := testAttachmentTab(sess) | |
| 1279 | 1279 | require.NotNil(t, tb) | |
| 1280 | 1280 | d.startPaneGoroutines(sess, tb, tb.focusedPane()) | |
| 1281 | 1281 | release() | |
| @@ -1116,7 +1116,7 @@ func TestProducerInvalidations(t *testing.T) { | |||||
| 1116 | name: "floating toggle to visible", | 1116 | name: "floating toggle to visible", | ||
| 1117 | run: func(t *testing.T, d *Daemon, sess *session, ac *attachedClient) { | 1117 | run: func(t *testing.T, d *Daemon, sess *session, ac *attachedClient) { | ||
| 1118 | fp := newPaneWithStableID(layout.PaneID("floating"), "float-producer", newQuietPTY(), domain.Size{Cols: 40, Rows: 10}) | 1118 | fp := newPaneWithStableID(layout.PaneID("floating"), "float-producer", newQuietPTY(), domain.Size{Cols: 40, Rows: 10}) | ||
| 1119 | - | tb := sess.activeTab() | 1119 | + | tb := testAttachmentTab(sess) |
| 1120 | tb.mu.Lock() | 1120 | tb.mu.Lock() | ||
| 1121 | tb.floating.pane = fp | 1121 | tb.floating.pane = fp | ||
| 1122 | tb.floating.state = floatingHidden | 1122 | tb.floating.state = floatingHidden | ||
| @@ -1134,7 +1134,7 @@ func TestProducerInvalidations(t *testing.T) { | |||||
| 1134 | ac.proxied = true | 1134 | ac.proxied = true | ||
| 1135 | sess.tabs[1].attention = true | 1135 | sess.tabs[1].attention = true | ||
| 1136 | daemonKeyHandler{d: d, ac: ac}.Action(keys.ActionJumpAttention, nil) | 1136 | daemonKeyHandler{d: d, ac: ac}.Action(keys.ActionJumpAttention, nil) | ||
| 1137 | - | require.Equal(t, 1, activeTabIndex(sess)) | 1137 | + | require.Equal(t, 1, testAttachmentTabIndex(sess)) |
| 1138 | }, | 1138 | }, | ||
| 1139 | }, | 1139 | }, | ||
| 1140 | { | 1140 | { | ||
| @@ -1275,7 +1275,7 @@ func TestConcurrentPaintInitializesOverlayUnderSendOwnership(t *testing.T) { | |||||
| 1275 | func TestStartPaneGoroutinesAccountsForOneReader(t *testing.T) { | 1275 | func TestStartPaneGoroutinesAccountsForOneReader(t *testing.T) { | ||
| 1276 | p, release := newBlockingPTY(t) | 1276 | p, release := newBlockingPTY(t) | ||
| 1277 | d, sess, _, _ := newManualSessionWithPTYs(t, p) | 1277 | d, sess, _, _ := newManualSessionWithPTYs(t, p) | ||
| 1278 | - | tb := sess.activeTab() | 1278 | + | tb := testAttachmentTab(sess) |
| 1279 | require.NotNil(t, tb) | 1279 | require.NotNil(t, tb) | ||
| 1280 | d.startPaneGoroutines(sess, tb, tb.focusedPane()) | 1280 | d.startPaneGoroutines(sess, tb, tb.focusedPane()) | ||
| 1281 | release() | 1281 | release() | ||
M internal/usecase/daemon/render_test.go +11 -11
| @@ -79,7 +79,7 @@ func TestFirstPaintRetainedFloatingPaneEmitsOneReset(t *testing.T) { | |||
| 79 | 79 | // outer terminal also changes, its completed transaction must cover | |
| 80 | 80 | // that popup rather than emitting a second reset-producing frame. | |
| 81 | 81 | floating := newPane(layout.PaneID("floating"), nil, domain.Size{Cols: 20, Rows: 8}) | |
| 82 | - | installTestFloating(sess.activeTab(), floating, true) | |
| 82 | + | installTestFloating(testAttachmentTab(sess), floating, true) | |
| 83 | 83 | ||
| 84 | 84 | d.firstPaint(sess, ac, tc.clientSize) | |
| 85 | 85 | ||
| @@ -321,7 +321,7 @@ func TestPTYReaderSyncVisibilityTransitions(t *testing.T) { | |||
| 321 | 321 | awaitPTYReadProcessed(t, inactiveProcessed) | |
| 322 | 322 | ||
| 323 | 323 | sess.mu.Lock() | |
| 324 | - | sess.active = 1 | |
| 324 | + | selectTestAttachmentTabLocked(sess, 1) | |
| 325 | 325 | sess.mu.Unlock() | |
| 326 | 326 | d.invalidateRender(sess, ac, true, "sync activation") | |
| 327 | 327 | fireCoordinatorTimer(t, rc, drainCoordinatorTimers(clock), urgentRenderDeadline) | |
| @@ -358,7 +358,7 @@ func TestPTYReaderSyncVisibilityTransitions(t *testing.T) { | |||
| 358 | 358 | awaitPTYReadProcessed(t, oldProcessed) | |
| 359 | 359 | ||
| 360 | 360 | sess.mu.Lock() | |
| 361 | - | sess.active = 1 | |
| 361 | + | selectTestAttachmentTabLocked(sess, 1) | |
| 362 | 362 | sess.mu.Unlock() | |
| 363 | 363 | newSteps <- channelPTYStep{data: []byte("newly active")} | |
| 364 | 364 | awaitPTYReadProcessed(t, newProcessed) | |
| @@ -601,7 +601,7 @@ func TestPaneRenderableActiveAttachmentDoesNotScanPickerPreviews(t *testing.T) { | |||
| 601 | 601 | p, release := newBlockingPTY(t) | |
| 602 | 602 | defer release() | |
| 603 | 603 | d, sess, _, _ := newManualSessionWithPTYs(t, p) | |
| 604 | - | tb := sess.activeTab() | |
| 604 | + | tb := testAttachmentTab(sess) | |
| 605 | 605 | pane := tb.focusedPane() | |
| 606 | 606 | ||
| 607 | 607 | // Holding daemon ownership makes a picker scan block. An active attached | |
| @@ -632,7 +632,7 @@ func TestNonRenderablePaneDamageRemainsPendingForCapture(t *testing.T) { | |||
| 632 | 632 | {name: "inactive tab", setup: func(_ *Daemon, sess *session, tb *tab, _ *pane) { | |
| 633 | 633 | other := newTab(nil, domain.Size{Cols: 80, Rows: 23}) | |
| 634 | 634 | sess.tabs = append([]*tab{other}, sess.tabs...) | |
| 635 | - | sess.active = 0 | |
| 635 | + | selectTestAttachmentTab(sess, 0) | |
| 636 | 636 | require.NotSame(t, other, tb) | |
| 637 | 637 | }}, | |
| 638 | 638 | {name: "collapsed", setup: func(_ *Daemon, _ *session, tb *tab, p *pane) { | |
| @@ -719,7 +719,7 @@ func TestPTYEOFClosesActiveNonFinalTabAndRepaintsRemaining(t *testing.T) { | |||
| 719 | 719 | p2, releasePTY2 := newBlockingPTY(t) | |
| 720 | 720 | d, sess, _, sends := newManualSessionWithPTYs(t, p1, p2) | |
| 721 | 721 | defer releasePTY2() | |
| 722 | - | sess.active = 0 | |
| 722 | + | selectTestAttachmentTab(sess, 0) | |
| 723 | 723 | sess.tabs[1].focusedPane().screen.Write([]byte("remaining")) | |
| 724 | 724 | ||
| 725 | 725 | d.sessWg.Add(1) | |
| @@ -728,7 +728,7 @@ func TestPTYEOFClosesActiveNonFinalTabAndRepaintsRemaining(t *testing.T) { | |||
| 728 | 728 | ||
| 729 | 729 | require.Eventually(t, func() bool { return tabCount(sess) == 1 }, 2*time.Second, 5*time.Millisecond) | |
| 730 | 730 | require.Equal(t, 1, sessionCount(d)) | |
| 731 | - | require.Equal(t, 0, activeTabIndex(sess)) | |
| 731 | + | require.Equal(t, 0, testAttachmentTabIndex(sess)) | |
| 732 | 732 | f := awaitFrame(t, sends, ports.MsgOutput) | |
| 733 | 733 | out, err := ports.UnmarshalOutput(f.Payload) | |
| 734 | 734 | require.NoError(t, err) | |
| @@ -745,7 +745,7 @@ func TestPTYEOFClosesInactiveNonFinalTabAndRepaintsStatus(t *testing.T) { | |||
| 745 | 745 | p2, releasePTY2 := newBlockingPTY(t) | |
| 746 | 746 | d, sess, _, sends := newManualSessionWithPTYs(t, p1, p2) | |
| 747 | 747 | defer releasePTY1() | |
| 748 | - | sess.active = 0 | |
| 748 | + | selectTestAttachmentTab(sess, 0) | |
| 749 | 749 | sess.tabs[0].focusedPane().screen.Write([]byte("active")) | |
| 750 | 750 | ||
| 751 | 751 | d.sessWg.Add(1) | |
| @@ -754,7 +754,7 @@ func TestPTYEOFClosesInactiveNonFinalTabAndRepaintsStatus(t *testing.T) { | |||
| 754 | 754 | ||
| 755 | 755 | require.Eventually(t, func() bool { return tabCount(sess) == 1 }, 2*time.Second, 5*time.Millisecond) | |
| 756 | 756 | require.Equal(t, 1, sessionCount(d)) | |
| 757 | - | require.Equal(t, 0, activeTabIndex(sess)) | |
| 757 | + | require.Equal(t, 0, testAttachmentTabIndex(sess)) | |
| 758 | 758 | f := awaitFrame(t, sends, ports.MsgOutput) | |
| 759 | 759 | out, err := ports.UnmarshalOutput(f.Payload) | |
| 760 | 760 | require.NoError(t, err) | |
| @@ -1198,7 +1198,7 @@ func TestPaintAlignsFloatingCursorWithCommittedGeometry(t *testing.T) { | |||
| 1198 | 1198 | floating := newPane("floating", nil, rectSize(committed.Inner)) | |
| 1199 | 1199 | floating.screen.Frame.Set(0, 0, renderer.Cell{Rune: 'F'}) | |
| 1200 | 1200 | floating.popupGeometry = committed | |
| 1201 | - | installTestFloating(sess.activeTab(), floating, true) | |
| 1201 | + | installTestFloating(testAttachmentTab(sess), floating, true) | |
| 1202 | 1202 | ||
| 1203 | 1203 | d.paint(sess, ac, true, nil) | |
| 1204 | 1204 | data := mustOutputData(t, sends) | |
| @@ -1300,7 +1300,7 @@ func TestPaintComposesCopyBodyAboveFloating(t *testing.T) { | |||
| 1300 | 1300 | fp := newPane("floating", floatingPTY, domain.Size{Cols: 20, Rows: 3}) | |
| 1301 | 1301 | appendHistoryRow(t, fp.history, testRow("flt-old")) | |
| 1302 | 1302 | fp.screen.Write([]byte("flt-live")) | |
| 1303 | - | installTestFloating(sess.activeTab(), fp, true) | |
| 1303 | + | installTestFloating(testAttachmentTab(sess), fp, true) | |
| 1304 | 1304 | ||
| 1305 | 1305 | d.enterCopyMode(sess, ac) | |
| 1306 | 1306 | data := mustOutputData(t, sends) | |
| @@ -79,7 +79,7 @@ func TestFirstPaintRetainedFloatingPaneEmitsOneReset(t *testing.T) { | |||||
| 79 | // outer terminal also changes, its completed transaction must cover | 79 | // outer terminal also changes, its completed transaction must cover | ||
| 80 | // that popup rather than emitting a second reset-producing frame. | 80 | // that popup rather than emitting a second reset-producing frame. | ||
| 81 | floating := newPane(layout.PaneID("floating"), nil, domain.Size{Cols: 20, Rows: 8}) | 81 | floating := newPane(layout.PaneID("floating"), nil, domain.Size{Cols: 20, Rows: 8}) | ||
| 82 | - | installTestFloating(sess.activeTab(), floating, true) | 82 | + | installTestFloating(testAttachmentTab(sess), floating, true) |
| 83 | 83 | ||||
| 84 | d.firstPaint(sess, ac, tc.clientSize) | 84 | d.firstPaint(sess, ac, tc.clientSize) | ||
| 85 | 85 | ||||
| @@ -321,7 +321,7 @@ func TestPTYReaderSyncVisibilityTransitions(t *testing.T) { | |||||
| 321 | awaitPTYReadProcessed(t, inactiveProcessed) | 321 | awaitPTYReadProcessed(t, inactiveProcessed) | ||
| 322 | 322 | ||||
| 323 | sess.mu.Lock() | 323 | sess.mu.Lock() | ||
| 324 | - | sess.active = 1 | 324 | + | selectTestAttachmentTabLocked(sess, 1) |
| 325 | sess.mu.Unlock() | 325 | sess.mu.Unlock() | ||
| 326 | d.invalidateRender(sess, ac, true, "sync activation") | 326 | d.invalidateRender(sess, ac, true, "sync activation") | ||
| 327 | fireCoordinatorTimer(t, rc, drainCoordinatorTimers(clock), urgentRenderDeadline) | 327 | fireCoordinatorTimer(t, rc, drainCoordinatorTimers(clock), urgentRenderDeadline) | ||
| @@ -358,7 +358,7 @@ func TestPTYReaderSyncVisibilityTransitions(t *testing.T) { | |||||
| 358 | awaitPTYReadProcessed(t, oldProcessed) | 358 | awaitPTYReadProcessed(t, oldProcessed) | ||
| 359 | 359 | ||||
| 360 | sess.mu.Lock() | 360 | sess.mu.Lock() | ||
| 361 | - | sess.active = 1 | 361 | + | selectTestAttachmentTabLocked(sess, 1) |
| 362 | sess.mu.Unlock() | 362 | sess.mu.Unlock() | ||
| 363 | newSteps <- channelPTYStep{data: []byte("newly active")} | 363 | newSteps <- channelPTYStep{data: []byte("newly active")} | ||
| 364 | awaitPTYReadProcessed(t, newProcessed) | 364 | awaitPTYReadProcessed(t, newProcessed) | ||
| @@ -601,7 +601,7 @@ func TestPaneRenderableActiveAttachmentDoesNotScanPickerPreviews(t *testing.T) { | |||||
| 601 | p, release := newBlockingPTY(t) | 601 | p, release := newBlockingPTY(t) | ||
| 602 | defer release() | 602 | defer release() | ||
| 603 | d, sess, _, _ := newManualSessionWithPTYs(t, p) | 603 | d, sess, _, _ := newManualSessionWithPTYs(t, p) | ||
| 604 | - | tb := sess.activeTab() | 604 | + | tb := testAttachmentTab(sess) |
| 605 | pane := tb.focusedPane() | 605 | pane := tb.focusedPane() | ||
| 606 | 606 | ||||
| 607 | // Holding daemon ownership makes a picker scan block. An active attached | 607 | // Holding daemon ownership makes a picker scan block. An active attached | ||
| @@ -632,7 +632,7 @@ func TestNonRenderablePaneDamageRemainsPendingForCapture(t *testing.T) { | |||||
| 632 | {name: "inactive tab", setup: func(_ *Daemon, sess *session, tb *tab, _ *pane) { | 632 | {name: "inactive tab", setup: func(_ *Daemon, sess *session, tb *tab, _ *pane) { | ||
| 633 | other := newTab(nil, domain.Size{Cols: 80, Rows: 23}) | 633 | other := newTab(nil, domain.Size{Cols: 80, Rows: 23}) | ||
| 634 | sess.tabs = append([]*tab{other}, sess.tabs...) | 634 | sess.tabs = append([]*tab{other}, sess.tabs...) | ||
| 635 | - | sess.active = 0 | 635 | + | selectTestAttachmentTab(sess, 0) |
| 636 | require.NotSame(t, other, tb) | 636 | require.NotSame(t, other, tb) | ||
| 637 | }}, | 637 | }}, | ||
| 638 | {name: "collapsed", setup: func(_ *Daemon, _ *session, tb *tab, p *pane) { | 638 | {name: "collapsed", setup: func(_ *Daemon, _ *session, tb *tab, p *pane) { | ||
| @@ -719,7 +719,7 @@ func TestPTYEOFClosesActiveNonFinalTabAndRepaintsRemaining(t *testing.T) { | |||||
| 719 | p2, releasePTY2 := newBlockingPTY(t) | 719 | p2, releasePTY2 := newBlockingPTY(t) | ||
| 720 | d, sess, _, sends := newManualSessionWithPTYs(t, p1, p2) | 720 | d, sess, _, sends := newManualSessionWithPTYs(t, p1, p2) | ||
| 721 | defer releasePTY2() | 721 | defer releasePTY2() | ||
| 722 | - | sess.active = 0 | 722 | + | selectTestAttachmentTab(sess, 0) |
| 723 | sess.tabs[1].focusedPane().screen.Write([]byte("remaining")) | 723 | sess.tabs[1].focusedPane().screen.Write([]byte("remaining")) | ||
| 724 | 724 | ||||
| 725 | d.sessWg.Add(1) | 725 | d.sessWg.Add(1) | ||
| @@ -728,7 +728,7 @@ func TestPTYEOFClosesActiveNonFinalTabAndRepaintsRemaining(t *testing.T) { | |||||
| 728 | 728 | ||||
| 729 | require.Eventually(t, func() bool { return tabCount(sess) == 1 }, 2*time.Second, 5*time.Millisecond) | 729 | require.Eventually(t, func() bool { return tabCount(sess) == 1 }, 2*time.Second, 5*time.Millisecond) | ||
| 730 | require.Equal(t, 1, sessionCount(d)) | 730 | require.Equal(t, 1, sessionCount(d)) | ||
| 731 | - | require.Equal(t, 0, activeTabIndex(sess)) | 731 | + | require.Equal(t, 0, testAttachmentTabIndex(sess)) |
| 732 | f := awaitFrame(t, sends, ports.MsgOutput) | 732 | f := awaitFrame(t, sends, ports.MsgOutput) | ||
| 733 | out, err := ports.UnmarshalOutput(f.Payload) | 733 | out, err := ports.UnmarshalOutput(f.Payload) | ||
| 734 | require.NoError(t, err) | 734 | require.NoError(t, err) | ||
| @@ -745,7 +745,7 @@ func TestPTYEOFClosesInactiveNonFinalTabAndRepaintsStatus(t *testing.T) { | |||||
| 745 | p2, releasePTY2 := newBlockingPTY(t) | 745 | p2, releasePTY2 := newBlockingPTY(t) | ||
| 746 | d, sess, _, sends := newManualSessionWithPTYs(t, p1, p2) | 746 | d, sess, _, sends := newManualSessionWithPTYs(t, p1, p2) | ||
| 747 | defer releasePTY1() | 747 | defer releasePTY1() | ||
| 748 | - | sess.active = 0 | 748 | + | selectTestAttachmentTab(sess, 0) |
| 749 | sess.tabs[0].focusedPane().screen.Write([]byte("active")) | 749 | sess.tabs[0].focusedPane().screen.Write([]byte("active")) | ||
| 750 | 750 | ||||
| 751 | d.sessWg.Add(1) | 751 | d.sessWg.Add(1) | ||
| @@ -754,7 +754,7 @@ func TestPTYEOFClosesInactiveNonFinalTabAndRepaintsStatus(t *testing.T) { | |||||
| 754 | 754 | ||||
| 755 | require.Eventually(t, func() bool { return tabCount(sess) == 1 }, 2*time.Second, 5*time.Millisecond) | 755 | require.Eventually(t, func() bool { return tabCount(sess) == 1 }, 2*time.Second, 5*time.Millisecond) | ||
| 756 | require.Equal(t, 1, sessionCount(d)) | 756 | require.Equal(t, 1, sessionCount(d)) | ||
| 757 | - | require.Equal(t, 0, activeTabIndex(sess)) | 757 | + | require.Equal(t, 0, testAttachmentTabIndex(sess)) |
| 758 | f := awaitFrame(t, sends, ports.MsgOutput) | 758 | f := awaitFrame(t, sends, ports.MsgOutput) | ||
| 759 | out, err := ports.UnmarshalOutput(f.Payload) | 759 | out, err := ports.UnmarshalOutput(f.Payload) | ||
| 760 | require.NoError(t, err) | 760 | require.NoError(t, err) | ||
| @@ -1198,7 +1198,7 @@ func TestPaintAlignsFloatingCursorWithCommittedGeometry(t *testing.T) { | |||||
| 1198 | floating := newPane("floating", nil, rectSize(committed.Inner)) | 1198 | floating := newPane("floating", nil, rectSize(committed.Inner)) | ||
| 1199 | floating.screen.Frame.Set(0, 0, renderer.Cell{Rune: 'F'}) | 1199 | floating.screen.Frame.Set(0, 0, renderer.Cell{Rune: 'F'}) | ||
| 1200 | floating.popupGeometry = committed | 1200 | floating.popupGeometry = committed | ||
| 1201 | - | installTestFloating(sess.activeTab(), floating, true) | 1201 | + | installTestFloating(testAttachmentTab(sess), floating, true) |
| 1202 | 1202 | ||||
| 1203 | d.paint(sess, ac, true, nil) | 1203 | d.paint(sess, ac, true, nil) | ||
| 1204 | data := mustOutputData(t, sends) | 1204 | data := mustOutputData(t, sends) | ||
| @@ -1300,7 +1300,7 @@ func TestPaintComposesCopyBodyAboveFloating(t *testing.T) { | |||||
| 1300 | fp := newPane("floating", floatingPTY, domain.Size{Cols: 20, Rows: 3}) | 1300 | fp := newPane("floating", floatingPTY, domain.Size{Cols: 20, Rows: 3}) | ||
| 1301 | appendHistoryRow(t, fp.history, testRow("flt-old")) | 1301 | appendHistoryRow(t, fp.history, testRow("flt-old")) | ||
| 1302 | fp.screen.Write([]byte("flt-live")) | 1302 | fp.screen.Write([]byte("flt-live")) | ||
| 1303 | - | installTestFloating(sess.activeTab(), fp, true) | 1303 | + | installTestFloating(testAttachmentTab(sess), fp, true) |
| 1304 | 1304 | ||||
| 1305 | d.enterCopyMode(sess, ac) | 1305 | d.enterCopyMode(sess, ac) | ||
| 1306 | data := mustOutputData(t, sends) | 1306 | data := mustOutputData(t, sends) | ||
M internal/usecase/daemon/resize_mode_test.go +7 -7
| @@ -64,7 +64,7 @@ func TestResizeControlHeadlessAndErrors(t *testing.T) { | |||
| 64 | 64 | require.Equal(t, "pane is not in a split", tooEarly.Text) | |
| 65 | 65 | ||
| 66 | 66 | require.True(t, sendCommand(t, d, ports.CommandRequest{Slug: "split-right", TargetSession: "work"}).OK) | |
| 67 | - | tb := sess.activeTab() | |
| 67 | + | tb := testAttachmentTab(sess) | |
| 68 | 68 | tb.mu.Lock() | |
| 69 | 69 | beforeFocus := tb.tree.Focus | |
| 70 | 70 | beforeGeneration := tb.layoutGeneration | |
| @@ -130,7 +130,7 @@ func TestResizeModeStateRenderingAndMouseConsumption(t *testing.T) { | |||
| 130 | 130 | snapshot.Unlock() | |
| 131 | 131 | require.False(t, (capturedOverlayRenderState{resizeActive: true}).active(), "resize must not create a modal render layer") | |
| 132 | 132 | ||
| 133 | - | tb := sess.activeTab() | |
| 133 | + | tb := testAttachmentTab(sess) | |
| 134 | 134 | tb.mu.Lock() | |
| 135 | 135 | focused := tb.focusedPane() | |
| 136 | 136 | tb.mu.Unlock() | |
| @@ -199,7 +199,7 @@ func TestResizeModeKeysAndEscapes(t *testing.T) { | |||
| 199 | 199 | for _, tt := range tests { | |
| 200 | 200 | t.Run(tt.name, func(t *testing.T) { | |
| 201 | 201 | d, sess, ac := resizeModeFixture(t) | |
| 202 | - | tb := sess.activeTab() | |
| 202 | + | tb := testAttachmentTab(sess) | |
| 203 | 203 | tb.mu.Lock() | |
| 204 | 204 | before := tb.layoutGeneration | |
| 205 | 205 | if tt.equalize { | |
| @@ -229,7 +229,7 @@ func TestResizeModeKeysAndEscapes(t *testing.T) { | |||
| 229 | 229 | ||
| 230 | 230 | func TestResizeModeRendersGuidanceWithoutHidingContentOrCursor(t *testing.T) { | |
| 231 | 231 | d, sess, ac, sends := newManualSessionWithPTYs(t, nil) | |
| 232 | - | tb := sess.activeTab() | |
| 232 | + | tb := testAttachmentTab(sess) | |
| 233 | 233 | second := newPane("pane-2", nil, domain.Size{Cols: 39, Rows: 23}) | |
| 234 | 234 | tb.mu.Lock() | |
| 235 | 235 | tb.tree = &layout.Tree{Root: &layout.Node{Kind: layout.Split, Dir: layout.Horizontal, Children: []*layout.Node{layout.NewLeaf("pane-1"), layout.NewLeaf("pane-2")}}, Focus: "pane-2"} | |
| @@ -296,7 +296,7 @@ func TestResizeModeOverlayPriority(t *testing.T) { | |||
| 296 | 296 | func TestResizeModeWarningPersistsAndActionsShareTransaction(t *testing.T) { | |
| 297 | 297 | t.Run("too small warning leaves mode active", func(t *testing.T) { | |
| 298 | 298 | d, sess, ac := resizeModeFixture(t) | |
| 299 | - | tb := sess.activeTab() | |
| 299 | + | tb := testAttachmentTab(sess) | |
| 300 | 300 | tb.mu.Lock() | |
| 301 | 301 | tb.size.Cols = 41 // two minimum-width panes and their separator | |
| 302 | 302 | tb.bumpLayoutGenerationLocked() | |
| @@ -331,7 +331,7 @@ func TestResizeModeWarningPersistsAndActionsShareTransaction(t *testing.T) { | |||
| 331 | 331 | t.Run(tt.name, func(t *testing.T) { | |
| 332 | 332 | d, sess, ac := resizeModeFixture(t) | |
| 333 | 333 | require.NoError(t, tt.run(d, sess, ac)) | |
| 334 | - | tb := sess.activeTab() | |
| 334 | + | tb := testAttachmentTab(sess) | |
| 335 | 335 | tb.mu.Lock() | |
| 336 | 336 | got := []float64{tb.tree.Root.Children[0].Weight, tb.tree.Root.Children[1].Weight} | |
| 337 | 337 | tb.mu.Unlock() | |
| @@ -372,7 +372,7 @@ func TestResizeModeReleasesTabAndOverlayLocksBeforePTY(t *testing.T) { | |||
| 372 | 372 | // Build a live tree directly so the probe is not concurrently owned by a | |
| 373 | 373 | // pane reader; this isolates the lock boundary being asserted. | |
| 374 | 374 | d, sess, ac, _ := newManualSessionWithPTYs(t, nil) | |
| 375 | - | tb := sess.activeTab() | |
| 375 | + | tb := testAttachmentTab(sess) | |
| 376 | 376 | probe := &resizeLockProbePTY{entered: make(chan struct{}), release: make(chan struct{})} | |
| 377 | 377 | second := newPane("pane-2", probe, domain.Size{Cols: 39, Rows: 23}) | |
| 378 | 378 | tb.mu.Lock() | |
| @@ -64,7 +64,7 @@ func TestResizeControlHeadlessAndErrors(t *testing.T) { | |||||
| 64 | require.Equal(t, "pane is not in a split", tooEarly.Text) | 64 | require.Equal(t, "pane is not in a split", tooEarly.Text) | ||
| 65 | 65 | ||||
| 66 | require.True(t, sendCommand(t, d, ports.CommandRequest{Slug: "split-right", TargetSession: "work"}).OK) | 66 | require.True(t, sendCommand(t, d, ports.CommandRequest{Slug: "split-right", TargetSession: "work"}).OK) | ||
| 67 | - | tb := sess.activeTab() | 67 | + | tb := testAttachmentTab(sess) |
| 68 | tb.mu.Lock() | 68 | tb.mu.Lock() | ||
| 69 | beforeFocus := tb.tree.Focus | 69 | beforeFocus := tb.tree.Focus | ||
| 70 | beforeGeneration := tb.layoutGeneration | 70 | beforeGeneration := tb.layoutGeneration | ||
| @@ -130,7 +130,7 @@ func TestResizeModeStateRenderingAndMouseConsumption(t *testing.T) { | |||||
| 130 | snapshot.Unlock() | 130 | snapshot.Unlock() | ||
| 131 | require.False(t, (capturedOverlayRenderState{resizeActive: true}).active(), "resize must not create a modal render layer") | 131 | require.False(t, (capturedOverlayRenderState{resizeActive: true}).active(), "resize must not create a modal render layer") | ||
| 132 | 132 | ||||
| 133 | - | tb := sess.activeTab() | 133 | + | tb := testAttachmentTab(sess) |
| 134 | tb.mu.Lock() | 134 | tb.mu.Lock() | ||
| 135 | focused := tb.focusedPane() | 135 | focused := tb.focusedPane() | ||
| 136 | tb.mu.Unlock() | 136 | tb.mu.Unlock() | ||
| @@ -199,7 +199,7 @@ func TestResizeModeKeysAndEscapes(t *testing.T) { | |||||
| 199 | for _, tt := range tests { | 199 | for _, tt := range tests { | ||
| 200 | t.Run(tt.name, func(t *testing.T) { | 200 | t.Run(tt.name, func(t *testing.T) { | ||
| 201 | d, sess, ac := resizeModeFixture(t) | 201 | d, sess, ac := resizeModeFixture(t) | ||
| 202 | - | tb := sess.activeTab() | 202 | + | tb := testAttachmentTab(sess) |
| 203 | tb.mu.Lock() | 203 | tb.mu.Lock() | ||
| 204 | before := tb.layoutGeneration | 204 | before := tb.layoutGeneration | ||
| 205 | if tt.equalize { | 205 | if tt.equalize { | ||
| @@ -229,7 +229,7 @@ func TestResizeModeKeysAndEscapes(t *testing.T) { | |||||
| 229 | 229 | ||||
| 230 | func TestResizeModeRendersGuidanceWithoutHidingContentOrCursor(t *testing.T) { | 230 | func TestResizeModeRendersGuidanceWithoutHidingContentOrCursor(t *testing.T) { | ||
| 231 | d, sess, ac, sends := newManualSessionWithPTYs(t, nil) | 231 | d, sess, ac, sends := newManualSessionWithPTYs(t, nil) | ||
| 232 | - | tb := sess.activeTab() | 232 | + | tb := testAttachmentTab(sess) |
| 233 | second := newPane("pane-2", nil, domain.Size{Cols: 39, Rows: 23}) | 233 | second := newPane("pane-2", nil, domain.Size{Cols: 39, Rows: 23}) | ||
| 234 | tb.mu.Lock() | 234 | tb.mu.Lock() | ||
| 235 | tb.tree = &layout.Tree{Root: &layout.Node{Kind: layout.Split, Dir: layout.Horizontal, Children: []*layout.Node{layout.NewLeaf("pane-1"), layout.NewLeaf("pane-2")}}, Focus: "pane-2"} | 235 | tb.tree = &layout.Tree{Root: &layout.Node{Kind: layout.Split, Dir: layout.Horizontal, Children: []*layout.Node{layout.NewLeaf("pane-1"), layout.NewLeaf("pane-2")}}, Focus: "pane-2"} | ||
| @@ -296,7 +296,7 @@ func TestResizeModeOverlayPriority(t *testing.T) { | |||||
| 296 | func TestResizeModeWarningPersistsAndActionsShareTransaction(t *testing.T) { | 296 | func TestResizeModeWarningPersistsAndActionsShareTransaction(t *testing.T) { | ||
| 297 | t.Run("too small warning leaves mode active", func(t *testing.T) { | 297 | t.Run("too small warning leaves mode active", func(t *testing.T) { | ||
| 298 | d, sess, ac := resizeModeFixture(t) | 298 | d, sess, ac := resizeModeFixture(t) | ||
| 299 | - | tb := sess.activeTab() | 299 | + | tb := testAttachmentTab(sess) |
| 300 | tb.mu.Lock() | 300 | tb.mu.Lock() | ||
| 301 | tb.size.Cols = 41 // two minimum-width panes and their separator | 301 | tb.size.Cols = 41 // two minimum-width panes and their separator | ||
| 302 | tb.bumpLayoutGenerationLocked() | 302 | tb.bumpLayoutGenerationLocked() | ||
| @@ -331,7 +331,7 @@ func TestResizeModeWarningPersistsAndActionsShareTransaction(t *testing.T) { | |||||
| 331 | t.Run(tt.name, func(t *testing.T) { | 331 | t.Run(tt.name, func(t *testing.T) { | ||
| 332 | d, sess, ac := resizeModeFixture(t) | 332 | d, sess, ac := resizeModeFixture(t) | ||
| 333 | require.NoError(t, tt.run(d, sess, ac)) | 333 | require.NoError(t, tt.run(d, sess, ac)) | ||
| 334 | - | tb := sess.activeTab() | 334 | + | tb := testAttachmentTab(sess) |
| 335 | tb.mu.Lock() | 335 | tb.mu.Lock() | ||
| 336 | got := []float64{tb.tree.Root.Children[0].Weight, tb.tree.Root.Children[1].Weight} | 336 | got := []float64{tb.tree.Root.Children[0].Weight, tb.tree.Root.Children[1].Weight} | ||
| 337 | tb.mu.Unlock() | 337 | tb.mu.Unlock() | ||
| @@ -372,7 +372,7 @@ func TestResizeModeReleasesTabAndOverlayLocksBeforePTY(t *testing.T) { | |||||
| 372 | // Build a live tree directly so the probe is not concurrently owned by a | 372 | // Build a live tree directly so the probe is not concurrently owned by a | ||
| 373 | // pane reader; this isolates the lock boundary being asserted. | 373 | // pane reader; this isolates the lock boundary being asserted. | ||
| 374 | d, sess, ac, _ := newManualSessionWithPTYs(t, nil) | 374 | d, sess, ac, _ := newManualSessionWithPTYs(t, nil) | ||
| 375 | - | tb := sess.activeTab() | 375 | + | tb := testAttachmentTab(sess) |
| 376 | probe := &resizeLockProbePTY{entered: make(chan struct{}), release: make(chan struct{})} | 376 | probe := &resizeLockProbePTY{entered: make(chan struct{}), release: make(chan struct{})} | ||
| 377 | second := newPane("pane-2", probe, domain.Size{Cols: 39, Rows: 23}) | 377 | second := newPane("pane-2", probe, domain.Size{Cols: 39, Rows: 23}) | ||
| 378 | tb.mu.Lock() | 378 | tb.mu.Lock() | ||
M internal/usecase/daemon/role_effect_gate_test.go +5 -5
| @@ -626,7 +626,7 @@ func TestRoleEffectGateAdmittedActiveEffectsFinishBeforeReplacement(t *testing.T | |||
| 626 | 626 | { | |
| 627 | 627 | name: "mouse input", | |
| 628 | 628 | setup: func(_ *Daemon, sess *session, _ *attachedClient, _ *transactionalResizePTY) { | |
| 629 | - | p := sess.activeTab().focusedPane() | |
| 629 | + | p := testAttachmentTab(sess).focusedPane() | |
| 630 | 630 | p.mu.Lock() | |
| 631 | 631 | p.screen.Write([]byte("\x1b[?1000h\x1b[?1006h")) | |
| 632 | 632 | p.mu.Unlock() | |
| @@ -769,12 +769,12 @@ func TestJumpAttentionDoesNotMutateSourceAfterInitiatorReplacement(t *testing.T) | |||
| 769 | 769 | }) | |
| 770 | 770 | require.NoError(t, err) | |
| 771 | 771 | sess.mu.Lock() | |
| 772 | - | sess.active = 0 | |
| 772 | + | selectTestAttachmentTabLocked(sess, 0) | |
| 773 | 773 | sess.mu.Unlock() | |
| 774 | 774 | release() | |
| 775 | 775 | <-actionDone | |
| 776 | 776 | ||
| 777 | - | require.Equal(t, 0, activeTabIndex(sess), "the replaced initiator mutated source focus after losing its role") | |
| 777 | + | require.Equal(t, 0, testAttachmentTabIndex(sess), "the replaced initiator mutated source focus after losing its role") | |
| 778 | 778 | d.deferAttachmentTransitionCleanups(result) | |
| 779 | 779 | } | |
| 780 | 780 | ||
| @@ -802,7 +802,7 @@ func TestJumpAttentionAdmittedHandoffCrossesSessions(t *testing.T) { | |||
| 802 | 802 | d.handleActiveClientFrame(token, frameInput([]byte("\x1ba"))) | |
| 803 | 803 | ||
| 804 | 804 | require.Same(t, target, ac.currentSession()) | |
| 805 | - | require.Equal(t, 1, activeTabIndex(target)) | |
| 805 | + | require.Equal(t, 1, testAttachmentTabIndex(target)) | |
| 806 | 806 | } | |
| 807 | 807 | ||
| 808 | 808 | func TestJumpAttentionHandoffRevalidatesInitiatorAfterAdmissionEnds(t *testing.T) { | |
| @@ -854,7 +854,7 @@ func TestJumpAttentionHandoffRevalidatesInitiatorAfterAdmissionEnds(t *testing.T | |||
| 854 | 854 | release() | |
| 855 | 855 | <-actionDone | |
| 856 | 856 | ||
| 857 | - | require.Equal(t, 0, activeTabIndex(target), "stale handoff changed target focus") | |
| 857 | + | require.Equal(t, 0, testAttachmentTabIndex(target), "stale handoff changed target focus") | |
| 858 | 858 | target.mu.Lock() | |
| 859 | 859 | targetAttachments := target.snapshotAttachmentsLocked() | |
| 860 | 860 | target.mu.Unlock() | |
| @@ -626,7 +626,7 @@ func TestRoleEffectGateAdmittedActiveEffectsFinishBeforeReplacement(t *testing.T | |||||
| 626 | { | 626 | { | ||
| 627 | name: "mouse input", | 627 | name: "mouse input", | ||
| 628 | setup: func(_ *Daemon, sess *session, _ *attachedClient, _ *transactionalResizePTY) { | 628 | setup: func(_ *Daemon, sess *session, _ *attachedClient, _ *transactionalResizePTY) { | ||
| 629 | - | p := sess.activeTab().focusedPane() | 629 | + | p := testAttachmentTab(sess).focusedPane() |
| 630 | p.mu.Lock() | 630 | p.mu.Lock() | ||
| 631 | p.screen.Write([]byte("\x1b[?1000h\x1b[?1006h")) | 631 | p.screen.Write([]byte("\x1b[?1000h\x1b[?1006h")) | ||
| 632 | p.mu.Unlock() | 632 | p.mu.Unlock() | ||
| @@ -769,12 +769,12 @@ func TestJumpAttentionDoesNotMutateSourceAfterInitiatorReplacement(t *testing.T) | |||||
| 769 | }) | 769 | }) | ||
| 770 | require.NoError(t, err) | 770 | require.NoError(t, err) | ||
| 771 | sess.mu.Lock() | 771 | sess.mu.Lock() | ||
| 772 | - | sess.active = 0 | 772 | + | selectTestAttachmentTabLocked(sess, 0) |
| 773 | sess.mu.Unlock() | 773 | sess.mu.Unlock() | ||
| 774 | release() | 774 | release() | ||
| 775 | <-actionDone | 775 | <-actionDone | ||
| 776 | 776 | ||||
| 777 | - | require.Equal(t, 0, activeTabIndex(sess), "the replaced initiator mutated source focus after losing its role") | 777 | + | require.Equal(t, 0, testAttachmentTabIndex(sess), "the replaced initiator mutated source focus after losing its role") |
| 778 | d.deferAttachmentTransitionCleanups(result) | 778 | d.deferAttachmentTransitionCleanups(result) | ||
| 779 | } | 779 | } | ||
| 780 | 780 | ||||
| @@ -802,7 +802,7 @@ func TestJumpAttentionAdmittedHandoffCrossesSessions(t *testing.T) { | |||||
| 802 | d.handleActiveClientFrame(token, frameInput([]byte("\x1ba"))) | 802 | d.handleActiveClientFrame(token, frameInput([]byte("\x1ba"))) | ||
| 803 | 803 | ||||
| 804 | require.Same(t, target, ac.currentSession()) | 804 | require.Same(t, target, ac.currentSession()) | ||
| 805 | - | require.Equal(t, 1, activeTabIndex(target)) | 805 | + | require.Equal(t, 1, testAttachmentTabIndex(target)) |
| 806 | } | 806 | } | ||
| 807 | 807 | ||||
| 808 | func TestJumpAttentionHandoffRevalidatesInitiatorAfterAdmissionEnds(t *testing.T) { | 808 | func TestJumpAttentionHandoffRevalidatesInitiatorAfterAdmissionEnds(t *testing.T) { | ||
| @@ -854,7 +854,7 @@ func TestJumpAttentionHandoffRevalidatesInitiatorAfterAdmissionEnds(t *testing.T | |||||
| 854 | release() | 854 | release() | ||
| 855 | <-actionDone | 855 | <-actionDone | ||
| 856 | 856 | ||||
| 857 | - | require.Equal(t, 0, activeTabIndex(target), "stale handoff changed target focus") | 857 | + | require.Equal(t, 0, testAttachmentTabIndex(target), "stale handoff changed target focus") |
| 858 | target.mu.Lock() | 858 | target.mu.Lock() | ||
| 859 | targetAttachments := target.snapshotAttachmentsLocked() | 859 | targetAttachments := target.snapshotAttachmentsLocked() | ||
| 860 | target.mu.Unlock() | 860 | target.mu.Unlock() | ||
M internal/usecase/daemon/session.go +3 -25
| @@ -51,14 +51,13 @@ type session struct { | |||
| 51 | 51 | teardownWaiters uint | |
| 52 | 52 | teardownChanged chan struct{} | |
| 53 | 53 | lifecycleStopOnce sync.Once | |
| 54 | - | // sessionCore.mu guards tabs, active, attachment membership, restoreDone, | |
| 55 | - | // clipFiles, and clipboard queue state. | |
| 54 | + | // sessionCore.mu guards tabs, attachment membership, restoreDone, clipFiles, | |
| 55 | + | // and clipboard queue state. | |
| 56 | 56 | // restoreDone remains attached to a restored session after it is published in | |
| 57 | 57 | // the live registry, so racing attaches cannot bypass restoration completion. | |
| 58 | 58 | restoreDone chan struct{} | |
| 59 | 59 | themeMu sync.Mutex | |
| 60 | 60 | tabs []*tab | |
| 61 | - | active int | |
| 62 | 61 | clipboardQueue []clipboardForward | |
| 63 | 62 | clipboardWorkerRunning bool | |
| 64 | 63 | cwd string | |
| @@ -854,27 +853,6 @@ func (d *Daemon) detachIfRoleCurrentUntil(token attachmentRoleToken, done func() | |||
| 854 | 853 | return current | |
| 855 | 854 | } | |
| 856 | 855 | ||
| 857 | - | func (s *session) activeTab() *tab { | |
| 858 | - | s.mu.Lock() | |
| 859 | - | defer s.mu.Unlock() | |
| 860 | - | if s.active < 0 || s.active >= len(s.tabs) { | |
| 861 | - | return nil | |
| 862 | - | } | |
| 863 | - | return s.tabs[s.active] | |
| 864 | - | } | |
| 865 | - | ||
| 866 | - | func (s *session) switchTab(idx int) bool { | |
| 867 | - | s.mu.Lock() | |
| 868 | - | if idx < 0 || idx >= len(s.tabs) || idx == s.active { | |
| 869 | - | s.mu.Unlock() | |
| 870 | - | return false | |
| 871 | - | } | |
| 872 | - | s.active = idx | |
| 873 | - | s.mu.Unlock() | |
| 874 | - | markSnapshotDirty(s) | |
| 875 | - | return true | |
| 876 | - | } | |
| 877 | - | ||
| 878 | 856 | func (d *Daemon) renameSession(sess *session, name string) error { | |
| 879 | 857 | if name == "" { | |
| 880 | 858 | return errSessionNameRequired | |
| @@ -1747,7 +1725,7 @@ func (d *Daemon) refreshSessionCwd(sess *session) { | |||
| 1747 | 1725 | if d.procCwd == nil { | |
| 1748 | 1726 | return | |
| 1749 | 1727 | } | |
| 1750 | - | tb := sess.tabForAttachmentOrActive(nil) | |
| 1728 | + | tb := sess.firstTab() | |
| 1751 | 1729 | if tb == nil { | |
| 1752 | 1730 | return | |
| 1753 | 1731 | } | |
| @@ -51,14 +51,13 @@ type session struct { | |||||
| 51 | teardownWaiters uint | 51 | teardownWaiters uint | ||
| 52 | teardownChanged chan struct{} | 52 | teardownChanged chan struct{} | ||
| 53 | lifecycleStopOnce sync.Once | 53 | lifecycleStopOnce sync.Once | ||
| 54 | - | // sessionCore.mu guards tabs, active, attachment membership, restoreDone, | 54 | + | // sessionCore.mu guards tabs, attachment membership, restoreDone, clipFiles, |
| 55 | - | // clipFiles, and clipboard queue state. | 55 | + | // and clipboard queue state. |
| 56 | // restoreDone remains attached to a restored session after it is published in | 56 | // restoreDone remains attached to a restored session after it is published in | ||
| 57 | // the live registry, so racing attaches cannot bypass restoration completion. | 57 | // the live registry, so racing attaches cannot bypass restoration completion. | ||
| 58 | restoreDone chan struct{} | 58 | restoreDone chan struct{} | ||
| 59 | themeMu sync.Mutex | 59 | themeMu sync.Mutex | ||
| 60 | tabs []*tab | 60 | tabs []*tab | ||
| 61 | - | active int | |||
| 62 | clipboardQueue []clipboardForward | 61 | clipboardQueue []clipboardForward | ||
| 63 | clipboardWorkerRunning bool | 62 | clipboardWorkerRunning bool | ||
| 64 | cwd string | 63 | cwd string | ||
| @@ -854,27 +853,6 @@ func (d *Daemon) detachIfRoleCurrentUntil(token attachmentRoleToken, done func() | |||||
| 854 | return current | 853 | return current | ||
| 855 | } | 854 | } | ||
| 856 | 855 | ||||
| 857 | - | func (s *session) activeTab() *tab { | |||
| 858 | - | s.mu.Lock() | |||
| 859 | - | defer s.mu.Unlock() | |||
| 860 | - | if s.active < 0 || s.active >= len(s.tabs) { | |||
| 861 | - | return nil | |||
| 862 | - | } | |||
| 863 | - | return s.tabs[s.active] | |||
| 864 | - | } | |||
| 865 | - | ||||
| 866 | - | func (s *session) switchTab(idx int) bool { | |||
| 867 | - | s.mu.Lock() | |||
| 868 | - | if idx < 0 || idx >= len(s.tabs) || idx == s.active { | |||
| 869 | - | s.mu.Unlock() | |||
| 870 | - | return false | |||
| 871 | - | } | |||
| 872 | - | s.active = idx | |||
| 873 | - | s.mu.Unlock() | |||
| 874 | - | markSnapshotDirty(s) | |||
| 875 | - | return true | |||
| 876 | - | } | |||
| 877 | - | ||||
| 878 | func (d *Daemon) renameSession(sess *session, name string) error { | 856 | func (d *Daemon) renameSession(sess *session, name string) error { | ||
| 879 | if name == "" { | 857 | if name == "" { | ||
| 880 | return errSessionNameRequired | 858 | return errSessionNameRequired | ||
| @@ -1747,7 +1725,7 @@ func (d *Daemon) refreshSessionCwd(sess *session) { | |||||
| 1747 | if d.procCwd == nil { | 1725 | if d.procCwd == nil { | ||
| 1748 | return | 1726 | return | ||
| 1749 | } | 1727 | } | ||
| 1750 | - | tb := sess.tabForAttachmentOrActive(nil) | 1728 | + | tb := sess.firstTab() |
| 1751 | if tb == nil { | 1729 | if tb == nil { | ||
| 1752 | return | 1730 | return | ||
| 1753 | } | 1731 | } | ||
M internal/usecase/daemon/session_core.go +5 -4
| @@ -14,7 +14,7 @@ type attachmentSession interface { | |||
| 14 | 14 | snapshotView(viewOptions) sessionView | |
| 15 | 15 | statusSegments(includeTerminalTitle bool) statusSnapshot | |
| 16 | 16 | capturePrimary(*attachedClient, primaryCaptureRequest) (*capturedRenderState, bool) | |
| 17 | - | activateTargetLocked(tabIndex int) bool | |
| 17 | + | validTargetTabLocked(tabIndex int) bool | |
| 18 | 18 | isProxy() bool | |
| 19 | 19 | } | |
| 20 | 20 | ||
| @@ -53,9 +53,10 @@ func (s *session) capturePrimary(ac *attachedClient, req primaryCaptureRequest) | |||
| 53 | 53 | return captureLocalPrimaryRenderState(s, ac, req) | |
| 54 | 54 | } | |
| 55 | 55 | ||
| 56 | - | // activateTargetLocked validates a legacy session target. Client-facing | |
| 57 | - | // navigation updates attachment views through selectAttachmentTab. | |
| 58 | - | func (s *session) activateTargetLocked(tabIndex int) bool { | |
| 56 | + | // validTargetTabLocked validates a target tab index without changing any | |
| 57 | + | // attachment view. Client-facing navigation updates attachment views through | |
| 58 | + | // selectAttachmentTab. | |
| 59 | + | func (s *session) validTargetTabLocked(tabIndex int) bool { | |
| 59 | 60 | if tabIndex < 0 { | |
| 60 | 61 | return true | |
| 61 | 62 | } | |
| @@ -14,7 +14,7 @@ type attachmentSession interface { | |||||
| 14 | snapshotView(viewOptions) sessionView | 14 | snapshotView(viewOptions) sessionView | ||
| 15 | statusSegments(includeTerminalTitle bool) statusSnapshot | 15 | statusSegments(includeTerminalTitle bool) statusSnapshot | ||
| 16 | capturePrimary(*attachedClient, primaryCaptureRequest) (*capturedRenderState, bool) | 16 | capturePrimary(*attachedClient, primaryCaptureRequest) (*capturedRenderState, bool) | ||
| 17 | - | activateTargetLocked(tabIndex int) bool | 17 | + | validTargetTabLocked(tabIndex int) bool |
| 18 | isProxy() bool | 18 | isProxy() bool | ||
| 19 | } | 19 | } | ||
| 20 | 20 | ||||
| @@ -53,9 +53,10 @@ func (s *session) capturePrimary(ac *attachedClient, req primaryCaptureRequest) | |||||
| 53 | return captureLocalPrimaryRenderState(s, ac, req) | 53 | return captureLocalPrimaryRenderState(s, ac, req) | ||
| 54 | } | 54 | } | ||
| 55 | 55 | ||||
| 56 | - | // activateTargetLocked validates a legacy session target. Client-facing | 56 | + | // validTargetTabLocked validates a target tab index without changing any |
| 57 | - | // navigation updates attachment views through selectAttachmentTab. | 57 | + | // attachment view. Client-facing navigation updates attachment views through |
| 58 | - | func (s *session) activateTargetLocked(tabIndex int) bool { | 58 | + | // selectAttachmentTab. |
| 59 | + | func (s *session) validTargetTabLocked(tabIndex int) bool { | |||
| 59 | if tabIndex < 0 { | 60 | if tabIndex < 0 { | ||
| 60 | return true | 61 | return true | ||
| 61 | } | 62 | } | ||
M internal/usecase/daemon/session_core_test.go +7 -5
| @@ -49,7 +49,7 @@ func TestSessionCorePreservesLocalIdentityAndPromotedMutex(t *testing.T) { | |||
| 49 | 49 | func TestSessionSnapshotViewUsesPromotedMutexForLocalTabAndRoleState(t *testing.T) { | |
| 50 | 50 | sess := &session{ | |
| 51 | 51 | sessionCore: sessionCore{id: domain.SessionID("local"), name: "work"}, | |
| 52 | - | tabs: []*tab{{name: "shell"}}, | |
| 52 | + | tabs: []*tab{{stableID: "tab-shell", name: "shell"}}, | |
| 53 | 53 | } | |
| 54 | 54 | ac := &attachedClient{} | |
| 55 | 55 | ||
| @@ -57,9 +57,9 @@ func TestSessionSnapshotViewUsesPromotedMutexForLocalTabAndRoleState(t *testing. | |||
| 57 | 57 | // promoted core mutex. snapshotView must observe the same mutex before it | |
| 58 | 58 | // can publish either field. | |
| 59 | 59 | sess.core().mu.Lock() | |
| 60 | - | sess.tabs = append(sess.tabs, &tab{name: "logs"}) | |
| 61 | - | sess.active = 1 | |
| 60 | + | sess.tabs = append(sess.tabs, &tab{stableID: "tab-logs", name: "logs"}) | |
| 62 | 61 | sess.registerAttachmentLocked(ac) | |
| 62 | + | sess.activateAttachmentViewLocked(ac, 1) | |
| 63 | 63 | ||
| 64 | 64 | preCall := make(chan struct{}) | |
| 65 | 65 | allowCall := make(chan struct{}) | |
| @@ -89,7 +89,8 @@ func TestSessionSnapshotViewUsesPromotedMutexForLocalTabAndRoleState(t *testing. | |||
| 89 | 89 | view := awaitTestValue(t, snapshotted, "snapshotView did not complete after sessionCore.mu was released") | |
| 90 | 90 | require.True(t, view.attached) | |
| 91 | 91 | require.Equal(t, 2, view.tabCount) | |
| 92 | - | require.Equal(t, 1, view.active) | |
| 92 | + | require.Equal(t, 0, view.defaultTab) | |
| 93 | + | require.Equal(t, 1, testAttachmentTabIndex(sess)) | |
| 93 | 94 | } | |
| 94 | 95 | ||
| 95 | 96 | func TestLocalCreateThenKillRemovesLiveRegistryEntry(t *testing.T) { | |
| @@ -165,8 +166,9 @@ func TestInitialMetadataSkipsInvalidSnapshot(t *testing.T) { | |||
| 165 | 166 | sess := firstSession(d) | |
| 166 | 167 | require.NotNil(t, sess) | |
| 167 | 168 | sess.mu.Lock() | |
| 168 | - | sess.active = len(sess.tabs) | |
| 169 | + | invalidTabIndex := len(sess.tabs) | |
| 169 | 170 | sess.mu.Unlock() | |
| 171 | + | selectTestAttachmentTab(sess, invalidTabIndex) | |
| 170 | 172 | // finish (not just release) also closes recvDone, so runConnLoop's Recv | |
| 171 | 173 | // fails immediately after the first paint and the handshake goroutine | |
| 172 | 174 | // returns instead of blocking on further input. | |
| @@ -49,7 +49,7 @@ func TestSessionCorePreservesLocalIdentityAndPromotedMutex(t *testing.T) { | |||||
| 49 | func TestSessionSnapshotViewUsesPromotedMutexForLocalTabAndRoleState(t *testing.T) { | 49 | func TestSessionSnapshotViewUsesPromotedMutexForLocalTabAndRoleState(t *testing.T) { | ||
| 50 | sess := &session{ | 50 | sess := &session{ | ||
| 51 | sessionCore: sessionCore{id: domain.SessionID("local"), name: "work"}, | 51 | sessionCore: sessionCore{id: domain.SessionID("local"), name: "work"}, | ||
| 52 | - | tabs: []*tab{{name: "shell"}}, | 52 | + | tabs: []*tab{{stableID: "tab-shell", name: "shell"}}, |
| 53 | } | 53 | } | ||
| 54 | ac := &attachedClient{} | 54 | ac := &attachedClient{} | ||
| 55 | 55 | ||||
| @@ -57,9 +57,9 @@ func TestSessionSnapshotViewUsesPromotedMutexForLocalTabAndRoleState(t *testing. | |||||
| 57 | // promoted core mutex. snapshotView must observe the same mutex before it | 57 | // promoted core mutex. snapshotView must observe the same mutex before it | ||
| 58 | // can publish either field. | 58 | // can publish either field. | ||
| 59 | sess.core().mu.Lock() | 59 | sess.core().mu.Lock() | ||
| 60 | - | sess.tabs = append(sess.tabs, &tab{name: "logs"}) | 60 | + | sess.tabs = append(sess.tabs, &tab{stableID: "tab-logs", name: "logs"}) |
| 61 | - | sess.active = 1 | |||
| 62 | sess.registerAttachmentLocked(ac) | 61 | sess.registerAttachmentLocked(ac) | ||
| 62 | + | sess.activateAttachmentViewLocked(ac, 1) | |||
| 63 | 63 | ||||
| 64 | preCall := make(chan struct{}) | 64 | preCall := make(chan struct{}) | ||
| 65 | allowCall := make(chan struct{}) | 65 | allowCall := make(chan struct{}) | ||
| @@ -89,7 +89,8 @@ func TestSessionSnapshotViewUsesPromotedMutexForLocalTabAndRoleState(t *testing. | |||||
| 89 | view := awaitTestValue(t, snapshotted, "snapshotView did not complete after sessionCore.mu was released") | 89 | view := awaitTestValue(t, snapshotted, "snapshotView did not complete after sessionCore.mu was released") | ||
| 90 | require.True(t, view.attached) | 90 | require.True(t, view.attached) | ||
| 91 | require.Equal(t, 2, view.tabCount) | 91 | require.Equal(t, 2, view.tabCount) | ||
| 92 | - | require.Equal(t, 1, view.active) | 92 | + | require.Equal(t, 0, view.defaultTab) |
| 93 | + | require.Equal(t, 1, testAttachmentTabIndex(sess)) | |||
| 93 | } | 94 | } | ||
| 94 | 95 | ||||
| 95 | func TestLocalCreateThenKillRemovesLiveRegistryEntry(t *testing.T) { | 96 | func TestLocalCreateThenKillRemovesLiveRegistryEntry(t *testing.T) { | ||
| @@ -165,8 +166,9 @@ func TestInitialMetadataSkipsInvalidSnapshot(t *testing.T) { | |||||
| 165 | sess := firstSession(d) | 166 | sess := firstSession(d) | ||
| 166 | require.NotNil(t, sess) | 167 | require.NotNil(t, sess) | ||
| 167 | sess.mu.Lock() | 168 | sess.mu.Lock() | ||
| 168 | - | sess.active = len(sess.tabs) | 169 | + | invalidTabIndex := len(sess.tabs) |
| 169 | sess.mu.Unlock() | 170 | sess.mu.Unlock() | ||
| 171 | + | selectTestAttachmentTab(sess, invalidTabIndex) | |||
| 170 | // finish (not just release) also closes recvDone, so runConnLoop's Recv | 172 | // finish (not just release) also closes recvDone, so runConnLoop's Recv | ||
| 171 | // fails immediately after the first paint and the handshake goroutine | 173 | // fails immediately after the first paint and the handshake goroutine | ||
| 172 | // returns instead of blocking on further input. | 174 | // returns instead of blocking on further input. | ||
M internal/usecase/daemon/session_size_test.go +3 -3
| @@ -14,12 +14,12 @@ func TestFullViewportSize(t *testing.T) { | |||
| 14 | 14 | }{ | |
| 15 | 15 | { | |
| 16 | 16 | name: "derives from active tab content size", | |
| 17 | - | sess: &session{tabs: []*tab{{size: domain.Size{Cols: 120, Rows: 38}}}, active: 0}, | |
| 17 | + | sess: &session{tabs: []*tab{{size: domain.Size{Cols: 120, Rows: 38}}}}, | |
| 18 | 18 | want: domain.Size{Cols: 120, Rows: 40}, | |
| 19 | 19 | }, | |
| 20 | 20 | { | |
| 21 | 21 | name: "round-trips through tabSize", | |
| 22 | - | sess: &session{tabs: []*tab{{size: tabSize(domain.Size{Cols: 80, Rows: 24})}}, active: 0}, | |
| 22 | + | sess: &session{tabs: []*tab{{size: tabSize(domain.Size{Cols: 80, Rows: 24})}}}, | |
| 23 | 23 | want: domain.Size{Cols: 80, Rows: 24}, | |
| 24 | 24 | }, | |
| 25 | 25 | { | |
| @@ -29,7 +29,7 @@ func TestFullViewportSize(t *testing.T) { | |||
| 29 | 29 | }, | |
| 30 | 30 | { | |
| 31 | 31 | name: "falls back to defaultSize with invalid tab size", | |
| 32 | - | sess: &session{tabs: []*tab{{}}, active: 0}, | |
| 32 | + | sess: &session{tabs: []*tab{{}}}, | |
| 33 | 33 | want: defaultSize, | |
| 34 | 34 | }, | |
| 35 | 35 | } | |
| @@ -14,12 +14,12 @@ func TestFullViewportSize(t *testing.T) { | |||||
| 14 | }{ | 14 | }{ | ||
| 15 | { | 15 | { | ||
| 16 | name: "derives from active tab content size", | 16 | name: "derives from active tab content size", | ||
| 17 | - | sess: &session{tabs: []*tab{{size: domain.Size{Cols: 120, Rows: 38}}}, active: 0}, | 17 | + | sess: &session{tabs: []*tab{{size: domain.Size{Cols: 120, Rows: 38}}}}, |
| 18 | want: domain.Size{Cols: 120, Rows: 40}, | 18 | want: domain.Size{Cols: 120, Rows: 40}, | ||
| 19 | }, | 19 | }, | ||
| 20 | { | 20 | { | ||
| 21 | name: "round-trips through tabSize", | 21 | name: "round-trips through tabSize", | ||
| 22 | - | sess: &session{tabs: []*tab{{size: tabSize(domain.Size{Cols: 80, Rows: 24})}}, active: 0}, | 22 | + | sess: &session{tabs: []*tab{{size: tabSize(domain.Size{Cols: 80, Rows: 24})}}}, |
| 23 | want: domain.Size{Cols: 80, Rows: 24}, | 23 | want: domain.Size{Cols: 80, Rows: 24}, | ||
| 24 | }, | 24 | }, | ||
| 25 | { | 25 | { | ||
| @@ -29,7 +29,7 @@ func TestFullViewportSize(t *testing.T) { | |||||
| 29 | }, | 29 | }, | ||
| 30 | { | 30 | { | ||
| 31 | name: "falls back to defaultSize with invalid tab size", | 31 | name: "falls back to defaultSize with invalid tab size", | ||
| 32 | - | sess: &session{tabs: []*tab{{}}, active: 0}, | 32 | + | sess: &session{tabs: []*tab{{}}}, |
| 33 | want: defaultSize, | 33 | want: defaultSize, | ||
| 34 | }, | 34 | }, | ||
| 35 | } | 35 | } | ||
M internal/usecase/daemon/session_test.go +3 -3
| @@ -72,7 +72,7 @@ func TestRoutePropagatesHelloCwdAndTabsInheritIt(t *testing.T) { | |||
| 72 | 72 | ||
| 73 | 73 | require.NoError(t, d.createTab(sess, sz)) | |
| 74 | 74 | require.Equal(t, []string{"/tmp/work", "/tmp/work"}, dirs) | |
| 75 | - | requireFloatingInitialized(t, sess.activeTab()) | |
| 75 | + | requireFloatingInitialized(t, testAttachmentTab(sess)) | |
| 76 | 76 | _ = d.killSession(sess, ports.ReasonSessionKilled, false) | |
| 77 | 77 | releaseFirst() | |
| 78 | 78 | releaseSecond() | |
| @@ -998,7 +998,7 @@ func TestCloseActiveTabActivatesDestinationFloatingPane(t *testing.T) { | |||
| 998 | 998 | defer releases[1]() | |
| 999 | 999 | sess.mu.Lock() | |
| 1000 | 1000 | first, closing := sess.tabs[0], sess.tabs[1] | |
| 1001 | - | sess.active = 1 | |
| 1001 | + | selectTestAttachmentTabLocked(sess, 1) | |
| 1002 | 1002 | sess.mu.Unlock() | |
| 1003 | 1003 | first.mu.Lock() | |
| 1004 | 1004 | stale := first.takeFloatingLocked() | |
| @@ -1007,7 +1007,7 @@ func TestCloseActiveTabActivatesDestinationFloatingPane(t *testing.T) { | |||
| 1007 | 1007 | ||
| 1008 | 1008 | require.NoError(t, d.closeTab(sess, closing, false)) | |
| 1009 | 1009 | ||
| 1010 | - | require.Same(t, first, sess.activeTab()) | |
| 1010 | + | require.Same(t, first, testAttachmentTab(sess)) | |
| 1011 | 1011 | requireFloatingInitialized(t, first) | |
| 1012 | 1012 | } | |
| 1013 | 1013 | ||
| @@ -72,7 +72,7 @@ func TestRoutePropagatesHelloCwdAndTabsInheritIt(t *testing.T) { | |||||
| 72 | 72 | ||||
| 73 | require.NoError(t, d.createTab(sess, sz)) | 73 | require.NoError(t, d.createTab(sess, sz)) | ||
| 74 | require.Equal(t, []string{"/tmp/work", "/tmp/work"}, dirs) | 74 | require.Equal(t, []string{"/tmp/work", "/tmp/work"}, dirs) | ||
| 75 | - | requireFloatingInitialized(t, sess.activeTab()) | 75 | + | requireFloatingInitialized(t, testAttachmentTab(sess)) |
| 76 | _ = d.killSession(sess, ports.ReasonSessionKilled, false) | 76 | _ = d.killSession(sess, ports.ReasonSessionKilled, false) | ||
| 77 | releaseFirst() | 77 | releaseFirst() | ||
| 78 | releaseSecond() | 78 | releaseSecond() | ||
| @@ -998,7 +998,7 @@ func TestCloseActiveTabActivatesDestinationFloatingPane(t *testing.T) { | |||||
| 998 | defer releases[1]() | 998 | defer releases[1]() | ||
| 999 | sess.mu.Lock() | 999 | sess.mu.Lock() | ||
| 1000 | first, closing := sess.tabs[0], sess.tabs[1] | 1000 | first, closing := sess.tabs[0], sess.tabs[1] | ||
| 1001 | - | sess.active = 1 | 1001 | + | selectTestAttachmentTabLocked(sess, 1) |
| 1002 | sess.mu.Unlock() | 1002 | sess.mu.Unlock() | ||
| 1003 | first.mu.Lock() | 1003 | first.mu.Lock() | ||
| 1004 | stale := first.takeFloatingLocked() | 1004 | stale := first.takeFloatingLocked() | ||
| @@ -1007,7 +1007,7 @@ func TestCloseActiveTabActivatesDestinationFloatingPane(t *testing.T) { | |||||
| 1007 | 1007 | ||||
| 1008 | require.NoError(t, d.closeTab(sess, closing, false)) | 1008 | require.NoError(t, d.closeTab(sess, closing, false)) | ||
| 1009 | 1009 | ||||
| 1010 | - | require.Same(t, first, sess.activeTab()) | 1010 | + | require.Same(t, first, testAttachmentTab(sess)) |
| 1011 | requireFloatingInitialized(t, first) | 1011 | requireFloatingInitialized(t, first) | ||
| 1012 | } | 1012 | } | ||
| 1013 | 1013 | ||||
M internal/usecase/daemon/session_view.go +6 -14
| @@ -34,7 +34,7 @@ type sessionView struct { | |||
| 34 | 34 | name string | |
| 35 | 35 | ephemeral bool | |
| 36 | 36 | createdAt int64 | |
| 37 | - | active int | |
| 37 | + | defaultTab int | |
| 38 | 38 | mruAt uint64 | |
| 39 | 39 | attached bool | |
| 40 | 40 | tabCount int | |
| @@ -53,24 +53,16 @@ func (s *session) snapshotView(opts viewOptions) sessionView { | |||
| 53 | 53 | opts.tabDetails = true | |
| 54 | 54 | } | |
| 55 | 55 | s.mu.Lock() | |
| 56 | - | active := 0 | |
| 57 | - | if len(s.attachments) != 0 { | |
| 58 | - | for i, tb := range s.tabs { | |
| 59 | - | for ac := range s.attachments { | |
| 60 | - | if ac.viewSnapshot().tabID == domain.TabStableID(tb.stableID) { | |
| 61 | - | active = i | |
| 62 | - | break | |
| 63 | - | } | |
| 64 | - | } | |
| 65 | - | } | |
| 66 | - | } | |
| 56 | + | // Session-level listings use the first ordered tab as their deterministic | |
| 57 | + | // default. Interactive callers pass an attachment and resolve its stable | |
| 58 | + | // view separately. | |
| 67 | 59 | view := sessionView{ | |
| 68 | 60 | id: s.id, | |
| 69 | 61 | incarnation: s.incarnation, | |
| 70 | 62 | name: s.name, | |
| 71 | 63 | ephemeral: s.ephemeral, | |
| 72 | 64 | createdAt: s.createdAt, | |
| 73 | - | active: active, | |
| 65 | + | defaultTab: 0, | |
| 74 | 66 | mruAt: s.mruAt.Load(), | |
| 75 | 67 | attached: len(s.attachments) != 0, | |
| 76 | 68 | tabCount: len(s.tabs), | |
| @@ -115,7 +107,7 @@ func (view sessionView) pickerView() picker.SessionView { | |||
| 115 | 107 | Incarnation: view.incarnation, | |
| 116 | 108 | Name: view.name, | |
| 117 | 109 | TargetName: view.name, | |
| 118 | - | Active: view.active, | |
| 110 | + | Active: view.defaultTab, | |
| 119 | 111 | Tabs: make([]picker.TabEntry, 0, len(view.tabs)), | |
| 120 | 112 | CannotAcceptMoves: view.cannotAcceptMoves, | |
| 121 | 113 | } | |
| @@ -34,7 +34,7 @@ type sessionView struct { | |||||
| 34 | name string | 34 | name string | ||
| 35 | ephemeral bool | 35 | ephemeral bool | ||
| 36 | createdAt int64 | 36 | createdAt int64 | ||
| 37 | - | active int | 37 | + | defaultTab int |
| 38 | mruAt uint64 | 38 | mruAt uint64 | ||
| 39 | attached bool | 39 | attached bool | ||
| 40 | tabCount int | 40 | tabCount int | ||
| @@ -53,24 +53,16 @@ func (s *session) snapshotView(opts viewOptions) sessionView { | |||||
| 53 | opts.tabDetails = true | 53 | opts.tabDetails = true | ||
| 54 | } | 54 | } | ||
| 55 | s.mu.Lock() | 55 | s.mu.Lock() | ||
| 56 | - | active := 0 | 56 | + | // Session-level listings use the first ordered tab as their deterministic |
| 57 | - | if len(s.attachments) != 0 { | 57 | + | // default. Interactive callers pass an attachment and resolve its stable |
| 58 | - | for i, tb := range s.tabs { | 58 | + | // view separately. |
| 59 | - | for ac := range s.attachments { | |||
| 60 | - | if ac.viewSnapshot().tabID == domain.TabStableID(tb.stableID) { | |||
| 61 | - | active = i | |||
| 62 | - | break | |||
| 63 | - | } | |||
| 64 | - | } | |||
| 65 | - | } | |||
| 66 | - | } | |||
| 67 | view := sessionView{ | 59 | view := sessionView{ | ||
| 68 | id: s.id, | 60 | id: s.id, | ||
| 69 | incarnation: s.incarnation, | 61 | incarnation: s.incarnation, | ||
| 70 | name: s.name, | 62 | name: s.name, | ||
| 71 | ephemeral: s.ephemeral, | 63 | ephemeral: s.ephemeral, | ||
| 72 | createdAt: s.createdAt, | 64 | createdAt: s.createdAt, | ||
| 73 | - | active: active, | 65 | + | defaultTab: 0, |
| 74 | mruAt: s.mruAt.Load(), | 66 | mruAt: s.mruAt.Load(), | ||
| 75 | attached: len(s.attachments) != 0, | 67 | attached: len(s.attachments) != 0, | ||
| 76 | tabCount: len(s.tabs), | 68 | tabCount: len(s.tabs), | ||
| @@ -115,7 +107,7 @@ func (view sessionView) pickerView() picker.SessionView { | |||||
| 115 | Incarnation: view.incarnation, | 107 | Incarnation: view.incarnation, | ||
| 116 | Name: view.name, | 108 | Name: view.name, | ||
| 117 | TargetName: view.name, | 109 | TargetName: view.name, | ||
| 118 | - | Active: view.active, | 110 | + | Active: view.defaultTab, |
| 119 | Tabs: make([]picker.TabEntry, 0, len(view.tabs)), | 111 | Tabs: make([]picker.TabEntry, 0, len(view.tabs)), | ||
| 120 | CannotAcceptMoves: view.cannotAcceptMoves, | 112 | CannotAcceptMoves: view.cannotAcceptMoves, | ||
| 121 | } | 113 | } | ||
M internal/usecase/daemon/session_view_test.go +1 -2
| @@ -22,7 +22,7 @@ func TestSnapshotViewCapturesSessionFields(t *testing.T) { | |||
| 22 | 22 | name: "alpha", | |
| 23 | 23 | createdAt: 42, | |
| 24 | 24 | ||
| 25 | - | attachments: map[*attachedClient]struct{}{&attachedClient{}: {}}}, active: 1, | |
| 25 | + | attachments: map[*attachedClient]struct{}{&attachedClient{}: {}}}, | |
| 26 | 26 | ||
| 27 | 27 | tabs: []*tab{ | |
| 28 | 28 | {stableID: "t1", name: "build"}, | |
| @@ -38,7 +38,6 @@ func TestSnapshotViewCapturesSessionFields(t *testing.T) { | |||
| 38 | 38 | incarnation: domain.IncarnationID{1}, | |
| 39 | 39 | name: "alpha", | |
| 40 | 40 | createdAt: 42, | |
| 41 | - | active: 1, | |
| 42 | 41 | mruAt: 7, | |
| 43 | 42 | attached: true, | |
| 44 | 43 | tabCount: 2, | |
| @@ -22,7 +22,7 @@ func TestSnapshotViewCapturesSessionFields(t *testing.T) { | |||||
| 22 | name: "alpha", | 22 | name: "alpha", | ||
| 23 | createdAt: 42, | 23 | createdAt: 42, | ||
| 24 | 24 | ||||
| 25 | - | attachments: map[*attachedClient]struct{}{&attachedClient{}: {}}}, active: 1, | 25 | + | attachments: map[*attachedClient]struct{}{&attachedClient{}: {}}}, |
| 26 | 26 | ||||
| 27 | tabs: []*tab{ | 27 | tabs: []*tab{ | ||
| 28 | {stableID: "t1", name: "build"}, | 28 | {stableID: "t1", name: "build"}, | ||
| @@ -38,7 +38,6 @@ func TestSnapshotViewCapturesSessionFields(t *testing.T) { | |||||
| 38 | incarnation: domain.IncarnationID{1}, | 38 | incarnation: domain.IncarnationID{1}, | ||
| 39 | name: "alpha", | 39 | name: "alpha", | ||
| 40 | createdAt: 42, | 40 | createdAt: 42, | ||
| 41 | - | active: 1, | |||
| 42 | mruAt: 7, | 41 | mruAt: 7, | ||
| 43 | attached: true, | 42 | attached: true, | ||
| 44 | tabCount: 2, | 43 | tabCount: 2, | ||
M internal/usecase/daemon/snapshot.go +1 -1
| @@ -42,7 +42,7 @@ type snapshotCapture struct { | |||
| 42 | 42 | name string | |
| 43 | 43 | incarnation domain.IncarnationID | |
| 44 | 44 | createdAt uint64 | |
| 45 | - | active uint16 | |
| 45 | + | defaultTab uint16 | |
| 46 | 46 | tabs []snapshotCaptureTab | |
| 47 | 47 | publicationContext context.Context | |
| 48 | 48 | sealedRefs map[*vt.HistoryChunk]snapcodec.ObjectRef // set by the single encoder worker | |
| @@ -42,7 +42,7 @@ type snapshotCapture struct { | |||||
| 42 | name string | 42 | name string | ||
| 43 | incarnation domain.IncarnationID | 43 | incarnation domain.IncarnationID | ||
| 44 | createdAt uint64 | 44 | createdAt uint64 | ||
| 45 | - | active uint16 | 45 | + | defaultTab uint16 |
| 46 | tabs []snapshotCaptureTab | 46 | tabs []snapshotCaptureTab | ||
| 47 | publicationContext context.Context | 47 | publicationContext context.Context | ||
| 48 | sealedRefs map[*vt.HistoryChunk]snapcodec.ObjectRef // set by the single encoder worker | 48 | sealedRefs map[*vt.HistoryChunk]snapcodec.ObjectRef // set by the single encoder worker | ||
M internal/usecase/daemon/snapshot_capture.go +3 -1
| @@ -13,7 +13,9 @@ func (d *Daemon) captureSnapshotState(sess *session, generation uint64) (*snapsh | |||
| 13 | 13 | name: sess.name, | |
| 14 | 14 | incarnation: sess.incarnation, | |
| 15 | 15 | createdAt: uint64(sess.createdAt), | |
| 16 | - | active: 0, | |
| 16 | + | // Snapshot format retains a deterministic first-tab default; it is not | |
| 17 | + | // an interactive attachment selection. | |
| 18 | + | defaultTab: 0, | |
| 17 | 19 | } | |
| 18 | 20 | ephemeral := sess.ephemeral | |
| 19 | 21 | fallbackCwd := sess.cwd | |
| @@ -13,7 +13,9 @@ func (d *Daemon) captureSnapshotState(sess *session, generation uint64) (*snapsh | |||||
| 13 | name: sess.name, | 13 | name: sess.name, | ||
| 14 | incarnation: sess.incarnation, | 14 | incarnation: sess.incarnation, | ||
| 15 | createdAt: uint64(sess.createdAt), | 15 | createdAt: uint64(sess.createdAt), | ||
| 16 | - | active: 0, | 16 | + | // Snapshot format retains a deterministic first-tab default; it is not |
| 17 | + | // an interactive attachment selection. | |||
| 18 | + | defaultTab: 0, | |||
| 17 | } | 19 | } | ||
| 18 | ephemeral := sess.ephemeral | 20 | ephemeral := sess.ephemeral | ||
| 19 | fallbackCwd := sess.cwd | 21 | fallbackCwd := sess.cwd | ||
M internal/usecase/daemon/snapshot_incremental.go +1 -1
| @@ -114,7 +114,7 @@ func (d *Daemon) incrementalPublication(capture *snapshotCapture) (ports.Snapsho | |||
| 114 | 114 | if err := prepareSnapshotChunkCache(capture); err != nil { | |
| 115 | 115 | return ports.SnapshotPublication{}, err | |
| 116 | 116 | } | |
| 117 | - | manifest := snapcodec.Manifest{Generation: capture.generation, IncarnationID: capture.incarnation, ParentCheckpoint: capture.parentCheckpoint, Name: capture.name, CreatedAt: capture.createdAt, Active: capture.active, Tabs: make([]snapcodec.ManifestTab, 0, len(capture.tabs))} | |
| 117 | + | manifest := snapcodec.Manifest{Generation: capture.generation, IncarnationID: capture.incarnation, ParentCheckpoint: capture.parentCheckpoint, Name: capture.name, CreatedAt: capture.createdAt, Active: capture.defaultTab, Tabs: make([]snapcodec.ManifestTab, 0, len(capture.tabs))} | |
| 118 | 118 | objects := make([]ports.SnapshotObject, 0) | |
| 119 | 119 | capture.sealedRefs = make(map[*vt.HistoryChunk]snapcodec.ObjectRef) | |
| 120 | 120 | for _, tab := range capture.tabs { | |
| @@ -114,7 +114,7 @@ func (d *Daemon) incrementalPublication(capture *snapshotCapture) (ports.Snapsho | |||||
| 114 | if err := prepareSnapshotChunkCache(capture); err != nil { | 114 | if err := prepareSnapshotChunkCache(capture); err != nil { | ||
| 115 | return ports.SnapshotPublication{}, err | 115 | return ports.SnapshotPublication{}, err | ||
| 116 | } | 116 | } | ||
| 117 | - | manifest := snapcodec.Manifest{Generation: capture.generation, IncarnationID: capture.incarnation, ParentCheckpoint: capture.parentCheckpoint, Name: capture.name, CreatedAt: capture.createdAt, Active: capture.active, Tabs: make([]snapcodec.ManifestTab, 0, len(capture.tabs))} | 117 | + | manifest := snapcodec.Manifest{Generation: capture.generation, IncarnationID: capture.incarnation, ParentCheckpoint: capture.parentCheckpoint, Name: capture.name, CreatedAt: capture.createdAt, Active: capture.defaultTab, Tabs: make([]snapcodec.ManifestTab, 0, len(capture.tabs))} |
| 118 | objects := make([]ports.SnapshotObject, 0) | 118 | objects := make([]ports.SnapshotObject, 0) | ||
| 119 | capture.sealedRefs = make(map[*vt.HistoryChunk]snapcodec.ObjectRef) | 119 | capture.sealedRefs = make(map[*vt.HistoryChunk]snapcodec.ObjectRef) | ||
| 120 | for _, tab := range capture.tabs { | 120 | for _, tab := range capture.tabs { | ||
M internal/usecase/daemon/snapshot_restore.go +2 -0
| @@ -290,6 +290,8 @@ func (d *Daemon) restoreSnapshotPane(ctx context.Context, sessionName, tabStable | |||
| 290 | 290 | } | |
| 291 | 291 | ||
| 292 | 292 | func (d *Daemon) newRestoredSession(snap snapcodec.Session, sctx context.Context, cancel context.CancelFunc, tabs []*tab, term terminalEnv) *session { | |
| 293 | + | // Restored sessions keep ordered tabs; attachment repair deterministically | |
| 294 | + | // selects the first tab instead of restoring an interactive client view. | |
| 293 | 295 | sess := &session{sessionCore: sessionCore{name: snap.Name, createdAt: int64(snap.CreatedAt)}, ctx: sctx, cancel: cancel, tabs: tabs, terminal: term, env: copyEnvironment(d.baseEnv), snapshotWake: d.snapshotWake, snapshotChunkCache: newSnapshotChunkCache(snapshotChunkCacheLimit)} | |
| 294 | 296 | // Restored tabs remain private until persistAndRegisterRestoredSession. | |
| 295 | 297 | // Initialize owners now so registration and reader startup cannot expose an | |
| @@ -290,6 +290,8 @@ func (d *Daemon) restoreSnapshotPane(ctx context.Context, sessionName, tabStable | |||||
| 290 | } | 290 | } | ||
| 291 | 291 | ||||
| 292 | func (d *Daemon) newRestoredSession(snap snapcodec.Session, sctx context.Context, cancel context.CancelFunc, tabs []*tab, term terminalEnv) *session { | 292 | func (d *Daemon) newRestoredSession(snap snapcodec.Session, sctx context.Context, cancel context.CancelFunc, tabs []*tab, term terminalEnv) *session { | ||
| 293 | + | // Restored sessions keep ordered tabs; attachment repair deterministically | |||
| 294 | + | // selects the first tab instead of restoring an interactive client view. | |||
| 293 | sess := &session{sessionCore: sessionCore{name: snap.Name, createdAt: int64(snap.CreatedAt)}, ctx: sctx, cancel: cancel, tabs: tabs, terminal: term, env: copyEnvironment(d.baseEnv), snapshotWake: d.snapshotWake, snapshotChunkCache: newSnapshotChunkCache(snapshotChunkCacheLimit)} | 295 | sess := &session{sessionCore: sessionCore{name: snap.Name, createdAt: int64(snap.CreatedAt)}, ctx: sctx, cancel: cancel, tabs: tabs, terminal: term, env: copyEnvironment(d.baseEnv), snapshotWake: d.snapshotWake, snapshotChunkCache: newSnapshotChunkCache(snapshotChunkCacheLimit)} | ||
| 294 | // Restored tabs remain private until persistAndRegisterRestoredSession. | 296 | // Restored tabs remain private until persistAndRegisterRestoredSession. | ||
| 295 | // Initialize owners now so registration and reader startup cannot expose an | 297 | // Initialize owners now so registration and reader startup cannot expose an | ||
M internal/usecase/daemon/snapshot_restore_test.go +1 -1
| @@ -233,7 +233,7 @@ func TestRestoreIncrementalGenerationAcceptance(t *testing.T) { | |||
| 233 | 233 | require.False(t, restored.snapDirty.Load(), "a loaded generation must begin clean") | |
| 234 | 234 | require.Equal(t, uint64(9), restored.snapshotPublishedGeneration, "the loaded manifest is the repository generation head") | |
| 235 | 235 | require.True(t, restored.terminal.TrueColor, "restored sessions must retain startup terminal capability for future panes") | |
| 236 | - | require.Equal(t, 0, restored.active) | |
| 236 | + | require.Equal(t, 0, testAttachmentTabIndex(restored)) | |
| 237 | 237 | require.Equal(t, "/snapshot/cwd", restored.cwd) | |
| 238 | 238 | require.Len(t, restored.tabs, 1) | |
| 239 | 239 | tab := restored.tabs[0] | |
| @@ -233,7 +233,7 @@ func TestRestoreIncrementalGenerationAcceptance(t *testing.T) { | |||||
| 233 | require.False(t, restored.snapDirty.Load(), "a loaded generation must begin clean") | 233 | require.False(t, restored.snapDirty.Load(), "a loaded generation must begin clean") | ||
| 234 | require.Equal(t, uint64(9), restored.snapshotPublishedGeneration, "the loaded manifest is the repository generation head") | 234 | require.Equal(t, uint64(9), restored.snapshotPublishedGeneration, "the loaded manifest is the repository generation head") | ||
| 235 | require.True(t, restored.terminal.TrueColor, "restored sessions must retain startup terminal capability for future panes") | 235 | require.True(t, restored.terminal.TrueColor, "restored sessions must retain startup terminal capability for future panes") | ||
| 236 | - | require.Equal(t, 0, restored.active) | 236 | + | require.Equal(t, 0, testAttachmentTabIndex(restored)) |
| 237 | require.Equal(t, "/snapshot/cwd", restored.cwd) | 237 | require.Equal(t, "/snapshot/cwd", restored.cwd) | ||
| 238 | require.Len(t, restored.tabs, 1) | 238 | require.Len(t, restored.tabs, 1) | ||
| 239 | tab := restored.tabs[0] | 239 | tab := restored.tabs[0] | ||
M internal/usecase/daemon/snapshot_resume_publication_test.go +4 -4
| @@ -159,7 +159,7 @@ func TestNamedSessionPublishesNextCheckpointAfterClientSnatch(t *testing.T) { | |||
| 159 | 159 | sess, firstClient, err := d.route(snapshotLifecycleHello(ports.IntentNew, "work"), firstTransport) | |
| 160 | 160 | require.NoError(t, err) | |
| 161 | 161 | ||
| 162 | - | sess.activeTab().focusedPane().screen.Write([]byte("first checkpoint")) | |
| 162 | + | testAttachmentTab(sess).focusedPane().screen.Write([]byte("first checkpoint")) | |
| 163 | 163 | markSnapshotDirty(sess) | |
| 164 | 164 | require.True(t, d.scheduleSnapshot(sess)) | |
| 165 | 165 | awaitSnapshotIdle(t, sess) | |
| @@ -181,7 +181,7 @@ func TestNamedSessionPublishesNextCheckpointAfterClientSnatch(t *testing.T) { | |||
| 181 | 181 | require.Same(t, sess, snatchedSession, "client snatch must retain the live session") | |
| 182 | 182 | require.NotSame(t, firstClient, secondClient, "client B must displace client A") | |
| 183 | 183 | ||
| 184 | - | sess.activeTab().focusedPane().screen.Write([]byte("second checkpoint")) | |
| 184 | + | testAttachmentTab(sess).focusedPane().screen.Write([]byte("second checkpoint")) | |
| 185 | 185 | markSnapshotDirty(sess) | |
| 186 | 186 | require.True(t, d.scheduleFinalSnapshot(sess)) | |
| 187 | 187 | awaitSnapshotIdle(t, sess) | |
| @@ -223,7 +223,7 @@ func TestStoppedNamedSessionResumePublishesNextCheckpointInSameDaemon(t *testing | |||
| 223 | 223 | sess, _, err := d.route(snapshotLifecycleHello(ports.IntentNew, "work"), &closeTrackingTransport{}) | |
| 224 | 224 | require.NoError(t, err) | |
| 225 | 225 | ||
| 226 | - | sess.activeTab().focusedPane().screen.Write([]byte("first checkpoint")) | |
| 226 | + | testAttachmentTab(sess).focusedPane().screen.Write([]byte("first checkpoint")) | |
| 227 | 227 | markSnapshotDirty(sess) | |
| 228 | 228 | require.True(t, d.scheduleSnapshot(sess)) | |
| 229 | 229 | awaitSnapshotClean(t, sess) | |
| @@ -247,7 +247,7 @@ func TestStoppedNamedSessionResumePublishesNextCheckpointInSameDaemon(t *testing | |||
| 247 | 247 | require.NotSame(t, sess, resumed, "stopped-session attach must create a new live runtime") | |
| 248 | 248 | require.Equal(t, sess.incarnation, resumed.incarnation, "resume must retain durable incarnation authority") | |
| 249 | 249 | ||
| 250 | - | resumed.activeTab().focusedPane().screen.Write([]byte("checkpoint after resume")) | |
| 250 | + | testAttachmentTab(resumed).focusedPane().screen.Write([]byte("checkpoint after resume")) | |
| 251 | 251 | markSnapshotDirty(resumed) | |
| 252 | 252 | require.True(t, d.scheduleSnapshot(resumed)) | |
| 253 | 253 | awaitSnapshotIdle(t, resumed) | |
| @@ -159,7 +159,7 @@ func TestNamedSessionPublishesNextCheckpointAfterClientSnatch(t *testing.T) { | |||||
| 159 | sess, firstClient, err := d.route(snapshotLifecycleHello(ports.IntentNew, "work"), firstTransport) | 159 | sess, firstClient, err := d.route(snapshotLifecycleHello(ports.IntentNew, "work"), firstTransport) | ||
| 160 | require.NoError(t, err) | 160 | require.NoError(t, err) | ||
| 161 | 161 | ||||
| 162 | - | sess.activeTab().focusedPane().screen.Write([]byte("first checkpoint")) | 162 | + | testAttachmentTab(sess).focusedPane().screen.Write([]byte("first checkpoint")) |
| 163 | markSnapshotDirty(sess) | 163 | markSnapshotDirty(sess) | ||
| 164 | require.True(t, d.scheduleSnapshot(sess)) | 164 | require.True(t, d.scheduleSnapshot(sess)) | ||
| 165 | awaitSnapshotIdle(t, sess) | 165 | awaitSnapshotIdle(t, sess) | ||
| @@ -181,7 +181,7 @@ func TestNamedSessionPublishesNextCheckpointAfterClientSnatch(t *testing.T) { | |||||
| 181 | require.Same(t, sess, snatchedSession, "client snatch must retain the live session") | 181 | require.Same(t, sess, snatchedSession, "client snatch must retain the live session") | ||
| 182 | require.NotSame(t, firstClient, secondClient, "client B must displace client A") | 182 | require.NotSame(t, firstClient, secondClient, "client B must displace client A") | ||
| 183 | 183 | ||||
| 184 | - | sess.activeTab().focusedPane().screen.Write([]byte("second checkpoint")) | 184 | + | testAttachmentTab(sess).focusedPane().screen.Write([]byte("second checkpoint")) |
| 185 | markSnapshotDirty(sess) | 185 | markSnapshotDirty(sess) | ||
| 186 | require.True(t, d.scheduleFinalSnapshot(sess)) | 186 | require.True(t, d.scheduleFinalSnapshot(sess)) | ||
| 187 | awaitSnapshotIdle(t, sess) | 187 | awaitSnapshotIdle(t, sess) | ||
| @@ -223,7 +223,7 @@ func TestStoppedNamedSessionResumePublishesNextCheckpointInSameDaemon(t *testing | |||||
| 223 | sess, _, err := d.route(snapshotLifecycleHello(ports.IntentNew, "work"), &closeTrackingTransport{}) | 223 | sess, _, err := d.route(snapshotLifecycleHello(ports.IntentNew, "work"), &closeTrackingTransport{}) | ||
| 224 | require.NoError(t, err) | 224 | require.NoError(t, err) | ||
| 225 | 225 | ||||
| 226 | - | sess.activeTab().focusedPane().screen.Write([]byte("first checkpoint")) | 226 | + | testAttachmentTab(sess).focusedPane().screen.Write([]byte("first checkpoint")) |
| 227 | markSnapshotDirty(sess) | 227 | markSnapshotDirty(sess) | ||
| 228 | require.True(t, d.scheduleSnapshot(sess)) | 228 | require.True(t, d.scheduleSnapshot(sess)) | ||
| 229 | awaitSnapshotClean(t, sess) | 229 | awaitSnapshotClean(t, sess) | ||
| @@ -247,7 +247,7 @@ func TestStoppedNamedSessionResumePublishesNextCheckpointInSameDaemon(t *testing | |||||
| 247 | require.NotSame(t, sess, resumed, "stopped-session attach must create a new live runtime") | 247 | require.NotSame(t, sess, resumed, "stopped-session attach must create a new live runtime") | ||
| 248 | require.Equal(t, sess.incarnation, resumed.incarnation, "resume must retain durable incarnation authority") | 248 | require.Equal(t, sess.incarnation, resumed.incarnation, "resume must retain durable incarnation authority") | ||
| 249 | 249 | ||||
| 250 | - | resumed.activeTab().focusedPane().screen.Write([]byte("checkpoint after resume")) | 250 | + | testAttachmentTab(resumed).focusedPane().screen.Write([]byte("checkpoint after resume")) |
| 251 | markSnapshotDirty(resumed) | 251 | markSnapshotDirty(resumed) | ||
| 252 | require.True(t, d.scheduleSnapshot(resumed)) | 252 | require.True(t, d.scheduleSnapshot(resumed)) | ||
| 253 | awaitSnapshotIdle(t, resumed) | 253 | awaitSnapshotIdle(t, resumed) | ||
M internal/usecase/daemon/snapshot_test.go +1 -1
| @@ -89,7 +89,7 @@ func newSnapshotTestSession(t *testing.T, name string, ephemeral bool, cwd strin | |||
| 89 | 89 | p := tb.panes["pane-1"] | |
| 90 | 90 | p.screen.Write([]byte("hello")) | |
| 91 | 91 | appendHistoryRow(t, p.history, []renderer.Cell{{Rune: 'h'}, {Rune: 'i'}}) | |
| 92 | - | sess := &session{sessionCore: sessionCore{id: domain.SessionID("sess-" + name), name: name, ephemeral: ephemeral, createdAt: 42}, ctx: context.Background(), cancel: func() {}, tabs: []*tab{tb}, active: 0, cwd: cwd} | |
| 92 | + | sess := &session{sessionCore: sessionCore{id: domain.SessionID("sess-" + name), name: name, ephemeral: ephemeral, createdAt: 42}, ctx: context.Background(), cancel: func() {}, tabs: []*tab{tb}, cwd: cwd} | |
| 93 | 93 | if !ephemeral { | |
| 94 | 94 | sess.incarnation = domain.IncarnationID{1} | |
| 95 | 95 | } | |
| @@ -89,7 +89,7 @@ func newSnapshotTestSession(t *testing.T, name string, ephemeral bool, cwd strin | |||||
| 89 | p := tb.panes["pane-1"] | 89 | p := tb.panes["pane-1"] | ||
| 90 | p.screen.Write([]byte("hello")) | 90 | p.screen.Write([]byte("hello")) | ||
| 91 | appendHistoryRow(t, p.history, []renderer.Cell{{Rune: 'h'}, {Rune: 'i'}}) | 91 | appendHistoryRow(t, p.history, []renderer.Cell{{Rune: 'h'}, {Rune: 'i'}}) | ||
| 92 | - | sess := &session{sessionCore: sessionCore{id: domain.SessionID("sess-" + name), name: name, ephemeral: ephemeral, createdAt: 42}, ctx: context.Background(), cancel: func() {}, tabs: []*tab{tb}, active: 0, cwd: cwd} | 92 | + | sess := &session{sessionCore: sessionCore{id: domain.SessionID("sess-" + name), name: name, ephemeral: ephemeral, createdAt: 42}, ctx: context.Background(), cancel: func() {}, tabs: []*tab{tb}, cwd: cwd} |
| 93 | if !ephemeral { | 93 | if !ephemeral { | ||
| 94 | sess.incarnation = domain.IncarnationID{1} | 94 | sess.incarnation = domain.IncarnationID{1} | ||
| 95 | } | 95 | } | ||
M internal/usecase/daemon/status_test.go +17 -17
| @@ -34,7 +34,7 @@ func (*coreNilAttachment) statusSegments(bool) statusSnapshot { return statusSna | |||
| 34 | 34 | func (*coreNilAttachment) capturePrimary(*attachedClient, primaryCaptureRequest) (*capturedRenderState, bool) { | |
| 35 | 35 | return nil, false | |
| 36 | 36 | } | |
| 37 | - | func (*coreNilAttachment) activateTargetLocked(int) bool { return false } | |
| 37 | + | func (*coreNilAttachment) validTargetTabLocked(int) bool { return false } | |
| 38 | 38 | func (*coreNilAttachment) isProxy() bool { return false } | |
| 39 | 39 | ||
| 40 | 40 | // stubClock returns timers whose channel never fires, so a scheduler under it | |
| @@ -46,7 +46,7 @@ func TestApplyConfigThemeRepaintInvalidatesComposedFrameCache(t *testing.T) { | |||
| 46 | 46 | defer releasePTY() | |
| 47 | 47 | d.ApplyConfig(domain.Config{Theme: domain.ThemeLight}) | |
| 48 | 48 | ||
| 49 | - | win := sess.activeTab() | |
| 49 | + | win := testAttachmentTab(sess) | |
| 50 | 50 | left := win.focusedPane() | |
| 51 | 51 | right := newPane("pane-2", nil, domain.Size{Cols: 20, Rows: 4}) | |
| 52 | 52 | win.mu.Lock() | |
| @@ -89,13 +89,13 @@ func TestApplyConfigThemeRepaintInvalidatesComposedFrameCache(t *testing.T) { | |||
| 89 | 89 | func TestStatusCompositionGolden(t *testing.T) { | |
| 90 | 90 | p1, releasePTY1 := newBlockingPTY(t) | |
| 91 | 91 | p2, releasePTY2 := newBlockingPTY(t) | |
| 92 | - | _, sess, _, _ := newManualSessionWithPTYs(t, p1, p2) | |
| 92 | + | _, sess, ac, _ := newManualSessionWithPTYs(t, p1, p2) | |
| 93 | 93 | defer releasePTY1() | |
| 94 | 94 | defer releasePTY2() | |
| 95 | - | sess.active = 1 | |
| 95 | + | selectTestAttachmentTab(sess, 1) | |
| 96 | 96 | sess.name = "work" | |
| 97 | 97 | ||
| 98 | - | win := sess.activeTab() | |
| 98 | + | win := testAttachmentTab(sess) | |
| 99 | 99 | win.focusedPane().screen.Resize(12, 2) | |
| 100 | 100 | win.size = domain.Size{Cols: 12, Rows: 2} | |
| 101 | 101 | win.focusedPane().screen.Write([]byte("hello")) | |
| @@ -106,7 +106,7 @@ func TestStatusCompositionGolden(t *testing.T) { | |||
| 106 | 106 | panes: []capturedPaneRenderState{{ | |
| 107 | 107 | id: win.focusedPane().id, frame: win.focusedPane().screen.Frame.Clone(), placement: layout.Placement{ID: win.focusedPane().id, Content: domain.Rect{Width: 12, Height: 2}}, focused: true, damage: []renderer.Damage{renderer.FullRedraw()}, | |
| 108 | 108 | }}, | |
| 109 | - | bars: barState{status: sess.statusSegments(true)}, | |
| 109 | + | bars: barState{status: sess.statusSegmentsFor(ac, true)}, | |
| 110 | 110 | } | |
| 111 | 111 | composed := composeFrame(frameState, composeCacheInput{}) | |
| 112 | 112 | frame, damage := composed.frame, composed.damage | |
| @@ -418,7 +418,7 @@ func TestStatusCompositionUsesTruecolorTheme(t *testing.T) { | |||
| 418 | 418 | p, release := newBlockingPTY(t) | |
| 419 | 419 | _, sess, ac, _ := newManualSessionWithPTYs(t, p) | |
| 420 | 420 | defer release() | |
| 421 | - | win := sess.activeTab() | |
| 421 | + | win := testAttachmentTab(sess) | |
| 422 | 422 | win.focusedPane().screen.Resize(12, 2) | |
| 423 | 423 | win.size = domain.Size{Cols: 12, Rows: 2} | |
| 424 | 424 | ac.setThemeForTest(themeui.Theme{ | |
| @@ -462,7 +462,7 @@ func TestApplyThemePropagatesToFloatingPane(t *testing.T) { | |||
| 462 | 462 | p, releasePTY := newBlockingPTY(t) | |
| 463 | 463 | defer releasePTY() | |
| 464 | 464 | d, sess, ac, _ := newManualSessionWithPTYs(t, p) | |
| 465 | - | tb := sess.activeTab() | |
| 465 | + | tb := testAttachmentTab(sess) | |
| 466 | 466 | floating := newPane("floating", nil, domain.Size{Cols: 20, Rows: 5}) | |
| 467 | 467 | installTestFloating(tb, floating, true) | |
| 468 | 468 | clientTheme := ports.Theme{ | |
| @@ -694,7 +694,7 @@ func TestAttachClientClearsStaleScreenDefaultColors(t *testing.T) { | |||
| 694 | 694 | require.NoError(t, err) | |
| 695 | 695 | ||
| 696 | 696 | var got []byte | |
| 697 | - | tb := sess.activeTab() | |
| 697 | + | tb := testAttachmentTab(sess) | |
| 698 | 698 | tb.focusedPane().screen.OnResponse = func(b []byte) { got = append(got, b...) } | |
| 699 | 699 | tb.focusedPane().screen.Write([]byte("\x1b]10;?\a\x1b]11;?\a")) | |
| 700 | 700 | require.Empty(t, got) | |
| @@ -710,7 +710,7 @@ func TestClientGoneResetsScreenDefaultColors(t *testing.T) { | |||
| 710 | 710 | }) | |
| 711 | 711 | ||
| 712 | 712 | // Sanity: the tab answers OSC 10/11 while ac is attached. | |
| 713 | - | tb := sess.activeTab() | |
| 713 | + | tb := testAttachmentTab(sess) | |
| 714 | 714 | var before []byte | |
| 715 | 715 | tb.focusedPane().screen.OnResponse = func(b []byte) { before = append(before, b...) } | |
| 716 | 716 | tb.focusedPane().screen.Write([]byte("\x1b]10;?\a\x1b]11;?\a")) | |
| @@ -751,7 +751,7 @@ func TestClientGoneResetDoesNotClobberNewlyAttachedClient(t *testing.T) { | |||
| 751 | 751 | // new client's freshly applied colors. | |
| 752 | 752 | d.resetScreenDefaultColors(sess) | |
| 753 | 753 | ||
| 754 | - | tb := sess.activeTab() | |
| 754 | + | tb := testAttachmentTab(sess) | |
| 755 | 755 | var got []byte | |
| 756 | 756 | tb.focusedPane().screen.OnResponse = func(b []byte) { got = append(got, b...) } | |
| 757 | 757 | tb.focusedPane().screen.Write([]byte("\x1b]10;?\a\x1b]11;?\a")) | |
| @@ -769,7 +769,7 @@ func TestDetachOnSendErrorResetsScreenDefaultColors(t *testing.T) { | |||
| 769 | 769 | ||
| 770 | 770 | d.detachOnSendError(sess, ac, ac.transport()) | |
| 771 | 771 | ||
| 772 | - | tb := sess.activeTab() | |
| 772 | + | tb := testAttachmentTab(sess) | |
| 773 | 773 | var got []byte | |
| 774 | 774 | tb.focusedPane().screen.OnResponse = func(b []byte) { got = append(got, b...) } | |
| 775 | 775 | tb.focusedPane().screen.Write([]byte("\x1b]10;?\a\x1b]11;?\a")) | |
| @@ -788,7 +788,7 @@ func TestDetachOnSendErrorParkPreservesScreenDefaultColors(t *testing.T) { | |||
| 788 | 788 | ||
| 789 | 789 | d.detachOnSendError(sess, ac, ac.transport()) | |
| 790 | 790 | ||
| 791 | - | tb := sess.activeTab() | |
| 791 | + | tb := testAttachmentTab(sess) | |
| 792 | 792 | var got []byte | |
| 793 | 793 | tb.focusedPane().screen.OnResponse = func(b []byte) { got = append(got, b...) } | |
| 794 | 794 | tb.focusedPane().screen.Write([]byte("\x1b]10;?\a\x1b]11;?\a")) | |
| @@ -809,7 +809,7 @@ func TestApplyThemeIgnoresReplacedClient(t *testing.T) { | |||
| 809 | 809 | }) | |
| 810 | 810 | ||
| 811 | 811 | var got []byte | |
| 812 | - | tb := sess.activeTab() | |
| 812 | + | tb := testAttachmentTab(sess) | |
| 813 | 813 | tb.focusedPane().screen.OnResponse = func(b []byte) { got = append(got, b...) } | |
| 814 | 814 | tb.focusedPane().screen.Write([]byte("\x1b]10;?\a\x1b]11;?\a")) | |
| 815 | 815 | require.Empty(t, got) | |
| @@ -821,7 +821,7 @@ func TestStatusMarksEphemeralSession(t *testing.T) { | |||
| 821 | 821 | defer release() | |
| 822 | 822 | sess.name = "0" | |
| 823 | 823 | sess.ephemeral = true | |
| 824 | - | win := sess.activeTab() | |
| 824 | + | win := testAttachmentTab(sess) | |
| 825 | 825 | win.focusedPane().screen.Resize(12, 2) | |
| 826 | 826 | win.size = domain.Size{Cols: 12, Rows: 2} | |
| 827 | 827 | ||
| @@ -888,7 +888,7 @@ func TestStatusCopyFeedbackRendersOnlyWhenFullyFits(t *testing.T) { | |||
| 888 | 888 | _, sess, _, _ := newManualSessionWithPTYs(t, p) | |
| 889 | 889 | defer releasePTY() | |
| 890 | 890 | sess.name = "work" | |
| 891 | - | win := sess.activeTab() | |
| 891 | + | win := testAttachmentTab(sess) | |
| 892 | 892 | win.focusedPane().screen.Resize(30, 2) | |
| 893 | 893 | win.size = domain.Size{Cols: 30, Rows: 2} | |
| 894 | 894 | ||
| @@ -960,7 +960,7 @@ func TestTopBarRendersAttentionBell(t *testing.T) { | |||
| 960 | 960 | _, sess, _, _ := newManualSessionWithPTYs(t, p1, p2) | |
| 961 | 961 | defer releasePTY1() | |
| 962 | 962 | defer releasePTY2() | |
| 963 | - | win := sess.activeTab() | |
| 963 | + | win := testAttachmentTab(sess) | |
| 964 | 964 | win.focusedPane().screen.Resize(18, 2) | |
| 965 | 965 | win.size = domain.Size{Cols: 18, Rows: 2} | |
| 966 | 966 | sess.mu.Lock() | |
| @@ -34,7 +34,7 @@ func (*coreNilAttachment) statusSegments(bool) statusSnapshot { return statusSna | |||||
| 34 | func (*coreNilAttachment) capturePrimary(*attachedClient, primaryCaptureRequest) (*capturedRenderState, bool) { | 34 | func (*coreNilAttachment) capturePrimary(*attachedClient, primaryCaptureRequest) (*capturedRenderState, bool) { | ||
| 35 | return nil, false | 35 | return nil, false | ||
| 36 | } | 36 | } | ||
| 37 | - | func (*coreNilAttachment) activateTargetLocked(int) bool { return false } | 37 | + | func (*coreNilAttachment) validTargetTabLocked(int) bool { return false } |
| 38 | func (*coreNilAttachment) isProxy() bool { return false } | 38 | func (*coreNilAttachment) isProxy() bool { return false } | ||
| 39 | 39 | ||||
| 40 | // stubClock returns timers whose channel never fires, so a scheduler under it | 40 | // stubClock returns timers whose channel never fires, so a scheduler under it | ||
| @@ -46,7 +46,7 @@ func TestApplyConfigThemeRepaintInvalidatesComposedFrameCache(t *testing.T) { | |||||
| 46 | defer releasePTY() | 46 | defer releasePTY() | ||
| 47 | d.ApplyConfig(domain.Config{Theme: domain.ThemeLight}) | 47 | d.ApplyConfig(domain.Config{Theme: domain.ThemeLight}) | ||
| 48 | 48 | ||||
| 49 | - | win := sess.activeTab() | 49 | + | win := testAttachmentTab(sess) |
| 50 | left := win.focusedPane() | 50 | left := win.focusedPane() | ||
| 51 | right := newPane("pane-2", nil, domain.Size{Cols: 20, Rows: 4}) | 51 | right := newPane("pane-2", nil, domain.Size{Cols: 20, Rows: 4}) | ||
| 52 | win.mu.Lock() | 52 | win.mu.Lock() | ||
| @@ -89,13 +89,13 @@ func TestApplyConfigThemeRepaintInvalidatesComposedFrameCache(t *testing.T) { | |||||
| 89 | func TestStatusCompositionGolden(t *testing.T) { | 89 | func TestStatusCompositionGolden(t *testing.T) { | ||
| 90 | p1, releasePTY1 := newBlockingPTY(t) | 90 | p1, releasePTY1 := newBlockingPTY(t) | ||
| 91 | p2, releasePTY2 := newBlockingPTY(t) | 91 | p2, releasePTY2 := newBlockingPTY(t) | ||
| 92 | - | _, sess, _, _ := newManualSessionWithPTYs(t, p1, p2) | 92 | + | _, sess, ac, _ := newManualSessionWithPTYs(t, p1, p2) |
| 93 | defer releasePTY1() | 93 | defer releasePTY1() | ||
| 94 | defer releasePTY2() | 94 | defer releasePTY2() | ||
| 95 | - | sess.active = 1 | 95 | + | selectTestAttachmentTab(sess, 1) |
| 96 | sess.name = "work" | 96 | sess.name = "work" | ||
| 97 | 97 | ||||
| 98 | - | win := sess.activeTab() | 98 | + | win := testAttachmentTab(sess) |
| 99 | win.focusedPane().screen.Resize(12, 2) | 99 | win.focusedPane().screen.Resize(12, 2) | ||
| 100 | win.size = domain.Size{Cols: 12, Rows: 2} | 100 | win.size = domain.Size{Cols: 12, Rows: 2} | ||
| 101 | win.focusedPane().screen.Write([]byte("hello")) | 101 | win.focusedPane().screen.Write([]byte("hello")) | ||
| @@ -106,7 +106,7 @@ func TestStatusCompositionGolden(t *testing.T) { | |||||
| 106 | panes: []capturedPaneRenderState{{ | 106 | panes: []capturedPaneRenderState{{ | ||
| 107 | id: win.focusedPane().id, frame: win.focusedPane().screen.Frame.Clone(), placement: layout.Placement{ID: win.focusedPane().id, Content: domain.Rect{Width: 12, Height: 2}}, focused: true, damage: []renderer.Damage{renderer.FullRedraw()}, | 107 | id: win.focusedPane().id, frame: win.focusedPane().screen.Frame.Clone(), placement: layout.Placement{ID: win.focusedPane().id, Content: domain.Rect{Width: 12, Height: 2}}, focused: true, damage: []renderer.Damage{renderer.FullRedraw()}, | ||
| 108 | }}, | 108 | }}, | ||
| 109 | - | bars: barState{status: sess.statusSegments(true)}, | 109 | + | bars: barState{status: sess.statusSegmentsFor(ac, true)}, |
| 110 | } | 110 | } | ||
| 111 | composed := composeFrame(frameState, composeCacheInput{}) | 111 | composed := composeFrame(frameState, composeCacheInput{}) | ||
| 112 | frame, damage := composed.frame, composed.damage | 112 | frame, damage := composed.frame, composed.damage | ||
| @@ -418,7 +418,7 @@ func TestStatusCompositionUsesTruecolorTheme(t *testing.T) { | |||||
| 418 | p, release := newBlockingPTY(t) | 418 | p, release := newBlockingPTY(t) | ||
| 419 | _, sess, ac, _ := newManualSessionWithPTYs(t, p) | 419 | _, sess, ac, _ := newManualSessionWithPTYs(t, p) | ||
| 420 | defer release() | 420 | defer release() | ||
| 421 | - | win := sess.activeTab() | 421 | + | win := testAttachmentTab(sess) |
| 422 | win.focusedPane().screen.Resize(12, 2) | 422 | win.focusedPane().screen.Resize(12, 2) | ||
| 423 | win.size = domain.Size{Cols: 12, Rows: 2} | 423 | win.size = domain.Size{Cols: 12, Rows: 2} | ||
| 424 | ac.setThemeForTest(themeui.Theme{ | 424 | ac.setThemeForTest(themeui.Theme{ | ||
| @@ -462,7 +462,7 @@ func TestApplyThemePropagatesToFloatingPane(t *testing.T) { | |||||
| 462 | p, releasePTY := newBlockingPTY(t) | 462 | p, releasePTY := newBlockingPTY(t) | ||
| 463 | defer releasePTY() | 463 | defer releasePTY() | ||
| 464 | d, sess, ac, _ := newManualSessionWithPTYs(t, p) | 464 | d, sess, ac, _ := newManualSessionWithPTYs(t, p) | ||
| 465 | - | tb := sess.activeTab() | 465 | + | tb := testAttachmentTab(sess) |
| 466 | floating := newPane("floating", nil, domain.Size{Cols: 20, Rows: 5}) | 466 | floating := newPane("floating", nil, domain.Size{Cols: 20, Rows: 5}) | ||
| 467 | installTestFloating(tb, floating, true) | 467 | installTestFloating(tb, floating, true) | ||
| 468 | clientTheme := ports.Theme{ | 468 | clientTheme := ports.Theme{ | ||
| @@ -694,7 +694,7 @@ func TestAttachClientClearsStaleScreenDefaultColors(t *testing.T) { | |||||
| 694 | require.NoError(t, err) | 694 | require.NoError(t, err) | ||
| 695 | 695 | ||||
| 696 | var got []byte | 696 | var got []byte | ||
| 697 | - | tb := sess.activeTab() | 697 | + | tb := testAttachmentTab(sess) |
| 698 | tb.focusedPane().screen.OnResponse = func(b []byte) { got = append(got, b...) } | 698 | tb.focusedPane().screen.OnResponse = func(b []byte) { got = append(got, b...) } | ||
| 699 | tb.focusedPane().screen.Write([]byte("\x1b]10;?\a\x1b]11;?\a")) | 699 | tb.focusedPane().screen.Write([]byte("\x1b]10;?\a\x1b]11;?\a")) | ||
| 700 | require.Empty(t, got) | 700 | require.Empty(t, got) | ||
| @@ -710,7 +710,7 @@ func TestClientGoneResetsScreenDefaultColors(t *testing.T) { | |||||
| 710 | }) | 710 | }) | ||
| 711 | 711 | ||||
| 712 | // Sanity: the tab answers OSC 10/11 while ac is attached. | 712 | // Sanity: the tab answers OSC 10/11 while ac is attached. | ||
| 713 | - | tb := sess.activeTab() | 713 | + | tb := testAttachmentTab(sess) |
| 714 | var before []byte | 714 | var before []byte | ||
| 715 | tb.focusedPane().screen.OnResponse = func(b []byte) { before = append(before, b...) } | 715 | tb.focusedPane().screen.OnResponse = func(b []byte) { before = append(before, b...) } | ||
| 716 | tb.focusedPane().screen.Write([]byte("\x1b]10;?\a\x1b]11;?\a")) | 716 | tb.focusedPane().screen.Write([]byte("\x1b]10;?\a\x1b]11;?\a")) | ||
| @@ -751,7 +751,7 @@ func TestClientGoneResetDoesNotClobberNewlyAttachedClient(t *testing.T) { | |||||
| 751 | // new client's freshly applied colors. | 751 | // new client's freshly applied colors. | ||
| 752 | d.resetScreenDefaultColors(sess) | 752 | d.resetScreenDefaultColors(sess) | ||
| 753 | 753 | ||||
| 754 | - | tb := sess.activeTab() | 754 | + | tb := testAttachmentTab(sess) |
| 755 | var got []byte | 755 | var got []byte | ||
| 756 | tb.focusedPane().screen.OnResponse = func(b []byte) { got = append(got, b...) } | 756 | tb.focusedPane().screen.OnResponse = func(b []byte) { got = append(got, b...) } | ||
| 757 | tb.focusedPane().screen.Write([]byte("\x1b]10;?\a\x1b]11;?\a")) | 757 | tb.focusedPane().screen.Write([]byte("\x1b]10;?\a\x1b]11;?\a")) | ||
| @@ -769,7 +769,7 @@ func TestDetachOnSendErrorResetsScreenDefaultColors(t *testing.T) { | |||||
| 769 | 769 | ||||
| 770 | d.detachOnSendError(sess, ac, ac.transport()) | 770 | d.detachOnSendError(sess, ac, ac.transport()) | ||
| 771 | 771 | ||||
| 772 | - | tb := sess.activeTab() | 772 | + | tb := testAttachmentTab(sess) |
| 773 | var got []byte | 773 | var got []byte | ||
| 774 | tb.focusedPane().screen.OnResponse = func(b []byte) { got = append(got, b...) } | 774 | tb.focusedPane().screen.OnResponse = func(b []byte) { got = append(got, b...) } | ||
| 775 | tb.focusedPane().screen.Write([]byte("\x1b]10;?\a\x1b]11;?\a")) | 775 | tb.focusedPane().screen.Write([]byte("\x1b]10;?\a\x1b]11;?\a")) | ||
| @@ -788,7 +788,7 @@ func TestDetachOnSendErrorParkPreservesScreenDefaultColors(t *testing.T) { | |||||
| 788 | 788 | ||||
| 789 | d.detachOnSendError(sess, ac, ac.transport()) | 789 | d.detachOnSendError(sess, ac, ac.transport()) | ||
| 790 | 790 | ||||
| 791 | - | tb := sess.activeTab() | 791 | + | tb := testAttachmentTab(sess) |
| 792 | var got []byte | 792 | var got []byte | ||
| 793 | tb.focusedPane().screen.OnResponse = func(b []byte) { got = append(got, b...) } | 793 | tb.focusedPane().screen.OnResponse = func(b []byte) { got = append(got, b...) } | ||
| 794 | tb.focusedPane().screen.Write([]byte("\x1b]10;?\a\x1b]11;?\a")) | 794 | tb.focusedPane().screen.Write([]byte("\x1b]10;?\a\x1b]11;?\a")) | ||
| @@ -809,7 +809,7 @@ func TestApplyThemeIgnoresReplacedClient(t *testing.T) { | |||||
| 809 | }) | 809 | }) | ||
| 810 | 810 | ||||
| 811 | var got []byte | 811 | var got []byte | ||
| 812 | - | tb := sess.activeTab() | 812 | + | tb := testAttachmentTab(sess) |
| 813 | tb.focusedPane().screen.OnResponse = func(b []byte) { got = append(got, b...) } | 813 | tb.focusedPane().screen.OnResponse = func(b []byte) { got = append(got, b...) } | ||
| 814 | tb.focusedPane().screen.Write([]byte("\x1b]10;?\a\x1b]11;?\a")) | 814 | tb.focusedPane().screen.Write([]byte("\x1b]10;?\a\x1b]11;?\a")) | ||
| 815 | require.Empty(t, got) | 815 | require.Empty(t, got) | ||
| @@ -821,7 +821,7 @@ func TestStatusMarksEphemeralSession(t *testing.T) { | |||||
| 821 | defer release() | 821 | defer release() | ||
| 822 | sess.name = "0" | 822 | sess.name = "0" | ||
| 823 | sess.ephemeral = true | 823 | sess.ephemeral = true | ||
| 824 | - | win := sess.activeTab() | 824 | + | win := testAttachmentTab(sess) |
| 825 | win.focusedPane().screen.Resize(12, 2) | 825 | win.focusedPane().screen.Resize(12, 2) | ||
| 826 | win.size = domain.Size{Cols: 12, Rows: 2} | 826 | win.size = domain.Size{Cols: 12, Rows: 2} | ||
| 827 | 827 | ||||
| @@ -888,7 +888,7 @@ func TestStatusCopyFeedbackRendersOnlyWhenFullyFits(t *testing.T) { | |||||
| 888 | _, sess, _, _ := newManualSessionWithPTYs(t, p) | 888 | _, sess, _, _ := newManualSessionWithPTYs(t, p) | ||
| 889 | defer releasePTY() | 889 | defer releasePTY() | ||
| 890 | sess.name = "work" | 890 | sess.name = "work" | ||
| 891 | - | win := sess.activeTab() | 891 | + | win := testAttachmentTab(sess) |
| 892 | win.focusedPane().screen.Resize(30, 2) | 892 | win.focusedPane().screen.Resize(30, 2) | ||
| 893 | win.size = domain.Size{Cols: 30, Rows: 2} | 893 | win.size = domain.Size{Cols: 30, Rows: 2} | ||
| 894 | 894 | ||||
| @@ -960,7 +960,7 @@ func TestTopBarRendersAttentionBell(t *testing.T) { | |||||
| 960 | _, sess, _, _ := newManualSessionWithPTYs(t, p1, p2) | 960 | _, sess, _, _ := newManualSessionWithPTYs(t, p1, p2) | ||
| 961 | defer releasePTY1() | 961 | defer releasePTY1() | ||
| 962 | defer releasePTY2() | 962 | defer releasePTY2() | ||
| 963 | - | win := sess.activeTab() | 963 | + | win := testAttachmentTab(sess) |
| 964 | win.focusedPane().screen.Resize(18, 2) | 964 | win.focusedPane().screen.Resize(18, 2) | ||
| 965 | win.size = domain.Size{Cols: 18, Rows: 2} | 965 | win.size = domain.Size{Cols: 18, Rows: 2} | ||
| 966 | sess.mu.Lock() | 966 | sess.mu.Lock() | ||
M internal/usecase/daemon/transactional_resize_test.go +15 -15
| @@ -131,7 +131,7 @@ func TestReplayResizePendingBuffersSuccessFailureAndBatchOrder(t *testing.T) { | |||
| 131 | 131 | t.Run("success resizes then replays", func(t *testing.T) { | |
| 132 | 132 | pty := &transactionalResizePTY{} | |
| 133 | 133 | d, sess, _, _ := newManualSessionWithPTYs(t, pty) | |
| 134 | - | tb, p := sess.activeTab(), sess.activeTab().focusedPane() | |
| 134 | + | tb, p := testAttachmentTab(sess), testAttachmentTab(sess).focusedPane() | |
| 135 | 135 | p.resizeApplying = true | |
| 136 | 136 | p.resizePending = []byte("A\x1b[1;81HB") | |
| 137 | 137 | d.replayResizePending(sess, tb, p, true, domain.Rect{Width: 120, Height: 23}) | |
| @@ -142,7 +142,7 @@ func TestReplayResizePendingBuffersSuccessFailureAndBatchOrder(t *testing.T) { | |||
| 142 | 142 | t.Run("failure retains old parser width", func(t *testing.T) { | |
| 143 | 143 | pty := &transactionalResizePTY{} | |
| 144 | 144 | d, sess, _, _ := newManualSessionWithPTYs(t, pty) | |
| 145 | - | tb, p := sess.activeTab(), sess.activeTab().focusedPane() | |
| 145 | + | tb, p := testAttachmentTab(sess), testAttachmentTab(sess).focusedPane() | |
| 146 | 146 | p.resizeApplying = true | |
| 147 | 147 | p.resizePending = []byte("\x1b[1;81HB") | |
| 148 | 148 | d.replayResizePending(sess, tb, p, false, domain.Rect{Width: 120, Height: 23}) | |
| @@ -152,7 +152,7 @@ func TestReplayResizePendingBuffersSuccessFailureAndBatchOrder(t *testing.T) { | |||
| 152 | 152 | t.Run("batches retain read order", func(t *testing.T) { | |
| 153 | 153 | pty := &transactionalResizePTY{} | |
| 154 | 154 | d, sess, _, _ := newManualSessionWithPTYs(t, pty) | |
| 155 | - | tb, p := sess.activeTab(), sess.activeTab().focusedPane() | |
| 155 | + | tb, p := testAttachmentTab(sess), testAttachmentTab(sess).focusedPane() | |
| 156 | 156 | p.screen.OnResponse = func([]byte) { p.ptyResponses = append(p.ptyResponses, 'r') } | |
| 157 | 157 | pty.onWrite = func([]byte) { | |
| 158 | 158 | p.mu.Lock() | |
| @@ -171,7 +171,7 @@ func TestProcessPTYDataRetainsCallbacksDuringResizeReplay(t *testing.T) { | |||
| 171 | 171 | pty := &transactionalResizePTY{onWrite: func(b []byte) { responses <- b }} | |
| 172 | 172 | d, sess, ac, sends := newManualSessionWithPTYs(t, pty) | |
| 173 | 173 | publishActiveClipboardCapability(d, sess, ac, ac.transport()) | |
| 174 | - | tb, p := sess.activeTab(), sess.activeTab().focusedPane() | |
| 174 | + | tb, p := testAttachmentTab(sess), testAttachmentTab(sess).focusedPane() | |
| 175 | 175 | p.screen.OnResponse = func(b []byte) { p.ptyResponses = append(p.ptyResponses, b...) } | |
| 176 | 176 | p.screen.OnBell = func() { p.ptyAttention = true } | |
| 177 | 177 | p.screen.OnClipboard = func(b64 string) { p.ptyClipboards = append(p.ptyClipboards, b64) } | |
| @@ -207,7 +207,7 @@ func TestProcessPTYDataRetainsCallbacksDuringResizeReplay(t *testing.T) { | |||
| 207 | 207 | func TestApplySessionLayoutStopsRetryWhenSessionCanceled(t *testing.T) { | |
| 208 | 208 | pty := &transactionalResizePTY{} | |
| 209 | 209 | d, sess, _, _ := newManualSessionWithPTYs(t, pty) | |
| 210 | - | tb, p := sess.activeTab(), sess.activeTab().focusedPane() | |
| 210 | + | tb, p := testAttachmentTab(sess), testAttachmentTab(sess).focusedPane() | |
| 211 | 211 | d.beforeSessionResizePublication = sess.cancel | |
| 212 | 212 | ||
| 213 | 213 | _, ok := d.applySessionLayout(sess, domain.Size{Cols: 100, Rows: 30}, nil, nil) | |
| @@ -226,7 +226,7 @@ func TestApplySessionLayoutStopsRetryWhenSessionCanceled(t *testing.T) { | |||
| 226 | 226 | func TestTransactionalResizeApplyDoesNotHoldTabOrPaneLocks(t *testing.T) { | |
| 227 | 227 | pty := &transactionalResizePTY{} | |
| 228 | 228 | d, sess, _, _ := newManualSessionWithPTYs(t, pty) | |
| 229 | - | tb := sess.activeTab() | |
| 229 | + | tb := testAttachmentTab(sess) | |
| 230 | 230 | p := tb.focusedPane() | |
| 231 | 231 | var tabUnlocked, paneUnlocked bool | |
| 232 | 232 | pty.onResize = func() { | |
| @@ -291,7 +291,7 @@ func TestTransactionalResizePartialFailureCommitsOnlySuccessfulPTYState(t *testi | |||
| 291 | 291 | ok := &transactionalResizePTY{} | |
| 292 | 292 | failed := &transactionalResizePTY{errs: []error{errors.New("scripted resize failure")}} | |
| 293 | 293 | d, sess, ac, _ := newManualSessionWithPTYs(t, ok) | |
| 294 | - | tb := sess.activeTab() | |
| 294 | + | tb := testAttachmentTab(sess) | |
| 295 | 295 | second := newPane("pane-2", failed, domain.Size{Cols: 80, Rows: 23}) | |
| 296 | 296 | tb.mu.Lock() | |
| 297 | 297 | tb.panes[second.id] = second | |
| @@ -332,7 +332,7 @@ func TestTransactionalResizeRetryMarksNamedSnapshotDirtyOnlyOnSuccess(t *testing | |||
| 332 | 332 | pty := &transactionalResizePTY{errs: tc.errs} | |
| 333 | 333 | d, sess, ac, _ := newManualSessionWithPTYs(t, pty) | |
| 334 | 334 | sess.snapEligible.Store(true) | |
| 335 | - | tb, p := sess.activeTab(), sess.activeTab().focusedPane() | |
| 335 | + | tb, p := testAttachmentTab(sess), testAttachmentTab(sess).focusedPane() | |
| 336 | 336 | ||
| 337 | 337 | require.True(t, d.requestTransactionalResize(sess, ac, domain.Size{Cols: 100, Rows: 30}, true)) | |
| 338 | 338 | rc := sess.renderCoordinator() | |
| @@ -356,7 +356,7 @@ func TestTransactionalResizeRetryTargetsNewestCommittedEpoch(t *testing.T) { | |||
| 356 | 356 | ok := &transactionalResizePTY{} | |
| 357 | 357 | failed := &transactionalResizePTY{errs: []error{errors.New("first epoch fails"), nil}} | |
| 358 | 358 | d, sess, ac, _ := newManualSessionWithPTYs(t, ok) | |
| 359 | - | tb := sess.activeTab() | |
| 359 | + | tb := testAttachmentTab(sess) | |
| 360 | 360 | retry := newPane("retry", failed, domain.Size{Cols: 80, Rows: 23}) | |
| 361 | 361 | tb.mu.Lock() | |
| 362 | 362 | tb.panes[retry.id] = retry | |
| @@ -465,7 +465,7 @@ func TestTransactionalResizeRejectsNewerEpochBeforeSessionPublication(t *testing | |||
| 465 | 465 | func TestStaleRemovedMemberGateIsCanceledOnceByFreshPlan(t *testing.T) { | |
| 466 | 466 | pty := &transactionalResizePTY{} | |
| 467 | 467 | d, sess, _, _ := newManualSessionWithPTYs(t, pty) | |
| 468 | - | tb := sess.activeTab() | |
| 468 | + | tb := testAttachmentTab(sess) | |
| 469 | 469 | p := tb.focusedPane() | |
| 470 | 470 | p.resizeApplying = true | |
| 471 | 471 | p.resizePending = []byte("x") | |
| @@ -489,7 +489,7 @@ func TestStaleRemovedMemberGateIsCanceledOnceByFreshPlan(t *testing.T) { | |||
| 489 | 489 | func TestHeadlessResizeRetriesInvalidatedPlan(t *testing.T) { | |
| 490 | 490 | pty := &transactionalResizePTY{} | |
| 491 | 491 | d, sess, _, _ := newManualSessionWithPTYs(t, pty) | |
| 492 | - | tb := sess.activeTab() | |
| 492 | + | tb := testAttachmentTab(sess) | |
| 493 | 493 | var invalidate sync.Once | |
| 494 | 494 | pty.onResize = func() { | |
| 495 | 495 | invalidate.Do(func() { | |
| @@ -509,7 +509,7 @@ func TestHeadlessResizeRetriesInvalidatedPlan(t *testing.T) { | |||
| 509 | 509 | func TestRetryOwnerCannotPublishFloatingGeometryAfterMove(t *testing.T) { | |
| 510 | 510 | popupPTY := &transactionalResizePTY{errs: []error{errors.New("initial popup failure")}} | |
| 511 | 511 | d, source, ac, _ := newManualSessionWithPTYs(t, &transactionalResizePTY{}) | |
| 512 | - | tb := source.activeTab() | |
| 512 | + | tb := testAttachmentTab(source) | |
| 513 | 513 | popup := newPane("popup", popupPTY, domain.Size{Cols: 80, Rows: 23}) | |
| 514 | 514 | tb.mu.Lock() | |
| 515 | 515 | tb.floating = floatingSlot{state: floatingVisible, pane: popup, generation: 7} | |
| @@ -561,7 +561,7 @@ func TestTransactionalResizeFloatingBreakpointRace(t *testing.T) { | |||
| 561 | 561 | d.ApplyConfig(domain.Config{Floating: cfg}) | |
| 562 | 562 | require.True(t, d.requestTransactionalResize(sess, nil, tc.from, true)) | |
| 563 | 563 | ||
| 564 | - | tb := sess.activeTab() | |
| 564 | + | tb := testAttachmentTab(sess) | |
| 565 | 565 | fromGeometry := calculateContentFloatingGeometry(tabSize(tc.from), cfg) | |
| 566 | 566 | toGeometry := calculateContentFloatingGeometry(tabSize(tc.to), cfg) | |
| 567 | 567 | entered, release := make(chan struct{}), make(chan struct{}) | |
| @@ -735,7 +735,7 @@ func TestZeroInnerFloatingDrawerRejectsStaleRequestWithoutPhysicalPublication(t | |||
| 735 | 735 | func TestTransactionalResizeRetriesAcceptedFloatingSlotByIdentity(t *testing.T) { | |
| 736 | 736 | popupPTY := &transactionalResizePTY{errs: []error{errors.New("first popup resize fails")}} | |
| 737 | 737 | d, sess, ac, _ := newManualSessionWithPTYs(t, &transactionalResizePTY{}) | |
| 738 | - | tb := sess.activeTab() | |
| 738 | + | tb := testAttachmentTab(sess) | |
| 739 | 739 | popup := newPane("popup", popupPTY, domain.Size{Cols: 80, Rows: 23}) | |
| 740 | 740 | tb.mu.Lock() | |
| 741 | 741 | tb.floating = floatingSlot{state: floatingVisible, pane: popup, generation: 7} | |
| @@ -823,7 +823,7 @@ func TestTransactionalResizeFloatingMouseCopyAndSearchContract(t *testing.T) { | |||
| 823 | 823 | entered, release := make(chan struct{}), make(chan struct{}) | |
| 824 | 824 | pty := &transactionalResizePTY{errs: tc.errs, onResize: func() { close(entered); <-release }} | |
| 825 | 825 | d, sess, ac, _ := newManualSessionWithPTYs(t, pty) | |
| 826 | - | tb := sess.activeTab() | |
| 826 | + | tb := testAttachmentTab(sess) | |
| 827 | 827 | p := tb.focusedPane() | |
| 828 | 828 | old := p.rect | |
| 829 | 829 | d.enterCopyMode(sess, ac) | |
| @@ -131,7 +131,7 @@ func TestReplayResizePendingBuffersSuccessFailureAndBatchOrder(t *testing.T) { | |||||
| 131 | t.Run("success resizes then replays", func(t *testing.T) { | 131 | t.Run("success resizes then replays", func(t *testing.T) { | ||
| 132 | pty := &transactionalResizePTY{} | 132 | pty := &transactionalResizePTY{} | ||
| 133 | d, sess, _, _ := newManualSessionWithPTYs(t, pty) | 133 | d, sess, _, _ := newManualSessionWithPTYs(t, pty) | ||
| 134 | - | tb, p := sess.activeTab(), sess.activeTab().focusedPane() | 134 | + | tb, p := testAttachmentTab(sess), testAttachmentTab(sess).focusedPane() |
| 135 | p.resizeApplying = true | 135 | p.resizeApplying = true | ||
| 136 | p.resizePending = []byte("A\x1b[1;81HB") | 136 | p.resizePending = []byte("A\x1b[1;81HB") | ||
| 137 | d.replayResizePending(sess, tb, p, true, domain.Rect{Width: 120, Height: 23}) | 137 | d.replayResizePending(sess, tb, p, true, domain.Rect{Width: 120, Height: 23}) | ||
| @@ -142,7 +142,7 @@ func TestReplayResizePendingBuffersSuccessFailureAndBatchOrder(t *testing.T) { | |||||
| 142 | t.Run("failure retains old parser width", func(t *testing.T) { | 142 | t.Run("failure retains old parser width", func(t *testing.T) { | ||
| 143 | pty := &transactionalResizePTY{} | 143 | pty := &transactionalResizePTY{} | ||
| 144 | d, sess, _, _ := newManualSessionWithPTYs(t, pty) | 144 | d, sess, _, _ := newManualSessionWithPTYs(t, pty) | ||
| 145 | - | tb, p := sess.activeTab(), sess.activeTab().focusedPane() | 145 | + | tb, p := testAttachmentTab(sess), testAttachmentTab(sess).focusedPane() |
| 146 | p.resizeApplying = true | 146 | p.resizeApplying = true | ||
| 147 | p.resizePending = []byte("\x1b[1;81HB") | 147 | p.resizePending = []byte("\x1b[1;81HB") | ||
| 148 | d.replayResizePending(sess, tb, p, false, domain.Rect{Width: 120, Height: 23}) | 148 | d.replayResizePending(sess, tb, p, false, domain.Rect{Width: 120, Height: 23}) | ||
| @@ -152,7 +152,7 @@ func TestReplayResizePendingBuffersSuccessFailureAndBatchOrder(t *testing.T) { | |||||
| 152 | t.Run("batches retain read order", func(t *testing.T) { | 152 | t.Run("batches retain read order", func(t *testing.T) { | ||
| 153 | pty := &transactionalResizePTY{} | 153 | pty := &transactionalResizePTY{} | ||
| 154 | d, sess, _, _ := newManualSessionWithPTYs(t, pty) | 154 | d, sess, _, _ := newManualSessionWithPTYs(t, pty) | ||
| 155 | - | tb, p := sess.activeTab(), sess.activeTab().focusedPane() | 155 | + | tb, p := testAttachmentTab(sess), testAttachmentTab(sess).focusedPane() |
| 156 | p.screen.OnResponse = func([]byte) { p.ptyResponses = append(p.ptyResponses, 'r') } | 156 | p.screen.OnResponse = func([]byte) { p.ptyResponses = append(p.ptyResponses, 'r') } | ||
| 157 | pty.onWrite = func([]byte) { | 157 | pty.onWrite = func([]byte) { | ||
| 158 | p.mu.Lock() | 158 | p.mu.Lock() | ||
| @@ -171,7 +171,7 @@ func TestProcessPTYDataRetainsCallbacksDuringResizeReplay(t *testing.T) { | |||||
| 171 | pty := &transactionalResizePTY{onWrite: func(b []byte) { responses <- b }} | 171 | pty := &transactionalResizePTY{onWrite: func(b []byte) { responses <- b }} | ||
| 172 | d, sess, ac, sends := newManualSessionWithPTYs(t, pty) | 172 | d, sess, ac, sends := newManualSessionWithPTYs(t, pty) | ||
| 173 | publishActiveClipboardCapability(d, sess, ac, ac.transport()) | 173 | publishActiveClipboardCapability(d, sess, ac, ac.transport()) | ||
| 174 | - | tb, p := sess.activeTab(), sess.activeTab().focusedPane() | 174 | + | tb, p := testAttachmentTab(sess), testAttachmentTab(sess).focusedPane() |
| 175 | p.screen.OnResponse = func(b []byte) { p.ptyResponses = append(p.ptyResponses, b...) } | 175 | p.screen.OnResponse = func(b []byte) { p.ptyResponses = append(p.ptyResponses, b...) } | ||
| 176 | p.screen.OnBell = func() { p.ptyAttention = true } | 176 | p.screen.OnBell = func() { p.ptyAttention = true } | ||
| 177 | p.screen.OnClipboard = func(b64 string) { p.ptyClipboards = append(p.ptyClipboards, b64) } | 177 | p.screen.OnClipboard = func(b64 string) { p.ptyClipboards = append(p.ptyClipboards, b64) } | ||
| @@ -207,7 +207,7 @@ func TestProcessPTYDataRetainsCallbacksDuringResizeReplay(t *testing.T) { | |||||
| 207 | func TestApplySessionLayoutStopsRetryWhenSessionCanceled(t *testing.T) { | 207 | func TestApplySessionLayoutStopsRetryWhenSessionCanceled(t *testing.T) { | ||
| 208 | pty := &transactionalResizePTY{} | 208 | pty := &transactionalResizePTY{} | ||
| 209 | d, sess, _, _ := newManualSessionWithPTYs(t, pty) | 209 | d, sess, _, _ := newManualSessionWithPTYs(t, pty) | ||
| 210 | - | tb, p := sess.activeTab(), sess.activeTab().focusedPane() | 210 | + | tb, p := testAttachmentTab(sess), testAttachmentTab(sess).focusedPane() |
| 211 | d.beforeSessionResizePublication = sess.cancel | 211 | d.beforeSessionResizePublication = sess.cancel | ||
| 212 | 212 | ||||
| 213 | _, ok := d.applySessionLayout(sess, domain.Size{Cols: 100, Rows: 30}, nil, nil) | 213 | _, ok := d.applySessionLayout(sess, domain.Size{Cols: 100, Rows: 30}, nil, nil) | ||
| @@ -226,7 +226,7 @@ func TestApplySessionLayoutStopsRetryWhenSessionCanceled(t *testing.T) { | |||||
| 226 | func TestTransactionalResizeApplyDoesNotHoldTabOrPaneLocks(t *testing.T) { | 226 | func TestTransactionalResizeApplyDoesNotHoldTabOrPaneLocks(t *testing.T) { | ||
| 227 | pty := &transactionalResizePTY{} | 227 | pty := &transactionalResizePTY{} | ||
| 228 | d, sess, _, _ := newManualSessionWithPTYs(t, pty) | 228 | d, sess, _, _ := newManualSessionWithPTYs(t, pty) | ||
| 229 | - | tb := sess.activeTab() | 229 | + | tb := testAttachmentTab(sess) |
| 230 | p := tb.focusedPane() | 230 | p := tb.focusedPane() | ||
| 231 | var tabUnlocked, paneUnlocked bool | 231 | var tabUnlocked, paneUnlocked bool | ||
| 232 | pty.onResize = func() { | 232 | pty.onResize = func() { | ||
| @@ -291,7 +291,7 @@ func TestTransactionalResizePartialFailureCommitsOnlySuccessfulPTYState(t *testi | |||||
| 291 | ok := &transactionalResizePTY{} | 291 | ok := &transactionalResizePTY{} | ||
| 292 | failed := &transactionalResizePTY{errs: []error{errors.New("scripted resize failure")}} | 292 | failed := &transactionalResizePTY{errs: []error{errors.New("scripted resize failure")}} | ||
| 293 | d, sess, ac, _ := newManualSessionWithPTYs(t, ok) | 293 | d, sess, ac, _ := newManualSessionWithPTYs(t, ok) | ||
| 294 | - | tb := sess.activeTab() | 294 | + | tb := testAttachmentTab(sess) |
| 295 | second := newPane("pane-2", failed, domain.Size{Cols: 80, Rows: 23}) | 295 | second := newPane("pane-2", failed, domain.Size{Cols: 80, Rows: 23}) | ||
| 296 | tb.mu.Lock() | 296 | tb.mu.Lock() | ||
| 297 | tb.panes[second.id] = second | 297 | tb.panes[second.id] = second | ||
| @@ -332,7 +332,7 @@ func TestTransactionalResizeRetryMarksNamedSnapshotDirtyOnlyOnSuccess(t *testing | |||||
| 332 | pty := &transactionalResizePTY{errs: tc.errs} | 332 | pty := &transactionalResizePTY{errs: tc.errs} | ||
| 333 | d, sess, ac, _ := newManualSessionWithPTYs(t, pty) | 333 | d, sess, ac, _ := newManualSessionWithPTYs(t, pty) | ||
| 334 | sess.snapEligible.Store(true) | 334 | sess.snapEligible.Store(true) | ||
| 335 | - | tb, p := sess.activeTab(), sess.activeTab().focusedPane() | 335 | + | tb, p := testAttachmentTab(sess), testAttachmentTab(sess).focusedPane() |
| 336 | 336 | ||||
| 337 | require.True(t, d.requestTransactionalResize(sess, ac, domain.Size{Cols: 100, Rows: 30}, true)) | 337 | require.True(t, d.requestTransactionalResize(sess, ac, domain.Size{Cols: 100, Rows: 30}, true)) | ||
| 338 | rc := sess.renderCoordinator() | 338 | rc := sess.renderCoordinator() | ||
| @@ -356,7 +356,7 @@ func TestTransactionalResizeRetryTargetsNewestCommittedEpoch(t *testing.T) { | |||||
| 356 | ok := &transactionalResizePTY{} | 356 | ok := &transactionalResizePTY{} | ||
| 357 | failed := &transactionalResizePTY{errs: []error{errors.New("first epoch fails"), nil}} | 357 | failed := &transactionalResizePTY{errs: []error{errors.New("first epoch fails"), nil}} | ||
| 358 | d, sess, ac, _ := newManualSessionWithPTYs(t, ok) | 358 | d, sess, ac, _ := newManualSessionWithPTYs(t, ok) | ||
| 359 | - | tb := sess.activeTab() | 359 | + | tb := testAttachmentTab(sess) |
| 360 | retry := newPane("retry", failed, domain.Size{Cols: 80, Rows: 23}) | 360 | retry := newPane("retry", failed, domain.Size{Cols: 80, Rows: 23}) | ||
| 361 | tb.mu.Lock() | 361 | tb.mu.Lock() | ||
| 362 | tb.panes[retry.id] = retry | 362 | tb.panes[retry.id] = retry | ||
| @@ -465,7 +465,7 @@ func TestTransactionalResizeRejectsNewerEpochBeforeSessionPublication(t *testing | |||||
| 465 | func TestStaleRemovedMemberGateIsCanceledOnceByFreshPlan(t *testing.T) { | 465 | func TestStaleRemovedMemberGateIsCanceledOnceByFreshPlan(t *testing.T) { | ||
| 466 | pty := &transactionalResizePTY{} | 466 | pty := &transactionalResizePTY{} | ||
| 467 | d, sess, _, _ := newManualSessionWithPTYs(t, pty) | 467 | d, sess, _, _ := newManualSessionWithPTYs(t, pty) | ||
| 468 | - | tb := sess.activeTab() | 468 | + | tb := testAttachmentTab(sess) |
| 469 | p := tb.focusedPane() | 469 | p := tb.focusedPane() | ||
| 470 | p.resizeApplying = true | 470 | p.resizeApplying = true | ||
| 471 | p.resizePending = []byte("x") | 471 | p.resizePending = []byte("x") | ||
| @@ -489,7 +489,7 @@ func TestStaleRemovedMemberGateIsCanceledOnceByFreshPlan(t *testing.T) { | |||||
| 489 | func TestHeadlessResizeRetriesInvalidatedPlan(t *testing.T) { | 489 | func TestHeadlessResizeRetriesInvalidatedPlan(t *testing.T) { | ||
| 490 | pty := &transactionalResizePTY{} | 490 | pty := &transactionalResizePTY{} | ||
| 491 | d, sess, _, _ := newManualSessionWithPTYs(t, pty) | 491 | d, sess, _, _ := newManualSessionWithPTYs(t, pty) | ||
| 492 | - | tb := sess.activeTab() | 492 | + | tb := testAttachmentTab(sess) |
| 493 | var invalidate sync.Once | 493 | var invalidate sync.Once | ||
| 494 | pty.onResize = func() { | 494 | pty.onResize = func() { | ||
| 495 | invalidate.Do(func() { | 495 | invalidate.Do(func() { | ||
| @@ -509,7 +509,7 @@ func TestHeadlessResizeRetriesInvalidatedPlan(t *testing.T) { | |||||
| 509 | func TestRetryOwnerCannotPublishFloatingGeometryAfterMove(t *testing.T) { | 509 | func TestRetryOwnerCannotPublishFloatingGeometryAfterMove(t *testing.T) { | ||
| 510 | popupPTY := &transactionalResizePTY{errs: []error{errors.New("initial popup failure")}} | 510 | popupPTY := &transactionalResizePTY{errs: []error{errors.New("initial popup failure")}} | ||
| 511 | d, source, ac, _ := newManualSessionWithPTYs(t, &transactionalResizePTY{}) | 511 | d, source, ac, _ := newManualSessionWithPTYs(t, &transactionalResizePTY{}) | ||
| 512 | - | tb := source.activeTab() | 512 | + | tb := testAttachmentTab(source) |
| 513 | popup := newPane("popup", popupPTY, domain.Size{Cols: 80, Rows: 23}) | 513 | popup := newPane("popup", popupPTY, domain.Size{Cols: 80, Rows: 23}) | ||
| 514 | tb.mu.Lock() | 514 | tb.mu.Lock() | ||
| 515 | tb.floating = floatingSlot{state: floatingVisible, pane: popup, generation: 7} | 515 | tb.floating = floatingSlot{state: floatingVisible, pane: popup, generation: 7} | ||
| @@ -561,7 +561,7 @@ func TestTransactionalResizeFloatingBreakpointRace(t *testing.T) { | |||||
| 561 | d.ApplyConfig(domain.Config{Floating: cfg}) | 561 | d.ApplyConfig(domain.Config{Floating: cfg}) | ||
| 562 | require.True(t, d.requestTransactionalResize(sess, nil, tc.from, true)) | 562 | require.True(t, d.requestTransactionalResize(sess, nil, tc.from, true)) | ||
| 563 | 563 | ||||
| 564 | - | tb := sess.activeTab() | 564 | + | tb := testAttachmentTab(sess) |
| 565 | fromGeometry := calculateContentFloatingGeometry(tabSize(tc.from), cfg) | 565 | fromGeometry := calculateContentFloatingGeometry(tabSize(tc.from), cfg) | ||
| 566 | toGeometry := calculateContentFloatingGeometry(tabSize(tc.to), cfg) | 566 | toGeometry := calculateContentFloatingGeometry(tabSize(tc.to), cfg) | ||
| 567 | entered, release := make(chan struct{}), make(chan struct{}) | 567 | entered, release := make(chan struct{}), make(chan struct{}) | ||
| @@ -735,7 +735,7 @@ func TestZeroInnerFloatingDrawerRejectsStaleRequestWithoutPhysicalPublication(t | |||||
| 735 | func TestTransactionalResizeRetriesAcceptedFloatingSlotByIdentity(t *testing.T) { | 735 | func TestTransactionalResizeRetriesAcceptedFloatingSlotByIdentity(t *testing.T) { | ||
| 736 | popupPTY := &transactionalResizePTY{errs: []error{errors.New("first popup resize fails")}} | 736 | popupPTY := &transactionalResizePTY{errs: []error{errors.New("first popup resize fails")}} | ||
| 737 | d, sess, ac, _ := newManualSessionWithPTYs(t, &transactionalResizePTY{}) | 737 | d, sess, ac, _ := newManualSessionWithPTYs(t, &transactionalResizePTY{}) | ||
| 738 | - | tb := sess.activeTab() | 738 | + | tb := testAttachmentTab(sess) |
| 739 | popup := newPane("popup", popupPTY, domain.Size{Cols: 80, Rows: 23}) | 739 | popup := newPane("popup", popupPTY, domain.Size{Cols: 80, Rows: 23}) | ||
| 740 | tb.mu.Lock() | 740 | tb.mu.Lock() | ||
| 741 | tb.floating = floatingSlot{state: floatingVisible, pane: popup, generation: 7} | 741 | tb.floating = floatingSlot{state: floatingVisible, pane: popup, generation: 7} | ||
| @@ -823,7 +823,7 @@ func TestTransactionalResizeFloatingMouseCopyAndSearchContract(t *testing.T) { | |||||
| 823 | entered, release := make(chan struct{}), make(chan struct{}) | 823 | entered, release := make(chan struct{}), make(chan struct{}) | ||
| 824 | pty := &transactionalResizePTY{errs: tc.errs, onResize: func() { close(entered); <-release }} | 824 | pty := &transactionalResizePTY{errs: tc.errs, onResize: func() { close(entered); <-release }} | ||
| 825 | d, sess, ac, _ := newManualSessionWithPTYs(t, pty) | 825 | d, sess, ac, _ := newManualSessionWithPTYs(t, pty) | ||
| 826 | - | tb := sess.activeTab() | 826 | + | tb := testAttachmentTab(sess) |
| 827 | p := tb.focusedPane() | 827 | p := tb.focusedPane() | ||
| 828 | old := p.rect | 828 | old := p.rect | ||
| 829 | d.enterCopyMode(sess, ac) | 829 | d.enterCopyMode(sess, ac) | ||
M internal/usecase/daemon/transport_observability_test.go +2 -2
| @@ -125,7 +125,7 @@ func TestTransportObservabilityBlockedRenderObserverReleasesArchitectureLocks(t | |||
| 125 | 125 | } | |
| 126 | 126 | ac.ackOutputState(output.NewStateNum) | |
| 127 | 127 | ||
| 128 | - | pane := sess.activeTab().focusedPane() | |
| 128 | + | pane := testAttachmentTab(sess).focusedPane() | |
| 129 | 129 | for _, lock := range []struct { | |
| 130 | 130 | name string | |
| 131 | 131 | fn func() | |
| @@ -141,7 +141,7 @@ func TestTransportObservabilityBlockedRenderObserverReleasesArchitectureLocks(t | |||
| 141 | 141 | ||
| 142 | 142 | livePTY := make(chan struct{}) | |
| 143 | 143 | go func() { | |
| 144 | - | d.processPTYData(sess, sess.activeTab(), pane, []byte("live"), false) | |
| 144 | + | d.processPTYData(sess, testAttachmentTab(sess), pane, []byte("live"), false) | |
| 145 | 145 | close(livePTY) | |
| 146 | 146 | }() | |
| 147 | 147 | awaitDaemonObserver(t, livePTY, "live PTY processing") | |
| @@ -125,7 +125,7 @@ func TestTransportObservabilityBlockedRenderObserverReleasesArchitectureLocks(t | |||||
| 125 | } | 125 | } | ||
| 126 | ac.ackOutputState(output.NewStateNum) | 126 | ac.ackOutputState(output.NewStateNum) | ||
| 127 | 127 | ||||
| 128 | - | pane := sess.activeTab().focusedPane() | 128 | + | pane := testAttachmentTab(sess).focusedPane() |
| 129 | for _, lock := range []struct { | 129 | for _, lock := range []struct { | ||
| 130 | name string | 130 | name string | ||
| 131 | fn func() | 131 | fn func() | ||
| @@ -141,7 +141,7 @@ func TestTransportObservabilityBlockedRenderObserverReleasesArchitectureLocks(t | |||||
| 141 | 141 | ||||
| 142 | livePTY := make(chan struct{}) | 142 | livePTY := make(chan struct{}) | ||
| 143 | go func() { | 143 | go func() { | ||
| 144 | - | d.processPTYData(sess, sess.activeTab(), pane, []byte("live"), false) | 144 | + | d.processPTYData(sess, testAttachmentTab(sess), pane, []byte("live"), false) |
| 145 | close(livePTY) | 145 | close(livePTY) | ||
| 146 | }() | 146 | }() | ||
| 147 | awaitDaemonObserver(t, livePTY, "live PTY processing") | 147 | awaitDaemonObserver(t, livePTY, "live PTY processing") | ||