@kalada/codemirror 0.1.0 → 0.2.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 +126 -1
- package/dist/chunk-GCHU6EL7.js +566 -0
- package/dist/chunk-GCHU6EL7.js.map +1 -0
- package/dist/editor.cjs +568 -0
- package/dist/editor.cjs.map +1 -0
- package/dist/editor.d.cts +74 -0
- package/dist/editor.d.ts +74 -0
- package/dist/editor.js +3 -0
- package/dist/editor.js.map +1 -0
- package/dist/index.cjs +717 -358
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +166 -365
- package/dist/index.js.map +1 -1
- package/package.json +11 -1
package/dist/index.cjs
CHANGED
|
@@ -1,12 +1,112 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var autocomplete = require('@codemirror/autocomplete');
|
|
4
3
|
var commands = require('@codemirror/commands');
|
|
5
|
-
var lint = require('@codemirror/lint');
|
|
6
4
|
var state = require('@codemirror/state');
|
|
7
5
|
var view = require('@codemirror/view');
|
|
6
|
+
var autocomplete = require('@codemirror/autocomplete');
|
|
7
|
+
var lint = require('@codemirror/lint');
|
|
8
8
|
|
|
9
|
-
// src/session.ts
|
|
9
|
+
// src/editor-session.ts
|
|
10
|
+
|
|
11
|
+
// src/editor-presentation.ts
|
|
12
|
+
function editorTooltip(hover, from, to) {
|
|
13
|
+
return {
|
|
14
|
+
pos: from,
|
|
15
|
+
end: to,
|
|
16
|
+
above: true,
|
|
17
|
+
create(view) {
|
|
18
|
+
const document = view.dom.ownerDocument;
|
|
19
|
+
const dom = document.createElement("section");
|
|
20
|
+
dom.className = "cm-editor-hover";
|
|
21
|
+
dom.setAttribute("role", "tooltip");
|
|
22
|
+
if (typeof hover.content === "string") dom.textContent = hover.content;
|
|
23
|
+
else for (const section of hover.content) appendSection(document, dom, section);
|
|
24
|
+
return { dom };
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function appendSection(document, dom, section) {
|
|
29
|
+
const heading = document.createElement("strong");
|
|
30
|
+
heading.textContent = section.heading;
|
|
31
|
+
dom.append(heading);
|
|
32
|
+
const list = document.createElement("ul");
|
|
33
|
+
for (const text of section.text.length ? section.text : ["None"]) {
|
|
34
|
+
const item = document.createElement("li");
|
|
35
|
+
item.textContent = text;
|
|
36
|
+
list.append(item);
|
|
37
|
+
}
|
|
38
|
+
dom.append(list);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// src/editor-requests.ts
|
|
42
|
+
var EditorRequests = class {
|
|
43
|
+
pending = /* @__PURE__ */ new Map();
|
|
44
|
+
// Published completion edits remain guarded until supersession, not just settlement.
|
|
45
|
+
latest = /* @__PURE__ */ new Map();
|
|
46
|
+
generation = 0;
|
|
47
|
+
start(kind, snapshot, current, alive) {
|
|
48
|
+
const previous = this.pending.get(kind);
|
|
49
|
+
const controller = new AbortController();
|
|
50
|
+
this.pending.set(kind, controller);
|
|
51
|
+
this.latest.set(kind, controller);
|
|
52
|
+
const generation = this.generation;
|
|
53
|
+
previous?.abort();
|
|
54
|
+
return {
|
|
55
|
+
snapshot,
|
|
56
|
+
signal: controller.signal,
|
|
57
|
+
settle: () => {
|
|
58
|
+
if (this.pending.get(kind) === controller) this.pending.delete(kind);
|
|
59
|
+
},
|
|
60
|
+
current: () => !controller.signal.aborted && this.latest.get(kind) === controller && generation === this.generation && alive() && sameIdentity(snapshot, current())
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
invalidate() {
|
|
64
|
+
this.generation += 1;
|
|
65
|
+
const controllers = [...this.pending.values()];
|
|
66
|
+
this.pending.clear();
|
|
67
|
+
this.latest.clear();
|
|
68
|
+
for (const controller of controllers) controller.abort();
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
function sameIdentity(a, b) {
|
|
72
|
+
return !!a && a.uri === b.uri && a.version === b.version && a.environmentGeneration === b.environmentGeneration;
|
|
73
|
+
}
|
|
74
|
+
function resolveResponse(response, ticket, publish, fallback) {
|
|
75
|
+
const accept = (result) => {
|
|
76
|
+
ticket.settle();
|
|
77
|
+
return result && ticket.current() && sameIdentity(result, ticket.snapshot) ? publish(result) : fallback;
|
|
78
|
+
};
|
|
79
|
+
if (response instanceof Promise)
|
|
80
|
+
return response.then(accept, () => {
|
|
81
|
+
ticket.settle();
|
|
82
|
+
return fallback;
|
|
83
|
+
});
|
|
84
|
+
return accept(response);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// src/editor-validation.ts
|
|
88
|
+
function validRange(range, text) {
|
|
89
|
+
return !!range && Number.isSafeInteger(range.from) && Number.isSafeInteger(range.to) && range.from >= 0 && range.to >= range.from && range.to <= text.length && !insidePair(text, range.from) && !insidePair(text, range.to);
|
|
90
|
+
}
|
|
91
|
+
function insidePair(text, offset) {
|
|
92
|
+
const previous = text.charCodeAt(offset - 1);
|
|
93
|
+
const next = text.charCodeAt(offset);
|
|
94
|
+
return previous === 13 && next === 10 || previous >= 55296 && previous <= 56319 && next >= 56320 && next <= 57343;
|
|
95
|
+
}
|
|
96
|
+
function validEdit(edit, text) {
|
|
97
|
+
return validRange(edit, text) && typeof edit.text === "string";
|
|
98
|
+
}
|
|
99
|
+
function sourceOffset(doc, separator, offset) {
|
|
100
|
+
return offset + (doc.lineAt(offset).number - 1) * (separator.length - 1);
|
|
101
|
+
}
|
|
102
|
+
function editorOffset(doc, separator, offset) {
|
|
103
|
+
for (let number = 1; number <= doc.lines; number += 1) {
|
|
104
|
+
const line = doc.line(number);
|
|
105
|
+
const start = line.from + (number - 1) * (separator.length - 1);
|
|
106
|
+
if (offset <= start + line.length) return line.from + offset - start;
|
|
107
|
+
}
|
|
108
|
+
return doc.length;
|
|
109
|
+
}
|
|
10
110
|
function detectLineSeparator(text) {
|
|
11
111
|
if (text.includes("\r\n")) return "\r\n";
|
|
12
112
|
if (text.includes("\r")) return "\r";
|
|
@@ -18,9 +118,542 @@ function textForEditor(text) {
|
|
|
18
118
|
function textFromEditor(document, lineSeparator) {
|
|
19
119
|
return document.sliceString(0, document.length, lineSeparator);
|
|
20
120
|
}
|
|
121
|
+
var setKeyboardTooltip = state.StateEffect.define();
|
|
122
|
+
var keyboardTooltipField = state.StateField.define({
|
|
123
|
+
create: () => [],
|
|
124
|
+
update(value, transaction) {
|
|
125
|
+
let next = transaction.docChanged ? [] : value;
|
|
126
|
+
for (const effect of transaction.effects) {
|
|
127
|
+
if (effect.is(setKeyboardTooltip)) next = effect.value ? [effect.value] : [];
|
|
128
|
+
}
|
|
129
|
+
return next;
|
|
130
|
+
},
|
|
131
|
+
provide: (field) => view.showTooltip.computeN([field], (state) => state.field(field))
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// src/editor-tooling.ts
|
|
135
|
+
var EditorTooling = class {
|
|
136
|
+
requests = new EditorRequests();
|
|
137
|
+
frame = null;
|
|
138
|
+
hoverIntent = 0;
|
|
139
|
+
host;
|
|
140
|
+
provider;
|
|
141
|
+
constructor(host, provider = {}) {
|
|
142
|
+
this.host = host;
|
|
143
|
+
this.provider = provider;
|
|
144
|
+
}
|
|
145
|
+
extension() {
|
|
146
|
+
return [
|
|
147
|
+
autocomplete.autocompletion({ override: [(context) => this.complete(context)] }),
|
|
148
|
+
view.hoverTooltip((view, offset) => this.hover(view, offset)),
|
|
149
|
+
this.hoverIntentExtension(),
|
|
150
|
+
keyboardTooltipField,
|
|
151
|
+
view.keymap.of([
|
|
152
|
+
...autocomplete.completionKeymap,
|
|
153
|
+
{ key: "Mod-Shift-h", run: (view) => this.keyboardHover(view) },
|
|
154
|
+
{
|
|
155
|
+
key: "Escape",
|
|
156
|
+
run: (view) => {
|
|
157
|
+
this.clear(view);
|
|
158
|
+
view.focus();
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
])
|
|
163
|
+
];
|
|
164
|
+
}
|
|
165
|
+
hoverIntentExtension() {
|
|
166
|
+
return view.ViewPlugin.define((view) => {
|
|
167
|
+
const changed = () => {
|
|
168
|
+
this.hoverIntent += 1;
|
|
169
|
+
};
|
|
170
|
+
const events = ["mousemove", "mouseleave", "pointerdown"];
|
|
171
|
+
for (const event of events) view.dom.addEventListener(event, changed);
|
|
172
|
+
return {
|
|
173
|
+
update: (update) => {
|
|
174
|
+
if (!update.startState.selection.eq(update.state.selection)) changed();
|
|
175
|
+
},
|
|
176
|
+
destroy: () => {
|
|
177
|
+
for (const event of events) view.dom.removeEventListener(event, changed);
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
invalidate(view = this.host.view()) {
|
|
183
|
+
if (this.frame !== null && view)
|
|
184
|
+
view.dom.ownerDocument.defaultView?.cancelAnimationFrame(this.frame);
|
|
185
|
+
this.frame = null;
|
|
186
|
+
this.requests.invalidate();
|
|
187
|
+
}
|
|
188
|
+
clear(view$1, diagnostics = false) {
|
|
189
|
+
this.invalidate();
|
|
190
|
+
autocomplete.closeCompletion(view$1);
|
|
191
|
+
const interactions = { effects: [setKeyboardTooltip.of(null), view.closeHoverTooltips] };
|
|
192
|
+
if (diagnostics) view$1.dispatch(interactions, lint.setDiagnostics(view$1.state, []));
|
|
193
|
+
else view$1.dispatch(interactions);
|
|
194
|
+
}
|
|
195
|
+
schedule(view) {
|
|
196
|
+
const diagnosticsProvider = this.provider.diagnostics?.bind(this.provider);
|
|
197
|
+
const window = view.dom.ownerDocument.defaultView;
|
|
198
|
+
if (!diagnosticsProvider || !window) return;
|
|
199
|
+
const ticket = this.ticket("diagnostics", view);
|
|
200
|
+
this.frame = window.requestAnimationFrame(() => {
|
|
201
|
+
this.frame = null;
|
|
202
|
+
if (!ticket.current()) return;
|
|
203
|
+
const response = diagnosticsProvider(ticket);
|
|
204
|
+
void this.resolve(
|
|
205
|
+
response,
|
|
206
|
+
ticket,
|
|
207
|
+
(result) => {
|
|
208
|
+
const diagnostics = result.value.filter(
|
|
209
|
+
(entry) => validRange(entry, ticket.snapshot.text)
|
|
210
|
+
);
|
|
211
|
+
view.dispatch(
|
|
212
|
+
lint.setDiagnostics(
|
|
213
|
+
view.state,
|
|
214
|
+
diagnostics.map((entry) => ({
|
|
215
|
+
...this.range(view, entry),
|
|
216
|
+
message: entry.message,
|
|
217
|
+
severity: entry.severity
|
|
218
|
+
}))
|
|
219
|
+
)
|
|
220
|
+
);
|
|
221
|
+
return true;
|
|
222
|
+
},
|
|
223
|
+
false
|
|
224
|
+
);
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
format() {
|
|
228
|
+
const view = this.host.view();
|
|
229
|
+
if (!view || !this.provider.format) return false;
|
|
230
|
+
const ticket = this.ticket("format", view);
|
|
231
|
+
return this.resolve(
|
|
232
|
+
this.provider.format(ticket),
|
|
233
|
+
ticket,
|
|
234
|
+
(result, guarded) => this.apply(view, result.value, guarded),
|
|
235
|
+
false
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
complete(context) {
|
|
239
|
+
const view = this.host.view();
|
|
240
|
+
if (!view || view.state !== context.state || !this.provider.completion) return null;
|
|
241
|
+
const ticket = this.ticket("completion", view);
|
|
242
|
+
const offset = sourceOffset(view.state.doc, view.state.lineBreak, context.pos);
|
|
243
|
+
return this.resolve(
|
|
244
|
+
this.provider.completion(ticket, offset),
|
|
245
|
+
ticket,
|
|
246
|
+
(result, guarded) => {
|
|
247
|
+
const entries = result.value.filter((item) => validEdit(item.edit, ticket.snapshot.text));
|
|
248
|
+
const first = entries[0];
|
|
249
|
+
if (!first) return null;
|
|
250
|
+
return {
|
|
251
|
+
...this.range(view, first.edit),
|
|
252
|
+
filter: this.host.filterCompletions ?? false,
|
|
253
|
+
options: entries.map((item) => this.option(view, item, guarded))
|
|
254
|
+
};
|
|
255
|
+
},
|
|
256
|
+
null
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
option(view, item, ticket) {
|
|
260
|
+
return {
|
|
261
|
+
label: item.label,
|
|
262
|
+
type: item.type,
|
|
263
|
+
detail: item.detail,
|
|
264
|
+
boost: item.boost,
|
|
265
|
+
apply: (target) => {
|
|
266
|
+
if (target === view) this.apply(target, item.edit, ticket);
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
apply(view, edit, ticket) {
|
|
271
|
+
if (!ticket.current() || !validEdit(edit, ticket.snapshot.text)) return false;
|
|
272
|
+
view.dispatch({
|
|
273
|
+
changes: { ...this.range(view, edit), insert: textForEditor(edit.text) },
|
|
274
|
+
annotations: state.Transaction.addToHistory.of(true)
|
|
275
|
+
});
|
|
276
|
+
return true;
|
|
277
|
+
}
|
|
278
|
+
hover(view, offset, publish) {
|
|
279
|
+
if (this.host.view() !== view || !this.provider.hover) return null;
|
|
280
|
+
const intent = this.hoverIntent;
|
|
281
|
+
const selection = view.state.selection;
|
|
282
|
+
const request = this.ticket("hover", view);
|
|
283
|
+
const ticket = {
|
|
284
|
+
...request,
|
|
285
|
+
current: () => request.current() && intent === this.hoverIntent && selection.eq(view.state.selection)
|
|
286
|
+
};
|
|
287
|
+
const source = sourceOffset(view.state.doc, view.state.lineBreak, offset);
|
|
288
|
+
return this.resolve(
|
|
289
|
+
this.provider.hover(ticket, source),
|
|
290
|
+
ticket,
|
|
291
|
+
(result, guarded) => {
|
|
292
|
+
if (!validRange(result.value, ticket.snapshot.text)) return null;
|
|
293
|
+
const range = this.range(view, result.value);
|
|
294
|
+
const tooltip = (this.host.renderHover ?? editorTooltip)(
|
|
295
|
+
result.value,
|
|
296
|
+
range.from,
|
|
297
|
+
range.to
|
|
298
|
+
);
|
|
299
|
+
if (!guarded.current()) return null;
|
|
300
|
+
publish?.(tooltip);
|
|
301
|
+
return tooltip;
|
|
302
|
+
},
|
|
303
|
+
null
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
keyboardHover(view) {
|
|
307
|
+
const tooltip = this.hover(
|
|
308
|
+
view,
|
|
309
|
+
view.state.selection.main.head,
|
|
310
|
+
(value) => view.dispatch({ effects: setKeyboardTooltip.of(value) })
|
|
311
|
+
);
|
|
312
|
+
return tooltip instanceof Promise || tooltip !== null;
|
|
313
|
+
}
|
|
314
|
+
ticket(kind, view) {
|
|
315
|
+
return this.requests.start(
|
|
316
|
+
kind,
|
|
317
|
+
this.host.snapshot(),
|
|
318
|
+
() => this.host.snapshot(),
|
|
319
|
+
() => this.host.view() === view
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
resolve(response, ticket, publish, fallback) {
|
|
323
|
+
return resolveResponse(
|
|
324
|
+
response,
|
|
325
|
+
ticket,
|
|
326
|
+
(result) => {
|
|
327
|
+
const guarded = {
|
|
328
|
+
...ticket,
|
|
329
|
+
current: () => ticket.current() && (this.host.isCurrent?.(result) ?? true)
|
|
330
|
+
};
|
|
331
|
+
return guarded.current() ? publish(result, guarded) : fallback;
|
|
332
|
+
},
|
|
333
|
+
fallback
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
range(view, range) {
|
|
337
|
+
return {
|
|
338
|
+
from: editorOffset(view.state.doc, view.state.lineBreak, range.from),
|
|
339
|
+
to: editorOffset(view.state.doc, view.state.lineBreak, range.to)
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
// src/editor-session.ts
|
|
345
|
+
var NeutralEditorSession = class {
|
|
346
|
+
extension;
|
|
347
|
+
snapshot;
|
|
348
|
+
attached = null;
|
|
349
|
+
disposed = false;
|
|
350
|
+
failed = false;
|
|
351
|
+
failureView = null;
|
|
352
|
+
notifications = [];
|
|
353
|
+
publishingChanges = false;
|
|
354
|
+
changing = 0;
|
|
355
|
+
separator = new state.Compartment();
|
|
356
|
+
tooling;
|
|
357
|
+
options;
|
|
358
|
+
bridge;
|
|
359
|
+
constructor(options, bridge = {}) {
|
|
360
|
+
this.options = options;
|
|
361
|
+
this.bridge = bridge;
|
|
362
|
+
const document = options.document;
|
|
363
|
+
if (typeof document.text !== "string" || typeof document.uri !== "string" || document.uri.length === 0 || !Number.isSafeInteger(document.version) || document.version < 0)
|
|
364
|
+
throw new TypeError("Invalid editor document");
|
|
365
|
+
this.snapshot = Object.freeze({ ...document, environmentGeneration: 0 });
|
|
366
|
+
this.tooling = new EditorTooling(
|
|
367
|
+
{
|
|
368
|
+
snapshot: () => this.snapshot,
|
|
369
|
+
view: () => this.disposed || this.failed ? null : this.attached,
|
|
370
|
+
renderHover: bridge.renderHover,
|
|
371
|
+
isCurrent: bridge.isCurrent,
|
|
372
|
+
filterCompletions: bridge.filterCompletions
|
|
373
|
+
},
|
|
374
|
+
options.provider
|
|
375
|
+
);
|
|
376
|
+
this.extension = this.createExtension();
|
|
377
|
+
}
|
|
378
|
+
getSnapshot() {
|
|
379
|
+
return this.snapshot;
|
|
380
|
+
}
|
|
381
|
+
refreshEnvironment() {
|
|
382
|
+
this.assertOpen();
|
|
383
|
+
if (this.snapshot.environmentGeneration >= Number.MAX_SAFE_INTEGER)
|
|
384
|
+
throw new RangeError("Editor environment generation exhausted");
|
|
385
|
+
this.snapshot = Object.freeze({
|
|
386
|
+
...this.snapshot,
|
|
387
|
+
environmentGeneration: this.snapshot.environmentGeneration + 1
|
|
388
|
+
});
|
|
389
|
+
if (!this.attached) {
|
|
390
|
+
this.tooling.invalidate();
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
this.tooling.clear(this.attached, true);
|
|
394
|
+
this.tooling.schedule(this.attached);
|
|
395
|
+
}
|
|
396
|
+
replaceDocument(text) {
|
|
397
|
+
this.assertOpen();
|
|
398
|
+
if (typeof text !== "string") throw new TypeError("Replacement text must be a string");
|
|
399
|
+
const view = this.attached;
|
|
400
|
+
if (!view) {
|
|
401
|
+
this.changed(text);
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
const separator = detectLineSeparator(text);
|
|
405
|
+
view.dispatch({
|
|
406
|
+
changes: { from: 0, to: view.state.doc.length, insert: textForEditor(text) },
|
|
407
|
+
selection: state.EditorSelection.cursor(0),
|
|
408
|
+
annotations: state.Transaction.addToHistory.of(true),
|
|
409
|
+
effects: separator === view.state.lineBreak ? void 0 : this.separator.reconfigure(state.EditorState.lineSeparator.of(separator))
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
format() {
|
|
413
|
+
this.assertOpen();
|
|
414
|
+
return this.tooling.format();
|
|
415
|
+
}
|
|
416
|
+
dispose() {
|
|
417
|
+
if (this.disposed) return;
|
|
418
|
+
this.disposed = true;
|
|
419
|
+
this.tooling.invalidate(this.attached);
|
|
420
|
+
if (this.attached && !this.failed) this.tooling.clear(this.attached, true);
|
|
421
|
+
this.attached = null;
|
|
422
|
+
}
|
|
423
|
+
createExtension() {
|
|
424
|
+
const session = this;
|
|
425
|
+
return [
|
|
426
|
+
view.ViewPlugin.define((view) => {
|
|
427
|
+
this.attach(view);
|
|
428
|
+
return {
|
|
429
|
+
update: (update) => this.update(update),
|
|
430
|
+
destroy: () => this.detach(view)
|
|
431
|
+
};
|
|
432
|
+
}),
|
|
433
|
+
// Provider callbacks may dispatch; run them only after CodeMirror leaves its update phase.
|
|
434
|
+
view.EditorView.updateListener.of(() => {
|
|
435
|
+
this.publishChanges();
|
|
436
|
+
this.cleanupFailure();
|
|
437
|
+
}),
|
|
438
|
+
this.separator.of({
|
|
439
|
+
// Resolve when CodeMirror builds a state, including reuse of a cached extension.
|
|
440
|
+
get extension() {
|
|
441
|
+
return state.EditorState.lineSeparator.of(detectLineSeparator(session.snapshot.text));
|
|
442
|
+
}
|
|
443
|
+
}),
|
|
444
|
+
commands.history(),
|
|
445
|
+
this.tooling.extension(),
|
|
446
|
+
view.keymap.of(commands.historyKeymap),
|
|
447
|
+
view.EditorView.contentAttributes.of({ "aria-label": this.options.ariaLabel ?? "Source editor" }),
|
|
448
|
+
this.options.extensions ?? []
|
|
449
|
+
];
|
|
450
|
+
}
|
|
451
|
+
attach(view) {
|
|
452
|
+
this.assertOpen();
|
|
453
|
+
if (this.attached && this.attached !== view)
|
|
454
|
+
throw new Error("Editor session already has a view");
|
|
455
|
+
if (textFromEditor(view.state.doc, view.state.lineBreak) !== this.snapshot.text)
|
|
456
|
+
throw new Error("CodeMirror document does not match the opened document");
|
|
457
|
+
this.attached = view;
|
|
458
|
+
this.tooling.schedule(view);
|
|
459
|
+
}
|
|
460
|
+
detach(view) {
|
|
461
|
+
if (this.attached !== view) return;
|
|
462
|
+
this.attached = null;
|
|
463
|
+
this.tooling.invalidate(view);
|
|
464
|
+
}
|
|
465
|
+
update(update) {
|
|
466
|
+
if (this.disposed || this.failed || !update.docChanged) return;
|
|
467
|
+
try {
|
|
468
|
+
this.updateTransactions(update);
|
|
469
|
+
} catch (error) {
|
|
470
|
+
this.fail(update.view);
|
|
471
|
+
throw error;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
updateTransactions(update) {
|
|
475
|
+
for (const transaction of update.transactions) {
|
|
476
|
+
if (!transaction.docChanged) continue;
|
|
477
|
+
if (textFromEditor(transaction.startState.doc, transaction.startState.lineBreak) !== this.snapshot.text)
|
|
478
|
+
throw new Error("CodeMirror and editor snapshots diverged");
|
|
479
|
+
this.changed(textFromEditor(transaction.newDoc, transaction.state.lineBreak), transaction);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
changed(text, transaction) {
|
|
483
|
+
if (this.snapshot.version >= Number.MAX_SAFE_INTEGER)
|
|
484
|
+
throw new RangeError("Editor document revision exhausted");
|
|
485
|
+
const previous = this.snapshot;
|
|
486
|
+
const snapshot = Object.freeze({ ...previous, version: previous.version + 1, text });
|
|
487
|
+
this.snapshot = snapshot;
|
|
488
|
+
this.notifications.push(snapshot);
|
|
489
|
+
this.changing += 1;
|
|
490
|
+
let failed = false;
|
|
491
|
+
let failure;
|
|
492
|
+
try {
|
|
493
|
+
this.bridge.changed?.(snapshot, previous, transaction);
|
|
494
|
+
} catch (error) {
|
|
495
|
+
this.notifications = this.notifications.filter((pending) => pending !== snapshot);
|
|
496
|
+
this.reconcileFailure(snapshot, previous, !!transaction);
|
|
497
|
+
failed = true;
|
|
498
|
+
failure = error;
|
|
499
|
+
} finally {
|
|
500
|
+
this.changing -= 1;
|
|
501
|
+
}
|
|
502
|
+
try {
|
|
503
|
+
if (!transaction) this.publishChanges();
|
|
504
|
+
} catch (error) {
|
|
505
|
+
if (!failed) throw error;
|
|
506
|
+
}
|
|
507
|
+
if (failed) throw failure;
|
|
508
|
+
}
|
|
509
|
+
publishChanges() {
|
|
510
|
+
if (this.changing || this.publishingChanges || !this.notifications.length) return;
|
|
511
|
+
this.publishingChanges = true;
|
|
512
|
+
try {
|
|
513
|
+
while (this.notifications.length) {
|
|
514
|
+
const notifications = this.notifications;
|
|
515
|
+
this.notifications = [];
|
|
516
|
+
this.tooling.invalidate();
|
|
517
|
+
for (const snapshot of notifications) this.notifyChange(snapshot);
|
|
518
|
+
}
|
|
519
|
+
} finally {
|
|
520
|
+
this.publishingChanges = false;
|
|
521
|
+
}
|
|
522
|
+
if (!this.disposed && !this.failed && this.attached) this.tooling.schedule(this.attached);
|
|
523
|
+
}
|
|
524
|
+
reconcileFailure(attempt, previous, attached) {
|
|
525
|
+
if (attached) {
|
|
526
|
+
this.fail(this.attached);
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
try {
|
|
530
|
+
if (this.bridge.matches?.(this.snapshot)) return;
|
|
531
|
+
if (this.snapshot === attempt && this.bridge.matches?.(previous)) {
|
|
532
|
+
this.snapshot = previous;
|
|
533
|
+
this.tooling.invalidate();
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
} catch {
|
|
537
|
+
}
|
|
538
|
+
this.fail(null);
|
|
539
|
+
this.notifications = [];
|
|
540
|
+
}
|
|
541
|
+
fail(view) {
|
|
542
|
+
this.failed = true;
|
|
543
|
+
this.failureView = view;
|
|
544
|
+
this.tooling.invalidate(view);
|
|
545
|
+
}
|
|
546
|
+
cleanupFailure() {
|
|
547
|
+
const view = this.failureView;
|
|
548
|
+
this.failureView = null;
|
|
549
|
+
if (view) this.tooling.clear(view, true);
|
|
550
|
+
}
|
|
551
|
+
notifyChange(snapshot) {
|
|
552
|
+
try {
|
|
553
|
+
this.options.onDocumentChange?.(snapshot);
|
|
554
|
+
} catch {
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
assertOpen() {
|
|
558
|
+
if (this.disposed) throw new Error("Editor session is disposed");
|
|
559
|
+
if (this.failed) throw new Error("Editor session has failed");
|
|
560
|
+
}
|
|
561
|
+
};
|
|
562
|
+
|
|
563
|
+
// src/kalada-provider.ts
|
|
564
|
+
var KaladaProvider = class {
|
|
565
|
+
service;
|
|
566
|
+
uri;
|
|
567
|
+
hovers = /* @__PURE__ */ new WeakMap();
|
|
568
|
+
identities = /* @__PURE__ */ new WeakMap();
|
|
569
|
+
constructor(service, uri) {
|
|
570
|
+
this.service = service;
|
|
571
|
+
this.uri = uri;
|
|
572
|
+
}
|
|
573
|
+
isCurrent(identity) {
|
|
574
|
+
const original = this.identities.get(identity);
|
|
575
|
+
return !!original && this.service.isCurrent(original);
|
|
576
|
+
}
|
|
577
|
+
diagnostics(request) {
|
|
578
|
+
const result = this.service.diagnostics(this.uri, cancellation(request));
|
|
579
|
+
if (result.kind !== "diagnostics" || !this.service.isCurrent(result)) return null;
|
|
580
|
+
return this.tagged(
|
|
581
|
+
request,
|
|
582
|
+
result,
|
|
583
|
+
result.diagnostics.map((item) => ({
|
|
584
|
+
...item.source ? this.range(item.source.range) : { from: 0, to: 0 },
|
|
585
|
+
message: item.message,
|
|
586
|
+
severity: "error"
|
|
587
|
+
}))
|
|
588
|
+
);
|
|
589
|
+
}
|
|
590
|
+
completion(request, offset) {
|
|
591
|
+
const result = this.service.completion(
|
|
592
|
+
this.uri,
|
|
593
|
+
this.document().lineIndex.positionAt(offset),
|
|
594
|
+
cancellation(request)
|
|
595
|
+
);
|
|
596
|
+
if (result.kind !== "completion" || !this.service.isCurrent(result)) return null;
|
|
597
|
+
return this.tagged(
|
|
598
|
+
request,
|
|
599
|
+
result,
|
|
600
|
+
result.items.map((item) => ({
|
|
601
|
+
label: item.label,
|
|
602
|
+
edit: { ...this.range(item.edit.range), text: item.edit.text },
|
|
603
|
+
type: item.kind === "binding" ? "variable" : item.kind,
|
|
604
|
+
detail: `${item.support}, ${item.presence}`,
|
|
605
|
+
boost: item.support === "common" ? 10 : 0
|
|
606
|
+
}))
|
|
607
|
+
);
|
|
608
|
+
}
|
|
609
|
+
hover(request, offset) {
|
|
610
|
+
const result = this.service.hover(
|
|
611
|
+
this.uri,
|
|
612
|
+
this.document().lineIndex.positionAt(offset),
|
|
613
|
+
cancellation(request)
|
|
614
|
+
);
|
|
615
|
+
if (result.kind !== "hover" || !result.hover || !this.service.isCurrent(result)) return null;
|
|
616
|
+
const hover = { ...this.range(result.hover.range), content: "" };
|
|
617
|
+
this.hovers.set(hover, result.hover);
|
|
618
|
+
return this.tagged(request, result, hover);
|
|
619
|
+
}
|
|
620
|
+
format(request) {
|
|
621
|
+
const result = this.service.format(this.uri, cancellation(request));
|
|
622
|
+
if (result.kind !== "format" || !result.edit || !this.service.isCurrent(result)) return null;
|
|
623
|
+
return this.tagged(request, result, {
|
|
624
|
+
...this.range(result.edit.range),
|
|
625
|
+
text: result.edit.text
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
tagged(request, original, value) {
|
|
629
|
+
const result = {
|
|
630
|
+
uri: this.uri,
|
|
631
|
+
version: request.snapshot.version,
|
|
632
|
+
environmentGeneration: request.snapshot.environmentGeneration,
|
|
633
|
+
value
|
|
634
|
+
};
|
|
635
|
+
this.identities.set(result, original);
|
|
636
|
+
return result;
|
|
637
|
+
}
|
|
638
|
+
range(source) {
|
|
639
|
+
const document = this.document();
|
|
640
|
+
return {
|
|
641
|
+
from: document.lineIndex.offsetAt(source.start),
|
|
642
|
+
to: document.lineIndex.offsetAt(source.end)
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
document() {
|
|
646
|
+
const document = this.service.getDocument(this.uri);
|
|
647
|
+
if (!document) throw new Error("Kalada editor document is not open");
|
|
648
|
+
return document;
|
|
649
|
+
}
|
|
650
|
+
};
|
|
651
|
+
function cancellation(request) {
|
|
652
|
+
return { cancellation: { isCancellationRequested: () => request.signal.aborted } };
|
|
653
|
+
}
|
|
21
654
|
|
|
22
655
|
// src/render.ts
|
|
23
|
-
function
|
|
656
|
+
function hoverTooltip2(info, position) {
|
|
24
657
|
return {
|
|
25
658
|
pos: position,
|
|
26
659
|
above: true,
|
|
@@ -30,15 +663,15 @@ function hoverTooltip(info, position) {
|
|
|
30
663
|
dom.className = "cm-kalada-hover";
|
|
31
664
|
dom.setAttribute("role", "tooltip");
|
|
32
665
|
dom.setAttribute("aria-label", "Kalada expression information");
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
666
|
+
appendSection2(document, dom, "Input editor shape", info.input.map(shapeText));
|
|
667
|
+
appendSection2(document, dom, "Output Kalada semantic projection", [semanticText(info)]);
|
|
668
|
+
appendSection2(document, dom, "Presence / branch conditions", [accessText(info)]);
|
|
669
|
+
appendSection2(document, dom, "Limit/unknown evidence", info.evidence.map(evidenceText));
|
|
37
670
|
return { dom };
|
|
38
671
|
}
|
|
39
672
|
};
|
|
40
673
|
}
|
|
41
|
-
function
|
|
674
|
+
function appendSection2(document, parent, heading, values) {
|
|
42
675
|
const title = document.createElement("strong");
|
|
43
676
|
title.textContent = heading;
|
|
44
677
|
parent.append(title);
|
|
@@ -68,6 +701,17 @@ function evidenceText(evidence) {
|
|
|
68
701
|
return `${evidence.code} at ${evidence.path.join(".") || "root"}`;
|
|
69
702
|
}
|
|
70
703
|
|
|
704
|
+
// src/session-helpers.ts
|
|
705
|
+
function wholeReplacement(snapshot, text) {
|
|
706
|
+
return {
|
|
707
|
+
range: {
|
|
708
|
+
start: { line: 0, character: 0 },
|
|
709
|
+
end: snapshot.lineIndex.positionAt(snapshot.text.length)
|
|
710
|
+
},
|
|
711
|
+
text
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
|
|
71
715
|
// src/translation.ts
|
|
72
716
|
function changesToEdits(startDoc, newDoc, changes, snapshot, lineSeparator = "\n") {
|
|
73
717
|
const offsets = [];
|
|
@@ -83,14 +727,6 @@ function changesToEdits(startDoc, newDoc, changes, snapshot, lineSeparator = "\n
|
|
|
83
727
|
if (translated.every((edit) => edit !== null)) return translated;
|
|
84
728
|
return [wholeDocumentEdit(snapshot, newDoc.sliceString(0, newDoc.length, lineSeparator))];
|
|
85
729
|
}
|
|
86
|
-
function rangeToOffsets(document, range) {
|
|
87
|
-
const from = offsetAt(document, range.start.line, range.start.character);
|
|
88
|
-
const to = offsetAt(document, range.end.line, range.end.character);
|
|
89
|
-
return from === null || to === null || from > to ? null : { from, to };
|
|
90
|
-
}
|
|
91
|
-
function diagnosticOffsets(document, range) {
|
|
92
|
-
return rangeToOffsets(document, range) ?? { from: 0, to: 0 };
|
|
93
|
-
}
|
|
94
730
|
function offsetEdit(edit, document, index) {
|
|
95
731
|
const start = positionAt(document, edit.from);
|
|
96
732
|
const end = positionAt(document, edit.to);
|
|
@@ -101,14 +737,6 @@ function positionAt(document, offset) {
|
|
|
101
737
|
const line = document.lineAt(offset);
|
|
102
738
|
return { line: line.number - 1, character: offset - line.from };
|
|
103
739
|
}
|
|
104
|
-
function offsetAt(document, line, character) {
|
|
105
|
-
if (!Number.isSafeInteger(line) || !Number.isSafeInteger(character) || line < 0 || character < 0) {
|
|
106
|
-
return null;
|
|
107
|
-
}
|
|
108
|
-
if (line >= document.lines) return null;
|
|
109
|
-
const found = document.line(line + 1);
|
|
110
|
-
return character <= found.length ? found.from + character : null;
|
|
111
|
-
}
|
|
112
740
|
function coalesceInserts(edits) {
|
|
113
741
|
const output = [];
|
|
114
742
|
for (const edit of edits) {
|
|
@@ -129,345 +757,76 @@ function wholeDocumentEdit(snapshot, text) {
|
|
|
129
757
|
};
|
|
130
758
|
}
|
|
131
759
|
|
|
132
|
-
// src/session
|
|
133
|
-
function
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
function wholeReplacement(snapshot, text) {
|
|
760
|
+
// src/session.ts
|
|
761
|
+
function createKaladaEditorSession(options) {
|
|
762
|
+
const { service } = options;
|
|
763
|
+
const opened = service.openDocument(options.document);
|
|
764
|
+
let disposed = false;
|
|
765
|
+
const provider = new KaladaProvider(service, opened.uri);
|
|
766
|
+
const committed = /* @__PURE__ */ new WeakMap();
|
|
767
|
+
const core = new NeutralEditorSession(
|
|
768
|
+
{
|
|
769
|
+
document: opened,
|
|
770
|
+
ariaLabel: "Kalada expression editor",
|
|
771
|
+
provider,
|
|
772
|
+
onDocumentChange: (identity) => {
|
|
773
|
+
const snapshot = committed.get(identity);
|
|
774
|
+
if (snapshot) options.onDocumentChange?.(snapshot);
|
|
775
|
+
}
|
|
776
|
+
},
|
|
777
|
+
kaladaBridge(provider, (identity, snapshot) => committed.set(identity, snapshot))
|
|
778
|
+
);
|
|
152
779
|
return {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
780
|
+
extension: core.extension,
|
|
781
|
+
refreshEnvironment: () => core.refreshEnvironment(),
|
|
782
|
+
replaceDocument: (text) => core.replaceDocument(text),
|
|
783
|
+
format() {
|
|
784
|
+
const formatted = core.format();
|
|
785
|
+
if (typeof formatted !== "boolean") throw new Error("Kalada formatting must be synchronous");
|
|
786
|
+
return formatted;
|
|
156
787
|
},
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
create: () => [],
|
|
163
|
-
update(value, transaction) {
|
|
164
|
-
let next = value;
|
|
165
|
-
for (const effect of transaction.effects) {
|
|
166
|
-
if (effect.is(setKeyboardTooltip)) next = effect.value ? [effect.value] : [];
|
|
788
|
+
dispose() {
|
|
789
|
+
if (disposed) return;
|
|
790
|
+
disposed = true;
|
|
791
|
+
core.dispose();
|
|
792
|
+
service.closeDocument(opened.uri);
|
|
167
793
|
}
|
|
168
|
-
|
|
169
|
-
},
|
|
170
|
-
provide: (field) => view.showTooltip.computeN([field], (state) => state.field(field))
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
// src/session.ts
|
|
174
|
-
function createKaladaEditorSession(options) {
|
|
175
|
-
return new EditorSession(options);
|
|
794
|
+
};
|
|
176
795
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
this.extension = this.createExtension();
|
|
200
|
-
}
|
|
201
|
-
refreshEnvironment() {
|
|
202
|
-
this.assertOpen();
|
|
203
|
-
this.invalidateRequests();
|
|
204
|
-
const view = this.view;
|
|
205
|
-
if (!view) return;
|
|
206
|
-
this.clearEditor(view, false);
|
|
207
|
-
this.scheduleDiagnostics(view);
|
|
208
|
-
}
|
|
209
|
-
replaceDocument(text) {
|
|
210
|
-
this.assertOpen();
|
|
211
|
-
if (typeof text !== "string") throw new TypeError("Replacement text must be a string");
|
|
212
|
-
const view = this.view;
|
|
213
|
-
if (view) {
|
|
214
|
-
const separator = detectLineSeparator(text);
|
|
215
|
-
view.dispatch({
|
|
216
|
-
changes: { from: 0, to: view.state.doc.length, insert: textForEditor(text) },
|
|
217
|
-
selection: state.EditorSelection.cursor(0),
|
|
218
|
-
annotations: state.Transaction.addToHistory.of(true),
|
|
219
|
-
effects: separator === view.state.lineBreak ? void 0 : this.lineSeparator.reconfigure(state.EditorState.lineSeparator.of(separator))
|
|
220
|
-
});
|
|
221
|
-
return;
|
|
222
|
-
}
|
|
223
|
-
const current = this.requireDocument();
|
|
224
|
-
const snapshot = this.service.updateDocument({
|
|
225
|
-
uri: this.uri,
|
|
226
|
-
version: this.nextRevision(),
|
|
227
|
-
edits: [wholeReplacement(current, text)]
|
|
228
|
-
});
|
|
229
|
-
this.expectedText = snapshot.text;
|
|
230
|
-
this.invalidateRequests();
|
|
231
|
-
this.notify(snapshot);
|
|
232
|
-
}
|
|
233
|
-
format() {
|
|
234
|
-
this.assertOpen();
|
|
235
|
-
const view = this.view;
|
|
236
|
-
if (!view) return false;
|
|
237
|
-
const epoch = this.completionEpoch;
|
|
238
|
-
const result = this.service.format(this.uri, {
|
|
239
|
-
cancellation: this.cancellation("completion", epoch)
|
|
240
|
-
});
|
|
241
|
-
if (result.kind !== "format" || !result.edit || !this.publishable(result, view, epoch, "completion")) {
|
|
242
|
-
return false;
|
|
243
|
-
}
|
|
244
|
-
const range = rangeToOffsets(view.state.doc, result.edit.range);
|
|
245
|
-
if (!range) return false;
|
|
246
|
-
view.dispatch({
|
|
247
|
-
changes: { ...range, insert: textForEditor(result.edit.text) },
|
|
248
|
-
annotations: state.Transaction.addToHistory.of(true)
|
|
249
|
-
});
|
|
250
|
-
return true;
|
|
251
|
-
}
|
|
252
|
-
dispose() {
|
|
253
|
-
if (this.disposed) return;
|
|
254
|
-
this.disposed = true;
|
|
255
|
-
this.invalidateRequests();
|
|
256
|
-
this.cancelDiagnosticsFrame();
|
|
257
|
-
const view = this.view;
|
|
258
|
-
if (view) this.clearEditor(view, true);
|
|
259
|
-
this.view = null;
|
|
260
|
-
this.service.closeDocument(this.uri);
|
|
261
|
-
}
|
|
262
|
-
createExtension() {
|
|
263
|
-
const session = this;
|
|
264
|
-
const lifecycle = view.ViewPlugin.define((view) => {
|
|
265
|
-
session.attach(view);
|
|
266
|
-
return {
|
|
267
|
-
update(update) {
|
|
268
|
-
session.updateView(update);
|
|
269
|
-
},
|
|
270
|
-
destroy() {
|
|
271
|
-
session.detach(view);
|
|
272
|
-
}
|
|
273
|
-
};
|
|
274
|
-
});
|
|
275
|
-
return [
|
|
276
|
-
lifecycle,
|
|
277
|
-
this.lineSeparator.of(state.EditorState.lineSeparator.of(detectLineSeparator(this.expectedText))),
|
|
278
|
-
commands.history(),
|
|
279
|
-
autocomplete.autocompletion({ override: [(context) => this.complete(context)] }),
|
|
280
|
-
view.hoverTooltip((view, position) => this.pointerHover(view, position)),
|
|
281
|
-
keyboardTooltipField,
|
|
282
|
-
view.keymap.of([
|
|
283
|
-
...autocomplete.completionKeymap,
|
|
284
|
-
...commands.historyKeymap,
|
|
285
|
-
{ key: "Mod-Shift-h", run: (view) => this.keyboardHover(view) },
|
|
286
|
-
{ key: "Escape", run: (view) => this.clearInteractions(view) }
|
|
287
|
-
]),
|
|
288
|
-
view.EditorView.contentAttributes.of({ "aria-label": "Kalada expression editor" })
|
|
289
|
-
];
|
|
290
|
-
}
|
|
291
|
-
attach(view) {
|
|
292
|
-
this.assertOpen();
|
|
293
|
-
if (this.view && this.view !== view)
|
|
294
|
-
throw new Error("Kalada editor session already has a view");
|
|
295
|
-
if (textFromEditor(view.state.doc, view.state.lineBreak) !== this.expectedText) {
|
|
296
|
-
throw new Error("CodeMirror document does not match the opened Kalada document");
|
|
297
|
-
}
|
|
298
|
-
this.view = view;
|
|
299
|
-
this.viewEpoch += 1;
|
|
300
|
-
this.scheduleDiagnostics(view);
|
|
301
|
-
}
|
|
302
|
-
detach(view) {
|
|
303
|
-
if (this.view !== view) return;
|
|
304
|
-
this.cancelDiagnosticsFrame();
|
|
305
|
-
this.invalidateRequests();
|
|
306
|
-
this.view = null;
|
|
307
|
-
this.viewEpoch += 1;
|
|
308
|
-
}
|
|
309
|
-
updateView(update) {
|
|
310
|
-
if (this.disposed || !update.docChanged) return;
|
|
311
|
-
this.invalidateRequests();
|
|
312
|
-
for (const transaction of update.transactions) {
|
|
313
|
-
if (transaction.docChanged) this.applyTransaction(transaction);
|
|
314
|
-
}
|
|
315
|
-
this.expectedText = textFromEditor(update.state.doc, update.state.lineBreak);
|
|
316
|
-
this.scheduleDiagnostics(update.view);
|
|
317
|
-
}
|
|
318
|
-
applyTransaction(transaction) {
|
|
319
|
-
const current = this.requireDocument();
|
|
320
|
-
const startText = textFromEditor(transaction.startState.doc, transaction.startState.lineBreak);
|
|
321
|
-
if (current.text !== startText) {
|
|
322
|
-
throw new Error("CodeMirror and language-service snapshots diverged");
|
|
323
|
-
}
|
|
324
|
-
const edits = changesToEdits(
|
|
325
|
-
transaction.startState.doc,
|
|
326
|
-
transaction.newDoc,
|
|
327
|
-
transaction.changes,
|
|
328
|
-
current,
|
|
329
|
-
transaction.state.lineBreak
|
|
330
|
-
);
|
|
331
|
-
const snapshot = this.service.updateDocument({
|
|
332
|
-
uri: this.uri,
|
|
333
|
-
version: this.nextRevision(),
|
|
334
|
-
edits
|
|
335
|
-
});
|
|
336
|
-
if (snapshot.text !== textFromEditor(transaction.newDoc, transaction.state.lineBreak)) {
|
|
337
|
-
throw new Error("CodeMirror transaction translation changed document text");
|
|
338
|
-
}
|
|
339
|
-
this.expectedText = snapshot.text;
|
|
340
|
-
this.notify(snapshot);
|
|
341
|
-
}
|
|
342
|
-
complete(context) {
|
|
343
|
-
const view = this.view;
|
|
344
|
-
if (!view || view.state !== context.state || this.disposed) return null;
|
|
345
|
-
const epoch = ++this.completionEpoch;
|
|
346
|
-
const result = this.service.completion(this.uri, positionAt2(context.state.doc, context.pos), {
|
|
347
|
-
cancellation: this.cancellation("completion", epoch)
|
|
348
|
-
});
|
|
349
|
-
if (result.kind !== "completion" || !this.publishable(result, view, epoch, "completion")) {
|
|
350
|
-
return null;
|
|
351
|
-
}
|
|
352
|
-
const translated = result.items.map(
|
|
353
|
-
(item) => this.translateCompletion(item, result, view, epoch)
|
|
354
|
-
);
|
|
355
|
-
const available = translated.filter((entry) => entry !== null);
|
|
356
|
-
const first = available[0];
|
|
357
|
-
return first ? { from: first.from, to: first.to, options: available.map(({ option }) => option) } : null;
|
|
358
|
-
}
|
|
359
|
-
translateCompletion(item, identity, view, epoch) {
|
|
360
|
-
const range = rangeToOffsets(view.state.doc, item.edit.range);
|
|
361
|
-
if (!range) return null;
|
|
362
|
-
const option = {
|
|
363
|
-
label: item.label,
|
|
364
|
-
type: item.kind === "binding" ? "variable" : item.kind,
|
|
365
|
-
detail: `${item.support}, ${item.presence}`,
|
|
366
|
-
boost: item.support === "common" ? 10 : 0,
|
|
367
|
-
apply: (target) => this.applyCompletion(target, item, identity, epoch)
|
|
368
|
-
};
|
|
369
|
-
return { ...range, option };
|
|
370
|
-
}
|
|
371
|
-
applyCompletion(view, item, identity, epoch) {
|
|
372
|
-
if (!this.publishable(identity, view, epoch, "completion")) return;
|
|
373
|
-
const range = rangeToOffsets(view.state.doc, item.edit.range);
|
|
374
|
-
if (!range) return;
|
|
375
|
-
view.dispatch({ changes: { ...range, insert: item.edit.text } });
|
|
376
|
-
}
|
|
377
|
-
pointerHover(view, offset) {
|
|
378
|
-
return this.requestHover(view, offset);
|
|
379
|
-
}
|
|
380
|
-
keyboardHover(view) {
|
|
381
|
-
const tooltip = this.requestHover(view, view.state.selection.main.head);
|
|
382
|
-
if (!tooltip) return false;
|
|
383
|
-
view.dispatch({ effects: setKeyboardTooltip.of(tooltip) });
|
|
384
|
-
return true;
|
|
385
|
-
}
|
|
386
|
-
requestHover(view, offset) {
|
|
387
|
-
const epoch = ++this.hoverEpoch;
|
|
388
|
-
const result = this.service.hover(this.uri, positionAt2(view.state.doc, offset), {
|
|
389
|
-
cancellation: this.cancellation("hover", epoch)
|
|
390
|
-
});
|
|
391
|
-
if (result.kind !== "hover" || !result.hover || !this.publishable(result, view, epoch, "hover")) {
|
|
392
|
-
return null;
|
|
393
|
-
}
|
|
394
|
-
return translatedTooltip(result, view);
|
|
395
|
-
}
|
|
396
|
-
clearInteractions(view) {
|
|
397
|
-
this.clearEditor(view, false);
|
|
398
|
-
view.focus();
|
|
399
|
-
return true;
|
|
400
|
-
}
|
|
401
|
-
clearEditor(view$1, diagnostics) {
|
|
402
|
-
autocomplete.closeCompletion(view$1);
|
|
403
|
-
const interactions = { effects: [setKeyboardTooltip.of(null), view.closeHoverTooltips] };
|
|
404
|
-
if (diagnostics) view$1.dispatch(interactions, lint.setDiagnostics(view$1.state, []));
|
|
405
|
-
else view$1.dispatch(interactions);
|
|
406
|
-
}
|
|
407
|
-
scheduleDiagnostics(view) {
|
|
408
|
-
this.cancelDiagnosticsFrame();
|
|
409
|
-
const epoch = ++this.diagnosticsEpoch;
|
|
410
|
-
const viewEpoch = this.viewEpoch;
|
|
411
|
-
this.diagnosticsFrame = animationWindow(view).requestAnimationFrame(() => {
|
|
412
|
-
this.diagnosticsFrame = null;
|
|
413
|
-
const result = this.service.diagnostics(this.uri, {
|
|
414
|
-
cancellation: this.cancellation("diagnostics", epoch)
|
|
796
|
+
function kaladaBridge(provider, committed) {
|
|
797
|
+
return {
|
|
798
|
+
filterCompletions: true,
|
|
799
|
+
isCurrent: (identity) => provider.isCurrent(identity),
|
|
800
|
+
matches(snapshot) {
|
|
801
|
+
const current = provider.service.getDocument(provider.uri);
|
|
802
|
+
return !!current && current.uri === snapshot.uri && current.version === snapshot.version && current.text === snapshot.text;
|
|
803
|
+
},
|
|
804
|
+
changed(snapshot, previous, transaction) {
|
|
805
|
+
const current = provider.service.getDocument(provider.uri);
|
|
806
|
+
if (!current || current.text !== previous.text || current.version !== previous.version)
|
|
807
|
+
throw new Error("CodeMirror and language-service snapshots diverged");
|
|
808
|
+
const updated = provider.service.updateDocument({
|
|
809
|
+
uri: provider.uri,
|
|
810
|
+
version: snapshot.version,
|
|
811
|
+
edits: transaction ? changesToEdits(
|
|
812
|
+
transaction.startState.doc,
|
|
813
|
+
transaction.newDoc,
|
|
814
|
+
transaction.changes,
|
|
815
|
+
current,
|
|
816
|
+
transaction.state.lineBreak
|
|
817
|
+
) : [wholeReplacement(current, snapshot.text)]
|
|
415
818
|
});
|
|
416
|
-
if (
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
);
|
|
425
|
-
});
|
|
426
|
-
}
|
|
427
|
-
cancelDiagnosticsFrame() {
|
|
428
|
-
if (this.diagnosticsFrame === null || !this.view) return;
|
|
429
|
-
animationWindow(this.view).cancelAnimationFrame(this.diagnosticsFrame);
|
|
430
|
-
this.diagnosticsFrame = null;
|
|
431
|
-
}
|
|
432
|
-
cancellation(kind, epoch) {
|
|
433
|
-
return {
|
|
434
|
-
isCancellationRequested: () => this.disposed || this.epoch(kind) !== epoch
|
|
435
|
-
};
|
|
436
|
-
}
|
|
437
|
-
publishable(identity, view, epoch, kind) {
|
|
438
|
-
return !this.disposed && this.view === view && this.epoch(kind) === epoch && "status" in identity && identity.status === "current" && this.service.isCurrent(identity);
|
|
439
|
-
}
|
|
440
|
-
epoch(kind) {
|
|
441
|
-
if (kind === "completion") return this.completionEpoch;
|
|
442
|
-
if (kind === "hover") return this.hoverEpoch;
|
|
443
|
-
return this.diagnosticsEpoch;
|
|
444
|
-
}
|
|
445
|
-
invalidateRequests() {
|
|
446
|
-
this.completionEpoch += 1;
|
|
447
|
-
this.hoverEpoch += 1;
|
|
448
|
-
this.diagnosticsEpoch += 1;
|
|
449
|
-
}
|
|
450
|
-
nextRevision() {
|
|
451
|
-
if (this.revision >= Number.MAX_SAFE_INTEGER)
|
|
452
|
-
throw new RangeError("Kalada document revision exhausted");
|
|
453
|
-
this.revision += 1;
|
|
454
|
-
return this.revision;
|
|
455
|
-
}
|
|
456
|
-
requireDocument() {
|
|
457
|
-
const document = this.service.getDocument(this.uri);
|
|
458
|
-
if (!document) throw new Error("Kalada editor document is not open");
|
|
459
|
-
return document;
|
|
460
|
-
}
|
|
461
|
-
notify(snapshot) {
|
|
462
|
-
try {
|
|
463
|
-
this.onDocumentChange?.(snapshot);
|
|
464
|
-
} catch {
|
|
819
|
+
if (updated.text !== snapshot.text)
|
|
820
|
+
throw new Error("CodeMirror transaction translation changed document text");
|
|
821
|
+
committed(snapshot, updated);
|
|
822
|
+
},
|
|
823
|
+
renderHover(hover, from, to) {
|
|
824
|
+
const info = provider.hovers.get(hover);
|
|
825
|
+
if (!info) throw new Error("Missing Kalada hover presentation");
|
|
826
|
+
return { ...hoverTooltip2(info, from), end: to };
|
|
465
827
|
}
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
if (this.disposed) throw new Error("Kalada editor session is disposed");
|
|
469
|
-
}
|
|
470
|
-
};
|
|
828
|
+
};
|
|
829
|
+
}
|
|
471
830
|
|
|
472
831
|
exports.createKaladaEditorSession = createKaladaEditorSession;
|
|
473
832
|
//# sourceMappingURL=index.cjs.map
|