Open eamodio opened 5 years ago
TimelineService
not provide any caching at all. So as the active document changes, new requests are made to all registered timeline providers.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.DocumentScheme
when registering a TimelineProvider
isn't currently hooked up -- need to move the code around to marshal DocumentScheme
contextValue
)source
in the UI (and maybe backed by a setting)@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.
@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.
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
@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 source
s that are structured so as to avoid conflicts.
@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.
@amelio-vazquez-reina asks if this could be used to support a tree based undo history https://github.com/microsoft/vscode/issues/20889#issuecomment-583194444:
Could the the new Timeline view be used to support this feature? Or does it only support "path graphs" (aka "linear graphs"), i.e. graphs where nodes can have at most one predecessor and at most one successor?
Note that the "undo graph" (aka "history graph" aka "undo branches") is usually at least a tree, if not a general graph (if we assume a graph where any nodes with the same text collapse into one node)
@Zalastax @amelio-vazquez-reina Currently the timeline is linear, but we are thinking about allowing each linear node to expand to contain a set of child nodes. Although the current thinking is that there will only be 1 level of children.
For example, if you were to show the Git history of a folder, then each linear item would be a commit, which would expand to show the set of files within that commit.
Thanks @eamodio - What would you say is the main argument against supporting history trees in the Timeline view? is it the complexity and effort that it may require to do so while properly delivering a great / easy / intuitive UX for the user? Or are there any other reasons?
In my mind, there are good UX designs that could accommodate tree views and make them optional if needed. In case it helps, below is a screenshot and animation of a similar feature (for navigating the full undo history of a file) in Emacs:
Description of the animation: The video tries to be self-explanatory, but just in case, here's a detailed description. The user starts typing text on a buffer (editor in VScode), then undoing some of the typing, and then typing text again. At a specific point in time the user invokes a command called "undo-tree" (equivalent to our Timeline view) which splits the screen in half and shows:
Once the undo tree is visible, the user switches cursor focus to the tree in the lower half, and the user navigates up and down the tree (i.e. history of the original file) via keyboard shortcuts. As the user is visiting different nodes, the original file (at the top) is updating its contents to reflect the state of the file corresponding to that node, and at any point in time, the user can switch focus to the original file in the top half, and continue editing from that state.
Note also that when the focus is on the tree, the user can switch navigation branches in the tree from a node with multiple children. Doing so doesn't change the "current node" where the user is sitting, it just simply enables a different path the user can take via up/down.
Something that is not shown in this video is that the undo tree is not persisted anywhere (it's actually lost when a file is closed). But that's ok. The undo history of a file in most editors (VSCode included?) isn't really saved, since it only refers to an "editing session" while the user has both the editor and file open.
I can't say there is a specific argument against any usage, but we do want to keep things consistent and intuitive from a UX perspective, and avoid unnecessary complexity (in both the UI/UX as well as the API). We also want to make sure the Timeline view is accessible and not overwhelming especially when mixing different sources. At the same time, we are certainly open to evolving the UI/UX/API in response to new valuable use-cases.
For history trees, how would you envision that would look and behave? What would need to change to support it?
Also I'm sure I'm missing something, but I don't understand what you are trying to show in the video example.
Thanks @eamodio Very helpful.
For history trees, how would you envision that would look and behave? What would need to change to support it?
Sorry the gif I uploaded above was incomplete. I have now fixed it in my last comment above and included a long description of the video to make it all more self-contained / clear. I hope that answers the question! Please let me know otherwise. Thanks again.
Good idea for such a feature ! I made my own tree in my extension local-history. I'll contribute to this timeline view...
This is a nice feature. Not ran into any issues using it for svn commit history
Here is the latest proposal:
TimelineOptions
is now much simpler:
export interface TimelineOptions {
/**
* A provider-defined cursor specifying the starting point of the timeline items that should be returned.
*/
cursor?: string;
/**
* An optional maximum number timeline items or the all timeline items newer (inclusive) than the timestamp or id that should be returned.
* If `undefined` all timeline items should be returned.
*/
limit?: number | { timestamp: number; id?: string };
}
These are the usage combinations:
{ limit: number }
— to get the first page (most recent){ limit: { timestamp: number; id?: string }
— to get the most recent items, up to a set timeline item (or timestamp) -- we use this for providers with mutable histories (think git -- reset, rebase, etc), so when they tell us to update the timeline we can ask for all our current visible data{ cursor: string, limit: number }
— to get the next page of data starting at a specific point (cursor){ }
— to get all items in one shot (no paging){ cursor: string }
— to get all items in one shot starting from a specific point (we don't have to use this, since we can just use the above to get everything again).I've also simplified the paging
property of the Timeline
:
export interface Timeline {
readonly paging?: {
/**
* A provider-defined cursor specifying the starting point of timeline items which are after the ones returned.
* Use `undefined` to signal that there are no more items to be returned.
*/
readonly cursor: string | undefined;
}
/**
* An array of [timeline items](#TimelineItem).
*/
readonly items: readonly TimelineItem[];
}
Now you only return paging
if the results are paged, and you must either return a cursor to get the next set of items or undefined
if there are no more items.
And if you have ideas on other types of information you'd like to see in this view, let us know!
This is really good stuff. This is probably not the point of this feature but it would be great to see this developing over time into full commit history (like gitk) and/or history navigation (like switching back and forth between revisions of a file (+ blame (+ comment))).
Even without those it's a great stuff, one less reason to switch to terminal, great job.
When will this API be moved from proposed?
Mentioned over here https://github.com/microsoft/vscode/issues/94155#issuecomment-607636707 but putting it in the main api-proposal
too for visibility:
I think perhaps this would be easier to implement if it were like:
interface TimelineProvider {
/* existing properties */
// Get most recent `items` timelime items, starting after `start` if defined
provideTimeline(uri: Uri, options: TimelineOptions, token: CancellationToken, items: number, start?: string): ProviderResult<Timeline>
// Refresh items up to and including `cursor`
updateTimeline?(uri: Uri, options: TimelineOptions, token: CancellationToken, cursor: string): ProviderResult<Timeline>
// Not sure what this does tbh. Maybe not needed at all?
fetchEntireTimeline?(...)
}
This way extensions can implement a minimal subset of the functionality and we can detect that in core and work around it. For instance, providers can minimally implement just provideTimeline
. In that case, if they trigger a refresh the user will just get a single page back. If they go on to implement updateTimeline
, then the user can get their full set of results back. I'm not really sure what the use case is for fetchEntireTimeline
, but similar for that.
Right now when implementing one needs to perform a lot of runtime type checking and it's not clear to the implementor what the different cursor overloads are. If the method names made clear what the results they should return, rather than one needing to know how to associate cursor types with runtime behaviour, it'd be a lot more clear how to implement.
@JohnstonCode It might be this iteration, but more likely the next one.
@JacksonKearl I'm still really not in favor of something like this -- to me it just actually increases the complexity and when fully implementing it I would just have all those methods delegate down to the same underlying method.
The way it is now, a Timeline provider is pretty much in control over what they implement. If they don't want to implement paging or cursor, they can just ignore them and never return a paging
object in the result. And the limit
properties are guidelines, not absolute, so even if we ask for 20 items, and you always return all of your items, that is fine -- we will handle it appropriately.
I am thinking about a way to ease some of the confusion with cursor
and limit
. Because at a basic level, the cursor
defines a starting point, and limit
defines an ending point. So hopefully with some minor terminology changes, or slight shape tweaks this will become much clearer.
📢 Anyone currently using/trying this API or planning on it please join us in the April Extension Authors call: https://github.com/microsoft/vscode/issues/96927. We will be talking about the Timeline API and would love your feedback as we plan to finalize the API.
Hi - I'm implementing this for perforce - is there a way to identify that a user has explicitly requested a refresh?
I don't really want to hit the server for the file log every time we switch tabs (hence I've implemented a cache), but I would think if someone has pressed the refresh button then we would want to force an update to the state.
For example, if another person has submitted the file the user is working on, this is totally asynchronous in perforce and we don't really have a way of detecting that this has happened, as a user I wouldn't necessarily expect to immediately see that revision, but I would want to be able to click on the refresh button and see that new revision appear.
(as there's not really support for paging in perforce, just a limit from the latest, I implemented it so that it only gets limit results on the initial call, to reduce overhead of opening a file, then on any other call it gets the rest of the results. It did take me a while to understand the concepts of cursor and limit and how they would be used - without looking at the internal code it's rather opaque. so the docs will need some good examples of exactly when each combination of TimelineOptions
is used)
p.s. I suspect you already know this but the default null
setting for timeline.pageSize
shows an error in the settings view, and when you click to fix it with json, it automatically sets it to zero which results in an empty timeline view
@eamodio Since you're out on vacation, I'll move this forward.
@eamodio Since you're out on vacation, I'll move this forward again. 😆
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.
@InTheCloudDan Can you please open a new issue for that -- I think it is a great idea.
@lszomoru can this be considered for finalization?
@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.
Hi, are there any plans to bring this into a release Version, we would eagerly like to use it.
Thanks!
Ahead of finalization of this API maybe revisit #147304 which acknowledges it's currently exposing two undocumented TimelineOptions
properties cacheResult
and resetCache
to extensions.
Any update, a timeline API would be helpful.
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
A timeline source (e.g. an extension) registers a
TimelineProvider
for a set of documents. VS Code will then call the all the registeredTimelineProvider.provideTimeline
callbacks (in parallel) when the active editor changes and matches those registrations. The results will be merged into a unified set ordered by theTimelineItem.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 theonDidChange
event.Questions & Challenges
API
provideTimeline
againTimelimeChangeEvent
event provide a way to signal that an individual item(s) should be updated?onDidAdd
?id
s for the tree items, since they can come from multiple extensions. Probably should ensure a prefix or something per provider for any providedid
s (or not give control over theid
s at all)Behavior
source
of the timeline items? /cc @jriekenRefs: https://github.com/microsoft/vscode/issues/83995