@ours.network/fleet 0.15.7 → 0.16.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/README.md +21 -2
- package/dist/docs.d.ts +1 -1
- package/dist/docs.js +15 -0
- package/dist/web/fleet-config-service.js +51 -15
- package/dist/web/runtime.js +9 -2
- package/dist/web/server.d.ts +6 -2
- package/dist/web/server.js +50 -0
- package/dist/web/topology-draft-store.d.ts +80 -0
- package/dist/web/topology-draft-store.js +337 -0
- package/dist/web/topology-model.d.ts +62 -0
- package/dist/web/topology-model.js +220 -0
- package/dist/web/topology-promote.d.ts +31 -0
- package/dist/web/topology-promote.js +168 -0
- package/dist/web/yaml-document-edit.d.ts +31 -0
- package/dist/web/yaml-document-edit.js +401 -0
- package/dist/web-app/assets/{TerminalView-BCbgag6j.js → TerminalView-Mxxypj9w.js} +1 -1
- package/dist/web-app/assets/index-CsHEL0f6.js +10 -0
- package/dist/web-app/assets/{index-COg4Azq1.css → index-DuC-xnX4.css} +1 -1
- package/dist/web-app/index.html +2 -2
- package/package.json +1 -1
- package/dist/web-app/assets/index-BstfCP_F.js +0 -10
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { FleetError } from '../application/errors.js';
|
|
2
|
+
/**
|
|
3
|
+
* Turn sketches into configuration.
|
|
4
|
+
*
|
|
5
|
+
* Promotion is the middle step of sketch -> promote -> launch, and it writes
|
|
6
|
+
* configuration ONLY. It never provisions an identity, never registers a
|
|
7
|
+
* supervisor service and never starts a process: `Launch` is a separate, explicit
|
|
8
|
+
* action. That separation is what makes "visible but not launchable" true at the
|
|
9
|
+
* daemon level rather than as a UI rule.
|
|
10
|
+
*
|
|
11
|
+
* No YAML is written here. The mutation is expressed against the configuration
|
|
12
|
+
* *model* and handed to `FleetConfigService`, so the revision guard, the real
|
|
13
|
+
* loader validating a candidate file, the reviewed diff, the timestamped backup
|
|
14
|
+
* and the atomic 0600 replace are all impossible to bypass.
|
|
15
|
+
*/
|
|
16
|
+
/** Loop interval when the sketch did not choose one (`resolveLoops` requires it). */
|
|
17
|
+
const DEFAULT_LOOP_INTERVAL = '10m';
|
|
18
|
+
/** Scalar draft fields that are valid keys of a role mapping. */
|
|
19
|
+
const ROLE_FIELDS = ['mission', 'bio', 'persona', 'coordinator', 'harness', 'session', 'model', 'identity', 'cwd'];
|
|
20
|
+
/** Scalar draft fields that are valid keys of a watchdog mapping. */
|
|
21
|
+
const WATCHDOG_FIELDS = ['coordinator', 'interval', 'enabled'];
|
|
22
|
+
/** Scalar draft fields that are valid keys of a loop mapping. */
|
|
23
|
+
const LOOP_FIELDS = ['prompt', 'interval', 'initial_delay', 'jitter'];
|
|
24
|
+
export class TopologyPromoteService {
|
|
25
|
+
options;
|
|
26
|
+
constructor(options) {
|
|
27
|
+
this.options = options;
|
|
28
|
+
}
|
|
29
|
+
async preview(request) {
|
|
30
|
+
const { model, promoted } = await this.build(request);
|
|
31
|
+
const preview = await this.options.configuration.preview(request.configRevision, model);
|
|
32
|
+
return { ...preview, promoted };
|
|
33
|
+
}
|
|
34
|
+
async promote(request) {
|
|
35
|
+
const { model, promoted } = await this.build(request);
|
|
36
|
+
const written = await this.options.configuration.write(request.configRevision, model);
|
|
37
|
+
// The configuration is real now. Clearing the sketches is best effort: if the
|
|
38
|
+
// sidecar moved under us the drafts simply resurface as shadowed-by-config,
|
|
39
|
+
// which the merged model already reports, rather than losing the write.
|
|
40
|
+
const cleared = await this.clearDrafts(promoted, request.draftRevision);
|
|
41
|
+
return { ...written, promoted, draftsCleared: cleared.ok, draftRevision: cleared.revision };
|
|
42
|
+
}
|
|
43
|
+
async clearDrafts(promoted, draftRevision) {
|
|
44
|
+
const current = this.options.drafts.read();
|
|
45
|
+
if (draftRevision !== undefined && draftRevision !== current.revision)
|
|
46
|
+
return { ok: false, revision: current.revision };
|
|
47
|
+
const promotedIds = new Set(promoted);
|
|
48
|
+
const next = {
|
|
49
|
+
...current.draft,
|
|
50
|
+
drafts: {
|
|
51
|
+
nodes: current.draft.drafts.nodes.filter(node => !promotedIds.has(node.id)),
|
|
52
|
+
/*
|
|
53
|
+
* A drawn edge is owned by its SOURCE — it becomes the source's `watch:`
|
|
54
|
+
* or `roles:` list — so it is materialised, and therefore redundant, only
|
|
55
|
+
* once the source itself is written. Clearing it because the TARGET was
|
|
56
|
+
* promoted silently rewrites the survivor's meaning: a watchdog scoped to
|
|
57
|
+
* one agent loses its only scope edge, reads as standalone, and is then
|
|
58
|
+
* written with no `watch:` key at all — that is, watching everything.
|
|
59
|
+
*/
|
|
60
|
+
edges: current.draft.drafts.edges.filter(edge => !promotedIds.has(edge.from)),
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
try {
|
|
64
|
+
const written = await this.options.drafts.write(current.revision, next);
|
|
65
|
+
return { ok: true, revision: written.revision };
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return { ok: false, revision: current.revision };
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/** Build the configuration model that adding `ids` produces. */
|
|
72
|
+
async build(request) {
|
|
73
|
+
if (!Array.isArray(request.ids) || request.ids.length === 0)
|
|
74
|
+
throw new FleetError('invalid_request', 'name at least one sketch to add to the fleet');
|
|
75
|
+
const merged = await this.options.topology();
|
|
76
|
+
const read = this.options.configuration.read();
|
|
77
|
+
if (read.revision !== request.configRevision)
|
|
78
|
+
throw new FleetError('stale_state', 'fleet.yaml changed since it was opened; reload before adding to the fleet');
|
|
79
|
+
const selected = request.ids.map(id => resolve(merged, id));
|
|
80
|
+
const model = structuredClone(read.model);
|
|
81
|
+
// Agents first: a watchdog's `watch:` list and a loop's `roles:` list may only
|
|
82
|
+
// name roles that exist, including ones being added in this same write.
|
|
83
|
+
for (const node of [...selected].sort(byKind))
|
|
84
|
+
addNode(model, node, merged);
|
|
85
|
+
return { model, promoted: selected.map(node => node.id) };
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const KIND_ORDER = { agent: 0, watchdog: 1, loop: 2 };
|
|
89
|
+
const byKind = (a, b) => KIND_ORDER[a.kind] - KIND_ORDER[b.kind];
|
|
90
|
+
function resolve(merged, id) {
|
|
91
|
+
const node = merged.nodes.find(candidate => candidate.id === id);
|
|
92
|
+
if (!node)
|
|
93
|
+
throw new FleetError('invalid_request', `there is no sketch called ${id}`);
|
|
94
|
+
if (node.origin !== 'draft')
|
|
95
|
+
throw new FleetError('conflict', `${node.label} is already part of the fleet configuration`);
|
|
96
|
+
if (!node.valid || !node.complete) {
|
|
97
|
+
const why = node.missing.map(item => `${item.field}: ${item.fix}`).join(' ');
|
|
98
|
+
throw new FleetError('invalid_request', `${node.label} is not ready to add — ${why}`);
|
|
99
|
+
}
|
|
100
|
+
return node;
|
|
101
|
+
}
|
|
102
|
+
function addNode(model, node, merged) {
|
|
103
|
+
const block = section(model, node.kind === 'agent' ? 'roles' : node.kind === 'watchdog' ? 'watchdogs' : 'loops');
|
|
104
|
+
if (block[node.label] !== undefined)
|
|
105
|
+
throw new FleetError('conflict', `${node.label} already exists in the configuration`);
|
|
106
|
+
block[node.label] = node.kind === 'agent' ? agentEntry(node)
|
|
107
|
+
: node.kind === 'watchdog' ? watchdogEntry(node, merged)
|
|
108
|
+
: loopEntry(node, merged, model);
|
|
109
|
+
}
|
|
110
|
+
function agentEntry(node) {
|
|
111
|
+
return pick(node, ROLE_FIELDS);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* D4, and the owner requirement it encodes: a watchdog drawn on its own carries
|
|
115
|
+
* NO `watch:` key, which the config layer reads as "every configured role" —
|
|
116
|
+
* so an agent added tomorrow is covered with no edit at all. A watchdog created
|
|
117
|
+
* from a specific agent carries that agent explicitly and stays scoped to it.
|
|
118
|
+
*/
|
|
119
|
+
function watchdogEntry(node, merged) {
|
|
120
|
+
const scoped = linked(merged, node.id, 'watches');
|
|
121
|
+
return { ...pick(node, WATCHDOG_FIELDS), ...(scoped.length ? { watch: scoped } : {}) };
|
|
122
|
+
}
|
|
123
|
+
function loopEntry(node, merged, model) {
|
|
124
|
+
const roles = linked(merged, node.id, 'targets');
|
|
125
|
+
const entry = {
|
|
126
|
+
roles, ...pick(node, LOOP_FIELDS),
|
|
127
|
+
};
|
|
128
|
+
entry.interval ??= DEFAULT_LOOP_INTERVAL;
|
|
129
|
+
// An enabled loop hard-requires `session: acp` on every target. Adding one to a
|
|
130
|
+
// tmux agent would make the whole fleet unloadable, so it arrives switched off
|
|
131
|
+
// and badged instead — the owner turns it on after switching the agent to ACP.
|
|
132
|
+
if (node.enabled === false || !roles.every(role => sessionOf(model, role) === 'acp'))
|
|
133
|
+
entry.enabled = false;
|
|
134
|
+
return entry;
|
|
135
|
+
}
|
|
136
|
+
/** Names of the agents this node points at, from drawn edges and derived ones alike. */
|
|
137
|
+
function linked(merged, id, kind) {
|
|
138
|
+
return [...new Set(merged.edges
|
|
139
|
+
.filter(edge => edge.kind === kind && edge.from === id && !edge.implicit && !edge.dangling)
|
|
140
|
+
.map(edge => edge.to.slice('agent:'.length)))].sort();
|
|
141
|
+
}
|
|
142
|
+
function sessionOf(model, role) {
|
|
143
|
+
const roles = model.roles;
|
|
144
|
+
const defaults = model.defaults;
|
|
145
|
+
return String(roles?.[role]?.session ?? defaults?.session ?? 'tmux');
|
|
146
|
+
}
|
|
147
|
+
function section(model, key) {
|
|
148
|
+
const existing = model[key];
|
|
149
|
+
if (existing && typeof existing === 'object' && !Array.isArray(existing))
|
|
150
|
+
return existing;
|
|
151
|
+
const created = {};
|
|
152
|
+
model[key] = created;
|
|
153
|
+
return created;
|
|
154
|
+
}
|
|
155
|
+
/** Copy only the sketched fields that are real keys of the target mapping. */
|
|
156
|
+
function pick(node, keys) {
|
|
157
|
+
const fields = node.fields ?? {};
|
|
158
|
+
const out = {};
|
|
159
|
+
for (const key of keys) {
|
|
160
|
+
const value = fields[key];
|
|
161
|
+
if (value === undefined)
|
|
162
|
+
continue;
|
|
163
|
+
if (typeof value === 'string' && value.trim() === '')
|
|
164
|
+
continue;
|
|
165
|
+
out[key] = value;
|
|
166
|
+
}
|
|
167
|
+
return out;
|
|
168
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export type YamlModel = Record<string, unknown>;
|
|
2
|
+
/** Formatting of the source document, so a rewrite does not reflow it. */
|
|
3
|
+
export interface DocumentFormatting {
|
|
4
|
+
indent: number;
|
|
5
|
+
indentSeq: boolean;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Apply `model` to `source`, returning the new document text.
|
|
9
|
+
*
|
|
10
|
+
* Guarantees:
|
|
11
|
+
* - an unchanged model returns `source` byte for byte;
|
|
12
|
+
* - regions outside a change keep their bytes whenever the change is spliceable;
|
|
13
|
+
* - the result parses back to exactly `model`, verified before returning, so a
|
|
14
|
+
* planning bug can never silently write something else to disk.
|
|
15
|
+
*/
|
|
16
|
+
export declare function renderModelOntoSource(source: string, model: YamlModel): string;
|
|
17
|
+
/**
|
|
18
|
+
* Replace every `defaults.env` / `roles.*.env` value — and every `vars:` entry
|
|
19
|
+
* those values interpolate — with `marker`.
|
|
20
|
+
*
|
|
21
|
+
* Splices the scalars out of the original bytes rather than re-rendering, so the
|
|
22
|
+
* only difference from `source` is the secrets themselves. Redaction must not be
|
|
23
|
+
* able to introduce (or conceal) a formatting change in a review diff.
|
|
24
|
+
*/
|
|
25
|
+
export declare function redactSourceSecrets(source: string, marker: string): string;
|
|
26
|
+
/**
|
|
27
|
+
* Best-effort detection of the document's block indentation, used only when a
|
|
28
|
+
* change is not spliceable and the document has to be re-rendered. Detection
|
|
29
|
+
* failure falls back to the library defaults; correctness never depends on it.
|
|
30
|
+
*/
|
|
31
|
+
export declare function detectFormatting(source: string): DocumentFormatting;
|
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
import { Document, isMap, isPair, isScalar, isSeq, parseDocument, } from 'yaml';
|
|
2
|
+
/**
|
|
3
|
+
* Surgical fleet-YAML editing.
|
|
4
|
+
*
|
|
5
|
+
* The web console edits a JSON *model* of the configuration, but the file on
|
|
6
|
+
* disk belongs to the user. A `parse -> plain JS -> stringify` round-trip keeps
|
|
7
|
+
* the data and destroys everything around it. Re-rendering the parsed document
|
|
8
|
+
* is barely better: `yaml` keeps a comment's text but not its position, so
|
|
9
|
+
* `permissions: # note` comes back as `permissions:` with the note moved onto
|
|
10
|
+
* the next line, shifting everything below it. On `examples/fleet.yaml` a plain
|
|
11
|
+
* round-trip rewrites 80 of 147 lines.
|
|
12
|
+
*
|
|
13
|
+
* So edits are computed as *text splices* against the original bytes. Only the
|
|
14
|
+
* regions that genuinely changed are replaced; everything else is byte-identical
|
|
15
|
+
* by construction, which makes an unchanged save a true no-op and keeps a trailing
|
|
16
|
+
* inline comment attached to the scalar it annotates.
|
|
17
|
+
*
|
|
18
|
+
* The contract is deliberately narrower than "nothing else ever changes":
|
|
19
|
+
*
|
|
20
|
+
* - A *scalar* edit replaces only the value's source range, so the trailing
|
|
21
|
+
* comment beside it survives untouched.
|
|
22
|
+
* - Changing the *length* of a block sequence replaces that collection WHOLESALE,
|
|
23
|
+
* because an index-shifting edit cannot be attributed to particular items. Inline
|
|
24
|
+
* comments written on the individual items of that sequence are therefore LOST.
|
|
25
|
+
* The loss is bounded to the one collection being edited — every other line in
|
|
26
|
+
* the file keeps its bytes — and the caller diffs the real before/after text, so
|
|
27
|
+
* it is visible in review before anything is written and the save can be declined.
|
|
28
|
+
* - A model change that cannot be expressed as a splice at all falls back to
|
|
29
|
+
* re-rendering the whole document. That path does reflow, which is the other
|
|
30
|
+
* reason the caller diffs real bytes: reflow must be visible, never silent.
|
|
31
|
+
* - Deleting a mapping entry leaves any comment written above it in place. Removing
|
|
32
|
+
* a value must not quietly remove the operator's prose about it.
|
|
33
|
+
*/
|
|
34
|
+
const PARSE_OPTIONS = { strict: true, uniqueKeys: true, prettyErrors: true };
|
|
35
|
+
const MIN_INDENT = 1;
|
|
36
|
+
const MAX_INDENT = 8;
|
|
37
|
+
const DEFAULT_INDENT = 2;
|
|
38
|
+
/**
|
|
39
|
+
* Apply `model` to `source`, returning the new document text.
|
|
40
|
+
*
|
|
41
|
+
* Guarantees:
|
|
42
|
+
* - an unchanged model returns `source` byte for byte;
|
|
43
|
+
* - regions outside a change keep their bytes whenever the change is spliceable;
|
|
44
|
+
* - the result parses back to exactly `model`, verified before returning, so a
|
|
45
|
+
* planning bug can never silently write something else to disk.
|
|
46
|
+
*/
|
|
47
|
+
export function renderModelOntoSource(source, model) {
|
|
48
|
+
const document = parseSource(source);
|
|
49
|
+
const formatting = detectFormatting(source);
|
|
50
|
+
const plan = [];
|
|
51
|
+
const spliceable = planEdits(document, source, formatting, [], documentValue(document), model, plan);
|
|
52
|
+
const rendered = !spliceable ? reRender(document, source, formatting, model)
|
|
53
|
+
: plan.length === 0 ? source
|
|
54
|
+
: applySplices(source, plan);
|
|
55
|
+
const check = parseSource(rendered);
|
|
56
|
+
if (!deepEqual(documentValue(check), model))
|
|
57
|
+
throw new Error('surgical YAML edit did not reproduce the requested model');
|
|
58
|
+
return rendered;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Replace every `defaults.env` / `roles.*.env` value — and every `vars:` entry
|
|
62
|
+
* those values interpolate — with `marker`.
|
|
63
|
+
*
|
|
64
|
+
* Splices the scalars out of the original bytes rather than re-rendering, so the
|
|
65
|
+
* only difference from `source` is the secrets themselves. Redaction must not be
|
|
66
|
+
* able to introduce (or conceal) a formatting change in a review diff.
|
|
67
|
+
*/
|
|
68
|
+
export function redactSourceSecrets(source, marker) {
|
|
69
|
+
const document = parseSource(source);
|
|
70
|
+
const secretVars = new Set();
|
|
71
|
+
const plan = [];
|
|
72
|
+
const redactEnvMap = (path) => {
|
|
73
|
+
const node = document.getIn(path, true);
|
|
74
|
+
if (!isMap(node))
|
|
75
|
+
return;
|
|
76
|
+
for (const item of node.items) {
|
|
77
|
+
if (!isPair(item) || !isScalar(item.value))
|
|
78
|
+
continue;
|
|
79
|
+
if (typeof item.value.value === 'string')
|
|
80
|
+
for (const match of item.value.value.matchAll(/\$\{(\w+)\}/g))
|
|
81
|
+
secretVars.add(match[1]);
|
|
82
|
+
pushValueSplice(plan, item.value, marker);
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
redactEnvMap(['defaults', 'env']);
|
|
86
|
+
for (const name of mapKeys(document, ['roles']))
|
|
87
|
+
redactEnvMap(['roles', name, 'env']);
|
|
88
|
+
const vars = document.getIn(['vars'], true);
|
|
89
|
+
if (isMap(vars))
|
|
90
|
+
for (const item of vars.items) {
|
|
91
|
+
if (isPair(item) && isScalar(item.key) && isScalar(item.value)
|
|
92
|
+
&& secretVars.has(String(item.key.value)))
|
|
93
|
+
pushValueSplice(plan, item.value, marker);
|
|
94
|
+
}
|
|
95
|
+
return plan.length === 0 ? source : applySplices(source, plan);
|
|
96
|
+
}
|
|
97
|
+
function pushValueSplice(plan, node, marker) {
|
|
98
|
+
const range = node.range;
|
|
99
|
+
if (range)
|
|
100
|
+
plan.push({ start: range[0], end: range[1], text: marker });
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Best-effort detection of the document's block indentation, used only when a
|
|
104
|
+
* change is not spliceable and the document has to be re-rendered. Detection
|
|
105
|
+
* failure falls back to the library defaults; correctness never depends on it.
|
|
106
|
+
*/
|
|
107
|
+
export function detectFormatting(source) {
|
|
108
|
+
const lines = source.split('\n')
|
|
109
|
+
.filter(line => line.trim() !== '' && !line.trimStart().startsWith('#'));
|
|
110
|
+
let indent;
|
|
111
|
+
let indentSeq;
|
|
112
|
+
let previousWidth = 0;
|
|
113
|
+
let previousOpensBlock = false;
|
|
114
|
+
for (const line of lines) {
|
|
115
|
+
const width = line.length - line.trimStart().length;
|
|
116
|
+
const isItem = /^-(\s|$)/.test(line.trimStart());
|
|
117
|
+
if (previousOpensBlock && width >= previousWidth) {
|
|
118
|
+
if (isItem)
|
|
119
|
+
indentSeq ??= width > previousWidth;
|
|
120
|
+
if (width > previousWidth)
|
|
121
|
+
indent ??= width - previousWidth;
|
|
122
|
+
}
|
|
123
|
+
previousOpensBlock = !isItem && /:\s*(#.*)?$/.test(line);
|
|
124
|
+
previousWidth = width;
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
indent: indent !== undefined && indent >= MIN_INDENT && indent <= MAX_INDENT
|
|
128
|
+
? indent : DEFAULT_INDENT,
|
|
129
|
+
indentSeq: indentSeq ?? true,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
/* ------------------------------------------------------------------ *
|
|
133
|
+
* Planning: turn a model diff into text splices
|
|
134
|
+
* ------------------------------------------------------------------ */
|
|
135
|
+
/**
|
|
136
|
+
* Walk `next` against `current` and record the splices that would realise it.
|
|
137
|
+
* Returns false as soon as a change cannot be expressed against the source text,
|
|
138
|
+
* in which case the caller re-renders instead.
|
|
139
|
+
*/
|
|
140
|
+
function planEdits(document, source, formatting, path, current, next, plan) {
|
|
141
|
+
if (deepEqual(current, next))
|
|
142
|
+
return true;
|
|
143
|
+
if (isPlainObject(current) && isPlainObject(next)) {
|
|
144
|
+
const node = document.getIn(path, true);
|
|
145
|
+
if (!isMap(node))
|
|
146
|
+
return false;
|
|
147
|
+
// Removing every entry one by one would leave `watchdogs:` with no value,
|
|
148
|
+
// which reads back as null rather than the empty mapping that was asked for.
|
|
149
|
+
if (path.length > 0 && Object.keys(next).length === 0 && Object.keys(current).length > 0)
|
|
150
|
+
return planReplace(document, source, formatting, path, next, plan);
|
|
151
|
+
for (const key of Object.keys(current))
|
|
152
|
+
if (!hasOwn(next, key) && !planDelete(node, source, key, plan))
|
|
153
|
+
return false;
|
|
154
|
+
for (const key of Object.keys(next)) {
|
|
155
|
+
if (hasOwn(current, key)) {
|
|
156
|
+
if (!planEdits(document, source, formatting, [...path, key], own(current, key), own(next, key), plan))
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
else if (!planInsert(node, source, formatting, key, own(next, key), plan))
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
if (Array.isArray(current) && Array.isArray(next) && current.length === next.length)
|
|
165
|
+
return next.every((value, index) => planEdits(document, source, formatting, [...path, index], current[index], value, plan));
|
|
166
|
+
return planReplace(document, source, formatting, path, next, plan);
|
|
167
|
+
}
|
|
168
|
+
/** Replace one value in place, keeping the key, its layout and its comment. */
|
|
169
|
+
function planReplace(document, source, formatting, path, next, plan) {
|
|
170
|
+
if (path.length === 0)
|
|
171
|
+
return false;
|
|
172
|
+
const node = document.getIn(path, true);
|
|
173
|
+
if (!isNodeWithRange(node))
|
|
174
|
+
return false;
|
|
175
|
+
// A block scalar's body cannot be swapped for an inline one by splicing.
|
|
176
|
+
if (isScalar(node) && (node.type === 'BLOCK_LITERAL' || node.type === 'BLOCK_FOLDED'))
|
|
177
|
+
return false;
|
|
178
|
+
const start = node.range[0];
|
|
179
|
+
// A block collection's range runs past the newline that terminates it, while a
|
|
180
|
+
// scalar's stops at the value. Splicing over that newline would pull the next
|
|
181
|
+
// line up onto this one, so leave it where it is.
|
|
182
|
+
let end = node.range[1];
|
|
183
|
+
while (end > start && source[end - 1] === '\n')
|
|
184
|
+
end -= 1;
|
|
185
|
+
const ownLine = source.slice(lineStart(source, start), start).trim() === '';
|
|
186
|
+
// `roles: [Alice]` must not come back as a block list just because it grew.
|
|
187
|
+
const flow = (isSeq(node) || isMap(node)) && node.flow === true;
|
|
188
|
+
const text = renderValue(next, formatting, flow);
|
|
189
|
+
// An emptied collection belongs beside its key, not alone on the next line.
|
|
190
|
+
if (ownLine && (text === '{}' || text === '[]')
|
|
191
|
+
&& planCollapseOntoKey(document, source, path, start, end, text, plan))
|
|
192
|
+
return true;
|
|
193
|
+
if (text.includes('\n') && !ownLine)
|
|
194
|
+
return false;
|
|
195
|
+
plan.push({ start, end, text: ownLine ? indentContinuation(text, columnOf(source, start)) : text });
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
/** Rewrite `key:\n <block>` as `key: {}` when the block became empty. */
|
|
199
|
+
function planCollapseOntoKey(document, source, path, start, end, text, plan) {
|
|
200
|
+
const key = path[path.length - 1];
|
|
201
|
+
if (typeof key !== 'string')
|
|
202
|
+
return false;
|
|
203
|
+
const parent = document.getIn(path.slice(0, -1), true);
|
|
204
|
+
const pair = parent === undefined ? undefined : findPair(parent, key);
|
|
205
|
+
if (!pair || !isNodeWithRange(pair.key))
|
|
206
|
+
return false;
|
|
207
|
+
const colon = source.indexOf(':', pair.key.range[1]);
|
|
208
|
+
// Anything but whitespace between the key and its value is a comment; keep it.
|
|
209
|
+
if (colon < 0 || colon >= start || source.slice(colon + 1, start).trim() !== '')
|
|
210
|
+
return false;
|
|
211
|
+
plan.push({ start: colon + 1, end, text: ` ${text}` });
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
/** Remove a whole `key: value` entry, including the line it sits on. */
|
|
215
|
+
function planDelete(node, source, key, plan) {
|
|
216
|
+
const pair = findPair(node, key);
|
|
217
|
+
if (!pair || !isNodeWithRange(pair.key))
|
|
218
|
+
return false;
|
|
219
|
+
const end = isNodeWithRange(pair.value) ? pair.value.range[2] : pair.key.range[2];
|
|
220
|
+
plan.push({ start: lineStart(source, pair.key.range[0]), end, text: '' });
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
/** Append a new `key: value` entry to an existing block mapping. */
|
|
224
|
+
function planInsert(node, source, formatting, key, value, plan) {
|
|
225
|
+
if (!isMap(node) || node.flow)
|
|
226
|
+
return false;
|
|
227
|
+
const last = [...node.items].reverse().find(isPair);
|
|
228
|
+
// An empty mapping has no sibling to copy an indent or a position from.
|
|
229
|
+
if (!last || !isNodeWithRange(last.key))
|
|
230
|
+
return false;
|
|
231
|
+
const column = columnOf(source, last.key.range[0]);
|
|
232
|
+
const at = isNodeWithRange(last.value) ? last.value.range[2] : last.key.range[2];
|
|
233
|
+
const entry = indentContinuation(renderValue({ [key]: value }, formatting), column);
|
|
234
|
+
const prefix = at > 0 && source[at - 1] !== '\n' ? '\n' : '';
|
|
235
|
+
plan.push({ start: at, end: at, text: `${prefix}${' '.repeat(column)}${entry}\n` });
|
|
236
|
+
return true;
|
|
237
|
+
}
|
|
238
|
+
function applySplices(source, plan) {
|
|
239
|
+
const ordered = [...plan].sort((a, b) => b.start - a.start || b.end - a.end);
|
|
240
|
+
let out = source;
|
|
241
|
+
for (const splice of ordered)
|
|
242
|
+
out = out.slice(0, splice.start) + splice.text + out.slice(splice.end);
|
|
243
|
+
return out;
|
|
244
|
+
}
|
|
245
|
+
/* ------------------------------------------------------------------ *
|
|
246
|
+
* Fallback: re-render the whole document
|
|
247
|
+
* ------------------------------------------------------------------ */
|
|
248
|
+
function reRender(document, source, formatting, model) {
|
|
249
|
+
applyToDocument(document, [], documentValue(document), model);
|
|
250
|
+
return restoreCommentAlignment(source, document.toString({ lineWidth: 0, ...formatting }));
|
|
251
|
+
}
|
|
252
|
+
function applyToDocument(document, path, current, next) {
|
|
253
|
+
if (deepEqual(current, next))
|
|
254
|
+
return;
|
|
255
|
+
if (isPlainObject(current) && isPlainObject(next)) {
|
|
256
|
+
for (const key of Object.keys(next))
|
|
257
|
+
applyToDocument(document, [...path, key], own(current, key), own(next, key));
|
|
258
|
+
for (const key of Object.keys(current))
|
|
259
|
+
if (!hasOwn(next, key))
|
|
260
|
+
document.deleteIn([...path, key]);
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
if (Array.isArray(current) && Array.isArray(next) && current.length === next.length) {
|
|
264
|
+
for (const [index, value] of next.entries())
|
|
265
|
+
applyToDocument(document, [...path, index], current[index], value);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
if (next === undefined) {
|
|
269
|
+
document.deleteIn(path);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
// Mutating an existing scalar keeps its comment and quoting; replacing it loses both.
|
|
273
|
+
const existing = document.getIn(path, true);
|
|
274
|
+
if (isScalar(existing) && isScalarValue(next) && existing.type !== 'BLOCK_LITERAL' && existing.type !== 'BLOCK_FOLDED') {
|
|
275
|
+
existing.value = next;
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
if (path.length === 0)
|
|
279
|
+
document.contents = document.createNode(next);
|
|
280
|
+
else
|
|
281
|
+
document.setIn(path, document.createNode(next));
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* `yaml` keeps a comment's text but not the padding in front of it. Put the
|
|
285
|
+
* original spacing back on lines that are otherwise byte-identical to the
|
|
286
|
+
* source; ambiguous or altered lines are left as rendered, and the caller
|
|
287
|
+
* re-parses the result, so this can never change what the file means.
|
|
288
|
+
*/
|
|
289
|
+
function restoreCommentAlignment(source, rendered) {
|
|
290
|
+
const AMBIGUOUS = '';
|
|
291
|
+
const originals = new Map();
|
|
292
|
+
for (const line of source.split('\n')) {
|
|
293
|
+
const at = trailingCommentIndex(line);
|
|
294
|
+
if (at < 0)
|
|
295
|
+
continue;
|
|
296
|
+
const collapsed = `${line.slice(0, at).replace(/\s+$/, '')} ${line.slice(at)}`;
|
|
297
|
+
if (collapsed === line)
|
|
298
|
+
continue;
|
|
299
|
+
originals.set(collapsed, originals.has(collapsed) ? AMBIGUOUS : line);
|
|
300
|
+
}
|
|
301
|
+
if (originals.size === 0)
|
|
302
|
+
return rendered;
|
|
303
|
+
return rendered.split('\n').map(line => originals.get(line) || line).join('\n');
|
|
304
|
+
}
|
|
305
|
+
/** Index of a line's trailing `#` comment, ignoring `#` inside quoted scalars. */
|
|
306
|
+
function trailingCommentIndex(line) {
|
|
307
|
+
let quote;
|
|
308
|
+
for (let index = 0; index < line.length; index += 1) {
|
|
309
|
+
const character = line[index];
|
|
310
|
+
if (quote === "'") {
|
|
311
|
+
if (character !== "'")
|
|
312
|
+
continue;
|
|
313
|
+
if (line[index + 1] === "'")
|
|
314
|
+
index += 1;
|
|
315
|
+
else
|
|
316
|
+
quote = undefined;
|
|
317
|
+
}
|
|
318
|
+
else if (quote === '"') {
|
|
319
|
+
if (character === '\\')
|
|
320
|
+
index += 1;
|
|
321
|
+
else if (character === '"')
|
|
322
|
+
quote = undefined;
|
|
323
|
+
}
|
|
324
|
+
else if (character === '"' || character === "'")
|
|
325
|
+
quote = character;
|
|
326
|
+
else if (character === '#' && index > 0 && /\s/.test(line[index - 1]))
|
|
327
|
+
return index;
|
|
328
|
+
}
|
|
329
|
+
return -1;
|
|
330
|
+
}
|
|
331
|
+
/* ------------------------------------------------------------------ *
|
|
332
|
+
* Shared helpers
|
|
333
|
+
* ------------------------------------------------------------------ */
|
|
334
|
+
function parseSource(source) {
|
|
335
|
+
const document = parseDocument(source, PARSE_OPTIONS);
|
|
336
|
+
if (document.errors.length)
|
|
337
|
+
throw new Error(document.errors.map(error => error.message).join('; '));
|
|
338
|
+
return document;
|
|
339
|
+
}
|
|
340
|
+
function documentValue(document) {
|
|
341
|
+
return document.contents == null ? {} : document.toJS({ maxAliasCount: 100 });
|
|
342
|
+
}
|
|
343
|
+
function mapKeys(document, path) {
|
|
344
|
+
const node = document.getIn(path, true);
|
|
345
|
+
if (!isMap(node))
|
|
346
|
+
return [];
|
|
347
|
+
return node.items.flatMap(item => (isPair(item) && isScalar(item.key) ? [String(item.key.value)] : []));
|
|
348
|
+
}
|
|
349
|
+
function findPair(node, key) {
|
|
350
|
+
if (!isMap(node))
|
|
351
|
+
return undefined;
|
|
352
|
+
return node.items.find((item) => isPair(item) && isScalar(item.key) && String(item.key.value) === key);
|
|
353
|
+
}
|
|
354
|
+
function renderValue(value, formatting, flow = false) {
|
|
355
|
+
const document = new Document(value);
|
|
356
|
+
if (flow && (isSeq(document.contents) || isMap(document.contents)))
|
|
357
|
+
document.contents.flow = true;
|
|
358
|
+
return document
|
|
359
|
+
.toString({ lineWidth: 0, indent: formatting.indent, indentSeq: formatting.indentSeq })
|
|
360
|
+
.replace(/\n$/, '');
|
|
361
|
+
}
|
|
362
|
+
/** Indent every line but the first, which is spliced in after existing text. */
|
|
363
|
+
function indentContinuation(text, column) {
|
|
364
|
+
if (!text.includes('\n'))
|
|
365
|
+
return text;
|
|
366
|
+
const pad = ' '.repeat(column);
|
|
367
|
+
return text.split('\n').map((line, index) => (index === 0 || line === '' ? line : `${pad}${line}`)).join('\n');
|
|
368
|
+
}
|
|
369
|
+
const lineStart = (source, offset) => source.lastIndexOf('\n', offset - 1) + 1;
|
|
370
|
+
const columnOf = (source, offset) => offset - lineStart(source, offset);
|
|
371
|
+
function isNodeWithRange(node) {
|
|
372
|
+
return (isScalar(node) || isMap(node) || isSeq(node)) && Array.isArray(node.range);
|
|
373
|
+
}
|
|
374
|
+
function isScalarValue(value) {
|
|
375
|
+
return value === null || ['string', 'number', 'boolean'].includes(typeof value);
|
|
376
|
+
}
|
|
377
|
+
function isPlainObject(value) {
|
|
378
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
379
|
+
}
|
|
380
|
+
function hasOwn(value, key) {
|
|
381
|
+
return Object.prototype.hasOwnProperty.call(value, key);
|
|
382
|
+
}
|
|
383
|
+
function own(value, key) {
|
|
384
|
+
return hasOwn(value, key) ? value[key] : undefined;
|
|
385
|
+
}
|
|
386
|
+
function deepEqual(a, b) {
|
|
387
|
+
if (a === b)
|
|
388
|
+
return true;
|
|
389
|
+
if (typeof a === 'number' && typeof b === 'number')
|
|
390
|
+
return Number.isNaN(a) && Number.isNaN(b);
|
|
391
|
+
if (typeof a !== 'object' || typeof b !== 'object' || a === null || b === null)
|
|
392
|
+
return false;
|
|
393
|
+
if (Array.isArray(a) !== Array.isArray(b))
|
|
394
|
+
return false;
|
|
395
|
+
if (Array.isArray(a) && Array.isArray(b))
|
|
396
|
+
return a.length === b.length && a.every((item, index) => deepEqual(item, b[index]));
|
|
397
|
+
const [left, right] = [a, b];
|
|
398
|
+
const keys = Object.keys(left);
|
|
399
|
+
return keys.length === Object.keys(right).length
|
|
400
|
+
&& keys.every(key => hasOwn(right, key) && deepEqual(left[key], right[key]));
|
|
401
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{r as le,a as Ee,j as re}from"./index-
|
|
1
|
+
import{r as le,a as Ee,j as re}from"./index-CsHEL0f6.js";var ge={exports:{}},Se;function ke(){return Se||(Se=1,(function(se,ne){(function(Q,X){se.exports=X()})(globalThis,(()=>(()=>{var Q={4567:function(B,r,o){var l=this&&this.__decorate||function(e,i,a,v){var f,g=arguments.length,c=g<3?i:v===null?v=Object.getOwnPropertyDescriptor(i,a):v;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")c=Reflect.decorate(e,i,a,v);else for(var m=e.length-1;m>=0;m--)(f=e[m])&&(c=(g<3?f(c):g>3?f(i,a,c):f(i,a))||c);return g>3&&c&&Object.defineProperty(i,a,c),c},_=this&&this.__param||function(e,i){return function(a,v){i(a,v,e)}};Object.defineProperty(r,"__esModule",{value:!0}),r.AccessibilityManager=void 0;const n=o(9042),d=o(9924),u=o(844),p=o(4725),h=o(2585),t=o(3656);let s=r.AccessibilityManager=class extends u.Disposable{constructor(e,i,a,v){super(),this._terminal=e,this._coreBrowserService=a,this._renderService=v,this._rowColumns=new WeakMap,this._liveRegionLineCount=0,this._charsToConsume=[],this._charsToAnnounce="",this._accessibilityContainer=this._coreBrowserService.mainDocument.createElement("div"),this._accessibilityContainer.classList.add("xterm-accessibility"),this._rowContainer=this._coreBrowserService.mainDocument.createElement("div"),this._rowContainer.setAttribute("role","list"),this._rowContainer.classList.add("xterm-accessibility-tree"),this._rowElements=[];for(let f=0;f<this._terminal.rows;f++)this._rowElements[f]=this._createAccessibilityTreeNode(),this._rowContainer.appendChild(this._rowElements[f]);if(this._topBoundaryFocusListener=f=>this._handleBoundaryFocus(f,0),this._bottomBoundaryFocusListener=f=>this._handleBoundaryFocus(f,1),this._rowElements[0].addEventListener("focus",this._topBoundaryFocusListener),this._rowElements[this._rowElements.length-1].addEventListener("focus",this._bottomBoundaryFocusListener),this._refreshRowsDimensions(),this._accessibilityContainer.appendChild(this._rowContainer),this._liveRegion=this._coreBrowserService.mainDocument.createElement("div"),this._liveRegion.classList.add("live-region"),this._liveRegion.setAttribute("aria-live","assertive"),this._accessibilityContainer.appendChild(this._liveRegion),this._liveRegionDebouncer=this.register(new d.TimeBasedDebouncer(this._renderRows.bind(this))),!this._terminal.element)throw new Error("Cannot enable accessibility before Terminal.open");this._terminal.element.insertAdjacentElement("afterbegin",this._accessibilityContainer),this.register(this._terminal.onResize((f=>this._handleResize(f.rows)))),this.register(this._terminal.onRender((f=>this._refreshRows(f.start,f.end)))),this.register(this._terminal.onScroll((()=>this._refreshRows()))),this.register(this._terminal.onA11yChar((f=>this._handleChar(f)))),this.register(this._terminal.onLineFeed((()=>this._handleChar(`
|
|
2
2
|
`)))),this.register(this._terminal.onA11yTab((f=>this._handleTab(f)))),this.register(this._terminal.onKey((f=>this._handleKey(f.key)))),this.register(this._terminal.onBlur((()=>this._clearLiveRegion()))),this.register(this._renderService.onDimensionsChange((()=>this._refreshRowsDimensions()))),this.register((0,t.addDisposableDomListener)(document,"selectionchange",(()=>this._handleSelectionChange()))),this.register(this._coreBrowserService.onDprChange((()=>this._refreshRowsDimensions()))),this._refreshRows(),this.register((0,u.toDisposable)((()=>{this._accessibilityContainer.remove(),this._rowElements.length=0})))}_handleTab(e){for(let i=0;i<e;i++)this._handleChar(" ")}_handleChar(e){this._liveRegionLineCount<21&&(this._charsToConsume.length>0?this._charsToConsume.shift()!==e&&(this._charsToAnnounce+=e):this._charsToAnnounce+=e,e===`
|
|
3
3
|
`&&(this._liveRegionLineCount++,this._liveRegionLineCount===21&&(this._liveRegion.textContent+=n.tooMuchOutput)))}_clearLiveRegion(){this._liveRegion.textContent="",this._liveRegionLineCount=0}_handleKey(e){this._clearLiveRegion(),new RegExp("\\p{Control}","u").test(e)||this._charsToConsume.push(e)}_refreshRows(e,i){this._liveRegionDebouncer.refresh(e,i,this._terminal.rows)}_renderRows(e,i){const a=this._terminal.buffer,v=a.lines.length.toString();for(let f=e;f<=i;f++){const g=a.lines.get(a.ydisp+f),c=[],m=g?.translateToString(!0,void 0,void 0,c)||"",E=(a.ydisp+f+1).toString(),k=this._rowElements[f];k&&(m.length===0?(k.innerText=" ",this._rowColumns.set(k,[0,1])):(k.textContent=m,this._rowColumns.set(k,c)),k.setAttribute("aria-posinset",E),k.setAttribute("aria-setsize",v))}this._announceCharacters()}_announceCharacters(){this._charsToAnnounce.length!==0&&(this._liveRegion.textContent+=this._charsToAnnounce,this._charsToAnnounce="")}_handleBoundaryFocus(e,i){const a=e.target,v=this._rowElements[i===0?1:this._rowElements.length-2];if(a.getAttribute("aria-posinset")===(i===0?"1":`${this._terminal.buffer.lines.length}`)||e.relatedTarget!==v)return;let f,g;if(i===0?(f=a,g=this._rowElements.pop(),this._rowContainer.removeChild(g)):(f=this._rowElements.shift(),g=a,this._rowContainer.removeChild(f)),f.removeEventListener("focus",this._topBoundaryFocusListener),g.removeEventListener("focus",this._bottomBoundaryFocusListener),i===0){const c=this._createAccessibilityTreeNode();this._rowElements.unshift(c),this._rowContainer.insertAdjacentElement("afterbegin",c)}else{const c=this._createAccessibilityTreeNode();this._rowElements.push(c),this._rowContainer.appendChild(c)}this._rowElements[0].addEventListener("focus",this._topBoundaryFocusListener),this._rowElements[this._rowElements.length-1].addEventListener("focus",this._bottomBoundaryFocusListener),this._terminal.scrollLines(i===0?-1:1),this._rowElements[i===0?1:this._rowElements.length-2].focus(),e.preventDefault(),e.stopImmediatePropagation()}_handleSelectionChange(){if(this._rowElements.length===0)return;const e=document.getSelection();if(!e)return;if(e.isCollapsed)return void(this._rowContainer.contains(e.anchorNode)&&this._terminal.clearSelection());if(!e.anchorNode||!e.focusNode)return void console.error("anchorNode and/or focusNode are null");let i={node:e.anchorNode,offset:e.anchorOffset},a={node:e.focusNode,offset:e.focusOffset};if((i.node.compareDocumentPosition(a.node)&Node.DOCUMENT_POSITION_PRECEDING||i.node===a.node&&i.offset>a.offset)&&([i,a]=[a,i]),i.node.compareDocumentPosition(this._rowElements[0])&(Node.DOCUMENT_POSITION_CONTAINED_BY|Node.DOCUMENT_POSITION_FOLLOWING)&&(i={node:this._rowElements[0].childNodes[0],offset:0}),!this._rowContainer.contains(i.node))return;const v=this._rowElements.slice(-1)[0];if(a.node.compareDocumentPosition(v)&(Node.DOCUMENT_POSITION_CONTAINED_BY|Node.DOCUMENT_POSITION_PRECEDING)&&(a={node:v,offset:v.textContent?.length??0}),!this._rowContainer.contains(a.node))return;const f=({node:m,offset:E})=>{const k=m instanceof Text?m.parentNode:m;let D=parseInt(k?.getAttribute("aria-posinset"),10)-1;if(isNaN(D))return console.warn("row is invalid. Race condition?"),null;const b=this._rowColumns.get(k);if(!b)return console.warn("columns is null. Race condition?"),null;let x=E<b.length?b[E]:b.slice(-1)[0]+1;return x>=this._terminal.cols&&(++D,x=0),{row:D,column:x}},g=f(i),c=f(a);if(g&&c){if(g.row>c.row||g.row===c.row&&g.column>=c.column)throw new Error("invalid range");this._terminal.select(g.column,g.row,(c.row-g.row)*this._terminal.cols-g.column+c.column)}}_handleResize(e){this._rowElements[this._rowElements.length-1].removeEventListener("focus",this._bottomBoundaryFocusListener);for(let i=this._rowContainer.children.length;i<this._terminal.rows;i++)this._rowElements[i]=this._createAccessibilityTreeNode(),this._rowContainer.appendChild(this._rowElements[i]);for(;this._rowElements.length>e;)this._rowContainer.removeChild(this._rowElements.pop());this._rowElements[this._rowElements.length-1].addEventListener("focus",this._bottomBoundaryFocusListener),this._refreshRowsDimensions()}_createAccessibilityTreeNode(){const e=this._coreBrowserService.mainDocument.createElement("div");return e.setAttribute("role","listitem"),e.tabIndex=-1,this._refreshRowDimensions(e),e}_refreshRowsDimensions(){if(this._renderService.dimensions.css.cell.height){this._accessibilityContainer.style.width=`${this._renderService.dimensions.css.canvas.width}px`,this._rowElements.length!==this._terminal.rows&&this._handleResize(this._terminal.rows);for(let e=0;e<this._terminal.rows;e++)this._refreshRowDimensions(this._rowElements[e])}}_refreshRowDimensions(e){e.style.height=`${this._renderService.dimensions.css.cell.height}px`}};r.AccessibilityManager=s=l([_(1,h.IInstantiationService),_(2,p.ICoreBrowserService),_(3,p.IRenderService)],s)},3614:(B,r)=>{function o(d){return d.replace(/\r?\n/g,"\r")}function l(d,u){return u?"\x1B[200~"+d+"\x1B[201~":d}function _(d,u,p,h){d=l(d=o(d),p.decPrivateModes.bracketedPasteMode&&h.rawOptions.ignoreBracketedPasteMode!==!0),p.triggerDataEvent(d,!0),u.value=""}function n(d,u,p){const h=p.getBoundingClientRect(),t=d.clientX-h.left-10,s=d.clientY-h.top-10;u.style.width="20px",u.style.height="20px",u.style.left=`${t}px`,u.style.top=`${s}px`,u.style.zIndex="1000",u.focus()}Object.defineProperty(r,"__esModule",{value:!0}),r.rightClickHandler=r.moveTextAreaUnderMouseCursor=r.paste=r.handlePasteEvent=r.copyHandler=r.bracketTextForPaste=r.prepareTextForTerminal=void 0,r.prepareTextForTerminal=o,r.bracketTextForPaste=l,r.copyHandler=function(d,u){d.clipboardData&&d.clipboardData.setData("text/plain",u.selectionText),d.preventDefault()},r.handlePasteEvent=function(d,u,p,h){d.stopPropagation(),d.clipboardData&&_(d.clipboardData.getData("text/plain"),u,p,h)},r.paste=_,r.moveTextAreaUnderMouseCursor=n,r.rightClickHandler=function(d,u,p,h,t){n(d,u,p),t&&h.rightClickSelect(d),u.value=h.selectionText,u.select()}},7239:(B,r,o)=>{Object.defineProperty(r,"__esModule",{value:!0}),r.ColorContrastCache=void 0;const l=o(1505);r.ColorContrastCache=class{constructor(){this._color=new l.TwoKeyMap,this._css=new l.TwoKeyMap}setCss(_,n,d){this._css.set(_,n,d)}getCss(_,n){return this._css.get(_,n)}setColor(_,n,d){this._color.set(_,n,d)}getColor(_,n){return this._color.get(_,n)}clear(){this._color.clear(),this._css.clear()}}},3656:(B,r)=>{Object.defineProperty(r,"__esModule",{value:!0}),r.addDisposableDomListener=void 0,r.addDisposableDomListener=function(o,l,_,n){o.addEventListener(l,_,n);let d=!1;return{dispose:()=>{d||(d=!0,o.removeEventListener(l,_,n))}}}},3551:function(B,r,o){var l=this&&this.__decorate||function(s,e,i,a){var v,f=arguments.length,g=f<3?e:a===null?a=Object.getOwnPropertyDescriptor(e,i):a;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")g=Reflect.decorate(s,e,i,a);else for(var c=s.length-1;c>=0;c--)(v=s[c])&&(g=(f<3?v(g):f>3?v(e,i,g):v(e,i))||g);return f>3&&g&&Object.defineProperty(e,i,g),g},_=this&&this.__param||function(s,e){return function(i,a){e(i,a,s)}};Object.defineProperty(r,"__esModule",{value:!0}),r.Linkifier=void 0;const n=o(3656),d=o(8460),u=o(844),p=o(2585),h=o(4725);let t=r.Linkifier=class extends u.Disposable{get currentLink(){return this._currentLink}constructor(s,e,i,a,v){super(),this._element=s,this._mouseService=e,this._renderService=i,this._bufferService=a,this._linkProviderService=v,this._linkCacheDisposables=[],this._isMouseOut=!0,this._wasResized=!1,this._activeLine=-1,this._onShowLinkUnderline=this.register(new d.EventEmitter),this.onShowLinkUnderline=this._onShowLinkUnderline.event,this._onHideLinkUnderline=this.register(new d.EventEmitter),this.onHideLinkUnderline=this._onHideLinkUnderline.event,this.register((0,u.getDisposeArrayDisposable)(this._linkCacheDisposables)),this.register((0,u.toDisposable)((()=>{this._lastMouseEvent=void 0,this._activeProviderReplies?.clear()}))),this.register(this._bufferService.onResize((()=>{this._clearCurrentLink(),this._wasResized=!0}))),this.register((0,n.addDisposableDomListener)(this._element,"mouseleave",(()=>{this._isMouseOut=!0,this._clearCurrentLink()}))),this.register((0,n.addDisposableDomListener)(this._element,"mousemove",this._handleMouseMove.bind(this))),this.register((0,n.addDisposableDomListener)(this._element,"mousedown",this._handleMouseDown.bind(this))),this.register((0,n.addDisposableDomListener)(this._element,"mouseup",this._handleMouseUp.bind(this)))}_handleMouseMove(s){this._lastMouseEvent=s;const e=this._positionFromMouseEvent(s,this._element,this._mouseService);if(!e)return;this._isMouseOut=!1;const i=s.composedPath();for(let a=0;a<i.length;a++){const v=i[a];if(v.classList.contains("xterm"))break;if(v.classList.contains("xterm-hover"))return}this._lastBufferCell&&e.x===this._lastBufferCell.x&&e.y===this._lastBufferCell.y||(this._handleHover(e),this._lastBufferCell=e)}_handleHover(s){if(this._activeLine!==s.y||this._wasResized)return this._clearCurrentLink(),this._askForLink(s,!1),void(this._wasResized=!1);this._currentLink&&this._linkAtPosition(this._currentLink.link,s)||(this._clearCurrentLink(),this._askForLink(s,!0))}_askForLink(s,e){this._activeProviderReplies&&e||(this._activeProviderReplies?.forEach((a=>{a?.forEach((v=>{v.link.dispose&&v.link.dispose()}))})),this._activeProviderReplies=new Map,this._activeLine=s.y);let i=!1;for(const[a,v]of this._linkProviderService.linkProviders.entries())e?this._activeProviderReplies?.get(a)&&(i=this._checkLinkProviderResult(a,s,i)):v.provideLinks(s.y,(f=>{if(this._isMouseOut)return;const g=f?.map((c=>({link:c})));this._activeProviderReplies?.set(a,g),i=this._checkLinkProviderResult(a,s,i),this._activeProviderReplies?.size===this._linkProviderService.linkProviders.length&&this._removeIntersectingLinks(s.y,this._activeProviderReplies)}))}_removeIntersectingLinks(s,e){const i=new Set;for(let a=0;a<e.size;a++){const v=e.get(a);if(v)for(let f=0;f<v.length;f++){const g=v[f],c=g.link.range.start.y<s?0:g.link.range.start.x,m=g.link.range.end.y>s?this._bufferService.cols:g.link.range.end.x;for(let E=c;E<=m;E++){if(i.has(E)){v.splice(f--,1);break}i.add(E)}}}}_checkLinkProviderResult(s,e,i){if(!this._activeProviderReplies)return i;const a=this._activeProviderReplies.get(s);let v=!1;for(let f=0;f<s;f++)this._activeProviderReplies.has(f)&&!this._activeProviderReplies.get(f)||(v=!0);if(!v&&a){const f=a.find((g=>this._linkAtPosition(g.link,e)));f&&(i=!0,this._handleNewLink(f))}if(this._activeProviderReplies.size===this._linkProviderService.linkProviders.length&&!i)for(let f=0;f<this._activeProviderReplies.size;f++){const g=this._activeProviderReplies.get(f)?.find((c=>this._linkAtPosition(c.link,e)));if(g){i=!0,this._handleNewLink(g);break}}return i}_handleMouseDown(){this._mouseDownLink=this._currentLink}_handleMouseUp(s){if(!this._currentLink)return;const e=this._positionFromMouseEvent(s,this._element,this._mouseService);e&&this._mouseDownLink===this._currentLink&&this._linkAtPosition(this._currentLink.link,e)&&this._currentLink.link.activate(s,this._currentLink.link.text)}_clearCurrentLink(s,e){this._currentLink&&this._lastMouseEvent&&(!s||!e||this._currentLink.link.range.start.y>=s&&this._currentLink.link.range.end.y<=e)&&(this._linkLeave(this._element,this._currentLink.link,this._lastMouseEvent),this._currentLink=void 0,(0,u.disposeArray)(this._linkCacheDisposables))}_handleNewLink(s){if(!this._lastMouseEvent)return;const e=this._positionFromMouseEvent(this._lastMouseEvent,this._element,this._mouseService);e&&this._linkAtPosition(s.link,e)&&(this._currentLink=s,this._currentLink.state={decorations:{underline:s.link.decorations===void 0||s.link.decorations.underline,pointerCursor:s.link.decorations===void 0||s.link.decorations.pointerCursor},isHovered:!0},this._linkHover(this._element,s.link,this._lastMouseEvent),s.link.decorations={},Object.defineProperties(s.link.decorations,{pointerCursor:{get:()=>this._currentLink?.state?.decorations.pointerCursor,set:i=>{this._currentLink?.state&&this._currentLink.state.decorations.pointerCursor!==i&&(this._currentLink.state.decorations.pointerCursor=i,this._currentLink.state.isHovered&&this._element.classList.toggle("xterm-cursor-pointer",i))}},underline:{get:()=>this._currentLink?.state?.decorations.underline,set:i=>{this._currentLink?.state&&this._currentLink?.state?.decorations.underline!==i&&(this._currentLink.state.decorations.underline=i,this._currentLink.state.isHovered&&this._fireUnderlineEvent(s.link,i))}}}),this._linkCacheDisposables.push(this._renderService.onRenderedViewportChange((i=>{if(!this._currentLink)return;const a=i.start===0?0:i.start+1+this._bufferService.buffer.ydisp,v=this._bufferService.buffer.ydisp+1+i.end;if(this._currentLink.link.range.start.y>=a&&this._currentLink.link.range.end.y<=v&&(this._clearCurrentLink(a,v),this._lastMouseEvent)){const f=this._positionFromMouseEvent(this._lastMouseEvent,this._element,this._mouseService);f&&this._askForLink(f,!1)}}))))}_linkHover(s,e,i){this._currentLink?.state&&(this._currentLink.state.isHovered=!0,this._currentLink.state.decorations.underline&&this._fireUnderlineEvent(e,!0),this._currentLink.state.decorations.pointerCursor&&s.classList.add("xterm-cursor-pointer")),e.hover&&e.hover(i,e.text)}_fireUnderlineEvent(s,e){const i=s.range,a=this._bufferService.buffer.ydisp,v=this._createLinkUnderlineEvent(i.start.x-1,i.start.y-a-1,i.end.x,i.end.y-a-1,void 0);(e?this._onShowLinkUnderline:this._onHideLinkUnderline).fire(v)}_linkLeave(s,e,i){this._currentLink?.state&&(this._currentLink.state.isHovered=!1,this._currentLink.state.decorations.underline&&this._fireUnderlineEvent(e,!1),this._currentLink.state.decorations.pointerCursor&&s.classList.remove("xterm-cursor-pointer")),e.leave&&e.leave(i,e.text)}_linkAtPosition(s,e){const i=s.range.start.y*this._bufferService.cols+s.range.start.x,a=s.range.end.y*this._bufferService.cols+s.range.end.x,v=e.y*this._bufferService.cols+e.x;return i<=v&&v<=a}_positionFromMouseEvent(s,e,i){const a=i.getCoords(s,e,this._bufferService.cols,this._bufferService.rows);if(a)return{x:a[0],y:a[1]+this._bufferService.buffer.ydisp}}_createLinkUnderlineEvent(s,e,i,a,v){return{x1:s,y1:e,x2:i,y2:a,cols:this._bufferService.cols,fg:v}}};r.Linkifier=t=l([_(1,h.IMouseService),_(2,h.IRenderService),_(3,p.IBufferService),_(4,h.ILinkProviderService)],t)},9042:(B,r)=>{Object.defineProperty(r,"__esModule",{value:!0}),r.tooMuchOutput=r.promptLabel=void 0,r.promptLabel="Terminal input",r.tooMuchOutput="Too much output to announce, navigate to rows manually to read"},3730:function(B,r,o){var l=this&&this.__decorate||function(h,t,s,e){var i,a=arguments.length,v=a<3?t:e===null?e=Object.getOwnPropertyDescriptor(t,s):e;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")v=Reflect.decorate(h,t,s,e);else for(var f=h.length-1;f>=0;f--)(i=h[f])&&(v=(a<3?i(v):a>3?i(t,s,v):i(t,s))||v);return a>3&&v&&Object.defineProperty(t,s,v),v},_=this&&this.__param||function(h,t){return function(s,e){t(s,e,h)}};Object.defineProperty(r,"__esModule",{value:!0}),r.OscLinkProvider=void 0;const n=o(511),d=o(2585);let u=r.OscLinkProvider=class{constructor(h,t,s){this._bufferService=h,this._optionsService=t,this._oscLinkService=s}provideLinks(h,t){const s=this._bufferService.buffer.lines.get(h-1);if(!s)return void t(void 0);const e=[],i=this._optionsService.rawOptions.linkHandler,a=new n.CellData,v=s.getTrimmedLength();let f=-1,g=-1,c=!1;for(let m=0;m<v;m++)if(g!==-1||s.hasContent(m)){if(s.loadCell(m,a),a.hasExtendedAttrs()&&a.extended.urlId){if(g===-1){g=m,f=a.extended.urlId;continue}c=a.extended.urlId!==f}else g!==-1&&(c=!0);if(c||g!==-1&&m===v-1){const E=this._oscLinkService.getLinkData(f)?.uri;if(E){const k={start:{x:g+1,y:h},end:{x:m+(c||m!==v-1?0:1),y:h}};let D=!1;if(!i?.allowNonHttpProtocols)try{const b=new URL(E);["http:","https:"].includes(b.protocol)||(D=!0)}catch{D=!0}D||e.push({text:E,range:k,activate:(b,x)=>i?i.activate(b,x,k):p(0,x),hover:(b,x)=>i?.hover?.(b,x,k),leave:(b,x)=>i?.leave?.(b,x,k)})}c=!1,a.hasExtendedAttrs()&&a.extended.urlId?(g=m,f=a.extended.urlId):(g=-1,f=-1)}}t(e)}};function p(h,t){if(confirm(`Do you want to navigate to ${t}?
|
|
4
4
|
|