mbeditor 0.2.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 +7 -0
- data/CHANGELOG.md +116 -0
- data/README.md +180 -0
- data/app/assets/javascripts/mbeditor/application.js +21 -0
- data/app/assets/javascripts/mbeditor/components/CodeReviewPanel.js +202 -0
- data/app/assets/javascripts/mbeditor/components/CollapsibleSection.js +71 -0
- data/app/assets/javascripts/mbeditor/components/CombinedDiffViewer.js +139 -0
- data/app/assets/javascripts/mbeditor/components/CommitGraph.js +65 -0
- data/app/assets/javascripts/mbeditor/components/DiffViewer.js +166 -0
- data/app/assets/javascripts/mbeditor/components/EditorPanel.js +1139 -0
- data/app/assets/javascripts/mbeditor/components/FileHistoryPanel.js +117 -0
- data/app/assets/javascripts/mbeditor/components/FileTree.js +339 -0
- data/app/assets/javascripts/mbeditor/components/GitPanel.js +501 -0
- data/app/assets/javascripts/mbeditor/components/MbeditorApp.js +3108 -0
- data/app/assets/javascripts/mbeditor/components/QuickOpenDialog.js +272 -0
- data/app/assets/javascripts/mbeditor/components/ShortcutHelp.js +186 -0
- data/app/assets/javascripts/mbeditor/components/TabBar.js +238 -0
- data/app/assets/javascripts/mbeditor/components/TestResultsPanel.js +150 -0
- data/app/assets/javascripts/mbeditor/editor_plugins.js +758 -0
- data/app/assets/javascripts/mbeditor/editor_store.js +69 -0
- data/app/assets/javascripts/mbeditor/file_icon.js +30 -0
- data/app/assets/javascripts/mbeditor/file_service.js +96 -0
- data/app/assets/javascripts/mbeditor/git_service.js +104 -0
- data/app/assets/javascripts/mbeditor/search_service.js +63 -0
- data/app/assets/javascripts/mbeditor/tab_manager.js +485 -0
- data/app/assets/stylesheets/mbeditor/application.css +848 -0
- data/app/assets/stylesheets/mbeditor/editor.css +2061 -0
- data/app/controllers/mbeditor/application_controller.rb +70 -0
- data/app/controllers/mbeditor/editors_controller.rb +996 -0
- data/app/controllers/mbeditor/git_controller.rb +234 -0
- data/app/services/mbeditor/git_blame_service.rb +98 -0
- data/app/services/mbeditor/git_commit_graph_service.rb +60 -0
- data/app/services/mbeditor/git_diff_service.rb +74 -0
- data/app/services/mbeditor/git_file_history_service.rb +42 -0
- data/app/services/mbeditor/git_service.rb +95 -0
- data/app/services/mbeditor/redmine_service.rb +86 -0
- data/app/services/mbeditor/ruby_definition_service.rb +168 -0
- data/app/services/mbeditor/test_runner_service.rb +286 -0
- data/app/views/layouts/mbeditor/application.html.erb +120 -0
- data/app/views/mbeditor/editors/index.html.erb +1 -0
- data/config/initializers/assets.rb +9 -0
- data/config/routes.rb +44 -0
- data/lib/mbeditor/configuration.rb +22 -0
- data/lib/mbeditor/engine.rb +37 -0
- data/lib/mbeditor/rack/silence_ping_request.rb +56 -0
- data/lib/mbeditor/version.rb +3 -0
- data/lib/mbeditor.rb +19 -0
- data/mbeditor.gemspec +31 -0
- data/public/mbeditor-icon.svg +4 -0
- data/public/min-maps/vs/base/worker/workerMain.js.map +1 -0
- data/public/monaco-editor/vs/base/browser/ui/codicons/codicon/codicon.ttf +0 -0
- data/public/monaco-editor/vs/base/worker/workerMain.js +31 -0
- data/public/monaco-editor/vs/basic-languages/cameligo/cameligo.js +10 -0
- data/public/monaco-editor/vs/basic-languages/css/css.js +12 -0
- data/public/monaco-editor/vs/basic-languages/dart/dart.js +10 -0
- data/public/monaco-editor/vs/basic-languages/flow9/flow9.js +10 -0
- data/public/monaco-editor/vs/basic-languages/go/go.js +10 -0
- data/public/monaco-editor/vs/basic-languages/handlebars/handlebars.js +440 -0
- data/public/monaco-editor/vs/basic-languages/javascript/javascript.js +10 -0
- data/public/monaco-editor/vs/basic-languages/markdown/markdown.js +10 -0
- data/public/monaco-editor/vs/basic-languages/msdax/msdax.js +10 -0
- data/public/monaco-editor/vs/basic-languages/postiats/postiats.js +10 -0
- data/public/monaco-editor/vs/basic-languages/pug/pug.js +412 -0
- data/public/monaco-editor/vs/basic-languages/restructuredtext/restructuredtext.js +10 -0
- data/public/monaco-editor/vs/basic-languages/ruby/ruby.js +10 -0
- data/public/monaco-editor/vs/basic-languages/sb/sb.js +10 -0
- data/public/monaco-editor/vs/basic-languages/shell/shell.js +41 -0
- data/public/monaco-editor/vs/basic-languages/typescript/typescript.js +10 -0
- data/public/monaco-editor/vs/basic-languages/typespec/typespec.js +10 -0
- data/public/monaco-editor/vs/basic-languages/yaml/yaml.js +10 -0
- data/public/monaco-editor/vs/editor/editor.api.js +6 -0
- data/public/monaco-editor/vs/editor/editor.main.css +8 -0
- data/public/monaco-editor/vs/editor/editor.main.js +797 -0
- data/public/monaco-editor/vs/language/typescript/tsMode.js +20 -0
- data/public/monaco-editor/vs/language/typescript/tsWorker.js +51328 -0
- data/public/monaco-editor/vs/loader.js +10 -0
- data/public/monaco-editor/vs/nls.messages.de.js +20 -0
- data/public/monaco-editor/vs/nls.messages.es.js +20 -0
- data/public/monaco-editor/vs/nls.messages.fr.js +18 -0
- data/public/monaco-editor/vs/nls.messages.it.js +18 -0
- data/public/monaco-editor/vs/nls.messages.ja.js +20 -0
- data/public/monaco-editor/vs/nls.messages.ko.js +18 -0
- data/public/monaco-editor/vs/nls.messages.ru.js +20 -0
- data/public/monaco-editor/vs/nls.messages.zh-cn.js +20 -0
- data/public/monaco-editor/vs/nls.messages.zh-tw.js +18 -0
- data/public/monaco_worker.js +5 -0
- data/public/sw.js +8 -0
- data/public/ts_worker.js +5 -0
- data/vendor/assets/javascripts/axios.min.js +5 -0
- data/vendor/assets/javascripts/emmet.js +5452 -0
- data/vendor/assets/javascripts/lodash.min.js +136 -0
- data/vendor/assets/javascripts/marked.min.js +6 -0
- data/vendor/assets/javascripts/minisearch.min.js +2044 -0
- data/vendor/assets/javascripts/monaco-themes-bundle.js +10 -0
- data/vendor/assets/javascripts/monaco-vim.js +9867 -0
- data/vendor/assets/javascripts/prettier-plugin-babel.js +16 -0
- data/vendor/assets/javascripts/prettier-plugin-estree.js +35 -0
- data/vendor/assets/javascripts/prettier-plugin-html.js +19 -0
- data/vendor/assets/javascripts/prettier-plugin-markdown.js +59 -0
- data/vendor/assets/javascripts/prettier-plugin-postcss.js +52 -0
- data/vendor/assets/javascripts/prettier-standalone.js +37 -0
- data/vendor/assets/javascripts/react-dom.min.js +267 -0
- data/vendor/assets/javascripts/react.min.js +31 -0
- data/vendor/assets/stylesheets/fontawesome.min.css.erb +9 -0
- data/vendor/assets/webfonts/fa-brands-400.woff2 +0 -0
- data/vendor/assets/webfonts/fa-regular-400.woff2 +0 -0
- data/vendor/assets/webfonts/fa-solid-900.woff2 +0 -0
- metadata +188 -0
|
@@ -0,0 +1,758 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
(function () {
|
|
4
|
+
var RUBY_BLOCK_START = /^\s*(def|class|module|if|unless|case|while|until|for|begin)\b.*$/;
|
|
5
|
+
var RUBY_DO_BLOCK_START = /\bdo(\s*\|.*\|)?\s*$/;
|
|
6
|
+
var RUBY_END_LINE = /^end\b/;
|
|
7
|
+
var VOID_HTML_ELEMENTS = {
|
|
8
|
+
area: true,
|
|
9
|
+
base: true,
|
|
10
|
+
br: true,
|
|
11
|
+
col: true,
|
|
12
|
+
embed: true,
|
|
13
|
+
hr: true,
|
|
14
|
+
img: true,
|
|
15
|
+
input: true,
|
|
16
|
+
link: true,
|
|
17
|
+
meta: true,
|
|
18
|
+
param: true,
|
|
19
|
+
source: true,
|
|
20
|
+
track: true,
|
|
21
|
+
wbr: true
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
var RUBY_KEYWORDS = {
|
|
25
|
+
def: true, end: true, 'if': true, 'else': true, elsif: true,
|
|
26
|
+
unless: true, 'while': true, until: true, 'for': true, 'do': true,
|
|
27
|
+
'return': true, 'class': true, 'module': true, begin: true,
|
|
28
|
+
rescue: true, ensure: true, 'raise': true, yield: true,
|
|
29
|
+
'self': true, 'super': true, 'true': true, 'false': true, 'nil': true,
|
|
30
|
+
then: true, when: true, 'case': true, 'in': true, 'and': true,
|
|
31
|
+
'or': true, not: true, require: true, include: true, extend: true
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
var globalsRegistered = false;
|
|
35
|
+
|
|
36
|
+
function leadingWhitespace(line) {
|
|
37
|
+
var match = line.match(/^\s*/);
|
|
38
|
+
return match ? match[0] : '';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function escapeRegExp(value) {
|
|
42
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function rubyIndentUnit(model) {
|
|
46
|
+
var options = model.getOptions ? model.getOptions() : null;
|
|
47
|
+
var tabSize = options && options.tabSize ? options.tabSize : 4;
|
|
48
|
+
return new Array(tabSize + 1).join(' ');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function rubyClosingIndent(model, cursorLineNumber, openerLine) {
|
|
52
|
+
var cursorIndent = leadingWhitespace(model.getLineContent(cursorLineNumber));
|
|
53
|
+
var indentUnit = rubyIndentUnit(model);
|
|
54
|
+
|
|
55
|
+
if (cursorIndent.length >= indentUnit.length) {
|
|
56
|
+
return cursorIndent.slice(0, cursorIndent.length - indentUnit.length);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return leadingWhitespace(openerLine);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function isRubyBlockStart(line) {
|
|
63
|
+
var trimmed = line.trim();
|
|
64
|
+
if (!trimmed || trimmed[0] === '#') return false;
|
|
65
|
+
return RUBY_BLOCK_START.test(line) || RUBY_DO_BLOCK_START.test(trimmed);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function hasMatchingRubyEnd(model, openerLineNumber, openerIndent) {
|
|
69
|
+
var lineCount = model.getLineCount();
|
|
70
|
+
var openerIndentLength = openerIndent.length;
|
|
71
|
+
|
|
72
|
+
for (var lineNumber = openerLineNumber + 1; lineNumber <= lineCount; lineNumber += 1) {
|
|
73
|
+
var line = model.getLineContent(lineNumber);
|
|
74
|
+
var trimmed = line.trim();
|
|
75
|
+
if (!trimmed || trimmed[0] === '#') continue;
|
|
76
|
+
|
|
77
|
+
var lineIndent = leadingWhitespace(line);
|
|
78
|
+
var lineIndentLength = lineIndent.length;
|
|
79
|
+
|
|
80
|
+
if (RUBY_END_LINE.test(trimmed)) {
|
|
81
|
+
if (lineIndentLength === openerIndentLength) return true;
|
|
82
|
+
if (lineIndentLength < openerIndentLength) return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function rubyEnterContext(editor, model) {
|
|
90
|
+
var selection = editor.getSelection();
|
|
91
|
+
if (!selection || !selection.isEmpty()) return null;
|
|
92
|
+
|
|
93
|
+
var cursorPosition = editor.getPosition();
|
|
94
|
+
if (!cursorPosition) return null;
|
|
95
|
+
|
|
96
|
+
var openerLine = model.getLineContent(cursorPosition.lineNumber);
|
|
97
|
+
var beforeCursor = openerLine.substring(0, cursorPosition.column - 1);
|
|
98
|
+
var afterCursor = openerLine.substring(cursorPosition.column - 1);
|
|
99
|
+
|
|
100
|
+
if (afterCursor.trim() !== '') return null;
|
|
101
|
+
if (!isRubyBlockStart(beforeCursor)) return null;
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
cursorPosition: cursorPosition,
|
|
105
|
+
openerIndent: leadingWhitespace(openerLine),
|
|
106
|
+
hasExistingEnd: hasMatchingRubyEnd(model, cursorPosition.lineNumber, leadingWhitespace(openerLine))
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function handleRubyEnter(editor, model) {
|
|
111
|
+
var context = rubyEnterContext(editor, model);
|
|
112
|
+
if (!context) return false;
|
|
113
|
+
|
|
114
|
+
var innerIndent = context.openerIndent + rubyIndentUnit(model);
|
|
115
|
+
var insertedText = '\n' + innerIndent;
|
|
116
|
+
if (!context.hasExistingEnd) {
|
|
117
|
+
insertedText += '\n' + context.openerIndent + 'end';
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
editor.executeEdits('ruby-auto-end', [{
|
|
121
|
+
range: new window.monaco.Range(context.cursorPosition.lineNumber, context.cursorPosition.column, context.cursorPosition.lineNumber, context.cursorPosition.column),
|
|
122
|
+
text: insertedText
|
|
123
|
+
}]);
|
|
124
|
+
|
|
125
|
+
editor.setPosition({
|
|
126
|
+
lineNumber: context.cursorPosition.lineNumber + 1,
|
|
127
|
+
column: innerIndent.length + 1
|
|
128
|
+
});
|
|
129
|
+
editor.focus();
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function handleMarkupAutoClose(editor, model, change) {
|
|
134
|
+
if (change.rangeLength !== 0 || change.text !== '>') return false;
|
|
135
|
+
|
|
136
|
+
var lineNumber = change.range.startLineNumber;
|
|
137
|
+
var columnBeforeInsert = change.range.startColumn;
|
|
138
|
+
var lineContent = model.getLineContent(lineNumber);
|
|
139
|
+
var textBefore = lineContent.substring(0, columnBeforeInsert - 1);
|
|
140
|
+
|
|
141
|
+
if (/\/$/.test(textBefore)) return false;
|
|
142
|
+
|
|
143
|
+
var tagMatch = textBefore.match(/<([a-zA-Z][a-zA-Z0-9:\-_]*)(?:\s+[^>]*?)?$/);
|
|
144
|
+
if (!tagMatch) return false;
|
|
145
|
+
|
|
146
|
+
var tagName = tagMatch[1];
|
|
147
|
+
if (VOID_HTML_ELEMENTS[tagName.toLowerCase()]) return false;
|
|
148
|
+
|
|
149
|
+
var closingTag = '</' + tagName + '>';
|
|
150
|
+
var afterCursor = lineContent.substring(columnBeforeInsert);
|
|
151
|
+
var existingClosePattern = new RegExp('^\\s*' + escapeRegExp(closingTag));
|
|
152
|
+
if (existingClosePattern.test(afterCursor)) return false;
|
|
153
|
+
|
|
154
|
+
window.setTimeout(function () {
|
|
155
|
+
var activeModel = editor.getModel();
|
|
156
|
+
if (!activeModel || activeModel !== model) return;
|
|
157
|
+
|
|
158
|
+
var latestLineContent = model.getLineContent(lineNumber);
|
|
159
|
+
var latestAfterCursor = latestLineContent.substring(columnBeforeInsert);
|
|
160
|
+
if (existingClosePattern.test(latestAfterCursor)) return;
|
|
161
|
+
|
|
162
|
+
editor.executeEdits('html-auto-close', [{
|
|
163
|
+
range: new window.monaco.Range(lineNumber, columnBeforeInsert + 1, lineNumber, columnBeforeInsert + 1),
|
|
164
|
+
text: closingTag
|
|
165
|
+
}]);
|
|
166
|
+
|
|
167
|
+
window.setTimeout(function () {
|
|
168
|
+
editor.setPosition({ lineNumber: lineNumber, column: columnBeforeInsert + 1 });
|
|
169
|
+
editor.focus();
|
|
170
|
+
}, 0);
|
|
171
|
+
}, 0);
|
|
172
|
+
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function attachEditorFeatures(editor, language) {
|
|
177
|
+
var model = editor && editor.getModel ? editor.getModel() : null;
|
|
178
|
+
if (!model) {
|
|
179
|
+
return { dispose: function dispose() {} };
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
var suppressInternalEdit = false;
|
|
183
|
+
var keydownDisposable = null;
|
|
184
|
+
var emmetTabDisposable = null;
|
|
185
|
+
var gotoMouseDisposable = null;
|
|
186
|
+
var gotoActionDisposable = null;
|
|
187
|
+
|
|
188
|
+
// Emmet Tab expansion — active for markup and stylesheet languages
|
|
189
|
+
var EMMET_MARKUP_LANGS = { html: true, xml: true, erb: true, 'html.erb': true, haml: true };
|
|
190
|
+
var EMMET_STYLE_LANGS = { css: true, scss: true, less: true };
|
|
191
|
+
var isEmmetLang = EMMET_MARKUP_LANGS[language] || EMMET_STYLE_LANGS[language];
|
|
192
|
+
|
|
193
|
+
if (isEmmetLang && window.emmet && window.emmet.extract && window.emmet.default) {
|
|
194
|
+
var emmetType = EMMET_STYLE_LANGS[language] ? 'stylesheet' : 'markup';
|
|
195
|
+
// editor.addAction() returns a real IDisposable; addCommand() only returns a string.
|
|
196
|
+
emmetTabDisposable = editor.addAction({
|
|
197
|
+
id: 'mbeditor.emmet.expandAbbreviation',
|
|
198
|
+
label: 'Emmet: Expand Abbreviation',
|
|
199
|
+
keybindings: [window.monaco.KeyCode.Tab],
|
|
200
|
+
precondition: '!suggestWidgetVisible && !parameterHintsVisible',
|
|
201
|
+
run: function(editor) {
|
|
202
|
+
var selection = editor.getSelection();
|
|
203
|
+
// Only expand when the selection is collapsed (no range selected)
|
|
204
|
+
if (!selection.isEmpty()) {
|
|
205
|
+
editor.trigger('keyboard', 'type', { text: '\t' });
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
var pos = editor.getPosition();
|
|
209
|
+
var lineText = model.getLineContent(pos.lineNumber);
|
|
210
|
+
var textBeforeCursor = lineText.substring(0, pos.column - 1);
|
|
211
|
+
|
|
212
|
+
var extracted = null;
|
|
213
|
+
try {
|
|
214
|
+
extracted = window.emmet.extract(textBeforeCursor, { type: emmetType });
|
|
215
|
+
} catch (e) { /* not a valid context */ }
|
|
216
|
+
|
|
217
|
+
if (!extracted || !extracted.abbreviation) {
|
|
218
|
+
editor.trigger('keyboard', 'type', { text: '\t' });
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
var abbr = extracted.abbreviation;
|
|
223
|
+
var expanded = null;
|
|
224
|
+
try {
|
|
225
|
+
expanded = window.emmet.default(abbr, { type: emmetType });
|
|
226
|
+
} catch (e) { /* not a valid abbreviation */ }
|
|
227
|
+
|
|
228
|
+
if (!expanded) {
|
|
229
|
+
editor.trigger('keyboard', 'type', { text: '\t' });
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Place the first tab stop ($1 or ${1}) at cursor; strip remaining markers
|
|
234
|
+
var withTabStop = expanded.replace(/\$\{[0-9]+:[^}]*\}|\$\{[0-9]+\}|\$[0-9]+/g, function(m, offset, str) {
|
|
235
|
+
// Keep first occurrence as cursor position marker (replaced below), remove rest
|
|
236
|
+
return m;
|
|
237
|
+
});
|
|
238
|
+
var firstTabStop = null;
|
|
239
|
+
var expandedClean = withTabStop.replace(/\$\{1:[^}]*\}|\$\{1\}|\$1/g, function(m) {
|
|
240
|
+
if (firstTabStop === null) { firstTabStop = true; return '\x00'; }
|
|
241
|
+
return '';
|
|
242
|
+
}).replace(/\$\{[0-9]+:[^}]*\}|\$\{[0-9]+\}|\$[0-9]+/g, '');
|
|
243
|
+
|
|
244
|
+
var cursorMarkerIdx = expandedClean.indexOf('\x00');
|
|
245
|
+
var finalText = expandedClean.replace('\x00', '');
|
|
246
|
+
|
|
247
|
+
// Replace the abbreviation text with the expanded result
|
|
248
|
+
var abbrStart = extracted.location + 1; // 1-based column
|
|
249
|
+
var abbrEnd = pos.column; // exclusive
|
|
250
|
+
|
|
251
|
+
var range = new window.monaco.Range(pos.lineNumber, abbrStart, pos.lineNumber, abbrEnd);
|
|
252
|
+
editor.executeEdits('emmet', [{ range: range, text: finalText }]);
|
|
253
|
+
|
|
254
|
+
// Position cursor at first tab stop if we found one
|
|
255
|
+
if (cursorMarkerIdx >= 0) {
|
|
256
|
+
var textBefore = finalText.substring(0, cursorMarkerIdx);
|
|
257
|
+
var newlines = textBefore.split('\n');
|
|
258
|
+
var newLine = pos.lineNumber + newlines.length - 1;
|
|
259
|
+
var newCol = newlines.length === 1
|
|
260
|
+
? abbrStart + textBefore.length
|
|
261
|
+
: newlines[newlines.length - 1].length + 1;
|
|
262
|
+
editor.setPosition({ lineNumber: newLine, column: newCol });
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (language === 'ruby') {
|
|
269
|
+
keydownDisposable = editor.onKeyDown(function (event) {
|
|
270
|
+
if (event.keyCode !== window.monaco.KeyCode.Enter) return;
|
|
271
|
+
if (!handleRubyEnter(editor, model)) return;
|
|
272
|
+
|
|
273
|
+
event.preventDefault();
|
|
274
|
+
event.stopPropagation();
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
// Ctrl/Cmd+click — navigate to definition
|
|
278
|
+
gotoMouseDisposable = editor.onMouseDown(function(event) {
|
|
279
|
+
var ctrlOrCmd = event.event.ctrlKey || event.event.metaKey;
|
|
280
|
+
if (!ctrlOrCmd) return;
|
|
281
|
+
// Target type 6 = CONTENT_TEXT in Monaco's MouseTargetType enum
|
|
282
|
+
if (!event.target || event.target.type !== 6) return;
|
|
283
|
+
|
|
284
|
+
var position = event.target.position;
|
|
285
|
+
if (!position) return;
|
|
286
|
+
|
|
287
|
+
var wordInfo = model.getWordAtPosition(position);
|
|
288
|
+
if (!wordInfo || !wordInfo.word || wordInfo.word.length < 2) return;
|
|
289
|
+
if (RUBY_KEYWORDS[wordInfo.word]) return;
|
|
290
|
+
if (typeof FileService === 'undefined' || !FileService.getDefinition) return;
|
|
291
|
+
|
|
292
|
+
event.event.preventDefault();
|
|
293
|
+
|
|
294
|
+
FileService.getDefinition(wordInfo.word, 'ruby').then(function(data) {
|
|
295
|
+
var results = data && Array.isArray(data.results) ? data.results : [];
|
|
296
|
+
if (results.length === 0) return;
|
|
297
|
+
var r = results[0];
|
|
298
|
+
var filename = r.file.split('/').pop();
|
|
299
|
+
if (typeof TabManager !== 'undefined' && TabManager.openTab) {
|
|
300
|
+
TabManager.openTab(r.file, filename, r.line);
|
|
301
|
+
}
|
|
302
|
+
}).catch(function() {});
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
// F12 — go to definition from keyboard
|
|
306
|
+
gotoActionDisposable = editor.addAction({
|
|
307
|
+
id: 'mbeditor.gotoRubyDefinition',
|
|
308
|
+
label: 'Go to Ruby Definition',
|
|
309
|
+
keybindings: [window.monaco.KeyCode.F12],
|
|
310
|
+
contextMenuGroupId: 'navigation',
|
|
311
|
+
contextMenuOrder: 1.5,
|
|
312
|
+
run: function(ed) {
|
|
313
|
+
var pos = ed.getPosition();
|
|
314
|
+
if (!pos) return;
|
|
315
|
+
var wordInfo = model.getWordAtPosition(pos);
|
|
316
|
+
if (!wordInfo || !wordInfo.word || wordInfo.word.length < 2) return;
|
|
317
|
+
if (RUBY_KEYWORDS[wordInfo.word]) return;
|
|
318
|
+
if (typeof FileService === 'undefined' || !FileService.getDefinition) return;
|
|
319
|
+
|
|
320
|
+
FileService.getDefinition(wordInfo.word, 'ruby').then(function(data) {
|
|
321
|
+
var results = data && Array.isArray(data.results) ? data.results : [];
|
|
322
|
+
if (results.length === 0) return;
|
|
323
|
+
var r = results[0];
|
|
324
|
+
var filename = r.file.split('/').pop();
|
|
325
|
+
if (typeof TabManager !== 'undefined' && TabManager.openTab) {
|
|
326
|
+
TabManager.openTab(r.file, filename, r.line);
|
|
327
|
+
}
|
|
328
|
+
}).catch(function() {});
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
var contentDisposable = model.onDidChangeContent(function (event) {
|
|
334
|
+
if (suppressInternalEdit) return;
|
|
335
|
+
if (event.isUndoing || event.isRedoing) return;
|
|
336
|
+
if (!event.changes || event.changes.length !== 1) return;
|
|
337
|
+
|
|
338
|
+
var change = event.changes[0];
|
|
339
|
+
var handled = false;
|
|
340
|
+
|
|
341
|
+
suppressInternalEdit = true;
|
|
342
|
+
try {
|
|
343
|
+
if (language === 'html') {
|
|
344
|
+
handled = handleMarkupAutoClose(editor, model, change) || handled;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (language === 'javascript' || language === 'typescript') {
|
|
348
|
+
handled = handleMarkupAutoClose(editor, model, change) || handled;
|
|
349
|
+
}
|
|
350
|
+
} finally {
|
|
351
|
+
suppressInternalEdit = false;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
return handled;
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
return {
|
|
358
|
+
dispose: function dispose() {
|
|
359
|
+
if (keydownDisposable) keydownDisposable.dispose();
|
|
360
|
+
if (emmetTabDisposable) emmetTabDisposable.dispose();
|
|
361
|
+
if (gotoMouseDisposable) gotoMouseDisposable.dispose();
|
|
362
|
+
if (gotoActionDisposable) gotoActionDisposable.dispose();
|
|
363
|
+
contentDisposable.dispose();
|
|
364
|
+
}
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function registerGlobalExtensions(monaco) {
|
|
369
|
+
if (globalsRegistered) return;
|
|
370
|
+
if (!monaco || !monaco.languages) return;
|
|
371
|
+
|
|
372
|
+
globalsRegistered = true;
|
|
373
|
+
|
|
374
|
+
monaco.languages.setLanguageConfiguration('ruby', {
|
|
375
|
+
comments: { lineComment: '#', blockComment: ['=begin', '=end'] },
|
|
376
|
+
brackets: [['(', ')'], ['{', '}'], ['[', ']']],
|
|
377
|
+
autoClosingPairs: [
|
|
378
|
+
{ open: '{', close: '}' }, { open: '[', close: ']' }, { open: '(', close: ')' },
|
|
379
|
+
{ open: '"', close: '"' }, { open: "'", close: "'" }
|
|
380
|
+
],
|
|
381
|
+
surroundingPairs: [
|
|
382
|
+
{ open: '{', close: '}' }, { open: '[', close: ']' }, { open: '(', close: ')' },
|
|
383
|
+
{ open: '"', close: '"' }, { open: "'", close: "'" }
|
|
384
|
+
],
|
|
385
|
+
indentationRules: {
|
|
386
|
+
increaseIndentPattern: /^\s*(def|class|module|if|unless|case|while|until|for|begin|elsif|else|rescue|ensure|when)\b/,
|
|
387
|
+
decreaseIndentPattern: /^\s*(end|elsif|else|rescue|ensure|when)\b/
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
// Override Monaco's built-in Ruby tokenizer with a comprehensive Monarch grammar
|
|
392
|
+
// that uses TextMate-standard scope names so all bundled themes colour them correctly.
|
|
393
|
+
monaco.languages.setMonarchTokensProvider('ruby', {
|
|
394
|
+
defaultToken: '',
|
|
395
|
+
tokenPostfix: '.ruby',
|
|
396
|
+
|
|
397
|
+
tokenizer: {
|
|
398
|
+
root: [
|
|
399
|
+
// =begin / =end block comments
|
|
400
|
+
[/^=begin\b/, { token: 'comment', next: '@blockComment' }],
|
|
401
|
+
|
|
402
|
+
// Single-line comments
|
|
403
|
+
[/#.*$/, 'comment'],
|
|
404
|
+
|
|
405
|
+
// Heredoc start — capture the terminator word into state arg
|
|
406
|
+
[/<<[-~]?(['"]?)(\w+)\1/, { token: 'string.heredoc.delimiter', next: '@heredoc.$2' }],
|
|
407
|
+
|
|
408
|
+
// def + method name (handles self. prefix and operator method names)
|
|
409
|
+
[/(\bdef\b)(\s+)(self)(\.)([\w]+[!?=]?|[+\-*\/%<>=!\[\]&|^~]+)/,
|
|
410
|
+
['keyword.control.def', '', 'variable.language', 'delimiter', 'entity.name.function']],
|
|
411
|
+
[/(\bdef\b)(\s+)([\w]+[!?=]?|[+\-*\/%<>=!\[\]&|^~]+)/,
|
|
412
|
+
['keyword.control.def', '', 'entity.name.function']],
|
|
413
|
+
[/\bdef\b/, 'keyword.control.def'],
|
|
414
|
+
|
|
415
|
+
// class + name (including singleton class << self)
|
|
416
|
+
[/(\bclass\b)(\s+)([A-Z][\w:]*)/, ['keyword.control.class', '', 'entity.name.class']],
|
|
417
|
+
[/\bclass\b/, 'keyword.control.class'],
|
|
418
|
+
|
|
419
|
+
// module + name
|
|
420
|
+
[/(\bmodule\b)(\s+)([A-Z][\w:]*)/, ['keyword.control.module', '', 'entity.name.class']],
|
|
421
|
+
[/\bmodule\b/, 'keyword.control.module'],
|
|
422
|
+
|
|
423
|
+
// Language literals
|
|
424
|
+
[/\b(nil|true|false)\b/, 'constant.language'],
|
|
425
|
+
[/\b(self|super)\b/, 'variable.language'],
|
|
426
|
+
|
|
427
|
+
// Class variables (@@) — must precede instance variable rule
|
|
428
|
+
[/@@[a-zA-Z_]\w*/, 'variable.other'],
|
|
429
|
+
// Instance variables (@)
|
|
430
|
+
[/@[a-zA-Z_]\w*/, 'variable.other.readwrite.instance'],
|
|
431
|
+
// Global variables ($)
|
|
432
|
+
[/\$[a-zA-Z_]\w*|\$\d+|\$[!@&*()\-.,;<>\/\\~`+?=:#]/, 'variable.other.constant'],
|
|
433
|
+
|
|
434
|
+
// Symbols :foo :"foo" :'foo'
|
|
435
|
+
[/:[a-zA-Z_]\w*[!?]?/, 'constant.other.symbol'],
|
|
436
|
+
[/:"/, { token: 'constant.other.symbol', next: '@symDqString' }],
|
|
437
|
+
[/:'/, { token: 'constant.other.symbol', next: '@symSqString' }],
|
|
438
|
+
|
|
439
|
+
// Numbers
|
|
440
|
+
[/0[xX][0-9a-fA-F][0-9a-fA-F_]*/, 'constant.numeric'],
|
|
441
|
+
[/0[bB][01][01_]*/, 'constant.numeric'],
|
|
442
|
+
[/0[oO][0-7][0-7_]*/, 'constant.numeric'],
|
|
443
|
+
[/\d[\d_]*(?:\.\d[\d_]*)?(?:[eE][+-]?\d[\d_]*)?/, 'constant.numeric'],
|
|
444
|
+
|
|
445
|
+
// Strings
|
|
446
|
+
[/"/, { token: 'string.quoted.double', next: '@dqString' }],
|
|
447
|
+
[/'/, { token: 'string.quoted.single', next: '@sqString' }],
|
|
448
|
+
|
|
449
|
+
// Percent literals — %w[] %i[] %(string)
|
|
450
|
+
[/%[wW]\[/, { token: 'string', next: '@percentWordBracket' }],
|
|
451
|
+
[/%[wW]\(/, { token: 'string', next: '@percentWordParen' }],
|
|
452
|
+
[/%[wW]\{/, { token: 'string', next: '@percentWordCurly' }],
|
|
453
|
+
[/%[iI]\[/, { token: 'constant.other.symbol', next: '@percentSymBracket' }],
|
|
454
|
+
[/%[iI]\(/, { token: 'constant.other.symbol', next: '@percentSymParen' }],
|
|
455
|
+
[/%[qQ]?\(/, { token: 'string.quoted.double', next: '@percentDqParen' }],
|
|
456
|
+
[/%[qQ]?\[/, { token: 'string.quoted.double', next: '@percentDqBracket' }],
|
|
457
|
+
[/%[qQ]?\{/, { token: 'string.quoted.double', next: '@percentDqCurly' }],
|
|
458
|
+
|
|
459
|
+
// Regexp literals — simplified: /pat/imxo not preceded by a word boundary that looks like division
|
|
460
|
+
[/\/(?!\s)(?:[^\/\\\n]|\\.)+\/[imxo]*/, 'string.regexp'],
|
|
461
|
+
|
|
462
|
+
// Control-flow and other keywords
|
|
463
|
+
[/\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'],
|
|
464
|
+
|
|
465
|
+
// Built-in kernel / module methods (support.function so themes highlight them distinctly)
|
|
466
|
+
[/\b(require|require_relative|load|autoload|include|extend|prepend|attr_reader|attr_writer|attr_accessor|attr|public|private|protected|module_function|puts|print|p|pp|gets|printf|sprintf|format|abort|exit|sleep|rand|srand|lambda|proc|block_given\?|respond_to\?|fail|warn|at_exit|freeze|frozen\?|nil\?|is_a\?|kind_of\?|instance_of\?|tap|itself|raise)\b/, 'support.function'],
|
|
467
|
+
|
|
468
|
+
// CamelCase constants and class references
|
|
469
|
+
[/[A-Z][a-zA-Z0-9_]*[?!]?/, 'entity.name.type.class'],
|
|
470
|
+
|
|
471
|
+
// Regular identifiers
|
|
472
|
+
[/[a-z_]\w*[!?]?/, 'identifier'],
|
|
473
|
+
|
|
474
|
+
// Operators
|
|
475
|
+
[/::/, 'keyword.operator'],
|
|
476
|
+
[/\.\.\.|\.\./, 'keyword.operator'],
|
|
477
|
+
[/<<=|>>=|\*\*=|&&=|\|\|=|[+\-*\/%&|^]=/, 'keyword.operator'],
|
|
478
|
+
[/<=>|===|==|!=|=~|!~|>=|<=|<<|>>|\*\*/, 'keyword.operator'],
|
|
479
|
+
[/[+\-*\/%&|^~<>=!?]/, 'keyword.operator'],
|
|
480
|
+
|
|
481
|
+
// Brackets and punctuation
|
|
482
|
+
[/[{}()\[\]]/, '@brackets'],
|
|
483
|
+
[/[;,.]/, 'delimiter'],
|
|
484
|
+
[/\s+/, '']
|
|
485
|
+
],
|
|
486
|
+
|
|
487
|
+
dqString: [
|
|
488
|
+
[/[^\\"\#]+/, 'string.quoted.double'],
|
|
489
|
+
[/#\{/, { token: 'string.interpolated', next: '@interpolated' }],
|
|
490
|
+
[/#[^{]?/, 'string.quoted.double'],
|
|
491
|
+
[/\\./, 'string.quoted.double.escape'],
|
|
492
|
+
[/"/, { token: 'string.quoted.double', next: '@pop' }]
|
|
493
|
+
],
|
|
494
|
+
|
|
495
|
+
sqString: [
|
|
496
|
+
[/[^\\']+/, 'string.quoted.single'],
|
|
497
|
+
[/\\./, 'string.quoted.single.escape'],
|
|
498
|
+
[/'/, { token: 'string.quoted.single', next: '@pop' }]
|
|
499
|
+
],
|
|
500
|
+
|
|
501
|
+
symDqString: [
|
|
502
|
+
[/[^\\"\#]+/, 'constant.other.symbol'],
|
|
503
|
+
[/#\{/, { token: 'string.interpolated', next: '@interpolated' }],
|
|
504
|
+
[/\\./, 'constant.other.symbol'],
|
|
505
|
+
[/"/, { token: 'constant.other.symbol', next: '@pop' }]
|
|
506
|
+
],
|
|
507
|
+
symSqString: [
|
|
508
|
+
[/[^\\']+/, 'constant.other.symbol'],
|
|
509
|
+
[/\\./, 'constant.other.symbol'],
|
|
510
|
+
[/'/, { token: 'constant.other.symbol', next: '@pop' }]
|
|
511
|
+
],
|
|
512
|
+
|
|
513
|
+
interpolated: [
|
|
514
|
+
[/\}/, { token: 'string.interpolated', next: '@pop' }],
|
|
515
|
+
[/"/, { token: 'string.quoted.double', next: '@dqString' }],
|
|
516
|
+
[/'/, { token: 'string.quoted.single', next: '@sqString' }],
|
|
517
|
+
[/@@[a-zA-Z_]\w*/, 'variable.other'],
|
|
518
|
+
[/@[a-zA-Z_]\w*/, 'variable.other.readwrite.instance'],
|
|
519
|
+
[/\$[a-zA-Z_]\w*/, 'variable.other.constant'],
|
|
520
|
+
[/\b(nil|true|false)\b/, 'constant.language'],
|
|
521
|
+
[/\b(self)\b/, 'variable.language'],
|
|
522
|
+
[/[A-Z][a-zA-Z0-9_]*/, 'entity.name.type.class'],
|
|
523
|
+
[/\d[\d_]*(?:\.\d[\d_]*)?/, 'constant.numeric'],
|
|
524
|
+
[/:[a-zA-Z_]\w*[!?]?/, 'constant.other.symbol'],
|
|
525
|
+
[/[a-z_]\w*[!?]?/, 'identifier'],
|
|
526
|
+
[/::|\.\.\.|\.\./, 'keyword.operator'],
|
|
527
|
+
[/[+\-*\/%&|^~<>=!?.,:()\[\]]+/, 'keyword.operator'],
|
|
528
|
+
[/\s+/, '']
|
|
529
|
+
],
|
|
530
|
+
|
|
531
|
+
heredoc: [
|
|
532
|
+
[/^(\w+)\s*$/, {
|
|
533
|
+
cases: {
|
|
534
|
+
'$1==$S2': { token: 'string.heredoc.delimiter', next: '@pop' },
|
|
535
|
+
'@default': 'string.heredoc'
|
|
536
|
+
}
|
|
537
|
+
}],
|
|
538
|
+
[/.+/, 'string.heredoc']
|
|
539
|
+
],
|
|
540
|
+
|
|
541
|
+
// %w[] %W[] word arrays
|
|
542
|
+
percentWordBracket: [
|
|
543
|
+
[/\]/, { token: 'string', next: '@pop' }],
|
|
544
|
+
[/[^\]\s\\]+/, 'string'],
|
|
545
|
+
[/\s+/, 'string'],
|
|
546
|
+
[/\\./, 'string.escape']
|
|
547
|
+
],
|
|
548
|
+
percentWordParen: [
|
|
549
|
+
[/\)/, { token: 'string', next: '@pop' }],
|
|
550
|
+
[/[^)\s\\]+/, 'string'],
|
|
551
|
+
[/\s+/, 'string'],
|
|
552
|
+
[/\\./, 'string.escape']
|
|
553
|
+
],
|
|
554
|
+
percentWordCurly: [
|
|
555
|
+
[/\}/, { token: 'string', next: '@pop' }],
|
|
556
|
+
[/[^}\s\\]+/, 'string'],
|
|
557
|
+
[/\s+/, 'string'],
|
|
558
|
+
[/\\./, 'string.escape']
|
|
559
|
+
],
|
|
560
|
+
|
|
561
|
+
// %i[] %I[] symbol arrays
|
|
562
|
+
percentSymBracket: [
|
|
563
|
+
[/\]/, { token: 'constant.other.symbol', next: '@pop' }],
|
|
564
|
+
[/[^\]\s\\]+/, 'constant.other.symbol'],
|
|
565
|
+
[/\s+/, 'constant.other.symbol'],
|
|
566
|
+
[/\\./, 'constant.other.symbol']
|
|
567
|
+
],
|
|
568
|
+
percentSymParen: [
|
|
569
|
+
[/\)/, { token: 'constant.other.symbol', next: '@pop' }],
|
|
570
|
+
[/[^)\s\\]+/, 'constant.other.symbol'],
|
|
571
|
+
[/\s+/, 'constant.other.symbol'],
|
|
572
|
+
[/\\./, 'constant.other.symbol']
|
|
573
|
+
],
|
|
574
|
+
|
|
575
|
+
// %(str) %[str] %{str} interpolating strings
|
|
576
|
+
percentDqParen: [
|
|
577
|
+
[/\)/, { token: 'string.quoted.double', next: '@pop' }],
|
|
578
|
+
[/#\{/, { token: 'string.interpolated', next: '@interpolated' }],
|
|
579
|
+
[/[^)\\\#]+/, 'string.quoted.double'],
|
|
580
|
+
[/\\./, 'string.quoted.double.escape']
|
|
581
|
+
],
|
|
582
|
+
percentDqBracket: [
|
|
583
|
+
[/\]/, { token: 'string.quoted.double', next: '@pop' }],
|
|
584
|
+
[/#\{/, { token: 'string.interpolated', next: '@interpolated' }],
|
|
585
|
+
[/[^\]\\\#]+/, 'string.quoted.double'],
|
|
586
|
+
[/\\./, 'string.quoted.double.escape']
|
|
587
|
+
],
|
|
588
|
+
percentDqCurly: [
|
|
589
|
+
[/\}/, { token: 'string.quoted.double', next: '@pop' }],
|
|
590
|
+
[/#\{/, { token: 'string.interpolated', next: '@interpolated' }],
|
|
591
|
+
[/[^}\\\#]+/, 'string.quoted.double'],
|
|
592
|
+
[/\\./, 'string.quoted.double.escape']
|
|
593
|
+
],
|
|
594
|
+
|
|
595
|
+
blockComment: [
|
|
596
|
+
[/^=end\b.*$/, { token: 'comment', next: '@pop' }],
|
|
597
|
+
[/.+/, 'comment']
|
|
598
|
+
]
|
|
599
|
+
}
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
var genericLinkedProvider = {
|
|
603
|
+
provideLinkedEditingRanges: function provideLinkedEditingRanges(model, position) {
|
|
604
|
+
var line = model.getLineContent(position.lineNumber);
|
|
605
|
+
var wordInfo = model.getWordAtPosition(position);
|
|
606
|
+
if (!wordInfo) return null;
|
|
607
|
+
|
|
608
|
+
var word = wordInfo.word;
|
|
609
|
+
var startCol = wordInfo.startColumn;
|
|
610
|
+
var endCol = wordInfo.endColumn;
|
|
611
|
+
|
|
612
|
+
if (line[startCol - 2] === '<') {
|
|
613
|
+
var closeTagStr = '</' + word + '>';
|
|
614
|
+
var closeIdx = line.indexOf(closeTagStr, endCol - 1);
|
|
615
|
+
if (closeIdx !== -1) {
|
|
616
|
+
return {
|
|
617
|
+
ranges: [new monaco.Range(position.lineNumber, startCol, position.lineNumber, endCol), new monaco.Range(position.lineNumber, closeIdx + 3, position.lineNumber, closeIdx + 3 + word.length)],
|
|
618
|
+
wordPattern: /[a-zA-Z0-9:\-_]+/
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
if (line[startCol - 3] === '<' && line[startCol - 2] === '/') {
|
|
624
|
+
var openTagRegex = new RegExp('<' + word + '(?:\\s|>)');
|
|
625
|
+
var match = line.match(openTagRegex);
|
|
626
|
+
if (match) {
|
|
627
|
+
var openStart = match.index + 2;
|
|
628
|
+
if (openStart < startCol) {
|
|
629
|
+
return {
|
|
630
|
+
ranges: [new monaco.Range(position.lineNumber, openStart, position.lineNumber, openStart + word.length), new monaco.Range(position.lineNumber, startCol, position.lineNumber, endCol)],
|
|
631
|
+
wordPattern: /[a-zA-Z0-9:\-_]+/
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
return null;
|
|
638
|
+
}
|
|
639
|
+
};
|
|
640
|
+
|
|
641
|
+
monaco.languages.registerLinkedEditingRangeProvider('javascript', genericLinkedProvider);
|
|
642
|
+
monaco.languages.registerLinkedEditingRangeProvider('typescript', genericLinkedProvider);
|
|
643
|
+
monaco.languages.registerLinkedEditingRangeProvider('ruby', genericLinkedProvider);
|
|
644
|
+
|
|
645
|
+
// RuboCop quick-fix code-action provider for Ruby files.
|
|
646
|
+
// Only registers when RuboCop is available in the workspace.
|
|
647
|
+
monaco.languages.registerCodeActionProvider('ruby', {
|
|
648
|
+
provideCodeActions: function provideCodeActions(model, _range, context) {
|
|
649
|
+
if (!window.MBEDITOR_RUBOCOP_AVAILABLE) return { actions: [], dispose: function() {} };
|
|
650
|
+
|
|
651
|
+
var correctableCops = model._mbeditorCorrectableCops || new Set();
|
|
652
|
+
var rubocopMarkers = context.markers.filter(function(m) {
|
|
653
|
+
return m.source === 'rubocop' && m.code && correctableCops.has(m.code);
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
if (rubocopMarkers.length === 0) return { actions: [], dispose: function() {} };
|
|
657
|
+
|
|
658
|
+
var modelPath = model._mbeditorPath || null;
|
|
659
|
+
if (!modelPath) return { actions: [], dispose: function() {} };
|
|
660
|
+
|
|
661
|
+
var code = model.getValue();
|
|
662
|
+
|
|
663
|
+
var actions = rubocopMarkers.map(function(marker) {
|
|
664
|
+
return {
|
|
665
|
+
title: 'Fix: ' + marker.code,
|
|
666
|
+
kind: 'quickfix',
|
|
667
|
+
isPreferred: rubocopMarkers.length === 1,
|
|
668
|
+
diagnostics: [marker],
|
|
669
|
+
command: {
|
|
670
|
+
id: 'mbeditor.applyRubocopFix',
|
|
671
|
+
title: 'Apply RuboCop fix for ' + marker.code,
|
|
672
|
+
arguments: [model, marker, code, modelPath]
|
|
673
|
+
}
|
|
674
|
+
};
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
return { actions: actions, dispose: function() {} };
|
|
678
|
+
}
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
// Command handler that fetches the fix from the backend and applies it.
|
|
682
|
+
monaco.editor.registerCommand('mbeditor.applyRubocopFix', function(_accessor, model, marker, code, modelPath) {
|
|
683
|
+
if (typeof FileService === 'undefined' || !FileService.quickFixOffense) return;
|
|
684
|
+
FileService.quickFixOffense(modelPath, code, marker.code).then(function(data) {
|
|
685
|
+
if (!data || !data.fix) return;
|
|
686
|
+
var fix = data.fix;
|
|
687
|
+
model.pushEditOperations([], [{
|
|
688
|
+
range: new monaco.Range(fix.startLine, fix.startCol, fix.endLine, fix.endCol),
|
|
689
|
+
text: fix.replacement
|
|
690
|
+
}], function() { return null; });
|
|
691
|
+
}).catch(function() {});
|
|
692
|
+
});
|
|
693
|
+
|
|
694
|
+
// Ruby method definition hover provider.
|
|
695
|
+
// Calls the backend /definition endpoint (Ripper-based) and renders
|
|
696
|
+
// the method signature and any preceding # comments as hover markdown.
|
|
697
|
+
monaco.languages.registerHoverProvider('ruby', {
|
|
698
|
+
provideHover: function provideHover(model, position) {
|
|
699
|
+
var wordInfo = model.getWordAtPosition(position);
|
|
700
|
+
if (!wordInfo) return null;
|
|
701
|
+
|
|
702
|
+
var word = wordInfo.word;
|
|
703
|
+
if (!word || word.length < 2) return null;
|
|
704
|
+
if (RUBY_KEYWORDS[word]) return null;
|
|
705
|
+
if (typeof FileService === 'undefined' || !FileService.getDefinition) return null;
|
|
706
|
+
|
|
707
|
+
var currentFile = model._mbeditorPath || null;
|
|
708
|
+
|
|
709
|
+
return FileService.getDefinition(word, 'ruby').then(function(data) {
|
|
710
|
+
var results = data && Array.isArray(data.results) ? data.results : [];
|
|
711
|
+
// Filter out results that are in the file currently being edited —
|
|
712
|
+
// showing a hover for a method you can already see adds no value.
|
|
713
|
+
if (currentFile) {
|
|
714
|
+
results = results.filter(function(r) { return r.file !== currentFile; });
|
|
715
|
+
}
|
|
716
|
+
if (results.length === 0) return null;
|
|
717
|
+
|
|
718
|
+
var first = results[0];
|
|
719
|
+
|
|
720
|
+
// Build two separate MarkdownString sections so Monaco renders a
|
|
721
|
+
// visual divider between the code block and the documentation.
|
|
722
|
+
var codeParts = ['```ruby'];
|
|
723
|
+
|
|
724
|
+
// Include a trimmed comment block as a Ruby comment inside the code
|
|
725
|
+
// fence so the whole thing looks like source you'd read in an editor.
|
|
726
|
+
if (first.comments && first.comments.length > 0) {
|
|
727
|
+
first.comments.split('\n').forEach(function(l) {
|
|
728
|
+
codeParts.push(l.trim() || '#');
|
|
729
|
+
});
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
codeParts.push(first.signature);
|
|
733
|
+
codeParts.push('```');
|
|
734
|
+
|
|
735
|
+
var locationParts = results.length > 1
|
|
736
|
+
? first.file + ':' + first.line + ' _(+' + (results.length - 1) + ' more)_'
|
|
737
|
+
: first.file + ':' + first.line;
|
|
738
|
+
|
|
739
|
+
return {
|
|
740
|
+
contents: [
|
|
741
|
+
{ value: codeParts.join('\n'), isTrusted: true },
|
|
742
|
+
{ value: '<span style="opacity:0.55;font-size:0.9em;">' + locationParts + '</span>', isTrusted: true, supportHtml: true }
|
|
743
|
+
]
|
|
744
|
+
};
|
|
745
|
+
}).catch(function() { return null; });
|
|
746
|
+
}
|
|
747
|
+
});
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
window.MbeditorEditorPlugins = {
|
|
751
|
+
registerGlobalExtensions: registerGlobalExtensions,
|
|
752
|
+
attachEditorFeatures: attachEditorFeatures,
|
|
753
|
+
runRubyEnter: function runRubyEnter(editor) {
|
|
754
|
+
if (!editor || !editor.getModel) return false;
|
|
755
|
+
return handleRubyEnter(editor, editor.getModel());
|
|
756
|
+
}
|
|
757
|
+
};
|
|
758
|
+
})();
|