overleaf/services/web/public/js/ace/mode/text.js

385 lines
14 KiB
JavaScript
Raw Normal View History

2014-02-12 05:23:40 -05:00
/* ***** BEGIN LICENSE BLOCK *****
* Distributed under the BSD license:
*
* Copyright (c) 2010, Ajax.org B.V.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Ajax.org B.V. nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* ***** END LICENSE BLOCK ***** */
define(function(require, exports, module) {
"use strict";
var Tokenizer = require("../tokenizer").Tokenizer;
var TextHighlightRules = require("./text_highlight_rules").TextHighlightRules;
var Behaviour = require("./behaviour").Behaviour;
var unicode = require("../unicode");
var lang = require("../lib/lang");
var TokenIterator = require("../token_iterator").TokenIterator;
var Range = require("../range").Range;
var Mode = function() {
this.HighlightRules = TextHighlightRules;
this.$behaviour = new Behaviour();
};
(function() {
this.tokenRe = new RegExp("^["
+ unicode.packages.L
+ unicode.packages.Mn + unicode.packages.Mc
+ unicode.packages.Nd
+ unicode.packages.Pc + "\\$_]+", "g"
);
this.nonTokenRe = new RegExp("^(?:[^"
+ unicode.packages.L
+ unicode.packages.Mn + unicode.packages.Mc
+ unicode.packages.Nd
+ unicode.packages.Pc + "\\$_]|\s])+", "g"
);
this.getTokenizer = function() {
if (!this.$tokenizer) {
this.$highlightRules = new this.HighlightRules();
this.$tokenizer = new Tokenizer(this.$highlightRules.getRules());
}
return this.$tokenizer;
};
this.lineCommentStart = "";
this.blockComment = "";
this.toggleCommentLines = function(state, session, startRow, endRow) {
var doc = session.doc;
var ignoreBlankLines = true;
var shouldRemove = true;
var minIndent = Infinity;
var tabSize = session.getTabSize();
var insertAtTabStop = false;
if (!this.lineCommentStart) {
if (!this.blockComment)
return false;
var lineCommentStart = this.blockComment.start;
var lineCommentEnd = this.blockComment.end;
var regexpStart = new RegExp("^(\\s*)(?:" + lang.escapeRegExp(lineCommentStart) + ")");
var regexpEnd = new RegExp("(?:" + lang.escapeRegExp(lineCommentEnd) + ")\\s*$");
var comment = function(line, i) {
if (testRemove(line, i))
return;
if (!ignoreBlankLines || /\S/.test(line)) {
doc.insertInLine({row: i, column: line.length}, lineCommentEnd);
doc.insertInLine({row: i, column: minIndent}, lineCommentStart);
}
};
var uncomment = function(line, i) {
var m;
if (m = line.match(regexpEnd))
doc.removeInLine(i, line.length - m[0].length, line.length);
if (m = line.match(regexpStart))
doc.removeInLine(i, m[1].length, m[0].length);
};
var testRemove = function(line, row) {
if (regexpStart.test(line))
return true;
var tokens = session.getTokens(row);
for (var i = 0; i < tokens.length; i++) {
if (tokens[i].type === 'comment')
return true;
}
};
} else {
if (Array.isArray(this.lineCommentStart)) {
var regexpStart = this.lineCommentStart.map(lang.escapeRegExp).join("|");
var lineCommentStart = this.lineCommentStart[0];
} else {
var regexpStart = lang.escapeRegExp(this.lineCommentStart);
var lineCommentStart = this.lineCommentStart;
}
regexpStart = new RegExp("^(\\s*)(?:" + regexpStart + ") ?");
insertAtTabStop = session.getUseSoftTabs();
var uncomment = function(line, i) {
var m = line.match(regexpStart);
if (!m) return;
var start = m[1].length, end = m[0].length;
if (!shouldInsertSpace(line, start, end) && m[0][end - 1] == " ")
end--;
doc.removeInLine(i, start, end);
};
var commentWithSpace = lineCommentStart + " ";
var comment = function(line, i) {
if (!ignoreBlankLines || /\S/.test(line)) {
if (shouldInsertSpace(line, minIndent, minIndent))
doc.insertInLine({row: i, column: minIndent}, commentWithSpace);
else
doc.insertInLine({row: i, column: minIndent}, lineCommentStart);
}
};
var testRemove = function(line, i) {
return regexpStart.test(line);
};
var shouldInsertSpace = function(line, before, after) {
var spaces = 0;
while (before-- && line.charAt(before) == " ")
spaces++;
if (spaces % tabSize != 0)
return false;
var spaces = 0;
while (line.charAt(after++) == " ")
spaces++;
if (tabSize > 2)
return spaces % tabSize != tabSize - 1;
else
return spaces % tabSize == 0;
return true;
};
}
function iter(fun) {
for (var i = startRow; i <= endRow; i++)
fun(doc.getLine(i), i);
}
var minEmptyLength = Infinity;
iter(function(line, i) {
var indent = line.search(/\S/);
if (indent !== -1) {
if (indent < minIndent)
minIndent = indent;
if (shouldRemove && !testRemove(line, i))
shouldRemove = false;
} else if (minEmptyLength > line.length) {
minEmptyLength = line.length;
}
});
if (minIndent == Infinity) {
minIndent = minEmptyLength;
ignoreBlankLines = false;
shouldRemove = false;
}
if (insertAtTabStop && minIndent % tabSize != 0)
minIndent = Math.floor(minIndent / tabSize) * tabSize;
iter(shouldRemove ? uncomment : comment);
};
this.toggleBlockComment = function(state, session, range, cursor) {
var comment = this.blockComment;
if (!comment)
return;
if (!comment.start && comment[0])
comment = comment[0];
var iterator = new TokenIterator(session, cursor.row, cursor.column);
var token = iterator.getCurrentToken();
var sel = session.selection;
var initialRange = session.selection.toOrientedRange();
var startRow, colDiff;
if (token && /comment/.test(token.type)) {
var startRange, endRange;
while (token && /comment/.test(token.type)) {
var i = token.value.indexOf(comment.start);
if (i != -1) {
var row = iterator.getCurrentTokenRow();
var column = iterator.getCurrentTokenColumn() + i;
startRange = new Range(row, column, row, column + comment.start.length);
break
}
token = iterator.stepBackward();
};
var iterator = new TokenIterator(session, cursor.row, cursor.column);
var token = iterator.getCurrentToken();
while (token && /comment/.test(token.type)) {
var i = token.value.indexOf(comment.end);
if (i != -1) {
var row = iterator.getCurrentTokenRow();
var column = iterator.getCurrentTokenColumn() + i;
endRange = new Range(row, column, row, column + comment.end.length);
break;
}
token = iterator.stepForward();
}
if (endRange)
session.remove(endRange);
if (startRange) {
session.remove(startRange);
startRow = startRange.start.row;
colDiff = -comment.start.length
}
} else {
colDiff = comment.start.length
startRow = range.start.row;
session.insert(range.end, comment.end);
session.insert(range.start, comment.start);
}
// todo: selection should have ended up in the right place automatically!
if (initialRange.start.row == startRow)
initialRange.start.column += colDiff;
if (initialRange.end.row == startRow)
initialRange.end.column += colDiff;
session.selection.fromOrientedRange(initialRange);
};
this.getNextLineIndent = function(state, line, tab) {
return this.$getIndent(line);
};
this.checkOutdent = function(state, line, input) {
return false;
};
this.autoOutdent = function(state, doc, row) {
};
this.$getIndent = function(line) {
return line.match(/^\s*/)[0];
};
this.createWorker = function(session) {
return null;
};
this.createModeDelegates = function (mapping) {
this.$embeds = [];
this.$modes = {};
for (var i in mapping) {
if (mapping[i]) {
this.$embeds.push(i);
this.$modes[i] = new mapping[i]();
}
}
var delegations = ['toggleCommentLines', 'getNextLineIndent', 'checkOutdent', 'autoOutdent', 'transformAction', 'getCompletions'];
for (var i = 0; i < delegations.length; i++) {
(function(scope) {
var functionName = delegations[i];
var defaultHandler = scope[functionName];
scope[delegations[i]] = function() {
return this.$delegator(functionName, arguments, defaultHandler);
}
} (this));
}
};
this.$delegator = function(method, args, defaultHandler) {
var state = args[0];
if (typeof state != "string")
state = state[0];
for (var i = 0; i < this.$embeds.length; i++) {
if (!this.$modes[this.$embeds[i]]) continue;
var split = state.split(this.$embeds[i]);
if (!split[0] && split[1]) {
args[0] = split[1];
var mode = this.$modes[this.$embeds[i]];
return mode[method].apply(mode, args);
}
}
var ret = defaultHandler.apply(this, args);
return defaultHandler ? ret : undefined;
};
this.transformAction = function(state, action, editor, session, param) {
if (this.$behaviour) {
var behaviours = this.$behaviour.getBehaviours();
for (var key in behaviours) {
if (behaviours[key][action]) {
var ret = behaviours[key][action].apply(this, arguments);
if (ret) {
return ret;
}
}
}
}
};
this.getKeywords = function(append) {
// this is for autocompletion to pick up regexp'ed keywords
if (!this.completionKeywords) {
var rules = this.$tokenizer.rules;
var completionKeywords = [];
for (var rule in rules) {
var ruleItr = rules[rule];
for (var r = 0, l = ruleItr.length; r < l; r++) {
if (typeof ruleItr[r].token === "string") {
if (/keyword|support|storage/.test(ruleItr[r].token))
completionKeywords.push(ruleItr[r].regex);
}
else if (typeof ruleItr[r].token === "object") {
for (var a = 0, aLength = ruleItr[r].token.length; a < aLength; a++) {
if (/keyword|support|storage/.test(ruleItr[r].token[a])) {
// drop surrounding parens
var rule = ruleItr[r].regex.match(/\(.+?\)/g)[a];
completionKeywords.push(rule.substr(1, rule.length - 2));
}
}
}
}
}
this.completionKeywords = completionKeywords;
}
// this is for highlighting embed rules, like HAML/Ruby or Obj-C/C
if (!append)
return this.$keywordList;
return completionKeywords.concat(this.$keywordList || []);
};
this.$createKeywordList = function() {
if (!this.$highlightRules)
this.getTokenizer();
return this.$keywordList = this.$highlightRules.$keywordList || [];
}
this.getCompletions = function(state, session, pos, prefix) {
var keywords = this.$keywordList || this.$createKeywordList();
return keywords.map(function(word) {
return {
name: word,
value: word,
score: 0,
meta: "keyword"
};
});
};
}).call(Mode.prototype);
exports.Mode = Mode;
});