/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ 'use strict'; import { ProgressOptions } from 'vscode'; import { MainThreadProgressShape, ExtHostProgressShape } from './extHost.protocol'; import { ProgressLocation } from './extHostTypeConverters'; import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; import { IProgressStep, Progress } from 'vs/platform/progress/common/progress'; import { localize } from 'vs/nls'; import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cancellation'; import { debounce } from 'vs/base/common/decorators'; export class ExtHostProgress implements ExtHostProgressShape { private _proxy: MainThreadProgressShape; private _handles: number = 0; private _mapHandleToCancellationSource: Map = new Map(); constructor(proxy: MainThreadProgressShape) { this._proxy = proxy; } withProgress(extension: IExtensionDescription, options: ProgressOptions, task: (progress: Progress, token: CancellationToken) => Thenable): Thenable { const handle = this._handles++; const { title, location, cancellable } = options; const source = localize('extensionSource', "{0} (Extension)", extension.displayName || extension.name); this._proxy.$startProgress(handle, { location: ProgressLocation.from(location), title, source, cancellable }); return this._withProgress(handle, task, cancellable); } private _withProgress(handle: number, task: (progress: Progress, token: CancellationToken) => Thenable, cancellable: boolean): Thenable { let source: CancellationTokenSource; if (cancellable) { source = new CancellationTokenSource(); this._mapHandleToCancellationSource.set(handle, source); } const progressEnd = (handle: number): void => { this._proxy.$progressEnd(handle); this._mapHandleToCancellationSource.delete(handle); if (source) { source.dispose(); } }; let p: Thenable; try { p = task(new ProgressCallback(this._proxy, handle), cancellable ? source.token : CancellationToken.None); } catch (err) { progressEnd(handle); throw err; } p.then(result => progressEnd(handle), err => progressEnd(handle)); return p; } public $acceptProgressCanceled(handle: number): void { const source = this._mapHandleToCancellationSource.get(handle); if (source) { source.cancel(); this._mapHandleToCancellationSource.delete(handle); } } } function mergeProgress(result: IProgressStep, currentValue: IProgressStep): IProgressStep { result.message = currentValue.message; if (typeof currentValue.percentage === 'number' && typeof result.message === 'number') { result.percentage += currentValue.percentage; } else { result.percentage = currentValue.percentage || result.percentage; } return result; } class ProgressCallback extends Progress { constructor(private _proxy: MainThreadProgressShape, private _handle: number) { super(p => this.throttledReport(p)); } @debounce(100, (result: IProgressStep, currentValue: IProgressStep) => mergeProgress(result, currentValue), () => Object.create(null)) throttledReport(p: IProgressStep): void { this._proxy.$progressReport(this._handle, p); } }