@lvce-editor/editor-worker 16.2.0 → 16.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2619,6 +2619,108 @@ const getIncrementalEdits = async (oldState, newState) => {
2619
2619
  return emptyIncrementalEdits;
2620
2620
  };
2621
2621
 
2622
+ /**
2623
+ * Gets all regex matches for a given text and regex pattern
2624
+ * @param text The text to match against
2625
+ * @param regex The regex pattern to use (should have global flag)
2626
+ * @returns Array of regex matches
2627
+ */
2628
+ const getRegexMatches = (text, regex) => {
2629
+ return [...text.matchAll(regex)];
2630
+ };
2631
+
2632
+ // URL matching regex pattern - matches common URL schemes
2633
+ // Supports: http://, https://, ftp://, ftps://, file://
2634
+ // Also matches URLs without explicit scheme (www.example.com)
2635
+ const URL_PATTERN = /(?:(?:https?|ftp|ftps|file):\/\/)?(?:www\.)?(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?(?:\/[^\s]*)?/g;
2636
+
2637
+ // Regex to check if URL has a scheme (http://, https://, ftp://, etc.)
2638
+ const HAS_SCHEME_PATTERN = /^(?:https?|ftp|ftps|file):\/\//;
2639
+
2640
+ // Regex to check if URL starts with www.
2641
+ const HAS_WWW_PATTERN = /^www\./;
2642
+
2643
+ /**
2644
+ * Detects links in a given text and returns their positions
2645
+ * @param text The text to scan for links
2646
+ * @returns Array of links with their start position and length
2647
+ */
2648
+ const detectLinks = text => {
2649
+ const matches = getRegexMatches(text, URL_PATTERN);
2650
+ const links = [];
2651
+ for (const match of matches) {
2652
+ const url = match[0];
2653
+ // Only consider as link if it has a scheme or starts with www.
2654
+ if (HAS_SCHEME_PATTERN.test(url) || HAS_WWW_PATTERN.test(url)) {
2655
+ links.push({
2656
+ length: url.length,
2657
+ start: match.index ?? 0
2658
+ });
2659
+ }
2660
+ }
2661
+ return links;
2662
+ };
2663
+
2664
+ /**
2665
+ * Detects all links in an editor and returns them as decorations
2666
+ * @param editor The editor containing lines to scan
2667
+ * @returns Flat array of decorations in format [offset, length, type, modifiers, ...]
2668
+ */
2669
+ const detectAllLinksAsDecorations = editor => {
2670
+ const decorations = [];
2671
+ const {
2672
+ lines
2673
+ } = editor;
2674
+ let offset = 0;
2675
+ for (const line of lines) {
2676
+ const links = detectLinks(line);
2677
+ for (const link of links) {
2678
+ const linkOffset = offset + link.start;
2679
+ // Add link decoration: offset, length, type, modifiers
2680
+ decorations.push(linkOffset, link.length, Link, 0);
2681
+ }
2682
+ offset += line.length + 1; // +1 for newline
2683
+ }
2684
+ return decorations;
2685
+ };
2686
+
2687
+ /**
2688
+ * Gets the URL text at a given offset in the editor if it's a link
2689
+ * @param editor The editor
2690
+ * @param offset The offset in the document
2691
+ * @returns The URL string if the offset is on a link, or undefined
2692
+ */
2693
+ const getUrlAtOffset = (editor, offset) => {
2694
+ const {
2695
+ decorations,
2696
+ lines
2697
+ } = editor;
2698
+
2699
+ // Iterate through decorations in groups of 4 (offset, length, type, modifiers)
2700
+ for (let i = 0; i < decorations.length; i += 4) {
2701
+ const decorationOffset = decorations[i];
2702
+ const decorationLength = decorations[i + 1];
2703
+ const decorationType = decorations[i + 2];
2704
+
2705
+ // Check if this decoration is a link and if the offset falls within it
2706
+ if (decorationType === Link && offset >= decorationOffset && offset < decorationOffset + decorationLength) {
2707
+ // Extract the URL text from the editor content
2708
+ let currentOffset = 0;
2709
+ for (const line of lines) {
2710
+ const lineLength = line.length + 1; // +1 for newline
2711
+ if (currentOffset + lineLength > decorationOffset) {
2712
+ // The link starts in this line
2713
+ const linkStartInLine = decorationOffset - currentOffset;
2714
+ const url = line.slice(linkStartInLine, linkStartInLine + decorationLength);
2715
+ return url;
2716
+ }
2717
+ currentOffset += lineLength;
2718
+ }
2719
+ }
2720
+ }
2721
+ return undefined;
2722
+ };
2723
+
2622
2724
  const splitLines = lines => {
2623
2725
  if (!lines) {
2624
2726
  return [''];
@@ -3034,11 +3136,17 @@ const scheduleDocumentAndCursorsSelections = async (editor, changes, selectionCh
3034
3136
  selections: newSelections,
3035
3137
  undoStack: [...editor.undoStack, changes]
3036
3138
  };
3037
- set$6(editor.uid, editor, newEditor);
3038
- const incrementalEdits = await getIncrementalEdits(editor, newEditor);
3039
- const editorWithNewWidgets = await applyWidgetChanges(newEditor, changes);
3040
- const newEditor2 = {
3139
+ // Update link decorations after text changes
3140
+ const linkDecorations = detectAllLinksAsDecorations(newEditor);
3141
+ const newEditorWithDecorations = {
3041
3142
  ...newEditor,
3143
+ decorations: linkDecorations
3144
+ };
3145
+ set$6(editor.uid, editor, newEditorWithDecorations);
3146
+ const incrementalEdits = await getIncrementalEdits(editor, newEditorWithDecorations);
3147
+ const editorWithNewWidgets = await applyWidgetChanges(newEditorWithDecorations, changes);
3148
+ const newEditor2 = {
3149
+ ...newEditorWithDecorations,
3042
3150
  ...editorWithNewWidgets,
3043
3151
  incrementalEdits
3044
3152
  };
@@ -3308,108 +3416,6 @@ const getLanguages = async (platform, assetDir) => {
3308
3416
  return languages;
3309
3417
  };
3310
3418
 
3311
- /**
3312
- * Gets all regex matches for a given text and regex pattern
3313
- * @param text The text to match against
3314
- * @param regex The regex pattern to use (should have global flag)
3315
- * @returns Array of regex matches
3316
- */
3317
- const getRegexMatches = (text, regex) => {
3318
- return [...text.matchAll(regex)];
3319
- };
3320
-
3321
- // URL matching regex pattern - matches common URL schemes
3322
- // Supports: http://, https://, ftp://, ftps://, file://
3323
- // Also matches URLs without explicit scheme (www.example.com)
3324
- const URL_PATTERN = /(?:(?:https?|ftp|ftps|file):\/\/)?(?:www\.)?(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?(?:\/[^\s]*)?/g;
3325
-
3326
- // Regex to check if URL has a scheme (http://, https://, ftp://, etc.)
3327
- const HAS_SCHEME_PATTERN = /^(?:https?|ftp|ftps|file):\/\//;
3328
-
3329
- // Regex to check if URL starts with www.
3330
- const HAS_WWW_PATTERN = /^www\./;
3331
-
3332
- /**
3333
- * Detects links in a given text and returns their positions
3334
- * @param text The text to scan for links
3335
- * @returns Array of links with their start position and length
3336
- */
3337
- const detectLinks = text => {
3338
- const matches = getRegexMatches(text, URL_PATTERN);
3339
- const links = [];
3340
- for (const match of matches) {
3341
- const url = match[0];
3342
- // Only consider as link if it has a scheme or starts with www.
3343
- if (HAS_SCHEME_PATTERN.test(url) || HAS_WWW_PATTERN.test(url)) {
3344
- links.push({
3345
- length: url.length,
3346
- start: match.index ?? 0
3347
- });
3348
- }
3349
- }
3350
- return links;
3351
- };
3352
-
3353
- /**
3354
- * Detects all links in an editor and returns them as decorations
3355
- * @param editor The editor containing lines to scan
3356
- * @returns Flat array of decorations in format [offset, length, type, modifiers, ...]
3357
- */
3358
- const detectAllLinksAsDecorations = editor => {
3359
- const decorations = [];
3360
- const {
3361
- lines
3362
- } = editor;
3363
- let offset = 0;
3364
- for (const line of lines) {
3365
- const links = detectLinks(line);
3366
- for (const link of links) {
3367
- const linkOffset = offset + link.start;
3368
- // Add link decoration: offset, length, type, modifiers
3369
- decorations.push(linkOffset, link.length, Link, 0);
3370
- }
3371
- offset += line.length + 1; // +1 for newline
3372
- }
3373
- return decorations;
3374
- };
3375
-
3376
- /**
3377
- * Gets the URL text at a given offset in the editor if it's a link
3378
- * @param editor The editor
3379
- * @param offset The offset in the document
3380
- * @returns The URL string if the offset is on a link, or undefined
3381
- */
3382
- const getUrlAtOffset = (editor, offset) => {
3383
- const {
3384
- decorations,
3385
- lines
3386
- } = editor;
3387
-
3388
- // Iterate through decorations in groups of 4 (offset, length, type, modifiers)
3389
- for (let i = 0; i < decorations.length; i += 4) {
3390
- const decorationOffset = decorations[i];
3391
- const decorationLength = decorations[i + 1];
3392
- const decorationType = decorations[i + 2];
3393
-
3394
- // Check if this decoration is a link and if the offset falls within it
3395
- if (decorationType === Link && offset >= decorationOffset && offset < decorationOffset + decorationLength) {
3396
- // Extract the URL text from the editor content
3397
- let currentOffset = 0;
3398
- for (const line of lines) {
3399
- const lineLength = line.length + 1; // +1 for newline
3400
- if (currentOffset + lineLength > decorationOffset) {
3401
- // The link starts in this line
3402
- const linkStartInLine = decorationOffset - currentOffset;
3403
- const url = line.slice(linkStartInLine, linkStartInLine + decorationLength);
3404
- return url;
3405
- }
3406
- currentOffset += lineLength;
3407
- }
3408
- }
3409
- }
3410
- return undefined;
3411
- };
3412
-
3413
3419
  const measureCharacterWidth = async (fontWeight, fontSize, fontFamily, letterSpacing) => {
3414
3420
  return await measureTextWidth('a', fontWeight, fontSize, fontFamily, letterSpacing, false, 0);
3415
3421
  };
@@ -3497,6 +3503,37 @@ const getVisibleDiagnostics = async (editor, diagnostics) => {
3497
3503
  return visibleDiagnostics;
3498
3504
  };
3499
3505
 
3506
+ /**
3507
+ * Merges link decorations with diagnostic decorations
3508
+ * Links should always be present, but we also need to include any diagnostic decorations
3509
+ */
3510
+ const mergeLinksWithDiagnosticDecorations = (editor, diagnosticDecorations) => {
3511
+ // Get link decorations
3512
+ const linkDecorations = detectAllLinksAsDecorations(editor);
3513
+
3514
+ // Merge with diagnostic decorations
3515
+ const allDecorations = [...linkDecorations, ...diagnosticDecorations];
3516
+
3517
+ // Sort by offset to maintain proper order
3518
+ const sortedDecorations = [];
3519
+ for (let i = 0; i < allDecorations.length; i += 4) {
3520
+ sortedDecorations.push({
3521
+ length: allDecorations[i + 1],
3522
+ modifiers: allDecorations[i + 3],
3523
+ offset: allDecorations[i],
3524
+ type: allDecorations[i + 2]
3525
+ });
3526
+ }
3527
+ sortedDecorations.sort((a, b) => a.offset - b.offset);
3528
+
3529
+ // Flatten back to array format
3530
+ const result = [];
3531
+ for (const dec of sortedDecorations) {
3532
+ result.push(dec.offset, dec.length, dec.type, dec.modifiers);
3533
+ }
3534
+ return result;
3535
+ };
3536
+
3500
3537
  const updateDiagnostics = async newState => {
3501
3538
  try {
3502
3539
  // TODO handle error
@@ -3517,11 +3554,13 @@ const updateDiagnostics = async newState => {
3517
3554
  return newState;
3518
3555
  }
3519
3556
  const visualDecorations = await getVisibleDiagnostics(latest.newState, diagnostics);
3520
- // Re-detect link decorations after text changes
3521
- const linkDecorations = detectAllLinksAsDecorations(latest.newState);
3557
+ // Get diagnostic decorations from visual decorations (if any)
3558
+ const diagnosticDecorations = visualDecorations.flatMap(deco => [deco.offset, deco.length, deco.type, deco.modifiers || 0]);
3559
+ // Merge link decorations with diagnostic decorations
3560
+ const mergedDecorations = mergeLinksWithDiagnosticDecorations(latest.newState, diagnosticDecorations);
3522
3561
  const newEditor = {
3523
3562
  ...latest.newState,
3524
- decorations: linkDecorations,
3563
+ decorations: mergedDecorations,
3525
3564
  // Text-level decorations (flat array) for CSS classes
3526
3565
  diagnostics,
3527
3566
  visualDecorations // Visual decorations (objects with x, y, width, height) for squiggly underlines
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lvce-editor/editor-worker",
3
- "version": "16.2.0",
3
+ "version": "16.3.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git@github.com:lvce-editor/editor-worker.git"