Skip to content

Commit

Permalink
Merge pull request #2332 from yoyo930021/impl-cancellationToken
Browse files Browse the repository at this point in the history
Stop computing outdated diagnostics with CancellationToken
  • Loading branch information
octref authored Oct 30, 2020
2 parents b624efb + c314a09 commit e21eae3
Show file tree
Hide file tree
Showing 9 changed files with 128 additions and 23 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
- 🙌 Complete with `?.` for optional properies in completion. Thanks to contribution from [@yoyo930021](https://github.com/yoyo930021). #2326 and #2357.
- 🙌 Respect typescript language settings. Thanks to contribution from [@yoyo930021](https://github.com/yoyo930021). #2109 and #2375.
- 🙌 Slim syntax highlighting. Thanks to contribution from [@Antti](https://github.com/Antti).
- 🙌 Stop computing outdated diagnostics with CancellationToken. Thanks to contribution from [@yoyo930021](https://github.com/yoyo930021). #1263 and #2332.

### 0.28.0 | 2020-09-23 | [VSIX](https://marketplace.visualstudio.com/_apis/public/gallery/publishers/octref/vsextensions/vetur/0.28.0/vspackage)

Expand Down
3 changes: 2 additions & 1 deletion server/src/embeddedSupport/languageModes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import { getServiceHost, IServiceHost } from '../services/typescriptService/serv
import { VLSFullConfig } from '../config';
import { SassLanguageMode } from '../modes/style/sass/sassLanguageMode';
import { getPugMode } from '../modes/pug';
import { VCancellationToken } from '../utils/cancellationToken';

export interface VLSServices {
infoService?: VueInfoService;
Expand All @@ -49,7 +50,7 @@ export interface LanguageMode {
configure?(options: VLSFullConfig): void;
updateFileInfo?(doc: TextDocument): void;

doValidation?(document: TextDocument): Diagnostic[];
doValidation?(document: TextDocument, cancellationToken?: VCancellationToken): Promise<Diagnostic[]>;
getCodeActions?(
document: TextDocument,
range: Range,
Expand Down
29 changes: 25 additions & 4 deletions server/src/modes/script/javascript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import { RefactorAction } from '../../types';
import { IServiceHost } from '../../services/typescriptService/serviceHost';
import { toCompletionItemKind, toSymbolKind } from '../../services/typescriptService/util';
import * as Previewer from './previewer';
import { isVCancellationRequested, VCancellationToken } from '../../utils/cancellationToken';

// Todo: After upgrading to LS server 4.0, use CompletionContext for filtering trigger chars
// https://microsoft.github.io/language-server-protocol/specification#completion-request-leftwards_arrow_with_hook
Expand Down Expand Up @@ -145,18 +146,38 @@ export async function getJavascriptMode(
}
},

doValidation(doc: TextDocument): Diagnostic[] {
async doValidation(doc: TextDocument, cancellationToken?: VCancellationToken): Promise<Diagnostic[]> {
if (await isVCancellationRequested(cancellationToken)) {
return [];
}
const { scriptDoc, service } = updateCurrentVueTextDocument(doc);
if (!languageServiceIncludesFile(service, doc.uri)) {
return [];
}

if (await isVCancellationRequested(cancellationToken)) {
return [];
}
const fileFsPath = getFileFsPath(doc.uri);
const rawScriptDiagnostics = [
...service.getSyntacticDiagnostics(fileFsPath),
...service.getSemanticDiagnostics(fileFsPath)
const program = service.getProgram();
const sourceFile = program?.getSourceFile(fileFsPath);
if (!program || !sourceFile) {
return [];
}

let rawScriptDiagnostics = [
...program.getSyntacticDiagnostics(sourceFile, cancellationToken?.tsToken),
...program.getSemanticDiagnostics(sourceFile, cancellationToken?.tsToken)
];

const compilerOptions = program.getCompilerOptions();
if (compilerOptions.declaration || compilerOptions.composite) {
rawScriptDiagnostics = [
...rawScriptDiagnostics,
...program.getDeclarationDiagnostics(sourceFile, cancellationToken?.tsToken)
];
}

return rawScriptDiagnostics.map(diag => {
const tags: DiagnosticTag[] = [];

Expand Down
2 changes: 1 addition & 1 deletion server/src/modes/style/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ function getStyleMode(
languageService.configure(c && c.css);
config = c;
},
doValidation(document) {
async doValidation(document) {
if (languageId === 'postcss') {
return [];
} else {
Expand Down
9 changes: 8 additions & 1 deletion server/src/modes/template/htmlMode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { getComponentInfoTagProvider } from './tagProviders/componentInfoTagProv
import { VueVersion } from '../../services/typescriptService/vueVersion';
import { doPropValidation } from './services/vuePropValidation';
import { getFoldingRanges } from './services/htmlFolding';
import { isVCancellationRequested, VCancellationToken } from '../../utils/cancellationToken';

export class HTMLMode implements LanguageMode {
private tagProviderSettings: CompletionConfiguration;
Expand Down Expand Up @@ -60,16 +61,22 @@ export class HTMLMode implements LanguageMode {
this.config = c;
}

doValidation(document: TextDocument) {
async doValidation(document: TextDocument, cancellationToken?: VCancellationToken) {
const diagnostics = [];

if (await isVCancellationRequested(cancellationToken)) {
return [];
}
if (this.config.vetur.validation.templateProps) {
const info = this.vueInfoService ? this.vueInfoService.getInfo(document) : undefined;
if (info && info.componentInfo.childComponents) {
diagnostics.push(...doPropValidation(document, this.vueDocuments.refreshAndGet(document), info));
}
}

if (await isVCancellationRequested(cancellationToken)) {
return diagnostics;
}
if (this.config.vetur.validation.template) {
const embedded = this.embeddedDocuments.refreshAndGet(document);
diagnostics.push(...doESLintValidation(embedded, this.lintEngine));
Expand Down
8 changes: 6 additions & 2 deletions server/src/modes/template/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { IServiceHost } from '../../services/typescriptService/serviceHost';
import { T_TypeScript } from '../../services/dependencyService';
import { HTMLDocument, parseHTMLDocument } from './parser/htmlParser';
import { inferVueVersion } from '../../services/typescriptService/vueVersion';
import { VCancellationToken } from '../../utils/cancellationToken';

type DocumentRegionCache = LanguageModelCache<VueDocumentRegions>;

Expand Down Expand Up @@ -47,8 +48,11 @@ export class VueHTMLMode implements LanguageMode {
queryVirtualFileInfo(fileName: string, currFileText: string) {
return this.vueInterpolationMode.queryVirtualFileInfo(fileName, currFileText);
}
doValidation(document: TextDocument) {
return this.htmlMode.doValidation(document).concat(this.vueInterpolationMode.doValidation(document));
async doValidation(document: TextDocument, cancellationToken?: VCancellationToken) {
return Promise.all([
this.vueInterpolationMode.doValidation(document, cancellationToken),
this.htmlMode.doValidation(document, cancellationToken)
]).then(result => [...result[0], ...result[1]]);
}
doComplete(document: TextDocument, position: Position) {
const htmlList = this.htmlMode.doComplete(document, position);
Expand Down
11 changes: 10 additions & 1 deletion server/src/modes/template/interpolationMode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { mapBackRange, mapFromPositionToOffset } from '../../services/typescript
import { createTemplateDiagnosticFilter } from '../../services/typescriptService/templateDiagnosticFilter';
import { toCompletionItemKind } from '../../services/typescriptService/util';
import { VueInfoService } from '../../services/vueInfoService';
import { isVCancellationRequested, VCancellationToken } from '../../utils/cancellationToken';
import { getFileFsPath } from '../../utils/paths';
import { NULL_COMPLETION } from '../nullMode';
import { languageServiceIncludesFile } from '../script/javascript';
Expand Down Expand Up @@ -52,14 +53,18 @@ export class VueInterpolationMode implements LanguageMode {
return this.serviceHost.queryVirtualFileInfo(fileName, currFileText);
}

doValidation(document: TextDocument): Diagnostic[] {
async doValidation(document: TextDocument, cancellationToken?: VCancellationToken): Promise<Diagnostic[]> {
if (
!_.get(this.config, ['vetur', 'experimental', 'templateInterpolationService'], true) ||
!this.config.vetur.validation.interpolation
) {
return [];
}

if (await isVCancellationRequested(cancellationToken)) {
return [];
}

// Add suffix to process this doc as vue template.
const templateDoc = TextDocument.create(
document.uri + '.template',
Expand All @@ -81,6 +86,10 @@ export class VueInterpolationMode implements LanguageMode {
return [];
}

if (await isVCancellationRequested(cancellationToken)) {
return [];
}

const templateFileFsPath = getFileFsPath(templateDoc.uri);
// We don't need syntactic diagnostics because
// compiled template is always valid JavaScript syntax.
Expand Down
49 changes: 36 additions & 13 deletions server/src/services/vls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ import {
CompletionTriggerKind,
ExecuteCommandParams,
ApplyWorkspaceEditRequest,
FoldingRangeParams
FoldingRangeParams,
CancellationTokenSource
} from 'vscode-languageserver';
import {
ColorInformation,
Expand All @@ -46,7 +47,7 @@ import { URI } from 'vscode-uri';
import { LanguageModes, LanguageModeRange, LanguageMode } from '../embeddedSupport/languageModes';
import { NULL_COMPLETION, NULL_HOVER, NULL_SIGNATURE } from '../modes/nullMode';
import { VueInfoService } from './vueInfoService';
import { DependencyService } from './dependencyService';
import { DependencyService, State } from './dependencyService';
import * as _ from 'lodash';
import { DocumentContext, RefactorAction } from '../types';
import { DocumentService } from './documentService';
Expand All @@ -55,6 +56,7 @@ import { logger } from '../log';
import { getDefaultVLSConfig, VLSFullConfig, VLSConfig } from '../config';
import { LanguageId } from '../embeddedSupport/embeddedSupport';
import { APPLY_REFACTOR_COMMAND } from '../modes/script/javascript';
import { VCancellationToken, VCancellationTokenSource } from '../utils/cancellationToken';

export class VLS {
// @Todo: Remove this and DocumentContext
Expand All @@ -67,6 +69,7 @@ export class VLS {
private languageModes: LanguageModes;

private pendingValidationRequests: { [uri: string]: NodeJS.Timer } = {};
private cancellationTokenValidationRequests: { [uri: string]: VCancellationTokenSource } = {};
private validationDelayMs = 200;
private validation: { [k: string]: boolean } = {
'vue-html': true,
Expand Down Expand Up @@ -173,10 +176,11 @@ export class VLS {
return (this.languageModes.getMode('vue-html') as VueHTMLMode).queryVirtualFileInfo(fileName, currFileText);
});

this.lspConnection.onRequest('$/getDiagnostics', params => {
this.lspConnection.onRequest('$/getDiagnostics', async params => {
const doc = this.documentService.getDocument(params.uri);
if (doc) {
return this.doValidate(doc);
const diagnostics = await this.doValidate(doc);
return diagnostics ?? [];
}
return [];
});
Expand Down Expand Up @@ -509,12 +513,26 @@ export class VLS {
}

this.cleanPendingValidation(textDocument);
this.cancelPastValidation(textDocument);
this.pendingValidationRequests[textDocument.uri] = setTimeout(() => {
delete this.pendingValidationRequests[textDocument.uri];
this.validateTextDocument(textDocument);
const tsDep = this.dependencyService.getDependency('typescript');
if (tsDep?.state === State.Loaded) {
this.cancellationTokenValidationRequests[textDocument.uri] = new VCancellationTokenSource(tsDep.module);
this.validateTextDocument(textDocument, this.cancellationTokenValidationRequests[textDocument.uri].token);
}
}, this.validationDelayMs);
}

cancelPastValidation(textDocument: TextDocument): void {
const source = this.cancellationTokenValidationRequests[textDocument.uri];
if (source) {
source.cancel();
source.dispose();
delete this.cancellationTokenValidationRequests[textDocument.uri];
}
}

cleanPendingValidation(textDocument: TextDocument): void {
const request = this.pendingValidationRequests[textDocument.uri];
if (request) {
Expand All @@ -523,25 +541,30 @@ export class VLS {
}
}

validateTextDocument(textDocument: TextDocument): void {
const diagnostics: Diagnostic[] = this.doValidate(textDocument);
this.lspConnection.sendDiagnostics({ uri: textDocument.uri, diagnostics });
async validateTextDocument(textDocument: TextDocument, cancellationToken?: VCancellationToken) {
const diagnostics = await this.doValidate(textDocument, cancellationToken);
if (diagnostics) {
this.lspConnection.sendDiagnostics({ uri: textDocument.uri, diagnostics });
}
}

doValidate(doc: TextDocument): Diagnostic[] {
async doValidate(doc: TextDocument, cancellationToken?: VCancellationToken) {
const diagnostics: Diagnostic[] = [];
if (doc.languageId === 'vue') {
this.languageModes.getAllLanguageModeRangesInDocument(doc).forEach(lmr => {
for (const lmr of this.languageModes.getAllLanguageModeRangesInDocument(doc)) {
if (lmr.mode.doValidation) {
if (this.validation[lmr.mode.getId()]) {
pushAll(diagnostics, lmr.mode.doValidation(doc));
pushAll(diagnostics, await lmr.mode.doValidation(doc, cancellationToken));
}
// Special case for template type checking
else if (lmr.mode.getId() === 'vue-html' && this.templateInterpolationValidation) {
pushAll(diagnostics, lmr.mode.doValidation(doc));
pushAll(diagnostics, await lmr.mode.doValidation(doc, cancellationToken));
}
}
});
}
}
if (cancellationToken?.isCancellationRequested) {
return null;
}
return diagnostics;
}
Expand Down
39 changes: 39 additions & 0 deletions server/src/utils/cancellationToken.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import type { T_TypeScript } from '../services/dependencyService';
import { CancellationToken as TSCancellationToken } from 'typescript';
import { CancellationTokenSource, CancellationToken as LSPCancellationToken } from 'vscode-languageserver';

export interface VCancellationToken extends LSPCancellationToken {
tsToken: TSCancellationToken;
}

export class VCancellationTokenSource extends CancellationTokenSource {
constructor(private tsModule: T_TypeScript) {
super();
}

get token(): VCancellationToken {
const operationCancelException = this.tsModule.OperationCanceledException;
const token = super.token as VCancellationToken;
token.tsToken = {
isCancellationRequested() {
return token.isCancellationRequested;
},
throwIfCancellationRequested() {
if (token.isCancellationRequested) {
throw new operationCancelException();
}
}
};
return token;
}
}

export function isVCancellationRequested(token?: VCancellationToken) {
return new Promise(resolve => {
if (!token) {
resolve(false);
} else {
setImmediate(() => resolve(token.isCancellationRequested));
}
});
}

0 comments on commit e21eae3

Please sign in to comment.