@lvce-editor/editor-worker 16.1.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,71 +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
3419
  const measureCharacterWidth = async (fontWeight, fontSize, fontFamily, letterSpacing) => {
3377
3420
  return await measureTextWidth('a', fontWeight, fontSize, fontFamily, letterSpacing, false, 0);
3378
3421
  };
@@ -3460,6 +3503,37 @@ const getVisibleDiagnostics = async (editor, diagnostics) => {
3460
3503
  return visibleDiagnostics;
3461
3504
  };
3462
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
+
3463
3537
  const updateDiagnostics = async newState => {
3464
3538
  try {
3465
3539
  // TODO handle error
@@ -3480,11 +3554,13 @@ const updateDiagnostics = async newState => {
3480
3554
  return newState;
3481
3555
  }
3482
3556
  const visualDecorations = await getVisibleDiagnostics(latest.newState, diagnostics);
3483
- // Re-detect link decorations after text changes
3484
- 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);
3485
3561
  const newEditor = {
3486
3562
  ...latest.newState,
3487
- decorations: linkDecorations,
3563
+ decorations: mergedDecorations,
3488
3564
  // Text-level decorations (flat array) for CSS classes
3489
3565
  diagnostics,
3490
3566
  visualDecorations // Visual decorations (objects with x, y, width, height) for squiggly underlines
@@ -5440,6 +5516,10 @@ const setPosition$1 = position => {
5440
5516
  state$3.position = position;
5441
5517
  };
5442
5518
 
5519
+ const openExternal = async url => {
5520
+ await invoke$b('Open.openUrl', url);
5521
+ };
5522
+
5443
5523
  // TODO first change cursor position, then run go to definition
5444
5524
  // cursor should appear at mousedown position immediately
5445
5525
  const handleSingleClickWithAlt = async (editor, position) => {
@@ -5447,12 +5527,21 @@ const handleSingleClickWithAlt = async (editor, position) => {
5447
5527
  columnIndex,
5448
5528
  rowIndex
5449
5529
  } = position;
5530
+
5531
+ // Check if the click is on a link
5532
+ const offset = offsetAt(editor, rowIndex, columnIndex);
5533
+ const url = getUrlAtOffset(editor, offset);
5534
+ if (url) {
5535
+ // Open the link
5536
+ await openExternal(url);
5537
+ return editor;
5538
+ }
5539
+
5540
+ // Otherwise, perform the default go to definition
5450
5541
  const newEditor = {
5451
5542
  ...editor,
5452
5543
  selections: new Uint32Array([rowIndex, columnIndex, rowIndex, columnIndex])
5453
5544
  };
5454
- // TODO rectangular selection with alt click,
5455
- // but also go to definition with alt click
5456
5545
  const newEditor2 = await goToDefinition(newEditor);
5457
5546
  return newEditor2;
5458
5547
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lvce-editor/editor-worker",
3
- "version": "16.1.0",
3
+ "version": "16.3.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git@github.com:lvce-editor/editor-worker.git"