@singapore-editor/minimap 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 +29 -0
- package/dist/assets/minimap.worker-i4S59IOV.js +1616 -0
- package/dist/assets/minimap.worker-i4S59IOV.js.map +1 -0
- package/dist/color.d.ts +11 -0
- package/dist/color.d.ts.map +1 -0
- package/dist/color.js +83 -0
- package/dist/color.js.map +1 -0
- package/dist/decorationMerge.d.ts +10 -0
- package/dist/decorationMerge.d.ts.map +1 -0
- package/dist/decorationMerge.js +65 -0
- package/dist/decorationMerge.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/layout.d.ts +43 -0
- package/dist/layout.d.ts.map +1 -0
- package/dist/layout.js +137 -0
- package/dist/layout.js.map +1 -0
- package/dist/minimap.worker.d.ts +2 -0
- package/dist/minimap.worker.d.ts.map +1 -0
- package/dist/minimapCharRenderer.d.ts +13 -0
- package/dist/minimapCharRenderer.d.ts.map +1 -0
- package/dist/minimapCharRendererFactory.d.ts +14 -0
- package/dist/minimapCharRendererFactory.d.ts.map +1 -0
- package/dist/minimapCharSheet.d.ts +15 -0
- package/dist/minimapCharSheet.d.ts.map +1 -0
- package/dist/minimapCharSheet.js +24 -0
- package/dist/minimapCharSheet.js.map +1 -0
- package/dist/options.d.ts +3 -0
- package/dist/options.d.ts.map +1 -0
- package/dist/options.js +65 -0
- package/dist/options.js.map +1 -0
- package/dist/plugin.d.ts +4 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +415 -0
- package/dist/plugin.js.map +1 -0
- package/dist/raster.d.ts +10 -0
- package/dist/raster.d.ts.map +1 -0
- package/dist/renderer.d.ts +58 -0
- package/dist/renderer.d.ts.map +1 -0
- package/dist/sectionHeaders.d.ts +13 -0
- package/dist/sectionHeaders.d.ts.map +1 -0
- package/dist/style.css +97 -0
- package/dist/types.d.ts +211 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +11 -0
- package/dist/types.js.map +1 -0
- package/dist/viewportGeometry.d.ts +28 -0
- package/dist/viewportGeometry.d.ts.map +1 -0
- package/dist/viewportGeometry.js +22 -0
- package/dist/viewportGeometry.js.map +1 -0
- package/dist/workerClient.d.ts +131 -0
- package/dist/workerClient.d.ts.map +1 -0
- package/dist/workerClient.js +1337 -0
- package/dist/workerClient.js.map +1 -0
- package/package.json +50 -0
|
@@ -0,0 +1,1616 @@
|
|
|
1
|
+
//#region ../editor/dist/logging/evlog.js
|
|
2
|
+
var evlogErrorInternalKey = Symbol.for("editor.evlog.error.internal");
|
|
3
|
+
var EditorEvlogError = class extends Error {
|
|
4
|
+
[evlogErrorInternalKey];
|
|
5
|
+
code;
|
|
6
|
+
fix;
|
|
7
|
+
link;
|
|
8
|
+
status;
|
|
9
|
+
why;
|
|
10
|
+
constructor(options) {
|
|
11
|
+
const resolved = typeof options === "string" ? { message: options } : options;
|
|
12
|
+
super(resolved.message, { cause: resolved.cause });
|
|
13
|
+
this.name = "EvlogError";
|
|
14
|
+
this.code = resolved.code;
|
|
15
|
+
this.fix = resolved.fix;
|
|
16
|
+
this.link = resolved.link;
|
|
17
|
+
this.status = resolved.status ?? 500;
|
|
18
|
+
this.why = resolved.why;
|
|
19
|
+
if (resolved.internal !== void 0) this.defineInternal(resolved.internal);
|
|
20
|
+
}
|
|
21
|
+
get internal() {
|
|
22
|
+
return this[evlogErrorInternalKey];
|
|
23
|
+
}
|
|
24
|
+
get statusText() {
|
|
25
|
+
return this.message;
|
|
26
|
+
}
|
|
27
|
+
get statusCode() {
|
|
28
|
+
return this.status;
|
|
29
|
+
}
|
|
30
|
+
get statusMessage() {
|
|
31
|
+
return this.message;
|
|
32
|
+
}
|
|
33
|
+
get data() {
|
|
34
|
+
if (!this.code && !this.why && !this.fix && !this.link) return void 0;
|
|
35
|
+
return {
|
|
36
|
+
code: this.code,
|
|
37
|
+
why: this.why,
|
|
38
|
+
fix: this.fix,
|
|
39
|
+
link: this.link
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
toString() {
|
|
43
|
+
return evlogErrorLines(this).join("\n");
|
|
44
|
+
}
|
|
45
|
+
toJSON() {
|
|
46
|
+
const serialized = {
|
|
47
|
+
message: this.message,
|
|
48
|
+
name: this.name,
|
|
49
|
+
status: this.status
|
|
50
|
+
};
|
|
51
|
+
if (this.data) serialized.data = this.data;
|
|
52
|
+
if (this.cause instanceof Error) serialized.cause = {
|
|
53
|
+
message: this.cause.message,
|
|
54
|
+
name: this.cause.name
|
|
55
|
+
};
|
|
56
|
+
return serialized;
|
|
57
|
+
}
|
|
58
|
+
defineInternal(internal) {
|
|
59
|
+
Object.defineProperty(this, evlogErrorInternalKey, {
|
|
60
|
+
configurable: true,
|
|
61
|
+
enumerable: false,
|
|
62
|
+
value: internal,
|
|
63
|
+
writable: false
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
function createError(options) {
|
|
68
|
+
return new EditorEvlogError(options);
|
|
69
|
+
}
|
|
70
|
+
function evlogErrorLines(error) {
|
|
71
|
+
const lines = [`Error: ${error.message}`];
|
|
72
|
+
if (error.code) lines.push(`Code: ${error.code}`);
|
|
73
|
+
if (error.why) lines.push(`Why: ${error.why}`);
|
|
74
|
+
if (error.fix) lines.push(`Fix: ${error.fix}`);
|
|
75
|
+
if (error.link) lines.push(`More info: ${error.link}`);
|
|
76
|
+
if (error.cause instanceof Error) lines.push(`Caused by: ${error.cause.message}`);
|
|
77
|
+
return lines;
|
|
78
|
+
}
|
|
79
|
+
//#endregion
|
|
80
|
+
//#region src/color.ts
|
|
81
|
+
const RGBA_EMPTY = {
|
|
82
|
+
r: 0,
|
|
83
|
+
g: 0,
|
|
84
|
+
b: 0,
|
|
85
|
+
a: 0
|
|
86
|
+
};
|
|
87
|
+
function transparent(color, ratio) {
|
|
88
|
+
return {
|
|
89
|
+
...color,
|
|
90
|
+
a: clamp8(color.a * ratio)
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
function rgbaToCss(color) {
|
|
94
|
+
return `rgba(${color.r}, ${color.g}, ${color.b}, ${color.a / 255})`;
|
|
95
|
+
}
|
|
96
|
+
function parseCssColor(value, fallback = RGBA_EMPTY) {
|
|
97
|
+
if (!value) return fallback;
|
|
98
|
+
const trimmed = value.trim();
|
|
99
|
+
if (trimmed.startsWith("#")) return parseHexColor(trimmed, fallback);
|
|
100
|
+
if (trimmed.startsWith("rgb")) return parseRgbColor(trimmed, fallback);
|
|
101
|
+
return fallback;
|
|
102
|
+
}
|
|
103
|
+
function relativeLuminance(color) {
|
|
104
|
+
const r = linearRgb(color.r / 255);
|
|
105
|
+
const g = linearRgb(color.g / 255);
|
|
106
|
+
const b = linearRgb(color.b / 255);
|
|
107
|
+
return .2126 * r + .7152 * g + .0722 * b;
|
|
108
|
+
}
|
|
109
|
+
function clamp8(value) {
|
|
110
|
+
return Math.min(255, Math.max(0, Math.round(value)));
|
|
111
|
+
}
|
|
112
|
+
function parseHexColor(value, fallback) {
|
|
113
|
+
const hex = value.slice(1);
|
|
114
|
+
if (hex.length === 3) return shortHex(hex, fallback);
|
|
115
|
+
if (hex.length === 4) return shortHexAlpha(hex, fallback);
|
|
116
|
+
if (hex.length === 6) return longHex(hex, "ff", fallback);
|
|
117
|
+
if (hex.length === 8) return longHex(hex.slice(0, 6), hex.slice(6), fallback);
|
|
118
|
+
return fallback;
|
|
119
|
+
}
|
|
120
|
+
function shortHex(hex, fallback) {
|
|
121
|
+
return longHex(hex.split("").map((char) => char + char).join(""), "ff", fallback);
|
|
122
|
+
}
|
|
123
|
+
function shortHexAlpha(hex, fallback) {
|
|
124
|
+
const rgb = hex.slice(0, 3);
|
|
125
|
+
const alpha = hex[3] ?? "f";
|
|
126
|
+
return longHex(rgb.split("").map((char) => char + char).join(""), alpha + alpha, fallback);
|
|
127
|
+
}
|
|
128
|
+
function longHex(rgb, alpha, fallback) {
|
|
129
|
+
const value = Number.parseInt(rgb + alpha, 16);
|
|
130
|
+
if (!Number.isFinite(value)) return fallback;
|
|
131
|
+
return {
|
|
132
|
+
r: value >> 24 & 255,
|
|
133
|
+
g: value >> 16 & 255,
|
|
134
|
+
b: value >> 8 & 255,
|
|
135
|
+
a: value & 255
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function parseRgbColor(value, fallback) {
|
|
139
|
+
const parts = value.slice(value.indexOf("(") + 1, value.lastIndexOf(")")).split(/[,\s/]+/).filter(Boolean);
|
|
140
|
+
if (parts.length < 3) return fallback;
|
|
141
|
+
return {
|
|
142
|
+
r: clamp8(Number(parts[0])),
|
|
143
|
+
g: clamp8(Number(parts[1])),
|
|
144
|
+
b: clamp8(Number(parts[2])),
|
|
145
|
+
a: alphaPart(parts[3])
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
function alphaPart(value) {
|
|
149
|
+
if (!value) return 255;
|
|
150
|
+
if (value.endsWith("%")) return clamp8(Number.parseFloat(value) / 100 * 255);
|
|
151
|
+
const number = Number(value);
|
|
152
|
+
if (!Number.isFinite(number)) return 255;
|
|
153
|
+
if (number <= 1) return clamp8(number * 255);
|
|
154
|
+
return clamp8(number);
|
|
155
|
+
}
|
|
156
|
+
function linearRgb(value) {
|
|
157
|
+
if (value <= .04045) return value / 12.92;
|
|
158
|
+
return ((value + .055) / 1.055) ** 2.4;
|
|
159
|
+
}
|
|
160
|
+
//#endregion
|
|
161
|
+
//#region src/minimapCharSheet.ts
|
|
162
|
+
let Constants = /* @__PURE__ */ function(Constants) {
|
|
163
|
+
Constants[Constants["START_CH_CODE"] = 32] = "START_CH_CODE";
|
|
164
|
+
Constants[Constants["END_CH_CODE"] = 126] = "END_CH_CODE";
|
|
165
|
+
Constants[Constants["UNKNOWN_CODE"] = 65533] = "UNKNOWN_CODE";
|
|
166
|
+
Constants[Constants["CHAR_COUNT"] = 96] = "CHAR_COUNT";
|
|
167
|
+
Constants[Constants["SAMPLED_CHAR_HEIGHT"] = 16] = "SAMPLED_CHAR_HEIGHT";
|
|
168
|
+
Constants[Constants["SAMPLED_CHAR_WIDTH"] = 10] = "SAMPLED_CHAR_WIDTH";
|
|
169
|
+
Constants[Constants["BASE_CHAR_HEIGHT"] = 2] = "BASE_CHAR_HEIGHT";
|
|
170
|
+
Constants[Constants["BASE_CHAR_WIDTH"] = 1] = "BASE_CHAR_WIDTH";
|
|
171
|
+
Constants[Constants["RGBA_CHANNELS_CNT"] = 4] = "RGBA_CHANNELS_CNT";
|
|
172
|
+
Constants[Constants["RGBA_SAMPLED_ROW_WIDTH"] = 3840] = "RGBA_SAMPLED_ROW_WIDTH";
|
|
173
|
+
return Constants;
|
|
174
|
+
}({});
|
|
175
|
+
const allCharCodes = (() => {
|
|
176
|
+
const values = [];
|
|
177
|
+
for (let index = 32; index <= 126; index += 1) values.push(index);
|
|
178
|
+
values.push(65533);
|
|
179
|
+
return values;
|
|
180
|
+
})();
|
|
181
|
+
const getCharIndex = (chCode, fontScale) => {
|
|
182
|
+
const index = chCode - 32;
|
|
183
|
+
if (index >= 0 && index <= 96) return index;
|
|
184
|
+
if (fontScale <= 2) return (index + 96) % 96;
|
|
185
|
+
return 95;
|
|
186
|
+
};
|
|
187
|
+
//#endregion
|
|
188
|
+
//#region src/types.ts
|
|
189
|
+
let RenderMinimap = /* @__PURE__ */ function(RenderMinimap) {
|
|
190
|
+
RenderMinimap[RenderMinimap["None"] = 0] = "None";
|
|
191
|
+
RenderMinimap[RenderMinimap["Text"] = 1] = "Text";
|
|
192
|
+
RenderMinimap[RenderMinimap["Blocks"] = 2] = "Blocks";
|
|
193
|
+
return RenderMinimap;
|
|
194
|
+
}({});
|
|
195
|
+
function computeRenderLayout(options) {
|
|
196
|
+
const pixelRatio = Math.max(1, options.metrics.devicePixelRatio);
|
|
197
|
+
const height = Math.max(0, options.viewport.minimapHeight);
|
|
198
|
+
const baseCharHeight = options.minimap.renderCharacters ? Constants.BASE_CHAR_HEIGHT : Constants.BASE_CHAR_HEIGHT + 1;
|
|
199
|
+
const configuredScale = pixelRatio >= 2 ? Math.round(options.minimap.scale * 2) : options.minimap.scale;
|
|
200
|
+
const baseCanvasInnerHeight = Math.floor(pixelRatio * height);
|
|
201
|
+
const fitted = computeFittedScale({
|
|
202
|
+
baseCharHeight,
|
|
203
|
+
canvasInnerHeight: baseCanvasInnerHeight,
|
|
204
|
+
configuredScale,
|
|
205
|
+
height,
|
|
206
|
+
lineCount: options.lineCount,
|
|
207
|
+
minimap: (options.viewport.visibleEnd - options.viewport.visibleStart) * baseCharHeight * configuredScale > baseCanvasInnerHeight ? {
|
|
208
|
+
...options.minimap,
|
|
209
|
+
size: "fill"
|
|
210
|
+
} : options.minimap,
|
|
211
|
+
pixelRatio,
|
|
212
|
+
rowHeight: options.metrics.rowHeight
|
|
213
|
+
});
|
|
214
|
+
const width = computeMinimapWidth({
|
|
215
|
+
charWidth: fitted.scale / pixelRatio / fitted.widthMultiplier,
|
|
216
|
+
maxColumn: options.minimap.maxColumn,
|
|
217
|
+
viewportWidth: options.viewport.clientWidth + Math.max(0, options.viewport.reservedWidth),
|
|
218
|
+
characterWidth: options.metrics.characterWidth
|
|
219
|
+
});
|
|
220
|
+
const baseCanvasInnerWidth = Math.floor(pixelRatio * width);
|
|
221
|
+
return {
|
|
222
|
+
width,
|
|
223
|
+
height,
|
|
224
|
+
canvasInnerWidth: Math.floor(baseCanvasInnerWidth * fitted.widthMultiplier),
|
|
225
|
+
canvasInnerHeight: fitted.heightIsEditorHeight ? Math.ceil(Math.max(baseCanvasInnerHeight, fitted.documentMinimapHeight)) : baseCanvasInnerHeight,
|
|
226
|
+
canvasOuterWidth: baseCanvasInnerWidth / pixelRatio,
|
|
227
|
+
canvasOuterHeight: baseCanvasInnerHeight / pixelRatio,
|
|
228
|
+
lineHeight: fitted.lineHeight,
|
|
229
|
+
charWidth: Constants.BASE_CHAR_WIDTH * fitted.scale,
|
|
230
|
+
scale: fitted.scale,
|
|
231
|
+
isSampling: fitted.isSampling,
|
|
232
|
+
heightIsEditorHeight: fitted.heightIsEditorHeight,
|
|
233
|
+
renderMinimap: options.minimap.enabled ? options.minimap.renderCharacters ? RenderMinimap.Text : RenderMinimap.Blocks : RenderMinimap.None
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
function computeFrameLayout(options) {
|
|
237
|
+
const { renderLayout: layout, viewport, realLineCount } = options;
|
|
238
|
+
const pixelRatio = layout.canvasInnerHeight / Math.max(1, layout.canvasOuterHeight);
|
|
239
|
+
const documentLineHeight = layout.isSampling ? layout.height / Math.max(1, realLineCount) : layout.lineHeight / pixelRatio;
|
|
240
|
+
const start = Math.min(realLineCount, Math.max(0, viewport.visibleStart));
|
|
241
|
+
const visibleCount = Math.min(realLineCount, Math.max(start, viewport.visibleEnd)) - start;
|
|
242
|
+
const linesFitting = layout.height / documentLineHeight;
|
|
243
|
+
const travel = Math.max(0, realLineCount - linesFitting);
|
|
244
|
+
const progress = start / Math.max(1, realLineCount - visibleCount);
|
|
245
|
+
const documentStart = Math.min(start, travel * progress);
|
|
246
|
+
const rasterStart = layout.isSampling ? 0 : documentStart;
|
|
247
|
+
const startLineNumber = Math.floor(rasterStart) + 1;
|
|
248
|
+
const startLineFraction = rasterStart - Math.floor(rasterStart);
|
|
249
|
+
return {
|
|
250
|
+
scrollTop: viewport.scrollTop,
|
|
251
|
+
scrollHeight: viewport.scrollHeight,
|
|
252
|
+
sliderNeeded: visibleCount < realLineCount,
|
|
253
|
+
documentLineHeight,
|
|
254
|
+
sliderLineHeight: travel > 0 ? Math.max(0, layout.height - visibleCount * documentLineHeight) / Math.max(1, realLineCount - visibleCount) : documentLineHeight,
|
|
255
|
+
documentStart,
|
|
256
|
+
sliderTop: (start - documentStart) * documentLineHeight,
|
|
257
|
+
sliderHeight: Math.min(layout.height, visibleCount * documentLineHeight),
|
|
258
|
+
topPaddingLineCount: 0,
|
|
259
|
+
startLineNumber,
|
|
260
|
+
startLineFraction,
|
|
261
|
+
endLineNumber: Math.min(options.lineCount, startLineNumber + Math.ceil(layout.canvasInnerHeight / layout.lineHeight + startLineFraction) - 1)
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
function yForLineNumber(frame, lineNumber, minimapLineHeight) {
|
|
265
|
+
return (lineNumber - frame.startLineNumber + frame.topPaddingLineCount) * minimapLineHeight;
|
|
266
|
+
}
|
|
267
|
+
function computeFittedScale(options) {
|
|
268
|
+
const lineHeight = options.baseCharHeight * options.configuredScale;
|
|
269
|
+
if (options.minimap.size === "proportional") return fittedScale(options.configuredScale, lineHeight, 1, false, false, 0);
|
|
270
|
+
const desiredRatio = options.lineCount / Math.max(1, options.canvasInnerHeight);
|
|
271
|
+
if (desiredRatio > 1) return fittedScale(1, 1, 1, true, true, options.canvasInnerHeight);
|
|
272
|
+
if (options.minimap.size === "fit") {
|
|
273
|
+
const documentHeight = Math.ceil(options.lineCount * lineHeight);
|
|
274
|
+
if (documentHeight <= options.canvasInnerHeight) return fittedScale(options.configuredScale, lineHeight, 1, false, false, documentHeight);
|
|
275
|
+
}
|
|
276
|
+
const maxScale = options.configuredScale + 1;
|
|
277
|
+
const fillLineHeight = Math.min(options.rowHeight * options.pixelRatio, Math.max(1, Math.floor(1 / desiredRatio)));
|
|
278
|
+
const scale = Math.min(maxScale, Math.max(1, Math.floor(fillLineHeight / options.baseCharHeight)));
|
|
279
|
+
const widthMultiplier = scale > options.configuredScale ? Math.min(2, scale / options.configuredScale) : 1;
|
|
280
|
+
const typicalViewportLineCount = options.height / Math.max(1, options.rowHeight);
|
|
281
|
+
return fittedScale(scale, fillLineHeight, widthMultiplier, false, true, Math.ceil(Math.max(typicalViewportLineCount, options.lineCount) * fillLineHeight));
|
|
282
|
+
}
|
|
283
|
+
function fittedScale(scale, lineHeight, widthMultiplier, isSampling, heightIsEditorHeight, documentMinimapHeight) {
|
|
284
|
+
return {
|
|
285
|
+
scale,
|
|
286
|
+
lineHeight,
|
|
287
|
+
widthMultiplier,
|
|
288
|
+
isSampling,
|
|
289
|
+
heightIsEditorHeight,
|
|
290
|
+
documentMinimapHeight
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
function computeMinimapWidth(options) {
|
|
294
|
+
const availableWidth = Math.max(0, options.viewportWidth);
|
|
295
|
+
const minimapMaxWidth = Math.floor(options.maxColumn * options.charWidth);
|
|
296
|
+
const proportionalWidth = Math.floor((availableWidth - 2) * options.charWidth / (options.characterWidth + options.charWidth));
|
|
297
|
+
return Math.min(minimapMaxWidth, Math.max(0, proportionalWidth) + 2 + 8);
|
|
298
|
+
}
|
|
299
|
+
//#endregion
|
|
300
|
+
//#region src/minimapCharRenderer.ts
|
|
301
|
+
var MinimapCharRenderer = class MinimapCharRenderer {
|
|
302
|
+
scale;
|
|
303
|
+
charDataNormal;
|
|
304
|
+
charDataLight;
|
|
305
|
+
constructor(charData, scale) {
|
|
306
|
+
this.scale = scale;
|
|
307
|
+
this.charDataNormal = MinimapCharRenderer.soften(charData, 12 / 15);
|
|
308
|
+
this.charDataLight = MinimapCharRenderer.soften(charData, 50 / 60);
|
|
309
|
+
}
|
|
310
|
+
renderChar(target, dx, dy, chCode, color, foregroundAlpha, backgroundColor, backgroundAlpha, fontScale, useLighterFont, force1pxHeight) {
|
|
311
|
+
const charWidth = Constants.BASE_CHAR_WIDTH * this.scale;
|
|
312
|
+
const charHeight = Constants.BASE_CHAR_HEIGHT * this.scale;
|
|
313
|
+
const renderHeight = force1pxHeight ? 1 : charHeight;
|
|
314
|
+
if (dx + charWidth > target.width || dy + renderHeight > target.height) return;
|
|
315
|
+
this.renderCharData({
|
|
316
|
+
target,
|
|
317
|
+
dx,
|
|
318
|
+
dy,
|
|
319
|
+
charWidth,
|
|
320
|
+
renderHeight,
|
|
321
|
+
charIndex: getCharIndex(chCode, fontScale),
|
|
322
|
+
color,
|
|
323
|
+
foregroundAlpha,
|
|
324
|
+
backgroundColor,
|
|
325
|
+
backgroundAlpha,
|
|
326
|
+
charData: useLighterFont ? this.charDataLight : this.charDataNormal
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
blockRenderChar(target, dx, dy, color, foregroundAlpha, backgroundColor, backgroundAlpha, force1pxHeight) {
|
|
330
|
+
const charWidth = Constants.BASE_CHAR_WIDTH * this.scale;
|
|
331
|
+
const charHeight = Constants.BASE_CHAR_HEIGHT * this.scale;
|
|
332
|
+
const renderHeight = force1pxHeight ? 1 : charHeight;
|
|
333
|
+
if (dx + charWidth > target.width || dy + renderHeight > target.height) return;
|
|
334
|
+
const foregroundRatio = .5 * (foregroundAlpha / 255);
|
|
335
|
+
const alpha = sourceOverAlpha(backgroundAlpha / 255, foregroundRatio);
|
|
336
|
+
const blended = sourceOverColor(backgroundColor, color, backgroundAlpha / 255, foregroundRatio, alpha);
|
|
337
|
+
const destWidth = target.width * Constants.RGBA_CHANNELS_CNT;
|
|
338
|
+
let row = dy * destWidth + dx * Constants.RGBA_CHANNELS_CNT;
|
|
339
|
+
for (let y = 0; y < renderHeight; y += 1) {
|
|
340
|
+
writeBlockRow(target.data, row, charWidth, blended, blended.a);
|
|
341
|
+
row += destWidth;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
renderCharData(options) {
|
|
345
|
+
if (options.backgroundAlpha === 0) {
|
|
346
|
+
this.renderTransparentCharData(options);
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
const dest = options.target.data;
|
|
350
|
+
const destWidth = options.target.width * Constants.RGBA_CHANNELS_CNT;
|
|
351
|
+
let sourceOffset = options.charIndex * options.charWidth * Constants.BASE_CHAR_HEIGHT * this.scale;
|
|
352
|
+
let row = options.dy * destWidth + options.dx * Constants.RGBA_CHANNELS_CNT;
|
|
353
|
+
for (let y = 0; y < options.renderHeight; y += 1) {
|
|
354
|
+
sourceOffset = writeCharRow(dest, row, sourceOffset, {
|
|
355
|
+
charWidth: options.charWidth,
|
|
356
|
+
charData: options.charData,
|
|
357
|
+
foregroundAlpha: options.foregroundAlpha,
|
|
358
|
+
color: options.color,
|
|
359
|
+
backgroundColor: options.backgroundColor,
|
|
360
|
+
backgroundAlpha: options.backgroundAlpha
|
|
361
|
+
});
|
|
362
|
+
row += destWidth;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
renderTransparentCharData(options) {
|
|
366
|
+
const dest = options.target.data;
|
|
367
|
+
const destWidth = options.target.width * Constants.RGBA_CHANNELS_CNT;
|
|
368
|
+
let sourceOffset = options.charIndex * options.charWidth * Constants.BASE_CHAR_HEIGHT * this.scale;
|
|
369
|
+
let row = options.dy * destWidth + options.dx * Constants.RGBA_CHANNELS_CNT;
|
|
370
|
+
for (let y = 0; y < options.renderHeight; y += 1) {
|
|
371
|
+
sourceOffset = writeTransparentCharRow(dest, row, sourceOffset, {
|
|
372
|
+
charWidth: options.charWidth,
|
|
373
|
+
charData: options.charData,
|
|
374
|
+
foregroundAlpha: options.foregroundAlpha,
|
|
375
|
+
color: options.color
|
|
376
|
+
});
|
|
377
|
+
row += destWidth;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
static soften(input, ratio) {
|
|
381
|
+
const result = new Uint8ClampedArray(input.length);
|
|
382
|
+
for (let index = 0; index < input.length; index += 1) result[index] = clamp8(input[index] * ratio);
|
|
383
|
+
return result;
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
function writeCharRow(dest, row, sourceOffset, options) {
|
|
387
|
+
let column = row;
|
|
388
|
+
let source = sourceOffset;
|
|
389
|
+
for (let x = 0; x < options.charWidth; x += 1) {
|
|
390
|
+
const c = (options.charData[source] ?? 0) / 255 * (options.foregroundAlpha / 255);
|
|
391
|
+
source += 1;
|
|
392
|
+
const alpha = sourceOverAlpha(options.backgroundAlpha / 255, c);
|
|
393
|
+
const color = sourceOverColor(options.backgroundColor, options.color, options.backgroundAlpha / 255, c, alpha);
|
|
394
|
+
dest[column++] = color.r;
|
|
395
|
+
dest[column++] = color.g;
|
|
396
|
+
dest[column++] = color.b;
|
|
397
|
+
dest[column++] = clamp8(alpha * 255);
|
|
398
|
+
}
|
|
399
|
+
return source;
|
|
400
|
+
}
|
|
401
|
+
function writeTransparentCharRow(dest, row, sourceOffset, options) {
|
|
402
|
+
let column = row;
|
|
403
|
+
let source = sourceOffset;
|
|
404
|
+
for (let x = 0; x < options.charWidth; x += 1) {
|
|
405
|
+
const coverage = (options.charData[source] ?? 0) / 255 * (options.foregroundAlpha / 255);
|
|
406
|
+
source += 1;
|
|
407
|
+
dest[column++] = options.color.r;
|
|
408
|
+
dest[column++] = options.color.g;
|
|
409
|
+
dest[column++] = options.color.b;
|
|
410
|
+
dest[column++] = clamp8(coverage * 255);
|
|
411
|
+
}
|
|
412
|
+
return source;
|
|
413
|
+
}
|
|
414
|
+
function sourceOverAlpha(backgroundAlpha, foregroundAlpha) {
|
|
415
|
+
return foregroundAlpha + backgroundAlpha * (1 - foregroundAlpha);
|
|
416
|
+
}
|
|
417
|
+
function sourceOverColor(background, foreground, backgroundAlpha, foregroundAlpha, alpha) {
|
|
418
|
+
if (alpha <= 0) return {
|
|
419
|
+
r: 0,
|
|
420
|
+
g: 0,
|
|
421
|
+
b: 0,
|
|
422
|
+
a: 0
|
|
423
|
+
};
|
|
424
|
+
const backgroundRatio = backgroundAlpha * (1 - foregroundAlpha) / alpha;
|
|
425
|
+
const foregroundRatio = foregroundAlpha / alpha;
|
|
426
|
+
return {
|
|
427
|
+
r: clamp8(background.r * backgroundRatio + foreground.r * foregroundRatio),
|
|
428
|
+
g: clamp8(background.g * backgroundRatio + foreground.g * foregroundRatio),
|
|
429
|
+
b: clamp8(background.b * backgroundRatio + foreground.b * foregroundRatio),
|
|
430
|
+
a: clamp8(alpha * 255)
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
function writeBlockRow(dest, row, width, color, alpha) {
|
|
434
|
+
let column = row;
|
|
435
|
+
for (let x = 0; x < width; x += 1) {
|
|
436
|
+
dest[column++] = color.r;
|
|
437
|
+
dest[column++] = color.g;
|
|
438
|
+
dest[column++] = color.b;
|
|
439
|
+
dest[column++] = alpha;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
//#endregion
|
|
443
|
+
//#region src/minimapCharRendererFactory.ts
|
|
444
|
+
/**
|
|
445
|
+
* Creates minimap character renderers by sampling a normal canvas once and
|
|
446
|
+
* downsampling the glyph data for tiny minimap text rendering.
|
|
447
|
+
*/
|
|
448
|
+
var MinimapCharRendererFactory = class MinimapCharRendererFactory {
|
|
449
|
+
static lastCreated;
|
|
450
|
+
static lastFontFamily;
|
|
451
|
+
static create(scale, fontFamily) {
|
|
452
|
+
if (this.lastCreated && scale === this.lastCreated.scale && fontFamily === this.lastFontFamily) return this.lastCreated;
|
|
453
|
+
const renderer = this.createFromSampleData(this.createSampleData(fontFamily).data, scale);
|
|
454
|
+
this.lastFontFamily = fontFamily;
|
|
455
|
+
this.lastCreated = renderer;
|
|
456
|
+
return renderer;
|
|
457
|
+
}
|
|
458
|
+
static createSampleData(fontFamily) {
|
|
459
|
+
const context = createSamplingCanvas().getContext("2d");
|
|
460
|
+
context.fillStyle = "#ffffff";
|
|
461
|
+
context.font = `bold ${Constants.SAMPLED_CHAR_HEIGHT}px ${fontFamily}`;
|
|
462
|
+
context.textBaseline = "middle";
|
|
463
|
+
let x = 0;
|
|
464
|
+
for (const code of allCharCodes) {
|
|
465
|
+
context.fillText(String.fromCharCode(code), x, Constants.SAMPLED_CHAR_HEIGHT / 2);
|
|
466
|
+
x += Constants.SAMPLED_CHAR_WIDTH;
|
|
467
|
+
}
|
|
468
|
+
return context.getImageData(0, 0, Constants.CHAR_COUNT * Constants.SAMPLED_CHAR_WIDTH, Constants.SAMPLED_CHAR_HEIGHT);
|
|
469
|
+
}
|
|
470
|
+
static createFromSampleData(source, scale) {
|
|
471
|
+
const expectedLength = Constants.SAMPLED_CHAR_HEIGHT * Constants.SAMPLED_CHAR_WIDTH * Constants.RGBA_CHANNELS_CNT * Constants.CHAR_COUNT;
|
|
472
|
+
if (source.length !== expectedLength) throw new Error("Unexpected source in MinimapCharRenderer");
|
|
473
|
+
return new MinimapCharRenderer(MinimapCharRendererFactory.downsample(source, scale), scale);
|
|
474
|
+
}
|
|
475
|
+
static downsample(data, scale) {
|
|
476
|
+
const pixelsPerCharacter = Constants.BASE_CHAR_HEIGHT * scale * Constants.BASE_CHAR_WIDTH * scale;
|
|
477
|
+
const resultLength = pixelsPerCharacter * Constants.CHAR_COUNT;
|
|
478
|
+
const result = new Uint8ClampedArray(resultLength);
|
|
479
|
+
let resultOffset = 0;
|
|
480
|
+
let sourceOffset = 0;
|
|
481
|
+
let brightest = 0;
|
|
482
|
+
for (let index = 0; index < Constants.CHAR_COUNT; index += 1) {
|
|
483
|
+
brightest = Math.max(brightest, downsampleChar(data, sourceOffset, result, resultOffset, scale));
|
|
484
|
+
resultOffset += pixelsPerCharacter;
|
|
485
|
+
sourceOffset += Constants.SAMPLED_CHAR_WIDTH * Constants.RGBA_CHANNELS_CNT;
|
|
486
|
+
}
|
|
487
|
+
boostIntensity(result, brightest);
|
|
488
|
+
return result;
|
|
489
|
+
}
|
|
490
|
+
};
|
|
491
|
+
function createSamplingCanvas() {
|
|
492
|
+
return new OffscreenCanvas(Constants.CHAR_COUNT * Constants.SAMPLED_CHAR_WIDTH, Constants.SAMPLED_CHAR_HEIGHT);
|
|
493
|
+
}
|
|
494
|
+
function downsampleChar(source, sourceOffset, dest, destOffset, scale) {
|
|
495
|
+
const width = Constants.BASE_CHAR_WIDTH * scale;
|
|
496
|
+
const height = Constants.BASE_CHAR_HEIGHT * scale;
|
|
497
|
+
let targetIndex = destOffset;
|
|
498
|
+
let brightest = 0;
|
|
499
|
+
for (let y = 0; y < height; y += 1) for (let x = 0; x < width; x += 1) {
|
|
500
|
+
const final = samplePixel(source, sourceOffset, x, y, width, height);
|
|
501
|
+
brightest = Math.max(brightest, final);
|
|
502
|
+
dest[targetIndex++] = clamp8(final);
|
|
503
|
+
}
|
|
504
|
+
return brightest;
|
|
505
|
+
}
|
|
506
|
+
function samplePixel(source, sourceOffset, x, y, width, height) {
|
|
507
|
+
const sourceY1 = y / height * Constants.SAMPLED_CHAR_HEIGHT;
|
|
508
|
+
const sourceY2 = (y + 1) / height * Constants.SAMPLED_CHAR_HEIGHT;
|
|
509
|
+
const sample = sampleSourceRect(source, sourceOffset, x / width * Constants.SAMPLED_CHAR_WIDTH, (x + 1) / width * Constants.SAMPLED_CHAR_WIDTH, sourceY1, sourceY2);
|
|
510
|
+
return sample.value / sample.samples;
|
|
511
|
+
}
|
|
512
|
+
function sampleSourceRect(source, sourceOffset, sourceX1, sourceX2, sourceY1, sourceY2) {
|
|
513
|
+
let value = 0;
|
|
514
|
+
let samples = 0;
|
|
515
|
+
for (let sy = sourceY1; sy < sourceY2; sy += 1) {
|
|
516
|
+
const sampled = sampleSourceRow(source, sourceOffset + Math.floor(sy) * Constants.RGBA_SAMPLED_ROW_WIDTH, sourceX1, sourceX2, 1 - (sy - Math.floor(sy)));
|
|
517
|
+
value += sampled.value;
|
|
518
|
+
samples += sampled.samples;
|
|
519
|
+
}
|
|
520
|
+
return {
|
|
521
|
+
value,
|
|
522
|
+
samples
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
function sampleSourceRow(source, row, sourceX1, sourceX2, yBalance) {
|
|
526
|
+
let value = 0;
|
|
527
|
+
let samples = 0;
|
|
528
|
+
for (let sx = sourceX1; sx < sourceX2; sx += 1) {
|
|
529
|
+
const xBalance = 1 - (sx - Math.floor(sx));
|
|
530
|
+
const sourceIndex = row + Math.floor(sx) * Constants.RGBA_CHANNELS_CNT;
|
|
531
|
+
const weight = xBalance * yBalance;
|
|
532
|
+
samples += weight;
|
|
533
|
+
value += (source[sourceIndex] ?? 0) * (source[sourceIndex + 3] ?? 0) / 255 * weight;
|
|
534
|
+
}
|
|
535
|
+
return {
|
|
536
|
+
value,
|
|
537
|
+
samples
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
function boostIntensity(result, brightest) {
|
|
541
|
+
if (brightest <= 0) return;
|
|
542
|
+
const adjust = 255 / brightest;
|
|
543
|
+
for (let index = 0; index < result.length; index += 1) result[index] = clamp8(result[index] * adjust);
|
|
544
|
+
}
|
|
545
|
+
//#endregion
|
|
546
|
+
//#region src/raster.ts
|
|
547
|
+
var RasterBuffer = class {
|
|
548
|
+
imageData;
|
|
549
|
+
pixels;
|
|
550
|
+
background;
|
|
551
|
+
constructor(context, width, height, background) {
|
|
552
|
+
this.imageData = context.createImageData(Math.max(1, width), Math.max(1, height));
|
|
553
|
+
this.pixels = new Uint32Array(this.imageData.data.buffer);
|
|
554
|
+
const channels = Uint8ClampedArray.of(background.r, background.g, background.b, background.a);
|
|
555
|
+
this.background = new Uint32Array(channels.buffer)[0];
|
|
556
|
+
}
|
|
557
|
+
clearRows(start, end) {
|
|
558
|
+
this.pixels.fill(this.background, start * this.imageData.width, end * this.imageData.width);
|
|
559
|
+
}
|
|
560
|
+
copyRows(source, destination, count) {
|
|
561
|
+
const width = this.imageData.width;
|
|
562
|
+
this.pixels.copyWithin(destination * width, source * width, (source + count) * width);
|
|
563
|
+
}
|
|
564
|
+
};
|
|
565
|
+
//#endregion
|
|
566
|
+
//#region src/sectionHeaders.ts
|
|
567
|
+
const CHUNK_SIZE = 100;
|
|
568
|
+
function findSectionHeaderDecorations(lines, options) {
|
|
569
|
+
if (!options.showMarkSectionHeaders) return [];
|
|
570
|
+
return collectMarkHeaders(lines, options.markSectionHeaderRegex).map(headerToDecoration);
|
|
571
|
+
}
|
|
572
|
+
function findSectionHeaderDecorationsInRange(lines, startLineNumber, options) {
|
|
573
|
+
return findSectionHeaderDecorations(lines, options).map((decoration) => shiftDecorationLineNumbers(decoration, startLineNumber - 1));
|
|
574
|
+
}
|
|
575
|
+
function collectMarkHeaders(lines, markSectionHeaderRegex) {
|
|
576
|
+
if (markSectionHeaderRegex.trim() === "") return [];
|
|
577
|
+
const regex = createHeaderRegex(markSectionHeaderRegex);
|
|
578
|
+
if (!regex || regExpLeadsToEndlessLoop(regex)) return [];
|
|
579
|
+
const headers = [];
|
|
580
|
+
for (let startLine = 1; startLine <= lines.length; startLine += 95) collectHeadersInChunk(headers, lines, startLine, regex);
|
|
581
|
+
return headers;
|
|
582
|
+
}
|
|
583
|
+
function collectHeadersInChunk(headers, lines, startLine, regex) {
|
|
584
|
+
const endLine = Math.min(startLine + CHUNK_SIZE - 1, lines.length);
|
|
585
|
+
const text = lines.slice(startLine - 1, endLine).join("\n");
|
|
586
|
+
regex.lastIndex = 0;
|
|
587
|
+
let match;
|
|
588
|
+
while ((match = regex.exec(text)) !== null) {
|
|
589
|
+
pushHeader(headers, startLine, text, match);
|
|
590
|
+
regex.lastIndex = match.index + match[0].length;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
function pushHeader(headers, startLine, chunkText, match) {
|
|
594
|
+
const header = matchToSectionHeader(startLine, chunkText, match);
|
|
595
|
+
if (!header.text && !header.hasSeparatorLine) return;
|
|
596
|
+
const previous = headers.at(-1);
|
|
597
|
+
if (previous && previous.endLineNumber >= header.startLineNumber) return;
|
|
598
|
+
headers.push(header);
|
|
599
|
+
}
|
|
600
|
+
function matchToSectionHeader(startLine, chunkText, match) {
|
|
601
|
+
const precedingText = chunkText.substring(0, match.index);
|
|
602
|
+
const lineNumber = startLine + (precedingText.match(/\n/g) || []).length;
|
|
603
|
+
const matchLines = match[0].split("\n");
|
|
604
|
+
const matchHeight = matchLines.length;
|
|
605
|
+
const lineStartIndex = precedingText.lastIndexOf("\n") + 1;
|
|
606
|
+
const startColumn = match.index - lineStartIndex + 1;
|
|
607
|
+
const lastMatchLine = matchLines[matchLines.length - 1] ?? "";
|
|
608
|
+
return {
|
|
609
|
+
startLineNumber: lineNumber,
|
|
610
|
+
startColumn,
|
|
611
|
+
endLineNumber: lineNumber + matchHeight - 1,
|
|
612
|
+
endColumn: matchHeight === 1 ? startColumn + match[0].length : lastMatchLine.length + 1,
|
|
613
|
+
text: (match.groups ?? {})["label"] ?? "",
|
|
614
|
+
hasSeparatorLine: ((match.groups ?? {})["separator"] ?? "") !== ""
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
function headerToDecoration(header) {
|
|
618
|
+
return {
|
|
619
|
+
startLineNumber: header.startLineNumber,
|
|
620
|
+
startColumn: header.startColumn,
|
|
621
|
+
endLineNumber: header.endLineNumber,
|
|
622
|
+
endColumn: header.endColumn,
|
|
623
|
+
position: "inline",
|
|
624
|
+
sectionHeaderStyle: headerStyle(header.hasSeparatorLine),
|
|
625
|
+
sectionHeaderText: header.text
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
function shiftDecorationLineNumbers(decoration, lineDelta) {
|
|
629
|
+
if (lineDelta === 0) return decoration;
|
|
630
|
+
return {
|
|
631
|
+
...decoration,
|
|
632
|
+
startLineNumber: decoration.startLineNumber + lineDelta,
|
|
633
|
+
endLineNumber: decoration.endLineNumber + lineDelta
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
function headerStyle(hasSeparatorLine) {
|
|
637
|
+
return hasSeparatorLine ? "underlined" : "normal";
|
|
638
|
+
}
|
|
639
|
+
function createHeaderRegex(source) {
|
|
640
|
+
try {
|
|
641
|
+
return new RegExp(source, `gdm${isMultilineRegexSource(source) ? "s" : ""}`);
|
|
642
|
+
} catch {
|
|
643
|
+
return null;
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
function isMultilineRegexSource(source) {
|
|
647
|
+
return source.includes("\\n") || source.includes("\n");
|
|
648
|
+
}
|
|
649
|
+
function regExpLeadsToEndlessLoop(regexp) {
|
|
650
|
+
regexp.lastIndex = 0;
|
|
651
|
+
const match = regexp.exec("");
|
|
652
|
+
regexp.lastIndex = 0;
|
|
653
|
+
return match !== null && match[0].length === 0;
|
|
654
|
+
}
|
|
655
|
+
//#endregion
|
|
656
|
+
//#region src/renderer.ts
|
|
657
|
+
const EMPTY_DOCUMENT = {
|
|
658
|
+
textLength: 0,
|
|
659
|
+
lineStarts: [0],
|
|
660
|
+
lines: [{
|
|
661
|
+
text: "",
|
|
662
|
+
length: 0
|
|
663
|
+
}],
|
|
664
|
+
tokens: [],
|
|
665
|
+
selections: [],
|
|
666
|
+
decorations: [],
|
|
667
|
+
externalDecorations: []
|
|
668
|
+
};
|
|
669
|
+
var MinimapWorkerRenderer = class {
|
|
670
|
+
state = null;
|
|
671
|
+
init(options) {
|
|
672
|
+
const mainContext = options.mainCanvas.getContext("2d");
|
|
673
|
+
const decorationsContext = options.decorationsCanvas.getContext("2d");
|
|
674
|
+
if (!mainContext || !decorationsContext) throw new Error("Unable to create minimap canvas context");
|
|
675
|
+
this.state = {
|
|
676
|
+
mainCanvas: options.mainCanvas,
|
|
677
|
+
decorationsCanvas: options.decorationsCanvas,
|
|
678
|
+
mainContext,
|
|
679
|
+
decorationsContext,
|
|
680
|
+
options: options.options,
|
|
681
|
+
styles: options.styles,
|
|
682
|
+
document: EMPTY_DOCUMENT,
|
|
683
|
+
externalDecorations: [],
|
|
684
|
+
metrics: defaultMetrics(),
|
|
685
|
+
viewport: defaultViewport(),
|
|
686
|
+
layout: null,
|
|
687
|
+
previousFrame: null,
|
|
688
|
+
raster: null,
|
|
689
|
+
tokenIndex: null,
|
|
690
|
+
surface: null,
|
|
691
|
+
linesDirty: true,
|
|
692
|
+
decorationsDirty: true
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
setDocument(document) {
|
|
696
|
+
if (!this.state) return;
|
|
697
|
+
const externalDecorations = document.externalDecorations ?? externalDecorationsFrom(document.decorations);
|
|
698
|
+
const sectionHeaders = findSectionHeaderDecorations(documentLines(document), this.state.options);
|
|
699
|
+
this.state.externalDecorations = externalDecorations;
|
|
700
|
+
this.state.document = {
|
|
701
|
+
...document,
|
|
702
|
+
decorations: sectionHeaders.concat(externalDecorations),
|
|
703
|
+
externalDecorations
|
|
704
|
+
};
|
|
705
|
+
this.state.previousFrame = null;
|
|
706
|
+
this.state.linesDirty = true;
|
|
707
|
+
this.state.decorationsDirty = true;
|
|
708
|
+
}
|
|
709
|
+
applyEdit(edit, document) {
|
|
710
|
+
this.applyEdits([edit], document);
|
|
711
|
+
}
|
|
712
|
+
applyEdits(edits, document) {
|
|
713
|
+
if (!this.state) return;
|
|
714
|
+
if (edits.length === 0) {
|
|
715
|
+
this.setSelections(document.selections);
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
const previous = this.state.document;
|
|
719
|
+
const next = applyTextEditsToMinimapDocument(previous, edits, document.summaryPatch);
|
|
720
|
+
this.setEditedDocument(previous, next, edits, document.selections);
|
|
721
|
+
}
|
|
722
|
+
setExternalDecorations(decorations) {
|
|
723
|
+
if (!this.state) return;
|
|
724
|
+
this.state.externalDecorations = decorations;
|
|
725
|
+
this.state.document = {
|
|
726
|
+
...this.state.document,
|
|
727
|
+
externalDecorations: decorations,
|
|
728
|
+
decorations: sectionHeaderDecorationsFrom(this.state.document.decorations).concat(decorations)
|
|
729
|
+
};
|
|
730
|
+
this.state.decorationsDirty = true;
|
|
731
|
+
}
|
|
732
|
+
setEditedDocument(previous, document, edits, selections) {
|
|
733
|
+
const state = this.requireState();
|
|
734
|
+
const decorations = updateSectionHeaderDecorations(previous, document, edits, state.options).concat(state.externalDecorations);
|
|
735
|
+
state.document = {
|
|
736
|
+
textLength: document.textLength,
|
|
737
|
+
lineStarts: document.lineStarts,
|
|
738
|
+
lines: document.lines,
|
|
739
|
+
tokens: document.tokens,
|
|
740
|
+
selections,
|
|
741
|
+
decorations,
|
|
742
|
+
externalDecorations: state.externalDecorations
|
|
743
|
+
};
|
|
744
|
+
state.linesDirty = true;
|
|
745
|
+
state.decorationsDirty = true;
|
|
746
|
+
}
|
|
747
|
+
setBaseStyles(styles) {
|
|
748
|
+
if (!this.state) return;
|
|
749
|
+
this.state.styles = styles;
|
|
750
|
+
this.state.raster = null;
|
|
751
|
+
this.state.linesDirty = true;
|
|
752
|
+
this.state.decorationsDirty = true;
|
|
753
|
+
}
|
|
754
|
+
setTokens(tokens) {
|
|
755
|
+
if (!this.state) return;
|
|
756
|
+
this.state.document = {
|
|
757
|
+
...this.state.document,
|
|
758
|
+
tokens
|
|
759
|
+
};
|
|
760
|
+
this.state.linesDirty = true;
|
|
761
|
+
}
|
|
762
|
+
updateTokenRange(patch) {
|
|
763
|
+
if (!this.state) return;
|
|
764
|
+
const tokens = replaceTokenRange(this.state.document.tokens, patch);
|
|
765
|
+
this.state.document = {
|
|
766
|
+
...this.state.document,
|
|
767
|
+
tokens
|
|
768
|
+
};
|
|
769
|
+
this.state.linesDirty = true;
|
|
770
|
+
}
|
|
771
|
+
setSelections(selections) {
|
|
772
|
+
if (!this.state) return;
|
|
773
|
+
this.state.document = {
|
|
774
|
+
...this.state.document,
|
|
775
|
+
selections
|
|
776
|
+
};
|
|
777
|
+
this.state.decorationsDirty = true;
|
|
778
|
+
}
|
|
779
|
+
setDecorations(decorations) {
|
|
780
|
+
if (!this.state) return;
|
|
781
|
+
this.state.externalDecorations = externalDecorationsFrom(decorations);
|
|
782
|
+
this.state.document = {
|
|
783
|
+
...this.state.document,
|
|
784
|
+
decorations
|
|
785
|
+
};
|
|
786
|
+
this.state.decorationsDirty = true;
|
|
787
|
+
}
|
|
788
|
+
updateLayout(metrics, viewport) {
|
|
789
|
+
if (!this.state) return null;
|
|
790
|
+
this.state.metrics = metrics;
|
|
791
|
+
this.state.viewport = viewport;
|
|
792
|
+
const nextLayout = this.createRenderLayout();
|
|
793
|
+
const layoutChanged = !this.state.layout || !renderLayoutsEqual(this.state.layout, nextLayout);
|
|
794
|
+
this.state.layout = nextLayout;
|
|
795
|
+
if (layoutChanged) {
|
|
796
|
+
this.state.linesDirty = true;
|
|
797
|
+
this.state.decorationsDirty = true;
|
|
798
|
+
this.state.previousFrame = null;
|
|
799
|
+
}
|
|
800
|
+
this.resizeCanvases(this.state.layout);
|
|
801
|
+
return this.state.layout;
|
|
802
|
+
}
|
|
803
|
+
updateViewport(viewport) {
|
|
804
|
+
if (!this.state) return null;
|
|
805
|
+
const previous = this.state.layout;
|
|
806
|
+
const next = this.updateLayout(this.state.metrics, viewport);
|
|
807
|
+
if (!next || previous && renderLayoutsEqual(previous, next)) return null;
|
|
808
|
+
return next;
|
|
809
|
+
}
|
|
810
|
+
render() {
|
|
811
|
+
if (!this.state) return null;
|
|
812
|
+
const layout = this.state.layout ?? this.createRenderLayout();
|
|
813
|
+
this.state.layout = layout;
|
|
814
|
+
this.resizeCanvases(layout);
|
|
815
|
+
if (layout.renderMinimap === RenderMinimap.None) return emptyRenderResult();
|
|
816
|
+
const frame = computeFrameLayout({
|
|
817
|
+
renderLayout: layout,
|
|
818
|
+
metrics: this.state.metrics,
|
|
819
|
+
viewport: this.state.viewport,
|
|
820
|
+
lineCount: this.minimapLineCount(layout),
|
|
821
|
+
realLineCount: this.state.document.lineStarts.length,
|
|
822
|
+
previous: this.state.previousFrame
|
|
823
|
+
});
|
|
824
|
+
const frameChanged = !this.state.previousFrame || !framesPaintSameWindow(this.state.previousFrame, frame);
|
|
825
|
+
const surface = this.surfaceForLayout(layout);
|
|
826
|
+
if (this.state.linesDirty || frameChanged) this.renderLines(layout, frame, surface.mainContext);
|
|
827
|
+
if (this.state.decorationsDirty || frameChanged) this.renderDecorations(layout, frame, surface.decorationsContext);
|
|
828
|
+
this.presentSurface(layout, frame, surface);
|
|
829
|
+
this.state.previousFrame = frame;
|
|
830
|
+
this.state.linesDirty = false;
|
|
831
|
+
this.state.decorationsDirty = false;
|
|
832
|
+
return {
|
|
833
|
+
sliderNeeded: frame.sliderNeeded,
|
|
834
|
+
sliderTop: frame.sliderTop,
|
|
835
|
+
sliderHeight: frame.sliderHeight,
|
|
836
|
+
shadowVisible: this.state.viewport.scrollLeft + this.state.viewport.clientWidth < this.state.viewport.scrollWidth
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
dispose() {
|
|
840
|
+
this.state = null;
|
|
841
|
+
}
|
|
842
|
+
createRenderLayout() {
|
|
843
|
+
const state = this.requireState();
|
|
844
|
+
return computeRenderLayout({
|
|
845
|
+
minimap: state.options,
|
|
846
|
+
metrics: state.metrics,
|
|
847
|
+
viewport: state.viewport,
|
|
848
|
+
lineCount: state.document.lineStarts.length
|
|
849
|
+
});
|
|
850
|
+
}
|
|
851
|
+
renderLines(layout, frame, context) {
|
|
852
|
+
const state = this.requireState();
|
|
853
|
+
const raster = this.rasterForLayout(layout, context);
|
|
854
|
+
const previous = state.linesDirty ? null : state.previousFrame;
|
|
855
|
+
const reusedStart = Math.max(frame.startLineNumber, previous?.startLineNumber ?? Infinity);
|
|
856
|
+
const reusedEnd = Math.min(frame.endLineNumber, previous?.endLineNumber ?? -Infinity);
|
|
857
|
+
if (previous && reusedStart <= reusedEnd) {
|
|
858
|
+
const sourceY = yForLineNumber(previous, reusedStart, layout.lineHeight);
|
|
859
|
+
const destinationY = yForLineNumber(frame, reusedStart, layout.lineHeight);
|
|
860
|
+
const reusedHeight = (reusedEnd - reusedStart + 1) * layout.lineHeight;
|
|
861
|
+
raster.copyRows(sourceY, destinationY, reusedHeight);
|
|
862
|
+
raster.clearRows(0, destinationY);
|
|
863
|
+
raster.clearRows(destinationY + reusedHeight, raster.imageData.height);
|
|
864
|
+
this.renderLineRange(layout, frame, raster.imageData, frame.startLineNumber, reusedStart - 1);
|
|
865
|
+
this.renderLineRange(layout, frame, raster.imageData, reusedEnd + 1, frame.endLineNumber);
|
|
866
|
+
} else {
|
|
867
|
+
raster.clearRows(0, raster.imageData.height);
|
|
868
|
+
this.renderLineRange(layout, frame, raster.imageData, frame.startLineNumber, frame.endLineNumber);
|
|
869
|
+
}
|
|
870
|
+
context.putImageData(raster.imageData, 0, 0);
|
|
871
|
+
}
|
|
872
|
+
rasterForLayout(layout, context) {
|
|
873
|
+
const state = this.requireState();
|
|
874
|
+
const width = Math.max(1, layout.canvasInnerWidth);
|
|
875
|
+
const height = rasterHeight(layout);
|
|
876
|
+
if (state.raster?.imageData.width === width && state.raster.imageData.height === height) return state.raster;
|
|
877
|
+
state.raster = new RasterBuffer(context, width, height, state.styles.minimapBackground);
|
|
878
|
+
return state.raster;
|
|
879
|
+
}
|
|
880
|
+
renderLineRange(layout, frame, imageData, startLineNumber, endLineNumber) {
|
|
881
|
+
if (startLineNumber > endLineNumber) return;
|
|
882
|
+
const state = this.requireState();
|
|
883
|
+
const charRenderer = MinimapCharRendererFactory.create(layout.scale, state.styles.fontFamily);
|
|
884
|
+
const useLighterFont = relativeLuminance(state.styles.background) >= .5;
|
|
885
|
+
const renderBackground = state.styles.background;
|
|
886
|
+
let tokenCursor = this.tokenCursorForOffset(this.lineStartOffset(this.documentLineForRasterLine(layout, startLineNumber)));
|
|
887
|
+
for (let line = startLineNumber; line <= endLineNumber; line += 1) {
|
|
888
|
+
const documentLine = this.documentLineForRasterLine(layout, line);
|
|
889
|
+
const text = this.lineText(documentLine);
|
|
890
|
+
const lineStart = this.lineStartOffset(documentLine);
|
|
891
|
+
const lineEnd = lineStart + text.length;
|
|
892
|
+
const lineTokens = tokensForLineFromCursor(state.document.tokens, lineStart, lineEnd, tokenCursor);
|
|
893
|
+
tokenCursor = lineTokens.cursor;
|
|
894
|
+
this.renderLine({
|
|
895
|
+
imageData,
|
|
896
|
+
layout,
|
|
897
|
+
frame,
|
|
898
|
+
line,
|
|
899
|
+
text,
|
|
900
|
+
lineStart,
|
|
901
|
+
tokens: state.document.tokens,
|
|
902
|
+
tokenEnd: lineTokens.end,
|
|
903
|
+
tokenStart: lineTokens.start,
|
|
904
|
+
charRenderer,
|
|
905
|
+
useLighterFont,
|
|
906
|
+
renderBackground,
|
|
907
|
+
renderBackgroundAlpha: state.styles.minimapBackground.a
|
|
908
|
+
});
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
tokenCursorForOffset(offset) {
|
|
912
|
+
const state = this.requireState();
|
|
913
|
+
const tokens = state.document.tokens;
|
|
914
|
+
if (state.tokenIndex?.tokens !== tokens) state.tokenIndex = {
|
|
915
|
+
tokens,
|
|
916
|
+
maxEnds: tokenMaxEnds(tokens)
|
|
917
|
+
};
|
|
918
|
+
return firstTokenEndingAfter(state.tokenIndex.maxEnds, offset);
|
|
919
|
+
}
|
|
920
|
+
renderLine(options) {
|
|
921
|
+
const state = this.requireState();
|
|
922
|
+
const y = yForLineNumber(options.frame, options.line, options.layout.lineHeight);
|
|
923
|
+
const maxDx = options.layout.canvasInnerWidth - options.layout.charWidth;
|
|
924
|
+
let dx = 2;
|
|
925
|
+
visitTokenSegments(options.text, options.lineStart, options.tokens, options.tokenStart, options.tokenEnd, state.styles.foreground, (text, color) => {
|
|
926
|
+
if (dx > maxDx) return false;
|
|
927
|
+
dx = renderSegment({
|
|
928
|
+
...options,
|
|
929
|
+
text,
|
|
930
|
+
color,
|
|
931
|
+
dx,
|
|
932
|
+
y,
|
|
933
|
+
maxDx
|
|
934
|
+
});
|
|
935
|
+
return dx <= maxDx;
|
|
936
|
+
});
|
|
937
|
+
}
|
|
938
|
+
renderDecorations(layout, frame, context) {
|
|
939
|
+
context.clearRect(0, 0, layout.canvasInnerWidth, rasterHeight(layout));
|
|
940
|
+
const lineCount = this.requireState().document.lineStarts.length;
|
|
941
|
+
const decorationLayout = layout.isSampling ? {
|
|
942
|
+
...layout,
|
|
943
|
+
lineHeight: layout.canvasInnerHeight / Math.max(1, lineCount)
|
|
944
|
+
} : layout;
|
|
945
|
+
const decorationFrame = layout.isSampling ? {
|
|
946
|
+
...frame,
|
|
947
|
+
endLineNumber: lineCount
|
|
948
|
+
} : frame;
|
|
949
|
+
this.renderSelectionHighlights(decorationLayout, decorationFrame, context);
|
|
950
|
+
this.renderMinimapDecorations(decorationLayout, decorationFrame, context);
|
|
951
|
+
this.renderSectionHeaders(decorationLayout, decorationFrame, context);
|
|
952
|
+
}
|
|
953
|
+
renderSelectionHighlights(layout, frame, context) {
|
|
954
|
+
const state = this.requireState();
|
|
955
|
+
context.fillStyle = rgbaToCss(transparent(state.styles.selection, .5));
|
|
956
|
+
for (const selection of state.document.selections) {
|
|
957
|
+
const range = this.offsetRangeToLineRange(selection.startOffset, selection.endOffset);
|
|
958
|
+
fillLineRange(context, layout, frame, range.start, range.end);
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
renderMinimapDecorations(layout, frame, context) {
|
|
962
|
+
const state = this.requireState();
|
|
963
|
+
const decorations = state.document.decorations.filter((decoration) => !decoration.sectionHeaderStyle).toSorted((left, right) => (left.zIndex ?? 0) - (right.zIndex ?? 0));
|
|
964
|
+
for (const decoration of decorations) {
|
|
965
|
+
context.fillStyle = rgbaToCss(transparent(parseCssColor(decoration.color, state.styles.selection), .5));
|
|
966
|
+
fillDecorationRange(context, layout, frame, decoration);
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
renderSectionHeaders(layout, frame, context) {
|
|
970
|
+
const state = this.requireState();
|
|
971
|
+
const fontSize = state.options.sectionHeaderFontSize * state.metrics.devicePixelRatio;
|
|
972
|
+
context.font = `500 ${fontSize}px ${state.styles.fontFamily}`;
|
|
973
|
+
context.fillStyle = rgbaToCss(transparent(state.styles.minimapBackground, .7));
|
|
974
|
+
context.strokeStyle = rgbaToCss(state.styles.foreground);
|
|
975
|
+
context.lineWidth = .4;
|
|
976
|
+
for (const decoration of state.document.decorations) {
|
|
977
|
+
if (!decoration.sectionHeaderStyle) continue;
|
|
978
|
+
renderSectionHeader(context, decoration, layout, frame, fontSize, state.styles.foreground);
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
minimapLineCount(layout) {
|
|
982
|
+
const state = this.requireState();
|
|
983
|
+
if (!layout.isSampling) return state.document.lineStarts.length;
|
|
984
|
+
return Math.max(1, Math.min(state.document.lineStarts.length, layout.canvasInnerHeight));
|
|
985
|
+
}
|
|
986
|
+
documentLineForRasterLine(layout, line) {
|
|
987
|
+
if (!layout.isSampling) return line;
|
|
988
|
+
const count = this.requireState().document.lineStarts.length;
|
|
989
|
+
return Math.floor((line - 1) * count / this.minimapLineCount(layout)) + 1;
|
|
990
|
+
}
|
|
991
|
+
lineText(lineNumber) {
|
|
992
|
+
return this.requireState().document.lines[lineNumber - 1]?.text ?? "";
|
|
993
|
+
}
|
|
994
|
+
lineStartOffset(lineNumber) {
|
|
995
|
+
const state = this.requireState();
|
|
996
|
+
return state.document.lineStarts[lineNumber - 1] ?? state.document.textLength;
|
|
997
|
+
}
|
|
998
|
+
offsetRangeToLineRange(startOffset, endOffset) {
|
|
999
|
+
return {
|
|
1000
|
+
start: this.lineNumberForOffset(startOffset),
|
|
1001
|
+
end: this.lineNumberForOffset(endOffset)
|
|
1002
|
+
};
|
|
1003
|
+
}
|
|
1004
|
+
lineNumberForOffset(offset) {
|
|
1005
|
+
const state = this.requireState();
|
|
1006
|
+
let low = 0;
|
|
1007
|
+
let high = state.document.lineStarts.length - 1;
|
|
1008
|
+
const clamped = Math.max(0, Math.min(offset, state.document.textLength));
|
|
1009
|
+
while (low <= high) {
|
|
1010
|
+
const middle = Math.floor((low + high) / 2);
|
|
1011
|
+
const start = state.document.lineStarts[middle] ?? 0;
|
|
1012
|
+
const next = state.document.lineStarts[middle + 1] ?? Number.POSITIVE_INFINITY;
|
|
1013
|
+
if (clamped < start) high = middle - 1;
|
|
1014
|
+
else if (clamped >= next) low = middle + 1;
|
|
1015
|
+
else return middle + 1;
|
|
1016
|
+
}
|
|
1017
|
+
return state.document.lineStarts.length;
|
|
1018
|
+
}
|
|
1019
|
+
resizeCanvases(layout) {
|
|
1020
|
+
const state = this.requireState();
|
|
1021
|
+
resizeCanvas(state.mainCanvas, layout.canvasInnerWidth, layout.canvasInnerHeight);
|
|
1022
|
+
resizeCanvas(state.decorationsCanvas, layout.canvasInnerWidth, layout.canvasInnerHeight);
|
|
1023
|
+
}
|
|
1024
|
+
surfaceForLayout(layout) {
|
|
1025
|
+
const state = this.requireState();
|
|
1026
|
+
state.surface ??= createRasterSurface();
|
|
1027
|
+
resizeCanvas(state.surface.mainCanvas, layout.canvasInnerWidth, rasterHeight(layout));
|
|
1028
|
+
resizeCanvas(state.surface.decorationsCanvas, layout.canvasInnerWidth, rasterHeight(layout));
|
|
1029
|
+
return state.surface;
|
|
1030
|
+
}
|
|
1031
|
+
presentSurface(layout, frame, surface) {
|
|
1032
|
+
const state = this.requireState();
|
|
1033
|
+
const offsetY = Math.round(frame.startLineFraction * layout.lineHeight);
|
|
1034
|
+
presentRaster(state.mainContext, surface.mainCanvas, state.mainCanvas, offsetY);
|
|
1035
|
+
presentRaster(state.decorationsContext, surface.decorationsCanvas, state.decorationsCanvas, offsetY);
|
|
1036
|
+
}
|
|
1037
|
+
requireState() {
|
|
1038
|
+
if (!this.state) throw new Error("Minimap renderer is not initialized");
|
|
1039
|
+
return this.state;
|
|
1040
|
+
}
|
|
1041
|
+
};
|
|
1042
|
+
function createRasterSurface() {
|
|
1043
|
+
const mainCanvas = new OffscreenCanvas(1, 1);
|
|
1044
|
+
const decorationsCanvas = new OffscreenCanvas(1, 1);
|
|
1045
|
+
const mainContext = mainCanvas.getContext("2d");
|
|
1046
|
+
const decorationsContext = decorationsCanvas.getContext("2d");
|
|
1047
|
+
if (!mainContext || !decorationsContext) throw createError({
|
|
1048
|
+
message: "Unable to create minimap raster surface",
|
|
1049
|
+
code: "MINIMAP_RASTER_CONTEXT",
|
|
1050
|
+
status: 500,
|
|
1051
|
+
why: "The browser did not provide a 2D context for the minimap raster.",
|
|
1052
|
+
fix: "Check OffscreenCanvas 2D support and canvas resource limits."
|
|
1053
|
+
});
|
|
1054
|
+
return {
|
|
1055
|
+
mainCanvas,
|
|
1056
|
+
decorationsCanvas,
|
|
1057
|
+
mainContext,
|
|
1058
|
+
decorationsContext
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
1061
|
+
function rasterHeight(layout) {
|
|
1062
|
+
return Math.max(1, (Math.ceil(layout.canvasInnerHeight / layout.lineHeight) + 1) * layout.lineHeight);
|
|
1063
|
+
}
|
|
1064
|
+
function presentRaster(context, source, target, offsetY) {
|
|
1065
|
+
context.globalCompositeOperation = "copy";
|
|
1066
|
+
context.imageSmoothingEnabled = true;
|
|
1067
|
+
context.drawImage(source, 0, offsetY, target.width, target.height, 0, 0, target.width, target.height);
|
|
1068
|
+
}
|
|
1069
|
+
function renderSegment(options) {
|
|
1070
|
+
let dx = options.dx;
|
|
1071
|
+
for (let index = 0; index < options.text.length; index += 1) {
|
|
1072
|
+
if (dx > options.maxDx) return dx;
|
|
1073
|
+
dx = renderCharacter(options, dx, options.text.charCodeAt(index));
|
|
1074
|
+
}
|
|
1075
|
+
return dx;
|
|
1076
|
+
}
|
|
1077
|
+
function renderCharacter(options, dx, code) {
|
|
1078
|
+
if (code === 9) return dx + 4 * options.layout.charWidth;
|
|
1079
|
+
if (code === 32) return dx + options.layout.charWidth;
|
|
1080
|
+
if (options.layout.renderMinimap === RenderMinimap.Blocks) {
|
|
1081
|
+
options.charRenderer.blockRenderChar(options.imageData, dx, options.y, options.color, 255, options.renderBackground, options.renderBackgroundAlpha, options.layout.lineHeight === 1);
|
|
1082
|
+
return dx + options.layout.charWidth;
|
|
1083
|
+
}
|
|
1084
|
+
options.charRenderer.renderChar(options.imageData, dx, options.y, code, options.color, 255, options.renderBackground, options.renderBackgroundAlpha, options.layout.scale, options.useLighterFont, options.layout.lineHeight === 1);
|
|
1085
|
+
return dx + options.layout.charWidth;
|
|
1086
|
+
}
|
|
1087
|
+
function visitTokenSegments(text, lineStart, tokens, tokenStart, tokenEnd, fallback, visit) {
|
|
1088
|
+
let cursor = 0;
|
|
1089
|
+
for (let index = tokenStart; index < tokenEnd; index += 1) {
|
|
1090
|
+
const token = tokens[index];
|
|
1091
|
+
const start = Math.max(0, token.start - lineStart);
|
|
1092
|
+
const end = Math.min(text.length, token.end - lineStart);
|
|
1093
|
+
if (start > cursor && !visit(text.slice(cursor, start), fallback)) return;
|
|
1094
|
+
if (end > start && !visit(text.slice(start, end), token.color)) return;
|
|
1095
|
+
cursor = Math.max(cursor, end);
|
|
1096
|
+
}
|
|
1097
|
+
if (cursor < text.length) visit(text.slice(cursor), fallback);
|
|
1098
|
+
}
|
|
1099
|
+
function tokensForLineFromCursor(tokens, lineStart, lineEnd, cursor) {
|
|
1100
|
+
let index = cursor;
|
|
1101
|
+
while (index < tokens.length && tokens[index].end <= lineStart) index += 1;
|
|
1102
|
+
const start = index;
|
|
1103
|
+
while (index < tokens.length && tokens[index].start < lineEnd) index += 1;
|
|
1104
|
+
return {
|
|
1105
|
+
start,
|
|
1106
|
+
end: index,
|
|
1107
|
+
cursor: start
|
|
1108
|
+
};
|
|
1109
|
+
}
|
|
1110
|
+
function tokenMaxEnds(tokens) {
|
|
1111
|
+
const maxEnds = new Float64Array(tokens.length);
|
|
1112
|
+
let maxEnd = 0;
|
|
1113
|
+
for (let index = 0; index < tokens.length; index += 1) {
|
|
1114
|
+
maxEnd = Math.max(maxEnd, tokens[index].end);
|
|
1115
|
+
maxEnds[index] = maxEnd;
|
|
1116
|
+
}
|
|
1117
|
+
return maxEnds;
|
|
1118
|
+
}
|
|
1119
|
+
function firstTokenEndingAfter(maxEnds, offset) {
|
|
1120
|
+
let low = 0;
|
|
1121
|
+
let high = maxEnds.length;
|
|
1122
|
+
while (low < high) {
|
|
1123
|
+
const middle = Math.floor((low + high) / 2);
|
|
1124
|
+
if (maxEnds[middle] <= offset) {
|
|
1125
|
+
low = middle + 1;
|
|
1126
|
+
continue;
|
|
1127
|
+
}
|
|
1128
|
+
high = middle;
|
|
1129
|
+
}
|
|
1130
|
+
return low;
|
|
1131
|
+
}
|
|
1132
|
+
function fillLineRange(context, layout, frame, startLineNumber, endLineNumber) {
|
|
1133
|
+
const start = Math.max(frame.startLineNumber, startLineNumber);
|
|
1134
|
+
const end = Math.min(frame.endLineNumber, endLineNumber);
|
|
1135
|
+
if (start > end) return;
|
|
1136
|
+
const y = yForLineNumber(frame, start, layout.lineHeight);
|
|
1137
|
+
const height = Math.max(layout.lineHeight, yForLineNumber(frame, end, layout.lineHeight) - y + layout.lineHeight);
|
|
1138
|
+
context.fillRect(2, y, layout.canvasInnerWidth, height);
|
|
1139
|
+
}
|
|
1140
|
+
function fillDecorationRange(context, layout, frame, decoration) {
|
|
1141
|
+
if (decoration.position === "gutter") {
|
|
1142
|
+
fillGutterLineRange(context, layout, frame, decoration.startLineNumber, decoration.endLineNumber);
|
|
1143
|
+
return;
|
|
1144
|
+
}
|
|
1145
|
+
fillLineRange(context, layout, frame, decoration.startLineNumber, decoration.endLineNumber);
|
|
1146
|
+
}
|
|
1147
|
+
function fillGutterLineRange(context, layout, frame, startLineNumber, endLineNumber) {
|
|
1148
|
+
const range = visibleLineRange(frame, startLineNumber, endLineNumber);
|
|
1149
|
+
if (!range) return;
|
|
1150
|
+
const y = yForLineNumber(frame, range.start, layout.lineHeight);
|
|
1151
|
+
const height = lineRangeHeight(layout, frame, range.end, y);
|
|
1152
|
+
const width = minimapRightGutterWidth(layout);
|
|
1153
|
+
context.fillRect(layout.canvasInnerWidth - width, y, width, height);
|
|
1154
|
+
}
|
|
1155
|
+
function visibleLineRange(frame, startLineNumber, endLineNumber) {
|
|
1156
|
+
const start = Math.max(frame.startLineNumber, startLineNumber);
|
|
1157
|
+
const end = Math.min(frame.endLineNumber, endLineNumber);
|
|
1158
|
+
if (start > end) return null;
|
|
1159
|
+
return {
|
|
1160
|
+
start,
|
|
1161
|
+
end
|
|
1162
|
+
};
|
|
1163
|
+
}
|
|
1164
|
+
function lineRangeHeight(layout, frame, endLineNumber, y) {
|
|
1165
|
+
return Math.max(layout.lineHeight, yForLineNumber(frame, endLineNumber, layout.lineHeight) - y + layout.lineHeight);
|
|
1166
|
+
}
|
|
1167
|
+
function minimapRightGutterWidth(layout) {
|
|
1168
|
+
const horizontalScale = layout.canvasInnerWidth / Math.max(1, layout.canvasOuterWidth);
|
|
1169
|
+
return Math.max(2, Math.ceil(8 * horizontalScale));
|
|
1170
|
+
}
|
|
1171
|
+
function renderSectionHeader(context, decoration, layout, frame, fontSize, color) {
|
|
1172
|
+
if (decoration.startLineNumber < frame.startLineNumber || decoration.startLineNumber > frame.endLineNumber) return;
|
|
1173
|
+
const y = yForLineNumber(frame, decoration.startLineNumber, layout.lineHeight) + fontSize;
|
|
1174
|
+
context.fillRect(0, y - fontSize, layout.canvasInnerWidth, fontSize * 1.5);
|
|
1175
|
+
context.fillStyle = rgbaToCss(color);
|
|
1176
|
+
if (decoration.sectionHeaderText) context.fillText(decoration.sectionHeaderText, 2, y, layout.canvasInnerWidth);
|
|
1177
|
+
if (decoration.sectionHeaderStyle === "underlined") {
|
|
1178
|
+
context.beginPath();
|
|
1179
|
+
context.moveTo(0, y - fontSize + 2);
|
|
1180
|
+
context.lineTo(layout.canvasInnerWidth, y - fontSize + 2);
|
|
1181
|
+
context.stroke();
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
function resizeCanvas(canvas, width, height) {
|
|
1185
|
+
const safeWidth = Math.max(1, width);
|
|
1186
|
+
const safeHeight = Math.max(1, height);
|
|
1187
|
+
if (canvas.width !== safeWidth) canvas.width = safeWidth;
|
|
1188
|
+
if (canvas.height !== safeHeight) canvas.height = safeHeight;
|
|
1189
|
+
}
|
|
1190
|
+
function renderLayoutsEqual(left, right) {
|
|
1191
|
+
return left.width === right.width && left.height === right.height && left.canvasInnerWidth === right.canvasInnerWidth && left.canvasInnerHeight === right.canvasInnerHeight && left.lineHeight === right.lineHeight && left.charWidth === right.charWidth && left.scale === right.scale && left.isSampling === right.isSampling && left.heightIsEditorHeight === right.heightIsEditorHeight && left.renderMinimap === right.renderMinimap;
|
|
1192
|
+
}
|
|
1193
|
+
function framesPaintSameWindow(left, right) {
|
|
1194
|
+
return left.startLineNumber === right.startLineNumber && left.endLineNumber === right.endLineNumber && left.topPaddingLineCount === right.topPaddingLineCount;
|
|
1195
|
+
}
|
|
1196
|
+
function defaultMetrics() {
|
|
1197
|
+
return {
|
|
1198
|
+
rowHeight: 20,
|
|
1199
|
+
characterWidth: 8,
|
|
1200
|
+
devicePixelRatio: 1
|
|
1201
|
+
};
|
|
1202
|
+
}
|
|
1203
|
+
function defaultViewport() {
|
|
1204
|
+
return {
|
|
1205
|
+
scrollTop: 0,
|
|
1206
|
+
scrollRow: 0,
|
|
1207
|
+
scrollLeft: 0,
|
|
1208
|
+
scrollHeight: 0,
|
|
1209
|
+
scrollWidth: 0,
|
|
1210
|
+
clientHeight: 0,
|
|
1211
|
+
clientWidth: 0,
|
|
1212
|
+
minimapHeight: 0,
|
|
1213
|
+
visibleStart: 0,
|
|
1214
|
+
reservedWidth: 0,
|
|
1215
|
+
visibleEnd: 1
|
|
1216
|
+
};
|
|
1217
|
+
}
|
|
1218
|
+
function emptyRenderResult() {
|
|
1219
|
+
return {
|
|
1220
|
+
sliderNeeded: false,
|
|
1221
|
+
sliderTop: 0,
|
|
1222
|
+
sliderHeight: 0,
|
|
1223
|
+
shadowVisible: false
|
|
1224
|
+
};
|
|
1225
|
+
}
|
|
1226
|
+
function applyTextEditsToMinimapDocument(document, edits, summaryPatch) {
|
|
1227
|
+
let tokens = document.tokens;
|
|
1228
|
+
for (const edit of edits) tokens = projectMinimapTokensThroughEdit(tokens, edit, tokenProjectionText(document));
|
|
1229
|
+
return {
|
|
1230
|
+
...applyMinimapDocumentSummaryPatch(document, summaryPatch),
|
|
1231
|
+
tokens
|
|
1232
|
+
};
|
|
1233
|
+
}
|
|
1234
|
+
function applyMinimapDocumentSummaryPatch(document, patch) {
|
|
1235
|
+
const startLine = Math.min(Math.max(0, patch.startLine), document.lines.length);
|
|
1236
|
+
const deleteCount = Math.min(Math.max(0, patch.deleteCount), document.lines.length - startLine);
|
|
1237
|
+
return {
|
|
1238
|
+
textLength: patch.textLength,
|
|
1239
|
+
lineStarts: patch.lineStarts ?? splicedLineStarts(document, patch, startLine, deleteCount),
|
|
1240
|
+
lines: document.lines.slice(0, startLine).concat(patch.lines, document.lines.slice(startLine + deleteCount))
|
|
1241
|
+
};
|
|
1242
|
+
}
|
|
1243
|
+
function splicedLineStarts(document, patch, startLine, deleteCount) {
|
|
1244
|
+
const previous = document.lineStarts;
|
|
1245
|
+
const next = previous.slice(0, startLine);
|
|
1246
|
+
let cursor = lineStartAt(previous, document.lines, startLine);
|
|
1247
|
+
for (const line of patch.lines) {
|
|
1248
|
+
next.push(cursor);
|
|
1249
|
+
cursor += line.length + 1;
|
|
1250
|
+
}
|
|
1251
|
+
const delta = patch.textLength - document.textLength;
|
|
1252
|
+
for (let index = startLine + deleteCount; index < previous.length; index += 1) next.push(previous[index] + delta);
|
|
1253
|
+
return next;
|
|
1254
|
+
}
|
|
1255
|
+
function lineStartAt(lineStarts, lines, line) {
|
|
1256
|
+
const existing = lineStarts[line];
|
|
1257
|
+
if (existing !== void 0) return existing;
|
|
1258
|
+
if (line === 0) return 0;
|
|
1259
|
+
return (lineStarts[line - 1] ?? 0) + (lines[line - 1]?.length ?? 0) + 1;
|
|
1260
|
+
}
|
|
1261
|
+
function lineIndexForOffset(lineStarts, offset) {
|
|
1262
|
+
let low = 0;
|
|
1263
|
+
let high = lineStarts.length - 1;
|
|
1264
|
+
const clamped = Math.max(0, offset);
|
|
1265
|
+
while (low <= high) {
|
|
1266
|
+
const middle = Math.floor((low + high) / 2);
|
|
1267
|
+
const start = lineStarts[middle] ?? 0;
|
|
1268
|
+
const next = lineStarts[middle + 1] ?? Number.POSITIVE_INFINITY;
|
|
1269
|
+
if (clamped < start) {
|
|
1270
|
+
high = middle - 1;
|
|
1271
|
+
continue;
|
|
1272
|
+
}
|
|
1273
|
+
if (clamped >= next) {
|
|
1274
|
+
low = middle + 1;
|
|
1275
|
+
continue;
|
|
1276
|
+
}
|
|
1277
|
+
return middle;
|
|
1278
|
+
}
|
|
1279
|
+
return Math.max(0, lineStarts.length - 1);
|
|
1280
|
+
}
|
|
1281
|
+
const SECTION_HEADER_EDIT_CONTEXT_LINES = 10;
|
|
1282
|
+
function updateSectionHeaderDecorations(previous, next, edits, options) {
|
|
1283
|
+
if (!options.showMarkSectionHeaders) return [];
|
|
1284
|
+
if (edits.length === 0) return sectionHeaderDecorationsFrom(previous.decorations);
|
|
1285
|
+
const oldRange = affectedOldLineRange(previous.lineStarts, edits);
|
|
1286
|
+
const nextRange = affectedNextLineRange(next.lineStarts, edits);
|
|
1287
|
+
const lineDelta = next.lineStarts.length - previous.lineStarts.length;
|
|
1288
|
+
const shifted = shiftedSectionHeaders(previous.decorations, oldRange, nextRange, lineDelta);
|
|
1289
|
+
const rescanned = rescanSectionHeaders(next, nextRange, options);
|
|
1290
|
+
return shifted.concat(rescanned).sort(compareDecorationsByLine);
|
|
1291
|
+
}
|
|
1292
|
+
function affectedOldLineRange(lineStarts, edits) {
|
|
1293
|
+
let start = Number.POSITIVE_INFINITY;
|
|
1294
|
+
let end = 1;
|
|
1295
|
+
for (const edit of edits) {
|
|
1296
|
+
start = Math.min(start, lineIndexForOffset(lineStarts, edit.from) + 1);
|
|
1297
|
+
end = Math.max(end, lineIndexForOffset(lineStarts, edit.to) + 1);
|
|
1298
|
+
}
|
|
1299
|
+
return expandLineRange({
|
|
1300
|
+
start,
|
|
1301
|
+
end
|
|
1302
|
+
}, lineStarts.length);
|
|
1303
|
+
}
|
|
1304
|
+
function affectedNextLineRange(lineStarts, edits) {
|
|
1305
|
+
let start = Number.POSITIVE_INFINITY;
|
|
1306
|
+
let end = 1;
|
|
1307
|
+
for (const edit of edits) {
|
|
1308
|
+
start = Math.min(start, lineIndexForOffset(lineStarts, edit.from) + 1);
|
|
1309
|
+
end = Math.max(end, lineIndexForOffset(lineStarts, edit.from + edit.text.length) + 1);
|
|
1310
|
+
}
|
|
1311
|
+
return expandLineRange({
|
|
1312
|
+
start,
|
|
1313
|
+
end
|
|
1314
|
+
}, lineStarts.length);
|
|
1315
|
+
}
|
|
1316
|
+
function expandLineRange(range, lineCount) {
|
|
1317
|
+
return {
|
|
1318
|
+
start: Math.max(1, range.start - SECTION_HEADER_EDIT_CONTEXT_LINES),
|
|
1319
|
+
end: Math.min(lineCount, range.end + SECTION_HEADER_EDIT_CONTEXT_LINES)
|
|
1320
|
+
};
|
|
1321
|
+
}
|
|
1322
|
+
function shiftedSectionHeaders(decorations, oldRange, nextRange, lineDelta) {
|
|
1323
|
+
return sectionHeaderDecorationsFrom(decorations).flatMap((decoration) => shiftSectionHeaderDecoration(decoration, oldRange, nextRange, lineDelta));
|
|
1324
|
+
}
|
|
1325
|
+
function shiftSectionHeaderDecoration(decoration, oldRange, nextRange, lineDelta) {
|
|
1326
|
+
if (lineRangesIntersect(decoration, oldRange)) return [];
|
|
1327
|
+
if (decoration.startLineNumber <= oldRange.end) return lineRangesIntersect(decoration, nextRange) ? [] : [decoration];
|
|
1328
|
+
const shifted = shiftDecoration(decoration, lineDelta);
|
|
1329
|
+
return lineRangesIntersect(shifted, nextRange) ? [] : [shifted];
|
|
1330
|
+
}
|
|
1331
|
+
function shiftDecoration(decoration, lineDelta) {
|
|
1332
|
+
if (lineDelta === 0) return decoration;
|
|
1333
|
+
return {
|
|
1334
|
+
...decoration,
|
|
1335
|
+
startLineNumber: decoration.startLineNumber + lineDelta,
|
|
1336
|
+
endLineNumber: decoration.endLineNumber + lineDelta
|
|
1337
|
+
};
|
|
1338
|
+
}
|
|
1339
|
+
function rescanSectionHeaders(document, range, options) {
|
|
1340
|
+
return findSectionHeaderDecorationsInRange(linesForRange(document.lines, range), range.start, options);
|
|
1341
|
+
}
|
|
1342
|
+
function linesForRange(summaries, range) {
|
|
1343
|
+
const lines = [];
|
|
1344
|
+
for (let lineNumber = range.start; lineNumber <= range.end; lineNumber += 1) lines.push(summaries[lineNumber - 1]?.text ?? "");
|
|
1345
|
+
return lines;
|
|
1346
|
+
}
|
|
1347
|
+
function lineRangesIntersect(decoration, range) {
|
|
1348
|
+
return decoration.startLineNumber <= range.end && decoration.endLineNumber >= range.start;
|
|
1349
|
+
}
|
|
1350
|
+
function compareDecorationsByLine(left, right) {
|
|
1351
|
+
return left.startLineNumber - right.startLineNumber || left.endLineNumber - right.endLineNumber;
|
|
1352
|
+
}
|
|
1353
|
+
function externalDecorationsFrom(decorations) {
|
|
1354
|
+
return decorations.filter((decoration) => !decoration.sectionHeaderStyle);
|
|
1355
|
+
}
|
|
1356
|
+
function sectionHeaderDecorationsFrom(decorations) {
|
|
1357
|
+
return decorations.filter((decoration) => decoration.sectionHeaderStyle);
|
|
1358
|
+
}
|
|
1359
|
+
function replaceTokenRange(tokens, patch) {
|
|
1360
|
+
const start = clampTokenIndex(patch.start, tokens.length);
|
|
1361
|
+
const deleteEnd = clampTokenIndex(start + patch.deleteCount, tokens.length);
|
|
1362
|
+
return tokens.slice(0, start).concat(patch.tokens, tokens.slice(deleteEnd));
|
|
1363
|
+
}
|
|
1364
|
+
function clampTokenIndex(index, length) {
|
|
1365
|
+
return Math.min(length, Math.max(0, index));
|
|
1366
|
+
}
|
|
1367
|
+
function projectMinimapTokensThroughEdit(tokens, edit, previousText) {
|
|
1368
|
+
const delta = edit.text.length - (edit.to - edit.from);
|
|
1369
|
+
const projected = [];
|
|
1370
|
+
for (const token of tokens) {
|
|
1371
|
+
const next = projectMinimapTokenThroughEdit(token, edit, previousText, delta);
|
|
1372
|
+
if (isRenderableMinimapToken(next)) projected.push(next);
|
|
1373
|
+
}
|
|
1374
|
+
return projected;
|
|
1375
|
+
}
|
|
1376
|
+
function projectMinimapTokenThroughEdit(token, edit, previousText, delta) {
|
|
1377
|
+
if (edit.from === edit.to) return projectMinimapTokenThroughInsertion(token, edit, previousText);
|
|
1378
|
+
if (token.end <= edit.from) return token;
|
|
1379
|
+
if (token.start >= edit.to) return shiftMinimapToken(token, delta);
|
|
1380
|
+
if (!canResizeMinimapTokenAcrossEdit(token, edit)) return null;
|
|
1381
|
+
return {
|
|
1382
|
+
...token,
|
|
1383
|
+
end: token.end + delta
|
|
1384
|
+
};
|
|
1385
|
+
}
|
|
1386
|
+
function projectMinimapTokenThroughInsertion(token, edit, previousText) {
|
|
1387
|
+
if (shouldExpandMinimapTokenForInsertion(token, edit, previousText)) return {
|
|
1388
|
+
...token,
|
|
1389
|
+
end: token.end + edit.text.length
|
|
1390
|
+
};
|
|
1391
|
+
if (token.start >= edit.from) return shiftMinimapToken(token, edit.text.length);
|
|
1392
|
+
return token;
|
|
1393
|
+
}
|
|
1394
|
+
function canResizeMinimapTokenAcrossEdit(token, edit) {
|
|
1395
|
+
if (edit.text.includes("\n")) return false;
|
|
1396
|
+
return token.start < edit.from && edit.to < token.end;
|
|
1397
|
+
}
|
|
1398
|
+
function shouldExpandMinimapTokenForInsertion(token, edit, previousText) {
|
|
1399
|
+
if (edit.text.length === 0) return false;
|
|
1400
|
+
if (edit.text.includes("\n")) return false;
|
|
1401
|
+
if (token.start < edit.from && edit.from < token.end) return true;
|
|
1402
|
+
if (!isWordLikeText(edit.text)) return false;
|
|
1403
|
+
if (token.end === edit.from) return textHasWordBeforeOffset(previousText, edit.from);
|
|
1404
|
+
if (token.start === edit.from) return !textHasWordBeforeOffset(previousText, edit.from) && textHasWordCodePointAt(previousText, edit.from);
|
|
1405
|
+
return false;
|
|
1406
|
+
}
|
|
1407
|
+
function shiftMinimapToken(token, delta) {
|
|
1408
|
+
return {
|
|
1409
|
+
...token,
|
|
1410
|
+
start: token.start + delta,
|
|
1411
|
+
end: token.end + delta
|
|
1412
|
+
};
|
|
1413
|
+
}
|
|
1414
|
+
function isRenderableMinimapToken(token) {
|
|
1415
|
+
if (!token) return false;
|
|
1416
|
+
return token.end > token.start;
|
|
1417
|
+
}
|
|
1418
|
+
function isWordLikeText(text) {
|
|
1419
|
+
return /^[\p{L}\p{N}_]+$/u.test(text);
|
|
1420
|
+
}
|
|
1421
|
+
function tokenProjectionText(document) {
|
|
1422
|
+
return {
|
|
1423
|
+
isWordBeforeOffset: (offset) => summaryHasWordBeforeOffset(document, offset),
|
|
1424
|
+
isWordCodePointAt: (offset) => summaryHasWordCodePointAt(document, offset)
|
|
1425
|
+
};
|
|
1426
|
+
}
|
|
1427
|
+
function documentLines(document) {
|
|
1428
|
+
return document.lines.map((line) => line.text);
|
|
1429
|
+
}
|
|
1430
|
+
function textHasWordBeforeOffset(text, offset) {
|
|
1431
|
+
if (typeof text !== "string") return text.isWordBeforeOffset(offset);
|
|
1432
|
+
return stringHasWordBeforeOffset(text, offset);
|
|
1433
|
+
}
|
|
1434
|
+
function textHasWordCodePointAt(text, offset) {
|
|
1435
|
+
if (typeof text !== "string") return text.isWordCodePointAt(offset);
|
|
1436
|
+
return stringHasWordCodePointAt(text, offset);
|
|
1437
|
+
}
|
|
1438
|
+
function summaryHasWordBeforeOffset(document, offset) {
|
|
1439
|
+
const location = summaryOffsetLocation(document, offset);
|
|
1440
|
+
if (!location) return false;
|
|
1441
|
+
return stringHasWordBeforeOffset(location.text, location.localOffset);
|
|
1442
|
+
}
|
|
1443
|
+
function summaryHasWordCodePointAt(document, offset) {
|
|
1444
|
+
const location = summaryOffsetLocation(document, offset);
|
|
1445
|
+
if (!location) return false;
|
|
1446
|
+
return stringHasWordCodePointAt(location.text, location.localOffset);
|
|
1447
|
+
}
|
|
1448
|
+
function summaryOffsetLocation(document, offset) {
|
|
1449
|
+
const clamped = Math.max(0, Math.min(offset, document.textLength));
|
|
1450
|
+
const lineIndex = lineIndexForOffset(document.lineStarts, clamped);
|
|
1451
|
+
const lineStart = document.lineStarts[lineIndex] ?? 0;
|
|
1452
|
+
const text = document.lines[lineIndex]?.text ?? "";
|
|
1453
|
+
const localOffset = clamped - lineStart;
|
|
1454
|
+
if (localOffset < 0 || localOffset > text.length) return null;
|
|
1455
|
+
return {
|
|
1456
|
+
text,
|
|
1457
|
+
localOffset
|
|
1458
|
+
};
|
|
1459
|
+
}
|
|
1460
|
+
function stringHasWordBeforeOffset(text, offset) {
|
|
1461
|
+
const previous = previousCodePointStart(text, offset);
|
|
1462
|
+
if (previous === null) return false;
|
|
1463
|
+
return stringHasWordCodePointAt(text, previous);
|
|
1464
|
+
}
|
|
1465
|
+
function stringHasWordCodePointAt(text, offset) {
|
|
1466
|
+
const codePoint = text.codePointAt(offset);
|
|
1467
|
+
if (codePoint === void 0) return false;
|
|
1468
|
+
return /^[\p{L}\p{N}_]$/u.test(String.fromCodePoint(codePoint));
|
|
1469
|
+
}
|
|
1470
|
+
function previousCodePointStart(text, offset) {
|
|
1471
|
+
if (offset <= 0) return null;
|
|
1472
|
+
const previous = offset - 1;
|
|
1473
|
+
const codeUnit = text.charCodeAt(previous);
|
|
1474
|
+
const beforePrevious = previous - 1;
|
|
1475
|
+
if (!(codeUnit >= 56320 && codeUnit <= 57343) || beforePrevious < 0) return previous;
|
|
1476
|
+
const previousCodeUnit = text.charCodeAt(beforePrevious);
|
|
1477
|
+
return previousCodeUnit >= 55296 && previousCodeUnit <= 56319 ? beforePrevious : previous;
|
|
1478
|
+
}
|
|
1479
|
+
//#endregion
|
|
1480
|
+
//#region src/minimap.worker.ts
|
|
1481
|
+
const renderer = new MinimapWorkerRenderer();
|
|
1482
|
+
globalThis.onmessage = (event) => {
|
|
1483
|
+
const request = event.data;
|
|
1484
|
+
try {
|
|
1485
|
+
measureWorkerRequest(request, () => handleRequest(request));
|
|
1486
|
+
} catch (error) {
|
|
1487
|
+
post({
|
|
1488
|
+
type: "error",
|
|
1489
|
+
message: errorMessage(error)
|
|
1490
|
+
});
|
|
1491
|
+
}
|
|
1492
|
+
};
|
|
1493
|
+
function handleRequest(request) {
|
|
1494
|
+
switch (request.type) {
|
|
1495
|
+
case "init":
|
|
1496
|
+
renderer.init({
|
|
1497
|
+
mainCanvas: request.mainCanvas,
|
|
1498
|
+
decorationsCanvas: request.decorationsCanvas,
|
|
1499
|
+
options: request.options,
|
|
1500
|
+
styles: request.baseStyles
|
|
1501
|
+
});
|
|
1502
|
+
return;
|
|
1503
|
+
case "updateBaseStyles":
|
|
1504
|
+
renderer.setBaseStyles(request.baseStyles);
|
|
1505
|
+
return;
|
|
1506
|
+
case "openDocument":
|
|
1507
|
+
case "replaceDocument":
|
|
1508
|
+
renderer.setDocument(request.document);
|
|
1509
|
+
return;
|
|
1510
|
+
case "applyEdit":
|
|
1511
|
+
renderer.applyEdit(request.edit, request.document);
|
|
1512
|
+
return;
|
|
1513
|
+
case "applyEdits":
|
|
1514
|
+
renderer.applyEdits(request.edits, request.document);
|
|
1515
|
+
return;
|
|
1516
|
+
case "updateTokens":
|
|
1517
|
+
renderer.setTokens(request.tokens);
|
|
1518
|
+
return;
|
|
1519
|
+
case "updateTokenRange":
|
|
1520
|
+
renderer.updateTokenRange(request.patch);
|
|
1521
|
+
return;
|
|
1522
|
+
case "updateSelection":
|
|
1523
|
+
renderer.setSelections(request.selections);
|
|
1524
|
+
return;
|
|
1525
|
+
case "updateDecorations":
|
|
1526
|
+
renderer.setDecorations(request.decorations);
|
|
1527
|
+
return;
|
|
1528
|
+
case "updateExternalDecorations":
|
|
1529
|
+
renderer.setExternalDecorations(request.decorations);
|
|
1530
|
+
return;
|
|
1531
|
+
case "updateLayout": {
|
|
1532
|
+
const layout = renderer.updateLayout(request.metrics, request.viewport);
|
|
1533
|
+
if (layout) post({
|
|
1534
|
+
type: "layout",
|
|
1535
|
+
sequence: 0,
|
|
1536
|
+
layout
|
|
1537
|
+
});
|
|
1538
|
+
return;
|
|
1539
|
+
}
|
|
1540
|
+
case "updateViewport": {
|
|
1541
|
+
const layout = renderer.updateViewport(request.viewport);
|
|
1542
|
+
if (layout) post({
|
|
1543
|
+
type: "layout",
|
|
1544
|
+
sequence: 0,
|
|
1545
|
+
layout
|
|
1546
|
+
});
|
|
1547
|
+
return;
|
|
1548
|
+
}
|
|
1549
|
+
case "render":
|
|
1550
|
+
postRender(request.sequence);
|
|
1551
|
+
return;
|
|
1552
|
+
case "dispose":
|
|
1553
|
+
renderer.dispose();
|
|
1554
|
+
post({ type: "disposed" });
|
|
1555
|
+
return;
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
function postRender(sequence) {
|
|
1559
|
+
const result = renderer.render();
|
|
1560
|
+
if (!result) return;
|
|
1561
|
+
post({
|
|
1562
|
+
type: "rendered",
|
|
1563
|
+
sequence,
|
|
1564
|
+
sliderNeeded: result.sliderNeeded,
|
|
1565
|
+
sliderTop: result.sliderTop,
|
|
1566
|
+
sliderHeight: result.sliderHeight,
|
|
1567
|
+
shadowVisible: result.shadowVisible
|
|
1568
|
+
});
|
|
1569
|
+
}
|
|
1570
|
+
function post(response) {
|
|
1571
|
+
globalThis.postMessage(response);
|
|
1572
|
+
}
|
|
1573
|
+
function errorMessage(error) {
|
|
1574
|
+
if (error instanceof Error) return error.message;
|
|
1575
|
+
return String(error);
|
|
1576
|
+
}
|
|
1577
|
+
function measureWorkerRequest(request, run) {
|
|
1578
|
+
const sink = minimapWorkerDiagnosticSink();
|
|
1579
|
+
if (!sink) {
|
|
1580
|
+
run();
|
|
1581
|
+
return;
|
|
1582
|
+
}
|
|
1583
|
+
const start = nowMs();
|
|
1584
|
+
try {
|
|
1585
|
+
run();
|
|
1586
|
+
} finally {
|
|
1587
|
+
recordMinimapWorkerDiagnostic(sink, {
|
|
1588
|
+
name: "minimap.worker.request",
|
|
1589
|
+
durationMs: nowMs() - start,
|
|
1590
|
+
detail: { request: request.type }
|
|
1591
|
+
});
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
function minimapWorkerDiagnosticSink() {
|
|
1595
|
+
const sink = minimapWorkerDiagnosticGlobal().__EDITOR_PERFORMANCE_DIAGNOSTICS__;
|
|
1596
|
+
if (!sink) return null;
|
|
1597
|
+
if (typeof sink === "function") return sink;
|
|
1598
|
+
if (sink.enabled !== true && typeof sink.record !== "function") return null;
|
|
1599
|
+
return sink;
|
|
1600
|
+
}
|
|
1601
|
+
function recordMinimapWorkerDiagnostic(sink, diagnostic) {
|
|
1602
|
+
if (typeof sink === "function") {
|
|
1603
|
+
sink(diagnostic);
|
|
1604
|
+
return;
|
|
1605
|
+
}
|
|
1606
|
+
sink.record?.(diagnostic);
|
|
1607
|
+
}
|
|
1608
|
+
function minimapWorkerDiagnosticGlobal() {
|
|
1609
|
+
return globalThis;
|
|
1610
|
+
}
|
|
1611
|
+
function nowMs() {
|
|
1612
|
+
return globalThis.performance?.now() ?? Date.now();
|
|
1613
|
+
}
|
|
1614
|
+
//#endregion
|
|
1615
|
+
|
|
1616
|
+
//# sourceMappingURL=minimap.worker-i4S59IOV.js.map
|