alm
Version:
The best IDE for TypeScript
435 lines (434 loc) • 17.8 kB
JavaScript
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
var liner = require("./liner");
var events_1 = require("../common/events");
var toPath = ts.toPath;
var LineIndex = liner.LineIndex;
var unorderedRemoveItem = ts.unorderedRemoveItem;
/** BAS : a function I added, useful as we are working without true fs host */
var toSimplePath = function (fileName) { return toPath(fileName, '', function (x) { return x; }); };
/** our compiler settings for simple tokenization */
var defaultCompilerOptions = {
jsx: ts.JsxEmit.React,
module: ts.ModuleKind.CommonJS,
target: ts.ScriptTarget.Latest,
experimentalDecorators: true,
noLib: true,
};
/**
* These classes are modified version of session.ts
* Same names but modified to not use `fs` / `sys` / `host`
*/
var ScriptInfo = /** @class */ (function () {
function ScriptInfo(fileName, content, isOpen) {
if (isOpen === void 0) { isOpen = false; }
this.fileName = fileName;
this.content = content;
this.isOpen = isOpen;
this.children = []; // files referenced by this file
this.path = toSimplePath(fileName);
this.svc = ScriptVersionCache.fromString(content);
}
ScriptInfo.prototype.close = function () {
this.isOpen = false;
};
ScriptInfo.prototype.addChild = function (childInfo) {
this.children.push(childInfo);
};
ScriptInfo.prototype.snap = function () {
return this.svc.getSnapshot();
};
ScriptInfo.prototype.getText = function () {
var snap = this.snap();
return snap.getText(0, snap.getLength());
};
ScriptInfo.prototype.getLineInfo = function (line) {
var snap = this.snap();
return snap.index.lineNumberToInfo(line);
};
ScriptInfo.prototype.editContent = function (start, end, newText) {
this.svc.edit(start, end - start, newText);
};
ScriptInfo.prototype.getTextChangeRangeBetweenVersions = function (startVersion, endVersion) {
return this.svc.getTextChangesBetweenVersions(startVersion, endVersion);
};
ScriptInfo.prototype.getChangeRange = function (oldSnapshot) {
return this.snap().getChangeRange(oldSnapshot);
};
return ScriptInfo;
}());
exports.ScriptInfo = ScriptInfo;
var ScriptVersionCache = /** @class */ (function () {
function ScriptVersionCache() {
this.changes = [];
this.versions = [];
this.minVersion = 0; // no versions earlier than min version will maintain change history
this.currentVersion = 0;
}
// REVIEW: can optimize by coalescing simple edits
ScriptVersionCache.prototype.edit = function (pos, deleteLen, insertedText) {
this.changes[this.changes.length] = new TextChange(pos, deleteLen, insertedText);
if ((this.changes.length > ScriptVersionCache.changeNumberThreshold) ||
(deleteLen > ScriptVersionCache.changeLengthThreshold) ||
(insertedText && (insertedText.length > ScriptVersionCache.changeLengthThreshold))) {
this.getSnapshot();
}
};
ScriptVersionCache.prototype.latest = function () {
return this.versions[this.currentVersion];
};
ScriptVersionCache.prototype.latestVersion = function () {
if (this.changes.length > 0) {
this.getSnapshot();
}
return this.currentVersion;
};
// reload whole script, leaving no change history behind reload
ScriptVersionCache.prototype.reload = function (script) {
this.currentVersion++;
this.changes = []; // history wiped out by reload
var snap = new LineIndexSnapshot(this.currentVersion, this);
this.versions[this.currentVersion] = snap;
snap.index = new LineIndex();
var lm = LineIndex.linesFromText(script);
snap.index.load(lm.lines);
// REVIEW: could use linked list
for (var i = this.minVersion; i < this.currentVersion; i++) {
this.versions[i] = undefined;
}
this.minVersion = this.currentVersion;
};
ScriptVersionCache.prototype.getSnapshot = function () {
var snap = this.versions[this.currentVersion];
if (this.changes.length > 0) {
var snapIndex = this.latest().index;
for (var i = 0, len = this.changes.length; i < len; i++) {
var change = this.changes[i];
snapIndex = snapIndex.edit(change.pos, change.deleteLen, change.insertedText);
}
snap = new LineIndexSnapshot(this.currentVersion + 1, this);
snap.index = snapIndex;
snap.changesSincePreviousVersion = this.changes;
this.currentVersion = snap.version;
this.versions[snap.version] = snap;
this.changes = [];
if ((this.currentVersion - this.minVersion) >= ScriptVersionCache.maxVersions) {
var oldMin = this.minVersion;
this.minVersion = (this.currentVersion - ScriptVersionCache.maxVersions) + 1;
for (var j = oldMin; j < this.minVersion; j++) {
this.versions[j] = undefined;
}
}
}
return snap;
};
ScriptVersionCache.prototype.getTextChangesBetweenVersions = function (oldVersion, newVersion) {
if (oldVersion < newVersion) {
if (oldVersion >= this.minVersion) {
var textChangeRanges = [];
for (var i = oldVersion + 1; i <= newVersion; i++) {
var snap = this.versions[i];
for (var j = 0, len = snap.changesSincePreviousVersion.length; j < len; j++) {
var textChange = snap.changesSincePreviousVersion[j];
textChangeRanges[textChangeRanges.length] = textChange.getTextChangeRange();
}
}
return ts.collapseTextChangeRangesAcrossMultipleVersions(textChangeRanges);
}
else {
return undefined;
}
}
else {
return ts.unchangedTextChangeRange;
}
};
ScriptVersionCache.fromString = function (script) {
var svc = new ScriptVersionCache();
var snap = new LineIndexSnapshot(0, svc);
svc.versions[svc.currentVersion] = snap;
snap.index = new LineIndex();
var lm = LineIndex.linesFromText(script);
snap.index.load(lm.lines);
return svc;
};
ScriptVersionCache.changeNumberThreshold = 8;
ScriptVersionCache.changeLengthThreshold = 256;
ScriptVersionCache.maxVersions = 8;
return ScriptVersionCache;
}());
exports.ScriptVersionCache = ScriptVersionCache;
var LineIndexSnapshot = /** @class */ (function () {
function LineIndexSnapshot(version, cache) {
this.version = version;
this.cache = cache;
this.changesSincePreviousVersion = [];
}
LineIndexSnapshot.prototype.getText = function (rangeStart, rangeEnd) {
return this.index.getText(rangeStart, rangeEnd - rangeStart);
};
LineIndexSnapshot.prototype.getLength = function () {
return this.index.root.charCount();
};
// this requires linear space so don't hold on to these
LineIndexSnapshot.prototype.getLineStartPositions = function () {
var starts = [-1];
var count = 1;
var pos = 0;
this.index.every(function (ll, s, len) {
starts[count++] = pos;
pos += ll.text.length;
return true;
}, 0);
return starts;
};
LineIndexSnapshot.prototype.getLineMapper = function () {
var _this = this;
return (function (line) {
return _this.index.lineNumberToInfo(line).offset;
});
};
LineIndexSnapshot.prototype.getTextChangeRangeSinceVersion = function (scriptVersion) {
if (this.version <= scriptVersion) {
return ts.unchangedTextChangeRange;
}
else {
return this.cache.getTextChangesBetweenVersions(scriptVersion, this.version);
}
};
LineIndexSnapshot.prototype.getChangeRange = function (oldSnapshot) {
var oldSnap = oldSnapshot;
return this.getTextChangeRangeSinceVersion(oldSnap.version);
};
return LineIndexSnapshot;
}());
exports.LineIndexSnapshot = LineIndexSnapshot;
// text change information
var TextChange = /** @class */ (function () {
function TextChange(pos, deleteLen, insertedText) {
this.pos = pos;
this.deleteLen = deleteLen;
this.insertedText = insertedText;
}
TextChange.prototype.getTextChangeRange = function () {
return ts.createTextChangeRange(ts.createTextSpan(this.pos, this.deleteLen), this.insertedText ? this.insertedText.length : 0);
};
return TextChange;
}());
exports.TextChange = TextChange;
var LSHost = /** @class */ (function () {
/**
* BAS: add configuration arguments
* - projectDirectory,
* - compilerOptions
*/
function LSHost(projectDirectory, compilerOptions) {
if (compilerOptions === void 0) { compilerOptions = defaultCompilerOptions; }
var _this = this;
this.projectDirectory = projectDirectory;
this.compilerOptions = compilerOptions;
this.roots = [];
this.getDefaultLibFileName = function () { return null; };
// BAS : wired to config. Needed for proper `@types` expansion.
// See the child class `LanguageServiceHost` implementation below (with more comments).
this.getCurrentDirectory = function () { return _this.projectDirectory; };
this.filenameToScript = ts.createMap();
}
LSHost.prototype.getScriptSnapshot = function (filename) {
var scriptInfo = this.getScriptInfo(filename);
if (scriptInfo) {
return scriptInfo.snap();
}
};
LSHost.prototype.lineAffectsRefs = function (filename, line) {
var info = this.getScriptInfo(filename);
var lineInfo = info.getLineInfo(line);
if (lineInfo && lineInfo.text) {
var regex = /reference|import|\/\*|\*\//;
return regex.test(lineInfo.text);
}
};
// BAS change this to return active project settings for file
LSHost.prototype.getCompilationSettings = function () {
return this.compilerOptions;
};
LSHost.prototype.getScriptFileNames = function () {
return this.roots.map(function (root) { return root.fileName; });
};
LSHost.prototype.getScriptVersion = function (filename) {
return this.getScriptInfo(filename).svc.latestVersion().toString();
};
LSHost.prototype.getScriptIsOpen = function (filename) {
return this.getScriptInfo(filename).isOpen;
};
LSHost.prototype.getScriptInfo = function (filename) {
var path = toSimplePath(filename);
var scriptInfo = this.filenameToScript.get(path);
return scriptInfo;
};
LSHost.prototype.addRoot = function (info) {
if (!this.filenameToScript.has(info.path)) {
this.filenameToScript.set(info.path, info);
this.roots.push(info);
}
};
LSHost.prototype.removeRoot = function (info) {
if (!this.filenameToScript.has(info.path)) {
this.filenameToScript.delete(info.path);
unorderedRemoveItem(this.roots, info);
}
};
LSHost.prototype.editScript = function (filename, start, end, newText) {
var script = this.getScriptInfo(filename);
if (script) {
script.editContent(start, end, newText);
return;
}
throw new Error("No script with name '" + filename + "'");
};
/**
* Add a file with contents
*/
LSHost.prototype.addScript = function (filePath, contents) {
var path = toSimplePath(filePath);
if (!this.filenameToScript.has(path)) {
var info = new ScriptInfo(filePath, contents);
this.addRoot(info);
}
};
/**
* @param line 1 based index
*/
LSHost.prototype.lineToTextSpan = function (filename, line) {
var path = toSimplePath(filename);
var script = this.filenameToScript.get(path);
var index = script.snap().index;
var lineInfo = index.lineNumberToInfo(line + 1);
var len;
if (lineInfo.leaf) {
len = lineInfo.leaf.text.length;
}
else {
var nextLineInfo = index.lineNumberToInfo(line + 2);
len = nextLineInfo.offset - lineInfo.offset;
}
return ts.createTextSpan(lineInfo.offset, len);
};
/**
* @param line 1 based index
* @param offset 1 based index
*/
LSHost.prototype.lineOffsetToPosition = function (filename, line, offset) {
var path = toSimplePath(filename);
var script = this.filenameToScript.get(path);
var index = script.snap().index;
var lineInfo = index.lineNumberToInfo(line);
// TODO: assert this offset is actually on the line
return (lineInfo.offset + offset - 1);
};
/**
* @param line 1-based index
* @param offset 1-based index
*/
LSHost.prototype.positionToLineOffset = function (filename, position) {
var path = toSimplePath(filename);
var script = this.filenameToScript.get(path);
var index = script.snap().index;
var lineOffset = index.charOffsetToLineNumberAndPos(position);
return { line: lineOffset.line, offset: lineOffset.offset + 1 };
};
return LSHost;
}());
exports.LSHost = LSHost;
/**
* BAS:
* This class is my own creation.
*/
var LanguageServiceHost = /** @class */ (function (_super) {
__extends(LanguageServiceHost, _super);
function LanguageServiceHost() {
var _this = _super !== null && _super.apply(this, arguments) || this;
/*
* LS host can optionally implement these methods to support getImportModuleCompletionsAtPosition.
* Without these methods, only completions for ambient modules will be provided.
*/
_this.readDirectory = ts.sys ? ts.sys.readDirectory : undefined;
_this.readFile = ts.sys ? ts.sys.readFile : undefined;
_this.fileExists = ts.sys ? ts.sys.fileExists : undefined;
/**
* getDirectories is also required for full import and type reference completions.
* Without it defined, certain completions will not be provided
*/
_this.getDirectories = ts.sys ? ts.sys.getDirectories : undefined;
/**
* For @types expansion, these two functions are needed.
*/
_this.directoryExists = ts.sys ? ts.sys.directoryExists : undefined;
_this.getCurrentDirectory = function () {
/**
* TODO: use the same path as the path of tsconfig.json (if any)
* `undefined` is handled correctly in the compiler source :
* https://github.com/Microsoft/TypeScript/blob/02493de5ccd9e8c4c901bb154ba584dee392bd14/src/compiler/moduleNameResolver.ts#L98
*/
return _this.projectDirectory;
};
/**
* We allow incremental loading of resources.
* Needed for node_modules and for stuff like `user types a require statement`
*/
_this.incrementallyAddedFile = new events_1.TypedEvent();
return _this;
}
LanguageServiceHost.prototype.removeFile = function (filename) {
var script = this.getScriptInfo(filename);
this.removeRoot(script);
};
/**
* Basically having setContents ensure long term stability even if stuff does get out of sync due to errors in above implementation
*/
LanguageServiceHost.prototype.setContents = function (filename, contents) {
var script = this.getScriptInfo(filename);
if (script) {
script.svc.reload(contents);
return;
}
throw new Error("No script with name '" + filename + "'");
};
/**
* Note : This can be slow
*/
LanguageServiceHost.prototype.getContents = function (filename) {
var script = this.getScriptInfo(filename);
if (script) {
return script.getText();
}
throw new Error("No script with name '" + filename + "'");
};
/** 0 based */
LanguageServiceHost.prototype.getPositionOfLineAndCharacter = function (filePath, line, ch) {
return this.lineOffsetToPosition(filePath, line + 1, ch + 1);
};
/** 0 based */
LanguageServiceHost.prototype.getLineAndCharacterOfPosition = function (filePath, pos) {
var res = this.positionToLineOffset(filePath, pos);
return { line: res.line - 1, ch: res.offset - 1 };
};
/** Like parent EditScript but works on EditorPosition */
LanguageServiceHost.prototype.applyCodeEdit = function (fileName, start, end, newText) {
var minChar = this.getPositionOfLineAndCharacter(fileName, start.line, start.ch);
var limChar = this.getPositionOfLineAndCharacter(fileName, end.line, end.ch);
_super.prototype.editScript.call(this, fileName, minChar, limChar, newText);
};
return LanguageServiceHost;
}(LSHost));
exports.LanguageServiceHost = LanguageServiceHost;