From 0f4d93b96e8d82d42de07477e9be9f0655ccdb5c Mon Sep 17 00:00:00 2001 From: Brad Cornes Date: Fri, 8 Oct 2021 16:51:14 +0100 Subject: [PATCH] add `classAttributes` setting --- .../src/completionProvider.ts | 23 +++++++++------- .../src/util/find.ts | 27 +++++++++++++++---- .../src/util/state.ts | 1 + packages/vscode-tailwindcss/README.md | 4 +++ packages/vscode-tailwindcss/package.json | 12 +++++++++ 5 files changed, 53 insertions(+), 14 deletions(-) diff --git a/packages/tailwindcss-language-service/src/completionProvider.ts b/packages/tailwindcss-language-service/src/completionProvider.ts index 9410223..f5d3614 100644 --- a/packages/tailwindcss-language-service/src/completionProvider.ts +++ b/packages/tailwindcss-language-service/src/completionProvider.ts @@ -14,7 +14,7 @@ import removeMeta from './util/removeMeta' import { getColor, getColorFromValue } from './util/color' import { isHtmlContext } from './util/html' import { isCssContext } from './util/css' -import { findLast } from './util/find' +import { findLast, matchClassAttributes } from './util/find' import { stringifyConfigValue, stringifyCss } from './util/stringify' import { stringifyScreen, Screen } from './util/screens' import isObject from './util/isObject' @@ -327,25 +327,30 @@ export function completionsFromClassList( } } -function provideClassAttributeCompletions( +async function provideClassAttributeCompletions( state: State, document: TextDocument, position: Position, context?: CompletionContext -): CompletionList { +): Promise { let str = document.getText({ start: document.positionAt(Math.max(0, document.offsetAt(position) - 500)), 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 } + let match = matches[matches.length - 1] + const lexer = - match[0][0] === ':' || match[0].trim().startsWith('[ngClass]') + match[0][0] === ':' || (match[1].startsWith('[') && match[1].endsWith(']')) ? getComputedClassAttributeLexer() : getClassAttributeLexer() lexer.reset(str.substr(match.index + match[0].length - 1)) @@ -490,12 +495,12 @@ function provideAtApplyCompletions( ) } -function provideClassNameCompletions( +async function provideClassNameCompletions( state: State, document: TextDocument, position: Position, context?: CompletionContext -): CompletionList { +): Promise { if (isCssContext(state, document, position)) { return provideAtApplyCompletions(state, document, position) } @@ -1035,7 +1040,7 @@ export async function doComplete( if (state === null) return { items: [], isIncomplete: false } const result = - provideClassNameCompletions(state, document, position, context) || + (await provideClassNameCompletions(state, document, position, context)) || provideCssHelperCompletions(state, document, position) || provideCssDirectiveCompletions(state, document, position) || provideScreenDirectiveCompletions(state, document, position) || diff --git a/packages/tailwindcss-language-service/src/util/find.ts b/packages/tailwindcss-language-service/src/util/find.ts index 6224cc5..a522de0 100644 --- a/packages/tailwindcss-language-service/src/util/find.ts +++ b/packages/tailwindcss-language-service/src/util/find.ts @@ -172,16 +172,31 @@ async function findCustomClassLists( 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 { 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[] = [] matches.forEach((match) => { const subtext = text.substr(match.index + match[0].length - 1) let lexer = - match[0][0] === ':' || match[0].trim().startsWith('[ngClass]') + match[0][0] === ':' || (match[1].startsWith('[') && match[1].endsWith(']')) ? getComputedClassAttributeLexer() : getClassAttributeLexer() lexer.reset(subtext) @@ -273,7 +288,7 @@ export async function findClassListsInRange( if (mode === 'css') { classLists = findClassListsInCssRange(doc, range) } else { - classLists = findClassListsInHtmlRange(doc, range) + classLists = await findClassListsInHtmlRange(state, doc, range) } return [...classLists, ...(includeCustom ? await findCustomClassLists(state, doc, range) : [])] } @@ -290,7 +305,9 @@ export async function findClassListsInDocument( if (!boundaries) return [] 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)), await findCustomClassLists(state, doc), ]) diff --git a/packages/tailwindcss-language-service/src/util/state.ts b/packages/tailwindcss-language-service/src/util/state.ts index 2cb86ca..d24c995 100644 --- a/packages/tailwindcss-language-service/src/util/state.ts +++ b/packages/tailwindcss-language-service/src/util/state.ts @@ -39,6 +39,7 @@ export type Settings = { tailwindCSS: { emmetCompletions: boolean includeLanguages: Record + classAttributes: string[] validate: boolean showPixelEquivalents: boolean rootFontSize: number diff --git a/packages/vscode-tailwindcss/README.md b/packages/vscode-tailwindcss/README.md index cc86c5e..8ae8915 100644 --- a/packages/vscode-tailwindcss/README.md +++ b/packages/vscode-tailwindcss/README.md @@ -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`** +### `tailwindCSS.classAttributes` + +The HTML attributes for which to provide class completions, hover previews, linting etc. **Default: `class`, `className`, `ngClass`** + ### `tailwindCSS.colorDecorators` Controls whether the editor should render inline color decorators for Tailwind CSS classes and helper functions. **Default: `true`** diff --git a/packages/vscode-tailwindcss/package.json b/packages/vscode-tailwindcss/package.json index 2bbbdec..ac731fa 100755 --- a/packages/vscode-tailwindcss/package.json +++ b/packages/vscode-tailwindcss/package.json @@ -81,6 +81,18 @@ "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\"}`" }, + "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": { "type": "boolean", "default": true,