mirror of
https://github.com/microsoft/vscode.git
synced 2026-07-10 16:58:55 -05:00
Adds refresh for when timeline changes
This commit is contained in:
@@ -3,15 +3,25 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { CancellationToken, Disposable, TimelineItem, TimelineProvider, Uri, workspace, ThemeIcon } from 'vscode';
|
||||
import { CancellationToken, Disposable, Event, EventEmitter, ThemeIcon, TimelineItem, TimelineProvider, Uri, workspace } from 'vscode';
|
||||
import { Model } from './model';
|
||||
import { Repository } from './repository';
|
||||
import { debounce } from './decorators';
|
||||
|
||||
export class GitTimelineProvider implements TimelineProvider {
|
||||
private _onDidChange = new EventEmitter<Uri | undefined>();
|
||||
get onDidChange(): Event<Uri | undefined> {
|
||||
return this._onDidChange.event;
|
||||
}
|
||||
|
||||
readonly source = 'git-history';
|
||||
readonly sourceDescription = 'Git History';
|
||||
|
||||
private _disposable: Disposable;
|
||||
|
||||
private _repo: Repository | undefined;
|
||||
private _repoDisposable: Disposable | undefined;
|
||||
|
||||
constructor(private readonly _model: Model) {
|
||||
this._disposable = workspace.registerTimelineProvider('*', this);
|
||||
}
|
||||
@@ -21,12 +31,35 @@ export class GitTimelineProvider implements TimelineProvider {
|
||||
}
|
||||
|
||||
async provideTimeline(uri: Uri, _since: number, _token: CancellationToken): Promise<TimelineItem[]> {
|
||||
console.log(`GitTimelineProvider.provideTimeline: uri=${uri} state=${this._model.state}`);
|
||||
|
||||
const repo = this._model.getRepository(uri);
|
||||
if (!repo) {
|
||||
console.log(`GitTimelineProvider.provideTimeline: repo NOT found`);
|
||||
|
||||
this._repoDisposable?.dispose();
|
||||
this._repo = undefined;
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
console.log(`GitTimelineProvider.provideTimeline: repo found`);
|
||||
|
||||
if (this._repo?.root !== repo.root) {
|
||||
this._repoDisposable?.dispose();
|
||||
|
||||
this._repo = repo;
|
||||
this._repoDisposable = Disposable.from(
|
||||
repo.onDidChangeRepository(() => this.onRepositoryChanged(), this)
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: Ensure that the uri is a file -- if not we could get the history of the repo?
|
||||
|
||||
const commits = await repo.logFile(uri, { maxEntries: 10 });
|
||||
|
||||
console.log(`GitTimelineProvider.provideTimeline: commits=${commits.length}`);
|
||||
|
||||
return commits.map<TimelineItem>(c => {
|
||||
let message = c.message;
|
||||
|
||||
@@ -50,4 +83,11 @@ export class GitTimelineProvider implements TimelineProvider {
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@debounce(500)
|
||||
private onRepositoryChanged() {
|
||||
console.log(`GitTimelineProvider.onRepositoryChanged`);
|
||||
|
||||
this._onDidChange.fire();
|
||||
}
|
||||
}
|
||||
|
||||
2
src/vs/vscode.proposed.d.ts
vendored
2
src/vs/vscode.proposed.d.ts
vendored
@@ -1549,7 +1549,7 @@ declare module 'vscode' {
|
||||
// onDidAdd?: Event<TimelimeAddEvent>;
|
||||
// onDidChange?: Event<TimelimeChangeEvent>;
|
||||
|
||||
onDidChange?: Event<void>;
|
||||
onDidChange?: Event<Uri | undefined>;
|
||||
|
||||
/**
|
||||
* An identifier of the source of the timeline items. This can be used for filtering and/or overriding existing sources.
|
||||
|
||||
@@ -8,10 +8,12 @@ import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
|
||||
import { ITimelineService, TimelineItem, TimelineProviderDescriptor } from 'vs/workbench/contrib/timeline/common/timeline';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadTimeline)
|
||||
export class MainThreadTimeline implements MainThreadTimelineShape {
|
||||
private readonly _proxy: ExtHostTimelineShape;
|
||||
private readonly _providerEmitters = new Map<string, Emitter<URI | undefined>>();
|
||||
|
||||
constructor(
|
||||
context: IExtHostContext,
|
||||
@@ -29,12 +31,24 @@ export class MainThreadTimeline implements MainThreadTimelineShape {
|
||||
|
||||
const proxy = this._proxy;
|
||||
|
||||
const emitters = this._providerEmitters;
|
||||
let onDidChange = emitters.get(provider.source);
|
||||
// eslint-disable-next-line eqeqeq
|
||||
if (onDidChange == null) {
|
||||
onDidChange = new Emitter<URI | undefined>();
|
||||
emitters.set(provider.source, onDidChange);
|
||||
}
|
||||
|
||||
this._timelineService.registerTimelineProvider({
|
||||
...provider,
|
||||
onDidChange: onDidChange.event,
|
||||
provideTimeline(uri: URI, since: number, token: CancellationToken) {
|
||||
return proxy.$getTimeline(provider.source, uri, since, token);
|
||||
},
|
||||
dispose() { }
|
||||
dispose() {
|
||||
emitters.delete(provider.source);
|
||||
onDidChange?.dispose();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -43,6 +57,13 @@ export class MainThreadTimeline implements MainThreadTimelineShape {
|
||||
this._timelineService.unregisterTimelineProvider(source);
|
||||
}
|
||||
|
||||
$emitTimelineChangeEvent(source: string, uri: URI | undefined): void {
|
||||
console.log(`MainThreadTimeline#emitChangeEvent: source=${source} uri=${uri?.toString(true)}`);
|
||||
|
||||
const emitter = this._providerEmitters.get(source);
|
||||
emitter?.fire(uri);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
// noop
|
||||
}
|
||||
|
||||
@@ -801,8 +801,9 @@ 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;
|
||||
|
||||
$getTimeline(resource: UriComponents, since: number, token: CancellationToken): Promise<TimelineItem[]>;
|
||||
$getTimeline(uri: UriComponents, since: number, token: CancellationToken): Promise<TimelineItem[]>;
|
||||
}
|
||||
|
||||
// -- extension host
|
||||
|
||||
@@ -39,13 +39,20 @@ export class ExtHostTimeline implements IExtHostTimeline {
|
||||
}
|
||||
|
||||
registerTimelineProvider(provider: vscode.TimelineProvider, commandConverter: CommandsConverter): IDisposable {
|
||||
const disposables = new DisposableStore();
|
||||
const timelineDisposables = new DisposableStore();
|
||||
|
||||
const convertTimelineItem = this.convertTimelineItem(provider.source, commandConverter, timelineDisposables);
|
||||
|
||||
let disposable: IDisposable | undefined;
|
||||
if (provider.onDidChange) {
|
||||
console.log(`ExtHostTimeline#registerTimelineProvider: provider=${provider.source} hooking up onDidChange`);
|
||||
disposable = provider.onDidChange(this.emitTimelineChangeEvent(provider.source), this);
|
||||
}
|
||||
|
||||
const convertTimelineItem = this.convertTimelineItem(provider.source, commandConverter, disposables);
|
||||
return this.registerTimelineProviderCore({
|
||||
...provider,
|
||||
async provideTimeline(uri: URI, since: number, token: CancellationToken) {
|
||||
disposables.clear();
|
||||
timelineDisposables.clear();
|
||||
|
||||
const results = await provider.provideTimeline(uri, since, token);
|
||||
// eslint-disable-next-line eqeqeq
|
||||
@@ -54,7 +61,8 @@ export class ExtHostTimeline implements IExtHostTimeline {
|
||||
: [];
|
||||
},
|
||||
dispose() {
|
||||
disposables.dispose();
|
||||
disposable?.dispose();
|
||||
timelineDisposables.dispose();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -90,6 +98,13 @@ export class ExtHostTimeline implements IExtHostTimeline {
|
||||
};
|
||||
}
|
||||
|
||||
private emitTimelineChangeEvent(source: string) {
|
||||
return (uri: vscode.Uri | undefined) => {
|
||||
console.log(`ExtHostTimeline#registerTimelineProvider: provider=${source} onDidChange fired; uri=${uri?.toString(true)}`);
|
||||
this._proxy.$emitTimelineChangeEvent(source, uri);
|
||||
};
|
||||
}
|
||||
|
||||
private registerTimelineProviderCore(provider: TimelineProvider): IDisposable {
|
||||
console.log(`ExtHostTimeline#registerTimelineProvider: provider=${provider.source}`);
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel';
|
||||
import { IListVirtualDelegate, IIdentityProvider, IKeyboardNavigationLabelProvider } from 'vs/base/browser/ui/list/list';
|
||||
import { ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree';
|
||||
import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
import { WorkbenchObjectTree, TreeResourceNavigator } from 'vs/platform/list/browser/listService';
|
||||
import { TreeResourceNavigator, WorkbenchObjectTree } from 'vs/platform/list/browser/listService';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
@@ -62,17 +62,32 @@ export class TimelinePane extends ViewPane {
|
||||
uri = toResource(editor, { supportSideBySide: SideBySideEditor.MASTER });
|
||||
}
|
||||
|
||||
console.log(`TimelinePane.onActiveEditorChanged: uri=${uri?.toString(true)}`);
|
||||
|
||||
this.updateUri(uri);
|
||||
}
|
||||
|
||||
private onProvidersChanged() {
|
||||
console.log(`TimelinePane.onProvidersChanged`);
|
||||
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
private onTimelineChanged(uri: URI | undefined) {
|
||||
console.log(`TimelinePane.onTimelineChanged: uri=${uri?.toString(true)} this._uri=${this._uri?.toString(true)}`);
|
||||
|
||||
// eslint-disable-next-line eqeqeq
|
||||
if (uri == null || uri.toString(true) !== this._uri?.toString(true)) {
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
private async refresh() {
|
||||
this._tokenSource?.cancel();
|
||||
this._tokenSource = new CancellationTokenSource();
|
||||
|
||||
console.log(`TimelinePane.refresh: uri=${this._uri?.toString(true)}`);
|
||||
|
||||
// TODO: Deal with no uri -- use a view title? or keep the last one cached?
|
||||
// TODO: Deal with no items -- use a view title?
|
||||
|
||||
@@ -89,9 +104,12 @@ export class TimelinePane extends ViewPane {
|
||||
|
||||
private updateUri(uri: URI | undefined) {
|
||||
if (uri?.toString(true) === this._uri?.toString(true)) {
|
||||
console.log(`TimelinePane.updateUri(same): uri=${uri?.toString(true)}`);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`TimelinePane.updateUri: uri=${uri?.toString(true)}`);
|
||||
this._uri = uri;
|
||||
this.refresh();
|
||||
}
|
||||
@@ -102,12 +120,15 @@ export class TimelinePane extends ViewPane {
|
||||
}
|
||||
|
||||
setVisible(visible: boolean): void {
|
||||
console.log(`TimelinePane.setVisible: visible=${visible}`);
|
||||
|
||||
if (visible) {
|
||||
this._visibilityDisposables = new DisposableStore();
|
||||
|
||||
this.timelineService.onDidChangeProviders(this.onProvidersChanged, this, this._visibilityDisposables);
|
||||
|
||||
this.timelineService.onDidChangeTimeline(this.onTimelineChanged, this, this._visibilityDisposables);
|
||||
this.editorService.onDidActiveEditorChange(this.onActiveEditorChanged, this, this._visibilityDisposables);
|
||||
|
||||
this.onActiveEditorChanged();
|
||||
} else {
|
||||
this._visibilityDisposables?.dispose();
|
||||
|
||||
@@ -33,6 +33,8 @@ export interface TimelineItemWithSource extends TimelineItem {
|
||||
}
|
||||
|
||||
export interface TimelineProvider extends TimelineProviderDescriptor, IDisposable {
|
||||
onDidChange?: Event<URI | undefined>;
|
||||
|
||||
provideTimeline(uri: URI, since: number, token: CancellationToken): Promise<TimelineItem[]>;
|
||||
}
|
||||
|
||||
@@ -48,6 +50,8 @@ export interface ITimelineService {
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
onDidChangeProviders: Event<void>;
|
||||
onDidChangeTimeline: Event<URI | undefined>;
|
||||
|
||||
registerTimelineProvider(provider: TimelineProvider): IDisposable;
|
||||
unregisterTimelineProvider(source: string): void;
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { ITimelineService, TimelineProvider } from './timeline';
|
||||
import { ITimelineService, TimelineProvider, TimelineItem } from './timeline';
|
||||
|
||||
export class TimelineService implements ITimelineService {
|
||||
_serviceBrand: undefined;
|
||||
@@ -16,7 +16,11 @@ export class TimelineService implements ITimelineService {
|
||||
private readonly _onDidChangeProviders = new Emitter<void>();
|
||||
readonly onDidChangeProviders: Event<void> = this._onDidChangeProviders.event;
|
||||
|
||||
private readonly _onDidChangeTimeline = new Emitter<URI | undefined>();
|
||||
readonly onDidChangeTimeline: Event<URI | undefined> = 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({
|
||||
@@ -42,27 +46,34 @@ export class TimelineService implements ITimelineService {
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: Add filtering
|
||||
async getTimeline(uri: URI, since: number, token: CancellationToken) {
|
||||
async getTimeline(uri: URI, since: number, token: CancellationToken, sources?: Set<string>) {
|
||||
this.logService.trace(`TimelineService#getTimeline: uri=${uri.toString(true)}`);
|
||||
const requests = [];
|
||||
|
||||
console.log(`TimelineService.getTimeline providers=${this._providers.size}`);
|
||||
|
||||
const requests = new Map<string, Promise<TimelineItem[] | CancellationErrorWithId<string>>>();
|
||||
|
||||
for (const provider of this._providers.values()) {
|
||||
requests.push(
|
||||
provider.provideTimeline(uri, since, token).then(items => ({ source: provider.source, items: items }))
|
||||
);
|
||||
if (sources && !sources.has(provider.source)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
requests.set(provider.source, provider.provideTimeline(uri, since, token));
|
||||
}
|
||||
|
||||
const timelines = await raceAll(requests /*, 5000*/);
|
||||
|
||||
console.log(`TimelineService.getTimeline timelines=${timelines.size}`);
|
||||
|
||||
const timeline = [];
|
||||
for (const result of timelines) {
|
||||
// eslint-disable-next-line eqeqeq
|
||||
if (result == null || result instanceof CancellationError) {
|
||||
for (const [source, items] of timelines) {
|
||||
if (items instanceof CancellationError) {
|
||||
console.log(`TimelineService.getTimeline source=${source} cancelled`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const { source, items } = result;
|
||||
console.log(`TimelineService.getTimeline source=${source} items=${items.length}`);
|
||||
|
||||
if (items.length === 0) {
|
||||
continue;
|
||||
}
|
||||
@@ -85,6 +96,9 @@ export class TimelineService implements ITimelineService {
|
||||
}
|
||||
|
||||
this._providers.set(source, provider);
|
||||
if (provider.onDidChange) {
|
||||
this._providerSubscriptions.set(source, provider.onDidChange(uri => this.onProviderTimelineChanged(provider.source, uri)));
|
||||
}
|
||||
this._onDidChangeProviders.fire();
|
||||
|
||||
return {
|
||||
@@ -103,8 +117,15 @@ export class TimelineService implements ITimelineService {
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
function* map<T, TMapped>(source: Iterable<T> | IterableIterator<T>, mapper: (item: T) => TMapped): Iterable<TMapped> {
|
||||
|
||||
Reference in New Issue
Block a user