detached 9065670

feat(commands): expose floating pane toggle

commit
9065670a743c1baa4f20ab163e564e268c1126b0
author
brice · · 2026-07-09 23:22 UTC
committer
brice · · 2026-07-10 06:37 UTC
parent
773d08a
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
9 files · +91 -19
M internal/usecase/command/command.go +1 -0
@@ -11,6 +11,7 @@ type Context interface {
1111 SplitDown() error
1212 StackPane() error
1313 ToggleStack() error
14+ ToggleFloatingPane() error
1415 ClosePane() error
1516 FocusPaneLeft() error
1617 FocusPaneRight() error
@@ -11,6 +11,7 @@ type Context interface {
11 SplitDown() error11 SplitDown() error
12 StackPane() error12 StackPane() error
13 ToggleStack() error13 ToggleStack() error
14+ ToggleFloatingPane() error
14 ClosePane() error15 ClosePane() error
15 FocusPaneLeft() error16 FocusPaneLeft() error
16 FocusPaneRight() error17 FocusPaneRight() error
M internal/usecase/command/context_mock_test.go +44 -0
@@ -1003,6 +1003,50 @@ func (_c *MockContext_StackPane_Call) RunAndReturn(run func() error) *MockContex
10031003 return _c
10041004 }
10051005
1006+// ToggleFloatingPane provides a mock function for the type MockContext
1007+func (_mock *MockContext) ToggleFloatingPane() error {
1008+ ret := _mock.Called()
1009+
1010+ if len(ret) == 0 {
1011+ panic("no return value specified for ToggleFloatingPane")
1012+ }
1013+
1014+ var r0 error
1015+ if returnFunc, ok := ret.Get(0).(func() error); ok {
1016+ r0 = returnFunc()
1017+ } else {
1018+ r0 = ret.Error(0)
1019+ }
1020+ return r0
1021+}
1022+
1023+// MockContext_ToggleFloatingPane_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ToggleFloatingPane'
1024+type MockContext_ToggleFloatingPane_Call struct {
1025+ *mock.Call
1026+}
1027+
1028+// ToggleFloatingPane is a helper method to define mock.On call
1029+func (_e *MockContext_Expecter) ToggleFloatingPane() *MockContext_ToggleFloatingPane_Call {
1030+ return &MockContext_ToggleFloatingPane_Call{Call: _e.mock.On("ToggleFloatingPane")}
1031+}
1032+
1033+func (_c *MockContext_ToggleFloatingPane_Call) Run(run func()) *MockContext_ToggleFloatingPane_Call {
1034+ _c.Call.Run(func(args mock.Arguments) {
1035+ run()
1036+ })
1037+ return _c
1038+}
1039+
1040+func (_c *MockContext_ToggleFloatingPane_Call) Return(err error) *MockContext_ToggleFloatingPane_Call {
1041+ _c.Call.Return(err)
1042+ return _c
1043+}
1044+
1045+func (_c *MockContext_ToggleFloatingPane_Call) RunAndReturn(run func() error) *MockContext_ToggleFloatingPane_Call {
1046+ _c.Call.Return(run)
1047+ return _c
1048+}
1049+
10061050 // ToggleStack provides a mock function for the type MockContext
10071051 func (_mock *MockContext) ToggleStack() error {
10081052 ret := _mock.Called()
@@ -1003,6 +1003,50 @@ func (_c *MockContext_StackPane_Call) RunAndReturn(run func() error) *MockContex
1003 return _c1003 return _c
1004 }1004 }
1005 1005
1006+// ToggleFloatingPane provides a mock function for the type MockContext
1007+func (_mock *MockContext) ToggleFloatingPane() error {
1008+ ret := _mock.Called()
1009+
1010+ if len(ret) == 0 {
1011+ panic("no return value specified for ToggleFloatingPane")
1012+ }
1013+
1014+ var r0 error
1015+ if returnFunc, ok := ret.Get(0).(func() error); ok {
1016+ r0 = returnFunc()
1017+ } else {
1018+ r0 = ret.Error(0)
1019+ }
1020+ return r0
1021+}
1022+
1023+// MockContext_ToggleFloatingPane_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ToggleFloatingPane'
1024+type MockContext_ToggleFloatingPane_Call struct {
1025+ *mock.Call
1026+}
1027+
1028+// ToggleFloatingPane is a helper method to define mock.On call
1029+func (_e *MockContext_Expecter) ToggleFloatingPane() *MockContext_ToggleFloatingPane_Call {
1030+ return &MockContext_ToggleFloatingPane_Call{Call: _e.mock.On("ToggleFloatingPane")}
1031+}
1032+
1033+func (_c *MockContext_ToggleFloatingPane_Call) Run(run func()) *MockContext_ToggleFloatingPane_Call {
1034+ _c.Call.Run(func(args mock.Arguments) {
1035+ run()
1036+ })
1037+ return _c
1038+}
1039+
1040+func (_c *MockContext_ToggleFloatingPane_Call) Return(err error) *MockContext_ToggleFloatingPane_Call {
1041+ _c.Call.Return(err)
1042+ return _c
1043+}
1044+
1045+func (_c *MockContext_ToggleFloatingPane_Call) RunAndReturn(run func() error) *MockContext_ToggleFloatingPane_Call {
1046+ _c.Call.Return(run)
1047+ return _c
1048+}
1049+
1006 // ToggleStack provides a mock function for the type MockContext1050 // ToggleStack provides a mock function for the type MockContext
1007 func (_mock *MockContext) ToggleStack() error {1051 func (_mock *MockContext) ToggleStack() error {
1008 ret := _mock.Called()1052 ret := _mock.Called()
M internal/usecase/command/registry.go +1 -0
@@ -14,6 +14,7 @@ func Registry() []Command {
1414 {Slug: "split-down", Code: "SPD", Name: "Split down", Desc: "Split the focused pane downward", Run: func(ctx Context) error { return ctx.SplitDown() }},
1515 {Slug: "stack-pane", Code: "STP", Name: "Stack pane", Desc: "Create a new pane in a stack", Run: func(ctx Context) error { return ctx.StackPane() }},
1616 {Slug: "toggle-stack", Code: "TST", Name: "Toggle stack", Desc: "Toggle the focused pane stack", Run: func(ctx Context) error { return ctx.ToggleStack() }},
17+ {Slug: "toggle-floating-pane", Code: "FLT", Name: "Toggle floating pane", Desc: "Toggle the floating pane", Run: func(ctx Context) error { return ctx.ToggleFloatingPane() }},
1718 {Slug: "close-pane", Code: "CLP", Name: "Close pane", Desc: "Close the focused pane", Run: func(ctx Context) error { return ctx.ClosePane() }},
1819 {Slug: "focus-pane-left", Code: "FPL", Name: "Focus pane left", Desc: "Focus the pane to the left", Run: func(ctx Context) error { return ctx.FocusPaneLeft() }},
1920 {Slug: "focus-pane-right", Code: "FPR", Name: "Focus pane right", Desc: "Focus the pane to the right", Run: func(ctx Context) error { return ctx.FocusPaneRight() }},
@@ -14,6 +14,7 @@ func Registry() []Command {
14 {Slug: "split-down", Code: "SPD", Name: "Split down", Desc: "Split the focused pane downward", Run: func(ctx Context) error { return ctx.SplitDown() }},14 {Slug: "split-down", Code: "SPD", Name: "Split down", Desc: "Split the focused pane downward", Run: func(ctx Context) error { return ctx.SplitDown() }},
15 {Slug: "stack-pane", Code: "STP", Name: "Stack pane", Desc: "Create a new pane in a stack", Run: func(ctx Context) error { return ctx.StackPane() }},15 {Slug: "stack-pane", Code: "STP", Name: "Stack pane", Desc: "Create a new pane in a stack", Run: func(ctx Context) error { return ctx.StackPane() }},
16 {Slug: "toggle-stack", Code: "TST", Name: "Toggle stack", Desc: "Toggle the focused pane stack", Run: func(ctx Context) error { return ctx.ToggleStack() }},16 {Slug: "toggle-stack", Code: "TST", Name: "Toggle stack", Desc: "Toggle the focused pane stack", Run: func(ctx Context) error { return ctx.ToggleStack() }},
17+ {Slug: "toggle-floating-pane", Code: "FLT", Name: "Toggle floating pane", Desc: "Toggle the floating pane", Run: func(ctx Context) error { return ctx.ToggleFloatingPane() }},
17 {Slug: "close-pane", Code: "CLP", Name: "Close pane", Desc: "Close the focused pane", Run: func(ctx Context) error { return ctx.ClosePane() }},18 {Slug: "close-pane", Code: "CLP", Name: "Close pane", Desc: "Close the focused pane", Run: func(ctx Context) error { return ctx.ClosePane() }},
18 {Slug: "focus-pane-left", Code: "FPL", Name: "Focus pane left", Desc: "Focus the pane to the left", Run: func(ctx Context) error { return ctx.FocusPaneLeft() }},19 {Slug: "focus-pane-left", Code: "FPL", Name: "Focus pane left", Desc: "Focus the pane to the left", Run: func(ctx Context) error { return ctx.FocusPaneLeft() }},
19 {Slug: "focus-pane-right", Code: "FPR", Name: "Focus pane right", Desc: "Focus the pane to the right", Run: func(ctx Context) error { return ctx.FocusPaneRight() }},20 {Slug: "focus-pane-right", Code: "FPR", Name: "Focus pane right", Desc: "Focus the pane to the right", Run: func(ctx Context) error { return ctx.FocusPaneRight() }},
M internal/usecase/command/registry_test.go +12 -1
@@ -7,7 +7,7 @@ import (
77
88 func TestRegistryCodesAndSlugsAreUniqueInOrder(t *testing.T) {
99 commands := Registry()
10- wantCodes := []string{"CNT", "CNS", "CLT", "SPR", "SPL", "SPU", "SPD", "STP", "TST", "CLP", "FPL", "FPR", "FPU", "FPD", "NXT", "PVT", "BSK", "FSK", "SSP", "VIS", "RNS", "RNT", "DET"}
10+ wantCodes := []string{"CNT", "CNS", "CLT", "SPR", "SPL", "SPU", "SPD", "STP", "TST", "FLT", "CLP", "FPL", "FPR", "FPU", "FPD", "NXT", "PVT", "BSK", "FSK", "SSP", "VIS", "RNS", "RNT", "DET"}
1111
1212 if len(commands) != len(wantCodes) {
1313 t.Fatalf("Registry() returned %d commands, want %d", len(commands), len(wantCodes))
@@ -37,6 +37,16 @@ func TestRegistryCodesAndSlugsAreUniqueInOrder(t *testing.T) {
3737 }
3838 }
3939
40+func TestRegistryIncludesFloatingPaneToggle(t *testing.T) {
41+ cmd, ok := ByCode("FLT")
42+ if !ok {
43+ t.Fatal("ByCode(\"FLT\") ok = false, want true")
44+ }
45+ if cmd.Slug != "toggle-floating-pane" || cmd.Code != "FLT" || cmd.Name != "Toggle floating pane" {
46+ t.Fatalf("floating command = %#v, want toggle-floating-pane/FLT/Toggle floating pane", cmd)
47+ }
48+}
49+
4050 func TestByCodeIsCaseInsensitive(t *testing.T) {
4151 for _, code := range []string{"CNT", "cnt", "CnT"} {
4252 cmd, ok := ByCode(code)
@@ -67,6 +77,7 @@ func TestCommandRunCallsMatchingContextMethod(t *testing.T) {
6777 {code: "SPD", expect: func(ctx *MockContext) { ctx.EXPECT().SplitDown().Return(nil).Once() }},
6878 {code: "STP", expect: func(ctx *MockContext) { ctx.EXPECT().StackPane().Return(nil).Once() }},
6979 {code: "TST", expect: func(ctx *MockContext) { ctx.EXPECT().ToggleStack().Return(nil).Once() }},
80+ {code: "FLT", expect: func(ctx *MockContext) { ctx.EXPECT().ToggleFloatingPane().Return(nil).Once() }},
7081 {code: "CLP", expect: func(ctx *MockContext) { ctx.EXPECT().ClosePane().Return(nil).Once() }},
7182 {code: "FPL", expect: func(ctx *MockContext) { ctx.EXPECT().FocusPaneLeft().Return(nil).Once() }},
7283 {code: "FPR", expect: func(ctx *MockContext) { ctx.EXPECT().FocusPaneRight().Return(nil).Once() }},
@@ -7,7 +7,7 @@ import (
7 7
8 func TestRegistryCodesAndSlugsAreUniqueInOrder(t *testing.T) {8 func TestRegistryCodesAndSlugsAreUniqueInOrder(t *testing.T) {
9 commands := Registry()9 commands := Registry()
10- wantCodes := []string{"CNT", "CNS", "CLT", "SPR", "SPL", "SPU", "SPD", "STP", "TST", "CLP", "FPL", "FPR", "FPU", "FPD", "NXT", "PVT", "BSK", "FSK", "SSP", "VIS", "RNS", "RNT", "DET"}10+ wantCodes := []string{"CNT", "CNS", "CLT", "SPR", "SPL", "SPU", "SPD", "STP", "TST", "FLT", "CLP", "FPL", "FPR", "FPU", "FPD", "NXT", "PVT", "BSK", "FSK", "SSP", "VIS", "RNS", "RNT", "DET"}
11 11
12 if len(commands) != len(wantCodes) {12 if len(commands) != len(wantCodes) {
13 t.Fatalf("Registry() returned %d commands, want %d", len(commands), len(wantCodes))13 t.Fatalf("Registry() returned %d commands, want %d", len(commands), len(wantCodes))
@@ -37,6 +37,16 @@ func TestRegistryCodesAndSlugsAreUniqueInOrder(t *testing.T) {
37 }37 }
38 }38 }
39 39
40+func TestRegistryIncludesFloatingPaneToggle(t *testing.T) {
41+ cmd, ok := ByCode("FLT")
42+ if !ok {
43+ t.Fatal("ByCode(\"FLT\") ok = false, want true")
44+ }
45+ if cmd.Slug != "toggle-floating-pane" || cmd.Code != "FLT" || cmd.Name != "Toggle floating pane" {
46+ t.Fatalf("floating command = %#v, want toggle-floating-pane/FLT/Toggle floating pane", cmd)
47+ }
48+}
49+
40 func TestByCodeIsCaseInsensitive(t *testing.T) {50 func TestByCodeIsCaseInsensitive(t *testing.T) {
41 for _, code := range []string{"CNT", "cnt", "CnT"} {51 for _, code := range []string{"CNT", "cnt", "CnT"} {
42 cmd, ok := ByCode(code)52 cmd, ok := ByCode(code)
@@ -67,6 +77,7 @@ func TestCommandRunCallsMatchingContextMethod(t *testing.T) {
67 {code: "SPD", expect: func(ctx *MockContext) { ctx.EXPECT().SplitDown().Return(nil).Once() }},77 {code: "SPD", expect: func(ctx *MockContext) { ctx.EXPECT().SplitDown().Return(nil).Once() }},
68 {code: "STP", expect: func(ctx *MockContext) { ctx.EXPECT().StackPane().Return(nil).Once() }},78 {code: "STP", expect: func(ctx *MockContext) { ctx.EXPECT().StackPane().Return(nil).Once() }},
69 {code: "TST", expect: func(ctx *MockContext) { ctx.EXPECT().ToggleStack().Return(nil).Once() }},79 {code: "TST", expect: func(ctx *MockContext) { ctx.EXPECT().ToggleStack().Return(nil).Once() }},
80+ {code: "FLT", expect: func(ctx *MockContext) { ctx.EXPECT().ToggleFloatingPane().Return(nil).Once() }},
70 {code: "CLP", expect: func(ctx *MockContext) { ctx.EXPECT().ClosePane().Return(nil).Once() }},81 {code: "CLP", expect: func(ctx *MockContext) { ctx.EXPECT().ClosePane().Return(nil).Once() }},
71 {code: "FPL", expect: func(ctx *MockContext) { ctx.EXPECT().FocusPaneLeft().Return(nil).Once() }},82 {code: "FPL", expect: func(ctx *MockContext) { ctx.EXPECT().FocusPaneLeft().Return(nil).Once() }},
72 {code: "FPR", expect: func(ctx *MockContext) { ctx.EXPECT().FocusPaneRight().Return(nil).Once() }},83 {code: "FPR", expect: func(ctx *MockContext) { ctx.EXPECT().FocusPaneRight().Return(nil).Once() }},
M internal/usecase/keys/bindings.go +1 -0
@@ -17,6 +17,7 @@ func DefaultBindings() *Bindings {
1717 altRunes := map[rune]Action{
1818 ' ': ActionOpenPalette,
1919 'a': ActionJumpAttention,
20+ 'f': ActionToggleFloatingPane,
2021 'h': ActionFocusPaneLeft,
2122 'j': ActionFocusPaneDown,
2223 'k': ActionFocusPaneUp,
@@ -17,6 +17,7 @@ func DefaultBindings() *Bindings {
17 altRunes := map[rune]Action{17 altRunes := map[rune]Action{
18 ' ': ActionOpenPalette,18 ' ': ActionOpenPalette,
19 'a': ActionJumpAttention,19 'a': ActionJumpAttention,
20+ 'f': ActionToggleFloatingPane,
20 'h': ActionFocusPaneLeft,21 'h': ActionFocusPaneLeft,
21 'j': ActionFocusPaneDown,22 'j': ActionFocusPaneDown,
22 'k': ActionFocusPaneUp,23 'k': ActionFocusPaneUp,
M internal/usecase/keys/keyspec.go +16 -15
@@ -49,21 +49,22 @@ func ParseKeySpec(spec string) (KeySpec, error) {
4949 }
5050
5151 var actionNames = map[Action]string{
52- ActionOpenPalette: "open-palette",
53- ActionJumpAttention: "jump-attention",
54- ActionFocusPaneLeft: "focus-pane-left",
55- ActionFocusPaneRight: "focus-pane-right",
56- ActionFocusPaneUp: "focus-pane-up",
57- ActionFocusPaneDown: "focus-pane-down",
58- ActionSwitchTab1: "switch-tab-1",
59- ActionSwitchTab2: "switch-tab-2",
60- ActionSwitchTab3: "switch-tab-3",
61- ActionSwitchTab4: "switch-tab-4",
62- ActionSwitchTab5: "switch-tab-5",
63- ActionSwitchTab6: "switch-tab-6",
64- ActionSwitchTab7: "switch-tab-7",
65- ActionSwitchTab8: "switch-tab-8",
66- ActionSwitchTab9: "switch-tab-9",
52+ ActionOpenPalette: "open-palette",
53+ ActionToggleFloatingPane: "toggle-floating-pane",
54+ ActionJumpAttention: "jump-attention",
55+ ActionFocusPaneLeft: "focus-pane-left",
56+ ActionFocusPaneRight: "focus-pane-right",
57+ ActionFocusPaneUp: "focus-pane-up",
58+ ActionFocusPaneDown: "focus-pane-down",
59+ ActionSwitchTab1: "switch-tab-1",
60+ ActionSwitchTab2: "switch-tab-2",
61+ ActionSwitchTab3: "switch-tab-3",
62+ ActionSwitchTab4: "switch-tab-4",
63+ ActionSwitchTab5: "switch-tab-5",
64+ ActionSwitchTab6: "switch-tab-6",
65+ ActionSwitchTab7: "switch-tab-7",
66+ ActionSwitchTab8: "switch-tab-8",
67+ ActionSwitchTab9: "switch-tab-9",
6768 }
6869
6970 var actionsByName = func() map[string]Action {
@@ -49,21 +49,22 @@ func ParseKeySpec(spec string) (KeySpec, error) {
49 }49 }
50 50
51 var actionNames = map[Action]string{51 var actionNames = map[Action]string{
52- ActionOpenPalette: "open-palette",52+ ActionOpenPalette: "open-palette",
53- ActionJumpAttention: "jump-attention",53+ ActionToggleFloatingPane: "toggle-floating-pane",
54- ActionFocusPaneLeft: "focus-pane-left",54+ ActionJumpAttention: "jump-attention",
55- ActionFocusPaneRight: "focus-pane-right",55+ ActionFocusPaneLeft: "focus-pane-left",
56- ActionFocusPaneUp: "focus-pane-up",56+ ActionFocusPaneRight: "focus-pane-right",
57- ActionFocusPaneDown: "focus-pane-down",57+ ActionFocusPaneUp: "focus-pane-up",
58- ActionSwitchTab1: "switch-tab-1",58+ ActionFocusPaneDown: "focus-pane-down",
59- ActionSwitchTab2: "switch-tab-2",59+ ActionSwitchTab1: "switch-tab-1",
60- ActionSwitchTab3: "switch-tab-3",60+ ActionSwitchTab2: "switch-tab-2",
61- ActionSwitchTab4: "switch-tab-4",61+ ActionSwitchTab3: "switch-tab-3",
62- ActionSwitchTab5: "switch-tab-5",62+ ActionSwitchTab4: "switch-tab-4",
63- ActionSwitchTab6: "switch-tab-6",63+ ActionSwitchTab5: "switch-tab-5",
64- ActionSwitchTab7: "switch-tab-7",64+ ActionSwitchTab6: "switch-tab-6",
65- ActionSwitchTab8: "switch-tab-8",65+ ActionSwitchTab7: "switch-tab-7",
66- ActionSwitchTab9: "switch-tab-9",66+ ActionSwitchTab8: "switch-tab-8",
67+ ActionSwitchTab9: "switch-tab-9",
67 }68 }
68 69
69 var actionsByName = func() map[string]Action {70 var actionsByName = func() map[string]Action {
M internal/usecase/keys/keyspec_test.go +7 -3
@@ -64,6 +64,7 @@ func TestActionNamesAreCanonical(t *testing.T) {
6464 name string
6565 }{
6666 {ActionOpenPalette, "open-palette"},
67+ {ActionToggleFloatingPane, "toggle-floating-pane"},
6768 {ActionJumpAttention, "jump-attention"},
6869 {ActionFocusPaneLeft, "focus-pane-left"},
6970 {ActionFocusPaneRight, "focus-pane-right"},
@@ -96,14 +97,17 @@ func TestActionNamesAreCanonical(t *testing.T) {
9697
9798 func TestBuildBindingsAppliesOverrides(t *testing.T) {
9899 bindings, warnings := BuildBindings(map[string]string{
99- "open-palette": "alt+o",
100- "focus-pane-left": "alt+left",
101- "switch-tab-2": "alt+2",
100+ "open-palette": "alt+o",
101+ "toggle-floating-pane": "alt+g",
102+ "focus-pane-left": "alt+left",
103+ "switch-tab-2": "alt+2",
102104 })
103105 require.Empty(t, warnings)
104106
105107 assertAltRuneAction(t, bindings, 'o', ActionOpenPalette)
106108 assertAltRuneUnbound(t, bindings, ' ')
109+ assertAltRuneAction(t, bindings, 'g', ActionToggleFloatingPane)
110+ assertAltRuneUnbound(t, bindings, 'f')
107111 assertAltArrowAction(t, bindings, 'D', ActionFocusPaneLeft)
108112 assertAltRuneUnbound(t, bindings, 'h')
109113 assertAltRuneAction(t, bindings, '2', ActionSwitchTab2)
@@ -64,6 +64,7 @@ func TestActionNamesAreCanonical(t *testing.T) {
64 name string64 name string
65 }{65 }{
66 {ActionOpenPalette, "open-palette"},66 {ActionOpenPalette, "open-palette"},
67+ {ActionToggleFloatingPane, "toggle-floating-pane"},
67 {ActionJumpAttention, "jump-attention"},68 {ActionJumpAttention, "jump-attention"},
68 {ActionFocusPaneLeft, "focus-pane-left"},69 {ActionFocusPaneLeft, "focus-pane-left"},
69 {ActionFocusPaneRight, "focus-pane-right"},70 {ActionFocusPaneRight, "focus-pane-right"},
@@ -96,14 +97,17 @@ func TestActionNamesAreCanonical(t *testing.T) {
96 97
97 func TestBuildBindingsAppliesOverrides(t *testing.T) {98 func TestBuildBindingsAppliesOverrides(t *testing.T) {
98 bindings, warnings := BuildBindings(map[string]string{99 bindings, warnings := BuildBindings(map[string]string{
99- "open-palette": "alt+o",100+ "open-palette": "alt+o",
100- "focus-pane-left": "alt+left",101+ "toggle-floating-pane": "alt+g",
101- "switch-tab-2": "alt+2",102+ "focus-pane-left": "alt+left",
103+ "switch-tab-2": "alt+2",
102 })104 })
103 require.Empty(t, warnings)105 require.Empty(t, warnings)
104 106
105 assertAltRuneAction(t, bindings, 'o', ActionOpenPalette)107 assertAltRuneAction(t, bindings, 'o', ActionOpenPalette)
106 assertAltRuneUnbound(t, bindings, ' ')108 assertAltRuneUnbound(t, bindings, ' ')
109+ assertAltRuneAction(t, bindings, 'g', ActionToggleFloatingPane)
110+ assertAltRuneUnbound(t, bindings, 'f')
107 assertAltArrowAction(t, bindings, 'D', ActionFocusPaneLeft)111 assertAltArrowAction(t, bindings, 'D', ActionFocusPaneLeft)
108 assertAltRuneUnbound(t, bindings, 'h')112 assertAltRuneUnbound(t, bindings, 'h')
109 assertAltRuneAction(t, bindings, '2', ActionSwitchTab2)113 assertAltRuneAction(t, bindings, '2', ActionSwitchTab2)
M internal/usecase/keys/router.go +1 -0
@@ -37,6 +37,7 @@ const (
3737 ActionFocusPaneRight
3838 ActionFocusPaneUp
3939 ActionFocusPaneDown
40+ ActionToggleFloatingPane
4041 )
4142
4243 // Handler receives router outputs. Forward is called only for bytes that should
@@ -37,6 +37,7 @@ const (
37 ActionFocusPaneRight37 ActionFocusPaneRight
38 ActionFocusPaneUp38 ActionFocusPaneUp
39 ActionFocusPaneDown39 ActionFocusPaneDown
40+ ActionToggleFloatingPane
40 )41 )
41 42
42 // Handler receives router outputs. Forward is called only for bytes that should43 // Handler receives router outputs. Forward is called only for bytes that should
M internal/usecase/keys/router_test.go +8 -0
@@ -53,6 +53,7 @@ func TestDefaultBindingsParityRoutesBuiltInBindings(t *testing.T) {
5353 want Action
5454 }{
5555 {name: "palette", in: []byte{ESC, ' '}, want: ActionOpenPalette},
56+ {name: "floating pane", in: []byte{ESC, 'f'}, want: ActionToggleFloatingPane},
5657 {name: "jump attention", in: []byte{ESC, 'a'}, want: ActionJumpAttention},
5758 {name: "focus left", in: []byte{ESC, 'h'}, want: ActionFocusPaneLeft},
5859 {name: "focus down", in: []byte{ESC, 'j'}, want: ActionFocusPaneDown},
@@ -237,6 +238,13 @@ func switchTabActions() []Action {
237238 }
238239 }
239240
241+func TestSwitchTabActionsRemainContiguous(t *testing.T) {
242+ for i, action := range switchTabActions() {
243+ require.Equal(t, ActionSwitchTab1+Action(i), action)
244+ }
245+ require.False(t, ActionToggleFloatingPane >= ActionSwitchTab1 && ActionToggleFloatingPane <= ActionSwitchTab9)
246+}
247+
240248 func TestRouterInterceptsAltAForJumpAttention(t *testing.T) {
241249 clk := &fakeClock{}
242250 h := &captureHandler{}
@@ -53,6 +53,7 @@ func TestDefaultBindingsParityRoutesBuiltInBindings(t *testing.T) {
53 want Action53 want Action
54 }{54 }{
55 {name: "palette", in: []byte{ESC, ' '}, want: ActionOpenPalette},55 {name: "palette", in: []byte{ESC, ' '}, want: ActionOpenPalette},
56+ {name: "floating pane", in: []byte{ESC, 'f'}, want: ActionToggleFloatingPane},
56 {name: "jump attention", in: []byte{ESC, 'a'}, want: ActionJumpAttention},57 {name: "jump attention", in: []byte{ESC, 'a'}, want: ActionJumpAttention},
57 {name: "focus left", in: []byte{ESC, 'h'}, want: ActionFocusPaneLeft},58 {name: "focus left", in: []byte{ESC, 'h'}, want: ActionFocusPaneLeft},
58 {name: "focus down", in: []byte{ESC, 'j'}, want: ActionFocusPaneDown},59 {name: "focus down", in: []byte{ESC, 'j'}, want: ActionFocusPaneDown},
@@ -237,6 +238,13 @@ func switchTabActions() []Action {
237 }238 }
238 }239 }
239 240
241+func TestSwitchTabActionsRemainContiguous(t *testing.T) {
242+ for i, action := range switchTabActions() {
243+ require.Equal(t, ActionSwitchTab1+Action(i), action)
244+ }
245+ require.False(t, ActionToggleFloatingPane >= ActionSwitchTab1 && ActionToggleFloatingPane <= ActionSwitchTab9)
246+}
247+
240 func TestRouterInterceptsAltAForJumpAttention(t *testing.T) {248 func TestRouterInterceptsAltAForJumpAttention(t *testing.T) {
241 clk := &fakeClock{}249 clk := &fakeClock{}
242 h := &captureHandler{}250 h := &captureHandler{}

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