Skip to content

Commit

Permalink
Fixes #208220
Browse files Browse the repository at this point in the history
  • Loading branch information
hediet committed May 6, 2024
1 parent 3af934a commit e11c336
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 44 deletions.
38 changes: 36 additions & 2 deletions src/vs/base/common/observableInternal/derived.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
*--------------------------------------------------------------------------------------------*/

import { assertFn } from 'vs/base/common/assert';
import { strictEquals, EqualityComparer } from 'vs/base/common/equals';
import { EqualityComparer, strictEquals } from 'vs/base/common/equals';
import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
import { BaseObservable, IChangeContext, IObservable, IObserver, IReader, _setDerivedOpts, } from 'vs/base/common/observableInternal/base';
import { BaseObservable, IChangeContext, IObservable, IObserver, IReader, ISettableObservable, ITransaction, _setDerivedOpts, } from 'vs/base/common/observableInternal/base';
import { DebugNameData, IDebugNameData, Owner } from 'vs/base/common/observableInternal/debugName';
import { getLogger } from 'vs/base/common/observableInternal/logging';

Expand Down Expand Up @@ -39,6 +39,18 @@ export function derived<T>(computeFnOrOwner: ((reader: IReader) => T) | Owner, c
);
}

export function derivedWithSetter<T>(owner: Owner | undefined, computeFn: (reader: IReader) => T, setter: (value: T, transaction: ITransaction | undefined) => void): ISettableObservable<T> {
return new DerivedWithSetter(
new DebugNameData(owner, undefined, computeFn),
computeFn,
undefined,
undefined,
undefined,
strictEquals,
setter,
);
}

export function derivedOpts<T>(
options: IDebugNameData & {
equalsFn?: EqualityComparer<T>;
Expand Down Expand Up @@ -383,3 +395,25 @@ export class Derived<T, TChangeSummary = any> extends BaseObservable<T, void> im
}
}
}


