diff --git a/Jakefile b/Jakefile
index dc8070fd7514d..07b0d267a6895 100644
--- a/Jakefile
+++ b/Jakefile
@@ -8,6 +8,7 @@ var child_process = require("child_process");
// Variables
var compilerDirectory = "src/compiler/";
var servicesDirectory = "src/services/";
+var serverDirectory = "src/server/";
var harnessDirectory = "src/harness/";
var libraryDirectory = "src/lib/";
var scriptsDirectory = "scripts/";
@@ -90,6 +91,16 @@ var servicesSources = [
return path.join(servicesDirectory, f);
}));
+var serverSources = [
+ "node.d.ts",
+ "editorServices.ts",
+ "protocol.d.ts",
+ "session.ts",
+ "server.ts"
+].map(function (f) {
+ return path.join(serverDirectory, f);
+});
+
var definitionsRoots = [
"compiler/types.d.ts",
"compiler/scanner.d.ts",
@@ -130,6 +141,13 @@ var harnessSources = [
"services/preProcessFile.ts"
].map(function (f) {
return path.join(unittestsDirectory, f);
+})).concat([
+ "protocol.d.ts",
+ "session.ts",
+ "client.ts",
+ "editorServices.ts",
+].map(function (f) {
+ return path.join(serverDirectory, f);
}));
var librarySourceMap = [
@@ -327,6 +345,7 @@ var tscFile = path.join(builtLocalDirectory, compilerFilename);
compileFile(tscFile, compilerSources, [builtLocalDirectory, copyright].concat(compilerSources), [copyright], /*useBuiltCompiler:*/ false);
var servicesFile = path.join(builtLocalDirectory, "typescriptServices.js");
+var nodePackageFile = path.join(builtLocalDirectory, "typescript.js");
compileFile(servicesFile, servicesSources,[builtLocalDirectory, copyright].concat(servicesSources),
/*prefixes*/ [copyright],
/*useBuiltCompiler*/ true,
@@ -336,7 +355,10 @@ compileFile(servicesFile, servicesSources,[builtLocalDirectory, copyright].conca
/*preserveConstEnums*/ true,
/*keepComments*/ false,
/*noResolve*/ false,
- /*stripInternal*/ false);
+ /*stripInternal*/ false,
+ /*callback*/ function () {
+ jake.cpR(servicesFile, nodePackageFile, {silent: true});
+ });
var nodeDefinitionsFile = path.join(builtLocalDirectory, "typescript.d.ts");
var standaloneDefinitionsFile = path.join(builtLocalDirectory, "typescriptServices.d.ts");
@@ -378,9 +400,12 @@ compileFile(nodeDefinitionsFile, servicesSources,[builtLocalDirectory, copyright
jake.rmRf(tempDirPath, {silent: true});
});
+var serverFile = path.join(builtLocalDirectory, "tsserver.js");
+compileFile(serverFile, serverSources,[builtLocalDirectory, copyright].concat(serverSources), /*prefixes*/ [copyright], /*useBuiltCompiler*/ true);
+
// Local target to build the compiler and services
desc("Builds the full compiler and services");
-task("local", ["generate-diagnostics", "lib", tscFile, servicesFile, nodeDefinitionsFile]);
+task("local", ["generate-diagnostics", "lib", tscFile, servicesFile, nodeDefinitionsFile, serverFile]);
// Local target to build only tsc.js
desc("Builds only the compiler");
@@ -435,7 +460,7 @@ task("generate-spec", [specMd])
// Makes a new LKG. This target does not build anything, but errors if not all the outputs are present in the built/local directory
desc("Makes a new LKG out of the built js files");
task("LKG", ["clean", "release", "local"].concat(libraryTargets), function() {
- var expectedFiles = [tscFile, servicesFile, nodeDefinitionsFile, standaloneDefinitionsFile, internalNodeDefinitionsFile, internalStandaloneDefinitionsFile].concat(libraryTargets);
+ var expectedFiles = [tscFile, servicesFile, nodePackageFile, nodeDefinitionsFile, standaloneDefinitionsFile, internalNodeDefinitionsFile, internalStandaloneDefinitionsFile].concat(libraryTargets);
var missingFiles = expectedFiles.filter(function (f) {
return !fs.existsSync(f);
});
diff --git a/bin/tsserver b/bin/tsserver
new file mode 100644
index 0000000000000..003eb0d22af9c
--- /dev/null
+++ b/bin/tsserver
@@ -0,0 +1,2 @@
+#!/usr/bin/env node
+require('./tsserver.js')
diff --git a/package.json b/package.json
index 2bd8970660631..9261174b68f3b 100644
--- a/package.json
+++ b/package.json
@@ -25,9 +25,10 @@
"url": "https://github.com/Microsoft/TypeScript.git"
},
"preferGlobal": true,
- "main": "./bin/typescriptServices.js",
+ "main": "./bin/typescript.js",
"bin": {
- "tsc": "./bin/tsc"
+ "tsc": "./bin/tsc",
+ "tsserver": "./bin/tsserver"
},
"engines": {
"node": ">=0.8.0"
diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts
index 603a78b5f75b9..bb144742f45fd 100644
--- a/src/harness/fourslash.ts
+++ b/src/harness/fourslash.ts
@@ -282,6 +282,8 @@ module FourSlash {
return new Harness.LanguageService.NativeLanugageServiceAdapter(cancellationToken, compilationOptions);
case FourSlashTestType.Shims:
return new Harness.LanguageService.ShimLanugageServiceAdapter(cancellationToken, compilationOptions);
+ case FourSlashTestType.Server:
+ return new Harness.LanguageService.ServerLanugageServiceAdapter(cancellationToken, compilationOptions);
default:
throw new Error("Unknown FourSlash test type: ");
}
@@ -418,6 +420,9 @@ module FourSlash {
this.activeFile = fileToOpen;
var fileName = fileToOpen.fileName.replace(Harness.IO.directoryName(fileToOpen.fileName), '').substr(1);
this.scenarioActions.push('');
+
+ // Let the host know that this file is now open
+ this.languageServiceAdapterHost.openFile(fileToOpen.fileName);
}
public verifyErrorExistsBetweenMarkers(startMarkerName: string, endMarkerName: string, negative: boolean) {
@@ -1927,7 +1932,7 @@ module FourSlash {
}
var missingItem = { name: name, kind: kind };
- this.raiseError('verifyGetScriptLexicalStructureListContains failed - could not find the item: ' + JSON.stringify(missingItem) + ' in the returned list: (' + JSON.stringify(items) + ')');
+ this.raiseError('verifyGetScriptLexicalStructureListContains failed - could not find the item: ' + JSON.stringify(missingItem) + ' in the returned list: (' + JSON.stringify(items, null, " ") + ')');
}
private navigationBarItemsContains(items: ts.NavigationBarItem[], name: string, kind: string) {
diff --git a/src/harness/fourslashRunner.ts b/src/harness/fourslashRunner.ts
index fe3b6c7a91f8d..1ab6e31cdbbdb 100644
--- a/src/harness/fourslashRunner.ts
+++ b/src/harness/fourslashRunner.ts
@@ -4,7 +4,8 @@
const enum FourSlashTestType {
Native,
- Shims
+ Shims,
+ Server
}
class FourSlashRunner extends RunnerBase {
@@ -22,6 +23,10 @@ class FourSlashRunner extends RunnerBase {
this.basePath = 'tests/cases/fourslash/shims';
this.testSuiteName = 'fourslash-shims';
break;
+ case FourSlashTestType.Server:
+ this.basePath = 'tests/cases/fourslash/server';
+ this.testSuiteName = 'fourslash-server';
+ break;
}
}
diff --git a/src/harness/harness.ts b/src/harness/harness.ts
index 534bf85d119ea..8ee51f89c9c55 100644
--- a/src/harness/harness.ts
+++ b/src/harness/harness.ts
@@ -16,14 +16,15 @@
///
///
+///
+///
+///
///
///
///
///
-declare var require: any;
-declare var process: any;
-var Buffer = require('buffer').Buffer;
+var Buffer: BufferConstructor = require('buffer').Buffer;
// this will work in the browser via browserify
var _chai: typeof chai = require('chai');
diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts
index 2f95f5072d067..712f0afe0aaf3 100644
--- a/src/harness/harnessLanguageService.ts
+++ b/src/harness/harnessLanguageService.ts
@@ -1,5 +1,6 @@
///
///
+///
///
module Harness.LanguageService {
@@ -23,18 +24,18 @@ module Harness.LanguageService {
this.version++;
}
- public editContent(minChar: number, limChar: number, newText: string): void {
+ public editContent(start: number, end: number, newText: string): void {
// Apply edits
- var prefix = this.content.substring(0, minChar);
+ var prefix = this.content.substring(0, start);
var middle = newText;
- var suffix = this.content.substring(limChar);
+ var suffix = this.content.substring(end);
this.setContent(prefix + middle + suffix);
// Store edit range + new length of script
this.editRanges.push({
length: this.content.length,
textChangeRange: ts.createTextChangeRange(
- ts.createTextSpanFromBounds(minChar, limChar), newText.length)
+ ts.createTextSpanFromBounds(start, end), newText.length)
});
// Update version #
@@ -145,24 +146,17 @@ module Harness.LanguageService {
this.fileNameToScript[fileName] = new ScriptInfo(fileName, content);
}
- public updateScript(fileName: string, content: string) {
+ public editScript(fileName: string, start: number, end: number, newText: string) {
var script = this.getScriptInfo(fileName);
if (script !== null) {
- script.updateContent(content);
+ script.editContent(start, end, newText);
return;
}
- this.addScript(fileName, content);
+ throw new Error("No script with name '" + fileName + "'");
}
- public editScript(fileName: string, minChar: number, limChar: number, newText: string) {
- var script = this.getScriptInfo(fileName);
- if (script !== null) {
- script.editContent(minChar, limChar, newText);
- return;
- }
-
- throw new Error("No script with name '" + fileName + "'");
+ public openFile(fileName: string): void {
}
/**
@@ -236,8 +230,7 @@ module Harness.LanguageService {
getFilenames(): string[] { return this.nativeHost.getFilenames(); }
getScriptInfo(fileName: string): ScriptInfo { return this.nativeHost.getScriptInfo(fileName); }
addScript(fileName: string, content: string): void { this.nativeHost.addScript(fileName, content); }
- updateScript(fileName: string, content: string): void { return this.nativeHost.updateScript(fileName, content); }
- editScript(fileName: string, minChar: number, limChar: number, newText: string): void { this.nativeHost.editScript(fileName, minChar, limChar, newText); }
+ editScript(fileName: string, start: number, end: number, newText: string): void { this.nativeHost.editScript(fileName, start, end, newText); }
lineColToPosition(fileName: string, line: number, col: number): number { return this.nativeHost.lineColToPosition(fileName, line, col); }
positionToZeroBasedLineCol(fileName: string, position: number): ts.LineAndCharacter { return this.nativeHost.positionToZeroBasedLineCol(fileName, position); }
@@ -442,5 +435,156 @@ module Harness.LanguageService {
return convertResult;
}
}
+
+ // Server adapter
+ class SessionClientHost extends NativeLanguageServiceHost implements ts.server.SessionClientHost {
+ private client: ts.server.SessionClient;
+
+ constructor(cancellationToken: ts.CancellationToken, settings: ts.CompilerOptions) {
+ super(cancellationToken, settings);
+ }
+
+ onMessage(message: string): void {
+
+ }
+
+ writeMessage(message: string): void {
+
+ }
+
+ setClient(client: ts.server.SessionClient) {
+ this.client = client;
+ }
+
+ openFile(fileName: string): void {
+ super.openFile(fileName);
+ this.client.openFile(fileName);
+ }
+
+ editScript(fileName: string, start: number, end: number, newText: string) {
+ super.editScript(fileName, start, end, newText);
+ this.client.changeFile(fileName, start, end, newText);
+ }
+ }
+
+ class SessionServerHost implements ts.server.ServerHost, ts.server.Logger {
+ args: string[] = [];
+ newLine: string;
+ useCaseSensitiveFileNames: boolean = false;
+
+ constructor(private host: NativeLanguageServiceHost) {
+ this.newLine = this.host.getNewLine();
+ }
+
+ onMessage(message: string): void {
+
+ }
+
+ writeMessage(message: string): void {
+ }
+
+ write(message: string): void {
+ this.writeMessage(message);
+ }
+
+ readFile(fileName: string): string {
+ if (fileName.indexOf(Harness.Compiler.defaultLibFileName) >= 0) {
+ fileName = Harness.Compiler.defaultLibFileName;
+ }
+
+ var snapshot = this.host.getScriptSnapshot(fileName);
+ return snapshot && snapshot.getText(0, snapshot.getLength());
+ }
+
+ writeFile(name: string, text: string, writeByteOrderMark: boolean): void {
+ }
+
+ resolvePath(path: string): string {
+ return path;
+ }
+
+ fileExists(path: string): boolean {
+ return !!this.host.getScriptSnapshot(path);
+ }
+
+ directoryExists(path: string): boolean {
+ return false;
+ }
+
+ getExecutingFilePath(): string {
+ return "";
+ }
+
+ exit(exitCode: number): void {
+ }
+
+ createDirectory(directoryName: string): void {
+ throw new Error("Not Implemented Yet.");
+ }
+
+ getCurrentDirectory(): string {
+ return this.host.getCurrentDirectory();
+ }
+
+ readDirectory(path: string, extension?: string): string[] {
+ throw new Error("Not implemented Yet.");
+ }
+
+ watchFile(fileName: string, callback: (fileName: string) => void): ts.FileWatcher {
+ return { close() { } };
+ }
+
+ close(): void {
+ }
+
+ info(message: string): void {
+ return this.host.log(message);
+ }
+
+ msg(message: string) {
+ return this.host.log(message);
+ }
+
+ endGroup(): void {
+ }
+
+ perftrc(message: string): void {
+ return this.host.log(message);
+ }
+
+ startGroup(): void {
+ }
+ }
+
+ export class ServerLanugageServiceAdapter implements LanguageServiceAdapter {
+ private host: SessionClientHost;
+ private client: ts.server.SessionClient;
+ constructor(cancellationToken?: ts.CancellationToken, options?: ts.CompilerOptions) {
+ // This is the main host that tests use to direct tests
+ var clientHost = new SessionClientHost(cancellationToken, options);
+ var client = new ts.server.SessionClient(clientHost);
+
+ // This host is just a proxy for the clientHost, it uses the client
+ // host to answer server queries about files on disk
+ var serverHost = new SessionServerHost(clientHost);
+ var server = new ts.server.Session(serverHost, serverHost);
+
+ // Fake the connection between the client and the server
+ serverHost.writeMessage = client.onMessage.bind(client);
+ clientHost.writeMessage = server.onMessage.bind(server);
+
+ // Wire the client to the host to get notifications when a file is open
+ // or edited.
+ clientHost.setClient(client);
+
+ // Set the properties
+ this.client = client;
+ this.host = clientHost;
+ }
+ getHost() { return this.host; }
+ getLanguageService(): ts.LanguageService { return this.client; }
+ getClassifier(): ts.Classifier { throw new Error("getClassifier is not available using the server interface."); }
+ getPreProcessedFileInfo(fileName: string, fileContents: string): ts.PreProcessedFileInfo { throw new Error("getPreProcessedFileInfo is not available using the server interface."); }
+ }
}
\ No newline at end of file
diff --git a/src/harness/runner.ts b/src/harness/runner.ts
index 942ae56bf6fe8..e1e5429b00723 100644
--- a/src/harness/runner.ts
+++ b/src/harness/runner.ts
@@ -66,6 +66,9 @@ if (testConfigFile !== '') {
case 'fourslash-shims':
runners.push(new FourSlashRunner(FourSlashTestType.Shims));
break;
+ case 'fourslash-server':
+ runners.push(new FourSlashRunner(FourSlashTestType.Server));
+ break;
case 'fourslash-generated':
runners.push(new GeneratedFourslashRunner(FourSlashTestType.Native));
break;
@@ -95,6 +98,7 @@ if (runners.length === 0) {
// language services
runners.push(new FourSlashRunner(FourSlashTestType.Native));
runners.push(new FourSlashRunner(FourSlashTestType.Shims));
+ runners.push(new FourSlashRunner(FourSlashTestType.Server));
//runners.push(new GeneratedFourslashRunner());
}
diff --git a/src/server/client.ts b/src/server/client.ts
new file mode 100644
index 0000000000000..6185834a022df
--- /dev/null
+++ b/src/server/client.ts
@@ -0,0 +1,494 @@
+///
+
+module ts.server {
+
+ export interface SessionClientHost extends LanguageServiceHost {
+ writeMessage(message: string): void;
+ }
+
+ interface CompletionEntry extends CompletionInfo {
+ fileName: string;
+ position: number;
+ }
+
+ interface RenameEntry extends RenameInfo {
+ fileName: string;
+ position: number;
+ locations: RenameLocation[];
+ findInStrings: boolean;
+ findInComments: boolean;
+ }
+
+ export class SessionClient implements LanguageService {
+ private sequence: number = 0;
+ private fileMapping: ts.Map = {};
+ private lineMaps: ts.Map = {};
+ private messages: string[] = [];
+ private lastRenameEntry: RenameEntry;
+
+ constructor(private host: SessionClientHost) {
+ }
+
+ public onMessage(message: string): void {
+ this.messages.push(message);
+ }
+
+ private writeMessage(message: string): void {
+ this.host.writeMessage(message);
+ }
+
+ private getLineMap(fileName: string): number[] {
+ var lineMap = ts.lookUp(this.lineMaps, fileName);
+ if (!lineMap) {
+ var scriptSnapshot = this.host.getScriptSnapshot(fileName);
+ lineMap = this.lineMaps[fileName] = ts.computeLineStarts(scriptSnapshot.getText(0, scriptSnapshot.getLength()));
+ }
+ return lineMap;
+ }
+
+ private lineColToPosition(fileName: string, lineCol: protocol.Location): number {
+ return ts.computePositionFromLineAndCharacter(this.getLineMap(fileName), lineCol.line, lineCol.col);
+ }
+
+ private positionToOneBasedLineCol(fileName: string, position: number): protocol.Location {
+ var lineCol = ts.computeLineAndCharacterOfPosition(this.getLineMap(fileName), position);
+ return {
+ line: lineCol.line,
+ col: lineCol.character
+ };
+ }
+
+ private convertCodeEditsToTextChange(fileName: string, codeEdit: protocol.CodeEdit): ts.TextChange {
+ var start = this.lineColToPosition(fileName, codeEdit.start);
+ var end = this.lineColToPosition(fileName, codeEdit.end);
+
+ return {
+ span: ts.createTextSpanFromBounds(start, end),
+ newText: codeEdit.newText
+ };
+ }
+
+ private processRequest(command: string, arguments?: any): T {
+ var request: protocol.Request = {
+ seq: this.sequence++,
+ type: "request",
+ command: command,
+ arguments: arguments
+ };
+
+ this.writeMessage(JSON.stringify(request));
+
+ return request;
+ }
+
+ private processResponse(request: protocol.Request): T {
+ var lastMessage = this.messages.shift();
+ Debug.assert(!!lastMessage, "Did not recieve any responses.");
+
+ // Read the content length
+ var contentLengthPrefix = "Content-Length: ";
+ var lines = lastMessage.split("\r\n");
+ Debug.assert(lines.length >= 2, "Malformed response: Expected 3 lines in the response.");
+
+ var contentLengthText = lines[0];
+ Debug.assert(contentLengthText.indexOf(contentLengthPrefix) === 0, "Malformed response: Response text did not contain content-length header.");
+ var contentLength = parseInt(contentLengthText.substring(contentLengthPrefix.length));
+
+ // Read the body
+ var responseBody = lines[2];
+
+ // Verify content length
+ Debug.assert(responseBody.length + 1 === contentLength, "Malformed response: Content length did not match the response's body length.");
+
+ try {
+ var response: T = JSON.parse(responseBody);
+ }
+ catch (e) {
+ throw new Error("Malformed response: Failed to parse server response: " + lastMessage + ". \r\n Error detailes: " + e.message);
+ }
+
+ // verify the sequence numbers
+ Debug.assert(response.request_seq === request.seq, "Malformed response: response sequance number did not match request sequence number.");
+
+ // unmarshal errors
+ if (!response.success) {
+ throw new Error("Error " + response.message);
+ }
+
+ Debug.assert(!!response.body, "Malformed response: Unexpected empty response body.");
+
+ return response;
+ }
+
+ openFile(fileName: string): void {
+ var args: protocol.FileRequestArgs = { file: fileName };
+ this.processRequest(CommandNames.Open, args);
+ }
+
+ closeFile(fileName: string): void {
+ var args: protocol.FileRequestArgs = { file: fileName };
+ this.processRequest(CommandNames.Close, args);
+ }
+
+ changeFile(fileName: string, start: number, end: number, newText: string): void {
+ // clear the line map after an edit
+ this.lineMaps[fileName] = undefined;
+
+ var lineCol = this.positionToOneBasedLineCol(fileName, start);
+ var endLineCol = this.positionToOneBasedLineCol(fileName, end);
+
+ var args: protocol.ChangeRequestArgs = {
+ file: fileName,
+ line: lineCol.line,
+ col: lineCol.col,
+ endLine: endLineCol.line,
+ endCol: endLineCol.col,
+ insertString: newText
+ };
+
+ this.processRequest(CommandNames.Change, args);
+ }
+
+ getQuickInfoAtPosition(fileName: string, position: number): QuickInfo {
+ var lineCol = this.positionToOneBasedLineCol(fileName, position);
+ var args: protocol.FileLocationRequestArgs = {
+ file: fileName,
+ line: lineCol.line,
+ col: lineCol.col
+ };
+
+ var request = this.processRequest(CommandNames.Quickinfo, args);
+ var response = this.processResponse(request);
+
+ var start = this.lineColToPosition(fileName, response.body.start);
+ var end = this.lineColToPosition(fileName, response.body.end);
+
+ return {
+ kind: response.body.kind,
+ kindModifiers: response.body.kindModifiers,
+ textSpan: ts.createTextSpanFromBounds(start, end),
+ displayParts: [{ kind: "text", text: response.body.displayString }],
+ documentation: [{ kind: "text", text: response.body.documentation }]
+ };
+ }
+
+ getCompletionsAtPosition(fileName: string, position: number): CompletionInfo {
+ var lineCol = this.positionToOneBasedLineCol(fileName, position);
+ var args: protocol.CompletionsRequestArgs = {
+ file: fileName,
+ line: lineCol.line,
+ col: lineCol.col,
+ prefix: undefined
+ };
+
+ var request = this.processRequest(CommandNames.Completions, args);
+ var response = this.processResponse(request);
+
+ return {
+ isMemberCompletion: false,
+ isNewIdentifierLocation: false,
+ entries: response.body,
+ fileName: fileName,
+ position: position
+ };
+ }
+
+ getCompletionEntryDetails(fileName: string, position: number, entryName: string): CompletionEntryDetails {
+ var lineCol = this.positionToOneBasedLineCol(fileName, position);
+ var args: protocol.CompletionDetailsRequestArgs = {
+ file: fileName,
+ line: lineCol.line,
+ col: lineCol.col,
+ entryNames: [entryName]
+ };
+
+ var request = this.processRequest(CommandNames.CompletionDetails, args);
+ var response = this.processResponse(request);
+ Debug.assert(response.body.length == 1, "Unexpected length of completion details response body.");
+ return response.body[0];
+ }
+
+ getNavigateToItems(searchTerm: string): NavigateToItem[] {
+ var args: protocol.NavtoRequestArgs = {
+ searchTerm,
+ file: this.host.getScriptFileNames()[0]
+ };
+
+ var request = this.processRequest(CommandNames.Navto, args);
+ var response = this.processResponse(request);
+
+ return response.body.map(entry => {
+ var fileName = entry.file;
+ var start = this.lineColToPosition(fileName, entry.start);
+ var end = this.lineColToPosition(fileName, entry.end);
+
+ return {
+ name: entry.name,
+ containerName: entry.containerName || "",
+ containerKind: entry.containerKind || "",
+ kind: entry.kind,
+ kindModifiers: entry.kindModifiers,
+ matchKind: entry.matchKind,
+ fileName: fileName,
+ textSpan: ts.createTextSpanFromBounds(start, end)
+ };
+ });
+ }
+
+ getFormattingEditsForRange(fileName: string, start: number, end: number, options: ts.FormatCodeOptions): ts.TextChange[] {
+ var startLineCol = this.positionToOneBasedLineCol(fileName, start);
+ var endLineCol = this.positionToOneBasedLineCol(fileName, end);
+ var args: protocol.FormatRequestArgs = {
+ file: fileName,
+ line: startLineCol.line,
+ col: startLineCol.col,
+ endLine: endLineCol.line,
+ endCol: endLineCol.col,
+ };
+
+ // TODO: handle FormatCodeOptions
+ var request = this.processRequest(CommandNames.Format, args);
+ var response = this.processResponse(request);
+
+ return response.body.map(entry=> this.convertCodeEditsToTextChange(fileName, entry));
+ }
+
+ getFormattingEditsForDocument(fileName: string, options: ts.FormatCodeOptions): ts.TextChange[] {
+ return this.getFormattingEditsForRange(fileName, 0, this.host.getScriptSnapshot(fileName).getLength(), options);
+ }
+
+ getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: FormatCodeOptions): ts.TextChange[] {
+ var lineCol = this.positionToOneBasedLineCol(fileName, position);
+ var args: protocol.FormatOnKeyRequestArgs = {
+ file: fileName,
+ line: lineCol.line,
+ col: lineCol.col,
+ key: key
+ };
+
+ // TODO: handle FormatCodeOptions
+ var request = this.processRequest(CommandNames.Formatonkey, args);
+ var response = this.processResponse(request);
+
+ return response.body.map(entry=> this.convertCodeEditsToTextChange(fileName, entry));
+ }
+
+ getDefinitionAtPosition(fileName: string, position: number): DefinitionInfo[] {
+ var lineCol = this.positionToOneBasedLineCol(fileName, position);
+ var args: protocol.FileLocationRequestArgs = {
+ file: fileName,
+ line: lineCol.line,
+ col: lineCol.col,
+ };
+
+ var request = this.processRequest(CommandNames.Definition, args);
+ var response = this.processResponse(request);
+
+ return response.body.map(entry => {
+ var fileName = entry.file;
+ var start = this.lineColToPosition(fileName, entry.start);
+ var end = this.lineColToPosition(fileName, entry.end);
+ return {
+ containerKind: "",
+ containerName: "",
+ fileName: fileName,
+ textSpan: ts.createTextSpanFromBounds(start, end),
+ kind: "",
+ name: ""
+ };
+ });
+ }
+
+ getReferencesAtPosition(fileName: string, position: number): ReferenceEntry[] {
+ var lineCol = this.positionToOneBasedLineCol(fileName, position);
+ var args: protocol.FileLocationRequestArgs = {
+ file: fileName,
+ line: lineCol.line,
+ col: lineCol.col,
+ };
+
+ var request = this.processRequest(CommandNames.References, args);
+ var response = this.processResponse(request);
+
+ return response.body.refs.map(entry => {
+ var fileName = entry.file;
+ var start = this.lineColToPosition(fileName, entry.start);
+ var end = this.lineColToPosition(fileName, entry.end);
+ return {
+ fileName: fileName,
+ textSpan: ts.createTextSpanFromBounds(start, end),
+ isWriteAccess: entry.isWriteAccess,
+ };
+ });
+ }
+
+ getEmitOutput(fileName: string): EmitOutput {
+ throw new Error("Not Implemented Yet.");
+ }
+
+ getSyntacticDiagnostics(fileName: string): Diagnostic[] {
+ throw new Error("Not Implemented Yet.");
+ }
+
+ getSemanticDiagnostics(fileName: string): Diagnostic[] {
+ throw new Error("Not Implemented Yet.");
+ }
+
+ getCompilerOptionsDiagnostics(): Diagnostic[] {
+ throw new Error("Not Implemented Yet.");
+ }
+
+ getRenameInfo(fileName: string, position: number, findInStrings?: boolean, findInComments?: boolean): RenameInfo {
+ var lineCol = this.positionToOneBasedLineCol(fileName, position);
+ var args: protocol.RenameRequestArgs = {
+ file: fileName,
+ line: lineCol.line,
+ col: lineCol.col,
+ findInStrings,
+ findInComments
+ };
+
+ var request = this.processRequest(CommandNames.Rename, args);
+ var response = this.processResponse(request);
+ var locations: RenameLocation[] = [];
+ response.body.locs.map((entry: protocol.SpanGroup) => {
+ var fileName = entry.file;
+ entry.locs.map((loc: protocol.TextSpan) => {
+ var start = this.lineColToPosition(fileName, loc.start);
+ var end = this.lineColToPosition(fileName, loc.end);
+ locations.push({
+ textSpan: ts.createTextSpanFromBounds(start, end),
+ fileName: fileName
+ });
+ });
+ });
+ return this.lastRenameEntry = {
+ canRename: response.body.info.canRename,
+ displayName: response.body.info.displayName,
+ fullDisplayName: response.body.info.fullDisplayName,
+ kind: response.body.info.kind,
+ kindModifiers: response.body.info.kindModifiers,
+ localizedErrorMessage: response.body.info.localizedErrorMessage,
+ triggerSpan: ts.createTextSpanFromBounds(position, position),
+ fileName: fileName,
+ position: position,
+ findInStrings: findInStrings,
+ findInComments: findInComments,
+ locations: locations
+ };
+ }
+
+ findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean): RenameLocation[] {
+ if (!this.lastRenameEntry ||
+ this.lastRenameEntry.fileName !== fileName ||
+ this.lastRenameEntry.position !== position ||
+ this.lastRenameEntry.findInStrings != findInStrings ||
+ this.lastRenameEntry.findInComments != findInComments) {
+ this.getRenameInfo(fileName, position, findInStrings, findInComments);
+ }
+
+ return this.lastRenameEntry.locations;
+ }
+
+ decodeNavigationBarItems(items: protocol.NavigationBarItem[], fileName: string): NavigationBarItem[] {
+ if (!items) {
+ return [];
+ }
+
+ return items.map(item => ({
+ text: item.text,
+ kind: item.kind,
+ kindModifiers: item.kindModifiers || "",
+ spans: item.spans.map(span=> createTextSpanFromBounds(this.lineColToPosition(fileName, span.start), this.lineColToPosition(fileName, span.end))),
+ childItems: this.decodeNavigationBarItems(item.childItems, fileName),
+ indent: 0,
+ bolded: false,
+ grayed: false
+ }));
+ }
+
+ getNavigationBarItems(fileName: string): NavigationBarItem[] {
+ var args: protocol.FileRequestArgs = {
+ file: fileName
+ };
+
+ var request = this.processRequest(CommandNames.NavBar, args);
+ var response = this.processResponse(request);
+
+ return this.decodeNavigationBarItems(response.body, fileName);
+ }
+
+ getNameOrDottedNameSpan(fileName: string, startPos: number, endPos: number): TextSpan {
+ throw new Error("Not Implemented Yet.");
+ }
+
+ getBreakpointStatementAtPosition(fileName: string, position: number): TextSpan {
+ throw new Error("Not Implemented Yet.");
+ }
+
+ getSignatureHelpItems(fileName: string, position: number): SignatureHelpItems {
+ throw new Error("Not Implemented Yet.");
+ }
+
+ getOccurrencesAtPosition(fileName: string, position: number): ReferenceEntry[] {
+ throw new Error("Not Implemented Yet.");
+ }
+
+ getOutliningSpans(fileName: string): OutliningSpan[] {
+ throw new Error("Not Implemented Yet.");
+ }
+
+ getTodoComments(fileName: string, descriptors: TodoCommentDescriptor[]): TodoComment[] {
+ throw new Error("Not Implemented Yet.");
+ }
+
+ getBraceMatchingAtPosition(fileName: string, position: number): TextSpan[] {
+ var lineCol = this.positionToOneBasedLineCol(fileName, position);
+ var args: protocol.FileLocationRequestArgs = {
+ file: fileName,
+ line: lineCol.line,
+ col: lineCol.col,
+ };
+
+ var request = this.processRequest(CommandNames.Brace, args);
+ var response = this.processResponse(request);
+
+ return response.body.map(entry => {
+ var start = this.lineColToPosition(fileName, entry.start);
+ var end = this.lineColToPosition(fileName, entry.end);
+ return {
+ start: start,
+ length: end - start,
+ };
+ });
+ }
+
+ getIndentationAtPosition(fileName: string, position: number, options: EditorOptions): number {
+ throw new Error("Not Implemented Yet.");
+ }
+
+ getSyntacticClassifications(fileName: string, span: TextSpan): ClassifiedSpan[] {
+ throw new Error("Not Implemented Yet.");
+ }
+
+ getSemanticClassifications(fileName: string, span: TextSpan): ClassifiedSpan[] {
+ throw new Error("Not Implemented Yet.");
+ }
+
+ getProgram(): Program {
+ throw new Error("SourceFile objects are not serializable through the server protocol.");
+ }
+
+ getSourceFile(fileName: string): SourceFile {
+ throw new Error("SourceFile objects are not serializable through the server protocol.");
+ }
+
+ cleanupSemanticCache(): void {
+ throw new Error("cleanupSemanticCache is not available through the server layer.");
+ }
+
+ dispose(): void {
+ throw new Error("dispose is not available through the server layer.");
+ }
+ }
+}
diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts
new file mode 100644
index 0000000000000..5b5ee27d70738
--- /dev/null
+++ b/src/server/editorServices.ts
@@ -0,0 +1,1646 @@
+///
+///
+///
+
+module ts.server {
+ export interface Logger {
+ close(): void;
+ perftrc(s: string): void;
+ info(s: string): void;
+ startGroup(): void;
+ endGroup(): void;
+ msg(s: string, type?: string): void;
+ }
+
+ var lineCollectionCapacity = 4;
+
+ class ScriptInfo {
+ svc: ScriptVersionCache;
+ children: ScriptInfo[] = []; // files referenced by this file
+
+ defaultProject: Project; // project to use by default for file
+
+ fileWatcher: FileWatcher;
+
+ constructor(private host: ServerHost, public fileName: string, public content: string, public isOpen = false) {
+ this.svc = ScriptVersionCache.fromString(content);
+ }
+
+ close() {
+ this.isOpen = false;
+ }
+
+ addChild(childInfo: ScriptInfo) {
+ this.children.push(childInfo);
+ }
+
+ snap() {
+ return this.svc.getSnapshot();
+ }
+
+ getText() {
+ var snap = this.snap();
+ return snap.getText(0, snap.getLength());
+ }
+
+ getLineInfo(line: number) {
+ var snap = this.snap();
+ return snap.index.lineNumberToInfo(line);
+ }
+
+ editContent(start: number, end: number, newText: string): void {
+ this.svc.edit(start, end - start, newText);
+ }
+
+ getTextChangeRangeBetweenVersions(startVersion: number, endVersion: number): ts.TextChangeRange {
+ return this.svc.getTextChangesBetweenVersions(startVersion, endVersion);
+ }
+
+ getChangeRange(oldSnapshot: ts.IScriptSnapshot): ts.TextChangeRange {
+ return this.snap().getChangeRange(oldSnapshot);
+ }
+ }
+
+ class LSHost implements ts.LanguageServiceHost {
+ ls: ts.LanguageService = null;
+ compilationSettings: ts.CompilerOptions;
+ filenameToScript: ts.Map = {};
+
+ constructor(public host: ServerHost, public project: Project) {
+ }
+
+ getDefaultLibFileName() {
+ var nodeModuleBinDir = ts.getDirectoryPath(ts.normalizePath(this.host.getExecutingFilePath()));
+ return ts.combinePaths(nodeModuleBinDir, ts.getDefaultLibFileName(this.compilationSettings));
+ }
+
+ getScriptSnapshot(filename: string): ts.IScriptSnapshot {
+ var scriptInfo = this.getScriptInfo(filename);
+ if (scriptInfo) {
+ return scriptInfo.snap();
+ }
+ }
+
+ setCompilationSettings(opt: ts.CompilerOptions) {
+ this.compilationSettings = opt;
+ }
+
+ lineAffectsRefs(filename: string, line: number) {
+ var info = this.getScriptInfo(filename);
+ var lineInfo = info.getLineInfo(line);
+ if (lineInfo && lineInfo.text) {
+ var regex = /reference|import|\/\*|\*\//;
+ return regex.test(lineInfo.text);
+ }
+ }
+
+ getCompilationSettings() {
+ // change this to return active project settings for file
+ return this.compilationSettings;
+ }
+
+ getScriptFileNames() {
+ var filenames: string[] = [];
+ for (var filename in this.filenameToScript) {
+ if (this.filenameToScript[filename] && this.filenameToScript[filename].isOpen) {
+ filenames.push(filename);
+ }
+ }
+ return filenames;
+ }
+
+ getScriptVersion(filename: string) {
+ return this.getScriptInfo(filename).svc.latestVersion().toString();
+ }
+
+ getCurrentDirectory(): string {
+ return "";
+ }
+
+ getScriptIsOpen(filename: string) {
+ return this.getScriptInfo(filename).isOpen;
+ }
+
+ removeReferencedFile(info: ScriptInfo) {
+ if (!info.isOpen) {
+ this.filenameToScript[info.fileName] = undefined;
+ }
+ }
+
+ getScriptInfo(filename: string): ScriptInfo {
+ var scriptInfo = ts.lookUp(this.filenameToScript, filename);
+ if (!scriptInfo) {
+ scriptInfo = this.project.openReferencedFile(filename);
+ if (scriptInfo) {
+ this.filenameToScript[scriptInfo.fileName] = scriptInfo;
+ }
+ }
+ else {
+ }
+ return scriptInfo;
+ }
+
+ addRoot(info: ScriptInfo) {
+ var scriptInfo = ts.lookUp(this.filenameToScript, info.fileName);
+ if (!scriptInfo) {
+ this.filenameToScript[info.fileName] = info;
+ return info;
+ }
+ }
+
+ saveTo(filename: string, tmpfilename: string) {
+ var script = this.getScriptInfo(filename);
+ if (script) {
+ var snap = script.snap();
+ this.host.writeFile(tmpfilename, snap.getText(0, snap.getLength()));
+ }
+ }
+
+ reloadScript(filename: string, tmpfilename: string, cb: () => any) {
+ var script = this.getScriptInfo(filename);
+ if (script) {
+ script.svc.reloadFromFile(tmpfilename, cb);
+ }
+ }
+
+ editScript(filename: string, start: number, end: number, newText: string) {
+ var script = this.getScriptInfo(filename);
+ if (script) {
+ script.editContent(start, end, newText);
+ return;
+ }
+
+ throw new Error("No script with name '" + filename + "'");
+ }
+
+ resolvePath(path: string): string {
+ var start = new Date().getTime();
+ var result = this.host.resolvePath(path);
+ return result;
+ }
+
+ fileExists(path: string): boolean {
+ var start = new Date().getTime();
+ var result = this.host.fileExists(path);
+ return result;
+ }
+
+ directoryExists(path: string): boolean {
+ return this.host.directoryExists(path);
+ }
+
+ /**
+ * @param line 1 based index
+ */
+ lineToTextSpan(filename: string, line: number): ts.TextSpan {
+ var script: ScriptInfo = this.filenameToScript[filename];
+ var index = script.snap().index;
+
+ var lineInfo = index.lineNumberToInfo(line + 1);
+ var len: number;
+ if (lineInfo.leaf) {
+ len = lineInfo.leaf.text.length;
+ }
+ else {
+ var nextLineInfo = index.lineNumberToInfo(line + 2);
+ len = nextLineInfo.col - lineInfo.col;
+ }
+ return ts.createTextSpan(lineInfo.col, len);
+ }
+
+ /**
+ * @param line 1 based index
+ * @param col 1 based index
+ */
+ lineColToPosition(filename: string, line: number, col: number): number {
+ var script: ScriptInfo = this.filenameToScript[filename];
+ var index = script.snap().index;
+
+ var lineInfo = index.lineNumberToInfo(line);
+ // TODO: assert this column is actually on the line
+ return (lineInfo.col + col - 1);
+ }
+
+ /**
+ * @param line 1-based index
+ * @param col 1-based index
+ */
+ positionToLineCol(filename: string, position: number): ILineInfo {
+ var script: ScriptInfo = this.filenameToScript[filename];
+ var index = script.snap().index;
+ var lineCol = index.charOffsetToLineNumberAndPos(position);
+ return { line: lineCol.line, col: lineCol.col + 1 };
+ }
+ }
+
+ // assumes normalized paths
+ function getAbsolutePath(filename: string, directory: string) {
+ var rootLength = ts.getRootLength(filename);
+ if (rootLength > 0) {
+ return filename;
+ }
+ else {
+ var splitFilename = filename.split('/');
+ var splitDir = directory.split('/');
+ var i = 0;
+ var dirTail = 0;
+ var sflen = splitFilename.length;
+ while ((i < sflen) && (splitFilename[i].charAt(0) == '.')) {
+ var dots = splitFilename[i];
+ if (dots == '..') {
+ dirTail++;
+ }
+ else if (dots != '.') {
+ return undefined;
+ }
+ i++;
+ }
+ return splitDir.slice(0, splitDir.length - dirTail).concat(splitFilename.slice(i)).join('/');
+ }
+ }
+
+ interface ProjectOptions {
+ // these fields can be present in the project file
+ files?: string[];
+ formatCodeOptions?: ts.FormatCodeOptions;
+ compilerOptions?: ts.CompilerOptions;
+ }
+
+ export class Project {
+ compilerService: CompilerService;
+ projectOptions: ProjectOptions;
+ projectFilename: string;
+ program: ts.Program;
+ filenameToSourceFile: ts.Map = {};
+ updateGraphSeq = 0;
+
+ constructor(public projectService: ProjectService) {
+ this.compilerService = new CompilerService(this);
+ }
+
+ openReferencedFile(filename: string) {
+ return this.projectService.openFile(filename, false);
+ }
+
+ getSourceFile(info: ScriptInfo) {
+ return this.filenameToSourceFile[info.fileName];
+ }
+
+ getSourceFileFromName(filename: string) {
+ var info = this.projectService.getScriptInfo(filename);
+ if (info) {
+ return this.getSourceFile(info);
+ }
+ }
+
+ removeReferencedFile(info: ScriptInfo) {
+ this.compilerService.host.removeReferencedFile(info);
+ this.updateGraph();
+ }
+
+ updateFileMap() {
+ this.filenameToSourceFile = {};
+ var sourceFiles = this.program.getSourceFiles();
+ for (var i = 0, len = sourceFiles.length; i < len; i++) {
+ var normFilename = ts.normalizePath(sourceFiles[i].fileName);
+ this.filenameToSourceFile[normFilename] = sourceFiles[i];
+ }
+ }
+
+ finishGraph() {
+ this.updateGraph();
+ this.compilerService.languageService.getNavigateToItems(".*");
+ }
+
+ updateGraph() {
+ this.program = this.compilerService.languageService.getProgram();
+ this.updateFileMap();
+ }
+
+ isConfiguredProject() {
+ return this.projectFilename;
+ }
+
+ // add a root file to project
+ addRoot(info: ScriptInfo) {
+ info.defaultProject = this;
+ return this.compilerService.host.addRoot(info);
+ }
+
+ filesToString() {
+ var strBuilder = "";
+ ts.forEachValue(this.filenameToSourceFile,
+ sourceFile => { strBuilder += sourceFile.fileName + "\n"; });
+ return strBuilder;
+ }
+
+ setProjectOptions(projectOptions: ProjectOptions) {
+ this.projectOptions = projectOptions;
+ if (projectOptions.compilerOptions) {
+ this.compilerService.setCompilerOptions(projectOptions.compilerOptions);
+ }
+ // TODO: format code options
+ }
+ }
+
+ interface ProjectOpenResult {
+ success?: boolean;
+ errorMsg?: string;
+ project?: Project;
+ }
+
+ function copyListRemovingItem(item: T, list: T[]) {
+ var copiedList: T[] = [];
+ for (var i = 0, len = list.length; i < len; i++) {
+ if (list[i] != item) {
+ copiedList.push(list[i]);
+ }
+ }
+ return copiedList;
+ }
+
+ interface ProjectServiceEventHandler {
+ (eventName: string, project: Project): void;
+ }
+
+ export class ProjectService {
+ filenameToScriptInfo: ts.Map = {};
+ // open, non-configured files in two lists
+ openFileRoots: ScriptInfo[] = [];
+ openFilesReferenced: ScriptInfo[] = [];
+ // projects covering open files
+ inferredProjects: Project[] = [];
+
+ constructor(public host: ServerHost, public psLogger: Logger, public eventHandler?: ProjectServiceEventHandler) {
+ // ts.disableIncrementalParsing = true;
+ }
+
+ watchedFileChanged(fileName: string) {
+ var info = this.filenameToScriptInfo[fileName];
+ if (!info) {
+ this.psLogger.info("Error: got watch notification for unknown file: " + fileName);
+ }
+
+ if (!this.host.fileExists(fileName)) {
+ // File was deleted
+ this.fileDeletedInFilesystem(info);
+ }
+ else {
+ if (info && (!info.isOpen)) {
+ info.svc.reloadFromFile(info.fileName);
+ }
+ }
+ }
+
+
+ log(msg: string, type = "Err") {
+ this.psLogger.msg(msg, type);
+ }
+
+ closeLog() {
+ this.psLogger.close();
+ }
+
+ createInferredProject(root: ScriptInfo) {
+ var iproj = new Project(this);
+ iproj.addRoot(root);
+ iproj.finishGraph();
+ this.inferredProjects.push(iproj);
+ return iproj;
+ }
+
+ fileDeletedInFilesystem(info: ScriptInfo) {
+ this.psLogger.info(info.fileName + " deleted");
+
+ if (info.fileWatcher) {
+ info.fileWatcher.close();
+ info.fileWatcher = undefined;
+ }
+
+ if (!info.isOpen) {
+ this.filenameToScriptInfo[info.fileName] = undefined;
+ var referencingProjects = this.findReferencingProjects(info);
+ for (var i = 0, len = referencingProjects.length; i < len; i++) {
+ referencingProjects[i].removeReferencedFile(info);
+ }
+ }
+ this.printProjects();
+ }
+
+ addOpenFile(info: ScriptInfo) {
+ this.findReferencingProjects(info);
+ if (info.defaultProject) {
+ this.openFilesReferenced.push(info);
+ }
+ else {
+ // create new inferred project p with the newly opened file as root
+ info.defaultProject = this.createInferredProject(info);
+ var openFileRoots: ScriptInfo[] = [];
+ // for each inferred project root r
+ for (var i = 0, len = this.openFileRoots.length; i < len; i++) {
+ var r = this.openFileRoots[i];
+ // if r referenced by the new project
+ if (info.defaultProject.getSourceFile(r)) {
+ // remove project rooted at r
+ this.inferredProjects =
+ copyListRemovingItem(r.defaultProject, this.inferredProjects);
+ // put r in referenced open file list
+ this.openFilesReferenced.push(r);
+ // set default project of r to the new project
+ r.defaultProject = info.defaultProject;
+ }
+ else {
+ // otherwise, keep r as root of inferred project
+ openFileRoots.push(r);
+ }
+ }
+ this.openFileRoots = openFileRoots;
+ this.openFileRoots.push(info);
+ }
+ }
+
+ closeOpenFile(info: ScriptInfo) {
+ var openFileRoots: ScriptInfo[] = [];
+ var removedProject: Project;
+ for (var i = 0, len = this.openFileRoots.length; i < len; i++) {
+ // if closed file is root of project
+ if (info == this.openFileRoots[i]) {
+ // remove that project and remember it
+ removedProject = info.defaultProject;
+ }
+ else {
+ openFileRoots.push(this.openFileRoots[i]);
+ }
+ }
+ this.openFileRoots = openFileRoots;
+ if (removedProject) {
+ // remove project from inferred projects list
+ this.inferredProjects = copyListRemovingItem(removedProject, this.inferredProjects);
+ var openFilesReferenced: ScriptInfo[] = [];
+ var orphanFiles: ScriptInfo[] = [];
+ // for all open, referenced files f
+ for (var i = 0, len = this.openFilesReferenced.length; i < len; i++) {
+ var f = this.openFilesReferenced[i];
+ // if f was referenced by the removed project, remember it
+ if (f.defaultProject == removedProject) {
+ f.defaultProject = undefined;
+ orphanFiles.push(f);
+ }
+ else {
+ // otherwise add it back to the list of referenced files
+ openFilesReferenced.push(f);
+ }
+ }
+ this.openFilesReferenced = openFilesReferenced;
+ // treat orphaned files as newly opened
+ for (var i = 0, len = orphanFiles.length; i < len; i++) {
+ this.addOpenFile(orphanFiles[i]);
+ }
+ }
+ else {
+ this.openFilesReferenced = copyListRemovingItem(info, this.openFilesReferenced);
+ }
+ info.close();
+ }
+
+ findReferencingProjects(info: ScriptInfo) {
+ var referencingProjects: Project[] = [];
+ info.defaultProject = undefined;
+ for (var i = 0, len = this.inferredProjects.length; i < len; i++) {
+ this.inferredProjects[i].updateGraph();
+ if (this.inferredProjects[i].getSourceFile(info)) {
+ info.defaultProject = this.inferredProjects[i];
+ referencingProjects.push(this.inferredProjects[i]);
+ }
+ }
+ return referencingProjects;
+ }
+
+ getScriptInfo(filename: string) {
+ filename = ts.normalizePath(filename);
+ return ts.lookUp(this.filenameToScriptInfo, filename);
+ }
+
+ /**
+ * @param filename is absolute pathname
+ */
+ openFile(fileName: string, openedByClient = false) {
+ fileName = ts.normalizePath(fileName);
+ var info = ts.lookUp(this.filenameToScriptInfo, fileName);
+ if (!info) {
+ var content: string;
+ if (this.host.fileExists(fileName)) {
+ content = this.host.readFile(fileName);
+ }
+ if (!content) {
+ if (openedByClient) {
+ content = "";
+ }
+ }
+ if (content !== undefined) {
+ info = new ScriptInfo(this.host, fileName, content, openedByClient);
+ this.filenameToScriptInfo[fileName] = info;
+ if (!info.isOpen) {
+ info.fileWatcher = this.host.watchFile(fileName, _ => { this.watchedFileChanged(fileName); });
+ }
+ }
+ }
+ if (info) {
+ if (openedByClient) {
+ info.isOpen = true;
+ }
+ }
+ return info;
+ }
+
+ /**
+ * Open file whose contents is managed by the client
+ * @param filename is absolute pathname
+ */
+
+ openClientFile(filename: string) {
+ // TODO: tsconfig check
+ var info = this.openFile(filename, true);
+ this.addOpenFile(info);
+ this.printProjects();
+ return info;
+ }
+
+ /**
+ * Close file whose contents is managed by the client
+ * @param filename is absolute pathname
+ */
+
+ closeClientFile(filename: string) {
+ // TODO: tsconfig check
+ var info = ts.lookUp(this.filenameToScriptInfo, filename);
+ if (info) {
+ this.closeOpenFile(info);
+ info.isOpen = false;
+ }
+ this.printProjects();
+ }
+
+ getProjectsReferencingFile(filename: string) {
+ var scriptInfo = ts.lookUp(this.filenameToScriptInfo, filename);
+ if (scriptInfo) {
+ var projects: Project[] = [];
+ for (var i = 0, len = this.inferredProjects.length; i < len; i++) {
+ if (this.inferredProjects[i].getSourceFile(scriptInfo)) {
+ projects.push(this.inferredProjects[i]);
+ }
+ }
+ return projects;
+ }
+ }
+
+ getProjectForFile(filename: string) {
+ var scriptInfo = ts.lookUp(this.filenameToScriptInfo, filename);
+ if (scriptInfo) {
+ return scriptInfo.defaultProject;
+ }
+ }
+
+ printProjectsForFile(filename: string) {
+ var scriptInfo = ts.lookUp(this.filenameToScriptInfo, filename);
+ if (scriptInfo) {
+ this.psLogger.startGroup();
+ this.psLogger.info("Projects for " + filename)
+ var projects = this.getProjectsReferencingFile(filename);
+ for (var i = 0, len = projects.length; i < len; i++) {
+ this.psLogger.info("Inferred Project " + i.toString());
+ }
+ this.psLogger.endGroup();
+ }
+ else {
+ this.psLogger.info(filename + " not in any project");
+ }
+ }
+
+ printProjects() {
+ this.psLogger.startGroup();
+ for (var i = 0, len = this.inferredProjects.length; i < len; i++) {
+ var project = this.inferredProjects[i];
+ this.psLogger.info("Project " + i.toString());
+ this.psLogger.info(project.filesToString());
+ this.psLogger.info("-----------------------------------------------");
+ }
+ this.psLogger.info("Open file roots: ")
+ for (var i = 0, len = this.openFileRoots.length; i < len; i++) {
+ this.psLogger.info(this.openFileRoots[i].fileName);
+ }
+ this.psLogger.info("Open files referenced: ")
+ for (var i = 0, len = this.openFilesReferenced.length; i < len; i++) {
+ this.psLogger.info(this.openFilesReferenced[i].fileName);
+ }
+ this.psLogger.endGroup();
+ }
+
+ openConfigFile(configFilename: string): ProjectOpenResult {
+ configFilename = ts.normalizePath(configFilename);
+ // file references will be relative to dirPath (or absolute)
+ var dirPath = ts.getDirectoryPath(configFilename);
+ var rawConfig = ts.readConfigFile(configFilename);
+ if (!rawConfig) {
+ return { errorMsg: "tsconfig syntax error" };
+ }
+ else {
+ // REVIEW: specify no base path so can get absolute path below
+ var parsedCommandLine = ts.parseConfigFile(rawConfig);
+
+ if (parsedCommandLine.errors) {
+ // TODO: gather diagnostics and transmit
+ return { errorMsg: "tsconfig option errors" };
+ }
+ else if (parsedCommandLine.fileNames) {
+ var proj = this.createProject(configFilename);
+ for (var i = 0, len = parsedCommandLine.fileNames.length; i < len; i++) {
+ var rootFilename = parsedCommandLine.fileNames[i];
+ var normRootFilename = ts.normalizePath(rootFilename);
+ normRootFilename = getAbsolutePath(normRootFilename, dirPath);
+ if (this.host.fileExists(normRootFilename)) {
+ var info = this.openFile(normRootFilename);
+ proj.addRoot(info);
+ }
+ else {
+ return { errorMsg: "specified file " + rootFilename + " not found" };
+ }
+ }
+ var projectOptions: ProjectOptions = {
+ files: parsedCommandLine.fileNames,
+ compilerOptions: parsedCommandLine.options
+ };
+ if (rawConfig.formatCodeOptions) {
+ projectOptions.formatCodeOptions = rawConfig.formatCodeOptions;
+ }
+ proj.setProjectOptions(projectOptions);
+ return { success: true, project: proj };
+ }
+ else {
+ return { errorMsg: "no files found" };
+ }
+ }
+ }
+
+ createProject(projectFilename: string) {
+ var eproj = new Project(this);
+ eproj.projectFilename = projectFilename;
+ return eproj;
+ }
+
+ }
+
+ class CompilerService {
+ host: LSHost;
+ languageService: ts.LanguageService;
+ classifier: ts.Classifier;
+ settings = ts.getDefaultCompilerOptions();
+ documentRegistry = ts.createDocumentRegistry();
+ formatCodeOptions: ts.FormatCodeOptions = CompilerService.defaultFormatCodeOptions;
+
+ constructor(public project: Project) {
+ this.host = new LSHost(project.projectService.host, project);
+ // override default ES6 (remove when compiler default back at ES5)
+ this.settings.target = ts.ScriptTarget.ES5;
+ this.host.setCompilationSettings(this.settings);
+ this.languageService = ts.createLanguageService(this.host, this.documentRegistry);
+ this.classifier = ts.createClassifier();
+ }
+
+ setCompilerOptions(opt: ts.CompilerOptions) {
+ this.settings = opt;
+ this.host.setCompilationSettings(opt);
+ }
+
+ isExternalModule(filename: string): boolean {
+ var sourceFile = this.languageService.getSourceFile(filename);
+ return ts.isExternalModule(sourceFile);
+ }
+
+ static defaultFormatCodeOptions: ts.FormatCodeOptions = {
+ IndentSize: 4,
+ TabSize: 4,
+ NewLineCharacter: ts.sys.newLine,
+ ConvertTabsToSpaces: true,
+ InsertSpaceAfterCommaDelimiter: true,
+ InsertSpaceAfterSemicolonInForStatements: true,
+ InsertSpaceBeforeAndAfterBinaryOperators: true,
+ InsertSpaceAfterKeywordsInControlFlowStatements: true,
+ InsertSpaceAfterFunctionKeywordForAnonymousFunctions: false,
+ InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false,
+ PlaceOpenBraceOnNewLineForFunctions: false,
+ PlaceOpenBraceOnNewLineForControlBlocks: false,
+ }
+
+ }
+
+ interface LineCollection {
+ charCount(): number;
+ lineCount(): number;
+ isLeaf(): boolean;
+ walk(rangeStart: number, rangeLength: number, walkFns: ILineIndexWalker): void;
+ }
+
+ export interface ILineInfo {
+ line: number;
+ col: number;
+ text?: string;
+ leaf?: LineLeaf;
+ }
+
+ enum CharRangeSection {
+ PreStart,
+ Start,
+ Entire,
+ Mid,
+ End,
+ PostEnd
+ }
+
+ interface ILineIndexWalker {
+ goSubtree: boolean;
+ done: boolean;
+ leaf(relativeStart: number, relativeLength: number, lineCollection: LineLeaf): void;
+ pre? (relativeStart: number, relativeLength: number, lineCollection: LineCollection,
+ parent: LineNode, nodeType: CharRangeSection): LineCollection;
+ post? (relativeStart: number, relativeLength: number, lineCollection: LineCollection,
+ parent: LineNode, nodeType: CharRangeSection): LineCollection;
+ }
+
+ class BaseLineIndexWalker implements ILineIndexWalker {
+ goSubtree = true;
+ done = false;
+ leaf(rangeStart: number, rangeLength: number, ll: LineLeaf) {
+ }
+ }
+
+ class EditWalker extends BaseLineIndexWalker {
+ lineIndex = new LineIndex();
+ // path to start of range
+ startPath: LineCollection[];
+ endBranch: LineCollection[] = [];
+ branchNode: LineNode;
+ // path to current node
+ stack: LineNode[];
+ state = CharRangeSection.Entire;
+ lineCollectionAtBranch: LineCollection;
+ initialText = "";
+ trailingText = "";
+ suppressTrailingText = false;
+
+ constructor() {
+ super();
+ this.lineIndex.root = new LineNode();
+ this.startPath = [this.lineIndex.root];
+ this.stack = [this.lineIndex.root];
+ }
+
+ insertLines(insertedText: string) {
+ if (this.suppressTrailingText) {
+ this.trailingText = "";
+ }
+ if (insertedText) {
+ insertedText = this.initialText + insertedText + this.trailingText;
+ }
+ else {
+ insertedText = this.initialText + this.trailingText;
+ }
+ var lm = LineIndex.linesFromText(insertedText);
+ var lines = lm.lines;
+ if (lines.length > 1) {
+ if (lines[lines.length - 1] == "") {
+ lines.length--;
+ }
+ }
+ var branchParent: LineNode;
+ var lastZeroCount: LineCollection;
+
+ for (var k = this.endBranch.length - 1; k >= 0; k--) {
+ (this.endBranch[k]).updateCounts();
+ if (this.endBranch[k].charCount() == 0) {
+ lastZeroCount = this.endBranch[k];
+ if (k > 0) {
+ branchParent = this.endBranch[k - 1];
+ }
+ else {
+ branchParent = this.branchNode;
+ }
+ }
+ }
+ if (lastZeroCount) {
+ branchParent.remove(lastZeroCount);
+ }
+
+ // path at least length two (root and leaf)
+ var insertionNode = this.startPath[this.startPath.length - 2];
+ var leafNode = this.startPath[this.startPath.length - 1];
+ var len = lines.length;
+
+ if (len > 0) {
+ leafNode.text = lines[0];
+
+ if (len > 1) {
+ var insertedNodes = new Array(len - 1);
+ var startNode = leafNode;
+ for (var i = 1, len = lines.length; i < len; i++) {
+ insertedNodes[i - 1] = new LineLeaf(lines[i]);
+ }
+ var pathIndex = this.startPath.length - 2;
+ while (pathIndex >= 0) {
+ insertionNode = this.startPath[pathIndex];
+ insertedNodes = insertionNode.insertAt(startNode, insertedNodes);
+ pathIndex--;
+ startNode = insertionNode;
+ }
+ var insertedNodesLen = insertedNodes.length;
+ while (insertedNodesLen > 0) {
+ var newRoot = new LineNode();
+ newRoot.add(this.lineIndex.root);
+ insertedNodes = newRoot.insertAt(this.lineIndex.root, insertedNodes);
+ insertedNodesLen = insertedNodes.length;
+ this.lineIndex.root = newRoot;
+ }
+ this.lineIndex.root.updateCounts();
+ }
+ else {
+ for (var j = this.startPath.length - 2; j >= 0; j--) {
+ (this.startPath[j]).updateCounts();
+ }
+ }
+ }
+ else {
+ // no content for leaf node, so delete it
+ insertionNode.remove(leafNode);
+ for (var j = this.startPath.length - 2; j >= 0; j--) {
+ (this.startPath[j]).updateCounts();
+ }
+ }
+
+ return this.lineIndex;
+ }
+
+ post(relativeStart: number, relativeLength: number, lineCollection: LineCollection, parent: LineCollection, nodeType: CharRangeSection): LineCollection {
+ // have visited the path for start of range, now looking for end
+ // if range is on single line, we will never make this state transition
+ if (lineCollection == this.lineCollectionAtBranch) {
+ this.state = CharRangeSection.End;
+ }
+ // always pop stack because post only called when child has been visited
+ this.stack.length--;
+ return undefined;
+ }
+
+ pre(relativeStart: number, relativeLength: number, lineCollection: LineCollection, parent: LineCollection, nodeType: CharRangeSection) {
+ // currentNode corresponds to parent, but in the new tree
+ var currentNode = this.stack[this.stack.length - 1];
+
+ if ((this.state == CharRangeSection.Entire) && (nodeType == CharRangeSection.Start)) {
+ // if range is on single line, we will never make this state transition
+ this.state = CharRangeSection.Start;
+ this.branchNode = currentNode;
+ this.lineCollectionAtBranch = lineCollection;
+ }
+
+ var child: LineCollection;
+ function fresh(node: LineCollection): LineCollection {
+ if (node.isLeaf()) {
+ return new LineLeaf("");
+ }
+ else return new LineNode();
+ }
+ switch (nodeType) {
+ case CharRangeSection.PreStart:
+ this.goSubtree = false;
+ if (this.state != CharRangeSection.End) {
+ currentNode.add(lineCollection);
+ }
+ break;
+ case CharRangeSection.Start:
+ if (this.state == CharRangeSection.End) {
+ this.goSubtree = false;
+ }
+ else {
+ child = fresh(lineCollection);
+ currentNode.add(child);
+ this.startPath[this.startPath.length] = child;
+ }
+ break;
+ case CharRangeSection.Entire:
+ if (this.state != CharRangeSection.End) {
+ child = fresh(lineCollection);
+ currentNode.add(child);
+ this.startPath[this.startPath.length] = child;
+ }
+ else {
+ if (!lineCollection.isLeaf()) {
+ child = fresh(lineCollection);
+ currentNode.add(child);
+ this.endBranch[this.endBranch.length] = child;
+ }
+ }
+ break;
+ case CharRangeSection.Mid:
+ this.goSubtree = false;
+ break;
+ case CharRangeSection.End:
+ if (this.state != CharRangeSection.End) {
+ this.goSubtree = false;
+ }
+ else {
+ if (!lineCollection.isLeaf()) {
+ child = fresh(lineCollection);
+ currentNode.add(child);
+ this.endBranch[this.endBranch.length] = child;
+ }
+ }
+ break;
+ case CharRangeSection.PostEnd:
+ this.goSubtree = false;
+ if (this.state != CharRangeSection.Start) {
+ currentNode.add(lineCollection);
+ }
+ break;
+ }
+ if (this.goSubtree) {
+ this.stack[this.stack.length] = child;
+ }
+ return lineCollection;
+ }
+ // just gather text from the leaves
+ leaf(relativeStart: number, relativeLength: number, ll: LineLeaf) {
+ if (this.state == CharRangeSection.Start) {
+ this.initialText = ll.text.substring(0, relativeStart);
+ }
+ else if (this.state == CharRangeSection.Entire) {
+ this.initialText = ll.text.substring(0, relativeStart);
+ this.trailingText = ll.text.substring(relativeStart + relativeLength);
+ }
+ else {
+ // state is CharRangeSection.End
+ this.trailingText = ll.text.substring(relativeStart + relativeLength);
+ }
+ }
+ }
+
+ // text change information
+ class TextChange {
+ constructor(public pos: number, public deleteLen: number, public insertedText?: string) {
+ }
+
+ getTextChangeRange() {
+ return ts.createTextChangeRange(ts.createTextSpan(this.pos, this.deleteLen),
+ this.insertedText ? this.insertedText.length : 0);
+ }
+ }
+
+ class ScriptVersionCache {
+ changes: TextChange[] = [];
+ versions: LineIndexSnapshot[] = [];
+ minVersion = 0; // no versions earlier than min version will maintain change history
+ private currentVersion = 0;
+
+ static changeNumberThreshold = 8;
+ static changeLengthThreshold = 256;
+
+ // REVIEW: can optimize by coalescing simple edits
+ edit(pos: number, deleteLen: number, insertedText?: string) {
+ 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();
+ }
+ }
+
+ latest() {
+ return this.versions[this.currentVersion];
+ }
+
+ latestVersion() {
+ if (this.changes.length > 0) {
+ this.getSnapshot();
+ }
+ return this.currentVersion;
+ }
+
+ reloadFromFile(filename: string, cb?: () => any) {
+ var content = ts.sys.readFile(filename);
+ this.reload(content);
+ if (cb)
+ cb();
+ }
+
+ // reload whole script, leaving no change history behind reload
+ reload(script: string) {
+ 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;
+
+ }
+
+ getSnapshot() {
+ 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 = [];
+ }
+ return snap;
+ }
+
+ getTextChangesBetweenVersions(oldVersion: number, newVersion: number) {
+ if (oldVersion < newVersion) {
+ if (oldVersion >= this.minVersion) {
+ var textChangeRanges: ts.TextChangeRange[] = [];
+ 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;
+ }
+ }
+
+ static fromString(script: string) {
+ 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;
+ }
+ }
+
+ class LineIndexSnapshot implements ts.IScriptSnapshot {
+ index: LineIndex;
+ changesSincePreviousVersion: TextChange[] = [];
+
+ constructor(public version: number, public cache: ScriptVersionCache) {
+ }
+
+ getText(rangeStart: number, rangeEnd: number) {
+ return this.index.getText(rangeStart, rangeEnd - rangeStart);
+ }
+
+ getLength() {
+ return this.index.root.charCount();
+ }
+
+ // this requires linear space so don't hold on to these
+ getLineStartPositions(): number[] {
+ var starts: number[] = [-1];
+ var count = 1;
+ var pos = 0;
+ this.index.every((ll, s, len) => {
+ starts[count++] = pos;
+ pos += ll.text.length;
+ return true;
+ }, 0);
+ return starts;
+ }
+
+ getLineMapper() {
+ return ((line: number) => {
+ return this.index.lineNumberToInfo(line).col;
+ });
+ }
+
+ getTextChangeRangeSinceVersion(scriptVersion: number) {
+ if (this.version <= scriptVersion) {
+ return ts.unchangedTextChangeRange;
+ }
+ else {
+ return this.cache.getTextChangesBetweenVersions(scriptVersion, this.version);
+ }
+ }
+ getChangeRange(oldSnapshot: ts.IScriptSnapshot): ts.TextChangeRange {
+ var oldSnap = oldSnapshot;
+ return this.getTextChangeRangeSinceVersion(oldSnap.version);
+ }
+ }
+
+ class LineIndex {
+ root: LineNode;
+ // set this to true to check each edit for accuracy
+ checkEdits = false;
+
+ charOffsetToLineNumberAndPos(charOffset: number) {
+ return this.root.charOffsetToLineNumberAndPos(1, charOffset);
+ }
+
+ lineNumberToInfo(lineNumber: number): ILineInfo {
+ var lineCount = this.root.lineCount();
+ if (lineNumber <= lineCount) {
+ var lineInfo = this.root.lineNumberToInfo(lineNumber, 0);
+ lineInfo.line = lineNumber;
+ return lineInfo;
+ }
+ else {
+ return {
+ line: lineNumber,
+ col: this.root.charCount()
+ }
+ }
+ }
+
+ load(lines: string[]) {
+ if (lines.length > 0) {
+ var leaves: LineLeaf[] = [];
+ for (var i = 0, len = lines.length; i < len; i++) {
+ leaves[i] = new LineLeaf(lines[i]);
+ }
+ this.root = LineIndex.buildTreeFromBottom(leaves);
+ }
+ else {
+ this.root = new LineNode();
+ }
+ }
+
+ walk(rangeStart: number, rangeLength: number, walkFns: ILineIndexWalker) {
+ this.root.walk(rangeStart, rangeLength, walkFns);
+ }
+
+ getText(rangeStart: number, rangeLength: number) {
+ var accum = "";
+ if (rangeLength > 0) {
+ this.walk(rangeStart, rangeLength, {
+ goSubtree: true,
+ done: false,
+ leaf: (relativeStart: number, relativeLength: number, ll: LineLeaf) => {
+ accum = accum.concat(ll.text.substring(relativeStart, relativeStart + relativeLength));
+ }
+ });
+ }
+ return accum;
+ }
+
+ every(f: (ll: LineLeaf, s: number, len: number) => boolean, rangeStart: number, rangeEnd?: number) {
+ if (!rangeEnd) {
+ rangeEnd = this.root.charCount();
+ }
+ var walkFns = {
+ goSubtree: true,
+ done: false,
+ leaf: function (relativeStart: number, relativeLength: number, ll: LineLeaf) {
+ if (!f(ll, relativeStart, relativeLength)) {
+ this.done = true;
+ }
+ }
+ }
+ this.walk(rangeStart, rangeEnd - rangeStart, walkFns);
+ return !walkFns.done;
+ }
+
+ edit(pos: number, deleteLength: number, newText?: string) {
+ function editFlat(source: string, s: number, dl: number, nt = "") {
+ return source.substring(0, s) + nt + source.substring(s + dl, source.length);
+ }
+ if (this.root.charCount() == 0) {
+ // TODO: assert deleteLength == 0
+ if (newText) {
+ this.load(LineIndex.linesFromText(newText).lines);
+ return this;
+ }
+ }
+ else {
+ if (this.checkEdits) {
+ var checkText = editFlat(this.getText(0, this.root.charCount()), pos, deleteLength, newText);
+ }
+ var walker = new EditWalker();
+ if (deleteLength > 0) {
+ // check whether last characters deleted are line break
+ var e = pos + deleteLength;
+ var lineInfo = this.charOffsetToLineNumberAndPos(e);
+ if ((lineInfo && (lineInfo.col == 0))) {
+ // move range end just past line that will merge with previous line
+ deleteLength += lineInfo.text.length;
+ // store text by appending to end of insertedText
+ if (newText) {
+ newText = newText + lineInfo.text;
+ }
+ else {
+ newText = lineInfo.text;
+ }
+ }
+ }
+ else if (pos >= this.root.charCount()) {
+ // insert at end
+ var endString = this.getText(pos - 1, 1);
+ if (newText) {
+ newText = endString + newText;
+ }
+ else {
+ newText = endString;
+ }
+ pos = pos - 1;
+ deleteLength = 0;
+ walker.suppressTrailingText = true;
+ }
+ this.root.walk(pos, deleteLength, walker);
+ walker.insertLines(newText);
+ if (this.checkEdits) {
+ var updatedText = this.getText(0, this.root.charCount());
+ Debug.assert(checkText == updatedText, "buffer edit mismatch");
+ }
+ return walker.lineIndex;
+ }
+ }
+
+ static buildTreeFromBottom(nodes: LineCollection[]): LineNode {
+ var nodeCount = Math.ceil(nodes.length / lineCollectionCapacity);
+ var interiorNodes: LineNode[] = [];
+ var nodeIndex = 0;
+ for (var i = 0; i < nodeCount; i++) {
+ interiorNodes[i] = new LineNode();
+ var charCount = 0;
+ var lineCount = 0;
+ for (var j = 0; j < lineCollectionCapacity; j++) {
+ if (nodeIndex < nodes.length) {
+ interiorNodes[i].add(nodes[nodeIndex]);
+ charCount += nodes[nodeIndex].charCount();
+ lineCount += nodes[nodeIndex].lineCount();
+ }
+ else {
+ break;
+ }
+ nodeIndex++;
+ }
+ interiorNodes[i].totalChars = charCount;
+ interiorNodes[i].totalLines = lineCount;
+ }
+ if (interiorNodes.length == 1) {
+ return interiorNodes[0];
+ }
+ else {
+ return this.buildTreeFromBottom(interiorNodes);
+ }
+ }
+
+ static linesFromText(text: string) {
+ var lineStarts = ts.computeLineStarts(text);
+
+ if (lineStarts.length == 0) {
+ return { lines: [], lineMap: lineStarts };
+ }
+ var lines = new Array(lineStarts.length);
+ var lc = lineStarts.length - 1;
+ for (var lmi = 0; lmi < lc; lmi++) {
+ lines[lmi] = text.substring(lineStarts[lmi], lineStarts[lmi + 1]);
+ }
+
+ var endText = text.substring(lineStarts[lc]);
+ if (endText.length > 0) {
+ lines[lc] = endText;
+ }
+ else {
+ lines.length--;
+ }
+ return { lines: lines, lineMap: lineStarts };
+ }
+ }
+
+ class LineNode implements LineCollection {
+ totalChars = 0;
+ totalLines = 0;
+ children: LineCollection[] = [];
+
+ isLeaf() {
+ return false;
+ }
+
+ updateCounts() {
+ this.totalChars = 0;
+ this.totalLines = 0;
+ for (var i = 0, len = this.children.length; i < len; i++) {
+ var child = this.children[i];
+ this.totalChars += child.charCount();
+ this.totalLines += child.lineCount();
+ }
+ }
+
+ execWalk(rangeStart: number, rangeLength: number, walkFns: ILineIndexWalker, childIndex: number, nodeType: CharRangeSection) {
+ if (walkFns.pre) {
+ walkFns.pre(rangeStart, rangeLength, this.children[childIndex], this, nodeType);
+ }
+ if (walkFns.goSubtree) {
+ this.children[childIndex].walk(rangeStart, rangeLength, walkFns);
+ if (walkFns.post) {
+ walkFns.post(rangeStart, rangeLength, this.children[childIndex], this, nodeType);
+ }
+ }
+ else {
+ walkFns.goSubtree = true;
+ }
+ return walkFns.done;
+ }
+
+ skipChild(relativeStart: number, relativeLength: number, childIndex: number, walkFns: ILineIndexWalker, nodeType: CharRangeSection) {
+ if (walkFns.pre && (!walkFns.done)) {
+ walkFns.pre(relativeStart, relativeLength, this.children[childIndex], this, nodeType);
+ walkFns.goSubtree = true;
+ }
+ }
+
+ walk(rangeStart: number, rangeLength: number, walkFns: ILineIndexWalker) {
+ // assume (rangeStart < this.totalChars) && (rangeLength <= this.totalChars)
+ var childIndex = 0;
+ var child = this.children[0];
+ var childCharCount = child.charCount();
+ // find sub-tree containing start
+ var adjustedStart = rangeStart;
+ while (adjustedStart >= childCharCount) {
+ this.skipChild(adjustedStart, rangeLength, childIndex, walkFns, CharRangeSection.PreStart);
+ adjustedStart -= childCharCount;
+ child = this.children[++childIndex];
+ childCharCount = child.charCount();
+ }
+ // Case I: both start and end of range in same subtree
+ if ((adjustedStart + rangeLength) <= childCharCount) {
+ if (this.execWalk(adjustedStart, rangeLength, walkFns, childIndex, CharRangeSection.Entire)) {
+ return;
+ }
+ }
+ else {
+ // Case II: start and end of range in different subtrees (possibly with subtrees in the middle)
+ if (this.execWalk(adjustedStart, childCharCount - adjustedStart, walkFns, childIndex, CharRangeSection.Start)) {
+ return;
+ }
+ var adjustedLength = rangeLength - (childCharCount - adjustedStart);
+ child = this.children[++childIndex];
+ childCharCount = child.charCount();
+ while (adjustedLength > childCharCount) {
+ if (this.execWalk(0, childCharCount, walkFns, childIndex, CharRangeSection.Mid)) {
+ return;
+ }
+ adjustedLength -= childCharCount;
+ child = this.children[++childIndex];
+ childCharCount = child.charCount();
+ }
+ if (adjustedLength > 0) {
+ if (this.execWalk(0, adjustedLength, walkFns, childIndex, CharRangeSection.End)) {
+ return;
+ }
+ }
+ }
+ // Process any subtrees after the one containing range end
+ if (walkFns.pre) {
+ var clen = this.children.length;
+ if (childIndex < (clen - 1)) {
+ for (var ej = childIndex + 1; ej < clen; ej++) {
+ this.skipChild(0, 0, ej, walkFns, CharRangeSection.PostEnd);
+ }
+ }
+ }
+ }
+
+ charOffsetToLineNumberAndPos(lineNumber: number, charOffset: number): ILineInfo {
+ var childInfo = this.childFromCharOffset(lineNumber, charOffset);
+ if (!childInfo.child) {
+ return {
+ line: lineNumber,
+ col: charOffset,
+ }
+ }
+ else if (childInfo.childIndex < this.children.length) {
+ if (childInfo.child.isLeaf()) {
+ return {
+ line: childInfo.lineNumber,
+ col: childInfo.charOffset,
+ text: ((childInfo.child)).text,
+ leaf: ((childInfo.child))
+ };
+ }
+ else {
+ var lineNode = (childInfo.child);
+ return lineNode.charOffsetToLineNumberAndPos(childInfo.lineNumber, childInfo.charOffset);
+ }
+ }
+ else {
+ var lineInfo = this.lineNumberToInfo(this.lineCount(), 0);
+ return { line: this.lineCount(), col: lineInfo.leaf.charCount() };
+ }
+ }
+
+ lineNumberToInfo(lineNumber: number, charOffset: number): ILineInfo {
+ var childInfo = this.childFromLineNumber(lineNumber, charOffset);
+ if (!childInfo.child) {
+ return {
+ line: lineNumber,
+ col: charOffset
+ }
+ }
+ else if (childInfo.child.isLeaf()) {
+ return {
+ line: lineNumber,
+ col: childInfo.charOffset,
+ text: ((childInfo.child)).text,
+ leaf: ((childInfo.child))
+ }
+ }
+ else {
+ var lineNode = (childInfo.child);
+ return lineNode.lineNumberToInfo(childInfo.relativeLineNumber, childInfo.charOffset);
+ }
+ }
+
+ childFromLineNumber(lineNumber: number, charOffset: number) {
+ var child: LineCollection;
+ var relativeLineNumber = lineNumber;
+ for (var i = 0, len = this.children.length; i < len; i++) {
+ child = this.children[i];
+ var childLineCount = child.lineCount();
+ if (childLineCount >= relativeLineNumber) {
+ break;
+ }
+ else {
+ relativeLineNumber -= childLineCount;
+ charOffset += child.charCount();
+ }
+ }
+ return {
+ child: child,
+ childIndex: i,
+ relativeLineNumber: relativeLineNumber,
+ charOffset: charOffset
+ };
+ }
+
+ childFromCharOffset(lineNumber: number, charOffset: number) {
+ var child: LineCollection;
+ for (var i = 0, len = this.children.length; i < len; i++) {
+ child = this.children[i];
+ if (child.charCount() > charOffset) {
+ break;
+ }
+ else {
+ charOffset -= child.charCount();
+ lineNumber += child.lineCount();
+ }
+ }
+ return {
+ child: child,
+ childIndex: i,
+ charOffset: charOffset,
+ lineNumber: lineNumber
+ }
+ }
+
+ splitAfter(childIndex: number) {
+ var splitNode: LineNode;
+ var clen = this.children.length;
+ childIndex++;
+ var endLength = childIndex;
+ if (childIndex < clen) {
+ splitNode = new LineNode();
+ while (childIndex < clen) {
+ splitNode.add(this.children[childIndex++]);
+ }
+ splitNode.updateCounts();
+ }
+ this.children.length = endLength;
+ return splitNode;
+ }
+
+ remove(child: LineCollection) {
+ var childIndex = this.findChildIndex(child);
+ var clen = this.children.length;
+ if (childIndex < (clen - 1)) {
+ for (var i = childIndex; i < (clen - 1); i++) {
+ this.children[i] = this.children[i + 1];
+ }
+ }
+ this.children.length--;
+ }
+
+ findChildIndex(child: LineCollection) {
+ var childIndex = 0;
+ var clen = this.children.length;
+ while ((this.children[childIndex] != child) && (childIndex < clen)) childIndex++;
+ return childIndex;
+ }
+
+ insertAt(child: LineCollection, nodes: LineCollection[]) {
+ var childIndex = this.findChildIndex(child);
+ var clen = this.children.length;
+ var nodeCount = nodes.length;
+ // if child is last and there is more room and only one node to place, place it
+ if ((clen < lineCollectionCapacity) && (childIndex == (clen - 1)) && (nodeCount == 1)) {
+ this.add(nodes[0]);
+ this.updateCounts();
+ return [];
+ }
+ else {
+ var shiftNode = this.splitAfter(childIndex);
+ var nodeIndex = 0;
+ childIndex++;
+ while ((childIndex < lineCollectionCapacity) && (nodeIndex < nodeCount)) {
+ this.children[childIndex++] = nodes[nodeIndex++];
+ }
+ var splitNodes: LineNode[] = [];
+ var splitNodeCount = 0;
+ if (nodeIndex < nodeCount) {
+ splitNodeCount = Math.ceil((nodeCount - nodeIndex) / lineCollectionCapacity);
+ splitNodes = new Array(splitNodeCount);
+ var splitNodeIndex = 0;
+ for (var i = 0; i < splitNodeCount; i++) {
+ splitNodes[i] = new LineNode();
+ }
+ var splitNode = splitNodes[0];
+ while (nodeIndex < nodeCount) {
+ splitNode.add(nodes[nodeIndex++]);
+ if (splitNode.children.length == lineCollectionCapacity) {
+ splitNodeIndex++;
+ splitNode = splitNodes[splitNodeIndex];
+ }
+ }
+ for (i = splitNodes.length - 1; i >= 0; i--) {
+ if (splitNodes[i].children.length == 0) {
+ splitNodes.length--;
+ }
+ }
+ }
+ if (shiftNode) {
+ splitNodes[splitNodes.length] = shiftNode;
+ }
+ this.updateCounts();
+ for (i = 0; i < splitNodeCount; i++) {
+ (splitNodes[i]).updateCounts();
+ }
+ return splitNodes;
+ }
+ }
+
+ // assume there is room for the item; return true if more room
+ add(collection: LineCollection) {
+ this.children[this.children.length] = collection;
+ return (this.children.length < lineCollectionCapacity);
+ }
+
+ charCount() {
+ return this.totalChars;
+ }
+
+ lineCount() {
+ return this.totalLines;
+ }
+ }
+
+ class LineLeaf implements LineCollection {
+ udata: any;
+
+ constructor(public text: string) {
+
+ }
+
+ setUdata(data: any) {
+ this.udata = data;
+ }
+
+ getUdata() {
+ return this.udata;
+ }
+
+ isLeaf() {
+ return true;
+ }
+
+ walk(rangeStart: number, rangeLength: number, walkFns: ILineIndexWalker) {
+ walkFns.leaf(rangeStart, rangeLength, this);
+ }
+
+ charCount() {
+ return this.text.length;
+ }
+
+ lineCount() {
+ return 1;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/server/node.d.ts b/src/server/node.d.ts
new file mode 100644
index 0000000000000..2002f973a3789
--- /dev/null
+++ b/src/server/node.d.ts
@@ -0,0 +1,677 @@
+// Type definitions for Node.js v0.10.1
+// Project: http://nodejs.org/
+// Definitions by: Microsoft TypeScript , DefinitelyTyped
+// Definitions: https://github.com/borisyankov/DefinitelyTyped
+
+/************************************************
+* *
+* Node.js v0.10.1 API *
+* *
+************************************************/
+
+/************************************************
+* *
+* GLOBAL *
+* *
+************************************************/
+declare var process: NodeJS.Process;
+declare var global: any;
+
+declare var __filename: string;
+declare var __dirname: string;
+
+declare function setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): NodeJS.Timer;
+declare function clearTimeout(timeoutId: NodeJS.Timer): void;
+declare function setInterval(callback: (...args: any[]) => void, ms: number, ...args: any[]): NodeJS.Timer;
+declare function clearInterval(intervalId: NodeJS.Timer): void;
+declare function setImmediate(callback: (...args: any[]) => void, ...args: any[]): any;
+declare function clearImmediate(immediateId: any): void;
+
+declare var require: {
+ (id: string): any;
+ resolve(id: string): string;
+ cache: any;
+ extensions: any;
+ main: any;
+};
+
+declare var module: {
+ exports: any;
+ require(id: string): any;
+ id: string;
+ filename: string;
+ loaded: boolean;
+ parent: any;
+ children: any[];
+};
+
+// Same as module.exports
+declare var exports: any;
+declare var SlowBuffer: {
+ new (str: string, encoding?: string): Buffer;
+ new (size: number): Buffer;
+ new (size: Uint8Array): Buffer;
+ new (array: any[]): Buffer;
+ prototype: Buffer;
+ isBuffer(obj: any): boolean;
+ byteLength(string: string, encoding?: string): number;
+ concat(list: Buffer[], totalLength?: number): Buffer;
+};
+
+
+// Buffer class
+interface Buffer extends NodeBuffer { }
+interface BufferConstructor {
+ new (str: string, encoding ?: string): Buffer;
+ new (size: number): Buffer;
+ new (size: Uint8Array): Buffer;
+ new (array: any[]): Buffer;
+ prototype: Buffer;
+ isBuffer(obj: any): boolean;
+ byteLength(string: string, encoding ?: string): number;
+ concat(list: Buffer[], totalLength ?: number): Buffer;
+}
+declare var Buffer: BufferConstructor;
+
+/************************************************
+* *
+* GLOBAL INTERFACES *
+* *
+************************************************/
+declare module NodeJS {
+ export interface ErrnoException extends Error {
+ errno?: any;
+ code?: string;
+ path?: string;
+ syscall?: string;
+ }
+
+ export interface EventEmitter {
+ addListener(event: string, listener: Function): EventEmitter;
+ on(event: string, listener: Function): EventEmitter;
+ once(event: string, listener: Function): EventEmitter;
+ removeListener(event: string, listener: Function): EventEmitter;
+ removeAllListeners(event?: string): EventEmitter;
+ setMaxListeners(n: number): void;
+ listeners(event: string): Function[];
+ emit(event: string, ...args: any[]): boolean;
+ }
+
+ export interface ReadableStream extends EventEmitter {
+ readable: boolean;
+ read(size?: number): any;
+ setEncoding(encoding: string): void;
+ pause(): void;
+ resume(): void;
+ pipe(destination: T, options?: { end?: boolean; }): T;
+ unpipe(destination?: T): void;
+ unshift(chunk: string): void;
+ unshift(chunk: Buffer): void;
+ wrap(oldStream: ReadableStream): ReadableStream;
+ }
+
+ export interface WritableStream extends EventEmitter {
+ writable: boolean;
+ write(buffer: Buffer, cb?: Function): boolean;
+ write(str: string, cb?: Function): boolean;
+ write(str: string, encoding?: string, cb?: Function): boolean;
+ end(): void;
+ end(buffer: Buffer, cb?: Function): void;
+ end(str: string, cb?: Function): void;
+ end(str: string, encoding?: string, cb?: Function): void;
+ }
+
+ export interface ReadWriteStream extends ReadableStream, WritableStream { }
+
+ export interface Process extends EventEmitter {
+ stdout: WritableStream;
+ stderr: WritableStream;
+ stdin: ReadableStream;
+ argv: string[];
+ execPath: string;
+ abort(): void;
+ chdir(directory: string): void;
+ cwd(): string;
+ env: any;
+ exit(code?: number): void;
+ getgid(): number;
+ setgid(id: number): void;
+ setgid(id: string): void;
+ getuid(): number;
+ setuid(id: number): void;
+ setuid(id: string): void;
+ version: string;
+ versions: {
+ http_parser: string;
+ node: string;
+ v8: string;
+ ares: string;
+ uv: string;
+ zlib: string;
+ openssl: string;
+ };
+ config: {
+ target_defaults: {
+ cflags: any[];
+ default_configuration: string;
+ defines: string[];
+ include_dirs: string[];
+ libraries: string[];
+ };
+ variables: {
+ clang: number;
+ host_arch: string;
+ node_install_npm: boolean;
+ node_install_waf: boolean;
+ node_prefix: string;
+ node_shared_openssl: boolean;
+ node_shared_v8: boolean;
+ node_shared_zlib: boolean;
+ node_use_dtrace: boolean;
+ node_use_etw: boolean;
+ node_use_openssl: boolean;
+ target_arch: string;
+ v8_no_strict_aliasing: number;
+ v8_use_snapshot: boolean;
+ visibility: string;
+ };
+ };
+ kill(pid: number, signal?: string): void;
+ pid: number;
+ title: string;
+ arch: string;
+ platform: string;
+ memoryUsage(): { rss: number; heapTotal: number; heapUsed: number; };
+ nextTick(callback: Function): void;
+ umask(mask?: number): number;
+ uptime(): number;
+ hrtime(time?: number[]): number[];
+
+ // Worker
+ send? (message: any, sendHandle?: any): void;
+ }
+
+ export interface Timer {
+ ref(): void;
+ unref(): void;
+ }
+}
+
+
+/**
+ * @deprecated
+ */
+interface NodeBuffer {
+ [index: number]: number;
+ write(string: string, offset?: number, length?: number, encoding?: string): number;
+ toString(encoding?: string, start?: number, end?: number): string;
+ toJSON(): any;
+ length: number;
+ copy(targetBuffer: Buffer, targetStart?: number, sourceStart?: number, sourceEnd?: number): number;
+ slice(start?: number, end?: number): Buffer;
+ readUInt8(offset: number, noAsset?: boolean): number;
+ readUInt16LE(offset: number, noAssert?: boolean): number;
+ readUInt16BE(offset: number, noAssert?: boolean): number;
+ readUInt32LE(offset: number, noAssert?: boolean): number;
+ readUInt32BE(offset: number, noAssert?: boolean): number;
+ readInt8(offset: number, noAssert?: boolean): number;
+ readInt16LE(offset: number, noAssert?: boolean): number;
+ readInt16BE(offset: number, noAssert?: boolean): number;
+ readInt32LE(offset: number, noAssert?: boolean): number;
+ readInt32BE(offset: number, noAssert?: boolean): number;
+ readFloatLE(offset: number, noAssert?: boolean): number;
+ readFloatBE(offset: number, noAssert?: boolean): number;
+ readDoubleLE(offset: number, noAssert?: boolean): number;
+ readDoubleBE(offset: number, noAssert?: boolean): number;
+ writeUInt8(value: number, offset: number, noAssert?: boolean): void;
+ writeUInt16LE(value: number, offset: number, noAssert?: boolean): void;
+ writeUInt16BE(value: number, offset: number, noAssert?: boolean): void;
+ writeUInt32LE(value: number, offset: number, noAssert?: boolean): void;
+ writeUInt32BE(value: number, offset: number, noAssert?: boolean): void;
+ writeInt8(value: number, offset: number, noAssert?: boolean): void;
+ writeInt16LE(value: number, offset: number, noAssert?: boolean): void;
+ writeInt16BE(value: number, offset: number, noAssert?: boolean): void;
+ writeInt32LE(value: number, offset: number, noAssert?: boolean): void;
+ writeInt32BE(value: number, offset: number, noAssert?: boolean): void;
+ writeFloatLE(value: number, offset: number, noAssert?: boolean): void;
+ writeFloatBE(value: number, offset: number, noAssert?: boolean): void;
+ writeDoubleLE(value: number, offset: number, noAssert?: boolean): void;
+ writeDoubleBE(value: number, offset: number, noAssert?: boolean): void;
+ fill(value: any, offset?: number, end?: number): void;
+}
+
+declare module NodeJS {
+ export interface Path {
+ normalize(p: string): string;
+ join(...paths: any[]): string;
+ resolve(...pathSegments: any[]): string;
+ relative(from: string, to: string): string;
+ dirname(p: string): string;
+ basename(p: string, ext?: string): string;
+ extname(p: string): string;
+ sep: string;
+ }
+}
+
+declare module NodeJS {
+ export interface ReadLineInstance extends EventEmitter {
+ setPrompt(prompt: string, length: number): void;
+ prompt(preserveCursor?: boolean): void;
+ question(query: string, callback: Function): void;
+ pause(): void;
+ resume(): void;
+ close(): void;
+ write(data: any, key?: any): void;
+ }
+ export interface ReadLineOptions {
+ input: NodeJS.ReadableStream;
+ output: NodeJS.WritableStream;
+ completer?: Function;
+ terminal?: boolean;
+ }
+
+ export interface ReadLine {
+ createInterface(options: ReadLineOptions): ReadLineInstance;
+ }
+}
+
+declare module NodeJS {
+ module events {
+ export class EventEmitter implements NodeJS.EventEmitter {
+ static listenerCount(emitter: EventEmitter, event: string): number;
+
+ addListener(event: string, listener: Function): EventEmitter;
+ on(event: string, listener: Function): EventEmitter;
+ once(event: string, listener: Function): EventEmitter;
+ removeListener(event: string, listener: Function): EventEmitter;
+ removeAllListeners(event?: string): EventEmitter;
+ setMaxListeners(n: number): void;
+ listeners(event: string): Function[];
+ emit(event: string, ...args: any[]): boolean;
+ }
+ }
+}
+
+declare module NodeJS {
+ module stream {
+
+ export interface Stream extends events.EventEmitter {
+ pipe(destination: T, options?: { end?: boolean; }): T;
+ }
+
+ export interface ReadableOptions {
+ highWaterMark?: number;
+ encoding?: string;
+ objectMode?: boolean;
+ }
+
+ export class Readable extends events.EventEmitter implements NodeJS.ReadableStream {
+ readable: boolean;
+ constructor(opts?: ReadableOptions);
+ _read(size: number): void;
+ read(size?: number): any;
+ setEncoding(encoding: string): void;
+ pause(): void;
+ resume(): void;
+ pipe(destination: T, options?: { end?: boolean; }): T;
+ unpipe(destination?: T): void;
+ unshift(chunk: string): void;
+ unshift(chunk: Buffer): void;
+ wrap(oldStream: NodeJS.ReadableStream): NodeJS.ReadableStream;
+ push(chunk: any, encoding?: string): boolean;
+ }
+
+ export interface WritableOptions {
+ highWaterMark?: number;
+ decodeStrings?: boolean;
+ }
+
+ export class Writable extends events.EventEmitter implements NodeJS.WritableStream {
+ writable: boolean;
+ constructor(opts?: WritableOptions);
+ _write(data: Buffer, encoding: string, callback: Function): void;
+ _write(data: string, encoding: string, callback: Function): void;
+ write(buffer: Buffer, cb?: Function): boolean;
+ write(str: string, cb?: Function): boolean;
+ write(str: string, encoding?: string, cb?: Function): boolean;
+ end(): void;
+ end(buffer: Buffer, cb?: Function): void;
+ end(str: string, cb?: Function): void;
+ end(str: string, encoding?: string, cb?: Function): void;
+ }
+
+ export interface DuplexOptions extends ReadableOptions, WritableOptions {
+ allowHalfOpen?: boolean;
+ }
+
+ // Note: Duplex extends both Readable and Writable.
+ export class Duplex extends Readable implements NodeJS.ReadWriteStream {
+ writable: boolean;
+ constructor(opts?: DuplexOptions);
+ _write(data: Buffer, encoding: string, callback: Function): void;
+ _write(data: string, encoding: string, callback: Function): void;
+ write(buffer: Buffer, cb?: Function): boolean;
+ write(str: string, cb?: Function): boolean;
+ write(str: string, encoding?: string, cb?: Function): boolean;
+ end(): void;
+ end(buffer: Buffer, cb?: Function): void;
+ end(str: string, cb?: Function): void;
+ end(str: string, encoding?: string, cb?: Function): void;
+ }
+
+ export interface TransformOptions extends ReadableOptions, WritableOptions { }
+
+ // Note: Transform lacks the _read and _write methods of Readable/Writable.
+ export class Transform extends events.EventEmitter implements NodeJS.ReadWriteStream {
+ readable: boolean;
+ writable: boolean;
+ constructor(opts?: TransformOptions);
+ _transform(chunk: Buffer, encoding: string, callback: Function): void;
+ _transform(chunk: string, encoding: string, callback: Function): void;
+ _flush(callback: Function): void;
+ read(size?: number): any;
+ setEncoding(encoding: string): void;
+ pause(): void;
+ resume(): void;
+ pipe(destination: T, options?: { end?: boolean; }): T;
+ unpipe(destination?: T): void;
+ unshift(chunk: string): void;
+ unshift(chunk: Buffer): void;
+ wrap(oldStream: NodeJS.ReadableStream): NodeJS.ReadableStream;
+ push(chunk: any, encoding?: string): boolean;
+ write(buffer: Buffer, cb?: Function): boolean;
+ write(str: string, cb?: Function): boolean;
+ write(str: string, encoding?: string, cb?: Function): boolean;
+ end(): void;
+ end(buffer: Buffer, cb?: Function): void;
+ end(str: string, cb?: Function): void;
+ end(str: string, encoding?: string, cb?: Function): void;
+ }
+
+ export class PassThrough extends Transform { }
+ }
+}
+
+declare module NodeJS {
+ module fs {
+ interface Stats {
+ isFile(): boolean;
+ isDirectory(): boolean;
+ isBlockDevice(): boolean;
+ isCharacterDevice(): boolean;
+ isSymbolicLink(): boolean;
+ isFIFO(): boolean;
+ isSocket(): boolean;
+ dev: number;
+ ino: number;
+ mode: number;
+ nlink: number;
+ uid: number;
+ gid: number;
+ rdev: number;
+ size: number;
+ blksize: number;
+ blocks: number;
+ atime: Date;
+ mtime: Date;
+ ctime: Date;
+ }
+ interface FSWatcher extends events.EventEmitter {
+ close(): void;
+ }
+
+ export interface ReadStream extends stream.Readable { }
+ export interface WriteStream extends stream.Writable { }
+
+ export function rename(oldPath: string, newPath: string, callback?: (err?: NodeJS.ErrnoException) => void): void;
+ export function renameSync(oldPath: string, newPath: string): void;
+ export function truncate(path: string, callback?: (err?: NodeJS.ErrnoException) => void): void;
+ export function truncate(path: string, len: number, callback?: (err?: NodeJS.ErrnoException) => void): void;
+ export function truncateSync(path: string, len?: number): void;
+ export function ftruncate(fd: number, callback?: (err?: NodeJS.ErrnoException) => void): void;
+ export function ftruncate(fd: number, len: number, callback?: (err?: NodeJS.ErrnoException) => void): void;
+ export function ftruncateSync(fd: number, len?: number): void;
+ export function chown(path: string, uid: number, gid: number, callback?: (err?: NodeJS.ErrnoException) => void): void;
+ export function chownSync(path: string, uid: number, gid: number): void;
+ export function fchown(fd: number, uid: number, gid: number, callback?: (err?: NodeJS.ErrnoException) => void): void;
+ export function fchownSync(fd: number, uid: number, gid: number): void;
+ export function lchown(path: string, uid: number, gid: number, callback?: (err?: NodeJS.ErrnoException) => void): void;
+ export function lchownSync(path: string, uid: number, gid: number): void;
+ export function chmod(path: string, mode: number, callback?: (err?: NodeJS.ErrnoException) => void): void;
+ export function chmod(path: string, mode: string, callback?: (err?: NodeJS.ErrnoException) => void): void;
+ export function chmodSync(path: string, mode: number): void;
+ export function chmodSync(path: string, mode: string): void;
+ export function fchmod(fd: number, mode: number, callback?: (err?: NodeJS.ErrnoException) => void): void;
+ export function fchmod(fd: number, mode: string, callback?: (err?: NodeJS.ErrnoException) => void): void;
+ export function fchmodSync(fd: number, mode: number): void;
+ export function fchmodSync(fd: number, mode: string): void;
+ export function lchmod(path: string, mode: number, callback?: (err?: NodeJS.ErrnoException) => void): void;
+ export function lchmod(path: string, mode: string, callback?: (err?: NodeJS.ErrnoException) => void): void;
+ export function lchmodSync(path: string, mode: number): void;
+ export function lchmodSync(path: string, mode: string): void;
+ export function stat(path: string, callback?: (err: NodeJS.ErrnoException, stats: Stats) => any): void;
+ export function lstat(path: string, callback?: (err: NodeJS.ErrnoException, stats: Stats) => any): void;
+ export function fstat(fd: number, callback?: (err: NodeJS.ErrnoException, stats: Stats) => any): void;
+ export function statSync(path: string): Stats;
+ export function lstatSync(path: string): Stats;
+ export function fstatSync(fd: number): Stats;
+ export function link(srcpath: string, dstpath: string, callback?: (err?: NodeJS.ErrnoException) => void): void;
+ export function linkSync(srcpath: string, dstpath: string): void;
+ export function symlink(srcpath: string, dstpath: string, type?: string, callback?: (err?: NodeJS.ErrnoException) => void): void;
+ export function symlinkSync(srcpath: string, dstpath: string, type?: string): void;
+ export function readlink(path: string, callback?: (err: NodeJS.ErrnoException, linkString: string) => any): void;
+ export function readlinkSync(path: string): string;
+ export function realpath(path: string, callback?: (err: NodeJS.ErrnoException, resolvedPath: string) => any): void;
+ export function realpath(path: string, cache: { [path: string]: string }, callback: (err: NodeJS.ErrnoException, resolvedPath: string) => any): void;
+ export function realpathSync(path: string, cache?: { [path: string]: string }): string;
+ export function unlink(path: string, callback?: (err?: NodeJS.ErrnoException) => void): void;
+ export function unlinkSync(path: string): void;
+ export function rmdir(path: string, callback?: (err?: NodeJS.ErrnoException) => void): void;
+ export function rmdirSync(path: string): void;
+ export function mkdir(path: string, callback?: (err?: NodeJS.ErrnoException) => void): void;
+ export function mkdir(path: string, mode: number, callback?: (err?: NodeJS.ErrnoException) => void): void;
+ export function mkdir(path: string, mode: string, callback?: (err?: NodeJS.ErrnoException) => void): void;
+ export function mkdirSync(path: string, mode?: number): void;
+ export function mkdirSync(path: string, mode?: string): void;
+ export function readdir(path: string, callback?: (err: NodeJS.ErrnoException, files: string[]) => void): void;
+ export function readdirSync(path: string): string[];
+ export function close(fd: number, callback?: (err?: NodeJS.ErrnoException) => void): void;
+ export function closeSync(fd: number): void;
+ export function open(path: string, flags: string, callback?: (err: NodeJS.ErrnoException, fd: number) => any): void;
+ export function open(path: string, flags: string, mode: number, callback?: (err: NodeJS.ErrnoException, fd: number) => any): void;
+ export function open(path: string, flags: string, mode: string, callback?: (err: NodeJS.ErrnoException, fd: number) => any): void;
+ export function openSync(path: string, flags: string, mode?: number): number;
+ export function openSync(path: string, flags: string, mode?: string): number;
+ export function utimes(path: string, atime: number, mtime: number, callback?: (err?: NodeJS.ErrnoException) => void): void;
+ export function utimes(path: string, atime: Date, mtime: Date, callback?: (err?: NodeJS.ErrnoException) => void): void;
+ export function utimesSync(path: string, atime: number, mtime: number): void;
+ export function utimesSync(path: string, atime: Date, mtime: Date): void;
+ export function futimes(fd: number, atime: number, mtime: number, callback?: (err?: NodeJS.ErrnoException) => void): void;
+ export function futimes(fd: number, atime: Date, mtime: Date, callback?: (err?: NodeJS.ErrnoException) => void): void;
+ export function futimesSync(fd: number, atime: number, mtime: number): void;
+ export function futimesSync(fd: number, atime: Date, mtime: Date): void;
+ export function fsync(fd: number, callback?: (err?: NodeJS.ErrnoException) => void): void;
+ export function fsyncSync(fd: number): void;
+ export function write(fd: number, buffer: Buffer, offset: number, length: number, position: number, callback?: (err: NodeJS.ErrnoException, written: number, buffer: Buffer) => void): void;
+ export function writeSync(fd: number, buffer: Buffer, offset: number, length: number, position: number): number;
+ export function read(fd: number, buffer: Buffer, offset: number, length: number, position: number, callback?: (err: NodeJS.ErrnoException, bytesRead: number, buffer: Buffer) => void): void;
+ export function readSync(fd: number, buffer: Buffer, offset: number, length: number, position: number): number;
+ export function readFile(filename: string, encoding: string, callback: (err: NodeJS.ErrnoException, data: string) => void): void;
+ export function readFile(filename: string, options: { encoding: string; flag?: string; }, callback: (err: NodeJS.ErrnoException, data: string) => void): void;
+ export function readFile(filename: string, options: { flag?: string; }, callback: (err: NodeJS.ErrnoException, data: Buffer) => void): void;
+ export function readFile(filename: string, callback: (err: NodeJS.ErrnoException, data: Buffer) => void): void;
+ export function readFileSync(filename: string, encoding: string): string;
+ export function readFileSync(filename: string, options: { encoding: string; flag?: string; }): string;
+ export function readFileSync(filename: string, options?: { flag?: string; }): Buffer;
+ export function writeFile(filename: string, data: any, callback?: (err: NodeJS.ErrnoException) => void): void;
+ export function writeFile(filename: string, data: any, options: { encoding?: string; mode?: number; flag?: string; }, callback?: (err: NodeJS.ErrnoException) => void): void;
+ export function writeFile(filename: string, data: any, options: { encoding?: string; mode?: string; flag?: string; }, callback?: (err: NodeJS.ErrnoException) => void): void;
+ export function writeFileSync(filename: string, data: any, options?: { encoding?: string; mode?: number; flag?: string; }): void;
+ export function writeFileSync(filename: string, data: any, options?: { encoding?: string; mode?: string; flag?: string; }): void;
+ export function appendFile(filename: string, data: any, options: { encoding?: string; mode?: number; flag?: string; }, callback?: (err: NodeJS.ErrnoException) => void): void;
+ export function appendFile(filename: string, data: any, options: { encoding?: string; mode?: string; flag?: string; }, callback?: (err: NodeJS.ErrnoException) => void): void;
+ export function appendFile(filename: string, data: any, callback?: (err: NodeJS.ErrnoException) => void): void;
+ export function appendFileSync(filename: string, data: any, options?: { encoding?: string; mode?: number; flag?: string; }): void;
+ export function appendFileSync(filename: string, data: any, options?: { encoding?: string; mode?: string; flag?: string; }): void;
+ export function watchFile(filename: string, listener: (curr: Stats, prev: Stats) => void): void;
+ export function watchFile(filename: string, options: { persistent?: boolean; interval?: number; }, listener: (curr: Stats, prev: Stats) => void): void;
+ export function unwatchFile(filename: string, listener?: (curr: Stats, prev: Stats) => void): void;
+ export function watch(filename: string, listener?: (event: string, filename: string) => any): FSWatcher;
+ export function watch(filename: string, options: { persistent?: boolean; }, listener?: (event: string, filename: string) => any): FSWatcher;
+ export function exists(path: string, callback?: (exists: boolean) => void): void;
+ export function existsSync(path: string): boolean;
+ export function createReadStream(path: string, options?: {
+ flags?: string;
+ encoding?: string;
+ fd?: string;
+ mode?: number;
+ bufferSize?: number;
+ }): ReadStream;
+ export function createReadStream(path: string, options?: {
+ flags?: string;
+ encoding?: string;
+ fd?: string;
+ mode?: string;
+ bufferSize?: number;
+ }): ReadStream;
+ export function createWriteStream(path: string, options?: {
+ flags?: string;
+ encoding?: string;
+ string?: string;
+ }): WriteStream;
+ }
+}
+
+declare module NodeJS {
+ module path {
+ export function normalize(p: string): string;
+ export function join(...paths: any[]): string;
+ export function resolve(...pathSegments: any[]): string;
+ export function relative(from: string, to: string): string;
+ export function dirname(p: string): string;
+ export function basename(p: string, ext?: string): string;
+ export function extname(p: string): string;
+ export var sep: string;
+ }
+}
+
+declare module NodeJS {
+ module _debugger {
+ export interface Packet {
+ raw: string;
+ headers: string[];
+ body: Message;
+ }
+
+ export interface Message {
+ seq: number;
+ type: string;
+ }
+
+ export interface RequestInfo {
+ command: string;
+ arguments: any;
+ }
+
+ export interface Request extends Message, RequestInfo {
+ }
+
+ export interface Event extends Message {
+ event: string;
+ body?: any;
+ }
+
+ export interface Response extends Message {
+ request_seq: number;
+ success: boolean;
+ /** Contains error message if success == false. */
+ message?: string;
+ /** Contains message body if success == true. */
+ body?: any;
+ }
+
+ export interface BreakpointMessageBody {
+ type: string;
+ target: number;
+ line: number;
+ }
+
+ export class Protocol {
+ res: Packet;
+ state: string;
+ execute(data: string): void;
+ serialize(rq: Request): string;
+ onResponse: (pkt: Packet) => void;
+ }
+
+ export var NO_FRAME: number;
+ export var port: number;
+
+ export interface ScriptDesc {
+ name: string;
+ id: number;
+ isNative?: boolean;
+ handle?: number;
+ type: string;
+ lineOffset?: number;
+ columnOffset?: number;
+ lineCount?: number;
+ }
+
+ export interface Breakpoint {
+ id: number;
+ scriptId: number;
+ script: ScriptDesc;
+ line: number;
+ condition?: string;
+ scriptReq?: string;
+ }
+
+ export interface RequestHandler {
+ (err: boolean, body: Message, res: Packet): void;
+ request_seq?: number;
+ }
+
+ export interface ResponseBodyHandler {
+ (err: boolean, body?: any): void;
+ request_seq?: number;
+ }
+
+ export interface ExceptionInfo {
+ text: string;
+ }
+
+ export interface BreakResponse {
+ script?: ScriptDesc;
+ exception?: ExceptionInfo;
+ sourceLine: number;
+ sourceLineText: string;
+ sourceColumn: number;
+ }
+
+ export function SourceInfo(body: BreakResponse): string;
+
+ export class Client extends events.EventEmitter {
+ protocol: Protocol;
+ scripts: ScriptDesc[];
+ handles: ScriptDesc[];
+ breakpoints: Breakpoint[];
+ currentSourceLine: number;
+ currentSourceColumn: number;
+ currentSourceLineText: string;
+ currentFrame: number;
+ currentScript: string;
+
+ connect(port: number, host: string): void;
+ req(req: any, cb: RequestHandler): void;
+ reqFrameEval(code: string, frame: number, cb: RequestHandler): void;
+ mirrorObject(obj: any, depth: number, cb: ResponseBodyHandler): void;
+ setBreakpoint(rq: BreakpointMessageBody, cb: RequestHandler): void;
+ clearBreakpoint(rq: Request, cb: RequestHandler): void;
+ listbreakpoints(cb: RequestHandler): void;
+ reqSource(from: number, to: number, cb: RequestHandler): void;
+ reqScripts(cb: any): void;
+ reqContinue(cb: RequestHandler): void;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/server/protocol.d.ts b/src/server/protocol.d.ts
new file mode 100644
index 0000000000000..b07fb20d48d4d
--- /dev/null
+++ b/src/server/protocol.d.ts
@@ -0,0 +1,823 @@
+/**
+ * Declaration module describing the TypeScript Server protocol
+ */
+declare module ts.server.protocol {
+ /**
+ * A TypeScript Server message
+ */
+ export interface Message {
+ /**
+ * Sequence number of the message
+ */
+ seq: number;
+
+ /**
+ * One of "request", "response", or "event"
+ */
+ type: string;
+ }
+
+ /**
+ * Client-initiated request message
+ */
+ export interface Request extends Message {
+ /**
+ * The command to execute
+ */
+ command: string;
+
+ /**
+ * Object containing arguments for the command
+ */
+ arguments?: any;
+ }
+
+ /**
+ * Server-initiated event message
+ */
+ export interface Event extends Message {
+ /**
+ * Name of event
+ */
+ event: string;
+
+ /**
+ * Event-specific information
+ */
+ body?: any;
+ }
+
+ /**
+ * Response by server to client request message.
+ */
+ export interface Response extends Message {
+ /**
+ * Sequence number of the request message.
+ */
+ request_seq: number;
+
+ /**
+ * Outcome of the request.
+ */
+ success: boolean;
+
+ /**
+ * The command requested.
+ */
+ command: string;
+
+ /**
+ * Contains error message if success == false.
+ */
+ message?: string;
+
+ /**
+ * Contains message body if success == true.
+ */
+ body?: any;
+ }
+
+ /**
+ * Arguments for FileRequest messages.
+ */
+ export interface FileRequestArgs {
+ /**
+ * The file for the request (absolute pathname required).
+ */
+ file: string;
+ }
+
+ /**
+ * Request whose sole parameter is a file name.
+ */
+ export interface FileRequest extends Request {
+ arguments: FileRequestArgs;
+ }
+
+ /**
+ * Instances of this interface specify a location in a source file:
+ * (file, line, col), where line and column are 1-based.
+ */
+ export interface FileLocationRequestArgs extends FileRequestArgs {
+ /**
+ * The line number for the request (1-based).
+ */
+ line: number;
+
+ /**
+ * The column for the request (1-based).
+ */
+ col: number;
+ }
+
+ /**
+ * A request whose arguments specify a file location (file, line, col).
+ */
+ export interface FileLocationRequest extends FileRequest {
+ arguments: FileLocationRequestArgs;
+ }
+
+ /**
+ * Go to definition request; value of command field is
+ * "definition". Return response giving the file locations that
+ * define the symbol found in file at location line, col.
+ */
+ export interface DefinitionRequest extends FileLocationRequest {
+ }
+
+ /**
+ * Location in source code expressed as (one-based) line and column.
+ */
+ export interface Location {
+ line: number;
+ col: number;
+ }
+
+ /**
+ * Object found in response messages defining a span of text in source code.
+ */
+ export interface TextSpan {
+ /**
+ * First character of the definition.
+ */
+ start: Location;
+
+ /**
+ * One character past last character of the definition.
+ */
+ end: Location;
+ }
+
+ /**
+ * Object found in response messages defining a span of text in a specific source file.
+ */
+ export interface FileSpan extends TextSpan {
+ /**
+ * File containing text span.
+ */
+ file: string;
+ }
+
+ /**
+ * Definition response message. Gives text range for definition.
+ */
+ export interface DefinitionResponse extends Response {
+ body?: FileSpan[];
+ }
+
+ /**
+ * Find references request; value of command field is
+ * "references". Return response giving the file locations that
+ * reference the symbol found in file at location line, col.
+ */
+ export interface ReferencesRequest extends FileLocationRequest {
+ }
+
+ export interface ReferencesResponseItem extends FileSpan {
+ /** Text of line containing the reference. Including this
+ * with the response avoids latency of editor loading files
+ * to show text of reference line (the server already has
+ * loaded the referencing files).
+ */
+ lineText: string;
+
+ /**
+ * True if reference is a write location, false otherwise.
+ */
+ isWriteAccess: boolean;
+ }
+
+ /**
+ * The body of a "references" response message.
+ */
+ export interface ReferencesResponseBody {
+ /**
+ * The file locations referencing the symbol.
+ */
+ refs: ReferencesResponseItem[];
+
+ /**
+ * The name of the symbol.
+ */
+ symbolName: string;
+
+ /**
+ * The start column of the symbol (on the line provided by the references request).
+ */
+ symbolStartCol: number;
+
+ /**
+ * The full display name of the symbol.
+ */
+ symbolDisplayString: string;
+ }
+
+ /**
+ * Response to "references" request.
+ */
+ export interface ReferencesResponse extends Response {
+ body?: ReferencesResponseBody;
+ }
+
+ export interface RenameRequestArgs extends FileLocationRequestArgs {
+ findInComments?: boolean;
+ findInStrings?: boolean;
+ }
+
+
+ /**
+ * Rename request; value of command field is "rename". Return
+ * response giving the file locations that reference the symbol
+ * found in file at location line, col. Also return full display
+ * name of the symbol so that client can print it unambiguously.
+ */
+ export interface RenameRequest extends FileLocationRequest {
+ arguments: RenameRequestArgs;
+ }
+
+ /**
+ * Information about the item to be renamed.
+ */
+ export interface RenameInfo {
+ /**
+ * True if item can be renamed.
+ */
+ canRename: boolean;
+
+ /**
+ * Error message if item can not be renamed.
+ */
+ localizedErrorMessage?: string;
+
+ /**
+ * Display name of the item to be renamed.
+ */
+ displayName: string;
+
+ /**
+ * Full display name of item to be renamed.
+ */
+ fullDisplayName: string;
+
+ /**
+ * The items's kind (such as 'className' or 'parameterName' or plain 'text').
+ */
+ kind: string;
+
+ /**
+ * Optional modifiers for the kind (such as 'public').
+ */
+ kindModifiers: string;
+ }
+
+ /**
+ * A group of text spans, all in 'file'.
+ */
+ export interface SpanGroup {
+ /** The file to which the spans apply */
+ file: string;
+ /** The text spans in this group */
+ locs: TextSpan[];
+ }
+
+ export interface RenameResponseBody {
+ /**
+ * Information about the item to be renamed.
+ */
+ info: RenameInfo;
+
+ /**
+ * An array of span groups (one per file) that refer to the item to be renamed.
+ */
+ locs: SpanGroup[];
+ }
+
+ /**
+ * Rename response message.
+ */
+ export interface RenameResponse extends Response {
+ body?: RenameResponseBody;
+ }
+
+ /**
+ * Open request; value of command field is "open". Notify the
+ * server that the client has file open. The server will not
+ * monitor the filesystem for changes in this file and will assume
+ * that the client is updating the server (using the change and/or
+ * reload messages) when the file changes. Server does not currently
+ * send a response to an open request.
+ */
+ export interface OpenRequest extends FileRequest {
+ }
+
+ /**
+ * Close request; value of command field is "close". Notify the
+ * server that the client has closed a previously open file. If
+ * file is still referenced by open files, the server will resume
+ * monitoring the filesystem for changes to file. Server does not
+ * currently send a response to a close request.
+ */
+ export interface CloseRequest extends FileRequest {
+ }
+
+ /**
+ * Quickinfo request; value of command field is
+ * "quickinfo". Return response giving a quick type and
+ * documentation string for the symbol found in file at location
+ * line, col.
+ */
+ export interface QuickInfoRequest extends FileLocationRequest {
+ }
+
+ /**
+ * Body of QuickInfoResponse.
+ */
+ export interface QuickInfoResponseBody {
+ /**
+ * The symbol's kind (such as 'className' or 'parameterName' or plain 'text').
+ */
+ kind: string;
+
+ /**
+ * Optional modifiers for the kind (such as 'public').
+ */
+ kindModifiers: string;
+
+ /**
+ * Starting file location of symbol.
+ */
+ start: Location;
+
+ /**
+ * One past last character of symbol.
+ */
+ end: Location;
+
+ /**
+ * Type and kind of symbol.
+ */
+ displayString: string;
+
+ /**
+ * Documentation associated with symbol.
+ */
+ documentation: string;
+ }
+
+ /**
+ * Quickinfo response message.
+ */
+ export interface QuickInfoResponse extends Response {
+ body?: QuickInfoResponseBody;
+ }
+
+ /**
+ * Arguments for format messages.
+ */
+ export interface FormatRequestArgs extends FileLocationRequestArgs {
+ /**
+ * Last line of range for which to format text in file.
+ */
+ endLine: number;
+
+ /**
+ * Last column of range for which to format text in file.
+ */
+ endCol: number;
+ }
+
+ /**
+ * Format request; value of command field is "format". Return
+ * response giving zero or more edit instructions. The edit
+ * instructions will be sorted in file order. Applying the edit
+ * instructions in reverse to file will result in correctly
+ * reformatted text.
+ */
+ export interface FormatRequest extends FileLocationRequest {
+ arguments: FormatRequestArgs;
+ }
+
+ /**
+ * Object found in response messages defining an editing
+ * instruction for a span of text in source code. The effect of
+ * this instruction is to replace the text starting at start and
+ * ending one character before end with newText. For an insertion,
+ * the text span is empty. For a deletion, newText is empty.
+ */
+ export interface CodeEdit {
+ /**
+ * First character of the text span to edit.
+ */
+ start: Location;
+
+ /**
+ * One character past last character of the text span to edit.
+ */
+ end: Location;
+
+ /**
+ * Replace the span defined above with this string (may be
+ * the empty string).
+ */
+ newText: string;
+ }
+
+ /**
+ * Format and format on key response message.
+ */
+ export interface FormatResponse extends Response {
+ body?: CodeEdit[];
+ }
+
+ /**
+ * Arguments for format on key messages.
+ */
+ export interface FormatOnKeyRequestArgs extends FileLocationRequestArgs {
+ /**
+ * Key pressed (';', '\n', or '}').
+ */
+ key: string;
+ }
+
+ /**
+ * Format on key request; value of command field is
+ * "formatonkey". Given file location and key typed (as string),
+ * return response giving zero or more edit instructions. The
+ * edit instructions will be sorted in file order. Applying the
+ * edit instructions in reverse to file will result in correctly
+ * reformatted text.
+ */
+ export interface FormatOnKeyRequest extends FileLocationRequest {
+ arguments: FormatOnKeyRequestArgs;
+ }
+
+ /**
+ * Arguments for completions messages.
+ */
+ export interface CompletionsRequestArgs extends FileLocationRequestArgs {
+ /**
+ * Optional prefix to apply to possible completions.
+ */
+ prefix?: string;
+ }
+
+ /**
+ * Completions request; value of command field is "completions".
+ * Given a file location (file, line, col) and a prefix (which may
+ * be the empty string), return the possible completions that
+ * begin with prefix.
+ */
+ export interface CompletionsRequest extends FileLocationRequest {
+ arguments: CompletionsRequestArgs;
+ }
+
+ /**
+ * Arguments for completion details request.
+ */
+ export interface CompletionDetailsRequestArgs extends FileLocationRequestArgs {
+ /**
+ * Names of one or more entries for which to obtain details.
+ */
+ entryNames: string[];
+ }
+
+ /**
+ * Completion entry details request; value of command field is
+ * "completionEntryDetails". Given a file location (file, line,
+ * col) and an array of completion entry names return more
+ * detailed information for each completion entry.
+ */
+ export interface CompletionDetailsRequest extends FileLocationRequest {
+ arguments: CompletionDetailsRequestArgs;
+ }
+
+ /**
+ * Part of a symbol description.
+ */
+ export interface SymbolDisplayPart {
+ /**
+ * Text of an item describing the symbol.
+ */
+ text: string;
+
+ /**
+ * The symbol's kind (such as 'className' or 'parameterName' or plain 'text').
+ */
+ kind: string;
+ }
+
+ /**
+ * An item found in a completion response.
+ */
+ export interface CompletionEntry {
+ /**
+ * The symbol's name.
+ */
+ name: string;
+ /**
+ * The symbol's kind (such as 'className' or 'parameterName').
+ */
+ kind: string;
+ /**
+ * Optional modifiers for the kind (such as 'public').
+ */
+ kindModifiers: string;
+ }
+
+ /**
+ * Additional completion entry details, available on demand
+ */
+ export interface CompletionEntryDetails extends CompletionEntry {
+ /**
+ * Display parts of the symbol (similar to quick info).
+ */
+ displayParts: SymbolDisplayPart[];
+
+ /**
+ * Documentation strings for the symbol.
+ */
+ documentation: SymbolDisplayPart[];
+ }
+
+ export interface CompletionsResponse extends Response {
+ body?: CompletionEntry[];
+ }
+
+ export interface CompletionDetailsResponse extends Response {
+ body?: CompletionEntryDetails[];
+ }
+
+ /**
+ * Arguments for geterr messages.
+ */
+ export interface GeterrRequestArgs {
+ /**
+ * List of file names for which to compute compiler errors.
+ * The files will be checked in list order.
+ */
+ files: string[];
+
+ /**
+ * Delay in milliseconds to wait before starting to compute
+ * errors for the files in the file list
+ */
+ delay: number;
+ }
+
+ /**
+ * Geterr request; value of command field is "geterr". Wait for
+ * delay milliseconds and then, if during the wait no change or
+ * reload messages have arrived for the first file in the files
+ * list, get the syntactic errors for the file, field requests,
+ * and then get the semantic errors for the file. Repeat with a
+ * smaller delay for each subsequent file on the files list. Best
+ * practice for an editor is to send a file list containing each
+ * file that is currently visible, in most-recently-used order.
+ */
+ export interface GeterrRequest extends Request {
+ arguments: GeterrRequestArgs;
+ }
+
+ /**
+ * Item of diagnostic information found in a DiagnosticEvent message.
+ */
+ export interface Diagnostic {
+ /**
+ * Starting file location at which text appies.
+ */
+ start: Location;
+
+ /**
+ * The last file location at which the text applies.
+ */
+ end: Location;
+
+ /**
+ * Text of diagnostic message.
+ */
+ text: string;
+ }
+
+ export interface DiagnosticEventBody {
+ /**
+ * The file for which diagnostic information is reported.
+ */
+ file: string;
+
+ /**
+ * An array of diagnostic information items.
+ */
+ diagnostics: Diagnostic[];
+ }
+
+ /**
+ * Event message for "syntaxDiag" and "semanticDiag" event types.
+ * These events provide syntactic and semantic errors for a file.
+ */
+ export interface DiagnosticEvent extends Event {
+ body?: DiagnosticEventBody;
+ }
+
+ /**
+ * Arguments for reload request.
+ */
+ export interface ReloadRequestArgs extends FileRequestArgs {
+ /**
+ * Name of temporary file from which to reload file
+ * contents. May be same as file.
+ */
+ tmpfile: string;
+ }
+
+ /**
+ * Reload request message; value of command field is "reload".
+ * Reload contents of file with name given by the 'file' argument
+ * from temporary file with name given by the 'tmpfile' argument.
+ * The two names can be identical.
+ */
+ export interface ReloadRequest extends FileRequest {
+ arguments: ReloadRequestArgs;
+ }
+
+ /**
+ * Response to "reload" request. This is just an acknowledgement, so
+ * no body field is required.
+ */
+ export interface ReloadResponse extends Response {
+ }
+
+ /**
+ * Arguments for saveto request.
+ */
+ export interface SavetoRequestArgs extends FileRequestArgs {
+ /**
+ * Name of temporary file into which to save server's view of
+ * file contents.
+ */
+ tmpfile: string;
+ }
+
+ /**
+ * Saveto request message; value of command field is "saveto".
+ * For debugging purposes, save to a temporaryfile (named by
+ * argument 'tmpfile') the contents of file named by argument
+ * 'file'. The server does not currently send a response to a
+ * "saveto" request.
+ */
+ export interface SavetoRequest extends FileRequest {
+ arguments: SavetoRequestArgs;
+ }
+
+ /**
+ * Arguments for navto request message.
+ */
+ export interface NavtoRequestArgs extends FileRequestArgs {
+ /**
+ * Search term to navigate to from current location; term can
+ * be '.*' or an identifier prefix.
+ */
+ searchTerm: string;
+ }
+
+ /**
+ * Navto request message; value of command field is "navto".
+ * Return list of objects giving file locations and symbols that
+ * match the search term given in argument 'searchTerm'. The
+ * context for the search is given by the named file.
+ */
+ export interface NavtoRequest extends FileRequest {
+ arguments: NavtoRequestArgs;
+ }
+
+ /**
+ * An item found in a navto response.
+ */
+ export interface NavtoItem {
+ /**
+ * The symbol's name.
+ */
+ name: string;
+
+ /**
+ * The symbol's kind (such as 'className' or 'parameterName').
+ */
+ kind: string;
+
+ /**
+ * exact, substring, or prefix.
+ */
+ matchKind?: string;
+
+ /**
+ * Optional modifiers for the kind (such as 'public').
+ */
+ kindModifiers?: string;
+
+ /**
+ * The file in which the symbol is found.
+ */
+ file: string;
+
+ /**
+ * The location within file at which the symbol is found.
+ */
+ start: Location;
+
+ /**
+ * One past the last character of the symbol.
+ */
+ end: Location;
+
+ /**
+ * Name of symbol's container symbol (if any); for example,
+ * the class name if symbol is a class member.
+ */
+ containerName?: string;
+
+ /**
+ * Kind of symbol's container symbol (if any).
+ */
+ containerKind?: string;
+ }
+
+ /**
+ * Navto response message. Body is an array of navto items. Each
+ * item gives a symbol that matched the search term.
+ */
+ export interface NavtoResponse extends Response {
+ body?: NavtoItem[];
+ }
+
+ /**
+ * Arguments for change request message.
+ */
+ export interface ChangeRequestArgs extends FormatRequestArgs {
+ /**
+ * Optional string to insert at location (file, line, col).
+ */
+ insertString?: string;
+ }
+
+ /**
+ * Change request message; value of command field is "change".
+ * Update the server's view of the file named by argument 'file'.
+ * Server does not currently send a response to a change request.
+ */
+ export interface ChangeRequest extends FileLocationRequest {
+ arguments: ChangeRequestArgs;
+ }
+
+ /**
+ * Response to "brace" request.
+ */
+ export interface BraceResponse extends Response {
+ body?: TextSpan[];
+ }
+
+ /**
+ * Brace matching request; value of command field is "brace".
+ * Return response giving the file locations of matching braces
+ * found in file at location line, col.
+ */
+ export interface BraceRequest extends FileLocationRequest {
+ }
+
+ /**
+ * NavBar itesm request; value of command field is "navbar".
+ * Return response giving the list of navigation bar entries
+ * extracted from the requested file.
+ */
+ export interface NavBarRequest extends FileRequest {
+ }
+
+ export interface NavigationBarItem {
+ /**
+ * The item's display text.
+ */
+ text: string;
+
+ /**
+ * The symbol's kind (such as 'className' or 'parameterName').
+ */
+ kind: string;
+
+ /**
+ * Optional modifiers for the kind (such as 'public').
+ */
+ kindModifiers?: string;
+
+ /**
+ * The definition locations of the item.
+ */
+ spans: TextSpan[];
+
+ /**
+ * Optional children.
+ */
+ childItems?: NavigationBarItem[];
+ }
+
+ export interface NavBarResponse extends Response {
+ body?: NavigationBarItem[];
+ }
+}
diff --git a/src/server/server.ts b/src/server/server.ts
new file mode 100644
index 0000000000000..8921a4b476824
--- /dev/null
+++ b/src/server/server.ts
@@ -0,0 +1,219 @@
+///
+///
+
+module ts.server {
+ var nodeproto: typeof NodeJS._debugger = require('_debugger');
+ var readline: NodeJS.ReadLine = require('readline');
+ var path: NodeJS.Path = require('path');
+ var fs: typeof NodeJS.fs = require('fs');
+
+ var rl = readline.createInterface({
+ input: process.stdin,
+ output: process.stdout,
+ terminal: false,
+ });
+
+ class Logger implements ts.server.Logger {
+ fd = -1;
+ seq = 0;
+ inGroup = false;
+ firstInGroup = true;
+
+ constructor(public logFilename: string) {
+ }
+
+ static padStringRight(str: string, padding: string) {
+ return (str + padding).slice(0, padding.length);
+ }
+
+ close() {
+ if (this.fd >= 0) {
+ fs.close(this.fd);
+ }
+ }
+
+ perftrc(s: string) {
+ this.msg(s, "Perf");
+ }
+
+ info(s: string) {
+ this.msg(s, "Info");
+ }
+
+ startGroup() {
+ this.inGroup = true;
+ this.firstInGroup = true;
+ }
+
+ endGroup() {
+ this.inGroup = false;
+ this.seq++;
+ this.firstInGroup = true;
+ }
+
+ msg(s: string, type = "Err") {
+ if (this.fd < 0) {
+ this.fd = fs.openSync(this.logFilename, "w");
+ }
+ if (this.fd >= 0) {
+ s = s + "\n";
+ var prefix = Logger.padStringRight(type + " " + this.seq.toString(), " ");
+ if (this.firstInGroup) {
+ s = prefix + s;
+ this.firstInGroup = false;
+ }
+ if (!this.inGroup) {
+ this.seq++;
+ this.firstInGroup = true;
+ }
+ var buf = new Buffer(s);
+ fs.writeSync(this.fd, buf, 0, buf.length, null);
+ }
+ }
+ }
+
+ interface WatchedFile {
+ fileName: string;
+ callback: (fileName: string) => void;
+ mtime: Date;
+ }
+
+ class WatchedFileSet {
+ private watchedFiles: WatchedFile[] = [];
+ private nextFileToCheck = 0;
+ private watchTimer: NodeJS.Timer;
+ private static fileDeleted = 34;
+
+ // average async stat takes about 30 microseconds
+ // set chunk size to do 30 files in < 1 millisecond
+ constructor(public interval = 2500, public chunkSize = 30) {
+ }
+
+ private static copyListRemovingItem(item: T, list: T[]) {
+ var copiedList: T[] = [];
+ for (var i = 0, len = list.length; i < len; i++) {
+ if (list[i] != item) {
+ copiedList.push(list[i]);
+ }
+ }
+ return copiedList;
+ }
+
+ private static getModifiedTime(fileName: string): Date {
+ return fs.statSync(fileName).mtime;
+ }
+
+ private poll(checkedIndex: number) {
+ var watchedFile = this.watchedFiles[checkedIndex];
+ if (!watchedFile) {
+ return;
+ }
+
+ fs.stat(watchedFile.fileName,(err, stats) => {
+ if (err) {
+ var msg = err.message;
+ if (err.errno) {
+ msg += " errno: " + err.errno.toString();
+ }
+ if (err.errno == WatchedFileSet.fileDeleted) {
+ watchedFile.callback(watchedFile.fileName);
+ }
+ }
+ else if (watchedFile.mtime.getTime() != stats.mtime.getTime()) {
+ watchedFile.mtime = WatchedFileSet.getModifiedTime(watchedFile.fileName);
+ watchedFile.callback(watchedFile.fileName);
+ }
+ });
+ }
+
+ // this implementation uses polling and
+ // stat due to inconsistencies of fs.watch
+ // and efficiency of stat on modern filesystems
+ private startWatchTimer() {
+ this.watchTimer = setInterval(() => {
+ var count = 0;
+ var nextToCheck = this.nextFileToCheck;
+ var firstCheck = -1;
+ while ((count < this.chunkSize) && (nextToCheck != firstCheck)) {
+ this.poll(nextToCheck);
+ if (firstCheck < 0) {
+ firstCheck = nextToCheck;
+ }
+ nextToCheck++;
+ if (nextToCheck === this.watchedFiles.length) {
+ nextToCheck = 0;
+ }
+ count++;
+ }
+ this.nextFileToCheck = nextToCheck;
+ }, this.interval);
+ }
+
+ addFile(fileName: string, callback: (fileName: string) => void ): WatchedFile {
+ var file: WatchedFile = {
+ fileName,
+ callback,
+ mtime: WatchedFileSet.getModifiedTime(fileName)
+ };
+
+ this.watchedFiles.push(file);
+ if (this.watchedFiles.length === 1) {
+ this.startWatchTimer();
+ }
+ return file;
+ }
+
+ removeFile(file: WatchedFile) {
+ this.watchedFiles = WatchedFileSet.copyListRemovingItem(file, this.watchedFiles);
+ }
+ }
+
+ class IOSession extends Session {
+ constructor(host: ServerHost, logger: ts.server.Logger) {
+ super(host, logger);
+ }
+
+ listen() {
+ rl.on('line',(input: string) => {
+ var message = input.trim();
+ this.onMessage(message);
+ });
+
+ rl.on('close',() => {
+ this.projectService.closeLog();
+ this.projectService.log("Exiting...");
+ process.exit(0);
+ });
+ }
+ }
+
+ // This places log file in the directory containing editorServices.js
+ // TODO: check that this location is writable
+ var logger = new Logger(__dirname + "/.log" + process.pid.toString());
+
+
+ // REVIEW: for now this implementation uses polling.
+ // The advantage of polling is that it works reliably
+ // on all os and with network mounted files.
+ // For 90 referenced files, the average time to detect
+ // changes is 2*msInterval (by default 5 seconds).
+ // The overhead of this is .04 percent (1/2500) with
+ // average pause of < 1 millisecond (and max
+ // pause less than 1.5 milliseconds); question is
+ // do we anticipate reference sets in the 100s and
+ // do we care about waiting 10-20 seconds to detect
+ // changes for large reference sets? If so, do we want
+ // to increase the chunk size or decrease the interval
+ // time dynamically to match the large reference set?
+ var watchedFileSet = new WatchedFileSet();
+ ts.sys.watchFile = function (fileName, callback) {
+ var watchedFile = watchedFileSet.addFile(fileName, callback);
+ return {
+ close: () => watchedFileSet.removeFile(watchedFile)
+ }
+
+ };
+
+ // Start listening
+ new IOSession(ts.sys, logger).listen();
+}
\ No newline at end of file
diff --git a/src/server/session.ts b/src/server/session.ts
new file mode 100644
index 0000000000000..19f6cd3645ea0
--- /dev/null
+++ b/src/server/session.ts
@@ -0,0 +1,801 @@
+///
+///
+///
+///
+///
+
+module ts.server {
+ var spaceCache = [" ", " ", " ", " "];
+
+ interface StackTraceError extends Error {
+ stack?: string;
+ }
+
+ function generateSpaces(n: number): string {
+ if (!spaceCache[n]) {
+ var strBuilder = "";
+ for (var i = 0; i < n; i++) {
+ strBuilder += " ";
+ }
+ spaceCache[n] = strBuilder;
+ }
+ return spaceCache[n];
+ }
+
+ interface FileStart {
+ file: string;
+ start: ILineInfo;
+ }
+
+ function compareNumber(a: number, b: number) {
+ if (a < b) {
+ return -1;
+ }
+ else if (a == b) {
+ return 0;
+ }
+ else return 1;
+ }
+
+ function compareFileStart(a: FileStart, b: FileStart) {
+ if (a.file < b.file) {
+ return -1;
+ }
+ else if (a.file == b.file) {
+ var n = compareNumber(a.start.line, b.start.line);
+ if (n == 0) {
+ return compareNumber(a.start.col, b.start.col);
+ }
+ else return n;
+ }
+ else {
+ return 1;
+ }
+ }
+
+ function sortNavItems(items: ts.NavigateToItem[]) {
+ return items.sort((a, b) => {
+ if (a.matchKind < b.matchKind) {
+ return -1;
+ }
+ else if (a.matchKind == b.matchKind) {
+ var lowa = a.name.toLowerCase();
+ var lowb = b.name.toLowerCase();
+ if (lowa < lowb) {
+ return -1;
+ }
+ else if (lowa == lowb) {
+ return 0;
+ }
+ else {
+ return 1;
+ }
+ }
+ else {
+ return 1;
+ }
+ })
+ }
+
+ function formatDiag(fileName: string, project: Project, diag: ts.Diagnostic) {
+ return {
+ start: project.compilerService.host.positionToLineCol(fileName, diag.start),
+ end: project.compilerService.host.positionToLineCol(fileName, diag.start + diag.length),
+ text: ts.flattenDiagnosticMessageText(diag.messageText, "\n")
+ };
+ }
+
+ interface PendingErrorCheck {
+ fileName: string;
+ project: Project;
+ }
+
+ function allEditsBeforePos(edits: ts.TextChange[], pos: number) {
+ for (var i = 0, len = edits.length; i < len; i++) {
+ if (ts.textSpanEnd(edits[i].span) >= pos) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ export module CommandNames {
+ export var Change = "change";
+ export var Close = "close";
+ export var Completions = "completions";
+ export var CompletionDetails = "completionEntryDetails";
+ export var Definition = "definition";
+ export var Format = "format";
+ export var Formatonkey = "formatonkey";
+ export var Geterr = "geterr";
+ export var NavBar = "navbar";
+ export var Navto = "navto";
+ export var Open = "open";
+ export var Quickinfo = "quickinfo";
+ export var References = "references";
+ export var Reload = "reload";
+ export var Rename = "rename";
+ export var Saveto = "saveto";
+ export var Brace = "brace";
+ export var Unknown = "unknown";
+ }
+
+ module Errors {
+ export var NoProject = new Error("No Project.");
+ export var NoContent = new Error("No Content.");
+ }
+
+ export interface ServerHost extends ts.System {
+ }
+
+ export class Session {
+ projectService: ProjectService;
+ pendingOperation = false;
+ fileHash: ts.Map = {};
+ nextFileId = 1;
+ errorTimer: NodeJS.Timer;
+ immediateId: any;
+ changeSeq = 0;
+
+ constructor(private host: ServerHost, private logger: Logger) {
+ this.projectService = new ProjectService(host, logger);
+ }
+
+ logError(err: Error, cmd: string) {
+ var typedErr = err;
+ var msg = "Exception on executing command " + cmd;
+ if (typedErr.message) {
+ msg += ":\n" + typedErr.message;
+ if (typedErr.stack) {
+ msg += "\n" + typedErr.stack;
+ }
+ }
+ this.projectService.log(msg);
+ }
+
+ sendLineToClient(line: string) {
+ this.host.write(line + this.host.newLine);
+ }
+
+ send(msg: NodeJS._debugger.Message) {
+ var json = JSON.stringify(msg);
+ this.sendLineToClient('Content-Length: ' + (1 + Buffer.byteLength(json, 'utf8')) +
+ '\r\n\r\n' + json);
+ }
+
+ event(info: any, eventName: string) {
+ var ev: NodeJS._debugger.Event = {
+ seq: 0,
+ type: "event",
+ event: eventName,
+ body: info,
+ };
+ this.send(ev);
+ }
+
+ response(info: any, cmdName: string, reqSeq = 0, errorMsg?: string) {
+ var res: protocol.Response = {
+ seq: 0,
+ type: "response",
+ command: cmdName,
+ request_seq: reqSeq,
+ success: !errorMsg,
+ }
+ if (!errorMsg) {
+ res.body = info;
+ }
+ else {
+ res.message = errorMsg;
+ }
+ this.send(res);
+ }
+
+ output(body: any, commandName: string, requestSequence = 0, errorMessage?: string) {
+ this.response(body, commandName, requestSequence, errorMessage);
+ }
+
+ semanticCheck(file: string, project: Project) {
+ var diags = project.compilerService.languageService.getSemanticDiagnostics(file);
+ if (diags) {
+ var bakedDiags = diags.map((diag) => formatDiag(file, project, diag));
+ this.event({ file: file, diagnostics: bakedDiags }, "semanticDiag");
+ }
+ }
+
+ syntacticCheck(file: string, project: Project) {
+ var diags = project.compilerService.languageService.getSyntacticDiagnostics(file);
+ if (diags) {
+ var bakedDiags = diags.map((diag) => formatDiag(file, project, diag));
+ this.event({ file: file, diagnostics: bakedDiags }, "syntaxDiag");
+ }
+ }
+
+ errorCheck(file: string, project: Project) {
+ this.syntacticCheck(file, project);
+ this.semanticCheck(file, project);
+ }
+
+ updateErrorCheck(checkList: PendingErrorCheck[], seq: number,
+ matchSeq: (seq: number) => boolean, ms = 1500, followMs = 200) {
+ if (followMs > ms) {
+ followMs = ms;
+ }
+ if (this.errorTimer) {
+ clearTimeout(this.errorTimer);
+ }
+ if (this.immediateId) {
+ clearImmediate(this.immediateId);
+ this.immediateId = undefined;
+ }
+ var index = 0;
+ var checkOne = () => {
+ if (matchSeq(seq)) {
+ var checkSpec = checkList[index++];
+ if (checkSpec.project.getSourceFileFromName(checkSpec.fileName)) {
+ this.syntacticCheck(checkSpec.fileName, checkSpec.project);
+ this.immediateId = setImmediate(() => {
+ this.semanticCheck(checkSpec.fileName, checkSpec.project);
+ this.immediateId = undefined;
+ if (checkList.length > index) {
+ this.errorTimer = setTimeout(checkOne, followMs);
+ }
+ else {
+ this.errorTimer = undefined;
+ }
+ });
+ }
+ }
+ }
+ if ((checkList.length > index) && (matchSeq(seq))) {
+ this.errorTimer = setTimeout(checkOne, ms);
+ }
+ }
+
+ getDefinition(line: number, col: number, fileName: string): protocol.FileSpan[] {
+ var file = ts.normalizePath(fileName);
+ var project = this.projectService.getProjectForFile(file);
+ if (!project) {
+ throw Errors.NoProject;
+ }
+
+ var compilerService = project.compilerService;
+ var position = compilerService.host.lineColToPosition(file, line, col);
+
+ var definitions = compilerService.languageService.getDefinitionAtPosition(file, position);
+ if (!definitions) {
+ throw Errors.NoContent;
+ }
+
+ return definitions.map(def => ({
+ file: def.fileName,
+ start: compilerService.host.positionToLineCol(def.fileName, def.textSpan.start),
+ end: compilerService.host.positionToLineCol(def.fileName, ts.textSpanEnd(def.textSpan))
+ }));
+ }
+
+ getRenameLocations(line: number, col: number, fileName: string,findInComments: boolean, findInStrings: boolean): protocol.RenameResponseBody {
+ var file = ts.normalizePath(fileName);
+ var project = this.projectService.getProjectForFile(file);
+ if (!project) {
+ throw Errors.NoProject;
+ }
+
+ var compilerService = project.compilerService;
+ var position = compilerService.host.lineColToPosition(file, line, col);
+ var renameInfo = compilerService.languageService.getRenameInfo(file, position);
+ if (!renameInfo) {
+ throw Errors.NoContent;
+ }
+
+ if (!renameInfo.canRename) {
+ return {
+ info: renameInfo,
+ locs: []
+ };
+ }
+
+ var renameLocations = compilerService.languageService.findRenameLocations(file, position, findInStrings, findInComments);
+ if (!renameLocations) {
+ throw Errors.NoContent;
+ }
+
+ var bakedRenameLocs = renameLocations.map(location => ({
+ file: location.fileName,
+ start: compilerService.host.positionToLineCol(location.fileName, location.textSpan.start),
+ end: compilerService.host.positionToLineCol(location.fileName, ts.textSpanEnd(location.textSpan)),
+ })).sort((a, b) => {
+ if (a.file < b.file) {
+ return -1;
+ }
+ else if (a.file > b.file) {
+ return 1;
+ }
+ else {
+ // reverse sort assuming no overlap
+ if (a.start.line < b.start.line) {
+ return 1;
+ }
+ else if (a.start.line > b.start.line) {
+ return -1;
+ }
+ else {
+ return b.start.col - a.start.col;
+ }
+ }
+ }).reduce((accum: protocol.SpanGroup[], cur: protocol.FileSpan) => {
+ var curFileAccum: protocol.SpanGroup;
+ if (accum.length > 0) {
+ curFileAccum = accum[accum.length - 1];
+ if (curFileAccum.file != cur.file) {
+ curFileAccum = undefined;
+ }
+ }
+ if (!curFileAccum) {
+ curFileAccum = { file: cur.file, locs: [] };
+ accum.push(curFileAccum);
+ }
+ curFileAccum.locs.push({ start: cur.start, end: cur.end });
+ return accum;
+ }, []);
+
+ return { info: renameInfo, locs: bakedRenameLocs };
+ }
+
+ getReferences(line: number, col: number, fileName: string): protocol.ReferencesResponseBody {
+ // TODO: get all projects for this file; report refs for all projects deleting duplicates
+ // can avoid duplicates by eliminating same ref file from subsequent projects
+ var file = ts.normalizePath(fileName);
+ var project = this.projectService.getProjectForFile(file);
+ if (!project) {
+ throw Errors.NoProject;
+ }
+
+ var compilerService = project.compilerService;
+ var position = compilerService.host.lineColToPosition(file, line, col);
+
+ var references = compilerService.languageService.getReferencesAtPosition(file, position);
+ if (!references) {
+ throw Errors.NoContent;
+ }
+
+ var nameInfo = compilerService.languageService.getQuickInfoAtPosition(file, position);
+ if (!nameInfo) {
+ throw Errors.NoContent;
+ }
+
+ var displayString = ts.displayPartsToString(nameInfo.displayParts);
+ var nameSpan = nameInfo.textSpan;
+ var nameColStart = compilerService.host.positionToLineCol(file, nameSpan.start).col;
+ var nameText = compilerService.host.getScriptSnapshot(file).getText(nameSpan.start, ts.textSpanEnd(nameSpan));
+ var bakedRefs: protocol.ReferencesResponseItem[] = references.map((ref) => {
+ var start = compilerService.host.positionToLineCol(ref.fileName, ref.textSpan.start);
+ var refLineSpan = compilerService.host.lineToTextSpan(ref.fileName, start.line - 1);
+ var snap = compilerService.host.getScriptSnapshot(ref.fileName);
+ var lineText = snap.getText(refLineSpan.start, ts.textSpanEnd(refLineSpan)).replace(/\r|\n/g, "");
+ return {
+ file: ref.fileName,
+ start: start,
+ lineText: lineText,
+ end: compilerService.host.positionToLineCol(ref.fileName, ts.textSpanEnd(ref.textSpan)),
+ isWriteAccess: ref.isWriteAccess
+ };
+ }).sort(compareFileStart);
+ return {
+ refs: bakedRefs,
+ symbolName: nameText,
+ symbolStartCol: nameColStart,
+ symbolDisplayString: displayString
+ };
+ }
+
+ openClientFile(fileName: string) {
+ var file = ts.normalizePath(fileName);
+ this.projectService.openClientFile(file);
+ }
+
+ getQuickInfo(line: number, col: number, fileName: string): protocol.QuickInfoResponseBody {
+ var file = ts.normalizePath(fileName);
+ var project = this.projectService.getProjectForFile(file);
+ if (!project) {
+ throw Errors.NoProject;
+ }
+
+ var compilerService = project.compilerService;
+ var position = compilerService.host.lineColToPosition(file, line, col);
+ var quickInfo = compilerService.languageService.getQuickInfoAtPosition(file, position);
+ if (!quickInfo) {
+ throw Errors.NoContent;
+ }
+
+ var displayString = ts.displayPartsToString(quickInfo.displayParts);
+ var docString = ts.displayPartsToString(quickInfo.documentation);
+ return {
+ kind: quickInfo.kind,
+ kindModifiers: quickInfo.kindModifiers,
+ start: compilerService.host.positionToLineCol(file, quickInfo.textSpan.start),
+ end: compilerService.host.positionToLineCol(file, ts.textSpanEnd(quickInfo.textSpan)),
+ displayString: displayString,
+ documentation: docString,
+ };
+ }
+
+ getFormattingEditsForRange(line: number, col: number, endLine: number, endCol: number, fileName: string): protocol.CodeEdit[] {
+ var file = ts.normalizePath(fileName);
+ var project = this.projectService.getProjectForFile(file);
+ if (!project) {
+ throw Errors.NoProject;
+ }
+
+ var compilerService = project.compilerService;
+ var startPosition = compilerService.host.lineColToPosition(file, line, col);
+ var endPosition = compilerService.host.lineColToPosition(file, endLine, endCol);
+
+ // TODO: avoid duplicate code (with formatonkey)
+ var edits = compilerService.languageService.getFormattingEditsForRange(file, startPosition, endPosition, compilerService.formatCodeOptions);
+ if (!edits) {
+ throw Errors.NoContent;
+ }
+
+ return edits.map((edit) => {
+ return {
+ start: compilerService.host.positionToLineCol(file, edit.span.start),
+ end: compilerService.host.positionToLineCol(file, ts.textSpanEnd(edit.span)),
+ newText: edit.newText ? edit.newText : ""
+ };
+ });
+ }
+
+ getFormattingEditsAfterKeystroke(line: number, col: number, key: string, fileName: string): protocol.CodeEdit[] {
+ var file = ts.normalizePath(fileName);
+
+ var project = this.projectService.getProjectForFile(file);
+ if (!project) {
+ throw Errors.NoProject;
+ }
+
+ var compilerService = project.compilerService;
+ var position = compilerService.host.lineColToPosition(file, line, col);
+ var edits = compilerService.languageService.getFormattingEditsAfterKeystroke(file, position, key,
+ compilerService.formatCodeOptions);
+ if ((key == "\n") && ((!edits) || (edits.length == 0) || allEditsBeforePos(edits, position))) {
+ // TODO: get these options from host
+ var editorOptions: ts.EditorOptions = {
+ IndentSize: 4,
+ TabSize: 4,
+ NewLineCharacter: "\n",
+ ConvertTabsToSpaces: true,
+ };
+ var indentPosition = compilerService.languageService.getIndentationAtPosition(file, position, editorOptions);
+ var spaces = generateSpaces(indentPosition);
+ if (indentPosition > 0) {
+ edits.push({ span: ts.createTextSpanFromBounds(position, position), newText: spaces });
+ }
+ }
+
+ if (!edits) {
+ throw Errors.NoContent;
+ }
+
+ return edits.map((edit) => {
+ return {
+ start: compilerService.host.positionToLineCol(file,
+ edit.span.start),
+ end: compilerService.host.positionToLineCol(file,
+ ts.textSpanEnd(edit.span)),
+ newText: edit.newText ? edit.newText : ""
+ };
+ });
+ }
+
+ getCompletions(line: number, col: number, prefix: string, fileName: string): protocol.CompletionEntry[] {
+ if (!prefix) {
+ prefix = "";
+ }
+ var file = ts.normalizePath(fileName);
+ var project = this.projectService.getProjectForFile(file);
+ if (!project) {
+ throw Errors.NoProject;
+ }
+
+ var compilerService = project.compilerService;
+ var position = compilerService.host.lineColToPosition(file, line, col);
+
+ var completions = compilerService.languageService.getCompletionsAtPosition(file, position);
+ if (!completions) {
+ throw Errors.NoContent;
+ }
+
+ return completions.entries.reduce((result: protocol.CompletionEntry[], entry: ts.CompletionEntry) => {
+ if (completions.isMemberCompletion || entry.name.indexOf(prefix) == 0) {
+ result.push(entry);
+ }
+ return result;
+ }, []);
+ }
+
+ getCompletionEntryDetails(line: number, col: number,
+ entryNames: string[], fileName: string): protocol.CompletionEntryDetails[] {
+ var file = ts.normalizePath(fileName);
+ var project = this.projectService.getProjectForFile(file);
+ if (!project) {
+ throw Errors.NoProject;
+ }
+
+ var compilerService = project.compilerService;
+ var position = compilerService.host.lineColToPosition(file, line, col);
+
+ return entryNames.reduce((accum: protocol.CompletionEntryDetails[], entryName: string) => {
+ var details = compilerService.languageService.getCompletionEntryDetails(file, position, entryName);
+ if (details) {
+ accum.push(details);
+ }
+ return accum;
+ }, []);
+ }
+
+ getDiagnostics(delay: number, fileNames: string[]) {
+ var checkList = fileNames.reduce((accum: PendingErrorCheck[], fileName: string) => {
+ fileName = ts.normalizePath(fileName);
+ var project = this.projectService.getProjectForFile(fileName);
+ if (project) {
+ accum.push({ fileName, project });
+ }
+ return accum;
+ }, []);
+
+ if (checkList.length > 0) {
+ this.updateErrorCheck(checkList, this.changeSeq,(n) => n == this.changeSeq, delay)
+ }
+ }
+
+ change(line: number, col: number, endLine: number, endCol: number, insertString: string, fileName: string) {
+ var file = ts.normalizePath(fileName);
+ var project = this.projectService.getProjectForFile(file);
+ if (project) {
+ var compilerService = project.compilerService;
+ var start = compilerService.host.lineColToPosition(file, line, col);
+ var end = compilerService.host.lineColToPosition(file, endLine, endCol);
+ if (start >= 0) {
+ compilerService.host.editScript(file, start, end, insertString);
+ this.changeSeq++;
+ }
+ }
+ }
+
+ reload(fileName: string, tempFileName: string, reqSeq = 0) {
+ var file = ts.normalizePath(fileName);
+ var tmpfile = ts.normalizePath(tempFileName);
+ var project = this.projectService.getProjectForFile(file);
+ if (project) {
+ this.changeSeq++;
+ // make sure no changes happen before this one is finished
+ project.compilerService.host.reloadScript(file, tmpfile,() => {
+ this.output(undefined, CommandNames.Reload, reqSeq);
+ });
+ }
+ }
+
+ saveToTmp(fileName: string, tempFileName: string) {
+ var file = ts.normalizePath(fileName);
+ var tmpfile = ts.normalizePath(tempFileName);
+
+ var project = this.projectService.getProjectForFile(file);
+ if (project) {
+ project.compilerService.host.saveTo(file, tmpfile);
+ }
+ }
+
+ closeClientFile(fileName: string) {
+ var file = ts.normalizePath(fileName);
+ this.projectService.closeClientFile(file);
+ }
+
+ decorateNavigationBarItem(project: Project, fileName: string, items: ts.NavigationBarItem[]): protocol.NavigationBarItem[] {
+ if (!items) {
+ return undefined;
+ }
+
+ var compilerService = project.compilerService;
+
+ return items.map(item => ({
+ text: item.text,
+ kind: item.kind,
+ kindModifiers: item.kindModifiers,
+ spans: item.spans.map(span => ({
+ start: compilerService.host.positionToLineCol(fileName, span.start),
+ end: compilerService.host.positionToLineCol(fileName, ts.textSpanEnd(span))
+ })),
+ childItems: this.decorateNavigationBarItem(project, fileName, item.childItems)
+ }));
+ }
+
+ getNavigationBarItems(fileName: string): protocol.NavigationBarItem[] {
+ var file = ts.normalizePath(fileName);
+ var project = this.projectService.getProjectForFile(file);
+ if (!project) {
+ throw Errors.NoProject;
+ }
+
+ var compilerService = project.compilerService;
+ var items = compilerService.languageService.getNavigationBarItems(file);
+ if (!items) {
+ throw Errors.NoContent;
+ }
+
+ return this.decorateNavigationBarItem(project, fileName, items);
+ }
+
+ getNavigateToItems(searchTerm: string, fileName: string): protocol.NavtoItem[] {
+ var file = ts.normalizePath(fileName);
+ var project = this.projectService.getProjectForFile(file);
+ if (!project) {
+ throw Errors.NoProject;
+ }
+
+ var compilerService = project.compilerService;
+ var navItems = sortNavItems(compilerService.languageService.getNavigateToItems(searchTerm));
+ if (!navItems) {
+ throw Errors.NoContent;
+ }
+
+ return navItems.map((navItem) => {
+ var start = compilerService.host.positionToLineCol(navItem.fileName, navItem.textSpan.start);
+ var end = compilerService.host.positionToLineCol(navItem.fileName, ts.textSpanEnd(navItem.textSpan));
+ var bakedItem: protocol.NavtoItem = {
+ name: navItem.name,
+ kind: navItem.kind,
+ file: navItem.fileName,
+ start: start,
+ end: end,
+ };
+ if (navItem.kindModifiers && (navItem.kindModifiers != "")) {
+ bakedItem.kindModifiers = navItem.kindModifiers;
+ }
+ if (navItem.matchKind != 'none') {
+ bakedItem.matchKind = navItem.matchKind;
+ }
+ if (navItem.containerName && (navItem.containerName.length > 0)) {
+ bakedItem.containerName = navItem.containerName;
+ }
+ if (navItem.containerKind && (navItem.containerKind.length > 0)) {
+ bakedItem.containerKind = navItem.containerKind;
+ }
+ return bakedItem;
+ });
+ }
+
+ getBraceMatching(line: number, col: number, fileName: string): protocol.TextSpan[] {
+ var file = ts.normalizePath(fileName);
+
+ var project = this.projectService.getProjectForFile(file);
+ if (!project) {
+ throw Errors.NoProject;
+ }
+
+ var compilerService = project.compilerService;
+ var position = compilerService.host.lineColToPosition(file, line, col);
+
+ var spans = compilerService.languageService.getBraceMatchingAtPosition(file, position);
+ if (!spans) {
+ throw Errors.NoContent;
+ }
+
+ return spans.map(span => ({
+ start: compilerService.host.positionToLineCol(file, span.start),
+ end: compilerService.host.positionToLineCol(file, span.start + span.length)
+ }));
+ }
+
+ onMessage(message: string) {
+ try {
+ var request = JSON.parse(message);
+ var response: any;
+ switch (request.command) {
+ case CommandNames.Definition: {
+ var defArgs = request.arguments;
+ response = this.getDefinition(defArgs.line, defArgs.col, defArgs.file);
+ break;
+ }
+ case CommandNames.References: {
+ var refArgs = request.arguments;
+ response = this.getReferences(refArgs.line, refArgs.col, refArgs.file);
+ break;
+ }
+ case CommandNames.Rename: {
+ var renameArgs = request.arguments;
+ response = this.getRenameLocations(renameArgs.line, renameArgs.col, renameArgs.file, renameArgs.findInComments, renameArgs.findInStrings);
+ break;
+ }
+ case CommandNames.Open: {
+ var openArgs = request.arguments;
+ this.openClientFile(openArgs.file);
+ break;
+ }
+ case CommandNames.Quickinfo: {
+ var quickinfoArgs = request.arguments;
+ response = this.getQuickInfo(quickinfoArgs.line, quickinfoArgs.col, quickinfoArgs.file);
+ break;
+ }
+ case CommandNames.Format: {
+ var formatArgs = request.arguments;
+ response = this.getFormattingEditsForRange(formatArgs.line, formatArgs.col, formatArgs.endLine, formatArgs.endCol, formatArgs.file);
+ break;
+ }
+ case CommandNames.Formatonkey: {
+ var formatOnKeyArgs = request.arguments;
+ response = this.getFormattingEditsAfterKeystroke(formatOnKeyArgs.line, formatOnKeyArgs.col, formatOnKeyArgs.key, formatOnKeyArgs.file);
+ break;
+ }
+ case CommandNames.Completions: {
+ var completionsArgs = request.arguments;
+ response = this.getCompletions(request.arguments.line, request.arguments.col, completionsArgs.prefix, request.arguments.file);
+ break;
+ }
+ case CommandNames.CompletionDetails: {
+ var completionDetailsArgs = request.arguments;
+ response = this.getCompletionEntryDetails(request.arguments.line, request.arguments.col, completionDetailsArgs.entryNames,
+ request.arguments.file);
+ break;
+ }
+ case CommandNames.Geterr: {
+ var geterrArgs = request.arguments;
+ response = this.getDiagnostics(geterrArgs.delay, geterrArgs.files);
+ break;
+ }
+ case CommandNames.Change: {
+ var changeArgs = request.arguments;
+ this.change(changeArgs.line, changeArgs.col, changeArgs.endLine, changeArgs.endCol,
+ changeArgs.insertString, changeArgs.file);
+ break;
+ }
+ case CommandNames.Reload: {
+ var reloadArgs = request.arguments;
+ this.reload(reloadArgs.file, reloadArgs.tmpfile, request.seq);
+ break;
+ }
+ case CommandNames.Saveto: {
+ var savetoArgs = request.arguments;
+ this.saveToTmp(savetoArgs.file, savetoArgs.tmpfile);
+ break;
+ }
+ case CommandNames.Close: {
+ var closeArgs = request.arguments;
+ this.closeClientFile(closeArgs.file);
+ break;
+ }
+ case CommandNames.Navto: {
+ var navtoArgs = request.arguments;
+ response = this.getNavigateToItems(navtoArgs.searchTerm, navtoArgs.file);
+ break;
+ }
+ case CommandNames.Brace: {
+ var braceArguments = request.arguments;
+ response = this.getBraceMatching(braceArguments.line, braceArguments.col, braceArguments.file);
+ break;
+ }
+ case CommandNames.NavBar: {
+ var navBarArgs = request.arguments;
+ response = this.getNavigationBarItems(navBarArgs.file);
+ break;
+ }
+ default: {
+ this.projectService.log("Unrecognized JSON command: " + message);
+ this.output(undefined, CommandNames.Unknown, request.seq, "Unrecognized JSON command: " + request.command);
+ break;
+ }
+ }
+
+ if (response) {
+ this.output(response, request.command, request.seq);
+ }
+
+ } catch (err) {
+ if (err instanceof OperationCanceledException) {
+ // Handle cancellation exceptions
+ }
+ this.logError(err, message);
+ this.output(undefined, request ? request.command : CommandNames.Unknown, request ? request.seq : 0, "Error processing request. " + err.message);
+ }
+ }
+ }
+}
diff --git a/src/services/services.ts b/src/services/services.ts
index 56e68b2f5a138..05884b9bb987f 100644
--- a/src/services/services.ts
+++ b/src/services/services.ts
@@ -991,6 +991,7 @@ module ts {
InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: boolean;
PlaceOpenBraceOnNewLineForFunctions: boolean;
PlaceOpenBraceOnNewLineForControlBlocks: boolean;
+ [s: string]: boolean | number| string;
}
export interface DefinitionInfo {
diff --git a/tests/baselines/reference/APISample_compile.js b/tests/baselines/reference/APISample_compile.js
index 7d89d88b5b321..1b7ca0c37d2ff 100644
--- a/tests/baselines/reference/APISample_compile.js
+++ b/tests/baselines/reference/APISample_compile.js
@@ -1605,6 +1605,7 @@ declare module "typescript" {
InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: boolean;
PlaceOpenBraceOnNewLineForFunctions: boolean;
PlaceOpenBraceOnNewLineForControlBlocks: boolean;
+ [s: string]: boolean | number | string;
}
interface DefinitionInfo {
fileName: string;
diff --git a/tests/baselines/reference/APISample_compile.types b/tests/baselines/reference/APISample_compile.types
index 8cf789bf20fd0..98063887df465 100644
--- a/tests/baselines/reference/APISample_compile.types
+++ b/tests/baselines/reference/APISample_compile.types
@@ -5195,6 +5195,9 @@ declare module "typescript" {
PlaceOpenBraceOnNewLineForControlBlocks: boolean;
>PlaceOpenBraceOnNewLineForControlBlocks : boolean
+
+ [s: string]: boolean | number | string;
+>s : string
}
interface DefinitionInfo {
>DefinitionInfo : DefinitionInfo
diff --git a/tests/baselines/reference/APISample_linter.js b/tests/baselines/reference/APISample_linter.js
index 0ce5a27796836..ebb2f7503d87e 100644
--- a/tests/baselines/reference/APISample_linter.js
+++ b/tests/baselines/reference/APISample_linter.js
@@ -1636,6 +1636,7 @@ declare module "typescript" {
InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: boolean;
PlaceOpenBraceOnNewLineForFunctions: boolean;
PlaceOpenBraceOnNewLineForControlBlocks: boolean;
+ [s: string]: boolean | number | string;
}
interface DefinitionInfo {
fileName: string;
diff --git a/tests/baselines/reference/APISample_linter.types b/tests/baselines/reference/APISample_linter.types
index 115a0f5a16b6e..4d92af6dd2ea2 100644
--- a/tests/baselines/reference/APISample_linter.types
+++ b/tests/baselines/reference/APISample_linter.types
@@ -5339,6 +5339,9 @@ declare module "typescript" {
PlaceOpenBraceOnNewLineForControlBlocks: boolean;
>PlaceOpenBraceOnNewLineForControlBlocks : boolean
+
+ [s: string]: boolean | number | string;
+>s : string
}
interface DefinitionInfo {
>DefinitionInfo : DefinitionInfo
diff --git a/tests/baselines/reference/APISample_transform.js b/tests/baselines/reference/APISample_transform.js
index 6ecf4e7c395f8..315b1a97eac20 100644
--- a/tests/baselines/reference/APISample_transform.js
+++ b/tests/baselines/reference/APISample_transform.js
@@ -1637,6 +1637,7 @@ declare module "typescript" {
InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: boolean;
PlaceOpenBraceOnNewLineForFunctions: boolean;
PlaceOpenBraceOnNewLineForControlBlocks: boolean;
+ [s: string]: boolean | number | string;
}
interface DefinitionInfo {
fileName: string;
diff --git a/tests/baselines/reference/APISample_transform.types b/tests/baselines/reference/APISample_transform.types
index e2797fe526744..bad0f064116c2 100644
--- a/tests/baselines/reference/APISample_transform.types
+++ b/tests/baselines/reference/APISample_transform.types
@@ -5291,6 +5291,9 @@ declare module "typescript" {
PlaceOpenBraceOnNewLineForControlBlocks: boolean;
>PlaceOpenBraceOnNewLineForControlBlocks : boolean
+
+ [s: string]: boolean | number | string;
+>s : string
}
interface DefinitionInfo {
>DefinitionInfo : DefinitionInfo
diff --git a/tests/baselines/reference/APISample_watcher.js b/tests/baselines/reference/APISample_watcher.js
index eb141f20672a4..543677016a569 100644
--- a/tests/baselines/reference/APISample_watcher.js
+++ b/tests/baselines/reference/APISample_watcher.js
@@ -1674,6 +1674,7 @@ declare module "typescript" {
InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: boolean;
PlaceOpenBraceOnNewLineForFunctions: boolean;
PlaceOpenBraceOnNewLineForControlBlocks: boolean;
+ [s: string]: boolean | number | string;
}
interface DefinitionInfo {
fileName: string;
diff --git a/tests/baselines/reference/APISample_watcher.types b/tests/baselines/reference/APISample_watcher.types
index 692e07f317a0a..7889e492db6d1 100644
--- a/tests/baselines/reference/APISample_watcher.types
+++ b/tests/baselines/reference/APISample_watcher.types
@@ -5464,6 +5464,9 @@ declare module "typescript" {
PlaceOpenBraceOnNewLineForControlBlocks: boolean;
>PlaceOpenBraceOnNewLineForControlBlocks : boolean
+
+ [s: string]: boolean | number | string;
+>s : string
}
interface DefinitionInfo {
>DefinitionInfo : DefinitionInfo
diff --git a/tests/cases/fourslash/definition.ts b/tests/cases/fourslash/definition.ts
new file mode 100644
index 0000000000000..531e45628433d
--- /dev/null
+++ b/tests/cases/fourslash/definition.ts
@@ -0,0 +1,12 @@
+///
+
+// @Filename: b.ts
+////import n = require('a/*1*/');
+////var x = new n.Foo();
+
+// @Filename: a.ts
+//// /*2*/export class Foo {}
+
+goTo.marker('1');
+goTo.definition();
+verify.caretAtMarker('2');
\ No newline at end of file
diff --git a/tests/cases/fourslash/server/brace.ts b/tests/cases/fourslash/server/brace.ts
new file mode 100644
index 0000000000000..fc8a71197db11
--- /dev/null
+++ b/tests/cases/fourslash/server/brace.ts
@@ -0,0 +1,43 @@
+///
+
+//////curly braces
+////module Foo [|{
+//// class Bar [|{
+//// private f() [|{
+//// }|]
+////
+//// private f2() [|{
+//// if (true) [|{ }|] [|{ }|];
+//// }|]
+//// }|]
+////}|]
+////
+//////parenthesis
+////class FooBar {
+//// private f[|()|] {
+//// return [|([|(1 + 1)|])|];
+//// }
+////
+//// private f2[|()|] {
+//// if [|(true)|] { }
+//// }
+////}
+////
+//////square brackets
+////class Baz {
+//// private f() {
+//// var a: any[|[]|] = [|[[|[1, 2]|], [|[3, 4]|], 5]|];
+//// }
+////}
+////
+////// angular brackets
+////class TemplateTest [||] {
+//// public foo(a, b) {
+//// return [||] a;
+//// }
+////}
+
+test.ranges().forEach((range) => {
+ verify.matchingBracePositionInCurrentFile(range.start, range.end - 1);
+ verify.matchingBracePositionInCurrentFile(range.end - 1, range.start);
+});
\ No newline at end of file
diff --git a/tests/cases/fourslash/server/completions.ts b/tests/cases/fourslash/server/completions.ts
new file mode 100644
index 0000000000000..82d3b5b2400cd
--- /dev/null
+++ b/tests/cases/fourslash/server/completions.ts
@@ -0,0 +1,17 @@
+///
+
+////var x: string[] = [];
+////x.forEach(function (y) { y/*1*/
+////x.forEach(y => y/*2*/
+
+goTo.marker('1');
+edit.insert('.');
+verify.memberListContains('trim');
+verify.memberListCount(20);
+edit.insert('});'); // need the following lines to not have parse errors in order for completion list to appear
+
+goTo.marker('2');
+edit.insert('.');
+verify.memberListContains('trim');
+verify.memberListCount(20);
+
\ No newline at end of file
diff --git a/tests/cases/fourslash/server/completions2.ts b/tests/cases/fourslash/server/completions2.ts
new file mode 100644
index 0000000000000..b34aad66ba264
--- /dev/null
+++ b/tests/cases/fourslash/server/completions2.ts
@@ -0,0 +1,18 @@
+///
+
+////class Foo {
+////}
+////module Foo {
+//// export var x: number;
+////}
+////Foo./**/
+
+goTo.marker("");
+verify.completionListContains("x");
+
+// Make an edit
+edit.insert("a");
+edit.backspace();
+
+// Checking for completion details after edit should work too
+verify.completionEntryDetailIs("x", "(var) Foo.x: number");
diff --git a/tests/cases/fourslash/server/definition.ts b/tests/cases/fourslash/server/definition.ts
new file mode 100644
index 0000000000000..531e45628433d
--- /dev/null
+++ b/tests/cases/fourslash/server/definition.ts
@@ -0,0 +1,12 @@
+///
+
+// @Filename: b.ts
+////import n = require('a/*1*/');
+////var x = new n.Foo();
+
+// @Filename: a.ts
+//// /*2*/export class Foo {}
+
+goTo.marker('1');
+goTo.definition();
+verify.caretAtMarker('2');
\ No newline at end of file
diff --git a/tests/cases/fourslash/server/format.ts b/tests/cases/fourslash/server/format.ts
new file mode 100644
index 0000000000000..75d06eef0c2e4
--- /dev/null
+++ b/tests/cases/fourslash/server/format.ts
@@ -0,0 +1,8 @@
+///
+
+/////**/module Default{var x= ( { } ) ;}
+
+
+format.document();
+goTo.marker();
+verify.currentLineContentIs('module Default { var x = ({}); }');
\ No newline at end of file
diff --git a/tests/cases/fourslash/server/formatonkey.ts b/tests/cases/fourslash/server/formatonkey.ts
new file mode 100644
index 0000000000000..64cd74ca8580b
--- /dev/null
+++ b/tests/cases/fourslash/server/formatonkey.ts
@@ -0,0 +1,12 @@
+///
+
+////switch (1) {
+//// case 1:
+//// {
+//// /*1*/
+//// break;
+////}
+
+goTo.marker("1");
+edit.insert("}");
+verify.currentLineContentIs(" }");
diff --git a/tests/cases/fourslash/server/navbar.ts b/tests/cases/fourslash/server/navbar.ts
new file mode 100644
index 0000000000000..e18f49c1db9dc
--- /dev/null
+++ b/tests/cases/fourslash/server/navbar.ts
@@ -0,0 +1,52 @@
+///
+
+////// Interface
+////{| "itemName": "IPoint", "kind": "interface", "parentName": "" |}interface IPoint {
+//// {| "itemName": "getDist", "kind": "method", "parentName": "IPoint" |}getDist(): number;
+//// {| "itemName": "new()", "kind": "construct", "parentName": "IPoint" |}new(): IPoint;
+//// {| "itemName": "()", "kind": "call", "parentName": "IPoint" |}(): any;
+//// {| "itemName": "[]", "kind": "index", "parentName": "IPoint" |}[x:string]: number;
+//// {| "itemName": "prop", "kind": "property", "parentName": "IPoint" |}prop: string;
+////}
+////
+/////// Module
+////{| "itemName": "Shapes", "kind": "module", "parentName": "" |}module Shapes {
+////
+//// // Class
+//// {| "itemName": "Point", "kind": "class", "parentName": "Shapes" |}export class Point implements IPoint {
+//// {| "itemName": "constructor", "kind": "constructor", "parentName": "Shapes.Point" |}constructor (public x: number, public y: number) { }
+////
+//// // Instance member
+//// {| "itemName": "getDist", "kind": "method", "parentName": "Shapes.Point" |}getDist() { return Math.sqrt(this.x * this.x + this.y * this.y); }
+////
+//// // Getter
+//// {| "itemName": "value", "kind": "getter", "parentName": "Shapes.Point" |}get value(): number { return 0; }
+////
+//// // Setter
+//// {| "itemName": "value", "kind": "setter", "parentName": "Shapes.Point" |}set value(newValue: number) { return; }
+////
+//// // Static member
+//// {| "itemName": "origin", "kind": "property", "parentName": "Shapes.Point" |}static origin = new Point(0, 0);
+////
+//// // Static method
+//// {| "itemName": "getOrigin", "kind": "method", "parentName": "Shapes.Point" |}private static getOrigin() { return Point.origin;}
+//// }
+////
+//// {| "itemName": "Values", "kind": "enum", "parentName": "Shapes" |}enum Values {
+//// value1,
+//// {| "itemName": "value2", "kind": "property", "parentName": "Shapes.Values" |}value2,
+//// value3,
+//// }
+////}
+////
+////// Local variables
+////{| "itemName": "p", "kind": "var", "parentName": "" |}var p: IPoint = new Shapes.Point(3, 4);
+////{| "itemName": "dist", "kind": "var", "parentName": "" |}var dist = p.getDist();
+
+test.markers().forEach((marker) => {
+ if (marker.data) {
+ verify.getScriptLexicalStructureListContains(marker.data.itemName, marker.data.kind, marker.fileName, marker.data.parentName);
+ }
+});
+
+verify.getScriptLexicalStructureListCount(23);
diff --git a/tests/cases/fourslash/server/navto.ts b/tests/cases/fourslash/server/navto.ts
new file mode 100644
index 0000000000000..8dc42a2365e36
--- /dev/null
+++ b/tests/cases/fourslash/server/navto.ts
@@ -0,0 +1,28 @@
+///
+
+/////// Module
+////{| "itemName": "Shapes", "kind": "module", "parentName": "", "matchKind": "substring" |}module Shapes {
+////
+//// // Class
+//// {| "itemName": "Point", "kind": "class", "parentName": "Shapes", "matchKind": "substring" |}export class Point {
+//// // Instance member
+//// {| "itemName": "originPointAttheHorizon", "kind": "property", "parentName": "Point", "matchKind": "substring"|}private originPointAttheHorizon = 0.0;
+////
+//// // Getter
+//// {| "itemName": "distanceFromOrigin", "kind": "getter", "parentName": "Point", "matchKind": "substring" |}get distanceFromOrigin(): number { return 0; }
+////
+//// }
+////}
+////
+////// Local variables
+////{| "itemName": "myPointThatIJustInitiated", "kind": "var", "parentName": "", "matchKind": "substring"|}var myPointThatIJustInitiated = new Shapes.Point();
+
+//// Testing for substring matching of navigationItems
+//var searchValue = "FromOrigin horizon INITIATED Shape Point";
+
+test.markers().forEach((marker) => {
+ if (marker.data) {
+ var name = marker.data.itemName;
+ verify.navigationItemsListContains(name, marker.data.kind, name.substr(1), marker.data.matchKind, marker.fileName, marker.data.parentName);
+ }
+});
\ No newline at end of file
diff --git a/tests/cases/fourslash/server/quickinfo.ts b/tests/cases/fourslash/server/quickinfo.ts
new file mode 100644
index 0000000000000..b5c7dc0a01c0f
--- /dev/null
+++ b/tests/cases/fourslash/server/quickinfo.ts
@@ -0,0 +1,27 @@
+///
+
+////interface One {
+//// commonProperty: number;
+//// commonFunction(): number;
+////}
+////
+////interface Two {
+//// commonProperty: string
+//// commonFunction(): number;
+////}
+////
+////var /*1*/x : One | Two;
+////
+////x./*2*/commonProperty;
+////x./*3*/commonFunction;
+
+
+goTo.marker("1");
+verify.quickInfoIs('(var) x: One | Two');
+
+
+goTo.marker("2");
+verify.quickInfoIs('(property) commonProperty: string | number');
+
+goTo.marker("3");
+verify.quickInfoIs('(method) commonFunction(): number');
diff --git a/tests/cases/fourslash/server/references.ts b/tests/cases/fourslash/server/references.ts
new file mode 100644
index 0000000000000..55b2161555139
--- /dev/null
+++ b/tests/cases/fourslash/server/references.ts
@@ -0,0 +1,18 @@
+///
+
+// Global class reference.
+
+// @Filename: referencesForGlobals_1.ts
+////class /*2*/globalClass {
+//// public f() { }
+////}
+
+// @Filename: referencesForGlobals_2.ts
+///////
+////var c = /*1*/globalClass();
+
+goTo.marker("1");
+verify.referencesCountIs(2);
+
+goTo.marker("2");
+verify.referencesCountIs(2);
\ No newline at end of file
diff --git a/tests/cases/fourslash/server/rename.ts b/tests/cases/fourslash/server/rename.ts
new file mode 100644
index 0000000000000..4f8b7b98cd49f
--- /dev/null
+++ b/tests/cases/fourslash/server/rename.ts
@@ -0,0 +1,11 @@
+///
+
+///////
+
+////function /**/[|Bar|]() {
+//// // This is a reference to [|Bar|] in a comment.
+//// "this is a reference to [|Bar|] in a string"
+////}
+
+goTo.marker();
+verify.renameLocations(/*findInStrings:*/ true, /*findInComments:*/ true);
\ No newline at end of file