mirror of
https://github.com/Microsoft/vscode
synced 2024-08-27 04:49:35 +00:00
Implements first iteration of multi diff editors.
Co-authored-by: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com>
This commit is contained in:
parent
93351c7436
commit
090fd2c772
|
@ -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"
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -17,6 +17,7 @@ export interface IDocumentDiffProviderOptions {
|
|||
|
||||
export interface IDiffProviderFactoryService {
|
||||
readonly _serviceBrand: undefined;
|
||||
// TODO, don't include IDiffEditor
|
||||
createDiffProvider(editor: IDiffEditor, options: IDocumentDiffProviderOptions): IDocumentDiffProvider;
|
||||
}
|
||||
|
||||
|
|
13
src/vs/editor/browser/widget/multiDiffEditorWidget/colors.ts
Normal file
13
src/vs/editor/browser/widget/multiDiffEditorWidget/colors.ts
Normal 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')
|
||||
);
|
40
src/vs/editor/browser/widget/multiDiffEditorWidget/model.ts
Normal file
40
src/vs/editor/browser/widget/multiDiffEditorWidget/model.ts
Normal 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.
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
2
src/vs/monaco.d.ts
vendored
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
) {
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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
|
||||
);
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
) { }
|
||||
}
|
|
@ -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 { }
|
||||
|
|
|
@ -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.`);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
Loading…
Reference in a new issue