From c9c04b012a16c30508a5ab1df2599ab18dea793a Mon Sep 17 00:00:00 2001 From: Raashish Aggarwal <94279692+raashish1601@users.noreply.github.com> Date: Thu, 26 Mar 2026 21:56:41 +0530 Subject: [PATCH] Preserve PR diff scroll position --- src/commands.ts | 33 +++++++++++++++++++++- src/test/view/fileChangeNode.test.ts | 41 +++++++++++++++++++++++++++ src/view/treeNodes/fileChangeNode.ts | 42 ++++++++++++++++++---------- 3 files changed, 101 insertions(+), 15 deletions(-) create mode 100644 src/test/view/fileChangeNode.test.ts diff --git a/src/commands.ts b/src/commands.ts index 796a9dabcb..50f7d07c7a 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -46,10 +46,12 @@ import { CategoryTreeNode } from './view/treeNodes/categoryNode'; import { CommitNode } from './view/treeNodes/commitNode'; import { FileChangeNode, + getPullRequestDiffViewState, GitFileChangeNode, InMemFileChangeNode, openFileCommand, RemoteFileChangeNode, + storePullRequestDiffViewState, } from './view/treeNodes/fileChangeNode'; import { PRNode } from './view/treeNodes/pullRequestNode'; import { RepositoryChangesNode } from './view/treeNodes/repositoryChangesNode'; @@ -99,6 +101,32 @@ export async function openDescription( } } +function cacheActivePullRequestDiffViewState(): void { + const input = vscode.window.tabGroups.activeTabGroup.activeTab?.input; + const editor = vscode.window.activeTextEditor; + if (!(input instanceof vscode.TabInputTextDiff) || !editor || editor.document.uri.toString() !== input.modified.toString()) { + return; + } + + const visibleRange = editor.visibleRanges[0]; + if (visibleRange) { + storePullRequestDiffViewState(input.original, input.modified, visibleRange); + } +} + +function restoreActivePullRequestDiffViewState(): void { + const input = vscode.window.tabGroups.activeTabGroup.activeTab?.input; + const editor = vscode.window.activeTextEditor; + if (!(input instanceof vscode.TabInputTextDiff) || !editor || editor.document.uri.toString() !== input.modified.toString()) { + return; + } + + const visibleRange = getPullRequestDiffViewState(input.original, input.modified); + if (visibleRange) { + editor.revealRange(visibleRange, vscode.TextEditorRevealType.AtTop); + } +} + export async function openPullRequestOnGitHub(e: PRNode | RepositoryChangesNode | IssueModel | NotificationTreeItem, telemetry: ITelemetry) { if (e instanceof PRNode || e instanceof RepositoryChangesNode) { vscode.commands.executeCommand('vscode.open', vscode.Uri.parse(e.pullRequestModel.html_url)); @@ -308,7 +336,10 @@ export function registerCommands( if (!folderManager) { return; } - return fileChangeNode.openDiff(folderManager); + cacheActivePullRequestDiffViewState(); + await fileChangeNode.openDiff(folderManager); + restoreActivePullRequestDiffViewState(); + return; } else if (fileChangeNode || vscode.window.activeTextEditor) { const editor = fileChangeNode instanceof vscode.Uri ? vscode.window.visibleTextEditors.find(editor => editor.document.uri.toString() === fileChangeNode.toString())! : vscode.window.activeTextEditor!; const visibleRanges = editor.visibleRanges; diff --git a/src/test/view/fileChangeNode.test.ts b/src/test/view/fileChangeNode.test.ts new file mode 100644 index 0000000000..624d7a2f30 --- /dev/null +++ b/src/test/view/fileChangeNode.test.ts @@ -0,0 +1,41 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { strict as assert } from 'assert'; +import * as vscode from 'vscode'; +import { clearPullRequestDiffViewStates, getPullRequestDiffViewState, storePullRequestDiffViewState } from '../../view/treeNodes/fileChangeNode'; + +describe('fileChangeNode diff view state', function () { + beforeEach(function () { + clearPullRequestDiffViewStates(); + }); + + afterEach(function () { + clearPullRequestDiffViewStates(); + }); + + it('stores diff view state per original and modified uri pair', function () { + const original = vscode.Uri.parse('review:/repo/file.ts?base=true'); + const modified = vscode.Uri.parse('review:/repo/file.ts?base=false'); + const range = new vscode.Range(10, 0, 25, 0); + + storePullRequestDiffViewState(original, modified, range); + + assert.deepStrictEqual(getPullRequestDiffViewState(original, modified), range); + assert.strictEqual(getPullRequestDiffViewState(original, vscode.Uri.parse('review:/repo/file.ts?base=false&other=true')), undefined); + }); + + it('overwrites the saved state for the same diff pair', function () { + const original = vscode.Uri.parse('review:/repo/file.ts?base=true'); + const modified = vscode.Uri.parse('review:/repo/file.ts?base=false'); + const firstRange = new vscode.Range(1, 0, 5, 0); + const secondRange = new vscode.Range(20, 0, 30, 0); + + storePullRequestDiffViewState(original, modified, firstRange); + storePullRequestDiffViewState(original, modified, secondRange); + + assert.deepStrictEqual(getPullRequestDiffViewState(original, modified), secondRange); + }); +}); diff --git a/src/view/treeNodes/fileChangeNode.ts b/src/view/treeNodes/fileChangeNode.ts index 4e1e8ac0c6..c7625320d3 100644 --- a/src/view/treeNodes/fileChangeNode.ts +++ b/src/view/treeNodes/fileChangeNode.ts @@ -15,6 +15,24 @@ import { IResolvedPullRequestModel, PullRequestModel } from '../../github/pullRe import { FileChangeModel, GitFileChangeModel, InMemFileChangeModel, RemoteFileChangeModel } from '../fileChangeModel'; import { TreeNode, TreeNodeParent } from './treeNode'; +const pullRequestDiffViewStates = new Map(); + +export function getPullRequestDiffViewStateKey(original: vscode.Uri, modified: vscode.Uri): string { + return `${original.toString()}::${modified.toString()}`; +} + +export function storePullRequestDiffViewState(original: vscode.Uri, modified: vscode.Uri, visibleRange: vscode.Range): void { + pullRequestDiffViewStates.set(getPullRequestDiffViewStateKey(original, modified), visibleRange); +} + +export function getPullRequestDiffViewState(original: vscode.Uri, modified: vscode.Uri): vscode.Range | undefined { + return pullRequestDiffViewStates.get(getPullRequestDiffViewStateKey(original, modified)); +} + +export function clearPullRequestDiffViewStates(): void { + pullRequestDiffViewStates.clear(); +} + export function openFileCommand(uri: vscode.Uri, inputOpts: vscode.TextDocumentShowOptions = {}): vscode.Command { const activeTextEditor = vscode.window.activeTextEditor; const opts = { @@ -294,13 +312,11 @@ export class InMemFileChangeNode extends FileChangeNode implements vscode.TreeIt if (this.status === GitChangeType.ADD) { this.command = openFileCommand(this.changeModel.filePath); } else { - this.command = await openDiffCommand( - this.folderRepositoryManager, - this.changeModel.parentFilePath, - this.changeModel.filePath, - undefined, - this.changeModel.status, - ); + this.command = { + command: 'pr.openDiffView', + arguments: [this], + title: 'Open Changed File in pull request', + }; } } } @@ -408,13 +424,11 @@ export class GitFileChangeNode extends FileChangeNode implements vscode.TreeItem } else { const openDiff = vscode.workspace.getConfiguration(GIT, this.pullRequestManager.repository.rootUri).get(OPEN_DIFF_ON_CLICK, true); if (openDiff && this.status !== GitChangeType.ADD) { - this.command = await openDiffCommand( - this.pullRequestManager, - this.changeModel.parentFilePath, - this.changeModel.filePath, - this.opts, - this.status, - ); + this.command = { + command: 'pr.openDiffView', + arguments: [this], + title: 'Open Changed File in pull request', + }; } else { this.command = this.openFileCommand(); }