From 605274a5679e80915fe5d40bb4efdedd8c19ebf3 Mon Sep 17 00:00:00 2001 From: soreavis <263610811+soreavis@users.noreply.github.com> Date: Sat, 18 Jul 2026 21:10:46 +0200 Subject: [PATCH] Git - resolve the active notebook for Open Changes / Open File The keybinding path of git.openChange, git.openFile and git.openHEADFile resolves the target through getSCMResource, which only considered window.activeTextEditor. That is undefined when a notebook editor is active, so the commands silently did nothing for notebooks while the editor-title buttons and SCM context menu (which pass the resource) kept working. Resolve the notebook uri from window.activeNotebookEditor first, and fall back to a notebook diff editor's modified resource so Open File can toggle back from an open notebook diff, matching the text-editor behavior. Fixes #176401 --- extensions/git/src/commands.ts | 10 +++++++-- extensions/git/src/test/smoke.test.ts | 29 ++++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 51cbef08d2e68..02507896f3d82 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -5,7 +5,7 @@ import * as os from 'os'; import * as path from 'path'; -import { Command, commands, Disposable, MessageOptions, Position, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, Selection, TextDocumentContentProvider, InputBoxValidationSeverity, TabInputText, TabInputTextMerge, QuickPickItemKind, TextDocument, LogOutputChannel, l10n, Memento, UIKind, QuickInputButton, ThemeIcon, SourceControlHistoryItem, SourceControl, InputBoxValidationMessage, Tab, TabInputNotebook, QuickInputButtonLocation, languages, SourceControlArtifact, ProgressLocation } from 'vscode'; +import { Command, commands, Disposable, MessageOptions, Position, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, Selection, TextDocumentContentProvider, InputBoxValidationSeverity, TabInputText, TabInputTextMerge, QuickPickItemKind, TextDocument, LogOutputChannel, l10n, Memento, UIKind, QuickInputButton, ThemeIcon, SourceControlHistoryItem, SourceControl, InputBoxValidationMessage, Tab, TabInputNotebook, TabInputNotebookDiff, QuickInputButtonLocation, languages, SourceControlArtifact, ProgressLocation } from 'vscode'; import TelemetryReporter from '@vscode/extension-telemetry'; import type { CommitOptions, RemoteSourcePublisher, Remote, Branch, Ref } from './api/git'; import { ForcePushMode, GitErrorCodes, RefType, Status } from './api/git.constants'; @@ -5700,7 +5700,13 @@ export class CommandCenter { } private getSCMResource(uri?: Uri): Resource | undefined { - uri = uri ? uri : (window.activeTextEditor && window.activeTextEditor.document.uri); + uri = uri ?? window.activeNotebookEditor?.notebook.uri ?? window.activeTextEditor?.document.uri; + + // A notebook diff editor is neither the active text nor notebook editor, so + // fall back to its modified resource. + if (!uri && window.tabGroups.activeTabGroup.activeTab?.input instanceof TabInputNotebookDiff) { + uri = window.tabGroups.activeTabGroup.activeTab.input.modified; + } this.logger.debug(`[CommandCenter][getSCMResource] git.getSCMResource.uri: ${uri && uri.toString()}`); diff --git a/extensions/git/src/test/smoke.test.ts b/extensions/git/src/test/smoke.test.ts index c2870a2631ee3..6c5a97427b4a9 100644 --- a/extensions/git/src/test/smoke.test.ts +++ b/extensions/git/src/test/smoke.test.ts @@ -5,7 +5,7 @@ import 'mocha'; import assert from 'assert'; -import { workspace, commands, window, Uri, WorkspaceEdit, Range, TextDocument, extensions, TabInputTextDiff } from 'vscode'; +import { workspace, commands, window, Uri, WorkspaceEdit, Range, TextDocument, extensions, TabInputTextDiff, TabInputNotebook, TabInputNotebookDiff } from 'vscode'; import * as cp from 'child_process'; import * as fs from 'fs'; import * as path from 'path'; @@ -147,6 +147,33 @@ suite('git smoke test', function () { assert.strictEqual(repository.state.indexChanges.length, 0); }); + test('opens notebook diff and file from active notebook editor', async function () { + const committed = JSON.stringify({ cells: [{ cell_type: 'code', source: ['x = 1'], metadata: {}, outputs: [], execution_count: null }], metadata: {}, nbformat: 4, nbformat_minor: 5 }); + fs.writeFileSync(file('notebook.ipynb'), committed); + await repository.add([file('notebook.ipynb')]); + await repository.commit('add notebook'); + + fs.writeFileSync(file('notebook.ipynb'), committed.replace('x = 1', 'x = 2')); + await repository.status(); + + try { + const notebook = await workspace.openNotebookDocument(uri('notebook.ipynb')); + await window.showNotebookDocument(notebook); + + // git.openChange without an argument resolves the resource from the active notebook editor + await commands.executeCommand('git.openChange'); + assert(window.tabGroups.activeTabGroup.activeTab?.input instanceof TabInputNotebookDiff); + + // git.openFile toggles back to the notebook from the active notebook diff editor + await commands.executeCommand('git.openFile'); + assert(window.tabGroups.activeTabGroup.activeTab?.input instanceof TabInputNotebook); + } finally { + // Restore the committed content so the following tests start from a clean tree + fs.writeFileSync(file('notebook.ipynb'), committed); + await repository.status(); + } + }); + test('rename/delete conflict', async function () { await commands.executeCommand('workbench.view.scm');