Add support for arbitrary variants (#557)
* Support arbitrary variants * Bump typescript and types versionsmaster
parent
5516e3321c
commit
76cbaa4948
File diff suppressed because it is too large
Load Diff
|
@ -38,7 +38,7 @@
|
||||||
"@tailwindcss/typography": "0.5.0",
|
"@tailwindcss/typography": "0.5.0",
|
||||||
"@types/debounce": "1.2.0",
|
"@types/debounce": "1.2.0",
|
||||||
"@types/node": "14.14.34",
|
"@types/node": "14.14.34",
|
||||||
"@types/vscode": "1.60.0",
|
"@types/vscode": "1.67.0",
|
||||||
"builtin-modules": "3.2.0",
|
"builtin-modules": "3.2.0",
|
||||||
"chokidar": "3.5.1",
|
"chokidar": "3.5.1",
|
||||||
"color-name": "1.1.4",
|
"color-name": "1.1.4",
|
||||||
|
@ -65,7 +65,7 @@
|
||||||
"stack-trace": "0.0.10",
|
"stack-trace": "0.0.10",
|
||||||
"tailwindcss": "3.0.11",
|
"tailwindcss": "3.0.11",
|
||||||
"terser": "4.6.12",
|
"terser": "4.6.12",
|
||||||
"typescript": "4.2.4",
|
"typescript": "4.6.4",
|
||||||
"vscode-css-languageservice": "5.4.1",
|
"vscode-css-languageservice": "5.4.1",
|
||||||
"vscode-languageserver": "7.0.0",
|
"vscode-languageserver": "7.0.0",
|
||||||
"vscode-languageserver-textdocument": "1.0.1",
|
"vscode-languageserver-textdocument": "1.0.1",
|
||||||
|
|
|
@ -38,6 +38,6 @@
|
||||||
"prettier": "2.3.0",
|
"prettier": "2.3.0",
|
||||||
"tsdx": "0.14.1",
|
"tsdx": "0.14.1",
|
||||||
"tslib": "2.2.0",
|
"tslib": "2.2.0",
|
||||||
"typescript": "4.2.4"
|
"typescript": "4.6.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -364,10 +364,10 @@ async function provideClassAttributeCompletions(
|
||||||
try {
|
try {
|
||||||
let tokens = Array.from(lexer)
|
let tokens = Array.from(lexer)
|
||||||
let last = tokens[tokens.length - 1]
|
let last = tokens[tokens.length - 1]
|
||||||
if (last.type.startsWith('start') || last.type === 'classlist') {
|
if (last.type.startsWith('start') || last.type === 'classlist' || last.type.startsWith('arb')) {
|
||||||
let classList = ''
|
let classList = ''
|
||||||
for (let i = tokens.length - 1; i >= 0; i--) {
|
for (let i = tokens.length - 1; i >= 0; i--) {
|
||||||
if (tokens[i].type === 'classlist') {
|
if (tokens[i].type === 'classlist' || tokens[i].type.startsWith('arb')) {
|
||||||
classList = tokens[i].value + classList
|
classList = tokens[i].value + classList
|
||||||
} else {
|
} else {
|
||||||
break
|
break
|
||||||
|
|
|
@ -207,7 +207,7 @@ export async function findClassListsInHtmlRange(
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for (let token of lexer) {
|
for (let token of lexer) {
|
||||||
if (token.type === 'classlist') {
|
if (token.type === 'classlist' || token.type.startsWith('arb')) {
|
||||||
if (currentClassList) {
|
if (currentClassList) {
|
||||||
currentClassList.value += token.value
|
currentClassList.value += token.value
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,28 +1,94 @@
|
||||||
import { State } from './state'
|
import { State } from './state'
|
||||||
|
import * as jit from './jit'
|
||||||
|
|
||||||
export function getVariantsFromClassName(
|
export function getVariantsFromClassName(
|
||||||
state: State,
|
state: State,
|
||||||
className: string
|
className: string
|
||||||
): { variants: string[]; offset: number } {
|
): { variants: string[]; offset: number } {
|
||||||
let str = className
|
|
||||||
let allVariants = Object.keys(state.variants)
|
let allVariants = Object.keys(state.variants)
|
||||||
let allVariantsByLength = allVariants.sort((a, b) => b.length - a.length)
|
let parts = Array.from(splitAtTopLevelOnly(className, state.separator)).filter(Boolean)
|
||||||
let variants = new Set<string>()
|
let variants = new Set<string>()
|
||||||
let offset = 0
|
let offset = 0
|
||||||
|
|
||||||
while (str) {
|
for (let part of parts) {
|
||||||
let found = false
|
if (
|
||||||
for (let variant of allVariantsByLength) {
|
allVariants.includes(part) ||
|
||||||
if (str.startsWith(variant + state.separator)) {
|
(state.jit &&
|
||||||
variants.add(variant)
|
part.startsWith('[') &&
|
||||||
str = str.substr(variant.length + state.separator.length)
|
part.endsWith(']') &&
|
||||||
offset += variant.length + state.separator.length
|
jit.generateRules(state, [`${part}${state.separator}[color:red]`]).rules.length > 0)
|
||||||
found = true
|
) {
|
||||||
|
variants.add(part)
|
||||||
|
offset += part.length + state.separator.length
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (!found) str = ''
|
|
||||||
}
|
|
||||||
|
|
||||||
return { variants: Array.from(variants), offset }
|
return { variants: Array.from(variants), offset }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const REGEX_SPECIAL = /[\\^$.*+?()[\]{}|]/g
|
||||||
|
const REGEX_HAS_SPECIAL = RegExp(REGEX_SPECIAL.source)
|
||||||
|
|
||||||
|
function regexEscape(string: string): string {
|
||||||
|
return string && REGEX_HAS_SPECIAL.test(string)
|
||||||
|
? string.replace(REGEX_SPECIAL, '\\$&')
|
||||||
|
: string || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function* splitAtTopLevelOnly(input: string, separator: string): Generator<string> {
|
||||||
|
let SPECIALS = new RegExp(`[(){}\\[\\]${regexEscape(separator)}]`, 'g')
|
||||||
|
|
||||||
|
let depth = 0
|
||||||
|
let lastIndex = 0
|
||||||
|
let found = false
|
||||||
|
let separatorIndex = 0
|
||||||
|
let separatorStart = 0
|
||||||
|
let separatorLength = separator.length
|
||||||
|
|
||||||
|
// Find all paren-like things & character
|
||||||
|
// And only split on commas if they're top-level
|
||||||
|
for (let match of input.matchAll(SPECIALS)) {
|
||||||
|
let matchesSeparator = match[0] === separator[separatorIndex]
|
||||||
|
let atEndOfSeparator = separatorIndex === separatorLength - 1
|
||||||
|
let matchesFullSeparator = matchesSeparator && atEndOfSeparator
|
||||||
|
|
||||||
|
if (match[0] === '(') depth++
|
||||||
|
if (match[0] === ')') depth--
|
||||||
|
if (match[0] === '[') depth++
|
||||||
|
if (match[0] === ']') depth--
|
||||||
|
if (match[0] === '{') depth++
|
||||||
|
if (match[0] === '}') depth--
|
||||||
|
|
||||||
|
if (matchesSeparator && depth === 0) {
|
||||||
|
if (separatorStart === 0) {
|
||||||
|
separatorStart = match.index
|
||||||
|
}
|
||||||
|
|
||||||
|
separatorIndex++
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matchesFullSeparator && depth === 0) {
|
||||||
|
found = true
|
||||||
|
|
||||||
|
yield input.substring(lastIndex, separatorStart)
|
||||||
|
lastIndex = separatorStart + separatorLength
|
||||||
|
}
|
||||||
|
|
||||||
|
if (separatorIndex === separatorLength) {
|
||||||
|
separatorIndex = 0
|
||||||
|
separatorStart = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provide the last segment of the string if available
|
||||||
|
// Otherwise the whole string since no `char`s were found
|
||||||
|
// This mirrors the behavior of string.split()
|
||||||
|
if (found) {
|
||||||
|
yield input.substring(lastIndex)
|
||||||
|
} else {
|
||||||
|
yield input
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { lazy } from './lazy'
|
||||||
|
|
||||||
const classAttributeStates: () => { [x: string]: moo.Rules } = () => ({
|
const classAttributeStates: () => { [x: string]: moo.Rules } = () => ({
|
||||||
doubleClassList: {
|
doubleClassList: {
|
||||||
|
arb: { match: new RegExp('(?<!\\\\)\\['), push: 'arbitrary' },
|
||||||
lbrace: { match: new RegExp('(?<!\\\\)\\{'), push: 'interpBrace' },
|
lbrace: { match: new RegExp('(?<!\\\\)\\{'), push: 'interpBrace' },
|
||||||
rbrace: { match: new RegExp('(?<!\\\\)\\}'), pop: 1 },
|
rbrace: { match: new RegExp('(?<!\\\\)\\}'), pop: 1 },
|
||||||
end: { match: new RegExp('(?<!\\\\)"'), pop: 1 },
|
end: { match: new RegExp('(?<!\\\\)"'), pop: 1 },
|
||||||
|
@ -40,6 +41,11 @@ const classAttributeStates: () => { [x: string]: moo.Rules } = () => ({
|
||||||
double: { match: new RegExp('(?<!\\\\)"'), pop: 1 },
|
double: { match: new RegExp('(?<!\\\\)"'), pop: 1 },
|
||||||
text: { match: new RegExp('[\\s\\S]'), lineBreaks: true },
|
text: { match: new RegExp('[\\s\\S]'), lineBreaks: true },
|
||||||
},
|
},
|
||||||
|
arbitrary: {
|
||||||
|
arb: { match: new RegExp('(?<!\\\\)\\]'), pop: 1 },
|
||||||
|
space: { match: /\s/, pop: 1, lineBreaks: true },
|
||||||
|
arb2: { match: new RegExp('[\\s\\S]'), lineBreaks: true },
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const simpleClassAttributeStates: { [x: string]: moo.Rules } = {
|
const simpleClassAttributeStates: { [x: string]: moo.Rules } = {
|
||||||
|
|
|
@ -321,7 +321,7 @@
|
||||||
"check": "tsc --noEmit"
|
"check": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/vscode": "1.60.0",
|
"@types/vscode": "1.67.0",
|
||||||
"color-name": "1.1.4",
|
"color-name": "1.1.4",
|
||||||
"concurrently": "7.0.0",
|
"concurrently": "7.0.0",
|
||||||
"rimraf": "3.0.2",
|
"rimraf": "3.0.2",
|
||||||
|
|
Loading…
Reference in New Issue