mbeditor 0.13.0 → 0.13.1
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 +66 -1
- data/app/assets/javascripts/mbeditor/application.js +0 -1
- data/app/assets/javascripts/mbeditor/collaboration_service.js +22 -2
- data/app/assets/javascripts/mbeditor/components/EditorPanel.js +11 -87
- data/app/assets/javascripts/mbeditor/components/ImportDialog.js +9 -1
- data/app/assets/javascripts/mbeditor/components/MbeditorApp.js +171 -205
- data/app/assets/javascripts/mbeditor/components/ProblemsPanel.js +35 -7
- data/app/assets/javascripts/mbeditor/components/QuickOpenDialog.js +97 -75
- data/app/assets/javascripts/mbeditor/editor_plugins.js +7 -1
- data/app/assets/javascripts/mbeditor/file_service.js +8 -18
- data/app/assets/javascripts/mbeditor/search_service.js +20 -2
- data/app/assets/javascripts/mbeditor/tab_manager.js +7 -4
- data/app/assets/stylesheets/mbeditor/editor.css +138 -67
- data/app/channels/mbeditor/collaboration_channel.rb +8 -2
- data/app/controllers/mbeditor/editors_controller.rb +8 -26
- data/app/services/mbeditor/collaboration_doc_store.rb +42 -3
- data/app/services/mbeditor/duplicate_content_scanner.rb +105 -0
- data/app/services/mbeditor/js_globals_service.rb +12 -1
- data/app/services/mbeditor/rubocop_run_service.rb +17 -5
- data/app/services/mbeditor/schema_service.rb +8 -2
- data/app/services/mbeditor/search_replace_service.rb +11 -2
- data/app/services/mbeditor/test_runner_service.rb +3 -56
- data/lib/mbeditor/configuration.rb +0 -8
- data/lib/mbeditor/route_map.rb +0 -1
- data/lib/mbeditor/version.rb +1 -1
- data/lib/tasks/mbeditor.rake +23 -0
- metadata +4 -3
- data/app/assets/javascripts/mbeditor/components/TestRunPanel.js +0 -312
|
@@ -216,6 +216,23 @@ var SectionActionGroup = function SectionActionGroup(_ref2) {
|
|
|
216
216
|
);
|
|
217
217
|
};
|
|
218
218
|
|
|
219
|
+
// Split a search hit into [before, match, after] so the match can be tinted
|
|
220
|
+
// and pinned on screen. `col`/`end_col` are 1-based against the RAW line while
|
|
221
|
+
// the row renders the stripped `text`, so `lead` — the characters strip took
|
|
222
|
+
// off the front — is what maps one onto the other. Returns null for the tiers
|
|
223
|
+
// and queries that produce no columns; the row then renders as plain text.
|
|
224
|
+
function searchMatchParts(res) {
|
|
225
|
+
var text = res.text == null ? "" : String(res.text);
|
|
226
|
+
if (!res.col || !res.end_col) return null;
|
|
227
|
+
var start = res.col - 1 - (res.lead || 0);
|
|
228
|
+
var end = Math.min(res.end_col - 1 - (res.lead || 0), text.length);
|
|
229
|
+
if (!(start >= 0 && end > start && start < text.length)) return null;
|
|
230
|
+
// U+200E: a strong LTR character, so the left-ellipsis trick in
|
|
231
|
+
// .search-result-pre (direction: rtl) can never reorder a segment that
|
|
232
|
+
// happens to be all punctuation.
|
|
233
|
+
return ["" + text.slice(0, start), text.slice(start, end), text.slice(end)];
|
|
234
|
+
}
|
|
235
|
+
|
|
219
236
|
function FileReloadBanner(_ref) {
|
|
220
237
|
var pendingReloads = _ref.pendingReloads;
|
|
221
238
|
var onSaveAndReload = _ref.onSaveAndReload;
|
|
@@ -366,10 +383,28 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
366
383
|
// SearchReplaceService::MAX_RESULTS (10_000) rows, and every row is five
|
|
367
384
|
// elements, so rendering the list in full built ~50k nodes — enough to kill
|
|
368
385
|
// the tab outright, and 92 ms of render for a mere 3_000 rows. Rows are a
|
|
369
|
-
// fixed
|
|
370
|
-
//
|
|
371
|
-
|
|
386
|
+
// fixed 22px, which is what makes the arithmetic here as simple as the file
|
|
387
|
+
// tree's. The results are a VS Code-style tree — a header row per file, its
|
|
388
|
+
// matches nested under it — but header and match rows are deliberately the
|
|
389
|
+
// *same* height (.search-result-file-row and .search-result-item both pin
|
|
390
|
+
// it), so the flattened row array still windows by plain multiplication.
|
|
391
|
+
// Give the two rows different heights and every offset here is wrong.
|
|
392
|
+
var SEARCH_ROW_HEIGHT = 22;
|
|
372
393
|
var SEARCH_ROW_BUFFER = 5;
|
|
394
|
+
|
|
395
|
+
// File paths whose match list is folded away. Keyed by path, so a file that
|
|
396
|
+
// scrolls out of the window keeps its state.
|
|
397
|
+
var _useStateSC = useState({});
|
|
398
|
+
var _useStateSC2 = _slicedToArray(_useStateSC, 2);
|
|
399
|
+
var searchCollapsedFiles = _useStateSC2[0];
|
|
400
|
+
var setSearchCollapsedFiles = _useStateSC2[1];
|
|
401
|
+
var toggleSearchFile = function toggleSearchFile(file) {
|
|
402
|
+
setSearchCollapsedFiles(function (prev) {
|
|
403
|
+
var next = Object.assign({}, prev);
|
|
404
|
+
if (next[file]) delete next[file]; else next[file] = true;
|
|
405
|
+
return next;
|
|
406
|
+
});
|
|
407
|
+
};
|
|
373
408
|
var _useStateSV = useState({ scrollTop: 0, height: 0 });
|
|
374
409
|
var _useStateSV2 = _slicedToArray(_useStateSV, 2);
|
|
375
410
|
var searchViewport = _useStateSV2[0];
|
|
@@ -561,11 +596,6 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
561
596
|
var showProblemsPanel = _useStateProblems2[0];
|
|
562
597
|
var setShowProblemsPanel = _useStateProblems2[1];
|
|
563
598
|
|
|
564
|
-
var _useStateTestRun = useState(false);
|
|
565
|
-
var _useStateTestRun2 = _slicedToArray(_useStateTestRun, 2);
|
|
566
|
-
var showTestRunPanel = _useStateTestRun2[0];
|
|
567
|
-
var setShowTestRunPanel = _useStateTestRun2[1];
|
|
568
|
-
|
|
569
599
|
// Error/warning tallies across the open tabs, mirrored into the status bar.
|
|
570
600
|
var _useStateProblemCounts = useState({ errors: 0, warnings: 0 });
|
|
571
601
|
var _useStateProblemCounts2 = _slicedToArray(_useStateProblemCounts, 2);
|
|
@@ -613,13 +643,6 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
613
643
|
var modelGraphLoading = _useStateModelGraphLoading2[0];
|
|
614
644
|
var setModelGraphLoading = _useStateModelGraphLoading2[1];
|
|
615
645
|
|
|
616
|
-
// ruby-lsp status for the status-bar chip. 'off' means never available here,
|
|
617
|
-
// 'degraded' means we backed off after a failure, 'ok' means it's answering.
|
|
618
|
-
var _useStateLspHealth = useState({ status: 'off', reason: null });
|
|
619
|
-
var _useStateLspHealth2 = _slicedToArray(_useStateLspHealth, 2);
|
|
620
|
-
var lspHealth = _useStateLspHealth2[0];
|
|
621
|
-
var setLspHealth = _useStateLspHealth2[1];
|
|
622
|
-
|
|
623
646
|
var _useState18g = useState(320);
|
|
624
647
|
var _useState18g2 = _slicedToArray(_useState18g, 2);
|
|
625
648
|
var gitPanelWidth = _useState18g2[0];
|
|
@@ -675,11 +698,6 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
675
698
|
var rubocopConfigPath = _useState18rc2[0];
|
|
676
699
|
var setRubocopConfigPath = _useState18rc2[1];
|
|
677
700
|
|
|
678
|
-
var _useState18t = useState(false);
|
|
679
|
-
var _useState18t2 = _slicedToArray(_useState18t, 2);
|
|
680
|
-
var testAvailable = _useState18t2[0];
|
|
681
|
-
var setTestAvailable = _useState18t2[1];
|
|
682
|
-
|
|
683
701
|
var _useState18u = useState(null);
|
|
684
702
|
var _useState18u2 = _slicedToArray(_useState18u, 2);
|
|
685
703
|
var testResult = _useState18u2[0];
|
|
@@ -1023,64 +1041,6 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1023
1041
|
loadModelGraph(false);
|
|
1024
1042
|
}, [activeSidebarTab, sidebarCollapsed]);
|
|
1025
1043
|
|
|
1026
|
-
var readLspHealth = function readLspHealth() {
|
|
1027
|
-
if (!window.MBEDITOR_RUBY_LSP_AVAILABLE) {
|
|
1028
|
-
return { status: 'off', reason: window.MBEDITOR_RUBY_LSP_REASON || null };
|
|
1029
|
-
}
|
|
1030
|
-
if (window.MbeditorEditorPlugins && MbeditorEditorPlugins.lspBackedOff()) {
|
|
1031
|
-
return { status: 'degraded', reason: window.MBEDITOR_RUBY_LSP_REASON || null };
|
|
1032
|
-
}
|
|
1033
|
-
return { status: 'ok', reason: null };
|
|
1034
|
-
};
|
|
1035
|
-
|
|
1036
|
-
// The backoff expires on a wall-clock deadline rather than a timer, so the
|
|
1037
|
-
// chip also re-reads on a slow interval — otherwise it would sit on
|
|
1038
|
-
// 'degraded' until the next failure or restart click.
|
|
1039
|
-
//
|
|
1040
|
-
// readLspHealth() builds a fresh object every call, so handing it straight to
|
|
1041
|
-
// setLspHealth re-rendered the whole app every 10 seconds whether or not the
|
|
1042
|
-
// health had changed — React bails on Object.is, and two object literals are
|
|
1043
|
-
// never identical. Compare the fields and keep the previous object when they
|
|
1044
|
-
// match. (Same shape of bug as the file-tree poll; see _treeUpdater.)
|
|
1045
|
-
useEffect(function () {
|
|
1046
|
-
var sync = function () {
|
|
1047
|
-
setLspHealth(function (prev) {
|
|
1048
|
-
var next = readLspHealth();
|
|
1049
|
-
if (prev && prev.status === next.status && prev.reason === next.reason) return prev;
|
|
1050
|
-
return next;
|
|
1051
|
-
});
|
|
1052
|
-
};
|
|
1053
|
-
sync();
|
|
1054
|
-
window.addEventListener('mbeditor:lsp-health', sync);
|
|
1055
|
-
var tick = setInterval(sync, 10000);
|
|
1056
|
-
return function () {
|
|
1057
|
-
window.removeEventListener('mbeditor:lsp-health', sync);
|
|
1058
|
-
clearInterval(tick);
|
|
1059
|
-
};
|
|
1060
|
-
}, []);
|
|
1061
|
-
|
|
1062
|
-
var restartRubyLsp = function restartRubyLsp() {
|
|
1063
|
-
if (!FileService.rubyLspRequest) return;
|
|
1064
|
-
EditorStore.setStatus('Restarting ruby-lsp…', 'info');
|
|
1065
|
-
FileService.rubyLspRequest('restart', '', '', 1, 1).then(function (data) {
|
|
1066
|
-
var ok = data && data.available && data.state !== 'failed';
|
|
1067
|
-
if (ok) {
|
|
1068
|
-
window.MBEDITOR_RUBY_LSP_AVAILABLE = true;
|
|
1069
|
-
window.MBEDITOR_RUBY_LSP_DISABLED_UNTIL = 0;
|
|
1070
|
-
window.MBEDITOR_RUBY_LSP_REASON = null;
|
|
1071
|
-
} else {
|
|
1072
|
-
window.MBEDITOR_RUBY_LSP_REASON =
|
|
1073
|
-
(data && (data.reason || data.error)) || 'ruby-lsp did not come back';
|
|
1074
|
-
}
|
|
1075
|
-
EditorStore.setStatus(ok ? 'ruby-lsp restarted' : 'ruby-lsp unavailable', ok ? 'success' : 'warning');
|
|
1076
|
-
setLspHealth(readLspHealth());
|
|
1077
|
-
})["catch"](function (err) {
|
|
1078
|
-
noteLspFailure(err);
|
|
1079
|
-
EditorStore.setStatus('Could not restart ruby-lsp', 'error');
|
|
1080
|
-
setLspHealth(readLspHealth());
|
|
1081
|
-
});
|
|
1082
|
-
};
|
|
1083
|
-
|
|
1084
1044
|
// The one writer for the markers map. Two things it must not do:
|
|
1085
1045
|
//
|
|
1086
1046
|
// * write a fresh map when nothing changed — the auto-lint fires per
|
|
@@ -1260,14 +1220,8 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1260
1220
|
if (workspace && typeof workspace.redmineEnabled === 'boolean') {
|
|
1261
1221
|
setRedmineEnabled(workspace.redmineEnabled);
|
|
1262
1222
|
}
|
|
1263
|
-
if (workspace &&
|
|
1264
|
-
FileService.
|
|
1265
|
-
test: workspace.testTimeout,
|
|
1266
|
-
testAll: workspace.testAllTimeout
|
|
1267
|
-
});
|
|
1268
|
-
}
|
|
1269
|
-
if (workspace && typeof workspace.testAvailable === 'boolean') {
|
|
1270
|
-
setTestAvailable(workspace.testAvailable);
|
|
1223
|
+
if (workspace && workspace.testTimeout) {
|
|
1224
|
+
FileService.setTestTimeout(workspace.testTimeout);
|
|
1271
1225
|
}
|
|
1272
1226
|
if (workspace && typeof workspace.actionCableEnabled === 'boolean') {
|
|
1273
1227
|
WebSocketService.connect(workspace.actionCableEnabled);
|
|
@@ -2019,6 +1973,12 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2019
1973
|
});
|
|
2020
1974
|
})
|
|
2021
1975
|
});
|
|
1976
|
+
// The text just moved under the diagnostics. Only the mounted editor
|
|
1977
|
+
// re-lints, so without this a background tab kept reporting offenses
|
|
1978
|
+
// at line numbers the external write had shifted — and the Problems
|
|
1979
|
+
// panel and status-bar tallies reported them too. Dropping them says
|
|
1980
|
+
// "not known yet", which is true: the file re-lints when you open it.
|
|
1981
|
+
discardStaleMarkers(pt.tab.path);
|
|
2022
1982
|
} else {
|
|
2023
1983
|
// Re-verify the tab still exists before queuing
|
|
2024
1984
|
var currentState = EditorStore.getState();
|
|
@@ -2125,8 +2085,8 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2125
2085
|
};
|
|
2126
2086
|
}, [monacoReady]);
|
|
2127
2087
|
|
|
2128
|
-
var handleSelectFile = function handleSelectFile(path, name, line, col) {
|
|
2129
|
-
TabManager.openTab(path, name, line, null, false, col);
|
|
2088
|
+
var handleSelectFile = function handleSelectFile(path, name, line, col, endCol) {
|
|
2089
|
+
TabManager.openTab(path, name, line, null, false, col, endCol);
|
|
2130
2090
|
handleNodeSelect({ path: path, name: name || path.split('/').pop(), type: 'file' });
|
|
2131
2091
|
setQuickOpen(false);
|
|
2132
2092
|
};
|
|
@@ -3391,7 +3351,19 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3391
3351
|
// counts because only the visible tab was re-checked. Dropping them says
|
|
3392
3352
|
// "not known yet", which is true: the file re-lints when you open it.
|
|
3393
3353
|
var discardStaleMarkers = function discardStaleMarkers(path) {
|
|
3394
|
-
if (!path
|
|
3354
|
+
if (!path) return;
|
|
3355
|
+
|
|
3356
|
+
// The React map has to go too, not just Monaco's copy. It is what TabBar
|
|
3357
|
+
// counts, and EditorPanel re-applies it to the model the next time that tab
|
|
3358
|
+
// mounts — so clearing only Monaco left every background tab primed to put
|
|
3359
|
+
// its stale squiggles straight back on the next tab switch.
|
|
3360
|
+
EditorStore.getState().panes.forEach(function (p) {
|
|
3361
|
+
p.tabs.forEach(function (t) {
|
|
3362
|
+
if (t.path === path) applyMarkersForTab(t.id, []);
|
|
3363
|
+
});
|
|
3364
|
+
});
|
|
3365
|
+
|
|
3366
|
+
if (!window.monaco || !window.monaco.editor) return;
|
|
3395
3367
|
var entry = window.__mbeditorModels && window.__mbeditorModels[path];
|
|
3396
3368
|
if (!entry || !entry.model || entry.model.isDisposed()) return;
|
|
3397
3369
|
|
|
@@ -3522,15 +3494,6 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3522
3494
|
|
|
3523
3495
|
var TEST_CACHE_PREFIX = 'mbeditor_test_result_';
|
|
3524
3496
|
|
|
3525
|
-
var loadCachedTestResult = function loadCachedTestResult(filePath) {
|
|
3526
|
-
try {
|
|
3527
|
-
var stored = localStorage.getItem(TEST_CACHE_PREFIX + filePath);
|
|
3528
|
-
return stored ? JSON.parse(stored) : null;
|
|
3529
|
-
} catch (e) {
|
|
3530
|
-
return null;
|
|
3531
|
-
}
|
|
3532
|
-
};
|
|
3533
|
-
|
|
3534
3497
|
var saveCachedTestResult = function saveCachedTestResult(filePath, result) {
|
|
3535
3498
|
try {
|
|
3536
3499
|
localStorage.setItem(TEST_CACHE_PREFIX + filePath, JSON.stringify(result));
|
|
@@ -3572,21 +3535,6 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3572
3535
|
});
|
|
3573
3536
|
};
|
|
3574
3537
|
|
|
3575
|
-
var handleRunTest = function handleRunTest() {
|
|
3576
|
-
if (!activeTab || !activeTab.path) return;
|
|
3577
|
-
if (testLoading) return;
|
|
3578
|
-
|
|
3579
|
-
var cached = loadCachedTestResult(activeTab.path);
|
|
3580
|
-
if (cached && !testPanelOpen) {
|
|
3581
|
-
setTestResult(cached);
|
|
3582
|
-
setTestPanelFile(cached.testFile || activeTab.path);
|
|
3583
|
-
setTestPanelOpen(true);
|
|
3584
|
-
return;
|
|
3585
|
-
}
|
|
3586
|
-
|
|
3587
|
-
executeTestRun(activeTab.path);
|
|
3588
|
-
};
|
|
3589
|
-
|
|
3590
3538
|
var handleRerunTest = function handleRerunTest() {
|
|
3591
3539
|
if (!activeTab || !activeTab.path) return;
|
|
3592
3540
|
if (testLoading) return;
|
|
@@ -3627,6 +3575,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3627
3575
|
searchOffsetRef.current = 0;
|
|
3628
3576
|
searchLoadingMoreRef.current = false;
|
|
3629
3577
|
searchQueryRef.current = q;
|
|
3578
|
+
setSearchCollapsedFiles({});
|
|
3630
3579
|
EditorStore.setState({ searchResults: [], searchHasMore: false });
|
|
3631
3580
|
EditorStore.setStatus("Searching project...", "info");
|
|
3632
3581
|
SearchService.projectSearch(q, 0, SearchService.PAGE_SIZE, { regex: searchUseRegexRef.current, matchCase: searchMatchCaseRef.current, wholeWord: searchWholeWordRef.current }).then(function (res) {
|
|
@@ -3795,10 +3744,6 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3795
3744
|
setShowLogPanel(function (prev) { return !prev; });
|
|
3796
3745
|
};
|
|
3797
3746
|
|
|
3798
|
-
var toggleTestRunPanel = function toggleTestRunPanel() {
|
|
3799
|
-
setShowTestRunPanel(function (prev) { return !prev; });
|
|
3800
|
-
};
|
|
3801
|
-
|
|
3802
3747
|
var toggleProblemsPanel = function toggleProblemsPanel() {
|
|
3803
3748
|
setShowProblemsPanel(function (prev) { return !prev; });
|
|
3804
3749
|
};
|
|
@@ -3885,12 +3830,17 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3885
3830
|
});
|
|
3886
3831
|
};
|
|
3887
3832
|
|
|
3888
|
-
var finishImport = function finishImport(result) {
|
|
3889
|
-
var
|
|
3833
|
+
var finishImport = function finishImport(result, destFolder) {
|
|
3834
|
+
var written = result.imported || [];
|
|
3835
|
+
var imported = written.length;
|
|
3890
3836
|
var skipped = (result.conflicts || []).length;
|
|
3891
3837
|
var failed = (result.errors || []).length;
|
|
3892
3838
|
|
|
3893
|
-
|
|
3839
|
+
// Say where the files went. A bulk upload that reports only a count looks
|
|
3840
|
+
// the same whether it landed where you meant it to or in the workspace
|
|
3841
|
+
// root, and the destination is the whole question a folder import raises.
|
|
3842
|
+
var where = destFolder ? ' to ' + destFolder : ' to the workspace root';
|
|
3843
|
+
var parts = [imported + ' file' + (imported === 1 ? '' : 's') + ' imported' + (imported > 0 ? where : '')];
|
|
3894
3844
|
if (skipped > 0) parts.push(skipped + ' skipped');
|
|
3895
3845
|
if (failed > 0) parts.push(failed + ' failed');
|
|
3896
3846
|
|
|
@@ -3898,7 +3848,21 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3898
3848
|
EditorStore.setStatus(parts.join(', ') + '.', level);
|
|
3899
3849
|
|
|
3900
3850
|
if (imported > 0) {
|
|
3901
|
-
|
|
3851
|
+
// Expand down to what was just written, so the tree actually shows it —
|
|
3852
|
+
// importing into a collapsed folder otherwise leaves the explorer looking
|
|
3853
|
+
// untouched. Deliberately does not *select* the folder: the tree
|
|
3854
|
+
// selection is what the toolbar's Upload button reads for its default
|
|
3855
|
+
// destination, and pinning it here would make every later upload default
|
|
3856
|
+
// to this import's folder.
|
|
3857
|
+
var landed = parentDir(written[0].path);
|
|
3858
|
+
var toExpand = {};
|
|
3859
|
+
var bits = landed ? landed.split('/') : [];
|
|
3860
|
+
for (var i = 1; i <= bits.length; i++) toExpand[bits.slice(0, i).join('/')] = true;
|
|
3861
|
+
|
|
3862
|
+
refreshProjectTree().then(function() {
|
|
3863
|
+
if (bits.length) setExpandedDirs(function (prev) { return Object.assign({}, prev, toExpand); });
|
|
3864
|
+
GitService.fetchStatus();
|
|
3865
|
+
});
|
|
3902
3866
|
}
|
|
3903
3867
|
};
|
|
3904
3868
|
|
|
@@ -3942,7 +3906,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3942
3906
|
if (result.conflicts && result.conflicts.length > 0) {
|
|
3943
3907
|
setImportConflict({ result: result, entries: entries, targetFolderPath: targetFolderPath });
|
|
3944
3908
|
} else {
|
|
3945
|
-
finishImport(result);
|
|
3909
|
+
finishImport(result, targetFolderPath);
|
|
3946
3910
|
}
|
|
3947
3911
|
})['catch'](function(err) {
|
|
3948
3912
|
var message = err && err.response && err.response.data && err.response.data.error || err.message;
|
|
@@ -3955,14 +3919,14 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3955
3919
|
setImportConflict(null);
|
|
3956
3920
|
if (!pending) return;
|
|
3957
3921
|
|
|
3958
|
-
if (mode === 'skip') { finishImport(pending.result); return; }
|
|
3922
|
+
if (mode === 'skip') { finishImport(pending.result, pending.targetFolderPath); return; }
|
|
3959
3923
|
|
|
3960
3924
|
var retry = FileImport.conflictedEntries(
|
|
3961
3925
|
pending.entries,
|
|
3962
3926
|
pending.targetFolderPath,
|
|
3963
3927
|
pending.result.conflicts
|
|
3964
3928
|
);
|
|
3965
|
-
if (retry.length === 0) { finishImport(pending.result); return; }
|
|
3929
|
+
if (retry.length === 0) { finishImport(pending.result, pending.targetFolderPath); return; }
|
|
3966
3930
|
|
|
3967
3931
|
FileService.importFiles(FileImport.buildFormData(retry, pending.targetFolderPath, mode))
|
|
3968
3932
|
.then(function(second) {
|
|
@@ -3970,7 +3934,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3970
3934
|
imported: (pending.result.imported || []).concat(second.imported || []),
|
|
3971
3935
|
conflicts: [],
|
|
3972
3936
|
errors: (pending.result.errors || []).concat(second.errors || [])
|
|
3973
|
-
});
|
|
3937
|
+
}, pending.targetFolderPath);
|
|
3974
3938
|
})['catch'](function(err) {
|
|
3975
3939
|
var message = err && err.response && err.response.data && err.response.data.error || err.message;
|
|
3976
3940
|
EditorStore.setStatus('Import failed: ' + message, 'error');
|
|
@@ -5088,17 +5052,24 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
5088
5052
|
React.createElement("i", { className: "tree-item-icon " + (window.getFileIcon ? window.getFileIcon(tab.name) : 'far fa-file-code') + " tree-file-icon" }),
|
|
5089
5053
|
React.createElement(
|
|
5090
5054
|
"div",
|
|
5091
|
-
|
|
5055
|
+
// minWidth:0 on both the row's name cell and the label
|
|
5056
|
+
// itself: without it a flex item refuses to shrink
|
|
5057
|
+
// below its content, so a long filename pushed out
|
|
5058
|
+
// under the (formerly absolute) action buttons.
|
|
5059
|
+
{ className: "tree-item-name", style: { display: 'flex', alignItems: 'center', minWidth: 0 } },
|
|
5092
5060
|
React.createElement(
|
|
5093
5061
|
"span",
|
|
5094
|
-
{ style: { overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } },
|
|
5062
|
+
{ style: { overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', minWidth: 0 } },
|
|
5095
5063
|
tab.name
|
|
5096
5064
|
),
|
|
5097
|
-
tab.dirty && React.createElement("i", { className: "fas fa-circle", style: { fontSize: '5px', color: '#e3d286', marginLeft: '6px', marginTop: '1px' } })
|
|
5065
|
+
tab.dirty && React.createElement("i", { className: "fas fa-circle", style: { fontSize: '5px', color: '#e3d286', marginLeft: '6px', marginTop: '1px', flexShrink: 0 } })
|
|
5098
5066
|
),
|
|
5099
5067
|
React.createElement(
|
|
5100
5068
|
"div",
|
|
5101
|
-
|
|
5069
|
+
// In flow, not absolute — the buttons now claim their
|
|
5070
|
+
// own width so the name truncates instead of running
|
|
5071
|
+
// underneath them.
|
|
5072
|
+
{ className: "tab-actions", style: { display: 'flex', alignItems: 'center', flexShrink: 0, marginLeft: 'auto' } },
|
|
5102
5073
|
React.createElement(
|
|
5103
5074
|
"div",
|
|
5104
5075
|
{ className: "tab-split", onClick: function (e) {
|
|
@@ -5337,13 +5308,29 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
5337
5308
|
var total = searchTotalCount > 0 ? searchTotalCount : loadedCount;
|
|
5338
5309
|
var hasAny = loadedCount > 0;
|
|
5339
5310
|
|
|
5311
|
+
// Grouped into a VS Code-style tree, then flattened straight back
|
|
5312
|
+
// into one row array: the windowing below is unchanged, it just
|
|
5313
|
+
// indexes rows instead of results. Every tier emits its hits file
|
|
5314
|
+
// by file, so a run of the same path is the whole group.
|
|
5315
|
+
var rows = [];
|
|
5316
|
+
var group = null;
|
|
5317
|
+
allResults.forEach(function (res, idx) {
|
|
5318
|
+
if (!group || group.file !== res.file) {
|
|
5319
|
+
group = { type: 'file', file: res.file, count: 0 };
|
|
5320
|
+
rows.push(group);
|
|
5321
|
+
}
|
|
5322
|
+
group.count += 1;
|
|
5323
|
+
if (!searchCollapsedFiles[res.file]) rows.push({ type: 'match', res: res, idx: idx });
|
|
5324
|
+
});
|
|
5325
|
+
var rowCount = rows.length;
|
|
5326
|
+
|
|
5340
5327
|
// Only the rows on screen (plus a small buffer either side) are
|
|
5341
5328
|
// built. Everything else is represented by the height of the
|
|
5342
5329
|
// spacer, so the scrollbar and every scroll position stay exactly
|
|
5343
5330
|
// as they would be for the full list.
|
|
5344
5331
|
var winStart = Math.max(0, Math.floor(searchViewport.scrollTop / SEARCH_ROW_HEIGHT) - SEARCH_ROW_BUFFER);
|
|
5345
|
-
var winEnd = Math.min(
|
|
5346
|
-
var visible =
|
|
5332
|
+
var winEnd = Math.min(rowCount, Math.ceil((searchViewport.scrollTop + (searchViewport.height || 600)) / SEARCH_ROW_HEIGHT) + SEARCH_ROW_BUFFER);
|
|
5333
|
+
var visible = rows.slice(winStart, winEnd);
|
|
5347
5334
|
|
|
5348
5335
|
return React.createElement(
|
|
5349
5336
|
React.Fragment,
|
|
@@ -5367,30 +5354,57 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
5367
5354
|
},
|
|
5368
5355
|
React.createElement(
|
|
5369
5356
|
"div",
|
|
5370
|
-
{ style: { height:
|
|
5371
|
-
visible.map(function(
|
|
5357
|
+
{ style: { height: rowCount * SEARCH_ROW_HEIGHT, position: 'relative' } },
|
|
5358
|
+
visible.map(function(row, vi) {
|
|
5372
5359
|
var i = winStart + vi;
|
|
5373
|
-
var
|
|
5360
|
+
var top = { position: 'absolute', top: i * SEARCH_ROW_HEIGHT, left: 0, right: 0 };
|
|
5361
|
+
|
|
5362
|
+
if (row.type === 'file') {
|
|
5363
|
+
var fileName = row.file.split('/').pop();
|
|
5364
|
+
var dir = row.file.slice(0, row.file.length - fileName.length).replace(/\/$/, '');
|
|
5365
|
+
var collapsed = !!searchCollapsedFiles[row.file];
|
|
5366
|
+
return React.createElement(
|
|
5367
|
+
"div",
|
|
5368
|
+
{
|
|
5369
|
+
key: 'f:' + row.file,
|
|
5370
|
+
className: "search-result-file-row",
|
|
5371
|
+
style: top,
|
|
5372
|
+
title: row.file,
|
|
5373
|
+
onClick: (function(f) { return function() { toggleSearchFile(f); }; })(row.file)
|
|
5374
|
+
},
|
|
5375
|
+
React.createElement("i", { className: "codicon codicon-chevron-" + (collapsed ? "right" : "down") + " search-result-chevron" }),
|
|
5376
|
+
React.createElement("i", { className: (window.getFileIcon ? window.getFileIcon(fileName) : 'far fa-file-code') + " search-result-icon" }),
|
|
5377
|
+
React.createElement("span", { className: "search-result-file-name" }, fileName),
|
|
5378
|
+
dir && React.createElement("span", { className: "search-result-file-dir" }, dir),
|
|
5379
|
+
React.createElement("span", { className: "search-result-count" }, row.count)
|
|
5380
|
+
);
|
|
5381
|
+
}
|
|
5382
|
+
|
|
5383
|
+
var res = row.res;
|
|
5374
5384
|
return React.createElement(
|
|
5375
5385
|
"div",
|
|
5376
5386
|
{
|
|
5377
|
-
key:
|
|
5387
|
+
key: 'm:' + row.idx,
|
|
5378
5388
|
className: "search-result-item",
|
|
5379
|
-
style:
|
|
5380
|
-
|
|
5381
|
-
//
|
|
5382
|
-
|
|
5389
|
+
style: top,
|
|
5390
|
+
title: res.file + ":" + res.line,
|
|
5391
|
+
// col..end_col selects the match and leaves the cursor
|
|
5392
|
+
// just past it, which is where you want to start
|
|
5393
|
+
// typing after jumping to a hit — not column 1.
|
|
5394
|
+
onClick: (function(r) { return function() { handleSelectFile(r.file, r.file.split('/').pop(), r.line, r.col || r.end_col, r.end_col); }; })(res)
|
|
5383
5395
|
},
|
|
5384
|
-
React.createElement("
|
|
5385
|
-
|
|
5386
|
-
|
|
5387
|
-
React.createElement(
|
|
5388
|
-
|
|
5389
|
-
|
|
5390
|
-
|
|
5391
|
-
|
|
5392
|
-
|
|
5393
|
-
|
|
5396
|
+
React.createElement("span", { className: "search-result-line-num" }, res.line),
|
|
5397
|
+
(function () {
|
|
5398
|
+
var parts = searchMatchParts(res);
|
|
5399
|
+
if (!parts) return React.createElement("span", { className: "search-result-text" }, res.text);
|
|
5400
|
+
return React.createElement(
|
|
5401
|
+
"span",
|
|
5402
|
+
{ className: "search-result-text search-result-text-split" },
|
|
5403
|
+
React.createElement("span", { className: "search-result-pre" }, parts[0]),
|
|
5404
|
+
React.createElement("mark", { className: "search-result-match" }, parts[1]),
|
|
5405
|
+
React.createElement("span", { className: "search-result-post" }, parts[2])
|
|
5406
|
+
);
|
|
5407
|
+
})()
|
|
5394
5408
|
);
|
|
5395
5409
|
})
|
|
5396
5410
|
),
|
|
@@ -6240,17 +6254,14 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
6240
6254
|
paneId: pane.id,
|
|
6241
6255
|
markers: markers[pActiveTab.id] || [],
|
|
6242
6256
|
gitAvailable: gitAvailable,
|
|
6243
|
-
testAvailable: testAvailable,
|
|
6244
6257
|
treeData: treeData,
|
|
6245
6258
|
testResult: testResult,
|
|
6246
6259
|
testPanelFile: testPanelFile,
|
|
6247
|
-
testLoading: testLoading,
|
|
6248
6260
|
testInlineVisible: testInlineVisible,
|
|
6249
6261
|
editorPrefs: editorPrefs,
|
|
6250
6262
|
monacoReady: monacoReady,
|
|
6251
6263
|
onFormat: function() { onFormatRef.current(); },
|
|
6252
6264
|
onSave: function() { handleSave(pane.id, pActiveTab); },
|
|
6253
|
-
onRunTest: handleRunTest,
|
|
6254
6265
|
onRunTestAtCursor: handleRunTestAtCursor,
|
|
6255
6266
|
onShowHistory: function(path) { setHistoryPanelPath(path); },
|
|
6256
6267
|
onContentChange: function onContentChange(val) {
|
|
@@ -6432,24 +6443,16 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
6432
6443
|
}),
|
|
6433
6444
|
showProblemsPanel && !zenMode && React.createElement(window.ProblemsPanel || ProblemsPanel, {
|
|
6434
6445
|
onClose: function () { setShowProblemsPanel(false); },
|
|
6435
|
-
onOpenFile: function (path, line, col) {
|
|
6436
|
-
handleSelectFile(path, path.split('/').pop(), line, col);
|
|
6437
|
-
}
|
|
6438
|
-
}),
|
|
6439
|
-
showTestRunPanel && !zenMode && React.createElement(window.TestRunPanel, {
|
|
6440
|
-
onClose: function () { setShowTestRunPanel(false); },
|
|
6441
6446
|
onOpenFile: function (path, line, col) {
|
|
6442
6447
|
handleSelectFile(path, path.split('/').pop(), line, col);
|
|
6443
6448
|
},
|
|
6444
|
-
//
|
|
6445
|
-
//
|
|
6446
|
-
//
|
|
6447
|
-
//
|
|
6448
|
-
|
|
6449
|
-
|
|
6450
|
-
|
|
6451
|
-
}
|
|
6452
|
-
})
|
|
6449
|
+
// `rubocop -a` writes through a subprocess, so the server's
|
|
6450
|
+
// files_changed broadcast is the only notice — and there is no
|
|
6451
|
+
// broadcast at all without a cable connection. Re-read every open tab
|
|
6452
|
+
// here too: clean tabs take the corrected text (and re-lint), dirty
|
|
6453
|
+
// ones get the usual reload prompt instead of being silently clobbered.
|
|
6454
|
+
onFilesRewritten: function () { checkOpenTabsForExternalChanges(); }
|
|
6455
|
+
}),
|
|
6453
6456
|
),
|
|
6454
6457
|
|
|
6455
6458
|
// Right-side Git panel (children of ide-body, alongside sidebar and ide-main)
|
|
@@ -6509,46 +6512,9 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
6509
6512
|
},
|
|
6510
6513
|
React.createElement("i", { className: "fas fa-bug statusbar-problems-error-icon" }),
|
|
6511
6514
|
React.createElement("span", { className: "statusbar-problems-count" }, problemCounts.errors),
|
|
6512
|
-
React.createElement("i", {
|
|
6513
|
-
className: "fas fa-exclamation-triangle statusbar-problems-warning-icon",
|
|
6514
|
-
style: { marginLeft: "8px" }
|
|
6515
|
-
}),
|
|
6515
|
+
React.createElement("i", { className: "fas fa-exclamation-triangle statusbar-problems-warning-icon" }),
|
|
6516
6516
|
React.createElement("span", { className: "statusbar-problems-count" }, problemCounts.warnings)
|
|
6517
6517
|
),
|
|
6518
|
-
// Gated on the same probe as the per-file Test button: a project with no
|
|
6519
|
-
// test directory has nothing for this to run.
|
|
6520
|
-
testAvailable && React.createElement(
|
|
6521
|
-
"button",
|
|
6522
|
-
{
|
|
6523
|
-
type: "button",
|
|
6524
|
-
className: "statusbar-btn statusbar-testrun" + (showTestRunPanel ? " active" : ""),
|
|
6525
|
-
onClick: toggleTestRunPanel,
|
|
6526
|
-
title: "Run the whole test suite"
|
|
6527
|
-
},
|
|
6528
|
-
React.createElement("i", { className: "fas fa-flask" }),
|
|
6529
|
-
React.createElement("span", null, " Tests")
|
|
6530
|
-
),
|
|
6531
|
-
// ruby-lsp indicator. Hidden entirely when ruby-lsp was never available
|
|
6532
|
-
// and nothing has gone wrong — a permanent "off" badge in a project with
|
|
6533
|
-
// no Ruby is noise. A healthy server gets a quiet icon; a degraded one
|
|
6534
|
-
// gets an amber chip you can click to restart.
|
|
6535
|
-
(lspHealth.status !== 'off' || lspHealth.reason) && React.createElement(
|
|
6536
|
-
"button",
|
|
6537
|
-
{
|
|
6538
|
-
type: "button",
|
|
6539
|
-
className: "statusbar-btn statusbar-lsp statusbar-lsp-" + lspHealth.status,
|
|
6540
|
-
onClick: restartRubyLsp,
|
|
6541
|
-
title: lspHealth.status === 'ok'
|
|
6542
|
-
? 'ruby-lsp is running — click to restart'
|
|
6543
|
-
: 'ruby-lsp unavailable' + (lspHealth.reason ? ': ' + lspHealth.reason : '') +
|
|
6544
|
-
'. Falling back to search-based lookups. Click to retry.'
|
|
6545
|
-
},
|
|
6546
|
-
React.createElement("i", {
|
|
6547
|
-
className: "fas " + (lspHealth.status === 'ok' ? 'fa-gem' : 'fa-plug'),
|
|
6548
|
-
"aria-hidden": "true"
|
|
6549
|
-
}),
|
|
6550
|
-
lspHealth.status !== 'ok' && React.createElement("span", null, " ruby-lsp")
|
|
6551
|
-
),
|
|
6552
6518
|
!serverOnline && (function () {
|
|
6553
6519
|
var dirtyCount = state.panes.reduce(function (acc, p) {
|
|
6554
6520
|
return acc + p.tabs.filter(function (t) { return t.dirty; }).length;
|