Skip to content

Commit

Permalink
perf: use named pipes to signal cancellation
Browse files Browse the repository at this point in the history
  • Loading branch information
johnsoncodehk committed Oct 2, 2022
1 parent 452a51f commit 2e1108c
Show file tree
Hide file tree
Showing 28 changed files with 222 additions and 147 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export function activate(context: vscode.ExtensionContext) {
return commonActivate(context, async (
id,
name,
documentSelector,
langs,
initOptions,
fillInitializeParams,
) => {
Expand All @@ -21,7 +21,7 @@ export function activate(context: vscode.ExtensionContext) {
const serverMain = vscode.Uri.joinPath(context.extensionUri, 'dist/browser/server.js');
const worker = new Worker(serverMain.toString());
const clientOptions: lsp.LanguageClientOptions = {
documentSelector,
documentSelector: langs.map<lsp.DocumentFilter>(lang => ({ language: lang })),
initializationOptions: initOptions,
progressOnInitialization: true,
synchronize: {
Expand Down
31 changes: 15 additions & 16 deletions extensions/vscode-vue-language-features/src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ let client_syntactic: lsp.BaseLanguageClient;
type CreateLanguageClient = (
id: string,
name: string,
documentSelector: lsp.DocumentSelector,
langs: string[],
initOptions: VueServerInitializationOptions,
fillInitializeParams: (params: lsp.InitializeParams) => void,
port: number,
Expand Down Expand Up @@ -190,28 +190,27 @@ export function takeOverModeEnabled() {
return false;
}

function getDocumentSelector(serverMode: ServerMode) {
export function getDocumentSelector(serverMode: ServerMode) {
const takeOverMode = takeOverModeEnabled();
const selector: lsp.DocumentSelector = takeOverMode ?
[
{ language: 'vue' },
{ language: 'javascript' },
{ language: 'typescript' },
{ language: 'javascriptreact' },
{ language: 'typescriptreact' },
] : [
{ language: 'vue' },
];
const langs = takeOverMode ? [
'vue',
'javascript',
'typescript',
'javascriptreact',
'typescriptreact',
] : [
'vue',
];
if (takeOverMode && serverMode === ServerMode.Semantic) {
selector.push({ language: 'json' });
langs.push('json');
}
if (processHtml()) {
selector.push({ language: 'html' });
langs.push('html');
}
if (processMd()) {
selector.push({ language: 'markdown' });
langs.push('markdown');
}
return selector;
return langs;
}

function useSecondServer() {
Expand Down
22 changes: 19 additions & 3 deletions extensions/vscode-vue-language-features/src/nodeClientMain.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,34 @@
import { ServerMode } from '@volar/vue-language-server';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import * as vscode from 'vscode';
import * as lsp from 'vscode-languageclient/node';
import { activate as commonActivate, deactivate as commonDeactivate } from './common';
import { activate as commonActivate, deactivate as commonDeactivate, getDocumentSelector } from './common';
import { middleware } from './middleware';

export function activate(context: vscode.ExtensionContext) {

const cancellationPipeName = path.join(os.tmpdir(), `vscode-${context.extension.id}-cancellation-pipe.tmp`);
const langs = getDocumentSelector(ServerMode.Semantic);

vscode.workspace.onDidChangeTextDocument((e) => {
if (langs.includes(e.document.languageId)) {
fs.writeFileSync(cancellationPipeName, e.document.uri.fsPath + '|' + e.document.version);
}
});

return commonActivate(context, async (
id,
name,
documentSelector,
langs,
initOptions,
fillInitializeParams,
port,
) => {

initOptions.cancellationPipeName = cancellationPipeName;

class _LanguageClient extends lsp.LanguageClient {
fillInitializeParams(params: lsp.InitializeParams) {
fillInitializeParams(params);
Expand Down Expand Up @@ -40,7 +56,7 @@ export function activate(context: vscode.ExtensionContext) {
};
const clientOptions: lsp.LanguageClientOptions = {
middleware,
documentSelector,
documentSelector: langs.map<lsp.DocumentFilter>(lang => ({ language: lang })),
initializationOptions: initOptions,
progressOnInitialization: true,
synchronize: {
Expand Down
2 changes: 1 addition & 1 deletion packages/language-server/src/features/customFeatures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export function register(
if (progress.token.isCancellationRequested) {
continue;
}
let _result = await ls.doValidation(vueFile.uri);
let _result = await ls.doValidation(vueFile.uri, progress.token);
connection.sendDiagnostics({ uri: vueFile.uri, diagnostics: _result });
errors += _result.filter(error => error.severity === vscode.DiagnosticSeverity.Error).length;
warnings += _result.filter(error => error.severity === vscode.DiagnosticSeverity.Warning).length;
Expand Down
65 changes: 39 additions & 26 deletions packages/language-server/src/features/languageFeatures.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import * as embedded from '@volar/language-service';
import * as vscode from 'vscode-languageserver';
import { AutoInsertRequest, FindFileReferenceRequest, ShowReferencesNotification } from '../protocol';
import { CancellactionTokenHost } from '../utils/cancellationPipe';
import type { Workspaces } from '../utils/workspaces';
import * as shared from '@volar/shared';

export function register(
connection: vscode.Connection,
projects: Workspaces,
initParams: vscode.InitializeParams,
cancelHost: CancellactionTokenHost,
) {

let lastCompleteUri: string;
Expand All @@ -15,7 +18,7 @@ export function register(
let lastCodeActionLs: embedded.LanguageService;
let lastCallHierarchyLs: embedded.LanguageService;

connection.onCompletion(async params => {
connection.onCompletion(async (params) => {
return worker(params.textDocument.uri, async vueLs => {
lastCompleteUri = params.textDocument.uri;
lastCompleteLs = vueLs;
Expand All @@ -32,44 +35,43 @@ export function register(
return list;
});
});
connection.onCompletionResolve(async item => {
connection.onCompletionResolve(async (item) => {
if (lastCompleteUri && lastCompleteLs) {
item = await lastCompleteLs.doCompletionResolve(item);
fixTextEdit(item);
}
return item;
});
connection.onHover(async params => {
connection.onHover(async (params) => {
return worker(params.textDocument.uri, vueLs => {
return vueLs.doHover(params.textDocument.uri, params.position);
});
});
connection.onSignatureHelp(async params => {
connection.onSignatureHelp(async (params) => {
return worker(params.textDocument.uri, vueLs => {
return vueLs.getSignatureHelp(params.textDocument.uri, params.position, params.context);
});
});
connection.onPrepareRename(async params => {
connection.onPrepareRename(async (params) => {
return worker(params.textDocument.uri, vueLs => {
return vueLs.prepareRename(params.textDocument.uri, params.position);
});
});
connection.onRenameRequest(async params => {
connection.onRenameRequest(async (params) => {
return worker(params.textDocument.uri, vueLs => {
return vueLs.doRename(params.textDocument.uri, params.position, params.newName);
});
});
connection.onCodeLens(async params => {
connection.onCodeLens(async (params) => {
return worker(params.textDocument.uri, async vueLs => {
lastCodeLensLs = vueLs;
return vueLs.doCodeLens(params.textDocument.uri);
});
});
connection.onCodeLensResolve(async codeLens => {
connection.onCodeLensResolve(async (codeLens) => {
return await lastCodeLensLs?.doCodeLensResolve(codeLens) ?? codeLens;
});
connection.onExecuteCommand(async (params, token, workDoneProgress) => {

if (params.command === embedded.executePluginCommand) {

const args = params.arguments as embedded.ExecutePluginCommandArgs | undefined;
Expand All @@ -87,7 +89,7 @@ export function register(
});
}
});
connection.onCodeAction(async params => {
connection.onCodeAction(async (params) => {
return worker(params.textDocument.uri, async vueLs => {
lastCodeActionLs = vueLs;
let codeActions = await vueLs.doCodeActions(params.textDocument.uri, params.range, params.context) ?? [];
Expand All @@ -105,46 +107,47 @@ export function register(
return codeActions;
});
});
connection.onCodeActionResolve(async codeAction => {
connection.onCodeActionResolve(async (codeAction) => {
return await lastCodeActionLs.doCodeActionResolve(codeAction) ?? codeAction;
});
connection.onReferences(async params => {
connection.onReferences(async (params) => {
return worker(params.textDocument.uri, vueLs => {
return vueLs.findReferences(params.textDocument.uri, params.position);
});
});
connection.onRequest(FindFileReferenceRequest.type, async params => {
connection.onRequest(FindFileReferenceRequest.type, async (params) => {
return worker(params.textDocument.uri, vueLs => {
return vueLs.findFileReferences(params.textDocument.uri);
});
});
connection.onImplementation(async params => {
connection.onImplementation(async (params) => {
return worker(params.textDocument.uri, vueLs => {
return vueLs.findImplementations(params.textDocument.uri, params.position);
});
});
connection.onDefinition(async params => {
connection.onDefinition(async (params) => {
return worker(params.textDocument.uri, vueLs => {
return vueLs.findDefinition(params.textDocument.uri, params.position);
});
});
connection.onTypeDefinition(async params => {
connection.onTypeDefinition(async (params) => {
return worker(params.textDocument.uri, vueLs => {
return vueLs.findTypeDefinition(params.textDocument.uri, params.position);
});
});
connection.onDocumentHighlight(async params => {
connection.onDocumentHighlight(async (params) => {
return worker(params.textDocument.uri, vueLs => {
return vueLs.findDocumentHighlights(params.textDocument.uri, params.position);
});
});
connection.onDocumentLinks(async params => {
connection.onDocumentLinks(async (params) => {
return worker(params.textDocument.uri, vueLs => {
return vueLs.findDocumentLinks(params.textDocument.uri);
});
});
connection.onWorkspaceSymbol(async (params, token) => {


let results: vscode.SymbolInformation[] = [];

for (const _workspace of projects.workspaces.values()) {
Expand All @@ -164,19 +167,21 @@ export function register(

return results;
});
connection.languages.callHierarchy.onPrepare(async params => {
connection.languages.callHierarchy.onPrepare(async (params) => {
return await worker(params.textDocument.uri, async vueLs => {
lastCallHierarchyLs = vueLs;
return vueLs.callHierarchy.doPrepare(params.textDocument.uri, params.position);
}) ?? [];
});
connection.languages.callHierarchy.onIncomingCalls(async params => {
connection.languages.callHierarchy.onIncomingCalls(async (params) => {
return await lastCallHierarchyLs?.callHierarchy.getIncomingCalls(params.item) ?? [];
});
connection.languages.callHierarchy.onOutgoingCalls(async params => {
connection.languages.callHierarchy.onOutgoingCalls(async (params) => {
return await lastCallHierarchyLs?.callHierarchy.getOutgoingCalls(params.item) ?? [];
});
connection.languages.semanticTokens.on(async (params, token, _, resultProgress) => {
await shared.sleep(200);
if (token.isCancellationRequested) return buildTokens([]);
return await worker(params.textDocument.uri, async vueLs => {

const result = await vueLs?.getSemanticTokens(
Expand All @@ -190,6 +195,8 @@ export function register(
}) ?? buildTokens([]);
});
connection.languages.semanticTokens.onRange(async (params, token, _, resultProgress) => {
await shared.sleep(200);
if (token.isCancellationRequested) return buildTokens([]);
return await worker(params.textDocument.uri, async vueLs => {

const result = await vueLs?.getSemanticTokens(
Expand All @@ -202,9 +209,10 @@ export function register(
return buildTokens(result);
}) ?? buildTokens([]);
});
connection.languages.diagnostics.on(async (params, cancellationToken, workDoneProgressReporter, resultProgressReporter) => {
connection.languages.diagnostics.on(async (params, token, workDoneProgressReporter, resultProgressReporter) => {
token = cancelHost.createCancellactionToken(token);
const result = await worker(params.textDocument.uri, vueLs => {
return vueLs.doValidation(params.textDocument.uri, errors => {
return vueLs.doValidation(params.textDocument.uri, token, errors => {
// resultProgressReporter is undefined in vscode
resultProgressReporter?.report({
relatedDocuments: {
Expand All @@ -214,15 +222,15 @@ export function register(
},
},
});
}, cancellationToken);
});
});
return {
kind: vscode.DocumentDiagnosticReportKind.Full,
items: result ?? [],
};
});
connection.languages.inlayHint.on(async params => {
return worker(params.textDocument.uri, vueLs => {
return worker(params.textDocument.uri, async vueLs => {
return vueLs.getInlayHints(params.textDocument.uri, params.range);
});
});
Expand Down Expand Up @@ -253,7 +261,12 @@ export function register(
async function worker<T>(uri: string, cb: (vueLs: embedded.LanguageService) => T) {
const vueLs = await getLanguageService(uri);
if (vueLs) {
return cb(vueLs);
try {
return cb(vueLs); // handle for TS cancel throw
}
catch {
return undefined;
}
}
}
function buildTokens(tokens: embedded.SemanticToken[]) {
Expand Down
5 changes: 4 additions & 1 deletion packages/language-server/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { createDocumentServiceHost } from './utils/documentServiceHost';
import { createSnapshots } from './utils/snapshots';
import { createWorkspaces } from './utils/workspaces';
import { setupSemanticCapabilities, setupSyntacticCapabilities } from './registerFeatures';
import { createCancellactionTokenHost } from './utils/cancellationPipe';

export function createCommonLanguageServer(
connection: vscode.Connection,
Expand Down Expand Up @@ -123,6 +124,7 @@ export function createCommonLanguageServer(
fsHost = runtimeEnv.createFileSystemHost(ts, params.capabilities);

const tsLocalized = params.locale ? runtimeEnv.loadTypescriptLocalized(options.typescript.tsdk, params.locale) : undefined;
const cancelTokenHost = createCancellactionTokenHost(options.cancellationPipeName);
const _projects = createWorkspaces(
runtimeEnv,
plugins,
Expand All @@ -134,6 +136,7 @@ export function createCommonLanguageServer(
options,
documents,
connection,
cancelTokenHost,
);
projects = _projects;

Expand All @@ -142,7 +145,7 @@ export function createCommonLanguageServer(
}

(await import('./features/customFeatures')).register(connection, projects);
(await import('./features/languageFeatures')).register(connection, projects, params);
(await import('./features/languageFeatures')).register(connection, projects, params, cancelTokenHost);

for (const plugin of plugins) {
plugin.languageService?.onInitialize?.(connection, getLanguageService as any);
Expand Down
6 changes: 5 additions & 1 deletion packages/language-server/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,5 +108,9 @@ export interface LanguageServerInitializationOptions {
diagnosticModel?: DiagnosticModel;
textDocumentSync?: vscode.TextDocumentSyncKind | number;
// for resolve https://github.com/sublimelsp/LSP-volar/issues/114
ignoreTriggerCharacters?: string[],
ignoreTriggerCharacters?: string[];
/**
* https://github.com/Microsoft/TypeScript/wiki/Standalone-Server-%28tsserver%29#cancellation
*/
cancellationPipeName?: string;
}
Loading

0 comments on commit 2e1108c

Please sign in to comment.