Skip to content

Commit 6c573a8

Browse files
v4: Improve DX around completions when prefixes are in use (#1292)
Fixes #1291 - [x] Really needs tests
1 parent 5ffa254 commit 6c573a8

File tree

3 files changed

+98
-18
lines changed

3 files changed

+98
-18
lines changed

packages/tailwindcss-language-server/tests/completions/completions.test.js

+74
Original file line numberDiff line numberDiff line change
@@ -740,6 +740,80 @@ defineTest({
740740
},
741741
})
742742

743+
defineTest({
744+
name: 'v4: Completions show after a variant arbitrary value, using prefixes',
745+
fs: {
746+
'app.css': css`
747+
@import 'tailwindcss' prefix(tw);
748+
`,
749+
},
750+
prepare: async ({ root }) => ({ client: await createClient({ root }) }),
751+
handle: async ({ client }) => {
752+
let document = await client.open({
753+
lang: 'html',
754+
text: '<div class="tw:data-[foo]:">',
755+
})
756+
757+
// <div class="tw:data-[foo]:">
758+
// ^
759+
let completion = await document.completions({ line: 0, character: 26 })
760+
761+
expect(completion?.items.length).toBe(19236)
762+
},
763+
})
764+
765+
defineTest({
766+
name: 'v4: Variant and utility suggestions show prefix when one has been typed',
767+
fs: {
768+
'app.css': css`
769+
@import 'tailwindcss' prefix(tw);
770+
`,
771+
},
772+
prepare: async ({ root }) => ({ client: await createClient({ root }) }),
773+
handle: async ({ client }) => {
774+
let document = await client.open({
775+
lang: 'html',
776+
text: '<div class="">',
777+
})
778+
779+
// <div class="">
780+
// ^
781+
let completion = await document.completions({ line: 0, character: 12 })
782+
783+
expect(completion?.items.length).toBe(19237)
784+
785+
// Verify that variants and utilities are all prefixed
786+
let prefixed = completion.items.filter((item) => !item.label.startsWith('tw:'))
787+
expect(prefixed).toHaveLength(0)
788+
},
789+
})
790+
791+
defineTest({
792+
name: 'v4: Variant and utility suggestions hide prefix when it has been typed',
793+
fs: {
794+
'app.css': css`
795+
@import 'tailwindcss' prefix(tw);
796+
`,
797+
},
798+
prepare: async ({ root }) => ({ client: await createClient({ root }) }),
799+
handle: async ({ client }) => {
800+
let document = await client.open({
801+
lang: 'html',
802+
text: '<div class="tw:">',
803+
})
804+
805+
// <div class="tw:">
806+
// ^
807+
let completion = await document.completions({ line: 0, character: 15 })
808+
809+
expect(completion?.items.length).toBe(19236)
810+
811+
// Verify that no variants and utilities have prefixes
812+
let prefixed = completion.items.filter((item) => item.label.startsWith('tw:'))
813+
expect(prefixed).toHaveLength(0)
814+
},
815+
})
816+
743817
defineTest({
744818
name: 'v4: Completions show inside class functions in JS/TS files',
745819
fs: {

packages/tailwindcss-language-service/src/completionProvider.ts

+18-18
Original file line numberDiff line numberDiff line change
@@ -261,29 +261,25 @@ export function completionsFromClassList(
261261

262262
// TODO: This is a bit of a hack
263263
if (prefix.length > 0) {
264-
// No variants seen: suggest the prefix only
264+
// No variants seen:
265+
// - suggest the prefix as a variant
266+
// - Modify the remaining items to include the prefix in the variant name
265267
if (existingVariants.length === 0) {
266-
items = items.slice(0, 1)
268+
items = items.map((item, idx) => {
269+
if (idx === 0) return item
267270

268-
return withDefaults(
269-
{
270-
isIncomplete: false,
271-
items,
272-
},
273-
{
274-
data: {
275-
...(state.completionItemData ?? {}),
276-
...(important ? { important } : {}),
277-
variants: existingVariants,
278-
},
279-
range: replacementRange,
280-
},
281-
state.editor.capabilities.itemDefaults,
282-
)
271+
item.label = `${prefix}:${item.label}`
272+
273+
if (item.textEditText) {
274+
item.textEditText = `${prefix}:${item.textEditText}`
275+
}
276+
277+
return item
278+
})
283279
}
284280

285281
// The first variant is not the prefix: don't suggest anything
286-
if (existingVariants[0] !== prefix) {
282+
if (existingVariants.length > 0 && existingVariants[0] !== prefix) {
287283
return null
288284
}
289285
}
@@ -304,6 +300,10 @@ export function completionsFromClassList(
304300
documentation = formatColor(color)
305301
}
306302

303+
if (prefix.length > 0 && existingVariants.length === 0) {
304+
className = `${prefix}:${className}`
305+
}
306+
307307
items.push({
308308
label: className,
309309
kind,

packages/tailwindcss-language-service/src/util/getVariantsFromClassName.ts

+6
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ export function getVariantsFromClassName(
3333
// NOTE: This should never happen
3434
if (!state.designSystem) return false
3535

36+
let prefix = state.designSystem.theme.prefix ?? ''
37+
38+
if (prefix !== '') {
39+
className = `${prefix}:${className}`
40+
}
41+
3642
// We don't use `compile()` so there's no overhead from PostCSS
3743
let compiled = state.designSystem.candidatesToCss([className])
3844

0 commit comments

Comments
 (0)