Overhauls timeline display to stream in results
Implements many API review changes Fixes #89558
This commit is contained in:
parent
1db20bbb60
commit
4cc5b776dc
|
@ -6,7 +6,7 @@
|
|||
import * as dayjs from 'dayjs';
|
||||
import * as advancedFormat from 'dayjs/plugin/advancedFormat';
|
||||
import * as relativeTime from 'dayjs/plugin/relativeTime';
|
||||
import { CancellationToken, Disposable, Event, EventEmitter, ThemeIcon, TimelineItem, TimelineProvider, Uri, workspace } from 'vscode';
|
||||
import { CancellationToken, Disposable, Event, EventEmitter, ThemeIcon, TimelineItem, TimelineProvider, Uri, workspace, TimelineChangeEvent } from 'vscode';
|
||||
import { Model } from './model';
|
||||
import { Repository } from './repository';
|
||||
import { debounce } from './decorators';
|
||||
|
@ -19,13 +19,13 @@ dayjs.extend(relativeTime);
|
|||
// TODO[ECA]: Localize or use a setting for date format
|
||||
|
||||
export class GitTimelineProvider implements TimelineProvider {
|
||||
private _onDidChange = new EventEmitter<Uri | undefined>();
|
||||
get onDidChange(): Event<Uri | undefined> {
|
||||
private _onDidChange = new EventEmitter<TimelineChangeEvent>();
|
||||
get onDidChange(): Event<TimelineChangeEvent> {
|
||||
return this._onDidChange.event;
|
||||
}
|
||||
|
||||
readonly source = 'git-history';
|
||||
readonly sourceDescription = 'Git History';
|
||||
readonly id = 'git-history';
|
||||
readonly label = 'Git History';
|
||||
|
||||
private _disposable: Disposable;
|
||||
|
||||
|
@ -82,19 +82,18 @@ export class GitTimelineProvider implements TimelineProvider {
|
|||
|
||||
dateFormatter = dayjs(c.authorDate);
|
||||
|
||||
return {
|
||||
id: c.hash,
|
||||
timestamp: c.authorDate?.getTime() ?? 0,
|
||||
iconPath: new (ThemeIcon as any)('git-commit'),
|
||||
label: message,
|
||||
description: `${dateFormatter.fromNow()} \u2022 ${c.authorName}`,
|
||||
detail: `${c.authorName} (${c.authorEmail}) \u2014 ${c.hash.substr(0, 8)}\n${dateFormatter.fromNow()} (${dateFormatter.format('MMMM Do, YYYY h:mma')})\n\n${c.message}`,
|
||||
command: {
|
||||
title: 'Open Diff',
|
||||
command: 'git.openDiff',
|
||||
arguments: [uri, c.hash]
|
||||
}
|
||||
const item = new TimelineItem(message, c.authorDate?.getTime() ?? 0);
|
||||
item.id = c.hash;
|
||||
item.iconPath = new (ThemeIcon as any)('git-commit');
|
||||
item.description = `${dateFormatter.fromNow()} \u2022 ${c.authorName}`;
|
||||
item.detail = `${c.authorName} (${c.authorEmail}) \u2014 ${c.hash.substr(0, 8)}\n${dateFormatter.fromNow()} (${dateFormatter.format('MMMM Do, YYYY h:mma')})\n\n${c.message}`;
|
||||
item.command = {
|
||||
title: 'Open Diff',
|
||||
command: 'git.openDiff',
|
||||
arguments: [uri, c.hash]
|
||||
};
|
||||
|
||||
return item;
|
||||
});
|
||||
|
||||
const index = repo.indexGroup.resourceStates.find(r => r.resourceUri.fsPath === uri.fsPath);
|
||||
|
@ -124,21 +123,19 @@ export class GitTimelineProvider implements TimelineProvider {
|
|||
break;
|
||||
}
|
||||
|
||||
const item = new TimelineItem('Staged Changes', date.getTime());
|
||||
item.id = '~';
|
||||
// TODO[ECA]: Replace with a better icon -- reflecting its status maybe?
|
||||
item.iconPath = new (ThemeIcon as any)('git-commit');
|
||||
item.description = `${dateFormatter.fromNow()} \u2022 You`;
|
||||
item.detail = `You \u2014 Index\n${dateFormatter.fromNow()} (${dateFormatter.format('MMMM Do, YYYY h:mma')})\n${status}`;
|
||||
item.command = {
|
||||
title: 'Open Comparison',
|
||||
command: 'git.openDiff',
|
||||
arguments: [uri, '~']
|
||||
};
|
||||
|
||||
items.push({
|
||||
id: '~',
|
||||
timestamp: date.getTime(),
|
||||
// TODO[ECA]: Replace with a better icon -- reflecting its status maybe?
|
||||
iconPath: new (ThemeIcon as any)('git-commit'),
|
||||
label: 'Staged Changes',
|
||||
description: `${dateFormatter.fromNow()} \u2022 You`,
|
||||
detail: `You \u2014 Index\n${dateFormatter.fromNow()} (${dateFormatter.format('MMMM Do, YYYY h:mma')})\n${status}`,
|
||||
command: {
|
||||
title: 'Open Comparison',
|
||||
command: 'git.openDiff',
|
||||
arguments: [uri, '~']
|
||||
}
|
||||
});
|
||||
items.push(item);
|
||||
}
|
||||
|
||||
return items;
|
||||
|
|
57
src/vs/vscode.proposed.d.ts
vendored
57
src/vs/vscode.proposed.d.ts
vendored
|
@ -1505,27 +1505,32 @@ declare module 'vscode' {
|
|||
|
||||
export class TimelineItem {
|
||||
/**
|
||||
* A timestamp (in milliseconds since 1 January 1970 00:00:00) for when the timeline item occurred
|
||||
* A timestamp (in milliseconds since 1 January 1970 00:00:00) for when the timeline item occurred.
|
||||
*/
|
||||
timestamp: number;
|
||||
|
||||
/**
|
||||
* A human-readable string describing the timeline item. When `falsy`, it is derived from [resourceUri](#TreeItem.resourceUri).
|
||||
* A human-readable string describing the timeline item.
|
||||
*/
|
||||
label: string;
|
||||
|
||||
/**
|
||||
* Optional id for the timeline item. See [TreeItem.id](#TreeItem.id) for more details.
|
||||
* Optional id for the timeline item.
|
||||
*/
|
||||
/**
|
||||
* Optional id for the timeline item that has to be unique across your timeline source.
|
||||
*
|
||||
* If not provided, an id is generated using the timeline item's label.
|
||||
*/
|
||||
id?: string;
|
||||
|
||||
/**
|
||||
* The icon path or [ThemeIcon](#ThemeIcon) for the timeline item. See [TreeItem.iconPath](#TreeItem.iconPath) for more details.
|
||||
* The icon path or [ThemeIcon](#ThemeIcon) for the timeline item.
|
||||
*/
|
||||
iconPath?: Uri | { light: Uri; dark: Uri } | ThemeIcon;
|
||||
|
||||
/**
|
||||
* A human readable string describing less prominent details of the timeline item. See [TreeItem.description](#TreeItem.description) for more details.
|
||||
* A human readable string describing less prominent details of the timeline item.
|
||||
*/
|
||||
description?: string;
|
||||
|
||||
|
@ -1540,7 +1545,22 @@ declare module 'vscode' {
|
|||
command?: Command;
|
||||
|
||||
/**
|
||||
* Context value of the timeline item. See [TreeItem.contextValue](#TreeItem.contextValue) for more details.
|
||||
* Context value of the timeline item. This can be used to contribute specific actions to the item.
|
||||
* For example, a timeline item is given a context value as `commit`. When contributing actions to `timeline/item/context`
|
||||
* using `menus` extension point, you can specify context value for key `timelineItem` in `when` expression like `timelineItem == commit`.
|
||||
* ```
|
||||
* "contributes": {
|
||||
* "menus": {
|
||||
* "timeline/item/context": [
|
||||
* {
|
||||
* "command": "extension.copyCommitId",
|
||||
* "when": "timelineItem == commit"
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
* This will show the `extension.copyCommitId` action only for items where `contextValue` is `commit`.
|
||||
*/
|
||||
contextValue?: string;
|
||||
|
||||
|
@ -1551,32 +1571,35 @@ declare module 'vscode' {
|
|||
constructor(label: string, timestamp: number);
|
||||
}
|
||||
|
||||
export interface TimelineChangeEvent {
|
||||
/**
|
||||
* The [uri](#Uri) of the resource for which the timeline changed.
|
||||
* If the [uri](#Uri) is `undefined` that signals that the timeline source for all resources changed.
|
||||
*/
|
||||
uri?: Uri;
|
||||
}
|
||||
|
||||
export interface TimelineProvider {
|
||||
/**
|
||||
* An optional event to signal that the timeline for a source has changed.
|
||||
* To signal that the timeline for all resources (uris) has changed, do not pass any argument or pass `undefined`.
|
||||
*/
|
||||
onDidChange?: Event<Uri | undefined>;
|
||||
onDidChange?: Event<TimelineChangeEvent>;
|
||||
|
||||
/**
|
||||
* An identifier of the source of the timeline items. This can be used for filtering and/or overriding existing sources.
|
||||
* An identifier of the source of the timeline items. This can be used to filter sources.
|
||||
*/
|
||||
source: string;
|
||||
id: string;
|
||||
|
||||
/**
|
||||
* A human-readable string describing the source of the timeline items. This can be as the display label when filtering by sources.
|
||||
* A human-readable string describing the source of the timeline items. This can be used as the display label when filtering sources.
|
||||
*/
|
||||
sourceDescription: string;
|
||||
|
||||
/**
|
||||
* A flag that signals whether this provider can be swapped out (replaced) for another provider using the same [TimelineProvider.source](#TimelineProvider.source).
|
||||
*/
|
||||
replaceable?: boolean;
|
||||
label: string;
|
||||
|
||||
/**
|
||||
* Provide [timeline items](#TimelineItem) for a [Uri](#Uri).
|
||||
*
|
||||
* @param uri The uri of the file to provide the timeline for.
|
||||
* @param uri The [uri](#Uri) of the file to provide the timeline for.
|
||||
* @param token A cancellation token.
|
||||
* @return An array of timeline items or a thenable that resolves to such. The lack of a result
|
||||
* can be signaled by returning `undefined`, `null`, or an empty array.
|
||||
|
|
|
@ -9,12 +9,12 @@ import { URI } from 'vs/base/common/uri';
|
|||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { MainContext, MainThreadTimelineShape, IExtHostContext, ExtHostTimelineShape, ExtHostContext } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
|
||||
import { ITimelineService, TimelineItem, TimelineProviderDescriptor } from 'vs/workbench/contrib/timeline/common/timeline';
|
||||
import { ITimelineService, TimelineItem, TimelineProviderDescriptor, TimelineChangeEvent } from 'vs/workbench/contrib/timeline/common/timeline';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadTimeline)
|
||||
export class MainThreadTimeline implements MainThreadTimelineShape {
|
||||
private readonly _proxy: ExtHostTimelineShape;
|
||||
private readonly _providerEmitters = new Map<string, Emitter<URI | undefined>>();
|
||||
private readonly _providerEmitters = new Map<string, Emitter<TimelineChangeEvent>>();
|
||||
|
||||
constructor(
|
||||
context: IExtHostContext,
|
||||
|
@ -29,41 +29,41 @@ export class MainThreadTimeline implements MainThreadTimelineShape {
|
|||
}
|
||||
|
||||
$registerTimelineProvider(provider: TimelineProviderDescriptor): void {
|
||||
this.logService.trace(`MainThreadTimeline#registerTimelineProvider: source=${provider.source}`);
|
||||
this.logService.trace(`MainThreadTimeline#registerTimelineProvider: id=${provider.id}`);
|
||||
|
||||
const proxy = this._proxy;
|
||||
|
||||
const emitters = this._providerEmitters;
|
||||
let onDidChange = emitters.get(provider.source);
|
||||
let onDidChange = emitters.get(provider.id);
|
||||
if (onDidChange === undefined) {
|
||||
onDidChange = new Emitter<URI | undefined>();
|
||||
emitters.set(provider.source, onDidChange);
|
||||
onDidChange = new Emitter<TimelineChangeEvent>();
|
||||
emitters.set(provider.id, onDidChange);
|
||||
}
|
||||
|
||||
this._timelineService.registerTimelineProvider({
|
||||
...provider,
|
||||
onDidChange: onDidChange.event,
|
||||
provideTimeline(uri: URI, token: CancellationToken) {
|
||||
return proxy.$getTimeline(provider.source, uri, token);
|
||||
return proxy.$getTimeline(provider.id, uri, token);
|
||||
},
|
||||
dispose() {
|
||||
emitters.delete(provider.source);
|
||||
emitters.delete(provider.id);
|
||||
onDidChange?.dispose();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$unregisterTimelineProvider(source: string): void {
|
||||
this.logService.trace(`MainThreadTimeline#unregisterTimelineProvider: source=${source}`);
|
||||
$unregisterTimelineProvider(id: string): void {
|
||||
this.logService.trace(`MainThreadTimeline#unregisterTimelineProvider: id=${id}`);
|
||||
|
||||
this._timelineService.unregisterTimelineProvider(source);
|
||||
this._timelineService.unregisterTimelineProvider(id);
|
||||
}
|
||||
|
||||
$emitTimelineChangeEvent(source: string, uri: URI | undefined): void {
|
||||
this.logService.trace(`MainThreadTimeline#emitChangeEvent: source=${source}, uri=${uri?.toString(true)}`);
|
||||
$emitTimelineChangeEvent(e: TimelineChangeEvent): void {
|
||||
this.logService.trace(`MainThreadTimeline#emitChangeEvent: id=${e.id}, uri=${e.uri?.toString(true)}`);
|
||||
|
||||
const emitter = this._providerEmitters.get(source);
|
||||
emitter?.fire(uri);
|
||||
const emitter = this._providerEmitters.get(e.id!);
|
||||
emitter?.fire(e);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
|
|
|
@ -764,7 +764,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
|||
},
|
||||
registerTimelineProvider: (scheme: string, provider: vscode.TimelineProvider) => {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostTimeline.registerTimelineProvider(provider, extHostCommands.converter);
|
||||
return extHostTimeline.registerTimelineProvider(provider, extension.identifier, extHostCommands.converter);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -49,7 +49,7 @@ import { SaveReason } from 'vs/workbench/common/editor';
|
|||
import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtensionActivator';
|
||||
import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService';
|
||||
import { TunnelOptions } from 'vs/platform/remote/common/tunnel';
|
||||
import { TimelineItem, TimelineProviderDescriptor } from 'vs/workbench/contrib/timeline/common/timeline';
|
||||
import { TimelineItem, TimelineProviderDescriptor, TimelineChangeEvent, TimelineItemWithSource } from 'vs/workbench/contrib/timeline/common/timeline';
|
||||
|
||||
export interface IEnvironment {
|
||||
isExtensionDevelopmentDebug: boolean;
|
||||
|
@ -801,7 +801,7 @@ export interface MainThreadTunnelServiceShape extends IDisposable {
|
|||
export interface MainThreadTimelineShape extends IDisposable {
|
||||
$registerTimelineProvider(provider: TimelineProviderDescriptor): void;
|
||||
$unregisterTimelineProvider(source: string): void;
|
||||
$emitTimelineChangeEvent(source: string, uri: UriComponents | undefined): void;
|
||||
$emitTimelineChangeEvent(e: TimelineChangeEvent): void;
|
||||
|
||||
$getTimeline(uri: UriComponents, token: CancellationToken): Promise<TimelineItem[]>;
|
||||
}
|
||||
|
@ -1451,7 +1451,7 @@ export interface ExtHostTunnelServiceShape {
|
|||
}
|
||||
|
||||
export interface ExtHostTimelineShape {
|
||||
$getTimeline(source: string, uri: UriComponents, token: CancellationToken): Promise<TimelineItem[]>;
|
||||
$getTimeline(source: string, uri: UriComponents, token: CancellationToken): Promise<TimelineItemWithSource[]>;
|
||||
}
|
||||
|
||||
// --- proxy identifiers
|
||||
|
|
|
@ -7,15 +7,16 @@ import * as vscode from 'vscode';
|
|||
import { UriComponents, URI } from 'vs/base/common/uri';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ExtHostTimelineShape, MainThreadTimelineShape, IMainContext, MainContext } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { TimelineItem, TimelineItemWithSource, TimelineProvider } from 'vs/workbench/contrib/timeline/common/timeline';
|
||||
import { TimelineItemWithSource, TimelineProvider } from 'vs/workbench/contrib/timeline/common/timeline';
|
||||
import { IDisposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { CommandsConverter } from 'vs/workbench/api/common/extHostCommands';
|
||||
import { ThemeIcon } from 'vs/workbench/api/common/extHostTypes';
|
||||
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
|
||||
|
||||
export interface IExtHostTimeline extends ExtHostTimelineShape {
|
||||
readonly _serviceBrand: undefined;
|
||||
$getTimeline(source: string, uri: UriComponents, token: vscode.CancellationToken): Promise<TimelineItem[]>;
|
||||
$getTimeline(id: string, uri: UriComponents, token: vscode.CancellationToken): Promise<TimelineItemWithSource[]>;
|
||||
}
|
||||
|
||||
export const IExtHostTimeline = createDecorator<IExtHostTimeline>('IExtHostTimeline');
|
||||
|
@ -33,23 +34,24 @@ export class ExtHostTimeline implements IExtHostTimeline {
|
|||
this._proxy = mainContext.getProxy(MainContext.MainThreadTimeline);
|
||||
}
|
||||
|
||||
async $getTimeline(source: string, uri: UriComponents, token: vscode.CancellationToken): Promise<TimelineItem[]> {
|
||||
const provider = this._providers.get(source);
|
||||
async $getTimeline(id: string, uri: UriComponents, token: vscode.CancellationToken): Promise<TimelineItemWithSource[]> {
|
||||
const provider = this._providers.get(id);
|
||||
return provider?.provideTimeline(URI.revive(uri), token) ?? [];
|
||||
}
|
||||
|
||||
registerTimelineProvider(provider: vscode.TimelineProvider, commandConverter: CommandsConverter): IDisposable {
|
||||
registerTimelineProvider(provider: vscode.TimelineProvider, extensionId: ExtensionIdentifier, commandConverter: CommandsConverter): IDisposable {
|
||||
const timelineDisposables = new DisposableStore();
|
||||
|
||||
const convertTimelineItem = this.convertTimelineItem(provider.source, commandConverter, timelineDisposables);
|
||||
const convertTimelineItem = this.convertTimelineItem(provider.id, commandConverter, timelineDisposables);
|
||||
|
||||
let disposable: IDisposable | undefined;
|
||||
if (provider.onDidChange) {
|
||||
disposable = provider.onDidChange(this.emitTimelineChangeEvent(provider.source), this);
|
||||
disposable = provider.onDidChange(this.emitTimelineChangeEvent(provider.id), this);
|
||||
}
|
||||
|
||||
return this.registerTimelineProviderCore({
|
||||
...provider,
|
||||
onDidChange: undefined,
|
||||
async provideTimeline(uri: URI, token: CancellationToken) {
|
||||
timelineDisposables.clear();
|
||||
|
||||
|
@ -98,30 +100,29 @@ export class ExtHostTimeline implements IExtHostTimeline {
|
|||
};
|
||||
}
|
||||
|
||||
private emitTimelineChangeEvent(source: string) {
|
||||
return (uri: vscode.Uri | undefined) => {
|
||||
this._proxy.$emitTimelineChangeEvent(source, uri);
|
||||
private emitTimelineChangeEvent(id: string) {
|
||||
return (e: vscode.TimelineChangeEvent) => {
|
||||
this._proxy.$emitTimelineChangeEvent({ ...e, id: id });
|
||||
};
|
||||
}
|
||||
|
||||
private registerTimelineProviderCore(provider: TimelineProvider): IDisposable {
|
||||
// console.log(`ExtHostTimeline#registerTimelineProvider: source=${provider.source}`);
|
||||
// console.log(`ExtHostTimeline#registerTimelineProvider: id=${provider.id}`);
|
||||
|
||||
const existing = this._providers.get(provider.source);
|
||||
if (existing && !existing.replaceable) {
|
||||
throw new Error(`Timeline Provider ${provider.source} already exists.`);
|
||||
const existing = this._providers.get(provider.id);
|
||||
if (existing) {
|
||||
throw new Error(`Timeline Provider ${provider.id} already exists.`);
|
||||
}
|
||||
|
||||
this._proxy.$registerTimelineProvider({
|
||||
source: provider.source,
|
||||
sourceDescription: provider.sourceDescription,
|
||||
replaceable: provider.replaceable
|
||||
id: provider.id,
|
||||
label: provider.label
|
||||
});
|
||||
this._providers.set(provider.source, provider);
|
||||
this._providers.set(provider.id, provider);
|
||||
|
||||
return toDisposable(() => {
|
||||
this._providers.delete(provider.source);
|
||||
this._proxy.$unregisterTimelineProvider(provider.source);
|
||||
this._providers.delete(provider.id);
|
||||
this._proxy.$unregisterTimelineProvider(provider.id);
|
||||
provider.dispose();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView
|
|||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { TimelineItem, ITimelineService } from 'vs/workbench/contrib/timeline/common/timeline';
|
||||
import { TimelineItem, ITimelineService, TimelineChangeEvent, TimelineProvidersChangeEvent, TimelineRequest, TimelineItemWithSource } from 'vs/workbench/contrib/timeline/common/timeline';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { SideBySideEditor, toResource } from 'vs/workbench/common/editor';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
|
@ -29,6 +29,7 @@ import { IViewDescriptorService } from 'vs/workbench/common/views';
|
|||
import { basename } from 'vs/base/common/path';
|
||||
import { IProgressService } from 'vs/platform/progress/common/progress';
|
||||
import { VIEWLET_ID } from 'vs/workbench/contrib/files/common/files';
|
||||
import { debounce } from 'vs/base/common/decorators';
|
||||
|
||||
type TreeElement = TimelineItem;
|
||||
|
||||
|
@ -42,9 +43,14 @@ export class TimelinePane extends ViewPane {
|
|||
private _messageElement!: HTMLDivElement;
|
||||
private _treeElement!: HTMLDivElement;
|
||||
private _tree!: WorkbenchObjectTree<TreeElement, FuzzyScore>;
|
||||
private _tokenSource: CancellationTokenSource | undefined;
|
||||
private _visibilityDisposables: DisposableStore | undefined;
|
||||
|
||||
// private _excludedSources: Set<string> | undefined;
|
||||
private _items: TimelineItemWithSource[] = [];
|
||||
private _loadingMessageTimer: NodeJS.Timeout | undefined;
|
||||
private _pendingRequests = new Map<string, TimelineRequest>();
|
||||
private _uri: URI | undefined;
|
||||
|
||||
constructor(
|
||||
options: IViewPaneOptions,
|
||||
@IKeybindingService protected keybindingService: IKeybindingService,
|
||||
|
@ -72,16 +78,31 @@ export class TimelinePane extends ViewPane {
|
|||
uri = toResource(editor, { supportSideBySide: SideBySideEditor.MASTER });
|
||||
}
|
||||
|
||||
this.updateUri(uri);
|
||||
if ((uri?.toString(true) === this._uri?.toString(true) && uri !== undefined) ||
|
||||
// Fallback to match on fsPath if we are dealing with files or git schemes
|
||||
(uri?.fsPath === this._uri?.fsPath && (uri?.scheme === 'file' || uri?.scheme === 'git') && (this._uri?.scheme === 'file' || this._uri?.scheme === 'git'))) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._uri = uri;
|
||||
this.loadTimeline();
|
||||
}
|
||||
|
||||
private onProvidersChanged() {
|
||||
this.refresh();
|
||||
private onProvidersChanged(e: TimelineProvidersChangeEvent) {
|
||||
if (e.removed) {
|
||||
for (const source of e.removed) {
|
||||
this.replaceItems(source);
|
||||
}
|
||||
}
|
||||
|
||||
if (e.added) {
|
||||
this.loadTimeline(e.added);
|
||||
}
|
||||
}
|
||||
|
||||
private onTimelineChanged(uri: URI | undefined) {
|
||||
if (uri === undefined || uri.toString(true) !== this._uri?.toString(true)) {
|
||||
this.refresh();
|
||||
private onTimelineChanged(e: TimelineChangeEvent) {
|
||||
if (e.uri === undefined || e.uri.toString(true) !== this._uri?.toString(true)) {
|
||||
this.loadTimeline([e.id]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -119,53 +140,114 @@ export class TimelinePane extends ViewPane {
|
|||
DOM.clearNode(this._messageElement);
|
||||
}
|
||||
|
||||
private async refresh() {
|
||||
this._tokenSource?.cancel();
|
||||
this._tokenSource = new CancellationTokenSource();
|
||||
private async loadTimeline(sources?: string[]) {
|
||||
// If we have no source, we are reseting all sources, so cancel everything in flight and reset caches
|
||||
if (sources === undefined) {
|
||||
this._items.length = 0;
|
||||
|
||||
let children;
|
||||
|
||||
const uri = this._uri;
|
||||
// TODO[ECA]: Fix the list of schemes here
|
||||
if (uri && (uri.scheme === 'file' || uri.scheme === 'git' || uri.scheme === 'gitlens')) {
|
||||
const messageTimer = setTimeout(() => {
|
||||
this._tree.setChildren(null, undefined);
|
||||
this.message = `Loading timeline for ${basename(uri.fsPath)}...`;
|
||||
}, 500);
|
||||
|
||||
const token = this._tokenSource.token;
|
||||
const items = await this.progressService.withProgress({ location: VIEWLET_ID }, () => this.timelineService.getTimeline(uri, token));
|
||||
|
||||
clearTimeout(messageTimer);
|
||||
|
||||
children = items.map(item => ({ element: item }));
|
||||
|
||||
if (children.length === 0) {
|
||||
this.message = 'No timeline information was provided.';
|
||||
} else {
|
||||
this.message = undefined;
|
||||
if (this._loadingMessageTimer) {
|
||||
clearTimeout(this._loadingMessageTimer);
|
||||
this._loadingMessageTimer = undefined;
|
||||
}
|
||||
|
||||
for (const { tokenSource } of this._pendingRequests.values()) {
|
||||
tokenSource.dispose(true);
|
||||
}
|
||||
|
||||
this._pendingRequests.clear();
|
||||
|
||||
// TODO[ECA]: Are these the right the list of schemes to exclude? Is there a better way?
|
||||
if (this._uri && (this._uri.scheme === 'vscode-settings' || this._uri.scheme === 'webview-panel' || this._uri.scheme === 'walkThrough')) {
|
||||
this.message = 'The active editor cannot provide timeline information.';
|
||||
this._tree.setChildren(null, undefined);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._uri !== undefined) {
|
||||
this._loadingMessageTimer = setTimeout((uri: URI) => {
|
||||
if (uri !== this._uri) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._tree.setChildren(null, undefined);
|
||||
this.message = `Loading timeline for ${basename(uri.fsPath)}...`;
|
||||
}, 500, this._uri);
|
||||
}
|
||||
} else {
|
||||
this.message = 'The active editor cannot provide timeline information.';
|
||||
}
|
||||
|
||||
this._tree.setChildren(null, children);
|
||||
if (this._uri === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const source of sources ?? this.timelineService.getSources()) {
|
||||
let request = this._pendingRequests.get(source);
|
||||
request?.tokenSource.dispose(true);
|
||||
|
||||
request = this.timelineService.getTimelineRequest(source, this._uri, new CancellationTokenSource())!;
|
||||
|
||||
this._pendingRequests.set(source, request);
|
||||
request.tokenSource.token.onCancellationRequested(() => this._pendingRequests.delete(source));
|
||||
|
||||
this.handleRequest(request);
|
||||
}
|
||||
}
|
||||
|
||||
private _uri: URI | undefined;
|
||||
private async handleRequest(request: TimelineRequest) {
|
||||
let items;
|
||||
try {
|
||||
items = await this.progressService.withProgress({ location: VIEWLET_ID }, () => request.items);
|
||||
}
|
||||
catch { }
|
||||
|
||||
private updateUri(uri: URI | undefined) {
|
||||
if (uri?.toString(true) === this._uri?.toString(true) && uri !== undefined) {
|
||||
this._pendingRequests.delete(request.source);
|
||||
if (request.tokenSource.token.isCancellationRequested || request.uri !== this._uri) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback to match on fsPath if we are dealing with files or git schemes
|
||||
if (uri?.fsPath === this._uri?.fsPath && (uri?.scheme === 'file' || uri?.scheme === 'git') && (this._uri?.scheme === 'file' || this._uri?.scheme === 'git')) {
|
||||
this.replaceItems(request.source, items);
|
||||
}
|
||||
|
||||
private replaceItems(source: string, items?: TimelineItemWithSource[]) {
|
||||
const hasItems = this._items.length !== 0;
|
||||
|
||||
if (items?.length) {
|
||||
this._items.splice(0, this._items.length, ...this._items.filter(i => i.source !== source), ...items);
|
||||
this._items.sort((a, b) => (b.timestamp - a.timestamp) || b.source.localeCompare(a.source, undefined, { numeric: true, sensitivity: 'base' }));
|
||||
}
|
||||
else if (this._items.length && this._items.some(i => i.source === source)) {
|
||||
this._items = this._items.filter(i => i.source !== source);
|
||||
}
|
||||
else {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we have items already and there are other pending requests, debounce for a bit to wait for other requests
|
||||
if (hasItems && this._pendingRequests.size !== 0) {
|
||||
this.refreshDebounced();
|
||||
}
|
||||
else {
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
this._uri = uri;
|
||||
private refresh() {
|
||||
if (this._loadingMessageTimer) {
|
||||
clearTimeout(this._loadingMessageTimer);
|
||||
this._loadingMessageTimer = undefined;
|
||||
}
|
||||
|
||||
if (this._items.length === 0) {
|
||||
this.message = 'No timeline information was provided.';
|
||||
} else {
|
||||
this.message = undefined;
|
||||
}
|
||||
|
||||
this._tree.setChildren(null, this._items.map(item => ({ element: item })));
|
||||
}
|
||||
|
||||
@debounce(500)
|
||||
private refreshDebounced() {
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
@ -32,30 +32,50 @@ export interface TimelineItemWithSource extends TimelineItem {
|
|||
source: string;
|
||||
}
|
||||
|
||||
export interface TimelineProvider extends TimelineProviderDescriptor, IDisposable {
|
||||
onDidChange?: Event<URI | undefined>;
|
||||
export interface TimelineChangeEvent {
|
||||
id: string;
|
||||
uri?: URI;
|
||||
}
|
||||
|
||||
provideTimeline(uri: URI, token: CancellationToken): Promise<TimelineItem[]>;
|
||||
export interface TimelineProvider extends TimelineProviderDescriptor, IDisposable {
|
||||
onDidChange?: Event<TimelineChangeEvent>;
|
||||
|
||||
provideTimeline(uri: URI, token: CancellationToken): Promise<TimelineItemWithSource[]>;
|
||||
}
|
||||
|
||||
export interface TimelineProviderDescriptor {
|
||||
source: string;
|
||||
sourceDescription: string;
|
||||
id: string;
|
||||
label: string;
|
||||
|
||||
replaceable?: boolean;
|
||||
// selector: DocumentSelector;
|
||||
}
|
||||
|
||||
export interface TimelineProvidersChangeEvent {
|
||||
readonly added?: string[];
|
||||
readonly removed?: string[];
|
||||
}
|
||||
|
||||
export interface TimelineRequest {
|
||||
readonly items: Promise<TimelineItemWithSource[]>;
|
||||
readonly source: string;
|
||||
readonly tokenSource: CancellationTokenSource;
|
||||
readonly uri: URI;
|
||||
}
|
||||
|
||||
export interface ITimelineService {
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
onDidChangeProviders: Event<void>;
|
||||
onDidChangeTimeline: Event<URI | undefined>;
|
||||
onDidChangeProviders: Event<TimelineProvidersChangeEvent>;
|
||||
onDidChangeTimeline: Event<TimelineChangeEvent>;
|
||||
|
||||
registerTimelineProvider(provider: TimelineProvider): IDisposable;
|
||||
unregisterTimelineProvider(source: string): void;
|
||||
unregisterTimelineProvider(id: string): void;
|
||||
|
||||
getSources(): string[];
|
||||
|
||||
getTimeline(uri: URI, token: CancellationToken): Promise<TimelineItem[]>;
|
||||
|
||||
getTimelineRequest(id: string, uri: URI, tokenSource: CancellationTokenSource): TimelineRequest | undefined;
|
||||
}
|
||||
|
||||
const TIMELINE_SERVICE_ID = 'timeline';
|
||||
|
|
|
@ -3,60 +3,95 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
// import { basename } from 'vs/base/common/path';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { ITimelineService, TimelineProvider, TimelineItem } from './timeline';
|
||||
import { ITimelineService, TimelineProvider, TimelineItem, TimelineChangeEvent, TimelineProvidersChangeEvent } from './timeline';
|
||||
|
||||
export class TimelineService implements ITimelineService {
|
||||
_serviceBrand: undefined;
|
||||
|
||||
private readonly _onDidChangeProviders = new Emitter<void>();
|
||||
readonly onDidChangeProviders: Event<void> = this._onDidChangeProviders.event;
|
||||
private readonly _onDidChangeProviders = new Emitter<TimelineProvidersChangeEvent>();
|
||||
readonly onDidChangeProviders: Event<TimelineProvidersChangeEvent> = this._onDidChangeProviders.event;
|
||||
|
||||
private readonly _onDidChangeTimeline = new Emitter<URI | undefined>();
|
||||
readonly onDidChangeTimeline: Event<URI | undefined> = this._onDidChangeTimeline.event;
|
||||
private readonly _onDidChangeTimeline = new Emitter<TimelineChangeEvent>();
|
||||
readonly onDidChangeTimeline: Event<TimelineChangeEvent> = this._onDidChangeTimeline.event;
|
||||
|
||||
private readonly _providers = new Map<string, TimelineProvider>();
|
||||
private readonly _providerSubscriptions = new Map<string, IDisposable>();
|
||||
|
||||
constructor(@ILogService private readonly logService: ILogService) {
|
||||
// this.registerTimelineProvider({
|
||||
// source: 'local-history',
|
||||
// sourceDescription: 'Local History',
|
||||
// async provideTimeline(uri: URI, token: CancellationToken) {
|
||||
// return [
|
||||
// {
|
||||
// id: '1',
|
||||
// label: 'Undo Timeline1',
|
||||
// description: uri.toString(true),
|
||||
// date: Date.now()
|
||||
// },
|
||||
// {
|
||||
// id: '2',
|
||||
// label: 'Undo Timeline2',
|
||||
// description: uri.toString(true),
|
||||
// date: Date.now() - 100
|
||||
// }
|
||||
// ];
|
||||
// id: 'local-history',
|
||||
// label: 'Local History',
|
||||
// provideTimeline(uri: URI, token: CancellationToken) {
|
||||
// return new Promise(resolve => setTimeout(() => {
|
||||
// resolve([
|
||||
// {
|
||||
// id: '1',
|
||||
// label: 'Slow Timeline1',
|
||||
// description: basename(uri.fsPath),
|
||||
// timestamp: Date.now(),
|
||||
// source: 'local-history'
|
||||
// },
|
||||
// {
|
||||
// id: '2',
|
||||
// label: 'Slow Timeline2',
|
||||
// description: basename(uri.fsPath),
|
||||
// timestamp: new Date(0).getTime(),
|
||||
// source: 'local-history'
|
||||
// }
|
||||
// ]);
|
||||
// }, 3000));
|
||||
// },
|
||||
// dispose() { }
|
||||
// });
|
||||
|
||||
// this.registerTimelineProvider({
|
||||
// id: 'slow-history',
|
||||
// label: 'Slow History',
|
||||
// provideTimeline(uri: URI, token: CancellationToken) {
|
||||
// return new Promise(resolve => setTimeout(() => {
|
||||
// resolve([
|
||||
// {
|
||||
// id: '1',
|
||||
// label: 'VERY Slow Timeline1',
|
||||
// description: basename(uri.fsPath),
|
||||
// timestamp: Date.now(),
|
||||
// source: 'slow-history'
|
||||
// },
|
||||
// {
|
||||
// id: '2',
|
||||
// label: 'VERY Slow Timeline2',
|
||||
// description: basename(uri.fsPath),
|
||||
// timestamp: new Date(0).getTime(),
|
||||
// source: 'slow-history'
|
||||
// }
|
||||
// ]);
|
||||
// }, 6000));
|
||||
// },
|
||||
// dispose() { }
|
||||
// });
|
||||
}
|
||||
|
||||
async getTimeline(uri: URI, token: CancellationToken, sources?: Set<string>) {
|
||||
getSources() {
|
||||
return [...this._providers.keys()];
|
||||
}
|
||||
|
||||
async getTimeline(uri: URI, token: CancellationToken, predicate?: (provider: TimelineProvider) => boolean) {
|
||||
this.logService.trace(`TimelineService#getTimeline(${uri.toString(true)})`);
|
||||
|
||||
const requests: Promise<[string, TimelineItem[]]>[] = [];
|
||||
|
||||
for (const provider of this._providers.values()) {
|
||||
if (sources && !sources.has(provider.source)) {
|
||||
if (!(predicate?.(provider) ?? true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
requests.push(provider.provideTimeline(uri, token).then(p => [provider.source, p]));
|
||||
requests.push(provider.provideTimeline(uri, token).then(p => [provider.id, p]));
|
||||
}
|
||||
|
||||
const timelines = await Promise.all(requests);
|
||||
|
@ -70,164 +105,71 @@ export class TimelineService implements ITimelineService {
|
|||
timeline.push(...items.map(item => ({ ...item, source: source })));
|
||||
}
|
||||
|
||||
// const requests = new Map<string, Promise<TimelineItem[] | CancellationErrorWithId<string>>>();
|
||||
|
||||
// for (const provider of this._providers.values()) {
|
||||
// if (sources && !sources.has(provider.source)) {
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// requests.set(provider.source, provider.provideTimeline(uri, token));
|
||||
// }
|
||||
|
||||
// // TODO[ECA]: What should the timeout be for waiting for individual providers?
|
||||
// const timelines = await raceAll(requests /*, 5000*/);
|
||||
|
||||
// const timeline = [];
|
||||
// for (const [source, items] of timelines) {
|
||||
// if (items instanceof CancellationError) {
|
||||
// this.logService.trace(`TimelineService#getTimeline(${uri.toString(true)}) source=${source} cancelled`);
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// if (items.length === 0) {
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// timeline.push(...items.map(item => ({ ...item, source: source })));
|
||||
// }
|
||||
|
||||
timeline.sort((a, b) => b.timestamp - a.timestamp);
|
||||
return timeline;
|
||||
}
|
||||
|
||||
getTimelineRequest(id: string, uri: URI, tokenSource: CancellationTokenSource) {
|
||||
this.logService.trace(`TimelineService#getTimeline(${id}): uri=${uri.toString(true)}`);
|
||||
|
||||
const provider = this._providers.get(id);
|
||||
if (provider === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
items: provider.provideTimeline(uri, tokenSource.token)
|
||||
.then(items => {
|
||||
items = items.map(item => ({ ...item, source: provider.id }));
|
||||
items.sort((a, b) => (b.timestamp - a.timestamp) || b.source.localeCompare(a.source, undefined, { numeric: true, sensitivity: 'base' }));
|
||||
|
||||
return items;
|
||||
}),
|
||||
source: provider.id,
|
||||
tokenSource: tokenSource,
|
||||
uri: uri
|
||||
};
|
||||
}
|
||||
|
||||
registerTimelineProvider(provider: TimelineProvider): IDisposable {
|
||||
this.logService.trace(`TimelineService#registerTimelineProvider: source=${provider.source}`);
|
||||
this.logService.trace(`TimelineService#registerTimelineProvider: id=${provider.id}`);
|
||||
|
||||
const source = provider.source;
|
||||
const id = provider.id;
|
||||
|
||||
const existing = this._providers.get(source);
|
||||
// For now to deal with https://github.com/microsoft/vscode/issues/89553 allow any overwritting here (still will be blocked in the Extension Host)
|
||||
// TODO[ECA]: Ultimately will need to figure out a way to unregister providers when the Extension Host restarts/crashes
|
||||
// if (existing && !existing.replaceable) {
|
||||
// throw new Error(`Timeline Provider ${source} already exists.`);
|
||||
// }
|
||||
const existing = this._providers.get(id);
|
||||
if (existing) {
|
||||
// For now to deal with https://github.com/microsoft/vscode/issues/89553 allow any overwritting here (still will be blocked in the Extension Host)
|
||||
// TODO[ECA]: Ultimately will need to figure out a way to unregister providers when the Extension Host restarts/crashes
|
||||
// throw new Error(`Timeline Provider ${id} already exists.`);
|
||||
try {
|
||||
existing?.dispose();
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
this._providers.set(source, provider);
|
||||
this._providers.set(id, provider);
|
||||
if (provider.onDidChange) {
|
||||
this._providerSubscriptions.set(source, provider.onDidChange(uri => this.onProviderTimelineChanged(provider.source, uri)));
|
||||
this._providerSubscriptions.set(id, provider.onDidChange(e => this._onDidChangeTimeline.fire(e)));
|
||||
}
|
||||
this._onDidChangeProviders.fire();
|
||||
this._onDidChangeProviders.fire({ added: [id] });
|
||||
|
||||
return {
|
||||
dispose: () => {
|
||||
this._providers.delete(source);
|
||||
this._onDidChangeProviders.fire();
|
||||
this._providers.delete(id);
|
||||
this._onDidChangeProviders.fire({ removed: [id] });
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
unregisterTimelineProvider(source: string): void {
|
||||
this.logService.trace(`TimelineService#unregisterTimelineProvider: source=${source}`);
|
||||
unregisterTimelineProvider(id: string): void {
|
||||
this.logService.trace(`TimelineService#unregisterTimelineProvider: id=${id}`);
|
||||
|
||||
if (!this._providers.has(source)) {
|
||||
if (!this._providers.has(id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._providers.delete(source);
|
||||
this._providerSubscriptions.delete(source);
|
||||
this._onDidChangeProviders.fire();
|
||||
}
|
||||
|
||||
private onProviderTimelineChanged(source: string, uri: URI | undefined) {
|
||||
// console.log(`TimelineService.onProviderTimelineChanged: source=${source} uri=${uri?.toString(true)}`);
|
||||
|
||||
this._onDidChangeTimeline.fire(uri);
|
||||
this._providers.delete(id);
|
||||
this._providerSubscriptions.delete(id);
|
||||
this._onDidChangeProviders.fire({ removed: [id] });
|
||||
}
|
||||
}
|
||||
|
||||
// function* map<T, TMapped>(source: Iterable<T> | IterableIterator<T>, mapper: (item: T) => TMapped): Iterable<TMapped> {
|
||||
// for (const item of source) {
|
||||
// yield mapper(item);
|
||||
// }
|
||||
// }
|
||||
|
||||
// class CancellationError<TPromise = any> extends Error {
|
||||
// constructor(public readonly promise: TPromise, message: string) {
|
||||
// super(message);
|
||||
// }
|
||||
// }
|
||||
|
||||
// class CancellationErrorWithId<T, TPromise = any> extends CancellationError<TPromise> {
|
||||
// constructor(public readonly id: T, promise: TPromise, message: string) {
|
||||
// super(promise, message);
|
||||
// }
|
||||
// }
|
||||
|
||||
// function raceAll<TPromise>(
|
||||
// promises: Promise<TPromise>[],
|
||||
// timeout?: number
|
||||
// ): Promise<(TPromise | CancellationError<Promise<TPromise>>)[]>;
|
||||
// function raceAll<TPromise, T>(
|
||||
// promises: Map<T, Promise<TPromise>>,
|
||||
// timeout?: number
|
||||
// ): Promise<Map<T, TPromise | CancellationErrorWithId<T, Promise<TPromise>>>>;
|
||||
// function raceAll<TPromise, T>(
|
||||
// ids: Iterable<T>,
|
||||
// fn: (id: T) => Promise<TPromise>,
|
||||
// timeout?: number
|
||||
// ): Promise<Map<T, TPromise | CancellationErrorWithId<T, Promise<TPromise>>>>;
|
||||
// async function raceAll<TPromise, T>(
|
||||
// promisesOrIds: Promise<TPromise>[] | Map<T, Promise<TPromise>> | Iterable<T>,
|
||||
// timeoutOrFn?: number | ((id: T) => Promise<TPromise>),
|
||||
// timeout?: number
|
||||
// ) {
|
||||
// let promises;
|
||||
// if (timeoutOrFn !== undefined && typeof timeoutOrFn !== 'number') {
|
||||
// promises = new Map(
|
||||
// map<T, [T, Promise<TPromise>]>(promisesOrIds as Iterable<T>, id => [id, timeoutOrFn(id)])
|
||||
// );
|
||||
// } else {
|
||||
// timeout = timeoutOrFn;
|
||||
// promises = promisesOrIds as Promise<TPromise>[] | Map<T, Promise<TPromise>>;
|
||||
// }
|
||||
|
||||
// if (promises instanceof Map) {
|
||||
// return new Map(
|
||||
// await Promise.all(
|
||||
// map<[T, Promise<TPromise>], Promise<[T, TPromise | CancellationErrorWithId<T, Promise<TPromise>>]>>(
|
||||
// promises.entries(),
|
||||
// timeout === undefined
|
||||
// ? ([id, promise]) => promise.then(p => [id, p])
|
||||
// : ([id, promise]) =>
|
||||
// Promise.race([
|
||||
// promise,
|
||||
|
||||
// new Promise<CancellationErrorWithId<T, Promise<TPromise>>>(resolve =>
|
||||
// setTimeout(() => resolve(new CancellationErrorWithId(id, promise, 'TIMED OUT')), timeout!)
|
||||
// )
|
||||
// ]).then(p => [id, p])
|
||||
// )
|
||||
// )
|
||||
// );
|
||||
// }
|
||||
|
||||
// return Promise.all(
|
||||
// timeout === undefined
|
||||
// ? promises
|
||||
// : promises.map(p =>
|
||||
// Promise.race([
|
||||
// p,
|
||||
// new Promise<CancellationError<Promise<TPromise>>>(resolve =>
|
||||
// setTimeout(() => resolve(new CancellationError(p, 'TIMED OUT')), timeout!)
|
||||
// )
|
||||
// ])
|
||||
// )
|
||||
// );
|
||||
// }
|
||||
|
|
Loading…
Reference in a new issue