Add support for arbitrary variants (#557)

* Support arbitrary variants

* Bump typescript and types versions
master
Brad Cornes 2022-05-26 11:31:22 +01:00 committed by GitHub
parent 5516e3321c
commit 76cbaa4948
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 1234 additions and 966 deletions

2088
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -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",

View File

@ -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"
} }
} }

View File

@ -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

View File

@ -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 {

View File

@ -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 ) {
break variants.add(part)
} offset += part.length + state.separator.length
continue
} }
if (!found) str = ''
break
} }
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
}
}

View File

@ -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 } = {

View File

@ -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",