Home

tailwind-ctp-intellisense @master - refs - log -
-
https://git.jolheiser.com/tailwind-ctp-intellisense.git
Tailwind intellisense + Catppuccin
tree log patch
Adopt `getVariants` API
Brad Cornes <hello@bradley.dev>
2 years ago
6 changed files, 281 additions(+), 126 deletions(-)
packages/tailwindcss-language-server/src/server.tspackages/tailwindcss-language-service/src/completionProvider.tspackages/tailwindcss-language-service/src/diagnostics/getInvalidVariantDiagnostics.tspackages/tailwindcss-language-service/src/util/getVariantsFromClassName.tspackages/tailwindcss-language-service/src/util/state.tspackages/vscode-tailwindcss/src/extension.ts
M packages/tailwindcss-language-server/src/server.tspackages/tailwindcss-language-server/src/server.ts
  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
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
diff --git a/packages/tailwindcss-language-server/src/server.ts b/packages/tailwindcss-language-server/src/server.ts
index d98ab34cbba69391205ffaf7f6be1f5b49e83546..008c221a7744fb579ca9a8887c75c87a9916755c 100644
--- a/packages/tailwindcss-language-server/src/server.ts
+++ b/packages/tailwindcss-language-server/src/server.ts
@@ -63,6 +63,7 @@   State,
   FeatureFlags,
   Settings,
   ClassNames,
