Path: blob/main/src/vs/editor/browser/viewParts/viewLines/viewLines.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { FastDomNode } from '../../../../base/browser/fastDomNode.js';6import { MOUSE_CURSOR_TEXT_CSS_CLASS_NAME } from '../../../../base/browser/ui/mouseCursor/mouseCursor.js';7import { RunOnceScheduler } from '../../../../base/common/async.js';8import * as platform from '../../../../base/common/platform.js';9import { Constants } from '../../../../base/common/uint.js';10import './viewLines.css';11import { applyFontInfo } from '../../config/domFontInfo.js';12import { HorizontalPosition, HorizontalRange, IViewLines, LineVisibleRanges, VisibleRanges } from '../../view/renderingContext.js';13import { VisibleLinesCollection } from '../../view/viewLayer.js';14import { PartFingerprint, PartFingerprints, ViewPart } from '../../view/viewPart.js';15import { DomReadingContext } from './domReadingContext.js';16import { ViewLine } from './viewLine.js';17import { EditorOption } from '../../../common/config/editorOptions.js';18import { Position } from '../../../common/core/position.js';19import { Range } from '../../../common/core/range.js';20import { Selection } from '../../../common/core/selection.js';21import { ScrollType } from '../../../common/editorCommon.js';22import * as viewEvents from '../../../common/viewEvents.js';23import { ViewportData } from '../../../common/viewLayout/viewLinesViewportData.js';24import { Viewport } from '../../../common/viewModel.js';25import { ViewContext } from '../../../common/viewModel/viewContext.js';26import { ViewLineOptions } from './viewLineOptions.js';27import type { ViewGpuContext } from '../../gpu/viewGpuContext.js';28import { TextDirection } from '../../../common/model.js';2930class LastRenderedData {3132private _currentVisibleRange: Range;3334constructor() {35this._currentVisibleRange = new Range(1, 1, 1, 1);36}3738public getCurrentVisibleRange(): Range {39return this._currentVisibleRange;40}4142public setCurrentVisibleRange(currentVisibleRange: Range): void {43this._currentVisibleRange = currentVisibleRange;44}45}4647class HorizontalRevealRangeRequest {48public readonly type = 'range';49public readonly minLineNumber: number;50public readonly maxLineNumber: number;5152constructor(53public readonly minimalReveal: boolean,54public readonly lineNumber: number,55public readonly startColumn: number,56public readonly endColumn: number,57public readonly startScrollTop: number,58public readonly stopScrollTop: number,59public readonly scrollType: ScrollType60) {61this.minLineNumber = lineNumber;62this.maxLineNumber = lineNumber;63}64}6566class HorizontalRevealSelectionsRequest {67public readonly type = 'selections';68public readonly minLineNumber: number;69public readonly maxLineNumber: number;7071constructor(72public readonly minimalReveal: boolean,73public readonly selections: Selection[],74public readonly startScrollTop: number,75public readonly stopScrollTop: number,76public readonly scrollType: ScrollType77) {78let minLineNumber = selections[0].startLineNumber;79let maxLineNumber = selections[0].endLineNumber;80for (let i = 1, len = selections.length; i < len; i++) {81const selection = selections[i];82minLineNumber = Math.min(minLineNumber, selection.startLineNumber);83maxLineNumber = Math.max(maxLineNumber, selection.endLineNumber);84}85this.minLineNumber = minLineNumber;86this.maxLineNumber = maxLineNumber;87}88}8990type HorizontalRevealRequest = HorizontalRevealRangeRequest | HorizontalRevealSelectionsRequest;9192/**93* The view lines part is responsible for rendering the actual content of a94* file.95*/96export class ViewLines extends ViewPart implements IViewLines {97/**98* Adds this amount of pixels to the right of lines (no-one wants to type near the edge of the viewport)99*/100private static readonly HORIZONTAL_EXTRA_PX = 30;101102private readonly _linesContent: FastDomNode<HTMLElement>;103private readonly _textRangeRestingSpot: HTMLElement;104private readonly _visibleLines: VisibleLinesCollection<ViewLine>;105private readonly domNode: FastDomNode<HTMLElement>;106107// --- config108private _lineHeight: number;109private _typicalHalfwidthCharacterWidth: number;110private _isViewportWrapping: boolean;111private _revealHorizontalRightPadding: number;112private _cursorSurroundingLines: number;113private _cursorSurroundingLinesStyle: 'default' | 'all';114private _canUseLayerHinting: boolean;115private _viewLineOptions: ViewLineOptions;116117// --- width118private _maxLineWidth: number;119private readonly _asyncUpdateLineWidths: RunOnceScheduler;120private readonly _asyncCheckMonospaceFontAssumptions: RunOnceScheduler;121122private _horizontalRevealRequest: HorizontalRevealRequest | null;123private readonly _lastRenderedData: LastRenderedData;124125// Sticky Scroll126private _stickyScrollEnabled: boolean;127private _maxNumberStickyLines: number;128129constructor(context: ViewContext, viewGpuContext: ViewGpuContext | undefined, linesContent: FastDomNode<HTMLElement>) {130super(context);131132const conf = this._context.configuration;133const options = this._context.configuration.options;134const fontInfo = options.get(EditorOption.fontInfo);135const wrappingInfo = options.get(EditorOption.wrappingInfo);136137this._lineHeight = options.get(EditorOption.lineHeight);138this._typicalHalfwidthCharacterWidth = fontInfo.typicalHalfwidthCharacterWidth;139this._isViewportWrapping = wrappingInfo.isViewportWrapping;140this._revealHorizontalRightPadding = options.get(EditorOption.revealHorizontalRightPadding);141this._cursorSurroundingLines = options.get(EditorOption.cursorSurroundingLines);142this._cursorSurroundingLinesStyle = options.get(EditorOption.cursorSurroundingLinesStyle);143this._canUseLayerHinting = !options.get(EditorOption.disableLayerHinting);144this._viewLineOptions = new ViewLineOptions(conf, this._context.theme.type);145146this._linesContent = linesContent;147this._textRangeRestingSpot = document.createElement('div');148this._visibleLines = new VisibleLinesCollection(this._context, {149createLine: () => new ViewLine(viewGpuContext, this._viewLineOptions),150});151this.domNode = this._visibleLines.domNode;152153PartFingerprints.write(this.domNode, PartFingerprint.ViewLines);154this.domNode.setClassName(`view-lines ${MOUSE_CURSOR_TEXT_CSS_CLASS_NAME}`);155applyFontInfo(this.domNode, fontInfo);156157// --- width & height158this._maxLineWidth = 0;159this._asyncUpdateLineWidths = new RunOnceScheduler(() => {160this._updateLineWidthsSlow();161}, 200);162this._asyncCheckMonospaceFontAssumptions = new RunOnceScheduler(() => {163this._checkMonospaceFontAssumptions();164}, 2000);165166this._lastRenderedData = new LastRenderedData();167168this._horizontalRevealRequest = null;169170// sticky scroll widget171this._stickyScrollEnabled = options.get(EditorOption.stickyScroll).enabled;172this._maxNumberStickyLines = options.get(EditorOption.stickyScroll).maxLineCount;173}174175public override dispose(): void {176this._asyncUpdateLineWidths.dispose();177this._asyncCheckMonospaceFontAssumptions.dispose();178super.dispose();179}180181public getDomNode(): FastDomNode<HTMLElement> {182return this.domNode;183}184185// ---- begin view event handlers186187public override onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean {188this._visibleLines.onConfigurationChanged(e);189if (e.hasChanged(EditorOption.wrappingInfo)) {190this._maxLineWidth = 0;191}192193const options = this._context.configuration.options;194const fontInfo = options.get(EditorOption.fontInfo);195const wrappingInfo = options.get(EditorOption.wrappingInfo);196197this._lineHeight = options.get(EditorOption.lineHeight);198this._typicalHalfwidthCharacterWidth = fontInfo.typicalHalfwidthCharacterWidth;199this._isViewportWrapping = wrappingInfo.isViewportWrapping;200this._revealHorizontalRightPadding = options.get(EditorOption.revealHorizontalRightPadding);201this._cursorSurroundingLines = options.get(EditorOption.cursorSurroundingLines);202this._cursorSurroundingLinesStyle = options.get(EditorOption.cursorSurroundingLinesStyle);203this._canUseLayerHinting = !options.get(EditorOption.disableLayerHinting);204205// sticky scroll206this._stickyScrollEnabled = options.get(EditorOption.stickyScroll).enabled;207this._maxNumberStickyLines = options.get(EditorOption.stickyScroll).maxLineCount;208209applyFontInfo(this.domNode, fontInfo);210211this._onOptionsMaybeChanged();212213if (e.hasChanged(EditorOption.layoutInfo)) {214this._maxLineWidth = 0;215}216217return true;218}219private _onOptionsMaybeChanged(): boolean {220const conf = this._context.configuration;221222const newViewLineOptions = new ViewLineOptions(conf, this._context.theme.type);223if (!this._viewLineOptions.equals(newViewLineOptions)) {224this._viewLineOptions = newViewLineOptions;225226const startLineNumber = this._visibleLines.getStartLineNumber();227const endLineNumber = this._visibleLines.getEndLineNumber();228for (let lineNumber = startLineNumber; lineNumber <= endLineNumber; lineNumber++) {229const line = this._visibleLines.getVisibleLine(lineNumber);230line.onOptionsChanged(this._viewLineOptions);231}232return true;233}234235return false;236}237public override onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean {238const rendStartLineNumber = this._visibleLines.getStartLineNumber();239const rendEndLineNumber = this._visibleLines.getEndLineNumber();240let r = false;241for (let lineNumber = rendStartLineNumber; lineNumber <= rendEndLineNumber; lineNumber++) {242r = this._visibleLines.getVisibleLine(lineNumber).onSelectionChanged() || r;243}244return r;245}246public override onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean {247if (true/*e.inlineDecorationsChanged*/) {248const rendStartLineNumber = this._visibleLines.getStartLineNumber();249const rendEndLineNumber = this._visibleLines.getEndLineNumber();250for (let lineNumber = rendStartLineNumber; lineNumber <= rendEndLineNumber; lineNumber++) {251this._visibleLines.getVisibleLine(lineNumber).onDecorationsChanged();252}253}254return true;255}256public override onFlushed(e: viewEvents.ViewFlushedEvent): boolean {257const shouldRender = this._visibleLines.onFlushed(e, this._viewLineOptions.useGpu);258this._maxLineWidth = 0;259return shouldRender;260}261public override onLinesChanged(e: viewEvents.ViewLinesChangedEvent): boolean {262return this._visibleLines.onLinesChanged(e);263}264public override onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): boolean {265return this._visibleLines.onLinesDeleted(e);266}267public override onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): boolean {268return this._visibleLines.onLinesInserted(e);269}270public override onRevealRangeRequest(e: viewEvents.ViewRevealRangeRequestEvent): boolean {271// Using the future viewport here in order to handle multiple272// incoming reveal range requests that might all desire to be animated273const desiredScrollTop = this._computeScrollTopToRevealRange(this._context.viewLayout.getFutureViewport(), e.source, e.minimalReveal, e.range, e.selections, e.verticalType);274275if (desiredScrollTop === -1) {276// marker to abort the reveal range request277return false;278}279280// validate the new desired scroll top281let newScrollPosition = this._context.viewLayout.validateScrollPosition({ scrollTop: desiredScrollTop });282283if (e.revealHorizontal) {284if (e.range && e.range.startLineNumber !== e.range.endLineNumber) {285// Two or more lines? => scroll to base (That's how you see most of the two lines)286newScrollPosition = {287scrollTop: newScrollPosition.scrollTop,288scrollLeft: 0289};290} else if (e.range) {291// We don't necessarily know the horizontal offset of this range since the line might not be in the view...292this._horizontalRevealRequest = new HorizontalRevealRangeRequest(e.minimalReveal, e.range.startLineNumber, e.range.startColumn, e.range.endColumn, this._context.viewLayout.getCurrentScrollTop(), newScrollPosition.scrollTop, e.scrollType);293} else if (e.selections && e.selections.length > 0) {294this._horizontalRevealRequest = new HorizontalRevealSelectionsRequest(e.minimalReveal, e.selections, this._context.viewLayout.getCurrentScrollTop(), newScrollPosition.scrollTop, e.scrollType);295}296} else {297this._horizontalRevealRequest = null;298}299300const scrollTopDelta = Math.abs(this._context.viewLayout.getCurrentScrollTop() - newScrollPosition.scrollTop);301const scrollType = (scrollTopDelta <= this._lineHeight ? ScrollType.Immediate : e.scrollType);302this._context.viewModel.viewLayout.setScrollPosition(newScrollPosition, scrollType);303304return true;305}306public override onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean {307if (this._horizontalRevealRequest && e.scrollLeftChanged) {308// cancel any outstanding horizontal reveal request if someone else scrolls horizontally.309this._horizontalRevealRequest = null;310}311if (this._horizontalRevealRequest && e.scrollTopChanged) {312const min = Math.min(this._horizontalRevealRequest.startScrollTop, this._horizontalRevealRequest.stopScrollTop);313const max = Math.max(this._horizontalRevealRequest.startScrollTop, this._horizontalRevealRequest.stopScrollTop);314if (e.scrollTop < min || e.scrollTop > max) {315// cancel any outstanding horizontal reveal request if someone else scrolls vertically.316this._horizontalRevealRequest = null;317}318}319this.domNode.setWidth(e.scrollWidth);320return this._visibleLines.onScrollChanged(e) || true;321}322323public override onTokensChanged(e: viewEvents.ViewTokensChangedEvent): boolean {324return this._visibleLines.onTokensChanged(e);325}326public override onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean {327this._context.viewModel.viewLayout.setMaxLineWidth(this._maxLineWidth);328return this._visibleLines.onZonesChanged(e);329}330public override onThemeChanged(e: viewEvents.ViewThemeChangedEvent): boolean {331return this._onOptionsMaybeChanged();332}333334// ---- end view event handlers335336// ----------- HELPERS FOR OTHERS337338public getPositionFromDOMInfo(spanNode: HTMLElement, offset: number): Position | null {339const viewLineDomNode = this._getViewLineDomNode(spanNode);340if (viewLineDomNode === null) {341// Couldn't find view line node342return null;343}344const lineNumber = this._getLineNumberFor(viewLineDomNode);345346if (lineNumber === -1) {347// Couldn't find view line node348return null;349}350351if (lineNumber < 1 || lineNumber > this._context.viewModel.getLineCount()) {352// lineNumber is outside range353return null;354}355356if (this._context.viewModel.getLineMaxColumn(lineNumber) === 1) {357// Line is empty358return new Position(lineNumber, 1);359}360361const rendStartLineNumber = this._visibleLines.getStartLineNumber();362const rendEndLineNumber = this._visibleLines.getEndLineNumber();363if (lineNumber < rendStartLineNumber || lineNumber > rendEndLineNumber) {364// Couldn't find line365return null;366}367368let column = this._visibleLines.getVisibleLine(lineNumber).getColumnOfNodeOffset(spanNode, offset);369const minColumn = this._context.viewModel.getLineMinColumn(lineNumber);370if (column < minColumn) {371column = minColumn;372}373return new Position(lineNumber, column);374}375376private _getViewLineDomNode(node: HTMLElement | null): HTMLElement | null {377while (node && node.nodeType === 1) {378if (node.className === ViewLine.CLASS_NAME) {379return node;380}381node = node.parentElement;382}383return null;384}385386/**387* @returns the line number of this view line dom node.388*/389private _getLineNumberFor(domNode: HTMLElement): number {390const startLineNumber = this._visibleLines.getStartLineNumber();391const endLineNumber = this._visibleLines.getEndLineNumber();392for (let lineNumber = startLineNumber; lineNumber <= endLineNumber; lineNumber++) {393const line = this._visibleLines.getVisibleLine(lineNumber);394if (domNode === line.getDomNode()) {395return lineNumber;396}397}398return -1;399}400401public getLineWidth(lineNumber: number): number {402const rendStartLineNumber = this._visibleLines.getStartLineNumber();403const rendEndLineNumber = this._visibleLines.getEndLineNumber();404if (lineNumber < rendStartLineNumber || lineNumber > rendEndLineNumber) {405// Couldn't find line406return -1;407}408409const context = new DomReadingContext(this.domNode.domNode, this._textRangeRestingSpot);410const result = this._visibleLines.getVisibleLine(lineNumber).getWidth(context);411this._updateLineWidthsSlowIfDomDidLayout(context);412413return result;414}415416public linesVisibleRangesForRange(_range: Range, includeNewLines: boolean): LineVisibleRanges[] | null {417if (this.shouldRender()) {418// Cannot read from the DOM because it is dirty419// i.e. the model & the dom are out of sync, so I'd be reading something stale420return null;421}422423const originalEndLineNumber = _range.endLineNumber;424const range = Range.intersectRanges(_range, this._lastRenderedData.getCurrentVisibleRange());425if (!range) {426return null;427}428429const visibleRanges: LineVisibleRanges[] = [];430let visibleRangesLen = 0;431const domReadingContext = new DomReadingContext(this.domNode.domNode, this._textRangeRestingSpot);432433let nextLineModelLineNumber: number = 0;434if (includeNewLines) {435nextLineModelLineNumber = this._context.viewModel.coordinatesConverter.convertViewPositionToModelPosition(new Position(range.startLineNumber, 1)).lineNumber;436}437438const rendStartLineNumber = this._visibleLines.getStartLineNumber();439const rendEndLineNumber = this._visibleLines.getEndLineNumber();440for (let lineNumber = range.startLineNumber; lineNumber <= range.endLineNumber; lineNumber++) {441442if (lineNumber < rendStartLineNumber || lineNumber > rendEndLineNumber) {443continue;444}445446const startColumn = lineNumber === range.startLineNumber ? range.startColumn : 1;447const continuesInNextLine = lineNumber !== originalEndLineNumber;448const endColumn = continuesInNextLine ? this._context.viewModel.getLineMaxColumn(lineNumber) : range.endColumn;449const visibleLine = this._visibleLines.getVisibleLine(lineNumber);450const visibleRangesForLine = visibleLine.getVisibleRangesForRange(lineNumber, startColumn, endColumn, domReadingContext);451452if (!visibleRangesForLine) {453continue;454}455456if (includeNewLines && lineNumber < originalEndLineNumber) {457const currentLineModelLineNumber = nextLineModelLineNumber;458nextLineModelLineNumber = this._context.viewModel.coordinatesConverter.convertViewPositionToModelPosition(new Position(lineNumber + 1, 1)).lineNumber;459460if (currentLineModelLineNumber !== nextLineModelLineNumber) {461const floatHorizontalRange = visibleRangesForLine.ranges[visibleRangesForLine.ranges.length - 1];462floatHorizontalRange.width += this._typicalHalfwidthCharacterWidth;463if (this._context.viewModel.getTextDirection(currentLineModelLineNumber) === TextDirection.RTL) {464floatHorizontalRange.left -= this._typicalHalfwidthCharacterWidth;465}466}467}468469visibleRanges[visibleRangesLen++] = new LineVisibleRanges(visibleRangesForLine.outsideRenderedLine, lineNumber, HorizontalRange.from(visibleRangesForLine.ranges), continuesInNextLine);470}471472this._updateLineWidthsSlowIfDomDidLayout(domReadingContext);473474if (visibleRangesLen === 0) {475return null;476}477478return visibleRanges;479}480481private _visibleRangesForLineRange(lineNumber: number, startColumn: number, endColumn: number): VisibleRanges | null {482if (this.shouldRender()) {483// Cannot read from the DOM because it is dirty484// i.e. the model & the dom are out of sync, so I'd be reading something stale485return null;486}487488if (lineNumber < this._visibleLines.getStartLineNumber() || lineNumber > this._visibleLines.getEndLineNumber()) {489return null;490}491492const domReadingContext = new DomReadingContext(this.domNode.domNode, this._textRangeRestingSpot);493const result = this._visibleLines.getVisibleLine(lineNumber).getVisibleRangesForRange(lineNumber, startColumn, endColumn, domReadingContext);494this._updateLineWidthsSlowIfDomDidLayout(domReadingContext);495496return result;497}498499private _lineIsRenderedRTL(lineNumber: number): boolean {500if (lineNumber < this._visibleLines.getStartLineNumber() || lineNumber > this._visibleLines.getEndLineNumber()) {501return false;502}503const visibleLine = this._visibleLines.getVisibleLine(lineNumber);504return visibleLine.isRenderedRTL();505}506507public visibleRangeForPosition(position: Position): HorizontalPosition | null {508const visibleRanges = this._visibleRangesForLineRange(position.lineNumber, position.column, position.column);509if (!visibleRanges) {510return null;511}512return new HorizontalPosition(visibleRanges.outsideRenderedLine, visibleRanges.ranges[0].left);513}514515// --- implementation516517public updateLineWidths(): void {518this._updateLineWidths(false);519}520521/**522* Updates the max line width if it is fast to compute.523* Returns true if all lines were taken into account.524* Returns false if some lines need to be reevaluated (in a slow fashion).525*/526private _updateLineWidthsFast(): boolean {527return this._updateLineWidths(true);528}529530private _updateLineWidthsSlow(): void {531this._updateLineWidths(false);532}533534/**535* Update the line widths using DOM layout information after someone else536* has caused a synchronous layout.537*/538private _updateLineWidthsSlowIfDomDidLayout(domReadingContext: DomReadingContext): void {539if (!domReadingContext.didDomLayout) {540// only proceed if we just did a layout541return;542}543if (this._asyncUpdateLineWidths.isScheduled()) {544// reading widths is not scheduled => widths are up-to-date545return;546}547this._asyncUpdateLineWidths.cancel();548this._updateLineWidthsSlow();549}550551private _updateLineWidths(fast: boolean): boolean {552const rendStartLineNumber = this._visibleLines.getStartLineNumber();553const rendEndLineNumber = this._visibleLines.getEndLineNumber();554555let localMaxLineWidth = 1;556let allWidthsComputed = true;557for (let lineNumber = rendStartLineNumber; lineNumber <= rendEndLineNumber; lineNumber++) {558const visibleLine = this._visibleLines.getVisibleLine(lineNumber);559560if (fast && !visibleLine.getWidthIsFast()) {561// Cannot compute width in a fast way for this line562allWidthsComputed = false;563continue;564}565566localMaxLineWidth = Math.max(localMaxLineWidth, visibleLine.getWidth(null));567}568569if (allWidthsComputed && rendStartLineNumber === 1 && rendEndLineNumber === this._context.viewModel.getLineCount()) {570// we know the max line width for all the lines571this._maxLineWidth = 0;572}573574this._ensureMaxLineWidth(localMaxLineWidth);575576return allWidthsComputed;577}578579private _checkMonospaceFontAssumptions(): void {580// Problems with monospace assumptions are more apparent for longer lines,581// as small rounding errors start to sum up, so we will select the longest582// line for a closer inspection583let longestLineNumber = -1;584let longestWidth = -1;585const rendStartLineNumber = this._visibleLines.getStartLineNumber();586const rendEndLineNumber = this._visibleLines.getEndLineNumber();587for (let lineNumber = rendStartLineNumber; lineNumber <= rendEndLineNumber; lineNumber++) {588const visibleLine = this._visibleLines.getVisibleLine(lineNumber);589if (visibleLine.needsMonospaceFontCheck()) {590const lineWidth = visibleLine.getWidth(null);591if (lineWidth > longestWidth) {592longestWidth = lineWidth;593longestLineNumber = lineNumber;594}595}596}597598if (longestLineNumber === -1) {599return;600}601602if (!this._visibleLines.getVisibleLine(longestLineNumber).monospaceAssumptionsAreValid()) {603for (let lineNumber = rendStartLineNumber; lineNumber <= rendEndLineNumber; lineNumber++) {604const visibleLine = this._visibleLines.getVisibleLine(lineNumber);605visibleLine.onMonospaceAssumptionsInvalidated();606}607}608}609610public prepareRender(): void {611throw new Error('Not supported');612}613614public render(): void {615throw new Error('Not supported');616}617618public renderText(viewportData: ViewportData): void {619// (1) render lines - ensures lines are in the DOM620this._visibleLines.renderLines(viewportData);621this._lastRenderedData.setCurrentVisibleRange(viewportData.visibleRange);622this.domNode.setWidth(this._context.viewLayout.getScrollWidth());623this.domNode.setHeight(Math.min(this._context.viewLayout.getScrollHeight(), 1000000));624625// (2) compute horizontal scroll position:626// - this must happen after the lines are in the DOM since it might need a line that rendered just now627// - it might change `scrollWidth` and `scrollLeft`628if (this._horizontalRevealRequest) {629630const horizontalRevealRequest = this._horizontalRevealRequest;631632// Check that we have the line that contains the horizontal range in the viewport633if (viewportData.startLineNumber <= horizontalRevealRequest.minLineNumber && horizontalRevealRequest.maxLineNumber <= viewportData.endLineNumber) {634635this._horizontalRevealRequest = null;636637// allow `visibleRangesForRange2` to work638this.onDidRender();639640// compute new scroll position641const newScrollLeft = this._computeScrollLeftToReveal(horizontalRevealRequest);642643if (newScrollLeft) {644if (!this._isViewportWrapping && !newScrollLeft.hasRTL) {645// ensure `scrollWidth` is large enough646this._ensureMaxLineWidth(newScrollLeft.maxHorizontalOffset);647}648// set `scrollLeft`649this._context.viewModel.viewLayout.setScrollPosition({650scrollLeft: newScrollLeft.scrollLeft651}, horizontalRevealRequest.scrollType);652}653}654}655656// Update max line width (not so important, it is just so the horizontal scrollbar doesn't get too small)657if (!this._updateLineWidthsFast()) {658// Computing the width of some lines would be slow => delay it659this._asyncUpdateLineWidths.schedule();660} else {661this._asyncUpdateLineWidths.cancel();662}663664if (platform.isLinux && !this._asyncCheckMonospaceFontAssumptions.isScheduled()) {665const rendStartLineNumber = this._visibleLines.getStartLineNumber();666const rendEndLineNumber = this._visibleLines.getEndLineNumber();667for (let lineNumber = rendStartLineNumber; lineNumber <= rendEndLineNumber; lineNumber++) {668const visibleLine = this._visibleLines.getVisibleLine(lineNumber);669if (visibleLine.needsMonospaceFontCheck()) {670this._asyncCheckMonospaceFontAssumptions.schedule();671break;672}673}674}675676// (3) handle scrolling677this._linesContent.setLayerHinting(this._canUseLayerHinting);678this._linesContent.setContain('strict');679const adjustedScrollTop = this._context.viewLayout.getCurrentScrollTop() - viewportData.bigNumbersDelta;680this._linesContent.setTop(-adjustedScrollTop);681this._linesContent.setLeft(-this._context.viewLayout.getCurrentScrollLeft());682}683684// --- width685686private _ensureMaxLineWidth(lineWidth: number): void {687const iLineWidth = Math.ceil(lineWidth);688if (this._maxLineWidth < iLineWidth) {689this._maxLineWidth = iLineWidth;690this._context.viewModel.viewLayout.setMaxLineWidth(this._maxLineWidth);691}692}693694private _computeScrollTopToRevealRange(viewport: Viewport, source: string | null | undefined, minimalReveal: boolean, range: Range | null, selections: Selection[] | null, verticalType: viewEvents.VerticalRevealType): number {695const viewportStartY = viewport.top;696const viewportHeight = viewport.height;697const viewportEndY = viewportStartY + viewportHeight;698let boxIsSingleRange: boolean;699let boxStartY: number;700let boxEndY: number;701702if (selections && selections.length > 0) {703let minLineNumber = selections[0].startLineNumber;704let maxLineNumber = selections[0].endLineNumber;705for (let i = 1, len = selections.length; i < len; i++) {706const selection = selections[i];707minLineNumber = Math.min(minLineNumber, selection.startLineNumber);708maxLineNumber = Math.max(maxLineNumber, selection.endLineNumber);709}710boxIsSingleRange = false;711boxStartY = this._context.viewLayout.getVerticalOffsetForLineNumber(minLineNumber);712boxEndY = this._context.viewLayout.getVerticalOffsetForLineNumber(maxLineNumber) + this._lineHeight;713} else if (range) {714boxIsSingleRange = true;715boxStartY = this._context.viewLayout.getVerticalOffsetForLineNumber(range.startLineNumber);716boxEndY = this._context.viewLayout.getVerticalOffsetForLineNumber(range.endLineNumber) + this._lineHeight;717} else {718return -1;719}720721const shouldIgnoreScrollOff = (source === 'mouse' || minimalReveal) && this._cursorSurroundingLinesStyle === 'default';722723let paddingTop: number = 0;724let paddingBottom: number = 0;725726if (!shouldIgnoreScrollOff) {727const maxLinesInViewport = (viewportHeight / this._lineHeight);728const surroundingLines = Math.max(this._cursorSurroundingLines, this._stickyScrollEnabled ? this._maxNumberStickyLines : 0);729const context = Math.min(maxLinesInViewport / 2, surroundingLines);730paddingTop = context * this._lineHeight;731paddingBottom = Math.max(0, (context - 1)) * this._lineHeight;732} else {733if (!minimalReveal) {734// Reveal one more line above (this case is hit when dragging)735paddingTop = this._lineHeight;736}737}738if (!minimalReveal) {739if (verticalType === viewEvents.VerticalRevealType.Simple || verticalType === viewEvents.VerticalRevealType.Bottom) {740// Reveal one line more when the last line would be covered by the scrollbar - arrow down case or revealing a line explicitly at bottom741paddingBottom += this._lineHeight;742}743}744745boxStartY -= paddingTop;746boxEndY += paddingBottom;747let newScrollTop: number;748749if (boxEndY - boxStartY > viewportHeight) {750// the box is larger than the viewport ... scroll to its top751if (!boxIsSingleRange) {752// do not reveal multiple cursors if there are more than fit the viewport753return -1;754}755newScrollTop = boxStartY;756} else if (verticalType === viewEvents.VerticalRevealType.NearTop || verticalType === viewEvents.VerticalRevealType.NearTopIfOutsideViewport) {757if (verticalType === viewEvents.VerticalRevealType.NearTopIfOutsideViewport && viewportStartY <= boxStartY && boxEndY <= viewportEndY) {758// Box is already in the viewport... do nothing759newScrollTop = viewportStartY;760} else {761// We want a gap that is 20% of the viewport, but with a minimum of 5 lines762const desiredGapAbove = Math.max(5 * this._lineHeight, viewportHeight * 0.2);763// Try to scroll just above the box with the desired gap764const desiredScrollTop = boxStartY - desiredGapAbove;765// But ensure that the box is not pushed out of viewport766const minScrollTop = boxEndY - viewportHeight;767newScrollTop = Math.max(minScrollTop, desiredScrollTop);768}769} else if (verticalType === viewEvents.VerticalRevealType.Center || verticalType === viewEvents.VerticalRevealType.CenterIfOutsideViewport) {770if (verticalType === viewEvents.VerticalRevealType.CenterIfOutsideViewport && viewportStartY <= boxStartY && boxEndY <= viewportEndY) {771// Box is already in the viewport... do nothing772newScrollTop = viewportStartY;773} else {774// Box is outside the viewport... center it775const boxMiddleY = (boxStartY + boxEndY) / 2;776newScrollTop = Math.max(0, boxMiddleY - viewportHeight / 2);777}778} else {779newScrollTop = this._computeMinimumScrolling(viewportStartY, viewportEndY, boxStartY, boxEndY, verticalType === viewEvents.VerticalRevealType.Top, verticalType === viewEvents.VerticalRevealType.Bottom);780}781782return newScrollTop;783}784785private _computeScrollLeftToReveal(horizontalRevealRequest: HorizontalRevealRequest): { scrollLeft: number; maxHorizontalOffset: number; hasRTL: boolean } | null {786787const viewport = this._context.viewLayout.getCurrentViewport();788const layoutInfo = this._context.configuration.options.get(EditorOption.layoutInfo);789const viewportStartX = viewport.left;790const viewportEndX = viewportStartX + viewport.width - layoutInfo.verticalScrollbarWidth;791792let boxStartX = Constants.MAX_SAFE_SMALL_INTEGER;793let boxEndX = 0;794let hasRTL = false;795if (horizontalRevealRequest.type === 'range') {796hasRTL = this._lineIsRenderedRTL(horizontalRevealRequest.lineNumber);797const visibleRanges = this._visibleRangesForLineRange(horizontalRevealRequest.lineNumber, horizontalRevealRequest.startColumn, horizontalRevealRequest.endColumn);798if (!visibleRanges) {799return null;800}801for (const visibleRange of visibleRanges.ranges) {802boxStartX = Math.min(boxStartX, Math.round(visibleRange.left));803boxEndX = Math.max(boxEndX, Math.round(visibleRange.left + visibleRange.width));804}805} else {806for (const selection of horizontalRevealRequest.selections) {807if (selection.startLineNumber !== selection.endLineNumber) {808return null;809}810const visibleRanges = this._visibleRangesForLineRange(selection.startLineNumber, selection.startColumn, selection.endColumn);811hasRTL ||= this._lineIsRenderedRTL(selection.startLineNumber);812if (!visibleRanges) {813return null;814}815for (const visibleRange of visibleRanges.ranges) {816boxStartX = Math.min(boxStartX, Math.round(visibleRange.left));817boxEndX = Math.max(boxEndX, Math.round(visibleRange.left + visibleRange.width));818}819}820}821822if (!horizontalRevealRequest.minimalReveal) {823boxStartX = Math.max(0, boxStartX - ViewLines.HORIZONTAL_EXTRA_PX);824boxEndX += this._revealHorizontalRightPadding;825}826827if (horizontalRevealRequest.type === 'selections' && boxEndX - boxStartX > viewport.width) {828return null;829}830831const newScrollLeft = this._computeMinimumScrolling(viewportStartX, viewportEndX, boxStartX, boxEndX);832return {833scrollLeft: newScrollLeft,834maxHorizontalOffset: boxEndX,835hasRTL836};837}838839private _computeMinimumScrolling(viewportStart: number, viewportEnd: number, boxStart: number, boxEnd: number, revealAtStart?: boolean, revealAtEnd?: boolean): number {840viewportStart = viewportStart | 0;841viewportEnd = viewportEnd | 0;842boxStart = boxStart | 0;843boxEnd = boxEnd | 0;844revealAtStart = !!revealAtStart;845revealAtEnd = !!revealAtEnd;846847const viewportLength = viewportEnd - viewportStart;848const boxLength = boxEnd - boxStart;849850if (boxLength < viewportLength) {851// The box would fit in the viewport852853if (revealAtStart) {854return boxStart;855}856857if (revealAtEnd) {858return Math.max(0, boxEnd - viewportLength);859}860861if (boxStart < viewportStart) {862// The box is above the viewport863return boxStart;864} else if (boxEnd > viewportEnd) {865// The box is below the viewport866return Math.max(0, boxEnd - viewportLength);867}868} else {869// The box would not fit in the viewport870// Reveal the beginning of the box871return boxStart;872}873874return viewportStart;875}876}877878879