@lvce-editor/editor-worker 19.72.5 → 19.72.7

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.
@@ -2370,45 +2370,43 @@ const getHorizontalScrollDimensions = ({
2370
2370
 
2371
2371
  const defaultThreshold = 500;
2372
2372
  const getSegments = line => {
2373
- if ('Segmenter' in Intl) {
2373
+ if (typeof Intl.Segmenter === 'function') {
2374
2374
  // @ts-ignore
2375
- return [...new Intl.Segmenter(undefined, {
2375
+ return [...Array.from(new Intl.Segmenter(undefined, {
2376
2376
  granularity: 'grapheme'
2377
- }).segment(line)];
2377
+ }).segment(line), ({
2378
+ index
2379
+ }) => index), line.length];
2378
2380
  }
2379
- const segments = [];
2381
+ const segments = [0];
2380
2382
  let index = 0;
2381
2383
  for (const segment of line) {
2382
- segments.push({
2383
- index,
2384
- segment
2385
- });
2386
2384
  index += segment.length;
2385
+ segments.push(index);
2387
2386
  }
2388
2387
  return segments;
2389
2388
  };
2390
- const getCharacterWidth = (character, tabSize) => {
2391
- if (character === '\t') {
2389
+ const getCharacterWidth = (line, start, end, tabSize) => {
2390
+ if (end - start === 1 && line.codePointAt(start) === 9) {
2392
2391
  return tabSize;
2393
2392
  }
2394
2393
  // Grapheme clusters such as combining characters and ZWJ emoji are indivisible.
2395
2394
  // Treat each cluster as one display column; the extra cluster below is a safe overscan.
2396
2395
  return 1;
2397
2396
  };
2398
- const getHorizontalDisplayColumn = (line, sourceColumn, tabSize) => {
2397
+ const getHorizontalDisplayColumn = (line, sourceColumn, tabSize, segments = getSegments(line)) => {
2399
2398
  let displayColumn = 0;
2400
- for (const {
2401
- index,
2402
- segment
2403
- } of getSegments(line)) {
2404
- if (index + segment.length > sourceColumn) {
2399
+ for (let i = 0; i < segments.length - 1; i++) {
2400
+ const start = segments[i];
2401
+ const end = segments[i + 1];
2402
+ if (end > sourceColumn) {
2405
2403
  break;
2406
2404
  }
2407
- displayColumn += segment === '\t' ? tabSize : segment.length;
2405
+ displayColumn += end - start === 1 && line.codePointAt(start) === 9 ? tabSize : end - start;
2408
2406
  }
2409
2407
  return displayColumn;
2410
2408
  };
2411
- const getHorizontalVisibleRange = (line, deltaX, width, averageCharWidth, tabSize, threshold = defaultThreshold) => {
2409
+ const getHorizontalVisibleRange = (line, deltaX, width, averageCharWidth, tabSize, threshold = defaultThreshold, segments = getSegments(line)) => {
2412
2410
  if (line.length <= threshold) {
2413
2411
  return {
2414
2412
  end: line.length,
@@ -2424,15 +2422,13 @@ const getHorizontalVisibleRange = (line, deltaX, width, averageCharWidth, tabSiz
2424
2422
  let index = 0;
2425
2423
  let start = 0;
2426
2424
  let started = false;
2427
- const segments = getSegments(line);
2428
- for (const {
2429
- index: segmentIndex,
2430
- segment
2431
- } of segments) {
2432
- const nextColumn = column + getCharacterWidth(segment, tabSize);
2425
+ for (let i = 0; i < segments.length - 1; i++) {
2426
+ const segmentIndex = segments[i];
2427
+ const segmentEnd = segments[i + 1];
2428
+ const nextColumn = column + getCharacterWidth(line, segmentIndex, segmentEnd, tabSize);
2433
2429
  if (!started && nextColumn <= targetStart) {
2434
2430
  column = nextColumn;
2435
- index = segmentIndex + segment.length;
2431
+ index = segmentEnd;
2436
2432
  start = index;
2437
2433
  continue;
2438
2434
  }
@@ -2440,7 +2436,7 @@ const getHorizontalVisibleRange = (line, deltaX, width, averageCharWidth, tabSiz
2440
2436
  start = segmentIndex;
2441
2437
  started = true;
2442
2438
  }
2443
- index = segmentIndex + segment.length;
2439
+ index = segmentEnd;
2444
2440
  column = nextColumn;
2445
2441
  if (column >= targetEnd) {
2446
2442
  break;
@@ -2451,11 +2447,9 @@ const getHorizontalVisibleRange = (line, deltaX, width, averageCharWidth, tabSiz
2451
2447
  }
2452
2448
  // Keep one extra complete grapheme to cover fractional offsets and avoid a blank edge.
2453
2449
  if (index < line.length) {
2454
- const nextSegment = segments.find(({
2455
- index: segmentIndex
2456
- }) => segmentIndex >= index);
2457
- if (nextSegment) {
2458
- index = nextSegment.index + nextSegment.segment.length;
2450
+ const nextSegmentIndex = segments.findIndex(segmentIndex => segmentIndex >= index);
2451
+ if (nextSegmentIndex !== -1 && nextSegmentIndex < segments.length - 1) {
2452
+ index = segments[nextSegmentIndex + 1];
2459
2453
  }
2460
2454
  }
2461
2455
  return {
@@ -2463,82 +2457,134 @@ const getHorizontalVisibleRange = (line, deltaX, width, averageCharWidth, tabSiz
2463
2457
  start
2464
2458
  };
2465
2459
  };
2466
- const getHorizontalVisibleRangeMeasured = async (line, deltaX, width, averageCharWidth, tabSize, threshold, measureWidth) => {
2467
- const fallback = () => {
2468
- const range = getHorizontalVisibleRange(line, deltaX, width, averageCharWidth, tabSize, threshold);
2460
+ const getHorizontalVisibleRangesMeasured = async (requests, measureWidths) => {
2461
+ const fallback = request => {
2462
+ const segments = request.segments || getSegments(request.line);
2463
+ const range = getHorizontalVisibleRange(request.line, request.deltaX, request.width, request.averageCharWidth, request.tabSize, request.threshold, segments);
2469
2464
  return {
2470
2465
  ...range,
2471
- difference: getHorizontalVisibleDifference(line, range.start, deltaX, averageCharWidth, tabSize)
2466
+ difference: getHorizontalVisibleDifference(request.line, range.start, request.deltaX, request.averageCharWidth, request.tabSize, segments)
2472
2467
  };
2473
2468
  };
2474
- if (line.length <= threshold) {
2475
- return fallback();
2469
+ const results = requests.map(request => fallback(request));
2470
+ const boundariesByLine = [];
2471
+ const measurementsByLine = [];
2472
+ const cursors = [];
2473
+ for (let lineIndex = 0; lineIndex < requests.length; lineIndex++) {
2474
+ const request = requests[lineIndex];
2475
+ if (request.line.length <= request.threshold) {
2476
+ continue;
2477
+ }
2478
+ const boundaries = request.segments || getSegments(request.line);
2479
+ boundariesByLine[lineIndex] = boundaries;
2480
+ measurementsByLine[lineIndex] = new Map([[0, 0]]);
2481
+ const scrollOffset = Math.max(0, Number.isFinite(request.deltaX) ? request.deltaX : 0);
2482
+ const viewportWidth = Number.isFinite(request.width) ? Math.max(0, request.width) : Infinity;
2483
+ cursors.push({
2484
+ high: boundaries.length,
2485
+ isStart: true,
2486
+ lineIndex,
2487
+ low: 0,
2488
+ target: scrollOffset
2489
+ });
2490
+ cursors.push({
2491
+ high: boundaries.length,
2492
+ isStart: false,
2493
+ lineIndex,
2494
+ low: 0,
2495
+ target: scrollOffset + viewportWidth
2496
+ });
2476
2497
  }
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;
2498
+ try {
2499
+ const addPendingMeasurement = (cursor, pending) => {
2500
+ if (cursor.low >= cursor.high) {
2501
+ return;
2504
2502
  }
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;
2503
+ const boundaryIndex = Math.floor((cursor.low + cursor.high) / 2);
2504
+ if (measurementsByLine[cursor.lineIndex].has(boundaryIndex)) {
2505
+ return;
2506
+ }
2507
+ const request = requests[cursor.lineIndex];
2508
+ const prefix = request.line.slice(0, boundariesByLine[cursor.lineIndex][boundaryIndex]).replaceAll('\t', () => ' '.repeat(request.tabSize));
2509
+ pending.set(`${cursor.lineIndex}:${boundaryIndex}`, {
2510
+ boundaryIndex,
2511
+ lineIndex: cursor.lineIndex,
2512
+ text: prefix
2513
+ });
2514
+ };
2515
+ const updateCursor = cursor => {
2516
+ if (cursor.low >= cursor.high) {
2517
+ return;
2518
+ }
2519
+ const middle = Math.floor((cursor.low + cursor.high) / 2);
2520
+ const measured = measurementsByLine[cursor.lineIndex].get(middle);
2521
+ if (measured === undefined) {
2522
+ return;
2523
+ }
2524
+ if (cursor.isStart ? measured <= cursor.target : measured < cursor.target) {
2525
+ cursor.low = middle + 1;
2515
2526
  } else {
2516
- high = middle;
2527
+ cursor.high = middle;
2528
+ }
2529
+ };
2530
+ while (cursors.some(({
2531
+ high,
2532
+ low
2533
+ }) => low < high)) {
2534
+ const pending = new Map();
2535
+ for (const cursor of cursors) {
2536
+ addPendingMeasurement(cursor, pending);
2537
+ }
2538
+ if (pending.size > 0) {
2539
+ const entries = pending.values().toArray();
2540
+ const measuredWidths = await measureWidths(entries.map(({
2541
+ text
2542
+ }) => text));
2543
+ if (measuredWidths.length !== entries.length) {
2544
+ throw new Error('Text measurement returned an unexpected number of widths');
2545
+ }
2546
+ for (let i = 0; i < entries.length; i++) {
2547
+ const {
2548
+ boundaryIndex,
2549
+ lineIndex
2550
+ } = entries[i];
2551
+ measurementsByLine[lineIndex].set(boundaryIndex, measuredWidths[i]);
2552
+ }
2553
+ }
2554
+ for (const cursor of cursors) {
2555
+ updateCursor(cursor);
2517
2556
  }
2518
2557
  }
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)];
2558
+ for (let lineIndex = 0; lineIndex < boundariesByLine.length; lineIndex++) {
2559
+ const boundaries = boundariesByLine[lineIndex];
2560
+ if (!boundaries) {
2561
+ continue;
2562
+ }
2563
+ const request = requests[lineIndex];
2564
+ const lineCursors = cursors.filter(cursor => cursor.lineIndex === lineIndex);
2565
+ const startBoundary = Math.max(0, lineCursors[0].low - 1);
2566
+ const endBoundary = Math.min(boundaries.length - 1, lineCursors[1].low);
2567
+ const start = boundaries[startBoundary];
2568
+ let end = boundaries[endBoundary];
2569
+ if (end < request.line.length) {
2570
+ end = boundaries[Math.min(boundaries.length - 1, endBoundary + 1)];
2571
+ }
2572
+ const scrollOffset = Math.max(0, Number.isFinite(request.deltaX) ? request.deltaX : 0);
2573
+ results[lineIndex] = {
2574
+ difference: (measurementsByLine[lineIndex].get(startBoundary) ?? 0) - scrollOffset,
2575
+ end,
2576
+ start
2577
+ };
2528
2578
  }
2529
- return {
2530
- difference: (await measureBoundary(startBoundary)) - scrollOffset,
2531
- end,
2532
- start
2533
- };
2534
2579
  } catch {
2535
- return fallback();
2580
+ // Keep the existing approximate range fallback when worker measurement fails.
2536
2581
  }
2582
+ return results;
2537
2583
  };
2538
- const getHorizontalVisibleDifference = (line, start, deltaX, averageCharWidth, tabSize) => {
2584
+ const getHorizontalVisibleDifference = (line, start, deltaX, averageCharWidth, tabSize, segments) => {
2539
2585
  const charWidth = averageCharWidth > 0 ? averageCharWidth : 1;
2540
2586
  const scrollOffset = Number.isFinite(deltaX) ? deltaX : 0;
2541
- return getHorizontalDisplayColumn(line, start, tabSize) * charWidth - scrollOffset;
2587
+ return getHorizontalDisplayColumn(line, start, tabSize, segments) * charWidth - scrollOffset;
2542
2588
  };
2543
2589
 
2544
2590
  // Large files have no token cache. Only materialize the text inside the horizontal viewport.
@@ -2953,6 +2999,10 @@ const measureTextWidthSlow = async (text, fontWeight, fontSize, fontFamily, lett
2953
2999
  return width;
2954
3000
  };
2955
3001
 
3002
+ const measureTextWidthsSlow = async (texts, fontWeight, fontSize, fontFamily, letterSpacing, isMonoSpaceFont, charWidth) => {
3003
+ return await invoke$e('TextMeasurement.measureTextWidths', texts, fontWeight, fontSize, fontFamily, letterSpacing, isMonoSpaceFont, charWidth);
3004
+ };
3005
+
2956
3006
  const measureTextWidth = async (text, fontWeight, fontSize, fontFamily, letterSpacing, isMonoSpaceFont, charWidth) => {
2957
3007
  // TODO maybe have a property for the whole text document
2958
3008
  // whether the document is ASCII or not
@@ -2963,6 +3013,29 @@ const measureTextWidth = async (text, fontWeight, fontSize, fontFamily, letterSp
2963
3013
  }
2964
3014
  return await measureTextWidthSlow(text, fontWeight, fontSize, fontFamily, letterSpacing, isMonoSpaceFont, charWidth);
2965
3015
  };
3016
+ const measureTextWidths = async (texts, fontWeight, fontSize, fontFamily, letterSpacing, isMonoSpaceFont, charWidth) => {
3017
+ const widths = Array.from({
3018
+ length: texts.length
3019
+ }, (_, index) => index);
3020
+ const slowTexts = [];
3021
+ const slowIndexes = [];
3022
+ for (let i = 0; i < texts.length; i++) {
3023
+ const text = texts[i];
3024
+ if (isMonoSpaceFont && isAscii(text)) {
3025
+ widths[i] = await measureTextWidthFast(text, charWidth);
3026
+ } else {
3027
+ slowTexts.push(text);
3028
+ slowIndexes.push(i);
3029
+ }
3030
+ }
3031
+ if (slowTexts.length > 0) {
3032
+ const slowWidths = await measureTextWidthsSlow(slowTexts, fontWeight, fontSize, fontFamily, letterSpacing, isMonoSpaceFont, charWidth);
3033
+ for (let i = 0; i < slowWidths.length; i++) {
3034
+ widths[slowIndexes[i]] = slowWidths[i];
3035
+ }
3036
+ }
3037
+ return widths;
3038
+ };
2966
3039
 
2967
3040
  const Dot = '.';
2968
3041
  const DoubleQuote$1 = '"';
@@ -3162,6 +3235,13 @@ const positionAt = (textDocument, offset) => {
3162
3235
  };
3163
3236
 
3164
3237
  const maxTokenizerLoadPasses$1 = 10;
3238
+ const getGraphemeSegments = async lines => {
3239
+ try {
3240
+ return await invoke$e('TextMeasurement.getGraphemeSegments', lines);
3241
+ } catch {
3242
+ return undefined;
3243
+ }
3244
+ };
3165
3245
 
3166
3246
  // const getTokensIncremental = (editor, min, max) => {
3167
3247
  // const currentLength = editor.lineStateCache.length
@@ -3487,10 +3567,21 @@ const getLineInfosViewport = async (editor, tokens, embeddedResults, minLineY, m
3487
3567
  const tokenMap = get$5(languageId);
3488
3568
  let offset = minLineOffset;
3489
3569
  const tabSize = editor.tabSize ?? 2;
3570
+ const visibleLines = lines.slice(minLineY, maxLineY);
3571
+ const graphemeSegments = await getGraphemeSegments(visibleLines);
3572
+ const measuredRanges = await getHorizontalVisibleRangesMeasured(visibleLines.map((line, index) => ({
3573
+ averageCharWidth,
3574
+ deltaX,
3575
+ line,
3576
+ segments: graphemeSegments?.[index],
3577
+ tabSize,
3578
+ threshold: horizontalVirtualizationThreshold,
3579
+ width
3580
+ })), texts => measureTextWidths(texts, editor.fontWeight, editor.fontSize, editor.fontFamily, editor.letterSpacing, editor.isMonospaceFont, editor.charWidth));
3490
3581
  for (let i = minLineY; i < maxLineY; i++) {
3491
3582
  const line = lines[i];
3492
3583
  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));
3584
+ const measuredRange = measuredRanges[i - minLineY];
3494
3585
 
3495
3586
  // Use decorations that were pre-computed (includes links and diagnostics)
3496
3587
  // Filter decorations to only include those for this line
@@ -3514,6 +3605,7 @@ const getLineInfosViewport = async (editor, tokens, embeddedResults, minLineY, m
3514
3605
  horizontalVisibleRanges.push({
3515
3606
  end: measuredRange.end,
3516
3607
  rowIndex: i,
3608
+ segments: graphemeSegments?.[i - minLineY],
3517
3609
  start: measuredRange.start
3518
3610
  });
3519
3611
  offset += line.length + 1;
@@ -17616,16 +17708,16 @@ const mergeConflictActions = [{
17616
17708
  action: 'both',
17617
17709
  label: 'Accept Both Changes'
17618
17710
  }];
17619
- const getUnnecessaryRanges = (diagnostics, rowIndex, textLength, visibleStartColumn, line, hasLine, tabSize) => {
17620
- const visibleStart = hasLine ? getHorizontalDisplayColumn(line, visibleStartColumn, tabSize) : visibleStartColumn;
17711
+ const getUnnecessaryRanges = (diagnostics, rowIndex, textLength, visibleStartColumn, line, hasLine, tabSize, segments) => {
17712
+ const visibleStart = hasLine ? getHorizontalDisplayColumn(line, visibleStartColumn, tabSize, segments) : visibleStartColumn;
17621
17713
  return diagnostics.filter(diagnostic => diagnostic.tags?.includes(DiagnosticTag.Unnecessary)).flatMap(diagnostic => {
17622
17714
  if (rowIndex < diagnostic.rowIndex || rowIndex > diagnostic.endRowIndex) {
17623
17715
  return [];
17624
17716
  }
17625
17717
  const diagnosticStart = rowIndex === diagnostic.rowIndex ? diagnostic.columnIndex : 0;
17626
17718
  const diagnosticEnd = rowIndex === diagnostic.endRowIndex ? diagnostic.endColumnIndex : line.length;
17627
- const startColumn = hasLine ? getHorizontalDisplayColumn(line, diagnosticStart, tabSize) : diagnosticStart;
17628
- const endColumn = hasLine ? getHorizontalDisplayColumn(line, diagnosticEnd, tabSize) : diagnosticEnd;
17719
+ const startColumn = hasLine ? getHorizontalDisplayColumn(line, diagnosticStart, tabSize, segments) : diagnosticStart;
17720
+ const endColumn = hasLine ? getHorizontalDisplayColumn(line, diagnosticEnd, tabSize, segments) : diagnosticEnd;
17629
17721
  const start = Math.max(0, startColumn - visibleStart);
17630
17722
  const end = Math.min(textLength, endColumn - visibleStart);
17631
17723
  return start < end ? [{
@@ -17709,7 +17801,7 @@ const getEditorRowsVirtualDom$1 = (textInfos, differences, lineNumbers = true, h
17709
17801
  for (let index = 0; index < textInfo.length; index += 2) {
17710
17802
  rowTextLength += textInfo[index].length;
17711
17803
  }
17712
- const tokenParts = getTokenParts(textInfo, getUnnecessaryRanges(diagnostics, rowIndex, rowTextLength, visibleRange.start, line, lines[rowIndex] !== undefined, tabSize));
17804
+ const tokenParts = getTokenParts(textInfo, getUnnecessaryRanges(diagnostics, rowIndex, rowTextLength, visibleRange.start, line, lines[rowIndex] !== undefined, tabSize, visibleRange.segments));
17713
17805
  let className = EditorRow;
17714
17806
  if (rowIndex === highlightedLine) {
17715
17807
  className = mergeClassNames(className, EditorRowHighlighted);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lvce-editor/editor-worker",
3
- "version": "19.72.5",
3
+ "version": "19.72.7",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git@github.com:lvce-editor/editor-worker.git"