diff --git a/packages/core/src/common/progress-service.ts b/packages/core/src/common/progress-service.ts index ad6383f440e5c..a7ecfeb67a8a0 100644 --- a/packages/core/src/common/progress-service.ts +++ b/packages/core/src/common/progress-service.ts @@ -74,10 +74,7 @@ export class ProgressService { async withProgress(text: string, locationId: string, task: () => Promise): Promise { const progress = await this.showProgress({ text, options: { cancelable: true, location: locationId } }); try { - const result = task(); - return result; - } catch (error) { - throw error; + return await task(); } finally { progress.cancel(); } diff --git a/packages/navigator/src/browser/navigator-frontend-module.ts b/packages/navigator/src/browser/navigator-frontend-module.ts index bee418be321f2..b47290469edcd 100644 --- a/packages/navigator/src/browser/navigator-frontend-module.ts +++ b/packages/navigator/src/browser/navigator-frontend-module.ts @@ -56,7 +56,10 @@ export default new ContainerModule(bind => { bind(WidgetFactory).toDynamicValue(({ container }) => ({ id: EXPLORER_VIEW_CONTAINER_ID, createWidget: async () => { - const viewContainer = container.get(ViewContainer.Factory)({ id: EXPLORER_VIEW_CONTAINER_ID }); + const viewContainer = container.get(ViewContainer.Factory)({ + id: EXPLORER_VIEW_CONTAINER_ID, + progressLocationId: 'explorer' + }); viewContainer.setTitleOptions(EXPLORER_VIEW_CONTAINER_TITLE_OPTIONS); const widget = await container.get(WidgetManager).getOrCreateWidget(FILE_NAVIGATOR_ID); viewContainer.addWidget(widget, { diff --git a/packages/navigator/src/browser/navigator-model.spec.ts b/packages/navigator/src/browser/navigator-model.spec.ts index 7159132b5a2e6..25c2729675ef5 100644 --- a/packages/navigator/src/browser/navigator-model.spec.ts +++ b/packages/navigator/src/browser/navigator-model.spec.ts @@ -18,7 +18,7 @@ import { enableJSDOM } from '@theia/core/lib/browser/test/jsdom'; let disableJSDOM = enableJSDOM(); import { Container } from 'inversify'; -import { Emitter, ILogger, Logger } from '@theia/core'; +import { Event, Emitter, ILogger, Logger } from '@theia/core'; import { CompositeTreeNode, DefaultOpenerService, ExpandableTreeNode, LabelProvider, OpenerService, Tree, TreeNode, TreeSelectionService, TreeExpansionService, TreeExpansionServiceImpl, @@ -37,6 +37,7 @@ import { expect } from 'chai'; import URI from '@theia/core/lib/common/uri'; import * as sinon from 'sinon'; import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state'; +import { ProgressService } from '@theia/core/lib/common/progress-service'; disableJSDOM(); @@ -113,6 +114,7 @@ const setup = () => { }); }; +// TODO rewrite as integration tests instead of testing mocks describe('FileNavigatorModel', () => { let testContainer: Container; @@ -177,12 +179,16 @@ describe('FileNavigatorModel', () => { testContainer.bind(TreeSearch).toConstantValue(mockTreeSearch); testContainer.bind(CorePreferences).toConstantValue(mockPreferences); testContainer.bind(FrontendApplicationStateService).toConstantValue(mockApplicationStateService); + testContainer.bind(ProgressService).toConstantValue({ + withProgress: (_, __, task) => task() + }); sinon.stub(mockWorkspaceService, 'onWorkspaceChanged').value(mockWorkspaceServiceEmitter.event); sinon.stub(mockWorkspaceService, 'onWorkspaceLocationChanged').value(mockWorkspaceOnLocationChangeEmitter.event); sinon.stub(mockFileSystemWatcher, 'onFilesChanged').value(mockFileChangeEmitter.event); sinon.stub(mockFileSystemWatcher, 'onDidMove').value(mockFileMoveEmitter.event); sinon.stub(mockFileNavigatorTree, 'onChanged').value(mockTreeChangeEmitter.event); + sinon.stub(mockFileNavigatorTree, 'onDidChangeBusy').value(Event.None); sinon.stub(mockTreeExpansionService, 'onExpansionChanged').value(mockExpansionChangeEmitter.event); setup(); diff --git a/packages/navigator/src/browser/navigator-model.ts b/packages/navigator/src/browser/navigator-model.ts index d1e0f5b0ecb15..bbe6aa8975a4c 100644 --- a/packages/navigator/src/browser/navigator-model.ts +++ b/packages/navigator/src/browser/navigator-model.ts @@ -21,6 +21,9 @@ import { OpenerService, open, TreeNode, ExpandableTreeNode, CompositeTreeNode, S import { FileNavigatorTree, WorkspaceRootNode, WorkspaceNode } from './navigator-tree'; import { WorkspaceService } from '@theia/workspace/lib/browser'; import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state'; +import { ProgressService } from '@theia/core/lib/common/progress-service'; +import { Deferred } from '@theia/core/lib/common/promise-util'; +import { Disposable } from '@theia/core/lib/common/disposable'; @injectable() export class FileNavigatorModel extends FileTreeModel { @@ -30,12 +33,41 @@ export class FileNavigatorModel extends FileTreeModel { @inject(WorkspaceService) protected readonly workspaceService: WorkspaceService; @inject(FrontendApplicationStateService) protected readonly applicationState: FrontendApplicationStateService; + @inject(ProgressService) + protected readonly progressService: ProgressService; + @postConstruct() protected init(): void { super.init(); + this.reportBusyProgress(); this.initializeRoot(); } + protected readonly pendingBusyProgress = new Map>(); + protected reportBusyProgress(): void { + this.toDispose.push(this.onDidChangeBusy(node => { + const pending = this.pendingBusyProgress.get(node.id); + if (pending) { + if (!node.busy) { + pending.resolve(); + this.pendingBusyProgress.delete(node.id); + } + return; + } + if (node.busy) { + const progress = new Deferred(); + this.pendingBusyProgress.set(node.id, progress); + this.progressService.withProgress('', 'explorer', () => progress.promise); + } + })); + this.toDispose.push(Disposable.create(() => { + for (const pending of this.pendingBusyProgress.values()) { + pending.resolve(); + } + this.pendingBusyProgress.clear(); + })); + } + protected async initializeRoot(): Promise { await Promise.all([ this.applicationState.reachedState('initialized_layout'),