From 37a9761fca7ad6220b0670f36ac9eaeb7b6238d6 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 18 Jul 2024 11:40:24 +0200 Subject: [PATCH] Introduced diffEditor.experimental.useTrueInlineView (defaults to false). Fixes #176969 --- .../components/diffEditorDecorations.ts | 22 +++++++++++++++++-- .../diffEditorViewZones.ts | 14 +++++++++++- .../widget/diffEditor/diffEditorOptions.ts | 11 ++++++---- .../browser/widget/diffEditor/style.css | 6 ++++- src/vs/editor/common/config/diffEditor.ts | 1 + .../config/editorConfigurationSchema.ts | 7 +++++- src/vs/editor/common/config/editorOptions.ts | 5 +++++ src/vs/monaco.d.ts | 4 ++++ .../contrib/chat/browser/codeBlockPart.ts | 3 +++ 9 files changed, 64 insertions(+), 9 deletions(-) diff --git a/src/vs/editor/browser/widget/diffEditor/components/diffEditorDecorations.ts b/src/vs/editor/browser/widget/diffEditor/components/diffEditorDecorations.ts index a5992771a9a9d..efbbe00f6c158 100644 --- a/src/vs/editor/browser/widget/diffEditor/components/diffEditorDecorations.ts +++ b/src/vs/editor/browser/widget/diffEditor/components/diffEditorDecorations.ts @@ -6,6 +6,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { IObservable, derived } from 'vs/base/common/observable'; import { DiffEditorEditors } from 'vs/editor/browser/widget/diffEditor/components/diffEditorEditors'; +import { allowsTrueInlineDiffRendering } from 'vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/diffEditorViewZones'; import { DiffEditorOptions } from 'vs/editor/browser/widget/diffEditor/diffEditorOptions'; import { DiffEditorViewModel } from 'vs/editor/browser/widget/diffEditor/diffEditorViewModel'; import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget'; @@ -28,7 +29,8 @@ export class DiffEditorDecorations extends Disposable { } private readonly _decorations = derived(this, (reader) => { - const diff = this._diffModel.read(reader)?.diff.read(reader); + const diffModel = this._diffModel.read(reader); + const diff = diffModel?.diff.read(reader); if (!diff) { return null; } @@ -56,13 +58,29 @@ export class DiffEditorDecorations extends Disposable { modifiedDecorations.push({ range: m.lineRangeMapping.modified.toInclusiveRange()!, options: diffWholeLineAddDecoration }); } } else { + const useInlineDiff = this._options.useTrueInlineDiffRendering.read(reader) && allowsTrueInlineDiffRendering(m.lineRangeMapping); for (const i of m.lineRangeMapping.innerChanges || []) { // Don't show empty markers outside the line range if (m.lineRangeMapping.original.contains(i.originalRange.startLineNumber)) { originalDecorations.push({ range: i.originalRange, options: (i.originalRange.isEmpty() && showEmptyDecorations) ? diffDeleteDecorationEmpty : diffDeleteDecoration }); } if (m.lineRangeMapping.modified.contains(i.modifiedRange.startLineNumber)) { - modifiedDecorations.push({ range: i.modifiedRange, options: (i.modifiedRange.isEmpty() && showEmptyDecorations) ? diffAddDecorationEmpty : diffAddDecoration }); + modifiedDecorations.push({ range: i.modifiedRange, options: (i.modifiedRange.isEmpty() && showEmptyDecorations && !useInlineDiff) ? diffAddDecorationEmpty : diffAddDecoration }); + } + if (useInlineDiff) { + const deletedText = diffModel!.model.original.getValueInRange(i.originalRange); + modifiedDecorations.push({ + range: i.modifiedRange, + options: { + description: 'deleted-text', + before: { + content: deletedText, + inlineClassName: 'inline-deleted-text', + }, + zIndex: 100000, + showIfCollapsed: true, + } + }); } } } diff --git a/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/diffEditorViewZones.ts b/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/diffEditorViewZones.ts index 6a5283f24917d..cfbad5b68b374 100644 --- a/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/diffEditorViewZones.ts +++ b/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/diffEditorViewZones.ts @@ -30,6 +30,7 @@ import { InlineDecoration, InlineDecorationType } from 'vs/editor/common/viewMod import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { DiffEditorOptions } from '../../diffEditorOptions'; +import { Range } from 'vs/editor/common/core/range'; /** * Ensures both editors have the same height by aligning unchanged lines. @@ -187,7 +188,7 @@ export class DiffEditorViewZones extends Disposable { const renderOptions = RenderOptions.fromEditor(this._editors.modified); for (const a of alignmentsVal) { - if (a.diff && !renderSideBySide) { + if (a.diff && !renderSideBySide && (!this._options.useTrueInlineDiffRendering.read(reader) || !allowsTrueInlineDiffRendering(a.diff))) { if (!a.originalRange.isEmpty) { originalModelTokenizationCompleted.read(reader); // Update view-zones once tokenization completes @@ -627,3 +628,14 @@ function getAdditionalLineHeights(editor: CodeEditorWidget, viewZonesToIgnore: R return result; } + +export function allowsTrueInlineDiffRendering(mapping: DetailedLineRangeMapping): boolean { + if (!mapping.innerChanges) { + return false; + } + return mapping.innerChanges.every(c => rangeIsSingleLine(c.modifiedRange) && rangeIsSingleLine(c.originalRange)); +} + +function rangeIsSingleLine(range: Range): boolean { + return range.startLineNumber === range.endLineNumber; +} diff --git a/src/vs/editor/browser/widget/diffEditor/diffEditorOptions.ts b/src/vs/editor/browser/widget/diffEditor/diffEditorOptions.ts index 582ce9f8884af..2f5d221b76c6a 100644 --- a/src/vs/editor/browser/widget/diffEditor/diffEditorOptions.ts +++ b/src/vs/editor/browser/widget/diffEditor/diffEditorOptions.ts @@ -6,6 +6,7 @@ import { IObservable, ISettableObservable, derived, observableFromEvent, observableValue } from 'vs/base/common/observable'; import { getIfDefined } from 'vs/base/common/observableInternal/utils'; import { Constants } from 'vs/base/common/uint'; +import { allowsTrueInlineDiffRendering } from 'vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/diffEditorViewZones'; import { DiffEditorViewModel, DiffState } from 'vs/editor/browser/widget/diffEditor/diffEditorViewModel'; import { diffEditorDefaultOptions } from 'vs/editor/common/config/diffEditor'; import { IDiffEditorBaseOptions, IDiffEditorOptions, IEditorOptions, ValidDiffEditorBaseOptions, clampedFloat, clampedInt, boolean as validateBooleanOption, stringSet as validateStringSetOption } from 'vs/editor/common/config/editorOptions'; @@ -70,6 +71,7 @@ export class DiffEditorOptions { public readonly showEmptyDecorations = derived(this, reader => this._options.read(reader).experimental.showEmptyDecorations!); public readonly onlyShowAccessibleDiffViewer = derived(this, reader => this._options.read(reader).onlyShowAccessibleDiffViewer); public readonly compactMode = derived(this, reader => this._options.read(reader).compactMode); + public readonly useTrueInlineDiffRendering = derived(this, reader => this._options.read(reader).experimental.useTrueInlineView!); public readonly hideUnchangedRegions = derived(this, reader => this._options.read(reader).hideUnchangedRegions.enabled!); public readonly hideUnchangedRegionsRevealLineCount = derived(this, reader => this._options.read(reader).hideUnchangedRegions.revealLineCount!); @@ -95,14 +97,14 @@ export class DiffEditorOptions { private readonly shouldRenderInlineViewInSmartMode = this._model .map(this, model => getIfDefined(this, reader => { const diffs = model?.diff.read(reader); - return diffs ? isSimpleDiff(diffs) : undefined; + return diffs ? isSimpleDiff(diffs, this.useTrueInlineDiffRendering.read(reader)) : undefined; })) .flatten() .map(this, v => !!v); } -function isSimpleDiff(diff: DiffState): boolean { - return diff.mappings.every(m => isInsertion(m.lineRangeMapping) || isDeletion(m.lineRangeMapping)); +function isSimpleDiff(diff: DiffState, supportsTrueDiffRendering: boolean): boolean { + return diff.mappings.every(m => isInsertion(m.lineRangeMapping) || isDeletion(m.lineRangeMapping) || (supportsTrueDiffRendering && allowsTrueInlineDiffRendering(m.lineRangeMapping))); } function isInsertion(mapping: LineRangeMapping): boolean { @@ -113,7 +115,7 @@ function isDeletion(mapping: LineRangeMapping): boolean { return mapping.modified.length === 0; } -function validateDiffEditorOptions(options: Readonly, defaults: ValidDiffEditorBaseOptions): ValidDiffEditorBaseOptions { +function validateDiffEditorOptions(options: Readonly, defaults: typeof diffEditorDefaultOptions | ValidDiffEditorBaseOptions): ValidDiffEditorBaseOptions { return { enableSplitViewResizing: validateBooleanOption(options.enableSplitViewResizing, defaults.enableSplitViewResizing), splitViewDefaultRatio: clampedFloat(options.splitViewDefaultRatio, 0.5, 0.1, 0.9), @@ -132,6 +134,7 @@ function validateDiffEditorOptions(options: Readonly, defaul experimental: { showMoves: validateBooleanOption(options.experimental?.showMoves, defaults.experimental.showMoves!), showEmptyDecorations: validateBooleanOption(options.experimental?.showEmptyDecorations, defaults.experimental.showEmptyDecorations!), + useTrueInlineView: validateBooleanOption(options.experimental?.useTrueInlineView, defaults.experimental.useTrueInlineView!), }, hideUnchangedRegions: { enabled: validateBooleanOption(options.hideUnchangedRegions?.enabled ?? (options.experimental as any)?.collapseUnchangedRegions, defaults.hideUnchangedRegions.enabled!), diff --git a/src/vs/editor/browser/widget/diffEditor/style.css b/src/vs/editor/browser/widget/diffEditor/style.css index ebb5223465875..2e743455bcbc9 100644 --- a/src/vs/editor/browser/widget/diffEditor/style.css +++ b/src/vs/editor/browser/widget/diffEditor/style.css @@ -276,10 +276,14 @@ background-color: var(--vscode-diffEditorGutter-insertedLineBackground, var(--vscode-diffEditor-insertedLineBackground), var(--vscode-diffEditor-insertedTextBackground)); } -.monaco-editor .char-delete, .monaco-diff-editor .char-delete { +.monaco-editor .char-delete, .monaco-diff-editor .char-delete, .monaco-editor .inline-deleted-text { background-color: var(--vscode-diffEditor-removedTextBackground); } +.monaco-editor .inline-deleted-text { + text-decoration: line-through; +} + .monaco-editor .line-delete, .monaco-diff-editor .line-delete { background-color: var(--vscode-diffEditor-removedLineBackground, var(--vscode-diffEditor-removedTextBackground)); } diff --git a/src/vs/editor/common/config/diffEditor.ts b/src/vs/editor/common/config/diffEditor.ts index c8a7680d97fca..ff97a530fb60c 100644 --- a/src/vs/editor/common/config/diffEditor.ts +++ b/src/vs/editor/common/config/diffEditor.ts @@ -24,6 +24,7 @@ export const diffEditorDefaultOptions = { experimental: { showMoves: false, showEmptyDecorations: true, + useTrueInlineView: false, }, hideUnchangedRegions: { enabled: false, diff --git a/src/vs/editor/common/config/editorConfigurationSchema.ts b/src/vs/editor/common/config/editorConfigurationSchema.ts index ab2b8cc70c62e..98d82d7f8782a 100644 --- a/src/vs/editor/common/config/editorConfigurationSchema.ts +++ b/src/vs/editor/common/config/editorConfigurationSchema.ts @@ -247,7 +247,12 @@ const editorConfiguration: IConfigurationNode = { type: 'boolean', default: diffEditorDefaultOptions.experimental.showEmptyDecorations, description: nls.localize('showEmptyDecorations', "Controls whether the diff editor shows empty decorations to see where characters got inserted or deleted."), - } + }, + 'diffEditor.experimental.useTrueInlineView': { + type: 'boolean', + default: diffEditorDefaultOptions.experimental.useTrueInlineView, + description: nls.localize('useTrueInlineView', "If enabled and the editor uses the inline view, word changes are rendered inline."), + }, } }; diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 730ea74c6e9a6..b2a3191c8678e 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -878,6 +878,11 @@ export interface IDiffEditorBaseOptions { showMoves?: boolean; showEmptyDecorations?: boolean; + + /** + * Only applies when `renderSideBySide` is set to false. + */ + useTrueInlineView?: boolean; }; /** diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 60118e178bb53..eae4fbd4957ca 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -3888,6 +3888,10 @@ declare namespace monaco.editor { */ showMoves?: boolean; showEmptyDecorations?: boolean; + /** + * Only applies when `renderSideBySide` is set to false. + */ + useTrueInlineView?: boolean; }; /** * Is the diff editor inside another editor diff --git a/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts b/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts index b8ea2ff0ce6ac..3dd842a992f45 100644 --- a/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts +++ b/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts @@ -619,6 +619,9 @@ export class CodeCompareBlockPart extends Disposable { readOnly: false, isInEmbeddedEditor: true, useInlineViewWhenSpaceIsLimited: true, + experimental: { + useTrueInlineView: true, + }, renderSideBySideInlineBreakpoint: 300, compactMode: true, hideUnchangedRegions: { enabled: true, contextLineCount: 1 },