diff --git a/packages/tailwindcss-language-server/src/server.ts b/packages/tailwindcss-language-server/src/server.ts index 008c221a7744fb579ca9a8887c75c87a9916755c..d98ab34cbba69391205ffaf7f6be1f5b49e83546 100644 --- a/packages/tailwindcss-language-server/src/server.ts +++ b/packages/tailwindcss-language-server/src/server.ts @@ -63,7 +63,6 @@ State, FeatureFlags, Settings, ClassNames, - Variant, } from 'tailwindcss-language-service/src/util/state' import { provideDiagnostics, @@ -1182,117 +1181,105 @@ function isAtRule(node: Node): node is AtRule { return node.type === 'atrule' } -function getVariants(state: State): Array { - if (state.jitContext?.getVariants) { - return state.jitContext.getVariants() - } - +function getVariants(state: State): Record { if (state.jit) { - let result: Array = [] + function escape(className: string): string { + let node = state.modules.postcssSelectorParser.module.className() + node.value = className + return dlv(node, 'raws.value', node.value) + } + + let result = {} // [name, [sort, fn]] // [name, [[sort, fn]]] Array.from(state.jitContext.variantMap as Map).forEach( ([variantName, variantFnOrFns]) => { - result.push({ - name: variantName, - values: [], - isArbitrary: false, - hasDash: true, - selectors: () => { - function escape(className: string): string { - let node = state.modules.postcssSelectorParser.module.className() - node.value = className - return dlv(node, 'raws.value', node.value) - } - - let fns = (Array.isArray(variantFnOrFns[0]) ? variantFnOrFns : [variantFnOrFns]).map( - ([_sort, fn]) => fn - ) - - let placeholder = '__variant_placeholder__' + let fns = (Array.isArray(variantFnOrFns[0]) ? variantFnOrFns : [variantFnOrFns]).map( + ([_sort, fn]) => fn + ) - let root = state.modules.postcss.module.root({ - nodes: [ - state.modules.postcss.module.rule({ - selector: `.${escape(placeholder)}`, - nodes: [], - }), - ], - }) + let placeholder = '__variant_placeholder__' - let classNameParser = state.modules.postcssSelectorParser.module((selectors) => { - return selectors.first.filter(({ type }) => type === 'class').pop().value - }) + let root = state.modules.postcss.module.root({ + nodes: [ + state.modules.postcss.module.rule({ + selector: `.${escape(placeholder)}`, + nodes: [], + }), + ], + }) - function getClassNameFromSelector(selector) { - return classNameParser.transformSync(selector) - } + let classNameParser = state.modules.postcssSelectorParser.module((selectors) => { + return selectors.first.filter(({ type }) => type === 'class').pop().value + }) - function modifySelectors(modifierFunction) { - root.each((rule) => { - if (rule.type !== 'rule') { - return - } + function getClassNameFromSelector(selector) { + return classNameParser.transformSync(selector) + } - rule.selectors = rule.selectors.map((selector) => { - return modifierFunction({ - get className() { - return getClassNameFromSelector(selector) - }, - selector, - }) - }) - }) - return root + function modifySelectors(modifierFunction) { + root.each((rule) => { + if (rule.type !== 'rule') { + return } - let definitions = [] - - for (let fn of fns) { - let definition: string - let container = root.clone() - let returnValue = fn({ - container, - separator: state.separator, - modifySelectors, - format: (def: string) => { - definition = def.replace(/:merge\(([^)]+)\)/g, '$1') - }, - wrap: (rule: Container) => { - if (isAtRule(rule)) { - definition = `@${rule.name} ${rule.params}` - } + rule.selectors = rule.selectors.map((selector) => { + return modifierFunction({ + get className() { + return getClassNameFromSelector(selector) }, + selector, }) + }) + }) + return root + } - if (!definition) { - definition = returnValue - } + let definitions = [] - if (definition) { - definitions.push(definition) - continue + for (let fn of fns) { + let definition: string + let container = root.clone() + let returnValue = fn({ + container, + separator: state.separator, + modifySelectors, + format: (def: string) => { + definition = def.replace(/:merge\(([^)]+)\)/g, '$1') + }, + wrap: (rule: Container) => { + if (isAtRule(rule)) { + definition = `@${rule.name} ${rule.params}` } + }, + }) - container.walkDecls((decl) => { - decl.remove() - }) + if (!definition) { + definition = returnValue + } - definition = container - .toString() - .replace(`.${escape(`${variantName}:${placeholder}`)}`, '&') - .replace(/(? { + decl.remove() + }) - return definitions - }, - }) + definition = container + .toString() + .replace(`.${escape(`${variantName}:${placeholder}`)}`, '&') + .replace(/(? ({ - name: variant, - values: [], - isArbitrary: false, - hasDash: true, - selectors: () => [], - })) + return variants.reduce((obj, variant) => ({ ...obj, [variant]: null }), {}) } async function getPlugins(config: any) { diff --git a/packages/tailwindcss-language-service/src/completionProvider.ts b/packages/tailwindcss-language-service/src/completionProvider.ts index adacd136a9a752e0df6731784e1a5fbf05345fd8..941554f165372cf461f94d82d5f1a7f4aa9f6c4c 100644 --- a/packages/tailwindcss-language-service/src/completionProvider.ts +++ b/packages/tailwindcss-language-service/src/completionProvider.ts @@ -1,4 +1,4 @@ -import { Settings, State, Variant } from './util/state' +import { Settings, State } from './util/state' import type { CompletionItem, CompletionItemKind, @@ -110,6 +110,7 @@ } } } + let allVariants = Object.keys(state.variants) let { variants: existingVariants, offset } = getVariantsFromClassName(state, partialClassName) replacementRange.start.character += offset @@ -122,109 +123,55 @@ let items: CompletionItem[] = [] if (!important) { - let variantOrder = 0 - - function variantItem( - item: Omit & { - textEdit?: { newText: string; range?: Range } - } - ): CompletionItem { - return { - kind: 9, - data: 'variant', - command: - item.insertTextFormat === 2 // Snippet - ? undefined - : { - title: '', - command: 'editor.action.triggerSuggest', - }, - sortText: '-' + naturalExpand(variantOrder++), - ...item, - textEdit: { - newText: item.label, - range: replacementRange, - ...item.textEdit, - }, - } - } + let shouldSortVariants = !semver.gte(state.version, '2.99.0') items.push( - ...state.variants.flatMap((variant) => { - let items: CompletionItem[] = [] - - if (variant.isArbitrary) { - items.push( - variantItem({ - label: `${variant.name}${variant.hasDash ? '-' : ''}[]${sep}`, - insertTextFormat: 2, - textEdit: { - newText: `${variant.name}-[\${1:&}]${sep}\${0}`, - }, - // command: { - // title: '', - // command: 'tailwindCSS.onInsertArbitraryVariantSnippet', - // arguments: [variant.name, replacementRange], - // }, - }) - ) - } else if (!existingVariants.includes(variant.name)) { - let shouldSortVariants = !semver.gte(state.version, '2.99.0') - let resultingVariants = [...existingVariants, variant.name] + ...Object.entries(state.variants) + .filter(([variant]) => !existingVariants.includes(variant)) + .map(([variant, definition], index) => { + let resultingVariants = [...existingVariants, variant] if (shouldSortVariants) { - let allVariants = state.variants.map(({ name }) => name) resultingVariants = resultingVariants.sort( (a, b) => allVariants.indexOf(b) - allVariants.indexOf(a) ) } - items.push( - variantItem({ - label: `${variant.name}${sep}`, - detail: variant.selectors().join(', '), - textEdit: { - newText: resultingVariants[resultingVariants.length - 1] + sep, - }, - additionalTextEdits: - shouldSortVariants && resultingVariants.length > 1 - ? [ - { - newText: - resultingVariants.slice(0, resultingVariants.length - 1).join(sep) + - sep, - range: { - start: { - ...classListRange.start, - character: classListRange.end.character - partialClassName.length, - }, - end: { - ...replacementRange.start, - character: replacementRange.start.character, - }, + return { + label: variant + sep, + kind: 9, + detail: definition, + data: 'variant', + command: { + title: '', + command: 'editor.action.triggerSuggest', + }, + sortText: '-' + naturalExpand(index), + textEdit: { + newText: resultingVariants[resultingVariants.length - 1] + sep, + range: replacementRange, + }, + additionalTextEdits: + shouldSortVariants && resultingVariants.length > 1 + ? [ + { + newText: + resultingVariants.slice(0, resultingVariants.length - 1).join(sep) + sep, + range: { + start: { + ...classListRange.start, + character: classListRange.end.character - partialClassName.length, + }, + end: { + ...replacementRange.start, + character: replacementRange.start.character, }, }, - ] - : [], - }) - ) - } - - if (variant.values.length) { - items.push( - ...variant.values - .filter((value) => !existingVariants.includes(`${variant.name}-${value}`)) - .map((value) => - variantItem({ - label: `${variant.name}${variant.hasDash ? '-' : ''}${value}${sep}`, - detail: variant.selectors({ value }).join(', '), - }) - ) - ) - } - - return items - }) + }, + ] + : [], + } as CompletionItem + }) ) } @@ -843,12 +790,7 @@ const parts = match.groups.partial.split(/\s*,\s*/) if (/\s+/.test(parts[parts.length - 1])) return null - let possibleVariants = state.variants.flatMap((variant) => { - if (variant.values.length) { - return variant.values.map((value) => `${variant.name}${variant.hasDash ? '-' : ''}${value}`) - } - return [variant.name] - }) + let possibleVariants = Object.keys(state.variants) const existingVariants = parts.slice(0, parts.length - 1) if (state.jit) { diff --git a/packages/tailwindcss-language-service/src/diagnostics/getInvalidVariantDiagnostics.ts b/packages/tailwindcss-language-service/src/diagnostics/getInvalidVariantDiagnostics.ts index 454e0f59cafae735251e5b97a10af6329a9f8d8c..1b6c997abe4119d48fd73b428837f673c1fd08e1 100644 --- a/packages/tailwindcss-language-service/src/diagnostics/getInvalidVariantDiagnostics.ts +++ b/packages/tailwindcss-language-service/src/diagnostics/getInvalidVariantDiagnostics.ts @@ -32,12 +32,7 @@ if (!boundaries) return [] ranges.push(...boundaries.filter((b) => b.type === 'css').map(({ range }) => range)) } - let possibleVariants = state.variants.flatMap((variant) => { - if (variant.values.length) { - return variant.values.map((value) => `${variant.name}${variant.hasDash ? '-' : ''}${value}`) - } - return [variant.name] - }) + let possibleVariants = Object.keys(state.variants) if (state.jit) { possibleVariants.unshift('responsive') possibleVariants = possibleVariants.filter((v) => !state.screens.includes(v)) diff --git a/packages/tailwindcss-language-service/src/util/getVariantsFromClassName.ts b/packages/tailwindcss-language-service/src/util/getVariantsFromClassName.ts index b1709e12c482168bcb00cd38cc5a581eb24ec0eb..10dfe90c7cf4d353d820a43f32ed880f5fc490c8 100644 --- a/packages/tailwindcss-language-service/src/util/getVariantsFromClassName.ts +++ b/packages/tailwindcss-language-service/src/util/getVariantsFromClassName.ts @@ -5,25 +5,17 @@ export function getVariantsFromClassName( state: State, className: string ): { variants: string[]; offset: number } { - let allVariants = state.variants.flatMap((variant) => { - if (variant.values.length) { - return variant.values.map((value) => `${variant.name}${variant.hasDash ? '-' : ''}${value}`) - } - return [variant.name] - }) + let allVariants = Object.keys(state.variants) + let parts = splitAtTopLevelOnly(className, state.separator).filter(Boolean) let variants = new Set() let offset = 0 - let parts = splitAtTopLevelOnly(className, state.separator) - if (parts.length < 2) { - return { variants: Array.from(variants), offset } - } - parts = parts.filter(Boolean) for (let part of parts) { if ( allVariants.includes(part) || (state.jit && - ((part.includes('[') && part.endsWith(']')) || part.includes('/')) && + ((part.includes('[') && part.endsWith(']')) || + (part.includes('<') && part.includes('>'))) && jit.generateRules(state, [`${part}${state.separator}[color:red]`]).rules.length > 0) ) { variants.add(part) diff --git a/packages/tailwindcss-language-service/src/util/state.ts b/packages/tailwindcss-language-service/src/util/state.ts index 4c82bb52f4d20aa0a7ff551700be178b14318179..cb863773db039a83b1afeae41807701a07a88219 100644 --- a/packages/tailwindcss-language-service/src/util/state.ts +++ b/packages/tailwindcss-language-service/src/util/state.ts @@ -80,14 +80,6 @@ future: string[] experimental: string[] } -export interface Variant { - name: string - values: string[] - isArbitrary: boolean - hasDash: boolean - selectors: (params?: { value?: string; label?: string }) => string[] -} - export interface State { enabled: boolean configPath?: string @@ -98,7 +90,7 @@ separator?: string dependencies?: string[] plugins?: any screens?: string[] - variants?: Variant[] + variants?: Record corePlugins?: string[] modules?: { tailwindcss?: { version: string; module: any } diff --git a/packages/vscode-tailwindcss/src/extension.ts b/packages/vscode-tailwindcss/src/extension.ts index d1cb232a15727263b29110a09d58a70b75da31fe..4b2bdfb03c7c526ba31f67193e9c573fdec095d8 100755 --- a/packages/vscode-tailwindcss/src/extension.ts +++ b/packages/vscode-tailwindcss/src/extension.ts @@ -26,7 +26,6 @@ CompletionList, ProviderResult, SnippetString, TextEdit, - TextEditorSelectionChangeKind, } from 'vscode' import { LanguageClient, @@ -149,62 +148,6 @@ outputChannel.show() } }) ) - - // context.subscriptions.push( - // commands.registerCommand( - // 'tailwindCSS.onInsertArbitraryVariantSnippet', - // ( - // variantName: string, - // range: { - // start: { line: number; character: number } - // end: { line: number; character: number } - // } - // ) => { - // let listener = Window.onDidChangeTextEditorSelection((event) => { - // if (event.selections.length !== 1) { - // listener.dispose() - // return - // } - - // let document = event.textEditor.document - // let selection = event.selections[0] - - // let line = document.lineAt(range.start.line) - // let lineRangeFromCompletion = new Range( - // range.start.line, - // range.start.character, - // line.range.end.line, - // line.range.end.character - // ) - // let lineText = document.getText(lineRangeFromCompletion) - // let match = lineText.match(/^(\S+)]:/) - - // if (!match) { - // listener.dispose() - // return - // } - - // let arbitraryValueRange = new Range( - // lineRangeFromCompletion.start.translate(0, variantName.length + 2), - // lineRangeFromCompletion.start.translate(0, match[1].length) - // ) - - // if (!arbitraryValueRange.contains(selection)) { - // listener.dispose() - // } - - // if ( - // event.kind === TextEditorSelectionChangeKind.Command && - // selection.isEmpty && - // selection.start.isEqual(arbitraryValueRange.end.translate(0, 2)) - // ) { - // commands.executeCommand('editor.action.triggerSuggest') - // } - // }) - // context.subscriptions.push(listener) - // } - // ) - // ) let watcher = Workspace.createFileSystemWatcher(`**/${CONFIG_FILE_GLOB}`, false, true, true)