mbeditor 0.4.0 → 0.4.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1a73400b5400313c994fbd40ebae5000cc776f07eebeb9790380a622a193ba73
4
- data.tar.gz: bdd3a1d8825e5ff23eef4fab724a33e3f48a0ab8ce3be316652c822f378ceffa
3
+ metadata.gz: d378fdda8fc933a3c3d1f5b1fc5eb1622ca4ebee91db435378dd069827f708e2
4
+ data.tar.gz: 2c58b8db51c1df5e7f746af61550dc2ccf62ba33a515cb374c79931aefed1beb
5
5
  SHA512:
6
- metadata.gz: af01460ee4ece9edf319dfd84da74e0a4d6016cdc96c73c8f7bd1a1a421de254b7abcf157ec81c182591e94dff9b44a437b274f48c02129c6099a67f89dc6af2
7
- data.tar.gz: 84d1af77774b4676e193db1800ba33401df62893491a134de11c84e6e3f17f26d2a7cc8b19c99bbdc05bbb9af018f079017ba1466680b29a00a065173399c572
6
+ metadata.gz: 43753349cc14d328dbb45c4d6413cb0a56b9ac49979e7cd0bafe884de0889ede353decdd8552da38d872c278fa3c70162a1730df5827f45d0f629aa0d44a1f6f
7
+ data.tar.gz: 3547eec20751a3c7279765e79429dda30398dfd6aa64c7b70d7e2f70cda6e8de6d9ae8c30a0c671a9750dcce1422d115d2b42f1a3d35b6679e81d795ba134c3e
data/CHANGELOG.md CHANGED
@@ -5,6 +5,19 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.4.2] - 2026-04-22
9
+
10
+ ### Fixed
11
+ - Updated sidebar search system test selectors to match the current UI loading and clear controls, resolving CI matrix failures in GitHub Actions.
12
+
13
+ ## [0.4.1] - 2026-04-22
14
+
15
+ ### Added
16
+ - Real-time file update integration via Action Cable to improve collaborative and live-refresh editing workflows.
17
+
18
+ ### Changed
19
+ - Search functionality enhancements for improved result handling and responsiveness.
20
+
8
21
  ## [0.4.0] - 2026-04-22
9
22
 
10
23
  ### Added
@@ -2,6 +2,7 @@
2
2
  //= require mbeditor/editor_store
3
3
  //= require mbeditor/file_icon
4
4
  //= require mbeditor/file_service
5
+ //= require mbeditor/websocket_service
5
6
  //= require mbeditor/git_service
6
7
  //= require mbeditor/search_service
7
8
  //= require mbeditor/tab_manager
