Skip to content

Use paths relative to opened folder when searching for projects #1013

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

Merged
merged 8 commits into from
Jul 5, 2024
Merged
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
41 changes: 37 additions & 4 deletions packages/tailwindcss-language-server/src/project-locator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ function testFixture(fixture: string, details: any[]) {

expect(actual).toEqual(expected)
}

if (detail?.selectors) {
let expected = detail?.selectors.map((path) => path.replace('{URL}', fixturePath)).sort()

let actual = project.documentSelector.map((selector) => selector.pattern).sort()

expect(actual).toEqual(expected)
}
}

expect(projects).toHaveLength(details.length)
Expand Down Expand Up @@ -90,10 +98,35 @@ testFixture('v4/multi-config', [
])

testFixture('v4/workspaces', [
{ config: 'packages/admin/app.css' },
// { config: 'packages/shared/ui.css' }, // Should this be included?
// { config: 'packages/style-export/lib.css' }, // Should this be included?
{ config: 'packages/web/app.css' },
{
config: 'packages/admin/app.css',
selectors: [
'{URL}/node_modules/tailwindcss/**',
'{URL}/node_modules/tailwindcss/index.css',
'{URL}/node_modules/tailwindcss/theme.css',
'{URL}/node_modules/tailwindcss/utilities.css',
'{URL}/packages/admin/**',
'{URL}/packages/admin/app.css',
'{URL}/packages/admin/package.json',
],
},
{
config: 'packages/web/app.css',
selectors: [
'{URL}/node_modules/tailwindcss/**',
'{URL}/node_modules/tailwindcss/index.css',
'{URL}/node_modules/tailwindcss/theme.css',
'{URL}/node_modules/tailwindcss/utilities.css',
'{URL}/packages/style-export/**',
'{URL}/packages/style-export/lib.css',
'{URL}/packages/style-export/theme.css',
'{URL}/packages/style-main-field/**',
'{URL}/packages/style-main-field/lib.css',
'{URL}/packages/web/**',
'{URL}/packages/web/app.css',
'{URL}/packages/web/package.json',
],
},
])

