UNPKG

alm

Version:

The best IDE for TypeScript

435 lines (434 loc) 17.8 kB
"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;