From a75fdf21f51eb9579680bb345ff0f452a1c52412 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Fri, 30 Sep 2022 18:43:08 +0800 Subject: [PATCH] Refactor for Server Initialization Options (#1916) * refactor: remove languageFeatures, documentFeatures from ServerInitializationOptions * feat: re-add ignoreTriggerCharacters * refactor: simplify client code * refactor: config ts paths as single option * chore: fix `*.trace.server` properties * refactor: ServerInitializationOptions -> InitializationOptions * refactor: enum values --- examples/svelte/package.json | 3 - examples/svelte/src/client.ts | 34 +-- .../vscode-vue-language-features/package.json | 14 +- .../src/browserClientMain.ts | 9 +- .../src/common.ts | 245 ++++++++++-------- .../src/features/activeSelection.ts | 17 -- .../src/features/doctor.ts | 7 +- .../src/features/documentContent.ts | 42 --- .../src/features/splitEditors.ts | 6 +- .../src/features/tsVersion.ts | 185 ++++++------- .../src/nodeClientMain.ts | 11 +- .../src/features/customFeatures.ts | 11 - .../src/features/languageFeatures.ts | 13 +- packages/language-server/src/node.ts | 17 +- ...anguageFeatures.ts => registerFeatures.ts} | 105 +++++--- .../src/registers/registerDocumentFeatures.ts | 32 --- packages/language-server/src/requests.ts | 13 - packages/language-server/src/server.ts | 111 +++++--- packages/language-server/src/types.ts | 96 ++----- packages/language-server/src/utils/project.ts | 22 +- .../src/utils/workspaceProjects.ts | 4 +- .../language-server/src/utils/workspaces.ts | 13 +- .../src/languageFeatures/completeResolve.ts | 7 +- .../src/languageFeatures/executeCommand.ts | 2 +- packages/language-service/src/plugin.ts | 2 +- packages/shared/src/node.ts | 1 - packages/shared/src/ts_node.ts | 108 -------- packages/vue-language-server/src/types.ts | 4 +- 28 files changed, 447 insertions(+), 687 deletions(-) delete mode 100644 extensions/vscode-vue-language-features/src/features/activeSelection.ts delete mode 100644 extensions/vscode-vue-language-features/src/features/documentContent.ts rename packages/language-server/src/{registers/registerlanguageFeatures.ts => registerFeatures.ts} (54%) delete mode 100644 packages/language-server/src/registers/registerDocumentFeatures.ts delete mode 100644 packages/shared/src/ts_node.ts diff --git a/examples/svelte/package.json b/examples/svelte/package.json index 3f71fc17f3..ec25e63f00 100644 --- a/examples/svelte/package.json +++ b/examples/svelte/package.json @@ -14,9 +14,6 @@ "engines": { "vscode": "^1.67.0" }, - "keywords": [ - "svelte" - ], "activationEvents": [ "onLanguage:svelte" ], diff --git a/examples/svelte/src/client.ts b/examples/svelte/src/client.ts index 75bb2dd07a..7d29fc5620 100644 --- a/examples/svelte/src/client.ts +++ b/examples/svelte/src/client.ts @@ -1,4 +1,4 @@ -import { ServerInitializationOptions } from '@volar/language-server'; +import { InitializationOptions } from '@volar/language-server'; import * as path from 'path'; import * as vscode from 'vscode'; import * as lsp from 'vscode-languageclient/node'; @@ -8,37 +8,9 @@ let client: lsp.BaseLanguageClient; export async function activate(context: vscode.ExtensionContext) { const documentSelector: lsp.DocumentSelector = [{ language: 'svelte' }]; - const initializationOptions: ServerInitializationOptions = { + const initializationOptions: InitializationOptions = { typescript: { - serverPath: path.join(vscode.env.appRoot, 'extensions', 'node_modules', 'typescript', 'lib', 'typescript.js'), - }, - languageFeatures: { - references: true, - implementation: true, - definition: true, - typeDefinition: true, - callHierarchy: true, - hover: true, - rename: true, - renameFileRefactoring: true, - signatureHelp: true, - codeAction: true, - workspaceSymbol: true, - completion: true, - documentHighlight: true, - documentLink: true, - codeLens: true, - semanticTokens: true, - inlayHints: true, - diagnostics: true, - }, - documentFeatures: { - selectionRange: true, - foldingRange: true, - linkedEditingRange: true, - documentSymbol: true, - documentColor: true, - documentFormatting: true, + tsdk: path.join(vscode.env.appRoot, 'extensions', 'node_modules', 'typescript', 'lib'), }, }; const serverModule = vscode.Uri.joinPath(context.extensionUri, 'out', 'server'); diff --git a/extensions/vscode-vue-language-features/package.json b/extensions/vscode-vue-language-features/package.json index 81efd0598b..47aa8f1d59 100644 --- a/extensions/vscode-vue-language-features/package.json +++ b/extensions/vscode-vue-language-features/package.json @@ -18,14 +18,6 @@ "engines": { "vscode": "^1.67.0" }, - "keywords": [ - "volar", - "vue", - "vue3", - "ts", - "typescript", - "pug" - ], "activationEvents": [ "onLanguage:vue", "onLanguage:markdown", @@ -269,7 +261,7 @@ "type": "object", "title": "Volar", "properties": { - "volar-language-features.trace.server": { + "vue-semantic-server-1.trace.server": { "scope": "window", "type": "string", "enum": [ @@ -280,7 +272,7 @@ "default": "off", "description": "Traces the communication between VS Code and the language server." }, - "volar-language-features-2.trace.server": { + "vue-semantic-server-2.trace.server": { "scope": "window", "type": "string", "enum": [ @@ -291,7 +283,7 @@ "default": "off", "description": "Traces the communication between VS Code and the language server." }, - "volar-document-features.trace.server": { + "vue-syntactic-server.trace.server": { "scope": "window", "type": "string", "enum": [ diff --git a/extensions/vscode-vue-language-features/src/browserClientMain.ts b/extensions/vscode-vue-language-features/src/browserClientMain.ts index 1d5a68dc2d..cf5427ba1c 100644 --- a/extensions/vscode-vue-language-features/src/browserClientMain.ts +++ b/extensions/vscode-vue-language-features/src/browserClientMain.ts @@ -9,8 +9,15 @@ export function activate(context: vscode.ExtensionContext) { name, documentSelector, initOptions, + fillInitializeParams, ) => { + class _LanguageClient extends lsp.LanguageClient { + fillInitializeParams(params: lsp.InitializeParams) { + fillInitializeParams(params); + } + } + const serverMain = vscode.Uri.joinPath(context.extensionUri, 'dist/browser/server.js'); const worker = new Worker(serverMain.toString()); const clientOptions: lsp.LanguageClientOptions = { @@ -22,7 +29,7 @@ export function activate(context: vscode.ExtensionContext) { }, middleware, }; - const client = new lsp.LanguageClient( + const client = new _LanguageClient( id, name, clientOptions, diff --git a/extensions/vscode-vue-language-features/src/common.ts b/extensions/vscode-vue-language-features/src/common.ts index f258768c9a..e7dd71d861 100644 --- a/extensions/vscode-vue-language-features/src/common.ts +++ b/extensions/vscode-vue-language-features/src/common.ts @@ -1,9 +1,7 @@ import * as shared from '@volar/shared'; import * as vscode from 'vscode'; import * as lsp from 'vscode-languageclient'; -import * as activeSelection from './features/activeSelection'; import * as nameCasing from './features/nameCasing'; -import * as documentContent from './features/documentContent'; import * as preview from './features/preview'; import * as showReferences from './features/showReferences'; import * as splitEditors from './features/splitEditors'; @@ -16,17 +14,24 @@ import * as doctor from './features/doctor'; import * as fileReferences from './features/fileReferences'; import * as reloadProject from './features/reloadProject'; import * as serverSys from './features/serverSys'; -import { VueServerInitializationOptions } from '@volar/vue-language-server'; +import { DiagnosticModel, ServerMode, VueServerInitializationOptions } from '@volar/vue-language-server'; -let apiClient: lsp.BaseLanguageClient | undefined; -let docClient: lsp.BaseLanguageClient | undefined; -let htmlClient: lsp.BaseLanguageClient; +enum LanguageFeaturesKind { + Semantic_Sensitive, + Semantic_Tardy, + Syntactic, +} + +let client_semantic_sensitive: lsp.BaseLanguageClient | undefined; +let client_semantic_tardy: lsp.BaseLanguageClient | undefined; +let client_syntactic: lsp.BaseLanguageClient; type CreateLanguageClient = ( id: string, name: string, documentSelector: lsp.DocumentSelector, initOptions: VueServerInitializationOptions, + fillInitializeParams: (params: lsp.InitializeParams) => void, port: number, ) => Promise; @@ -62,67 +67,49 @@ async function doActivate(context: vscode.ExtensionContext, createLc: CreateLang vscode.commands.executeCommand('setContext', 'volar.activated', true); - const takeOverMode = takeOverModeEnabled(); - const languageFeaturesDocumentSelector: lsp.DocumentSelector = takeOverMode ? - [ - { language: 'vue' }, - { language: 'javascript' }, - { language: 'typescript' }, - { language: 'javascriptreact' }, - { language: 'typescriptreact' }, - { language: 'json' }, - ] : [ - { language: 'vue' }, - ]; - const documentFeaturesDocumentSelector: lsp.DocumentSelector = takeOverMode ? - [ - { language: 'vue' }, - { language: 'javascript' }, - { language: 'typescript' }, - { language: 'javascriptreact' }, - { language: 'typescriptreact' }, - ] : [ - { language: 'vue' }, - ]; - - if (processHtml()) { - languageFeaturesDocumentSelector.push({ language: 'html' }); - documentFeaturesDocumentSelector.push({ language: 'html' }); - } - - if (processMd()) { - languageFeaturesDocumentSelector.push({ language: 'markdown' }); - documentFeaturesDocumentSelector.push({ language: 'markdown' }); - } - const _useSecondServer = useSecondServer(); const _serverMaxOldSpaceSize = serverMaxOldSpaceSize(); - [apiClient, docClient, htmlClient] = await Promise.all([ + [client_semantic_sensitive, client_semantic_tardy, client_syntactic] = await Promise.all([ createLc( - 'volar-language-features', - 'Volar - Language Features Server', - languageFeaturesDocumentSelector, - getInitializationOptions(context, 'main-language-features', _useSecondServer), + 'vue-semantic-server-1', + 'Vue Semantic Server', + getDocumentSelector(ServerMode.Semantic), + getInitializationOptions( + ServerMode.Semantic, + _useSecondServer ? [LanguageFeaturesKind.Semantic_Sensitive] : [LanguageFeaturesKind.Semantic_Sensitive, LanguageFeaturesKind.Semantic_Tardy], + context, + ), + getFillInitializeParams(_useSecondServer ? [LanguageFeaturesKind.Semantic_Sensitive] : [LanguageFeaturesKind.Semantic_Sensitive, LanguageFeaturesKind.Semantic_Tardy]), 6009, ), _useSecondServer ? createLc( - 'volar-language-features-2', - 'Volar - Second Language Features Server', - languageFeaturesDocumentSelector, - getInitializationOptions(context, 'second-language-features', _useSecondServer), + 'vue-semantic-2', + 'Vue Tardy Semantic Server', + getDocumentSelector(ServerMode.Semantic), + getInitializationOptions( + ServerMode.Semantic, + [LanguageFeaturesKind.Semantic_Tardy], + context, + ), + getFillInitializeParams([LanguageFeaturesKind.Semantic_Tardy]), 6010, ) : undefined, createLc( - 'volar-document-features', - 'Volar - Document Features Server', - documentFeaturesDocumentSelector, - getInitializationOptions(context, 'document-features', _useSecondServer), + 'vue-syntactic-server', + 'Vue Syntactic Server', + getDocumentSelector(ServerMode.Syntactic), + getInitializationOptions( + ServerMode.Syntactic, + [LanguageFeaturesKind.Syntactic], + context, + ), + getFillInitializeParams([LanguageFeaturesKind.Syntactic]), 6011, ), ]); - const clients = [apiClient, docClient, htmlClient].filter(shared.notEmpty); + const clients = [client_semantic_sensitive, client_semantic_tardy, client_syntactic].filter(shared.notEmpty); registerUseSecondServerChange(); registerServerMaxOldSpaceSizeChange(); @@ -132,15 +119,15 @@ async function doActivate(context: vscode.ExtensionContext, createLc: CreateLang splitEditors.register(context); preview.register(context); doctor.register(context); - tsVersion.register('volar.selectTypeScriptVersion', context, [apiClient, docClient].filter(shared.notEmpty)); - reloadProject.register('volar.action.reloadProject', context, [apiClient, docClient].filter(shared.notEmpty)); + tsVersion.register('volar.selectTypeScriptVersion', context, [client_semantic_sensitive, client_semantic_tardy].filter(shared.notEmpty)); + reloadProject.register('volar.action.reloadProject', context, [client_semantic_sensitive, client_semantic_tardy].filter(shared.notEmpty)); - if (apiClient) { - tsconfig.register('volar.openTsconfig', context, docClient ?? apiClient); - fileReferences.register('volar.vue.findAllFileReferences', apiClient); - verifyAll.register(context, docClient ?? apiClient); - autoInsertion.register(context, htmlClient, apiClient); - virtualFiles.register('volar.action.writeVirtualFiles', context, docClient ?? apiClient); + if (client_semantic_sensitive) { + tsconfig.register('volar.openTsconfig', context, client_semantic_tardy ?? client_semantic_sensitive); + fileReferences.register('volar.vue.findAllFileReferences', client_semantic_sensitive); + verifyAll.register(context, client_semantic_tardy ?? client_semantic_sensitive); + autoInsertion.register(context, client_syntactic, client_semantic_sensitive); + virtualFiles.register('volar.action.writeVirtualFiles', context, client_semantic_tardy ?? client_semantic_sensitive); } async function requestReloadVscode() { @@ -168,9 +155,6 @@ async function doActivate(context: vscode.ExtensionContext, createLc: CreateLang }); } async function registerRestartRequest() { - - // await Promise.all(clients.map(client => client.onReady())); - context.subscriptions.push(vscode.commands.registerCommand('volar.action.restartServer', async () => { await Promise.all(clients.map(client => client.stop())); await Promise.all(clients.map(client => client.start())); @@ -181,22 +165,20 @@ async function doActivate(context: vscode.ExtensionContext, createLc: CreateLang for (const client of clients) { showReferences.activate(context, client); - documentContent.activate(context, client); - activeSelection.activate(context, client); serverSys.activate(context, client); } - if (apiClient) { - nameCasing.activate(context, apiClient); + if (client_semantic_sensitive) { + nameCasing.activate(context, client_semantic_sensitive); } } } export function deactivate(): Thenable | undefined { return Promise.all([ - apiClient?.stop(), - docClient?.stop(), - htmlClient?.stop(), + client_semantic_sensitive?.stop(), + client_semantic_tardy?.stop(), + client_syntactic?.stop(), ].filter(shared.notEmpty)); } @@ -208,6 +190,30 @@ export function takeOverModeEnabled() { return false; } +function getDocumentSelector(serverMode: ServerMode) { + const takeOverMode = takeOverModeEnabled(); + const selector: lsp.DocumentSelector = takeOverMode ? + [ + { language: 'vue' }, + { language: 'javascript' }, + { language: 'typescript' }, + { language: 'javascriptreact' }, + { language: 'typescriptreact' }, + ] : [ + { language: 'vue' }, + ]; + if (takeOverMode && serverMode === ServerMode.Semantic) { + selector.push({ language: 'json' }); + } + if (processHtml()) { + selector.push({ language: 'html' }); + } + if (processMd()) { + selector.push({ language: 'markdown' }); + } + return selector; +} + function useSecondServer() { return !!vscode.workspace.getConfiguration('volar').get('vueserver.useSecondServer'); } @@ -224,61 +230,70 @@ function processMd() { return !!vscode.workspace.getConfiguration('volar').get('vueserver.vitePress.processMdFile'); } +function getFillInitializeParams(featuresKinds: LanguageFeaturesKind[]) { + return function (params: lsp.InitializeParams) { + if (params.capabilities.textDocument) { + if (!featuresKinds.includes(LanguageFeaturesKind.Semantic_Sensitive)) { + params.capabilities.textDocument.references = undefined; + params.capabilities.textDocument.implementation = undefined; + params.capabilities.textDocument.definition = undefined; + params.capabilities.textDocument.typeDefinition = undefined; + params.capabilities.textDocument.callHierarchy = undefined; + params.capabilities.textDocument.hover = undefined; + params.capabilities.textDocument.rename = undefined; + params.capabilities.textDocument.signatureHelp = undefined; + params.capabilities.textDocument.codeAction = undefined; + params.capabilities.textDocument.completion = undefined; + } + if (!featuresKinds.includes(LanguageFeaturesKind.Semantic_Tardy)) { + params.capabilities.textDocument.documentHighlight = undefined; + params.capabilities.textDocument.documentLink = undefined; + params.capabilities.textDocument.codeLens = undefined; + params.capabilities.textDocument.semanticTokens = undefined; + params.capabilities.textDocument.inlayHint = undefined; + params.capabilities.textDocument.diagnostic = undefined; + } + if (!featuresKinds.includes(LanguageFeaturesKind.Syntactic)) { + params.capabilities.textDocument.selectionRange = undefined; + params.capabilities.textDocument.foldingRange = undefined; + params.capabilities.textDocument.linkedEditingRange = undefined; + params.capabilities.textDocument.documentSymbol = undefined; + params.capabilities.textDocument.colorProvider = undefined; + params.capabilities.textDocument.formatting = undefined; + params.capabilities.textDocument.rangeFormatting = undefined; + params.capabilities.textDocument.onTypeFormatting = undefined; + } + } + if (params.capabilities.workspace) { + if (!featuresKinds.includes(LanguageFeaturesKind.Semantic_Sensitive)) { + params.capabilities.workspace.symbol = undefined; + params.capabilities.workspace.fileOperations = undefined; + } + } + }; +} + function getInitializationOptions( + serverMode: ServerMode, + featuresKinds: LanguageFeaturesKind[], context: vscode.ExtensionContext, - mode: 'main-language-features' | 'second-language-features' | 'document-features', - useSecondServer: boolean, ) { const textDocumentSync = vscode.workspace.getConfiguration('volar').get<'incremental' | 'full' | 'none'>('vueserver.textDocumentSync'); const initializationOptions: VueServerInitializationOptions = { + serverMode, + diagnosticModel: featuresKinds.includes(LanguageFeaturesKind.Semantic_Tardy) ? DiagnosticModel.Push : DiagnosticModel.None, + textDocumentSync: textDocumentSync ? { + incremental: lsp.TextDocumentSyncKind.Incremental, + full: lsp.TextDocumentSyncKind.Full, + none: lsp.TextDocumentSyncKind.None, + }[textDocumentSync] : lsp.TextDocumentSyncKind.Incremental, + typescript: tsVersion.getCurrentTsdk(context), petiteVue: { processHtmlFile: processHtml(), }, vitePress: { processMdFile: processMd(), }, - textDocumentSync: textDocumentSync ? { - incremental: lsp.TextDocumentSyncKind.Incremental, - full: lsp.TextDocumentSyncKind.Full, - none: lsp.TextDocumentSyncKind.None, - }[textDocumentSync] : lsp.TextDocumentSyncKind.Incremental, - typescript: tsVersion.getCurrentTsPaths(context), - languageFeatures: (mode === 'main-language-features' || mode === 'second-language-features') ? { - ...(mode === 'main-language-features' ? { - references: true, - implementation: true, - definition: true, - typeDefinition: true, - callHierarchy: true, - hover: true, - rename: true, - renameFileRefactoring: true, - signatureHelp: true, - codeAction: true, - workspaceSymbol: true, - completion: { - getDocumentSelectionRequest: true, - }, - schemaRequestService: { getDocumentContentRequest: true }, - } : {}), - ...((mode === 'second-language-features' || (mode === 'main-language-features' && !useSecondServer)) ? { - documentHighlight: true, - documentLink: true, - codeLens: { showReferencesNotification: true }, - semanticTokens: true, - inlayHints: true, - diagnostics: true, - schemaRequestService: { getDocumentContentRequest: true }, - } : {}), - } : undefined, - documentFeatures: mode === 'document-features' ? { - selectionRange: true, - foldingRange: true, - linkedEditingRange: true, - documentSymbol: true, - documentColor: true, - documentFormatting: true, - } : undefined, }; return initializationOptions; } diff --git a/extensions/vscode-vue-language-features/src/features/activeSelection.ts b/extensions/vscode-vue-language-features/src/features/activeSelection.ts deleted file mode 100644 index 1bb8c0a9af..0000000000 --- a/extensions/vscode-vue-language-features/src/features/activeSelection.ts +++ /dev/null @@ -1,17 +0,0 @@ -import * as vscode from 'vscode'; -import type { BaseLanguageClient } from 'vscode-languageclient'; -import { GetEditorSelectionRequest } from '@volar/vue-language-server'; - -export async function activate(context: vscode.ExtensionContext, languageClient: BaseLanguageClient) { - context.subscriptions.push(languageClient.onRequest(GetEditorSelectionRequest.type, () => { - const editor = vscode.window.activeTextEditor; - if (editor) { - return { - textDocument: { - uri: languageClient.code2ProtocolConverter.asUri(editor.document.uri), - }, - position: editor.selection.end, - }; - } - })); -} diff --git a/extensions/vscode-vue-language-features/src/features/doctor.ts b/extensions/vscode-vue-language-features/src/features/doctor.ts index 31501e358d..3f4f484cf8 100644 --- a/extensions/vscode-vue-language-features/src/features/doctor.ts +++ b/extensions/vscode-vue-language-features/src/features/doctor.ts @@ -1,6 +1,5 @@ -import { getCurrentTsPaths } from './tsVersion'; +import { getCurrentTsdk, getTsdkVersion } from './tsVersion'; import * as vscode from 'vscode'; -import * as shared from '@volar/shared'; import { takeOverModeEnabled } from '../common'; import * as fs from '../utils/fs'; import * as semver from 'semver' @@ -59,8 +58,8 @@ export async function register(context: vscode.ExtensionContext) { } } - const tsPaths = getCurrentTsPaths(context); - const tsVersion = shared.getTypeScriptVersion(tsPaths.serverPath); + const tsdk = getCurrentTsdk(context); + const tsVersion = getTsdkVersion(tsdk.tsdk); const content = ` ## Infos diff --git a/extensions/vscode-vue-language-features/src/features/documentContent.ts b/extensions/vscode-vue-language-features/src/features/documentContent.ts deleted file mode 100644 index bd21f54330..0000000000 --- a/extensions/vscode-vue-language-features/src/features/documentContent.ts +++ /dev/null @@ -1,42 +0,0 @@ -import * as vscode from 'vscode'; -import { ResponseError } from 'vscode-languageclient'; -import type { BaseLanguageClient } from 'vscode-languageclient'; -import * as nls from 'vscode-nls'; -import { GetDocumentContentRequest } from '@volar/vue-language-server'; - -const localize = nls.loadMessageBundle(); - -export async function activate(context: vscode.ExtensionContext, languageClient: BaseLanguageClient) { - - const schemaDocuments: { [uri: string]: boolean; } = {}; - - context.subscriptions.push(languageClient.onRequest(GetDocumentContentRequest.type, handle => { - const uri = vscode.Uri.parse(handle.uri); - if (uri.scheme === 'untitled') { - return Promise.reject(new ResponseError(3, localize('untitled.schema', 'Unable to load {0}', uri.toString()))); - } - if (uri.scheme !== 'http' && uri.scheme !== 'https') { - return vscode.workspace.openTextDocument(uri).then(doc => { - schemaDocuments[uri.toString()] = true; - return doc.getText(); - }, error => { - return Promise.reject(new ResponseError(2, error.toString())); - }); - } - // else if (schemaDownloadEnabled) { - // if (runtime.telemetry && uri.authority === 'schema.management.azure.com') { - // /* __GDPR__ - // "json.schema" : { - // "schemaURL" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } - // } - // */ - // runtime.telemetry.sendTelemetryEvent('json.schema', { schemaURL: uriPath }); - // } - // return runtime.http.getContent(uriPath); - // } - else { - // return Promise.reject(new ResponseError(1, localize('schemaDownloadDisabled', 'Downloading schemas is disabled through setting \'{0}\'', SettingIds.enableSchemaDownload))); - return Promise.reject(new ResponseError(0, 'Downloading schemas is not support yet')); - } - })); -} diff --git a/extensions/vscode-vue-language-features/src/features/splitEditors.ts b/extensions/vscode-vue-language-features/src/features/splitEditors.ts index 9f87eb72e2..e402df5b8b 100644 --- a/extensions/vscode-vue-language-features/src/features/splitEditors.ts +++ b/extensions/vscode-vue-language-features/src/features/splitEditors.ts @@ -94,7 +94,7 @@ function useDocDescriptor() { } } -export function userPick(groups: T | T[], placeholder?: string) { +export function userPick(groups: T | T[], placeholder?: string) { return new Promise(resolve => { const quickPick = vscode.window.createQuickPick(); const items: vscode.QuickPickItem[] = []; @@ -105,7 +105,9 @@ export function userPick(group items.push({ label: '', kind: vscode.QuickPickItemKind.Separator }); } for (const item of groupItems) { - items.push(item); + if (item) { + items.push(item); + } } } } diff --git a/extensions/vscode-vue-language-features/src/features/tsVersion.ts b/extensions/vscode-vue-language-features/src/features/tsVersion.ts index 3afb5a8c7b..767bccd202 100644 --- a/extensions/vscode-vue-language-features/src/features/tsVersion.ts +++ b/extensions/vscode-vue-language-features/src/features/tsVersion.ts @@ -1,10 +1,10 @@ import * as path from 'path'; import * as vscode from 'vscode'; import { BaseLanguageClient } from 'vscode-languageclient'; -import * as shared from '@volar/shared'; import { userPick } from './splitEditors'; import { takeOverModeEnabled } from '../common'; -import { ServerInitializationOptions } from '@volar/vue-language-server'; +import { InitializationOptions } from '@volar/vue-language-server'; +import * as fs from 'fs'; const defaultTsdk = 'node_modules/typescript/lib'; @@ -15,69 +15,56 @@ export async function register(cmd: string, context: vscode.ExtensionContext, cl const subscription = vscode.commands.registerCommand(cmd, async () => { - const useWorkspaceTsdk = getCurrentTsPaths(context).isWorkspacePath; - const workspaceTsPaths = getWorkspaceTsPaths(); - const workspaceTsVersion = workspaceTsPaths ? shared.getTypeScriptVersion(workspaceTsPaths.serverPath) : undefined; - const vscodeTsPaths = getVscodeTsPaths(); - const vscodeTsVersion = shared.getTypeScriptVersion(vscodeTsPaths.serverPath); - const tsdk = getTsdk(); - const defaultTsServer = shared.getWorkspaceTypescriptPath(defaultTsdk, (vscode.workspace.workspaceFolders ?? []).map(folder => folder.uri.fsPath)); - const defaultTsVersion = defaultTsServer ? shared.getTypeScriptVersion(defaultTsServer) : undefined; - - const options: Record = {}; - options[0] = { - label: (!useWorkspaceTsdk ? '• ' : '') + "Use VS Code's Version", - description: vscodeTsVersion, - }; - if (tsdk) { - options[1] = { - label: (useWorkspaceTsdk ? '• ' : '') + 'Use Workspace Version', - description: workspaceTsVersion ?? 'Could not load the TypeScript version at this path', - detail: tsdk, - }; - } - if (tsdk !== defaultTsdk) { - options[2] = { - label: (useWorkspaceTsdk ? '• ' : '') + 'Use Workspace Version', - description: defaultTsVersion ?? 'Could not load the TypeScript version at this path', - detail: defaultTsdk, - }; - } - if (takeOverModeEnabled()) { - options[3] = { - label: 'What is Takeover Mode?', - }; - } - - const select = await userPick(options); - if (select === undefined) + const usingWorkspaceTsdk = getCurrentTsdk(context).isWorkspacePath; + const configTsdk = getConfigTsdk(); + const select = await userPick([ + { + 'use_vscode_tsdk': { + label: (!usingWorkspaceTsdk ? '• ' : '') + "Use VS Code's Version", + description: getTsdkVersion(getVscodeTsdk()), + }, + 'use_workspace_tsdk': configTsdk ? { + label: (usingWorkspaceTsdk ? '• ' : '') + 'Use Workspace Version', + description: getTsdkVersion(resolveConfigTsdk(configTsdk)) ?? 'Could not load the TypeScript version at this path', + detail: configTsdk, + } : undefined, + 'use_workspace_tsdk_deafult': configTsdk !== defaultTsdk ? { + label: (usingWorkspaceTsdk ? '• ' : '') + 'Use Workspace Version', + description: getTsdkVersion(resolveConfigTsdk(defaultTsdk)) ?? 'Could not load the TypeScript version at this path', + detail: defaultTsdk, + } : undefined, + }, + { + 'takeover': { + label: 'What is Takeover Mode?', + }, + } + ]); + if (select === undefined) { return; // cancel - - if (select === '3') { + } + if (select === 'takeover') { vscode.env.openExternal(vscode.Uri.parse('https://vuejs.org/guide/typescript/overview.html#takeover-mode')); return; } - - if (select === '2') { + if (select === 'use_workspace_tsdk_deafult') { vscode.workspace.getConfiguration('typescript').update('tsdk', defaultTsdk); } - - const nowUseWorkspaceTsdk = select !== '0'; - if (nowUseWorkspaceTsdk !== isUseWorkspaceTsdk(context)) { - context.workspaceState.update('typescript.useWorkspaceTsdk', nowUseWorkspaceTsdk); + const shouldUseWorkspaceTsdk = select !== 'use_vscode_tsdk'; + if (shouldUseWorkspaceTsdk !== useWorkspaceTsdk(context)) { + context.workspaceState.update('typescript.useWorkspaceTsdk', shouldUseWorkspaceTsdk); reloadServers(); } - updateStatusBar(); }); context.subscriptions.push(subscription); - let tsdk = getTsdk(); + let tsdk = getConfigTsdk(); vscode.workspace.onDidChangeConfiguration(() => { - const newTsdk = getTsdk(); + const newTsdk = getConfigTsdk(); if (newTsdk !== tsdk) { tsdk = newTsdk; - if (isUseWorkspaceTsdk(context)) { + if (useWorkspaceTsdk(context)) { reloadServers(); } } @@ -100,8 +87,7 @@ export async function register(cmd: string, context: vscode.ExtensionContext, cl statusBar.hide(); } else { - const tsPaths = getCurrentTsPaths(context); - const tsVersion = shared.getTypeScriptVersion(tsPaths.serverPath); + const tsVersion = getTsdkVersion(getCurrentTsdk(context).tsdk); statusBar.text = '' + tsVersion; if (takeOverModeEnabled()) { statusBar.text += ' (takeover)'; @@ -110,9 +96,9 @@ export async function register(cmd: string, context: vscode.ExtensionContext, cl } } async function reloadServers() { - const tsPaths = getCurrentTsPaths(context); + const tsPaths = getCurrentTsdk(context); for (const client of clients) { - const newInitOptions: ServerInitializationOptions = { + const newInitOptions: InitializationOptions = { ...client.clientOptions.initializationOptions, typescript: tsPaths, }; @@ -122,57 +108,76 @@ export async function register(cmd: string, context: vscode.ExtensionContext, cl } } -export function getCurrentTsPaths(context: vscode.ExtensionContext) { - if (isUseWorkspaceTsdk(context)) { - const workspaceTsPaths = getWorkspaceTsPaths(true); - if (workspaceTsPaths) { - return { ...workspaceTsPaths, isWorkspacePath: true }; +export function getCurrentTsdk(context: vscode.ExtensionContext) { + if (useWorkspaceTsdk(context)) { + const resolvedTsdk = resolveConfigTsdk(getConfigTsdk() ?? defaultTsdk); + if (resolvedTsdk) { + return { tsdk: resolvedTsdk, isWorkspacePath: true }; } } - return { ...getVscodeTsPaths(), isWorkspacePath: false }; + return { tsdk: getVscodeTsdk(), isWorkspacePath: false }; } -function getWorkspaceTsPaths(useDefault = false) { - let tsdk = getTsdk(); - if (!tsdk && useDefault) { - tsdk = defaultTsdk; +function resolveConfigTsdk(tsdk: string) { + if (path.isAbsolute(tsdk)) { + return tsdk; } - if (tsdk) { - const fsPaths = (vscode.workspace.workspaceFolders ?? []).map(folder => folder.uri.fsPath); - const tsPath = shared.getWorkspaceTypescriptPath(tsdk, fsPaths); - if (tsPath) { - return { - serverPath: tsPath, - localizedPath: shared.getWorkspaceTypescriptLocalizedPath(tsdk, vscode.env.language, fsPaths), - }; + const workspaceFolderFsPaths = (vscode.workspace.workspaceFolders ?? []).map(folder => folder.uri.fsPath); + for (const folder of workspaceFolderFsPaths) { + const _path = path.join(folder, tsdk); + if (fs.existsSync(_path)) { + return _path; } } } -function getVscodeTsPaths() { +function getVscodeTsdk() { const nightly = vscode.extensions.getExtension('ms-vscode.vscode-typescript-next'); if (nightly) { - const tsLibPath = path.join(nightly.extensionPath, 'node_modules/typescript/lib'); - const serverPath = shared.findTypescriptModulePathInLib(tsLibPath); - if (serverPath) { - return { - serverPath, - localizedPath: shared.findTypescriptLocalizedPathInLib(tsLibPath, vscode.env.language) - }; - } + return path.join(nightly.extensionPath, 'node_modules/typescript/lib'); } - return { - serverPath: shared.getVscodeTypescriptPath(vscode.env.appRoot), - localizedPath: shared.getVscodeTypescriptLocalizedPath(vscode.env.appRoot, vscode.env.language), - }; + return path.join(vscode.env.appRoot, 'extensions', 'node_modules', 'typescript', 'lib'); } -function getTsdk() { - const tsConfigs = vscode.workspace.getConfiguration('typescript'); - const tsdk = tsConfigs.get('tsdk'); - return tsdk; +function getConfigTsdk() { + return vscode.workspace.getConfiguration('typescript').get('tsdk'); } -function isUseWorkspaceTsdk(context: vscode.ExtensionContext) { +function useWorkspaceTsdk(context: vscode.ExtensionContext) { return context.workspaceState.get('typescript.useWorkspaceTsdk', false); } + +export function getTsdkVersion(tsdk: string | undefined): string | undefined { + if (!tsdk || !fs.existsSync(tsdk)) { + return undefined; + } + + const p = tsdk.split(path.sep); + if (p.length <= 1) { + return undefined; + } + const p2 = p.slice(0, -1); + const modulePath = p2.join(path.sep); + let fileName = path.join(modulePath, 'package.json'); + if (!fs.existsSync(fileName)) { + // Special case for ts dev versions + if (path.basename(modulePath) === 'built') { + fileName = path.join(modulePath, '..', 'package.json'); + } + } + if (!fs.existsSync(fileName)) { + return undefined; + } + + const contents = fs.readFileSync(fileName).toString(); + let desc: any = null; + try { + desc = JSON.parse(contents); + } catch (err) { + return undefined; + } + if (!desc || !desc.version) { + return undefined; + } + return desc.version; +} diff --git a/extensions/vscode-vue-language-features/src/nodeClientMain.ts b/extensions/vscode-vue-language-features/src/nodeClientMain.ts index ceea8966a6..ecc4e3b3d8 100644 --- a/extensions/vscode-vue-language-features/src/nodeClientMain.ts +++ b/extensions/vscode-vue-language-features/src/nodeClientMain.ts @@ -9,9 +9,16 @@ export function activate(context: vscode.ExtensionContext) { name, documentSelector, initOptions, + fillInitializeParams, port, ) => { + class _LanguageClient extends lsp.LanguageClient { + fillInitializeParams(params: lsp.InitializeParams) { + fillInitializeParams(params); + } + } + const serverModule = vscode.Uri.joinPath(context.extensionUri, 'server'); const maxOldSpaceSize = vscode.workspace.getConfiguration('volar').get('vueserver.maxOldSpaceSize'); const runOptions = { execArgv: [] }; @@ -38,9 +45,9 @@ export function activate(context: vscode.ExtensionContext) { progressOnInitialization: true, synchronize: { fileEvents: vscode.workspace.createFileSystemWatcher('{**/*.vue,**/*.md,**/*.html,**/*.js,**/*.jsx,**/*.ts,**/*.tsx,**/*.json}') - } + }, }; - const client = new lsp.LanguageClient( + const client = new _LanguageClient( id, name, serverOptions, diff --git a/packages/language-server/src/features/customFeatures.ts b/packages/language-server/src/features/customFeatures.ts index cf8d870ec7..b846d177c9 100644 --- a/packages/language-server/src/features/customFeatures.ts +++ b/packages/language-server/src/features/customFeatures.ts @@ -3,12 +3,10 @@ import * as path from 'upath'; import * as vscode from 'vscode-languageserver'; import type { Workspaces } from '../utils/workspaces'; import { GetMatchTsConfigRequest, ReloadProjectNotification, VerifyAllScriptsNotification, WriteVirtualFilesNotification } from '../requests'; -import { LanguageServerPlugin } from '../types'; export function register( connection: vscode.Connection, projects: Workspaces, - plugins: ReturnType[], ) { connection.onRequest(GetMatchTsConfigRequest.type, async params => { return (await projects.getProject(params.uri))?.tsconfig; @@ -67,13 +65,4 @@ export function register( connection.window.showInformationMessage(`Verification complete. Found ${errors} errors and ${warnings} warnings.`); }); - - for (const plugin of plugins) { - plugin.languageService?.onInitialize?.(connection, getLanguageService as any); - } - - async function getLanguageService(uri: string) { - const project = (await projects.getProject(uri))?.project; - return project?.getLanguageService(); - } } diff --git a/packages/language-server/src/features/languageFeatures.ts b/packages/language-server/src/features/languageFeatures.ts index 4fdc42901f..f6487f63e0 100644 --- a/packages/language-server/src/features/languageFeatures.ts +++ b/packages/language-server/src/features/languageFeatures.ts @@ -1,13 +1,11 @@ import * as embedded from '@volar/language-service'; import * as vscode from 'vscode-languageserver'; -import { AutoInsertRequest, FindFileReferenceRequest, GetEditorSelectionRequest, ShowReferencesNotification } from '../requests'; -import { ServerInitializationOptions } from '../types'; +import { AutoInsertRequest, FindFileReferenceRequest, ShowReferencesNotification } from '../requests'; import type { Workspaces } from '../utils/workspaces'; export function register( connection: vscode.Connection, projects: Workspaces, - features: NonNullable, initParams: vscode.InitializeParams, ) { @@ -36,14 +34,7 @@ export function register( }); connection.onCompletionResolve(async item => { if (lastCompleteUri && lastCompleteLs) { - - const activeSel = typeof features.completion === 'object' && features.completion.getDocumentSelectionRequest - ? await connection.sendRequest(GetEditorSelectionRequest.type) - : undefined; - const newPosition = activeSel?.textDocument.uri.toLowerCase() === lastCompleteUri.toLowerCase() ? activeSel.position : undefined; - - item = await lastCompleteLs.doCompletionResolve(item, newPosition); - + item = await lastCompleteLs.doCompletionResolve(item); fixTextEdit(item); } return item; diff --git a/packages/language-server/src/node.ts b/packages/language-server/src/node.ts index 0426f6ea25..de9e1db1f6 100644 --- a/packages/language-server/src/node.ts +++ b/packages/language-server/src/node.ts @@ -1,7 +1,6 @@ import * as shared from '@volar/shared'; import * as fs from 'fs'; import { configure as configureHttpRequests } from 'request-light'; -import * as path from 'upath'; import * as html from 'vscode-html-languageservice'; import * as vscode from 'vscode-languageserver/node'; import { createCommonLanguageServer } from './server'; @@ -15,16 +14,20 @@ export function createLanguageServer(plugins: LanguageServerPlugin[]) { const connection = vscode.createConnection(vscode.ProposedFeatures.all); createCommonLanguageServer(connection, { - loadTypescript(options) { - return require(path.toUnix(options.typescript.serverPath)); - }, - loadTypescriptLocalized(options) { - if (options.typescript.localizedPath) { + loadTypescript(tsdk) { + for (const name of ['./typescript.js', './tsserverlibrary.js', './tsserver.js']) { try { - return require(path.toUnix(options.typescript.localizedPath)); + const path = require.resolve(name, { paths: [tsdk] }); + return require(path); } catch { } } }, + loadTypescriptLocalized(tsdk, locale) { + try { + const path = require.resolve(`./${locale}/diagnosticMessages.generated.json`, { paths: [tsdk] }); + return require(path); + } catch { } + }, schemaRequestHandlers: { file: fileSchemaRequestHandler, http: httpSchemaRequestHandler, diff --git a/packages/language-server/src/registers/registerlanguageFeatures.ts b/packages/language-server/src/registerFeatures.ts similarity index 54% rename from packages/language-server/src/registers/registerlanguageFeatures.ts rename to packages/language-server/src/registerFeatures.ts index 591e848761..1b2f021e2d 100644 --- a/packages/language-server/src/registers/registerlanguageFeatures.ts +++ b/packages/language-server/src/registerFeatures.ts @@ -1,36 +1,72 @@ import * as embedded from '@volar/language-service'; -import { LanguageServerPlugin, ServerInitializationOptions } from '../types'; +import { DiagnosticModel, LanguageServerPlugin, InitializationOptions } from './types'; import * as vscode from 'vscode-languageserver'; +import { ClientCapabilities } from 'vscode-languageserver'; -export function register( - features: NonNullable, +export function setupSyntacticCapabilities( + params: ClientCapabilities, server: vscode.ServerCapabilities, +) { + if (params.textDocument?.selectionRange) { + server.selectionRangeProvider = true; + } + if (params.textDocument?.foldingRange) { + server.foldingRangeProvider = true; + } + if (params.textDocument?.linkedEditingRange) { + server.linkedEditingRangeProvider = true; + } + if (params.textDocument?.colorProvider) { + server.colorProvider = true; + } + if (params.textDocument?.documentSymbol) { + server.documentSymbolProvider = true; + } + if (params.textDocument?.formatting) { + server.documentFormattingProvider = true; + } + if (params.textDocument?.rangeFormatting) { + server.documentRangeFormattingProvider = true; + } + if (params.textDocument?.onTypeFormatting) { + // https://github.com/microsoft/vscode/blob/ce119308e8fd4cd3f992d42b297588e7abe33a0c/extensions/typescript-language-features/src/languageFeatures/formatting.ts#L99 + server.documentOnTypeFormattingProvider = { + firstTriggerCharacter: ';', + moreTriggerCharacter: ['}', '\n'], + }; + } +} + +export function setupSemanticCapabilities( + params: ClientCapabilities, + server: vscode.ServerCapabilities, + options: InitializationOptions, plugins: ReturnType[], ) { - if (features.references) { + if (params.textDocument?.references) { server.referencesProvider = true; } - if (features.implementation) { + if (params.textDocument?.implementation) { server.implementationProvider = true; } - if (features.definition) { + if (params.textDocument?.definition) { server.definitionProvider = true; } - if (features.typeDefinition) { + if (params.textDocument?.typeDefinition) { server.typeDefinitionProvider = true; } - if (features.callHierarchy) { + if (params.textDocument?.callHierarchy) { server.callHierarchyProvider = true; } - if (features.hover) { + if (params.textDocument?.hover) { server.hoverProvider = true; } - if (features.rename) { + if (params.textDocument?.rename) { server.renameProvider = { prepareProvider: true, }; } - if (features.renameFileRefactoring) { + if (params.workspace?.fileOperations) { server.workspace = { fileOperations: { willRename: { @@ -46,13 +82,13 @@ export function register( } }; } - if (features.signatureHelp) { + if (params.textDocument?.signatureHelp) { server.signatureHelpProvider = { triggerCharacters: ['(', ',', '<'], retriggerCharacters: [')'], }; } - if (features.completion) { + if (params.textDocument?.completion) { server.completionProvider = { // triggerCharacters: '!@#$%^&*()_+-=`~{}|[]\:";\'<>?,./ '.split(''), // all symbols on keyboard // hardcode to fix https://github.com/sublimelsp/LSP-volar/issues/114 @@ -68,35 +104,34 @@ export function register( ])], resolveProvider: true, }; - if (typeof features.completion === 'object' && features.completion.ignoreTriggerCharacters) { - const ignores = features.completion.ignoreTriggerCharacters; + if (options.ignoreTriggerCharacters) { server.completionProvider.triggerCharacters = server.completionProvider.triggerCharacters - ?.filter(c => !ignores.includes(c)); + ?.filter(c => !options.ignoreTriggerCharacters!.includes(c)); } } - if (features.documentHighlight) { + if (params.textDocument?.documentHighlight) { server.documentHighlightProvider = true; } - if (features.documentLink) { + if (params.textDocument?.documentLink) { server.documentLinkProvider = { resolveProvider: false, // TODO }; } - if (features.workspaceSymbol) { + if (params.workspace?.symbol) { server.workspaceSymbolProvider = true; } - if (features.codeLens) { + if (params.textDocument?.codeLens) { server.codeLensProvider = { resolveProvider: true, }; server.executeCommandProvider = { commands: [ - ...(server.executeCommandProvider?.commands ?? []), + ...server.executeCommandProvider?.commands ?? [], embedded.executePluginCommand, ] }; } - if (features.semanticTokens) { + if (params.textDocument?.semanticTokens) { server.semanticTokensProvider = { range: true, full: false, @@ -112,7 +147,7 @@ export function register( } } } - if (features.codeAction) { + if (params.textDocument?.codeAction) { server.codeActionProvider = { codeActionKinds: [ vscode.CodeActionKind.Empty, @@ -128,19 +163,17 @@ export function register( resolveProvider: true, }; } - if (features.inlayHints) { + if (params.textDocument?.inlayHint) { server.inlayHintProvider = true; } - // buggy - // if (features.diagnostics) { - // server.diagnosticProvider = { - // documentSelector: [ - // ...languageConfigs.definitelyExts.map(ext => ({ pattern: `**/*${ext}` })), - // ...languageConfigs.indeterminateExts.map(ext => ({ pattern: `**/*${ext}` })), - // { pattern: '**/*.{ts,js,tsx,jsx}' }, - // ], - // interFileDependencies: true, - // workspaceDiagnostics: false, - // }; - // } + if (params.textDocument?.diagnostic && (options.diagnosticModel ?? DiagnosticModel.Push) === DiagnosticModel.Pull) { + server.diagnosticProvider = { + documentSelector: [ + ...plugins.map(plugin => plugin.extensions.map(ext => ({ pattern: `**/*${ext}` }))).flat(), + { pattern: '**/*.{ts,js,tsx,jsx}' }, + ], + interFileDependencies: true, + workspaceDiagnostics: false, + }; + } } diff --git a/packages/language-server/src/registers/registerDocumentFeatures.ts b/packages/language-server/src/registers/registerDocumentFeatures.ts deleted file mode 100644 index f577bda378..0000000000 --- a/packages/language-server/src/registers/registerDocumentFeatures.ts +++ /dev/null @@ -1,32 +0,0 @@ -import * as vscode from 'vscode-languageserver'; -import { ServerInitializationOptions } from '../types'; - -export function register( - features: NonNullable, - server: vscode.ServerCapabilities, -) { - if (features.selectionRange) { - server.selectionRangeProvider = true; - } - if (features.foldingRange) { - server.foldingRangeProvider = true; - } - if (features.linkedEditingRange) { - server.linkedEditingRangeProvider = true; - } - if (features.documentColor) { - server.colorProvider = true; - } - if (features.documentSymbol) { - server.documentSymbolProvider = true; - } - if (features.documentFormatting) { - server.documentFormattingProvider = true; - server.documentRangeFormattingProvider = true; - // https://github.com/microsoft/vscode/blob/ce119308e8fd4cd3f992d42b297588e7abe33a0c/extensions/typescript-language-features/src/languageFeatures/formatting.ts#L99 - server.documentOnTypeFormattingProvider = { - firstTriggerCharacter: ';', - moreTriggerCharacter: ['}', '\n'], - }; - } -} diff --git a/packages/language-server/src/requests.ts b/packages/language-server/src/requests.ts index c7261ae0f9..e5984eedfe 100644 --- a/packages/language-server/src/requests.ts +++ b/packages/language-server/src/requests.ts @@ -5,13 +5,6 @@ import type * as html from 'vscode-html-languageservice'; * Server request client */ -export namespace GetDocumentContentRequest { - export type ParamsType = vscode.TextDocumentIdentifier; - export type ResponseType = string; - export type ErrorType = never; - export const type = new vscode.RequestType('vscode/content'); -} - export namespace ShowReferencesNotification { export type ResponseType = vscode.TextDocumentPositionParams & { references: vscode.Location[]; }; export const type = new vscode.NotificationType('vue.findReferences'); @@ -24,12 +17,6 @@ export namespace GetDocumentPrintWidthRequest { export const type = new vscode.RequestType('vue/getDocumentWordWrapColumn'); } -export namespace GetEditorSelectionRequest { - export type ResponseType = vscode.TextDocumentPositionParams | undefined; - export type ErrorType = never; - export const type = new vscode.RequestType0('vue/activeSelection'); -} - export namespace FindFileReferenceRequest { export type ParamsType = { textDocument: vscode.TextDocumentIdentifier; diff --git a/packages/language-server/src/server.ts b/packages/language-server/src/server.ts index 0aadf3e358..102a76c758 100644 --- a/packages/language-server/src/server.ts +++ b/packages/language-server/src/server.ts @@ -1,10 +1,11 @@ import * as vscode from 'vscode-languageserver'; import { URI } from 'vscode-uri'; -import { FileSystemHost, LanguageServerPlugin, RuntimeEnvironment, ServerInitializationOptions } from './types'; +import { FileSystemHost, LanguageServerPlugin, ServerMode, RuntimeEnvironment, InitializationOptions } from './types'; import { createConfigurationHost } from './utils/configurationHost'; import { createDocumentServiceHost } from './utils/documentServiceHost'; import { createSnapshots } from './utils/snapshots'; import { createWorkspaces } from './utils/workspaces'; +import { setupSemanticCapabilities, setupSyntacticCapabilities } from './registerFeatures'; export function createCommonLanguageServer( connection: vscode.Connection, @@ -13,7 +14,7 @@ export function createCommonLanguageServer( ) { let params: vscode.InitializeParams; - let options: ServerInitializationOptions; + let options: InitializationOptions; let roots: URI[] = []; let fsHost: FileSystemHost | undefined; let projects: ReturnType | undefined; @@ -44,52 +45,17 @@ export function createCommonLanguageServer( textDocumentSync: (options.textDocumentSync as vscode.TextDocumentSyncKind) ?? vscode.TextDocumentSyncKind.Incremental, }, }; - const ts = runtimeEnv.loadTypescript(options); configHost = params.capabilities.workspace?.configuration ? createConfigurationHost(params, connection) : undefined; - if (options.documentFeatures) { + const serverMode = options.serverMode ?? ServerMode.Semantic; - (await import('./registers/registerDocumentFeatures')).register(options.documentFeatures, result.capabilities); + setupSyntacticCapabilities(params.capabilities, result.capabilities); + await createDocumenntServiceHost(); - documentServiceHost = createDocumentServiceHost( - runtimeEnv, - plugins, - ts, - configHost, - ); - - for (const root of roots) { - documentServiceHost.add(root); - } - - (await import('./features/documentFeatures')).register(connection, documents, documentServiceHost); - } - if (options.languageFeatures) { - (await import('./registers/registerlanguageFeatures')).register(options.languageFeatures!, result.capabilities, plugins); - - fsHost = runtimeEnv.createFileSystemHost(ts, params.capabilities); - - const tsLocalized = runtimeEnv.loadTypescriptLocalized(options); - - projects = createWorkspaces( - runtimeEnv, - plugins, - fsHost, - configHost, - ts, - tsLocalized, - options, - documents, - connection, - ); - - for (const root of roots) { - projects.add(root); - } - - (await import('./features/customFeatures')).register(connection, projects, plugins); - (await import('./features/languageFeatures')).register(connection, projects, options.languageFeatures, params); + if (serverMode === ServerMode.Semantic) { + setupSemanticCapabilities(params.capabilities, result.capabilities, options, plugins); + await createLanguageServiceHost(); } try { @@ -124,4 +90,63 @@ export function createCommonLanguageServer( } }); connection.listen(); + + async function createDocumenntServiceHost() { + + const ts = runtimeEnv.loadTypescript(options.typescript.tsdk); + + documentServiceHost = createDocumentServiceHost( + runtimeEnv, + plugins, + ts, + configHost, + ); + + for (const root of roots) { + documentServiceHost.add(root); + } + + (await import('./features/documentFeatures')).register( + connection, + documents, + documentServiceHost, + ); + } + + async function createLanguageServiceHost() { + + const ts = runtimeEnv.loadTypescript(options.typescript.tsdk); + fsHost = runtimeEnv.createFileSystemHost(ts, params.capabilities); + + const tsLocalized = params.locale ? runtimeEnv.loadTypescriptLocalized(options.typescript.tsdk, params.locale) : undefined; + const _projects = createWorkspaces( + runtimeEnv, + plugins, + fsHost, + configHost, + ts, + tsLocalized, + params.capabilities, + options, + documents, + connection, + ); + projects = _projects; + + for (const root of roots) { + projects.add(root); + } + + (await import('./features/customFeatures')).register(connection, projects); + (await import('./features/languageFeatures')).register(connection, projects, params); + + for (const plugin of plugins) { + plugin.languageService?.onInitialize?.(connection, getLanguageService as any); + } + + async function getLanguageService(uri: string) { + const project = (await projects!.getProject(uri))?.project; + return project?.getLanguageService(); + } + } } diff --git a/packages/language-server/src/types.ts b/packages/language-server/src/types.ts index 9516f73aaf..4b9db51661 100644 --- a/packages/language-server/src/types.ts +++ b/packages/language-server/src/types.ts @@ -27,8 +27,8 @@ export type FileSystem = Pick & Partial; export interface RuntimeEnvironment { - loadTypescript: (initOptions: ServerInitializationOptions) => typeof import('typescript/lib/tsserverlibrary'), - loadTypescriptLocalized: (initOptions: ServerInitializationOptions) => any, + loadTypescript: (tsdk: string) => typeof import('typescript/lib/tsserverlibrary'), + loadTypescriptLocalized: (tsdk: string, locale: string) => any, schemaRequestHandlers: { [schema: string]: (uri: string, encoding?: BufferEncoding) => Promise; }, onDidChangeConfiguration?: (settings: any) => void, fileSystemProvide: FileSystemProvider | undefined, @@ -39,7 +39,7 @@ export interface RuntimeEnvironment { } export type LanguageServerPlugin< - A extends ServerInitializationOptions = ServerInitializationOptions, + A extends InitializationOptions = InitializationOptions, B extends embedded.LanguageServiceHost = embedded.LanguageServiceHost, C = embeddedLS.LanguageService > = (initOptions: A) => { @@ -83,76 +83,26 @@ export type LanguageServerPlugin< }; }; -export interface ServerInitializationOptions { - textDocumentSync?: vscode.TextDocumentSyncKind | number; +export enum ServerMode { + Semantic = 0, + // PartialSemantic = 1, // not support yet + Syntactic = 2 +} + +export enum DiagnosticModel { + None = 0, + Push = 1, + Pull = 2, +} + +export interface InitializationOptions { typescript: { - /** - * Path to tsserverlibrary.js / tsserver.js / typescript.js - * @example - * '/usr/local/lib/node_modules/typescript/lib/tsserverlibrary.js' // use global typescript install - * 'typescript/lib/tsserverlibrary.js' // if `typescript` exist in `@volar/vue-lannguage-server` itself node_modules directory - * '../../../typescript/lib/tsserverlibrary.js' // relative path to @volar/vue-language-server/out/index.js - */ - serverPath: string; - /** - * Path to lib/xxx/diagnosticMessages.generated.json - * @example - * '/usr/local/lib/node_modules/typescript/lib/ja/diagnosticMessages.generated.json' // use global typescript install - * 'typescript/lib/ja/diagnosticMessages.generated.json' // if `typescript` exist in `@volar/vue-lannguage-server` itself node_modules directory - * '../../../typescript/lib/ja/diagnosticMessages.generated.json' // relative path to @volar/vue-language-server/out/index.js - */ - localizedPath?: string; - }; - /** - * typescript, html, css... language service will be create in server if this option is not null - */ - languageFeatures?: { - references?: boolean; - implementation?: boolean; - definition?: boolean; - typeDefinition?: boolean; - callHierarchy?: boolean; - hover?: boolean; - rename?: boolean; - renameFileRefactoring?: boolean; - signatureHelp?: boolean; - completion?: boolean | { - /** - * {@link __requests.GetDocumentSelectionRequest} - * */ - getDocumentSelectionRequest?: boolean, - // for resolve https://github.com/sublimelsp/LSP-volar/issues/114 - ignoreTriggerCharacters?: string, - }; - documentHighlight?: boolean; - documentLink?: boolean; - workspaceSymbol?: boolean; - codeLens?: boolean | { - /** - * {@link __requests.ShowReferencesNotification} - * */ - showReferencesNotification?: boolean, - }; - semanticTokens?: boolean; - codeAction?: boolean; - inlayHints?: boolean; - diagnostics?: boolean; - schemaRequestService?: boolean | { - /** - * {@link __requests.GetDocumentContentRequest} - * */ - getDocumentContentRequest?: boolean, - }; - }; - /** - * html language service will be create in server if this option is not null - */ - documentFeatures?: { - selectionRange?: boolean; - foldingRange?: boolean; - linkedEditingRange?: boolean; - documentSymbol?: boolean; - documentColor?: boolean; - documentFormatting?: boolean, + // Absolute path to node_modules/typescript/lib + tsdk: string; }; + serverMode?: ServerMode; + diagnosticModel?: DiagnosticModel; + textDocumentSync?: vscode.TextDocumentSyncKind | number; + // for resolve https://github.com/sublimelsp/LSP-volar/issues/114 + ignoreTriggerCharacters?: string[], } diff --git a/packages/language-server/src/utils/project.ts b/packages/language-server/src/utils/project.ts index cd495a9366..690b3ec404 100644 --- a/packages/language-server/src/utils/project.ts +++ b/packages/language-server/src/utils/project.ts @@ -5,8 +5,7 @@ import type * as ts from 'typescript/lib/tsserverlibrary'; import * as vscode from 'vscode-languageserver'; import { URI } from 'vscode-uri'; import { loadCustomPlugins } from './config'; -import { GetDocumentContentRequest } from '../requests'; -import { FileSystem, FileSystemHost, LanguageServerPlugin, RuntimeEnvironment, ServerInitializationOptions } from '../types'; +import { FileSystem, FileSystemHost, LanguageServerPlugin, RuntimeEnvironment, InitializationOptions } from '../types'; import { createSnapshots } from './snapshots'; import { ConfigurationHost } from '@volar/vue-language-service'; import * as upath from 'upath'; @@ -20,7 +19,7 @@ export async function createProject( plugins: ReturnType[], fsHost: FileSystemHost, ts: typeof import('typescript/lib/tsserverlibrary'), - options: ServerInitializationOptions, + options: InitializationOptions, rootUri: URI, tsConfig: string | ts.CompilerOptions, tsLocalized: ts.MapLike | undefined, @@ -78,24 +77,13 @@ export async function createProject( configurationHost: configHost, fileSystemProvider: runtimeEnv.fileSystemProvide, documentContext: getHTMLDocumentContext(ts, languageServiceHost), - schemaRequestService: uri => { + schemaRequestService: async uri => { const protocol = uri.substring(0, uri.indexOf(':')); - const builtInHandler = runtimeEnv.schemaRequestHandlers[protocol]; if (builtInHandler) { - return builtInHandler(uri); - } - - if (typeof options === 'object' && options.languageFeatures?.schemaRequestService) { - return connection.sendRequest(GetDocumentContentRequest.type, { uri }).then(responseText => { - return responseText; - }, error => { - return Promise.reject(error.message); - }); - } - else { - return Promise.reject('clientHandledGetDocumentContentRequest is false'); + return await builtInHandler(uri); } + return ''; }, }, documentRegistry, diff --git a/packages/language-server/src/utils/workspaceProjects.ts b/packages/language-server/src/utils/workspaceProjects.ts index dbe3d74e2d..b2b1e7c45d 100644 --- a/packages/language-server/src/utils/workspaceProjects.ts +++ b/packages/language-server/src/utils/workspaceProjects.ts @@ -3,7 +3,7 @@ import type * as ts from 'typescript/lib/tsserverlibrary'; import * as path from 'upath'; import * as vscode from 'vscode-languageserver'; import { createProject, Project } from './project'; -import { LanguageServerPlugin, RuntimeEnvironment, FileSystemHost, ServerInitializationOptions } from '../types'; +import { LanguageServerPlugin, RuntimeEnvironment, FileSystemHost, InitializationOptions } from '../types'; import { createSnapshots } from './snapshots'; import { getInferredCompilerOptions } from './inferredCompilerOptions'; import { URI } from 'vscode-uri'; @@ -18,7 +18,7 @@ export async function createWorkspaceProjects( rootUri: URI, ts: typeof import('typescript/lib/tsserverlibrary'), tsLocalized: ts.MapLike | undefined, - options: ServerInitializationOptions, + options: InitializationOptions, documents: ReturnType, connection: vscode.Connection, configHost: ConfigurationHost | undefined, diff --git a/packages/language-server/src/utils/workspaces.ts b/packages/language-server/src/utils/workspaces.ts index 03206fc9b2..d90c44ddf4 100644 --- a/packages/language-server/src/utils/workspaces.ts +++ b/packages/language-server/src/utils/workspaces.ts @@ -3,7 +3,7 @@ import { ConfigurationHost } from '@volar/vue-language-service'; import type * as ts from 'typescript/lib/tsserverlibrary'; import * as vscode from 'vscode-languageserver'; import { URI } from 'vscode-uri'; -import { FileSystemHost, LanguageServerPlugin, RuntimeEnvironment, ServerInitializationOptions } from '../types'; +import { DiagnosticModel, FileSystemHost, LanguageServerPlugin, RuntimeEnvironment, InitializationOptions } from '../types'; import { createSnapshots } from './snapshots'; import { createWorkspaceProjects, rootTsConfigNames, sortTsConfigs } from './workspaceProjects'; @@ -16,7 +16,8 @@ export function createWorkspaces( configurationHost: ConfigurationHost | undefined, ts: typeof import('typescript/lib/tsserverlibrary'), tsLocalized: ts.MapLike | undefined, - options: ServerInitializationOptions, + client: vscode.ClientCapabilities, + options: InitializationOptions, documents: ReturnType, connection: vscode.Connection, ) { @@ -93,17 +94,17 @@ export function createWorkspaces( await updateDiagnostics(); if (req === semanticTokensReq) { - if (options.languageFeatures?.semanticTokens) { + if (client.textDocument?.semanticTokens) { connection.languages.semanticTokens.refresh(); } - if (options.languageFeatures?.inlayHints) { - connection.languages.semanticTokens.refresh(); + if (client.textDocument?.inlayHint) { + connection.languages.inlayHint.refresh(); } } } async function updateDiagnostics(docUri?: string) { - if (!options.languageFeatures?.diagnostics) + if ((options.diagnosticModel ?? DiagnosticModel.Push) !== DiagnosticModel.Push) return; const req = ++documentUpdatedReq; diff --git a/packages/language-service/src/languageFeatures/completeResolve.ts b/packages/language-service/src/languageFeatures/completeResolve.ts index 8ef6386ceb..9b1ffb441c 100644 --- a/packages/language-service/src/languageFeatures/completeResolve.ts +++ b/packages/language-service/src/languageFeatures/completeResolve.ts @@ -5,7 +5,7 @@ import { PluginCompletionData } from './complete'; export function register(context: LanguageServiceRuntimeContext) { - return async (item: vscode.CompletionItem, newPosition?: vscode.Position) => { + return async (item: vscode.CompletionItem) => { const data: PluginCompletionData | undefined = item.data; @@ -27,10 +27,7 @@ export function register(context: LanguageServiceRuntimeContext) { if (sourceMap) { - const newPosition_2 = newPosition - ? sourceMap.toGeneratedPosition(newPosition, data => !!data.completion) - : undefined; - const resolvedItem = await plugin.complete.resolve(originalItem, newPosition_2); + const resolvedItem = await plugin.complete.resolve(originalItem); // fix https://github.com/johnsoncodehk/volar/issues/916 if (resolvedItem.additionalTextEdits) { diff --git a/packages/language-service/src/languageFeatures/executeCommand.ts b/packages/language-service/src/languageFeatures/executeCommand.ts index 988176f940..d1bf9ea509 100644 --- a/packages/language-service/src/languageFeatures/executeCommand.ts +++ b/packages/language-service/src/languageFeatures/executeCommand.ts @@ -2,7 +2,7 @@ import type { LanguageServiceRuntimeContext } from '../types'; import { ExecuteCommandContext } from '@volar/language-service'; import * as vscode from 'vscode-languageserver-protocol'; -export const executePluginCommand = 'volar.executtePluginCommand'; +export const executePluginCommand = 'volar.executePluginCommand'; export type ExecutePluginCommandArgs = [ string, // uri diff --git a/packages/language-service/src/plugin.ts b/packages/language-service/src/plugin.ts index 27863b0d87..f137975e66 100644 --- a/packages/language-service/src/plugin.ts +++ b/packages/language-service/src/plugin.ts @@ -63,7 +63,7 @@ export interface LanguageServicePlugin { triggerCharacters?: string[], isAdditional?: boolean, on?(document: TextDocument, position: vscode.Position, context?: vscode.CompletionContext): NullableResult, - resolve?(item: vscode.CompletionItem, newPosition?: vscode.Position): NotNullableResult, + resolve?(item: vscode.CompletionItem): NotNullableResult, }, rename?: { diff --git a/packages/shared/src/node.ts b/packages/shared/src/node.ts index e26f5cf9de..501340a305 100644 --- a/packages/shared/src/node.ts +++ b/packages/shared/src/node.ts @@ -1,5 +1,4 @@ export * from './browser'; -export * from './ts_node'; export * from './http'; // fix build diff --git a/packages/shared/src/ts_node.ts b/packages/shared/src/ts_node.ts deleted file mode 100644 index da39599a56..0000000000 --- a/packages/shared/src/ts_node.ts +++ /dev/null @@ -1,108 +0,0 @@ -import * as fs from 'fs'; -import * as path from 'upath'; - -export function getWorkspaceTypescriptPath(tsdk: string, workspaceFolderFsPaths: string[]) { - if (path.isAbsolute(tsdk)) { - const tsPath = findTypescriptModulePathInLib(tsdk); - if (tsPath) { - return tsPath; - } - } - else { - for (const folder of workspaceFolderFsPaths) { - const tsPath = findTypescriptModulePathInLib(path.join(folder, tsdk)); - if (tsPath) { - return tsPath; - } - } - } -} - -export function getWorkspaceTypescriptLocalizedPath(tsdk: string, lang: string, workspaceFolderFsPaths: string[]) { - if (path.isAbsolute(tsdk)) { - const tsPath = findTypescriptLocalizedPathInLib(tsdk, lang); - if (tsPath) { - return tsPath; - } - } - else { - for (const folder of workspaceFolderFsPaths) { - const tsPath = findTypescriptLocalizedPathInLib(path.join(folder, tsdk), lang); - if (tsPath) { - return tsPath; - } - } - } -} - -export function findTypescriptModulePathInLib(lib: string) { - - const tsserverlibrary = path.join(lib, 'tsserverlibrary.js'); - const typescript = path.join(lib, 'typescript.js'); - const tsserver = path.join(lib, 'tsserver.js'); - - if (fs.existsSync(tsserverlibrary)) { - return tsserverlibrary; - } - if (fs.existsSync(typescript)) { - return typescript; - } - if (fs.existsSync(tsserver)) { - return tsserver; - } -} - -export function findTypescriptLocalizedPathInLib(lib: string, lang: string) { - - const localized = path.join(lib, lang, 'diagnosticMessages.generated.json'); - - if (fs.existsSync(localized)) { - return localized; - } -} - -export function getVscodeTypescriptPath(appRoot: string) { - return path.join(appRoot, 'extensions', 'node_modules', 'typescript', 'lib', 'typescript.js'); -} - -export function getVscodeTypescriptLocalizedPath(appRoot: string, lang: string): string | undefined { - const tsPath = path.join(appRoot, 'extensions', 'node_modules', 'typescript', 'lib', lang, 'diagnosticMessages.generated.json'); - if (fs.existsSync(tsPath)) { - return tsPath; - } -} - -export function getTypeScriptVersion(serverPath: string): string | undefined { - if (!fs.existsSync(serverPath)) { - return undefined; - } - - const p = serverPath.split(path.sep); - if (p.length <= 2) { - return undefined; - } - const p2 = p.slice(0, -2); - const modulePath = p2.join(path.sep); - let fileName = path.join(modulePath, 'package.json'); - if (!fs.existsSync(fileName)) { - // Special case for ts dev versions - if (path.basename(modulePath) === 'built') { - fileName = path.join(modulePath, '..', 'package.json'); - } - } - if (!fs.existsSync(fileName)) { - return undefined; - } - - const contents = fs.readFileSync(fileName).toString(); - let desc: any = null; - try { - desc = JSON.parse(contents); - } catch (err) { - return undefined; - } - if (!desc || !desc.version) { - return undefined; - } - return desc.version; -} diff --git a/packages/vue-language-server/src/types.ts b/packages/vue-language-server/src/types.ts index 99d913d9b8..c069c120e2 100644 --- a/packages/vue-language-server/src/types.ts +++ b/packages/vue-language-server/src/types.ts @@ -1,6 +1,6 @@ -import { ServerInitializationOptions } from "@volar/language-server"; +import { InitializationOptions } from "@volar/language-server"; -export type VueServerInitializationOptions = ServerInitializationOptions & { +export type VueServerInitializationOptions = InitializationOptions & { petiteVue?: { processHtmlFile: boolean, },