@@ -68,6 +68,23 @@ var EditorPanel = function EditorPanel(_ref) {
68
68
  var editorReady = _useState10[0];
69
69
  var setEditorReady = _useState10[1];
70
70
 
71
+ var _useState11 = useState(false);
72
+ var _useState12 = _slicedToArray(_useState11, 2);
73
+ var methodsOpen = _useState12[0];
74
+ var setMethodsOpen = _useState12[1];
75
+
76
+ var _useState13 = useState([]);
77
+ var _useState14 = _slicedToArray(_useState13, 2);
78
+ var methodsList = _useState14[0];
79
+ var setMethodsList = _useState14[1];
80
+
81
+ var _useState15 = useState(null);
82
+ var _useState16 = _slicedToArray(_useState15, 2);
83
+ var methodsDropdownPos = _useState16[0];
84
+ var setMethodsDropdownPos = _useState16[1];
85
+
86
+ var methodsBtnRef = useRef(null);
87
+
71
88
  var onFormatRef = useRef(onFormat);
72
89
  onFormatRef.current = onFormat;
73
90
 
@@ -514,6 +531,46 @@ var EditorPanel = function EditorPanel(_ref) {
514
531
  editor.restoreViewState(tab.viewState);
515
532
  }
516
533
 
534
+ // Restore the find widget state from the previous editor (when persistFindState is on).
535
+ if (editorPrefs.persistFindState !== false && window.__mbeditorFindState && window.__mbeditorFindState.searchString) {
536
+ var _savedFind = window.__mbeditorFindState;
537
+ if (_savedFind.isRevealed) {
538
+ // Open the widget first (setTimeout so layout is ready), then re-apply the
539
+ // saved query — actions.find may seed from the selection and overwrite it.
540
+ setTimeout(function() {
541
+ try {
542
+ editor.trigger('', 'actions.find', null);
543
+ setTimeout(function() {
544
+ try {
545
+ var _fc0 = editor.getContribution('editor.contrib.findController');
546
+ if (_fc0 && _fc0.getState) {
547
+ _fc0.getState().change({
548
+ searchString: _savedFind.searchString,
549
+ isRegex: !!_savedFind.isRegex,
550
+ matchCase: !!_savedFind.matchCase,
551
+ wholeWord: !!_savedFind.wholeWord
552
+ }, false);
553
+ }
554
+ } catch (e) {}
555
+ }, 0);
556
+ } catch (e) {}
557
+ }, 0);
558
+ } else {
559
+ // Widget was closed — just seed the state silently so Ctrl+F pre-fills it.
560
+ try {
561
+ var _fc0 = editor.getContribution('editor.contrib.findController');
562
+ if (_fc0 && _fc0.getState) {
563
+ _fc0.getState().change({
564
+ searchString: _savedFind.searchString,
565
+ isRegex: !!_savedFind.isRegex,
566
+ matchCase: !!_savedFind.matchCase,
567
+ wholeWord: !!_savedFind.wholeWord
568
+ }, false);
569
+ }
570
+ } catch (e) {}
571
+ }
572
+ }
573
+
517
574
  monacoRef.current = editor;
518
575
  window.__mbeditorActiveEditor = editor;
519
576
  setEditorReady(true);
@@ -644,6 +701,26 @@ var EditorPanel = function EditorPanel(_ref) {
644
701
  _me.aviBase = aviBaseRef.current;
645
702
  _me.aviMax = aviMaxRef.current;
646
703
  }
704
+ // Save the current find widget state so it can be restored on next tab switch.
705
+ // Only overwrite the global state when there is an actual search string — this
706
+ // prevents a blank/fresh editor from clobbering the shared query.
707
+ if (editorPrefs.persistFindState !== false) {
708
+ try {
709
+ var _fc = editor.getContribution('editor.contrib.findController');
710
+ if (_fc && _fc.getState) {
711
+ var _fs = _fc.getState();
712
+ if (_fs.searchString) {
713
+ window.__mbeditorFindState = {
714
+ searchString: _fs.searchString,
715
+ isRegex: _fs.isRegex,
716
+ matchCase: _fs.matchCase,
717
+ wholeWord: _fs.wholeWord,
718
+ isRevealed: _fs.isRevealed
719
+ };
720
+ }
721
+ }
722
+ } catch (e) {}
723
+ }
647
724
  if (window.__mbeditorActiveEditor === editor) {
648
725
  window.__mbeditorActiveEditor = null;
649
726
  }
@@ -1172,6 +1249,9 @@ var EditorPanel = function EditorPanel(_ref) {
1172
1249
  var ext = parts.length > 1 ? parts.pop().toLowerCase() : '';
1173
1250
  var isImage = tab.isImage || IMAGE_EXTENSIONS.includes(ext);
1174
1251
  var isMarkdown = ['md', 'markdown'].includes(ext);
1252
+ var fileBaseName = (tab.path || '').split('/').pop().toLowerCase();
1253
+ var isRubyFile = ext === 'rb' || ext === 'ruby' || ext === 'gemspec' ||
1254
+ fileBaseName === 'gemfile' || fileBaseName === 'gemfile.lock' || fileBaseName === 'rakefile';
1175
1255
 
1176
1256
  useEffect(function () {
1177
1257
  if (isMarkdown && window.marked) {
@@ -1204,6 +1284,36 @@ var EditorPanel = function EditorPanel(_ref) {
1204
1284
  }
1205
1285
  }, [markdownContent, isMarkdown]);
1206
1286
 
1287
+ // Click-outside handler to close the methods dropdown
1288
+ useEffect(function() {
1289
+ if (!methodsOpen) return;
1290
+ function handleClickOutside(e) {
1291
+ var btn = methodsBtnRef.current;
1292
+ // Close if click is not on the button (the dropdown uses onMouseDown with preventDefault)
1293
+ if (btn && !btn.contains(e.target)) {
1294
+ setMethodsOpen(false);
1295
+ }
1296
+ }
1297
+ document.addEventListener('mousedown', handleClickOutside);
1298
+ return function() { document.removeEventListener('mousedown', handleClickOutside); };
1299
+ }, [methodsOpen]);
1300
+
1301
+ // Parse all method definitions from the current Monaco model
1302
+ function parseRubyMethods(model) {
1303
+ var methods = [];
1304
+ var lineCount = model.getLineCount();
1305
+ var DEF_RE = /^\s*def\s+(self\.)?([a-zA-Z_][a-zA-Z0-9_?!=]*)/;
1306
+ for (var i = 1; i <= lineCount; i++) {
1307
+ var line = model.getLineContent(i);
1308
+ var m = DEF_RE.exec(line);
1309
+ if (m) {
1310
+ var selfPrefix = m[1] ? 'self.' : '';
1311
+ methods.push({ line: i, name: selfPrefix + m[2] });
1312
+ }
1313
+ }
1314
+ return methods;
1315
+ }
1316
+
1207
1317
  if (tab.fileNotFound) {
1208
1318
  return React.createElement(
1209
1319
  'div',
@@ -1282,6 +1392,28 @@ var EditorPanel = function EditorPanel(_ref) {
1282
1392
  React.createElement('i', { className: 'fas fa-history', style: { marginRight: editorPrefs.toolbarIconOnly ? 0 : '5px', flexShrink: 0 } }),
1283
1393
  !editorPrefs.toolbarIconOnly && React.createElement('span', { className: 'ide-toolbar-label' }, 'History')
1284
1394
  ),
1395
+ isRubyFile && React.createElement(
1396
+ 'button',
1397
+ {
1398
+ ref: methodsBtnRef,
1399
+ className: 'ide-icon-btn' + (methodsOpen ? ' active' : ''),
1400
+ onClick: function() {
1401
+ var nextOpen = !methodsOpen;
1402
+ if (nextOpen) {
1403
+ var model = monacoRef.current && monacoRef.current.getModel();
1404
+ setMethodsList(model ? parseRubyMethods(model) : []);
1405
+ if (methodsBtnRef.current) {
1406
+ var rect = methodsBtnRef.current.getBoundingClientRect();
1407
+ setMethodsDropdownPos({ top: rect.bottom + 4, right: window.innerWidth - rect.right });
1408
+ }
1409
+ }
1410
+ setMethodsOpen(nextOpen);
1411
+ },
1412
+ title: 'Jump to Method'
1413
+ },
1414
+ React.createElement('i', { className: 'fas fa-list-ul', style: { marginRight: editorPrefs.toolbarIconOnly ? 0 : '5px', flexShrink: 0 } }),
1415
+ !editorPrefs.toolbarIconOnly && React.createElement('span', { className: 'ide-toolbar-label' }, 'Methods')
1416
+ ),
1285
1417
  gitAvailable && React.createElement(
1286
1418
  'button',
1287
1419
  {
@@ -1306,6 +1438,35 @@ var EditorPanel = function EditorPanel(_ref) {
1306
1438
  )
1307
1439
  ),
1308
1440
  React.createElement('div', { ref: editorRef, className: 'monaco-container', style: { flex: 1, minHeight: 0 } }),
1441
+ methodsOpen && methodsDropdownPos && React.createElement(
1442
+ 'div',
1443
+ {
1444
+ className: 'ide-methods-dropdown',
1445
+ style: { position: 'fixed', top: methodsDropdownPos.top + 'px', right: methodsDropdownPos.right + 'px', left: 'auto', zIndex: 9900 }
1446
+ },
1447
+ methodsList.length === 0
1448
+ ? React.createElement('div', { className: 'ide-methods-dropdown-empty' }, 'No methods found')
1449
+ : methodsList.map(function(m) {
1450
+ return React.createElement(
1451
+ 'div',
1452
+ {
1453
+ key: m.line,
1454
+ className: 'ide-methods-dropdown-item',
1455
+ onMouseDown: function(e) {
1456
+ e.preventDefault();
1457
+ setMethodsOpen(false);
1458
+ if (monacoRef.current) {
1459
+ monacoRef.current.revealLineInCenter(m.line);
1460
+ monacoRef.current.setPosition({ lineNumber: m.line, column: 1 });
1461
+ monacoRef.current.focus();
1462
+ }
1463
+ }
1464
+ },
1465
+ React.createElement('span', { className: 'ide-methods-dropdown-line' }, m.line),
1466
+ m.name
1467
+ );
1468
+ })
1469
+ ),
1309
1470
  React.createElement('div', { ref: vimStatusRef, className: 'vim-statusbar', style: { display: editorPrefs.vimMode ? 'flex' : 'none', height: '22px', alignItems: 'center', padding: '0 10px', fontFamily: "'JetBrains Mono', 'Fira Code', Consolas, monospace", fontSize: '12px', background: 'var(--ide-statusbar-bg, #1e1e2e)', color: 'var(--ide-statusbar-fg, #9cdcfe)', borderTop: '1px solid var(--ide-border, #3e3e3e)', flexShrink: 0, userSelect: 'none', letterSpacing: '0.02em' } })
1310
1471
  );
1311
1472
  };
@@ -61,7 +61,8 @@ var DEFAULT_EDITOR_PREFS = {
61
61
  vimMode: false,
62
62
  fileTreeTypeahead: true,
63
63
  quickOpenShowFolders: false,
64
- tabDisplayMode: 'scroll'
64
+ tabDisplayMode: 'scroll',
65
+ persistFindState: true
65
66
  };
66
67
 
67
68
  var SidebarActionButton = function SidebarActionButton(_ref) {
@@ -196,7 +197,25 @@ var MbeditorApp = function MbeditorApp() {
196
197
  var searchOffsetRef = useRef(0);
197
198
  var searchLoadingMoreRef = useRef(false);
198
199
 
200
+ var _useStateRx = useState(false);
201
+ var _useStateRx2 = _slicedToArray(_useStateRx, 2);
202
+ var searchUseRegex = _useStateRx2[0];
203
+ var setSearchUseRegex = _useStateRx2[1];
204
+
205
+ var _useStateMC = useState(false);
206
+ var _useStateMC2 = _slicedToArray(_useStateMC, 2);
207
+ var searchMatchCase = _useStateMC2[0];
208
+ var setSearchMatchCase = _useStateMC2[1];
209
+
210
+ var _useStateWW = useState(false);
211
+ var _useStateWW2 = _slicedToArray(_useStateWW, 2);
212
+ var searchWholeWord = _useStateWW2[0];
213
+ var setSearchWholeWord = _useStateWW2[1];
214
+
199
215
  var searchQueryRef = useRef('');
216
+ var searchUseRegexRef = useRef(false);
217
+ var searchMatchCaseRef = useRef(false);
218
+ var searchWholeWordRef = useRef(false);
200
219
  var searchResultsContainerRef = useRef(null);
201
220
 
202
221
  var _useState8 = useState("explorer");
@@ -665,6 +684,9 @@ var MbeditorApp = function MbeditorApp() {
665
684
  if (workspace && typeof workspace.testAvailable === 'boolean') {
666
685
  setTestAvailable(workspace.testAvailable);
667
686
  }
687
+ if (workspace && typeof workspace.actionCableEnabled === 'boolean') {
688
+ WebSocketService.connect(workspace.actionCableEnabled);
689
+ }
668
690
  });
669
691
 
670
692
  // Helper: load tab content for a set of panes and restore them into EditorStore
@@ -990,11 +1012,33 @@ var MbeditorApp = function MbeditorApp() {
990
1012
  if (offers.length > 0) setDraftRestoreOffer(offers);
991
1013
  }, [serverOnline]);
992
1014
 
1015
+ // WebSocket push — when the server broadcasts files_changed, refresh the tree
1016
+ // and git status immediately (same work as the 10s poll below does).
1017
+ useEffect(function () {
1018
+ function handleFilesChanged() {
1019
+ if (document.hidden) return;
1020
+ GitService.fetchStatus()["catch"](function () {});
1021
+ FileService.getTree().then(function (data) {
1022
+ var newData = data || [];
1023
+ setTreeData(function (prevData) {
1024
+ if (JSON.stringify(newData) === JSON.stringify(prevData)) return prevData;
1025
+ SearchService.buildIndex(newData);
1026
+ return newData;
1027
+ });
1028
+ })["catch"](function () {});
1029
+ }
1030
+ WebSocketService.onFilesChanged(handleFilesChanged);
1031
+ return function () { WebSocketService.offFilesChanged(handleFilesChanged); };
1032
+ }, []);
1033
+
993
1034
  // Auto-refresh the file tree every 10s to pick up external changes (new files, deletions, etc.)
1035
+ // When an ActionCable WebSocket is connected this acts only as a safety-net fallback —
1036
+ // the WebSocket push above handles immediate invalidation after mbeditor mutations.
994
1037
  // Uses functional setTreeData to skip the re-render when nothing has changed.
995
1038
  useEffect(function () {
996
1039
  var intervalId = setInterval(function () {
997
1040
  if (document.hidden) return;
1041
+ if (WebSocketService.isConnected()) return; // WebSocket is handling refreshes
998
1042
  // Refresh tree and check for git branch changes (to trigger per-branch tab state swap)
999
1043
  GitService.fetchStatus()["catch"](function () {});
1000
1044
  FileService.getTree().then(function (data) {
@@ -1198,6 +1242,12 @@ var MbeditorApp = function MbeditorApp() {
1198
1242
  var activeFileCommit = _useState32[0];
1199
1243
  var setActiveFileCommit = _useState32[1];
1200
1244
 
1245
+ // EOL indicator — tracks current line-ending style of the active file
1246
+ var _useState31e = useState(null);
1247
+ var _useState31e2 = _slicedToArray(_useState31e, 2);
1248
+ var activeEOL = _useState31e2[0];
1249
+ var setActiveEOL = _useState31e2[1];
1250
+
1201
1251
  useEffect(function () {
1202
1252
  if (!gitAvailable || !activeTab || activeTab.isDiff || activeTab.isCombinedDiff || activeTab.isCommitGraph || !activeTab.path || activeTab.path.indexOf('diff://') === 0 || activeTab.path.indexOf('combined-diff://') === 0) {
1203
1253
  setActiveFileCommit(null);
@@ -1216,6 +1266,22 @@ var MbeditorApp = function MbeditorApp() {
1216
1266
  });
1217
1267
  }, [activeTab ? activeTab.id : null, gitAvailable]);
1218
1268
 
1269
+ // Update EOL indicator whenever active tab or its content changes
1270
+ useEffect(function () {
1271
+ if (!activeTab || typeof activeTab.content !== 'string' ||
1272
+ activeTab.isDiff || activeTab.isCombinedDiff || activeTab.isCommitGraph || activeTab.isPreview) {
1273
+ setActiveEOL(null);
1274
+ return;
1275
+ }
1276
+ if (activeTab.content.indexOf('\r\n') !== -1) {
1277
+ setActiveEOL('CRLF');
1278
+ } else if (activeTab.content.indexOf('\r') !== -1) {
1279
+ setActiveEOL('CR');
1280
+ } else {
1281
+ setActiveEOL('LF');
1282
+ }
1283
+ }, [activeTab ? activeTab.id : null, activeTab ? activeTab.content : null]);
1284
+
1219
1285
  useEffect(function () {
1220
1286
  if (!activeTab || typeof activeTab.content !== 'string') return;
1221
1287
  if (activeTab.isDiff || activeTab.isCombinedDiff || activeTab.isCommitGraph) return;
@@ -1381,6 +1447,21 @@ var MbeditorApp = function MbeditorApp() {
1381
1447
  clearDragState();
1382
1448
  };
1383
1449
 
1450
+ var handleChangeEOL = function handleChangeEOL(newEOL) {
1451
+ var ed = window.__mbeditorActiveEditor;
1452
+ if (!ed || !window.monaco) return;
1453
+ var model = ed.getModel();
1454
+ if (!model || !activeTab || !focusedPane) return;
1455
+ var seq = newEOL === 'CRLF'
1456
+ ? window.monaco.editor.EndOfLineSequence.CRLF
1457
+ : window.monaco.editor.EndOfLineSequence.LF;
1458
+ model.setEOL(seq);
1459
+ var newContent = model.getValue();
1460
+ TabManager.markDirty(focusedPane.id, activeTab.path, newContent);
1461
+ setActiveEOL(newEOL);
1462
+ EditorStore.setStatus('Line endings changed to ' + newEOL, 'info');
1463
+ };
1464
+
1384
1465
  var handleFormat = function handleFormat() {
1385
1466
  if (!activeTab) return;
1386
1467
 
@@ -1578,7 +1659,7 @@ var MbeditorApp = function MbeditorApp() {
1578
1659
  searchQueryRef.current = q;
1579
1660
  EditorStore.setState({ searchResults: [], searchHasMore: false });
1580
1661
  EditorStore.setStatus("Searching project...", "info");
1581
- SearchService.projectSearch(q, 0, SearchService.PAGE_SIZE).then(function (res) {
1662
+ SearchService.projectSearch(q, 0, SearchService.PAGE_SIZE, { regex: searchUseRegexRef.current, matchCase: searchMatchCaseRef.current, wholeWord: searchWholeWordRef.current }).then(function (res) {
1582
1663
  if (searchRequestIdRef.current !== requestId) return;
1583
1664
  var hasMore = !!(res && res.hasMore);
1584
1665
  setSearchHasMore(hasMore); searchHasMoreRef.current = hasMore;
@@ -1596,7 +1677,7 @@ var MbeditorApp = function MbeditorApp() {
1596
1677
  if (!q || searchLoadingMoreRef.current || !searchHasMoreRef.current) return;
1597
1678
  searchLoadingMoreRef.current = true;
1598
1679
  var offset = searchOffsetRef.current;
1599
- SearchService.projectSearch(q, offset, SearchService.PAGE_SIZE).then(function(res) {
1680
+ SearchService.projectSearch(q, offset, SearchService.PAGE_SIZE, { regex: searchUseRegexRef.current, matchCase: searchMatchCaseRef.current, wholeWord: searchWholeWordRef.current }).then(function(res) {
1600
1681
  if (searchQueryRef.current !== q) { searchLoadingMoreRef.current = false; return; }
1601
1682
  var hasMore = !!(res && res.hasMore);
1602
1683
  searchHasMoreRef.current = hasMore;
@@ -1615,6 +1696,33 @@ var MbeditorApp = function MbeditorApp() {
1615
1696
  _debouncedSearch(val);
1616
1697
  };
1617
1698
 
1699
+ var handleSearchRegexToggle = function handleSearchRegexToggle() {
1700
+ var next = !searchUseRegexRef.current;
1701
+ searchUseRegexRef.current = next;
1702
+ setSearchUseRegex(next);
1703
+ if (searchQueryRef.current) {
1704
+ _debouncedSearch(searchQueryRef.current);
1705
+ }
1706
+ };
1707
+
1708
+ var handleSearchMatchCaseToggle = function handleSearchMatchCaseToggle() {
1709
+ var next = !searchMatchCaseRef.current;
1710
+ searchMatchCaseRef.current = next;
1711
+ setSearchMatchCase(next);
1712
+ if (searchQueryRef.current) {
1713
+ _debouncedSearch(searchQueryRef.current);
1714
+ }
1715
+ };
1716
+
1717
+ var handleSearchWholeWordToggle = function handleSearchWholeWordToggle() {
1718
+ var next = !searchWholeWordRef.current;
1719
+ searchWholeWordRef.current = next;
1720
+ setSearchWholeWord(next);
1721
+ if (searchQueryRef.current) {
1722
+ _debouncedSearch(searchQueryRef.current);
1723
+ }
1724
+ };
1725
+
1618
1726
  var clearSearch = function clearSearch() {
1619
1727
  searchRequestIdRef.current += 1;
1620
1728
  if (_debouncedSearch.cancel) _debouncedSearch.cancel();
@@ -2564,32 +2672,57 @@ var MbeditorApp = function MbeditorApp() {
2564
2672
  { className: "search-panel" },
2565
2673
  React.createElement(
2566
2674
  "div",
2567
- { className: "search-input-wrap" },
2675
+ { className: "search-input-shell" },
2676
+ React.createElement("input", {
2677
+ className: "search-input",
2678
+ placeholder: "Find in files…",
2679
+ value: searchQuery,
2680
+ onChange: handleSearchChange
2681
+ }),
2568
2682
  React.createElement(
2569
2683
  "div",
2570
- { className: "search-input-shell" },
2571
- React.createElement("input", {
2572
- className: "search-input",
2573
- placeholder: "Find in files…",
2574
- value: searchQuery,
2575
- onChange: handleSearchChange
2576
- }),
2684
+ { className: "search-input-adornments" },
2685
+ React.createElement(
2686
+ "button",
2687
+ {
2688
+ type: "button",
2689
+ className: "search-adornment-btn" + (searchMatchCase ? " active" : ""),
2690
+ onClick: handleSearchMatchCaseToggle,
2691
+ title: "Match Case"
2692
+ },
2693
+ React.createElement("i", { className: "codicon codicon-case-sensitive" })
2694
+ ),
2695
+ React.createElement(
2696
+ "button",
2697
+ {
2698
+ type: "button",
2699
+ className: "search-adornment-btn" + (searchWholeWord ? " active" : ""),
2700
+ onClick: handleSearchWholeWordToggle,
2701
+ title: "Match Whole Word"
2702
+ },
2703
+ React.createElement("i", { className: "codicon codicon-whole-word" })
2704
+ ),
2705
+ React.createElement(
2706
+ "button",
2707
+ {
2708
+ type: "button",
2709
+ className: "search-adornment-btn" + (searchUseRegex ? " active" : ""),
2710
+ onClick: handleSearchRegexToggle,
2711
+ title: "Use Regular Expression"
2712
+ },
2713
+ React.createElement("i", { className: "codicon codicon-regex" })
2714
+ ),
2577
2715
  searchQuery && React.createElement(
2578
2716
  "button",
2579
2717
  {
2580
2718
  type: "button",
2581
- className: "search-clear-btn",
2719
+ className: "search-adornment-btn search-adornment-clear",
2582
2720
  onClick: clearSearch,
2583
2721
  title: "Clear search",
2584
2722
  "aria-label": "Clear search"
2585
2723
  },
2586
2724
  React.createElement("i", { className: "fas fa-times" })
2587
2725
  )
2588
- ),
2589
- React.createElement(
2590
- "button",
2591
- { type: "submit", className: "search-btn", disabled: searchLoading, title: searchLoading ? "Searching..." : "Search" },
2592
- React.createElement("i", { className: searchLoading ? "fas fa-spinner fa-spin" : "fas fa-search" })
2593
2726
  )
2594
2727
  ),
2595
2728
  (function() {
@@ -2608,38 +2741,47 @@ var MbeditorApp = function MbeditorApp() {
2608
2741
  ? (total + (searchHasMore ? '+' : '') + " result" + (total !== 1 ? "s" : ""))
2609
2742
  : "No results"
2610
2743
  ),
2611
- hasAny && React.createElement(
2744
+ React.createElement(
2612
2745
  "div",
2613
- {
2614
- className: "search-results",
2615
- ref: searchResultsContainerRef,
2616
- onScroll: handleSearchResultsScroll
2617
- },
2618
- allResults.map(function(res, i) {
2619
- var fileName = res.file.split('/').pop();
2620
- return React.createElement(
2621
- "div",
2622
- {
2623
- key: i,
2624
- className: "search-result-item",
2625
- onClick: (function(r) { return function() { handleSelectFile(r.file, r.file.split('/').pop(), r.line); }; })(res)
2626
- },
2627
- React.createElement("i", { className: (window.getFileIcon ? window.getFileIcon(fileName) : 'far fa-file-code') + " search-result-icon" }),
2628
- React.createElement(
2629
- "div", { className: "search-result-body" },
2746
+ { className: "search-results-area" },
2747
+ hasAny && React.createElement(
2748
+ "div",
2749
+ {
2750
+ className: "search-results" + (searchLoading ? " search-results-blurred" : ""),
2751
+ ref: searchResultsContainerRef,
2752
+ onScroll: handleSearchResultsScroll
2753
+ },
2754
+ allResults.map(function(res, i) {
2755
+ var fileName = res.file.split('/').pop();
2756
+ return React.createElement(
2757
+ "div",
2758
+ {
2759
+ key: i,
2760
+ className: "search-result-item",
2761
+ onClick: (function(r) { return function() { handleSelectFile(r.file, r.file.split('/').pop(), r.line); }; })(res)
2762
+ },
2763
+ React.createElement("i", { className: (window.getFileIcon ? window.getFileIcon(fileName) : 'far fa-file-code') + " search-result-icon" }),
2630
2764
  React.createElement(
2631
- "div", { className: "search-result-file" },
2632
- fileName,
2633
- React.createElement("span", { className: "search-result-line-num" }, " ", res.file, ":", res.line)
2634
- ),
2635
- React.createElement("div", { className: "search-result-text" }, res.text)
2636
- )
2637
- );
2638
- }),
2639
- searchHasMore && React.createElement(
2640
- "div", { className: "search-loading-more" },
2641
- React.createElement("i", { className: "fas fa-spinner fa-spin" }),
2642
- " Loading more\u2026"
2765
+ "div", { className: "search-result-body" },
2766
+ React.createElement(
2767
+ "div", { className: "search-result-file" },
2768
+ fileName,
2769
+ React.createElement("span", { className: "search-result-line-num" }, " ", res.file, ":", res.line)
2770
+ ),
2771
+ React.createElement("div", { className: "search-result-text" }, res.text)
2772
+ )
2773
+ );
2774
+ }),
2775
+ searchHasMore && React.createElement(
2776
+ "div", { className: "search-loading-more" },
2777
+ React.createElement("i", { className: "fas fa-spinner fa-spin" }),
2778
+ " Loading more\u2026"
2779
+ )
2780
+ ),
2781
+ searchLoading && React.createElement(
2782
+ "div",
2783
+ { className: "search-loading-overlay" },
2784
+ React.createElement("div", { className: "search-loading-spinner" })
2643
2785
  )
2644
2786
  )
2645
2787
  );
@@ -3228,6 +3370,17 @@ var MbeditorApp = function MbeditorApp() {
3228
3370
  })
3229
3371
  ),
3230
3372
 
3373
+ React.createElement(
3374
+ 'label', { className: 'ide-settings-row ide-settings-row-check' },
3375
+ React.createElement('span', { className: 'ide-settings-label' }, 'Persist find state across files'),
3376
+ React.createElement('input', {
3377
+ type: 'checkbox',
3378
+ className: 'ide-settings-checkbox',
3379
+ checked: editorPrefs.persistFindState !== false,
3380
+ onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { persistFindState: v }); }); }
3381
+ })
3382
+ ),
3383
+
3231
3384
  /* ── RuboCop ─────────────────────────────────── */
3232
3385
  React.createElement('div', { className: 'ide-settings-section-header' }, 'RuboCop'),
3233
3386
  React.createElement(
@@ -3538,6 +3691,16 @@ var MbeditorApp = function MbeditorApp() {
3538
3691
  { className: "statusbar-msg " + state.statusMessage.kind },
3539
3692
  state.statusMessage.text
3540
3693
  ),
3694
+ activeEOL && React.createElement(
3695
+ "button",
3696
+ {
3697
+ type: "button",
3698
+ className: "statusbar-btn statusbar-eol-btn",
3699
+ title: "Line endings: " + activeEOL + " — click to change",
3700
+ onClick: function() { handleChangeEOL(activeEOL === 'CRLF' ? 'LF' : 'CRLF'); }
3701
+ },
3702
+ activeEOL
3703
+ ),
3541
3704
  React.createElement(
3542
3705
  "div",
3543
3706
  { className: "statusbar-version" },
@@ -132,10 +132,12 @@ var QuickOpenDialog = function QuickOpenDialog(_ref) {
132
132
  // JS sort is stable in modern engines so MiniSearch relevance score order is the tiebreaker
133
133
  // when match relevance is equal.
134
134
  filtered.sort(function(a, b) {
135
+ var aRelevance = getMatchRelevance(a, query);
136
+ var bRelevance = getMatchRelevance(b, query);
137
+ if (aRelevance !== bRelevance) return aRelevance - bRelevance;
135
138
  var aPriority = getFilePriority(a.path) + (a.type === 'dir' ? 100 : 0);
136
139
  var bPriority = getFilePriority(b.path) + (b.type === 'dir' ? 100 : 0);
137
- if (aPriority !== bPriority) return aPriority - bPriority;
138
- return getMatchRelevance(a, query) - getMatchRelevance(b, query);
140
+ return aPriority - bPriority;
139
141
  });
140
142
  setResults(filtered.slice(0, 200));
141
143
  setSelectedIndex(0);