Home

tailwind-ctp-intellisense @76cbaa494871177cf7e4239fcbf5b053dddae77c - refs - log -
-
https://git.jolheiser.com/tailwind-ctp-intellisense.git
Tailwind intellisense + Catppuccin
tailwind-ctp-intellisense / packages / tailwindcss-language-service / src / util / getVariantsFromClassName.ts
- raw
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
import { State } from './state'
import * as jit from './jit'

export function getVariantsFromClassName(
  state: State,
  className: string
): { variants: string[]; offset: number } {
  let allVariants = Object.keys(state.variants)
  let parts = Array.from(splitAtTopLevelOnly(className, state.separator)).filter(Boolean)
  let variants = new Set<string>()
  let offset = 0

  for (let part of parts) {
    if (
      allVariants.includes(part) ||
      (state.jit &&
        part.startsWith('[') &&
        part.endsWith(']') &&
        jit.generateRules(state, [`${part}${state.separator}[color:red]`]).rules.length > 0)
    ) {
      variants.add(part)
      offset += part.length + state.separator.length
      continue
    }

    break
  }

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