mbeditor 0.5.3 → 0.7.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 +77 -0
- data/README.md +7 -0
- data/app/assets/javascripts/mbeditor/application.js +3 -0
- data/app/assets/javascripts/mbeditor/components/ChangelogView.js +145 -0
- data/app/assets/javascripts/mbeditor/components/DiffViewer.js +1 -1
- data/app/assets/javascripts/mbeditor/components/EditorPanel.js +359 -31
- data/app/assets/javascripts/mbeditor/components/FileTree.js +177 -116
- data/app/assets/javascripts/mbeditor/components/MbeditorApp.js +952 -143
- data/app/assets/javascripts/mbeditor/components/TabBar.js +9 -0
- data/app/assets/javascripts/mbeditor/conflict_parser.js +48 -0
- data/app/assets/javascripts/mbeditor/editor_plugins.js +420 -67
- data/app/assets/javascripts/mbeditor/editor_store.js +1 -0
- data/app/assets/javascripts/mbeditor/file_service.js +34 -6
- data/app/assets/javascripts/mbeditor/git_service.js +2 -1
- data/app/assets/javascripts/mbeditor/history_service.js +177 -0
- data/app/assets/javascripts/mbeditor/search_service.js +1 -0
- data/app/assets/javascripts/mbeditor/tab_manager.js +8 -5
- data/app/assets/stylesheets/mbeditor/application.css +112 -0
- data/app/assets/stylesheets/mbeditor/editor.css +443 -78
- data/app/channels/mbeditor/editor_channel.rb +5 -41
- data/app/controllers/mbeditor/application_controller.rb +8 -1
- data/app/controllers/mbeditor/editors_controller.rb +276 -654
- data/app/controllers/mbeditor/git_controller.rb +2 -61
- data/app/services/mbeditor/availability_probe.rb +83 -0
- data/app/services/mbeditor/code_search_service.rb +42 -0
- data/app/services/mbeditor/editor_state_service.rb +91 -0
- data/app/services/mbeditor/exclusion_matcher.rb +23 -0
- data/app/services/mbeditor/file_operation_service.rb +68 -0
- data/app/services/mbeditor/file_tree_service.rb +69 -0
- data/app/services/mbeditor/git_combined_diff_service.rb +43 -0
- data/app/services/mbeditor/git_commit_detail_service.rb +46 -0
- data/app/services/mbeditor/git_info_service.rb +151 -0
- data/app/services/mbeditor/git_service.rb +36 -26
- data/app/services/mbeditor/js_definition_service.rb +59 -0
- data/app/services/mbeditor/js_members_service.rb +62 -0
- data/app/services/mbeditor/process_runner.rb +48 -0
- data/app/services/mbeditor/rails_related_files_service.rb +282 -0
- data/app/services/mbeditor/ruby_definition_service.rb +77 -101
- data/app/services/mbeditor/schema_service.rb +270 -0
- data/app/services/mbeditor/search_replace_service.rb +184 -0
- data/app/services/mbeditor/test_runner_service.rb +5 -27
- data/app/views/layouts/mbeditor/application.html.erb +2 -2
- data/config/routes.rb +8 -1
- data/lib/mbeditor/configuration.rb +4 -2
- data/lib/mbeditor/version.rb +1 -1
- data/public/monaco-editor/vs/language/css/cssMode.js +13 -0
- data/public/monaco-editor/vs/language/css/cssWorker.js +77 -0
- data/public/monaco-editor/vs/language/html/htmlMode.js +13 -0
- data/public/monaco-editor/vs/language/html/htmlWorker.js +454 -0
- data/public/monaco-editor/vs/language/json/jsonMode.js +19 -0
- data/public/monaco-editor/vs/language/json/jsonWorker.js +42 -0
- metadata +26 -3
- data/app/services/mbeditor/unused_methods_service.rb +0 -139
|
@@ -157,6 +157,15 @@ var TabBar = function TabBar(_ref) {
|
|
|
157
157
|
if (isSpecial) return;
|
|
158
158
|
e.preventDefault();
|
|
159
159
|
setTabContextMenu({ x: e.clientX, y: e.clientY, tab: tab });
|
|
160
|
+
},
|
|
161
|
+
onMouseDown: function (e) {
|
|
162
|
+
if (e.button === 1) e.preventDefault();
|
|
163
|
+
},
|
|
164
|
+
onAuxClick: function (e) {
|
|
165
|
+
if (e.button === 1) {
|
|
166
|
+
e.preventDefault();
|
|
167
|
+
onClose(tab.id);
|
|
168
|
+
}
|
|
160
169
|
}
|
|
161
170
|
},
|
|
162
171
|
React.createElement('i', { className: 'tab-item-icon ' + (tab.isSettings ? 'fas fa-cog' : (window.getFileIcon ? window.getFileIcon(tab.name) : 'far fa-file-code')) }),
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
var ConflictParser = (function () {
|
|
2
|
+
function hasConflicts(content) {
|
|
3
|
+
return /^<<<<<<< /m.test(content);
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
function parse(content) {
|
|
7
|
+
var blocks = [];
|
|
8
|
+
var lines = content.split('\n');
|
|
9
|
+
var state = null;
|
|
10
|
+
var headStart = -1, headEnd = -1, dividerLine = -1;
|
|
11
|
+
var headLines = [], incomingLines = [];
|
|
12
|
+
var marker = null;
|
|
13
|
+
|
|
14
|
+
for (var i = 0; i < lines.length; i++) {
|
|
15
|
+
var line = lines[i];
|
|
16
|
+
if (/^<<<<<<< /.test(line) && state === null) {
|
|
17
|
+
state = 'head';
|
|
18
|
+
headStart = i;
|
|
19
|
+
headLines = [];
|
|
20
|
+
marker = line;
|
|
21
|
+
} else if (/^=======\s*$/.test(line) && state === 'head') {
|
|
22
|
+
state = 'incoming';
|
|
23
|
+
headEnd = i;
|
|
24
|
+
dividerLine = i;
|
|
25
|
+
incomingLines = [];
|
|
26
|
+
} else if (/^>>>>>>> /.test(line) && state === 'incoming') {
|
|
27
|
+
blocks.push({
|
|
28
|
+
startLine: headStart,
|
|
29
|
+
headEnd: headEnd,
|
|
30
|
+
dividerLine: dividerLine,
|
|
31
|
+
endLine: i,
|
|
32
|
+
headContent: headLines.join('\n'),
|
|
33
|
+
incomingContent: incomingLines.join('\n'),
|
|
34
|
+
marker: marker,
|
|
35
|
+
endMarker: line
|
|
36
|
+
});
|
|
37
|
+
state = null;
|
|
38
|
+
} else if (state === 'head') {
|
|
39
|
+
headLines.push(line);
|
|
40
|
+
} else if (state === 'incoming') {
|
|
41
|
+
incomingLines.push(line);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return blocks;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return { hasConflicts: hasConflicts, parse: parse };
|
|
48
|
+
})();
|
|
@@ -51,6 +51,13 @@
|
|
|
51
51
|
|
|
52
52
|
var globalsRegistered = false;
|
|
53
53
|
|
|
54
|
+
// JS global discovery: populated as definitions are found via hover/goto/auto-resolve.
|
|
55
|
+
// Persists for the page lifetime so each symbol is only declared once.
|
|
56
|
+
var discoveredJsGlobals = {};
|
|
57
|
+
var attemptedJsGlobals = {}; // symbols already looked up (found OR not found)
|
|
58
|
+
var jsHoverCache = {};
|
|
59
|
+
var jsMembersCache = {};
|
|
60
|
+
|
|
54
61
|
// Enumerate window for user-defined globals and return a TypeScript declaration string.
|
|
55
62
|
// Sprockets exposes every top-level var/function as a window property before Monaco
|
|
56
63
|
// initialises, so scanning at registration time captures all components and helpers.
|
|
@@ -60,7 +67,7 @@
|
|
|
60
67
|
// this reliably separates them from user-assigned globals without a native-code test
|
|
61
68
|
// (which only works for functions, not objects like `document` or `location`).
|
|
62
69
|
function buildWindowGlobalsShim() {
|
|
63
|
-
var alreadyDeclared = { React: 1, ReactDOM: 1, PropTypes: 1, MaterialUI: 1, $: 1, jQuery: 1 };
|
|
70
|
+
var alreadyDeclared = { React: 1, ReactDOM: 1, PropTypes: 1, MaterialUI: 1, $: 1, jQuery: 1, JSX: 1 };
|
|
64
71
|
var lines = [];
|
|
65
72
|
try {
|
|
66
73
|
var keys = Object.keys(window);
|
|
@@ -77,6 +84,64 @@
|
|
|
77
84
|
return lines.join('\n');
|
|
78
85
|
}
|
|
79
86
|
|
|
87
|
+
// Return true if sym is a user-assigned window property (not a browser built-in).
|
|
88
|
+
// Uses the same property-descriptor filter as buildWindowGlobalsShim: browser built-ins
|
|
89
|
+
// are either non-configurable or accessor properties (have a getter), so plain writable
|
|
90
|
+
// configurable data properties reliably identify user-assigned globals.
|
|
91
|
+
function isRuntimeWindowGlobal(sym) {
|
|
92
|
+
if (!sym || typeof window === 'undefined') return false;
|
|
93
|
+
try {
|
|
94
|
+
if (!Object.prototype.hasOwnProperty.call(window, sym)) return false;
|
|
95
|
+
var val;
|
|
96
|
+
try { val = window[sym]; } catch (e) { return false; }
|
|
97
|
+
if (val === null || val === undefined) return false;
|
|
98
|
+
var desc = Object.getOwnPropertyDescriptor(window, sym);
|
|
99
|
+
if (!desc) return false;
|
|
100
|
+
return desc.configurable === true && desc.writable === true && !desc.get;
|
|
101
|
+
} catch (e) { return false; }
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Globals already declared in the React mini-UMD — never add these to
|
|
105
|
+
// discovered-globals.d.ts or TypeScript will see a duplicate identifier.
|
|
106
|
+
var REACT_MINI_UMD_GLOBALS = { React: 1, ReactDOM: 1, PropTypes: 1, MaterialUI: 1, $: 1, jQuery: 1, JSX: 1 };
|
|
107
|
+
|
|
108
|
+
// Declare a discovered global in Monaco's extra libs so the TS2304 warning disappears.
|
|
109
|
+
// Calling addExtraLib with the same URI replaces the previous content in-place.
|
|
110
|
+
function addDiscoveredGlobal(name) {
|
|
111
|
+
if (discoveredJsGlobals[name]) return;
|
|
112
|
+
if (REACT_MINI_UMD_GLOBALS[name]) return; // already in the mini-UMD
|
|
113
|
+
discoveredJsGlobals[name] = true;
|
|
114
|
+
var mts = window.monaco && window.monaco.languages && window.monaco.languages.typescript;
|
|
115
|
+
if (!mts) return;
|
|
116
|
+
var decls = Object.keys(discoveredJsGlobals)
|
|
117
|
+
.map(function(k) { return 'declare var ' + k + ': any;'; }).join('\n');
|
|
118
|
+
mts.javascriptDefaults.addExtraLib(decls, 'inmemory://mbeditor/discovered-globals.d.ts');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Navigate to the first workspace definition of a JS symbol.
|
|
122
|
+
// Returns a Promise<boolean> — true if a definition was found and opened.
|
|
123
|
+
function navigateToJsWord(editor, word) {
|
|
124
|
+
if (typeof FileService === 'undefined' || !FileService.getJsDefinition) return Promise.resolve(false);
|
|
125
|
+
var currentPath = editor.getModel && editor.getModel() && editor.getModel()._mbeditorPath;
|
|
126
|
+
return FileService.getJsDefinition(word)
|
|
127
|
+
.then(function(data) {
|
|
128
|
+
var results = data && data.results;
|
|
129
|
+
if (!results || !results.length) {
|
|
130
|
+
if (isRuntimeWindowGlobal(word)) addDiscoveredGlobal(word);
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
var r = results[0];
|
|
134
|
+
// Only declare as a global when the definition lives in a different file.
|
|
135
|
+
// Locally-defined functions/classes must not get a duplicate declare var.
|
|
136
|
+
if (r.file !== currentPath) addDiscoveredGlobal(word);
|
|
137
|
+
if (typeof TabManager !== 'undefined' && TabManager.openTab) {
|
|
138
|
+
TabManager.openTab(r.file, r.file.split('/').pop(), r.line);
|
|
139
|
+
}
|
|
140
|
+
return true;
|
|
141
|
+
})
|
|
142
|
+
.catch(function() { return false; });
|
|
143
|
+
}
|
|
144
|
+
|
|
80
145
|
function leadingWhitespace(line) {
|
|
81
146
|
var match = line.match(/^\s*/);
|
|
82
147
|
return match ? match[0] : '';
|
|
@@ -231,6 +296,8 @@
|
|
|
231
296
|
var emmetTabDisposable = null;
|
|
232
297
|
var gotoMouseDisposable = null;
|
|
233
298
|
var gotoActionDisposable = null;
|
|
299
|
+
var jsGotoMouseDisposable = null;
|
|
300
|
+
var jsGotoActionDisposable = null;
|
|
234
301
|
|
|
235
302
|
// Emmet Tab expansion — active for markup and stylesheet languages
|
|
236
303
|
var EMMET_MARKUP_LANGS = { html: true, xml: true, erb: true, 'html.erb': true, haml: true };
|
|
@@ -383,43 +450,39 @@
|
|
|
383
450
|
});
|
|
384
451
|
}
|
|
385
452
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
var
|
|
395
|
-
if (!
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
if (defIdx < 0) return null;
|
|
402
|
-
var nameCol = defIdx + 5; // 1-based column of method name
|
|
403
|
-
return {
|
|
404
|
-
range: new monaco.Range(m.line, nameCol, m.line, nameCol + m.name.length),
|
|
405
|
-
options: {
|
|
406
|
-
inlineClassName: 'mbeditor-unused-def',
|
|
407
|
-
stickiness: monaco.editor.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
|
|
408
|
-
hoverMessage: { value: '**Unused method** — `' + m.name + '` is never called in this application.' }
|
|
409
|
-
}
|
|
410
|
-
};
|
|
411
|
-
}).filter(Boolean);
|
|
412
|
-
unusedDecIds = editor.deltaDecorations(unusedDecIds, newDecs);
|
|
413
|
-
}).catch(function() {});
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
// Initial check after a short delay (server cache may not be warm yet).
|
|
417
|
-
unusedTimer = setTimeout(refreshUnused, 3000);
|
|
453
|
+
if (language === 'javascript') {
|
|
454
|
+
// Ctrl/Cmd+click — look up workspace definition; fall back to Monaco's built-in.
|
|
455
|
+
jsGotoMouseDisposable = editor.onMouseDown(function(event) {
|
|
456
|
+
var ctrlOrCmd = event.event.ctrlKey || event.event.metaKey;
|
|
457
|
+
if (!ctrlOrCmd) return;
|
|
458
|
+
if (!event.target || event.target.type !== 6) return;
|
|
459
|
+
var position = event.target.position;
|
|
460
|
+
if (!position) return;
|
|
461
|
+
var wordInfo = model.getWordAtPosition(position);
|
|
462
|
+
if (!wordInfo || !wordInfo.word || wordInfo.word.length < 2) return;
|
|
463
|
+
event.event.preventDefault();
|
|
464
|
+
navigateToJsWord(editor, wordInfo.word).then(function(found) {
|
|
465
|
+
if (!found) editor.trigger('', 'editor.action.revealDefinition', null);
|
|
466
|
+
});
|
|
467
|
+
});
|
|
418
468
|
|
|
419
|
-
//
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
469
|
+
// F12 — go to JS definition from keyboard
|
|
470
|
+
jsGotoActionDisposable = editor.addAction({
|
|
471
|
+
id: 'mbeditor.gotoJsDefinition',
|
|
472
|
+
label: 'Go to JS Definition',
|
|
473
|
+
keybindings: [window.monaco.KeyCode.F12],
|
|
474
|
+
precondition: 'editorLangId == javascript',
|
|
475
|
+
contextMenuGroupId: 'navigation',
|
|
476
|
+
contextMenuOrder: 1.5,
|
|
477
|
+
run: function(ed) {
|
|
478
|
+
var pos = ed.getPosition();
|
|
479
|
+
if (!pos) return;
|
|
480
|
+
var wordInfo = model.getWordAtPosition(pos);
|
|
481
|
+
if (!wordInfo || !wordInfo.word || wordInfo.word.length < 2) return;
|
|
482
|
+
navigateToJsWord(ed, wordInfo.word).then(function(found) {
|
|
483
|
+
if (!found) ed.trigger('', 'editor.action.revealDefinition', null);
|
|
484
|
+
});
|
|
485
|
+
}
|
|
423
486
|
});
|
|
424
487
|
}
|
|
425
488
|
|
|
@@ -453,10 +516,9 @@
|
|
|
453
516
|
if (emmetTabDisposable) emmetTabDisposable.dispose();
|
|
454
517
|
if (gotoMouseDisposable) gotoMouseDisposable.dispose();
|
|
455
518
|
if (gotoActionDisposable) gotoActionDisposable.dispose();
|
|
519
|
+
if (jsGotoMouseDisposable) jsGotoMouseDisposable.dispose();
|
|
520
|
+
if (jsGotoActionDisposable) jsGotoActionDisposable.dispose();
|
|
456
521
|
contentDisposable.dispose();
|
|
457
|
-
if (unusedSaveDisposable) unusedSaveDisposable.dispose();
|
|
458
|
-
clearTimeout(unusedTimer);
|
|
459
|
-
if (unusedDecIds.length > 0) { editor.deltaDecorations(unusedDecIds, []); }
|
|
460
522
|
}
|
|
461
523
|
};
|
|
462
524
|
}
|
|
@@ -485,6 +547,105 @@
|
|
|
485
547
|
});
|
|
486
548
|
}
|
|
487
549
|
|
|
550
|
+
// ── React mini-UMD type declarations ────────────────────────────────────
|
|
551
|
+
// A self-contained React + JSX type stub vendored locally so Monaco's
|
|
552
|
+
// TypeScript language service has enough information to:
|
|
553
|
+
// • complete React hooks and lifecycle methods
|
|
554
|
+
// • resolve JSX element types (no "Cannot find name" for <div /> etc.)
|
|
555
|
+
// • navigate to component definitions on hover / Ctrl+Click
|
|
556
|
+
// This replaces the bare `declare var React: any` that previously gave
|
|
557
|
+
// Monaco no useful type information.
|
|
558
|
+
var REACT_MINI_UMD_DTS = [
|
|
559
|
+
'// React mini-UMD — mbeditor local type stub',
|
|
560
|
+
'declare namespace React {',
|
|
561
|
+
' type Key = string | number;',
|
|
562
|
+
' type ReactText = string | number;',
|
|
563
|
+
' type ReactNode = ReactElement | ReactText | boolean | null | undefined | ReactNodeArray;',
|
|
564
|
+
' interface ReactNodeArray extends Array<ReactNode> {}',
|
|
565
|
+
' interface ReactElement<P = any> { type: any; props: P; key: Key | null; }',
|
|
566
|
+
' interface RefObject<T> { readonly current: T | null; }',
|
|
567
|
+
' type Ref<T> = RefObject<T> | ((instance: T | null) => void) | null;',
|
|
568
|
+
' interface MutableRefObject<T> { current: T; }',
|
|
569
|
+
' type FC<P = {}> = (props: P & { children?: ReactNode; key?: Key }) => ReactElement | null;',
|
|
570
|
+
' type FunctionComponent<P = {}> = FC<P>;',
|
|
571
|
+
' type ComponentType<P = {}> = FC<P>;',
|
|
572
|
+
' type DependencyList = ReadonlyArray<any>;',
|
|
573
|
+
' type EffectCallback = () => (void | (() => void | undefined));',
|
|
574
|
+
' type SetStateAction<S> = S | ((prevState: S) => S);',
|
|
575
|
+
' type Dispatch<A> = (value: A) => void;',
|
|
576
|
+
' type Reducer<S, A> = (prevState: S, action: A) => S;',
|
|
577
|
+
' interface Context<T> { Provider: FC<{ value: T; children?: ReactNode }>; Consumer: any; displayName?: string; }',
|
|
578
|
+
' class Component<P = {}, S = {}> {',
|
|
579
|
+
' constructor(props: Readonly<P>);',
|
|
580
|
+
' props: Readonly<P>;',
|
|
581
|
+
' state: Readonly<S>;',
|
|
582
|
+
' setState(state: SetStateAction<S>, cb?: () => void): void;',
|
|
583
|
+
' forceUpdate(cb?: () => void): void;',
|
|
584
|
+
' render(): ReactNode;',
|
|
585
|
+
' componentDidMount?(): void;',
|
|
586
|
+
' componentDidUpdate?(prevProps: Readonly<P>, prevState: Readonly<S>): void;',
|
|
587
|
+
' componentWillUnmount?(): void;',
|
|
588
|
+
' }',
|
|
589
|
+
' class PureComponent<P = {}, S = {}> extends Component<P, S> {}',
|
|
590
|
+
' function createElement(type: any, props?: any, ...children: any[]): ReactElement;',
|
|
591
|
+
' function cloneElement(element: ReactElement, props?: any, ...children: any[]): ReactElement;',
|
|
592
|
+
' function isValidElement(object: any): object is ReactElement;',
|
|
593
|
+
' function createContext<T>(defaultValue: T): Context<T>;',
|
|
594
|
+
' function forwardRef<T, P = {}>(render: (props: P, ref: Ref<T>) => ReactElement | null): FC<P & { ref?: Ref<T> }>;',
|
|
595
|
+
' function memo<P>(comp: FC<P>, compare?: (a: P, b: P) => boolean): FC<P>;',
|
|
596
|
+
' function lazy<T extends ComponentType<any>>(factory: () => Promise<{ default: T }>): T;',
|
|
597
|
+
' function useState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>];',
|
|
598
|
+
' function useEffect(effect: EffectCallback, deps?: DependencyList): void;',
|
|
599
|
+
' function useLayoutEffect(effect: EffectCallback, deps?: DependencyList): void;',
|
|
600
|
+
' function useRef<T>(initialValue: T): MutableRefObject<T>;',
|
|
601
|
+
' function useRef<T>(initialValue: T | null): RefObject<T>;',
|
|
602
|
+
' function useRef<T = undefined>(): MutableRefObject<T | undefined>;',
|
|
603
|
+
' function useMemo<T>(factory: () => T, deps: DependencyList | undefined): T;',
|
|
604
|
+
' function useCallback<T extends (...args: any[]) => any>(callback: T, deps: DependencyList): T;',
|
|
605
|
+
' function useContext<T>(context: Context<T>): T;',
|
|
606
|
+
' function useReducer<S, A>(reducer: Reducer<S, A>, initialState: S): [S, Dispatch<A>];',
|
|
607
|
+
' function useImperativeHandle<T>(ref: Ref<T>, init: () => T, deps?: DependencyList): void;',
|
|
608
|
+
' function useDebugValue<T>(value: T, format?: (value: T) => any): void;',
|
|
609
|
+
' function useId(): string;',
|
|
610
|
+
' const Fragment: any;',
|
|
611
|
+
' const StrictMode: any;',
|
|
612
|
+
' const Suspense: FC<{ fallback?: ReactNode; children?: ReactNode }>;',
|
|
613
|
+
' const Children: { map<T,C>(children: any, fn: (child: C, index: number) => T): T[]; forEach(children: any, fn: (child: any, index: number) => void): void; count(children: any): number; toArray(children: any): any[]; only(children: any): ReactElement; };',
|
|
614
|
+
' const version: string;',
|
|
615
|
+
'}',
|
|
616
|
+
'',
|
|
617
|
+
'// Allow `var React = window.React;` in host-app files without TS2300.',
|
|
618
|
+
'// Using a namespace+var combo lets Monaco see the namespace members while',
|
|
619
|
+
'// still accepting the runtime assignment pattern common in Sprockets apps.',
|
|
620
|
+
'declare var React: typeof React;',
|
|
621
|
+
'',
|
|
622
|
+
'// ReactDOM global',
|
|
623
|
+
'declare var ReactDOM: {',
|
|
624
|
+
' render(element: React.ReactElement, container: Element | null, cb?: () => void): any;',
|
|
625
|
+
' unmountComponentAtNode(container: Element): boolean;',
|
|
626
|
+
' createPortal(children: React.ReactNode, container: Element): React.ReactElement;',
|
|
627
|
+
' findDOMNode(instance: any): Element | null;',
|
|
628
|
+
'};',
|
|
629
|
+
'',
|
|
630
|
+
'// JSX intrinsic elements — wildcard so any HTML tag is accepted.',
|
|
631
|
+
'// Without this, TypeScript reports TS2339 / TS7026 for every <div> etc.',
|
|
632
|
+
'declare namespace JSX {',
|
|
633
|
+
' interface Element extends React.ReactElement<any> {}',
|
|
634
|
+
' interface ElementClass { render(): React.ReactNode; }',
|
|
635
|
+
' interface ElementAttributesProperty { props: {}; }',
|
|
636
|
+
' interface ElementChildrenAttribute { children: {}; }',
|
|
637
|
+
' type LibraryManagedAttributes<C, P> = P;',
|
|
638
|
+
' interface IntrinsicElements { [elem: string]: any; }',
|
|
639
|
+
'}',
|
|
640
|
+
'',
|
|
641
|
+
'// Other common Sprockets globals',
|
|
642
|
+
'declare var PropTypes: any;',
|
|
643
|
+
'declare var MaterialUI: any;',
|
|
644
|
+
'declare var $: any;',
|
|
645
|
+
'declare var jQuery: any;',
|
|
646
|
+
'interface Window { [key: string]: any; }'
|
|
647
|
+
].join('\n');
|
|
648
|
+
|
|
488
649
|
// Declare globals that are injected at runtime so checkJs doesn't flag them
|
|
489
650
|
// as undefined. The buildWindowGlobalsShim() function automatically detects
|
|
490
651
|
// window globals from the host application. Common Sprockets globals
|
|
@@ -492,16 +653,8 @@
|
|
|
492
653
|
// not auto-detected, add `/* global MyComponent */` at the top of the file.
|
|
493
654
|
if (monaco.languages.typescript && monaco.languages.typescript.javascriptDefaults) {
|
|
494
655
|
monaco.languages.typescript.javascriptDefaults.addExtraLib(
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
'declare var ReactDOM: any;',
|
|
498
|
-
'declare var PropTypes: any;',
|
|
499
|
-
'declare var MaterialUI: any;',
|
|
500
|
-
'declare var $: any;',
|
|
501
|
-
'declare var jQuery: any;',
|
|
502
|
-
'interface Window { [key: string]: any; }'
|
|
503
|
-
].join('\n'),
|
|
504
|
-
'inmemory://mbeditor/sprockets-globals.d.ts'
|
|
656
|
+
REACT_MINI_UMD_DTS,
|
|
657
|
+
'inmemory://mbeditor/react-mini-umd.d.ts'
|
|
505
658
|
);
|
|
506
659
|
|
|
507
660
|
var dynamicShim = buildWindowGlobalsShim();
|
|
@@ -517,12 +670,17 @@
|
|
|
517
670
|
// the marker set after the worker fires and re-apply with lower severity.
|
|
518
671
|
//
|
|
519
672
|
// Patch markers after the TypeScript worker fires:
|
|
673
|
+
// - JS files: downgrade TS2304 ("Cannot find name") to Warning — host-app
|
|
674
|
+
// globals injected at runtime are invisible to the language service, so
|
|
675
|
+
// hard errors are almost always false positives. Downgrading keeps the
|
|
676
|
+
// signal without blocking genuine undefined-variable detection.
|
|
520
677
|
// - Both: downgrade TS6133 ("declared but never read") from Error to Warning.
|
|
521
|
-
//
|
|
522
|
-
//
|
|
523
|
-
//
|
|
524
|
-
|
|
525
|
-
var
|
|
678
|
+
// - JS/JSX: suppress TS2300 ("Duplicate identifier") — in Sprockets apps all
|
|
679
|
+
// open files share a global script context in Monaco's TS worker, so a
|
|
680
|
+
// component defined in file_a.jsx looks like a redeclaration when file_b.jsx
|
|
681
|
+
// is also open. This is a structural false positive, not a real error.
|
|
682
|
+
var JS_SUPPRESS_CODES = { '2300': true, '2451': true };
|
|
683
|
+
var JS_WARN_CODES = { '2304': true, '6133': true };
|
|
526
684
|
var TS_WARN_CODES = { '6133': true };
|
|
527
685
|
var _severityPatchActive = false;
|
|
528
686
|
monaco.editor.onDidChangeMarkers(function(uris) {
|
|
@@ -554,6 +712,37 @@
|
|
|
554
712
|
} finally {
|
|
555
713
|
_severityPatchActive = false;
|
|
556
714
|
}
|
|
715
|
+
|
|
716
|
+
// Auto-resolve TS2304 ("Cannot find name 'X'") for JS files by
|
|
717
|
+
// looking up the symbol in the workspace. If found, addDiscoveredGlobal
|
|
718
|
+
// declares it via addExtraLib and Monaco re-validates, removing the warning.
|
|
719
|
+
if (typeof FileService !== 'undefined' && FileService.getJsDefinition) {
|
|
720
|
+
uris.forEach(function(uri) {
|
|
721
|
+
var model = monaco.editor.getModel(uri);
|
|
722
|
+
if (!model) return;
|
|
723
|
+
var markers = monaco.editor.getModelMarkers({ resource: uri, owner: 'javascript' });
|
|
724
|
+
markers.forEach(function(m) {
|
|
725
|
+
if (String(m.code) !== '2304') return;
|
|
726
|
+
// Extract symbol name from message: "Cannot find name 'ReactWindow'."
|
|
727
|
+
var match = m.message && m.message.match(/Cannot find name '([^']+)'/);
|
|
728
|
+
if (!match) return;
|
|
729
|
+
var sym = match[1];
|
|
730
|
+
if (attemptedJsGlobals[sym]) return;
|
|
731
|
+
attemptedJsGlobals[sym] = true;
|
|
732
|
+
var modelPath = model._mbeditorPath;
|
|
733
|
+
FileService.getJsDefinition(sym)
|
|
734
|
+
.then(function(data) {
|
|
735
|
+
var results = data && data.results;
|
|
736
|
+
if (results && results.length && results[0].file !== modelPath) {
|
|
737
|
+
addDiscoveredGlobal(sym);
|
|
738
|
+
} else if (!results || !results.length) {
|
|
739
|
+
if (isRuntimeWindowGlobal(sym)) addDiscoveredGlobal(sym);
|
|
740
|
+
}
|
|
741
|
+
})
|
|
742
|
+
.catch(function() {});
|
|
743
|
+
});
|
|
744
|
+
});
|
|
745
|
+
}
|
|
557
746
|
});
|
|
558
747
|
}
|
|
559
748
|
|
|
@@ -567,14 +756,6 @@
|
|
|
567
756
|
});
|
|
568
757
|
}
|
|
569
758
|
|
|
570
|
-
// CSS for greyed-out unused method names (applied via decoration inlineClassName).
|
|
571
|
-
if (!document.getElementById('mbeditor-unused-style')) {
|
|
572
|
-
var unusedStyle = document.createElement('style');
|
|
573
|
-
unusedStyle.id = 'mbeditor-unused-style';
|
|
574
|
-
unusedStyle.textContent = '.mbeditor-unused-def { opacity: 0.35; }';
|
|
575
|
-
document.head.appendChild(unusedStyle);
|
|
576
|
-
}
|
|
577
|
-
|
|
578
759
|
monaco.languages.setLanguageConfiguration('ruby', {
|
|
579
760
|
comments: { lineComment: '#', blockComment: ['=begin', '=end'] },
|
|
580
761
|
brackets: [['(', ')'], ['{', '}'], ['[', ']']],
|
|
@@ -607,8 +788,15 @@
|
|
|
607
788
|
// Single-line comments
|
|
608
789
|
[/#.*$/, 'comment'],
|
|
609
790
|
|
|
610
|
-
// Heredoc start — capture the terminator word
|
|
611
|
-
[/<<[-~]?(['"]?)(\w+)\1/, {
|
|
791
|
+
// Heredoc start — capture the terminator word; route to specialized state by delimiter name
|
|
792
|
+
[/<<[-~]?(['"]?)(\w+)\1/, {
|
|
793
|
+
cases: {
|
|
794
|
+
'$2~(?i:SQL)': { token: 'string.heredoc.delimiter', next: '@heredocSQL.$2' },
|
|
795
|
+
'$2~(?i:HTML?)': { token: 'string.heredoc.delimiter', next: '@heredocHTML.$2' },
|
|
796
|
+
'$2~(?i:JS|JAVASCRIPT)': { token: 'string.heredoc.delimiter', next: '@heredocJS.$2' },
|
|
797
|
+
'@default': { token: 'string.heredoc.delimiter', next: '@heredoc.$2' }
|
|
798
|
+
}
|
|
799
|
+
}],
|
|
612
800
|
|
|
613
801
|
// def + method name (handles self. prefix and operator method names)
|
|
614
802
|
[/(\bdef\b)(\s+)(self)(\.)([\w]+[!?=]?|[+\-*\/%<>=!\[\]&|^~]+)/,
|
|
@@ -661,8 +849,9 @@
|
|
|
661
849
|
[/%[qQ]?\[/, { token: 'string.quoted.double', next: '@percentDqBracket' }],
|
|
662
850
|
[/%[qQ]?\{/, { token: 'string.quoted.double', next: '@percentDqCurly' }],
|
|
663
851
|
|
|
664
|
-
// Regexp literals
|
|
665
|
-
|
|
852
|
+
// Regexp literals: /pat/imxo
|
|
853
|
+
// Negative lookbehind (?<![.\w]) prevents matching division operators like a/b or obj.method/n
|
|
854
|
+
[/(?<![.\w])\/(?!\s)(?:[^\/\\\n]|\\.)+\/[imxo]*/, 'string.regexp'],
|
|
666
855
|
|
|
667
856
|
// Control-flow and other keywords
|
|
668
857
|
[/\b(if|unless|while|until|for|do|case|when|then|else|elsif|end|return|yield|begin|rescue|ensure|raise|break|next|retry|and|or|not|in|__LINE__|__FILE__|__ENCODING__|__method__|__callee__|__dir__|alias|undef|defined\?)\b/, 'keyword.control'],
|
|
@@ -733,6 +922,7 @@
|
|
|
733
922
|
[/\s+/, '']
|
|
734
923
|
],
|
|
735
924
|
|
|
925
|
+
// Generic heredoc — all content is string.heredoc
|
|
736
926
|
heredoc: [
|
|
737
927
|
[/^(\w+)\s*$/, {
|
|
738
928
|
cases: {
|
|
@@ -743,6 +933,69 @@
|
|
|
743
933
|
[/.+/, 'string.heredoc']
|
|
744
934
|
],
|
|
745
935
|
|
|
936
|
+
// SQL heredoc — keyword/string/number/comment tokenization
|
|
937
|
+
heredocSQL: [
|
|
938
|
+
[/^(\w+)\s*$/, {
|
|
939
|
+
cases: {
|
|
940
|
+
'$1==$S2': { token: 'string.heredoc.delimiter', next: '@pop' },
|
|
941
|
+
'@default': { token: '@rematch', next: '@heredocSQLLine' }
|
|
942
|
+
}
|
|
943
|
+
}],
|
|
944
|
+
[/.+/, { token: '@rematch', next: '@heredocSQLLine' }]
|
|
945
|
+
],
|
|
946
|
+
|
|
947
|
+
heredocSQLLine: [
|
|
948
|
+
[/--.*$/, { token: 'comment.sql', next: '@pop' }],
|
|
949
|
+
[/'[^']*'/, 'string.sql'],
|
|
950
|
+
[/\b\d+(?:\.\d+)?\b/, 'number.sql'],
|
|
951
|
+
[/\b(?:SELECT|FROM|WHERE|INSERT|UPDATE|DELETE|JOIN|LEFT|RIGHT|INNER|OUTER|ON|GROUP|ORDER|BY|HAVING|LIMIT|OFFSET|CREATE|DROP|ALTER|TABLE|INDEX|INTO|VALUES|SET|AS|AND|OR|NOT|NULL|IS|IN|LIKE|BETWEEN|DISTINCT|COUNT|SUM|AVG|MIN|MAX)\b/i, 'keyword.sql'],
|
|
952
|
+
[/[^\s\w'"-]+/, 'string.heredoc'],
|
|
953
|
+
[/\w+/, 'string.heredoc'],
|
|
954
|
+
[/$/, { token: '', next: '@pop' }]
|
|
955
|
+
],
|
|
956
|
+
|
|
957
|
+
// HTML heredoc — tag/attribute tokenization
|
|
958
|
+
heredocHTML: [
|
|
959
|
+
[/^(\w+)\s*$/, {
|
|
960
|
+
cases: {
|
|
961
|
+
'$1==$S2': { token: 'string.heredoc.delimiter', next: '@pop' },
|
|
962
|
+
'@default': { token: '@rematch', next: '@heredocHTMLLine' }
|
|
963
|
+
}
|
|
964
|
+
}],
|
|
965
|
+
[/.+/, { token: '@rematch', next: '@heredocHTMLLine' }]
|
|
966
|
+
],
|
|
967
|
+
|
|
968
|
+
heredocHTMLLine: [
|
|
969
|
+
[/<\/?[a-zA-Z][a-zA-Z0-9]*/, 'tag.html'],
|
|
970
|
+
[/[a-zA-Z_:][a-zA-Z0-9_:\-\.]*(?=\s*=)/, 'attribute.name.html'],
|
|
971
|
+
[/\/?>/, 'tag.html'],
|
|
972
|
+
[/[^<>]+/, 'string.heredoc'],
|
|
973
|
+
[/$/, { token: '', next: '@pop' }]
|
|
974
|
+
],
|
|
975
|
+
|
|
976
|
+
// JS heredoc — keyword/string/number/comment tokenization
|
|
977
|
+
heredocJS: [
|
|
978
|
+
[/^(\w+)\s*$/, {
|
|
979
|
+
cases: {
|
|
980
|
+
'$1==$S2': { token: 'string.heredoc.delimiter', next: '@pop' },
|
|
981
|
+
'@default': { token: '@rematch', next: '@heredocJSLine' }
|
|
982
|
+
}
|
|
983
|
+
}],
|
|
984
|
+
[/.+/, { token: '@rematch', next: '@heredocJSLine' }]
|
|
985
|
+
],
|
|
986
|
+
|
|
987
|
+
heredocJSLine: [
|
|
988
|
+
[/\/\/.*$/, { token: 'comment', next: '@pop' }],
|
|
989
|
+
[/"(?:[^"\\]|\\.)*"/, 'string'],
|
|
990
|
+
[/'(?:[^'\\]|\\.)*'/, 'string'],
|
|
991
|
+
[/`(?:[^`\\]|\\.)*`/, 'string'],
|
|
992
|
+
[/\b\d+(?:\.\d+)?\b/, 'number'],
|
|
993
|
+
[/\b(?:var|let|const|function|return|if|else|for|while|do|switch|case|break|continue|new|delete|typeof|instanceof|in|of|class|extends|import|export|default|null|undefined|true|false|this|super|async|await|try|catch|finally|throw|void|yield)\b/, 'keyword'],
|
|
994
|
+
[/[^\s\w'"`;\/]+/, 'string.heredoc'],
|
|
995
|
+
[/\w+/, 'string.heredoc'],
|
|
996
|
+
[/$/, { token: '', next: '@pop' }]
|
|
997
|
+
],
|
|
998
|
+
|
|
746
999
|
// %w[] %W[] word arrays
|
|
747
1000
|
percentWordBracket: [
|
|
748
1001
|
[/\]/, { token: 'string', next: '@pop' }],
|
|
@@ -1076,6 +1329,106 @@
|
|
|
1076
1329
|
}
|
|
1077
1330
|
});
|
|
1078
1331
|
|
|
1332
|
+
// JS/JSX hover provider: looks up workspace definitions for window globals.
|
|
1333
|
+
// Fires for mixed-case identifiers and for any symbol already in discoveredJsGlobals.
|
|
1334
|
+
var JS_HOVER_CACHE_TTL_MS = 60000;
|
|
1335
|
+
monaco.languages.registerHoverProvider('javascript', {
|
|
1336
|
+
provideHover: function(model, position, token) {
|
|
1337
|
+
var wordInfo = model.getWordAtPosition(position);
|
|
1338
|
+
if (!wordInfo || !wordInfo.word || wordInfo.word.length < 2) return null;
|
|
1339
|
+
var word = wordInfo.word;
|
|
1340
|
+
if (!discoveredJsGlobals[word] && !/[A-Z]/.test(word)) return null;
|
|
1341
|
+
if (typeof FileService === 'undefined' || !FileService.getJsDefinition) return null;
|
|
1342
|
+
|
|
1343
|
+
// Build a position-specific range for this hover instance.
|
|
1344
|
+
// The range must NOT be cached because the same symbol can appear on
|
|
1345
|
+
// different lines; a stale cached lineNumber would highlight the wrong place.
|
|
1346
|
+
function makeHoverRange() {
|
|
1347
|
+
return new monaco.Range(position.lineNumber, wordInfo.startColumn, position.lineNumber, wordInfo.endColumn);
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
var cached = jsHoverCache[word];
|
|
1351
|
+
if (cached && (Date.now() - cached.ts) < JS_HOVER_CACHE_TTL_MS) {
|
|
1352
|
+
if (!cached.contents) return null;
|
|
1353
|
+
return { range: makeHoverRange(), contents: cached.contents };
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
var controller = typeof AbortController !== 'undefined' ? new AbortController() : null;
|
|
1357
|
+
if (controller && token && token.onCancellationRequested) {
|
|
1358
|
+
token.onCancellationRequested(function() { controller.abort(); });
|
|
1359
|
+
}
|
|
1360
|
+
return FileService.getJsDefinition(word, controller ? { signal: controller.signal } : {})
|
|
1361
|
+
.then(function(data) {
|
|
1362
|
+
if (token && token.isCancellationRequested) return null;
|
|
1363
|
+
var results = data && data.results;
|
|
1364
|
+
if (!results || !results.length) {
|
|
1365
|
+
if (isRuntimeWindowGlobal(word)) {
|
|
1366
|
+
addDiscoveredGlobal(word);
|
|
1367
|
+
var kind = typeof window[word];
|
|
1368
|
+
var rtContents = [{ value: '**' + word + '** — runtime global (`' + kind + '`)' }];
|
|
1369
|
+
jsHoverCache[word] = { ts: Date.now(), contents: rtContents };
|
|
1370
|
+
return { range: makeHoverRange(), contents: rtContents };
|
|
1371
|
+
}
|
|
1372
|
+
jsHoverCache[word] = { ts: Date.now(), contents: null };
|
|
1373
|
+
return null;
|
|
1374
|
+
}
|
|
1375
|
+
var r = results[0];
|
|
1376
|
+
// Only declare as global when the definition is in a different file —
|
|
1377
|
+
// locally-defined functions must not get a duplicate declare var.
|
|
1378
|
+
if (r.file !== model._mbeditorPath) addDiscoveredGlobal(word);
|
|
1379
|
+
var fileRef = r.file + ':' + r.line;
|
|
1380
|
+
var contents = [
|
|
1381
|
+
{ value: '```javascript\n' + r.snippet + '\n```', isTrusted: true },
|
|
1382
|
+
{ value: '<span style="opacity:0.55;font-size:0.9em;">' + fileRef + '</span>', isTrusted: true, supportHtml: true }
|
|
1383
|
+
];
|
|
1384
|
+
jsHoverCache[word] = { ts: Date.now(), contents: contents };
|
|
1385
|
+
return { range: makeHoverRange(), contents: contents };
|
|
1386
|
+
}).catch(function() { return null; });
|
|
1387
|
+
}
|
|
1388
|
+
});
|
|
1389
|
+
|
|
1390
|
+
// JS/JSX member completion provider: suggests properties/methods of workspace globals after '.'.
|
|
1391
|
+
// Only looks up PascalCase/mixed-case identifiers or previously discovered globals.
|
|
1392
|
+
var JS_MEMBERS_CACHE_TTL_MS = 60000;
|
|
1393
|
+
monaco.languages.registerCompletionItemProvider('javascript', {
|
|
1394
|
+
triggerCharacters: ['.'],
|
|
1395
|
+
provideCompletionItems: function(model, position) {
|
|
1396
|
+
var line = model.getLineContent(position.lineNumber);
|
|
1397
|
+
var col = position.column - 2; // index of character just before the '.'
|
|
1398
|
+
var end = col;
|
|
1399
|
+
while (col >= 0 && /[a-zA-Z0-9_$]/.test(line[col])) col--;
|
|
1400
|
+
var symbol = line.slice(col + 1, end + 1);
|
|
1401
|
+
if (!symbol || symbol.length < 2) return { suggestions: [] };
|
|
1402
|
+
if (!discoveredJsGlobals[symbol] && !/^[A-Z]/.test(symbol)) return { suggestions: [] };
|
|
1403
|
+
if (typeof FileService === 'undefined' || !FileService.getJsMembers) return { suggestions: [] };
|
|
1404
|
+
|
|
1405
|
+
var cached = jsMembersCache[symbol];
|
|
1406
|
+
if (cached && (Date.now() - cached.ts) < JS_MEMBERS_CACHE_TTL_MS) {
|
|
1407
|
+
return { suggestions: cached.suggestions };
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
return FileService.getJsMembers(symbol)
|
|
1411
|
+
.then(function(data) {
|
|
1412
|
+
var members = (data && data.members) || [];
|
|
1413
|
+
var suggestions = members.map(function(m) {
|
|
1414
|
+
return {
|
|
1415
|
+
label: m.name,
|
|
1416
|
+
kind: monaco.languages.CompletionItemKind.Method,
|
|
1417
|
+
detail: symbol,
|
|
1418
|
+
documentation: m.snippet,
|
|
1419
|
+
insertText: m.name,
|
|
1420
|
+
range: {
|
|
1421
|
+
startLineNumber: position.lineNumber, endLineNumber: position.lineNumber,
|
|
1422
|
+
startColumn: position.column, endColumn: position.column
|
|
1423
|
+
}
|
|
1424
|
+
};
|
|
1425
|
+
});
|
|
1426
|
+
jsMembersCache[symbol] = { ts: Date.now(), suggestions: suggestions };
|
|
1427
|
+
return { suggestions: suggestions };
|
|
1428
|
+
}).catch(function() { return { suggestions: [] }; });
|
|
1429
|
+
}
|
|
1430
|
+
});
|
|
1431
|
+
|
|
1079
1432
|
// Vim-style fold-marker folding provider.
|
|
1080
1433
|
// Recognises {{{ (open) and }}} (close) anywhere in a line, matching the
|
|
1081
1434
|
// convention used by vim's `foldmethod=marker`. Registered for every
|