mbeditor 0.3.8 → 0.3.9
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 +4 -4
- data/CHANGELOG.md +10 -0
- data/app/assets/javascripts/mbeditor/application_iife_head.js +7 -0
- data/app/assets/javascripts/mbeditor/components/CodeReviewPanel.js +1 -1
- data/app/assets/javascripts/mbeditor/components/EditorPanel.js +52 -11
- data/app/assets/javascripts/mbeditor/components/GitPanel.js +14 -4
- data/app/assets/javascripts/mbeditor/components/MbeditorApp.js +316 -133
- data/app/assets/javascripts/mbeditor/components/QuickOpenDialog.js +25 -1
- data/app/assets/javascripts/mbeditor/editor_store.js +10 -2
- data/app/assets/javascripts/mbeditor/file_service.js +23 -23
- data/app/assets/javascripts/mbeditor/git_service.js +7 -11
- data/app/assets/javascripts/mbeditor/search_service.js +45 -12
- data/app/assets/javascripts/mbeditor/tab_manager.js +3 -3
- data/app/assets/stylesheets/mbeditor/editor.css +12 -5
- data/app/controllers/mbeditor/editors_controller.rb +134 -128
- data/app/controllers/mbeditor/git_controller.rb +5 -40
- data/app/services/mbeditor/git_blame_service.rb +6 -0
- data/app/services/mbeditor/git_commit_graph_service.rb +2 -0
- data/app/services/mbeditor/git_service.rb +97 -28
- data/app/services/mbeditor/redmine_service.rb +7 -0
- data/app/services/mbeditor/ruby_definition_service.rb +23 -2
- data/lib/mbeditor/configuration.rb +7 -1
- data/lib/mbeditor/engine.rb +27 -0
- data/lib/mbeditor/version.rb +3 -1
- data/lib/mbeditor.rb +2 -0
- metadata +2 -2
|
@@ -24,6 +24,8 @@ var DEFAULT_EDITOR_PREFS = {
|
|
|
24
24
|
theme: 'vs-dark',
|
|
25
25
|
fontSize: 13,
|
|
26
26
|
fontFamily: "'JetBrains Mono', 'Fira Code', Consolas, 'Courier New', monospace",
|
|
27
|
+
lineHeight: 0,
|
|
28
|
+
letterSpacing: 0,
|
|
27
29
|
tabSize: 4,
|
|
28
30
|
insertSpaces: false,
|
|
29
31
|
wordWrap: 'off',
|
|
@@ -32,6 +34,20 @@ var DEFAULT_EDITOR_PREFS = {
|
|
|
32
34
|
scrollBeyondLastLine: false,
|
|
33
35
|
minimap: false,
|
|
34
36
|
bracketPairColorization: true,
|
|
37
|
+
renderLineHighlight: 'none',
|
|
38
|
+
cursorStyle: 'line',
|
|
39
|
+
cursorBlinking: 'blink',
|
|
40
|
+
folding: true,
|
|
41
|
+
smoothScrolling: false,
|
|
42
|
+
mouseWheelZoom: false,
|
|
43
|
+
autoClosingBrackets: 'always',
|
|
44
|
+
autoClosingQuotes: 'always',
|
|
45
|
+
autoIndent: 'full',
|
|
46
|
+
formatOnPaste: true,
|
|
47
|
+
formatOnType: true,
|
|
48
|
+
quickSuggestions: true,
|
|
49
|
+
wordBasedSuggestions: 'matchingDocuments',
|
|
50
|
+
acceptSuggestionOnEnter: 'on',
|
|
35
51
|
autoRevealInExplorer: true,
|
|
36
52
|
toolbarIconOnly: false,
|
|
37
53
|
rubocopLintEnabled: true,
|
|
@@ -170,20 +186,17 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
170
186
|
var searchHasMore = _useState33h2[0];
|
|
171
187
|
var setSearchHasMore = _useState33h2[1];
|
|
172
188
|
|
|
173
|
-
var
|
|
174
|
-
var
|
|
175
|
-
var
|
|
176
|
-
var
|
|
189
|
+
var _useState33tc = useState(0);
|
|
190
|
+
var _useState33tc2 = _slicedToArray(_useState33tc, 2);
|
|
191
|
+
var searchTotalCount = _useState33tc2[0];
|
|
192
|
+
var setSearchTotalCount = _useState33tc2[1];
|
|
177
193
|
|
|
178
|
-
var
|
|
179
|
-
var
|
|
180
|
-
var
|
|
181
|
-
var searchVirtStartRef = useRef(0); // first visible item index (for virtual list)
|
|
194
|
+
var searchHasMoreRef = useRef(false);
|
|
195
|
+
var searchOffsetRef = useRef(0);
|
|
196
|
+
var searchLoadingMoreRef = useRef(false);
|
|
182
197
|
|
|
183
|
-
var
|
|
184
|
-
var
|
|
185
|
-
var searchVirtStart = _useState33j2[0];
|
|
186
|
-
var setSearchVirtStart = _useState33j2[1];
|
|
198
|
+
var searchQueryRef = useRef('');
|
|
199
|
+
var searchResultsContainerRef = useRef(null);
|
|
187
200
|
|
|
188
201
|
var _useState8 = useState("explorer");
|
|
189
202
|
|
|
@@ -1481,40 +1494,61 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1481
1494
|
var onFormatRef = useRef(handleFormat);
|
|
1482
1495
|
onFormatRef.current = handleFormat;
|
|
1483
1496
|
|
|
1497
|
+
// Eagerly load all remaining pages sequentially in the background.
|
|
1498
|
+
// Self-chains via .then() so only one request is in-flight at a time.
|
|
1499
|
+
// Uses only refs so it's safe to call from async callbacks without
|
|
1500
|
+
// worrying about stale closure state.
|
|
1484
1501
|
var _debouncedSearch = useRef(window._.debounce(function (q) {
|
|
1485
1502
|
if (!q.trim()) {
|
|
1486
1503
|
searchRequestIdRef.current += 1;
|
|
1487
1504
|
setSearchLoading(false);
|
|
1488
|
-
setSearchHasMore(false);
|
|
1505
|
+
setSearchHasMore(false); searchHasMoreRef.current = false;
|
|
1506
|
+
setSearchTotalCount(0);
|
|
1489
1507
|
searchOffsetRef.current = 0;
|
|
1508
|
+
searchLoadingMoreRef.current = false;
|
|
1490
1509
|
searchQueryRef.current = '';
|
|
1491
1510
|
EditorStore.setState({ searchResults: [], searchHasMore: false });
|
|
1492
1511
|
return;
|
|
1493
1512
|
}
|
|
1494
1513
|
var requestId = ++searchRequestIdRef.current;
|
|
1495
1514
|
setSearchLoading(true);
|
|
1496
|
-
setSearchHasMore(false);
|
|
1515
|
+
setSearchHasMore(false); searchHasMoreRef.current = false;
|
|
1516
|
+
setSearchTotalCount(0);
|
|
1497
1517
|
searchOffsetRef.current = 0;
|
|
1518
|
+
searchLoadingMoreRef.current = false;
|
|
1498
1519
|
searchQueryRef.current = q;
|
|
1499
|
-
setSearchVirtStart(0);
|
|
1500
|
-
searchVirtStartRef.current = 0;
|
|
1501
1520
|
EditorStore.setState({ searchResults: [], searchHasMore: false });
|
|
1502
1521
|
EditorStore.setStatus("Searching project...", "info");
|
|
1503
1522
|
SearchService.projectSearch(q, 0, SearchService.PAGE_SIZE).then(function (res) {
|
|
1504
|
-
if (searchRequestIdRef.current
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1523
|
+
if (searchRequestIdRef.current !== requestId) return;
|
|
1524
|
+
var hasMore = !!(res && res.hasMore);
|
|
1525
|
+
setSearchHasMore(hasMore); searchHasMoreRef.current = hasMore;
|
|
1526
|
+
searchOffsetRef.current = SearchService.PAGE_SIZE;
|
|
1527
|
+
if (res && res.totalCount != null) setSearchTotalCount(res.totalCount);
|
|
1528
|
+
var total = (res && res.totalCount != null) ? res.totalCount : (res && res.results ? res.results.length : 0);
|
|
1529
|
+
EditorStore.setStatus("Found " + total + (hasMore ? '+' : '') + " result" + (total !== 1 ? "s" : ""), "success");
|
|
1511
1530
|
}).finally(function () {
|
|
1512
|
-
if (searchRequestIdRef.current === requestId)
|
|
1513
|
-
setSearchLoading(false);
|
|
1514
|
-
}
|
|
1531
|
+
if (searchRequestIdRef.current === requestId) setSearchLoading(false);
|
|
1515
1532
|
});
|
|
1516
1533
|
}, 400)).current;
|
|
1517
1534
|
|
|
1535
|
+
var loadMoreSearchResults = function loadMoreSearchResults() {
|
|
1536
|
+
var q = searchQueryRef.current;
|
|
1537
|
+
if (!q || searchLoadingMoreRef.current || !searchHasMoreRef.current) return;
|
|
1538
|
+
searchLoadingMoreRef.current = true;
|
|
1539
|
+
var offset = searchOffsetRef.current;
|
|
1540
|
+
SearchService.projectSearch(q, offset, SearchService.PAGE_SIZE).then(function(res) {
|
|
1541
|
+
if (searchQueryRef.current !== q) { searchLoadingMoreRef.current = false; return; }
|
|
1542
|
+
var hasMore = !!(res && res.hasMore);
|
|
1543
|
+
searchHasMoreRef.current = hasMore;
|
|
1544
|
+
setSearchHasMore(hasMore);
|
|
1545
|
+
searchOffsetRef.current = offset + SearchService.PAGE_SIZE;
|
|
1546
|
+
searchLoadingMoreRef.current = false;
|
|
1547
|
+
}).catch(function() {
|
|
1548
|
+
searchLoadingMoreRef.current = false;
|
|
1549
|
+
});
|
|
1550
|
+
};
|
|
1551
|
+
|
|
1518
1552
|
var handleSearchChange = function handleSearchChange(e) {
|
|
1519
1553
|
var val = e.target.value;
|
|
1520
1554
|
if (!val) { clearSearch(); return; }
|
|
@@ -1527,12 +1561,11 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1527
1561
|
if (_debouncedSearch.cancel) _debouncedSearch.cancel();
|
|
1528
1562
|
setSearchQuery("");
|
|
1529
1563
|
setSearchLoading(false);
|
|
1530
|
-
setSearchHasMore(false);
|
|
1531
|
-
|
|
1564
|
+
setSearchHasMore(false); searchHasMoreRef.current = false;
|
|
1565
|
+
setSearchTotalCount(0);
|
|
1532
1566
|
searchOffsetRef.current = 0;
|
|
1567
|
+
searchLoadingMoreRef.current = false;
|
|
1533
1568
|
searchQueryRef.current = '';
|
|
1534
|
-
setSearchVirtStart(0);
|
|
1535
|
-
searchVirtStartRef.current = 0;
|
|
1536
1569
|
EditorStore.setState({ searchResults: [], searchHasMore: false });
|
|
1537
1570
|
};
|
|
1538
1571
|
|
|
@@ -1541,35 +1574,11 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1541
1574
|
_debouncedSearch(searchQuery);
|
|
1542
1575
|
};
|
|
1543
1576
|
|
|
1544
|
-
|
|
1545
|
-
var q = searchQueryRef.current;
|
|
1546
|
-
if (!q || searchLoadingMore || !searchHasMore) return;
|
|
1547
|
-
var offset = searchOffsetRef.current;
|
|
1548
|
-
setSearchLoadingMore(true);
|
|
1549
|
-
SearchService.projectSearch(q, offset, SearchService.PAGE_SIZE).then(function (res) {
|
|
1550
|
-
var hasMore = !!(res && res.hasMore);
|
|
1551
|
-
setSearchHasMore(hasMore);
|
|
1552
|
-
searchOffsetRef.current = offset + SearchService.PAGE_SIZE;
|
|
1553
|
-
}).finally(function () {
|
|
1554
|
-
setSearchLoadingMore(false);
|
|
1555
|
-
});
|
|
1556
|
-
};
|
|
1557
|
-
|
|
1558
|
-
// Scroll handler for the virtualized search results list.
|
|
1559
|
-
var SEARCH_ITEM_H = 40;
|
|
1560
|
-
var SEARCH_OVERSCAN = 8;
|
|
1577
|
+
// Load more results when the user scrolls near the bottom of the list.
|
|
1561
1578
|
var handleSearchResultsScroll = function handleSearchResultsScroll(e) {
|
|
1562
1579
|
var el = e.currentTarget;
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
searchVirtStartRef.current = newStart;
|
|
1566
|
-
setSearchVirtStart(newStart);
|
|
1567
|
-
}
|
|
1568
|
-
// Trigger load-more when within 200px of the bottom
|
|
1569
|
-
if (!searchLoadingMore && searchHasMore) {
|
|
1570
|
-
if (el.scrollHeight - el.scrollTop - el.clientHeight < 200) {
|
|
1571
|
-
loadMoreSearchResults();
|
|
1572
|
-
}
|
|
1580
|
+
if (el.scrollHeight - el.scrollTop - el.clientHeight < 200) {
|
|
1581
|
+
loadMoreSearchResults();
|
|
1573
1582
|
}
|
|
1574
1583
|
};
|
|
1575
1584
|
|
|
@@ -2523,88 +2532,58 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2523
2532
|
React.createElement("i", { className: searchLoading ? "fas fa-spinner fa-spin" : "fas fa-search" })
|
|
2524
2533
|
)
|
|
2525
2534
|
),
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
onScroll: handleSearchResultsScroll
|
|
2532
|
-
},
|
|
2533
|
-
(function() {
|
|
2534
|
-
var allResults = state.searchResults || [];
|
|
2535
|
-
var totalCount = allResults.length;
|
|
2536
|
-
|
|
2537
|
-
if (searchQuery && totalCount === 0 && !searchLoading) {
|
|
2538
|
-
return React.createElement(
|
|
2539
|
-
"div", { className: "search-results-empty" }, "No results"
|
|
2540
|
-
);
|
|
2541
|
-
}
|
|
2542
|
-
|
|
2543
|
-
if (totalCount === 0) return null;
|
|
2535
|
+
(function() {
|
|
2536
|
+
var allResults = state.searchResults || [];
|
|
2537
|
+
var loadedCount = allResults.length;
|
|
2538
|
+
var total = searchTotalCount > 0 ? searchTotalCount : loadedCount;
|
|
2539
|
+
var hasAny = loadedCount > 0;
|
|
2544
2540
|
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
"
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
React.createElement(
|
|
2565
|
-
React.createElement(
|
|
2541
|
+
return React.createElement(
|
|
2542
|
+
React.Fragment,
|
|
2543
|
+
null,
|
|
2544
|
+
searchQuery && !searchLoading && React.createElement(
|
|
2545
|
+
"div",
|
|
2546
|
+
{ className: "search-results-header" },
|
|
2547
|
+
hasAny
|
|
2548
|
+
? (total + (searchHasMore ? '+' : '') + " result" + (total !== 1 ? "s" : ""))
|
|
2549
|
+
: "No results"
|
|
2550
|
+
),
|
|
2551
|
+
hasAny && React.createElement(
|
|
2552
|
+
"div",
|
|
2553
|
+
{
|
|
2554
|
+
className: "search-results",
|
|
2555
|
+
ref: searchResultsContainerRef,
|
|
2556
|
+
onScroll: handleSearchResultsScroll
|
|
2557
|
+
},
|
|
2558
|
+
allResults.map(function(res, i) {
|
|
2559
|
+
var fileName = res.file.split('/').pop();
|
|
2560
|
+
return React.createElement(
|
|
2566
2561
|
"div",
|
|
2567
|
-
{
|
|
2562
|
+
{
|
|
2563
|
+
key: i,
|
|
2564
|
+
className: "search-result-item",
|
|
2565
|
+
onClick: (function(r) { return function() { handleSelectFile(r.file, r.file.split('/').pop(), r.line); }; })(res)
|
|
2566
|
+
},
|
|
2567
|
+
React.createElement("i", { className: (window.getFileIcon ? window.getFileIcon(fileName) : 'far fa-file-code') + " search-result-icon" }),
|
|
2568
2568
|
React.createElement(
|
|
2569
|
-
"div",
|
|
2570
|
-
{ className: "search-result-file" },
|
|
2571
|
-
fileName,
|
|
2569
|
+
"div", { className: "search-result-body" },
|
|
2572
2570
|
React.createElement(
|
|
2573
|
-
"
|
|
2574
|
-
|
|
2575
|
-
" ", res.file, ":", res.line
|
|
2576
|
-
)
|
|
2577
|
-
|
|
2578
|
-
React.createElement(
|
|
2579
|
-
"div",
|
|
2580
|
-
{ className: "search-result-text" },
|
|
2581
|
-
res.text
|
|
2571
|
+
"div", { className: "search-result-file" },
|
|
2572
|
+
fileName,
|
|
2573
|
+
React.createElement("span", { className: "search-result-line-num" }, " ", res.file, ":", res.line)
|
|
2574
|
+
),
|
|
2575
|
+
React.createElement("div", { className: "search-result-text" }, res.text)
|
|
2582
2576
|
)
|
|
2583
|
-
)
|
|
2584
|
-
)
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
null,
|
|
2590
|
-
React.createElement(
|
|
2591
|
-
"div",
|
|
2592
|
-
{ className: "search-results-meta" },
|
|
2593
|
-
totalCount,
|
|
2594
|
-
" result" + (totalCount !== 1 ? "s" : ""),
|
|
2595
|
-
searchHasMore && React.createElement("span", { className: "search-results-capped" }, " — more available, scroll for next page")
|
|
2596
|
-
),
|
|
2597
|
-
React.createElement("div", { style: { height: paddingTop + 'px', flexShrink: 0 } }),
|
|
2598
|
-
visibleItems,
|
|
2599
|
-
React.createElement("div", { style: { height: paddingBottom + 'px', flexShrink: 0 } }),
|
|
2600
|
-
searchLoadingMore && React.createElement(
|
|
2601
|
-
"div",
|
|
2602
|
-
{ className: "search-results-loading-more", 'aria-busy': 'true' },
|
|
2603
|
-
'Loading more…'
|
|
2577
|
+
);
|
|
2578
|
+
}),
|
|
2579
|
+
searchHasMore && React.createElement(
|
|
2580
|
+
"div", { className: "search-loading-more" },
|
|
2581
|
+
React.createElement("i", { className: "fas fa-spinner fa-spin" }),
|
|
2582
|
+
" Loading more\u2026"
|
|
2604
2583
|
)
|
|
2605
|
-
)
|
|
2606
|
-
|
|
2607
|
-
)
|
|
2584
|
+
)
|
|
2585
|
+
);
|
|
2586
|
+
})()
|
|
2608
2587
|
)
|
|
2609
2588
|
)
|
|
2610
2589
|
),
|
|
@@ -2735,6 +2714,32 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2735
2714
|
onChange: function(e) { setEditorPrefs(function(p) { return Object.assign({}, p, { fontFamily: e.target.value }); }); }
|
|
2736
2715
|
})
|
|
2737
2716
|
),
|
|
2717
|
+
React.createElement(
|
|
2718
|
+
'label', { className: 'ide-settings-row ide-settings-row-half', title: 'Row height in pixels. 0 = auto (roughly font size × 1.5)' },
|
|
2719
|
+
React.createElement('span', { className: 'ide-settings-label' }, 'Line height (0=auto)'),
|
|
2720
|
+
React.createElement('input', {
|
|
2721
|
+
type: 'number', min: '0', max: '100', step: '1',
|
|
2722
|
+
className: 'ide-settings-input',
|
|
2723
|
+
value: editorPrefs.lineHeight != null ? editorPrefs.lineHeight : 0,
|
|
2724
|
+
onChange: function(e) {
|
|
2725
|
+
var v = parseInt(e.target.value, 10);
|
|
2726
|
+
if (!isNaN(v) && v >= 0 && v <= 100) setEditorPrefs(function(p) { return Object.assign({}, p, { lineHeight: v }); });
|
|
2727
|
+
}
|
|
2728
|
+
})
|
|
2729
|
+
),
|
|
2730
|
+
React.createElement(
|
|
2731
|
+
'label', { className: 'ide-settings-row ide-settings-row-half', title: 'Extra space between characters in pixels. 0 = default' },
|
|
2732
|
+
React.createElement('span', { className: 'ide-settings-label' }, 'Letter spacing (px)'),
|
|
2733
|
+
React.createElement('input', {
|
|
2734
|
+
type: 'number', min: '-5', max: '20', step: '0.5',
|
|
2735
|
+
className: 'ide-settings-input',
|
|
2736
|
+
value: editorPrefs.letterSpacing != null ? editorPrefs.letterSpacing : 0,
|
|
2737
|
+
onChange: function(e) {
|
|
2738
|
+
var v = parseFloat(e.target.value);
|
|
2739
|
+
if (!isNaN(v) && v >= -5 && v <= 20) setEditorPrefs(function(p) { return Object.assign({}, p, { letterSpacing: v }); });
|
|
2740
|
+
}
|
|
2741
|
+
})
|
|
2742
|
+
),
|
|
2738
2743
|
|
|
2739
2744
|
/* ── Indentation (unified editor + Prettier) ── */
|
|
2740
2745
|
React.createElement('div', { className: 'ide-settings-section-header' }, 'Indentation'),
|
|
@@ -2844,6 +2849,184 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2844
2849
|
onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { vimMode: v }); }); }
|
|
2845
2850
|
})
|
|
2846
2851
|
),
|
|
2852
|
+
React.createElement(
|
|
2853
|
+
'label', { className: 'ide-settings-row ide-settings-row-half', title: 'When to insert a matching closing bracket automatically' },
|
|
2854
|
+
React.createElement('span', { className: 'ide-settings-label' }, 'Auto-close brackets'),
|
|
2855
|
+
React.createElement(
|
|
2856
|
+
'select', {
|
|
2857
|
+
value: editorPrefs.autoClosingBrackets || 'always',
|
|
2858
|
+
onChange: function(e) { setEditorPrefs(function(p) { return Object.assign({}, p, { autoClosingBrackets: e.target.value }); }); }
|
|
2859
|
+
},
|
|
2860
|
+
React.createElement('option', { value: 'always' }, 'Always'),
|
|
2861
|
+
React.createElement('option', { value: 'languageDefined' }, 'Per language rules'),
|
|
2862
|
+
React.createElement('option', { value: 'beforeWhitespace' }, 'Only before whitespace'),
|
|
2863
|
+
React.createElement('option', { value: 'never' }, 'Never')
|
|
2864
|
+
)
|
|
2865
|
+
),
|
|
2866
|
+
React.createElement(
|
|
2867
|
+
'label', { className: 'ide-settings-row ide-settings-row-half', title: 'When to insert a matching closing quote automatically' },
|
|
2868
|
+
React.createElement('span', { className: 'ide-settings-label' }, 'Auto-close quotes'),
|
|
2869
|
+
React.createElement(
|
|
2870
|
+
'select', {
|
|
2871
|
+
value: editorPrefs.autoClosingQuotes || 'always',
|
|
2872
|
+
onChange: function(e) { setEditorPrefs(function(p) { return Object.assign({}, p, { autoClosingQuotes: e.target.value }); }); }
|
|
2873
|
+
},
|
|
2874
|
+
React.createElement('option', { value: 'always' }, 'Always'),
|
|
2875
|
+
React.createElement('option', { value: 'languageDefined' }, 'Per language rules'),
|
|
2876
|
+
React.createElement('option', { value: 'beforeWhitespace' }, 'Only before whitespace'),
|
|
2877
|
+
React.createElement('option', { value: 'never' }, 'Never')
|
|
2878
|
+
)
|
|
2879
|
+
),
|
|
2880
|
+
React.createElement(
|
|
2881
|
+
'label', { className: 'ide-settings-row ide-settings-row-half', title: 'What to highlight on the current editor line' },
|
|
2882
|
+
React.createElement('span', { className: 'ide-settings-label' }, 'Line highlight'),
|
|
2883
|
+
React.createElement(
|
|
2884
|
+
'select', {
|
|
2885
|
+
value: editorPrefs.renderLineHighlight || 'none',
|
|
2886
|
+
onChange: function(e) { setEditorPrefs(function(p) { return Object.assign({}, p, { renderLineHighlight: e.target.value }); }); }
|
|
2887
|
+
},
|
|
2888
|
+
React.createElement('option', { value: 'none' }, 'None'),
|
|
2889
|
+
React.createElement('option', { value: 'gutter' }, 'Line number only'),
|
|
2890
|
+
React.createElement('option', { value: 'line' }, 'Current line background'),
|
|
2891
|
+
React.createElement('option', { value: 'all' }, 'Line number + background')
|
|
2892
|
+
)
|
|
2893
|
+
),
|
|
2894
|
+
React.createElement(
|
|
2895
|
+
'label', { className: 'ide-settings-row ide-settings-row-half' },
|
|
2896
|
+
React.createElement('span', { className: 'ide-settings-label' }, 'Cursor style'),
|
|
2897
|
+
React.createElement(
|
|
2898
|
+
'select', {
|
|
2899
|
+
value: editorPrefs.cursorStyle || 'line',
|
|
2900
|
+
onChange: function(e) { setEditorPrefs(function(p) { return Object.assign({}, p, { cursorStyle: e.target.value }); }); }
|
|
2901
|
+
},
|
|
2902
|
+
React.createElement('option', { value: 'line' }, 'Line (|)'),
|
|
2903
|
+
React.createElement('option', { value: 'block' }, 'Block (filled)'),
|
|
2904
|
+
React.createElement('option', { value: 'underline' }, 'Underline (_)'),
|
|
2905
|
+
React.createElement('option', { value: 'line-thin' }, 'Line thin'),
|
|
2906
|
+
React.createElement('option', { value: 'block-outline' }, 'Block outline'),
|
|
2907
|
+
React.createElement('option', { value: 'underline-thin' }, 'Underline thin')
|
|
2908
|
+
)
|
|
2909
|
+
),
|
|
2910
|
+
React.createElement(
|
|
2911
|
+
'label', { className: 'ide-settings-row ide-settings-row-half' },
|
|
2912
|
+
React.createElement('span', { className: 'ide-settings-label' }, 'Cursor blinking'),
|
|
2913
|
+
React.createElement(
|
|
2914
|
+
'select', {
|
|
2915
|
+
value: editorPrefs.cursorBlinking || 'blink',
|
|
2916
|
+
onChange: function(e) { setEditorPrefs(function(p) { return Object.assign({}, p, { cursorBlinking: e.target.value }); }); }
|
|
2917
|
+
},
|
|
2918
|
+
React.createElement('option', { value: 'blink' }, 'Blink (on/off)'),
|
|
2919
|
+
React.createElement('option', { value: 'smooth' }, 'Smooth (fade)'),
|
|
2920
|
+
React.createElement('option', { value: 'phase' }, 'Phase (offset fade)'),
|
|
2921
|
+
React.createElement('option', { value: 'expand' }, 'Expand (grow/shrink)'),
|
|
2922
|
+
React.createElement('option', { value: 'solid' }, 'Solid (no blink)')
|
|
2923
|
+
)
|
|
2924
|
+
),
|
|
2925
|
+
React.createElement(
|
|
2926
|
+
'label', { className: 'ide-settings-row ide-settings-row-check', title: 'Show collapse arrows next to foldable regions (functions, classes, blocks)' },
|
|
2927
|
+
React.createElement('span', { className: 'ide-settings-label' }, 'Code folding'),
|
|
2928
|
+
React.createElement('input', {
|
|
2929
|
+
type: 'checkbox',
|
|
2930
|
+
className: 'ide-settings-checkbox',
|
|
2931
|
+
checked: editorPrefs.folding !== false,
|
|
2932
|
+
onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { folding: v }); }); }
|
|
2933
|
+
})
|
|
2934
|
+
),
|
|
2935
|
+
React.createElement(
|
|
2936
|
+
'label', { className: 'ide-settings-row ide-settings-row-check', title: 'Animate scrolling instead of jumping instantly' },
|
|
2937
|
+
React.createElement('span', { className: 'ide-settings-label' }, 'Smooth scrolling'),
|
|
2938
|
+
React.createElement('input', {
|
|
2939
|
+
type: 'checkbox',
|
|
2940
|
+
className: 'ide-settings-checkbox',
|
|
2941
|
+
checked: !!(editorPrefs.smoothScrolling),
|
|
2942
|
+
onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { smoothScrolling: v }); }); }
|
|
2943
|
+
})
|
|
2944
|
+
),
|
|
2945
|
+
React.createElement(
|
|
2946
|
+
'label', { className: 'ide-settings-row ide-settings-row-check', title: 'Hold Ctrl (or Cmd) and scroll the mouse wheel to zoom the font size' },
|
|
2947
|
+
React.createElement('span', { className: 'ide-settings-label' }, 'Ctrl+scroll to zoom'),
|
|
2948
|
+
React.createElement('input', {
|
|
2949
|
+
type: 'checkbox',
|
|
2950
|
+
className: 'ide-settings-checkbox',
|
|
2951
|
+
checked: !!(editorPrefs.mouseWheelZoom),
|
|
2952
|
+
onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { mouseWheelZoom: v }); }); }
|
|
2953
|
+
})
|
|
2954
|
+
),
|
|
2955
|
+
|
|
2956
|
+
/* ── Behaviour ───────────────────────────────── */
|
|
2957
|
+
React.createElement('div', { className: 'ide-settings-section-header' }, 'Behaviour'),
|
|
2958
|
+
React.createElement(
|
|
2959
|
+
'label', { className: 'ide-settings-row ide-settings-row-half', title: 'How aggressively the editor re-indents lines as you type' },
|
|
2960
|
+
React.createElement('span', { className: 'ide-settings-label' }, 'Auto indent'),
|
|
2961
|
+
React.createElement(
|
|
2962
|
+
'select', {
|
|
2963
|
+
value: editorPrefs.autoIndent || 'full',
|
|
2964
|
+
onChange: function(e) { setEditorPrefs(function(p) { return Object.assign({}, p, { autoIndent: e.target.value }); }); }
|
|
2965
|
+
},
|
|
2966
|
+
React.createElement('option', { value: 'none' }, 'None (disabled)'),
|
|
2967
|
+
React.createElement('option', { value: 'keep' }, 'Keep current level'),
|
|
2968
|
+
React.createElement('option', { value: 'brackets' }, 'Indent on { and ['),
|
|
2969
|
+
React.createElement('option', { value: 'advanced' }, 'Language indent rules'),
|
|
2970
|
+
React.createElement('option', { value: 'full' }, 'Full (language grammar)')
|
|
2971
|
+
)
|
|
2972
|
+
),
|
|
2973
|
+
React.createElement(
|
|
2974
|
+
'label', { className: 'ide-settings-row ide-settings-row-half', title: 'Whether pressing Enter accepts the highlighted autocomplete suggestion' },
|
|
2975
|
+
React.createElement('span', { className: 'ide-settings-label' }, 'Accept suggestion on Enter'),
|
|
2976
|
+
React.createElement(
|
|
2977
|
+
'select', {
|
|
2978
|
+
value: editorPrefs.acceptSuggestionOnEnter || 'on',
|
|
2979
|
+
onChange: function(e) { setEditorPrefs(function(p) { return Object.assign({}, p, { acceptSuggestionOnEnter: e.target.value }); }); }
|
|
2980
|
+
},
|
|
2981
|
+
React.createElement('option', { value: 'on' }, 'Always'),
|
|
2982
|
+
React.createElement('option', { value: 'smart' }, 'Only when navigated (↑↓)'),
|
|
2983
|
+
React.createElement('option', { value: 'off' }, 'Never (Tab only)')
|
|
2984
|
+
)
|
|
2985
|
+
),
|
|
2986
|
+
React.createElement(
|
|
2987
|
+
'label', { className: 'ide-settings-row ide-settings-row-half', title: 'Suggest completions based on words already present in open files' },
|
|
2988
|
+
React.createElement('span', { className: 'ide-settings-label' }, 'Word-based suggestions'),
|
|
2989
|
+
React.createElement(
|
|
2990
|
+
'select', {
|
|
2991
|
+
value: editorPrefs.wordBasedSuggestions || 'matchingDocuments',
|
|
2992
|
+
onChange: function(e) { setEditorPrefs(function(p) { return Object.assign({}, p, { wordBasedSuggestions: e.target.value }); }); }
|
|
2993
|
+
},
|
|
2994
|
+
React.createElement('option', { value: 'off' }, 'Off'),
|
|
2995
|
+
React.createElement('option', { value: 'currentDocument' }, 'Current file only'),
|
|
2996
|
+
React.createElement('option', { value: 'matchingDocuments' }, 'Same language files'),
|
|
2997
|
+
React.createElement('option', { value: 'allDocuments' }, 'All open files')
|
|
2998
|
+
)
|
|
2999
|
+
),
|
|
3000
|
+
React.createElement(
|
|
3001
|
+
'label', { className: 'ide-settings-row ide-settings-row-check', title: 'Auto-format pasted code using the language formatter' },
|
|
3002
|
+
React.createElement('span', { className: 'ide-settings-label' }, 'Format on paste'),
|
|
3003
|
+
React.createElement('input', {
|
|
3004
|
+
type: 'checkbox',
|
|
3005
|
+
className: 'ide-settings-checkbox',
|
|
3006
|
+
checked: editorPrefs.formatOnPaste !== false,
|
|
3007
|
+
onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { formatOnPaste: v }); }); }
|
|
3008
|
+
})
|
|
3009
|
+
),
|
|
3010
|
+
React.createElement(
|
|
3011
|
+
'label', { className: 'ide-settings-row ide-settings-row-check', title: 'Re-indent and auto-close blocks as you type (e.g. after pressing Enter inside {})' },
|
|
3012
|
+
React.createElement('span', { className: 'ide-settings-label' }, 'Format on type'),
|
|
3013
|
+
React.createElement('input', {
|
|
3014
|
+
type: 'checkbox',
|
|
3015
|
+
className: 'ide-settings-checkbox',
|
|
3016
|
+
checked: editorPrefs.formatOnType !== false,
|
|
3017
|
+
onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { formatOnType: v }); }); }
|
|
3018
|
+
})
|
|
3019
|
+
),
|
|
3020
|
+
React.createElement(
|
|
3021
|
+
'label', { className: 'ide-settings-row ide-settings-row-check', title: 'Show autocomplete suggestions while typing (not just on trigger characters like .)' },
|
|
3022
|
+
React.createElement('span', { className: 'ide-settings-label' }, 'Quick suggestions'),
|
|
3023
|
+
React.createElement('input', {
|
|
3024
|
+
type: 'checkbox',
|
|
3025
|
+
className: 'ide-settings-checkbox',
|
|
3026
|
+
checked: editorPrefs.quickSuggestions !== false,
|
|
3027
|
+
onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { quickSuggestions: v }); }); }
|
|
3028
|
+
})
|
|
3029
|
+
),
|
|
2847
3030
|
|
|
2848
3031
|
/* ── Formatting (Prettier) ───────────────────── */
|
|
2849
3032
|
React.createElement('div', { className: 'ide-settings-section-header' }, 'Formatting'),
|
|
@@ -76,6 +76,24 @@ var QuickOpenDialog = function QuickOpenDialog(_ref) {
|
|
|
76
76
|
if (inputRef.current) inputRef.current.focus();
|
|
77
77
|
};
|
|
78
78
|
|
|
79
|
+
// Priority tier for a file path: lower = shown first.
|
|
80
|
+
// Order: controller > model > helper > concern > view > job > other > noise
|
|
81
|
+
function getFilePriority(path) {
|
|
82
|
+
var p = (path || '').toLowerCase();
|
|
83
|
+
if (p.indexOf('/controllers/') >= 0) return 1;
|
|
84
|
+
if (p.indexOf('/models/') >= 0) return 2;
|
|
85
|
+
if (p.indexOf('/helpers/') >= 0) return 3;
|
|
86
|
+
if (p.indexOf('/concerns/') >= 0) return 4;
|
|
87
|
+
if (p.indexOf('/views/') >= 0) return 5;
|
|
88
|
+
if (p.indexOf('/jobs/') >= 0) return 6;
|
|
89
|
+
// Deprioritise: migrations, schema, compiled assets, vendor, lock files
|
|
90
|
+
if (p.indexOf('/migrate/') >= 0 || p.indexOf('schema.rb') >= 0) return 90;
|
|
91
|
+
if (p.indexOf('/public/') >= 0 || p.indexOf('/vendor/') >= 0) return 91;
|
|
92
|
+
if (p.slice(-7) === '.min.js' || p.slice(-8) === '.min.css' ||
|
|
93
|
+
p.slice(-4) === '.map' || p.slice(-5) === '.lock') return 92;
|
|
94
|
+
return 50;
|
|
95
|
+
}
|
|
96
|
+
|
|
79
97
|
var getQuickOpenIcon = function getQuickOpenIcon(path, name, type) {
|
|
80
98
|
if (type === 'dir') {
|
|
81
99
|
return React.createElement('i', { className: 'fas fa-folder quick-open-result-icon quick-open-folder-icon', 'aria-hidden': 'true' });
|
|
@@ -99,7 +117,13 @@ var QuickOpenDialog = function QuickOpenDialog(_ref) {
|
|
|
99
117
|
var res = SearchService.searchFiles(query);
|
|
100
118
|
// Filter by type: always include files; include dirs only when showFolders is on
|
|
101
119
|
var filtered = showFolders ? res : res.filter(function(r) { return r.type !== 'dir'; });
|
|
102
|
-
|
|
120
|
+
// Stable priority sort: controller > model > helper > concern > view > job > other > noise.
|
|
121
|
+
// JS sort is stable in modern engines, so relative order within the same tier
|
|
122
|
+
// (i.e. MiniSearch relevance score) is preserved.
|
|
123
|
+
filtered.sort(function(a, b) {
|
|
124
|
+
return getFilePriority(a.path) - getFilePriority(b.path);
|
|
125
|
+
});
|
|
126
|
+
setResults(filtered.slice(0, 200));
|
|
103
127
|
setSelectedIndex(0);
|
|
104
128
|
}, [query, showFolders]);
|
|
105
129
|
|
|
@@ -20,6 +20,7 @@ var EditorStore = (function () {
|
|
|
20
20
|
};
|
|
21
21
|
|
|
22
22
|
var _listeners = [];
|
|
23
|
+
var _statusTimer = null;
|
|
23
24
|
|
|
24
25
|
function getState() { return _state; }
|
|
25
26
|
|
|
@@ -42,6 +43,9 @@ var EditorStore = (function () {
|
|
|
42
43
|
// Subscribe to changes in a specific subset of state keys.
|
|
43
44
|
// The listener is only called when at least one of the watched keys changes
|
|
44
45
|
// by reference (===), preventing unnecessary re-renders for unrelated updates.
|
|
46
|
+
// IMPORTANT: all state updates MUST produce a new object reference for any
|
|
47
|
+
// nested value (use Object.assign / spread — never mutate in place), otherwise
|
|
48
|
+
// subscribeToSlice will not detect the change.
|
|
45
49
|
function subscribeToSlice(keys, fn) {
|
|
46
50
|
var prev = {};
|
|
47
51
|
keys.forEach(function(k) { prev[k] = _state[k]; });
|
|
@@ -57,9 +61,13 @@ var EditorStore = (function () {
|
|
|
57
61
|
function setStatus(text, kind) {
|
|
58
62
|
kind = kind || "info";
|
|
59
63
|
setState({ statusMessage: { text: text, kind: kind } });
|
|
60
|
-
|
|
64
|
+
if (_statusTimer !== null) {
|
|
65
|
+
clearTimeout(_statusTimer);
|
|
66
|
+
_statusTimer = null;
|
|
67
|
+
}
|
|
61
68
|
if (kind !== "error") {
|
|
62
|
-
setTimeout(function () {
|
|
69
|
+
_statusTimer = setTimeout(function () {
|
|
70
|
+
_statusTimer = null;
|
|
63
71
|
if (_state.statusMessage.text === text) {
|
|
64
72
|
setState({ statusMessage: { text: "", kind: "info" } });
|
|
65
73
|
}
|