@ssebastien/tcmd 0.1.3 → 0.1.7
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 +256 -62
- package/bin/tcmd +10 -2
- package/bin/tcmd.js +13 -2
- package/desktop/dist/index.html +13 -0
- package/desktop/dist/main.js +20298 -0
- package/desktop/dist/style.css +1515 -0
- package/desktop/frontend/index.html +13 -0
- package/package.json +19 -6
- package/public/fonts/OFL.txt +93 -0
- package/public/index.html +14 -0
- package/public/licenses/LUCIDE.txt +43 -0
- package/public/main.js +20679 -0
- package/public/style.css +1515 -0
- package/scripts/build-desktop.js +31 -0
- package/scripts/serve-web.js +61 -0
- package/src/js/tcmd/components/App.html +80 -14
- package/src/js/tcmd/components/App.js +339 -114
- package/src/js/tcmd/components/App.xml +76 -22
- package/src/js/tcmd/components/BookmarkList.html +5 -5
- package/src/js/tcmd/components/BookmarkList.js +15 -41
- package/src/js/tcmd/components/BookmarkList.xml +1 -2
- package/src/js/tcmd/components/BookmarkRow.html +2 -1
- package/src/js/tcmd/components/BookmarkRow.js +14 -9
- package/src/js/tcmd/components/BookmarkRow.xml +1 -1
- package/src/js/tcmd/components/Breadcrumbs.js +1 -1
- package/src/js/tcmd/components/CommandArgRow.html +28 -0
- package/src/js/tcmd/components/CommandArgRow.js +146 -30
- package/src/js/tcmd/components/CommandArgRow.xml +55 -4
- package/src/js/tcmd/components/CommandBar.html +2 -2
- package/src/js/tcmd/components/CommandBar.js +33 -27
- package/src/js/tcmd/components/CommandBar.xml +2 -2
- package/src/js/tcmd/components/CommandItem.html +1 -1
- package/src/js/tcmd/components/CommandItem.js +25 -25
- package/src/js/tcmd/components/CommandItem.xml +2 -2
- package/src/js/tcmd/components/CommandPanel.html +29 -0
- package/src/js/tcmd/components/CommandPanel.js +78 -52
- package/src/js/tcmd/components/CommandPanel.xml +49 -25
- package/src/js/tcmd/components/Confirm.html +2 -2
- package/src/js/tcmd/components/EntryDetail.html +7 -7
- package/src/js/tcmd/components/EntryDetail.js +22 -8
- package/src/js/tcmd/components/FileList.html +5 -1
- package/src/js/tcmd/components/FileList.js +2 -1
- package/src/js/tcmd/components/FilePicker.html +10 -0
- package/src/js/tcmd/components/FilePicker.js +43 -0
- package/src/js/tcmd/components/FilePicker.xml +13 -0
- package/src/js/tcmd/components/FilePickerRow.html +6 -0
- package/src/js/tcmd/components/FilePickerRow.js +27 -0
- package/src/js/tcmd/components/FilePickerRow.xml +10 -0
- package/src/js/tcmd/components/FilePreview.html +14 -4
- package/src/js/tcmd/components/FilePreview.js +111 -12
- package/src/js/tcmd/components/FilePreview.xml +28 -8
- package/src/js/tcmd/components/FileRow.html +2 -1
- package/src/js/tcmd/components/FileRow.js +70 -12
- 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/components/Frame.js +324 -0
- package/src/js/tcmd/components/Help.html +15 -0
- package/src/js/tcmd/components/Help.js +260 -0
- package/src/js/tcmd/components/Help.xml +13 -4
- package/src/js/tcmd/components/HelpRow.html +8 -0
- package/src/js/tcmd/components/HelpRow.xml +7 -0
- package/src/js/tcmd/components/OverlayList.html +8 -0
- package/src/js/tcmd/components/OverlayList.js +69 -0
- package/src/js/tcmd/components/{CommandPalette.xml → OverlayList.xml} +2 -5
- package/src/js/tcmd/components/OverlayRow.html +5 -0
- package/src/js/tcmd/components/OverlayRow.js +47 -0
- package/src/js/tcmd/components/OverlayRow.xml +9 -0
- package/src/js/tcmd/components/Summary.html +1 -1
- package/src/js/tcmd/components/Summary.js +1 -6
- package/src/js/tcmd/components/format.js +4 -23
- package/src/js/tcmd/components/icons.js +176 -5
- package/src/js/tcmd/components/index.js +9 -5
- package/src/js/tcmd/components/rows.js +176 -48
- package/src/js/tcmd/core/base64.js +64 -0
- package/src/js/tcmd/core/bookmarks.js +45 -8
- package/src/js/tcmd/core/commands.js +978 -260
- package/src/js/tcmd/core/concurrency.js +59 -0
- package/src/js/tcmd/core/decompress.js +117 -0
- package/src/js/tcmd/core/directory.js +219 -93
- package/src/js/tcmd/core/format.js +36 -0
- package/src/js/tcmd/core/fs.js +438 -54
- package/src/js/tcmd/core/history.js +58 -0
- package/src/js/tcmd/core/index.js +3 -9
- package/src/js/tcmd/core/journal.js +93 -0
- package/src/js/tcmd/core/keymap.js +42 -6
- package/src/js/tcmd/core/operations.js +813 -48
- package/src/js/tcmd/core/os.js +29 -3
- package/src/js/tcmd/core/pane.js +33 -8
- package/src/js/tcmd/core/panel.js +280 -104
- package/src/js/tcmd/core/path.js +30 -3
- package/src/js/tcmd/core/permissions.js +107 -0
- package/src/js/tcmd/core/picker.js +156 -0
- package/src/js/tcmd/core/preview.js +102 -0
- 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 +498 -200
- package/src/js/tcmd/core/watch.js +153 -0
- package/src/js/tcmd/desktop/backend/api.js +165 -0
- package/src/js/tcmd/desktop/backend/fs.js +626 -0
- package/src/js/tcmd/desktop/backend/main.ts +9 -0
- package/src/js/tcmd/desktop/bookmarks.js +23 -0
- package/src/js/tcmd/desktop/fs.js +91 -0
- package/src/js/tcmd/desktop/main.js +194 -0
- package/src/js/tcmd/desktop/os.js +33 -0
- package/src/js/tcmd/index.js +0 -1
- package/src/js/tcmd/tui/app.js +118 -60
- package/src/js/tcmd/tui/catalogue.js +238 -42
- package/src/js/tcmd/tui/index.js +18 -19
- package/src/js/tcmd/tui/opentui.js +33 -0
- package/src/js/tcmd/tui/syntax.js +91 -0
- package/src/js/tcmd/tui/theme.js +32 -2
- package/src/js/tcmd/vendor/bzip2.js +276 -0
- package/src/js/tcmd/web/app.js +225 -41
- package/src/js/tcmd/web/bookmarks.js +36 -0
- package/src/js/tcmd/web/catalogue.js +81 -38
- package/src/js/tcmd/web/effects.js +223 -0
- package/src/js/tcmd/web/header.html +1 -1
- package/src/js/tcmd/web/header.js +15 -11
- package/src/js/tcmd/web/http.js +13 -0
- package/src/js/tcmd/web/index.js +2 -2
- package/src/js/tcmd/web/main.js +6 -66
- package/src/js/tcmd/web/opfs.js +297 -30
- package/src/js/tcmd/web/seed.js +4 -4
- package/src/js/tcmd/web/server.js +124 -21
- package/src/js/tcmd/web/theme.js +4 -0
- package/tinyjs.json +16 -0
- package/src/js/tcmd/components/CommandPalette.html +0 -7
- package/src/js/tcmd/components/CommandPalette.js +0 -58
- 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/frame.js +0 -269
- package/src/js/tcmd/tui/help.js +0 -32
package/src/js/tcmd/web/theme.js
CHANGED
|
@@ -14,6 +14,10 @@ const webTheme = {
|
|
|
14
14
|
currentClass: "is-current",
|
|
15
15
|
dirClass: "is-dir",
|
|
16
16
|
errorClass: "is-error",
|
|
17
|
+
execClass: "is-exec",
|
|
18
|
+
focusedClass: "is-focused",
|
|
19
|
+
lucideIcons: true,
|
|
20
|
+
readonlyClass: "is-readonly",
|
|
17
21
|
rootClass: "is-root",
|
|
18
22
|
selectedClass: "is-selected",
|
|
19
23
|
}
|
package/tinyjs.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "tcmd",
|
|
3
|
+
"title": "TinyCommand",
|
|
4
|
+
"id": "io.github.sebastien.tcmd",
|
|
5
|
+
"version": "0.1.4",
|
|
6
|
+
"minTinyjsVersion": "0.57.0",
|
|
7
|
+
"size": "1280x800",
|
|
8
|
+
"chrome": { "menu": false },
|
|
9
|
+
"backend": "src/js/tcmd/desktop/backend/main.ts",
|
|
10
|
+
"openFolders": true,
|
|
11
|
+
"frontend": {
|
|
12
|
+
"build": "bun run desktop:bundle",
|
|
13
|
+
"dir": "desktop/dist",
|
|
14
|
+
"dist": "desktop/dist"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
<section class="palette" when="open">
|
|
2
|
-
<div class="palette-line">
|
|
3
|
-
<span class="palette-sign" out="sign"></span>
|
|
4
|
-
<input ref="query" inout="query" class="palette-input" placeholder="Command..." on:enter="confirm" />
|
|
5
|
-
</div>
|
|
6
|
-
<ul class="palette-list" out="rows"></ul>
|
|
7
|
-
</section>
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
// Project: TinyCommand
|
|
2
|
-
// Author: Sebastien Pierre
|
|
3
|
-
// License: BSD
|
|
4
|
-
// Created: 2026-09-26
|
|
5
|
-
|
|
6
|
-
// Module: tcmd/components/CommandPalette
|
|
7
|
-
// CommandPalette: popup listing commands filtered by the typed query. In
|
|
8
|
-
// scripting mode it lists operations and filesets and prefixes the input with
|
|
9
|
-
// `:`. The row logic lives in `PaletteRow.js`; this parent maps its `items` to
|
|
10
|
-
// rows.
|
|
11
|
-
//
|
|
12
|
-
// Environment: `overlayBg`, `borderPrimaryActiveFg`, `textSecondaryFg`
|
|
13
|
-
// (footer), `textHeadingFg` (exposed as the `titleColor` behavior),
|
|
14
|
-
// `rows(data)` returning the keyed rows and `scrollTop(self, data)` (TUI
|
|
15
|
-
// viewport tracking).
|
|
16
|
-
|
|
17
|
-
import { unwrap } from "select/state/cells"
|
|
18
|
-
|
|
19
|
-
// Function: paletteSign
|
|
20
|
-
// The prompt shown before the input; overridable per renderer.
|
|
21
|
-
function paletteSign(mode) {
|
|
22
|
-
return unwrap(mode) === "script" ? ":" : "›"
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// Function: paletteTitle
|
|
26
|
-
function paletteTitle(mode) {
|
|
27
|
-
return unwrap(mode) === "script" ? "script" : "palette"
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Function: createBehaviors
|
|
31
|
-
// Builds the CommandPalette behavior map for the given renderer environment.
|
|
32
|
-
function createBehaviors(env) {
|
|
33
|
-
const behaviors = {
|
|
34
|
-
bg: () => env.overlayBg,
|
|
35
|
-
borderFg: () => env.borderPrimaryActiveFg,
|
|
36
|
-
confirm: (self, data) => self.pub("palette:confirm", unwrap(data.query) ?? ""),
|
|
37
|
-
focused: (_self, data) => Boolean(unwrap(data.open)),
|
|
38
|
-
footer: (_self, data) =>
|
|
39
|
-
unwrap(data.mode) === "script"
|
|
40
|
-
? "Tab complete ↵ run Esc close"
|
|
41
|
-
: "↑↓ move Tab complete ↵ run Esc close",
|
|
42
|
-
footerFg: () => env.textSecondaryFg,
|
|
43
|
-
sign: (_self, data) => paletteSign(data.mode),
|
|
44
|
-
title: (_self, data) => paletteTitle(data.mode),
|
|
45
|
-
titleColor: () => env.textHeadingFg,
|
|
46
|
-
}
|
|
47
|
-
if (env?.rows) {
|
|
48
|
-
behaviors.rows = (_self, data) => env.rows(self, data)
|
|
49
|
-
}
|
|
50
|
-
if (env?.scrollTop) {
|
|
51
|
-
behaviors.scrollTop = (self, data) => env.scrollTop(self, data)
|
|
52
|
-
}
|
|
53
|
-
return behaviors
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export { createBehaviors, paletteSign, paletteTitle }
|
|
57
|
-
|
|
58
|
-
// EOF
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
// Project: TinyCommand
|
|
2
|
-
// Author: Sebastien Pierre
|
|
3
|
-
// License: BSD
|
|
4
|
-
// Created: 2026-09-26
|
|
5
|
-
|
|
6
|
-
// Module: tcmd/components/PaletteRow
|
|
7
|
-
// Command-palette row behaviors. Read `{ item: command, index, selected }`, the
|
|
8
|
-
// same bag shape in both engines. Selection is snapshotted by the row builder
|
|
9
|
-
// into a plain `selectedRow` boolean and read through the shared `isSelected`
|
|
10
|
-
// predicate, with the `selected` cell as fallback for hand-built bags. Script
|
|
11
|
-
// rows carry a `detail` (operation signature, fileset count) appended to the
|
|
12
|
-
// label.
|
|
13
|
-
//
|
|
14
|
-
// Environment: `accentFg`, `textFg`, `selectionBg` (TUI row background),
|
|
15
|
-
// `selectedClass` (web).
|
|
16
|
-
|
|
17
|
-
import { isSelected } from "./rows.js"
|
|
18
|
-
|
|
19
|
-
// Function: paletteLabel
|
|
20
|
-
// "Switch [Tab]" for commands; script rows append their detail.
|
|
21
|
-
function paletteLabel(command) {
|
|
22
|
-
if (!command) return ""
|
|
23
|
-
const keys = command.keys ? ` [${command.keys}]` : ""
|
|
24
|
-
const detail = command.detail ? ` ${command.detail}` : ""
|
|
25
|
-
return `${command.title}${keys}${detail}`
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Function: createBehaviors
|
|
29
|
-
// Builds the PaletteRow behavior map for the given renderer environment.
|
|
30
|
-
function createBehaviors(env) {
|
|
31
|
-
return {
|
|
32
|
-
paletteLabel: (_self, data) => paletteLabel(data.item),
|
|
33
|
-
rowBg: (_self, data) => (isSelected(data) ? env.selectionBg : undefined),
|
|
34
|
-
rowClass: (_self, data) => (isSelected(data) ? env.selectedClass : null),
|
|
35
|
-
rowFg: (_self, data) => (isSelected(data) ? env.accentFg : env.textFg),
|
|
36
|
-
// TUI: a marker so the selection is not color-only.
|
|
37
|
-
rowMarker: (_self, data) => (isSelected(data) ? ">" : " "),
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export { createBehaviors, paletteLabel }
|
|
42
|
-
|
|
43
|
-
// EOF
|
package/src/js/tcmd/tui/frame.js
DELETED
|
@@ -1,269 +0,0 @@
|
|
|
1
|
-
// Project: TinyCommand
|
|
2
|
-
// Author: Sebastien Pierre
|
|
3
|
-
// License: BSD
|
|
4
|
-
// Created: 2026-10-02
|
|
5
|
-
|
|
6
|
-
// Module: tcmd/tui/frame
|
|
7
|
-
// TUI-only presentation behaviors for the workspace chrome: the responsive
|
|
8
|
-
// breakpoints driven by the `uiSize` cell, the pane borders, header rules,
|
|
9
|
-
// the command-bar geometry and the help overlay state. Everything here is
|
|
10
|
-
// injected into the App binding; none of it lives in the shared
|
|
11
|
-
// `components/` logic.
|
|
12
|
-
|
|
13
|
-
import { unwrap } from "select/state/cells"
|
|
14
|
-
import { DEFAULT_KEYMAP } from "../core/keymap.js"
|
|
15
|
-
import { getCommands } from "../core/commands.js"
|
|
16
|
-
import { commandBarMarksItem, commandBarRowCount, commandBarStatus } from "../components/CommandBar.js"
|
|
17
|
-
|
|
18
|
-
// Below this the two-pane workspace is replaced by a resize hint.
|
|
19
|
-
const MIN_WIDTH = 64
|
|
20
|
-
const MIN_HEIGHT = 16
|
|
21
|
-
// At or above this the inspector can sit beside the panes; below it becomes a
|
|
22
|
-
// fullscreen overlay.
|
|
23
|
-
const INSPECTOR_INLINE_WIDTH = 100
|
|
24
|
-
|
|
25
|
-
// Function: sizeOf
|
|
26
|
-
// Reads the viewport size from the App data, falling back to the renderer's
|
|
27
|
-
// own dimensions so the gates work even when no `uiSize` cell is injected
|
|
28
|
-
// (tests, web). Returns `null` when neither is known.
|
|
29
|
-
function sizeOf(data, self) {
|
|
30
|
-
const size = unwrap(data?.uiSize)
|
|
31
|
-
if (size && (size.width || size.height)) return size
|
|
32
|
-
const width = Math.floor(self?.ctx?.width ?? 0)
|
|
33
|
-
const height = Math.floor(self?.ctx?.height ?? 0)
|
|
34
|
-
if (!width && !height) return null
|
|
35
|
-
return { width, height }
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// Function: isNarrow
|
|
39
|
-
// True when the viewport is known and too narrow for the inline inspector.
|
|
40
|
-
function isNarrow(data, self) {
|
|
41
|
-
const size = sizeOf(data, self)
|
|
42
|
-
return Boolean(size && (size.width ?? 0) > 0 && size.width < INSPECTOR_INLINE_WIDTH)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Function: commandBarRowsFor
|
|
46
|
-
// The entries the bar currently shows: the context hints or the `bar`
|
|
47
|
-
// commands, plus the active pane's marked count as a trailing entry.
|
|
48
|
-
function commandBarRowsFor(data) {
|
|
49
|
-
const items = unwrap(data?.commandBarItems) ?? []
|
|
50
|
-
const marks = commandBarMarksItem(data)
|
|
51
|
-
return marks ? [...items, marks] : items
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Function: commandBarLines
|
|
55
|
-
// Rows the wrapped command bar occupies at the current viewport (the bar
|
|
56
|
-
// is inset one column from the viewport edge).
|
|
57
|
-
function commandBarLines(data, self) {
|
|
58
|
-
const width = Math.floor(sizeOf(data, self)?.width ?? 0)
|
|
59
|
-
if (width <= 0) return 1
|
|
60
|
-
return commandBarRowCount(commandBarRowsFor(data), width - 1)
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Function: paneListHeight
|
|
64
|
-
// Rows available to a pane's listing: the viewport height minus the identity
|
|
65
|
-
// bar (1), the pane's own border (2), the pane header + rule (2), the entry
|
|
66
|
-
// detail (2), the summary (1) and the wrapped command bar — eight chrome rows
|
|
67
|
-
// plus the bar. Fed to the windowed `filelist` so a large directory mounts
|
|
68
|
-
// only the visible slice. `0` means "unknown — render everything to measure".
|
|
69
|
-
function paneListHeight(data, self) {
|
|
70
|
-
const height = Math.floor(sizeOf(data, self)?.height ?? 0)
|
|
71
|
-
if (height <= 0) return 0
|
|
72
|
-
// A notice or error swaps the keys for a one-line status.
|
|
73
|
-
const bar = commandBarStatus(data) ? 1 : commandBarLines(data, self)
|
|
74
|
-
return Math.max(1, height - 8 - bar)
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Function: mixHex
|
|
78
|
-
// Linear interpolation between two `#rrggbb` colours at `t` in [0, 1]. A
|
|
79
|
-
// missing colour (the mono theme) yields `undefined` so the whole run falls
|
|
80
|
-
// back to the terminal default.
|
|
81
|
-
function mixHex(from, to, t) {
|
|
82
|
-
if (!from || !to) return undefined
|
|
83
|
-
const channel = (index) => {
|
|
84
|
-
const a = Number.parseInt(from.slice(index, index + 2), 16)
|
|
85
|
-
const b = Number.parseInt(to.slice(index, index + 2), 16)
|
|
86
|
-
return Math.round(a + (b - a) * t)
|
|
87
|
-
.toString(16)
|
|
88
|
-
.padStart(2, "0")
|
|
89
|
-
}
|
|
90
|
-
return `#${channel(1)}${channel(3)}${channel(5)}`
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Function: shineOf
|
|
94
|
-
// One `{ letter, fg }` per letter of a brand word, interpolating `from` → `to`
|
|
95
|
-
// across it so the word reads as a gradient; a missing colour (mono theme)
|
|
96
|
-
// leaves every `fg` undefined and the run falls back to the terminal default.
|
|
97
|
-
function shineOf(letters, from, to) {
|
|
98
|
-
return `${letters}`.split("").map((letter, index, all) => ({
|
|
99
|
-
letter,
|
|
100
|
-
fg: mixHex(from, to, index / (all.length - 1)),
|
|
101
|
-
}))
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Function: createFrameBehaviors
|
|
105
|
-
// Behaviors for the App root and its chrome.
|
|
106
|
-
function createFrameBehaviors(env) {
|
|
107
|
-
const tiny = shineOf("Tiny", env.theme.brandTinyFrom, env.theme.brandTinyTo)
|
|
108
|
-
const cmd = shineOf("Cmd", env.theme.brandCmdFrom, env.theme.brandCmdTo)
|
|
109
|
-
return {
|
|
110
|
-
// Identity bar: "Tiny" shimmers along the brand blues, "Cmd" along a
|
|
111
|
-
// brighter orange than the mark accent, and the host trail follows in
|
|
112
|
-
// the heading colour. Everything else on screen belongs to the user's
|
|
113
|
-
// files; this one row is the app's own.
|
|
114
|
-
brandT: () => tiny[0].letter,
|
|
115
|
-
brandTFg: () => tiny[0].fg,
|
|
116
|
-
brandI: () => tiny[1].letter,
|
|
117
|
-
brandIFg: () => tiny[1].fg,
|
|
118
|
-
brandN: () => tiny[2].letter,
|
|
119
|
-
brandNFg: () => tiny[2].fg,
|
|
120
|
-
brandY: () => tiny[3].letter,
|
|
121
|
-
brandYFg: () => tiny[3].fg,
|
|
122
|
-
brandC: () => cmd[0].letter,
|
|
123
|
-
brandCFg: () => cmd[0].fg,
|
|
124
|
-
brandM: () => cmd[1].letter,
|
|
125
|
-
brandMFg: () => cmd[1].fg,
|
|
126
|
-
brandD: () => cmd[2].letter,
|
|
127
|
-
brandDFg: () => cmd[2].fg,
|
|
128
|
-
topBar: (_self, data) => {
|
|
129
|
-
const host = `${unwrap(data.appHost) ?? ""}`.trim()
|
|
130
|
-
return host ? ` - ${host} ` : " "
|
|
131
|
-
},
|
|
132
|
-
topBarFg: () => env.theme.textHeadingFg,
|
|
133
|
-
|
|
134
|
-
// Responsive gates. `uiSize` is absent in tests and the web, so the
|
|
135
|
-
// workspace renders normally there.
|
|
136
|
-
tooSmall: (self, data) => {
|
|
137
|
-
const size = sizeOf(data, self)
|
|
138
|
-
if (!size) return false
|
|
139
|
-
return (size.width ?? MIN_WIDTH) < MIN_WIDTH || (size.height ?? MIN_HEIGHT) < MIN_HEIGHT
|
|
140
|
-
},
|
|
141
|
-
minSizeTitle: () => "Terminal too small",
|
|
142
|
-
minSizeHint: (self, data) => {
|
|
143
|
-
const size = sizeOf(data, self) ?? { width: 0, height: 0 }
|
|
144
|
-
return `need ${MIN_WIDTH}x${MIN_HEIGHT}, have ${size.width ?? 0}x${size.height ?? 0}`
|
|
145
|
-
},
|
|
146
|
-
// The min-size gate text uses the heading/secondary text tokens.
|
|
147
|
-
titleFg: () => env.theme.textHeadingFg,
|
|
148
|
-
dimFg: () => env.theme.textSecondaryFg,
|
|
149
|
-
|
|
150
|
-
// Each pane is a complete box. Both draw all four edges (the middle
|
|
151
|
-
// shows two adjacent rules) so each pane's border colour can track its
|
|
152
|
-
// own focus independently — a shared edge would be owned by one pane
|
|
153
|
-
// and never highlight when the other is active.
|
|
154
|
-
leftBorder: () => ["top", "left", "bottom", "right"],
|
|
155
|
-
rightBorder: () => ["top", "left", "bottom", "right"],
|
|
156
|
-
// The header rule separates the pane header row from the listing body.
|
|
157
|
-
headRule: () => ["top"],
|
|
158
|
-
|
|
159
|
-
// The breadcrumb yields the pane header row to the filter while a
|
|
160
|
-
// filter is being edited or applied.
|
|
161
|
-
leftFilterOn: (_self, data) => Boolean(unwrap(data.leftFilter)),
|
|
162
|
-
rightFilterOn: (_self, data) => Boolean(unwrap(data.rightFilter)),
|
|
163
|
-
|
|
164
|
-
// The inline inspector shows when the preview is on and the viewport is
|
|
165
|
-
// wide enough; otherwise `v` opens the fullscreen viewer.
|
|
166
|
-
inspectorNarrow: (self, data) => isNarrow(data, self),
|
|
167
|
-
showInspectorInline: (self, data) => Boolean(unwrap(data.showPreview)) && !isNarrow(data, self),
|
|
168
|
-
showViewer: (self, data) => Boolean(unwrap(data.viewerOpen)) && isNarrow(data, self),
|
|
169
|
-
|
|
170
|
-
// Help overlay visibility; the content lives in `tui/Help.js` so the
|
|
171
|
-
// overlay has a single source of truth.
|
|
172
|
-
helpOpen: (_self, data) => Boolean(unwrap(data.helpOpen)),
|
|
173
|
-
|
|
174
|
-
// The windowed listing height for both panes (they share the row).
|
|
175
|
-
listViewport: (self, data) => paneListHeight(data, self),
|
|
176
|
-
|
|
177
|
-
// The command bar's height: one line for a status, otherwise as many
|
|
178
|
-
// rows as the wrapped bracket shorthands need.
|
|
179
|
-
barHeight: (self, data) => (commandBarStatus(data) ? 1 : commandBarLines(data, self)),
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// Function: keyGlyph
|
|
184
|
-
// Human-readable glyph for a keymap chord ("↑", "⇧Tab", "^W", "PgUp").
|
|
185
|
-
function keyGlyph(chord) {
|
|
186
|
-
const name = chord.key
|
|
187
|
-
const named = {
|
|
188
|
-
up: "↑",
|
|
189
|
-
down: "↓",
|
|
190
|
-
left: "←",
|
|
191
|
-
right: "→",
|
|
192
|
-
return: "↵",
|
|
193
|
-
enter: "↵",
|
|
194
|
-
space: "Space",
|
|
195
|
-
tab: "Tab",
|
|
196
|
-
pageup: "PgUp",
|
|
197
|
-
pagedown: "PgDn",
|
|
198
|
-
escape: "Esc",
|
|
199
|
-
backspace: "Bksp",
|
|
200
|
-
home: "Home",
|
|
201
|
-
end: "End",
|
|
202
|
-
}
|
|
203
|
-
let base = named[name] ?? (name.length === 1 ? name.toUpperCase() : `${name}`)
|
|
204
|
-
if (chord.ctrl) base = `^${base}`
|
|
205
|
-
if (chord.meta) base = `⌘${base}`
|
|
206
|
-
if (chord.shift && name !== "tab") base = `⇧${base}`
|
|
207
|
-
return base
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// Constant: GROUP_ORDER / GROUP_TITLES
|
|
211
|
-
// Help sections, in display order.
|
|
212
|
-
const GROUP_ORDER = ["nav", "pane", "list", "bookmark", "view", "file", "panel", "palette", "filter", "app"]
|
|
213
|
-
const GROUP_TITLES = {
|
|
214
|
-
app: "Session",
|
|
215
|
-
bookmark: "Bookmarks",
|
|
216
|
-
file: "Files",
|
|
217
|
-
filter: "Filter",
|
|
218
|
-
list: "Selection",
|
|
219
|
-
nav: "Navigation",
|
|
220
|
-
palette: "Commands",
|
|
221
|
-
panel: "Command panel",
|
|
222
|
-
pane: "Panes",
|
|
223
|
-
view: "View",
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// Function: helpLines
|
|
227
|
-
// Builds the categorized key reference from the keymap and the command
|
|
228
|
-
// registry, so it can never drift from the actual bindings.
|
|
229
|
-
function helpLines() {
|
|
230
|
-
const byId = new Map(getCommands().map((command) => [command.id, command]))
|
|
231
|
-
const groups = new Map()
|
|
232
|
-
const seen = new Set()
|
|
233
|
-
for (const binding of DEFAULT_KEYMAP) {
|
|
234
|
-
const command = byId.get(binding.command)
|
|
235
|
-
if (!command) continue
|
|
236
|
-
const group = binding.command.split(".")[0]
|
|
237
|
-
if (!groups.has(group)) groups.set(group, new Map())
|
|
238
|
-
const rows = groups.get(group)
|
|
239
|
-
if (!rows.has(binding.command)) rows.set(binding.command, { keys: [], title: command.title })
|
|
240
|
-
const row = rows.get(binding.command)
|
|
241
|
-
const glyph = keyGlyph(binding)
|
|
242
|
-
if (!row.keys.includes(glyph) && !seen.has(`${binding.command}:${glyph}`)) {
|
|
243
|
-
row.keys.push(glyph)
|
|
244
|
-
seen.add(`${binding.command}:${glyph}`)
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
const lines = []
|
|
248
|
-
for (const group of GROUP_ORDER) {
|
|
249
|
-
const rows = groups.get(group)
|
|
250
|
-
if (!rows) continue
|
|
251
|
-
lines.push(GROUP_TITLES[group] ?? group)
|
|
252
|
-
for (const row of rows.values()) {
|
|
253
|
-
lines.push(` ${row.keys.join("/").padEnd(9)} ${row.title}`)
|
|
254
|
-
}
|
|
255
|
-
lines.push("")
|
|
256
|
-
}
|
|
257
|
-
return lines
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
export {
|
|
261
|
-
commandBarLines,
|
|
262
|
-
createFrameBehaviors,
|
|
263
|
-
helpLines,
|
|
264
|
-
INSPECTOR_INLINE_WIDTH,
|
|
265
|
-
isNarrow,
|
|
266
|
-
keyGlyph,
|
|
267
|
-
sizeOf,
|
|
268
|
-
}
|
|
269
|
-
// EOF
|
package/src/js/tcmd/tui/help.js
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
// Project: TinyCommand
|
|
2
|
-
// Author: Sebastien Pierre
|
|
3
|
-
// License: BSD
|
|
4
|
-
// Created: 2026-10-02
|
|
5
|
-
|
|
6
|
-
// Module: tcmd/tui/help
|
|
7
|
-
// TUI-only behavior for the `?` key reference overlay. The content is built
|
|
8
|
-
// from the live keymap and command registry (see `tui/frame.js`), so it
|
|
9
|
-
// cannot drift from the actual bindings.
|
|
10
|
-
|
|
11
|
-
import { unwrap } from "select/state/cells"
|
|
12
|
-
import { helpLines } from "./frame.js"
|
|
13
|
-
|
|
14
|
-
// Function: createBehaviors
|
|
15
|
-
// Builds the Help behavior map for the given TUI environment.
|
|
16
|
-
function createBehaviors(env) {
|
|
17
|
-
return {
|
|
18
|
-
bg: () => env.theme.surfaceBg,
|
|
19
|
-
borderFg: () => env.theme.borderPrimaryActiveFg,
|
|
20
|
-
dimFg: () => env.theme.textSecondaryFg,
|
|
21
|
-
hint: () => " Esc / ? close ",
|
|
22
|
-
open: (_self, data) => Boolean(unwrap(data.open)),
|
|
23
|
-
text: () => helpLines().join("\n"),
|
|
24
|
-
textFg: () => env.theme.textFg,
|
|
25
|
-
title: () => " Keys ",
|
|
26
|
-
titleFg: () => env.theme.textHeadingFg,
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export { createBehaviors }
|
|
31
|
-
|
|
32
|
-
// EOF
|