filter @apply completions
parent
ef1f922c86
commit
c9fea34deb
|
@ -46,99 +46,102 @@ function getClassNamesFromSelector(selector) {
|
|||
return classNames
|
||||
}
|
||||
|
||||
async function process(ast) {
|
||||
async function process(groups) {
|
||||
const tree = {}
|
||||
const commonContext = {}
|
||||
|
||||
ast.root.walkRules((rule) => {
|
||||
const classNames = getClassNamesFromSelector(rule.selector)
|
||||
groups.forEach((group) => {
|
||||
group.root.walkRules((rule) => {
|
||||
const classNames = getClassNamesFromSelector(rule.selector)
|
||||
|
||||
const decls = {}
|
||||
rule.walkDecls((decl) => {
|
||||
if (decls[decl.prop]) {
|
||||
decls[decl.prop] = [
|
||||
...(Array.isArray(decls[decl.prop])
|
||||
? decls[decl.prop]
|
||||
: [decls[decl.prop]]),
|
||||
decl.value,
|
||||
]
|
||||
} else {
|
||||
decls[decl.prop] = decl.value
|
||||
const decls = {}
|
||||
rule.walkDecls((decl) => {
|
||||
if (decls[decl.prop]) {
|
||||
decls[decl.prop] = [
|
||||
...(Array.isArray(decls[decl.prop])
|
||||
? decls[decl.prop]
|
||||
: [decls[decl.prop]]),
|
||||
decl.value,
|
||||
]
|
||||
} else {
|
||||
decls[decl.prop] = decl.value
|
||||
}
|
||||
})
|
||||
|
||||
let p = rule
|
||||
const keys = []
|
||||
while (p.parent.type !== 'root') {
|
||||
p = p.parent
|
||||
if (p.type === 'atrule') {
|
||||
keys.push(`@${p.name} ${p.params}`)
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < classNames.length; i++) {
|
||||
const context = keys.concat([])
|
||||
const baseKeys = classNames[i].className.split('__TAILWIND_SEPARATOR__')
|
||||
const contextKeys = baseKeys.slice(0, baseKeys.length - 1)
|
||||
const index = []
|
||||
|
||||
const existing = dlv(tree, baseKeys)
|
||||
if (typeof existing !== 'undefined') {
|
||||
if (Array.isArray(existing)) {
|
||||
const scopeIndex = existing.findIndex(
|
||||
(x) =>
|
||||
x.__scope === classNames[i].scope &&
|
||||
arraysEqual(existing.__context, context)
|
||||
)
|
||||
if (scopeIndex > -1) {
|
||||
keys.unshift(scopeIndex)
|
||||
index.push(scopeIndex)
|
||||
} else {
|
||||
keys.unshift(existing.length)
|
||||
index.push(existing.length)
|
||||
}
|
||||
} else {
|
||||
if (
|
||||
existing.__scope !== classNames[i].scope ||
|
||||
!arraysEqual(existing.__context, context)
|
||||
) {
|
||||
dset(tree, baseKeys, [existing])
|
||||
keys.unshift(1)
|
||||
index.push(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (classNames[i].__rule) {
|
||||
dset(tree, [...baseKeys, ...index, '__rule'], true)
|
||||
dset(tree, [...baseKeys, ...index, '__source'], group.source)
|
||||
|
||||
dsetEach(tree, [...baseKeys, ...index], decls)
|
||||
}
|
||||
if (classNames[i].__pseudo) {
|
||||
dset(tree, [...baseKeys, '__pseudo'], classNames[i].__pseudo)
|
||||
}
|
||||
dset(tree, [...baseKeys, ...index, '__scope'], classNames[i].scope)
|
||||
dset(
|
||||
tree,
|
||||
[...baseKeys, ...index, '__context'],
|
||||
context.concat([]).reverse()
|
||||
)
|
||||
|
||||
// common context
|
||||
if (classNames[i].__pseudo) {
|
||||
context.push(...classNames[i].__pseudo)
|
||||
}
|
||||
|
||||
for (let i = 0; i < contextKeys.length; i++) {
|
||||
if (typeof commonContext[contextKeys[i]] === 'undefined') {
|
||||
commonContext[contextKeys[i]] = context
|
||||
} else {
|
||||
commonContext[contextKeys[i]] = intersection(
|
||||
commonContext[contextKeys[i]],
|
||||
context
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
let p = rule
|
||||
const keys = []
|
||||
while (p.parent.type !== 'root') {
|
||||
p = p.parent
|
||||
if (p.type === 'atrule') {
|
||||
keys.push(`@${p.name} ${p.params}`)
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < classNames.length; i++) {
|
||||
const context = keys.concat([])
|
||||
const baseKeys = classNames[i].className.split('__TAILWIND_SEPARATOR__')
|
||||
const contextKeys = baseKeys.slice(0, baseKeys.length - 1)
|
||||
const index = []
|
||||
|
||||
const existing = dlv(tree, baseKeys)
|
||||
if (typeof existing !== 'undefined') {
|
||||
if (Array.isArray(existing)) {
|
||||
const scopeIndex = existing.findIndex(
|
||||
(x) =>
|
||||
x.__scope === classNames[i].scope &&
|
||||
arraysEqual(existing.__context, context)
|
||||
)
|
||||
if (scopeIndex > -1) {
|
||||
keys.unshift(scopeIndex)
|
||||
index.push(scopeIndex)
|
||||
} else {
|
||||
keys.unshift(existing.length)
|
||||
index.push(existing.length)
|
||||
}
|
||||
} else {
|
||||
if (
|
||||
existing.__scope !== classNames[i].scope ||
|
||||
!arraysEqual(existing.__context, context)
|
||||
) {
|
||||
dset(tree, baseKeys, [existing])
|
||||
keys.unshift(1)
|
||||
index.push(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (classNames[i].__rule) {
|
||||
dset(tree, [...baseKeys, ...index, '__rule'], true)
|
||||
|
||||
dsetEach(tree, [...baseKeys, ...index], decls)
|
||||
}
|
||||
if (classNames[i].__pseudo) {
|
||||
dset(tree, [...baseKeys, '__pseudo'], classNames[i].__pseudo)
|
||||
}
|
||||
dset(tree, [...baseKeys, ...index, '__scope'], classNames[i].scope)
|
||||
dset(
|
||||
tree,
|
||||
[...baseKeys, ...index, '__context'],
|
||||
context.concat([]).reverse()
|
||||
)
|
||||
|
||||
// common context
|
||||
if (classNames[i].__pseudo) {
|
||||
context.push(...classNames[i].__pseudo)
|
||||
}
|
||||
|
||||
for (let i = 0; i < contextKeys.length; i++) {
|
||||
if (typeof commonContext[contextKeys[i]] === 'undefined') {
|
||||
commonContext[contextKeys[i]] = context
|
||||
} else {
|
||||
commonContext[contextKeys[i]] = intersection(
|
||||
commonContext[contextKeys[i]],
|
||||
context
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return { classNames: tree, context: commonContext }
|
||||
|
|
|
@ -83,12 +83,12 @@ export default async function getClassNames(
|
|||
}
|
||||
hook.unwatch()
|
||||
|
||||
const ast = await postcss([tailwindcss(configPath)]).process(
|
||||
`
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`,
|
||||
{ from: undefined }
|
||||
const [components, utilities] = await Promise.all(
|
||||
['components', 'utilities'].map((group) =>
|
||||
postcss([tailwindcss(configPath)]).process(`@tailwind ${group};`, {
|
||||
from: undefined,
|
||||
})
|
||||
)
|
||||
)
|
||||
|
||||
hook.unhook()
|
||||
|
@ -111,7 +111,10 @@ export default async function getClassNames(
|
|||
configPath,
|
||||
config: resolvedConfig,
|
||||
separator: typeof userSeperator === 'undefined' ? ':' : userSeperator,
|
||||
classNames: await extractClassNames(ast),
|
||||
classNames: await extractClassNames([
|
||||
{ root: components.root, source: 'components' },
|
||||
{ root: utilities.root, source: 'utilities' },
|
||||
]),
|
||||
dependencies: hook.deps,
|
||||
plugins: getPlugins(config),
|
||||
variants: getVariants({ config, version, postcss, browserslist }),
|
||||
|
|
|
@ -28,7 +28,8 @@ import { ensureArray } from '../../util/array'
|
|||
function completionsFromClassList(
|
||||
state: State,
|
||||
classList: string,
|
||||
classListRange: Range
|
||||
classListRange: Range,
|
||||
filter?: (item: CompletionItem) => boolean
|
||||
): CompletionList {
|
||||
let classNames = classList.split(/[\s+]/)
|
||||
const partialClassName = classNames[classNames.length - 1]
|
||||
|
@ -68,8 +69,8 @@ function completionsFromClassList(
|
|||
|
||||
return {
|
||||
isIncomplete: false,
|
||||
items: Object.keys(isSubset ? subset : state.classNames.classNames).map(
|
||||
(className, index) => {
|
||||
items: Object.keys(isSubset ? subset : state.classNames.classNames)
|
||||
.map((className, index) => {
|
||||
let label = className
|
||||
let kind: CompletionItemKind = CompletionItemKind.Constant
|
||||
let documentation: string = null
|
||||
|
@ -88,7 +89,7 @@ function completionsFromClassList(
|
|||
}
|
||||
}
|
||||
|
||||
return {
|
||||
const item = {
|
||||
label,
|
||||
kind,
|
||||
documentation,
|
||||
|
@ -100,8 +101,14 @@ function completionsFromClassList(
|
|||
range: replacementRange,
|
||||
},
|
||||
}
|
||||
}
|
||||
),
|
||||
|
||||
if (filter && !filter(item)) {
|
||||
return null
|
||||
}
|
||||
|
||||
return item
|
||||
})
|
||||
.filter((item) => item !== null),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -175,13 +182,29 @@ function provideAtApplyCompletions(
|
|||
|
||||
const classList = match.groups.classList
|
||||
|
||||
return completionsFromClassList(state, classList, {
|
||||
start: {
|
||||
line: position.line,
|
||||
character: position.character - classList.length,
|
||||
return completionsFromClassList(
|
||||
state,
|
||||
classList,
|
||||
{
|
||||
start: {
|
||||
line: position.line,
|
||||
character: position.character - classList.length,
|
||||
},
|
||||
end: position,
|
||||
},
|
||||
end: position,
|
||||
})
|
||||
(item) => {
|
||||
// TODO: first line excludes all subtrees but there could _technically_ be
|
||||
// valid apply-able class names in there. Will be correct in 99% of cases
|
||||
if (item.kind === CompletionItemKind.Module) return false
|
||||
let info = dlv(state.classNames.classNames, item.data)
|
||||
return (
|
||||
!Array.isArray(info) &&
|
||||
info.__source === 'utilities' &&
|
||||
(info.__context || []).length === 0 &&
|
||||
(info.__pseudo || []).length === 0
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
function provideClassNameCompletions(
|
||||
|
|
Loading…
Reference in New Issue