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, 178 additions(+), 88 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
@@ -10,7 +10,7 @@ async ({ document }) => {
let doc = state.editor.documents.get(document)
if (!doc) return { colors: [] }
- return { colors: getDocumentColors(state, doc) }
+ return { colors: await getDocumentColors(state, doc) }
}
)
}
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
@@ -31,8 +31,8 @@ getComputedClassAttributeLexer,
} from './util/lexers'
import { validateApply } from './util/validateApply'
import { flagEnabled } from './util/flagEnabled'
-import MultiRegexp from 'multi-regexp2'
import { remToPx } from './util/remToPx'
+import { createMultiRegexp } from './util/createMultiRegexp'
export function completionsFromClassList(
state: State,
@@ -195,62 +195,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,16 +10,16 @@ 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
-): CssConflictDiagnostic[] {
+): Promise<CssConflictDiagnostic[]> {
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,15 +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 []
- const classNames = findClassNamesInRange(document, undefined, 'css')
+ const classNames = await findClassNamesInRange(
+ state,
+ document,
+ undefined,
+ 'css',
+ false
+ )
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,11 +10,11 @@ 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
- let classLists = findClassListsInDocument(state, document)
+ let classLists = await findClassListsInDocument(state, document)
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,20 +80,28 @@ }
return names
}
-export function findClassNamesInRange(
+export async function findClassNamesInRange(
+ state: State,
doc: TextDocument,
range?: Range,
- mode?: 'html' | 'css'
-): DocumentClassName[] {
- const classLists = findClassListsInRange(doc, range, mode)
+ mode?: 'html' | 'css',
+ includeCustom: boolean = true
+): Promise<DocumentClassName[]> {
+ const classLists = await findClassListsInRange(
+ state,
+ doc,
+ range,
+ mode,
+ includeCustom
+ )
return flatten(classLists.map(getClassNamesInClassList))
}
-export function findClassNamesInDocument(
+export async function findClassNamesInDocument(
state: State,
doc: TextDocument
-): DocumentClassName[] {
- const classLists = findClassListsInDocument(state, doc)
+): Promise<DocumentClassName[]> {
+ const classLists = await findClassListsInDocument(state, doc)
return flatten(classLists.map(getClassNamesInClassList))
}
@@ -130,12 +141,77 @@ }
})
}
+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)
+ const result: DocumentClassList[] = []
+
+ 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) {
+ const searchStart = doc.offsetAt(
+ range?.start || { line: 0, character: 0 }
+ )
+ 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
+ result.push({
+ classList: classMatch.match,
+ range: {
+ start: doc.positionAt(classMatchStart),
+ end: doc.positionAt(classMatchEnd),
+ },
+ })
+ }
+ } else {
+ result.push({
+ classList: containerMatch.match,
+ range: {
+ start: doc.positionAt(matchStart),
+ end: doc.positionAt(matchEnd),
+ },
+ })
+ }
+ }
+ } 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)
+ const matches = findAll(
+ /(?:\s|:)(?:class(?:Name)?|\[ngClass\])=['"`{]/g,
+ text
+ )
const result: DocumentClassList[] = []
matches.forEach((match) => {
@@ -232,21 +308,29 @@
return result
}
-export function findClassListsInRange(
+export async function findClassListsInRange(
+ state: State,
doc: TextDocument,
range?: Range,
- mode?: 'html' | 'css'
-): DocumentClassList[] {
+ mode?: 'html' | 'css',
+ includeCustom: boolean = true
+): Promise<DocumentClassList[]> {
+ let classLists: DocumentClassList[]
if (mode === 'css') {
- return findClassListsInCssRange(doc, range)
+ classLists = findClassListsInCssRange(doc, range)
+ } else {
+ classLists = findClassListsInHtmlRange(doc, range)
}
- return findClassListsInHtmlRange(doc, range)
+ return [
+ ...classLists,
+ ...(includeCustom ? await findCustomClassLists(state, doc, range) : []),
+ ]
}
-export function findClassListsInDocument(
+export async function findClassListsInDocument(
state: State,
doc: TextDocument
-): DocumentClassList[] {
+): Promise<DocumentClassList[]> {
if (isCssDoc(state, doc)) {
return findClassListsInCssRange(doc)
}
@@ -257,6 +341,7 @@
return flatten([
...boundaries.html.map((range) => findClassListsInHtmlRange(doc, range)),
...boundaries.css.map((range) => findClassListsInCssRange(doc, range)),
+ await findCustomClassLists(state, doc),
])
}
@@ -323,11 +408,11 @@ const { line, col } = lineColumn(str + '\n', index)
return { line: line - 1, character: col - 1 }
}
-export function findClassNameAtPosition(
+export async function findClassNameAtPosition(
state: State,
doc: TextDocument,
position: Position
-): DocumentClassName {
+): Promise<DocumentClassName> {
let classNames = []
const searchRange = {
start: { line: Math.max(position.line - 10, 0), character: 0 },
@@ -335,12 +420,12 @@ 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)
) {
- classNames = findClassNamesInRange(doc, searchRange, 'html')
+ classNames = await findClassNamesInRange(state, doc, searchRange, 'html')
}
if (classNames.length === 0) {