Skip to content

Commit c9fea34

Browse files
author
Brad Cornes
committed
filter @apply completions
1 parent ef1f922 commit c9fea34

File tree

3 files changed

+129
-100
lines changed

3 files changed

+129
-100
lines changed

src/class-names/extractClassNames.js

+84-81
Original file line numberDiff line numberDiff line change
@@ -46,99 +46,102 @@ function getClassNamesFromSelector(selector) {
4646
return classNames
4747
}
4848

49-
async function process(ast) {
49+
async function process(groups) {
5050
const tree = {}
5151
const commonContext = {}
5252

53-
ast.root.walkRules((rule) => {
54-
const classNames = getClassNamesFromSelector(rule.selector)
55-
56-
const decls = {}
57-
rule.walkDecls((decl) => {
58-
if (decls[decl.prop]) {
59-
decls[decl.prop] = [
60-
...(Array.isArray(decls[decl.prop])
61-
? decls[decl.prop]
62-
: [decls[decl.prop]]),
63-
decl.value,
64-
]
65-
} else {
66-
decls[decl.prop] = decl.value
53+
groups.forEach((group) => {
54+
group.root.walkRules((rule) => {
55+
const classNames = getClassNamesFromSelector(rule.selector)
56+
57+
const decls = {}
58+
rule.walkDecls((decl) => {
59+
if (decls[decl.prop]) {
60+
decls[decl.prop] = [
61+
...(Array.isArray(decls[decl.prop])
62+
? decls[decl.prop]
63+
: [decls[decl.prop]]),
64+
decl.value,
65+
]
66+
} else {
67+
decls[decl.prop] = decl.value
68+
}
69+
})
70+
71+
let p = rule
72+
const keys = []
73+
while (p.parent.type !== 'root') {
74+
p = p.parent
75+
if (p.type === 'atrule') {
76+
keys.push(`@${p.name} ${p.params}`)
77+
}
6778
}
68-
})
6979

70-
let p = rule
71-
const keys = []
72-
while (p.parent.type !== 'root') {
73-
p = p.parent
74-
if (p.type === 'atrule') {
75-
keys.push(`@${p.name} ${p.params}`)
76-
}
77-
}
78-
79-
for (let i = 0; i < classNames.length; i++) {
80-
const context = keys.concat([])
81-
const baseKeys = classNames[i].className.split('__TAILWIND_SEPARATOR__')
82-
const contextKeys = baseKeys.slice(0, baseKeys.length - 1)
83-
const index = []
84-
85-
const existing = dlv(tree, baseKeys)
86-
if (typeof existing !== 'undefined') {
87-
if (Array.isArray(existing)) {
88-
const scopeIndex = existing.findIndex(
89-
(x) =>
90-
x.__scope === classNames[i].scope &&
91-
arraysEqual(existing.__context, context)
92-
)
93-
if (scopeIndex > -1) {
94-
keys.unshift(scopeIndex)
95-
index.push(scopeIndex)
80+
for (let i = 0; i < classNames.length; i++) {
81+
const context = keys.concat([])
82+
const baseKeys = classNames[i].className.split('__TAILWIND_SEPARATOR__')
83+
const contextKeys = baseKeys.slice(0, baseKeys.length - 1)
84+
const index = []
85+
86+
const existing = dlv(tree, baseKeys)
87+
if (typeof existing !== 'undefined') {
88+
if (Array.isArray(existing)) {
89+
const scopeIndex = existing.findIndex(
90+
(x) =>
91+
x.__scope === classNames[i].scope &&
92+
arraysEqual(existing.__context, context)
93+
)
94+
if (scopeIndex > -1) {
95+
keys.unshift(scopeIndex)
96+
index.push(scopeIndex)
97+
} else {
98+
keys.unshift(existing.length)
99+
index.push(existing.length)
100+
}
96101
} else {
97-
keys.unshift(existing.length)
98-
index.push(existing.length)
99-
}
100-
} else {
101-
if (
102-
existing.__scope !== classNames[i].scope ||
103-
!arraysEqual(existing.__context, context)
104-
) {
105-
dset(tree, baseKeys, [existing])
106-
keys.unshift(1)
107-
index.push(1)
102+
if (
103+
existing.__scope !== classNames[i].scope ||
104+
!arraysEqual(existing.__context, context)
105+
) {
106+
dset(tree, baseKeys, [existing])
107+
keys.unshift(1)
108+
index.push(1)
109+
}
108110
}
109111
}
110-
}
111-
if (classNames[i].__rule) {
112-
dset(tree, [...baseKeys, ...index, '__rule'], true)
112+
if (classNames[i].__rule) {
113+
dset(tree, [...baseKeys, ...index, '__rule'], true)
114+
dset(tree, [...baseKeys, ...index, '__source'], group.source)
113115

114-
dsetEach(tree, [...baseKeys, ...index], decls)
115-
}
116-
if (classNames[i].__pseudo) {
117-
dset(tree, [...baseKeys, '__pseudo'], classNames[i].__pseudo)
118-
}
119-
dset(tree, [...baseKeys, ...index, '__scope'], classNames[i].scope)
120-
dset(
121-
tree,
122-
[...baseKeys, ...index, '__context'],
123-
context.concat([]).reverse()
124-
)
125-
126-
// common context
127-
if (classNames[i].__pseudo) {
128-
context.push(...classNames[i].__pseudo)
129-
}
116+
dsetEach(tree, [...baseKeys, ...index], decls)
117+
}
118+
if (classNames[i].__pseudo) {
119+
dset(tree, [...baseKeys, '__pseudo'], classNames[i].__pseudo)
120+
}
121+
dset(tree, [...baseKeys, ...index, '__scope'], classNames[i].scope)
122+
dset(
123+
tree,
124+
[...baseKeys, ...index, '__context'],
125+
context.concat([]).reverse()
126+
)
127+
128+
// common context
129+
if (classNames[i].__pseudo) {
130+
context.push(...classNames[i].__pseudo)
131+
}
130132

131-
for (let i = 0; i < contextKeys.length; i++) {
132-
if (typeof commonContext[contextKeys[i]] === 'undefined') {
133-
commonContext[contextKeys[i]] = context
134-
} else {
135-
commonContext[contextKeys[i]] = intersection(
136-
commonContext[contextKeys[i]],
137-
context
138-
)
133+
for (let i = 0; i < contextKeys.length; i++) {
134+
if (typeof commonContext[contextKeys[i]] === 'undefined') {
135+
commonContext[contextKeys[i]] = context
136+
} else {
137+
commonContext[contextKeys[i]] = intersection(
138+
commonContext[contextKeys[i]],
139+
context
140+
)
141+
}
139142
}
140143
}
141-
}
144+
})
142145
})
143146

144147
return { classNames: tree, context: commonContext }

src/class-names/index.js

+10-7
Original file line numberDiff line numberDiff line change
@@ -83,12 +83,12 @@ export default async function getClassNames(
8383
}
8484
hook.unwatch()
8585

86-
const ast = await postcss([tailwindcss(configPath)]).process(
87-
`
88-
@tailwind components;
89-
@tailwind utilities;
90-
`,
91-
{ from: undefined }
86+
const [components, utilities] = await Promise.all(
87+
['components', 'utilities'].map((group) =>
88+
postcss([tailwindcss(configPath)]).process(`@tailwind ${group};`, {
89+
from: undefined,
90+
})
91+
)
9292
)
9393

9494
hook.unhook()
@@ -111,7 +111,10 @@ export default async function getClassNames(
111111
configPath,
112112
config: resolvedConfig,
113113
separator: typeof userSeperator === 'undefined' ? ':' : userSeperator,
114-
classNames: await extractClassNames(ast),
114+
classNames: await extractClassNames([
115+
{ root: components.root, source: 'components' },
116+
{ root: utilities.root, source: 'utilities' },
117+
]),
115118
dependencies: hook.deps,
116119
plugins: getPlugins(config),
117120
variants: getVariants({ config, version, postcss, browserslist }),

src/lsp/providers/completionProvider.ts

+35-12
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ import { ensureArray } from '../../util/array'
2828
function completionsFromClassList(
2929
state: State,
3030
classList: string,
31-
classListRange: Range
31+
classListRange: Range,
32+
filter?: (item: CompletionItem) => boolean
3233
): CompletionList {
3334
let classNames = classList.split(/[\s+]/)
3435
const partialClassName = classNames[classNames.length - 1]
@@ -68,8 +69,8 @@ function completionsFromClassList(
6869

6970
return {
7071
isIncomplete: false,
71-
items: Object.keys(isSubset ? subset : state.classNames.classNames).map(
72-
(className, index) => {
72+
items: Object.keys(isSubset ? subset : state.classNames.classNames)
73+
.map((className, index) => {
7374
let label = className
7475
let kind: CompletionItemKind = CompletionItemKind.Constant
7576
let documentation: string = null
@@ -88,7 +89,7 @@ function completionsFromClassList(
8889
}
8990
}
9091

91-
return {
92+
const item = {
9293
label,
9394
kind,
9495
documentation,
@@ -100,8 +101,14 @@ function completionsFromClassList(
100101
range: replacementRange,
101102
},
102103
}
103-
}
104-
),
104+
105+
if (filter && !filter(item)) {
106+
return null
107+
}
108+
109+
return item
110+
})
111+
.filter((item) => item !== null),
105112
}
106113
}
107114

@@ -175,13 +182,29 @@ function provideAtApplyCompletions(
175182

176183
const classList = match.groups.classList
177184

178-
return completionsFromClassList(state, classList, {
179-
start: {
180-
line: position.line,
181-
character: position.character - classList.length,
185+
return completionsFromClassList(
186+
state,
187+
classList,
188+
{
189+
start: {
190+
line: position.line,
191+
character: position.character - classList.length,
192+
},
193+
end: position,
182194
},
183-
end: position,
184-
})
195+
(item) => {
196+
// TODO: first line excludes all subtrees but there could _technically_ be
197+
// valid apply-able class names in there. Will be correct in 99% of cases
198+
if (item.kind === CompletionItemKind.Module) return false
199+
let info = dlv(state.classNames.classNames, item.data)
200+
return (
201+
!Array.isArray(info) &&
202+
info.__source === 'utilities' &&
203+
(info.__context || []).length === 0 &&
204+
(info.__pseudo || []).length === 0
205+
)
206+
}
207+
)
185208
}
186209

187210
function provideClassNameCompletions(

0 commit comments

Comments
 (0)