Skip to content

Commit 7c23767

Browse files
authored
fix: migrate glob file watch for chokidar v4 (#2551)
#2539 chokidar v4 removes all glob support. This migrates fallback watcher and CSS global var to use the ignored config
1 parent 02847e0 commit 7c23767

File tree

6 files changed

+85
-19
lines changed

6 files changed

+85
-19
lines changed

packages/language-server/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,13 @@
4040
},
4141
"devDependencies": {
4242
"@types/estree": "^0.0.42",
43+
"@types/globrex": "^0.1.4",
4344
"@types/lodash": "^4.14.116",
4445
"@types/mocha": "^9.1.0",
4546
"@types/node": "^18.0.0",
4647
"@types/sinon": "^7.5.2",
4748
"cross-env": "^7.0.2",
49+
"globrex": "^0.1.2",
4850
"mocha": "^9.2.0",
4951
"sinon": "^11.0.0",
5052
"ts-node": "^10.0.0"

packages/language-server/src/lib/FallbackWatcher.ts

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
} from 'vscode-languageserver';
1010
import { pathToUrl } from '../utils';
1111
import { fileURLToPath } from 'url';
12+
import { Stats } from 'fs';
1213

1314
type DidChangeHandler = (para: DidChangeWatchedFilesParams) => void;
1415

