Skip to content

Commit

Permalink
feat: improve auto-complete in template
Browse files Browse the repository at this point in the history
close #823, close #1284
  • Loading branch information
johnsoncodehk committed Aug 10, 2022
1 parent 4d26332 commit e6ed2e7
Show file tree
Hide file tree
Showing 7 changed files with 76 additions and 38 deletions.
90 changes: 67 additions & 23 deletions packages/vue-language-core/src/generators/template.ts
Expand Up @@ -6,6 +6,7 @@ import * as CompilerCore from '@vue/compiler-core';
import { EmbeddedFileMappingData, _VueCompilerOptions } from '../types';
import { colletVars, walkInterpolationFragment } from '../utils/transform';
import { parseBindingRanges } from '../parsers/scriptSetupRanges';
import { SearchTexts } from '../utils/string';

const capabilitiesSet = {
all: { basic: true, diagnostic: true, references: true, definitions: true, rename: true, completion: true, semanticTokens: true },
Expand Down Expand Up @@ -64,10 +65,6 @@ export function generate(
templateAst: CompilerDOM.RootNode,
hasScriptSetup: boolean,
cssScopedClasses: string[] = [],
searchTexts: {
getEmitCompletion(tag: string): string,
getPropsCompletion(tag: string): string,
},
) {

const tsCodeGen = new CodeGen<EmbeddedFileMappingData>();
Expand All @@ -89,6 +86,7 @@ export function generate(
offsets: number[],
} | undefined> = {};
const localVars: Record<string, number> = {};
const tempVars: ReturnType<typeof walkInterpolationFragment>[] = [];
const identifiers = new Set<string>();
const scopedClasses: { className: string, offset: number; }[] = [];
const blockConditions: string[] = [];
Expand Down Expand Up @@ -202,12 +200,12 @@ export function generate(
tsCodeGen.addText('/* Completion: Emits */\n');
for (const name of componentNames) {
tsCodeGen.addText('// @ts-ignore\n');
tsCodeGen.addText(`${var_emit}('${searchTexts.getEmitCompletion(name)}');\n`);
tsCodeGen.addText(`${var_emit}('${SearchTexts.EmitCompletion(name)}');\n`);
}
tsCodeGen.addText('/* Completion: Props */\n');
for (const name of componentNames) {
tsCodeGen.addText('// @ts-ignore\n');
tsCodeGen.addText(`(<${isIntrinsicElement(vueCompilerOptions.experimentalRuntimeMode, tagName) ? tagName : var_componentVar} ${searchTexts.getPropsCompletion(name)}/>);\n`);
tsCodeGen.addText(`(<${isIntrinsicElement(vueCompilerOptions.experimentalRuntimeMode, tagName) ? tagName : var_componentVar} ${SearchTexts.PropsCompletion(name)}/>);\n`);
}

tagResolves[tagName] = {
Expand Down Expand Up @@ -264,6 +262,8 @@ export function generate(
}
tsCodeGen.addText(`};\n`);

writeInterpolationVarsExtraCompletion();

return {
codeGen: tsCodeGen,
formatCodeGen: tsFormatCodeGen,
Expand Down Expand Up @@ -293,23 +293,22 @@ export function generate(
const context = node.loc.source.substring(2, node.loc.source.length - 2);
let start = node.loc.start.offset + 2;

tsCodeGen.addText(`(`);
writeInterpolation(
context,
start,
{
vueTag: 'template',
capabilities: capabilitiesSet.all,
},
'',
'',
'(',
');\n',
);
writeInterpolationVarsExtraCompletion();
writeFormatCode(
context,
start,
formatBrackets.curly,
);
tsCodeGen.addText(`);\n`);
}
else if (node.type === CompilerDOM.NodeTypes.IF) {
// v-if / v-else-if / v-else
Expand Down Expand Up @@ -351,12 +350,14 @@ export function generate(
blockConditions.push(branch.condition.content);
addedBlockCondition = true;
}

tsCodeGen.addText(` {\n`);
writeInterpolationVarsExtraCompletion();
for (const childNode of branch.children) {
visitNode(childNode, parentEl);
}
tsCodeGen.addText('}\n');
}
tsCodeGen.addText(` {\n`);
for (const childNode of branch.children) {
visitNode(childNode, parentEl);
}
tsCodeGen.addText('}\n');

if (addedBlockCondition) {
blockConditions[blockConditions.length - 1] = `!(${blockConditions[blockConditions.length - 1]})`;
Expand Down Expand Up @@ -413,14 +414,17 @@ export function generate(
source.loc.start.offset,
formatBrackets.empty,
);
}
tsCodeGen.addText(`) {\n`);

for (const childNode of node.children) {
visitNode(childNode, parentEl);
}
tsCodeGen.addText(`) {\n`);

writeInterpolationVarsExtraCompletion();

for (const childNode of node.children) {
visitNode(childNode, parentEl);
}

tsCodeGen.addText('}\n');
tsCodeGen.addText('}\n');
}

for (const varName of forBlockVars)
localVars[varName]--;
Expand Down Expand Up @@ -758,6 +762,7 @@ export function generate(
appendExpressionNode(prop, prop_2.value);
}
tsCodeGen.addText(`};\n`);
writeInterpolationVarsExtraCompletion();
}
}
else if (
Expand Down Expand Up @@ -830,7 +835,7 @@ export function generate(
capabilities: capabilitiesSet.all,
},
prefix,
suffix,
suffix
);
writeFormatCode(
expNode.content,
Expand All @@ -845,6 +850,8 @@ export function generate(
}
}

writeInterpolationVarsExtraCompletion();

function tryWriteInstance() {

if (writedInstance)
Expand Down Expand Up @@ -1310,6 +1317,7 @@ export function generate(
tsCodeGen.addText(`const ${varComponentInstance} = new ${tag.component}({ `);
writeProps(parentEl, false, 'slots');
tsCodeGen.addText(`});\n`);
writeInterpolationVarsExtraCompletion();
tsCodeGen.addText(`declare const ${varSlots}: __VLS_types.ExtractComponentSlots<typeof ${varComponentInstance}>;\n`);
}

Expand Down Expand Up @@ -1389,6 +1397,7 @@ export function generate(
'',
);
tsCodeGen.addText(`]`);
writeInterpolationVarsExtraCompletion();
}
const diagEnd = tsCodeGen.getText().length;
tsCodeGen.addMapping2({
Expand Down Expand Up @@ -1495,6 +1504,7 @@ export function generate(
},
});
tsCodeGen.addText(`;\n`);
writeInterpolationVarsExtraCompletion();
}
}
}
Expand All @@ -1517,6 +1527,7 @@ export function generate(
')',
);
tsCodeGen.addText(`;\n`);
writeInterpolationVarsExtraCompletion();
}
}
}
Expand Down Expand Up @@ -1599,6 +1610,7 @@ export function generate(
')',
);
tsCodeGen.addText(`;\n`);
writeInterpolationVarsExtraCompletion();
break;
}
}
Expand Down Expand Up @@ -1664,6 +1676,8 @@ export function generate(
}
tsCodeGen.addText(`};\n`);

