diff --git a/packages/tailwindcss-language-server/src/server.ts b/packages/tailwindcss-language-server/src/server.ts index 190503a8a44871df2191a3ffa9e36a2815cd5ee1..99bb98946f010e802c6ea1425a2afe4b692a73bb 100644 --- a/packages/tailwindcss-language-server/src/server.ts +++ b/packages/tailwindcss-language-server/src/server.ts @@ -378,21 +378,8 @@ const folder = projectConfig.folder const disposables: Array> = [] 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 = { enabled: false, - completionItemData: { - _projectKey: projectKey, - }, editor: { connection, folder, @@ -403,7 +390,6 @@ // TODO capabilities: { configuration: true, diagnosticRelatedInformation: true, - itemDefaults, }, documents: documentService.documents, getConfiguration, @@ -1128,13 +1114,21 @@ if (!document) return null let settings = await state.editor.getConfiguration(document.uri) if (!settings.tailwindCSS.suggestions) return null if (await isExcluded(state, document)) return null - return doComplete(state, document, params.position, params.context) + let result = await 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) }, onCompletionResolve(item: CompletionItem): Promise { return withFallback(() => { if (!state.enabled) return null - return resolveCompletionItem(state, item) + return resolveCompletionItem(state, { ...item, data: item.data?.originalData }) }, null) }, async onCodeAction(params: CodeActionParams): Promise { @@ -2168,7 +2162,7 @@ return this.getProject(params.textDocument)?.onCompletion(params) ?? null } async onCompletionResolve(item: CompletionItem): Promise { - return this.projects.get(item.data?._projectKey)?.onCompletionResolve(item) ?? null + return this.projects.get(item.data.projectKey)?.onCompletionResolve(item) ?? null } onCodeAction(params: CodeActionParams): Promise { diff --git a/packages/tailwindcss-language-service/src/completionProvider.ts b/packages/tailwindcss-language-service/src/completionProvider.ts index 9f7a246cd3d96580cb705ca6551bac845ced5dd2..63a5edf3b6f87daf725592ef09d841e7e54b5bae 100644 --- a/packages/tailwindcss-language-service/src/completionProvider.ts +++ b/packages/tailwindcss-language-service/src/completionProvider.ts @@ -97,36 +97,34 @@ } } if (modifiers) { - return withDefaults( - { - isIncomplete: false, - items: modifiers.map((modifier, index) => { - let className = `${beforeSlash}/${modifier}` - let kind: CompletionItemKind = 21 - let documentation: string | undefined + return { + isIncomplete: false, + items: modifiers.map((modifier, index) => { + let className = `${beforeSlash}/${modifier}` + 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) - } + 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 ? { documentation } : {}), - kind, - sortText: naturalExpand(index), - } - }), - }, - { - range: replacementRange, - data: state.completionItemData, - }, - state.editor.capabilities.itemDefaults - ) + return { + label: className, + documentation, + kind, + sortText: naturalExpand(index), + data: [className], + textEdit: { + newText: className, + range: replacementRange, + }, + } + }), + } } } @@ -143,14 +141,13 @@ if (!important) { let variantOrder = 0 function variantItem( - item: Omit + item: Omit & { + textEdit?: { newText: string; range?: Range } + } ): CompletionItem { return { kind: 9, - data: { - ...(state.completionItemData ?? {}), - _type: 'variant', - }, + data: 'variant', command: item.insertTextFormat === 2 // Snippet ? undefined @@ -160,6 +157,11 @@ command: 'editor.action.triggerSuggest', }, sortText: '-' + naturalExpand(variantOrder++), ...item, + textEdit: { + newText: item.label, + range: replacementRange, + ...item.textEdit, + }, } } @@ -172,7 +174,9 @@ items.push( variantItem({ label: `${variant.name}${variant.hasDash ? '-' : ''}[]${sep}`, insertTextFormat: 2, - textEditText: `${variant.name}${variant.hasDash ? '-' : ''}[\${1}]${sep}\${0}`, + textEdit: { + newText: `${variant.name}${variant.hasDash ? '-' : ''}[\${1}]${sep}\${0}`, + }, // command: { // title: '', // command: 'tailwindCSS.onInsertArbitraryVariantSnippet', @@ -195,7 +199,9 @@ items.push( variantItem({ label: `${variant.name}${sep}`, detail: variant.selectors().join(', '), - textEditText: resultingVariants[resultingVariants.length - 1] + sep, + textEdit: { + newText: resultingVariants[resultingVariants.length - 1] + sep, + }, additionalTextEdits: shouldSortVariants && resultingVariants.length > 1 ? [ @@ -242,141 +248,48 @@ ) } if (state.classList) { - 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( - { + return { isIncomplete: false, - 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 - - const color = getColor(state, className) - if (color !== null) { - kind = 16 - if (typeof color !== 'string' && (color.alpha ?? 1) !== 0) { - documentation = culori.formatRgb(color) - } - } + items: items.concat( + state.classList.map(([className, { color }], index) => { + let kind: CompletionItemKind = color ? 16 : 21 + let documentation = null - return { - label: className, - kind, - ...(documentation ? { documentation } : {}), - sortText: naturalExpand(index, classNames.length), - } as CompletionItem - }) - ) - .filter((item) => { - if (item === null) { - return false + if (color && typeof color !== 'string') { + documentation = culori.formatRgb(color) } - if (filter && !filter(item)) { - return false - } - return true - }), - }, - { - range: replacementRange, - data: { - ...(state.completionItemData ?? {}), - variants: existingVariants, - ...(important ? { important } : {}), - }, - }, - state.editor.capabilities.itemDefaults - ) - } - 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, - }, + return { + label: className, + kind, + documentation, + sortText: naturalExpand(index), + data: [...existingVariants, important ? `!${className}` : className], + textEdit: { + newText: className, + range: replacementRange, + }, + } as CompletionItem + }) + ), } - break } - } - return withDefaults( - { + return { isIncomplete: false, - 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, - }, - } - }) + items: items .concat( - Object.keys(isSubset ? subset : state.classNames.classNames) - .filter((className) => - dlv(state.classNames.classNames, [...subsetKey, className, '__info']) - ) - .map((className, index, classNames) => { + 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) => { let kind: CompletionItemKind = 21 - let documentation: string | undefined + let documentation: string = null const color = getColor(state, className) if (color !== null) { @@ -389,9 +302,14 @@ return { label: className, kind, - ...(documentation ? { documentation } : {}), - sortText: naturalExpand(index, classNames.length), - } + documentation, + sortText: naturalExpand(index), + data: [...existingVariants, important ? `!${className}` : className], + textEdit: { + newText: className, + range: replacementRange, + }, + } as CompletionItem }) ) .filter((item) => { @@ -403,16 +321,88 @@ return false } return true }), - }, - { - range: replacementRange, - data: { - ...(state.completionItemData ?? {}), - variants: subsetKey, - }, - }, - state.editor.capabilities.itemDefaults - ) + } + } + + 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 + }), + } } async function provideClassAttributeCompletions( @@ -579,9 +569,7 @@ return ( semver.gte(state.version, '2.0.0-alpha.1') || flagEnabled(state, 'applyComplexClasses') ) } - let variants = item.data?.variants ?? [] - let className = item.data?.className ?? item.label - let validated = validateApply(state, [...variants, className]) + let validated = validateApply(state, item.data) return validated !== null && validated.isApplyable === true } ) @@ -685,72 +673,66 @@ }, end: position, } - 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 { + 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 { - 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, + 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, }, + end: editRange.start, }, - ] - : [], - } - }), - }, - { - range: editRange, - data: { - ...(state.completionItemData ?? {}), - _type: 'helper', - }, - }, - state.editor.capabilities.itemDefaults - ) + }, + ] + : [], + data: 'helper', + } + }), + } } function provideTailwindDirectiveCompletions( @@ -771,94 +753,87 @@ const match = text.match(/^\s*@tailwind\s+(?[^\s]*)$/i) if (match === null) return null - 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' - )})`, + 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' + )})`, + }, }, - } - : { - 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' - )})`, + 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' + )})`, + }, }, - } - : { - 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' - )})`, + ].map((item) => ({ + ...item, + kind: 21, + data: '@tailwind', + textEdit: { + newText: item.label, + range: { + start: { + line: position.line, + character: position.character - match.groups.partial.length, }, + 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( @@ -902,33 +877,29 @@ possibleVariants.unshift('responsive') possibleVariants = possibleVariants.filter((v) => !state.screens.includes(v)) } - 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, + 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, + }, }, - end: position, - }, - }, - state.editor.capabilities.itemDefaults - ) + })), + } } function provideLayerDirectiveCompletions( @@ -949,67 +920,24 @@ const match = text.match(/^\s*@layer\s+(?[^\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, + 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, + }, + 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, - }, - }), - })), + })), } } @@ -1035,30 +963,25 @@ const screens = dlv(state.config, ['screens'], dlv(state.config, ['theme', 'screens'], {})) if (!isObject(screens)) return null - 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, + 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, }, - end: position, }, - }, - state.editor.capabilities.itemDefaults - ) + })), + } } function provideCssDirectiveCompletions( @@ -1166,29 +1089,24 @@ ] : []), ] - 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, + 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, }, - end: position, }, - }, - state.editor.capabilities.itemDefaults - ) + })), + } } async function provideConfigDirectiveCompletions( @@ -1213,34 +1131,28 @@ let partial = match.groups.partial.slice(1) // remove quote let valueBeforeLastSlash = partial.substring(0, partial.lastIndexOf('/')) let valueAfterLastSlash = partial.substring(partial.lastIndexOf('/') + 1) - 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, + 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, + }, }, - end: position, - }, - }, - state.editor.capabilities.itemDefaults - ) + command: type.isDirectory + ? { command: 'editor.action.triggerSuggest', title: '' } + : undefined, + })), + } } async function provideEmmetCompletions( @@ -1343,31 +1255,25 @@ export async function resolveCompletionItem( state: State, item: CompletionItem ): Promise { - if ( - ['helper', 'directive', 'variant', 'layer', '@tailwind', 'filesystem'].includes( - item.data?._type - ) - ) { + if (['helper', 'directive', 'variant', 'layer', '@tailwind'].includes(item.data)) { return item } - if (item.data?._type === 'screen') { + if (item.data === '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 } - let className = item.data?.className ?? item.label - if (item.data?.important) { - className = `!${className}` + if (!Array.isArray(item.data)) { + return item } - 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, [[...variants, className].join(state.separator)]) + let { root, rules } = jit.generateRules(state, [item.data.join(state.separator)]) if (rules.length === 0) return item if (!item.detail) { if (rules.length === 1) { @@ -1385,14 +1291,14 @@ } return item } - const rules = dlv(state.classNames.classNames, [...variants, className, '__info']) + const className = dlv(state.classNames.classNames, [...item.data, '__info']) if (item.kind === 9) { - item.detail = state.classNames.context[className].join(', ') + item.detail = state.classNames.context[item.data[item.data.length - 1]].join(', ') } else { - item.detail = await getCssDetail(state, rules) + item.detail = await getCssDetail(state, className) if (!item.documentation) { const settings = await state.editor.getConfiguration() - const css = stringifyCss([...variants, className].join(':'), rules, settings) + const css = stringifyCss(item.data.join(':'), className, settings) if (css) { item.documentation = { kind: 'markdown' as typeof MarkupKind.Markdown, diff --git a/packages/tailwindcss-language-service/src/util/naturalExpand.ts b/packages/tailwindcss-language-service/src/util/naturalExpand.ts index bedbd804c4b25117ef5991ab6549217d8d82bc89..bce1690b07e955422e6191dea79dac186bb08abe 100644 --- a/packages/tailwindcss-language-service/src/util/naturalExpand.ts +++ b/packages/tailwindcss-language-service/src/util/naturalExpand.ts @@ -1,4 +1,8 @@ -export function naturalExpand(value: number, total?: number): string { - let length = typeof total === 'number' ? total.toString().length : 8 - return ('0'.repeat(length) + value).slice(-length) +function pad(n: string): string { + return ('00000000' + n).substr(-8) +} + +export function naturalExpand(value: number | string): string { + let str = typeof value === 'string' ? value : value.toString() + return str.replace(/\d+/g, pad) } diff --git a/packages/tailwindcss-language-service/src/util/state.ts b/packages/tailwindcss-language-service/src/util/state.ts index cba7c64faf85135984c3ac79d964f6392f05b40a..3787bdcfa6213f187e9fdd458f83791ab7a34e9b 100644 --- a/packages/tailwindcss-language-service/src/util/state.ts +++ b/packages/tailwindcss-language-service/src/util/state.ts @@ -25,7 +25,6 @@ userLanguages: Record capabilities: { configuration: boolean diagnosticRelatedInformation: boolean - itemDefaults: string[] } getConfiguration: (uri?: string) => Promise getDocumentSymbols: (uri: string) => Promise @@ -119,7 +118,6 @@ jit?: boolean jitContext?: any classList?: Array<[string, { color: culori.Color | KeywordColor | null; modifiers?: string[] }]> pluginVersions?: string - completionItemData?: Record // postcssPlugins?: { before: any[]; after: any[] } } diff --git a/packages/vscode-tailwindcss/package.json b/packages/vscode-tailwindcss/package.json index 0cd66304c1f468f4179c0574ab44c23321728872..434d802adb03ac4be6e0f6c9446122b48ae74428 100755 --- a/packages/vscode-tailwindcss/package.json +++ b/packages/vscode-tailwindcss/package.json @@ -24,7 +24,7 @@ "autocomplete", "vscode" ], "engines": { - "vscode": "^1.67.0" + "vscode": "^1.65.0" }, "categories": [ "Linters",