diff --git a/.aspect/rules/external_repository_action_cache/npm_translate_lock_LTE4Nzc1MDcwNjU= b/.aspect/rules/external_repository_action_cache/npm_translate_lock_LTE4Nzc1MDcwNjU= index f65377f999..da688cb534 100755 --- a/.aspect/rules/external_repository_action_cache/npm_translate_lock_LTE4Nzc1MDcwNjU= +++ b/.aspect/rules/external_repository_action_cache/npm_translate_lock_LTE4Nzc1MDcwNjU= @@ -2,7 +2,7 @@ # Input hashes for repository rule npm_translate_lock(name = "npm", pnpm_lock = "//:pnpm-lock.yaml"). # This file should be checked into version control along with the pnpm-lock.yaml file. .npmrc=974837034 -pnpm-lock.yaml=1493175183 -yarn.lock=485928896 -package.json=-100959756 +pnpm-lock.yaml=1752070694 +yarn.lock=1122184668 +package.json=991750528 pnpm-workspace.yaml=1711114604 diff --git a/client/src/client.ts b/client/src/client.ts index a3f9da2002..3430a0fec2 100644 --- a/client/src/client.ts +++ b/client/src/client.ts @@ -171,6 +171,16 @@ export class AngularLanguageClient implements vscode.Disposable { } return next(document, context, token); }, + provideDocumentSemanticTokens: async (document, token, next) => { + if (await this.isInAngularProject(document)) { + return next(document, token); + } + }, + provideDocumentRangeSemanticTokens: async (document, range, token, next) => { + if (await this.isInAngularProject(document)) { + return next(document, range, token); + } + }, } }; } diff --git a/package.json b/package.json index e95f8696fc..6865de9e61 100644 --- a/package.json +++ b/package.json @@ -274,7 +274,7 @@ "test:legacy-syntaxes": "yarn compile:syntaxes-test && yarn build:syntaxes && jasmine dist/syntaxes/test/driver.js" }, "dependencies": { - "@angular/language-service": "^20.1.0-next.0", + "@angular/language-service": "20.1.0-rc.0", "typescript": "^5.8.1", "vscode-html-languageservice": "^4.2.5", "vscode-jsonrpc": "6.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c843a0352e..b310ad588a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,8 +9,8 @@ importers: .: dependencies: '@angular/language-service': - specifier: ^20.1.0-next.0 - version: registry.npmjs.org/@angular/language-service@20.1.0-next.0 + specifier: 20.1.0-rc.0 + version: registry.npmjs.org/@angular/language-service@20.1.0-rc.0 typescript: specifier: ^5.8.1 version: registry.npmjs.org/typescript@5.8.3 @@ -438,10 +438,10 @@ packages: tslib: registry.npmjs.org/tslib@2.6.3 dev: true - registry.npmjs.org/@angular/language-service@20.1.0-next.0: - resolution: {integrity: sha512-CH2Oj7ytaEXl/4W0CzCgO3gC71uzU+gHOnM8NdAnM0ccEjzOeq1ty0PaQzQbf2psKed7V0KRfpWT+0xOO38V4A==, registry: https://registry.yarnpkg.com/, tarball: https://registry.npmjs.org/@angular/language-service/-/language-service-20.1.0-next.0.tgz} + registry.npmjs.org/@angular/language-service@20.1.0-rc.0: + resolution: {integrity: sha512-8iXIiBGOD4fUulzhH1yEZIQhlwTsY3Kbj1N8wpS1uV1sH2DSgHNq4WR5k3fQYUx7VYbF3VyTCjdBzjh7s2Xcsg==, registry: https://registry.yarnpkg.com/, tarball: https://registry.npmjs.org/@angular/language-service/-/language-service-20.1.0-rc.0.tgz} name: '@angular/language-service' - version: 20.1.0-next.0 + version: 20.1.0-rc.0 engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} dev: false diff --git a/server/package.json b/server/package.json index 1cb6ceb7a2..1b67d37b8f 100644 --- a/server/package.json +++ b/server/package.json @@ -15,7 +15,7 @@ "ngserver": "./bin/ngserver" }, "dependencies": { - "@angular/language-service": "20.1.0-next.0", + "@angular/language-service": "20.1.0-rc.0", "vscode-html-languageservice": "^4.2.5", "vscode-jsonrpc": "6.0.0", "vscode-languageserver": "7.0.0", diff --git a/server/src/semantic_tokens.ts b/server/src/semantic_tokens.ts new file mode 100644 index 0000000000..5b49fa9e91 --- /dev/null +++ b/server/src/semantic_tokens.ts @@ -0,0 +1,47 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { NgLanguageService } from '@angular/language-service/api'; +import * as ts from 'typescript/lib/tsserverlibrary'; +import * as lsp from 'vscode-languageserver/node'; + +export function getSemanticTokens( + languageService: NgLanguageService, classifications: ts.Classifications, script: ts.server.ScriptInfo): lsp.SemanticTokens { + const spans = classifications.spans; + const builder = new lsp.SemanticTokensBuilder(); + + for (let i = 0; i < spans.length;) { + const offset = spans[i++]; + const length = spans[i++]; + const classification = spans[i++]; + + const tokenType = languageService.getTokenTypeFromClassification(classification); + if (tokenType === undefined) { + continue; + } + + const tokenModifiers = languageService.getTokenModifierFromClassification(classification); + + const startPos = script.positionToLineOffset(offset); + startPos.line -= 1; + startPos.offset -= 1; + + const endPos = script.positionToLineOffset(offset + length); + endPos.line -= 1; + endPos.offset -= 1; + + for (let line = startPos.line; line <= endPos.line; line++) { + const startCharacter = line === startPos.line ? startPos.offset : 0; + const endCharacter = + line === endPos.line ? endPos.offset : script.lineToTextSpan(line - 1).length; + builder.push(line, startCharacter, endCharacter - startCharacter, tokenType, tokenModifiers); + } + } + + return builder.build(); +} diff --git a/server/src/session.ts b/server/src/session.ts index ee132ca08b..b22077e3b6 100644 --- a/server/src/session.ts +++ b/server/src/session.ts @@ -20,6 +20,7 @@ import {GetComponentsWithTemplateFile, GetTcbParams, GetTcbRequest, GetTcbRespon import {readNgCompletionData, tsCompletionEntryToLspCompletionItem} from './completion'; import {tsDiagnosticToLspDiagnostic} from './diagnostic'; import {getHTMLVirtualContent} from './embedded_support'; +import {getSemanticTokens} from './semantic_tokens'; import {ServerHost} from './server_host'; import {documentationToMarkdown} from './text_render'; import {filePathToUri, getMappedDefinitionInfo, isConfiguredProject, isDebugMode, lspPositionToTsPosition, lspRangeToTsPositions, MruTracker, tsDisplayPartsToText, tsFileTextChangesToLspWorkspaceEdit, tsTextSpanToLspRange, uriToFilePath} from './utils'; @@ -217,8 +218,38 @@ export class Session { conn.onSignatureHelp(p => this.onSignatureHelp(p)); conn.onCodeAction(p => this.onCodeAction(p)); conn.onCodeActionResolve(async p => await this.onCodeActionResolve(p)); + conn.onRequest(lsp.SemanticTokensRequest.type, p => this.onSemanticTokensRequest(p)); + conn.onRequest(lsp.SemanticTokensRangeRequest.type, p => this.onSemanticTokensRangeRequest(p)); } + private onSemanticTokensRequest(params: lsp.SemanticTokensParams): lsp.SemanticTokens|null { + const lsInfo = this.getLSAndScriptInfo(params.textDocument); + if (lsInfo === null) { + return null; + } + const {languageService, scriptInfo} = lsInfo; + const span = {start: 0, length: scriptInfo.getSnapshot().getLength()}; + const classifications = languageService.getEncodedSemanticClassifications( + scriptInfo.fileName, span, ts.SemanticClassificationFormat.TwentyTwenty); + return getSemanticTokens(languageService, classifications, scriptInfo); + } + + private onSemanticTokensRangeRequest(params: lsp.SemanticTokensRangeParams): lsp.SemanticTokens + |null { + const lsInfo = this.getLSAndScriptInfo(params.textDocument); + if (lsInfo === null) { + return null; + } + const {languageService, scriptInfo} = lsInfo; + const start = lspPositionToTsPosition(lsInfo.scriptInfo, params.range.start); + const end = lspPositionToTsPosition(lsInfo.scriptInfo, params.range.end); + const span = {start, length: end - start}; + const classifications = languageService.getEncodedSemanticClassifications( + scriptInfo.fileName, span, ts.SemanticClassificationFormat.TwentyTwenty); + return getSemanticTokens(languageService, classifications, scriptInfo); + } + + private onCodeAction(params: lsp.CodeActionParams): lsp.CodeAction[]|null { const filePath = uriToFilePath(params.textDocument.uri); const lsInfo = this.getLSAndScriptInfo(params.textDocument); @@ -809,6 +840,17 @@ export class Session { // [here](https://github.com/angular/vscode-ng-language-service/issues/1828) codeActionKinds: [lsp.CodeActionKind.QuickFix], }, + semanticTokensProvider: { + documentSelector: null, + legend: { + tokenTypes: [ + 'class', + ], + tokenModifiers: [], + }, + full: true, + range: true + } }, serverOptions, }; diff --git a/yarn.lock b/yarn.lock index 414476d2fd..2e1f079bc9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -173,10 +173,10 @@ uuid "^8.3.2" yargs "^17.0.0" -"@angular/language-service@^20.1.0-next.0": - version "20.1.0-next.0" - resolved "https://registry.yarnpkg.com/@angular/language-service/-/language-service-20.1.0-next.0.tgz#d294576dee2c2be966020a8dc8ef0bc073e71487" - integrity sha512-CH2Oj7ytaEXl/4W0CzCgO3gC71uzU+gHOnM8NdAnM0ccEjzOeq1ty0PaQzQbf2psKed7V0KRfpWT+0xOO38V4A== +"@angular/language-service@20.1.0-rc.0": + version "20.1.0-rc.0" + resolved "https://registry.yarnpkg.com/@angular/language-service/-/language-service-20.1.0-rc.0.tgz#6d4e8a45e6716410778f42ce8c0d54e3e7912d02" + integrity sha512-8iXIiBGOD4fUulzhH1yEZIQhlwTsY3Kbj1N8wpS1uV1sH2DSgHNq4WR5k3fQYUx7VYbF3VyTCjdBzjh7s2Xcsg== "@assemblyscript/loader@^0.10.1": version "0.10.1"