mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-15 21:36:50 -05:00
Merge branch 'master' into glob2
Conflicts: Jakefile.js src/compiler/commandLineParser.ts src/compiler/core.ts src/compiler/sys.ts src/harness/harness.ts src/server/editorServices.ts src/services/shims.ts tests/cases/unittests/cachingInServerLSHost.ts
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/// <reference path="session.ts" />
|
||||
|
||||
|
||||
namespace ts.server {
|
||||
|
||||
export interface SessionClientHost extends LanguageServiceHost {
|
||||
@@ -25,23 +25,23 @@ namespace ts.server {
|
||||
private lineMaps: ts.Map<number[]> = {};
|
||||
private messages: string[] = [];
|
||||
private lastRenameEntry: RenameEntry;
|
||||
|
||||
|
||||
constructor(private host: SessionClientHost) {
|
||||
}
|
||||
|
||||
public onMessage(message: string): void {
|
||||
public onMessage(message: string): void {
|
||||
this.messages.push(message);
|
||||
}
|
||||
|
||||
private writeMessage(message: string): void {
|
||||
private writeMessage(message: string): void {
|
||||
this.host.writeMessage(message);
|
||||
}
|
||||
|
||||
private getLineMap(fileName: string): number[] {
|
||||
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()));
|
||||
lineMap = this.lineMaps[fileName] = ts.computeLineStarts(scriptSnapshot.getText(0, scriptSnapshot.getLength()));
|
||||
}
|
||||
return lineMap;
|
||||
}
|
||||
@@ -82,34 +82,29 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
private processResponse<T extends protocol.Response>(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 details: " + e.message);
|
||||
let foundResponseMessage = false;
|
||||
let lastMessage: string;
|
||||
let response: T;
|
||||
while (!foundResponseMessage) {
|
||||
lastMessage = this.messages.shift();
|
||||
Debug.assert(!!lastMessage, "Did not receive any responses.");
|
||||
const responseBody = processMessage(lastMessage);
|
||||
try {
|
||||
response = JSON.parse(responseBody);
|
||||
// the server may emit events before emitting the response. We
|
||||
// want to ignore these events for testing purpose.
|
||||
if (response.type === "response") {
|
||||
foundResponseMessage = true;
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
throw new Error("Malformed response: Failed to parse server response: " + lastMessage + ". \r\n Error details: " + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
// verify the sequence numbers
|
||||
Debug.assert(response.request_seq === request.seq, "Malformed response: response sequance number did not match request sequence number.");
|
||||
|
||||
Debug.assert(response.request_seq === request.seq, "Malformed response: response sequence number did not match request sequence number.");
|
||||
|
||||
// unmarshal errors
|
||||
if (!response.success) {
|
||||
throw new Error("Error " + response.message);
|
||||
@@ -118,10 +113,28 @@ namespace ts.server {
|
||||
Debug.assert(!!response.body, "Malformed response: Unexpected empty response body.");
|
||||
|
||||
return response;
|
||||
|
||||
function processMessage(message: string) {
|
||||
// Read the content length
|
||||
const contentLengthPrefix = "Content-Length: ";
|
||||
const lines = message.split("\r\n");
|
||||
Debug.assert(lines.length >= 2, "Malformed response: Expected 3 lines in the response.");
|
||||
|
||||
const contentLengthText = lines[0];
|
||||
Debug.assert(contentLengthText.indexOf(contentLengthPrefix) === 0, "Malformed response: Response text did not contain content-length header.");
|
||||
const contentLength = parseInt(contentLengthText.substring(contentLengthPrefix.length));
|
||||
|
||||
// Read the body
|
||||
const responseBody = lines[2];
|
||||
|
||||
// Verify content length
|
||||
Debug.assert(responseBody.length + 1 === contentLength, "Malformed response: Content length did not match the response's body length.");
|
||||
return responseBody;
|
||||
}
|
||||
}
|
||||
|
||||
openFile(fileName: string, content?: string): void {
|
||||
var args: protocol.OpenRequestArgs = { file: fileName, fileContent: content };
|
||||
openFile(fileName: string, content?: string, scriptKindName?: "TS" | "JS" | "TSX" | "JSX"): void {
|
||||
var args: protocol.OpenRequestArgs = { file: fileName, fileContent: content, scriptKindName };
|
||||
this.processRequest(CommandNames.Open, args);
|
||||
}
|
||||
|
||||
@@ -186,7 +199,7 @@ namespace ts.server {
|
||||
fileNames: response.body.fileNames
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
getCompletionsAtPosition(fileName: string, position: number): CompletionInfo {
|
||||
var lineOffset = this.positionToOneBasedLineOffset(fileName, position);
|
||||
var args: protocol.CompletionsRequestArgs = {
|
||||
@@ -199,13 +212,13 @@ namespace ts.server {
|
||||
var request = this.processRequest<protocol.CompletionsRequest>(CommandNames.Completions, args);
|
||||
var response = this.processResponse<protocol.CompletionsResponse>(request);
|
||||
|
||||
return {
|
||||
return {
|
||||
isMemberCompletion: false,
|
||||
isNewIdentifierLocation: false,
|
||||
entries: response.body
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
getCompletionEntryDetails(fileName: string, position: number, entryName: string): CompletionEntryDetails {
|
||||
var lineOffset = this.positionToOneBasedLineOffset(fileName, position);
|
||||
var args: protocol.CompletionDetailsRequestArgs = {
|
||||
@@ -234,7 +247,7 @@ namespace ts.server {
|
||||
var fileName = entry.file;
|
||||
var start = this.lineOffsetToPosition(fileName, entry.start);
|
||||
var end = this.lineOffsetToPosition(fileName, entry.end);
|
||||
|
||||
|
||||
return {
|
||||
name: entry.name,
|
||||
containerName: entry.containerName || "",
|
||||
@@ -264,7 +277,7 @@ namespace ts.server {
|
||||
var request = this.processRequest<protocol.FormatRequest>(CommandNames.Format, args);
|
||||
var response = this.processResponse<protocol.FormatResponse>(request);
|
||||
|
||||
return response.body.map(entry=> this.convertCodeEditsToTextChange(fileName, entry));
|
||||
return response.body.map(entry => this.convertCodeEditsToTextChange(fileName, entry));
|
||||
}
|
||||
|
||||
getFormattingEditsForDocument(fileName: string, options: ts.FormatCodeOptions): ts.TextChange[] {
|
||||
@@ -284,7 +297,7 @@ namespace ts.server {
|
||||
var request = this.processRequest<protocol.FormatOnKeyRequest>(CommandNames.Formatonkey, args);
|
||||
var response = this.processResponse<protocol.FormatResponse>(request);
|
||||
|
||||
return response.body.map(entry=> this.convertCodeEditsToTextChange(fileName, entry));
|
||||
return response.body.map(entry => this.convertCodeEditsToTextChange(fileName, entry));
|
||||
}
|
||||
|
||||
getDefinitionAtPosition(fileName: string, position: number): DefinitionInfo[] {
|
||||
@@ -339,7 +352,7 @@ namespace ts.server {
|
||||
});
|
||||
}
|
||||
|
||||
findReferences(fileName: string, position: number): ReferencedSymbol[]{
|
||||
findReferences(fileName: string, position: number): ReferencedSymbol[] {
|
||||
// Not yet implemented.
|
||||
return [];
|
||||
}
|
||||
@@ -444,7 +457,7 @@ namespace ts.server {
|
||||
text: item.text,
|
||||
kind: item.kind,
|
||||
kindModifiers: item.kindModifiers || "",
|
||||
spans: item.spans.map(span=> createTextSpanFromBounds(this.lineOffsetToPosition(fileName, span.start), this.lineOffsetToPosition(fileName, span.end))),
|
||||
spans: item.spans.map(span => createTextSpanFromBounds(this.lineOffsetToPosition(fileName, span.start), this.lineOffsetToPosition(fileName, span.end))),
|
||||
childItems: this.decodeNavigationBarItems(item.childItems, fileName),
|
||||
indent: 0,
|
||||
bolded: false,
|
||||
@@ -478,10 +491,10 @@ namespace ts.server {
|
||||
line: lineOffset.line,
|
||||
offset: lineOffset.offset
|
||||
};
|
||||
|
||||
|
||||
var request = this.processRequest<protocol.SignatureHelpRequest>(CommandNames.SignatureHelp, args);
|
||||
var response = this.processResponse<protocol.SignatureHelpResponse>(request);
|
||||
|
||||
|
||||
if (!response.body) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -490,7 +503,7 @@ namespace ts.server {
|
||||
var span = helpItems.applicableSpan;
|
||||
var start = this.lineOffsetToPosition(fileName, span.start);
|
||||
var end = this.lineOffsetToPosition(fileName, span.end);
|
||||
|
||||
|
||||
var result: SignatureHelpItems = {
|
||||
items: helpItems.items,
|
||||
applicableSpan: {
|
||||
@@ -499,7 +512,7 @@ namespace ts.server {
|
||||
},
|
||||
selectedItemIndex: helpItems.selectedItemIndex,
|
||||
argumentIndex: helpItems.argumentIndex,
|
||||
argumentCount: helpItems.argumentCount,
|
||||
argumentCount: helpItems.argumentCount,
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -561,11 +574,15 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
getTodoComments(fileName: string, descriptors: TodoCommentDescriptor[]): TodoComment[] {
|
||||
throw new Error("Not Implemented Yet.");
|
||||
throw new Error("Not Implemented Yet.");
|
||||
}
|
||||
|
||||
|
||||
getDocCommentTemplateAtPosition(fileName: string, position: number): TextInsertion {
|
||||
throw new Error("Not Implemented Yet.");
|
||||
throw new Error("Not Implemented Yet.");
|
||||
}
|
||||
|
||||
isValidBraceCompletionAtPostion(fileName: string, position: number, openingBrace: number): boolean {
|
||||
throw new Error("Not Implemented Yet.");
|
||||
}
|
||||
|
||||
getBraceMatchingAtPosition(fileName: string, position: number): TextSpan[] {
|
||||
@@ -613,7 +630,7 @@ namespace ts.server {
|
||||
throw new Error("SourceFile objects are not serializable through the server protocol.");
|
||||
}
|
||||
|
||||
getSourceFile(fileName: string): SourceFile {
|
||||
getNonBoundSourceFile(fileName: string): SourceFile {
|
||||
throw new Error("SourceFile objects are not serializable through the server protocol.");
|
||||
}
|
||||
|
||||
|
||||
@@ -32,8 +32,9 @@ namespace ts.server {
|
||||
children: ScriptInfo[] = []; // files referenced by this file
|
||||
defaultProject: Project; // project to use by default for file
|
||||
fileWatcher: FileWatcher;
|
||||
formatCodeOptions = ts.clone(CompilerService.defaultFormatCodeOptions);
|
||||
formatCodeOptions = ts.clone(CompilerService.getDefaultFormatCodeOptions(this.host));
|
||||
path: Path;
|
||||
scriptKind: ScriptKind;
|
||||
|
||||
constructor(private host: ServerHost, public fileName: string, public content: string, public isOpen = false) {
|
||||
this.path = toPath(fileName, host.getCurrentDirectory(), createGetCanonicalFileName(host.useCaseSensitiveFileNames));
|
||||
@@ -81,8 +82,14 @@ namespace ts.server {
|
||||
}
|
||||
}
|
||||
|
||||
interface TimestampedResolvedModule extends ResolvedModuleWithFailedLookupLocations {
|
||||
lastCheckTime: number;
|
||||
interface Timestamped {
|
||||
lastCheckTime?: number;
|
||||
}
|
||||
|
||||
interface TimestampedResolvedModule extends ResolvedModuleWithFailedLookupLocations, Timestamped {
|
||||
}
|
||||
|
||||
interface TimestampedResolvedTypeReferenceDirective extends ResolvedTypeReferenceDirectiveWithFailedLookupLocations, Timestamped {
|
||||
}
|
||||
|
||||
export class LSHost implements ts.LanguageServiceHost {
|
||||
@@ -90,60 +97,69 @@ namespace ts.server {
|
||||
compilationSettings: ts.CompilerOptions;
|
||||
filenameToScript: ts.FileMap<ScriptInfo>;
|
||||
roots: ScriptInfo[] = [];
|
||||
|
||||
private resolvedModuleNames: ts.FileMap<Map<TimestampedResolvedModule>>;
|
||||
private resolvedTypeReferenceDirectives: ts.FileMap<Map<TimestampedResolvedTypeReferenceDirective>>;
|
||||
private moduleResolutionHost: ts.ModuleResolutionHost;
|
||||
private getCanonicalFileName: (fileName: string) => string;
|
||||
|
||||
constructor(public host: ServerHost, public project: Project) {
|
||||
this.getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames);
|
||||
this.resolvedModuleNames = createFileMap<Map<TimestampedResolvedModule>>();
|
||||
this.resolvedTypeReferenceDirectives = createFileMap<Map<TimestampedResolvedTypeReferenceDirective>>();
|
||||
this.filenameToScript = createFileMap<ScriptInfo>();
|
||||
this.moduleResolutionHost = {
|
||||
fileExists: fileName => this.fileExists(fileName),
|
||||
readFile: fileName => this.host.readFile(fileName)
|
||||
readFile: fileName => this.host.readFile(fileName),
|
||||
directoryExists: directoryName => this.host.directoryExists(directoryName)
|
||||
};
|
||||
}
|
||||
|
||||
resolveModuleNames(moduleNames: string[], containingFile: string): ResolvedModule[] {
|
||||
private resolveNamesWithLocalCache<T extends Timestamped & { failedLookupLocations: string[] }, R>(
|
||||
names: string[],
|
||||
containingFile: string,
|
||||
cache: ts.FileMap<Map<T>>,
|
||||
loader: (name: string, containingFile: string, options: CompilerOptions, host: ModuleResolutionHost) => T,
|
||||
getResult: (s: T) => R): R[] {
|
||||
|
||||
const path = toPath(containingFile, this.host.getCurrentDirectory(), this.getCanonicalFileName);
|
||||
const currentResolutionsInFile = this.resolvedModuleNames.get(path);
|
||||
|
||||
const newResolutions: Map<TimestampedResolvedModule> = {};
|
||||
const resolvedModules: ResolvedModule[] = [];
|
||||
const currentResolutionsInFile = cache.get(path);
|
||||
|
||||
const newResolutions: Map<T> = {};
|
||||
const resolvedModules: R[] = [];
|
||||
const compilerOptions = this.getCompilationSettings();
|
||||
|
||||
for (const moduleName of moduleNames) {
|
||||
for (const name of names) {
|
||||
// check if this is a duplicate entry in the list
|
||||
let resolution = lookUp(newResolutions, moduleName);
|
||||
let resolution = lookUp(newResolutions, name);
|
||||
if (!resolution) {
|
||||
const existingResolution = currentResolutionsInFile && ts.lookUp(currentResolutionsInFile, moduleName);
|
||||
const existingResolution = currentResolutionsInFile && ts.lookUp(currentResolutionsInFile, name);
|
||||
if (moduleResolutionIsValid(existingResolution)) {
|
||||
// ok, it is safe to use existing module resolution results
|
||||
// ok, it is safe to use existing name resolution results
|
||||
resolution = existingResolution;
|
||||
}
|
||||
else {
|
||||
resolution = <TimestampedResolvedModule>resolveModuleName(moduleName, containingFile, compilerOptions, this.moduleResolutionHost);
|
||||
resolution = loader(name, containingFile, compilerOptions, this.moduleResolutionHost);
|
||||
resolution.lastCheckTime = Date.now();
|
||||
newResolutions[moduleName] = resolution;
|
||||
newResolutions[name] = resolution;
|
||||
}
|
||||
}
|
||||
|
||||
ts.Debug.assert(resolution !== undefined);
|
||||
|
||||
resolvedModules.push(resolution.resolvedModule);
|
||||
resolvedModules.push(getResult(resolution));
|
||||
}
|
||||
|
||||
// replace old results with a new one
|
||||
this.resolvedModuleNames.set(path, newResolutions);
|
||||
cache.set(path, newResolutions);
|
||||
return resolvedModules;
|
||||
|
||||
function moduleResolutionIsValid(resolution: TimestampedResolvedModule): boolean {
|
||||
function moduleResolutionIsValid(resolution: T): boolean {
|
||||
if (!resolution) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (resolution.resolvedModule) {
|
||||
if (getResult(resolution)) {
|
||||
// TODO: consider checking failedLookupLocations
|
||||
// TODO: use lastCheckTime to track expiration for module name resolution
|
||||
return true;
|
||||
@@ -155,6 +171,14 @@ namespace ts.server {
|
||||
}
|
||||
}
|
||||
|
||||
resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[] {
|
||||
return this.resolveNamesWithLocalCache(typeDirectiveNames, containingFile, this.resolvedTypeReferenceDirectives, resolveTypeReferenceDirective, m => m.resolvedTypeReferenceDirective);
|
||||
}
|
||||
|
||||
resolveModuleNames(moduleNames: string[], containingFile: string): ResolvedModule[] {
|
||||
return this.resolveNamesWithLocalCache(moduleNames, containingFile, this.resolvedModuleNames, resolveModuleName, m => m.resolvedModule);
|
||||
}
|
||||
|
||||
getDefaultLibFileName() {
|
||||
const nodeModuleBinDir = ts.getDirectoryPath(ts.normalizePath(this.host.getExecutingFilePath()));
|
||||
return ts.combinePaths(nodeModuleBinDir, ts.getDefaultLibFileName(this.compilationSettings));
|
||||
@@ -171,6 +195,7 @@ namespace ts.server {
|
||||
this.compilationSettings = opt;
|
||||
// conservatively assume that changing compiler options might affect module resolution strategy
|
||||
this.resolvedModuleNames.clear();
|
||||
this.resolvedTypeReferenceDirectives.clear();
|
||||
}
|
||||
|
||||
lineAffectsRefs(filename: string, line: number) {
|
||||
@@ -191,6 +216,18 @@ namespace ts.server {
|
||||
return this.roots.map(root => root.fileName);
|
||||
}
|
||||
|
||||
getScriptKind(fileName: string) {
|
||||
const info = this.getScriptInfo(fileName);
|
||||
if (!info) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!info.scriptKind) {
|
||||
info.scriptKind = getScriptKindFromFileName(fileName);
|
||||
}
|
||||
return info.scriptKind;
|
||||
}
|
||||
|
||||
getScriptVersion(filename: string) {
|
||||
return this.getScriptInfo(filename).svc.latestVersion().toString();
|
||||
}
|
||||
@@ -207,6 +244,7 @@ namespace ts.server {
|
||||
if (!info.isOpen) {
|
||||
this.filenameToScript.remove(info.path);
|
||||
this.resolvedModuleNames.remove(info.path);
|
||||
this.resolvedTypeReferenceDirectives.remove(info.path);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,6 +272,7 @@ namespace ts.server {
|
||||
this.filenameToScript.remove(info.path);
|
||||
this.roots = copyListRemovingItem(info, this.roots);
|
||||
this.resolvedModuleNames.remove(info.path);
|
||||
this.resolvedTypeReferenceDirectives.remove(info.path);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -461,6 +500,14 @@ namespace ts.server {
|
||||
return copiedList;
|
||||
}
|
||||
|
||||
/**
|
||||
* This helper funciton processes a list of projects and return the concatenated, sortd and deduplicated output of processing each project.
|
||||
*/
|
||||
export function combineProjectOutput<T>(projects: Project[], action: (project: Project) => T[], comparer?: (a: T, b: T) => number, areEqual?: (a: T, b: T) => boolean) {
|
||||
const result = projects.reduce<T[]>((previous, current) => concatenate(previous, action(current)), []).sort(comparer);
|
||||
return projects.length > 1 ? deduplicate(result, areEqual) : result;
|
||||
}
|
||||
|
||||
export interface ProjectServiceEventHandler {
|
||||
(eventName: string, project: Project, fileName: string): void;
|
||||
}
|
||||
@@ -488,7 +535,7 @@ namespace ts.server {
|
||||
// number becomes 0 for a watcher, then we should close it.
|
||||
directoryWatchersRefCount: ts.Map<number> = {};
|
||||
hostConfiguration: HostConfiguration;
|
||||
timerForDetectingProjectFileListChanges: Map<NodeJS.Timer> = {};
|
||||
timerForDetectingProjectFileListChanges: Map<any> = {};
|
||||
|
||||
constructor(public host: ServerHost, public psLogger: Logger, public eventHandler?: ProjectServiceEventHandler) {
|
||||
// ts.disableIncrementalParsing = true;
|
||||
@@ -497,7 +544,7 @@ namespace ts.server {
|
||||
|
||||
addDefaultHostConfiguration() {
|
||||
this.hostConfiguration = {
|
||||
formatCodeOptions: ts.clone(CompilerService.defaultFormatCodeOptions),
|
||||
formatCodeOptions: ts.clone(CompilerService.getDefaultFormatCodeOptions(this.host)),
|
||||
hostInfo: "Unknown host"
|
||||
};
|
||||
}
|
||||
@@ -548,9 +595,9 @@ namespace ts.server {
|
||||
|
||||
startTimerForDetectingProjectFileListChanges(project: Project) {
|
||||
if (this.timerForDetectingProjectFileListChanges[project.projectFilename]) {
|
||||
clearTimeout(this.timerForDetectingProjectFileListChanges[project.projectFilename]);
|
||||
this.host.clearTimeout(this.timerForDetectingProjectFileListChanges[project.projectFilename]);
|
||||
}
|
||||
this.timerForDetectingProjectFileListChanges[project.projectFilename] = setTimeout(
|
||||
this.timerForDetectingProjectFileListChanges[project.projectFilename] = this.host.setTimeout(
|
||||
() => this.handleProjectFileListChanges(project),
|
||||
250
|
||||
);
|
||||
@@ -876,6 +923,7 @@ namespace ts.server {
|
||||
configuredProject.updateGraph();
|
||||
if (configuredProject.getSourceFile(info)) {
|
||||
info.defaultProject = configuredProject;
|
||||
referencingProjects.push(configuredProject);
|
||||
}
|
||||
}
|
||||
return referencingProjects;
|
||||
@@ -987,7 +1035,7 @@ namespace ts.server {
|
||||
* @param filename is absolute pathname
|
||||
* @param fileContent is a known version of the file content that is more up to date than the one on disk
|
||||
*/
|
||||
openFile(fileName: string, openedByClient: boolean, fileContent?: string) {
|
||||
openFile(fileName: string, openedByClient: boolean, fileContent?: string, scriptKind?: ScriptKind) {
|
||||
fileName = ts.normalizePath(fileName);
|
||||
let info = ts.lookUp(this.filenameToScriptInfo, fileName);
|
||||
if (!info) {
|
||||
@@ -1002,6 +1050,7 @@ namespace ts.server {
|
||||
}
|
||||
if (content !== undefined) {
|
||||
info = new ScriptInfo(this.host, fileName, content, openedByClient);
|
||||
info.scriptKind = scriptKind;
|
||||
info.setFormatOptions(this.getFormatCodeOptions());
|
||||
this.filenameToScriptInfo[fileName] = info;
|
||||
if (!info.isOpen) {
|
||||
@@ -1027,10 +1076,16 @@ namespace ts.server {
|
||||
// the newly opened file.
|
||||
findConfigFile(searchPath: string): string {
|
||||
while (true) {
|
||||
const fileName = ts.combinePaths(searchPath, "tsconfig.json");
|
||||
if (this.host.fileExists(fileName)) {
|
||||
return fileName;
|
||||
const tsconfigFileName = ts.combinePaths(searchPath, "tsconfig.json");
|
||||
if (this.host.fileExists(tsconfigFileName)) {
|
||||
return tsconfigFileName;
|
||||
}
|
||||
|
||||
const jsconfigFileName = ts.combinePaths(searchPath, "jsconfig.json");
|
||||
if (this.host.fileExists(jsconfigFileName)) {
|
||||
return jsconfigFileName;
|
||||
}
|
||||
|
||||
const parentPath = ts.getDirectoryPath(searchPath);
|
||||
if (parentPath === searchPath) {
|
||||
break;
|
||||
@@ -1045,12 +1100,12 @@ namespace ts.server {
|
||||
* @param filename is absolute pathname
|
||||
* @param fileContent is a known version of the file content that is more up to date than the one on disk
|
||||
*/
|
||||
openClientFile(fileName: string, fileContent?: string) {
|
||||
this.openOrUpdateConfiguredProjectForFile(fileName);
|
||||
const info = this.openFile(fileName, /*openedByClient*/ true, fileContent);
|
||||
openClientFile(fileName: string, fileContent?: string, scriptKind?: ScriptKind): { configFileName?: string, configFileErrors?: Diagnostic[] } {
|
||||
const { configFileName, configFileErrors } = this.openOrUpdateConfiguredProjectForFile(fileName);
|
||||
const info = this.openFile(fileName, /*openedByClient*/ true, fileContent, scriptKind);
|
||||
this.addOpenFile(info);
|
||||
this.printProjects();
|
||||
return info;
|
||||
return { configFileName, configFileErrors };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1058,7 +1113,7 @@ namespace ts.server {
|
||||
* we first detect if there is already a configured project created for it: if so, we re-read
|
||||
* the tsconfig file content and update the project; otherwise we create a new one.
|
||||
*/
|
||||
openOrUpdateConfiguredProjectForFile(fileName: string) {
|
||||
openOrUpdateConfiguredProjectForFile(fileName: string): { configFileName?: string, configFileErrors?: Diagnostic[] } {
|
||||
const searchPath = ts.normalizePath(getDirectoryPath(fileName));
|
||||
this.log("Search path: " + searchPath, "Info");
|
||||
const configFileName = this.findConfigFile(searchPath);
|
||||
@@ -1068,11 +1123,16 @@ namespace ts.server {
|
||||
if (!project) {
|
||||
const configResult = this.openConfigFile(configFileName, fileName);
|
||||
if (!configResult.success) {
|
||||
this.log("Error opening config file " + configFileName + " " + configResult.errorMsg);
|
||||
return { configFileName, configFileErrors: configResult.errors };
|
||||
}
|
||||
else {
|
||||
// even if opening config file was successful, it could still
|
||||
// contain errors that were tolerated.
|
||||
this.log("Opened configuration file " + configFileName, "Info");
|
||||
this.configuredProjects.push(configResult.project);
|
||||
if (configResult.errors && configResult.errors.length > 0) {
|
||||
return { configFileName, configFileErrors: configResult.errors };
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -1082,6 +1142,7 @@ namespace ts.server {
|
||||
else {
|
||||
this.log("No config files found.");
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1171,24 +1232,25 @@ namespace ts.server {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
configFileToProjectOptions(configFilename: string): { succeeded: boolean, projectOptions?: ProjectOptions, error?: ProjectOpenResult } {
|
||||
configFileToProjectOptions(configFilename: string): { succeeded: boolean, projectOptions?: ProjectOptions, errors?: Diagnostic[] } {
|
||||
configFilename = ts.normalizePath(configFilename);
|
||||
// file references will be relative to dirPath (or absolute)
|
||||
const dirPath = ts.getDirectoryPath(configFilename);
|
||||
const contents = this.host.readFile(configFilename);
|
||||
const rawConfig: { config?: ProjectOptions; error?: Diagnostic; } = ts.parseConfigFileTextToJson(configFilename, contents);
|
||||
if (rawConfig.error) {
|
||||
return { succeeded: false, error: rawConfig.error };
|
||||
return { succeeded: false, errors: [rawConfig.error] };
|
||||
}
|
||||
else {
|
||||
const parsedCommandLine = ts.parseJsonConfigFileContent(rawConfig.config, this.host, dirPath);
|
||||
const parsedCommandLine = ts.parseJsonConfigFileContent(rawConfig.config, this.host, dirPath, /*existingOptions*/ {}, configFilename);
|
||||
Debug.assert(!!parsedCommandLine.fileNames);
|
||||
|
||||
if (parsedCommandLine.errors && (parsedCommandLine.errors.length > 0)) {
|
||||
return { succeeded: false, error: { errorMsg: "tsconfig option errors" } };
|
||||
return { succeeded: false, errors: parsedCommandLine.errors };
|
||||
}
|
||||
else if (parsedCommandLine.fileNames.length === 0) {
|
||||
return { succeeded: false, error: { errorMsg: "no files found" } };
|
||||
const error = createCompilerDiagnostic(Diagnostics.The_config_file_0_found_doesn_t_contain_any_source_files, configFilename);
|
||||
return { succeeded: false, errors: [error] };
|
||||
}
|
||||
else {
|
||||
const projectOptions: ProjectOptions = {
|
||||
@@ -1202,20 +1264,21 @@ namespace ts.server {
|
||||
|
||||
}
|
||||
|
||||
openConfigFile(configFilename: string, clientFileName?: string): ProjectOpenResult {
|
||||
const { succeeded, projectOptions, error } = this.configFileToProjectOptions(configFilename);
|
||||
openConfigFile(configFilename: string, clientFileName?: string): { success: boolean, project?: Project, errors?: Diagnostic[] } {
|
||||
const { succeeded, projectOptions, errors } = this.configFileToProjectOptions(configFilename);
|
||||
if (!succeeded) {
|
||||
return error;
|
||||
return { success: false, errors };
|
||||
}
|
||||
else {
|
||||
const project = this.createProject(configFilename, projectOptions);
|
||||
let errors: Diagnostic[];
|
||||
for (const rootFilename of projectOptions.files) {
|
||||
if (this.host.fileExists(rootFilename)) {
|
||||
const info = this.openFile(rootFilename, /*openedByClient*/ clientFileName == rootFilename);
|
||||
project.addRoot(info);
|
||||
}
|
||||
else {
|
||||
return { errorMsg: "specified file " + rootFilename + " not found" };
|
||||
(errors || (errors = [])).push(createCompilerDiagnostic(Diagnostics.File_0_not_found, rootFilename));
|
||||
}
|
||||
}
|
||||
project.finishGraph();
|
||||
@@ -1244,7 +1307,7 @@ namespace ts.server {
|
||||
return watchers;
|
||||
}, <Map<FileWatcher>>{});
|
||||
|
||||
return { success: true, project: project };
|
||||
return { success: true, project: project, errors };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1254,13 +1317,13 @@ namespace ts.server {
|
||||
this.removeProject(project);
|
||||
}
|
||||
else {
|
||||
const { succeeded, projectOptions, error } = this.configFileToProjectOptions(project.projectFilename);
|
||||
const { succeeded, projectOptions, errors } = this.configFileToProjectOptions(project.projectFilename);
|
||||
if (!succeeded) {
|
||||
return error;
|
||||
return errors;
|
||||
}
|
||||
else {
|
||||
const oldFileNames = project.compilerService.host.roots.map(info => info.fileName);
|
||||
const newFileNames = projectOptions.files;
|
||||
const newFileNames = ts.filter(projectOptions.files, f => this.host.fileExists(f));
|
||||
const fileNamesToRemove = oldFileNames.filter(f => newFileNames.indexOf(f) < 0);
|
||||
const fileNamesToAdd = newFileNames.filter(f => oldFileNames.indexOf(f) < 0);
|
||||
|
||||
@@ -1325,6 +1388,7 @@ namespace ts.server {
|
||||
else {
|
||||
const defaultOpts = ts.getDefaultCompilerOptions();
|
||||
defaultOpts.allowNonTsExtensions = true;
|
||||
defaultOpts.allowJs = true;
|
||||
this.setCompilerOptions(defaultOpts);
|
||||
}
|
||||
this.languageService = ts.createLanguageService(this.host, this.documentRegistry);
|
||||
@@ -1337,26 +1401,29 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
isExternalModule(filename: string): boolean {
|
||||
const sourceFile = this.languageService.getSourceFile(filename);
|
||||
const sourceFile = this.languageService.getNonBoundSourceFile(filename);
|
||||
return ts.isExternalModule(sourceFile);
|
||||
}
|
||||
|
||||
static defaultFormatCodeOptions: ts.FormatCodeOptions = {
|
||||
IndentSize: 4,
|
||||
TabSize: 4,
|
||||
NewLineCharacter: ts.sys ? ts.sys.newLine : "\n",
|
||||
ConvertTabsToSpaces: true,
|
||||
IndentStyle: ts.IndentStyle.Smart,
|
||||
InsertSpaceAfterCommaDelimiter: true,
|
||||
InsertSpaceAfterSemicolonInForStatements: true,
|
||||
InsertSpaceBeforeAndAfterBinaryOperators: true,
|
||||
InsertSpaceAfterKeywordsInControlFlowStatements: true,
|
||||
InsertSpaceAfterFunctionKeywordForAnonymousFunctions: false,
|
||||
InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false,
|
||||
InsertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false,
|
||||
PlaceOpenBraceOnNewLineForFunctions: false,
|
||||
PlaceOpenBraceOnNewLineForControlBlocks: false,
|
||||
};
|
||||
static getDefaultFormatCodeOptions(host: ServerHost): ts.FormatCodeOptions {
|
||||
return ts.clone({
|
||||
IndentSize: 4,
|
||||
TabSize: 4,
|
||||
NewLineCharacter: host.newLine || "\n",
|
||||
ConvertTabsToSpaces: true,
|
||||
IndentStyle: ts.IndentStyle.Smart,
|
||||
InsertSpaceAfterCommaDelimiter: true,
|
||||
InsertSpaceAfterSemicolonInForStatements: true,
|
||||
InsertSpaceBeforeAndAfterBinaryOperators: true,
|
||||
InsertSpaceAfterKeywordsInControlFlowStatements: true,
|
||||
InsertSpaceAfterFunctionKeywordForAnonymousFunctions: false,
|
||||
InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false,
|
||||
InsertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false,
|
||||
InsertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false,
|
||||
PlaceOpenBraceOnNewLineForFunctions: false,
|
||||
PlaceOpenBraceOnNewLineForControlBlocks: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export interface LineCollection {
|
||||
@@ -1651,7 +1718,12 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
reloadFromFile(filename: string, cb?: () => any) {
|
||||
const content = this.host.readFile(filename);
|
||||
let content = this.host.readFile(filename);
|
||||
// If the file doesn't exist or cannot be read, we should
|
||||
// wipe out its cached content on the server to avoid side effects.
|
||||
if (!content) {
|
||||
content = "";
|
||||
}
|
||||
this.reload(content);
|
||||
if (cb)
|
||||
cb();
|
||||
|
||||
157
src/server/protocol.d.ts
vendored
157
src/server/protocol.d.ts
vendored
@@ -1,54 +1,54 @@
|
||||
/**
|
||||
* Declaration module describing the TypeScript Server protocol
|
||||
/**
|
||||
* Declaration module describing the TypeScript Server protocol
|
||||
*/
|
||||
declare namespace ts.server.protocol {
|
||||
/**
|
||||
* A TypeScript Server message
|
||||
/**
|
||||
* A TypeScript Server message
|
||||
*/
|
||||
export interface Message {
|
||||
/**
|
||||
* Sequence number of the message
|
||||
/**
|
||||
* Sequence number of the message
|
||||
*/
|
||||
seq: number;
|
||||
|
||||
/**
|
||||
* One of "request", "response", or "event"
|
||||
* One of "request", "response", or "event"
|
||||
*/
|
||||
type: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Client-initiated request message
|
||||
/**
|
||||
* Client-initiated request message
|
||||
*/
|
||||
export interface Request extends Message {
|
||||
/**
|
||||
* The command to execute
|
||||
* The command to execute
|
||||
*/
|
||||
command: string;
|
||||
|
||||
/**
|
||||
* Object containing arguments for the command
|
||||
/**
|
||||
* Object containing arguments for the command
|
||||
*/
|
||||
arguments?: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request to reload the project structure for all the opened files
|
||||
* Request to reload the project structure for all the opened files
|
||||
*/
|
||||
export interface ReloadProjectsRequest extends Message {
|
||||
}
|
||||
|
||||
/**
|
||||
* Server-initiated event message
|
||||
/**
|
||||
* Server-initiated event message
|
||||
*/
|
||||
export interface Event extends Message {
|
||||
/**
|
||||
* Name of event
|
||||
/**
|
||||
* Name of event
|
||||
*/
|
||||
event: string;
|
||||
|
||||
/**
|
||||
* Event-specific information
|
||||
/**
|
||||
* Event-specific information
|
||||
*/
|
||||
body?: any;
|
||||
}
|
||||
@@ -62,18 +62,18 @@ declare namespace ts.server.protocol {
|
||||
*/
|
||||
request_seq: number;
|
||||
|
||||
/**
|
||||
* Outcome of the request.
|
||||
/**
|
||||
* Outcome of the request.
|
||||
*/
|
||||
success: boolean;
|
||||
|
||||
/**
|
||||
/**
|
||||
* The command requested.
|
||||
*/
|
||||
command: string;
|
||||
|
||||
/**
|
||||
* Contains error message if success === false.
|
||||
/**
|
||||
* Contains error message if success === false.
|
||||
*/
|
||||
message?: string;
|
||||
|
||||
@@ -83,7 +83,7 @@ declare namespace ts.server.protocol {
|
||||
body?: any;
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Arguments for FileRequest messages.
|
||||
*/
|
||||
export interface FileRequestArgs {
|
||||
@@ -93,7 +93,7 @@ declare namespace ts.server.protocol {
|
||||
file: string;
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Arguments for ProjectInfoRequest request.
|
||||
*/
|
||||
export interface ProjectInfoRequestArgs extends FileRequestArgs {
|
||||
@@ -110,7 +110,7 @@ declare namespace ts.server.protocol {
|
||||
arguments: ProjectInfoRequestArgs;
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Response message body for "projectInfo" request
|
||||
*/
|
||||
export interface ProjectInfo {
|
||||
@@ -125,7 +125,7 @@ declare namespace ts.server.protocol {
|
||||
fileNames?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Response message for "projectInfo" request
|
||||
*/
|
||||
export interface ProjectInfoResponse extends Response {
|
||||
@@ -144,12 +144,12 @@ declare namespace ts.server.protocol {
|
||||
* (file, line, character offset), where line and character offset are 1-based.
|
||||
*/
|
||||
export interface FileLocationRequestArgs extends FileRequestArgs {
|
||||
/**
|
||||
/**
|
||||
* The line number for the request (1-based).
|
||||
*/
|
||||
line: number;
|
||||
|
||||
/**
|
||||
/**
|
||||
* The character offset (on the line) for the request (1-based).
|
||||
*/
|
||||
offset: number;
|
||||
@@ -216,7 +216,7 @@ declare namespace ts.server.protocol {
|
||||
* 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;
|
||||
@@ -300,14 +300,14 @@ declare namespace ts.server.protocol {
|
||||
*/
|
||||
lineText: string;
|
||||
|
||||
/**
|
||||
/**
|
||||
* True if reference is a write location, false otherwise.
|
||||
*/
|
||||
isWriteAccess: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* The body of a "references" response message.
|
||||
* The body of a "references" response message.
|
||||
*/
|
||||
export interface ReferencesResponseBody {
|
||||
/**
|
||||
@@ -325,7 +325,7 @@ declare namespace ts.server.protocol {
|
||||
*/
|
||||
symbolStartOffset: number;
|
||||
|
||||
/**
|
||||
/**
|
||||
* The full display name of the symbol.
|
||||
*/
|
||||
symbolDisplayString: string;
|
||||
@@ -355,7 +355,7 @@ declare namespace ts.server.protocol {
|
||||
}
|
||||
|
||||
/**
|
||||
* Information about the item to be renamed.
|
||||
* Information about the item to be renamed.
|
||||
*/
|
||||
export interface RenameInfo {
|
||||
/**
|
||||
@@ -373,7 +373,7 @@ declare namespace ts.server.protocol {
|
||||
*/
|
||||
displayName: string;
|
||||
|
||||
/**
|
||||
/**
|
||||
* Full display name of item to be renamed.
|
||||
*/
|
||||
fullDisplayName: string;
|
||||
@@ -383,7 +383,7 @@ declare namespace ts.server.protocol {
|
||||
*/
|
||||
kind: string;
|
||||
|
||||
/**
|
||||
/**
|
||||
* Optional modifiers for the kind (such as 'public').
|
||||
*/
|
||||
kindModifiers: string;
|
||||
@@ -444,7 +444,7 @@ declare namespace ts.server.protocol {
|
||||
/** Defines space handling after a comma delimiter. Default value is true. */
|
||||
insertSpaceAfterCommaDelimiter?: boolean;
|
||||
|
||||
/** Defines space handling after a semicolon in a for statemen. Default value is true */
|
||||
/** Defines space handling after a semicolon in a for statement. Default value is true */
|
||||
insertSpaceAfterSemicolonInForStatements?: boolean;
|
||||
|
||||
/** Defines space handling after a binary operator. Default value is true. */
|
||||
@@ -469,7 +469,7 @@ declare namespace ts.server.protocol {
|
||||
placeOpenBraceOnNewLineForControlBlocks?: boolean;
|
||||
|
||||
/** Index operator */
|
||||
[key: string] : string | number | boolean;
|
||||
[key: string]: string | number | boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -477,7 +477,7 @@ declare namespace ts.server.protocol {
|
||||
*/
|
||||
export interface ConfigureRequestArguments {
|
||||
|
||||
/**
|
||||
/**
|
||||
* Information about the host, for example 'Emacs 24.4' or
|
||||
* 'Sublime Text version 3075'
|
||||
*/
|
||||
@@ -495,7 +495,7 @@ declare namespace ts.server.protocol {
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure request; value of command field is "configure". Specifies
|
||||
* Configure request; value of command field is "configure". Specifies
|
||||
* host information, such as host type, tab size, and indent size.
|
||||
*/
|
||||
export interface ConfigureRequest extends Request {
|
||||
@@ -514,10 +514,15 @@ declare namespace ts.server.protocol {
|
||||
*/
|
||||
export interface OpenRequestArgs extends FileRequestArgs {
|
||||
/**
|
||||
* Used when a version of the file content is known to be more up to date than the one on disk.
|
||||
* Then the known content will be used upon opening instead of the disk copy
|
||||
* Used when a version of the file content is known to be more up to date than the one on disk.
|
||||
* Then the known content will be used upon opening instead of the disk copy
|
||||
*/
|
||||
fileContent?: string;
|
||||
/**
|
||||
* Used to specify the script kind of the file explicitly. It could be one of the following:
|
||||
* "TS", "JS", "TSX", "JSX"
|
||||
*/
|
||||
scriptKindName?: "TS" | "JS" | "TSX" | "JSX";
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -751,7 +756,7 @@ declare namespace ts.server.protocol {
|
||||
* Optional modifiers for the kind (such as 'public').
|
||||
*/
|
||||
kindModifiers: string;
|
||||
/**
|
||||
/**
|
||||
* A string that is used for comparing completion items so that they can be ordered. This
|
||||
* is often the same as the name but may be different in certain circumstances.
|
||||
*/
|
||||
@@ -794,7 +799,7 @@ declare namespace ts.server.protocol {
|
||||
}
|
||||
|
||||
/**
|
||||
* Signature help information for a single parameter
|
||||
* Signature help information for a single parameter
|
||||
*/
|
||||
export interface SignatureHelpParameter {
|
||||
|
||||
@@ -814,18 +819,18 @@ declare namespace ts.server.protocol {
|
||||
displayParts: SymbolDisplayPart[];
|
||||
|
||||
/**
|
||||
* Whether the parameter is optional or not.
|
||||
* Whether the parameter is optional or not.
|
||||
*/
|
||||
isOptional: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a single signature to show in signature help.
|
||||
* Represents a single signature to show in signature help.
|
||||
*/
|
||||
export interface SignatureHelpItem {
|
||||
|
||||
/**
|
||||
* Whether the signature accepts a variable number of arguments.
|
||||
* Whether the signature accepts a variable number of arguments.
|
||||
*/
|
||||
isVariadic: boolean;
|
||||
|
||||
@@ -835,7 +840,7 @@ declare namespace ts.server.protocol {
|
||||
prefixDisplayParts: SymbolDisplayPart[];
|
||||
|
||||
/**
|
||||
* The suffix disaply parts.
|
||||
* The suffix display parts.
|
||||
*/
|
||||
suffixDisplayParts: SymbolDisplayPart[];
|
||||
|
||||
@@ -845,7 +850,7 @@ declare namespace ts.server.protocol {
|
||||
separatorDisplayParts: SymbolDisplayPart[];
|
||||
|
||||
/**
|
||||
* The signature helps items for the parameters.
|
||||
* The signature helps items for the parameters.
|
||||
*/
|
||||
parameters: SignatureHelpParameter[];
|
||||
|
||||
@@ -861,17 +866,17 @@ declare namespace ts.server.protocol {
|
||||
export interface SignatureHelpItems {
|
||||
|
||||
/**
|
||||
* The signature help items.
|
||||
* The signature help items.
|
||||
*/
|
||||
items: SignatureHelpItem[];
|
||||
|
||||
/**
|
||||
* The span for which signature help should appear on a signature
|
||||
* The span for which signature help should appear on a signature
|
||||
*/
|
||||
applicableSpan: TextSpan;
|
||||
|
||||
/**
|
||||
* The item selected in the set of available help items.
|
||||
* The item selected in the set of available help items.
|
||||
*/
|
||||
selectedItemIndex: number;
|
||||
|
||||
@@ -895,7 +900,7 @@ declare namespace ts.server.protocol {
|
||||
|
||||
/**
|
||||
* Signature help request; value of command field is "signatureHelp".
|
||||
* Given a file location (file, line, col), return the signature
|
||||
* Given a file location (file, line, col), return the signature
|
||||
* help.
|
||||
*/
|
||||
export interface SignatureHelpRequest extends FileLocationRequest {
|
||||
@@ -903,7 +908,7 @@ declare namespace ts.server.protocol {
|
||||
}
|
||||
|
||||
/**
|
||||
* Repsonse object for a SignatureHelpRequest.
|
||||
* Response object for a SignatureHelpRequest.
|
||||
*/
|
||||
export interface SignatureHelpResponse extends Response {
|
||||
body?: SignatureHelpItems;
|
||||
@@ -926,8 +931,8 @@ declare namespace ts.server.protocol {
|
||||
}
|
||||
|
||||
/**
|
||||
* GeterrForProjectRequest request; value of command field is
|
||||
* "geterrForProject". It works similarly with 'Geterr', only
|
||||
* GeterrForProjectRequest request; value of command field is
|
||||
* "geterrForProject". It works similarly with 'Geterr', only
|
||||
* it request for every file in this project.
|
||||
*/
|
||||
export interface GeterrForProjectRequest extends Request {
|
||||
@@ -970,7 +975,7 @@ declare namespace ts.server.protocol {
|
||||
*/
|
||||
export interface Diagnostic {
|
||||
/**
|
||||
* Starting file location at which text appies.
|
||||
* Starting file location at which text applies.
|
||||
*/
|
||||
start: Location;
|
||||
|
||||
@@ -997,7 +1002,7 @@ declare namespace ts.server.protocol {
|
||||
diagnostics: Diagnostic[];
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Event message for "syntaxDiag" and "semanticDiag" event types.
|
||||
* These events provide syntactic and semantic errors for a file.
|
||||
*/
|
||||
@@ -1005,6 +1010,32 @@ declare namespace ts.server.protocol {
|
||||
body?: DiagnosticEventBody;
|
||||
}
|
||||
|
||||
export interface ConfigFileDiagnosticEventBody {
|
||||
/**
|
||||
* The file which trigged the searching and error-checking of the config file
|
||||
*/
|
||||
triggerFile: string;
|
||||
|
||||
/**
|
||||
* The name of the found config file.
|
||||
*/
|
||||
configFile: string;
|
||||
|
||||
/**
|
||||
* An arry of diagnostic information items for the found config file.
|
||||
*/
|
||||
diagnostics: Diagnostic[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Event message for "configFileDiag" event type.
|
||||
* This event provides errors for a found config file.
|
||||
*/
|
||||
export interface ConfigFileDiagnosticEvent extends Event {
|
||||
body?: ConfigFileDiagnosticEventBody;
|
||||
event: "configFileDiag";
|
||||
}
|
||||
|
||||
/**
|
||||
* Arguments for reload request.
|
||||
*/
|
||||
@@ -1033,7 +1064,7 @@ declare namespace ts.server.protocol {
|
||||
export interface ReloadResponse extends Response {
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Arguments for saveto request.
|
||||
*/
|
||||
export interface SavetoRequestArgs extends FileRequestArgs {
|
||||
@@ -1109,7 +1140,7 @@ declare namespace ts.server.protocol {
|
||||
*/
|
||||
kindModifiers?: string;
|
||||
|
||||
/**
|
||||
/**
|
||||
* The file in which the symbol is found.
|
||||
*/
|
||||
file: string;
|
||||
@@ -1156,7 +1187,7 @@ declare namespace ts.server.protocol {
|
||||
|
||||
/**
|
||||
* Change request message; value of command field is "change".
|
||||
* Update the server's view of the file named by argument 'file'.
|
||||
* 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 {
|
||||
@@ -1179,7 +1210,7 @@ declare namespace ts.server.protocol {
|
||||
}
|
||||
|
||||
/**
|
||||
* NavBar itesm request; value of command field is "navbar".
|
||||
* NavBar items request; value of command field is "navbar".
|
||||
* Return response giving the list of navigation bar entries
|
||||
* extracted from the requested file.
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/// <reference path="node.d.ts" />
|
||||
/// <reference path="session.ts" />
|
||||
// used in fs.writeSync
|
||||
/* tslint:disable:no-null */
|
||||
/* tslint:disable:no-null-keyword */
|
||||
|
||||
namespace ts.server {
|
||||
const readline: NodeJS.ReadLine = require("readline");
|
||||
@@ -153,6 +153,98 @@ namespace ts.server {
|
||||
// This places log file in the directory containing editorServices.js
|
||||
// TODO: check that this location is writable
|
||||
|
||||
// average async stat takes about 30 microseconds
|
||||
// set chunk size to do 30 files in < 1 millisecond
|
||||
function createPollingWatchedFileSet(interval = 2500, chunkSize = 30) {
|
||||
let watchedFiles: WatchedFile[] = [];
|
||||
let nextFileToCheck = 0;
|
||||
let watchTimer: any;
|
||||
|
||||
function getModifiedTime(fileName: string): Date {
|
||||
return fs.statSync(fileName).mtime;
|
||||
}
|
||||
|
||||
function poll(checkedIndex: number) {
|
||||
const watchedFile = watchedFiles[checkedIndex];
|
||||
if (!watchedFile) {
|
||||
return;
|
||||
}
|
||||
|
||||
fs.stat(watchedFile.fileName, (err: any, stats: any) => {
|
||||
if (err) {
|
||||
watchedFile.callback(watchedFile.fileName);
|
||||
}
|
||||
else if (watchedFile.mtime.getTime() !== stats.mtime.getTime()) {
|
||||
watchedFile.mtime = getModifiedTime(watchedFile.fileName);
|
||||
watchedFile.callback(watchedFile.fileName, watchedFile.mtime.getTime() === 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// this implementation uses polling and
|
||||
// stat due to inconsistencies of fs.watch
|
||||
// and efficiency of stat on modern filesystems
|
||||
function startWatchTimer() {
|
||||
watchTimer = setInterval(() => {
|
||||
let count = 0;
|
||||
let nextToCheck = nextFileToCheck;
|
||||
let firstCheck = -1;
|
||||
while ((count < chunkSize) && (nextToCheck !== firstCheck)) {
|
||||
poll(nextToCheck);
|
||||
if (firstCheck < 0) {
|
||||
firstCheck = nextToCheck;
|
||||
}
|
||||
nextToCheck++;
|
||||
if (nextToCheck === watchedFiles.length) {
|
||||
nextToCheck = 0;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
nextFileToCheck = nextToCheck;
|
||||
}, interval);
|
||||
}
|
||||
|
||||
function addFile(fileName: string, callback: FileWatcherCallback): WatchedFile {
|
||||
const file: WatchedFile = {
|
||||
fileName,
|
||||
callback,
|
||||
mtime: getModifiedTime(fileName)
|
||||
};
|
||||
|
||||
watchedFiles.push(file);
|
||||
if (watchedFiles.length === 1) {
|
||||
startWatchTimer();
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
function removeFile(file: WatchedFile) {
|
||||
watchedFiles = copyListRemovingItem(file, watchedFiles);
|
||||
}
|
||||
|
||||
return {
|
||||
getModifiedTime: getModifiedTime,
|
||||
poll: poll,
|
||||
startWatchTimer: startWatchTimer,
|
||||
addFile: addFile,
|
||||
removeFile: removeFile
|
||||
};
|
||||
}
|
||||
|
||||
// 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?
|
||||
const pollingWatchedFileSet = createPollingWatchedFileSet();
|
||||
const logger = createLoggerFromEnv();
|
||||
|
||||
const pending: string[] = [];
|
||||
@@ -174,10 +266,21 @@ namespace ts.server {
|
||||
}
|
||||
}
|
||||
|
||||
// Override sys.write because fs.writeSync is not reliable on Node 4
|
||||
ts.sys.write = (s: string) => writeMessage(s);
|
||||
const sys = <ServerHost>ts.sys;
|
||||
|
||||
const ioSession = new IOSession(ts.sys, logger);
|
||||
// Override sys.write because fs.writeSync is not reliable on Node 4
|
||||
sys.write = (s: string) => writeMessage(s);
|
||||
sys.watchFile = (fileName, callback) => {
|
||||
const watchedFile = pollingWatchedFileSet.addFile(fileName, callback);
|
||||
return {
|
||||
close: () => pollingWatchedFileSet.removeFile(watchedFile)
|
||||
};
|
||||
};
|
||||
|
||||
sys.setTimeout = setTimeout;
|
||||
sys.clearTimeout = clearTimeout;
|
||||
|
||||
const ioSession = new IOSession(sys, logger);
|
||||
process.on("uncaughtException", function(err: Error) {
|
||||
ioSession.logError(err, "unknown");
|
||||
});
|
||||
|
||||
@@ -68,7 +68,7 @@ namespace ts.server {
|
||||
}
|
||||
}
|
||||
|
||||
function formatDiag(fileName: string, project: Project, diag: ts.Diagnostic) {
|
||||
function formatDiag(fileName: string, project: Project, diag: ts.Diagnostic): protocol.Diagnostic {
|
||||
return {
|
||||
start: project.compilerService.host.positionToLineOffset(fileName, diag.start),
|
||||
end: project.compilerService.host.positionToLineOffset(fileName, diag.start + diag.length),
|
||||
@@ -76,6 +76,14 @@ namespace ts.server {
|
||||
};
|
||||
}
|
||||
|
||||
function formatConfigFileDiag(diag: ts.Diagnostic): protocol.Diagnostic {
|
||||
return {
|
||||
start: undefined,
|
||||
end: undefined,
|
||||
text: ts.flattenDiagnosticMessageText(diag.messageText, "\n")
|
||||
};
|
||||
}
|
||||
|
||||
export interface PendingErrorCheck {
|
||||
fileName: string;
|
||||
project: Project;
|
||||
@@ -125,6 +133,8 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
export interface ServerHost extends ts.System {
|
||||
setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): any;
|
||||
clearTimeout(timeoutId: any): void;
|
||||
}
|
||||
|
||||
export class Session {
|
||||
@@ -141,8 +151,8 @@ namespace ts.server {
|
||||
) {
|
||||
this.projectService =
|
||||
new ProjectService(host, logger, (eventName, project, fileName) => {
|
||||
this.handleEvent(eventName, project, fileName);
|
||||
});
|
||||
this.handleEvent(eventName, project, fileName);
|
||||
});
|
||||
}
|
||||
|
||||
private handleEvent(eventName: string, project: Project, fileName: string) {
|
||||
@@ -178,6 +188,21 @@ namespace ts.server {
|
||||
"\r\n\r\n" + json);
|
||||
}
|
||||
|
||||
public configFileDiagnosticEvent(triggerFile: string, configFile: string, diagnostics: ts.Diagnostic[]) {
|
||||
const bakedDiags = ts.map(diagnostics, formatConfigFileDiag);
|
||||
const ev: protocol.ConfigFileDiagnosticEvent = {
|
||||
seq: 0,
|
||||
type: "event",
|
||||
event: "configFileDiag",
|
||||
body: {
|
||||
triggerFile,
|
||||
configFile,
|
||||
diagnostics: bakedDiags
|
||||
}
|
||||
};
|
||||
this.send(ev);
|
||||
}
|
||||
|
||||
public event(info: any, eventName: string) {
|
||||
const ev: protocol.Event = {
|
||||
seq: 0,
|
||||
@@ -412,14 +437,17 @@ namespace ts.server {
|
||||
|
||||
private getRenameLocations(line: number, offset: number, fileName: string, findInComments: boolean, findInStrings: boolean): protocol.RenameResponseBody {
|
||||
const file = ts.normalizePath(fileName);
|
||||
const project = this.projectService.getProjectForFile(file);
|
||||
if (!project) {
|
||||
const info = this.projectService.getScriptInfo(file);
|
||||
const projects = this.projectService.findReferencingProjects(info);
|
||||
if (!projects.length) {
|
||||
throw Errors.NoProject;
|
||||
}
|
||||
|
||||
const compilerService = project.compilerService;
|
||||
const position = compilerService.host.lineOffsetToPosition(file, line, offset);
|
||||
const renameInfo = compilerService.languageService.getRenameInfo(file, position);
|
||||
const defaultProject = projects[0];
|
||||
// The rename info should be the same for every project
|
||||
const defaultProjectCompilerService = defaultProject.compilerService;
|
||||
const position = defaultProjectCompilerService.host.lineOffsetToPosition(file, line, offset);
|
||||
const renameInfo = defaultProjectCompilerService.languageService.getRenameInfo(file, position);
|
||||
if (!renameInfo) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -431,16 +459,43 @@ namespace ts.server {
|
||||
};
|
||||
}
|
||||
|
||||
const renameLocations = compilerService.languageService.findRenameLocations(file, position, findInStrings, findInComments);
|
||||
if (!renameLocations) {
|
||||
return undefined;
|
||||
}
|
||||
const fileSpans = combineProjectOutput(
|
||||
projects,
|
||||
(project: Project) => {
|
||||
const compilerService = project.compilerService;
|
||||
const renameLocations = compilerService.languageService.findRenameLocations(file, position, findInStrings, findInComments);
|
||||
if (!renameLocations) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const bakedRenameLocs = renameLocations.map(location => (<protocol.FileSpan>{
|
||||
file: location.fileName,
|
||||
start: compilerService.host.positionToLineOffset(location.fileName, location.textSpan.start),
|
||||
end: compilerService.host.positionToLineOffset(location.fileName, ts.textSpanEnd(location.textSpan)),
|
||||
})).sort((a, b) => {
|
||||
return renameLocations.map(location => (<protocol.FileSpan>{
|
||||
file: location.fileName,
|
||||
start: compilerService.host.positionToLineOffset(location.fileName, location.textSpan.start),
|
||||
end: compilerService.host.positionToLineOffset(location.fileName, ts.textSpanEnd(location.textSpan)),
|
||||
}));
|
||||
},
|
||||
compareRenameLocation,
|
||||
(a, b) => a.file === b.file && a.start.line === b.start.line && a.start.offset === b.start.offset
|
||||
);
|
||||
const locs = fileSpans.reduce<protocol.SpanGroup[]>((accum, cur) => {
|
||||
let 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 };
|
||||
|
||||
function compareRenameLocation(a: protocol.FileSpan, b: protocol.FileSpan) {
|
||||
if (a.file < b.file) {
|
||||
return -1;
|
||||
}
|
||||
@@ -459,79 +514,82 @@ namespace ts.server {
|
||||
return b.start.offset - a.start.offset;
|
||||
}
|
||||
}
|
||||
}).reduce<protocol.SpanGroup[]>((accum: protocol.SpanGroup[], cur: protocol.FileSpan) => {
|
||||
let 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 };
|
||||
}
|
||||
}
|
||||
|
||||
private getReferences(line: number, offset: 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
|
||||
const file = ts.normalizePath(fileName);
|
||||
const project = this.projectService.getProjectForFile(file);
|
||||
if (!project) {
|
||||
const info = this.projectService.getScriptInfo(file);
|
||||
const projects = this.projectService.findReferencingProjects(info);
|
||||
if (!projects.length) {
|
||||
throw Errors.NoProject;
|
||||
}
|
||||
|
||||
const compilerService = project.compilerService;
|
||||
const position = compilerService.host.lineOffsetToPosition(file, line, offset);
|
||||
|
||||
const references = compilerService.languageService.getReferencesAtPosition(file, position);
|
||||
if (!references) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const nameInfo = compilerService.languageService.getQuickInfoAtPosition(file, position);
|
||||
const defaultProject = projects[0];
|
||||
const position = defaultProject.compilerService.host.lineOffsetToPosition(file, line, offset);
|
||||
const nameInfo = defaultProject.compilerService.languageService.getQuickInfoAtPosition(file, position);
|
||||
if (!nameInfo) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const displayString = ts.displayPartsToString(nameInfo.displayParts);
|
||||
const nameSpan = nameInfo.textSpan;
|
||||
const nameColStart = compilerService.host.positionToLineOffset(file, nameSpan.start).offset;
|
||||
const nameText = compilerService.host.getScriptSnapshot(file).getText(nameSpan.start, ts.textSpanEnd(nameSpan));
|
||||
const bakedRefs: protocol.ReferencesResponseItem[] = references.map(ref => {
|
||||
const start = compilerService.host.positionToLineOffset(ref.fileName, ref.textSpan.start);
|
||||
const refLineSpan = compilerService.host.lineToTextSpan(ref.fileName, start.line - 1);
|
||||
const snap = compilerService.host.getScriptSnapshot(ref.fileName);
|
||||
const lineText = snap.getText(refLineSpan.start, ts.textSpanEnd(refLineSpan)).replace(/\r|\n/g, "");
|
||||
return {
|
||||
file: ref.fileName,
|
||||
start: start,
|
||||
lineText: lineText,
|
||||
end: compilerService.host.positionToLineOffset(ref.fileName, ts.textSpanEnd(ref.textSpan)),
|
||||
isWriteAccess: ref.isWriteAccess
|
||||
};
|
||||
}).sort(compareFileStart);
|
||||
const nameColStart = defaultProject.compilerService.host.positionToLineOffset(file, nameSpan.start).offset;
|
||||
const nameText = defaultProject.compilerService.host.getScriptSnapshot(file).getText(nameSpan.start, ts.textSpanEnd(nameSpan));
|
||||
const refs = combineProjectOutput<protocol.ReferencesResponseItem>(
|
||||
projects,
|
||||
(project: Project) => {
|
||||
const compilerService = project.compilerService;
|
||||
const references = compilerService.languageService.getReferencesAtPosition(file, position);
|
||||
if (!references) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return references.map(ref => {
|
||||
const start = compilerService.host.positionToLineOffset(ref.fileName, ref.textSpan.start);
|
||||
const refLineSpan = compilerService.host.lineToTextSpan(ref.fileName, start.line - 1);
|
||||
const snap = compilerService.host.getScriptSnapshot(ref.fileName);
|
||||
const lineText = snap.getText(refLineSpan.start, ts.textSpanEnd(refLineSpan)).replace(/\r|\n/g, "");
|
||||
return {
|
||||
file: ref.fileName,
|
||||
start: start,
|
||||
lineText: lineText,
|
||||
end: compilerService.host.positionToLineOffset(ref.fileName, ts.textSpanEnd(ref.textSpan)),
|
||||
isWriteAccess: ref.isWriteAccess
|
||||
};
|
||||
});
|
||||
},
|
||||
compareFileStart,
|
||||
areReferencesResponseItemsForTheSameLocation
|
||||
);
|
||||
|
||||
return {
|
||||
refs: bakedRefs,
|
||||
refs,
|
||||
symbolName: nameText,
|
||||
symbolStartOffset: nameColStart,
|
||||
symbolDisplayString: displayString
|
||||
};
|
||||
|
||||
function areReferencesResponseItemsForTheSameLocation(a: protocol.ReferencesResponseItem, b: protocol.ReferencesResponseItem) {
|
||||
if (a && b) {
|
||||
return a.file === b.file &&
|
||||
a.start === b.start &&
|
||||
a.end === b.end;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param fileName is the name of the file to be opened
|
||||
* @param fileContent is a version of the file content that is known to be more up to date than the one on disk
|
||||
*/
|
||||
private openClientFile(fileName: string, fileContent?: string) {
|
||||
private openClientFile(fileName: string, fileContent?: string, scriptKind?: ScriptKind) {
|
||||
const file = ts.normalizePath(fileName);
|
||||
this.projectService.openClientFile(file, fileContent);
|
||||
const { configFileName, configFileErrors } = this.projectService.openClientFile(file, fileContent, scriptKind);
|
||||
if (configFileErrors) {
|
||||
this.configFileDiagnosticEvent(fileName, configFileName, configFileErrors);
|
||||
}
|
||||
}
|
||||
|
||||
private getQuickInfo(line: number, offset: number, fileName: string): protocol.QuickInfoResponseBody {
|
||||
@@ -603,7 +661,7 @@ namespace ts.server {
|
||||
// Check whether we should auto-indent. This will be when
|
||||
// the position is on a line containing only whitespace.
|
||||
// This should leave the edits returned from
|
||||
// getFormattingEditsAfterKeytroke either empty or pertaining
|
||||
// getFormattingEditsAfterKeystroke either empty or pertaining
|
||||
// only to the previous line. If all this is true, then
|
||||
// add edits necessary to properly indent the current line.
|
||||
if ((key == "\n") && ((!edits) || (edits.length === 0) || allEditsBeforePos(edits, position))) {
|
||||
@@ -617,7 +675,7 @@ namespace ts.server {
|
||||
const editorOptions: ts.EditorOptions = {
|
||||
IndentSize: formatOptions.IndentSize,
|
||||
TabSize: formatOptions.TabSize,
|
||||
NewLineCharacter: "\n",
|
||||
NewLineCharacter: formatOptions.NewLineCharacter,
|
||||
ConvertTabsToSpaces: formatOptions.ConvertTabsToSpaces,
|
||||
IndentStyle: ts.IndentStyle.Smart,
|
||||
};
|
||||
@@ -792,7 +850,9 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
private closeClientFile(fileName: string) {
|
||||
if (!fileName) { return; }
|
||||
if (!fileName) {
|
||||
return;
|
||||
}
|
||||
const file = ts.normalizePath(fileName);
|
||||
this.projectService.closeClientFile(file);
|
||||
}
|
||||
@@ -834,41 +894,60 @@ namespace ts.server {
|
||||
|
||||
private getNavigateToItems(searchValue: string, fileName: string, maxResultCount?: number): protocol.NavtoItem[] {
|
||||
const file = ts.normalizePath(fileName);
|
||||
const project = this.projectService.getProjectForFile(file);
|
||||
if (!project) {
|
||||
const info = this.projectService.getScriptInfo(file);
|
||||
const projects = this.projectService.findReferencingProjects(info);
|
||||
const defaultProject = projects[0];
|
||||
if (!defaultProject) {
|
||||
throw Errors.NoProject;
|
||||
}
|
||||
|
||||
const compilerService = project.compilerService;
|
||||
const navItems = compilerService.languageService.getNavigateToItems(searchValue, maxResultCount);
|
||||
if (!navItems) {
|
||||
return undefined;
|
||||
}
|
||||
const allNavToItems = combineProjectOutput(
|
||||
projects,
|
||||
(project: Project) => {
|
||||
const compilerService = project.compilerService;
|
||||
const navItems = compilerService.languageService.getNavigateToItems(searchValue, maxResultCount);
|
||||
if (!navItems) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return navItems.map((navItem) => {
|
||||
const start = compilerService.host.positionToLineOffset(navItem.fileName, navItem.textSpan.start);
|
||||
const end = compilerService.host.positionToLineOffset(navItem.fileName, ts.textSpanEnd(navItem.textSpan));
|
||||
const 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;
|
||||
return navItems.map((navItem) => {
|
||||
const start = compilerService.host.positionToLineOffset(navItem.fileName, navItem.textSpan.start);
|
||||
const end = compilerService.host.positionToLineOffset(navItem.fileName, ts.textSpanEnd(navItem.textSpan));
|
||||
const 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;
|
||||
});
|
||||
},
|
||||
/*comparer*/ undefined,
|
||||
areNavToItemsForTheSameLocation
|
||||
);
|
||||
return allNavToItems;
|
||||
|
||||
function areNavToItemsForTheSameLocation(a: protocol.NavtoItem, b: protocol.NavtoItem) {
|
||||
if (a && b) {
|
||||
return a.file === b.file &&
|
||||
a.start === b.start &&
|
||||
a.end === b.end;
|
||||
}
|
||||
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;
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private getBraceMatching(line: number, offset: number, fileName: string): protocol.TextSpan[] {
|
||||
@@ -942,129 +1021,146 @@ namespace ts.server {
|
||||
exit() {
|
||||
}
|
||||
|
||||
private handlers: Map<(request: protocol.Request) => {response?: any, responseRequired?: boolean}> = {
|
||||
private handlers: Map<(request: protocol.Request) => { response?: any, responseRequired?: boolean }> = {
|
||||
[CommandNames.Exit]: () => {
|
||||
this.exit();
|
||||
return { responseRequired: false};
|
||||
return { responseRequired: false };
|
||||
},
|
||||
[CommandNames.Definition]: (request: protocol.Request) => {
|
||||
const defArgs = <protocol.FileLocationRequestArgs>request.arguments;
|
||||
return {response: this.getDefinition(defArgs.line, defArgs.offset, defArgs.file), responseRequired: true};
|
||||
return { response: this.getDefinition(defArgs.line, defArgs.offset, defArgs.file), responseRequired: true };
|
||||
},
|
||||
[CommandNames.TypeDefinition]: (request: protocol.Request) => {
|
||||
const defArgs = <protocol.FileLocationRequestArgs>request.arguments;
|
||||
return {response: this.getTypeDefinition(defArgs.line, defArgs.offset, defArgs.file), responseRequired: true};
|
||||
return { response: this.getTypeDefinition(defArgs.line, defArgs.offset, defArgs.file), responseRequired: true };
|
||||
},
|
||||
[CommandNames.References]: (request: protocol.Request) => {
|
||||
const defArgs = <protocol.FileLocationRequestArgs>request.arguments;
|
||||
return {response: this.getReferences(defArgs.line, defArgs.offset, defArgs.file), responseRequired: true};
|
||||
return { response: this.getReferences(defArgs.line, defArgs.offset, defArgs.file), responseRequired: true };
|
||||
},
|
||||
[CommandNames.Rename]: (request: protocol.Request) => {
|
||||
const renameArgs = <protocol.RenameRequestArgs>request.arguments;
|
||||
return {response: this.getRenameLocations(renameArgs.line, renameArgs.offset, renameArgs.file, renameArgs.findInComments, renameArgs.findInStrings), responseRequired: true};
|
||||
return { response: this.getRenameLocations(renameArgs.line, renameArgs.offset, renameArgs.file, renameArgs.findInComments, renameArgs.findInStrings), responseRequired: true };
|
||||
},
|
||||
[CommandNames.Open]: (request: protocol.Request) => {
|
||||
const openArgs = <protocol.OpenRequestArgs>request.arguments;
|
||||
this.openClientFile(openArgs.file, openArgs.fileContent);
|
||||
return {responseRequired: false};
|
||||
let scriptKind: ScriptKind;
|
||||
switch (openArgs.scriptKindName) {
|
||||
case "TS":
|
||||
scriptKind = ScriptKind.TS;
|
||||
break;
|
||||
case "JS":
|
||||
scriptKind = ScriptKind.JS;
|
||||
break;
|
||||
case "TSX":
|
||||
scriptKind = ScriptKind.TSX;
|
||||
break;
|
||||
case "JSX":
|
||||
scriptKind = ScriptKind.JSX;
|
||||
break;
|
||||
}
|
||||
this.openClientFile(openArgs.file, openArgs.fileContent, scriptKind);
|
||||
return { responseRequired: false };
|
||||
},
|
||||
[CommandNames.Quickinfo]: (request: protocol.Request) => {
|
||||
const quickinfoArgs = <protocol.FileLocationRequestArgs>request.arguments;
|
||||
return {response: this.getQuickInfo(quickinfoArgs.line, quickinfoArgs.offset, quickinfoArgs.file), responseRequired: true};
|
||||
return { response: this.getQuickInfo(quickinfoArgs.line, quickinfoArgs.offset, quickinfoArgs.file), responseRequired: true };
|
||||
},
|
||||
[CommandNames.Format]: (request: protocol.Request) => {
|
||||
const formatArgs = <protocol.FormatRequestArgs>request.arguments;
|
||||
return {response: this.getFormattingEditsForRange(formatArgs.line, formatArgs.offset, formatArgs.endLine, formatArgs.endOffset, formatArgs.file), responseRequired: true};
|
||||
return { response: this.getFormattingEditsForRange(formatArgs.line, formatArgs.offset, formatArgs.endLine, formatArgs.endOffset, formatArgs.file), responseRequired: true };
|
||||
},
|
||||
[CommandNames.Formatonkey]: (request: protocol.Request) => {
|
||||
const formatOnKeyArgs = <protocol.FormatOnKeyRequestArgs>request.arguments;
|
||||
return {response: this.getFormattingEditsAfterKeystroke(formatOnKeyArgs.line, formatOnKeyArgs.offset, formatOnKeyArgs.key, formatOnKeyArgs.file), responseRequired: true};
|
||||
return { response: this.getFormattingEditsAfterKeystroke(formatOnKeyArgs.line, formatOnKeyArgs.offset, formatOnKeyArgs.key, formatOnKeyArgs.file), responseRequired: true };
|
||||
},
|
||||
[CommandNames.Completions]: (request: protocol.Request) => {
|
||||
const completionsArgs = <protocol.CompletionsRequestArgs>request.arguments;
|
||||
return {response: this.getCompletions(completionsArgs.line, completionsArgs.offset, completionsArgs.prefix, completionsArgs.file), responseRequired: true};
|
||||
return { response: this.getCompletions(completionsArgs.line, completionsArgs.offset, completionsArgs.prefix, completionsArgs.file), responseRequired: true };
|
||||
},
|
||||
[CommandNames.CompletionDetails]: (request: protocol.Request) => {
|
||||
const completionDetailsArgs = <protocol.CompletionDetailsRequestArgs>request.arguments;
|
||||
return {response: this.getCompletionEntryDetails(completionDetailsArgs.line, completionDetailsArgs.offset,
|
||||
completionDetailsArgs.entryNames, completionDetailsArgs.file), responseRequired: true};
|
||||
return {
|
||||
response: this.getCompletionEntryDetails(completionDetailsArgs.line, completionDetailsArgs.offset,
|
||||
completionDetailsArgs.entryNames, completionDetailsArgs.file), responseRequired: true
|
||||
};
|
||||
},
|
||||
[CommandNames.SignatureHelp]: (request: protocol.Request) => {
|
||||
const signatureHelpArgs = <protocol.SignatureHelpRequestArgs>request.arguments;
|
||||
return {response: this.getSignatureHelpItems(signatureHelpArgs.line, signatureHelpArgs.offset, signatureHelpArgs.file), responseRequired: true};
|
||||
return { response: this.getSignatureHelpItems(signatureHelpArgs.line, signatureHelpArgs.offset, signatureHelpArgs.file), responseRequired: true };
|
||||
},
|
||||
[CommandNames.Geterr]: (request: protocol.Request) => {
|
||||
const geterrArgs = <protocol.GeterrRequestArgs>request.arguments;
|
||||
return {response: this.getDiagnostics(geterrArgs.delay, geterrArgs.files), responseRequired: false};
|
||||
return { response: this.getDiagnostics(geterrArgs.delay, geterrArgs.files), responseRequired: false };
|
||||
},
|
||||
[CommandNames.GeterrForProject]: (request: protocol.Request) => {
|
||||
const { file, delay } = <protocol.GeterrForProjectRequestArgs>request.arguments;
|
||||
return {response: this.getDiagnosticsForProject(delay, file), responseRequired: false};
|
||||
return { response: this.getDiagnosticsForProject(delay, file), responseRequired: false };
|
||||
},
|
||||
[CommandNames.Change]: (request: protocol.Request) => {
|
||||
const changeArgs = <protocol.ChangeRequestArgs>request.arguments;
|
||||
this.change(changeArgs.line, changeArgs.offset, changeArgs.endLine, changeArgs.endOffset,
|
||||
changeArgs.insertString, changeArgs.file);
|
||||
return {responseRequired: false};
|
||||
changeArgs.insertString, changeArgs.file);
|
||||
return { responseRequired: false };
|
||||
},
|
||||
[CommandNames.Configure]: (request: protocol.Request) => {
|
||||
const configureArgs = <protocol.ConfigureRequestArguments>request.arguments;
|
||||
this.projectService.setHostConfiguration(configureArgs);
|
||||
this.output(undefined, CommandNames.Configure, request.seq);
|
||||
return {responseRequired: false};
|
||||
return { responseRequired: false };
|
||||
},
|
||||
[CommandNames.Reload]: (request: protocol.Request) => {
|
||||
const reloadArgs = <protocol.ReloadRequestArgs>request.arguments;
|
||||
this.reload(reloadArgs.file, reloadArgs.tmpfile, request.seq);
|
||||
return {responseRequired: false};
|
||||
return {response: { reloadFinished: true }, responseRequired: true};
|
||||
},
|
||||
[CommandNames.Saveto]: (request: protocol.Request) => {
|
||||
const savetoArgs = <protocol.SavetoRequestArgs>request.arguments;
|
||||
this.saveToTmp(savetoArgs.file, savetoArgs.tmpfile);
|
||||
return {responseRequired: false};
|
||||
return { responseRequired: false };
|
||||
},
|
||||
[CommandNames.Close]: (request: protocol.Request) => {
|
||||
const closeArgs = <protocol.FileRequestArgs>request.arguments;
|
||||
this.closeClientFile(closeArgs.file);
|
||||
return {responseRequired: false};
|
||||
return { responseRequired: false };
|
||||
},
|
||||
[CommandNames.Navto]: (request: protocol.Request) => {
|
||||
const navtoArgs = <protocol.NavtoRequestArgs>request.arguments;
|
||||
return {response: this.getNavigateToItems(navtoArgs.searchValue, navtoArgs.file, navtoArgs.maxResultCount), responseRequired: true};
|
||||
return { response: this.getNavigateToItems(navtoArgs.searchValue, navtoArgs.file, navtoArgs.maxResultCount), responseRequired: true };
|
||||
},
|
||||
[CommandNames.Brace]: (request: protocol.Request) => {
|
||||
const braceArguments = <protocol.FileLocationRequestArgs>request.arguments;
|
||||
return {response: this.getBraceMatching(braceArguments.line, braceArguments.offset, braceArguments.file), responseRequired: true};
|
||||
return { response: this.getBraceMatching(braceArguments.line, braceArguments.offset, braceArguments.file), responseRequired: true };
|
||||
},
|
||||
[CommandNames.NavBar]: (request: protocol.Request) => {
|
||||
const navBarArgs = <protocol.FileRequestArgs>request.arguments;
|
||||
return {response: this.getNavigationBarItems(navBarArgs.file), responseRequired: true};
|
||||
return { response: this.getNavigationBarItems(navBarArgs.file), responseRequired: true };
|
||||
},
|
||||
[CommandNames.Occurrences]: (request: protocol.Request) => {
|
||||
const { line, offset, file: fileName } = <protocol.FileLocationRequestArgs>request.arguments;
|
||||
return {response: this.getOccurrences(line, offset, fileName), responseRequired: true};
|
||||
return { response: this.getOccurrences(line, offset, fileName), responseRequired: true };
|
||||
},
|
||||
[CommandNames.DocumentHighlights]: (request: protocol.Request) => {
|
||||
const { line, offset, file: fileName, filesToSearch } = <protocol.DocumentHighlightsRequestArgs>request.arguments;
|
||||
return {response: this.getDocumentHighlights(line, offset, fileName, filesToSearch), responseRequired: true};
|
||||
return { response: this.getDocumentHighlights(line, offset, fileName, filesToSearch), responseRequired: true };
|
||||
},
|
||||
[CommandNames.ProjectInfo]: (request: protocol.Request) => {
|
||||
const { file, needFileNameList } = <protocol.ProjectInfoRequestArgs>request.arguments;
|
||||
return {response: this.getProjectInfo(file, needFileNameList), responseRequired: true};
|
||||
return { response: this.getProjectInfo(file, needFileNameList), responseRequired: true };
|
||||
},
|
||||
[CommandNames.ReloadProjects]: (request: protocol.ReloadProjectsRequest) => {
|
||||
this.reloadProjects();
|
||||
return {responseRequired: false};
|
||||
return { responseRequired: false };
|
||||
}
|
||||
};
|
||||
public addProtocolHandler(command: string, handler: (request: protocol.Request) => {response?: any, responseRequired: boolean}) {
|
||||
public addProtocolHandler(command: string, handler: (request: protocol.Request) => { response?: any, responseRequired: boolean }) {
|
||||
if (this.handlers[command]) {
|
||||
throw new Error(`Protocol handler already exists for command "${command}"`);
|
||||
}
|
||||
this.handlers[command] = handler;
|
||||
}
|
||||
|
||||
public executeCommand(request: protocol.Request): {response?: any, responseRequired?: boolean} {
|
||||
public executeCommand(request: protocol.Request): { response?: any, responseRequired?: boolean } {
|
||||
const handler = this.handlers[request.command];
|
||||
if (handler) {
|
||||
return handler(request);
|
||||
@@ -1072,7 +1168,7 @@ namespace ts.server {
|
||||
else {
|
||||
this.projectService.log("Unrecognized JSON command: " + JSON.stringify(request));
|
||||
this.output(undefined, CommandNames.Unknown, request.seq, "Unrecognized JSON command: " + request.command);
|
||||
return {responseRequired: false};
|
||||
return { responseRequired: false };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"noImplicitAny": true,
|
||||
"removeComments": true,
|
||||
"preserveConstEnums": true,
|
||||
|
||||
Reference in New Issue
Block a user