Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

API support for Timeline view #84297

Open
eamodio opened this issue Nov 8, 2019 · 28 comments · Fixed by #89262
Open

API support for Timeline view #84297

eamodio opened this issue Nov 8, 2019 · 28 comments · Fixed by #89262
Assignees
Labels
api api-proposal feature-request Request for new features or functionality scm General SCM compound issues timeline Timeline view issues tree-views Extension tree view issues
Milestone

Comments

@eamodio
Copy link
Contributor

eamodio commented Nov 8, 2019

Goals

Add support for a unified file-based timeline view to be added to the Explorer sidebar which will track the active document (similar to the Outline view). This new view can be contributed by multiple sources, e.g. save/undo points, source control commits, test runs/failures, etc. Events from all sources will be aggregated into a single chronologically-ordered view.

Proposal

	export class TimelineItem {
		/**
		 * A date for when the timeline item occurred
		 */
		date: Date;

		/**
		 * A human-readable string describing the source of the timeline item. This can be used for filtering by sources so keep it consistent across timeline item types.
		 */
		source: string;

		/**
		 * Optional method to get the children of the timeline item (if any).
		 *
		 * @return Children of the timeline item (if any).
		 */
		getChildren?(): ProviderResult<TreeItem[]>;

		/**
		 * A human-readable string describing the timeline item. When `falsy`, it is derived from [resourceUri](#TreeItem.resourceUri).
		 */
		label: string;

		/**
		 * Optional id for the timeline item. See [TreeItem.id](#TreeItem.id) for more details.
		 */
		id?: string;

		/**
		 * The icon path or [ThemeIcon](#ThemeIcon) for the timeline item. See [TreeItem.iconPath](#TreeItem.iconPath) for more details.
		 */
		iconPath?: string | Uri | { light: string | Uri; dark: string | Uri } | ThemeIcon;

		/**
		 * A human readable string describing less prominent details of the timeline item. See [TreeItem.description](#TreeItem.description) for more details.
		 */
		description?: string | boolean;

		/**
		 * The [uri](#Uri) of the resource representing the timeline item (if any). See [TreeItem.resourceUri](#TreeItem.resourceUri) for more details.
		 */
		resourceUri?: Uri;

		/**
		 * The tooltip text when you hover over the timeline item.
		 */
		tooltip?: string | undefined;

		/**
		 * The [command](#Command) that should be executed when the timeline item is selected.
		 */
		command?: Command;

		/**
		 * [TreeItemCollapsibleState](#TreeItemCollapsibleState) of the timeline item.
		 */
		collapsibleState?: TreeItemCollapsibleState;

		/**
		 * Context value of the timeline item.  See [TreeItem.contextValue](#TreeItem.contextValue) for more details.
		 */
		contextValue?: string;

		/**
		 * @param label A human-readable string describing the timeline item
		 * @param date A date for when the timeline item occurred
		 * @param source A human-readable string describing the source of the timeline item
		 * @param collapsibleState [TreeItemCollapsibleState](#TreeItemCollapsibleState) of the timeline item. Default is [TreeItemCollapsibleState.None](#TreeItemCollapsibleState.None)
		 */
		constructor(label: string, date: Date, source: string, collapsibleState?: TreeItemCollapsibleState);
	}

	export interface TimelimeAddEvent {

		/**
		 * An array of timeline items which have been added.
		 */
		readonly items: readonly TimelineItem[];

		/**
		 * The uri of the file to which the timeline items belong.
		 */
		readonly uri: Uri;
	}

	export interface TimelimeChangeEvent {

		/**
		 * The date after which the timeline has changed. If `undefined` the entire timeline will be reset.
		 */
		readonly since?: Date;

		/**
		 * The uri of the file to which the timeline changed.
		 */
		readonly uri: Uri;
	}

	export interface TimelineProvider {
		onDidAdd?: Event<TimelimeAddEvent>;
		onDidChange?: Event<TimelimeChangeEvent>;

		/**
		 * Provide [timeline items](#TimelineItem) for a [Uri](#Uri) after a particular date.
		 *
		 * @param uri The uri of the file to provide the timeline for.
		 * @param since A date after which timeline items should be provided.
		 * @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.
		 */
		provideTimeline(uri: Uri, since: Date, token: CancellationToken): ProviderResult<TimelineItem[]>;
	}

	export namespace workspace {
		/**
		 * Register a timeline provider.
		 *
		 * Multiple providers can be registered. In that case, providers are asked in
		 * parallel and the results are merged. A failing provider (rejected promise or exception) will
		 * not cause a failure of the whole operation.
		 *
		 * @param selector A selector that defines the documents this provider is applicable to.
		 * @param provider A timeline provider.
		 * @return A [disposable](#Disposable) that unregisters this provider when being disposed.
		 */
		export function registerTimelineProvider(selector: DocumentSelector, provider: TimelineProvider): Disposable;
	}