+  Variant,
 } from 'tailwindcss-language-service/src/util/state'
 import {
   provideDiagnostics,
@@ -1181,105 +1182,117 @@ function isAtRule(node: Node): node is AtRule {
   return node.type === 'atrule'
 }
 
-function getVariants(state: State): Record<string, string> {
-  if (state.jit) {
-    function escape(className: string): string {
-      let node = state.modules.postcssSelectorParser.module.className()
-      node.value = className
-      return dlv(node, 'raws.value', node.value)
-    }
+function getVariants(state: State): Array<Variant> {
+  if (state.jitContext?.getVariants) {
+    return state.jitContext.getVariants()
+  }
 
-    let result = {}
+  if (state.jit) {
+    let result: Array<Variant> = []
     // [name, [sort, fn]]
     // [name, [[sort, fn]]]
     Array.from(state.jitContext.variantMap as Map<string, [any, any]>).forEach(
       ([variantName, variantFnOrFns]) => {
-        let fns = (Array.isArray(variantFnOrFns[0]) ? variantFnOrFns : [variantFnOrFns]).map(
-          ([_sort, fn]) => fn
-        )
+        result.push({
+          name: variantName,
+          values: [],
+          isArbitrary: false,
+          hasDash: true,
+          selectors: () => {
+            function escape(className: string): string {
+              let node = state.modules.postcssSelectorParser.module.className()
+              node.value = className
+              return dlv(node, 'raws.value', node.value)
+            }
 
-        let placeholder = '__variant_placeholder__'
+            let fns = (Array.isArray(variantFnOrFns[0]) ? variantFnOrFns : [variantFnOrFns]).map(
+              ([_sort, fn]) => fn
+            )
 
-        let root = state.modules.postcss.module.root({
-          nodes: [
-            state.modules.postcss.module.rule({
-              selector: `.${escape(placeholder)}`,
-              nodes: [],
-            }),
-          ],
-        })
+            let placeholder = '__variant_placeholder__'
 
-        let classNameParser = state.modules.postcssSelectorParser.module((selectors) => {
-          return selectors.first.filter(({ type }) => type === 'class').pop().value
-        })
+            let root = state.modules.postcss.module.root({
+              nodes: [
+                state.modules.postcss.module.rule({
+                  selector: `.${escape(placeholder)}`,
+                  nodes: [],
+                }),
+              ],
+            })
 
-        function getClassNameFromSelector(selector) {
-          return classNameParser.transformSync(selector)
-        }
+            let classNameParser = state.modules.postcssSelectorParser.module((selectors) => {
+              return selectors.first.filter(({ type }) => type === 'class').pop().value
+            })
 
-        function modifySelectors(modifierFunction) {
-          root.each((rule) => {
-            if (rule.type !== 'rule') {
-              return
+            function getClassNameFromSelector(selector) {
+              return classNameParser.transformSync(selector)
             }
 
-            rule.selectors = rule.selectors.map((selector) => {
-              return modifierFunction({
-                get className() {
-                  return getClassNameFromSelector(selector)
-                },
-                selector,
+            function modifySelectors(modifierFunction) {
+              root.each((rule) => {
+                if (rule.type !== 'rule') {
+                  return
+                }
+
+                rule.selectors = rule.selectors.map((selector) => {
+                  return modifierFunction({
+                    get className() {
+                      return getClassNameFromSelector(selector)
+                    },
+                    selector,
+                  })
+                })
               })
-            })
-          })
-          return root
-        }
+              return root
+            }
 
-        let definitions = []
+            let definitions = []
 
-        for (let fn of fns) {
-          let definition: string
-          let container = root.clone()
-          let returnValue = fn({
-            container,
-            separator: state.separator,
-            modifySelectors,
-            format: (def: string) => {
-              definition = def.replace(/:merge\(([^)]+)\)/g, '$1')
-            },
-            wrap: (rule: Container) => {
-              if (isAtRule(rule)) {
-                definition = `@${rule.name} ${rule.params}`
-              }
-            },
-          })
+            for (let fn of fns) {
+              let definition: string
+              let container = root.clone()
+              let returnValue = fn({
+                container,
+                separator: state.separator,
+                modifySelectors,
+                format: (def: string) => {
+                  definition = def.replace(/:merge\(([^)]+)\)/g, '$1')
+                },
+                wrap: (rule: Container) => {
+                  if (isAtRule(rule)) {
+                    definition = `@${rule.name} ${rule.params}`
+                  }
+                },
+              })
 
-          if (!definition) {
-            definition = returnValue
-          }
+              if (!definition) {
+                definition = returnValue
+              }
 
-          if (definition) {
-            definitions.push(definition)
-            continue
-          }
+              if (definition) {
+                definitions.push(definition)
+                continue
+              }
 
-          container.walkDecls((decl) => {
-            decl.remove()
-          })
+              container.walkDecls((decl) => {
+                decl.remove()
+              })
 
-          definition = container
-            .toString()
-            .replace(`.${escape(`${variantName}:${placeholder}`)}`, '&')
-            .replace(/(?<!\\)[{}]/g, '')
-            .replace(/\s*\n\s*/g, ' ')
-            .trim()
+              definition = container
+                .toString()
+                .replace(`.${escape(`${variantName}:${placeholder}`)}`, '&')
+                .replace(/(?<!\\)[{}]/g, '')
+                .replace(/\s*\n\s*/g, ' ')
+                .trim()
 
-          if (!definition.includes(placeholder)) {
-            definitions.push(definition)
-          }
-        }
+              if (!definition.includes(placeholder)) {
+                definitions.push(definition)
+              }
+            }
 
-        result[variantName] = definitions.join(', ') || null
+            return definitions
+          },
+        })
       }
     )
 
@@ -1311,7 +1324,13 @@       },
     })
   })
 
-  return variants.reduce((obj, variant) => ({ ...obj, [variant]: null }), {})
+  return variants.map((variant) => ({
+    name: variant,
+    values: [],
+    isArbitrary: false,
+    hasDash: true,
+    selectors: () => [],
+  }))
 }
 
 async function getPlugins(config: any) {
M packages/tailwindcss-language-service/src/completionProvider.tspackages/tailwindcss-language-service/src/completionProvider.ts
  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
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
diff --git a/packages/tailwindcss-language-service/src/completionProvider.ts b/packages/tailwindcss-language-service/src/completionProvider.ts
index 941554f165372cf461f94d82d5f1a7f4aa9f6c4c..adacd136a9a752e0df6731784e1a5fbf05345fd8 100644
--- a/packages/tailwindcss-language-service/src/completionProvider.ts
+++ b/packages/tailwindcss-language-service/src/completionProvider.ts
@@ -1,4 +1,4 @@
-import { Settings, State } from './util/state'
+import { Settings, State, Variant } from './util/state'
 import type {
   CompletionItem,
   CompletionItemKind,
@@ -110,7 +110,6 @@         }
       }
     }
 
