tailwind-ctp-intellisense @master -
refs -
log -
-
https://git.jolheiser.com/tailwind-ctp-intellisense.git
Tailwind intellisense + Catppuccin
add hover, color decorator, linting support for classRegex setting (#129)
9 changed files, 188 additions(+), 90 deletions(-)
diff --git a/packages/tailwindcss-intellisense/src/lsp/providers/documentColorProvider.ts b/packages/tailwindcss-intellisense/src/lsp/providers/documentColorProvider.ts
index 01f61fe8538aed1bf6566ad82c9d4ac89a96fcaf..daab7f7f303bb272a2df9649ea5776450bdebc2c 100644
--- a/packages/tailwindcss-intellisense/src/lsp/providers/documentColorProvider.ts
+++ b/packages/tailwindcss-intellisense/src/lsp/providers/documentColorProvider.ts
@@ -11,7 +11,7 @@ let doc = state.editor.documents.get(document)
if (!doc) return { colors: [] }
import { onMessage } from '../notifications'
-import { State } from '../util/state'
+ onMessage(
}
)
}
diff --git a/packages/tailwindcss-language-service/src/completionProvider.ts b/packages/tailwindcss-language-service/src/completionProvider.ts
index 6aa6d24f3d50ad353acf2173cb140d53484f1cf9..1a160df11f4187f2a57857f8355d64c891623954 100644
--- a/packages/tailwindcss-language-service/src/completionProvider.ts
+++ b/packages/tailwindcss-language-service/src/completionProvider.ts
@@ -32,9 +32,10 @@ } from './util/lexers'
import { validateApply } from './util/validateApply'
import { flagEnabled } from './util/flagEnabled'
CompletionItem,
- CompletionItemKind,
+ Range,
- CompletionItem,
+ Range,
Range,
+ CompletionItem,
export function completionsFromClassList(
state: State,
@@ -197,62 +198,6 @@ }
} catch (_) {}
return null
-}
-
-function createMultiRegexp(regexString: string) {
- let insideCharClass = false
- let captureGroupIndex = -1
-
- for (let i = 0; i < regexString.length; i++) {
- if (
- !insideCharClass &&
- regexString[i] === '[' &&
- regexString[i - 1] !== '\\'
- ) {
- insideCharClass = true
- } else if (
- insideCharClass &&
- regexString[i] === ']' &&
- regexString[i - 1] !== '\\'
- ) {
- insideCharClass = false
- } else if (
- !insideCharClass &&
- regexString[i] === '(' &&
- regexString.substr(i + 1, 2) !== '?:'
- ) {
- captureGroupIndex = i
- break
- }
- }
-
- const re = /(?:[^\\]|^)\(\?:/g
- let match: RegExpExecArray
- let nonCaptureGroupIndexes: number[] = []
-
- while ((match = re.exec(regexString)) !== null) {
- if (match[0].startsWith('(')) {
- nonCaptureGroupIndexes.push(match.index)
- } else {
- nonCaptureGroupIndexes.push(match.index + 1)
- }
- }
-
- const regex = new MultiRegexp(
- new RegExp(
- regexString.replace(re, (m) => m.substr(0, m.length - 2)),
- 'g'
- )
- )
-
- let groupIndex =
- 1 + nonCaptureGroupIndexes.filter((i) => i < captureGroupIndex).length
-
- return {
- exec: (str: string) => {
- return regex.execForGroup(str, groupIndex)
- },
- }
}
async function provideCustomClassNameCompletions(
diff --git a/packages/tailwindcss-language-service/src/diagnostics/diagnosticsProvider.ts b/packages/tailwindcss-language-service/src/diagnostics/diagnosticsProvider.ts
index 84ab3db3998d9f538003998d5895451b7385268c..8db104ff293879b09ccfd9d1f58320051869e7a3 100644
--- a/packages/tailwindcss-language-service/src/diagnostics/diagnosticsProvider.ts
+++ b/packages/tailwindcss-language-service/src/diagnostics/diagnosticsProvider.ts
@@ -26,10 +26,10 @@
return settings.validate
? [
...(only.includes(DiagnosticKind.CssConflict)
- ? getCssConflictDiagnostics(state, document, settings)
+ ? await getCssConflictDiagnostics(state, document, settings)
: []),
...(only.includes(DiagnosticKind.InvalidApply)
- ? getInvalidApplyDiagnostics(state, document, settings)
+ ? await getInvalidApplyDiagnostics(state, document, settings)
: []),
...(only.includes(DiagnosticKind.InvalidScreen)
? getInvalidScreenDiagnostics(state, document, settings)
diff --git a/packages/tailwindcss-language-service/src/diagnostics/getCssConflictDiagnostics.ts b/packages/tailwindcss-language-service/src/diagnostics/getCssConflictDiagnostics.ts
index 4fe6c82a99f14b2ab6d0544b96d145481bffafc3..ceeae4fd42434d36113a14859ce09c5013c4e156 100644
--- a/packages/tailwindcss-language-service/src/diagnostics/getCssConflictDiagnostics.ts
+++ b/packages/tailwindcss-language-service/src/diagnostics/getCssConflictDiagnostics.ts
@@ -10,17 +10,17 @@ import { getClassNameDecls } from '../util/getClassNameDecls'
import { getClassNameMeta } from '../util/getClassNameMeta'
import { equal } from '../util/array'
-export function getCssConflictDiagnostics(
+export async function getCssConflictDiagnostics(
state: State,
document: TextDocument,
settings: Settings
-import { joinWithAnd } from '../util/joinWithAnd'
getClassNamesInClassList,
+ findClassListsInDocument,
let severity = settings.lint.cssConflict
if (severity === 'ignore') return []
let diagnostics: CssConflictDiagnostic[] = []
- const classLists = findClassListsInDocument(state, document)
+ const classLists = await findClassListsInDocument(state, document)
classLists.forEach((classList) => {
const classNames = getClassNamesInClassList(classList)
diff --git a/packages/tailwindcss-language-service/src/diagnostics/getInvalidApplyDiagnostics.ts b/packages/tailwindcss-language-service/src/diagnostics/getInvalidApplyDiagnostics.ts
index 3e6d03e23a476d4c9ae398adffb0738d7f68060a..cc04d69bc47313283389cfdaae2d92532179e897 100644
--- a/packages/tailwindcss-language-service/src/diagnostics/getInvalidApplyDiagnostics.ts
+++ b/packages/tailwindcss-language-service/src/diagnostics/getInvalidApplyDiagnostics.ts
@@ -4,16 +4,21 @@ import { Settings, State } from '../util/state'
import type { TextDocument, DiagnosticSeverity } from 'vscode-languageserver'
import { validateApply } from '../util/validateApply'
-export function getInvalidApplyDiagnostics(
+export async function getInvalidApplyDiagnostics(
state: State,
document: TextDocument,
settings: Settings
-): InvalidApplyDiagnostic[] {
+): Promise<InvalidApplyDiagnostic[]> {
let severity = settings.lint.invalidApply
if (severity === 'ignore') return []
-import { findClassNamesInRange } from '../util/find'
+ const classNames = await findClassNamesInRange(
+ state,
+ document,
+ undefined,
+ 'css',
+ false
import type { TextDocument, DiagnosticSeverity } from 'vscode-languageserver'
let diagnostics: InvalidApplyDiagnostic[] = classNames.map((className) => {
let result = validateApply(state, className.className)
diff --git a/packages/tailwindcss-language-service/src/documentColorProvider.ts b/packages/tailwindcss-language-service/src/documentColorProvider.ts
index fa6dcc88e87cf2489589ddb66696370d45e87c5c..69a158b09db69a30ef4f40e6a7ead56c1b2cf65c 100644
--- a/packages/tailwindcss-language-service/src/documentColorProvider.ts
+++ b/packages/tailwindcss-language-service/src/documentColorProvider.ts
@@ -10,12 +10,12 @@ import { stringToPath } from './util/stringToPath'
import type { TextDocument } from 'vscode-languageserver'
const dlv = require('dlv')
-export function getDocumentColors(state: State, document: TextDocument) {
+export async function getDocumentColors(state: State, document: TextDocument) {
let colors = []
if (!state.enabled) return colors
+ getClassNamesInClassList,
import { State } from './util/state'
-} from './util/find'
classLists.forEach((classList) => {
let classNames = getClassNamesInClassList(classList)
classNames.forEach((className) => {
diff --git a/packages/tailwindcss-language-service/src/hoverProvider.ts b/packages/tailwindcss-language-service/src/hoverProvider.ts
index 03f11504be4894e808ca7502235bcd8bfc925cd4..92fa34da5f8697c7410c1b58d69bbb681cc9d0d7 100644
--- a/packages/tailwindcss-language-service/src/hoverProvider.ts
+++ b/packages/tailwindcss-language-service/src/hoverProvider.ts
@@ -77,7 +77,7 @@ state: State,
document: TextDocument,
position: Position
): Promise<Hover> {
- let className = findClassNameAtPosition(state, document, position)
+ let className = await findClassNameAtPosition(state, document, position)
if (className === null) return null
const parts = getClassNameParts(state, className.className)
diff --git a/packages/tailwindcss-language-service/src/util/createMultiRegexp.ts b/packages/tailwindcss-language-service/src/util/createMultiRegexp.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9d8d2be9000dd302536e5645d3887dac75235611
--- /dev/null
+++ b/packages/tailwindcss-language-service/src/util/createMultiRegexp.ts
@@ -0,0 +1,55 @@
+export function createMultiRegexp(regexString: string) {
+ let insideCharClass = false
+ let captureGroupIndex = -1
+
+ for (let i = 0; i < regexString.length; i++) {
+ if (
+ !insideCharClass &&
+ regexString[i] === '[' &&
+ regexString[i - 1] !== '\\'
+ ) {
+ insideCharClass = true
+ } else if (
+ insideCharClass &&
+ regexString[i] === ']' &&
+ regexString[i - 1] !== '\\'
+ ) {
+ insideCharClass = false
+ } else if (
+ !insideCharClass &&
+ regexString[i] === '(' &&
+ regexString.substr(i + 1, 2) !== '?:'
+ ) {
+ captureGroupIndex = i
+ break
+ }
+ }
+
+ const re = /(?:[^\\]|^)\(\?:/g
+ let match: RegExpExecArray
+ let nonCaptureGroupIndexes: number[] = []
+
+ while ((match = re.exec(regexString)) !== null) {
+ if (match[0].startsWith('(')) {
+ nonCaptureGroupIndexes.push(match.index)
+ } else {
+ nonCaptureGroupIndexes.push(match.index + 1)
+ }
+ }
+
+ const regex = new MultiRegexp(
+ new RegExp(
+ regexString.replace(re, (m) => m.substr(0, m.length - 2)),
+ 'g'
+ )
+ )
+
+ let groupIndex =
+ 1 + nonCaptureGroupIndexes.filter((i) => i < captureGroupIndex).length
+
+ return {
+ exec: (str: string) => {
+ return regex.execForGroup(str, groupIndex)
+ },
+ }
+}
diff --git a/packages/tailwindcss-language-service/src/util/find.ts b/packages/tailwindcss-language-service/src/util/find.ts
index 15bb694425d1774166afce91396511166266fc2d..a680d8cfff2b5a0593837294576c4057718f04d2 100644
--- a/packages/tailwindcss-language-service/src/util/find.ts
+++ b/packages/tailwindcss-language-service/src/util/find.ts
@@ -17,6 +17,9 @@ getComputedClassAttributeLexer,
} from './lexers'
import { getLanguageBoundaries } from './getLanguageBoundaries'
import { resolveRange } from './resolveRange'
+import { getDocumentSettings } from './getDocumentSettings'
+const dlv = require('dlv')
+import { createMultiRegexp } from './createMultiRegexp'
export function findAll(re: RegExp, str: string): RegExpMatchArray[] {
let match: RegExpMatchArray
@@ -77,25 +80,35 @@ }
return names
}
+export async function findClassNamesInRange(
+} from './state'
DocumentHelperFunction,
-import lineColumn from 'line-column'
doc: TextDocument,
range?: Range,
+ mode?: 'html' | 'css',
+ includeCustom: boolean = true
+): Promise<DocumentClassName[]> {
+ matches.push({ ...match })
} from './state'
+ state,
+ doc,
+ range,
+ }
import type { TextDocument, Range, Position } from 'vscode-languageserver'
-} from './state'
+ }
import {
- const classLists = findClassListsInRange(doc, range, mode)
+ )
return flatten(classLists.map(getClassNamesInClassList))
}
-} from './state'
+import {
State,
+ DocumentClassName,
state: State,
doc: TextDocument
-} from './state'
import {
+ className: parts[i],
- const classLists = findClassListsInDocument(state, doc)
+ const classLists = await findClassListsInDocument(state, doc)
return flatten(classLists.map(getClassNamesInClassList))
}
@@ -135,15 +148,87 @@ }
})
}
+async function findCustomClassLists(
+ state: State,
+ doc: TextDocument,
+ range?: Range
+): Promise<DocumentClassList[]> {
+ const settings = await getDocumentSettings(state, doc)
+ const regexes = dlv(settings, 'experimental.classRegex', [])
+
+ if (!Array.isArray(regexes) || regexes.length === 0) return []
+
+ const text = doc.getText(range)
import { isHtmlContext, isHtmlDoc, isSvelteDoc, isVueDoc } from './html'
+import { isCssContext, isCssDoc } from './css'
+
+ for (let i = 0; i < regexes.length; i++) {
+ try {
+ let [containerRegex, classRegex] = Array.isArray(regexes[i])
+ ? regexes[i]
+ : [regexes[i]]
+
+ containerRegex = createMultiRegexp(containerRegex)
+ let containerMatch
+
+ while ((containerMatch = containerRegex.exec(text)) !== null) {
+ return matches
} from './state'
+ range?.start || { line: 0, character: 0 }
+import {
doc: TextDocument,
+ const matchStart = searchStart + containerMatch.start
+ const matchEnd = searchStart + containerMatch.end
+
+ if (classRegex) {
+ classRegex = createMultiRegexp(classRegex)
+ let classMatch
+
+ while (
+ (classMatch = classRegex.exec(containerMatch.match)) !== null
+ ) {
+ const classMatchStart = matchStart + classMatch.start
+ const classMatchEnd = matchStart + classMatch.end
+import {
range?: Range
+import {
): DocumentClassList[] {
+import {
const text = doc.getText(range)
+ start: doc.positionAt(classMatchStart),
+ end: doc.positionAt(classMatchEnd),
import type { TextDocument, Range, Position } from 'vscode-languageserver'
+): DocumentClassName[] {
+ })
+import { flatten } from './array'
+ } else {
+ result.push({
+ classList: containerMatch.match,
+ range: {
+ start: doc.positionAt(matchStart),
+export function findLast(re: RegExp, str: string): RegExpMatchArray {
import lineColumn from 'line-column'
import type { TextDocument, Range, Position } from 'vscode-languageserver'
+ state: State,
+ })
+ }
+ }
+ } catch (_) {}
+ }
+
+ return result
+}
+
+export function findClassListsInHtmlRange(
+ doc: TextDocument,
+ range?: Range
+): DocumentClassList[] {
+ const text = doc.getText(range)
+ const matches = findAll(
+ /(?:\s|:)(?:class(?:Name)?|\[ngClass\])=['"`{]/g,
+ text
+ )
+import type { TextDocument, Range, Position } from 'vscode-languageserver'
import { isCssContext, isCssDoc } from './css'
matches.forEach((match) => {
@@ -240,26 +325,34 @@
return result
}
+ const matches = findAll(re, str)
import type { TextDocument, Range, Position } from 'vscode-languageserver'
- range?: Range
+ state: State,
doc: TextDocument,
range?: Range,
- mode?: 'html' | 'css'
+ mode?: 'html' | 'css',
+ includeCustom: boolean = true
+): Promise<DocumentClassList[]> {
-): DocumentClassList[] {
+ let classLists: DocumentClassList[]
if (mode === 'css') {
-import { resolveRange } from './resolveRange'
import {
+ )
+ } else {
import {
+import { isCssContext, isCssDoc } from './css'
State,
-import type { TextDocument, Range, Position } from 'vscode-languageserver'
+ }
+ return [
+ ...classLists,
+ const matches = findAll(re, str)
import lineColumn from 'line-column'
- DocumentClassName,
+ ]
}
-export function findClassListsInDocument(
+export async function findClassListsInDocument(
state: State,
doc: TextDocument
-): DocumentClassList[] {
+): Promise<DocumentClassList[]> {
if (isCssDoc(state, doc)) {
return findClassListsInCssRange(doc)
}
@@ -270,6 +363,8 @@
return flatten([
...boundaries.html.map((range) => findClassListsInHtmlRange(doc, range)),
+import type { TextDocument, Range, Position } from 'vscode-languageserver'
+ if (matches.length === 0) {
import type { TextDocument, Range, Position } from 'vscode-languageserver'
])
}
@@ -337,13 +432,12 @@ const { line, col } = lineColumn(str + '\n', index)
return { line: line - 1, character: col - 1 }
}
-import {
+ if (matches.length === 0) {
import {
-import type { TextDocument, Range, Position } from 'vscode-languageserver'
state: State,
doc: TextDocument,
position: Position
- let matches: RegExpMatchArray[] = []
+ if (matches.length === 0) {
DocumentClassName,
let classNames = []
const searchRange = {
@@ -352,14 +446,13 @@ end: { line: position.line + 10, character: 0 },
}
if (isCssContext(state, doc, position)) {
- classNames = findClassNamesInRange(doc, searchRange, 'css')
+ classNames = await findClassNamesInRange(state, doc, searchRange, 'css')
} else if (
isHtmlContext(state, doc, position) ||
isJsContext(state, doc, position)
) {
-import {
DocumentClassName,
- DocumentClassList,
+ State,
}
if (classNames.length === 0) {