diff --git a/packages/tailwindcss-language-server/src/lsp/diagnosticsProvider.ts b/packages/tailwindcss-language-server/src/lsp/diagnosticsProvider.ts index 4936767..e5f01a8 100644 --- a/packages/tailwindcss-language-server/src/lsp/diagnosticsProvider.ts +++ b/packages/tailwindcss-language-server/src/lsp/diagnosticsProvider.ts @@ -1,12 +1,17 @@ import { TextDocument } from 'vscode-languageserver/node' import { State } from 'tailwindcss-language-service/src/util/state' import { doValidate } from 'tailwindcss-language-service/src/diagnostics/diagnosticsProvider' +import isExcluded from '../util/isExcluded' export async function provideDiagnostics(state: State, document: TextDocument) { - state.editor?.connection.sendDiagnostics({ - uri: document.uri, - diagnostics: await doValidate(state, document), - }) + if (await isExcluded(state, document)) { + clearDiagnostics(state, document) + } else { + state.editor?.connection.sendDiagnostics({ + uri: document.uri, + diagnostics: await doValidate(state, document), + }) + } } export function clearDiagnostics(state: State, document: TextDocument): void { diff --git a/packages/tailwindcss-language-server/src/server.ts b/packages/tailwindcss-language-server/src/server.ts index c23e57b..f05c842 100644 --- a/packages/tailwindcss-language-server/src/server.ts +++ b/packages/tailwindcss-language-server/src/server.ts @@ -78,6 +78,9 @@ import * as culori from 'culori' import namedColors from 'color-name' import preflight from './lib/preflight' import tailwindPlugins from './lib/plugins' +import isExcluded from './util/isExcluded' +import { getFileFsPath, normalizeFileNameToFsPath } from './util/uri' +import { equal } from 'tailwindcss-language-service/src/util/array' let oldReadFileSync = fs.readFileSync // @ts-ignore @@ -121,14 +124,6 @@ process.on('unhandledRejection', (e: any) => { connection.console.error(formatError(`Unhandled exception`, e)) }) -function normalizeFileNameToFsPath(fileName: string) { - return URI.file(fileName).fsPath -} - -function getFileFsPath(documentUri: string): string { - return URI.parse(documentUri).fsPath -} - function deletePropertyPath(obj: any, path: string | string[]): void { if (typeof path === 'string') { path = path.split('.') @@ -220,6 +215,7 @@ async function createProjectService( enabled: false, editor: { connection, + folder, globalSettings: params.initializationOptions.configuration as Settings, userLanguages: params.initializationOptions.userLanguages ? params.initializationOptions.userLanguages @@ -258,12 +254,7 @@ async function createProjectService( let registrations: Promise let chokidarWatcher: chokidar.FSWatcher - let ignore = [ - '**/.git/objects/**', - '**/.git/subtree-cache/**', - '**/node_modules/**', - '**/.hg/store/**', - ] + let ignore = state.editor.globalSettings.tailwindCSS.files.exclude function onFileEvents(changes: Array<{ file: string; type: FileChangeType }>): void { let needsInit = false @@ -456,7 +447,7 @@ async function createProjectService( let [configPath] = ( await glob([`**/${CONFIG_FILE_GLOB}`], { cwd: folder, - ignore: ['**/node_modules'], + ignore: state.editor.globalSettings.tailwindCSS.files.exclude, onlyFiles: true, absolute: true, suppressErrors: true, @@ -989,25 +980,33 @@ async function createProjectService( }, onUpdateSettings(settings: any): void { documentSettingsCache.clear() - if (state.enabled) { - updateAllDiagnostics(state) - } - if (settings.editor.colorDecorators) { - registerCapabilities(state.dependencies) + let previousExclude = state.editor.globalSettings.tailwindCSS.files.exclude + state.editor.globalSettings = settings + if (!equal(previousExclude, settings.tailwindCSS.files.exclude)) { + tryInit() } else { - connection.sendNotification('@/tailwindCSS/clearColors') + if (state.enabled) { + updateAllDiagnostics(state) + } + if (settings.editor.colorDecorators) { + registerCapabilities(state.dependencies) + } else { + connection.sendNotification('@/tailwindCSS/clearColors') + } } }, - onHover(params: TextDocumentPositionParams): Promise { + async onHover(params: TextDocumentPositionParams): Promise { if (!state.enabled) return null let document = documentService.getDocument(params.textDocument.uri) if (!document) return null + if (await isExcluded(state, document)) return null return doHover(state, document, params.position) }, - onCompletion(params: CompletionParams): Promise { + async onCompletion(params: CompletionParams): Promise { if (!state.enabled) return null let document = documentService.getDocument(params.textDocument.uri) if (!document) return null + if (await isExcluded(state, document)) return null return doComplete(state, document, params.position, params.context) }, onCompletionResolve(item: CompletionItem): Promise { @@ -1026,6 +1025,7 @@ async function createProjectService( if (!state.enabled) return [] let document = documentService.getDocument(params.textDocument.uri) if (!document) return [] + if (await isExcluded(state, document)) return null return getDocumentColors(state, document) }, async onColorPresentation(params: ColorPresentationParams): Promise { diff --git a/packages/tailwindcss-language-server/src/util/isExcluded.ts b/packages/tailwindcss-language-server/src/util/isExcluded.ts new file mode 100644 index 0000000..9f48b83 --- /dev/null +++ b/packages/tailwindcss-language-server/src/util/isExcluded.ts @@ -0,0 +1,18 @@ +import minimatch from 'minimatch' +import * as path from 'path' +import { State } from 'tailwindcss-language-service/src/util/state' +import { TextDocument } from 'vscode-languageserver-textdocument' +import { getFileFsPath } from './uri' + +export default async function isExcluded(state: State, document: TextDocument): Promise { + let settings = await state.editor.getConfiguration(document.uri) + let file = getFileFsPath(document.uri) + + for (let pattern of settings.tailwindCSS.files.exclude) { + if (minimatch(file, path.join(state.editor.folder, pattern))) { + return true + } + } + + return false +} diff --git a/packages/tailwindcss-language-server/src/util/uri.ts b/packages/tailwindcss-language-server/src/util/uri.ts new file mode 100644 index 0000000..492d32f --- /dev/null +++ b/packages/tailwindcss-language-server/src/util/uri.ts @@ -0,0 +1,9 @@ +import { URI } from 'vscode-uri' + +export function normalizeFileNameToFsPath(fileName: string) { + return URI.file(fileName).fsPath +} + +export function getFileFsPath(documentUri: string): string { + return URI.parse(documentUri).fsPath +} diff --git a/packages/tailwindcss-language-service/src/util/state.ts b/packages/tailwindcss-language-service/src/util/state.ts index 8b886ba..30c8fba 100644 --- a/packages/tailwindcss-language-service/src/util/state.ts +++ b/packages/tailwindcss-language-service/src/util/state.ts @@ -19,6 +19,7 @@ export type ClassNames = { export type EditorState = { connection: Connection + folder: string documents: TextDocuments globalSettings: Settings userLanguages: Record @@ -56,6 +57,9 @@ export type Settings = { experimental: { classRegex: string[] } + files: { + exclude: string[] + } } } diff --git a/packages/vscode-tailwindcss/README.md b/packages/vscode-tailwindcss/README.md index 1a03a1c..0766ce3 100644 --- a/packages/vscode-tailwindcss/README.md +++ b/packages/vscode-tailwindcss/README.md @@ -62,6 +62,10 @@ This setting allows you to add additional language support. The key of each entr } ``` +### `tailwindCSS.files.exclude` + +Configure glob patterns to exclude from all IntelliSense features. Inherits all glob patterns from the `files.exclude` setting. **Default: ["\*\*/.git/\*\*", "\*\*/node_modules/\*\*", "\*\*/.hg/\*\*"]** + ### `tailwindCSS.emmetCompletions` Enable completions when using [Emmet](https://emmet.io/)-style syntax, for example `div.bg-red-500.uppercase`. **Default: `false`** diff --git a/packages/vscode-tailwindcss/package.json b/packages/vscode-tailwindcss/package.json index 8b9356c..24bb193 100755 --- a/packages/vscode-tailwindcss/package.json +++ b/packages/vscode-tailwindcss/package.json @@ -119,6 +119,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.files.exclude": { + "type": "array", + "items": { + "type": "string" + }, + "default": [ + "**/.git/**", + "**/node_modules/**", + "**/.hg/**" + ], + "markdownDescription": "Configure glob patterns to exclude from all IntelliSense features. Inherits all glob patterns from the `#files.exclude#` setting." + }, "tailwindCSS.classAttributes": { "type": "array", "items": { diff --git a/packages/vscode-tailwindcss/src/extension.ts b/packages/vscode-tailwindcss/src/extension.ts index b6c51c0..30cc692 100755 --- a/packages/vscode-tailwindcss/src/extension.ts +++ b/packages/vscode-tailwindcss/src/extension.ts @@ -31,6 +31,7 @@ import { languages as defaultLanguages } from 'tailwindcss-language-service/src/ import isObject from 'tailwindcss-language-service/src/util/isObject' import { dedupe, equal } from 'tailwindcss-language-service/src/util/array' import namedColors from 'color-name' +import minimatch from 'minimatch' const colorNames = Object.keys(namedColors) @@ -82,6 +83,45 @@ function getUserLanguages(folder?: WorkspaceFolder): Record { return isObject(langs) ? langs : {} } +function getExcludePatterns(folder: WorkspaceFolder): string[] { + let globalExclude = Workspace.getConfiguration('files', folder).get('exclude') + let exclude = Object.entries(globalExclude) + .filter(([, value]) => value) + .map(([key]) => key) + + return [ + ...exclude, + ...(Workspace.getConfiguration('tailwindCSS', folder).get('files.exclude')), + ] +} + +function isExcluded(file: string, folder: WorkspaceFolder): boolean { + let exclude = getExcludePatterns(folder) + + for (let pattern of exclude) { + if (minimatch(file, path.join(folder.uri.fsPath, pattern))) { + return true + } + } + + return false +} + +function mergeExcludes(settings, scope) { + // merge `files.exclude` into `tailwindCSS.files.exclude` + let globalExclude = Object.entries(Workspace.getConfiguration('files', scope).get('exclude')) + .filter(([, value]) => value) + .map(([key]) => key) + + return { + ...settings, + files: { + ...settings.files, + exclude: [...globalExclude, ...settings.files.exclude], + }, + } +} + export async function activate(context: ExtensionContext) { let module = context.asAbsolutePath(path.join('dist', 'server', 'index.js')) let prod = path.join('dist', 'server', 'tailwindServer.js') @@ -108,8 +148,10 @@ export async function activate(context: ExtensionContext) { if (!folder) { return } - folder = getOuterMostWorkspaceFolder(folder) - bootWorkspaceClient(folder) + if (!isExcluded(uri.fsPath, folder)) { + folder = getOuterMostWorkspaceFolder(folder) + bootWorkspaceClient(folder) + } }) context.subscriptions.push(watcher) @@ -180,7 +222,7 @@ export async function activate(context: ExtensionContext) { let configuration = { editor: Workspace.getConfiguration('editor', folder), - tailwindCSS: Workspace.getConfiguration('tailwindCSS', folder), + tailwindCSS: mergeExcludes(Workspace.getConfiguration('tailwindCSS', folder), folder), } let inspectPort = configuration.tailwindCSS.get('inspectPort') @@ -309,7 +351,13 @@ export async function activate(context: ExtensionContext) { } } } - return Workspace.getConfiguration(section, scope) + let settings = Workspace.getConfiguration(section, scope) + + if (section === 'tailwindCSS') { + return mergeExcludes(settings, scope) + } + + return settings }) }, }, @@ -375,7 +423,7 @@ export async function activate(context: ExtensionContext) { let [configFile] = await Workspace.findFiles( new RelativePattern(folder, `**/${CONFIG_FILE_GLOB}`), - '**/node_modules/**', + `{${getExcludePatterns(folder).join(',')}}`, 1 )