diff --git a/extensions/git/package.json b/extensions/git/package.json index 0a7ebadcbe0..57e332a8d78 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -1958,7 +1958,7 @@ "scm/history/title": [ { "command": "git.fetchAll", - "group": "navigation@2", + "group": "navigation@999", "when": "scmProvider == git" } ], diff --git a/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts index 50440a3201f..4be940247bf 100644 --- a/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts @@ -61,6 +61,7 @@ import { compare } from '../../../../base/common/strings.js'; import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js'; import { getDefaultHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; +import { INotificationService } from '../../../../platform/notification/common/notification.js'; const PICK_REPOSITORY_ACTION_ID = 'workbench.scm.action.graph.pickRepository'; const PICK_HISTORY_ITEM_REFS_ACTION_ID = 'workbench.scm.action.graph.pickHistoryItemRefs'; @@ -185,6 +186,26 @@ registerAction2(class extends ViewAction { } }); +registerAction2(class extends ViewAction { + constructor() { + super({ + id: 'workbench.scm.action.graph.revealCurrentHistoryItem', + title: localize('goToCurrentHistoryItem', "Go to Current History Item"), + viewId: HISTORY_VIEW_PANE_ID, + f1: false, + icon: Codicon.target, + menu: { + id: MenuId.SCMHistoryTitle, + group: 'navigation', + order: 2 + } + }); + } + + async runInView(_: ServicesAccessor, view: SCMHistoryViewPane): Promise { + view.revealCurrentHistoryItem(); + } +}); registerAction2(class extends ViewAction { constructor() { @@ -652,7 +673,11 @@ class SCMHistoryTreeDataSource extends Disposable implements IAsyncDataSource { const repository = this.repository.get(); - if (!repository) { + const historyProvider = repository?.provider.historyProvider.get(); + + if (!repository || !historyProvider) { return []; } let state = this._state.get(repository); - const historyProvider = repository.provider.historyProvider.get(); - if (!historyProvider) { - return []; - } - - if (!state || state.loadMore) { - const existingHistoryItems = state?.items ?? []; + if (!state || state.loadMore !== false) { let historyItemRefs = state?.historyItemRefs; + const historyItems = state?.viewModels + .map(vm => vm.historyItemViewModel.historyItem) ?? []; if (!historyItemRefs) { const historyItemsFilter = this.historyItemsFilter.get(); @@ -781,28 +813,28 @@ class SCMHistoryViewModel extends Disposable { const limit = clamp(this._configurationService.getValue('scm.graph.pageSize'), 1, 1000); const historyItemRefIds = historyItemRefs.map(ref => ref.revision ?? ref.id); - const historyItems = await historyProvider.provideHistoryItems({ - historyItemRefs: historyItemRefIds, limit, skip: existingHistoryItems.length - }) ?? []; + do { + // Fetch the next page of history items + historyItems.push(...(await historyProvider.provideHistoryItems({ + historyItemRefs: historyItemRefIds, limit, skip: historyItems.length + }) ?? [])); + } while (typeof state?.loadMore === 'string' && !historyItems.find(item => item.id === state?.loadMore)); - state = { - historyItemRefs, - items: [...existingHistoryItems, ...historyItems], - loadMore: false - }; + // Create the color map + const colorMap = this._getGraphColorMap(historyItemRefs); + const viewModels = toISCMHistoryItemViewModelArray(historyItems, colorMap, historyProvider.historyItemRef.get()) + .map(historyItemViewModel => ({ + repository, + historyItemViewModel, + type: 'historyItemViewModel' + }) satisfies SCMHistoryItemViewModelTreeElement); + + state = { historyItemRefs, viewModels, loadMore: false }; this._state.set(repository, state); } - // Create the color map - const colorMap = this._getGraphColorMap(state.historyItemRefs); - - return toISCMHistoryItemViewModelArray(state.items, colorMap, historyProvider.historyItemRef.get()) - .map(historyItemViewModel => ({ - repository, - historyItemViewModel, - type: 'historyItemViewModel' - }) satisfies SCMHistoryItemViewModelTreeElement); + return state.viewModels; } setRepository(repository: ISCMRepository | 'auto'): void { @@ -1046,6 +1078,7 @@ export class SCMHistoryViewPane extends ViewPane { options: IViewPaneOptions, @ICommandService private readonly _commandService: ICommandService, @IInstantiationService private readonly _instantiationService: IInstantiationService, + @INotificationService private readonly _notificationService: INotificationService, @IProgressService private readonly _progressService: IProgressService, @IConfigurationService configurationService: IConfigurationService, @IContextMenuService contextMenuService: IContextMenuService, @@ -1266,12 +1299,12 @@ export class SCMHistoryViewPane extends ViewPane { async pickHistoryItemRef(): Promise { const repository = this._treeViewModel.repository.get(); const historyProvider = repository?.provider.historyProvider.get(); - const historyItemsFilter = this._treeViewModel.historyItemsFilter.get(); if (!historyProvider) { return; } + const historyItemsFilter = this._treeViewModel.historyItemsFilter.get(); const picker = this._instantiationService.createInstance(HistoryItemRefPicker, historyProvider, historyItemsFilter); const result = await picker.pickHistoryItemRef(); @@ -1280,6 +1313,58 @@ export class SCMHistoryViewPane extends ViewPane { } } + async revealCurrentHistoryItem(): Promise { + const repository = this._treeViewModel.repository.get(); + const historyProvider = repository?.provider.historyProvider.get(); + const historyItemRef = historyProvider?.historyItemRef.get(); + + if (!repository || !historyItemRef?.revision) { + return; + } + + const historyItemFilter = this._treeViewModel.historyItemsFilter.get(); + + // Filter is `all`, `auto` or it contains the current history item + if (Array.isArray(historyItemFilter) && + !historyItemFilter.find(r => r.id === historyItemRef.id)) { + this._notificationService.info(localize('scmGraphViewRevealCurrentHistoryItem', "The current history item is not present in the source control graph. Please use the history item references picker to expand the set of history items in the graph.")); + return; + } + + const revealTreeNode = (): boolean => { + const state = this._treeViewModel.getRepositoryState(); + if (!state) { + return false; + } + + const historyItemViewModel = state.viewModels.find(item => + item.historyItemViewModel.historyItem.id === historyItemRef.revision); + + if (historyItemViewModel && this._tree.hasNode(historyItemViewModel)) { + this._tree.reveal(historyItemViewModel, 0.5); + + this._tree.setSelection([historyItemViewModel]); + this._tree.setFocus([historyItemViewModel]); + return true; + } + + return false; + }; + + if (revealTreeNode()) { + return; + } + + // Fetch current history item + this._treeViewModel.setLoadMore(repository, historyItemRef.revision); + + // Update the tree + await this._updateChildren(); + + // Reveal node + revealTreeNode(); + } + private _createTree(container: HTMLElement): void { this._treeIdentityProvider = new SCMHistoryTreeIdentityProvider();