export class DerivedWithSetter<T, TChangeSummary = any> extends Derived<T, TChangeSummary> implements ISettableObservable<T> {
constructor(
debugNameData: DebugNameData,
computeFn: (reader: IReader, changeSummary: TChangeSummary) => T,
createChangeSummary: (() => TChangeSummary) | undefined,
handleChange: ((context: IChangeContext, summary: TChangeSummary) => boolean) | undefined,
handleLastObserverRemoved: (() => void) | undefined = undefined,
equalityComparator: EqualityComparer<T>,
public readonly set: (value: T, tx: ITransaction | undefined) => void,
) {
super(
debugNameData,
computeFn,
createChangeSummary,
handleChange,
handleLastObserverRemoved,
equalityComparator,
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,52 @@

import { IBoundarySashes, ISashEvent, Orientation, Sash, SashState } from 'vs/base/browser/ui/sash/sash';
import { Disposable } from 'vs/base/common/lifecycle';
import { IObservable, IReader, autorun, derived, observableValue } from 'vs/base/common/observable';
import { IObservable, IReader, ISettableObservable, autorun, observableValue } from 'vs/base/common/observable';
import { DiffEditorOptions } from '../diffEditorOptions';
import { derivedWithSetter } from 'vs/base/common/observableInternal/derived';

export class DiffEditorSash extends Disposable {
private readonly _sashRatio = observableValue<number | undefined>(this, undefined);

public readonly sashLeft = derived(this, reader => {
export class SashLayout {
public readonly sashLeft = derivedWithSetter(this, reader => {
const ratio = this._sashRatio.read(reader) ?? this._options.splitViewDefaultRatio.read(reader);
return this._computeSashLeft(ratio, reader);
}, (value, tx) => {
const contentWidth = this.dimensions.width.get();
this._sashRatio.set(value / contentWidth, tx);
});

private readonly _sashRatio = observableValue<number | undefined>(this, undefined);

public resetSash(): void {
this._sashRatio.set(undefined, undefined);
}

constructor(
private readonly _options: DiffEditorOptions,
public readonly dimensions: { height: IObservable<number>; width: IObservable<number> },
) {
}

/** @pure */
private _computeSashLeft(desiredRatio: number, reader: IReader | undefined): number {
const contentWidth = this.dimensions.width.read(reader);
const midPoint = Math.floor(this._options.splitViewDefaultRatio.read(reader) * contentWidth);
const sashLeft = this._options.enableSplitViewResizing.read(reader) ? Math.floor(desiredRatio * contentWidth) : midPoint;

const MINIMUM_EDITOR_WIDTH = 100;
if (contentWidth <= MINIMUM_EDITOR_WIDTH * 2) {
return midPoint;
}
if (sashLeft < MINIMUM_EDITOR_WIDTH) {
return MINIMUM_EDITOR_WIDTH;
}
if (sashLeft > contentWidth - MINIMUM_EDITOR_WIDTH) {
return contentWidth - MINIMUM_EDITOR_WIDTH;
}
return sashLeft;
}
}

export class DiffEditorSash extends Disposable {
private readonly _sash = this._register(new Sash(this._domNode, {
getVerticalSashTop: (_sash: Sash): number => 0,
getVerticalSashLeft: (_sash: Sash): number => this.sashLeft.get(),
Expand All @@ -25,57 +60,38 @@ export class DiffEditorSash extends Disposable {
private _startSashPosition: number | undefined = undefined;

constructor(
private readonly _options: DiffEditorOptions,
private readonly _domNode: HTMLElement,
private readonly _dimensions: { height: IObservable<number>; width: IObservable<number> },
private readonly _sashes: IObservable<IBoundarySashes | undefined, void>,
private readonly _enabled: IObservable<boolean>,
private readonly _boundarySashes: IObservable<IBoundarySashes | undefined, void>,
public readonly sashLeft: ISettableObservable<number>,
private readonly _resetSash: () => void,
) {
super();

this._register(this._sash.onDidStart(() => {
this._startSashPosition = this.sashLeft.get();
}));
this._register(this._sash.onDidChange((e: ISashEvent) => {
const contentWidth = this._dimensions.width.get();
const sashPosition = this._computeSashLeft((this._startSashPosition! + (e.currentX - e.startX)) / contentWidth, undefined);
this._sashRatio.set(sashPosition / contentWidth, undefined);
this.sashLeft.set(this._startSashPosition! + (e.currentX - e.startX), undefined);
}));
this._register(this._sash.onDidEnd(() => this._sash.layout()));
this._register(this._sash.onDidReset(() => this._sashRatio.set(undefined, undefined)));
this._register(this._sash.onDidReset(() => this._resetSash()));

this._register(autorun(reader => {
const sashes = this._sashes.read(reader);
const sashes = this._boundarySashes.read(reader);
if (sashes) {
this._sash.orthogonalEndSash = sashes.bottom;
}
}));

this._register(autorun(reader => {
/** @description DiffEditorSash.layoutSash */
const enabled = this._options.enableSplitViewResizing.read(reader);
const enabled = this._enabled.read(reader);
this._sash.state = enabled ? SashState.Enabled : SashState.Disabled;
this.sashLeft.read(reader);
this._dimensions.height.read(reader);
this._sash.layout();
}));
}

/** @pure */
private _computeSashLeft(desiredRatio: number, reader: IReader | undefined): number {
const contentWidth = this._dimensions.width.read(reader);
const midPoint = Math.floor(this._options.splitViewDefaultRatio.read(reader) * contentWidth);
const sashLeft = this._options.enableSplitViewResizing.read(reader) ? Math.floor(desiredRatio * contentWidth) : midPoint;

const MINIMUM_EDITOR_WIDTH = 100;
if (contentWidth <= MINIMUM_EDITOR_WIDTH * 2) {
return midPoint;
}
if (sashLeft < MINIMUM_EDITOR_WIDTH) {
return MINIMUM_EDITOR_WIDTH;
}
if (sashLeft > contentWidth - MINIMUM_EDITOR_WIDTH) {
return contentWidth - MINIMUM_EDITOR_WIDTH;
}
return sashLeft;
}
}
28 changes: 18 additions & 10 deletions src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { StableEditorScrollState } from 'vs/editor/browser/stableEditorScroll';
import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget';
import { AccessibleDiffViewer, AccessibleDiffViewerModelFromEditors } from 'vs/editor/browser/widget/diffEditor/components/accessibleDiffViewer';
import { DiffEditorDecorations } from 'vs/editor/browser/widget/diffEditor/components/diffEditorDecorations';
import { DiffEditorSash } from 'vs/editor/browser/widget/diffEditor/components/diffEditorSash';
import { DiffEditorSash, SashLayout } from 'vs/editor/browser/widget/diffEditor/components/diffEditorSash';
import { DiffEditorViewZones } from 'vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/diffEditorViewZones';
import { DiffEditorGutter } from 'vs/editor/browser/widget/diffEditor/features/gutterFeature';
import { HideUnchangedRegionsFeature } from 'vs/editor/browser/widget/diffEditor/features/hideUnchangedRegionsFeature';
Expand Down Expand Up @@ -72,9 +72,8 @@ export class DiffEditorWidget extends DelegatingEditor implements IDiffEditor {
);
private readonly _rootSizeObserver: ObservableElementSizeObserver;

/**
* Is undefined if and only if side-by-side
*/

private readonly _sashLayout: SashLayout;
private readonly _sash: IObservable<DiffEditorSash | undefined>;
private readonly _boundarySashes = observableValue<IBoundarySashes | undefined>(this, undefined);

Expand Down Expand Up @@ -175,17 +174,23 @@ export class DiffEditorWidget extends DelegatingEditor implements IDiffEditor {
)
).recomputeInitiallyAndOnChange(this._store);

const dimensions = {
height: this._rootSizeObserver.height,
width: this._rootSizeObserver.width.map((w, reader) => w - (this._overviewRulerPart.read(reader)?.width ?? 0)),
};

this._sashLayout = new SashLayout(this._options, dimensions);

this._sash = derivedDisposable(this, reader => {
const showSash = this._options.renderSideBySide.read(reader);
this.elements.root.classList.toggle('side-by-side', showSash);
return !showSash ? undefined : new DiffEditorSash(
this._options,
this.elements.root,
{
height: this._rootSizeObserver.height,
width: this._rootSizeObserver.width.map((w, reader) => w - (this._overviewRulerPart.read(reader)?.width ?? 0)),
},
dimensions,
this._options.enableSplitViewResizing,
this._boundarySashes,
this._sashLayout.sashLeft,
() => this._sashLayout.resetSash(),
);
}).recomputeInitiallyAndOnChange(this._store);

Expand Down Expand Up @@ -272,7 +277,10 @@ export class DiffEditorWidget extends DelegatingEditor implements IDiffEditor {
readHotReloadableExport(DiffEditorGutter, reader),
this.elements.root,
this._diffModel,
this._editors
this._editors,
this._options,
this._sashLayout,
this._boundarySashes,
)
: undefined;
});
Expand Down
23 changes: 23 additions & 0 deletions src/vs/editor/browser/widget/diffEditor/features/gutterFeature.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@ import { EventType, addDisposableListener, h } from 'vs/base/browser/dom';
import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent';
import { ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget';
import { IBoundarySashes } from 'vs/base/browser/ui/sash/sash';
import { Disposable } from 'vs/base/common/lifecycle';
import { IObservable, autorun, autorunWithStore, derived, observableFromEvent, observableValue } from 'vs/base/common/observable';
import { derivedDisposable, derivedWithSetter } from 'vs/base/common/observableInternal/derived';
import { URI } from 'vs/base/common/uri';
import { DiffEditorEditors } from 'vs/editor/browser/widget/diffEditor/components/diffEditorEditors';
import { DiffEditorSash, SashLayout } from 'vs/editor/browser/widget/diffEditor/components/diffEditorSash';
import { DiffEditorOptions } from 'vs/editor/browser/widget/diffEditor/diffEditorOptions';
import { DiffEditorViewModel } from 'vs/editor/browser/widget/diffEditor/diffEditorViewModel';
import { appendRemoveOnDispose, applyStyle, prependRemoveOnDispose } from 'vs/editor/browser/widget/diffEditor/utils';
import { EditorGutter, IGutterItemInfo, IGutterItemView } from 'vs/editor/browser/widget/diffEditor/utils/editorGutter';
Expand All @@ -35,6 +39,7 @@ export class DiffEditorGutter extends Disposable {
private readonly _menu = this._register(this._menuService.createMenu(MenuId.DiffEditorHunkToolbar, this._contextKeyService));
private readonly _actions = observableFromEvent(this._menu.onDidChange, () => this._menu.getActions());
private readonly _hasActions = this._actions.map(a => a.length > 0);
private readonly _showSash = derived(this, reader => this._options.renderSideBySide.read(reader) && this._hasActions.read(reader));

public readonly width = derived(this, reader => this._hasActions.read(reader) ? width : 0);

Expand All @@ -44,6 +49,9 @@ export class DiffEditorGutter extends Disposable {
diffEditorRoot: HTMLDivElement,
private readonly _diffModel: IObservable<DiffEditorViewModel | undefined>,
private readonly _editors: DiffEditorEditors,
private readonly _options: DiffEditorOptions,
private readonly _sashLayout: SashLayout,
private readonly _boundarySashes: IObservable<IBoundarySashes | undefined, void>,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
@IMenuService private readonly _menuService: IMenuService,
Expand All @@ -58,6 +66,21 @@ export class DiffEditorGutter extends Disposable {

this._register(applyStyle(this.elements.root, { display: this._hasActions.map(a => a ? 'block' : 'none') }));

derivedDisposable(this, reader => {
const showSash = this._showSash.read(reader);
return !showSash ? undefined : new DiffEditorSash(
diffEditorRoot,
this._sashLayout.dimensions,
this._options.enableSplitViewResizing,
this._boundarySashes,
derivedWithSetter(
this, reader => this._sashLayout.sashLeft.read(reader) - width,
(v, tx) => this._sashLayout.sashLeft.set(v + width, tx)
),
() => this._sashLayout.resetSash(),
);
}).recomputeInitiallyAndOnChange(this._store);

this._register(new EditorGutter<DiffGutterItem>(this._editors.modified, this.elements.root, {
getIntersectingGutterItems: (range, reader) => {
const model = this._diffModel.read(reader);
Expand Down

0 comments on commit e11c336

Please sign in to comment.