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 listCommand("env", "Session environment", "Print the session's desktop and login variables (JSON null means unset)", TargetSession, func(ctx ControlContext, json bool) (string, error) { return ctx.SessionEnvironment(json) }),
56 remoteCatalogCommand(),
57 sessionRecoveryCommand(),
58 }
59 return commands
60}
61
62func paletteOnly(slug, code, name, desc string, run func(Context, []string) error) Command {
63 return Command{Slug: slug, Code: code, Name: name, Desc: desc, Usage: slug, PaletteVisible: true, Run: run}
64}
65
66func paletteControl(slug, code, name, desc string, target TargetKind, run func(Context, []string) error, control func(ControlContext) error) Command {
67 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) {
68 if len(args) != 0 {
69 return ControlResult{}, ErrInvalidArguments
70 }
71 return ControlResult{}, control(ctx)
72 }}
73}
74
75func paletteOptionalOne(slug, code, name, desc string, target TargetKind, prompt func(Context) error, direct func(Context, string) error, control func(ControlContext, string) error) Command {
76 cmd := Command{Slug: slug, Code: code, Name: name, Desc: desc, Usage: slug + " [name]", PaletteVisible: true, Scriptable: true, Target: target, Arguments: ArgumentsOptional}
77 cmd.Preview = func(args []string, hasArgument bool) string {
78 if len(args) == 0 {
79 if hasArgument {
80 return "Type a name, or press Enter"
81 }
82 return desc
83 }
84 if len(args) > 1 {
85 return "Use one name"
86 }
87 switch slug {
88 case "new-session":
89 return "Create session “" + args[0] + "”"
90 case "rename-session":
91 return "Rename this session to “" + args[0] + "”"
92 default:
93 return "Rename this tab to “" + args[0] + "”"
94 }
95 }
96 cmd.Run = func(ctx Context, args []string) error {
97 switch len(args) {
98 case 0:
99 return prompt(ctx)
100 case 1:
101 return direct(ctx, args[0])
102 default:
103 return ErrInvalidArguments
104 }
105 }
106 cmd.Control = func(ctx ControlContext, args []string, _ ControlOptions) (ControlResult, error) {
107 if len(args) != 1 || args[0] == "" {
108 return ControlResult{}, ErrInvalidArguments
109 }
110 return ControlResult{}, control(ctx, args[0])
111 }
112 return cmd
113}
114
115func movePaneCommand() Command {
116 return Command{
117 Slug: "move-pane", Code: "MFP", Name: "Move pane to tab",
118 Desc: "Move focused pane to tab", Usage: "move-pane <destination-session> <destination-tab-id>",
119 PaletteVisible: true, Scriptable: true, Target: TargetPane, Scope: CommandScopeCrossSession,
120 Run: func(ctx Context, _ []string) error { return ctx.OpenMovePanePicker() },
121 Control: func(ctx ControlContext, args []string, _ ControlOptions) (ControlResult, error) {
122 if len(args) != 2 || args[0] == "" || args[1] == "" {
123 return ControlResult{}, ErrInvalidArguments
124 }
125 return ControlResult{}, ctx.MovePane(args[0], args[1])
126 },
127 }
128}
129
130func moveTabCommand() Command {
131 return Command{
132 Slug: "move-tab", Code: "MAT", Name: "Move tab to session",
133 Desc: "Move active tab to session", Usage: "move-tab <destination-session>",
134 PaletteVisible: true, Scriptable: true, Target: TargetTab, Scope: CommandScopeCrossSession,
135 Run: func(ctx Context, _ []string) error { return ctx.OpenMoveTabPicker() },
136 Control: func(ctx ControlContext, args []string, _ ControlOptions) (ControlResult, error) {
137 if len(args) != 1 || args[0] == "" {
138 return ControlResult{}, ErrInvalidArguments
139 }
140 return ControlResult{}, ctx.MoveTab(args[0])
141 },
142 }
143}
144
145func toastCommand() Command {
146 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) {
147 severity := "info"
148 if len(args) >= 2 && args[0] == "-l" {
149 severity = args[1]
150 args = args[2:]
151 }
152 if len(args) != 1 || args[0] == "" {
153 return ControlResult{}, ErrInvalidArguments
154 }
155 return ControlResult{}, ctx.Toast(severity, args[0])
156 }}
157}
158
159func sessionRecoveryCommand() Command {
160 return Command{
161 Slug: "session-recovery", Name: "Session recovery", Desc: "Discard a broken durable session's persisted state",
162 Usage: "session-recovery discard",
163 Scriptable: true, Target: TargetNone,
164 Control: func(ctx ControlContext, args []string, _ ControlOptions) (ControlResult, error) {
165 if len(args) != 1 || args[0] != "discard" {
166 return ControlResult{}, ErrInvalidArguments
167 }
168 output, err := ctx.SessionRecovery("discard")
169 return ControlResult{Output: output}, err
170 },
171 }
172}
173
174func remoteCatalogCommand() Command {
175 return Command{
176 Slug: "remote-catalog", Name: "Remote catalog",
177 Desc: "Return the versioned session catalog for remote discovery",
178 Usage: "remote-catalog --json", Scriptable: true, Target: TargetNone,
179 Control: func(ctx ControlContext, args []string, opts ControlOptions) (ControlResult, error) {
180 if len(args) != 0 || !opts.JSON {
181 return ControlResult{}, ErrInvalidArguments
182 }
183 output, err := ctx.RemoteCatalog(true)
184 return ControlResult{Output: output}, err
185 },
186 }
187}
188
189func listCommand(slug, name, desc string, target TargetKind, list func(ControlContext, bool) (string, error)) Command {
190 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) {
191 if len(args) != 0 {
192 return ControlResult{}, ErrInvalidArguments
193 }
194 out, err := list(ctx, opts.JSON)
195 return ControlResult{Output: out}, err
196 }}
197}
198
199// ByCode returns the command matching code, case-insensitively.
200func ByCode(code string) (Command, bool) {
201 code = strings.ToUpper(code)
202 for _, cmd := range PaletteRegistry() {
203 if cmd.Code == code {
204 return cmd, true
205 }
206 }
207 return Command{}, false
208}
209
210// BySlug returns the exact command slug. Slugs are stable CLI identifiers.
211func BySlug(slug string) (Command, bool) {
212 for _, cmd := range Registry() {
213 if cmd.Slug == slug {
214 return cmd, true
215 }
216 }
217 return Command{}, false
218}
219
220// PaletteRegistry returns commands visible in the in-band palette.
221func PaletteRegistry() []Command {
222 all := Registry()
223 out := make([]Command, 0, len(all))
224 for _, cmd := range all {
225 if cmd.PaletteVisible {
226 out = append(out, cmd)
227 }
228 }
229 return out
230}