Home

tailwind-ctp-intellisense @master - refs - log -
-
https://git.jolheiser.com/tailwind-ctp-intellisense.git
Tailwind intellisense + Catppuccin
tree log patch
Theme helper improvements
Brad Cornes <hello@bradley.dev>
2 years ago
9 changed files, 179 additions(+), 156 deletions(-)
M packages/tailwindcss-language-server/src/language/cssServer.ts -> packages/tailwindcss-language-server/src/language/cssServer.ts
diff --git a/packages/tailwindcss-language-server/src/language/cssServer.ts b/packages/tailwindcss-language-server/src/language/cssServer.ts
index 496a2511de903323408179797d7b4afe763eb72d..731eca3691ec58192c43be4c6d15139baecdf2ec 100644
--- a/packages/tailwindcss-language-server/src/language/cssServer.ts
+++ b/packages/tailwindcss-language-server/src/language/cssServer.ts
@@ -162,10 +162,15 @@             item,
             {
               ...item,
               label: 'theme()',
+              filterText: 'theme',
               documentation: {
                 kind: 'markdown',
                 value:
                   'Use the `theme()` function to access your Tailwind config values using dot notation.',
+              },
+              command: {
+                title: '',
+                command: 'editor.action.triggerSuggest',
               },
               textEdit: {
                 ...item.textEdit,
@@ -357,6 +362,7 @@       .replace(
         /@media(\s+screen\s*\([^)]+\))/g,
         (_match, screen) => `@media (${MEDIA_MARKER})${' '.repeat(screen.length - 4)}`
       )
+      .replace(/(?<=\b(?:theme|config)\([^)]*)[.[\]]/g, '_')
   )
 }
 
M packages/tailwindcss-language-server/src/server.ts -> packages/tailwindcss-language-server/src/server.ts
diff --git a/packages/tailwindcss-language-server/src/server.ts b/packages/tailwindcss-language-server/src/server.ts
index dbc1f0b787c0cbfa4ba6d9efbc3381aef201070f..a3b6131252b1b5d0b45dbedb2e189db4f6ce5c36 100644
--- a/packages/tailwindcss-language-server/src/server.ts
+++ b/packages/tailwindcss-language-server/src/server.ts
@@ -112,6 +112,7 @@   ' ',
   // @apply and emmet-style
   '.',
   // config/theme helper
+  '(',
   '[',
   // JIT "important" prefix
   '!',
M packages/tailwindcss-language-service/src/completionProvider.ts -> packages/tailwindcss-language-service/src/completionProvider.ts
diff --git a/packages/tailwindcss-language-service/src/completionProvider.ts b/packages/tailwindcss-language-service/src/completionProvider.ts
index 8ac58761c1f1eb7c0b6f2cf6f38cf0eddaef183d..86f9fb70aa71b0bbb1c755fb20e3d66695fe776e 100644
--- a/packages/tailwindcss-language-service/src/completionProvider.ts
+++ b/packages/tailwindcss-language-service/src/completionProvider.ts
@@ -503,6 +503,11 @@     }
   )
 }
 
+const NUMBER_REGEX = /^(\d+\.?|\d*\.\d+)$/
+function isNumber(str: string): boolean {
+  return NUMBER_REGEX.test(str)
+}
+
 async function provideClassNameCompletions(
   state: State,
   document: TextDocument,
@@ -537,14 +542,26 @@   })
 
   const match = text
     .substr(0, text.length - 1) // don't include that extra character from earlier
