@singapore-editor/scope-lines 0.1.1
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 +27 -0
- package/dist/bracketColors.d.ts +33 -0
- package/dist/bracketColors.d.ts.map +1 -0
- package/dist/bracketColors.js +146 -0
- package/dist/bracketColors.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +473 -0
- package/dist/index.js.map +1 -0
- package/dist/stickyScroll.d.ts +8 -0
- package/dist/stickyScroll.d.ts.map +1 -0
- package/dist/stickyScroll.js +302 -0
- package/dist/stickyScroll.js.map +1 -0
- package/dist/style.css +153 -0
- package/package.json +46 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
import { BRACKET_COLOR_Z_INDEX, createBracketColorsPlugin } from "./bracketColors.js";
|
|
2
|
+
import { createStickyScrollPlugin } from "./stickyScroll.js";
|
|
3
|
+
import { scheduleFrame } from "@singapore-editor/core/rendering";
|
|
4
|
+
import "./style.css";
|
|
5
|
+
//#region src/index.ts
|
|
6
|
+
var DEFAULT_MIN_LINE_SPAN = 1;
|
|
7
|
+
var BODY_INDENT_PROBE_LINES = 24;
|
|
8
|
+
var SCOPE_LINE_COLOR_COUNT = 6;
|
|
9
|
+
function createScopeLinesPlugin(options = {}) {
|
|
10
|
+
const resolved = resolveScopeLinesOptions(options);
|
|
11
|
+
return {
|
|
12
|
+
name: "scope-lines",
|
|
13
|
+
activate(context) {
|
|
14
|
+
return context.registerViewContribution({ createContribution: (contributionContext) => createScopeLinesContribution(contributionContext, resolved) });
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function createScopeLinesContribution(context, options) {
|
|
19
|
+
if (!options.enabled) return null;
|
|
20
|
+
return new ScopeLinesContribution(context, options);
|
|
21
|
+
}
|
|
22
|
+
var ScopeLinesContribution = class {
|
|
23
|
+
snapshotKey;
|
|
24
|
+
context;
|
|
25
|
+
root;
|
|
26
|
+
options;
|
|
27
|
+
pendingContentSnapshot = null;
|
|
28
|
+
pendingContentFrame = null;
|
|
29
|
+
renderedSnapshot = null;
|
|
30
|
+
renderedSegments = [];
|
|
31
|
+
signature = "";
|
|
32
|
+
constructor(context, options) {
|
|
33
|
+
this.context = context;
|
|
34
|
+
this.options = options;
|
|
35
|
+
this.snapshotKey = `scope-lines:${JSON.stringify(options)}`;
|
|
36
|
+
this.root = createRoot(context, options);
|
|
37
|
+
this.update(context.getSnapshot(), "document");
|
|
38
|
+
}
|
|
39
|
+
update(snapshot, kind, _change) {
|
|
40
|
+
if (snapshot.geometryCommitted === false) return;
|
|
41
|
+
if (kind === "content") {
|
|
42
|
+
this.scheduleContentUpdate(snapshot);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
this.cancelContentUpdate();
|
|
46
|
+
this.renderSnapshot(snapshot);
|
|
47
|
+
}
|
|
48
|
+
captureVisiblePaint(snapshot) {
|
|
49
|
+
if (this.pendingContentSnapshot || snapshot !== this.renderedSnapshot) return {
|
|
50
|
+
id: "scope-lines",
|
|
51
|
+
status: "pending"
|
|
52
|
+
};
|
|
53
|
+
if (snapshot.syntaxStatus === "loading") return {
|
|
54
|
+
id: "scope-lines",
|
|
55
|
+
status: "pending"
|
|
56
|
+
};
|
|
57
|
+
const rectangles = captureScopeLineRectangles(this.renderedSegments, snapshot);
|
|
58
|
+
if (!rectangles) return {
|
|
59
|
+
id: "scope-lines",
|
|
60
|
+
status: "pending"
|
|
61
|
+
};
|
|
62
|
+
return {
|
|
63
|
+
id: "scope-lines",
|
|
64
|
+
status: "ready",
|
|
65
|
+
rectangles
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
dispose() {
|
|
69
|
+
this.cancelContentUpdate();
|
|
70
|
+
this.root.remove();
|
|
71
|
+
}
|
|
72
|
+
scheduleContentUpdate(snapshot) {
|
|
73
|
+
this.pendingContentSnapshot = snapshot;
|
|
74
|
+
if (this.pendingContentFrame !== null) return;
|
|
75
|
+
this.pendingContentFrame = scheduleFrame(this.flushContentUpdate, this.root.ownerDocument.defaultView ?? globalThis);
|
|
76
|
+
}
|
|
77
|
+
flushContentUpdate = () => {
|
|
78
|
+
const snapshot = this.pendingContentSnapshot;
|
|
79
|
+
this.pendingContentFrame = null;
|
|
80
|
+
this.pendingContentSnapshot = null;
|
|
81
|
+
if (!snapshot || this.context.getSnapshot().geometryCommitted === false) return;
|
|
82
|
+
this.renderSnapshot(snapshot);
|
|
83
|
+
this.context.requestViewUpdate();
|
|
84
|
+
};
|
|
85
|
+
cancelContentUpdate() {
|
|
86
|
+
const frame = this.pendingContentFrame;
|
|
87
|
+
this.pendingContentFrame = null;
|
|
88
|
+
this.pendingContentSnapshot = null;
|
|
89
|
+
if (frame === null) return;
|
|
90
|
+
frame.cancel();
|
|
91
|
+
}
|
|
92
|
+
renderSnapshot(snapshot) {
|
|
93
|
+
const renderContext = createScopeLinesRenderContext(snapshot);
|
|
94
|
+
const model = measureScopeLinesPerformance("scopeLines.renderModel", () => createScopeLinesRenderModel(renderContext, this.options), () => ({
|
|
95
|
+
markerCount: snapshot.foldMarkers.length,
|
|
96
|
+
visibleRows: snapshot.visibleRows.length
|
|
97
|
+
}));
|
|
98
|
+
if (model.signature === this.signature) {
|
|
99
|
+
this.renderedSnapshot = snapshot;
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
this.signature = model.signature;
|
|
103
|
+
this.renderedSegments = renderScopeLines(this.root, snapshot, model);
|
|
104
|
+
this.renderedSnapshot = snapshot;
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
function captureScopeLineRectangles(segments, snapshot) {
|
|
108
|
+
const rectangles = [];
|
|
109
|
+
for (const rendered of segments) {
|
|
110
|
+
if (rendered.segment.height <= 4) continue;
|
|
111
|
+
const rectangle = captureScopeLineRectangle(rendered, snapshot);
|
|
112
|
+
if (!rectangle) return null;
|
|
113
|
+
if (rectangle.width <= 0) continue;
|
|
114
|
+
rectangles.push(rectangle);
|
|
115
|
+
}
|
|
116
|
+
return rectangles;
|
|
117
|
+
}
|
|
118
|
+
function captureScopeLineRectangle({ element, segment }, snapshot) {
|
|
119
|
+
const style = element.ownerDocument.defaultView?.getComputedStyle(element);
|
|
120
|
+
if (!style?.backgroundColor) return null;
|
|
121
|
+
const width = Number.parseFloat(style.width);
|
|
122
|
+
if (!Number.isFinite(width)) return null;
|
|
123
|
+
return {
|
|
124
|
+
left: snapshot.gutterWidth + segment.column * snapshot.metrics.characterWidth,
|
|
125
|
+
top: segment.top + 1,
|
|
126
|
+
width,
|
|
127
|
+
height: Math.max(0, segment.height - 4),
|
|
128
|
+
backgroundColor: style.backgroundColor
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
function createScopeLinesRenderContext(snapshot) {
|
|
132
|
+
return {
|
|
133
|
+
snapshot,
|
|
134
|
+
textSnapshot: snapshot.textSnapshot,
|
|
135
|
+
lineTextCache: /* @__PURE__ */ new Map(),
|
|
136
|
+
indentColumnCache: /* @__PURE__ */ new Map()
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function resolveScopeLinesOptions(options) {
|
|
140
|
+
return {
|
|
141
|
+
enabled: options.enabled ?? true,
|
|
142
|
+
className: options.className,
|
|
143
|
+
minLineSpan: normalizeMinLineSpan(options.minLineSpan),
|
|
144
|
+
mode: options.mode ?? "all",
|
|
145
|
+
showActive: options.showActive ?? true
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
function normalizeMinLineSpan(value) {
|
|
149
|
+
if (value === void 0) return DEFAULT_MIN_LINE_SPAN;
|
|
150
|
+
if (!Number.isFinite(value)) return DEFAULT_MIN_LINE_SPAN;
|
|
151
|
+
return Math.max(1, Math.floor(value));
|
|
152
|
+
}
|
|
153
|
+
function createRoot(context, options) {
|
|
154
|
+
const root = context.container.ownerDocument.createElement("div");
|
|
155
|
+
root.className = "editor-scope-lines";
|
|
156
|
+
root.setAttribute("aria-hidden", "true");
|
|
157
|
+
if (options.className) root.classList.add(options.className);
|
|
158
|
+
context.contentElement.appendChild(root);
|
|
159
|
+
return root;
|
|
160
|
+
}
|
|
161
|
+
function renderScopeLines(root, snapshot, model) {
|
|
162
|
+
root.style.setProperty("--editor-scope-lines-content-width", `${snapshot.contentWidth}px`);
|
|
163
|
+
const rendered = model.segments.map((segment) => ({
|
|
164
|
+
segment,
|
|
165
|
+
element: createSegmentElement(root.ownerDocument, segment, snapshot)
|
|
166
|
+
}));
|
|
167
|
+
root.replaceChildren(...rendered.map(({ element }) => element));
|
|
168
|
+
return rendered;
|
|
169
|
+
}
|
|
170
|
+
function createSegmentElement(document, segment, snapshot) {
|
|
171
|
+
const element = document.createElement("div");
|
|
172
|
+
element.className = "editor-scope-line";
|
|
173
|
+
element.dataset.editorScopeLineLevel = String(segment.indentLevel % SCOPE_LINE_COLOR_COUNT);
|
|
174
|
+
element.style.left = `${segment.column * snapshot.metrics.characterWidth}px`;
|
|
175
|
+
element.style.top = `${segment.top + 1}px`;
|
|
176
|
+
element.style.height = `${Math.max(0, segment.height - 4)}px`;
|
|
177
|
+
if (segment.active) element.classList.add("editor-scope-line-active");
|
|
178
|
+
return element;
|
|
179
|
+
}
|
|
180
|
+
function createScopeLinesRenderModel(context, options) {
|
|
181
|
+
const guides = createScopeGuides(context, options);
|
|
182
|
+
const segments = [];
|
|
183
|
+
for (const guide of guides) appendGuideSegments(segments, guide, context.snapshot.visibleRows);
|
|
184
|
+
return {
|
|
185
|
+
signature: snapshotSignature(context, options, guides),
|
|
186
|
+
segments
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
function createScopeGuides(context, options) {
|
|
190
|
+
const guides = [];
|
|
191
|
+
for (const marker of candidateFoldMarkers(context, options)) {
|
|
192
|
+
const guide = createScopeGuide(marker, context, options);
|
|
193
|
+
if (guide) guides.push(guide);
|
|
194
|
+
}
|
|
195
|
+
if (options.mode === "all") return guides;
|
|
196
|
+
return nearestCursorScopeGuides(guides);
|
|
197
|
+
}
|
|
198
|
+
function candidateFoldMarkers(context, options) {
|
|
199
|
+
const snapshot = context.snapshot;
|
|
200
|
+
const bounds = visibleTextRowBounds(snapshot.visibleRows);
|
|
201
|
+
if (!bounds) return [];
|
|
202
|
+
const cursor = options.mode === "current" ? snapshot.selections[0]?.headOffset : void 0;
|
|
203
|
+
if (options.mode === "current" && cursor === void 0) return [];
|
|
204
|
+
const markers = [];
|
|
205
|
+
for (const marker of snapshot.foldMarkers) {
|
|
206
|
+
if (!markerIntersectsVisibleRows(marker, bounds)) continue;
|
|
207
|
+
if (cursor !== void 0 && !markerContainsOffset(marker, cursor)) continue;
|
|
208
|
+
markers.push(marker);
|
|
209
|
+
}
|
|
210
|
+
return markers;
|
|
211
|
+
}
|
|
212
|
+
function visibleTextRowBounds(rows) {
|
|
213
|
+
let startRow = Number.POSITIVE_INFINITY;
|
|
214
|
+
let endRow = Number.NEGATIVE_INFINITY;
|
|
215
|
+
for (const row of rows) {
|
|
216
|
+
if (row.kind !== "text") continue;
|
|
217
|
+
startRow = Math.min(startRow, row.bufferRow);
|
|
218
|
+
endRow = Math.max(endRow, row.bufferRow);
|
|
219
|
+
}
|
|
220
|
+
if (!Number.isFinite(startRow) || !Number.isFinite(endRow)) return null;
|
|
221
|
+
return {
|
|
222
|
+
startRow,
|
|
223
|
+
endRow
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
function markerIntersectsVisibleRows(marker, bounds) {
|
|
227
|
+
if (marker.startRow >= bounds.endRow) return false;
|
|
228
|
+
return marker.endRow > bounds.startRow;
|
|
229
|
+
}
|
|
230
|
+
function createScopeGuide(marker, context, options) {
|
|
231
|
+
const geometry = scopeGuideGeometry(marker, context, options);
|
|
232
|
+
if (!geometry) return null;
|
|
233
|
+
const containsCursor = markerContainsCursor(marker, context.snapshot);
|
|
234
|
+
return {
|
|
235
|
+
marker,
|
|
236
|
+
column: geometry.column,
|
|
237
|
+
indentLevel: geometry.indentLevel,
|
|
238
|
+
containsCursor,
|
|
239
|
+
active: options.showActive && containsCursor
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
function scopeGuideGeometry(marker, context, options) {
|
|
243
|
+
if (marker.collapsed) return null;
|
|
244
|
+
if (marker.endRow - marker.startRow < options.minLineSpan) return null;
|
|
245
|
+
const placement = scopeGuidePlacement(marker, context);
|
|
246
|
+
if (placement.column < 0) return null;
|
|
247
|
+
return {
|
|
248
|
+
marker,
|
|
249
|
+
column: placement.column,
|
|
250
|
+
indentLevel: placement.indentLevel
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
function scopeGuidePlacement(marker, context) {
|
|
254
|
+
const snapshot = context.snapshot;
|
|
255
|
+
const startIndent = lineIndentColumn(context, marker.startRow);
|
|
256
|
+
const bodyIndent = minimumBodyIndentColumn(context, marker);
|
|
257
|
+
if (bodyIndent === null) return placementFromIndent(startIndent, startIndent, snapshot.tabSize);
|
|
258
|
+
if (bodyIndent <= startIndent) return placementFromIndent(startIndent, startIndent, snapshot.tabSize);
|
|
259
|
+
return placementFromIndent(Math.max(startIndent, bodyIndent - snapshot.tabSize), bodyIndent, snapshot.tabSize);
|
|
260
|
+
}
|
|
261
|
+
function placementFromIndent(column, indent, tabSize) {
|
|
262
|
+
return {
|
|
263
|
+
column,
|
|
264
|
+
indentLevel: indentLevelForColumn(indent, tabSize)
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
function indentLevelForColumn(column, tabSize) {
|
|
268
|
+
return Math.max(0, Math.floor(column / Math.max(1, tabSize)));
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Shallowest indentation any line of the body is written at, which is the only column the whole
|
|
272
|
+
* guide can sit left of. Reading the first line instead puts the guide inside the code of every
|
|
273
|
+
* block that opens with a deeper line — a wrapped argument list, a chained call, a `case` label
|
|
274
|
+
* whose statements are outdented — and a guide drawn through text reads as a rendering fault.
|
|
275
|
+
*
|
|
276
|
+
* The closing row is excluded because the guide does not span it, and it is written at the opener's
|
|
277
|
+
* own indentation, which would drag the minimum back to where the opener sits every time.
|
|
278
|
+
*/
|
|
279
|
+
function minimumBodyIndentColumn(context, marker) {
|
|
280
|
+
const probeEnd = Math.min(marker.endRow - 1, marker.startRow + BODY_INDENT_PROBE_LINES);
|
|
281
|
+
let minimum = null;
|
|
282
|
+
for (let row = marker.startRow + 1; row <= probeEnd; row += 1) {
|
|
283
|
+
if (isBlankLine(lineText(context, row))) continue;
|
|
284
|
+
const column = indentColumnForRow(context, row);
|
|
285
|
+
if (minimum === null || column < minimum) minimum = column;
|
|
286
|
+
}
|
|
287
|
+
return minimum;
|
|
288
|
+
}
|
|
289
|
+
function lineIndentColumn(context, row) {
|
|
290
|
+
return indentColumnForRow(context, row);
|
|
291
|
+
}
|
|
292
|
+
function lineText(context, row) {
|
|
293
|
+
const cached = context.lineTextCache.get(row);
|
|
294
|
+
if (cached !== void 0) return cached;
|
|
295
|
+
const text = uncachedLineText(context, row);
|
|
296
|
+
context.lineTextCache.set(row, text);
|
|
297
|
+
return text;
|
|
298
|
+
}
|
|
299
|
+
function uncachedLineText(context, row) {
|
|
300
|
+
const lineStarts = context.snapshot.lineStartsView;
|
|
301
|
+
const start = lineStarts.at(row);
|
|
302
|
+
if (start === void 0) return "";
|
|
303
|
+
const textSnapshot = context.textSnapshot;
|
|
304
|
+
const nextStart = lineStarts.at(row + 1) ?? textSnapshot.length + 1;
|
|
305
|
+
const end = Math.max(start, Math.min(textSnapshot.length, nextStart - 1));
|
|
306
|
+
return textSnapshot.readRange(start, end);
|
|
307
|
+
}
|
|
308
|
+
function indentColumnForRow(context, row) {
|
|
309
|
+
const cached = context.indentColumnCache.get(row);
|
|
310
|
+
if (cached !== void 0) return cached;
|
|
311
|
+
const column = indentColumn(lineText(context, row), context.snapshot.tabSize);
|
|
312
|
+
context.indentColumnCache.set(row, column);
|
|
313
|
+
return column;
|
|
314
|
+
}
|
|
315
|
+
function isBlankLine(text) {
|
|
316
|
+
return text.trim().length === 0;
|
|
317
|
+
}
|
|
318
|
+
function indentColumn(text, tabSize) {
|
|
319
|
+
let column = 0;
|
|
320
|
+
for (const character of text) {
|
|
321
|
+
if (character === " ") {
|
|
322
|
+
column += 1;
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
if (character !== " ") return column;
|
|
326
|
+
column += tabSize - column % tabSize;
|
|
327
|
+
}
|
|
328
|
+
return column;
|
|
329
|
+
}
|
|
330
|
+
function markerContainsCursor(marker, snapshot) {
|
|
331
|
+
const cursor = snapshot.selections[0]?.headOffset;
|
|
332
|
+
if (cursor === void 0) return false;
|
|
333
|
+
return markerContainsOffset(marker, cursor);
|
|
334
|
+
}
|
|
335
|
+
function markerContainsOffset(marker, offset) {
|
|
336
|
+
return offset > marker.startOffset && offset < marker.endOffset;
|
|
337
|
+
}
|
|
338
|
+
function nearestCursorScopeGuides(guides) {
|
|
339
|
+
const nearest = guides.reduce(nearestCursorScopeGuide, null);
|
|
340
|
+
return nearest ? [nearest] : [];
|
|
341
|
+
}
|
|
342
|
+
function nearestCursorScopeGuide(current, candidate) {
|
|
343
|
+
if (!candidate.containsCursor) return current;
|
|
344
|
+
if (!current) return candidate;
|
|
345
|
+
const currentSpan = current.marker.endOffset - current.marker.startOffset;
|
|
346
|
+
if (candidate.marker.endOffset - candidate.marker.startOffset >= currentSpan) return current;
|
|
347
|
+
return candidate;
|
|
348
|
+
}
|
|
349
|
+
function appendGuideSegments(segments, guide, visibleRows) {
|
|
350
|
+
let open = null;
|
|
351
|
+
for (const row of visibleRows) {
|
|
352
|
+
const rowSegment = guideSegmentForRow(guide, row);
|
|
353
|
+
if (!rowSegment) {
|
|
354
|
+
if (open) segments.push(open);
|
|
355
|
+
open = null;
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
if (open && canMergeSegments(open, rowSegment)) {
|
|
359
|
+
const merged = open;
|
|
360
|
+
open = {
|
|
361
|
+
...merged,
|
|
362
|
+
height: rowSegment.top + rowSegment.height - merged.top
|
|
363
|
+
};
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
if (open) segments.push(open);
|
|
367
|
+
open = rowSegment;
|
|
368
|
+
}
|
|
369
|
+
if (open) segments.push(open);
|
|
370
|
+
}
|
|
371
|
+
function guideSegmentForRow(guide, row) {
|
|
372
|
+
if (row.kind !== "text") return null;
|
|
373
|
+
if (row.bufferRow <= guide.marker.startRow) return null;
|
|
374
|
+
if (row.bufferRow >= guide.marker.endRow) return null;
|
|
375
|
+
return {
|
|
376
|
+
column: guide.column,
|
|
377
|
+
indentLevel: guide.indentLevel,
|
|
378
|
+
top: row.top,
|
|
379
|
+
height: row.height,
|
|
380
|
+
active: guide.active
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
function canMergeSegments(left, right) {
|
|
384
|
+
if (left.column !== right.column) return false;
|
|
385
|
+
if (left.indentLevel !== right.indentLevel) return false;
|
|
386
|
+
if (left.active !== right.active) return false;
|
|
387
|
+
return Math.abs(left.top + left.height - right.top) < .5;
|
|
388
|
+
}
|
|
389
|
+
function snapshotSignature(context, options, guides) {
|
|
390
|
+
const snapshot = context.snapshot;
|
|
391
|
+
return [
|
|
392
|
+
snapshot.contentWidth,
|
|
393
|
+
snapshot.metrics.characterWidth,
|
|
394
|
+
snapshot.tabSize,
|
|
395
|
+
options.minLineSpan,
|
|
396
|
+
options.mode,
|
|
397
|
+
options.showActive,
|
|
398
|
+
scopeGuideSignature(guides),
|
|
399
|
+
visibleRowSignature(snapshot.visibleRows)
|
|
400
|
+
].join("|");
|
|
401
|
+
}
|
|
402
|
+
function scopeGuideSignature(guides) {
|
|
403
|
+
return guides.map(scopeGuideKey).join(",");
|
|
404
|
+
}
|
|
405
|
+
function scopeGuideKey(guide) {
|
|
406
|
+
return [
|
|
407
|
+
guide.marker.startRow,
|
|
408
|
+
guide.marker.endRow,
|
|
409
|
+
guide.column,
|
|
410
|
+
guide.indentLevel,
|
|
411
|
+
guide.active ? 1 : 0
|
|
412
|
+
].join(":");
|
|
413
|
+
}
|
|
414
|
+
function visibleRowSignature(rows) {
|
|
415
|
+
return rows.map((row) => [
|
|
416
|
+
row.index,
|
|
417
|
+
row.bufferRow,
|
|
418
|
+
row.top,
|
|
419
|
+
row.height,
|
|
420
|
+
row.kind
|
|
421
|
+
].join(":")).join(",");
|
|
422
|
+
}
|
|
423
|
+
function measureScopeLinesPerformance(name, run, detail) {
|
|
424
|
+
const sink = scopeLinesDiagnosticSink();
|
|
425
|
+
if (!sink) return run();
|
|
426
|
+
const start = nowMs();
|
|
427
|
+
try {
|
|
428
|
+
return run();
|
|
429
|
+
} finally {
|
|
430
|
+
recordScopeLinesDiagnostic(sink, name, detail, nowMs() - start);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
function recordScopeLinesDiagnostic(sink, name, detail, durationMs) {
|
|
434
|
+
const diagnostic = createDiagnostic(name, detail, durationMs);
|
|
435
|
+
if (typeof sink === "function") {
|
|
436
|
+
sink(diagnostic);
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
sink.record?.(diagnostic);
|
|
440
|
+
}
|
|
441
|
+
function scopeLinesDiagnosticSink() {
|
|
442
|
+
const sink = scopeLinesDiagnosticGlobal().__EDITOR_PERFORMANCE_DIAGNOSTICS__;
|
|
443
|
+
if (!sink) return null;
|
|
444
|
+
if (typeof sink === "function") return sink;
|
|
445
|
+
if (sink.enabled !== true && typeof sink.record !== "function") return null;
|
|
446
|
+
return sink;
|
|
447
|
+
}
|
|
448
|
+
function createDiagnostic(name, detail, durationMs) {
|
|
449
|
+
const resolvedDetail = resolveDiagnosticDetail(detail);
|
|
450
|
+
if (resolvedDetail === void 0) return {
|
|
451
|
+
name,
|
|
452
|
+
durationMs
|
|
453
|
+
};
|
|
454
|
+
return {
|
|
455
|
+
name,
|
|
456
|
+
durationMs,
|
|
457
|
+
detail: resolvedDetail
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
function resolveDiagnosticDetail(detail) {
|
|
461
|
+
if (typeof detail === "function") return detail();
|
|
462
|
+
return detail;
|
|
463
|
+
}
|
|
464
|
+
function scopeLinesDiagnosticGlobal() {
|
|
465
|
+
return globalThis;
|
|
466
|
+
}
|
|
467
|
+
function nowMs() {
|
|
468
|
+
return globalThis.performance?.now() ?? Date.now();
|
|
469
|
+
}
|
|
470
|
+
//#endregion
|
|
471
|
+
export { BRACKET_COLOR_Z_INDEX, createBracketColorsPlugin, createScopeLinesPlugin, createStickyScrollPlugin };
|
|
472
|
+
|
|
473
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import { scheduleFrame, type ScheduledFrame } from '@singapore-editor/core/rendering'\nimport type { TextReadSnapshot } from '@singapore-editor/core/document'\nimport type { VirtualizedFoldMarker } from '@singapore-editor/core/rendering'\nimport type {\n EditorContributionChange,\n EditorPlugin,\n EditorViewContribution,\n EditorViewContributionContext,\n EditorViewContributionUpdateKind,\n EditorViewSnapshot,\n EditorVisiblePaintCapture,\n EditorVisiblePaintRectangle,\n EditorVisibleRowSnapshot,\n} from '@singapore-editor/core/extensions'\nimport './style.css'\n\nexport { BRACKET_COLOR_Z_INDEX, createBracketColorsPlugin } from './bracketColors'\nexport type { BracketColorsPluginOptions } from './bracketColors'\nexport { createStickyScrollPlugin } from './stickyScroll'\nexport type { StickyScrollPluginOptions } from './stickyScroll'\n\nexport type ScopeLinesPluginOptions = {\n readonly enabled?: boolean\n readonly className?: string\n readonly minLineSpan?: number\n readonly mode?: ScopeLinesMode\n readonly showActive?: boolean\n}\n\nexport type ScopeLinesMode = 'all' | 'current'\n\ntype ResolvedScopeLinesOptions = {\n readonly enabled: boolean\n readonly className?: string\n readonly minLineSpan: number\n readonly mode: ScopeLinesMode\n readonly showActive: boolean\n}\n\ntype ScopeGuide = {\n readonly marker: VirtualizedFoldMarker\n readonly column: number\n readonly indentLevel: number\n readonly containsCursor: boolean\n readonly active: boolean\n}\n\ntype ScopeLineSegment = {\n readonly column: number\n readonly indentLevel: number\n readonly top: number\n readonly height: number\n readonly active: boolean\n}\n\ntype RenderedScopeLineSegment = {\n readonly segment: ScopeLineSegment\n readonly element: HTMLDivElement\n}\n\ntype ScopeGuidePlacement = {\n readonly column: number\n readonly indentLevel: number\n}\n\ntype ScopeGuideGeometry = ScopeGuidePlacement & {\n readonly marker: VirtualizedFoldMarker\n}\n\ntype ScopeLinesRenderContext = {\n readonly snapshot: EditorViewSnapshot\n readonly textSnapshot: TextReadSnapshot\n readonly lineTextCache: Map<number, string>\n readonly indentColumnCache: Map<number, number>\n}\n\ntype ScopeLinesRenderModel = {\n readonly signature: string\n readonly segments: readonly ScopeLineSegment[]\n}\n\ntype VisibleTextRowBounds = {\n readonly startRow: number\n readonly endRow: number\n}\n\nconst DEFAULT_MIN_LINE_SPAN = 1\n// The body is only sampled this far in. Every scope on screen is measured on every frame that moves\n// one of them, so a file-length scope must not cost a file-length scan; the first lines of a block\n// are also where its indentation is decided, and a later outlier is not worth a frame to find.\nconst BODY_INDENT_PROBE_LINES = 24\nconst SCOPE_LINE_COLOR_COUNT = 6\n\nexport function createScopeLinesPlugin(options: ScopeLinesPluginOptions = {}): EditorPlugin {\n const resolved = resolveScopeLinesOptions(options)\n\n return {\n name: 'scope-lines',\n activate(context) {\n return context.registerViewContribution({\n createContribution: (contributionContext) =>\n createScopeLinesContribution(contributionContext, resolved),\n })\n },\n }\n}\n\nfunction createScopeLinesContribution(\n context: EditorViewContributionContext,\n options: ResolvedScopeLinesOptions,\n): EditorViewContribution | null {\n if (!options.enabled) return null\n return new ScopeLinesContribution(context, options)\n}\n\nclass ScopeLinesContribution implements EditorViewContribution {\n public readonly snapshotKey: string\n private readonly context: EditorViewContributionContext\n private readonly root: HTMLDivElement\n private readonly options: ResolvedScopeLinesOptions\n private pendingContentSnapshot: EditorViewSnapshot | null = null\n private pendingContentFrame: ScheduledFrame | null = null\n private renderedSnapshot: EditorViewSnapshot | null = null\n private renderedSegments: readonly RenderedScopeLineSegment[] = []\n private signature = ''\n\n public constructor(context: EditorViewContributionContext, options: ResolvedScopeLinesOptions) {\n this.context = context\n this.options = options\n this.snapshotKey = `scope-lines:${JSON.stringify(options)}`\n this.root = createRoot(context, options)\n this.update(context.getSnapshot(), 'document')\n }\n\n public update(\n snapshot: EditorViewSnapshot,\n kind: EditorViewContributionUpdateKind,\n _change?: EditorContributionChange | null,\n ): void {\n if (snapshot.geometryCommitted === false) return\n if (kind === 'content') {\n this.scheduleContentUpdate(snapshot)\n return\n }\n\n this.cancelContentUpdate()\n this.renderSnapshot(snapshot)\n }\n\n public captureVisiblePaint(snapshot: EditorViewSnapshot): EditorVisiblePaintCapture {\n if (this.pendingContentSnapshot || snapshot !== this.renderedSnapshot) {\n return { id: 'scope-lines', status: 'pending' }\n }\n if (snapshot.syntaxStatus === 'loading') return { id: 'scope-lines', status: 'pending' }\n\n const rectangles = captureScopeLineRectangles(this.renderedSegments, snapshot)\n if (!rectangles) return { id: 'scope-lines', status: 'pending' }\n return { id: 'scope-lines', status: 'ready', rectangles }\n }\n\n public dispose(): void {\n this.cancelContentUpdate()\n this.root.remove()\n }\n\n private scheduleContentUpdate(snapshot: EditorViewSnapshot): void {\n this.pendingContentSnapshot = snapshot\n if (this.pendingContentFrame !== null) return\n\n this.pendingContentFrame = scheduleFrame(\n this.flushContentUpdate,\n this.root.ownerDocument.defaultView ?? globalThis,\n )\n }\n\n private flushContentUpdate = (): void => {\n const snapshot = this.pendingContentSnapshot\n this.pendingContentFrame = null\n this.pendingContentSnapshot = null\n if (!snapshot || this.context.getSnapshot().geometryCommitted === false) return\n\n this.renderSnapshot(snapshot)\n this.context.requestViewUpdate()\n }\n\n private cancelContentUpdate(): void {\n const frame = this.pendingContentFrame\n this.pendingContentFrame = null\n this.pendingContentSnapshot = null\n if (frame === null) return\n\n frame.cancel()\n }\n\n private renderSnapshot(snapshot: EditorViewSnapshot): void {\n const renderContext = createScopeLinesRenderContext(snapshot)\n const model = measureScopeLinesPerformance(\n 'scopeLines.renderModel',\n () => createScopeLinesRenderModel(renderContext, this.options),\n () => ({\n markerCount: snapshot.foldMarkers.length,\n visibleRows: snapshot.visibleRows.length,\n }),\n )\n if (model.signature === this.signature) {\n this.renderedSnapshot = snapshot\n return\n }\n\n this.signature = model.signature\n this.renderedSegments = renderScopeLines(this.root, snapshot, model)\n this.renderedSnapshot = snapshot\n }\n}\n\nfunction captureScopeLineRectangles(\n segments: readonly RenderedScopeLineSegment[],\n snapshot: EditorViewSnapshot,\n): readonly EditorVisiblePaintRectangle[] | null {\n const rectangles: EditorVisiblePaintRectangle[] = []\n for (const rendered of segments) {\n if (rendered.segment.height <= 4) continue\n const rectangle = captureScopeLineRectangle(rendered, snapshot)\n if (!rectangle) return null\n if (rectangle.width <= 0) continue\n rectangles.push(rectangle)\n }\n return rectangles\n}\n\nfunction captureScopeLineRectangle(\n { element, segment }: RenderedScopeLineSegment,\n snapshot: EditorViewSnapshot,\n): EditorVisiblePaintRectangle | null {\n const style = element.ownerDocument.defaultView?.getComputedStyle(element)\n if (!style?.backgroundColor) return null\n const width = Number.parseFloat(style.width)\n if (!Number.isFinite(width)) return null\n\n return {\n left: snapshot.gutterWidth + segment.column * snapshot.metrics.characterWidth,\n top: segment.top + 1,\n width,\n height: Math.max(0, segment.height - 4),\n backgroundColor: style.backgroundColor,\n }\n}\n\nfunction createScopeLinesRenderContext(snapshot: EditorViewSnapshot): ScopeLinesRenderContext {\n return {\n snapshot,\n textSnapshot: snapshot.textSnapshot,\n lineTextCache: new Map(),\n indentColumnCache: new Map(),\n }\n}\n\nfunction resolveScopeLinesOptions(options: ScopeLinesPluginOptions): ResolvedScopeLinesOptions {\n return {\n enabled: options.enabled ?? true,\n className: options.className,\n minLineSpan: normalizeMinLineSpan(options.minLineSpan),\n mode: options.mode ?? 'all',\n showActive: options.showActive ?? true,\n }\n}\n\nfunction normalizeMinLineSpan(value: number | undefined): number {\n if (value === undefined) return DEFAULT_MIN_LINE_SPAN\n if (!Number.isFinite(value)) return DEFAULT_MIN_LINE_SPAN\n return Math.max(1, Math.floor(value))\n}\n\nfunction createRoot(\n context: EditorViewContributionContext,\n options: ResolvedScopeLinesOptions,\n): HTMLDivElement {\n const root = context.container.ownerDocument.createElement('div')\n root.className = 'editor-scope-lines'\n root.setAttribute('aria-hidden', 'true')\n if (options.className) root.classList.add(options.className)\n context.contentElement.appendChild(root)\n return root\n}\n\nfunction renderScopeLines(\n root: HTMLDivElement,\n snapshot: EditorViewSnapshot,\n model: ScopeLinesRenderModel,\n): readonly RenderedScopeLineSegment[] {\n root.style.setProperty('--editor-scope-lines-content-width', `${snapshot.contentWidth}px`)\n const rendered = model.segments.map((segment) => ({\n segment,\n element: createSegmentElement(root.ownerDocument, segment, snapshot),\n }))\n root.replaceChildren(...rendered.map(({ element }) => element))\n return rendered\n}\n\nfunction createSegmentElement(\n document: Document,\n segment: ScopeLineSegment,\n snapshot: EditorViewSnapshot,\n): HTMLDivElement {\n const element = document.createElement('div')\n element.className = 'editor-scope-line'\n element.dataset.editorScopeLineLevel = String(segment.indentLevel % SCOPE_LINE_COLOR_COUNT)\n element.style.left = `${segment.column * snapshot.metrics.characterWidth}px`\n element.style.top = `${segment.top + 1}px`\n element.style.height = `${Math.max(0, segment.height - 4)}px`\n if (segment.active) element.classList.add('editor-scope-line-active')\n return element\n}\n\nfunction createScopeLinesRenderModel(\n context: ScopeLinesRenderContext,\n options: ResolvedScopeLinesOptions,\n): ScopeLinesRenderModel {\n const guides = createScopeGuides(context, options)\n const segments: ScopeLineSegment[] = []\n for (const guide of guides) appendGuideSegments(segments, guide, context.snapshot.visibleRows)\n\n return {\n signature: snapshotSignature(context, options, guides),\n segments,\n }\n}\n\nfunction createScopeGuides(\n context: ScopeLinesRenderContext,\n options: ResolvedScopeLinesOptions,\n): ScopeGuide[] {\n const guides: ScopeGuide[] = []\n for (const marker of candidateFoldMarkers(context, options)) {\n const guide = createScopeGuide(marker, context, options)\n if (guide) guides.push(guide)\n }\n if (options.mode === 'all') return guides\n return nearestCursorScopeGuides(guides)\n}\n\nfunction candidateFoldMarkers(\n context: ScopeLinesRenderContext,\n options: ResolvedScopeLinesOptions,\n): readonly VirtualizedFoldMarker[] {\n const snapshot = context.snapshot\n const bounds = visibleTextRowBounds(snapshot.visibleRows)\n if (!bounds) return []\n\n const cursor = options.mode === 'current' ? snapshot.selections[0]?.headOffset : undefined\n if (options.mode === 'current' && cursor === undefined) return []\n\n const markers: VirtualizedFoldMarker[] = []\n for (const marker of snapshot.foldMarkers) {\n if (!markerIntersectsVisibleRows(marker, bounds)) continue\n if (cursor !== undefined && !markerContainsOffset(marker, cursor)) continue\n markers.push(marker)\n }\n return markers\n}\n\nfunction visibleTextRowBounds(\n rows: readonly EditorVisibleRowSnapshot[],\n): VisibleTextRowBounds | null {\n let startRow = Number.POSITIVE_INFINITY\n let endRow = Number.NEGATIVE_INFINITY\n\n for (const row of rows) {\n if (row.kind !== 'text') continue\n startRow = Math.min(startRow, row.bufferRow)\n endRow = Math.max(endRow, row.bufferRow)\n }\n\n if (!Number.isFinite(startRow) || !Number.isFinite(endRow)) return null\n return { startRow, endRow }\n}\n\nfunction markerIntersectsVisibleRows(\n marker: VirtualizedFoldMarker,\n bounds: VisibleTextRowBounds,\n): boolean {\n if (marker.startRow >= bounds.endRow) return false\n return marker.endRow > bounds.startRow\n}\n\nfunction createScopeGuide(\n marker: VirtualizedFoldMarker,\n context: ScopeLinesRenderContext,\n options: ResolvedScopeLinesOptions,\n): ScopeGuide | null {\n const geometry = scopeGuideGeometry(marker, context, options)\n if (!geometry) return null\n\n const containsCursor = markerContainsCursor(marker, context.snapshot)\n\n return {\n marker,\n column: geometry.column,\n indentLevel: geometry.indentLevel,\n containsCursor,\n active: options.showActive && containsCursor,\n }\n}\n\nfunction scopeGuideGeometry(\n marker: VirtualizedFoldMarker,\n context: ScopeLinesRenderContext,\n options: ResolvedScopeLinesOptions,\n): ScopeGuideGeometry | null {\n if (marker.collapsed) return null\n if (marker.endRow - marker.startRow < options.minLineSpan) return null\n\n const placement = scopeGuidePlacement(marker, context)\n if (placement.column < 0) return null\n\n return {\n marker,\n column: placement.column,\n indentLevel: placement.indentLevel,\n }\n}\n\nfunction scopeGuidePlacement(\n marker: VirtualizedFoldMarker,\n context: ScopeLinesRenderContext,\n): ScopeGuidePlacement {\n const snapshot = context.snapshot\n const startIndent = lineIndentColumn(context, marker.startRow)\n const bodyIndent = minimumBodyIndentColumn(context, marker)\n if (bodyIndent === null) return placementFromIndent(startIndent, startIndent, snapshot.tabSize)\n if (bodyIndent <= startIndent) {\n return placementFromIndent(startIndent, startIndent, snapshot.tabSize)\n }\n\n return placementFromIndent(\n Math.max(startIndent, bodyIndent - snapshot.tabSize),\n bodyIndent,\n snapshot.tabSize,\n )\n}\n\nfunction placementFromIndent(column: number, indent: number, tabSize: number): ScopeGuidePlacement {\n return {\n column,\n indentLevel: indentLevelForColumn(indent, tabSize),\n }\n}\n\nfunction indentLevelForColumn(column: number, tabSize: number): number {\n return Math.max(0, Math.floor(column / Math.max(1, tabSize)))\n}\n\n/**\n * Shallowest indentation any line of the body is written at, which is the only column the whole\n * guide can sit left of. Reading the first line instead puts the guide inside the code of every\n * block that opens with a deeper line — a wrapped argument list, a chained call, a `case` label\n * whose statements are outdented — and a guide drawn through text reads as a rendering fault.\n *\n * The closing row is excluded because the guide does not span it, and it is written at the opener's\n * own indentation, which would drag the minimum back to where the opener sits every time.\n */\nfunction minimumBodyIndentColumn(\n context: ScopeLinesRenderContext,\n marker: VirtualizedFoldMarker,\n): number | null {\n const probeEnd = Math.min(marker.endRow - 1, marker.startRow + BODY_INDENT_PROBE_LINES)\n let minimum: number | null = null\n\n for (let row = marker.startRow + 1; row <= probeEnd; row += 1) {\n if (isBlankLine(lineText(context, row))) continue\n\n const column = indentColumnForRow(context, row)\n if (minimum === null || column < minimum) minimum = column\n }\n\n return minimum\n}\n\nfunction lineIndentColumn(context: ScopeLinesRenderContext, row: number): number {\n return indentColumnForRow(context, row)\n}\n\nfunction lineText(context: ScopeLinesRenderContext, row: number): string {\n const cached = context.lineTextCache.get(row)\n if (cached !== undefined) return cached\n\n const text = uncachedLineText(context, row)\n context.lineTextCache.set(row, text)\n return text\n}\n\nfunction uncachedLineText(context: ScopeLinesRenderContext, row: number): string {\n const snapshot = context.snapshot\n const lineStarts = snapshot.lineStartsView\n const start = lineStarts.at(row)\n if (start === undefined) return ''\n\n const textSnapshot = context.textSnapshot\n const nextStart = lineStarts.at(row + 1) ?? textSnapshot.length + 1\n const end = Math.max(start, Math.min(textSnapshot.length, nextStart - 1))\n return textSnapshot.readRange(start, end)\n}\n\nfunction indentColumnForRow(context: ScopeLinesRenderContext, row: number): number {\n const cached = context.indentColumnCache.get(row)\n if (cached !== undefined) return cached\n\n const column = indentColumn(lineText(context, row), context.snapshot.tabSize)\n context.indentColumnCache.set(row, column)\n return column\n}\n\nfunction isBlankLine(text: string): boolean {\n return text.trim().length === 0\n}\n\nfunction indentColumn(text: string, tabSize: number): number {\n let column = 0\n for (const character of text) {\n if (character === ' ') {\n column += 1\n continue\n }\n if (character !== '\\t') return column\n column += tabSize - (column % tabSize)\n }\n return column\n}\n\nfunction markerContainsCursor(\n marker: VirtualizedFoldMarker,\n snapshot: EditorViewSnapshot,\n): boolean {\n const cursor = snapshot.selections[0]?.headOffset\n if (cursor === undefined) return false\n return markerContainsOffset(marker, cursor)\n}\n\nfunction markerContainsOffset(marker: VirtualizedFoldMarker, offset: number): boolean {\n return offset > marker.startOffset && offset < marker.endOffset\n}\n\nfunction nearestCursorScopeGuides(guides: readonly ScopeGuide[]): ScopeGuide[] {\n const nearest = guides.reduce<ScopeGuide | null>(nearestCursorScopeGuide, null)\n return nearest ? [nearest] : []\n}\n\nfunction nearestCursorScopeGuide(\n current: ScopeGuide | null,\n candidate: ScopeGuide,\n): ScopeGuide | null {\n if (!candidate.containsCursor) return current\n if (!current) return candidate\n\n const currentSpan = current.marker.endOffset - current.marker.startOffset\n const candidateSpan = candidate.marker.endOffset - candidate.marker.startOffset\n if (candidateSpan >= currentSpan) return current\n return candidate\n}\n\nfunction appendGuideSegments(\n segments: ScopeLineSegment[],\n guide: ScopeGuide,\n visibleRows: readonly EditorVisibleRowSnapshot[],\n): void {\n let open: ScopeLineSegment | null = null\n for (const row of visibleRows) {\n const rowSegment = guideSegmentForRow(guide, row)\n if (!rowSegment) {\n if (open) segments.push(open)\n open = null\n continue\n }\n\n if (open && canMergeSegments(open, rowSegment)) {\n const merged: ScopeLineSegment = open\n open = { ...merged, height: rowSegment.top + rowSegment.height - merged.top }\n continue\n }\n if (open) segments.push(open)\n open = rowSegment\n }\n\n if (open) segments.push(open)\n}\n\nfunction guideSegmentForRow(\n guide: ScopeGuide,\n row: EditorVisibleRowSnapshot,\n): ScopeLineSegment | null {\n if (row.kind !== 'text') return null\n if (row.bufferRow <= guide.marker.startRow) return null\n if (row.bufferRow >= guide.marker.endRow) return null\n\n return {\n column: guide.column,\n indentLevel: guide.indentLevel,\n top: row.top,\n height: row.height,\n active: guide.active,\n }\n}\n\nfunction canMergeSegments(left: ScopeLineSegment, right: ScopeLineSegment): boolean {\n if (left.column !== right.column) return false\n if (left.indentLevel !== right.indentLevel) return false\n if (left.active !== right.active) return false\n return Math.abs(left.top + left.height - right.top) < 0.5\n}\n\nfunction snapshotSignature(\n context: ScopeLinesRenderContext,\n options: ResolvedScopeLinesOptions,\n guides: readonly ScopeGuide[],\n): string {\n const snapshot = context.snapshot\n return [\n snapshot.contentWidth,\n snapshot.metrics.characterWidth,\n snapshot.tabSize,\n options.minLineSpan,\n options.mode,\n options.showActive,\n scopeGuideSignature(guides),\n visibleRowSignature(snapshot.visibleRows),\n ].join('|')\n}\n\nfunction scopeGuideSignature(guides: readonly ScopeGuide[]): string {\n return guides.map(scopeGuideKey).join(',')\n}\n\nfunction scopeGuideKey(guide: ScopeGuide): string {\n return [\n guide.marker.startRow,\n guide.marker.endRow,\n guide.column,\n guide.indentLevel,\n guide.active ? 1 : 0,\n ].join(':')\n}\n\nfunction visibleRowSignature(rows: readonly EditorVisibleRowSnapshot[]): string {\n return rows\n .map((row) => [row.index, row.bufferRow, row.top, row.height, row.kind].join(':'))\n .join(',')\n}\n\ntype ScopeLinesDiagnostic = {\n readonly name: string\n readonly durationMs?: number\n readonly detail?: Readonly<Record<string, unknown>>\n}\n\ntype ScopeLinesDiagnosticSink =\n | ((diagnostic: ScopeLinesDiagnostic) => void)\n | {\n readonly enabled?: boolean\n readonly record?: (diagnostic: ScopeLinesDiagnostic) => void\n }\n\ntype ScopeLinesDiagnosticGlobal = typeof globalThis & {\n __EDITOR_PERFORMANCE_DIAGNOSTICS__?: ScopeLinesDiagnosticSink | null\n}\n\ntype DiagnosticDetail =\n | Readonly<Record<string, unknown>>\n | (() => Readonly<Record<string, unknown>> | undefined)\n | undefined\n\nfunction measureScopeLinesPerformance<T>(name: string, run: () => T, detail?: DiagnosticDetail): T {\n const sink = scopeLinesDiagnosticSink()\n if (!sink) return run()\n\n const start = nowMs()\n try {\n return run()\n } finally {\n recordScopeLinesDiagnostic(sink, name, detail, nowMs() - start)\n }\n}\n\nfunction recordScopeLinesDiagnostic(\n sink: ScopeLinesDiagnosticSink,\n name: string,\n detail: DiagnosticDetail,\n durationMs: number,\n): void {\n const diagnostic = createDiagnostic(name, detail, durationMs)\n if (typeof sink === 'function') {\n sink(diagnostic)\n return\n }\n\n sink.record?.(diagnostic)\n}\n\nfunction scopeLinesDiagnosticSink(): ScopeLinesDiagnosticSink | null {\n const sink = scopeLinesDiagnosticGlobal().__EDITOR_PERFORMANCE_DIAGNOSTICS__\n if (!sink) return null\n if (typeof sink === 'function') return sink\n if (sink.enabled !== true && typeof sink.record !== 'function') return null\n return sink\n}\n\nfunction createDiagnostic(\n name: string,\n detail: DiagnosticDetail,\n durationMs: number,\n): ScopeLinesDiagnostic {\n const resolvedDetail = resolveDiagnosticDetail(detail)\n if (resolvedDetail === undefined) return { name, durationMs }\n return { name, durationMs, detail: resolvedDetail }\n}\n\nfunction resolveDiagnosticDetail(\n detail: DiagnosticDetail,\n): Readonly<Record<string, unknown>> | undefined {\n if (typeof detail === 'function') return detail()\n return detail\n}\n\nfunction scopeLinesDiagnosticGlobal(): ScopeLinesDiagnosticGlobal {\n return globalThis as ScopeLinesDiagnosticGlobal\n}\n\nfunction nowMs(): number {\n return globalThis.performance?.now() ?? Date.now()\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { EditorPlugin } from '@singapore-editor/core/extensions';
|
|
2
|
+
import './style.css';
|
|
3
|
+
export type StickyScrollPluginOptions = {
|
|
4
|
+
readonly enabled?: boolean;
|
|
5
|
+
readonly maxLineCount?: number;
|
|
6
|
+
};
|
|
7
|
+
export declare function createStickyScrollPlugin(options?: StickyScrollPluginOptions): EditorPlugin;
|
|
8
|
+
//# sourceMappingURL=stickyScroll.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stickyScroll.d.ts","sourceRoot":"","sources":["../src/stickyScroll.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAEV,YAAY,EAKb,MAAM,mCAAmC,CAAA;AAK1C,OAAO,aAAa,CAAA;AAEpB,MAAM,MAAM,yBAAyB,GAAG;IACtC,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAA;IAC1B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAC/B,CAAA;AAsDD,wBAAgB,wBAAwB,CAAC,OAAO,GAAE,yBAA8B,GAAG,YAAY,CAY9F"}
|