diff --git a/packages/language-core/index.ts b/packages/language-core/index.ts index 54dffda72f..993c326573 100644 --- a/packages/language-core/index.ts +++ b/packages/language-core/index.ts @@ -6,6 +6,7 @@ export * from './lib/parsers/scriptSetupRanges'; export * from './lib/plugins'; export * from './lib/types'; export * from './lib/utils/collectBindings'; +export * from './lib/utils/forEachTemplateNode'; export * from './lib/utils/parseSfc'; export * from './lib/utils/shared'; export * from './lib/virtualFile/vueFile'; diff --git a/packages/language-core/lib/codegen/template/index.ts b/packages/language-core/lib/codegen/template/index.ts index ffa43f2820..ad2e926bf6 100644 --- a/packages/language-core/lib/codegen/template/index.ts +++ b/packages/language-core/lib/codegen/template/index.ts @@ -1,4 +1,3 @@ -import * as CompilerDOM from '@vue/compiler-dom'; import type * as ts from 'typescript'; import type { Code, Sfc, VueCompilerOptions } from '../../types'; import { codeFeatures } from '../codeFeatures'; @@ -7,7 +6,7 @@ import { wrapWith } from '../utils/wrapWith'; import { createTemplateCodegenContext, type TemplateCodegenContext } from './context'; import { generateObjectProperty } from './objectProperty'; import { generateStyleScopedClassReferences } from './styleScopedClasses'; -import { generateTemplateChild, getVForNode } from './templateChild'; +import { generateTemplateChild } from './templateChild'; export interface TemplateCodegenOptions { ts: typeof ts; @@ -203,39 +202,3 @@ function* generateRootEl( yield endOfLine; return `__VLS_RootEl`; } - -export function* forEachElementNode( - node: CompilerDOM.RootNode | CompilerDOM.TemplateChildNode, -): Generator { - if (node.type === CompilerDOM.NodeTypes.ROOT) { - for (const child of node.children) { - yield* forEachElementNode(child); - } - } - else if (node.type === CompilerDOM.NodeTypes.ELEMENT) { - const patchForNode = getVForNode(node); - if (patchForNode) { - yield* forEachElementNode(patchForNode); - } - else { - yield node; - for (const child of node.children) { - yield* forEachElementNode(child); - } - } - } - else if (node.type === CompilerDOM.NodeTypes.IF) { - // v-if / v-else-if / v-else - for (const branch of node.branches) { - for (const childNode of branch.children) { - yield* forEachElementNode(childNode); - } - } - } - else if (node.type === CompilerDOM.NodeTypes.FOR) { - // v-for - for (const child of node.children) { - yield* forEachElementNode(child); - } - } -} diff --git a/packages/language-core/lib/plugins/vue-template-inline-css.ts b/packages/language-core/lib/plugins/vue-template-inline-css.ts index 46fe7aac0b..1f6a218814 100644 --- a/packages/language-core/lib/plugins/vue-template-inline-css.ts +++ b/packages/language-core/lib/plugins/vue-template-inline-css.ts @@ -1,6 +1,6 @@ import * as CompilerDOM from '@vue/compiler-dom'; -import { forEachElementNode } from '../codegen/template'; import type { Code, VueLanguagePlugin } from '../types'; +import { forEachElementNode } from '../utils/forEachTemplateNode'; import { allCodeFeatures } from './shared'; const codeFeatures = { diff --git a/packages/language-core/lib/utils/forEachTemplateNode.ts b/packages/language-core/lib/utils/forEachTemplateNode.ts new file mode 100644 index 0000000000..9b127b8e05 --- /dev/null +++ b/packages/language-core/lib/utils/forEachTemplateNode.ts @@ -0,0 +1,69 @@ +import * as CompilerDOM from '@vue/compiler-dom'; + +export function* forEachElementNode( + node: CompilerDOM.RootNode | CompilerDOM.TemplateChildNode, +): Generator { + if (node.type === CompilerDOM.NodeTypes.ROOT) { + for (const child of node.children) { + yield* forEachElementNode(child); + } + } + else if (node.type === CompilerDOM.NodeTypes.ELEMENT) { + yield node; + for (const child of node.children) { + yield* forEachElementNode(child); + } + } + else if (node.type === CompilerDOM.NodeTypes.IF) { + for (const branch of node.branches) { + for (const childNode of branch.children) { + yield* forEachElementNode(childNode); + } + } + } + else if (node.type === CompilerDOM.NodeTypes.FOR) { + for (const child of node.children) { + yield* forEachElementNode(child); + } + } +} + +export function* forEachInterpolationNode( + node: CompilerDOM.RootNode | CompilerDOM.TemplateChildNode | CompilerDOM.SimpleExpressionNode, +): Generator { + if (node.type === CompilerDOM.NodeTypes.ROOT) { + for (const child of node.children) { + yield* forEachInterpolationNode(child); + } + } + else if (node.type === CompilerDOM.NodeTypes.ELEMENT) { + for (const child of node.children) { + yield* forEachInterpolationNode(child); + } + } + else if (node.type === CompilerDOM.NodeTypes.TEXT_CALL) { + yield* forEachInterpolationNode(node.content); + } + else if (node.type === CompilerDOM.NodeTypes.COMPOUND_EXPRESSION) { + for (const child of node.children) { + if (typeof child === 'object') { + yield* forEachInterpolationNode(child); + } + } + } + else if (node.type === CompilerDOM.NodeTypes.INTERPOLATION) { + yield node; + } + else if (node.type === CompilerDOM.NodeTypes.IF) { + for (const branch of node.branches) { + for (const childNode of branch.children) { + yield* forEachInterpolationNode(childNode); + } + } + } + else if (node.type === CompilerDOM.NodeTypes.FOR) { + for (const child of node.children) { + yield* forEachInterpolationNode(child); + } + } +} diff --git a/packages/language-service/lib/plugins/vue-template.ts b/packages/language-service/lib/plugins/vue-template.ts index 0784333f74..c419994da1 100644 --- a/packages/language-service/lib/plugins/vue-template.ts +++ b/packages/language-service/lib/plugins/vue-template.ts @@ -6,7 +6,14 @@ import type { LanguageServicePlugin, TextDocument, } from '@volar/language-service'; -import { hyphenateAttr, hyphenateTag, tsCodegen, type VueVirtualCode } from '@vue/language-core'; +import type * as CompilerDOM from '@vue/compiler-dom'; +import { + forEachInterpolationNode, + hyphenateAttr, + hyphenateTag, + tsCodegen, + type VueVirtualCode, +} from '@vue/language-core'; import { camelize, capitalize } from '@vue/shared'; import type { ComponentPropInfo } from '@vue/typescript-plugin/lib/requests/getComponentProps'; import { create as createHtmlService } from 'volar-service-html'; @@ -157,6 +164,43 @@ export function create( disposable?.dispose(); }, + provideAutoInsertSnippet(document, position, autoInsertContext, token) { + if (document.languageId !== languageId) { + return; + } + const info = resolveEmbeddedCode(context, document.uri); + if (info?.code.id !== 'template') { + return; + } + + return baseServiceInstance.provideAutoInsertSnippet?.( + { + ...document, + getText: () => { + if (info.root.sfc.template?.ast) { + const locs: CompilerDOM.SourceLocation[] = []; + for (const node of forEachInterpolationNode(info.root.sfc.template.ast)) { + locs.push(node.loc); + } + let text = ''; + let offset = 0; + for (const loc of locs) { + text += info.root.sfc.template.content.slice(offset, loc.start.offset + 2); + text += ' '.repeat(loc.source.length - 4); + offset = loc.end.offset - 2; + } + text += info.root.sfc.template.content.slice(offset); + return text; + } + return document.getText(); + }, + }, + position, + autoInsertContext, + token, + ); + }, + async provideCompletionItems(document, position, completionContext, token) { if (document.languageId !== languageId) { return;