testFixture('v4/auto-content', [
Expand Down
81 changes: 51 additions & 30 deletions packages/tailwindcss-language-server/src/project-locator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,20 +242,31 @@ export class ProjectLocator {
concurrency: Math.max(os.cpus().length, 1),
})

files = await Promise.all(
files.map(async (file) => {
// Resolve symlinks for all found files
let actualPath = await fs.realpath(file)

// Ignore network paths on Windows. Resolving relative paths on a
// netshare throws in `enhanced-resolve` :/
if (actualPath.startsWith('\\') && process.platform === 'win32') {
return normalizePath(file)
}
let realpaths = await Promise.all(files.map((file) => fs.realpath(file)))

return normalizePath(actualPath)
}),
)
// Remove files that are symlinked yet have an existing file in the list
files = files.filter((normalPath, idx) => {
let realPath = realpaths[idx]

if (normalPath === realPath) {
return true
}

// If the file is a symlink, aliased path, network share, etc…; AND
// the realpath is not already in the list of files, then we can add
// the file to the list of files
//
// For example, node_modules in a monorepo setup would be symlinked
// and list both unless you opened one of the directories directly
else if (!files.includes(realPath)) {
return true
}

return false
})

// Make sure Windows-style paths are normalized
files = files.map((file) => normalizePath(file))

// Deduplicate the list of files and sort them for deterministic results
// across environments
Expand Down Expand Up @@ -327,6 +338,9 @@ export class ProjectLocator {
// Resolve imports in all the CSS files
await Promise.all(imports.map((file) => file.resolveImports()))

// Resolve real paths for all the files in the CSS import graph
await Promise.all(imports.map((file) => file.resolveRealpaths()))

// Create a graph of all the CSS files that might (indirectly) use Tailwind
let graph = new Graph<FileEntry>()

Expand All @@ -335,24 +349,21 @@ export class ProjectLocator {
let utilitiesPath: string | null = null

for (let file of imports) {
graph.add(file.path, file)

for (let msg of file.deps) {
let importedPath: string = normalizePath(msg.file)

// Record that `file.path` imports `msg.file`
graph.add(importedPath, new FileEntry('css', importedPath))
graph.add(file.realpath, file)

graph.connect(file.path, importedPath)
// Record that `file.path` imports `msg.file`
for (let entry of file.deps) {
graph.add(entry.realpath, entry)
graph.connect(file.realpath, entry.realpath)
}

// Collect the index, theme, and utilities files for manual connection
if (file.path.includes('node_modules/tailwindcss/index.css')) {
indexPath = file.path
} else if (file.path.includes('node_modules/tailwindcss/theme.css')) {
themePath = file.path
} else if (file.path.includes('node_modules/tailwindcss/utilities.css')) {
utilitiesPath = file.path
if (file.realpath.includes('node_modules/tailwindcss/index.css')) {
indexPath = file.realpath
} else if (file.realpath.includes('node_modules/tailwindcss/theme.css')) {
themePath = file.realpath
} else if (file.realpath.includes('node_modules/tailwindcss/utilities.css')) {
utilitiesPath = file.realpath
}
}

Expand Down Expand Up @@ -383,7 +394,7 @@ export class ProjectLocator {

// And add the config to all their descendants as we need to track updates
// that might affect the config / project
for (let child of graph.descendants(root.path)) {
for (let child of graph.descendants(root.realpath)) {
child.configs.push(config)
}
}
Expand Down Expand Up @@ -540,7 +551,8 @@ type ConfigEntry = {

class FileEntry {
content: string | null
deps: Message[] = []
deps: FileEntry[] = []
realpath: string | null

constructor(
public type: 'js' | 'css',
Expand All @@ -559,7 +571,10 @@ class FileEntry {
async resolveImports() {
try {
let result = await resolveCssImports().process(this.content, { from: this.path })
this.deps = result.messages.filter((msg) => msg.type === 'dependency')
let deps = result.messages.filter((msg) => msg.type === 'dependency')

// Record entries for each of the dependencies
this.deps = deps.map((msg) => new FileEntry('css', normalizePath(msg.file)))

// Replace the file content with the processed CSS
this.content = result.css
Expand All @@ -568,6 +583,12 @@ class FileEntry {
}
}

async resolveRealpaths() {
this.realpath = normalizePath(await fs.realpath(this.path))

await Promise.all(this.deps.map((entry) => entry.resolveRealpaths()))
}

/**
* Look for `@config` directives in a CSS file and return the path to the config
* file that it points to. This path is (possibly) relative to the CSS file so
Expand Down
28 changes: 19 additions & 9 deletions packages/tailwindcss-language-server/src/tw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -634,9 +634,15 @@ export class TW {
}

private filterNewWatchPatterns(patterns: string[]) {
let newWatchPatterns = patterns.filter((pattern) => !this.watched.includes(pattern))
this.watched.push(...newWatchPatterns)
return newWatchPatterns
// Make sure the list of patterns is unique
patterns = Array.from(new Set(patterns))

// Filter out any patterns that are already being watched
patterns = patterns.filter((pattern) => !this.watched.includes(pattern))

this.watched.push(...patterns)

return patterns
}

private async addProject(
Expand Down Expand Up @@ -792,11 +798,6 @@ export class TW {
// to normalize it so that we can compare it properly.
fsPath = normalizeDriveLetter(fsPath)

console.debug('[GLOBAL] Matching project to document', {
fsPath,
normalPath,
})

for (let project of this.projects.values()) {
if (!project.projectConfig.configPath) {
fallbackProject = fallbackProject ?? project
Expand Down Expand Up @@ -846,7 +847,16 @@ export class TW {
}
}

return matchedProject ?? fallbackProject
let project = matchedProject ?? fallbackProject

if (!project) {
console.debug('[GLOBAL] No matching project for document', {
fsPath,
normalPath,
})
}

return project
}

async onDocumentColor(params: DocumentColorParams): Promise<ColorInformation[]> {
Expand Down
2 changes: 1 addition & 1 deletion packages/vscode-tailwindcss/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Prerelease

- Nothing yet!
- Use paths relative to opened folder when searching for projects ([#1013](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1013))

## 0.12.4

Expand Down