/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ 'use strict'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { Scrollable, ScrollbarVisibility } from 'vs/base/common/scrollable'; import * as editorCommon from 'vs/editor/common/editorCommon'; import { LinesLayout } from 'vs/editor/common/viewLayout/linesLayout'; import { IViewLayout, IViewModel } from 'vs/editor/common/viewModel/viewModel'; import { IPartialViewLinesViewportData } from 'vs/editor/common/viewLayout/viewLinesViewportData'; import { IViewEventBus } from 'vs/editor/common/view/viewContext'; export class LayoutProvider implements IDisposable, IViewLayout { static LINES_HORIZONTAL_EXTRA_PX = 30; private _toDispose: IDisposable[]; private _configuration: editorCommon.IConfiguration; private _privateViewEventBus: IViewEventBus; private _model: IViewModel; private _linesLayout: LinesLayout; private _scrollable: Scrollable; constructor(configuration: editorCommon.IConfiguration, model: IViewModel, privateViewEventBus: IViewEventBus) { this._scrollable = new Scrollable(); this._scrollable.updateState({ width: configuration.editor.layoutInfo.contentWidth, height: configuration.editor.layoutInfo.contentHeight }); this._toDispose = []; this._toDispose.push(this._scrollable); this._configuration = configuration; this._privateViewEventBus = privateViewEventBus; this._model = model; this._configuration.setMaxLineNumber(this._model.getMaxLineNumber()); this._linesLayout = new LinesLayout(this._model.getLineCount(), this._configuration.editor.lineHeight); this._updateHeight(); } public dispose(): void { this._toDispose = dispose(this._toDispose); } public getScrollable(): Scrollable { return this._scrollable; } public onHeightMaybeChanged(): void { this._updateHeight(); } // ---- begin view event handlers public onModelFlushed(): void { this._linesLayout.onModelFlushed(this._model.getLineCount()); this._configuration.setMaxLineNumber(this._model.getMaxLineNumber()); this._updateHeight(); } public onModelLinesDeleted(e: editorCommon.IViewLinesDeletedEvent): void { this._linesLayout.onModelLinesDeleted(e.fromLineNumber, e.toLineNumber); this._configuration.setMaxLineNumber(this._model.getMaxLineNumber()); this._updateHeight(); } public onModelLinesInserted(e: editorCommon.IViewLinesInsertedEvent): void { this._linesLayout.onModelLinesInserted(e.fromLineNumber, e.toLineNumber); this._configuration.setMaxLineNumber(this._model.getMaxLineNumber()); this._updateHeight(); } public onConfigurationChanged(e: editorCommon.IConfigurationChangedEvent): void { if (e.lineHeight) { this._linesLayout.setLineHeight(this._configuration.editor.lineHeight); } if (e.layoutInfo) { this._scrollable.updateState({ width: this._configuration.editor.layoutInfo.contentWidth, height: this._configuration.editor.layoutInfo.contentHeight }); this._emitLayoutChangedEvent(); } this._updateHeight(); } private _updateHeight(): void { this._scrollable.updateState({ scrollHeight: this.getTotalHeight() }); } // ---- end view event handlers // ---- Layouting logic public getCurrentViewport(): editorCommon.Viewport { const scrollState = this._scrollable.getState(); return new editorCommon.Viewport( scrollState.scrollTop, scrollState.scrollLeft, scrollState.width, scrollState.height ); } private _emitLayoutChangedEvent(): void { this._privateViewEventBus.emit(editorCommon.EventType.ViewLayoutChanged, this._configuration.editor.layoutInfo); } public emitLayoutChangedEvent(): void { this._emitLayoutChangedEvent(); } private _computeScrollWidth(maxLineWidth: number, viewportWidth: number): number { let isViewportWrapping = this._configuration.editor.wrappingInfo.isViewportWrapping; if (!isViewportWrapping) { return Math.max(maxLineWidth + LayoutProvider.LINES_HORIZONTAL_EXTRA_PX, viewportWidth); } return Math.max(maxLineWidth, viewportWidth); } public onMaxLineWidthChanged(maxLineWidth: number): void { let newScrollWidth = this._computeScrollWidth(maxLineWidth, this.getCurrentViewport().width); this._scrollable.updateState({ scrollWidth: newScrollWidth }); // The height might depend on the fact that there is a horizontal scrollbar or not this._updateHeight(); } // ---- view state public saveState(): editorCommon.IViewState { const scrollState = this._scrollable.getState(); let scrollTop = scrollState.scrollTop; let firstLineNumberInViewport = this._linesLayout.getLineNumberAtOrAfterVerticalOffset(scrollTop); let whitespaceAboveFirstLine = this._linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(firstLineNumberInViewport); return { scrollTop: scrollTop, scrollTopWithoutViewZones: scrollTop - whitespaceAboveFirstLine, scrollLeft: scrollState.scrollLeft }; } public restoreState(state: editorCommon.IViewState): void { let restoreScrollTop = state.scrollTop; if (typeof state.scrollTopWithoutViewZones === 'number' && !this._linesLayout.hasWhitespace()) { restoreScrollTop = state.scrollTopWithoutViewZones; } this._scrollable.updateState({ scrollLeft: state.scrollLeft, scrollTop: restoreScrollTop }); } // ---- IVerticalLayoutProvider public addWhitespace(afterLineNumber: number, ordinal: number, height: number): number { return this._linesLayout.insertWhitespace(afterLineNumber, ordinal, height); } public changeWhitespace(id: number, newAfterLineNumber: number, newHeight: number): boolean { return this._linesLayout.changeWhitespace(id, newAfterLineNumber, newHeight); } public removeWhitespace(id: number): boolean { return this._linesLayout.removeWhitespace(id); } public getVerticalOffsetForLineNumber(lineNumber: number): number { return this._linesLayout.getVerticalOffsetForLineNumber(lineNumber); } public isAfterLines(verticalOffset: number): boolean { return this._linesLayout.isAfterLines(verticalOffset); } public getLineNumberAtVerticalOffset(verticalOffset: number): number { return this._linesLayout.getLineNumberAtOrAfterVerticalOffset(verticalOffset); } /** * Get the sum of heights for all objects and compute basically the `scrollHeight` for the editor content. * * Take into account the `scrollBeyondLastLine` and `reserveHorizontalScrollbarHeight` and produce a scrollHeight that is at least as large as `viewport`.height. * * @param viewport The viewport. * @param reserveHorizontalScrollbarHeight The height of the horizontal scrollbar. * @return Basically, the `scrollHeight` for the editor content. */ private _getTotalHeight(viewport: editorCommon.Viewport, reserveHorizontalScrollbarHeight: number): number { var totalLinesHeight = this._linesLayout.getLinesTotalHeight(); if (this._configuration.editor.viewInfo.scrollBeyondLastLine) { totalLinesHeight += viewport.height - this._configuration.editor.lineHeight; } else { totalLinesHeight += reserveHorizontalScrollbarHeight; } return Math.max(viewport.height, totalLinesHeight); } public getTotalHeight(): number { const scrollState = this._scrollable.getState(); let reserveHorizontalScrollbarHeight = 0; if (scrollState.scrollWidth > scrollState.width) { if (this._configuration.editor.viewInfo.scrollbar.horizontal !== ScrollbarVisibility.Hidden) { reserveHorizontalScrollbarHeight = this._configuration.editor.viewInfo.scrollbar.horizontalScrollbarSize; } } return this._getTotalHeight(this.getCurrentViewport(), reserveHorizontalScrollbarHeight); } public getWhitespaceAtVerticalOffset(verticalOffset: number): editorCommon.IViewWhitespaceViewportData { return this._linesLayout.getWhitespaceAtVerticalOffset(verticalOffset); } public getLinesViewportData(): IPartialViewLinesViewportData { const visibleBox = this.getCurrentViewport(); return this._linesLayout.getLinesViewportData(visibleBox.top, visibleBox.top + visibleBox.height); } public getWhitespaceViewportData(): editorCommon.IViewWhitespaceViewportData[] { const visibleBox = this.getCurrentViewport(); return this._linesLayout.getWhitespaceViewportData(visibleBox.top, visibleBox.top + visibleBox.height); } public getWhitespaces(): editorCommon.IEditorWhitespace[] { return this._linesLayout.getWhitespaces(); } // ---- IScrollingProvider public getScrollWidth(): number { const scrollState = this._scrollable.getState(); return scrollState.scrollWidth; } public getScrollLeft(): number { const scrollState = this._scrollable.getState(); return scrollState.scrollLeft; } public getScrollHeight(): number { const scrollState = this._scrollable.getState(); return scrollState.scrollHeight; } public getScrollTop(): number { const scrollState = this._scrollable.getState(); return scrollState.scrollTop; } public setScrollPosition(position: editorCommon.INewScrollPosition): void { this._scrollable.updateState(position); } public getScrolledTopFromAbsoluteTop(top: number): number { const scrollState = this._scrollable.getState(); return top - scrollState.scrollTop; } }