Use `itemDefaults` to reduce size of completion lists (#706)
* Use completion list `itemDefaults` * more defaultsmaster
parent
7235aeab48
commit
637f838725
|
@ -378,8 +378,21 @@ async function createProjectService(
|
||||||
const disposables: Array<Disposable | Promise<Disposable>> = []
|
const disposables: Array<Disposable | Promise<Disposable>> = []
|
||||||
let documentSelector = projectConfig.documentSelector
|
let documentSelector = projectConfig.documentSelector
|
||||||
|
|
||||||
|
let itemDefaults =
|
||||||
|
params.capabilities.textDocument?.completion?.completionList?.itemDefaults ?? []
|
||||||
|
|
||||||
|
// VS Code _does_ support `itemDefaults.data` since at least 1.67.0 (this extension's min version)
|
||||||
|
// but it doesn't advertise it in its capabilities. So we manually add it here.
|
||||||
|
// See also: https://github.com/microsoft/vscode-languageserver-node/issues/1181
|
||||||
|
if (params.clientInfo?.name === 'Visual Studio Code' && !itemDefaults.includes('data')) {
|
||||||
|
itemDefaults.push('data')
|
||||||
|
}
|
||||||
|
|
||||||
let state: State = {
|
let state: State = {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
|
completionItemData: {
|
||||||
|
_projectKey: projectKey,
|
||||||
|
},
|
||||||
editor: {
|
editor: {
|
||||||
connection,
|
connection,
|
||||||
folder,
|
folder,
|
||||||
|
@ -390,6 +403,7 @@ async function createProjectService(
|
||||||
capabilities: {
|
capabilities: {
|
||||||
configuration: true,
|
configuration: true,
|
||||||
diagnosticRelatedInformation: true,
|
diagnosticRelatedInformation: true,
|
||||||
|
itemDefaults,
|
||||||
},
|
},
|
||||||
documents: documentService.documents,
|
documents: documentService.documents,
|
||||||
getConfiguration,
|
getConfiguration,
|
||||||
|
@ -1114,21 +1128,13 @@ async function createProjectService(
|
||||||
let settings = await state.editor.getConfiguration(document.uri)
|
let settings = await state.editor.getConfiguration(document.uri)
|
||||||
if (!settings.tailwindCSS.suggestions) return null
|
if (!settings.tailwindCSS.suggestions) return null
|
||||||
if (await isExcluded(state, document)) return null
|
if (await isExcluded(state, document)) return null
|
||||||
let result = await doComplete(state, document, params.position, params.context)
|
return doComplete(state, document, params.position, params.context)
|
||||||
if (!result) return result
|
|
||||||
return {
|
|
||||||
isIncomplete: result.isIncomplete,
|
|
||||||
items: result.items.map((item) => ({
|
|
||||||
...item,
|
|
||||||
data: { projectKey, originalData: item.data },
|
|
||||||
})),
|
|
||||||
}
|
|
||||||
}, null)
|
}, null)
|
||||||
},
|
},
|
||||||
onCompletionResolve(item: CompletionItem): Promise<CompletionItem> {
|
onCompletionResolve(item: CompletionItem): Promise<CompletionItem> {
|
||||||
return withFallback(() => {
|
return withFallback(() => {
|
||||||
if (!state.enabled) return null
|
if (!state.enabled) return null
|
||||||
return resolveCompletionItem(state, { ...item, data: item.data?.originalData })
|
return resolveCompletionItem(state, item)
|
||||||
}, null)
|
}, null)
|
||||||
},
|
},
|
||||||
async onCodeAction(params: CodeActionParams): Promise<CodeAction[]> {
|
async onCodeAction(params: CodeActionParams): Promise<CodeAction[]> {
|
||||||
|
@ -2162,7 +2168,7 @@ class TW {
|
||||||
}
|
}
|
||||||
|
|
||||||
async onCompletionResolve(item: CompletionItem): Promise<CompletionItem> {
|
async onCompletionResolve(item: CompletionItem): Promise<CompletionItem> {
|
||||||
return this.projects.get(item.data.projectKey)?.onCompletionResolve(item) ?? null
|
return this.projects.get(item.data?._projectKey)?.onCompletionResolve(item) ?? null
|
||||||
}
|
}
|
||||||
|
|
||||||
onCodeAction(params: CodeActionParams): Promise<CodeAction[]> {
|
onCodeAction(params: CodeActionParams): Promise<CodeAction[]> {
|
||||||
|
|
|
@ -97,12 +97,13 @@ export function completionsFromClassList(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (modifiers) {
|
if (modifiers) {
|
||||||
return {
|
return withDefaults(
|
||||||
|
{
|
||||||
isIncomplete: false,
|
isIncomplete: false,
|
||||||
items: modifiers.map((modifier, index) => {
|
items: modifiers.map((modifier, index) => {
|
||||||
let className = `${beforeSlash}/${modifier}`
|
let className = `${beforeSlash}/${modifier}`
|
||||||
let kind: CompletionItemKind = 21
|
let kind: CompletionItemKind = 21
|
||||||
let documentation: string = null
|
let documentation: string | undefined
|
||||||
|
|
||||||
const color = getColor(state, className)
|
const color = getColor(state, className)
|
||||||
if (color !== null) {
|
if (color !== null) {
|
||||||
|
@ -114,17 +115,18 @@ export function completionsFromClassList(
|
||||||
|
|
||||||
return {
|
return {
|
||||||
label: className,
|
label: className,
|
||||||
documentation,
|
...(documentation ? { documentation } : {}),
|
||||||
kind,
|
kind,
|
||||||
sortText: naturalExpand(index),
|
sortText: naturalExpand(index),
|
||||||
data: [className],
|
|
||||||
textEdit: {
|
|
||||||
newText: className,
|
|
||||||
range: replacementRange,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
range: replacementRange,
|
||||||
|
data: state.completionItemData,
|
||||||
|
},
|
||||||
|
state.editor.capabilities.itemDefaults
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,13 +143,14 @@ export function completionsFromClassList(
|
||||||
let variantOrder = 0
|
let variantOrder = 0
|
||||||
|
|
||||||
function variantItem(
|
function variantItem(
|
||||||
item: Omit<CompletionItem, 'kind' | 'data' | 'sortText' | 'textEdit'> & {
|
item: Omit<CompletionItem, 'kind' | 'data' | 'command' | 'sortText' | 'textEdit'>
|
||||||
textEdit?: { newText: string; range?: Range }
|
|
||||||
}
|
|
||||||
): CompletionItem {
|
): CompletionItem {
|
||||||
return {
|
return {
|
||||||
kind: 9,
|
kind: 9,
|
||||||
data: 'variant',
|
data: {
|
||||||
|
...(state.completionItemData ?? {}),
|
||||||
|
_type: 'variant',
|
||||||
|
},
|
||||||
command:
|
command:
|
||||||
item.insertTextFormat === 2 // Snippet
|
item.insertTextFormat === 2 // Snippet
|
||||||
? undefined
|
? undefined
|
||||||
|
@ -157,11 +160,6 @@ export function completionsFromClassList(
|
||||||
},
|
},
|
||||||
sortText: '-' + naturalExpand(variantOrder++),
|
sortText: '-' + naturalExpand(variantOrder++),
|
||||||
...item,
|
...item,
|
||||||
textEdit: {
|
|
||||||
newText: item.label,
|
|
||||||
range: replacementRange,
|
|
||||||
...item.textEdit,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -174,9 +172,7 @@ export function completionsFromClassList(
|
||||||
variantItem({
|
variantItem({
|
||||||
label: `${variant.name}${variant.hasDash ? '-' : ''}[]${sep}`,
|
label: `${variant.name}${variant.hasDash ? '-' : ''}[]${sep}`,
|
||||||
insertTextFormat: 2,
|
insertTextFormat: 2,
|
||||||
textEdit: {
|
textEditText: `${variant.name}${variant.hasDash ? '-' : ''}[\${1}]${sep}\${0}`,
|
||||||
newText: `${variant.name}${variant.hasDash ? '-' : ''}[\${1}]${sep}\${0}`,
|
|
||||||
},
|
|
||||||
// command: {
|
// command: {
|
||||||
// title: '',
|
// title: '',
|
||||||
// command: 'tailwindCSS.onInsertArbitraryVariantSnippet',
|
// command: 'tailwindCSS.onInsertArbitraryVariantSnippet',
|
||||||
|
@ -199,9 +195,7 @@ export function completionsFromClassList(
|
||||||
variantItem({
|
variantItem({
|
||||||
label: `${variant.name}${sep}`,
|
label: `${variant.name}${sep}`,
|
||||||
detail: variant.selectors().join(', '),
|
detail: variant.selectors().join(', '),
|
||||||
textEdit: {
|
textEditText: resultingVariants[resultingVariants.length - 1] + sep,
|
||||||
newText: resultingVariants[resultingVariants.length - 1] + sep,
|
|
||||||
},
|
|
||||||
additionalTextEdits:
|
additionalTextEdits:
|
||||||
shouldSortVariants && resultingVariants.length > 1
|
shouldSortVariants && resultingVariants.length > 1
|
||||||
? [
|
? [
|
||||||
|
@ -248,12 +242,13 @@ export function completionsFromClassList(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.classList) {
|
if (state.classList) {
|
||||||
return {
|
return withDefaults(
|
||||||
|
{
|
||||||
isIncomplete: false,
|
isIncomplete: false,
|
||||||
items: items.concat(
|
items: items.concat(
|
||||||
state.classList.map(([className, { color }], index) => {
|
state.classList.map(([className, { color }], index) => {
|
||||||
let kind: CompletionItemKind = color ? 16 : 21
|
let kind: CompletionItemKind = color ? 16 : 21
|
||||||
let documentation = null
|
let documentation: string | undefined
|
||||||
|
|
||||||
if (color && typeof color !== 'string') {
|
if (color && typeof color !== 'string') {
|
||||||
documentation = culori.formatRgb(color)
|
documentation = culori.formatRgb(color)
|
||||||
|
@ -262,20 +257,26 @@ export function completionsFromClassList(
|
||||||
return {
|
return {
|
||||||
label: className,
|
label: className,
|
||||||
kind,
|
kind,
|
||||||
documentation,
|
...(documentation ? { documentation } : {}),
|
||||||
sortText: naturalExpand(index),
|
sortText: naturalExpand(index, state.classList.length),
|
||||||
data: [...existingVariants, important ? `!${className}` : className],
|
|
||||||
textEdit: {
|
|
||||||
newText: className,
|
|
||||||
range: replacementRange,
|
|
||||||
},
|
|
||||||
} as CompletionItem
|
} as CompletionItem
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
...(state.completionItemData ?? {}),
|
||||||
|
...(important ? { important } : {}),
|
||||||
|
variants: existingVariants,
|
||||||
|
},
|
||||||
|
range: replacementRange,
|
||||||
|
},
|
||||||
|
state.editor.capabilities.itemDefaults
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return withDefaults(
|
||||||
|
{
|
||||||
isIncomplete: false,
|
isIncomplete: false,
|
||||||
items: items
|
items: items
|
||||||
.concat(
|
.concat(
|
||||||
|
@ -287,9 +288,9 @@ export function completionsFromClassList(
|
||||||
}
|
}
|
||||||
return item.__info && isUtil(item)
|
return item.__info && isUtil(item)
|
||||||
})
|
})
|
||||||
.map((className, index) => {
|
.map((className, index, classNames) => {
|
||||||
let kind: CompletionItemKind = 21
|
let kind: CompletionItemKind = 21
|
||||||
let documentation: string = null
|
let documentation: string | undefined
|
||||||
|
|
||||||
const color = getColor(state, className)
|
const color = getColor(state, className)
|
||||||
if (color !== null) {
|
if (color !== null) {
|
||||||
|
@ -302,13 +303,8 @@ export function completionsFromClassList(
|
||||||
return {
|
return {
|
||||||
label: className,
|
label: className,
|
||||||
kind,
|
kind,
|
||||||
documentation,
|
...(documentation ? { documentation } : {}),
|
||||||
sortText: naturalExpand(index),
|
sortText: naturalExpand(index, classNames.length),
|
||||||
data: [...existingVariants, important ? `!${className}` : className],
|
|
||||||
textEdit: {
|
|
||||||
newText: className,
|
|
||||||
range: replacementRange,
|
|
||||||
},
|
|
||||||
} as CompletionItem
|
} as CompletionItem
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
@ -321,7 +317,17 @@ export function completionsFromClassList(
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}),
|
}),
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
range: replacementRange,
|
||||||
|
data: {
|
||||||
|
...(state.completionItemData ?? {}),
|
||||||
|
variants: existingVariants,
|
||||||
|
...(important ? { important } : {}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
state.editor.capabilities.itemDefaults
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = parts.length - 1; i > 0; i--) {
|
for (let i = parts.length - 1; i > 0; i--) {
|
||||||
|
@ -341,25 +347,25 @@ export function completionsFromClassList(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return withDefaults(
|
||||||
|
{
|
||||||
isIncomplete: false,
|
isIncomplete: false,
|
||||||
items: Object.keys(isSubset ? subset : state.classNames.classNames)
|
items: Object.keys(isSubset ? subset : state.classNames.classNames)
|
||||||
.filter((k) => k !== '__info')
|
.filter((k) => k !== '__info')
|
||||||
.filter((className) => isContextItem(state, [...subsetKey, className]))
|
.filter((className) => isContextItem(state, [...subsetKey, className]))
|
||||||
.map((className, index): CompletionItem => {
|
.map((className, index, classNames): CompletionItem => {
|
||||||
return {
|
return {
|
||||||
label: className + sep,
|
label: className + sep,
|
||||||
kind: 9,
|
kind: 9,
|
||||||
documentation: null,
|
|
||||||
command: {
|
command: {
|
||||||
title: '',
|
title: '',
|
||||||
command: 'editor.action.triggerSuggest',
|
command: 'editor.action.triggerSuggest',
|
||||||
},
|
},
|
||||||
sortText: '-' + naturalExpand(index),
|
sortText: '-' + naturalExpand(index, classNames.length),
|
||||||
data: [...subsetKey, className],
|
data: {
|
||||||
textEdit: {
|
...(state.completionItemData ?? {}),
|
||||||
newText: className + sep,
|
className,
|
||||||
range: replacementRange,
|
variants: subsetKey,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -368,9 +374,9 @@ export function completionsFromClassList(
|
||||||
.filter((className) =>
|
.filter((className) =>
|
||||||
dlv(state.classNames.classNames, [...subsetKey, className, '__info'])
|
dlv(state.classNames.classNames, [...subsetKey, className, '__info'])
|
||||||
)
|
)
|
||||||
.map((className, index) => {
|
.map((className, index, classNames) => {
|
||||||
let kind: CompletionItemKind = 21
|
let kind: CompletionItemKind = 21
|
||||||
let documentation: string = null
|
let documentation: string | undefined
|
||||||
|
|
||||||
const color = getColor(state, className)
|
const color = getColor(state, className)
|
||||||
if (color !== null) {
|
if (color !== null) {
|
||||||
|
@ -383,13 +389,8 @@ export function completionsFromClassList(
|
||||||
return {
|
return {
|
||||||
label: className,
|
label: className,
|
||||||
kind,
|
kind,
|
||||||
documentation,
|
...(documentation ? { documentation } : {}),
|
||||||
sortText: naturalExpand(index),
|
sortText: naturalExpand(index, classNames.length),
|
||||||
data: [...subsetKey, className],
|
|
||||||
textEdit: {
|
|
||||||
newText: className,
|
|
||||||
range: replacementRange,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
@ -402,7 +403,16 @@ export function completionsFromClassList(
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}),
|
}),
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
range: replacementRange,
|
||||||
|
data: {
|
||||||
|
...(state.completionItemData ?? {}),
|
||||||
|
variants: subsetKey,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
state.editor.capabilities.itemDefaults
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function provideClassAttributeCompletions(
|
async function provideClassAttributeCompletions(
|
||||||
|
@ -569,7 +579,9 @@ function provideAtApplyCompletions(
|
||||||
semver.gte(state.version, '2.0.0-alpha.1') || flagEnabled(state, 'applyComplexClasses')
|
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
|
return validated !== null && validated.isApplyable === true
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -673,7 +685,8 @@ function provideCssHelperCompletions(
|
||||||
end: position,
|
end: position,
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return withDefaults(
|
||||||
|
{
|
||||||
isIncomplete: false,
|
isIncomplete: false,
|
||||||
items: Object.keys(obj)
|
items: Object.keys(obj)
|
||||||
.sort((a, z) => {
|
.sort((a, z) => {
|
||||||
|
@ -690,9 +703,10 @@ function provideCssHelperCompletions(
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
})
|
})
|
||||||
.map((item, index) => {
|
.map((item, index, items) => {
|
||||||
let color = getColorFromValue(obj[item])
|
let color = getColorFromValue(obj[item])
|
||||||
const replaceDot: boolean = item.indexOf('.') !== -1 && separator && separator.endsWith('.')
|
const replaceDot: boolean =
|
||||||
|
item.indexOf('.') !== -1 && separator && separator.endsWith('.')
|
||||||
const insertClosingBrace: boolean =
|
const insertClosingBrace: boolean =
|
||||||
text.charAt(text.length - 1) !== ']' &&
|
text.charAt(text.length - 1) !== ']' &&
|
||||||
(replaceDot || (separator && separator.endsWith('[')))
|
(replaceDot || (separator && separator.endsWith('[')))
|
||||||
|
@ -700,21 +714,17 @@ function provideCssHelperCompletions(
|
||||||
|
|
||||||
return {
|
return {
|
||||||
label: item,
|
label: item,
|
||||||
sortText: naturalExpand(index),
|
sortText: naturalExpand(index, items.length),
|
||||||
commitCharacters: [!item.includes('.') && '.', !item.includes('[') && '['].filter(
|
commitCharacters: [!item.includes('.') && '.', !item.includes('[') && '['].filter(
|
||||||
Boolean
|
Boolean
|
||||||
),
|
),
|
||||||
kind: color ? 16 : isObject(obj[item]) ? 9 : 10,
|
kind: color ? 16 : isObject(obj[item]) ? 9 : 10,
|
||||||
// VS Code bug causes some values to not display in some cases
|
// VS Code bug causes some values to not display in some cases
|
||||||
detail: detail === '0' || detail === 'transparent' ? `${detail} ` : detail,
|
detail: detail === '0' || detail === 'transparent' ? `${detail} ` : detail,
|
||||||
documentation:
|
...(color && typeof color !== 'string' && (color.alpha ?? 1) !== 0
|
||||||
color && typeof color !== 'string' && (color.alpha ?? 1) !== 0
|
? { documentation: culori.formatRgb(color) }
|
||||||
? culori.formatRgb(color)
|
: {}),
|
||||||
: null,
|
...(insertClosingBrace ? { textEditText: `${item}]` } : {}),
|
||||||
textEdit: {
|
|
||||||
newText: `${item}${insertClosingBrace ? ']' : ''}`,
|
|
||||||
range: editRange,
|
|
||||||
},
|
|
||||||
additionalTextEdits: replaceDot
|
additionalTextEdits: replaceDot
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
|
@ -729,10 +739,18 @@ function provideCssHelperCompletions(
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
: [],
|
: [],
|
||||||
data: 'helper',
|
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
range: editRange,
|
||||||
|
data: {
|
||||||
|
...(state.completionItemData ?? {}),
|
||||||
|
_type: 'helper',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
state.editor.capabilities.itemDefaults
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function provideTailwindDirectiveCompletions(
|
function provideTailwindDirectiveCompletions(
|
||||||
|
@ -753,9 +771,7 @@ function provideTailwindDirectiveCompletions(
|
||||||
|
|
||||||
if (match === null) return null
|
if (match === null) return null
|
||||||
|
|
||||||
return {
|
let items = [
|
||||||
isIncomplete: false,
|
|
||||||
items: [
|
|
||||||
semver.gte(state.version, '1.0.0-beta.1')
|
semver.gte(state.version, '1.0.0-beta.1')
|
||||||
? {
|
? {
|
||||||
label: 'base',
|
label: 'base',
|
||||||
|
@ -818,12 +834,21 @@ function provideTailwindDirectiveCompletions(
|
||||||
)})`,
|
)})`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
].map((item) => ({
|
]
|
||||||
|
|
||||||
|
return withDefaults(
|
||||||
|
{
|
||||||
|
isIncomplete: false,
|
||||||
|
items: items.map((item) => ({
|
||||||
...item,
|
...item,
|
||||||
kind: 21,
|
kind: 21,
|
||||||
data: '@tailwind',
|
})),
|
||||||
textEdit: {
|
},
|
||||||
newText: item.label,
|
{
|
||||||
|
data: {
|
||||||
|
...(state.completionItemData ?? {}),
|
||||||
|
_type: '@tailwind',
|
||||||
|
},
|
||||||
range: {
|
range: {
|
||||||
start: {
|
start: {
|
||||||
line: position.line,
|
line: position.line,
|
||||||
|
@ -832,8 +857,8 @@ function provideTailwindDirectiveCompletions(
|
||||||
end: position,
|
end: position,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})),
|
state.editor.capabilities.itemDefaults
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function provideVariantsDirectiveCompletions(
|
function provideVariantsDirectiveCompletions(
|
||||||
|
@ -877,19 +902,23 @@ function provideVariantsDirectiveCompletions(
|
||||||
possibleVariants = possibleVariants.filter((v) => !state.screens.includes(v))
|
possibleVariants = possibleVariants.filter((v) => !state.screens.includes(v))
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return withDefaults(
|
||||||
|
{
|
||||||
isIncomplete: false,
|
isIncomplete: false,
|
||||||
items: possibleVariants
|
items: possibleVariants
|
||||||
.filter((v) => existingVariants.indexOf(v) === -1)
|
.filter((v) => existingVariants.indexOf(v) === -1)
|
||||||
.map((variant, index) => ({
|
.map((variant, index, variants) => ({
|
||||||
// TODO: detail
|
// TODO: detail
|
||||||
label: variant,
|
label: variant,
|
||||||
detail: state.variants[variant],
|
|
||||||
kind: 21,
|
kind: 21,
|
||||||
data: 'variant',
|
sortText: naturalExpand(index, variants.length),
|
||||||
sortText: naturalExpand(index),
|
})),
|
||||||
textEdit: {
|
},
|
||||||
newText: variant,
|
{
|
||||||
|
data: {
|
||||||
|
...(state.completionItemData ?? {}),
|
||||||
|
_type: 'variant',
|
||||||
|
},
|
||||||
range: {
|
range: {
|
||||||
start: {
|
start: {
|
||||||
line: position.line,
|
line: position.line,
|
||||||
|
@ -898,8 +927,8 @@ function provideVariantsDirectiveCompletions(
|
||||||
end: position,
|
end: position,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})),
|
state.editor.capabilities.itemDefaults
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function provideLayerDirectiveCompletions(
|
function provideLayerDirectiveCompletions(
|
||||||
|
@ -920,15 +949,20 @@ function provideLayerDirectiveCompletions(
|
||||||
|
|
||||||
if (match === null) return null
|
if (match === null) return null
|
||||||
|
|
||||||
return {
|
return withDefaults(
|
||||||
|
{
|
||||||
isIncomplete: false,
|
isIncomplete: false,
|
||||||
items: ['base', 'components', 'utilities'].map((layer, index) => ({
|
items: ['base', 'components', 'utilities'].map((layer, index, layers) => ({
|
||||||
label: layer,
|
label: layer,
|
||||||
kind: 21,
|
kind: 21,
|
||||||
data: 'layer',
|
sortText: naturalExpand(index, layers.length),
|
||||||
sortText: naturalExpand(index),
|
})),
|
||||||
textEdit: {
|
},
|
||||||
newText: layer,
|
{
|
||||||
|
data: {
|
||||||
|
...(state.completionItemData ?? {}),
|
||||||
|
_type: 'layer',
|
||||||
|
},
|
||||||
range: {
|
range: {
|
||||||
start: {
|
start: {
|
||||||
line: position.line,
|
line: position.line,
|
||||||
|
@ -937,6 +971,44 @@ function provideLayerDirectiveCompletions(
|
||||||
end: position,
|
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 {
|
||||||
|
...completionList,
|
||||||
|
...(defaultData || defaultRange
|
||||||
|
? {
|
||||||
|
itemDefaults: {
|
||||||
|
...(defaultData && defaults.data ? { data: defaults.data } : {}),
|
||||||
|
...(defaultRange && defaults.range ? { editRange: defaults.range } : {}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
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,15 +1035,20 @@ function provideScreenDirectiveCompletions(
|
||||||
|
|
||||||
if (!isObject(screens)) return null
|
if (!isObject(screens)) return null
|
||||||
|
|
||||||
return {
|
return withDefaults(
|
||||||
|
{
|
||||||
isIncomplete: false,
|
isIncomplete: false,
|
||||||
items: Object.keys(screens).map((screen, index) => ({
|
items: Object.keys(screens).map((screen, index) => ({
|
||||||
label: screen,
|
label: screen,
|
||||||
kind: 21,
|
kind: 21,
|
||||||
data: 'screen',
|
|
||||||
sortText: naturalExpand(index),
|
sortText: naturalExpand(index),
|
||||||
textEdit: {
|
})),
|
||||||
newText: screen,
|
},
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
...(state.completionItemData ?? {}),
|
||||||
|
_type: 'screen',
|
||||||
|
},
|
||||||
range: {
|
range: {
|
||||||
start: {
|
start: {
|
||||||
line: position.line,
|
line: position.line,
|
||||||
|
@ -980,8 +1057,8 @@ function provideScreenDirectiveCompletions(
|
||||||
end: position,
|
end: position,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})),
|
state.editor.capabilities.itemDefaults
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function provideCssDirectiveCompletions(
|
function provideCssDirectiveCompletions(
|
||||||
|
@ -1089,14 +1166,19 @@ function provideCssDirectiveCompletions(
|
||||||
: []),
|
: []),
|
||||||
]
|
]
|
||||||
|
|
||||||
return {
|
return withDefaults(
|
||||||
|
{
|
||||||
isIncomplete: false,
|
isIncomplete: false,
|
||||||
items: items.map((item) => ({
|
items: items.map((item) => ({
|
||||||
...item,
|
...item,
|
||||||
kind: 14,
|
kind: 14,
|
||||||
data: 'directive',
|
})),
|
||||||
textEdit: {
|
},
|
||||||
newText: item.label,
|
{
|
||||||
|
data: {
|
||||||
|
...(state.completionItemData ?? {}),
|
||||||
|
_type: 'directive',
|
||||||
|
},
|
||||||
range: {
|
range: {
|
||||||
start: {
|
start: {
|
||||||
line: position.line,
|
line: position.line,
|
||||||
|
@ -1105,8 +1187,8 @@ function provideCssDirectiveCompletions(
|
||||||
end: position,
|
end: position,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})),
|
state.editor.capabilities.itemDefaults
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function provideConfigDirectiveCompletions(
|
async function provideConfigDirectiveCompletions(
|
||||||
|
@ -1131,15 +1213,24 @@ async function provideConfigDirectiveCompletions(
|
||||||
let valueBeforeLastSlash = partial.substring(0, partial.lastIndexOf('/'))
|
let valueBeforeLastSlash = partial.substring(0, partial.lastIndexOf('/'))
|
||||||
let valueAfterLastSlash = partial.substring(partial.lastIndexOf('/') + 1)
|
let valueAfterLastSlash = partial.substring(partial.lastIndexOf('/') + 1)
|
||||||
|
|
||||||
return {
|
return withDefaults(
|
||||||
|
{
|
||||||
isIncomplete: false,
|
isIncomplete: false,
|
||||||
items: (await state.editor.readDirectory(document, valueBeforeLastSlash || '.'))
|
items: (await state.editor.readDirectory(document, valueBeforeLastSlash || '.'))
|
||||||
.filter(([name, type]) => type.isDirectory || /\.c?js$/.test(name))
|
.filter(([name, type]) => type.isDirectory || /\.c?js$/.test(name))
|
||||||
.map(([name, type]) => ({
|
.map(([name, type]) => ({
|
||||||
label: type.isDirectory ? name + '/' : name,
|
label: type.isDirectory ? name + '/' : name,
|
||||||
kind: type.isDirectory ? 19 : 17,
|
kind: type.isDirectory ? 19 : 17,
|
||||||
textEdit: {
|
command: type.isDirectory
|
||||||
newText: type.isDirectory ? name + '/' : name,
|
? { command: 'editor.action.triggerSuggest', title: '' }
|
||||||
|
: undefined,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
...(state.completionItemData ?? {}),
|
||||||
|
_type: 'filesystem',
|
||||||
|
},
|
||||||
range: {
|
range: {
|
||||||
start: {
|
start: {
|
||||||
line: position.line,
|
line: position.line,
|
||||||
|
@ -1148,11 +1239,8 @@ async function provideConfigDirectiveCompletions(
|
||||||
end: position,
|
end: position,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
command: type.isDirectory
|
state.editor.capabilities.itemDefaults
|
||||||
? { command: 'editor.action.triggerSuggest', title: '' }
|
)
|
||||||
: undefined,
|
|
||||||
})),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function provideEmmetCompletions(
|
async function provideEmmetCompletions(
|
||||||
|
@ -1255,25 +1343,31 @@ export async function resolveCompletionItem(
|
||||||
state: State,
|
state: State,
|
||||||
item: CompletionItem
|
item: CompletionItem
|
||||||
): Promise<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
|
return item
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.data === 'screen') {
|
if (item.data?._type === 'screen') {
|
||||||
let screens = dlv(state.config, ['theme', 'screens'], dlv(state.config, ['screens'], {}))
|
let screens = dlv(state.config, ['theme', 'screens'], dlv(state.config, ['screens'], {}))
|
||||||
if (!isObject(screens)) screens = {}
|
if (!isObject(screens)) screens = {}
|
||||||
item.detail = stringifyScreen(screens[item.label] as Screen)
|
item.detail = stringifyScreen(screens[item.label] as Screen)
|
||||||
return item
|
return item
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Array.isArray(item.data)) {
|
let className = item.data?.className ?? item.label
|
||||||
return item
|
if (item.data?.important) {
|
||||||
|
className = `!${className}`
|
||||||
}
|
}
|
||||||
|
let variants = item.data?.variants ?? []
|
||||||
|
|
||||||
if (state.jit) {
|
if (state.jit) {
|
||||||
if (item.kind === 9) return item
|
if (item.kind === 9) return item
|
||||||
if (item.detail && item.documentation) 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 (rules.length === 0) return item
|
||||||
if (!item.detail) {
|
if (!item.detail) {
|
||||||
if (rules.length === 1) {
|
if (rules.length === 1) {
|
||||||
|
@ -1291,14 +1385,14 @@ export async function resolveCompletionItem(
|
||||||
return item
|
return item
|
||||||
}
|
}
|
||||||
|
|
||||||
const className = dlv(state.classNames.classNames, [...item.data, '__info'])
|
const rules = dlv(state.classNames.classNames, [...variants, className, '__info'])
|
||||||
if (item.kind === 9) {
|
if (item.kind === 9) {
|
||||||
item.detail = state.classNames.context[item.data[item.data.length - 1]].join(', ')
|
item.detail = state.classNames.context[className].join(', ')
|
||||||
} else {
|
} else {
|
||||||
item.detail = await getCssDetail(state, className)
|
item.detail = await getCssDetail(state, rules)
|
||||||
if (!item.documentation) {
|
if (!item.documentation) {
|
||||||
const settings = await state.editor.getConfiguration()
|
const settings = await state.editor.getConfiguration()
|
||||||
const css = stringifyCss(item.data.join(':'), className, settings)
|
const css = stringifyCss([...variants, className].join(':'), rules, settings)
|
||||||
if (css) {
|
if (css) {
|
||||||
item.documentation = {
|
item.documentation = {
|
||||||
kind: 'markdown' as typeof MarkupKind.Markdown,
|
kind: 'markdown' as typeof MarkupKind.Markdown,
|
||||||
|
|
|
@ -1,8 +1,4 @@
|
||||||
function pad(n: string): string {
|
export function naturalExpand(value: number, total?: number): string {
|
||||||
return ('00000000' + n).substr(-8)
|
let length = typeof total === 'number' ? total.toString().length : 8
|
||||||
}
|
return ('0'.repeat(length) + value).slice(-length)
|
||||||
|
|
||||||
export function naturalExpand(value: number | string): string {
|
|
||||||
let str = typeof value === 'string' ? value : value.toString()
|
|
||||||
return str.replace(/\d+/g, pad)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ export type EditorState = {
|
||||||
capabilities: {
|
capabilities: {
|
||||||
configuration: boolean
|
configuration: boolean
|
||||||
diagnosticRelatedInformation: boolean
|
diagnosticRelatedInformation: boolean
|
||||||
|
itemDefaults: string[]
|
||||||
}
|
}
|
||||||
getConfiguration: (uri?: string) => Promise<Settings>
|
getConfiguration: (uri?: string) => Promise<Settings>
|
||||||
getDocumentSymbols: (uri: string) => Promise<SymbolInformation[]>
|
getDocumentSymbols: (uri: string) => Promise<SymbolInformation[]>
|
||||||
|
@ -118,6 +119,7 @@ export interface State {
|
||||||
jitContext?: any
|
jitContext?: any
|
||||||
classList?: Array<[string, { color: culori.Color | KeywordColor | null; modifiers?: string[] }]>
|
classList?: Array<[string, { color: culori.Color | KeywordColor | null; modifiers?: string[] }]>
|
||||||
pluginVersions?: string
|
pluginVersions?: string
|
||||||
|
completionItemData?: Record<string, any>
|
||||||
// postcssPlugins?: { before: any[]; after: any[] }
|
// postcssPlugins?: { before: any[]; after: any[] }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
"vscode"
|
"vscode"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"vscode": "^1.65.0"
|
"vscode": "^1.67.0"
|
||||||
},
|
},
|
||||||
"categories": [
|
"categories": [
|
||||||
"Linters",
|
"Linters",
|
||||||
|
|
Loading…
Reference in New Issue