Use `itemDefaults` to reduce size of completion lists (#706)
* Use completion list `itemDefaults`
* more defaults
diff --git a/packages/tailwindcss-language-service/src/completionProvider.ts b/packages/tailwindcss-language-service/src/completionProvider.ts
index 63a5edf3b6f87daf725592ef09d841e7e54b5bae..9f7a246cd3d96580cb705ca6551bac845ced5dd2 100644
--- a/packages/tailwindcss-language-service/src/completionProvider.ts
+++ b/packages/tailwindcss-language-service/src/completionProvider.ts
@@ -97,34 +97,36 @@ }
}
if (modifiers) {
- return {
- isIncomplete: false,
- items: modifiers.map((modifier, index) => {
- let className = `${beforeSlash}/${modifier}`
- let kind: CompletionItemKind = 21
- let documentation: string = null
+ return withDefaults(
+ {
+ isIncomplete: false,
+ items: modifiers.map((modifier, index) => {
+ let className = `${beforeSlash}/${modifier}`
+ let kind: CompletionItemKind = 21
+ let documentation: string | undefined
- const color = getColor(state, className)
- if (color !== null) {
- kind = 16
- if (typeof color !== 'string' && (color.alpha ?? 1) !== 0) {
- documentation = culori.formatRgb(color)
+ const color = getColor(state, className)
+ if (color !== null) {
+ kind = 16
+ if (typeof color !== 'string' && (color.alpha ?? 1) !== 0) {
+ documentation = culori.formatRgb(color)
+ }
}
- }
- return {
- label: className,
- documentation,
- kind,
- sortText: naturalExpand(index),
- data: [className],
- textEdit: {
- newText: className,
- range: replacementRange,
- },
- }
- }),
- }
+ return {
+ label: className,
+ ...(documentation ? { documentation } : {}),
+ kind,
+ sortText: naturalExpand(index),
+ }
+ }),
+ },
+ {
+ range: replacementRange,
+ data: state.completionItemData,
+ },
+ state.editor.capabilities.itemDefaults
+ )
}
}
@@ -141,13 +143,14 @@ if (!important) {
let variantOrder = 0
function variantItem(
- item: Omit<CompletionItem, 'kind' | 'data' | 'sortText' | 'textEdit'> & {
- textEdit?: { newText: string; range?: Range }
- }
+ item: Omit<CompletionItem, 'kind' | 'data' | 'command' | 'sortText' | 'textEdit'>
): CompletionItem {
return {
kind: 9,
- data: 'variant',
+ data: {
+ ...(state.completionItemData ?? {}),
+ _type: 'variant',
+ },
command:
item.insertTextFormat === 2 // Snippet
? undefined
@@ -157,11 +160,6 @@ command: 'editor.action.triggerSuggest',
},
sortText: '-' + naturalExpand(variantOrder++),
...item,
- textEdit: {
- newText: item.label,
- range: replacementRange,
- ...item.textEdit,
- },
}
}
@@ -174,9 +172,7 @@ items.push(
variantItem({
label: `${variant.name}${variant.hasDash ? '-' : ''}[]${sep}`,
insertTextFormat: 2,
- textEdit: {
- newText: `${variant.name}${variant.hasDash ? '-' : ''}[\${1}]${sep}\${0}`,
- },
+ textEditText: `${variant.name}${variant.hasDash ? '-' : ''}[\${1}]${sep}\${0}`,
// command: {
// title: '',
// command: 'tailwindCSS.onInsertArbitraryVariantSnippet',
@@ -199,9 +195,7 @@ items.push(
variantItem({
label: `${variant.name}${sep}`,
detail: variant.selectors().join(', '),
- textEdit: {
- newText: resultingVariants[resultingVariants.length - 1] + sep,
- },
+ textEditText: resultingVariants[resultingVariants.length - 1] + sep,
additionalTextEdits:
shouldSortVariants && resultingVariants.length > 1
? [
@@ -248,48 +242,141 @@ )
}
if (state.classList) {
- return {
+ return withDefaults(
+ {
+ isIncomplete: false,
+ items: items.concat(
+ state.classList.map(([className, { color }], index) => {
+ let kind: CompletionItemKind = color ? 16 : 21
+ let documentation: string | undefined
+
+ if (color && typeof color !== 'string') {
+ documentation = culori.formatRgb(color)
+ }
+
+ return {
+ label: className,
+ kind,
+ ...(documentation ? { documentation } : {}),
+ sortText: naturalExpand(index, state.classList.length),
+ } as CompletionItem
+ })
+ ),
+ },
+ {
+ data: {
+ ...(state.completionItemData ?? {}),
+ ...(important ? { important } : {}),
+ variants: existingVariants,
+ },
+ range: replacementRange,
+ },
+ state.editor.capabilities.itemDefaults
+ )
+ }
+
+ return withDefaults(
+ {
isIncomplete: false,
- items: items.concat(
- state.classList.map(([className, { color }], index) => {
- let kind: CompletionItemKind = color ? 16 : 21
- let documentation = null
+ items: items
+ .concat(
+ Object.keys(state.classNames.classNames)
+ .filter((className) => {
+ let item = state.classNames.classNames[className]
+ if (existingVariants.length === 0) {
+ return item.__info
+ }
+ return item.__info && isUtil(item)
+ })
+ .map((className, index, classNames) => {
+ let kind: CompletionItemKind = 21
+ let documentation: string | undefined
- if (color && typeof color !== 'string') {
- documentation = culori.formatRgb(color)
+ const color = getColor(state, className)
+ if (color !== null) {
+ kind = 16
+ if (typeof color !== 'string' && (color.alpha ?? 1) !== 0) {
+ documentation = culori.formatRgb(color)
+ }
+ }
+
+ return {
+ label: className,
+ kind,
+ ...(documentation ? { documentation } : {}),
+ sortText: naturalExpand(index, classNames.length),
+ } as CompletionItem
+ })
+ )
+ .filter((item) => {
+ if (item === null) {
+ return false
}
+ if (filter && !filter(item)) {
+ return false
+ }
+ return true
+ }),
+ },
+ {
+ range: replacementRange,
+ data: {
+ ...(state.completionItemData ?? {}),
+ variants: existingVariants,
+ ...(important ? { important } : {}),
+ },
+ },
+ state.editor.capabilities.itemDefaults
+ )
+ }
- return {
- label: className,
- kind,
- documentation,
- sortText: naturalExpand(index),
- data: [...existingVariants, important ? `!${className}` : className],
- textEdit: {
- newText: className,
- range: replacementRange,
- },
- } as CompletionItem
- })
- ),
+ for (let i = parts.length - 1; i > 0; i--) {
+ let keys = parts.slice(0, i).filter(Boolean)
+ subset = dlv(state.classNames.classNames, keys)
+ if (typeof subset !== 'undefined' && typeof dlv(subset, ['__info', '__rule']) === 'undefined') {
+ isSubset = true
+ subsetKey = keys
+ replacementRange = {
+ ...replacementRange,
+ start: {
+ ...replacementRange.start,
+ character: replacementRange.start.character + keys.join(sep).length + sep.length,
+ },
}
+ break
}
+ }
- return {
+ return withDefaults(
+ {
isIncomplete: false,
- items: items
+ items: Object.keys(isSubset ? subset : state.classNames.classNames)
+ .filter((k) => k !== '__info')
+ .filter((className) => isContextItem(state, [...subsetKey, className]))
+ .map((className, index, classNames): CompletionItem => {
+ return {
+ label: className + sep,
+ kind: 9,
+ command: {
+ title: '',
+ command: 'editor.action.triggerSuggest',
+ },
+ sortText: '-' + naturalExpand(index, classNames.length),
+ data: {
+ ...(state.completionItemData ?? {}),
+ className,
+ variants: subsetKey,
+ },
+ }
+ })
.concat(
- Object.keys(state.classNames.classNames)
- .filter((className) => {
- let item = state.classNames.classNames[className]
- if (existingVariants.length === 0) {
- return item.__info
- }
- return item.__info && isUtil(item)
- })
- .map((className, index) => {
+ Object.keys(isSubset ? subset : state.classNames.classNames)
+ .filter((className) =>
+ dlv(state.classNames.classNames, [...subsetKey, className, '__info'])
+ )
+ .map((className, index, classNames) => {
let kind: CompletionItemKind = 21
- let documentation: string = null
+ let documentation: string | undefined
const color = getColor(state, className)
if (color !== null) {
@@ -302,14 +389,9 @@
return {
label: className,
kind,
- documentation,
- sortText: naturalExpand(index),
- data: [...existingVariants, important ? `!${className}` : className],
- textEdit: {
- newText: className,
- range: replacementRange,
- },
- } as CompletionItem
+ ...(documentation ? { documentation } : {}),
+ sortText: naturalExpand(index, classNames.length),
+ }
})
)
.filter((item) => {
@@ -321,88 +403,16 @@ return false
}
return true
}),
- }
- }
-
- for (let i = parts.length - 1; i > 0; i--) {
- let keys = parts.slice(0, i).filter(Boolean)
- subset = dlv(state.classNames.classNames, keys)
- if (typeof subset !== 'undefined' && typeof dlv(subset, ['__info', '__rule']) === 'undefined') {
- isSubset = true
- subsetKey = keys
- replacementRange = {
- ...replacementRange,
- start: {
- ...replacementRange.start,
- character: replacementRange.start.character + keys.join(sep).length + sep.length,
- },
- }
- break
- }
- }
-
- return {
- isIncomplete: false,
- items: Object.keys(isSubset ? subset : state.classNames.classNames)
- .filter((k) => k !== '__info')
- .filter((className) => isContextItem(state, [...subsetKey, className]))
- .map((className, index): CompletionItem => {
- return {
- label: className + sep,
- kind: 9,
- documentation: null,
- command: {
- title: '',
- command: 'editor.action.triggerSuggest',
- },
- sortText: '-' + naturalExpand(index),
- data: [...subsetKey, className],
- textEdit: {
- newText: className + sep,
- range: replacementRange,
- },
- }
- })
- .concat(
- Object.keys(isSubset ? subset : state.classNames.classNames)
- .filter((className) =>
- dlv(state.classNames.classNames, [...subsetKey, className, '__info'])
- )
- .map((className, index) => {
- let kind: CompletionItemKind = 21
- let documentation: string = null
-
- const color = getColor(state, className)
- if (color !== null) {
- kind = 16
- if (typeof color !== 'string' && (color.alpha ?? 1) !== 0) {
- documentation = culori.formatRgb(color)
- }
- }
-
- return {
- label: className,
- kind,
- documentation,
- sortText: naturalExpand(index),
- data: [...subsetKey, className],
- textEdit: {
- newText: className,
- range: replacementRange,
- },
- }
- })
- )
- .filter((item) => {
- if (item === null) {
- return false
- }
- if (filter && !filter(item)) {
- return false
- }
- return true
- }),
- }
+ },
+ {
+ range: replacementRange,
+ data: {
+ ...(state.completionItemData ?? {}),
+ variants: subsetKey,
+ },
+ },
+ state.editor.capabilities.itemDefaults
+ )
}
async function provideClassAttributeCompletions(
@@ -569,7 +579,9 @@ return (
semver.gte(state.version, '2.0.0-alpha.1') || flagEnabled(state, 'applyComplexClasses')
)
}
- let validated = validateApply(state, item.data)
+ let variants = item.data?.variants ?? []
+ let className = item.data?.className ?? item.label
+ let validated = validateApply(state, [...variants, className])
return validated !== null && validated.isApplyable === true
}
)
@@ -673,66 +685,72 @@ },
end: position,
}
- return {
- isIncomplete: false,
- items: Object.keys(obj)
- .sort((a, z) => {
- let aIsNumber = isNumber(a)
- let zIsNumber = isNumber(z)
- if (aIsNumber && !zIsNumber) {
- return -1
- }
- if (!aIsNumber && zIsNumber) {
- return 1
- }
- if (aIsNumber && zIsNumber) {
- return parseFloat(a) - parseFloat(z)
- }
- return 0
- })
- .map((item, index) => {
- let color = getColorFromValue(obj[item])
- const replaceDot: boolean = item.indexOf('.') !== -1 && separator && separator.endsWith('.')
- const insertClosingBrace: boolean =
- text.charAt(text.length - 1) !== ']' &&
- (replaceDot || (separator && separator.endsWith('[')))
- const detail = stringifyConfigValue(obj[item])
+ return withDefaults(
+ {
+ isIncomplete: false,
+ items: Object.keys(obj)
+ .sort((a, z) => {
+ let aIsNumber = isNumber(a)
+ let zIsNumber = isNumber(z)
+ if (aIsNumber && !zIsNumber) {
+ return -1
+ }
+ if (!aIsNumber && zIsNumber) {
+ return 1
+ }
+ if (aIsNumber && zIsNumber) {
+ return parseFloat(a) - parseFloat(z)
+ }
+ return 0
+ })
+ .map((item, index, items) => {
+ let color = getColorFromValue(obj[item])
+ const replaceDot: boolean =
+ item.indexOf('.') !== -1 && separator && separator.endsWith('.')
+ const insertClosingBrace: boolean =
+ text.charAt(text.length - 1) !== ']' &&
+ (replaceDot || (separator && separator.endsWith('[')))
+ const detail = stringifyConfigValue(obj[item])
- return {
- label: item,
- sortText: naturalExpand(index),
- commitCharacters: [!item.includes('.') && '.', !item.includes('[') && '['].filter(
- Boolean
- ),
- kind: color ? 16 : isObject(obj[item]) ? 9 : 10,
- // VS Code bug causes some values to not display in some cases
- detail: detail === '0' || detail === 'transparent' ? `${detail} ` : detail,
- documentation:
- color && typeof color !== 'string' && (color.alpha ?? 1) !== 0
- ? culori.formatRgb(color)
- : null,
- textEdit: {
- newText: `${item}${insertClosingBrace ? ']' : ''}`,
- range: editRange,
- },
- additionalTextEdits: replaceDot
- ? [
- {
- newText: '[',
- range: {
- start: {
- ...editRange.start,
- character: editRange.start.character - 1,
+ return {
+ label: item,
+ sortText: naturalExpand(index, items.length),
+ commitCharacters: [!item.includes('.') && '.', !item.includes('[') && '['].filter(
+ Boolean
+ ),
+ kind: color ? 16 : isObject(obj[item]) ? 9 : 10,
+ // VS Code bug causes some values to not display in some cases
+ detail: detail === '0' || detail === 'transparent' ? `${detail} ` : detail,
+ ...(color && typeof color !== 'string' && (color.alpha ?? 1) !== 0
+ ? { documentation: culori.formatRgb(color) }
+ : {}),
+ ...(insertClosingBrace ? { textEditText: `${item}]` } : {}),
+ additionalTextEdits: replaceDot
+ ? [
+ {
+ newText: '[',
+ range: {
+ start: {
+ ...editRange.start,
+ character: editRange.start.character - 1,
+ },
+ end: editRange.start,
},
- end: editRange.start,
},
- },
- ]
- : [],
- data: 'helper',
- }
- }),
- }
+ ]
+ : [],
+ }
+ }),
+ },
+ {
+ range: editRange,
+ data: {
+ ...(state.completionItemData ?? {}),
+ _type: 'helper',
+ },
+ },
+ state.editor.capabilities.itemDefaults
+ )
}
function provideTailwindDirectiveCompletions(
@@ -753,87 +771,94 @@ const match = text.match(/^\s*@tailwind\s+(?<partial>[^\s]*)$/i)
if (match === null) return null
- return {
- isIncomplete: false,
- items: [
- semver.gte(state.version, '1.0.0-beta.1')
- ? {
- label: 'base',
- documentation: {
- kind: 'markdown' as typeof MarkupKind.Markdown,
- value: `This injects Tailwind’s base styles and any base styles registered by plugins.\n\n[Tailwind CSS Documentation](${docsUrl(
- state.version,
- 'functions-and-directives/#tailwind'
- )})`,
- },
- }
- : {
- label: 'preflight',
- documentation: {
- kind: 'markdown' as typeof MarkupKind.Markdown,
- value: `This injects Tailwind’s base styles, which is a combination of Normalize.css and some additional base styles.\n\n[Tailwind CSS Documentation](${docsUrl(
- state.version,
- 'functions-and-directives/#tailwind'
- )})`,
- },
+ let items = [
+ semver.gte(state.version, '1.0.0-beta.1')
+ ? {
+ label: 'base',
+ documentation: {
+ kind: 'markdown' as typeof MarkupKind.Markdown,
+ value: `This injects Tailwind’s base styles and any base styles registered by plugins.\n\n[Tailwind CSS Documentation](${docsUrl(
+ state.version,
+ 'functions-and-directives/#tailwind'
+ )})`,
+ },
+ }
+ : {
+ label: 'preflight',
+ documentation: {
+ kind: 'markdown' as typeof MarkupKind.Markdown,
+ value: `This injects Tailwind’s base styles, which is a combination of Normalize.css and some additional base styles.\n\n[Tailwind CSS Documentation](${docsUrl(
+ state.version,
+ 'functions-and-directives/#tailwind'
+ )})`,
},
- {
- label: 'components',
- documentation: {
- kind: 'markdown' as typeof MarkupKind.Markdown,
- value: `This injects Tailwind’s component classes and any component classes registered by plugins.\n\n[Tailwind CSS Documentation](${docsUrl(
- state.version,
- 'functions-and-directives/#tailwind'
- )})`,
},
+ {
+ label: 'components',
+ documentation: {
+ kind: 'markdown' as typeof MarkupKind.Markdown,
+ value: `This injects Tailwind’s component classes and any component classes registered by plugins.\n\n[Tailwind CSS Documentation](${docsUrl(
+ state.version,
+ 'functions-and-directives/#tailwind'
+ )})`,
},
- {
- label: 'utilities',
- documentation: {
- kind: 'markdown' as typeof MarkupKind.Markdown,
- value: `This injects Tailwind’s utility classes and any utility classes registered by plugins.\n\n[Tailwind CSS Documentation](${docsUrl(
- state.version,
- 'functions-and-directives/#tailwind'
- )})`,
- },
+ },
+ {
+ label: 'utilities',
+ documentation: {
+ kind: 'markdown' as typeof MarkupKind.Markdown,
+ value: `This injects Tailwind’s utility classes and any utility classes registered by plugins.\n\n[Tailwind CSS Documentation](${docsUrl(
+ state.version,
+ 'functions-and-directives/#tailwind'
+ )})`,
},
- state.jit && semver.gte(state.version, '2.1.99')
- ? {
- label: 'variants',
- documentation: {
- kind: 'markdown' as typeof MarkupKind.Markdown,
- value: `Use this directive to control where Tailwind injects the utility variants.\n\nThis directive is considered an advanced escape hatch and it is recommended to omit it whenever possible. If omitted, Tailwind will append these classes to the very end of your stylesheet by default.\n\n[Tailwind CSS Documentation](${docsUrl(
- state.version,
- 'just-in-time-mode#variants-are-inserted-at-tailwind-variants'
- )})`,
- },
- }
- : {
- label: 'screens',
- documentation: {
- kind: 'markdown' as typeof MarkupKind.Markdown,
- value: `Use this directive to control where Tailwind injects the responsive variations of each utility.\n\nIf omitted, Tailwind will append these classes to the very end of your stylesheet by default.\n\n[Tailwind CSS Documentation](${docsUrl(
- state.version,
- 'functions-and-directives/#tailwind'
- )})`,
- },
+ },
+ state.jit && semver.gte(state.version, '2.1.99')
+ ? {
+ label: 'variants',
+ documentation: {
+ kind: 'markdown' as typeof MarkupKind.Markdown,
+ value: `Use this directive to control where Tailwind injects the utility variants.\n\nThis directive is considered an advanced escape hatch and it is recommended to omit it whenever possible. If omitted, Tailwind will append these classes to the very end of your stylesheet by default.\n\n[Tailwind CSS Documentation](${docsUrl(
+ state.version,
+ 'just-in-time-mode#variants-are-inserted-at-tailwind-variants'
+ )})`,
},
- ].map((item) => ({
- ...item,
- kind: 21,
- data: '@tailwind',
- textEdit: {
- newText: item.label,
- range: {
- start: {
- line: position.line,
- character: position.character - match.groups.partial.length,
+ }
+ : {
+ label: 'screens',
+ documentation: {
+ kind: 'markdown' as typeof MarkupKind.Markdown,
+ value: `Use this directive to control where Tailwind injects the responsive variations of each utility.\n\nIf omitted, Tailwind will append these classes to the very end of your stylesheet by default.\n\n[Tailwind CSS Documentation](${docsUrl(
+ state.version,
+ 'functions-and-directives/#tailwind'
+ )})`,
},
- end: position,
},
+ ]
+
+ return withDefaults(
+ {
+ isIncomplete: false,
+ items: items.map((item) => ({
+ ...item,
+ kind: 21,
+ })),
+ },
+ {
+ data: {
+ ...(state.completionItemData ?? {}),
+ _type: '@tailwind',
},
- })),
- }
+ range: {
+ start: {
+ line: position.line,
+ character: position.character - match.groups.partial.length,
+ },
+ end: position,
+ },
+ },
+ state.editor.capabilities.itemDefaults
+ )
}
function provideVariantsDirectiveCompletions(
@@ -877,29 +902,33 @@ possibleVariants.unshift('responsive')
possibleVariants = possibleVariants.filter((v) => !state.screens.includes(v))
}
- return {
- isIncomplete: false,
- items: possibleVariants
- .filter((v) => existingVariants.indexOf(v) === -1)
- .map((variant, index) => ({
- // TODO: detail
- label: variant,
- detail: state.variants[variant],
- kind: 21,
- data: 'variant',
- sortText: naturalExpand(index),
- textEdit: {
- newText: variant,
- range: {
- start: {
- line: position.line,
- character: position.character - parts[parts.length - 1].length,
- },
- end: position,
- },
+ return withDefaults(
+ {
+ isIncomplete: false,
+ items: possibleVariants
+ .filter((v) => existingVariants.indexOf(v) === -1)
+ .map((variant, index, variants) => ({
+ // TODO: detail
+ label: variant,
+ kind: 21,
+ sortText: naturalExpand(index, variants.length),
+ })),
+ },
+ {
+ data: {
+ ...(state.completionItemData ?? {}),
+ _type: 'variant',
+ },
+ range: {
+ start: {
+ line: position.line,
+ character: position.character - parts[parts.length - 1].length,
},
- })),
- }
+ end: position,
+ },
+ },
+ state.editor.capabilities.itemDefaults
+ )
}
function provideLayerDirectiveCompletions(
@@ -920,24 +949,67 @@ const match = text.match(/^\s*@layer\s+(?<partial>[^\s]*)$/i)
if (match === null) return null
+ return withDefaults(
+ {
+ isIncomplete: false,
+ items: ['base', 'components', 'utilities'].map((layer, index, layers) => ({
+ label: layer,
+ kind: 21,
+ sortText: naturalExpand(index, layers.length),
+ })),
+ },
+ {
+ data: {
+ ...(state.completionItemData ?? {}),
+ _type: 'layer',
+ },
+ range: {
+ start: {
+ line: position.line,
+ character: position.character - match.groups.partial.length,
+ },
+ end: position,
+ },
+ },
+ state.editor.capabilities.itemDefaults
+ )
+}
+
+function withDefaults(
+ completionList: CompletionList,
+ defaults: Partial<{ data: any; range: Range }>,
+ supportedDefaults: string[]
+): CompletionList {
+ let defaultData = supportedDefaults.includes('data')
+ let defaultRange = supportedDefaults.includes('editRange')
+
return {
- isIncomplete: false,
- items: ['base', 'components', 'utilities'].map((layer, index) => ({
- label: layer,
- kind: 21,
- data: 'layer',
- sortText: naturalExpand(index),
- textEdit: {
- newText: layer,
- range: {
- start: {
- line: position.line,
- character: position.character - match.groups.partial.length,
+ ...completionList,
+ ...(defaultData || defaultRange
+ ? {
+ itemDefaults: {
+ ...(defaultData && defaults.data ? { data: defaults.data } : {}),
+ ...(defaultRange && defaults.range ? { editRange: defaults.range } : {}),
},
- end: position,
- },
- },
- })),
+ }
+ : {}),
+ items:
+ defaultData && defaultRange
+ ? completionList.items
+ : completionList.items.map(({ textEditText, ...item }) => ({
+ ...item,
+ ...(defaultData || !defaults.data || item.data ? {} : { data: defaults.data }),
+ ...(defaultRange || !defaults.range
+ ? textEditText
+ ? { textEditText }
+ : {}
+ : {
+ textEdit: {
+ newText: textEditText ?? item.label,
+ range: defaults.range,
+ },
+ }),
+ })),
}
}
@@ -963,25 +1035,30 @@ const screens = dlv(state.config, ['screens'], dlv(state.config, ['theme', 'screens'], {}))
if (!isObject(screens)) return null
- return {
- isIncomplete: false,
- items: Object.keys(screens).map((screen, index) => ({
- label: screen,
- kind: 21,
- data: 'screen',
- sortText: naturalExpand(index),
- textEdit: {
- newText: screen,
- range: {
- start: {
- line: position.line,
- character: position.character - match.groups.partial.length,
- },
- end: position,
+ return withDefaults(
+ {
+ isIncomplete: false,
+ items: Object.keys(screens).map((screen, index) => ({
+ label: screen,
+ kind: 21,
+ sortText: naturalExpand(index),
+ })),
+ },
+ {
+ data: {
+ ...(state.completionItemData ?? {}),
+ _type: 'screen',
+ },
+ range: {
+ start: {
+ line: position.line,
+ character: position.character - match.groups.partial.length,
},
+ end: position,
},
- })),
- }
+ },
+ state.editor.capabilities.itemDefaults
+ )
}
function provideCssDirectiveCompletions(
@@ -1089,24 +1166,29 @@ ]
: []),
]
- return {
- isIncomplete: false,
- items: items.map((item) => ({
- ...item,
- kind: 14,
- data: 'directive',
- textEdit: {
- newText: item.label,
- range: {
- start: {
- line: position.line,
- character: position.character - match.groups.partial.length - 1,
- },
- end: position,
+ return withDefaults(
+ {
+ isIncomplete: false,
+ items: items.map((item) => ({
+ ...item,
+ kind: 14,
+ })),
+ },
+ {
+ data: {
+ ...(state.completionItemData ?? {}),
+ _type: 'directive',
+ },
+ range: {
+ start: {
+ line: position.line,
+ character: position.character - match.groups.partial.length - 1,
},
+ end: position,
},
- })),
- }
+ },
+ state.editor.capabilities.itemDefaults
+ )
}
async function provideConfigDirectiveCompletions(
@@ -1131,28 +1213,34 @@ let partial = match.groups.partial.slice(1) // remove quote
let valueBeforeLastSlash = partial.substring(0, partial.lastIndexOf('/'))
let valueAfterLastSlash = partial.substring(partial.lastIndexOf('/') + 1)
- return {
- isIncomplete: false,
- items: (await state.editor.readDirectory(document, valueBeforeLastSlash || '.'))
- .filter(([name, type]) => type.isDirectory || /\.c?js$/.test(name))
- .map(([name, type]) => ({
- label: type.isDirectory ? name + '/' : name,
- kind: type.isDirectory ? 19 : 17,
- textEdit: {
- newText: type.isDirectory ? name + '/' : name,
- range: {
- start: {
- line: position.line,
- character: position.character - valueAfterLastSlash.length,
- },
- end: position,
- },
+ return withDefaults(
+ {
+ isIncomplete: false,
+ items: (await state.editor.readDirectory(document, valueBeforeLastSlash || '.'))
+ .filter(([name, type]) => type.isDirectory || /\.c?js$/.test(name))
+ .map(([name, type]) => ({
+ label: type.isDirectory ? name + '/' : name,
+ kind: type.isDirectory ? 19 : 17,
+ command: type.isDirectory
+ ? { command: 'editor.action.triggerSuggest', title: '' }
+ : undefined,
+ })),
+ },
+ {
+ data: {
+ ...(state.completionItemData ?? {}),
+ _type: 'filesystem',
+ },
+ range: {
+ start: {
+ line: position.line,
+ character: position.character - valueAfterLastSlash.length,
},
- command: type.isDirectory
- ? { command: 'editor.action.triggerSuggest', title: '' }
- : undefined,
- })),
- }
+ end: position,
+ },
+ },
+ state.editor.capabilities.itemDefaults
+ )
}
async function provideEmmetCompletions(
@@ -1255,25 +1343,31 @@ export async function resolveCompletionItem(
state: State,
item: CompletionItem
): Promise<CompletionItem> {
- if (['helper', 'directive', 'variant', 'layer', '@tailwind'].includes(item.data)) {
+ if (
+ ['helper', 'directive', 'variant', 'layer', '@tailwind', 'filesystem'].includes(
+ item.data?._type
+ )
+ ) {
return item
}
- if (item.data === 'screen') {
+ if (item.data?._type === 'screen') {
let screens = dlv(state.config, ['theme', 'screens'], dlv(state.config, ['screens'], {}))
if (!isObject(screens)) screens = {}
item.detail = stringifyScreen(screens[item.label] as Screen)
return item
}
- if (!Array.isArray(item.data)) {
- return item
+ let className = item.data?.className ?? item.label
+ if (item.data?.important) {
+ className = `!${className}`
}
+ let variants = item.data?.variants ?? []
if (state.jit) {
if (item.kind === 9) return item
if (item.detail && item.documentation) return item
- let { root, rules } = jit.generateRules(state, [item.data.join(state.separator)])
+ let { root, rules } = jit.generateRules(state, [[...variants, className].join(state.separator)])
if (rules.length === 0) return item
if (!item.detail) {
if (rules.length === 1) {
@@ -1291,14 +1385,14 @@ }
return item
}
- const className = dlv(state.classNames.classNames, [...item.data, '__info'])
+ const rules = dlv(state.classNames.classNames, [...variants, className, '__info'])
if (item.kind === 9) {
- item.detail = state.classNames.context[item.data[item.data.length - 1]].join(', ')
+ item.detail = state.classNames.context[className].join(', ')
} else {
- item.detail = await getCssDetail(state, className)
+ item.detail = await getCssDetail(state, rules)
if (!item.documentation) {
const settings = await state.editor.getConfiguration()
- const css = stringifyCss(item.data.join(':'), className, settings)
+ const css = stringifyCss([...variants, className].join(':'), rules, settings)
if (css) {
item.documentation = {
kind: 'markdown' as typeof MarkupKind.Markdown,