Skip to content

Commit c30b678

Browse files
committed
feat(language-service): auto insert const props = with props completion
1 parent df6780a commit c30b678

File tree

5 files changed

+117
-3
lines changed

5 files changed

+117
-3
lines changed

packages/language-core/lib/parsers/scriptSetupRanges.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@ export function parseScriptSetupRanges(
2727
const slots: {
2828
name?: string;
2929
isObjectBindingPattern?: boolean;
30-
define?: ReturnType<typeof parseDefineFunction>;
30+
define?: ReturnType<typeof parseDefineFunction> & {
31+
statement: TextRange;
32+
};
3133
} = {};
3234
const emits: {
3335
name?: string;
@@ -281,7 +283,10 @@ export function parseScriptSetupRanges(
281283
});
282284
}
283285
else if (vueCompilerOptions.macros.defineSlots.includes(callText)) {
284-
slots.define = parseDefineFunction(node);
286+
slots.define = {
287+
...parseDefineFunction(node),
288+
statement: getStatementRange(ts, parents, node, ast)
289+
};
285290
if (ts.isVariableDeclaration(parent)) {
286291
if (ts.isIdentifier(parent.name)) {
287292
slots.name = getNodeText(ts, parent.name, ast);

packages/language-server/tests/completions.spec.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,20 @@ describe('Completions', async () => {
456456
`);
457457
});
458458
459+
// FIXME:
460+
it.skip('Auto insert defines', async () => {
461+
expect(
462+
(await requestCompletionItem('tsconfigProject/fixture.vue', 'vue', `
463+
<script lang="ts" setup>
464+
defineProps<{
465+
foo: string;
466+
}>();
467+
props|
468+
</script>
469+
`, 'props'))
470+
).toMatchInlineSnapshot(``)
471+
});
472+
459473
const openedDocuments: TextDocument[] = [];
460474
461475
afterEach(async () => {

packages/language-service/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { create as createTypeScriptTwoslashQueriesPlugin } from 'volar-service-t
1616
import { create as createTypeScriptDocCommentTemplatePlugin } from 'volar-service-typescript/lib/plugins/docCommentTemplate';
1717
import { create as createTypeScriptSyntacticPlugin } from 'volar-service-typescript/lib/plugins/syntactic';
1818
import { create as createCssPlugin } from './lib/plugins/css';
19+
import { create as createVueAutoDefinesPlugin } from './lib/plugins/vue-autoinsert-defines';
1920
import { create as createVueAutoDotValuePlugin } from './lib/plugins/vue-autoinsert-dotvalue';
2021
import { create as createVueAutoAddSpacePlugin } from './lib/plugins/vue-autoinsert-space';
2122
import { create as createVueDirectiveCommentsPlugin } from './lib/plugins/vue-directive-comments';
@@ -197,6 +198,7 @@ function getCommonLanguageServicePlugins(
197198
createVueTwoslashQueriesPlugin(getTsPluginClient),
198199
createVueDocumentLinksPlugin(),
199200
createVueDocumentDropPlugin(ts, getTsPluginClient),
201+
createVueAutoDefinesPlugin(),
200202
createVueAutoDotValuePlugin(ts, getTsPluginClient),
201203
createVueAutoAddSpacePlugin(),
202204
createVueInlayHintsPlugin(ts),
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import type { LanguageServicePlugin, LanguageServicePluginInstance } from '@volar/language-service';
2+
import * as vscode from 'vscode-languageserver-protocol';
3+
import { URI } from 'vscode-uri';
4+
import { TextRange, tsCodegen, VueVirtualCode } from '@vue/language-core';
5+
import { isTsDocument } from './vue-autoinsert-dotvalue';
6+
7+
export function create(): LanguageServicePlugin {
8+
return {
9+
name: 'vue-autoinsert-defines',
10+
capabilities: {
11+
completionProvider: {
12+
triggerCharacters: ['\\w']
13+
}
14+
},
15+
create(context): LanguageServicePluginInstance {
16+
return {
17+
async provideCompletionItems(document) {
18+
if (!isTsDocument(document)) {
19+
return;
20+
}
21+
22+
const uri = URI.parse(document.uri);
23+
const result: vscode.CompletionItem[] = [];
24+
const decoded = context.decodeEmbeddedDocumentUri(uri);
25+
const sourceScript = decoded && context.language.scripts.get(decoded[0]);
26+
const virtualCode = decoded && sourceScript?.generated?.embeddedCodes.get(decoded[1]);
27+
if (!sourceScript || !virtualCode) {
28+
return;
29+
}
30+
31+
const root = sourceScript?.generated?.root;
32+
if (!(root instanceof VueVirtualCode)) {
33+
return;
34+
}
35+
36+
const codegen = tsCodegen.get(root._sfc);
37+
const scriptSetup = root._sfc.scriptSetup;
38+
const scriptSetupRanges = codegen?.scriptSetupRanges.get();
39+
if (!scriptSetup || !scriptSetupRanges) {
40+
return;
41+
}
42+
43+
const mappings = [...context.language.maps.forEach(virtualCode)];
44+
45+
addDefineCompletionItem(scriptSetupRanges.props.define, 'props');
46+
addDefineCompletionItem(scriptSetupRanges.emits.define, 'emit');
47+
addDefineCompletionItem(scriptSetupRanges.slots.define, 'slots');
48+
49+
return {
50+
isIncomplete: false,
51+
items: result
52+
};
53+
54+
function addDefineCompletionItem(
55+
define: {
56+
exp: TextRange,
57+
statement: TextRange
58+
} | undefined,
59+
name: string
60+
) {
61+
if (!define || define.exp.start !== define.statement?.start) {
62+
return;
63+
}
64+
65+
let offset;
66+
for (const [, map] of mappings) {
67+
for (const [generatedOffset] of map.toGeneratedLocation(scriptSetup!.startTagEnd + define.exp.start)) {
68+
offset = generatedOffset;
69+
break;
70+
}
71+
}
72+
if (offset === undefined) {
73+
return;
74+
}
75+
76+
const pos = document.positionAt(offset);
77+
result.push({
78+
label: name,
79+
kind: vscode.CompletionItemKind.Variable,
80+
additionalTextEdits: [{
81+
newText: `const ${name} = `,
82+
range: {
83+
start: pos,
84+
end: pos
85+
}
86+
}]
87+
});
88+
}
89+
},
90+
};
91+
},
92+
};
93+
}

packages/language-service/lib/plugins/vue-autoinsert-dotvalue.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ function sleep(ms: number) {
113113
return new Promise(resolve => setTimeout(resolve, ms));
114114
}
115115

116-
function isTsDocument(document: TextDocument) {
116+
export function isTsDocument(document: TextDocument) {
117117
return document.languageId === 'javascript' ||
118118
document.languageId === 'typescript' ||
119119
document.languageId === 'javascriptreact' ||

0 commit comments

Comments
 (0)