Skip to content

wip: Improve language server detection for workspace folders #1334

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 68 additions & 35 deletions packages/vscode-tailwindcss/src/analyze.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,33 +29,53 @@ export async function anyWorkspaceFoldersNeedServer({ folders, token }: SearchOp
if (typeof configFilePath === 'object' && Object.values(configFilePath).length > 0) return true
}

let configs: Array<() => Thenable<Uri[]>> = []
let stylesheets: Array<() => Thenable<Uri[]>> = []
// If any search returns that it needs a workspace then the server needs to be started
// and the remainder of the searches will be cancelled
let searches = folders.map((folder) =>
workspaceFoldersNeedServer({ folder, token }).then((found) => {
if (found) return true

for (let folder of folders) {
let exclusions = getExcludePatterns(folder).flatMap((pattern) => braces.expand(pattern))
let exclude = `{${exclusions.join(',').replace(/{/g, '%7B').replace(/}/g, '%7D')}}`

configs.push(() =>
workspace.findFiles(
new RelativePattern(folder, `**/${CONFIG_GLOB}`),
exclude,
undefined,
token,
),
)

stylesheets.push(() =>
workspace.findFiles(new RelativePattern(folder, `**/${CSS_GLOB}`), exclude, undefined, token),
)
// We use `throw` so we can use Promise.any(…)
throw new Error(DUMMY_ERROR_MESSAGE)
}),
)

const DUMMY_ERROR_MESSAGE = 'Workspace folder not needed'

try {
return await Promise.any(searches)
} catch (err) {
for (let anErr of (err as AggregateError).errors ?? []) {
if (typeof anErr === 'object' && err.message === DUMMY_ERROR_MESSAGE) {
continue
}

console.error(anErr)
}

return false
}
}

export interface FolderSearchOptions {
folder: WorkspaceFolder
token: CancellationToken
}

async function workspaceFoldersNeedServer({ folder, token }: FolderSearchOptions) {
let exclusions = getExcludePatterns(folder).flatMap((pattern) => braces.expand(pattern))
let exclude = `{${exclusions.join(',').replace(/{/g, '%7B').replace(/}/g, '%7D')}}`

// If we find a config file then we need the server
let configUrls = await Promise.all(configs.map((fn) => fn()))
for (let group of configUrls) {
if (group.length > 0) {
return true
}
let configs = await workspace.findFiles(
new RelativePattern(folder, `**/${CONFIG_GLOB}`),
exclude,
undefined,
token,
)

if (configs.length > 0) {
return true
}

// If we find a possibly-related stylesheet then we need the server
Expand All @@ -65,12 +85,16 @@ export async function anyWorkspaceFoldersNeedServer({ folders, token }: SearchOp
// This is also, unfortunately, prone to starting the server unncessarily
// in projects that don't use TailwindCSS so we do this one-by-one instead
// of all at once to keep disk I/O low.
let stylesheetUrls = await Promise.all(stylesheets.map((fn) => fn()))
for (let group of stylesheetUrls) {
for (let file of group) {
if (await fileMayBeTailwindRelated(file)) {
return true
}
let stylesheets = await workspace.findFiles(
new RelativePattern(folder, `**/${CSS_GLOB}`),
exclude,
undefined,
token,
)

for (let file of stylesheets) {
if (await fileMayBeTailwindRelated(file)) {
return true
}
}
}
Expand All @@ -84,10 +108,19 @@ export async function fileMayBeTailwindRelated(uri: Uri) {
let buffer = await workspace.fs.readFile(uri)
let contents = buffer.toString()

return (
HAS_CONFIG.test(contents) ||
HAS_IMPORT.test(contents) ||
HAS_TAILWIND.test(contents) ||
HAS_THEME.test(contents)
)
// This is a clear signal that this is Tailwind related in v0–v4
if (HAS_CONFIG.test(contents)) return true

if (uri.path.endsWith('.css')) {
// In v4 these are Tailwind related *in .css files only*
// other stylesheets like lesss, stylus, etc… don't consider these files
if (HAS_THEME.test(contents)) return true
if (HAS_TAILWIND.test(contents)) return true

// @import *might* signal the need for the language server we'll have to
// start it, let it check, and hope we were right.
if (HAS_IMPORT.test(contents)) return true
}

return false
}
5 changes: 4 additions & 1 deletion packages/vscode-tailwindcss/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@ export async function createApi({ context, outputChannel }: ApiOptions) {

async function workspaceNeedsLanguageServer() {
if (folderAnalysis) return folderAnalysis
let found = false

let source: CancellationTokenSource | null = new CancellationTokenSource()
source.token.onCancellationRequested(() => {
source?.dispose()
source = null
if (found) return

outputChannel.appendLine(
'Server was not started. Search for Tailwind CSS-related files was taking too long.',
Expand All @@ -32,7 +34,8 @@ export async function createApi({ context, outputChannel }: ApiOptions) {
})

let result = await folderAnalysis
source?.dispose()
found = true
source?.cancel()
return result
}

Expand Down
Loading