A timeline source (e.g. an extension) registers a TimelineProvider for a set of documents. VS Code will then call the all the registered TimelineProvider.provideTimeline callbacks (in parallel) when the active editor changes and matches those registrations. The results will be merged into a unified set ordered by the TimelineItem.date and displayed in a new File Timeline view in the Explorer sidebar.

A timeline provider can signal that new events have occurred via the onDidAdd event, providing the set of additional timeline items. A provider can also signal a refresh of its timeline items via the onDidChange event.

Questions & Challenges

API

  • Should we provides a model/signal that a timeline provider should not be cached or more accurately flushed when the file is no longer the active editor? Basically an alternative to sending events, just call provideTimeline again
  • Caching will be a bit challenging
    • How much do we keep around? And for how long?
  • Should the TimelimeChangeEvent event provide a way to signal that an individual item(s) should be updated?
  • Do we need a throttle on onDidAdd?
  • Need to be careful with ids for the tree items, since they can come from multiple extensions. Probably should ensure a prefix or something per provider for any provided ids (or not give control over the ids at all)

Behavior

  • Should tracking the active document be a toggle (like in GitLens where you can turn on/off tracking in the file history view on demand).
  • Should there be a way to trigger a specific file/folder/uri timelime to be shown (which would also turn off active file tracking) -- again similar to GitLens
  • Should the view support a refresh action that will drop timeline caches and re-request the timeline from all providers? (Hopefully we don't really need this)
  • Should we support filtering based on the source of the timeline items?
    /cc @jrieken

Refs: #83995

@eamodio eamodio added api plan-item VS Code - planned item for upcoming tree-views Extension tree view issues scm General SCM compound issues labels Nov 8, 2019
@eamodio eamodio added this to the November 2019 milestone Nov 8, 2019
@eamodio eamodio self-assigned this Nov 8, 2019
@eamodio eamodio modified the milestones: November 2019, December 2019 Dec 9, 2019
@eamodio
Copy link
Contributor Author

eamodio commented Jan 25, 2020

Latest Proposal

//#region eamodio - timeline: https://github.com/microsoft/vscode/issues/84297
export class TimelineItem {
/**
* 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).
*/
label: string;
/**
* Optional id for the timeline item. See [TreeItem.id](#TreeItem.id) for more details.
*/
id?: string;
/**
* The icon path or [ThemeIcon](#ThemeIcon) for the timeline item. See [TreeItem.iconPath](#TreeItem.iconPath) for more details.
*/
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.
*/
description?: string;
/**
* The tooltip text when you hover over the timeline item.
*/
detail?: string;
/**
* The [command](#Command) that should be executed when the timeline item is selected.
*/
command?: Command;
/**
* Context value of the timeline item. See [TreeItem.contextValue](#TreeItem.contextValue) for more details.
*/
contextValue?: string;
/**
* @param label A human-readable string describing the timeline item
* @param timestamp A timestamp (in milliseconds since 1 January 1970 00:00:00) for when the timeline item occurred
* @param source A human-readable string describing the source of the timeline item
*/
constructor(label: string, timestamp: number, source: string);
}
export interface TimelineProvider {
onDidChange?: Event<Uri | undefined>;
/**
* An identifier of the source of the timeline items. This can be used for filtering and/or overriding existing sources.
*/
source: string;
/**
* A human-readable string describing the source of the timeline items. This can be as the display label when filtering by sources.
*/
sourceDescription: string;
replaceable?: boolean;
/**
* Provide [timeline items](#TimelineItem) for a [Uri](#Uri).
*
* @param uri The 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.
*/
provideTimeline(uri: Uri, token: CancellationToken): ProviderResult<TimelineItem[]>;
}
export namespace workspace {
/**
* Register a timeline provider.
*
* Multiple providers can be registered. In that case, providers are asked in
* parallel and the results are merged. A failing provider (rejected promise or exception) will
* not cause a failure of the whole operation.
*
* @param selector A selector that defines the documents this provider is applicable to.
* @param provider A timeline provider.
* @return A [disposable](#Disposable) that unregisters this provider when being disposed.
*/
export function registerTimelineProvider(selector: DocumentSelector, provider: TimelineProvider): Disposable;
}
//#endregion
}

