main
js 399 lines 13.9 KB
Raw
1
2 "use strict";
3 /**
4 * @typedef {import("../../ace-internal").Ace.SyntaxMode} SyntaxMode
5 */
6
7 var config = require("../config");
8
9 var Tokenizer = require("../tokenizer").Tokenizer;
10
11 var TextHighlightRules = require("./text_highlight_rules").TextHighlightRules;
12 var CstyleBehaviour = require("./behaviour/cstyle").CstyleBehaviour;
13 var unicode = require("../unicode");
14 var lang = require("../lib/lang");
15 var TokenIterator = require("../token_iterator").TokenIterator;
16 var Range = require("../range").Range;
17
18 var Mode;
19 Mode = function() {
20 this.HighlightRules = TextHighlightRules;
21 };
22
23 (function() {
24 this.$defaultBehaviour = new CstyleBehaviour();
25
26 this.tokenRe = new RegExp("^[" + unicode.wordChars + "\\$_]+", "g");
27
28 this.nonTokenRe = new RegExp("^(?:[^" + unicode.wordChars + "\\$_]|\\s])+", "g");
29
30 /**
31 * @this {SyntaxMode}
32 */
33 this.getTokenizer = function() {
34 if (!this.$tokenizer) {
35 this.$highlightRules = this.$highlightRules || new this.HighlightRules(this.$highlightRuleConfig);
36 this.$tokenizer = new Tokenizer(this.$highlightRules.getRules());
37 }
38 return this.$tokenizer;
39 };
40
41 this.lineCommentStart = "";
42 this.blockComment = "";
43
44 /**
45 * @this {SyntaxMode}
46 */
47 this.toggleCommentLines = function(state, session, startRow, endRow) {
48 var doc = session.doc;
49
50 var ignoreBlankLines = true;
51 var shouldRemove = true;
52 var minIndent = Infinity;
53 var tabSize = session.getTabSize();
54 var insertAtTabStop = false;
55
56 if (!this.lineCommentStart) {
57 if (!this.blockComment)
58 return false;
59 /**@type {any}*/
60 var lineCommentStart = this.blockComment.start;
61 var lineCommentEnd = this.blockComment.end;
62 /**@type {any}*/
63 var regexpStart = new RegExp("^(\\s*)(?:" + lang.escapeRegExp(lineCommentStart) + ")");
64 var regexpEnd = new RegExp("(?:" + lang.escapeRegExp(lineCommentEnd) + ")\\s*$");
65
66 var comment = function(line, i) {
67 if (testRemove(line, i))
68 return;
69 if (!ignoreBlankLines || /\S/.test(line)) {
70 doc.insertInLine({row: i, column: line.length}, lineCommentEnd);
71 doc.insertInLine({row: i, column: minIndent}, lineCommentStart);
72 }
73 };
74
75 var uncomment = function(line, i) {
76 var m;
77 if (m = line.match(regexpEnd))
78 doc.removeInLine(i, line.length - m[0].length, line.length);
79 if (m = line.match(regexpStart))
80 doc.removeInLine(i, m[1].length, m[0].length);
81 };
82
83 /**@type {any}*/
84 var testRemove = function(line, row) {
85 if (regexpStart.test(line))
86 return true;
87 var tokens = session.getTokens(row);
88 for (var i = 0; i < tokens.length; i++) {
89 if (tokens[i].type === "comment")
90 return true;
91 }
92 };
93 } else {
94 if (Array.isArray(this.lineCommentStart)) {
95 /**@type {any}*/
96 var regexpStart = this.lineCommentStart.map(lang.escapeRegExp).join("|");
97 /**@type {any}*/
98 var lineCommentStart = this.lineCommentStart[0];
99 } else {
100 var regexpStart = lang.escapeRegExp(this.lineCommentStart);
101 /**@type {any}*/
102 var lineCommentStart = this.lineCommentStart;
103 }
104 regexpStart = new RegExp("^(\\s*)(?:" + regexpStart + ") ?");
105
106 insertAtTabStop = session.getUseSoftTabs();
107
108 var uncomment = function(line, i) {
109 var m = line.match(regexpStart);
110 if (!m) return;
111 var start = m[1].length, end = m[0].length;
112 if (!shouldInsertSpace(line, start, end) && m[0][end - 1] == " ")
113 end--;
114 doc.removeInLine(i, start, end);
115 };
116 var commentWithSpace = lineCommentStart + " ";
117 var comment = function(line, i) {
118 if (!ignoreBlankLines || /\S/.test(line)) {
119 if (shouldInsertSpace(line, minIndent, minIndent))
120 doc.insertInLine({row: i, column: minIndent}, commentWithSpace);
121 else
122 doc.insertInLine({row: i, column: minIndent}, lineCommentStart);
123 }
124 };
125 /**@type {any}*/
126 var testRemove = function(line, i) {
127 return regexpStart.test(line);
128 };
129
130 var shouldInsertSpace = function(line, before, after) {
131 var spaces = 0;
132 while (before-- && line.charAt(before) == " ")
133 spaces++;
134 if (spaces % tabSize != 0)
135 return false;
136 var spaces = 0;
137 while (line.charAt(after++) == " ")
138 spaces++;
139 if (tabSize > 2)
140 return spaces % tabSize != tabSize - 1;
141 else
142 return spaces % tabSize == 0;
143 };
144 }
145
146 function iter(fun) {
147 for (var i = startRow; i <= endRow; i++)
148 fun(doc.getLine(i), i);
149 }
150
151
152 var minEmptyLength = Infinity;
153 iter(function(line, i) {
154 var indent = line.search(/\S/);
155 if (indent !== -1) {
156 if (indent < minIndent)
157 minIndent = indent;
158 if (shouldRemove && !testRemove(line, i))
159 shouldRemove = false;
160 } else if (minEmptyLength > line.length) {
161 minEmptyLength = line.length;
162 }
163 });
164
165 if (minIndent == Infinity) {
166 minIndent = minEmptyLength;
167 ignoreBlankLines = false;
168 shouldRemove = false;
169 }
170
171 if (insertAtTabStop && minIndent % tabSize != 0)
172 minIndent = Math.floor(minIndent / tabSize) * tabSize;
173
174 iter(shouldRemove ? uncomment : comment);
175 };
176
177 /**
178 * @this {SyntaxMode}
179 */
180 this.toggleBlockComment = function(state, session, range, cursor) {
181 var comment = this.blockComment;
182 if (!comment)
183 return;
184 if (!comment.start && comment[0])
185 comment = comment[0];
186
187 var iterator = new TokenIterator(session, cursor.row, cursor.column);
188 var token = iterator.getCurrentToken();
189
190 var sel = session.selection;
191 var initialRange = session.selection.toOrientedRange();
192 var startRow, colDiff;
193
194 if (token && /comment/.test(token.type)) {
195 var startRange, endRange;
196 while (token && /comment/.test(token.type)) {
197 var i = token.value.indexOf(comment.start);
198 if (i != -1) {
199 var row = iterator.getCurrentTokenRow();
200 var column = iterator.getCurrentTokenColumn() + i;
201 startRange = new Range(row, column, row, column + comment.start.length);
202 break;
203 }
204 token = iterator.stepBackward();
205 }
206
207 var iterator = new TokenIterator(session, cursor.row, cursor.column);
208 var token = iterator.getCurrentToken();
209 while (token && /comment/.test(token.type)) {
210 var i = token.value.indexOf(comment.end);
211 if (i != -1) {
212 var row = iterator.getCurrentTokenRow();
213 var column = iterator.getCurrentTokenColumn() + i;
214 endRange = new Range(row, column, row, column + comment.end.length);
215 break;
216 }
217 token = iterator.stepForward();
218 }
219 if (endRange)
220 session.remove(endRange);
221 if (startRange) {
222 session.remove(startRange);
223 startRow = startRange.start.row;
224 colDiff = -comment.start.length;
225 }
226 } else {
227 colDiff = comment.start.length;
228 startRow = range.start.row;
229 session.insert(range.end, comment.end);
230 session.insert(range.start, comment.start);
231 }
232 // todo: selection should have ended up in the right place automatically!
233 if (initialRange.start.row == startRow)
234 initialRange.start.column += colDiff;
235 if (initialRange.end.row == startRow)
236 initialRange.end.column += colDiff;
237 session.selection.fromOrientedRange(initialRange);
238 };
239
240 this.getNextLineIndent = function(state, line, tab) {
241 return this.$getIndent(line);
242 };
243
244 this.checkOutdent = function(state, line, input) {
245 return false;
246 };
247
248 this.autoOutdent = function(state, doc, row) {
249 };
250
251 this.$getIndent = function(line) {
252 return line.match(/^\s*/)[0];
253 };
254
255 this.createWorker = function(session) {
256 return null;
257 };
258
259 this.createModeDelegates = function (mapping) {
260 this.$embeds = [];
261 this.$modes = {};
262 for (let i in mapping) {
263 if (mapping[i]) {
264 var Mode = mapping[i];
265 var id = Mode.prototype.$id;
266 var mode = config.$modes[id];
267 if (!mode)
268 config.$modes[id] = mode = new Mode();
269 if (!config.$modes[i])
270 config.$modes[i] = mode;
271 this.$embeds.push(i);
272 this.$modes[i] = mode;
273 }
274 }
275
276 var delegations = ["toggleBlockComment", "toggleCommentLines", "getNextLineIndent",
277 "checkOutdent", "autoOutdent", "transformAction", "getCompletions"];
278
279 for (let i = 0; i < delegations.length; i++) {
280 (function(scope) {
281 var functionName = delegations[i];
282 var defaultHandler = scope[functionName];
283 scope[delegations[i]] =
284 /** @this {import("../../ace-internal").Ace.SyntaxMode} */
285 function () {
286 return this.$delegator(functionName, arguments, defaultHandler);
287 };
288 }(this));
289 }
290 };
291
292 /**
293 * @this {SyntaxMode}
294 */
295 this.$delegator = function(method, args, defaultHandler) {
296 var state = args[0] || "start";
297 if (typeof state != "string") {
298 if (Array.isArray(state[2])) {
299 var language = state[2][state[2].length - 1];
300 var mode = this.$modes[language];
301 if (mode)
302 return mode[method].apply(mode, [state[1]].concat([].slice.call(args, 1)));
303 }
304 state = state[0] || "start";
305 }
306
307 for (var i = 0; i < this.$embeds.length; i++) {
308 if (!this.$modes[this.$embeds[i]]) continue;
309
310 var split = state.split(this.$embeds[i]);
311 if (!split[0] && split[1]) {
312 args[0] = split[1];
313 var mode = this.$modes[this.$embeds[i]];
314 return mode[method].apply(mode, args);
315 }
316 }
317 var ret = defaultHandler.apply(this, args);
318 return defaultHandler ? ret : undefined;
319 };
320
321 /**
322 * @this {SyntaxMode}
323 */
324 this.transformAction = function(state, action, editor, session, param) {
325 if (this.$behaviour) {
326 var behaviours = this.$behaviour.getBehaviours();
327 for (var key in behaviours) {
328 if (behaviours[key][action]) {
329 var ret = behaviours[key][action].apply(this, arguments);
330 if (ret) {
331 return ret;
332 }
333 }
334 }
335 }
336 };
337
338 /**
339 * @this {SyntaxMode}
340 */
341 this.getKeywords = function(append) {
342 // this is for autocompletion to pick up regexp'ed keywords
343 if (!this.completionKeywords) {
344 var rules = this.$tokenizer["rules"];
345 var completionKeywords = [];
346 for (var rule in rules) {
347 var ruleItr = rules[rule];
348 for (var r = 0, l = ruleItr.length; r < l; r++) {
349 if (typeof ruleItr[r].token === "string") {
350 if (/keyword|support|storage/.test(ruleItr[r].token))
351 completionKeywords.push(ruleItr[r].regex);
352 }
353 else if (typeof ruleItr[r].token === "object") {
354 for (var a = 0, aLength = ruleItr[r].token.length; a < aLength; a++) {
355 if (/keyword|support|storage/.test(ruleItr[r].token[a])) {
356 // drop surrounding parens
357 var rule = ruleItr[r].regex.match(/\(.+?\)/g)[a];
358 completionKeywords.push(rule.substr(1, rule.length - 2));
359 }
360 }
361 }
362 }
363 }
364 this.completionKeywords = completionKeywords;
365 }
366 // this is for highlighting embed rules, like HAML/Ruby or Obj-C/C
367 if (!append)
368 return this.$keywordList;
369 return completionKeywords.concat(this.$keywordList || []);
370 };
371
372 /**
373 * @this {SyntaxMode}
374 */
375 this.$createKeywordList = function() {
376 if (!this.$highlightRules)
377 this.getTokenizer();
378 return this.$keywordList = this.$highlightRules.$keywordList || [];
379 };
380
381 /**
382 * @this {SyntaxMode}
383 */
384 this.getCompletions = function(state, session, pos, prefix) {
385 var keywords = this.$keywordList || this.$createKeywordList();
386 return keywords.map(function(word) {
387 return {
388 name: word,
389 value: word,
390 score: 0,
391 meta: "keyword"
392 };
393 });
394 };
395
396 this.$id = "ace/mode/text";
397 }).call(Mode.prototype);
398
399 exports.Mode = Mode;