mbeditor 0.13.1 → 0.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +132 -0
- data/app/assets/javascripts/mbeditor/application.js +5 -0
- data/app/assets/javascripts/mbeditor/audit_log.js +165 -0
- data/app/assets/javascripts/mbeditor/collaboration_service.js +248 -26
- 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/CombinedFileView.js +214 -0
- data/app/assets/javascripts/mbeditor/components/DiffViewer.js +1 -1
- data/app/assets/javascripts/mbeditor/components/EditorPanel.js +442 -248
- 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/ImportConflictModal.js +60 -5
- data/app/assets/javascripts/mbeditor/components/LogPanel.js +3 -44
- data/app/assets/javascripts/mbeditor/components/MbeditorApp.js +1241 -1256
- data/app/assets/javascripts/mbeditor/components/ModelGraph.js +94 -37
- data/app/assets/javascripts/mbeditor/components/PastePlaceModal.js +329 -0
- data/app/assets/javascripts/mbeditor/components/ProblemsPanel.js +47 -65
- data/app/assets/javascripts/mbeditor/components/QuickOpenDialog.js +26 -8
- data/app/assets/javascripts/mbeditor/components/SettingsModal.js +342 -0
- data/app/assets/javascripts/mbeditor/components/ShortcutHelp.js +18 -1
- data/app/assets/javascripts/mbeditor/components/TabBar.js +67 -98
- data/app/assets/javascripts/mbeditor/editor_plugins.js +705 -323
- data/app/assets/javascripts/mbeditor/file_import.js +13 -15
- data/app/assets/javascripts/mbeditor/file_service.js +48 -39
- 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 +9 -0
- data/app/assets/javascripts/mbeditor/tab_manager.js +127 -49
- 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 +672 -249
- data/app/assets/stylesheets/mbeditor/glass.css +174 -0
- data/app/assets/stylesheets/mbeditor/themes.css +93 -33
- data/app/channels/mbeditor/collaboration_channel.rb +17 -4
- data/app/controllers/mbeditor/application_controller.rb +26 -3
- data/app/controllers/mbeditor/editors_controller.rb +124 -439
- data/app/services/mbeditor/archive_service.rb +137 -0
- data/app/services/mbeditor/collaboration_doc_store.rb +143 -14
- 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_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/ruby_lsp_result_translator.rb +226 -0
- data/app/services/mbeditor/ruby_method_ranges_service.rb +120 -0
- data/app/services/mbeditor/search_replace_service.rb +8 -0
- data/app/views/layouts/mbeditor/application.html.erb +1 -1
- data/lib/mbeditor/audit_log.rb +203 -0
- data/lib/mbeditor/configuration.rb +6 -1
- data/lib/mbeditor/rack/pending_migration_bypass.rb +31 -10
- data/lib/mbeditor/route_map.rb +5 -0
- data/lib/mbeditor/ruby_lsp_client.rb +82 -14
- data/lib/mbeditor/version.rb +1 -1
- data/lib/mbeditor.rb +1 -0
- data/public/monaco-editor/VERSIONS +1 -1
- data/public/monaco-editor/codicon.ttf +0 -0
- data/public/monaco-editor/css.worker.js +41 -37
- data/public/monaco-editor/editor.worker.js +19 -15
- data/public/monaco-editor/html.worker.js +53 -49
- data/public/monaco-editor/json.worker.js +36 -32
- data/public/monaco-editor/monaco.css +1 -1
- data/public/monaco-editor/monaco.js +388 -301
- data/public/monaco-editor/ts.worker.js +226 -222
- data/vendor/assets/javascripts/axios.min.js +3 -3
- metadata +15 -2
|
@@ -44,6 +44,32 @@ var ProblemsPanel = (function () {
|
|
|
44
44
|
return text.length > CODE_PREVIEW_LIMIT ? text.slice(0, CODE_PREVIEW_LIMIT) + '…' : text;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
// VS Code-style file group header: chevron, file-type icon, basename, then
|
|
48
|
+
// the containing directory in a dimmer colour. `path` is workspace-relative.
|
|
49
|
+
function fileGroupName(path) {
|
|
50
|
+
var slash = path.lastIndexOf('/');
|
|
51
|
+
return {
|
|
52
|
+
base: slash === -1 ? path : path.slice(slash + 1),
|
|
53
|
+
dir: slash === -1 ? '' : path.slice(0, slash)
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function renderFileGroupHeader(path, count) {
|
|
58
|
+
var parts = fileGroupName(path);
|
|
59
|
+
return React.createElement(
|
|
60
|
+
'div',
|
|
61
|
+
{ className: 'ide-problems-file-name' },
|
|
62
|
+
React.createElement('i', { className: 'fas fa-chevron-down', 'aria-hidden': 'true' }),
|
|
63
|
+
React.createElement('i', {
|
|
64
|
+
className: (window.getFileIcon ? window.getFileIcon(parts.base) : 'far fa-file-code'),
|
|
65
|
+
'aria-hidden': 'true'
|
|
66
|
+
}),
|
|
67
|
+
React.createElement('span', null, parts.base),
|
|
68
|
+
parts.dir && React.createElement('span', { className: 'ide-problems-dir' }, parts.dir),
|
|
69
|
+
React.createElement('span', { className: 'ide-problems-file-count' }, count)
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
47
73
|
// Reading markers means walking every model, so callers that only want the
|
|
48
74
|
// counts share this one pass. Exposed on the component for the status bar.
|
|
49
75
|
function collect() {
|
|
@@ -111,6 +137,9 @@ var ProblemsPanel = (function () {
|
|
|
111
137
|
|
|
112
138
|
var Panel = function ProblemsPanelComponent(_ref) {
|
|
113
139
|
var onClose = _ref.onClose;
|
|
140
|
+
// Height is owned by MbeditorApp: the gutter that drags it lives between
|
|
141
|
+
// the editor card and this drawer, not inside it.
|
|
142
|
+
var height = _ref.height;
|
|
114
143
|
var onOpenFile = _ref.onOpenFile;
|
|
115
144
|
// Called after `-a` has rewritten files on disk, so the app can re-read
|
|
116
145
|
// every open tab. Nothing else would: the write came from a subprocess,
|
|
@@ -226,34 +255,6 @@ var ProblemsPanel = (function () {
|
|
|
226
255
|
});
|
|
227
256
|
};
|
|
228
257
|
|
|
229
|
-
var MIN_HEIGHT = 120;
|
|
230
|
-
var _height = React.useState(function () {
|
|
231
|
-
var saved = parseInt(window.localStorage.getItem('mbeditorProblemsHeight'), 10);
|
|
232
|
-
return (saved && saved >= MIN_HEIGHT) ? saved : 240;
|
|
233
|
-
});
|
|
234
|
-
var height = _height[0], setHeight = _height[1];
|
|
235
|
-
var heightRef = React.useRef(height);
|
|
236
|
-
heightRef.current = height;
|
|
237
|
-
|
|
238
|
-
// Same delta-based resize as the log drawer.
|
|
239
|
-
var onResizeMouseDown = function (e) {
|
|
240
|
-
e.preventDefault();
|
|
241
|
-
var startY = e.clientY;
|
|
242
|
-
var startHeight = heightRef.current;
|
|
243
|
-
var onMove = function (ev) {
|
|
244
|
-
var vh = window.innerHeight || document.documentElement.clientHeight || 0;
|
|
245
|
-
var maxH = vh > 0 ? Math.round(vh * 0.85) : Infinity;
|
|
246
|
-
setHeight(Math.min(maxH, Math.max(MIN_HEIGHT, startHeight + (startY - ev.clientY))));
|
|
247
|
-
};
|
|
248
|
-
var onUp = function () {
|
|
249
|
-
document.removeEventListener('mousemove', onMove);
|
|
250
|
-
document.removeEventListener('mouseup', onUp);
|
|
251
|
-
window.localStorage.setItem('mbeditorProblemsHeight', String(heightRef.current));
|
|
252
|
-
};
|
|
253
|
-
document.addEventListener('mousemove', onMove);
|
|
254
|
-
document.addEventListener('mouseup', onUp);
|
|
255
|
-
};
|
|
256
|
-
|
|
257
258
|
React.useEffect(function () {
|
|
258
259
|
if (!window.monaco || !window.monaco.editor) return;
|
|
259
260
|
// Debounced for the same reason the status-bar tally is: markers change
|
|
@@ -314,16 +315,11 @@ var ProblemsPanel = (function () {
|
|
|
314
315
|
return React.createElement(
|
|
315
316
|
'div',
|
|
316
317
|
{ className: 'ide-problems-drawer', style: { height: height + 'px' } },
|
|
317
|
-
React.createElement('div', {
|
|
318
|
-
className: 'ide-problems-resize',
|
|
319
|
-
title: 'Drag to resize',
|
|
320
|
-
onMouseDown: onResizeMouseDown
|
|
321
|
-
}),
|
|
322
318
|
React.createElement(
|
|
323
319
|
'div',
|
|
324
320
|
{ className: 'ide-problems-header' },
|
|
325
|
-
React.createElement('i', { className: 'fas fa-bug' }),
|
|
326
321
|
React.createElement('span', { className: 'ide-problems-title' }, 'Problems'),
|
|
322
|
+
React.createElement('span', { className: 'ide-problems-total-pill' }, total + wsCounts.error + wsCounts.warning + wsCounts.info),
|
|
327
323
|
// No prose summary: the severity chips below carry the same three
|
|
328
324
|
// counts, and restating them was the longest thing in the header.
|
|
329
325
|
React.createElement(
|
|
@@ -340,7 +336,7 @@ var ProblemsPanel = (function () {
|
|
|
340
336
|
{
|
|
341
337
|
key: s.kind,
|
|
342
338
|
type: 'button',
|
|
343
|
-
className: 'ide-problems-sev-chip
|
|
339
|
+
className: 'ide-problems-sev-chip' + (on ? ' is-on' : ''),
|
|
344
340
|
title: (on ? 'Hide' : 'Show') + ' ' + SEVERITY_LABEL[s.kind].toLowerCase() + 's',
|
|
345
341
|
'aria-pressed': on ? 'true' : 'false',
|
|
346
342
|
onClick: function () { toggleSeverity(s.kind); }
|
|
@@ -350,19 +346,25 @@ var ProblemsPanel = (function () {
|
|
|
350
346
|
);
|
|
351
347
|
})
|
|
352
348
|
),
|
|
349
|
+
React.createElement('input', {
|
|
350
|
+
className: 'ide-problems-filter',
|
|
351
|
+
type: 'text',
|
|
352
|
+
placeholder: 'Filter…',
|
|
353
|
+
value: filter,
|
|
354
|
+
onChange: function (e) { setFilter(e.target.value); }
|
|
355
|
+
}),
|
|
353
356
|
React.createElement(
|
|
354
357
|
'div',
|
|
355
358
|
{ className: 'ide-problems-actions' },
|
|
356
359
|
React.createElement('button', {
|
|
357
|
-
type: 'button', className: 'ide-
|
|
360
|
+
type: 'button', className: 'ide-icon-btn',
|
|
358
361
|
disabled: !!running,
|
|
359
362
|
title: 'Run rubocop over the whole workspace',
|
|
360
363
|
onClick: function () { runRubocop('check'); }
|
|
361
364
|
},
|
|
362
|
-
React.createElement('i', { className: running === 'check' ? 'fas fa-spinner fa-spin' : 'fas fa-play' }),
|
|
363
|
-
React.createElement('span', null, ' RuboCop')),
|
|
365
|
+
React.createElement('i', { className: running === 'check' ? 'fas fa-spinner fa-spin' : 'fas fa-play' })),
|
|
364
366
|
React.createElement('button', {
|
|
365
|
-
type: 'button', className: 'ide-
|
|
367
|
+
type: 'button', className: 'ide-icon-btn',
|
|
366
368
|
// Nothing to correct until a run says so, and rerunning `-a` on a
|
|
367
369
|
// clean tree is a slow no-op.
|
|
368
370
|
disabled: !!running || !workspace || !workspace.correctable,
|
|
@@ -373,28 +375,19 @@ var ProblemsPanel = (function () {
|
|
|
373
375
|
: 'Run RuboCop first',
|
|
374
376
|
onClick: function () { runRubocop('autocorrect'); }
|
|
375
377
|
},
|
|
376
|
-
React.createElement('i', { className: running === 'autocorrect' ? 'fas fa-spinner fa-spin' : 'fas fa-magic' }),
|
|
377
|
-
React.createElement('span', null, ' Fix')),
|
|
378
|
+
React.createElement('i', { className: running === 'autocorrect' ? 'fas fa-spinner fa-spin' : 'fas fa-magic' })),
|
|
378
379
|
React.createElement('button', {
|
|
379
|
-
type: 'button', className: 'ide-
|
|
380
|
+
type: 'button', className: 'ide-icon-btn',
|
|
380
381
|
disabled: !workspace || !(workspace.files || []).length,
|
|
381
382
|
title: workspace && (workspace.files || []).length
|
|
382
383
|
? 'Open all ' + workspace.files.length + ' file(s) with offenses'
|
|
383
384
|
: 'Run RuboCop first',
|
|
384
385
|
onClick: openAllOffending
|
|
385
386
|
},
|
|
386
|
-
React.createElement('i', { className: 'fas fa-folder-open' })
|
|
387
|
-
React.createElement('span', null, ' Open all'))
|
|
387
|
+
React.createElement('i', { className: 'fas fa-folder-open' }))
|
|
388
388
|
),
|
|
389
|
-
React.createElement('input', {
|
|
390
|
-
className: 'ide-problems-filter',
|
|
391
|
-
type: 'text',
|
|
392
|
-
placeholder: 'Filter…',
|
|
393
|
-
value: filter,
|
|
394
|
-
onChange: function (e) { setFilter(e.target.value); }
|
|
395
|
-
}),
|
|
396
389
|
React.createElement('button', {
|
|
397
|
-
type: 'button', className: 'ide-
|
|
390
|
+
type: 'button', className: 'ide-icon-btn',
|
|
398
391
|
title: 'Close', onClick: onClose
|
|
399
392
|
}, React.createElement('i', { className: 'fas fa-times' }))
|
|
400
393
|
),
|
|
@@ -412,13 +405,7 @@ var ProblemsPanel = (function () {
|
|
|
412
405
|
return React.createElement(
|
|
413
406
|
'div',
|
|
414
407
|
{ className: 'ide-problems-file', key: 'ws:' + file.path },
|
|
415
|
-
|
|
416
|
-
'div',
|
|
417
|
-
{ className: 'ide-problems-file-name' },
|
|
418
|
-
React.createElement('i', { className: 'fas fa-gavel', 'aria-hidden': 'true' }),
|
|
419
|
-
' ' + file.path,
|
|
420
|
-
React.createElement('span', { className: 'ide-problems-file-count' }, file.offenses.length)
|
|
421
|
-
),
|
|
408
|
+
renderFileGroupHeader(file.path, file.offenses.length),
|
|
422
409
|
file.offenses.map(function (o, i) {
|
|
423
410
|
var kind = COP_SEVERITY_KIND[o.severity] || 'info';
|
|
424
411
|
return React.createElement(
|
|
@@ -502,12 +489,7 @@ var ProblemsPanel = (function () {
|
|
|
502
489
|
return React.createElement(
|
|
503
490
|
'div',
|
|
504
491
|
{ className: 'ide-problems-file', key: entry.path },
|
|
505
|
-
|
|
506
|
-
'div',
|
|
507
|
-
{ className: 'ide-problems-file-name' },
|
|
508
|
-
entry.path,
|
|
509
|
-
React.createElement('span', { className: 'ide-problems-file-count' }, entry.markers.length)
|
|
510
|
-
),
|
|
492
|
+
renderFileGroupHeader(entry.path, entry.markers.length),
|
|
511
493
|
entry.markers.map(function (item, index) {
|
|
512
494
|
var marker = item.marker;
|
|
513
495
|
var kind = SEVERITY_KIND[marker.severity] || 'info';
|
|
@@ -65,10 +65,19 @@ function recentRanks() {
|
|
|
65
65
|
// in — a typo'd or transposed query hits nothing literally, so an exact match
|
|
66
66
|
// can never be demoted by loosening the index search.
|
|
67
67
|
var FUZZY_ONLY = 4;
|
|
68
|
+
|
|
69
|
+
// CamelCase -> snake_case, so a query like "ThemeController" ranks against
|
|
70
|
+
// "theme_controller.rb" the same way "theme_controller" would. A no-op
|
|
71
|
+
// (beyond lowercasing) for queries with no lower-to-upper transition, so
|
|
72
|
+
// non-CamelCase ranking is unchanged.
|
|
73
|
+
function toSnakeCase(q) {
|
|
74
|
+
return (q || '').replace(/([a-z0-9])([A-Z])/g, '$1_$2').toLowerCase();
|
|
75
|
+
}
|
|
76
|
+
|
|
68
77
|
function getMatchRelevance(result, q) {
|
|
69
78
|
if (!q) return FUZZY_ONLY;
|
|
70
79
|
var name = (result.name || (result.path || '').split('/').pop() || '').toLowerCase();
|
|
71
|
-
var lq = q
|
|
80
|
+
var lq = toSnakeCase(q);
|
|
72
81
|
if (name === lq) return 0;
|
|
73
82
|
if (name.slice(0, lq.length) === lq) return 1;
|
|
74
83
|
if (name.indexOf(lq) >= 0) return 2;
|
|
@@ -194,7 +203,17 @@ var QuickOpenDialog = function QuickOpenDialog(_ref) {
|
|
|
194
203
|
// path in the workspace ran on every keystroke, between the keypress and
|
|
195
204
|
// the character appearing.
|
|
196
205
|
var timer = setTimeout(function () {
|
|
197
|
-
var
|
|
206
|
+
var hits = SearchService.searchFiles(query);
|
|
207
|
+
// CamelCase query, e.g. "ThemeController": also search its snake_case
|
|
208
|
+
// form so it finds theme_controller.rb, whose index terms are snake_case.
|
|
209
|
+
var snake = toSnakeCase(query);
|
|
210
|
+
if (snake !== query.toLowerCase()) {
|
|
211
|
+
var seen = new Set(hits.map(function (r) { return r.path; }));
|
|
212
|
+
SearchService.searchFiles(snake).forEach(function (r) {
|
|
213
|
+
if (!seen.has(r.path)) { seen.add(r.path); hits.push(r); }
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
var filtered = rankResults(hits, query, showFolders);
|
|
198
217
|
setResults(filtered.slice(0, 200));
|
|
199
218
|
setSelectedIndex(0);
|
|
200
219
|
}, 120);
|
|
@@ -291,7 +310,7 @@ var QuickOpenDialog = function QuickOpenDialog(_ref) {
|
|
|
291
310
|
return React.createElement(
|
|
292
311
|
'div',
|
|
293
312
|
{ className: 'quick-open-section' },
|
|
294
|
-
React.createElement('div', { className: 'quick-open-section-header' },
|
|
313
|
+
React.createElement('div', { className: 'quick-open-section-header quick-open-section-header-muted' },
|
|
295
314
|
React.createElement('i', { className: 'fas fa-history', style: { marginRight: '6px', fontSize: '10px' } }),
|
|
296
315
|
'Recent Searches'
|
|
297
316
|
),
|
|
@@ -303,6 +322,8 @@ var QuickOpenDialog = function QuickOpenDialog(_ref) {
|
|
|
303
322
|
var recentFiles = (typeof TabManager !== 'undefined' && TabManager.getRecentFiles)
|
|
304
323
|
? TabManager.getRecentFiles() : [];
|
|
305
324
|
if (recentFiles.length === 0) return null;
|
|
325
|
+
// No section header here (unlike Favourites/Recent Searches) — VS Code
|
|
326
|
+
// marks each row inline with a trailing "recently opened" label instead.
|
|
306
327
|
var rows = recentFiles.map(function (entry) {
|
|
307
328
|
var path = entry.path;
|
|
308
329
|
var name = entry.name || (path.split('/').pop());
|
|
@@ -320,16 +341,13 @@ var QuickOpenDialog = function QuickOpenDialog(_ref) {
|
|
|
320
341
|
React.createElement('div', { className: 'quick-open-result-name' }, name),
|
|
321
342
|
React.createElement('div', { className: 'quick-open-result-path' }, path)
|
|
322
343
|
),
|
|
344
|
+
React.createElement('span', { className: 'quick-open-recent-label' }, 'recently opened'),
|
|
323
345
|
renderStarBtn(path)
|
|
324
346
|
);
|
|
325
347
|
});
|
|
326
348
|
return React.createElement(
|
|
327
349
|
'div',
|
|
328
350
|
{ className: 'quick-open-section' },
|
|
329
|
-
React.createElement('div', { className: 'quick-open-section-header' },
|
|
330
|
-
React.createElement('i', { className: 'fas fa-clock', style: { marginRight: '6px', fontSize: '10px' } }),
|
|
331
|
-
'Recently Opened'
|
|
332
|
-
),
|
|
333
351
|
rows
|
|
334
352
|
);
|
|
335
353
|
}
|
|
@@ -349,7 +367,7 @@ var QuickOpenDialog = function QuickOpenDialog(_ref) {
|
|
|
349
367
|
ref: inputRef,
|
|
350
368
|
type: 'text',
|
|
351
369
|
className: 'quick-open-input',
|
|
352
|
-
placeholder: 'Search files by name
|
|
370
|
+
placeholder: 'Search files by name',
|
|
353
371
|
value: query,
|
|
354
372
|
onChange: function (e) { setQuery(e.target.value); },
|
|
355
373
|
onKeyDown: handleKeyDown
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// SettingsModal — the editor preferences screen, as a VS Code-style modal.
|
|
4
|
+
// Owns the row catalogue (SETTINGS_ROWS) and the row renderer; MbeditorApp only
|
|
5
|
+
// mounts it and hands over the prefs pair. Dismissable with ×, the backdrop, or
|
|
6
|
+
// Escape.
|
|
7
|
+
|
|
8
|
+
// Settings rows, in the order they render. `{ header: ... }` entries start a
|
|
9
|
+
// section; everything else is a row descriptor consumed by renderSettingsRow.
|
|
10
|
+
// Checkbox default: plain `!!value` unless `def: true` (default checked,
|
|
11
|
+
// `value !== false`) or `strict: true` (`value === true`, used only where the
|
|
12
|
+
// on-state must be exact). Number default: `value || def` unless `nullish: true`
|
|
13
|
+
// (`value != null ? value : def`, needed so 0 is a valid stored value).
|
|
14
|
+
var SETTINGS_ROWS = [
|
|
15
|
+
{ header: 'Appearance' },
|
|
16
|
+
{ key: 'theme', type: 'select', label: 'Theme', title: 'Color theme for the editor', def: 'vs-dark', options: [
|
|
17
|
+
['vs-dark', 'Dark'], ['vs', 'Light'], ['hc-black', 'HC Dark'], ['hc-light', 'HC Light'],
|
|
18
|
+
['dracula', 'Dracula'], ['night-owl', 'Night Owl'], ['monokai', 'Monokai'], ['nord', 'Nord'],
|
|
19
|
+
['github-dark', 'GitHub Dark'], ['tomorrow-night', 'Tomorrow Night'], ['github-light', 'GitHub Light']
|
|
20
|
+
] },
|
|
21
|
+
{ key: 'glass', type: 'checkbox', label: 'Liquid Glass chrome', title: 'Translucent, blurred panels layered over the current theme. Monaco stays solid' },
|
|
22
|
+
{ key: 'fontSize', type: 'number', label: 'Font size', title: 'Editor font size in pixels (8–32)', min: 8, max: 32, step: 1, def: 13 },
|
|
23
|
+
{ key: 'fontFamily', type: 'text', label: 'Font family', title: 'Font stack used in the editor — the first font available on your system is used', def: "'JetBrains Mono', 'Fira Code', Consolas, 'Courier New', monospace" },
|
|
24
|
+
{ key: 'lineHeight', type: 'number', label: 'Line height (0=auto)', title: 'Row height in pixels. 0 = auto (roughly font size × 1.5)', min: 0, max: 100, step: 1, def: 0, nullish: true },
|
|
25
|
+
{ key: 'letterSpacing', type: 'number', label: 'Letter spacing (px)', title: 'Extra space between characters in pixels. 0 = default', min: -5, max: 20, step: 0.5, parse: 'float', def: 0, nullish: true },
|
|
26
|
+
|
|
27
|
+
{ header: 'Indentation' },
|
|
28
|
+
{ key: 'tabSize', type: 'number', label: 'Tab size', title: 'Number of spaces per indentation level (also sets Prettier tab width)', min: 1, max: 8, step: 1, def: 4 },
|
|
29
|
+
{ key: 'insertSpaces', type: 'checkbox', label: 'Use spaces', title: 'Insert spaces instead of tab characters when pressing Tab' },
|
|
30
|
+
|
|
31
|
+
{ header: 'Editor' },
|
|
32
|
+
{ key: 'wordWrap', type: 'select', label: 'Word wrap', title: 'How long lines are handled — Off: scroll horizontally, On: wrap at viewport width, Column: wrap at a fixed column', def: 'off', options: [
|
|
33
|
+
['off', 'Off'], ['on', 'On'], ['wordWrapColumn', 'Column']
|
|
34
|
+
] },
|
|
35
|
+
{ key: 'lineNumbers', type: 'select', label: 'Line numbers', title: 'Show line numbers in the gutter — On, Off, or Relative (useful with Vim mode)', def: 'on', options: [
|
|
36
|
+
['on', 'On'], ['off', 'Off'], ['relative', 'Relative']
|
|
37
|
+
] },
|
|
38
|
+
{ key: 'renderWhitespace', type: 'select', label: 'Whitespace', title: 'Render whitespace characters visually — None, Selection only, Boundary (leading/trailing), or All', def: 'none', options: [
|
|
39
|
+
['none', 'None'], ['selection', 'Selection'], ['boundary', 'Boundary'], ['all', 'All']
|
|
40
|
+
] },
|
|
41
|
+
{ key: 'minimap', type: 'checkbox', label: 'Minimap', title: 'Show a scaled-down overview of the file on the right edge of the editor' },
|
|
42
|
+
{ key: 'scrollBeyondLastLine', type: 'checkbox', label: 'Scroll past end', title: 'Allow scrolling past the last line so it can be positioned at the top of the viewport' },
|
|
43
|
+
{ key: 'bracketPairColorization', type: 'checkbox', label: 'Bracket colors', title: 'Colorize matching bracket pairs with distinct colors to make nesting easier to read' },
|
|
44
|
+
{ key: 'vimMode', type: 'checkbox', label: 'Vim mode', title: 'Enable Vim keybindings (Normal/Insert/Visual modes). Press Escape to return to Normal mode.' },
|
|
45
|
+
{ key: 'autoClosingBrackets', type: 'select', label: 'Auto-close brackets', title: 'When to insert a matching closing bracket automatically', def: 'always', options: [
|
|
46
|
+
['always', 'Always'], ['languageDefined', 'Per language rules'], ['beforeWhitespace', 'Only before whitespace'], ['never', 'Never']
|
|
47
|
+
] },
|
|
48
|
+
{ key: 'autoClosingQuotes', type: 'select', label: 'Auto-close quotes', title: 'When to insert a matching closing quote automatically', def: 'always', options: [
|
|
49
|
+
['always', 'Always'], ['languageDefined', 'Per language rules'], ['beforeWhitespace', 'Only before whitespace'], ['never', 'Never']
|
|
50
|
+
] },
|
|
51
|
+
{ key: 'renderLineHighlight', type: 'select', label: 'Line highlight', title: 'What to highlight on the current editor line', def: 'none', options: [
|
|
52
|
+
['none', 'None'], ['gutter', 'Line number only'], ['line', 'Current line background'], ['all', 'Line number + background']
|
|
53
|
+
] },
|
|
54
|
+
{ key: 'cursorStyle', type: 'select', label: 'Cursor style', title: 'Shape of the text cursor in the editor', def: 'line', options: [
|
|
55
|
+
['line', 'Line (|)'], ['block', 'Block (filled)'], ['underline', 'Underline (_)'],
|
|
56
|
+
['line-thin', 'Line thin'], ['block-outline', 'Block outline'], ['underline-thin', 'Underline thin']
|
|
57
|
+
] },
|
|
58
|
+
{ key: 'cursorBlinking', type: 'select', label: 'Cursor blinking', title: 'Cursor animation style — Blink (on/off), Smooth (fade), Phase (offset fade), Expand (grow), or Solid (no animation)', def: 'blink', options: [
|
|
59
|
+
['blink', 'Blink (on/off)'], ['smooth', 'Smooth (fade)'], ['phase', 'Phase (offset fade)'],
|
|
60
|
+
['expand', 'Expand (grow/shrink)'], ['solid', 'Solid (no blink)']
|
|
61
|
+
] },
|
|
62
|
+
{ key: 'folding', type: 'checkbox', label: 'Code folding', title: 'Show collapse arrows next to foldable regions (functions, classes, blocks)', def: true },
|
|
63
|
+
{ key: 'smoothScrolling', type: 'checkbox', label: 'Smooth scrolling', title: 'Animate scrolling instead of jumping instantly' },
|
|
64
|
+
{ key: 'mouseWheelZoom', type: 'checkbox', label: 'Ctrl+scroll to zoom', title: 'Hold Ctrl (or Cmd) and scroll the mouse wheel to zoom the font size' },
|
|
65
|
+
|
|
66
|
+
{ header: 'Behaviour' },
|
|
67
|
+
{ key: 'autoIndent', type: 'select', label: 'Auto indent', title: 'How aggressively the editor re-indents lines as you type', def: 'full', options: [
|
|
68
|
+
['none', 'None (disabled)'], ['keep', 'Keep current level'], ['brackets', 'Indent on { and ['],
|
|
69
|
+
['advanced', 'Language indent rules'], ['full', 'Full (language grammar)']
|
|
70
|
+
] },
|
|
71
|
+
{ key: 'acceptSuggestionOnEnter', type: 'select', label: 'Accept suggestion on Enter', title: 'Whether pressing Enter accepts the highlighted autocomplete suggestion', def: 'on', options: [
|
|
72
|
+
['on', 'Always'], ['smart', 'Only when navigated (↑↓)'], ['off', 'Never (Tab only)']
|
|
73
|
+
] },
|
|
74
|
+
{ key: 'wordBasedSuggestions', type: 'select', label: 'Word-based suggestions', title: 'Suggest completions based on words already present in open files', def: 'matchingDocuments', options: [
|
|
75
|
+
['off', 'Off'], ['currentDocument', 'Current file only'], ['matchingDocuments', 'Same language files'], ['allDocuments', 'All open files']
|
|
76
|
+
] },
|
|
77
|
+
{ key: 'formatOnType', type: 'checkbox', label: 'Format on type', title: 'Re-indent and auto-close blocks as you type (e.g. after pressing Enter inside {})', strict: true },
|
|
78
|
+
{ key: 'formatOnSave', type: 'checkbox', label: 'Format on save', title: 'Format the file before every save — RuboCop -A for Ruby, Prettier for JS/JSX/CSS/HTML/Markdown', strict: true },
|
|
79
|
+
{ key: 'quickSuggestions', type: 'checkbox', label: 'Quick suggestions', title: 'Show autocomplete suggestions while typing (not just on trigger characters like .)', def: true },
|
|
80
|
+
|
|
81
|
+
{ header: 'Formatting' },
|
|
82
|
+
{ key: 'prettierPrintWidth', type: 'number', label: 'Print width', title: 'Prettier: maximum line length before wrapping (40–200)', min: 40, max: 200, step: 1, def: 80, nullish: true },
|
|
83
|
+
{ key: 'prettierTrailingComma', type: 'select', label: 'Trailing commas', title: 'Prettier: add trailing commas in multi-line expressions — All (ES2017+), ES5 (objects/arrays only), or None', def: 'all', options: [
|
|
84
|
+
['all', 'All'], ['es5', 'ES5'], ['none', 'None']
|
|
85
|
+
] },
|
|
86
|
+
{ key: 'prettierSemi', type: 'checkbox', label: 'Semicolons', title: 'Prettier: add semicolons at the end of statements', def: true },
|
|
87
|
+
{ key: 'prettierSingleQuote', type: 'checkbox', label: 'Single quotes', title: 'Prettier: use single quotes instead of double quotes for strings' },
|
|
88
|
+
{ key: 'prettierBracketSpacing', type: 'checkbox', label: 'Bracket spacing', title: 'Prettier: add spaces inside object literal braces, e.g. { a: 1 } vs {a: 1}', def: true },
|
|
89
|
+
|
|
90
|
+
{ header: 'Interface' },
|
|
91
|
+
{ key: 'autoRevealInExplorer', type: 'checkbox', label: 'Explorer follows active file', title: 'Automatically scroll the file explorer to reveal and highlight the file you are editing' },
|
|
92
|
+
{ key: 'fileTreeTypeahead', type: 'checkbox', label: 'Explorer type-ahead', title: 'Jump to a file in the explorer by typing its name when the sidebar is focused', def: true },
|
|
93
|
+
{ key: 'showDotFiles', type: 'checkbox', label: 'Show dotfiles', title: 'Show hidden files and directories (those starting with a dot, e.g. .env, .gitignore) in the file explorer' },
|
|
94
|
+
{ key: 'tabDisplayMode', type: 'select', label: 'Tab bar layout', title: 'Scroll: tabs overflow horizontally with a scrollbar; Wrap: tabs flow onto multiple rows', def: 'scroll', options: [
|
|
95
|
+
['scroll', 'Scroll'], ['wrap', 'Wrap (multi-row)']
|
|
96
|
+
] },
|
|
97
|
+
{ key: 'quickOpenShowFolders', type: 'checkbox', label: 'Quick Open: show folders', title: 'Include folder names in the Quick Open picker (Ctrl+P / Cmd+P) results, not just files' },
|
|
98
|
+
// The stored preference, not the derived value: at a narrow width labels are
|
|
99
|
+
// dropped regardless, and the box must not appear to do nothing.
|
|
100
|
+
{ key: 'toolbarLabels', type: 'checkbox', label: 'Toolbar: show labels', title: 'Show a text label beside each toolbar icon. Every button already names itself on hover' },
|
|
101
|
+
{ key: 'persistFindState', type: 'checkbox', label: 'Persist find state across files', title: 'Keep the search/replace text when switching between files in the editor', def: true },
|
|
102
|
+
{ key: 'branchStateRestore', type: 'checkbox', label: 'Restore tabs on branch switch', title: 'Save which files are open per branch and restore them when switching branches. Disable to always start with a clean slate when switching.', def: true },
|
|
103
|
+
{ key: 'routeHints', type: 'checkbox', label: 'Controller route hints', title: 'Show the verb and path that route to each controller action after its def line, and mark public actions nothing routes to', def: true },
|
|
104
|
+
|
|
105
|
+
{ header: 'RuboCop' },
|
|
106
|
+
{ key: 'rubocopLintEnabled', type: 'checkbox', label: 'Enable RuboCop linting', title: 'Run RuboCop in the background and show lint warnings/errors as markers in the editor gutter', def: true },
|
|
107
|
+
|
|
108
|
+
{ header: 'Diagnostics' },
|
|
109
|
+
{ key: 'auditLog', type: 'checkbox', label: 'Record audit log', title: 'Record a numbers-only trace of editor activity you can download and hand to an AI to analyse. It never contains code, file names, URLs or host paths.', def: true },
|
|
110
|
+
// Lazy references: the handlers are declared in MbeditorApp.js, which loads
|
|
111
|
+
// after this file in the shared IIFE.
|
|
112
|
+
{ key: 'auditLogDownload', type: 'button', label: 'Download log', action: 'Download', title: 'Save the recorded trace as a JSON file to hand to an AI. It carries numbers only — no code, file names, URLs or host paths', onClick: function () { downloadAuditLog(); } },
|
|
113
|
+
{ key: 'auditLogClear', type: 'button', label: 'Clear log', action: 'Clear', title: 'Discard everything recorded so far, so the next download is a clean trace', onClick: function () { clearAuditLog(); } }
|
|
114
|
+
];
|
|
115
|
+
|
|
116
|
+
function setEditorPref(setEditorPrefs, key, value) {
|
|
117
|
+
setEditorPrefs(function(p) {
|
|
118
|
+
var next = Object.assign({}, p);
|
|
119
|
+
next[key] = value;
|
|
120
|
+
return next;
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Label + description line, then the control. The checkbox is the exception:
|
|
125
|
+
// it sits inline before the label, with the description under both.
|
|
126
|
+
function settingsRowText(desc) {
|
|
127
|
+
return [
|
|
128
|
+
React.createElement('span', { className: 'ide-settings-label', key: 'l' }, desc.label),
|
|
129
|
+
desc.title ? React.createElement('span', { className: 'ide-settings-desc', key: 'd' }, desc.title) : null
|
|
130
|
+
];
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function renderSettingsRow(desc, editorPrefs, setEditorPrefs) {
|
|
134
|
+
var raw = editorPrefs[desc.key];
|
|
135
|
+
function set(v) { setEditorPref(setEditorPrefs, desc.key, v); }
|
|
136
|
+
|
|
137
|
+
if (desc.type === 'checkbox') {
|
|
138
|
+
var checked = desc.strict ? raw === true : (desc.def === true ? raw !== false : !!raw);
|
|
139
|
+
return React.createElement(
|
|
140
|
+
'label', { className: 'ide-settings-row ide-settings-row-check', key: desc.key },
|
|
141
|
+
React.createElement('input', {
|
|
142
|
+
type: 'checkbox',
|
|
143
|
+
className: 'ide-settings-checkbox',
|
|
144
|
+
checked: checked,
|
|
145
|
+
onChange: function(e) { set(e.target.checked); }
|
|
146
|
+
}),
|
|
147
|
+
settingsRowText(desc)
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (desc.type === 'button') {
|
|
152
|
+
return React.createElement(
|
|
153
|
+
'div', { className: 'ide-settings-row', key: desc.key },
|
|
154
|
+
settingsRowText(desc),
|
|
155
|
+
React.createElement('button', {
|
|
156
|
+
type: 'button', className: 'ide-settings-reset-btn', onClick: desc.onClick
|
|
157
|
+
}, desc.action)
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
var control = null;
|
|
162
|
+
if (desc.type === 'select') {
|
|
163
|
+
control = React.createElement(
|
|
164
|
+
'select', {
|
|
165
|
+
className: 'ide-settings-input',
|
|
166
|
+
value: raw || desc.def,
|
|
167
|
+
onChange: function(e) { set(e.target.value); }
|
|
168
|
+
},
|
|
169
|
+
desc.options.map(function(opt) {
|
|
170
|
+
return React.createElement('option', { value: opt[0], key: opt[0] }, opt[1]);
|
|
171
|
+
})
|
|
172
|
+
);
|
|
173
|
+
} else if (desc.type === 'number') {
|
|
174
|
+
var val = desc.nullish ? (raw != null ? raw : desc.def) : (raw || desc.def);
|
|
175
|
+
var parse = function(e) { return desc.parse === 'float' ? parseFloat(e.target.value) : parseInt(e.target.value, 10); };
|
|
176
|
+
control = React.createElement('input', {
|
|
177
|
+
key: String(val),
|
|
178
|
+
type: 'number', min: String(desc.min), max: String(desc.max), step: String(desc.step),
|
|
179
|
+
className: 'ide-settings-input ide-settings-input-number',
|
|
180
|
+
defaultValue: val,
|
|
181
|
+
onChange: function(e) {
|
|
182
|
+
var v = parse(e);
|
|
183
|
+
if (!isNaN(v) && v >= desc.min && v <= desc.max) set(v);
|
|
184
|
+
},
|
|
185
|
+
onBlur: function(e) {
|
|
186
|
+
var v = parse(e);
|
|
187
|
+
if (isNaN(v) || v < desc.min || v > desc.max) e.target.value = String(val);
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
} else if (desc.type === 'text') {
|
|
191
|
+
control = React.createElement('input', {
|
|
192
|
+
type: 'text',
|
|
193
|
+
className: 'ide-settings-input ide-settings-input-wide',
|
|
194
|
+
value: raw || desc.def,
|
|
195
|
+
onChange: function(e) { set(e.target.value); }
|
|
196
|
+
});
|
|
197
|
+
} else {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return React.createElement(
|
|
202
|
+
'label', { className: 'ide-settings-row', key: desc.key },
|
|
203
|
+
settingsRowText(desc), control
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Flat SETTINGS_ROWS → [{ name, rows }], split on the `{ header }` entries.
|
|
208
|
+
function settingsSections(rows) {
|
|
209
|
+
var out = [];
|
|
210
|
+
var cur = null;
|
|
211
|
+
rows.forEach(function(d) {
|
|
212
|
+
if (d.header) { cur = { name: d.header, rows: [] }; out.push(cur); }
|
|
213
|
+
else if (cur) cur.rows.push(d);
|
|
214
|
+
});
|
|
215
|
+
return out;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
var SETTINGS_SECTIONS = settingsSections(SETTINGS_ROWS);
|
|
219
|
+
|
|
220
|
+
var SettingsModal = function SettingsModal(props) {
|
|
221
|
+
var _React = React;
|
|
222
|
+
var useState = _React.useState;
|
|
223
|
+
var useEffect = _React.useEffect;
|
|
224
|
+
var useRef = _React.useRef;
|
|
225
|
+
|
|
226
|
+
var onClose = props.onClose;
|
|
227
|
+
var editorPrefs = props.editorPrefs;
|
|
228
|
+
var setEditorPrefs = props.setEditorPrefs;
|
|
229
|
+
|
|
230
|
+
var _q = useState('');
|
|
231
|
+
var query = _q[0];
|
|
232
|
+
var setQuery = _q[1];
|
|
233
|
+
|
|
234
|
+
var sections = SETTINGS_SECTIONS;
|
|
235
|
+
var _as = useState(sections[0] ? sections[0].name : null);
|
|
236
|
+
var activeSection = _as[0];
|
|
237
|
+
var setActiveSection = _as[1];
|
|
238
|
+
|
|
239
|
+
var sectionRefs = useRef({});
|
|
240
|
+
|
|
241
|
+
useEffect(function() {
|
|
242
|
+
function onKeyDown(e) { if (e.key === 'Escape') onClose(); }
|
|
243
|
+
window.addEventListener('keydown', onKeyDown);
|
|
244
|
+
return function() { window.removeEventListener('keydown', onKeyDown); };
|
|
245
|
+
}, [onClose]);
|
|
246
|
+
|
|
247
|
+
var q = query.trim().toLowerCase();
|
|
248
|
+
var visible = sections.map(function(s) {
|
|
249
|
+
if (!q) return s;
|
|
250
|
+
return {
|
|
251
|
+
name: s.name,
|
|
252
|
+
rows: s.rows.filter(function(d) {
|
|
253
|
+
return (d.label || '').toLowerCase().indexOf(q) !== -1 ||
|
|
254
|
+
(d.title || '').toLowerCase().indexOf(q) !== -1;
|
|
255
|
+
})
|
|
256
|
+
};
|
|
257
|
+
}).filter(function(s) { return s.rows.length > 0; });
|
|
258
|
+
|
|
259
|
+
return React.createElement(
|
|
260
|
+
'div',
|
|
261
|
+
{ className: 'ide-settings-overlay', onClick: onClose },
|
|
262
|
+
React.createElement(
|
|
263
|
+
'div',
|
|
264
|
+
{ className: 'ide-settings-modal', onClick: function(e) { e.stopPropagation(); } },
|
|
265
|
+
React.createElement(
|
|
266
|
+
'div', { className: 'ide-settings-modal-header' },
|
|
267
|
+
React.createElement('span', { className: 'ide-settings-modal-title' }, 'Settings'),
|
|
268
|
+
React.createElement('input', {
|
|
269
|
+
type: 'search',
|
|
270
|
+
className: 'ide-settings-search',
|
|
271
|
+
placeholder: 'Search settings',
|
|
272
|
+
autoFocus: true,
|
|
273
|
+
value: query,
|
|
274
|
+
onChange: function(e) { setQuery(e.target.value); }
|
|
275
|
+
}),
|
|
276
|
+
React.createElement('button', {
|
|
277
|
+
type: 'button', className: 'ide-settings-modal-close', title: 'Close', 'aria-label': 'Close settings', onClick: onClose
|
|
278
|
+
}, React.createElement('i', { className: 'fas fa-times', 'aria-hidden': 'true' }))
|
|
279
|
+
),
|
|
280
|
+
React.createElement(
|
|
281
|
+
'div', { className: 'ide-settings-modal-body' },
|
|
282
|
+
React.createElement(
|
|
283
|
+
'nav', { className: 'ide-settings-nav' },
|
|
284
|
+
visible.map(function(s) {
|
|
285
|
+
return React.createElement('button', {
|
|
286
|
+
type: 'button',
|
|
287
|
+
key: s.name,
|
|
288
|
+
className: 'ide-settings-nav-item' + (s.name === activeSection ? ' active' : ''),
|
|
289
|
+
onClick: function() {
|
|
290
|
+
setActiveSection(s.name);
|
|
291
|
+
var el = sectionRefs.current[s.name];
|
|
292
|
+
if (el && el.scrollIntoView) el.scrollIntoView({ block: 'start' });
|
|
293
|
+
}
|
|
294
|
+
}, s.name);
|
|
295
|
+
})
|
|
296
|
+
),
|
|
297
|
+
React.createElement(
|
|
298
|
+
'div', { className: 'ide-settings-body' },
|
|
299
|
+
visible.map(function(s) {
|
|
300
|
+
return React.createElement(
|
|
301
|
+
'section', { className: 'ide-settings-section', key: s.name,
|
|
302
|
+
ref: function(el) { sectionRefs.current[s.name] = el; } },
|
|
303
|
+
React.createElement('h3', { className: 'ide-settings-section-header' }, s.name),
|
|
304
|
+
s.rows.map(function(d) { return renderSettingsRow(d, editorPrefs, setEditorPrefs); }),
|
|
305
|
+
s.name === 'RuboCop' && props.rubocopAvailable && props.rubocopConfigPath
|
|
306
|
+
? React.createElement(
|
|
307
|
+
'div', { className: 'ide-settings-row' },
|
|
308
|
+
React.createElement('span', { className: 'ide-settings-label' }, 'Config file'),
|
|
309
|
+
React.createElement(
|
|
310
|
+
'button', {
|
|
311
|
+
type: 'button',
|
|
312
|
+
className: 'ide-settings-config-link',
|
|
313
|
+
title: 'Open ' + props.rubocopConfigPath,
|
|
314
|
+
onClick: function() { props.onOpenRubocopConfig(); onClose(); }
|
|
315
|
+
},
|
|
316
|
+
React.createElement('i', { className: 'fas fa-file-alt', style: { marginRight: 5 } }),
|
|
317
|
+
props.rubocopConfigPath
|
|
318
|
+
)
|
|
319
|
+
)
|
|
320
|
+
: null
|
|
321
|
+
);
|
|
322
|
+
}),
|
|
323
|
+
visible.length === 0
|
|
324
|
+
? React.createElement('div', { className: 'ide-settings-empty' }, 'No settings match "' + query + '".')
|
|
325
|
+
: null,
|
|
326
|
+
React.createElement(
|
|
327
|
+
'button', {
|
|
328
|
+
className: 'ide-settings-reset-btn',
|
|
329
|
+
type: 'button',
|
|
330
|
+
title: 'Restore every editor preference on this page to its default',
|
|
331
|
+
onClick: props.onReset
|
|
332
|
+
},
|
|
333
|
+
React.createElement('i', { className: 'fas fa-undo', style: { marginRight: 6 } }),
|
|
334
|
+
'Reset to defaults'
|
|
335
|
+
)
|
|
336
|
+
)
|
|
337
|
+
)
|
|
338
|
+
)
|
|
339
|
+
);
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
window.SettingsModal = SettingsModal;
|
|
@@ -102,7 +102,8 @@ var ShortcutHelp = function ShortcutHelp(_ref) {
|
|
|
102
102
|
'tbody',
|
|
103
103
|
null,
|
|
104
104
|
React.createElement(Row, { keys: 'Ctrl+P', desc: 'Quick-open any file by name' }),
|
|
105
|
-
React.createElement(Row, { keys: 'PgUp
|
|
105
|
+
React.createElement(Row, { keys: 'PgUp', desc: 'Navigate back through cursor history (across files)' }),
|
|
106
|
+
React.createElement(Row, { keys: 'PgDn', desc: 'Navigate forward through cursor history' }),
|
|
106
107
|
React.createElement(Row, { keys: 'Ctrl+S', desc: 'Save the active file' }),
|
|
107
108
|
React.createElement(Row, { keys: 'Ctrl+Shift+G', desc: 'Toggle git panel' }),
|
|
108
109
|
React.createElement(Row, { keys: 'Ctrl+Shift+L', desc: 'Toggle Rails log panel' }),
|
|
@@ -181,6 +182,22 @@ var ShortcutHelp = function ShortcutHelp(_ref) {
|
|
|
181
182
|
]
|
|
182
183
|
}
|
|
183
184
|
)
|
|
185
|
+
),
|
|
186
|
+
|
|
187
|
+
React.createElement(
|
|
188
|
+
Section,
|
|
189
|
+
{ title: 'Sharing code with a chat assistant' },
|
|
190
|
+
React.createElement(
|
|
191
|
+
Tips,
|
|
192
|
+
{
|
|
193
|
+
items: [
|
|
194
|
+
'Combined View: Ctrl/Cmd-click several files or folders, right-click, "Open in Combined View" — every file on one page with Copy and Download',
|
|
195
|
+
'Pick methods: in Combined View, tick methods under a Ruby file to send only those; "Download .md" / "Copy .md" bundles everything into one attachment',
|
|
196
|
+
'Paste & Place: right-click inside a Ruby file (or F1) → "Paste & Place Ruby Method...", paste the reply, "Find placement", check the diff, "Apply to editor" — then save; Ctrl+Z undoes',
|
|
197
|
+
'Upload compare: drop or upload a file over an existing one and click "Compare" to see the diff before choosing Skip / Keep both / Overwrite'
|
|
198
|
+
]
|
|
199
|
+
}
|
|
200
|
+
)
|
|
184
201
|
)
|
|
185
202
|
)
|
|
186
203
|
)
|