@lvce-editor/find-widget-worker 3.1.0 → 3.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.
@@ -374,7 +374,7 @@ const callbacks = Object.create(null);
374
374
  const get$2 = id => {
375
375
  return callbacks[id];
376
376
  };
377
- const remove = id => {
377
+ const remove$1 = id => {
378
378
  delete callbacks[id];
379
379
  };
380
380
  class JsonRpcError extends Error {
@@ -527,7 +527,7 @@ const resolve = (id, response) => {
527
527
  return;
528
528
  }
529
529
  fn(response);
530
- remove(id);
530
+ remove$1(id);
531
531
  };
532
532
  const E_COMMAND_NOT_FOUND = 'E_COMMAND_NOT_FOUND';
533
533
  const getErrorType = prettyError => {
@@ -834,6 +834,34 @@ const WebWorkerRpcClient = {
834
834
  __proto__: null,
835
835
  create: create$1$1
836
836
  };
837
+ const createMockRpc = ({
838
+ commandMap
839
+ }) => {
840
+ const invocations = [];
841
+ const invoke = (method, ...params) => {
842
+ invocations.push([method, ...params]);
843
+ const command = commandMap[method];
844
+ if (!command) {
845
+ throw new Error(`command ${method} not found`);
846
+ }
847
+ return command(...params);
848
+ };
849
+ const mockRpc = {
850
+ invocations,
851
+ invoke,
852
+ invokeAndTransfer: invoke
853
+ };
854
+ return mockRpc;
855
+ };
856
+
857
+ const CheckBox = 'checkbox';
858
+ const Group = 'group';
859
+ const None = 'none';
860
+
861
+ const Button = 1;
862
+ const Div = 4;
863
+ const Text = 12;
864
+ const TextArea = 62;
837
865
 
838
866
  const ClientX = 'event.clientX';
839
867
  const ClientY = 'event.clientY';
@@ -887,7 +915,11 @@ const set$2 = (id, rpc) => {
887
915
  const get$1 = id => {
888
916
  return rpcs[id];
889
917
  };
918
+ const remove = id => {
919
+ delete rpcs[id];
920
+ };
890
921
 
922
+ /* eslint-disable @typescript-eslint/explicit-function-return-type */
891
923
  const create$2 = rpcId => {
892
924
  return {
893
925
  async dispose() {
@@ -906,6 +938,18 @@ const create$2 = rpcId => {
906
938
  // @ts-ignore
907
939
  return rpc.invokeAndTransfer(method, ...params);
908
940
  },
941
+ registerMockRpc(commandMap) {
942
+ const mockRpc = createMockRpc({
943
+ commandMap
944
+ });
945
+ set$2(rpcId, mockRpc);
946
+ // @ts-ignore
947
+ mockRpc[Symbol.dispose] = () => {
948
+ remove(rpcId);
949
+ };
950
+ // @ts-ignore
951
+ return mockRpc;
952
+ },
909
953
  set(rpc) {
910
954
  set$2(rpcId, rpc);
911
955
  }
@@ -1181,16 +1225,16 @@ const focusIndex = async (state, index) => {
1181
1225
  const {
1182
1226
  editorUid,
1183
1227
  matches,
1184
- matchIndex,
1185
- value
1228
+ matchIndex
1186
1229
  } = state;
1187
1230
  if (index === matchIndex) {
1188
1231
  return state;
1189
1232
  }
1190
1233
  // // 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]);
1234
+ const matchRowIndex = matches[index * 3];
1235
+ const matchColumnIndex = matches[index * 3 + 1];
1236
+ const matchLength = matches[index * 3 + 2];
1237
+ const newSelections = new Uint32Array([matchRowIndex, matchColumnIndex, matchRowIndex, matchColumnIndex + matchLength]);
1194
1238
  await setSelections(editorUid, newSelections);
1195
1239
  return {
1196
1240
  ...state,
@@ -1287,17 +1331,18 @@ const findRegexMatches = (lines, regex) => {
1287
1331
  if (!lastMatch) {
1288
1332
  break;
1289
1333
  }
1290
- matches.push(i, lastMatch.index);
1334
+ matches.push(i, lastMatch.index, lastMatch[0].length);
1291
1335
  } while (true);
1292
1336
  }
1293
1337
  return new Uint32Array(matches);
1294
1338
  };
1295
1339
 
1340
+ const emptyMatches$1 = new Uint32Array([]);
1296
1341
  const findMatches = (lines, searchString, options) => {
1297
1342
  if (searchString.length === 0) {
1298
1343
  return {
1299
1344
  error: '',
1300
- matches: new Uint32Array([])
1345
+ matches: emptyMatches$1
1301
1346
  };
1302
1347
  }
1303
1348
  const {
@@ -1307,7 +1352,7 @@ const findMatches = (lines, searchString, options) => {
1307
1352
  if (error) {
1308
1353
  return {
1309
1354
  error,
1310
- matches: new Uint32Array([])
1355
+ matches: emptyMatches$1
1311
1356
  };
1312
1357
  }
1313
1358
  const matches = findRegexMatches(lines, regex);
@@ -1318,7 +1363,7 @@ const findMatches = (lines, searchString, options) => {
1318
1363
  };
1319
1364
 
1320
1365
  const getMatchCount = matches => {
1321
- return matches.length / 2;
1366
+ return matches.length / 3;
1322
1367
  };
1323
1368
 
1324
1369
  const measureTextHeight = (value, lineHeight) => {
@@ -1459,6 +1504,47 @@ const applyEdits = async (editorId, edits) => {
1459
1504
  await invoke('Editor.applyDocumentEdits', editorId, edits);
1460
1505
  };
1461
1506
 
1507
+ /**
1508
+ * Apply regex replacement with back-reference substitution ($1, $2, etc.)
1509
+ */
1510
+ const applyRegexReplacement = (originalText, regex, replacement) => {
1511
+ // Create a non-global version of the regex to get capture groups
1512
+ const flags = regex.flags.replace('g', '');
1513
+ const nonGlobalRegex = new RegExp(regex.source, flags);
1514
+ const match = nonGlobalRegex.exec(originalText);
1515
+ if (!match) {
1516
+ return replacement;
1517
+ }
1518
+
1519
+ // Replace $n back-references with captured groups
1520
+ let result = replacement;
1521
+
1522
+ // Handle $& (entire match)
1523
+ result = result.replaceAll('$&', match[0]);
1524
+
1525
+ // Handle $` (text before match) - in this context it's empty since we're matching the whole text
1526
+ result = result.replaceAll('$`', '');
1527
+
1528
+ // Handle $' (text after match) - in this context it's empty since we're matching the whole text
1529
+ result = result.replaceAll("$'", '');
1530
+
1531
+ // Handle $$ (literal $)
1532
+ result = result.replaceAll('$$', '\u0000DOLLAR\u0000');
1533
+
1534
+ // Handle $n and $nn (numbered capture groups)
1535
+ result = result.replaceAll(/\$(\d{1,2})/g, (_match, groupNumber) => {
1536
+ const index = Number.parseInt(groupNumber, 10);
1537
+ if (index < match.length) {
1538
+ return match[index] ?? '';
1539
+ }
1540
+ return `$${groupNumber}`;
1541
+ });
1542
+
1543
+ // Restore literal $
1544
+ result = result.replaceAll('\u0000DOLLAR\u0000', '$');
1545
+ return result;
1546
+ };
1547
+
1462
1548
  /**
1463
1549
  * Preserves the case pattern of the original text when replacing.
1464
1550
  * Supports uppercase, lowercase, PascalCase, and camelCase patterns.
@@ -1498,24 +1584,47 @@ const preserveCase$1 = (originalText, replacementText) => {
1498
1584
  return replacementText;
1499
1585
  };
1500
1586
 
1501
- const getEdits = (matches, value, replacement, startIndex, replaceAll, lines, preserveCaseOption = false) => {
1587
+ const getEdits = (matches, value, replacement, startIndex, replaceAll, lines, preserveCaseOption = false, options) => {
1502
1588
  if (value.length === 0 || matches.length === 0) {
1503
1589
  return [];
1504
1590
  }
1505
1591
  const edits = [];
1506
- const totalMatches = Math.floor(matches.length / 2);
1592
+ const totalMatches = Math.floor(matches.length / 3);
1507
1593
  const fromIndex = replaceAll ? 0 : Math.max(0, startIndex);
1508
1594
  const toIndex = replaceAll ? totalMatches : Math.min(totalMatches, fromIndex + 1);
1595
+
1596
+ // Build regex once if using regular expressions
1597
+ let regex;
1598
+ if (options?.useRegularExpression) {
1599
+ const {
1600
+ error,
1601
+ regex: builtRegex
1602
+ } = buildRegex(value, options);
1603
+ if (!error) {
1604
+ regex = builtRegex;
1605
+ }
1606
+ }
1509
1607
  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;
1608
+ const rowIndex = matches[i * 3];
1609
+ const startColumnIndex = matches[i * 3 + 1];
1610
+ const matchLength = matches[i * 3 + 2];
1611
+ const endColumnIndex = startColumnIndex + matchLength;
1513
1612
 
1514
1613
  // Get the original text that was matched
1515
1614
  const originalText = lines[rowIndex].slice(startColumnIndex, endColumnIndex);
1516
1615
 
1616
+ // Apply regex replacement with back-references if enabled
1617
+ let finalReplacement;
1618
+ if (options?.useRegularExpression && regex) {
1619
+ finalReplacement = applyRegexReplacement(originalText, regex, replacement);
1620
+ } else {
1621
+ finalReplacement = replacement;
1622
+ }
1623
+
1517
1624
  // Apply case preservation if enabled
1518
- const finalReplacement = preserveCaseOption ? preserveCase$1(originalText, replacement) : replacement;
1625
+ if (preserveCaseOption) {
1626
+ finalReplacement = preserveCase$1(originalText, finalReplacement);
1627
+ }
1519
1628
 
1520
1629
  // Convert row/column positions to character offsets
1521
1630
  let startOffset = 0;
@@ -1538,39 +1647,86 @@ const getEdits = (matches, value, replacement, startIndex, replaceAll, lines, pr
1538
1647
  return edits;
1539
1648
  };
1540
1649
 
1650
+ const getLines = async editorUid => {
1651
+ const lines = await getLines$1(editorUid);
1652
+ return lines;
1653
+ };
1654
+
1655
+ const updateAfterReplace = async (state, matchIndex) => {
1656
+ const {
1657
+ editorUid,
1658
+ matchCase,
1659
+ matchWholeWord,
1660
+ useRegularExpression,
1661
+ value
1662
+ } = state;
1663
+ const options = {
1664
+ matchCase,
1665
+ matchWholeWord,
1666
+ useRegularExpression
1667
+ };
1668
+ const newLines = await getLines(editorUid);
1669
+ const {
1670
+ error,
1671
+ matches: newMatches
1672
+ } = findMatches(newLines, value, options);
1673
+ const newMatchCount = getMatchCount(newMatches);
1674
+
1675
+ // Keep matchIndex in bounds, or reset to 0 if no matches remain
1676
+ const newMatchIndex = newMatchCount === 0 ? 0 : Math.min(matchIndex, newMatchCount - 1);
1677
+ return {
1678
+ ...state,
1679
+ inputErrorMessage: error,
1680
+ lines: newLines,
1681
+ matchCount: newMatchCount,
1682
+ matches: newMatches,
1683
+ matchIndex: newMatchIndex
1684
+ };
1685
+ };
1686
+
1541
1687
  const replace$1 = async state => {
1542
1688
  const {
1543
1689
  editorUid,
1544
1690
  lines,
1691
+ matchCase,
1545
1692
  matches,
1546
1693
  matchIndex,
1694
+ matchWholeWord,
1547
1695
  preserveCase,
1548
1696
  replacement,
1697
+ useRegularExpression,
1549
1698
  value
1550
1699
  } = state;
1551
- const edits = getEdits(matches, value, replacement, matchIndex, false, lines, preserveCase);
1552
- await applyEdits(editorUid, edits);
1553
- // TODO ask editor worker to apply edit
1554
- // TODO ask editor worker for new lines?
1555
- return {
1556
- ...state
1700
+ const options = {
1701
+ matchCase,
1702
+ matchWholeWord,
1703
+ useRegularExpression
1557
1704
  };
1705
+ const edits = getEdits(matches, value, replacement, matchIndex, false, lines, preserveCase, options);
1706
+ await applyEdits(editorUid, edits);
1707
+ return updateAfterReplace(state, matchIndex);
1558
1708
  };
1559
1709
 
1560
- const replaceAll$1 = state => {
1710
+ const replaceAll$1 = async state => {
1561
1711
  const {
1562
1712
  editorUid,
1563
1713
  lines,
1714
+ matchCase,
1564
1715
  matches,
1716
+ matchWholeWord,
1565
1717
  preserveCase,
1566
1718
  replacement,
1719
+ useRegularExpression,
1567
1720
  value
1568
1721
  } = state;
1569
- const edits = getEdits(matches, value, replacement, 0, true, lines, preserveCase);
1570
- void applyEdits(editorUid, edits);
1571
- return {
1572
- ...state
1722
+ const options = {
1723
+ matchCase,
1724
+ matchWholeWord,
1725
+ useRegularExpression
1573
1726
  };
1727
+ const edits = getEdits(matches, value, replacement, 0, true, lines, preserveCase, options);
1728
+ await applyEdits(editorUid, edits);
1729
+ return updateAfterReplace(state, 0);
1574
1730
  };
1575
1731
 
1576
1732
  const getClickHandler = name => {
@@ -1605,7 +1761,7 @@ const handleFindWidgetFocus = (state, focusKey) => {
1605
1761
  const {
1606
1762
  focus
1607
1763
  } = state;
1608
- if (focus === focusKey) {
1764
+ if (!focusKey || focus === focusKey) {
1609
1765
  return state;
1610
1766
  }
1611
1767
  return {
@@ -1619,6 +1775,7 @@ const handleFindFocus = state => {
1619
1775
  return handleFindWidgetFocus(state, FindWidget$1);
1620
1776
  };
1621
1777
 
1778
+ // TODO maybe use one handleFocus function with a name parameter
1622
1779
  const handleReplaceFocus = state => {
1623
1780
  return handleFindWidgetFocus(state, FocusFindWidgetReplace);
1624
1781
  };
@@ -1822,6 +1979,10 @@ const getKeyBindings = () => {
1822
1979
  command: 'FindWidget.focusPreviousMatchButton',
1823
1980
  key: Tab,
1824
1981
  when: FocusFindWidgetReplace$1
1982
+ }, {
1983
+ command: 'FindWidget.replace',
1984
+ key: Enter,
1985
+ when: FocusFindWidgetReplace$1
1825
1986
  }, {
1826
1987
  command: 'FindWidget.replaceAll',
1827
1988
  key: Alt | CtrlCmd | Enter,
@@ -1910,12 +2071,10 @@ const getFindWidgetPosition = (editorX, editorY, editorWidth) => {
1910
2071
  };
1911
2072
  };
1912
2073
 
1913
- const getLines = async editorUid => {
1914
- const lines = await getLines$1(editorUid);
1915
- return lines;
1916
- };
1917
-
1918
2074
  const getSelectedText = (lines, selections) => {
2075
+ if (selections.length === 0) {
2076
+ return '';
2077
+ }
1919
2078
  const startRowIndex = selections[0];
1920
2079
  const startColumnIndex = selections[1];
1921
2080
  const endColumnIndex = selections[3];
@@ -2198,24 +2357,15 @@ const getFindWidgetButtonsEnabled = (matchCount, value) => {
2198
2357
  };
2199
2358
  };
2200
2359
 
2201
- const CheckBox = 'checkbox';
2202
- const Group = 'group';
2203
- const None = 'none';
2204
-
2205
- const Button = 1;
2206
- const Div = 4;
2207
- const Text = 12;
2208
- const TextArea = 62;
2209
-
2210
2360
  const mergeClassNames = (...classNames) => {
2211
2361
  return classNames.filter(Boolean).join(' ');
2212
2362
  };
2213
2363
 
2214
2364
  const text = data => {
2215
2365
  return {
2216
- type: Text,
2366
+ childCount: 0,
2217
2367
  text: data,
2218
- childCount: 0
2368
+ type: Text
2219
2369
  };
2220
2370
  };
2221
2371
 
@@ -2514,7 +2664,7 @@ const getEventListeners = (uid, widgetId) => {
2514
2664
  params: ['executeWidgetCommand', 'FindWidget', 'FindWidget.handleBlur', 0, widgetId]
2515
2665
  }, {
2516
2666
  name: HandleReplaceInput,
2517
- params: ['executeWidgetCommand', 'FindWidget', 'FindWidget.handleReplaceInput', 0, widgetId]
2667
+ params: ['executeWidgetCommand', 'FindWidget', 'FindWidget.handleReplaceInput', 0, widgetId, TargetValue]
2518
2668
  }, {
2519
2669
  name: HandleReplaceFocus,
2520
2670
  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.3.0",
4
4
  "description": "Web Worker for the find widget in Lvce Editor",
5
5
  "repository": {
6
6
  "type": "git",