improve support for tailwindcss v2

master
Brad Cornes 2020-11-19 17:34:59 +00:00
parent 0cce870c13
commit 2c05f5df3a
11 changed files with 24389 additions and 95 deletions

File diff suppressed because it is too large Load Diff

View File

@ -5,15 +5,15 @@
"preview": true, "preview": true,
"author": "Brad Cornes <hello@bradley.dev>", "author": "Brad Cornes <hello@bradley.dev>",
"license": "MIT", "license": "MIT",
"version": "0.4.3", "version": "0.5.0",
"homepage": "https://github.com/tailwindcss/intellisense", "homepage": "https://github.com/tailwindlabs/tailwindcss-intellisense",
"bugs": { "bugs": {
"url": "https://github.com/tailwindcss/intellisense/issues", "url": "https://github.com/tailwindlabs/tailwindcss-intellisense/issues",
"email": "hello@bradley.dev" "email": "hello@bradley.dev"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/tailwindcss/intellisense.git" "url": "https://github.com/tailwindlabs/tailwindcss-intellisense.git"
}, },
"publisher": "bradlc", "publisher": "bradlc",
"keywords": [ "keywords": [
@ -177,6 +177,7 @@
"@types/node": "^13.9.3", "@types/node": "^13.9.3",
"@types/vscode": "^1.32.0", "@types/vscode": "^1.32.0",
"@zeit/ncc": "^0.22.0", "@zeit/ncc": "^0.22.0",
"bufferutil": "^4.0.2",
"callsite": "^1.0.0", "callsite": "^1.0.0",
"chokidar": "^3.3.1", "chokidar": "^3.3.1",
"debounce": "^1.2.0", "debounce": "^1.2.0",
@ -196,11 +197,12 @@
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"semver": "^7.3.2", "semver": "^7.3.2",
"stack-trace": "0.0.10", "stack-trace": "0.0.10",
"tailwindcss-language-service": "0.0.3", "tailwindcss-language-service": "0.0.4",
"terser": "^4.6.12", "terser": "^4.6.12",
"tiny-invariant": "^1.1.0", "tiny-invariant": "^1.1.0",
"tslint": "^5.16.0", "tslint": "^5.16.0",
"typescript": "^3.8.3", "typescript": "^3.8.3",
"utf-8-validate": "^5.0.3",
"vsce": "^1.76.1", "vsce": "^1.76.1",
"vscode-languageclient": "^6.1.3", "vscode-languageclient": "^6.1.3",
"vscode-languageserver": "^6.1.1", "vscode-languageserver": "^6.1.1",

View File

@ -1,6 +1,4 @@
import selectorParser from 'postcss-selector-parser' import selectorParser from 'postcss-selector-parser'
import fs from 'fs'
import path from 'path'
import dset from 'dset' import dset from 'dset'
import dlv from 'dlv' import dlv from 'dlv'
@ -83,48 +81,43 @@ async function process(groups) {
const contextKeys = baseKeys.slice(0, baseKeys.length - 1) const contextKeys = baseKeys.slice(0, baseKeys.length - 1)
const index = [] const index = []
const existing = dlv(tree, baseKeys) const existing = dlv(tree, [...baseKeys, '__info'])
if (typeof existing !== 'undefined') { if (typeof existing !== 'undefined') {
if (Array.isArray(existing)) { 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) index.push(existing.length)
}
} else { } else {
if ( dset(tree, [...baseKeys, '__info'], [existing])
existing.__scope !== classNames[i].scope ||
!arraysEqual(existing.__context, context)
) {
dset(tree, baseKeys, [existing])
keys.unshift(1)
index.push(1) index.push(1)
} }
} }
}
if (classNames[i].__rule) { if (classNames[i].__rule) {
dset(tree, [...baseKeys, ...index, '__rule'], true) dset(tree, [...baseKeys, '__info', ...index, '__rule'], true)
dset(tree, [...baseKeys, ...index, '__source'], group.source)
dsetEach(tree, [...baseKeys, ...index], decls)
}
dset(tree, [...baseKeys, ...index, '__pseudo'], classNames[i].__pseudo)
dset(tree, [...baseKeys, ...index, '__scope'], classNames[i].scope)
dset( dset(
tree, tree,
[...baseKeys, ...index, '__context'], [...baseKeys, '__info', ...index, '__source'],
group.source
)
dsetEach(tree, [...baseKeys, '__info', ...index], decls)
}
dset(
tree,
[...baseKeys, '__info', ...index, '__pseudo'],
classNames[i].__pseudo
)
dset(
tree,
[...baseKeys, '__info', ...index, '__scope'],
classNames[i].scope
)
dset(
tree,
[...baseKeys, '__info', ...index, '__context'],
context.concat([]).reverse() context.concat([]).reverse()
) )
// common context // common context
context.push(...classNames[i].__pseudo) context.push(...classNames[i].__pseudo.map((x) => `&${x}`))
for (let i = 0; i < contextKeys.length; i++) { for (let i = 0; i < contextKeys.length; i++) {
if (typeof commonContext[contextKeys[i]] === 'undefined') { if (typeof commonContext[contextKeys[i]] === 'undefined') {

View File

@ -1,11 +1,11 @@
{ {
"name": "tailwindcss-language-service", "name": "tailwindcss-language-service",
"version": "0.0.3", "version": "0.0.4",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"version": "0.0.3", "version": "0.0.4",
"dependencies": { "dependencies": {
"@ctrl/tinycolor": "^3.1.4", "@ctrl/tinycolor": "^3.1.4",
"@types/moo": "^0.5.3", "@types/moo": "^0.5.3",

View File

@ -1,6 +1,6 @@
{ {
"name": "tailwindcss-language-service", "name": "tailwindcss-language-service",
"version": "0.0.3", "version": "0.0.4",
"main": "dist/index.js", "main": "dist/index.js",
"typings": "dist/index.d.ts", "typings": "dist/index.d.ts",
"files": [ "files": [

View File

@ -57,7 +57,10 @@ function completionsFromClassList(
for (let i = parts.length - 1; i > 0; i--) { for (let i = parts.length - 1; i > 0; i--) {
let keys = parts.slice(0, i).filter(Boolean) let keys = parts.slice(0, i).filter(Boolean)
subset = dlv(state.classNames.classNames, keys) subset = dlv(state.classNames.classNames, keys)
if (typeof subset !== 'undefined' && typeof subset.__rule === 'undefined') { if (
typeof subset !== 'undefined' &&
typeof dlv(subset, ['__info', '__rule']) === 'undefined'
) {
isSubset = true isSubset = true
subsetKey = keys subsetKey = keys
replacementRange = { replacementRange = {
@ -74,21 +77,45 @@ function completionsFromClassList(
} }
} }
// console.log(Object.keys(isSubset ? subset : state.classNames.classNames))
return { return {
isIncomplete: false, isIncomplete: false,
items: Object.keys(isSubset ? subset : state.classNames.classNames) items: Object.keys(isSubset ? subset : state.classNames.classNames)
.filter((k) => k !== '__info')
.filter((className) => isContextItem(state, [...subsetKey, className]))
.map(
(className, index): CompletionItem => {
return {
label: className + sep,
kind: 9,
documentation: null,
command: {
title: '',
command: 'editor.action.triggerSuggest',
},
sortText: '-' + naturalExpand(index),
data: [...subsetKey, className],
textEdit: {
newText: className + sep,
range: replacementRange,
},
}
}
)
.concat(
Object.keys(isSubset ? subset : state.classNames.classNames)
.filter((className) =>
dlv(state.classNames.classNames, [
...subsetKey,
className,
'__info',
])
)
.map((className, index) => { .map((className, index) => {
let label = className
let kind: CompletionItemKind = 21 let kind: CompletionItemKind = 21
let documentation: string = null let documentation: string = null
let command: any
let sortText = naturalExpand(index)
if (isContextItem(state, [...subsetKey, className])) {
kind = 9
command = { title: '', command: 'editor.action.triggerSuggest' }
label += sep
sortText = '-' + sortText // move to top
} else {
const color = getColor(state, [className]) const color = getColor(state, [className])
if (color !== null) { if (color !== null) {
kind = 16 kind = 16
@ -96,28 +123,29 @@ function completionsFromClassList(
documentation = color.toRgbString() documentation = color.toRgbString()
} }
} }
}
const item = { return {
label, label: className,
kind, kind,
documentation, documentation,
command, sortText: naturalExpand(index),
sortText,
data: [...subsetKey, className], data: [...subsetKey, className],
textEdit: { textEdit: {
newText: label, newText: className,
range: replacementRange, range: replacementRange,
}, },
} }
if (filter && !filter(item)) {
return null
}
return item
}) })
.filter((item) => item !== null), )
.filter((item) => {
if (item === null) {
return false
}
if (filter && !filter(item)) {
return false
}
return true
}),
} }
} }
@ -199,7 +227,10 @@ function provideAtApplyCompletions(
}, },
(item) => { (item) => {
if (item.kind === 9) { if (item.kind === 9) {
return semver.gte(state.version, '2.0.0-alpha.1') || flagEnabled(state, 'applyComplexClasses') return (
semver.gte(state.version, '2.0.0-alpha.1') ||
flagEnabled(state, 'applyComplexClasses')
)
} }
let validated = validateApply(state, item.data) let validated = validateApply(state, item.data)
return validated !== null && validated.isApplyable === true return validated !== null && validated.isApplyable === true
@ -708,8 +739,8 @@ export function resolveCompletionItem(
return item return item
} }
const className = dlv(state.classNames.classNames, item.data) const className = dlv(state.classNames.classNames, [...item.data, '__info'])
if (isContextItem(state, item.data)) { if (item.kind === 9) {
item.detail = state.classNames.context[ item.detail = state.classNames.context[
item.data[item.data.length - 1] item.data[item.data.length - 1]
].join(', ') ].join(', ')
@ -729,13 +760,19 @@ export function resolveCompletionItem(
} }
function isContextItem(state: State, keys: string[]): boolean { function isContextItem(state: State, keys: string[]): boolean {
const item = dlv(state.classNames.classNames, keys) const item = dlv(state.classNames.classNames, [keys])
return Boolean(
isObject(item) && if (!isObject(item)) {
!item.__rule && return false
!Array.isArray(item) && }
state.classNames.context[keys[keys.length - 1]] if (!state.classNames.context[keys[keys.length - 1]]) {
) return false
}
if (Object.keys(item).filter((x) => x !== '__info').length > 0) {
return true
}
return isObject(item.__info) && !item.__info.__rule
} }
function stringifyDecls(obj: any): string { function stringifyDecls(obj: any): string {

View File

@ -89,13 +89,17 @@ function provideClassNameHover(
} }
} }
const css = stringifyCss(
className.className,
dlv(state.classNames.classNames, [...parts, '__info'])
)
if (!css) return null
return { return {
contents: { contents: {
language: 'css', language: 'css',
value: stringifyCss( value: css,
className.className,
dlv(state.classNames.classNames, parts)
),
}, },
range: className.range, range: className.range,
} }

View File

@ -29,7 +29,7 @@ export function getColor(
state: State, state: State,
keys: string[] keys: string[]
): TinyColor | string | null { ): TinyColor | string | null {
const item = dlv(state.classNames.classNames, keys) const item = dlv(state.classNames.classNames, [...keys, '__info'])
if (!item.__rule) return null if (!item.__rule) return null
const props = Object.keys(removeMeta(item)) const props = Object.keys(removeMeta(item))
if (props.length === 0) return null if (props.length === 0) return null

View File

@ -8,8 +8,9 @@ export function getClassNameParts(state: State, className: string): string[] {
let parts: string[] = className.split(separator) let parts: string[] = className.split(separator)
if (parts.length === 1) { if (parts.length === 1) {
return dlv(state.classNames.classNames, [className, '__rule']) === true || return dlv(state.classNames.classNames, [className, '__info', '__rule']) ===
Array.isArray(dlv(state.classNames.classNames, [className])) true ||
Array.isArray(dlv(state.classNames.classNames, [className, '__info']))
? [className] ? [className]
: null : null
} }
@ -34,8 +35,8 @@ export function getClassNameParts(state: State, className: string): string[] {
return possibilities.find((key) => { return possibilities.find((key) => {
if ( if (
dlv(state.classNames.classNames, [...key, '__rule']) === true || dlv(state.classNames.classNames, [...key, '__info', '__rule']) === true ||
Array.isArray(dlv(state.classNames.classNames, [...key])) Array.isArray(dlv(state.classNames.classNames, [...key, '__info']))
) { ) {
return true return true
} }

View File

@ -10,7 +10,7 @@ export function getClassNameDecls(
const parts = getClassNameParts(state, className) const parts = getClassNameParts(state, className)
if (!parts) return null if (!parts) return null
const info = dlv(state.classNames.classNames, parts) const info = dlv(state.classNames.classNames, [...parts, '__info'])
if (Array.isArray(info)) { if (Array.isArray(info)) {
return info.map(removeMeta) return info.map(removeMeta)

View File

@ -10,7 +10,7 @@ export function getClassNameMeta(
? classNameOrParts ? classNameOrParts
: getClassNameParts(state, classNameOrParts) : getClassNameParts(state, classNameOrParts)
if (!parts) return null if (!parts) return null
const info = dlv(state.classNames.classNames, parts) const info = dlv(state.classNames.classNames, [...parts, '__info'])
if (Array.isArray(info)) { if (Array.isArray(info)) {
return info.map((i) => ({ return info.map((i) => ({