-    .match(/\b(?<helper>config|theme)\(['"](?<keys>[^'"]*)$/)
+    .match(/\b(?<helper>config|theme)\(\s*['"]?(?<path>[^)'"]*)$/)
 
   if (match === null) {
     return null
   }
 
+  let alpha: string
+  let path = match.groups.path.replace(/^['"]+/g, '')
+  let matches = path.match(/^([^\s]+)(?![^\[]*\])(?:\s*\/\s*([^\/\s]*))$/)
+  if (matches) {
+    path = matches[1]
+    alpha = matches[2]
+  }
+
+  if (alpha !== undefined) {
+    return null
+  }
+
   let base = match.groups.helper === 'config' ? state.config : dlv(state.config, 'theme', {})
-  let parts = match.groups.keys.split(/([\[\].]+)/)
+  let parts = path.split(/([\[\].]+)/)
   let keys = parts.filter((_, i) => i % 2 === 0)
   let separators = parts.filter((_, i) => i % 2 !== 0)
   // let obj =
@@ -557,7 +574,7 @@     return arr.reduce((acc, cur) => acc + cur.length, 0)
   }
 
   let obj: any
-  let offset: number = 0
+  let offset: number = keys[keys.length - 1].length
   let separator: string = separators.length ? separators[separators.length - 1] : null
 
   if (keys.length === 1) {
@@ -576,41 +593,73 @@   }
 
   if (!obj) return null
 
+  let editRange = {
+    start: {
+      line: position.line,
+      character: position.character - offset,
+    },
+    end: position,
+  }
+
   return {
     isIncomplete: false,
-    items: Object.keys(obj).map((item, index) => {
-      let color = getColorFromValue(obj[item])
-      const replaceDot: boolean = item.indexOf('.') !== -1 && separator && separator.endsWith('.')
-      const insertClosingBrace: boolean =
-        text.charAt(text.length - 1) !== ']' &&
-        (replaceDot || (separator && separator.endsWith('[')))
-      const detail = stringifyConfigValue(obj[item])
+    items: Object.keys(obj)
+      .sort((a, z) => {
+        let aIsNumber = isNumber(a)
+        let zIsNumber = isNumber(z)
+        if (aIsNumber && !zIsNumber) {
+          return -1
+        }
+        if (!aIsNumber && zIsNumber) {
+          return 1
+        }
+        if (aIsNumber && zIsNumber) {
+          return parseFloat(a) - parseFloat(z)
+        }
+        return 0
+      })
+      .map((item, index) => {
+        let color = getColorFromValue(obj[item])
+        const replaceDot: boolean = item.indexOf('.') !== -1 && separator && separator.endsWith('.')
+        const insertClosingBrace: boolean =
+          text.charAt(text.length - 1) !== ']' &&
+          (replaceDot || (separator && separator.endsWith('[')))
+        const detail = stringifyConfigValue(obj[item])
 
-      return {
-        label: item,
-        filterText: `${replaceDot ? '.' : ''}${item}`,
-        sortText: naturalExpand(index),
-        kind: color ? 16 : isObject(obj[item]) ? 9 : 10,
-        // VS Code bug causes some values to not display in some cases
-        detail: detail === '0' || detail === 'transparent' ? `${detail} ` : detail,
-        documentation:
-          color && typeof color !== 'string' && (color.alpha ?? 1) !== 0
-            ? culori.formatRgb(color)
-            : null,
-        textEdit: {
-          newText: `${replaceDot ? '[' : ''}${item}${insertClosingBrace ? ']' : ''}`,
-          range: {
-            start: {
-              line: position.line,
-              character:
-                position.character - keys[keys.length - 1].length - (replaceDot ? 1 : 0) - offset,
-            },
-            end: position,
+        return {
+          label: item,
+          sortText: naturalExpand(index),
+          commitCharacters: [!item.includes('.') && '.', !item.includes('[') && '['].filter(
+            Boolean
+          ),
+          kind: color ? 16 : isObject(obj[item]) ? 9 : 10,
+          // VS Code bug causes some values to not display in some cases
+          detail: detail === '0' || detail === 'transparent' ? `${detail} ` : detail,
+          documentation:
+            color && typeof color !== 'string' && (color.alpha ?? 1) !== 0
+              ? culori.formatRgb(color)
+              : null,
+          textEdit: {
+            newText: `${item}${insertClosingBrace ? ']' : ''}`,
+            range: editRange,
           },
-        },
-        data: 'helper',
-      }
-    }),
+          additionalTextEdits: replaceDot
+            ? [
+                {
+                  newText: '[',
+                  range: {
+                    start: {
+                      ...editRange.start,
+                      character: editRange.start.character - 1,
+                    },
+                    end: editRange.start,
+                  },
+                },
+              ]
+            : [],
+          data: 'helper',
+        }
+      }),
   }
 }
 
M packages/tailwindcss-language-service/src/diagnostics/getInvalidConfigPathDiagnostics.ts -> packages/tailwindcss-language-service/src/diagnostics/getInvalidConfigPathDiagnostics.ts
diff --git a/packages/tailwindcss-language-service/src/diagnostics/getInvalidConfigPathDiagnostics.ts b/packages/tailwindcss-language-service/src/diagnostics/getInvalidConfigPathDiagnostics.ts
index 716ce2c8a05086bd55fc13265b0e9284f2591666..0af368315ee8d9cb145467251a9cd2e403707383 100644
--- a/packages/tailwindcss-language-service/src/diagnostics/getInvalidConfigPathDiagnostics.ts
+++ b/packages/tailwindcss-language-service/src/diagnostics/getInvalidConfigPathDiagnostics.ts
@@ -1,16 +1,12 @@
 import { State, Settings } from '../util/state'
-import type { TextDocument, Range, DiagnosticSeverity } from 'vscode-languageserver'
+import type { TextDocument } from 'vscode-languageserver'
 import { InvalidConfigPathDiagnostic, DiagnosticKind } from './types'
-import { isCssDoc } from '../util/css'
-import { getLanguageBoundaries } from '../util/getLanguageBoundaries'
-import { findAll, indexToPosition } from '../util/find'
+import { findHelperFunctionsInDocument } from '../util/find'
 import { stringToPath } from '../util/stringToPath'
 import isObject from '../util/isObject'
 import { closest } from '../util/closest'
-import { absoluteRange } from '../util/absoluteRange'
 import { combinations } from '../util/combinations'
 import dlv from 'dlv'
-import { getTextWithoutComments } from '../util/doc'
 
 function pathToString(path: string | string[]): string {
   if (typeof path === 'string') return path
@@ -167,54 +163,24 @@   let severity = settings.tailwindCSS.lint.invalidConfigPath
   if (severity === 'ignore') return []
 
   let diagnostics: InvalidConfigPathDiagnostic[] = []
-  let ranges: Range[] = []
 
-  if (isCssDoc(state, document)) {
-    ranges.push(undefined)
-  } else {
-    let boundaries = getLanguageBoundaries(state, document)
-    if (!boundaries) return []
-    ranges.push(...boundaries.filter((b) => b.type === 'css').map(({ range }) => range))
-  }
-
-  ranges.forEach((range) => {
-    let text = getTextWithoutComments(document, 'css', range)
-    let matches = findAll(
-      /(?<prefix>\s|^)(?<helper>config|theme)\((?<quote>['"])(?<key>[^)]+)\k<quote>[^)]*\)/g,
-      text
-    )
-
-    matches.forEach((match) => {
-      let base = match.groups.helper === 'theme' ? ['theme'] : []
-      let result = validateConfigPath(state, match.groups.key, base)
+  findHelperFunctionsInDocument(state, document).forEach((helperFn) => {
+    let base = helperFn.helper === 'theme' ? ['theme'] : []
+    let result = validateConfigPath(state, helperFn.path, base)
 
-      if (result.isValid === true) {
-        return null
-      }
-
-      let startIndex =
-        match.index +
-        match.groups.prefix.length +
-        match.groups.helper.length +
-        1 + // open paren
-        match.groups.quote.length
+    if (result.isValid === true) {
+      return
+    }
 
-      diagnostics.push({
-        code: DiagnosticKind.InvalidConfigPath,
-        range: absoluteRange(
-          {
-            start: indexToPosition(text, startIndex),
-            end: indexToPosition(text, startIndex + match.groups.key.length),
-          },
-          range
-        ),
-        severity:
-          severity === 'error'
-            ? 1 /* DiagnosticSeverity.Error */
-            : 2 /* DiagnosticSeverity.Warning */,
-        message: result.reason,
-        suggestions: result.suggestions,
-      })
+    diagnostics.push({
+      code: DiagnosticKind.InvalidConfigPath,
+      range: helperFn.ranges.path,
+      severity:
+        severity === 'error'
+          ? 1 /* DiagnosticSeverity.Error */
+          : 2 /* DiagnosticSeverity.Warning */,
+      message: result.reason,
+      suggestions: result.suggestions,
     })
   })
 
M packages/tailwindcss-language-service/src/documentColorProvider.ts -> packages/tailwindcss-language-service/src/documentColorProvider.ts
diff --git a/packages/tailwindcss-language-service/src/documentColorProvider.ts b/packages/tailwindcss-language-service/src/documentColorProvider.ts
index 081d1c0c8086a553d36727711fb0aae744d38ca9..dac832f646da92365e4ef4edc5844bc0e52f45a7 100644
--- a/packages/tailwindcss-language-service/src/documentColorProvider.ts
+++ b/packages/tailwindcss-language-service/src/documentColorProvider.ts
@@ -36,12 +36,12 @@   })
 
   let helperFns = findHelperFunctionsInDocument(state, document)
   helperFns.forEach((fn) => {
-    let keys = stringToPath(fn.value)
+    let keys = stringToPath(fn.path)
     let base = fn.helper === 'theme' ? ['theme'] : []
     let value = dlv(state.config, [...base, ...keys])
     let color = getColorFromValue(value)
     if (color && typeof color !== 'string' && (color.alpha ?? 1) !== 0) {
-      colors.push({ range: fn.valueRange, color: culoriColorToVscodeColor(color) })
+      colors.push({ range: fn.ranges.path, color: culoriColorToVscodeColor(color) })
     }
   })
 
M packages/tailwindcss-language-service/src/hoverProvider.ts -> packages/tailwindcss-language-service/src/hoverProvider.ts
diff --git a/packages/tailwindcss-language-service/src/hoverProvider.ts b/packages/tailwindcss-language-service/src/hoverProvider.ts
index 090482ecbc7d765411296a9e4cbfbfe8e8c41ad3..f506bb27415453404d2b078e434866ebb9f47016 100644
--- a/packages/tailwindcss-language-service/src/hoverProvider.ts
+++ b/packages/tailwindcss-language-service/src/hoverProvider.ts
@@ -3,12 +3,12 @@ import type { Hover, TextDocument, Position } from 'vscode-languageserver'
 import { stringifyCss, stringifyConfigValue } from './util/stringify'
 import dlv from 'dlv'
 import { isCssContext } from './util/css'
-import { findClassNameAtPosition } from './util/find'
+import { findClassNameAtPosition, findHelperFunctionsInRange } from './util/find'
 import { validateApply } from './util/validateApply'
 import { getClassNameParts } from './util/getClassNameAtPosition'
 import * as jit from './util/jit'
 import { validateConfigPath } from './diagnostics/getInvalidConfigPathDiagnostics'
-import { getTextWithoutComments } from './util/doc'
+import { isWithinRange } from './util/isWithinRange'
 
 export async function doHover(
   state: State,
@@ -22,49 +22,34 @@   )
 }
 
 function provideCssHelperHover(state: State, document: TextDocument, position: Position): Hover {
-  if (!isCssContext(state, document, position)) return null
-
-  const line = getTextWithoutComments(document, 'css').split('\n')[position.line]
-
-  const match = line.match(/(?<helper>theme|config)\((?<quote>['"])(?<key>[^)]+)\k<quote>[^)]*\)/)
-
-  if (match === null) return null
-
-  const startChar = match.index + match.groups.helper.length + 2
-  const endChar = startChar + match.groups.key.length
-
-  if (position.character < startChar || position.character >= endChar) {
+  if (!isCssContext(state, document, position)) {
     return null
   }
 
-  let key = match.groups.key
-    .split(/(\[[^\]]+\]|\.)/)
-    .filter(Boolean)
-    .filter((x) => x !== '.')
-    .map((x) => x.replace(/^\[([^\]]+)\]$/, '$1'))
+  let helperFns = findHelperFunctionsInRange(document, {
+    start: { line: position.line, character: 0 },
+    end: { line: position.line + 1, character: 0 },
+  })
 
-  if (key.length === 0) return null
-
-  if (match.groups.helper === 'theme') {
-    key = ['theme', ...key]
+  for (let helperFn of helperFns) {
+    if (isWithinRange(position, helperFn.ranges.path)) {
+      let validated = validateConfigPath(
+        state,
+        helperFn.path,
+        helperFn.helper === 'theme' ? ['theme'] : []
+      )
+      let value = validated.isValid ? stringifyConfigValue(validated.value) : null
+      if (value === null) {
+        return null
+      }
+      return {
+        contents: { kind: 'markdown', value: ['```plaintext', value, '```'].join('\n') },
+        range: helperFn.ranges.path,
+      }
+    }
   }
 
-  const value = validateConfigPath(state, key).isValid
-    ? stringifyConfigValue(dlv(state.config, key))
-    : null
-
-  if (value === null) return null
-
-  return {
-    contents: { kind: 'markdown', value: ['```plaintext', value, '```'].join('\n') },
-    range: {
-      start: { line: position.line, character: startChar },
-      end: {
-        line: position.line,
-        character: endChar,
-      },
-    },
-  }
+  return null
 }
 
 async function provideClassNameHover(
M packages/tailwindcss-language-service/src/util/find.ts -> packages/tailwindcss-language-service/src/util/find.ts
diff --git a/packages/tailwindcss-language-service/src/util/find.ts b/packages/tailwindcss-language-service/src/util/find.ts
index 4e851158c1020139e6cc73d0214cc21127e1fe02..edcb59529869ed0bbdcf4f559a5fb160300f3e71 100644
--- a/packages/tailwindcss-language-service/src/util/find.ts
+++ b/packages/tailwindcss-language-service/src/util/find.ts
@@ -359,36 +359,48 @@   doc: TextDocument,
   range?: Range
 ): DocumentHelperFunction[] {
   const text = getTextWithoutComments(doc, 'css', range)
-  const matches = findAll(
-    /(?<before>^|\s)(?<helper>theme|config)\((?:(?<single>')([^']+)'|(?<double>")([^"]+)")[^)]*\)/gm,
+  let matches = findAll(
+    /(?<prefix>\s|^)(?<helper>config|theme)(?<innerPrefix>\(\s*)(?<path>[^)]*?)\s*\)/g,
     text
   )
 
   return matches.map((match) => {
-    let value = match[4] || match[6]
-    let startIndex = match.index + match.groups.before.length
+    let quotesBefore = ''
+    let path = match.groups.path.replace(/['"]+$/, '').replace(/^['"]+/, (m) => {
+      quotesBefore = m
+      return ''
+    })
+    let matches = path.match(/^([^\s]+)(?![^\[]*\])(?:\s*\/\s*([^\/\s]+))$/)
+    if (matches) {
+      path = matches[1]
+    }
+    path = path.replace(/['"]*\s*$/, '')
+
+    let startIndex =
+      match.index +
+      match.groups.prefix.length +
+      match.groups.helper.length +
+      match.groups.innerPrefix.length
+
     return {
-      full: match[0].substr(match.groups.before.length),
-      value,
       helper: match.groups.helper === 'theme' ? 'theme' : 'config',
-      quotes: match.groups.single ? "'" : '"',
-      range: resolveRange(
-        {
-          start: indexToPosition(text, startIndex),
-          end: indexToPosition(text, match.index + match[0].length),
-        },
-        range
-      ),
-      valueRange: resolveRange(
-        {
-          start: indexToPosition(text, startIndex + match.groups.helper.length + 1),
-          end: indexToPosition(
-            text,
-            startIndex + match.groups.helper.length + 1 + 1 + value.length + 1
-          ),
-        },
-        range
-      ),
+      path,
+      ranges: {
+        full: resolveRange(
+          {
+            start: indexToPosition(text, startIndex),
+            end: indexToPosition(text, startIndex + match.groups.path.length),
+          },
+          range
+        ),
+        path: resolveRange(
+          {
+            start: indexToPosition(text, startIndex + quotesBefore.length),
+            end: indexToPosition(text, startIndex + quotesBefore.length + path.length),
+          },
+          range
+        ),
+      },
     }
   })
 }
M packages/tailwindcss-language-service/src/util/state.ts -> packages/tailwindcss-language-service/src/util/state.ts
diff --git a/packages/tailwindcss-language-service/src/util/state.ts b/packages/tailwindcss-language-service/src/util/state.ts
index d699ffb6c83f4b694966ceb8444c872c383a283e..31946432adf25beb59b436dd8ebc3420f88c5c51 100644
--- a/packages/tailwindcss-language-service/src/util/state.ts
+++ b/packages/tailwindcss-language-service/src/util/state.ts
@@ -124,12 +124,12 @@   classList: DocumentClassList
 }
 
 export type DocumentHelperFunction = {
-  full: string
   helper: 'theme' | 'config'
-  value: string
-  quotes: '"' | "'"
-  range: Range
-  valueRange: Range
+  path: string
+  ranges: {
+    full: Range
+    path: Range
+  }
 }
 
 export type ClassNameMeta = {
M packages/vscode-tailwindcss/src/extension.ts -> packages/vscode-tailwindcss/src/extension.ts
diff --git a/packages/vscode-tailwindcss/src/extension.ts b/packages/vscode-tailwindcss/src/extension.ts
index 1677c1b2b19f8fd9b951516f3ccc2acf9042cd1d..4b2bdfb03c7c526ba31f67193e9c573fdec095d8 100755
--- a/packages/vscode-tailwindcss/src/extension.ts
+++ b/packages/vscode-tailwindcss/src/extension.ts
@@ -378,7 +378,11 @@       middleware: {
         async resolveCompletionItem(item, token, next) {
           let result = await next(item, token)
           let selections = Window.activeTextEditor.selections
-          if (selections.length > 1 && result.additionalTextEdits?.length > 0) {
+          if (
+            result['data'] === 'variant' &&
+            selections.length > 1 &&
+            result.additionalTextEdits?.length > 0
+          ) {
             let length =
               selections[0].start.character - result.additionalTextEdits[0].range.start.character
             let prefixLength =