提交 97c753fa 编写于 作者: M Matt Bierner

Don't treat cancellation as an error

**Problem**
In the ts server communication, canceling a request currently throws an exception. This is wrong because  cancellation is not an error. It also means that we need to wrap every server call in a generic try catch to handle cancellation. There are no checks in place to distinquish a cancellation from a rea
上级 df411e9d
......@@ -5,7 +5,7 @@
import * as vscode from 'vscode';
import * as Proto from '../protocol';
import { ITypeScriptServiceClient } from '../typescriptService';
import { ITypeScriptServiceClient, ServerResponse } from '../typescriptService';
import { escapeRegExp } from '../utils/regexp';
import * as typeConverters from '../utils/typeConverters';
......@@ -21,13 +21,13 @@ export class ReferencesCodeLens extends vscode.CodeLens {
}
export class CachedNavTreeResponse {
private response?: Promise<Proto.NavTreeResponse>;
private response?: Promise<ServerResponse<Proto.NavTreeResponse>>;
private version: number = -1;
private document: string = '';
public execute(
document: vscode.TextDocument,
f: () => Promise<Proto.NavTreeResponse>
f: () => Promise<ServerResponse<Proto.NavTreeResponse>>
) {
if (this.matches(document)) {
return this.response;
......@@ -42,8 +42,8 @@ export class CachedNavTreeResponse {
private update(
document: vscode.TextDocument,
response: Promise<Proto.NavTreeResponse>
): Promise<Proto.NavTreeResponse> {
response: Promise<ServerResponse<Proto.NavTreeResponse>>
): Promise<ServerResponse<Proto.NavTreeResponse>> {
this.response = response;
this.version = document.version;
this.document = document.uri.toString();
......@@ -69,21 +69,17 @@ export abstract class TypeScriptBaseCodeLensProvider implements vscode.CodeLensP
return [];
}
try {
const response = await this.cachedResponse.execute(document, () => this.client.execute('navtree', { file: filepath }, token));
if (!response) {
return [];
}
const tree = response.body;
const referenceableSpans: vscode.Range[] = [];
if (tree && tree.childItems) {
tree.childItems.forEach(item => this.walkNavTree(document, item, null, referenceableSpans));
}
return referenceableSpans.map(span => new ReferencesCodeLens(document.uri, filepath, span));
} catch {
const response = await this.cachedResponse.execute(document, () => this.client.execute('navtree', { file: filepath }, token));
if (!response || response.type !== 'response') {
return [];
}
const tree = response.body;
const referenceableSpans: vscode.Range[] = [];
if (tree && tree.childItems) {
tree.childItems.forEach(item => this.walkNavTree(document, item, null, referenceableSpans));
}
return referenceableSpans.map(span => new ReferencesCodeLens(document.uri, filepath, span));
}
protected abstract extractSymbol(
......
......@@ -321,23 +321,20 @@ class TypeScriptCompletionItemProvider implements vscode.CompletionItemProvider
let isNewIdentifierLocation = true;
let msg: ReadonlyArray<Proto.CompletionEntry> | undefined = undefined;
try {
if (this.client.apiVersion.gte(API.v300)) {
const { body } = await this.client.interuptGetErr(() => this.client.execute('completionInfo', args, token));
if (!body) {
return null;
}
isNewIdentifierLocation = body.isNewIdentifierLocation;
msg = body.entries;
} else {
const { body } = await this.client.interuptGetErr(() => this.client.execute('completions', args, token));
if (!body) {
return null;
}
msg = body;
if (this.client.apiVersion.gte(API.v300)) {
const response = await this.client.interuptGetErr(() => this.client.execute('completionInfo', args, token));
if (response.type !== 'response' || !response.body) {
return null;
}
} catch {
return null;
isNewIdentifierLocation = response.body.isNewIdentifierLocation;
msg = response.body.entries;
} else {
const response = await this.client.interuptGetErr(() => this.client.execute('completions', args, token));
if (response.type !== 'response' || !response.body) {
return null;
}
msg = response.body;
}
const isInValidCommitCharacterContext = this.isInValidCommitCharacterContext(document, position);
......@@ -371,12 +368,12 @@ class TypeScriptCompletionItemProvider implements vscode.CompletionItemProvider
};
let details: Proto.CompletionEntryDetails[] | undefined;
try {
const { body } = await this.client.execute('completionEntryDetails', args, token);
details = body;
} catch {
const response = await this.client.execute('completionEntryDetails', args, token);
if (response.type !== 'response') {
return item;
}
const { body } = response;
details = body;
if (!details || !details.length || !details[0]) {
return item;
......@@ -528,7 +525,13 @@ class TypeScriptCompletionItemProvider implements vscode.CompletionItemProvider
// Workaround for https://github.com/Microsoft/TypeScript/issues/12677
// Don't complete function calls inside of destructive assigments or imports
try {
const { body } = await this.client.execute('quickinfo', typeConverters.Position.toFileLocationRequestArgs(filepath, position), token);
const args: Proto.FileLocationRequestArgs = typeConverters.Position.toFileLocationRequestArgs(filepath, position);
const response = await this.client.execute('quickinfo', args, token);
if (response.type !== 'response') {
return true;
}
const { body } = response;
switch (body && body.kind) {
case 'var':
case 'let':
......
......@@ -26,13 +26,14 @@ export default class TypeScriptDefinitionProviderBase {
}
const args = typeConverters.Position.toFileLocationRequestArgs(filepath, position);
try {
const response = await this.client.execute(definitionType, args, token);
const locations: Proto.FileSpan[] = (response && response.body) || [];
return locations.map(location =>
typeConverters.Location.fromTextSpan(this.client.toResource(location.file), location));
} catch {
const response = await this.client.execute(definitionType, args, token);
if (response.type !== 'response') {
return undefined;
}
const locations: Proto.FileSpan[] = (response && response.body) || [];
return locations.map(location =>
typeConverters.Location.fromTextSpan(this.client.toResource(location.file), location));
}
}
\ No newline at end of file
......@@ -28,25 +28,21 @@ export default class TypeScriptDefinitionProvider extends DefinitionProviderBase
}
const args = typeConverters.Position.toFileLocationRequestArgs(filepath, position);
try {
const { body } = await this.client.execute('definitionAndBoundSpan', args, token);
if (!body) {
return undefined;
}
const span = body.textSpan ? typeConverters.Range.fromTextSpan(body.textSpan) : undefined;
return body.definitions
.map(location => {
const target = typeConverters.Location.fromTextSpan(this.client.toResource(location.file), location);
return <vscode.DefinitionLink>{
originSelectionRange: span,
targetRange: target.range,
targetUri: target.uri,
};
});
} catch {
return [];
const response = await this.client.execute('definitionAndBoundSpan', args, token);
if (response.type !== 'response' || !response.body) {
return undefined;
}
const span = response.body.textSpan ? typeConverters.Range.fromTextSpan(response.body.textSpan) : undefined;
return response.body.definitions
.map(location => {
const target = typeConverters.Location.fromTextSpan(this.client.toResource(location.file), location);
return <vscode.DefinitionLink>{
originSelectionRange: span,
targetRange: target.range,
targetUri: target.uri,
};
});
}
return this.getSymbolLocations('definition', document, position, token);
......
......@@ -26,18 +26,12 @@ class TypeScriptDocumentHighlightProvider implements vscode.DocumentHighlightPro
}
const args = typeConverters.Position.toFileLocationRequestArgs(file, position);
let items: Proto.OccurrencesResponseItem[];
try {
const { body } = await this.client.execute('occurrences', args, token);
if (!body) {
return [];
}
items = body;
} catch {
const response = await this.client.execute('occurrences', args, token);
if (response.type !== 'response' || !response.body) {
return [];
}
return items
return response.body
.filter(x => !x.isInString)
.map(documentHighlightFromOccurance);
}
......
......@@ -40,18 +40,13 @@ class TypeScriptDocumentSymbolProvider implements vscode.DocumentSymbolProvider
return undefined;
}
let tree: Proto.NavigationTree;
try {
const args: Proto.FileRequestArgs = { file };
const { body } = await this.client.execute('navtree', args, token);
if (!body) {
return undefined;
}
tree = body;
} catch {
const args: Proto.FileRequestArgs = { file };
const response = await this.client.execute('navtree', args, token);
if (response.type !== 'response' || !response.body) {
return undefined;
}
let tree = response.body;
if (tree && tree.childItems) {
// The root represents the file. Ignore this when showing in the UI
const result: vscode.DocumentSymbol[] = [];
......
......@@ -26,18 +26,12 @@ class TypeScriptFoldingProvider implements vscode.FoldingRangeProvider {
}
const args: Proto.FileRequestArgs = { file };
let body: Proto.OutliningSpan[] | undefined;
try {
body = (await this.client.execute('getOutliningSpans', args, token)).body;
} catch {
// noop
}
if (!body) {
const response = await this.client.execute('getOutliningSpans', args, token);
if (response.type !== 'response' || !response.body) {
return;
}
return body
return response.body
.map(span => this.convertOutliningSpan(span, document))
.filter(foldingRange => !!foldingRange) as vscode.FoldingRange[];
}
......
......@@ -29,19 +29,13 @@ class TypeScriptFormattingProvider implements vscode.DocumentRangeFormattingEdit
await this.formattingOptionsManager.ensureConfigurationOptions(document, options, token);
let edits: Proto.CodeEdit[];
try {
const args = typeConverters.Range.toFormattingRequestArgs(file, range);
const { body } = await this.client.execute('format', args, token);
if (!body) {
return undefined;
}
edits = body;
} catch {
const args = typeConverters.Range.toFormattingRequestArgs(file, range);
const response = await this.client.execute('format', args, token);
if (response.type !== 'response' || !response.body) {
return undefined;
}
return edits.map(typeConverters.TextEdit.fromCodeEdit);
return response.body.map(typeConverters.TextEdit.fromCodeEdit);
}
public async provideOnTypeFormattingEdits(
......@@ -62,35 +56,33 @@ class TypeScriptFormattingProvider implements vscode.DocumentRangeFormattingEdit
...typeConverters.Position.toFileLocationRequestArgs(file, position),
key: ch
};
try {
const { body } = await this.client.execute('formatonkey', args, token);
const edits = body;
const result: vscode.TextEdit[] = [];
if (!edits) {
return result;
}
for (const edit of edits) {
const textEdit = typeConverters.TextEdit.fromCodeEdit(edit);
const range = textEdit.range;
// Work around for https://github.com/Microsoft/TypeScript/issues/6700.
// Check if we have an edit at the beginning of the line which only removes white spaces and leaves
// an empty line. Drop those edits
if (range.start.character === 0 && range.start.line === range.end.line && textEdit.newText === '') {
const lText = document.lineAt(range.start.line).text;
// If the edit leaves something on the line keep the edit (note that the end character is exclusive).
// Keep it also if it removes something else than whitespace
if (lText.trim().length > 0 || lText.length > range.end.character) {
result.push(textEdit);
}
} else {
const response = await this.client.execute('formatonkey', args, token);
if (response.type !== 'response' || !response.body) {
return [];
}
const edits = response.body;
const result: vscode.TextEdit[] = [];
if (!edits) {
return result;
}
for (const edit of edits) {
const textEdit = typeConverters.TextEdit.fromCodeEdit(edit);
const range = textEdit.range;
// Work around for https://github.com/Microsoft/TypeScript/issues/6700.
// Check if we have an edit at the beginning of the line which only removes white spaces and leaves
// an empty line. Drop those edits
if (range.start.character === 0 && range.start.line === range.end.line && textEdit.newText === '') {
const lText = document.lineAt(range.start.line).text;
// If the edit leaves something on the line keep the edit (note that the end character is exclusive).
// Keep it also if it removes something else than whitespace
if (lText.trim().length > 0 || lText.length > range.end.character) {
result.push(textEdit);
}
} else {
result.push(textEdit);
}
return result;
} catch {
// noop
}
return [];
return result;
}
}
......
......@@ -27,17 +27,14 @@ class TypeScriptHoverProvider implements vscode.HoverProvider {
}
const args = typeConverters.Position.toFileLocationRequestArgs(filepath, position);
try {
const { body } = await this.client.interuptGetErr(() => this.client.execute('quickinfo', args, token));
if (body) {
return new vscode.Hover(
TypeScriptHoverProvider.getContents(body),
typeConverters.Range.fromTextSpan(body));
}
} catch (e) {
// noop
const response = await this.client.interuptGetErr(() => this.client.execute('quickinfo', args, token));
if (response.type !== 'response' || !response.body) {
return undefined;
}
return undefined;
return new vscode.Hover(
TypeScriptHoverProvider.getContents(response.body),
typeConverters.Range.fromTextSpan(response.body));
}
private static getContents(
......
......@@ -5,7 +5,6 @@
import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
import * as Proto from '../protocol';
import { ITypeScriptServiceClient } from '../typescriptService';
import { ConfigurationDependentRegistration } from '../utils/dependentRegistration';
import * as typeConverters from '../utils/typeConverters';
......@@ -55,14 +54,8 @@ class JsDocCompletionProvider implements vscode.CompletionItemProvider {
}
const args = typeConverters.Position.toFileLocationRequestArgs(file, position);
let res: Proto.DocCommandTemplateResponse | undefined;
try {
res = await this.client.execute('docCommentTemplate', args, token);
} catch {
return undefined;
}
if (!res.body) {
const response = await this.client.execute('docCommentTemplate', args, token);
if (response.type !== 'response' || !response.body) {
return undefined;
}
......@@ -71,10 +64,10 @@ class JsDocCompletionProvider implements vscode.CompletionItemProvider {
// Workaround for #43619
// docCommentTemplate previously returned undefined for empty jsdoc templates.
// TS 2.7 now returns a single line doc comment, which breaks indentation.
if (res.body.newText === '/** */') {
if (response.body.newText === '/** */') {
item.insertText = defaultJsDoc;
} else {
item.insertText = templateToSnippet(res.body.newText);
item.insertText = templateToSnippet(response.body.newText);
}
return [item];
......
......@@ -46,8 +46,12 @@ class OrganizeImportsCommand implements Command {
}
}
};
const { body } = await this.client.execute('organizeImports', args, nulToken);
const edits = typeconverts.WorkspaceEdit.fromFileCodeEdits(this.client, body);
const response = await this.client.execute('organizeImports', args, nulToken);
if (response.type !== 'response' || !response.body) {
return false;
}
const edits = typeconverts.WorkspaceEdit.fromFileCodeEdits(this.client, response.body);
return vscode.workspace.applyEdit(edits);
}
}
......
......@@ -85,18 +85,14 @@ class ApplyFixAllCodeAction implements Command {
fixId: tsAction.fixId
};
try {
const { body } = await this.client.execute('getCombinedCodeFix', args, nulToken);
if (!body) {
return;
}
const edit = typeConverters.WorkspaceEdit.fromFileCodeEdits(this.client, body.changes);
await vscode.workspace.applyEdit(edit);
await applyCodeActionCommands(this.client, body.commands, nulToken);
} catch {
// noop
const response = await this.client.execute('getCombinedCodeFix', args, nulToken);
if (response.type !== 'response' || !response.body) {
return undefined;
}
const edit = typeConverters.WorkspaceEdit.fromFileCodeEdits(this.client, response.body.changes);
await vscode.workspace.applyEdit(edit);
await applyCodeActionCommands(this.client, response.body.commands, nulToken);
}
}
......@@ -172,7 +168,7 @@ class SupportedCodeActionProvider {
private get supportedCodeActions(): Thenable<Set<number>> {
if (!this._supportedCodeActions) {
this._supportedCodeActions = this.client.execute('getSupportedCodeFixes', null, nulToken)
.then(response => response.body || [])
.then(response => response.type === 'response' ? response.body || [] : [])
.then(codes => codes.map(code => +code).filter(code => !isNaN(code)))
.then(codes => new Set(codes));
}
......@@ -240,13 +236,13 @@ class TypeScriptQuickFixProvider implements vscode.CodeActionProvider {
...typeConverters.Range.toFileRangeRequestArgs(file, diagnostic.range),
errorCodes: [+(diagnostic.code!)]
};
const { body } = await this.client.execute('getCodeFixes', args, token);
if (!body) {
const response = await this.client.execute('getCodeFixes', args, token);
if (response.type !== 'response' || !response.body) {
return [];
}
const results = new CodeActionSet();
for (const tsCodeFix of body) {
for (const tsCodeFix of response.body) {
this.addAllFixesForTsCodeAction(results, document, file, diagnostic, tsCodeFix);
}
return results.values;
......
......@@ -48,17 +48,17 @@ class ApplyRefactoringCommand implements Command {
refactor,
action
};
const { body } = await this.client.execute('getEditsForRefactor', args, nulToken);
if (!body || !body.edits.length) {
const response = await this.client.execute('getEditsForRefactor', args, nulToken);
if (response.type !== 'response' || !response.body || !response.body.edits.length) {
return false;
}
const workspaceEdit = await this.toWorkspaceEdit(body);
const workspaceEdit = await this.toWorkspaceEdit(response.body);
if (!(await vscode.workspace.applyEdit(workspaceEdit))) {
return false;
}
const renameLocation = body.renameLocation;
const renameLocation = response.body.renameLocation;
if (renameLocation) {
await vscode.commands.executeCommand('editor.action.rename', [
document.uri,
......@@ -140,18 +140,12 @@ class TypeScriptRefactorProvider implements vscode.CodeActionProvider {
await this.formattingOptionsManager.ensureConfigurationForDocument(document, token);
const args: Proto.GetApplicableRefactorsRequestArgs = typeConverters.Range.toFileRangeRequestArgs(file, rangeOrSelection);
let refactorings: Proto.ApplicableRefactorInfo[];
try {
const { body } = await this.client.execute('getApplicableRefactors', args, token);
if (!body) {
return undefined;
}
refactorings = body;
} catch {
const response = await this.client.execute('getApplicableRefactors', args, token);
if (response.type !== 'response' || !response.body) {
return undefined;
}
return this.convertApplicableRefactors(refactorings, document, file, rangeOrSelection);
return this.convertApplicableRefactors(response.body, document, file, rangeOrSelection);
}
private convertApplicableRefactors(
......
......@@ -25,25 +25,22 @@ class TypeScriptReferenceSupport implements vscode.ReferenceProvider {
}
const args = typeConverters.Position.toFileLocationRequestArgs(filepath, position);
try {
const { body } = await this.client.execute('references', args, token);
if (!body) {
return [];
}
const result: vscode.Location[] = [];
const has203Features = this.client.apiVersion.gte(API.v203);
for (const ref of body.refs) {
if (!options.includeDeclaration && has203Features && ref.isDefinition) {
continue;
}
const url = this.client.toResource(ref.file);
const location = typeConverters.Location.fromTextSpan(url, ref);
result.push(location);
}
return result;
} catch {
const response = await this.client.execute('references', args, token);
if (response.type !== 'response' || !response.body) {
return [];
}
const result: vscode.Location[] = [];
const has203Features = this.client.apiVersion.gte(API.v203);
for (const ref of response.body.refs) {
if (!options.includeDeclaration && has203Features && ref.isDefinition) {
continue;
}
const url = this.client.toResource(ref.file);
const location = typeConverters.Location.fromTextSpan(url, ref);
result.push(location);
}
return result;
}
}
......
......@@ -6,7 +6,7 @@
import * as path from 'path';
import * as vscode from 'vscode';
import * as Proto from '../protocol';
import { ITypeScriptServiceClient } from '../typescriptService';
import { ITypeScriptServiceClient, ServerResponse } from '../typescriptService';
import API from '../utils/api';
import * as typeConverters from '../utils/typeConverters';
......@@ -21,12 +21,12 @@ class TypeScriptRenameProvider implements vscode.RenameProvider {
position: vscode.Position,
token: vscode.CancellationToken
): Promise<vscode.Range | null> {
const body = await this.execRename(document, position, token);
if (!body) {
const response = await this.execRename(document, position, token);
if (!response || response.type !== 'response' || !response.body) {
return null;
}
const renameInfo = body.info;
const renameInfo = response.body.info;
if (!renameInfo.canRename) {
return Promise.reject<vscode.Range>(renameInfo.localizedErrorMessage);
}
......@@ -47,12 +47,12 @@ class TypeScriptRenameProvider implements vscode.RenameProvider {
newName: string,
token: vscode.CancellationToken
): Promise<vscode.WorkspaceEdit | null> {
const body = await this.execRename(document, position, token);
if (!body) {
const response = await this.execRename(document, position, token);
if (!response || response.type !== 'response' || !response.body) {
return null;
}
const renameInfo = body.info;
const renameInfo = response.body.info;
if (!renameInfo.canRename) {
return Promise.reject<vscode.WorkspaceEdit>(renameInfo.localizedErrorMessage);
}
......@@ -63,7 +63,7 @@ class TypeScriptRenameProvider implements vscode.RenameProvider {
this.renameFile(edit, renameInfo.fileToRename, newName);
}
}
this.updateLocs(edit, body.locs, newName);
this.updateLocs(edit, response.body.locs, newName);
return edit;
}
......@@ -71,7 +71,7 @@ class TypeScriptRenameProvider implements vscode.RenameProvider {
document: vscode.TextDocument,
position: vscode.Position,
token: vscode.CancellationToken
): Promise<Proto.RenameResponseBody | undefined> {
): Promise<ServerResponse<Proto.RenameResponse> | undefined> {
const file = this.client.toPath(document.uri);
if (!file) {
return undefined;
......@@ -83,12 +83,7 @@ class TypeScriptRenameProvider implements vscode.RenameProvider {
findInComments: false
};
try {
return (await this.client.execute('rename', args, token)).body;
} catch {
// noop
return undefined;
}
return this.client.execute('rename', args, token);
}
private updateLocs(
......
......@@ -27,22 +27,17 @@ class TypeScriptSignatureHelpProvider implements vscode.SignatureHelpProvider {
if (!filepath) {
return undefined;
}
const args: Proto.SignatureHelpRequestArgs = {
...typeConverters.Position.toFileLocationRequestArgs(filepath, position),
triggerReason: toTsTriggerReason(context!)
};
let info: Proto.SignatureHelpItems;
try {
const { body } = await this.client.execute('signatureHelp', args, token);
if (!body) {
return undefined;
}
info = body;
} catch {
const response = await this.client.execute('signatureHelp', args, token);
if (response.type !== 'response' || !response.body) {
return undefined;
}
const info = response.body;
const result = new vscode.SignatureHelp();
result.activeSignature = info.selectedItemIndex;
result.activeParameter = this.getActiveParmeter(info);
......
......@@ -90,17 +90,11 @@ class TagClosing extends Disposable {
}
let position = new vscode.Position(rangeStart.line, rangeStart.character + lastChange.text.length);
let insertion: Proto.TextInsertion;
const args: Proto.JsxClosingTagRequestArgs = typeConverters.Position.toFileLocationRequestArgs(filepath, position);
this._cancel = new vscode.CancellationTokenSource();
try {
const { body } = await this.client.execute('jsxClosingTag', args, this._cancel.token);
if (!body) {
return;
}
insertion = body;
} catch {
const response = await this.client.execute('jsxClosingTag', args, this._cancel.token);
if (response.type !== 'response' || !response.body) {
return;
}
......@@ -113,6 +107,7 @@ class TagClosing extends Disposable {
return;
}
const insertion = response.body;
const activeDocument = activeEditor.document;
if (document === activeDocument && activeDocument.version === version) {
activeEditor.insertSnippet(
......
......@@ -7,7 +7,6 @@ import * as fs from 'fs';
import * as path from 'path';
import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
import * as Proto from '../protocol';
import { ITypeScriptServiceClient } from '../typescriptService';
import { Lazy } from '../utils/lazy';
import { isImplicitProjectConfigFile } from '../utils/tsconfig';
......@@ -105,29 +104,25 @@ class TscTaskProvider implements vscode.TaskProvider {
return [];
}
try {
const res: Proto.ProjectInfoResponse = await this.client.value.execute(
'projectInfo',
{ file, needFileNameList: false },
token);
if (!res || !res.body) {
return [];
}
const response = await this.client.value.execute(
'projectInfo',
{ file, needFileNameList: false },
token);
if (response.type !== 'response' || !response.body) {
return [];
}
const { configFileName } = res.body;
if (configFileName && !isImplicitProjectConfigFile(configFileName)) {
const normalizedConfigPath = path.normalize(configFileName);
const uri = vscode.Uri.file(normalizedConfigPath);
const folder = vscode.workspace.getWorkspaceFolder(uri);
return [{
path: normalizedConfigPath,
workspaceFolder: folder
}];
}
} catch (e) {
// noop
const { configFileName } = response.body;
if (configFileName && !isImplicitProjectConfigFile(configFileName)) {
const normalizedConfigPath = path.normalize(configFileName);
const uri = vscode.Uri.file(normalizedConfigPath);
const folder = vscode.workspace.getWorkspaceFolder(uri);
return [{
path: normalizedConfigPath,
workspaceFolder: folder
}];
}
return [];
}
......
......@@ -85,11 +85,15 @@ class UpdateImportsOnFileRenameHandler {
// Workaround for https://github.com/Microsoft/vscode/issues/52967
// Never attempt to update import paths if the file does not contain something the looks like an export
try {
const { body } = await this.client.execute('navtree', { file: newFile }, nulToken);
const response = await this.client.execute('navtree', { file: newFile }, nulToken);
if (response.type !== 'response' || !response.body) {
return;
}
const hasExport = (node: Proto.NavigationTree): boolean => {
return !!node.kindModifiers.match(/\bexports?\b/g) || !!(node.childItems && node.childItems.some(hasExport));
};
if (!body || !hasExport(body)) {
if (!hasExport(response.body)) {
return;
}
} catch {
......@@ -238,7 +242,11 @@ class UpdateImportsOnFileRenameHandler {
newFilePath: newFile,
};
const response = await this.client.execute('getEditsForFileRename', args, nulToken);
if (!response || !response.body) {
if (response.type !== 'response') {
return;
}
if (!response.body) {
return;
}
......
......@@ -45,20 +45,13 @@ class TypeScriptWorkspaceSymbolProvider implements vscode.WorkspaceSymbolProvide
searchValue: search
};
let response: Proto.NavtoResponse;
try {
response = await this.client.execute('navto', args, token);
} catch {
return [];
}
const { body } = response;
if (!body) {
const response = await this.client.execute('navto', args, token);
if (response.type !== 'response' || !response.body) {
return [];
}
const result: vscode.SymbolInformation[] = [];
for (const item of body) {
for (const item of response.body) {
if (!item.containerName && item.kind === 'alias') {
continue;
}
......
......@@ -4,22 +4,23 @@
*--------------------------------------------------------------------------------------------*/
import * as cp from 'child_process';
import * as path from 'path';
import * as fs from 'fs';
import * as path from 'path';
import * as vscode from 'vscode';
import * as Proto from './protocol';
import { CancelledResponse, NoContentResponse, ServerResponse } from './typescriptService';
import API from './utils/api';
import { TsServerLogLevel, TypeScriptServiceConfiguration } from './utils/configuration';
import { Disposable } from './utils/dispose';
import * as electron from './utils/electron';
import LogDirectoryProvider from './utils/logDirectoryProvider';
import Logger from './utils/logger';
import { TypeScriptPluginPathsProvider } from './utils/pluginPathsProvider';
import { TypeScriptServerPlugin } from './utils/plugins';
import TelemetryReporter from './utils/telemetry';
import Tracer from './utils/tracer';
import { Reader } from './utils/wireProtocol';
import { TypeScriptVersion, TypeScriptVersionProvider } from './utils/versionProvider';
import API from './utils/api';
import { TypeScriptServiceConfiguration, TsServerLogLevel } from './utils/configuration';
import { TypeScriptServerPlugin } from './utils/plugins';
import { TypeScriptPluginPathsProvider } from './utils/pluginPathsProvider';
import LogDirectoryProvider from './utils/logDirectoryProvider';
import { Reader } from './utils/wireProtocol';
interface CallbackItem<R> {
readonly onSuccess: (value: R) => void;
......@@ -28,23 +29,24 @@ interface CallbackItem<R> {
readonly isAsync: boolean;
}
class CallbackMap<R> {
private readonly _callbacks = new Map<number, CallbackItem<R>>();
private readonly _asyncCallbacks = new Map<number, CallbackItem<R>>();
class CallbackMap<R extends Proto.Response> {
private readonly _callbacks = new Map<number, CallbackItem<ServerResponse<R> | undefined>>();
private readonly _asyncCallbacks = new Map<number, CallbackItem<ServerResponse<R> | undefined>>();
public destroy(cause: Error): void {
public destroy(cause: string): void {
const cancellation = new CancelledResponse(cause);
for (const callback of this._callbacks.values()) {
callback.onError(cause);
callback.onSuccess(cancellation);
}
this._callbacks.clear();
for (const callback of this._asyncCallbacks.values()) {
callback.onError(cause);
callback.onSuccess(cancellation);
}
this._asyncCallbacks.clear();
}
public add(seq: number, callback: CallbackItem<R>, isAsync: boolean) {
public add(seq: number, callback: CallbackItem<ServerResponse<R> | undefined>, isAsync: boolean) {
if (isAsync) {
this._asyncCallbacks.set(seq, callback);
} else {
......@@ -53,7 +55,7 @@ class CallbackMap<R> {
}
public fetch(seq: number): CallbackItem<R> | undefined {
public fetch(seq: number): CallbackItem<ServerResponse<R> | undefined> | undefined {
const callback = this._callbacks.get(seq) || this._asyncCallbacks.get(seq);
this.delete(seq);
return callback;
......@@ -250,7 +252,7 @@ export class TypeScriptServerSpawner {
export class TypeScriptServer extends Disposable {
private readonly _reader: Reader<Proto.Response>;
private readonly _requestQueue = new RequestQueue();
private readonly _callbacks = new CallbackMap<Proto.Response | undefined>();
private readonly _callbacks = new CallbackMap<Proto.Response>();
private readonly _pendingResponses = new Set<number>();
constructor(
......@@ -287,7 +289,7 @@ export class TypeScriptServer extends Disposable {
public dispose() {
super.dispose();
this._callbacks.destroy(new Error('server disposed'));
this._callbacks.destroy('server disposed');
this._pendingResponses.clear();
}
......@@ -297,12 +299,12 @@ export class TypeScriptServer extends Disposable {
private handleExit(error: any) {
this._onExit.fire(error);
this._callbacks.destroy(new Error('server exited'));
this._callbacks.destroy('server exited');
}
private handleError(error: any) {
this._onError.fire(error);
this._callbacks.destroy(new Error('server errored'));
this._callbacks.destroy('server errored');
}
private dispatchMessage(message: Proto.Message) {
......@@ -335,7 +337,7 @@ export class TypeScriptServer extends Disposable {
}
}
private tryCancelRequest(seq: number): boolean {
private tryCancelRequest(seq: number, command: string): boolean {
try {
if (this._requestQueue.tryCancelPendingRequest(seq)) {
this._tracer.logTrace(`TypeScript Server: canceled request with sequence number ${seq}`);
......@@ -357,7 +359,7 @@ export class TypeScriptServer extends Disposable {
} finally {
const callback = this.fetchCallback(seq);
if (callback) {
callback.onError(new Error(`Cancelled Request ${seq}`));
callback.onSuccess(new CancelledResponse(`Cancelled request ${seq} - ${command}`));
}
}
}
......@@ -371,6 +373,9 @@ export class TypeScriptServer extends Disposable {
this._tracer.traceResponse(response, callback.startTime);
if (response.success) {
callback.onSuccess(response);
} else if (response.message === 'No content available.') {
// Special case where response itself is successful but there is not any data to return.
callback.onSuccess(new NoContentResponse());
} else {
callback.onError(response);
}
......@@ -392,7 +397,7 @@ export class TypeScriptServer extends Disposable {
if (executeInfo.token) {
executeInfo.token.onCancellationRequested(() => {
wasCancelled = true;
this.tryCancelRequest(request.seq);
this.tryCancelRequest(request.seq, command);
});
}
}).catch((err: any) => {
......
......@@ -11,6 +11,20 @@ import { TypeScriptServiceConfiguration } from './utils/configuration';
import Logger from './utils/logger';
import { TypeScriptServerPlugin } from './utils/plugins';
export class CancelledResponse {
readonly type: 'cancelled' = 'cancelled';
constructor(
public readonly reason: string
) { }
}
export class NoContentResponse {
readonly type: 'noContent' = 'noContent';
}
export type ServerResponse<T extends Proto.Response> = T | CancelledResponse | NoContentResponse;
interface TypeScriptRequestTypes {
'applyCodeActionCommand': [Proto.ApplyCodeActionCommandRequestArgs, Proto.ApplyCodeActionCommandResponse];
'completionEntryDetails': [Proto.CompletionDetailsRequestArgs, Proto.CompletionDetailsResponse];
......@@ -82,7 +96,7 @@ export interface ITypeScriptServiceClient {
command: K,
args: TypeScriptRequestTypes[K][0],
token: vscode.CancellationToken
): Promise<TypeScriptRequestTypes[K][1]>;
): Promise<ServerResponse<TypeScriptRequestTypes[K][1]>>;
executeWithoutWaitingForResponse(command: 'open', args: Proto.OpenRequestArgs): void;
executeWithoutWaitingForResponse(command: 'close', args: Proto.FileRequestArgs): void;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册