mbeditor 0.13.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +157 -1
- data/app/assets/javascripts/mbeditor/application.js +3 -1
- data/app/assets/javascripts/mbeditor/audit_log.js +165 -0
- data/app/assets/javascripts/mbeditor/collaboration_service.js +264 -22
- data/app/assets/javascripts/mbeditor/components/ChangelogView.js +89 -94
- data/app/assets/javascripts/mbeditor/components/CodeReviewPanel.js +6 -9
- data/app/assets/javascripts/mbeditor/components/CollapsibleSection.js +12 -7
- data/app/assets/javascripts/mbeditor/components/CombinedDiffViewer.js +20 -0
- data/app/assets/javascripts/mbeditor/components/DiffViewer.js +1 -1
- data/app/assets/javascripts/mbeditor/components/EditorPanel.js +440 -334
- data/app/assets/javascripts/mbeditor/components/FileHistoryPanel.js +6 -9
- data/app/assets/javascripts/mbeditor/components/FileTree.js +26 -12
- data/app/assets/javascripts/mbeditor/components/GitPanel.js +3 -0
- data/app/assets/javascripts/mbeditor/components/Gutter.js +51 -0
- data/app/assets/javascripts/mbeditor/components/ImportDialog.js +9 -1
- data/app/assets/javascripts/mbeditor/components/LogPanel.js +3 -44
- data/app/assets/javascripts/mbeditor/components/MbeditorApp.js +1322 -1431
- data/app/assets/javascripts/mbeditor/components/ModelGraph.js +94 -37
- data/app/assets/javascripts/mbeditor/components/ProblemsPanel.js +82 -72
- data/app/assets/javascripts/mbeditor/components/QuickOpenDialog.js +121 -81
- data/app/assets/javascripts/mbeditor/components/SettingsModal.js +342 -0
- data/app/assets/javascripts/mbeditor/components/ShortcutHelp.js +2 -1
- data/app/assets/javascripts/mbeditor/components/TabBar.js +67 -98
- data/app/assets/javascripts/mbeditor/editor_plugins.js +694 -306
- data/app/assets/javascripts/mbeditor/file_import.js +13 -15
- data/app/assets/javascripts/mbeditor/file_service.js +46 -57
- data/app/assets/javascripts/mbeditor/git_service.js +15 -1
- data/app/assets/javascripts/mbeditor/history_service.js +5 -13
- data/app/assets/javascripts/mbeditor/search_service.js +29 -2
- data/app/assets/javascripts/mbeditor/tab_manager.js +135 -54
- data/app/assets/javascripts/mbeditor/websocket_service.js +13 -5
- data/app/assets/stylesheets/mbeditor/application.css +6 -1
- data/app/assets/stylesheets/mbeditor/editor.css +762 -297
- data/app/assets/stylesheets/mbeditor/glass.css +163 -0
- data/app/assets/stylesheets/mbeditor/themes.css +90 -30
- data/app/channels/mbeditor/collaboration_channel.rb +25 -6
- data/app/controllers/mbeditor/application_controller.rb +26 -3
- data/app/controllers/mbeditor/editors_controller.rb +125 -465
- data/app/services/mbeditor/archive_service.rb +137 -0
- data/app/services/mbeditor/collaboration_doc_store.rb +180 -12
- data/app/services/mbeditor/duplicate_content_scanner.rb +105 -0
- data/app/services/mbeditor/editor_state_service.rb +14 -51
- data/app/services/mbeditor/file_history_service.rb +222 -0
- data/app/services/mbeditor/git_info_service.rb +6 -0
- data/app/services/mbeditor/js_globals_service.rb +12 -1
- data/app/services/mbeditor/js_syntax_check_service.rb +42 -16
- data/app/services/mbeditor/lint_service.rb +137 -0
- data/app/services/mbeditor/locked_json_file.rb +67 -0
- data/app/services/mbeditor/process_runner.rb +32 -0
- data/app/services/mbeditor/rubocop_run_service.rb +17 -5
- data/app/services/mbeditor/ruby_lsp_result_translator.rb +226 -0
- data/app/services/mbeditor/schema_service.rb +8 -2
- data/app/services/mbeditor/search_replace_service.rb +19 -2
- data/app/services/mbeditor/test_runner_service.rb +3 -56
- data/app/views/layouts/mbeditor/application.html.erb +1 -1
- data/lib/mbeditor/audit_log.rb +203 -0
- data/lib/mbeditor/configuration.rb +6 -9
- data/lib/mbeditor/rack/pending_migration_bypass.rb +15 -9
- data/lib/mbeditor/route_map.rb +4 -1
- data/lib/mbeditor/ruby_lsp_client.rb +82 -14
- data/lib/mbeditor/version.rb +1 -1
- data/lib/mbeditor.rb +1 -0
- data/lib/tasks/mbeditor.rake +23 -0
- metadata +14 -3
- data/app/assets/javascripts/mbeditor/components/TestRunPanel.js +0 -312
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
var _slicedToArray = (function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; })();
|
|
4
3
|
|
|
5
4
|
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
|
|
6
5
|
|
|
@@ -25,14 +24,23 @@ var useMemo = _React.useMemo;
|
|
|
25
24
|
// the re-render happened anyway, and the quick-open index was never rebuilt.
|
|
26
25
|
// JSON.stringify over the whole tree measures 0.33 ms for ~1600 nodes, well
|
|
27
26
|
// under the render it saves.
|
|
28
|
-
|
|
27
|
+
// newSignature lets a caller that has already stringified newData hand the
|
|
28
|
+
// result in. The tree poll needs its own changed/unchanged answer and cannot
|
|
29
|
+
// read one out of a functional setState updater, which React invokes during a
|
|
30
|
+
// later render and which has to stay pure. Without this the poll stringified
|
|
31
|
+
// the whole tree a second time on every tick, forever.
|
|
32
|
+
function _treeUpdater(newData, newSignature) {
|
|
29
33
|
return function (prevData) {
|
|
30
|
-
|
|
34
|
+
var signature = newSignature != null ? newSignature : JSON.stringify(newData);
|
|
35
|
+
if (signature === JSON.stringify(prevData)) return prevData;
|
|
31
36
|
SearchService.buildIndex(newData);
|
|
32
37
|
return newData;
|
|
33
38
|
};
|
|
34
39
|
}
|
|
35
40
|
|
|
41
|
+
// The tree poll's own view of the last tree it saw.
|
|
42
|
+
var _lastPolledTree = null;
|
|
43
|
+
|
|
36
44
|
var SIDEBAR_MIN_WIDTH = 280;
|
|
37
45
|
var SIDEBAR_MAX_WIDTH = 560;
|
|
38
46
|
var EDITOR_MIN_WIDTH = 320;
|
|
@@ -40,6 +48,7 @@ var GIT_PANEL_MIN_WIDTH = 280;
|
|
|
40
48
|
var PANE_MIN_WIDTH_PERCENT = 20;
|
|
41
49
|
var PANE_MAX_WIDTH_PERCENT = 80;
|
|
42
50
|
var SIDEBAR_COLLAPSED_WIDTH = 48;
|
|
51
|
+
var DRAWER_MIN_HEIGHT = 120;
|
|
43
52
|
// Extension -> Prettier parser. Limited to what the vendored plugins actually
|
|
44
53
|
// parse (babel, estree, html, postcss, markdown); anything outside this map
|
|
45
54
|
// falls through to Monaco's re-indent.
|
|
@@ -61,6 +70,27 @@ function prettierParserFor(path) {
|
|
|
61
70
|
return PRETTIER_PARSERS[String(path || '').split('.').pop().toLowerCase()] || null;
|
|
62
71
|
}
|
|
63
72
|
|
|
73
|
+
// The audit ring takes numbers only, so a path travels as the index of its
|
|
74
|
+
// extension in the legend and can never be reconstructed from the log.
|
|
75
|
+
function _auditExt(path) {
|
|
76
|
+
var audit = window.MbeditorAudit;
|
|
77
|
+
return audit ? audit.code('ext', String(path || '').split('.').pop().toLowerCase()) : 0;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Named rather than passed an EV code so callers need no audit reference.
|
|
81
|
+
function _recErr(evName) {
|
|
82
|
+
var audit = window.MbeditorAudit;
|
|
83
|
+
if (audit) audit.rec(audit.EV.ERR, audit.EV[evName]);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// null means no formatter covers this file type, which is not a round trip.
|
|
87
|
+
function _recFormat(path, startedAt, formatted) {
|
|
88
|
+
var audit = window.MbeditorAudit;
|
|
89
|
+
if (audit && formatted != null) {
|
|
90
|
+
audit.rec(audit.EV.FORMAT, _auditExt(path), Date.now() - startedAt, formatted.length);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
64
94
|
// Indentation comes from the editor's own tabSize/insertSpaces, so formatting
|
|
65
95
|
// produces what the user set up. `insertSpaces !== true` mirrors how
|
|
66
96
|
// EditorPanel configures Monaco: anything but an explicit true means tabs.
|
|
@@ -116,7 +146,8 @@ var DEFAULT_EDITOR_PREFS = {
|
|
|
116
146
|
wordBasedSuggestions: 'matchingDocuments',
|
|
117
147
|
acceptSuggestionOnEnter: 'on',
|
|
118
148
|
autoRevealInExplorer: true,
|
|
119
|
-
|
|
149
|
+
toolbarLabels: false,
|
|
150
|
+
glass: false,
|
|
120
151
|
rubocopLintEnabled: true,
|
|
121
152
|
routeHints: true,
|
|
122
153
|
prettierPrintWidth: 80,
|
|
@@ -130,7 +161,8 @@ var DEFAULT_EDITOR_PREFS = {
|
|
|
130
161
|
tabDisplayMode: 'scroll',
|
|
131
162
|
persistFindState: true,
|
|
132
163
|
showDotFiles: false,
|
|
133
|
-
branchStateRestore: true
|
|
164
|
+
branchStateRestore: true,
|
|
165
|
+
auditLog: true
|
|
134
166
|
};
|
|
135
167
|
|
|
136
168
|
// Indentation of formatted output is the formatter's job, not a post-pass:
|
|
@@ -139,6 +171,52 @@ var DEFAULT_EDITOR_PREFS = {
|
|
|
139
171
|
// "Convert Indentation to Tabs / to Spaces" commands (F1) cover converting a
|
|
140
172
|
// file that is already open, using its own indentation guesser.
|
|
141
173
|
|
|
174
|
+
// Drops both rings — the browser's and the server's — so the next download is
|
|
175
|
+
// a trace of one session and nothing else.
|
|
176
|
+
function clearAuditLog() {
|
|
177
|
+
if (window.MbeditorAudit) window.MbeditorAudit.clear();
|
|
178
|
+
fetch(window.mbeditorBasePath() + '/audit_log', {
|
|
179
|
+
method: 'DELETE',
|
|
180
|
+
headers: { 'X-Mbeditor-Client': '1' }
|
|
181
|
+
}).then(function () {
|
|
182
|
+
EditorStore.setStatus('Audit log cleared', 'info');
|
|
183
|
+
})["catch"](function () {
|
|
184
|
+
EditorStore.setStatus('Could not clear the audit log', 'error');
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Flush first, so the entries recorded in this session are on the server
|
|
189
|
+
// before it builds the payload.
|
|
190
|
+
function downloadAuditLog() {
|
|
191
|
+
var fetchLog = function () {
|
|
192
|
+
return fetch(window.mbeditorBasePath() + '/audit_log').then(function (resp) {
|
|
193
|
+
if (!resp.ok) throw new Error('HTTP ' + resp.status);
|
|
194
|
+
return resp.json();
|
|
195
|
+
}).then(function (payload) {
|
|
196
|
+
// The legend lives only in the browser bundle, so it is stitched on here
|
|
197
|
+
// rather than uploaded — see audit_log.js.
|
|
198
|
+
if (payload.client && window.MbeditorAudit) payload.client.legend = window.MbeditorAudit.LEGEND;
|
|
199
|
+
var blob = new Blob([JSON.stringify(payload, null, 2)], { type: 'application/json' });
|
|
200
|
+
var objectUrl = URL.createObjectURL(blob);
|
|
201
|
+
var link = document.createElement('a');
|
|
202
|
+
link.href = objectUrl;
|
|
203
|
+
link.download = 'mbeditor-audit-' + Date.now() + '.json';
|
|
204
|
+
link.rel = 'noopener';
|
|
205
|
+
document.body.appendChild(link);
|
|
206
|
+
link.click();
|
|
207
|
+
document.body.removeChild(link);
|
|
208
|
+
// Revoking in the same tick can free the blob before the browser has
|
|
209
|
+
// started reading it, which cancels the download instead of saving it.
|
|
210
|
+
setTimeout(function () { URL.revokeObjectURL(objectUrl); }, 60000);
|
|
211
|
+
EditorStore.setStatus('Downloading audit log...', 'info');
|
|
212
|
+
});
|
|
213
|
+
};
|
|
214
|
+
var flushed = window.MbeditorAudit ? window.MbeditorAudit.flush() : Promise.resolve();
|
|
215
|
+
flushed.then(fetchLog, fetchLog)["catch"](function (err) {
|
|
216
|
+
EditorStore.setStatus('Audit log download failed: ' + (err && err.message || 'network error'), 'error');
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
142
220
|
function diffLines(oldLines, newLines) {
|
|
143
221
|
var n = oldLines.length, m = newLines.length;
|
|
144
222
|
var dp = [];
|
|
@@ -157,6 +235,46 @@ function diffLines(oldLines, newLines) {
|
|
|
157
235
|
return changed;
|
|
158
236
|
}
|
|
159
237
|
|
|
238
|
+
// Cursor line/column for the status bar.
|
|
239
|
+
//
|
|
240
|
+
// Its own component holding its own state, deliberately: the cursor moves on
|
|
241
|
+
// every keystroke and every click, and MbeditorApp subscribes to the whole
|
|
242
|
+
// store — reading the position there would re-render the entire app, tree and
|
|
243
|
+
// all, on each keypress. Here a move re-renders one span.
|
|
244
|
+
var CursorPosition = function CursorPosition() {
|
|
245
|
+
var _pos = React.useState(null);
|
|
246
|
+
var pos = _pos[0];
|
|
247
|
+
var setPos = _pos[1];
|
|
248
|
+
|
|
249
|
+
React.useEffect(function () {
|
|
250
|
+
var disposable = null;
|
|
251
|
+
var attach = function () {
|
|
252
|
+
if (disposable) { disposable.dispose(); disposable = null; }
|
|
253
|
+
var editor = window.__mbeditorActiveEditor;
|
|
254
|
+
if (!editor) { setPos(null); return; }
|
|
255
|
+
var read = function () {
|
|
256
|
+
var p = editor.getPosition();
|
|
257
|
+
setPos(p ? { line: p.lineNumber, col: p.column } : null);
|
|
258
|
+
};
|
|
259
|
+
read();
|
|
260
|
+
disposable = editor.onDidChangeCursorPosition(read);
|
|
261
|
+
};
|
|
262
|
+
attach();
|
|
263
|
+
window.addEventListener('mbeditor:active-editor', attach);
|
|
264
|
+
return function () {
|
|
265
|
+
window.removeEventListener('mbeditor:active-editor', attach);
|
|
266
|
+
if (disposable) disposable.dispose();
|
|
267
|
+
};
|
|
268
|
+
}, []);
|
|
269
|
+
|
|
270
|
+
if (!pos) return null;
|
|
271
|
+
return React.createElement(
|
|
272
|
+
'span',
|
|
273
|
+
{ className: 'statusbar-cursor', title: 'Line ' + pos.line + ', column ' + pos.col },
|
|
274
|
+
'Ln ' + pos.line + ', Col ' + pos.col
|
|
275
|
+
);
|
|
276
|
+
};
|
|
277
|
+
|
|
160
278
|
var SidebarActionButton = function SidebarActionButton(_ref) {
|
|
161
279
|
var title = _ref.title;
|
|
162
280
|
var iconClass = _ref.iconClass;
|
|
@@ -191,7 +309,13 @@ var SidebarActionButton = function SidebarActionButton(_ref) {
|
|
|
191
309
|
className: "project-action-btn" + (danger ? " danger" : ""),
|
|
192
310
|
"aria-label": ariaLabel || title,
|
|
193
311
|
"aria-busy": !!ariaBusy,
|
|
194
|
-
onClick:
|
|
312
|
+
onClick: function (e) {
|
|
313
|
+
if (onClick) onClick(e);
|
|
314
|
+
// Clicking leaves the button focused, which keeps the :focus-within
|
|
315
|
+
// tooltip open until the user clicks elsewhere. Blur to drop it
|
|
316
|
+
// immediately; a Tab-focused button still shows it on keyboard focus.
|
|
317
|
+
e.currentTarget.blur();
|
|
318
|
+
},
|
|
195
319
|
disabled: !!disabled
|
|
196
320
|
},
|
|
197
321
|
!ariaBusy && React.createElement("i", { className: iconClass })
|
|
@@ -216,6 +340,23 @@ var SectionActionGroup = function SectionActionGroup(_ref2) {
|
|
|
216
340
|
);
|
|
217
341
|
};
|
|
218
342
|
|
|
343
|
+
// Split a search hit into [before, match, after] so the match can be tinted
|
|
344
|
+
// and pinned on screen. `col`/`end_col` are 1-based against the RAW line while
|
|
345
|
+
// the row renders the stripped `text`, so `lead` — the characters strip took
|
|
346
|
+
// off the front — is what maps one onto the other. Returns null for the tiers
|
|
347
|
+
// and queries that produce no columns; the row then renders as plain text.
|
|
348
|
+
function searchMatchParts(res) {
|
|
349
|
+
var text = res.text == null ? "" : String(res.text);
|
|
350
|
+
if (!res.col || !res.end_col) return null;
|
|
351
|
+
var start = res.col - 1 - (res.lead || 0);
|
|
352
|
+
var end = Math.min(res.end_col - 1 - (res.lead || 0), text.length);
|
|
353
|
+
if (!(start >= 0 && end > start && start < text.length)) return null;
|
|
354
|
+
// U+200E: a strong LTR character, so the left-ellipsis trick in
|
|
355
|
+
// .search-result-pre (direction: rtl) can never reorder a segment that
|
|
356
|
+
// happens to be all punctuation.
|
|
357
|
+
return ["" + text.slice(0, start), text.slice(start, end), text.slice(end)];
|
|
358
|
+
}
|
|
359
|
+
|
|
219
360
|
function FileReloadBanner(_ref) {
|
|
220
361
|
var pendingReloads = _ref.pendingReloads;
|
|
221
362
|
var onSaveAndReload = _ref.onSaveAndReload;
|
|
@@ -254,107 +395,126 @@ function FileReloadBanner(_ref) {
|
|
|
254
395
|
);
|
|
255
396
|
}
|
|
256
397
|
|
|
398
|
+
// ── Test-result cache (localStorage) ────────────────────────────────────
|
|
399
|
+
// One entry per test file, capped so an unbounded suite can't fill the
|
|
400
|
+
// origin's quota and take the unsaved-draft backup down with it (#84).
|
|
401
|
+
var TEST_CACHE_PREFIX = 'mbeditor_test_result_';
|
|
402
|
+
var TEST_CACHE_MAX_ENTRIES = 50;
|
|
403
|
+
|
|
404
|
+
// ponytail: full localStorage scan per save, fine at a 50-entry cap.
|
|
405
|
+
function _trimTestResultCache(maxEntries) {
|
|
406
|
+
var entries = [];
|
|
407
|
+
for (var i = 0; i < localStorage.length; i++) {
|
|
408
|
+
var key = localStorage.key(i);
|
|
409
|
+
if (key && key.indexOf(TEST_CACHE_PREFIX) === 0) {
|
|
410
|
+
var ts = 0;
|
|
411
|
+
try { ts = (JSON.parse(localStorage.getItem(key)) || {}).ts || 0; } catch (e) {}
|
|
412
|
+
entries.push({ key: key, ts: ts });
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
entries.sort(function (a, b) { return a.ts - b.ts; });
|
|
416
|
+
while (entries.length > maxEntries) localStorage.removeItem(entries.shift().key);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function saveCachedTestResult(filePath, result) {
|
|
420
|
+
var payload = JSON.stringify(_extends({}, result, { ts: Date.now() }));
|
|
421
|
+
try {
|
|
422
|
+
localStorage.setItem(TEST_CACHE_PREFIX + filePath, payload);
|
|
423
|
+
} catch (e) {
|
|
424
|
+
_trimTestResultCache(Math.floor(TEST_CACHE_MAX_ENTRIES / 2));
|
|
425
|
+
try { localStorage.setItem(TEST_CACHE_PREFIX + filePath, payload); } catch (e2) { return; }
|
|
426
|
+
}
|
|
427
|
+
_trimTestResultCache(TEST_CACHE_MAX_ENTRIES);
|
|
428
|
+
}
|
|
429
|
+
|
|
257
430
|
var MbeditorApp = function MbeditorApp() {
|
|
258
431
|
var _useState = useState(EditorStore.getState());
|
|
259
432
|
|
|
260
|
-
var _useState2 = _slicedToArray(_useState, 2);
|
|
261
433
|
|
|
262
|
-
var state =
|
|
263
|
-
var setState =
|
|
434
|
+
var state = _useState[0];
|
|
435
|
+
var setState = _useState[1];
|
|
264
436
|
|
|
265
437
|
var _useState21 = useState(null);
|
|
266
|
-
var
|
|
267
|
-
var
|
|
268
|
-
var setHistoryPanelPath = _useState22[1];
|
|
438
|
+
var historyPanelPath = _useState21[0];
|
|
439
|
+
var setHistoryPanelPath = _useState21[1];
|
|
269
440
|
|
|
270
441
|
var _useState23 = useState(false);
|
|
271
|
-
var
|
|
272
|
-
var
|
|
273
|
-
var setIsNavigating = _useState24[1];
|
|
442
|
+
var isNavigating = _useState23[0];
|
|
443
|
+
var setIsNavigating = _useState23[1];
|
|
274
444
|
|
|
275
445
|
var _useState25 = useState(false);
|
|
276
|
-
var
|
|
277
|
-
var
|
|
278
|
-
var setIsReviewOpen = _useState26[1];
|
|
446
|
+
var isReviewOpen = _useState25[0];
|
|
447
|
+
var setIsReviewOpen = _useState25[1];
|
|
279
448
|
|
|
280
449
|
var _useState27 = useState(null);
|
|
281
|
-
var
|
|
282
|
-
var
|
|
283
|
-
var setSelectedCommit = _useState28[1];
|
|
450
|
+
var selectedCommit = _useState27[0];
|
|
451
|
+
var setSelectedCommit = _useState27[1];
|
|
284
452
|
|
|
285
453
|
var _useState29 = useState(null);
|
|
286
|
-
var
|
|
287
|
-
var
|
|
288
|
-
|
|
454
|
+
var commitDetailFiles = _useState29[0];
|
|
455
|
+
var setCommitDetailFiles = _useState29[1];
|
|
456
|
+
|
|
457
|
+
var _useStateCFI = useState(0);
|
|
458
|
+
var conflictFileIndex = _useStateCFI[0];
|
|
459
|
+
var setConflictFileIndex = _useStateCFI[1];
|
|
289
460
|
|
|
290
461
|
var _useState4 = useState([]);
|
|
291
462
|
|
|
292
|
-
var _useState42 = _slicedToArray(_useState4, 2);
|
|
293
463
|
|
|
294
|
-
var treeData =
|
|
295
|
-
var setTreeData =
|
|
464
|
+
var treeData = _useState4[0];
|
|
465
|
+
var setTreeData = _useState4[1];
|
|
296
466
|
|
|
297
467
|
var _useState5 = useState("");
|
|
298
468
|
|
|
299
|
-
var _useState52 = _slicedToArray(_useState5, 2);
|
|
300
469
|
|
|
301
|
-
var projectRootName =
|
|
302
|
-
var setProjectRootName =
|
|
470
|
+
var projectRootName = _useState5[0];
|
|
471
|
+
var setProjectRootName = _useState5[1];
|
|
303
472
|
|
|
304
473
|
var _useState6 = useState(null);
|
|
305
474
|
|
|
306
|
-
var _useState62 = _slicedToArray(_useState6, 2);
|
|
307
475
|
|
|
308
|
-
var selectedTreeNode =
|
|
309
|
-
var setSelectedTreeNode =
|
|
476
|
+
var selectedTreeNode = _useState6[0];
|
|
477
|
+
var setSelectedTreeNode = _useState6[1];
|
|
310
478
|
|
|
311
479
|
var _useStateSP = useState(new Set());
|
|
312
|
-
var
|
|
313
|
-
var
|
|
314
|
-
var setSelectedPaths = _useStateSP2[1];
|
|
480
|
+
var selectedPaths = _useStateSP[0];
|
|
481
|
+
var setSelectedPaths = _useStateSP[1];
|
|
315
482
|
|
|
316
483
|
var _useState7 = useState("");
|
|
317
484
|
|
|
318
|
-
var _useState72 = _slicedToArray(_useState7, 2);
|
|
319
485
|
|
|
320
|
-
var searchQuery =
|
|
321
|
-
var setSearchQuery =
|
|
486
|
+
var searchQuery = _useState7[0];
|
|
487
|
+
var setSearchQuery = _useState7[1];
|
|
322
488
|
|
|
323
489
|
var _useState33 = useState(false);
|
|
324
|
-
var
|
|
325
|
-
var
|
|
326
|
-
var setSearchLoading = _useState332[1];
|
|
490
|
+
var searchLoading = _useState33[0];
|
|
491
|
+
var setSearchLoading = _useState33[1];
|
|
327
492
|
|
|
328
493
|
var searchRequestIdRef = useRef(0);
|
|
329
494
|
|
|
330
495
|
var _useState33h = useState(false);
|
|
331
|
-
var
|
|
332
|
-
var
|
|
333
|
-
var setSearchHasMore = _useState33h2[1];
|
|
496
|
+
var searchHasMore = _useState33h[0];
|
|
497
|
+
var setSearchHasMore = _useState33h[1];
|
|
334
498
|
|
|
335
499
|
var _useState33tc = useState(0);
|
|
336
|
-
var
|
|
337
|
-
var
|
|
338
|
-
var setSearchTotalCount = _useState33tc2[1];
|
|
500
|
+
var searchTotalCount = _useState33tc[0];
|
|
501
|
+
var setSearchTotalCount = _useState33tc[1];
|
|
339
502
|
|
|
340
503
|
var searchHasMoreRef = useRef(false);
|
|
341
504
|
var searchOffsetRef = useRef(0);
|
|
342
505
|
var searchLoadingMoreRef = useRef(false);
|
|
343
506
|
|
|
344
507
|
var _useStateRx = useState(false);
|
|
345
|
-
var
|
|
346
|
-
var
|
|
347
|
-
var setSearchUseRegex = _useStateRx2[1];
|
|
508
|
+
var searchUseRegex = _useStateRx[0];
|
|
509
|
+
var setSearchUseRegex = _useStateRx[1];
|
|
348
510
|
|
|
349
511
|
var _useStateMC = useState(false);
|
|
350
|
-
var
|
|
351
|
-
var
|
|
352
|
-
var setSearchMatchCase = _useStateMC2[1];
|
|
512
|
+
var searchMatchCase = _useStateMC[0];
|
|
513
|
+
var setSearchMatchCase = _useStateMC[1];
|
|
353
514
|
|
|
354
515
|
var _useStateWW = useState(false);
|
|
355
|
-
var
|
|
356
|
-
var
|
|
357
|
-
var setSearchWholeWord = _useStateWW2[1];
|
|
516
|
+
var searchWholeWord = _useStateWW[0];
|
|
517
|
+
var setSearchWholeWord = _useStateWW[1];
|
|
358
518
|
|
|
359
519
|
var searchQueryRef = useRef('');
|
|
360
520
|
var searchUseRegexRef = useRef(false);
|
|
@@ -366,14 +526,30 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
366
526
|
// SearchReplaceService::MAX_RESULTS (10_000) rows, and every row is five
|
|
367
527
|
// elements, so rendering the list in full built ~50k nodes — enough to kill
|
|
368
528
|
// the tab outright, and 92 ms of render for a mere 3_000 rows. Rows are a
|
|
369
|
-
// fixed
|
|
370
|
-
//
|
|
371
|
-
|
|
529
|
+
// fixed 22px, which is what makes the arithmetic here as simple as the file
|
|
530
|
+
// tree's. The results are a VS Code-style tree — a header row per file, its
|
|
531
|
+
// matches nested under it — but header and match rows are deliberately the
|
|
532
|
+
// *same* height (.search-result-file-row and .search-result-item both pin
|
|
533
|
+
// it), so the flattened row array still windows by plain multiplication.
|
|
534
|
+
// Give the two rows different heights and every offset here is wrong.
|
|
535
|
+
var SEARCH_ROW_HEIGHT = 22;
|
|
372
536
|
var SEARCH_ROW_BUFFER = 5;
|
|
537
|
+
|
|
538
|
+
// File paths whose match list is folded away. Keyed by path, so a file that
|
|
539
|
+
// scrolls out of the window keeps its state.
|
|
540
|
+
var _useStateSC = useState({});
|
|
541
|
+
var searchCollapsedFiles = _useStateSC[0];
|
|
542
|
+
var setSearchCollapsedFiles = _useStateSC[1];
|
|
543
|
+
var toggleSearchFile = function toggleSearchFile(file) {
|
|
544
|
+
setSearchCollapsedFiles(function (prev) {
|
|
545
|
+
var next = Object.assign({}, prev);
|
|
546
|
+
if (next[file]) delete next[file]; else next[file] = true;
|
|
547
|
+
return next;
|
|
548
|
+
});
|
|
549
|
+
};
|
|
373
550
|
var _useStateSV = useState({ scrollTop: 0, height: 0 });
|
|
374
|
-
var
|
|
375
|
-
var
|
|
376
|
-
var setSearchViewport = _useStateSV2[1];
|
|
551
|
+
var searchViewport = _useStateSV[0];
|
|
552
|
+
var setSearchViewport = _useStateSV[1];
|
|
377
553
|
|
|
378
554
|
// The container's height is only known once it is on screen, and a viewport
|
|
379
555
|
// of 0 would render a single row and never grow — nothing would scroll, so
|
|
@@ -406,31 +582,26 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
406
582
|
}).current;
|
|
407
583
|
|
|
408
584
|
var _useStateRM = useState(false);
|
|
409
|
-
var
|
|
410
|
-
var
|
|
411
|
-
var setReplaceMode = _useStateRM2[1];
|
|
585
|
+
var replaceMode = _useStateRM[0];
|
|
586
|
+
var setReplaceMode = _useStateRM[1];
|
|
412
587
|
|
|
413
588
|
var _useStateRQ = useState('');
|
|
414
|
-
var
|
|
415
|
-
var
|
|
416
|
-
var setReplaceQuery = _useStateRQ2[1];
|
|
589
|
+
var replaceQuery = _useStateRQ[0];
|
|
590
|
+
var setReplaceQuery = _useStateRQ[1];
|
|
417
591
|
|
|
418
592
|
var _useStateRL = useState(false);
|
|
419
|
-
var
|
|
420
|
-
var
|
|
421
|
-
var setReplaceLoading = _useStateRL2[1];
|
|
593
|
+
var replaceLoading = _useStateRL[0];
|
|
594
|
+
var setReplaceLoading = _useStateRL[1];
|
|
422
595
|
|
|
423
596
|
var _useState8 = useState("explorer");
|
|
424
597
|
|
|
425
|
-
var _useState82 = _slicedToArray(_useState8, 2);
|
|
426
598
|
|
|
427
|
-
var activeSidebarTab =
|
|
428
|
-
var setActiveSidebarTab =
|
|
599
|
+
var activeSidebarTab = _useState8[0];
|
|
600
|
+
var setActiveSidebarTab = _useState8[1];
|
|
429
601
|
|
|
430
602
|
var _useStateSC = useState(false);
|
|
431
|
-
var
|
|
432
|
-
var
|
|
433
|
-
var setSidebarCollapsed = _useStateSC2[1];
|
|
603
|
+
var sidebarCollapsed = _useStateSC[0];
|
|
604
|
+
var setSidebarCollapsed = _useStateSC[1];
|
|
434
605
|
|
|
435
606
|
// Ref mirrors for use inside long-lived event handlers registered with
|
|
436
607
|
// empty-dependency effects (live search refresh on files_changed).
|
|
@@ -442,135 +613,110 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
442
613
|
var jsSyntaxCheckAvailableRef = useRef(false);
|
|
443
614
|
|
|
444
615
|
var _useStateRFMap = useState({});
|
|
445
|
-
var
|
|
446
|
-
var
|
|
447
|
-
var setRailsFilesMap = _useStateRFMap2[1];
|
|
616
|
+
var railsFilesMap = _useStateRFMap[0];
|
|
617
|
+
var setRailsFilesMap = _useStateRFMap[1];
|
|
448
618
|
|
|
449
619
|
var _useStateRFC = useState({});
|
|
450
|
-
var
|
|
451
|
-
var
|
|
452
|
-
var setRailsGroupsCollapsed = _useStateRFC2[1];
|
|
620
|
+
var railsGroupsCollapsed = _useStateRFC[0];
|
|
621
|
+
var setRailsGroupsCollapsed = _useStateRFC[1];
|
|
453
622
|
|
|
454
623
|
var _useStateChangelog = useState(null); // null | { content, loading, error }
|
|
455
|
-
var
|
|
456
|
-
var
|
|
457
|
-
var setChangelogState = _useStateChangelog2[1];
|
|
624
|
+
var changelogState = _useStateChangelog[0];
|
|
625
|
+
var setChangelogState = _useStateChangelog[1];
|
|
458
626
|
|
|
459
627
|
var _useStateSchemaModal = useState(null);
|
|
460
|
-
var
|
|
461
|
-
var
|
|
462
|
-
var setSchemaModal = _useStateSchemaModal2[1];
|
|
628
|
+
var schemaModal = _useStateSchemaModal[0];
|
|
629
|
+
var setSchemaModal = _useStateSchemaModal[1];
|
|
463
630
|
|
|
464
631
|
var _useStateImportConflict = useState(null);
|
|
465
|
-
var
|
|
466
|
-
var
|
|
467
|
-
var setImportConflict = _useStateImportConflict2[1];
|
|
632
|
+
var importConflict = _useStateImportConflict[0];
|
|
633
|
+
var setImportConflict = _useStateImportConflict[1];
|
|
468
634
|
|
|
469
635
|
// { initialFolder } while the upload dialog is open, null otherwise.
|
|
470
636
|
var _useStateImportDialog = useState(null);
|
|
471
|
-
var
|
|
472
|
-
var
|
|
473
|
-
var setImportDialog = _useStateImportDialog2[1];
|
|
637
|
+
var importDialog = _useStateImportDialog[0];
|
|
638
|
+
var setImportDialog = _useStateImportDialog[1];
|
|
474
639
|
|
|
475
640
|
var _useStateSchemaLoading = useState(null);
|
|
476
|
-
var
|
|
477
|
-
var
|
|
478
|
-
var setSchemaLoadingLabel = _useStateSchemaLoading2[1];
|
|
641
|
+
var schemaLoadingLabel = _useStateSchemaLoading[0];
|
|
642
|
+
var setSchemaLoadingLabel = _useStateSchemaLoading[1];
|
|
479
643
|
|
|
480
644
|
var _useState9 = useState({});
|
|
481
645
|
|
|
482
|
-
var _useState92 = _slicedToArray(_useState9, 2);
|
|
483
646
|
|
|
484
|
-
var markers =
|
|
485
|
-
var setMarkers =
|
|
647
|
+
var markers = _useState9[0];
|
|
648
|
+
var setMarkers = _useState9[1];
|
|
486
649
|
// { tabId: [] }
|
|
487
650
|
|
|
488
651
|
var _useState10 = useState({});
|
|
489
652
|
|
|
490
|
-
var _useState102 = _slicedToArray(_useState10, 2);
|
|
491
653
|
|
|
492
|
-
var loading =
|
|
493
|
-
var setLoading =
|
|
654
|
+
var loading = _useState10[0];
|
|
655
|
+
var setLoading = _useState10[1];
|
|
494
656
|
|
|
495
657
|
var _useState11 = useState(null);
|
|
496
658
|
|
|
497
|
-
var _useState112 = _slicedToArray(_useState11, 2);
|
|
498
659
|
|
|
499
|
-
var closingTabId =
|
|
500
|
-
var setClosingTabId =
|
|
660
|
+
var closingTabId = _useState11[0];
|
|
661
|
+
var setClosingTabId = _useState11[1];
|
|
501
662
|
|
|
502
663
|
var _useState12 = useState(null);
|
|
503
664
|
|
|
504
|
-
var _useState122 = _slicedToArray(_useState12, 2);
|
|
505
665
|
|
|
506
|
-
var closingPaneId =
|
|
507
|
-
var setClosingPaneId =
|
|
666
|
+
var closingPaneId = _useState12[0];
|
|
667
|
+
var setClosingPaneId = _useState12[1];
|
|
508
668
|
|
|
509
669
|
var _useState13 = useState(SIDEBAR_MIN_WIDTH);
|
|
510
670
|
|
|
511
|
-
var _useState132 = _slicedToArray(_useState13, 2);
|
|
512
671
|
|
|
513
|
-
var sidebarWidth =
|
|
514
|
-
var setSidebarWidth =
|
|
672
|
+
var sidebarWidth = _useState13[0];
|
|
673
|
+
var setSidebarWidth = _useState13[1];
|
|
515
674
|
|
|
516
675
|
var _useState14 = useState(50);
|
|
517
676
|
|
|
518
|
-
var _useState142 = _slicedToArray(_useState14, 2);
|
|
519
677
|
|
|
520
|
-
var pane1Width =
|
|
521
|
-
var setPane1Width =
|
|
678
|
+
var pane1Width = _useState14[0];
|
|
679
|
+
var setPane1Width = _useState14[1];
|
|
522
680
|
// percentage
|
|
523
681
|
|
|
524
682
|
var dragSplitWidthRef = useRef(pane1Width);
|
|
525
683
|
|
|
526
684
|
var _useState15 = useState(null);
|
|
527
685
|
|
|
528
|
-
var _useState152 = _slicedToArray(_useState15, 2);
|
|
529
686
|
|
|
530
|
-
var activeResizeMode =
|
|
531
|
-
var setActiveResizeMode =
|
|
687
|
+
var activeResizeMode = _useState15[0];
|
|
688
|
+
var setActiveResizeMode = _useState15[1];
|
|
532
689
|
|
|
533
690
|
var _useState16 = useState(null);
|
|
534
691
|
|
|
535
|
-
var _useState162 = _slicedToArray(_useState16, 2);
|
|
536
692
|
|
|
537
|
-
var draggedTab =
|
|
538
|
-
var setDraggedTab =
|
|
693
|
+
var draggedTab = _useState16[0];
|
|
694
|
+
var setDraggedTab = _useState16[1];
|
|
539
695
|
|
|
540
696
|
var _useState17 = useState(null);
|
|
541
697
|
|
|
542
|
-
var _useState172 = _slicedToArray(_useState17, 2);
|
|
543
698
|
|
|
544
|
-
var dragOverPaneId =
|
|
545
|
-
var setDragOverPaneId =
|
|
699
|
+
var dragOverPaneId = _useState17[0];
|
|
700
|
+
var setDragOverPaneId = _useState17[1];
|
|
546
701
|
|
|
547
702
|
var _useState18 = useState(false);
|
|
548
|
-
var
|
|
549
|
-
var
|
|
550
|
-
var setShowGitPanel = _useState182[1];
|
|
703
|
+
var showGitPanel = _useState18[0];
|
|
704
|
+
var setShowGitPanel = _useState18[1];
|
|
551
705
|
var showGitPanelRef = useRef(showGitPanel);
|
|
552
706
|
showGitPanelRef.current = showGitPanel;
|
|
553
707
|
|
|
554
708
|
var _useStateLog = useState(false);
|
|
555
|
-
var
|
|
556
|
-
var
|
|
557
|
-
var setShowLogPanel = _useStateLog2[1];
|
|
709
|
+
var showLogPanel = _useStateLog[0];
|
|
710
|
+
var setShowLogPanel = _useStateLog[1];
|
|
558
711
|
|
|
559
712
|
var _useStateProblems = useState(false);
|
|
560
|
-
var
|
|
561
|
-
var
|
|
562
|
-
var setShowProblemsPanel = _useStateProblems2[1];
|
|
563
|
-
|
|
564
|
-
var _useStateTestRun = useState(false);
|
|
565
|
-
var _useStateTestRun2 = _slicedToArray(_useStateTestRun, 2);
|
|
566
|
-
var showTestRunPanel = _useStateTestRun2[0];
|
|
567
|
-
var setShowTestRunPanel = _useStateTestRun2[1];
|
|
713
|
+
var showProblemsPanel = _useStateProblems[0];
|
|
714
|
+
var setShowProblemsPanel = _useStateProblems[1];
|
|
568
715
|
|
|
569
716
|
// Error/warning tallies across the open tabs, mirrored into the status bar.
|
|
570
717
|
var _useStateProblemCounts = useState({ errors: 0, warnings: 0 });
|
|
571
|
-
var
|
|
572
|
-
var
|
|
573
|
-
var setProblemCounts = _useStateProblemCounts2[1];
|
|
718
|
+
var problemCounts = _useStateProblemCounts[0];
|
|
719
|
+
var setProblemCounts = _useStateProblemCounts[1];
|
|
574
720
|
|
|
575
721
|
// Below this the toolbar's labelled buttons no longer fit beside the title
|
|
576
722
|
// and the file search, and start pushing each other out of the bar.
|
|
@@ -582,9 +728,8 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
582
728
|
return typeof window.matchMedia === 'function' &&
|
|
583
729
|
window.matchMedia('(max-width: ' + TOOLBAR_LABEL_MIN_WIDTH + 'px)').matches;
|
|
584
730
|
});
|
|
585
|
-
var
|
|
586
|
-
var
|
|
587
|
-
var setNarrowToolbar = _useStateNarrow2[1];
|
|
731
|
+
var narrowToolbar = _useStateNarrow[0];
|
|
732
|
+
var setNarrowToolbar = _useStateNarrow[1];
|
|
588
733
|
|
|
589
734
|
useEffect(function () {
|
|
590
735
|
if (typeof window.matchMedia !== 'function') return;
|
|
@@ -604,158 +749,183 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
604
749
|
// Model graph. Built lazily — generating it eager-loads the host app — and
|
|
605
750
|
// only when the Models tab is opened.
|
|
606
751
|
var _useStateModelGraph = useState(null);
|
|
607
|
-
var
|
|
608
|
-
var
|
|
609
|
-
var setModelGraph = _useStateModelGraph2[1];
|
|
752
|
+
var modelGraph = _useStateModelGraph[0];
|
|
753
|
+
var setModelGraph = _useStateModelGraph[1];
|
|
610
754
|
|
|
611
755
|
var _useStateModelGraphLoading = useState(false);
|
|
612
|
-
var
|
|
613
|
-
var
|
|
614
|
-
var setModelGraphLoading = _useStateModelGraphLoading2[1];
|
|
615
|
-
|
|
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];
|
|
756
|
+
var modelGraphLoading = _useStateModelGraphLoading[0];
|
|
757
|
+
var setModelGraphLoading = _useStateModelGraphLoading[1];
|
|
622
758
|
|
|
623
759
|
var _useState18g = useState(320);
|
|
624
|
-
var
|
|
625
|
-
var
|
|
626
|
-
var setGitPanelWidth = _useState18g2[1];
|
|
760
|
+
var gitPanelWidth = _useState18g[0];
|
|
761
|
+
var setGitPanelWidth = _useState18g[1];
|
|
627
762
|
var gitPanelWidthRef = useRef(gitPanelWidth);
|
|
628
763
|
gitPanelWidthRef.current = gitPanelWidth;
|
|
629
764
|
|
|
630
765
|
var _useState18h = useState(false);
|
|
631
766
|
|
|
632
|
-
var _useState18h2 = _slicedToArray(_useState18h, 2);
|
|
633
767
|
|
|
634
|
-
var showHelp =
|
|
635
|
-
var setShowHelp =
|
|
768
|
+
var showHelp = _useState18h[0];
|
|
769
|
+
var setShowHelp = _useState18h[1];
|
|
770
|
+
|
|
771
|
+
var _useState18s = useState(false);
|
|
772
|
+
var showSettings = _useState18s[0];
|
|
773
|
+
var setShowSettings = _useState18s[1];
|
|
636
774
|
|
|
637
775
|
var _useStatePwa = useState(null);
|
|
638
|
-
var
|
|
639
|
-
var
|
|
640
|
-
var setPwaInstallPrompt = _useStatePwa2[1];
|
|
776
|
+
var pwaInstallPrompt = _useStatePwa[0];
|
|
777
|
+
var setPwaInstallPrompt = _useStatePwa[1];
|
|
641
778
|
|
|
642
779
|
var _useState18b = useState(true);
|
|
643
780
|
|
|
644
|
-
var _useState18b2 = _slicedToArray(_useState18b, 2);
|
|
645
781
|
|
|
646
|
-
var serverOnline =
|
|
647
|
-
var setServerOnline =
|
|
782
|
+
var serverOnline = _useState18b[0];
|
|
783
|
+
var setServerOnline = _useState18b[1];
|
|
784
|
+
|
|
785
|
+
// Pending migrations. file_service.js owns the truth (it is what sees the
|
|
786
|
+
// response header) and publishes it as a <body> class plus an event; this
|
|
787
|
+
// only mirrors it into the status bar.
|
|
788
|
+
var _useStateMig = useState(function () {
|
|
789
|
+
return document.body.classList.contains('mbeditor-migration-pending');
|
|
790
|
+
});
|
|
791
|
+
var migrationsPending = _useStateMig[0];
|
|
792
|
+
var setMigrationsPending = _useStateMig[1];
|
|
793
|
+
|
|
794
|
+
useEffect(function () {
|
|
795
|
+
function onChange(e) { setMigrationsPending(!!e.detail); }
|
|
796
|
+
window.addEventListener('mbeditor:pending-migration', onChange);
|
|
797
|
+
return function () { window.removeEventListener('mbeditor:pending-migration', onChange); };
|
|
798
|
+
}, []);
|
|
648
799
|
|
|
649
800
|
var _useState18c = useState(false);
|
|
650
801
|
|
|
651
|
-
var _useState18c2 = _slicedToArray(_useState18c, 2);
|
|
652
802
|
|
|
653
|
-
var rubocopAvailable =
|
|
654
|
-
var setRubocopAvailable =
|
|
803
|
+
var rubocopAvailable = _useState18c[0];
|
|
804
|
+
var setRubocopAvailable = _useState18c[1];
|
|
655
805
|
|
|
656
806
|
var _useState18d = useState(false);
|
|
657
807
|
|
|
658
|
-
var _useState18d2 = _slicedToArray(_useState18d, 2);
|
|
659
808
|
|
|
660
|
-
var hamlLintAvailable =
|
|
661
|
-
var setHamlLintAvailable =
|
|
809
|
+
var hamlLintAvailable = _useState18d[0];
|
|
810
|
+
var setHamlLintAvailable = _useState18d[1];
|
|
662
811
|
|
|
663
812
|
var _useState18e = useState(false);
|
|
664
|
-
var
|
|
665
|
-
var
|
|
666
|
-
var setGitAvailable = _useState18e2[1];
|
|
813
|
+
var gitAvailable = _useState18e[0];
|
|
814
|
+
var setGitAvailable = _useState18e[1];
|
|
667
815
|
|
|
668
816
|
var _useState18f = useState(false);
|
|
669
|
-
var
|
|
670
|
-
var
|
|
671
|
-
var setRedmineEnabled = _useState18f2[1];
|
|
817
|
+
var redmineEnabled = _useState18f[0];
|
|
818
|
+
var setRedmineEnabled = _useState18f[1];
|
|
672
819
|
|
|
673
820
|
var _useState18rc = useState(null);
|
|
674
|
-
var
|
|
675
|
-
var
|
|
676
|
-
var setRubocopConfigPath = _useState18rc2[1];
|
|
677
|
-
|
|
678
|
-
var _useState18t = useState(false);
|
|
679
|
-
var _useState18t2 = _slicedToArray(_useState18t, 2);
|
|
680
|
-
var testAvailable = _useState18t2[0];
|
|
681
|
-
var setTestAvailable = _useState18t2[1];
|
|
821
|
+
var rubocopConfigPath = _useState18rc[0];
|
|
822
|
+
var setRubocopConfigPath = _useState18rc[1];
|
|
682
823
|
|
|
683
824
|
var _useState18u = useState(null);
|
|
684
|
-
var
|
|
685
|
-
var
|
|
686
|
-
var setTestResult = _useState18u2[1];
|
|
825
|
+
var testResult = _useState18u[0];
|
|
826
|
+
var setTestResult = _useState18u[1];
|
|
687
827
|
|
|
688
828
|
var _useState18v = useState(false);
|
|
689
|
-
var
|
|
690
|
-
var
|
|
691
|
-
var setTestLoading = _useState18v2[1];
|
|
829
|
+
var testLoading = _useState18v[0];
|
|
830
|
+
var setTestLoading = _useState18v[1];
|
|
692
831
|
|
|
693
832
|
var _useState18w = useState(true);
|
|
694
|
-
var
|
|
695
|
-
var
|
|
696
|
-
var setTestInlineVisible = _useState18w2[1];
|
|
833
|
+
var testInlineVisible = _useState18w[0];
|
|
834
|
+
var setTestInlineVisible = _useState18w[1];
|
|
697
835
|
|
|
698
836
|
var _useState18x = useState(null);
|
|
699
|
-
var
|
|
700
|
-
var
|
|
701
|
-
var setTestPanelFile = _useState18x2[1];
|
|
837
|
+
var testPanelFile = _useState18x[0];
|
|
838
|
+
var setTestPanelFile = _useState18x[1];
|
|
702
839
|
|
|
703
840
|
var _useState18y = useState(false);
|
|
704
|
-
var
|
|
705
|
-
var
|
|
706
|
-
var setTestPanelOpen = _useState18y2[1];
|
|
841
|
+
var testPanelOpen = _useState18y[0];
|
|
842
|
+
var setTestPanelOpen = _useState18y[1];
|
|
707
843
|
|
|
708
844
|
var _useState18p = useState(DEFAULT_EDITOR_PREFS);
|
|
709
|
-
var
|
|
710
|
-
var
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
//
|
|
714
|
-
// too narrow for
|
|
715
|
-
var toolbarIconOnly = editorPrefs.
|
|
845
|
+
var editorPrefs = _useState18p[0];
|
|
846
|
+
var setEditorPrefs = _useState18p[1];
|
|
847
|
+
|
|
848
|
+
// Icon-only toolbar: the default, since every button carries its name in a
|
|
849
|
+
// title attribute. Labels are opt-in, and are dropped regardless once the
|
|
850
|
+
// window is too narrow for them to fit beside the title and file search.
|
|
851
|
+
var toolbarIconOnly = !editorPrefs.toolbarLabels || narrowToolbar;
|
|
852
|
+
|
|
853
|
+
// The audit ring never reads preferences itself; enablement is pushed in.
|
|
854
|
+
// BOOT is recorded on the first enable rather than at mount, because the
|
|
855
|
+
// stored preference arrives from /state after the default has already been
|
|
856
|
+
// applied and switching off discards anything buffered before it.
|
|
857
|
+
var auditBooted = useRef(false);
|
|
858
|
+
useEffect(function () {
|
|
859
|
+
var audit = window.MbeditorAudit;
|
|
860
|
+
if (!audit) return;
|
|
861
|
+
var on = editorPrefs.auditLog !== false;
|
|
862
|
+
audit.setEnabled(on);
|
|
863
|
+
if (!on || auditBooted.current) return;
|
|
864
|
+
auditBooted.current = true;
|
|
865
|
+
// Ready is when Monaco is up and the file can be typed in, not when React
|
|
866
|
+
// mounted: the gap between the two is most of the boot.
|
|
867
|
+
Promise.resolve(window.__monacoReady).then(function () {
|
|
868
|
+
audit.rec(audit.EV.BOOT, performance.now() | 0);
|
|
869
|
+
});
|
|
870
|
+
}, [editorPrefs.auditLog]);
|
|
716
871
|
|
|
717
872
|
var _useState19 = useState({
|
|
718
873
|
openEditors: false,
|
|
719
874
|
projects: false
|
|
720
875
|
});
|
|
721
876
|
|
|
722
|
-
var _useState192 = _slicedToArray(_useState19, 2);
|
|
723
877
|
|
|
724
|
-
var collapsedSections =
|
|
725
|
-
var setCollapsedSections =
|
|
878
|
+
var collapsedSections = _useState19[0];
|
|
879
|
+
var setCollapsedSections = _useState19[1];
|
|
726
880
|
|
|
727
881
|
var _useState20 = useState({});
|
|
728
882
|
|
|
729
|
-
var _useState202 = _slicedToArray(_useState20, 2);
|
|
730
883
|
|
|
731
|
-
var expandedDirs =
|
|
732
|
-
var setExpandedDirs =
|
|
884
|
+
var expandedDirs = _useState20[0];
|
|
885
|
+
var setExpandedDirs = _useState20[1];
|
|
733
886
|
|
|
734
887
|
var _useState21 = useState(null);
|
|
735
888
|
|
|
736
|
-
var _useState212 = _slicedToArray(_useState21, 2);
|
|
737
889
|
|
|
738
|
-
var pendingCreate =
|
|
739
|
-
var setPendingCreate =
|
|
890
|
+
var pendingCreate = _useState21[0];
|
|
891
|
+
var setPendingCreate = _useState21[1];
|
|
740
892
|
|
|
741
893
|
var _useState22 = useState(null);
|
|
742
894
|
|
|
743
|
-
var _useState222 = _slicedToArray(_useState22, 2);
|
|
744
895
|
|
|
745
|
-
var pendingRename =
|
|
746
|
-
var setPendingRename =
|
|
896
|
+
var pendingRename = _useState22[0];
|
|
897
|
+
var setPendingRename = _useState22[1];
|
|
747
898
|
|
|
748
899
|
var _useState23 = useState(null);
|
|
749
900
|
|
|
750
|
-
var _useState232 = _slicedToArray(_useState23, 2);
|
|
751
901
|
|
|
752
|
-
var contextMenu =
|
|
753
|
-
var setContextMenu =
|
|
902
|
+
var contextMenu = _useState23[0];
|
|
903
|
+
var setContextMenu = _useState23[1];
|
|
904
|
+
var contextMenuRef = useRef(null);
|
|
754
905
|
|
|
755
906
|
var _useState24 = useState(140);
|
|
756
|
-
var
|
|
757
|
-
var
|
|
758
|
-
|
|
907
|
+
var openEditorsHeight = _useState24[0];
|
|
908
|
+
var setOpenEditorsHeight = _useState24[1];
|
|
909
|
+
|
|
910
|
+
// Both bottom drawers are resized by a gutter that sits between the editor
|
|
911
|
+
// card and the drawer, so the height lives here rather than in the drawer —
|
|
912
|
+
// a panel cannot own the handle that is no longer inside it.
|
|
913
|
+
var _drawerHeight = function (key) {
|
|
914
|
+
var saved = parseInt(window.localStorage.getItem(key), 10);
|
|
915
|
+
return (saved && saved >= DRAWER_MIN_HEIGHT) ? saved : 240;
|
|
916
|
+
};
|
|
917
|
+
var _useStateLogH = useState(function () { return _drawerHeight('mbeditorLogHeight'); });
|
|
918
|
+
var logHeight = _useStateLogH[0];
|
|
919
|
+
var setLogHeight = _useStateLogH[1];
|
|
920
|
+
var _useStateProblemsH = useState(function () { return _drawerHeight('mbeditorProblemsHeight'); });
|
|
921
|
+
var problemsHeight = _useStateProblemsH[0];
|
|
922
|
+
var setProblemsHeight = _useStateProblemsH[1];
|
|
923
|
+
|
|
924
|
+
// One-shot target for post-operation scroll reveal (create/rename/delete).
|
|
925
|
+
// A fresh object each time so the effect re-fires even for a repeated path.
|
|
926
|
+
var _useState25 = useState(null);
|
|
927
|
+
var revealTarget = _useState25[0];
|
|
928
|
+
var setRevealTarget = _useState25[1];
|
|
759
929
|
|
|
760
930
|
var resizeSessionRef = useRef(null);
|
|
761
931
|
var resizeRafRef = useRef(null);
|
|
@@ -765,9 +935,8 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
765
935
|
var ctrlWPendingRef = useRef(false);
|
|
766
936
|
var ctrlWTimeoutRef = useRef(null);
|
|
767
937
|
var _useStateCP = useState([]);
|
|
768
|
-
var
|
|
769
|
-
var
|
|
770
|
-
var setCustomPaths = _useStateCP2[1];
|
|
938
|
+
var customPaths = _useStateCP[0];
|
|
939
|
+
var setCustomPaths = _useStateCP[1];
|
|
771
940
|
var customPathsRef = useRef([]);
|
|
772
941
|
customPathsRef.current = customPaths;
|
|
773
942
|
// Whether to show the presence chips at all. Read at render rather than held in
|
|
@@ -780,33 +949,29 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
780
949
|
var _useStateIdent = useState(
|
|
781
950
|
typeof CollaborationIdentity !== 'undefined' ? CollaborationIdentity.get() : null
|
|
782
951
|
);
|
|
783
|
-
var
|
|
784
|
-
var
|
|
785
|
-
var setCollabIdentity = _useStateIdent2[1];
|
|
952
|
+
var collabIdentity = _useStateIdent[0];
|
|
953
|
+
var setCollabIdentity = _useStateIdent[1];
|
|
786
954
|
// Presence roster: other connected participants, keyed by client_id →
|
|
787
955
|
// { name, colour, current_file }. Fed by the global presence stream; rendered
|
|
788
956
|
// as click-to-jump chips in the status bar.
|
|
789
957
|
var _useStateRoster = useState({});
|
|
790
|
-
var
|
|
791
|
-
var
|
|
792
|
-
var setCollabRoster = _useStateRoster2[1];
|
|
958
|
+
var collabRoster = _useStateRoster[0];
|
|
959
|
+
var setCollabRoster = _useStateRoster[1];
|
|
793
960
|
|
|
794
961
|
// Follow mode (slice 8): the presence client_id of the participant whose file +
|
|
795
962
|
// viewport we're tracking, or null when navigating independently. Toggled from a
|
|
796
963
|
// roster chip. The file-open is driven by the effect below; the scroll-tracking
|
|
797
964
|
// lives in CollaborationService.setFollow().
|
|
798
965
|
var _useStateFollow = useState(null);
|
|
799
|
-
var
|
|
800
|
-
var
|
|
801
|
-
var setFollowedClientId = _useStateFollow2[1];
|
|
966
|
+
var followedClientId = _useStateFollow[0];
|
|
967
|
+
var setFollowedClientId = _useStateFollow[1];
|
|
802
968
|
var recentSavesRef = useRef({});
|
|
803
969
|
var isSavingRef = useRef(false);
|
|
804
970
|
// True once the saved session has finished loading into the panes. Anything
|
|
805
971
|
// that opens a tab on startup must wait for this, or the restore overwrites it.
|
|
806
972
|
var _useStateSR = useState(false);
|
|
807
|
-
var
|
|
808
|
-
var
|
|
809
|
-
var setSessionRestored = _useStateSR2[1];
|
|
973
|
+
var sessionRestored = _useStateSR[0];
|
|
974
|
+
var setSessionRestored = _useStateSR[1];
|
|
810
975
|
var pendingChangelogRef = useRef(false);
|
|
811
976
|
// path -> the file's content as last seen ON DISK (newline-normalised).
|
|
812
977
|
// External-change detection compares disk-to-disk; comparing disk to the
|
|
@@ -835,15 +1000,37 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
835
1000
|
|
|
836
1001
|
// ── Draft backup helpers ─────────────────────────────────────────────────
|
|
837
1002
|
var draftWriteTimerRef = useRef({});
|
|
1003
|
+
var draftWriteWarnedRef = useRef({});
|
|
838
1004
|
var serverOnlineRef = useRef(true);
|
|
839
1005
|
|
|
840
1006
|
var _draftKey = function _draftKey(path) {
|
|
841
1007
|
var base = typeof window.mbeditorBasePath === 'function' ? window.mbeditorBasePath() : '';
|
|
842
1008
|
return 'mbeditor_draft\x00' + base + '\x00' + path;
|
|
843
1009
|
};
|
|
1010
|
+
// localStorage is UTF-16 and most browsers cap it near 5MB, so a draft for a
|
|
1011
|
+
// file past this can never fit: every debounced write failed, warned once, and
|
|
1012
|
+
// then kept stringifying + attempting the whole buffer every 500ms of typing.
|
|
1013
|
+
// Skip it outright; an explicit save is the safety net for huge files.
|
|
1014
|
+
var MAX_DRAFT_CHARS = 1000000;
|
|
844
1015
|
var _saveDraftNow = function _saveDraftNow(path, content) {
|
|
1016
|
+
if (typeof content === 'string' && content.length > MAX_DRAFT_CHARS) {
|
|
1017
|
+
if (!draftWriteWarnedRef.current[path]) {
|
|
1018
|
+
draftWriteWarnedRef.current[path] = true;
|
|
1019
|
+
EditorStore.setStatus('Crash-recovery backup skipped for ' + path + ' (file too large)', 'info');
|
|
1020
|
+
}
|
|
1021
|
+
_clearDraft(path);
|
|
1022
|
+
return;
|
|
1023
|
+
}
|
|
845
1024
|
var doWrite = function() {
|
|
846
|
-
try {
|
|
1025
|
+
try {
|
|
1026
|
+
localStorage.setItem(_draftKey(path), JSON.stringify({ content: content, ts: Date.now() }));
|
|
1027
|
+
} catch (e) {
|
|
1028
|
+
// Once per path: this fires on every debounced keystroke otherwise.
|
|
1029
|
+
if (!draftWriteWarnedRef.current[path]) {
|
|
1030
|
+
draftWriteWarnedRef.current[path] = true;
|
|
1031
|
+
EditorStore.setStatus('Crash-recovery backup failed for ' + path + ' (storage full)', 'error');
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
847
1034
|
};
|
|
848
1035
|
if (typeof requestIdleCallback !== 'undefined') {
|
|
849
1036
|
requestIdleCallback(doWrite, { timeout: 2000 });
|
|
@@ -866,24 +1053,25 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
866
1053
|
};
|
|
867
1054
|
|
|
868
1055
|
var _useState_dro = useState(null);
|
|
869
|
-
var
|
|
870
|
-
var
|
|
871
|
-
var setDraftRestoreOffer = _useState_dro2[1];
|
|
1056
|
+
var draftRestoreOffer = _useState_dro[0];
|
|
1057
|
+
var setDraftRestoreOffer = _useState_dro[1];
|
|
872
1058
|
|
|
873
1059
|
var _useStateMR = useState(false);
|
|
874
|
-
var
|
|
875
|
-
var
|
|
876
|
-
var setMonacoReady = _useStateMR2[1];
|
|
1060
|
+
var monacoReady = _useStateMR[0];
|
|
1061
|
+
var setMonacoReady = _useStateMR[1];
|
|
877
1062
|
|
|
878
1063
|
var _useStateZen = useState(false);
|
|
879
|
-
var
|
|
880
|
-
var
|
|
881
|
-
|
|
1064
|
+
var zenMode = _useStateZen[0];
|
|
1065
|
+
var setZenMode = _useStateZen[1];
|
|
1066
|
+
|
|
1067
|
+
// Transient, like zen mode — never persisted, always starts closed.
|
|
1068
|
+
var _useStateMGV = useState(false);
|
|
1069
|
+
var showModelGraphView = _useStateMGV[0];
|
|
1070
|
+
var setShowModelGraphView = _useStateMGV[1];
|
|
882
1071
|
|
|
883
1072
|
var _useStateSB = useState(false);
|
|
884
|
-
var
|
|
885
|
-
var
|
|
886
|
-
var setIsSwitchingBranch = _useStateSB2[1];
|
|
1073
|
+
var isSwitchingBranch = _useStateSB[0];
|
|
1074
|
+
var setIsSwitchingBranch = _useStateSB[1];
|
|
887
1075
|
|
|
888
1076
|
var clamp = function clamp(value, min, max) {
|
|
889
1077
|
return Math.min(max, Math.max(min, value));
|
|
@@ -937,6 +1125,48 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
937
1125
|
});
|
|
938
1126
|
};
|
|
939
1127
|
|
|
1128
|
+
// Finds the children array of parentPath within tree ('' means the root).
|
|
1129
|
+
var findTreeChildren = function findTreeChildren(tree, parentPath) {
|
|
1130
|
+
if (!parentPath) return tree;
|
|
1131
|
+
for (var i = 0; i < tree.length; i++) {
|
|
1132
|
+
var item = tree[i];
|
|
1133
|
+
if (item.path === parentPath && item.type === 'folder') return item.children || [];
|
|
1134
|
+
if (item.type === 'folder' && item.children) {
|
|
1135
|
+
var found = findTreeChildren(item.children, parentPath);
|
|
1136
|
+
if (found) return found;
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
return null;
|
|
1140
|
+
};
|
|
1141
|
+
|
|
1142
|
+
// Row to reveal after a delete: the next surviving sibling in display
|
|
1143
|
+
// order, else the previous one, else the parent directory. Sorted the same
|
|
1144
|
+
// way FileTree sorts (folders first, then name) so it matches what the
|
|
1145
|
+
// user actually sees.
|
|
1146
|
+
var computeDeleteRevealTarget = function computeDeleteRevealTarget(tree, deletedPaths) {
|
|
1147
|
+
var anchor = deletedPaths[0];
|
|
1148
|
+
var parent = parentDir(anchor);
|
|
1149
|
+
var siblings = findTreeChildren(tree, parent);
|
|
1150
|
+
if (siblings) {
|
|
1151
|
+
var sorted = siblings.concat().sort(function (a, b) {
|
|
1152
|
+
if (a.type !== b.type) return a.type === 'folder' ? -1 : 1;
|
|
1153
|
+
return a.name.localeCompare(b.name);
|
|
1154
|
+
});
|
|
1155
|
+
var idx = sorted.findIndex(function (n) { return n.path === anchor; });
|
|
1156
|
+
if (idx >= 0) {
|
|
1157
|
+
var deletedSet = new Set(deletedPaths);
|
|
1158
|
+
var j, k;
|
|
1159
|
+
for (j = idx + 1; j < sorted.length; j++) {
|
|
1160
|
+
if (!deletedSet.has(sorted[j].path)) return sorted[j].path;
|
|
1161
|
+
}
|
|
1162
|
+
for (k = idx - 1; k >= 0; k--) {
|
|
1163
|
+
if (!deletedSet.has(sorted[k].path)) return sorted[k].path;
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
return parent || null;
|
|
1168
|
+
};
|
|
1169
|
+
|
|
940
1170
|
// Every structural mutation (create, delete, rename, import) funnels through
|
|
941
1171
|
// here, so this is where the project-search cache has to be dropped. Saves
|
|
942
1172
|
// invalidated it at their own call sites, which is why editing a file
|
|
@@ -1023,64 +1253,6 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1023
1253
|
loadModelGraph(false);
|
|
1024
1254
|
}, [activeSidebarTab, sidebarCollapsed]);
|
|
1025
1255
|
|
|
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
1256
|
// The one writer for the markers map. Two things it must not do:
|
|
1085
1257
|
//
|
|
1086
1258
|
// * write a fresh map when nothing changed — the auto-lint fires per
|
|
@@ -1125,6 +1297,8 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1125
1297
|
EditorStore.setStatus('Linting...', 'info');
|
|
1126
1298
|
}
|
|
1127
1299
|
|
|
1300
|
+
var startedAt = Date.now();
|
|
1301
|
+
|
|
1128
1302
|
// ruby-lsp answers diagnostics for Ruby files when it's available: same
|
|
1129
1303
|
// markers, plus Prism syntax errors, without a per-keystroke rubocop boot.
|
|
1130
1304
|
// Anything short of a usable answer falls through to the HTTP lint for
|
|
@@ -1147,6 +1321,8 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1147
1321
|
|
|
1148
1322
|
return lintRequest.then(function (res) {
|
|
1149
1323
|
var nextMarkers = res.markers || [];
|
|
1324
|
+
var audit = window.MbeditorAudit;
|
|
1325
|
+
if (audit) audit.rec(audit.EV.LINT, _auditExt(tab.path), Date.now() - startedAt, nextMarkers.length);
|
|
1150
1326
|
applyMarkersForTab(tab.id, nextMarkers);
|
|
1151
1327
|
|
|
1152
1328
|
if (options.showStatus) {
|
|
@@ -1156,6 +1332,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1156
1332
|
|
|
1157
1333
|
return res;
|
|
1158
1334
|
})["catch"](function (err) {
|
|
1335
|
+
_recErr('LINT');
|
|
1159
1336
|
if (options.showStatus) {
|
|
1160
1337
|
EditorStore.setStatus('Lint failed: ' + err.message, 'error');
|
|
1161
1338
|
}
|
|
@@ -1231,9 +1408,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1231
1408
|
Promise.all([FileService.getWorkspace()["catch"](function () {
|
|
1232
1409
|
return null;
|
|
1233
1410
|
}), refreshProjectTree()]).then(function (_ref) {
|
|
1234
|
-
var
|
|
1235
|
-
|
|
1236
|
-
var workspace = _ref2[0];
|
|
1411
|
+
var workspace = _ref[0];
|
|
1237
1412
|
|
|
1238
1413
|
if (workspace && workspace.rootName) {
|
|
1239
1414
|
setProjectRootName(workspace.rootName);
|
|
@@ -1254,20 +1429,17 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1254
1429
|
if (workspace && typeof workspace.rubyLspAvailable === 'boolean') {
|
|
1255
1430
|
window.MBEDITOR_RUBY_LSP_AVAILABLE = workspace.rubyLspAvailable;
|
|
1256
1431
|
}
|
|
1432
|
+
if (workspace && workspace.searchBackend) {
|
|
1433
|
+
window.MBEDITOR_SEARCH_BACKEND = workspace.searchBackend;
|
|
1434
|
+
}
|
|
1257
1435
|
if (workspace && typeof workspace.gitAvailable === 'boolean') {
|
|
1258
1436
|
setGitAvailable(workspace.gitAvailable);
|
|
1259
1437
|
}
|
|
1260
1438
|
if (workspace && typeof workspace.redmineEnabled === 'boolean') {
|
|
1261
1439
|
setRedmineEnabled(workspace.redmineEnabled);
|
|
1262
1440
|
}
|
|
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);
|
|
1441
|
+
if (workspace && workspace.testTimeout) {
|
|
1442
|
+
FileService.setTestTimeout(workspace.testTimeout);
|
|
1271
1443
|
}
|
|
1272
1444
|
if (workspace && typeof workspace.actionCableEnabled === 'boolean') {
|
|
1273
1445
|
WebSocketService.connect(workspace.actionCableEnabled);
|
|
@@ -1277,11 +1449,15 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1277
1449
|
// Helper: load tab content for a set of panes and restore them into EditorStore
|
|
1278
1450
|
function loadPaneState(panesToLoad, focusedPaneId) {
|
|
1279
1451
|
if (!panesToLoad || panesToLoad.length === 0) return Promise.resolve();
|
|
1452
|
+
// Settings is a modal now; a state saved when it was a tab must not
|
|
1453
|
+
// restore one.
|
|
1454
|
+
panesToLoad = panesToLoad.map(function (p) {
|
|
1455
|
+
return _extends({}, p, {
|
|
1456
|
+
tabs: p.tabs.filter(function (t) { return !t.isSettings && t.path !== '__settings__'; })
|
|
1457
|
+
});
|
|
1458
|
+
});
|
|
1280
1459
|
var allTabs = panesToLoad.flatMap(function (p) { return p.tabs; });
|
|
1281
1460
|
return Promise.all(allTabs.map(function (t) {
|
|
1282
|
-
if (t.isSettings || t.path === '__settings__') {
|
|
1283
|
-
return Promise.resolve({ content: '' });
|
|
1284
|
-
}
|
|
1285
1461
|
if (t.isChangelog || t.path === 'mbeditor://changelog') {
|
|
1286
1462
|
return Promise.resolve({ content: '' });
|
|
1287
1463
|
}
|
|
@@ -1293,9 +1469,6 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1293
1469
|
if (t.isCombinedDiff || (t.path || '').startsWith('combined-diff://') || (t.path || '').startsWith('diff://')) {
|
|
1294
1470
|
return Promise.resolve({ content: '' });
|
|
1295
1471
|
}
|
|
1296
|
-
if (t.isModelGraph || t.path === 'mbeditor://model-graph') {
|
|
1297
|
-
return Promise.resolve({ content: '' });
|
|
1298
|
-
}
|
|
1299
1472
|
var sourcePath = t.isPreview || /::preview$/.test(t.path || '') ? t.previewFor || (t.path || '').replace(/::preview$/, '') : t.path;
|
|
1300
1473
|
return FileService.getFile(sourcePath, { allowMissing: true }).then(function (data) {
|
|
1301
1474
|
return {
|
|
@@ -1318,8 +1491,8 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1318
1491
|
var tabs = [];
|
|
1319
1492
|
p.tabs.forEach(function (t) {
|
|
1320
1493
|
var res = results[resIdx++];
|
|
1321
|
-
var isPlainFile = t.path && !t.isDiff && !t.isCombinedDiff &&
|
|
1322
|
-
!t.isChangelog && !t.isPreview &&
|
|
1494
|
+
var isPlainFile = t.path && !t.isDiff && !t.isCombinedDiff &&
|
|
1495
|
+
!t.isChangelog && !t.isPreview &&
|
|
1323
1496
|
!/^(diff|combined-diff):\/\//.test(t.path) && !/::preview$/.test(t.path);
|
|
1324
1497
|
if (isPlainFile) {
|
|
1325
1498
|
if (seenPaths[t.path]) return;
|
|
@@ -1329,9 +1502,6 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1329
1502
|
content: res.content,
|
|
1330
1503
|
externalContentVersion: (t.externalContentVersion || 0) + 1
|
|
1331
1504
|
},
|
|
1332
|
-
// A state saved before this tab type existed carries the path but
|
|
1333
|
-
// not the flag, and would restore as a missing file.
|
|
1334
|
-
t.path === 'mbeditor://model-graph' ? { isModelGraph: true } : {},
|
|
1335
1505
|
res._isDiffResult ? { diffOriginal: res.diffOriginal, diffModified: res.diffModified } : {},
|
|
1336
1506
|
typeof res.fileNotFound === 'boolean' ? { fileNotFound: res.fileNotFound, dirty: res.fileNotFound ? false : t.dirty } : {},
|
|
1337
1507
|
res.image === true ? { isImage: true } : {}));
|
|
@@ -1443,10 +1613,10 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1443
1613
|
return {
|
|
1444
1614
|
id: p.id,
|
|
1445
1615
|
activeTabId: p.activeTabId,
|
|
1446
|
-
tabs: p.tabs.filter(function (t) { return !t.isCombinedDiff && !t.
|
|
1616
|
+
tabs: p.tabs.filter(function (t) { return !t.isCombinedDiff && !t.isUntitled; }).map(function (t) {
|
|
1447
1617
|
return {
|
|
1448
1618
|
id: t.id, path: t.path, name: t.name, dirty: t.dirty, viewState: t.viewState,
|
|
1449
|
-
|
|
1619
|
+
isPreview: !!t.isPreview, previewFor: t.previewFor || null,
|
|
1450
1620
|
isDiff: !!t.isDiff, diffBaseSha: t.diffBaseSha || null, diffHeadSha: t.diffHeadSha || null,
|
|
1451
1621
|
repoPath: t.repoPath || null, isChangelog: !!t.isChangelog
|
|
1452
1622
|
};
|
|
@@ -1529,6 +1699,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1529
1699
|
if (e.key === 'Escape') {
|
|
1530
1700
|
setContextMenu(null);
|
|
1531
1701
|
setShowHelp(false);
|
|
1702
|
+
setCollabOverflowOpen(false);
|
|
1532
1703
|
}
|
|
1533
1704
|
};
|
|
1534
1705
|
|
|
@@ -1562,6 +1733,9 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1562
1733
|
var reservedRight = EDITOR_MIN_WIDTH + (showGitPanelRef.current ? gitPanelWidthRef.current : 0);
|
|
1563
1734
|
var maxSidebarWidth = Math.min(SIDEBAR_MAX_WIDTH, Math.max(SIDEBAR_MIN_WIDTH, rect.width - reservedRight));
|
|
1564
1735
|
var nextWidth = clientX - rect.left - SIDEBAR_COLLAPSED_WIDTH;
|
|
1736
|
+
// Remembered for mouseup: dragged well under the minimum, the
|
|
1737
|
+
// explorer snaps shut instead of pinning at SIDEBAR_MIN_WIDTH.
|
|
1738
|
+
s.snapClose = nextWidth < SIDEBAR_MIN_WIDTH - 80;
|
|
1565
1739
|
setSidebarWidth(clamp(nextWidth, SIDEBAR_MIN_WIDTH, maxSidebarWidth));
|
|
1566
1740
|
}
|
|
1567
1741
|
|
|
@@ -1579,11 +1753,26 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1579
1753
|
var nextHeight = Math.max(60, Math.min(400, s.startHeight + delta));
|
|
1580
1754
|
setOpenEditorsHeight(nextHeight);
|
|
1581
1755
|
}
|
|
1756
|
+
|
|
1757
|
+
// Bottom drawers. Delta-based and inverted: dragging the gutter up
|
|
1758
|
+
// grows the drawer. Independent of where the drawer is anchored and
|
|
1759
|
+
// survives a zero/unknown viewport height.
|
|
1760
|
+
if (s.mode === 'log' || s.mode === 'problems') {
|
|
1761
|
+
var vh = window.innerHeight || document.documentElement.clientHeight || 0;
|
|
1762
|
+
var maxH = vh > 0 ? Math.round(vh * 0.85) : Infinity;
|
|
1763
|
+
s.lastHeight = Math.min(maxH, Math.max(DRAWER_MIN_HEIGHT, s.startHeight + (s.startY - clientY)));
|
|
1764
|
+
(s.mode === 'log' ? setLogHeight : setProblemsHeight)(s.lastHeight);
|
|
1765
|
+
}
|
|
1582
1766
|
});
|
|
1583
1767
|
};
|
|
1584
1768
|
|
|
1585
1769
|
var handleMouseUp = function handleMouseUp() {
|
|
1586
1770
|
if (!resizeSessionRef.current) return;
|
|
1771
|
+
if (resizeSessionRef.current.snapClose) setSidebarCollapsed(true);
|
|
1772
|
+
var s = resizeSessionRef.current;
|
|
1773
|
+
if (s.storeKey && s.lastHeight) {
|
|
1774
|
+
try { window.localStorage.setItem(s.storeKey, String(s.lastHeight)); } catch (err) {}
|
|
1775
|
+
}
|
|
1587
1776
|
|
|
1588
1777
|
if (resizeRafRef.current) {
|
|
1589
1778
|
cancelAnimationFrame(resizeRafRef.current);
|
|
@@ -1896,7 +2085,10 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1896
2085
|
function handleFileSaved(data) {
|
|
1897
2086
|
var path = data && data.path;
|
|
1898
2087
|
if (!path) return;
|
|
1899
|
-
|
|
2088
|
+
// Only an attached (CRDT-bound) tab has the peer's content. A room that is
|
|
2089
|
+
// still deferring, or degraded to local, holds its own buffer — a peer save
|
|
2090
|
+
// is an external change for it, handled by the files_changed path.
|
|
2091
|
+
if (typeof CollaborationService === 'undefined' || !CollaborationService.isAttached(path)) return;
|
|
1900
2092
|
|
|
1901
2093
|
var st = EditorStore.getState();
|
|
1902
2094
|
var changed = false;
|
|
@@ -1957,7 +2149,6 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1957
2149
|
!path.startsWith('combined-diff://') &&
|
|
1958
2150
|
!path.startsWith('untitled://') &&
|
|
1959
2151
|
!pt.tab.isCombinedDiff &&
|
|
1960
|
-
!pt.tab.isSettings &&
|
|
1961
2152
|
!pt.tab.isImage &&
|
|
1962
2153
|
!pt.tab.isDiff &&
|
|
1963
2154
|
typeof pt.tab.content === 'string';
|
|
@@ -2019,6 +2210,12 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2019
2210
|
});
|
|
2020
2211
|
})
|
|
2021
2212
|
});
|
|
2213
|
+
// The text just moved under the diagnostics. Only the mounted editor
|
|
2214
|
+
// re-lints, so without this a background tab kept reporting offenses
|
|
2215
|
+
// at line numbers the external write had shifted — and the Problems
|
|
2216
|
+
// panel and status-bar tallies reported them too. Dropping them says
|
|
2217
|
+
// "not known yet", which is true: the file re-lints when you open it.
|
|
2218
|
+
discardStaleMarkers(pt.tab.path);
|
|
2022
2219
|
} else {
|
|
2023
2220
|
// Re-verify the tab still exists before queuing
|
|
2024
2221
|
var currentState = EditorStore.getState();
|
|
@@ -2058,8 +2255,18 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2058
2255
|
useEffect(function () {
|
|
2059
2256
|
var intervalId = setInterval(function () {
|
|
2060
2257
|
if (document.hidden) return;
|
|
2258
|
+
var startedAt = Date.now();
|
|
2061
2259
|
FileService.getTree({ background: true }).then(function (data) {
|
|
2062
|
-
|
|
2260
|
+
var next = data || [];
|
|
2261
|
+
var audit = window.MbeditorAudit;
|
|
2262
|
+
// Stringified once and reused by the updater, so instrumenting the poll
|
|
2263
|
+
// costs no extra pass over the tree.
|
|
2264
|
+
var signature = audit ? JSON.stringify(next) : null;
|
|
2265
|
+
setTreeData(_treeUpdater(next, signature));
|
|
2266
|
+
if (!audit) return;
|
|
2267
|
+
var changed = signature !== _lastPolledTree;
|
|
2268
|
+
_lastPolledTree = signature;
|
|
2269
|
+
audit.rec(audit.EV.TREE_POLL, Date.now() - startedAt, changed ? 1 : 0);
|
|
2063
2270
|
}).catch(function () {}); // silently ignore auto-refresh errors
|
|
2064
2271
|
}, 10000);
|
|
2065
2272
|
return function () { clearInterval(intervalId); };
|
|
@@ -2125,8 +2332,12 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2125
2332
|
};
|
|
2126
2333
|
}, [monacoReady]);
|
|
2127
2334
|
|
|
2128
|
-
var handleSelectFile = function handleSelectFile(path, name, line, col) {
|
|
2129
|
-
|
|
2335
|
+
var handleSelectFile = function handleSelectFile(path, name, line, col, endCol) {
|
|
2336
|
+
// The one place every reachable "open a file" action funnels through while
|
|
2337
|
+
// the model graph view is up (quick open is the only such surface left —
|
|
2338
|
+
// explorer, search, rails and git panel are all hidden with it).
|
|
2339
|
+
setShowModelGraphView(false);
|
|
2340
|
+
TabManager.openTab(path, name, line, null, false, col, endCol);
|
|
2130
2341
|
handleNodeSelect({ path: path, name: name || path.split('/').pop(), type: 'file' });
|
|
2131
2342
|
setQuickOpen(false);
|
|
2132
2343
|
};
|
|
@@ -2181,6 +2392,17 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2181
2392
|
});
|
|
2182
2393
|
};
|
|
2183
2394
|
|
|
2395
|
+
// Drop any pending-reload prompt whose tab is no longer open in any pane.
|
|
2396
|
+
var _prunePendingReloads = function _prunePendingReloads() {
|
|
2397
|
+
EditorStore.setState({
|
|
2398
|
+
pendingReloads: EditorStore.getState().pendingReloads.filter(function (r) {
|
|
2399
|
+
return EditorStore.getState().panes.some(function (p) {
|
|
2400
|
+
return p.tabs.some(function (t) { return t.id === r.tabId; });
|
|
2401
|
+
});
|
|
2402
|
+
})
|
|
2403
|
+
});
|
|
2404
|
+
};
|
|
2405
|
+
|
|
2184
2406
|
var requestCloseTab = function requestCloseTab(paneId, id) {
|
|
2185
2407
|
var pane = state.panes.find(function (p) {
|
|
2186
2408
|
return p.id === paneId;
|
|
@@ -2193,13 +2415,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2193
2415
|
setClosingTabId(id);
|
|
2194
2416
|
} else {
|
|
2195
2417
|
TabManager.closeTab(paneId, id);
|
|
2196
|
-
|
|
2197
|
-
pendingReloads: EditorStore.getState().pendingReloads.filter(function (r) {
|
|
2198
|
-
return EditorStore.getState().panes.some(function (p) {
|
|
2199
|
-
return p.tabs.some(function (t) { return t.id === r.tabId; });
|
|
2200
|
-
});
|
|
2201
|
-
})
|
|
2202
|
-
});
|
|
2418
|
+
_prunePendingReloads();
|
|
2203
2419
|
forgetClosedTabs();
|
|
2204
2420
|
}
|
|
2205
2421
|
};
|
|
@@ -2218,6 +2434,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2218
2434
|
}
|
|
2219
2435
|
|
|
2220
2436
|
if (save) {
|
|
2437
|
+
tab = _freshTab(closingPaneId, tab);
|
|
2221
2438
|
if (tab.isUntitled) {
|
|
2222
2439
|
// Save-as converts the scratch tab to a real one (closing the scratch
|
|
2223
2440
|
// tab in the process); a cancelled prompt keeps the tab open.
|
|
@@ -2231,49 +2448,20 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2231
2448
|
});
|
|
2232
2449
|
return;
|
|
2233
2450
|
}
|
|
2234
|
-
|
|
2235
|
-
return _extends({}, prev, { save: true });
|
|
2236
|
-
});
|
|
2237
|
-
EditorStore.setStatus("Saving " + tab.name + "...", "info");
|
|
2238
|
-
isSavingRef.current = true;
|
|
2239
|
-
FileService.saveFile(tab.path, tab.content).then(function () {
|
|
2240
|
-
noteLocalSave(tab.path, tab.content);
|
|
2451
|
+
_runSave(tab, function () {
|
|
2241
2452
|
EditorStore.setStatus("Saved", "success");
|
|
2242
2453
|
SearchService.invalidate();
|
|
2243
2454
|
GitService.fetchStatusLite({ background: true });
|
|
2244
|
-
// Reset the AVI clean baseline so undo past this save point shows dirty correctly.
|
|
2245
|
-
var _closeEntry = window.__mbeditorModels && window.__mbeditorModels[tab.path];
|
|
2246
|
-
if (_closeEntry && _closeEntry.model && !_closeEntry.model.isDisposed()) {
|
|
2247
|
-
_closeEntry.cleanVersionId = _closeEntry.model.getAlternativeVersionId();
|
|
2248
|
-
}
|
|
2249
2455
|
TabManager.closeTab(closingPaneId, tab.id);
|
|
2250
|
-
|
|
2251
|
-
pendingReloads: EditorStore.getState().pendingReloads.filter(function (r) {
|
|
2252
|
-
return EditorStore.getState().panes.some(function (p) {
|
|
2253
|
-
return p.tabs.some(function (t) { return t.id === r.tabId; });
|
|
2254
|
-
});
|
|
2255
|
-
})
|
|
2256
|
-
});
|
|
2456
|
+
_prunePendingReloads();
|
|
2257
2457
|
forgetClosedTabs();
|
|
2258
|
-
}
|
|
2259
|
-
EditorStore.setStatus("Save failed: " + err.message, "error");
|
|
2260
|
-
})["finally"](function () {
|
|
2261
|
-
isSavingRef.current = false;
|
|
2262
|
-
setLoading(function (prev) {
|
|
2263
|
-
return _extends({}, prev, { save: false });
|
|
2264
|
-
});
|
|
2458
|
+
}, function () {
|
|
2265
2459
|
setClosingTabId(null);
|
|
2266
2460
|
setClosingPaneId(null);
|
|
2267
2461
|
});
|
|
2268
2462
|
} else {
|
|
2269
2463
|
TabManager.closeTab(closingPaneId, tab.id);
|
|
2270
|
-
|
|
2271
|
-
pendingReloads: EditorStore.getState().pendingReloads.filter(function (r) {
|
|
2272
|
-
return EditorStore.getState().panes.some(function (p) {
|
|
2273
|
-
return p.tabs.some(function (t) { return t.id === r.tabId; });
|
|
2274
|
-
});
|
|
2275
|
-
})
|
|
2276
|
-
});
|
|
2464
|
+
_prunePendingReloads();
|
|
2277
2465
|
forgetClosedTabs();
|
|
2278
2466
|
setClosingTabId(null);
|
|
2279
2467
|
setClosingPaneId(null);
|
|
@@ -2348,7 +2536,10 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2348
2536
|
var input = window.prompt('Save as (path relative to workspace root):', tab.name + '.txt');
|
|
2349
2537
|
if (!input || !input.trim()) return Promise.reject({ cancelled: true });
|
|
2350
2538
|
var newPath = input.trim().replace(/^\/+/, '');
|
|
2539
|
+
var startedAt = Date.now();
|
|
2351
2540
|
return FileService.saveFile(newPath, tab.content).then(function () {
|
|
2541
|
+
var audit = window.MbeditorAudit;
|
|
2542
|
+
if (audit) audit.rec(audit.EV.SAVE, _auditExt(newPath), tab.content.length, Date.now() - startedAt);
|
|
2352
2543
|
noteLocalSave(newPath, tab.content);
|
|
2353
2544
|
SearchService.invalidate();
|
|
2354
2545
|
GitService.fetchStatusLite({ background: true });
|
|
@@ -2373,14 +2564,13 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2373
2564
|
return {
|
|
2374
2565
|
id: p.id,
|
|
2375
2566
|
activeTabId: p.activeTabId,
|
|
2376
|
-
tabs: p.tabs.filter(function(t) { return !t.isCombinedDiff && !t.
|
|
2567
|
+
tabs: p.tabs.filter(function(t) { return !t.isCombinedDiff && !t.isUntitled; }).map(function (t) {
|
|
2377
2568
|
return {
|
|
2378
2569
|
id: t.id,
|
|
2379
2570
|
path: t.path,
|
|
2380
2571
|
name: t.name,
|
|
2381
2572
|
dirty: t.dirty,
|
|
2382
2573
|
viewState: t.viewState,
|
|
2383
|
-
isSettings: !!t.isSettings,
|
|
2384
2574
|
isChangelog: !!t.isChangelog,
|
|
2385
2575
|
isPreview: !!t.isPreview,
|
|
2386
2576
|
previewFor: t.previewFor || null,
|
|
@@ -2407,7 +2597,11 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2407
2597
|
|
|
2408
2598
|
useEffect(function() {
|
|
2409
2599
|
document.documentElement.setAttribute('data-theme', editorPrefs.theme || 'vs-dark');
|
|
2410
|
-
|
|
2600
|
+
// Glass is a layer over the active theme, not a theme, so it rides on its
|
|
2601
|
+
// own attribute and glass.css derives every colour from the theme tokens.
|
|
2602
|
+
if (editorPrefs.glass) document.documentElement.dataset.glass = '1';
|
|
2603
|
+
else delete document.documentElement.dataset.glass;
|
|
2604
|
+
}, [editorPrefs.theme, editorPrefs.glass]);
|
|
2411
2605
|
|
|
2412
2606
|
useEffect(function() {
|
|
2413
2607
|
EditorStore.setState({ editorPrefs: editorPrefs });
|
|
@@ -2531,7 +2725,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2531
2725
|
});
|
|
2532
2726
|
tabs.forEach(function(t) {
|
|
2533
2727
|
if (Object.keys(deps).length >= RAILS_MAX_RESOURCES) return;
|
|
2534
|
-
if (!t.path || t.path
|
|
2728
|
+
if (!t.path || t.path.startsWith('mbeditor://')) return;
|
|
2535
2729
|
var label = resourceLabelFromPath(t.path);
|
|
2536
2730
|
if (label && !deps[label]) deps[label] = t.path;
|
|
2537
2731
|
});
|
|
@@ -2544,7 +2738,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2544
2738
|
var all = {};
|
|
2545
2739
|
state.panes.forEach(function(p) {
|
|
2546
2740
|
p.tabs.forEach(function(t) {
|
|
2547
|
-
if (!t.path || t.path
|
|
2741
|
+
if (!t.path || t.path.startsWith('mbeditor://')) return;
|
|
2548
2742
|
var label = resourceLabelFromPath(t.path);
|
|
2549
2743
|
if (label) all[label] = true;
|
|
2550
2744
|
});
|
|
@@ -2604,14 +2798,14 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2604
2798
|
|
|
2605
2799
|
// ── Collaboration presence (slice 7) ──────────────────────────────────────
|
|
2606
2800
|
// Only real, openable files belong in presence — virtual tabs (diffs, previews,
|
|
2607
|
-
//
|
|
2801
|
+
// changelog) are reported as "no file" so a peer's chip stays blank
|
|
2608
2802
|
// rather than pointing at something click-to-jump can't open.
|
|
2609
2803
|
var _presenceFileFor = function (tab) {
|
|
2610
2804
|
if (!tab || !tab.path) return null;
|
|
2611
2805
|
var p = tab.path;
|
|
2612
|
-
if (tab.isDiff || tab.isCombinedDiff || tab.isCommitGraph || tab.isPreview || tab.
|
|
2806
|
+
if (tab.isDiff || tab.isCombinedDiff || tab.isCommitGraph || tab.isPreview || tab.isChangelog) return null;
|
|
2613
2807
|
if (p.indexOf('diff://') === 0 || p.indexOf('combined-diff://') === 0 || p.indexOf('mbeditor://') === 0) return null;
|
|
2614
|
-
if (p.indexOf('::preview') !== -1
|
|
2808
|
+
if (p.indexOf('::preview') !== -1) return null;
|
|
2615
2809
|
return p;
|
|
2616
2810
|
};
|
|
2617
2811
|
var presenceFile = _presenceFileFor(activeTab);
|
|
@@ -2817,14 +3011,13 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2817
3011
|
|
|
2818
3012
|
var collabPeerIds = Object.keys(collabRoster);
|
|
2819
3013
|
|
|
2820
|
-
//
|
|
2821
|
-
//
|
|
2822
|
-
//
|
|
2823
|
-
//
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
var
|
|
2827
|
-
var collabPeerLabels = !toolbarIconOnly && collabPeerIds.length <= COLLAB_LABEL_LIMIT;
|
|
3014
|
+
// One button per peer stopped working past ~4 people: the titlebar cluster
|
|
3015
|
+
// does not shrink or wrap, so it squeezed the search pill to its floor and
|
|
3016
|
+
// pushed Help / Install off the right edge. Fixed-width stack instead: up to
|
|
3017
|
+
// three overlapping avatars, the rest collapse into one "+N" with a popover.
|
|
3018
|
+
var COLLAB_AVATAR_LIMIT = 3;
|
|
3019
|
+
var collabAvatarIds = collabPeerIds.slice(0, COLLAB_AVATAR_LIMIT);
|
|
3020
|
+
var collabOverflowIds = collabPeerIds.slice(COLLAB_AVATAR_LIMIT);
|
|
2828
3021
|
|
|
2829
3022
|
// Colour is minted from a hash before any peer is known, so it has to be
|
|
2830
3023
|
// reconciled against the roster once one exists. Runs on every roster change;
|
|
@@ -2841,9 +3034,8 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2841
3034
|
// chips sit against the right edge of the titlebar and a left-anchored card
|
|
2842
3035
|
// would run off screen.
|
|
2843
3036
|
var _useStateHover = useState(null);
|
|
2844
|
-
var
|
|
2845
|
-
var
|
|
2846
|
-
var setCollabHover = _useStateHover2[1];
|
|
3037
|
+
var collabHover = _useStateHover[0];
|
|
3038
|
+
var setCollabHover = _useStateHover[1];
|
|
2847
3039
|
|
|
2848
3040
|
var openCollabHover = function (cid, e) {
|
|
2849
3041
|
var r = e.currentTarget.getBoundingClientRect();
|
|
@@ -2853,8 +3045,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2853
3045
|
// Latency and last-seen only need to tick while the card is actually on screen,
|
|
2854
3046
|
// so the interval lives and dies with it. Idle cost stays zero.
|
|
2855
3047
|
var _useStateHoverTick = useState(0);
|
|
2856
|
-
var
|
|
2857
|
-
var setCollabHoverTick = _useStateHoverTick2[1];
|
|
3048
|
+
var setCollabHoverTick = _useStateHoverTick[1];
|
|
2858
3049
|
useEffect(function () {
|
|
2859
3050
|
if (!collabHover) return;
|
|
2860
3051
|
var id = setInterval(function () { setCollabHoverTick(function (n) { return n + 1; }); }, 1000);
|
|
@@ -2885,17 +3076,57 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2885
3076
|
handleSelectFile(followedFile, followedFile.split('/').pop());
|
|
2886
3077
|
}, [followedClientId, followedFile]);
|
|
2887
3078
|
|
|
3079
|
+
// Shared by the avatar stack and the overflow popover so "which peer, what
|
|
3080
|
+
// file, are we following them" is computed once per id instead of twice.
|
|
3081
|
+
var collabPeerInfo = function (cid) {
|
|
3082
|
+
var peer = collabRoster[cid];
|
|
3083
|
+
var file = peer.current_file;
|
|
3084
|
+
return {
|
|
3085
|
+
name: peer.name || 'Anonymous',
|
|
3086
|
+
colour: peer.colour || '#888888',
|
|
3087
|
+
file: file,
|
|
3088
|
+
following: followedClientId === cid,
|
|
3089
|
+
elsewhere: file !== presenceFile
|
|
3090
|
+
};
|
|
3091
|
+
};
|
|
3092
|
+
|
|
3093
|
+
// The "+N" overflow popover. A plain onMouseLeave that closes immediately
|
|
3094
|
+
// makes it uncrossable — the pointer has to travel off the button and onto
|
|
3095
|
+
// the popover — so the close is delayed and cancelled by either one's
|
|
3096
|
+
// onMouseEnter. Click still toggles it directly, which is what makes it
|
|
3097
|
+
// reachable without a mouse at all.
|
|
3098
|
+
// The anchor, not a boolean: the popover renders at the root next to the
|
|
3099
|
+
// hovercard, because an absolutely-positioned child of the title bar is
|
|
3100
|
+
// painted under the editor whatever its z-index.
|
|
3101
|
+
var _useStateOverflow = useState(null);
|
|
3102
|
+
var collabOverflowAnchor = _useStateOverflow[0];
|
|
3103
|
+
var setCollabOverflowAnchor = _useStateOverflow[1];
|
|
3104
|
+
var collabOverflowOpen = !!collabOverflowAnchor;
|
|
3105
|
+
var anchorFrom = function (el) {
|
|
3106
|
+
var r = el.getBoundingClientRect();
|
|
3107
|
+
return { top: r.bottom + 4, right: window.innerWidth - r.right };
|
|
3108
|
+
};
|
|
3109
|
+
var collabOverflowCloseTimerRef = useRef(null);
|
|
3110
|
+
var cancelOverflowClose = function () {
|
|
3111
|
+
if (collabOverflowCloseTimerRef.current) {
|
|
3112
|
+
clearTimeout(collabOverflowCloseTimerRef.current);
|
|
3113
|
+
collabOverflowCloseTimerRef.current = null;
|
|
3114
|
+
}
|
|
3115
|
+
};
|
|
3116
|
+
var scheduleOverflowClose = function () {
|
|
3117
|
+
cancelOverflowClose();
|
|
3118
|
+
collabOverflowCloseTimerRef.current = setTimeout(function () { setCollabOverflowAnchor(null); }, 250);
|
|
3119
|
+
};
|
|
3120
|
+
|
|
2888
3121
|
// Phase 7: Per-file last-commit info shown in the status bar
|
|
2889
3122
|
var _useState31 = useState(null);
|
|
2890
|
-
var
|
|
2891
|
-
var
|
|
2892
|
-
var setActiveFileCommit = _useState32[1];
|
|
3123
|
+
var activeFileCommit = _useState31[0];
|
|
3124
|
+
var setActiveFileCommit = _useState31[1];
|
|
2893
3125
|
|
|
2894
3126
|
// EOL indicator — tracks current line-ending style of the active file
|
|
2895
3127
|
var _useState31e = useState(null);
|
|
2896
|
-
var
|
|
2897
|
-
var
|
|
2898
|
-
var setActiveEOL = _useState31e2[1];
|
|
3128
|
+
var activeEOL = _useState31e[0];
|
|
3129
|
+
var setActiveEOL = _useState31e[1];
|
|
2899
3130
|
|
|
2900
3131
|
useEffect(function () {
|
|
2901
3132
|
if (!gitAvailable || !activeTab || activeTab.isDiff || activeTab.isCombinedDiff || activeTab.isCommitGraph || !activeTab.path || activeTab.path.indexOf('://') >= 0) {
|
|
@@ -2915,7 +3146,9 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2915
3146
|
});
|
|
2916
3147
|
}, [activeTab ? activeTab.id : null, gitAvailable]);
|
|
2917
3148
|
|
|
2918
|
-
// Update EOL indicator
|
|
3149
|
+
// Update EOL indicator on external loads, not on every content write. The
|
|
3150
|
+
// content effect fires on the 250ms typing flush, and scanning the whole
|
|
3151
|
+
// buffer for '\r' there was pure waste — typing cannot change the line ending.
|
|
2919
3152
|
useEffect(function () {
|
|
2920
3153
|
if (!activeTab || typeof activeTab.content !== 'string' ||
|
|
2921
3154
|
activeTab.isDiff || activeTab.isCombinedDiff || activeTab.isCommitGraph || activeTab.isPreview) {
|
|
@@ -2929,7 +3162,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2929
3162
|
} else {
|
|
2930
3163
|
setActiveEOL('LF');
|
|
2931
3164
|
}
|
|
2932
|
-
}, [activeTab ? activeTab.id : null, activeTab ? activeTab.
|
|
3165
|
+
}, [activeTab ? activeTab.id : null, activeTab ? activeTab.externalContentVersion : null]);
|
|
2933
3166
|
|
|
2934
3167
|
useEffect(function () {
|
|
2935
3168
|
if (!activeTab || typeof activeTab.content !== 'string') return;
|
|
@@ -3006,15 +3239,35 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3006
3239
|
// by a formatter problem).
|
|
3007
3240
|
var _formatContentForSave = function _formatContentForSave(tab) {
|
|
3008
3241
|
var isRubyLang = /\.(rb|rake|gemspec)$/.test(tab.path) || /(?:^|\/)(Rakefile|Gemfile)$/.test(tab.path);
|
|
3242
|
+
var startedAt = Date.now();
|
|
3009
3243
|
if (isRubyLang) {
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3244
|
+
// Same route as the Format button: formatRubySource tries ruby-lsp and
|
|
3245
|
+
// falls back to /format's `rubocop -A`. Calling /format directly here
|
|
3246
|
+
// meant saving a Ruby file could produce different bytes from formatting
|
|
3247
|
+
// the same file by hand — the two-formatters-disagree trap this file
|
|
3248
|
+
// already documents for JS — and gating on RuboCop alone made
|
|
3249
|
+
// format-on-save silently do nothing when only ruby-lsp was present.
|
|
3250
|
+
if (!rubocopAvailable && !window.MBEDITOR_RUBY_LSP_AVAILABLE) return Promise.resolve(null);
|
|
3251
|
+
return formatRubySource(tab.path, tab.content)
|
|
3252
|
+
.then(function (res) {
|
|
3253
|
+
var out = (res && res.content) || null;
|
|
3254
|
+
_recFormat(tab.path, startedAt, out);
|
|
3255
|
+
return out;
|
|
3256
|
+
})
|
|
3257
|
+
["catch"](function () {
|
|
3258
|
+
_recErr('FORMAT');
|
|
3259
|
+
return null;
|
|
3260
|
+
});
|
|
3014
3261
|
}
|
|
3015
3262
|
var parserName = prettierParserFor(tab.path);
|
|
3016
3263
|
if (!parserName) return Promise.resolve(null);
|
|
3017
|
-
return runPrettier(tab.content, editorPrefs, parserName)
|
|
3264
|
+
return runPrettier(tab.content, editorPrefs, parserName).then(function (out) {
|
|
3265
|
+
_recFormat(tab.path, startedAt, out);
|
|
3266
|
+
return out;
|
|
3267
|
+
})["catch"](function () {
|
|
3268
|
+
_recErr('FORMAT');
|
|
3269
|
+
return null;
|
|
3270
|
+
});
|
|
3018
3271
|
};
|
|
3019
3272
|
|
|
3020
3273
|
// Re-read a tab from the store after flushing TabManager's throttled content
|
|
@@ -3053,6 +3306,40 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3053
3306
|
_doSave(paneId, tab);
|
|
3054
3307
|
};
|
|
3055
3308
|
|
|
3309
|
+
// Shared save-execution flow for a known-on-disk (non-untitled) tab: the
|
|
3310
|
+
// saving status/loading flag, the FileService write, the AVI clean-baseline
|
|
3311
|
+
// reset, and save-failed/cleanup handling. `onSuccess` runs whatever the
|
|
3312
|
+
// caller does after a successful write (before the finally cleanup);
|
|
3313
|
+
// `onFinally` runs any extra caller-specific cleanup after the standard one.
|
|
3314
|
+
var _runSave = function _runSave(tab, onSuccess, onFinally) {
|
|
3315
|
+
setLoading(function (prev) {
|
|
3316
|
+
return _extends({}, prev, { save: true });
|
|
3317
|
+
});
|
|
3318
|
+
EditorStore.setStatus("Saving " + tab.name + "...", "info");
|
|
3319
|
+
isSavingRef.current = true;
|
|
3320
|
+
var startedAt = Date.now();
|
|
3321
|
+
return FileService.saveFile(tab.path, tab.content).then(function () {
|
|
3322
|
+
var audit = window.MbeditorAudit;
|
|
3323
|
+
if (audit) audit.rec(audit.EV.SAVE, _auditExt(tab.path), tab.content.length, Date.now() - startedAt);
|
|
3324
|
+
noteLocalSave(tab.path, tab.content);
|
|
3325
|
+
// Reset the AVI clean baseline so undo past this save point shows dirty correctly.
|
|
3326
|
+
var _modelEntry = window.__mbeditorModels && window.__mbeditorModels[tab.path];
|
|
3327
|
+
if (_modelEntry && _modelEntry.model && !_modelEntry.model.isDisposed()) {
|
|
3328
|
+
_modelEntry.cleanVersionId = _modelEntry.model.getAlternativeVersionId();
|
|
3329
|
+
}
|
|
3330
|
+
onSuccess();
|
|
3331
|
+
})["catch"](function (err) {
|
|
3332
|
+
_recErr('SAVE');
|
|
3333
|
+
EditorStore.setStatus("Save failed: " + err.message, "error");
|
|
3334
|
+
})["finally"](function () {
|
|
3335
|
+
isSavingRef.current = false;
|
|
3336
|
+
setLoading(function (prev) {
|
|
3337
|
+
return _extends({}, prev, { save: false });
|
|
3338
|
+
});
|
|
3339
|
+
if (onFinally) onFinally();
|
|
3340
|
+
});
|
|
3341
|
+
};
|
|
3342
|
+
|
|
3056
3343
|
var _doSave = function _doSave(paneId, tab) {
|
|
3057
3344
|
if (tab.isUntitled) {
|
|
3058
3345
|
saveUntitledTab(paneId, tab)["catch"](function (err) {
|
|
@@ -3061,13 +3348,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3061
3348
|
});
|
|
3062
3349
|
return;
|
|
3063
3350
|
}
|
|
3064
|
-
|
|
3065
|
-
return _extends({}, prev, { save: true });
|
|
3066
|
-
});
|
|
3067
|
-
EditorStore.setStatus("Saving " + tab.name + "...", "info");
|
|
3068
|
-
isSavingRef.current = true;
|
|
3069
|
-
FileService.saveFile(tab.path, tab.content).then(function () {
|
|
3070
|
-
noteLocalSave(tab.path, tab.content);
|
|
3351
|
+
_runSave(tab, function () {
|
|
3071
3352
|
var newPanes = EditorStore.getState().panes.map(function (p) {
|
|
3072
3353
|
if (p.id === paneId) {
|
|
3073
3354
|
return _extends({}, p, { tabs: p.tabs.map(function (t) {
|
|
@@ -3077,13 +3358,10 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3077
3358
|
return p;
|
|
3078
3359
|
});
|
|
3079
3360
|
EditorStore.setState({ panes: newPanes });
|
|
3080
|
-
// Reset the AVI clean baseline so undo past this save point shows dirty correctly.
|
|
3081
|
-
var _modelEntry = window.__mbeditorModels && window.__mbeditorModels[tab.path];
|
|
3082
|
-
if (_modelEntry && _modelEntry.model && !_modelEntry.model.isDisposed()) {
|
|
3083
|
-
_modelEntry.cleanVersionId = _modelEntry.model.getAlternativeVersionId();
|
|
3084
|
-
}
|
|
3085
3361
|
// Collab: push a fresh snapshot so the server compacts the buffered deltas.
|
|
3086
|
-
|
|
3362
|
+
// Only for an attached tab — a deferred or degraded room pushing its local
|
|
3363
|
+
// buffer as a snapshot could seed a second copy into what a peer holds.
|
|
3364
|
+
if (typeof CollaborationService !== 'undefined' && CollaborationService.isAttached(tab.path)) {
|
|
3087
3365
|
CollaborationService.pushSnapshot(tab.path);
|
|
3088
3366
|
}
|
|
3089
3367
|
EditorStore.setStatus("Saved", "success");
|
|
@@ -3102,8 +3380,11 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3102
3380
|
// sprockets/react-rails pipeline can't transform (errors Monaco's TS
|
|
3103
3381
|
// worker misses). Save only — never per keystroke.
|
|
3104
3382
|
if (jsSyntaxCheckAvailableRef.current && /\.(js|jsx)$/i.test(tab.path)) {
|
|
3383
|
+
var lintStartedAt = Date.now();
|
|
3105
3384
|
FileService.lintFile(tab.path, tab.content, 'javascript').then(function (res) {
|
|
3106
3385
|
var babelMarkers = (res && res.markers) || [];
|
|
3386
|
+
var audit = window.MbeditorAudit;
|
|
3387
|
+
if (audit) audit.rec(audit.EV.LINT, _auditExt(tab.path), Date.now() - lintStartedAt, babelMarkers.length);
|
|
3107
3388
|
if (babelMarkers.length > 0) {
|
|
3108
3389
|
applyMarkersForTab(tab.id, babelMarkers);
|
|
3109
3390
|
EditorStore.setStatus('Saved — babel: ' + babelMarkers[0].message, 'warning');
|
|
@@ -3113,7 +3394,9 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3113
3394
|
// applyMarkersForTab keeps the previous map when it is already empty.
|
|
3114
3395
|
applyMarkersForTab(tab.id, []);
|
|
3115
3396
|
}
|
|
3116
|
-
})["catch"](function () {
|
|
3397
|
+
})["catch"](function () {
|
|
3398
|
+
_recErr('LINT');
|
|
3399
|
+
});
|
|
3117
3400
|
}
|
|
3118
3401
|
|
|
3119
3402
|
// The server broadcasts files_changed for this very write, and the
|
|
@@ -3124,13 +3407,6 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3124
3407
|
// file every time does by definition, so the duplicate was the expensive
|
|
3125
3408
|
// one. Without a socket no broadcast arrives, so keep it for that case.
|
|
3126
3409
|
if (!_socketWillBroadcast()) GitService.fetchStatusLite({ background: true });
|
|
3127
|
-
})["catch"](function (err) {
|
|
3128
|
-
EditorStore.setStatus("Save failed: " + err.message, "error");
|
|
3129
|
-
})["finally"](function () {
|
|
3130
|
-
isSavingRef.current = false;
|
|
3131
|
-
return setLoading(function (prev) {
|
|
3132
|
-
return _extends({}, prev, { save: false });
|
|
3133
|
-
});
|
|
3134
3410
|
});
|
|
3135
3411
|
};
|
|
3136
3412
|
|
|
@@ -3219,7 +3495,15 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3219
3495
|
// disk stayed dirty and the only thing on offer was to hit Save All again.
|
|
3220
3496
|
// Each file is now settled on its own result.
|
|
3221
3497
|
var promises = dirtyTabs.map(function (tab) {
|
|
3222
|
-
|
|
3498
|
+
var startedAt = Date.now();
|
|
3499
|
+
return FileService.saveFile(tab.path, tab.content).then(function (res) {
|
|
3500
|
+
var audit = window.MbeditorAudit;
|
|
3501
|
+
if (audit) audit.rec(audit.EV.SAVE, _auditExt(tab.path), tab.content.length, Date.now() - startedAt);
|
|
3502
|
+
return res;
|
|
3503
|
+
}, function (err) {
|
|
3504
|
+
_recErr('SAVE');
|
|
3505
|
+
throw err;
|
|
3506
|
+
});
|
|
3223
3507
|
});
|
|
3224
3508
|
Promise.allSettled(promises).then(function (results) {
|
|
3225
3509
|
var saved = dirtyTabs.filter(function (tab, i) { return results[i].status === "fulfilled"; });
|
|
@@ -3367,6 +3651,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3367
3651
|
// rather than the button appearing to do nothing.
|
|
3368
3652
|
var formatTabContent = function formatTabContent(tab, prefs) {
|
|
3369
3653
|
prefs = prefs || editorPrefs;
|
|
3654
|
+
var startedAt = Date.now();
|
|
3370
3655
|
if (isRubyPath(tab.path) || tab.path.endsWith('.rake')) {
|
|
3371
3656
|
if (!rubocopAvailable) return Promise.reject(new Error("RuboCop is not available for this workspace."));
|
|
3372
3657
|
// RuboCop's output is taken as-is. Ruby indentation belongs to the
|
|
@@ -3375,13 +3660,18 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3375
3660
|
// the same file. The old code tried the opposite — converting the source
|
|
3376
3661
|
// to tabs *before* handing it over — which RuboCop simply discarded.
|
|
3377
3662
|
return formatRubySource(tab.path, tab.content).then(function (res) {
|
|
3378
|
-
|
|
3663
|
+
var out = (res && res.content) || null;
|
|
3664
|
+
_recFormat(tab.path, startedAt, out);
|
|
3665
|
+
return out;
|
|
3379
3666
|
});
|
|
3380
3667
|
}
|
|
3381
3668
|
|
|
3382
3669
|
var parserName = prettierParserFor(tab.path);
|
|
3383
3670
|
if (!parserName) return Promise.resolve(null);
|
|
3384
|
-
return runPrettier(tab.content, prefs, parserName)
|
|
3671
|
+
return runPrettier(tab.content, prefs, parserName).then(function (out) {
|
|
3672
|
+
_recFormat(tab.path, startedAt, out);
|
|
3673
|
+
return out;
|
|
3674
|
+
});
|
|
3385
3675
|
};
|
|
3386
3676
|
|
|
3387
3677
|
// Markers are per-model and only the mounted editor re-lints, so a document
|
|
@@ -3391,7 +3681,19 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3391
3681
|
// counts because only the visible tab was re-checked. Dropping them says
|
|
3392
3682
|
// "not known yet", which is true: the file re-lints when you open it.
|
|
3393
3683
|
var discardStaleMarkers = function discardStaleMarkers(path) {
|
|
3394
|
-
if (!path
|
|
3684
|
+
if (!path) return;
|
|
3685
|
+
|
|
3686
|
+
// The React map has to go too, not just Monaco's copy. It is what TabBar
|
|
3687
|
+
// counts, and EditorPanel re-applies it to the model the next time that tab
|
|
3688
|
+
// mounts — so clearing only Monaco left every background tab primed to put
|
|
3689
|
+
// its stale squiggles straight back on the next tab switch.
|
|
3690
|
+
EditorStore.getState().panes.forEach(function (p) {
|
|
3691
|
+
p.tabs.forEach(function (t) {
|
|
3692
|
+
if (t.path === path) applyMarkersForTab(t.id, []);
|
|
3693
|
+
});
|
|
3694
|
+
});
|
|
3695
|
+
|
|
3696
|
+
if (!window.monaco || !window.monaco.editor) return;
|
|
3395
3697
|
var entry = window.__mbeditorModels && window.__mbeditorModels[path];
|
|
3396
3698
|
if (!entry || !entry.model || entry.model.isDisposed()) return;
|
|
3397
3699
|
|
|
@@ -3462,6 +3764,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3462
3764
|
EditorStore.setStatus(formatted === originalContent ? "Already formatted" : "Formatted (Unsaved)", "success");
|
|
3463
3765
|
GitService.fetchStatus();
|
|
3464
3766
|
})["catch"](function (err) {
|
|
3767
|
+
_recErr('FORMAT');
|
|
3465
3768
|
EditorStore.setStatus("Format failed: " + (err && err.message ? err.message : err), "error");
|
|
3466
3769
|
})["finally"](function () {
|
|
3467
3770
|
setLoading(function (prev) { return _extends({}, prev, { format: false }); });
|
|
@@ -3479,7 +3782,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3479
3782
|
var targets = [];
|
|
3480
3783
|
EditorStore.getState().panes.forEach(function (p) {
|
|
3481
3784
|
p.tabs.forEach(function (t) {
|
|
3482
|
-
if (t.path && !t.path.startsWith('mbeditor://') &&
|
|
3785
|
+
if (t.path && !t.path.startsWith('mbeditor://') && typeof t.content === 'string') {
|
|
3483
3786
|
targets.push({ paneId: p.id, tab: t });
|
|
3484
3787
|
}
|
|
3485
3788
|
});
|
|
@@ -3520,23 +3823,6 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3520
3823
|
});
|
|
3521
3824
|
};
|
|
3522
3825
|
|
|
3523
|
-
var TEST_CACHE_PREFIX = 'mbeditor_test_result_';
|
|
3524
|
-
|
|
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
|
-
var saveCachedTestResult = function saveCachedTestResult(filePath, result) {
|
|
3535
|
-
try {
|
|
3536
|
-
localStorage.setItem(TEST_CACHE_PREFIX + filePath, JSON.stringify(result));
|
|
3537
|
-
} catch (e) {}
|
|
3538
|
-
};
|
|
3539
|
-
|
|
3540
3826
|
var executeTestRun = function executeTestRun(filePath, line) {
|
|
3541
3827
|
setTestLoading(true);
|
|
3542
3828
|
EditorStore.setStatus(line ? 'Running test at line ' + line + '...' : 'Running tests...', 'info');
|
|
@@ -3572,21 +3858,6 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3572
3858
|
});
|
|
3573
3859
|
};
|
|
3574
3860
|
|
|
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
3861
|
var handleRerunTest = function handleRerunTest() {
|
|
3591
3862
|
if (!activeTab || !activeTab.path) return;
|
|
3592
3863
|
if (testLoading) return;
|
|
@@ -3627,6 +3898,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3627
3898
|
searchOffsetRef.current = 0;
|
|
3628
3899
|
searchLoadingMoreRef.current = false;
|
|
3629
3900
|
searchQueryRef.current = q;
|
|
3901
|
+
setSearchCollapsedFiles({});
|
|
3630
3902
|
EditorStore.setState({ searchResults: [], searchHasMore: false });
|
|
3631
3903
|
EditorStore.setStatus("Searching project...", "info");
|
|
3632
3904
|
SearchService.projectSearch(q, 0, SearchService.PAGE_SIZE, { regex: searchUseRegexRef.current, matchCase: searchMatchCaseRef.current, wholeWord: searchWholeWordRef.current }).then(function (res) {
|
|
@@ -3659,6 +3931,15 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3659
3931
|
});
|
|
3660
3932
|
};
|
|
3661
3933
|
|
|
3934
|
+
// Paging is scroll-driven, so a list too short to scroll — few hits, or a
|
|
3935
|
+
// big file group just collapsed — would show "Loading more…" and never load
|
|
3936
|
+
// it. Fill the viewport whenever the list stops being scrollable.
|
|
3937
|
+
useEffect(function () {
|
|
3938
|
+
var el = searchResultsContainerRef.current;
|
|
3939
|
+
if (!searchHasMore || !el) return;
|
|
3940
|
+
if (el.scrollHeight <= el.clientHeight + 200) loadMoreSearchResults();
|
|
3941
|
+
}, [searchHasMore, state.searchResults, searchCollapsedFiles]);
|
|
3942
|
+
|
|
3662
3943
|
var handleSearchChange = function handleSearchChange(e) {
|
|
3663
3944
|
var val = e.target.value;
|
|
3664
3945
|
if (!val) { clearSearch(); return; }
|
|
@@ -3795,10 +4076,6 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3795
4076
|
setShowLogPanel(function (prev) { return !prev; });
|
|
3796
4077
|
};
|
|
3797
4078
|
|
|
3798
|
-
var toggleTestRunPanel = function toggleTestRunPanel() {
|
|
3799
|
-
setShowTestRunPanel(function (prev) { return !prev; });
|
|
3800
|
-
};
|
|
3801
|
-
|
|
3802
4079
|
var toggleProblemsPanel = function toggleProblemsPanel() {
|
|
3803
4080
|
setShowProblemsPanel(function (prev) { return !prev; });
|
|
3804
4081
|
};
|
|
@@ -3885,12 +4162,17 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3885
4162
|
});
|
|
3886
4163
|
};
|
|
3887
4164
|
|
|
3888
|
-
var finishImport = function finishImport(result) {
|
|
3889
|
-
var
|
|
4165
|
+
var finishImport = function finishImport(result, destFolder) {
|
|
4166
|
+
var written = result.imported || [];
|
|
4167
|
+
var imported = written.length;
|
|
3890
4168
|
var skipped = (result.conflicts || []).length;
|
|
3891
4169
|
var failed = (result.errors || []).length;
|
|
3892
4170
|
|
|
3893
|
-
|
|
4171
|
+
// Say where the files went. A bulk upload that reports only a count looks
|
|
4172
|
+
// the same whether it landed where you meant it to or in the workspace
|
|
4173
|
+
// root, and the destination is the whole question a folder import raises.
|
|
4174
|
+
var where = destFolder ? ' to ' + destFolder : ' to the workspace root';
|
|
4175
|
+
var parts = [imported + ' file' + (imported === 1 ? '' : 's') + ' imported' + (imported > 0 ? where : '')];
|
|
3894
4176
|
if (skipped > 0) parts.push(skipped + ' skipped');
|
|
3895
4177
|
if (failed > 0) parts.push(failed + ' failed');
|
|
3896
4178
|
|
|
@@ -3898,7 +4180,19 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3898
4180
|
EditorStore.setStatus(parts.join(', ') + '.', level);
|
|
3899
4181
|
|
|
3900
4182
|
if (imported > 0) {
|
|
3901
|
-
|
|
4183
|
+
// Expand down to what was just written, so the tree actually shows it —
|
|
4184
|
+
// importing into a collapsed folder otherwise leaves the explorer looking
|
|
4185
|
+
// untouched. Deliberately does not *select* the folder, which would move
|
|
4186
|
+
// the selection somewhere the user did not put it.
|
|
4187
|
+
var landed = parentDir(written[0].path);
|
|
4188
|
+
var toExpand = {};
|
|
4189
|
+
var bits = landed ? landed.split('/') : [];
|
|
4190
|
+
for (var i = 1; i <= bits.length; i++) toExpand[bits.slice(0, i).join('/')] = true;
|
|
4191
|
+
|
|
4192
|
+
refreshProjectTree().then(function() {
|
|
4193
|
+
if (bits.length) setExpandedDirs(function (prev) { return Object.assign({}, prev, toExpand); });
|
|
4194
|
+
GitService.fetchStatus();
|
|
4195
|
+
});
|
|
3902
4196
|
}
|
|
3903
4197
|
};
|
|
3904
4198
|
|
|
@@ -3908,8 +4202,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3908
4202
|
// rather than a disaster. Real targets stopPropagation before this runs.
|
|
3909
4203
|
useEffect(function () {
|
|
3910
4204
|
var swallow = function (e) {
|
|
3911
|
-
|
|
3912
|
-
if (Array.prototype.indexOf.call(types, 'Files') === -1) return;
|
|
4205
|
+
if (!FileImport.hasExternalFiles(e.dataTransfer)) return;
|
|
3913
4206
|
e.preventDefault();
|
|
3914
4207
|
// dragover is left alone beyond preventDefault: setting dropEffect here
|
|
3915
4208
|
// would override the 'copy' cursor the tree sets on a valid folder.
|
|
@@ -3937,12 +4230,20 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3937
4230
|
(entries.length === 1 ? '' : 's') + '...', 'info');
|
|
3938
4231
|
}
|
|
3939
4232
|
|
|
3940
|
-
|
|
4233
|
+
var where = targetFolderPath || 'the workspace root';
|
|
4234
|
+
var noun = entries.length + ' file' + (entries.length === 1 ? '' : 's');
|
|
4235
|
+
var progress = function (percent) {
|
|
4236
|
+
EditorStore.setStatus(percent === null
|
|
4237
|
+
? 'Uploaded ' + noun + ' to ' + where + ', writing them...'
|
|
4238
|
+
: 'Uploading ' + noun + ' to ' + where + '... ' + percent + '%', 'info');
|
|
4239
|
+
};
|
|
4240
|
+
|
|
4241
|
+
return FileService.importFiles(FileImport.buildFormData(entries, targetFolderPath, 'ask'), progress)
|
|
3941
4242
|
.then(function(result) {
|
|
3942
4243
|
if (result.conflicts && result.conflicts.length > 0) {
|
|
3943
4244
|
setImportConflict({ result: result, entries: entries, targetFolderPath: targetFolderPath });
|
|
3944
4245
|
} else {
|
|
3945
|
-
finishImport(result);
|
|
4246
|
+
finishImport(result, targetFolderPath);
|
|
3946
4247
|
}
|
|
3947
4248
|
})['catch'](function(err) {
|
|
3948
4249
|
var message = err && err.response && err.response.data && err.response.data.error || err.message;
|
|
@@ -3955,14 +4256,14 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3955
4256
|
setImportConflict(null);
|
|
3956
4257
|
if (!pending) return;
|
|
3957
4258
|
|
|
3958
|
-
if (mode === 'skip') { finishImport(pending.result); return; }
|
|
4259
|
+
if (mode === 'skip') { finishImport(pending.result, pending.targetFolderPath); return; }
|
|
3959
4260
|
|
|
3960
4261
|
var retry = FileImport.conflictedEntries(
|
|
3961
4262
|
pending.entries,
|
|
3962
4263
|
pending.targetFolderPath,
|
|
3963
4264
|
pending.result.conflicts
|
|
3964
4265
|
);
|
|
3965
|
-
if (retry.length === 0) { finishImport(pending.result); return; }
|
|
4266
|
+
if (retry.length === 0) { finishImport(pending.result, pending.targetFolderPath); return; }
|
|
3966
4267
|
|
|
3967
4268
|
FileService.importFiles(FileImport.buildFormData(retry, pending.targetFolderPath, mode))
|
|
3968
4269
|
.then(function(second) {
|
|
@@ -3970,39 +4271,88 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3970
4271
|
imported: (pending.result.imported || []).concat(second.imported || []),
|
|
3971
4272
|
conflicts: [],
|
|
3972
4273
|
errors: (pending.result.errors || []).concat(second.errors || [])
|
|
3973
|
-
});
|
|
4274
|
+
}, pending.targetFolderPath);
|
|
3974
4275
|
})['catch'](function(err) {
|
|
3975
4276
|
var message = err && err.response && err.response.data && err.response.data.error || err.message;
|
|
3976
4277
|
EditorStore.setStatus('Import failed: ' + message, 'error');
|
|
3977
4278
|
});
|
|
3978
4279
|
};
|
|
3979
4280
|
|
|
3980
|
-
//
|
|
3981
|
-
//
|
|
3982
|
-
//
|
|
3983
|
-
//
|
|
3984
|
-
//
|
|
4281
|
+
// /raw stays a bare anchor for the single-file case — nothing buffered
|
|
4282
|
+
// client-side. /archive can fail with a JSON error body a plain anchor
|
|
4283
|
+
// navigation can't see, so that path goes through fetch instead.
|
|
4284
|
+
// Right-clicking a node inside a multi-selection acts on the whole selection;
|
|
4285
|
+
// right-clicking outside it acts on that node alone. The standard file-manager
|
|
4286
|
+
// rule, and why FileTree's onContextMenu stopped collapsing the set. Every
|
|
4287
|
+
// menu action that can operate on more than one path asks this.
|
|
4288
|
+
var actsOnSelection = function actsOnSelection(node) {
|
|
4289
|
+
return !!(node && selectedPaths && selectedPaths.size > 1 && selectedPaths.has(node.path));
|
|
4290
|
+
};
|
|
4291
|
+
|
|
3985
4292
|
var handleDownloadFile = function handleDownloadFile(node) {
|
|
3986
|
-
if (!node
|
|
3987
|
-
var
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
4293
|
+
if (!node) return;
|
|
4294
|
+
var isMultiSelect = actsOnSelection(node);
|
|
4295
|
+
if (!isMultiSelect && node.type === 'file') {
|
|
4296
|
+
var link = document.createElement('a');
|
|
4297
|
+
link.href = window.mbeditorBasePath() + '/raw?download=1&path=' + encodeURIComponent(node.path);
|
|
4298
|
+
link.download = node.name;
|
|
4299
|
+
link.rel = 'noopener';
|
|
4300
|
+
document.body.appendChild(link);
|
|
4301
|
+
link.click();
|
|
4302
|
+
document.body.removeChild(link);
|
|
4303
|
+
EditorStore.setStatus('Downloading ' + node.name + '...', 'info');
|
|
4304
|
+
return;
|
|
4305
|
+
}
|
|
4306
|
+
var paths = isMultiSelect ? Array.from(selectedPaths) : [node.path];
|
|
4307
|
+
var url = window.mbeditorBasePath() + '/archive?' + paths.map(function (p) {
|
|
4308
|
+
return 'paths[]=' + encodeURIComponent(p);
|
|
4309
|
+
}).join('&');
|
|
4310
|
+
fetch(url).then(function (resp) {
|
|
4311
|
+
if (!resp.ok) {
|
|
4312
|
+
return resp.json().catch(function () { return {}; }).then(function (body) {
|
|
4313
|
+
var message = (body && body.error) || ('HTTP ' + resp.status);
|
|
4314
|
+
EditorStore.setStatus('Download failed: ' + message, 'error');
|
|
4315
|
+
});
|
|
4316
|
+
}
|
|
4317
|
+
var disposition = resp.headers.get('Content-Disposition');
|
|
4318
|
+
var match = disposition && disposition.match(/filename="([^"]+)"/);
|
|
4319
|
+
var filename = (match && match[1]) || 'archive.tar.gz';
|
|
4320
|
+
return resp.blob().then(function (blob) {
|
|
4321
|
+
var objectUrl = URL.createObjectURL(blob);
|
|
4322
|
+
var archiveLink = document.createElement('a');
|
|
4323
|
+
archiveLink.href = objectUrl;
|
|
4324
|
+
archiveLink.download = filename;
|
|
4325
|
+
archiveLink.rel = 'noopener';
|
|
4326
|
+
document.body.appendChild(archiveLink);
|
|
4327
|
+
archiveLink.click();
|
|
4328
|
+
document.body.removeChild(archiveLink);
|
|
4329
|
+
// Revoking in the same tick can free the blob before the browser has
|
|
4330
|
+
// started reading it, which cancels the download instead of saving it.
|
|
4331
|
+
setTimeout(function () { URL.revokeObjectURL(objectUrl); }, 60000);
|
|
4332
|
+
EditorStore.setStatus('Downloading ' + filename + '...', 'info');
|
|
4333
|
+
});
|
|
4334
|
+
}).catch(function (err) {
|
|
4335
|
+
EditorStore.setStatus('Download failed: ' + (err && err.message || 'network error'), 'error');
|
|
4336
|
+
});
|
|
4337
|
+
};
|
|
4338
|
+
|
|
4339
|
+
var downloadMenuLabel = function downloadMenuLabel(node) {
|
|
4340
|
+
if (!node) return 'Download';
|
|
4341
|
+
if (actsOnSelection(node)) return 'Download ' + selectedPaths.size + ' items';
|
|
4342
|
+
if (node.type === 'folder') return 'Download folder';
|
|
4343
|
+
return 'Download';
|
|
4344
|
+
};
|
|
4345
|
+
|
|
4346
|
+
// Deleting is irreversible, so the item has to say how much it will take —
|
|
4347
|
+
// "Delete" over a 3-item selection would understate it.
|
|
4348
|
+
var deleteMenuLabel = function deleteMenuLabel(node) {
|
|
4349
|
+
return actsOnSelection(node) ? 'Delete ' + selectedPaths.size + ' items' : 'Delete';
|
|
3995
4350
|
};
|
|
3996
4351
|
|
|
3997
4352
|
// A file node means "upload alongside this file", so the dialog opens on its
|
|
3998
4353
|
// parent — right-clicking a file to upload next to it is the common gesture.
|
|
3999
4354
|
var openImportDialog = function openImportDialog(node) {
|
|
4000
|
-
var folder = '';
|
|
4001
|
-
if (node && node.type === 'folder') {
|
|
4002
|
-
folder = node.path;
|
|
4003
|
-
} else if (node && node.path) {
|
|
4004
|
-
folder = node.path.split('/').slice(0, -1).join('/');
|
|
4005
|
-
}
|
|
4355
|
+
var folder = node ? (node.type === 'folder' ? node.path : parentDir(node.path)) : '';
|
|
4006
4356
|
setImportDialog({ initialFolder: folder });
|
|
4007
4357
|
};
|
|
4008
4358
|
|
|
@@ -4013,13 +4363,18 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
4013
4363
|
|
|
4014
4364
|
var openContextMenu = function openContextMenu(e, node) {
|
|
4015
4365
|
setContextMenu({ x: e.clientX, y: e.clientY, node: node });
|
|
4016
|
-
handleNodeSelect(node);
|
|
4366
|
+
if (!actsOnSelection(node)) handleNodeSelect(node);
|
|
4017
4367
|
};
|
|
4018
4368
|
|
|
4019
4369
|
var closeContextMenu = function closeContextMenu() {
|
|
4020
4370
|
return setContextMenu(null);
|
|
4021
4371
|
};
|
|
4022
4372
|
|
|
4373
|
+
// Clamp into view whenever the menu opens at a new position.
|
|
4374
|
+
useEffect(function () {
|
|
4375
|
+
if (contextMenu) window.clampMenuIntoView(contextMenuRef.current);
|
|
4376
|
+
}, [contextMenu]);
|
|
4377
|
+
|
|
4023
4378
|
var handleContextMenuAction = function handleContextMenuAction(action) {
|
|
4024
4379
|
var node = contextMenu && contextMenu.node;
|
|
4025
4380
|
closeContextMenu();
|
|
@@ -4078,17 +4433,34 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
4078
4433
|
document.body.style.userSelect = 'none';
|
|
4079
4434
|
};
|
|
4080
4435
|
|
|
4436
|
+
var startDrawerResize = function startDrawerResize(mode, startHeight, storeKey) {
|
|
4437
|
+
return function (e) {
|
|
4438
|
+
e.preventDefault();
|
|
4439
|
+
resizeSessionRef.current = { mode: mode, startY: e.clientY, startHeight: startHeight, storeKey: storeKey };
|
|
4440
|
+
setActiveResizeMode(mode);
|
|
4441
|
+
document.body.style.cursor = 'row-resize';
|
|
4442
|
+
document.body.style.userSelect = 'none';
|
|
4443
|
+
};
|
|
4444
|
+
};
|
|
4445
|
+
|
|
4081
4446
|
var handleActivityBarClick = function handleActivityBarClick(tab) {
|
|
4082
4447
|
if (tab === 'settings') {
|
|
4083
|
-
|
|
4448
|
+
setShowModelGraphView(false);
|
|
4449
|
+
setShowSettings(!showSettings);
|
|
4084
4450
|
return;
|
|
4085
4451
|
}
|
|
4086
4452
|
// The model graph is a view, not a panel: it takes over the central area
|
|
4087
|
-
// and needs the width. There is no sidebar half to show
|
|
4453
|
+
// and needs the width. There is no sidebar half to show, so it toggles
|
|
4454
|
+
// instead of behaving like the other sidebar tabs.
|
|
4088
4455
|
if (tab === 'models') {
|
|
4089
|
-
|
|
4456
|
+
setShowModelGraphView(function (prev) {
|
|
4457
|
+
var next = !prev;
|
|
4458
|
+
if (next) loadModelGraph(false);
|
|
4459
|
+
return next;
|
|
4460
|
+
});
|
|
4090
4461
|
return;
|
|
4091
4462
|
}
|
|
4463
|
+
setShowModelGraphView(false);
|
|
4092
4464
|
if (!sidebarCollapsed && activeSidebarTab === tab) {
|
|
4093
4465
|
setSidebarCollapsed(true);
|
|
4094
4466
|
} else {
|
|
@@ -4243,6 +4615,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
4243
4615
|
// Optimistically insert the new node so the tree doesn't flash empty while waiting for the server
|
|
4244
4616
|
var optimisticNode = { path: path, name: name, type: type === 'folder' ? 'folder' : 'file', children: type === 'folder' ? [] : undefined };
|
|
4245
4617
|
setTreeData(function (prev) { return insertNodeIntoTree(prev, parentPath, optimisticNode); });
|
|
4618
|
+
setRevealTarget({ path: path });
|
|
4246
4619
|
|
|
4247
4620
|
if (type === 'file') {
|
|
4248
4621
|
setLoading(function (prev) {
|
|
@@ -4373,6 +4746,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
4373
4746
|
EditorStore.setStatus('Renamed to: ' + renamedPath, 'success');
|
|
4374
4747
|
return refreshProjectTree().then(function () {
|
|
4375
4748
|
GitService.fetchStatusLite({ background: true });
|
|
4749
|
+
setRevealTarget({ path: renamedPath });
|
|
4376
4750
|
});
|
|
4377
4751
|
})["catch"](function (err) {
|
|
4378
4752
|
var message = err && err.response && err.response.data && err.response.data.error || err.message;
|
|
@@ -4389,10 +4763,12 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
4389
4763
|
};
|
|
4390
4764
|
|
|
4391
4765
|
var handleDeletePath = function handleDeletePath(targetNode) {
|
|
4392
|
-
//
|
|
4393
|
-
//
|
|
4766
|
+
// A node from the context menu deletes just that node — unless it is part
|
|
4767
|
+
// of a live multi-selection, in which case the whole selection goes, the
|
|
4768
|
+
// same rule Download follows. With no node (the toolbar button) it is
|
|
4769
|
+
// always the selection, falling back to selectedTreeNode.
|
|
4394
4770
|
var pathsToDelete;
|
|
4395
|
-
if (targetNode
|
|
4771
|
+
if (targetNode && !actsOnSelection(targetNode)) {
|
|
4396
4772
|
pathsToDelete = [targetNode.path];
|
|
4397
4773
|
} else if (selectedPaths && selectedPaths.size > 0) {
|
|
4398
4774
|
pathsToDelete = Array.from(selectedPaths);
|
|
@@ -4416,6 +4792,10 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
4416
4792
|
var confirmed = window.confirm('Delete ' + label + '? This cannot be undone.');
|
|
4417
4793
|
if (!confirmed) return;
|
|
4418
4794
|
|
|
4795
|
+
// Computed against the pre-delete tree, since the deleted paths' siblings
|
|
4796
|
+
// are only known before the tree is refreshed.
|
|
4797
|
+
var deleteRevealTarget = computeDeleteRevealTarget(treeData, pathsToDelete);
|
|
4798
|
+
|
|
4419
4799
|
setLoading(function (prev) {
|
|
4420
4800
|
return _extends({}, prev, { deletePath: true });
|
|
4421
4801
|
});
|
|
@@ -4434,6 +4814,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
4434
4814
|
}
|
|
4435
4815
|
return refreshProjectTree().then(function () {
|
|
4436
4816
|
GitService.fetchStatusLite({ background: true });
|
|
4817
|
+
if (failures.length === 0 && deleteRevealTarget) setRevealTarget({ path: deleteRevealTarget });
|
|
4437
4818
|
});
|
|
4438
4819
|
})["finally"](function () {
|
|
4439
4820
|
setLoading(function (prev) {
|
|
@@ -4451,6 +4832,35 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
4451
4832
|
var canLintAndFormat = !!activeTab;
|
|
4452
4833
|
var hasGitBranch = !!(state.gitBranch && state.gitBranch.trim());
|
|
4453
4834
|
|
|
4835
|
+
// Derived at render from state already in the store — not stored via setState,
|
|
4836
|
+
// so the 5s git-status poll (MbeditorApp.js re-render rules) never fires an
|
|
4837
|
+
// extra re-render on an unchanged tick. Only conflictFileIndex is state, and
|
|
4838
|
+
// it changes only on explicit user click, never from a poll.
|
|
4839
|
+
var conflictedFiles = (state.gitInfo && state.gitInfo.workingTree || []).filter(function (f) {
|
|
4840
|
+
return window.MBEDITOR_UNMERGED_STATUSES[(f.status || '').trim()];
|
|
4841
|
+
});
|
|
4842
|
+
|
|
4843
|
+
// Opens the conflicted file at index idx (wrapping), landing the cursor/reveal
|
|
4844
|
+
// on its first `<<<<<<<` marker. Prefers an already-open tab's live content
|
|
4845
|
+
// (may hold unsaved edits) over a fresh disk fetch.
|
|
4846
|
+
var openConflictedFileAt = function openConflictedFileAt(idx) {
|
|
4847
|
+
if (!conflictedFiles.length) return;
|
|
4848
|
+
var i = ((idx % conflictedFiles.length) + conflictedFiles.length) % conflictedFiles.length;
|
|
4849
|
+
var file = conflictedFiles[i];
|
|
4850
|
+
setConflictFileIndex(i);
|
|
4851
|
+
var openTab = state.panes.reduce(function (found, p) {
|
|
4852
|
+
return found || p.tabs.find(function (t) { return t.path === file.path; });
|
|
4853
|
+
}, null);
|
|
4854
|
+
var name = file.path.split('/').pop();
|
|
4855
|
+
var contentPromise = openTab ? Promise.resolve({ content: openTab.content }) : FileService.getFile(file.path);
|
|
4856
|
+
contentPromise.then(function (data) {
|
|
4857
|
+
var content = typeof data.content === 'string' ? data.content : '';
|
|
4858
|
+
var blocks = ConflictParser.parse(content);
|
|
4859
|
+
var line = blocks.length ? blocks[0].startLine + 1 : null;
|
|
4860
|
+
TabManager.openTab(file.path, name, line);
|
|
4861
|
+
});
|
|
4862
|
+
};
|
|
4863
|
+
|
|
4454
4864
|
var renderTabBar = function renderTabBar(paneId, tabs, activeId) {
|
|
4455
4865
|
return React.createElement(TabBar, {
|
|
4456
4866
|
tabs: tabs,
|
|
@@ -4463,7 +4873,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
4463
4873
|
onSelect: function (id) {
|
|
4464
4874
|
// Sync explorer selection with the newly active tab so there's only one highlight
|
|
4465
4875
|
var tab = tabs.find(function(t) { return t.id === id; });
|
|
4466
|
-
if (tab && tab.path && !tab.path.startsWith('mbeditor://')
|
|
4876
|
+
if (tab && tab.path && !tab.path.startsWith('mbeditor://')) {
|
|
4467
4877
|
handleNodeSelect({ path: tab.path, name: tab.name, type: 'file' });
|
|
4468
4878
|
}
|
|
4469
4879
|
TabManager.switchTab(paneId, id);
|
|
@@ -4500,86 +4910,14 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
4500
4910
|
});
|
|
4501
4911
|
};
|
|
4502
4912
|
|
|
4503
|
-
|
|
4913
|
+
var CHANGELOG_TAB_ID = 'mbeditor://changelog';
|
|
4914
|
+
function openChangelogTab() {
|
|
4504
4915
|
var st = EditorStore.getState();
|
|
4505
|
-
|
|
4506
|
-
var foundTab = null;
|
|
4916
|
+
// Focus existing tab if already open
|
|
4917
|
+
var foundPaneId = null, foundTab = null;
|
|
4507
4918
|
st.panes.forEach(function(p) {
|
|
4508
4919
|
if (!foundTab) {
|
|
4509
|
-
var t = p.tabs.find(function(tab) { return tab.
|
|
4510
|
-
if (t) { foundTab = t; foundPaneId = p.id; }
|
|
4511
|
-
}
|
|
4512
|
-
});
|
|
4513
|
-
if (foundTab) {
|
|
4514
|
-
var newPanes = st.panes.map(function(p) {
|
|
4515
|
-
if (p.id === foundPaneId) return Object.assign({}, p, { activeTabId: '__settings__' });
|
|
4516
|
-
return p;
|
|
4517
|
-
});
|
|
4518
|
-
EditorStore.setState({ panes: newPanes, focusedPaneId: foundPaneId, activeTabId: '__settings__' });
|
|
4519
|
-
return;
|
|
4520
|
-
}
|
|
4521
|
-
var paneId = st.focusedPaneId;
|
|
4522
|
-
var pane = st.panes.find(function(p) { return p.id === paneId; }) || st.panes[0];
|
|
4523
|
-
if (!pane) return;
|
|
4524
|
-
paneId = pane.id;
|
|
4525
|
-
var newTab = { id: '__settings__', path: '__settings__', name: 'Settings', dirty: false, content: '', isSettings: true };
|
|
4526
|
-
var newPanes2 = st.panes.map(function(p) {
|
|
4527
|
-
if (p.id === paneId) return Object.assign({}, p, { tabs: p.tabs.concat(newTab), activeTabId: '__settings__' });
|
|
4528
|
-
return p;
|
|
4529
|
-
});
|
|
4530
|
-
EditorStore.setState({ panes: newPanes2, focusedPaneId: paneId, activeTabId: '__settings__' });
|
|
4531
|
-
}
|
|
4532
|
-
|
|
4533
|
-
// The diagram lives in an editor tab, not the sidebar: a layered graph is
|
|
4534
|
-
// inherently wide and a ~300px panel can only ever show its first column.
|
|
4535
|
-
// The sidebar tab is the entry point and the searchable model list.
|
|
4536
|
-
var MODEL_GRAPH_TAB_ID = 'mbeditor://model-graph';
|
|
4537
|
-
function openModelGraphTab() {
|
|
4538
|
-
var st = EditorStore.getState();
|
|
4539
|
-
var paneId = st.focusedPaneId;
|
|
4540
|
-
|
|
4541
|
-
var existing = null;
|
|
4542
|
-
st.panes.forEach(function (p) {
|
|
4543
|
-
if (!existing && p.tabs.some(function (t) { return t.id === MODEL_GRAPH_TAB_ID; })) {
|
|
4544
|
-
existing = p.id;
|
|
4545
|
-
}
|
|
4546
|
-
});
|
|
4547
|
-
if (existing) {
|
|
4548
|
-
EditorStore.setState({
|
|
4549
|
-
panes: st.panes.map(function (p) {
|
|
4550
|
-
return p.id === existing ? Object.assign({}, p, { activeTabId: MODEL_GRAPH_TAB_ID }) : p;
|
|
4551
|
-
}),
|
|
4552
|
-
focusedPaneId: existing
|
|
4553
|
-
});
|
|
4554
|
-
return;
|
|
4555
|
-
}
|
|
4556
|
-
|
|
4557
|
-
var pane = st.panes.find(function (p) { return p.id === paneId; }) || st.panes[0];
|
|
4558
|
-
if (!pane) return;
|
|
4559
|
-
|
|
4560
|
-
var newTab = {
|
|
4561
|
-
id: MODEL_GRAPH_TAB_ID, path: MODEL_GRAPH_TAB_ID, name: 'Model Graph',
|
|
4562
|
-
dirty: false, content: '', isModelGraph: true
|
|
4563
|
-
};
|
|
4564
|
-
EditorStore.setState({
|
|
4565
|
-
panes: st.panes.map(function (p) {
|
|
4566
|
-
return p.id === pane.id
|
|
4567
|
-
? Object.assign({}, p, { tabs: p.tabs.concat(newTab), activeTabId: MODEL_GRAPH_TAB_ID })
|
|
4568
|
-
: p;
|
|
4569
|
-
}),
|
|
4570
|
-
focusedPaneId: pane.id
|
|
4571
|
-
});
|
|
4572
|
-
loadModelGraph(false);
|
|
4573
|
-
}
|
|
4574
|
-
|
|
4575
|
-
var CHANGELOG_TAB_ID = 'mbeditor://changelog';
|
|
4576
|
-
function openChangelogTab() {
|
|
4577
|
-
var st = EditorStore.getState();
|
|
4578
|
-
// Focus existing tab if already open
|
|
4579
|
-
var foundPaneId = null, foundTab = null;
|
|
4580
|
-
st.panes.forEach(function(p) {
|
|
4581
|
-
if (!foundTab) {
|
|
4582
|
-
var t = p.tabs.find(function(tab) { return tab.id === CHANGELOG_TAB_ID; });
|
|
4920
|
+
var t = p.tabs.find(function(tab) { return tab.id === CHANGELOG_TAB_ID; });
|
|
4583
4921
|
if (t) { foundTab = t; foundPaneId = p.id; }
|
|
4584
4922
|
}
|
|
4585
4923
|
});
|
|
@@ -4629,10 +4967,11 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
4629
4967
|
{ className: "ide-titlebar" },
|
|
4630
4968
|
React.createElement("i", { className: "fas fa-layer-group ide-titlebar-icon" }),
|
|
4631
4969
|
React.createElement(
|
|
4970
|
+
// The product name, not the window title: the host and port are in the
|
|
4971
|
+
// browser's own address bar, and the long form crowded the search pill.
|
|
4632
4972
|
"div",
|
|
4633
|
-
{ className: "ide-titlebar-title" },
|
|
4634
|
-
"
|
|
4635
|
-
window.location.host
|
|
4973
|
+
{ className: "ide-titlebar-title", title: "Mini Browser Editor — " + window.location.host },
|
|
4974
|
+
"Mbeditor"
|
|
4636
4975
|
),
|
|
4637
4976
|
// The slot claims all the room between the title and the buttons; the
|
|
4638
4977
|
// search pill then takes 75% of it, centred. Sizing the pill against a
|
|
@@ -4722,6 +5061,35 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
4722
5061
|
!toolbarIconOnly && " Git"
|
|
4723
5062
|
)
|
|
4724
5063
|
),
|
|
5064
|
+
conflictedFiles.length > 0 && React.createElement(
|
|
5065
|
+
React.Fragment,
|
|
5066
|
+
null,
|
|
5067
|
+
React.createElement("div", { className: "statusbar-sep" }),
|
|
5068
|
+
React.createElement(
|
|
5069
|
+
"button",
|
|
5070
|
+
{
|
|
5071
|
+
type: "button",
|
|
5072
|
+
className: "statusbar-btn mbeditor-conflict-chip",
|
|
5073
|
+
title: "Open the first of " + conflictedFiles.length + " conflicted file" + (conflictedFiles.length === 1 ? "" : "s"),
|
|
5074
|
+
onClick: function () { openConflictedFileAt(0); }
|
|
5075
|
+
},
|
|
5076
|
+
React.createElement("i", { className: "fas fa-exclamation-circle" }),
|
|
5077
|
+
// The count always renders, even icon-only: it is the chip's
|
|
5078
|
+
// content, not a label. A bare warning icon says less than nothing.
|
|
5079
|
+
" " + conflictedFiles.length,
|
|
5080
|
+
!toolbarIconOnly && (conflictedFiles.length === 1 ? " conflict" : " conflicts")
|
|
5081
|
+
),
|
|
5082
|
+
conflictedFiles.length > 1 && React.createElement(
|
|
5083
|
+
"button",
|
|
5084
|
+
{
|
|
5085
|
+
type: "button",
|
|
5086
|
+
className: "statusbar-btn",
|
|
5087
|
+
title: "Open the next conflicted file",
|
|
5088
|
+
onClick: function () { openConflictedFileAt(conflictFileIndex + 1); }
|
|
5089
|
+
},
|
|
5090
|
+
React.createElement("i", { className: "fas fa-arrow-right" })
|
|
5091
|
+
)
|
|
5092
|
+
),
|
|
4725
5093
|
// Collaboration trouble chip. Deliberately not shown when everything is
|
|
4726
5094
|
// healthy and you are simply alone — that was the whole point of hiding
|
|
4727
5095
|
// the solo chip. It appears only when a condition pairing needs has
|
|
@@ -4773,45 +5141,48 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
4773
5141
|
React.Fragment,
|
|
4774
5142
|
null,
|
|
4775
5143
|
React.createElement("div", { className: "statusbar-sep" }),
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
|
|
4781
|
-
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
:
|
|
4796
|
-
|
|
4797
|
-
|
|
4798
|
-
|
|
4799
|
-
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
|
|
5144
|
+
React.createElement(
|
|
5145
|
+
"div",
|
|
5146
|
+
{ className: "collab-avatar-stack" },
|
|
5147
|
+
collabAvatarIds.map(function (cid) {
|
|
5148
|
+
var info = collabPeerInfo(cid);
|
|
5149
|
+
return React.createElement(
|
|
5150
|
+
"button",
|
|
5151
|
+
{
|
|
5152
|
+
key: cid,
|
|
5153
|
+
type: "button",
|
|
5154
|
+
className: "collab-avatar" +
|
|
5155
|
+
(info.following ? " collab-avatar-following" : "") +
|
|
5156
|
+
(info.elsewhere ? " collab-avatar-elsewhere" : ""),
|
|
5157
|
+
style: { background: info.colour },
|
|
5158
|
+
// The dot carries no glyph, so the name has to reach a
|
|
5159
|
+
// screen reader some other way.
|
|
5160
|
+
"aria-label": info.name,
|
|
5161
|
+
onMouseEnter: function (e) { openCollabHover(cid, e); },
|
|
5162
|
+
onMouseLeave: function () { setCollabHover(null); },
|
|
5163
|
+
onClick: function () { toggleFollow(cid); }
|
|
5164
|
+
}
|
|
5165
|
+
);
|
|
5166
|
+
}),
|
|
5167
|
+
collabOverflowIds.length > 0 && React.createElement(
|
|
5168
|
+
"div",
|
|
5169
|
+
{ className: "collab-avatar-overflow" },
|
|
5170
|
+
React.createElement(
|
|
5171
|
+
"button",
|
|
5172
|
+
{
|
|
5173
|
+
type: "button",
|
|
5174
|
+
className: "collab-avatar collab-avatar-more",
|
|
5175
|
+
onMouseEnter: function (e) { cancelOverflowClose(); setCollabOverflowAnchor(anchorFrom(e.currentTarget)); },
|
|
5176
|
+
onMouseLeave: scheduleOverflowClose,
|
|
5177
|
+
onClick: function (e) {
|
|
5178
|
+
var el = e.currentTarget;
|
|
5179
|
+
setCollabOverflowAnchor(function (prev) { return prev ? null : anchorFrom(el); });
|
|
5180
|
+
}
|
|
5181
|
+
},
|
|
5182
|
+
"+" + collabOverflowIds.length
|
|
4812
5183
|
)
|
|
4813
|
-
)
|
|
4814
|
-
|
|
5184
|
+
)
|
|
5185
|
+
)
|
|
4815
5186
|
),
|
|
4816
5187
|
React.createElement("div", { className: "statusbar-sep" }),
|
|
4817
5188
|
React.createElement(
|
|
@@ -4841,6 +5212,31 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
4841
5212
|
)
|
|
4842
5213
|
)
|
|
4843
5214
|
),
|
|
5215
|
+
collabOverflowOpen && React.createElement(
|
|
5216
|
+
"div",
|
|
5217
|
+
{
|
|
5218
|
+
className: "collab-overflow-popover",
|
|
5219
|
+
style: { top: collabOverflowAnchor.top + 'px', right: collabOverflowAnchor.right + 'px' },
|
|
5220
|
+
onMouseEnter: cancelOverflowClose,
|
|
5221
|
+
onMouseLeave: scheduleOverflowClose
|
|
5222
|
+
},
|
|
5223
|
+
collabOverflowIds.map(function (cid) {
|
|
5224
|
+
var info = collabPeerInfo(cid);
|
|
5225
|
+
return React.createElement(
|
|
5226
|
+
"button",
|
|
5227
|
+
{
|
|
5228
|
+
key: cid,
|
|
5229
|
+
type: "button",
|
|
5230
|
+
className: "collab-overflow-row" + (info.following ? " is-following" : ""),
|
|
5231
|
+
onClick: function () { toggleFollow(cid); }
|
|
5232
|
+
},
|
|
5233
|
+
React.createElement("span", { className: "collab-overflow-swatch", style: { background: info.colour } }),
|
|
5234
|
+
React.createElement("span", { className: "collab-overflow-name" }, info.name),
|
|
5235
|
+
React.createElement("span", { className: "collab-overflow-file" }, info.file ? info.file.split('/').pop() : 'No file open'),
|
|
5236
|
+
info.following && React.createElement("i", { className: "fas fa-eye collab-overflow-eye" })
|
|
5237
|
+
);
|
|
5238
|
+
})
|
|
5239
|
+
),
|
|
4844
5240
|
collabHover && (function () {
|
|
4845
5241
|
var isMe = collabHover.cid === '__me__';
|
|
4846
5242
|
var peer = isMe ? null : collabRoster[collabHover.cid];
|
|
@@ -4929,6 +5325,15 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
4929
5325
|
);
|
|
4930
5326
|
})(),
|
|
4931
5327
|
showHelp && React.createElement(ShortcutHelp, { onClose: function () { return setShowHelp(false); } }),
|
|
5328
|
+
showSettings && React.createElement(window.SettingsModal || SettingsModal, {
|
|
5329
|
+
editorPrefs: editorPrefs,
|
|
5330
|
+
setEditorPrefs: setEditorPrefs,
|
|
5331
|
+
rubocopAvailable: rubocopAvailable,
|
|
5332
|
+
rubocopConfigPath: rubocopConfigPath,
|
|
5333
|
+
onOpenRubocopConfig: function () { handleSelectFile(rubocopConfigPath, rubocopConfigPath.split('/').pop()); },
|
|
5334
|
+
onReset: function () { setEditorPrefs(Object.assign({}, DEFAULT_EDITOR_PREFS)); },
|
|
5335
|
+
onClose: function () { return setShowSettings(false); }
|
|
5336
|
+
}),
|
|
4932
5337
|
React.createElement(
|
|
4933
5338
|
"div",
|
|
4934
5339
|
{ className: "ide-body", id: "ide-body-container" },
|
|
@@ -4973,7 +5378,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
4973
5378
|
"button",
|
|
4974
5379
|
{
|
|
4975
5380
|
type: "button",
|
|
4976
|
-
className: "ide-activity-btn" + (
|
|
5381
|
+
className: "ide-activity-btn" + (showModelGraphView ? ' active' : ''),
|
|
4977
5382
|
title: "Model graph",
|
|
4978
5383
|
onClick: function() { handleActivityBarClick('models'); }
|
|
4979
5384
|
},
|
|
@@ -4987,16 +5392,16 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
4987
5392
|
"button",
|
|
4988
5393
|
{
|
|
4989
5394
|
type: "button",
|
|
4990
|
-
className: "ide-activity-btn" + (
|
|
5395
|
+
className: "ide-activity-btn" + (showSettings ? ' active' : ''),
|
|
4991
5396
|
title: "Editor Preferences",
|
|
4992
|
-
onClick:
|
|
5397
|
+
onClick: function() { handleActivityBarClick('settings'); }
|
|
4993
5398
|
},
|
|
4994
5399
|
React.createElement("i", { className: "fas fa-cog" })
|
|
4995
5400
|
)
|
|
4996
5401
|
)
|
|
4997
5402
|
),
|
|
4998
|
-
/* Panel content — shown when not collapsed
|
|
4999
|
-
!sidebarCollapsed && !zenMode && React.createElement(
|
|
5403
|
+
/* Panel content — shown when not collapsed, not in zen mode, and not showing the model graph */
|
|
5404
|
+
!sidebarCollapsed && !zenMode && !showModelGraphView && React.createElement(
|
|
5000
5405
|
"div",
|
|
5001
5406
|
{ className: "ide-sidebar", style: { width: sidebarWidth + "px" } },
|
|
5002
5407
|
React.createElement("div", { className: "sidebar-panel-title" },
|
|
@@ -5034,101 +5439,126 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
5034
5439
|
React.createElement(
|
|
5035
5440
|
"div",
|
|
5036
5441
|
{ style: { marginBottom: "12px" } },
|
|
5037
|
-
|
|
5038
|
-
|
|
5039
|
-
|
|
5040
|
-
return
|
|
5041
|
-
|
|
5042
|
-
|
|
5043
|
-
|
|
5044
|
-
|
|
5045
|
-
|
|
5046
|
-
},
|
|
5047
|
-
React.createElement(
|
|
5442
|
+
(function () {
|
|
5443
|
+
// VS Code drops the "GROUP N" sub-header when only one group
|
|
5444
|
+
// is populated — it's only useful to tell groups apart.
|
|
5445
|
+
var activePanes = state.panes.filter(function (p) { return p.tabs.length > 0; });
|
|
5446
|
+
var showGroupHeaders = activePanes.length > 1;
|
|
5447
|
+
return state.panes.map(function (pane) {
|
|
5448
|
+
if (pane.tabs.length === 0) return null;
|
|
5449
|
+
var isPane2 = pane.id === 2;
|
|
5450
|
+
return React.createElement(
|
|
5048
5451
|
"div",
|
|
5049
|
-
{
|
|
5050
|
-
|
|
5051
|
-
"
|
|
5052
|
-
{
|
|
5053
|
-
|
|
5054
|
-
|
|
5452
|
+
{
|
|
5453
|
+
key: pane.id,
|
|
5454
|
+
className: "open-editors-group",
|
|
5455
|
+
style: { marginBottom: pane.id === 1 && state.panes[1].tabs.length > 0 ? "10px" : "0" }
|
|
5456
|
+
},
|
|
5457
|
+
showGroupHeaders && React.createElement(
|
|
5458
|
+
"div",
|
|
5459
|
+
{ className: "ide-sidebar-header open-editors-group-header" },
|
|
5460
|
+
React.createElement(
|
|
5461
|
+
"span",
|
|
5462
|
+
{ className: "open-editors-group-title" },
|
|
5463
|
+
"GROUP ",
|
|
5464
|
+
pane.id
|
|
5465
|
+
),
|
|
5466
|
+
React.createElement(
|
|
5467
|
+
SectionActionGroup,
|
|
5468
|
+
{ ariaLabel: "Group " + pane.id + " actions", className: "collapsible-actions open-editors-group-actions" },
|
|
5469
|
+
React.createElement(SidebarActionButton, {
|
|
5470
|
+
title: "Close all editors in Group " + pane.id,
|
|
5471
|
+
ariaLabel: "Close all editors in Group " + pane.id,
|
|
5472
|
+
iconClass: "far fa-window-close",
|
|
5473
|
+
onClick: function (e) {
|
|
5474
|
+
e.stopPropagation();handleCloseEditorsInGroup(pane.id);
|
|
5475
|
+
}
|
|
5476
|
+
})
|
|
5477
|
+
)
|
|
5055
5478
|
),
|
|
5056
5479
|
React.createElement(
|
|
5057
|
-
|
|
5058
|
-
{
|
|
5059
|
-
|
|
5060
|
-
|
|
5061
|
-
|
|
5062
|
-
|
|
5063
|
-
onClick: function (e) {
|
|
5064
|
-
e.stopPropagation();handleCloseEditorsInGroup(pane.id);
|
|
5065
|
-
}
|
|
5066
|
-
})
|
|
5067
|
-
)
|
|
5068
|
-
),
|
|
5069
|
-
React.createElement(
|
|
5070
|
-
"div",
|
|
5071
|
-
{ className: "file-tree" },
|
|
5072
|
-
pane.tabs.map(function (tab) {
|
|
5073
|
-
return React.createElement(
|
|
5074
|
-
"div",
|
|
5075
|
-
{
|
|
5076
|
-
key: tab.id,
|
|
5077
|
-
className: "tree-item " + (pane.activeTabId === tab.id && state.focusedPaneId === pane.id ? "active" : ""),
|
|
5078
|
-
// The name ellipsises in a narrow sidebar, so the row
|
|
5079
|
-
// carries the full path the way file-tree rows do.
|
|
5080
|
-
title: tab.path || tab.name,
|
|
5081
|
-
onClick: function () {
|
|
5082
|
-
if (tab.path && !tab.path.startsWith('mbeditor://') && tab.path !== '__settings__') {
|
|
5083
|
-
handleNodeSelect({ path: tab.path, name: tab.name, type: 'file' });
|
|
5084
|
-
}
|
|
5085
|
-
TabManager.focusPane(pane.id);TabManager.switchTab(pane.id, tab.id);
|
|
5086
|
-
}
|
|
5087
|
-
},
|
|
5088
|
-
React.createElement("i", { className: "tree-item-icon " + (window.getFileIcon ? window.getFileIcon(tab.name) : 'far fa-file-code') + " tree-file-icon" }),
|
|
5089
|
-
React.createElement(
|
|
5090
|
-
"div",
|
|
5091
|
-
{ className: "tree-item-name", style: { display: 'flex', alignItems: 'center' } },
|
|
5092
|
-
React.createElement(
|
|
5093
|
-
"span",
|
|
5094
|
-
{ style: { overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } },
|
|
5095
|
-
tab.name
|
|
5096
|
-
),
|
|
5097
|
-
tab.dirty && React.createElement("i", { className: "fas fa-circle", style: { fontSize: '5px', color: '#e3d286', marginLeft: '6px', marginTop: '1px' } })
|
|
5098
|
-
),
|
|
5099
|
-
React.createElement(
|
|
5480
|
+
"div",
|
|
5481
|
+
{ className: "file-tree" },
|
|
5482
|
+
pane.tabs.map(function (tab) {
|
|
5483
|
+
var isActive = pane.activeTabId === tab.id && state.focusedPaneId === pane.id;
|
|
5484
|
+
var dir = tab.path ? tab.path.split('/').slice(0, -1).join('/') : '';
|
|
5485
|
+
return React.createElement(
|
|
5100
5486
|
"div",
|
|
5101
|
-
{
|
|
5487
|
+
{
|
|
5488
|
+
key: tab.id,
|
|
5489
|
+
className: "tree-item open-editors-item " + (isActive ? "active" : ""),
|
|
5490
|
+
// The name ellipsises in a narrow sidebar, so the row
|
|
5491
|
+
// carries the full path the way file-tree rows do.
|
|
5492
|
+
title: tab.path || tab.name,
|
|
5493
|
+
onClick: function () {
|
|
5494
|
+
if (tab.path && !tab.path.startsWith('mbeditor://')) {
|
|
5495
|
+
handleNodeSelect({ path: tab.path, name: tab.name, type: 'file' });
|
|
5496
|
+
}
|
|
5497
|
+
TabManager.focusPane(pane.id);TabManager.switchTab(pane.id, tab.id);
|
|
5498
|
+
}
|
|
5499
|
+
},
|
|
5500
|
+
React.createElement("i", { className: "tree-item-icon " + (window.getFileIcon ? window.getFileIcon(tab.name) : 'far fa-file-code') + " tree-file-icon" }),
|
|
5102
5501
|
React.createElement(
|
|
5103
5502
|
"div",
|
|
5104
|
-
|
|
5105
|
-
|
|
5106
|
-
|
|
5107
|
-
|
|
5503
|
+
// minWidth:0 on both the row's name cell and the label
|
|
5504
|
+
// itself: without it a flex item refuses to shrink
|
|
5505
|
+
// below its content, so a long filename pushed out
|
|
5506
|
+
// under the (formerly absolute) action buttons.
|
|
5507
|
+
{ className: "tree-item-name", style: { display: 'flex', alignItems: 'center', minWidth: 0 } },
|
|
5508
|
+
React.createElement(
|
|
5509
|
+
"span",
|
|
5510
|
+
{ className: "open-editors-name", style: { overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', minWidth: 0, flexShrink: 0 } },
|
|
5511
|
+
tab.name
|
|
5512
|
+
),
|
|
5513
|
+
dir && React.createElement(
|
|
5514
|
+
"span",
|
|
5515
|
+
{ className: "open-editors-dir" },
|
|
5516
|
+
dir
|
|
5517
|
+
)
|
|
5108
5518
|
),
|
|
5109
5519
|
React.createElement(
|
|
5110
5520
|
"div",
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
|
|
5521
|
+
// In flow, not absolute — the buttons now claim their
|
|
5522
|
+
// own width so the name truncates instead of running
|
|
5523
|
+
// underneath them.
|
|
5524
|
+
{ className: "tab-actions", style: { display: 'flex', alignItems: 'center', flexShrink: 0, marginLeft: 'auto' } },
|
|
5525
|
+
React.createElement(
|
|
5526
|
+
"div",
|
|
5527
|
+
{ className: "tab-split", onClick: function (e) {
|
|
5528
|
+
e.stopPropagation();TabManager.moveTabToPane(pane.id, pane.id === 1 ? 2 : 1, tab.id);
|
|
5529
|
+
}, style: { padding: '0 4px', cursor: 'pointer' }, title: "Move to Group " + (pane.id === 1 ? 2 : 1) },
|
|
5530
|
+
React.createElement("i", { className: isPane2 ? "fas fa-chevron-left" : "fas fa-chevron-right" })
|
|
5531
|
+
),
|
|
5532
|
+
React.createElement(
|
|
5533
|
+
"div",
|
|
5534
|
+
{ className: "open-editors-close-slot" },
|
|
5535
|
+
tab.dirty && React.createElement("i", { className: "fas fa-circle open-editors-dirty-dot", 'aria-hidden': 'true' }),
|
|
5536
|
+
React.createElement(
|
|
5537
|
+
"button",
|
|
5538
|
+
{ type: "button", className: "tab-close", onClick: function (e) {
|
|
5539
|
+
e.stopPropagation();requestCloseTab(pane.id, tab.id);
|
|
5540
|
+
}, style: { padding: '0 4px' },
|
|
5541
|
+
title: "Close " + tab.name + (tab.dirty ? " (unsaved changes)" : ""),
|
|
5542
|
+
'aria-label': "Close " + tab.name + (tab.dirty ? " (unsaved changes)" : "") },
|
|
5543
|
+
React.createElement("i", { className: "fas fa-times" })
|
|
5544
|
+
)
|
|
5545
|
+
)
|
|
5118
5546
|
)
|
|
5119
|
-
)
|
|
5120
|
-
)
|
|
5121
|
-
|
|
5122
|
-
)
|
|
5123
|
-
);
|
|
5124
|
-
})
|
|
5547
|
+
);
|
|
5548
|
+
})
|
|
5549
|
+
)
|
|
5550
|
+
);
|
|
5551
|
+
});
|
|
5552
|
+
})()
|
|
5125
5553
|
)
|
|
5126
5554
|
)
|
|
5127
5555
|
),
|
|
5128
|
-
state.panes.flatMap(function (p) { return p.tabs; }).length > 0 && React.createElement(
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
|
|
5556
|
+
state.panes.flatMap(function (p) { return p.tabs; }).length > 0 && React.createElement((window.Gutter || Gutter), {
|
|
5557
|
+
orientation: 'horizontal',
|
|
5558
|
+
label: "Resize open editors list",
|
|
5559
|
+
active: activeResizeMode === 'openeditors',
|
|
5560
|
+
onDragStart: startOpenEditorsResize
|
|
5561
|
+
}),
|
|
5132
5562
|
React.createElement(
|
|
5133
5563
|
"div",
|
|
5134
5564
|
{ className: "ide-sidebar-scrollable" },
|
|
@@ -5136,10 +5566,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
5136
5566
|
CollapsibleSection,
|
|
5137
5567
|
{
|
|
5138
5568
|
title: projectSectionTitle,
|
|
5139
|
-
|
|
5140
|
-
onToggle: function (isCollapsed) {
|
|
5141
|
-
return handleToggleSection('projects', isCollapsed);
|
|
5142
|
-
},
|
|
5569
|
+
collapsible: false,
|
|
5143
5570
|
actions: React.createElement(
|
|
5144
5571
|
SectionActionGroup,
|
|
5145
5572
|
{ ariaLabel: "Project actions" },
|
|
@@ -5174,10 +5601,15 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
5174
5601
|
disabled: !!loading.createDir
|
|
5175
5602
|
}),
|
|
5176
5603
|
React.createElement(SidebarActionButton, {
|
|
5177
|
-
title: "Upload files",
|
|
5604
|
+
title: "Upload files to the workspace root",
|
|
5178
5605
|
iconClass: 'fas fa-upload',
|
|
5606
|
+
// Defaults to the workspace root, not whatever happens to be
|
|
5607
|
+
// selected in the tree: an upload that silently follows the
|
|
5608
|
+
// selection lands files somewhere the user was not looking,
|
|
5609
|
+
// and the count reads as a success either way. The context
|
|
5610
|
+
// menu's "Upload Files Here..." is the way to target a folder.
|
|
5179
5611
|
onClick: function () {
|
|
5180
|
-
return openImportDialog(
|
|
5612
|
+
return openImportDialog(null);
|
|
5181
5613
|
}
|
|
5182
5614
|
}),
|
|
5183
5615
|
React.createElement(SidebarActionButton, {
|
|
@@ -5207,6 +5639,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
5207
5639
|
activePath: editorPrefs.autoRevealInExplorer !== false ? (activeTab && activeTab.path) : null,
|
|
5208
5640
|
selectedPaths: selectedPaths,
|
|
5209
5641
|
anchorPath: selectedTreePath,
|
|
5642
|
+
revealTarget: revealTarget,
|
|
5210
5643
|
onNodeSelect: handleNodeSelect,
|
|
5211
5644
|
onMultiSelect: handleMultiSelect,
|
|
5212
5645
|
onMove: handleMoveNodes,
|
|
@@ -5337,13 +5770,29 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
5337
5770
|
var total = searchTotalCount > 0 ? searchTotalCount : loadedCount;
|
|
5338
5771
|
var hasAny = loadedCount > 0;
|
|
5339
5772
|
|
|
5773
|
+
// Grouped into a VS Code-style tree, then flattened straight back
|
|
5774
|
+
// into one row array: the windowing below is unchanged, it just
|
|
5775
|
+
// indexes rows instead of results. Every tier emits its hits file
|
|
5776
|
+
// by file, so a run of the same path is the whole group.
|
|
5777
|
+
var rows = [];
|
|
5778
|
+
var group = null;
|
|
5779
|
+
allResults.forEach(function (res, idx) {
|
|
5780
|
+
if (!group || group.file !== res.file) {
|
|
5781
|
+
group = { type: 'file', file: res.file, count: 0 };
|
|
5782
|
+
rows.push(group);
|
|
5783
|
+
}
|
|
5784
|
+
group.count += 1;
|
|
5785
|
+
if (!searchCollapsedFiles[res.file]) rows.push({ type: 'match', res: res, idx: idx });
|
|
5786
|
+
});
|
|
5787
|
+
var rowCount = rows.length;
|
|
5788
|
+
|
|
5340
5789
|
// Only the rows on screen (plus a small buffer either side) are
|
|
5341
5790
|
// built. Everything else is represented by the height of the
|
|
5342
5791
|
// spacer, so the scrollbar and every scroll position stay exactly
|
|
5343
5792
|
// as they would be for the full list.
|
|
5344
5793
|
var winStart = Math.max(0, Math.floor(searchViewport.scrollTop / SEARCH_ROW_HEIGHT) - SEARCH_ROW_BUFFER);
|
|
5345
|
-
var winEnd = Math.min(
|
|
5346
|
-
var visible =
|
|
5794
|
+
var winEnd = Math.min(rowCount, Math.ceil((searchViewport.scrollTop + (searchViewport.height || 600)) / SEARCH_ROW_HEIGHT) + SEARCH_ROW_BUFFER);
|
|
5795
|
+
var visible = rows.slice(winStart, winEnd);
|
|
5347
5796
|
|
|
5348
5797
|
return React.createElement(
|
|
5349
5798
|
React.Fragment,
|
|
@@ -5367,30 +5816,57 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
5367
5816
|
},
|
|
5368
5817
|
React.createElement(
|
|
5369
5818
|
"div",
|
|
5370
|
-
{ style: { height:
|
|
5371
|
-
visible.map(function(
|
|
5819
|
+
{ style: { height: rowCount * SEARCH_ROW_HEIGHT, position: 'relative' } },
|
|
5820
|
+
visible.map(function(row, vi) {
|
|
5372
5821
|
var i = winStart + vi;
|
|
5373
|
-
var
|
|
5822
|
+
var top = { position: 'absolute', top: i * SEARCH_ROW_HEIGHT, left: 0, right: 0 };
|
|
5823
|
+
|
|
5824
|
+
if (row.type === 'file') {
|
|
5825
|
+
var fileName = row.file.split('/').pop();
|
|
5826
|
+
var dir = row.file.slice(0, row.file.length - fileName.length).replace(/\/$/, '');
|
|
5827
|
+
var collapsed = !!searchCollapsedFiles[row.file];
|
|
5828
|
+
return React.createElement(
|
|
5829
|
+
"div",
|
|
5830
|
+
{
|
|
5831
|
+
key: 'f:' + row.file,
|
|
5832
|
+
className: "search-result-file-row",
|
|
5833
|
+
style: top,
|
|
5834
|
+
title: row.file,
|
|
5835
|
+
onClick: (function(f) { return function() { toggleSearchFile(f); }; })(row.file)
|
|
5836
|
+
},
|
|
5837
|
+
React.createElement("i", { className: "codicon codicon-chevron-" + (collapsed ? "right" : "down") + " search-result-chevron" }),
|
|
5838
|
+
React.createElement("i", { className: (window.getFileIcon ? window.getFileIcon(fileName) : 'far fa-file-code') + " search-result-icon" }),
|
|
5839
|
+
React.createElement("span", { className: "search-result-file-name" }, fileName),
|
|
5840
|
+
dir && React.createElement("span", { className: "search-result-file-dir" }, dir),
|
|
5841
|
+
React.createElement("span", { className: "search-result-count" }, row.count)
|
|
5842
|
+
);
|
|
5843
|
+
}
|
|
5844
|
+
|
|
5845
|
+
var res = row.res;
|
|
5374
5846
|
return React.createElement(
|
|
5375
5847
|
"div",
|
|
5376
5848
|
{
|
|
5377
|
-
key:
|
|
5849
|
+
key: 'm:' + row.idx,
|
|
5378
5850
|
className: "search-result-item",
|
|
5379
|
-
style:
|
|
5380
|
-
|
|
5381
|
-
//
|
|
5382
|
-
|
|
5851
|
+
style: top,
|
|
5852
|
+
title: res.file + ":" + res.line,
|
|
5853
|
+
// col..end_col selects the match and leaves the cursor
|
|
5854
|
+
// just past it, which is where you want to start
|
|
5855
|
+
// typing after jumping to a hit — not column 1.
|
|
5856
|
+
onClick: (function(r) { return function() { handleSelectFile(r.file, r.file.split('/').pop(), r.line, r.col || r.end_col, r.end_col); }; })(res)
|
|
5383
5857
|
},
|
|
5384
|
-
React.createElement("
|
|
5385
|
-
|
|
5386
|
-
|
|
5387
|
-
React.createElement(
|
|
5388
|
-
|
|
5389
|
-
|
|
5390
|
-
|
|
5391
|
-
|
|
5392
|
-
|
|
5393
|
-
|
|
5858
|
+
React.createElement("span", { className: "search-result-line-num" }, res.line),
|
|
5859
|
+
(function () {
|
|
5860
|
+
var parts = searchMatchParts(res);
|
|
5861
|
+
if (!parts) return React.createElement("span", { className: "search-result-text" }, res.text);
|
|
5862
|
+
return React.createElement(
|
|
5863
|
+
"span",
|
|
5864
|
+
{ className: "search-result-text search-result-text-split" },
|
|
5865
|
+
React.createElement("span", { className: "search-result-pre" }, parts[0]),
|
|
5866
|
+
React.createElement("mark", { className: "search-result-match" }, parts[1]),
|
|
5867
|
+
React.createElement("span", { className: "search-result-post" }, parts[2])
|
|
5868
|
+
);
|
|
5869
|
+
})()
|
|
5394
5870
|
);
|
|
5395
5871
|
})
|
|
5396
5872
|
),
|
|
@@ -5502,18 +5978,32 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
5502
5978
|
})()
|
|
5503
5979
|
)
|
|
5504
5980
|
),
|
|
5505
|
-
|
|
5506
|
-
|
|
5507
|
-
|
|
5508
|
-
|
|
5509
|
-
|
|
5510
|
-
|
|
5511
|
-
"aria-label": "Resize explorer panel"
|
|
5981
|
+
// Collapsed: the same gutter is a handle. Drag it right and the
|
|
5982
|
+
// explorer snaps open; there is no intermediate width to preview.
|
|
5983
|
+
sidebarCollapsed && !zenMode && !showModelGraphView && React.createElement((window.Gutter || Gutter), {
|
|
5984
|
+
orientation: 'vertical',
|
|
5985
|
+
label: "Drag right to open the explorer",
|
|
5986
|
+
onSnap: function () { setSidebarCollapsed(false); }
|
|
5512
5987
|
}),
|
|
5513
|
-
|
|
5514
|
-
|
|
5515
|
-
|
|
5516
|
-
|
|
5988
|
+
/* Sidebar resize gutter — only when panel is open */
|
|
5989
|
+
!sidebarCollapsed && !zenMode && !showModelGraphView && React.createElement((window.Gutter || Gutter), {
|
|
5990
|
+
orientation: 'vertical',
|
|
5991
|
+
label: "Resize explorer panel",
|
|
5992
|
+
active: activeResizeMode === 'sidebar',
|
|
5993
|
+
onDragStart: startSidebarResize
|
|
5994
|
+
}),
|
|
5995
|
+
// The graph replaces the whole column — no tab bar, no panes, no drawers —
|
|
5996
|
+
// rather than living inside ide-main, so it gets the full center width.
|
|
5997
|
+
showModelGraphView ? React.createElement(
|
|
5998
|
+
"div",
|
|
5999
|
+
{ className: "ide-model-graph-view" },
|
|
6000
|
+
React.createElement(ModelGraph, {
|
|
6001
|
+
graph: modelGraph,
|
|
6002
|
+
loading: modelGraphLoading,
|
|
6003
|
+
onRefresh: function () { loadModelGraph(true); },
|
|
6004
|
+
onOpenModel: function (model) { openSchemaModal(model.name); }
|
|
6005
|
+
})
|
|
6006
|
+
) : React.createElement(
|
|
5517
6007
|
"div",
|
|
5518
6008
|
{ className: "ide-center-column" },
|
|
5519
6009
|
React.createElement(
|
|
@@ -5611,13 +6101,6 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
5611
6101
|
commits: pActiveTab.commits || [],
|
|
5612
6102
|
onSelectCommit: handleSelectCommit
|
|
5613
6103
|
});
|
|
5614
|
-
} else if (pActiveTab.isModelGraph) {
|
|
5615
|
-
content = React.createElement(ModelGraph, {
|
|
5616
|
-
graph: modelGraph,
|
|
5617
|
-
loading: modelGraphLoading,
|
|
5618
|
-
onRefresh: function () { loadModelGraph(true); },
|
|
5619
|
-
onOpenModel: function (model) { openSchemaModal(model.name); }
|
|
5620
|
-
});
|
|
5621
6104
|
} else if (pActiveTab.isChangelog) {
|
|
5622
6105
|
content = React.createElement(ChangelogView, {
|
|
5623
6106
|
changelogState: changelogState,
|
|
@@ -5630,597 +6113,6 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
5630
6113
|
}
|
|
5631
6114
|
}
|
|
5632
6115
|
});
|
|
5633
|
-
} else if (pActiveTab.isSettings) {
|
|
5634
|
-
content = React.createElement(
|
|
5635
|
-
'div',
|
|
5636
|
-
{ className: 'ide-settings-tab-content' },
|
|
5637
|
-
React.createElement(
|
|
5638
|
-
'div',
|
|
5639
|
-
{ className: 'ide-settings-body' },
|
|
5640
|
-
|
|
5641
|
-
/* ── Appearance ──────────────────────────────── */
|
|
5642
|
-
React.createElement('div', { className: 'ide-settings-section-header' }, 'Appearance'),
|
|
5643
|
-
React.createElement(
|
|
5644
|
-
'label', { className: 'ide-settings-row ide-settings-row-half', title: 'Color theme for the editor' },
|
|
5645
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Theme'),
|
|
5646
|
-
React.createElement(
|
|
5647
|
-
'select', {
|
|
5648
|
-
value: editorPrefs.theme || 'vs-dark',
|
|
5649
|
-
onChange: function(e) { setEditorPrefs(function(p) { return Object.assign({}, p, { theme: e.target.value }); }); }
|
|
5650
|
-
},
|
|
5651
|
-
React.createElement('option', { value: 'vs-dark' }, 'Dark'),
|
|
5652
|
-
React.createElement('option', { value: 'vs' }, 'Light'),
|
|
5653
|
-
React.createElement('option', { value: 'hc-black' }, 'HC Dark'),
|
|
5654
|
-
React.createElement('option', { value: 'hc-light' }, 'HC Light'),
|
|
5655
|
-
React.createElement('option', { value: 'dracula' }, 'Dracula'),
|
|
5656
|
-
React.createElement('option', { value: 'night-owl' }, 'Night Owl'),
|
|
5657
|
-
React.createElement('option', { value: 'monokai' }, 'Monokai'),
|
|
5658
|
-
React.createElement('option', { value: 'nord' }, 'Nord'),
|
|
5659
|
-
React.createElement('option', { value: 'github-dark' }, 'GitHub Dark'),
|
|
5660
|
-
React.createElement('option', { value: 'tomorrow-night' }, 'Tomorrow Night'),
|
|
5661
|
-
React.createElement('option', { value: 'github-light' }, 'GitHub Light')
|
|
5662
|
-
)
|
|
5663
|
-
),
|
|
5664
|
-
React.createElement(
|
|
5665
|
-
'label', { className: 'ide-settings-row ide-settings-row-half', title: 'Editor font size in pixels (8–32)' },
|
|
5666
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Font size'),
|
|
5667
|
-
React.createElement('input', {
|
|
5668
|
-
key: String(editorPrefs.fontSize || 13),
|
|
5669
|
-
type: 'number', min: '8', max: '32', step: '1',
|
|
5670
|
-
className: 'ide-settings-input',
|
|
5671
|
-
defaultValue: editorPrefs.fontSize || 13,
|
|
5672
|
-
onChange: function(e) {
|
|
5673
|
-
var v = parseInt(e.target.value, 10);
|
|
5674
|
-
if (v >= 8 && v <= 32) setEditorPrefs(function(p) { return Object.assign({}, p, { fontSize: v }); });
|
|
5675
|
-
},
|
|
5676
|
-
onBlur: function(e) {
|
|
5677
|
-
var v = parseInt(e.target.value, 10);
|
|
5678
|
-
if (isNaN(v) || v < 8 || v > 32) e.target.value = String(editorPrefs.fontSize || 13);
|
|
5679
|
-
}
|
|
5680
|
-
})
|
|
5681
|
-
),
|
|
5682
|
-
React.createElement(
|
|
5683
|
-
'label', { className: 'ide-settings-row-full', title: 'Font stack used in the editor — the first font available on your system is used' },
|
|
5684
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Font family'),
|
|
5685
|
-
React.createElement('input', {
|
|
5686
|
-
type: 'text',
|
|
5687
|
-
className: 'ide-settings-input ide-settings-input-wide',
|
|
5688
|
-
value: editorPrefs.fontFamily || "'JetBrains Mono', 'Fira Code', Consolas, 'Courier New', monospace",
|
|
5689
|
-
onChange: function(e) { setEditorPrefs(function(p) { return Object.assign({}, p, { fontFamily: e.target.value }); }); }
|
|
5690
|
-
})
|
|
5691
|
-
),
|
|
5692
|
-
React.createElement(
|
|
5693
|
-
'label', { className: 'ide-settings-row ide-settings-row-half', title: 'Row height in pixels. 0 = auto (roughly font size × 1.5)' },
|
|
5694
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Line height (0=auto)'),
|
|
5695
|
-
React.createElement('input', {
|
|
5696
|
-
key: String(editorPrefs.lineHeight != null ? editorPrefs.lineHeight : 0),
|
|
5697
|
-
type: 'number', min: '0', max: '100', step: '1',
|
|
5698
|
-
className: 'ide-settings-input',
|
|
5699
|
-
defaultValue: editorPrefs.lineHeight != null ? editorPrefs.lineHeight : 0,
|
|
5700
|
-
onChange: function(e) {
|
|
5701
|
-
var v = parseInt(e.target.value, 10);
|
|
5702
|
-
if (!isNaN(v) && v >= 0 && v <= 100) setEditorPrefs(function(p) { return Object.assign({}, p, { lineHeight: v }); });
|
|
5703
|
-
},
|
|
5704
|
-
onBlur: function(e) {
|
|
5705
|
-
var v = parseInt(e.target.value, 10);
|
|
5706
|
-
if (isNaN(v) || v < 0 || v > 100) e.target.value = String(editorPrefs.lineHeight != null ? editorPrefs.lineHeight : 0);
|
|
5707
|
-
}
|
|
5708
|
-
})
|
|
5709
|
-
),
|
|
5710
|
-
React.createElement(
|
|
5711
|
-
'label', { className: 'ide-settings-row ide-settings-row-half', title: 'Extra space between characters in pixels. 0 = default' },
|
|
5712
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Letter spacing (px)'),
|
|
5713
|
-
React.createElement('input', {
|
|
5714
|
-
key: String(editorPrefs.letterSpacing != null ? editorPrefs.letterSpacing : 0),
|
|
5715
|
-
type: 'number', min: '-5', max: '20', step: '0.5',
|
|
5716
|
-
className: 'ide-settings-input',
|
|
5717
|
-
defaultValue: editorPrefs.letterSpacing != null ? editorPrefs.letterSpacing : 0,
|
|
5718
|
-
onChange: function(e) {
|
|
5719
|
-
var v = parseFloat(e.target.value);
|
|
5720
|
-
if (!isNaN(v) && v >= -5 && v <= 20) setEditorPrefs(function(p) { return Object.assign({}, p, { letterSpacing: v }); });
|
|
5721
|
-
},
|
|
5722
|
-
onBlur: function(e) {
|
|
5723
|
-
var v = parseFloat(e.target.value);
|
|
5724
|
-
if (isNaN(v) || v < -5 || v > 20) e.target.value = String(editorPrefs.letterSpacing != null ? editorPrefs.letterSpacing : 0);
|
|
5725
|
-
}
|
|
5726
|
-
})
|
|
5727
|
-
),
|
|
5728
|
-
|
|
5729
|
-
/* ── Indentation (unified editor + Prettier) ── */
|
|
5730
|
-
React.createElement('div', { className: 'ide-settings-section-header' }, 'Indentation'),
|
|
5731
|
-
React.createElement(
|
|
5732
|
-
'label', { className: 'ide-settings-row ide-settings-row-half', title: 'Number of spaces per indentation level (also sets Prettier tab width)' },
|
|
5733
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Tab size'),
|
|
5734
|
-
React.createElement('input', {
|
|
5735
|
-
key: String(editorPrefs.tabSize || 4),
|
|
5736
|
-
type: 'number', min: '1', max: '8', step: '1',
|
|
5737
|
-
className: 'ide-settings-input',
|
|
5738
|
-
defaultValue: editorPrefs.tabSize || 4,
|
|
5739
|
-
onChange: function(e) {
|
|
5740
|
-
var v = parseInt(e.target.value, 10);
|
|
5741
|
-
if (v >= 1 && v <= 8) setEditorPrefs(function(p) { return Object.assign({}, p, { tabSize: v }); });
|
|
5742
|
-
},
|
|
5743
|
-
onBlur: function(e) {
|
|
5744
|
-
var v = parseInt(e.target.value, 10);
|
|
5745
|
-
if (isNaN(v) || v < 1 || v > 8) e.target.value = String(editorPrefs.tabSize || 4);
|
|
5746
|
-
}
|
|
5747
|
-
})
|
|
5748
|
-
),
|
|
5749
|
-
React.createElement(
|
|
5750
|
-
'label', { className: 'ide-settings-row ide-settings-row-check', title: 'Insert spaces instead of tab characters when pressing Tab' },
|
|
5751
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Use spaces'),
|
|
5752
|
-
React.createElement('input', {
|
|
5753
|
-
type: 'checkbox',
|
|
5754
|
-
className: 'ide-settings-checkbox',
|
|
5755
|
-
checked: !!(editorPrefs.insertSpaces),
|
|
5756
|
-
onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { insertSpaces: v }); }); }
|
|
5757
|
-
})
|
|
5758
|
-
),
|
|
5759
|
-
|
|
5760
|
-
/* ── Editor ──────────────────────────────────── */
|
|
5761
|
-
React.createElement('div', { className: 'ide-settings-section-header' }, 'Editor'),
|
|
5762
|
-
React.createElement(
|
|
5763
|
-
'label', { className: 'ide-settings-row ide-settings-row-half', title: 'How long lines are handled — Off: scroll horizontally, On: wrap at viewport width, Column: wrap at a fixed column' },
|
|
5764
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Word wrap'),
|
|
5765
|
-
React.createElement(
|
|
5766
|
-
'select', {
|
|
5767
|
-
value: editorPrefs.wordWrap || 'off',
|
|
5768
|
-
onChange: function(e) { setEditorPrefs(function(p) { return Object.assign({}, p, { wordWrap: e.target.value }); }); }
|
|
5769
|
-
},
|
|
5770
|
-
React.createElement('option', { value: 'off' }, 'Off'),
|
|
5771
|
-
React.createElement('option', { value: 'on' }, 'On'),
|
|
5772
|
-
React.createElement('option', { value: 'wordWrapColumn' }, 'Column')
|
|
5773
|
-
)
|
|
5774
|
-
),
|
|
5775
|
-
React.createElement(
|
|
5776
|
-
'label', { className: 'ide-settings-row ide-settings-row-half', title: 'Show line numbers in the gutter — On, Off, or Relative (useful with Vim mode)' },
|
|
5777
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Line numbers'),
|
|
5778
|
-
React.createElement(
|
|
5779
|
-
'select', {
|
|
5780
|
-
value: editorPrefs.lineNumbers || 'on',
|
|
5781
|
-
onChange: function(e) { setEditorPrefs(function(p) { return Object.assign({}, p, { lineNumbers: e.target.value }); }); }
|
|
5782
|
-
},
|
|
5783
|
-
React.createElement('option', { value: 'on' }, 'On'),
|
|
5784
|
-
React.createElement('option', { value: 'off' }, 'Off'),
|
|
5785
|
-
React.createElement('option', { value: 'relative' }, 'Relative')
|
|
5786
|
-
)
|
|
5787
|
-
),
|
|
5788
|
-
React.createElement(
|
|
5789
|
-
'label', { className: 'ide-settings-row ide-settings-row-half', title: 'Render whitespace characters visually — None, Selection only, Boundary (leading/trailing), or All' },
|
|
5790
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Whitespace'),
|
|
5791
|
-
React.createElement(
|
|
5792
|
-
'select', {
|
|
5793
|
-
value: editorPrefs.renderWhitespace || 'none',
|
|
5794
|
-
onChange: function(e) { setEditorPrefs(function(p) { return Object.assign({}, p, { renderWhitespace: e.target.value }); }); }
|
|
5795
|
-
},
|
|
5796
|
-
React.createElement('option', { value: 'none' }, 'None'),
|
|
5797
|
-
React.createElement('option', { value: 'selection' }, 'Selection'),
|
|
5798
|
-
React.createElement('option', { value: 'boundary' }, 'Boundary'),
|
|
5799
|
-
React.createElement('option', { value: 'all' }, 'All')
|
|
5800
|
-
)
|
|
5801
|
-
),
|
|
5802
|
-
React.createElement(
|
|
5803
|
-
'label', { className: 'ide-settings-row ide-settings-row-check', title: 'Show a scaled-down overview of the file on the right edge of the editor' },
|
|
5804
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Minimap'),
|
|
5805
|
-
React.createElement('input', {
|
|
5806
|
-
type: 'checkbox',
|
|
5807
|
-
className: 'ide-settings-checkbox',
|
|
5808
|
-
checked: !!(editorPrefs.minimap),
|
|
5809
|
-
onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { minimap: v }); }); }
|
|
5810
|
-
})
|
|
5811
|
-
),
|
|
5812
|
-
React.createElement(
|
|
5813
|
-
'label', { className: 'ide-settings-row ide-settings-row-check', title: 'Allow scrolling past the last line so it can be positioned at the top of the viewport' },
|
|
5814
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Scroll past end'),
|
|
5815
|
-
React.createElement('input', {
|
|
5816
|
-
type: 'checkbox',
|
|
5817
|
-
className: 'ide-settings-checkbox',
|
|
5818
|
-
checked: !!(editorPrefs.scrollBeyondLastLine),
|
|
5819
|
-
onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { scrollBeyondLastLine: v }); }); }
|
|
5820
|
-
})
|
|
5821
|
-
),
|
|
5822
|
-
React.createElement(
|
|
5823
|
-
'label', { className: 'ide-settings-row ide-settings-row-check', title: 'Colorize matching bracket pairs with distinct colors to make nesting easier to read' },
|
|
5824
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Bracket colors'),
|
|
5825
|
-
React.createElement('input', {
|
|
5826
|
-
type: 'checkbox',
|
|
5827
|
-
className: 'ide-settings-checkbox',
|
|
5828
|
-
checked: !!(editorPrefs.bracketPairColorization),
|
|
5829
|
-
onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { bracketPairColorization: v }); }); }
|
|
5830
|
-
})
|
|
5831
|
-
),
|
|
5832
|
-
React.createElement(
|
|
5833
|
-
'label', { className: 'ide-settings-row ide-settings-row-check', title: 'Enable Vim keybindings (Normal/Insert/Visual modes). Press Escape to return to Normal mode.' },
|
|
5834
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Vim mode'),
|
|
5835
|
-
React.createElement('input', {
|
|
5836
|
-
type: 'checkbox',
|
|
5837
|
-
className: 'ide-settings-checkbox',
|
|
5838
|
-
checked: !!(editorPrefs.vimMode),
|
|
5839
|
-
onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { vimMode: v }); }); }
|
|
5840
|
-
})
|
|
5841
|
-
),
|
|
5842
|
-
React.createElement(
|
|
5843
|
-
'label', { className: 'ide-settings-row ide-settings-row-half', title: 'When to insert a matching closing bracket automatically' },
|
|
5844
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Auto-close brackets'),
|
|
5845
|
-
React.createElement(
|
|
5846
|
-
'select', {
|
|
5847
|
-
value: editorPrefs.autoClosingBrackets || 'always',
|
|
5848
|
-
onChange: function(e) { setEditorPrefs(function(p) { return Object.assign({}, p, { autoClosingBrackets: e.target.value }); }); }
|
|
5849
|
-
},
|
|
5850
|
-
React.createElement('option', { value: 'always' }, 'Always'),
|
|
5851
|
-
React.createElement('option', { value: 'languageDefined' }, 'Per language rules'),
|
|
5852
|
-
React.createElement('option', { value: 'beforeWhitespace' }, 'Only before whitespace'),
|
|
5853
|
-
React.createElement('option', { value: 'never' }, 'Never')
|
|
5854
|
-
)
|
|
5855
|
-
),
|
|
5856
|
-
React.createElement(
|
|
5857
|
-
'label', { className: 'ide-settings-row ide-settings-row-half', title: 'When to insert a matching closing quote automatically' },
|
|
5858
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Auto-close quotes'),
|
|
5859
|
-
React.createElement(
|
|
5860
|
-
'select', {
|
|
5861
|
-
value: editorPrefs.autoClosingQuotes || 'always',
|
|
5862
|
-
onChange: function(e) { setEditorPrefs(function(p) { return Object.assign({}, p, { autoClosingQuotes: e.target.value }); }); }
|
|
5863
|
-
},
|
|
5864
|
-
React.createElement('option', { value: 'always' }, 'Always'),
|
|
5865
|
-
React.createElement('option', { value: 'languageDefined' }, 'Per language rules'),
|
|
5866
|
-
React.createElement('option', { value: 'beforeWhitespace' }, 'Only before whitespace'),
|
|
5867
|
-
React.createElement('option', { value: 'never' }, 'Never')
|
|
5868
|
-
)
|
|
5869
|
-
),
|
|
5870
|
-
React.createElement(
|
|
5871
|
-
'label', { className: 'ide-settings-row ide-settings-row-half', title: 'What to highlight on the current editor line' },
|
|
5872
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Line highlight'),
|
|
5873
|
-
React.createElement(
|
|
5874
|
-
'select', {
|
|
5875
|
-
value: editorPrefs.renderLineHighlight || 'none',
|
|
5876
|
-
onChange: function(e) { setEditorPrefs(function(p) { return Object.assign({}, p, { renderLineHighlight: e.target.value }); }); }
|
|
5877
|
-
},
|
|
5878
|
-
React.createElement('option', { value: 'none' }, 'None'),
|
|
5879
|
-
React.createElement('option', { value: 'gutter' }, 'Line number only'),
|
|
5880
|
-
React.createElement('option', { value: 'line' }, 'Current line background'),
|
|
5881
|
-
React.createElement('option', { value: 'all' }, 'Line number + background')
|
|
5882
|
-
)
|
|
5883
|
-
),
|
|
5884
|
-
React.createElement(
|
|
5885
|
-
'label', { className: 'ide-settings-row ide-settings-row-half', title: 'Shape of the text cursor in the editor' },
|
|
5886
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Cursor style'),
|
|
5887
|
-
React.createElement(
|
|
5888
|
-
'select', {
|
|
5889
|
-
value: editorPrefs.cursorStyle || 'line',
|
|
5890
|
-
onChange: function(e) { setEditorPrefs(function(p) { return Object.assign({}, p, { cursorStyle: e.target.value }); }); }
|
|
5891
|
-
},
|
|
5892
|
-
React.createElement('option', { value: 'line' }, 'Line (|)'),
|
|
5893
|
-
React.createElement('option', { value: 'block' }, 'Block (filled)'),
|
|
5894
|
-
React.createElement('option', { value: 'underline' }, 'Underline (_)'),
|
|
5895
|
-
React.createElement('option', { value: 'line-thin' }, 'Line thin'),
|
|
5896
|
-
React.createElement('option', { value: 'block-outline' }, 'Block outline'),
|
|
5897
|
-
React.createElement('option', { value: 'underline-thin' }, 'Underline thin')
|
|
5898
|
-
)
|
|
5899
|
-
),
|
|
5900
|
-
React.createElement(
|
|
5901
|
-
'label', { className: 'ide-settings-row ide-settings-row-half', title: 'Cursor animation style — Blink (on/off), Smooth (fade), Phase (offset fade), Expand (grow), or Solid (no animation)' },
|
|
5902
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Cursor blinking'),
|
|
5903
|
-
React.createElement(
|
|
5904
|
-
'select', {
|
|
5905
|
-
value: editorPrefs.cursorBlinking || 'blink',
|
|
5906
|
-
onChange: function(e) { setEditorPrefs(function(p) { return Object.assign({}, p, { cursorBlinking: e.target.value }); }); }
|
|
5907
|
-
},
|
|
5908
|
-
React.createElement('option', { value: 'blink' }, 'Blink (on/off)'),
|
|
5909
|
-
React.createElement('option', { value: 'smooth' }, 'Smooth (fade)'),
|
|
5910
|
-
React.createElement('option', { value: 'phase' }, 'Phase (offset fade)'),
|
|
5911
|
-
React.createElement('option', { value: 'expand' }, 'Expand (grow/shrink)'),
|
|
5912
|
-
React.createElement('option', { value: 'solid' }, 'Solid (no blink)')
|
|
5913
|
-
)
|
|
5914
|
-
),
|
|
5915
|
-
React.createElement(
|
|
5916
|
-
'label', { className: 'ide-settings-row ide-settings-row-check', title: 'Show collapse arrows next to foldable regions (functions, classes, blocks)' },
|
|
5917
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Code folding'),
|
|
5918
|
-
React.createElement('input', {
|
|
5919
|
-
type: 'checkbox',
|
|
5920
|
-
className: 'ide-settings-checkbox',
|
|
5921
|
-
checked: editorPrefs.folding !== false,
|
|
5922
|
-
onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { folding: v }); }); }
|
|
5923
|
-
})
|
|
5924
|
-
),
|
|
5925
|
-
React.createElement(
|
|
5926
|
-
'label', { className: 'ide-settings-row ide-settings-row-check', title: 'Animate scrolling instead of jumping instantly' },
|
|
5927
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Smooth scrolling'),
|
|
5928
|
-
React.createElement('input', {
|
|
5929
|
-
type: 'checkbox',
|
|
5930
|
-
className: 'ide-settings-checkbox',
|
|
5931
|
-
checked: !!(editorPrefs.smoothScrolling),
|
|
5932
|
-
onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { smoothScrolling: v }); }); }
|
|
5933
|
-
})
|
|
5934
|
-
),
|
|
5935
|
-
React.createElement(
|
|
5936
|
-
'label', { className: 'ide-settings-row ide-settings-row-check', title: 'Hold Ctrl (or Cmd) and scroll the mouse wheel to zoom the font size' },
|
|
5937
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Ctrl+scroll to zoom'),
|
|
5938
|
-
React.createElement('input', {
|
|
5939
|
-
type: 'checkbox',
|
|
5940
|
-
className: 'ide-settings-checkbox',
|
|
5941
|
-
checked: !!(editorPrefs.mouseWheelZoom),
|
|
5942
|
-
onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { mouseWheelZoom: v }); }); }
|
|
5943
|
-
})
|
|
5944
|
-
),
|
|
5945
|
-
|
|
5946
|
-
/* ── Behaviour ───────────────────────────────── */
|
|
5947
|
-
React.createElement('div', { className: 'ide-settings-section-header' }, 'Behaviour'),
|
|
5948
|
-
React.createElement(
|
|
5949
|
-
'label', { className: 'ide-settings-row ide-settings-row-half', title: 'How aggressively the editor re-indents lines as you type' },
|
|
5950
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Auto indent'),
|
|
5951
|
-
React.createElement(
|
|
5952
|
-
'select', {
|
|
5953
|
-
value: editorPrefs.autoIndent || 'full',
|
|
5954
|
-
onChange: function(e) { setEditorPrefs(function(p) { return Object.assign({}, p, { autoIndent: e.target.value }); }); }
|
|
5955
|
-
},
|
|
5956
|
-
React.createElement('option', { value: 'none' }, 'None (disabled)'),
|
|
5957
|
-
React.createElement('option', { value: 'keep' }, 'Keep current level'),
|
|
5958
|
-
React.createElement('option', { value: 'brackets' }, 'Indent on { and ['),
|
|
5959
|
-
React.createElement('option', { value: 'advanced' }, 'Language indent rules'),
|
|
5960
|
-
React.createElement('option', { value: 'full' }, 'Full (language grammar)')
|
|
5961
|
-
)
|
|
5962
|
-
),
|
|
5963
|
-
React.createElement(
|
|
5964
|
-
'label', { className: 'ide-settings-row ide-settings-row-half', title: 'Whether pressing Enter accepts the highlighted autocomplete suggestion' },
|
|
5965
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Accept suggestion on Enter'),
|
|
5966
|
-
React.createElement(
|
|
5967
|
-
'select', {
|
|
5968
|
-
value: editorPrefs.acceptSuggestionOnEnter || 'on',
|
|
5969
|
-
onChange: function(e) { setEditorPrefs(function(p) { return Object.assign({}, p, { acceptSuggestionOnEnter: e.target.value }); }); }
|
|
5970
|
-
},
|
|
5971
|
-
React.createElement('option', { value: 'on' }, 'Always'),
|
|
5972
|
-
React.createElement('option', { value: 'smart' }, 'Only when navigated (↑↓)'),
|
|
5973
|
-
React.createElement('option', { value: 'off' }, 'Never (Tab only)')
|
|
5974
|
-
)
|
|
5975
|
-
),
|
|
5976
|
-
React.createElement(
|
|
5977
|
-
'label', { className: 'ide-settings-row ide-settings-row-half', title: 'Suggest completions based on words already present in open files' },
|
|
5978
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Word-based suggestions'),
|
|
5979
|
-
React.createElement(
|
|
5980
|
-
'select', {
|
|
5981
|
-
value: editorPrefs.wordBasedSuggestions || 'matchingDocuments',
|
|
5982
|
-
onChange: function(e) { setEditorPrefs(function(p) { return Object.assign({}, p, { wordBasedSuggestions: e.target.value }); }); }
|
|
5983
|
-
},
|
|
5984
|
-
React.createElement('option', { value: 'off' }, 'Off'),
|
|
5985
|
-
React.createElement('option', { value: 'currentDocument' }, 'Current file only'),
|
|
5986
|
-
React.createElement('option', { value: 'matchingDocuments' }, 'Same language files'),
|
|
5987
|
-
React.createElement('option', { value: 'allDocuments' }, 'All open files')
|
|
5988
|
-
)
|
|
5989
|
-
),
|
|
5990
|
-
React.createElement(
|
|
5991
|
-
'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 {})' },
|
|
5992
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Format on type'),
|
|
5993
|
-
React.createElement('input', {
|
|
5994
|
-
type: 'checkbox',
|
|
5995
|
-
className: 'ide-settings-checkbox',
|
|
5996
|
-
checked: editorPrefs.formatOnType === true,
|
|
5997
|
-
onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { formatOnType: v }); }); }
|
|
5998
|
-
})
|
|
5999
|
-
),
|
|
6000
|
-
React.createElement(
|
|
6001
|
-
'label', { className: 'ide-settings-row ide-settings-row-check', title: 'Format the file before every save — RuboCop -A for Ruby, Prettier for JS/JSX/CSS/HTML/Markdown' },
|
|
6002
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Format on save'),
|
|
6003
|
-
React.createElement('input', {
|
|
6004
|
-
type: 'checkbox',
|
|
6005
|
-
className: 'ide-settings-checkbox',
|
|
6006
|
-
checked: editorPrefs.formatOnSave === true,
|
|
6007
|
-
onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { formatOnSave: v }); }); }
|
|
6008
|
-
})
|
|
6009
|
-
),
|
|
6010
|
-
React.createElement(
|
|
6011
|
-
'label', { className: 'ide-settings-row ide-settings-row-check', title: 'Show autocomplete suggestions while typing (not just on trigger characters like .)' },
|
|
6012
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Quick suggestions'),
|
|
6013
|
-
React.createElement('input', {
|
|
6014
|
-
type: 'checkbox',
|
|
6015
|
-
className: 'ide-settings-checkbox',
|
|
6016
|
-
checked: editorPrefs.quickSuggestions !== false,
|
|
6017
|
-
onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { quickSuggestions: v }); }); }
|
|
6018
|
-
})
|
|
6019
|
-
),
|
|
6020
|
-
|
|
6021
|
-
/* ── Formatting (Prettier) ───────────────────── */
|
|
6022
|
-
React.createElement('div', { className: 'ide-settings-section-header' }, 'Formatting'),
|
|
6023
|
-
React.createElement(
|
|
6024
|
-
'label', { className: 'ide-settings-row ide-settings-row-half', title: 'Prettier: maximum line length before wrapping (40–200)' },
|
|
6025
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Print width'),
|
|
6026
|
-
React.createElement('input', {
|
|
6027
|
-
key: String(editorPrefs.prettierPrintWidth != null ? editorPrefs.prettierPrintWidth : 80),
|
|
6028
|
-
type: 'number', min: '40', max: '200', step: '1',
|
|
6029
|
-
className: 'ide-settings-input',
|
|
6030
|
-
defaultValue: editorPrefs.prettierPrintWidth != null ? editorPrefs.prettierPrintWidth : 80,
|
|
6031
|
-
onChange: function(e) {
|
|
6032
|
-
var v = parseInt(e.target.value, 10);
|
|
6033
|
-
if (v >= 40 && v <= 200) setEditorPrefs(function(p) { return Object.assign({}, p, { prettierPrintWidth: v }); });
|
|
6034
|
-
},
|
|
6035
|
-
onBlur: function(e) {
|
|
6036
|
-
var v = parseInt(e.target.value, 10);
|
|
6037
|
-
if (isNaN(v) || v < 40 || v > 200) e.target.value = String(editorPrefs.prettierPrintWidth != null ? editorPrefs.prettierPrintWidth : 80);
|
|
6038
|
-
}
|
|
6039
|
-
})
|
|
6040
|
-
),
|
|
6041
|
-
React.createElement(
|
|
6042
|
-
'label', { className: 'ide-settings-row ide-settings-row-half', title: 'Prettier: add trailing commas in multi-line expressions — All (ES2017+), ES5 (objects/arrays only), or None' },
|
|
6043
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Trailing commas'),
|
|
6044
|
-
React.createElement(
|
|
6045
|
-
'select', {
|
|
6046
|
-
value: editorPrefs.prettierTrailingComma || 'all',
|
|
6047
|
-
onChange: function(e) { setEditorPrefs(function(p) { return Object.assign({}, p, { prettierTrailingComma: e.target.value }); }); }
|
|
6048
|
-
},
|
|
6049
|
-
React.createElement('option', { value: 'all' }, 'All'),
|
|
6050
|
-
React.createElement('option', { value: 'es5' }, 'ES5'),
|
|
6051
|
-
React.createElement('option', { value: 'none' }, 'None')
|
|
6052
|
-
)
|
|
6053
|
-
),
|
|
6054
|
-
React.createElement(
|
|
6055
|
-
'label', { className: 'ide-settings-row ide-settings-row-check', title: 'Prettier: add semicolons at the end of statements' },
|
|
6056
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Semicolons'),
|
|
6057
|
-
React.createElement('input', {
|
|
6058
|
-
type: 'checkbox',
|
|
6059
|
-
className: 'ide-settings-checkbox',
|
|
6060
|
-
checked: editorPrefs.prettierSemi !== false,
|
|
6061
|
-
onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { prettierSemi: v }); }); }
|
|
6062
|
-
})
|
|
6063
|
-
),
|
|
6064
|
-
React.createElement(
|
|
6065
|
-
'label', { className: 'ide-settings-row ide-settings-row-check', title: "Prettier: use single quotes instead of double quotes for strings" },
|
|
6066
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Single quotes'),
|
|
6067
|
-
React.createElement('input', {
|
|
6068
|
-
type: 'checkbox',
|
|
6069
|
-
className: 'ide-settings-checkbox',
|
|
6070
|
-
checked: !!editorPrefs.prettierSingleQuote,
|
|
6071
|
-
onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { prettierSingleQuote: v }); }); }
|
|
6072
|
-
})
|
|
6073
|
-
),
|
|
6074
|
-
React.createElement(
|
|
6075
|
-
'label', { className: 'ide-settings-row ide-settings-row-check', title: 'Prettier: add spaces inside object literal braces, e.g. { a: 1 } vs {a: 1}' },
|
|
6076
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Bracket spacing'),
|
|
6077
|
-
React.createElement('input', {
|
|
6078
|
-
type: 'checkbox',
|
|
6079
|
-
className: 'ide-settings-checkbox',
|
|
6080
|
-
checked: editorPrefs.prettierBracketSpacing !== false,
|
|
6081
|
-
onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { prettierBracketSpacing: v }); }); }
|
|
6082
|
-
})
|
|
6083
|
-
),
|
|
6084
|
-
|
|
6085
|
-
/* ── Interface ───────────────────────────────── */
|
|
6086
|
-
React.createElement('div', { className: 'ide-settings-section-header' }, 'Interface'),
|
|
6087
|
-
React.createElement(
|
|
6088
|
-
'label', { className: 'ide-settings-row ide-settings-row-check', title: 'Automatically scroll the file explorer to reveal and highlight the file you are editing' },
|
|
6089
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Explorer follows active file'),
|
|
6090
|
-
React.createElement('input', {
|
|
6091
|
-
type: 'checkbox',
|
|
6092
|
-
className: 'ide-settings-checkbox',
|
|
6093
|
-
checked: !!(editorPrefs.autoRevealInExplorer),
|
|
6094
|
-
onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { autoRevealInExplorer: v }); }); }
|
|
6095
|
-
})
|
|
6096
|
-
),
|
|
6097
|
-
React.createElement(
|
|
6098
|
-
'label', { className: 'ide-settings-row ide-settings-row-check', title: 'Jump to a file in the explorer by typing its name when the sidebar is focused' },
|
|
6099
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Explorer type-ahead'),
|
|
6100
|
-
React.createElement('input', {
|
|
6101
|
-
type: 'checkbox',
|
|
6102
|
-
className: 'ide-settings-checkbox',
|
|
6103
|
-
checked: !!(editorPrefs.fileTreeTypeahead !== false),
|
|
6104
|
-
onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { fileTreeTypeahead: v }); }); }
|
|
6105
|
-
})
|
|
6106
|
-
),
|
|
6107
|
-
React.createElement(
|
|
6108
|
-
'label', { className: 'ide-settings-row ide-settings-row-check', title: 'Show hidden files and directories (those starting with a dot, e.g. .env, .gitignore) in the file explorer' },
|
|
6109
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Show dotfiles'),
|
|
6110
|
-
React.createElement('input', {
|
|
6111
|
-
type: 'checkbox',
|
|
6112
|
-
className: 'ide-settings-checkbox',
|
|
6113
|
-
checked: !!(editorPrefs.showDotFiles),
|
|
6114
|
-
onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { showDotFiles: v }); }); }
|
|
6115
|
-
})
|
|
6116
|
-
),
|
|
6117
|
-
React.createElement(
|
|
6118
|
-
'label', { className: 'ide-settings-row ide-settings-row-half', title: 'Scroll: tabs overflow horizontally with a scrollbar; Wrap: tabs flow onto multiple rows' },
|
|
6119
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Tab bar layout'),
|
|
6120
|
-
React.createElement(
|
|
6121
|
-
'select', {
|
|
6122
|
-
value: editorPrefs.tabDisplayMode || 'scroll',
|
|
6123
|
-
onChange: function(e) { var v = e.target.value; setEditorPrefs(function(p) { return Object.assign({}, p, { tabDisplayMode: v }); }); }
|
|
6124
|
-
},
|
|
6125
|
-
React.createElement('option', { value: 'scroll' }, 'Scroll'),
|
|
6126
|
-
React.createElement('option', { value: 'wrap' }, 'Wrap (multi-row)')
|
|
6127
|
-
)
|
|
6128
|
-
),
|
|
6129
|
-
React.createElement(
|
|
6130
|
-
'label', { className: 'ide-settings-row ide-settings-row-check', title: 'Include folder names in the Quick Open picker (Ctrl+P / Cmd+P) results, not just files' },
|
|
6131
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Quick Open: show folders'),
|
|
6132
|
-
React.createElement('input', {
|
|
6133
|
-
type: 'checkbox',
|
|
6134
|
-
className: 'ide-settings-checkbox',
|
|
6135
|
-
checked: !!(editorPrefs.quickOpenShowFolders),
|
|
6136
|
-
onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { quickOpenShowFolders: v }); }); }
|
|
6137
|
-
})
|
|
6138
|
-
),
|
|
6139
|
-
React.createElement(
|
|
6140
|
-
'label', { className: 'ide-settings-row ide-settings-row-check', title: 'Hide toolbar button labels and show only icons, giving more horizontal space' },
|
|
6141
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Toolbar: icons only'),
|
|
6142
|
-
React.createElement('input', {
|
|
6143
|
-
type: 'checkbox',
|
|
6144
|
-
className: 'ide-settings-checkbox',
|
|
6145
|
-
// The stored preference, not the derived value: at a
|
|
6146
|
-
// narrow width the box would otherwise show as checked
|
|
6147
|
-
// and unchecking it would appear to do nothing.
|
|
6148
|
-
checked: !!(editorPrefs.toolbarIconOnly),
|
|
6149
|
-
onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { toolbarIconOnly: v }); }); }
|
|
6150
|
-
})
|
|
6151
|
-
),
|
|
6152
|
-
|
|
6153
|
-
React.createElement(
|
|
6154
|
-
'label', { className: 'ide-settings-row ide-settings-row-check', title: 'Keep the search/replace text when switching between files in the editor' },
|
|
6155
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Persist find state across files'),
|
|
6156
|
-
React.createElement('input', {
|
|
6157
|
-
type: 'checkbox',
|
|
6158
|
-
className: 'ide-settings-checkbox',
|
|
6159
|
-
checked: editorPrefs.persistFindState !== false,
|
|
6160
|
-
onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { persistFindState: v }); }); }
|
|
6161
|
-
})
|
|
6162
|
-
),
|
|
6163
|
-
React.createElement(
|
|
6164
|
-
'label', { className: 'ide-settings-row ide-settings-row-check', title: 'Save which files are open per branch and restore them when switching branches. Disable to always start with a clean slate when switching.' },
|
|
6165
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Restore tabs on branch switch'),
|
|
6166
|
-
React.createElement('input', {
|
|
6167
|
-
type: 'checkbox',
|
|
6168
|
-
className: 'ide-settings-checkbox',
|
|
6169
|
-
checked: editorPrefs.branchStateRestore !== false,
|
|
6170
|
-
onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { branchStateRestore: v }); }); }
|
|
6171
|
-
})
|
|
6172
|
-
),
|
|
6173
|
-
|
|
6174
|
-
React.createElement(
|
|
6175
|
-
'label', { className: 'ide-settings-row ide-settings-row-check', title: 'Show the verb and path that route to each controller action after its def line, and mark public actions nothing routes to' },
|
|
6176
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Controller route hints'),
|
|
6177
|
-
React.createElement('input', {
|
|
6178
|
-
type: 'checkbox',
|
|
6179
|
-
className: 'ide-settings-checkbox',
|
|
6180
|
-
checked: editorPrefs.routeHints !== false,
|
|
6181
|
-
onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { routeHints: v }); }); }
|
|
6182
|
-
})
|
|
6183
|
-
),
|
|
6184
|
-
|
|
6185
|
-
/* ── RuboCop ─────────────────────────────────── */
|
|
6186
|
-
React.createElement('div', { className: 'ide-settings-section-header' }, 'RuboCop'),
|
|
6187
|
-
React.createElement(
|
|
6188
|
-
'label', { className: 'ide-settings-row ide-settings-row-check', title: 'Run RuboCop in the background and show lint warnings/errors as markers in the editor gutter' },
|
|
6189
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Enable RuboCop linting'),
|
|
6190
|
-
React.createElement('input', {
|
|
6191
|
-
type: 'checkbox',
|
|
6192
|
-
className: 'ide-settings-checkbox',
|
|
6193
|
-
checked: editorPrefs.rubocopLintEnabled !== false,
|
|
6194
|
-
onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { rubocopLintEnabled: v }); }); }
|
|
6195
|
-
})
|
|
6196
|
-
),
|
|
6197
|
-
rubocopAvailable && rubocopConfigPath ? React.createElement(
|
|
6198
|
-
'div', { className: 'ide-settings-row ide-settings-row-link' },
|
|
6199
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Config file'),
|
|
6200
|
-
React.createElement(
|
|
6201
|
-
'button', {
|
|
6202
|
-
type: 'button',
|
|
6203
|
-
className: 'ide-settings-config-link',
|
|
6204
|
-
title: 'Open ' + rubocopConfigPath,
|
|
6205
|
-
onClick: function() { handleSelectFile(rubocopConfigPath, rubocopConfigPath.split('/').pop()); }
|
|
6206
|
-
},
|
|
6207
|
-
React.createElement('i', { className: 'fas fa-file-alt', style: { marginRight: 5 } }),
|
|
6208
|
-
rubocopConfigPath
|
|
6209
|
-
)
|
|
6210
|
-
) : null,
|
|
6211
|
-
React.createElement(
|
|
6212
|
-
'button',
|
|
6213
|
-
{
|
|
6214
|
-
className: 'ide-settings-reset-btn',
|
|
6215
|
-
type: 'button',
|
|
6216
|
-
title: 'Restore every editor preference on this page to its default',
|
|
6217
|
-
onClick: function() { setEditorPrefs(Object.assign({}, DEFAULT_EDITOR_PREFS)); }
|
|
6218
|
-
},
|
|
6219
|
-
React.createElement('i', { className: 'fas fa-undo', style: { marginRight: 6 } }),
|
|
6220
|
-
'Reset to defaults'
|
|
6221
|
-
)
|
|
6222
|
-
)
|
|
6223
|
-
);
|
|
6224
6116
|
} else if (pActiveTab.isDiff) {
|
|
6225
6117
|
var _t = editorPrefs.theme || 'vs-dark';
|
|
6226
6118
|
var isDiffDark = _t !== 'vs' && _t !== 'hc-light' && _t !== 'github-light';
|
|
@@ -6240,17 +6132,14 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
6240
6132
|
paneId: pane.id,
|
|
6241
6133
|
markers: markers[pActiveTab.id] || [],
|
|
6242
6134
|
gitAvailable: gitAvailable,
|
|
6243
|
-
testAvailable: testAvailable,
|
|
6244
6135
|
treeData: treeData,
|
|
6245
6136
|
testResult: testResult,
|
|
6246
6137
|
testPanelFile: testPanelFile,
|
|
6247
|
-
testLoading: testLoading,
|
|
6248
6138
|
testInlineVisible: testInlineVisible,
|
|
6249
6139
|
editorPrefs: editorPrefs,
|
|
6250
6140
|
monacoReady: monacoReady,
|
|
6251
6141
|
onFormat: function() { onFormatRef.current(); },
|
|
6252
6142
|
onSave: function() { handleSave(pane.id, pActiveTab); },
|
|
6253
|
-
onRunTest: handleRunTest,
|
|
6254
6143
|
onRunTestAtCursor: handleRunTestAtCursor,
|
|
6255
6144
|
onShowHistory: function(path) { setHistoryPanelPath(path); },
|
|
6256
6145
|
onContentChange: function onContentChange(val) {
|
|
@@ -6272,9 +6161,12 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
6272
6161
|
return React.createElement(
|
|
6273
6162
|
React.Fragment,
|
|
6274
6163
|
{ key: pane.id },
|
|
6275
|
-
idx === 1 && isSplit && React.createElement(
|
|
6276
|
-
|
|
6277
|
-
|
|
6164
|
+
idx === 1 && isSplit && React.createElement((window.Gutter || Gutter), {
|
|
6165
|
+
orientation: 'vertical',
|
|
6166
|
+
className: 'ide-gutter-pane',
|
|
6167
|
+
label: "Resize editor groups",
|
|
6168
|
+
active: activeResizeMode === 'pane',
|
|
6169
|
+
onDragStart: startPaneResize
|
|
6278
6170
|
}),
|
|
6279
6171
|
React.createElement(
|
|
6280
6172
|
"div",
|
|
@@ -6282,10 +6174,6 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
6282
6174
|
className: "ide-pane " + (isFocused ? 'focused' : '') + " " + (isDropTarget ? 'drop-target' : ''),
|
|
6283
6175
|
style: { flexBasis: flexBasis, flexShrink: 0, flexGrow: 0, display: 'flex', flexDirection: 'column', minWidth: 0 },
|
|
6284
6176
|
onClickCapture: function (e) {
|
|
6285
|
-
// Do not steal click events from controls inside the Settings tab.
|
|
6286
|
-
// Focusing the pane in capture phase can rerender before checkbox
|
|
6287
|
-
// change events are processed, making toggles appear stuck.
|
|
6288
|
-
if (e.target && e.target.closest && e.target.closest('.ide-settings-tab-content')) return;
|
|
6289
6177
|
return TabManager.focusPane(pane.id);
|
|
6290
6178
|
},
|
|
6291
6179
|
onDragOver: function (e) {
|
|
@@ -6427,40 +6315,54 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
6427
6315
|
);
|
|
6428
6316
|
})
|
|
6429
6317
|
),
|
|
6318
|
+
showLogPanel && !zenMode && React.createElement((window.Gutter || Gutter), {
|
|
6319
|
+
orientation: 'horizontal',
|
|
6320
|
+
label: "Resize log drawer",
|
|
6321
|
+
active: activeResizeMode === 'log',
|
|
6322
|
+
onDragStart: startDrawerResize('log', logHeight, 'mbeditorLogHeight')
|
|
6323
|
+
}),
|
|
6430
6324
|
showLogPanel && !zenMode && React.createElement(window.LogPanel || LogPanel, {
|
|
6325
|
+
height: logHeight,
|
|
6431
6326
|
onClose: function () { setShowLogPanel(false); }
|
|
6432
6327
|
}),
|
|
6328
|
+
// With the drawer closed, the gutter above the status bar is a handle:
|
|
6329
|
+
// drag it up to open Problems at the dragged height.
|
|
6330
|
+
showProblemsPanel && !zenMode && React.createElement((window.Gutter || Gutter), {
|
|
6331
|
+
orientation: 'horizontal',
|
|
6332
|
+
label: "Resize problems drawer",
|
|
6333
|
+
active: activeResizeMode === 'problems',
|
|
6334
|
+
onDragStart: startDrawerResize('problems', problemsHeight, 'mbeditorProblemsHeight')
|
|
6335
|
+
}),
|
|
6433
6336
|
showProblemsPanel && !zenMode && React.createElement(window.ProblemsPanel || ProblemsPanel, {
|
|
6337
|
+
height: problemsHeight,
|
|
6434
6338
|
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
6339
|
onOpenFile: function (path, line, col) {
|
|
6442
6340
|
handleSelectFile(path, path.split('/').pop(), line, col);
|
|
6443
6341
|
},
|
|
6444
|
-
//
|
|
6445
|
-
//
|
|
6446
|
-
//
|
|
6447
|
-
//
|
|
6448
|
-
|
|
6449
|
-
|
|
6450
|
-
|
|
6451
|
-
}
|
|
6452
|
-
})
|
|
6342
|
+
// `rubocop -a` writes through a subprocess, so the server's
|
|
6343
|
+
// files_changed broadcast is the only notice — and there is no
|
|
6344
|
+
// broadcast at all without a cable connection. Re-read every open tab
|
|
6345
|
+
// here too: clean tabs take the corrected text (and re-lint), dirty
|
|
6346
|
+
// ones get the usual reload prompt instead of being silently clobbered.
|
|
6347
|
+
onFilesRewritten: function () { checkOpenTabsForExternalChanges(); }
|
|
6348
|
+
}),
|
|
6453
6349
|
),
|
|
6454
6350
|
|
|
6455
6351
|
// Right-side Git panel (children of ide-body, alongside sidebar and ide-main)
|
|
6456
|
-
showGitPanel && !zenMode && React.createElement(
|
|
6457
|
-
|
|
6458
|
-
|
|
6459
|
-
|
|
6460
|
-
|
|
6461
|
-
|
|
6352
|
+
!showGitPanel && !zenMode && !showModelGraphView && React.createElement((window.Gutter || Gutter), {
|
|
6353
|
+
orientation: 'vertical',
|
|
6354
|
+
label: "Drag left to open the git panel",
|
|
6355
|
+
snapDirection: -1,
|
|
6356
|
+
onSnap: function () { setShowGitPanel(true); }
|
|
6357
|
+
}),
|
|
6358
|
+
showGitPanel && !zenMode && !showModelGraphView && React.createElement((window.Gutter || Gutter), {
|
|
6359
|
+
orientation: 'vertical',
|
|
6360
|
+
className: 'ide-gutter-gitpanel',
|
|
6361
|
+
label: "Resize git panel",
|
|
6362
|
+
active: activeResizeMode === 'gitpanel',
|
|
6363
|
+
onDragStart: startGitPanelResize
|
|
6462
6364
|
}),
|
|
6463
|
-
showGitPanel && !zenMode && React.createElement(
|
|
6365
|
+
showGitPanel && !zenMode && !showModelGraphView && React.createElement(
|
|
6464
6366
|
"div",
|
|
6465
6367
|
{ className: "ide-git-right-panel", style: { width: gitPanelWidth + "px" } },
|
|
6466
6368
|
React.createElement(window.GitPanel || GitPanel, {
|
|
@@ -6476,6 +6378,19 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
6476
6378
|
})
|
|
6477
6379
|
),
|
|
6478
6380
|
),
|
|
6381
|
+
// ponytail: opens on release, no live preview; track the drag if that grates.
|
|
6382
|
+
!showProblemsPanel && !zenMode && !showModelGraphView && React.createElement((window.Gutter || Gutter), {
|
|
6383
|
+
orientation: 'horizontal',
|
|
6384
|
+
className: 'ide-gutter-drawer-open',
|
|
6385
|
+
label: "Drag up to open Problems",
|
|
6386
|
+
snapThreshold: 40,
|
|
6387
|
+
onSnap: function (dragged) {
|
|
6388
|
+
var h = Math.max(DRAWER_MIN_HEIGHT, dragged);
|
|
6389
|
+
try { window.localStorage.setItem('mbeditorProblemsHeight', String(h)); } catch (err) {}
|
|
6390
|
+
setProblemsHeight(h);
|
|
6391
|
+
setShowProblemsPanel(true);
|
|
6392
|
+
}
|
|
6393
|
+
}),
|
|
6479
6394
|
React.createElement(
|
|
6480
6395
|
"div",
|
|
6481
6396
|
{ className: "ide-statusbar" },
|
|
@@ -6509,45 +6424,17 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
6509
6424
|
},
|
|
6510
6425
|
React.createElement("i", { className: "fas fa-bug statusbar-problems-error-icon" }),
|
|
6511
6426
|
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
|
-
}),
|
|
6427
|
+
React.createElement("i", { className: "fas fa-exclamation-triangle statusbar-problems-warning-icon" }),
|
|
6516
6428
|
React.createElement("span", { className: "statusbar-problems-count" }, problemCounts.warnings)
|
|
6517
6429
|
),
|
|
6518
|
-
|
|
6519
|
-
|
|
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",
|
|
6430
|
+
migrationsPending && React.createElement(
|
|
6431
|
+
"div",
|
|
6537
6432
|
{
|
|
6538
|
-
|
|
6539
|
-
|
|
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.'
|
|
6433
|
+
className: "statusbar-migration",
|
|
6434
|
+
title: "Pending migrations \u2014 run `rails db:migrate`. Editing still works in the meantime."
|
|
6545
6435
|
},
|
|
6546
|
-
React.createElement("i", {
|
|
6547
|
-
|
|
6548
|
-
"aria-hidden": "true"
|
|
6549
|
-
}),
|
|
6550
|
-
lspHealth.status !== 'ok' && React.createElement("span", null, " ruby-lsp")
|
|
6436
|
+
React.createElement("i", { className: "fas fa-database" }),
|
|
6437
|
+
" Migrations pending"
|
|
6551
6438
|
),
|
|
6552
6439
|
!serverOnline && (function () {
|
|
6553
6440
|
var dirtyCount = state.panes.reduce(function (acc, p) {
|
|
@@ -6608,6 +6495,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
6608
6495
|
},
|
|
6609
6496
|
React.createElement("i", { className: "fas fa-paragraph" })
|
|
6610
6497
|
),
|
|
6498
|
+
React.createElement(CursorPosition, null),
|
|
6611
6499
|
activeEOL && React.createElement(
|
|
6612
6500
|
"button",
|
|
6613
6501
|
{
|
|
@@ -6820,6 +6708,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
6820
6708
|
"div",
|
|
6821
6709
|
{
|
|
6822
6710
|
className: "context-menu",
|
|
6711
|
+
ref: contextMenuRef,
|
|
6823
6712
|
style: { left: contextMenu.x, top: contextMenu.y },
|
|
6824
6713
|
onClick: function (e) {
|
|
6825
6714
|
return e.stopPropagation();
|
|
@@ -6864,16 +6753,16 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
6864
6753
|
return handleContextMenuAction('delete');
|
|
6865
6754
|
} },
|
|
6866
6755
|
React.createElement("i", { className: "far fa-trash-alt context-menu-icon" }),
|
|
6867
|
-
"
|
|
6756
|
+
" " + deleteMenuLabel(contextMenu.node)
|
|
6868
6757
|
),
|
|
6869
6758
|
React.createElement("div", { className: "context-menu-divider" }),
|
|
6870
|
-
contextMenu.node &&
|
|
6759
|
+
contextMenu.node && React.createElement(
|
|
6871
6760
|
"div",
|
|
6872
6761
|
{ className: "context-menu-item", onClick: function () {
|
|
6873
6762
|
return handleContextMenuAction('download');
|
|
6874
6763
|
} },
|
|
6875
6764
|
React.createElement("i", { className: "fas fa-download context-menu-icon" }),
|
|
6876
|
-
"
|
|
6765
|
+
" " + downloadMenuLabel(contextMenu.node)
|
|
6877
6766
|
),
|
|
6878
6767
|
React.createElement(
|
|
6879
6768
|
"div",
|
|
@@ -7067,4 +6956,6 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
7067
6956
|
};
|
|
7068
6957
|
|
|
7069
6958
|
window.MbeditorApp = MbeditorApp;
|
|
6959
|
+
// Test-only: lets the system test seed/inspect the test-result cache directly.
|
|
6960
|
+
window.__mbeditorTestCache = { save: saveCachedTestResult, prefix: TEST_CACHE_PREFIX };
|
|
7070
6961
|
/* TITLE BAR */ /* SIDEBAR */ /* EDITOR AREA */ /* STATUS BAR */ /* Right-click context menu */
|