mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
Merge branch 'master' into isInMultiLineComment
This commit is contained in:
+49
-79
@@ -84,13 +84,17 @@ namespace ts.server {
|
||||
* NOTE: this field is created on demand and should not be accessed directly.
|
||||
* Use 'getFileInfos' instead.
|
||||
*/
|
||||
private fileInfos_doNotAccessDirectly: FileMap<T>;
|
||||
private fileInfos_doNotAccessDirectly: Map<T>;
|
||||
|
||||
constructor(public readonly project: Project, private ctor: { new (scriptInfo: ScriptInfo, project: Project): T }) {
|
||||
}
|
||||
|
||||
private getFileInfos() {
|
||||
return this.fileInfos_doNotAccessDirectly || (this.fileInfos_doNotAccessDirectly = createFileMap<T>());
|
||||
return this.fileInfos_doNotAccessDirectly || (this.fileInfos_doNotAccessDirectly = createMap<T>());
|
||||
}
|
||||
|
||||
protected hasFileInfos() {
|
||||
return !!this.fileInfos_doNotAccessDirectly;
|
||||
}
|
||||
|
||||
public clear() {
|
||||
@@ -113,7 +117,7 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
protected getFileInfoPaths(): Path[] {
|
||||
return this.getFileInfos().getKeys();
|
||||
return arrayFrom(this.getFileInfos().keys() as Iterator<Path>);
|
||||
}
|
||||
|
||||
protected setFileInfo(path: Path, info: T) {
|
||||
@@ -121,20 +125,22 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
protected removeFileInfo(path: Path) {
|
||||
this.getFileInfos().remove(path);
|
||||
this.getFileInfos().delete(path);
|
||||
}
|
||||
|
||||
protected forEachFileInfo(action: (fileInfo: T) => any) {
|
||||
this.getFileInfos().forEachValue((_path, value) => action(value));
|
||||
this.getFileInfos().forEach(action);
|
||||
}
|
||||
|
||||
abstract getFilesAffectedBy(scriptInfo: ScriptInfo): string[];
|
||||
abstract onProjectUpdateGraph(): void;
|
||||
protected abstract ensureFileInfoIfInProject(scriptInfo: ScriptInfo): void;
|
||||
|
||||
/**
|
||||
* @returns {boolean} whether the emit was conducted or not
|
||||
*/
|
||||
emitFile(scriptInfo: ScriptInfo, writeFile: (path: string, data: string, writeByteOrderMark?: boolean) => void): boolean {
|
||||
this.ensureFileInfoIfInProject(scriptInfo);
|
||||
const fileInfo = this.getFileInfo(scriptInfo.path);
|
||||
if (!fileInfo) {
|
||||
return false;
|
||||
@@ -158,7 +164,21 @@ namespace ts.server {
|
||||
super(project, BuilderFileInfo);
|
||||
}
|
||||
|
||||
protected ensureFileInfoIfInProject(scriptInfo: ScriptInfo) {
|
||||
if (this.project.containsScriptInfo(scriptInfo)) {
|
||||
this.getOrCreateFileInfo(scriptInfo.path);
|
||||
}
|
||||
}
|
||||
|
||||
onProjectUpdateGraph() {
|
||||
if (this.hasFileInfos()) {
|
||||
this.forEachFileInfo(fileInfo => {
|
||||
if (!this.project.containsScriptInfo(fileInfo.scriptInfo)) {
|
||||
// This file was deleted from this project
|
||||
this.removeFileInfo(fileInfo.scriptInfo.path);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -183,57 +203,27 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
class ModuleBuilderFileInfo extends BuilderFileInfo {
|
||||
references: ModuleBuilderFileInfo[] = [];
|
||||
referencedBy: ModuleBuilderFileInfo[] = [];
|
||||
references = createSortedArray<ModuleBuilderFileInfo>();
|
||||
readonly referencedBy = createSortedArray<ModuleBuilderFileInfo>();
|
||||
scriptVersionForReferences: string;
|
||||
|
||||
static compareFileInfos(lf: ModuleBuilderFileInfo, rf: ModuleBuilderFileInfo): number {
|
||||
const l = lf.scriptInfo.fileName;
|
||||
const r = rf.scriptInfo.fileName;
|
||||
return (l < r ? -1 : (l > r ? 1 : 0));
|
||||
}
|
||||
|
||||
static addToReferenceList(array: ModuleBuilderFileInfo[], fileInfo: ModuleBuilderFileInfo) {
|
||||
if (array.length === 0) {
|
||||
array.push(fileInfo);
|
||||
return;
|
||||
}
|
||||
|
||||
const insertIndex = binarySearch(array, fileInfo, ModuleBuilderFileInfo.compareFileInfos);
|
||||
if (insertIndex < 0) {
|
||||
array.splice(~insertIndex, 0, fileInfo);
|
||||
}
|
||||
}
|
||||
|
||||
static removeFromReferenceList(array: ModuleBuilderFileInfo[], fileInfo: ModuleBuilderFileInfo) {
|
||||
if (!array || array.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (array[0] === fileInfo) {
|
||||
array.splice(0, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
const removeIndex = binarySearch(array, fileInfo, ModuleBuilderFileInfo.compareFileInfos);
|
||||
if (removeIndex >= 0) {
|
||||
array.splice(removeIndex, 1);
|
||||
}
|
||||
static compareFileInfos(lf: ModuleBuilderFileInfo, rf: ModuleBuilderFileInfo): Comparison {
|
||||
return compareStrings(lf.scriptInfo.fileName, rf.scriptInfo.fileName);
|
||||
}
|
||||
|
||||
addReferencedBy(fileInfo: ModuleBuilderFileInfo): void {
|
||||
ModuleBuilderFileInfo.addToReferenceList(this.referencedBy, fileInfo);
|
||||
insertSorted(this.referencedBy, fileInfo, ModuleBuilderFileInfo.compareFileInfos);
|
||||
}
|
||||
|
||||
removeReferencedBy(fileInfo: ModuleBuilderFileInfo): void {
|
||||
ModuleBuilderFileInfo.removeFromReferenceList(this.referencedBy, fileInfo);
|
||||
removeSorted(this.referencedBy, fileInfo, ModuleBuilderFileInfo.compareFileInfos);
|
||||
}
|
||||
|
||||
removeFileReferences() {
|
||||
for (const reference of this.references) {
|
||||
reference.removeReferencedBy(this);
|
||||
}
|
||||
this.references = [];
|
||||
clear(this.references);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -250,20 +240,24 @@ namespace ts.server {
|
||||
super.clear();
|
||||
}
|
||||
|
||||
private getReferencedFileInfos(fileInfo: ModuleBuilderFileInfo): ModuleBuilderFileInfo[] {
|
||||
private getReferencedFileInfos(fileInfo: ModuleBuilderFileInfo): SortedArray<ModuleBuilderFileInfo> {
|
||||
if (!fileInfo.isExternalModuleOrHasOnlyAmbientExternalModules()) {
|
||||
return [];
|
||||
return createSortedArray();
|
||||
}
|
||||
|
||||
const referencedFilePaths = this.project.getReferencedFiles(fileInfo.scriptInfo.path);
|
||||
if (referencedFilePaths.length > 0) {
|
||||
return map(referencedFilePaths, f => this.getOrCreateFileInfo(f)).sort(ModuleBuilderFileInfo.compareFileInfos);
|
||||
}
|
||||
return [];
|
||||
return toSortedArray(referencedFilePaths.map(f => this.getOrCreateFileInfo(f)), ModuleBuilderFileInfo.compareFileInfos);
|
||||
}
|
||||
|
||||
protected ensureFileInfoIfInProject(_scriptInfo: ScriptInfo) {
|
||||
this.ensureProjectDependencyGraphUpToDate();
|
||||
}
|
||||
|
||||
onProjectUpdateGraph() {
|
||||
this.ensureProjectDependencyGraphUpToDate();
|
||||
// Update the graph only if we have computed graph earlier
|
||||
if (this.hasFileInfos()) {
|
||||
this.ensureProjectDependencyGraphUpToDate();
|
||||
}
|
||||
}
|
||||
|
||||
private ensureProjectDependencyGraphUpToDate() {
|
||||
@@ -292,39 +286,15 @@ namespace ts.server {
|
||||
|
||||
const newReferences = this.getReferencedFileInfos(fileInfo);
|
||||
const oldReferences = fileInfo.references;
|
||||
|
||||
let oldIndex = 0;
|
||||
let newIndex = 0;
|
||||
while (oldIndex < oldReferences.length && newIndex < newReferences.length) {
|
||||
const oldReference = oldReferences[oldIndex];
|
||||
const newReference = newReferences[newIndex];
|
||||
const compare = ModuleBuilderFileInfo.compareFileInfos(oldReference, newReference);
|
||||
if (compare < 0) {
|
||||
enumerateInsertsAndDeletes(newReferences, oldReferences,
|
||||
/*inserted*/ newReference => newReference.addReferencedBy(fileInfo),
|
||||
/*deleted*/ oldReference => {
|
||||
// New reference is greater then current reference. That means
|
||||
// the current reference doesn't exist anymore after parsing. So delete
|
||||
// references.
|
||||
oldReference.removeReferencedBy(fileInfo);
|
||||
oldIndex++;
|
||||
}
|
||||
else if (compare > 0) {
|
||||
// A new reference info. Add it.
|
||||
newReference.addReferencedBy(fileInfo);
|
||||
newIndex++;
|
||||
}
|
||||
else {
|
||||
// Equal. Go to next
|
||||
oldIndex++;
|
||||
newIndex++;
|
||||
}
|
||||
}
|
||||
// Clean old references
|
||||
for (let i = oldIndex; i < oldReferences.length; i++) {
|
||||
oldReferences[i].removeReferencedBy(fileInfo);
|
||||
}
|
||||
// Update new references
|
||||
for (let i = newIndex; i < newReferences.length; i++) {
|
||||
newReferences[i].addReferencedBy(fileInfo);
|
||||
}
|
||||
},
|
||||
/*compare*/ ModuleBuilderFileInfo.compareFileInfos);
|
||||
|
||||
fileInfo.references = newReferences;
|
||||
fileInfo.scriptVersionForReferences = fileInfo.scriptInfo.getLatestVersion();
|
||||
@@ -386,4 +356,4 @@ namespace ts.server {
|
||||
return new ModuleBuilder(project);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+184
-325
@@ -34,7 +34,7 @@ namespace ts.server {
|
||||
|
||||
export class SessionClient implements LanguageService {
|
||||
private sequence = 0;
|
||||
private lineMaps: ts.Map<number[]> = ts.createMap<number[]>();
|
||||
private lineMaps: Map<number[]> = createMap<number[]>();
|
||||
private messages: string[] = [];
|
||||
private lastRenameEntry: RenameEntry;
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace ts.server {
|
||||
let lineMap = this.lineMaps.get(fileName);
|
||||
if (!lineMap) {
|
||||
const scriptSnapshot = this.host.getScriptSnapshot(fileName);
|
||||
lineMap = ts.computeLineStarts(scriptSnapshot.getText(0, scriptSnapshot.getLength()));
|
||||
lineMap = computeLineStarts(scriptSnapshot.getText(0, scriptSnapshot.getLength()));
|
||||
this.lineMaps.set(fileName, lineMap);
|
||||
}
|
||||
return lineMap;
|
||||
@@ -61,11 +61,11 @@ namespace ts.server {
|
||||
|
||||
private lineOffsetToPosition(fileName: string, lineOffset: protocol.Location, lineMap?: number[]): number {
|
||||
lineMap = lineMap || this.getLineMap(fileName);
|
||||
return ts.computePositionOfLineAndCharacter(lineMap, lineOffset.line - 1, lineOffset.offset - 1);
|
||||
return computePositionOfLineAndCharacter(lineMap, lineOffset.line - 1, lineOffset.offset - 1);
|
||||
}
|
||||
|
||||
private positionToOneBasedLineOffset(fileName: string, position: number): protocol.Location {
|
||||
const lineOffset = ts.computeLineAndCharacterOfPosition(this.getLineMap(fileName), position);
|
||||
const lineOffset = computeLineAndCharacterOfPosition(this.getLineMap(fileName), position);
|
||||
return {
|
||||
line: lineOffset.line + 1,
|
||||
offset: lineOffset.character + 1
|
||||
@@ -73,13 +73,7 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
private convertCodeEditsToTextChange(fileName: string, codeEdit: protocol.CodeEdit): ts.TextChange {
|
||||
const start = this.lineOffsetToPosition(fileName, codeEdit.start);
|
||||
const end = this.lineOffsetToPosition(fileName, codeEdit.end);
|
||||
|
||||
return {
|
||||
span: ts.createTextSpanFromBounds(start, end),
|
||||
newText: codeEdit.newText
|
||||
};
|
||||
return { span: this.decodeSpan(codeEdit, fileName), newText: codeEdit.newText };
|
||||
}
|
||||
|
||||
private processRequest<T extends protocol.Request>(command: string, args?: any): T {
|
||||
@@ -130,64 +124,42 @@ namespace ts.server {
|
||||
return response;
|
||||
}
|
||||
|
||||
openFile(fileName: string, content?: string, scriptKindName?: "TS" | "JS" | "TSX" | "JSX"): void {
|
||||
const args: protocol.OpenRequestArgs = { file: fileName, fileContent: content, scriptKindName };
|
||||
openFile(file: string, fileContent?: string, scriptKindName?: "TS" | "JS" | "TSX" | "JSX"): void {
|
||||
const args: protocol.OpenRequestArgs = { file, fileContent, scriptKindName };
|
||||
this.processRequest(CommandNames.Open, args);
|
||||
}
|
||||
|
||||
closeFile(fileName: string): void {
|
||||
const args: protocol.FileRequestArgs = { file: fileName };
|
||||
closeFile(file: string): void {
|
||||
const args: protocol.FileRequestArgs = { file };
|
||||
this.processRequest(CommandNames.Close, args);
|
||||
}
|
||||
|
||||
changeFile(fileName: string, start: number, end: number, newText: string): void {
|
||||
changeFile(fileName: string, start: number, end: number, insertString: string): void {
|
||||
// clear the line map after an edit
|
||||
this.lineMaps.set(fileName, undefined);
|
||||
|
||||
const lineOffset = this.positionToOneBasedLineOffset(fileName, start);
|
||||
const endLineOffset = this.positionToOneBasedLineOffset(fileName, end);
|
||||
|
||||
const args: protocol.ChangeRequestArgs = {
|
||||
file: fileName,
|
||||
line: lineOffset.line,
|
||||
offset: lineOffset.offset,
|
||||
endLine: endLineOffset.line,
|
||||
endOffset: endLineOffset.offset,
|
||||
insertString: newText
|
||||
};
|
||||
|
||||
const args: protocol.ChangeRequestArgs = { ...this.createFileLocationRequestArgsWithEndLineAndOffset(fileName, start, end), insertString };
|
||||
this.processRequest(CommandNames.Change, args);
|
||||
}
|
||||
|
||||
getQuickInfoAtPosition(fileName: string, position: number): QuickInfo {
|
||||
const lineOffset = this.positionToOneBasedLineOffset(fileName, position);
|
||||
const args: protocol.FileLocationRequestArgs = {
|
||||
file: fileName,
|
||||
line: lineOffset.line,
|
||||
offset: lineOffset.offset
|
||||
};
|
||||
const args = this.createFileLocationRequestArgs(fileName, position);
|
||||
|
||||
const request = this.processRequest<protocol.QuickInfoRequest>(CommandNames.Quickinfo, args);
|
||||
const response = this.processResponse<protocol.QuickInfoResponse>(request);
|
||||
|
||||
const start = this.lineOffsetToPosition(fileName, response.body.start);
|
||||
const end = this.lineOffsetToPosition(fileName, response.body.end);
|
||||
|
||||
return {
|
||||
kind: response.body.kind,
|
||||
kindModifiers: response.body.kindModifiers,
|
||||
textSpan: ts.createTextSpanFromBounds(start, end),
|
||||
textSpan: this.decodeSpan(response.body, fileName),
|
||||
displayParts: [{ kind: "text", text: response.body.displayString }],
|
||||
documentation: [{ kind: "text", text: response.body.documentation }],
|
||||
tags: response.body.tags
|
||||
};
|
||||
}
|
||||
|
||||
getProjectInfo(fileName: string, needFileNameList: boolean): protocol.ProjectInfo {
|
||||
const args: protocol.ProjectInfoRequestArgs = {
|
||||
file: fileName,
|
||||
needFileNameList: needFileNameList
|
||||
};
|
||||
getProjectInfo(file: string, needFileNameList: boolean): protocol.ProjectInfo {
|
||||
const args: protocol.ProjectInfoRequestArgs = { file, needFileNameList };
|
||||
|
||||
const request = this.processRequest<protocol.ProjectInfoRequest>(CommandNames.ProjectInfo, args);
|
||||
const response = this.processResponse<protocol.ProjectInfoResponse>(request);
|
||||
@@ -199,13 +171,7 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
getCompletionsAtPosition(fileName: string, position: number): CompletionInfo {
|
||||
const lineOffset = this.positionToOneBasedLineOffset(fileName, position);
|
||||
const args: protocol.CompletionsRequestArgs = {
|
||||
file: fileName,
|
||||
line: lineOffset.line,
|
||||
offset: lineOffset.offset,
|
||||
prefix: undefined
|
||||
};
|
||||
const args: protocol.CompletionsRequestArgs = this.createFileLocationRequestArgs(fileName, position);
|
||||
|
||||
const request = this.processRequest<protocol.CompletionsRequest>(CommandNames.Completions, args);
|
||||
const response = this.processResponse<protocol.CompletionsResponse>(request);
|
||||
@@ -217,11 +183,8 @@ namespace ts.server {
|
||||
entries: response.body.map(entry => {
|
||||
|
||||
if (entry.replacementSpan !== undefined) {
|
||||
const { name, kind, kindModifiers, sortText, replacementSpan} = entry;
|
||||
|
||||
const convertedSpan = createTextSpanFromBounds(this.lineOffsetToPosition(fileName, replacementSpan.start),
|
||||
this.lineOffsetToPosition(fileName, replacementSpan.end));
|
||||
return { name, kind, kindModifiers, sortText, replacementSpan: convertedSpan };
|
||||
const { name, kind, kindModifiers, sortText, replacementSpan } = entry;
|
||||
return { name, kind, kindModifiers, sortText, replacementSpan: this.decodeSpan(replacementSpan, fileName) };
|
||||
}
|
||||
|
||||
return entry as { name: string, kind: ScriptElementKind, kindModifiers: string, sortText: string };
|
||||
@@ -230,13 +193,7 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
getCompletionEntryDetails(fileName: string, position: number, entryName: string): CompletionEntryDetails {
|
||||
const lineOffset = this.positionToOneBasedLineOffset(fileName, position);
|
||||
const args: protocol.CompletionDetailsRequestArgs = {
|
||||
file: fileName,
|
||||
line: lineOffset.line,
|
||||
offset: lineOffset.offset,
|
||||
entryNames: [entryName]
|
||||
};
|
||||
const args: protocol.CompletionDetailsRequestArgs = { ...this.createFileLocationRequestArgs(fileName, position), entryNames: [entryName] };
|
||||
|
||||
const request = this.processRequest<protocol.CompletionDetailsRequest>(CommandNames.CompletionDetails, args);
|
||||
const response = this.processResponse<protocol.CompletionDetailsResponse>(request);
|
||||
@@ -257,55 +214,36 @@ namespace ts.server {
|
||||
const request = this.processRequest<protocol.NavtoRequest>(CommandNames.Navto, args);
|
||||
const response = this.processResponse<protocol.NavtoResponse>(request);
|
||||
|
||||
return response.body.map(entry => {
|
||||
const fileName = entry.file;
|
||||
const start = this.lineOffsetToPosition(fileName, entry.start);
|
||||
const end = this.lineOffsetToPosition(fileName, entry.end);
|
||||
|
||||
return {
|
||||
name: entry.name,
|
||||
containerName: entry.containerName || "",
|
||||
containerKind: entry.containerKind || ScriptElementKind.unknown,
|
||||
kind: entry.kind,
|
||||
kindModifiers: entry.kindModifiers,
|
||||
matchKind: entry.matchKind,
|
||||
isCaseSensitive: entry.isCaseSensitive,
|
||||
fileName: fileName,
|
||||
textSpan: ts.createTextSpanFromBounds(start, end)
|
||||
};
|
||||
});
|
||||
return response.body.map(entry => ({
|
||||
name: entry.name,
|
||||
containerName: entry.containerName || "",
|
||||
containerKind: entry.containerKind || ScriptElementKind.unknown,
|
||||
kind: entry.kind,
|
||||
kindModifiers: entry.kindModifiers,
|
||||
matchKind: entry.matchKind,
|
||||
isCaseSensitive: entry.isCaseSensitive,
|
||||
fileName: entry.file,
|
||||
textSpan: this.decodeSpan(entry),
|
||||
}));
|
||||
}
|
||||
|
||||
getFormattingEditsForRange(fileName: string, start: number, end: number, _options: ts.FormatCodeOptions): ts.TextChange[] {
|
||||
const startLineOffset = this.positionToOneBasedLineOffset(fileName, start);
|
||||
const endLineOffset = this.positionToOneBasedLineOffset(fileName, end);
|
||||
const args: protocol.FormatRequestArgs = {
|
||||
file: fileName,
|
||||
line: startLineOffset.line,
|
||||
offset: startLineOffset.offset,
|
||||
endLine: endLineOffset.line,
|
||||
endOffset: endLineOffset.offset,
|
||||
};
|
||||
getFormattingEditsForRange(file: string, start: number, end: number, _options: FormatCodeOptions): ts.TextChange[] {
|
||||
const args: protocol.FormatRequestArgs = this.createFileLocationRequestArgsWithEndLineAndOffset(file, start, end);
|
||||
|
||||
|
||||
// TODO: handle FormatCodeOptions
|
||||
const request = this.processRequest<protocol.FormatRequest>(CommandNames.Format, args);
|
||||
const response = this.processResponse<protocol.FormatResponse>(request);
|
||||
|
||||
return response.body.map(entry => this.convertCodeEditsToTextChange(fileName, entry));
|
||||
return response.body.map(entry => this.convertCodeEditsToTextChange(file, entry));
|
||||
}
|
||||
|
||||
getFormattingEditsForDocument(fileName: string, options: ts.FormatCodeOptions): ts.TextChange[] {
|
||||
getFormattingEditsForDocument(fileName: string, options: 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[] {
|
||||
const lineOffset = this.positionToOneBasedLineOffset(fileName, position);
|
||||
const args: protocol.FormatOnKeyRequestArgs = {
|
||||
file: fileName,
|
||||
line: lineOffset.line,
|
||||
offset: lineOffset.offset,
|
||||
key: key
|
||||
};
|
||||
const args: protocol.FormatOnKeyRequestArgs = { ...this.createFileLocationRequestArgs(fileName, position), key };
|
||||
|
||||
// TODO: handle FormatCodeOptions
|
||||
const request = this.processRequest<protocol.FormatOnKeyRequest>(CommandNames.Formatonkey, args);
|
||||
@@ -315,79 +253,49 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
getDefinitionAtPosition(fileName: string, position: number): DefinitionInfo[] {
|
||||
const lineOffset = this.positionToOneBasedLineOffset(fileName, position);
|
||||
const args: protocol.FileLocationRequestArgs = {
|
||||
file: fileName,
|
||||
line: lineOffset.line,
|
||||
offset: lineOffset.offset,
|
||||
};
|
||||
const args: protocol.FileLocationRequestArgs = this.createFileLocationRequestArgs(fileName, position);
|
||||
|
||||
const request = this.processRequest<protocol.DefinitionRequest>(CommandNames.Definition, args);
|
||||
const response = this.processResponse<protocol.DefinitionResponse>(request);
|
||||
|
||||
return response.body.map(entry => {
|
||||
const fileName = entry.file;
|
||||
const start = this.lineOffsetToPosition(fileName, entry.start);
|
||||
const end = this.lineOffsetToPosition(fileName, entry.end);
|
||||
return {
|
||||
containerKind: ScriptElementKind.unknown,
|
||||
containerName: "",
|
||||
fileName: fileName,
|
||||
textSpan: ts.createTextSpanFromBounds(start, end),
|
||||
kind: ScriptElementKind.unknown,
|
||||
name: ""
|
||||
};
|
||||
});
|
||||
return response.body.map(entry => ({
|
||||
containerKind: ScriptElementKind.unknown,
|
||||
containerName: "",
|
||||
fileName: entry.file,
|
||||
textSpan: this.decodeSpan(entry),
|
||||
kind: ScriptElementKind.unknown,
|
||||
name: ""
|
||||
}));
|
||||
}
|
||||
|
||||
getTypeDefinitionAtPosition(fileName: string, position: number): DefinitionInfo[] {
|
||||
const lineOffset = this.positionToOneBasedLineOffset(fileName, position);
|
||||
const args: protocol.FileLocationRequestArgs = {
|
||||
file: fileName,
|
||||
line: lineOffset.line,
|
||||
offset: lineOffset.offset,
|
||||
};
|
||||
const args: protocol.FileLocationRequestArgs = this.createFileLocationRequestArgs(fileName, position);
|
||||
|
||||
const request = this.processRequest<protocol.TypeDefinitionRequest>(CommandNames.TypeDefinition, args);
|
||||
const response = this.processResponse<protocol.TypeDefinitionResponse>(request);
|
||||
|
||||
return response.body.map(entry => {
|
||||
const fileName = entry.file;
|
||||
const start = this.lineOffsetToPosition(fileName, entry.start);
|
||||
const end = this.lineOffsetToPosition(fileName, entry.end);
|
||||
return {
|
||||
containerKind: ScriptElementKind.unknown,
|
||||
containerName: "",
|
||||
fileName: fileName,
|
||||
textSpan: ts.createTextSpanFromBounds(start, end),
|
||||
kind: ScriptElementKind.unknown,
|
||||
name: ""
|
||||
};
|
||||
});
|
||||
return response.body.map(entry => ({
|
||||
containerKind: ScriptElementKind.unknown,
|
||||
containerName: "",
|
||||
fileName: entry.file,
|
||||
textSpan: this.decodeSpan(entry),
|
||||
kind: ScriptElementKind.unknown,
|
||||
name: ""
|
||||
}));
|
||||
}
|
||||
|
||||
getImplementationAtPosition(fileName: string, position: number): ImplementationLocation[] {
|
||||
const lineOffset = this.positionToOneBasedLineOffset(fileName, position);
|
||||
const args: protocol.FileLocationRequestArgs = {
|
||||
file: fileName,
|
||||
line: lineOffset.line,
|
||||
offset: lineOffset.offset,
|
||||
};
|
||||
const args = this.createFileLocationRequestArgs(fileName, position);
|
||||
|
||||
const request = this.processRequest<protocol.ImplementationRequest>(CommandNames.Implementation, args);
|
||||
const response = this.processResponse<protocol.ImplementationResponse>(request);
|
||||
|
||||
return response.body.map(entry => {
|
||||
const fileName = entry.file;
|
||||
const start = this.lineOffsetToPosition(fileName, entry.start);
|
||||
const end = this.lineOffsetToPosition(fileName, entry.end);
|
||||
return {
|
||||
fileName,
|
||||
textSpan: ts.createTextSpanFromBounds(start, end),
|
||||
kind: ScriptElementKind.unknown,
|
||||
displayParts: []
|
||||
};
|
||||
});
|
||||
return response.body.map(entry => ({
|
||||
fileName: entry.file,
|
||||
textSpan: this.decodeSpan(entry),
|
||||
kind: ScriptElementKind.unknown,
|
||||
displayParts: []
|
||||
}));
|
||||
}
|
||||
|
||||
findReferences(_fileName: string, _position: number): ReferencedSymbol[] {
|
||||
@@ -396,49 +304,39 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
getReferencesAtPosition(fileName: string, position: number): ReferenceEntry[] {
|
||||
const lineOffset = this.positionToOneBasedLineOffset(fileName, position);
|
||||
const args: protocol.FileLocationRequestArgs = {
|
||||
file: fileName,
|
||||
line: lineOffset.line,
|
||||
offset: lineOffset.offset,
|
||||
};
|
||||
const args = this.createFileLocationRequestArgs(fileName, position);
|
||||
|
||||
const request = this.processRequest<protocol.ReferencesRequest>(CommandNames.References, args);
|
||||
const response = this.processResponse<protocol.ReferencesResponse>(request);
|
||||
|
||||
return response.body.refs.map(entry => {
|
||||
const fileName = entry.file;
|
||||
const start = this.lineOffsetToPosition(fileName, entry.start);
|
||||
const end = this.lineOffsetToPosition(fileName, entry.end);
|
||||
return {
|
||||
fileName: fileName,
|
||||
textSpan: ts.createTextSpanFromBounds(start, end),
|
||||
isWriteAccess: entry.isWriteAccess,
|
||||
isDefinition: entry.isDefinition,
|
||||
};
|
||||
});
|
||||
return response.body.refs.map(entry => ({
|
||||
fileName: entry.file,
|
||||
textSpan: this.decodeSpan(entry),
|
||||
isWriteAccess: entry.isWriteAccess,
|
||||
isDefinition: entry.isDefinition,
|
||||
}));
|
||||
}
|
||||
|
||||
getEmitOutput(_fileName: string): EmitOutput {
|
||||
return notImplemented();
|
||||
}
|
||||
|
||||
getSyntacticDiagnostics(fileName: string): Diagnostic[] {
|
||||
const args: protocol.SyntacticDiagnosticsSyncRequestArgs = { file: fileName, includeLinePosition: true };
|
||||
getSyntacticDiagnostics(file: string): Diagnostic[] {
|
||||
const args: protocol.SyntacticDiagnosticsSyncRequestArgs = { file, includeLinePosition: true };
|
||||
|
||||
const request = this.processRequest<protocol.SyntacticDiagnosticsSyncRequest>(CommandNames.SyntacticDiagnosticsSync, args);
|
||||
const response = this.processResponse<protocol.SyntacticDiagnosticsSyncResponse>(request);
|
||||
|
||||
return (<protocol.DiagnosticWithLinePosition[]>response.body).map(entry => this.convertDiagnostic(entry, fileName));
|
||||
return (<protocol.DiagnosticWithLinePosition[]>response.body).map(entry => this.convertDiagnostic(entry, file));
|
||||
}
|
||||
|
||||
getSemanticDiagnostics(fileName: string): Diagnostic[] {
|
||||
const args: protocol.SemanticDiagnosticsSyncRequestArgs = { file: fileName, includeLinePosition: true };
|
||||
getSemanticDiagnostics(file: string): Diagnostic[] {
|
||||
const args: protocol.SemanticDiagnosticsSyncRequestArgs = { file, includeLinePosition: true };
|
||||
|
||||
const request = this.processRequest<protocol.SemanticDiagnosticsSyncRequest>(CommandNames.SemanticDiagnosticsSync, args);
|
||||
const response = this.processResponse<protocol.SemanticDiagnosticsSyncResponse>(request);
|
||||
|
||||
return (<protocol.DiagnosticWithLinePosition[]>response.body).map(entry => this.convertDiagnostic(entry, fileName));
|
||||
return (<protocol.DiagnosticWithLinePosition[]>response.body).map(entry => this.convertDiagnostic(entry, file));
|
||||
}
|
||||
|
||||
convertDiagnostic(entry: protocol.DiagnosticWithLinePosition, _fileName: string): Diagnostic {
|
||||
@@ -456,7 +354,7 @@ namespace ts.server {
|
||||
start: entry.start,
|
||||
length: entry.length,
|
||||
messageText: entry.message,
|
||||
category: category,
|
||||
category,
|
||||
code: entry.code
|
||||
};
|
||||
}
|
||||
@@ -466,29 +364,18 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
getRenameInfo(fileName: string, position: number, findInStrings?: boolean, findInComments?: boolean): RenameInfo {
|
||||
const lineOffset = this.positionToOneBasedLineOffset(fileName, position);
|
||||
const args: protocol.RenameRequestArgs = {
|
||||
file: fileName,
|
||||
line: lineOffset.line,
|
||||
offset: lineOffset.offset,
|
||||
findInStrings,
|
||||
findInComments
|
||||
};
|
||||
const args: protocol.RenameRequestArgs = { ...this.createFileLocationRequestArgs(fileName, position), findInStrings, findInComments };
|
||||
|
||||
const request = this.processRequest<protocol.RenameRequest>(CommandNames.Rename, args);
|
||||
const response = this.processResponse<protocol.RenameResponse>(request);
|
||||
const locations: RenameLocation[] = [];
|
||||
response.body.locs.map((entry: protocol.SpanGroup) => {
|
||||
for (const entry of response.body.locs) {
|
||||
const fileName = entry.file;
|
||||
entry.locs.map((loc: protocol.TextSpan) => {
|
||||
const start = this.lineOffsetToPosition(fileName, loc.start);
|
||||
const end = this.lineOffsetToPosition(fileName, loc.end);
|
||||
locations.push({
|
||||
textSpan: ts.createTextSpanFromBounds(start, end),
|
||||
fileName: fileName
|
||||
});
|
||||
});
|
||||
});
|
||||
for (const loc of entry.locs) {
|
||||
locations.push({ textSpan: this.decodeSpan(loc, fileName), fileName });
|
||||
}
|
||||
}
|
||||
|
||||
return this.lastRenameEntry = {
|
||||
canRename: response.body.info.canRename,
|
||||
displayName: response.body.info.displayName,
|
||||
@@ -496,12 +383,12 @@ namespace ts.server {
|
||||
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
|
||||
triggerSpan: createTextSpanFromBounds(position, position),
|
||||
fileName,
|
||||
position,
|
||||
findInStrings,
|
||||
findInComments,
|
||||
locations,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -534,12 +421,12 @@ namespace ts.server {
|
||||
}));
|
||||
}
|
||||
|
||||
getNavigationBarItems(fileName: string): NavigationBarItem[] {
|
||||
const request = this.processRequest<protocol.NavBarRequest>(CommandNames.NavBar, { file: fileName });
|
||||
getNavigationBarItems(file: string): NavigationBarItem[] {
|
||||
const request = this.processRequest<protocol.NavBarRequest>(CommandNames.NavBar, { file });
|
||||
const response = this.processResponse<protocol.NavBarResponse>(request);
|
||||
|
||||
const lineMap = this.getLineMap(fileName);
|
||||
return this.decodeNavigationBarItems(response.body, fileName, lineMap);
|
||||
const lineMap = this.getLineMap(file);
|
||||
return this.decodeNavigationBarItems(response.body, file, lineMap);
|
||||
}
|
||||
|
||||
private decodeNavigationTree(tree: protocol.NavigationTree, fileName: string, lineMap: number[]): NavigationTree {
|
||||
@@ -552,15 +439,19 @@ namespace ts.server {
|
||||
};
|
||||
}
|
||||
|
||||
getNavigationTree(fileName: string): NavigationTree {
|
||||
const request = this.processRequest<protocol.NavTreeRequest>(CommandNames.NavTree, { file: fileName });
|
||||
getNavigationTree(file: string): NavigationTree {
|
||||
const request = this.processRequest<protocol.NavTreeRequest>(CommandNames.NavTree, { file });
|
||||
const response = this.processResponse<protocol.NavTreeResponse>(request);
|
||||
|
||||
const lineMap = this.getLineMap(fileName);
|
||||
return this.decodeNavigationTree(response.body, fileName, lineMap);
|
||||
const lineMap = this.getLineMap(file);
|
||||
return this.decodeNavigationTree(response.body, file, lineMap);
|
||||
}
|
||||
|
||||
private decodeSpan(span: protocol.TextSpan, fileName: string, lineMap: number[]) {
|
||||
private decodeSpan(span: protocol.TextSpan & { file: string }): TextSpan;
|
||||
private decodeSpan(span: protocol.TextSpan, fileName: string, lineMap?: number[]): TextSpan;
|
||||
private decodeSpan(span: protocol.TextSpan & { file: string }, fileName?: string, lineMap?: number[]): TextSpan {
|
||||
fileName = fileName || span.file;
|
||||
lineMap = lineMap || this.getLineMap(fileName);
|
||||
return createTextSpanFromBounds(
|
||||
this.lineOffsetToPosition(fileName, span.start, lineMap),
|
||||
this.lineOffsetToPosition(fileName, span.end, lineMap));
|
||||
@@ -575,12 +466,7 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
getSignatureHelpItems(fileName: string, position: number): SignatureHelpItems {
|
||||
const lineOffset = this.positionToOneBasedLineOffset(fileName, position);
|
||||
const args: protocol.SignatureHelpRequestArgs = {
|
||||
file: fileName,
|
||||
line: lineOffset.line,
|
||||
offset: lineOffset.offset
|
||||
};
|
||||
const args: protocol.SignatureHelpRequestArgs = this.createFileLocationRequestArgs(fileName, position);
|
||||
|
||||
const request = this.processRequest<protocol.SignatureHelpRequest>(CommandNames.SignatureHelp, args);
|
||||
const response = this.processResponse<protocol.SignatureHelpResponse>(request);
|
||||
@@ -589,75 +475,40 @@ namespace ts.server {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const helpItems: protocol.SignatureHelpItems = response.body;
|
||||
const span = helpItems.applicableSpan;
|
||||
const start = this.lineOffsetToPosition(fileName, span.start);
|
||||
const end = this.lineOffsetToPosition(fileName, span.end);
|
||||
const { items, applicableSpan: encodedApplicableSpan, selectedItemIndex, argumentIndex, argumentCount } = response.body;
|
||||
|
||||
const result: SignatureHelpItems = {
|
||||
items: helpItems.items,
|
||||
applicableSpan: {
|
||||
start: start,
|
||||
length: end - start
|
||||
},
|
||||
selectedItemIndex: helpItems.selectedItemIndex,
|
||||
argumentIndex: helpItems.argumentIndex,
|
||||
argumentCount: helpItems.argumentCount,
|
||||
};
|
||||
return result;
|
||||
const applicableSpan = this.decodeSpan(encodedApplicableSpan, fileName);
|
||||
|
||||
return { items, applicableSpan, selectedItemIndex, argumentIndex, argumentCount };
|
||||
}
|
||||
|
||||
getOccurrencesAtPosition(fileName: string, position: number): ReferenceEntry[] {
|
||||
const lineOffset = this.positionToOneBasedLineOffset(fileName, position);
|
||||
const args: protocol.FileLocationRequestArgs = {
|
||||
file: fileName,
|
||||
line: lineOffset.line,
|
||||
offset: lineOffset.offset,
|
||||
};
|
||||
const args = this.createFileLocationRequestArgs(fileName, position);
|
||||
|
||||
const request = this.processRequest<protocol.OccurrencesRequest>(CommandNames.Occurrences, args);
|
||||
const response = this.processResponse<protocol.OccurrencesResponse>(request);
|
||||
|
||||
return response.body.map(entry => {
|
||||
const fileName = entry.file;
|
||||
const start = this.lineOffsetToPosition(fileName, entry.start);
|
||||
const end = this.lineOffsetToPosition(fileName, entry.end);
|
||||
return {
|
||||
fileName,
|
||||
textSpan: ts.createTextSpanFromBounds(start, end),
|
||||
isWriteAccess: entry.isWriteAccess,
|
||||
isDefinition: false
|
||||
};
|
||||
});
|
||||
return response.body.map(entry => ({
|
||||
fileName: entry.file,
|
||||
textSpan: this.decodeSpan(entry),
|
||||
isWriteAccess: entry.isWriteAccess,
|
||||
isDefinition: false
|
||||
}));
|
||||
}
|
||||
|
||||
getDocumentHighlights(fileName: string, position: number, filesToSearch: string[]): DocumentHighlights[] {
|
||||
const { line, offset } = this.positionToOneBasedLineOffset(fileName, position);
|
||||
const args: protocol.DocumentHighlightsRequestArgs = { file: fileName, line, offset, filesToSearch };
|
||||
const args: protocol.DocumentHighlightsRequestArgs = { ...this.createFileLocationRequestArgs(fileName, position), filesToSearch };
|
||||
|
||||
const request = this.processRequest<protocol.DocumentHighlightsRequest>(CommandNames.DocumentHighlights, args);
|
||||
const response = this.processResponse<protocol.DocumentHighlightsResponse>(request);
|
||||
|
||||
const self = this;
|
||||
return response.body.map(convertToDocumentHighlights);
|
||||
|
||||
function convertToDocumentHighlights(item: ts.server.protocol.DocumentHighlightsItem): ts.DocumentHighlights {
|
||||
const { file, highlightSpans } = item;
|
||||
|
||||
return {
|
||||
fileName: file,
|
||||
highlightSpans: highlightSpans.map(convertHighlightSpan)
|
||||
};
|
||||
|
||||
function convertHighlightSpan(span: ts.server.protocol.HighlightSpan): ts.HighlightSpan {
|
||||
const start = self.lineOffsetToPosition(file, span.start);
|
||||
const end = self.lineOffsetToPosition(file, span.end);
|
||||
return {
|
||||
textSpan: ts.createTextSpanFromBounds(start, end),
|
||||
kind: span.kind
|
||||
};
|
||||
}
|
||||
}
|
||||
return response.body.map(item => ({
|
||||
fileName: item.file,
|
||||
highlightSpans: item.highlightSpans.map(span => ({
|
||||
textSpan: this.decodeSpan(span, item.file),
|
||||
kind: span.kind
|
||||
})),
|
||||
}));
|
||||
}
|
||||
|
||||
getOutliningSpans(_fileName: string): OutliningSpan[] {
|
||||
@@ -680,39 +531,36 @@ namespace ts.server {
|
||||
return notImplemented();
|
||||
}
|
||||
|
||||
getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[]): CodeAction[] {
|
||||
const startLineOffset = this.positionToOneBasedLineOffset(fileName, start);
|
||||
const endLineOffset = this.positionToOneBasedLineOffset(fileName, end);
|
||||
|
||||
const args: protocol.CodeFixRequestArgs = {
|
||||
file: fileName,
|
||||
startLine: startLineOffset.line,
|
||||
startOffset: startLineOffset.offset,
|
||||
endLine: endLineOffset.line,
|
||||
endOffset: endLineOffset.offset,
|
||||
errorCodes: errorCodes,
|
||||
};
|
||||
getCodeFixesAtPosition(file: string, start: number, end: number, errorCodes: number[]): CodeAction[] {
|
||||
const args: protocol.CodeFixRequestArgs = { ...this.createFileRangeRequestArgs(file, start, end), errorCodes };
|
||||
|
||||
const request = this.processRequest<protocol.CodeFixRequest>(CommandNames.GetCodeFixes, args);
|
||||
const response = this.processResponse<protocol.CodeFixResponse>(request);
|
||||
|
||||
return response.body.map(entry => this.convertCodeActions(entry, fileName));
|
||||
return response.body.map(entry => this.convertCodeActions(entry, file));
|
||||
}
|
||||
|
||||
private createFileLocationOrRangeRequestArgs(positionOrRange: number | TextRange, fileName: string): protocol.FileLocationOrRangeRequestArgs {
|
||||
if (typeof positionOrRange === "number") {
|
||||
const { line, offset } = this.positionToOneBasedLineOffset(fileName, positionOrRange);
|
||||
return <protocol.FileLocationRequestArgs>{ file: fileName, line, offset };
|
||||
}
|
||||
const { line: startLine, offset: startOffset } = this.positionToOneBasedLineOffset(fileName, positionOrRange.pos);
|
||||
const { line: endLine, offset: endOffset } = this.positionToOneBasedLineOffset(fileName, positionOrRange.end);
|
||||
return <protocol.FileRangeRequestArgs>{
|
||||
file: fileName,
|
||||
startLine,
|
||||
startOffset,
|
||||
endLine,
|
||||
endOffset
|
||||
};
|
||||
return typeof positionOrRange === "number"
|
||||
? this.createFileLocationRequestArgs(fileName, positionOrRange)
|
||||
: this.createFileRangeRequestArgs(fileName, positionOrRange.pos, positionOrRange.end);
|
||||
}
|
||||
|
||||
private createFileLocationRequestArgs(file: string, position: number): protocol.FileLocationRequestArgs {
|
||||
const { line, offset } = this.positionToOneBasedLineOffset(file, position);
|
||||
return { file, line, offset };
|
||||
}
|
||||
|
||||
private createFileRangeRequestArgs(file: string, start: number, end: number): protocol.FileRangeRequestArgs {
|
||||
const { line: startLine, offset: startOffset } = this.positionToOneBasedLineOffset(file, start);
|
||||
const { line: endLine, offset: endOffset } = this.positionToOneBasedLineOffset(file, end);
|
||||
return { file, startLine, startOffset, endLine, endOffset };
|
||||
}
|
||||
|
||||
private createFileLocationRequestArgsWithEndLineAndOffset(file: string, start: number, end: number): protocol.FileLocationRequestArgs & { endLine: number, endOffset: number } {
|
||||
const { line, offset } = this.positionToOneBasedLineOffset(file, start);
|
||||
const { line: endLine, offset: endOffset } = this.positionToOneBasedLineOffset(file, end);
|
||||
return { file, line, offset, endLine, endOffset };
|
||||
}
|
||||
|
||||
getApplicableRefactors(fileName: string, positionOrRange: number | TextRange): ApplicableRefactorInfo[] {
|
||||
@@ -723,20 +571,49 @@ namespace ts.server {
|
||||
return response.body;
|
||||
}
|
||||
|
||||
getRefactorCodeActions(
|
||||
getEditsForRefactor(
|
||||
fileName: string,
|
||||
_formatOptions: FormatCodeSettings,
|
||||
positionOrRange: number | TextRange,
|
||||
refactorName: string) {
|
||||
refactorName: string,
|
||||
actionName: string): RefactorEditInfo {
|
||||
|
||||
const args = this.createFileLocationOrRangeRequestArgs(positionOrRange, fileName) as protocol.GetRefactorCodeActionsRequestArgs;
|
||||
args.refactorName = refactorName;
|
||||
const args = this.createFileLocationOrRangeRequestArgs(positionOrRange, fileName) as protocol.GetEditsForRefactorRequestArgs;
|
||||
args.refactor = refactorName;
|
||||
args.action = actionName;
|
||||
|
||||
const request = this.processRequest<protocol.GetRefactorCodeActionsRequest>(CommandNames.GetRefactorCodeActions, args);
|
||||
const response = this.processResponse<protocol.GetRefactorCodeActionsResponse>(request);
|
||||
const codeActions = response.body.actions;
|
||||
const request = this.processRequest<protocol.GetEditsForRefactorRequest>(CommandNames.GetEditsForRefactor, args);
|
||||
const response = this.processResponse<protocol.GetEditsForRefactorResponse>(request);
|
||||
|
||||
return map(codeActions, codeAction => this.convertCodeActions(codeAction, fileName));
|
||||
if (!response.body) {
|
||||
return {
|
||||
edits: []
|
||||
};
|
||||
}
|
||||
|
||||
const edits: FileTextChanges[] = this.convertCodeEditsToTextChanges(response.body.edits);
|
||||
|
||||
const renameFilename: string | undefined = response.body.renameFilename;
|
||||
let renameLocation: number | undefined = undefined;
|
||||
if (renameFilename !== undefined) {
|
||||
renameLocation = this.lineOffsetToPosition(renameFilename, response.body.renameLocation);
|
||||
}
|
||||
|
||||
return {
|
||||
edits,
|
||||
renameFilename,
|
||||
renameLocation
|
||||
};
|
||||
}
|
||||
|
||||
private convertCodeEditsToTextChanges(edits: protocol.FileCodeEdits[]): FileTextChanges[] {
|
||||
return edits.map(edit => {
|
||||
const fileName = edit.fileName;
|
||||
return {
|
||||
fileName,
|
||||
textChanges: edit.textChanges.map(t => this.convertTextChangeToCodeEdit(t, fileName))
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
convertCodeActions(entry: protocol.CodeAction, fileName: string): CodeAction {
|
||||
@@ -750,37 +627,19 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
convertTextChangeToCodeEdit(change: protocol.CodeEdit, fileName: string): ts.TextChange {
|
||||
const start = this.lineOffsetToPosition(fileName, change.start);
|
||||
const end = this.lineOffsetToPosition(fileName, change.end);
|
||||
|
||||
return {
|
||||
span: {
|
||||
start: start,
|
||||
length: end - start
|
||||
},
|
||||
span: this.decodeSpan(change, fileName),
|
||||
newText: change.newText ? change.newText : ""
|
||||
};
|
||||
}
|
||||
|
||||
getBraceMatchingAtPosition(fileName: string, position: number): TextSpan[] {
|
||||
const lineOffset = this.positionToOneBasedLineOffset(fileName, position);
|
||||
const args: protocol.FileLocationRequestArgs = {
|
||||
file: fileName,
|
||||
line: lineOffset.line,
|
||||
offset: lineOffset.offset,
|
||||
};
|
||||
const args = this.createFileLocationRequestArgs(fileName, position);
|
||||
|
||||
const request = this.processRequest<protocol.BraceRequest>(CommandNames.Brace, args);
|
||||
const response = this.processResponse<protocol.BraceResponse>(request);
|
||||
|
||||
return response.body.map(entry => {
|
||||
const start = this.lineOffsetToPosition(fileName, entry.start);
|
||||
const end = this.lineOffsetToPosition(fileName, entry.end);
|
||||
return {
|
||||
start: start,
|
||||
length: end - start,
|
||||
};
|
||||
});
|
||||
return response.body.map(entry => this.decodeSpan(entry, fileName));
|
||||
}
|
||||
|
||||
getIndentationAtPosition(_fileName: string, _position: number, _options: EditorOptions): number {
|
||||
|
||||
@@ -37,13 +37,15 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
export interface ProjectInfoTelemetryEventData {
|
||||
/** Cryptographically secure hash of project file location. */
|
||||
readonly projectId: string;
|
||||
/** Count of file extensions seen in the project. */
|
||||
readonly fileStats: FileStats;
|
||||
/**
|
||||
* Any compiler options that might contain paths will be taken out.
|
||||
* Enum compiler options will be converted to strings.
|
||||
*/
|
||||
readonly compilerOptions: ts.CompilerOptions;
|
||||
readonly compilerOptions: CompilerOptions;
|
||||
// "extends", "files", "include", or "exclude" will be undefined if an external config is used.
|
||||
// Otherwise, we will use "true" if the property is present and "false" if it is missing.
|
||||
readonly extends: boolean | undefined;
|
||||
@@ -199,7 +201,7 @@ namespace ts.server {
|
||||
* This helper function 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);
|
||||
const result = flatMap(projects, action).sort(comparer);
|
||||
return projects.length > 1 ? deduplicate(result, areEqual) : result;
|
||||
}
|
||||
|
||||
@@ -237,10 +239,7 @@ namespace ts.server {
|
||||
const fileNamePropertyReader: FilePropertyReader<string> = {
|
||||
getFileName: x => x,
|
||||
getScriptKind: _ => undefined,
|
||||
hasMixedContent: (fileName, extraFileExtensions) => {
|
||||
const mixedContentExtensions = ts.map(ts.filter(extraFileExtensions, item => item.isMixedContent), item => item.extension);
|
||||
return forEach(mixedContentExtensions, extension => fileExtensionIs(fileName, extension));
|
||||
}
|
||||
hasMixedContent: (fileName, extraFileExtensions) => some(extraFileExtensions, ext => ext.isMixedContent && fileExtensionIs(fileName, ext.extension)),
|
||||
};
|
||||
|
||||
const externalFilePropertyReader: FilePropertyReader<protocol.ExternalFile> = {
|
||||
@@ -341,7 +340,7 @@ namespace ts.server {
|
||||
/**
|
||||
* Container of all known scripts
|
||||
*/
|
||||
private readonly filenameToScriptInfo = createFileMap<ScriptInfo>();
|
||||
private readonly filenameToScriptInfo = createMap<ScriptInfo>();
|
||||
/**
|
||||
* maps external project file name to list of config files that were the part of this project
|
||||
*/
|
||||
@@ -371,7 +370,7 @@ namespace ts.server {
|
||||
private readonly throttledOperations: ThrottledOperations;
|
||||
|
||||
private readonly hostConfiguration: HostConfiguration;
|
||||
private static safelist: SafeList = defaultTypeSafeList;
|
||||
private safelist: SafeList = defaultTypeSafeList;
|
||||
|
||||
private changedFiles: ScriptInfo[];
|
||||
|
||||
@@ -563,10 +562,17 @@ namespace ts.server {
|
||||
}
|
||||
else {
|
||||
if (info && (!info.isScriptOpen())) {
|
||||
// file has been changed which might affect the set of referenced files in projects that include
|
||||
// this file and set of inferred projects
|
||||
info.reloadFromFile();
|
||||
this.updateProjectGraphs(info.containingProjects);
|
||||
if (info.containingProjects.length === 0) {
|
||||
// Orphan script info, remove it as we can always reload it on next open
|
||||
info.stopWatcher();
|
||||
this.filenameToScriptInfo.delete(info.path);
|
||||
}
|
||||
else {
|
||||
// file has been changed which might affect the set of referenced files in projects that include
|
||||
// this file and set of inferred projects
|
||||
info.reloadFromFile();
|
||||
this.updateProjectGraphs(info.containingProjects);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -579,7 +585,7 @@ namespace ts.server {
|
||||
// TODO: handle isOpen = true case
|
||||
|
||||
if (!info.isScriptOpen()) {
|
||||
this.filenameToScriptInfo.remove(info.path);
|
||||
this.filenameToScriptInfo.delete(info.path);
|
||||
this.lastDeletedFile = info;
|
||||
|
||||
// capture list of projects since detachAllProjects will wipe out original list
|
||||
@@ -624,7 +630,7 @@ namespace ts.server {
|
||||
// If a change was made inside "folder/file", node will trigger the callback twice:
|
||||
// one with the fileName being "folder/file", and the other one with "folder".
|
||||
// We don't respond to the second one.
|
||||
if (fileName && !ts.isSupportedSourceFileName(fileName, project.getCompilerOptions(), this.hostConfiguration.extraFileExtensions)) {
|
||||
if (fileName && !isSupportedSourceFileName(fileName, project.getCompilerOptions(), this.hostConfiguration.extraFileExtensions)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -694,15 +700,15 @@ namespace ts.server {
|
||||
|
||||
switch (project.projectKind) {
|
||||
case ProjectKind.External:
|
||||
removeItemFromSet(this.externalProjects, <ExternalProject>project);
|
||||
unorderedRemoveItem(this.externalProjects, <ExternalProject>project);
|
||||
this.projectToSizeMap.delete((project as ExternalProject).externalProjectName);
|
||||
break;
|
||||
case ProjectKind.Configured:
|
||||
removeItemFromSet(this.configuredProjects, <ConfiguredProject>project);
|
||||
unorderedRemoveItem(this.configuredProjects, <ConfiguredProject>project);
|
||||
this.projectToSizeMap.delete((project as ConfiguredProject).canonicalConfigFilePath);
|
||||
break;
|
||||
case ProjectKind.Inferred:
|
||||
removeItemFromSet(this.inferredProjects, <InferredProject>project);
|
||||
unorderedRemoveItem(this.inferredProjects, <InferredProject>project);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -781,7 +787,7 @@ namespace ts.server {
|
||||
// to the disk, and the server's version of the file can be out of sync.
|
||||
info.close();
|
||||
|
||||
removeItemFromSet(this.openFiles, info);
|
||||
unorderedRemoveItem(this.openFiles, info);
|
||||
|
||||
// collect all projects that should be removed
|
||||
let projectsToRemove: Project[];
|
||||
@@ -827,11 +833,29 @@ namespace ts.server {
|
||||
this.assignScriptInfoToInferredProjectIfNecessary(f, /*addToListOfOpenFiles*/ false);
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup script infos that arent part of any project is postponed to
|
||||
// next file open so that if file from same project is opened we wont end up creating same script infos
|
||||
}
|
||||
if (info.containingProjects.length === 0) {
|
||||
// if there are not projects that include this script info - delete it
|
||||
this.filenameToScriptInfo.remove(info.path);
|
||||
|
||||
// If the current info is being just closed - add the watcher file to track changes
|
||||
// But if file was deleted, handle that part
|
||||
if (this.host.fileExists(info.fileName)) {
|
||||
this.watchClosedScriptInfo(info);
|
||||
}
|
||||
else {
|
||||
this.handleDeletedFile(info);
|
||||
}
|
||||
}
|
||||
|
||||
private deleteOrphanScriptInfoNotInAnyProject() {
|
||||
this.filenameToScriptInfo.forEach(info => {
|
||||
if (!info.isScriptOpen() && info.containingProjects.length === 0) {
|
||||
// if there are not projects that include this script info - delete it
|
||||
info.stopWatcher();
|
||||
this.filenameToScriptInfo.delete(info.path);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -913,7 +937,7 @@ namespace ts.server {
|
||||
|
||||
this.logger.info("Open files: ");
|
||||
for (const rootFile of this.openFiles) {
|
||||
this.logger.info(rootFile.fileName);
|
||||
this.logger.info(`\t${rootFile.fileName}`);
|
||||
}
|
||||
|
||||
this.logger.endGroup();
|
||||
@@ -948,20 +972,14 @@ namespace ts.server {
|
||||
configFilename = normalizePath(configFilename);
|
||||
|
||||
const configFileContent = this.host.readFile(configFilename);
|
||||
let errors: Diagnostic[];
|
||||
|
||||
const result = parseConfigFileTextToJson(configFilename, configFileContent);
|
||||
let config = result.config;
|
||||
|
||||
if (result.error) {
|
||||
// try to reparse config file
|
||||
const { configJsonObject: sanitizedConfig, diagnostics } = sanitizeConfigFile(configFilename, configFileContent);
|
||||
config = sanitizedConfig;
|
||||
errors = diagnostics.length ? diagnostics : [result.error];
|
||||
const result = parseJsonText(configFilename, configFileContent);
|
||||
if (!result.endOfFileToken) {
|
||||
result.endOfFileToken = <EndOfFileToken>{ kind: SyntaxKind.EndOfFileToken };
|
||||
}
|
||||
|
||||
const parsedCommandLine = parseJsonConfigFileContent(
|
||||
config,
|
||||
const errors = result.parseDiagnostics;
|
||||
const parsedCommandLine = parseJsonSourceFileConfigFileContent(
|
||||
result,
|
||||
this.host,
|
||||
getDirectoryPath(configFilename),
|
||||
/*existingOptions*/ {},
|
||||
@@ -970,23 +988,23 @@ namespace ts.server {
|
||||
this.hostConfiguration.extraFileExtensions);
|
||||
|
||||
if (parsedCommandLine.errors.length) {
|
||||
errors = concatenate(errors, parsedCommandLine.errors);
|
||||
errors.push(...parsedCommandLine.errors);
|
||||
}
|
||||
|
||||
Debug.assert(!!parsedCommandLine.fileNames);
|
||||
|
||||
if (parsedCommandLine.fileNames.length === 0) {
|
||||
(errors || (errors = [])).push(createCompilerDiagnostic(Diagnostics.The_config_file_0_found_doesn_t_contain_any_source_files, configFilename));
|
||||
errors.push(createCompilerDiagnostic(Diagnostics.The_config_file_0_found_doesn_t_contain_any_source_files, configFilename));
|
||||
return { success: false, configFileErrors: errors };
|
||||
}
|
||||
|
||||
const projectOptions: ProjectOptions = {
|
||||
files: parsedCommandLine.fileNames,
|
||||
compilerOptions: parsedCommandLine.options,
|
||||
configHasExtendsProperty: config.extends !== undefined,
|
||||
configHasFilesProperty: config.files !== undefined,
|
||||
configHasIncludeProperty: config.include !== undefined,
|
||||
configHasExcludeProperty: config.exclude !== undefined,
|
||||
configHasExtendsProperty: parsedCommandLine.raw["extends"] !== undefined,
|
||||
configHasFilesProperty: parsedCommandLine.raw["files"] !== undefined,
|
||||
configHasIncludeProperty: parsedCommandLine.raw["include"] !== undefined,
|
||||
configHasExcludeProperty: parsedCommandLine.raw["exclude"] !== undefined,
|
||||
wildcardDirectories: createMapFromTemplate(parsedCommandLine.wildcardDirectories),
|
||||
typeAcquisition: parsedCommandLine.typeAcquisition,
|
||||
compileOnSave: parsedCommandLine.compileOnSave
|
||||
@@ -1049,6 +1067,7 @@ namespace ts.server {
|
||||
if (!this.eventHandler) return;
|
||||
|
||||
const data: ProjectInfoTelemetryEventData = {
|
||||
projectId: this.host.createHash(projectKey),
|
||||
fileStats: countEachFileTypes(project.getScriptInfos()),
|
||||
compilerOptions: convertCompilerOptionsForTelemetry(project.getCompilerOptions()),
|
||||
typeAcquisition: convertTypeAcquisition(project.getTypeAcquisition()),
|
||||
@@ -1060,7 +1079,7 @@ namespace ts.server {
|
||||
configFileName: configFileName(),
|
||||
projectType: project instanceof server.ExternalProject ? "external" : "configured",
|
||||
languageServiceEnabled: project.languageServiceEnabled,
|
||||
version: ts.version,
|
||||
version,
|
||||
};
|
||||
this.eventHandler({ eventName: ProjectInfoTelemetryEvent, data });
|
||||
|
||||
@@ -1070,7 +1089,7 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
const configFilePath = project instanceof server.ConfiguredProject && project.getConfigFilePath();
|
||||
const base = ts.getBaseFileName(configFilePath);
|
||||
const base = getBaseFileName(configFilePath);
|
||||
return base === "tsconfig.json" || base === "jsconfig.json" ? base : "other";
|
||||
}
|
||||
|
||||
@@ -1154,7 +1173,7 @@ namespace ts.server {
|
||||
return {
|
||||
success: conversionResult.success,
|
||||
project,
|
||||
errors: project.getProjectErrors()
|
||||
errors: project.getGlobalProjectErrors()
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1300,6 +1319,14 @@ namespace ts.server {
|
||||
return this.getScriptInfoForNormalizedPath(toNormalizedPath(uncheckedFileName));
|
||||
}
|
||||
|
||||
watchClosedScriptInfo(info: ScriptInfo) {
|
||||
// do not watch files with mixed content - server doesn't know how to interpret it
|
||||
if (!info.hasMixedContent) {
|
||||
const { fileName } = info;
|
||||
info.setWatcher(this.host.watchFile(fileName, _ => this.onSourceFileChanged(fileName)));
|
||||
}
|
||||
}
|
||||
|
||||
getOrCreateScriptInfoForNormalizedPath(fileName: NormalizedPath, openedByClient: boolean, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean) {
|
||||
let info = this.getScriptInfoForNormalizedPath(fileName);
|
||||
if (!info) {
|
||||
@@ -1315,15 +1342,13 @@ namespace ts.server {
|
||||
}
|
||||
}
|
||||
else {
|
||||
// do not watch files with mixed content - server doesn't know how to interpret it
|
||||
if (!hasMixedContent) {
|
||||
info.setWatcher(this.host.watchFile(fileName, _ => this.onSourceFileChanged(fileName)));
|
||||
}
|
||||
this.watchClosedScriptInfo(info);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (info) {
|
||||
if (openedByClient && !info.isScriptOpen()) {
|
||||
info.stopWatcher();
|
||||
info.open(fileContent);
|
||||
if (hasMixedContent) {
|
||||
info.registerFileUpdate();
|
||||
@@ -1418,6 +1443,7 @@ namespace ts.server {
|
||||
for (const p of this.inferredProjects) {
|
||||
p.updateGraph();
|
||||
}
|
||||
|
||||
this.printProjects();
|
||||
}
|
||||
|
||||
@@ -1451,6 +1477,11 @@ namespace ts.server {
|
||||
// at this point if file is the part of some configured/external project then this project should be created
|
||||
const info = this.getOrCreateScriptInfoForNormalizedPath(fileName, /*openedByClient*/ true, fileContent, scriptKind, hasMixedContent);
|
||||
this.assignScriptInfoToInferredProjectIfNecessary(info, /*addToListOfOpenFiles*/ true);
|
||||
// Delete the orphan files here because there might be orphan script infos (which are not part of project)
|
||||
// when some file/s were closed which resulted in project removal.
|
||||
// It was then postponed to cleanup these script infos so that they can be reused if
|
||||
// the file from that old project is reopened because of opening file from here.
|
||||
this.deleteOrphanScriptInfoNotInAnyProject();
|
||||
this.printProjects();
|
||||
return { configFileName, configFileErrors };
|
||||
}
|
||||
@@ -1583,13 +1614,13 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
/** Makes a filename safe to insert in a RegExp */
|
||||
private static filenameEscapeRegexp = /[-\/\\^$*+?.()|[\]{}]/g;
|
||||
private static readonly filenameEscapeRegexp = /[-\/\\^$*+?.()|[\]{}]/g;
|
||||
private static escapeFilenameForRegex(filename: string) {
|
||||
return filename.replace(this.filenameEscapeRegexp, "\\$&");
|
||||
}
|
||||
|
||||
resetSafeList(): void {
|
||||
ProjectService.safelist = defaultTypeSafeList;
|
||||
this.safelist = defaultTypeSafeList;
|
||||
}
|
||||
|
||||
loadSafeList(fileName: string): void {
|
||||
@@ -1599,7 +1630,7 @@ namespace ts.server {
|
||||
raw[k].match = new RegExp(raw[k].match as {} as string, "i");
|
||||
}
|
||||
// raw is now fixed and ready
|
||||
ProjectService.safelist = raw;
|
||||
this.safelist = raw;
|
||||
}
|
||||
|
||||
applySafeList(proj: protocol.ExternalProject): void {
|
||||
@@ -1610,8 +1641,8 @@ namespace ts.server {
|
||||
|
||||
const normalizedNames = rootFiles.map(f => normalizeSlashes(f.fileName));
|
||||
|
||||
for (const name of Object.keys(ProjectService.safelist)) {
|
||||
const rule = ProjectService.safelist[name];
|
||||
for (const name of Object.keys(this.safelist)) {
|
||||
const rule = this.safelist[name];
|
||||
for (const root of normalizedNames) {
|
||||
if (rule.match.test(root)) {
|
||||
this.logger.info(`Excluding files based on rule ${name}`);
|
||||
|
||||
+28
-20
@@ -3,21 +3,21 @@
|
||||
/// <reference path="scriptInfo.ts" />
|
||||
|
||||
namespace ts.server {
|
||||
export class LSHost implements ts.LanguageServiceHost, ModuleResolutionHost {
|
||||
private compilationSettings: ts.CompilerOptions;
|
||||
private readonly resolvedModuleNames = createFileMap<Map<ResolvedModuleWithFailedLookupLocations>>();
|
||||
private readonly resolvedTypeReferenceDirectives = createFileMap<Map<ResolvedTypeReferenceDirectiveWithFailedLookupLocations>>();
|
||||
export class LSHost implements LanguageServiceHost, ModuleResolutionHost {
|
||||
private compilationSettings: CompilerOptions;
|
||||
private readonly resolvedModuleNames = createMap<Map<ResolvedModuleWithFailedLookupLocations>>();
|
||||
private readonly resolvedTypeReferenceDirectives = createMap<Map<ResolvedTypeReferenceDirectiveWithFailedLookupLocations>>();
|
||||
private readonly getCanonicalFileName: (fileName: string) => string;
|
||||
|
||||
private filesWithChangedSetOfUnresolvedImports: Path[];
|
||||
|
||||
private readonly resolveModuleName: typeof resolveModuleName;
|
||||
private resolveModuleName: typeof resolveModuleName;
|
||||
readonly trace: (s: string) => void;
|
||||
readonly realpath?: (path: string) => string;
|
||||
|
||||
constructor(private readonly host: ServerHost, private readonly project: Project, private readonly cancellationToken: HostCancellationToken) {
|
||||
constructor(private readonly host: ServerHost, private project: Project, private readonly cancellationToken: HostCancellationToken) {
|
||||
this.cancellationToken = new ThrottledCancellationToken(cancellationToken, project.projectService.throttleWaitMilliseconds);
|
||||
this.getCanonicalFileName = ts.createGetCanonicalFileName(this.host.useCaseSensitiveFileNames);
|
||||
this.getCanonicalFileName = createGetCanonicalFileName(this.host.useCaseSensitiveFileNames);
|
||||
|
||||
if (host.trace) {
|
||||
this.trace = s => host.trace(s);
|
||||
@@ -29,7 +29,7 @@ namespace ts.server {
|
||||
: undefined;
|
||||
const primaryResult = resolveModuleName(moduleName, containingFile, compilerOptions, host);
|
||||
// return result immediately only if it is .ts, .tsx or .d.ts
|
||||
if (moduleHasNonRelativeName(moduleName) && !(primaryResult.resolvedModule && extensionIsTypeScript(primaryResult.resolvedModule.extension)) && globalCache !== undefined) {
|
||||
if (!isExternalModuleNameRelative(moduleName) && !(primaryResult.resolvedModule && extensionIsTypeScript(primaryResult.resolvedModule.extension)) && globalCache !== undefined) {
|
||||
// otherwise try to load typings from @types
|
||||
|
||||
// create different collection of failed lookup locations for second pass
|
||||
@@ -47,6 +47,11 @@ namespace ts.server {
|
||||
}
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.project = undefined;
|
||||
this.resolveModuleName = undefined;
|
||||
}
|
||||
|
||||
public startRecordingFilesWithChangedResolutions() {
|
||||
this.filesWithChangedSetOfUnresolvedImports = [];
|
||||
}
|
||||
@@ -60,7 +65,7 @@ namespace ts.server {
|
||||
private resolveNamesWithLocalCache<T extends { failedLookupLocations: string[] }, R>(
|
||||
names: string[],
|
||||
containingFile: string,
|
||||
cache: ts.FileMap<Map<T>>,
|
||||
cache: Map<Map<T>>,
|
||||
loader: (name: string, containingFile: string, options: CompilerOptions, host: ModuleResolutionHost) => T,
|
||||
getResult: (s: T) => R,
|
||||
getResultFileName: (result: R) => string | undefined,
|
||||
@@ -94,7 +99,7 @@ namespace ts.server {
|
||||
}
|
||||
}
|
||||
|
||||
ts.Debug.assert(resolution !== undefined);
|
||||
Debug.assert(resolution !== undefined);
|
||||
|
||||
resolvedModules.push(getResult(resolution));
|
||||
}
|
||||
@@ -172,7 +177,7 @@ namespace ts.server {
|
||||
return combinePaths(nodeModuleBinDir, getDefaultLibFileName(this.compilationSettings));
|
||||
}
|
||||
|
||||
getScriptSnapshot(filename: string): ts.IScriptSnapshot {
|
||||
getScriptSnapshot(filename: string): IScriptSnapshot {
|
||||
const scriptInfo = this.project.getScriptInfoLSHost(filename);
|
||||
if (scriptInfo) {
|
||||
return scriptInfo.getSnapshot();
|
||||
@@ -205,11 +210,14 @@ namespace ts.server {
|
||||
return this.host.resolvePath(path);
|
||||
}
|
||||
|
||||
fileExists(path: string): boolean {
|
||||
return this.host.fileExists(path);
|
||||
fileExists(file: string): boolean {
|
||||
// As an optimization, don't hit the disks for files we already know don't exist
|
||||
// (because we're watching for their creation).
|
||||
const path = toPath(file, this.host.getCurrentDirectory(), this.getCanonicalFileName);
|
||||
return !this.project.isWatchedMissingFile(path) && this.host.fileExists(file);
|
||||
}
|
||||
|
||||
readFile(fileName: string): string {
|
||||
readFile(fileName: string): string | undefined {
|
||||
return this.host.readFile(fileName);
|
||||
}
|
||||
|
||||
@@ -217,8 +225,8 @@ namespace ts.server {
|
||||
return this.host.directoryExists(path);
|
||||
}
|
||||
|
||||
readDirectory(path: string, extensions?: string[], exclude?: string[], include?: string[]): string[] {
|
||||
return this.host.readDirectory(path, extensions, exclude, include);
|
||||
readDirectory(path: string, extensions?: ReadonlyArray<string>, exclude?: ReadonlyArray<string>, include?: ReadonlyArray<string>, depth?: number): string[] {
|
||||
return this.host.readDirectory(path, extensions, exclude, include, depth);
|
||||
}
|
||||
|
||||
getDirectories(path: string): string[] {
|
||||
@@ -226,11 +234,11 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
notifyFileRemoved(info: ScriptInfo) {
|
||||
this.resolvedModuleNames.remove(info.path);
|
||||
this.resolvedTypeReferenceDirectives.remove(info.path);
|
||||
this.resolvedModuleNames.delete(info.path);
|
||||
this.resolvedTypeReferenceDirectives.delete(info.path);
|
||||
}
|
||||
|
||||
setCompilationSettings(opt: ts.CompilerOptions) {
|
||||
setCompilationSettings(opt: CompilerOptions) {
|
||||
if (changesAffectModuleResolution(this.compilationSettings, opt)) {
|
||||
this.resolvedModuleNames.clear();
|
||||
this.resolvedTypeReferenceDirectives.clear();
|
||||
@@ -238,4 +246,4 @@ namespace ts.server {
|
||||
this.compilationSettings = opt;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+126
-42
@@ -25,7 +25,7 @@ namespace ts.server {
|
||||
result.jsx += 1;
|
||||
break;
|
||||
case ScriptKind.TS:
|
||||
fileExtensionIs(info.fileName, ".d.ts")
|
||||
fileExtensionIs(info.fileName, Extension.Dts)
|
||||
? result.dts += 1
|
||||
: result.ts += 1;
|
||||
break;
|
||||
@@ -58,7 +58,7 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
export class UnresolvedImportsMap {
|
||||
readonly perFileMap = createFileMap<ReadonlyArray<string>>();
|
||||
readonly perFileMap = createMap<ReadonlyArray<string>>();
|
||||
private version = 0;
|
||||
|
||||
public clear() {
|
||||
@@ -71,7 +71,7 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
public remove(path: Path) {
|
||||
this.perFileMap.remove(path);
|
||||
this.perFileMap.delete(path);
|
||||
this.version++;
|
||||
}
|
||||
|
||||
@@ -104,9 +104,10 @@ namespace ts.server {
|
||||
|
||||
export abstract class Project {
|
||||
private rootFiles: ScriptInfo[] = [];
|
||||
private rootFilesMap: FileMap<ScriptInfo> = createFileMap<ScriptInfo>();
|
||||
private program: ts.Program;
|
||||
private rootFilesMap: Map<ScriptInfo> = createMap<ScriptInfo>();
|
||||
private program: Program;
|
||||
private externalFiles: SortedReadonlyArray<string>;
|
||||
private missingFilesMap: Map<FileWatcher> = createMap<FileWatcher>();
|
||||
|
||||
private cachedUnresolvedImportsPerFile = new UnresolvedImportsMap();
|
||||
private lastCachedUnresolvedImportsList: SortedReadonlyArray<string>;
|
||||
@@ -116,17 +117,17 @@ namespace ts.server {
|
||||
|
||||
public languageServiceEnabled = true;
|
||||
|
||||
protected readonly lsHost: LSHost;
|
||||
protected lsHost: LSHost;
|
||||
|
||||
builder: Builder;
|
||||
/**
|
||||
* Set of files names that were updated since the last call to getChangesSinceVersion.
|
||||
*/
|
||||
private updatedFileNames: Map<string>;
|
||||
private updatedFileNames: Map<true>;
|
||||
/**
|
||||
* Set of files that was returned from the last call to getChangesSinceVersion.
|
||||
*/
|
||||
private lastReportedFileNames: Map<string>;
|
||||
private lastReportedFileNames: Map<true>;
|
||||
/**
|
||||
* Last version that was reported.
|
||||
*/
|
||||
@@ -179,14 +180,14 @@ namespace ts.server {
|
||||
private readonly projectName: string,
|
||||
readonly projectKind: ProjectKind,
|
||||
readonly projectService: ProjectService,
|
||||
private documentRegistry: ts.DocumentRegistry,
|
||||
private documentRegistry: DocumentRegistry,
|
||||
hasExplicitListOfFiles: boolean,
|
||||
languageServiceEnabled: boolean,
|
||||
private compilerOptions: CompilerOptions,
|
||||
public compileOnSaveEnabled: boolean) {
|
||||
|
||||
if (!this.compilerOptions) {
|
||||
this.compilerOptions = ts.getDefaultCompilerOptions();
|
||||
this.compilerOptions = getDefaultCompilerOptions();
|
||||
this.compilerOptions.allowNonTsExtensions = true;
|
||||
this.compilerOptions.allowJs = true;
|
||||
}
|
||||
@@ -200,7 +201,7 @@ namespace ts.server {
|
||||
this.lsHost = new LSHost(this.projectService.host, this, this.projectService.cancellationToken);
|
||||
this.lsHost.setCompilationSettings(this.compilerOptions);
|
||||
|
||||
this.languageService = ts.createLanguageService(this.lsHost, this.documentRegistry);
|
||||
this.languageService = createLanguageService(this.lsHost, this.documentRegistry);
|
||||
|
||||
if (!languageServiceEnabled) {
|
||||
this.disableLanguageService();
|
||||
@@ -216,7 +217,14 @@ namespace ts.server {
|
||||
}
|
||||
}
|
||||
|
||||
getProjectErrors() {
|
||||
/**
|
||||
* Get the errors that dont have any file name associated
|
||||
*/
|
||||
getGlobalProjectErrors() {
|
||||
return filter(this.projectErrors, diagnostic => !diagnostic.file);
|
||||
}
|
||||
|
||||
getAllProjectErrors() {
|
||||
return this.projectErrors;
|
||||
}
|
||||
|
||||
@@ -297,9 +305,19 @@ namespace ts.server {
|
||||
this.rootFiles = undefined;
|
||||
this.rootFilesMap = undefined;
|
||||
this.program = undefined;
|
||||
this.builder = undefined;
|
||||
this.cachedUnresolvedImportsPerFile = undefined;
|
||||
this.projectErrors = undefined;
|
||||
this.lsHost.dispose();
|
||||
this.lsHost = undefined;
|
||||
|
||||
// Clean up file watchers waiting for missing files
|
||||
this.missingFilesMap.forEach(fileWatcher => fileWatcher.close());
|
||||
this.missingFilesMap = undefined;
|
||||
|
||||
// signal language service to release source files acquired from document registry
|
||||
this.languageService.dispose();
|
||||
this.languageService = undefined;
|
||||
}
|
||||
|
||||
getCompilerOptions() {
|
||||
@@ -357,7 +375,7 @@ namespace ts.server {
|
||||
return this.getLanguageService().getEmitOutput(info.fileName, emitOnlyDtsFiles);
|
||||
}
|
||||
|
||||
getFileNames(excludeFilesFromExternalLibraries?: boolean) {
|
||||
getFileNames(excludeFilesFromExternalLibraries?: boolean, excludeConfigFiles?: boolean) {
|
||||
if (!this.program) {
|
||||
return [];
|
||||
}
|
||||
@@ -380,9 +398,39 @@ namespace ts.server {
|
||||
}
|
||||
result.push(asNormalizedPath(f.fileName));
|
||||
}
|
||||
if (!excludeConfigFiles) {
|
||||
const configFile = this.program.getCompilerOptions().configFile;
|
||||
if (configFile) {
|
||||
result.push(asNormalizedPath(configFile.fileName));
|
||||
if (configFile.extendedSourceFiles) {
|
||||
for (const f of configFile.extendedSourceFiles) {
|
||||
result.push(asNormalizedPath(f));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
hasConfigFile(configFilePath: NormalizedPath) {
|
||||
if (this.program && this.languageServiceEnabled) {
|
||||
const configFile = this.program.getCompilerOptions().configFile;
|
||||
if (configFile) {
|
||||
if (configFilePath === asNormalizedPath(configFile.fileName)) {
|
||||
return true;
|
||||
}
|
||||
if (configFile.extendedSourceFiles) {
|
||||
for (const f of configFile.extendedSourceFiles) {
|
||||
if (configFilePath === asNormalizedPath(f)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
getAllEmittableFiles() {
|
||||
if (!this.languageServiceEnabled) {
|
||||
return [];
|
||||
@@ -410,7 +458,7 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
isRoot(info: ScriptInfo) {
|
||||
return this.rootFilesMap && this.rootFilesMap.contains(info.path);
|
||||
return this.rootFilesMap && this.rootFilesMap.has(info.path);
|
||||
}
|
||||
|
||||
// add a root file to project
|
||||
@@ -439,7 +487,7 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
registerFileUpdate(fileName: string) {
|
||||
(this.updatedFileNames || (this.updatedFileNames = createMap<string>())).set(fileName, fileName);
|
||||
(this.updatedFileNames || (this.updatedFileNames = createMap<true>())).set(fileName, true);
|
||||
}
|
||||
|
||||
markAsDirty() {
|
||||
@@ -506,7 +554,7 @@ namespace ts.server {
|
||||
for (const sourceFile of this.program.getSourceFiles()) {
|
||||
this.extractUnresolvedImportsFromSourceFile(sourceFile, result);
|
||||
}
|
||||
this.lastCachedUnresolvedImportsList = toSortedReadonlyArray(result);
|
||||
this.lastCachedUnresolvedImportsList = toSortedArray(result);
|
||||
}
|
||||
unresolvedImports = this.lastCachedUnresolvedImportsList;
|
||||
|
||||
@@ -543,12 +591,12 @@ namespace ts.server {
|
||||
const oldProgram = this.program;
|
||||
this.program = this.languageService.getProgram();
|
||||
|
||||
let hasChanges = false;
|
||||
// bump up the version if
|
||||
// - oldProgram is not set - this is a first time updateGraph is called
|
||||
// - newProgram is different from the old program and structure of the old program was not reused.
|
||||
if (!oldProgram || (this.program !== oldProgram && !(oldProgram.structureIsReused & StructureIsReused.Completely))) {
|
||||
hasChanges = true;
|
||||
const hasChanges = !oldProgram || (this.program !== oldProgram && !(oldProgram.structureIsReused & StructureIsReused.Completely));
|
||||
|
||||
if (hasChanges) {
|
||||
if (oldProgram) {
|
||||
for (const f of oldProgram.getSourceFiles()) {
|
||||
if (this.program.getSourceFileByPath(f.path)) {
|
||||
@@ -561,6 +609,35 @@ namespace ts.server {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const missingFilePaths = this.program.getMissingFilePaths();
|
||||
const missingFilePathsSet = arrayToSet(missingFilePaths);
|
||||
|
||||
// Files that are no longer missing (e.g. because they are no longer required)
|
||||
// should no longer be watched.
|
||||
this.missingFilesMap.forEach((fileWatcher, missingFilePath) => {
|
||||
if (!missingFilePathsSet.has(missingFilePath)) {
|
||||
this.missingFilesMap.delete(missingFilePath);
|
||||
fileWatcher.close();
|
||||
}
|
||||
});
|
||||
|
||||
// Missing files that are not yet watched should be added to the map.
|
||||
for (const missingFilePath of missingFilePaths) {
|
||||
if (!this.missingFilesMap.has(missingFilePath)) {
|
||||
const fileWatcher = this.projectService.host.watchFile(missingFilePath, (_filename: string, eventKind: FileWatcherEventKind) => {
|
||||
if (eventKind === FileWatcherEventKind.Created && this.missingFilesMap.has(missingFilePath)) {
|
||||
fileWatcher.close();
|
||||
this.missingFilesMap.delete(missingFilePath);
|
||||
|
||||
// When a missing file is created, we should update the graph.
|
||||
this.markAsDirty();
|
||||
this.updateGraph();
|
||||
}
|
||||
});
|
||||
this.missingFilesMap.set(missingFilePath, fileWatcher);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const oldExternalFiles = this.externalFiles || emptyArray as SortedReadonlyArray<string>;
|
||||
@@ -583,6 +660,10 @@ namespace ts.server {
|
||||
return hasChanges;
|
||||
}
|
||||
|
||||
isWatchedMissingFile(path: Path) {
|
||||
return this.missingFilesMap.has(path);
|
||||
}
|
||||
|
||||
getScriptInfoLSHost(fileName: string) {
|
||||
const scriptInfo = this.projectService.getOrCreateScriptInfo(fileName, /*openedByClient*/ false);
|
||||
if (scriptInfo) {
|
||||
@@ -609,7 +690,7 @@ namespace ts.server {
|
||||
}
|
||||
let strBuilder = "";
|
||||
for (const file of this.program.getSourceFiles()) {
|
||||
strBuilder += `${file.fileName}\n`;
|
||||
strBuilder += `\t${file.fileName}\n`;
|
||||
}
|
||||
return strBuilder;
|
||||
}
|
||||
@@ -657,11 +738,11 @@ namespace ts.server {
|
||||
if (this.lastReportedFileNames && lastKnownVersion === this.lastReportedVersion) {
|
||||
// if current structure version is the same - return info without any changes
|
||||
if (this.projectStructureVersion === this.lastReportedVersion && !updatedFileNames) {
|
||||
return { info, projectErrors: this.projectErrors };
|
||||
return { info, projectErrors: this.getGlobalProjectErrors() };
|
||||
}
|
||||
// compute and return the difference
|
||||
const lastReportedFileNames = this.lastReportedFileNames;
|
||||
const currentFiles = arrayToMap(this.getFileNames(), x => x);
|
||||
const currentFiles = arrayToSet(this.getFileNames());
|
||||
|
||||
const added: string[] = [];
|
||||
const removed: string[] = [];
|
||||
@@ -679,14 +760,14 @@ namespace ts.server {
|
||||
});
|
||||
this.lastReportedFileNames = currentFiles;
|
||||
this.lastReportedVersion = this.projectStructureVersion;
|
||||
return { info, changes: { added, removed, updated }, projectErrors: this.projectErrors };
|
||||
return { info, changes: { added, removed, updated }, projectErrors: this.getGlobalProjectErrors() };
|
||||
}
|
||||
else {
|
||||
// unknown version - return everything
|
||||
const projectFileNames = this.getFileNames();
|
||||
this.lastReportedFileNames = arrayToMap(projectFileNames, x => x);
|
||||
this.lastReportedFileNames = arrayToSet(projectFileNames);
|
||||
this.lastReportedVersion = this.projectStructureVersion;
|
||||
return { info, files: projectFileNames, projectErrors: this.projectErrors };
|
||||
return { info, files: projectFileNames, projectErrors: this.getGlobalProjectErrors() };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -702,7 +783,7 @@ namespace ts.server {
|
||||
// We need to use a set here since the code can contain the same import twice,
|
||||
// but that will only be one dependency.
|
||||
// To avoid invernal conversion, the key of the referencedFiles map must be of type Path
|
||||
const referencedFiles = createMap<boolean>();
|
||||
const referencedFiles = createMap<true>();
|
||||
if (sourceFile.imports && sourceFile.imports.length > 0) {
|
||||
const checker: TypeChecker = this.program.getTypeChecker();
|
||||
for (const importName of sourceFile.imports) {
|
||||
@@ -746,7 +827,7 @@ namespace ts.server {
|
||||
// remove a root file from project
|
||||
protected removeRoot(info: ScriptInfo): void {
|
||||
orderedRemoveItem(this.rootFiles, info);
|
||||
this.rootFilesMap.remove(info.path);
|
||||
this.rootFilesMap.delete(info.path);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -756,7 +837,7 @@ namespace ts.server {
|
||||
*/
|
||||
export class InferredProject extends Project {
|
||||
|
||||
private static newName = (() => {
|
||||
private static readonly newName = (() => {
|
||||
let nextId = 1;
|
||||
return () => {
|
||||
const id = nextId;
|
||||
@@ -776,7 +857,7 @@ namespace ts.server {
|
||||
|
||||
setCompilerOptions(options?: CompilerOptions) {
|
||||
// Avoid manipulating the given options directly
|
||||
const newOptions = options ? clone(options) : this.getCompilerOptions();
|
||||
const newOptions = options ? cloneCompilerOptions(options) : this.getCompilerOptions();
|
||||
if (!newOptions) {
|
||||
return;
|
||||
}
|
||||
@@ -794,7 +875,7 @@ namespace ts.server {
|
||||
// Used to keep track of what directories are watched for this project
|
||||
directoriesWatchedForTsconfig: string[] = [];
|
||||
|
||||
constructor(projectService: ProjectService, documentRegistry: ts.DocumentRegistry, compilerOptions: CompilerOptions) {
|
||||
constructor(projectService: ProjectService, documentRegistry: DocumentRegistry, compilerOptions: CompilerOptions) {
|
||||
super(InferredProject.newName(),
|
||||
ProjectKind.Inferred,
|
||||
projectService,
|
||||
@@ -855,9 +936,9 @@ namespace ts.server {
|
||||
export class ConfiguredProject extends Project {
|
||||
private typeAcquisition: TypeAcquisition;
|
||||
private projectFileWatcher: FileWatcher;
|
||||
private directoryWatcher: FileWatcher;
|
||||
private directoriesWatchedForWildcards: Map<FileWatcher>;
|
||||
private typeRootsWatchers: FileWatcher[];
|
||||
private directoryWatcher: FileWatcher | undefined;
|
||||
private directoriesWatchedForWildcards: Map<FileWatcher> | undefined;
|
||||
private typeRootsWatchers: FileWatcher[] | undefined;
|
||||
readonly canonicalConfigFilePath: NormalizedPath;
|
||||
|
||||
private plugins: PluginModule[] = [];
|
||||
@@ -867,7 +948,7 @@ namespace ts.server {
|
||||
|
||||
constructor(configFileName: NormalizedPath,
|
||||
projectService: ProjectService,
|
||||
documentRegistry: ts.DocumentRegistry,
|
||||
documentRegistry: DocumentRegistry,
|
||||
hasExplicitListOfFiles: boolean,
|
||||
compilerOptions: CompilerOptions,
|
||||
private wildcardDirectories: Map<WatchDirectoryFlags>,
|
||||
@@ -976,7 +1057,7 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
getExternalFiles(): SortedReadonlyArray<string> {
|
||||
return toSortedReadonlyArray(flatMap(this.plugins, plugin => {
|
||||
return toSortedArray(flatMap(this.plugins, plugin => {
|
||||
if (typeof plugin.getExternalFiles !== "function") return;
|
||||
try {
|
||||
return plugin.getExternalFiles(this);
|
||||
@@ -1043,6 +1124,7 @@ namespace ts.server {
|
||||
|
||||
if (this.projectFileWatcher) {
|
||||
this.projectFileWatcher.close();
|
||||
this.projectFileWatcher = undefined;
|
||||
}
|
||||
|
||||
if (this.typeRootsWatchers) {
|
||||
@@ -1052,10 +1134,12 @@ namespace ts.server {
|
||||
this.typeRootsWatchers = undefined;
|
||||
}
|
||||
|
||||
this.directoriesWatchedForWildcards.forEach(watcher => {
|
||||
watcher.close();
|
||||
});
|
||||
this.directoriesWatchedForWildcards = undefined;
|
||||
if (this.directoriesWatchedForWildcards) {
|
||||
this.directoriesWatchedForWildcards.forEach(watcher => {
|
||||
watcher.close();
|
||||
});
|
||||
this.directoriesWatchedForWildcards = undefined;
|
||||
}
|
||||
|
||||
this.stopWatchingDirectory();
|
||||
}
|
||||
@@ -1070,7 +1154,7 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
getEffectiveTypeRoots() {
|
||||
return ts.getEffectiveTypeRoots(this.getCompilerOptions(), this.projectService.host) || [];
|
||||
return getEffectiveTypeRoots(this.getCompilerOptions(), this.projectService.host) || [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1082,7 +1166,7 @@ namespace ts.server {
|
||||
private typeAcquisition: TypeAcquisition;
|
||||
constructor(public externalProjectName: string,
|
||||
projectService: ProjectService,
|
||||
documentRegistry: ts.DocumentRegistry,
|
||||
documentRegistry: DocumentRegistry,
|
||||
compilerOptions: CompilerOptions,
|
||||
languageServiceEnabled: boolean,
|
||||
public compileOnSaveEnabled: boolean,
|
||||
@@ -1132,4 +1216,4 @@ namespace ts.server {
|
||||
this.typeAcquisition = newTypeAcquisition;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+88
-32
@@ -99,8 +99,11 @@ namespace ts.server.protocol {
|
||||
GetSupportedCodeFixes = "getSupportedCodeFixes",
|
||||
|
||||
GetApplicableRefactors = "getApplicableRefactors",
|
||||
GetRefactorCodeActions = "getRefactorCodeActions",
|
||||
GetRefactorCodeActionsFull = "getRefactorCodeActions-full",
|
||||
GetEditsForRefactor = "getEditsForRefactor",
|
||||
/* @internal */
|
||||
GetEditsForRefactorFull = "getEditsForRefactor-full",
|
||||
|
||||
// NOTE: If updating this, be sure to also update `allCommandNames` in `harness/unittests/session.ts`.
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -417,52 +420,98 @@ namespace ts.server.protocol {
|
||||
|
||||
export type FileLocationOrRangeRequestArgs = FileLocationRequestArgs | FileRangeRequestArgs;
|
||||
|
||||
/**
|
||||
* Request refactorings at a given position or selection area.
|
||||
*/
|
||||
export interface GetApplicableRefactorsRequest extends Request {
|
||||
command: CommandTypes.GetApplicableRefactors;
|
||||
arguments: GetApplicableRefactorsRequestArgs;
|
||||
}
|
||||
|
||||
export type GetApplicableRefactorsRequestArgs = FileLocationOrRangeRequestArgs;
|
||||
|
||||
export interface ApplicableRefactorInfo {
|
||||
name: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Response is a list of available refactorings.
|
||||
* Each refactoring exposes one or more "Actions"; a user selects one action to invoke a refactoring
|
||||
*/
|
||||
export interface GetApplicableRefactorsResponse extends Response {
|
||||
body?: ApplicableRefactorInfo[];
|
||||
}
|
||||
|
||||
export interface GetRefactorCodeActionsRequest extends Request {
|
||||
command: CommandTypes.GetRefactorCodeActions;
|
||||
arguments: GetRefactorCodeActionsRequestArgs;
|
||||
/**
|
||||
* A set of one or more available refactoring actions, grouped under a parent refactoring.
|
||||
*/
|
||||
export interface ApplicableRefactorInfo {
|
||||
/**
|
||||
* The programmatic name of the refactoring
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* A description of this refactoring category to show to the user.
|
||||
* If the refactoring gets inlined (see below), this text will not be visible.
|
||||
*/
|
||||
description: string;
|
||||
/**
|
||||
* Inlineable refactorings can have their actions hoisted out to the top level
|
||||
* of a context menu. Non-inlineanable refactorings should always be shown inside
|
||||
* their parent grouping.
|
||||
*
|
||||
* If not specified, this value is assumed to be 'true'
|
||||
*/
|
||||
inlineable?: boolean;
|
||||
|
||||
actions: RefactorActionInfo[];
|
||||
}
|
||||
|
||||
export type GetRefactorCodeActionsRequestArgs = FileLocationOrRangeRequestArgs & {
|
||||
/* The kind of the applicable refactor */
|
||||
refactorName: string;
|
||||
/**
|
||||
* Represents a single refactoring action - for example, the "Extract Method..." refactor might
|
||||
* offer several actions, each corresponding to a surround class or closure to extract into.
|
||||
*/
|
||||
export type RefactorActionInfo = {
|
||||
/**
|
||||
* The programmatic name of the refactoring action
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* A description of this refactoring action to show to the user.
|
||||
* If the parent refactoring is inlined away, this will be the only text shown,
|
||||
* so this description should make sense by itself if the parent is inlineable=true
|
||||
*/
|
||||
description: string;
|
||||
};
|
||||
|
||||
export type RefactorCodeActions = {
|
||||
actions: protocol.CodeAction[];
|
||||
renameLocation?: number
|
||||
};
|
||||
|
||||
/* @internal */
|
||||
export type RefactorCodeActionsFull = {
|
||||
actions: ts.CodeAction[];
|
||||
renameLocation?: number
|
||||
};
|
||||
|
||||
export interface GetRefactorCodeActionsResponse extends Response {
|
||||
body: RefactorCodeActions;
|
||||
export interface GetEditsForRefactorRequest extends Request {
|
||||
command: CommandTypes.GetEditsForRefactor;
|
||||
arguments: GetEditsForRefactorRequestArgs;
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export interface GetRefactorCodeActionsFullResponse extends Response {
|
||||
body: RefactorCodeActionsFull;
|
||||
/**
|
||||
* Request the edits that a particular refactoring action produces.
|
||||
* Callers must specify the name of the refactor and the name of the action.
|
||||
*/
|
||||
export type GetEditsForRefactorRequestArgs = FileLocationOrRangeRequestArgs & {
|
||||
/* The 'name' property from the refactoring that offered this action */
|
||||
refactor: string;
|
||||
/* The 'name' property from the refactoring action */
|
||||
action: string;
|
||||
};
|
||||
|
||||
|
||||
export interface GetEditsForRefactorResponse extends Response {
|
||||
body?: RefactorEditInfo;
|
||||
}
|
||||
|
||||
export type RefactorEditInfo = {
|
||||
edits: FileCodeEdits[];
|
||||
|
||||
/**
|
||||
* An optional location where the editor should start a rename operation once
|
||||
* the refactoring edits have been applied
|
||||
*/
|
||||
renameLocation?: Location;
|
||||
renameFilename?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Request for the available codefixes at a specific position.
|
||||
*/
|
||||
@@ -607,7 +656,7 @@ namespace ts.server.protocol {
|
||||
}
|
||||
|
||||
/**
|
||||
* Location in source code expressed as (one-based) line and character offset.
|
||||
* Location in source code expressed as (one-based) line and (one-based) column offset.
|
||||
*/
|
||||
export interface Location {
|
||||
line: number;
|
||||
@@ -1910,6 +1959,13 @@ namespace ts.server.protocol {
|
||||
source?: string;
|
||||
}
|
||||
|
||||
export interface DiagnosticWithFileName extends Diagnostic {
|
||||
/**
|
||||
* Name of the file the diagnostic is in
|
||||
*/
|
||||
fileName: string;
|
||||
}
|
||||
|
||||
export interface DiagnosticEventBody {
|
||||
/**
|
||||
* The file for which diagnostic information is reported.
|
||||
@@ -1944,7 +2000,7 @@ namespace ts.server.protocol {
|
||||
/**
|
||||
* An arry of diagnostic information items for the found config file.
|
||||
*/
|
||||
diagnostics: Diagnostic[];
|
||||
diagnostics: DiagnosticWithFileName[];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+15
-34
@@ -61,7 +61,7 @@ namespace ts.server {
|
||||
: ScriptSnapshot.fromString(this.getOrLoadText());
|
||||
}
|
||||
|
||||
public getLineInfo(line: number) {
|
||||
public getLineInfo(line: number): AbsolutePositionAndLineText {
|
||||
return this.switchToScriptVersionCache().getSnapshot().index.lineNumberToInfo(line);
|
||||
}
|
||||
/**
|
||||
@@ -72,19 +72,12 @@ namespace ts.server {
|
||||
const lineMap = this.getLineMap();
|
||||
const start = lineMap[line]; // -1 since line is 1-based
|
||||
const end = line + 1 < lineMap.length ? lineMap[line + 1] : this.text.length;
|
||||
return ts.createTextSpanFromBounds(start, end);
|
||||
return createTextSpanFromBounds(start, end);
|
||||
}
|
||||
const index = this.svc.getSnapshot().index;
|
||||
const lineInfo = index.lineNumberToInfo(line + 1);
|
||||
let len: number;
|
||||
if (lineInfo.leaf) {
|
||||
len = lineInfo.leaf.text.length;
|
||||
}
|
||||
else {
|
||||
const nextLineInfo = index.lineNumberToInfo(line + 2);
|
||||
len = nextLineInfo.offset - lineInfo.offset;
|
||||
}
|
||||
return ts.createTextSpan(lineInfo.offset, len);
|
||||
const { lineText, absolutePosition } = index.lineNumberToInfo(line + 1);
|
||||
const len = lineText !== undefined ? lineText.length : index.absolutePositionOfStartOfLine(line + 2) - absolutePosition;
|
||||
return createTextSpan(absolutePosition, len);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -93,27 +86,19 @@ namespace ts.server {
|
||||
*/
|
||||
lineOffsetToPosition(line: number, offset: number): number {
|
||||
if (!this.svc) {
|
||||
return computePositionOfLineAndCharacter(this.getLineMap(), line - 1, offset - 1);
|
||||
return computePositionOfLineAndCharacter(this.getLineMap(), line - 1, offset - 1, this.text);
|
||||
}
|
||||
const index = this.svc.getSnapshot().index;
|
||||
|
||||
const lineInfo = index.lineNumberToInfo(line);
|
||||
// TODO: assert this offset is actually on the line
|
||||
return (lineInfo.offset + offset - 1);
|
||||
return this.svc.getSnapshot().index.absolutePositionOfStartOfLine(line) + (offset - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param line 1-based index
|
||||
* @param offset 1-based index
|
||||
*/
|
||||
positionToLineOffset(position: number): ILineInfo {
|
||||
positionToLineOffset(position: number): protocol.Location {
|
||||
if (!this.svc) {
|
||||
const { line, character } = computeLineAndCharacterOfPosition(this.getLineMap(), position);
|
||||
return { line: line + 1, offset: character + 1 };
|
||||
}
|
||||
const index = this.svc.getSnapshot().index;
|
||||
const lineOffset = index.charOffsetToLineNumberAndPos(position);
|
||||
return { line: lineOffset.line, offset: lineOffset.offset + 1 };
|
||||
return this.svc.getSnapshot().index.positionToLineOffset(position);
|
||||
}
|
||||
|
||||
private getFileText(tempFileName?: string) {
|
||||
@@ -126,7 +111,7 @@ namespace ts.server {
|
||||
|
||||
private switchToScriptVersionCache(newText?: string): ScriptVersionCache {
|
||||
if (!this.svc) {
|
||||
this.svc = ScriptVersionCache.fromString(this.host, newText !== undefined ? newText : this.getOrLoadText());
|
||||
this.svc = ScriptVersionCache.fromString(newText !== undefined ? newText : this.getOrLoadText());
|
||||
this.svcVersion++;
|
||||
this.text = undefined;
|
||||
}
|
||||
@@ -162,7 +147,7 @@ namespace ts.server {
|
||||
* All projects that include this file
|
||||
*/
|
||||
readonly containingProjects: Project[] = [];
|
||||
private formatCodeSettings: ts.FormatCodeSettings;
|
||||
private formatCodeSettings: FormatCodeSettings;
|
||||
readonly path: Path;
|
||||
|
||||
private fileWatcher: FileWatcher;
|
||||
@@ -247,7 +232,7 @@ namespace ts.server {
|
||||
}
|
||||
break;
|
||||
default:
|
||||
removeItemFromSet(this.containingProjects, project);
|
||||
unorderedRemoveItem(this.containingProjects, project);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -257,7 +242,7 @@ namespace ts.server {
|
||||
// detach is unnecessary since we'll clean the list of containing projects anyways
|
||||
p.removeFile(this, /*detachFromProjects*/ false);
|
||||
}
|
||||
this.containingProjects.length = 0;
|
||||
clear(this.containingProjects);
|
||||
}
|
||||
|
||||
getDefaultProject() {
|
||||
@@ -334,7 +319,7 @@ namespace ts.server {
|
||||
}
|
||||
}
|
||||
|
||||
getLineInfo(line: number) {
|
||||
getLineInfo(line: number): AbsolutePositionAndLineText {
|
||||
return this.textStorage.getLineInfo(line);
|
||||
}
|
||||
|
||||
@@ -364,11 +349,7 @@ namespace ts.server {
|
||||
return this.textStorage.lineOffsetToPosition(line, offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param line 1-based index
|
||||
* @param offset 1-based index
|
||||
*/
|
||||
positionToLineOffset(position: number): ILineInfo {
|
||||
positionToLineOffset(position: number): protocol.Location {
|
||||
return this.textStorage.positionToLineOffset(position);
|
||||
}
|
||||
|
||||
|
||||
+168
-249
@@ -8,15 +8,13 @@ namespace ts.server {
|
||||
export interface LineCollection {
|
||||
charCount(): number;
|
||||
lineCount(): number;
|
||||
isLeaf(): boolean;
|
||||
isLeaf(): this is LineLeaf;
|
||||
walk(rangeStart: number, rangeLength: number, walkFns: ILineIndexWalker): void;
|
||||
}
|
||||
|
||||
export interface ILineInfo {
|
||||
line: number;
|
||||
offset: number;
|
||||
text?: string;
|
||||
leaf?: LineLeaf;
|
||||
export interface AbsolutePositionAndLineText {
|
||||
absolutePosition: number;
|
||||
lineText: string | undefined;
|
||||
}
|
||||
|
||||
export enum CharRangeSection {
|
||||
@@ -33,41 +31,35 @@ namespace ts.server {
|
||||
done: boolean;
|
||||
leaf(relativeStart: number, relativeLength: number, lineCollection: LineLeaf): void;
|
||||
pre?(relativeStart: number, relativeLength: number, lineCollection: LineCollection,
|
||||
parent: LineNode, nodeType: CharRangeSection): LineCollection;
|
||||
parent: LineNode, nodeType: CharRangeSection): void;
|
||||
post?(relativeStart: number, relativeLength: number, lineCollection: LineCollection,
|
||||
parent: LineNode, nodeType: CharRangeSection): LineCollection;
|
||||
parent: LineNode, nodeType: CharRangeSection): void;
|
||||
}
|
||||
|
||||
class BaseLineIndexWalker implements ILineIndexWalker {
|
||||
class EditWalker implements ILineIndexWalker {
|
||||
goSubtree = true;
|
||||
done = false;
|
||||
leaf(_rangeStart: number, _rangeLength: number, _ll: LineLeaf) {
|
||||
}
|
||||
}
|
||||
get done() { return false; }
|
||||
|
||||
class EditWalker extends BaseLineIndexWalker {
|
||||
lineIndex = new LineIndex();
|
||||
readonly lineIndex = new LineIndex();
|
||||
// path to start of range
|
||||
startPath: LineCollection[];
|
||||
endBranch: LineCollection[] = [];
|
||||
branchNode: LineNode;
|
||||
private readonly startPath: LineCollection[];
|
||||
private readonly endBranch: LineCollection[] = [];
|
||||
private branchNode: LineNode;
|
||||
// path to current node
|
||||
stack: LineNode[];
|
||||
state = CharRangeSection.Entire;
|
||||
lineCollectionAtBranch: LineCollection;
|
||||
initialText = "";
|
||||
trailingText = "";
|
||||
suppressTrailingText = false;
|
||||
private readonly stack: LineNode[];
|
||||
private state = CharRangeSection.Entire;
|
||||
private lineCollectionAtBranch: LineCollection;
|
||||
private initialText = "";
|
||||
private trailingText = "";
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.lineIndex.root = new LineNode();
|
||||
this.startPath = [this.lineIndex.root];
|
||||
this.stack = [this.lineIndex.root];
|
||||
}
|
||||
|
||||
insertLines(insertedText: string) {
|
||||
if (this.suppressTrailingText) {
|
||||
insertLines(insertedText: string, suppressTrailingText: boolean) {
|
||||
if (suppressTrailingText) {
|
||||
this.trailingText = "";
|
||||
}
|
||||
if (insertedText) {
|
||||
@@ -80,7 +72,7 @@ namespace ts.server {
|
||||
const lines = lm.lines;
|
||||
if (lines.length > 1) {
|
||||
if (lines[lines.length - 1] === "") {
|
||||
lines.length--;
|
||||
lines.pop();
|
||||
}
|
||||
}
|
||||
let branchParent: LineNode;
|
||||
@@ -103,22 +95,20 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
// path at least length two (root and leaf)
|
||||
let insertionNode = <LineNode>this.startPath[this.startPath.length - 2];
|
||||
const leafNode = <LineLeaf>this.startPath[this.startPath.length - 1];
|
||||
const len = lines.length;
|
||||
|
||||
if (len > 0) {
|
||||
if (lines.length > 0) {
|
||||
leafNode.text = lines[0];
|
||||
|
||||
if (len > 1) {
|
||||
let insertedNodes = <LineCollection[]>new Array(len - 1);
|
||||
if (lines.length > 1) {
|
||||
let insertedNodes = <LineCollection[]>new Array(lines.length - 1);
|
||||
let startNode = <LineCollection>leafNode;
|
||||
for (let i = 1; i < lines.length; i++) {
|
||||
insertedNodes[i - 1] = new LineLeaf(lines[i]);
|
||||
}
|
||||
let pathIndex = this.startPath.length - 2;
|
||||
while (pathIndex >= 0) {
|
||||
insertionNode = <LineNode>this.startPath[pathIndex];
|
||||
const insertionNode = <LineNode>this.startPath[pathIndex];
|
||||
insertedNodes = insertionNode.insertAt(startNode, insertedNodes);
|
||||
pathIndex--;
|
||||
startNode = insertionNode;
|
||||
@@ -140,6 +130,7 @@ namespace ts.server {
|
||||
}
|
||||
}
|
||||
else {
|
||||
const insertionNode = <LineNode>this.startPath[this.startPath.length - 2];
|
||||
// no content for leaf node, so delete it
|
||||
insertionNode.remove(leafNode);
|
||||
for (let j = this.startPath.length - 2; j >= 0; j--) {
|
||||
@@ -150,18 +141,17 @@ namespace ts.server {
|
||||
return this.lineIndex;
|
||||
}
|
||||
|
||||
post(_relativeStart: number, _relativeLength: number, lineCollection: LineCollection): LineCollection {
|
||||
post(_relativeStart: number, _relativeLength: number, lineCollection: LineCollection): void {
|
||||
// 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;
|
||||
this.stack.pop();
|
||||
}
|
||||
|
||||
pre(_relativeStart: number, _relativeLength: number, lineCollection: LineCollection, _parent: LineCollection, nodeType: CharRangeSection) {
|
||||
pre(_relativeStart: number, _relativeLength: number, lineCollection: LineCollection, _parent: LineCollection, nodeType: CharRangeSection): void {
|
||||
// currentNode corresponds to parent, but in the new tree
|
||||
const currentNode = this.stack[this.stack.length - 1];
|
||||
|
||||
@@ -193,20 +183,20 @@ namespace ts.server {
|
||||
else {
|
||||
child = fresh(lineCollection);
|
||||
currentNode.add(child);
|
||||
this.startPath[this.startPath.length] = child;
|
||||
this.startPath.push(child);
|
||||
}
|
||||
break;
|
||||
case CharRangeSection.Entire:
|
||||
if (this.state !== CharRangeSection.End) {
|
||||
child = fresh(lineCollection);
|
||||
currentNode.add(child);
|
||||
this.startPath[this.startPath.length] = child;
|
||||
this.startPath.push(child);
|
||||
}
|
||||
else {
|
||||
if (!lineCollection.isLeaf()) {
|
||||
child = fresh(lineCollection);
|
||||
currentNode.add(child);
|
||||
this.endBranch[this.endBranch.length] = child;
|
||||
this.endBranch.push(child);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -221,7 +211,7 @@ namespace ts.server {
|
||||
if (!lineCollection.isLeaf()) {
|
||||
child = fresh(lineCollection);
|
||||
currentNode.add(child);
|
||||
this.endBranch[this.endBranch.length] = child;
|
||||
this.endBranch.push(child);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -233,9 +223,8 @@ namespace ts.server {
|
||||
break;
|
||||
}
|
||||
if (this.goSubtree) {
|
||||
this.stack[this.stack.length] = <LineNode>child;
|
||||
this.stack.push(<LineNode>child);
|
||||
}
|
||||
return lineCollection;
|
||||
}
|
||||
// just gather text from the leaves
|
||||
leaf(relativeStart: number, relativeLength: number, ll: LineLeaf) {
|
||||
@@ -259,22 +248,21 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
getTextChangeRange() {
|
||||
return ts.createTextChangeRange(ts.createTextSpan(this.pos, this.deleteLen),
|
||||
return createTextChangeRange(createTextSpan(this.pos, this.deleteLen),
|
||||
this.insertedText ? this.insertedText.length : 0);
|
||||
}
|
||||
}
|
||||
|
||||
export class ScriptVersionCache {
|
||||
changes: TextChange[] = [];
|
||||
versions: LineIndexSnapshot[] = new Array<LineIndexSnapshot>(ScriptVersionCache.maxVersions);
|
||||
minVersion = 0; // no versions earlier than min version will maintain change history
|
||||
private changes: TextChange[] = [];
|
||||
private readonly versions: LineIndexSnapshot[] = new Array<LineIndexSnapshot>(ScriptVersionCache.maxVersions);
|
||||
private minVersion = 0; // no versions earlier than min version will maintain change history
|
||||
|
||||
private host: ServerHost;
|
||||
private currentVersion = 0;
|
||||
|
||||
static changeNumberThreshold = 8;
|
||||
static changeLengthThreshold = 256;
|
||||
static maxVersions = 8;
|
||||
private static readonly changeNumberThreshold = 8;
|
||||
private static readonly changeLengthThreshold = 256;
|
||||
private static readonly maxVersions = 8;
|
||||
|
||||
private versionToIndex(version: number) {
|
||||
if (version < this.minVersion || version > this.currentVersion) {
|
||||
@@ -289,10 +277,10 @@ namespace ts.server {
|
||||
|
||||
// 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.changes.push(new TextChange(pos, deleteLen, insertedText));
|
||||
if (this.changes.length > ScriptVersionCache.changeNumberThreshold ||
|
||||
deleteLen > ScriptVersionCache.changeLengthThreshold ||
|
||||
insertedText && insertedText.length > ScriptVersionCache.changeLengthThreshold) {
|
||||
this.getSnapshot();
|
||||
}
|
||||
}
|
||||
@@ -308,21 +296,11 @@ namespace ts.server {
|
||||
return this.currentVersion;
|
||||
}
|
||||
|
||||
reloadFromFile(filename: string) {
|
||||
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);
|
||||
}
|
||||
|
||||
// reload whole script, leaving no change history behind reload
|
||||
reload(script: string) {
|
||||
this.currentVersion++;
|
||||
this.changes = []; // history wiped out by reload
|
||||
const snap = new LineIndexSnapshot(this.currentVersion, this);
|
||||
const snap = new LineIndexSnapshot(this.currentVersion, this, new LineIndex());
|
||||
|
||||
// delete all versions
|
||||
for (let i = 0; i < this.versions.length; i++) {
|
||||
@@ -330,7 +308,6 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
this.versions[this.currentVersionToIndex()] = snap;
|
||||
snap.index = new LineIndex();
|
||||
const lm = LineIndex.linesFromText(script);
|
||||
snap.index.load(lm.lines);
|
||||
|
||||
@@ -344,9 +321,7 @@ namespace ts.server {
|
||||
for (const change of this.changes) {
|
||||
snapIndex = snapIndex.edit(change.pos, change.deleteLen, change.insertedText);
|
||||
}
|
||||
snap = new LineIndexSnapshot(this.currentVersion + 1, this);
|
||||
snap.index = snapIndex;
|
||||
snap.changesSincePreviousVersion = this.changes;
|
||||
snap = new LineIndexSnapshot(this.currentVersion + 1, this, snapIndex, this.changes);
|
||||
|
||||
this.currentVersion = snap.version;
|
||||
this.versions[this.currentVersionToIndex()] = snap;
|
||||
@@ -362,41 +337,36 @@ namespace ts.server {
|
||||
getTextChangesBetweenVersions(oldVersion: number, newVersion: number) {
|
||||
if (oldVersion < newVersion) {
|
||||
if (oldVersion >= this.minVersion) {
|
||||
const textChangeRanges: ts.TextChangeRange[] = [];
|
||||
const textChangeRanges: TextChangeRange[] = [];
|
||||
for (let i = oldVersion + 1; i <= newVersion; i++) {
|
||||
const snap = this.versions[this.versionToIndex(i)];
|
||||
for (const textChange of snap.changesSincePreviousVersion) {
|
||||
textChangeRanges[textChangeRanges.length] = textChange.getTextChangeRange();
|
||||
textChangeRanges.push(textChange.getTextChangeRange());
|
||||
}
|
||||
}
|
||||
return ts.collapseTextChangeRangesAcrossMultipleVersions(textChangeRanges);
|
||||
return collapseTextChangeRangesAcrossMultipleVersions(textChangeRanges);
|
||||
}
|
||||
else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return ts.unchangedTextChangeRange;
|
||||
return unchangedTextChangeRange;
|
||||
}
|
||||
}
|
||||
|
||||
static fromString(host: ServerHost, script: string) {
|
||||
static fromString(script: string) {
|
||||
const svc = new ScriptVersionCache();
|
||||
const snap = new LineIndexSnapshot(0, svc);
|
||||
const snap = new LineIndexSnapshot(0, svc, new LineIndex());
|
||||
svc.versions[svc.currentVersion] = snap;
|
||||
svc.host = host;
|
||||
snap.index = new LineIndex();
|
||||
const lm = LineIndex.linesFromText(script);
|
||||
snap.index.load(lm.lines);
|
||||
return svc;
|
||||
}
|
||||
}
|
||||
|
||||
export class LineIndexSnapshot implements ts.IScriptSnapshot {
|
||||
index: LineIndex;
|
||||
changesSincePreviousVersion: TextChange[] = [];
|
||||
|
||||
constructor(readonly version: number, readonly cache: ScriptVersionCache) {
|
||||
export class LineIndexSnapshot implements IScriptSnapshot {
|
||||
constructor(readonly version: number, readonly cache: ScriptVersionCache, readonly index: LineIndex, readonly changesSincePreviousVersion: ReadonlyArray<TextChange> = emptyArray) {
|
||||
}
|
||||
|
||||
getText(rangeStart: number, rangeEnd: number) {
|
||||
@@ -407,37 +377,14 @@ namespace ts.server {
|
||||
return this.index.root.charCount();
|
||||
}
|
||||
|
||||
// this requires linear space so don't hold on to these
|
||||
getLineStartPositions(): number[] {
|
||||
const starts: number[] = [-1];
|
||||
let count = 1;
|
||||
let pos = 0;
|
||||
this.index.every(ll => {
|
||||
starts[count] = pos;
|
||||
count++;
|
||||
pos += ll.text.length;
|
||||
return true;
|
||||
}, 0);
|
||||
return starts;
|
||||
}
|
||||
|
||||
getLineMapper() {
|
||||
return (line: number) => {
|
||||
return this.index.lineNumberToInfo(line).offset;
|
||||
};
|
||||
}
|
||||
|
||||
getTextChangeRangeSinceVersion(scriptVersion: number) {
|
||||
if (this.version <= scriptVersion) {
|
||||
return ts.unchangedTextChangeRange;
|
||||
}
|
||||
else {
|
||||
return this.cache.getTextChangesBetweenVersions(scriptVersion, this.version);
|
||||
}
|
||||
}
|
||||
getChangeRange(oldSnapshot: ts.IScriptSnapshot): ts.TextChangeRange {
|
||||
getChangeRange(oldSnapshot: IScriptSnapshot): TextChangeRange {
|
||||
if (oldSnapshot instanceof LineIndexSnapshot && this.cache === oldSnapshot.cache) {
|
||||
return this.getTextChangeRangeSinceVersion(oldSnapshot.version);
|
||||
if (this.version <= oldSnapshot.version) {
|
||||
return unchangedTextChangeRange;
|
||||
}
|
||||
else {
|
||||
return this.cache.getTextChangesBetweenVersions(oldSnapshot.version, this.version);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -447,22 +394,27 @@ namespace ts.server {
|
||||
// set this to true to check each edit for accuracy
|
||||
checkEdits = false;
|
||||
|
||||
charOffsetToLineNumberAndPos(charOffset: number) {
|
||||
return this.root.charOffsetToLineNumberAndPos(1, charOffset);
|
||||
absolutePositionOfStartOfLine(oneBasedLine: number): number {
|
||||
return this.lineNumberToInfo(oneBasedLine).absolutePosition;
|
||||
}
|
||||
|
||||
lineNumberToInfo(lineNumber: number): ILineInfo {
|
||||
positionToLineOffset(position: number): protocol.Location {
|
||||
const { oneBasedLine, zeroBasedColumn } = this.root.charOffsetToLineInfo(1, position);
|
||||
return { line: oneBasedLine, offset: zeroBasedColumn + 1 };
|
||||
}
|
||||
|
||||
private positionToColumnAndLineText(position: number): { zeroBasedColumn: number, lineText: string } {
|
||||
return this.root.charOffsetToLineInfo(1, position);
|
||||
}
|
||||
|
||||
lineNumberToInfo(oneBasedLine: number): AbsolutePositionAndLineText {
|
||||
const lineCount = this.root.lineCount();
|
||||
if (lineNumber <= lineCount) {
|
||||
const lineInfo = this.root.lineNumberToInfo(lineNumber, 0);
|
||||
lineInfo.line = lineNumber;
|
||||
return lineInfo;
|
||||
if (oneBasedLine <= lineCount) {
|
||||
const { position, leaf } = this.root.lineNumberToInfo(oneBasedLine, 0);
|
||||
return { absolutePosition: position, lineText: leaf && leaf.text };
|
||||
}
|
||||
else {
|
||||
return {
|
||||
line: lineNumber,
|
||||
offset: this.root.charCount()
|
||||
};
|
||||
return { absolutePosition: this.root.charCount(), lineText: undefined };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -508,7 +460,7 @@ namespace ts.server {
|
||||
const walkFns = {
|
||||
goSubtree: true,
|
||||
done: false,
|
||||
leaf: function (this: ILineIndexWalker, relativeStart: number, relativeLength: number, ll: LineLeaf) {
|
||||
leaf(this: ILineIndexWalker, relativeStart: number, relativeLength: number, ll: LineLeaf) {
|
||||
if (!f(ll, relativeStart, relativeLength)) {
|
||||
this.done = true;
|
||||
}
|
||||
@@ -518,12 +470,9 @@ namespace ts.server {
|
||||
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);
|
||||
}
|
||||
edit(pos: number, deleteLength: number, newText?: string): LineIndex {
|
||||
if (this.root.charCount() === 0) {
|
||||
// TODO: assert deleteLength === 0
|
||||
Debug.assert(deleteLength === 0); // Can't delete from empty document
|
||||
if (newText !== undefined) {
|
||||
this.load(LineIndex.linesFromText(newText).lines);
|
||||
return this;
|
||||
@@ -532,9 +481,11 @@ namespace ts.server {
|
||||
else {
|
||||
let checkText: string;
|
||||
if (this.checkEdits) {
|
||||
checkText = editFlat(this.getText(0, this.root.charCount()), pos, deleteLength, newText);
|
||||
const source = this.getText(0, this.root.charCount());
|
||||
checkText = source.slice(0, pos) + newText + source.slice(pos + deleteLength);
|
||||
}
|
||||
const walker = new EditWalker();
|
||||
let suppressTrailingText = false;
|
||||
if (pos >= this.root.charCount()) {
|
||||
// insert at end
|
||||
pos = this.root.charCount() - 1;
|
||||
@@ -546,93 +497,77 @@ namespace ts.server {
|
||||
newText = endString;
|
||||
}
|
||||
deleteLength = 0;
|
||||
walker.suppressTrailingText = true;
|
||||
suppressTrailingText = true;
|
||||
}
|
||||
else if (deleteLength > 0) {
|
||||
// check whether last characters deleted are line break
|
||||
const e = pos + deleteLength;
|
||||
const lineInfo = this.charOffsetToLineNumberAndPos(e);
|
||||
if ((lineInfo && (lineInfo.offset === 0))) {
|
||||
const { zeroBasedColumn, lineText } = this.positionToColumnAndLineText(e);
|
||||
if (zeroBasedColumn === 0) {
|
||||
// move range end just past line that will merge with previous line
|
||||
deleteLength += lineInfo.text.length;
|
||||
deleteLength += lineText.length;
|
||||
// store text by appending to end of insertedText
|
||||
if (newText) {
|
||||
newText = newText + lineInfo.text;
|
||||
}
|
||||
else {
|
||||
newText = lineInfo.text;
|
||||
}
|
||||
newText = newText ? newText + lineText : lineText;
|
||||
}
|
||||
}
|
||||
if (pos < this.root.charCount()) {
|
||||
this.root.walk(pos, deleteLength, walker);
|
||||
walker.insertLines(newText);
|
||||
}
|
||||
|
||||
this.root.walk(pos, deleteLength, walker);
|
||||
walker.insertLines(newText, suppressTrailingText);
|
||||
|
||||
if (this.checkEdits) {
|
||||
const updatedText = this.getText(0, this.root.charCount());
|
||||
const updatedText = walker.lineIndex.getText(0, walker.lineIndex.getLength());
|
||||
Debug.assert(checkText === updatedText, "buffer edit mismatch");
|
||||
}
|
||||
|
||||
return walker.lineIndex;
|
||||
}
|
||||
}
|
||||
|
||||
static buildTreeFromBottom(nodes: LineCollection[]): LineNode {
|
||||
const nodeCount = Math.ceil(nodes.length / lineCollectionCapacity);
|
||||
const interiorNodes: LineNode[] = [];
|
||||
private static buildTreeFromBottom(nodes: LineCollection[]): LineNode {
|
||||
if (nodes.length < lineCollectionCapacity) {
|
||||
return new LineNode(nodes);
|
||||
}
|
||||
|
||||
const interiorNodes = new Array<LineNode>(Math.ceil(nodes.length / lineCollectionCapacity));
|
||||
let nodeIndex = 0;
|
||||
for (let i = 0; i < nodeCount; i++) {
|
||||
interiorNodes[i] = new LineNode();
|
||||
let charCount = 0;
|
||||
let lineCount = 0;
|
||||
for (let 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);
|
||||
for (let i = 0; i < interiorNodes.length; i++) {
|
||||
const end = Math.min(nodeIndex + lineCollectionCapacity, nodes.length);
|
||||
interiorNodes[i] = new LineNode(nodes.slice(nodeIndex, end));
|
||||
nodeIndex = end;
|
||||
}
|
||||
return this.buildTreeFromBottom(interiorNodes);
|
||||
}
|
||||
|
||||
static linesFromText(text: string) {
|
||||
const lineStarts = ts.computeLineStarts(text);
|
||||
const lineMap = computeLineStarts(text);
|
||||
|
||||
if (lineStarts.length === 0) {
|
||||
return { lines: <string[]>[], lineMap: lineStarts };
|
||||
if (lineMap.length === 0) {
|
||||
return { lines: <string[]>[], lineMap };
|
||||
}
|
||||
const lines = <string[]>new Array(lineStarts.length);
|
||||
const lc = lineStarts.length - 1;
|
||||
const lines = <string[]>new Array(lineMap.length);
|
||||
const lc = lineMap.length - 1;
|
||||
for (let lmi = 0; lmi < lc; lmi++) {
|
||||
lines[lmi] = text.substring(lineStarts[lmi], lineStarts[lmi + 1]);
|
||||
lines[lmi] = text.substring(lineMap[lmi], lineMap[lmi + 1]);
|
||||
}
|
||||
|
||||
const endText = text.substring(lineStarts[lc]);
|
||||
const endText = text.substring(lineMap[lc]);
|
||||
if (endText.length > 0) {
|
||||
lines[lc] = endText;
|
||||
}
|
||||
else {
|
||||
lines.length--;
|
||||
lines.pop();
|
||||
}
|
||||
return { lines: lines, lineMap: lineStarts };
|
||||
return { lines, lineMap };
|
||||
}
|
||||
}
|
||||
|
||||
export class LineNode implements LineCollection {
|
||||
totalChars = 0;
|
||||
totalLines = 0;
|
||||
children: LineCollection[] = [];
|
||||
|
||||
constructor(private readonly children: LineCollection[] = []) {
|
||||
if (children.length) this.updateCounts();
|
||||
}
|
||||
|
||||
isLeaf() {
|
||||
return false;
|
||||
@@ -647,7 +582,7 @@ namespace ts.server {
|
||||
}
|
||||
}
|
||||
|
||||
execWalk(rangeStart: number, rangeLength: number, walkFns: ILineIndexWalker, childIndex: number, nodeType: CharRangeSection) {
|
||||
private execWalk(rangeStart: number, rangeLength: number, walkFns: ILineIndexWalker, childIndex: number, nodeType: CharRangeSection) {
|
||||
if (walkFns.pre) {
|
||||
walkFns.pre(rangeStart, rangeLength, this.children[childIndex], this, nodeType);
|
||||
}
|
||||
@@ -663,7 +598,7 @@ namespace ts.server {
|
||||
return walkFns.done;
|
||||
}
|
||||
|
||||
skipChild(relativeStart: number, relativeLength: number, childIndex: number, walkFns: ILineIndexWalker, nodeType: CharRangeSection) {
|
||||
private 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;
|
||||
@@ -673,16 +608,14 @@ namespace ts.server {
|
||||
walk(rangeStart: number, rangeLength: number, walkFns: ILineIndexWalker) {
|
||||
// assume (rangeStart < this.totalChars) && (rangeLength <= this.totalChars)
|
||||
let childIndex = 0;
|
||||
let child = this.children[0];
|
||||
let childCharCount = child.charCount();
|
||||
let childCharCount = this.children[childIndex].charCount();
|
||||
// find sub-tree containing start
|
||||
let adjustedStart = rangeStart;
|
||||
while (adjustedStart >= childCharCount) {
|
||||
this.skipChild(adjustedStart, rangeLength, childIndex, walkFns, CharRangeSection.PreStart);
|
||||
adjustedStart -= childCharCount;
|
||||
childIndex++;
|
||||
child = this.children[childIndex];
|
||||
childCharCount = child.charCount();
|
||||
childCharCount = this.children[childIndex].charCount();
|
||||
}
|
||||
// Case I: both start and end of range in same subtree
|
||||
if ((adjustedStart + rangeLength) <= childCharCount) {
|
||||
@@ -697,7 +630,7 @@ namespace ts.server {
|
||||
}
|
||||
let adjustedLength = rangeLength - (childCharCount - adjustedStart);
|
||||
childIndex++;
|
||||
child = this.children[childIndex];
|
||||
const child = this.children[childIndex];
|
||||
childCharCount = child.charCount();
|
||||
while (adjustedLength > childCharCount) {
|
||||
if (this.execWalk(0, childCharCount, walkFns, childIndex, CharRangeSection.Mid)) {
|
||||
@@ -705,8 +638,7 @@ namespace ts.server {
|
||||
}
|
||||
adjustedLength -= childCharCount;
|
||||
childIndex++;
|
||||
child = this.children[childIndex];
|
||||
childCharCount = child.charCount();
|
||||
childCharCount = this.children[childIndex].charCount();
|
||||
}
|
||||
if (adjustedLength > 0) {
|
||||
if (this.execWalk(0, adjustedLength, walkFns, childIndex, CharRangeSection.End)) {
|
||||
@@ -725,103 +657,91 @@ namespace ts.server {
|
||||
}
|
||||
}
|
||||
|
||||
charOffsetToLineNumberAndPos(lineNumber: number, charOffset: number): ILineInfo {
|
||||
const childInfo = this.childFromCharOffset(lineNumber, charOffset);
|
||||
// Input position is relative to the start of this node.
|
||||
// Output line number is absolute.
|
||||
charOffsetToLineInfo(lineNumberAccumulator: number, relativePosition: number): { oneBasedLine: number, zeroBasedColumn: number, lineText: string | undefined } {
|
||||
const childInfo = this.childFromCharOffset(lineNumberAccumulator, relativePosition);
|
||||
if (!childInfo.child) {
|
||||
return {
|
||||
line: lineNumber,
|
||||
offset: charOffset,
|
||||
oneBasedLine: lineNumberAccumulator,
|
||||
zeroBasedColumn: relativePosition,
|
||||
lineText: undefined,
|
||||
};
|
||||
}
|
||||
else if (childInfo.childIndex < this.children.length) {
|
||||
if (childInfo.child.isLeaf()) {
|
||||
return {
|
||||
line: childInfo.lineNumber,
|
||||
offset: childInfo.charOffset,
|
||||
text: (<LineLeaf>(childInfo.child)).text,
|
||||
leaf: (<LineLeaf>(childInfo.child))
|
||||
oneBasedLine: childInfo.lineNumberAccumulator,
|
||||
zeroBasedColumn: childInfo.relativePosition,
|
||||
lineText: childInfo.child.text,
|
||||
};
|
||||
}
|
||||
else {
|
||||
const lineNode = <LineNode>(childInfo.child);
|
||||
return lineNode.charOffsetToLineNumberAndPos(childInfo.lineNumber, childInfo.charOffset);
|
||||
return lineNode.charOffsetToLineInfo(childInfo.lineNumberAccumulator, childInfo.relativePosition);
|
||||
}
|
||||
}
|
||||
else {
|
||||
const lineInfo = this.lineNumberToInfo(this.lineCount(), 0);
|
||||
return { line: this.lineCount(), offset: lineInfo.leaf.charCount() };
|
||||
return { oneBasedLine: this.lineCount(), zeroBasedColumn: lineInfo.leaf.charCount(), lineText: undefined };
|
||||
}
|
||||
}
|
||||
|
||||
lineNumberToInfo(lineNumber: number, charOffset: number): ILineInfo {
|
||||
const childInfo = this.childFromLineNumber(lineNumber, charOffset);
|
||||
lineNumberToInfo(relativeOneBasedLine: number, positionAccumulator: number): { position: number, leaf: LineLeaf | undefined } {
|
||||
const childInfo = this.childFromLineNumber(relativeOneBasedLine, positionAccumulator);
|
||||
if (!childInfo.child) {
|
||||
return {
|
||||
line: lineNumber,
|
||||
offset: charOffset
|
||||
};
|
||||
return { position: positionAccumulator, leaf: undefined };
|
||||
}
|
||||
else if (childInfo.child.isLeaf()) {
|
||||
return {
|
||||
line: lineNumber,
|
||||
offset: childInfo.charOffset,
|
||||
text: (<LineLeaf>(childInfo.child)).text,
|
||||
leaf: (<LineLeaf>(childInfo.child))
|
||||
};
|
||||
return { position: childInfo.positionAccumulator, leaf: childInfo.child };
|
||||
}
|
||||
else {
|
||||
const lineNode = <LineNode>(childInfo.child);
|
||||
return lineNode.lineNumberToInfo(childInfo.relativeLineNumber, childInfo.charOffset);
|
||||
return lineNode.lineNumberToInfo(childInfo.relativeOneBasedLine, childInfo.positionAccumulator);
|
||||
}
|
||||
}
|
||||
|
||||
childFromLineNumber(lineNumber: number, charOffset: number) {
|
||||
/**
|
||||
* Input line number is relative to the start of this node.
|
||||
* Output line number is relative to the child.
|
||||
* positionAccumulator will be an absolute position once relativeLineNumber reaches 0.
|
||||
*/
|
||||
private childFromLineNumber(relativeOneBasedLine: number, positionAccumulator: number): { child: LineCollection, relativeOneBasedLine: number, positionAccumulator: number } {
|
||||
let child: LineCollection;
|
||||
let relativeLineNumber = lineNumber;
|
||||
let i: number;
|
||||
let len: number;
|
||||
for (i = 0, len = this.children.length; i < len; i++) {
|
||||
for (i = 0; i < this.children.length; i++) {
|
||||
child = this.children[i];
|
||||
const childLineCount = child.lineCount();
|
||||
if (childLineCount >= relativeLineNumber) {
|
||||
if (childLineCount >= relativeOneBasedLine) {
|
||||
break;
|
||||
}
|
||||
else {
|
||||
relativeLineNumber -= childLineCount;
|
||||
charOffset += child.charCount();
|
||||
relativeOneBasedLine -= childLineCount;
|
||||
positionAccumulator += child.charCount();
|
||||
}
|
||||
}
|
||||
return {
|
||||
child: child,
|
||||
childIndex: i,
|
||||
relativeLineNumber: relativeLineNumber,
|
||||
charOffset: charOffset
|
||||
};
|
||||
return { child, relativeOneBasedLine, positionAccumulator };
|
||||
}
|
||||
|
||||
childFromCharOffset(lineNumber: number, charOffset: number) {
|
||||
private childFromCharOffset(lineNumberAccumulator: number, relativePosition: number
|
||||
): { child: LineCollection, childIndex: number, relativePosition: number, lineNumberAccumulator: number } {
|
||||
let child: LineCollection;
|
||||
let i: number;
|
||||
let len: number;
|
||||
for (i = 0, len = this.children.length; i < len; i++) {
|
||||
child = this.children[i];
|
||||
if (child.charCount() > charOffset) {
|
||||
if (child.charCount() > relativePosition) {
|
||||
break;
|
||||
}
|
||||
else {
|
||||
charOffset -= child.charCount();
|
||||
lineNumber += child.lineCount();
|
||||
relativePosition -= child.charCount();
|
||||
lineNumberAccumulator += child.lineCount();
|
||||
}
|
||||
}
|
||||
return {
|
||||
child: child,
|
||||
childIndex: i,
|
||||
charOffset: charOffset,
|
||||
lineNumber: lineNumber
|
||||
};
|
||||
return { child, childIndex: i, relativePosition, lineNumberAccumulator };
|
||||
}
|
||||
|
||||
splitAfter(childIndex: number) {
|
||||
private splitAfter(childIndex: number) {
|
||||
let splitNode: LineNode;
|
||||
const clen = this.children.length;
|
||||
childIndex++;
|
||||
@@ -846,13 +766,12 @@ namespace ts.server {
|
||||
this.children[i] = this.children[i + 1];
|
||||
}
|
||||
}
|
||||
this.children.length--;
|
||||
this.children.pop();
|
||||
}
|
||||
|
||||
findChildIndex(child: LineCollection) {
|
||||
let childIndex = 0;
|
||||
const clen = this.children.length;
|
||||
while ((this.children[childIndex] !== child) && (childIndex < clen)) childIndex++;
|
||||
private findChildIndex(child: LineCollection) {
|
||||
const childIndex = this.children.indexOf(child);
|
||||
Debug.assert(childIndex !== -1);
|
||||
return childIndex;
|
||||
}
|
||||
|
||||
@@ -895,12 +814,12 @@ namespace ts.server {
|
||||
}
|
||||
for (let i = splitNodes.length - 1; i >= 0; i--) {
|
||||
if (splitNodes[i].children.length === 0) {
|
||||
splitNodes.length--;
|
||||
splitNodes.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (shiftNode) {
|
||||
splitNodes[splitNodes.length] = shiftNode;
|
||||
splitNodes.push(shiftNode);
|
||||
}
|
||||
this.updateCounts();
|
||||
for (let i = 0; i < splitNodeCount; i++) {
|
||||
@@ -911,9 +830,9 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
// 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);
|
||||
add(collection: LineCollection): void {
|
||||
this.children.push(collection);
|
||||
Debug.assert(this.children.length <= lineCollectionCapacity);
|
||||
}
|
||||
|
||||
charCount() {
|
||||
|
||||
+29
-17
@@ -43,7 +43,7 @@ namespace ts.server {
|
||||
process.env.USERPROFILE ||
|
||||
(process.env.HOMEDRIVE && process.env.HOMEPATH && normalizeSlashes(process.env.HOMEDRIVE + process.env.HOMEPATH)) ||
|
||||
os.tmpdir();
|
||||
return combinePaths(normalizeSlashes(basePath), "Microsoft/TypeScript");
|
||||
return combinePaths(combinePaths(normalizeSlashes(basePath), "Microsoft/TypeScript"), versionMajorMinor);
|
||||
}
|
||||
case "openbsd":
|
||||
case "freebsd":
|
||||
@@ -51,7 +51,7 @@ namespace ts.server {
|
||||
case "linux":
|
||||
case "android": {
|
||||
const cacheLocation = getNonWindowsCacheLocation(process.platform === "darwin");
|
||||
return combinePaths(cacheLocation, "typescript");
|
||||
return combinePaths(combinePaths(cacheLocation, "typescript"), versionMajorMinor);
|
||||
}
|
||||
default:
|
||||
Debug.fail(`unsupported platform '${process.platform}'`);
|
||||
@@ -138,7 +138,7 @@ namespace ts.server {
|
||||
terminal: false,
|
||||
});
|
||||
|
||||
class Logger implements ts.server.Logger {
|
||||
class Logger implements server.Logger {
|
||||
private fd = -1;
|
||||
private seq = 0;
|
||||
private inGroup = false;
|
||||
@@ -200,7 +200,7 @@ namespace ts.server {
|
||||
|
||||
msg(s: string, type: Msg.Types = Msg.Err) {
|
||||
if (this.fd >= 0 || this.traceToConsole) {
|
||||
s = s + "\n";
|
||||
s = `[${nowString()}] ${s}\n`;
|
||||
const prefix = Logger.padStringRight(type + " " + this.seq.toString(), " ");
|
||||
if (this.firstInGroup) {
|
||||
s = prefix + s;
|
||||
@@ -222,6 +222,12 @@ namespace ts.server {
|
||||
}
|
||||
}
|
||||
|
||||
// E.g. "12:34:56.789"
|
||||
function nowString() {
|
||||
const d = new Date();
|
||||
return `${d.getHours()}:${d.getMinutes()}:${d.getSeconds()}.${d.getMilliseconds()}`;
|
||||
}
|
||||
|
||||
class NodeTypingsInstaller implements ITypingsInstaller {
|
||||
private installer: NodeChildProcess;
|
||||
private installerPidReported = false;
|
||||
@@ -510,6 +516,7 @@ namespace ts.server {
|
||||
const watchedFiles: WatchedFile[] = [];
|
||||
let nextFileToCheck = 0;
|
||||
let watchTimer: any;
|
||||
return { getModifiedTime, poll, startWatchTimer, addFile, removeFile };
|
||||
|
||||
function getModifiedTime(fileName: string): Date {
|
||||
return fs.statSync(fileName).mtime;
|
||||
@@ -523,11 +530,20 @@ namespace ts.server {
|
||||
|
||||
fs.stat(watchedFile.fileName, (err: any, stats: any) => {
|
||||
if (err) {
|
||||
watchedFile.callback(watchedFile.fileName);
|
||||
watchedFile.callback(watchedFile.fileName, FileWatcherEventKind.Changed);
|
||||
}
|
||||
else if (watchedFile.mtime.getTime() !== stats.mtime.getTime()) {
|
||||
watchedFile.mtime = getModifiedTime(watchedFile.fileName);
|
||||
watchedFile.callback(watchedFile.fileName, watchedFile.mtime.getTime() === 0);
|
||||
else {
|
||||
const oldTime = watchedFile.mtime.getTime();
|
||||
const newTime = stats.mtime.getTime();
|
||||
if (oldTime !== newTime) {
|
||||
watchedFile.mtime = stats.mtime;
|
||||
const eventKind = oldTime === 0
|
||||
? FileWatcherEventKind.Created
|
||||
: newTime === 0
|
||||
? FileWatcherEventKind.Deleted
|
||||
: FileWatcherEventKind.Changed;
|
||||
watchedFile.callback(watchedFile.fileName, eventKind);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -559,7 +575,9 @@ namespace ts.server {
|
||||
const file: WatchedFile = {
|
||||
fileName,
|
||||
callback,
|
||||
mtime: getModifiedTime(fileName)
|
||||
mtime: sys.fileExists(fileName)
|
||||
? getModifiedTime(fileName)
|
||||
: new Date(0) // Any subsequent modification will occur after this time
|
||||
};
|
||||
|
||||
watchedFiles.push(file);
|
||||
@@ -572,14 +590,6 @@ namespace ts.server {
|
||||
function removeFile(file: WatchedFile) {
|
||||
unorderedRemoveItem(watchedFiles, file);
|
||||
}
|
||||
|
||||
return {
|
||||
getModifiedTime: getModifiedTime,
|
||||
poll: poll,
|
||||
startWatchTimer: startWatchTimer,
|
||||
addFile: addFile,
|
||||
removeFile: removeFile
|
||||
};
|
||||
}
|
||||
|
||||
// REVIEW: for now this implementation uses polling.
|
||||
@@ -747,6 +757,8 @@ namespace ts.server {
|
||||
validateLocaleAndSetLanguage(localeStr, sys);
|
||||
}
|
||||
|
||||
setStackTraceLimit();
|
||||
|
||||
const typingSafeListLocation = findArgument(Arguments.TypingSafeListLocation);
|
||||
const npmLocation = findArgument(Arguments.NpmLocation);
|
||||
|
||||
|
||||
+201
-128
@@ -49,7 +49,7 @@ namespace ts.server {
|
||||
|
||||
interface FileStart {
|
||||
file: string;
|
||||
start: ILineInfo;
|
||||
start: protocol.Location;
|
||||
}
|
||||
|
||||
function compareNumber(a: number, b: number) {
|
||||
@@ -72,26 +72,32 @@ namespace ts.server {
|
||||
}
|
||||
}
|
||||
|
||||
function formatDiag(fileName: NormalizedPath, project: Project, diag: ts.Diagnostic): protocol.Diagnostic {
|
||||
function formatDiag(fileName: NormalizedPath, project: Project, diag: Diagnostic): protocol.Diagnostic {
|
||||
const scriptInfo = project.getScriptInfoForNormalizedPath(fileName);
|
||||
return {
|
||||
start: scriptInfo.positionToLineOffset(diag.start),
|
||||
end: scriptInfo.positionToLineOffset(diag.start + diag.length),
|
||||
text: ts.flattenDiagnosticMessageText(diag.messageText, "\n"),
|
||||
text: flattenDiagnosticMessageText(diag.messageText, "\n"),
|
||||
code: diag.code,
|
||||
category: DiagnosticCategory[diag.category].toLowerCase(),
|
||||
source: diag.source
|
||||
};
|
||||
}
|
||||
|
||||
function formatConfigFileDiag(diag: ts.Diagnostic): protocol.Diagnostic {
|
||||
return {
|
||||
start: undefined,
|
||||
end: undefined,
|
||||
text: ts.flattenDiagnosticMessageText(diag.messageText, "\n"),
|
||||
category: DiagnosticCategory[diag.category].toLowerCase(),
|
||||
source: diag.source
|
||||
};
|
||||
function convertToLocation(lineAndCharacter: LineAndCharacter): protocol.Location {
|
||||
return { line: lineAndCharacter.line + 1, offset: lineAndCharacter.character + 1 };
|
||||
}
|
||||
|
||||
function formatConfigFileDiag(diag: Diagnostic, includeFileName: true): protocol.DiagnosticWithFileName;
|
||||
function formatConfigFileDiag(diag: Diagnostic, includeFileName: false): protocol.Diagnostic;
|
||||
function formatConfigFileDiag(diag: Diagnostic, includeFileName: boolean): protocol.Diagnostic | protocol.DiagnosticWithFileName {
|
||||
const start = diag.file && convertToLocation(getLineAndCharacterOfPosition(diag.file, diag.start));
|
||||
const end = diag.file && convertToLocation(getLineAndCharacterOfPosition(diag.file, diag.start + diag.length));
|
||||
const text = flattenDiagnosticMessageText(diag.messageText, "\n");
|
||||
const { code, source } = diag;
|
||||
const category = DiagnosticCategory[diag.category].toLowerCase();
|
||||
return includeFileName ? { start, end, text, code, category, source, fileName: diag.file && diag.file.fileName } :
|
||||
{ start, end, text, code, category, source };
|
||||
}
|
||||
|
||||
export interface PendingErrorCheck {
|
||||
@@ -112,7 +118,13 @@ namespace ts.server {
|
||||
return true;
|
||||
}
|
||||
|
||||
export import CommandNames = protocol.CommandTypes;
|
||||
// CommandNames used to be exposed before TS 2.4 as a namespace
|
||||
// In TS 2.4 we switched to an enum, keep this for backward compatibility
|
||||
// The var assignment ensures that even though CommandTypes are a const enum
|
||||
// we want to ensure the value is maintained in the out since the file is
|
||||
// built using --preseveConstEnum.
|
||||
export type CommandNames = protocol.CommandTypes;
|
||||
export const CommandNames = (<any>protocol).CommandTypes;
|
||||
|
||||
export function formatMessage<T extends protocol.Message>(msg: T, logger: server.Logger, byteLength: (s: string, encoding: string) => number, newLine: string): string {
|
||||
const verboseLogging = logger.hasLevel(LogLevel.verbose);
|
||||
@@ -150,19 +162,13 @@ namespace ts.server {
|
||||
* Represents operation that can schedule its next step to be executed later.
|
||||
* Scheduling is done via instance of NextStep. If on current step subsequent step was not scheduled - operation is assumed to be completed.
|
||||
*/
|
||||
class MultistepOperation {
|
||||
class MultistepOperation implements NextStep {
|
||||
private requestId: number;
|
||||
private timerHandle: any;
|
||||
private immediateId: any;
|
||||
private completed = true;
|
||||
private readonly next: NextStep;
|
||||
|
||||
constructor(private readonly operationHost: MultistepOperationHost) {
|
||||
this.next = {
|
||||
immediate: action => this.immediate(action),
|
||||
delay: (ms, action) => this.delay(ms, action)
|
||||
};
|
||||
}
|
||||
constructor(private readonly operationHost: MultistepOperationHost) {}
|
||||
|
||||
public startNew(action: (next: NextStep) => void) {
|
||||
this.complete();
|
||||
@@ -182,7 +188,7 @@ namespace ts.server {
|
||||
this.setImmediateId(undefined);
|
||||
}
|
||||
|
||||
private immediate(action: () => void) {
|
||||
public immediate(action: () => void) {
|
||||
const requestId = this.requestId;
|
||||
Debug.assert(requestId === this.operationHost.getCurrentRequestId(), "immediate: incorrect request id");
|
||||
this.setImmediateId(this.operationHost.getServerHost().setImmediate(() => {
|
||||
@@ -191,7 +197,7 @@ namespace ts.server {
|
||||
}));
|
||||
}
|
||||
|
||||
private delay(ms: number, action: () => void) {
|
||||
public delay(ms: number, action: () => void) {
|
||||
const requestId = this.requestId;
|
||||
Debug.assert(requestId === this.operationHost.getCurrentRequestId(), "delay: incorrect request id");
|
||||
this.setTimerHandle(this.operationHost.getServerHost().setTimeout(() => {
|
||||
@@ -207,7 +213,7 @@ namespace ts.server {
|
||||
stop = true;
|
||||
}
|
||||
else {
|
||||
action(this.next);
|
||||
action(this);
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
@@ -377,8 +383,8 @@ namespace ts.server {
|
||||
this.host.write(formatMessage(msg, this.logger, this.byteLength, this.host.newLine));
|
||||
}
|
||||
|
||||
public configFileDiagnosticEvent(triggerFile: string, configFile: string, diagnostics: ts.Diagnostic[]) {
|
||||
const bakedDiags = ts.map(diagnostics, formatConfigFileDiag);
|
||||
public configFileDiagnosticEvent(triggerFile: string, configFile: string, diagnostics: Diagnostic[]) {
|
||||
const bakedDiags = map(diagnostics, diagnostic => formatConfigFileDiag(diagnostic, /*includeFileName*/ true));
|
||||
const ev: protocol.ConfigFileDiagnosticEvent = {
|
||||
seq: 0,
|
||||
type: "event",
|
||||
@@ -427,7 +433,7 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
const bakedDiags = diags.map((diag) => formatDiag(file, project, diag));
|
||||
this.event<protocol.DiagnosticEventBody>({ file: file, diagnostics: bakedDiags }, "semanticDiag");
|
||||
this.event<protocol.DiagnosticEventBody>({ file, diagnostics: bakedDiags }, "semanticDiag");
|
||||
}
|
||||
catch (err) {
|
||||
this.logError(err, "semantic check");
|
||||
@@ -439,7 +445,7 @@ namespace ts.server {
|
||||
const diags = project.getLanguageService().getSyntacticDiagnostics(file);
|
||||
if (diags) {
|
||||
const bakedDiags = diags.map((diag) => formatDiag(file, project, diag));
|
||||
this.event<protocol.DiagnosticEventBody>({ file: file, diagnostics: bakedDiags }, "syntaxDiag");
|
||||
this.event<protocol.DiagnosticEventBody>({ file, diagnostics: bakedDiags }, "syntaxDiag");
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
@@ -511,9 +517,55 @@ namespace ts.server {
|
||||
return projectFileName && this.projectService.findProject(projectFileName);
|
||||
}
|
||||
|
||||
private getConfigFileAndProject(args: protocol.FileRequestArgs) {
|
||||
const project = this.getProject(args.projectFileName);
|
||||
const file = toNormalizedPath(args.file);
|
||||
|
||||
return {
|
||||
configFile: project && project.hasConfigFile(file) && file,
|
||||
project
|
||||
};
|
||||
}
|
||||
|
||||
private getConfigFileDiagnostics(configFile: NormalizedPath, project: Project, includeLinePosition: boolean) {
|
||||
const projectErrors = project.getAllProjectErrors();
|
||||
const optionsErrors = project.getLanguageService().getCompilerOptionsDiagnostics();
|
||||
const diagnosticsForConfigFile = filter(
|
||||
concatenate(projectErrors, optionsErrors),
|
||||
diagnostic => diagnostic.file && diagnostic.file.fileName === configFile
|
||||
);
|
||||
return includeLinePosition ?
|
||||
this.convertToDiagnosticsWithLinePositionFromDiagnosticFile(diagnosticsForConfigFile) :
|
||||
map(
|
||||
diagnosticsForConfigFile,
|
||||
diagnostic => formatConfigFileDiag(diagnostic, /*includeFileName*/ false)
|
||||
);
|
||||
}
|
||||
|
||||
private convertToDiagnosticsWithLinePositionFromDiagnosticFile(diagnostics: Diagnostic[]) {
|
||||
return diagnostics.map(d => <protocol.DiagnosticWithLinePosition>{
|
||||
message: flattenDiagnosticMessageText(d.messageText, this.host.newLine),
|
||||
start: d.start,
|
||||
length: d.length,
|
||||
category: DiagnosticCategory[d.category].toLowerCase(),
|
||||
code: d.code,
|
||||
startLocation: d.file && convertToLocation(getLineAndCharacterOfPosition(d.file, d.start)),
|
||||
endLocation: d.file && convertToLocation(getLineAndCharacterOfPosition(d.file, d.start + d.length))
|
||||
});
|
||||
}
|
||||
|
||||
private getCompilerOptionsDiagnostics(args: protocol.CompilerOptionsDiagnosticsRequestArgs) {
|
||||
const project = this.getProject(args.projectFileName);
|
||||
return this.convertToDiagnosticsWithLinePosition(project.getLanguageService().getCompilerOptionsDiagnostics(), /*scriptInfo*/ undefined);
|
||||
// Get diagnostics that dont have associated file with them
|
||||
// The diagnostics which have file would be in config file and
|
||||
// would be reported as part of configFileDiagnostics
|
||||
return this.convertToDiagnosticsWithLinePosition(
|
||||
filter(
|
||||
project.getLanguageService().getCompilerOptionsDiagnostics(),
|
||||
diagnostic => !diagnostic.file
|
||||
),
|
||||
/*scriptInfo*/ undefined
|
||||
);
|
||||
}
|
||||
|
||||
private convertToDiagnosticsWithLinePosition(diagnostics: Diagnostic[], scriptInfo: ScriptInfo) {
|
||||
@@ -557,7 +609,7 @@ namespace ts.server {
|
||||
return {
|
||||
file: def.fileName,
|
||||
start: defScriptInfo.positionToLineOffset(def.textSpan.start),
|
||||
end: defScriptInfo.positionToLineOffset(ts.textSpanEnd(def.textSpan))
|
||||
end: defScriptInfo.positionToLineOffset(textSpanEnd(def.textSpan))
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -581,7 +633,7 @@ namespace ts.server {
|
||||
return {
|
||||
file: def.fileName,
|
||||
start: defScriptInfo.positionToLineOffset(def.textSpan.start),
|
||||
end: defScriptInfo.positionToLineOffset(ts.textSpanEnd(def.textSpan))
|
||||
end: defScriptInfo.positionToLineOffset(textSpanEnd(def.textSpan))
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -599,7 +651,7 @@ namespace ts.server {
|
||||
return {
|
||||
file: fileName,
|
||||
start: scriptInfo.positionToLineOffset(textSpan.start),
|
||||
end: scriptInfo.positionToLineOffset(ts.textSpanEnd(textSpan))
|
||||
end: scriptInfo.positionToLineOffset(textSpanEnd(textSpan))
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -623,7 +675,7 @@ namespace ts.server {
|
||||
const { fileName, isWriteAccess, textSpan, isInString } = occurrence;
|
||||
const scriptInfo = project.getScriptInfo(fileName);
|
||||
const start = scriptInfo.positionToLineOffset(textSpan.start);
|
||||
const end = scriptInfo.positionToLineOffset(ts.textSpanEnd(textSpan));
|
||||
const end = scriptInfo.positionToLineOffset(textSpanEnd(textSpan));
|
||||
const result: protocol.OccurrencesResponseItem = {
|
||||
start,
|
||||
end,
|
||||
@@ -639,10 +691,20 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
private getSyntacticDiagnosticsSync(args: protocol.SyntacticDiagnosticsSyncRequestArgs): protocol.Diagnostic[] | protocol.DiagnosticWithLinePosition[] {
|
||||
const { configFile } = this.getConfigFileAndProject(args);
|
||||
if (configFile) {
|
||||
// all the config file errors are reported as part of semantic check so nothing to report here
|
||||
return [];
|
||||
}
|
||||
|
||||
return this.getDiagnosticsWorker(args, /*isSemantic*/ false, (project, file) => project.getLanguageService().getSyntacticDiagnostics(file), args.includeLinePosition);
|
||||
}
|
||||
|
||||
private getSemanticDiagnosticsSync(args: protocol.SemanticDiagnosticsSyncRequestArgs): protocol.Diagnostic[] | protocol.DiagnosticWithLinePosition[] {
|
||||
const { configFile, project } = this.getConfigFileAndProject(args);
|
||||
if (configFile) {
|
||||
return this.getConfigFileDiagnostics(configFile, project, args.includeLinePosition);
|
||||
}
|
||||
return this.getDiagnosticsWorker(args, /*isSemantic*/ true, (project, file) => project.getLanguageService().getSemanticDiagnostics(file), args.includeLinePosition);
|
||||
}
|
||||
|
||||
@@ -663,7 +725,7 @@ namespace ts.server {
|
||||
return documentHighlights;
|
||||
}
|
||||
|
||||
function convertToDocumentHighlightsItem(documentHighlights: ts.DocumentHighlights): ts.server.protocol.DocumentHighlightsItem {
|
||||
function convertToDocumentHighlightsItem(documentHighlights: DocumentHighlights): protocol.DocumentHighlightsItem {
|
||||
const { fileName, highlightSpans } = documentHighlights;
|
||||
|
||||
const scriptInfo = project.getScriptInfo(fileName);
|
||||
@@ -672,10 +734,10 @@ namespace ts.server {
|
||||
highlightSpans: highlightSpans.map(convertHighlightSpan)
|
||||
};
|
||||
|
||||
function convertHighlightSpan(highlightSpan: ts.HighlightSpan): ts.server.protocol.HighlightSpan {
|
||||
function convertHighlightSpan(highlightSpan: HighlightSpan): protocol.HighlightSpan {
|
||||
const { textSpan, kind } = highlightSpan;
|
||||
const start = scriptInfo.positionToLineOffset(textSpan.start);
|
||||
const end = scriptInfo.positionToLineOffset(ts.textSpanEnd(textSpan));
|
||||
const end = scriptInfo.positionToLineOffset(textSpanEnd(textSpan));
|
||||
return { start, end, kind };
|
||||
}
|
||||
}
|
||||
@@ -686,15 +748,15 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
private getProjectInfo(args: protocol.ProjectInfoRequestArgs): protocol.ProjectInfo {
|
||||
return this.getProjectInfoWorker(args.file, args.projectFileName, args.needFileNameList);
|
||||
return this.getProjectInfoWorker(args.file, args.projectFileName, args.needFileNameList, /*excludeConfigFiles*/ false);
|
||||
}
|
||||
|
||||
private getProjectInfoWorker(uncheckedFileName: string, projectFileName: string, needFileNameList: boolean) {
|
||||
private getProjectInfoWorker(uncheckedFileName: string, projectFileName: string, needFileNameList: boolean, excludeConfigFiles: boolean) {
|
||||
const { project } = this.getFileAndProjectWorker(uncheckedFileName, projectFileName, /*refreshInferredProjects*/ true, /*errorOnMissingProject*/ true);
|
||||
const projectInfo = {
|
||||
configFileName: project.getProjectName(),
|
||||
languageServiceDisabled: !project.languageServiceEnabled,
|
||||
fileNames: needFileNameList ? project.getFileNames() : undefined
|
||||
fileNames: needFileNameList ? project.getFileNames(/*excludeFilesFromExternalLibraries*/ false, excludeConfigFiles) : undefined
|
||||
};
|
||||
return projectInfo;
|
||||
}
|
||||
@@ -718,7 +780,7 @@ namespace ts.server {
|
||||
const scriptInfo = this.projectService.getScriptInfo(args.file);
|
||||
projects = scriptInfo.containingProjects;
|
||||
}
|
||||
// ts.filter handles case when 'projects' is undefined
|
||||
// filter handles case when 'projects' is undefined
|
||||
projects = filter(projects, p => p.languageServiceEnabled);
|
||||
if (!projects || !projects.length) {
|
||||
return Errors.ThrowNoProject();
|
||||
@@ -771,28 +833,29 @@ namespace ts.server {
|
||||
return <protocol.FileSpan>{
|
||||
file: location.fileName,
|
||||
start: locationScriptInfo.positionToLineOffset(location.textSpan.start),
|
||||
end: locationScriptInfo.positionToLineOffset(ts.textSpanEnd(location.textSpan)),
|
||||
end: locationScriptInfo.positionToLineOffset(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) => {
|
||||
|
||||
const locs: protocol.SpanGroup[] = [];
|
||||
for (const cur of fileSpans) {
|
||||
let curFileAccum: protocol.SpanGroup;
|
||||
if (accum.length > 0) {
|
||||
curFileAccum = accum[accum.length - 1];
|
||||
if (locs.length > 0) {
|
||||
curFileAccum = locs[locs.length - 1];
|
||||
if (curFileAccum.file !== cur.file) {
|
||||
curFileAccum = undefined;
|
||||
}
|
||||
}
|
||||
if (!curFileAccum) {
|
||||
curFileAccum = { file: cur.file, locs: [] };
|
||||
accum.push(curFileAccum);
|
||||
locs.push(curFileAccum);
|
||||
}
|
||||
curFileAccum.locs.push({ start: cur.start, end: cur.end });
|
||||
return accum;
|
||||
}, []);
|
||||
}
|
||||
|
||||
return { info: renameInfo, locs };
|
||||
}
|
||||
@@ -852,10 +915,10 @@ namespace ts.server {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const displayString = ts.displayPartsToString(nameInfo.displayParts);
|
||||
const displayString = displayPartsToString(nameInfo.displayParts);
|
||||
const nameSpan = nameInfo.textSpan;
|
||||
const nameColStart = scriptInfo.positionToLineOffset(nameSpan.start).offset;
|
||||
const nameText = scriptInfo.getSnapshot().getText(nameSpan.start, ts.textSpanEnd(nameSpan));
|
||||
const nameText = scriptInfo.getSnapshot().getText(nameSpan.start, textSpanEnd(nameSpan));
|
||||
const refs = combineProjectOutput<protocol.ReferencesResponseItem>(
|
||||
projects,
|
||||
(project: Project) => {
|
||||
@@ -868,12 +931,12 @@ namespace ts.server {
|
||||
const refScriptInfo = project.getScriptInfo(ref.fileName);
|
||||
const start = refScriptInfo.positionToLineOffset(ref.textSpan.start);
|
||||
const refLineSpan = refScriptInfo.lineToTextSpan(start.line - 1);
|
||||
const lineText = refScriptInfo.getSnapshot().getText(refLineSpan.start, ts.textSpanEnd(refLineSpan)).replace(/\r|\n/g, "");
|
||||
const lineText = refScriptInfo.getSnapshot().getText(refLineSpan.start, textSpanEnd(refLineSpan)).replace(/\r|\n/g, "");
|
||||
return {
|
||||
file: ref.fileName,
|
||||
start: start,
|
||||
lineText: lineText,
|
||||
end: refScriptInfo.positionToLineOffset(ts.textSpanEnd(ref.textSpan)),
|
||||
start,
|
||||
lineText,
|
||||
end: refScriptInfo.positionToLineOffset(textSpanEnd(ref.textSpan)),
|
||||
isWriteAccess: ref.isWriteAccess,
|
||||
isDefinition: ref.isDefinition
|
||||
};
|
||||
@@ -1004,15 +1067,15 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
if (simplifiedResult) {
|
||||
const displayString = ts.displayPartsToString(quickInfo.displayParts);
|
||||
const docString = ts.displayPartsToString(quickInfo.documentation);
|
||||
const displayString = displayPartsToString(quickInfo.displayParts);
|
||||
const docString = displayPartsToString(quickInfo.documentation);
|
||||
|
||||
return {
|
||||
kind: quickInfo.kind,
|
||||
kindModifiers: quickInfo.kindModifiers,
|
||||
start: scriptInfo.positionToLineOffset(quickInfo.textSpan.start),
|
||||
end: scriptInfo.positionToLineOffset(ts.textSpanEnd(quickInfo.textSpan)),
|
||||
displayString: displayString,
|
||||
end: scriptInfo.positionToLineOffset(textSpanEnd(quickInfo.textSpan)),
|
||||
displayString,
|
||||
documentation: docString,
|
||||
tags: quickInfo.tags || []
|
||||
};
|
||||
@@ -1071,32 +1134,29 @@ namespace ts.server {
|
||||
// only to the previous line. If all this is true, then
|
||||
// add edits necessary to properly indent the current line.
|
||||
if ((args.key === "\n") && ((!edits) || (edits.length === 0) || allEditsBeforePos(edits, position))) {
|
||||
const lineInfo = scriptInfo.getLineInfo(args.line);
|
||||
if (lineInfo && (lineInfo.leaf) && (lineInfo.leaf.text)) {
|
||||
const lineText = lineInfo.leaf.text;
|
||||
if (lineText.search("\\S") < 0) {
|
||||
const preferredIndent = project.getLanguageService(/*ensureSynchronized*/ false).getIndentationAtPosition(file, position, formatOptions);
|
||||
let hasIndent = 0;
|
||||
let i: number, len: number;
|
||||
for (i = 0, len = lineText.length; i < len; i++) {
|
||||
if (lineText.charAt(i) === " ") {
|
||||
hasIndent++;
|
||||
}
|
||||
else if (lineText.charAt(i) === "\t") {
|
||||
hasIndent += formatOptions.tabSize;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
const { lineText, absolutePosition } = scriptInfo.getLineInfo(args.line);
|
||||
if (lineText && lineText.search("\\S") < 0) {
|
||||
const preferredIndent = project.getLanguageService(/*ensureSynchronized*/ false).getIndentationAtPosition(file, position, formatOptions);
|
||||
let hasIndent = 0;
|
||||
let i: number, len: number;
|
||||
for (i = 0, len = lineText.length; i < len; i++) {
|
||||
if (lineText.charAt(i) === " ") {
|
||||
hasIndent++;
|
||||
}
|
||||
// i points to the first non whitespace character
|
||||
if (preferredIndent !== hasIndent) {
|
||||
const firstNoWhiteSpacePosition = lineInfo.offset + i;
|
||||
edits.push({
|
||||
span: ts.createTextSpanFromBounds(lineInfo.offset, firstNoWhiteSpacePosition),
|
||||
newText: formatting.getIndentationString(preferredIndent, formatOptions)
|
||||
});
|
||||
else if (lineText.charAt(i) === "\t") {
|
||||
hasIndent += formatOptions.tabSize;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// i points to the first non whitespace character
|
||||
if (preferredIndent !== hasIndent) {
|
||||
const firstNoWhiteSpacePosition = absolutePosition + i;
|
||||
edits.push({
|
||||
span: createTextSpanFromBounds(absolutePosition, firstNoWhiteSpacePosition),
|
||||
newText: formatting.getIndentationString(preferredIndent, formatOptions)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1108,7 +1168,7 @@ namespace ts.server {
|
||||
return edits.map((edit) => {
|
||||
return {
|
||||
start: scriptInfo.positionToLineOffset(edit.span.start),
|
||||
end: scriptInfo.positionToLineOffset(ts.textSpanEnd(edit.span)),
|
||||
end: scriptInfo.positionToLineOffset(textSpanEnd(edit.span)),
|
||||
newText: edit.newText ? edit.newText : ""
|
||||
};
|
||||
});
|
||||
@@ -1126,15 +1186,13 @@ namespace ts.server {
|
||||
return undefined;
|
||||
}
|
||||
if (simplifiedResult) {
|
||||
return completions.entries.reduce((result: protocol.CompletionEntry[], entry: ts.CompletionEntry) => {
|
||||
return mapDefined(completions.entries, entry => {
|
||||
if (completions.isMemberCompletion || (entry.name.toLowerCase().indexOf(prefix.toLowerCase()) === 0)) {
|
||||
const { name, kind, kindModifiers, sortText, replacementSpan } = entry;
|
||||
const convertedSpan: protocol.TextSpan =
|
||||
replacementSpan ? this.decorateSpan(replacementSpan, scriptInfo) : undefined;
|
||||
result.push({ name, kind, kindModifiers, sortText, replacementSpan: convertedSpan });
|
||||
const convertedSpan = replacementSpan ? this.decorateSpan(replacementSpan, scriptInfo) : undefined;
|
||||
return { name, kind, kindModifiers, sortText, replacementSpan: convertedSpan };
|
||||
}
|
||||
return result;
|
||||
}, []).sort((a, b) => ts.compareStrings(a.name, b.name));
|
||||
}).sort((a, b) => compareStrings(a.name, b.name));
|
||||
}
|
||||
else {
|
||||
return completions;
|
||||
@@ -1146,13 +1204,8 @@ namespace ts.server {
|
||||
const scriptInfo = project.getScriptInfoForNormalizedPath(file);
|
||||
const position = this.getPosition(args, scriptInfo);
|
||||
|
||||
return args.entryNames.reduce((accum: protocol.CompletionEntryDetails[], entryName: string) => {
|
||||
const details = project.getLanguageService().getCompletionEntryDetails(file, position, entryName);
|
||||
if (details) {
|
||||
accum.push(details);
|
||||
}
|
||||
return accum;
|
||||
}, []);
|
||||
return mapDefined(args.entryNames, entryName =>
|
||||
project.getLanguageService().getCompletionEntryDetails(file, position, entryName));
|
||||
}
|
||||
|
||||
private getCompileOnSaveAffectedFileList(args: protocol.FileRequestArgs): protocol.CompileOnSaveAffectedFileListSingleProject[] {
|
||||
@@ -1217,14 +1270,11 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
private getDiagnostics(next: NextStep, delay: number, fileNames: string[]): void {
|
||||
const checkList = fileNames.reduce((accum: PendingErrorCheck[], uncheckedFileName: string) => {
|
||||
const checkList = mapDefined(fileNames, uncheckedFileName => {
|
||||
const fileName = toNormalizedPath(uncheckedFileName);
|
||||
const project = this.projectService.getDefaultProjectForFile(fileName, /*refreshInferredProjects*/ true);
|
||||
if (project) {
|
||||
accum.push({ fileName, project });
|
||||
}
|
||||
return accum;
|
||||
}, []);
|
||||
return project && { fileName, project };
|
||||
});
|
||||
|
||||
if (checkList.length > 0) {
|
||||
this.updateErrorCheck(next, checkList, this.changeSeq, (n) => n === this.changeSeq, delay);
|
||||
@@ -1269,11 +1319,11 @@ namespace ts.server {
|
||||
if (!fileName) {
|
||||
return;
|
||||
}
|
||||
const file = ts.normalizePath(fileName);
|
||||
const file = normalizePath(fileName);
|
||||
this.projectService.closeClientFile(file);
|
||||
}
|
||||
|
||||
private decorateNavigationBarItems(items: ts.NavigationBarItem[], scriptInfo: ScriptInfo): protocol.NavigationBarItem[] {
|
||||
private decorateNavigationBarItems(items: NavigationBarItem[], scriptInfo: ScriptInfo): protocol.NavigationBarItem[] {
|
||||
return map(items, item => ({
|
||||
text: item.text,
|
||||
kind: item.kind,
|
||||
@@ -1294,7 +1344,7 @@ namespace ts.server {
|
||||
: items;
|
||||
}
|
||||
|
||||
private decorateNavigationTree(tree: ts.NavigationTree, scriptInfo: ScriptInfo): protocol.NavigationTree {
|
||||
private decorateNavigationTree(tree: NavigationTree, scriptInfo: ScriptInfo): protocol.NavigationTree {
|
||||
return {
|
||||
text: tree.text,
|
||||
kind: tree.kind,
|
||||
@@ -1307,7 +1357,7 @@ namespace ts.server {
|
||||
private decorateSpan(span: TextSpan, scriptInfo: ScriptInfo): protocol.TextSpan {
|
||||
return {
|
||||
start: scriptInfo.positionToLineOffset(span.start),
|
||||
end: scriptInfo.positionToLineOffset(ts.textSpanEnd(span))
|
||||
end: scriptInfo.positionToLineOffset(textSpanEnd(span))
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1337,13 +1387,13 @@ namespace ts.server {
|
||||
return navItems.map((navItem) => {
|
||||
const scriptInfo = project.getScriptInfo(navItem.fileName);
|
||||
const start = scriptInfo.positionToLineOffset(navItem.textSpan.start);
|
||||
const end = scriptInfo.positionToLineOffset(ts.textSpanEnd(navItem.textSpan));
|
||||
const end = scriptInfo.positionToLineOffset(textSpanEnd(navItem.textSpan));
|
||||
const bakedItem: protocol.NavtoItem = {
|
||||
name: navItem.name,
|
||||
kind: navItem.kind,
|
||||
file: navItem.fileName,
|
||||
start: start,
|
||||
end: end,
|
||||
start,
|
||||
end,
|
||||
};
|
||||
if (navItem.kindModifiers && (navItem.kindModifiers !== "")) {
|
||||
bakedItem.kindModifiers = navItem.kindModifiers;
|
||||
@@ -1402,7 +1452,7 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
private getSupportedCodeFixes(): string[] {
|
||||
return ts.getSupportedCodeFixes();
|
||||
return getSupportedCodeFixes();
|
||||
}
|
||||
|
||||
private isLocation(locationOrSpan: protocol.FileLocationOrRangeRequestArgs): locationOrSpan is protocol.FileLocationRequestArgs {
|
||||
@@ -1433,29 +1483,40 @@ namespace ts.server {
|
||||
return project.getLanguageService().getApplicableRefactors(file, position || textRange);
|
||||
}
|
||||
|
||||
private getRefactorCodeActions(args: protocol.GetRefactorCodeActionsRequestArgs, simplifiedResult: boolean): protocol.RefactorCodeActions | protocol.RefactorCodeActionsFull {
|
||||
private getEditsForRefactor(args: protocol.GetEditsForRefactorRequestArgs, simplifiedResult: boolean): RefactorEditInfo | protocol.RefactorEditInfo {
|
||||
const { file, project } = this.getFileAndProjectWithoutRefreshingInferredProjects(args);
|
||||
const scriptInfo = project.getScriptInfoForNormalizedPath(file);
|
||||
const { position, textRange } = this.extractPositionAndRange(args, scriptInfo);
|
||||
|
||||
const result: ts.CodeAction[] = project.getLanguageService().getRefactorCodeActions(
|
||||
const result = project.getLanguageService().getEditsForRefactor(
|
||||
file,
|
||||
this.projectService.getFormatCodeOptions(),
|
||||
position || textRange,
|
||||
args.refactorName
|
||||
args.refactor,
|
||||
args.action
|
||||
);
|
||||
|
||||
if (simplifiedResult) {
|
||||
// Not full
|
||||
if (result === undefined) {
|
||||
return {
|
||||
actions: result.map(action => this.mapCodeAction(action, scriptInfo))
|
||||
edits: []
|
||||
};
|
||||
}
|
||||
|
||||
if (simplifiedResult) {
|
||||
const file = result.renameFilename;
|
||||
let location: protocol.Location | undefined;
|
||||
if (file !== undefined && result.renameLocation !== undefined) {
|
||||
const renameScriptInfo = project.getScriptInfoForNormalizedPath(toNormalizedPath(file));
|
||||
location = renameScriptInfo.positionToLineOffset(result.renameLocation);
|
||||
}
|
||||
return {
|
||||
renameLocation: location,
|
||||
renameFilename: file,
|
||||
edits: result.edits.map(change => this.mapTextChangesToCodeEdits(project, change))
|
||||
};
|
||||
}
|
||||
else {
|
||||
// Full
|
||||
return {
|
||||
actions: result
|
||||
};
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1513,6 +1574,14 @@ namespace ts.server {
|
||||
};
|
||||
}
|
||||
|
||||
private mapTextChangesToCodeEdits(project: Project, textChanges: FileTextChanges): protocol.FileCodeEdits {
|
||||
const scriptInfo = project.getScriptInfoForNormalizedPath(toNormalizedPath(textChanges.fileName));
|
||||
return {
|
||||
fileName: textChanges.fileName,
|
||||
textChanges: textChanges.textChanges.map(textChange => this.convertTextChangeToCodeEdit(textChange, scriptInfo))
|
||||
};
|
||||
}
|
||||
|
||||
private convertTextChangeToCodeEdit(change: ts.TextChange, scriptInfo: ScriptInfo): protocol.CodeEdit {
|
||||
return {
|
||||
start: scriptInfo.positionToLineOffset(change.span.start),
|
||||
@@ -1536,7 +1605,7 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
private getDiagnosticsForProject(next: NextStep, delay: number, fileName: string): void {
|
||||
const { fileNames, languageServiceDisabled } = this.getProjectInfoWorker(fileName, /*projectFileName*/ undefined, /*needFileNameList*/ true);
|
||||
const { fileNames, languageServiceDisabled } = this.getProjectInfoWorker(fileName, /*projectFileName*/ undefined, /*needFileNameList*/ true, /*excludeConfigFiles*/ true);
|
||||
if (languageServiceDisabled) {
|
||||
return;
|
||||
}
|
||||
@@ -1552,18 +1621,22 @@ namespace ts.server {
|
||||
const normalizedFileName = toNormalizedPath(fileName);
|
||||
const project = this.projectService.getDefaultProjectForFile(normalizedFileName, /*refreshInferredProjects*/ true);
|
||||
for (const fileNameInProject of fileNamesInProject) {
|
||||
if (this.getCanonicalFileName(fileNameInProject) === this.getCanonicalFileName(fileName))
|
||||
if (this.getCanonicalFileName(fileNameInProject) === this.getCanonicalFileName(fileName)) {
|
||||
highPriorityFiles.push(fileNameInProject);
|
||||
}
|
||||
else {
|
||||
const info = this.projectService.getScriptInfo(fileNameInProject);
|
||||
if (!info.isScriptOpen()) {
|
||||
if (fileNameInProject.indexOf(".d.ts") > 0)
|
||||
if (fileNameInProject.indexOf(Extension.Dts) > 0) {
|
||||
veryLowPriorityFiles.push(fileNameInProject);
|
||||
else
|
||||
}
|
||||
else {
|
||||
lowPriorityFiles.push(fileNameInProject);
|
||||
}
|
||||
}
|
||||
else
|
||||
else {
|
||||
mediumPriorityFiles.push(fileNameInProject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1579,7 +1652,7 @@ namespace ts.server {
|
||||
|
||||
getCanonicalFileName(fileName: string) {
|
||||
const name = this.host.useCaseSensitiveFileNames ? fileName : fileName.toLowerCase();
|
||||
return ts.normalizePath(name);
|
||||
return normalizePath(name);
|
||||
}
|
||||
|
||||
exit() {
|
||||
@@ -1844,11 +1917,11 @@ namespace ts.server {
|
||||
[CommandNames.GetApplicableRefactors]: (request: protocol.GetApplicableRefactorsRequest) => {
|
||||
return this.requiredResponse(this.getApplicableRefactors(request.arguments));
|
||||
},
|
||||
[CommandNames.GetRefactorCodeActions]: (request: protocol.GetRefactorCodeActionsRequest) => {
|
||||
return this.requiredResponse(this.getRefactorCodeActions(request.arguments, /*simplifiedResult*/ true));
|
||||
[CommandNames.GetEditsForRefactor]: (request: protocol.GetEditsForRefactorRequest) => {
|
||||
return this.requiredResponse(this.getEditsForRefactor(request.arguments, /*simplifiedResult*/ true));
|
||||
},
|
||||
[CommandNames.GetRefactorCodeActionsFull]: (request: protocol.GetRefactorCodeActionsRequest) => {
|
||||
return this.requiredResponse(this.getRefactorCodeActions(request.arguments, /*simplifiedResult*/ false));
|
||||
[CommandNames.GetEditsForRefactorFull]: (request: protocol.GetEditsForRefactorRequest) => {
|
||||
return this.requiredResponse(this.getEditsForRefactor(request.arguments, /*simplifiedResult*/ false));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"compilerOptions": {
|
||||
"noImplicitAny": true,
|
||||
"noImplicitThis": true,
|
||||
"alwaysStrict": true,
|
||||
"preserveConstEnums": true,
|
||||
"pretty": true,
|
||||
"outFile": "../../built/local/tsserverlibrary.js",
|
||||
|
||||
+10
-6
@@ -20,8 +20,12 @@ declare namespace ts.server {
|
||||
require?(initialPath: string, moduleName: string): RequireResult;
|
||||
}
|
||||
|
||||
export interface SortedArray<T> extends Array<T> {
|
||||
" __sortedArrayBrand": any;
|
||||
}
|
||||
|
||||
export interface SortedReadonlyArray<T> extends ReadonlyArray<T> {
|
||||
" __sortedReadonlyArrayBrand": any;
|
||||
" __sortedArrayBrand": any;
|
||||
}
|
||||
|
||||
export interface TypingInstallerRequest {
|
||||
@@ -31,9 +35,9 @@ declare namespace ts.server {
|
||||
|
||||
export interface DiscoverTypings extends TypingInstallerRequest {
|
||||
readonly fileNames: string[];
|
||||
readonly projectRootPath: ts.Path;
|
||||
readonly compilerOptions: ts.CompilerOptions;
|
||||
readonly typeAcquisition: ts.TypeAcquisition;
|
||||
readonly projectRootPath: Path;
|
||||
readonly compilerOptions: CompilerOptions;
|
||||
readonly typeAcquisition: TypeAcquisition;
|
||||
readonly unresolvedImports: SortedReadonlyArray<string>;
|
||||
readonly cachePath?: string;
|
||||
readonly kind: "discover";
|
||||
@@ -63,8 +67,8 @@ declare namespace ts.server {
|
||||
}
|
||||
|
||||
export interface SetTypings extends ProjectResponse {
|
||||
readonly typeAcquisition: ts.TypeAcquisition;
|
||||
readonly compilerOptions: ts.CompilerOptions;
|
||||
readonly typeAcquisition: TypeAcquisition;
|
||||
readonly compilerOptions: CompilerOptions;
|
||||
readonly typings: string[];
|
||||
readonly unresolvedImports: SortedReadonlyArray<string>;
|
||||
readonly kind: ActionSet;
|
||||
|
||||
@@ -110,7 +110,7 @@ namespace ts.server {
|
||||
this.perProjectCache.set(projectName, {
|
||||
compilerOptions,
|
||||
typeAcquisition,
|
||||
typings: toSortedReadonlyArray(newTypings),
|
||||
typings: toSortedArray(newTypings),
|
||||
unresolvedImports,
|
||||
poisoned: false
|
||||
});
|
||||
|
||||
@@ -85,6 +85,7 @@ namespace ts.server.typingsInstaller {
|
||||
private readonly missingTypingsSet: Map<true> = createMap<true>();
|
||||
private readonly knownCachesSet: Map<true> = createMap<true>();
|
||||
private readonly projectWatchers: Map<FileWatcher[]> = createMap<FileWatcher[]>();
|
||||
private safeList: JsTyping.SafeList | undefined;
|
||||
readonly pendingRunRequests: PendingRequest[] = [];
|
||||
|
||||
private installRunCount = 1;
|
||||
@@ -93,10 +94,10 @@ namespace ts.server.typingsInstaller {
|
||||
abstract readonly typesRegistry: Map<void>;
|
||||
|
||||
constructor(
|
||||
readonly installTypingHost: InstallTypingHost,
|
||||
readonly globalCachePath: string,
|
||||
readonly safeListPath: Path,
|
||||
readonly throttleLimit: number,
|
||||
protected readonly installTypingHost: InstallTypingHost,
|
||||
private readonly globalCachePath: string,
|
||||
private readonly safeListPath: Path,
|
||||
private readonly throttleLimit: number,
|
||||
protected readonly log = nullLog) {
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`Global cache location '${globalCachePath}', safe file path '${safeListPath}'`);
|
||||
@@ -143,11 +144,15 @@ namespace ts.server.typingsInstaller {
|
||||
this.processCacheLocation(req.cachePath);
|
||||
}
|
||||
|
||||
if (this.safeList === undefined) {
|
||||
this.safeList = JsTyping.loadSafeList(this.installTypingHost, this.safeListPath);
|
||||
}
|
||||
const discoverTypingsResult = JsTyping.discoverTypings(
|
||||
this.installTypingHost,
|
||||
this.log.isEnabled() ? this.log.writeLine : undefined,
|
||||
req.fileNames,
|
||||
req.projectRootPath,
|
||||
this.safeListPath,
|
||||
this.safeList,
|
||||
this.packageNameToTypingLocation,
|
||||
req.typeAcquisition,
|
||||
req.unresolvedImports);
|
||||
@@ -389,7 +394,7 @@ namespace ts.server.typingsInstaller {
|
||||
this.log.writeLine(`Got FS notification for ${f}, handler is already invoked '${isInvoked}'`);
|
||||
}
|
||||
if (!isInvoked) {
|
||||
this.sendResponse({ projectName: projectName, kind: server.ActionInvalidate });
|
||||
this.sendResponse({ projectName, kind: server.ActionInvalidate });
|
||||
isInvoked = true;
|
||||
}
|
||||
}, /*pollingInterval*/ 2000);
|
||||
@@ -434,5 +439,4 @@ namespace ts.server.typingsInstaller {
|
||||
export function typingsName(packageName: string): string {
|
||||
return `@types/${packageName}@ts${versionMajorMinor}`;
|
||||
}
|
||||
const versionMajorMinor = version.split(".").slice(0, 2).join(".");
|
||||
}
|
||||
+74
-55
@@ -9,7 +9,7 @@ namespace ts.server {
|
||||
verbose
|
||||
}
|
||||
|
||||
export const emptyArray: ReadonlyArray<any> = [];
|
||||
export const emptyArray: SortedReadonlyArray<never> = createSortedArray<never>();
|
||||
|
||||
export interface Logger {
|
||||
close(): void;
|
||||
@@ -49,7 +49,7 @@ namespace ts.server {
|
||||
export function createInstallTypingsRequest(project: Project, typeAcquisition: TypeAcquisition, unresolvedImports: SortedReadonlyArray<string>, cachePath?: string): DiscoverTypings {
|
||||
return {
|
||||
projectName: project.getProjectName(),
|
||||
fileNames: project.getFileNames(/*excludeFilesFromExternalLibraries*/ true),
|
||||
fileNames: project.getFileNames(/*excludeFilesFromExternalLibraries*/ true, /*excludeConfigFiles*/ true),
|
||||
compilerOptions: project.getCompilerOptions(),
|
||||
typeAcquisition,
|
||||
unresolvedImports,
|
||||
@@ -77,7 +77,7 @@ namespace ts.server {
|
||||
tabSize: 4,
|
||||
newLineCharacter: host.newLine || "\n",
|
||||
convertTabsToSpaces: true,
|
||||
indentStyle: ts.IndentStyle.Smart,
|
||||
indentStyle: IndentStyle.Smart,
|
||||
insertSpaceAfterConstructor: false,
|
||||
insertSpaceAfterCommaDelimiter: true,
|
||||
insertSpaceAfterSemicolonInForStatements: true,
|
||||
@@ -103,24 +103,6 @@ namespace ts.server {
|
||||
}
|
||||
}
|
||||
|
||||
export function removeItemFromSet<T>(items: T[], itemToRemove: T) {
|
||||
if (items.length === 0) {
|
||||
return;
|
||||
}
|
||||
const index = items.indexOf(itemToRemove);
|
||||
if (index < 0) {
|
||||
return;
|
||||
}
|
||||
if (index === items.length - 1) {
|
||||
// last item - pop it
|
||||
items.pop();
|
||||
}
|
||||
else {
|
||||
// non-last item - replace it with the last one
|
||||
items[index] = items.pop();
|
||||
}
|
||||
}
|
||||
|
||||
export type NormalizedPath = string & { __normalizedPathTag: any };
|
||||
|
||||
export function toNormalizedPath(fileName: string): NormalizedPath {
|
||||
@@ -190,40 +172,8 @@ namespace ts.server {
|
||||
return `/dev/null/inferredProject${counter}*`;
|
||||
}
|
||||
|
||||
export function toSortedReadonlyArray(arr: string[]): SortedReadonlyArray<string> {
|
||||
arr.sort();
|
||||
return <any>arr;
|
||||
}
|
||||
|
||||
export function enumerateInsertsAndDeletes<T>(a: SortedReadonlyArray<T>, b: SortedReadonlyArray<T>, inserted: (item: T) => void, deleted: (item: T) => void, compare?: (a: T, b: T) => Comparison) {
|
||||
compare = compare || ts.compareValues;
|
||||
let aIndex = 0;
|
||||
let bIndex = 0;
|
||||
const aLen = a.length;
|
||||
const bLen = b.length;
|
||||
while (aIndex < aLen && bIndex < bLen) {
|
||||
const aItem = a[aIndex];
|
||||
const bItem = b[bIndex];
|
||||
const compareResult = compare(aItem, bItem);
|
||||
if (compareResult === Comparison.LessThan) {
|
||||
inserted(aItem);
|
||||
aIndex++;
|
||||
}
|
||||
else if (compareResult === Comparison.GreaterThan) {
|
||||
deleted(bItem);
|
||||
bIndex++;
|
||||
}
|
||||
else {
|
||||
aIndex++;
|
||||
bIndex++;
|
||||
}
|
||||
}
|
||||
while (aIndex < aLen) {
|
||||
inserted(a[aIndex++]);
|
||||
}
|
||||
while (bIndex < bLen) {
|
||||
deleted(b[bIndex++]);
|
||||
}
|
||||
export function createSortedArray<T>(): SortedArray<T> {
|
||||
return [] as SortedArray<T>;
|
||||
}
|
||||
|
||||
export class ThrottledOperations {
|
||||
@@ -273,4 +223,73 @@ namespace ts.server {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
namespace ts.server {
|
||||
export function insertSorted<T>(array: SortedArray<T>, insert: T, compare: Comparer<T>): void {
|
||||
if (array.length === 0) {
|
||||
array.push(insert);
|
||||
return;
|
||||
}
|
||||
|
||||
const insertIndex = binarySearch(array, insert, compare);
|
||||
if (insertIndex < 0) {
|
||||
array.splice(~insertIndex, 0, insert);
|
||||
}
|
||||
}
|
||||
|
||||
export function removeSorted<T>(array: SortedArray<T>, remove: T, compare: Comparer<T>): void {
|
||||
if (!array || array.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (array[0] === remove) {
|
||||
array.splice(0, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
const removeIndex = binarySearch(array, remove, compare);
|
||||
if (removeIndex >= 0) {
|
||||
array.splice(removeIndex, 1);
|
||||
}
|
||||
}
|
||||
|
||||
export function toSortedArray(arr: string[]): SortedArray<string>;
|
||||
export function toSortedArray<T>(arr: T[], comparer: Comparer<T>): SortedArray<T>;
|
||||
export function toSortedArray<T>(arr: T[], comparer?: Comparer<T>): SortedArray<T> {
|
||||
arr.sort(comparer);
|
||||
return arr as SortedArray<T>;
|
||||
}
|
||||
|
||||
export function enumerateInsertsAndDeletes<T>(newItems: SortedReadonlyArray<T>, oldItems: SortedReadonlyArray<T>, inserted: (newItem: T) => void, deleted: (oldItem: T) => void, compare?: Comparer<T>) {
|
||||
compare = compare || compareValues;
|
||||
let newIndex = 0;
|
||||
let oldIndex = 0;
|
||||
const newLen = newItems.length;
|
||||
const oldLen = oldItems.length;
|
||||
while (newIndex < newLen && oldIndex < oldLen) {
|
||||
const newItem = newItems[newIndex];
|
||||
const oldItem = oldItems[oldIndex];
|
||||
const compareResult = compare(newItem, oldItem);
|
||||
if (compareResult === Comparison.LessThan) {
|
||||
inserted(newItem);
|
||||
newIndex++;
|
||||
}
|
||||
else if (compareResult === Comparison.GreaterThan) {
|
||||
deleted(oldItem);
|
||||
oldIndex++;
|
||||
}
|
||||
else {
|
||||
newIndex++;
|
||||
oldIndex++;
|
||||
}
|
||||
}
|
||||
while (newIndex < newLen) {
|
||||
inserted(newItems[newIndex++]);
|
||||
}
|
||||
while (oldIndex < oldLen) {
|
||||
deleted(oldItems[oldIndex++]);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user