From 90df2d09f4412aaa9dfd57e7ff2821f135369dca Mon Sep 17 00:00:00 2001 From: Eric Amodio Date: Tue, 3 Mar 2020 15:04:13 -0500 Subject: [PATCH] Adds Open Timeline command to explorer files Adds follow/unfollow actions to the timeline view Reworks timeline view commands --- .../timeline/browser/timeline.contribution.ts | 90 +++--------- .../contrib/timeline/browser/timelinePane.ts | 132 +++++++++++++++--- .../contrib/timeline/common/timeline.ts | 7 +- .../timeline/common/timelineService.ts | 22 +-- 4 files changed, 143 insertions(+), 108 deletions(-) diff --git a/src/vs/workbench/contrib/timeline/browser/timeline.contribution.ts b/src/vs/workbench/contrib/timeline/browser/timeline.contribution.ts index b3958cb47c7..a5cbf2fb130 100644 --- a/src/vs/workbench/contrib/timeline/browser/timeline.contribution.ts +++ b/src/vs/workbench/contrib/timeline/browser/timeline.contribution.ts @@ -9,7 +9,7 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { Registry } from 'vs/platform/registry/common/platform'; import { IViewsRegistry, IViewDescriptor, Extensions as ViewExtensions } from 'vs/workbench/common/views'; import { VIEW_CONTAINER } from 'vs/workbench/contrib/files/browser/explorerViewlet'; -import { ITimelineService } from 'vs/workbench/contrib/timeline/common/timeline'; +import { ITimelineService, TimelinePaneId } from 'vs/workbench/contrib/timeline/common/timeline'; import { TimelineService } from 'vs/workbench/contrib/timeline/common/timelineService'; import { TimelinePane } from './timelinePane'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; @@ -17,9 +17,11 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; import { ICommandHandler, CommandsRegistry } from 'vs/platform/commands/common/commands'; import product from 'vs/platform/product/common/product'; +import { ExplorerFolderContext } from 'vs/workbench/contrib/files/common/files'; +import { ResourceContextKey } from 'vs/workbench/common/resources'; export class TimelinePaneDescriptor implements IViewDescriptor { - readonly id = TimelinePane.ID; + readonly id = TimelinePaneId; readonly name = TimelinePane.TITLE; readonly ctorDescriptor = new SyncDescriptor(TimelinePane); readonly when = ContextKeyExpr.equals('config.timeline.showView', true); @@ -56,90 +58,30 @@ configurationRegistry.registerConfiguration({ Registry.as(ViewExtensions.ViewsRegistry).registerViews([new TimelinePaneDescriptor()], VIEW_CONTAINER); -namespace TimelineViewRefreshAction { +namespace OpenTimelineAction { - export const ID = 'timeline.refresh'; - export const LABEL = localize('timeline.refreshView', "Refresh"); + export const ID = 'files.openTimeline'; + export const LABEL = localize('files.openTimeline', "Open Timeline"); export function handler(): ICommandHandler { return (accessor, arg) => { const service = accessor.get(ITimelineService); - return service.reset(); + return service.setUri(arg); }; } } -CommandsRegistry.registerCommand(TimelineViewRefreshAction.ID, TimelineViewRefreshAction.handler()); +CommandsRegistry.registerCommand(OpenTimelineAction.ID, OpenTimelineAction.handler()); -// namespace TimelineViewRefreshHardAction { - -// export const ID = 'timeline.refreshHard'; -// export const LABEL = localize('timeline.refreshHard', "Refresh (Hard)"); - -// export function handler(fetch?: 'all' | 'more'): ICommandHandler { -// return (accessor, arg) => { -// const service = accessor.get(ITimelineService); -// return service.refresh(fetch); -// }; -// } -// } - -// CommandsRegistry.registerCommand(TimelineViewRefreshAction.ID, TimelineViewRefreshAction.handler()); - -// namespace TimelineViewLoadMoreAction { - -// export const ID = 'timeline.loadMore'; -// export const LABEL = localize('timeline.loadMoreInView', "Load More"); - -// export function handler(): ICommandHandler { -// return (accessor, arg) => { -// const service = accessor.get(ITimelineService); -// return service.refresh('more'); -// }; -// } -// } - -// CommandsRegistry.registerCommand(TimelineViewLoadMoreAction.ID, TimelineViewLoadMoreAction.handler()); - -// namespace TimelineViewLoadAllAction { - -// export const ID = 'timeline.loadAll'; -// export const LABEL = localize('timeline.loadAllInView', "Load All"); - -// export function handler(): ICommandHandler { -// return (accessor, arg) => { -// const service = accessor.get(ITimelineService); -// return service.refresh('all'); -// }; -// } -// } - -// CommandsRegistry.registerCommand(TimelineViewLoadAllAction.ID, TimelineViewLoadAllAction.handler()); - -MenuRegistry.appendMenuItem(MenuId.TimelineTitle, ({ - group: 'navigation', +MenuRegistry.appendMenuItem(MenuId.ExplorerContext, ({ + group: '4_timeline', order: 1, command: { - id: TimelineViewRefreshAction.ID, - title: TimelineViewRefreshAction.LABEL, - icon: { id: 'codicon/refresh' } - } + id: OpenTimelineAction.ID, + title: localize(OpenTimelineAction.LABEL, "Open Timeline"), + icon: { id: 'codicon/history' } + }, + when: ContextKeyExpr.and(ExplorerFolderContext.toNegated(), ResourceContextKey.HasResource) })); -// MenuRegistry.appendMenuItem(MenuId.TimelineTitle, ({ -// group: 'navigation', -// order: 2, -// command: { -// id: TimelineViewLoadMoreAction.ID, -// title: TimelineViewLoadMoreAction.LABEL, -// icon: { id: 'codicon/unfold' } -// }, -// alt: { -// id: TimelineViewLoadAllAction.ID, -// title: TimelineViewLoadAllAction.LABEL, -// icon: { id: 'codicon/unfold' } - -// } -// })); - registerSingleton(ITimelineService, TimelineService, true); diff --git a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts index b7fe88a1660..163e735e021 100644 --- a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts +++ b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts @@ -18,13 +18,13 @@ import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/vie 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'; +import { IContextKeyService, IContextKey, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { ITimelineService, TimelineChangeEvent, TimelineItem, TimelineOptions, TimelineProvidersChangeEvent, TimelineRequest, Timeline } from 'vs/workbench/contrib/timeline/common/timeline'; +import { ITimelineService, TimelineChangeEvent, TimelineItem, TimelineOptions, TimelineProvidersChangeEvent, TimelineRequest, Timeline, TimelinePaneId } 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'; +import { ICommandService, CommandsRegistry, ICommandHandler } from 'vs/platform/commands/common/commands'; import { IThemeService, LIGHT, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { IViewDescriptorService } from 'vs/workbench/common/views'; import { basename } from 'vs/base/common/path'; @@ -34,7 +34,7 @@ import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IActionViewItemProvider, ActionBar, ActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { IAction, ActionRunner } from 'vs/base/common/actions'; import { ContextAwareMenuEntryActionViewItem, createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; -import { MenuItemAction, IMenuService, MenuId } from 'vs/platform/actions/common/actions'; +import { MenuItemAction, IMenuService, MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; import { fromNow } from 'vs/base/common/date'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -85,8 +85,9 @@ interface TimelineCursors { more: boolean; } +export const TimelineFollowActiveEditorContext = new RawContextKey('timelineFollowActiveEditor', true); + export class TimelinePane extends ViewPane { - static readonly ID = 'timeline'; static readonly TITLE = localize('timeline', 'Timeline'); private _$container!: HTMLElement; @@ -95,9 +96,11 @@ export class TimelinePane extends ViewPane { private _$tree!: HTMLDivElement; private _tree!: WorkbenchObjectTree; private _treeRenderer: TimelineTreeRenderer | undefined; - private _menus: TimelineMenus; + private _menus: TimelinePaneMenus; private _visibilityDisposables: DisposableStore | undefined; + private _followActiveEditorContext: IContextKey; + private _excludedSources: Set; private _cursorsByProvider: Map = new Map(); private _items: { element: TreeElement }[] = []; @@ -122,13 +125,53 @@ export class TimelinePane extends ViewPane { ) { super({ ...options, titleMenuId: MenuId.TimelineTitle }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); - this._menus = this._register(this.instantiationService.createInstance(TimelineMenus, this.id)); + this._menus = this._register(this.instantiationService.createInstance(TimelinePaneMenus, this.id)); + this._register(this.instantiationService.createInstance(TimelinePaneCommands, this)); const scopedContextKeyService = this._register(this.contextKeyService.createScoped()); - scopedContextKeyService.createKey('view', TimelinePane.ID); + scopedContextKeyService.createKey('view', TimelinePaneId); + + this._followActiveEditorContext = TimelineFollowActiveEditorContext.bindTo(this.contextKeyService); this._excludedSources = new Set(configurationService.getValue('timeline.excludeSources')); configurationService.onDidChangeConfiguration(this.onConfigurationChanged, this); + + this._register(timelineService.onDidChangeUri(uri => this.setUri(uri), this)); + } + + private _followActiveEditor: boolean = true; + get followActiveEditor(): boolean { + return this._followActiveEditor; + } + set followActiveEditor(value: boolean) { + if (this._followActiveEditor === value) { + return; + } + + this._followActiveEditor = value; + this._followActiveEditorContext.set(value); + + if (value) { + this.onActiveEditorChanged(); + } + } + + reset() { + this.loadTimeline(true); + } + + setUri(uri: URI) { + this.setUriCore(uri, true); + } + + private setUriCore(uri: URI | undefined, disableFollowing: boolean) { + if (disableFollowing) { + this.followActiveEditor = false; + } + + this._uri = uri; + this._treeRenderer?.setUri(uri); + this.loadTimeline(true); } private onConfigurationChanged(e: IConfigurationChangeEvent) { @@ -141,6 +184,10 @@ export class TimelinePane extends ViewPane { } private onActiveEditorChanged() { + if (!this.followActiveEditor) { + return; + } + let uri; const editor = this.editorService.activeEditor; @@ -154,9 +201,7 @@ export class TimelinePane extends ViewPane { return; } - this._uri = uri; - this._treeRenderer?.setUri(uri); - this.loadTimeline(true); + this.setUriCore(uri, false); } private onProvidersChanged(e: TimelineProvidersChangeEvent) { @@ -177,11 +222,6 @@ export class TimelinePane extends ViewPane { } } - private onReset() { - this.loadTimeline(true); - } - - private _titleDescription: string | undefined; get titleDescription(): string | undefined { return this._titleDescription; @@ -574,13 +614,14 @@ export class TimelinePane extends ViewPane { this.timelineService.onDidChangeProviders(this.onProvidersChanged, this, this._visibilityDisposables); this.timelineService.onDidChangeTimeline(this.onTimelineChanged, this, this._visibilityDisposables); - this.timelineService.onDidReset(this.onReset, this, this._visibilityDisposables); this.editorService.onDidActiveEditorChange(this.onActiveEditorChanged, this, this._visibilityDisposables); this.onActiveEditorChanged(); } else { this._visibilityDisposables?.dispose(); } + + super.setVisible(visible); } protected layoutBody(height: number, width: number): void { @@ -671,7 +712,7 @@ export class TimelinePane extends ViewPane { this.message = file ? localize('timeline.loading', 'Loading timeline for {0}...', file) : ''; } - private onContextMenu(menus: TimelineMenus, treeEvent: ITreeContextMenuEvent): void { + private onContextMenu(menus: TimelinePaneMenus, treeEvent: ITreeContextMenuEvent): void { const item = treeEvent.element; if (item === null) { return; @@ -795,7 +836,7 @@ class TimelineTreeRenderer implements ITreeRenderer this._pane.reset(); + } + + toggleFollowActiveEditorCommand(): ICommandHandler { + return (accessor, arg) => this._pane.followActiveEditor = !this._pane.followActiveEditor; + } +} + +class TimelinePaneMenus extends Disposable { constructor( private id: string, diff --git a/src/vs/workbench/contrib/timeline/common/timeline.ts b/src/vs/workbench/contrib/timeline/common/timeline.ts index 3f7cad3109e..d9e4720eb00 100644 --- a/src/vs/workbench/contrib/timeline/common/timeline.ts +++ b/src/vs/workbench/contrib/timeline/common/timeline.ts @@ -15,6 +15,8 @@ export function toKey(extension: ExtensionIdentifier | string, source: string) { return `${typeof extension === 'string' ? extension : ExtensionIdentifier.toKey(extension)}|${source}`; } +export const TimelinePaneId = 'timeline'; + export interface TimelineItem { handle: string; source: string; @@ -91,7 +93,7 @@ export interface ITimelineService { onDidChangeProviders: Event; onDidChangeTimeline: Event; - onDidReset: Event; + onDidChangeUri: Event; registerTimelineProvider(provider: TimelineProvider): IDisposable; unregisterTimelineProvider(id: string): void; @@ -100,8 +102,7 @@ export interface ITimelineService { getTimeline(id: string, uri: URI, options: TimelineOptions, tokenSource: CancellationTokenSource, internalOptions?: InternalTimelineOptions): TimelineRequest | undefined; - // refresh(fetch?: 'all' | 'more'): void; - reset(): void; + setUri(uri: URI): void; } const TIMELINE_SERVICE_ID = 'timeline'; diff --git a/src/vs/workbench/contrib/timeline/common/timelineService.ts b/src/vs/workbench/contrib/timeline/common/timelineService.ts index 4f61ad5ef20..fc996a3d8c5 100644 --- a/src/vs/workbench/contrib/timeline/common/timelineService.ts +++ b/src/vs/workbench/contrib/timeline/common/timelineService.ts @@ -9,7 +9,8 @@ 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, TimelineChangeEvent, TimelineOptions, TimelineProvidersChangeEvent, TimelineProvider, InternalTimelineOptions } from './timeline'; +import { ITimelineService, TimelineChangeEvent, TimelineOptions, TimelineProvidersChangeEvent, TimelineProvider, InternalTimelineOptions, TimelinePaneId } from './timeline'; +import { IViewsService } from 'vs/workbench/common/views'; export class TimelineService implements ITimelineService { _serviceBrand: undefined; @@ -19,14 +20,16 @@ export class TimelineService implements ITimelineService { private readonly _onDidChangeTimeline = new Emitter(); readonly onDidChangeTimeline: Event = this._onDidChangeTimeline.event; - - private readonly _onDidReset = new Emitter(); - readonly onDidReset: Event = this._onDidReset.event; + private readonly _onDidChangeUri = new Emitter(); + readonly onDidChangeUri: Event = this._onDidChangeUri.event; private readonly _providers = new Map(); private readonly _providerSubscriptions = new Map(); - constructor(@ILogService private readonly logService: ILogService) { + constructor( + @ILogService private readonly logService: ILogService, + @IViewsService protected viewsService: IViewsService, + ) { // let source = 'slow-source'; // this.registerTimelineProvider({ // scheme: '*', @@ -175,11 +178,8 @@ export class TimelineService implements ITimelineService { this._onDidChangeProviders.fire({ removed: [id] }); } - // refresh(fetch?: 'all' | 'more') { - // this._onDidChangeTimeline.fire({ fetch: fetch }); - // } - - reset() { - this._onDidReset.fire(); + setUri(uri: URI) { + this.viewsService.openView(TimelinePaneId, true); + this._onDidChangeUri.fire(uri); } }