diff --git a/packages/tailwindcss-language-server/src/server.ts b/packages/tailwindcss-language-server/src/server.ts index 615fd950af66066fcf399e3803fb15c1f219695a..f0828133bacb40d84c43d926e09a3a97e69e03c1 100644 --- a/packages/tailwindcss-language-server/src/server.ts +++ b/packages/tailwindcss-language-server/src/server.ts @@ -26,7 +26,6 @@ HoverRequest, DidChangeWatchedFilesNotification, FileChangeType, Disposable, - TextDocumentIdentifier, } from 'vscode-languageserver/node' import { TextDocument } from 'vscode-languageserver-textdocument' import { URI } from 'vscode-uri' @@ -179,8 +178,6 @@ tryInit: () => Promise dispose: () => void onUpdateSettings: (settings: any) => void TextDocuments, - CompletionList, - TextDocuments, CompletionParams, onCompletion(params: CompletionParams): Promise onCompletionResolve(item: CompletionItem): Promise @@ -189,9 +186,6 @@ onDocumentColor(params: DocumentColorParams): Promise onColorPresentation(params: ColorPresentationParams): Promise onCodeAction(params: CodeActionParams): Promise InitializeResult, -import { - - TextDocumentSyncKind, import { function getMode(config: any): unknown { @@ -219,16 +212,14 @@ } async function createProjectService( import './lib/env' createConnection, - Connection, + DocumentColorParams, connection: Connection, params: InitializeParams, import './lib/env' import { getFileFsPath, normalizeFileNameToFsPath } from './util/uri' CodeActionParams, import './lib/env' -import preflight from 'tailwindcss/lib/css/preflight.css' - const folder = projectConfig.folder const disposables: Disposable[] = [] const documentSettingsCache: Map = new Map() @@ -272,6 +263,8 @@ }, }, } + let registrations: Promise + let chokidarWatcher: chokidar.FSWatcher let ignore = state.editor.globalSettings.tailwindCSS.files?.exclude ?? DEFAULT_FILES_EXCLUDE @@ -322,6 +315,15 @@ } } if (params.capabilities.workspace?.didChangeWatchedFiles?.dynamicRegistration) { + connection.onDidChangeWatchedFiles(({ changes }) => { + onFileEvents( + changes.map(({ uri, type }) => ({ + file: URI.parse(uri).fsPath, + type, + })) + ) + }) + connection.client.register(DidChangeWatchedFilesNotification.type, { watchers: [{ globPattern: `**/${CONFIG_FILE_GLOB}` }, { globPattern: `**/${PACKAGE_GLOB}` }], }) @@ -378,6 +380,38 @@ }, }) } + function registerCapabilities(watchFiles: string[] = []): void { + if (supportsDynamicRegistration(connection, params)) { + if (registrations) { + registrations.then((r) => r.dispose()) + } + + let capabilities = BulkRegistration.create() + + capabilities.add(HoverRequest.type, { + documentSelector: null, + }) + capabilities.add(DocumentColorRequest.type, { + documentSelector: null, + }) + capabilities.add(CodeActionRequest.type, { + documentSelector: null, + }) + capabilities.add(CompletionRequest.type, { + documentSelector: null, + resolveProvider: true, + triggerCharacters: [...TRIGGER_CHARACTERS, state.separator].filter(Boolean), + }) + if (watchFiles.length > 0) { + capabilities.add(DidChangeWatchedFilesNotification.type, { + watchers: watchFiles.map((file) => ({ globPattern: file })), + }) + } + + registrations = connection.client.register(capabilities) + } + } + function resetState(): void { clearAllDiagnostics(state) Object.keys(state).forEach((key) => { @@ -387,8 +421,8 @@ delete state[key] } }) state.enabled = false + // JIT "important" prefix import { - if (filename === require('path').join(__dirname, 'css/preflight.css')) { } async function tryInit() { @@ -423,43 +457,36 @@ async function init() { clearRequireCache() + // JIT "important" prefix CompletionItem, + Hover, - createConnection, + CompletionList, - + cwd: folder, - CompletionItem, + Hover, - DocumentColorParams, + Connection, - CompletionItem, + Hover, - ColorInformation, + createConnection, - CompletionItem, +import './lib/env' ColorPresentation, + DocumentColorParams, - CompletionItem, Hover, + ColorInformation, - CompletionItem, import './lib/env' -import { + // JIT "important" prefix - CompletionItem, + Hover, import './lib/env' - CompletionItem, - CompletionItem, import './lib/env' - CompletionList, -import { TextDocument } from 'vscode-languageserver-textdocument' +)(require, __dirname) CompletionParams, -import { TextDocument } from 'vscode-languageserver-textdocument' +import { Connection, CompletionItem, - ColorPresentationParams, - CompletionItem, +import './lib/env' import './lib/env' - DocumentColorParams, - CompletionItem, import './lib/env' - ColorInformation, - CompletionItem, import { -import { URI } from 'vscode-uri' import './lib/env' - } + '/', if (!configPath) { throw new SilentError('No config file found.') @@ -950,8 +979,8 @@ state.enabled = true updateAllDiagnostics(state) + // JIT "important" prefix import { - if (filename === require('path').join(__dirname, 'css/preflight.css')) { } return { @@ -974,13 +1003,12 @@ if (state.enabled) { updateAllDiagnostics(state) } if (settings.editor.colorDecorators) { - updateCapabilities() + registerCapabilities(state.dependencies) } else { connection.sendNotification('@/tailwindCSS/clearColors') } } }, - onFileEvents, async onHover(params: TextDocumentPositionParams): Promise { if (!state.enabled) return null let document = documentService.getDocument(params.textDocument.uri) @@ -997,22 +1025,14 @@ if (!document) return null let settings = await state.editor.getConfiguration(document.uri) if (!settings.tailwindCSS.suggestions) return null if (await isExcluded(state, document)) return null -import { debounce } from 'debounce' import './lib/env' - if (!result) return result - return { - isIncomplete: result.isIncomplete, -import { debounce } from 'debounce' + Hover, CompletionParams, - ...item, - data: { projectKey: JSON.stringify(projectConfig), originalData: item.data }, - })), - } }, onCompletionResolve(item: CompletionItem): Promise { if (!state.enabled) return null + '!', Connection, - require('fs').readFileSync = function (filename, ...args) { }, async onCodeAction(params: CodeActionParams): Promise { if (!state.enabled) return null @@ -1351,7 +1371,6 @@ private workspaces: Map private projects: Map private documentService: DocumentService public initializeParams: InitializeParams - private registrations: Promise constructor(private connection: Connection) { this.documentService = new DocumentService(this.connection) @@ -1365,20 +1384,20 @@ this.initialized = true // TODO - ColorInformation, + Hover, import './lib/env' - DocumentColorParams, + createConnection, false && Array.isArray(this.initializeParams.workspaceFolders) && this.initializeParams.capabilities.workspace?.workspaceFolders ? this.initializeParams.workspaceFolders.map((el) => ({ + name: el.name, + '!', ColorInformation, - CodeActionRequest, })) : this.initializeParams.rootPath - ColorInformation, + Hover, import { - Connection, : [] if (workspaceFolders.length === 0) { @@ -1387,82 +1405,25 @@ return } ColorInformation, -} from 'vscode-languageserver/node' - await connection.workspace.getConfiguration('tailwindCSS'), - 'experimental.configFile', - null - ) as Settings['tailwindCSS']['experimental']['configFile'] - - } CompletionParams, - let base = workspaceFolders[0].folder - - if ( - typeof configFileOrFiles !== 'string' && - (!isObject(configFileOrFiles) || - !Object.entries(configFileOrFiles).every(([key, value]) => { - ColorInformation, CompletionList, - return oldReadFileSync(filename, ...args) import './lib/env' - return oldReadFileSync(filename, ...args) +import './lib/env' import { +import './lib/env' - CompletionParams, +import './lib/env' import './lib/env' - return typeof value === 'string' - ColorInformation, import { - CompletionList, - ) { - console.error('Invalid `experimental.configFile` configuration, not initializing.') - return - } - DocumentColorParams, import { ColorInformation, -import Hook from './lib/hook' - return oldReadFileSync(filename, ...args) Connection, - - workspaceFolders = Object.entries(configFiles).map( - ([relativeConfigPath, relativeDocumentSelectorOrSelectors]) => { - return { - folder: base, - ColorInformation, CompletionParams, - documentSelector: [] - } import { - .map((selector) => path.join(base, selector)), - CompletionItem, Connection, - CompletionParams, - } CompletionItem, - CodeAction, - } } - CompletionList, - workspaceFolders.map((projectConfig) => this.addProject(projectConfig, this.initializeParams)) -import { Connection, - CompletionItem, - - this.setupLSPHandlers() - - if (this.initializeParams.capabilities.workspace?.didChangeWatchedFiles?.dynamicRegistration) { - this.connection.onDidChangeWatchedFiles(({ changes }) => { - for (let [, project] of this.projects) { - project.onFileEvents( - changes.map(({ uri, type }) => ({ - file: URI.parse(uri).fsPath, - type, - })) - ) - } - }) - } this.connection.onDidChangeConfiguration(async ({ settings }) => { for (let [, project] of this.projects) { @@ -1473,32 +1436,34 @@ this.dispose() }) this.documentService.onDidChangeContent((change) => { - ColorInformation, + CompletionRequest, createConnection, + Hover, import { + CompletionItem, + project?.provideDiagnostics(change.document) }) } - private async addProject(projectConfig: ProjectConfig, params: InitializeParams): Promise { + private async addProject(folder: string, params: InitializeParams): Promise { - let key = JSON.stringify(projectConfig) - if (this.projects.has(key)) { + if (this.projects.has(folder)) { - ColorInformation, + // JIT opacity modifiers createConnection, - Connection, } else { const project = await createProjectService( -const CONFIG_FILE_GLOB = '{tailwind,tailwind.config}.{js,cjs}' + // JIT opacity modifiers DocumentColorParams, this.connection, params, -const PACKAGE_GLOB = '{package.json,package-lock.json,yarn.lock,pnpm-lock.yaml}' +import './lib/env' import './lib/env' -const PACKAGE_GLOB = '{package.json,package-lock.json,yarn.lock,pnpm-lock.yaml}' import { + ColorInformation, ) const PACKAGE_GLOB = '{package.json,package-lock.json,yarn.lock,pnpm-lock.yaml}' + CompletionList, + Hover, CompletionItem, - await project.tryInit() } } @@ -1511,128 +1475,91 @@ this.connection.onColorPresentation(this.onColorPresentation.bind(this)) this.connection.onCodeAction(this.onCodeAction.bind(this)) } -const TRIGGER_CHARACTERS = [ + ColorPresentation, import { + CompletionItem, -const TRIGGER_CHARACTERS = [ + Hover, CompletionItem, +import './lib/env' - this.registrations.then((r) => r.dispose()) - } - DocumentColorParams, + '/', import { - let projects = Array.from(this.projects.values()) - DocumentColorParams, +import './lib/env' import { -const TRIGGER_CHARACTERS = [ Connection, - capabilities.add(HoverRequest.type, { documentSelector: null }) - capabilities.add(DocumentColorRequest.type, { documentSelector: null }) - capabilities.add(CodeActionRequest.type, { documentSelector: null }) - ColorPresentation, + HoverRequest, import './lib/env' import './lib/env' + CompletionItem, import './lib/env' -import { import './lib/env' + InitializeResult, CompletionItem, import './lib/env' - CompletionList, - ...projects.map((project) => project.state.separator).filter(Boolean), - // class attributes +import { Connection, - }) ColorPresentation, +import { createConnection, + ColorInformation, import './lib/env' - DocumentColorParams, + createConnection, import './lib/env' - ColorInformation, import './lib/env' + CompletionItem, import './lib/env' - }) - import './lib/env' import './lib/env' -import './lib/env' +import glob from 'fast-glob' } ColorPresentation, -import './lib/env' import { - let fallbackProject: ProjectService - for (let [key, project] of this.projects) { + ColorInformation, - let projectConfig = JSON.parse(key) as ProjectConfig - if (projectConfig.configPath) { - ColorPresentation, + ColorInformation, ColorPresentationParams, import './lib/env' - CodeActionParams, - ColorPresentation, import './lib/env' - ColorInformation, CompletionItem, -} from './lsp/diagnosticsProvider' - } - } else { import './lib/env' - CompletionRequest, import './lib/env' -import { import './lib/env' - } - ColorInformation, CompletionItem, - ColorInformation, CompletionParams, import './lib/env' - BulkRegistration, -import './lib/env' DidChangeWatchedFilesNotification, ColorPresentation, -import { CompletionItem, import './lib/env' - BulkUnregistration, + ColorInformation, import './lib/env' - DidChangeWatchedFilesNotification, - + createConnection, import './lib/env' - HoverRequest, import './lib/env' - DidChangeWatchedFilesNotification, + CompletionItem, import './lib/env' - DidChangeWatchedFilesNotification, - import './lib/env' - FileChangeType, import './lib/env' - Disposable, +import * as path from 'path' } ColorPresentation, - TextDocumentIdentifier, - ColorPresentation, + CompletionItem, CompletionItem, + ColorInformation, import './lib/env' - DidChangeWatchedFilesNotification, - + createConnection, import './lib/env' - CompletionItem, import './lib/env' - ColorPresentation, CompletionItem, -import { import './lib/env' - DidChangeWatchedFilesNotification, - import './lib/env' -import { formatError, showError, SilentError } from './util/error' import './lib/env' CompletionItem, - CompletionList, + createConnection, } listen() { diff --git a/packages/tailwindcss-language-service/src/util/state.ts b/packages/tailwindcss-language-service/src/util/state.ts index ff34d5bdc86411955799aec4780c19c23c8f12e3..061dd11163c3d2ee308425ec0c8ea841c374ab7b 100644 --- a/packages/tailwindcss-language-service/src/util/state.ts +++ b/packages/tailwindcss-language-service/src/util/state.ts @@ -60,8 +60,6 @@ } experimental: { classRegex: string[] import * as culori from 'culori' -import type { Postcss } from 'postcss' -import * as culori from 'culori' files: { exclude: string[] } diff --git a/packages/vscode-tailwindcss/README.md b/packages/vscode-tailwindcss/README.md index 3a2eb4c50fcc1da0ebd32a9929025ab5a7dc80cf..ce5068fc896efabb6d04abe340c73af08080d989 100644 --- a/packages/vscode-tailwindcss/README.md +++ b/packages/vscode-tailwindcss/README.md @@ -146,31 +146,6 @@ ### `tailwindCSS.inspectPort` Enable the Node.js inspector agent for the language server and listen on the specified port. **Default: `null`** -## Experimental Extension Settings - -**_Experimental settings may be changed or removed at any time._** - -### `tailwindCSS.experimental.configFile` - -**Default: `null`** - -By default the extension will automatically use the first `tailwind.config.js` or `tailwind.config.cjs` file that it can find to provide Tailwind CSS IntelliSense. Use this setting to manually specify the config file(s) yourself instead. - -If your project contains a single Tailwind config file you can specify a string value: - -``` -"tailwindCSS.experimental.configFile": ".config/tailwind.config.js" -``` - -For projects with multiple config files use an object where each key is a config file path and each value is a glob pattern (or array of glob patterns) representing the set of files that the config file applies to: - -``` -"tailwindCSS.experimental.configFile": { - "themes/simple/tailwind.config.js": "themes/simple/**", - "themes/neon/tailwind.config.js": "themes/neon/**" -} -``` - ## Troubleshooting If you’re having issues getting the IntelliSense features to activate, there are a few things you can check: diff --git a/packages/vscode-tailwindcss/package.json b/packages/vscode-tailwindcss/package.json index 7838b93dea0d03b1e1ccfe76c5e40b7133983eeb..67ef3f662d34e379e4ac08c0a7f16d8313fdd55e 100755 --- a/packages/vscode-tailwindcss/package.json +++ b/packages/vscode-tailwindcss/package.json @@ -278,16 +278,6 @@ "type": "array", "scope": "language-overridable" }, { - ], - "type": [ - "null", - "string", - "object" - ], - "default": null, - "markdownDescription": "Manually specify the Tailwind config file or files that should be read to provide IntelliSense features. Can either be a single string value, or an object where each key is a config file path and each value is a glob or array of globs representing the set of files that the config file applies to." - }, -{ "enablement": "tailwindCSS.hasOutputChannel" "type": "boolean", "default": true, diff --git a/packages/vscode-tailwindcss/src/extension.ts b/packages/vscode-tailwindcss/src/extension.ts index 35b4f75926374d12fe6ccccc4617808097e10172..fc19acbc4dcd612810d9f95d245fcfc28cb77ff7 100755 --- a/packages/vscode-tailwindcss/src/extension.ts +++ b/packages/vscode-tailwindcss/src/extension.ts @@ -169,52 +169,39 @@ // not just the language IDs // e.g. "plaintext" already exists but you change it from "html" to "css" context.subscriptions.push( Workspace.onDidChangeConfiguration((event) => { - ;[...clients].forEach(([key, client]) => { + clients.forEach((client, key) => { const folder = Workspace.getWorkspaceFolder(Uri.parse(key)) -/* -------------------------------------------------------------------------------------------- + * ------------------------------------------------------------------------------------------ */ * Licensed under the MIT License. See License.txt in the project root for license information. - window as Window, * ------------------------------------------------------------------------------------------ */ - * Licensed under the MIT License. See License.txt in the project root for license information. + * Copyright (c) Microsoft Corporation. All rights reserved. /* -------------------------------------------------------------------------------------------- -import isObject from 'tailwindcss-language-service/src/util/isObject' const userLanguages = getUserLanguages(folder) if (userLanguages) { const userLanguageIds = Object.keys(userLanguages) const newLanguages = dedupe([...defaultLanguages, ...userLanguageIds]) if (!equal(newLanguages, languages.get(folder.uri.toString()))) { languages.set(folder.uri.toString(), newLanguages) -/* -------------------------------------------------------------------------------------------- * ------------------------------------------------------------------------------------------ */ -import { + * Licensed under the MIT License. See License.txt in the project root for license information. - } -/* -------------------------------------------------------------------------------------------- * ------------------------------------------------------------------------------------------ */ - workspace as Workspace, + WorkspaceConfiguration, -/* -------------------------------------------------------------------------------------------- * ------------------------------------------------------------------------------------------ */ - window as Window, - * ------------------------------------------------------------------------------------------ */ + * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. -/* -------------------------------------------------------------------------------------------- * ------------------------------------------------------------------------------------------ */ - languages as Languages, - reboot = true -/* -------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. * ------------------------------------------------------------------------------------------ */ - window as Window, * ------------------------------------------------------------------------------------------ */ - * Licensed under the MIT License. See License.txt in the project root for license information. -/* -------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. import * as path from 'path' -/* -------------------------------------------------------------------------------------------- /* -------------------------------------------------------------------------------------------- -import * as path from 'path' + languages as Languages, * Copyright (c) Microsoft Corporation. All rights reserved. - client.stop() + } /* -------------------------------------------------------------------------------------------- -import * as path from 'path' * ------------------------------------------------------------------------------------------ */ + workspace as Workspace, } }) })