@@ -20,18 +21,20 @@ export class FallbackWatcher {
2021

2122
private undeliveredFileEvents: FileEvent[] = [];
2223

23-
constructor(recursivePatterns: string, workspacePaths: string[]) {
24+
constructor(watchExtensions: string[], workspacePaths: string[]) {
2425
const gitOrNodeModules = /\.git|node_modules/;
25-
this.watcher = watch(
26-
workspacePaths.map((workspacePath) => join(workspacePath, recursivePatterns)),
27-
{
28-
ignored: gitOrNodeModules,
29-
// typescript would scan the project files on init.
30-
// We only need to know what got updated.
31-
ignoreInitial: true,
32-
ignorePermissionErrors: true
33-
}
34-
);
26+
const ignoredExtensions = (fileName: string, stats?: Stats) => {
27+
return (
28+
stats?.isFile() === true && !watchExtensions.some((ext) => fileName.endsWith(ext))
29+
);
30+
};
31+
this.watcher = watch(workspacePaths, {
32+
ignored: [gitOrNodeModules, ignoredExtensions],
33+
// typescript would scan the project files on init.
34+
// We only need to know what got updated.
35+
ignoreInitial: true,
36+
ignorePermissionErrors: true
37+
});
3538

3639
this.watcher
3740
.on('add', (path) => this.onFSEvent(path, FileChangeType.Created))

packages/language-server/src/plugins/css/CSSPlugin.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ import { StyleAttributeDocument } from './StyleAttributeDocument';
5050
import { getDocumentContext } from '../documentContext';
5151
import { FoldingRange, FoldingRangeKind } from 'vscode-languageserver-types';
5252
import { indentBasedFoldingRangeForTag } from '../../lib/foldingRange/indentFolding';
53+
import { isNotNullOrUndefined, urlToPath } from '../../utils';
5354

5455
export class CSSPlugin
5556
implements
@@ -68,7 +69,7 @@ export class CSSPlugin
6869
private cssLanguageServices: CSSLanguageServices;
6970
private workspaceFolders: WorkspaceFolder[];
7071
private triggerCharacters = ['.', ':', '-', '/'];
71-
private globalVars = new GlobalVars();
72+
private globalVars: GlobalVars;
7273

7374
constructor(
7475
docManager: DocumentManager,
@@ -80,6 +81,10 @@ export class CSSPlugin
8081
this.workspaceFolders = workspaceFolders;
8182
this.configManager = configManager;
8283
this.updateConfigs();
84+
const workspacePaths = workspaceFolders
85+
.map((folder) => urlToPath(folder.uri))
86+
.filter(isNotNullOrUndefined);
87+
this.globalVars = new GlobalVars(workspacePaths);
8388

8489
this.globalVars.watchFiles(this.configManager.get('css.globals'));
8590
this.configManager.onChange((config) => {

packages/language-server/src/plugins/css/global-vars.ts

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
import { watch, FSWatcher } from 'chokidar';
1+
import { FSWatcher, watch } from 'chokidar';
22
import { readFile } from 'fs';
3-
import { isNotNullOrUndefined, flatten } from '../../utils';
3+
import globrex from 'globrex';
4+
import { join } from 'path';
5+
import { flatten, isNotNullOrUndefined, normalizePath } from '../../utils';
46

57
const varRegex = /^\s*(--\w+.*?):\s*?([^;]*)/;
68

@@ -12,26 +14,67 @@ export interface GlobalVar {
1214

1315
export class GlobalVars {
1416
private fsWatcher?: FSWatcher;
17+
private watchedFiles: string | undefined;
1518
private globalVars = new Map<string, GlobalVar[]>();
19+
private readonly workspaceRoot: string[];
20+
21+
constructor(workspaceRoot: string[]) {
22+
this.workspaceRoot = workspaceRoot;
23+
}
1624

1725
watchFiles(filesToWatch: string): void {
18-
if (!filesToWatch) {
26+
if (!filesToWatch || this.watchedFiles === filesToWatch) {
1927
return;
2028
}
2129

30+
this.watchedFiles = filesToWatch;
2231
if (this.fsWatcher) {
2332
this.fsWatcher.close();
2433
this.globalVars.clear();
2534
}
2635

27-
this.fsWatcher = watch(filesToWatch.split(','))
36+
const paths = new Set<string>();
37+
const includePatterns = new Set<string>();
38+
39+
for (const root of this.workspaceRoot) {
40+
for (const filePath of filesToWatch.split(',')) {
41+
if (!filePath.includes('*')) {
42+
paths.add(filePath);
43+
continue;
44+
}
45+
46+
const normalizedPath = normalizePath(join(root, filePath));
47+
includePatterns.add(normalizedPath);
48+
const pathSegments = normalizedPath.split('**');
49+
let directory = pathSegments[0] || '.';
50+
paths.add(directory);
51+
}
52+
}
53+
54+
this.fsWatcher = watch(Array.from(paths), {
55+
ignored: this.createIgnoreMatcher(includePatterns)
56+
})
2857
.addListener('add', (file) => this.updateForFile(file))
2958
.addListener('change', (file) => {
3059
this.updateForFile(file);
3160
})
3261
.addListener('unlink', (file) => this.globalVars.delete(file));
3362
}
3463

64+
private createIgnoreMatcher(includePatterns: Set<string>) {
65+
if (includePatterns.size === 0) {
66+
return undefined;
67+
}
68+
69+
const regexList = Array.from(includePatterns).map(
70+
(pattern) => globrex(pattern, { globstar: true }).regex
71+
);
72+
73+
return (path: string) => {
74+
return !regexList.some((regex) => regex.test(path));
75+
};
76+
}
77+
3578
private updateForFile(filename: string) {
3679
// Inside a small timeout because it seems chikidar is "too fast"
3780
// and reading the file will then return empty content

packages/language-server/src/server.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,9 @@ export function startServer(options?: LSOptions) {
106106

107107
// Include Svelte files to better deal with scenarios such as switching git branches
108108
// where files that are not opened in the client could change
109-
const nonRecursiveWatchPattern = '*.{ts,js,mts,mjs,cjs,cts,json,svelte}';
109+
const watchExtensions = ['.ts', '.js', '.mts', '.mjs', '.cjs', '.cts', '.json', '.svelte'];
110+
const nonRecursiveWatchPattern =
111+
'*.{' + watchExtensions.map((ext) => ext.slice(1)).join(',') + '}';
110112
const recursiveWatchPattern = '**/' + nonRecursiveWatchPattern;
111113

112114
connection.onInitialize((evt) => {
@@ -120,7 +122,7 @@ export function startServer(options?: LSOptions) {
120122

121123
if (!evt.capabilities.workspace?.didChangeWatchedFiles) {
122124
const workspacePaths = workspaceUris.map(urlToPath).filter(isNotNullOrUndefined);
123-
watcher = new FallbackWatcher(recursiveWatchPattern, workspacePaths);
125+
watcher = new FallbackWatcher(watchExtensions, workspacePaths);
124126
watcher.onDidChangeWatchedFiles(onDidChangeWatchedFiles);
125127

126128
watchDirectory = (patterns) => {
@@ -338,7 +340,7 @@ export function startServer(options?: LSOptions) {
338340
connection?.client.register(DidChangeWatchedFilesNotification.type, {
339341
watchers: [
340342
{
341-
// Editors have exlude configs, such as VSCode with `files.watcherExclude`,
343+
// Editors have exclude configs, such as VSCode with `files.watcherExclude`,
342344
// which means it's safe to watch recursively here
343345
globPattern: recursiveWatchPattern
344346
}

pnpm-lock.yaml

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)