mbeditor 0.14.0 → 0.14.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +64 -0
- data/app/assets/javascripts/mbeditor/application.js +2 -0
- data/app/assets/javascripts/mbeditor/components/CombinedFileView.js +228 -0
- data/app/assets/javascripts/mbeditor/components/DiffViewer.js +2 -1
- data/app/assets/javascripts/mbeditor/components/EditorPanel.js +18 -1
- data/app/assets/javascripts/mbeditor/components/ImportConflictModal.js +60 -5
- data/app/assets/javascripts/mbeditor/components/MbeditorApp.js +98 -9
- data/app/assets/javascripts/mbeditor/components/PastePlaceModal.js +329 -0
- data/app/assets/javascripts/mbeditor/components/ShortcutHelp.js +16 -0
- data/app/assets/javascripts/mbeditor/editor_plugins.js +20 -20
- data/app/assets/javascripts/mbeditor/file_service.js +10 -0
- data/app/assets/javascripts/mbeditor/tab_manager.js +3 -0
- data/app/assets/stylesheets/mbeditor/editor.css +33 -1
- data/app/assets/stylesheets/mbeditor/glass.css +11 -0
- data/app/assets/stylesheets/mbeditor/themes.css +3 -3
- data/app/controllers/mbeditor/editors_controller.rb +7 -0
- data/app/services/mbeditor/ruby_method_ranges_service.rb +120 -0
- data/lib/mbeditor/rack/pending_migration_bypass.rb +16 -1
- data/lib/mbeditor/route_map.rb +1 -0
- data/lib/mbeditor/version.rb +1 -1
- 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 +5 -2
|
@@ -145,7 +145,7 @@
|
|
|
145
145
|
var _discoveredFlushTimer = null;
|
|
146
146
|
function flushDiscoveredGlobals() {
|
|
147
147
|
_discoveredFlushTimer = null;
|
|
148
|
-
var mts = window.monaco && window.monaco.
|
|
148
|
+
var mts = window.monaco && window.monaco.typescript;
|
|
149
149
|
if (!mts || !mts.javascriptDefaults) return;
|
|
150
150
|
var decls = Object.keys(discoveredJsGlobals)
|
|
151
151
|
.map(function(k) { return 'declare var ' + k + ': any;'; }).join('\n');
|
|
@@ -263,7 +263,7 @@
|
|
|
263
263
|
entry.lib.dispose();
|
|
264
264
|
entry.lib = null;
|
|
265
265
|
} else if (!open && !entry.lib) {
|
|
266
|
-
entry.lib = monaco.
|
|
266
|
+
entry.lib = monaco.typescript.javascriptDefaults
|
|
267
267
|
.addExtraLib(entry.content, programUri(path));
|
|
268
268
|
}
|
|
269
269
|
}
|
|
@@ -278,7 +278,7 @@
|
|
|
278
278
|
|
|
279
279
|
function loadWorkspaceProgram(monaco) {
|
|
280
280
|
if (typeof FileService === 'undefined') return;
|
|
281
|
-
var mts = monaco && monaco.
|
|
281
|
+
var mts = monaco && monaco.typescript;
|
|
282
282
|
if (!mts || !mts.javascriptDefaults) return;
|
|
283
283
|
|
|
284
284
|
var programLoaded = FileService.getJsProgram
|
|
@@ -301,7 +301,7 @@
|
|
|
301
301
|
|
|
302
302
|
function loadWorkspaceGlobals(monaco) {
|
|
303
303
|
if (typeof FileService === 'undefined' || !FileService.getJsGlobals) return;
|
|
304
|
-
var mts = monaco && monaco.
|
|
304
|
+
var mts = monaco && monaco.typescript;
|
|
305
305
|
if (!mts || !mts.javascriptDefaults) return;
|
|
306
306
|
FileService.getJsGlobals().then(function (data) {
|
|
307
307
|
if (!data || !data.ok || !data.symbols) return;
|
|
@@ -335,7 +335,7 @@
|
|
|
335
335
|
// tree. A workspace can be tens of MB, so re-fetching it on every save would
|
|
336
336
|
// cost more than the feature is worth.
|
|
337
337
|
function refreshProgramPaths(monaco, paths) {
|
|
338
|
-
var mts = monaco && monaco.
|
|
338
|
+
var mts = monaco && monaco.typescript;
|
|
339
339
|
if (!mts || !mts.javascriptDefaults) return;
|
|
340
340
|
if (typeof FileService === 'undefined' || !FileService.getJsProgramFile) return;
|
|
341
341
|
var wanted = (paths || []).filter(function (path) {
|
|
@@ -1253,8 +1253,8 @@
|
|
|
1253
1253
|
function registerJsProviders(monaco) {
|
|
1254
1254
|
// JavaScript: enable semantic checking (off by default in Monaco) and JSX support.
|
|
1255
1255
|
// checkJs catches undefined variables, noUnusedLocals catches dead assignments.
|
|
1256
|
-
if (monaco.
|
|
1257
|
-
monaco.
|
|
1256
|
+
if (monaco.typescript && monaco.typescript.javascriptDefaults) {
|
|
1257
|
+
monaco.typescript.javascriptDefaults.setDiagnosticsOptions({
|
|
1258
1258
|
noSemanticValidation: false,
|
|
1259
1259
|
noSyntaxValidation: false,
|
|
1260
1260
|
// Suggestion diagnostics are TypeScript's advice to a TypeScript
|
|
@@ -1271,12 +1271,12 @@
|
|
|
1271
1271
|
// which is a closed rule rather than a list that grows by one code
|
|
1272
1272
|
// every time a new false positive turns up.
|
|
1273
1273
|
});
|
|
1274
|
-
monaco.
|
|
1275
|
-
target: monaco.
|
|
1274
|
+
monaco.typescript.javascriptDefaults.setCompilerOptions({
|
|
1275
|
+
target: monaco.typescript.ScriptTarget.ES2020,
|
|
1276
1276
|
allowNonTsExtensions: true,
|
|
1277
1277
|
allowJs: true,
|
|
1278
1278
|
checkJs: true,
|
|
1279
|
-
jsx: monaco.
|
|
1279
|
+
jsx: monaco.typescript.JsxEmit.React,
|
|
1280
1280
|
noUnusedLocals: true
|
|
1281
1281
|
});
|
|
1282
1282
|
// Hand JS formatting to Prettier alone.
|
|
@@ -1289,8 +1289,8 @@
|
|
|
1289
1289
|
// two spaces and never adds semicolons, ignoring every preference. Only
|
|
1290
1290
|
// formatting is switched off — completions, hovers, diagnostics, rename
|
|
1291
1291
|
// and the rest of the JS intelligence still come from the worker.
|
|
1292
|
-
monaco.
|
|
1293
|
-
Object.assign({}, monaco.
|
|
1292
|
+
monaco.typescript.javascriptDefaults.setModeConfiguration(
|
|
1293
|
+
Object.assign({}, monaco.typescript.javascriptDefaults.modeConfiguration, {
|
|
1294
1294
|
documentRangeFormattingEdits: false,
|
|
1295
1295
|
onTypeFormattingEdits: false
|
|
1296
1296
|
})
|
|
@@ -1401,15 +1401,15 @@
|
|
|
1401
1401
|
// window globals from the host application. Common Sprockets globals
|
|
1402
1402
|
// (React, ReactDOM, etc.) are declared explicitly. For additional globals
|
|
1403
1403
|
// not auto-detected, add `/* global MyComponent */` at the top of the file.
|
|
1404
|
-
if (monaco.
|
|
1405
|
-
monaco.
|
|
1404
|
+
if (monaco.typescript && monaco.typescript.javascriptDefaults) {
|
|
1405
|
+
monaco.typescript.javascriptDefaults.addExtraLib(
|
|
1406
1406
|
REACT_MINI_UMD_DTS,
|
|
1407
1407
|
'inmemory://mbeditor/react-mini-umd.d.ts'
|
|
1408
1408
|
);
|
|
1409
1409
|
|
|
1410
1410
|
var dynamicShim = buildWindowGlobalsShim();
|
|
1411
1411
|
if (dynamicShim) {
|
|
1412
|
-
monaco.
|
|
1412
|
+
monaco.typescript.javascriptDefaults.addExtraLib(
|
|
1413
1413
|
dynamicShim,
|
|
1414
1414
|
'inmemory://mbeditor/window-globals.d.ts'
|
|
1415
1415
|
);
|
|
@@ -1725,11 +1725,11 @@
|
|
|
1725
1725
|
}
|
|
1726
1726
|
|
|
1727
1727
|
// TypeScript: enable JSX for .tsx files.
|
|
1728
|
-
if (monaco.
|
|
1729
|
-
monaco.
|
|
1730
|
-
target: monaco.
|
|
1728
|
+
if (monaco.typescript && monaco.typescript.typescriptDefaults) {
|
|
1729
|
+
monaco.typescript.typescriptDefaults.setCompilerOptions({
|
|
1730
|
+
target: monaco.typescript.ScriptTarget.ES2020,
|
|
1731
1731
|
allowNonTsExtensions: true,
|
|
1732
|
-
jsx: monaco.
|
|
1732
|
+
jsx: monaco.typescript.JsxEmit.React,
|
|
1733
1733
|
noUnusedLocals: true
|
|
1734
1734
|
});
|
|
1735
1735
|
}
|
|
@@ -1932,7 +1932,7 @@
|
|
|
1932
1932
|
function collectComponentProps(monaco, model, name) {
|
|
1933
1933
|
var sources = [model.getValue()];
|
|
1934
1934
|
try {
|
|
1935
|
-
var libs = monaco.
|
|
1935
|
+
var libs = monaco.typescript.javascriptDefaults.getExtraLibs();
|
|
1936
1936
|
Object.keys(libs).forEach(function(uri) {
|
|
1937
1937
|
if (libs[uri] && libs[uri].content) sources.push(libs[uri].content);
|
|
1938
1938
|
});
|
|
@@ -304,6 +304,15 @@ var FileService = (function () {
|
|
|
304
304
|
.then(function (res) { return res.data; });
|
|
305
305
|
}
|
|
306
306
|
|
|
307
|
+
// Method line ranges of Ruby source (code).
|
|
308
|
+
// Resolves { methods, owners } or { error } — a syntax error is data, not a throw.
|
|
309
|
+
function getRubyMethods(code) {
|
|
310
|
+
return axios.post(window.mbeditorBasePath() + '/ruby_methods', { code: code }).then(function (res) { return res.data; }, function (err) {
|
|
311
|
+
if (err && err.response && err.response.data && err.response.data.error && err.response.status === 422) return { error: err.response.data.error };
|
|
312
|
+
throw err;
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
|
|
307
316
|
function getState() {
|
|
308
317
|
return axios.get(window.mbeditorBasePath() + '/state').then(function(res) { return res.data; });
|
|
309
318
|
}
|
|
@@ -550,6 +559,7 @@ var FileService = (function () {
|
|
|
550
559
|
runTests: runTests,
|
|
551
560
|
ping: ping,
|
|
552
561
|
getRoutes: getRoutes,
|
|
562
|
+
getRubyMethods: getRubyMethods,
|
|
553
563
|
isServerReachable: ServerReachability.isOnline,
|
|
554
564
|
onReachabilityChange: ServerReachability.onChange,
|
|
555
565
|
getState: getState,
|
|
@@ -620,6 +620,9 @@ var TabManager = (function () {
|
|
|
620
620
|
var state = EditorStore.getState();
|
|
621
621
|
var pane = state.panes.find(function(p) { return p.id === paneId; });
|
|
622
622
|
var newActive = pane ? pane.activeTabId : null;
|
|
623
|
+
// Every click in a pane lands here; a no-op setState still re-renders the
|
|
624
|
+
// app, which mid-click resets controlled inputs (checkboxes) to their old value.
|
|
625
|
+
if (state.focusedPaneId === paneId && state.activeTabId === newActive) return;
|
|
623
626
|
EditorStore.setState({ focusedPaneId: paneId, activeTabId: newActive });
|
|
624
627
|
}
|
|
625
628
|
|
|
@@ -128,6 +128,10 @@ html, body, #mbeditor-root {
|
|
|
128
128
|
}
|
|
129
129
|
|
|
130
130
|
.ide-titlebar {
|
|
131
|
+
/* .statusbar-btn paints from --ide-accent-text, which is "text on the accent
|
|
132
|
+
fill" in the legacy themes. The title bar is panel-coloured in every theme,
|
|
133
|
+
so its buttons read the ordinary text colour instead. */
|
|
134
|
+
--ide-accent-text: var(--ide-text);
|
|
131
135
|
display: flex;
|
|
132
136
|
align-items: center;
|
|
133
137
|
background: var(--ide-panel-bg);
|
|
@@ -1961,7 +1965,7 @@ body.mbeditor-migration-pending .ide-statusbar {
|
|
|
1961
1965
|
.changelog-version-badge {
|
|
1962
1966
|
font-size: 15px;
|
|
1963
1967
|
font-weight: 700;
|
|
1964
|
-
color: var(--ide-accent-
|
|
1968
|
+
color: var(--ide-accent-fg);
|
|
1965
1969
|
letter-spacing: -0.01em;
|
|
1966
1970
|
}
|
|
1967
1971
|
.changelog-version-date {
|
|
@@ -2363,6 +2367,8 @@ body.mbeditor-migration-pending .ide-statusbar {
|
|
|
2363
2367
|
/* Nothing to click, so nothing that looks clickable. */
|
|
2364
2368
|
.collapsible-header-static { cursor: default; }
|
|
2365
2369
|
.collapsible-header-static:hover { background: none; }
|
|
2370
|
+
/* A sticky header still needs its own fill, or scrolled rows show through it. */
|
|
2371
|
+
.ide-sidebar-scrollable .collapsible-header-static:hover { background: var(--ide-panel-bg); }
|
|
2366
2372
|
|
|
2367
2373
|
|
|
2368
2374
|
.collapsible-toggle {
|
|
@@ -4135,3 +4141,29 @@ input.ide-model-graph-search[type="search"] {
|
|
|
4135
4141
|
.mbeditor-collab-diag-label { font-weight: 500; }
|
|
4136
4142
|
.mbeditor-collab-diag-detail { color: var(--ide-text-muted, #858585); margin-top: 3px; line-height: 1.5; }
|
|
4137
4143
|
.mbeditor-collab-diag-foot { color: var(--ide-text-muted, #858585); margin-bottom: 12px; }
|
|
4144
|
+
|
|
4145
|
+
.combined-view { height: 100%; overflow-y: auto; background: var(--ide-bg, #1e1e1e); color: var(--ide-text, #d4d4d4); }
|
|
4146
|
+
.combined-note { padding: 8px 16px; font-size: 12px; color: var(--ide-text-muted, #888); }
|
|
4147
|
+
.combined-file-header { position: sticky; top: 0; z-index: 1; display: flex; align-items: center; gap: 8px; padding: 6px 16px; background: var(--ide-bg-elevated, var(--ide-bg, #252526)); border-bottom: 1px solid var(--ide-border, #333); font-size: 12px; }
|
|
4148
|
+
.combined-file-path { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600; }
|
|
4149
|
+
.combined-file-btn { font-size: 11px; padding: 2px 8px; cursor: pointer; color: inherit; background: transparent; border: 1px solid var(--ide-border, #444); border-radius: 3px; }
|
|
4150
|
+
.combined-file-btn:disabled { opacity: 0.4; cursor: default; }
|
|
4151
|
+
.combined-file-body { margin: 0; padding: 8px 16px 24px; overflow-x: auto; font: 12px/1.5 Menlo, Consolas, monospace; tab-size: 2; }
|
|
4152
|
+
|
|
4153
|
+
/* Combined view: method picking + Markdown export; import Compare; Paste & Place */
|
|
4154
|
+
.combined-toolbar { display: flex; gap: 8px; padding: 6px 16px; border-bottom: 1px solid var(--ide-border, #333); }
|
|
4155
|
+
.combined-picker { max-height: 30vh; overflow-y: auto; padding: 4px 16px; font-size: 12px; border-bottom: 1px solid var(--ide-border, #333); }
|
|
4156
|
+
.combined-pick-file, .combined-pick-method { display: flex; align-items: center; gap: 6px; white-space: nowrap; cursor: pointer; }
|
|
4157
|
+
.combined-pick-method { padding-left: 22px; font-size: 11px; }
|
|
4158
|
+
.import-conflict-compare { margin-left: 8px; padding: 0; background: none; border: 0; color: var(--ide-accent, #0e639c); font-size: 11px; cursor: pointer; }
|
|
4159
|
+
.import-conflict-compare:hover { text-decoration: underline; }
|
|
4160
|
+
.import-conflict-modal.import-conflict-wide { min-width: min(900px, 94vw); max-width: 94vw; }
|
|
4161
|
+
.import-conflict-diff { height: 360px; margin-top: 8px; }
|
|
4162
|
+
.paste-place-modal { min-width: min(900px, 94vw); max-width: 94vw; }
|
|
4163
|
+
.paste-place-body { padding: 8px 16px; }
|
|
4164
|
+
.paste-place-input { width: 100%; box-sizing: border-box; font: 12px/1.5 var(--ide-mono-font, monospace); color: var(--ide-text, #d4d4d4); background: var(--ide-input-bg, #3c3c3c); border: 1px solid var(--ide-border, #3c3c3c); border-radius: 3px; padding: 8px; resize: vertical; }
|
|
4165
|
+
.paste-place-error { color: var(--ide-error, #f48771); font-size: 12px; padding: 6px 0; }
|
|
4166
|
+
.paste-place-row { display: flex; align-items: center; gap: 8px; padding: 4px 0; font-size: 12px; }
|
|
4167
|
+
.paste-place-name { font-family: var(--ide-mono-font, monospace); min-width: 160px; }
|
|
4168
|
+
.paste-place-row select { flex: 1; min-width: 0; color: var(--ide-text, #d4d4d4); background: var(--ide-input-bg, #3c3c3c); border: 1px solid var(--ide-border, #3c3c3c); }
|
|
4169
|
+
.paste-place-diff { height: 260px; margin-top: 8px; border: 1px solid var(--ide-border, #3c3c3c); }
|
|
@@ -117,6 +117,17 @@ html[data-glass] .ide-editor-wrapper .monaco-editor .margin { background-color:
|
|
|
117
117
|
/* Taller than Modern's 22px so the chips have room; the 6px gutter above it
|
|
118
118
|
stays, matching the explorer gutter, and it eats 2px of shell padding below. */
|
|
119
119
|
html[data-glass] .ide-statusbar { border-top: 1px solid var(--g-edge); height: 28px; margin: 0 0 -2px; }
|
|
120
|
+
/* The bar is a panel-coloured card here, not the accent fill the legacy themes
|
|
121
|
+
paint flat, so its text is the ordinary text colour. While migrations are
|
|
122
|
+
pending it is amber instead, and the warn pair is what stays readable on it:
|
|
123
|
+
the base .ide-statusbar background is overridden by the glass surface above,
|
|
124
|
+
so the warn background has to be restated. */
|
|
125
|
+
html[data-glass] .ide-statusbar { --ide-accent-text: var(--ide-text); }
|
|
126
|
+
html[data-glass] body.mbeditor-migration-pending .ide-statusbar {
|
|
127
|
+
background: var(--ide-statusbar-warn-bg);
|
|
128
|
+
--ide-accent-text: var(--ide-statusbar-warn-text);
|
|
129
|
+
--ide-text-muted: var(--ide-statusbar-warn-text);
|
|
130
|
+
}
|
|
120
131
|
/* The drawer-open handle lies over the 6px gutter between the editor card and
|
|
121
132
|
the status bar rather than adding a strip of its own above it. */
|
|
122
133
|
/* Sits in the shell's own 6px gap between the body and the status bar. */
|
|
@@ -162,7 +162,7 @@
|
|
|
162
162
|
/* ══ DRACULA ════════════════════════════════════════════════════════════════ */
|
|
163
163
|
[data-theme="dracula"] {
|
|
164
164
|
color-scheme: dark;
|
|
165
|
-
--ide-accent-text: #
|
|
165
|
+
--ide-accent-text: #282a36;
|
|
166
166
|
--ide-statusbar-warn-bg: #ffb86c;
|
|
167
167
|
--ide-statusbar-warn-text: #282a36;
|
|
168
168
|
--status-hover-bg: rgba(255,255,255,0.08);
|
|
@@ -194,7 +194,7 @@
|
|
|
194
194
|
/* ══ NIGHT OWL ══════════════════════════════════════════════════════════════ */
|
|
195
195
|
[data-theme="night-owl"] {
|
|
196
196
|
color-scheme: dark;
|
|
197
|
-
--ide-accent-text: #
|
|
197
|
+
--ide-accent-text: #011627;
|
|
198
198
|
--ide-statusbar-warn-bg: #ffcb8b;
|
|
199
199
|
--ide-statusbar-warn-text: #011627;
|
|
200
200
|
--status-hover-bg: rgba(255,255,255,0.08);
|
|
@@ -322,7 +322,7 @@
|
|
|
322
322
|
/* ══ TOMORROW NIGHT ════════════════════════════════════════════════════════ */
|
|
323
323
|
[data-theme="tomorrow-night"] {
|
|
324
324
|
color-scheme: dark;
|
|
325
|
-
--ide-accent-text: #
|
|
325
|
+
--ide-accent-text: #1d1f21;
|
|
326
326
|
--ide-statusbar-warn-bg: #de935f;
|
|
327
327
|
--ide-statusbar-warn-text: #1d1f21;
|
|
328
328
|
--status-hover-bg: rgba(255,255,255,0.08);
|
|
@@ -758,6 +758,13 @@ module Mbeditor
|
|
|
758
758
|
status: :unprocessable_content
|
|
759
759
|
end
|
|
760
760
|
|
|
761
|
+
# POST /mbeditor/ruby_methods {code:} — method line ranges of Ruby source.
|
|
762
|
+
# A syntax error is a 422 with {error:}, never a raise.
|
|
763
|
+
def ruby_methods
|
|
764
|
+
result = RubyMethodRangesService.call(params[:code].to_s)
|
|
765
|
+
render json: result, status: result[:error] ? :unprocessable_content : :ok
|
|
766
|
+
end
|
|
767
|
+
|
|
761
768
|
# GET /mbeditor/exceptions — recorded host-app exceptions, newest first.
|
|
762
769
|
#
|
|
763
770
|
# The cable push is the live path; this seeds the panel on load and covers
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ripper"
|
|
4
|
+
|
|
5
|
+
module Mbeditor
|
|
6
|
+
# Line ranges of every method (and enclosing class/module) in Ruby source.
|
|
7
|
+
#
|
|
8
|
+
# Ripper rather than Prism: it is stdlib on every Ruby this gem supports.
|
|
9
|
+
# Events fire bottom-up, so a node's end line is the last significant token
|
|
10
|
+
# the scanner produced before it reduced, and owners are resolved afterwards
|
|
11
|
+
# by range containment instead of a stack.
|
|
12
|
+
#
|
|
13
|
+
# call(src) => { methods: [{ name:, singleton:, owner:, start_line:,
|
|
14
|
+
# doc_start_line:, end_line: }],
|
|
15
|
+
# owners: [{ kind:, name:, start_line:, end_line: }] }
|
|
16
|
+
# | { error: "..." } (owners also hold `class << self` as kind "sclass")
|
|
17
|
+
class RubyMethodRangesService
|
|
18
|
+
MAX_BYTES = 200 * 1024
|
|
19
|
+
|
|
20
|
+
# Tokens that carry no code; a def never ends on one of these.
|
|
21
|
+
BLANK = %i[sp nl ignored_nl comment embdoc_beg embdoc embdoc_end words_sep ignored_sp].freeze
|
|
22
|
+
|
|
23
|
+
class Collector < Ripper::SexpBuilder
|
|
24
|
+
attr_reader :methods_found, :owners_found
|
|
25
|
+
|
|
26
|
+
def initialize(*)
|
|
27
|
+
super
|
|
28
|
+
@methods_found = []
|
|
29
|
+
@owners_found = []
|
|
30
|
+
@last_line = 1
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
(Ripper::SCANNER_EVENTS - BLANK).each do |ev|
|
|
34
|
+
define_method(:"on_#{ev}") do |tok|
|
|
35
|
+
@last_line = lineno
|
|
36
|
+
super(tok)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def on_def(name, *rest)
|
|
41
|
+
record(name, false)
|
|
42
|
+
super
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def on_defs(target, period, name, *rest)
|
|
46
|
+
record(name, true)
|
|
47
|
+
super
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def on_class(const, *rest)
|
|
51
|
+
owner(:class, const)
|
|
52
|
+
super
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def on_sclass(target, *rest)
|
|
56
|
+
@owners_found << { kind: "sclass", name: "<< self", start_line: first_line(target), end_line: @last_line }
|
|
57
|
+
super
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def on_module(const, *rest)
|
|
61
|
+
owner(:module, const)
|
|
62
|
+
super
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
def record(ident, singleton)
|
|
68
|
+
@methods_found << { name: ident[1], singleton: singleton, start_line: ident[2][0], end_line: @last_line }
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def owner(kind, const)
|
|
72
|
+
@owners_found << { kind: kind.to_s, name: const_name(const), start_line: first_line(const), end_line: @last_line }
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def const_name(node)
|
|
76
|
+
return "" unless node.is_a?(Array)
|
|
77
|
+
|
|
78
|
+
case node[0]
|
|
79
|
+
when :@const then node[1]
|
|
80
|
+
when :const_ref, :var_ref, :top_const_ref then const_name(node[1])
|
|
81
|
+
when :const_path_ref then "#{const_name(node[1])}::#{const_name(node[2])}"
|
|
82
|
+
else ""
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def first_line(node)
|
|
87
|
+
return node[2][0] if node.is_a?(Array) && node[0].is_a?(Symbol) && node[0].to_s.start_with?("@")
|
|
88
|
+
|
|
89
|
+
node.each { |c| l = first_line(c) if c.is_a?(Array); return l if l } if node.is_a?(Array)
|
|
90
|
+
nil
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def self.call(source)
|
|
95
|
+
source = source.to_s
|
|
96
|
+
return { error: "Source too large" } if source.bytesize > MAX_BYTES
|
|
97
|
+
|
|
98
|
+
parser = Collector.new(source)
|
|
99
|
+
parser.parse
|
|
100
|
+
return { error: "Syntax error" } if parser.error?
|
|
101
|
+
|
|
102
|
+
lines = source.lines
|
|
103
|
+
owners = parser.owners_found.sort_by { |o| [o[:start_line], -o[:end_line]] }
|
|
104
|
+
methods = parser.methods_found.sort_by { |m| m[:start_line] }.map do |m|
|
|
105
|
+
chain = owners.select { |o| o[:start_line] <= m[:start_line] && o[:end_line] >= m[:end_line] }
|
|
106
|
+
m.merge(singleton: m[:singleton] || chain.any? { |o| o[:kind] == "sclass" },
|
|
107
|
+
owner: chain.reject { |o| o[:kind] == "sclass" }.map { |o| o[:name] }.join("::"), doc_start_line: doc_start(lines, m[:start_line]))
|
|
108
|
+
end
|
|
109
|
+
{ methods: methods, owners: owners }
|
|
110
|
+
rescue StandardError, SystemStackError => e
|
|
111
|
+
{ error: e.message }
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def self.doc_start(lines, start_line)
|
|
115
|
+
line = start_line
|
|
116
|
+
line -= 1 while line > 1 && lines[line - 2].to_s.lstrip.start_with?("#")
|
|
117
|
+
line
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
@@ -33,6 +33,7 @@ module Mbeditor
|
|
|
33
33
|
# is still told to migrate — they are just no longer prevented from working.
|
|
34
34
|
class PendingMigrationBypass
|
|
35
35
|
DEFAULT_ASSET_PREFIX = "/assets"
|
|
36
|
+
DEFAULT_CABLE_PATH = "/cable"
|
|
36
37
|
|
|
37
38
|
def initialize(app, file_watcher: nil)
|
|
38
39
|
@app = app
|
|
@@ -94,7 +95,21 @@ module Mbeditor
|
|
|
94
95
|
prefix = Mbeditor::MountPath.resolve
|
|
95
96
|
return true if path == prefix || path.start_with?("#{prefix}/")
|
|
96
97
|
|
|
97
|
-
path.start_with?("#{asset_prefix}/")
|
|
98
|
+
return true if path.start_with?("#{asset_prefix}/")
|
|
99
|
+
|
|
100
|
+
# The cable carries presence, collaboration and file-change push. Left
|
|
101
|
+
# blocked it 500s on every reconnect, and the client's status chips
|
|
102
|
+
# flicker for as long as the migration stays pending.
|
|
103
|
+
path == cable_path || path.start_with?("#{cable_path}/")
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def cable_path
|
|
107
|
+
@cable_path ||= begin
|
|
108
|
+
configured = Rails.application.config.action_cable.mount_path if Rails.application.config.respond_to?(:action_cable)
|
|
109
|
+
(configured.presence || DEFAULT_CABLE_PATH).to_s.chomp("/")
|
|
110
|
+
rescue StandardError
|
|
111
|
+
DEFAULT_CABLE_PATH
|
|
112
|
+
end
|
|
98
113
|
end
|
|
99
114
|
|
|
100
115
|
def asset_prefix
|
data/lib/mbeditor/route_map.rb
CHANGED
|
@@ -37,6 +37,7 @@ module Mbeditor
|
|
|
37
37
|
get 'js_program', to: 'editors#js_program'
|
|
38
38
|
post 'ruby_lsp', to: 'editors#ruby_lsp'
|
|
39
39
|
post 'ruby_rename', to: 'editors#ruby_rename'
|
|
40
|
+
post 'ruby_methods', to: 'editors#ruby_methods'
|
|
40
41
|
get 'model_graph', to: 'editors#model_graph'
|
|
41
42
|
get 'exceptions', to: 'editors#exceptions'
|
|
42
43
|
delete 'exceptions', to: 'editors#clear_exceptions'
|
data/lib/mbeditor/version.rb
CHANGED
|
Binary file
|