@ssebastien/tcmd 0.1.3 → 0.1.4
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 +121 -38
- package/bin/tcmd +10 -2
- package/bin/tcmd.js +13 -2
- package/package.json +4 -3
- package/public/index.html +14 -0
- package/public/main.js +17501 -0
- package/public/style.css +901 -0
- package/scripts/serve-web.js +61 -0
- package/src/js/tcmd/components/App.html +28 -12
- package/src/js/tcmd/components/App.js +135 -85
- package/src/js/tcmd/components/App.xml +64 -15
- package/src/js/tcmd/components/BookmarkList.html +3 -2
- package/src/js/tcmd/components/BookmarkList.js +1 -1
- package/src/js/tcmd/components/Breadcrumbs.js +1 -1
- package/src/js/tcmd/components/CommandArgRow.html +17 -0
- package/src/js/tcmd/components/CommandArgRow.js +86 -26
- package/src/js/tcmd/components/CommandArgRow.xml +31 -4
- package/src/js/tcmd/components/CommandBar.html +1 -1
- package/src/js/tcmd/components/CommandBar.js +3 -3
- package/src/js/tcmd/components/CommandItem.html +1 -1
- package/src/js/tcmd/components/CommandItem.js +1 -0
- package/src/js/tcmd/components/CommandPanel.html +29 -0
- package/src/js/tcmd/components/CommandPanel.js +54 -52
- package/src/js/tcmd/components/CommandPanel.xml +38 -24
- package/src/js/tcmd/components/Confirm.html +1 -1
- package/src/js/tcmd/components/EntryDetail.html +7 -7
- package/src/js/tcmd/components/EntryDetail.js +1 -1
- package/src/js/tcmd/components/FileList.html +5 -1
- package/src/js/tcmd/components/FileList.js +2 -1
- package/src/js/tcmd/components/FilePreview.html +14 -3
- package/src/js/tcmd/components/FilePreview.js +73 -10
- package/src/js/tcmd/components/FilePreview.xml +19 -6
- package/src/js/tcmd/components/FileRow.html +1 -1
- package/src/js/tcmd/components/FileRow.js +10 -3
- package/src/js/tcmd/components/FilterBar.html +1 -1
- package/src/js/tcmd/components/FilterBar.js +14 -4
- package/src/js/tcmd/components/FilterBar.xml +1 -1
- package/src/js/tcmd/{tui/frame.js → components/Frame.js} +120 -91
- package/src/js/tcmd/components/Help.html +7 -0
- package/src/js/tcmd/components/Help.js +109 -0
- package/src/js/tcmd/components/Summary.html +1 -1
- package/src/js/tcmd/components/Summary.js +1 -6
- package/src/js/tcmd/components/icons.js +39 -5
- package/src/js/tcmd/components/index.js +3 -4
- package/src/js/tcmd/components/rows.js +37 -33
- package/src/js/tcmd/core/commands.js +422 -180
- package/src/js/tcmd/core/directory.js +145 -95
- package/src/js/tcmd/core/fs.js +29 -2
- package/src/js/tcmd/core/history.js +58 -0
- package/src/js/tcmd/core/index.js +3 -9
- package/src/js/tcmd/core/keymap.js +3 -3
- package/src/js/tcmd/core/operations.js +108 -15
- package/src/js/tcmd/core/pane.js +31 -8
- package/src/js/tcmd/core/panel.js +172 -82
- package/src/js/tcmd/core/script.js +7 -95
- package/src/js/tcmd/core/selection.js +9 -1
- package/src/js/tcmd/core/session.js +280 -152
- package/src/js/tcmd/index.js +0 -1
- package/src/js/tcmd/tui/app.js +26 -5
- package/src/js/tcmd/tui/catalogue.js +6 -19
- package/src/js/tcmd/tui/index.js +1 -16
- package/src/js/tcmd/tui/theme.js +5 -0
- package/src/js/tcmd/web/app.js +158 -32
- package/src/js/tcmd/web/bookmarks.js +36 -0
- package/src/js/tcmd/web/catalogue.js +45 -39
- package/src/js/tcmd/web/effects.js +143 -0
- package/src/js/tcmd/web/header.html +1 -1
- package/src/js/tcmd/web/header.js +15 -11
- package/src/js/tcmd/web/main.js +6 -66
- package/src/js/tcmd/web/seed.js +3 -3
- package/src/js/tcmd/web/server.js +6 -4
- package/src/js/tcmd/web/theme.js +1 -0
- package/src/js/tcmd/components/CommandPalette.html +0 -7
- package/src/js/tcmd/components/CommandPalette.js +0 -58
- package/src/js/tcmd/components/CommandPalette.xml +0 -16
- package/src/js/tcmd/components/PaletteRow.html +0 -3
- package/src/js/tcmd/components/PaletteRow.js +0 -43
- package/src/js/tcmd/components/PaletteRow.xml +0 -4
- package/src/js/tcmd/tui/help.js +0 -32
|
@@ -10,14 +10,15 @@
|
|
|
10
10
|
// source of truth for every presentation layer.
|
|
11
11
|
//
|
|
12
12
|
// Commands carry presentation metadata:
|
|
13
|
-
// - `kind`: "ui" for UI-level actions; the command
|
|
14
|
-
//
|
|
13
|
+
// - `kind`: "ui" for UI-level actions; the command panel lists the visible UI
|
|
14
|
+
// actions alongside the CLI operations (see core/panel.js).
|
|
15
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
|
|
16
|
+
// - `hidden`: true for internal commands that never show in the panel.
|
|
17
17
|
|
|
18
18
|
import { basename, parent as parentOf } from "./path.js"
|
|
19
|
-
import { PANEL_FOCUS, argvFor, bindingAt,
|
|
19
|
+
import { PANEL_FOCUS, argvFor, bindingAt, missingBinding, rowAt, withArgValue, withToggledItem } from "./panel.js"
|
|
20
20
|
import { clampCursor, moveCursor } from "./selection.js"
|
|
21
|
+
import { currentPane } from "./pane.js"
|
|
21
22
|
import { runLine } from "./script.js"
|
|
22
23
|
import { getOperation, runOperation } from "./operations.js"
|
|
23
24
|
|
|
@@ -27,24 +28,6 @@ import { getOperation, runOperation } from "./operations.js"
|
|
|
27
28
|
const PAGE = 10
|
|
28
29
|
const JUMP = 5
|
|
29
30
|
|
|
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
31
|
// Constant: FILTER_KEYS
|
|
49
32
|
// Command-bar hints shown while a pane filter is capturing input.
|
|
50
33
|
const FILTER_KEYS = [
|
|
@@ -73,11 +56,24 @@ const BOOKMARK_LIST_KEYS = [
|
|
|
73
56
|
const PANEL_HINT_KEYS = [
|
|
74
57
|
{ id: null, title: "Command", keys: "↑/↓" },
|
|
75
58
|
{ id: null, title: "Arg", keys: "⇥" },
|
|
59
|
+
{ id: null, title: "Browse", keys: "^O" },
|
|
76
60
|
{ id: null, title: "Toggle", keys: "Spc" },
|
|
77
61
|
{ id: null, title: "Run", keys: "↵", primary: true },
|
|
78
62
|
{ id: null, title: "Close", keys: "Esc" },
|
|
79
63
|
]
|
|
80
64
|
|
|
65
|
+
// Constant: PANEL_PICK_KEYS
|
|
66
|
+
// Command-bar hints while the panel's file picker owns the keyboard.
|
|
67
|
+
const PANEL_PICK_KEYS = [
|
|
68
|
+
{ id: null, title: "Filter", keys: "type" },
|
|
69
|
+
{ id: null, title: "Row", keys: "↑/↓" },
|
|
70
|
+
{ id: null, title: "Open", keys: "→" },
|
|
71
|
+
{ id: null, title: "Choose", keys: "↵", primary: true },
|
|
72
|
+
{ id: null, title: "Here", keys: "^↵" },
|
|
73
|
+
{ id: null, title: "Up", keys: "⌫" },
|
|
74
|
+
{ id: null, title: "Back", keys: "Esc" },
|
|
75
|
+
]
|
|
76
|
+
|
|
81
77
|
const commands = [
|
|
82
78
|
{
|
|
83
79
|
id: "nav.up",
|
|
@@ -159,7 +155,21 @@ const commands = [
|
|
|
159
155
|
bar: true,
|
|
160
156
|
primary: true,
|
|
161
157
|
run(session) {
|
|
162
|
-
|
|
158
|
+
// The entry under the *focused* pane's cursor decides where the
|
|
159
|
+
// current directory goes: a directory descends into it. On an
|
|
160
|
+
// auxiliary column (parent/preview) that means the current pane
|
|
161
|
+
// follows the selection, which is what keeps the mirror in step.
|
|
162
|
+
const focused = pane(session)
|
|
163
|
+
const entry = focused.currentEntry()
|
|
164
|
+
if (!entry) return
|
|
165
|
+
const current = currentPane(session)
|
|
166
|
+
if (entry.isDir) return current.navigate(entry.path)
|
|
167
|
+
// A file outside the current pane pins the inspector on it.
|
|
168
|
+
if (focused !== current) {
|
|
169
|
+
session.previewTarget.set(entry)
|
|
170
|
+
session.showPreview.set(true)
|
|
171
|
+
}
|
|
172
|
+
return undefined
|
|
163
173
|
},
|
|
164
174
|
},
|
|
165
175
|
{
|
|
@@ -169,7 +179,10 @@ const commands = [
|
|
|
169
179
|
kind: "ui",
|
|
170
180
|
bar: true,
|
|
171
181
|
run(session) {
|
|
172
|
-
|
|
182
|
+
// Stepping up always moves the current directory: the parent
|
|
183
|
+
// column mirrors it, so navigating that column itself would
|
|
184
|
+
// break the mirror.
|
|
185
|
+
return currentPane(session).up()
|
|
173
186
|
},
|
|
174
187
|
},
|
|
175
188
|
{
|
|
@@ -178,7 +191,7 @@ const commands = [
|
|
|
178
191
|
keys: "Esc",
|
|
179
192
|
kind: "ui",
|
|
180
193
|
run(session) {
|
|
181
|
-
return
|
|
194
|
+
return currentPane(session).back()
|
|
182
195
|
},
|
|
183
196
|
},
|
|
184
197
|
{
|
|
@@ -187,7 +200,7 @@ const commands = [
|
|
|
187
200
|
keys: "⇧Esc",
|
|
188
201
|
kind: "ui",
|
|
189
202
|
run(session) {
|
|
190
|
-
return
|
|
203
|
+
return currentPane(session).forward()
|
|
191
204
|
},
|
|
192
205
|
},
|
|
193
206
|
{
|
|
@@ -207,7 +220,23 @@ const commands = [
|
|
|
207
220
|
kind: "ui",
|
|
208
221
|
bar: true,
|
|
209
222
|
run(session) {
|
|
210
|
-
|
|
223
|
+
// Cycle through the columns the current view mode shows:
|
|
224
|
+
// source ⇄ destination in commander, parent → current → preview
|
|
225
|
+
// in explorer. Focus parked on a column that is not in the cycle
|
|
226
|
+
// (the inspector in commander) lands on the other side.
|
|
227
|
+
const current = session.currentId.value
|
|
228
|
+
const order =
|
|
229
|
+
session.viewMode.value === "explorer"
|
|
230
|
+
? session.showPreview.value
|
|
231
|
+
? ["parent", current, "preview"]
|
|
232
|
+
: ["parent", current]
|
|
233
|
+
: ["left", "right"]
|
|
234
|
+
const at = order.indexOf(session.active.value)
|
|
235
|
+
const next =
|
|
236
|
+
at >= 0
|
|
237
|
+
? order[(at + 1) % order.length]
|
|
238
|
+
: order.find((paneId) => paneId !== current) ?? order[0]
|
|
239
|
+
session.active.set(next)
|
|
211
240
|
},
|
|
212
241
|
},
|
|
213
242
|
{
|
|
@@ -321,7 +350,7 @@ const commands = [
|
|
|
321
350
|
const items = session.bookmarkItems.value
|
|
322
351
|
const item = items[clampCursor(session.bookmarkIndex.value, items.length)]
|
|
323
352
|
if (!item) return
|
|
324
|
-
const target = session
|
|
353
|
+
const target = currentPane(session)
|
|
325
354
|
const path = item.path
|
|
326
355
|
// Resolve the kind when it is unknown (legacy path-only bookmark).
|
|
327
356
|
let isDir = item.isDir
|
|
@@ -353,13 +382,24 @@ const commands = [
|
|
|
353
382
|
},
|
|
354
383
|
{
|
|
355
384
|
id: "list.cursor",
|
|
356
|
-
title: "
|
|
385
|
+
title: "Select row",
|
|
357
386
|
kind: "ui",
|
|
358
387
|
hidden: true,
|
|
359
388
|
run(session, payload) {
|
|
360
389
|
const target = payload?.paneId ? session.panes[payload.paneId] : pane(session)
|
|
361
390
|
activate(session, payload?.paneId)
|
|
362
|
-
|
|
391
|
+
const index = payload?.index ?? 0
|
|
392
|
+
target.cursor.set(clampCursor(index, target.entries.value.length))
|
|
393
|
+
// In the explorer the parent and preview columns are mirrors: a
|
|
394
|
+
// click on one of their rows retargets the current directory
|
|
395
|
+
// instead of only moving that column's cursor (see
|
|
396
|
+
// `retargetFromParent` / `retargetFromPreview`). Focus stays on the
|
|
397
|
+
// clicked column; the pane body's own `pane:activate` agrees.
|
|
398
|
+
if (session.viewMode.value !== "explorer") return
|
|
399
|
+
if (payload?.paneId === "parent") return retargetFromParent(session, target, index)
|
|
400
|
+
if (payload?.paneId === "preview" && isColumnPane(session, "preview")) {
|
|
401
|
+
return retargetFromPreview(session, target, index)
|
|
402
|
+
}
|
|
363
403
|
},
|
|
364
404
|
},
|
|
365
405
|
{
|
|
@@ -377,9 +417,11 @@ const commands = [
|
|
|
377
417
|
kind: "ui",
|
|
378
418
|
hidden: true,
|
|
379
419
|
run(session, payload) {
|
|
380
|
-
|
|
420
|
+
// A breadcrumb click focuses its column, then lands the current
|
|
421
|
+
// directory there: the auxiliary columns mirror it, so their
|
|
422
|
+
// crumbs navigate the pane they follow.
|
|
381
423
|
activate(session, payload?.paneId)
|
|
382
|
-
return
|
|
424
|
+
return currentPane(session).navigate(payload?.path)
|
|
383
425
|
},
|
|
384
426
|
},
|
|
385
427
|
{
|
|
@@ -392,6 +434,33 @@ const commands = [
|
|
|
392
434
|
session.showPreview.set(!session.showPreview.value)
|
|
393
435
|
},
|
|
394
436
|
},
|
|
437
|
+
{
|
|
438
|
+
id: "view.maximize",
|
|
439
|
+
title: "Maximize preview",
|
|
440
|
+
keys: "⇧V",
|
|
441
|
+
kind: "ui",
|
|
442
|
+
run(session) {
|
|
443
|
+
// The inspector takes the whole pane row, or gives it back. The
|
|
444
|
+
// preference cells are untouched, so the previous layout — pane
|
|
445
|
+
// widths, focus and whether the preview was even shown — returns
|
|
446
|
+
// untouched on the next press.
|
|
447
|
+
session.previewFull.set(!session.previewFull.value)
|
|
448
|
+
},
|
|
449
|
+
},
|
|
450
|
+
{
|
|
451
|
+
id: "view.toggleMode",
|
|
452
|
+
title: "Explorer view",
|
|
453
|
+
keys: "t",
|
|
454
|
+
kind: "ui",
|
|
455
|
+
bar: true,
|
|
456
|
+
run(session) {
|
|
457
|
+
// Flip the workspace between the two-pane commander and the
|
|
458
|
+
// three-column explorer (parent · current · preview). Focus on a
|
|
459
|
+
// column that does not exist in the target mode is handed back to
|
|
460
|
+
// the pane it was following (see the guard effect in the session).
|
|
461
|
+
session.viewMode.set(session.viewMode.value === "explorer" ? "commander" : "explorer")
|
|
462
|
+
},
|
|
463
|
+
},
|
|
395
464
|
{
|
|
396
465
|
id: "file.open",
|
|
397
466
|
title: "Open file",
|
|
@@ -414,36 +483,33 @@ const commands = [
|
|
|
414
483
|
session.notice.set({ kind: "warning", text: "no file to edit" })
|
|
415
484
|
return
|
|
416
485
|
}
|
|
417
|
-
return session.os.edit(entry.path).then(() => session.
|
|
486
|
+
return session.os.edit(entry.path).then(() => currentPane(session).refresh())
|
|
418
487
|
},
|
|
419
488
|
},
|
|
420
|
-
// Command panel (
|
|
421
|
-
//
|
|
489
|
+
// Command panel (`:`): the one entry point, over both the CLI operations
|
|
490
|
+
// and the TinyCmd actions. Lifecycle, then navigation, then draft editing,
|
|
491
|
+
// then run.
|
|
422
492
|
{
|
|
423
|
-
id: "panel.
|
|
493
|
+
id: "panel.open",
|
|
424
494
|
title: "Commands",
|
|
425
|
-
keys: "
|
|
495
|
+
keys: ":",
|
|
426
496
|
kind: "ui",
|
|
427
497
|
bar: true,
|
|
428
498
|
run(session) {
|
|
429
|
-
|
|
499
|
+
openPanel(session)
|
|
430
500
|
},
|
|
431
501
|
},
|
|
432
502
|
{
|
|
433
|
-
id: "panel.
|
|
434
|
-
title: "
|
|
435
|
-
keys: ":",
|
|
503
|
+
id: "panel.shell",
|
|
504
|
+
title: "Shell command",
|
|
436
505
|
kind: "ui",
|
|
437
506
|
hidden: true,
|
|
438
507
|
run(session) {
|
|
439
|
-
//
|
|
440
|
-
//
|
|
441
|
-
//
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
return
|
|
445
|
-
}
|
|
446
|
-
openPanel(session)
|
|
508
|
+
// `!` is the "run a shell command" reflex: land on the `run`
|
|
509
|
+
// operation with its raw CMD field already owning the keyboard. The
|
|
510
|
+
// field starts empty — `!` is a script-line alias, not part of the
|
|
511
|
+
// command itself.
|
|
512
|
+
openPanel(session, { name: "run", focus: PANEL_FOCUS.ARGS })
|
|
447
513
|
},
|
|
448
514
|
},
|
|
449
515
|
{
|
|
@@ -492,8 +558,9 @@ const commands = [
|
|
|
492
558
|
kind: "ui",
|
|
493
559
|
hidden: true,
|
|
494
560
|
run(session, payload) {
|
|
495
|
-
// Tab cycles the focus ring: the command
|
|
496
|
-
// header, wrapping at both ends
|
|
561
|
+
// Tab cycles the focus ring: the command filter then each argument
|
|
562
|
+
// header, wrapping at both ends (an item row is reached with the
|
|
563
|
+
// arrows). Shift-Tab walks it backwards.
|
|
497
564
|
const delta = payload?.delta === -1 ? -1 : 1
|
|
498
565
|
const rows = session.panelRows.value
|
|
499
566
|
const headers = []
|
|
@@ -577,165 +644,194 @@ const commands = [
|
|
|
577
644
|
if (!row) return
|
|
578
645
|
const draft = session.panelDraft.value
|
|
579
646
|
if (row.type === "item") {
|
|
580
|
-
session.panelDraft.set(withToggledItem(draft, row.argIndex, row.itemIndex))
|
|
647
|
+
session.panelDraft.set(withToggledItem(draft, row.argIndex, row.itemIndex, row.multi !== false))
|
|
581
648
|
return
|
|
582
649
|
}
|
|
583
650
|
// An argument header toggles its whole selection: select all when
|
|
584
|
-
// some are off, clear when all are on.
|
|
651
|
+
// some are off, clear when all are on. A single-path argument is a
|
|
652
|
+
// radio list, so its header has nothing to toggle.
|
|
585
653
|
const binding = bindingAt(draft, row.argIndex)
|
|
586
|
-
if (!binding || binding.kind !== "selection") return
|
|
654
|
+
if (!binding || binding.kind !== "selection" || binding.multi !== true) return
|
|
587
655
|
const all = (binding.items ?? []).length > 0 && (binding.items ?? []).every((item) => item.selected)
|
|
588
656
|
const items = (binding.items ?? []).map((item) => ({ ...item, selected: !all }))
|
|
589
657
|
session.panelDraft.set(draft.map((b) => (b.index === row.argIndex ? { ...b, items } : b)))
|
|
590
658
|
},
|
|
591
659
|
},
|
|
660
|
+
// File picker: a sub-mode of the panel that browses the filesystem for a
|
|
661
|
+
// path/destination argument. Lifecycle then navigation then choose.
|
|
592
662
|
{
|
|
593
|
-
id: "panel.
|
|
594
|
-
title: "
|
|
663
|
+
id: "panel.browse",
|
|
664
|
+
title: "Browse for a path",
|
|
595
665
|
kind: "ui",
|
|
596
666
|
hidden: true,
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
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)
|
|
667
|
+
run(session) {
|
|
668
|
+
// Browse the focused argument when it is pickable, else the first
|
|
669
|
+
// pickable argument of the operation; a no-op when there is none.
|
|
670
|
+
const focused = focusedBinding(session)
|
|
671
|
+
const binding = focused?.picker === true ? focused : firstPickableBinding(session)
|
|
672
|
+
if (!binding) return undefined
|
|
673
|
+
return openPicker(session, binding.index, binding)
|
|
612
674
|
},
|
|
613
675
|
},
|
|
614
676
|
{
|
|
615
|
-
id: "panel.
|
|
616
|
-
title: "
|
|
677
|
+
id: "panel.pick.up",
|
|
678
|
+
title: "Previous picker row",
|
|
617
679
|
kind: "ui",
|
|
618
680
|
hidden: true,
|
|
619
681
|
run(session) {
|
|
620
|
-
|
|
682
|
+
const length = session.pickerRows.value.length
|
|
683
|
+
session.panelRowIndex.set(moveCursor(session.panelRowIndex.value, -1, length))
|
|
621
684
|
},
|
|
622
685
|
},
|
|
623
686
|
{
|
|
624
|
-
id: "
|
|
625
|
-
title: "
|
|
626
|
-
keys: "^P",
|
|
687
|
+
id: "panel.pick.down",
|
|
688
|
+
title: "Next picker row",
|
|
627
689
|
kind: "ui",
|
|
628
|
-
|
|
690
|
+
hidden: true,
|
|
629
691
|
run(session) {
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
return
|
|
633
|
-
}
|
|
634
|
-
openPalette(session, "ui", "")
|
|
692
|
+
const length = session.pickerRows.value.length
|
|
693
|
+
session.panelRowIndex.set(moveCursor(session.panelRowIndex.value, 1, length))
|
|
635
694
|
},
|
|
636
695
|
},
|
|
637
696
|
{
|
|
638
|
-
id: "
|
|
639
|
-
title: "
|
|
697
|
+
id: "panel.pick.setCursor",
|
|
698
|
+
title: "Highlight picker row",
|
|
640
699
|
kind: "ui",
|
|
641
|
-
|
|
642
|
-
|
|
700
|
+
hidden: true,
|
|
701
|
+
run(session, payload) {
|
|
702
|
+
if (payload?.index !== undefined) session.panelRowIndex.set(Math.max(0, payload.index))
|
|
643
703
|
},
|
|
644
704
|
},
|
|
645
705
|
{
|
|
646
|
-
id: "
|
|
647
|
-
title: "
|
|
648
|
-
keys: "!",
|
|
706
|
+
id: "panel.pick.enter",
|
|
707
|
+
title: "Choose picker row",
|
|
649
708
|
kind: "ui",
|
|
709
|
+
hidden: true,
|
|
650
710
|
run(session) {
|
|
651
|
-
|
|
711
|
+
const entry = pickerEntry(session)
|
|
712
|
+
if (!entry) return undefined
|
|
713
|
+
// A directory descends unless this is a destination picker, where
|
|
714
|
+
// every row is already a valid choice.
|
|
715
|
+
if (entry.isDir && session.panelPickerMode.value !== "dir") {
|
|
716
|
+
return descendPicker(session, entry.path)
|
|
717
|
+
}
|
|
718
|
+
return choosePicker(session, entry.path)
|
|
652
719
|
},
|
|
653
720
|
},
|
|
654
721
|
{
|
|
655
|
-
id: "
|
|
656
|
-
title: "
|
|
657
|
-
keys: "/",
|
|
722
|
+
id: "panel.pick.descend",
|
|
723
|
+
title: "Open the picker's directory",
|
|
658
724
|
kind: "ui",
|
|
659
|
-
|
|
725
|
+
hidden: true,
|
|
660
726
|
run(session) {
|
|
661
|
-
|
|
727
|
+
const entry = pickerEntry(session)
|
|
728
|
+
if (entry?.isDir) return descendPicker(session, entry.path)
|
|
729
|
+
return undefined
|
|
662
730
|
},
|
|
663
731
|
},
|
|
664
732
|
{
|
|
665
|
-
id: "
|
|
666
|
-
title: "
|
|
733
|
+
id: "panel.pick.parent",
|
|
734
|
+
title: "Picker parent directory",
|
|
667
735
|
kind: "ui",
|
|
668
736
|
hidden: true,
|
|
669
|
-
run(session) {
|
|
670
|
-
session.
|
|
737
|
+
async run(session) {
|
|
738
|
+
session.pickerQuery.set("")
|
|
739
|
+
await session.pickerPane.up()
|
|
740
|
+
session.panelRowIndex.set(0)
|
|
671
741
|
},
|
|
672
742
|
},
|
|
673
743
|
{
|
|
674
|
-
id: "
|
|
675
|
-
title: "
|
|
744
|
+
id: "panel.pick.chooseDir",
|
|
745
|
+
title: "Choose the picker's directory",
|
|
676
746
|
kind: "ui",
|
|
677
747
|
hidden: true,
|
|
678
748
|
run(session) {
|
|
679
|
-
|
|
680
|
-
session.filtering.set(null)
|
|
749
|
+
return choosePicker(session, session.pickerPane.path.value)
|
|
681
750
|
},
|
|
682
751
|
},
|
|
683
752
|
{
|
|
684
|
-
id: "
|
|
685
|
-
title: "
|
|
753
|
+
id: "panel.pick.cancel",
|
|
754
|
+
title: "Leave the picker",
|
|
686
755
|
kind: "ui",
|
|
687
756
|
hidden: true,
|
|
688
757
|
run(session) {
|
|
689
|
-
|
|
758
|
+
// Back to the argument column, panel still open.
|
|
759
|
+
session.panelPicking.set(false)
|
|
760
|
+
session.panelPickerArg.set(-1)
|
|
761
|
+
session.panelFocus.set(PANEL_FOCUS.ARGS)
|
|
762
|
+
session.panelRowIndex.set(0)
|
|
690
763
|
},
|
|
691
764
|
},
|
|
692
765
|
{
|
|
693
|
-
id: "
|
|
694
|
-
title: "
|
|
766
|
+
id: "panel.run",
|
|
767
|
+
title: "Run command",
|
|
695
768
|
kind: "ui",
|
|
696
769
|
hidden: true,
|
|
697
|
-
run(session) {
|
|
698
|
-
|
|
770
|
+
async run(session) {
|
|
771
|
+
const entry = session.panelEntry.value
|
|
772
|
+
if (!entry) return
|
|
773
|
+
const op = entry.op
|
|
774
|
+
const draft = session.panelDraft.value
|
|
775
|
+
// An incomplete command does not fail: a blank destination/path
|
|
776
|
+
// asks where to (the picker opens and the run resumes on a choice —
|
|
777
|
+
// explorer mode has no DST pane to default from), and any other
|
|
778
|
+
// missing argument just takes the cursor, so Enter walks the
|
|
779
|
+
// command field by field.
|
|
780
|
+
const missing = missingBinding(draft)
|
|
781
|
+
if (missing) {
|
|
782
|
+
if (missing.kind === "dest" || missing.kind === "path") {
|
|
783
|
+
session.panelPendingRun.set(true)
|
|
784
|
+
return openPicker(session, missing.index, missing)
|
|
785
|
+
}
|
|
786
|
+
focusBinding(session, missing)
|
|
787
|
+
return
|
|
788
|
+
}
|
|
789
|
+
const status = await runOperation(session, op, argvFor(draft))
|
|
790
|
+
// A confirm keeps the panel open until the user answers; a run
|
|
791
|
+
// closes it. Errors stay open so the draft can be fixed.
|
|
792
|
+
if (status === "ran") closePanel(session)
|
|
699
793
|
},
|
|
700
794
|
},
|
|
701
795
|
{
|
|
702
|
-
id: "
|
|
703
|
-
title: "
|
|
796
|
+
id: "panel.cancel",
|
|
797
|
+
title: "Close command panel",
|
|
704
798
|
kind: "ui",
|
|
705
799
|
hidden: true,
|
|
706
800
|
run(session) {
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
801
|
+
closePanel(session)
|
|
802
|
+
},
|
|
803
|
+
},
|
|
804
|
+
{
|
|
805
|
+
id: "filter.start",
|
|
806
|
+
title: "Filter",
|
|
807
|
+
keys: "/",
|
|
808
|
+
kind: "ui",
|
|
809
|
+
bar: true,
|
|
810
|
+
run(session) {
|
|
811
|
+
// A column with no filter field (the inspector, or a column the
|
|
812
|
+
// current mode does not show) hands focus to the pane it follows
|
|
813
|
+
// before the input starts capturing keystrokes.
|
|
814
|
+
if (!hasFilterField(session, session.active.value)) activate(session, session.currentId.value)
|
|
815
|
+
session.filtering.set(session.active.value)
|
|
710
816
|
},
|
|
711
817
|
},
|
|
712
818
|
{
|
|
713
|
-
id: "
|
|
714
|
-
title: "
|
|
819
|
+
id: "filter.accept",
|
|
820
|
+
title: "Keep filter",
|
|
715
821
|
kind: "ui",
|
|
716
822
|
hidden: true,
|
|
717
|
-
run(session
|
|
718
|
-
session.
|
|
823
|
+
run(session) {
|
|
824
|
+
session.filtering.set(null)
|
|
719
825
|
},
|
|
720
826
|
},
|
|
721
827
|
{
|
|
722
|
-
id: "
|
|
723
|
-
title: "
|
|
828
|
+
id: "filter.clear",
|
|
829
|
+
title: "Clear filter",
|
|
724
830
|
kind: "ui",
|
|
725
831
|
hidden: true,
|
|
726
832
|
run(session) {
|
|
727
|
-
|
|
728
|
-
|
|
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
|
-
})
|
|
833
|
+
pane(session).clearFilter()
|
|
834
|
+
session.filtering.set(null)
|
|
739
835
|
},
|
|
740
836
|
},
|
|
741
837
|
{
|
|
@@ -772,7 +868,7 @@ const commands = [
|
|
|
772
868
|
// Quit: presentation-only. The key path intercepts `app.quit` before
|
|
773
869
|
// dispatch, and the command-bar click does the same (the renderer owns
|
|
774
870
|
// exiting); the registry entry exists so the bar and the `?` reference
|
|
775
|
-
// can advertise it. Hidden from the
|
|
871
|
+
// can advertise it. Hidden from the panel, where it could only no-op.
|
|
776
872
|
{
|
|
777
873
|
id: "app.quit",
|
|
778
874
|
title: "Quit",
|
|
@@ -799,34 +895,27 @@ function getCommand(id) {
|
|
|
799
895
|
return null
|
|
800
896
|
}
|
|
801
897
|
|
|
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
898
|
// Function: getCommandBarItems
|
|
809
|
-
// Command-bar rows for the current context: the
|
|
810
|
-
//
|
|
811
|
-
//
|
|
812
|
-
|
|
813
|
-
|
|
899
|
+
// Command-bar rows for the current context: the panel's key hints while it is
|
|
900
|
+
// open, the filter hints while a pane filter is capturing input, the bookmark
|
|
901
|
+
// hints while the picker owns the keyboard, the `bar`-flagged UI commands
|
|
902
|
+
// otherwise (keymap order). Takes the context as named fields — four adjacent
|
|
903
|
+
// flags are too easy to transpose.
|
|
904
|
+
function getCommandBarItems({ filtering, bookmarkOpen, bookmarkMode, panelOpen, panelPicking }) {
|
|
905
|
+
if (panelOpen) return panelPicking ? PANEL_PICK_KEYS : PANEL_HINT_KEYS
|
|
814
906
|
if (bookmarkOpen) return bookmarkMode === "name" ? BOOKMARK_NAME_KEYS : BOOKMARK_LIST_KEYS
|
|
815
|
-
if (paletteOpen) return paletteMode === "script" ? SCRIPT_KEYS : PALETTE_KEYS
|
|
816
907
|
if (filtering) return FILTER_KEYS
|
|
817
|
-
return commands.filter((command) => command.bar).map(
|
|
908
|
+
return commands.filter((command) => command.bar).map(barRow)
|
|
818
909
|
}
|
|
819
910
|
|
|
820
|
-
// Function:
|
|
821
|
-
// Projects a command to the small row object
|
|
822
|
-
function
|
|
911
|
+
// Function: barRow
|
|
912
|
+
// Projects a command to the small row object the command bar renders.
|
|
913
|
+
function barRow(command) {
|
|
823
914
|
return {
|
|
824
915
|
id: command.id,
|
|
825
916
|
title: command.title,
|
|
826
917
|
keys: command.keys,
|
|
827
|
-
kind: command.kind ?? "ui",
|
|
828
918
|
primary: command.primary === true,
|
|
829
|
-
completion: command.completion ?? command.title,
|
|
830
919
|
}
|
|
831
920
|
}
|
|
832
921
|
|
|
@@ -854,8 +943,33 @@ function fail(session, error) {
|
|
|
854
943
|
return null
|
|
855
944
|
}
|
|
856
945
|
|
|
946
|
+
// Function: isColumnPane
|
|
947
|
+
// Whether the focused column is backed by a rendered pane. The parent column
|
|
948
|
+
// only exists in explorer mode (focus is handed back on leaving it); the
|
|
949
|
+
// inspector column only while it previews a directory — over a file it is a
|
|
950
|
+
// read-only card with no listing to drive.
|
|
951
|
+
function isColumnPane(session, paneId) {
|
|
952
|
+
if (paneId !== "preview") return paneId !== "parent" || session.viewMode.value === "explorer"
|
|
953
|
+
return Boolean(session.showPreview.value) && Boolean(session.previewEntry.value?.isDir) && !session.preview.value
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
// Function: hasFilterField
|
|
957
|
+
// Whether the focused column renders a filter input. The inspector column
|
|
958
|
+
// never does — its header is the preview title — so `/` hands focus to the
|
|
959
|
+
// pane it shows before capturing keystrokes, the same way a nav key falls
|
|
960
|
+
// back to it.
|
|
961
|
+
function hasFilterField(session, paneId) {
|
|
962
|
+
return paneId !== "preview" && isColumnPane(session, paneId)
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
// Function: pane
|
|
966
|
+
// The pane cursor movement, marks and reload act on: the focused column's
|
|
967
|
+
// pane when it has one, else the current pane (so the inspector over a file
|
|
968
|
+
// keeps scrolling the directory it is showing).
|
|
857
969
|
function pane(session) {
|
|
858
|
-
|
|
970
|
+
const focused = session.panes[session.active.value]
|
|
971
|
+
if (!focused || !isColumnPane(session, session.active.value)) return currentPane(session)
|
|
972
|
+
return focused
|
|
859
973
|
}
|
|
860
974
|
|
|
861
975
|
// Function: activate
|
|
@@ -866,16 +980,50 @@ function activate(session, paneId) {
|
|
|
866
980
|
if (session.active.value !== paneId) session.active.set(paneId)
|
|
867
981
|
}
|
|
868
982
|
|
|
983
|
+
// Function: retargetFromParent
|
|
984
|
+
// A click on a row of the explorer's parent column retargets the current
|
|
985
|
+
// directory: a directory becomes it, a file opens its containing directory
|
|
986
|
+
// with the file selected so the inspector previews it. Both moves are
|
|
987
|
+
// recorded, so Esc steps back to where the click came from.
|
|
988
|
+
function retargetFromParent(session, parent, index) {
|
|
989
|
+
const entries = parent.entries.value
|
|
990
|
+
const entry = entries[clampCursor(index, entries.length)]
|
|
991
|
+
if (!entry) return
|
|
992
|
+
const current = currentPane(session)
|
|
993
|
+
if (entry.isDir) return current.navigate(entry.path)
|
|
994
|
+
return current
|
|
995
|
+
.navigate(parent.path.value, { cursorPath: entry.path })
|
|
996
|
+
.then(() => pinPreview(session, current))
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
// Function: retargetFromPreview
|
|
1000
|
+
// A click on a row of the explorer's preview column promotes the previewed
|
|
1001
|
+
// directory to the current one, selecting the clicked entry: a directory then
|
|
1002
|
+
// previews its own contents (the next click descends further), a file previews
|
|
1003
|
+
// as a card.
|
|
1004
|
+
function retargetFromPreview(session, preview, index) {
|
|
1005
|
+
const entries = preview.entries.value
|
|
1006
|
+
const entry = entries[clampCursor(index, entries.length)]
|
|
1007
|
+
if (!entry) return
|
|
1008
|
+
const current = currentPane(session)
|
|
1009
|
+
return current.navigate(preview.path.value, { cursorPath: entry.path }).then(() => {
|
|
1010
|
+
if (!entry.isDir) pinPreview(session, current)
|
|
1011
|
+
})
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
// Function: pinPreview
|
|
1015
|
+
// Points the inspector at the pane's cursor entry, so a file a click just
|
|
1016
|
+
// revealed previews even if the follow effect lags the navigation.
|
|
1017
|
+
function pinPreview(session, pane) {
|
|
1018
|
+
session.previewTarget.set(pane.currentEntry() ?? null)
|
|
1019
|
+
session.showPreview.set(true)
|
|
1020
|
+
}
|
|
1021
|
+
|
|
869
1022
|
function bump(session, delta) {
|
|
870
1023
|
const p = pane(session)
|
|
871
1024
|
p.cursor.set(moveCursor(p.cursor.value, delta, p.entries.value.length))
|
|
872
1025
|
}
|
|
873
1026
|
|
|
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
1027
|
function moveBookmark(session, delta) {
|
|
880
1028
|
const length = session.bookmarkItems.value.length
|
|
881
1029
|
session.bookmarkIndex.set(moveCursor(session.bookmarkIndex.value, delta, length))
|
|
@@ -885,46 +1033,140 @@ function closeBookmark(session) {
|
|
|
885
1033
|
session.bookmarkOpen.set(false)
|
|
886
1034
|
}
|
|
887
1035
|
|
|
888
|
-
|
|
1036
|
+
// Function: focusedBinding
|
|
1037
|
+
// The draft binding under the right pane's cursor (an argument header or one
|
|
1038
|
+
// of its item rows), or null on the command filter.
|
|
1039
|
+
function focusedBinding(session) {
|
|
1040
|
+
if (session.panelFocus.value !== PANEL_FOCUS.ARGS) return null
|
|
1041
|
+
const row = rowAt(session.panelRows.value, session.panelRowIndex.value)
|
|
1042
|
+
return row ? bindingAt(session.panelDraft.value, row.argIndex) : null
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
// Function: firstPickableBinding
|
|
1046
|
+
// The first browsable (`picker`) binding of the draft, so `^O` works from the
|
|
1047
|
+
// command filter too.
|
|
1048
|
+
function firstPickableBinding(session) {
|
|
1049
|
+
return (session.panelDraft.value ?? []).find((binding) => binding.picker === true) ?? null
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
// Function: focusBinding
|
|
1053
|
+
// Moves the cursor onto the argument header row of the binding Enter found
|
|
1054
|
+
// missing.
|
|
1055
|
+
function focusBinding(session, binding) {
|
|
1056
|
+
const rowIndex = (session.panelRows.value ?? []).findIndex(
|
|
1057
|
+
(row) => row.type === "arg" && row.argIndex === binding.index,
|
|
1058
|
+
)
|
|
1059
|
+
session.panelFocus.set(PANEL_FOCUS.ARGS)
|
|
1060
|
+
session.panelRowIndex.set(rowIndex >= 0 ? rowIndex : 0)
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
// Function: openPanel
|
|
1064
|
+
// Opens the command panel fresh: empty query, first entry selected, the entry
|
|
1065
|
+
// column focused. `options.name` selects a CLI operation by name — `!` lands on
|
|
1066
|
+
// `run` — and `options.focus` moves the focus ring to the argument column so a
|
|
1067
|
+
// raw or literal argument owns the keyboard right away.
|
|
1068
|
+
function openPanel(session, options = {}) {
|
|
889
1069
|
session.panelQuery.set("")
|
|
890
1070
|
session.panelOpIndex.set(0)
|
|
891
1071
|
session.panelRowIndex.set(0)
|
|
892
1072
|
session.panelFocus.set(PANEL_FOCUS.OPS)
|
|
1073
|
+
session.panelPicking.set(false)
|
|
1074
|
+
session.panelPickerArg.set(-1)
|
|
1075
|
+
session.panelPendingRun.set(false)
|
|
893
1076
|
session.panelOpen.set(true)
|
|
1077
|
+
if (!options.name) return
|
|
1078
|
+
// Select the operation by index rather than by query, so the filter stays
|
|
1079
|
+
// empty and the list keeps its full ordering. A missing name is a bug in
|
|
1080
|
+
// the caller (a renamed operation), not something to silently paper over by
|
|
1081
|
+
// landing on the first entry.
|
|
1082
|
+
const index = session.panelFiltered.value.findIndex((entry) => entry.name === options.name)
|
|
1083
|
+
if (index < 0) throw new Error(`command panel has no entry named "${options.name}"`)
|
|
1084
|
+
session.panelOpIndex.set(index)
|
|
1085
|
+
if (options.focus === PANEL_FOCUS.ARGS) {
|
|
1086
|
+
session.panelFocus.set(PANEL_FOCUS.ARGS)
|
|
1087
|
+
session.panelRowIndex.set(0)
|
|
1088
|
+
}
|
|
894
1089
|
}
|
|
895
1090
|
|
|
896
|
-
// Function:
|
|
897
|
-
//
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
1091
|
+
// Function: pickerEntry
|
|
1092
|
+
// The entry under the picker cursor, or null (empty listing / stale cursor).
|
|
1093
|
+
function pickerEntry(session) {
|
|
1094
|
+
const rows = session.pickerRows.value
|
|
1095
|
+
return rows[clampCursor(session.panelRowIndex.value, rows.length)] ?? null
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
// Function: openPicker
|
|
1099
|
+
// Opens the panel's file picker on a path argument: the argument column
|
|
1100
|
+
// switches to a browsable listing seated on the argument's current value (a
|
|
1101
|
+
// directory opens there, a file opens in its parent with the file under the
|
|
1102
|
+
// cursor), else on the current pane's directory. Focus moves to the argument
|
|
1103
|
+
// column so the picker's own input owns the keyboard.
|
|
1104
|
+
async function openPicker(session, argIndex, binding) {
|
|
1105
|
+
const mode = binding?.mode ?? "dir"
|
|
1106
|
+
session.panelPickerMode.set(mode)
|
|
1107
|
+
session.panelPickerArg.set(argIndex)
|
|
1108
|
+
session.pickerQuery.set("")
|
|
1109
|
+
session.panelPicking.set(true)
|
|
1110
|
+
session.panelFocus.set(PANEL_FOCUS.ARGS)
|
|
1111
|
+
let startPath = currentPane(session)?.path?.value ?? session.fs.cwd?.() ?? "/"
|
|
1112
|
+
let cursorPath = null
|
|
1113
|
+
const value = `${binding?.value ?? ""}`.trim()
|
|
1114
|
+
if (value) {
|
|
1115
|
+
const stats = await session.fs.stat(value)
|
|
1116
|
+
if (stats?.isDir) startPath = value
|
|
1117
|
+
else {
|
|
1118
|
+
startPath = parentOf(value) ?? "/"
|
|
1119
|
+
cursorPath = value
|
|
1120
|
+
}
|
|
904
1121
|
}
|
|
905
|
-
|
|
1122
|
+
await session.pickerPane.navigate(startPath, { record: false, cursorPath })
|
|
1123
|
+
const rows = session.pickerRows.value
|
|
1124
|
+
const index = cursorPath ? rows.findIndex((row) => row.path === cursorPath) : -1
|
|
1125
|
+
session.panelRowIndex.set(index >= 0 ? index : 0)
|
|
906
1126
|
}
|
|
907
1127
|
|
|
908
|
-
|
|
909
|
-
|
|
1128
|
+
// Function: descendPicker
|
|
1129
|
+
// Moves the picker into `path`, clearing its filter and re-anchoring the
|
|
1130
|
+
// cursor at the top of the fresh listing.
|
|
1131
|
+
async function descendPicker(session, path) {
|
|
1132
|
+
if (!path) return undefined
|
|
1133
|
+
session.pickerQuery.set("")
|
|
1134
|
+
await session.pickerPane.navigate(path, { record: false })
|
|
1135
|
+
session.panelRowIndex.set(0)
|
|
1136
|
+
return undefined
|
|
910
1137
|
}
|
|
911
1138
|
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
1139
|
+
// Function: choosePicker
|
|
1140
|
+
// Accepts `path` as the picked value: writes it into the argument, leaves the
|
|
1141
|
+
// picker (the panel stays open on the argument column) and, when the picker was
|
|
1142
|
+
// opened to satisfy a pending run, runs the operation now.
|
|
1143
|
+
function choosePicker(session, path) {
|
|
1144
|
+
if (path == null) return undefined
|
|
1145
|
+
const argIndex = session.panelPickerArg.value
|
|
1146
|
+
session.panelDraft.set(withArgValue(session.panelDraft.value, argIndex, path))
|
|
1147
|
+
session.panelPicking.set(false)
|
|
1148
|
+
session.panelPickerArg.set(-1)
|
|
1149
|
+
session.panelFocus.set(PANEL_FOCUS.ARGS)
|
|
1150
|
+
const rows = session.panelRows.value
|
|
1151
|
+
const index = rows.findIndex((row) => row.type === "arg" && row.argIndex === argIndex)
|
|
1152
|
+
session.panelRowIndex.set(index >= 0 ? index : 0)
|
|
1153
|
+
if (session.panelPendingRun.value) {
|
|
1154
|
+
session.panelPendingRun.set(false)
|
|
1155
|
+
return session.dispatch("panel.run")
|
|
1156
|
+
}
|
|
1157
|
+
return undefined
|
|
917
1158
|
}
|
|
918
1159
|
|
|
919
|
-
function
|
|
920
|
-
session.
|
|
1160
|
+
function closePanel(session) {
|
|
1161
|
+
session.panelOpen.set(false)
|
|
921
1162
|
}
|
|
922
1163
|
|
|
923
1164
|
export {
|
|
924
|
-
|
|
1165
|
+
barRow,
|
|
1166
|
+
closePanel,
|
|
925
1167
|
createDispatch,
|
|
926
1168
|
getCommand,
|
|
927
1169
|
getCommandBarItems,
|
|
928
1170
|
getCommands,
|
|
929
|
-
|
|
1171
|
+
openPanel,
|
|
930
1172
|
}
|