@textui/core 0.1.0
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/LICENSE +21 -0
- package/README.md +67 -0
- package/dist/adapters/index.d.ts +12 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +11 -0
- package/dist/app/app.d.ts +200 -0
- package/dist/app/app.d.ts.map +1 -0
- package/dist/app/app.js +980 -0
- package/dist/core/animation.d.ts +34 -0
- package/dist/core/animation.d.ts.map +1 -0
- package/dist/core/animation.js +125 -0
- package/dist/core/clipboard.d.ts +29 -0
- package/dist/core/clipboard.d.ts.map +1 -0
- package/dist/core/clipboard.js +30 -0
- package/dist/core/commands.d.ts +42 -0
- package/dist/core/commands.d.ts.map +1 -0
- package/dist/core/commands.js +146 -0
- package/dist/core/components.d.ts +27 -0
- package/dist/core/components.d.ts.map +1 -0
- package/dist/core/components.js +78 -0
- package/dist/core/events.d.ts +23 -0
- package/dist/core/events.d.ts.map +1 -0
- package/dist/core/events.js +77 -0
- package/dist/core/focus.d.ts +69 -0
- package/dist/core/focus.d.ts.map +1 -0
- package/dist/core/focus.js +336 -0
- package/dist/core/i18n.d.ts +27 -0
- package/dist/core/i18n.d.ts.map +1 -0
- package/dist/core/i18n.js +88 -0
- package/dist/core/keybindings.d.ts +53 -0
- package/dist/core/keybindings.d.ts.map +1 -0
- package/dist/core/keybindings.js +163 -0
- package/dist/core/layers.d.ts +28 -0
- package/dist/core/layers.d.ts.map +1 -0
- package/dist/core/layers.js +84 -0
- package/dist/core/manifest.d.ts +22 -0
- package/dist/core/manifest.d.ts.map +1 -0
- package/dist/core/manifest.js +85 -0
- package/dist/core/navigation.d.ts +47 -0
- package/dist/core/navigation.d.ts.map +1 -0
- package/dist/core/navigation.js +110 -0
- package/dist/core/resources.d.ts +115 -0
- package/dist/core/resources.d.ts.map +1 -0
- package/dist/core/resources.js +321 -0
- package/dist/core/services.d.ts +21 -0
- package/dist/core/services.d.ts.map +1 -0
- package/dist/core/services.js +63 -0
- package/dist/core/store.d.ts +60 -0
- package/dist/core/store.d.ts.map +1 -0
- package/dist/core/store.js +593 -0
- package/dist/core/surfaces.d.ts +81 -0
- package/dist/core/surfaces.d.ts.map +1 -0
- package/dist/core/surfaces.js +237 -0
- package/dist/core/syntax.d.ts +49 -0
- package/dist/core/syntax.d.ts.map +1 -0
- package/dist/core/syntax.js +172 -0
- package/dist/core/when.d.ts +15 -0
- package/dist/core/when.d.ts.map +1 -0
- package/dist/core/when.js +224 -0
- package/dist/index.d.ts +49 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +54 -0
- package/dist/jsx/factory.d.ts +27 -0
- package/dist/jsx/factory.d.ts.map +1 -0
- package/dist/jsx/factory.js +102 -0
- package/dist/jsx/intrinsics.d.ts +96 -0
- package/dist/jsx/intrinsics.d.ts.map +1 -0
- package/dist/jsx/intrinsics.js +1 -0
- package/dist/jsx/jsx-dev-runtime.d.ts +13 -0
- package/dist/jsx/jsx-dev-runtime.d.ts.map +1 -0
- package/dist/jsx/jsx-dev-runtime.js +12 -0
- package/dist/jsx/jsx-runtime.d.ts +42 -0
- package/dist/jsx/jsx-runtime.d.ts.map +1 -0
- package/dist/jsx/jsx-runtime.js +16 -0
- package/dist/render/buffer.d.ts +77 -0
- package/dist/render/buffer.d.ts.map +1 -0
- package/dist/render/buffer.js +275 -0
- package/dist/render/color.d.ts +35 -0
- package/dist/render/color.d.ts.map +1 -0
- package/dist/render/color.js +160 -0
- package/dist/render/diff.d.ts +37 -0
- package/dist/render/diff.d.ts.map +1 -0
- package/dist/render/diff.js +66 -0
- package/dist/render/layout.d.ts +72 -0
- package/dist/render/layout.d.ts.map +1 -0
- package/dist/render/layout.js +633 -0
- package/dist/render/static.d.ts +63 -0
- package/dist/render/static.d.ts.map +1 -0
- package/dist/render/static.js +209 -0
- package/dist/runtime/bindings.d.ts +46 -0
- package/dist/runtime/bindings.d.ts.map +1 -0
- package/dist/runtime/bindings.js +116 -0
- package/dist/runtime/hooks.d.ts +284 -0
- package/dist/runtime/hooks.d.ts.map +1 -0
- package/dist/runtime/hooks.js +846 -0
- package/dist/runtime/instance.d.ts +106 -0
- package/dist/runtime/instance.d.ts.map +1 -0
- package/dist/runtime/instance.js +181 -0
- package/dist/runtime/paint.d.ts +27 -0
- package/dist/runtime/paint.d.ts.map +1 -0
- package/dist/runtime/paint.js +567 -0
- package/dist/runtime/reconcile.d.ts +23 -0
- package/dist/runtime/reconcile.d.ts.map +1 -0
- package/dist/runtime/reconcile.js +260 -0
- package/dist/runtime/runtime.d.ts +42 -0
- package/dist/runtime/runtime.d.ts.map +1 -0
- package/dist/runtime/runtime.js +1 -0
- package/dist/runtime/style.d.ts +55 -0
- package/dist/runtime/style.d.ts.map +1 -0
- package/dist/runtime/style.js +143 -0
- package/dist/themes/borders.d.ts +10 -0
- package/dist/themes/borders.d.ts.map +1 -0
- package/dist/themes/borders.js +88 -0
- package/dist/themes/builtin.d.ts +31 -0
- package/dist/themes/builtin.d.ts.map +1 -0
- package/dist/themes/builtin.js +291 -0
- package/dist/themes/glyphs.d.ts +15 -0
- package/dist/themes/glyphs.d.ts.map +1 -0
- package/dist/themes/glyphs.js +97 -0
- package/dist/themes/index.d.ts +5 -0
- package/dist/themes/index.d.ts.map +1 -0
- package/dist/themes/index.js +4 -0
- package/dist/themes/registry.d.ts +20 -0
- package/dist/themes/registry.d.ts.map +1 -0
- package/dist/themes/registry.js +204 -0
- package/dist/types/adapter.d.ts +38 -0
- package/dist/types/adapter.d.ts.map +1 -0
- package/dist/types/adapter.js +1 -0
- package/dist/types/animation.d.ts +31 -0
- package/dist/types/animation.d.ts.map +1 -0
- package/dist/types/animation.js +1 -0
- package/dist/types/app.d.ts +148 -0
- package/dist/types/app.d.ts.map +1 -0
- package/dist/types/app.js +1 -0
- package/dist/types/async.d.ts +30 -0
- package/dist/types/async.d.ts.map +1 -0
- package/dist/types/async.js +1 -0
- package/dist/types/capabilities.d.ts +44 -0
- package/dist/types/capabilities.d.ts.map +1 -0
- package/dist/types/capabilities.js +33 -0
- package/dist/types/cells.d.ts +72 -0
- package/dist/types/cells.d.ts.map +1 -0
- package/dist/types/cells.js +10 -0
- package/dist/types/command.d.ts +127 -0
- package/dist/types/command.d.ts.map +1 -0
- package/dist/types/command.js +1 -0
- package/dist/types/component-registry.d.ts +77 -0
- package/dist/types/component-registry.d.ts.map +1 -0
- package/dist/types/component-registry.js +1 -0
- package/dist/types/disposable.d.ts +8 -0
- package/dist/types/disposable.d.ts.map +1 -0
- package/dist/types/disposable.js +1 -0
- package/dist/types/focus.d.ts +72 -0
- package/dist/types/focus.d.ts.map +1 -0
- package/dist/types/focus.js +1 -0
- package/dist/types/geometry.d.ts +28 -0
- package/dist/types/geometry.d.ts.map +1 -0
- package/dist/types/geometry.js +14 -0
- package/dist/types/graph.d.ts +129 -0
- package/dist/types/graph.d.ts.map +1 -0
- package/dist/types/graph.js +31 -0
- package/dist/types/i18n.d.ts +23 -0
- package/dist/types/i18n.d.ts.map +1 -0
- package/dist/types/i18n.js +1 -0
- package/dist/types/index.d.ts +32 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +31 -0
- package/dist/types/input.d.ts +65 -0
- package/dist/types/input.d.ts.map +1 -0
- package/dist/types/input.js +1 -0
- package/dist/types/keybinding.d.ts +49 -0
- package/dist/types/keybinding.d.ts.map +1 -0
- package/dist/types/keybinding.js +1 -0
- package/dist/types/layer.d.ts +54 -0
- package/dist/types/layer.d.ts.map +1 -0
- package/dist/types/layer.js +1 -0
- package/dist/types/manifest.d.ts +114 -0
- package/dist/types/manifest.d.ts.map +1 -0
- package/dist/types/manifest.js +1 -0
- package/dist/types/markdown.d.ts +58 -0
- package/dist/types/markdown.d.ts.map +1 -0
- package/dist/types/markdown.js +1 -0
- package/dist/types/navigation.d.ts +40 -0
- package/dist/types/navigation.d.ts.map +1 -0
- package/dist/types/navigation.js +1 -0
- package/dist/types/render.d.ts +72 -0
- package/dist/types/render.d.ts.map +1 -0
- package/dist/types/render.js +1 -0
- package/dist/types/resource.d.ts +170 -0
- package/dist/types/resource.d.ts.map +1 -0
- package/dist/types/resource.js +1 -0
- package/dist/types/services.d.ts +24 -0
- package/dist/types/services.d.ts.map +1 -0
- package/dist/types/services.js +3 -0
- package/dist/types/shell.d.ts +32 -0
- package/dist/types/shell.d.ts.map +1 -0
- package/dist/types/shell.js +1 -0
- package/dist/types/store.d.ts +124 -0
- package/dist/types/store.d.ts.map +1 -0
- package/dist/types/store.js +1 -0
- package/dist/types/stream.d.ts +18 -0
- package/dist/types/stream.d.ts.map +1 -0
- package/dist/types/stream.js +1 -0
- package/dist/types/style.d.ts +166 -0
- package/dist/types/style.d.ts.map +1 -0
- package/dist/types/style.js +1 -0
- package/dist/types/surface.d.ts +97 -0
- package/dist/types/surface.d.ts.map +1 -0
- package/dist/types/surface.js +1 -0
- package/dist/types/syntax.d.ts +68 -0
- package/dist/types/syntax.d.ts.map +1 -0
- package/dist/types/syntax.js +5 -0
- package/dist/types/terminal.d.ts +52 -0
- package/dist/types/terminal.d.ts.map +1 -0
- package/dist/types/terminal.js +1 -0
- package/dist/types/theme.d.ts +117 -0
- package/dist/types/theme.d.ts.map +1 -0
- package/dist/types/theme.js +1 -0
- package/dist/types/when.d.ts +19 -0
- package/dist/types/when.d.ts.map +1 -0
- package/dist/types/when.js +1 -0
- package/dist/ui/primitives.d.ts +56 -0
- package/dist/ui/primitives.d.ts.map +1 -0
- package/dist/ui/primitives.js +108 -0
- package/dist/ui/screen.d.ts +23 -0
- package/dist/ui/screen.d.ts.map +1 -0
- package/dist/ui/screen.js +18 -0
- package/dist/util/disposable.d.ts +6 -0
- package/dist/util/disposable.d.ts.map +1 -0
- package/dist/util/disposable.js +49 -0
- package/dist/util/markdown.d.ts +24 -0
- package/dist/util/markdown.d.ts.map +1 -0
- package/dist/util/markdown.js +220 -0
- package/dist/util/paths.d.ts +47 -0
- package/dist/util/paths.d.ts.map +1 -0
- package/dist/util/paths.js +134 -0
- package/dist/util/stream.d.ts +32 -0
- package/dist/util/stream.d.ts.map +1 -0
- package/dist/util/stream.js +206 -0
- package/dist/util/text.d.ts +65 -0
- package/dist/util/text.d.ts.map +1 -0
- package/dist/util/text.js +419 -0
- package/package.json +70 -0
- package/src/adapters/index.ts +11 -0
- package/src/app/app.ts +1096 -0
- package/src/core/animation.ts +144 -0
- package/src/core/clipboard.ts +40 -0
- package/src/core/commands.ts +169 -0
- package/src/core/components.ts +91 -0
- package/src/core/events.ts +95 -0
- package/src/core/focus.ts +358 -0
- package/src/core/i18n.ts +107 -0
- package/src/core/keybindings.ts +184 -0
- package/src/core/layers.ts +94 -0
- package/src/core/manifest.ts +84 -0
- package/src/core/navigation.ts +135 -0
- package/src/core/resources.ts +362 -0
- package/src/core/services.ts +69 -0
- package/src/core/store.ts +640 -0
- package/src/core/surfaces.ts +292 -0
- package/src/core/syntax.ts +200 -0
- package/src/core/when.ts +238 -0
- package/src/index.ts +76 -0
- package/src/jsx/factory.ts +124 -0
- package/src/jsx/intrinsics.ts +99 -0
- package/src/jsx/jsx-dev-runtime.ts +23 -0
- package/src/jsx/jsx-runtime.ts +76 -0
- package/src/render/buffer.ts +318 -0
- package/src/render/color.ts +180 -0
- package/src/render/diff.ts +99 -0
- package/src/render/layout.ts +764 -0
- package/src/render/static.ts +290 -0
- package/src/runtime/bindings.ts +150 -0
- package/src/runtime/hooks.ts +1057 -0
- package/src/runtime/instance.ts +284 -0
- package/src/runtime/paint.ts +737 -0
- package/src/runtime/reconcile.ts +335 -0
- package/src/runtime/runtime.ts +47 -0
- package/src/runtime/style.ts +205 -0
- package/src/themes/borders.ts +95 -0
- package/src/themes/builtin.ts +301 -0
- package/src/themes/glyphs.ts +101 -0
- package/src/themes/index.ts +4 -0
- package/src/themes/registry.ts +224 -0
- package/src/types/adapter.ts +41 -0
- package/src/types/animation.ts +34 -0
- package/src/types/app.ts +149 -0
- package/src/types/async.ts +35 -0
- package/src/types/capabilities.ts +79 -0
- package/src/types/cells.ts +79 -0
- package/src/types/command.ts +139 -0
- package/src/types/component-registry.ts +82 -0
- package/src/types/disposable.ts +8 -0
- package/src/types/focus.ts +77 -0
- package/src/types/geometry.ts +50 -0
- package/src/types/graph.ts +174 -0
- package/src/types/i18n.ts +25 -0
- package/src/types/index.ts +31 -0
- package/src/types/input.ts +81 -0
- package/src/types/keybinding.ts +51 -0
- package/src/types/layer.ts +46 -0
- package/src/types/manifest.ts +101 -0
- package/src/types/markdown.ts +47 -0
- package/src/types/navigation.ts +42 -0
- package/src/types/render.ts +86 -0
- package/src/types/resource.ts +185 -0
- package/src/types/services.ts +28 -0
- package/src/types/shell.ts +30 -0
- package/src/types/store.ts +148 -0
- package/src/types/stream.ts +24 -0
- package/src/types/style.ts +205 -0
- package/src/types/surface.ts +119 -0
- package/src/types/syntax.ts +93 -0
- package/src/types/terminal.ts +58 -0
- package/src/types/theme.ts +121 -0
- package/src/types/when.ts +21 -0
- package/src/ui/primitives.ts +118 -0
- package/src/ui/screen.ts +41 -0
- package/src/util/disposable.ts +49 -0
- package/src/util/markdown.ts +225 -0
- package/src/util/paths.ts +138 -0
- package/src/util/stream.ts +213 -0
- package/src/util/text.ts +428 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { toDisposable } from '../util/disposable.js';
|
|
2
|
+
/**
|
|
3
|
+
* The surfaces the shipped shells use.
|
|
4
|
+
*
|
|
5
|
+
* A convenience, not a constraint: it is what gets seeded at boot so the
|
|
6
|
+
* built-in shells have state to read. An application is free to mount onto a
|
|
7
|
+
* name that is not here, and nothing checks.
|
|
8
|
+
*/
|
|
9
|
+
export const SURFACE_NAMES = [
|
|
10
|
+
'header', 'rail', 'sidebar', 'aside', 'main', 'panel', 'status', 'overlay', 'notify',
|
|
11
|
+
];
|
|
12
|
+
/**
|
|
13
|
+
* How a surface arranges its mounts when nobody has said.
|
|
14
|
+
*
|
|
15
|
+
* Only the shipped names have an opinion. Anything else gets `single`, which
|
|
16
|
+
* shows the active mount and is the one answer that is never wrong - a surface
|
|
17
|
+
* the library has never heard of cannot be assumed to want tabs.
|
|
18
|
+
*/
|
|
19
|
+
const DEFAULT_LAYOUTS = {
|
|
20
|
+
header: 'bar',
|
|
21
|
+
rail: 'rail',
|
|
22
|
+
// One at a time, not stacked. A second panel arriving under the first
|
|
23
|
+
// halves both of them; a sidebar is a narrow column and the panels in it -
|
|
24
|
+
// a tree, a source control list, a search - each want the whole height.
|
|
25
|
+
// `activeKey` picks, and `single` falls back to the first mount.
|
|
26
|
+
sidebar: 'single',
|
|
27
|
+
aside: 'stack',
|
|
28
|
+
main: 'tabs',
|
|
29
|
+
panel: 'tabs',
|
|
30
|
+
status: 'bar',
|
|
31
|
+
overlay: 'floating',
|
|
32
|
+
notify: 'toast',
|
|
33
|
+
};
|
|
34
|
+
const FALLBACK_LAYOUT = 'single';
|
|
35
|
+
function defaultLayout(surface) {
|
|
36
|
+
return DEFAULT_LAYOUTS[surface] ?? FALLBACK_LAYOUT;
|
|
37
|
+
}
|
|
38
|
+
function defaultState(surface) {
|
|
39
|
+
return { layout: defaultLayout(surface), activeKey: null, visible: true };
|
|
40
|
+
}
|
|
41
|
+
function statePath(surface) {
|
|
42
|
+
return `$/layout/surfaces/${surface}`;
|
|
43
|
+
}
|
|
44
|
+
function mountsPath(surface) {
|
|
45
|
+
return `$/layout/mounts/${surface}`;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Surfaces, mounts and their state.
|
|
49
|
+
*
|
|
50
|
+
* A surface is whatever the application calls a region. The runtime keeps
|
|
51
|
+
* state per name and never validates one: mounting onto `lateral1` works
|
|
52
|
+
* exactly as well as mounting onto `sidebar`, and needs no registration. The
|
|
53
|
+
* shipped names are seeded at boot only so the shipped shells have something
|
|
54
|
+
* to read.
|
|
55
|
+
*
|
|
56
|
+
* Which layout a surface uses is store state rather than code, which is what
|
|
57
|
+
* makes it switchable at runtime and persistable per user.
|
|
58
|
+
*/
|
|
59
|
+
export class Surfaces {
|
|
60
|
+
deps;
|
|
61
|
+
byKey = new Map();
|
|
62
|
+
/** Subscriptions to the paths a mount's `when` clause reads. */
|
|
63
|
+
conditions = new Map();
|
|
64
|
+
constructor(deps) {
|
|
65
|
+
this.deps = deps;
|
|
66
|
+
for (const surface of SURFACE_NAMES) {
|
|
67
|
+
if (this.deps.store.get(statePath(surface)) === undefined) {
|
|
68
|
+
this.deps.store.set(statePath(surface), defaultState(surface));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
id(surface, key) {
|
|
73
|
+
return `${surface}/${key}`;
|
|
74
|
+
}
|
|
75
|
+
publish(surface) {
|
|
76
|
+
const list = this.mounts(surface).map((m) => ({
|
|
77
|
+
key: m.key,
|
|
78
|
+
display: m.display ?? {},
|
|
79
|
+
policy: m.policy ?? {},
|
|
80
|
+
}));
|
|
81
|
+
this.deps.store.set(mountsPath(surface), list);
|
|
82
|
+
this.deps.onChange();
|
|
83
|
+
}
|
|
84
|
+
open(mount) {
|
|
85
|
+
const id = this.id(mount.surface, mount.key);
|
|
86
|
+
this.byKey.set(id, mount);
|
|
87
|
+
// A conditional mount has to react to the paths its clause reads, or it
|
|
88
|
+
// stays hidden until something unrelated happens to redraw the surface.
|
|
89
|
+
this.watchCondition(id, mount);
|
|
90
|
+
const state = this.state(mount.surface);
|
|
91
|
+
if (state.activeKey === null || mount.policy?.transient) {
|
|
92
|
+
this.setState(mount.surface, { activeKey: mount.key });
|
|
93
|
+
}
|
|
94
|
+
this.publish(mount.surface);
|
|
95
|
+
return toDisposable(() => this.close(mount.surface, mount.key));
|
|
96
|
+
}
|
|
97
|
+
close(surface, key) {
|
|
98
|
+
const id = this.id(surface, key);
|
|
99
|
+
this.unwatchCondition(id);
|
|
100
|
+
if (!this.byKey.delete(id))
|
|
101
|
+
return;
|
|
102
|
+
const state = this.state(surface);
|
|
103
|
+
if (state.activeKey === key) {
|
|
104
|
+
const remaining = this.mounts(surface);
|
|
105
|
+
this.setState(surface, { activeKey: remaining[0]?.key ?? null });
|
|
106
|
+
}
|
|
107
|
+
this.publish(surface);
|
|
108
|
+
}
|
|
109
|
+
closeAll(surface) {
|
|
110
|
+
for (const mount of this.all(surface)) {
|
|
111
|
+
const id = this.id(surface, mount.key);
|
|
112
|
+
this.unwatchCondition(id);
|
|
113
|
+
this.byKey.delete(id);
|
|
114
|
+
}
|
|
115
|
+
this.setState(surface, { activeKey: null });
|
|
116
|
+
this.publish(surface);
|
|
117
|
+
}
|
|
118
|
+
get(surface, key) {
|
|
119
|
+
return this.byKey.get(this.id(surface, key));
|
|
120
|
+
}
|
|
121
|
+
watchCondition(id, mount) {
|
|
122
|
+
this.unwatchCondition(id);
|
|
123
|
+
if (!mount.when)
|
|
124
|
+
return;
|
|
125
|
+
const deps = this.deps.when.dependencies(mount.when);
|
|
126
|
+
if (deps.length === 0)
|
|
127
|
+
return;
|
|
128
|
+
this.conditions.set(id, deps.map((path) => this.deps.store.subscribe(path, () => this.publish(mount.surface))));
|
|
129
|
+
}
|
|
130
|
+
unwatchCondition(id) {
|
|
131
|
+
for (const sub of this.conditions.get(id) ?? [])
|
|
132
|
+
sub.dispose();
|
|
133
|
+
this.conditions.delete(id);
|
|
134
|
+
}
|
|
135
|
+
/** Every mount on a surface, conditional or not. */
|
|
136
|
+
all(surface) {
|
|
137
|
+
return [...this.byKey.values()]
|
|
138
|
+
.filter((m) => m.surface === surface)
|
|
139
|
+
.sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
140
|
+
}
|
|
141
|
+
/** Mounts whose `when` clause passes, in order. */
|
|
142
|
+
mounts(surface) {
|
|
143
|
+
return [...this.byKey.values()]
|
|
144
|
+
.filter((m) => m.surface === surface && this.deps.when.evaluate(m.when))
|
|
145
|
+
.sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
146
|
+
}
|
|
147
|
+
activate(surface, key) {
|
|
148
|
+
if (!this.byKey.has(this.id(surface, key)))
|
|
149
|
+
return;
|
|
150
|
+
this.setState(surface, { activeKey: key });
|
|
151
|
+
}
|
|
152
|
+
state(surface) {
|
|
153
|
+
return this.deps.store.get(statePath(surface)) ?? defaultState(surface);
|
|
154
|
+
}
|
|
155
|
+
setState(surface, patch) {
|
|
156
|
+
// A name nobody seeded is still a surface. Give it defaults on first use
|
|
157
|
+
// rather than patching a hole, or `visible` and `layout` arrive undefined
|
|
158
|
+
// and every reader has to guess what an unseeded surface meant.
|
|
159
|
+
if (this.deps.store.get(statePath(surface)) === undefined) {
|
|
160
|
+
this.deps.store.set(statePath(surface), defaultState(surface));
|
|
161
|
+
}
|
|
162
|
+
this.deps.store.patch(statePath(surface), patch);
|
|
163
|
+
this.deps.onChange();
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Resource-level open: ask the registry which viewer handles this kind, then
|
|
167
|
+
* mount it. The caller never names a component, which is what lets a new
|
|
168
|
+
* resource type arrive with its own viewer and work everywhere at once.
|
|
169
|
+
*/
|
|
170
|
+
async openResource(uri, options = {}) {
|
|
171
|
+
const resources = this.deps.resources();
|
|
172
|
+
const resource = await resources.stat(uri);
|
|
173
|
+
if (!resource)
|
|
174
|
+
return null;
|
|
175
|
+
const node = resources.nodeFor(resource, options);
|
|
176
|
+
if (!node)
|
|
177
|
+
return null;
|
|
178
|
+
return this.open({
|
|
179
|
+
surface: options.surface ?? 'main',
|
|
180
|
+
key: uri,
|
|
181
|
+
target: node,
|
|
182
|
+
display: { title: resource.metadata.name, icon: resource.kind },
|
|
183
|
+
policy: { closable: true },
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
export class Layouts {
|
|
188
|
+
defs = new Map();
|
|
189
|
+
register(def) {
|
|
190
|
+
this.defs.set(def.name, def);
|
|
191
|
+
return toDisposable(() => this.defs.delete(def.name));
|
|
192
|
+
}
|
|
193
|
+
get(name) {
|
|
194
|
+
return this.defs.get(name);
|
|
195
|
+
}
|
|
196
|
+
list(surface) {
|
|
197
|
+
const all = [...this.defs.values()];
|
|
198
|
+
return surface ? all.filter((d) => !d.surfaces || d.surfaces.includes(surface)) : all;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
export class Shells {
|
|
202
|
+
defs = new Map();
|
|
203
|
+
register(def) {
|
|
204
|
+
this.defs.set(def.id, def);
|
|
205
|
+
return toDisposable(() => this.defs.delete(def.id));
|
|
206
|
+
}
|
|
207
|
+
get(id) {
|
|
208
|
+
return this.defs.get(id);
|
|
209
|
+
}
|
|
210
|
+
list() {
|
|
211
|
+
return [...this.defs.values()];
|
|
212
|
+
}
|
|
213
|
+
/** Shells that fit, largest minimum first - the richest one that works. */
|
|
214
|
+
suitable(width, height) {
|
|
215
|
+
return [...this.defs.values()]
|
|
216
|
+
.filter((s) => {
|
|
217
|
+
const min = s.minSize;
|
|
218
|
+
if (!min)
|
|
219
|
+
return true;
|
|
220
|
+
return (min.width ?? 0) <= width && (min.height ?? 0) <= height;
|
|
221
|
+
})
|
|
222
|
+
.sort((a, b) => {
|
|
223
|
+
const av = (a.minSize?.width ?? 0) + (a.minSize?.height ?? 0);
|
|
224
|
+
const bv = (b.minSize?.width ?? 0) + (b.minSize?.height ?? 0);
|
|
225
|
+
return bv - av;
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
export function createSurfaces(deps) {
|
|
230
|
+
return new Surfaces(deps);
|
|
231
|
+
}
|
|
232
|
+
export function createLayouts() {
|
|
233
|
+
return new Layouts();
|
|
234
|
+
}
|
|
235
|
+
export function createShells() {
|
|
236
|
+
return new Shells();
|
|
237
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { HighlighterDefinition, SyntaxQuery, SyntaxRegistry, SyntaxSpan, SyntaxToken } from '../types/syntax.js';
|
|
2
|
+
import type { Disposable } from '../types/disposable.js';
|
|
3
|
+
/**
|
|
4
|
+
* The highlighter registry.
|
|
5
|
+
*
|
|
6
|
+
* Late binding, like every other registry here: a file whose kind has no
|
|
7
|
+
* highlighter is shown uncoloured rather than refused, and registering one
|
|
8
|
+
* later colours every viewer already on screen.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* The last segment of a URI, without a query or a fragment.
|
|
12
|
+
*
|
|
13
|
+
* Exported because three places wanted it and each had written its own: the
|
|
14
|
+
* highlighter matching a glob, the resource registry naming a resource, and a
|
|
15
|
+
* viewer saying which file it is showing.
|
|
16
|
+
*/
|
|
17
|
+
export declare function nameOf(uri: string): string;
|
|
18
|
+
/** One plain token per line: what an unhighlighted document looks like. */
|
|
19
|
+
export declare function plainTokens(text: string): SyntaxToken[][];
|
|
20
|
+
/**
|
|
21
|
+
* Split scored spans into per-line tokens.
|
|
22
|
+
*
|
|
23
|
+
* Highlighters are far easier to write as a single scan over the source than
|
|
24
|
+
* as a line-aware state machine, so they score offsets and this does the
|
|
25
|
+
* splitting - including the newlines inside a multi-line string, which is the
|
|
26
|
+
* part a hand-written splitter always gets wrong.
|
|
27
|
+
*/
|
|
28
|
+
export declare function tokensFromSpans(text: string, spans: SyntaxSpan[]): SyntaxToken[][];
|
|
29
|
+
/** Build a highlighter from a function that scores spans. */
|
|
30
|
+
export declare function spanHighlighter(def: Omit<HighlighterDefinition, 'tokenize'> & {
|
|
31
|
+
scan(text: string): SyntaxSpan[];
|
|
32
|
+
}): HighlighterDefinition;
|
|
33
|
+
export declare class Syntax implements SyntaxRegistry {
|
|
34
|
+
private deps;
|
|
35
|
+
private defs;
|
|
36
|
+
constructor(deps?: {
|
|
37
|
+
/** True when `kind` is `ancestor` or specialises it. */
|
|
38
|
+
kindMatches(kind: string, ancestor: string): boolean;
|
|
39
|
+
onError?(err: unknown, context: string): void;
|
|
40
|
+
});
|
|
41
|
+
register(def: HighlighterDefinition): Disposable;
|
|
42
|
+
unregister(id: string): void;
|
|
43
|
+
get(id: string): HighlighterDefinition | undefined;
|
|
44
|
+
list(): HighlighterDefinition[];
|
|
45
|
+
find(query: SyntaxQuery): HighlighterDefinition | undefined;
|
|
46
|
+
tokenize(text: string, query?: SyntaxQuery): SyntaxToken[][];
|
|
47
|
+
}
|
|
48
|
+
export declare function createSyntax(deps?: ConstructorParameters<typeof Syntax>[0]): Syntax;
|
|
49
|
+
//# sourceMappingURL=syntax.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"syntax.d.ts","sourceRoot":"","sources":["../../src/core/syntax.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,qBAAqB,EAAE,WAAW,EAAE,cAAc,EAAe,UAAU,EAC3E,WAAW,EACZ,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAGzD;;;;;;GAMG;AAEH;;;;;;GAMG;AACH,wBAAgB,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAG1C;AASD,2EAA2E;AAC3E,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,EAAE,EAAE,CAEzD;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,WAAW,EAAE,EAAE,CAiDlF;AAED,6DAA6D;AAC7D,wBAAgB,eAAe,CAC7B,GAAG,EAAE,IAAI,CAAC,qBAAqB,EAAE,UAAU,CAAC,GAAG;IAAE,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,EAAE,CAAA;CAAE,GAClF,qBAAqB,CAGvB;AAED,qBAAa,MAAO,YAAW,cAAc;IAIzC,OAAO,CAAC,IAAI;IAHd,OAAO,CAAC,IAAI,CAA4C;gBAG9C,IAAI,GAAE;QACZ,wDAAwD;QACxD,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;QACrD,OAAO,CAAC,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;KACU;IAG5D,QAAQ,CAAC,GAAG,EAAE,qBAAqB,GAAG,UAAU;IAOhD,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAI5B,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,qBAAqB,GAAG,SAAS;IAIlD,IAAI,IAAI,qBAAqB,EAAE;IAI/B,IAAI,CAAC,KAAK,EAAE,WAAW,GAAG,qBAAqB,GAAG,SAAS;IAgC3D,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,GAAE,WAAgB,GAAG,WAAW,EAAE,EAAE;CA2BjE;AAED,wBAAgB,YAAY,CAAC,IAAI,CAAC,EAAE,qBAAqB,CAAC,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,CAEnF"}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { toDisposable } from '../util/disposable.js';
|
|
2
|
+
/**
|
|
3
|
+
* The highlighter registry.
|
|
4
|
+
*
|
|
5
|
+
* Late binding, like every other registry here: a file whose kind has no
|
|
6
|
+
* highlighter is shown uncoloured rather than refused, and registering one
|
|
7
|
+
* later colours every viewer already on screen.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* The last segment of a URI, without a query or a fragment.
|
|
11
|
+
*
|
|
12
|
+
* Exported because three places wanted it and each had written its own: the
|
|
13
|
+
* highlighter matching a glob, the resource registry naming a resource, and a
|
|
14
|
+
* viewer saying which file it is showing.
|
|
15
|
+
*/
|
|
16
|
+
export function nameOf(uri) {
|
|
17
|
+
const clean = uri.split(/[?#]/)[0] ?? uri;
|
|
18
|
+
return clean.split('/').pop() ?? clean;
|
|
19
|
+
}
|
|
20
|
+
function matchesGlob(name, pattern) {
|
|
21
|
+
if (pattern.startsWith('*.')) {
|
|
22
|
+
return name.toLowerCase().endsWith(pattern.slice(1).toLowerCase());
|
|
23
|
+
}
|
|
24
|
+
return name.toLowerCase() === pattern.toLowerCase();
|
|
25
|
+
}
|
|
26
|
+
/** One plain token per line: what an unhighlighted document looks like. */
|
|
27
|
+
export function plainTokens(text) {
|
|
28
|
+
return text.split('\n').map((line) => (line === '' ? [] : [{ text: line, scope: 'plain' }]));
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Split scored spans into per-line tokens.
|
|
32
|
+
*
|
|
33
|
+
* Highlighters are far easier to write as a single scan over the source than
|
|
34
|
+
* as a line-aware state machine, so they score offsets and this does the
|
|
35
|
+
* splitting - including the newlines inside a multi-line string, which is the
|
|
36
|
+
* part a hand-written splitter always gets wrong.
|
|
37
|
+
*/
|
|
38
|
+
export function tokensFromSpans(text, spans) {
|
|
39
|
+
const ordered = [...spans].sort((a, b) => a.start - b.start);
|
|
40
|
+
const lines = text.split('\n');
|
|
41
|
+
const out = lines.map(() => []);
|
|
42
|
+
// Line index for every offset, computed once.
|
|
43
|
+
const lineStarts = [0];
|
|
44
|
+
for (let i = 0; i < text.length; i++) {
|
|
45
|
+
if (text[i] === '\n')
|
|
46
|
+
lineStarts.push(i + 1);
|
|
47
|
+
}
|
|
48
|
+
const lineAt = (offset) => {
|
|
49
|
+
let lo = 0;
|
|
50
|
+
let hi = lineStarts.length - 1;
|
|
51
|
+
while (lo < hi) {
|
|
52
|
+
const mid = (lo + hi + 1) >> 1;
|
|
53
|
+
if (lineStarts[mid] <= offset)
|
|
54
|
+
lo = mid;
|
|
55
|
+
else
|
|
56
|
+
hi = mid - 1;
|
|
57
|
+
}
|
|
58
|
+
return lo;
|
|
59
|
+
};
|
|
60
|
+
const push = (start, end, scope) => {
|
|
61
|
+
if (end <= start)
|
|
62
|
+
return;
|
|
63
|
+
let cursor = start;
|
|
64
|
+
while (cursor < end) {
|
|
65
|
+
const line = lineAt(cursor);
|
|
66
|
+
const lineEnd = lineStarts[line] + lines[line].length;
|
|
67
|
+
const stop = Math.min(end, lineEnd);
|
|
68
|
+
if (stop > cursor) {
|
|
69
|
+
out[line].push({ text: text.slice(cursor, stop), scope });
|
|
70
|
+
}
|
|
71
|
+
cursor = stop + 1; // step over the newline
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
let cursor = 0;
|
|
75
|
+
for (const span of ordered) {
|
|
76
|
+
// Overlaps are a bug in the highlighter, not a reason to lose text: the
|
|
77
|
+
// first span wins the shared cells and the rest of this one still lands.
|
|
78
|
+
const start = Math.max(cursor, span.start);
|
|
79
|
+
const end = Math.min(Math.max(start, span.end), text.length);
|
|
80
|
+
if (end <= cursor)
|
|
81
|
+
continue;
|
|
82
|
+
if (start > cursor)
|
|
83
|
+
push(cursor, start, 'plain');
|
|
84
|
+
push(start, end, span.scope);
|
|
85
|
+
cursor = end;
|
|
86
|
+
}
|
|
87
|
+
if (cursor < text.length)
|
|
88
|
+
push(cursor, text.length, 'plain');
|
|
89
|
+
return out;
|
|
90
|
+
}
|
|
91
|
+
/** Build a highlighter from a function that scores spans. */
|
|
92
|
+
export function spanHighlighter(def) {
|
|
93
|
+
const { scan, ...rest } = def;
|
|
94
|
+
return { ...rest, tokenize: (text) => tokensFromSpans(text, scan(text)) };
|
|
95
|
+
}
|
|
96
|
+
export class Syntax {
|
|
97
|
+
deps;
|
|
98
|
+
defs = new Map();
|
|
99
|
+
constructor(deps = { kindMatches: (kind, ancestor) => kind === ancestor }) {
|
|
100
|
+
this.deps = deps;
|
|
101
|
+
}
|
|
102
|
+
register(def) {
|
|
103
|
+
this.defs.set(def.id, def);
|
|
104
|
+
return toDisposable(() => {
|
|
105
|
+
if (this.defs.get(def.id) === def)
|
|
106
|
+
this.defs.delete(def.id);
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
unregister(id) {
|
|
110
|
+
this.defs.delete(id);
|
|
111
|
+
}
|
|
112
|
+
get(id) {
|
|
113
|
+
return this.defs.get(id);
|
|
114
|
+
}
|
|
115
|
+
list() {
|
|
116
|
+
return [...this.defs.values()];
|
|
117
|
+
}
|
|
118
|
+
find(query) {
|
|
119
|
+
if (query.language) {
|
|
120
|
+
const named = this.defs.get(query.language);
|
|
121
|
+
if (named)
|
|
122
|
+
return named;
|
|
123
|
+
}
|
|
124
|
+
const name = query.uri ? nameOf(query.uri) : undefined;
|
|
125
|
+
let best;
|
|
126
|
+
let bestScore = -Infinity;
|
|
127
|
+
for (const def of this.defs.values()) {
|
|
128
|
+
let score = def.priority ?? 0;
|
|
129
|
+
let matched = false;
|
|
130
|
+
if (query.kind && def.kinds?.some((k) => this.deps.kindMatches(query.kind, k))) {
|
|
131
|
+
// A highlighter registered for the exact kind beats one registered
|
|
132
|
+
// for an ancestor of it.
|
|
133
|
+
matched = true;
|
|
134
|
+
score += def.kinds.includes(query.kind) ? 120 : 100;
|
|
135
|
+
}
|
|
136
|
+
if (name && def.extensions?.some((p) => matchesGlob(name, p))) {
|
|
137
|
+
matched = true;
|
|
138
|
+
score += 60;
|
|
139
|
+
}
|
|
140
|
+
if (matched && score > bestScore) {
|
|
141
|
+
best = def;
|
|
142
|
+
bestScore = score;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return best;
|
|
146
|
+
}
|
|
147
|
+
tokenize(text, query = {}) {
|
|
148
|
+
const def = this.find(query);
|
|
149
|
+
if (!def)
|
|
150
|
+
return plainTokens(text);
|
|
151
|
+
const lines = text.split('\n');
|
|
152
|
+
let result;
|
|
153
|
+
try {
|
|
154
|
+
result = def.tokenize(text);
|
|
155
|
+
}
|
|
156
|
+
catch (err) {
|
|
157
|
+
this.deps.onError?.(err, `highlighter "${def.id}"`);
|
|
158
|
+
return plainTokens(text);
|
|
159
|
+
}
|
|
160
|
+
// A highlighter that loses or invents a line would slide the whole file
|
|
161
|
+
// against its gutter. Uncoloured is wrong; misaligned is unreadable.
|
|
162
|
+
if (result.length !== lines.length) {
|
|
163
|
+
this.deps.onError?.(new Error(`[textui] highlighter "${def.id}" returned ${result.length} lines for ` +
|
|
164
|
+
`a ${lines.length}-line document`), 'syntax');
|
|
165
|
+
return plainTokens(text);
|
|
166
|
+
}
|
|
167
|
+
return result;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
export function createSyntax(deps) {
|
|
171
|
+
return new Syntax(deps);
|
|
172
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ContextValue, WhenClause, WhenEngine } from '../types/when.js';
|
|
2
|
+
import type { BindingPath } from '../types/graph.js';
|
|
3
|
+
import type { ReactiveStore } from '../types/store.js';
|
|
4
|
+
export declare class When implements WhenEngine {
|
|
5
|
+
private store;
|
|
6
|
+
private context;
|
|
7
|
+
private cache;
|
|
8
|
+
constructor(store: ReactiveStore);
|
|
9
|
+
evaluate(clause: WhenClause | undefined, extra?: Record<string, ContextValue>): boolean;
|
|
10
|
+
dependencies(clause: WhenClause): BindingPath[];
|
|
11
|
+
setContext(key: string, value: ContextValue): void;
|
|
12
|
+
getContext(key: string): ContextValue | undefined;
|
|
13
|
+
}
|
|
14
|
+
export declare function createWhen(store: ReactiveStore): When;
|
|
15
|
+
//# sourceMappingURL=when.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"when.d.ts","sourceRoot":"","sources":["../../src/core/when.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC7E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AA0LvD,qBAAa,IAAK,YAAW,UAAU;IAIzB,OAAO,CAAC,KAAK;IAHzB,OAAO,CAAC,OAAO,CAAmC;IAClD,OAAO,CAAC,KAAK,CAA8B;gBAEvB,KAAK,EAAE,aAAa;IAExC,QAAQ,CAAC,MAAM,EAAE,UAAU,GAAG,SAAS,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,GAAG,OAAO;IAsBvF,YAAY,CAAC,MAAM,EAAE,UAAU,GAAG,WAAW,EAAE;IAU/C,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,IAAI;IAIlD,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;CAGlD;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI,CAErD"}
|