monaco-editor-core
Version:
A browser based code editor
327 lines (326 loc) • 13.8 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createFastDomNode } from '../../../../base/browser/fastDomNode.js';
import { onUnexpectedError } from '../../../../base/common/errors.js';
import { ViewPart } from '../../view/viewPart.js';
import { Position } from '../../../common/core/position.js';
const invalidFunc = () => { throw new Error(`Invalid change accessor`); };
export class ViewZones extends ViewPart {
constructor(context) {
super(context);
const options = this._context.configuration.options;
const layoutInfo = options.get(146 /* EditorOption.layoutInfo */);
this._lineHeight = options.get(67 /* EditorOption.lineHeight */);
this._contentWidth = layoutInfo.contentWidth;
this._contentLeft = layoutInfo.contentLeft;
this.domNode = createFastDomNode(document.createElement('div'));
this.domNode.setClassName('view-zones');
this.domNode.setPosition('absolute');
this.domNode.setAttribute('role', 'presentation');
this.domNode.setAttribute('aria-hidden', 'true');
this.marginDomNode = createFastDomNode(document.createElement('div'));
this.marginDomNode.setClassName('margin-view-zones');
this.marginDomNode.setPosition('absolute');
this.marginDomNode.setAttribute('role', 'presentation');
this.marginDomNode.setAttribute('aria-hidden', 'true');
this._zones = {};
}
dispose() {
super.dispose();
this._zones = {};
}
// ---- begin view event handlers
_recomputeWhitespacesProps() {
const whitespaces = this._context.viewLayout.getWhitespaces();
const oldWhitespaces = new Map();
for (const whitespace of whitespaces) {
oldWhitespaces.set(whitespace.id, whitespace);
}
let hadAChange = false;
this._context.viewModel.changeWhitespace((whitespaceAccessor) => {
const keys = Object.keys(this._zones);
for (let i = 0, len = keys.length; i < len; i++) {
const id = keys[i];
const zone = this._zones[id];
const props = this._computeWhitespaceProps(zone.delegate);
zone.isInHiddenArea = props.isInHiddenArea;
const oldWhitespace = oldWhitespaces.get(id);
if (oldWhitespace && (oldWhitespace.afterLineNumber !== props.afterViewLineNumber || oldWhitespace.height !== props.heightInPx)) {
whitespaceAccessor.changeOneWhitespace(id, props.afterViewLineNumber, props.heightInPx);
this._safeCallOnComputedHeight(zone.delegate, props.heightInPx);
hadAChange = true;
}
}
});
return hadAChange;
}
onConfigurationChanged(e) {
const options = this._context.configuration.options;
const layoutInfo = options.get(146 /* EditorOption.layoutInfo */);
this._lineHeight = options.get(67 /* EditorOption.lineHeight */);
this._contentWidth = layoutInfo.contentWidth;
this._contentLeft = layoutInfo.contentLeft;
if (e.hasChanged(67 /* EditorOption.lineHeight */)) {
this._recomputeWhitespacesProps();
}
return true;
}
onLineMappingChanged(e) {
return this._recomputeWhitespacesProps();
}
onLinesDeleted(e) {
return true;
}
onScrollChanged(e) {
return e.scrollTopChanged || e.scrollWidthChanged;
}
onZonesChanged(e) {
return true;
}
onLinesInserted(e) {
return true;
}
// ---- end view event handlers
_getZoneOrdinal(zone) {
return zone.ordinal ?? zone.afterColumn ?? 10000;
}
_computeWhitespaceProps(zone) {
if (zone.afterLineNumber === 0) {
return {
isInHiddenArea: false,
afterViewLineNumber: 0,
heightInPx: this._heightInPixels(zone),
minWidthInPx: this._minWidthInPixels(zone)
};
}
let zoneAfterModelPosition;
if (typeof zone.afterColumn !== 'undefined') {
zoneAfterModelPosition = this._context.viewModel.model.validatePosition({
lineNumber: zone.afterLineNumber,
column: zone.afterColumn
});
}
else {
const validAfterLineNumber = this._context.viewModel.model.validatePosition({
lineNumber: zone.afterLineNumber,
column: 1
}).lineNumber;
zoneAfterModelPosition = new Position(validAfterLineNumber, this._context.viewModel.model.getLineMaxColumn(validAfterLineNumber));
}
let zoneBeforeModelPosition;
if (zoneAfterModelPosition.column === this._context.viewModel.model.getLineMaxColumn(zoneAfterModelPosition.lineNumber)) {
zoneBeforeModelPosition = this._context.viewModel.model.validatePosition({
lineNumber: zoneAfterModelPosition.lineNumber + 1,
column: 1
});
}
else {
zoneBeforeModelPosition = this._context.viewModel.model.validatePosition({
lineNumber: zoneAfterModelPosition.lineNumber,
column: zoneAfterModelPosition.column + 1
});
}
const viewPosition = this._context.viewModel.coordinatesConverter.convertModelPositionToViewPosition(zoneAfterModelPosition, zone.afterColumnAffinity, true);
const isVisible = zone.showInHiddenAreas || this._context.viewModel.coordinatesConverter.modelPositionIsVisible(zoneBeforeModelPosition);
return {
isInHiddenArea: !isVisible,
afterViewLineNumber: viewPosition.lineNumber,
heightInPx: (isVisible ? this._heightInPixels(zone) : 0),
minWidthInPx: this._minWidthInPixels(zone)
};
}
changeViewZones(callback) {
let zonesHaveChanged = false;
this._context.viewModel.changeWhitespace((whitespaceAccessor) => {
const changeAccessor = {
addZone: (zone) => {
zonesHaveChanged = true;
return this._addZone(whitespaceAccessor, zone);
},
removeZone: (id) => {
if (!id) {
return;
}
zonesHaveChanged = this._removeZone(whitespaceAccessor, id) || zonesHaveChanged;
},
layoutZone: (id) => {
if (!id) {
return;
}
zonesHaveChanged = this._layoutZone(whitespaceAccessor, id) || zonesHaveChanged;
}
};
safeInvoke1Arg(callback, changeAccessor);
// Invalidate changeAccessor
changeAccessor.addZone = invalidFunc;
changeAccessor.removeZone = invalidFunc;
changeAccessor.layoutZone = invalidFunc;
});
return zonesHaveChanged;
}
_addZone(whitespaceAccessor, zone) {
const props = this._computeWhitespaceProps(zone);
const whitespaceId = whitespaceAccessor.insertWhitespace(props.afterViewLineNumber, this._getZoneOrdinal(zone), props.heightInPx, props.minWidthInPx);
const myZone = {
whitespaceId: whitespaceId,
delegate: zone,
isInHiddenArea: props.isInHiddenArea,
isVisible: false,
domNode: createFastDomNode(zone.domNode),
marginDomNode: zone.marginDomNode ? createFastDomNode(zone.marginDomNode) : null
};
this._safeCallOnComputedHeight(myZone.delegate, props.heightInPx);
myZone.domNode.setPosition('absolute');
myZone.domNode.domNode.style.width = '100%';
myZone.domNode.setDisplay('none');
myZone.domNode.setAttribute('monaco-view-zone', myZone.whitespaceId);
this.domNode.appendChild(myZone.domNode);
if (myZone.marginDomNode) {
myZone.marginDomNode.setPosition('absolute');
myZone.marginDomNode.domNode.style.width = '100%';
myZone.marginDomNode.setDisplay('none');
myZone.marginDomNode.setAttribute('monaco-view-zone', myZone.whitespaceId);
this.marginDomNode.appendChild(myZone.marginDomNode);
}
this._zones[myZone.whitespaceId] = myZone;
this.setShouldRender();
return myZone.whitespaceId;
}
_removeZone(whitespaceAccessor, id) {
if (this._zones.hasOwnProperty(id)) {
const zone = this._zones[id];
delete this._zones[id];
whitespaceAccessor.removeWhitespace(zone.whitespaceId);
zone.domNode.removeAttribute('monaco-visible-view-zone');
zone.domNode.removeAttribute('monaco-view-zone');
zone.domNode.domNode.remove();
if (zone.marginDomNode) {
zone.marginDomNode.removeAttribute('monaco-visible-view-zone');
zone.marginDomNode.removeAttribute('monaco-view-zone');
zone.marginDomNode.domNode.remove();
}
this.setShouldRender();
return true;
}
return false;
}
_layoutZone(whitespaceAccessor, id) {
if (this._zones.hasOwnProperty(id)) {
const zone = this._zones[id];
const props = this._computeWhitespaceProps(zone.delegate);
zone.isInHiddenArea = props.isInHiddenArea;
// const newOrdinal = this._getZoneOrdinal(zone.delegate);
whitespaceAccessor.changeOneWhitespace(zone.whitespaceId, props.afterViewLineNumber, props.heightInPx);
// TODO@Alex: change `newOrdinal` too
this._safeCallOnComputedHeight(zone.delegate, props.heightInPx);
this.setShouldRender();
return true;
}
return false;
}
shouldSuppressMouseDownOnViewZone(id) {
if (this._zones.hasOwnProperty(id)) {
const zone = this._zones[id];
return Boolean(zone.delegate.suppressMouseDown);
}
return false;
}
_heightInPixels(zone) {
if (typeof zone.heightInPx === 'number') {
return zone.heightInPx;
}
if (typeof zone.heightInLines === 'number') {
return this._lineHeight * zone.heightInLines;
}
return this._lineHeight;
}
_minWidthInPixels(zone) {
if (typeof zone.minWidthInPx === 'number') {
return zone.minWidthInPx;
}
return 0;
}
_safeCallOnComputedHeight(zone, height) {
if (typeof zone.onComputedHeight === 'function') {
try {
zone.onComputedHeight(height);
}
catch (e) {
onUnexpectedError(e);
}
}
}
_safeCallOnDomNodeTop(zone, top) {
if (typeof zone.onDomNodeTop === 'function') {
try {
zone.onDomNodeTop(top);
}
catch (e) {
onUnexpectedError(e);
}
}
}
prepareRender(ctx) {
// Nothing to read
}
render(ctx) {
const visibleWhitespaces = ctx.viewportData.whitespaceViewportData;
const visibleZones = {};
let hasVisibleZone = false;
for (const visibleWhitespace of visibleWhitespaces) {
if (this._zones[visibleWhitespace.id].isInHiddenArea) {
continue;
}
visibleZones[visibleWhitespace.id] = visibleWhitespace;
hasVisibleZone = true;
}
const keys = Object.keys(this._zones);
for (let i = 0, len = keys.length; i < len; i++) {
const id = keys[i];
const zone = this._zones[id];
let newTop = 0;
let newHeight = 0;
let newDisplay = 'none';
if (visibleZones.hasOwnProperty(id)) {
newTop = visibleZones[id].verticalOffset - ctx.bigNumbersDelta;
newHeight = visibleZones[id].height;
newDisplay = 'block';
// zone is visible
if (!zone.isVisible) {
zone.domNode.setAttribute('monaco-visible-view-zone', 'true');
zone.isVisible = true;
}
this._safeCallOnDomNodeTop(zone.delegate, ctx.getScrolledTopFromAbsoluteTop(visibleZones[id].verticalOffset));
}
else {
if (zone.isVisible) {
zone.domNode.removeAttribute('monaco-visible-view-zone');
zone.isVisible = false;
}
this._safeCallOnDomNodeTop(zone.delegate, ctx.getScrolledTopFromAbsoluteTop(-1000000));
}
zone.domNode.setTop(newTop);
zone.domNode.setHeight(newHeight);
zone.domNode.setDisplay(newDisplay);
if (zone.marginDomNode) {
zone.marginDomNode.setTop(newTop);
zone.marginDomNode.setHeight(newHeight);
zone.marginDomNode.setDisplay(newDisplay);
}
}
if (hasVisibleZone) {
this.domNode.setWidth(Math.max(ctx.scrollWidth, this._contentWidth));
this.marginDomNode.setWidth(this._contentLeft);
}
}
}
function safeInvoke1Arg(func, arg1) {
try {
return func(arg1);
}
catch (e) {
onUnexpectedError(e);
}
}