diff --git a/packages/language-core/lib/codegen/script/template.ts b/packages/language-core/lib/codegen/script/template.ts index 982e37a909..37ae7e1b07 100644 --- a/packages/language-core/lib/codegen/script/template.ts +++ b/packages/language-core/lib/codegen/script/template.ts @@ -162,7 +162,7 @@ function* generateStyleScopedClasses( ctx: TemplateCodegenContext ): Generator { const firstClasses = new Set(); - yield `type __VLS_StyleScopedClasses = {}`; + yield `let __VLS_styleScopedClasses!: {}`; for (let i = 0; i < options.sfc.styles.length; i++) { const style = options.sfc.styles[i]; const option = options.vueCompilerOptions.experimentalResolveStyleCssClasses; diff --git a/packages/language-core/lib/codegen/template/styleScopedClasses.ts b/packages/language-core/lib/codegen/template/styleScopedClasses.ts index 43f2a4ce9b..04fad1bab1 100644 --- a/packages/language-core/lib/codegen/template/styleScopedClasses.ts +++ b/packages/language-core/lib/codegen/template/styleScopedClasses.ts @@ -2,7 +2,7 @@ import * as CompilerDOM from '@vue/compiler-dom'; import type * as ts from 'typescript'; import { getNodeText } from '../../parsers/scriptSetupRanges'; import type { Code } from '../../types'; -import { endOfLine, normalizeAttributeValue } from '../utils'; +import { endOfLine, newLine, normalizeAttributeValue } from '../utils'; import type { TemplateCodegenContext } from './context'; import type { TemplateCodegenOptions } from './index'; @@ -10,22 +10,18 @@ export function* generateStyleScopedClassReferences( ctx: TemplateCodegenContext, withDot = false ): Generator { - if (!ctx.emptyClassOffsets.length && !ctx.scopedClasses.length) { - return; - } - - yield `[`; for (const offset of ctx.emptyClassOffsets) { - yield `'`; + yield `__VLS_styleScopedClasses['`; yield [ '', 'template', offset, ctx.codeFeatures.additionalCompletion, ]; - yield `', `; + yield `']${endOfLine}`; } for (const { source, className, offset } of ctx.scopedClasses) { + yield `__VLS_styleScopedClasses[`; yield [ '', source, @@ -43,9 +39,9 @@ export function* generateStyleScopedClassReferences( offset + className.length, ctx.codeFeatures.navigationWithoutRename, ]; - yield `, `; + yield `]${endOfLine}`; } - yield `] as (keyof __VLS_StyleScopedClasses)[]${endOfLine}`; + yield newLine; function* escapeString(source: string, className: string, offset: number, escapeTargets: string[]): Generator { let count = 0; diff --git a/packages/language-service/lib/plugins/css.ts b/packages/language-service/lib/plugins/css.ts index de1dcaa276..3ec617d2e0 100644 --- a/packages/language-service/lib/plugins/css.ts +++ b/packages/language-service/lib/plugins/css.ts @@ -1,5 +1,9 @@ -import type { LanguageServicePlugin } from '@volar/language-service'; -import { create as baseCreate } from 'volar-service-css'; +import type { LanguageServicePlugin, VirtualCode } from '@volar/language-service'; +import { VueVirtualCode } from '@vue/language-core'; +import { create as baseCreate, type Provide } from 'volar-service-css'; +import * as css from 'vscode-css-languageservice'; +import type { TextDocument } from 'vscode-languageserver-textdocument'; +import { URI } from 'vscode-uri'; export function create(): LanguageServicePlugin { const base = baseCreate({ scssDocumentSelector: ['scss', 'postcss'] }); @@ -7,6 +11,11 @@ export function create(): LanguageServicePlugin { ...base, create(context) { const baseInstance = base.create(context); + const { + 'css/languageService': getCssLs, + 'css/stylesheet': getStylesheet + } = baseInstance.provide as Provide; + return { ...baseInstance, async provideDiagnostics(document, token) { @@ -18,7 +27,69 @@ export function create(): LanguageServicePlugin { } return diagnostics; }, + provideRenameRange(document, position) { + do { + const uri = URI.parse(document.uri); + const decoded = context.decodeEmbeddedDocumentUri(uri); + const sourceScript = decoded && context.language.scripts.get(decoded[0]); + const virtualCode = decoded && sourceScript?.generated?.embeddedCodes.get(decoded[1]); + if (!sourceScript?.generated || !virtualCode?.id.startsWith('style_')) { + break; + } + + const root = sourceScript.generated.root; + if (!(root instanceof VueVirtualCode)) { + break; + } + + const block = root._sfc.styles.find(style => style.name === decoded![1]); + if (!block) { + break; + } + + let script: VirtualCode | undefined; + for (const [key, value] of sourceScript.generated.embeddedCodes) { + if (key.startsWith('script_')) { + script = value; + break; + } + } + if (!script) { + break; + } + + const offset = document.offsetAt(position) + block.startTagEnd; + for (const { sourceOffsets, lengths, data } of script.mappings) { + if ( + !sourceOffsets.length + || !data.navigation + || typeof data.navigation === 'object' && !data.navigation.shouldRename + ) { + continue; + } + + const start = sourceOffsets[0]; + const end = sourceOffsets.at(-1)! + lengths.at(-1)!; + + if (offset >= start && offset <= end) { + return; + } + } + } while (0); + + return worker(document, (stylesheet, cssLs) => { + return cssLs.prepareRename(document, position, stylesheet); + }); + } }; + + function worker(document: TextDocument, callback: (stylesheet: css.Stylesheet, cssLs: css.LanguageService) => T) { + const cssLs = getCssLs(document); + if (!cssLs) { + return; + } + return callback(getStylesheet(document, cssLs), cssLs); + } }, }; } diff --git a/packages/language-service/package.json b/packages/language-service/package.json index 6a4e20ab0b..33e2e27b35 100644 --- a/packages/language-service/package.json +++ b/packages/language-service/package.json @@ -34,6 +34,7 @@ "volar-service-pug-beautify": "0.0.62", "volar-service-typescript": "0.0.62", "volar-service-typescript-twoslash-queries": "0.0.62", + "vscode-css-languageservice": "^6.3.1", "vscode-html-languageservice": "^5.2.0", "vscode-languageserver-textdocument": "^1.0.11", "vscode-uri": "^3.0.8" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 798a4265fb..0f95b82701 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -238,6 +238,9 @@ importers: volar-service-typescript-twoslash-queries: specifier: 0.0.62 version: 0.0.62(@volar/language-service@2.4.11) + vscode-css-languageservice: + specifier: ^6.3.1 + version: 6.3.2 vscode-html-languageservice: specifier: ^5.2.0 version: 5.3.1 @@ -944,51 +947,61 @@ packages: resolution: {integrity: sha512-QAg11ZIt6mcmzpNE6JZBpKfJaKkqTm1A9+y9O+frdZJEuhQxiugM05gnCWiANHj4RmbgeVJpTdmKRmH/a+0QbA==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.28.1': resolution: {integrity: sha512-dRP9PEBfolq1dmMcFqbEPSd9VlRuVWEGSmbxVEfiq2cs2jlZAl0YNxFzAQS2OrQmsLBLAATDMb3Z6MFv5vOcXg==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.28.1': resolution: {integrity: sha512-uGr8khxO+CKT4XU8ZUH1TTEUtlktK6Kgtv0+6bIFSeiSlnGJHG1tSFSjm41uQ9sAO/5ULx9mWOz70jYLyv1QkA==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.28.1': resolution: {integrity: sha512-QF54q8MYGAqMLrX2t7tNpi01nvq5RI59UBNx+3+37zoKX5KViPo/gk2QLhsuqok05sSCRluj0D00LzCwBikb0A==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loongarch64-gnu@4.28.1': resolution: {integrity: sha512-vPul4uodvWvLhRco2w0GcyZcdyBfpfDRgNKU+p35AWEbJ/HPs1tOUrkSueVbBS0RQHAf/A+nNtDpvw95PeVKOA==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-powerpc64le-gnu@4.28.1': resolution: {integrity: sha512-pTnTdBuC2+pt1Rmm2SV7JWRqzhYpEILML4PKODqLz+C7Ou2apEV52h19CR7es+u04KlqplggmN9sqZlekg3R1A==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-gnu@4.28.1': resolution: {integrity: sha512-vWXy1Nfg7TPBSuAncfInmAI/WZDd5vOklyLJDdIRKABcZWojNDY0NJwruY2AcnCLnRJKSaBgf/GiJfauu8cQZA==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-s390x-gnu@4.28.1': resolution: {integrity: sha512-/yqC2Y53oZjb0yz8PVuGOQQNOTwxcizudunl/tFs1aLvObTclTwZ0JhXF2XcPT/zuaymemCDSuuUPXJJyqeDOg==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.28.1': resolution: {integrity: sha512-fzgeABz7rrAlKYB0y2kSEiURrI0691CSL0+KXwKwhxvj92VULEDQLpBYLHpF49MSiPG4sq5CK3qHMnb9tlCjBw==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-musl@4.28.1': resolution: {integrity: sha512-xQTDVzSGiMlSshpJCtudbWyRfLaNiVPXt1WgdWTwWz9n0U12cI2ZVtWe/Jgwyv/6wjL7b66uu61Vg0POWVfz4g==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-win32-arm64-msvc@4.28.1': resolution: {integrity: sha512-wSXmDRVupJstFP7elGMgv+2HqXelQhuNf+IS4V+nUpNVi/GUiBgDmfwD0UGN3pcAnWsgKG3I52wMOBnk1VHr/A==} diff --git a/test-workspace/tsc/passedFixtures/#3688/main.vue b/test-workspace/tsc/passedFixtures/#3688/main.vue index a225186021..7fbf85ca0a 100644 --- a/test-workspace/tsc/passedFixtures/#3688/main.vue +++ b/test-workspace/tsc/passedFixtures/#3688/main.vue @@ -1,6 +1,6 @@