Skip to content

Commit b4b3a2a

Browse files
LaurynasGrLaurynas Grigutisthecrypticace
authored
Add tailwindCSS.classFunctions setting (#1258)
This PR adds `tailwindCSS.classFunctions` option to the settings to add simple and performant class completions, hover previews, linting etc. for such cases: ```ts const classes = cn( 'pointer-events-auto relative flex bg-red-500', 'items-center justify-between overflow-hidden', 'md:min-w-[20rem] md:max-w-[37.5rem] md:py-sm pl-md py-xs pr-xs gap-sm w-full', 'data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)]', Date.now() > 15000 ? 'text-red-200' : 'text-red-700', 'data-[swipe=move]:transition-none', ) ``` ![image](https://github.com/user-attachments/assets/eb5728af-6412-4323-b14c-893472f2e897) ```ts const variants = cva( cn( 'pointer-events-auto relative flex bg-green-500', 'md:min-w-[20rem] md:max-w-[37.5rem] md:py-sm pl-md py-xs pr-xs gap-sm w-full', 'md:h-[calc(100%-2rem)]', 'bg-red-700', ), { variants: { mobile: { default: 'bottom-0 left-0', fullScreen: ` inset-0 md:h-[calc(100%-2rem)] rounded-none bg-blue-700 `, }, }, defaultVariants: { mobile: 'default', }, }, ) ``` ![image](https://github.com/user-attachments/assets/47025c28-50bc-4aa5-874c-06434835141b) ```ts const tagged = cn` pointer-events-auto relative flex bg-red-500 items-center justify-between overflow-hidden md:min-w-[20rem] md:max-w-[37.5rem] md:py-sm pl-md py-xs pr-xs gap-sm w-full data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] md:h-[calc(100%-2rem)] text-green-700 data-[swipe=move]:transition-none ` ``` ![image](https://github.com/user-attachments/assets/0d112788-dd4f-48dc-aa01-0407b6c6b119) --------- Co-authored-by: Laurynas Grigutis <laurynas.grigutis@barbora.lt> Co-authored-by: Jordan Pittman <jordan@cryptica.me>
1 parent 7a4f8d5 commit b4b3a2a

File tree

18 files changed

+999
-216
lines changed

18 files changed

+999
-216
lines changed

packages/tailwindcss-language-server/src/config.ts

Lines changed: 5 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,16 @@
11
import merge from 'deepmerge'
22
import { isObject } from './utils'
3-
import type { Settings } from '@tailwindcss/language-service/src/util/state'
3+
import {
4+
getDefaultTailwindSettings,
5+
type Settings,
6+
} from '@tailwindcss/language-service/src/util/state'
47
import type { Connection } from 'vscode-languageserver'
58

69
export interface SettingsCache {
710
get(uri?: string): Promise<Settings>
811
clear(): void
912
}
1013

11-
function getDefaultSettings(): Settings {
12-
return {
13-
editor: { tabSize: 2 },
14-
tailwindCSS: {
15-
inspectPort: null,
16-
emmetCompletions: false,
17-
classAttributes: ['class', 'className', 'ngClass', 'class:list'],
18-
codeActions: true,
19-
hovers: true,
20-
suggestions: true,
21-
validate: true,
22-
colorDecorators: true,
23-
rootFontSize: 16,
24-
lint: {
25-
cssConflict: 'warning',
26-
invalidApply: 'error',
27-
invalidScreen: 'error',
28-
invalidVariant: 'error',
29-
invalidConfigPath: 'error',
30-
invalidTailwindDirective: 'error',
31-
invalidSourceDirective: 'error',
32-
recommendedVariantOrder: 'warning',
33-
},
34-
showPixelEquivalents: true,
35-
includeLanguages: {},
36-
files: { exclude: ['**/.git/**', '**/node_modules/**', '**/.hg/**', '**/.svn/**'] },
37-
experimental: {
38-
classRegex: [],
39-
configFile: null,
40-
},
41-
},
42-
}
43-
}
44-
4514
export function createSettingsCache(connection: Connection): SettingsCache {
4615
const cache: Map<string, Settings> = new Map()
4716

@@ -73,7 +42,7 @@ export function createSettingsCache(connection: Connection): SettingsCache {
7342
tailwindCSS = isObject(tailwindCSS) ? tailwindCSS : {}
7443

7544
return merge<Settings>(
76-
getDefaultSettings(),
45+
getDefaultTailwindSettings(),
7746
{ editor, tailwindCSS },
7847
{ arrayMerge: (_destinationArray, sourceArray, _options) => sourceArray },
7948
)

packages/tailwindcss-language-server/tests/utils/client.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ import { createConfiguration, Configuration } from './configuration'
4444
import { clearLanguageBoundariesCache } from '@tailwindcss/language-service/src/util/getLanguageBoundaries'
4545
import { DefaultMap } from '../../src/util/default-map'
4646
import { connect, ConnectOptions } from './connection'
47-
import type { DeepPartial } from './types'
47+
import type { DeepPartial } from '@tailwindcss/language-service/src/types'
4848

4949
export interface DocumentDescriptor {
5050
/**

packages/tailwindcss-language-server/tests/utils/configuration.ts

Lines changed: 5 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
import type { Settings } from '@tailwindcss/language-service/src/util/state'
1+
import {
2+
getDefaultTailwindSettings,
3+
type Settings,
4+
} from '@tailwindcss/language-service/src/util/state'
25
import { URI } from 'vscode-uri'
36
import type { DeepPartial } from './types'
47
import { CacheMap } from '../../src/cache-map'
@@ -10,41 +13,7 @@ export interface Configuration {
1013
}
1114

1215
export function createConfiguration(): Configuration {
13-
let defaults: Settings = {
14-
editor: {
15-
tabSize: 2,
16-
},
17-
tailwindCSS: {
18-
inspectPort: null,
19-
emmetCompletions: false,
20-
includeLanguages: {},
21-
classAttributes: ['class', 'className', 'ngClass', 'class:list'],
22-
suggestions: true,
23-
hovers: true,
24-
codeActions: true,
25-
validate: true,
26-
showPixelEquivalents: true,
27-
rootFontSize: 16,
28-
colorDecorators: true,
29-
lint: {
30-
cssConflict: 'warning',
31-
invalidApply: 'error',
32-
invalidScreen: 'error',
33-
invalidVariant: 'error',
34-
invalidConfigPath: 'error',
35-
invalidTailwindDirective: 'error',
36-
invalidSourceDirective: 'error',
37-
recommendedVariantOrder: 'warning',
38-
},
39-
experimental: {
40-
classRegex: [],
41-
configFile: {},
42-
},
43-
files: {
44-
exclude: ['**/.git/**', '**/node_modules/**', '**/.hg/**', '**/.svn/**'],
45-
},
46-
},
47-
}
16+
let defaults = getDefaultTailwindSettings()
4817

4918
/**
5019
* Settings per file or directory URI

packages/tailwindcss-language-server/tests/utils/types.ts

Lines changed: 0 additions & 9 deletions
This file was deleted.

packages/tailwindcss-language-service/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,11 @@
4141
},
4242
"devDependencies": {
4343
"@types/css.escape": "^1.5.2",
44+
"@types/dedent": "^0.7.2",
4445
"@types/line-column": "^1.0.2",
4546
"@types/node": "^18.19.33",
4647
"@types/stringify-object": "^4.0.5",
48+
"dedent": "^1.5.3",
4749
"esbuild": "^0.25.0",
4850
"esbuild-node-externals": "^1.9.0",
4951
"minimist": "^1.2.8",

packages/tailwindcss-language-service/scripts/build.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ let build = await esbuild.context({
3030
// Call the tsc command to generate the types
3131
spawnSync(
3232
'tsc',
33-
['--emitDeclarationOnly', '--outDir', path.resolve(__dirname, '../dist')],
33+
['-p', path.resolve(__dirname, './tsconfig.build.json'), '--emitDeclarationOnly', '--outDir', path.resolve(__dirname, '../dist')],
3434
{
3535
stdio: 'inherit',
3636
},
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"extends": "../tsconfig.json",
3+
"exclude": ["../src/**/*.test.ts"]
4+
}

packages/tailwindcss-language-service/src/completionProvider.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import removeMeta from './util/removeMeta'
1515
import { formatColor, getColor, getColorFromValue } from './util/color'
1616
import { isHtmlContext, isHtmlDoc, isVueDoc } from './util/html'
1717
import { isCssContext } from './util/css'
18-
import { findLast, matchClassAttributes } from './util/find'
18+
import { findLast, matchClassAttributes, matchClassFunctions } from './util/find'
1919
import { stringifyConfigValue, stringifyCss } from './util/stringify'
2020
import { stringifyScreen, Screen } from './util/screens'
2121
import isObject from './util/isObject'
@@ -45,6 +45,8 @@ import type { ThemeEntry } from './util/v4'
4545
import { segment } from './util/segment'
4646
import { resolveKnownThemeKeys, resolveKnownThemeNamespaces } from './util/v4/theme-keys'
4747
import { SEARCH_RANGE } from './util/constants'
48+
import { getLanguageBoundaries } from './util/getLanguageBoundaries'
49+
import { isWithinRange } from './util/isWithinRange'
4850

4951
let isUtil = (className) =>
5052
Array.isArray(className.__info)
@@ -747,6 +749,25 @@ async function provideClassAttributeCompletions(
747749

748750
let matches = matchClassAttributes(str, settings.classAttributes)
749751

752+
let boundaries = getLanguageBoundaries(state, document)
753+
754+
for (let boundary of boundaries ?? []) {
755+
let isJsContext = boundary.type === 'js' || boundary.type === 'jsx'
756+
if (!isJsContext) continue
757+
if (!settings.classFunctions?.length) continue
758+
if (!isWithinRange(position, boundary.range)) continue
759+
760+
let str = document.getText(boundary.range)
761+
let offset = document.offsetAt(boundary.range.start)
762+
let fnMatches = matchClassFunctions(str, settings.classFunctions)
763+
764+
fnMatches.forEach((match) => {
765+
if (match.index) match.index += offset
766+
})
767+
768+
matches.push(...fnMatches)
769+
}
770+
750771
if (matches.length === 0) {
751772
return null
752773
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export type DeepPartial<T> = {
2+
[P in keyof T]?: T[P] extends ((...args: any) => any) | ReadonlyArray<any> | Date
3+
? T[P]
4+
: T[P] extends (infer U)[]
5+
? U[]
6+
: T[P] extends object
7+
? DeepPartial<T[P]>
8+
: T[P]
9+
}

0 commit comments

Comments
 (0)