Skip to content

Commit

Permalink
Adopt getVariants API
Browse files Browse the repository at this point in the history
  • Loading branch information
bradlc committed Oct 17, 2022
1 parent bf57dd1 commit f59adbe
Show file tree
Hide file tree
Showing 6 changed files with 281 additions and 126 deletions.
177 changes: 98 additions & 79 deletions packages/tailwindcss-language-server/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ import {
FeatureFlags,
Settings,
ClassNames,
Variant,
} from 'tailwindcss-language-service/src/util/state'
import {
provideDiagnostics,
Expand Down Expand Up @@ -1181,105 +1182,117 @@ function isAtRule(node: Node): node is AtRule {
return node.type === 'atrule'
}

function getVariants(state: State): Record<string, string> {
if (state.jit) {
function escape(className: string): string {
let node = state.modules.postcssSelectorParser.module.className()
node.value = className
return dlv(node, 'raws.value', node.value)
}
function getVariants(state: State): Array<Variant> {
if (state.jitContext?.getVariants) {
return state.jitContext.getVariants()
}

let result = {}
if (state.jit) {
let result: Array<Variant> = []
// [name, [sort, fn]]
// [name, [[sort, fn]]]
Array.from(state.jitContext.variantMap as Map<string, [any, any]>).forEach(
([variantName, variantFnOrFns]) => {
let fns = (Array.isArray(variantFnOrFns[0]) ? variantFnOrFns : [variantFnOrFns]).map(
([_sort, fn]) => fn
)
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 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)
function modifySelectors(modifierFunction) {
root.each((rule) => {
if (rule.type !== 'rule') {
return
}

rule.selectors = rule.selectors.map((selector) => {
return modifierFunction({
get className() {
return getClassNameFromSelector(selector)
},
selector,
})
})
})
return root
}

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}`
}
},
selector,
})
})
})
return root
}

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}`
if (!definition) {
definition = returnValue
}
},
})

if (!definition) {
definition = returnValue
}

if (definition) {
definitions.push(definition)
continue
}
if (definition) {
definitions.push(definition)
continue
}

container.walkDecls((decl) => {
decl.remove()
})
container.walkDecls((decl) => {
decl.remove()
})

definition = container
.toString()
.replace(`.${escape(`${variantName}:${placeholder}`)}`, '&')
.replace(/(?<!\\)[{}]/g, '')
.replace(/\s*\n\s*/g, ' ')
.trim()
definition = container
.toString()
.replace(`.${escape(`${variantName}:${placeholder}`)}`, '&')
.replace(/(?<!\\)[{}]/g, '')
.replace(/\s*\n\s*/g, ' ')
.trim()

if (!definition.includes(placeholder)) {
definitions.push(definition)
}
}
if (!definition.includes(placeholder)) {
definitions.push(definition)
}
}

result[variantName] = definitions.join(', ') || null
return definitions
},
})
}
)

Expand Down Expand Up @@ -1311,7 +1324,13 @@ function getVariants(state: State): Record<string, string> {
})
})

return variants.reduce((obj, variant) => ({ ...obj, [variant]: null }), {})
return variants.map((variant) => ({
name: variant,
values: [],
isArbitrary: false,
hasDash: true,
selectors: () => [],
}))
}

async function getPlugins(config: any) {
Expand Down
140 changes: 99 additions & 41 deletions packages/tailwindcss-language-service/src/completionProvider.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Settings, State } from './util/state'
import { Settings, State, Variant } from './util/state'
import type {
CompletionItem,
CompletionItemKind,
Expand Down Expand Up @@ -110,7 +110,6 @@ export function completionsFromClassList(
}
}

let allVariants = Object.keys(state.variants)
let { variants: existingVariants, offset } = getVariantsFromClassName(state, partialClassName)

replacementRange.start.character += offset
Expand All @@ -123,55 +122,109 @@ export function completionsFromClassList(
let items: CompletionItem[] = []

if (!important) {
let shouldSortVariants = !semver.gte(state.version, '2.99.0')
let variantOrder = 0

function variantItem(
item: Omit<CompletionItem, 'kind' | 'data' | 'sortText' | 'textEdit'> & {
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,
},
}
}

items.push(
...Object.entries(state.variants)
.filter(([variant]) => !existingVariants.includes(variant))
.map(([variant, definition], index) => {
let resultingVariants = [...existingVariants, variant]
...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]

if (shouldSortVariants) {
let allVariants = state.variants.map(({ name }) => name)
resultingVariants = resultingVariants.sort(
(a, b) => allVariants.indexOf(b) - allVariants.indexOf(a)
)
}

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,
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,
},
},
},
},
]
: [],
} as CompletionItem
})
]
: [],
})
)
}

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
})
)
}

Expand Down Expand Up @@ -790,7 +843,12 @@ function provideVariantsDirectiveCompletions(

if (/\s+/.test(parts[parts.length - 1])) return null

let possibleVariants = Object.keys(state.variants)
let possibleVariants = state.variants.flatMap((variant) => {
if (variant.values.length) {
return variant.values.map((value) => `${variant.name}${variant.hasDash ? '-' : ''}${value}`)
}
return [variant.name]
})
const existingVariants = parts.slice(0, parts.length - 1)

if (state.jit) {
Expand Down
Loading

0 comments on commit f59adbe

Please sign in to comment.