@lvce-editor/editor-worker 19.72.4 → 19.72.6

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.
@@ -2463,77 +2463,132 @@ const getHorizontalVisibleRange = (line, deltaX, width, averageCharWidth, tabSiz
2463
2463
  start
2464
2464
  };
2465
2465
  };
2466
- const getHorizontalVisibleRangeMeasured = async (line, deltaX, width, averageCharWidth, tabSize, threshold, measureWidth) => {
2467
- const fallback = () => {
2468
- const range = getHorizontalVisibleRange(line, deltaX, width, averageCharWidth, tabSize, threshold);
2466
+ const getHorizontalVisibleRangesMeasured = async (requests, measureWidths) => {
2467
+ const fallback = request => {
2468
+ const range = getHorizontalVisibleRange(request.line, request.deltaX, request.width, request.averageCharWidth, request.tabSize, request.threshold);
2469
2469
  return {
2470
2470
  ...range,
2471
- difference: getHorizontalVisibleDifference(line, range.start, deltaX, averageCharWidth, tabSize)
2471
+ difference: getHorizontalVisibleDifference(request.line, range.start, request.deltaX, request.averageCharWidth, request.tabSize)
2472
2472
  };
2473
2473
  };
2474
- if (line.length <= threshold) {
2475
- return fallback();
2474
+ const results = requests.map(request => fallback(request));
2475
+ const boundariesByLine = [];
2476
+ const measurementsByLine = [];
2477
+ const cursors = [];
2478
+ for (let lineIndex = 0; lineIndex < requests.length; lineIndex++) {
2479
+ const request = requests[lineIndex];
2480
+ if (request.line.length <= request.threshold) {
2481
+ continue;
2482
+ }
2483
+ const segments = getSegments(request.line);
2484
+ const boundaries = [0, ...segments.map(({
2485
+ index,
2486
+ segment
2487
+ }) => index + segment.length)];
2488
+ boundariesByLine[lineIndex] = boundaries;
2489
+ measurementsByLine[lineIndex] = new Map([[0, 0]]);
2490
+ const scrollOffset = Math.max(0, Number.isFinite(request.deltaX) ? request.deltaX : 0);
2491
+ const viewportWidth = Number.isFinite(request.width) ? Math.max(0, request.width) : Infinity;
2492
+ cursors.push({
2493
+ high: boundaries.length,
2494
+ isStart: true,
2495
+ lineIndex,
2496
+ low: 0,
2497
+ target: scrollOffset
2498
+ });
2499
+ cursors.push({
2500
+ high: boundaries.length,
2501
+ isStart: false,
2502
+ lineIndex,
2503
+ low: 0,
2504
+ target: scrollOffset + viewportWidth
2505
+ });
2476
2506
  }
2477
- const scrollOffset = Math.max(0, Number.isFinite(deltaX) ? deltaX : 0);
2478
- const viewportWidth = Number.isFinite(width) ? Math.max(0, width) : Infinity;
2479
- const segments = getSegments(line);
2480
- const boundaries = [0, ...segments.map(({
2481
- index,
2482
- segment
2483
- }) => index + segment.length)];
2484
- const measurements = new Map([[0, 0]]);
2485
- const measureBoundary = async boundaryIndex => {
2486
- const cached = measurements.get(boundaryIndex);
2487
- if (cached !== undefined) {
2488
- return cached;
2489
- }
2490
- const prefix = line.slice(0, boundaries[boundaryIndex]).replaceAll('\t', () => ' '.repeat(tabSize));
2491
- const measured = await measureWidth(prefix);
2492
- measurements.set(boundaryIndex, measured);
2493
- return measured;
2494
- };
2495
- const findGreatestBoundaryAtMost = async target => {
2496
- let low = 0;
2497
- let high = boundaries.length;
2498
- while (low < high) {
2499
- const middle = Math.floor((low + high) / 2);
2500
- if ((await measureBoundary(middle)) <= target) {
2501
- low = middle + 1;
2502
- } else {
2503
- high = middle;
2507
+ try {
2508
+ const addPendingMeasurement = (cursor, pending) => {
2509
+ if (cursor.low >= cursor.high) {
2510
+ return;
2504
2511
  }
2505
- }
2506
- return Math.max(0, low - 1);
2507
- };
2508
- const findFirstBoundaryAtLeast = async target => {
2509
- let low = 0;
2510
- let high = boundaries.length;
2511
- while (low < high) {
2512
- const middle = Math.floor((low + high) / 2);
2513
- if ((await measureBoundary(middle)) < target) {
2514
- low = middle + 1;
2512
+ const boundaryIndex = Math.floor((cursor.low + cursor.high) / 2);
2513
+ if (measurementsByLine[cursor.lineIndex].has(boundaryIndex)) {
2514
+ return;
2515
+ }
2516
+ const request = requests[cursor.lineIndex];
2517
+ const prefix = request.line.slice(0, boundariesByLine[cursor.lineIndex][boundaryIndex]).replaceAll('\t', () => ' '.repeat(request.tabSize));
2518
+ pending.set(`${cursor.lineIndex}:${boundaryIndex}`, {
2519
+ boundaryIndex,
2520
+ lineIndex: cursor.lineIndex,
2521
+ text: prefix
2522
+ });
2523
+ };
2524
+ const updateCursor = cursor => {
2525
+ if (cursor.low >= cursor.high) {
2526
+ return;
2527
+ }
2528
+ const middle = Math.floor((cursor.low + cursor.high) / 2);
2529
+ const measured = measurementsByLine[cursor.lineIndex].get(middle);
2530
+ if (measured === undefined) {
2531
+ return;
2532
+ }
2533
+ if (cursor.isStart ? measured <= cursor.target : measured < cursor.target) {
2534
+ cursor.low = middle + 1;
2515
2535
  } else {
2516
- high = middle;
2536
+ cursor.high = middle;
2537
+ }
2538
+ };
2539
+ while (cursors.some(({
2540
+ high,
2541
+ low
2542
+ }) => low < high)) {
2543
+ const pending = new Map();
2544
+ for (const cursor of cursors) {
2545
+ addPendingMeasurement(cursor, pending);
2546
+ }
2547
+ if (pending.size > 0) {
2548
+ const entries = pending.values().toArray();
2549
+ const measuredWidths = await measureWidths(entries.map(({
2550
+ text
2551
+ }) => text));
2552
+ if (measuredWidths.length !== entries.length) {
2553
+ throw new Error('Text measurement returned an unexpected number of widths');
2554
+ }
2555
+ for (let i = 0; i < entries.length; i++) {
2556
+ const {
2557
+ boundaryIndex,
2558
+ lineIndex
2559
+ } = entries[i];
2560
+ measurementsByLine[lineIndex].set(boundaryIndex, measuredWidths[i]);
2561
+ }
2562
+ }
2563
+ for (const cursor of cursors) {
2564
+ updateCursor(cursor);
2517
2565
  }
2518
2566
  }
2519
- return Math.min(boundaries.length - 1, low);
2520
- };
2521
- try {
2522
- const startBoundary = await findGreatestBoundaryAtMost(scrollOffset);
2523
- const endBoundary = await findFirstBoundaryAtLeast(scrollOffset + viewportWidth);
2524
- const start = boundaries[startBoundary];
2525
- let end = boundaries[endBoundary];
2526
- if (end < line.length) {
2527
- end = boundaries[Math.min(boundaries.length - 1, endBoundary + 1)];
2567
+ for (let lineIndex = 0; lineIndex < boundariesByLine.length; lineIndex++) {
2568
+ const boundaries = boundariesByLine[lineIndex];
2569
+ if (!boundaries) {
2570
+ continue;
2571
+ }
2572
+ const request = requests[lineIndex];
2573
+ const lineCursors = cursors.filter(cursor => cursor.lineIndex === lineIndex);
2574
+ const startBoundary = Math.max(0, lineCursors[0].low - 1);
2575
+ const endBoundary = Math.min(boundaries.length - 1, lineCursors[1].low);
2576
+ const start = boundaries[startBoundary];
2577
+ let end = boundaries[endBoundary];
2578
+ if (end < request.line.length) {
2579
+ end = boundaries[Math.min(boundaries.length - 1, endBoundary + 1)];
2580
+ }
2581
+ const scrollOffset = Math.max(0, Number.isFinite(request.deltaX) ? request.deltaX : 0);
2582
+ results[lineIndex] = {
2583
+ difference: (measurementsByLine[lineIndex].get(startBoundary) ?? 0) - scrollOffset,
2584
+ end,
2585
+ start
2586
+ };
2528
2587
  }
2529
- return {
2530
- difference: (await measureBoundary(startBoundary)) - scrollOffset,
2531
- end,
2532
- start
2533
- };
2534
2588
  } catch {
2535
- return fallback();
2589
+ // Keep the existing approximate range fallback when worker measurement fails.
2536
2590
  }
2591
+ return results;
2537
2592
  };
2538
2593
  const getHorizontalVisibleDifference = (line, start, deltaX, averageCharWidth, tabSize) => {
2539
2594
  const charWidth = averageCharWidth > 0 ? averageCharWidth : 1;
@@ -2953,6 +3008,10 @@ const measureTextWidthSlow = async (text, fontWeight, fontSize, fontFamily, lett
2953
3008
  return width;
2954
3009
  };
2955
3010
 
3011
+ const measureTextWidthsSlow = async (texts, fontWeight, fontSize, fontFamily, letterSpacing, isMonoSpaceFont, charWidth) => {
3012
+ return await invoke$e('TextMeasurement.measureTextWidths', texts, fontWeight, fontSize, fontFamily, letterSpacing, isMonoSpaceFont, charWidth);
3013
+ };
3014
+
2956
3015
  const measureTextWidth = async (text, fontWeight, fontSize, fontFamily, letterSpacing, isMonoSpaceFont, charWidth) => {
2957
3016
  // TODO maybe have a property for the whole text document
2958
3017
  // whether the document is ASCII or not
@@ -2963,6 +3022,29 @@ const measureTextWidth = async (text, fontWeight, fontSize, fontFamily, letterSp
2963
3022
  }
2964
3023
  return await measureTextWidthSlow(text, fontWeight, fontSize, fontFamily, letterSpacing, isMonoSpaceFont, charWidth);
2965
3024
  };
3025
+ const measureTextWidths = async (texts, fontWeight, fontSize, fontFamily, letterSpacing, isMonoSpaceFont, charWidth) => {
3026
+ const widths = Array.from({
3027
+ length: texts.length
3028
+ }, (_, index) => index);
3029
+ const slowTexts = [];
3030
+ const slowIndexes = [];
3031
+ for (let i = 0; i < texts.length; i++) {
3032
+ const text = texts[i];
3033
+ if (isMonoSpaceFont && isAscii(text)) {
3034
+ widths[i] = await measureTextWidthFast(text, charWidth);
3035
+ } else {
3036
+ slowTexts.push(text);
3037
+ slowIndexes.push(i);
3038
+ }
3039
+ }
3040
+ if (slowTexts.length > 0) {
3041
+ const slowWidths = await measureTextWidthsSlow(slowTexts, fontWeight, fontSize, fontFamily, letterSpacing, isMonoSpaceFont, charWidth);
3042
+ for (let i = 0; i < slowWidths.length; i++) {
3043
+ widths[slowIndexes[i]] = slowWidths[i];
3044
+ }
3045
+ }
3046
+ return widths;
3047
+ };
2966
3048
 
2967
3049
  const Dot = '.';
2968
3050
  const DoubleQuote$1 = '"';
@@ -3487,10 +3569,18 @@ const getLineInfosViewport = async (editor, tokens, embeddedResults, minLineY, m
3487
3569
  const tokenMap = get$5(languageId);
3488
3570
  let offset = minLineOffset;
3489
3571
  const tabSize = editor.tabSize ?? 2;
3572
+ const measuredRanges = await getHorizontalVisibleRangesMeasured(lines.slice(minLineY, maxLineY).map(line => ({
3573
+ averageCharWidth,
3574
+ deltaX,
3575
+ line,
3576
+ tabSize,
3577
+ threshold: horizontalVirtualizationThreshold,
3578
+ width
3579
+ })), texts => measureTextWidths(texts, editor.fontWeight, editor.fontSize, editor.fontFamily, editor.letterSpacing, editor.isMonospaceFont, editor.charWidth));
3490
3580
  for (let i = minLineY; i < maxLineY; i++) {
3491
3581
  const line = lines[i];
3492
3582
  const normalize = shouldNormalizeText(line);
3493
- const measuredRange = await getHorizontalVisibleRangeMeasured(line, deltaX, width, averageCharWidth, tabSize, horizontalVirtualizationThreshold, text => measureTextWidth(text, editor.fontWeight, editor.fontSize, editor.fontFamily, editor.letterSpacing, editor.isMonospaceFont, editor.charWidth));
3583
+ const measuredRange = measuredRanges[i - minLineY];
3494
3584
 
3495
3585
  // Use decorations that were pre-computed (includes links and diagnostics)
3496
3586
  // Filter decorations to only include those for this line
@@ -5627,7 +5717,7 @@ const FocusFindWidgetToggleReplace = 42;
5627
5717
  const SourceActions = 38;
5628
5718
  const FocusCodeGenerator = 52;
5629
5719
 
5630
- const createEditor2 = (id, uri, x, y, width, height, platform, assetDir, _languageId, _tokenizePath, _useCache, applicationId) => {
5720
+ const createEditor2 = (id, uri, x, y, width, height, platform, assetDir, _languageId, _tokenizePath, _useCache, applicationId, editorFileCacheEnabled = false) => {
5631
5721
  number(id);
5632
5722
  const editor = {
5633
5723
  ...(applicationId !== undefined && {
@@ -5654,6 +5744,7 @@ const createEditor2 = (id, uri, x, y, width, height, platform, assetDir, _langua
5654
5744
  differences: [],
5655
5745
  documentSymbols: [],
5656
5746
  dragAndDropEnabled: true,
5747
+ editorFileCacheEnabled,
5657
5748
  embeds: [],
5658
5749
  endOfLine: 'lf',
5659
5750
  endOfLineDecorations: [],
@@ -16742,11 +16833,78 @@ const intialize = async (syntaxHighlightingEnabled, syncIncremental) => {
16742
16833
  };
16743
16834
 
16744
16835
  const largeFileContentLength = 10 * 1024 * 1024;
16836
+ const editorFileCacheMaxBytes = 500_000;
16745
16837
  const pendingFileReads = new Map();
16746
- const readFile = (applicationId, uri, forceReload) => {
16838
+ const getFileStatFingerprint = stat => {
16839
+ if (!stat || typeof stat !== 'object' || !Number.isFinite(stat.size)) {
16840
+ return '';
16841
+ }
16842
+ const modifiedTime = stat.mtimeMs ?? stat.mtime;
16843
+ if (typeof modifiedTime !== 'number' && typeof modifiedTime !== 'string') {
16844
+ return '';
16845
+ }
16846
+ const fingerprint = [stat.type, stat.size, modifiedTime];
16847
+ const changedTime = stat.ctimeMs ?? stat.ctime;
16848
+ if (typeof changedTime === 'number' || typeof changedTime === 'string') {
16849
+ fingerprint.push(changedTime);
16850
+ }
16851
+ if (typeof stat.etag === 'string') {
16852
+ fingerprint.push(stat.etag);
16853
+ }
16854
+ return JSON.stringify(fingerprint);
16855
+ };
16856
+ const isExcludedFromFileCache = uri => {
16857
+ const path = uri.split(/[?#]/, 1)[0];
16858
+ const fileName = path.slice(path.lastIndexOf('/') + 1);
16859
+ return fileName === '.env' || fileName.startsWith('.env.');
16860
+ };
16861
+ const removeCachedEditorFile = (applicationId, uri) => {
16862
+ void invoke$9(undefined, 'CacheStorage.removeEditorFileCache', applicationId || '', uri).catch(() => {});
16863
+ };
16864
+ const readFileWithCache = async (applicationId, uri, forceReload, useCache) => {
16865
+ if (!useCache) {
16866
+ return readFile$1(applicationId, uri);
16867
+ }
16747
16868
  if (forceReload) {
16869
+ removeCachedEditorFile(applicationId, uri);
16870
+ return readFile$1(applicationId, uri);
16871
+ }
16872
+ if (isExcludedFromFileCache(uri)) {
16873
+ removeCachedEditorFile(applicationId, uri);
16748
16874
  return readFile$1(applicationId, uri);
16749
16875
  }
16876
+ let fingerprint = '';
16877
+ try {
16878
+ const stat = await invoke$9(applicationId, 'FileSystem.statWithMetadata', uri);
16879
+ if (stat?.size <= editorFileCacheMaxBytes) {
16880
+ fingerprint = getFileStatFingerprint(stat);
16881
+ }
16882
+ } catch {
16883
+ // Filesystems without stat metadata continue to use normal reads.
16884
+ }
16885
+ if (!fingerprint) {
16886
+ removeCachedEditorFile(applicationId, uri);
16887
+ }
16888
+ if (fingerprint) {
16889
+ try {
16890
+ const cached = await invoke$9(undefined, 'CacheStorage.getEditorFileCache', applicationId || '', uri, fingerprint);
16891
+ if (typeof cached === 'string') {
16892
+ return cached;
16893
+ }
16894
+ } catch {
16895
+ // Cache Storage failures must not prevent opening a file.
16896
+ }
16897
+ }
16898
+ const content = await readFile$1(applicationId, uri);
16899
+ if (fingerprint && new TextEncoder().encode(content).byteLength <= editorFileCacheMaxBytes) {
16900
+ void invoke$9(undefined, 'CacheStorage.setEditorFileCache', applicationId || '', uri, fingerprint, content).catch(() => {});
16901
+ }
16902
+ return content;
16903
+ };
16904
+ const readFile = (applicationId, uri, forceReload, useCache) => {
16905
+ if (forceReload) {
16906
+ return readFileWithCache(applicationId, uri, forceReload, useCache);
16907
+ }
16750
16908
  let applicationReads = pendingFileReads.get(applicationId);
16751
16909
  if (!applicationReads) {
16752
16910
  applicationReads = new Map();
@@ -16768,7 +16926,7 @@ const readFile = (applicationId, uri, forceReload) => {
16768
16926
  };
16769
16927
  fileRead = (async () => {
16770
16928
  try {
16771
- return await readFile$1(applicationId, uri);
16929
+ return await readFileWithCache(applicationId, uri, forceReload, useCache);
16772
16930
  } finally {
16773
16931
  removePendingRead();
16774
16932
  }
@@ -16927,7 +17085,7 @@ const loadContent = async (state, savedState, largeFile = false, focus = true, f
16927
17085
  let endOfLine = existingEditor?.endOfLine || 'lf';
16928
17086
  try {
16929
17087
  if (!existingEditor) {
16930
- content = await readFile(state.applicationId, uri, forceReload);
17088
+ content = await readFile(state.applicationId, uri, forceReload, state.editorFileCacheEnabled === true);
16931
17089
  endOfLine = getEndOfLine(content);
16932
17090
  content = normalizeLineEndings(content);
16933
17091
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lvce-editor/editor-worker",
3
- "version": "19.72.4",
3
+ "version": "19.72.6",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git@github.com:lvce-editor/editor-worker.git"