diff --git a/packages/language-core/lib/codegen/script/template.ts b/packages/language-core/lib/codegen/script/template.ts index 7ae52d4789..69c3d01bc7 100644 --- a/packages/language-core/lib/codegen/script/template.ts +++ b/packages/language-core/lib/codegen/script/template.ts @@ -142,7 +142,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 77d3798391..8383c0d0ab 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -247,6 +247,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 @@ -1019,51 +1022,61 @@ packages: resolution: {integrity: sha512-Py5vFd5HWYN9zxBv3WMrLAXY3yYJ6Q/aVERoeUFwiDGiMOWsMs7FokXihSOaT/PMWUty/Pj60XDQndK3eAfE6A==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.29.1': resolution: {integrity: sha512-RiWpGgbayf7LUcuSNIbahr0ys2YnEERD4gYdISA06wa0i8RALrnzflh9Wxii7zQJEB2/Eh74dX4y/sHKLWp5uQ==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.29.1': resolution: {integrity: sha512-Z80O+taYxTQITWMjm/YqNoe9d10OX6kDh8X5/rFCMuPqsKsSyDilvfg+vd3iXIqtfmp+cnfL1UrYirkaF8SBZA==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.29.1': resolution: {integrity: sha512-fOHRtF9gahwJk3QVp01a/GqS4hBEZCV1oKglVVq13kcK3NeVlS4BwIFzOHDbmKzt3i0OuHG4zfRP0YoG5OF/rA==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loongarch64-gnu@4.29.1': resolution: {integrity: sha512-5a7q3tnlbcg0OodyxcAdrrCxFi0DgXJSoOuidFUzHZ2GixZXQs6Tc3CHmlvqKAmOs5eRde+JJxeIf9DonkmYkw==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-powerpc64le-gnu@4.29.1': resolution: {integrity: sha512-9b4Mg5Yfz6mRnlSPIdROcfw1BU22FQxmfjlp/CShWwO3LilKQuMISMTtAu/bxmmrE6A902W2cZJuzx8+gJ8e9w==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-gnu@4.29.1': resolution: {integrity: sha512-G5pn0NChlbRM8OJWpJFMX4/i8OEU538uiSv0P6roZcbpe/WfhEO+AT8SHVKfp8qhDQzaz7Q+1/ixMy7hBRidnQ==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-s390x-gnu@4.29.1': resolution: {integrity: sha512-WM9lIkNdkhVwiArmLxFXpWndFGuOka4oJOZh8EP3Vb8q5lzdSCBuhjavJsw68Q9AKDGeOOIHYzYm4ZFvmWez5g==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.29.1': resolution: {integrity: sha512-87xYCwb0cPGZFoGiErT1eDcssByaLX4fc0z2nRM6eMtV9njAfEE6OW3UniAoDhX4Iq5xQVpE6qO9aJbCFumKYQ==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-musl@4.29.1': resolution: {integrity: sha512-xufkSNppNOdVRCEC4WKvlR1FBDyqCSCpQeMMgv9ZyXqqtKBfkw1yfGMTUTs9Qsl6WQbJnsGboWCp7pJGkeMhKA==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-win32-arm64-msvc@4.29.1': resolution: {integrity: sha512-F2OiJ42m77lSkizZQLuC+jiZ2cgueWQL5YC9tjo3AgaEw+KJmVxHGSyQfDUoYR9cci0lAywv2Clmckzulcq6ig==} diff --git a/test-workspace/tsc/passedFixtures/vue3/#3688/main.vue b/test-workspace/tsc/passedFixtures/vue3/#3688/main.vue index 41c9ffce24..ebe0fa020a 100644 --- a/test-workspace/tsc/passedFixtures/vue3/#3688/main.vue +++ b/test-workspace/tsc/passedFixtures/vue3/#3688/main.vue @@ -2,7 +2,7 @@