monaco-editor-core
Version:
A browser based code editor
356 lines (355 loc) • 16 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 { Emitter } from '../../../base/common/event.js';
import { Disposable } from '../../../base/common/lifecycle.js';
import { Scrollable } from '../../../base/common/scrollable.js';
import { LinesLayout } from './linesLayout.js';
import { Viewport } from '../viewModel.js';
import { ContentSizeChangedEvent } from '../viewModelEventDispatcher.js';
const SMOOTH_SCROLLING_TIME = 125;
class EditorScrollDimensions {
constructor(width, contentWidth, height, contentHeight) {
width = width | 0;
contentWidth = contentWidth | 0;
height = height | 0;
contentHeight = contentHeight | 0;
if (width < 0) {
width = 0;
}
if (contentWidth < 0) {
contentWidth = 0;
}
if (height < 0) {
height = 0;
}
if (contentHeight < 0) {
contentHeight = 0;
}
this.width = width;
this.contentWidth = contentWidth;
this.scrollWidth = Math.max(width, contentWidth);
this.height = height;
this.contentHeight = contentHeight;
this.scrollHeight = Math.max(height, contentHeight);
}
equals(other) {
return (this.width === other.width
&& this.contentWidth === other.contentWidth
&& this.height === other.height
&& this.contentHeight === other.contentHeight);
}
}
class EditorScrollable extends Disposable {
constructor(smoothScrollDuration, scheduleAtNextAnimationFrame) {
super();
this._onDidContentSizeChange = this._register(new Emitter());
this.onDidContentSizeChange = this._onDidContentSizeChange.event;
this._dimensions = new EditorScrollDimensions(0, 0, 0, 0);
this._scrollable = this._register(new Scrollable({
forceIntegerValues: true,
smoothScrollDuration,
scheduleAtNextAnimationFrame
}));
this.onDidScroll = this._scrollable.onScroll;
}
getScrollable() {
return this._scrollable;
}
setSmoothScrollDuration(smoothScrollDuration) {
this._scrollable.setSmoothScrollDuration(smoothScrollDuration);
}
validateScrollPosition(scrollPosition) {
return this._scrollable.validateScrollPosition(scrollPosition);
}
getScrollDimensions() {
return this._dimensions;
}
setScrollDimensions(dimensions) {
if (this._dimensions.equals(dimensions)) {
return;
}
const oldDimensions = this._dimensions;
this._dimensions = dimensions;
this._scrollable.setScrollDimensions({
width: dimensions.width,
scrollWidth: dimensions.scrollWidth,
height: dimensions.height,
scrollHeight: dimensions.scrollHeight
}, true);
const contentWidthChanged = (oldDimensions.contentWidth !== dimensions.contentWidth);
const contentHeightChanged = (oldDimensions.contentHeight !== dimensions.contentHeight);
if (contentWidthChanged || contentHeightChanged) {
this._onDidContentSizeChange.fire(new ContentSizeChangedEvent(oldDimensions.contentWidth, oldDimensions.contentHeight, dimensions.contentWidth, dimensions.contentHeight));
}
}
getFutureScrollPosition() {
return this._scrollable.getFutureScrollPosition();
}
getCurrentScrollPosition() {
return this._scrollable.getCurrentScrollPosition();
}
setScrollPositionNow(update) {
this._scrollable.setScrollPositionNow(update);
}
setScrollPositionSmooth(update) {
this._scrollable.setScrollPositionSmooth(update);
}
hasPendingScrollAnimation() {
return this._scrollable.hasPendingScrollAnimation();
}
}
export class ViewLayout extends Disposable {
constructor(configuration, lineCount, scheduleAtNextAnimationFrame) {
super();
this._configuration = configuration;
const options = this._configuration.options;
const layoutInfo = options.get(146 /* EditorOption.layoutInfo */);
const padding = options.get(84 /* EditorOption.padding */);
this._linesLayout = new LinesLayout(lineCount, options.get(67 /* EditorOption.lineHeight */), padding.top, padding.bottom);
this._maxLineWidth = 0;
this._overlayWidgetsMinWidth = 0;
this._scrollable = this._register(new EditorScrollable(0, scheduleAtNextAnimationFrame));
this._configureSmoothScrollDuration();
this._scrollable.setScrollDimensions(new EditorScrollDimensions(layoutInfo.contentWidth, 0, layoutInfo.height, 0));
this.onDidScroll = this._scrollable.onDidScroll;
this.onDidContentSizeChange = this._scrollable.onDidContentSizeChange;
this._updateHeight();
}
dispose() {
super.dispose();
}
getScrollable() {
return this._scrollable.getScrollable();
}
onHeightMaybeChanged() {
this._updateHeight();
}
_configureSmoothScrollDuration() {
this._scrollable.setSmoothScrollDuration(this._configuration.options.get(115 /* EditorOption.smoothScrolling */) ? SMOOTH_SCROLLING_TIME : 0);
}
// ---- begin view event handlers
onConfigurationChanged(e) {
const options = this._configuration.options;
if (e.hasChanged(67 /* EditorOption.lineHeight */)) {
this._linesLayout.setLineHeight(options.get(67 /* EditorOption.lineHeight */));
}
if (e.hasChanged(84 /* EditorOption.padding */)) {
const padding = options.get(84 /* EditorOption.padding */);
this._linesLayout.setPadding(padding.top, padding.bottom);
}
if (e.hasChanged(146 /* EditorOption.layoutInfo */)) {
const layoutInfo = options.get(146 /* EditorOption.layoutInfo */);
const width = layoutInfo.contentWidth;
const height = layoutInfo.height;
const scrollDimensions = this._scrollable.getScrollDimensions();
const contentWidth = scrollDimensions.contentWidth;
this._scrollable.setScrollDimensions(new EditorScrollDimensions(width, scrollDimensions.contentWidth, height, this._getContentHeight(width, height, contentWidth)));
}
else {
this._updateHeight();
}
if (e.hasChanged(115 /* EditorOption.smoothScrolling */)) {
this._configureSmoothScrollDuration();
}
}
onFlushed(lineCount) {
this._linesLayout.onFlushed(lineCount);
}
onLinesDeleted(fromLineNumber, toLineNumber) {
this._linesLayout.onLinesDeleted(fromLineNumber, toLineNumber);
}
onLinesInserted(fromLineNumber, toLineNumber) {
this._linesLayout.onLinesInserted(fromLineNumber, toLineNumber);
}
// ---- end view event handlers
_getHorizontalScrollbarHeight(width, scrollWidth) {
const options = this._configuration.options;
const scrollbar = options.get(104 /* EditorOption.scrollbar */);
if (scrollbar.horizontal === 2 /* ScrollbarVisibility.Hidden */) {
// horizontal scrollbar not visible
return 0;
}
if (width >= scrollWidth) {
// horizontal scrollbar not visible
return 0;
}
return scrollbar.horizontalScrollbarSize;
}
_getContentHeight(width, height, contentWidth) {
const options = this._configuration.options;
let result = this._linesLayout.getLinesTotalHeight();
if (options.get(106 /* EditorOption.scrollBeyondLastLine */)) {
result += Math.max(0, height - options.get(67 /* EditorOption.lineHeight */) - options.get(84 /* EditorOption.padding */).bottom);
}
else if (!options.get(104 /* EditorOption.scrollbar */).ignoreHorizontalScrollbarInContentHeight) {
result += this._getHorizontalScrollbarHeight(width, contentWidth);
}
return result;
}
_updateHeight() {
const scrollDimensions = this._scrollable.getScrollDimensions();
const width = scrollDimensions.width;
const height = scrollDimensions.height;
const contentWidth = scrollDimensions.contentWidth;
this._scrollable.setScrollDimensions(new EditorScrollDimensions(width, scrollDimensions.contentWidth, height, this._getContentHeight(width, height, contentWidth)));
}
// ---- Layouting logic
getCurrentViewport() {
const scrollDimensions = this._scrollable.getScrollDimensions();
const currentScrollPosition = this._scrollable.getCurrentScrollPosition();
return new Viewport(currentScrollPosition.scrollTop, currentScrollPosition.scrollLeft, scrollDimensions.width, scrollDimensions.height);
}
getFutureViewport() {
const scrollDimensions = this._scrollable.getScrollDimensions();
const currentScrollPosition = this._scrollable.getFutureScrollPosition();
return new Viewport(currentScrollPosition.scrollTop, currentScrollPosition.scrollLeft, scrollDimensions.width, scrollDimensions.height);
}
_computeContentWidth() {
const options = this._configuration.options;
const maxLineWidth = this._maxLineWidth;
const wrappingInfo = options.get(147 /* EditorOption.wrappingInfo */);
const fontInfo = options.get(50 /* EditorOption.fontInfo */);
const layoutInfo = options.get(146 /* EditorOption.layoutInfo */);
if (wrappingInfo.isViewportWrapping) {
const minimap = options.get(73 /* EditorOption.minimap */);
if (maxLineWidth > layoutInfo.contentWidth + fontInfo.typicalHalfwidthCharacterWidth) {
// This is a case where viewport wrapping is on, but the line extends above the viewport
if (minimap.enabled && minimap.side === 'right') {
// We need to accomodate the scrollbar width
return maxLineWidth + layoutInfo.verticalScrollbarWidth;
}
}
return maxLineWidth;
}
else {
const extraHorizontalSpace = options.get(105 /* EditorOption.scrollBeyondLastColumn */) * fontInfo.typicalHalfwidthCharacterWidth;
const whitespaceMinWidth = this._linesLayout.getWhitespaceMinWidth();
return Math.max(maxLineWidth + extraHorizontalSpace + layoutInfo.verticalScrollbarWidth, whitespaceMinWidth, this._overlayWidgetsMinWidth);
}
}
setMaxLineWidth(maxLineWidth) {
this._maxLineWidth = maxLineWidth;
this._updateContentWidth();
}
setOverlayWidgetsMinWidth(maxMinWidth) {
this._overlayWidgetsMinWidth = maxMinWidth;
this._updateContentWidth();
}
_updateContentWidth() {
const scrollDimensions = this._scrollable.getScrollDimensions();
this._scrollable.setScrollDimensions(new EditorScrollDimensions(scrollDimensions.width, this._computeContentWidth(), scrollDimensions.height, scrollDimensions.contentHeight));
// The height might depend on the fact that there is a horizontal scrollbar or not
this._updateHeight();
}
// ---- view state
saveState() {
const currentScrollPosition = this._scrollable.getFutureScrollPosition();
const scrollTop = currentScrollPosition.scrollTop;
const firstLineNumberInViewport = this._linesLayout.getLineNumberAtOrAfterVerticalOffset(scrollTop);
const whitespaceAboveFirstLine = this._linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(firstLineNumberInViewport);
return {
scrollTop: scrollTop,
scrollTopWithoutViewZones: scrollTop - whitespaceAboveFirstLine,
scrollLeft: currentScrollPosition.scrollLeft
};
}
// ----
changeWhitespace(callback) {
const hadAChange = this._linesLayout.changeWhitespace(callback);
if (hadAChange) {
this.onHeightMaybeChanged();
}
return hadAChange;
}
getVerticalOffsetForLineNumber(lineNumber, includeViewZones = false) {
return this._linesLayout.getVerticalOffsetForLineNumber(lineNumber, includeViewZones);
}
getVerticalOffsetAfterLineNumber(lineNumber, includeViewZones = false) {
return this._linesLayout.getVerticalOffsetAfterLineNumber(lineNumber, includeViewZones);
}
isAfterLines(verticalOffset) {
return this._linesLayout.isAfterLines(verticalOffset);
}
isInTopPadding(verticalOffset) {
return this._linesLayout.isInTopPadding(verticalOffset);
}
isInBottomPadding(verticalOffset) {
return this._linesLayout.isInBottomPadding(verticalOffset);
}
getLineNumberAtVerticalOffset(verticalOffset) {
return this._linesLayout.getLineNumberAtOrAfterVerticalOffset(verticalOffset);
}
getWhitespaceAtVerticalOffset(verticalOffset) {
return this._linesLayout.getWhitespaceAtVerticalOffset(verticalOffset);
}
getLinesViewportData() {
const visibleBox = this.getCurrentViewport();
return this._linesLayout.getLinesViewportData(visibleBox.top, visibleBox.top + visibleBox.height);
}
getLinesViewportDataAtScrollTop(scrollTop) {
// do some minimal validations on scrollTop
const scrollDimensions = this._scrollable.getScrollDimensions();
if (scrollTop + scrollDimensions.height > scrollDimensions.scrollHeight) {
scrollTop = scrollDimensions.scrollHeight - scrollDimensions.height;
}
if (scrollTop < 0) {
scrollTop = 0;
}
return this._linesLayout.getLinesViewportData(scrollTop, scrollTop + scrollDimensions.height);
}
getWhitespaceViewportData() {
const visibleBox = this.getCurrentViewport();
return this._linesLayout.getWhitespaceViewportData(visibleBox.top, visibleBox.top + visibleBox.height);
}
getWhitespaces() {
return this._linesLayout.getWhitespaces();
}
// ----
getContentWidth() {
const scrollDimensions = this._scrollable.getScrollDimensions();
return scrollDimensions.contentWidth;
}
getScrollWidth() {
const scrollDimensions = this._scrollable.getScrollDimensions();
return scrollDimensions.scrollWidth;
}
getContentHeight() {
const scrollDimensions = this._scrollable.getScrollDimensions();
return scrollDimensions.contentHeight;
}
getScrollHeight() {
const scrollDimensions = this._scrollable.getScrollDimensions();
return scrollDimensions.scrollHeight;
}
getCurrentScrollLeft() {
const currentScrollPosition = this._scrollable.getCurrentScrollPosition();
return currentScrollPosition.scrollLeft;
}
getCurrentScrollTop() {
const currentScrollPosition = this._scrollable.getCurrentScrollPosition();
return currentScrollPosition.scrollTop;
}
validateScrollPosition(scrollPosition) {
return this._scrollable.validateScrollPosition(scrollPosition);
}
setScrollPosition(position, type) {
if (type === 1 /* ScrollType.Immediate */) {
this._scrollable.setScrollPositionNow(position);
}
else {
this._scrollable.setScrollPositionSmooth(position);
}
}
hasPendingScrollAnimation() {
return this._scrollable.hasPendingScrollAnimation();
}
deltaScrollNow(deltaScrollLeft, deltaScrollTop) {
const currentScrollPosition = this._scrollable.getCurrentScrollPosition();
this._scrollable.setScrollPositionNow({
scrollLeft: currentScrollPosition.scrollLeft + deltaScrollLeft,
scrollTop: currentScrollPosition.scrollTop + deltaScrollTop
});
}
}