add `classAttributes` setting

master
Brad Cornes 2021-10-08 16:51:14 +01:00
parent 80e2e5aba5
commit 0f4d93b96e
5 changed files with 53 additions and 14 deletions

View File

@ -14,7 +14,7 @@ import removeMeta from './util/removeMeta'
import { getColor, getColorFromValue } from './util/color' import { getColor, getColorFromValue } from './util/color'
import { isHtmlContext } from './util/html' import { isHtmlContext } from './util/html'
import { isCssContext } from './util/css' import { isCssContext } from './util/css'
import { findLast } from './util/find' import { findLast, matchClassAttributes } from './util/find'
import { stringifyConfigValue, stringifyCss } from './util/stringify' import { stringifyConfigValue, stringifyCss } from './util/stringify'
import { stringifyScreen, Screen } from './util/screens' import { stringifyScreen, Screen } from './util/screens'
import isObject from './util/isObject' import isObject from './util/isObject'
@ -327,25 +327,30 @@ export function completionsFromClassList(
} }
} }
function provideClassAttributeCompletions( async function provideClassAttributeCompletions(
state: State, state: State,
document: TextDocument, document: TextDocument,
position: Position, position: Position,
context?: CompletionContext context?: CompletionContext
): CompletionList { ): Promise<CompletionList> {
let str = document.getText({ let str = document.getText({
start: document.positionAt(Math.max(0, document.offsetAt(position) - 500)), start: document.positionAt(Math.max(0, document.offsetAt(position) - 500)),
end: position, end: position,
}) })
const match = findLast(/(?:\s|:|\()(?:class(?:Name)?|\[ngClass\])\s*=\s*['"`{]/gi, str) let matches = matchClassAttributes(
str,
(await state.editor.getConfiguration(document.uri)).tailwindCSS.classAttributes
)
if (match === null) { if (matches.length === 0) {
return null return null
} }
let match = matches[matches.length - 1]
const lexer = const lexer =
match[0][0] === ':' || match[0].trim().startsWith('[ngClass]') match[0][0] === ':' || (match[1].startsWith('[') && match[1].endsWith(']'))
? getComputedClassAttributeLexer() ? getComputedClassAttributeLexer()
: getClassAttributeLexer() : getClassAttributeLexer()
lexer.reset(str.substr(match.index + match[0].length - 1)) lexer.reset(str.substr(match.index + match[0].length - 1))
@ -490,12 +495,12 @@ function provideAtApplyCompletions(
) )
} }
function provideClassNameCompletions( async function provideClassNameCompletions(
state: State, state: State,
document: TextDocument, document: TextDocument,
position: Position, position: Position,
context?: CompletionContext context?: CompletionContext
): CompletionList { ): Promise<CompletionList> {
if (isCssContext(state, document, position)) { if (isCssContext(state, document, position)) {
return provideAtApplyCompletions(state, document, position) return provideAtApplyCompletions(state, document, position)
} }
@ -1035,7 +1040,7 @@ export async function doComplete(
if (state === null) return { items: [], isIncomplete: false } if (state === null) return { items: [], isIncomplete: false }
const result = const result =
provideClassNameCompletions(state, document, position, context) || (await provideClassNameCompletions(state, document, position, context)) ||
provideCssHelperCompletions(state, document, position) || provideCssHelperCompletions(state, document, position) ||
provideCssDirectiveCompletions(state, document, position) || provideCssDirectiveCompletions(state, document, position) ||
provideScreenDirectiveCompletions(state, document, position) || provideScreenDirectiveCompletions(state, document, position) ||

View File

@ -172,16 +172,31 @@ async function findCustomClassLists(
return result return result
} }
export function findClassListsInHtmlRange(doc: TextDocument, range?: Range): DocumentClassList[] { export function matchClassAttributes(text: string, attributes: string[]): RegExpMatchArray[] {
const attrs = attributes.filter((x) => typeof x === 'string').flatMap((a) => [a, `\\[${a}\\]`])
const re = /(?:\s|:|\()(ATTRS)\s*=\s*['"`{]/
return findAll(new RegExp(re.source.replace('ATTRS', attrs.join('|')), 'gi'), text)
}
export async function findClassListsInHtmlRange(
state: State,
doc: TextDocument,
range?: Range
): Promise<DocumentClassList[]> {
const text = doc.getText(range) const text = doc.getText(range)
const matches = findAll(/(?:\s|:|\()(?:class(?:Name)?|\[ngClass\])\s*=\s*['"`{]/gi, text)
const matches = matchClassAttributes(
text,
(await state.editor.getConfiguration(doc.uri)).tailwindCSS.classAttributes
)
const result: DocumentClassList[] = [] const result: DocumentClassList[] = []
matches.forEach((match) => { matches.forEach((match) => {
const subtext = text.substr(match.index + match[0].length - 1) const subtext = text.substr(match.index + match[0].length - 1)
let lexer = let lexer =
match[0][0] === ':' || match[0].trim().startsWith('[ngClass]') match[0][0] === ':' || (match[1].startsWith('[') && match[1].endsWith(']'))
? getComputedClassAttributeLexer() ? getComputedClassAttributeLexer()
: getClassAttributeLexer() : getClassAttributeLexer()
lexer.reset(subtext) lexer.reset(subtext)
@ -273,7 +288,7 @@ export async function findClassListsInRange(
if (mode === 'css') { if (mode === 'css') {
classLists = findClassListsInCssRange(doc, range) classLists = findClassListsInCssRange(doc, range)
} else { } else {
classLists = findClassListsInHtmlRange(doc, range) classLists = await findClassListsInHtmlRange(state, doc, range)
} }
return [...classLists, ...(includeCustom ? await findCustomClassLists(state, doc, range) : [])] return [...classLists, ...(includeCustom ? await findCustomClassLists(state, doc, range) : [])]
} }
@ -290,7 +305,9 @@ export async function findClassListsInDocument(
if (!boundaries) return [] if (!boundaries) return []
return flatten([ return flatten([
...boundaries.html.map((range) => findClassListsInHtmlRange(doc, range)), ...(await Promise.all(
boundaries.html.map((range) => findClassListsInHtmlRange(state, doc, range))
)),
...boundaries.css.map((range) => findClassListsInCssRange(doc, range)), ...boundaries.css.map((range) => findClassListsInCssRange(doc, range)),
await findCustomClassLists(state, doc), await findCustomClassLists(state, doc),
]) ])

View File

@ -39,6 +39,7 @@ export type Settings = {
tailwindCSS: { tailwindCSS: {
emmetCompletions: boolean emmetCompletions: boolean
includeLanguages: Record<string, string> includeLanguages: Record<string, string>
classAttributes: string[]
validate: boolean validate: boolean
showPixelEquivalents: boolean showPixelEquivalents: boolean
rootFontSize: number rootFontSize: number

View File

@ -66,6 +66,10 @@ This setting allows you to add additional language support. The key of each entr
Enable completions when using [Emmet](https://emmet.io/)-style syntax, for example `div.bg-red-500.uppercase`. **Default: `false`** Enable completions when using [Emmet](https://emmet.io/)-style syntax, for example `div.bg-red-500.uppercase`. **Default: `false`**
### `tailwindCSS.classAttributes`
The HTML attributes for which to provide class completions, hover previews, linting etc. **Default: `class`, `className`, `ngClass`**
### `tailwindCSS.colorDecorators` ### `tailwindCSS.colorDecorators`
Controls whether the editor should render inline color decorators for Tailwind CSS classes and helper functions. **Default: `true`** Controls whether the editor should render inline color decorators for Tailwind CSS classes and helper functions. **Default: `true`**

View File

@ -81,6 +81,18 @@
"default": {}, "default": {},
"markdownDescription": "Enable features in languages that are not supported by default. Add a mapping here between the new language and an already supported language.\n E.g.: `{\"plaintext\": \"html\"}`" "markdownDescription": "Enable features in languages that are not supported by default. Add a mapping here between the new language and an already supported language.\n E.g.: `{\"plaintext\": \"html\"}`"
}, },
"tailwindCSS.classAttributes": {
"type": "array",
"items": {
"type": "string"
},
"default": [
"class",
"className",
"ngClass"
],
"markdownDescription": "The HTML attributes for which to provide class completions, hover previews, linting etc."
},
"tailwindCSS.colorDecorators": { "tailwindCSS.colorDecorators": {
"type": "boolean", "type": "boolean",
"default": true, "default": true,