Implements first iteration of multi diff editors.

Co-authored-by: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com>
This commit is contained in:
Henning Dieterichs 2023-11-07 16:49:15 +01:00
parent 93351c7436
commit 090fd2c772
No known key found for this signature in database
GPG key ID: 771381EFFDB9EC06
35 changed files with 914 additions and 14 deletions

View file

@ -74,6 +74,10 @@
"name": "vs/workbench/contrib/dialogs",
"project": "vscode-workbench"
},
{
"name": "vs/workbench/contrib/multiDiffEditor",
"project": "vscode-workbench"
},
{
"name": "vs/workbench/contrib/emmet",
"project": "vscode-workbench"

View file

@ -417,6 +417,7 @@
"--vscode-minimapSlider-activeBackground",
"--vscode-minimapSlider-background",
"--vscode-minimapSlider-hoverBackground",
"--vscode-multiDiffEditor-headerBackground",
"--vscode-notebook-cellBorderColor",
"--vscode-notebook-cellEditorBackground",
"--vscode-notebook-cellHoverBackground",

View file

@ -694,6 +694,12 @@
"title": "%command.timelineCompareWithSelected%",
"category": "Git"
},
{
"command": "git.timeline.openCommit",
"title": "%command.timelineOpenCommit%",
"icon": "$(wand)",
"category": "Git"
},
{
"command": "git.rebaseAbort",
"title": "%command.rebaseAbort%",
@ -753,6 +759,18 @@
"command": "git.openRepositoriesInParentFolders",
"title": "%command.openRepositoriesInParentFolders%",
"category": "Git"
},
{
"command": "git.viewChanges",
"title": "View Changes",
"icon": "$(search)",
"category": "Git"
},
{
"command": "git.viewStagedChanges",
"title": "View Staged Changes",
"icon": "$(search)",
"category": "Git"
}
],
"continueEditSession": [
@ -1197,6 +1215,10 @@
"command": "git.timeline.compareWithSelected",
"when": "false"
},
{
"command": "git.timeline.openCommit",
"when": "false"
},
{
"command": "git.closeAllDiffEditors",
"when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0"
@ -1224,6 +1246,14 @@
{
"command": "git.openRepositoriesInParentFolders",
"when": "config.git.enabled && !git.missing && git.parentRepositoryCount != 0"
},
{
"command": "git.viewChanges",
"when": "false"
},
{
"command": "git.viewStagedChanges",
"when": "false"
}
],
"scm/title": [
@ -1390,6 +1420,16 @@
"command": "git.stageAllUntracked",
"when": "scmProvider == git && scmResourceGroup == untracked",
"group": "inline@2"
},
{
"command": "git.viewStagedChanges",
"when": "scmProvider == git && scmResourceGroup == index",
"group": "inline@1"
},
{
"command": "git.viewChanges",
"when": "scmProvider == git && scmResourceGroup == workingTree",
"group": "inline@1"
}
],
"scm/resourceFolder/context": [
@ -1767,11 +1807,21 @@
}
],
"timeline/item/context": [
{
"command": "git.timeline.openCommit",
"group": "inline",
"when": "config.git.enabled && !git.missing && timelineItem =~ /git:file:commit\\b/ && !listMultiSelection"
},
{
"command": "git.timeline.openDiff",
"group": "1_actions",
"group": "1_actions@1",
"when": "config.git.enabled && !git.missing && timelineItem =~ /git:file\\b/ && !listMultiSelection"
},
{
"command": "git.timeline.openCommit",
"group": "1_actions@2",
"when": "config.git.enabled && !git.missing && timelineItem =~ /git:file:commit\\b/ && !listMultiSelection"
},
{
"command": "git.timeline.compareWithSelected",
"group": "3_compare@1",

View file

@ -104,6 +104,7 @@
"command.stashDrop": "Drop Stash...",
"command.stashDropAll": "Drop All Stashes...",
"command.timelineOpenDiff": "Open Changes",
"command.timelineOpenCommit": "Open Commit",
"command.timelineCopyCommitId": "Copy Commit ID",
"command.timelineCopyCommitMessage": "Copy Commit Message",
"command.timelineSelectForCompare": "Select for Compare",

View file

@ -3426,6 +3426,54 @@ export class CommandCenter {
};
}
@command('git.timeline.openCommit', { repository: false })
async timelineOpenCommit(item: TimelineItem, uri: Uri | undefined, _source: string) {
console.log('timelineOpenCommit', item);
if (!GitTimelineItem.is(item)) {
return;
}
const cmd = await this._resolveTimelineOpenCommitCommand(
item, uri,
{
preserveFocus: true,
preview: true,
viewColumn: ViewColumn.Active
},
);
if (cmd === undefined) {
return undefined;
}
return commands.executeCommand(cmd.command, ...(cmd.arguments ?? []));
}
private async _resolveTimelineOpenCommitCommand(item: TimelineItem, uri: Uri | undefined, options?: TextDocumentShowOptions): Promise<Command | undefined> {
if (uri === undefined || uri === null || !GitTimelineItem.is(item)) {
return undefined;
}
const repository = await this.model.getRepository(uri.fsPath);
if (!repository) {
return undefined;
}
const commit = await repository.getCommit(item.ref);
const commitFiles = await repository.getCommitFiles(item.ref);
const args: [Uri, Uri | undefined, Uri | undefined][] = [];
for (const commitFile of commitFiles) {
const commitFileUri = Uri.file(path.join(repository.root, commitFile));
args.push([commitFileUri, toGitUri(commitFileUri, item.previousRef), toGitUri(commitFileUri, item.ref)]);
}
return {
command: 'vscode.changes',
title: l10n.t('Open Commit'),
arguments: [`${item.shortRef} - ${commit.message}`, args, options]
};
}
@command('git.timeline.copyCommitId', { repository: false })
async timelineCopyCommitId(item: TimelineItem, _uri: Uri | undefined, _source: string) {
if (!GitTimelineItem.is(item)) {
@ -3603,6 +3651,26 @@ export class CommandCenter {
repository.generateCommitMessageCancel();
}
@command('git.viewChanges', { repository: true })
viewChanges(repository: Repository): void {
this._viewChanges('Changes', repository.workingTreeGroup.resourceStates);
}
@command('git.viewStagedChanges', { repository: true })
viewStagedChanges(repository: Repository): void {
this._viewChanges('Staged Changes', repository.indexGroup.resourceStates);
}
private _viewChanges(title: string, resources: Resource[]): void {
const args: [Uri, Uri | undefined, Uri | undefined][] = [];
for (const resource of resources) {
args.push([resource.resourceUri, resource.leftUri, resource.rightUri]);
}
commands.executeCommand('vscode.changes', title, args);
}
private createCommand(id: string, key: string, method: Function, options: ScmCommandOptions): (...args: any[]) => any {
const result = (...args: any[]) => {
let result: Promise<any>;

View file

@ -2552,6 +2552,11 @@ export class Repository {
return commits[0];
}
async getCommitFiles(ref: string): Promise<string[]> {
const result = await this.exec(['diff-tree', '--no-commit-id', '--name-only', '-r', ref]);
return result.stdout.split('\n').filter(l => !!l);
}
async getCommitCount(range: string): Promise<{ ahead: number; behind: number }> {
const result = await this.exec(['rev-list', '--count', '--left-right', range]);
const [ahead, behind] = result.stdout.trim().split('\t');

View file

@ -1658,6 +1658,10 @@ export class Repository implements Disposable {
return await this.repository.getCommit(ref);
}
async getCommitFiles(ref: string): Promise<string[]> {
return await this.repository.getCommitFiles(ref);
}
async getCommitCount(range: string): Promise<{ ahead: number; behind: number }> {
return await this.run(Operation.RevList, () => this.repository.getCommitCount(range));
}

View file

@ -358,6 +358,11 @@ export class ViewZones extends ViewPart {
let hasVisibleZone = false;
for (const visibleWhitespace of visibleWhitespaces) {
if (!this._zones[visibleWhitespace.id]) {
// This zone got deleted in the meantime?
// TODO@hediet: This should not happen.
continue;
}
if (this._zones[visibleWhitespace.id].isInHiddenArea) {
continue;
}

View file

@ -1491,7 +1491,6 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
}
this._overlayWidgets[widget.getId()] = widgetData;
if (this._modelData && this._modelData.hasRealView) {
this._modelData.view.addOverlayWidget(widgetData);
}

View file

@ -143,7 +143,6 @@ export class DiffEditorEditors extends Disposable {
// Clone scrollbar options before changing them
clonedOptions.scrollbar = { ...(clonedOptions.scrollbar || {}) };
clonedOptions.scrollbar.vertical = 'visible';
clonedOptions.folding = false;
clonedOptions.codeLens = this._options.diffCodeLens.get();
clonedOptions.fixedOverflowWidgets = true;

View file

@ -17,6 +17,7 @@ export interface IDocumentDiffProviderOptions {
export interface IDiffProviderFactoryService {
readonly _serviceBrand: undefined;
// TODO, don't include IDiffEditor
createDiffProvider(editor: IDiffEditor, options: IDocumentDiffProviderOptions): IDocumentDiffProvider;
}

View file

@ -0,0 +1,13 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls';
import { registerColor } from 'vs/platform/theme/common/colorRegistry';
export const multiDiffEditorHeaderBackground = registerColor(
'multiDiffEditor.headerBackground',
{ dark: '#808080', light: '#b4b4b4', hcDark: '#808080', hcLight: '#b4b4b4', },
localize('multiDiffEditor.headerBackground', 'The background color of the diff editor\'s header')
);

View file

@ -0,0 +1,40 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Event } from 'vs/base/common/event';
import { ITextModel } from 'vs/editor/common/model';
export interface IMultiDocumentDiffEditorModel {
readonly diffs: LazyPromise<IDiffEntry>[];
readonly onDidChange: Event<void>;
}
export interface LazyPromise<T> {
request(): Promise<T>;
readonly value: T | undefined;
readonly onHasValueDidChange: Event<void>;
}
export class ConstLazyPromise<T> implements LazyPromise<T> {
public readonly onHasValueDidChange = Event.None;
constructor(
private readonly _value: T
) { }
public request(): Promise<T> {
return Promise.resolve(this._value);
}
public get value(): T {
return this._value;
}
}
export interface IDiffEntry {
readonly title: string;
readonly original: ITextModel | undefined; // undefined if the file was created.
readonly modified: ITextModel | undefined; // undefined if the file was deleted.
}

View file

@ -0,0 +1,44 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Dimension } from 'vs/base/browser/dom';
import { Disposable } from 'vs/base/common/lifecycle';
import { derivedWithStore, observableValue, recomputeInitiallyAndOnChange } from 'vs/base/common/observable';
import { readHotReloadableExport } from 'vs/editor/browser/widget/diffEditor/utils';
import { IMultiDocumentDiffEditorModel } from 'vs/editor/browser/widget/multiDiffEditorWidget/model';
import { MultiDiffEditorWidgetImpl } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import './colors';
export class MultiDiffEditorWidget extends Disposable {
private readonly _dimension = observableValue<Dimension | undefined>(this, undefined);
private readonly _model = observableValue<IMultiDocumentDiffEditorModel | undefined>(this, undefined);
private readonly widgetImpl = derivedWithStore(this, (reader, store) => {
return store.add(this._instantiationService.createInstance((
readHotReloadableExport(MultiDiffEditorWidgetImpl, reader)),
this._element,
this._dimension,
this._model,
));
});
constructor(
private readonly _element: HTMLElement,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
) {
super();
this._register(recomputeInitiallyAndOnChange(this.widgetImpl));
}
public setModel(model: IMultiDocumentDiffEditorModel): void {
this._model.set(model, undefined);
}
public layout(dimension: Dimension): void {
this._dimension.set(dimension, undefined);
}
}

View file

@ -0,0 +1,292 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Dimension, getWindow, h, scheduleAtNextAnimationFrame } from 'vs/base/browser/dom';
import { SmoothScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
import { Disposable, IReference, toDisposable } from 'vs/base/common/lifecycle';
import { IObservable, IReader, ISettableObservable, autorun, constObservable, observableFromEvent, observableValue } from 'vs/base/common/observable';
import { globalTransaction } from 'vs/base/common/observableInternal/base';
import { Scrollable, ScrollbarVisibility } from 'vs/base/common/scrollable';
import 'vs/css!./style';
import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget';
import { ObservableElementSizeObserver } from 'vs/editor/browser/widget/diffEditor/utils';
import { IDiffEntry, IMultiDocumentDiffEditorModel, LazyPromise } from 'vs/editor/browser/widget/multiDiffEditorWidget/model';
import { OffsetRange } from 'vs/editor/common/core/offsetRange';
import { IDiffEditorViewModel } from 'vs/editor/common/editorCommon';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ILabelService } from 'vs/platform/label/common/label';
import { ObjectPool } from './objectPool';
export class MultiDiffEditorWidgetImpl extends Disposable {
private readonly _elements = h('div', {
style: {
overflowY: 'hidden',
}
}, [
h('div@content', {
style: {
overflow: 'hidden',
}
})
]);
private readonly _sizeObserver = new ObservableElementSizeObserver(this._element, undefined);
private readonly _documentsObs = this._model.map(this, m => !m ? constObservable([]) : observableFromEvent(m.onDidChange, /** @description Documents changed */() => m.diffs));
private readonly _documents = this._documentsObs.map(this, (m, reader) => m.read(reader));
private readonly _objectPool = new ObjectPool<DiffEditorItemTemplate>(() => this._instantiationService.createInstance(DiffEditorItemTemplate, this._elements.content));
private readonly _hiddenContainer = document.createElement('div');
private readonly _editor = this._register(this._instantiationService.createInstance(DiffEditorWidget, this._hiddenContainer, {
hideUnchangedRegions: {
enabled: true,
},
}, {}));
private readonly _viewItems = this._documents.map(this,
docs => docs.map(d => new DiffEditorItem(this._objectPool, d, this._editor))
);
private readonly _totalHeight = this._viewItems.map(this, (items, reader) => items.reduce((r, i) => r + i.contentHeight.read(reader), 0));
private readonly scrollTop: IObservable<number>;
constructor(
private readonly _element: HTMLElement,
private readonly _dimension: IObservable<Dimension | undefined>,
private readonly _model: IObservable<IMultiDocumentDiffEditorModel | undefined>,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
) {
super();
this._sizeObserver.setAutomaticLayout(true);
this._register(autorun((reader) => {
/** @description Update widget dimension */
const dimension = this._dimension.read(reader);
this._sizeObserver.observe(dimension);
}));
const scrollable = this._register(new Scrollable({
forceIntegerValues: false,
scheduleAtNextAnimationFrame: (cb) => scheduleAtNextAnimationFrame(getWindow(_element), cb),
smoothScrollDuration: 100,
}));
this._elements.content.style.position = 'relative';
const scrollableElement = this._register(new SmoothScrollableElement(this._elements.root, {
vertical: ScrollbarVisibility.Auto,
className: 'monaco-component',
}, scrollable));
this.scrollTop = observableFromEvent(scrollableElement.onScroll, () => /** @description onScroll */ scrollableElement.getScrollPosition().scrollTop);
this._register(autorun((reader) => {
/** @description Update scroll dimensions */
const height = this._sizeObserver.height.read(reader);
this._elements.root.style.height = `${height}px`;
const totalHeight = this._totalHeight.read(reader);
this._elements.content.style.height = `${totalHeight}px`;
scrollableElement.setScrollDimensions({
width: _element.clientWidth,
height: height,
scrollHeight: totalHeight,
scrollWidth: _element.clientWidth,
});
}));
_element.replaceChildren(scrollableElement.getDomNode());
this._register(toDisposable(() => {
_element.replaceChildren();
}));
this._register(this._register(autorun(reader => {
/** @description Render all */
this.render(reader);
})));
}
private render(reader: IReader | undefined) {
const scrollTop = this.scrollTop.read(reader);
let contentScrollOffsetToScrollOffset = 0;
let itemHeightSumBefore = 0;
let itemContentHeightSumBefore = 0;
const viewPortHeight = this._elements.root.clientHeight;
const contentViewPort = OffsetRange.ofStartAndLength(scrollTop, viewPortHeight);
const width = this._elements.content.clientWidth;
for (const v of this._viewItems.read(reader)) {
const itemContentHeight = v.contentHeight.read(reader);
const itemHeight = Math.min(itemContentHeight, viewPortHeight);
const itemRange = OffsetRange.ofStartAndLength(itemHeightSumBefore, itemHeight);
const itemContentRange = OffsetRange.ofStartAndLength(itemContentHeightSumBefore, itemContentHeight);
if (itemContentRange.isBefore(contentViewPort)) {
contentScrollOffsetToScrollOffset -= itemContentHeight - itemHeight;
v.hide();
} else if (itemContentRange.isAfter(contentViewPort)) {
v.hide();
} else {
const scroll = Math.max(0, Math.min(contentViewPort.start - itemContentRange.start, itemContentHeight - itemHeight));
v.render(itemRange, scroll, width);
contentScrollOffsetToScrollOffset -= scroll;
}
itemHeightSumBefore += itemHeight;
itemContentHeightSumBefore += itemContentHeight;
}
this._elements.content.style.transform = `translateY(${-(scrollTop + contentScrollOffsetToScrollOffset)}px)`;
}
}
class DiffEditorItem {
private readonly _height = observableValue(this, 500);
public readonly contentHeight: IObservable<number> = this._height;
private _templateRef: IReference<DiffEditorItemTemplate> | undefined;
private _vm: IDiffEditorViewModel | undefined;
constructor(
private readonly _objectPool: ObjectPool<DiffEditorItemTemplate>,
private readonly _entry: LazyPromise<IDiffEntry>,
baseDiffEditorWidget: DiffEditorWidget,
) {
this._vm = baseDiffEditorWidget.createViewModel({
original: _entry.value!.original!,
modified: _entry.value!.modified!,
});
}
public toString(): string {
return `ViewItem(${this._entry.value!.title})`;
}
public hide(): void {
this._templateRef?.object.hide();
this._templateRef?.dispose();
this._templateRef = undefined;
}
public render(verticalSpace: OffsetRange, offset: number, width: number): void {
if (!this._templateRef) {
this._templateRef = this._objectPool.getUnusedObj();
this._templateRef.object.setData({ height: this._height, viewModel: this._vm!, entry: this._entry.value! });
}
this._templateRef.object.render(verticalSpace, width, offset);
}
}
class DiffEditorItemTemplate extends Disposable {
private _height: number = 500;
private _heightObs: ISettableObservable<number> | undefined = undefined;
private readonly _elements = h('div', {
style: {
display: 'flex',
flexDirection: 'column',
}
}, [
h('div', {
style: {
display: 'flex',
flexDirection: 'column',
flex: '1',
border: '1px solid #4d4d4d',
borderRadius: '5px',
overflow: 'hidden',
margin: '10px 10px 10px 10px',
}
}, [
h('div', { style: { display: 'flex', alignItems: 'center', padding: '8px 5px', background: 'var(--vscode-multiDiffEditor-headerBackground)', color: 'black' } }, [
//h('div.expand-button@collapseButton', { style: { margin: '0 5px' } }),
h('div@title', { style: { fontSize: '14px' } }, ['Title'] as any),
]),
h('div', {
style: {
flex: '1',
display: 'flex',
flexDirection: 'column',
}
}, [
h('div@editor', { style: { flex: '1' } }),
])
])
]);
private readonly editor = this._register(this._instantiationService.createInstance(DiffEditorWidget, this._elements.editor, {
automaticLayout: true,
scrollBeyondLastLine: false,
hideUnchangedRegions: {
enabled: true,
},
scrollbar: {
vertical: 'hidden',
handleMouseWheel: false,
},
renderOverviewRuler: false,
}, {
modifiedEditor: {
contributions: [],
},
originalEditor: {
contributions: [],
}
}));
constructor(
private readonly _container: HTMLElement,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@ILabelService private readonly _labelService: ILabelService,
) {
super();
// TODO@hediet
/*
const btn = new Button(this._elements.collapseButton, {});
btn.icon = Codicon.chevronDown;
*/
this._register(this.editor.onDidContentSizeChange(e => {
this._height = e.contentHeight + this._elements.root.clientHeight - this._elements.editor.clientHeight;
globalTransaction(tx => {
this._heightObs?.set(this._height, tx);
});
}));
this._container.appendChild(this._elements.root);
}
public setData(data: { height: ISettableObservable<number>; viewModel: IDiffEditorViewModel; entry: IDiffEntry }) {
this._heightObs = data.height;
globalTransaction(tx => {
this.editor.setModel(data.viewModel, tx);
this._heightObs!.set(this._height, tx);
});
this._elements.title.innerText = this._labelService.getUriLabel(data.viewModel.model.modified.uri, { relative: true }); // data.entry.title;
}
public hide(): void {
this._elements.root.style.top = `-100000px`;
this._elements.root.style.visibility = 'hidden'; // Some editor parts are still visible
}
public render(verticalRange: OffsetRange, width: number, editorScroll: number): void {
this._elements.root.style.visibility = 'visible';
this._elements.root.style.top = `${verticalRange.start}px`;
this._elements.root.style.height = `${verticalRange.length}px`;
this._elements.root.style.width = `${width}px`;
this._elements.root.style.position = 'absolute';
this.editor.getOriginalEditor().setScrollTop(editorScroll);
}
}

View file

@ -0,0 +1,36 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IDisposable, IReference } from 'vs/base/common/lifecycle';
export class ObjectPool<T extends IDisposable> {
private readonly _unused = new Set<T>();
private readonly _used = new Set<T>();
constructor(
private readonly _create: () => T
) { }
public getUnusedObj(): IReference<T> {
let obj: T;
if (this._unused.size === 0) {
obj = this._create();
} else {
obj = this._unused.values().next().value;
this._unused.delete(obj);
}
this._used.add(obj);
return {
object: obj,
dispose: () => {
this._used.delete(obj);
if (this._unused.size > 5) {
obj.dispose();
} else {
this._unused.add(obj);
}
}
};
}
}

View file

@ -0,0 +1,8 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-component .expand-button a {
display: block;
}

View file

@ -43,6 +43,10 @@ export class OffsetRange implements IOffsetRange {
return new OffsetRange(0, length);
}
public static ofStartAndLength(start: number, length: number): OffsetRange {
return new OffsetRange(start, start + length);
}
constructor(public readonly start: number, public readonly endExclusive: number) {
if (start > endExclusive) {
throw new BugIndicatingError(`Invalid range: ${this.toString()}`);
@ -114,6 +118,14 @@ export class OffsetRange implements IOffsetRange {
return start <= end;
}
public isBefore(other: OffsetRange): boolean {
return this.endExclusive <= other.start;
}
public isAfter(other: OffsetRange): boolean {
return this.start >= other.endExclusive;
}
public slice<T>(arr: T[]): T[] {
return arr.slice(this.start, this.endExclusive);
}

View file

@ -38,6 +38,7 @@ import { ITextResourceEditorInput } from 'vs/platform/editor/common/editor';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IMarker, IMarkerData, IMarkerService } from 'vs/platform/markers/common/markers';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { MultiDiffEditorWidget } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget';
/**
* Create a new editor under `domElement`.
@ -98,6 +99,11 @@ export function createDiffEditor(domElement: HTMLElement, options?: IStandaloneD
return instantiationService.createInstance(StandaloneDiffEditor2, domElement, options);
}
export function createMultiFileDiffEditor(domElement: HTMLElement, override?: IEditorOverrideServices) {
const instantiationService = StandaloneServices.initialize(override || {});
return new MultiDiffEditorWidget(domElement, instantiationService);
}
/**
* Description of a command contribution
*/
@ -567,6 +573,8 @@ export function createMonacoEditorAPI(): typeof monaco.editor {
ApplyUpdateResult: <any>ApplyUpdateResult,
EditorZoom: <any>EditorZoom,
createMultiFileDiffEditor: <any>createMultiFileDiffEditor,
// vars
EditorType: EditorType,
EditorOptions: <any>EditorOptions

View file

@ -393,7 +393,7 @@ export class StandaloneThemeService extends Disposable implements IStandaloneThe
colorVariables.push(`${asCssVariableName(item.id)}: ${color.toString()};`);
}
}
ruleCollector.addRule(`.monaco-editor, .monaco-diff-editor { ${colorVariables.join('\n')} }`);
ruleCollector.addRule(`.monaco-editor, .monaco-diff-editor, .monaco-component { ${colorVariables.join('\n')} }`);
const colorMap = this._colorMapOverride || this._theme.tokenTheme.getColorMap();
ruleCollector.addRule(generateTokensCSSForColorMap(colorMap));

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

@ -974,6 +974,8 @@ declare namespace monaco.editor {
*/
export function createDiffEditor(domElement: HTMLElement, options?: IStandaloneDiffEditorConstructionOptions, override?: IEditorOverrideServices): IStandaloneDiffEditor;
export function createMultiFileDiffEditor(domElement: HTMLElement, override?: IEditorOverrideServices): any;
/**
* Description of a command contribution
*/

View file

@ -432,6 +432,31 @@ const newCommands: ApiCommand[] = [
],
ApiCommandResult.Void
),
new ApiCommand(
'vscode.changes', '_workbench.changes', 'Opens a list of resources in the changes editor to compare their contents.',
[
ApiCommandArgument.String.with('title', 'Human readable title for the changes editor'),
new ApiCommandArgument<[URI, URI?, URI?][]>('resourceList', 'List of resources to compare',
resources => {
for (const resource of resources) {
if (resource.length !== 3) {
return false;
}
const [label, left, right] = resource;
if (!URI.isUri(label) ||
(!URI.isUri(left) && left !== undefined && left !== null) ||
(!URI.isUri(right) && right !== undefined && right !== null)) {
return false;
}
}
return true;
},
v => v)
],
ApiCommandResult.Void
),
// --- type hierarchy
new ApiCommand(
'vscode.prepareTypeHierarchy', '_executePrepareTypeHierarchy', 'Prepare type hierarchy at a position inside a document',

View file

@ -461,7 +461,7 @@ class ExtHostSourceControlResourceGroup implements vscode.SourceControlResourceG
let command: ICommandDto | undefined;
if (r.command) {
if (r.command.command === 'vscode.open' || r.command.command === 'vscode.diff') {
if (r.command.command === 'vscode.open' || r.command.command === 'vscode.diff' || r.command.command === 'vscode.changes') {
const disposables = new DisposableStore();
command = this._commands.converter.toInternal(r.command, disposables);
this._resourceStatesDisposablesMap.set(handle, disposables);

View file

@ -7,7 +7,7 @@ import { localize } from 'vs/nls';
import { isObject, isString, isUndefined, isNumber } from 'vs/base/common/types';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { IEditorIdentifier, IEditorCommandsContext, CloseDirection, IVisibleEditorPane, EditorsOrder, EditorInputCapabilities, isEditorIdentifier, isEditorInputWithOptionsAndGroup, IUntitledTextResourceEditorInput } from 'vs/workbench/common/editor';
import { IEditorIdentifier, IEditorCommandsContext, CloseDirection, IVisibleEditorPane, EditorsOrder, EditorInputCapabilities, isEditorIdentifier, isEditorInputWithOptionsAndGroup, IUntitledTextResourceEditorInput, IResourceDiffEditorInput } from 'vs/workbench/common/editor';
import { TextCompareEditorVisibleContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, ActiveEditorStickyContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, TextCompareEditorActiveContext, SideBySideEditorActiveContext } from 'vs/workbench/common/contextkeys';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
import { EditorGroupColumn, columnToEditorGroup } from 'vs/workbench/services/editor/common/editorGroupColumn';
@ -632,6 +632,39 @@ function registerOpenEditorAPICommands(): void {
await editorService.openEditor({ resource: URI.from(resource, true), options: { ...optionsArg, pinned: true, override: id } }, columnToEditorGroup(editorGroupsService, configurationService, columnArg));
});
// partial, renderer-side API command to open diff editor
// complements https://github.com/microsoft/vscode/blob/2b164efb0e6a5de3826bff62683eaeafe032284f/src/vs/workbench/api/common/extHostApiCommands.ts#L397
CommandsRegistry.registerCommand({
id: 'vscode.changes',
handler: (accessor, title: string, resources: [UriComponents, UriComponents?, UriComponents?][]) => {
accessor.get(ICommandService).executeCommand('_workbench.changes', title, resources);
},
metadata: {
description: 'Opens a list of resources in the changes editor to compare their contents.',
args: [
{ name: 'title', description: 'Human readable title for the diff editor' },
{ name: 'resources', description: 'List of resources to open in the changes editor' }
]
}
});
CommandsRegistry.registerCommand('_workbench.changes', async (accessor: ServicesAccessor, title: string, resources: [UriComponents, UriComponents?, UriComponents?][]) => {
const editorService = accessor.get(IEditorService);
// const editorGroupService = accessor.get(IEditorGroupsService);
// const configurationService = accessor.get(IConfigurationService);
const editor: (IResourceDiffEditorInput & { resource: URI })[] = [];
for (const [label, original, modified] of resources) {
editor.push({
resource: URI.revive(label),
original: { resource: URI.revive(original) },
modified: { resource: URI.revive(modified) },
});
}
await editorService.openEditor({ resources: editor, label: title });
});
}
function registerOpenEditorAtIndexCommands(): void {

View file

@ -489,6 +489,17 @@ export interface IResourceDiffEditorInput extends IBaseUntypedEditorInput {
readonly modified: IResourceEditorInput | ITextResourceEditorInput | IUntitledTextResourceEditorInput;
}
/**
* A resource list diff editor input compares multiple resources side by side
* highlighting the differences.
*/
export interface IResourceMultiDiffEditorInput extends IBaseUntypedEditorInput {
/**
* The list of resources to compare.
*/
readonly resources: (IResourceDiffEditorInput & { resource: URI })[];
}
export type IResourceMergeEditorInputSide = (IResourceEditorInput | ITextResourceEditorInput) & { detail?: string };
/**
@ -541,6 +552,16 @@ export function isResourceDiffEditorInput(editor: unknown): editor is IResourceD
return candidate?.original !== undefined && candidate.modified !== undefined;
}
export function isResourceDiffListEditorInput(editor: unknown): editor is IResourceMultiDiffEditorInput {
if (isEditorInput(editor)) {
return false; // make sure to not accidentally match on typed editor inputs
}
const candidate = editor as IResourceMultiDiffEditorInput | undefined;
return Array.isArray(candidate?.resources);
}
export function isResourceSideBySideEditorInput(editor: unknown): editor is IResourceSideBySideEditorInput {
if (isEditorInput(editor)) {
return false; // make sure to not accidentally match on typed editor inputs
@ -760,7 +781,7 @@ export const enum EditorInputCapabilities {
AuxWindowUnsupported = 1 << 10
}
export type IUntypedEditorInput = IResourceEditorInput | ITextResourceEditorInput | IUntitledTextResourceEditorInput | IResourceDiffEditorInput | IResourceSideBySideEditorInput | IResourceMergeEditorInput;
export type IUntypedEditorInput = IResourceEditorInput | ITextResourceEditorInput | IUntitledTextResourceEditorInput | IResourceDiffEditorInput | IResourceMultiDiffEditorInput | IResourceSideBySideEditorInput | IResourceMergeEditorInput;
export abstract class AbstractEditorInput extends Disposable {
// Marker class for implementing `isEditorInput`
@ -1260,7 +1281,7 @@ class EditorResourceAccessorImpl {
}
}
if (isResourceDiffEditorInput(editor) || isResourceSideBySideEditorInput(editor) || isResourceMergeEditorInput(editor)) {
if (isResourceDiffEditorInput(editor) || isResourceDiffListEditorInput(editor) || isResourceSideBySideEditorInput(editor) || isResourceMergeEditorInput(editor)) {
return undefined;
}
@ -1329,7 +1350,7 @@ class EditorResourceAccessorImpl {
}
}
if (isResourceDiffEditorInput(editor) || isResourceSideBySideEditorInput(editor) || isResourceMergeEditorInput(editor)) {
if (isResourceDiffEditorInput(editor) || isResourceDiffListEditorInput(editor) || isResourceSideBySideEditorInput(editor) || isResourceMergeEditorInput(editor)) {
return undefined;
}

View file

@ -9,7 +9,7 @@ import { URI } from 'vs/base/common/uri';
import { localize } from 'vs/nls';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { Registry } from 'vs/platform/registry/common/platform';
import { EditorInputCapabilities, GroupIdentifier, ISaveOptions, IRevertOptions, EditorExtensions, IEditorFactoryRegistry, IEditorSerializer, ISideBySideEditorInput, IUntypedEditorInput, isResourceSideBySideEditorInput, isDiffEditorInput, isResourceDiffEditorInput, IResourceSideBySideEditorInput, findViewStateForEditor, IMoveResult, isEditorInput, isResourceEditorInput, Verbosity, isResourceMergeEditorInput } from 'vs/workbench/common/editor';
import { EditorInputCapabilities, GroupIdentifier, ISaveOptions, IRevertOptions, EditorExtensions, IEditorFactoryRegistry, IEditorSerializer, ISideBySideEditorInput, IUntypedEditorInput, isResourceSideBySideEditorInput, isDiffEditorInput, isResourceDiffEditorInput, IResourceSideBySideEditorInput, findViewStateForEditor, IMoveResult, isEditorInput, isResourceEditorInput, Verbosity, isResourceMergeEditorInput, isResourceDiffListEditorInput } from 'vs/workbench/common/editor';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
@ -190,7 +190,7 @@ export class SideBySideEditorInput extends EditorInput implements ISideBySideEdi
return new SideBySideEditorInput(this.preferredName, this.preferredDescription, primarySaveResult, primarySaveResult, this.editorService);
}
if (!isResourceDiffEditorInput(primarySaveResult) && !isResourceSideBySideEditorInput(primarySaveResult) && !isResourceMergeEditorInput(primarySaveResult)) {
if (!isResourceDiffEditorInput(primarySaveResult) && !isResourceDiffListEditorInput(primarySaveResult) && !isResourceSideBySideEditorInput(primarySaveResult) && !isResourceMergeEditorInput(primarySaveResult)) {
return {
primary: primarySaveResult,
secondary: primarySaveResult,
@ -259,6 +259,7 @@ export class SideBySideEditorInput extends EditorInput implements ISideBySideEdi
if (
primaryResourceEditorInput && secondaryResourceEditorInput &&
!isResourceDiffEditorInput(primaryResourceEditorInput) && !isResourceDiffEditorInput(secondaryResourceEditorInput) &&
!isResourceDiffListEditorInput(primaryResourceEditorInput) && !isResourceDiffListEditorInput(secondaryResourceEditorInput) &&
!isResourceSideBySideEditorInput(primaryResourceEditorInput) && !isResourceSideBySideEditorInput(secondaryResourceEditorInput) &&
!isResourceMergeEditorInput(primaryResourceEditorInput) && !isResourceMergeEditorInput(secondaryResourceEditorInput)
) {

View file

@ -43,7 +43,7 @@ class DiffEditorHelperContribution extends Disposable implements IDiffEditorCont
const isEmbeddedDiffEditor = this._diffEditor instanceof EmbeddedDiffEditorWidget;
if (!isEmbeddedDiffEditor) {
const computationResult = observableFromEvent(e => this._diffEditor.onDidUpdateDiff(e), () => /** diffEditor.diffComputationResult */ this._diffEditor.getDiffComputationResult());
const computationResult = observableFromEvent(e => this._diffEditor.onDidUpdateDiff(e), () => /** @description diffEditor.diffComputationResult */ this._diffEditor.getDiffComputationResult());
const onlyWhiteSpaceChange = computationResult.map(r => r && !r.identical && r.changes2.length === 0);
this._register(autorunWithStore((reader, store) => {

View file

@ -0,0 +1,98 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls';
import { Disposable } from 'vs/base/common/lifecycle';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { Registry } from 'vs/platform/registry/common/platform';
import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor';
import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions';
import { DEFAULT_EDITOR_ASSOCIATION, EditorExtensions, EditorInputWithOptions, IEditorFactoryRegistry, IEditorSerializer, IResourceMultiDiffEditorInput } from 'vs/workbench/common/editor';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
import { IEditorResolverService, RegisteredEditorPriority } from 'vs/workbench/services/editor/common/editorResolverService';
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { parse } from 'vs/base/common/marshalling';
import { onUnexpectedError } from 'vs/base/common/errors';
import { MultiDiffEditorInput, MultiDiffEditorInputData } from 'vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput';
import { MultiDiffEditor } from 'vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor';
class MultiDiffEditorResolverContribution extends Disposable {
constructor(
@IEditorResolverService editorResolverService: IEditorResolverService,
@IInstantiationService instantiationService: IInstantiationService,
) {
super();
this._register(editorResolverService.registerEditor(
`*`,
{
id: DEFAULT_EDITOR_ASSOCIATION.id,
label: DEFAULT_EDITOR_ASSOCIATION.displayName,
detail: DEFAULT_EDITOR_ASSOCIATION.providerDisplayName,
priority: RegisteredEditorPriority.builtin
},
{},
{
createMultiDiffEditorInput: (diffListEditor: IResourceMultiDiffEditorInput): EditorInputWithOptions => {
return {
editor: instantiationService.createInstance(
MultiDiffEditorInput,
diffListEditor.label,
diffListEditor.resources.map(resource => {
return new MultiDiffEditorInputData(
resource.resource,
resource.original.resource,
resource.modified.resource
);
}))
};
}
}
));
}
}
Registry
.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench)
.registerWorkbenchContribution(MultiDiffEditorResolverContribution, LifecyclePhase.Starting);
class MultiDiffEditorSerializer implements IEditorSerializer {
canSerialize(editor: EditorInput): boolean {
return true;
}
serialize(editor: MultiDiffEditorInput): string | undefined {
return JSON.stringify({ label: editor.label, resources: editor.resources });
}
deserialize(instantiationService: IInstantiationService, serializedEditor: string): EditorInput | undefined {
try {
const data = parse(serializedEditor) as { label: string | undefined; resources: MultiDiffEditorInputData[] };
return instantiationService.createInstance(MultiDiffEditorInput, data.label, data.resources);
} catch (err) {
onUnexpectedError(err);
return undefined;
}
}
}
Registry.as<IEditorPaneRegistry>(EditorExtensions.EditorPane).registerEditorPane(
EditorPaneDescriptor.create(
MultiDiffEditor,
MultiDiffEditor.ID,
localize('name', "Multi Diff Editor")
),
[
new SyncDescriptor(MultiDiffEditorInput)
]
);
Registry.as<IEditorFactoryRegistry>(EditorExtensions.EditorFactory).registerEditorSerializer(
MultiDiffEditorInput.ID,
MultiDiffEditorSerializer
);

View file

@ -0,0 +1,63 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as DOM from 'vs/base/browser/dom';
import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane';
import { IEditorOpenContext } from 'vs/workbench/common/editor';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IEditorOptions } from 'vs/platform/editor/common/editor';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
import { MultiDiffEditorInput } from 'vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput';
import { MultiDiffEditorWidget } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget';
import { toDisposable } from 'vs/base/common/lifecycle';
import { ConstLazyPromise, IDiffEntry } from 'vs/editor/browser/widget/multiDiffEditorWidget/model';
export class MultiDiffEditor extends EditorPane {
static readonly ID = 'multiDiffEditor';
private _multiDiffEditorWidget: MultiDiffEditorWidget | undefined = undefined;
constructor(
@IInstantiationService private readonly instantiationService: InstantiationService,
@ITelemetryService telemetryService: ITelemetryService,
@ITextModelService private readonly textModelService: ITextModelService,
@IThemeService themeService: IThemeService,
@IStorageService storageService: IStorageService
) {
super(MultiDiffEditor.ID, telemetryService, themeService, storageService);
}
protected createEditor(parent: HTMLElement): void {
this._multiDiffEditorWidget = this._register(this.instantiationService.createInstance(MultiDiffEditorWidget, parent));
}
override async setInput(input: MultiDiffEditorInput, options: IEditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise<void> {
await super.setInput(input, options, context, token);
const rs = await Promise.all(input.resources.map(async r => ({
originalRef: await this.textModelService.createModelReference(r.original!),
modifiedRef: await this.textModelService.createModelReference(r.modified!),
title: r.resource.fsPath,
})));
this._multiDiffEditorWidget?.setModel({
onDidChange: () => toDisposable(() => { }),
diffs: rs.map(r => new ConstLazyPromise<IDiffEntry>({
original: r.originalRef.object.textEditorModel,
modified: r.modifiedRef.object.textEditorModel,
title: r.title,
})),
});
}
layout(dimension: DOM.Dimension): void {
this._multiDiffEditorWidget?.layout(dimension);
}
}

View file

@ -0,0 +1,45 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls';
import { URI } from 'vs/base/common/uri';
import { DEFAULT_EDITOR_ASSOCIATION, EditorInputCapabilities } from 'vs/workbench/common/editor';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
export class MultiDiffEditorInput extends EditorInput {
static readonly ID: string = 'workbench.input.multiDiffEditor';
get resource(): URI | undefined {
return undefined;
}
override get capabilities(): EditorInputCapabilities {
return EditorInputCapabilities.Readonly;
}
override get typeId(): string {
return MultiDiffEditorInput.ID;
}
override getName(): string {
return this.label ?? localize('name', "Multi Diff Editor");
}
override get editorId(): string {
return DEFAULT_EDITOR_ASSOCIATION.id;
}
constructor(readonly label: string | undefined, readonly resources: readonly MultiDiffEditorInputData[]) {
super();
}
}
export class MultiDiffEditorInputData {
constructor(
readonly resource: URI,
readonly original: URI | undefined,
readonly modified: URI | undefined
) { }
}

View file

@ -227,3 +227,7 @@ export interface ISCMViewService {
readonly onDidFocusRepository: Event<ISCMRepository | undefined>;
focus(repository: ISCMRepository): void;
}
export const SCM_CHANGES_EDITOR_ID = 'workbench.editor.scmChangesEditor';
export interface ISCMChangesEditor { }

View file

@ -10,7 +10,7 @@ import { basename, extname, isEqual } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { EditorActivation, EditorResolution, IEditorOptions } from 'vs/platform/editor/common/editor';
import { DEFAULT_EDITOR_ASSOCIATION, EditorResourceAccessor, EditorInputWithOptions, IResourceSideBySideEditorInput, isEditorInputWithOptions, isEditorInputWithOptionsAndGroup, isResourceDiffEditorInput, isResourceSideBySideEditorInput, isUntitledResourceEditorInput, isResourceMergeEditorInput, IUntypedEditorInput, SideBySideEditor } from 'vs/workbench/common/editor';
import { DEFAULT_EDITOR_ASSOCIATION, EditorResourceAccessor, EditorInputWithOptions, IResourceSideBySideEditorInput, isEditorInputWithOptions, isEditorInputWithOptionsAndGroup, isResourceDiffEditorInput, isResourceSideBySideEditorInput, isUntitledResourceEditorInput, isResourceMergeEditorInput, IUntypedEditorInput, SideBySideEditor, isResourceDiffListEditorInput } from 'vs/workbench/common/editor';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { Schemas } from 'vs/base/common/network';
@ -463,6 +463,15 @@ export class EditorResolverService extends Disposable implements IEditorResolver
return { editor: inputWithOptions.editor, options: inputWithOptions.options ?? options };
}
// If it's a diff list editor we trigger the create diff list editor input
if (isResourceDiffListEditorInput(editor)) {
if (!selectedEditor.editorFactoryObject.createMultiDiffEditorInput) {
return;
}
const inputWithOptions = await selectedEditor.editorFactoryObject.createMultiDiffEditorInput(editor, group);
return { editor: inputWithOptions.editor, options: inputWithOptions.options ?? options };
}
if (isResourceSideBySideEditorInput(editor)) {
throw new Error(`Untyped side by side editor input not supported here.`);
}

View file

@ -16,7 +16,7 @@ import { Extensions as ConfigurationExtensions, IConfigurationNode, IConfigurati
import { IResourceEditorInput, ITextResourceEditorInput } from 'vs/platform/editor/common/editor';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { Registry } from 'vs/platform/registry/common/platform';
import { EditorInputWithOptions, EditorInputWithOptionsAndGroup, IResourceDiffEditorInput, IResourceMergeEditorInput, IUntitledTextResourceEditorInput, IUntypedEditorInput } from 'vs/workbench/common/editor';
import { EditorInputWithOptions, EditorInputWithOptionsAndGroup, IResourceDiffEditorInput, IResourceMultiDiffEditorInput, IResourceMergeEditorInput, IUntitledTextResourceEditorInput, IUntypedEditorInput } from 'vs/workbench/common/editor';
import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService';
import { PreferredGroup } from 'vs/workbench/services/editor/common/editorService';
import { AtLeastOne } from 'vs/base/common/types';
@ -108,12 +108,15 @@ export type UntitledEditorInputFactoryFunction = (untitledEditorInput: IUntitled
export type DiffEditorInputFactoryFunction = (diffEditorInput: IResourceDiffEditorInput, group: IEditorGroup) => EditorInputFactoryResult;
export type DiffListEditorInputFactoryFunction = (diffEditorInput: IResourceMultiDiffEditorInput, group: IEditorGroup) => EditorInputFactoryResult;
export type MergeEditorInputFactoryFunction = (mergeEditorInput: IResourceMergeEditorInput, group: IEditorGroup) => EditorInputFactoryResult;
type EditorInputFactories = {
createEditorInput?: EditorInputFactoryFunction;
createUntitledEditorInput?: UntitledEditorInputFactoryFunction;
createDiffEditorInput?: DiffEditorInputFactoryFunction;
createMultiDiffEditorInput?: DiffListEditorInputFactoryFunction;
createMergeEditorInput?: MergeEditorInputFactoryFunction;
};

View file

@ -233,6 +233,9 @@ import 'vs/workbench/contrib/markers/browser/markers.contribution';
// Merge Editor
import 'vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution';
// Multi Diff Editor
import 'vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.contribution';
// Mapped Edits
import 'vs/workbench/contrib/mappedEdits/common/mappedEdits.contribution';

View file

@ -165,6 +165,9 @@ import 'vs/workbench/contrib/localHistory/electron-sandbox/localHistory.contribu
// Merge Editor
import 'vs/workbench/contrib/mergeEditor/electron-sandbox/mergeEditor.contribution';
// Multi Diff Editor
import 'vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.contribution';
// Remote Tunnel
import 'vs/workbench/contrib/remoteTunnel/electron-sandbox/remoteTunnel.contribution';