Add automatic support for multi-config workspaces, including `@config` resolution (#633)
* wip * wip * Boot client if a CSS file contains `@config` * wip * Check document exists * wip * Fix duplicate document selector * wip * Use enum for document selector priorities * Delete unused functions * Remove unused state type * Share glob patterns * Update config file glob * fix logs * Fix filename checks on Windows * Don't show error popups * wip * handle negated content paths * Handle non-tailwind dependency installs * add package root to document selectors * tidy * wip * dedupe document selectors * Fix `@config` regex * Fix document selectors when using `experimental.configFile` * Remove logmaster
parent
68750d859b
commit
b26e122fac
|
@ -0,0 +1,3 @@
|
||||||
|
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}'
|
|
@ -20,15 +20,3 @@ export function clearDiagnostics(state: State, document: TextDocument): void {
|
||||||
diagnostics: [],
|
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)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -25,11 +25,11 @@ export function showError(
|
||||||
message: string = 'Tailwind CSS'
|
message: string = 'Tailwind CSS'
|
||||||
): void {
|
): void {
|
||||||
console.error(formatError(message, err))
|
console.error(formatError(message, err))
|
||||||
if (!(err instanceof SilentError)) {
|
// if (!(err instanceof SilentError)) {
|
||||||
connection.sendNotification('@/tailwindCSS/error', {
|
// connection.sendNotification('@/tailwindCSS/error', {
|
||||||
message: formatError(message, err, false),
|
// message: formatError(message, err, false),
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SilentError(message: string) {
|
export function SilentError(message: string) {
|
||||||
|
|
|
@ -24,6 +24,7 @@ async function getDiagnosticsFromCodeActionParams(
|
||||||
only?: DiagnosticKind[]
|
only?: DiagnosticKind[]
|
||||||
): Promise<AugmentedDiagnostic[]> {
|
): Promise<AugmentedDiagnostic[]> {
|
||||||
let document = state.editor.documents.get(params.textDocument.uri)
|
let document = state.editor.documents.get(params.textDocument.uri)
|
||||||
|
if (!document) return []
|
||||||
let diagnostics = await doValidate(state, document, only)
|
let diagnostics = await doValidate(state, document, only)
|
||||||
|
|
||||||
return params.context.diagnostics
|
return params.context.diagnostics
|
||||||
|
@ -40,6 +41,10 @@ async function getDiagnosticsFromCodeActionParams(
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function doCodeActions(state: State, params: CodeActionParams): Promise<CodeAction[]> {
|
export async function doCodeActions(state: State, params: CodeActionParams): Promise<CodeAction[]> {
|
||||||
|
if (!state.enabled) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
let diagnostics = await getDiagnosticsFromCodeActionParams(
|
let diagnostics = await getDiagnosticsFromCodeActionParams(
|
||||||
state,
|
state,
|
||||||
params,
|
params,
|
||||||
|
|
|
@ -25,6 +25,7 @@ export async function provideInvalidApplyCodeActions(
|
||||||
diagnostic: InvalidApplyDiagnostic
|
diagnostic: InvalidApplyDiagnostic
|
||||||
): Promise<CodeAction[]> {
|
): Promise<CodeAction[]> {
|
||||||
let document = state.editor.documents.get(params.textDocument.uri)
|
let document = state.editor.documents.get(params.textDocument.uri)
|
||||||
|
if (!document) return []
|
||||||
let documentText = getTextWithoutComments(document, 'css')
|
let documentText = getTextWithoutComments(document, 'css')
|
||||||
let cssRange: Range
|
let cssRange: Range
|
||||||
let cssText = documentText
|
let cssText = documentText
|
||||||
|
|
|
@ -50,29 +50,3 @@ export async function doValidate(
|
||||||
]
|
]
|
||||||
: []
|
: []
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
|
@ -21,7 +21,6 @@ export type EditorState = {
|
||||||
connection: Connection
|
connection: Connection
|
||||||
folder: string
|
folder: string
|
||||||
documents: TextDocuments<TextDocument>
|
documents: TextDocuments<TextDocument>
|
||||||
globalSettings: Settings
|
|
||||||
userLanguages: Record<string, string>
|
userLanguages: Record<string, string>
|
||||||
capabilities: {
|
capabilities: {
|
||||||
configuration: boolean
|
configuration: boolean
|
||||||
|
|
|
@ -42,14 +42,13 @@ import isObject from 'tailwindcss-language-service/src/util/isObject'
|
||||||
import { dedupe, equal } from 'tailwindcss-language-service/src/util/array'
|
import { dedupe, equal } from 'tailwindcss-language-service/src/util/array'
|
||||||
import namedColors from 'color-name'
|
import namedColors from 'color-name'
|
||||||
import minimatch from 'minimatch'
|
import minimatch from 'minimatch'
|
||||||
|
import { CONFIG_GLOB, CSS_GLOB } from 'tailwindcss-language-server/src/lib/constants'
|
||||||
|
|
||||||
const colorNames = Object.keys(namedColors)
|
const colorNames = Object.keys(namedColors)
|
||||||
|
|
||||||
const CLIENT_ID = 'tailwindcss-intellisense'
|
const CLIENT_ID = 'tailwindcss-intellisense'
|
||||||
const CLIENT_NAME = 'Tailwind CSS IntelliSense'
|
const CLIENT_NAME = 'Tailwind CSS IntelliSense'
|
||||||
|
|
||||||
const CONFIG_FILE_GLOB = '{tailwind,tailwind.config}.{js,cjs}'
|
|
||||||
|
|
||||||
let clients: Map<string, LanguageClient> = new Map()
|
let clients: Map<string, LanguageClient> = new Map()
|
||||||
let languages: Map<string, string[]> = new Map()
|
let languages: Map<string, string[]> = new Map()
|
||||||
let searchedFolders: Set<string> = new Set()
|
let searchedFolders: Set<string> = new Set()
|
||||||
|
@ -131,6 +130,11 @@ function mergeExcludes(settings: WorkspaceConfiguration, scope: ConfigurationSco
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function fileContainsAtConfig(uri: Uri) {
|
||||||
|
let contents = (await Workspace.fs.readFile(uri)).toString()
|
||||||
|
return /@config\s*['"]/.test(contents)
|
||||||
|
}
|
||||||
|
|
||||||
export async function activate(context: ExtensionContext) {
|
export async function activate(context: ExtensionContext) {
|
||||||
let module = context.asAbsolutePath(path.join('dist', 'server.js'))
|
let module = context.asAbsolutePath(path.join('dist', 'server.js'))
|
||||||
let prod = path.join('dist', 'tailwindServer.js')
|
let prod = path.join('dist', 'tailwindServer.js')
|
||||||
|
@ -206,20 +210,36 @@ export async function activate(context: ExtensionContext) {
|
||||||
// )
|
// )
|
||||||
// )
|
// )
|
||||||
|
|
||||||
let watcher = Workspace.createFileSystemWatcher(`**/${CONFIG_FILE_GLOB}`, false, true, true)
|
let configWatcher = Workspace.createFileSystemWatcher(`**/${CONFIG_GLOB}`, false, true, true)
|
||||||
|
|
||||||
watcher.onDidCreate((uri) => {
|
configWatcher.onDidCreate((uri) => {
|
||||||
let folder = Workspace.getWorkspaceFolder(uri)
|
let folder = Workspace.getWorkspaceFolder(uri)
|
||||||
if (!folder) {
|
if (!folder || isExcluded(uri.fsPath, folder)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (!isExcluded(uri.fsPath, folder)) {
|
folder = getOuterMostWorkspaceFolder(folder)
|
||||||
|
bootWorkspaceClient(folder)
|
||||||
|
})
|
||||||
|
|
||||||
|
context.subscriptions.push(configWatcher)
|
||||||
|
|
||||||
|
let cssWatcher = Workspace.createFileSystemWatcher(`**/${CSS_GLOB}`, false, false, true)
|
||||||
|
|
||||||
|
async function bootClientIfCssFileContainsAtConfig(uri: Uri) {
|
||||||
|
let folder = Workspace.getWorkspaceFolder(uri)
|
||||||
|
if (!folder || isExcluded(uri.fsPath, folder)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (await fileContainsAtConfig(uri)) {
|
||||||
folder = getOuterMostWorkspaceFolder(folder)
|
folder = getOuterMostWorkspaceFolder(folder)
|
||||||
bootWorkspaceClient(folder)
|
bootWorkspaceClient(folder)
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
|
||||||
context.subscriptions.push(watcher)
|
cssWatcher.onDidCreate(bootClientIfCssFileContainsAtConfig)
|
||||||
|
cssWatcher.onDidChange(bootClientIfCssFileContainsAtConfig)
|
||||||
|
|
||||||
|
context.subscriptions.push(cssWatcher)
|
||||||
|
|
||||||
// TODO: check if the actual language MAPPING changed
|
// TODO: check if the actual language MAPPING changed
|
||||||
// not just the language IDs
|
// not just the language IDs
|
||||||
|
@ -607,16 +627,27 @@ export async function activate(context: ExtensionContext) {
|
||||||
searchedFolders.add(folder.uri.toString())
|
searchedFolders.add(folder.uri.toString())
|
||||||
|
|
||||||
let [configFile] = await Workspace.findFiles(
|
let [configFile] = await Workspace.findFiles(
|
||||||
new RelativePattern(folder, `**/${CONFIG_FILE_GLOB}`),
|
new RelativePattern(folder, `**/${CONFIG_GLOB}`),
|
||||||
`{${getExcludePatterns(folder).join(',')}}`,
|
`{${getExcludePatterns(folder).join(',')}}`,
|
||||||
1
|
1
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!configFile) {
|
if (configFile) {
|
||||||
|
bootWorkspaceClient(folder)
|
||||||
return
|
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)
|
bootWorkspaceClient(folder)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
context.subscriptions.push(Workspace.onDidOpenTextDocument(didOpenTextDocument))
|
context.subscriptions.push(Workspace.onDidOpenTextDocument(didOpenTextDocument))
|
||||||
|
|
Loading…
Reference in New Issue