diff --git a/package.json b/package.json index 8d4bd20..dd259ba 100644 --- a/package.json +++ b/package.json @@ -4295,6 +4295,13 @@ "scope": "window", "order": 120 }, + "gitlens.experimental.openChangesInMultiDiffEditor": { + "type": "boolean", + "default": false, + "markdownDescription": "(Experimental) Specifies whether to open multiple changes in VS Code's experimental multi-diff editor (single tab) or in individual diff editors (multiple tabs)", + "scope": "window", + "order": 120 + }, "gitlens.advanced.useSymmetricDifferenceNotation": { "deprecationMessage": "Deprecated. This setting is no longer used", "markdownDescription": "Deprecated. This setting is no longer used" diff --git a/src/commands/diffWith.ts b/src/commands/diffWith.ts index 54148cc..694eb14 100644 --- a/src/commands/diffWith.ts +++ b/src/commands/diffWith.ts @@ -58,6 +58,7 @@ export class DiffWithCommand extends Command { args = { repoPath: commit.repoPath, lhs: { + // Don't need to worry about verifying the previous sha, as the DiffWith command will sha: commit.unresolvedPreviousSha, uri: commit.file.originalUri ?? commit.file.uri, }, diff --git a/src/config.ts b/src/config.ts index f3a2fc3..8edb367 100644 --- a/src/config.ts +++ b/src/config.ts @@ -74,6 +74,7 @@ export interface Config { readonly experimental: { readonly generateCommitMessagePrompt: string; readonly nativeGit: boolean; + readonly openChangesInMultiDiffEditor: boolean; }; readonly fileAnnotations: { readonly command: string | null; diff --git a/src/constants.ts b/src/constants.ts index eb45521..4f9b3d6 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -638,6 +638,7 @@ export type CoreCommands = | 'vscode.open' | 'vscode.openFolder' | 'vscode.openWith' + | 'vscode.changes' | 'vscode.diff' | 'vscode.executeCodeLensProvider' | 'vscode.executeDocumentSymbolProvider' diff --git a/src/git/actions/commit.ts b/src/git/actions/commit.ts index 600f9be..6421cf2 100644 --- a/src/git/actions/commit.ts +++ b/src/git/actions/commit.ts @@ -9,18 +9,19 @@ import type { OpenWorkingFileCommandArgs } from '../../commands/openWorkingFile' import type { ShowQuickCommitCommandArgs } from '../../commands/showQuickCommit'; import type { ShowQuickCommitFileCommandArgs } from '../../commands/showQuickCommitFile'; import type { FileAnnotationType } from '../../config'; -import { Commands } from '../../constants'; +import { Commands, GlyphChars } from '../../constants'; import { Container } from '../../container'; import type { ShowInCommitGraphCommandArgs } from '../../plus/webviews/graph/protocol'; import { executeCommand, executeEditorCommand } from '../../system/command'; -import { findOrOpenEditor, findOrOpenEditors } from '../../system/utils'; +import { configuration } from '../../system/configuration'; +import { findOrOpenEditor, findOrOpenEditors, openChangesEditor } from '../../system/utils'; import { GitUri } from '../gitUri'; import type { GitCommit } from '../models/commit'; import { isCommit } from '../models/commit'; import { deletedOrMissing } from '../models/constants'; import type { GitFile } from '../models/file'; import type { GitRevisionReference } from '../models/reference'; -import { getReferenceFromRevision, isUncommitted, isUncommittedStaged } from '../models/reference'; +import { getReferenceFromRevision, isUncommitted, isUncommittedStaged, shortenRevision } from '../models/reference'; type Ref = { repoPath: string; ref: string }; type RefRange = { repoPath: string; rhs: string; lhs: string }; @@ -92,8 +93,11 @@ export async function openAllChanges( refsOrOptions: RefRange | TextDocumentShowOptions | undefined, options?: TextDocumentShowOptions, ) { + const useChangesEditor = configuration.get('experimental.openChangesInMultiDiffEditor'); + let files; let refs: RefRange | undefined; + let title; if (isCommit(commitOrFiles)) { if (commitOrFiles.files == null) { await commitOrFiles.ensureFullDetails(); @@ -103,17 +107,25 @@ export async function openAllChanges( refs = { repoPath: commitOrFiles.repoPath, rhs: commitOrFiles.sha, - // Don't need to worry about verifying the previous sha, as the DiffWith command will - lhs: commitOrFiles.unresolvedPreviousSha, + lhs: + commitOrFiles.resolvedPreviousSha ?? + (useChangesEditor + ? (await commitOrFiles.getPreviousSha()) ?? commitOrFiles.unresolvedPreviousSha + : // Don't need to worry about verifying the previous sha, as the DiffWith command will + commitOrFiles.unresolvedPreviousSha), }; options = refsOrOptions as TextDocumentShowOptions | undefined; + title = `Changes in ${shortenRevision(refs.rhs)}`; } else { files = commitOrFiles; refs = refsOrOptions as RefRange; + title = `Changes between ${shortenRevision(refs.lhs)} ${GlyphChars.ArrowLeftRightLong} ${shortenRevision( + refs.rhs, + )}`; } - if (files.length > 10) { + if (files.length > (useChangesEditor ? 50 : 10)) { const result = await window.showWarningMessage( `Are you sure you want to open the changes for all ${files.length} files?`, { title: 'Yes' }, @@ -124,9 +136,28 @@ export async function openAllChanges( options = { preserveFocus: true, preview: false, ...options }; + if (!useChangesEditor) { + for (const file of files) { + await openChanges(file, refs, options); + } + return; + } + + const { git } = Container.instance; + + const resources: Parameters[0] = []; for (const file of files) { - await openChanges(file, refs, options); + const rhs = + file.status === 'D' ? undefined : (await git.getBestRevisionUri(refs.repoPath, file.path, refs.rhs))!; + const lhs = + file.status === 'A' + ? undefined + : (await git.getBestRevisionUri(refs.repoPath, file.originalPath ?? file.path, refs.lhs))!; + const uri = (file.status === 'D' ? lhs : rhs) ?? GitUri.fromFile(file, refs.repoPath); + resources.push({ uri: uri, lhs: lhs, rhs: rhs }); } + + await openChangesEditor(resources, title, options); } export async function openAllChangesWithDiffTool(commit: GitCommit): Promise; @@ -191,7 +222,9 @@ export async function openAllChangesWithWorking( ref = refOrOptions as Ref; } - if (files.length > 10) { + const useChangesEditor = configuration.get('experimental.openChangesInMultiDiffEditor'); + + if (files.length > (useChangesEditor ? 50 : 10)) { const result = await window.showWarningMessage( `Are you sure you want to open the changes for all ${files.length} files?`, { title: 'Yes' }, @@ -202,9 +235,37 @@ export async function openAllChangesWithWorking( options = { preserveFocus: true, preview: false, ...options }; + if (!useChangesEditor) { + for (const file of files) { + await openChangesWithWorking(file, ref, options); + } + return; + } + + const { git } = Container.instance; + + const resources: Parameters[0] = []; for (const file of files) { - await openChangesWithWorking(file, ref, options); + const rhs = + file.status === 'D' + ? undefined + : await git.getWorkingUri( + ref.repoPath, + (await git.getBestRevisionUri(ref.repoPath, file.path, ref.ref))!, + ); + const lhs = + file.status === 'A' + ? undefined + : (await git.getBestRevisionUri(ref.repoPath, file.originalPath ?? file.path, ref.ref))!; + const uri = (file.status === 'D' ? lhs : rhs) ?? GitUri.fromFile(file, ref.repoPath); + resources.push({ uri: uri, lhs: lhs, rhs: rhs }); } + + await openChangesEditor( + resources, + `Changes between ${shortenRevision(ref.ref)} ${GlyphChars.ArrowLeftRightLong} Working Tree`, + options, + ); } export async function openChanges( diff --git a/src/git/models/commit.ts b/src/git/models/commit.ts index a2d2eb0..b5a9589 100644 --- a/src/git/models/commit.ts +++ b/src/git/models/commit.ts @@ -173,6 +173,10 @@ export class GitCommit implements GitRevisionReference { } private _resolvedPreviousSha: string | undefined; + get resolvedPreviousSha(): string | undefined { + return this._resolvedPreviousSha; + } + get unresolvedPreviousSha(): string { const previousSha = this._resolvedPreviousSha ?? @@ -517,7 +521,10 @@ export class GitCommit implements GitRevisionReference { } const parent = this.parents[0]; - if (parent != null && isSha(parent)) return parent; + if (parent != null && isSha(parent)) { + this._resolvedPreviousSha = parent; + return parent; + } const sha = await this.container.git.resolveReference( this.repoPath, diff --git a/src/system/command.ts b/src/system/command.ts index 9bf2480..f3aa17b 100644 --- a/src/system/command.ts +++ b/src/system/command.ts @@ -96,6 +96,7 @@ export function executeCoreCommand( if ( command != 'setContext' && command !== 'vscode.executeDocumentSymbolProvider' && + command !== 'vscode.changes' && command !== 'vscode.diff' && command !== 'vscode.open' ) { diff --git a/src/system/utils.ts b/src/system/utils.ts index 8e13eb7..bf4235b 100644 --- a/src/system/utils.ts +++ b/src/system/utils.ts @@ -165,6 +165,22 @@ export async function openEditor( } } +export async function openChangesEditor( + resources: { uri: Uri; lhs: Uri | undefined; rhs: Uri | undefined }[], + title: string, + _options?: TextDocumentShowOptions, +): Promise { + try { + await executeCoreCommand( + 'vscode.changes', + title, + resources.map(r => [r.uri, r.lhs, r.rhs]), + ); + } catch (ex) { + Logger.error(ex, 'openChangesEditor'); + } +} + export async function openDiffEditor( lhs: Uri, rhs: Uri,