Skip to content

Commit e94b82e

Browse files
authored
fix: handle issues with svelte file watching (#2421)
Fix the neovim file-watching issue mentioned in Discord by checking if a directory actually exists before watching it. Also fixes #2419 through several measures: - don't close a document when it was changed - fix the retrieval of the document (pathToUrl) was missing
1 parent 5312279 commit e94b82e

File tree

7 files changed

+132
-10
lines changed

7 files changed

+132
-10
lines changed

packages/language-server/src/lib/documents/DocumentManager.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ export class DocumentManager {
4747
let document: Document;
4848
if (this.documents.has(textDocument.uri)) {
4949
document = this.documents.get(textDocument.uri)!;
50-
document.openedByClient = openedByClient;
50+
// open state should only be updated when the document is closed
51+
document.openedByClient ||= openedByClient;
5152
document.setText(textDocument.text);
5253
} else {
5354
document = this.createDocument(textDocument);

packages/language-server/src/plugins/typescript/LSAndTSDocResolver.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -204,11 +204,21 @@ export class LSAndTSDocResolver {
204204
* Updates snapshot path in all existing ts services and retrieves snapshot
205205
*/
206206
async updateSnapshotPath(oldPath: string, newPath: string): Promise<void> {
207+
const document = this.docManager.get(pathToUrl(oldPath));
208+
const isOpenedInClient = document?.openedByClient;
207209
for (const snapshot of this.globalSnapshotsManager.getByPrefix(oldPath)) {
208210
await this.deleteSnapshot(snapshot.filePath);
209211
}
210-
// This may not be a file but a directory, still try
211-
await this.getSnapshot(newPath);
212+
213+
if (isOpenedInClient) {
214+
this.docManager.openClientDocument({
215+
uri: pathToUrl(newPath),
216+
text: document!.getText()
217+
});
218+
} else {
219+
// This may not be a file but a directory, still try
220+
await this.getSnapshot(newPath);
221+
}
212222
}
213223

214224
/**

packages/language-server/src/plugins/typescript/TypeScriptPlugin.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ import {
3535
mapSymbolInformationToOriginal
3636
} from '../../lib/documents';
3737
import { LSConfigManager, LSTypescriptConfig } from '../../ls-config';
38-
import { isNotNullOrUndefined, isZeroLengthRange } from '../../utils';
38+
import { isNotNullOrUndefined, isZeroLengthRange, pathToUrl } from '../../utils';
3939
import {
4040
AppCompletionItem,
4141
AppCompletionList,
@@ -523,7 +523,7 @@ export class TypeScriptPlugin
523523

524524
const isSvelteFile = isSvelteFilePath(fileName);
525525
const isClientSvelteFile =
526-
isSvelteFile && this.documentManager.get(fileName)?.openedByClient;
526+
isSvelteFile && this.documentManager.get(pathToUrl(fileName))?.openedByClient;
527527

528528
if (changeType === FileChangeType.Deleted) {
529529
if (!isClientSvelteFile) {

packages/language-server/src/plugins/typescript/service.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -353,11 +353,15 @@ async function createLanguageService(
353353
return;
354354
}
355355

356+
const canonicalWorkspacePath = getCanonicalFileName(workspacePath);
356357
const patterns: RelativePattern[] = [];
357358

358359
Object.entries(wildcardDirectories).forEach(([dir, flags]) => {
359-
// already watched
360-
if (getCanonicalFileName(dir).startsWith(workspacePath)) {
360+
if (
361+
// already watched
362+
getCanonicalFileName(dir).startsWith(canonicalWorkspacePath) ||
363+
!tsSystem.directoryExists(dir)
364+
) {
361365
return;
362366
}
363367
patterns.push({

packages/language-server/src/server.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,8 @@ export function startServer(options?: LSOptions) {
104104
pendingWatchPatterns = patterns;
105105
};
106106

107+
// Include Svelte files to better deal with scenarios such as switching git branches
108+
// where files that are not opened in the client could change
107109
const nonRecursiveWatchPattern = '*.{ts,js,mts,mjs,cjs,cts,json,svelte}';
108110
const recursiveWatchPattern = '**/' + nonRecursiveWatchPattern;
109111

@@ -329,6 +331,8 @@ export function startServer(options?: LSOptions) {
329331
connection?.client.register(DidChangeWatchedFilesNotification.type, {
330332
watchers: [
331333
{
334+
// Editors have exlude configs, such as VSCode with `files.watcherExclude`,
335+
// which means it's safe to watch recursively here
332336
globPattern: recursiveWatchPattern
333337
}
334338
]

packages/language-server/test/plugins/typescript/TypescriptPlugin.test.ts

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ describe('TypescriptPlugin', function () {
5757
docManager
5858
);
5959
docManager.openClientDocument(<any>'some doc');
60-
return { plugin, document, lsAndTsDocResolver };
60+
return { plugin, document, lsAndTsDocResolver, docManager };
6161
}
6262

6363
it('provides document symbols', async () => {
@@ -618,7 +618,7 @@ describe('TypescriptPlugin', function () {
618618
});
619619

620620
const setupForOnWatchedFileChanges = async () => {
621-
const { plugin, document, lsAndTsDocResolver } = setup('empty.svelte');
621+
const { plugin, document, lsAndTsDocResolver, docManager } = setup('empty.svelte');
622622
const targetSvelteFile = document.getFilePath()!;
623623
const snapshotManager = (await lsAndTsDocResolver.getTSService(targetSvelteFile))
624624
.snapshotManager;
@@ -627,7 +627,8 @@ describe('TypescriptPlugin', function () {
627627
snapshotManager,
628628
plugin,
629629
targetSvelteFile,
630-
lsAndTsDocResolver
630+
lsAndTsDocResolver,
631+
docManager
631632
};
632633
};
633634

@@ -769,6 +770,49 @@ describe('TypescriptPlugin', function () {
769770
);
770771
});
771772

773+
it("shouldn't close svelte document when renamed", async () => {
774+
const { plugin, docManager, targetSvelteFile } = await setupForOnWatchedFileChanges();
775+
docManager.openClientDocument({
776+
text: '',
777+
uri: pathToUrl(targetSvelteFile)
778+
});
779+
780+
const basename = path.basename(targetSvelteFile);
781+
const newFileName = basename.replace('.svelte', '').toUpperCase() + '.svelte';
782+
const newFilePath = path.join(path.dirname(targetSvelteFile), newFileName);
783+
await plugin.onWatchFileChanges([
784+
{
785+
fileName: targetSvelteFile,
786+
changeType: FileChangeType.Deleted
787+
},
788+
{
789+
fileName: newFilePath,
790+
changeType: FileChangeType.Created
791+
}
792+
]);
793+
794+
const document = docManager.get(pathToUrl(targetSvelteFile));
795+
assert.ok(document);
796+
});
797+
798+
it("shouldn't mark client svelte document as close", async () => {
799+
const { plugin, docManager, targetSvelteFile } = await setupForOnWatchedFileChanges();
800+
docManager.openClientDocument({
801+
text: '',
802+
uri: pathToUrl(targetSvelteFile)
803+
});
804+
805+
await plugin.onWatchFileChanges([
806+
{
807+
fileName: targetSvelteFile,
808+
changeType: FileChangeType.Changed
809+
}
810+
]);
811+
812+
const document = docManager.get(pathToUrl(targetSvelteFile));
813+
assert.equal(document?.openedByClient, true);
814+
});
815+
772816
// Hacky, but it works. Needed due to testing both new and old transformation
773817
after(() => {
774818
__resetCache();

packages/language-server/test/plugins/typescript/service.test.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import {
99
} from '../../../src/plugins/typescript/service';
1010
import { pathToUrl } from '../../../src/utils';
1111
import { createVirtualTsSystem, getRandomVirtualDirPath } from './test-utils';
12+
import sinon from 'sinon';
13+
import { RelativePattern } from 'vscode-languageserver-protocol';
1214

1315
describe('service', () => {
1416
const testDir = path.join(__dirname, 'testfiles');
@@ -263,4 +265,61 @@ describe('service', () => {
263265
ls.getService().getSemanticDiagnostics(document.getFilePath()!);
264266
});
265267
});
268+
269+
it('skip directory watching if directory is root', async () => {
270+
const dirPath = getRandomVirtualDirPath(path.join(testDir, 'Test'));
271+
const { virtualSystem, lsDocumentContext } = setup();
272+
273+
const rootUris = [pathToUrl(dirPath)];
274+
275+
const watchDirectory = sinon.spy();
276+
lsDocumentContext.watchDirectory = watchDirectory;
277+
lsDocumentContext.nonRecursiveWatchPattern = '*.ts';
278+
279+
virtualSystem.readDirectory = () => [];
280+
virtualSystem.directoryExists = () => true;
281+
282+
virtualSystem.writeFile(
283+
path.join(dirPath, 'tsconfig.json'),
284+
JSON.stringify({
285+
compilerOptions: {},
286+
include: ['src/**/*.ts', 'test/**/*.ts', '../foo/**/*.ts']
287+
})
288+
);
289+
290+
await getService(path.join(dirPath, 'random.svelte'), rootUris, lsDocumentContext);
291+
292+
sinon.assert.calledWith(watchDirectory.firstCall, <RelativePattern[]>[
293+
{
294+
baseUri: pathToUrl(path.join(dirPath, '../foo')),
295+
pattern: '**/*.ts'
296+
}
297+
]);
298+
});
299+
300+
it('skip directory watching if directory do not exist', async () => {
301+
const dirPath = getRandomVirtualDirPath(path.join(testDir, 'Test'));
302+
const { virtualSystem, lsDocumentContext } = setup();
303+
304+
const rootUris = [pathToUrl(dirPath)];
305+
306+
const watchDirectory = sinon.spy();
307+
lsDocumentContext.watchDirectory = watchDirectory;
308+
lsDocumentContext.nonRecursiveWatchPattern = '*.ts';
309+
310+
virtualSystem.readDirectory = () => [];
311+
virtualSystem.directoryExists = () => false;
312+
313+
virtualSystem.writeFile(
314+
path.join(dirPath, 'tsconfig.json'),
315+
JSON.stringify({
316+
compilerOptions: {},
317+
include: ['../test/**/*.ts']
318+
})
319+
);
320+
321+
await getService(path.join(dirPath, 'random.svelte'), rootUris, lsDocumentContext);
322+
323+
sinon.assert.calledWith(watchDirectory.firstCall, <RelativePattern[]>[]);
324+
});
266325
});

0 commit comments

Comments
 (0)