monaco-editor-core
Version:
A browser based code editor
697 lines (696 loc) • 35.1 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { MOUSE_CURSOR_TEXT_CSS_CLASS_NAME } from '../../../../base/browser/ui/mouseCursor/mouseCursor.js';
import { RunOnceScheduler } from '../../../../base/common/async.js';
import * as platform from '../../../../base/common/platform.js';
import './viewLines.css';
import { applyFontInfo } from '../../config/domFontInfo.js';
import { HorizontalPosition, HorizontalRange, LineVisibleRanges } from '../../view/renderingContext.js';
import { VisibleLinesCollection } from '../../view/viewLayer.js';
import { PartFingerprints, ViewPart } from '../../view/viewPart.js';
import { DomReadingContext } from './domReadingContext.js';
import { ViewLine, ViewLineOptions } from './viewLine.js';
import { Position } from '../../../common/core/position.js';
import { Range } from '../../../common/core/range.js';
class LastRenderedData {
constructor() {
this._currentVisibleRange = new Range(1, 1, 1, 1);
}
getCurrentVisibleRange() {
return this._currentVisibleRange;
}
setCurrentVisibleRange(currentVisibleRange) {
this._currentVisibleRange = currentVisibleRange;
}
}
class HorizontalRevealRangeRequest {
constructor(minimalReveal, lineNumber, startColumn, endColumn, startScrollTop, stopScrollTop, scrollType) {
this.minimalReveal = minimalReveal;
this.lineNumber = lineNumber;
this.startColumn = startColumn;
this.endColumn = endColumn;
this.startScrollTop = startScrollTop;
this.stopScrollTop = stopScrollTop;
this.scrollType = scrollType;
this.type = 'range';
this.minLineNumber = lineNumber;
this.maxLineNumber = lineNumber;
}
}
class HorizontalRevealSelectionsRequest {
constructor(minimalReveal, selections, startScrollTop, stopScrollTop, scrollType) {
this.minimalReveal = minimalReveal;
this.selections = selections;
this.startScrollTop = startScrollTop;
this.stopScrollTop = stopScrollTop;
this.scrollType = scrollType;
this.type = 'selections';
let minLineNumber = selections[0].startLineNumber;
let maxLineNumber = selections[0].endLineNumber;
for (let i = 1, len = selections.length; i < len; i++) {
const selection = selections[i];
minLineNumber = Math.min(minLineNumber, selection.startLineNumber);
maxLineNumber = Math.max(maxLineNumber, selection.endLineNumber);
}
this.minLineNumber = minLineNumber;
this.maxLineNumber = maxLineNumber;
}
}
export class ViewLines extends ViewPart {
/**
* Adds this amount of pixels to the right of lines (no-one wants to type near the edge of the viewport)
*/
static { this.HORIZONTAL_EXTRA_PX = 30; }
constructor(context, linesContent) {
super(context);
const conf = this._context.configuration;
const options = this._context.configuration.options;
const fontInfo = options.get(50 /* EditorOption.fontInfo */);
const wrappingInfo = options.get(147 /* EditorOption.wrappingInfo */);
this._lineHeight = options.get(67 /* EditorOption.lineHeight */);
this._typicalHalfwidthCharacterWidth = fontInfo.typicalHalfwidthCharacterWidth;
this._isViewportWrapping = wrappingInfo.isViewportWrapping;
this._revealHorizontalRightPadding = options.get(101 /* EditorOption.revealHorizontalRightPadding */);
this._cursorSurroundingLines = options.get(29 /* EditorOption.cursorSurroundingLines */);
this._cursorSurroundingLinesStyle = options.get(30 /* EditorOption.cursorSurroundingLinesStyle */);
this._canUseLayerHinting = !options.get(32 /* EditorOption.disableLayerHinting */);
this._viewLineOptions = new ViewLineOptions(conf, this._context.theme.type);
this._linesContent = linesContent;
this._textRangeRestingSpot = document.createElement('div');
this._visibleLines = new VisibleLinesCollection({
createLine: () => new ViewLine(this._viewLineOptions),
});
this.domNode = this._visibleLines.domNode;
PartFingerprints.write(this.domNode, 8 /* PartFingerprint.ViewLines */);
this.domNode.setClassName(`view-lines ${MOUSE_CURSOR_TEXT_CSS_CLASS_NAME}`);
applyFontInfo(this.domNode, fontInfo);
// --- width & height
this._maxLineWidth = 0;
this._asyncUpdateLineWidths = new RunOnceScheduler(() => {
this._updateLineWidthsSlow();
}, 200);
this._asyncCheckMonospaceFontAssumptions = new RunOnceScheduler(() => {
this._checkMonospaceFontAssumptions();
}, 2000);
this._lastRenderedData = new LastRenderedData();
this._horizontalRevealRequest = null;
// sticky scroll widget
this._stickyScrollEnabled = options.get(116 /* EditorOption.stickyScroll */).enabled;
this._maxNumberStickyLines = options.get(116 /* EditorOption.stickyScroll */).maxLineCount;
}
dispose() {
this._asyncUpdateLineWidths.dispose();
this._asyncCheckMonospaceFontAssumptions.dispose();
super.dispose();
}
getDomNode() {
return this.domNode;
}
// ---- begin view event handlers
onConfigurationChanged(e) {
this._visibleLines.onConfigurationChanged(e);
if (e.hasChanged(147 /* EditorOption.wrappingInfo */)) {
this._maxLineWidth = 0;
}
const options = this._context.configuration.options;
const fontInfo = options.get(50 /* EditorOption.fontInfo */);
const wrappingInfo = options.get(147 /* EditorOption.wrappingInfo */);
this._lineHeight = options.get(67 /* EditorOption.lineHeight */);
this._typicalHalfwidthCharacterWidth = fontInfo.typicalHalfwidthCharacterWidth;
this._isViewportWrapping = wrappingInfo.isViewportWrapping;
this._revealHorizontalRightPadding = options.get(101 /* EditorOption.revealHorizontalRightPadding */);
this._cursorSurroundingLines = options.get(29 /* EditorOption.cursorSurroundingLines */);
this._cursorSurroundingLinesStyle = options.get(30 /* EditorOption.cursorSurroundingLinesStyle */);
this._canUseLayerHinting = !options.get(32 /* EditorOption.disableLayerHinting */);
// sticky scroll
this._stickyScrollEnabled = options.get(116 /* EditorOption.stickyScroll */).enabled;
this._maxNumberStickyLines = options.get(116 /* EditorOption.stickyScroll */).maxLineCount;
applyFontInfo(this.domNode, fontInfo);
this._onOptionsMaybeChanged();
if (e.hasChanged(146 /* EditorOption.layoutInfo */)) {
this._maxLineWidth = 0;
}
return true;
}
_onOptionsMaybeChanged() {
const conf = this._context.configuration;
const newViewLineOptions = new ViewLineOptions(conf, this._context.theme.type);
if (!this._viewLineOptions.equals(newViewLineOptions)) {
this._viewLineOptions = newViewLineOptions;
const startLineNumber = this._visibleLines.getStartLineNumber();
const endLineNumber = this._visibleLines.getEndLineNumber();
for (let lineNumber = startLineNumber; lineNumber <= endLineNumber; lineNumber++) {
const line = this._visibleLines.getVisibleLine(lineNumber);
line.onOptionsChanged(this._viewLineOptions);
}
return true;
}
return false;
}
onCursorStateChanged(e) {
const rendStartLineNumber = this._visibleLines.getStartLineNumber();
const rendEndLineNumber = this._visibleLines.getEndLineNumber();
let r = false;
for (let lineNumber = rendStartLineNumber; lineNumber <= rendEndLineNumber; lineNumber++) {
r = this._visibleLines.getVisibleLine(lineNumber).onSelectionChanged() || r;
}
return r;
}
onDecorationsChanged(e) {
if (true /*e.inlineDecorationsChanged*/) {
const rendStartLineNumber = this._visibleLines.getStartLineNumber();
const rendEndLineNumber = this._visibleLines.getEndLineNumber();
for (let lineNumber = rendStartLineNumber; lineNumber <= rendEndLineNumber; lineNumber++) {
this._visibleLines.getVisibleLine(lineNumber).onDecorationsChanged();
}
}
return true;
}
onFlushed(e) {
const shouldRender = this._visibleLines.onFlushed(e);
this._maxLineWidth = 0;
return shouldRender;
}
onLinesChanged(e) {
return this._visibleLines.onLinesChanged(e);
}
onLinesDeleted(e) {
return this._visibleLines.onLinesDeleted(e);
}
onLinesInserted(e) {
return this._visibleLines.onLinesInserted(e);
}
onRevealRangeRequest(e) {
// Using the future viewport here in order to handle multiple
// incoming reveal range requests that might all desire to be animated
const desiredScrollTop = this._computeScrollTopToRevealRange(this._context.viewLayout.getFutureViewport(), e.source, e.minimalReveal, e.range, e.selections, e.verticalType);
if (desiredScrollTop === -1) {
// marker to abort the reveal range request
return false;
}
// validate the new desired scroll top
let newScrollPosition = this._context.viewLayout.validateScrollPosition({ scrollTop: desiredScrollTop });
if (e.revealHorizontal) {
if (e.range && e.range.startLineNumber !== e.range.endLineNumber) {
// Two or more lines? => scroll to base (That's how you see most of the two lines)
newScrollPosition = {
scrollTop: newScrollPosition.scrollTop,
scrollLeft: 0
};
}
else if (e.range) {
// We don't necessarily know the horizontal offset of this range since the line might not be in the view...
this._horizontalRevealRequest = new HorizontalRevealRangeRequest(e.minimalReveal, e.range.startLineNumber, e.range.startColumn, e.range.endColumn, this._context.viewLayout.getCurrentScrollTop(), newScrollPosition.scrollTop, e.scrollType);
}
else if (e.selections && e.selections.length > 0) {
this._horizontalRevealRequest = new HorizontalRevealSelectionsRequest(e.minimalReveal, e.selections, this._context.viewLayout.getCurrentScrollTop(), newScrollPosition.scrollTop, e.scrollType);
}
}
else {
this._horizontalRevealRequest = null;
}
const scrollTopDelta = Math.abs(this._context.viewLayout.getCurrentScrollTop() - newScrollPosition.scrollTop);
const scrollType = (scrollTopDelta <= this._lineHeight ? 1 /* ScrollType.Immediate */ : e.scrollType);
this._context.viewModel.viewLayout.setScrollPosition(newScrollPosition, scrollType);
return true;
}
onScrollChanged(e) {
if (this._horizontalRevealRequest && e.scrollLeftChanged) {
// cancel any outstanding horizontal reveal request if someone else scrolls horizontally.
this._horizontalRevealRequest = null;
}
if (this._horizontalRevealRequest && e.scrollTopChanged) {
const min = Math.min(this._horizontalRevealRequest.startScrollTop, this._horizontalRevealRequest.stopScrollTop);
const max = Math.max(this._horizontalRevealRequest.startScrollTop, this._horizontalRevealRequest.stopScrollTop);
if (e.scrollTop < min || e.scrollTop > max) {
// cancel any outstanding horizontal reveal request if someone else scrolls vertically.
this._horizontalRevealRequest = null;
}
}
this.domNode.setWidth(e.scrollWidth);
return this._visibleLines.onScrollChanged(e) || true;
}
onTokensChanged(e) {
return this._visibleLines.onTokensChanged(e);
}
onZonesChanged(e) {
this._context.viewModel.viewLayout.setMaxLineWidth(this._maxLineWidth);
return this._visibleLines.onZonesChanged(e);
}
onThemeChanged(e) {
return this._onOptionsMaybeChanged();
}
// ---- end view event handlers
// ----------- HELPERS FOR OTHERS
getPositionFromDOMInfo(spanNode, offset) {
const viewLineDomNode = this._getViewLineDomNode(spanNode);
if (viewLineDomNode === null) {
// Couldn't find view line node
return null;
}
const lineNumber = this._getLineNumberFor(viewLineDomNode);
if (lineNumber === -1) {
// Couldn't find view line node
return null;
}
if (lineNumber < 1 || lineNumber > this._context.viewModel.getLineCount()) {
// lineNumber is outside range
return null;
}
if (this._context.viewModel.getLineMaxColumn(lineNumber) === 1) {
// Line is empty
return new Position(lineNumber, 1);
}
const rendStartLineNumber = this._visibleLines.getStartLineNumber();
const rendEndLineNumber = this._visibleLines.getEndLineNumber();
if (lineNumber < rendStartLineNumber || lineNumber > rendEndLineNumber) {
// Couldn't find line
return null;
}
let column = this._visibleLines.getVisibleLine(lineNumber).getColumnOfNodeOffset(spanNode, offset);
const minColumn = this._context.viewModel.getLineMinColumn(lineNumber);
if (column < minColumn) {
column = minColumn;
}
return new Position(lineNumber, column);
}
_getViewLineDomNode(node) {
while (node && node.nodeType === 1) {
if (node.className === ViewLine.CLASS_NAME) {
return node;
}
node = node.parentElement;
}
return null;
}
/**
* @returns the line number of this view line dom node.
*/
_getLineNumberFor(domNode) {
const startLineNumber = this._visibleLines.getStartLineNumber();
const endLineNumber = this._visibleLines.getEndLineNumber();
for (let lineNumber = startLineNumber; lineNumber <= endLineNumber; lineNumber++) {
const line = this._visibleLines.getVisibleLine(lineNumber);
if (domNode === line.getDomNode()) {
return lineNumber;
}
}
return -1;
}
getLineWidth(lineNumber) {
const rendStartLineNumber = this._visibleLines.getStartLineNumber();
const rendEndLineNumber = this._visibleLines.getEndLineNumber();
if (lineNumber < rendStartLineNumber || lineNumber > rendEndLineNumber) {
// Couldn't find line
return -1;
}
const context = new DomReadingContext(this.domNode.domNode, this._textRangeRestingSpot);
const result = this._visibleLines.getVisibleLine(lineNumber).getWidth(context);
this._updateLineWidthsSlowIfDomDidLayout(context);
return result;
}
linesVisibleRangesForRange(_range, includeNewLines) {
if (this.shouldRender()) {
// Cannot read from the DOM because it is dirty
// i.e. the model & the dom are out of sync, so I'd be reading something stale
return null;
}
const originalEndLineNumber = _range.endLineNumber;
const range = Range.intersectRanges(_range, this._lastRenderedData.getCurrentVisibleRange());
if (!range) {
return null;
}
const visibleRanges = [];
let visibleRangesLen = 0;
const domReadingContext = new DomReadingContext(this.domNode.domNode, this._textRangeRestingSpot);
let nextLineModelLineNumber = 0;
if (includeNewLines) {
nextLineModelLineNumber = this._context.viewModel.coordinatesConverter.convertViewPositionToModelPosition(new Position(range.startLineNumber, 1)).lineNumber;
}
const rendStartLineNumber = this._visibleLines.getStartLineNumber();
const rendEndLineNumber = this._visibleLines.getEndLineNumber();
for (let lineNumber = range.startLineNumber; lineNumber <= range.endLineNumber; lineNumber++) {
if (lineNumber < rendStartLineNumber || lineNumber > rendEndLineNumber) {
continue;
}
const startColumn = lineNumber === range.startLineNumber ? range.startColumn : 1;
const continuesInNextLine = lineNumber !== range.endLineNumber;
const endColumn = continuesInNextLine ? this._context.viewModel.getLineMaxColumn(lineNumber) : range.endColumn;
const visibleRangesForLine = this._visibleLines.getVisibleLine(lineNumber).getVisibleRangesForRange(lineNumber, startColumn, endColumn, domReadingContext);
if (!visibleRangesForLine) {
continue;
}
if (includeNewLines && lineNumber < originalEndLineNumber) {
const currentLineModelLineNumber = nextLineModelLineNumber;
nextLineModelLineNumber = this._context.viewModel.coordinatesConverter.convertViewPositionToModelPosition(new Position(lineNumber + 1, 1)).lineNumber;
if (currentLineModelLineNumber !== nextLineModelLineNumber) {
visibleRangesForLine.ranges[visibleRangesForLine.ranges.length - 1].width += this._typicalHalfwidthCharacterWidth;
}
}
visibleRanges[visibleRangesLen++] = new LineVisibleRanges(visibleRangesForLine.outsideRenderedLine, lineNumber, HorizontalRange.from(visibleRangesForLine.ranges), continuesInNextLine);
}
this._updateLineWidthsSlowIfDomDidLayout(domReadingContext);
if (visibleRangesLen === 0) {
return null;
}
return visibleRanges;
}
_visibleRangesForLineRange(lineNumber, startColumn, endColumn) {
if (this.shouldRender()) {
// Cannot read from the DOM because it is dirty
// i.e. the model & the dom are out of sync, so I'd be reading something stale
return null;
}
if (lineNumber < this._visibleLines.getStartLineNumber() || lineNumber > this._visibleLines.getEndLineNumber()) {
return null;
}
const domReadingContext = new DomReadingContext(this.domNode.domNode, this._textRangeRestingSpot);
const result = this._visibleLines.getVisibleLine(lineNumber).getVisibleRangesForRange(lineNumber, startColumn, endColumn, domReadingContext);
this._updateLineWidthsSlowIfDomDidLayout(domReadingContext);
return result;
}
visibleRangeForPosition(position) {
const visibleRanges = this._visibleRangesForLineRange(position.lineNumber, position.column, position.column);
if (!visibleRanges) {
return null;
}
return new HorizontalPosition(visibleRanges.outsideRenderedLine, visibleRanges.ranges[0].left);
}
/**
* Updates the max line width if it is fast to compute.
* Returns true if all lines were taken into account.
* Returns false if some lines need to be reevaluated (in a slow fashion).
*/
_updateLineWidthsFast() {
return this._updateLineWidths(true);
}
_updateLineWidthsSlow() {
this._updateLineWidths(false);
}
/**
* Update the line widths using DOM layout information after someone else
* has caused a synchronous layout.
*/
_updateLineWidthsSlowIfDomDidLayout(domReadingContext) {
if (!domReadingContext.didDomLayout) {
// only proceed if we just did a layout
return;
}
if (this._asyncUpdateLineWidths.isScheduled()) {
// reading widths is not scheduled => widths are up-to-date
return;
}
this._asyncUpdateLineWidths.cancel();
this._updateLineWidthsSlow();
}
_updateLineWidths(fast) {
const rendStartLineNumber = this._visibleLines.getStartLineNumber();
const rendEndLineNumber = this._visibleLines.getEndLineNumber();
let localMaxLineWidth = 1;
let allWidthsComputed = true;
for (let lineNumber = rendStartLineNumber; lineNumber <= rendEndLineNumber; lineNumber++) {
const visibleLine = this._visibleLines.getVisibleLine(lineNumber);
if (fast && !visibleLine.getWidthIsFast()) {
// Cannot compute width in a fast way for this line
allWidthsComputed = false;
continue;
}
localMaxLineWidth = Math.max(localMaxLineWidth, visibleLine.getWidth(null));
}
if (allWidthsComputed && rendStartLineNumber === 1 && rendEndLineNumber === this._context.viewModel.getLineCount()) {
// we know the max line width for all the lines
this._maxLineWidth = 0;
}
this._ensureMaxLineWidth(localMaxLineWidth);
return allWidthsComputed;
}
_checkMonospaceFontAssumptions() {
// Problems with monospace assumptions are more apparent for longer lines,
// as small rounding errors start to sum up, so we will select the longest
// line for a closer inspection
let longestLineNumber = -1;
let longestWidth = -1;
const rendStartLineNumber = this._visibleLines.getStartLineNumber();
const rendEndLineNumber = this._visibleLines.getEndLineNumber();
for (let lineNumber = rendStartLineNumber; lineNumber <= rendEndLineNumber; lineNumber++) {
const visibleLine = this._visibleLines.getVisibleLine(lineNumber);
if (visibleLine.needsMonospaceFontCheck()) {
const lineWidth = visibleLine.getWidth(null);
if (lineWidth > longestWidth) {
longestWidth = lineWidth;
longestLineNumber = lineNumber;
}
}
}
if (longestLineNumber === -1) {
return;
}
if (!this._visibleLines.getVisibleLine(longestLineNumber).monospaceAssumptionsAreValid()) {
for (let lineNumber = rendStartLineNumber; lineNumber <= rendEndLineNumber; lineNumber++) {
const visibleLine = this._visibleLines.getVisibleLine(lineNumber);
visibleLine.onMonospaceAssumptionsInvalidated();
}
}
}
prepareRender() {
throw new Error('Not supported');
}
render() {
throw new Error('Not supported');
}
renderText(viewportData) {
// (1) render lines - ensures lines are in the DOM
this._visibleLines.renderLines(viewportData);
this._lastRenderedData.setCurrentVisibleRange(viewportData.visibleRange);
this.domNode.setWidth(this._context.viewLayout.getScrollWidth());
this.domNode.setHeight(Math.min(this._context.viewLayout.getScrollHeight(), 1000000));
// (2) compute horizontal scroll position:
// - this must happen after the lines are in the DOM since it might need a line that rendered just now
// - it might change `scrollWidth` and `scrollLeft`
if (this._horizontalRevealRequest) {
const horizontalRevealRequest = this._horizontalRevealRequest;
// Check that we have the line that contains the horizontal range in the viewport
if (viewportData.startLineNumber <= horizontalRevealRequest.minLineNumber && horizontalRevealRequest.maxLineNumber <= viewportData.endLineNumber) {
this._horizontalRevealRequest = null;
// allow `visibleRangesForRange2` to work
this.onDidRender();
// compute new scroll position
const newScrollLeft = this._computeScrollLeftToReveal(horizontalRevealRequest);
if (newScrollLeft) {
if (!this._isViewportWrapping) {
// ensure `scrollWidth` is large enough
this._ensureMaxLineWidth(newScrollLeft.maxHorizontalOffset);
}
// set `scrollLeft`
this._context.viewModel.viewLayout.setScrollPosition({
scrollLeft: newScrollLeft.scrollLeft
}, horizontalRevealRequest.scrollType);
}
}
}
// Update max line width (not so important, it is just so the horizontal scrollbar doesn't get too small)
if (!this._updateLineWidthsFast()) {
// Computing the width of some lines would be slow => delay it
this._asyncUpdateLineWidths.schedule();
}
else {
this._asyncUpdateLineWidths.cancel();
}
if (platform.isLinux && !this._asyncCheckMonospaceFontAssumptions.isScheduled()) {
const rendStartLineNumber = this._visibleLines.getStartLineNumber();
const rendEndLineNumber = this._visibleLines.getEndLineNumber();
for (let lineNumber = rendStartLineNumber; lineNumber <= rendEndLineNumber; lineNumber++) {
const visibleLine = this._visibleLines.getVisibleLine(lineNumber);
if (visibleLine.needsMonospaceFontCheck()) {
this._asyncCheckMonospaceFontAssumptions.schedule();
break;
}
}
}
// (3) handle scrolling
this._linesContent.setLayerHinting(this._canUseLayerHinting);
this._linesContent.setContain('strict');
const adjustedScrollTop = this._context.viewLayout.getCurrentScrollTop() - viewportData.bigNumbersDelta;
this._linesContent.setTop(-adjustedScrollTop);
this._linesContent.setLeft(-this._context.viewLayout.getCurrentScrollLeft());
}
// --- width
_ensureMaxLineWidth(lineWidth) {
const iLineWidth = Math.ceil(lineWidth);
if (this._maxLineWidth < iLineWidth) {
this._maxLineWidth = iLineWidth;
this._context.viewModel.viewLayout.setMaxLineWidth(this._maxLineWidth);
}
}
_computeScrollTopToRevealRange(viewport, source, minimalReveal, range, selections, verticalType) {
const viewportStartY = viewport.top;
const viewportHeight = viewport.height;
const viewportEndY = viewportStartY + viewportHeight;
let boxIsSingleRange;
let boxStartY;
let boxEndY;
if (selections && selections.length > 0) {
let minLineNumber = selections[0].startLineNumber;
let maxLineNumber = selections[0].endLineNumber;
for (let i = 1, len = selections.length; i < len; i++) {
const selection = selections[i];
minLineNumber = Math.min(minLineNumber, selection.startLineNumber);
maxLineNumber = Math.max(maxLineNumber, selection.endLineNumber);
}
boxIsSingleRange = false;
boxStartY = this._context.viewLayout.getVerticalOffsetForLineNumber(minLineNumber);
boxEndY = this._context.viewLayout.getVerticalOffsetForLineNumber(maxLineNumber) + this._lineHeight;
}
else if (range) {
boxIsSingleRange = true;
boxStartY = this._context.viewLayout.getVerticalOffsetForLineNumber(range.startLineNumber);
boxEndY = this._context.viewLayout.getVerticalOffsetForLineNumber(range.endLineNumber) + this._lineHeight;
}
else {
return -1;
}
const shouldIgnoreScrollOff = (source === 'mouse' || minimalReveal) && this._cursorSurroundingLinesStyle === 'default';
let paddingTop = 0;
let paddingBottom = 0;
if (!shouldIgnoreScrollOff) {
const maxLinesInViewport = (viewportHeight / this._lineHeight);
const surroundingLines = Math.max(this._cursorSurroundingLines, this._stickyScrollEnabled ? this._maxNumberStickyLines : 0);
const context = Math.min(maxLinesInViewport / 2, surroundingLines);
paddingTop = context * this._lineHeight;
paddingBottom = Math.max(0, (context - 1)) * this._lineHeight;
}
else {
if (!minimalReveal) {
// Reveal one more line above (this case is hit when dragging)
paddingTop = this._lineHeight;
}
}
if (!minimalReveal) {
if (verticalType === 0 /* viewEvents.VerticalRevealType.Simple */ || verticalType === 4 /* viewEvents.VerticalRevealType.Bottom */) {
// Reveal one line more when the last line would be covered by the scrollbar - arrow down case or revealing a line explicitly at bottom
paddingBottom += this._lineHeight;
}
}
boxStartY -= paddingTop;
boxEndY += paddingBottom;
let newScrollTop;
if (boxEndY - boxStartY > viewportHeight) {
// the box is larger than the viewport ... scroll to its top
if (!boxIsSingleRange) {
// do not reveal multiple cursors if there are more than fit the viewport
return -1;
}
newScrollTop = boxStartY;
}
else if (verticalType === 5 /* viewEvents.VerticalRevealType.NearTop */ || verticalType === 6 /* viewEvents.VerticalRevealType.NearTopIfOutsideViewport */) {
if (verticalType === 6 /* viewEvents.VerticalRevealType.NearTopIfOutsideViewport */ && viewportStartY <= boxStartY && boxEndY <= viewportEndY) {
// Box is already in the viewport... do nothing
newScrollTop = viewportStartY;
}
else {
// We want a gap that is 20% of the viewport, but with a minimum of 5 lines
const desiredGapAbove = Math.max(5 * this._lineHeight, viewportHeight * 0.2);
// Try to scroll just above the box with the desired gap
const desiredScrollTop = boxStartY - desiredGapAbove;
// But ensure that the box is not pushed out of viewport
const minScrollTop = boxEndY - viewportHeight;
newScrollTop = Math.max(minScrollTop, desiredScrollTop);
}
}
else if (verticalType === 1 /* viewEvents.VerticalRevealType.Center */ || verticalType === 2 /* viewEvents.VerticalRevealType.CenterIfOutsideViewport */) {
if (verticalType === 2 /* viewEvents.VerticalRevealType.CenterIfOutsideViewport */ && viewportStartY <= boxStartY && boxEndY <= viewportEndY) {
// Box is already in the viewport... do nothing
newScrollTop = viewportStartY;
}
else {
// Box is outside the viewport... center it
const boxMiddleY = (boxStartY + boxEndY) / 2;
newScrollTop = Math.max(0, boxMiddleY - viewportHeight / 2);
}
}
else {
newScrollTop = this._computeMinimumScrolling(viewportStartY, viewportEndY, boxStartY, boxEndY, verticalType === 3 /* viewEvents.VerticalRevealType.Top */, verticalType === 4 /* viewEvents.VerticalRevealType.Bottom */);
}
return newScrollTop;
}
_computeScrollLeftToReveal(horizontalRevealRequest) {
const viewport = this._context.viewLayout.getCurrentViewport();
const layoutInfo = this._context.configuration.options.get(146 /* EditorOption.layoutInfo */);
const viewportStartX = viewport.left;
const viewportEndX = viewportStartX + viewport.width - layoutInfo.verticalScrollbarWidth;
let boxStartX = 1073741824 /* Constants.MAX_SAFE_SMALL_INTEGER */;
let boxEndX = 0;
if (horizontalRevealRequest.type === 'range') {
const visibleRanges = this._visibleRangesForLineRange(horizontalRevealRequest.lineNumber, horizontalRevealRequest.startColumn, horizontalRevealRequest.endColumn);
if (!visibleRanges) {
return null;
}
for (const visibleRange of visibleRanges.ranges) {
boxStartX = Math.min(boxStartX, Math.round(visibleRange.left));
boxEndX = Math.max(boxEndX, Math.round(visibleRange.left + visibleRange.width));
}
}
else {
for (const selection of horizontalRevealRequest.selections) {
if (selection.startLineNumber !== selection.endLineNumber) {
return null;
}
const visibleRanges = this._visibleRangesForLineRange(selection.startLineNumber, selection.startColumn, selection.endColumn);
if (!visibleRanges) {
return null;
}
for (const visibleRange of visibleRanges.ranges) {
boxStartX = Math.min(boxStartX, Math.round(visibleRange.left));
boxEndX = Math.max(boxEndX, Math.round(visibleRange.left + visibleRange.width));
}
}
}
if (!horizontalRevealRequest.minimalReveal) {
boxStartX = Math.max(0, boxStartX - ViewLines.HORIZONTAL_EXTRA_PX);
boxEndX += this._revealHorizontalRightPadding;
}
if (horizontalRevealRequest.type === 'selections' && boxEndX - boxStartX > viewport.width) {
return null;
}
const newScrollLeft = this._computeMinimumScrolling(viewportStartX, viewportEndX, boxStartX, boxEndX);
return {
scrollLeft: newScrollLeft,
maxHorizontalOffset: boxEndX
};
}
_computeMinimumScrolling(viewportStart, viewportEnd, boxStart, boxEnd, revealAtStart, revealAtEnd) {
viewportStart = viewportStart | 0;
viewportEnd = viewportEnd | 0;
boxStart = boxStart | 0;
boxEnd = boxEnd | 0;
revealAtStart = !!revealAtStart;
revealAtEnd = !!revealAtEnd;
const viewportLength = viewportEnd - viewportStart;
const boxLength = boxEnd - boxStart;
if (boxLength < viewportLength) {
// The box would fit in the viewport
if (revealAtStart) {
return boxStart;
}
if (revealAtEnd) {
return Math.max(0, boxEnd - viewportLength);
}
if (boxStart < viewportStart) {
// The box is above the viewport
return boxStart;
}
else if (boxEnd > viewportEnd) {
// The box is below the viewport
return Math.max(0, boxEnd - viewportLength);
}
}
else {
// The box would not fit in the viewport
// Reveal the beginning of the box
return boxStart;
}
return viewportStart;
}
}