This commit is contained in:
Henning Dieterichs 2024-03-15 16:52:52 +01:00 committed by Henning Dieterichs
parent 15715aedf0
commit b7d35c8bdd
26 changed files with 800 additions and 52 deletions

View file

@ -15,6 +15,7 @@
"contribEditorContentMenu",
"contribMergeEditorMenus",
"contribMultiDiffEditorMenus",
"contribDiffEditorGutterToolBarMenus",
"contribSourceControlHistoryItemGroupMenu",
"contribSourceControlHistoryItemMenu",
"contribSourceControlInputBoxMenu",
@ -188,6 +189,18 @@
"category": "Git",
"enablement": "!operationInProgress"
},
{
"command": "git.diff.stageHunk",
"title": "%command.stageBlock%",
"category": "Git",
"icon": "$(plus)"
},
{
"command": "git.diff.stageSelection",
"title": "%command.stageSelection%",
"category": "Git",
"icon": "$(plus)"
},
{
"command": "git.revertSelectedRanges",
"title": "%command.revertSelectedRanges%",
@ -2047,6 +2060,20 @@
"when": "scmProvider == git && scmResourceGroup == index"
}
],
"diffEditor/gutter/hunk": [
{
"command": "git.diff.stageHunk",
"group": "primary@10",
"when": "diffEditorOriginalUri =~ /^git\\:.*%22ref%22%3A%22~%22%7D$/"
}
],
"diffEditor/gutter/selection": [
{
"command": "git.diff.stageSelection",
"group": "primary@10",
"when": "diffEditorOriginalUri =~ /^git\\:.*%22ref%22%3A%22~%22%7D$/"
}
],
"scm/change/title": [
{
"command": "git.stageChange",

View file

@ -23,6 +23,8 @@
"command.stageSelectedRanges": "Stage Selected Ranges",
"command.revertSelectedRanges": "Revert Selected Ranges",
"command.stageChange": "Stage Change",
"command.stageSelection": "Stage Selection",
"command.stageBlock": "Stage Block",
"command.revertChange": "Revert Change",
"command.unstage": "Unstage Changes",
"command.unstageAll": "Unstage All Changes",

View file

@ -12,7 +12,7 @@ import { ForcePushMode, GitErrorCodes, Ref, RefType, Status, CommitOptions, Remo
import { Git, Stash } from './git';
import { Model } from './model';
import { Repository, Resource, ResourceGroupType } from './repository';
import { applyLineChanges, getModifiedRange, intersectDiffWithRange, invertLineChange, toLineRanges } from './staging';
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 { GitTimelineItem } from './timelineProvider';
@ -1511,6 +1511,30 @@ export class CommandCenter {
textEditor.selections = [new Selection(firstStagedLine, 0, firstStagedLine, 0)];
}
@command('git.diff.stageHunk')
async diffStageHunk(changes: DiffEditorSelectionHunkToolbarContext): Promise<void> {
this.diffStageHunkOrSelection(changes);
}
@command('git.diff.stageSelection')
async diffStageSelection(changes: DiffEditorSelectionHunkToolbarContext): Promise<void> {
this.diffStageHunkOrSelection(changes);
}
async diffStageHunkOrSelection(changes: DiffEditorSelectionHunkToolbarContext): Promise<void> {
const textEditor = window.activeTextEditor;
if (!textEditor) {
return;
}
const modifiedDocument = textEditor.document;
const modifiedUri = modifiedDocument.uri;
if (modifiedUri.scheme !== 'file') {
return;
}
const result = changes.originalWithModifiedChanges;
await this.runByRepository(modifiedUri, async (repository, resource) => await repository.stage(resource, result));
}
@command('git.stageSelectedRanges', { diff: true })
async stageSelectedChanges(changes: LineChange[]): Promise<void> {
const textEditor = window.activeTextEditor;

View file

@ -142,3 +142,11 @@ export function invertLineChange(diff: LineChange): LineChange {
originalEndLineNumber: diff.modifiedEndLineNumber
};
}
export interface DiffEditorSelectionHunkToolbarContext {
mapping: unknown;
/**
* The original text with the selected modified changes applied.
*/
originalWithModifiedChanges: string;
}

View file

@ -18,6 +18,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import './registrations.contribution';
import { DiffEditorSelectionHunkToolbarContext } from 'vs/editor/browser/widget/diffEditor/features/gutterFeature';
export class ToggleCollapseUnchangedRegions extends Action2 {
constructor() {
@ -165,6 +166,26 @@ export class ShowAllUnchangedRegions extends EditorAction2 {
}
}
export class RevertHunkOrSelection extends EditorAction2 {
constructor() {
super({
id: 'diffEditor.revert',
title: localize2('revert', 'Revert'),
precondition: ContextKeyExpr.has('isInDiffEditor'),
f1: false,
category: diffEditorCategory,
});
}
runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, arg: DiffEditorSelectionHunkToolbarContext): unknown {
const diffEditor = findFocusedDiffEditor(accessor);
if (diffEditor instanceof DiffEditorWidget) {
diffEditor.revertRangeMappings(arg.mapping.innerChanges ?? []);
}
return undefined;
}
}
const accessibleDiffViewerCategory: ILocalizedString = localize2('accessibleDiffViewer', "Accessible Diff Viewer");
export class AccessibleDiffViewerNext extends Action2 {

View file

@ -3,64 +3,56 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Emitter } from 'vs/base/common/event';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { IObservable, IReader, autorunHandleChanges, derivedOpts, observableFromEvent } from 'vs/base/common/observable';
import { IReader, autorunHandleChanges, derived, derivedOpts, observableFromEvent } from 'vs/base/common/observable';
import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration';
import { IDiffEditorConstructionOptions } from 'vs/editor/browser/editorBrowser';
import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget';
import { IDiffCodeEditorWidgetOptions } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget';
import { OverviewRulerFeature } from 'vs/editor/browser/widget/diffEditor/features/overviewRulerFeature';
import { EditorOptions, IEditorOptions } from 'vs/editor/common/config/editorOptions';
import { Position } from 'vs/editor/common/core/position';
import { IContentSizeChangedEvent } from 'vs/editor/common/editorCommon';
import { localize } from 'vs/nls';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { DiffEditorOptions } from '../diffEditorOptions';
import { ITextModel } from 'vs/editor/common/model';
import { IDiffCodeEditorWidgetOptions } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget';
import { Selection } from 'vs/editor/common/core/selection';
import { Position } from 'vs/editor/common/core/position';
export class DiffEditorEditors extends Disposable {
public readonly modified: CodeEditorWidget;
public readonly original: CodeEditorWidget;
public readonly original = this._register(this._createLeftHandSideEditor(this._options.editorOptions.get(), this._argCodeEditorWidgetOptions.originalEditor || {}));
public readonly modified = this._register(this._createRightHandSideEditor(this._options.editorOptions.get(), this._argCodeEditorWidgetOptions.modifiedEditor || {}));
private readonly _onDidContentSizeChange = this._register(new Emitter<IContentSizeChangedEvent>());
public get onDidContentSizeChange() { return this._onDidContentSizeChange.event; }
public readonly modifiedScrollTop: IObservable<number>;
public readonly modifiedScrollHeight: IObservable<number>;
public readonly modifiedScrollTop = observableFromEvent(this.modified.onDidScrollChange, () => /** @description modified.getScrollTop */ this.modified.getScrollTop());
public readonly modifiedScrollHeight = observableFromEvent(this.modified.onDidScrollChange, () => /** @description modified.getScrollHeight */ this.modified.getScrollHeight());
public readonly modifiedModel: IObservable<ITextModel | null>;
public readonly modifiedModel = observableFromEvent(this.modified.onDidChangeModel, () => /** @description modified.model */ this.modified.getModel());
public readonly modifiedSelections: IObservable<Selection[]>;
public readonly modifiedCursor: IObservable<Position>;
public readonly modifiedSelections = observableFromEvent(this.modified.onDidChangeCursorSelection, () => this.modified.getSelections() ?? []);
public readonly modifiedCursor = derivedOpts({ owner: this, equalityComparer: Position.equals }, reader => this.modifiedSelections.read(reader)[0]?.getPosition() ?? new Position(1, 1));
public readonly originalCursor: IObservable<Position>;
public readonly originalCursor = observableFromEvent(this.original.onDidChangeCursorPosition, () => this.original.getPosition() ?? new Position(1, 1));
public readonly isOriginalFocused = observableFromEvent(Event.any(this.original.onDidFocusEditorWidget, this.original.onDidBlurEditorWidget), () => this.original.hasWidgetFocus());
public readonly isModifiedFocused = observableFromEvent(Event.any(this.modified.onDidFocusEditorWidget, this.modified.onDidBlurEditorWidget), () => this.modified.hasWidgetFocus());
public readonly isFocused = derived(this, reader => this.isOriginalFocused.read(reader) || this.isModifiedFocused.read(reader));
constructor(
private readonly originalEditorElement: HTMLElement,
private readonly modifiedEditorElement: HTMLElement,
private readonly _options: DiffEditorOptions,
codeEditorWidgetOptions: IDiffCodeEditorWidgetOptions,
private _argCodeEditorWidgetOptions: IDiffCodeEditorWidgetOptions,
private readonly _createInnerEditor: (instantiationService: IInstantiationService, container: HTMLElement, options: Readonly<IEditorOptions>, editorWidgetOptions: ICodeEditorWidgetOptions) => CodeEditorWidget,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@IKeybindingService private readonly _keybindingService: IKeybindingService,
@IKeybindingService private readonly _keybindingService: IKeybindingService
) {
super();
this.original = this._register(this._createLeftHandSideEditor(_options.editorOptions.get(), codeEditorWidgetOptions.originalEditor || {}));
this.modified = this._register(this._createRightHandSideEditor(_options.editorOptions.get(), codeEditorWidgetOptions.modifiedEditor || {}));
this.modifiedModel = observableFromEvent(this.modified.onDidChangeModel, () => /** @description modified.model */ this.modified.getModel());
this.modifiedScrollTop = observableFromEvent(this.modified.onDidScrollChange, () => /** @description modified.getScrollTop */ this.modified.getScrollTop());
this.modifiedScrollHeight = observableFromEvent(this.modified.onDidScrollChange, () => /** @description modified.getScrollHeight */ this.modified.getScrollHeight());
this.modifiedSelections = observableFromEvent(this.modified.onDidChangeCursorSelection, () => this.modified.getSelections() ?? []);
this.modifiedCursor = derivedOpts({ owner: this, equalityComparer: Position.equals }, reader => this.modifiedSelections.read(reader)[0]?.getPosition() ?? new Position(1, 1));
this.originalCursor = observableFromEvent(this.original.onDidChangeCursorPosition, () => this.original.getPosition() ?? new Position(1, 1));
this._argCodeEditorWidgetOptions = null as any;
this._register(autorunHandleChanges({
createEmptyChangeSummary: () => ({} as IDiffEditorConstructionOptions),

View file

@ -312,7 +312,7 @@ export class DiffEditorViewZones extends Disposable {
}
let marginDomNode: HTMLElement | undefined = undefined;
if (a.diff && a.diff.modified.isEmpty && this._options.shouldRenderRevertArrows.read(reader)) {
if (a.diff && a.diff.modified.isEmpty && this._options.shouldRenderOldRevertArrows.read(reader)) {
marginDomNode = createViewZoneMarginArrow();
}

View file

@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { Codicon } from 'vs/base/common/codicons';
import { AccessibleDiffViewerNext, AccessibleDiffViewerPrev, CollapseAllUnchangedRegions, ExitCompareMove, ShowAllUnchangedRegions, SwitchSide, ToggleCollapseUnchangedRegions, ToggleShowMovedCodeBlocks, ToggleUseInlineViewWhenSpaceIsLimited } from 'vs/editor/browser/widget/diffEditor/commands';
import { AccessibleDiffViewerNext, AccessibleDiffViewerPrev, CollapseAllUnchangedRegions, ExitCompareMove, RevertHunkOrSelection, ShowAllUnchangedRegions, SwitchSide, ToggleCollapseUnchangedRegions, ToggleShowMovedCodeBlocks, ToggleUseInlineViewWhenSpaceIsLimited } from 'vs/editor/browser/widget/diffEditor/commands';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { localize } from 'vs/nls';
import { MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions';
@ -44,6 +44,35 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, {
when: ContextKeyExpr.has('isInDiffEditor'),
});
registerAction2(RevertHunkOrSelection);
for (const ctx of [
{ icon: Codicon.arrowRight, key: EditorContextKeys.diffEditorInlineMode.toNegated() },
{ icon: Codicon.discard, key: EditorContextKeys.diffEditorInlineMode }
]) {
MenuRegistry.appendMenuItem(MenuId.DiffEditorHunkToolbar, {
command: {
id: new RevertHunkOrSelection().desc.id,
title: localize('revertHunk', "Revert Block"),
icon: ctx.icon,
},
when: ContextKeyExpr.and(EditorContextKeys.diffEditorModifiedWritable, ctx.key),
order: 5,
group: 'primary',
});
MenuRegistry.appendMenuItem(MenuId.DiffEditorSelectionToolbar, {
command: {
id: new RevertHunkOrSelection().desc.id,
title: localize('revertSelection', "Revert Selection"),
icon: ctx.icon,
},
when: ContextKeyExpr.and(EditorContextKeys.diffEditorModifiedWritable, ctx.key),
order: 5,
group: 'primary',
});
}
registerAction2(SwitchSide);
registerAction2(ExitCompareMove);

View file

@ -32,12 +32,15 @@ export class DiffEditorOptions {
);
public readonly readOnly = derived(this, reader => this._options.read(reader).readOnly);
public readonly shouldRenderRevertArrows = derived(this, reader => {
public readonly shouldRenderOldRevertArrows = derived(this, reader => {
if (!this._options.read(reader).renderMarginRevertIcon) { return false; }
if (!this.renderSideBySide.read(reader)) { return false; }
if (this.readOnly.read(reader)) { return false; }
if (this.shouldRenderGutterMenu.read(reader)) { return false; }
return true;
});
public readonly shouldRenderGutterMenu = derived(this, reader => this._options.read(reader).renderGutterMenu);
public readonly renderIndicators = derived(this, reader => this._options.read(reader).renderIndicators);
public readonly enableSplitViewResizing = derived(this, reader => this._options.read(reader).enableSplitViewResizing);
public readonly splitViewDefaultRatio = derived(this, reader => this._options.read(reader).splitViewDefaultRatio);
@ -99,5 +102,6 @@ function validateDiffEditorOptions(options: Readonly<IDiffEditorOptions>, defaul
onlyShowAccessibleDiffViewer: validateBooleanOption(options.onlyShowAccessibleDiffViewer, defaults.onlyShowAccessibleDiffViewer),
renderSideBySideInlineBreakpoint: clampedInt(options.renderSideBySideInlineBreakpoint, defaults.renderSideBySideInlineBreakpoint, 0, Constants.MAX_SAFE_SMALL_INTEGER),
useInlineViewWhenSpaceIsLimited: validateBooleanOption(options.useInlineViewWhenSpaceIsLimited, defaults.useInlineViewWhenSpaceIsLimited),
renderGutterMenu: validateBooleanOption(options.renderGutterMenu, defaults.renderGutterMenu),
};
}

View file

@ -45,6 +45,7 @@ import { DiffEditorEditors } from './components/diffEditorEditors';
import { DelegatingEditor } from './delegatingEditorImpl';
import { DiffEditorOptions } from './diffEditorOptions';
import { DiffEditorViewModel, DiffMapping, DiffState } from './diffEditorViewModel';
import { DiffEditorGutter } from 'vs/editor/browser/widget/diffEditor/features/gutterFeature';
export interface IDiffCodeEditorWidgetOptions {
originalEditor?: ICodeEditorWidgetOptions;
@ -56,8 +57,8 @@ export class DiffEditorWidget extends DelegatingEditor implements IDiffEditor {
private readonly elements = h('div.monaco-diff-editor.side-by-side', { style: { position: 'relative', height: '100%' } }, [
h('div.noModificationsOverlay@overlay', { style: { position: 'absolute', height: '100%', visibility: 'hidden', } }, [$('span', {}, 'No Changes')]),
h('div.editor.original@original', { style: { position: 'absolute', height: '100%' } }),
h('div.editor.modified@modified', { style: { position: 'absolute', height: '100%' } }),
h('div.editor.original@original', { style: { position: 'absolute', height: '100%', zIndex: '1', } }),
h('div.editor.modified@modified', { style: { position: 'absolute', height: '100%', zIndex: '1', } }),
h('div.accessibleDiffViewer@accessibleDiffViewer', { style: { position: 'absolute', height: '100%' } }),
]);
private readonly _diffModel = observableValue<DiffEditorViewModel | undefined>(this, undefined);
@ -72,6 +73,9 @@ export class DiffEditorWidget extends DelegatingEditor implements IDiffEditor {
);
private readonly _rootSizeObserver: ObservableElementSizeObserver;
/**
* Is undefined if and only if side-by-side
*/
private readonly _sash: IObservable<DiffEditorSash | undefined>;
private readonly _boundarySashes = observableValue<IBoundarySashes | undefined>(this, undefined);
@ -88,6 +92,8 @@ export class DiffEditorWidget extends DelegatingEditor implements IDiffEditor {
private readonly _overviewRulerPart: IObservable<OverviewRulerFeature | undefined>;
private readonly _movedBlocksLinesPart = observableValue<MovedBlocksLinesFeature | undefined>(this, undefined);
private readonly _gutter: IObservable<DiffEditorGutter | undefined>;
public get collapseUnchangedRegions() { return this._options.hideUnchangedRegions.get(); }
constructor(
@ -126,6 +132,9 @@ export class DiffEditorWidget extends DelegatingEditor implements IDiffEditor {
this._register(bindContextKey(EditorContextKeys.diffEditorRenderSideBySideInlineBreakpointReached, this._contextKeyService,
reader => this._options.couldShowInlineViewBecauseOfSize.read(reader)
));
this._register(bindContextKey(EditorContextKeys.diffEditorInlineMode, this._contextKeyService,
reader => !this._options.renderSideBySide.read(reader)
));
this._register(bindContextKey(EditorContextKeys.hasChanges, this._contextKeyService,
reader => (this._diffModel.read(reader)?.diff.read(reader)?.mappings.length ?? 0) > 0
@ -140,6 +149,19 @@ export class DiffEditorWidget extends DelegatingEditor implements IDiffEditor {
(i, c, o, o2) => this._createInnerEditor(i, c, o, o2),
));
this._register(bindContextKey(EditorContextKeys.diffEditorOriginalWritable, this._contextKeyService,
reader => this._options.originalEditable.read(reader)
));
this._register(bindContextKey(EditorContextKeys.diffEditorModifiedWritable, this._contextKeyService,
reader => !this._options.readOnly.read(reader)
));
this._register(bindContextKey(EditorContextKeys.diffEditorOriginalUri, this._contextKeyService,
reader => this._diffModel.read(reader)?.model.original.uri.toString() ?? ''
));
this._register(bindContextKey(EditorContextKeys.diffEditorModifiedUri, this._contextKeyService,
reader => this._diffModel.read(reader)?.model.modified.uri.toString() ?? ''
));
this._overviewRulerPart = derivedDisposable(this, reader =>
!this._options.renderOverviewRuler.read(reader)
? undefined
@ -245,6 +267,17 @@ export class DiffEditorWidget extends DelegatingEditor implements IDiffEditor {
codeEditorService.addDiffEditor(this);
this._gutter = derivedDisposable(this, reader => {
return this._options.shouldRenderGutterMenu.read(reader)
? this._instantiationService.createInstance(
readHotReloadableExport(DiffEditorGutter, reader),
this.elements.root,
this._diffModel,
this._editors
)
: undefined;
});
this._register(recomputeInitiallyAndOnChange(this._layoutInfo));
derivedDisposable(this, reader => /** @description MovedBlocksLinesPart */
@ -290,7 +323,9 @@ export class DiffEditorWidget extends DelegatingEditor implements IDiffEditor {
}
}));
this._register(new RevertButtonsFeature(this._editors, this._diffModel, this._options, this));
this._register(autorunWithStore((reader, store) => {
store.add(new (readHotReloadableExport(RevertButtonsFeature, reader))(this._editors, this._diffModel, this._options, this));
}));
}
public getViewWidth(): number {
@ -307,23 +342,49 @@ export class DiffEditorWidget extends DelegatingEditor implements IDiffEditor {
}
private readonly _layoutInfo = derived(this, reader => {
const width = this._rootSizeObserver.width.read(reader);
const height = this._rootSizeObserver.height.read(reader);
const sashLeft = this._sash.read(reader)?.sashLeft.read(reader);
const fullWidth = this._rootSizeObserver.width.read(reader);
const fullHeight = this._rootSizeObserver.height.read(reader);
const originalWidth = sashLeft ?? Math.max(5, this._editors.original.getLayoutInfo().decorationsLeft);
const modifiedWidth = width - originalWidth - (this._overviewRulerPart.read(reader)?.width ?? 0);
const sash = this._sash.read(reader);
const movedBlocksLinesWidth = this._movedBlocksLinesPart.read(reader)?.width.read(reader) ?? 0;
const originalWidthWithoutMovedBlockLines = originalWidth - movedBlocksLinesWidth;
this.elements.original.style.width = originalWidthWithoutMovedBlockLines + 'px';
this.elements.original.style.left = '0px';
const gutter = this._gutter.read(reader);
const gutterWidth = gutter?.width.read(reader) ?? 0;
const overviewRulerPartWidth = this._overviewRulerPart.read(reader)?.width ?? 0;
let originalLeft: number, originalWidth: number, modifiedLeft: number, modifiedWidth: number, gutterLeft: number;
const sideBySide = !!sash;
if (sideBySide) {
const sashLeft = sash.sashLeft.read(reader);
const movedBlocksLinesWidth = this._movedBlocksLinesPart.read(reader)?.width.read(reader) ?? 0;
originalLeft = 0;
originalWidth = sashLeft - gutterWidth - movedBlocksLinesWidth;
gutterLeft = sashLeft - gutterWidth;
modifiedLeft = sashLeft;
modifiedWidth = fullWidth - modifiedLeft - overviewRulerPartWidth;
} else {
gutterLeft = 0;
originalLeft = gutterWidth;
originalWidth = Math.max(5, this._editors.original.getLayoutInfo().decorationsLeft);
modifiedLeft = gutterWidth + originalWidth;
modifiedWidth = fullWidth - modifiedLeft - overviewRulerPartWidth;
}
this.elements.original.style.left = originalLeft + 'px';
this.elements.original.style.width = originalWidth + 'px';
this._editors.original.layout({ width: originalWidth, height: fullHeight }, true);
gutter?.layout(gutterLeft);
this.elements.modified.style.left = modifiedLeft + 'px';
this.elements.modified.style.width = modifiedWidth + 'px';
this.elements.modified.style.left = originalWidth + 'px';
this._editors.original.layout({ width: originalWidthWithoutMovedBlockLines, height }, true);
this._editors.modified.layout({ width: modifiedWidth, height }, true);
this._editors.modified.layout({ width: modifiedWidth, height: fullHeight }, true);
return {
modifiedEditor: this._editors.modified.getLayoutInfo(),

View file

@ -0,0 +1,276 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { EventType, addDisposableListener, h } from 'vs/base/browser/dom';
import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent';
import { ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget';
import { Disposable } from 'vs/base/common/lifecycle';
import { IObservable, autorun, autorunWithStore, derived, observableFromEvent, observableValue } from 'vs/base/common/observable';
import { DiffEditorEditors } from 'vs/editor/browser/widget/diffEditor/components/diffEditorEditors';
import { DiffEditorViewModel } from 'vs/editor/browser/widget/diffEditor/diffEditorViewModel';
import { appendRemoveOnDispose, applyStyle } from 'vs/editor/browser/widget/diffEditor/utils';
import { EditorGutter, IGutterItemInfo, IGutterItemView } from 'vs/editor/browser/widget/diffEditor/utils/editorGutter';
import { ActionRunnerWithContext } from 'vs/editor/browser/widget/multiDiffEditor/utils';
import { EditorOption } from 'vs/editor/common/config/editorOptions';
import { LineRange, LineRangeSet } from 'vs/editor/common/core/lineRange';
import { OffsetRange } from 'vs/editor/common/core/offsetRange';
import { Range } from 'vs/editor/common/core/range';
import { SingleTextEdit, TextEdit } from 'vs/editor/common/core/textEdit';
import { DetailedLineRangeMapping } from 'vs/editor/common/diff/rangeMapping';
import { TextModelText } from 'vs/editor/common/model/textModelText';
import { HiddenItemStrategy, MenuWorkbenchToolBar } from 'vs/platform/actions/browser/toolbar';
import { IMenuService, MenuId } from 'vs/platform/actions/common/actions';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { WorkbenchHoverDelegate } from 'vs/platform/hover/browser/hover';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
const emptyArr: never[] = [];
const width = 35;
export class DiffEditorGutter extends Disposable {
private readonly _menu = this._register(this._menuService.createMenu(MenuId.DiffEditorHunkToolbar, this._contextKeyService));
private readonly _actions = observableFromEvent(this._menu.onDidChange, () => this._menu.getActions());
private readonly _hasActions = this._actions.map(a => a.length > 0);
public readonly width = derived(this, reader => this._hasActions.read(reader) ? width : 0);
private readonly elements = h('div.gutter@gutter', { style: { position: 'absolute', height: '100%', width: width + 'px', zIndex: '0' } }, []);
constructor(
diffEditorRoot: HTMLDivElement,
private readonly _diffModel: IObservable<DiffEditorViewModel | undefined>,
private readonly _editors: DiffEditorEditors,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
@IMenuService private readonly _menuService: IMenuService,
) {
super();
this._register(appendRemoveOnDispose(diffEditorRoot, this.elements.root));
this._register(addDisposableListener(this.elements.root, 'click', () => {
this._editors.modified.focus();
}));
this._register(applyStyle(this.elements.root, { display: this._hasActions.map(a => a ? 'block' : 'none') }));
this._register(new EditorGutter<DiffGutterItem>(this._editors.modified, this.elements.root, {
getIntersectingGutterItems: (range, reader) => {
const model = this._diffModel.read(reader);
if (!model) {
return [];
}
const diffs = model.diff.read(reader);
if (!diffs) { return []; }
const selection = this._selectedDiffs.read(reader);
if (selection.length > 0) {
const m = DetailedLineRangeMapping.fromRangeMappings(selection.flatMap(s => s.rangeMappings));
return [new DiffGutterItem(m, true, MenuId.DiffEditorSelectionToolbar, undefined)];
}
const currentDiff = this._currentDiff.read(reader);
return diffs.mappings.map(m => new DiffGutterItem(
m.lineRangeMapping,
m.lineRangeMapping === currentDiff?.lineRangeMapping,
MenuId.DiffEditorHunkToolbar,
undefined,
));
},
createView: (item, target) => {
return this._instantiationService.createInstance(DiffToolBar, item, target, this);
},
}));
this._register(addDisposableListener(this.elements.gutter, EventType.MOUSE_WHEEL, (e: IMouseWheelEvent) => {
if (!this._editors.modified.getOption(EditorOption.scrollbar).handleMouseWheel) {
this._editors.modified.delegateScrollFromMouseWheelEvent(e);
}
}, { passive: false }));
}
public computeStagedValue(mapping: DetailedLineRangeMapping): string {
const c = mapping.innerChanges ?? [];
const edit = new TextEdit(c.map(c => new SingleTextEdit(c.originalRange, this._editors.modifiedModel.get()!.getValueInRange(c.modifiedRange))));
const value = edit.apply(new TextModelText(this._editors.original.getModel()!));
return value;
}
private readonly _currentDiff = derived(this, (reader) => {
const model = this._diffModel.read(reader);
if (!model) {
return undefined;
}
const mappings = model.diff.read(reader)?.mappings;
const cursorPosition = this._editors.modifiedCursor.read(reader);
if (!cursorPosition) { return undefined; }
return mappings?.find(m => m.lineRangeMapping.modified.contains(cursorPosition.lineNumber));
});
private readonly _selectedDiffs = derived(this, (reader) => {
/** @description selectedDiffs */
const model = this._diffModel.read(reader);
const diff = model?.diff.read(reader);
// Return `emptyArr` because it is a constant. [] is always a new array and would trigger a change.
if (!diff) { return emptyArr; }
const selections = this._editors.modifiedSelections.read(reader);
if (selections.every(s => s.isEmpty())) { return emptyArr; }
const selectedLineNumbers = new LineRangeSet(selections.map(s => LineRange.fromRangeInclusive(s)));
const selectedMappings = diff.mappings.filter(m =>
m.lineRangeMapping.innerChanges && selectedLineNumbers.intersects(m.lineRangeMapping.modified)
);
const result = selectedMappings.map(mapping => ({
mapping,
rangeMappings: mapping.lineRangeMapping.innerChanges!.filter(
c => selections.some(s => Range.areIntersecting(c.modifiedRange, s))
)
}));
if (result.length === 0 || result.every(r => r.rangeMappings.length === 0)) { return emptyArr; }
return result;
});
layout(left: number) {
this.elements.gutter.style.left = left + 'px';
}
}
class DiffGutterItem implements IGutterItemInfo {
constructor(
public readonly mapping: DetailedLineRangeMapping,
public readonly showAlways: boolean,
public readonly menuId: MenuId,
public readonly rangeOverride: LineRange | undefined,
) {
}
get id(): string { return this.mapping.modified.toString(); }
get range(): LineRange { return this.rangeOverride ?? this.mapping.modified; }
}
class DiffToolBar extends Disposable implements IGutterItemView {
private readonly _elements = h('div.gutterItem', { style: { height: '20px', width: '34px' } }, [
h('div.background@background', {}, []),
h('div.buttons@buttons', {}, []),
]);
private readonly _showAlways = this._item.map(this, item => item.showAlways);
private readonly _menuId = this._item.map(this, item => item.menuId);
private readonly _isSmall = observableValue(this, false);
constructor(
private readonly _item: IObservable<DiffGutterItem>,
target: HTMLElement,
gutter: DiffEditorGutter,
@IInstantiationService instantiationService: IInstantiationService
) {
super();
//const r = new ObservableElementSizeObserver
const hoverDelegate = this._register(instantiationService.createInstance(
WorkbenchHoverDelegate,
'element',
true,
{ position: { hoverPosition: HoverPosition.RIGHT } }
));
this._register(appendRemoveOnDispose(target, this._elements.root));
this._register(autorun(reader => {
/** @description update showAlways */
const showAlways = this._showAlways.read(reader);
this._elements.root.classList.toggle('noTransition', true);
this._elements.root.classList.toggle('showAlways', showAlways);
setTimeout(() => {
this._elements.root.classList.toggle('noTransition', false);
}, 0);
}));
this._register(autorunWithStore((reader, store) => {
this._elements.buttons.replaceChildren();
const i = store.add(instantiationService.createInstance(MenuWorkbenchToolBar, this._elements.buttons, this._menuId.read(reader), {
orientation: ActionsOrientation.VERTICAL,
hoverDelegate,
toolbarOptions: {
primaryGroup: g => g.startsWith('primary'),
},
overflowBehavior: { maxItems: this._isSmall.read(reader) ? 1 : 3 },
hiddenItemStrategy: HiddenItemStrategy.Ignore,
actionRunner: new ActionRunnerWithContext(() => {
const mapping = this._item.get().mapping;
return {
mapping,
originalWithModifiedChanges: gutter.computeStagedValue(mapping),
} satisfies DiffEditorSelectionHunkToolbarContext;
}),
menuOptions: {
shouldForwardArgs: true,
},
}));
store.add(i.onDidChangeMenuItems(() => {
if (this._lastItemRange) {
this.layout(this._lastItemRange, this._lastViewRange!);
}
}));
}));
}
private _lastItemRange: OffsetRange | undefined = undefined;
private _lastViewRange: OffsetRange | undefined = undefined;
layout(itemRange: OffsetRange, viewRange: OffsetRange): void {
this._lastItemRange = itemRange;
this._lastViewRange = viewRange;
let itemHeight = this._elements.buttons.clientHeight;
this._isSmall.set(this._item.get().mapping.original.startLineNumber === 1 && itemRange.length < 30, undefined);
// Item might have changed
itemHeight = this._elements.buttons.clientHeight;
this._elements.root.style.top = itemRange.start + 'px';
this._elements.root.style.height = itemRange.length + 'px';
const middleHeight = itemRange.length / 2 - itemHeight / 2;
const margin = itemHeight;
let effectiveCheckboxTop = itemRange.start + middleHeight;
const preferredViewPortRange = OffsetRange.tryCreate(
margin,
viewRange.endExclusive - margin - itemHeight
);
const preferredParentRange = OffsetRange.tryCreate(
itemRange.start + margin,
itemRange.endExclusive - itemHeight - margin
);
if (preferredParentRange && preferredViewPortRange && preferredParentRange.start < preferredParentRange.endExclusive) {
effectiveCheckboxTop = preferredViewPortRange!.clip(effectiveCheckboxTop);
effectiveCheckboxTop = preferredParentRange!.clip(effectiveCheckboxTop);
}
this._elements.buttons.style.top = `${effectiveCheckboxTop - itemRange.start}px`;
}
}
export interface DiffEditorSelectionHunkToolbarContext {
mapping: DetailedLineRangeMapping;
/**
* The original text with the selected modified changes applied.
*/
originalWithModifiedChanges: string;
}

View file

@ -31,7 +31,7 @@ export class RevertButtonsFeature extends Disposable {
super();
this._register(autorunWithStore((reader, store) => {
if (!this._options.shouldRenderRevertArrows.read(reader)) { return; }
if (!this._options.shouldRenderOldRevertArrows.read(reader)) { return; }
const model = this._diffModel.read(reader);
const diff = model?.diff.read(reader);
if (!model || !diff) { return; }

View file

@ -294,6 +294,11 @@
border-left: 1px solid var(--vscode-diffEditor-border);
}
.monaco-diff-editor.side-by-side .editor.original {
box-shadow: 6px 0 5px -5px var(--vscode-scrollbar-shadow);
border-right: 1px solid var(--vscode-diffEditor-border);
}
.monaco-diff-editor .diffViewport {
background: var(--vscode-scrollbarSlider-background);
}
@ -316,3 +321,76 @@
);
background-size: 8px 8px;
}
.monaco-diff-editor .gutter {
position: relative;
overflow: hidden;
flex-shrink: 0;
flex-grow: 0;
.gutterItem {
opacity: 0;
transition: opacity 0.7s;
&.showAlways {
opacity: 1;
transition: none;
}
&.noTransition {
transition: none;
}
}
&:hover .gutterItem {
opacity: 1;
transition: opacity 0.1s ease-in-out;
}
.gutterItem {
.background {
position: absolute;
height: 100%;
left: 50%;
width: 1px;
border-left: 2px rgb(214, 215, 229) solid;
}
.buttons {
position: absolute;
/*height: 100%;*/
width: 100%;
display: flex;
justify-content: center;
align-items: center;
.monaco-toolbar {
height: fit-content;
.monaco-action-bar {
line-height: 1;
.actions-container {
width: fit-content;
border-radius: 4px;
border: 1px rgba(214, 215, 229, 0.644) solid;
background: white;
.action-item {
&:hover {
background: var(--vscode-toolbar-hoverBackground);
}
.action-label {
padding: 0.5px 1px;
}
}
}
}
}
}
}
}

View file

@ -0,0 +1,164 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { h, reset } from 'vs/base/browser/dom';
import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { autorun, IObservable, IReader, ISettableObservable, observableFromEvent, observableSignal, observableSignalFromEvent, observableValue, transaction } from 'vs/base/common/observable';
import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget';
import { LineRange } from 'vs/editor/common/core/lineRange';
import { OffsetRange } from 'vs/editor/common/core/offsetRange';
export class EditorGutter<T extends IGutterItemInfo = IGutterItemInfo> extends Disposable {
private readonly scrollTop = observableFromEvent(
this._editor.onDidScrollChange,
(e) => /** @description editor.onDidScrollChange */ this._editor.getScrollTop()
);
private readonly isScrollTopZero = this.scrollTop.map((scrollTop) => /** @description isScrollTopZero */ scrollTop === 0);
private readonly modelAttached = observableFromEvent(
this._editor.onDidChangeModel,
(e) => /** @description editor.onDidChangeModel */ this._editor.hasModel()
);
private readonly editorOnDidChangeViewZones = observableSignalFromEvent('onDidChangeViewZones', this._editor.onDidChangeViewZones);
private readonly editorOnDidContentSizeChange = observableSignalFromEvent('onDidContentSizeChange', this._editor.onDidContentSizeChange);
private readonly domNodeSizeChanged = observableSignal('domNodeSizeChanged');
constructor(
private readonly _editor: CodeEditorWidget,
private readonly _domNode: HTMLElement,
private readonly itemProvider: IGutterItemProvider<T>
) {
super();
this._domNode.className = 'gutter monaco-editor';
const scrollDecoration = this._domNode.appendChild(
h('div.scroll-decoration', { role: 'presentation', ariaHidden: 'true', style: { width: '100%' } })
.root
);
const o = new ResizeObserver(() => {
transaction(tx => {
/** @description ResizeObserver: size changed */
this.domNodeSizeChanged.trigger(tx);
});
});
o.observe(this._domNode);
this._register(toDisposable(() => o.disconnect()));
this._register(autorun(reader => {
/** @description update scroll decoration */
scrollDecoration.className = this.isScrollTopZero.read(reader) ? '' : 'scroll-decoration';
}));
this._register(autorun(reader => /** @description EditorGutter.Render */ this.render(reader)));
}
override dispose(): void {
super.dispose();
reset(this._domNode);
}
private readonly views = new Map<string, ManagedGutterItemView>();
private render(reader: IReader): void {
if (!this.modelAttached.read(reader)) {
return;
}
this.domNodeSizeChanged.read(reader);
this.editorOnDidChangeViewZones.read(reader);
this.editorOnDidContentSizeChange.read(reader);
const scrollTop = this.scrollTop.read(reader);
const visibleRanges = this._editor.getVisibleRanges();
const unusedIds = new Set(this.views.keys());
const viewRange = OffsetRange.ofStartAndLength(0, this._domNode.clientHeight);
if (!viewRange.isEmpty) {
for (const visibleRange of visibleRanges) {
const visibleRange2 = new LineRange(
visibleRange.startLineNumber,
visibleRange.endLineNumber + 1
);
const gutterItems = this.itemProvider.getIntersectingGutterItems(
visibleRange2,
reader
);
transaction(tx => {
/** EditorGutter.render */
for (const gutterItem of gutterItems) {
if (!gutterItem.range.intersect(visibleRange2)) {
continue;
}
unusedIds.delete(gutterItem.id);
let view = this.views.get(gutterItem.id);
if (!view) {
const viewDomNode = document.createElement('div');
this._domNode.appendChild(viewDomNode);
const gutterItemObs = observableValue('item', gutterItem);
const itemView = this.itemProvider.createView(
gutterItemObs,
viewDomNode
);
view = new ManagedGutterItemView(gutterItemObs, itemView, viewDomNode);
this.views.set(gutterItem.id, view);
} else {
view.item.set(gutterItem, tx);
}
const top =
gutterItem.range.startLineNumber <= this._editor.getModel()!.getLineCount()
? this._editor.getTopForLineNumber(gutterItem.range.startLineNumber, true) - scrollTop
: this._editor.getBottomForLineNumber(gutterItem.range.startLineNumber - 1, false) - scrollTop;
const bottom = this._editor.getBottomForLineNumber(gutterItem.range.endLineNumberExclusive - 1, true) - scrollTop;
const height = bottom - top;
view.domNode.style.top = `${top}px`;
view.domNode.style.height = `${height}px`;
view.gutterItemView.layout(OffsetRange.ofStartAndLength(top, height), viewRange);
}
});
}
}
for (const id of unusedIds) {
const view = this.views.get(id)!;
view.gutterItemView.dispose();
this._domNode.removeChild(view.domNode);
this.views.delete(id);
}
}
}
class ManagedGutterItemView {
constructor(
public readonly item: ISettableObservable<IGutterItemInfo>,
public readonly gutterItemView: IGutterItemView,
public readonly domNode: HTMLDivElement,
) { }
}
export interface IGutterItemProvider<TItem extends IGutterItemInfo> {
getIntersectingGutterItems(range: LineRange, reader: IReader): TItem[];
createView(item: IObservable<TItem>, target: HTMLElement): IGutterItemView;
}
export interface IGutterItemInfo {
id: string;
range: LineRange;
}
export interface IGutterItemView extends IDisposable {
layout(itemRange: OffsetRange, viewRange: OffsetRange): void;
}

View file

@ -6,11 +6,12 @@
import { ActionRunner, IAction } from 'vs/base/common/actions';
export class ActionRunnerWithContext extends ActionRunner {
constructor(private readonly _getContext: () => any) {
constructor(private readonly _getContext: () => unknown) {
super();
}
protected override runAction(action: IAction, _context?: unknown): Promise<void> {
return super.runAction(action, this._getContext());
const ctx = this._getContext();
return super.runAction(action, ctx);
}
}

View file

@ -10,6 +10,7 @@ export const diffEditorDefaultOptions = {
splitViewDefaultRatio: 0.5,
renderSideBySide: true,
renderMarginRevertIcon: true,
renderGutterMenu: true,
maxComputationTime: 5000,
maxFileSize: 50,
ignoreTrimWhitespace: true,

View file

@ -175,6 +175,11 @@ const editorConfiguration: IConfigurationNode = {
default: diffEditorDefaultOptions.renderMarginRevertIcon,
description: nls.localize('renderMarginRevertIcon', "When enabled, the diff editor shows arrows in its glyph margin to revert changes.")
},
'diffEditor.renderGutterMenu': {
type: 'boolean',
default: diffEditorDefaultOptions.renderGutterMenu,
description: nls.localize('renderGutterMenu', "When enabled, the diff editor shows a special gutter for revert and stage actions.")
},
'diffEditor.ignoreTrimWhitespace': {
type: 'boolean',
default: diffEditorDefaultOptions.ignoreTrimWhitespace,

View file

@ -803,6 +803,10 @@ export interface IDiffEditorBaseOptions {
* Default to true.
*/
renderMarginRevertIcon?: boolean;
/**
* Indicates if the gutter menu should be rendered.
*/
renderGutterMenu?: boolean;
/**
* Original model should be editable?
* Defaults to false.

View file

@ -52,6 +52,19 @@ export class LineRange {
return result.ranges;
}
public static join(lineRanges: LineRange[]): LineRange {
if (lineRanges.length === 0) {
throw new BugIndicatingError('lineRanges cannot be empty');
}
let startLineNumber = lineRanges[0].startLineNumber;
let endLineNumberExclusive = lineRanges[0].endLineNumberExclusive;
for (let i = 1; i < lineRanges.length; i++) {
startLineNumber = Math.min(startLineNumber, lineRanges[i].startLineNumber);
endLineNumberExclusive = Math.max(endLineNumberExclusive, lineRanges[i].endLineNumberExclusive);
}
return new LineRange(startLineNumber, endLineNumberExclusive);
}
public static ofLength(startLineNumber: number, length: number): LineRange {
return new LineRange(startLineNumber, startLineNumber + length);
}

View file

@ -92,6 +92,12 @@ export class LineRangeMapping {
* Also contains inner range mappings.
*/
export class DetailedLineRangeMapping extends LineRangeMapping {
public static fromRangeMappings(rangeMappings: RangeMapping[]): DetailedLineRangeMapping {
const originalRange = LineRange.join(rangeMappings.map(r => LineRange.fromRangeInclusive(r.originalRange)));
const modifiedRange = LineRange.join(rangeMappings.map(r => LineRange.fromRangeInclusive(r.modifiedRange)));
return new DetailedLineRangeMapping(originalRange, modifiedRange, rangeMappings);
}
/**
* If inner changes have not been computed, this is set to undefined.
* Otherwise, it represents the character-level diff in this line range.

View file

@ -30,10 +30,16 @@ export namespace EditorContextKeys {
export const inMultiDiffEditor = new RawContextKey<boolean>('inMultiDiffEditor', false, nls.localize('inMultiDiffEditor', "Whether the context is a multi diff editor"));
export const multiDiffEditorAllCollapsed = new RawContextKey<boolean>('multiDiffEditorAllCollapsed', undefined, nls.localize('multiDiffEditorAllCollapsed', "Whether all files in multi diff editor are collapsed"));
export const hasChanges = new RawContextKey<boolean>('diffEditorHasChanges', false, nls.localize('diffEditorHasChanges', "Whether the diff editor has changes"));
export const comparingMovedCode = new RawContextKey<boolean>('comparingMovedCode', false, nls.localize('comparingMovedCode', "Whether a moved code block is selected for comparison"));
export const accessibleDiffViewerVisible = new RawContextKey<boolean>('accessibleDiffViewerVisible', false, nls.localize('accessibleDiffViewerVisible', "Whether the accessible diff viewer is visible"));
export const diffEditorRenderSideBySideInlineBreakpointReached = new RawContextKey<boolean>('diffEditorRenderSideBySideInlineBreakpointReached', false, nls.localize('diffEditorRenderSideBySideInlineBreakpointReached', "Whether the diff editor render side by side inline breakpoint is reached"));
export const diffEditorInlineMode = new RawContextKey<boolean>('diffEditorInlineMode', false, nls.localize('diffEditorInlineMode', "Whether inline mode is active"));
export const diffEditorOriginalWritable = new RawContextKey<boolean>('diffEditorOriginalWritable', false, nls.localize('diffEditorOriginalWritable', "Whether modified is writable in the diff editor"));
export const diffEditorModifiedWritable = new RawContextKey<boolean>('diffEditorModifiedWritable', false, nls.localize('diffEditorModifiedWritable', "Whether modified is writable in the diff editor"));
export const diffEditorOriginalUri = new RawContextKey<string>('diffEditorOriginalUri', '', nls.localize('diffEditorOriginalUri', "The uri of the original document"));
export const diffEditorModifiedUri = new RawContextKey<string>('diffEditorModifiedUri', '', nls.localize('diffEditorModifiedUri', "The uri of the modified document"));
export const columnSelection = new RawContextKey<boolean>('editorColumnSelection', false, nls.localize('editorColumnSelection', "Whether `editor.columnSelection` is enabled"));
export const writable = readOnly.toNegated();
export const hasNonEmptySelection = new RawContextKey<boolean>('editorHasSelection', false, nls.localize('editorHasSelection', "Whether the editor has text selected"));

4
src/vs/monaco.d.ts vendored
View file

@ -3818,6 +3818,10 @@ declare namespace monaco.editor {
* Default to true.
*/
renderMarginRevertIcon?: boolean;
/**
* Indicates if the gutter menu should be rendered.
*/
renderGutterMenu?: boolean;
/**
* Original model should be editable?
* Defaults to false.

View file

@ -220,6 +220,9 @@ export class MenuId {
static readonly ChatInputSide = new MenuId('ChatInputSide');
static readonly AccessibleView = new MenuId('AccessibleView');
static readonly MultiDiffEditorFileToolbar = new MenuId('MultiDiffEditorFileToolbar');
static readonly DiffEditorHunkToolbar = new MenuId('DiffEditorHunkToolbar');
static readonly DiffEditorSelectionToolbar = new MenuId('DiffEditorSelectionToolbar');
/**
* Create or reuse a `MenuId` with the given identifier

View file

@ -441,6 +441,18 @@ const apiMenus: IAPIMenu[] = [
id: MenuId.MultiDiffEditorFileToolbar,
description: localize('menus.multiDiffEditorResource', "The resource toolbar in the multi diff editor"),
proposed: 'contribMultiDiffEditorMenus'
},
{
key: 'diffEditor/gutter/hunk',
id: MenuId.DiffEditorHunkToolbar,
description: localize('menus.diffEditorGutterToolBarMenus', "The gutter toolbar in the diff editor"),
proposed: 'contribDiffEditorGutterToolBarMenus'
},
{
key: 'diffEditor/gutter/selection',
id: MenuId.DiffEditorSelectionToolbar,
description: localize('menus.diffEditorGutterToolBarMenus', "The gutter toolbar in the diff editor"),
proposed: 'contribDiffEditorGutterToolBarMenus'
}
];

View file

@ -29,6 +29,7 @@ export const allApiProposals = Object.freeze({
contribCommentPeekContext: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribCommentPeekContext.d.ts',
contribCommentThreadAdditionalMenu: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribCommentThreadAdditionalMenu.d.ts',
contribCommentsViewThreadMenus: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribCommentsViewThreadMenus.d.ts',
contribDiffEditorGutterToolBarMenus: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribDiffEditorGutterToolBarMenus.d.ts',
contribEditSessions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribEditSessions.d.ts',
contribEditorContentMenu: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribEditorContentMenu.d.ts',
contribIssueReporter: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribIssueReporter.d.ts',

View file

@ -0,0 +1,6 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// empty placeholder declaration for `diffEditor/gutter/*` menus