diff --git a/src/lsp/DocumentManager.spec.ts b/src/lsp/DocumentManager.spec.ts index 0028e237e..16798983e 100644 --- a/src/lsp/DocumentManager.spec.ts +++ b/src/lsp/DocumentManager.spec.ts @@ -1,39 +1,42 @@ import { expect } from 'chai'; import util from '../util'; +import type { DocumentAction } from './DocumentManager'; import { DocumentManager } from './DocumentManager'; describe('DocumentManager', () => { let manager: DocumentManager; + let results: DocumentAction[] = []; + beforeEach(() => { + results = []; manager = new DocumentManager({ - delay: 5 + delay: 5, + flushHandler: (event) => { + results.push(...event.actions); + } }); }); it('throttles multiple events', async () => { - const actionsPromise = manager.once('flush'); manager.set({ srcPath: 'alpha', fileContents: 'one' }); await util.sleep(1); manager.set({ srcPath: 'alpha', fileContents: 'two' }); await util.sleep(1); manager.set({ srcPath: 'alpha', fileContents: 'three' }); + + await manager.onIdle(); + expect( - await actionsPromise - ).to.eql({ - actions: [ - { - type: 'set', - srcPath: 'alpha', - fileContents: 'three', - allowStandaloneProject: false - } - ] - }); + results + ).to.eql([{ + type: 'set', + srcPath: 'alpha', + fileContents: 'three', + allowStandaloneProject: false + }]); }); it('any file change delays the first one', async () => { - const actionsPromise = manager.once('flush'); - manager.set({ srcPath: 'alpha', fileContents: 'one' }); await util.sleep(1); @@ -46,53 +49,45 @@ describe('DocumentManager', () => { manager.set({ srcPath: 'beta', fileContents: 'four' }); await util.sleep(1); - expect( - await actionsPromise - ).to.eql({ - actions: [ - { - type: 'set', - srcPath: 'alpha', - fileContents: 'three', - allowStandaloneProject: false - }, { - type: 'set', - srcPath: 'beta', - fileContents: 'four', - allowStandaloneProject: false - } - ] - }); + await manager.onIdle(); + + expect(results).to.eql([{ + type: 'set', + srcPath: 'alpha', + fileContents: 'three', + allowStandaloneProject: false + }, { + type: 'set', + srcPath: 'beta', + fileContents: 'four', + allowStandaloneProject: false + }]); }); it('keeps the last-in change', async () => { manager.set({ srcPath: 'alpha', fileContents: 'one' }); manager.delete('alpha'); - expect( - await manager.once('flush') - ).to.eql({ - actions: [ - { - type: 'delete', - srcPath: 'alpha' - } - ] - }); + + await manager.onIdle(); + + expect(results).to.eql([{ + type: 'delete', + srcPath: 'alpha' + }]); + + results = []; manager.set({ srcPath: 'alpha', fileContents: 'two' }); manager.delete('alpha'); manager.set({ srcPath: 'alpha', fileContents: 'three' }); - expect( - await manager.once('flush') - ).to.eql({ - actions: [ - { - type: 'set', - srcPath: 'alpha', - fileContents: 'three', - allowStandaloneProject: false - } - ] - }); + + await manager.onIdle(); + + expect(results).to.eql([{ + type: 'set', + srcPath: 'alpha', + fileContents: 'three', + allowStandaloneProject: false + }]); }); }); diff --git a/src/lsp/DocumentManager.ts b/src/lsp/DocumentManager.ts index 8d4fdde26..d9d6d3499 100644 --- a/src/lsp/DocumentManager.ts +++ b/src/lsp/DocumentManager.ts @@ -11,7 +11,7 @@ export class DocumentManager { constructor( private options: { delay: number; - flushHandler?: (event: FlushEvent) => Promise; + flushHandler?: (event: FlushEvent) => any; }) { } @@ -102,9 +102,8 @@ export class DocumentManager { * and will wait until files are flushed if there are files. */ public async onIdle() { - if (!this.isIdle) { + while (!this.isIdle) { await this.once('flush'); - return this.onIdle(); } } diff --git a/src/lsp/PathFilterer.ts b/src/lsp/PathFilterer.ts index 7efe5dc5c..ec198d000 100644 --- a/src/lsp/PathFilterer.ts +++ b/src/lsp/PathFilterer.ts @@ -160,12 +160,14 @@ export class PathFilterer { } export class PathCollection { - constructor(options: { - rootDir: string; - globs: string[]; - } | { - matcher: (path: string) => boolean; - }) { + constructor( + public options: { + rootDir: string; + globs: string[]; + } | { + matcher: (path: string) => boolean; + } + ) { if ('globs' in options) { //build matcher patterns from the globs for (const glob of options.globs ?? []) { diff --git a/src/lsp/ProjectManager.spec.ts b/src/lsp/ProjectManager.spec.ts index 9b56950a1..119315e3e 100644 --- a/src/lsp/ProjectManager.spec.ts +++ b/src/lsp/ProjectManager.spec.ts @@ -404,32 +404,35 @@ describe('ProjectManager', () => { workspaceFolder: rootDir }]); + const stub = sinon.stub(manager as any, 'handleFileChange').callThrough(); + //register an exclusion filter pathFilterer.registerExcludeList(rootDir, [ '**/*.md' ]); //make sure the .md file is ignored await manager.handleFileChanges([ - { srcPath: `${rootDir}/source/file1.md`, type: FileChangeType.Created }, - { srcPath: `${rootDir}/source/file2.brs`, type: FileChangeType.Created } + { srcPath: s`${rootDir}/source/file1.md`, type: FileChangeType.Created }, + { srcPath: s`${rootDir}/source/file2.brs`, type: FileChangeType.Created } ]); - let onFlush = manager['documentManager'].once('flush'); + await manager.onIdle(); expect( - (await onFlush)?.actions.map(x => x.srcPath) + stub.getCalls().map(x => x.args[0]).map(x => x.srcPath) ).to.eql([ s`${rootDir}/source/file2.brs` ]); + stub.reset(); //remove all filters, make sure the markdown file is included pathFilterer.clear(); await manager.handleFileChanges([ - { srcPath: `${rootDir}/source/file1.md`, type: FileChangeType.Created }, - { srcPath: `${rootDir}/source/file2.brs`, type: FileChangeType.Created } + { srcPath: s`${rootDir}/source/file1.md`, type: FileChangeType.Created }, + { srcPath: s`${rootDir}/source/file2.brs`, type: FileChangeType.Created } ]); - onFlush = manager['documentManager'].once('flush'); + await manager.onIdle(); expect( - (await onFlush)?.actions.map(x => x.srcPath) + stub.getCalls().flatMap(x => x.args[0]).map(x => x.srcPath) ).to.eql([ s`${rootDir}/source/file1.md`, s`${rootDir}/source/file2.brs` @@ -448,6 +451,8 @@ describe('ProjectManager', () => { workspaceFolder: rootDir }]); + const stub = sinon.stub(manager['projects'][0], 'applyFileChanges').callThrough(); + //register an exclusion filter pathFilterer.registerExcludeList(rootDir, [ '**/*.md' @@ -457,9 +462,9 @@ describe('ProjectManager', () => { { srcPath: `${rootDir}/source/file1.md`, type: FileChangeType.Created }, { srcPath: `${rootDir}/source/file2.brs`, type: FileChangeType.Created } ]); - let onFlush = manager['documentManager'].once('flush'); + await manager.onIdle(); expect( - (await onFlush)?.actions.map(x => x.srcPath) + stub.getCalls().flatMap(x => x.args[0]).map(x => x.srcPath) ).to.eql([ s`${rootDir}/source/file1.md`, s`${rootDir}/source/file2.brs` diff --git a/src/lsp/ProjectManager.ts b/src/lsp/ProjectManager.ts index e81b9f6ef..e63bdc346 100644 --- a/src/lsp/ProjectManager.ts +++ b/src/lsp/ProjectManager.ts @@ -289,8 +289,6 @@ export class ProjectManager { * This is safe to call any time. Changes will be queued and flushed at the correct times */ public async _handleFileChanges(changes: FileChange[]) { - - //filter any changes that are not allowed by the path filterer changes = this.pathFilterer.filter(changes, x => x.srcPath);