|
@@ -1,895 +0,0 @@
|
|
|
-var assert = require("assert");
|
|
|
-var sourceMap = require("source-map");
|
|
|
-var normalizeOptions = require("./options").normalize;
|
|
|
-var secretKey = require("private").makeUniqueKey();
|
|
|
-var types = require("./types");
|
|
|
-var isString = types.builtInTypes.string;
|
|
|
-var comparePos = require("./util").comparePos;
|
|
|
-var Mapping = require("./mapping");
|
|
|
-
|
|
|
-// Goals:
|
|
|
-// 1. Minimize new string creation.
|
|
|
-// 2. Keep (de)identation O(lines) time.
|
|
|
-// 3. Permit negative indentations.
|
|
|
-// 4. Enforce immutability.
|
|
|
-// 5. No newline characters.
|
|
|
-
|
|
|
-function getSecret(lines) {
|
|
|
- return lines[secretKey];
|
|
|
-}
|
|
|
-
|
|
|
-function Lines(infos, sourceFileName) {
|
|
|
- assert.ok(this instanceof Lines);
|
|
|
- assert.ok(infos.length > 0);
|
|
|
-
|
|
|
- if (sourceFileName) {
|
|
|
- isString.assert(sourceFileName);
|
|
|
- } else {
|
|
|
- sourceFileName = null;
|
|
|
- }
|
|
|
-
|
|
|
- Object.defineProperty(this, secretKey, {
|
|
|
- value: {
|
|
|
- infos: infos,
|
|
|
- mappings: [],
|
|
|
- name: sourceFileName,
|
|
|
- cachedSourceMap: null
|
|
|
- }
|
|
|
- });
|
|
|
-
|
|
|
- if (sourceFileName) {
|
|
|
- getSecret(this).mappings.push(new Mapping(this, {
|
|
|
- start: this.firstPos(),
|
|
|
- end: this.lastPos()
|
|
|
- }));
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-// Exposed for instanceof checks. The fromString function should be used
|
|
|
-// to create new Lines objects.
|
|
|
-exports.Lines = Lines;
|
|
|
-var Lp = Lines.prototype;
|
|
|
-
|
|
|
-// These properties used to be assigned to each new object in the Lines
|
|
|
-// constructor, but we can more efficiently stuff them into the secret and
|
|
|
-// let these lazy accessors compute their values on-the-fly.
|
|
|
-Object.defineProperties(Lp, {
|
|
|
- length: {
|
|
|
- get: function() {
|
|
|
- return getSecret(this).infos.length;
|
|
|
- }
|
|
|
- },
|
|
|
-
|
|
|
- name: {
|
|
|
- get: function() {
|
|
|
- return getSecret(this).name;
|
|
|
- }
|
|
|
- }
|
|
|
-});
|
|
|
-
|
|
|
-function copyLineInfo(info) {
|
|
|
- return {
|
|
|
- line: info.line,
|
|
|
- indent: info.indent,
|
|
|
- locked: info.locked,
|
|
|
- sliceStart: info.sliceStart,
|
|
|
- sliceEnd: info.sliceEnd
|
|
|
- };
|
|
|
-}
|
|
|
-
|
|
|
-var fromStringCache = {};
|
|
|
-var hasOwn = fromStringCache.hasOwnProperty;
|
|
|
-var maxCacheKeyLen = 10;
|
|
|
-
|
|
|
-function countSpaces(spaces, tabWidth) {
|
|
|
- var count = 0;
|
|
|
- var len = spaces.length;
|
|
|
-
|
|
|
- for (var i = 0; i < len; ++i) {
|
|
|
- switch (spaces.charCodeAt(i)) {
|
|
|
- case 9: // '\t'
|
|
|
- assert.strictEqual(typeof tabWidth, "number");
|
|
|
- assert.ok(tabWidth > 0);
|
|
|
-
|
|
|
- var next = Math.ceil(count / tabWidth) * tabWidth;
|
|
|
- if (next === count) {
|
|
|
- count += tabWidth;
|
|
|
- } else {
|
|
|
- count = next;
|
|
|
- }
|
|
|
-
|
|
|
- break;
|
|
|
-
|
|
|
- case 11: // '\v'
|
|
|
- case 12: // '\f'
|
|
|
- case 13: // '\r'
|
|
|
- case 0xfeff: // zero-width non-breaking space
|
|
|
- // These characters contribute nothing to indentation.
|
|
|
- break;
|
|
|
-
|
|
|
- case 32: // ' '
|
|
|
- default: // Treat all other whitespace like ' '.
|
|
|
- count += 1;
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return count;
|
|
|
-}
|
|
|
-exports.countSpaces = countSpaces;
|
|
|
-
|
|
|
-var leadingSpaceExp = /^\s*/;
|
|
|
-
|
|
|
-// As specified here: http://www.ecma-international.org/ecma-262/6.0/#sec-line-terminators
|
|
|
-var lineTerminatorSeqExp =
|
|
|
- /\u000D\u000A|\u000D(?!\u000A)|\u000A|\u2028|\u2029/;
|
|
|
-
|
|
|
-/**
|
|
|
- * @param {Object} options - Options object that configures printing.
|
|
|
- */
|
|
|
-function fromString(string, options) {
|
|
|
- if (string instanceof Lines)
|
|
|
- return string;
|
|
|
-
|
|
|
- string += "";
|
|
|
-
|
|
|
- var tabWidth = options && options.tabWidth;
|
|
|
- var tabless = string.indexOf("\t") < 0;
|
|
|
- var locked = !! (options && options.locked);
|
|
|
- var cacheable = !options && tabless && (string.length <= maxCacheKeyLen);
|
|
|
-
|
|
|
- assert.ok(tabWidth || tabless, "No tab width specified but encountered tabs in string\n" + string);
|
|
|
-
|
|
|
- if (cacheable && hasOwn.call(fromStringCache, string))
|
|
|
- return fromStringCache[string];
|
|
|
-
|
|
|
- var lines = new Lines(string.split(lineTerminatorSeqExp).map(function(line) {
|
|
|
- var spaces = leadingSpaceExp.exec(line)[0];
|
|
|
- return {
|
|
|
- line: line,
|
|
|
- indent: countSpaces(spaces, tabWidth),
|
|
|
- // Boolean indicating whether this line can be reindented.
|
|
|
- locked: locked,
|
|
|
- sliceStart: spaces.length,
|
|
|
- sliceEnd: line.length
|
|
|
- };
|
|
|
- }), normalizeOptions(options).sourceFileName);
|
|
|
-
|
|
|
- if (cacheable)
|
|
|
- fromStringCache[string] = lines;
|
|
|
-
|
|
|
- return lines;
|
|
|
-}
|
|
|
-exports.fromString = fromString;
|
|
|
-
|
|
|
-function isOnlyWhitespace(string) {
|
|
|
- return !/\S/.test(string);
|
|
|
-}
|
|
|
-
|
|
|
-Lp.toString = function(options) {
|
|
|
- return this.sliceString(this.firstPos(), this.lastPos(), options);
|
|
|
-};
|
|
|
-
|
|
|
-Lp.getSourceMap = function(sourceMapName, sourceRoot) {
|
|
|
- if (!sourceMapName) {
|
|
|
- // Although we could make up a name or generate an anonymous
|
|
|
- // source map, instead we assume that any consumer who does not
|
|
|
- // provide a name does not actually want a source map.
|
|
|
- return null;
|
|
|
- }
|
|
|
-
|
|
|
- var targetLines = this;
|
|
|
-
|
|
|
- function updateJSON(json) {
|
|
|
- json = json || {};
|
|
|
-
|
|
|
- isString.assert(sourceMapName);
|
|
|
- json.file = sourceMapName;
|
|
|
-
|
|
|
- if (sourceRoot) {
|
|
|
- isString.assert(sourceRoot);
|
|
|
- json.sourceRoot = sourceRoot;
|
|
|
- }
|
|
|
-
|
|
|
- return json;
|
|
|
- }
|
|
|
-
|
|
|
- var secret = getSecret(targetLines);
|
|
|
- if (secret.cachedSourceMap) {
|
|
|
- // Since Lines objects are immutable, we can reuse any source map
|
|
|
- // that was previously generated. Nevertheless, we return a new
|
|
|
- // JSON object here to protect the cached source map from outside
|
|
|
- // modification.
|
|
|
- return updateJSON(secret.cachedSourceMap.toJSON());
|
|
|
- }
|
|
|
-
|
|
|
- var smg = new sourceMap.SourceMapGenerator(updateJSON());
|
|
|
- var sourcesToContents = {};
|
|
|
-
|
|
|
- secret.mappings.forEach(function(mapping) {
|
|
|
- var sourceCursor = mapping.sourceLines.skipSpaces(
|
|
|
- mapping.sourceLoc.start
|
|
|
- ) || mapping.sourceLines.lastPos();
|
|
|
-
|
|
|
- var targetCursor = targetLines.skipSpaces(
|
|
|
- mapping.targetLoc.start
|
|
|
- ) || targetLines.lastPos();
|
|
|
-
|
|
|
- while (comparePos(sourceCursor, mapping.sourceLoc.end) < 0 &&
|
|
|
- comparePos(targetCursor, mapping.targetLoc.end) < 0) {
|
|
|
-
|
|
|
- var sourceChar = mapping.sourceLines.charAt(sourceCursor);
|
|
|
- var targetChar = targetLines.charAt(targetCursor);
|
|
|
- assert.strictEqual(sourceChar, targetChar);
|
|
|
-
|
|
|
- var sourceName = mapping.sourceLines.name;
|
|
|
-
|
|
|
- // Add mappings one character at a time for maximum resolution.
|
|
|
- smg.addMapping({
|
|
|
- source: sourceName,
|
|
|
- original: { line: sourceCursor.line,
|
|
|
- column: sourceCursor.column },
|
|
|
- generated: { line: targetCursor.line,
|
|
|
- column: targetCursor.column }
|
|
|
- });
|
|
|
-
|
|
|
- if (!hasOwn.call(sourcesToContents, sourceName)) {
|
|
|
- var sourceContent = mapping.sourceLines.toString();
|
|
|
- smg.setSourceContent(sourceName, sourceContent);
|
|
|
- sourcesToContents[sourceName] = sourceContent;
|
|
|
- }
|
|
|
-
|
|
|
- targetLines.nextPos(targetCursor, true);
|
|
|
- mapping.sourceLines.nextPos(sourceCursor, true);
|
|
|
- }
|
|
|
- });
|
|
|
-
|
|
|
- secret.cachedSourceMap = smg;
|
|
|
-
|
|
|
- return smg.toJSON();
|
|
|
-};
|
|
|
-
|
|
|
-Lp.bootstrapCharAt = function(pos) {
|
|
|
- assert.strictEqual(typeof pos, "object");
|
|
|
- assert.strictEqual(typeof pos.line, "number");
|
|
|
- assert.strictEqual(typeof pos.column, "number");
|
|
|
-
|
|
|
- var line = pos.line,
|
|
|
- column = pos.column,
|
|
|
- strings = this.toString().split(lineTerminatorSeqExp),
|
|
|
- string = strings[line - 1];
|
|
|
-
|
|
|
- if (typeof string === "undefined")
|
|
|
- return "";
|
|
|
-
|
|
|
- if (column === string.length &&
|
|
|
- line < strings.length)
|
|
|
- return "\n";
|
|
|
-
|
|
|
- if (column >= string.length)
|
|
|
- return "";
|
|
|
-
|
|
|
- return string.charAt(column);
|
|
|
-};
|
|
|
-
|
|
|
-Lp.charAt = function(pos) {
|
|
|
- assert.strictEqual(typeof pos, "object");
|
|
|
- assert.strictEqual(typeof pos.line, "number");
|
|
|
- assert.strictEqual(typeof pos.column, "number");
|
|
|
-
|
|
|
- var line = pos.line,
|
|
|
- column = pos.column,
|
|
|
- secret = getSecret(this),
|
|
|
- infos = secret.infos,
|
|
|
- info = infos[line - 1],
|
|
|
- c = column;
|
|
|
-
|
|
|
- if (typeof info === "undefined" || c < 0)
|
|
|
- return "";
|
|
|
-
|
|
|
- var indent = this.getIndentAt(line);
|
|
|
- if (c < indent)
|
|
|
- return " ";
|
|
|
-
|
|
|
- c += info.sliceStart - indent;
|
|
|
-
|
|
|
- if (c === info.sliceEnd &&
|
|
|
- line < this.length)
|
|
|
- return "\n";
|
|
|
-
|
|
|
- if (c >= info.sliceEnd)
|
|
|
- return "";
|
|
|
-
|
|
|
- return info.line.charAt(c);
|
|
|
-};
|
|
|
-
|
|
|
-Lp.stripMargin = function(width, skipFirstLine) {
|
|
|
- if (width === 0)
|
|
|
- return this;
|
|
|
-
|
|
|
- assert.ok(width > 0, "negative margin: " + width);
|
|
|
-
|
|
|
- if (skipFirstLine && this.length === 1)
|
|
|
- return this;
|
|
|
-
|
|
|
- var secret = getSecret(this);
|
|
|
-
|
|
|
- var lines = new Lines(secret.infos.map(function(info, i) {
|
|
|
- if (info.line && (i > 0 || !skipFirstLine)) {
|
|
|
- info = copyLineInfo(info);
|
|
|
- info.indent = Math.max(0, info.indent - width);
|
|
|
- }
|
|
|
- return info;
|
|
|
- }));
|
|
|
-
|
|
|
- if (secret.mappings.length > 0) {
|
|
|
- var newMappings = getSecret(lines).mappings;
|
|
|
- assert.strictEqual(newMappings.length, 0);
|
|
|
- secret.mappings.forEach(function(mapping) {
|
|
|
- newMappings.push(mapping.indent(width, skipFirstLine, true));
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- return lines;
|
|
|
-};
|
|
|
-
|
|
|
-Lp.indent = function(by) {
|
|
|
- if (by === 0)
|
|
|
- return this;
|
|
|
-
|
|
|
- var secret = getSecret(this);
|
|
|
-
|
|
|
- var lines = new Lines(secret.infos.map(function(info) {
|
|
|
- if (info.line && ! info.locked) {
|
|
|
- info = copyLineInfo(info);
|
|
|
- info.indent += by;
|
|
|
- }
|
|
|
- return info
|
|
|
- }));
|
|
|
-
|
|
|
- if (secret.mappings.length > 0) {
|
|
|
- var newMappings = getSecret(lines).mappings;
|
|
|
- assert.strictEqual(newMappings.length, 0);
|
|
|
- secret.mappings.forEach(function(mapping) {
|
|
|
- newMappings.push(mapping.indent(by));
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- return lines;
|
|
|
-};
|
|
|
-
|
|
|
-Lp.indentTail = function(by) {
|
|
|
- if (by === 0)
|
|
|
- return this;
|
|
|
-
|
|
|
- if (this.length < 2)
|
|
|
- return this;
|
|
|
-
|
|
|
- var secret = getSecret(this);
|
|
|
-
|
|
|
- var lines = new Lines(secret.infos.map(function(info, i) {
|
|
|
- if (i > 0 && info.line && ! info.locked) {
|
|
|
- info = copyLineInfo(info);
|
|
|
- info.indent += by;
|
|
|
- }
|
|
|
-
|
|
|
- return info;
|
|
|
- }));
|
|
|
-
|
|
|
- if (secret.mappings.length > 0) {
|
|
|
- var newMappings = getSecret(lines).mappings;
|
|
|
- assert.strictEqual(newMappings.length, 0);
|
|
|
- secret.mappings.forEach(function(mapping) {
|
|
|
- newMappings.push(mapping.indent(by, true));
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- return lines;
|
|
|
-};
|
|
|
-
|
|
|
-Lp.lockIndentTail = function () {
|
|
|
- if (this.length < 2) {
|
|
|
- return this;
|
|
|
- }
|
|
|
-
|
|
|
- var infos = getSecret(this).infos;
|
|
|
-
|
|
|
- return new Lines(infos.map(function (info, i) {
|
|
|
- info = copyLineInfo(info);
|
|
|
- info.locked = i > 0;
|
|
|
- return info;
|
|
|
- }));
|
|
|
-};
|
|
|
-
|
|
|
-Lp.getIndentAt = function(line) {
|
|
|
- assert.ok(line >= 1, "no line " + line + " (line numbers start from 1)");
|
|
|
- var secret = getSecret(this),
|
|
|
- info = secret.infos[line - 1];
|
|
|
- return Math.max(info.indent, 0);
|
|
|
-};
|
|
|
-
|
|
|
-Lp.guessTabWidth = function() {
|
|
|
- var secret = getSecret(this);
|
|
|
- if (hasOwn.call(secret, "cachedTabWidth")) {
|
|
|
- return secret.cachedTabWidth;
|
|
|
- }
|
|
|
-
|
|
|
- var counts = []; // Sparse array.
|
|
|
- var lastIndent = 0;
|
|
|
-
|
|
|
- for (var line = 1, last = this.length; line <= last; ++line) {
|
|
|
- var info = secret.infos[line - 1];
|
|
|
- var sliced = info.line.slice(info.sliceStart, info.sliceEnd);
|
|
|
-
|
|
|
- // Whitespace-only lines don't tell us much about the likely tab
|
|
|
- // width of this code.
|
|
|
- if (isOnlyWhitespace(sliced)) {
|
|
|
- continue;
|
|
|
- }
|
|
|
-
|
|
|
- var diff = Math.abs(info.indent - lastIndent);
|
|
|
- counts[diff] = ~~counts[diff] + 1;
|
|
|
- lastIndent = info.indent;
|
|
|
- }
|
|
|
-
|
|
|
- var maxCount = -1;
|
|
|
- var result = 2;
|
|
|
-
|
|
|
- for (var tabWidth = 1;
|
|
|
- tabWidth < counts.length;
|
|
|
- tabWidth += 1) {
|
|
|
- if (hasOwn.call(counts, tabWidth) &&
|
|
|
- counts[tabWidth] > maxCount) {
|
|
|
- maxCount = counts[tabWidth];
|
|
|
- result = tabWidth;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return secret.cachedTabWidth = result;
|
|
|
-};
|
|
|
-
|
|
|
-// Determine if the list of lines has a first line that starts with a //
|
|
|
-// or /* comment. If this is the case, the code may need to be wrapped in
|
|
|
-// parens to avoid ASI issues.
|
|
|
-Lp.startsWithComment = function () {
|
|
|
- var secret = getSecret(this);
|
|
|
- if (secret.infos.length === 0) {
|
|
|
- return false;
|
|
|
- }
|
|
|
- var firstLineInfo = secret.infos[0],
|
|
|
- sliceStart = firstLineInfo.sliceStart,
|
|
|
- sliceEnd = firstLineInfo.sliceEnd,
|
|
|
- firstLine = firstLineInfo.line.slice(sliceStart, sliceEnd).trim();
|
|
|
- return firstLine.length === 0 ||
|
|
|
- firstLine.slice(0, 2) === "//" ||
|
|
|
- firstLine.slice(0, 2) === "/*";
|
|
|
-};
|
|
|
-
|
|
|
-Lp.isOnlyWhitespace = function() {
|
|
|
- return isOnlyWhitespace(this.toString());
|
|
|
-};
|
|
|
-
|
|
|
-Lp.isPrecededOnlyByWhitespace = function(pos) {
|
|
|
- var secret = getSecret(this);
|
|
|
- var info = secret.infos[pos.line - 1];
|
|
|
- var indent = Math.max(info.indent, 0);
|
|
|
-
|
|
|
- var diff = pos.column - indent;
|
|
|
- if (diff <= 0) {
|
|
|
- // If pos.column does not exceed the indentation amount, then
|
|
|
- // there must be only whitespace before it.
|
|
|
- return true;
|
|
|
- }
|
|
|
-
|
|
|
- var start = info.sliceStart;
|
|
|
- var end = Math.min(start + diff, info.sliceEnd);
|
|
|
- var prefix = info.line.slice(start, end);
|
|
|
-
|
|
|
- return isOnlyWhitespace(prefix);
|
|
|
-};
|
|
|
-
|
|
|
-Lp.getLineLength = function(line) {
|
|
|
- var secret = getSecret(this),
|
|
|
- info = secret.infos[line - 1];
|
|
|
- return this.getIndentAt(line) + info.sliceEnd - info.sliceStart;
|
|
|
-};
|
|
|
-
|
|
|
-Lp.nextPos = function(pos, skipSpaces) {
|
|
|
- var l = Math.max(pos.line, 0),
|
|
|
- c = Math.max(pos.column, 0);
|
|
|
-
|
|
|
- if (c < this.getLineLength(l)) {
|
|
|
- pos.column += 1;
|
|
|
-
|
|
|
- return skipSpaces
|
|
|
- ? !!this.skipSpaces(pos, false, true)
|
|
|
- : true;
|
|
|
- }
|
|
|
-
|
|
|
- if (l < this.length) {
|
|
|
- pos.line += 1;
|
|
|
- pos.column = 0;
|
|
|
-
|
|
|
- return skipSpaces
|
|
|
- ? !!this.skipSpaces(pos, false, true)
|
|
|
- : true;
|
|
|
- }
|
|
|
-
|
|
|
- return false;
|
|
|
-};
|
|
|
-
|
|
|
-Lp.prevPos = function(pos, skipSpaces) {
|
|
|
- var l = pos.line,
|
|
|
- c = pos.column;
|
|
|
-
|
|
|
- if (c < 1) {
|
|
|
- l -= 1;
|
|
|
-
|
|
|
- if (l < 1)
|
|
|
- return false;
|
|
|
-
|
|
|
- c = this.getLineLength(l);
|
|
|
-
|
|
|
- } else {
|
|
|
- c = Math.min(c - 1, this.getLineLength(l));
|
|
|
- }
|
|
|
-
|
|
|
- pos.line = l;
|
|
|
- pos.column = c;
|
|
|
-
|
|
|
- return skipSpaces
|
|
|
- ? !!this.skipSpaces(pos, true, true)
|
|
|
- : true;
|
|
|
-};
|
|
|
-
|
|
|
-Lp.firstPos = function() {
|
|
|
- // Trivial, but provided for completeness.
|
|
|
- return { line: 1, column: 0 };
|
|
|
-};
|
|
|
-
|
|
|
-Lp.lastPos = function() {
|
|
|
- return {
|
|
|
- line: this.length,
|
|
|
- column: this.getLineLength(this.length)
|
|
|
- };
|
|
|
-};
|
|
|
-
|
|
|
-Lp.skipSpaces = function(pos, backward, modifyInPlace) {
|
|
|
- if (pos) {
|
|
|
- pos = modifyInPlace ? pos : {
|
|
|
- line: pos.line,
|
|
|
- column: pos.column
|
|
|
- };
|
|
|
- } else if (backward) {
|
|
|
- pos = this.lastPos();
|
|
|
- } else {
|
|
|
- pos = this.firstPos();
|
|
|
- }
|
|
|
-
|
|
|
- if (backward) {
|
|
|
- while (this.prevPos(pos)) {
|
|
|
- if (!isOnlyWhitespace(this.charAt(pos)) &&
|
|
|
- this.nextPos(pos)) {
|
|
|
- return pos;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return null;
|
|
|
-
|
|
|
- } else {
|
|
|
- while (isOnlyWhitespace(this.charAt(pos))) {
|
|
|
- if (!this.nextPos(pos)) {
|
|
|
- return null;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return pos;
|
|
|
- }
|
|
|
-};
|
|
|
-
|
|
|
-Lp.trimLeft = function() {
|
|
|
- var pos = this.skipSpaces(this.firstPos(), false, true);
|
|
|
- return pos ? this.slice(pos) : emptyLines;
|
|
|
-};
|
|
|
-
|
|
|
-Lp.trimRight = function() {
|
|
|
- var pos = this.skipSpaces(this.lastPos(), true, true);
|
|
|
- return pos ? this.slice(this.firstPos(), pos) : emptyLines;
|
|
|
-};
|
|
|
-
|
|
|
-Lp.trim = function() {
|
|
|
- var start = this.skipSpaces(this.firstPos(), false, true);
|
|
|
- if (start === null)
|
|
|
- return emptyLines;
|
|
|
-
|
|
|
- var end = this.skipSpaces(this.lastPos(), true, true);
|
|
|
- assert.notStrictEqual(end, null);
|
|
|
-
|
|
|
- return this.slice(start, end);
|
|
|
-};
|
|
|
-
|
|
|
-Lp.eachPos = function(callback, startPos, skipSpaces) {
|
|
|
- var pos = this.firstPos();
|
|
|
-
|
|
|
- if (startPos) {
|
|
|
- pos.line = startPos.line,
|
|
|
- pos.column = startPos.column
|
|
|
- }
|
|
|
-
|
|
|
- if (skipSpaces && !this.skipSpaces(pos, false, true)) {
|
|
|
- return; // Encountered nothing but spaces.
|
|
|
- }
|
|
|
-
|
|
|
- do callback.call(this, pos);
|
|
|
- while (this.nextPos(pos, skipSpaces));
|
|
|
-};
|
|
|
-
|
|
|
-Lp.bootstrapSlice = function(start, end) {
|
|
|
- var strings = this.toString().split(
|
|
|
- lineTerminatorSeqExp
|
|
|
- ).slice(
|
|
|
- start.line - 1,
|
|
|
- end.line
|
|
|
- );
|
|
|
-
|
|
|
- strings.push(strings.pop().slice(0, end.column));
|
|
|
- strings[0] = strings[0].slice(start.column);
|
|
|
-
|
|
|
- return fromString(strings.join("\n"));
|
|
|
-};
|
|
|
-
|
|
|
-Lp.slice = function(start, end) {
|
|
|
- if (!end) {
|
|
|
- if (!start) {
|
|
|
- // The client seems to want a copy of this Lines object, but
|
|
|
- // Lines objects are immutable, so it's perfectly adequate to
|
|
|
- // return the same object.
|
|
|
- return this;
|
|
|
- }
|
|
|
-
|
|
|
- // Slice to the end if no end position was provided.
|
|
|
- end = this.lastPos();
|
|
|
- }
|
|
|
-
|
|
|
- var secret = getSecret(this);
|
|
|
- var sliced = secret.infos.slice(start.line - 1, end.line);
|
|
|
-
|
|
|
- if (start.line === end.line) {
|
|
|
- sliced[0] = sliceInfo(sliced[0], start.column, end.column);
|
|
|
- } else {
|
|
|
- assert.ok(start.line < end.line);
|
|
|
- sliced[0] = sliceInfo(sliced[0], start.column);
|
|
|
- sliced.push(sliceInfo(sliced.pop(), 0, end.column));
|
|
|
- }
|
|
|
-
|
|
|
- var lines = new Lines(sliced);
|
|
|
-
|
|
|
- if (secret.mappings.length > 0) {
|
|
|
- var newMappings = getSecret(lines).mappings;
|
|
|
- assert.strictEqual(newMappings.length, 0);
|
|
|
- secret.mappings.forEach(function(mapping) {
|
|
|
- var sliced = mapping.slice(this, start, end);
|
|
|
- if (sliced) {
|
|
|
- newMappings.push(sliced);
|
|
|
- }
|
|
|
- }, this);
|
|
|
- }
|
|
|
-
|
|
|
- return lines;
|
|
|
-};
|
|
|
-
|
|
|
-function sliceInfo(info, startCol, endCol) {
|
|
|
- var sliceStart = info.sliceStart;
|
|
|
- var sliceEnd = info.sliceEnd;
|
|
|
- var indent = Math.max(info.indent, 0);
|
|
|
- var lineLength = indent + sliceEnd - sliceStart;
|
|
|
-
|
|
|
- if (typeof endCol === "undefined") {
|
|
|
- endCol = lineLength;
|
|
|
- }
|
|
|
-
|
|
|
- startCol = Math.max(startCol, 0);
|
|
|
- endCol = Math.min(endCol, lineLength);
|
|
|
- endCol = Math.max(endCol, startCol);
|
|
|
-
|
|
|
- if (endCol < indent) {
|
|
|
- indent = endCol;
|
|
|
- sliceEnd = sliceStart;
|
|
|
- } else {
|
|
|
- sliceEnd -= lineLength - endCol;
|
|
|
- }
|
|
|
-
|
|
|
- lineLength = endCol;
|
|
|
- lineLength -= startCol;
|
|
|
-
|
|
|
- if (startCol < indent) {
|
|
|
- indent -= startCol;
|
|
|
- } else {
|
|
|
- startCol -= indent;
|
|
|
- indent = 0;
|
|
|
- sliceStart += startCol;
|
|
|
- }
|
|
|
-
|
|
|
- assert.ok(indent >= 0);
|
|
|
- assert.ok(sliceStart <= sliceEnd);
|
|
|
- assert.strictEqual(lineLength, indent + sliceEnd - sliceStart);
|
|
|
-
|
|
|
- if (info.indent === indent &&
|
|
|
- info.sliceStart === sliceStart &&
|
|
|
- info.sliceEnd === sliceEnd) {
|
|
|
- return info;
|
|
|
- }
|
|
|
-
|
|
|
- return {
|
|
|
- line: info.line,
|
|
|
- indent: indent,
|
|
|
- // A destructive slice always unlocks indentation.
|
|
|
- locked: false,
|
|
|
- sliceStart: sliceStart,
|
|
|
- sliceEnd: sliceEnd
|
|
|
- };
|
|
|
-}
|
|
|
-
|
|
|
-Lp.bootstrapSliceString = function(start, end, options) {
|
|
|
- return this.slice(start, end).toString(options);
|
|
|
-};
|
|
|
-
|
|
|
-Lp.sliceString = function(start, end, options) {
|
|
|
- if (!end) {
|
|
|
- if (!start) {
|
|
|
- // The client seems to want a copy of this Lines object, but
|
|
|
- // Lines objects are immutable, so it's perfectly adequate to
|
|
|
- // return the same object.
|
|
|
- return this;
|
|
|
- }
|
|
|
-
|
|
|
- // Slice to the end if no end position was provided.
|
|
|
- end = this.lastPos();
|
|
|
- }
|
|
|
-
|
|
|
- options = normalizeOptions(options);
|
|
|
-
|
|
|
- var infos = getSecret(this).infos;
|
|
|
- var parts = [];
|
|
|
- var tabWidth = options.tabWidth;
|
|
|
-
|
|
|
- for (var line = start.line; line <= end.line; ++line) {
|
|
|
- var info = infos[line - 1];
|
|
|
-
|
|
|
- if (line === start.line) {
|
|
|
- if (line === end.line) {
|
|
|
- info = sliceInfo(info, start.column, end.column);
|
|
|
- } else {
|
|
|
- info = sliceInfo(info, start.column);
|
|
|
- }
|
|
|
- } else if (line === end.line) {
|
|
|
- info = sliceInfo(info, 0, end.column);
|
|
|
- }
|
|
|
-
|
|
|
- var indent = Math.max(info.indent, 0);
|
|
|
-
|
|
|
- var before = info.line.slice(0, info.sliceStart);
|
|
|
- if (options.reuseWhitespace &&
|
|
|
- isOnlyWhitespace(before) &&
|
|
|
- countSpaces(before, options.tabWidth) === indent) {
|
|
|
- // Reuse original spaces if the indentation is correct.
|
|
|
- parts.push(info.line.slice(0, info.sliceEnd));
|
|
|
- continue;
|
|
|
- }
|
|
|
-
|
|
|
- var tabs = 0;
|
|
|
- var spaces = indent;
|
|
|
-
|
|
|
- if (options.useTabs) {
|
|
|
- tabs = Math.floor(indent / tabWidth);
|
|
|
- spaces -= tabs * tabWidth;
|
|
|
- }
|
|
|
-
|
|
|
- var result = "";
|
|
|
-
|
|
|
- if (tabs > 0) {
|
|
|
- result += new Array(tabs + 1).join("\t");
|
|
|
- }
|
|
|
-
|
|
|
- if (spaces > 0) {
|
|
|
- result += new Array(spaces + 1).join(" ");
|
|
|
- }
|
|
|
-
|
|
|
- result += info.line.slice(info.sliceStart, info.sliceEnd);
|
|
|
-
|
|
|
- parts.push(result);
|
|
|
- }
|
|
|
-
|
|
|
- return parts.join(options.lineTerminator);
|
|
|
-};
|
|
|
-
|
|
|
-Lp.isEmpty = function() {
|
|
|
- return this.length < 2 && this.getLineLength(1) < 1;
|
|
|
-};
|
|
|
-
|
|
|
-Lp.join = function(elements) {
|
|
|
- var separator = this;
|
|
|
- var separatorSecret = getSecret(separator);
|
|
|
- var infos = [];
|
|
|
- var mappings = [];
|
|
|
- var prevInfo;
|
|
|
-
|
|
|
- function appendSecret(secret) {
|
|
|
- if (secret === null)
|
|
|
- return;
|
|
|
-
|
|
|
- if (prevInfo) {
|
|
|
- var info = secret.infos[0];
|
|
|
- var indent = new Array(info.indent + 1).join(" ");
|
|
|
- var prevLine = infos.length;
|
|
|
- var prevColumn = Math.max(prevInfo.indent, 0) +
|
|
|
- prevInfo.sliceEnd - prevInfo.sliceStart;
|
|
|
-
|
|
|
- prevInfo.line = prevInfo.line.slice(
|
|
|
- 0, prevInfo.sliceEnd) + indent + info.line.slice(
|
|
|
- info.sliceStart, info.sliceEnd);
|
|
|
-
|
|
|
- // If any part of a line is indentation-locked, the whole line
|
|
|
- // will be indentation-locked.
|
|
|
- prevInfo.locked = prevInfo.locked || info.locked;
|
|
|
-
|
|
|
- prevInfo.sliceEnd = prevInfo.line.length;
|
|
|
-
|
|
|
- if (secret.mappings.length > 0) {
|
|
|
- secret.mappings.forEach(function(mapping) {
|
|
|
- mappings.push(mapping.add(prevLine, prevColumn));
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- } else if (secret.mappings.length > 0) {
|
|
|
- mappings.push.apply(mappings, secret.mappings);
|
|
|
- }
|
|
|
-
|
|
|
- secret.infos.forEach(function(info, i) {
|
|
|
- if (!prevInfo || i > 0) {
|
|
|
- prevInfo = copyLineInfo(info);
|
|
|
- infos.push(prevInfo);
|
|
|
- }
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- function appendWithSeparator(secret, i) {
|
|
|
- if (i > 0)
|
|
|
- appendSecret(separatorSecret);
|
|
|
- appendSecret(secret);
|
|
|
- }
|
|
|
-
|
|
|
- elements.map(function(elem) {
|
|
|
- var lines = fromString(elem);
|
|
|
- if (lines.isEmpty())
|
|
|
- return null;
|
|
|
- return getSecret(lines);
|
|
|
- }).forEach(separator.isEmpty()
|
|
|
- ? appendSecret
|
|
|
- : appendWithSeparator);
|
|
|
-
|
|
|
- if (infos.length < 1)
|
|
|
- return emptyLines;
|
|
|
-
|
|
|
- var lines = new Lines(infos);
|
|
|
-
|
|
|
- getSecret(lines).mappings = mappings;
|
|
|
-
|
|
|
- return lines;
|
|
|
-};
|
|
|
-
|
|
|
-exports.concat = function(elements) {
|
|
|
- return emptyLines.join(elements);
|
|
|
-};
|
|
|
-
|
|
|
-Lp.concat = function(other) {
|
|
|
- var args = arguments,
|
|
|
- list = [this];
|
|
|
- list.push.apply(list, args);
|
|
|
- assert.strictEqual(list.length, args.length + 1);
|
|
|
- return emptyLines.join(list);
|
|
|
-};
|
|
|
-
|
|
|
-// The emptyLines object needs to be created all the way down here so that
|
|
|
-// Lines.prototype will be fully populated.
|
|
|
-var emptyLines = fromString("");
|