Notes

  • I am currently avoiding all the caching complexities outlined in the original post by making the TimelineService not provide any caching at all. So as the active document changes, new requests are made to all registered timeline providers.

Questions

  • Currently, a TimelineProvider can signal that it is replaceable, but I think I should probably remove that ability and instead just deal with replacing existing providers by filtering out sources.
  • Should we have the ability to show a timeline for a non-file Uri -- say a workspace, folder, or a git repo? -- Could be very powerful

TODO

  • Add support for the DocumentScheme when registering a TimelineProvider isn't currently hooked up -- need to move the code around to marshal DocumentScheme
  • Add support for custom commands/menus to be applied to timeline items (using the contextValue)
  • Add filtering support by source in the UI (and maybe backed by a setting)
  • Add paging support -- investigate some sort of cursor support (something that the provider can control)
  • Add ability to show a timeline for a specific file (say via a command on explorer files)
  • Add ability to "pin/freeze" a timeline from following the active editor

@lukaszpolowczyk
Copy link

@eamodio Will this API give access to click history in the document, selection history in the document?

I use this in my extension, but I have to remember the clicked places myself.
And I know that VSCode has a history undo function (Go> Back) but also a selection history.

@eamodio
Copy link
Contributor Author

eamodio commented Jan 27, 2020

@lukaszpolowczyk This API is more about allowing a source to provide timeline information for a given Uri (document for now, but probably folder and workspace in the future). And that provided information is then shown as a list in a new Timeline view. This will enable extensions (and core) to start providing different sources to the timeline -- which could be undo history, selection history, etc.

Although, in this first iteration we will be providing Git file history in the timeline.

@eamodio
Copy link
Contributor Author

eamodio commented Jan 27, 2020

Also for this iteration this will only be available in insiders and you will need to add the following to your settings.json: "timeline.showView": true

@gjsjohnmurray
Copy link
Contributor

@eamodio wrote in the TODO above:

Add filtering support by source in the UI (and maybe backed by a setting)

Maybe worth the API comment for source recommending that an extension contributing to a timeline uses their extension ID (i.e. <publisher>.<name> from their manifest) as source or as a prefix for it, perhaps separated by /. For example: acmecorp.dynamite or for greater granularity acmecorp.dynamite/major, acmecorp.dynamite/minor.

Though if the timeline can already obtain the ID of the extension implementing the TimelineProvider that gave it the items it won't be necessary to ask providers to use sources that are structured so as to avoid conflicts.

@eamodio
Copy link
Contributor Author

eamodio commented Jan 30, 2020

@gjsjohnmurray Yeah, I'm not sure if the source needs to be namespaced per-extension (though if we do end up wanting that, I would just do it internally). Currently with the replaceable property the desire is to be able to override a source with another one. But I still need to think more about that -- since I might just get rid of replaceable completely and rely solely on filtering (rather than overriding) and if so, then namespacing is probably appropriate.

@joaomoreno
Copy link
Member

@eamodio Since you're out on vacation, I'll move this forward again. 😆

@joaomoreno joaomoreno modified the milestones: August 2020, September 2020 Sep 7, 2020
@bpasero bpasero modified the milestones: September 2020, October 2020 Oct 2, 2020
@eamodio eamodio modified the milestones: October 2020, Backlog Oct 6, 2020
@InTheCloudDan
Copy link

This is just feedback from my very initial usage of the API, if detail supported MarkdownString similar to how the Treeview does now, for our use case it would allow us to show much more feature rich contextual info.

@eamodio
Copy link
Contributor Author

eamodio commented Jan 14, 2021

@InTheCloudDan Can you please open a new issue for that -- I think it is a great idea.

@gjsjohnmurray
Copy link
Contributor

@lszomoru can this be considered for finalization?

@JacksonKearl JacksonKearl self-assigned this Nov 11, 2021
@JacksonKearl
Copy link
Contributor

@gjsjohnmurray the remaining unknown is in dealing with workspace scoped timeline events. The timeline view would ideally have a way to present a full git log of the open repo(s), but right now it only works for single resources. My understanding is that we don't want to move forward with finalization until we have an answer for that.

@kraabrsg
Copy link

Hi, are there any plans to bring this into a release Version, we would eagerly like to use it.

Thanks!

@gjsjohnmurray
Copy link
Contributor

Ahead of finalization of this API maybe revisit #147304 which acknowledges it's currently exposing two undocumented TimelineOptions properties cacheResult and resetCache to extensions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api api-proposal feature-request Request for new features or functionality scm General SCM compound issues timeline Timeline view issues tree-views Extension tree view issues
Projects
None yet
Development

Successfully merging a pull request may close this issue.