diff --git a/extensions/vscode/package.json b/extensions/vscode/package.json index 69a183c49f..5e776ede9f 100644 --- a/extensions/vscode/package.json +++ b/extensions/vscode/package.json @@ -85,32 +85,6 @@ "url": "./dist/schemas/vue-tsconfig.deprecated.schema.json" } ], - "semanticTokenScopes": [ - { - "language": "vue", - "scopes": { - "component": [ - "support.class.component.vue" - ] - } - }, - { - "language": "markdown", - "scopes": { - "component": [ - "support.class.component.vue" - ] - } - }, - { - "language": "html", - "scopes": { - "component": [ - "support.class.component.vue" - ] - } - } - ], "languages": [ { "id": "vue", diff --git a/extensions/vscode/src/nodeClientMain.ts b/extensions/vscode/src/nodeClientMain.ts index b5eb95f713..3b723d3a53 100644 --- a/extensions/vscode/src/nodeClientMain.ts +++ b/extensions/vscode/src/nodeClientMain.ts @@ -106,31 +106,7 @@ export async function activate(context: vscode.ExtensionContext) { serverOptions, clientOptions, ); - client.start().then(() => { - const legend = client.initializeResult?.capabilities.semanticTokensProvider?.legend; - if (!legend) { - console.error('Server does not support semantic tokens'); - return; - } - // When tsserver has provided semantic tokens for the .vue file, VSCode will no longer request semantic tokens from the Vue language server, so it needs to be provided here again. - vscode.languages.registerDocumentSemanticTokensProvider(documentSelector, { - async provideDocumentSemanticTokens(document) { - const tokens = await client.sendRequest(lsp.SemanticTokensRequest.type, { - textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document), - } satisfies lsp.SemanticTokensParams); - return client.protocol2CodeConverter.asSemanticTokens(tokens); - }, - }, legend); - vscode.languages.registerDocumentRangeSemanticTokensProvider(documentSelector, { - async provideDocumentRangeSemanticTokens(document, range) { - const tokens = await client.sendRequest(lsp.SemanticTokensRangeRequest.type, { - textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document), - range: client.code2ProtocolConverter.asRange(range), - } satisfies lsp.SemanticTokensRangeParams); - return client.protocol2CodeConverter.asSemanticTokens(tokens); - }, - }, legend); - }); + client.start(); volarLabs.addLanguageClient(client); @@ -193,9 +169,6 @@ function updateProviders(client: lsp.LanguageClient) { capabilities.workspace.fileOperations.willRename = undefined; } - // TODO: disalbe for now because this break ts plugin semantic tokens - capabilities.semanticTokensProvider = undefined; - return initializeFeatures.call(client, ...args); }; } diff --git a/packages/language-server/node.ts b/packages/language-server/node.ts index 8af7655ef4..0e84c069f1 100644 --- a/packages/language-server/node.ts +++ b/packages/language-server/node.ts @@ -15,7 +15,7 @@ let tsdk: ReturnType; connection.listen(); -connection.onInitialize(params => { +connection.onInitialize(async params => { const options: VueInitializationOptions = params.initializationOptions; @@ -29,7 +29,7 @@ connection.onInitialize(params => { } } - return server.initialize( + const result = await server.initialize( params, createSimpleProjectProviderFactory(), { @@ -81,6 +81,11 @@ connection.onInitialize(params => { }, }, ); + + // handle by tsserver + @vue/typescript-plugin + result.capabilities.semanticTokensProvider = undefined; + + return result; }); connection.onInitialized(() => { diff --git a/packages/language-service/lib/plugins/vue-autoinsert-dotvalue.ts b/packages/language-service/lib/plugins/vue-autoinsert-dotvalue.ts index 03d488c6e0..8ed746795c 100644 --- a/packages/language-service/lib/plugins/vue-autoinsert-dotvalue.ts +++ b/packages/language-service/lib/plugins/vue-autoinsert-dotvalue.ts @@ -76,7 +76,7 @@ export function create(ts: typeof import('typescript')): ServicePlugin { } function sleep(ms: number) { - return new Promise(resolve => setTimeout(resolve, ms)); + return new Promise(resolve => setTimeout(resolve, ms)); } function isTsDocument(document: TextDocument) { diff --git a/packages/language-service/lib/plugins/vue-template.ts b/packages/language-service/lib/plugins/vue-template.ts index e57bf1ffa8..efe06c6d56 100644 --- a/packages/language-service/lib/plugins/vue-template.ts +++ b/packages/language-service/lib/plugins/vue-template.ts @@ -303,71 +303,6 @@ export function create( ]; } }, - - async provideDocumentSemanticTokens(document, range, legend, token) { - - if (!isSupportedDocument(document)) - return; - - const result = await baseServiceInstance.provideDocumentSemanticTokens?.(document, range, legend, token) ?? []; - const scanner = getScanner(baseServiceInstance, document); - if (!scanner) - return; - - const [virtualCode] = context.documents.getVirtualCodeByUri(document.uri); - if (!virtualCode) - return; - - for (const map of context.documents.getMaps(virtualCode)) { - - const code = context.language.files.get(map.sourceDocument.uri)?.generated?.code; - if (!(code instanceof VueGeneratedCode)) - continue; - - const templateScriptData = await namedPipeClient.getComponentNames(code.fileName) ?? []; - const components = new Set([ - ...templateScriptData, - ...templateScriptData.map(hyphenateTag), - ]); - const offsetRange = { - start: document.offsetAt(range.start), - end: document.offsetAt(range.end), - }; - - let token = scanner.scan(); - - while (token !== html.TokenType.EOS) { - - const tokenOffset = scanner.getTokenOffset(); - - // TODO: fix source map perf and break in while condition - if (tokenOffset > offsetRange.end) - break; - - if (tokenOffset >= offsetRange.start && (token === html.TokenType.StartTag || token === html.TokenType.EndTag)) { - - const tokenText = scanner.getTokenText(); - - if (components.has(tokenText) || tokenText.indexOf('.') >= 0) { - - const tokenLength = scanner.getTokenLength(); - const tokenPosition = document.positionAt(tokenOffset); - - if (components.has(tokenText)) { - let tokenType = legend.tokenTypes.indexOf('component'); - if (tokenType === -1) { - tokenType = legend.tokenTypes.indexOf('class'); - } - result.push([tokenPosition.line, tokenPosition.character, tokenLength, tokenType, 0]); - } - } - } - token = scanner.scan(); - } - } - - return result; - }, }; async function provideHtmlData(sourceDocumentUri: string, vueCode: VueGeneratedCode) { diff --git a/packages/typescript-plugin/index.ts b/packages/typescript-plugin/index.ts index b75aa6c3f6..fbedd50b7c 100644 --- a/packages/typescript-plugin/index.ts +++ b/packages/typescript-plugin/index.ts @@ -5,6 +5,7 @@ import { createFileRegistry, resolveCommonLanguageId } from '@vue/language-core' import { projects } from './lib/utils'; import * as vue from '@vue/language-core'; import { startNamedPipeServer } from './lib/server'; +import { _getComponentNames } from './lib/requests/componentInfos'; const windowsPathReg = /\\/g; const externalFiles = new WeakMap(); @@ -62,6 +63,7 @@ function createLanguageServicePlugin(): ts.server.PluginModuleFactory { startNamedPipeServer(); const getCompletionsAtPosition = info.languageService.getCompletionsAtPosition; + const getEncodedSemanticClassifications = info.languageService.getEncodedSemanticClassifications; info.languageService.getCompletionsAtPosition = (fileName, position, options) => { const result = getCompletionsAtPosition(fileName, position, options); @@ -70,6 +72,62 @@ function createLanguageServicePlugin(): ts.server.PluginModuleFactory { } return result; }; + info.languageService.getEncodedSemanticClassifications = (fileName, span, format) => { + const result = getEncodedSemanticClassifications(fileName, span, format); + const file = files.get(fileName); + if ( + file?.generated?.code instanceof vue.VueGeneratedCode + && file.generated.code.sfc.template + ) { + const validComponentNames = _getComponentNames(ts, info.languageService, file.generated.code, vueOptions); + const components = new Set([ + ...validComponentNames, + ...validComponentNames.map(vue.hyphenateTag), + ]); + const { template } = file.generated.code.sfc; + const spanTemplateRange = [ + span.start - template.startTagEnd, + span.start + span.length - template.startTagEnd, + ] as const; + template.ast?.children.forEach(function visit(node) { + if (node.loc.end.offset <= spanTemplateRange[0] || node.loc.start.offset >= spanTemplateRange[1]) { + return; + } + if (node.type === 1 satisfies vue.CompilerDOM.NodeTypes.ELEMENT) { + if (components.has(node.tag)) { + result.spans.push( + node.loc.start.offset + node.loc.source.indexOf(node.tag) + template.startTagEnd, + node.tag.length, + 256, // class + ); + if (template.lang === 'html' && !node.isSelfClosing) { + result.spans.push( + node.loc.start.offset + node.loc.source.lastIndexOf(node.tag) + template.startTagEnd, + node.tag.length, + 256, // class + ); + } + } + for (const child of node.children) { + visit(child); + } + } + else if (node.type === 9 satisfies vue.CompilerDOM.NodeTypes.IF) { + for (const branch of node.branches) { + for (const child of branch.children) { + visit(child); + } + } + } + else if (node.type === 11 satisfies vue.CompilerDOM.NodeTypes.FOR) { + for (const child of node.children) { + visit(child); + } + } + }); + } + return result; + }; } return info.languageService; diff --git a/packages/typescript-plugin/lib/requests/componentInfos.ts b/packages/typescript-plugin/lib/requests/componentInfos.ts index 8bef775384..f93fa6e1c2 100644 --- a/packages/typescript-plugin/lib/requests/componentInfos.ts +++ b/packages/typescript-plugin/lib/requests/componentInfos.ts @@ -200,6 +200,21 @@ export function getComponentNames(fileName: string) { ?? []; } +export function _getComponentNames( + ts: typeof import('typescript'), + tsLs: ts.LanguageService, + vueCode: vue.VueGeneratedCode, + vueOptions: vue.VueCompilerOptions, +) { + return getVariableType(ts, tsLs, vueCode, '__VLS_components') + ?.type + ?.getProperties() + .map(c => c.name) + .filter(entry => entry.indexOf('$') === -1 && !entry.startsWith('_')) + .filter(entry => !vueOptions.nativeTags.includes(entry)) + ?? []; +} + export function getElementAttrs(fileName: string, tagName: string) { const match = getProject(fileName); if (!match) {