@ssebastien/tcmd 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +234 -2
- package/bin/tcmd +17 -0
- package/package.json +37 -5
- package/src/js/tcmd/components/App.html +28 -0
- package/src/js/tcmd/components/App.js +355 -0
- package/src/js/tcmd/components/App.xml +98 -0
- package/src/js/tcmd/components/BookmarkList.html +12 -0
- package/src/js/tcmd/components/BookmarkList.js +73 -0
- package/src/js/tcmd/components/BookmarkList.xml +18 -0
- package/src/js/tcmd/components/BookmarkRow.html +5 -0
- package/src/js/tcmd/components/BookmarkRow.js +40 -0
- package/src/js/tcmd/components/BookmarkRow.xml +10 -0
- package/src/js/tcmd/components/Breadcrumbs.js +27 -0
- package/src/js/tcmd/components/CommandArgRow.js +90 -0
- package/src/js/tcmd/components/CommandArgRow.xml +5 -0
- package/src/js/tcmd/components/CommandBar.html +4 -0
- package/src/js/tcmd/components/CommandBar.js +126 -0
- package/src/js/tcmd/components/CommandBar.xml +4 -0
- package/src/js/tcmd/components/CommandItem.html +3 -0
- package/src/js/tcmd/components/CommandItem.js +78 -0
- package/src/js/tcmd/components/CommandItem.xml +6 -0
- package/src/js/tcmd/components/CommandPalette.html +7 -0
- package/src/js/tcmd/components/CommandPalette.js +58 -0
- package/src/js/tcmd/components/CommandPalette.xml +16 -0
- package/src/js/tcmd/components/CommandPanel.js +98 -0
- package/src/js/tcmd/components/CommandPanel.xml +35 -0
- package/src/js/tcmd/components/Confirm.html +3 -0
- package/src/js/tcmd/components/Confirm.js +36 -0
- package/src/js/tcmd/components/Confirm.xml +7 -0
- package/src/js/tcmd/components/Crumb.html +4 -0
- package/src/js/tcmd/components/Crumb.js +48 -0
- package/src/js/tcmd/components/Crumb.xml +4 -0
- package/src/js/tcmd/components/EntryDetail.html +15 -0
- package/src/js/tcmd/components/EntryDetail.js +194 -0
- package/src/js/tcmd/components/EntryDetail.xml +16 -0
- package/src/js/tcmd/components/FileList.html +1 -0
- package/src/js/tcmd/components/FileList.js +41 -0
- package/src/js/tcmd/components/FileList.xml +7 -0
- package/src/js/tcmd/components/FilePreview.html +5 -0
- package/src/js/tcmd/components/FilePreview.js +110 -0
- package/src/js/tcmd/components/FilePreview.xml +10 -0
- package/src/js/tcmd/components/FileRow.html +5 -0
- package/src/js/tcmd/components/FileRow.js +135 -0
- package/src/js/tcmd/components/FileRow.xml +10 -0
- package/src/js/tcmd/components/FilterBar.html +6 -0
- package/src/js/tcmd/components/FilterBar.js +50 -0
- package/src/js/tcmd/components/FilterBar.xml +6 -0
- package/src/js/tcmd/components/Help.xml +11 -0
- package/src/js/tcmd/components/PaletteRow.html +3 -0
- package/src/js/tcmd/components/PaletteRow.js +43 -0
- package/src/js/tcmd/components/PaletteRow.xml +4 -0
- package/src/js/tcmd/components/RowList.html +1 -0
- package/src/js/tcmd/components/RowList.xml +1 -0
- package/src/js/tcmd/components/Summary.html +3 -0
- package/src/js/tcmd/components/Summary.js +77 -0
- package/src/js/tcmd/components/Summary.xml +3 -0
- package/src/js/tcmd/components/bind.js +20 -0
- package/src/js/tcmd/components/format.js +155 -0
- package/src/js/tcmd/components/icons.js +185 -0
- package/src/js/tcmd/components/index.js +41 -0
- package/src/js/tcmd/components/rows.js +212 -0
- package/src/js/tcmd/core/bookmarks.js +175 -0
- package/src/js/tcmd/core/commands.js +930 -0
- package/src/js/tcmd/core/directory.js +194 -0
- package/src/js/tcmd/core/entries.js +88 -0
- package/src/js/tcmd/core/fs.js +266 -0
- package/src/js/tcmd/core/index.js +39 -0
- package/src/js/tcmd/core/keymap.js +60 -0
- package/src/js/tcmd/core/operations.js +453 -0
- package/src/js/tcmd/core/os.js +128 -0
- package/src/js/tcmd/core/pane.js +74 -0
- package/src/js/tcmd/core/panel.js +383 -0
- package/src/js/tcmd/core/path.js +57 -0
- package/src/js/tcmd/core/script.js +186 -0
- package/src/js/tcmd/core/selection.js +43 -0
- package/src/js/tcmd/core/session.js +405 -0
- package/src/js/tcmd/index.js +53 -0
- package/src/js/tcmd/tui/app.js +140 -0
- package/src/js/tcmd/tui/catalogue.js +300 -0
- package/src/js/tcmd/tui/frame.js +269 -0
- package/src/js/tcmd/tui/help.js +32 -0
- package/src/js/tcmd/tui/index.js +104 -0
- package/src/js/tcmd/tui/theme.js +177 -0
- package/src/js/tcmd/vendor/fzy.js +243 -0
- package/src/js/tcmd/web/app.js +154 -0
- package/src/js/tcmd/web/catalogue.js +159 -0
- package/src/js/tcmd/web/header.html +1 -0
- package/src/js/tcmd/web/header.js +38 -0
- package/src/js/tcmd/web/http.js +79 -0
- package/src/js/tcmd/web/index.js +22 -0
- package/src/js/tcmd/web/main.js +151 -0
- package/src/js/tcmd/web/opfs.js +282 -0
- package/src/js/tcmd/web/seed.js +90 -0
- package/src/js/tcmd/web/server.js +191 -0
- package/src/js/tcmd/web/theme.js +23 -0
|
@@ -0,0 +1,930 @@
|
|
|
1
|
+
// Project: TinyCommand
|
|
2
|
+
// Author: Sebastien Pierre
|
|
3
|
+
// License: BSD
|
|
4
|
+
// Created: 2026-09-26
|
|
5
|
+
|
|
6
|
+
// Module: tcmd/core/commands
|
|
7
|
+
// Command registry and dispatch. Commands are pure operations over the
|
|
8
|
+
// session model: `run(session, payload)`. Views never mutate state directly —
|
|
9
|
+
// they call `session.dispatch(id, payload)`, keeping the model the single
|
|
10
|
+
// source of truth for every presentation layer.
|
|
11
|
+
//
|
|
12
|
+
// Commands carry presentation metadata:
|
|
13
|
+
// - `kind`: "ui" for UI-level actions; the command palette lists UI actions by
|
|
14
|
+
// default (scripting operations live in core/operations.js).
|
|
15
|
+
// - `bar`: true when the command appears in the command bar's key hints.
|
|
16
|
+
// - `hidden`: true for internal commands that never show in the palette.
|
|
17
|
+
|
|
18
|
+
import { basename, parent as parentOf } from "./path.js"
|
|
19
|
+
import { PANEL_FOCUS, argvFor, bindingAt, missingArg, rowAt, withToggledItem } from "./panel.js"
|
|
20
|
+
import { clampCursor, moveCursor } from "./selection.js"
|
|
21
|
+
import { runLine } from "./script.js"
|
|
22
|
+
import { getOperation, runOperation } from "./operations.js"
|
|
23
|
+
|
|
24
|
+
// Constant: PAGE / JUMP
|
|
25
|
+
// Cursor movement steps: a page for PgUp/PgDn, a shorter jump for the
|
|
26
|
+
// ctrl-arrow accelerators.
|
|
27
|
+
const PAGE = 10
|
|
28
|
+
const JUMP = 5
|
|
29
|
+
|
|
30
|
+
// Function: PALETTE_KEYS
|
|
31
|
+
// Command-bar hints shown while the command palette is open.
|
|
32
|
+
const PALETTE_KEYS = [
|
|
33
|
+
{ id: null, title: "Navigate", keys: "↑/↓" },
|
|
34
|
+
{ id: null, title: "Complete", keys: "Tab" },
|
|
35
|
+
{ id: null, title: "Run", keys: "Enter", primary: true },
|
|
36
|
+
{ id: null, title: "Close", keys: "Esc" },
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
// Constant: SCRIPT_KEYS
|
|
40
|
+
// Command-bar hints for scripting mode (no UI navigation, only the palette
|
|
41
|
+
// itself).
|
|
42
|
+
const SCRIPT_KEYS = [
|
|
43
|
+
{ id: null, title: "Complete", keys: "Tab" },
|
|
44
|
+
{ id: null, title: "Run", keys: "Enter", primary: true },
|
|
45
|
+
{ id: null, title: "Close", keys: "Esc" },
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
// Constant: FILTER_KEYS
|
|
49
|
+
// Command-bar hints shown while a pane filter is capturing input.
|
|
50
|
+
const FILTER_KEYS = [
|
|
51
|
+
{ id: null, title: "Navigate", keys: "↑/↓" },
|
|
52
|
+
{ id: null, title: "Done", keys: "Enter", primary: true },
|
|
53
|
+
{ id: null, title: "Clear", keys: "Esc" },
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
// Constant: BOOKMARK_NAME_KEYS
|
|
57
|
+
// Command-bar hints while naming a bookmark.
|
|
58
|
+
const BOOKMARK_NAME_KEYS = [
|
|
59
|
+
{ id: null, title: "Save", keys: "Enter", primary: true },
|
|
60
|
+
{ id: null, title: "Cancel", keys: "Esc" },
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
// Constant: BOOKMARK_LIST_KEYS
|
|
64
|
+
// Command-bar hints while picking a bookmark.
|
|
65
|
+
const BOOKMARK_LIST_KEYS = [
|
|
66
|
+
{ id: null, title: "Navigate", keys: "↑/↓" },
|
|
67
|
+
{ id: null, title: "Jump", keys: "Enter", primary: true },
|
|
68
|
+
{ id: null, title: "Close", keys: "Esc" },
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
// Constant: PANEL_HINT_KEYS
|
|
72
|
+
// Command-bar hints while the command panel is open.
|
|
73
|
+
const PANEL_HINT_KEYS = [
|
|
74
|
+
{ id: null, title: "Command", keys: "↑/↓" },
|
|
75
|
+
{ id: null, title: "Arg", keys: "⇥" },
|
|
76
|
+
{ id: null, title: "Toggle", keys: "Spc" },
|
|
77
|
+
{ id: null, title: "Run", keys: "↵", primary: true },
|
|
78
|
+
{ id: null, title: "Close", keys: "Esc" },
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
const commands = [
|
|
82
|
+
{
|
|
83
|
+
id: "nav.up",
|
|
84
|
+
title: "Cursor up",
|
|
85
|
+
keys: "↑",
|
|
86
|
+
kind: "ui",
|
|
87
|
+
run(session) {
|
|
88
|
+
bump(session, -1)
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
id: "nav.down",
|
|
93
|
+
title: "Cursor down",
|
|
94
|
+
keys: "↓",
|
|
95
|
+
kind: "ui",
|
|
96
|
+
run(session) {
|
|
97
|
+
bump(session, 1)
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
id: "nav.pageUp",
|
|
102
|
+
title: "Page up",
|
|
103
|
+
keys: "PgUp",
|
|
104
|
+
kind: "ui",
|
|
105
|
+
run(session) {
|
|
106
|
+
bump(session, -PAGE)
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
id: "nav.pageDown",
|
|
111
|
+
title: "Page down",
|
|
112
|
+
keys: "PgDn",
|
|
113
|
+
kind: "ui",
|
|
114
|
+
run(session) {
|
|
115
|
+
bump(session, PAGE)
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
id: "nav.jumpUp",
|
|
120
|
+
title: "Jump up",
|
|
121
|
+
keys: "^↑",
|
|
122
|
+
kind: "ui",
|
|
123
|
+
run(session) {
|
|
124
|
+
bump(session, -JUMP)
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
id: "nav.jumpDown",
|
|
129
|
+
title: "Jump down",
|
|
130
|
+
keys: "^↓",
|
|
131
|
+
kind: "ui",
|
|
132
|
+
run(session) {
|
|
133
|
+
bump(session, JUMP)
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
id: "nav.home",
|
|
138
|
+
title: "First entry",
|
|
139
|
+
keys: "Home",
|
|
140
|
+
kind: "ui",
|
|
141
|
+
run(session) {
|
|
142
|
+
pane(session).cursor.set(0)
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
id: "nav.end",
|
|
147
|
+
title: "Last entry",
|
|
148
|
+
keys: "End",
|
|
149
|
+
kind: "ui",
|
|
150
|
+
run(session) {
|
|
151
|
+
pane(session).cursor.set(Math.max(0, pane(session).entries.value.length - 1))
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
id: "pane.enter",
|
|
156
|
+
title: "Open",
|
|
157
|
+
keys: "Enter",
|
|
158
|
+
kind: "ui",
|
|
159
|
+
bar: true,
|
|
160
|
+
primary: true,
|
|
161
|
+
run(session) {
|
|
162
|
+
return pane(session).open()
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
id: "pane.up",
|
|
167
|
+
title: "Parent",
|
|
168
|
+
keys: "←",
|
|
169
|
+
kind: "ui",
|
|
170
|
+
bar: true,
|
|
171
|
+
run(session) {
|
|
172
|
+
return pane(session).up()
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
id: "pane.back",
|
|
177
|
+
title: "Back",
|
|
178
|
+
keys: "Esc",
|
|
179
|
+
kind: "ui",
|
|
180
|
+
run(session) {
|
|
181
|
+
return pane(session).back()
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
id: "pane.forward",
|
|
186
|
+
title: "Forward",
|
|
187
|
+
keys: "⇧Esc",
|
|
188
|
+
kind: "ui",
|
|
189
|
+
run(session) {
|
|
190
|
+
return pane(session).forward()
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
id: "pane.reload",
|
|
195
|
+
title: "Reload",
|
|
196
|
+
keys: "^R",
|
|
197
|
+
kind: "ui",
|
|
198
|
+
bar: true,
|
|
199
|
+
run(session) {
|
|
200
|
+
return pane(session).refresh()
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
id: "pane.toggle",
|
|
205
|
+
title: "Switch",
|
|
206
|
+
keys: "Tab",
|
|
207
|
+
kind: "ui",
|
|
208
|
+
bar: true,
|
|
209
|
+
run(session) {
|
|
210
|
+
session.active.set(session.active.value === "left" ? "right" : "left")
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
id: "list.mark",
|
|
215
|
+
title: "Mark",
|
|
216
|
+
keys: "Spc",
|
|
217
|
+
kind: "ui",
|
|
218
|
+
bar: true,
|
|
219
|
+
run(session) {
|
|
220
|
+
pane(session).toggleMark()
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
id: "bookmark.record",
|
|
225
|
+
title: "Bookmark",
|
|
226
|
+
keys: "b",
|
|
227
|
+
kind: "ui",
|
|
228
|
+
bar: true,
|
|
229
|
+
run(session) {
|
|
230
|
+
// Record the entry under the cursor; fall back to the pane's
|
|
231
|
+
// directory when the listing is empty.
|
|
232
|
+
const entry = pane(session).currentEntry()
|
|
233
|
+
const path = entry?.path ?? pane(session).path.value
|
|
234
|
+
const isDir = entry ? Boolean(entry.isDir) : true
|
|
235
|
+
const existing = session.bookmarks.find(path)
|
|
236
|
+
if (existing) {
|
|
237
|
+
// Already bookmarked: no need to re-name it.
|
|
238
|
+
session.notice.set({ kind: "tip", text: `bookmark ${existing.name}` })
|
|
239
|
+
return
|
|
240
|
+
}
|
|
241
|
+
// The default name is the path's basename.
|
|
242
|
+
session.bookmarkTarget.set({ path, isDir })
|
|
243
|
+
session.bookmarkQuery.set(basename(path))
|
|
244
|
+
session.bookmarkMode.set("name")
|
|
245
|
+
session.bookmarkOpen.set(true)
|
|
246
|
+
},
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
id: "bookmark.jump",
|
|
250
|
+
title: "Bookmarks",
|
|
251
|
+
keys: "g",
|
|
252
|
+
kind: "ui",
|
|
253
|
+
bar: true,
|
|
254
|
+
run(session) {
|
|
255
|
+
session.bookmarkTarget.set(null)
|
|
256
|
+
session.bookmarkQuery.set("")
|
|
257
|
+
session.bookmarkMode.set("list")
|
|
258
|
+
session.bookmarkOpen.set(true)
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
id: "bookmark.up",
|
|
263
|
+
title: "Previous bookmark",
|
|
264
|
+
kind: "ui",
|
|
265
|
+
hidden: true,
|
|
266
|
+
run(session) {
|
|
267
|
+
moveBookmark(session, -1)
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
id: "bookmark.down",
|
|
272
|
+
title: "Next bookmark",
|
|
273
|
+
kind: "ui",
|
|
274
|
+
hidden: true,
|
|
275
|
+
run(session) {
|
|
276
|
+
moveBookmark(session, 1)
|
|
277
|
+
},
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
id: "bookmark.save",
|
|
281
|
+
title: "Save bookmark",
|
|
282
|
+
kind: "ui",
|
|
283
|
+
hidden: true,
|
|
284
|
+
run(session) {
|
|
285
|
+
const name = `${session.bookmarkQuery.value ?? ""}`.trim()
|
|
286
|
+
const target = session.bookmarkTarget.value
|
|
287
|
+
if (!name) {
|
|
288
|
+
session.notice.set({ kind: "warning", text: "bookmark needs a name" })
|
|
289
|
+
return
|
|
290
|
+
}
|
|
291
|
+
if (!target) {
|
|
292
|
+
closeBookmark(session)
|
|
293
|
+
return
|
|
294
|
+
}
|
|
295
|
+
// Close synchronously: the global key handler and the input's
|
|
296
|
+
// `on:enter` can both fire, and a second save while the overlay is
|
|
297
|
+
// still open would re-record and flash the notice twice. A rejected
|
|
298
|
+
// `record` (rejected name) still surfaces through the dispatch's
|
|
299
|
+
// error notice.
|
|
300
|
+
closeBookmark(session)
|
|
301
|
+
return session.bookmarks.record(name, target.path, target.isDir).then(() => {
|
|
302
|
+
session.notice.set({ kind: "tip", text: `bookmarked ${name}` })
|
|
303
|
+
})
|
|
304
|
+
},
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
id: "bookmark.setIndex",
|
|
308
|
+
title: "Highlight bookmark",
|
|
309
|
+
kind: "ui",
|
|
310
|
+
hidden: true,
|
|
311
|
+
run(session, payload) {
|
|
312
|
+
session.bookmarkIndex.set(Math.max(0, payload?.index ?? 0))
|
|
313
|
+
},
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
id: "bookmark.select",
|
|
317
|
+
title: "Jump to bookmark",
|
|
318
|
+
kind: "ui",
|
|
319
|
+
hidden: true,
|
|
320
|
+
async run(session) {
|
|
321
|
+
const items = session.bookmarkItems.value
|
|
322
|
+
const item = items[clampCursor(session.bookmarkIndex.value, items.length)]
|
|
323
|
+
if (!item) return
|
|
324
|
+
const target = session.panes[session.active.value]
|
|
325
|
+
const path = item.path
|
|
326
|
+
// Resolve the kind when it is unknown (legacy path-only bookmark).
|
|
327
|
+
let isDir = item.isDir
|
|
328
|
+
if (isDir == null) isDir = Boolean((await session.fs.stat(path))?.isDir)
|
|
329
|
+
closeBookmark(session)
|
|
330
|
+
// A directory: open it (show its contents).
|
|
331
|
+
if (isDir) return target.navigate(path)
|
|
332
|
+
// A file: select it in its directory and preview it (a path at the
|
|
333
|
+
// filesystem root has no parent: stay at the root).
|
|
334
|
+
return target.navigate(parentOf(path) ?? "/").then(() => {
|
|
335
|
+
const entries = target.entries.value
|
|
336
|
+
const index = entries.findIndex((entry) => entry.path === path)
|
|
337
|
+
if (index < 0) return
|
|
338
|
+
target.cursor.set(index)
|
|
339
|
+
// Pin the preview so it shows even if the effect lags the cursor.
|
|
340
|
+
session.previewTarget.set(target.currentEntry() ?? null)
|
|
341
|
+
session.showPreview.set(true)
|
|
342
|
+
})
|
|
343
|
+
},
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
id: "bookmark.cancel",
|
|
347
|
+
title: "Cancel bookmark",
|
|
348
|
+
kind: "ui",
|
|
349
|
+
hidden: true,
|
|
350
|
+
run(session) {
|
|
351
|
+
closeBookmark(session)
|
|
352
|
+
},
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
id: "list.cursor",
|
|
356
|
+
title: "Set cursor",
|
|
357
|
+
kind: "ui",
|
|
358
|
+
hidden: true,
|
|
359
|
+
run(session, payload) {
|
|
360
|
+
const target = payload?.paneId ? session.panes[payload.paneId] : pane(session)
|
|
361
|
+
activate(session, payload?.paneId)
|
|
362
|
+
target.cursor.set(clampCursor(payload?.index ?? 0, target.entries.value.length))
|
|
363
|
+
},
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
id: "pane.activate",
|
|
367
|
+
title: "Activate pane",
|
|
368
|
+
kind: "ui",
|
|
369
|
+
hidden: true,
|
|
370
|
+
run(session, payload) {
|
|
371
|
+
activate(session, payload?.paneId)
|
|
372
|
+
},
|
|
373
|
+
},
|
|
374
|
+
{
|
|
375
|
+
id: "pane.setPath",
|
|
376
|
+
title: "Go to path",
|
|
377
|
+
kind: "ui",
|
|
378
|
+
hidden: true,
|
|
379
|
+
run(session, payload) {
|
|
380
|
+
const target = payload?.paneId ? session.panes[payload.paneId] : pane(session)
|
|
381
|
+
activate(session, payload?.paneId)
|
|
382
|
+
return target.navigate(payload?.path)
|
|
383
|
+
},
|
|
384
|
+
},
|
|
385
|
+
{
|
|
386
|
+
id: "view.togglePreview",
|
|
387
|
+
title: "Preview",
|
|
388
|
+
keys: "v",
|
|
389
|
+
kind: "ui",
|
|
390
|
+
bar: true,
|
|
391
|
+
run(session) {
|
|
392
|
+
session.showPreview.set(!session.showPreview.value)
|
|
393
|
+
},
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
id: "file.open",
|
|
397
|
+
title: "Open file",
|
|
398
|
+
keys: "O",
|
|
399
|
+
kind: "ui",
|
|
400
|
+
run(session) {
|
|
401
|
+
const entry = session.previewEntry.value
|
|
402
|
+
if (!entry) return
|
|
403
|
+
return session.os.open(entry.path)
|
|
404
|
+
},
|
|
405
|
+
},
|
|
406
|
+
{
|
|
407
|
+
id: "file.edit",
|
|
408
|
+
title: "Edit file",
|
|
409
|
+
keys: "E",
|
|
410
|
+
kind: "ui",
|
|
411
|
+
run(session) {
|
|
412
|
+
const entry = session.previewEntry.value
|
|
413
|
+
if (!entry || entry.isDir) {
|
|
414
|
+
session.notice.set({ kind: "warning", text: "no file to edit" })
|
|
415
|
+
return
|
|
416
|
+
}
|
|
417
|
+
return session.os.edit(entry.path).then(() => session.panes[session.active.value].refresh())
|
|
418
|
+
},
|
|
419
|
+
},
|
|
420
|
+
// Command panel (`:`/`m`): the structured Commando view over the
|
|
421
|
+
// operations. Lifecycle, then navigation, then draft editing, then run.
|
|
422
|
+
{
|
|
423
|
+
id: "panel.toggle",
|
|
424
|
+
title: "Commands",
|
|
425
|
+
keys: "m",
|
|
426
|
+
kind: "ui",
|
|
427
|
+
bar: true,
|
|
428
|
+
run(session) {
|
|
429
|
+
togglePanel(session)
|
|
430
|
+
},
|
|
431
|
+
},
|
|
432
|
+
{
|
|
433
|
+
id: "panel.open",
|
|
434
|
+
title: "Commands",
|
|
435
|
+
keys: ":",
|
|
436
|
+
kind: "ui",
|
|
437
|
+
hidden: true,
|
|
438
|
+
run(session) {
|
|
439
|
+
// `:` is the natural "command" key: open the structured panel on
|
|
440
|
+
// renderers that have it, and fall back to the flat script palette
|
|
441
|
+
// on those that do not (the web view).
|
|
442
|
+
if (!session.panelEnabled?.value) {
|
|
443
|
+
openPalette(session, "script", "")
|
|
444
|
+
return
|
|
445
|
+
}
|
|
446
|
+
openPanel(session)
|
|
447
|
+
},
|
|
448
|
+
},
|
|
449
|
+
{
|
|
450
|
+
id: "panel.up",
|
|
451
|
+
title: "Previous operation",
|
|
452
|
+
kind: "ui",
|
|
453
|
+
hidden: true,
|
|
454
|
+
run(session) {
|
|
455
|
+
const length = session.panelFiltered.value.length
|
|
456
|
+
session.panelOpIndex.set(moveCursor(session.panelOpIndex.value, -1, length))
|
|
457
|
+
},
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
id: "panel.down",
|
|
461
|
+
title: "Next operation",
|
|
462
|
+
kind: "ui",
|
|
463
|
+
hidden: true,
|
|
464
|
+
run(session) {
|
|
465
|
+
const length = session.panelFiltered.value.length
|
|
466
|
+
session.panelOpIndex.set(moveCursor(session.panelOpIndex.value, 1, length))
|
|
467
|
+
},
|
|
468
|
+
},
|
|
469
|
+
{
|
|
470
|
+
id: "panel.prevArg",
|
|
471
|
+
title: "Previous argument",
|
|
472
|
+
kind: "ui",
|
|
473
|
+
hidden: true,
|
|
474
|
+
run(session) {
|
|
475
|
+
const length = session.panelRows.value.length
|
|
476
|
+
session.panelRowIndex.set(moveCursor(session.panelRowIndex.value, -1, length))
|
|
477
|
+
},
|
|
478
|
+
},
|
|
479
|
+
{
|
|
480
|
+
id: "panel.nextArg",
|
|
481
|
+
title: "Next argument",
|
|
482
|
+
kind: "ui",
|
|
483
|
+
hidden: true,
|
|
484
|
+
run(session) {
|
|
485
|
+
const length = session.panelRows.value.length
|
|
486
|
+
session.panelRowIndex.set(moveCursor(session.panelRowIndex.value, 1, length))
|
|
487
|
+
},
|
|
488
|
+
},
|
|
489
|
+
{
|
|
490
|
+
id: "panel.tab",
|
|
491
|
+
title: "Next argument or command",
|
|
492
|
+
kind: "ui",
|
|
493
|
+
hidden: true,
|
|
494
|
+
run(session, payload) {
|
|
495
|
+
// Tab cycles the focus ring: the command (query) then each argument
|
|
496
|
+
// header, wrapping at both ends. Shift-Tab walks it backwards.
|
|
497
|
+
const delta = payload?.delta === -1 ? -1 : 1
|
|
498
|
+
const rows = session.panelRows.value
|
|
499
|
+
const headers = []
|
|
500
|
+
for (let i = 0; i < rows.length; i++) if (rows[i].type === "arg") headers.push(i)
|
|
501
|
+
if (!headers.length) return
|
|
502
|
+
if (session.panelFocus.value === PANEL_FOCUS.OPS) {
|
|
503
|
+
session.panelRowIndex.set(delta > 0 ? headers[0] : headers[headers.length - 1])
|
|
504
|
+
session.panelFocus.set(PANEL_FOCUS.ARGS)
|
|
505
|
+
return
|
|
506
|
+
}
|
|
507
|
+
const at = headers.indexOf(session.panelRowIndex.value)
|
|
508
|
+
if (delta > 0) {
|
|
509
|
+
if (at === -1 || at >= headers.length - 1) session.panelFocus.set(PANEL_FOCUS.OPS)
|
|
510
|
+
else session.panelRowIndex.set(headers[at + 1])
|
|
511
|
+
} else if (at <= 0) {
|
|
512
|
+
session.panelFocus.set(PANEL_FOCUS.OPS)
|
|
513
|
+
} else {
|
|
514
|
+
session.panelRowIndex.set(headers[at - 1])
|
|
515
|
+
}
|
|
516
|
+
},
|
|
517
|
+
},
|
|
518
|
+
{
|
|
519
|
+
id: "panel.setQuery",
|
|
520
|
+
title: "Filter commands",
|
|
521
|
+
kind: "ui",
|
|
522
|
+
hidden: true,
|
|
523
|
+
run(session, payload) {
|
|
524
|
+
session.panelQuery.set(`${payload?.query ?? ""}`)
|
|
525
|
+
},
|
|
526
|
+
},
|
|
527
|
+
{
|
|
528
|
+
id: "panel.type",
|
|
529
|
+
title: "Type into the command filter",
|
|
530
|
+
kind: "ui",
|
|
531
|
+
hidden: true,
|
|
532
|
+
run(session, payload) {
|
|
533
|
+
// A key pressed while a non-textual argument owns the cursor jumps
|
|
534
|
+
// back to the command column and edits the filter.
|
|
535
|
+
const query = `${session.panelQuery.value ?? ""}`
|
|
536
|
+
session.panelQuery.set(payload?.backspace ? query.slice(0, -1) : `${query}${payload?.char ?? ""}`)
|
|
537
|
+
session.panelFocus.set(PANEL_FOCUS.OPS)
|
|
538
|
+
},
|
|
539
|
+
},
|
|
540
|
+
{
|
|
541
|
+
id: "panel.setRow",
|
|
542
|
+
title: "Highlight argument",
|
|
543
|
+
kind: "ui",
|
|
544
|
+
hidden: true,
|
|
545
|
+
run(session, payload) {
|
|
546
|
+
if (payload?.index !== undefined) session.panelRowIndex.set(Math.max(0, payload.index))
|
|
547
|
+
},
|
|
548
|
+
},
|
|
549
|
+
{
|
|
550
|
+
id: "panel.setOp",
|
|
551
|
+
title: "Select operation",
|
|
552
|
+
kind: "ui",
|
|
553
|
+
hidden: true,
|
|
554
|
+
run(session, payload) {
|
|
555
|
+
// Selecting an operation (a click) moves the cursor without
|
|
556
|
+
// stealing focus from the command filter.
|
|
557
|
+
if (payload?.index !== undefined) session.panelOpIndex.set(Math.max(0, payload.index))
|
|
558
|
+
},
|
|
559
|
+
},
|
|
560
|
+
{
|
|
561
|
+
id: "panel.focusArg",
|
|
562
|
+
title: "Focus arguments",
|
|
563
|
+
kind: "ui",
|
|
564
|
+
hidden: true,
|
|
565
|
+
run(session) {
|
|
566
|
+
// A click on an argument focuses the argument column.
|
|
567
|
+
session.panelFocus.set(PANEL_FOCUS.ARGS)
|
|
568
|
+
},
|
|
569
|
+
},
|
|
570
|
+
{
|
|
571
|
+
id: "panel.toggleItem",
|
|
572
|
+
title: "Toggle argument item",
|
|
573
|
+
kind: "ui",
|
|
574
|
+
hidden: true,
|
|
575
|
+
run(session) {
|
|
576
|
+
const row = rowAt(session.panelRows.value, session.panelRowIndex.value)
|
|
577
|
+
if (!row) return
|
|
578
|
+
const draft = session.panelDraft.value
|
|
579
|
+
if (row.type === "item") {
|
|
580
|
+
session.panelDraft.set(withToggledItem(draft, row.argIndex, row.itemIndex))
|
|
581
|
+
return
|
|
582
|
+
}
|
|
583
|
+
// An argument header toggles its whole selection: select all when
|
|
584
|
+
// some are off, clear when all are on.
|
|
585
|
+
const binding = bindingAt(draft, row.argIndex)
|
|
586
|
+
if (!binding || binding.kind !== "selection") return
|
|
587
|
+
const all = (binding.items ?? []).length > 0 && (binding.items ?? []).every((item) => item.selected)
|
|
588
|
+
const items = (binding.items ?? []).map((item) => ({ ...item, selected: !all }))
|
|
589
|
+
session.panelDraft.set(draft.map((b) => (b.index === row.argIndex ? { ...b, items } : b)))
|
|
590
|
+
},
|
|
591
|
+
},
|
|
592
|
+
{
|
|
593
|
+
id: "panel.run",
|
|
594
|
+
title: "Run command",
|
|
595
|
+
kind: "ui",
|
|
596
|
+
hidden: true,
|
|
597
|
+
async run(session) {
|
|
598
|
+
const op = session.panelOperation.value
|
|
599
|
+
if (!op) return
|
|
600
|
+
// An unrunnable draft is refused with the missing argument's name —
|
|
601
|
+
// the `«NAME»` the preview already flags — instead of the generic
|
|
602
|
+
// usage error `runOperation` would produce.
|
|
603
|
+
const missing = missingArg(session.panelDraft.value)
|
|
604
|
+
if (missing) {
|
|
605
|
+
session.notice.set({ kind: "error", text: `${op.name}: fill in «${missing}»` })
|
|
606
|
+
return
|
|
607
|
+
}
|
|
608
|
+
const status = await runOperation(session, op, argvFor(session.panelDraft.value))
|
|
609
|
+
// A confirm keeps the panel open until the user answers; a run
|
|
610
|
+
// closes it. Errors stay open so the draft can be fixed.
|
|
611
|
+
if (status === "ran") closePanel(session)
|
|
612
|
+
},
|
|
613
|
+
},
|
|
614
|
+
{
|
|
615
|
+
id: "panel.cancel",
|
|
616
|
+
title: "Close command panel",
|
|
617
|
+
kind: "ui",
|
|
618
|
+
hidden: true,
|
|
619
|
+
run(session) {
|
|
620
|
+
closePanel(session)
|
|
621
|
+
},
|
|
622
|
+
},
|
|
623
|
+
{
|
|
624
|
+
id: "palette.toggle",
|
|
625
|
+
title: "Palette",
|
|
626
|
+
keys: "^P",
|
|
627
|
+
kind: "ui",
|
|
628
|
+
bar: true,
|
|
629
|
+
run(session) {
|
|
630
|
+
if (session.paletteOpen.value) {
|
|
631
|
+
closePalette(session)
|
|
632
|
+
return
|
|
633
|
+
}
|
|
634
|
+
openPalette(session, "ui", "")
|
|
635
|
+
},
|
|
636
|
+
},
|
|
637
|
+
{
|
|
638
|
+
id: "palette.script",
|
|
639
|
+
title: "Script",
|
|
640
|
+
kind: "ui",
|
|
641
|
+
run(session) {
|
|
642
|
+
openPalette(session, "script", "")
|
|
643
|
+
},
|
|
644
|
+
},
|
|
645
|
+
{
|
|
646
|
+
id: "palette.run",
|
|
647
|
+
title: "Run command",
|
|
648
|
+
keys: "!",
|
|
649
|
+
kind: "ui",
|
|
650
|
+
run(session) {
|
|
651
|
+
openPalette(session, "script", "!")
|
|
652
|
+
},
|
|
653
|
+
},
|
|
654
|
+
{
|
|
655
|
+
id: "filter.start",
|
|
656
|
+
title: "Filter",
|
|
657
|
+
keys: "/",
|
|
658
|
+
kind: "ui",
|
|
659
|
+
bar: true,
|
|
660
|
+
run(session) {
|
|
661
|
+
session.filtering.set(session.active.value)
|
|
662
|
+
},
|
|
663
|
+
},
|
|
664
|
+
{
|
|
665
|
+
id: "filter.accept",
|
|
666
|
+
title: "Keep filter",
|
|
667
|
+
kind: "ui",
|
|
668
|
+
hidden: true,
|
|
669
|
+
run(session) {
|
|
670
|
+
session.filtering.set(null)
|
|
671
|
+
},
|
|
672
|
+
},
|
|
673
|
+
{
|
|
674
|
+
id: "filter.clear",
|
|
675
|
+
title: "Clear filter",
|
|
676
|
+
kind: "ui",
|
|
677
|
+
hidden: true,
|
|
678
|
+
run(session) {
|
|
679
|
+
pane(session).clearFilter()
|
|
680
|
+
session.filtering.set(null)
|
|
681
|
+
},
|
|
682
|
+
},
|
|
683
|
+
{
|
|
684
|
+
id: "palette.up",
|
|
685
|
+
title: "Previous command",
|
|
686
|
+
kind: "ui",
|
|
687
|
+
hidden: true,
|
|
688
|
+
run(session) {
|
|
689
|
+
movePalette(session, -1)
|
|
690
|
+
},
|
|
691
|
+
},
|
|
692
|
+
{
|
|
693
|
+
id: "palette.down",
|
|
694
|
+
title: "Next command",
|
|
695
|
+
kind: "ui",
|
|
696
|
+
hidden: true,
|
|
697
|
+
run(session) {
|
|
698
|
+
movePalette(session, 1)
|
|
699
|
+
},
|
|
700
|
+
},
|
|
701
|
+
{
|
|
702
|
+
id: "palette.complete",
|
|
703
|
+
title: "Complete",
|
|
704
|
+
kind: "ui",
|
|
705
|
+
hidden: true,
|
|
706
|
+
run(session) {
|
|
707
|
+
const items = session.paletteItems.value
|
|
708
|
+
const item = items[clampCursor(session.paletteIndex.value, items.length)]
|
|
709
|
+
if (item?.completion) session.paletteQuery.set(item.completion)
|
|
710
|
+
},
|
|
711
|
+
},
|
|
712
|
+
{
|
|
713
|
+
id: "palette.setQuery",
|
|
714
|
+
title: "Set palette query",
|
|
715
|
+
kind: "ui",
|
|
716
|
+
hidden: true,
|
|
717
|
+
run(session, payload) {
|
|
718
|
+
session.paletteQuery.set(`${payload?.query ?? ""}`)
|
|
719
|
+
},
|
|
720
|
+
},
|
|
721
|
+
{
|
|
722
|
+
id: "palette.submit",
|
|
723
|
+
title: "Submit palette",
|
|
724
|
+
kind: "ui",
|
|
725
|
+
hidden: true,
|
|
726
|
+
run(session) {
|
|
727
|
+
const query = `${session.paletteQuery.value ?? ""}`.trim()
|
|
728
|
+
if (!query) {
|
|
729
|
+
// Nothing to run: complete the highlighted row instead.
|
|
730
|
+
const items = session.paletteItems.value
|
|
731
|
+
const item = items[clampCursor(session.paletteIndex.value, items.length)]
|
|
732
|
+
if (item?.completion) session.paletteQuery.set(item.completion)
|
|
733
|
+
return
|
|
734
|
+
}
|
|
735
|
+
return runLine(session, query).then((status) => {
|
|
736
|
+
if (status !== "empty") closePalette(session)
|
|
737
|
+
return status
|
|
738
|
+
})
|
|
739
|
+
},
|
|
740
|
+
},
|
|
741
|
+
{
|
|
742
|
+
id: "confirm.yes",
|
|
743
|
+
title: "Confirm",
|
|
744
|
+
kind: "ui",
|
|
745
|
+
hidden: true,
|
|
746
|
+
run(session) {
|
|
747
|
+
const pending = session.confirm.value
|
|
748
|
+
if (!pending) return
|
|
749
|
+
session.confirm.set(null)
|
|
750
|
+
if (pending.op) {
|
|
751
|
+
const op = getOperation(pending.op)
|
|
752
|
+
if (!op) return null
|
|
753
|
+
// A panel-originated confirm re-runs its draft and, on success,
|
|
754
|
+
// closes the panel that was waiting on it.
|
|
755
|
+
return runOperation(session, op, pending.args, { confirmed: true }).then((status) => {
|
|
756
|
+
if (status === "ran" && session.panelOpen?.value) closePanel(session)
|
|
757
|
+
return status
|
|
758
|
+
})
|
|
759
|
+
}
|
|
760
|
+
return runLine(session, pending.line, { confirmed: true })
|
|
761
|
+
},
|
|
762
|
+
},
|
|
763
|
+
{
|
|
764
|
+
id: "confirm.no",
|
|
765
|
+
title: "Cancel",
|
|
766
|
+
kind: "ui",
|
|
767
|
+
hidden: true,
|
|
768
|
+
run(session) {
|
|
769
|
+
session.confirm.set(null)
|
|
770
|
+
},
|
|
771
|
+
},
|
|
772
|
+
// Quit: presentation-only. The key path intercepts `app.quit` before
|
|
773
|
+
// dispatch, and the command-bar click does the same (the renderer owns
|
|
774
|
+
// exiting); the registry entry exists so the bar and the `?` reference
|
|
775
|
+
// can advertise it. Hidden from the palette, where it could only no-op.
|
|
776
|
+
{
|
|
777
|
+
id: "app.quit",
|
|
778
|
+
title: "Quit",
|
|
779
|
+
keys: "q",
|
|
780
|
+
kind: "ui",
|
|
781
|
+
bar: true,
|
|
782
|
+
hidden: true,
|
|
783
|
+
run() {},
|
|
784
|
+
},
|
|
785
|
+
]
|
|
786
|
+
|
|
787
|
+
// Function: getCommands
|
|
788
|
+
// The full command registry.
|
|
789
|
+
function getCommands() {
|
|
790
|
+
return commands
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
// Function: getCommand
|
|
794
|
+
// Looks up a command by id.
|
|
795
|
+
function getCommand(id) {
|
|
796
|
+
for (let i = 0; i < commands.length; i++) {
|
|
797
|
+
if (commands[i].id === id) return commands[i]
|
|
798
|
+
}
|
|
799
|
+
return null
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
// Function: getPaletteCommands
|
|
803
|
+
// Commands the command palette lists in UI mode: user-visible UI actions.
|
|
804
|
+
function getPaletteCommands() {
|
|
805
|
+
return commands.filter((command) => !command.hidden && (command.kind ?? "ui") === "ui")
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
// Function: getCommandBarItems
|
|
809
|
+
// Command-bar rows for the current context: the palette's key hints while it
|
|
810
|
+
// is open (scripting mode has its own), the filter hints while a pane filter is
|
|
811
|
+
// capturing input, the `bar`-flagged UI commands otherwise (keymap order).
|
|
812
|
+
function getCommandBarItems(paletteOpen, paletteMode, filtering, bookmarkOpen, bookmarkMode, panelOpen) {
|
|
813
|
+
if (panelOpen) return PANEL_HINT_KEYS
|
|
814
|
+
if (bookmarkOpen) return bookmarkMode === "name" ? BOOKMARK_NAME_KEYS : BOOKMARK_LIST_KEYS
|
|
815
|
+
if (paletteOpen) return paletteMode === "script" ? SCRIPT_KEYS : PALETTE_KEYS
|
|
816
|
+
if (filtering) return FILTER_KEYS
|
|
817
|
+
return commands.filter((command) => command.bar).map(commandRow)
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
// Function: commandRow
|
|
821
|
+
// Projects a command to the small row object views render.
|
|
822
|
+
function commandRow(command) {
|
|
823
|
+
return {
|
|
824
|
+
id: command.id,
|
|
825
|
+
title: command.title,
|
|
826
|
+
keys: command.keys,
|
|
827
|
+
kind: command.kind ?? "ui",
|
|
828
|
+
primary: command.primary === true,
|
|
829
|
+
completion: command.completion ?? command.title,
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
// Function: createDispatch
|
|
834
|
+
// Returns `dispatch(id, payload)` bound to the session. Unknown and `app.*`
|
|
835
|
+
// (presentation-only) commands are ignored here. Failures (sync throws and
|
|
836
|
+
// rejections) land on the active pane's error cell and the session notice.
|
|
837
|
+
function createDispatch(session) {
|
|
838
|
+
return (id, payload) => {
|
|
839
|
+
const command = getCommand(id)
|
|
840
|
+
if (!command) return null
|
|
841
|
+
session.notice?.set(null)
|
|
842
|
+
try {
|
|
843
|
+
return Promise.resolve(command.run(session, payload)).catch((error) => fail(session, error))
|
|
844
|
+
} catch (error) {
|
|
845
|
+
return fail(session, error)
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
function fail(session, error) {
|
|
851
|
+
const message = `${error?.message ?? error}`
|
|
852
|
+
pane(session).error.set(message)
|
|
853
|
+
session.notice?.set({ kind: "error", text: message })
|
|
854
|
+
return null
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
function pane(session) {
|
|
858
|
+
return session.panes[session.active.value]
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
// Function: activate
|
|
862
|
+
// Makes `paneId` the active pane (no-op for unknown ids or when already
|
|
863
|
+
// active). Clicks on rows, crumbs, tabs and pane bodies all route here.
|
|
864
|
+
function activate(session, paneId) {
|
|
865
|
+
if (!paneId || !session.panes[paneId]) return
|
|
866
|
+
if (session.active.value !== paneId) session.active.set(paneId)
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
function bump(session, delta) {
|
|
870
|
+
const p = pane(session)
|
|
871
|
+
p.cursor.set(moveCursor(p.cursor.value, delta, p.entries.value.length))
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
function movePalette(session, delta) {
|
|
875
|
+
const length = session.paletteItems.value.length
|
|
876
|
+
session.paletteIndex.set(moveCursor(session.paletteIndex.value, delta, length))
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
function moveBookmark(session, delta) {
|
|
880
|
+
const length = session.bookmarkItems.value.length
|
|
881
|
+
session.bookmarkIndex.set(moveCursor(session.bookmarkIndex.value, delta, length))
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
function closeBookmark(session) {
|
|
885
|
+
session.bookmarkOpen.set(false)
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
function openPanel(session) {
|
|
889
|
+
session.panelQuery.set("")
|
|
890
|
+
session.panelOpIndex.set(0)
|
|
891
|
+
session.panelRowIndex.set(0)
|
|
892
|
+
session.panelFocus.set(PANEL_FOCUS.OPS)
|
|
893
|
+
session.panelOpen.set(true)
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
// Function: togglePanel
|
|
897
|
+
// Opens the command panel, or closes it when already open. Disabled (TUI-only)
|
|
898
|
+
// renderers ignore it.
|
|
899
|
+
function togglePanel(session) {
|
|
900
|
+
if (!session.panelEnabled?.value) return
|
|
901
|
+
if (session.panelOpen.value) {
|
|
902
|
+
closePanel(session)
|
|
903
|
+
return
|
|
904
|
+
}
|
|
905
|
+
openPanel(session)
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
function closePanel(session) {
|
|
909
|
+
session.panelOpen.set(false)
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
function openPalette(session, mode, query) {
|
|
913
|
+
session.paletteMode.set(mode)
|
|
914
|
+
session.paletteQuery.set(query)
|
|
915
|
+
session.paletteIndex.set(0)
|
|
916
|
+
session.paletteOpen.set(true)
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
function closePalette(session) {
|
|
920
|
+
session.paletteOpen.set(false)
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
export {
|
|
924
|
+
commandRow,
|
|
925
|
+
createDispatch,
|
|
926
|
+
getCommand,
|
|
927
|
+
getCommandBarItems,
|
|
928
|
+
getCommands,
|
|
929
|
+
getPaletteCommands,
|
|
930
|
+
}
|