Path: blob/main/src/vs/editor/browser/viewParts/viewLines/viewLines.ts
5292 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 {247const rendStartLineNumber = this._visibleLines.getStartLineNumber();248const rendEndLineNumber = this._visibleLines.getEndLineNumber();249for (let lineNumber = rendStartLineNumber; lineNumber <= rendEndLineNumber; lineNumber++) {250this._visibleLines.getVisibleLine(lineNumber).onDecorationsChanged();251}252return true;253}254public override onFlushed(e: viewEvents.ViewFlushedEvent): boolean {255const shouldRender = this._visibleLines.onFlushed(e, this._viewLineOptions.useGpu);256this._maxLineWidth = 0;257return shouldRender;258}259public override onLinesChanged(e: viewEvents.ViewLinesChangedEvent): boolean {260return this._visibleLines.onLinesChanged(e);261}262public override onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): boolean {263return this._visibleLines.onLinesDeleted(e);264}265public override onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): boolean {266return this._visibleLines.onLinesInserted(e);267}268public override onRevealRangeRequest(e: viewEvents.ViewRevealRangeRequestEvent): boolean {269// Using the future viewport here in order to handle multiple270// incoming reveal range requests that might all desire to be animated271const desiredScrollTop = this._computeScrollTopToRevealRange(this._context.viewLayout.getFutureViewport(), e.source, e.minimalReveal, e.range, e.selections, e.verticalType);272273if (desiredScrollTop === -1) {274// marker to abort the reveal range request275return false;276}277278// validate the new desired scroll top279let newScrollPosition = this._context.viewLayout.validateScrollPosition({ scrollTop: desiredScrollTop });280281if (e.revealHorizontal) {282if (e.range && e.range.startLineNumber !== e.range.endLineNumber) {283// Two or more lines? => scroll to base (That's how you see most of the two lines)284newScrollPosition = {285scrollTop: newScrollPosition.scrollTop,286scrollLeft: 0287};288} else if (e.range) {289// We don't necessarily know the horizontal offset of this range since the line might not be in the view...290this._horizontalRevealRequest = new HorizontalRevealRangeRequest(e.minimalReveal, e.range.startLineNumber, e.range.startColumn, e.range.endColumn, this._context.viewLayout.getCurrentScrollTop(), newScrollPosition.scrollTop, e.scrollType);291} else if (e.selections && e.selections.length > 0) {292this._horizontalRevealRequest = new HorizontalRevealSelectionsRequest(e.minimalReveal, e.selections, this._context.viewLayout.getCurrentScrollTop(), newScrollPosition.scrollTop, e.scrollType);293}294} else {295this._horizontalRevealRequest = null;296}297298const scrollTopDelta = Math.abs(this._context.viewLayout.getCurrentScrollTop() - newScrollPosition.scrollTop);299const scrollType = (scrollTopDelta <= this._lineHeight ? ScrollType.Immediate : e.scrollType);300this._context.viewModel.viewLayout.setScrollPosition(newScrollPosition, scrollType);301302return true;303}304public override onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean {305if (this._horizontalRevealRequest && e.scrollLeftChanged) {306// cancel any outstanding horizontal reveal request if someone else scrolls horizontally.307this._horizontalRevealRequest = null;308}309if (this._horizontalRevealRequest && e.scrollTopChanged) {310const min = Math.min(this._horizontalRevealRequest.startScrollTop, this._horizontalRevealRequest.stopScrollTop);311const max = Math.max(this._horizontalRevealRequest.startScrollTop, this._horizontalRevealRequest.stopScrollTop);312if (e.scrollTop < min || e.scrollTop > max) {313// cancel any outstanding horizontal reveal request if someone else scrolls vertically.314this._horizontalRevealRequest = null;315}316}317this.domNode.setWidth(e.scrollWidth);318return this._visibleLines.onScrollChanged(e) || e.scrollTopChanged || e.scrollLeftChanged;319}320321public override onTokensChanged(e: viewEvents.ViewTokensChangedEvent): boolean {322return this._visibleLines.onTokensChanged(e);323}324public override onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean {325this._context.viewModel.viewLayout.setMaxLineWidth(this._maxLineWidth);326return this._visibleLines.onZonesChanged(e);327}328public override onThemeChanged(e: viewEvents.ViewThemeChangedEvent): boolean {329return this._onOptionsMaybeChanged();330}331332// ---- end view event handlers333334// ----------- HELPERS FOR OTHERS335336public getPositionFromDOMInfo(spanNode: HTMLElement, offset: number): Position | null {337const viewLineDomNode = this._getViewLineDomNode(spanNode);338if (viewLineDomNode === null) {339// Couldn't find view line node340return null;341}342const lineNumber = this._getLineNumberFor(viewLineDomNode);343344if (lineNumber === -1) {345// Couldn't find view line node346return null;347}348349if (lineNumber < 1 || lineNumber > this._context.viewModel.getLineCount()) {350// lineNumber is outside range351return null;352}353354if (this._context.viewModel.getLineMaxColumn(lineNumber) === 1) {355// Line is empty356return new Position(lineNumber, 1);357}358359const rendStartLineNumber = this._visibleLines.getStartLineNumber();360const rendEndLineNumber = this._visibleLines.getEndLineNumber();361if (lineNumber < rendStartLineNumber || lineNumber > rendEndLineNumber) {362// Couldn't find line363return null;364}365366let column = this._visibleLines.getVisibleLine(lineNumber).getColumnOfNodeOffset(spanNode, offset);367const minColumn = this._context.viewModel.getLineMinColumn(lineNumber);368if (column < minColumn) {369column = minColumn;370}371return new Position(lineNumber, column);372}373374private _getViewLineDomNode(node: HTMLElement | null): HTMLElement | null {375while (node && node.nodeType === 1) {376if (node.className === ViewLine.CLASS_NAME) {377return node;378}379node = node.parentElement;380}381return null;382}383384/**385* @returns the line number of this view line dom node.386*/387private _getLineNumberFor(domNode: HTMLElement): number {388const startLineNumber = this._visibleLines.getStartLineNumber();389const endLineNumber = this._visibleLines.getEndLineNumber();390for (let lineNumber = startLineNumber; lineNumber <= endLineNumber; lineNumber++) {391const line = this._visibleLines.getVisibleLine(lineNumber);392if (domNode === line.getDomNode()) {393return lineNumber;394}395}396return -1;397}398399public getLineWidth(lineNumber: number): number {400const rendStartLineNumber = this._visibleLines.getStartLineNumber();401const rendEndLineNumber = this._visibleLines.getEndLineNumber();402if (lineNumber < rendStartLineNumber || lineNumber > rendEndLineNumber) {403// Couldn't find line404return -1;405}406407const context = new DomReadingContext(this.domNode.domNode, this._textRangeRestingSpot);408const result = this._visibleLines.getVisibleLine(lineNumber).getWidth(context);409this._updateLineWidthsSlowIfDomDidLayout(context);410411return result;412}413414public resetLineWidthCaches(): void {415const rendStartLineNumber = this._visibleLines.getStartLineNumber();416const rendEndLineNumber = this._visibleLines.getEndLineNumber();417for (let lineNumber = rendStartLineNumber; lineNumber <= rendEndLineNumber; lineNumber++) {418this._visibleLines.getVisibleLine(lineNumber).resetCachedWidth();419}420}421422public linesVisibleRangesForRange(_range: Range, includeNewLines: boolean): LineVisibleRanges[] | null {423const 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 (lineNumber < this._visibleLines.getStartLineNumber() || lineNumber > this._visibleLines.getEndLineNumber()) {483return null;484}485486const domReadingContext = new DomReadingContext(this.domNode.domNode, this._textRangeRestingSpot);487const result = this._visibleLines.getVisibleLine(lineNumber).getVisibleRangesForRange(lineNumber, startColumn, endColumn, domReadingContext);488this._updateLineWidthsSlowIfDomDidLayout(domReadingContext);489490return result;491}492493private _lineIsRenderedRTL(lineNumber: number): boolean {494if (lineNumber < this._visibleLines.getStartLineNumber() || lineNumber > this._visibleLines.getEndLineNumber()) {495return false;496}497const visibleLine = this._visibleLines.getVisibleLine(lineNumber);498return visibleLine.isRenderedRTL();499}500501public visibleRangeForPosition(position: Position): HorizontalPosition | null {502const visibleRanges = this._visibleRangesForLineRange(position.lineNumber, position.column, position.column);503if (!visibleRanges) {504return null;505}506return new HorizontalPosition(visibleRanges.outsideRenderedLine, visibleRanges.ranges[0].left);507}508509// --- implementation510511public updateLineWidths(): void {512this._updateLineWidths(false);513}514515/**516* Updates the max line width if it is fast to compute.517* Returns true if all lines were taken into account.518* Returns false if some lines need to be reevaluated (in a slow fashion).519*/520private _updateLineWidthsFast(): boolean {521return this._updateLineWidths(true);522}523524private _updateLineWidthsSlow(): void {525this._updateLineWidths(false);526}527528/**529* Update the line widths using DOM layout information after someone else530* has caused a synchronous layout.531*/532private _updateLineWidthsSlowIfDomDidLayout(domReadingContext: DomReadingContext): void {533if (!domReadingContext.didDomLayout) {534// only proceed if we just did a layout535return;536}537if (!this._asyncUpdateLineWidths.isScheduled()) {538// reading widths is not scheduled => widths are up-to-date539return;540}541this._asyncUpdateLineWidths.cancel();542this._updateLineWidthsSlow();543}544545private _updateLineWidths(fast: boolean): boolean {546const rendStartLineNumber = this._visibleLines.getStartLineNumber();547const rendEndLineNumber = this._visibleLines.getEndLineNumber();548549let localMaxLineWidth = 1;550let allWidthsComputed = true;551for (let lineNumber = rendStartLineNumber; lineNumber <= rendEndLineNumber; lineNumber++) {552const visibleLine = this._visibleLines.getVisibleLine(lineNumber);553554if (fast && !visibleLine.getWidthIsFast()) {555// Cannot compute width in a fast way for this line556allWidthsComputed = false;557continue;558}559560localMaxLineWidth = Math.max(localMaxLineWidth, visibleLine.getWidth(null));561}562563if (allWidthsComputed && rendStartLineNumber === 1 && rendEndLineNumber === this._context.viewModel.getLineCount()) {564// we know the max line width for all the lines565this._maxLineWidth = 0;566}567568this._ensureMaxLineWidth(localMaxLineWidth);569570return allWidthsComputed;571}572573private _checkMonospaceFontAssumptions(): void {574// Problems with monospace assumptions are more apparent for longer lines,575// as small rounding errors start to sum up, so we will select the longest576// line for a closer inspection577let longestLineNumber = -1;578let longestWidth = -1;579const rendStartLineNumber = this._visibleLines.getStartLineNumber();580const rendEndLineNumber = this._visibleLines.getEndLineNumber();581for (let lineNumber = rendStartLineNumber; lineNumber <= rendEndLineNumber; lineNumber++) {582const visibleLine = this._visibleLines.getVisibleLine(lineNumber);583if (visibleLine.needsMonospaceFontCheck()) {584const lineWidth = visibleLine.getWidth(null);585if (lineWidth > longestWidth) {586longestWidth = lineWidth;587longestLineNumber = lineNumber;588}589}590}591592if (longestLineNumber === -1) {593return;594}595596if (!this._visibleLines.getVisibleLine(longestLineNumber).monospaceAssumptionsAreValid()) {597for (let lineNumber = rendStartLineNumber; lineNumber <= rendEndLineNumber; lineNumber++) {598const visibleLine = this._visibleLines.getVisibleLine(lineNumber);599visibleLine.onMonospaceAssumptionsInvalidated();600}601}602}603604public prepareRender(): void {605throw new Error('Not supported');606}607608public render(): void {609throw new Error('Not supported');610}611612public renderText(viewportData: ViewportData): void {613// (1) render lines - ensures lines are in the DOM614this._visibleLines.renderLines(viewportData);615this._lastRenderedData.setCurrentVisibleRange(viewportData.visibleRange);616this.domNode.setWidth(this._context.viewLayout.getScrollWidth());617this.domNode.setHeight(Math.min(this._context.viewLayout.getScrollHeight(), 1000000));618619// (2) compute horizontal scroll position:620// - this must happen after the lines are in the DOM since it might need a line that rendered just now621// - it might change `scrollWidth` and `scrollLeft`622if (this._horizontalRevealRequest) {623624const horizontalRevealRequest = this._horizontalRevealRequest;625626// Check that we have the line that contains the horizontal range in the viewport627if (viewportData.startLineNumber <= horizontalRevealRequest.minLineNumber && horizontalRevealRequest.maxLineNumber <= viewportData.endLineNumber) {628629this._horizontalRevealRequest = null;630631// allow `visibleRangesForRange2` to work632this.onDidRender();633634// compute new scroll position635const newScrollLeft = this._computeScrollLeftToReveal(horizontalRevealRequest);636637if (newScrollLeft) {638if (!this._isViewportWrapping && !newScrollLeft.hasRTL) {639// ensure `scrollWidth` is large enough640this._ensureMaxLineWidth(newScrollLeft.maxHorizontalOffset);641}642// set `scrollLeft`643this._context.viewModel.viewLayout.setScrollPosition({644scrollLeft: newScrollLeft.scrollLeft645}, horizontalRevealRequest.scrollType);646}647}648}649650// Update max line width (not so important, it is just so the horizontal scrollbar doesn't get too small)651if (!this._updateLineWidthsFast()) {652// Computing the width of some lines would be slow => delay it653this._asyncUpdateLineWidths.schedule();654} else {655this._asyncUpdateLineWidths.cancel();656}657658if (platform.isLinux && !this._asyncCheckMonospaceFontAssumptions.isScheduled()) {659const rendStartLineNumber = this._visibleLines.getStartLineNumber();660const rendEndLineNumber = this._visibleLines.getEndLineNumber();661for (let lineNumber = rendStartLineNumber; lineNumber <= rendEndLineNumber; lineNumber++) {662const visibleLine = this._visibleLines.getVisibleLine(lineNumber);663if (visibleLine.needsMonospaceFontCheck()) {664this._asyncCheckMonospaceFontAssumptions.schedule();665break;666}667}668}669670// (3) handle scrolling671this._linesContent.setLayerHinting(this._canUseLayerHinting);672this._linesContent.setContain('strict');673const adjustedScrollTop = this._context.viewLayout.getCurrentScrollTop() - viewportData.bigNumbersDelta;674this._linesContent.setTop(-adjustedScrollTop);675this._linesContent.setLeft(-this._context.viewLayout.getCurrentScrollLeft());676}677678// --- width679680private _ensureMaxLineWidth(lineWidth: number): void {681// When GPU rendering is enabled, ViewLinesGpu handles max line width tracking682if (this._viewLineOptions.useGpu) {683return;684}685const iLineWidth = Math.ceil(lineWidth);686if (this._maxLineWidth < iLineWidth) {687this._maxLineWidth = iLineWidth;688this._context.viewModel.viewLayout.setMaxLineWidth(this._maxLineWidth);689}690}691692private _computeScrollTopToRevealRange(viewport: Viewport, source: string | null | undefined, minimalReveal: boolean, range: Range | null, selections: Selection[] | null, verticalType: viewEvents.VerticalRevealType): number {693const viewportStartY = viewport.top;694const viewportHeight = viewport.height;695const viewportEndY = viewportStartY + viewportHeight;696let boxIsSingleRange: boolean;697let boxStartY: number;698let boxEndY: number;699700if (selections && selections.length > 0) {701let minLineNumber = selections[0].startLineNumber;702let maxLineNumber = selections[0].endLineNumber;703for (let i = 1, len = selections.length; i < len; i++) {704const selection = selections[i];705minLineNumber = Math.min(minLineNumber, selection.startLineNumber);706maxLineNumber = Math.max(maxLineNumber, selection.endLineNumber);707}708boxIsSingleRange = false;709boxStartY = this._context.viewLayout.getVerticalOffsetForLineNumber(minLineNumber);710boxEndY = this._context.viewLayout.getVerticalOffsetForLineNumber(maxLineNumber) + this._lineHeight;711} else if (range) {712boxIsSingleRange = true;713boxStartY = this._context.viewLayout.getVerticalOffsetForLineNumber(range.startLineNumber);714boxEndY = this._context.viewLayout.getVerticalOffsetForLineNumber(range.endLineNumber) + this._lineHeight;715} else {716return -1;717}718719const shouldIgnoreScrollOff = (source === 'mouse' || minimalReveal) && this._cursorSurroundingLinesStyle === 'default';720721let paddingTop: number = 0;722let paddingBottom: number = 0;723724if (!shouldIgnoreScrollOff) {725const maxLinesInViewport = (viewportHeight / this._lineHeight);726const surroundingLines = Math.max(this._cursorSurroundingLines, this._stickyScrollEnabled ? this._maxNumberStickyLines : 0);727const context = Math.min(maxLinesInViewport / 2, surroundingLines);728paddingTop = context * this._lineHeight;729paddingBottom = Math.max(0, (context - 1)) * this._lineHeight;730} else {731if (!minimalReveal) {732// Reveal one more line above (this case is hit when dragging)733paddingTop = this._lineHeight;734}735}736if (!minimalReveal) {737if (verticalType === viewEvents.VerticalRevealType.Simple || verticalType === viewEvents.VerticalRevealType.Bottom) {738// Reveal one line more when the last line would be covered by the scrollbar - arrow down case or revealing a line explicitly at bottom739paddingBottom += this._lineHeight;740}741}742743boxStartY -= paddingTop;744boxEndY += paddingBottom;745let newScrollTop: number;746747if (boxEndY - boxStartY > viewportHeight) {748// the box is larger than the viewport ... scroll to its top749if (!boxIsSingleRange) {750// do not reveal multiple cursors if there are more than fit the viewport751return -1;752}753newScrollTop = boxStartY;754} else if (verticalType === viewEvents.VerticalRevealType.NearTop || verticalType === viewEvents.VerticalRevealType.NearTopIfOutsideViewport) {755if (verticalType === viewEvents.VerticalRevealType.NearTopIfOutsideViewport && viewportStartY <= boxStartY && boxEndY <= viewportEndY) {756// Box is already in the viewport... do nothing757newScrollTop = viewportStartY;758} else {759// We want a gap that is 20% of the viewport, but with a minimum of 5 lines760const desiredGapAbove = Math.max(5 * this._lineHeight, viewportHeight * 0.2);761// Try to scroll just above the box with the desired gap762const desiredScrollTop = boxStartY - desiredGapAbove;763// But ensure that the box is not pushed out of viewport764const minScrollTop = boxEndY - viewportHeight;765newScrollTop = Math.max(minScrollTop, desiredScrollTop);766}767} else if (verticalType === viewEvents.VerticalRevealType.Center || verticalType === viewEvents.VerticalRevealType.CenterIfOutsideViewport) {768if (verticalType === viewEvents.VerticalRevealType.CenterIfOutsideViewport && viewportStartY <= boxStartY && boxEndY <= viewportEndY) {769// Box is already in the viewport... do nothing770newScrollTop = viewportStartY;771} else {772// Box is outside the viewport... center it773const boxMiddleY = (boxStartY + boxEndY) / 2;774newScrollTop = Math.max(0, boxMiddleY - viewportHeight / 2);775}776} else {777newScrollTop = this._computeMinimumScrolling(viewportStartY, viewportEndY, boxStartY, boxEndY, verticalType === viewEvents.VerticalRevealType.Top, verticalType === viewEvents.VerticalRevealType.Bottom);778}779780return newScrollTop;781}782783private _computeScrollLeftToReveal(horizontalRevealRequest: HorizontalRevealRequest): { scrollLeft: number; maxHorizontalOffset: number; hasRTL: boolean } | null {784785const viewport = this._context.viewLayout.getCurrentViewport();786const layoutInfo = this._context.configuration.options.get(EditorOption.layoutInfo);787const viewportStartX = viewport.left;788const viewportEndX = viewportStartX + viewport.width - layoutInfo.verticalScrollbarWidth;789790let boxStartX = Constants.MAX_SAFE_SMALL_INTEGER;791let boxEndX = 0;792let hasRTL = false;793if (horizontalRevealRequest.type === 'range') {794hasRTL = this._lineIsRenderedRTL(horizontalRevealRequest.lineNumber);795const visibleRanges = this._visibleRangesForLineRange(horizontalRevealRequest.lineNumber, horizontalRevealRequest.startColumn, horizontalRevealRequest.endColumn);796if (!visibleRanges) {797return null;798}799for (const visibleRange of visibleRanges.ranges) {800boxStartX = Math.min(boxStartX, Math.round(visibleRange.left));801boxEndX = Math.max(boxEndX, Math.round(visibleRange.left + visibleRange.width));802}803} else {804for (const selection of horizontalRevealRequest.selections) {805if (selection.startLineNumber !== selection.endLineNumber) {806return null;807}808const visibleRanges = this._visibleRangesForLineRange(selection.startLineNumber, selection.startColumn, selection.endColumn);809hasRTL ||= this._lineIsRenderedRTL(selection.startLineNumber);810if (!visibleRanges) {811return null;812}813for (const visibleRange of visibleRanges.ranges) {814boxStartX = Math.min(boxStartX, Math.round(visibleRange.left));815boxEndX = Math.max(boxEndX, Math.round(visibleRange.left + visibleRange.width));816}817}818}819820if (!horizontalRevealRequest.minimalReveal) {821boxStartX = Math.max(0, boxStartX - ViewLines.HORIZONTAL_EXTRA_PX);822boxEndX += this._revealHorizontalRightPadding;823}824825if (horizontalRevealRequest.type === 'selections' && boxEndX - boxStartX > viewport.width) {826return null;827}828829const newScrollLeft = this._computeMinimumScrolling(viewportStartX, viewportEndX, boxStartX, boxEndX);830return {831scrollLeft: newScrollLeft,832maxHorizontalOffset: boxEndX,833hasRTL834};835}836837private _computeMinimumScrolling(viewportStart: number, viewportEnd: number, boxStart: number, boxEnd: number, revealAtStart?: boolean, revealAtEnd?: boolean): number {838viewportStart = viewportStart | 0;839viewportEnd = viewportEnd | 0;840boxStart = boxStart | 0;841boxEnd = boxEnd | 0;842revealAtStart = !!revealAtStart;843revealAtEnd = !!revealAtEnd;844845const viewportLength = viewportEnd - viewportStart;846const boxLength = boxEnd - boxStart;847848if (boxLength < viewportLength) {849// The box would fit in the viewport850851if (revealAtStart) {852return boxStart;853}854855if (revealAtEnd) {856return Math.max(0, boxEnd - viewportLength);857}858859if (boxStart < viewportStart) {860// The box is above the viewport861return boxStart;862} else if (boxEnd > viewportEnd) {863// The box is below the viewport864return Math.max(0, boxEnd - viewportLength);865}866} else {867// The box would not fit in the viewport868// Reveal the beginning of the box869return boxStart;870}871872return viewportStart;873}874}875876877