detached c4a18a9

refactor(panes): finish review cleanup

commit
c4a18a99580649ed91233c7fd308bf26fbb7018f
author
brice · · 2026-07-04 08:00 UTC
parent
07742d2
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
9 files · +1127 -160
M .mockery.yml +7 -0
@@ -28,3 +28,10 @@ packages:
2828 dir: internal/ports/mocks
2929 filename: mocks.go
3030 pkgname: portsmocks
31+ github.com/bnema/vev/internal/usecase/command:
32+ interfaces:
33+ Context:
34+ config:
35+ dir: internal/usecase/command
36+ filename: context_mock_test.go
37+ pkgname: command
@@ -28,3 +28,10 @@ packages:
28 dir: internal/ports/mocks28 dir: internal/ports/mocks
29 filename: mocks.go29 filename: mocks.go
30 pkgname: portsmocks30 pkgname: portsmocks
31+ github.com/bnema/vev/internal/usecase/command:
32+ interfaces:
33+ Context:
34+ config:
35+ dir: internal/usecase/command
36+ filename: context_mock_test.go
37+ pkgname: command
A internal/usecase/command/context_mock_test.go +916 -0
@@ -0,0 +1,916 @@
1+// Code generated by mockery; DO NOT EDIT.
2+// github.com/vektra/mockery
3+// template: testify
4+
5+package command
6+
7+import (
8+ mock "github.com/stretchr/testify/mock"
9+)
10+
11+// NewMockContext creates a new instance of MockContext. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
12+// The first argument is typically a *testing.T value.
13+func NewMockContext(t interface {
14+ mock.TestingT
15+ Cleanup(func())
16+}) *MockContext {
17+ mock := &MockContext{}
18+ mock.Mock.Test(t)
19+
20+ t.Cleanup(func() { mock.AssertExpectations(t) })
21+
22+ return mock
23+}
24+
25+// MockContext is an autogenerated mock type for the Context type
26+type MockContext struct {
27+ mock.Mock
28+}
29+
30+type MockContext_Expecter struct {
31+ mock *mock.Mock
32+}
33+
34+func (_m *MockContext) EXPECT() *MockContext_Expecter {
35+ return &MockContext_Expecter{mock: &_m.Mock}
36+}
37+
38+// ClosePane provides a mock function for the type MockContext
39+func (_mock *MockContext) ClosePane() error {
40+ ret := _mock.Called()
41+
42+ if len(ret) == 0 {
43+ panic("no return value specified for ClosePane")
44+ }
45+
46+ var r0 error
47+ if returnFunc, ok := ret.Get(0).(func() error); ok {
48+ r0 = returnFunc()
49+ } else {
50+ r0 = ret.Error(0)
51+ }
52+ return r0
53+}
54+
55+// MockContext_ClosePane_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ClosePane'
56+type MockContext_ClosePane_Call struct {
57+ *mock.Call
58+}
59+
60+// ClosePane is a helper method to define mock.On call
61+func (_e *MockContext_Expecter) ClosePane() *MockContext_ClosePane_Call {
62+ return &MockContext_ClosePane_Call{Call: _e.mock.On("ClosePane")}
63+}
64+
65+func (_c *MockContext_ClosePane_Call) Run(run func()) *MockContext_ClosePane_Call {
66+ _c.Call.Run(func(args mock.Arguments) {
67+ run()
68+ })
69+ return _c
70+}
71+
72+func (_c *MockContext_ClosePane_Call) Return(err error) *MockContext_ClosePane_Call {
73+ _c.Call.Return(err)
74+ return _c
75+}
76+
77+func (_c *MockContext_ClosePane_Call) RunAndReturn(run func() error) *MockContext_ClosePane_Call {
78+ _c.Call.Return(run)
79+ return _c
80+}
81+
82+// CloseTab provides a mock function for the type MockContext
83+func (_mock *MockContext) CloseTab() error {
84+ ret := _mock.Called()
85+
86+ if len(ret) == 0 {
87+ panic("no return value specified for CloseTab")
88+ }
89+
90+ var r0 error
91+ if returnFunc, ok := ret.Get(0).(func() error); ok {
92+ r0 = returnFunc()
93+ } else {
94+ r0 = ret.Error(0)
95+ }
96+ return r0
97+}
98+
99+// MockContext_CloseTab_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CloseTab'
100+type MockContext_CloseTab_Call struct {
101+ *mock.Call
102+}
103+
104+// CloseTab is a helper method to define mock.On call
105+func (_e *MockContext_Expecter) CloseTab() *MockContext_CloseTab_Call {
106+ return &MockContext_CloseTab_Call{Call: _e.mock.On("CloseTab")}
107+}
108+
109+func (_c *MockContext_CloseTab_Call) Run(run func()) *MockContext_CloseTab_Call {
110+ _c.Call.Run(func(args mock.Arguments) {
111+ run()
112+ })
113+ return _c
114+}
115+
116+func (_c *MockContext_CloseTab_Call) Return(err error) *MockContext_CloseTab_Call {
117+ _c.Call.Return(err)
118+ return _c
119+}
120+
121+func (_c *MockContext_CloseTab_Call) RunAndReturn(run func() error) *MockContext_CloseTab_Call {
122+ _c.Call.Return(run)
123+ return _c
124+}
125+
126+// CreateSession provides a mock function for the type MockContext
127+func (_mock *MockContext) CreateSession() error {
128+ ret := _mock.Called()
129+
130+ if len(ret) == 0 {
131+ panic("no return value specified for CreateSession")
132+ }
133+
134+ var r0 error
135+ if returnFunc, ok := ret.Get(0).(func() error); ok {
136+ r0 = returnFunc()
137+ } else {
138+ r0 = ret.Error(0)
139+ }
140+ return r0
141+}
142+
143+// MockContext_CreateSession_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateSession'
144+type MockContext_CreateSession_Call struct {
145+ *mock.Call
146+}
147+
148+// CreateSession is a helper method to define mock.On call
149+func (_e *MockContext_Expecter) CreateSession() *MockContext_CreateSession_Call {
150+ return &MockContext_CreateSession_Call{Call: _e.mock.On("CreateSession")}
151+}
152+
153+func (_c *MockContext_CreateSession_Call) Run(run func()) *MockContext_CreateSession_Call {
154+ _c.Call.Run(func(args mock.Arguments) {
155+ run()
156+ })
157+ return _c
158+}
159+
160+func (_c *MockContext_CreateSession_Call) Return(err error) *MockContext_CreateSession_Call {
161+ _c.Call.Return(err)
162+ return _c
163+}
164+
165+func (_c *MockContext_CreateSession_Call) RunAndReturn(run func() error) *MockContext_CreateSession_Call {
166+ _c.Call.Return(run)
167+ return _c
168+}
169+
170+// CreateTab provides a mock function for the type MockContext
171+func (_mock *MockContext) CreateTab() error {
172+ ret := _mock.Called()
173+
174+ if len(ret) == 0 {
175+ panic("no return value specified for CreateTab")
176+ }
177+
178+ var r0 error
179+ if returnFunc, ok := ret.Get(0).(func() error); ok {
180+ r0 = returnFunc()
181+ } else {
182+ r0 = ret.Error(0)
183+ }
184+ return r0
185+}
186+
187+// MockContext_CreateTab_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateTab'
188+type MockContext_CreateTab_Call struct {
189+ *mock.Call
190+}
191+
192+// CreateTab is a helper method to define mock.On call
193+func (_e *MockContext_Expecter) CreateTab() *MockContext_CreateTab_Call {
194+ return &MockContext_CreateTab_Call{Call: _e.mock.On("CreateTab")}
195+}
196+
197+func (_c *MockContext_CreateTab_Call) Run(run func()) *MockContext_CreateTab_Call {
198+ _c.Call.Run(func(args mock.Arguments) {
199+ run()
200+ })
201+ return _c
202+}
203+
204+func (_c *MockContext_CreateTab_Call) Return(err error) *MockContext_CreateTab_Call {
205+ _c.Call.Return(err)
206+ return _c
207+}
208+
209+func (_c *MockContext_CreateTab_Call) RunAndReturn(run func() error) *MockContext_CreateTab_Call {
210+ _c.Call.Return(run)
211+ return _c
212+}
213+
214+// Detach provides a mock function for the type MockContext
215+func (_mock *MockContext) Detach() error {
216+ ret := _mock.Called()
217+
218+ if len(ret) == 0 {
219+ panic("no return value specified for Detach")
220+ }
221+
222+ var r0 error
223+ if returnFunc, ok := ret.Get(0).(func() error); ok {
224+ r0 = returnFunc()
225+ } else {
226+ r0 = ret.Error(0)
227+ }
228+ return r0
229+}
230+
231+// MockContext_Detach_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Detach'
232+type MockContext_Detach_Call struct {
233+ *mock.Call
234+}
235+
236+// Detach is a helper method to define mock.On call
237+func (_e *MockContext_Expecter) Detach() *MockContext_Detach_Call {
238+ return &MockContext_Detach_Call{Call: _e.mock.On("Detach")}
239+}
240+
241+func (_c *MockContext_Detach_Call) Run(run func()) *MockContext_Detach_Call {
242+ _c.Call.Run(func(args mock.Arguments) {
243+ run()
244+ })
245+ return _c
246+}
247+
248+func (_c *MockContext_Detach_Call) Return(err error) *MockContext_Detach_Call {
249+ _c.Call.Return(err)
250+ return _c
251+}
252+
253+func (_c *MockContext_Detach_Call) RunAndReturn(run func() error) *MockContext_Detach_Call {
254+ _c.Call.Return(run)
255+ return _c
256+}
257+
258+// EnterVisualMode provides a mock function for the type MockContext
259+func (_mock *MockContext) EnterVisualMode() error {
260+ ret := _mock.Called()
261+
262+ if len(ret) == 0 {
263+ panic("no return value specified for EnterVisualMode")
264+ }
265+
266+ var r0 error
267+ if returnFunc, ok := ret.Get(0).(func() error); ok {
268+ r0 = returnFunc()
269+ } else {
270+ r0 = ret.Error(0)
271+ }
272+ return r0
273+}
274+
275+// MockContext_EnterVisualMode_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'EnterVisualMode'
276+type MockContext_EnterVisualMode_Call struct {
277+ *mock.Call
278+}
279+
280+// EnterVisualMode is a helper method to define mock.On call
281+func (_e *MockContext_Expecter) EnterVisualMode() *MockContext_EnterVisualMode_Call {
282+ return &MockContext_EnterVisualMode_Call{Call: _e.mock.On("EnterVisualMode")}
283+}
284+
285+func (_c *MockContext_EnterVisualMode_Call) Run(run func()) *MockContext_EnterVisualMode_Call {
286+ _c.Call.Run(func(args mock.Arguments) {
287+ run()
288+ })
289+ return _c
290+}
291+
292+func (_c *MockContext_EnterVisualMode_Call) Return(err error) *MockContext_EnterVisualMode_Call {
293+ _c.Call.Return(err)
294+ return _c
295+}
296+
297+func (_c *MockContext_EnterVisualMode_Call) RunAndReturn(run func() error) *MockContext_EnterVisualMode_Call {
298+ _c.Call.Return(run)
299+ return _c
300+}
301+
302+// FocusPaneDown provides a mock function for the type MockContext
303+func (_mock *MockContext) FocusPaneDown() error {
304+ ret := _mock.Called()
305+
306+ if len(ret) == 0 {
307+ panic("no return value specified for FocusPaneDown")
308+ }
309+
310+ var r0 error
311+ if returnFunc, ok := ret.Get(0).(func() error); ok {
312+ r0 = returnFunc()
313+ } else {
314+ r0 = ret.Error(0)
315+ }
316+ return r0
317+}
318+
319+// MockContext_FocusPaneDown_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FocusPaneDown'
320+type MockContext_FocusPaneDown_Call struct {
321+ *mock.Call
322+}
323+
324+// FocusPaneDown is a helper method to define mock.On call
325+func (_e *MockContext_Expecter) FocusPaneDown() *MockContext_FocusPaneDown_Call {
326+ return &MockContext_FocusPaneDown_Call{Call: _e.mock.On("FocusPaneDown")}
327+}
328+
329+func (_c *MockContext_FocusPaneDown_Call) Run(run func()) *MockContext_FocusPaneDown_Call {
330+ _c.Call.Run(func(args mock.Arguments) {
331+ run()
332+ })
333+ return _c
334+}
335+
336+func (_c *MockContext_FocusPaneDown_Call) Return(err error) *MockContext_FocusPaneDown_Call {
337+ _c.Call.Return(err)
338+ return _c
339+}
340+
341+func (_c *MockContext_FocusPaneDown_Call) RunAndReturn(run func() error) *MockContext_FocusPaneDown_Call {
342+ _c.Call.Return(run)
343+ return _c
344+}
345+
346+// FocusPaneLeft provides a mock function for the type MockContext
347+func (_mock *MockContext) FocusPaneLeft() error {
348+ ret := _mock.Called()
349+
350+ if len(ret) == 0 {
351+ panic("no return value specified for FocusPaneLeft")
352+ }
353+
354+ var r0 error
355+ if returnFunc, ok := ret.Get(0).(func() error); ok {
356+ r0 = returnFunc()
357+ } else {
358+ r0 = ret.Error(0)
359+ }
360+ return r0
361+}
362+
363+// MockContext_FocusPaneLeft_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FocusPaneLeft'
364+type MockContext_FocusPaneLeft_Call struct {
365+ *mock.Call
366+}
367+
368+// FocusPaneLeft is a helper method to define mock.On call
369+func (_e *MockContext_Expecter) FocusPaneLeft() *MockContext_FocusPaneLeft_Call {
370+ return &MockContext_FocusPaneLeft_Call{Call: _e.mock.On("FocusPaneLeft")}
371+}
372+
373+func (_c *MockContext_FocusPaneLeft_Call) Run(run func()) *MockContext_FocusPaneLeft_Call {
374+ _c.Call.Run(func(args mock.Arguments) {
375+ run()
376+ })
377+ return _c
378+}
379+
380+func (_c *MockContext_FocusPaneLeft_Call) Return(err error) *MockContext_FocusPaneLeft_Call {
381+ _c.Call.Return(err)
382+ return _c
383+}
384+
385+func (_c *MockContext_FocusPaneLeft_Call) RunAndReturn(run func() error) *MockContext_FocusPaneLeft_Call {
386+ _c.Call.Return(run)
387+ return _c
388+}
389+
390+// FocusPaneRight provides a mock function for the type MockContext
391+func (_mock *MockContext) FocusPaneRight() error {
392+ ret := _mock.Called()
393+
394+ if len(ret) == 0 {
395+ panic("no return value specified for FocusPaneRight")
396+ }
397+
398+ var r0 error
399+ if returnFunc, ok := ret.Get(0).(func() error); ok {
400+ r0 = returnFunc()
401+ } else {
402+ r0 = ret.Error(0)
403+ }
404+ return r0
405+}
406+
407+// MockContext_FocusPaneRight_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FocusPaneRight'
408+type MockContext_FocusPaneRight_Call struct {
409+ *mock.Call
410+}
411+
412+// FocusPaneRight is a helper method to define mock.On call
413+func (_e *MockContext_Expecter) FocusPaneRight() *MockContext_FocusPaneRight_Call {
414+ return &MockContext_FocusPaneRight_Call{Call: _e.mock.On("FocusPaneRight")}
415+}
416+
417+func (_c *MockContext_FocusPaneRight_Call) Run(run func()) *MockContext_FocusPaneRight_Call {
418+ _c.Call.Run(func(args mock.Arguments) {
419+ run()
420+ })
421+ return _c
422+}
423+
424+func (_c *MockContext_FocusPaneRight_Call) Return(err error) *MockContext_FocusPaneRight_Call {
425+ _c.Call.Return(err)
426+ return _c
427+}
428+
429+func (_c *MockContext_FocusPaneRight_Call) RunAndReturn(run func() error) *MockContext_FocusPaneRight_Call {
430+ _c.Call.Return(run)
431+ return _c
432+}
433+
434+// FocusPaneUp provides a mock function for the type MockContext
435+func (_mock *MockContext) FocusPaneUp() error {
436+ ret := _mock.Called()
437+
438+ if len(ret) == 0 {
439+ panic("no return value specified for FocusPaneUp")
440+ }
441+
442+ var r0 error
443+ if returnFunc, ok := ret.Get(0).(func() error); ok {
444+ r0 = returnFunc()
445+ } else {
446+ r0 = ret.Error(0)
447+ }
448+ return r0
449+}
450+
451+// MockContext_FocusPaneUp_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FocusPaneUp'
452+type MockContext_FocusPaneUp_Call struct {
453+ *mock.Call
454+}
455+
456+// FocusPaneUp is a helper method to define mock.On call
457+func (_e *MockContext_Expecter) FocusPaneUp() *MockContext_FocusPaneUp_Call {
458+ return &MockContext_FocusPaneUp_Call{Call: _e.mock.On("FocusPaneUp")}
459+}
460+
461+func (_c *MockContext_FocusPaneUp_Call) Run(run func()) *MockContext_FocusPaneUp_Call {
462+ _c.Call.Run(func(args mock.Arguments) {
463+ run()
464+ })
465+ return _c
466+}
467+
468+func (_c *MockContext_FocusPaneUp_Call) Return(err error) *MockContext_FocusPaneUp_Call {
469+ _c.Call.Return(err)
470+ return _c
471+}
472+
473+func (_c *MockContext_FocusPaneUp_Call) RunAndReturn(run func() error) *MockContext_FocusPaneUp_Call {
474+ _c.Call.Return(run)
475+ return _c
476+}
477+
478+// NextTab provides a mock function for the type MockContext
479+func (_mock *MockContext) NextTab() error {
480+ ret := _mock.Called()
481+
482+ if len(ret) == 0 {
483+ panic("no return value specified for NextTab")
484+ }
485+
486+ var r0 error
487+ if returnFunc, ok := ret.Get(0).(func() error); ok {
488+ r0 = returnFunc()
489+ } else {
490+ r0 = ret.Error(0)
491+ }
492+ return r0
493+}
494+
495+// MockContext_NextTab_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'NextTab'
496+type MockContext_NextTab_Call struct {
497+ *mock.Call
498+}
499+
500+// NextTab is a helper method to define mock.On call
501+func (_e *MockContext_Expecter) NextTab() *MockContext_NextTab_Call {
502+ return &MockContext_NextTab_Call{Call: _e.mock.On("NextTab")}
503+}
504+
505+func (_c *MockContext_NextTab_Call) Run(run func()) *MockContext_NextTab_Call {
506+ _c.Call.Run(func(args mock.Arguments) {
507+ run()
508+ })
509+ return _c
510+}
511+
512+func (_c *MockContext_NextTab_Call) Return(err error) *MockContext_NextTab_Call {
513+ _c.Call.Return(err)
514+ return _c
515+}
516+
517+func (_c *MockContext_NextTab_Call) RunAndReturn(run func() error) *MockContext_NextTab_Call {
518+ _c.Call.Return(run)
519+ return _c
520+}
521+
522+// OpenSessionPicker provides a mock function for the type MockContext
523+func (_mock *MockContext) OpenSessionPicker() error {
524+ ret := _mock.Called()
525+
526+ if len(ret) == 0 {
527+ panic("no return value specified for OpenSessionPicker")
528+ }
529+
530+ var r0 error
531+ if returnFunc, ok := ret.Get(0).(func() error); ok {
532+ r0 = returnFunc()
533+ } else {
534+ r0 = ret.Error(0)
535+ }
536+ return r0
537+}
538+
539+// MockContext_OpenSessionPicker_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'OpenSessionPicker'
540+type MockContext_OpenSessionPicker_Call struct {
541+ *mock.Call
542+}
543+
544+// OpenSessionPicker is a helper method to define mock.On call
545+func (_e *MockContext_Expecter) OpenSessionPicker() *MockContext_OpenSessionPicker_Call {
546+ return &MockContext_OpenSessionPicker_Call{Call: _e.mock.On("OpenSessionPicker")}
547+}
548+
549+func (_c *MockContext_OpenSessionPicker_Call) Run(run func()) *MockContext_OpenSessionPicker_Call {
550+ _c.Call.Run(func(args mock.Arguments) {
551+ run()
552+ })
553+ return _c
554+}
555+
556+func (_c *MockContext_OpenSessionPicker_Call) Return(err error) *MockContext_OpenSessionPicker_Call {
557+ _c.Call.Return(err)
558+ return _c
559+}
560+
561+func (_c *MockContext_OpenSessionPicker_Call) RunAndReturn(run func() error) *MockContext_OpenSessionPicker_Call {
562+ _c.Call.Return(run)
563+ return _c
564+}
565+
566+// PrevTab provides a mock function for the type MockContext
567+func (_mock *MockContext) PrevTab() error {
568+ ret := _mock.Called()
569+
570+ if len(ret) == 0 {
571+ panic("no return value specified for PrevTab")
572+ }
573+
574+ var r0 error
575+ if returnFunc, ok := ret.Get(0).(func() error); ok {
576+ r0 = returnFunc()
577+ } else {
578+ r0 = ret.Error(0)
579+ }
580+ return r0
581+}
582+
583+// MockContext_PrevTab_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PrevTab'
584+type MockContext_PrevTab_Call struct {
585+ *mock.Call
586+}
587+
588+// PrevTab is a helper method to define mock.On call
589+func (_e *MockContext_Expecter) PrevTab() *MockContext_PrevTab_Call {
590+ return &MockContext_PrevTab_Call{Call: _e.mock.On("PrevTab")}
591+}
592+
593+func (_c *MockContext_PrevTab_Call) Run(run func()) *MockContext_PrevTab_Call {
594+ _c.Call.Run(func(args mock.Arguments) {
595+ run()
596+ })
597+ return _c
598+}
599+
600+func (_c *MockContext_PrevTab_Call) Return(err error) *MockContext_PrevTab_Call {
601+ _c.Call.Return(err)
602+ return _c
603+}
604+
605+func (_c *MockContext_PrevTab_Call) RunAndReturn(run func() error) *MockContext_PrevTab_Call {
606+ _c.Call.Return(run)
607+ return _c
608+}
609+
610+// RenameSession provides a mock function for the type MockContext
611+func (_mock *MockContext) RenameSession() error {
612+ ret := _mock.Called()
613+
614+ if len(ret) == 0 {
615+ panic("no return value specified for RenameSession")
616+ }
617+
618+ var r0 error
619+ if returnFunc, ok := ret.Get(0).(func() error); ok {
620+ r0 = returnFunc()
621+ } else {
622+ r0 = ret.Error(0)
623+ }
624+ return r0
625+}
626+
627+// MockContext_RenameSession_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RenameSession'
628+type MockContext_RenameSession_Call struct {
629+ *mock.Call
630+}
631+
632+// RenameSession is a helper method to define mock.On call
633+func (_e *MockContext_Expecter) RenameSession() *MockContext_RenameSession_Call {
634+ return &MockContext_RenameSession_Call{Call: _e.mock.On("RenameSession")}
635+}
636+
637+func (_c *MockContext_RenameSession_Call) Run(run func()) *MockContext_RenameSession_Call {
638+ _c.Call.Run(func(args mock.Arguments) {
639+ run()
640+ })
641+ return _c
642+}
643+
644+func (_c *MockContext_RenameSession_Call) Return(err error) *MockContext_RenameSession_Call {
645+ _c.Call.Return(err)
646+ return _c
647+}
648+
649+func (_c *MockContext_RenameSession_Call) RunAndReturn(run func() error) *MockContext_RenameSession_Call {
650+ _c.Call.Return(run)
651+ return _c
652+}
653+
654+// SplitDown provides a mock function for the type MockContext
655+func (_mock *MockContext) SplitDown() error {
656+ ret := _mock.Called()
657+
658+ if len(ret) == 0 {
659+ panic("no return value specified for SplitDown")
660+ }
661+
662+ var r0 error
663+ if returnFunc, ok := ret.Get(0).(func() error); ok {
664+ r0 = returnFunc()
665+ } else {
666+ r0 = ret.Error(0)
667+ }
668+ return r0
669+}
670+
671+// MockContext_SplitDown_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SplitDown'
672+type MockContext_SplitDown_Call struct {
673+ *mock.Call
674+}
675+
676+// SplitDown is a helper method to define mock.On call
677+func (_e *MockContext_Expecter) SplitDown() *MockContext_SplitDown_Call {
678+ return &MockContext_SplitDown_Call{Call: _e.mock.On("SplitDown")}
679+}
680+
681+func (_c *MockContext_SplitDown_Call) Run(run func()) *MockContext_SplitDown_Call {
682+ _c.Call.Run(func(args mock.Arguments) {
683+ run()
684+ })
685+ return _c
686+}
687+
688+func (_c *MockContext_SplitDown_Call) Return(err error) *MockContext_SplitDown_Call {
689+ _c.Call.Return(err)
690+ return _c
691+}
692+
693+func (_c *MockContext_SplitDown_Call) RunAndReturn(run func() error) *MockContext_SplitDown_Call {
694+ _c.Call.Return(run)
695+ return _c
696+}
697+
698+// SplitLeft provides a mock function for the type MockContext
699+func (_mock *MockContext) SplitLeft() error {
700+ ret := _mock.Called()
701+
702+ if len(ret) == 0 {
703+ panic("no return value specified for SplitLeft")
704+ }
705+
706+ var r0 error
707+ if returnFunc, ok := ret.Get(0).(func() error); ok {
708+ r0 = returnFunc()
709+ } else {
710+ r0 = ret.Error(0)
711+ }
712+ return r0
713+}
714+
715+// MockContext_SplitLeft_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SplitLeft'
716+type MockContext_SplitLeft_Call struct {
717+ *mock.Call
718+}
719+
720+// SplitLeft is a helper method to define mock.On call
721+func (_e *MockContext_Expecter) SplitLeft() *MockContext_SplitLeft_Call {
722+ return &MockContext_SplitLeft_Call{Call: _e.mock.On("SplitLeft")}
723+}
724+
725+func (_c *MockContext_SplitLeft_Call) Run(run func()) *MockContext_SplitLeft_Call {
726+ _c.Call.Run(func(args mock.Arguments) {
727+ run()
728+ })
729+ return _c
730+}
731+
732+func (_c *MockContext_SplitLeft_Call) Return(err error) *MockContext_SplitLeft_Call {
733+ _c.Call.Return(err)
734+ return _c
735+}
736+
737+func (_c *MockContext_SplitLeft_Call) RunAndReturn(run func() error) *MockContext_SplitLeft_Call {
738+ _c.Call.Return(run)
739+ return _c
740+}
741+
742+// SplitRight provides a mock function for the type MockContext
743+func (_mock *MockContext) SplitRight() error {
744+ ret := _mock.Called()
745+
746+ if len(ret) == 0 {
747+ panic("no return value specified for SplitRight")
748+ }
749+
750+ var r0 error
751+ if returnFunc, ok := ret.Get(0).(func() error); ok {
752+ r0 = returnFunc()
753+ } else {
754+ r0 = ret.Error(0)
755+ }
756+ return r0
757+}
758+
759+// MockContext_SplitRight_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SplitRight'
760+type MockContext_SplitRight_Call struct {
761+ *mock.Call
762+}
763+
764+// SplitRight is a helper method to define mock.On call
765+func (_e *MockContext_Expecter) SplitRight() *MockContext_SplitRight_Call {
766+ return &MockContext_SplitRight_Call{Call: _e.mock.On("SplitRight")}
767+}
768+
769+func (_c *MockContext_SplitRight_Call) Run(run func()) *MockContext_SplitRight_Call {
770+ _c.Call.Run(func(args mock.Arguments) {
771+ run()
772+ })
773+ return _c
774+}
775+
776+func (_c *MockContext_SplitRight_Call) Return(err error) *MockContext_SplitRight_Call {
777+ _c.Call.Return(err)
778+ return _c
779+}
780+
781+func (_c *MockContext_SplitRight_Call) RunAndReturn(run func() error) *MockContext_SplitRight_Call {
782+ _c.Call.Return(run)
783+ return _c
784+}
785+
786+// SplitUp provides a mock function for the type MockContext
787+func (_mock *MockContext) SplitUp() error {
788+ ret := _mock.Called()
789+
790+ if len(ret) == 0 {
791+ panic("no return value specified for SplitUp")
792+ }
793+
794+ var r0 error
795+ if returnFunc, ok := ret.Get(0).(func() error); ok {
796+ r0 = returnFunc()
797+ } else {
798+ r0 = ret.Error(0)
799+ }
800+ return r0
801+}
802+
803+// MockContext_SplitUp_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SplitUp'
804+type MockContext_SplitUp_Call struct {
805+ *mock.Call
806+}
807+
808+// SplitUp is a helper method to define mock.On call
809+func (_e *MockContext_Expecter) SplitUp() *MockContext_SplitUp_Call {
810+ return &MockContext_SplitUp_Call{Call: _e.mock.On("SplitUp")}
811+}
812+
813+func (_c *MockContext_SplitUp_Call) Run(run func()) *MockContext_SplitUp_Call {
814+ _c.Call.Run(func(args mock.Arguments) {
815+ run()
816+ })
817+ return _c
818+}
819+
820+func (_c *MockContext_SplitUp_Call) Return(err error) *MockContext_SplitUp_Call {
821+ _c.Call.Return(err)
822+ return _c
823+}
824+
825+func (_c *MockContext_SplitUp_Call) RunAndReturn(run func() error) *MockContext_SplitUp_Call {
826+ _c.Call.Return(run)
827+ return _c
828+}
829+
830+// StackPane provides a mock function for the type MockContext
831+func (_mock *MockContext) StackPane() error {
832+ ret := _mock.Called()
833+
834+ if len(ret) == 0 {
835+ panic("no return value specified for StackPane")
836+ }
837+
838+ var r0 error
839+ if returnFunc, ok := ret.Get(0).(func() error); ok {
840+ r0 = returnFunc()
841+ } else {
842+ r0 = ret.Error(0)
843+ }
844+ return r0
845+}
846+
847+// MockContext_StackPane_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'StackPane'
848+type MockContext_StackPane_Call struct {
849+ *mock.Call
850+}
851+
852+// StackPane is a helper method to define mock.On call
853+func (_e *MockContext_Expecter) StackPane() *MockContext_StackPane_Call {
854+ return &MockContext_StackPane_Call{Call: _e.mock.On("StackPane")}
855+}
856+
857+func (_c *MockContext_StackPane_Call) Run(run func()) *MockContext_StackPane_Call {
858+ _c.Call.Run(func(args mock.Arguments) {
859+ run()
860+ })
861+ return _c
862+}
863+
864+func (_c *MockContext_StackPane_Call) Return(err error) *MockContext_StackPane_Call {
865+ _c.Call.Return(err)
866+ return _c
867+}
868+
869+func (_c *MockContext_StackPane_Call) RunAndReturn(run func() error) *MockContext_StackPane_Call {
870+ _c.Call.Return(run)
871+ return _c
872+}
873+
874+// ToggleStack provides a mock function for the type MockContext
875+func (_mock *MockContext) ToggleStack() error {
876+ ret := _mock.Called()
877+
878+ if len(ret) == 0 {
879+ panic("no return value specified for ToggleStack")
880+ }
881+
882+ var r0 error
883+ if returnFunc, ok := ret.Get(0).(func() error); ok {
884+ r0 = returnFunc()
885+ } else {
886+ r0 = ret.Error(0)
887+ }
888+ return r0
889+}
890+
891+// MockContext_ToggleStack_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ToggleStack'
892+type MockContext_ToggleStack_Call struct {
893+ *mock.Call
894+}
895+
896+// ToggleStack is a helper method to define mock.On call
897+func (_e *MockContext_Expecter) ToggleStack() *MockContext_ToggleStack_Call {
898+ return &MockContext_ToggleStack_Call{Call: _e.mock.On("ToggleStack")}
899+}
900+
901+func (_c *MockContext_ToggleStack_Call) Run(run func()) *MockContext_ToggleStack_Call {
902+ _c.Call.Run(func(args mock.Arguments) {
903+ run()
904+ })
905+ return _c
906+}
907+
908+func (_c *MockContext_ToggleStack_Call) Return(err error) *MockContext_ToggleStack_Call {
909+ _c.Call.Return(err)
910+ return _c
911+}
912+
913+func (_c *MockContext_ToggleStack_Call) RunAndReturn(run func() error) *MockContext_ToggleStack_Call {
914+ _c.Call.Return(run)
915+ return _c
916+}
@@ -0,0 +1,916 @@
1+// Code generated by mockery; DO NOT EDIT.
2+// github.com/vektra/mockery
3+// template: testify
4+
5+package command
6+
7+import (
8+ mock "github.com/stretchr/testify/mock"
9+)
10+
11+// NewMockContext creates a new instance of MockContext. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
12+// The first argument is typically a *testing.T value.
13+func NewMockContext(t interface {
14+ mock.TestingT
15+ Cleanup(func())
16+}) *MockContext {
17+ mock := &MockContext{}
18+ mock.Mock.Test(t)
19+
20+ t.Cleanup(func() { mock.AssertExpectations(t) })
21+
22+ return mock
23+}
24+
25+// MockContext is an autogenerated mock type for the Context type
26+type MockContext struct {
27+ mock.Mock
28+}
29+
30+type MockContext_Expecter struct {
31+ mock *mock.Mock
32+}
33+
34+func (_m *MockContext) EXPECT() *MockContext_Expecter {
35+ return &MockContext_Expecter{mock: &_m.Mock}
36+}
37+
38+// ClosePane provides a mock function for the type MockContext
39+func (_mock *MockContext) ClosePane() error {
40+ ret := _mock.Called()
41+
42+ if len(ret) == 0 {
43+ panic("no return value specified for ClosePane")
44+ }
45+
46+ var r0 error
47+ if returnFunc, ok := ret.Get(0).(func() error); ok {
48+ r0 = returnFunc()
49+ } else {
50+ r0 = ret.Error(0)
51+ }
52+ return r0
53+}
54+
55+// MockContext_ClosePane_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ClosePane'
56+type MockContext_ClosePane_Call struct {
57+ *mock.Call
58+}
59+
60+// ClosePane is a helper method to define mock.On call
61+func (_e *MockContext_Expecter) ClosePane() *MockContext_ClosePane_Call {
62+ return &MockContext_ClosePane_Call{Call: _e.mock.On("ClosePane")}
63+}
64+
65+func (_c *MockContext_ClosePane_Call) Run(run func()) *MockContext_ClosePane_Call {
66+ _c.Call.Run(func(args mock.Arguments) {
67+ run()
68+ })
69+ return _c
70+}
71+
72+func (_c *MockContext_ClosePane_Call) Return(err error) *MockContext_ClosePane_Call {
73+ _c.Call.Return(err)
74+ return _c
75+}
76+
77+func (_c *MockContext_ClosePane_Call) RunAndReturn(run func() error) *MockContext_ClosePane_Call {
78+ _c.Call.Return(run)
79+ return _c
80+}
81+
82+// CloseTab provides a mock function for the type MockContext
83+func (_mock *MockContext) CloseTab() error {
84+ ret := _mock.Called()
85+
86+ if len(ret) == 0 {
87+ panic("no return value specified for CloseTab")
88+ }
89+
90+ var r0 error
91+ if returnFunc, ok := ret.Get(0).(func() error); ok {
92+ r0 = returnFunc()
93+ } else {
94+ r0 = ret.Error(0)
95+ }
96+ return r0
97+}
98+
99+// MockContext_CloseTab_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CloseTab'
100+type MockContext_CloseTab_Call struct {
101+ *mock.Call
102+}
103+
104+// CloseTab is a helper method to define mock.On call
105+func (_e *MockContext_Expecter) CloseTab() *MockContext_CloseTab_Call {
106+ return &MockContext_CloseTab_Call{Call: _e.mock.On("CloseTab")}
107+}
108+
109+func (_c *MockContext_CloseTab_Call) Run(run func()) *MockContext_CloseTab_Call {
110+ _c.Call.Run(func(args mock.Arguments) {
111+ run()
112+ })
113+ return _c
114+}
115+
116+func (_c *MockContext_CloseTab_Call) Return(err error) *MockContext_CloseTab_Call {
117+ _c.Call.Return(err)
118+ return _c
119+}
120+
121+func (_c *MockContext_CloseTab_Call) RunAndReturn(run func() error) *MockContext_CloseTab_Call {
122+ _c.Call.Return(run)
123+ return _c
124+}
125+
126+// CreateSession provides a mock function for the type MockContext
127+func (_mock *MockContext) CreateSession() error {
128+ ret := _mock.Called()
129+
130+ if len(ret) == 0 {
131+ panic("no return value specified for CreateSession")
132+ }
133+
134+ var r0 error
135+ if returnFunc, ok := ret.Get(0).(func() error); ok {
136+ r0 = returnFunc()
137+ } else {
138+ r0 = ret.Error(0)
139+ }
140+ return r0
141+}
142+
143+// MockContext_CreateSession_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateSession'
144+type MockContext_CreateSession_Call struct {
145+ *mock.Call
146+}
147+
148+// CreateSession is a helper method to define mock.On call
149+func (_e *MockContext_Expecter) CreateSession() *MockContext_CreateSession_Call {
150+ return &MockContext_CreateSession_Call{Call: _e.mock.On("CreateSession")}
151+}
152+
153+func (_c *MockContext_CreateSession_Call) Run(run func()) *MockContext_CreateSession_Call {
154+ _c.Call.Run(func(args mock.Arguments) {
155+ run()
156+ })
157+ return _c
158+}
159+
160+func (_c *MockContext_CreateSession_Call) Return(err error) *MockContext_CreateSession_Call {
161+ _c.Call.Return(err)
162+ return _c
163+}
164+
165+func (_c *MockContext_CreateSession_Call) RunAndReturn(run func() error) *MockContext_CreateSession_Call {
166+ _c.Call.Return(run)
167+ return _c
168+}
169+
170+// CreateTab provides a mock function for the type MockContext
171+func (_mock *MockContext) CreateTab() error {
172+ ret := _mock.Called()
173+
174+ if len(ret) == 0 {
175+ panic("no return value specified for CreateTab")
176+ }
177+
178+ var r0 error
179+ if returnFunc, ok := ret.Get(0).(func() error); ok {
180+ r0 = returnFunc()
181+ } else {
182+ r0 = ret.Error(0)
183+ }
184+ return r0
185+}
186+
187+// MockContext_CreateTab_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateTab'
188+type MockContext_CreateTab_Call struct {
189+ *mock.Call
190+}
191+
192+// CreateTab is a helper method to define mock.On call
193+func (_e *MockContext_Expecter) CreateTab() *MockContext_CreateTab_Call {
194+ return &MockContext_CreateTab_Call{Call: _e.mock.On("CreateTab")}
195+}
196+
197+func (_c *MockContext_CreateTab_Call) Run(run func()) *MockContext_CreateTab_Call {
198+ _c.Call.Run(func(args mock.Arguments) {
199+ run()
200+ })
201+ return _c
202+}
203+
204+func (_c *MockContext_CreateTab_Call) Return(err error) *MockContext_CreateTab_Call {
205+ _c.Call.Return(err)
206+ return _c
207+}
208+
209+func (_c *MockContext_CreateTab_Call) RunAndReturn(run func() error) *MockContext_CreateTab_Call {
210+ _c.Call.Return(run)
211+ return _c
212+}
213+
214+// Detach provides a mock function for the type MockContext
215+func (_mock *MockContext) Detach() error {
216+ ret := _mock.Called()
217+
218+ if len(ret) == 0 {
219+ panic("no return value specified for Detach")
220+ }
221+
222+ var r0 error
223+ if returnFunc, ok := ret.Get(0).(func() error); ok {
224+ r0 = returnFunc()
225+ } else {
226+ r0 = ret.Error(0)
227+ }
228+ return r0
229+}
230+
231+// MockContext_Detach_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Detach'
232+type MockContext_Detach_Call struct {
233+ *mock.Call
234+}
235+
236+// Detach is a helper method to define mock.On call
237+func (_e *MockContext_Expecter) Detach() *MockContext_Detach_Call {
238+ return &MockContext_Detach_Call{Call: _e.mock.On("Detach")}
239+}
240+
241+func (_c *MockContext_Detach_Call) Run(run func()) *MockContext_Detach_Call {
242+ _c.Call.Run(func(args mock.Arguments) {
243+ run()
244+ })
245+ return _c
246+}
247+
248+func (_c *MockContext_Detach_Call) Return(err error) *MockContext_Detach_Call {
249+ _c.Call.Return(err)
250+ return _c
251+}
252+
253+func (_c *MockContext_Detach_Call) RunAndReturn(run func() error) *MockContext_Detach_Call {
254+ _c.Call.Return(run)
255+ return _c
256+}
257+
258+// EnterVisualMode provides a mock function for the type MockContext
259+func (_mock *MockContext) EnterVisualMode() error {
260+ ret := _mock.Called()
261+
262+ if len(ret) == 0 {
263+ panic("no return value specified for EnterVisualMode")
264+ }
265+
266+ var r0 error
267+ if returnFunc, ok := ret.Get(0).(func() error); ok {
268+ r0 = returnFunc()
269+ } else {
270+ r0 = ret.Error(0)
271+ }
272+ return r0
273+}
274+
275+// MockContext_EnterVisualMode_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'EnterVisualMode'
276+type MockContext_EnterVisualMode_Call struct {
277+ *mock.Call
278+}
279+
280+// EnterVisualMode is a helper method to define mock.On call
281+func (_e *MockContext_Expecter) EnterVisualMode() *MockContext_EnterVisualMode_Call {
282+ return &MockContext_EnterVisualMode_Call{Call: _e.mock.On("EnterVisualMode")}
283+}
284+
285+func (_c *MockContext_EnterVisualMode_Call) Run(run func()) *MockContext_EnterVisualMode_Call {
286+ _c.Call.Run(func(args mock.Arguments) {
287+ run()
288+ })
289+ return _c
290+}
291+
292+func (_c *MockContext_EnterVisualMode_Call) Return(err error) *MockContext_EnterVisualMode_Call {
293+ _c.Call.Return(err)
294+ return _c
295+}
296+
297+func (_c *MockContext_EnterVisualMode_Call) RunAndReturn(run func() error) *MockContext_EnterVisualMode_Call {
298+ _c.Call.Return(run)
299+ return _c
300+}
301+
302+// FocusPaneDown provides a mock function for the type MockContext
303+func (_mock *MockContext) FocusPaneDown() error {
304+ ret := _mock.Called()
305+
306+ if len(ret) == 0 {
307+ panic("no return value specified for FocusPaneDown")
308+ }
309+
310+ var r0 error
311+ if returnFunc, ok := ret.Get(0).(func() error); ok {
312+ r0 = returnFunc()
313+ } else {
314+ r0 = ret.Error(0)
315+ }
316+ return r0
317+}
318+
319+// MockContext_FocusPaneDown_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FocusPaneDown'
320+type MockContext_FocusPaneDown_Call struct {
321+ *mock.Call
322+}
323+
324+// FocusPaneDown is a helper method to define mock.On call
325+func (_e *MockContext_Expecter) FocusPaneDown() *MockContext_FocusPaneDown_Call {
326+ return &MockContext_FocusPaneDown_Call{Call: _e.mock.On("FocusPaneDown")}
327+}
328+
329+func (_c *MockContext_FocusPaneDown_Call) Run(run func()) *MockContext_FocusPaneDown_Call {
330+ _c.Call.Run(func(args mock.Arguments) {
331+ run()
332+ })
333+ return _c
334+}
335+
336+func (_c *MockContext_FocusPaneDown_Call) Return(err error) *MockContext_FocusPaneDown_Call {
337+ _c.Call.Return(err)
338+ return _c
339+}
340+
341+func (_c *MockContext_FocusPaneDown_Call) RunAndReturn(run func() error) *MockContext_FocusPaneDown_Call {
342+ _c.Call.Return(run)
343+ return _c
344+}
345+
346+// FocusPaneLeft provides a mock function for the type MockContext
347+func (_mock *MockContext) FocusPaneLeft() error {
348+ ret := _mock.Called()
349+
350+ if len(ret) == 0 {
351+ panic("no return value specified for FocusPaneLeft")
352+ }
353+
354+ var r0 error
355+ if returnFunc, ok := ret.Get(0).(func() error); ok {
356+ r0 = returnFunc()
357+ } else {
358+ r0 = ret.Error(0)
359+ }
360+ return r0
361+}
362+
363+// MockContext_FocusPaneLeft_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FocusPaneLeft'
364+type MockContext_FocusPaneLeft_Call struct {
365+ *mock.Call
366+}
367+
368+// FocusPaneLeft is a helper method to define mock.On call
369+func (_e *MockContext_Expecter) FocusPaneLeft() *MockContext_FocusPaneLeft_Call {
370+ return &MockContext_FocusPaneLeft_Call{Call: _e.mock.On("FocusPaneLeft")}
371+}
372+
373+func (_c *MockContext_FocusPaneLeft_Call) Run(run func()) *MockContext_FocusPaneLeft_Call {
374+ _c.Call.Run(func(args mock.Arguments) {
375+ run()
376+ })
377+ return _c
378+}
379+
380+func (_c *MockContext_FocusPaneLeft_Call) Return(err error) *MockContext_FocusPaneLeft_Call {
381+ _c.Call.Return(err)
382+ return _c
383+}
384+
385+func (_c *MockContext_FocusPaneLeft_Call) RunAndReturn(run func() error) *MockContext_FocusPaneLeft_Call {
386+ _c.Call.Return(run)
387+ return _c
388+}
389+
390+// FocusPaneRight provides a mock function for the type MockContext
391+func (_mock *MockContext) FocusPaneRight() error {
392+ ret := _mock.Called()
393+
394+ if len(ret) == 0 {
395+ panic("no return value specified for FocusPaneRight")
396+ }
397+
398+ var r0 error
399+ if returnFunc, ok := ret.Get(0).(func() error); ok {
400+ r0 = returnFunc()
401+ } else {
402+ r0 = ret.Error(0)
403+ }
404+ return r0
405+}
406+
407+// MockContext_FocusPaneRight_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FocusPaneRight'
408+type MockContext_FocusPaneRight_Call struct {
409+ *mock.Call
410+}
411+
412+// FocusPaneRight is a helper method to define mock.On call
413+func (_e *MockContext_Expecter) FocusPaneRight() *MockContext_FocusPaneRight_Call {
414+ return &MockContext_FocusPaneRight_Call{Call: _e.mock.On("FocusPaneRight")}
415+}
416+
417+func (_c *MockContext_FocusPaneRight_Call) Run(run func()) *MockContext_FocusPaneRight_Call {
418+ _c.Call.Run(func(args mock.Arguments) {
419+ run()
420+ })
421+ return _c
422+}
423+
424+func (_c *MockContext_FocusPaneRight_Call) Return(err error) *MockContext_FocusPaneRight_Call {
425+ _c.Call.Return(err)
426+ return _c
427+}
428+
429+func (_c *MockContext_FocusPaneRight_Call) RunAndReturn(run func() error) *MockContext_FocusPaneRight_Call {
430+ _c.Call.Return(run)
431+ return _c
432+}
433+
434+// FocusPaneUp provides a mock function for the type MockContext
435+func (_mock *MockContext) FocusPaneUp() error {
436+ ret := _mock.Called()
437+
438+ if len(ret) == 0 {
439+ panic("no return value specified for FocusPaneUp")
440+ }
441+
442+ var r0 error
443+ if returnFunc, ok := ret.Get(0).(func() error); ok {
444+ r0 = returnFunc()
445+ } else {
446+ r0 = ret.Error(0)
447+ }
448+ return r0
449+}
450+
451+// MockContext_FocusPaneUp_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FocusPaneUp'
452+type MockContext_FocusPaneUp_Call struct {
453+ *mock.Call
454+}
455+
456+// FocusPaneUp is a helper method to define mock.On call
457+func (_e *MockContext_Expecter) FocusPaneUp() *MockContext_FocusPaneUp_Call {
458+ return &MockContext_FocusPaneUp_Call{Call: _e.mock.On("FocusPaneUp")}
459+}
460+
461+func (_c *MockContext_FocusPaneUp_Call) Run(run func()) *MockContext_FocusPaneUp_Call {
462+ _c.Call.Run(func(args mock.Arguments) {
463+ run()
464+ })
465+ return _c
466+}
467+
468+func (_c *MockContext_FocusPaneUp_Call) Return(err error) *MockContext_FocusPaneUp_Call {
469+ _c.Call.Return(err)
470+ return _c
471+}
472+
473+func (_c *MockContext_FocusPaneUp_Call) RunAndReturn(run func() error) *MockContext_FocusPaneUp_Call {
474+ _c.Call.Return(run)
475+ return _c
476+}
477+
478+// NextTab provides a mock function for the type MockContext
479+func (_mock *MockContext) NextTab() error {
480+ ret := _mock.Called()
481+
482+ if len(ret) == 0 {
483+ panic("no return value specified for NextTab")
484+ }
485+
486+ var r0 error
487+ if returnFunc, ok := ret.Get(0).(func() error); ok {
488+ r0 = returnFunc()
489+ } else {
490+ r0 = ret.Error(0)
491+ }
492+ return r0
493+}
494+
495+// MockContext_NextTab_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'NextTab'
496+type MockContext_NextTab_Call struct {
497+ *mock.Call
498+}
499+
500+// NextTab is a helper method to define mock.On call
501+func (_e *MockContext_Expecter) NextTab() *MockContext_NextTab_Call {
502+ return &MockContext_NextTab_Call{Call: _e.mock.On("NextTab")}
503+}
504+
505+func (_c *MockContext_NextTab_Call) Run(run func()) *MockContext_NextTab_Call {
506+ _c.Call.Run(func(args mock.Arguments) {
507+ run()
508+ })
509+ return _c
510+}
511+
512+func (_c *MockContext_NextTab_Call) Return(err error) *MockContext_NextTab_Call {
513+ _c.Call.Return(err)
514+ return _c
515+}
516+
517+func (_c *MockContext_NextTab_Call) RunAndReturn(run func() error) *MockContext_NextTab_Call {
518+ _c.Call.Return(run)
519+ return _c
520+}
521+
522+// OpenSessionPicker provides a mock function for the type MockContext
523+func (_mock *MockContext) OpenSessionPicker() error {
524+ ret := _mock.Called()
525+
526+ if len(ret) == 0 {
527+ panic("no return value specified for OpenSessionPicker")
528+ }
529+
530+ var r0 error
531+ if returnFunc, ok := ret.Get(0).(func() error); ok {
532+ r0 = returnFunc()
533+ } else {
534+ r0 = ret.Error(0)
535+ }
536+ return r0
537+}
538+
539+// MockContext_OpenSessionPicker_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'OpenSessionPicker'
540+type MockContext_OpenSessionPicker_Call struct {
541+ *mock.Call
542+}
543+
544+// OpenSessionPicker is a helper method to define mock.On call
545+func (_e *MockContext_Expecter) OpenSessionPicker() *MockContext_OpenSessionPicker_Call {
546+ return &MockContext_OpenSessionPicker_Call{Call: _e.mock.On("OpenSessionPicker")}
547+}
548+
549+func (_c *MockContext_OpenSessionPicker_Call) Run(run func()) *MockContext_OpenSessionPicker_Call {
550+ _c.Call.Run(func(args mock.Arguments) {
551+ run()
552+ })
553+ return _c
554+}
555+
556+func (_c *MockContext_OpenSessionPicker_Call) Return(err error) *MockContext_OpenSessionPicker_Call {
557+ _c.Call.Return(err)
558+ return _c
559+}
560+
561+func (_c *MockContext_OpenSessionPicker_Call) RunAndReturn(run func() error) *MockContext_OpenSessionPicker_Call {
562+ _c.Call.Return(run)
563+ return _c
564+}
565+
566+// PrevTab provides a mock function for the type MockContext
567+func (_mock *MockContext) PrevTab() error {
568+ ret := _mock.Called()
569+
570+ if len(ret) == 0 {
571+ panic("no return value specified for PrevTab")
572+ }
573+
574+ var r0 error
575+ if returnFunc, ok := ret.Get(0).(func() error); ok {
576+ r0 = returnFunc()
577+ } else {
578+ r0 = ret.Error(0)
579+ }
580+ return r0
581+}
582+
583+// MockContext_PrevTab_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PrevTab'
584+type MockContext_PrevTab_Call struct {
585+ *mock.Call
586+}
587+
588+// PrevTab is a helper method to define mock.On call
589+func (_e *MockContext_Expecter) PrevTab() *MockContext_PrevTab_Call {
590+ return &MockContext_PrevTab_Call{Call: _e.mock.On("PrevTab")}
591+}
592+
593+func (_c *MockContext_PrevTab_Call) Run(run func()) *MockContext_PrevTab_Call {
594+ _c.Call.Run(func(args mock.Arguments) {
595+ run()
596+ })
597+ return _c
598+}
599+
600+func (_c *MockContext_PrevTab_Call) Return(err error) *MockContext_PrevTab_Call {
601+ _c.Call.Return(err)
602+ return _c
603+}
604+
605+func (_c *MockContext_PrevTab_Call) RunAndReturn(run func() error) *MockContext_PrevTab_Call {
606+ _c.Call.Return(run)
607+ return _c
608+}
609+
610+// RenameSession provides a mock function for the type MockContext
611+func (_mock *MockContext) RenameSession() error {
612+ ret := _mock.Called()
613+
614+ if len(ret) == 0 {
615+ panic("no return value specified for RenameSession")
616+ }
617+
618+ var r0 error
619+ if returnFunc, ok := ret.Get(0).(func() error); ok {
620+ r0 = returnFunc()
621+ } else {
622+ r0 = ret.Error(0)
623+ }
624+ return r0
625+}
626+
627+// MockContext_RenameSession_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RenameSession'
628+type MockContext_RenameSession_Call struct {
629+ *mock.Call
630+}
631+
632+// RenameSession is a helper method to define mock.On call
633+func (_e *MockContext_Expecter) RenameSession() *MockContext_RenameSession_Call {
634+ return &MockContext_RenameSession_Call{Call: _e.mock.On("RenameSession")}
635+}
636+
637+func (_c *MockContext_RenameSession_Call) Run(run func()) *MockContext_RenameSession_Call {
638+ _c.Call.Run(func(args mock.Arguments) {
639+ run()
640+ })
641+ return _c
642+}
643+
644+func (_c *MockContext_RenameSession_Call) Return(err error) *MockContext_RenameSession_Call {
645+ _c.Call.Return(err)
646+ return _c
647+}
648+
649+func (_c *MockContext_RenameSession_Call) RunAndReturn(run func() error) *MockContext_RenameSession_Call {
650+ _c.Call.Return(run)
651+ return _c
652+}
653+
654+// SplitDown provides a mock function for the type MockContext
655+func (_mock *MockContext) SplitDown() error {
656+ ret := _mock.Called()
657+
658+ if len(ret) == 0 {
659+ panic("no return value specified for SplitDown")
660+ }
661+
662+ var r0 error
663+ if returnFunc, ok := ret.Get(0).(func() error); ok {
664+ r0 = returnFunc()
665+ } else {
666+ r0 = ret.Error(0)
667+ }
668+ return r0
669+}
670+
671+// MockContext_SplitDown_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SplitDown'
672+type MockContext_SplitDown_Call struct {
673+ *mock.Call
674+}
675+
676+// SplitDown is a helper method to define mock.On call
677+func (_e *MockContext_Expecter) SplitDown() *MockContext_SplitDown_Call {
678+ return &MockContext_SplitDown_Call{Call: _e.mock.On("SplitDown")}
679+}
680+
681+func (_c *MockContext_SplitDown_Call) Run(run func()) *MockContext_SplitDown_Call {
682+ _c.Call.Run(func(args mock.Arguments) {
683+ run()
684+ })
685+ return _c
686+}
687+
688+func (_c *MockContext_SplitDown_Call) Return(err error) *MockContext_SplitDown_Call {
689+ _c.Call.Return(err)
690+ return _c
691+}
692+
693+func (_c *MockContext_SplitDown_Call) RunAndReturn(run func() error) *MockContext_SplitDown_Call {
694+ _c.Call.Return(run)
695+ return _c
696+}
697+
698+// SplitLeft provides a mock function for the type MockContext
699+func (_mock *MockContext) SplitLeft() error {
700+ ret := _mock.Called()
701+
702+ if len(ret) == 0 {
703+ panic("no return value specified for SplitLeft")
704+ }
705+
706+ var r0 error
707+ if returnFunc, ok := ret.Get(0).(func() error); ok {
708+ r0 = returnFunc()
709+ } else {
710+ r0 = ret.Error(0)
711+ }
712+ return r0
713+}
714+
715+// MockContext_SplitLeft_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SplitLeft'
716+type MockContext_SplitLeft_Call struct {
717+ *mock.Call
718+}
719+
720+// SplitLeft is a helper method to define mock.On call
721+func (_e *MockContext_Expecter) SplitLeft() *MockContext_SplitLeft_Call {
722+ return &MockContext_SplitLeft_Call{Call: _e.mock.On("SplitLeft")}
723+}
724+
725+func (_c *MockContext_SplitLeft_Call) Run(run func()) *MockContext_SplitLeft_Call {
726+ _c.Call.Run(func(args mock.Arguments) {
727+ run()
728+ })
729+ return _c
730+}
731+
732+func (_c *MockContext_SplitLeft_Call) Return(err error) *MockContext_SplitLeft_Call {
733+ _c.Call.Return(err)
734+ return _c
735+}
736+
737+func (_c *MockContext_SplitLeft_Call) RunAndReturn(run func() error) *MockContext_SplitLeft_Call {
738+ _c.Call.Return(run)
739+ return _c
740+}
741+
742+// SplitRight provides a mock function for the type MockContext
743+func (_mock *MockContext) SplitRight() error {
744+ ret := _mock.Called()
745+
746+ if len(ret) == 0 {
747+ panic("no return value specified for SplitRight")
748+ }
749+
750+ var r0 error
751+ if returnFunc, ok := ret.Get(0).(func() error); ok {
752+ r0 = returnFunc()
753+ } else {
754+ r0 = ret.Error(0)
755+ }
756+ return r0
757+}
758+
759+// MockContext_SplitRight_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SplitRight'
760+type MockContext_SplitRight_Call struct {
761+ *mock.Call
762+}
763+
764+// SplitRight is a helper method to define mock.On call
765+func (_e *MockContext_Expecter) SplitRight() *MockContext_SplitRight_Call {
766+ return &MockContext_SplitRight_Call{Call: _e.mock.On("SplitRight")}
767+}
768+
769+func (_c *MockContext_SplitRight_Call) Run(run func()) *MockContext_SplitRight_Call {
770+ _c.Call.Run(func(args mock.Arguments) {
771+ run()
772+ })
773+ return _c
774+}
775+
776+func (_c *MockContext_SplitRight_Call) Return(err error) *MockContext_SplitRight_Call {
777+ _c.Call.Return(err)
778+ return _c
779+}
780+
781+func (_c *MockContext_SplitRight_Call) RunAndReturn(run func() error) *MockContext_SplitRight_Call {
782+ _c.Call.Return(run)
783+ return _c
784+}
785+
786+// SplitUp provides a mock function for the type MockContext
787+func (_mock *MockContext) SplitUp() error {
788+ ret := _mock.Called()
789+
790+ if len(ret) == 0 {
791+ panic("no return value specified for SplitUp")
792+ }
793+
794+ var r0 error
795+ if returnFunc, ok := ret.Get(0).(func() error); ok {
796+ r0 = returnFunc()
797+ } else {
798+ r0 = ret.Error(0)
799+ }
800+ return r0
801+}
802+
803+// MockContext_SplitUp_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SplitUp'
804+type MockContext_SplitUp_Call struct {
805+ *mock.Call
806+}
807+
808+// SplitUp is a helper method to define mock.On call
809+func (_e *MockContext_Expecter) SplitUp() *MockContext_SplitUp_Call {
810+ return &MockContext_SplitUp_Call{Call: _e.mock.On("SplitUp")}
811+}
812+
813+func (_c *MockContext_SplitUp_Call) Run(run func()) *MockContext_SplitUp_Call {
814+ _c.Call.Run(func(args mock.Arguments) {
815+ run()
816+ })
817+ return _c
818+}
819+
820+func (_c *MockContext_SplitUp_Call) Return(err error) *MockContext_SplitUp_Call {
821+ _c.Call.Return(err)
822+ return _c
823+}
824+
825+func (_c *MockContext_SplitUp_Call) RunAndReturn(run func() error) *MockContext_SplitUp_Call {
826+ _c.Call.Return(run)
827+ return _c
828+}
829+
830+// StackPane provides a mock function for the type MockContext
831+func (_mock *MockContext) StackPane() error {
832+ ret := _mock.Called()
833+
834+ if len(ret) == 0 {
835+ panic("no return value specified for StackPane")
836+ }
837+
838+ var r0 error
839+ if returnFunc, ok := ret.Get(0).(func() error); ok {
840+ r0 = returnFunc()
841+ } else {
842+ r0 = ret.Error(0)
843+ }
844+ return r0
845+}
846+
847+// MockContext_StackPane_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'StackPane'
848+type MockContext_StackPane_Call struct {
849+ *mock.Call
850+}
851+
852+// StackPane is a helper method to define mock.On call
853+func (_e *MockContext_Expecter) StackPane() *MockContext_StackPane_Call {
854+ return &MockContext_StackPane_Call{Call: _e.mock.On("StackPane")}
855+}
856+
857+func (_c *MockContext_StackPane_Call) Run(run func()) *MockContext_StackPane_Call {
858+ _c.Call.Run(func(args mock.Arguments) {
859+ run()
860+ })
861+ return _c
862+}
863+
864+func (_c *MockContext_StackPane_Call) Return(err error) *MockContext_StackPane_Call {
865+ _c.Call.Return(err)
866+ return _c
867+}
868+
869+func (_c *MockContext_StackPane_Call) RunAndReturn(run func() error) *MockContext_StackPane_Call {
870+ _c.Call.Return(run)
871+ return _c
872+}
873+
874+// ToggleStack provides a mock function for the type MockContext
875+func (_mock *MockContext) ToggleStack() error {
876+ ret := _mock.Called()
877+
878+ if len(ret) == 0 {
879+ panic("no return value specified for ToggleStack")
880+ }
881+
882+ var r0 error
883+ if returnFunc, ok := ret.Get(0).(func() error); ok {
884+ r0 = returnFunc()
885+ } else {
886+ r0 = ret.Error(0)
887+ }
888+ return r0
889+}
890+
891+// MockContext_ToggleStack_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ToggleStack'
892+type MockContext_ToggleStack_Call struct {
893+ *mock.Call
894+}
895+
896+// ToggleStack is a helper method to define mock.On call
897+func (_e *MockContext_Expecter) ToggleStack() *MockContext_ToggleStack_Call {
898+ return &MockContext_ToggleStack_Call{Call: _e.mock.On("ToggleStack")}
899+}
900+
901+func (_c *MockContext_ToggleStack_Call) Run(run func()) *MockContext_ToggleStack_Call {
902+ _c.Call.Run(func(args mock.Arguments) {
903+ run()
904+ })
905+ return _c
906+}
907+
908+func (_c *MockContext_ToggleStack_Call) Return(err error) *MockContext_ToggleStack_Call {
909+ _c.Call.Return(err)
910+ return _c
911+}
912+
913+func (_c *MockContext_ToggleStack_Call) RunAndReturn(run func() error) *MockContext_ToggleStack_Call {
914+ _c.Call.Return(run)
915+ return _c
916+}
M internal/usecase/command/registry_test.go +24 -64
@@ -1,7 +1,6 @@
11 package command
22
33 import (
4- "fmt"
54 "regexp"
65 "testing"
76 )
@@ -48,29 +47,29 @@ func TestByCodeIsCaseInsensitive(t *testing.T) {
4847
4948 func TestCommandRunCallsMatchingContextMethod(t *testing.T) {
5049 tests := []struct {
51- code string
52- want string
50+ code string
51+ expect func(*MockContext)
5352 }{
54- {code: "CNT", want: "CreateTab"},
55- {code: "CNS", want: "CreateSession"},
56- {code: "CLT", want: "CloseTab"},
57- {code: "SPR", want: "SplitRight"},
58- {code: "SPL", want: "SplitLeft"},
59- {code: "SPU", want: "SplitUp"},
60- {code: "SPD", want: "SplitDown"},
61- {code: "STP", want: "StackPane"},
62- {code: "TST", want: "ToggleStack"},
63- {code: "CLP", want: "ClosePane"},
64- {code: "FPL", want: "FocusPaneLeft"},
65- {code: "FPR", want: "FocusPaneRight"},
66- {code: "FPU", want: "FocusPaneUp"},
67- {code: "FPD", want: "FocusPaneDown"},
68- {code: "NXT", want: "NextTab"},
69- {code: "PVT", want: "PrevTab"},
70- {code: "SSP", want: "OpenSessionPicker"},
71- {code: "VIS", want: "EnterVisualMode"},
72- {code: "RNS", want: "RenameSession"},
73- {code: "DET", want: "Detach"},
53+ {code: "CNT", expect: func(ctx *MockContext) { ctx.EXPECT().CreateTab().Return(nil).Once() }},
54+ {code: "CNS", expect: func(ctx *MockContext) { ctx.EXPECT().CreateSession().Return(nil).Once() }},
55+ {code: "CLT", expect: func(ctx *MockContext) { ctx.EXPECT().CloseTab().Return(nil).Once() }},
56+ {code: "SPR", expect: func(ctx *MockContext) { ctx.EXPECT().SplitRight().Return(nil).Once() }},
57+ {code: "SPL", expect: func(ctx *MockContext) { ctx.EXPECT().SplitLeft().Return(nil).Once() }},
58+ {code: "SPU", expect: func(ctx *MockContext) { ctx.EXPECT().SplitUp().Return(nil).Once() }},
59+ {code: "SPD", expect: func(ctx *MockContext) { ctx.EXPECT().SplitDown().Return(nil).Once() }},
60+ {code: "STP", expect: func(ctx *MockContext) { ctx.EXPECT().StackPane().Return(nil).Once() }},
61+ {code: "TST", expect: func(ctx *MockContext) { ctx.EXPECT().ToggleStack().Return(nil).Once() }},
62+ {code: "CLP", expect: func(ctx *MockContext) { ctx.EXPECT().ClosePane().Return(nil).Once() }},
63+ {code: "FPL", expect: func(ctx *MockContext) { ctx.EXPECT().FocusPaneLeft().Return(nil).Once() }},
64+ {code: "FPR", expect: func(ctx *MockContext) { ctx.EXPECT().FocusPaneRight().Return(nil).Once() }},
65+ {code: "FPU", expect: func(ctx *MockContext) { ctx.EXPECT().FocusPaneUp().Return(nil).Once() }},
66+ {code: "FPD", expect: func(ctx *MockContext) { ctx.EXPECT().FocusPaneDown().Return(nil).Once() }},
67+ {code: "NXT", expect: func(ctx *MockContext) { ctx.EXPECT().NextTab().Return(nil).Once() }},
68+ {code: "PVT", expect: func(ctx *MockContext) { ctx.EXPECT().PrevTab().Return(nil).Once() }},
69+ {code: "SSP", expect: func(ctx *MockContext) { ctx.EXPECT().OpenSessionPicker().Return(nil).Once() }},
70+ {code: "VIS", expect: func(ctx *MockContext) { ctx.EXPECT().EnterVisualMode().Return(nil).Once() }},
71+ {code: "RNS", expect: func(ctx *MockContext) { ctx.EXPECT().RenameSession().Return(nil).Once() }},
72+ {code: "DET", expect: func(ctx *MockContext) { ctx.EXPECT().Detach().Return(nil).Once() }},
7473 }
7574
7675 for _, tt := range tests {
@@ -80,50 +79,11 @@ func TestCommandRunCallsMatchingContextMethod(t *testing.T) {
8079 t.Fatalf("ByCode(%q) ok = false, want true", tt.code)
8180 }
8281
83- ctx := &spyContext{}
82+ ctx := NewMockContext(t)
83+ tt.expect(ctx)
8484 if err := cmd.Run(ctx); err != nil {
8585 t.Fatalf("Run() error = %v, want nil", err)
8686 }
87- if got := ctx.onlyCall(); got != tt.want {
88- t.Fatalf("Run() called %q, want %q", got, tt.want)
89- }
9087 })
9188 }
9289 }
93-
94-type spyContext struct {
95- calls []string
96-}
97-
98-func (s *spyContext) record(name string) error {
99- s.calls = append(s.calls, name)
100- return nil
101-}
102-
103-func (s *spyContext) onlyCall() string {
104- if len(s.calls) != 1 {
105- return fmt.Sprintf("%v", s.calls)
106- }
107- return s.calls[0]
108-}
109-
110-func (s *spyContext) CreateTab() error { return s.record("CreateTab") }
111-func (s *spyContext) CreateSession() error { return s.record("CreateSession") }
112-func (s *spyContext) CloseTab() error { return s.record("CloseTab") }
113-func (s *spyContext) SplitRight() error { return s.record("SplitRight") }
114-func (s *spyContext) SplitLeft() error { return s.record("SplitLeft") }
115-func (s *spyContext) SplitUp() error { return s.record("SplitUp") }
116-func (s *spyContext) SplitDown() error { return s.record("SplitDown") }
117-func (s *spyContext) StackPane() error { return s.record("StackPane") }
118-func (s *spyContext) ToggleStack() error { return s.record("ToggleStack") }
119-func (s *spyContext) ClosePane() error { return s.record("ClosePane") }
120-func (s *spyContext) FocusPaneLeft() error { return s.record("FocusPaneLeft") }
121-func (s *spyContext) FocusPaneRight() error { return s.record("FocusPaneRight") }
122-func (s *spyContext) FocusPaneUp() error { return s.record("FocusPaneUp") }
123-func (s *spyContext) FocusPaneDown() error { return s.record("FocusPaneDown") }
124-func (s *spyContext) NextTab() error { return s.record("NextTab") }
125-func (s *spyContext) PrevTab() error { return s.record("PrevTab") }
126-func (s *spyContext) Detach() error { return s.record("Detach") }
127-func (s *spyContext) EnterVisualMode() error { return s.record("EnterVisualMode") }
128-func (s *spyContext) RenameSession() error { return s.record("RenameSession") }
129-func (s *spyContext) OpenSessionPicker() error { return s.record("OpenSessionPicker") }
@@ -1,7 +1,6 @@
1 package command1 package command
2 2
3 import (3 import (
4- "fmt"
5 "regexp"4 "regexp"
6 "testing"5 "testing"
7 )6 )
@@ -48,29 +47,29 @@ func TestByCodeIsCaseInsensitive(t *testing.T) {
48 47
49 func TestCommandRunCallsMatchingContextMethod(t *testing.T) {48 func TestCommandRunCallsMatchingContextMethod(t *testing.T) {
50 tests := []struct {49 tests := []struct {
51- code string50+ code string
52- want string51+ expect func(*MockContext)
53 }{52 }{
54- {code: "CNT", want: "CreateTab"},53+ {code: "CNT", expect: func(ctx *MockContext) { ctx.EXPECT().CreateTab().Return(nil).Once() }},
55- {code: "CNS", want: "CreateSession"},54+ {code: "CNS", expect: func(ctx *MockContext) { ctx.EXPECT().CreateSession().Return(nil).Once() }},
56- {code: "CLT", want: "CloseTab"},55+ {code: "CLT", expect: func(ctx *MockContext) { ctx.EXPECT().CloseTab().Return(nil).Once() }},
57- {code: "SPR", want: "SplitRight"},56+ {code: "SPR", expect: func(ctx *MockContext) { ctx.EXPECT().SplitRight().Return(nil).Once() }},
58- {code: "SPL", want: "SplitLeft"},57+ {code: "SPL", expect: func(ctx *MockContext) { ctx.EXPECT().SplitLeft().Return(nil).Once() }},
59- {code: "SPU", want: "SplitUp"},58+ {code: "SPU", expect: func(ctx *MockContext) { ctx.EXPECT().SplitUp().Return(nil).Once() }},
60- {code: "SPD", want: "SplitDown"},59+ {code: "SPD", expect: func(ctx *MockContext) { ctx.EXPECT().SplitDown().Return(nil).Once() }},
61- {code: "STP", want: "StackPane"},60+ {code: "STP", expect: func(ctx *MockContext) { ctx.EXPECT().StackPane().Return(nil).Once() }},
62- {code: "TST", want: "ToggleStack"},61+ {code: "TST", expect: func(ctx *MockContext) { ctx.EXPECT().ToggleStack().Return(nil).Once() }},
63- {code: "CLP", want: "ClosePane"},62+ {code: "CLP", expect: func(ctx *MockContext) { ctx.EXPECT().ClosePane().Return(nil).Once() }},
64- {code: "FPL", want: "FocusPaneLeft"},63+ {code: "FPL", expect: func(ctx *MockContext) { ctx.EXPECT().FocusPaneLeft().Return(nil).Once() }},
65- {code: "FPR", want: "FocusPaneRight"},64+ {code: "FPR", expect: func(ctx *MockContext) { ctx.EXPECT().FocusPaneRight().Return(nil).Once() }},
66- {code: "FPU", want: "FocusPaneUp"},65+ {code: "FPU", expect: func(ctx *MockContext) { ctx.EXPECT().FocusPaneUp().Return(nil).Once() }},
67- {code: "FPD", want: "FocusPaneDown"},66+ {code: "FPD", expect: func(ctx *MockContext) { ctx.EXPECT().FocusPaneDown().Return(nil).Once() }},
68- {code: "NXT", want: "NextTab"},67+ {code: "NXT", expect: func(ctx *MockContext) { ctx.EXPECT().NextTab().Return(nil).Once() }},
69- {code: "PVT", want: "PrevTab"},68+ {code: "PVT", expect: func(ctx *MockContext) { ctx.EXPECT().PrevTab().Return(nil).Once() }},
70- {code: "SSP", want: "OpenSessionPicker"},69+ {code: "SSP", expect: func(ctx *MockContext) { ctx.EXPECT().OpenSessionPicker().Return(nil).Once() }},
71- {code: "VIS", want: "EnterVisualMode"},70+ {code: "VIS", expect: func(ctx *MockContext) { ctx.EXPECT().EnterVisualMode().Return(nil).Once() }},
72- {code: "RNS", want: "RenameSession"},71+ {code: "RNS", expect: func(ctx *MockContext) { ctx.EXPECT().RenameSession().Return(nil).Once() }},
73- {code: "DET", want: "Detach"},72+ {code: "DET", expect: func(ctx *MockContext) { ctx.EXPECT().Detach().Return(nil).Once() }},
74 }73 }
75 74
76 for _, tt := range tests {75 for _, tt := range tests {
@@ -80,50 +79,11 @@ func TestCommandRunCallsMatchingContextMethod(t *testing.T) {
80 t.Fatalf("ByCode(%q) ok = false, want true", tt.code)79 t.Fatalf("ByCode(%q) ok = false, want true", tt.code)
81 }80 }
82 81
83- ctx := &spyContext{}82+ ctx := NewMockContext(t)
83+ tt.expect(ctx)
84 if err := cmd.Run(ctx); err != nil {84 if err := cmd.Run(ctx); err != nil {
85 t.Fatalf("Run() error = %v, want nil", err)85 t.Fatalf("Run() error = %v, want nil", err)
86 }86 }
87- if got := ctx.onlyCall(); got != tt.want {
88- t.Fatalf("Run() called %q, want %q", got, tt.want)
89- }
90 })87 })
91 }88 }
92 }89 }
93-
94-type spyContext struct {
95- calls []string
96-}
97-
98-func (s *spyContext) record(name string) error {
99- s.calls = append(s.calls, name)
100- return nil
101-}
102-
103-func (s *spyContext) onlyCall() string {
104- if len(s.calls) != 1 {
105- return fmt.Sprintf("%v", s.calls)
106- }
107- return s.calls[0]
108-}
109-
110-func (s *spyContext) CreateTab() error { return s.record("CreateTab") }
111-func (s *spyContext) CreateSession() error { return s.record("CreateSession") }
112-func (s *spyContext) CloseTab() error { return s.record("CloseTab") }
113-func (s *spyContext) SplitRight() error { return s.record("SplitRight") }
114-func (s *spyContext) SplitLeft() error { return s.record("SplitLeft") }
115-func (s *spyContext) SplitUp() error { return s.record("SplitUp") }
116-func (s *spyContext) SplitDown() error { return s.record("SplitDown") }
117-func (s *spyContext) StackPane() error { return s.record("StackPane") }
118-func (s *spyContext) ToggleStack() error { return s.record("ToggleStack") }
119-func (s *spyContext) ClosePane() error { return s.record("ClosePane") }
120-func (s *spyContext) FocusPaneLeft() error { return s.record("FocusPaneLeft") }
121-func (s *spyContext) FocusPaneRight() error { return s.record("FocusPaneRight") }
122-func (s *spyContext) FocusPaneUp() error { return s.record("FocusPaneUp") }
123-func (s *spyContext) FocusPaneDown() error { return s.record("FocusPaneDown") }
124-func (s *spyContext) NextTab() error { return s.record("NextTab") }
125-func (s *spyContext) PrevTab() error { return s.record("PrevTab") }
126-func (s *spyContext) Detach() error { return s.record("Detach") }
127-func (s *spyContext) EnterVisualMode() error { return s.record("EnterVisualMode") }
128-func (s *spyContext) RenameSession() error { return s.record("RenameSession") }
129-func (s *spyContext) OpenSessionPicker() error { return s.record("OpenSessionPicker") }
M internal/usecase/daemon/mouse_panes.go +48 -2
@@ -1,6 +1,8 @@
11 package daemon
22
33 import (
4+ "strings"
5+
46 "github.com/bnema/vev/internal/domain"
57 "github.com/bnema/vev/internal/usecase/layout"
68 "github.com/bnema/vev/internal/usecase/mouse"
@@ -38,11 +40,55 @@ func hitTestPlacementLocked(tb *tab, col, row int) (layout.Placement, bool) {
3840 return layout.Placement{}, false
3941 }
4042
43+type tabLayoutSnapshot struct {
44+ root *layout.Node
45+ fingerprint string
46+ area domain.Rect
47+ focus layout.PaneID
48+ placements []layout.Placement
49+ ok bool
50+}
51+
4152 func solvedPlacementsLocked(tb *tab) ([]layout.Placement, bool) {
53+ snap := solveTabLayoutLocked(tb)
54+ return snap.placements, snap.ok
55+}
56+
57+func solveTabLayoutLocked(tb *tab) tabLayoutSnapshot {
4258 if tb == nil || tb.tree == nil || tb.tree.Root == nil || !tb.size.Valid() {
43- return nil, false
59+ return tabLayoutSnapshot{}
60+ }
61+ area := domain.Rect{Width: tb.size.Cols, Height: tb.size.Rows}
62+ placements, ok := layout.Solve(tb.tree.Root, area)
63+ return tabLayoutSnapshot{root: tb.tree.Root, fingerprint: layoutFingerprint(tb.tree.Root), area: area, focus: tb.tree.Focus, placements: placements, ok: ok}
64+}
65+
66+func (snap tabLayoutSnapshot) matchesLocked(tb *tab) bool {
67+ return snap.ok && tb != nil && tb.tree != nil && tb.tree.Root == snap.root && tb.tree.Focus == snap.focus && tb.size.Cols == snap.area.Width && tb.size.Rows == snap.area.Height && snap.fingerprint == layoutFingerprint(tb.tree.Root)
68+}
69+
70+func layoutFingerprint(root *layout.Node) string {
71+ var b strings.Builder
72+ writeLayoutFingerprint(&b, root)
73+ return b.String()
74+}
75+
76+func writeLayoutFingerprint(b *strings.Builder, n *layout.Node) {
77+ if n == nil {
78+ b.WriteByte('0')
79+ return
80+ }
81+ b.WriteByte(byte('0' + n.Kind))
82+ b.WriteByte(byte('0' + n.Dir))
83+ b.WriteString(string(n.Leaf))
84+ b.WriteByte('|')
85+ b.WriteString(string(n.Expanded))
86+ b.WriteByte('[')
87+ for _, child := range n.Children {
88+ writeLayoutFingerprint(b, child)
89+ b.WriteByte(',')
4490 }
45- return layout.Solve(tb.tree.Root, domain.Rect{Width: tb.size.Cols, Height: tb.size.Rows})
91+ b.WriteByte(']')
4692 }
4793
4894 func pointInRect(col, row int, r domain.Rect) bool {
@@ -1,6 +1,8 @@
1 package daemon1 package daemon
2 2
3 import (3 import (
4+ "strings"
5+
4 "github.com/bnema/vev/internal/domain"6 "github.com/bnema/vev/internal/domain"
5 "github.com/bnema/vev/internal/usecase/layout"7 "github.com/bnema/vev/internal/usecase/layout"
6 "github.com/bnema/vev/internal/usecase/mouse"8 "github.com/bnema/vev/internal/usecase/mouse"
@@ -38,11 +40,55 @@ func hitTestPlacementLocked(tb *tab, col, row int) (layout.Placement, bool) {
38 return layout.Placement{}, false40 return layout.Placement{}, false
39 }41 }
40 42
43+type tabLayoutSnapshot struct {
44+ root *layout.Node
45+ fingerprint string
46+ area domain.Rect
47+ focus layout.PaneID
48+ placements []layout.Placement
49+ ok bool
50+}
51+
41 func solvedPlacementsLocked(tb *tab) ([]layout.Placement, bool) {52 func solvedPlacementsLocked(tb *tab) ([]layout.Placement, bool) {
53+ snap := solveTabLayoutLocked(tb)
54+ return snap.placements, snap.ok
55+}
56+
57+func solveTabLayoutLocked(tb *tab) tabLayoutSnapshot {
42 if tb == nil || tb.tree == nil || tb.tree.Root == nil || !tb.size.Valid() {58 if tb == nil || tb.tree == nil || tb.tree.Root == nil || !tb.size.Valid() {
43- return nil, false59+ return tabLayoutSnapshot{}
60+ }
61+ area := domain.Rect{Width: tb.size.Cols, Height: tb.size.Rows}
62+ placements, ok := layout.Solve(tb.tree.Root, area)
63+ return tabLayoutSnapshot{root: tb.tree.Root, fingerprint: layoutFingerprint(tb.tree.Root), area: area, focus: tb.tree.Focus, placements: placements, ok: ok}
64+}
65+
66+func (snap tabLayoutSnapshot) matchesLocked(tb *tab) bool {
67+ return snap.ok && tb != nil && tb.tree != nil && tb.tree.Root == snap.root && tb.tree.Focus == snap.focus && tb.size.Cols == snap.area.Width && tb.size.Rows == snap.area.Height && snap.fingerprint == layoutFingerprint(tb.tree.Root)
68+}
69+
70+func layoutFingerprint(root *layout.Node) string {
71+ var b strings.Builder
72+ writeLayoutFingerprint(&b, root)
73+ return b.String()
74+}
75+
76+func writeLayoutFingerprint(b *strings.Builder, n *layout.Node) {
77+ if n == nil {
78+ b.WriteByte('0')
79+ return
80+ }
81+ b.WriteByte(byte('0' + n.Kind))
82+ b.WriteByte(byte('0' + n.Dir))
83+ b.WriteString(string(n.Leaf))
84+ b.WriteByte('|')
85+ b.WriteString(string(n.Expanded))
86+ b.WriteByte('[')
87+ for _, child := range n.Children {
88+ writeLayoutFingerprint(b, child)
89+ b.WriteByte(',')
44 }90 }
45- return layout.Solve(tb.tree.Root, domain.Rect{Width: tb.size.Cols, Height: tb.size.Rows})91+ b.WriteByte(']')
46 }92 }
47 93
48 func pointInRect(col, row int, r domain.Rect) bool {94 func pointInRect(col, row int, r domain.Rect) bool {
M internal/usecase/daemon/pane_actions.go +15 -71
@@ -9,6 +9,17 @@ import (
99 )
1010
1111 func (d *Daemon) splitPane(sess *session, ac *attachedClient, dir layout.Direction) error {
12+ after := dir == layout.Right || dir == layout.Down
13+ return d.spawnPaneOp(sess, ac, func(tree *layout.Tree, oldFocus, newID layout.PaneID, area domain.Rect) error {
14+ return tree.Split(oldFocus, dir, after, newID, area)
15+ })
16+}
17+
18+func (d *Daemon) spawnPaneOp(
19+ sess *session,
20+ ac *attachedClient,
21+ mutate func(tree *layout.Tree, oldFocus, newID layout.PaneID, area domain.Rect) error,
22+) error {
1223 if d.ptys == nil {
1324 if ac != nil {
1425 d.paint(sess, ac, true)
@@ -31,9 +42,8 @@ func (d *Daemon) splitPane(sess *session, ac *attachedClient, dir layout.Directi
3142 }
3243 oldFocus := tb.tree.Focus
3344 newID := layout.PaneID(fmt.Sprintf("pane-%d", tb.nextPaneID))
34- after := dir == layout.Right || dir == layout.Down
3545 area := domain.Rect{Width: tb.size.Cols, Height: tb.size.Rows}
36- if err := tb.tree.Split(oldFocus, dir, after, newID, area); err != nil {
46+ if err := mutate(tb.tree, oldFocus, newID, area); err != nil {
3747 tb.mu.Unlock()
3848 return err
3949 }
@@ -134,75 +144,9 @@ func placementContent(placements []layout.Placement, id layout.PaneID) domain.Re
134144 func rectSize(r domain.Rect) domain.Size { return domain.Size{Cols: r.Width, Rows: r.Height} }
135145
136146 func (d *Daemon) stackPane(sess *session, ac *attachedClient) error {
137- if d.ptys == nil {
138- if ac != nil {
139- d.paint(sess, ac, true)
140- }
141- return nil
142- }
143- tb := sess.activeTab()
144- if tb == nil {
145- return layout.ErrNotFound
146- }
147-
148- sess.mu.Lock()
149- name, cwd := sess.name, sess.cwd
150- sess.mu.Unlock()
151-
152- tb.mu.Lock()
153- if tb.tree == nil || tb.tree.Root == nil {
154- tb.mu.Unlock()
155- return layout.ErrNotFound
156- }
157- oldFocus := tb.tree.Focus
158- newID := layout.PaneID(fmt.Sprintf("pane-%d", tb.nextPaneID))
159- area := domain.Rect{Width: tb.size.Cols, Height: tb.size.Rows}
160- if err := tb.tree.StackNew(oldFocus, newID, area); err != nil {
161- tb.mu.Unlock()
162- return err
163- }
164- tb.nextPaneID++
165- placements, ok := layout.Solve(tb.tree.Root, area)
166- if !ok {
167- _ = tb.tree.Close(newID)
168- tb.tree.Focus = oldFocus
169- tb.mu.Unlock()
170- return layout.ErrTooSmall
171- }
172- newRect := placementContent(placements, newID)
173- tb.mu.Unlock()
174-
175- pty, err := d.ptys.Open(d.shell, d.shellArgs, d.childEnv(name), cwd, rectSize(newRect))
176- if err != nil {
177- tb.mu.Lock()
178- _ = tb.tree.Close(newID)
179- tb.tree.Focus = oldFocus
180- tb.mu.Unlock()
181- return err
182- }
183-
184- pctx, cancel := context.WithCancel(tb.ctx)
185- p := newPane(newID, pty, rectSize(newRect))
186- p.ctx, p.cancel = pctx, cancel
187- p.rect = newRect
188-
189- tb.mu.Lock()
190- if _, ok := tb.panes[newID]; ok || !layout.ContainsLeaf(tb.tree.Root, newID) {
191- tb.mu.Unlock()
192- cancel()
193- _ = pty.Close()
194- return layout.ErrNotFound
195- }
196- tb.panes[newID] = p
197- tb.tree.Focus = newID
198- d.applyLayoutLocked(tb)
199- tb.mu.Unlock()
200-
201- d.startPaneGoroutines(sess, tb, p)
202- if ac != nil {
203- d.paint(sess, ac, true)
204- }
205- return nil
147+ return d.spawnPaneOp(sess, ac, func(tree *layout.Tree, oldFocus, newID layout.PaneID, area domain.Rect) error {
148+ return tree.StackNew(oldFocus, newID, area)
149+ })
206150 }
207151
208152 func (d *Daemon) toggleStack(sess *session, ac *attachedClient) error {
@@ -9,6 +9,17 @@ import (
9 )9 )
10 10
11 func (d *Daemon) splitPane(sess *session, ac *attachedClient, dir layout.Direction) error {11 func (d *Daemon) splitPane(sess *session, ac *attachedClient, dir layout.Direction) error {
12+ after := dir == layout.Right || dir == layout.Down
13+ return d.spawnPaneOp(sess, ac, func(tree *layout.Tree, oldFocus, newID layout.PaneID, area domain.Rect) error {
14+ return tree.Split(oldFocus, dir, after, newID, area)
15+ })
16+}
17+
18+func (d *Daemon) spawnPaneOp(
19+ sess *session,
20+ ac *attachedClient,
21+ mutate func(tree *layout.Tree, oldFocus, newID layout.PaneID, area domain.Rect) error,
22+) error {
12 if d.ptys == nil {23 if d.ptys == nil {
13 if ac != nil {24 if ac != nil {
14 d.paint(sess, ac, true)25 d.paint(sess, ac, true)
@@ -31,9 +42,8 @@ func (d *Daemon) splitPane(sess *session, ac *attachedClient, dir layout.Directi
31 }42 }
32 oldFocus := tb.tree.Focus43 oldFocus := tb.tree.Focus
33 newID := layout.PaneID(fmt.Sprintf("pane-%d", tb.nextPaneID))44 newID := layout.PaneID(fmt.Sprintf("pane-%d", tb.nextPaneID))
34- after := dir == layout.Right || dir == layout.Down
35 area := domain.Rect{Width: tb.size.Cols, Height: tb.size.Rows}45 area := domain.Rect{Width: tb.size.Cols, Height: tb.size.Rows}
36- if err := tb.tree.Split(oldFocus, dir, after, newID, area); err != nil {46+ if err := mutate(tb.tree, oldFocus, newID, area); err != nil {
37 tb.mu.Unlock()47 tb.mu.Unlock()
38 return err48 return err
39 }49 }
@@ -134,75 +144,9 @@ func placementContent(placements []layout.Placement, id layout.PaneID) domain.Re
134 func rectSize(r domain.Rect) domain.Size { return domain.Size{Cols: r.Width, Rows: r.Height} }144 func rectSize(r domain.Rect) domain.Size { return domain.Size{Cols: r.Width, Rows: r.Height} }
135 145
136 func (d *Daemon) stackPane(sess *session, ac *attachedClient) error {146 func (d *Daemon) stackPane(sess *session, ac *attachedClient) error {
137- if d.ptys == nil {147+ return d.spawnPaneOp(sess, ac, func(tree *layout.Tree, oldFocus, newID layout.PaneID, area domain.Rect) error {
138- if ac != nil {148+ return tree.StackNew(oldFocus, newID, area)
139- d.paint(sess, ac, true)149+ })
140- }
141- return nil
142- }
143- tb := sess.activeTab()
144- if tb == nil {
145- return layout.ErrNotFound
146- }
147-
148- sess.mu.Lock()
149- name, cwd := sess.name, sess.cwd
150- sess.mu.Unlock()
151-
152- tb.mu.Lock()
153- if tb.tree == nil || tb.tree.Root == nil {
154- tb.mu.Unlock()
155- return layout.ErrNotFound
156- }
157- oldFocus := tb.tree.Focus
158- newID := layout.PaneID(fmt.Sprintf("pane-%d", tb.nextPaneID))
159- area := domain.Rect{Width: tb.size.Cols, Height: tb.size.Rows}
160- if err := tb.tree.StackNew(oldFocus, newID, area); err != nil {
161- tb.mu.Unlock()
162- return err
163- }
164- tb.nextPaneID++
165- placements, ok := layout.Solve(tb.tree.Root, area)
166- if !ok {
167- _ = tb.tree.Close(newID)
168- tb.tree.Focus = oldFocus
169- tb.mu.Unlock()
170- return layout.ErrTooSmall
171- }
172- newRect := placementContent(placements, newID)
173- tb.mu.Unlock()
174-
175- pty, err := d.ptys.Open(d.shell, d.shellArgs, d.childEnv(name), cwd, rectSize(newRect))
176- if err != nil {
177- tb.mu.Lock()
178- _ = tb.tree.Close(newID)
179- tb.tree.Focus = oldFocus
180- tb.mu.Unlock()
181- return err
182- }
183-
184- pctx, cancel := context.WithCancel(tb.ctx)
185- p := newPane(newID, pty, rectSize(newRect))
186- p.ctx, p.cancel = pctx, cancel
187- p.rect = newRect
188-
189- tb.mu.Lock()
190- if _, ok := tb.panes[newID]; ok || !layout.ContainsLeaf(tb.tree.Root, newID) {
191- tb.mu.Unlock()
192- cancel()
193- _ = pty.Close()
194- return layout.ErrNotFound
195- }
196- tb.panes[newID] = p
197- tb.tree.Focus = newID
198- d.applyLayoutLocked(tb)
199- tb.mu.Unlock()
200-
201- d.startPaneGoroutines(sess, tb, p)
202- if ac != nil {
203- d.paint(sess, ac, true)
204- }
205- return nil
206 }150 }
207 151
208 func (d *Daemon) toggleStack(sess *session, ac *attachedClient) error {152 func (d *Daemon) toggleStack(sess *session, ac *attachedClient) error {
M internal/usecase/daemon/pane_titles_test.go +6 -2
@@ -15,7 +15,9 @@ func TestRefreshPaneTitleUsesForegroundProcessComm(t *testing.T) {
1515 pty := portsmocks.NewMockPTY(t)
1616 pty.EXPECT().ForegroundPgid().Return(1234, nil).Once()
1717 _, sess, _, _ := newManualSessionWithPTYs(t, pty)
18- d := newTestDaemon(t, nil, stubClock{})
18+ clk := portsmocks.NewMockClock(t)
19+ clk.EXPECT().Now().Return(time.Time{}).Maybe()
20+ d := newTestDaemon(t, nil, clk)
1921 d.shell = "/bin/zsh"
2022 d.procComm = func(pid int) (string, error) {
2123 require.Equal(t, 1234, pid)
@@ -55,7 +57,9 @@ func TestRefreshPaneTitleFallsBackToShellBase(t *testing.T) {
5557 pty := portsmocks.NewMockPTY(t)
5658 pty.EXPECT().ForegroundPgid().Return(0, errors.New("no foreground process")).Once()
5759 _, sess, _, _ := newManualSessionWithPTYs(t, pty)
58- d := newTestDaemon(t, nil, stubClock{})
60+ clk := portsmocks.NewMockClock(t)
61+ clk.EXPECT().Now().Return(time.Time{}).Maybe()
62+ d := newTestDaemon(t, nil, clk)
5963 d.shell = "/usr/bin/fish"
6064 d.procComm = func(int) (string, error) { return "", errors.New("unused") }
6165
@@ -15,7 +15,9 @@ func TestRefreshPaneTitleUsesForegroundProcessComm(t *testing.T) {
15 pty := portsmocks.NewMockPTY(t)15 pty := portsmocks.NewMockPTY(t)
16 pty.EXPECT().ForegroundPgid().Return(1234, nil).Once()16 pty.EXPECT().ForegroundPgid().Return(1234, nil).Once()
17 _, sess, _, _ := newManualSessionWithPTYs(t, pty)17 _, sess, _, _ := newManualSessionWithPTYs(t, pty)
18- d := newTestDaemon(t, nil, stubClock{})18+ clk := portsmocks.NewMockClock(t)
19+ clk.EXPECT().Now().Return(time.Time{}).Maybe()
20+ d := newTestDaemon(t, nil, clk)
19 d.shell = "/bin/zsh"21 d.shell = "/bin/zsh"
20 d.procComm = func(pid int) (string, error) {22 d.procComm = func(pid int) (string, error) {
21 require.Equal(t, 1234, pid)23 require.Equal(t, 1234, pid)
@@ -55,7 +57,9 @@ func TestRefreshPaneTitleFallsBackToShellBase(t *testing.T) {
55 pty := portsmocks.NewMockPTY(t)57 pty := portsmocks.NewMockPTY(t)
56 pty.EXPECT().ForegroundPgid().Return(0, errors.New("no foreground process")).Once()58 pty.EXPECT().ForegroundPgid().Return(0, errors.New("no foreground process")).Once()
57 _, sess, _, _ := newManualSessionWithPTYs(t, pty)59 _, sess, _, _ := newManualSessionWithPTYs(t, pty)
58- d := newTestDaemon(t, nil, stubClock{})60+ clk := portsmocks.NewMockClock(t)
61+ clk.EXPECT().Now().Return(time.Time{}).Maybe()
62+ d := newTestDaemon(t, nil, clk)
59 d.shell = "/usr/bin/fish"63 d.shell = "/usr/bin/fish"
60 d.procComm = func(int) (string, error) { return "", errors.New("unused") }64 d.procComm = func(int) (string, error) { return "", errors.New("unused") }
61 65
M internal/usecase/daemon/picker.go +23 -6
@@ -32,7 +32,9 @@ import (
3232 "github.com/bnema/vev/internal/platform"
3333 "github.com/bnema/vev/internal/ports"
3434 "github.com/bnema/vev/internal/usecase/keys"
35+ "github.com/bnema/vev/internal/usecase/layout"
3536 "github.com/bnema/vev/internal/usecase/picker"
37+ themeui "github.com/bnema/vev/internal/usecase/theme"
3638 "github.com/bnema/vev/internal/usecase/ui"
3739 "github.com/bnema/vev/pkg/renderer"
3840 )
@@ -503,15 +505,30 @@ func pickerPreviewFromLockedTab(tb *tab) picker.Preview {
503505 if p == nil {
504506 return picker.Preview{}
505507 }
506- p.mu.Lock()
507- defer p.mu.Unlock()
508- return pickerPreviewFromLockedPane(p)
508+ if tb.tree == nil || tb.tree.Root == nil || tb.tree.Root.Kind == layout.Leaf {
509+ p.mu.Lock()
510+ defer p.mu.Unlock()
511+ return pickerPreviewFromLockedPane(p)
512+ }
513+
514+ area := domain.Rect{Width: tb.size.Cols, Height: tb.size.Rows}
515+ if area.Width <= 0 || area.Height <= 0 {
516+ p.mu.Lock()
517+ defer p.mu.Unlock()
518+ return pickerPreviewFromLockedPane(p)
519+ }
520+ frame, _ := composeTabFrame(tb, area, themeui.Theme{})
521+ return pickerPreviewFromFrame(frame)
509522 }
510523
511524 func pickerPreviewFromLockedPane(p *pane) picker.Preview {
512- rows := make([][]renderer.Cell, p.screen.Frame.Height)
525+ return pickerPreviewFromFrame(p.screen.Frame)
526+}
527+
528+func pickerPreviewFromFrame(frame renderer.Frame) picker.Preview {
529+ rows := make([][]renderer.Cell, frame.Height)
513530 for y := range rows {
514- rows[y] = append([]renderer.Cell(nil), p.screen.Frame.Row(y)...)
531+ rows[y] = append([]renderer.Cell(nil), frame.Row(y)...)
515532 }
516- return picker.Preview{Rows: rows, Width: p.screen.Frame.Width, Height: p.screen.Frame.Height}
533+ return picker.Preview{Rows: rows, Width: frame.Width, Height: frame.Height}
517534 }
@@ -32,7 +32,9 @@ import (
32 "github.com/bnema/vev/internal/platform"32 "github.com/bnema/vev/internal/platform"
33 "github.com/bnema/vev/internal/ports"33 "github.com/bnema/vev/internal/ports"
34 "github.com/bnema/vev/internal/usecase/keys"34 "github.com/bnema/vev/internal/usecase/keys"
35+ "github.com/bnema/vev/internal/usecase/layout"
35 "github.com/bnema/vev/internal/usecase/picker"36 "github.com/bnema/vev/internal/usecase/picker"
37+ themeui "github.com/bnema/vev/internal/usecase/theme"
36 "github.com/bnema/vev/internal/usecase/ui"38 "github.com/bnema/vev/internal/usecase/ui"
37 "github.com/bnema/vev/pkg/renderer"39 "github.com/bnema/vev/pkg/renderer"
38 )40 )
@@ -503,15 +505,30 @@ func pickerPreviewFromLockedTab(tb *tab) picker.Preview {
503 if p == nil {505 if p == nil {
504 return picker.Preview{}506 return picker.Preview{}
505 }507 }
506- p.mu.Lock()508+ if tb.tree == nil || tb.tree.Root == nil || tb.tree.Root.Kind == layout.Leaf {
507- defer p.mu.Unlock()509+ p.mu.Lock()
508- return pickerPreviewFromLockedPane(p)510+ defer p.mu.Unlock()
511+ return pickerPreviewFromLockedPane(p)
512+ }
513+
514+ area := domain.Rect{Width: tb.size.Cols, Height: tb.size.Rows}
515+ if area.Width <= 0 || area.Height <= 0 {
516+ p.mu.Lock()
517+ defer p.mu.Unlock()
518+ return pickerPreviewFromLockedPane(p)
519+ }
520+ frame, _ := composeTabFrame(tb, area, themeui.Theme{})
521+ return pickerPreviewFromFrame(frame)
509 }522 }
510 523
511 func pickerPreviewFromLockedPane(p *pane) picker.Preview {524 func pickerPreviewFromLockedPane(p *pane) picker.Preview {
512- rows := make([][]renderer.Cell, p.screen.Frame.Height)525+ return pickerPreviewFromFrame(p.screen.Frame)
526+}
527+
528+func pickerPreviewFromFrame(frame renderer.Frame) picker.Preview {
529+ rows := make([][]renderer.Cell, frame.Height)
513 for y := range rows {530 for y := range rows {
514- rows[y] = append([]renderer.Cell(nil), p.screen.Frame.Row(y)...)531+ rows[y] = append([]renderer.Cell(nil), frame.Row(y)...)
515 }532 }
516- return picker.Preview{Rows: rows, Width: p.screen.Frame.Width, Height: p.screen.Frame.Height}533+ return picker.Preview{Rows: rows, Width: frame.Width, Height: frame.Height}
517 }534 }
M internal/usecase/daemon/picker_test.go +46 -0
@@ -11,6 +11,7 @@ import (
1111 "github.com/bnema/vev/internal/domain"
1212 "github.com/bnema/vev/internal/ports"
1313 "github.com/bnema/vev/internal/usecase/keys"
14+ "github.com/bnema/vev/internal/usecase/layout"
1415 "github.com/bnema/vev/pkg/renderer"
1516 )
1617
@@ -178,6 +179,51 @@ func TestPickerCrossSessionSwitchDetachesExistingClient(t *testing.T) {
178179 awaitFrame(t, sends1, ports.MsgOutput)
179180 }
180181
182+func TestPickerPreviewSinglePaneSnapshotsFocusedPane(t *testing.T) {
183+ tb := newTab(nil, domain.Size{Cols: 10, Rows: 3})
184+ p := tb.focusedPane()
185+ p.screen.Write([]byte("focused"))
186+
187+ preview := snapshotPickerPreview(tb)
188+
189+ require.Equal(t, 10, preview.Width)
190+ require.Equal(t, 3, preview.Height)
191+ require.Equal(t, 'f', preview.Rows[0][0].Rune)
192+ require.Equal(t, 'o', preview.Rows[0][1].Rune)
193+}
194+
195+func TestPickerPreviewMultiPaneComposesTabFrame(t *testing.T) {
196+ tb := newTab(nil, domain.Size{Cols: 41, Rows: 5})
197+ left := tb.focusedPane()
198+ left.title = "one"
199+ left.screen.Write([]byte("L"))
200+ rightTop := newPane("pane-2", nil, domain.Size{Cols: 20, Rows: 3})
201+ rightTop.title = "two"
202+ rightBottom := newPane("pane-3", nil, domain.Size{Cols: 20, Rows: 2})
203+ rightBottom.title = "three"
204+ rightBottom.screen.Write([]byte("R"))
205+
206+ tb.mu.Lock()
207+ tb.panes[rightTop.id] = rightTop
208+ tb.panes[rightBottom.id] = rightBottom
209+ tb.tree.Root = &layout.Node{Kind: layout.Split, Dir: layout.Horizontal, Children: []*layout.Node{
210+ layout.NewLeaf(left.id),
211+ {Kind: layout.Stack, Children: []*layout.Node{layout.NewLeaf(rightTop.id), layout.NewLeaf(rightBottom.id)}, Expanded: rightBottom.id},
212+ }}
213+ tb.tree.Focus = rightBottom.id
214+ tb.mu.Unlock()
215+
216+ preview := snapshotPickerPreview(tb)
217+
218+ require.Equal(t, 41, preview.Width)
219+ require.Equal(t, 5, preview.Height)
220+ require.Equal(t, 'L', preview.Rows[0][0].Rune, "left pane content should remain visible")
221+ require.Equal(t, '│', preview.Rows[0][20].Rune, "split divider should be included")
222+ require.Equal(t, "two", rowText(preview.Rows[0][21:24]), "collapsed stack title bar should be included")
223+ require.Equal(t, "three", rowText(preview.Rows[1][21:26]), "expanded stack title bar should be included")
224+ require.Equal(t, 'R', preview.Rows[2][21].Rune, "expanded stacked pane content should be included")
225+}
226+
181227 func TestPickerLivePreviewRepaintsInactiveTab(t *testing.T) {
182228 d, sess, ac, sends, releases := newManualTabSession(t, 2)
183229 defer func() {
@@ -11,6 +11,7 @@ import (
11 "github.com/bnema/vev/internal/domain"11 "github.com/bnema/vev/internal/domain"
12 "github.com/bnema/vev/internal/ports"12 "github.com/bnema/vev/internal/ports"
13 "github.com/bnema/vev/internal/usecase/keys"13 "github.com/bnema/vev/internal/usecase/keys"
14+ "github.com/bnema/vev/internal/usecase/layout"
14 "github.com/bnema/vev/pkg/renderer"15 "github.com/bnema/vev/pkg/renderer"
15 )16 )
16 17
@@ -178,6 +179,51 @@ func TestPickerCrossSessionSwitchDetachesExistingClient(t *testing.T) {
178 awaitFrame(t, sends1, ports.MsgOutput)179 awaitFrame(t, sends1, ports.MsgOutput)
179 }180 }
180 181
182+func TestPickerPreviewSinglePaneSnapshotsFocusedPane(t *testing.T) {
183+ tb := newTab(nil, domain.Size{Cols: 10, Rows: 3})
184+ p := tb.focusedPane()
185+ p.screen.Write([]byte("focused"))
186+
187+ preview := snapshotPickerPreview(tb)
188+
189+ require.Equal(t, 10, preview.Width)
190+ require.Equal(t, 3, preview.Height)
191+ require.Equal(t, 'f', preview.Rows[0][0].Rune)
192+ require.Equal(t, 'o', preview.Rows[0][1].Rune)
193+}
194+
195+func TestPickerPreviewMultiPaneComposesTabFrame(t *testing.T) {
196+ tb := newTab(nil, domain.Size{Cols: 41, Rows: 5})
197+ left := tb.focusedPane()
198+ left.title = "one"
199+ left.screen.Write([]byte("L"))
200+ rightTop := newPane("pane-2", nil, domain.Size{Cols: 20, Rows: 3})
201+ rightTop.title = "two"
202+ rightBottom := newPane("pane-3", nil, domain.Size{Cols: 20, Rows: 2})
203+ rightBottom.title = "three"
204+ rightBottom.screen.Write([]byte("R"))
205+
206+ tb.mu.Lock()
207+ tb.panes[rightTop.id] = rightTop
208+ tb.panes[rightBottom.id] = rightBottom
209+ tb.tree.Root = &layout.Node{Kind: layout.Split, Dir: layout.Horizontal, Children: []*layout.Node{
210+ layout.NewLeaf(left.id),
211+ {Kind: layout.Stack, Children: []*layout.Node{layout.NewLeaf(rightTop.id), layout.NewLeaf(rightBottom.id)}, Expanded: rightBottom.id},
212+ }}
213+ tb.tree.Focus = rightBottom.id
214+ tb.mu.Unlock()
215+
216+ preview := snapshotPickerPreview(tb)
217+
218+ require.Equal(t, 41, preview.Width)
219+ require.Equal(t, 5, preview.Height)
220+ require.Equal(t, 'L', preview.Rows[0][0].Rune, "left pane content should remain visible")
221+ require.Equal(t, '│', preview.Rows[0][20].Rune, "split divider should be included")
222+ require.Equal(t, "two", rowText(preview.Rows[0][21:24]), "collapsed stack title bar should be included")
223+ require.Equal(t, "three", rowText(preview.Rows[1][21:26]), "expanded stack title bar should be included")
224+ require.Equal(t, 'R', preview.Rows[2][21].Rune, "expanded stacked pane content should be included")
225+}
226+
181 func TestPickerLivePreviewRepaintsInactiveTab(t *testing.T) {227 func TestPickerLivePreviewRepaintsInactiveTab(t *testing.T) {
182 d, sess, ac, sends, releases := newManualTabSession(t, 2)228 d, sess, ac, sends, releases := newManualTabSession(t, 2)
183 defer func() {229 defer func() {
M internal/usecase/daemon/render.go +42 -15
@@ -247,6 +247,19 @@ func (d *Daemon) render(sess *session, tb *tab, p *pane) {
247247 // paint draws the composed client frame (active tab plus status bar) and
248248 // sends the resulting bytes. The renderer shadow is reset on explicit invalidations
249249 // such as switch/create/close/rename/resize so the repaint is complete.
250+func titleBarPaneIDs(placements []layout.Placement, ok bool) []layout.PaneID {
251+ if !ok {
252+ return nil
253+ }
254+ ids := make([]layout.PaneID, 0, len(placements))
255+ for _, pl := range placements {
256+ if pl.TitleBar.Height > 0 {
257+ ids = append(ids, pl.ID)
258+ }
259+ }
260+ return ids
261+}
262+
250263 func (d *Daemon) paint(sess *session, ac *attachedClient, reset bool) {
251264 tb := sess.activeTab()
252265 if tb == nil {
@@ -271,7 +284,10 @@ func (d *Daemon) paint(sess *session, ac *attachedClient, reset bool) {
271284 pickerModel := ac.picker.Clone()
272285 previewTab := ac.pickerPreview
273286 ac.pickerMu.Unlock()
274- preview := snapshotPickerPreview(previewTab)
287+ preview := snapshotPickerPreview(nil)
288+ if previewTab != tb {
289+ preview = snapshotPickerPreview(previewTab)
290+ }
275291 ac.paletteMu.Lock()
276292 paletteModel := ac.palette
277293 paletteActive := paletteModel != nil
@@ -292,20 +308,17 @@ func (d *Daemon) paint(sess *session, ac *attachedClient, reset bool) {
292308
293309 styles := newThemeStyles(ac.getTheme())
294310 tb.mu.Lock()
295- var titleIDs []layout.PaneID
296- if placements, ok := solvedPlacementsLocked(tb); ok {
297- for _, pl := range placements {
298- if pl.TitleBar.Height > 0 {
299- titleIDs = append(titleIDs, pl.ID)
300- }
301- }
302- }
311+ layoutSnap := solveTabLayoutLocked(tb)
312+ titleIDs := titleBarPaneIDs(layoutSnap.placements, layoutSnap.ok)
303313 tb.mu.Unlock()
304314 for _, id := range titleIDs {
305315 d.refreshPaneTitle(sess, id)
306316 }
307317
308318 tb.mu.Lock()
319+ if !layoutSnap.matchesLocked(tb) {
320+ layoutSnap = solveTabLayoutLocked(tb)
321+ }
309322 p := tb.focusedPane()
310323 if p == nil {
311324 tb.mu.Unlock()
@@ -319,15 +332,18 @@ func (d *Daemon) paint(sess *session, ac *attachedClient, reset bool) {
319332 if reset || pickerActive || paletteActive || promptActive {
320333 ac.lastCursor.valid = false
321334 }
322- frame, damage := composeClientFrameWithState(bars, tb, reset, &ac.bars)
335+ frame, damage := composeClientFrameWithLayout(bars, tb, reset, layoutSnap, &ac.bars)
323336 if copyActive {
324337 frame, damage = composeCopyClientFrame(copyMode, tb, bars)
325338 }
326339 if pickerActive {
327340 if previewTab == tb {
328- p.mu.Lock()
329- preview = pickerPreviewFromLockedPane(p)
330- p.mu.Unlock()
341+ if layoutSnap.ok && tb.tree != nil && tb.tree.Root != nil && tb.tree.Root.Kind != layout.Leaf {
342+ previewFrame, _ := composeTabFrameWithLayout(tb, layoutSnap.area, themeui.Theme{}, layoutSnap)
343+ preview = pickerPreviewFromFrame(previewFrame)
344+ } else {
345+ preview = pickerPreviewFromLockedTab(tb)
346+ }
331347 }
332348 frame, damage = composePickerClientFrame(pickerModel, preview, frame, styles)
333349 }
@@ -451,6 +467,10 @@ func composeClientFrame(sess *session, tb *tab, full bool, rightStatus string, c
451467 }
452468
453469 func composeClientFrameWithState(bars barState, tb *tab, full bool, caches ...*barCache) (renderer.Frame, []renderer.Damage) {
470+ return composeClientFrameWithLayout(bars, tb, full, solveTabLayoutLocked(tb), caches...)
471+}
472+
473+func composeClientFrameWithLayout(bars barState, tb *tab, full bool, layoutSnap tabLayoutSnapshot, caches ...*barCache) (renderer.Frame, []renderer.Damage) {
454474 var cache *barCache
455475 if len(caches) > 0 {
456476 cache = caches[0]
@@ -473,7 +493,7 @@ func composeClientFrameWithState(bars barState, tb *tab, full bool, caches ...*b
473493 frame := renderer.NewFrame(width, screenRows+2)
474494 topBar := frame.Row(0)
475495 drawTopBarSnapshot(topBar, bars.status, bars.attentionFrame, styles)
476- contentFrame, contentDamage := composeTabFrame(tb, domain.Rect{Width: width, Height: screenRows}, bars.theme)
496+ contentFrame, contentDamage := composeTabFrameWithLayout(tb, domain.Rect{Width: width, Height: screenRows}, bars.theme, layoutSnap)
477497 for y := range screenRows {
478498 copy(frame.Row(y+1), contentFrame.Row(y))
479499 }
@@ -499,9 +519,16 @@ func composeClientFrameWithState(bars barState, tb *tab, full bool, caches ...*b
499519 }
500520
501521 func composeTabFrame(tb *tab, area domain.Rect, theme themeui.Theme) (renderer.Frame, []renderer.Damage) {
522+ return composeTabFrameWithLayout(tb, area, theme, tabLayoutSnapshot{})
523+}
524+
525+func composeTabFrameWithLayout(tb *tab, area domain.Rect, theme themeui.Theme, layoutSnap tabLayoutSnapshot) (renderer.Frame, []renderer.Damage) {
502526 frame := renderer.NewFrame(area.Width, area.Height)
503527 root := tb.tree.Root
504- placements, ok := layout.Solve(root, area)
528+ placements, ok := layoutSnap.placements, layoutSnap.ok && layoutSnap.root == root && layoutSnap.area == area
529+ if !ok {
530+ placements, ok = layout.Solve(root, area)
531+ }
505532 var fallback *pane
506533 if !ok {
507534 fallback = tb.focusedPane()
@@ -247,6 +247,19 @@ func (d *Daemon) render(sess *session, tb *tab, p *pane) {
247 // paint draws the composed client frame (active tab plus status bar) and247 // paint draws the composed client frame (active tab plus status bar) and
248 // sends the resulting bytes. The renderer shadow is reset on explicit invalidations248 // sends the resulting bytes. The renderer shadow is reset on explicit invalidations
249 // such as switch/create/close/rename/resize so the repaint is complete.249 // such as switch/create/close/rename/resize so the repaint is complete.
250+func titleBarPaneIDs(placements []layout.Placement, ok bool) []layout.PaneID {
251+ if !ok {
252+ return nil
253+ }
254+ ids := make([]layout.PaneID, 0, len(placements))
255+ for _, pl := range placements {
256+ if pl.TitleBar.Height > 0 {
257+ ids = append(ids, pl.ID)
258+ }
259+ }
260+ return ids
261+}
262+
250 func (d *Daemon) paint(sess *session, ac *attachedClient, reset bool) {263 func (d *Daemon) paint(sess *session, ac *attachedClient, reset bool) {
251 tb := sess.activeTab()264 tb := sess.activeTab()
252 if tb == nil {265 if tb == nil {
@@ -271,7 +284,10 @@ func (d *Daemon) paint(sess *session, ac *attachedClient, reset bool) {
271 pickerModel := ac.picker.Clone()284 pickerModel := ac.picker.Clone()
272 previewTab := ac.pickerPreview285 previewTab := ac.pickerPreview
273 ac.pickerMu.Unlock()286 ac.pickerMu.Unlock()
274- preview := snapshotPickerPreview(previewTab)287+ preview := snapshotPickerPreview(nil)
288+ if previewTab != tb {
289+ preview = snapshotPickerPreview(previewTab)
290+ }
275 ac.paletteMu.Lock()291 ac.paletteMu.Lock()
276 paletteModel := ac.palette292 paletteModel := ac.palette
277 paletteActive := paletteModel != nil293 paletteActive := paletteModel != nil
@@ -292,20 +308,17 @@ func (d *Daemon) paint(sess *session, ac *attachedClient, reset bool) {
292 308
293 styles := newThemeStyles(ac.getTheme())309 styles := newThemeStyles(ac.getTheme())
294 tb.mu.Lock()310 tb.mu.Lock()
295- var titleIDs []layout.PaneID311+ layoutSnap := solveTabLayoutLocked(tb)
296- if placements, ok := solvedPlacementsLocked(tb); ok {312+ titleIDs := titleBarPaneIDs(layoutSnap.placements, layoutSnap.ok)
297- for _, pl := range placements {
298- if pl.TitleBar.Height > 0 {
299- titleIDs = append(titleIDs, pl.ID)
300- }
301- }
302- }
303 tb.mu.Unlock()313 tb.mu.Unlock()
304 for _, id := range titleIDs {314 for _, id := range titleIDs {
305 d.refreshPaneTitle(sess, id)315 d.refreshPaneTitle(sess, id)
306 }316 }
307 317
308 tb.mu.Lock()318 tb.mu.Lock()
319+ if !layoutSnap.matchesLocked(tb) {
320+ layoutSnap = solveTabLayoutLocked(tb)
321+ }
309 p := tb.focusedPane()322 p := tb.focusedPane()
310 if p == nil {323 if p == nil {
311 tb.mu.Unlock()324 tb.mu.Unlock()
@@ -319,15 +332,18 @@ func (d *Daemon) paint(sess *session, ac *attachedClient, reset bool) {
319 if reset || pickerActive || paletteActive || promptActive {332 if reset || pickerActive || paletteActive || promptActive {
320 ac.lastCursor.valid = false333 ac.lastCursor.valid = false
321 }334 }
322- frame, damage := composeClientFrameWithState(bars, tb, reset, &ac.bars)335+ frame, damage := composeClientFrameWithLayout(bars, tb, reset, layoutSnap, &ac.bars)
323 if copyActive {336 if copyActive {
324 frame, damage = composeCopyClientFrame(copyMode, tb, bars)337 frame, damage = composeCopyClientFrame(copyMode, tb, bars)
325 }338 }
326 if pickerActive {339 if pickerActive {
327 if previewTab == tb {340 if previewTab == tb {
328- p.mu.Lock()341+ if layoutSnap.ok && tb.tree != nil && tb.tree.Root != nil && tb.tree.Root.Kind != layout.Leaf {
329- preview = pickerPreviewFromLockedPane(p)342+ previewFrame, _ := composeTabFrameWithLayout(tb, layoutSnap.area, themeui.Theme{}, layoutSnap)
330- p.mu.Unlock()343+ preview = pickerPreviewFromFrame(previewFrame)
344+ } else {
345+ preview = pickerPreviewFromLockedTab(tb)
346+ }
331 }347 }
332 frame, damage = composePickerClientFrame(pickerModel, preview, frame, styles)348 frame, damage = composePickerClientFrame(pickerModel, preview, frame, styles)
333 }349 }
@@ -451,6 +467,10 @@ func composeClientFrame(sess *session, tb *tab, full bool, rightStatus string, c
451 }467 }
452 468
453 func composeClientFrameWithState(bars barState, tb *tab, full bool, caches ...*barCache) (renderer.Frame, []renderer.Damage) {469 func composeClientFrameWithState(bars barState, tb *tab, full bool, caches ...*barCache) (renderer.Frame, []renderer.Damage) {
470+ return composeClientFrameWithLayout(bars, tb, full, solveTabLayoutLocked(tb), caches...)
471+}
472+
473+func composeClientFrameWithLayout(bars barState, tb *tab, full bool, layoutSnap tabLayoutSnapshot, caches ...*barCache) (renderer.Frame, []renderer.Damage) {
454 var cache *barCache474 var cache *barCache
455 if len(caches) > 0 {475 if len(caches) > 0 {
456 cache = caches[0]476 cache = caches[0]
@@ -473,7 +493,7 @@ func composeClientFrameWithState(bars barState, tb *tab, full bool, caches ...*b
473 frame := renderer.NewFrame(width, screenRows+2)493 frame := renderer.NewFrame(width, screenRows+2)
474 topBar := frame.Row(0)494 topBar := frame.Row(0)
475 drawTopBarSnapshot(topBar, bars.status, bars.attentionFrame, styles)495 drawTopBarSnapshot(topBar, bars.status, bars.attentionFrame, styles)
476- contentFrame, contentDamage := composeTabFrame(tb, domain.Rect{Width: width, Height: screenRows}, bars.theme)496+ contentFrame, contentDamage := composeTabFrameWithLayout(tb, domain.Rect{Width: width, Height: screenRows}, bars.theme, layoutSnap)
477 for y := range screenRows {497 for y := range screenRows {
478 copy(frame.Row(y+1), contentFrame.Row(y))498 copy(frame.Row(y+1), contentFrame.Row(y))
479 }499 }
@@ -499,9 +519,16 @@ func composeClientFrameWithState(bars barState, tb *tab, full bool, caches ...*b
499 }519 }
500 520
501 func composeTabFrame(tb *tab, area domain.Rect, theme themeui.Theme) (renderer.Frame, []renderer.Damage) {521 func composeTabFrame(tb *tab, area domain.Rect, theme themeui.Theme) (renderer.Frame, []renderer.Damage) {
522+ return composeTabFrameWithLayout(tb, area, theme, tabLayoutSnapshot{})
523+}
524+
525+func composeTabFrameWithLayout(tb *tab, area domain.Rect, theme themeui.Theme, layoutSnap tabLayoutSnapshot) (renderer.Frame, []renderer.Damage) {
502 frame := renderer.NewFrame(area.Width, area.Height)526 frame := renderer.NewFrame(area.Width, area.Height)
503 root := tb.tree.Root527 root := tb.tree.Root
504- placements, ok := layout.Solve(root, area)528+ placements, ok := layoutSnap.placements, layoutSnap.ok && layoutSnap.root == root && layoutSnap.area == area
529+ if !ok {
530+ placements, ok = layout.Solve(root, area)
531+ }
505 var fallback *pane532 var fallback *pane
506 if !ok {533 if !ok {
507 fallback = tb.focusedPane()534 fallback = tb.focusedPane()

Keyboard shortcuts

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

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

Branches 12

Tags 11

↑↓ moveenter openesc close