-    let allVariants = Object.keys(state.variants)
     let { variants: existingVariants, offset } = getVariantsFromClassName(state, partialClassName)
 
     replacementRange.start.character += offset
@@ -123,55 +122,109 @@
     let items: CompletionItem[] = []
 
     if (!important) {
-      let shouldSortVariants = !semver.gte(state.version, '2.99.0')
+      let variantOrder = 0
+
+      function variantItem(
+        item: Omit<CompletionItem, 'kind' | 'data' | 'sortText' | 'textEdit'> & {
+          textEdit?: { newText: string; range?: Range }
+        }
+      ): CompletionItem {
+        return {
+          kind: 9,
+          data: 'variant',
+          command:
+            item.insertTextFormat === 2 // Snippet
+              ? undefined
+              : {
+                  title: '',
+                  command: 'editor.action.triggerSuggest',
+                },
+          sortText: '-' + naturalExpand(variantOrder++),
+          ...item,
+          textEdit: {
+            newText: item.label,
+            range: replacementRange,
+            ...item.textEdit,
+          },
+        }
+      }
 
       items.push(
-        ...Object.entries(state.variants)
-          .filter(([variant]) => !existingVariants.includes(variant))
-          .map(([variant, definition], index) => {
-            let resultingVariants = [...existingVariants, variant]
+        ...state.variants.flatMap((variant) => {
+          let items: CompletionItem[] = []
+
+          if (variant.isArbitrary) {
+            items.push(
+              variantItem({
+                label: `${variant.name}${variant.hasDash ? '-' : ''}[]${sep}`,
+                insertTextFormat: 2,
+                textEdit: {
+                  newText: `${variant.name}-[\${1:&}]${sep}\${0}`,
+                },
+                // command: {
+                //   title: '',
+                //   command: 'tailwindCSS.onInsertArbitraryVariantSnippet',
+                //   arguments: [variant.name, replacementRange],
+                // },
+              })
+            )
+          } else if (!existingVariants.includes(variant.name)) {
+            let shouldSortVariants = !semver.gte(state.version, '2.99.0')
+            let resultingVariants = [...existingVariants, variant.name]
 
             if (shouldSortVariants) {
+              let allVariants = state.variants.map(({ name }) => name)
               resultingVariants = resultingVariants.sort(
                 (a, b) => allVariants.indexOf(b) - allVariants.indexOf(a)
               )
             }
 
-            return {
-              label: variant + sep,
-              kind: 9,
-              detail: definition,
-              data: 'variant',
-              command: {
-                title: '',
-                command: 'editor.action.triggerSuggest',
-              },
-              sortText: '-' + naturalExpand(index),
-              textEdit: {
-                newText: resultingVariants[resultingVariants.length - 1] + sep,
-                range: replacementRange,
-              },
-              additionalTextEdits:
-                shouldSortVariants && resultingVariants.length > 1
-                  ? [
-                      {
-                        newText:
-                          resultingVariants.slice(0, resultingVariants.length - 1).join(sep) + sep,
-                        range: {
-                          start: {
-                            ...classListRange.start,
-                            character: classListRange.end.character - partialClassName.length,
-                          },
-                          end: {
-                            ...replacementRange.start,
-                            character: replacementRange.start.character,
+            items.push(
+              variantItem({
+                label: `${variant.name}${sep}`,
+                detail: variant.selectors().join(', '),
+                textEdit: {
+                  newText: resultingVariants[resultingVariants.length - 1] + sep,
+                },
+                additionalTextEdits:
+                  shouldSortVariants && resultingVariants.length > 1
+                    ? [
+                        {
+                          newText:
+                            resultingVariants.slice(0, resultingVariants.length - 1).join(sep) +
+                            sep,
+                          range: {
+                            start: {
+                              ...classListRange.start,
+                              character: classListRange.end.character - partialClassName.length,
+                            },
+                            end: {
+                              ...replacementRange.start,
+                              character: replacementRange.start.character,
+                            },
                           },
                         },
-                      },
-                    ]
-                  : [],
-            } as CompletionItem
-          })
+                      ]
+                    : [],
+              })
+            )
+          }
+
+          if (variant.values.length) {
+            items.push(
+              ...variant.values
+                .filter((value) => !existingVariants.includes(`${variant.name}-${value}`))
+                .map((value) =>
+                  variantItem({
+                    label: `${variant.name}${variant.hasDash ? '-' : ''}${value}${sep}`,
+                    detail: variant.selectors({ value }).join(', '),
+                  })
+                )
+            )
+          }
+
+          return items
+        })
       )
     }
 
@@ -790,7 +843,12 @@   const parts = match.groups.partial.split(/\s*,\s*/)
 
   if (/\s+/.test(parts[parts.length - 1])) return null
 
-  let possibleVariants = Object.keys(state.variants)
+  let possibleVariants = state.variants.flatMap((variant) => {
+    if (variant.values.length) {
+      return variant.values.map((value) => `${variant.name}${variant.hasDash ? '-' : ''}${value}`)
+    }
+    return [variant.name]
+  })
   const existingVariants = parts.slice(0, parts.length - 1)
 
   if (state.jit) {
M packages/tailwindcss-language-service/src/diagnostics/getInvalidVariantDiagnostics.tspackages/tailwindcss-language-service/src/diagnostics/getInvalidVariantDiagnostics.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
diff --git a/packages/tailwindcss-language-service/src/diagnostics/getInvalidVariantDiagnostics.ts b/packages/tailwindcss-language-service/src/diagnostics/getInvalidVariantDiagnostics.ts
index 1b6c997abe4119d48fd73b428837f673c1fd08e1..454e0f59cafae735251e5b97a10af6329a9f8d8c 100644
--- a/packages/tailwindcss-language-service/src/diagnostics/getInvalidVariantDiagnostics.ts
+++ b/packages/tailwindcss-language-service/src/diagnostics/getInvalidVariantDiagnostics.ts
@@ -32,7 +32,12 @@     if (!boundaries) return []
     ranges.push(...boundaries.filter((b) => b.type === 'css').map(({ range }) => range))
   }
 
-  let possibleVariants = Object.keys(state.variants)
+  let possibleVariants = state.variants.flatMap((variant) => {
+    if (variant.values.length) {
+      return variant.values.map((value) => `${variant.name}${variant.hasDash ? '-' : ''}${value}`)
+    }
+    return [variant.name]
+  })
   if (state.jit) {
     possibleVariants.unshift('responsive')
     possibleVariants = possibleVariants.filter((v) => !state.screens.includes(v))
M packages/tailwindcss-language-service/src/util/getVariantsFromClassName.tspackages/tailwindcss-language-service/src/util/getVariantsFromClassName.ts
 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
diff --git a/packages/tailwindcss-language-service/src/util/getVariantsFromClassName.ts b/packages/tailwindcss-language-service/src/util/getVariantsFromClassName.ts
index 10dfe90c7cf4d353d820a43f32ed880f5fc490c8..b1709e12c482168bcb00cd38cc5a581eb24ec0eb 100644
--- a/packages/tailwindcss-language-service/src/util/getVariantsFromClassName.ts
+++ b/packages/tailwindcss-language-service/src/util/getVariantsFromClassName.ts
@@ -5,17 +5,25 @@ export function getVariantsFromClassName(
   state: State,
   className: string
 ): { variants: string[]; offset: number } {
-  let allVariants = Object.keys(state.variants)
-  let parts = splitAtTopLevelOnly(className, state.separator).filter(Boolean)
+  let allVariants = state.variants.flatMap((variant) => {
+    if (variant.values.length) {
+      return variant.values.map((value) => `${variant.name}${variant.hasDash ? '-' : ''}${value}`)
+    }
+    return [variant.name]
+  })
   let variants = new Set<string>()
   let offset = 0
+  let parts = splitAtTopLevelOnly(className, state.separator)
+  if (parts.length < 2) {
+    return { variants: Array.from(variants), offset }
+  }
+  parts = parts.filter(Boolean)
 
   for (let part of parts) {
     if (
       allVariants.includes(part) ||
       (state.jit &&
-        ((part.includes('[') && part.endsWith(']')) ||
-          (part.includes('<') && part.includes('>'))) &&
+        ((part.includes('[') && part.endsWith(']')) || part.includes('/')) &&
         jit.generateRules(state, [`${part}${state.separator}[color:red]`]).rules.length > 0)
     ) {
       variants.add(part)
M packages/tailwindcss-language-service/src/util/state.tspackages/tailwindcss-language-service/src/util/state.ts
 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
diff --git a/packages/tailwindcss-language-service/src/util/state.ts b/packages/tailwindcss-language-service/src/util/state.ts
index cb863773db039a83b1afeae41807701a07a88219..4c82bb52f4d20aa0a7ff551700be178b14318179 100644
--- a/packages/tailwindcss-language-service/src/util/state.ts
+++ b/packages/tailwindcss-language-service/src/util/state.ts
@@ -80,6 +80,14 @@   future: string[]
   experimental: string[]
 }
 
+export interface Variant {
+  name: string
+  values: string[]
+  isArbitrary: boolean
+  hasDash: boolean
+  selectors: (params?: { value?: string; label?: string }) => string[]
+}
+
 export interface State {
   enabled: boolean
   configPath?: string
@@ -90,7 +98,7 @@   separator?: string
   dependencies?: string[]
   plugins?: any
   screens?: string[]
-  variants?: Record<string, string | null>
+  variants?: Variant[]
   corePlugins?: string[]
   modules?: {
     tailwindcss?: { version: string; module: any }
M packages/vscode-tailwindcss/src/extension.tspackages/vscode-tailwindcss/src/extension.ts
 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
diff --git a/packages/vscode-tailwindcss/src/extension.ts b/packages/vscode-tailwindcss/src/extension.ts
index 4b2bdfb03c7c526ba31f67193e9c573fdec095d8..d1cb232a15727263b29110a09d58a70b75da31fe 100755
--- a/packages/vscode-tailwindcss/src/extension.ts
+++ b/packages/vscode-tailwindcss/src/extension.ts
@@ -26,6 +26,7 @@   CompletionList,
   ProviderResult,
   SnippetString,
   TextEdit,
+  TextEditorSelectionChangeKind,
 } from 'vscode'
 import {
   LanguageClient,
@@ -148,6 +149,62 @@         outputChannel.show()
       }
     })
   )
+
+  // context.subscriptions.push(
+  //   commands.registerCommand(
+  //     'tailwindCSS.onInsertArbitraryVariantSnippet',
+  //     (
+  //       variantName: string,
+  //       range: {
+  //         start: { line: number; character: number }
+  //         end: { line: number; character: number }
+  //       }
+  //     ) => {
+  //       let listener = Window.onDidChangeTextEditorSelection((event) => {
+  //         if (event.selections.length !== 1) {
+  //           listener.dispose()
+  //           return
+  //         }
+
+  //         let document = event.textEditor.document
+  //         let selection = event.selections[0]
+
+  //         let line = document.lineAt(range.start.line)
+  //         let lineRangeFromCompletion = new Range(
+  //           range.start.line,
+  //           range.start.character,
+  //           line.range.end.line,
+  //           line.range.end.character
+  //         )
+  //         let lineText = document.getText(lineRangeFromCompletion)
+  //         let match = lineText.match(/^(\S+)]:/)
+
+  //         if (!match) {
+  //           listener.dispose()
+  //           return
+  //         }
+
+  //         let arbitraryValueRange = new Range(
+  //           lineRangeFromCompletion.start.translate(0, variantName.length + 2),
+  //           lineRangeFromCompletion.start.translate(0, match[1].length)
+  //         )
+
+  //         if (!arbitraryValueRange.contains(selection)) {
+  //           listener.dispose()
+  //         }
+
+  //         if (
+  //           event.kind === TextEditorSelectionChangeKind.Command &&
+  //           selection.isEmpty &&
+  //           selection.start.isEqual(arbitraryValueRange.end.translate(0, 2))
+  //         ) {
+  //           commands.executeCommand('editor.action.triggerSuggest')
+  //         }
+  //       })
+  //       context.subscriptions.push(listener)
+  //     }
+  //   )
+  // )
 
   let watcher = Workspace.createFileSystemWatcher(`**/${CONFIG_FILE_GLOB}`, false, true, true)