Skip to content

Commit f59adbe

Browse files
committed
Adopt getVariants API
1 parent bf57dd1 commit f59adbe

File tree

6 files changed

+281
-126
lines changed

6 files changed

+281
-126
lines changed

packages/tailwindcss-language-server/src/server.ts

Lines changed: 98 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ import {
6363
FeatureFlags,
6464
Settings,
6565
ClassNames,
66+
Variant,
6667
} from 'tailwindcss-language-service/src/util/state'
6768
import {
6869
provideDiagnostics,
@@ -1181,105 +1182,117 @@ function isAtRule(node: Node): node is AtRule {
11811182
return node.type === 'atrule'
11821183
}
11831184

1184-
function getVariants(state: State): Record<string, string> {
1185-
if (state.jit) {
1186-
function escape(className: string): string {
1187-
let node = state.modules.postcssSelectorParser.module.className()
1188-
node.value = className
1189-
return dlv(node, 'raws.value', node.value)
1190-
}
1185+
function getVariants(state: State): Array<Variant> {
1186+
if (state.jitContext?.getVariants) {
1187+
return state.jitContext.getVariants()
1188+
}
11911189

1192-
let result = {}
1190+
if (state.jit) {
1191+
let result: Array<Variant> = []
11931192
// [name, [sort, fn]]
11941193
// [name, [[sort, fn]]]
11951194
Array.from(state.jitContext.variantMap as Map<string, [any, any]>).forEach(
11961195
([variantName, variantFnOrFns]) => {
1197-
let fns = (Array.isArray(variantFnOrFns[0]) ? variantFnOrFns : [variantFnOrFns]).map(
1198-
([_sort, fn]) => fn
1199-
)
1196+
result.push({
1197+
name: variantName,
1198+
values: [],
1199+
isArbitrary: false,
1200+
hasDash: true,
1201+
selectors: () => {
1202+
function escape(className: string): string {
1203+
let node = state.modules.postcssSelectorParser.module.className()
1204+
node.value = className
1205+
return dlv(node, 'raws.value', node.value)
1206+
}
12001207

1201-
let placeholder = '__variant_placeholder__'
1208+
let fns = (Array.isArray(variantFnOrFns[0]) ? variantFnOrFns : [variantFnOrFns]).map(
1209+
([_sort, fn]) => fn
1210+
)
12021211

1203-
let root = state.modules.postcss.module.root({
1204-
nodes: [
1205-
state.modules.postcss.module.rule({
1206-
selector: `.${escape(placeholder)}`,
1207-
nodes: [],
1208-
}),
1209-
],
1210-
})
1212+
let placeholder = '__variant_placeholder__'
12111213

1212-
let classNameParser = state.modules.postcssSelectorParser.module((selectors) => {
1213-
return selectors.first.filter(({ type }) => type === 'class').pop().value
1214-
})
1214+
let root = state.modules.postcss.module.root({
1215+
nodes: [
1216+
state.modules.postcss.module.rule({
1217+
selector: `.${escape(placeholder)}`,
1218+
nodes: [],
1219+
}),
1220+
],
1221+
})
12151222

1216-
function getClassNameFromSelector(selector) {
1217-
return classNameParser.transformSync(selector)
1218-
}
1223+
let classNameParser = state.modules.postcssSelectorParser.module((selectors) => {
1224+
return selectors.first.filter(({ type }) => type === 'class').pop().value
1225+
})
12191226

1220-
function modifySelectors(modifierFunction) {
1221-
root.each((rule) => {
1222-
if (rule.type !== 'rule') {
1223-
return
1227+
function getClassNameFromSelector(selector) {
1228+
return classNameParser.transformSync(selector)
12241229
}
12251230

1226-
rule.selectors = rule.selectors.map((selector) => {
1227-
return modifierFunction({
1228-
get className() {
1229-
return getClassNameFromSelector(selector)
1231+
function modifySelectors(modifierFunction) {
1232+
root.each((rule) => {
1233+
if (rule.type !== 'rule') {
1234+
return
1235+
}
1236+
1237+
rule.selectors = rule.selectors.map((selector) => {
1238+
return modifierFunction({
1239+
get className() {
1240+
return getClassNameFromSelector(selector)
1241+
},
1242+
selector,
1243+
})
1244+
})
1245+
})
1246+
return root
1247+
}
1248+
1249+
let definitions = []
1250+
1251+
for (let fn of fns) {
1252+
let definition: string
1253+
let container = root.clone()
1254+
let returnValue = fn({
1255+
container,
1256+
separator: state.separator,
1257+
modifySelectors,
1258+
format: (def: string) => {
1259+
definition = def.replace(/:merge\(([^)]+)\)/g, '$1')
1260+
},
1261+
wrap: (rule: Container) => {
1262+
if (isAtRule(rule)) {
1263+
definition = `@${rule.name} ${rule.params}`
1264+
}
12301265
},
1231-
selector,
12321266
})
1233-
})
1234-
})
1235-
return root
1236-
}
12371267

1238-
let definitions = []
1239-
1240-
for (let fn of fns) {
1241-
let definition: string
1242-
let container = root.clone()
1243-
let returnValue = fn({
1244-
container,
1245-
separator: state.separator,
1246-
modifySelectors,
1247-
format: (def: string) => {
1248-
definition = def.replace(/:merge\(([^)]+)\)/g, '$1')
1249-
},
1250-
wrap: (rule: Container) => {
1251-
if (isAtRule(rule)) {
1252-
definition = `@${rule.name} ${rule.params}`
1268+
if (!definition) {
1269+
definition = returnValue
12531270
}
1254-
},
1255-
})
1256-
1257-
if (!definition) {
1258-
definition = returnValue
1259-
}
12601271

1261-
if (definition) {
1262-
definitions.push(definition)
1263-
continue
1264-
}
1272+
if (definition) {
1273+
definitions.push(definition)
1274+
continue
1275+
}
12651276

1266-
container.walkDecls((decl) => {
1267-
decl.remove()
1268-
})
1277+
container.walkDecls((decl) => {
1278+
decl.remove()
1279+
})
12691280

1270-
definition = container
1271-
.toString()
1272-
.replace(`.${escape(`${variantName}:${placeholder}`)}`, '&')
1273-
.replace(/(?<!\\)[{}]/g, '')
1274-
.replace(/\s*\n\s*/g, ' ')
1275-
.trim()
1281+
definition = container
1282+
.toString()
1283+
.replace(`.${escape(`${variantName}:${placeholder}`)}`, '&')
1284+
.replace(/(?<!\\)[{}]/g, '')
1285+
.replace(/\s*\n\s*/g, ' ')
1286+
.trim()
12761287

1277-
if (!definition.includes(placeholder)) {
1278-
definitions.push(definition)
1279-
}
1280-
}
1288+
if (!definition.includes(placeholder)) {
1289+
definitions.push(definition)
1290+
}
1291+
}
12811292

1282-
result[variantName] = definitions.join(', ') || null
1293+
return definitions
1294+
},
1295+
})
12831296
}
12841297
)
12851298

@@ -1311,7 +1324,13 @@ function getVariants(state: State): Record<string, string> {
13111324
})
13121325
})
13131326

1314-
return variants.reduce((obj, variant) => ({ ...obj, [variant]: null }), {})
1327+
return variants.map((variant) => ({
1328+
name: variant,
1329+
values: [],
1330+
isArbitrary: false,
1331+
hasDash: true,
1332+
selectors: () => [],
1333+
}))
13151334
}
13161335

13171336
async function getPlugins(config: any) {

packages/tailwindcss-language-service/src/completionProvider.ts

Lines changed: 99 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Settings, State } from './util/state'
1+
import { Settings, State, Variant } from './util/state'
22
import type {
33
CompletionItem,
44
CompletionItemKind,
@@ -110,7 +110,6 @@ export function completionsFromClassList(
110110
}
111111
}
112112

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

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

125124
if (!important) {
126-
let shouldSortVariants = !semver.gte(state.version, '2.99.0')
125+
let variantOrder = 0
126+
127+
function variantItem(
128+
item: Omit<CompletionItem, 'kind' | 'data' | 'sortText' | 'textEdit'> & {
129+
textEdit?: { newText: string; range?: Range }
130+
}
131+
): CompletionItem {
132+
return {
133+
kind: 9,
134+
data: 'variant',
135+
command:
136+
item.insertTextFormat === 2 // Snippet
137+
? undefined
138+
: {
139+
title: '',
140+
command: 'editor.action.triggerSuggest',
141+
},
142+
sortText: '-' + naturalExpand(variantOrder++),
143+
...item,
144+
textEdit: {
145+
newText: item.label,
146+
range: replacementRange,
147+
...item.textEdit,
148+
},
149+
}
150+
}
127151

128152
items.push(
129-
...Object.entries(state.variants)
130-
.filter(([variant]) => !existingVariants.includes(variant))
131-
.map(([variant, definition], index) => {
132-
let resultingVariants = [...existingVariants, variant]
153+
...state.variants.flatMap((variant) => {
154+
let items: CompletionItem[] = []
155+
156+
if (variant.isArbitrary) {
157+
items.push(
158+
variantItem({
159+
label: `${variant.name}${variant.hasDash ? '-' : ''}[]${sep}`,
160+
insertTextFormat: 2,
161+
textEdit: {
162+
newText: `${variant.name}-[\${1:&}]${sep}\${0}`,
163+
},
164+
// command: {
165+
// title: '',
166+
// command: 'tailwindCSS.onInsertArbitraryVariantSnippet',
167+
// arguments: [variant.name, replacementRange],
168+
// },
169+
})
170+
)
171+
} else if (!existingVariants.includes(variant.name)) {
172+
let shouldSortVariants = !semver.gte(state.version, '2.99.0')
173+
let resultingVariants = [...existingVariants, variant.name]
133174

134175
if (shouldSortVariants) {
176+
let allVariants = state.variants.map(({ name }) => name)
135177
resultingVariants = resultingVariants.sort(
136178
(a, b) => allVariants.indexOf(b) - allVariants.indexOf(a)
137179
)
138180
}
139181

140-
return {
141-
label: variant + sep,
142-
kind: 9,
143-
detail: definition,
144-
data: 'variant',
145-
command: {
146-
title: '',
147-
command: 'editor.action.triggerSuggest',
148-
},
149-
sortText: '-' + naturalExpand(index),
150-
textEdit: {
151-
newText: resultingVariants[resultingVariants.length - 1] + sep,
152-
range: replacementRange,
153-
},
154-
additionalTextEdits:
155-
shouldSortVariants && resultingVariants.length > 1
156-
? [
157-
{
158-
newText:
159-
resultingVariants.slice(0, resultingVariants.length - 1).join(sep) + sep,
160-
range: {
161-
start: {
162-
...classListRange.start,
163-
character: classListRange.end.character - partialClassName.length,
164-
},
165-
end: {
166-
...replacementRange.start,
167-
character: replacementRange.start.character,
182+
items.push(
183+
variantItem({
184+
label: `${variant.name}${sep}`,
185+
detail: variant.selectors().join(', '),
186+
textEdit: {
187+
newText: resultingVariants[resultingVariants.length - 1] + sep,
188+
},
189+
additionalTextEdits:
190+
shouldSortVariants && resultingVariants.length > 1
191+
? [
192+
{
193+
newText:
194+
resultingVariants.slice(0, resultingVariants.length - 1).join(sep) +
195+
sep,
196+
range: {
197+
start: {
198+
...classListRange.start,
199+
character: classListRange.end.character - partialClassName.length,
200+
},
201+
end: {
202+
...replacementRange.start,
203+
character: replacementRange.start.character,
204+
},
168205
},
169206
},
170-
},
171-
]
172-
: [],
173-
} as CompletionItem
174-
})
207+
]
208+
: [],
209+
})
210+
)
211+
}
212+
213+
if (variant.values.length) {
214+
items.push(
215+
...variant.values
216+
.filter((value) => !existingVariants.includes(`${variant.name}-${value}`))
217+
.map((value) =>
218+
variantItem({
219+
label: `${variant.name}${variant.hasDash ? '-' : ''}${value}${sep}`,
220+
detail: variant.selectors({ value }).join(', '),
221+
})
222+
)
223+
)
224+
}
225+
226+
return items
227+
})
175228
)
176229
}
177230

@@ -790,7 +843,12 @@ function provideVariantsDirectiveCompletions(
790843

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

793-
let possibleVariants = Object.keys(state.variants)
846+
let possibleVariants = state.variants.flatMap((variant) => {
847+
if (variant.values.length) {
848+
return variant.values.map((value) => `${variant.name}${variant.hasDash ? '-' : ''}${value}`)
849+
}
850+
return [variant.name]
851+
})
794852
const existingVariants = parts.slice(0, parts.length - 1)
795853

796854
if (state.jit) {

0 commit comments

Comments
 (0)