writeInterpolationVarsExtraCompletion();

if (hasDefaultBind) {
tsCodeGen.addText(`var ${varSlot}!: typeof ${varDefaultBind} & typeof ${varBinds};\n`);
}
Expand Down Expand Up @@ -1831,7 +1845,7 @@ export function generate(
prefix: string,
suffix: string,
) {
walkInterpolationFragment(ts, prefix + mapCode + suffix, (frag, fragOffset, isJustForErrorMapping) => {
const vars = walkInterpolationFragment(ts, prefix + mapCode + suffix, (frag, fragOffset, isJustForErrorMapping) => {
if (fragOffset === undefined) {
tsCodeGen.addText(frag);
}
Expand Down Expand Up @@ -1872,6 +1886,36 @@ export function generate(
tsCodeGen.addText(addSubfix);
}
}, localVars, identifiers);
if (sourceOffset !== undefined) {
for (const v of vars) {
v.offset = sourceOffset + v.offset - prefix.length;
}
if (vars.length) {
tempVars.push(vars);
}
}
}
function writeInterpolationVarsExtraCompletion() {

if (!tempVars.length)
return;

tsCodeGen.addText('[');
for (const _vars of tempVars) {
for (const v of _vars) {
tsCodeGen.addCode2(v.text, v.offset, {
vueTag: 'template',
capabilities: {
completion: {
additional: true,
},
},
});
tsCodeGen.addText(',');
}
}
tsCodeGen.addText('];\n');
tempVars.length = 0;
}
function writeFormatCode(mapCode: string, sourceOffset: number, formatWrapper: [string, string]) {
tsFormatCodeGen.addText(formatWrapper[0]);
Expand Down
5 changes: 0 additions & 5 deletions packages/vue-language-core/src/plugins/vue-tsx.ts
Expand Up @@ -7,7 +7,6 @@ import { Sfc, VueLanguagePlugin } from '../sourceFile';
import { TextRange } from '../types';
import { parseCssClassNames } from '../utils/parseCssClassNames';
import { parseCssVars } from '../utils/parseCssVars';
import { SearchTexts } from '../utils/string';

const plugin: VueLanguagePlugin = ({ modules, vueCompilerOptions, compilerOptions }) => {

Expand Down Expand Up @@ -132,10 +131,6 @@ const plugin: VueLanguagePlugin = ({ modules, vueCompilerOptions, compilerOption
sfc.templateAst,
!!sfc.scriptSetup,
Object.values(cssScopedClasses.value).map(style => style.classNames).flat(),
{
getEmitCompletion: SearchTexts.EmitCompletion,
getPropsCompletion: SearchTexts.PropsCompletion,
}
);
});
const tsxGen = computed(() => genScript(
Expand Down
4 changes: 3 additions & 1 deletion packages/vue-language-core/src/types.ts
Expand Up @@ -39,7 +39,9 @@ export interface EmbeddedFileMappingData {
in: boolean,
out: boolean,
},
completion?: boolean,
completion?: boolean | {
additional: boolean,
},
semanticTokens?: boolean,
referencesCodeLens?: boolean,
displayWithLink?: boolean,
Expand Down
4 changes: 2 additions & 2 deletions packages/vue-language-core/src/utils/transform.ts
Expand Up @@ -14,7 +14,6 @@ export function walkInterpolationFragment(
isShorthand: boolean,
offset: number,
}[] = [];
// let localVarOffsets: number[] = [];

const ast = ts.createSourceFile('/foo.ts', code, ts.ScriptTarget.ESNext);
const varCb = (id: ts.Identifier, isShorthand: boolean) => {
Expand All @@ -39,7 +38,6 @@ export function walkInterpolationFragment(
ast.forEachChild(node => walkIdentifiers(ts, node, varCb, localVars));

ctxVars = ctxVars.sort((a, b) => a.offset - b.offset);
// localVarOffsets = localVarOffsets.sort((a, b) => a - b);

if (ctxVars.length) {

Expand Down Expand Up @@ -73,6 +71,8 @@ export function walkInterpolationFragment(
else {
cb(code, 0);
}

return ctxVars;
}

function walkIdentifiers(
Expand Down
Expand Up @@ -136,7 +136,7 @@ export function register(context: LanguageServiceRuntimeContext) {

const plugins = context.getPlugins().sort(sortPlugins);

for (const [embeddedRange] of sourceMap.getMappedRanges(position, position, data => !!data.capabilities.completion)) {
for (const [embeddedRange, data] of sourceMap.getMappedRanges(position, position, data => !!data.capabilities.completion)) {

for (const plugin of plugins) {

Expand All @@ -149,7 +149,8 @@ export function register(context: LanguageServiceRuntimeContext) {
if (completionContext?.triggerCharacter && !plugin.complete.triggerCharacters?.includes(completionContext.triggerCharacter))
continue;

if (cache!.mainCompletion && (!plugin.complete.isAdditional || cache?.mainCompletion.documentUri !== sourceMap.mappedDocument.uri))
const isAdditionalMapping = typeof data.capabilities.completion === 'object' && data.capabilities.completion.additional;
if (cache!.mainCompletion && ((!plugin.complete.isAdditional && !isAdditionalMapping) || cache?.mainCompletion.documentUri !== sourceMap.mappedDocument.uri))
continue;

// avoid duplicate items with .vue and .vue.html
Expand Down
4 changes: 0 additions & 4 deletions packages/vue-language-service/tests/complete.ts
Expand Up @@ -75,10 +75,6 @@ for (const dirName of testDirs) {
expect(result.replace(/\r\n/g, '\n')).toBe(expectedFileText.replace(/\r\n/g, '\n'));
});
}

if (!actions.length) {
it(`ignore`);
}
}
});
}
Expand Down
@@ -1,6 +1,6 @@
<template>
{{ f }}
<!-- ^complete_todo: foo -->
<!-- ^complete: foo -->
</template>

<script lang="ts" setup>
Expand Down

0 comments on commit e6ed2e7

Please sign in to comment.