monaco-editor
Version:
A browser based code editor
299 lines (298 loc) • 15.7 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
import { getWindow, h, scheduleAtNextAnimationFrame } from '../../../../base/browser/dom.js';
import { SmoothScrollableElement } from '../../../../base/browser/ui/scrollbar/scrollableElement.js';
import { compareBy, numberComparator } from '../../../../base/common/arrays.js';
import { findFirstMax } from '../../../../base/common/arraysFind.js';
import { BugIndicatingError } from '../../../../base/common/errors.js';
import { Disposable, toDisposable } from '../../../../base/common/lifecycle.js';
import { autorun, autorunWithStore, derived, derivedWithStore, observableFromEvent, observableValue } from '../../../../base/common/observable.js';
import { disposableObservableValue, globalTransaction, transaction } from '../../../../base/common/observableInternal/base.js';
import { Scrollable } from '../../../../base/common/scrollable.js';
import './style.css';
import { ObservableElementSizeObserver } from '../diffEditor/utils.js';
import { OffsetRange } from '../../../common/core/offsetRange.js';
import { Selection } from '../../../common/core/selection.js';
import { EditorContextKeys } from '../../../common/editorContextKeys.js';
import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
import { ServiceCollection } from '../../../../platform/instantiation/common/serviceCollection.js';
import { DiffEditorItemTemplate, TemplateData } from './diffEditorItemTemplate.js';
import { ObjectPool } from './objectPool.js';
import { localize } from '../../../../nls.js';
let MultiDiffEditorWidgetImpl = class MultiDiffEditorWidgetImpl extends Disposable {
constructor(_element, _dimension, _viewModel, _workbenchUIElementFactory, _parentContextKeyService, _parentInstantiationService) {
super();
this._element = _element;
this._dimension = _dimension;
this._viewModel = _viewModel;
this._workbenchUIElementFactory = _workbenchUIElementFactory;
this._parentContextKeyService = _parentContextKeyService;
this._parentInstantiationService = _parentInstantiationService;
this._scrollableElements = h('div.scrollContent', [
h('div@content', {
style: {
overflow: 'hidden',
}
}),
h('div.monaco-editor@overflowWidgetsDomNode', {}),
]);
this._scrollable = this._register(new Scrollable({
forceIntegerValues: false,
scheduleAtNextAnimationFrame: (cb) => scheduleAtNextAnimationFrame(getWindow(this._element), cb),
smoothScrollDuration: 100,
}));
this._scrollableElement = this._register(new SmoothScrollableElement(this._scrollableElements.root, {
vertical: 1 /* ScrollbarVisibility.Auto */,
horizontal: 1 /* ScrollbarVisibility.Auto */,
useShadows: false,
}, this._scrollable));
this._elements = h('div.monaco-component.multiDiffEditor', {}, [
h('div', {}, [this._scrollableElement.getDomNode()]),
h('div.placeholder@placeholder', {}, [h('div', [localize('noChangedFiles', 'No Changed Files')])]),
]);
this._sizeObserver = this._register(new ObservableElementSizeObserver(this._element, undefined));
this._objectPool = this._register(new ObjectPool((data) => {
const template = this._instantiationService.createInstance(DiffEditorItemTemplate, this._scrollableElements.content, this._scrollableElements.overflowWidgetsDomNode, this._workbenchUIElementFactory);
template.setData(data);
return template;
}));
this.scrollTop = observableFromEvent(this, this._scrollableElement.onScroll, () => /** @description scrollTop */ this._scrollableElement.getScrollPosition().scrollTop);
this.scrollLeft = observableFromEvent(this, this._scrollableElement.onScroll, () => /** @description scrollLeft */ this._scrollableElement.getScrollPosition().scrollLeft);
this._viewItemsInfo = derivedWithStore(this, (reader, store) => {
const vm = this._viewModel.read(reader);
if (!vm) {
return { items: [], getItem: _d => { throw new BugIndicatingError(); } };
}
const viewModels = vm.items.read(reader);
const map = new Map();
const items = viewModels.map(d => {
const item = store.add(new VirtualizedViewItem(d, this._objectPool, this.scrollLeft, delta => {
this._scrollableElement.setScrollPosition({ scrollTop: this._scrollableElement.getScrollPosition().scrollTop + delta });
}));
const data = this._lastDocStates?.[item.getKey()];
if (data) {
transaction(tx => {
item.setViewState(data, tx);
});
}
map.set(d, item);
return item;
});
return { items, getItem: d => map.get(d) };
});
this._viewItems = this._viewItemsInfo.map(this, items => items.items);
this._spaceBetweenPx = 0;
this._totalHeight = this._viewItems.map(this, (items, reader) => items.reduce((r, i) => r + i.contentHeight.read(reader) + this._spaceBetweenPx, 0));
this._contextKeyService = this._register(this._parentContextKeyService.createScoped(this._element));
this._instantiationService = this._register(this._parentInstantiationService.createChild(new ServiceCollection([IContextKeyService, this._contextKeyService])));
/** This accounts for documents that are not loaded yet. */
this._lastDocStates = {};
this._contextKeyService.createKey(EditorContextKeys.inMultiDiffEditor.key, true);
this._register(autorunWithStore((reader, store) => {
const viewModel = this._viewModel.read(reader);
if (viewModel && viewModel.contextKeys) {
for (const [key, value] of Object.entries(viewModel.contextKeys)) {
const contextKey = this._contextKeyService.createKey(key, undefined);
contextKey.set(value);
store.add(toDisposable(() => contextKey.reset()));
}
}
}));
const ctxAllCollapsed = this._parentContextKeyService.createKey(EditorContextKeys.multiDiffEditorAllCollapsed.key, false);
this._register(autorun((reader) => {
const viewModel = this._viewModel.read(reader);
if (viewModel) {
const allCollapsed = viewModel.items.read(reader).every(item => item.collapsed.read(reader));
ctxAllCollapsed.set(allCollapsed);
}
}));
this._register(autorun((reader) => {
/** @description Update widget dimension */
const dimension = this._dimension.read(reader);
this._sizeObserver.observe(dimension);
}));
this._register(autorun((reader) => {
/** @description Update widget dimension */
const items = this._viewItems.read(reader);
this._elements.placeholder.classList.toggle('visible', items.length === 0);
}));
this._scrollableElements.content.style.position = 'relative';
this._register(autorun((reader) => {
/** @description Update scroll dimensions */
const height = this._sizeObserver.height.read(reader);
this._scrollableElements.root.style.height = `${height}px`;
const totalHeight = this._totalHeight.read(reader);
this._scrollableElements.content.style.height = `${totalHeight}px`;
const width = this._sizeObserver.width.read(reader);
let scrollWidth = width;
const viewItems = this._viewItems.read(reader);
const max = findFirstMax(viewItems, compareBy(i => i.maxScroll.read(reader).maxScroll, numberComparator));
if (max) {
const maxScroll = max.maxScroll.read(reader);
scrollWidth = width + maxScroll.maxScroll;
}
this._scrollableElement.setScrollDimensions({
width: width,
height: height,
scrollHeight: totalHeight,
scrollWidth,
});
}));
_element.replaceChildren(this._elements.root);
this._register(toDisposable(() => {
_element.replaceChildren();
}));
this._register(this._register(autorun(reader => {
/** @description Render all */
globalTransaction(tx => {
this.render(reader);
});
})));
}
render(reader) {
const scrollTop = this.scrollTop.read(reader);
let contentScrollOffsetToScrollOffset = 0;
let itemHeightSumBefore = 0;
let itemContentHeightSumBefore = 0;
const viewPortHeight = this._sizeObserver.height.read(reader);
const contentViewPort = OffsetRange.ofStartAndLength(scrollTop, viewPortHeight);
const width = this._sizeObserver.width.read(reader);
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));
contentScrollOffsetToScrollOffset -= scroll;
const viewPort = OffsetRange.ofStartAndLength(scrollTop + contentScrollOffsetToScrollOffset, viewPortHeight);
v.render(itemRange, scroll, width, viewPort);
}
itemHeightSumBefore += itemHeight + this._spaceBetweenPx;
itemContentHeightSumBefore += itemContentHeight + this._spaceBetweenPx;
}
this._scrollableElements.content.style.transform = `translateY(${-(scrollTop + contentScrollOffsetToScrollOffset)}px)`;
}
};
MultiDiffEditorWidgetImpl = __decorate([
__param(4, IContextKeyService),
__param(5, IInstantiationService)
], MultiDiffEditorWidgetImpl);
export { MultiDiffEditorWidgetImpl };
class VirtualizedViewItem extends Disposable {
constructor(viewModel, _objectPool, _scrollLeft, _deltaScrollVertical) {
super();
this.viewModel = viewModel;
this._objectPool = _objectPool;
this._scrollLeft = _scrollLeft;
this._deltaScrollVertical = _deltaScrollVertical;
this._templateRef = this._register(disposableObservableValue(this, undefined));
this.contentHeight = derived(this, reader => this._templateRef.read(reader)?.object.contentHeight?.read(reader) ?? this.viewModel.lastTemplateData.read(reader).contentHeight);
this.maxScroll = derived(this, reader => this._templateRef.read(reader)?.object.maxScroll.read(reader) ?? { maxScroll: 0, scrollWidth: 0 });
this.template = derived(this, reader => this._templateRef.read(reader)?.object);
this._isHidden = observableValue(this, false);
this._isFocused = derived(this, reader => this.template.read(reader)?.isFocused.read(reader) ?? false);
this.viewModel.setIsFocused(this._isFocused, undefined);
this._register(autorun((reader) => {
const scrollLeft = this._scrollLeft.read(reader);
this._templateRef.read(reader)?.object.setScrollLeft(scrollLeft);
}));
this._register(autorun(reader => {
const ref = this._templateRef.read(reader);
if (!ref) {
return;
}
const isHidden = this._isHidden.read(reader);
if (!isHidden) {
return;
}
const isFocused = ref.object.isFocused.read(reader);
if (isFocused) {
return;
}
this._clear();
}));
}
dispose() {
this._clear();
super.dispose();
}
toString() {
return `VirtualViewItem(${this.viewModel.documentDiffItem.modified?.uri.toString()})`;
}
getKey() {
return this.viewModel.getKey();
}
setViewState(viewState, tx) {
this.viewModel.collapsed.set(viewState.collapsed, tx);
this._updateTemplateData(tx);
const data = this.viewModel.lastTemplateData.get();
const selections = viewState.selections?.map(Selection.liftSelection);
this.viewModel.lastTemplateData.set({
...data,
selections,
}, tx);
const ref = this._templateRef.get();
if (ref) {
if (selections) {
ref.object.editor.setSelections(selections);
}
}
}
_updateTemplateData(tx) {
const ref = this._templateRef.get();
if (!ref) {
return;
}
this.viewModel.lastTemplateData.set({
contentHeight: ref.object.contentHeight.get(),
selections: ref.object.editor.getSelections() ?? undefined,
}, tx);
}
_clear() {
const ref = this._templateRef.get();
if (!ref) {
return;
}
transaction(tx => {
this._updateTemplateData(tx);
ref.object.hide();
this._templateRef.set(undefined, tx);
});
}
hide() {
this._isHidden.set(true, undefined);
}
render(verticalSpace, offset, width, viewPort) {
this._isHidden.set(false, undefined);
let ref = this._templateRef.get();
if (!ref) {
ref = this._objectPool.getUnusedObj(new TemplateData(this.viewModel, this._deltaScrollVertical));
this._templateRef.set(ref, undefined);
const selections = this.viewModel.lastTemplateData.get().selections;
if (selections) {
ref.object.editor.setSelections(selections);
}
}
ref.object.render(verticalSpace, width, offset, viewPort);
}
}