mbeditor 0.5.1 → 0.5.3
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/app/assets/javascripts/mbeditor/components/EditorPanel.js +8 -1
- data/app/assets/javascripts/mbeditor/components/FileTree.js +8 -1
- data/app/assets/javascripts/mbeditor/editor_plugins.js +87 -38
- data/app/controllers/mbeditor/editors_controller.rb +14 -5
- data/app/services/mbeditor/ruby_definition_service.rb +84 -64
- data/lib/mbeditor/configuration.rb +4 -2
- data/lib/mbeditor/rack/handle_pending_migrations.rb +72 -51
- data/lib/mbeditor/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 31199cd73e4e3321d8851a62ab20decd6e9c8d9ab328b50d9f987f85da1333b2
|
|
4
|
+
data.tar.gz: 27de9f3e0641c41e693023b3fcbedbe0d89d35d8d1fd7f3606aaa8095ceeb5b2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5f86f17e33bfd43248d7dc1a122f6ed3d85a0fcf3e745ca805561d7d490c3497e1bfaa53339ed28ed068a401ed91447888eb5f92a89654d89fe3b8ae0fd7c5b1
|
|
7
|
+
data.tar.gz: fea980a035f785508e1bc5444fce65c7a8c0824307c48e4db7ea73fcf7218f23889c1c687d999fc252f3ffb97fc732b1aae8292ec65938f40d97c48b95a470c7
|
|
@@ -35,7 +35,7 @@ var EditorPanel = function EditorPanel(_ref) {
|
|
|
35
35
|
var editorRef = useRef(null);
|
|
36
36
|
var monacoRef = useRef(null);
|
|
37
37
|
var latestContentRef = useRef('');
|
|
38
|
-
var lastAppliedExternalVersionRef = useRef(
|
|
38
|
+
var lastAppliedExternalVersionRef = useRef(0);
|
|
39
39
|
var aviBaseRef = useRef(0);
|
|
40
40
|
var aviMaxRef = useRef(0);
|
|
41
41
|
|
|
@@ -832,12 +832,19 @@ var EditorPanel = function EditorPanel(_ref) {
|
|
|
832
832
|
EditorStore.setState({ canUndo: false, canRedo: false });
|
|
833
833
|
} else {
|
|
834
834
|
// Keep undo stack for formats or replaces by using executeEdits
|
|
835
|
+
var _extEntry = window.__mbeditorModels && window.__mbeditorModels[tab.path];
|
|
836
|
+
if (_extEntry) _extEntry.cleanVersionId = null;
|
|
835
837
|
editor.pushUndoStop();
|
|
836
838
|
editor.executeEdits("external", [{
|
|
837
839
|
range: model.getFullModelRange(),
|
|
838
840
|
text: tab.content
|
|
839
841
|
}]);
|
|
840
842
|
editor.pushUndoStop();
|
|
843
|
+
// Re-anchor the clean baseline so onDidChangeContent doesn't mark this
|
|
844
|
+
// externally-applied content as a dirty edit.
|
|
845
|
+
var _newExtAvi = model.getAlternativeVersionId();
|
|
846
|
+
if (_extEntry) _extEntry.cleanVersionId = _newExtAvi;
|
|
847
|
+
aviBaseRef.current = _newExtAvi;
|
|
841
848
|
}
|
|
842
849
|
}, [tab.content, tab.externalContentVersion]);
|
|
843
850
|
|
|
@@ -12,6 +12,13 @@ var useRef = _React.useRef;
|
|
|
12
12
|
var useEffect = _React.useEffect;
|
|
13
13
|
var useMemo = _React.useMemo;
|
|
14
14
|
|
|
15
|
+
function formatSize(bytes) {
|
|
16
|
+
if (typeof bytes !== 'number' || bytes < 0) return '';
|
|
17
|
+
if (bytes < 1024) return bytes + ' B';
|
|
18
|
+
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
|
|
19
|
+
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
|
|
20
|
+
}
|
|
21
|
+
|
|
15
22
|
var FileTree = function FileTree(_ref) {
|
|
16
23
|
var items = _ref.items;
|
|
17
24
|
var onSelect = _ref.onSelect;
|
|
@@ -477,7 +484,7 @@ var FileTree = function FileTree(_ref) {
|
|
|
477
484
|
),
|
|
478
485
|
React.createElement(
|
|
479
486
|
'div',
|
|
480
|
-
{ className: 'tree-item-name', title: node.path },
|
|
487
|
+
{ className: 'tree-item-name', title: node.type === 'file' && node.size != null ? node.path + ' — ' + formatSize(node.size) : node.path },
|
|
481
488
|
node.name
|
|
482
489
|
),
|
|
483
490
|
statusMeta && React.createElement(
|
|
@@ -51,12 +51,16 @@
|
|
|
51
51
|
|
|
52
52
|
var globalsRegistered = false;
|
|
53
53
|
|
|
54
|
-
// Enumerate window for user-defined
|
|
55
|
-
//
|
|
56
|
-
//
|
|
57
|
-
//
|
|
54
|
+
// Enumerate window for user-defined globals and return a TypeScript declaration string.
|
|
55
|
+
// Sprockets exposes every top-level var/function as a window property before Monaco
|
|
56
|
+
// initialises, so scanning at registration time captures all components and helpers.
|
|
57
|
+
//
|
|
58
|
+
// Filter: keep only plain writable data properties (configurable, writable, no getter).
|
|
59
|
+
// Browser built-ins are either non-configurable or accessor properties (hasGet), so
|
|
60
|
+
// this reliably separates them from user-assigned globals without a native-code test
|
|
61
|
+
// (which only works for functions, not objects like `document` or `location`).
|
|
58
62
|
function buildWindowGlobalsShim() {
|
|
59
|
-
var alreadyDeclared = { React: 1, ReactDOM: 1, PropTypes: 1, MaterialUI: 1 };
|
|
63
|
+
var alreadyDeclared = { React: 1, ReactDOM: 1, PropTypes: 1, MaterialUI: 1, $: 1, jQuery: 1 };
|
|
60
64
|
var lines = [];
|
|
61
65
|
try {
|
|
62
66
|
var keys = Object.keys(window);
|
|
@@ -67,11 +71,6 @@
|
|
|
67
71
|
var value;
|
|
68
72
|
try { value = window[key]; } catch (e) { continue; }
|
|
69
73
|
if (value === null || value === undefined) continue;
|
|
70
|
-
if (typeof value === 'function') {
|
|
71
|
-
try {
|
|
72
|
-
if (/\[native code\]/.test(Function.prototype.toString.call(value))) continue;
|
|
73
|
-
} catch (e) { continue; }
|
|
74
|
-
}
|
|
75
74
|
lines.push('declare var ' + key + ': any;');
|
|
76
75
|
}
|
|
77
76
|
} catch (e) {}
|
|
@@ -322,6 +321,30 @@
|
|
|
322
321
|
event.stopPropagation();
|
|
323
322
|
});
|
|
324
323
|
|
|
324
|
+
// Navigate to a Ruby symbol: modules/classes go to their definition file,
|
|
325
|
+
// lowercase symbols go to their def line.
|
|
326
|
+
function navigateToWord(word) {
|
|
327
|
+
if (/^[A-Z]/.test(word) && typeof FileService !== 'undefined' && FileService.getModuleMembers) {
|
|
328
|
+
FileService.getModuleMembers(word).then(function(data) {
|
|
329
|
+
if (!data || !data.file) return;
|
|
330
|
+
var filename = data.file.split('/').pop();
|
|
331
|
+
if (typeof TabManager !== 'undefined' && TabManager.openTab) {
|
|
332
|
+
TabManager.openTab(data.file, filename, 1);
|
|
333
|
+
}
|
|
334
|
+
}).catch(function() {});
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
if (typeof FileService === 'undefined' || !FileService.getDefinition) return;
|
|
338
|
+
FileService.getDefinition(word, 'ruby').then(function(data) {
|
|
339
|
+
var results = data && Array.isArray(data.results) ? data.results : [];
|
|
340
|
+
if (results.length === 0) return;
|
|
341
|
+
var r = results[0];
|
|
342
|
+
if (typeof TabManager !== 'undefined' && TabManager.openTab) {
|
|
343
|
+
TabManager.openTab(r.file, r.file.split('/').pop(), r.line);
|
|
344
|
+
}
|
|
345
|
+
}).catch(function() {});
|
|
346
|
+
}
|
|
347
|
+
|
|
325
348
|
// Ctrl/Cmd+click — navigate to definition
|
|
326
349
|
gotoMouseDisposable = editor.onMouseDown(function(event) {
|
|
327
350
|
var ctrlOrCmd = event.event.ctrlKey || event.event.metaKey;
|
|
@@ -336,19 +359,9 @@
|
|
|
336
359
|
if (!wordInfo || !wordInfo.word || wordInfo.word.length < 2) return;
|
|
337
360
|
if (RUBY_KEYWORDS[wordInfo.word]) return;
|
|
338
361
|
if (RUBY_CORE_METHODS[wordInfo.word]) return;
|
|
339
|
-
if (typeof FileService === 'undefined' || !FileService.getDefinition) return;
|
|
340
362
|
|
|
341
363
|
event.event.preventDefault();
|
|
342
|
-
|
|
343
|
-
FileService.getDefinition(wordInfo.word, 'ruby').then(function(data) {
|
|
344
|
-
var results = data && Array.isArray(data.results) ? data.results : [];
|
|
345
|
-
if (results.length === 0) return;
|
|
346
|
-
var r = results[0];
|
|
347
|
-
var filename = r.file.split('/').pop();
|
|
348
|
-
if (typeof TabManager !== 'undefined' && TabManager.openTab) {
|
|
349
|
-
TabManager.openTab(r.file, filename, r.line);
|
|
350
|
-
}
|
|
351
|
-
}).catch(function() {});
|
|
364
|
+
navigateToWord(wordInfo.word);
|
|
352
365
|
});
|
|
353
366
|
|
|
354
367
|
// F12 — go to definition from keyboard
|
|
@@ -365,17 +378,7 @@
|
|
|
365
378
|
if (!wordInfo || !wordInfo.word || wordInfo.word.length < 2) return;
|
|
366
379
|
if (RUBY_KEYWORDS[wordInfo.word]) return;
|
|
367
380
|
if (RUBY_CORE_METHODS[wordInfo.word]) return;
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
FileService.getDefinition(wordInfo.word, 'ruby').then(function(data) {
|
|
371
|
-
var results = data && Array.isArray(data.results) ? data.results : [];
|
|
372
|
-
if (results.length === 0) return;
|
|
373
|
-
var r = results[0];
|
|
374
|
-
var filename = r.file.split('/').pop();
|
|
375
|
-
if (typeof TabManager !== 'undefined' && TabManager.openTab) {
|
|
376
|
-
TabManager.openTab(r.file, filename, r.line);
|
|
377
|
-
}
|
|
378
|
-
}).catch(function() {});
|
|
381
|
+
navigateToWord(wordInfo.word);
|
|
379
382
|
}
|
|
380
383
|
});
|
|
381
384
|
}
|
|
@@ -482,11 +485,11 @@
|
|
|
482
485
|
});
|
|
483
486
|
}
|
|
484
487
|
|
|
485
|
-
// Declare globals that
|
|
486
|
-
//
|
|
487
|
-
//
|
|
488
|
-
//
|
|
489
|
-
//
|
|
488
|
+
// Declare globals that are injected at runtime so checkJs doesn't flag them
|
|
489
|
+
// as undefined. The buildWindowGlobalsShim() function automatically detects
|
|
490
|
+
// window globals from the host application. Common Sprockets globals
|
|
491
|
+
// (React, ReactDOM, etc.) are declared explicitly. For additional globals
|
|
492
|
+
// not auto-detected, add `/* global MyComponent */` at the top of the file.
|
|
490
493
|
if (monaco.languages.typescript && monaco.languages.typescript.javascriptDefaults) {
|
|
491
494
|
monaco.languages.typescript.javascriptDefaults.addExtraLib(
|
|
492
495
|
[
|
|
@@ -494,6 +497,8 @@
|
|
|
494
497
|
'declare var ReactDOM: any;',
|
|
495
498
|
'declare var PropTypes: any;',
|
|
496
499
|
'declare var MaterialUI: any;',
|
|
500
|
+
'declare var $: any;',
|
|
501
|
+
'declare var jQuery: any;',
|
|
497
502
|
'interface Window { [key: string]: any; }'
|
|
498
503
|
].join('\n'),
|
|
499
504
|
'inmemory://mbeditor/sprockets-globals.d.ts'
|
|
@@ -506,9 +511,53 @@
|
|
|
506
511
|
'inmemory://mbeditor/window-globals.d.ts'
|
|
507
512
|
);
|
|
508
513
|
}
|
|
514
|
+
|
|
515
|
+
// Downgrade certain TypeScript diagnostic codes from Error to Warning.
|
|
516
|
+
// TypeScript has no built-in way to emit these as warnings, so we intercept
|
|
517
|
+
// the marker set after the worker fires and re-apply with lower severity.
|
|
518
|
+
//
|
|
519
|
+
// Patch markers after the TypeScript worker fires:
|
|
520
|
+
// - Both: downgrade TS6133 ("declared but never read") from Error to Warning.
|
|
521
|
+
// Host-app globals are handled by the dynamic window shim and explicit
|
|
522
|
+
// addExtraLib declarations above — we do not suppress TS2304 globally so
|
|
523
|
+
// that genuinely undefined names are still flagged as errors.
|
|
524
|
+
var JS_SUPPRESS_CODES = {};
|
|
525
|
+
var JS_WARN_CODES = { '6133': true };
|
|
526
|
+
var TS_WARN_CODES = { '6133': true };
|
|
527
|
+
var _severityPatchActive = false;
|
|
528
|
+
monaco.editor.onDidChangeMarkers(function(uris) {
|
|
529
|
+
if (_severityPatchActive) return;
|
|
530
|
+
_severityPatchActive = true;
|
|
531
|
+
try {
|
|
532
|
+
uris.forEach(function(uri) {
|
|
533
|
+
var model = monaco.editor.getModel(uri);
|
|
534
|
+
if (!model) return;
|
|
535
|
+
[
|
|
536
|
+
{ owner: 'javascript', suppress: JS_SUPPRESS_CODES, warn: JS_WARN_CODES },
|
|
537
|
+
{ owner: 'typescript', suppress: {}, warn: TS_WARN_CODES }
|
|
538
|
+
].forEach(function(entry) {
|
|
539
|
+
var markers = monaco.editor.getModelMarkers({ resource: uri, owner: entry.owner });
|
|
540
|
+
var needsPatch = markers.some(function(m) {
|
|
541
|
+
var code = String(m.code);
|
|
542
|
+
return (m.severity === monaco.MarkerSeverity.Error && (entry.suppress[code] || entry.warn[code]));
|
|
543
|
+
});
|
|
544
|
+
if (!needsPatch) return;
|
|
545
|
+
monaco.editor.setModelMarkers(model, entry.owner, markers.filter(function(m) {
|
|
546
|
+
return !entry.suppress[String(m.code)];
|
|
547
|
+
}).map(function(m) {
|
|
548
|
+
return (m.severity === monaco.MarkerSeverity.Error && entry.warn[String(m.code)])
|
|
549
|
+
? Object.assign({}, m, { severity: monaco.MarkerSeverity.Warning })
|
|
550
|
+
: m;
|
|
551
|
+
}));
|
|
552
|
+
});
|
|
553
|
+
});
|
|
554
|
+
} finally {
|
|
555
|
+
_severityPatchActive = false;
|
|
556
|
+
}
|
|
557
|
+
});
|
|
509
558
|
}
|
|
510
559
|
|
|
511
|
-
// TypeScript: enable JSX for .tsx files
|
|
560
|
+
// TypeScript: enable JSX for .tsx files.
|
|
512
561
|
if (monaco.languages.typescript && monaco.languages.typescript.typescriptDefaults) {
|
|
513
562
|
monaco.languages.typescript.typescriptDefaults.setCompilerOptions({
|
|
514
563
|
target: monaco.languages.typescript.ScriptTarget.ES2020,
|
|
@@ -337,7 +337,8 @@ module Mbeditor
|
|
|
337
337
|
workspace_root,
|
|
338
338
|
symbol,
|
|
339
339
|
excluded_dirnames: excluded_dirnames,
|
|
340
|
-
excluded_paths:
|
|
340
|
+
excluded_paths: excluded_paths,
|
|
341
|
+
included_dirs: ruby_def_include_dirs
|
|
341
342
|
)
|
|
342
343
|
ri = RiDefinitionService.call(symbol)
|
|
343
344
|
workspace + ri
|
|
@@ -360,7 +361,8 @@ module Mbeditor
|
|
|
360
361
|
file = RubyDefinitionService.module_defined_in(
|
|
361
362
|
workspace_root, name,
|
|
362
363
|
excluded_dirnames: excluded_dirnames,
|
|
363
|
-
excluded_paths: excluded_paths
|
|
364
|
+
excluded_paths: excluded_paths,
|
|
365
|
+
included_dirs: ruby_def_include_dirs
|
|
364
366
|
)
|
|
365
367
|
return render json: { name: name, methods: [] } unless file
|
|
366
368
|
|
|
@@ -383,14 +385,16 @@ module Mbeditor
|
|
|
383
385
|
# Fast no-op on subsequent calls (mtime checks only).
|
|
384
386
|
RubyDefinitionService.scan(workspace_root,
|
|
385
387
|
excluded_dirnames: excluded_dirnames,
|
|
386
|
-
excluded_paths: excluded_paths
|
|
388
|
+
excluded_paths: excluded_paths,
|
|
389
|
+
included_dirs: ruby_def_include_dirs)
|
|
387
390
|
|
|
388
391
|
module_names = RubyDefinitionService.includes_in_file(path)
|
|
389
392
|
includes = module_names.filter_map do |mod_name|
|
|
390
393
|
mod_file = RubyDefinitionService.module_defined_in(
|
|
391
394
|
workspace_root, mod_name,
|
|
392
395
|
excluded_dirnames: excluded_dirnames,
|
|
393
|
-
excluded_paths: excluded_paths
|
|
396
|
+
excluded_paths: excluded_paths,
|
|
397
|
+
included_dirs: ruby_def_include_dirs
|
|
394
398
|
)
|
|
395
399
|
next unless mod_file
|
|
396
400
|
|
|
@@ -1068,7 +1072,8 @@ module Mbeditor
|
|
|
1068
1072
|
if File.directory?(full)
|
|
1069
1073
|
{ name: name, type: "folder", path: rel, children: build_tree(full, depth: depth + 1) }
|
|
1070
1074
|
else
|
|
1071
|
-
|
|
1075
|
+
size = File.size(full) rescue nil
|
|
1076
|
+
{ name: name, type: "file", path: rel, size: size }
|
|
1072
1077
|
end
|
|
1073
1078
|
end
|
|
1074
1079
|
rescue Errno::EACCES
|
|
@@ -1083,6 +1088,10 @@ module Mbeditor
|
|
|
1083
1088
|
excluded_paths.filter { |path| !path.include?("/") }
|
|
1084
1089
|
end
|
|
1085
1090
|
|
|
1091
|
+
def ruby_def_include_dirs
|
|
1092
|
+
Array(Mbeditor.configuration.ruby_def_include_dirs).map(&:to_s).reject(&:blank?)
|
|
1093
|
+
end
|
|
1094
|
+
|
|
1086
1095
|
def excluded_path?(relative_path, name)
|
|
1087
1096
|
excluded_paths.any? do |pattern|
|
|
1088
1097
|
if pattern.include?("/")
|
|
@@ -51,10 +51,11 @@ module Mbeditor
|
|
|
51
51
|
attr_reader :file_cache, :mutex
|
|
52
52
|
attr_accessor :cache_path
|
|
53
53
|
|
|
54
|
-
def call(workspace_root, symbol, excluded_dirnames: [], excluded_paths: [])
|
|
54
|
+
def call(workspace_root, symbol, excluded_dirnames: [], excluded_paths: [], included_dirs: [])
|
|
55
55
|
new(workspace_root, symbol,
|
|
56
56
|
excluded_dirnames: excluded_dirnames,
|
|
57
|
-
excluded_paths:
|
|
57
|
+
excluded_paths: excluded_paths,
|
|
58
|
+
included_dirs: included_dirs).call
|
|
58
59
|
end
|
|
59
60
|
|
|
60
61
|
# Load the JSON cache from disk exactly once per process (double-checked
|
|
@@ -122,20 +123,26 @@ module Mbeditor
|
|
|
122
123
|
# Searches the cache (and triggers a workspace scan if needed) to find
|
|
123
124
|
# which file in +workspace_root+ defines the given module or class name.
|
|
124
125
|
# Returns the absolute file path string or nil.
|
|
125
|
-
def module_defined_in(workspace_root, module_name, excluded_dirnames: [], excluded_paths: [])
|
|
126
|
+
def module_defined_in(workspace_root, module_name, excluded_dirnames: [], excluded_paths: [], included_dirs: [])
|
|
126
127
|
load_disk_cache_once
|
|
128
|
+
root_prefix = workspace_root.to_s.chomp("/")
|
|
129
|
+
within_dirs = ->(path) {
|
|
130
|
+
return true if included_dirs.empty?
|
|
131
|
+
included_dirs.any? { |d| path.start_with?(File.join(root_prefix, d) + "/") }
|
|
132
|
+
}
|
|
127
133
|
result = @mutex.synchronize do
|
|
128
|
-
@file_cache.find { |
|
|
134
|
+
@file_cache.find { |path, entry| within_dirs.call(path) && entry[:module_names]&.include?(module_name) }
|
|
129
135
|
end
|
|
130
136
|
return result[0] if result
|
|
131
137
|
|
|
132
138
|
# Cache miss: scan workspace to populate cache entries with module_names.
|
|
133
139
|
new(workspace_root, nil,
|
|
134
140
|
excluded_dirnames: excluded_dirnames,
|
|
135
|
-
excluded_paths: excluded_paths
|
|
141
|
+
excluded_paths: excluded_paths,
|
|
142
|
+
included_dirs: included_dirs).scan_workspace
|
|
136
143
|
|
|
137
144
|
result = @mutex.synchronize do
|
|
138
|
-
@file_cache.find { |
|
|
145
|
+
@file_cache.find { |path, entry| within_dirs.call(path) && entry[:module_names]&.include?(module_name) }
|
|
139
146
|
end
|
|
140
147
|
result ? result[0] : nil
|
|
141
148
|
end
|
|
@@ -153,18 +160,20 @@ module Mbeditor
|
|
|
153
160
|
|
|
154
161
|
# Convenience wrapper: scan the whole workspace to warm the cache.
|
|
155
162
|
# Fast on subsequent calls (only re-parses files whose mtime changed).
|
|
156
|
-
def scan(workspace_root, excluded_dirnames: [], excluded_paths: [])
|
|
163
|
+
def scan(workspace_root, excluded_dirnames: [], excluded_paths: [], included_dirs: [])
|
|
157
164
|
new(workspace_root, nil,
|
|
158
165
|
excluded_dirnames: excluded_dirnames,
|
|
159
|
-
excluded_paths: excluded_paths
|
|
166
|
+
excluded_paths: excluded_paths,
|
|
167
|
+
included_dirs: included_dirs).scan_workspace
|
|
160
168
|
end
|
|
161
169
|
end
|
|
162
170
|
|
|
163
|
-
def initialize(workspace_root, symbol, excluded_dirnames: [], excluded_paths: [])
|
|
164
|
-
@workspace_root
|
|
165
|
-
@symbol
|
|
171
|
+
def initialize(workspace_root, symbol, excluded_dirnames: [], excluded_paths: [], included_dirs: [])
|
|
172
|
+
@workspace_root = workspace_root.to_s.chomp("/")
|
|
173
|
+
@symbol = symbol
|
|
166
174
|
@excluded_dirnames = Array(excluded_dirnames)
|
|
167
175
|
@excluded_paths = Array(excluded_paths)
|
|
176
|
+
@included_dirs = Array(included_dirs)
|
|
168
177
|
end
|
|
169
178
|
|
|
170
179
|
# Walks the entire workspace and populates the per-file cache (including the
|
|
@@ -176,27 +185,29 @@ module Mbeditor
|
|
|
176
185
|
files_scanned = 0
|
|
177
186
|
evict_deleted_cache_entries
|
|
178
187
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
188
|
+
search_roots.each do |root|
|
|
189
|
+
Find.find(root) do |path|
|
|
190
|
+
if File.directory?(path)
|
|
191
|
+
dirname = File.basename(path)
|
|
192
|
+
rel_dir = relative_path(path)
|
|
193
|
+
Find.prune if path != root && excluded_dir?(dirname, rel_dir)
|
|
194
|
+
next
|
|
195
|
+
end
|
|
196
|
+
next unless path.end_with?(".rb")
|
|
187
197
|
|
|
188
|
-
|
|
189
|
-
|
|
198
|
+
rel = relative_path(path)
|
|
199
|
+
next if excluded_rel_path?(rel, File.basename(path))
|
|
190
200
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
201
|
+
files_scanned += 1
|
|
202
|
+
if files_scanned > MAX_FILES_SCANNED
|
|
203
|
+
Rails.logger.warn("[mbeditor] RubyDefinitionService: workspace exceeds #{MAX_FILES_SCANNED} .rb files; stopping scan early")
|
|
204
|
+
break
|
|
205
|
+
end
|
|
206
|
+
begin
|
|
207
|
+
cache_entry_for(path)
|
|
208
|
+
rescue StandardError
|
|
209
|
+
nil
|
|
210
|
+
end
|
|
200
211
|
end
|
|
201
212
|
end
|
|
202
213
|
|
|
@@ -212,46 +223,46 @@ module Mbeditor
|
|
|
212
223
|
|
|
213
224
|
evict_deleted_cache_entries
|
|
214
225
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
Find.prune
|
|
226
|
+
search_roots.each do |root|
|
|
227
|
+
Find.find(root) do |path|
|
|
228
|
+
# Prune excluded directories
|
|
229
|
+
if File.directory?(path)
|
|
230
|
+
dirname = File.basename(path)
|
|
231
|
+
rel_dir = relative_path(path)
|
|
232
|
+
Find.prune if path != root && excluded_dir?(dirname, rel_dir)
|
|
233
|
+
next
|
|
222
234
|
end
|
|
223
|
-
next
|
|
224
|
-
end
|
|
225
235
|
|
|
226
|
-
|
|
236
|
+
next unless path.end_with?(".rb")
|
|
227
237
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
files_scanned += 1
|
|
232
|
-
if files_scanned > MAX_FILES_SCANNED
|
|
233
|
-
Rails.logger.warn("[mbeditor] RubyDefinitionService: workspace exceeds #{MAX_FILES_SCANNED} .rb files; stopping scan early")
|
|
234
|
-
break
|
|
235
|
-
end
|
|
238
|
+
rel = relative_path(path)
|
|
239
|
+
next if excluded_rel_path?(rel, File.basename(path))
|
|
236
240
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
next unless hit_lines && hit_lines.any?
|
|
241
|
+
files_scanned += 1
|
|
242
|
+
if files_scanned > MAX_FILES_SCANNED
|
|
243
|
+
Rails.logger.warn("[mbeditor] RubyDefinitionService: workspace exceeds #{MAX_FILES_SCANNED} .rb files; stopping scan early")
|
|
244
|
+
break
|
|
245
|
+
end
|
|
243
246
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
247
|
+
begin
|
|
248
|
+
cached = cache_entry_for(path)
|
|
249
|
+
next unless cached
|
|
250
|
+
|
|
251
|
+
hit_lines = @symbol ? cached[:all_defs].fetch(@symbol, nil) : nil
|
|
252
|
+
next unless hit_lines && hit_lines.any?
|
|
253
|
+
|
|
254
|
+
hit_lines.each do |def_line|
|
|
255
|
+
results << {
|
|
256
|
+
file: rel,
|
|
257
|
+
line: def_line,
|
|
258
|
+
signature: (cached[:lines][def_line - 1] || "").strip,
|
|
259
|
+
comments: extract_comments(cached[:lines], def_line)
|
|
260
|
+
}
|
|
261
|
+
return results if results.length >= MAX_RESULTS
|
|
262
|
+
end
|
|
263
|
+
rescue StandardError
|
|
264
|
+
# Malformed file or unreadable; skip silently
|
|
252
265
|
end
|
|
253
|
-
rescue StandardError
|
|
254
|
-
# Malformed file or unreadable; skip silently
|
|
255
266
|
end
|
|
256
267
|
end
|
|
257
268
|
|
|
@@ -386,6 +397,15 @@ module Mbeditor
|
|
|
386
397
|
full_path.to_s.delete_prefix(@workspace_root).delete_prefix("/")
|
|
387
398
|
end
|
|
388
399
|
|
|
400
|
+
def search_roots
|
|
401
|
+
return [@workspace_root] if @included_dirs.empty?
|
|
402
|
+
|
|
403
|
+
dirs = @included_dirs
|
|
404
|
+
.map { |d| File.join(@workspace_root, d) }
|
|
405
|
+
.select { |d| File.directory?(d) }
|
|
406
|
+
dirs.empty? ? [@workspace_root] : dirs
|
|
407
|
+
end
|
|
408
|
+
|
|
389
409
|
def excluded_dir?(dirname, rel_dir)
|
|
390
410
|
@excluded_dirnames.include?(dirname) ||
|
|
391
411
|
@excluded_paths.any? do |pattern|
|
|
@@ -6,7 +6,8 @@ module Mbeditor
|
|
|
6
6
|
:redmine_enabled, :redmine_url, :redmine_api_key, :redmine_ticket_source,
|
|
7
7
|
:test_framework, :test_command, :test_timeout,
|
|
8
8
|
:authenticate_with,
|
|
9
|
-
:lint_timeout, :base_branch_candidates, :git_timeout
|
|
9
|
+
:lint_timeout, :base_branch_candidates, :git_timeout,
|
|
10
|
+
:ruby_def_include_dirs
|
|
10
11
|
|
|
11
12
|
def initialize
|
|
12
13
|
@allowed_environments = [:development]
|
|
@@ -22,7 +23,8 @@ module Mbeditor
|
|
|
22
23
|
@test_timeout = 60 # seconds
|
|
23
24
|
@lint_timeout = 15 # seconds for RuboCop/haml-lint subprocesses
|
|
24
25
|
@base_branch_candidates = %w[origin/develop origin/main origin/master develop main master]
|
|
25
|
-
@git_timeout
|
|
26
|
+
@git_timeout = nil # seconds; nil disables (no timeout on git subprocesses)
|
|
27
|
+
@ruby_def_include_dirs = %w[app/models app/controllers app/helpers app/concerns]
|
|
26
28
|
end
|
|
27
29
|
end
|
|
28
30
|
end
|
|
@@ -36,6 +36,15 @@ module Mbeditor
|
|
|
36
36
|
private
|
|
37
37
|
|
|
38
38
|
def editor_shell_html(base)
|
|
39
|
+
prettier_scripts = %w[
|
|
40
|
+
prettier-standalone.js
|
|
41
|
+
prettier-plugin-babel.js
|
|
42
|
+
prettier-plugin-estree.js
|
|
43
|
+
prettier-plugin-html.js
|
|
44
|
+
prettier-plugin-postcss.js
|
|
45
|
+
prettier-plugin-markdown.js
|
|
46
|
+
].map { |f| "#{base}/assets/#{f}" }.to_json
|
|
47
|
+
|
|
39
48
|
<<~HTML
|
|
40
49
|
<!DOCTYPE html>
|
|
41
50
|
<html lang="en">
|
|
@@ -45,6 +54,16 @@ module Mbeditor
|
|
|
45
54
|
<title>Mbeditor</title>
|
|
46
55
|
<link rel="stylesheet" href="#{base}/assets/fontawesome.min.css" />
|
|
47
56
|
<link rel="stylesheet" href="#{base}/assets/mbeditor/application.css" />
|
|
57
|
+
<script defer src="#{base}/assets/react.min.js"></script>
|
|
58
|
+
<script defer src="#{base}/assets/react-dom.min.js"></script>
|
|
59
|
+
<script defer src="#{base}/assets/axios.min.js"></script>
|
|
60
|
+
<script defer src="#{base}/assets/lodash.min.js"></script>
|
|
61
|
+
<script defer src="#{base}/assets/minisearch.min.js"></script>
|
|
62
|
+
<script defer src="#{base}/assets/marked.min.js"></script>
|
|
63
|
+
<script defer src="#{base}/assets/emmet.js"></script>
|
|
64
|
+
<script defer src="#{base}/assets/monaco-themes-bundle.js"></script>
|
|
65
|
+
<script>var require = { paths: { vs: '#{base}/monaco-editor/vs', 'monaco-editor/esm/vs': '#{base}/monaco-editor/vs', 'monaco-vim': '#{base}/assets/monaco-vim' } };</script>
|
|
66
|
+
<script src="#{base}/monaco-editor/vs/loader.js"></script>
|
|
48
67
|
</head>
|
|
49
68
|
<body>
|
|
50
69
|
<script>
|
|
@@ -60,68 +79,70 @@ module Mbeditor
|
|
|
60
79
|
<div class="mbeditor-loading-text">Loading editor…</div>
|
|
61
80
|
</div>
|
|
62
81
|
</div>
|
|
63
|
-
<script defer src="#{base}/assets/react.min.js"></script>
|
|
64
|
-
<script defer src="#{base}/assets/react-dom.min.js"></script>
|
|
65
|
-
<script defer src="#{base}/assets/axios.min.js"></script>
|
|
66
|
-
<script defer src="#{base}/assets/lodash.min.js"></script>
|
|
67
|
-
<script defer src="#{base}/assets/minisearch.min.js"></script>
|
|
68
|
-
<script defer src="#{base}/assets/marked.min.js"></script>
|
|
69
|
-
<script defer src="#{base}/assets/emmet.js"></script>
|
|
70
|
-
<script defer src="#{base}/assets/monaco-themes-bundle.js"></script>
|
|
71
82
|
<script>
|
|
72
83
|
window.MonacoEnvironment = {
|
|
73
84
|
getWorkerUrl: function(workerId, label) {
|
|
74
|
-
|
|
75
|
-
return '
|
|
85
|
+
var b = window.MBEDITOR_BASE_PATH || '';
|
|
86
|
+
if (label === 'typescript' || label === 'javascript') return b + '/ts_worker.js';
|
|
87
|
+
return b + '/monaco_worker.js';
|
|
76
88
|
}
|
|
77
89
|
};
|
|
78
|
-
var require = { paths: { vs: '#{base}/monaco-editor/vs', 'monaco-editor/esm/vs': '#{base}/monaco-editor/vs', 'monaco-vim': '#{base}/assets/monaco-vim' } };
|
|
79
|
-
</script>
|
|
80
|
-
<script src="#{base}/monaco-editor/vs/loader.js"></script>
|
|
81
|
-
<script>
|
|
82
90
|
(function() {
|
|
83
|
-
var prettierScripts = [
|
|
84
|
-
'#{base}/assets/prettier-standalone.js',
|
|
85
|
-
'#{base}/assets/prettier-plugin-babel.js',
|
|
86
|
-
'#{base}/assets/prettier-plugin-estree.js',
|
|
87
|
-
'#{base}/assets/prettier-plugin-html.js',
|
|
88
|
-
'#{base}/assets/prettier-plugin-postcss.js',
|
|
89
|
-
'#{base}/assets/prettier-plugin-markdown.js'
|
|
90
|
-
];
|
|
91
91
|
var _define = window.define;
|
|
92
92
|
window.define = undefined;
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
window.define =
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
Object.keys(window.MBEDITOR_CUSTOM_THEMES).forEach(function(id) {
|
|
103
|
-
window.monaco.editor.defineTheme(id, window.MBEDITOR_CUSTOM_THEMES[id]);
|
|
104
|
-
});
|
|
105
|
-
}
|
|
93
|
+
|
|
94
|
+
var prettierScripts = #{prettier_scripts};
|
|
95
|
+
window.loadPrettierPlugins = function() {
|
|
96
|
+
if (window._prettierLoadPromise) return window._prettierLoadPromise;
|
|
97
|
+
window._prettierLoadPromise = new Promise(function(resolve, reject) {
|
|
98
|
+
var savedDefine = window.define;
|
|
99
|
+
window.define = undefined;
|
|
100
|
+
var pending = prettierScripts.length;
|
|
101
|
+
prettierScripts.forEach(function(src) {
|
|
106
102
|
var s = document.createElement('script');
|
|
107
|
-
s.src =
|
|
108
|
-
s.onload = function() {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
if (window.MbeditorApp && _R && _RD) _RD.render(_R.createElement(window.MbeditorApp), root);
|
|
112
|
-
};
|
|
113
|
-
document.body.appendChild(s);
|
|
103
|
+
s.src = src;
|
|
104
|
+
s.onload = function() { if (--pending === 0) { window.define = savedDefine; resolve(); } };
|
|
105
|
+
s.onerror = function() { window.define = savedDefine; reject(new Error('Failed to load Prettier: ' + src)); };
|
|
106
|
+
document.head.appendChild(s);
|
|
114
107
|
});
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
|
|
108
|
+
});
|
|
109
|
+
return window._prettierLoadPromise;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
function proceed() {
|
|
113
|
+
window.MbeditorRuntime = { React: window.React, ReactDOM: window.ReactDOM };
|
|
114
|
+
window.React = window._mbeditorHostReact;
|
|
115
|
+
window.ReactDOM = window._mbeditorHostReactDOM;
|
|
116
|
+
window.define = _define;
|
|
117
|
+
|
|
118
|
+
var _monacoResolve;
|
|
119
|
+
window.__monacoReady = new Promise(function(resolve) { _monacoResolve = resolve; });
|
|
120
|
+
|
|
121
|
+
var appScript = document.createElement('script');
|
|
122
|
+
appScript.src = '#{base}/assets/mbeditor/application.js';
|
|
123
|
+
appScript.onload = function() {
|
|
124
|
+
var root = document.getElementById('mbeditor-root');
|
|
125
|
+
var _R = window.MbeditorRuntime.React, _RD = window.MbeditorRuntime.ReactDOM;
|
|
126
|
+
if (window.MbeditorApp && _R && _RD) _RD.render(_R.createElement(window.MbeditorApp), root);
|
|
127
|
+
};
|
|
128
|
+
appScript.onerror = function() {
|
|
129
|
+
document.getElementById('mbeditor-root').innerHTML =
|
|
130
|
+
'<div style="padding:2rem;font-family:sans-serif;color:#c00">Editor failed to load. Please refresh the page.</div>';
|
|
131
|
+
};
|
|
132
|
+
document.body.appendChild(appScript);
|
|
133
|
+
|
|
134
|
+
require(['vs/editor/editor.main'], function() {
|
|
135
|
+
if (window.MBEDITOR_CUSTOM_THEMES && window.monaco) {
|
|
136
|
+
Object.keys(window.MBEDITOR_CUSTOM_THEMES).forEach(function(id) {
|
|
137
|
+
window.monaco.editor.defineTheme(id, window.MBEDITOR_CUSTOM_THEMES[id]);
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
_monacoResolve();
|
|
141
|
+
});
|
|
118
142
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
s.onload = function() { if (--pending === 0) onAllPrettierLoaded(); };
|
|
123
|
-
document.head.appendChild(s);
|
|
124
|
-
});
|
|
143
|
+
|
|
144
|
+
if (window._mbeditorDOMReady) proceed();
|
|
145
|
+
else document.addEventListener('DOMContentLoaded', proceed, { once: true });
|
|
125
146
|
})();
|
|
126
147
|
</script>
|
|
127
148
|
</body>
|
data/lib/mbeditor/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: mbeditor
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.5.
|
|
4
|
+
version: 0.5.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Oliver Noonan
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-04
|
|
11
|
+
date: 2026-05-04 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|