@lvce-editor/find-widget-worker 3.1.0 → 3.2.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.
@@ -1181,16 +1181,16 @@ const focusIndex = async (state, index) => {
1181
1181
  const {
1182
1182
  editorUid,
1183
1183
  matches,
1184
- matchIndex,
1185
- value
1184
+ matchIndex
1186
1185
  } = state;
1187
1186
  if (index === matchIndex) {
1188
1187
  return state;
1189
1188
  }
1190
1189
  // // TODO find next match and highlight it
1191
- const matchRowIndex = matches[index * 2];
1192
- const matchColumnIndex = matches[index * 2 + 1];
1193
- const newSelections = new Uint32Array([matchRowIndex, matchColumnIndex, matchRowIndex, matchColumnIndex + value.length]);
1190
+ const matchRowIndex = matches[index * 3];
1191
+ const matchColumnIndex = matches[index * 3 + 1];
1192
+ const matchLength = matches[index * 3 + 2];
1193
+ const newSelections = new Uint32Array([matchRowIndex, matchColumnIndex, matchRowIndex, matchColumnIndex + matchLength]);
1194
1194
  await setSelections(editorUid, newSelections);
1195
1195
  return {
1196
1196
  ...state,
@@ -1287,7 +1287,7 @@ const findRegexMatches = (lines, regex) => {
1287
1287
  if (!lastMatch) {
1288
1288
  break;
1289
1289
  }
1290
- matches.push(i, lastMatch.index);
1290
+ matches.push(i, lastMatch.index, lastMatch[0].length);
1291
1291
  } while (true);
1292
1292
  }
1293
1293
  return new Uint32Array(matches);
@@ -1318,7 +1318,7 @@ const findMatches = (lines, searchString, options) => {
1318
1318
  };
1319
1319
 
1320
1320
  const getMatchCount = matches => {
1321
- return matches.length / 2;
1321
+ return matches.length / 3;
1322
1322
  };
1323
1323
 
1324
1324
  const measureTextHeight = (value, lineHeight) => {
@@ -1459,6 +1459,47 @@ const applyEdits = async (editorId, edits) => {
1459
1459
  await invoke('Editor.applyDocumentEdits', editorId, edits);
1460
1460
  };
1461
1461
 
1462
+ /**
1463
+ * Apply regex replacement with back-reference substitution ($1, $2, etc.)
1464
+ */
1465
+ const applyRegexReplacement = (originalText, regex, replacement) => {
1466
+ // Create a non-global version of the regex to get capture groups
1467
+ const flags = regex.flags.replace('g', '');
1468
+ const nonGlobalRegex = new RegExp(regex.source, flags);
1469
+ const match = nonGlobalRegex.exec(originalText);
1470
+ if (!match) {
1471
+ return replacement;
1472
+ }
1473
+
1474
+ // Replace $n back-references with captured groups
1475
+ let result = replacement;
1476
+
1477
+ // Handle $& (entire match)
1478
+ result = result.replaceAll('$&', match[0]);
1479
+
1480
+ // Handle $` (text before match) - in this context it's empty since we're matching the whole text
1481
+ result = result.replaceAll('$`', '');
1482
+
1483
+ // Handle $' (text after match) - in this context it's empty since we're matching the whole text
1484
+ result = result.replaceAll("$'", '');
1485
+
1486
+ // Handle $$ (literal $)
1487
+ result = result.replaceAll('$$', '\u0000DOLLAR\u0000');
1488
+
1489
+ // Handle $n and $nn (numbered capture groups)
1490
+ result = result.replaceAll(/\$(\d{1,2})/g, (_match, groupNumber) => {
1491
+ const index = Number.parseInt(groupNumber, 10);
1492
+ if (index < match.length) {
1493
+ return match[index] ?? '';
1494
+ }
1495
+ return `$${groupNumber}`;
1496
+ });
1497
+
1498
+ // Restore literal $
1499
+ result = result.replaceAll('\u0000DOLLAR\u0000', '$');
1500
+ return result;
1501
+ };
1502
+
1462
1503
  /**
1463
1504
  * Preserves the case pattern of the original text when replacing.
1464
1505
  * Supports uppercase, lowercase, PascalCase, and camelCase patterns.
@@ -1498,24 +1539,47 @@ const preserveCase$1 = (originalText, replacementText) => {
1498
1539
  return replacementText;
1499
1540
  };
1500
1541
 
1501
- const getEdits = (matches, value, replacement, startIndex, replaceAll, lines, preserveCaseOption = false) => {
1542
+ const getEdits = (matches, value, replacement, startIndex, replaceAll, lines, preserveCaseOption = false, options) => {
1502
1543
  if (value.length === 0 || matches.length === 0) {
1503
1544
  return [];
1504
1545
  }
1505
1546
  const edits = [];
1506
- const totalMatches = Math.floor(matches.length / 2);
1547
+ const totalMatches = Math.floor(matches.length / 3);
1507
1548
  const fromIndex = replaceAll ? 0 : Math.max(0, startIndex);
1508
1549
  const toIndex = replaceAll ? totalMatches : Math.min(totalMatches, fromIndex + 1);
1550
+
1551
+ // Build regex once if using regular expressions
1552
+ let regex;
1553
+ if (options?.useRegularExpression) {
1554
+ const {
1555
+ error,
1556
+ regex: builtRegex
1557
+ } = buildRegex(value, options);
1558
+ if (!error) {
1559
+ regex = builtRegex;
1560
+ }
1561
+ }
1509
1562
  for (let i = fromIndex; i < toIndex; i++) {
1510
- const rowIndex = matches[i * 2];
1511
- const startColumnIndex = matches[i * 2 + 1];
1512
- const endColumnIndex = startColumnIndex + value.length;
1563
+ const rowIndex = matches[i * 3];
1564
+ const startColumnIndex = matches[i * 3 + 1];
1565
+ const matchLength = matches[i * 3 + 2];
1566
+ const endColumnIndex = startColumnIndex + matchLength;
1513
1567
 
1514
1568
  // Get the original text that was matched
1515
1569
  const originalText = lines[rowIndex].slice(startColumnIndex, endColumnIndex);
1516
1570
 
1571
+ // Apply regex replacement with back-references if enabled
1572
+ let finalReplacement;
1573
+ if (options?.useRegularExpression && regex) {
1574
+ finalReplacement = applyRegexReplacement(originalText, regex, replacement);
1575
+ } else {
1576
+ finalReplacement = replacement;
1577
+ }
1578
+
1517
1579
  // Apply case preservation if enabled
1518
- const finalReplacement = preserveCaseOption ? preserveCase$1(originalText, replacement) : replacement;
1580
+ if (preserveCaseOption) {
1581
+ finalReplacement = preserveCase$1(originalText, finalReplacement);
1582
+ }
1519
1583
 
1520
1584
  // Convert row/column positions to character offsets
1521
1585
  let startOffset = 0;
@@ -1542,16 +1606,22 @@ const replace$1 = async state => {
1542
1606
  const {
1543
1607
  editorUid,
1544
1608
  lines,
1609
+ matchCase,
1545
1610
  matches,
1546
1611
  matchIndex,
1612
+ matchWholeWord,
1547
1613
  preserveCase,
1548
1614
  replacement,
1615
+ useRegularExpression,
1549
1616
  value
1550
1617
  } = state;
1551
- const edits = getEdits(matches, value, replacement, matchIndex, false, lines, preserveCase);
1618
+ const options = {
1619
+ matchCase,
1620
+ matchWholeWord,
1621
+ useRegularExpression
1622
+ };
1623
+ const edits = getEdits(matches, value, replacement, matchIndex, false, lines, preserveCase, options);
1552
1624
  await applyEdits(editorUid, edits);
1553
- // TODO ask editor worker to apply edit
1554
- // TODO ask editor worker for new lines?
1555
1625
  return {
1556
1626
  ...state
1557
1627
  };
@@ -1561,12 +1631,20 @@ const replaceAll$1 = state => {
1561
1631
  const {
1562
1632
  editorUid,
1563
1633
  lines,
1634
+ matchCase,
1564
1635
  matches,
1636
+ matchWholeWord,
1565
1637
  preserveCase,
1566
1638
  replacement,
1639
+ useRegularExpression,
1567
1640
  value
1568
1641
  } = state;
1569
- const edits = getEdits(matches, value, replacement, 0, true, lines, preserveCase);
1642
+ const options = {
1643
+ matchCase,
1644
+ matchWholeWord,
1645
+ useRegularExpression
1646
+ };
1647
+ const edits = getEdits(matches, value, replacement, 0, true, lines, preserveCase, options);
1570
1648
  void applyEdits(editorUid, edits);
1571
1649
  return {
1572
1650
  ...state
@@ -1605,7 +1683,7 @@ const handleFindWidgetFocus = (state, focusKey) => {
1605
1683
  const {
1606
1684
  focus
1607
1685
  } = state;
1608
- if (focus === focusKey) {
1686
+ if (!focusKey || focus === focusKey) {
1609
1687
  return state;
1610
1688
  }
1611
1689
  return {
@@ -1619,6 +1697,7 @@ const handleFindFocus = state => {
1619
1697
  return handleFindWidgetFocus(state, FindWidget$1);
1620
1698
  };
1621
1699
 
1700
+ // TODO maybe use one handleFocus function with a name parameter
1622
1701
  const handleReplaceFocus = state => {
1623
1702
  return handleFindWidgetFocus(state, FocusFindWidgetReplace);
1624
1703
  };
@@ -1822,6 +1901,10 @@ const getKeyBindings = () => {
1822
1901
  command: 'FindWidget.focusPreviousMatchButton',
1823
1902
  key: Tab,
1824
1903
  when: FocusFindWidgetReplace$1
1904
+ }, {
1905
+ command: 'FindWidget.replace',
1906
+ key: Enter,
1907
+ when: FocusFindWidgetReplace$1
1825
1908
  }, {
1826
1909
  command: 'FindWidget.replaceAll',
1827
1910
  key: Alt | CtrlCmd | Enter,
@@ -1916,6 +1999,9 @@ const getLines = async editorUid => {
1916
1999
  };
1917
2000
 
1918
2001
  const getSelectedText = (lines, selections) => {
2002
+ if (selections.length === 0) {
2003
+ return '';
2004
+ }
1919
2005
  const startRowIndex = selections[0];
1920
2006
  const startColumnIndex = selections[1];
1921
2007
  const endColumnIndex = selections[3];
@@ -2514,7 +2600,7 @@ const getEventListeners = (uid, widgetId) => {
2514
2600
  params: ['executeWidgetCommand', 'FindWidget', 'FindWidget.handleBlur', 0, widgetId]
2515
2601
  }, {
2516
2602
  name: HandleReplaceInput,
2517
- params: ['executeWidgetCommand', 'FindWidget', 'FindWidget.handleReplaceInput', 0, widgetId]
2603
+ params: ['executeWidgetCommand', 'FindWidget', 'FindWidget.handleReplaceInput', 0, widgetId, TargetValue]
2518
2604
  }, {
2519
2605
  name: HandleReplaceFocus,
2520
2606
  params: ['executeWidgetCommand', 'FindWidget', 'FindWidget.handleReplaceFocus', 0, widgetId]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lvce-editor/find-widget-worker",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "Web Worker for the find widget in Lvce Editor",
5
5
  "repository": {
6
6
  "type": "git",