diff --git a/packages/tailwindcss-language-server/src/lib/constants.ts b/packages/tailwindcss-language-server/src/lib/constants.ts deleted file mode 100644 index 7324327c59283b13931a342a761e3242e62b4c35..0000000000000000000000000000000000000000 --- a/packages/tailwindcss-language-server/src/lib/constants.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const CONFIG_GLOB = '{tailwind,tailwind.config,tailwind.*.config,tailwind.config.*}.{js,cjs}' -export const PACKAGE_LOCK_GLOB = '{package-lock.json,yarn.lock,pnpm-lock.yaml}' -export const CSS_GLOB = '*.{css,scss,sass,less,pcss}' diff --git a/packages/tailwindcss-language-server/src/lsp/diagnosticsProvider.ts b/packages/tailwindcss-language-server/src/lsp/diagnosticsProvider.ts index fe08b247f564cd57d80d9ece27c2481456e23eaf..e5f01a8998bd771b9bd02cd78332e2dda1033527 100644 --- a/packages/tailwindcss-language-server/src/lsp/diagnosticsProvider.ts +++ b/packages/tailwindcss-language-server/src/lsp/diagnosticsProvider.ts @@ -20,3 +20,15 @@ uri: document.uri, diagnostics: [], }) } + +export function clearAllDiagnostics(state: State): void { + state.editor?.documents.all().forEach((document) => { + clearDiagnostics(state, document) + }) +} + +export function updateAllDiagnostics(state: State): void { + state.editor?.documents.all().forEach((document) => { + provideDiagnostics(state, document) + }) +} diff --git a/packages/tailwindcss-language-server/src/server.ts b/packages/tailwindcss-language-server/src/server.ts index 0b9b4f576833ca4784c611e280d7422d6253f4e8..008c221a7744fb579ca9a8887c75c87a9916755c 100644 --- a/packages/tailwindcss-language-server/src/server.ts +++ b/packages/tailwindcss-language-server/src/server.ts @@ -65,7 +65,11 @@ Settings, ClassNames, Variant, } from 'tailwindcss-language-service/src/util/state' -import { provideDiagnostics } from './lsp/diagnosticsProvider' +import { + provideDiagnostics, + updateAllDiagnostics, + clearAllDiagnostics, +} from './lsp/diagnosticsProvider' import { doCodeActions } from 'tailwindcss-language-service/src/codeActions/codeActionProvider' import { getDocumentColors } from 'tailwindcss-language-service/src/documentColorProvider' import { getDocumentLinks } from 'tailwindcss-language-service/src/documentLinksProvider' @@ -84,8 +88,6 @@ import { getFileFsPath, normalizeFileNameToFsPath } from './util/uri' import { equal } from 'tailwindcss-language-service/src/util/array' import preflight from 'tailwindcss/lib/css/preflight.css' import merge from 'deepmerge' -import { getTextWithoutComments } from 'tailwindcss-language-service/src/util/doc' -import { CONFIG_GLOB, CSS_GLOB, PACKAGE_LOCK_GLOB } from './lib/constants' // @ts-ignore global.__preflight = preflight @@ -103,6 +105,8 @@ } ` )(require, __dirname) +const CONFIG_FILE_GLOB = '{tailwind,tailwind.config}.{js,cjs}' +const PACKAGE_GLOB = '{package-lock.json,yarn.lock,pnpm-lock.yaml}' const TRIGGER_CHARACTERS = [ // class attributes '"', @@ -177,42 +181,22 @@ } } interface ProjectService { - enabled: () => boolean - enable: () => void - documentSelector: () => Array state: State tryInit: () => Promise - dispose: () => Promise + dispose: () => void onUpdateSettings: (settings: any) => void onFileEvents: (changes: Array<{ file: string; type: FileChangeType }>) => void onHover(params: TextDocumentPositionParams): Promise onCompletion(params: CompletionParams): Promise onCompletionResolve(item: CompletionItem): Promise provideDiagnostics(document: TextDocument): void - provideDiagnosticsForce(document: TextDocument): void onDocumentColor(params: DocumentColorParams): Promise onColorPresentation(params: ColorPresentationParams): Promise onCodeAction(params: CodeActionParams): Promise onDocumentLinks(params: DocumentLinkParams): DocumentLink[] } -type ProjectConfig = { - folder: string - configPath?: string - documentSelector?: Array - isUserConfigured: boolean -} - -enum DocumentSelectorPriority { - USER_CONFIGURED = 0, - CONFIG_FILE = 0, - CSS_FILE = 0, - CONTENT_FILE = 1, - CSS_DIRECTORY = 2, - CONFIG_DIRECTORY = 3, - ROOT_DIRECTORY = 4, -} -type DocumentSelector = { pattern: string; priority: DocumentSelectorPriority } +type ProjectConfig = { folder: string; configPath?: string; documentSelector?: string[] } function getMode(config: any): unknown { if (typeof config.mode !== 'undefined') { @@ -237,150 +221,77 @@ } } } -const documentSettingsCache: Map = new Map() -async function getConfiguration(uri?: string) { - if (documentSettingsCache.has(uri)) { - return documentSettingsCache.get(uri) - } - let [editor, tailwindCSS] = await Promise.all([ - connection.workspace.getConfiguration({ - section: 'editor', - scopeUri: uri, - }), - connection.workspace.getConfiguration({ - section: 'tailwindCSS', - scopeUri: uri, - }), - ]) - editor = isObject(editor) ? editor : {} - tailwindCSS = isObject(tailwindCSS) ? tailwindCSS : {} - - let config: Settings = merge( - { - editor: { tabSize: 2 }, - tailwindCSS: { - emmetCompletions: false, - classAttributes: ['class', 'className', 'ngClass'], - codeActions: true, - hovers: true, - suggestions: true, - validate: true, - colorDecorators: true, - rootFontSize: 16, - lint: { - cssConflict: 'warning', - invalidApply: 'error', - invalidScreen: 'error', - invalidVariant: 'error', - invalidConfigPath: 'error', - invalidTailwindDirective: 'error', - recommendedVariantOrder: 'warning', - }, - showPixelEquivalents: true, - includeLanguages: {}, - files: { exclude: ['**/.git/**', '**/node_modules/**', '**/.hg/**', '**/.svn/**'] }, - experimental: { - classRegex: [], - configFile: null, - }, - }, - }, - { editor, tailwindCSS }, - { arrayMerge: (_destinationArray, sourceArray, _options) => sourceArray } - ) - documentSettingsCache.set(uri, config) - return config -} - -function clearRequireCache(): void { - Object.keys(require.cache).forEach((key) => { - if (!key.endsWith('.node')) { - delete require.cache[key] - } - }) - Object.keys((Module as any)._pathCache).forEach((key) => { - delete (Module as any)._pathCache[key] - }) -} - -function withoutLogs(getter: () => T): T { - let fns = { - log: console.log, - warn: console.warn, - error: console.error, - } - for (let key in fns) { - console[key] = () => {} - } - try { - return getter() - } finally { - for (let key in fns) { - console[key] = fns[key] - } - } -} - -function withFallback(getter: () => T, fallback: T): T { - try { - return getter() - } catch (e) { - return fallback - } -} - -function dirContains(dir: string, file: string): boolean { - let relative = path.relative(dir, file) - return !!relative && !relative.startsWith('..') && !path.isAbsolute(relative) -} - -function changeAffectsFile(change: string, files: string[]): boolean { - for (let file of files) { - if (change === file || dirContains(change, file)) { - return true - } - } - return false -} - -// We need to add parent directories to the watcher: -// https://github.com/microsoft/vscode/issues/60813 -function getWatchPatternsForFile(file: string): string[] { - let tmp: string - let dir = path.dirname(file) - let patterns: string[] = [file, dir] - while (true) { - dir = path.dirname((tmp = dir)) - if (tmp === dir) { - break - } else { - patterns.push(dir) - } - } - return patterns -} - async function createProjectService( projectConfig: ProjectConfig, connection: Connection, params: InitializeParams, documentService: DocumentService, - updateCapabilities: () => void, - checkOpenDocuments: () => void, - refreshDiagnostics: () => void, - watchPatterns: (patterns: string[]) => void, - initialTailwindVersion: string + updateCapabilities: () => void ): Promise { - let enabled = false const folder = projectConfig.folder - const disposables: Array> = [] - let documentSelector = projectConfig.documentSelector + const disposables: Disposable[] = [] + const documentSettingsCache: Map = new Map() - let state: State = { + async function getConfiguration(uri?: string) { + if (documentSettingsCache.has(uri)) { + return documentSettingsCache.get(uri) + } + let [editor, tailwindCSS] = await Promise.all([ + connection.workspace.getConfiguration({ + section: 'editor', + scopeUri: uri, + }), + connection.workspace.getConfiguration({ + section: 'tailwindCSS', + scopeUri: uri, + }), + ]) + editor = isObject(editor) ? editor : {} + tailwindCSS = isObject(tailwindCSS) ? tailwindCSS : {} + + let config: Settings = merge( + { + editor: { tabSize: 2 }, + tailwindCSS: { + emmetCompletions: false, + classAttributes: ['class', 'className', 'ngClass'], + codeActions: true, + hovers: true, + suggestions: true, + validate: true, + colorDecorators: true, + rootFontSize: 16, + lint: { + cssConflict: 'warning', + invalidApply: 'error', + invalidScreen: 'error', + invalidVariant: 'error', + invalidConfigPath: 'error', + invalidTailwindDirective: 'error', + recommendedVariantOrder: 'warning', + }, + showPixelEquivalents: true, + includeLanguages: {}, + files: { exclude: ['**/.git/**', '**/node_modules/**', '**/.hg/**', '**/.svn/**'] }, + experimental: { + classRegex: [], + configFile: null, + }, + }, + }, + { editor, tailwindCSS }, + { arrayMerge: (_destinationArray, sourceArray, _options) => sourceArray } + ) + documentSettingsCache.set(uri, config) + return config + } + + const state: State = { enabled: false, editor: { connection, folder, + globalSettings: await getConfiguration(), userLanguages: params.initializationOptions.userLanguages ? params.initializationOptions.userLanguages : {}, @@ -418,22 +329,8 @@ }, }, } - if (projectConfig.configPath) { - let deps = [] - try { - deps = getModuleDependencies(projectConfig.configPath) - } catch {} - watchPatterns([ - ...getWatchPatternsForFile(projectConfig.configPath), - ...deps.flatMap((dep) => getWatchPatternsForFile(dep)), - ]) - } - - function log(...args: string[]): void { - console.log( - `[${path.relative(projectConfig.folder, projectConfig.configPath)}] ${args.join(' ')}` - ) - } + let chokidarWatcher: chokidar.FSWatcher + let ignore = state.editor.globalSettings.tailwindCSS.files.exclude function onFileEvents(changes: Array<{ file: string; type: FileChangeType }>): void { let needsInit = false @@ -442,40 +339,22 @@ for (let change of changes) { let file = normalizePath(change.file) - let isConfigFile = changeAffectsFile(file, [projectConfig.configPath]) - let isDependency = changeAffectsFile(change.file, state.dependencies ?? []) - let isPackageFile = minimatch(file, `**/${PACKAGE_LOCK_GLOB}`, { dot: true }) - - if (!isConfigFile && !isDependency && !isPackageFile) continue - - if (!enabled) { - if ( - !projectConfig.isUserConfigured && - projectConfig.configPath && - (isConfigFile || isDependency) - ) { - documentSelector = [ - ...documentSelector.filter( - ({ priority }) => priority !== DocumentSelectorPriority.CONTENT_FILE - ), - ...getContentDocumentSelectorFromConfigFile( - projectConfig.configPath, - initialTailwindVersion, - projectConfig.folder - ), - ] - - checkOpenDocuments() + for (let ignorePattern of ignore) { + if (minimatch(file, ignorePattern, { dot: true })) { + continue } - continue } + let isConfigFile = minimatch(file, `**/${CONFIG_FILE_GLOB}`, { dot: true }) + let isPackageFile = minimatch(file, `**/${PACKAGE_GLOB}`, { dot: true }) + let isDependency = state.dependencies && state.dependencies.includes(change.file) + + if (!isConfigFile && !isPackageFile && !isDependency) continue + if (change.type === FileChangeType.Created) { - log('File created:', change.file) needsInit = true break } else if (change.type === FileChangeType.Changed) { - log('File changed:', change.file) if (!state.enabled || isPackageFile) { needsInit = true break @@ -483,8 +362,7 @@ } else { needsRebuild = true } } else if (change.type === FileChangeType.Deleted) { - log('File deleted:', change.file) - if (!state.enabled || isConfigFile || isPackageFile) { + if (!state.enabled || isPackageFile || isConfigFile) { needsInit = true break } else { @@ -500,8 +378,65 @@ tryRebuild() } } + if (params.capabilities.workspace?.didChangeWatchedFiles?.dynamicRegistration) { + connection.client.register(DidChangeWatchedFilesNotification.type, { + watchers: [{ globPattern: `**/${CONFIG_FILE_GLOB}` }, { globPattern: `**/${PACKAGE_GLOB}` }], + }) + } else if (parcel.getBinding()) { + let typeMap = { + create: FileChangeType.Created, + update: FileChangeType.Changed, + delete: FileChangeType.Deleted, + } + + let subscription = await parcel.subscribe( + folder, + (err, events) => { + onFileEvents(events.map((event) => ({ file: event.path, type: typeMap[event.type] }))) + }, + { + ignore: ignore.map((ignorePattern) => + path.resolve(folder, ignorePattern.replace(/^[*/]+/, '').replace(/[*/]+$/, '')) + ), + } + ) + + disposables.push({ + dispose() { + subscription.unsubscribe() + }, + }) + } else { + let watch: typeof chokidar.watch = require('chokidar').watch + chokidarWatcher = watch([`**/${CONFIG_FILE_GLOB}`, `**/${PACKAGE_GLOB}`], { + cwd: folder, + ignorePermissionErrors: true, + ignoreInitial: true, + ignored: ignore, + awaitWriteFinish: { + stabilityThreshold: 100, + pollInterval: 20, + }, + }) + + await new Promise((resolve) => { + chokidarWatcher.on('ready', () => resolve()) + }) + + chokidarWatcher + .on('add', (file) => onFileEvents([{ file, type: FileChangeType.Created }])) + .on('change', (file) => onFileEvents([{ file, type: FileChangeType.Changed }])) + .on('unlink', (file) => onFileEvents([{ file, type: FileChangeType.Deleted }])) + + disposables.push({ + dispose() { + chokidarWatcher.close() + }, + }) + } + function resetState(): void { - // clearAllDiagnostics(state) + clearAllDiagnostics(state) Object.keys(state).forEach((key) => { // Keep `dependencies` to ensure that they are still watched if (key !== 'editor' && key !== 'dependencies') { @@ -509,14 +444,10 @@ delete state[key] } }) state.enabled = false - refreshDiagnostics() updateCapabilities() } async function tryInit() { - if (!enabled) { - return - } try { await init() } catch (error) { @@ -526,9 +457,6 @@ } } async function tryRebuild() { - if (!enabled) { - return - } try { await rebuild() } catch (error) { @@ -537,18 +465,41 @@ showError(connection, error) } } - async function init() { - log('Initializing...') + function clearRequireCache(): void { + Object.keys(require.cache).forEach((key) => { + if (!key.endsWith('.node')) { + delete require.cache[key] + } + }) + Object.keys((Module as any)._pathCache).forEach((key) => { + delete (Module as any)._pathCache[key] + }) + } + async function init() { clearRequireCache() let configPath = projectConfig.configPath if (!configPath) { - throw new SilentError('No config file found.') + configPath = ( + await glob([`**/${CONFIG_FILE_GLOB}`], { + cwd: folder, + ignore: state.editor.globalSettings.tailwindCSS.files.exclude, + onlyFiles: true, + absolute: true, + suppressErrors: true, + dot: true, + concurrency: Math.max(os.cpus().length, 1), + }) + ) + .sort((a: string, b: string) => a.split('/').length - b.split('/').length) + .map(path.normalize)[0] } - watchPatterns(getWatchPatternsForFile(configPath)) + if (!configPath) { + throw new SilentError('No config file found.') + } const pnpPath = findUp.sync( (dir) => { @@ -622,14 +573,14 @@ ) { return } - log(`Loaded Tailwind CSS config file: ${configPath}`) + console.log(`Found Tailwind CSS config file: ${configPath}`) postcss = require(postcssPath) postcssSelectorParser = require(postcssSelectorParserPath) - log(`Loaded postcss v${postcssVersion}: ${postcssDir}`) + console.log(`Loaded postcss v${postcssVersion}: ${postcssDir}`) tailwindcss = require(tailwindcssPath) - log(`Loaded tailwindcss v${tailwindcssVersion}: ${tailwindDir}`) + console.log(`Loaded tailwindcss v${tailwindcssVersion}: ${tailwindDir}`) try { resolveConfigFn = require(resolveFrom(tailwindDir, './resolveConfig.js')) @@ -776,9 +727,9 @@ expandApplyAtRules: { module: require('tailwindcss/lib/lib/expandApplyAtRules').default, }, } - log('Failed to load workspace modules.') - log(`Using bundled version of \`tailwindcss\`: v${tailwindcssVersion}`) - log(`Using bundled version of \`postcss\`: v${postcssVersion}`) + console.log('Failed to load workspace modules.') + console.log(`Using bundled version of \`tailwindcss\`: v${tailwindcssVersion}`) + console.log(`Using bundled version of \`postcss\`: v${postcssVersion}`) } state.configPath = configPath @@ -866,8 +817,6 @@ await tryRebuild() } async function rebuild() { - log('Building...') - clearRequireCache() const { tailwindcss, postcss, resolveConfig } = state.modules @@ -963,22 +912,6 @@ if (!originalConfig) { throw new SilentError(`Failed to load config file: ${state.configPath}`) } - ///////////////////// - if (!projectConfig.isUserConfigured) { - documentSelector = [ - ...documentSelector.filter( - ({ priority }) => priority !== DocumentSelectorPriority.CONTENT_FILE - ), - ...getContentDocumentSelectorFromConfigFile( - state.configPath, - tailwindcss.version, - projectConfig.folder, - originalConfig - ), - ] - } - ////////////////////// - try { state.config = resolveConfig.module(originalConfig) state.separator = state.config.separator @@ -1034,12 +967,11 @@ hook.unhook() } } - // if (state.dependencies) { - // chokidarWatcher?.unwatch(state.dependencies) - // } + if (state.dependencies) { + chokidarWatcher?.unwatch(state.dependencies) + } state.dependencies = getModuleDependencies(state.configPath) - // chokidarWatcher?.add(state.dependencies) - watchPatterns((state.dependencies ?? []).flatMap((dep) => getWatchPatternsForFile(dep))) + chokidarWatcher?.add(state.dependencies) state.configId = getConfigId(state.configPath, state.dependencies) @@ -1054,86 +986,74 @@ state.screens = isObject(screens) ? Object.keys(screens) : [] state.enabled = true - // updateAllDiagnostics(state) - refreshDiagnostics() + updateAllDiagnostics(state) updateCapabilities() } return { - enabled() { - return enabled - }, - enable() { - enabled = true - }, state, - documentSelector() { - return documentSelector - }, tryInit, - async dispose() { - state = { enabled: false } - for (let disposable of disposables) { - ;(await disposable).dispose() + dispose() { + for (let { dispose } of disposables) { + dispose() } }, async onUpdateSettings(settings: any): Promise { - if (state.enabled) { - refreshDiagnostics() - } - if (settings.editor.colorDecorators) { - updateCapabilities() + documentSettingsCache.clear() + let previousExclude = state.editor.globalSettings.tailwindCSS.files.exclude + state.editor.globalSettings = await state.editor.getConfiguration() + if (!equal(previousExclude, settings.tailwindCSS.files.exclude)) { + tryInit() } else { - connection.sendNotification('@/tailwindCSS/clearColors') + if (state.enabled) { + updateAllDiagnostics(state) + } + if (settings.editor.colorDecorators) { + updateCapabilities() + } else { + connection.sendNotification('@/tailwindCSS/clearColors') + } } }, onFileEvents, async onHover(params: TextDocumentPositionParams): Promise { - return withFallback(async () => { - if (!state.enabled) return null - let document = documentService.getDocument(params.textDocument.uri) - if (!document) return null - let settings = await state.editor.getConfiguration(document.uri) - if (!settings.tailwindCSS.hovers) return null - if (await isExcluded(state, document)) return null - return doHover(state, document, params.position) - }, null) + if (!state.enabled) return null + let document = documentService.getDocument(params.textDocument.uri) + if (!document) return null + let settings = await state.editor.getConfiguration(document.uri) + if (!settings.tailwindCSS.hovers) return null + if (await isExcluded(state, document)) return null + return doHover(state, document, params.position) }, async onCompletion(params: CompletionParams): Promise { - return withFallback(async () => { - if (!state.enabled) return null - let document = documentService.getDocument(params.textDocument.uri) - 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 - let result = await doComplete(state, document, params.position, params.context) - if (!result) return result - return { - isIncomplete: result.isIncomplete, - items: result.items.map((item) => ({ - ...item, - data: { projectKey: JSON.stringify(projectConfig), originalData: item.data }, - })), - } - }, null) + if (!state.enabled) return null + let document = documentService.getDocument(params.textDocument.uri) + 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 + let result = await doComplete(state, document, params.position, params.context) + if (!result) return result + return { + isIncomplete: result.isIncomplete, + items: result.items.map((item) => ({ + ...item, + data: { projectKey: JSON.stringify(projectConfig), originalData: item.data }, + })), + } }, onCompletionResolve(item: CompletionItem): Promise { - return withFallback(() => { - if (!state.enabled) return null - return resolveCompletionItem(state, { ...item, data: item.data?.originalData }) - }, null) + if (!state.enabled) return null + return resolveCompletionItem(state, { ...item, data: item.data?.originalData }) }, async onCodeAction(params: CodeActionParams): Promise { - return withFallback(async () => { - if (!state.enabled) return null - let document = documentService.getDocument(params.textDocument.uri) - if (!document) return null - let settings = await state.editor.getConfiguration(document.uri) - if (!settings.tailwindCSS.codeActions) return null - return doCodeActions(state, params) - }, null) + if (!state.enabled) return null + let document = documentService.getDocument(params.textDocument.uri) + if (!document) return null + let settings = await state.editor.getConfiguration(document.uri) + if (!settings.tailwindCSS.codeActions) return null + return doCodeActions(state, params) }, onDocumentLinks(params: DocumentLinkParams): DocumentLink[] { if (!state.enabled) return null @@ -1147,22 +1067,15 @@ provideDiagnostics: debounce((document: TextDocument) => { if (!state.enabled) return provideDiagnostics(state, document) }, 500), - provideDiagnosticsForce: (document: TextDocument) => { - if (!state.enabled) return - provideDiagnostics(state, document) - }, async onDocumentColor(params: DocumentColorParams): Promise { - return withFallback(async () => { - 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) - }, null) + 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 { let document = documentService.getDocument(params.textDocument.uri) - if (!document) return [] let className = document.getText(params.range) let match = className.match( new RegExp(`-\\[(${colorNames.join('|')}|(?:(?:#|rgba?\\(|hsla?\\())[^\\]]+)\\]$`, 'i') @@ -1338,21 +1251,19 @@ for (let fn of fns) { let definition: string let container = root.clone() - let returnValue = withoutLogs(() => - fn({ - container, - separator: state.separator, - modifySelectors, - format: (def: string) => { - definition = def.replace(/:merge\(([^)]+)\)/g, '$1') - }, - wrap: (rule: Container) => { - if (isAtRule(rule)) { - definition = `@${rule.name} ${rule.params}` - } - }, - }) - ) + let returnValue = fn({ + container, + separator: state.separator, + modifySelectors, + format: (def: string) => { + definition = def.replace(/:merge\(([^)]+)\)/g, '$1') + }, + wrap: (rule: Container) => { + if (isAtRule(rule)) { + definition = `@${rule.name} ${rule.params}` + } + }, + }) if (!definition) { definition = returnValue @@ -1493,77 +1404,13 @@ // return [] // } } -async function getConfigFileFromCssFile(cssFile: string): Promise { - let css = getTextWithoutComments(await fs.promises.readFile(cssFile, 'utf8'), 'css') - let match = css.match(/@config\s*(?'[^']+'|"[^"]+")/) - if (!match) { - return null - } - return path.resolve(path.dirname(cssFile), match.groups.config.slice(1, -1)) -} - -function getPackageRoot(cwd: string, rootDir: string) { - try { - let pkgJsonPath = findUp.sync( - (dir) => { - let pkgJson = path.join(dir, 'package.json') - if (findUp.sync.exists(pkgJson)) { - return pkgJson - } - if (dir === rootDir) { - return findUp.stop - } - }, - { cwd } - ) - return pkgJsonPath ? path.dirname(pkgJsonPath) : rootDir - } catch { - return rootDir - } -} - -function getContentDocumentSelectorFromConfigFile( - configPath: string, - tailwindVersion: string, - rootDir: string, - actualConfig?: any -): DocumentSelector[] { - let config = actualConfig ?? require(configPath) - let contentConfig: unknown = config.content?.files ?? config.content - let content = Array.isArray(contentConfig) ? contentConfig : [] - let relativeEnabled = semver.gte(tailwindVersion, '3.2.0') - ? config.future?.relativeContentPathsByDefault || config.content?.relative - : false - let contentBase: string - if (relativeEnabled) { - contentBase = path.dirname(configPath) - } else { - contentBase = getPackageRoot(path.dirname(configPath), rootDir) - } - return content - .filter((item): item is string => typeof item === 'string') - .map((item) => - item.startsWith('!') - ? `!${path.resolve(contentBase, item.slice(1))}` - : path.resolve(contentBase, item) - ) - .map((item) => ({ - pattern: normalizePath(item), - priority: DocumentSelectorPriority.CONTENT_FILE, - })) -} - class TW { private initialized = false - private lspHandlersAdded = false private workspaces: Map private projects: Map private documentService: DocumentService public initializeParams: InitializeParams private registrations: Promise - private disposables: Disposable[] = [] - private watchPatterns: (patterns: string[]) => void - private watched: string[] = [] constructor(private connection: Connection) { this.documentService = new DocumentService(this.connection) @@ -1574,25 +1421,34 @@ async init(): Promise { if (this.initialized) return - clearRequireCache() - this.initialized = true - if (!this.initializeParams.rootPath) { + // TODO + let workspaceFolders: Array = + false && + Array.isArray(this.initializeParams.workspaceFolders) && + this.initializeParams.capabilities.workspace?.workspaceFolders + ? this.initializeParams.workspaceFolders.map((el) => ({ + folder: getFileFsPath(el.uri), + })) + : this.initializeParams.rootPath + ? [{ folder: normalizeFileNameToFsPath(this.initializeParams.rootPath) }] + : [] + + if (workspaceFolders.length === 0) { console.error('No workspace folders found, not initializing.') return } - let workspaceFolders: Array = [] - let globalSettings = await getConfiguration() - let ignore = globalSettings.tailwindCSS.files.exclude - let configFileOrFiles = globalSettings.tailwindCSS.experimental.configFile - - let base = normalizeFileNameToFsPath(this.initializeParams.rootPath) - let cssFileConfigMap: Map = new Map() - let configTailwindVersionMap: Map = new Map() + let configFileOrFiles = dlv( + await connection.workspace.getConfiguration('tailwindCSS'), + 'experimental.configFile', + null + ) as Settings['tailwindCSS']['experimental']['configFile'] if (configFileOrFiles) { + let base = workspaceFolders[0].folder + if ( typeof configFileOrFiles !== 'string' && (!isObject(configFileOrFiles) || @@ -1616,441 +1472,66 @@ ([relativeConfigPath, relativeDocumentSelectorOrSelectors]) => { return { folder: base, configPath: path.resolve(base, relativeConfigPath), - documentSelector: [].concat(relativeDocumentSelectorOrSelectors).map((selector) => ({ - priority: DocumentSelectorPriority.USER_CONFIGURED, - pattern: path.resolve(base, selector), - })), - isUserConfigured: true, + documentSelector: [] + .concat(relativeDocumentSelectorOrSelectors) + .map((selector) => path.resolve(base, selector)), } } ) - } else { - let projects: Record> = {} - - let files = await glob([`**/${CONFIG_GLOB}`, `**/${CSS_GLOB}`], { - cwd: base, - ignore: (await getConfiguration()).tailwindCSS.files.exclude, - onlyFiles: true, - absolute: true, - suppressErrors: true, - dot: true, - concurrency: Math.max(os.cpus().length, 1), - }) - - for (let filename of files) { - let normalizedFilename = normalizePath(filename) - let isCssFile = minimatch(normalizedFilename, `**/${CSS_GLOB}`, { dot: true }) - let configPath = isCssFile ? await getConfigFileFromCssFile(filename) : filename - if (!configPath) { - continue - } - - let twVersion = require('tailwindcss/package.json').version - let isDefaultVersion = true - try { - let v = require(resolveFrom(path.dirname(configPath), 'tailwindcss/package.json')).version - if (typeof v === 'string') { - twVersion = v - isDefaultVersion = false - } - } catch {} - - if (isCssFile && (!semver.gte(twVersion, '3.2.0') || isDefaultVersion)) { - continue - } - - configTailwindVersionMap.set(configPath, twVersion) - - let contentSelector: Array = [] - try { - contentSelector = getContentDocumentSelectorFromConfigFile(configPath, twVersion, base) - } catch {} - - let documentSelector: DocumentSelector[] = [ - { - pattern: normalizePath(filename), - priority: isCssFile - ? DocumentSelectorPriority.CSS_FILE - : DocumentSelectorPriority.CONFIG_FILE, - }, - ...(isCssFile - ? [ - { - pattern: normalizePath(configPath), - priority: DocumentSelectorPriority.CONFIG_FILE, - }, - ] - : []), - ...contentSelector, - { - pattern: normalizePath(path.join(path.dirname(filename), '**')), - priority: isCssFile - ? DocumentSelectorPriority.CSS_DIRECTORY - : DocumentSelectorPriority.CONFIG_DIRECTORY, - }, - ...(isCssFile - ? [ - { - pattern: normalizePath(path.join(path.dirname(configPath), '**')), - priority: DocumentSelectorPriority.CONFIG_DIRECTORY, - }, - ] - : []), - { - pattern: normalizePath(path.join(getPackageRoot(path.dirname(configPath), base), '**')), - priority: DocumentSelectorPriority.ROOT_DIRECTORY, - }, - ] - - projects[configPath] = [...(projects[configPath] ?? []), ...documentSelector] - - if (isCssFile) { - cssFileConfigMap.set(normalizedFilename, configPath) - } - } - - if (Object.keys(projects).length > 0) { - workspaceFolders = Object.entries(projects).map(([configPath, documentSelector]) => { - return { - folder: base, - configPath, - isUserConfigured: false, - documentSelector: documentSelector - .sort((a, z) => a.priority - z.priority) - .filter( - ({ pattern }, index, documentSelectors) => - documentSelectors.findIndex(({ pattern: p }) => p === pattern) === index - ), - } - }) - } } - console.log(`[Global] Creating projects: ${JSON.stringify(workspaceFolders)}`) - - const onDidChangeWatchedFiles = async ( - changes: Array<{ file: string; type: FileChangeType }> - ): Promise => { - let needsRestart = false - - changeLoop: for (let change of changes) { - let normalizedFilename = normalizePath(change.file) - - for (let ignorePattern of ignore) { - if (minimatch(normalizedFilename, ignorePattern, { dot: true })) { - continue changeLoop - } - } - - let isPackageFile = minimatch(normalizedFilename, `**/${PACKAGE_LOCK_GLOB}`, { dot: true }) - if (isPackageFile) { - for (let [key] of this.projects) { - let projectConfig = JSON.parse(key) as ProjectConfig - let twVersion = require('tailwindcss/package.json').version - try { - let v = require(resolveFrom( - path.dirname(projectConfig.configPath), - 'tailwindcss/package.json' - )).version - if (typeof v === 'string') { - twVersion = v - } - } catch {} - if (configTailwindVersionMap.get(projectConfig.configPath) !== twVersion) { - needsRestart = true - break changeLoop - } - } - } - - let isCssFile = minimatch(normalizedFilename, `**/${CSS_GLOB}`, { - dot: true, - }) - if (isCssFile) { - let configPath = await getConfigFileFromCssFile(change.file) - if ( - cssFileConfigMap.has(normalizedFilename) && - cssFileConfigMap.get(normalizedFilename) !== configPath - ) { - needsRestart = true - break - } else if (!cssFileConfigMap.has(normalizedFilename) && configPath) { - needsRestart = true - break - } - } - - let isConfigFile = minimatch(normalizedFilename, `**/${CONFIG_GLOB}`, { - dot: true, - }) - if (isConfigFile && change.type === FileChangeType.Created) { - needsRestart = true - break - } - - for (let [key] of this.projects) { - let projectConfig = JSON.parse(key) as ProjectConfig - if ( - change.type === FileChangeType.Deleted && - changeAffectsFile(normalizedFilename, [projectConfig.configPath]) - ) { - needsRestart = true - break changeLoop - } - } - } - - if (needsRestart) { - this.restart() - return - } + await Promise.all( + workspaceFolders.map((projectConfig) => this.addProject(projectConfig, this.initializeParams)) + ) - for (let [, project] of this.projects) { - project.onFileEvents(changes) - } - } + this.setupLSPHandlers() if (this.initializeParams.capabilities.workspace?.didChangeWatchedFiles?.dynamicRegistration) { - this.disposables.push( - this.connection.onDidChangeWatchedFiles(async ({ changes }) => { - let normalizedChanges = changes - .map(({ uri, type }) => ({ + this.connection.onDidChangeWatchedFiles(({ changes }) => { + for (let [, project] of this.projects) { + project.onFileEvents( + changes.map(({ uri, type }) => ({ file: URI.parse(uri).fsPath, type, })) - .filter( - (change, changeIndex, changes) => - changes.findIndex((c) => c.file === change.file && c.type === change.type) === - changeIndex - ) - - await onDidChangeWatchedFiles(normalizedChanges) - }) - ) - - let disposable = await this.connection.client.register( - DidChangeWatchedFilesNotification.type, - { - watchers: [ - { globPattern: `**/${CONFIG_GLOB}` }, - { globPattern: `**/${PACKAGE_LOCK_GLOB}` }, - { globPattern: `**/${CSS_GLOB}` }, - ], - } - ) - - this.disposables.push(disposable) - - this.watchPatterns = (patterns) => { - let newPatterns = this.filterNewWatchPatterns(patterns) - if (newPatterns.length) { - console.log(`[Global] Adding watch patterns: ${newPatterns.join(', ')}`) - this.connection.client - .register(DidChangeWatchedFilesNotification.type, { - watchers: newPatterns.map((pattern) => ({ globPattern: pattern })), - }) - .then((disposable) => { - this.disposables.push(disposable) - }) - } - } - } else if (parcel.getBinding()) { - let typeMap = { - create: FileChangeType.Created, - update: FileChangeType.Changed, - delete: FileChangeType.Deleted, - } - - let subscription = await parcel.subscribe( - base, - (err, events) => { - onDidChangeWatchedFiles( - events.map((event) => ({ file: event.path, type: typeMap[event.type] })) ) - }, - { - ignore: ignore.map((ignorePattern) => - path.resolve(base, ignorePattern.replace(/^[*/]+/, '').replace(/[*/]+$/, '')) - ), } - ) - - this.disposables.push({ - dispose() { - subscription.unsubscribe() - }, }) - } else { - let watch: typeof chokidar.watch = require('chokidar').watch - let chokidarWatcher = watch( - [`**/${CONFIG_GLOB}`, `**/${PACKAGE_LOCK_GLOB}`, `**/${CSS_GLOB}`], - { - cwd: base, - ignorePermissionErrors: true, - ignoreInitial: true, - ignored: ignore, - awaitWriteFinish: { - stabilityThreshold: 100, - pollInterval: 20, - }, - } - ) - - await new Promise((resolve) => { - chokidarWatcher.on('ready', () => resolve()) - }) - - chokidarWatcher - .on('add', (file) => - onDidChangeWatchedFiles([ - { file: path.resolve(base, file), type: FileChangeType.Created }, - ]) - ) - .on('change', (file) => - onDidChangeWatchedFiles([ - { file: path.resolve(base, file), type: FileChangeType.Changed }, - ]) - ) - .on('unlink', (file) => - onDidChangeWatchedFiles([ - { file: path.resolve(base, file), type: FileChangeType.Deleted }, - ]) - ) - - this.disposables.push({ - dispose() { - chokidarWatcher.close() - }, - }) - - this.watchPatterns = (patterns) => { - let newPatterns = this.filterNewWatchPatterns(patterns) - if (newPatterns.length) { - console.log(`[Global] Adding watch patterns: ${newPatterns.join(', ')}`) - chokidarWatcher.add(newPatterns) - } - } } - await Promise.all( - workspaceFolders.map((projectConfig) => - this.addProject( - projectConfig, - this.initializeParams, - this.watchPatterns, - configTailwindVersionMap.get(projectConfig.configPath) - ) - ) - ) - - // init projects for documents that are _already_ open - for (let document of this.documentService.getAllDocuments()) { - let project = this.getProject(document) - if (project && !project.enabled()) { - project.enable() - await project.tryInit() + this.connection.onDidChangeConfiguration(async ({ settings }) => { + for (let [, project] of this.projects) { + project.onUpdateSettings(settings) } - } - - this.setupLSPHandlers() - - this.disposables.push( - this.connection.onDidChangeConfiguration(async ({ settings }) => { - let previousExclude = globalSettings.tailwindCSS.files.exclude - - documentSettingsCache.clear() - globalSettings = await getConfiguration() - - if (!equal(previousExclude, globalSettings.tailwindCSS.files.exclude)) { - this.restart() - return - } - - for (let [, project] of this.projects) { - project.onUpdateSettings(settings) - } - }) - ) - - this.disposables.push( - this.connection.onShutdown(() => { - this.dispose() - }) - ) - - this.disposables.push( - this.documentService.onDidChangeContent((change) => { - this.getProject(change.document)?.provideDiagnostics(change.document) - }) - ) + }) - this.disposables.push( - this.documentService.onDidOpen((event) => { - let project = this.getProject(event.document) - if (project && !project.enabled()) { - project.enable() - project.tryInit() - } - }) - ) - } + this.connection.onShutdown(() => { + this.dispose() + }) - private filterNewWatchPatterns(patterns: string[]) { - let newWatchPatterns = patterns.filter((pattern) => !this.watched.includes(pattern)) - this.watched.push(...newWatchPatterns) - return newWatchPatterns + this.documentService.onDidChangeContent((change) => { + this.getProject(change.document)?.provideDiagnostics(change.document) + }) } - private async addProject( - projectConfig: ProjectConfig, - params: InitializeParams, - watchPatterns: (patterns: string[]) => void, - tailwindVersion: string - ): Promise { + private async addProject(projectConfig: ProjectConfig, params: InitializeParams): Promise { let key = JSON.stringify(projectConfig) - - if (!this.projects.has(key)) { + if (this.projects.has(key)) { + await this.projects.get(key).tryInit() + } else { const project = await createProjectService( projectConfig, this.connection, params, this.documentService, - () => this.updateCapabilities(), - () => { - for (let document of this.documentService.getAllDocuments()) { - let project = this.getProject(document) - if (project && !project.enabled()) { - project.enable() - project.tryInit() - break - } - } - }, - () => this.refreshDiagnostics(), - (patterns: string[]) => watchPatterns(patterns), - tailwindVersion + () => this.updateCapabilities() ) this.projects.set(key, project) - } - } - - private refreshDiagnostics() { - for (let doc of this.documentService.getAllDocuments()) { - let project = this.getProject(doc) - if (project) { - project.provideDiagnosticsForce(doc) - } else { - this.connection.sendDiagnostics({ uri: doc.uri, diagnostics: [] }) - } + await project.tryInit() } } private setupLSPHandlers() { - if (this.lspHandlersAdded) { - return - } - this.lspHandlersAdded = true - this.connection.onHover(this.onHover.bind(this)) this.connection.onCompletion(this.onCompletion.bind(this)) this.connection.onCompletionResolve(this.onCompletionResolve.bind(this)) @@ -2086,38 +1567,23 @@ .map((sep) => sep.slice(-1)), ].filter(Boolean), }) + capabilities.add(DidChangeWatchedFilesNotification.type, { + watchers: projects.flatMap( + (project) => project.state.dependencies?.map((file) => ({ globPattern: file })) ?? [] + ), + }) + this.registrations = this.connection.client.register(capabilities) } private getProject(document: TextDocumentIdentifier): ProjectService { let fallbackProject: ProjectService - let matchedProject: ProjectService - let matchedPriority: number = Infinity - for (let [key, project] of this.projects) { let projectConfig = JSON.parse(key) as ProjectConfig if (projectConfig.configPath) { - let documentSelector = project - .documentSelector() - .concat() - // move all the negated patterns to the front - .sort((a, z) => { - if (a.pattern.startsWith('!') && !z.pattern.startsWith('!')) { - return -1 - } - if (!a.pattern.startsWith('!') && z.pattern.startsWith('!')) { - return 1 - } - return 0 - }) - for (let { pattern, priority } of documentSelector) { - let fsPath = URI.parse(document.uri).fsPath - if (pattern.startsWith('!') && minimatch(fsPath, pattern.slice(1), { dot: true })) { - break - } - if (minimatch(fsPath, pattern, { dot: true }) && priority < matchedPriority) { - matchedProject = project - matchedPriority = priority + for (let selector of projectConfig.documentSelector) { + if (minimatch(URI.parse(document.uri).fsPath, selector)) { + return project } } } else { @@ -2126,11 +1592,6 @@ fallbackProject = project } } } - - if (matchedProject) { - return matchedProject - } - return fallbackProject } @@ -2170,26 +1631,6 @@ dispose(): void { for (let [, project] of this.projects) { project.dispose() } - this.projects = new Map() - - this.refreshDiagnostics() - - if (this.registrations) { - this.registrations.then((r) => r.dispose()) - this.registrations = undefined - } - - this.disposables.forEach((d) => d.dispose()) - this.disposables.length = 0 - - this.watched.length = 0 - } - - restart(): void { - console.log('----------\nRESTARTING\n----------') - this.dispose() - this.initialized = false - this.init() } } @@ -2214,9 +1655,6 @@ return this.documents.onDidChangeContent } get onDidClose() { return this.documents.onDidClose - } - get onDidOpen() { - return this.documents.onDidOpen } } diff --git a/packages/tailwindcss-language-server/src/util/error.ts b/packages/tailwindcss-language-server/src/util/error.ts index 00fdac2592cf946d8c5b454db8a03cfe70ce671a..620412e0cecc14f2aeca93055bc0825d52278ef7 100644 --- a/packages/tailwindcss-language-server/src/util/error.ts +++ b/packages/tailwindcss-language-server/src/util/error.ts @@ -25,11 +25,11 @@ err: any, message: string = 'Tailwind CSS' ): void { console.error(formatError(message, err)) - // if (!(err instanceof SilentError)) { - // connection.sendNotification('@/tailwindCSS/error', { - // message: formatError(message, err, false), - // }) - // } + if (!(err instanceof SilentError)) { + connection.sendNotification('@/tailwindCSS/error', { + message: formatError(message, err, false), + }) + } } export function SilentError(message: string) { diff --git a/packages/tailwindcss-language-service/src/codeActions/codeActionProvider.ts b/packages/tailwindcss-language-service/src/codeActions/codeActionProvider.ts index df30e6610a098d3409e49592af8cdbf9b8fd4a28..cc3fbc63c752f51d817fdcea685b50a3d9049f69 100644 --- a/packages/tailwindcss-language-service/src/codeActions/codeActionProvider.ts +++ b/packages/tailwindcss-language-service/src/codeActions/codeActionProvider.ts @@ -24,7 +24,6 @@ params: CodeActionParams, only?: DiagnosticKind[] ): Promise { let document = state.editor.documents.get(params.textDocument.uri) - if (!document) return [] let diagnostics = await doValidate(state, document, only) return params.context.diagnostics @@ -41,10 +40,6 @@ .filter(Boolean) } export async function doCodeActions(state: State, params: CodeActionParams): Promise { - if (!state.enabled) { - return [] - } - let diagnostics = await getDiagnosticsFromCodeActionParams( state, params, diff --git a/packages/tailwindcss-language-service/src/codeActions/provideInvalidApplyCodeActions.ts b/packages/tailwindcss-language-service/src/codeActions/provideInvalidApplyCodeActions.ts index c212eead15bf139f3f373bf56d5ce4808a0ef71e..3730492a688a967d2b088d953e3ab4ae10323f6f 100644 --- a/packages/tailwindcss-language-service/src/codeActions/provideInvalidApplyCodeActions.ts +++ b/packages/tailwindcss-language-service/src/codeActions/provideInvalidApplyCodeActions.ts @@ -25,7 +25,6 @@ params: CodeActionParams, diagnostic: InvalidApplyDiagnostic ): Promise { let document = state.editor.documents.get(params.textDocument.uri) - if (!document) return [] let documentText = getTextWithoutComments(document, 'css') let cssRange: Range let cssText = documentText diff --git a/packages/tailwindcss-language-service/src/diagnostics/diagnosticsProvider.ts b/packages/tailwindcss-language-service/src/diagnostics/diagnosticsProvider.ts index a8d7f7dcec481c422d802a3d0b1bd8ccdbdf8bed..5ef686a605e75066a8c177eff5d969e790942ebc 100644 --- a/packages/tailwindcss-language-service/src/diagnostics/diagnosticsProvider.ts +++ b/packages/tailwindcss-language-service/src/diagnostics/diagnosticsProvider.ts @@ -50,3 +50,29 @@ : []), ] : [] } + +export async function provideDiagnostics(state: State, document: TextDocument) { + // state.editor.connection.sendDiagnostics({ + // uri: document.uri, + // diagnostics: await doValidate(state, document), + // }) +} + +export function clearDiagnostics(state: State, document: TextDocument): void { + // state.editor.connection.sendDiagnostics({ + // uri: document.uri, + // diagnostics: [], + // }) +} + +export function clearAllDiagnostics(state: State): void { + state.editor.documents.all().forEach((document) => { + clearDiagnostics(state, document) + }) +} + +export function updateAllDiagnostics(state: State): void { + state.editor.documents.all().forEach((document) => { + provideDiagnostics(state, document) + }) +} diff --git a/packages/tailwindcss-language-service/src/util/state.ts b/packages/tailwindcss-language-service/src/util/state.ts index 5d5c8321727efd11d0426c3d3eca1678def9318f..c958d2b1aeb517b609b701fb1524e202b2719882 100644 --- a/packages/tailwindcss-language-service/src/util/state.ts +++ b/packages/tailwindcss-language-service/src/util/state.ts @@ -21,6 +21,7 @@ export type EditorState = { connection: Connection folder: string documents: TextDocuments + globalSettings: Settings userLanguages: Record capabilities: { configuration: boolean diff --git a/packages/vscode-tailwindcss/src/extension.ts b/packages/vscode-tailwindcss/src/extension.ts index e618e06cf86e348ce942c0b94ba102df3307d186..d1cb232a15727263b29110a09d58a70b75da31fe 100755 --- a/packages/vscode-tailwindcss/src/extension.ts +++ b/packages/vscode-tailwindcss/src/extension.ts @@ -42,12 +42,13 @@ 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' -import { CONFIG_GLOB, CSS_GLOB } from 'tailwindcss-language-server/src/lib/constants' const colorNames = Object.keys(namedColors) const CLIENT_ID = 'tailwindcss-intellisense' const CLIENT_NAME = 'Tailwind CSS IntelliSense' + +const CONFIG_FILE_GLOB = '{tailwind,tailwind.config}.{js,cjs}' let clients: Map = new Map() let languages: Map = new Map() @@ -130,11 +131,6 @@ }, } } -async function fileContainsAtConfig(uri: Uri) { - let contents = (await Workspace.fs.readFile(uri)).toString() - return /@config\s*['"]/.test(contents) -} - export async function activate(context: ExtensionContext) { let module = context.asAbsolutePath(path.join('dist', 'server.js')) let prod = path.join('dist', 'tailwindServer.js') @@ -210,36 +206,20 @@ // } // ) // ) - let configWatcher = Workspace.createFileSystemWatcher(`**/${CONFIG_GLOB}`, false, true, true) - - configWatcher.onDidCreate((uri) => { - let folder = Workspace.getWorkspaceFolder(uri) - if (!folder || isExcluded(uri.fsPath, folder)) { - return - } - folder = getOuterMostWorkspaceFolder(folder) - bootWorkspaceClient(folder) - }) + let watcher = Workspace.createFileSystemWatcher(`**/${CONFIG_FILE_GLOB}`, false, true, true) - context.subscriptions.push(configWatcher) - - let cssWatcher = Workspace.createFileSystemWatcher(`**/${CSS_GLOB}`, false, false, true) - - async function bootClientIfCssFileContainsAtConfig(uri: Uri) { + watcher.onDidCreate((uri) => { let folder = Workspace.getWorkspaceFolder(uri) - if (!folder || isExcluded(uri.fsPath, folder)) { + if (!folder) { return } - if (await fileContainsAtConfig(uri)) { + if (!isExcluded(uri.fsPath, folder)) { folder = getOuterMostWorkspaceFolder(folder) bootWorkspaceClient(folder) } - } + }) - cssWatcher.onDidCreate(bootClientIfCssFileContainsAtConfig) - cssWatcher.onDidChange(bootClientIfCssFileContainsAtConfig) - - context.subscriptions.push(cssWatcher) + context.subscriptions.push(watcher) // TODO: check if the actual language MAPPING changed // not just the language IDs @@ -627,27 +607,16 @@ searchedFolders.add(folder.uri.toString()) let [configFile] = await Workspace.findFiles( - new RelativePattern(folder, `**/${CONFIG_GLOB}`), + new RelativePattern(folder, `**/${CONFIG_FILE_GLOB}`), `{${getExcludePatterns(folder).join(',')}}`, 1 ) - if (configFile) { - bootWorkspaceClient(folder) + if (!configFile) { return } - let cssFiles = await Workspace.findFiles( - new RelativePattern(folder, `**/${CSS_GLOB}`), - `{${getExcludePatterns(folder).join(',')}}` - ) - - for (let cssFile of cssFiles) { - if (await fileContainsAtConfig(cssFile)) { - bootWorkspaceClient(folder) - return - } - } + bootWorkspaceClient(folder) } context.subscriptions.push(Workspace.onDidOpenTextDocument(didOpenTextDocument))