From 973c7fbd28e30fe8c4b630e4e4bff8e4260e245d Mon Sep 17 00:00:00 2001 From: Stephen Zhou <38493346+hyoban@users.noreply.github.com> Date: Sun, 26 Jan 2025 16:08:42 +0800 Subject: [PATCH 01/10] feat: annotation --- .../src/projects.ts | 18 ++++++- .../tailwindcss-language-server/src/tw.ts | 14 +++++ .../src/annotation.ts | 52 +++++++++++++++++++ .../src/util/state.ts | 1 + packages/vscode-tailwindcss/src/extension.ts | 11 ++++ 5 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 packages/tailwindcss-language-service/src/annotation.ts diff --git a/packages/tailwindcss-language-server/src/projects.ts b/packages/tailwindcss-language-server/src/projects.ts index 426882a7..d8417c52 100644 --- a/packages/tailwindcss-language-server/src/projects.ts +++ b/packages/tailwindcss-language-server/src/projects.ts @@ -17,7 +17,7 @@ import type { DocumentLink, } from 'vscode-languageserver/node' import { FileChangeType } from 'vscode-languageserver/node' -import type { TextDocument } from 'vscode-languageserver-textdocument' +import type { Range, TextDocument } from 'vscode-languageserver-textdocument' import { URI } from 'vscode-uri' import { showError, SilentError } from './util/error' import * as path from 'node:path' @@ -35,6 +35,7 @@ import stackTrace from 'stack-trace' import extractClassNames from './lib/extractClassNames' import { klona } from 'klona/full' import { doHover } from '@tailwindcss/language-service/src/hoverProvider' +import { updateAnnotation } from '@tailwindcss/language-service/src/annotation' import { Resolver } from './resolver' import { doComplete, @@ -106,6 +107,7 @@ export interface ProjectService { onCompletionResolve(item: CompletionItem): Promise provideDiagnostics(document: TextDocument): void provideDiagnosticsForce(document: TextDocument): void + provideAnnotations(document: TextDocument): Promise onDocumentColor(params: DocumentColorParams): Promise onColorPresentation(params: ColorPresentationParams): Promise onCodeAction(params: CodeActionParams): Promise @@ -470,6 +472,9 @@ export async function createProjectService( postcss: { version: null, module: null }, resolveConfig: { module: null }, loadConfig: { module: null }, + defaultExtractor: { + module: require('tailwindcss/lib/lib/defaultExtractor').defaultExtractor, + } } return tryRebuild() @@ -695,6 +700,9 @@ export async function createProjectService( loadConfig: { module: loadConfigFn }, transformThemeValue: { module: transformThemeValueFn }, jit: jitModules, + defaultExtractor: { + module: require('tailwindcss/lib/lib/defaultExtractor').defaultExtractor, + } } state.browserslist = browserslist state.featureFlags = featureFlags @@ -1182,6 +1190,14 @@ export async function createProjectService( if (!state.enabled) return provideDiagnostics(state, document) }, + provideAnnotations: async (params) => { + if (!state.enabled) return [] + let document = documentService.getDocument(params.uri) + if (!document) return [] + await state.editor.getConfiguration(document.uri) + if (await isExcluded(state, document)) return [] + return updateAnnotation(state, params) + }, async onDocumentColor(params: DocumentColorParams): Promise { return withFallback(async () => { if (!state.enabled) return [] diff --git a/packages/tailwindcss-language-server/src/tw.ts b/packages/tailwindcss-language-server/src/tw.ts index e5d6522f..2c27599b 100644 --- a/packages/tailwindcss-language-server/src/tw.ts +++ b/packages/tailwindcss-language-server/src/tw.ts @@ -624,6 +624,13 @@ export class TW { this.disposables.push( this.documentService.onDidChangeContent((change) => { this.getProject(change.document)?.provideDiagnostics(change.document) + + const { document } = change + this.getProject(document) + ?.provideAnnotations(document) + .then((annotations) => { + this.connection.sendRequest('@/tailwindCSS/annotations', annotations) + }) }), ) @@ -644,6 +651,13 @@ export class TW { await this.connection.sendNotification('@/tailwindCSS/documentReady', { uri: event.document.uri, }) + + const { document } = event + this.getProject(document) + ?.provideAnnotations(document) + .then((annotations) => { + this.connection.sendRequest('@/tailwindCSS/annotations', annotations) + }) }), ) diff --git a/packages/tailwindcss-language-service/src/annotation.ts b/packages/tailwindcss-language-service/src/annotation.ts new file mode 100644 index 00000000..23efe4b9 --- /dev/null +++ b/packages/tailwindcss-language-service/src/annotation.ts @@ -0,0 +1,52 @@ +import type { Range, TextDocument } from 'vscode-languageserver-textdocument' +import * as jit from './util/jit' +import type { State } from './util/state' + +export async function updateAnnotation(state: State, document: TextDocument): Promise { + const text = document.getText() + + const extractorContext = { + tailwindConfig: { + separator: '-', + prefix: '', + }, + } + if (state.jitContext?.tailwindConfig?.separator) { + extractorContext.tailwindConfig.separator = state.jitContext.tailwindConfig.separator + } + if (state.jitContext?.tailwindConfig?.prefix) { + extractorContext.tailwindConfig.prefix = state.jitContext.tailwindConfig.prefix + } + + const classNames = state.modules.defaultExtractor.module(extractorContext)(text) as string[] + + const result: Range[] = [] + + if (state.v4) { + const rules = state.designSystem.compile(classNames) + + let index = 0 + classNames.forEach((className, i) => { + const start = text.indexOf(className, index) + const end = start + className.length + if (rules.at(i).nodes.length > 0 && start !== -1) { + result.push({ start: document.positionAt(start), end: document.positionAt(end) }) + } + index = end + }) + } else if (state.jit) { + const rules = jit.generateRules(state, classNames).rules + + let index = 0 + classNames.forEach((className, i) => { + const start = text.indexOf(className, index) + const end = start + className.length + if ((rules.at(i).raws.tailwind as any)?.candidate === className && start !== -1) { + result.push({ start: document.positionAt(start), end: document.positionAt(end) }) + } + index = end + }) + } + + return result +} diff --git a/packages/tailwindcss-language-service/src/util/state.ts b/packages/tailwindcss-language-service/src/util/state.ts index d16ca186..8ac3ec27 100644 --- a/packages/tailwindcss-language-service/src/util/state.ts +++ b/packages/tailwindcss-language-service/src/util/state.ts @@ -123,6 +123,7 @@ export interface State { expandApplyAtRules: { module: any } evaluateTailwindFunctions?: { module: any } } + defaultExtractor: { module: any } } v4?: boolean diff --git a/packages/vscode-tailwindcss/src/extension.ts b/packages/vscode-tailwindcss/src/extension.ts index d727f923..382892a3 100755 --- a/packages/vscode-tailwindcss/src/extension.ts +++ b/packages/vscode-tailwindcss/src/extension.ts @@ -16,6 +16,7 @@ import { Position, Range, RelativePattern, + DecorationRangeBehavior, } from 'vscode' import type { DocumentFilter, @@ -355,6 +356,13 @@ export async function activate(context: ExtensionContext) { context.subscriptions.push(colorDecorationType) + let underlineDecorationType = Window.createTextEditorDecorationType({ + textDecoration: 'none; border-bottom: 1px dashed currentColor', + rangeBehavior: DecorationRangeBehavior.ClosedClosed, + }) + + context.subscriptions.push(underlineDecorationType) + /** * Clear all decorated colors from all visible text editors */ @@ -535,6 +543,9 @@ export async function activate(context: ExtensionContext) { client.onNotification('@/tailwindCSS/projectInitialized', updateActiveTextEditorContext) client.onNotification('@/tailwindCSS/projectReset', updateActiveTextEditorContext) client.onNotification('@/tailwindCSS/projectsDestroyed', resetActiveTextEditorContext) + client.onRequest('@/tailwindCSS/annotations', (ranges) => { + Window.activeTextEditor.setDecorations(underlineDecorationType, ranges) + }) client.onRequest('@/tailwindCSS/getDocumentSymbols', showSymbols) interface ErrorNotification { From 6f3bdc84acbf16d2fef1b15b7690de7ef428b93a Mon Sep 17 00:00:00 2001 From: Stephen Zhou <38493346+hyoban@users.noreply.github.com> Date: Sun, 26 Jan 2025 16:32:21 +0800 Subject: [PATCH 02/10] fix: compare candidate for v3 --- packages/tailwindcss-language-service/src/annotation.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/tailwindcss-language-service/src/annotation.ts b/packages/tailwindcss-language-service/src/annotation.ts index 23efe4b9..8d32388a 100644 --- a/packages/tailwindcss-language-service/src/annotation.ts +++ b/packages/tailwindcss-language-service/src/annotation.ts @@ -35,13 +35,13 @@ export async function updateAnnotation(state: State, document: TextDocument): Pr index = end }) } else if (state.jit) { - const rules = jit.generateRules(state, classNames).rules + const rules = state.modules.jit.generateRules.module(classNames, state.jitContext) let index = 0 - classNames.forEach((className, i) => { + classNames.forEach((className) => { const start = text.indexOf(className, index) const end = start + className.length - if ((rules.at(i).raws.tailwind as any)?.candidate === className && start !== -1) { + if (rules?.find(([, i]) => (i.raws.tailwind as any)?.candidate === className)) { result.push({ start: document.positionAt(start), end: document.positionAt(end) }) } index = end From 25ada8ab940701ae760a142db02951a6a7fbec87 Mon Sep 17 00:00:00 2001 From: Stephen Zhou <38493346+hyoban@users.noreply.github.com> Date: Sun, 26 Jan 2025 16:37:58 +0800 Subject: [PATCH 03/10] feat: add configuration --- packages/tailwindcss-language-server/src/projects.ts | 3 ++- packages/tailwindcss-language-service/src/util/state.ts | 1 + packages/vscode-tailwindcss/package.json | 6 ++++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/tailwindcss-language-server/src/projects.ts b/packages/tailwindcss-language-server/src/projects.ts index d8417c52..7772cb0f 100644 --- a/packages/tailwindcss-language-server/src/projects.ts +++ b/packages/tailwindcss-language-server/src/projects.ts @@ -1194,7 +1194,8 @@ export async function createProjectService( if (!state.enabled) return [] let document = documentService.getDocument(params.uri) if (!document) return [] - await state.editor.getConfiguration(document.uri) + let settings = await state.editor.getConfiguration(document.uri) + if (!settings.tailwindCSS.annotations) return [] if (await isExcluded(state, document)) return [] return updateAnnotation(state, params) }, diff --git a/packages/tailwindcss-language-service/src/util/state.ts b/packages/tailwindcss-language-service/src/util/state.ts index 8ac3ec27..f058c7f1 100644 --- a/packages/tailwindcss-language-service/src/util/state.ts +++ b/packages/tailwindcss-language-service/src/util/state.ts @@ -47,6 +47,7 @@ export type TailwindCssSettings = { classAttributes: string[] suggestions: boolean hovers: boolean + annotations: boolean codeActions: boolean validate: boolean showPixelEquivalents: boolean diff --git a/packages/vscode-tailwindcss/package.json b/packages/vscode-tailwindcss/package.json index 85005f65..5d060a3f 100644 --- a/packages/vscode-tailwindcss/package.json +++ b/packages/vscode-tailwindcss/package.json @@ -196,6 +196,12 @@ "markdownDescription": "Enable hovers.", "scope": "language-overridable" }, + "tailwindCSS.annotations": { + "type": "boolean", + "default": false, + "markdownDescription": "Enable annotations.", + "scope": "language-overridable" + }, "tailwindCSS.codeActions": { "type": "boolean", "default": true, From fa31d19f2a4e758b372af8e78923d9d47d3ab657 Mon Sep 17 00:00:00 2001 From: Stephen Zhou <38493346+hyoban@users.noreply.github.com> Date: Sun, 26 Jan 2025 16:38:58 +0800 Subject: [PATCH 04/10] fix: type --- packages/tailwindcss-language-server/src/config.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/tailwindcss-language-server/src/config.ts b/packages/tailwindcss-language-server/src/config.ts index 90fb3207..e173f244 100644 --- a/packages/tailwindcss-language-server/src/config.ts +++ b/packages/tailwindcss-language-server/src/config.ts @@ -16,6 +16,7 @@ function getDefaultSettings(): Settings { classAttributes: ['class', 'className', 'ngClass', 'class:list'], codeActions: true, hovers: true, + annotations: false, suggestions: true, validate: true, colorDecorators: true, From 46c1266d4874cd79874d023189adafdfda197d4b Mon Sep 17 00:00:00 2001 From: Stephen Zhou <38493346+hyoban@users.noreply.github.com> Date: Sun, 26 Jan 2025 16:45:18 +0800 Subject: [PATCH 05/10] fix: catch --- .../src/projects.ts | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/packages/tailwindcss-language-server/src/projects.ts b/packages/tailwindcss-language-server/src/projects.ts index 7772cb0f..3efe7477 100644 --- a/packages/tailwindcss-language-server/src/projects.ts +++ b/packages/tailwindcss-language-server/src/projects.ts @@ -1191,13 +1191,18 @@ export async function createProjectService( provideDiagnostics(state, document) }, provideAnnotations: async (params) => { - if (!state.enabled) return [] - let document = documentService.getDocument(params.uri) - if (!document) return [] - let settings = await state.editor.getConfiguration(document.uri) - if (!settings.tailwindCSS.annotations) return [] - if (await isExcluded(state, document)) return [] - return updateAnnotation(state, params) + try { + if (!state.enabled) return [] + let document = documentService.getDocument(params.uri) + if (!document) return [] + let settings = await state.editor.getConfiguration(document.uri) + if (!settings.tailwindCSS.annotations) return [] + if (await isExcluded(state, document)) return [] + return updateAnnotation(state, params) + } catch (error) { + console.error(error) + return [] + } }, async onDocumentColor(params: DocumentColorParams): Promise { return withFallback(async () => { From c392424fab5e90b3cfac6c3e6b948aa6c9fde893 Mon Sep 17 00:00:00 2001 From: Stephen Zhou <38493346+hyoban@users.noreply.github.com> Date: Sun, 26 Jan 2025 16:55:57 +0800 Subject: [PATCH 06/10] chore: clean --- packages/tailwindcss-language-service/src/annotation.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/tailwindcss-language-service/src/annotation.ts b/packages/tailwindcss-language-service/src/annotation.ts index 8d32388a..4017cce8 100644 --- a/packages/tailwindcss-language-service/src/annotation.ts +++ b/packages/tailwindcss-language-service/src/annotation.ts @@ -1,5 +1,4 @@ import type { Range, TextDocument } from 'vscode-languageserver-textdocument' -import * as jit from './util/jit' import type { State } from './util/state' export async function updateAnnotation(state: State, document: TextDocument): Promise { From ca215f78f82c2f72153accdb0a4a42e0a79681ae Mon Sep 17 00:00:00 2001 From: Stephen Zhou <38493346+hyoban@users.noreply.github.com> Date: Sun, 26 Jan 2025 19:14:36 +0800 Subject: [PATCH 07/10] fix: handle init documents --- .../tailwindcss-language-server/src/tw.ts | 21 +++++++++++++++++-- packages/vscode-tailwindcss/src/extension.ts | 6 ++++-- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/packages/tailwindcss-language-server/src/tw.ts b/packages/tailwindcss-language-server/src/tw.ts index 2c27599b..83b820cf 100644 --- a/packages/tailwindcss-language-server/src/tw.ts +++ b/packages/tailwindcss-language-server/src/tw.ts @@ -629,7 +629,10 @@ export class TW { this.getProject(document) ?.provideAnnotations(document) .then((annotations) => { - this.connection.sendRequest('@/tailwindCSS/annotations', annotations) + this.connection.sendRequest('@/tailwindCSS/annotations', { + uri: document.uri, + annotations, + }) }) }), ) @@ -656,11 +659,25 @@ export class TW { this.getProject(document) ?.provideAnnotations(document) .then((annotations) => { - this.connection.sendRequest('@/tailwindCSS/annotations', annotations) + this.connection.sendRequest('@/tailwindCSS/annotations', { + uri: document.uri, + annotations, + }) }) }), ) + this.documentService.getAllDocuments().forEach((document) => { + this.getProject(document) + ?.provideAnnotations(document) + .then((annotations) => { + this.connection.sendRequest('@/tailwindCSS/annotations', { + uri: document.uri, + annotations, + }) + }) + }) + if (this.initializeParams.capabilities.workspace.workspaceFolders) { this.disposables.push( this.connection.workspace.onDidChangeWorkspaceFolders(async (evt) => { diff --git a/packages/vscode-tailwindcss/src/extension.ts b/packages/vscode-tailwindcss/src/extension.ts index 382892a3..209429e1 100755 --- a/packages/vscode-tailwindcss/src/extension.ts +++ b/packages/vscode-tailwindcss/src/extension.ts @@ -543,8 +543,10 @@ export async function activate(context: ExtensionContext) { client.onNotification('@/tailwindCSS/projectInitialized', updateActiveTextEditorContext) client.onNotification('@/tailwindCSS/projectReset', updateActiveTextEditorContext) client.onNotification('@/tailwindCSS/projectsDestroyed', resetActiveTextEditorContext) - client.onRequest('@/tailwindCSS/annotations', (ranges) => { - Window.activeTextEditor.setDecorations(underlineDecorationType, ranges) + client.onRequest('@/tailwindCSS/annotations', ({ uri, annotations }) => { + Window.visibleTextEditors + .find((editor) => editor.document.uri.toString() === uri) + ?.setDecorations(underlineDecorationType, annotations) }) client.onRequest('@/tailwindCSS/getDocumentSymbols', showSymbols) From e94eb5306352ea206f8b53d86bbafbd4a0f90514 Mon Sep 17 00:00:00 2001 From: Stephen Zhou <38493346+hyoban@users.noreply.github.com> Date: Sun, 26 Jan 2025 19:55:00 +0800 Subject: [PATCH 08/10] fix: handle semicolon --- packages/tailwindcss-language-service/src/annotation.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/tailwindcss-language-service/src/annotation.ts b/packages/tailwindcss-language-service/src/annotation.ts index 4017cce8..c7980300 100644 --- a/packages/tailwindcss-language-service/src/annotation.ts +++ b/packages/tailwindcss-language-service/src/annotation.ts @@ -17,7 +17,9 @@ export async function updateAnnotation(state: State, document: TextDocument): Pr extractorContext.tailwindConfig.prefix = state.jitContext.tailwindConfig.prefix } - const classNames = state.modules.defaultExtractor.module(extractorContext)(text) as string[] + const classNames = ( + state.modules.defaultExtractor.module(extractorContext)(text) as string[] + ).map((className) => className.replace(/;$/, '')) const result: Range[] = [] From 467fa75616caa03685dc1039bf0c163820698b3a Mon Sep 17 00:00:00 2001 From: Stephen Zhou <38493346+hyoban@users.noreply.github.com> Date: Sun, 26 Jan 2025 21:55:12 +0800 Subject: [PATCH 09/10] Revert "fix: handle semicolon" This reverts commit e94eb5306352ea206f8b53d86bbafbd4a0f90514. --- packages/tailwindcss-language-service/src/annotation.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/tailwindcss-language-service/src/annotation.ts b/packages/tailwindcss-language-service/src/annotation.ts index c7980300..4017cce8 100644 --- a/packages/tailwindcss-language-service/src/annotation.ts +++ b/packages/tailwindcss-language-service/src/annotation.ts @@ -17,9 +17,7 @@ export async function updateAnnotation(state: State, document: TextDocument): Pr extractorContext.tailwindConfig.prefix = state.jitContext.tailwindConfig.prefix } - const classNames = ( - state.modules.defaultExtractor.module(extractorContext)(text) as string[] - ).map((className) => className.replace(/;$/, '')) + const classNames = state.modules.defaultExtractor.module(extractorContext)(text) as string[] const result: Range[] = [] From 33476e7a1e13f7cba899105aaa49fd2c5fd54cfa Mon Sep 17 00:00:00 2001 From: Stephen Zhou <38493346+hyoban@users.noreply.github.com> Date: Wed, 5 Feb 2025 10:47:12 +0800 Subject: [PATCH 10/10] chore: update --- packages/tailwindcss-language-server/src/projects.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/tailwindcss-language-server/src/projects.ts b/packages/tailwindcss-language-server/src/projects.ts index 7356d077..5e141d96 100644 --- a/packages/tailwindcss-language-server/src/projects.ts +++ b/packages/tailwindcss-language-server/src/projects.ts @@ -484,7 +484,7 @@ export async function createProjectService( loadConfig: { module: null }, defaultExtractor: { module: require('tailwindcss/lib/lib/defaultExtractor').defaultExtractor, - } + }, } return tryRebuild() @@ -698,6 +698,9 @@ export async function createProjectService( postcss: { version: null, module: null }, resolveConfig: { module: null }, loadConfig: { module: null }, + defaultExtractor: { + module: require('tailwindcss/lib/lib/defaultExtractor').defaultExtractor, + }, } return tryRebuild() @@ -740,7 +743,7 @@ export async function createProjectService( jit: jitModules, defaultExtractor: { module: require('tailwindcss/lib/lib/defaultExtractor').defaultExtractor, - } + }, } state.browserslist = browserslist state.featureFlags = featureFlags