1package command
2
3import "strings"
4
5// Registry returns all palette and control commands in stable display order.
6func Registry() []Command {
7 commands := []Command{
8 paletteControl("new-tab", "CNT", "New tab", "Create a new tab", TargetSession, func(ctx Context, _ []string) error { return ctx.CreateTab() }, func(ctx ControlContext) error { return ctx.CreateTab() }),
9 paletteOptionalOne("new-session", "CNS", "New session", "Create named session", TargetSession, func(ctx Context) error { return ctx.CreateSession() }, func(ctx Context, name string) error { return ctx.CreateSessionNamed(name) }, func(ctx ControlContext, name string) error { return ctx.CreateSessionNamed(name) }),
10 paletteOnly("create-ephemeral-session", "CES", "New ephemeral session", "Create ephemeral session", func(ctx Context, _ []string) error { return ctx.CreateEphemeralSession() }),
11 paletteControl("close-tab", "CLT", "Close tab", "Close the current tab", TargetTab, func(ctx Context, _ []string) error { return ctx.CloseTab() }, func(ctx ControlContext) error { return ctx.CloseTab() }),
12 paletteControl("split-right", "SPR", "Split right", "Split the focused pane to the right", TargetPane, func(ctx Context, _ []string) error { return ctx.SplitRight() }, func(ctx ControlContext) error { return ctx.SplitRight() }),
13 paletteControl("split-left", "SPL", "Split left", "Split the focused pane to the left", TargetPane, func(ctx Context, _ []string) error { return ctx.SplitLeft() }, func(ctx ControlContext) error { return ctx.SplitLeft() }),
14 paletteControl("split-up", "SPU", "Split up", "Split the focused pane upward", TargetPane, func(ctx Context, _ []string) error { return ctx.SplitUp() }, func(ctx ControlContext) error { return ctx.SplitUp() }),
15 paletteControl("split-down", "SPD", "Split down", "Split the focused pane downward", TargetPane, func(ctx Context, _ []string) error { return ctx.SplitDown() }, func(ctx ControlContext) error { return ctx.SplitDown() }),
16 paletteControl("consume-or-expel-pane-left", "MPL", "Consume or expel pane left", "Move the focused pane left", TargetPane, func(ctx Context, _ []string) error { return ctx.ConsumeOrExpelPaneLeft() }, func(ctx ControlContext) error { return ctx.ConsumeOrExpelPaneLeft() }),
17 paletteControl("consume-or-expel-pane-right", "MPR", "Consume or expel pane right", "Move the focused pane right", TargetPane, func(ctx Context, _ []string) error { return ctx.ConsumeOrExpelPaneRight() }, func(ctx ControlContext) error { return ctx.ConsumeOrExpelPaneRight() }),
18 paletteControl("stack-pane", "STP", "Stack pane", "Create a new pane in a stack", TargetPane, func(ctx Context, _ []string) error { return ctx.StackPane() }, func(ctx ControlContext) error { return ctx.StackPane() }),
19 paletteControl("toggle-stack", "TFS", "Toggle stack", "Toggle the focused pane stack", TargetPane, func(ctx Context, _ []string) error { return ctx.ToggleStack() }, func(ctx ControlContext) error { return ctx.ToggleStack() }),
20 paletteOnly("toggle-floating-pane", "TFP", "Toggle floating pane", "Toggle the floating pane", func(ctx Context, _ []string) error { return ctx.ToggleFloatingPane() }),
21 paletteControl("close-pane", "CFP", "Close pane", "Close the focused pane", TargetPane, func(ctx Context, _ []string) error { return ctx.ClosePane() }, func(ctx ControlContext) error { return ctx.ClosePane() }),
22 movePaneCommand(),
23 moveTabCommand(),
24 paletteControl("focus-pane-left", "FPL", "Focus pane left", "Focus the pane to the left", TargetPane, func(ctx Context, _ []string) error { return ctx.FocusPaneLeft() }, func(ctx ControlContext) error { return ctx.FocusPaneLeft() }),
25 paletteControl("focus-pane-right", "FPR", "Focus pane right", "Focus the pane to the right", TargetPane, func(ctx Context, _ []string) error { return ctx.FocusPaneRight() }, func(ctx ControlContext) error { return ctx.FocusPaneRight() }),
26 paletteControl("focus-pane-up", "FPU", "Focus pane up", "Focus the pane above", TargetPane, func(ctx Context, _ []string) error { return ctx.FocusPaneUp() }, func(ctx ControlContext) error { return ctx.FocusPaneUp() }),
27 paletteControl("focus-pane-down", "FPD", "Focus pane down", "Focus the pane below", TargetPane, func(ctx Context, _ []string) error { return ctx.FocusPaneDown() }, func(ctx ControlContext) error { return ctx.FocusPaneDown() }),
28 paletteOnly("resize-pane", "RSZ", "Resize pane", "Enter pane resize mode", func(ctx Context, _ []string) error { return ctx.EnterResizeMode() }),
29 paletteControl("grow-pane-width", "GPW", "Grow pane width", "Grow the pane by two columns", TargetPane, func(ctx Context, _ []string) error { return ctx.GrowPaneWidth() }, func(ctx ControlContext) error { return ctx.GrowPaneWidth() }),
30 paletteControl("shrink-pane-width", "SPW", "Shrink pane width", "Shrink the pane by two columns", TargetPane, func(ctx Context, _ []string) error { return ctx.ShrinkPaneWidth() }, func(ctx ControlContext) error { return ctx.ShrinkPaneWidth() }),
31 paletteControl("grow-pane-height", "GPH", "Grow pane height", "Grow the pane by one row", TargetPane, func(ctx Context, _ []string) error { return ctx.GrowPaneHeight() }, func(ctx ControlContext) error { return ctx.GrowPaneHeight() }),
32 paletteControl("shrink-pane-height", "SPH", "Shrink pane height", "Shrink the pane by one row", TargetPane, func(ctx Context, _ []string) error { return ctx.ShrinkPaneHeight() }, func(ctx ControlContext) error { return ctx.ShrinkPaneHeight() }),
33 paletteControl("equalize-panes", "EQP", "Equalize panes", "Equalize pane sizes", TargetTab, func(ctx Context, _ []string) error { return ctx.EqualizePanes() }, func(ctx ControlContext) error { return ctx.EqualizePanes() }),
34 paletteControl("next-tab", "NXT", "Next tab", "Switch to the next tab", TargetSession, func(ctx Context, _ []string) error { return ctx.NextTab() }, func(ctx ControlContext) error { return ctx.NextTab() }),
35 paletteControl("previous-tab", "PVT", "Previous tab", "Switch to the previous tab", TargetSession, func(ctx Context, _ []string) error { return ctx.PrevTab() }, func(ctx ControlContext) error { return ctx.PrevTab() }),
36 paletteOnly("back-session", "BCK", "Previous session", "Switch to previous session", func(ctx Context, _ []string) error { return ctx.BackSession() }),
37 {Slug: "jump-recent-session", Code: "JRS", Name: "Jump to recent session", Desc: "Jump to a recent session by rank", Usage: "jump-recent-session <rank>", PaletteVisible: true, Arguments: ArgumentsRequired, ContextHint: ContextHintRecentSessions, Run: func(ctx Context, args []string) error {
38 rank, err := ParsePositiveDecimal(args)
39 if err != nil {
40 return err
41 }
42 return ctx.JumpRecentSession(rank)
43 }},
44 paletteOnly("session-picker", "SSP", "Session picker", "Open the session picker", func(ctx Context, _ []string) error { return ctx.OpenSessionPicker() }),
45 paletteOnly("notifications", "NTC", "Notifications", "Show notification history", func(ctx Context, _ []string) error { return ctx.OpenNotifications() }),
46 paletteOnly("yank-last-notification", "YLN", "Yank last notification", "Copy last notice", func(ctx Context, _ []string) error { return ctx.YankLastNotification() }),
47 paletteOnly("visual-mode", "VIS", "Visual mode", "Enter visual mode", func(ctx Context, _ []string) error { return ctx.EnterVisualMode() }),
48 paletteOptionalOne("rename-session", "RNS", "Rename session", "Rename session", TargetSession, func(ctx Context) error { return ctx.RenameSession() }, func(ctx Context, name string) error { return ctx.RenameSessionTo(name) }, func(ctx ControlContext, name string) error { return ctx.RenameSessionTo(name) }),
49 paletteOptionalOne("rename-tab", "RNT", "Rename tab", "Rename the current tab", TargetTab, func(ctx Context) error { return ctx.RenameTab() }, func(ctx Context, name string) error { return ctx.RenameTabTo(name) }, func(ctx ControlContext, name string) error { return ctx.RenameTabTo(name) }),
50 paletteOnly("detach", "DET", "Detach", "Detach from the session", func(ctx Context, _ []string) error { return ctx.Detach() }),
51 toastCommand(),
52 listCommand("list-sessions", "List sessions", "List sessions with active markers", TargetNone, func(ctx ControlContext, json bool) (string, error) { return ctx.ListSessions(json) }),
53 listCommand("list-tabs", "List tabs", "List tabs in the target session", TargetSession, func(ctx ControlContext, json bool) (string, error) { return ctx.ListTabs(json) }),
54 listCommand("list-panes", "List panes", "List panes in the target tab", TargetTab, func(ctx ControlContext, json bool) (string, error) { return ctx.ListPanes(json) }),
55 remoteCatalogCommand(),
56 sessionRecoveryCommand(),
57 }
58 return commands
59}
60
61func paletteOnly(slug, code, name, desc string, run func(Context, []string) error) Command {
62 return Command{Slug: slug, Code: code, Name: name, Desc: desc, Usage: slug, PaletteVisible: true, Run: run}
63}
64
65func paletteControl(slug, code, name, desc string, target TargetKind, run func(Context, []string) error, control func(ControlContext) error) Command {
66 return Command{Slug: slug, Code: code, Name: name, Desc: desc, Usage: slug, PaletteVisible: true, Scriptable: true, Target: target, Run: run, Control: func(ctx ControlContext, args []string, _ ControlOptions) (ControlResult, error) {
67 if len(args) != 0 {
68 return ControlResult{}, ErrInvalidArguments
69 }
70 return ControlResult{}, control(ctx)
71 }}
72}
73
74func paletteOptionalOne(slug, code, name, desc string, target TargetKind, prompt func(Context) error, direct func(Context, string) error, control func(ControlContext, string) error) Command {
75 cmd := Command{Slug: slug, Code: code, Name: name, Desc: desc, Usage: slug + " [name]", PaletteVisible: true, Scriptable: true, Target: target, Arguments: ArgumentsOptional}
76 cmd.Preview = func(args []string, hasArgument bool) string {
77 if len(args) == 0 {
78 if hasArgument {
79 return "Type a name, or press Enter"
80 }
81 return desc
82 }
83 if len(args) > 1 {
84 return "Use one name"
85 }
86 switch slug {
87 case "new-session":
88 return "Create session “" + args[0] + "”"
89 case "rename-session":
90 return "Rename this session to “" + args[0] + "”"
91 default:
92 return "Rename this tab to “" + args[0] + "”"
93 }
94 }
95 cmd.Run = func(ctx Context, args []string) error {
96 switch len(args) {
97 case 0:
98 return prompt(ctx)
99 case 1:
100 return direct(ctx, args[0])
101 default:
102 return ErrInvalidArguments
103 }
104 }
105 cmd.Control = func(ctx ControlContext, args []string, _ ControlOptions) (ControlResult, error) {
106 if len(args) != 1 || args[0] == "" {
107 return ControlResult{}, ErrInvalidArguments
108 }
109 return ControlResult{}, control(ctx, args[0])
110 }
111 return cmd
112}
113
114func movePaneCommand() Command {
115 return Command{
116 Slug: "move-pane", Code: "MFP", Name: "Move pane to tab",
117 Desc: "Move focused pane to tab", Usage: "move-pane <destination-session> <destination-tab-id>",
118 PaletteVisible: true, Scriptable: true, Target: TargetPane, Scope: CommandScopeCrossSession,
119 Run: func(ctx Context, _ []string) error { return ctx.OpenMovePanePicker() },
120 Control: func(ctx ControlContext, args []string, _ ControlOptions) (ControlResult, error) {
121 if len(args) != 2 || args[0] == "" || args[1] == "" {
122 return ControlResult{}, ErrInvalidArguments
123 }
124 return ControlResult{}, ctx.MovePane(args[0], args[1])
125 },
126 }
127}
128
129func moveTabCommand() Command {
130 return Command{
131 Slug: "move-tab", Code: "MAT", Name: "Move tab to session",
132 Desc: "Move active tab to session", Usage: "move-tab <destination-session>",
133 PaletteVisible: true, Scriptable: true, Target: TargetTab, Scope: CommandScopeCrossSession,
134 Run: func(ctx Context, _ []string) error { return ctx.OpenMoveTabPicker() },
135 Control: func(ctx ControlContext, args []string, _ ControlOptions) (ControlResult, error) {
136 if len(args) != 1 || args[0] == "" {
137 return ControlResult{}, ErrInvalidArguments
138 }
139 return ControlResult{}, ctx.MoveTab(args[0])
140 },
141 }
142}
143
144func toastCommand() Command {
145 return Command{Slug: "toast", Name: "Toast", Desc: "Show a toast notification in the target session", Usage: "toast [-l info|warn|error] <message>", Scriptable: true, Target: TargetSession, Control: func(ctx ControlContext, args []string, _ ControlOptions) (ControlResult, error) {
146 severity := "info"
147 if len(args) >= 2 && args[0] == "-l" {
148 severity = args[1]
149 args = args[2:]
150 }
151 if len(args) != 1 || args[0] == "" {
152 return ControlResult{}, ErrInvalidArguments
153 }
154 return ControlResult{}, ctx.Toast(severity, args[0])
155 }}
156}
157
158func sessionRecoveryCommand() Command {
159 return Command{
160 Slug: "session-recovery", Name: "Session recovery", Desc: "Discard a broken durable session's persisted state",
161 Usage: "session-recovery discard",
162 Scriptable: true, Target: TargetNone,
163 Control: func(ctx ControlContext, args []string, _ ControlOptions) (ControlResult, error) {
164 if len(args) != 1 || args[0] != "discard" {
165 return ControlResult{}, ErrInvalidArguments
166 }
167 output, err := ctx.SessionRecovery("discard")
168 return ControlResult{Output: output}, err
169 },
170 }
171}
172
173func remoteCatalogCommand() Command {
174 return Command{
175 Slug: "remote-catalog", Name: "Remote catalog",
176 Desc: "Return the versioned session catalog for remote discovery",
177 Usage: "remote-catalog --json", Scriptable: true, Target: TargetNone,
178 Control: func(ctx ControlContext, args []string, opts ControlOptions) (ControlResult, error) {
179 if len(args) != 0 || !opts.JSON {
180 return ControlResult{}, ErrInvalidArguments
181 }
182 output, err := ctx.RemoteCatalog(true)
183 return ControlResult{Output: output}, err
184 },
185 }
186}
187
188func listCommand(slug, name, desc string, target TargetKind, list func(ControlContext, bool) (string, error)) Command {
189 return Command{Slug: slug, Name: name, Desc: desc, Usage: slug + " [--json]", Scriptable: true, Target: target, Control: func(ctx ControlContext, args []string, opts ControlOptions) (ControlResult, error) {
190 if len(args) != 0 {
191 return ControlResult{}, ErrInvalidArguments
192 }
193 out, err := list(ctx, opts.JSON)
194 return ControlResult{Output: out}, err
195 }}
196}
197
198// ByCode returns the command matching code, case-insensitively.
199func ByCode(code string) (Command, bool) {
200 code = strings.ToUpper(code)
201 for _, cmd := range PaletteRegistry() {
202 if cmd.Code == code {
203 return cmd, true
204 }
205 }
206 return Command{}, false
207}
208
209// BySlug returns the exact command slug. Slugs are stable CLI identifiers.
210func BySlug(slug string) (Command, bool) {
211 for _, cmd := range Registry() {
212 if cmd.Slug == slug {
213 return cmd, true
214 }
215 }
216 return Command{}, false
217}
218
219// PaletteRegistry returns commands visible in the in-band palette.
220func PaletteRegistry() []Command {
221 all := Registry()
222 out := make([]Command, 0, len(all))
223 for _, cmd := range all {
224 if cmd.PaletteVisible {
225 out = append(out, cmd)
226 }
227 }
228 return out
229}