Skip to content

Commit 3eaf9ac

Browse files
authored
feat: directive keyword autocomplete (#2707)
#2696
1 parent 3922861 commit 3eaf9ac

File tree

5 files changed

+110
-6
lines changed

5 files changed

+110
-6
lines changed

packages/language-server/src/plugins/css/features/svelte-selectors.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Applying styles to a selector globally`,
99
references: [
1010
{
1111
name: 'Svelte.dev Reference',
12-
url: 'https://svelte.dev/docs#style'
12+
url: 'https://svelte.dev/docs/svelte/global-styles'
1313
}
1414
]
1515
}

packages/language-server/src/plugins/html/HTMLPlugin.ts

+22-4
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import { wordHighlightForTag } from '../../lib/documentHighlight/wordHighlight';
4747

4848
// https://github.com/microsoft/vscode/blob/c6f507deeb99925e713271b1048f21dbaab4bd54/extensions/html/language-configuration.json#L34
4949
const wordPattern = /(-?\d*\.\d\w*)|([^`~!@$^&*()=+[{\]}\|;:'",.<>\/\s]+)/g;
50+
const attributeValuePlaceHolder = '="$1"';
5051

5152
export class HTMLPlugin
5253
implements
@@ -166,18 +167,32 @@ export class HTMLPlugin
166167
: null;
167168

168169
const svelteStrictMode = prettierConfig?.svelteStrictMode;
170+
const startQuote = svelteStrictMode ? '"{' : '{';
171+
const endQuote = svelteStrictMode ? '}"' : '}';
169172

170173
items.forEach((item) => {
171-
const startQuote = svelteStrictMode ? '"{' : '{';
172-
const endQuote = svelteStrictMode ? '}"' : '}';
174+
if (item.label.endsWith(':')) {
175+
item.kind = CompletionItemKind.Keyword;
176+
177+
if (item.textEdit) {
178+
item.textEdit.newText = item.textEdit.newText.replace(
179+
attributeValuePlaceHolder,
180+
''
181+
);
182+
}
183+
}
184+
173185
if (!item.textEdit) {
174186
return;
175187
}
176188

177189
if (item.label.startsWith('on:')) {
178190
item.textEdit = {
179191
...item.textEdit,
180-
newText: item.textEdit.newText.replace('="$1"', `$2=${startQuote}$1${endQuote}`)
192+
newText: item.textEdit.newText.replace(
193+
attributeValuePlaceHolder,
194+
`$2=${startQuote}$1${endQuote}`
195+
)
181196
};
182197
// In Svelte 5, people should use `onclick` instead of `on:click`
183198
if (document.isSvelte5) {
@@ -188,7 +203,10 @@ export class HTMLPlugin
188203
if (item.label.startsWith('bind:')) {
189204
item.textEdit = {
190205
...item.textEdit,
191-
newText: item.textEdit.newText.replace('="$1"', `=${startQuote}$1${endQuote}`)
206+
newText: item.textEdit.newText.replace(
207+
attributeValuePlaceHolder,
208+
`=${startQuote}$1${endQuote}`
209+
)
192210
};
193211
}
194212
});

packages/language-server/src/plugins/html/dataProvider.ts

+71
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,76 @@ const svelteAttributes: IAttributeData[] = [
8787
'To get a reference to a DOM node, use bind:this. If used on a component, gets a reference to that component instance.'
8888
}
8989
];
90+
91+
// directives that takes a variable. Don't specify the variable here and let typescript handle it
92+
const svelteDirectiveKeywords: IAttributeData[] = [
93+
{
94+
name: 'use:',
95+
description: 'Actions are functions that are called when an element is mounted.',
96+
references: [
97+
{
98+
name: 'Svelte.dev Reference',
99+
url: 'https://svelte.dev/docs/svelte/use'
100+
}
101+
]
102+
},
103+
{
104+
name: 'transition:',
105+
description:
106+
'A transition is triggered by an element entering or leaving the DOM as a result of a state change.',
107+
references: [
108+
{
109+
name: 'Svelte.dev Reference',
110+
url: 'https://svelte.dev/docs/svelte/transition'
111+
}
112+
]
113+
},
114+
{
115+
name: 'in:',
116+
description:
117+
'The in: and out: directives are identical to transition:, except that the resulting transitions are not bidirectional.',
118+
references: [
119+
{
120+
name: 'Svelte.dev Reference',
121+
url: 'https://svelte.dev/docs/svelte/in-and-out'
122+
}
123+
]
124+
},
125+
{
126+
name: 'out:',
127+
description:
128+
'The in: and out: directives are identical to transition:, except that the resulting transitions are not bidirectional.',
129+
references: [
130+
{
131+
name: 'Svelte.dev Reference',
132+
url: 'https://svelte.dev/docs/svelte/in-and-out'
133+
}
134+
]
135+
},
136+
{
137+
name: 'animate:',
138+
description:
139+
'An animation is triggered when the contents of a keyed each block are re-ordered.',
140+
references: [
141+
{
142+
name: 'Svelte.dev Reference',
143+
url: 'https://svelte.dev/docs/svelte/animate'
144+
}
145+
]
146+
},
147+
{
148+
name: 'style:',
149+
description:
150+
'The style: directive provides a shorthand for setting multiple styles on an element.',
151+
references: [
152+
{
153+
name: 'Svelte.dev Reference',
154+
url: 'https://svelte.dev/docs/svelte/style'
155+
}
156+
]
157+
}
158+
];
159+
90160
const sveltekitAttributes: IAttributeData[] = [
91161
{
92162
name: 'data-sveltekit-keepfocus',
@@ -422,6 +492,7 @@ export const svelteHtmlDataProvider = newHTMLDataProvider('svelte-builtin', {
422492
version: 1,
423493
globalAttributes: [
424494
...htmlData.globalAttributes!,
495+
...svelteDirectiveKeywords,
425496
...svelteEvents,
426497
...svelteAttributes,
427498
...sveltekitAttributes

packages/language-server/src/plugins/svelte/features/getModifierData.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ export function getModifierData(): ModifierData[] {
5757
5858
${item.documentation}
5959
60-
https://svelte.dev/docs#template-syntax-element-directives-on-eventname`
60+
https://v4.svelte.dev/docs/element-directives#on-eventname`
6161
}
6262
}));
6363
}

packages/language-server/test/plugins/html/HTMLPlugin.test.ts

+15
Original file line numberDiff line numberDiff line change
@@ -353,4 +353,19 @@ describe('HTML Plugin', () => {
353353
}
354354
]);
355355
});
356+
357+
it('provide directive completions', async () => {
358+
const { plugin, document } = setup('<div t');
359+
360+
const completions = await plugin.getCompletions(document, Position.create(0, 6));
361+
const item = completions?.items.find((item) => item.label === 'transition:');
362+
assert.equal(item?.kind, CompletionItemKind.Keyword);
363+
assert.deepStrictEqual(item?.textEdit, {
364+
newText: 'transition:',
365+
range: {
366+
start: { line: 0, character: 5 },
367+
end: { line: 0, character: 6 }
368+
}
369+
});
370+
});
356371
});

0 commit comments

Comments
 (0)