tailwind-ctp-intellisense @master -
refs -
log -
-
https://git.jolheiser.com/tailwind-ctp-intellisense.git
Tailwind intellisense + Catppuccin
Theme helper improvements
9 changed files, 179 additions(+), 156 deletions(-)
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, '_')
)
}
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
'!',
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',
+ }
+ }),
}
}
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,
})
})
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) })
}
})
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(
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
+ ),
+ },
}
})
}
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 = {
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 =