UNPKG

monaco-editor-core

Version:

A browser based code editor

356 lines (355 loc) • 16 kB
/*--------------------------------------------------------------------------------------------- * 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 }); } }