UNPKG

monaco-editor

Version:
414 lines (413 loc) • 14.6 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { findLast } from '../../../../base/common/arraysFind.js'; import { CancellationTokenSource } from '../../../../base/common/cancellation.js'; import { Disposable, DisposableStore, toDisposable } from '../../../../base/common/lifecycle.js'; import { autorun, autorunHandleChanges, autorunOpts, autorunWithStore, observableValue, transaction } from '../../../../base/common/observable.js'; import { ElementSizeObserver } from '../../config/elementSizeObserver.js'; import { Position } from '../../../common/core/position.js'; import { Range } from '../../../common/core/range.js'; import { TextLength } from '../../../common/core/textLength.js'; export function joinCombine(arr1, arr2, keySelector, combine) { if (arr1.length === 0) { return arr2; } if (arr2.length === 0) { return arr1; } const result = []; let i = 0; let j = 0; while (i < arr1.length && j < arr2.length) { const val1 = arr1[i]; const val2 = arr2[j]; const key1 = keySelector(val1); const key2 = keySelector(val2); if (key1 < key2) { result.push(val1); i++; } else if (key1 > key2) { result.push(val2); j++; } else { result.push(combine(val1, val2)); i++; j++; } } while (i < arr1.length) { result.push(arr1[i]); i++; } while (j < arr2.length) { result.push(arr2[j]); j++; } return result; } // TODO make utility export function applyObservableDecorations(editor, decorations) { const d = new DisposableStore(); const decorationsCollection = editor.createDecorationsCollection(); d.add(autorunOpts({ debugName: () => `Apply decorations from ${decorations.debugName}` }, reader => { const d = decorations.read(reader); decorationsCollection.set(d); })); d.add({ dispose: () => { decorationsCollection.clear(); } }); return d; } export function appendRemoveOnDispose(parent, child) { parent.appendChild(child); return toDisposable(() => { child.remove(); }); } export function prependRemoveOnDispose(parent, child) { parent.prepend(child); return toDisposable(() => { child.remove(); }); } export class ObservableElementSizeObserver extends Disposable { get width() { return this._width; } get height() { return this._height; } get automaticLayout() { return this._automaticLayout; } constructor(element, dimension) { super(); this._automaticLayout = false; this.elementSizeObserver = this._register(new ElementSizeObserver(element, dimension)); this._width = observableValue(this, this.elementSizeObserver.getWidth()); this._height = observableValue(this, this.elementSizeObserver.getHeight()); this._register(this.elementSizeObserver.onDidChange(e => transaction(tx => { /** @description Set width/height from elementSizeObserver */ this._width.set(this.elementSizeObserver.getWidth(), tx); this._height.set(this.elementSizeObserver.getHeight(), tx); }))); } observe(dimension) { this.elementSizeObserver.observe(dimension); } setAutomaticLayout(automaticLayout) { this._automaticLayout = automaticLayout; if (automaticLayout) { this.elementSizeObserver.startObserving(); } else { this.elementSizeObserver.stopObserving(); } } } export function animatedObservable(targetWindow, base, store) { let targetVal = base.get(); let startVal = targetVal; let curVal = targetVal; const result = observableValue('animatedValue', targetVal); let animationStartMs = -1; const durationMs = 300; let animationFrame = undefined; store.add(autorunHandleChanges({ createEmptyChangeSummary: () => ({ animate: false }), handleChange: (ctx, s) => { if (ctx.didChange(base)) { s.animate = s.animate || ctx.change; } return true; } }, (reader, s) => { /** @description update value */ if (animationFrame !== undefined) { targetWindow.cancelAnimationFrame(animationFrame); animationFrame = undefined; } startVal = curVal; targetVal = base.read(reader); animationStartMs = Date.now() - (s.animate ? 0 : durationMs); update(); })); function update() { const passedMs = Date.now() - animationStartMs; curVal = Math.floor(easeOutExpo(passedMs, startVal, targetVal - startVal, durationMs)); if (passedMs < durationMs) { animationFrame = targetWindow.requestAnimationFrame(update); } else { curVal = targetVal; } result.set(curVal, undefined); } return result; } function easeOutExpo(t, b, c, d) { return t === d ? b + c : c * (-Math.pow(2, -10 * t / d) + 1) + b; } export class ViewZoneOverlayWidget extends Disposable { constructor(editor, viewZone, htmlElement) { super(); this._register(new ManagedOverlayWidget(editor, htmlElement)); this._register(applyStyle(htmlElement, { height: viewZone.actualHeight, top: viewZone.actualTop, })); } } export class PlaceholderViewZone { get afterLineNumber() { return this._afterLineNumber.get(); } constructor(_afterLineNumber, heightInPx) { this._afterLineNumber = _afterLineNumber; this.heightInPx = heightInPx; this.domNode = document.createElement('div'); this._actualTop = observableValue(this, undefined); this._actualHeight = observableValue(this, undefined); this.actualTop = this._actualTop; this.actualHeight = this._actualHeight; this.showInHiddenAreas = true; this.onChange = this._afterLineNumber; this.onDomNodeTop = (top) => { this._actualTop.set(top, undefined); }; this.onComputedHeight = (height) => { this._actualHeight.set(height, undefined); }; } } export class ManagedOverlayWidget { static { this._counter = 0; } constructor(_editor, _domElement) { this._editor = _editor; this._domElement = _domElement; this._overlayWidgetId = `managedOverlayWidget-${ManagedOverlayWidget._counter++}`; this._overlayWidget = { getId: () => this._overlayWidgetId, getDomNode: () => this._domElement, getPosition: () => null }; this._editor.addOverlayWidget(this._overlayWidget); } dispose() { this._editor.removeOverlayWidget(this._overlayWidget); } } export function applyStyle(domNode, style) { return autorun(reader => { /** @description applyStyle */ for (let [key, val] of Object.entries(style)) { if (val && typeof val === 'object' && 'read' in val) { val = val.read(reader); } if (typeof val === 'number') { val = `${val}px`; } key = key.replace(/[A-Z]/g, m => '-' + m.toLowerCase()); domNode.style[key] = val; } }); } export function applyViewZones(editor, viewZones, setIsUpdating, zoneIds) { const store = new DisposableStore(); const lastViewZoneIds = []; store.add(autorunWithStore((reader, store) => { /** @description applyViewZones */ const curViewZones = viewZones.read(reader); const viewZonIdsPerViewZone = new Map(); const viewZoneIdPerOnChangeObservable = new Map(); // Add/remove view zones if (setIsUpdating) { setIsUpdating(true); } editor.changeViewZones(a => { for (const id of lastViewZoneIds) { a.removeZone(id); zoneIds?.delete(id); } lastViewZoneIds.length = 0; for (const z of curViewZones) { const id = a.addZone(z); if (z.setZoneId) { z.setZoneId(id); } lastViewZoneIds.push(id); zoneIds?.add(id); viewZonIdsPerViewZone.set(z, id); } }); if (setIsUpdating) { setIsUpdating(false); } // Layout zone on change store.add(autorunHandleChanges({ createEmptyChangeSummary() { return { zoneIds: [] }; }, handleChange(context, changeSummary) { const id = viewZoneIdPerOnChangeObservable.get(context.changedObservable); if (id !== undefined) { changeSummary.zoneIds.push(id); } return true; }, }, (reader, changeSummary) => { /** @description layoutZone on change */ for (const vz of curViewZones) { if (vz.onChange) { viewZoneIdPerOnChangeObservable.set(vz.onChange, viewZonIdsPerViewZone.get(vz)); vz.onChange.read(reader); } } if (setIsUpdating) { setIsUpdating(true); } editor.changeViewZones(a => { for (const id of changeSummary.zoneIds) { a.layoutZone(id); } }); if (setIsUpdating) { setIsUpdating(false); } })); })); store.add({ dispose() { if (setIsUpdating) { setIsUpdating(true); } editor.changeViewZones(a => { for (const id of lastViewZoneIds) { a.removeZone(id); } }); zoneIds?.clear(); if (setIsUpdating) { setIsUpdating(false); } } }); return store; } export class DisposableCancellationTokenSource extends CancellationTokenSource { dispose() { super.dispose(true); } } export function translatePosition(posInOriginal, mappings) { const mapping = findLast(mappings, m => m.original.startLineNumber <= posInOriginal.lineNumber); if (!mapping) { // No changes before the position return Range.fromPositions(posInOriginal); } if (mapping.original.endLineNumberExclusive <= posInOriginal.lineNumber) { const newLineNumber = posInOriginal.lineNumber - mapping.original.endLineNumberExclusive + mapping.modified.endLineNumberExclusive; return Range.fromPositions(new Position(newLineNumber, posInOriginal.column)); } if (!mapping.innerChanges) { // Only for legacy algorithm return Range.fromPositions(new Position(mapping.modified.startLineNumber, 1)); } const innerMapping = findLast(mapping.innerChanges, m => m.originalRange.getStartPosition().isBeforeOrEqual(posInOriginal)); if (!innerMapping) { const newLineNumber = posInOriginal.lineNumber - mapping.original.startLineNumber + mapping.modified.startLineNumber; return Range.fromPositions(new Position(newLineNumber, posInOriginal.column)); } if (innerMapping.originalRange.containsPosition(posInOriginal)) { return innerMapping.modifiedRange; } else { const l = lengthBetweenPositions(innerMapping.originalRange.getEndPosition(), posInOriginal); return Range.fromPositions(l.addToPosition(innerMapping.modifiedRange.getEndPosition())); } } function lengthBetweenPositions(position1, position2) { if (position1.lineNumber === position2.lineNumber) { return new TextLength(0, position2.column - position1.column); } else { return new TextLength(position2.lineNumber - position1.lineNumber, position2.column - 1); } } export function filterWithPrevious(arr, filter) { let prev; return arr.filter(cur => { const result = filter(cur, prev); prev = cur; return result; }); } export class RefCounted { static create(value, debugOwner = undefined) { return new BaseRefCounted(value, value, debugOwner); } static createWithDisposable(value, disposable, debugOwner = undefined) { const store = new DisposableStore(); store.add(disposable); store.add(value); return new BaseRefCounted(value, store, debugOwner); } } class BaseRefCounted extends RefCounted { constructor(object, _disposable, _debugOwner) { super(); this.object = object; this._disposable = _disposable; this._debugOwner = _debugOwner; this._refCount = 1; this._isDisposed = false; this._owners = []; if (_debugOwner) { this._addOwner(_debugOwner); } } _addOwner(debugOwner) { if (debugOwner) { this._owners.push(debugOwner); } } createNewRef(debugOwner) { this._refCount++; if (debugOwner) { this._addOwner(debugOwner); } return new ClonedRefCounted(this, debugOwner); } dispose() { if (this._isDisposed) { return; } this._isDisposed = true; this._decreaseRefCount(this._debugOwner); } _decreaseRefCount(debugOwner) { this._refCount--; if (this._refCount === 0) { this._disposable.dispose(); } if (debugOwner) { const idx = this._owners.indexOf(debugOwner); if (idx !== -1) { this._owners.splice(idx, 1); } } } } class ClonedRefCounted extends RefCounted { constructor(_base, _debugOwner) { super(); this._base = _base; this._debugOwner = _debugOwner; this._isDisposed = false; } get object() { return this._base.object; } createNewRef(debugOwner) { return this._base.createNewRef(debugOwner); } dispose() { if (this._isDisposed) { return; } this._isDisposed = true; this._base._decreaseRefCount(this._debugOwner); } }