diff --git a/extensions/git/src/blame.ts b/extensions/git/src/blame.ts index b5d10b989fb..9c6bc5ae761 100644 --- a/extensions/git/src/blame.ts +++ b/extensions/git/src/blame.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { DecorationOptions, l10n, Position, Range, TextEditor, TextEditorChange, TextEditorDecorationType, TextEditorChangeKind, ThemeColor, Uri, window, workspace, EventEmitter, ConfigurationChangeEvent, StatusBarItem, StatusBarAlignment, Command, MarkdownString } from 'vscode'; +import { DecorationOptions, l10n, Position, Range, TextEditor, TextEditorChange, TextEditorDecorationType, TextEditorChangeKind, ThemeColor, Uri, window, workspace, EventEmitter, ConfigurationChangeEvent, StatusBarItem, StatusBarAlignment, Command, MarkdownString, TextEditorDiffInformation } from 'vscode'; import { Model } from './model'; import { dispose, fromNow, IDisposable } from './util'; import { Repository } from './repository'; @@ -63,22 +63,6 @@ type BlameInformationTemplateTokens = { readonly authorDateAgo: string; }; -function formatBlameInformation(template: string, blameInformation: BlameInformation): string { - const templateTokens = { - hash: blameInformation.hash, - hashShort: blameInformation.hash.substring(0, 8), - subject: blameInformation.subject ?? '', - authorName: blameInformation.authorName ?? '', - authorEmail: blameInformation.authorEmail ?? '', - authorDate: new Date(blameInformation.authorDate ?? new Date()).toLocaleString(), - authorDateAgo: fromNow(blameInformation.authorDate ?? new Date(), true, true) - } satisfies BlameInformationTemplateTokens; - - return template.replace(/\$\{(.+?)\}/g, (_, token) => { - return token in templateTokens ? templateTokens[token as keyof BlameInformationTemplateTokens] : `\${${token}}`; - }); -} - interface RepositoryBlameInformation { /** * Track the current HEAD of the repository so that we can clear cache entries @@ -182,6 +166,22 @@ export class GitBlameController { this._updateTextEditorBlameInformation(window.activeTextEditor); } + formatBlameInformationMessage(template: string, blameInformation: BlameInformation): string { + const templateTokens = { + hash: blameInformation.hash, + hashShort: blameInformation.hash.substring(0, 8), + subject: blameInformation.subject ?? '', + authorName: blameInformation.authorName ?? '', + authorEmail: blameInformation.authorEmail ?? '', + authorDate: new Date(blameInformation.authorDate ?? new Date()).toLocaleString(), + authorDateAgo: fromNow(blameInformation.authorDate ?? new Date(), true, true) + } satisfies BlameInformationTemplateTokens; + + return template.replace(/\$\{(.+?)\}/g, (_, token) => { + return token in templateTokens ? templateTokens[token as keyof BlameInformationTemplateTokens] : `\${${token}}`; + }); + } + getBlameInformationHover(documentUri: Uri, blameInformation: BlameInformation | string): MarkdownString { if (typeof blameInformation === 'string') { return new MarkdownString(blameInformation, true); @@ -270,6 +270,10 @@ export class GitBlameController { return blameInformation; } + private _findDiffInformation(textEditor: TextEditor, ref: string): TextEditorDiffInformation | undefined { + return textEditor.diffInformation?.find(diff => diff.original && isGitUri(diff.original) && fromGitUri(diff.original).ref === ref); + } + @throttle private async _updateTextEditorBlameInformation(textEditor: TextEditor | undefined): Promise { if (!textEditor?.diffInformation || textEditor !== window.activeTextEditor) { @@ -297,9 +301,7 @@ export class GitBlameController { workingTreeAndIndexChanges = undefined; } else if (ref === '') { // Resource on the right-hand side of the diff editor when viewing a resource from the index. - const diffInformationWorkingTreeAndIndex = textEditor.diffInformation - .filter(diff => diff.original && isGitUri(diff.original)) - .find(diff => fromGitUri(diff.original!).ref === 'HEAD'); + const diffInformationWorkingTreeAndIndex = this._findDiffInformation(textEditor, 'HEAD'); // Working tree + index diff information is present and it is stale if (diffInformationWorkingTreeAndIndex && diffInformationWorkingTreeAndIndex.isStale) { @@ -313,9 +315,7 @@ export class GitBlameController { } } else { // Working tree diff information - const diffInformationWorkingTree = textEditor.diffInformation - .filter(diff => diff.original && isGitUri(diff.original)) - .find(diff => fromGitUri(diff.original!).ref === ''); + const diffInformationWorkingTree = this._findDiffInformation(textEditor, ''); // Working tree diff information is not present or it is stale if (!diffInformationWorkingTree || diffInformationWorkingTree.isStale) { @@ -323,9 +323,7 @@ export class GitBlameController { } // Working tree + index diff information - const diffInformationWorkingTreeAndIndex = textEditor.diffInformation - .filter(diff => diff.original && isGitUri(diff.original)) - .find(diff => fromGitUri(diff.original!).ref === 'HEAD'); + const diffInformationWorkingTreeAndIndex = this._findDiffInformation(textEditor, 'HEAD'); // Working tree + index diff information is present and it is stale if (diffInformationWorkingTreeAndIndex && diffInformationWorkingTreeAndIndex.isStale) { @@ -469,7 +467,7 @@ class GitBlameEditorDecoration { const decorations = blameInformation.map(blame => { const contentText = typeof blame.blameInformation === 'string' ? blame.blameInformation - : formatBlameInformation(template, blame.blameInformation); + : this._controller.formatBlameInformationMessage(template, blame.blameInformation); const hoverMessage = this._controller.getBlameInformationHover(textEditor.document.uri, blame.blameInformation); return this._createDecoration(blame.lineNumber, contentText, hoverMessage); @@ -575,7 +573,7 @@ class GitBlameStatusBarItem { this._statusBarItem.tooltip = this._controller.getBlameInformationHover(textEditor.document.uri, blameInformation[0].blameInformation); this._statusBarItem.command = undefined; } else { - this._statusBarItem.text = formatBlameInformation(template, blameInformation[0].blameInformation); + this._statusBarItem.text = this._controller.formatBlameInformationMessage(template, blameInformation[0].blameInformation); this._statusBarItem.tooltip = this._controller.getBlameInformationHover(textEditor.document.uri, blameInformation[0].blameInformation); this._statusBarItem.command = { title: l10n.t('View Commit'), diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 192f893d3aa..cc85c83728a 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -14,7 +14,7 @@ import { Model } from './model'; import { GitResourceGroup, Repository, Resource, ResourceGroupType } from './repository'; import { DiffEditorSelectionHunkToolbarContext, applyLineChanges, getModifiedRange, intersectDiffWithRange, invertLineChange, toLineRanges } from './staging'; import { fromGitUri, toGitUri, isGitUri, toMergeUris, toMultiFileDiffEditorUris } from './uri'; -import { dispose, grep, isDefined, isDescendant, pathEquals, relativePath } from './util'; +import { dispose, grep, isDefined, isDescendant, pathEquals, relativePath, truncate } from './util'; import { GitTimelineItem } from './timelineProvider'; import { ApiRepository } from './api/api1'; import { getRemoteSourceActions, pickRemoteSource } from './remoteSource'; @@ -3981,8 +3981,8 @@ export class CommandCenter { const commitParentId = commit.parents.length > 0 ? commit.parents[0] : `${commit.hash}^`; const changes = await repository.diffBetween(commitParentId, commit.hash); - const title = `${item.shortRef} - ${commit.message}`; - const multiDiffSourceUri = toGitUri(Uri.file(repository.root), commit.hash, { scheme: 'git-commit' }); + const title = `${item.shortRef} - ${truncate(commit.message)}`; + const multiDiffSourceUri = Uri.from({ scheme: 'scm-history-item', path: `${repository.root}/${commitParentId}..${commit.hash}` }); const resources: { originalUri: Uri | undefined; modifiedUri: Uri | undefined }[] = []; for (const change of changes) { @@ -4243,14 +4243,14 @@ export class CommandCenter { // TODO@lszomoru - handle the case when historyItem2 is the first commit in the repository if (!historyItem2) { const commit = await repository.getCommit(historyItem1.id); - title = `${historyItem1.id.substring(0, 8)} - ${commit.message}`; + title = `${historyItem1.id.substring(0, 8)} - ${truncate(commit.message)}`; historyItemParentId = historyItem1.parentIds.length > 0 ? historyItem1.parentIds[0] : `${historyItem1.id}^`; } else { title = l10n.t('All Changes ({0} ↔ {1})', historyItem2.id.substring(0, 8), historyItem1.id.substring(0, 8)); historyItemParentId = historyItem2.parentIds.length > 0 ? historyItem2.parentIds[0] : `${historyItem2.id}^`; } - const multiDiffSourceUri = toGitUri(Uri.file(repository.root), `${historyItemParentId}..${historyItem1.id}`, { scheme: 'git-commit', }); + const multiDiffSourceUri = Uri.from({ scheme: 'scm-history-item', path: `${repository.root}/${historyItemParentId}..${historyItem1.id}` }); await this._viewChanges(repository, historyItem1.id, historyItemParentId, multiDiffSourceUri, title); } @@ -4302,10 +4302,10 @@ export class CommandCenter { } const commit = await repository.getCommit(historyItemId); - const title = `${historyItemId.substring(0, 8)} - ${commit.message}`; + const title = `${historyItemId.substring(0, 8)} - ${truncate(commit.message)}`; const historyItemParentId = commit.parents.length > 0 ? commit.parents[0] : `${historyItemId}^`; - const multiDiffSourceUri = toGitUri(Uri.file(repository.root), `${historyItemParentId}..${historyItemId}`, { scheme: 'git-commit', }); + const multiDiffSourceUri = Uri.from({ scheme: 'scm-history-item', path: `${repository.root}/${historyItemParentId}..${historyItemId}` }); const changes = await repository.diffBetween(historyItemParentId, historyItemId); const resources = changes.map(c => toMultiFileDiffEditorUris(c, historyItemParentId, historyItemId)); diff --git a/extensions/git/src/util.ts b/extensions/git/src/util.ts index cef1a8e36be..8fb85493a9b 100644 --- a/extensions/git/src/util.ts +++ b/extensions/git/src/util.ts @@ -288,6 +288,10 @@ export function detectUnicodeEncoding(buffer: Buffer): Encoding | null { return null; } +export function truncate(value: string, maxLength = 20): string { + return value.length <= maxLength ? value : `${value.substring(0, maxLength)}\u2026`; +} + function normalizePath(path: string): string { // Windows & Mac are currently being handled // as case insensitive file systems in VS Code. diff --git a/src/vs/workbench/api/browser/mainThreadEditors.ts b/src/vs/workbench/api/browser/mainThreadEditors.ts index 4686e135f56..3a751ff64e9 100644 --- a/src/vs/workbench/api/browser/mainThreadEditors.ts +++ b/src/vs/workbench/api/browser/mainThreadEditors.ts @@ -33,7 +33,7 @@ import { IDirtyDiffModelService } from '../../contrib/scm/browser/diff.js'; import { autorun, constObservable, derived, derivedOpts, IObservable, observableFromEvent } from '../../../base/common/observable.js'; import { IUriIdentityService } from '../../../platform/uriIdentity/common/uriIdentity.js'; import { isITextModel } from '../../../editor/common/model.js'; -import { LineRangeMapping, lineRangeMappingFromChanges } from '../../../editor/common/diff/rangeMapping.js'; +import { LineRangeMapping } from '../../../editor/common/diff/rangeMapping.js'; import { equals } from '../../../base/common/arrays.js'; import { Event } from '../../../base/common/event.js'; @@ -147,7 +147,7 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape { ? observableFromEvent(this, diffEditor.onDidChangeModel, () => diffEditor.getModel()) : observableFromEvent(this, codeEditor.onDidChangeModel, () => codeEditor.getModel()); - const editorChangesObs = derived>(reader => { + const editorChangesObs = derived>(reader => { const editorModel = editorModelObs.read(reader); if (!editorModel) { return constObservable(undefined); @@ -168,43 +168,19 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape { // TextEditor if (isITextModel(editorModel)) { return observableFromEvent(this, dirtyDiffModel.onDidChange, () => { - return dirtyDiffModel.quickDiffs.map(quickDiff => { - const changes = dirtyDiffModel.changes - .filter(change => change.label === quickDiff.label) - .map(change => change.change); - - // Convert IChange[] to LineRangeMapping[] - const lineRangeMappings = lineRangeMappingFromChanges(changes); - return { - original: quickDiff.originalResource, - modified: editorModel.uri, - lineRangeMappings - }; - }); + return dirtyDiffModel.getQuickDiffResults(); }); } // DiffEditor return observableFromEvent(Event.any(dirtyDiffModel.onDidChange, diffEditor.onDidUpdateDiff), () => { - const dirtyDiffInformation = dirtyDiffModel.quickDiffs.map(quickDiff => { - const changes = dirtyDiffModel.changes - .filter(change => change.label === quickDiff.label) - .map(change => change.change); - - // Convert IChange[] to LineRangeMapping[] - const lineRangeMappings = lineRangeMappingFromChanges(changes); - return { - original: quickDiff.originalResource, - modified: editorModel.modified.uri, - lineRangeMappings - }; - }); + const dirtyDiffInformation = dirtyDiffModel.getQuickDiffResults(); const diffChanges = diffEditor.getDiffComputationResult()?.changes2 ?? []; const diffInformation = [{ original: editorModel.original.uri, modified: editorModel.modified.uri, - lineRangeMappings: diffChanges.map(change => change as LineRangeMapping) + changes: diffChanges.map(change => change as LineRangeMapping) }]; return [...dirtyDiffInformation, ...diffInformation]; @@ -226,7 +202,7 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape { : editorModel.modified.getVersionId(); return editorChanges.map(change => { - const changes: ITextEditorChange[] = change.lineRangeMappings + const changes: ITextEditorChange[] = change.changes .map(change => [ change.original.startLineNumber, change.original.endLineNumberExclusive, diff --git a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts index 99235d94165..554ea526b66 100644 --- a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts +++ b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts @@ -57,9 +57,10 @@ import { ResourceMap } from '../../../../base/common/map.js'; import { IEditorService } from '../../../services/editor/common/editorService.js'; import { AccessibilitySignal, IAccessibilitySignalService } from '../../../../platform/accessibilitySignal/browser/accessibilitySignalService.js'; import { IAccessibilityService } from '../../../../platform/accessibility/common/accessibility.js'; -import { IQuickDiffService, QuickDiff } from '../common/quickDiff.js'; +import { IQuickDiffService, QuickDiff, QuickDiffResult } from '../common/quickDiff.js'; import { IQuickDiffSelectItem, SwitchQuickDiffBaseAction, SwitchQuickDiffViewItem } from './dirtyDiffSwitcher.js'; import { IChatEditingService, WorkingSetEntryState } from '../../chat/common/chatEditingService.js'; +import { lineRangeMappingFromChanges } from '../../../../editor/common/diff/rangeMapping.js'; class DiffActionRunner extends ActionRunner { @@ -1274,6 +1275,22 @@ export class DirtyDiffModel extends Disposable { return this._quickDiffs; } + public getQuickDiffResults(): QuickDiffResult[] { + return this._quickDiffs.map(quickDiff => { + const changes = this._changes + .filter(change => change.label === quickDiff.label) + .map(change => change.change); + + // Convert IChange[] to LineRangeMapping[] + const lineRangeMappings = lineRangeMappingFromChanges(changes); + return { + original: quickDiff.originalResource, + modified: this._model.resource, + changes: lineRangeMappings + }; + }); + } + public getDiffEditorModel(originalUri: string): IDiffEditorModel | undefined { if (!this._originalModels.has(originalUri)) { return; diff --git a/src/vs/workbench/contrib/scm/common/quickDiff.ts b/src/vs/workbench/contrib/scm/common/quickDiff.ts index 7b1d5daf318..0720dc9f5ff 100644 --- a/src/vs/workbench/contrib/scm/common/quickDiff.ts +++ b/src/vs/workbench/contrib/scm/common/quickDiff.ts @@ -8,6 +8,7 @@ import { createDecorator } from '../../../../platform/instantiation/common/insta import { IDisposable } from '../../../../base/common/lifecycle.js'; import { LanguageSelector } from '../../../../editor/common/languageSelector.js'; import { Event } from '../../../../base/common/event.js'; +import { LineRangeMapping } from '../../../../editor/common/diff/rangeMapping.js'; export const IQuickDiffService = createDecorator('quickDiff'); @@ -27,6 +28,12 @@ export interface QuickDiff { visible: boolean; } +export interface QuickDiffResult { + readonly original: URI; + readonly modified: URI; + readonly changes: readonly LineRangeMapping[]; +} + export interface IQuickDiffService { readonly _serviceBrand: undefined;