SQL Operations Studio Public Preview 1 (0.23) release source code

This commit is contained in:
Karl Burtram
2017-11-09 14:30:27 -08:00
parent b88ecb8d93
commit 3cdac41339
8829 changed files with 759707 additions and 286 deletions

View File

@@ -0,0 +1,431 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as dom from 'vs/base/browser/dom';
import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode';
import { ContentWidgetPositionPreference, IContentWidget } from 'vs/editor/browser/editorBrowser';
import { ViewPart, PartFingerprint, PartFingerprints } from 'vs/editor/browser/view/viewPart';
import { ViewContext } from 'vs/editor/common/view/viewContext';
import { RenderingContext, RestrictedRenderingContext } from 'vs/editor/common/view/renderingContext';
import { Position, IPosition } from 'vs/editor/common/core/position';
import * as viewEvents from 'vs/editor/common/view/viewEvents';
class Coordinate {
_coordinateBrand: void;
public readonly top: number;
public readonly left: number;
constructor(top: number, left: number) {
this.top = top;
this.left = left;
}
}
export class ViewContentWidgets extends ViewPart {
private _viewDomNode: FastDomNode<HTMLElement>;
private _widgets: { [key: string]: Widget; };
public domNode: FastDomNode<HTMLElement>;
public overflowingContentWidgetsDomNode: FastDomNode<HTMLElement>;
constructor(context: ViewContext, viewDomNode: FastDomNode<HTMLElement>) {
super(context);
this._viewDomNode = viewDomNode;
this._widgets = {};
this.domNode = createFastDomNode(document.createElement('div'));
PartFingerprints.write(this.domNode, PartFingerprint.ContentWidgets);
this.domNode.setClassName('contentWidgets');
this.domNode.setPosition('absolute');
this.domNode.setTop(0);
this.overflowingContentWidgetsDomNode = createFastDomNode(document.createElement('div'));
PartFingerprints.write(this.overflowingContentWidgetsDomNode, PartFingerprint.OverflowingContentWidgets);
this.overflowingContentWidgetsDomNode.setClassName('overflowingContentWidgets');
}
public dispose(): void {
super.dispose();
this._widgets = null;
this.domNode = null;
}
// --- begin event handlers
public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean {
let keys = Object.keys(this._widgets);
for (let i = 0, len = keys.length; i < len; i++) {
const widgetId = keys[i];
this._widgets[widgetId].onConfigurationChanged(e);
}
return true;
}
public onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean {
// true for inline decorations that can end up relayouting text
return true;
}
public onFlushed(e: viewEvents.ViewFlushedEvent): boolean {
return true;
}
public onLinesChanged(e: viewEvents.ViewLinesChangedEvent): boolean {
return true;
}
public onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): boolean {
return true;
}
public onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): boolean {
return true;
}
public onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean {
return true;
}
public onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean {
return true;
}
// ---- end view event handlers
public addWidget(_widget: IContentWidget): void {
const myWidget = new Widget(this._context, this._viewDomNode, _widget);
this._widgets[myWidget.id] = myWidget;
if (myWidget.allowEditorOverflow) {
this.overflowingContentWidgetsDomNode.appendChild(myWidget.domNode);
} else {
this.domNode.appendChild(myWidget.domNode);
}
this.setShouldRender();
}
public setWidgetPosition(widget: IContentWidget, position: IPosition, preference: ContentWidgetPositionPreference[]): void {
const myWidget = this._widgets[widget.getId()];
myWidget.setPosition(position, preference);
this.setShouldRender();
}
public removeWidget(widget: IContentWidget): void {
const widgetId = widget.getId();
if (this._widgets.hasOwnProperty(widgetId)) {
const myWidget = this._widgets[widgetId];
delete this._widgets[widgetId];
const domNode = myWidget.domNode.domNode;
domNode.parentNode.removeChild(domNode);
domNode.removeAttribute('monaco-visible-content-widget');
this.setShouldRender();
}
}
public shouldSuppressMouseDownOnWidget(widgetId: string): boolean {
if (this._widgets.hasOwnProperty(widgetId)) {
return this._widgets[widgetId].suppressMouseDown;
}
return false;
}
public prepareRender(ctx: RenderingContext): void {
let keys = Object.keys(this._widgets);
for (let i = 0, len = keys.length; i < len; i++) {
const widgetId = keys[i];
this._widgets[widgetId].prepareRender(ctx);
}
}
public render(ctx: RestrictedRenderingContext): void {
let keys = Object.keys(this._widgets);
for (let i = 0, len = keys.length; i < len; i++) {
const widgetId = keys[i];
this._widgets[widgetId].render(ctx);
}
}
}
interface IBoxLayoutResult {
aboveTop: number;
fitsAbove: boolean;
belowTop: number;
fitsBelow: boolean;
left: number;
}
class Widget {
private readonly _context: ViewContext;
private readonly _viewDomNode: FastDomNode<HTMLElement>;
private readonly _actual: IContentWidget;
public readonly domNode: FastDomNode<HTMLElement>;
public readonly id: string;
public readonly allowEditorOverflow: boolean;
public readonly suppressMouseDown: boolean;
private _fixedOverflowWidgets: boolean;
private _contentWidth: number;
private _contentLeft: number;
private _lineHeight: number;
private _position: IPosition;
private _preference: ContentWidgetPositionPreference[];
private _isVisible: boolean;
private _renderData: Coordinate;
constructor(context: ViewContext, viewDomNode: FastDomNode<HTMLElement>, actual: IContentWidget) {
this._context = context;
this._viewDomNode = viewDomNode;
this._actual = actual;
this.domNode = createFastDomNode(this._actual.getDomNode());
this.id = this._actual.getId();
this.allowEditorOverflow = this._actual.allowEditorOverflow || false;
this.suppressMouseDown = this._actual.suppressMouseDown || false;
this._fixedOverflowWidgets = this._context.configuration.editor.viewInfo.fixedOverflowWidgets;
this._contentWidth = this._context.configuration.editor.layoutInfo.contentWidth;
this._contentLeft = this._context.configuration.editor.layoutInfo.contentLeft;
this._lineHeight = this._context.configuration.editor.lineHeight;
this._position = null;
this._preference = null;
this._isVisible = false;
this._renderData = null;
this.domNode.setPosition((this._fixedOverflowWidgets && this.allowEditorOverflow) ? 'fixed' : 'absolute');
this._updateMaxWidth();
this.domNode.setVisibility('hidden');
this.domNode.setAttribute('widgetId', this.id);
}
public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): void {
if (e.lineHeight) {
this._lineHeight = this._context.configuration.editor.lineHeight;
}
if (e.layoutInfo) {
this._contentLeft = this._context.configuration.editor.layoutInfo.contentLeft;
this._contentWidth = this._context.configuration.editor.layoutInfo.contentWidth;
this._updateMaxWidth();
}
}
private _updateMaxWidth(): void {
const maxWidth = this.allowEditorOverflow
? window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth
: this._contentWidth;
this.domNode.setMaxWidth(maxWidth);
}
public setPosition(position: IPosition, preference: ContentWidgetPositionPreference[]): void {
this._position = position;
this._preference = preference;
}
private _layoutBoxInViewport(topLeft: Coordinate, width: number, height: number, ctx: RenderingContext): IBoxLayoutResult {
// Our visible box is split horizontally by the current line => 2 boxes
// a) the box above the line
let aboveLineTop = topLeft.top;
let heightAboveLine = aboveLineTop;
// b) the box under the line
let underLineTop = topLeft.top + this._lineHeight;
let heightUnderLine = ctx.viewportHeight - underLineTop;
let aboveTop = aboveLineTop - height;
let fitsAbove = (heightAboveLine >= height);
let belowTop = underLineTop;
let fitsBelow = (heightUnderLine >= height);
// And its left
let actualLeft = topLeft.left;
if (actualLeft + width > ctx.scrollLeft + ctx.viewportWidth) {
actualLeft = ctx.scrollLeft + ctx.viewportWidth - width;
}
if (actualLeft < ctx.scrollLeft) {
actualLeft = ctx.scrollLeft;
}
return {
aboveTop: aboveTop,
fitsAbove: fitsAbove,
belowTop: belowTop,
fitsBelow: fitsBelow,
left: actualLeft
};
}
private _layoutBoxInPage(topLeft: Coordinate, width: number, height: number, ctx: RenderingContext): IBoxLayoutResult {
let left0 = topLeft.left - ctx.scrollLeft;
if (left0 + width < 0 || left0 > this._contentWidth) {
return null;
}
let aboveTop = topLeft.top - height;
let belowTop = topLeft.top + this._lineHeight;
let left = left0 + this._contentLeft;
let domNodePosition = dom.getDomNodePagePosition(this._viewDomNode.domNode);
let absoluteAboveTop = domNodePosition.top + aboveTop - dom.StandardWindow.scrollY;
let absoluteBelowTop = domNodePosition.top + belowTop - dom.StandardWindow.scrollY;
let absoluteLeft = domNodePosition.left + left - dom.StandardWindow.scrollX;
let INNER_WIDTH = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
let INNER_HEIGHT = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
// Leave some clearance to the bottom
let TOP_PADDING = 22;
let BOTTOM_PADDING = 22;
let fitsAbove = (absoluteAboveTop >= TOP_PADDING),
fitsBelow = (absoluteBelowTop + height <= INNER_HEIGHT - BOTTOM_PADDING);
if (absoluteLeft + width + 20 > INNER_WIDTH) {
let delta = absoluteLeft - (INNER_WIDTH - width - 20);
absoluteLeft -= delta;
left -= delta;
}
if (absoluteLeft < 0) {
let delta = absoluteLeft;
absoluteLeft -= delta;
left -= delta;
}
if (this._fixedOverflowWidgets) {
aboveTop = absoluteAboveTop;
belowTop = absoluteBelowTop;
left = absoluteLeft;
}
return { aboveTop, fitsAbove, belowTop, fitsBelow, left };
}
private _prepareRenderWidgetAtExactPositionOverflowing(topLeft: Coordinate): Coordinate {
return new Coordinate(topLeft.top, topLeft.left + this._contentLeft);
}
private _getTopLeft(ctx: RenderingContext, position: Position): Coordinate {
const visibleRange = ctx.visibleRangeForPosition(position);
if (!visibleRange) {
return null;
}
const top = ctx.getVerticalOffsetForLineNumber(position.lineNumber) - ctx.scrollTop;
return new Coordinate(top, visibleRange.left);
}
private _prepareRenderWidget(ctx: RenderingContext): Coordinate {
if (!this._position || !this._preference) {
return null;
}
// Do not trust that widgets have a valid position
let validModelPosition = this._context.model.validateModelPosition(this._position);
if (!this._context.model.coordinatesConverter.modelPositionIsVisible(validModelPosition)) {
// this position is hidden by the view model
return null;
}
let position = this._context.model.coordinatesConverter.convertModelPositionToViewPosition(validModelPosition);
let placement: IBoxLayoutResult = null;
let fetchPlacement = (): void => {
if (placement) {
return;
}
const topLeft = this._getTopLeft(ctx, position);
if (!topLeft) {
return;
}
const domNode = this.domNode.domNode;
const width = domNode.clientWidth;
const height = domNode.clientHeight;
if (this.allowEditorOverflow) {
placement = this._layoutBoxInPage(topLeft, width, height, ctx);
} else {
placement = this._layoutBoxInViewport(topLeft, width, height, ctx);
}
};
// Do two passes, first for perfect fit, second picks first option
for (let pass = 1; pass <= 2; pass++) {
for (let i = 0; i < this._preference.length; i++) {
let pref = this._preference[i];
if (pref === ContentWidgetPositionPreference.ABOVE) {
fetchPlacement();
if (!placement) {
// Widget outside of viewport
return null;
}
if (pass === 2 || placement.fitsAbove) {
return new Coordinate(placement.aboveTop, placement.left);
}
} else if (pref === ContentWidgetPositionPreference.BELOW) {
fetchPlacement();
if (!placement) {
// Widget outside of viewport
return null;
}
if (pass === 2 || placement.fitsBelow) {
return new Coordinate(placement.belowTop, placement.left);
}
} else {
const topLeft = this._getTopLeft(ctx, position);
if (!topLeft) {
// Widget outside of viewport
return null;
}
if (this.allowEditorOverflow) {
return this._prepareRenderWidgetAtExactPositionOverflowing(topLeft);
} else {
return topLeft;
}
}
}
}
return null;
}
public prepareRender(ctx: RenderingContext): void {
this._renderData = this._prepareRenderWidget(ctx);
}
public render(ctx: RestrictedRenderingContext): void {
if (!this._renderData) {
// This widget should be invisible
if (this._isVisible) {
this.domNode.removeAttribute('monaco-visible-content-widget');
this._isVisible = false;
this.domNode.setVisibility('hidden');
}
return;
}
// This widget should be visible
if (this.allowEditorOverflow) {
this.domNode.setTop(this._renderData.top);
this.domNode.setLeft(this._renderData.left);
} else {
this.domNode.setTop(this._renderData.top + ctx.scrollTop - ctx.bigNumbersDelta);
this.domNode.setLeft(this._renderData.left);
}
if (!this._isVisible) {
this.domNode.setVisibility('inherit');
this.domNode.setAttribute('monaco-visible-content-widget', 'true');
this._isVisible = true;
}
}
}

View File

@@ -0,0 +1,12 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-editor .view-overlays .current-line {
display: block;
position: absolute;
left: 0;
top: 0;
box-sizing: border-box;
}

View File

@@ -0,0 +1,148 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'vs/css!./currentLineHighlight';
import { DynamicViewOverlay } from 'vs/editor/browser/view/dynamicViewOverlay';
import { ViewContext } from 'vs/editor/common/view/viewContext';
import { RenderingContext } from 'vs/editor/common/view/renderingContext';
import * as viewEvents from 'vs/editor/common/view/viewEvents';
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { editorLineHighlight, editorLineHighlightBorder } from 'vs/editor/common/view/editorColorRegistry';
export class CurrentLineHighlightOverlay extends DynamicViewOverlay {
private _context: ViewContext;
private _lineHeight: number;
private _readOnly: boolean;
private _renderLineHighlight: 'none' | 'gutter' | 'line' | 'all';
private _selectionIsEmpty: boolean;
private _primaryCursorIsInEditableRange: boolean;
private _primaryCursorLineNumber: number;
private _scrollWidth: number;
private _contentWidth: number;
constructor(context: ViewContext) {
super();
this._context = context;
this._lineHeight = this._context.configuration.editor.lineHeight;
this._readOnly = this._context.configuration.editor.readOnly;
this._renderLineHighlight = this._context.configuration.editor.viewInfo.renderLineHighlight;
this._selectionIsEmpty = true;
this._primaryCursorIsInEditableRange = true;
this._primaryCursorLineNumber = 1;
this._scrollWidth = 0;
this._contentWidth = this._context.configuration.editor.layoutInfo.contentWidth;
this._context.addEventHandler(this);
}
public dispose(): void {
this._context.removeEventHandler(this);
this._context = null;
super.dispose();
}
// --- begin event handlers
public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean {
if (e.lineHeight) {
this._lineHeight = this._context.configuration.editor.lineHeight;
}
if (e.readOnly) {
this._readOnly = this._context.configuration.editor.readOnly;
}
if (e.viewInfo) {
this._renderLineHighlight = this._context.configuration.editor.viewInfo.renderLineHighlight;
}
if (e.layoutInfo) {
this._contentWidth = this._context.configuration.editor.layoutInfo.contentWidth;
}
return true;
}
public onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean {
let hasChanged = false;
if (this._primaryCursorIsInEditableRange !== e.isInEditableRange) {
this._primaryCursorIsInEditableRange = e.isInEditableRange;
hasChanged = true;
}
const primaryCursorLineNumber = e.selections[0].positionLineNumber;
if (this._primaryCursorLineNumber !== primaryCursorLineNumber) {
this._primaryCursorLineNumber = primaryCursorLineNumber;
hasChanged = true;
}
const selectionIsEmpty = e.selections[0].isEmpty();
if (this._selectionIsEmpty !== selectionIsEmpty) {
this._selectionIsEmpty = selectionIsEmpty;
hasChanged = true;
return true;
}
return hasChanged;
}
public onFlushed(e: viewEvents.ViewFlushedEvent): boolean {
return true;
}
public onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): boolean {
return true;
}
public onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): boolean {
return true;
}
public onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean {
return e.scrollWidthChanged;
}
public onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean {
return true;
}
// --- end event handlers
public prepareRender(ctx: RenderingContext): void {
this._scrollWidth = ctx.scrollWidth;
}
public render(startLineNumber: number, lineNumber: number): string {
if (lineNumber === this._primaryCursorLineNumber) {
if (this._shouldShowCurrentLine()) {
return (
'<div class="current-line" style="width:'
+ String(Math.max(this._scrollWidth, this._contentWidth))
+ 'px; height:'
+ String(this._lineHeight)
+ 'px;"></div>'
);
} else {
return '';
}
}
return '';
}
private _shouldShowCurrentLine(): boolean {
return (this._renderLineHighlight === 'line' || this._renderLineHighlight === 'all') &&
this._selectionIsEmpty &&
this._primaryCursorIsInEditableRange;
}
}
registerThemingParticipant((theme, collector) => {
let lineHighlight = theme.getColor(editorLineHighlight);
if (lineHighlight) {
collector.addRule(`.monaco-editor .view-overlays .current-line { background-color: ${lineHighlight}; }`);
}
if (!lineHighlight || lineHighlight.isTransparent() || theme.defines(editorLineHighlightBorder)) {
let lineHighlightBorder = theme.getColor(editorLineHighlightBorder);
if (lineHighlightBorder) {
collector.addRule(`.monaco-editor .view-overlays .current-line { border: 2px solid ${lineHighlightBorder}; }`);
if (theme.type === 'hc') {
collector.addRule(`.monaco-editor .view-overlays .current-line { border-width: 1px; }`);
}
}
}
});

View File

@@ -0,0 +1,12 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-editor .margin-view-overlays .current-line-margin {
display: block;
position: absolute;
left: 0;
top: 0;
box-sizing: border-box;
}

View File

@@ -0,0 +1,125 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'vs/css!./currentLineMarginHighlight';
import { DynamicViewOverlay } from 'vs/editor/browser/view/dynamicViewOverlay';
import { ViewContext } from 'vs/editor/common/view/viewContext';
import { RenderingContext } from 'vs/editor/common/view/renderingContext';
import * as viewEvents from 'vs/editor/common/view/viewEvents';
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { editorLineHighlight, editorLineHighlightBorder } from 'vs/editor/common/view/editorColorRegistry';
export class CurrentLineMarginHighlightOverlay extends DynamicViewOverlay {
private _context: ViewContext;
private _lineHeight: number;
private _renderLineHighlight: 'none' | 'gutter' | 'line' | 'all';
private _primaryCursorIsInEditableRange: boolean;
private _primaryCursorLineNumber: number;
private _contentLeft: number;
constructor(context: ViewContext) {
super();
this._context = context;
this._lineHeight = this._context.configuration.editor.lineHeight;
this._renderLineHighlight = this._context.configuration.editor.viewInfo.renderLineHighlight;
this._primaryCursorIsInEditableRange = true;
this._primaryCursorLineNumber = 1;
this._contentLeft = this._context.configuration.editor.layoutInfo.contentLeft;
this._context.addEventHandler(this);
}
public dispose(): void {
this._context.removeEventHandler(this);
this._context = null;
super.dispose();
}
// --- begin event handlers
public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean {
if (e.lineHeight) {
this._lineHeight = this._context.configuration.editor.lineHeight;
}
if (e.viewInfo) {
this._renderLineHighlight = this._context.configuration.editor.viewInfo.renderLineHighlight;
}
if (e.layoutInfo) {
this._contentLeft = this._context.configuration.editor.layoutInfo.contentLeft;
}
return true;
}
public onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean {
let hasChanged = false;
if (this._primaryCursorIsInEditableRange !== e.isInEditableRange) {
this._primaryCursorIsInEditableRange = e.isInEditableRange;
hasChanged = true;
}
const primaryCursorLineNumber = e.selections[0].positionLineNumber;
if (this._primaryCursorLineNumber !== primaryCursorLineNumber) {
this._primaryCursorLineNumber = primaryCursorLineNumber;
hasChanged = true;
}
return hasChanged;
}
public onFlushed(e: viewEvents.ViewFlushedEvent): boolean {
return true;
}
public onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): boolean {
return true;
}
public onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): boolean {
return true;
}
public onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean {
return true;
}
// --- end event handlers
public prepareRender(ctx: RenderingContext): void {
}
public render(startLineNumber: number, lineNumber: number): string {
if (lineNumber === this._primaryCursorLineNumber) {
if (this._shouldShowCurrentLine()) {
return (
'<div class="current-line-margin" style="width:'
+ String(this._contentLeft)
+ 'px; height:'
+ String(this._lineHeight)
+ 'px;"></div>'
);
} else {
return '';
}
}
return '';
}
private _shouldShowCurrentLine(): boolean {
return (this._renderLineHighlight === 'gutter' || this._renderLineHighlight === 'all') && this._primaryCursorIsInEditableRange;
}
}
registerThemingParticipant((theme, collector) => {
let lineHighlight = theme.getColor(editorLineHighlight);
if (lineHighlight) {
collector.addRule(`.monaco-editor .margin-view-overlays .current-line-margin { background-color: ${lineHighlight}; border: none; }`);
} else {
let lineHighlightBorder = theme.getColor(editorLineHighlightBorder);
if (lineHighlightBorder) {
collector.addRule(`.monaco-editor .margin-view-overlays .current-line-margin { border: 2px solid ${lineHighlightBorder}; }`);
}
if (theme.type === 'hc') {
collector.addRule(`.monaco-editor .margin-view-overlays .current-line-margin { border-width: 1px; }`);
}
}
});

View File

@@ -0,0 +1,12 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/*
Keeping name short for faster parsing.
cdr = core decorations rendering (div)
*/
.monaco-editor .lines-content .cdr {
position: absolute;
}

View File

@@ -0,0 +1,209 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'vs/css!./decorations';
import { DynamicViewOverlay } from 'vs/editor/browser/view/dynamicViewOverlay';
import { Range } from 'vs/editor/common/core/range';
import { ViewContext } from 'vs/editor/common/view/viewContext';
import { RenderingContext, HorizontalRange } from 'vs/editor/common/view/renderingContext';
import { ViewModelDecoration } from 'vs/editor/common/viewModel/viewModel';
import * as viewEvents from 'vs/editor/common/view/viewEvents';
export class DecorationsOverlay extends DynamicViewOverlay {
private _context: ViewContext;
private _lineHeight: number;
private _typicalHalfwidthCharacterWidth: number;
private _renderResult: string[];
constructor(context: ViewContext) {
super();
this._context = context;
this._lineHeight = this._context.configuration.editor.lineHeight;
this._typicalHalfwidthCharacterWidth = this._context.configuration.editor.fontInfo.typicalHalfwidthCharacterWidth;
this._renderResult = null;
this._context.addEventHandler(this);
}
public dispose(): void {
this._context.removeEventHandler(this);
this._context = null;
this._renderResult = null;
super.dispose();
}
// --- begin event handlers
public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean {
if (e.lineHeight) {
this._lineHeight = this._context.configuration.editor.lineHeight;
}
if (e.fontInfo) {
this._typicalHalfwidthCharacterWidth = this._context.configuration.editor.fontInfo.typicalHalfwidthCharacterWidth;
}
return true;
}
public onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean {
return true;
}
public onFlushed(e: viewEvents.ViewFlushedEvent): boolean {
return true;
}
public onLinesChanged(e: viewEvents.ViewLinesChangedEvent): boolean {
return true;
}
public onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): boolean {
return true;
}
public onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): boolean {
return true;
}
public onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean {
return e.scrollTopChanged || e.scrollWidthChanged;
}
public onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean {
return true;
}
// --- end event handlers
public prepareRender(ctx: RenderingContext): void {
let _decorations = ctx.getDecorationsInViewport();
// Keep only decorations with `className`
let decorations: ViewModelDecoration[] = [], decorationsLen = 0;
for (let i = 0, len = _decorations.length; i < len; i++) {
let d = _decorations[i];
if (d.source.options.className) {
decorations[decorationsLen++] = d;
}
}
// Sort decorations for consistent render output
decorations = decorations.sort((a, b) => {
let aClassName = a.source.options.className;
let bClassName = b.source.options.className;
if (aClassName < bClassName) {
return -1;
}
if (aClassName > bClassName) {
return 1;
}
return Range.compareRangesUsingStarts(a.range, b.range);
});
let visibleStartLineNumber = ctx.visibleRange.startLineNumber;
let visibleEndLineNumber = ctx.visibleRange.endLineNumber;
let output: string[] = [];
for (let lineNumber = visibleStartLineNumber; lineNumber <= visibleEndLineNumber; lineNumber++) {
let lineIndex = lineNumber - visibleStartLineNumber;
output[lineIndex] = '';
}
// Render first whole line decorations and then regular decorations
this._renderWholeLineDecorations(ctx, decorations, output);
this._renderNormalDecorations(ctx, decorations, output);
this._renderResult = output;
}
private _renderWholeLineDecorations(ctx: RenderingContext, decorations: ViewModelDecoration[], output: string[]): void {
let lineHeight = String(this._lineHeight);
let visibleStartLineNumber = ctx.visibleRange.startLineNumber;
let visibleEndLineNumber = ctx.visibleRange.endLineNumber;
for (let i = 0, lenI = decorations.length; i < lenI; i++) {
let d = decorations[i];
if (!d.source.options.isWholeLine) {
continue;
}
let decorationOutput = (
'<div class="cdr '
+ d.source.options.className
+ '" style="left:0;width:100%;height:'
+ lineHeight
+ 'px;"></div>'
);
let startLineNumber = Math.max(d.range.startLineNumber, visibleStartLineNumber);
let endLineNumber = Math.min(d.range.endLineNumber, visibleEndLineNumber);
for (let j = startLineNumber; j <= endLineNumber; j++) {
let lineIndex = j - visibleStartLineNumber;
output[lineIndex] += decorationOutput;
}
}
}
private _renderNormalDecorations(ctx: RenderingContext, decorations: ViewModelDecoration[], output: string[]): void {
let lineHeight = String(this._lineHeight);
let visibleStartLineNumber = ctx.visibleRange.startLineNumber;
for (let i = 0, lenI = decorations.length; i < lenI; i++) {
const d = decorations[i];
if (d.source.options.isWholeLine) {
continue;
}
const className = d.source.options.className;
const showIfCollapsed = d.source.options.showIfCollapsed;
let range = d.range;
if (showIfCollapsed && range.endColumn === 1 && range.endLineNumber !== range.startLineNumber) {
range = new Range(range.startLineNumber, range.startColumn, range.endLineNumber - 1, this._context.model.getLineMaxColumn(range.endLineNumber - 1));
}
let linesVisibleRanges = ctx.linesVisibleRangesForRange(range, /*TODO@Alex*/className === 'findMatch');
if (!linesVisibleRanges) {
continue;
}
for (let j = 0, lenJ = linesVisibleRanges.length; j < lenJ; j++) {
let lineVisibleRanges = linesVisibleRanges[j];
const lineIndex = lineVisibleRanges.lineNumber - visibleStartLineNumber;
if (showIfCollapsed && lineVisibleRanges.ranges.length === 1) {
const singleVisibleRange = lineVisibleRanges.ranges[0];
if (singleVisibleRange.width === 0) {
// collapsed range case => make the decoration visible by faking its width
lineVisibleRanges.ranges[0] = new HorizontalRange(singleVisibleRange.left, this._typicalHalfwidthCharacterWidth);
}
}
for (let k = 0, lenK = lineVisibleRanges.ranges.length; k < lenK; k++) {
const visibleRange = lineVisibleRanges.ranges[k];
const decorationOutput = (
'<div class="cdr '
+ className
+ '" style="left:'
+ String(visibleRange.left)
+ 'px;width:'
+ String(visibleRange.width)
+ 'px;height:'
+ lineHeight
+ 'px;"></div>'
);
output[lineIndex] += decorationOutput;
}
}
}
}
public render(startLineNumber: number, lineNumber: number): string {
if (!this._renderResult) {
return '';
}
let lineIndex = lineNumber - startLineNumber;
if (lineIndex < 0 || lineIndex >= this._renderResult.length) {
throw new Error('Unexpected render request');
}
return this._renderResult[lineIndex];
}
}

View File

@@ -0,0 +1,155 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as dom from 'vs/base/browser/dom';
import { ScrollableElementCreationOptions, ScrollableElementChangeOptions } from 'vs/base/browser/ui/scrollbar/scrollableElementOptions';
import { IOverviewRulerLayoutInfo, SmoothScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
import { INewScrollPosition } from 'vs/editor/common/editorCommon';
import { ViewPart, PartFingerprint, PartFingerprints } from 'vs/editor/browser/view/viewPart';
import { ViewContext } from 'vs/editor/common/view/viewContext';
import * as viewEvents from 'vs/editor/common/view/viewEvents';
import { RenderingContext, RestrictedRenderingContext } from 'vs/editor/common/view/renderingContext';
import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode';
import { getThemeTypeSelector } from 'vs/platform/theme/common/themeService';
import { IMouseEvent } from 'vs/base/browser/mouseEvent';
import { ISimplifiedMouseEvent } from 'vs/base/browser/ui/scrollbar/abstractScrollbar';
export class EditorScrollbar extends ViewPart {
private scrollbar: SmoothScrollableElement;
private scrollbarDomNode: FastDomNode<HTMLElement>;
constructor(
context: ViewContext,
linesContent: FastDomNode<HTMLElement>,
viewDomNode: FastDomNode<HTMLElement>,
overflowGuardDomNode: FastDomNode<HTMLElement>
) {
super(context);
const editor = this._context.configuration.editor;
const configScrollbarOpts = editor.viewInfo.scrollbar;
let scrollbarOptions: ScrollableElementCreationOptions = {
listenOnDomNode: viewDomNode.domNode,
className: 'editor-scrollable' + ' ' + getThemeTypeSelector(context.theme.type),
useShadows: false,
lazyRender: true,
vertical: configScrollbarOpts.vertical,
horizontal: configScrollbarOpts.horizontal,
verticalHasArrows: configScrollbarOpts.verticalHasArrows,
horizontalHasArrows: configScrollbarOpts.horizontalHasArrows,
verticalScrollbarSize: configScrollbarOpts.verticalScrollbarSize,
verticalSliderSize: configScrollbarOpts.verticalSliderSize,
horizontalScrollbarSize: configScrollbarOpts.horizontalScrollbarSize,
horizontalSliderSize: configScrollbarOpts.horizontalSliderSize,
handleMouseWheel: configScrollbarOpts.handleMouseWheel,
arrowSize: configScrollbarOpts.arrowSize,
mouseWheelScrollSensitivity: configScrollbarOpts.mouseWheelScrollSensitivity,
};
this.scrollbar = this._register(new SmoothScrollableElement(linesContent.domNode, scrollbarOptions, this._context.viewLayout.scrollable));
PartFingerprints.write(this.scrollbar.getDomNode(), PartFingerprint.ScrollableElement);
this.scrollbarDomNode = createFastDomNode(this.scrollbar.getDomNode());
this.scrollbarDomNode.setPosition('absolute');
this._setLayout();
// When having a zone widget that calls .focus() on one of its dom elements,
// the browser will try desperately to reveal that dom node, unexpectedly
// changing the .scrollTop of this.linesContent
let onBrowserDesperateReveal = (domNode: HTMLElement, lookAtScrollTop: boolean, lookAtScrollLeft: boolean) => {
let newScrollPosition: INewScrollPosition = {};
if (lookAtScrollTop) {
let deltaTop = domNode.scrollTop;
if (deltaTop) {
newScrollPosition.scrollTop = this._context.viewLayout.getCurrentScrollTop() + deltaTop;
domNode.scrollTop = 0;
}
}
if (lookAtScrollLeft) {
let deltaLeft = domNode.scrollLeft;
if (deltaLeft) {
newScrollPosition.scrollLeft = this._context.viewLayout.getCurrentScrollLeft() + deltaLeft;
domNode.scrollLeft = 0;
}
}
this._context.viewLayout.setScrollPositionNow(newScrollPosition);
};
// I've seen this happen both on the view dom node & on the lines content dom node.
this._register(dom.addDisposableListener(viewDomNode.domNode, 'scroll', (e: Event) => onBrowserDesperateReveal(viewDomNode.domNode, true, true)));
this._register(dom.addDisposableListener(linesContent.domNode, 'scroll', (e: Event) => onBrowserDesperateReveal(linesContent.domNode, true, false)));
this._register(dom.addDisposableListener(overflowGuardDomNode.domNode, 'scroll', (e: Event) => onBrowserDesperateReveal(overflowGuardDomNode.domNode, true, false)));
}
public dispose(): void {
super.dispose();
}
private _setLayout(): void {
const layoutInfo = this._context.configuration.editor.layoutInfo;
this.scrollbarDomNode.setLeft(layoutInfo.contentLeft);
this.scrollbarDomNode.setWidth(layoutInfo.contentWidth + layoutInfo.minimapWidth);
this.scrollbarDomNode.setHeight(layoutInfo.contentHeight);
}
public getOverviewRulerLayoutInfo(): IOverviewRulerLayoutInfo {
return this.scrollbar.getOverviewRulerLayoutInfo();
}
public getDomNode(): FastDomNode<HTMLElement> {
return this.scrollbarDomNode;
}
public delegateVerticalScrollbarMouseDown(browserEvent: IMouseEvent): void {
this.scrollbar.delegateVerticalScrollbarMouseDown(browserEvent);
}
public delegateSliderMouseDown(e: ISimplifiedMouseEvent, onDragFinished: () => void): void {
this.scrollbar.delegateSliderMouseDown(e, onDragFinished);
}
// --- begin event handlers
public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean {
if (e.viewInfo) {
const editor = this._context.configuration.editor;
let newOpts: ScrollableElementChangeOptions = {
handleMouseWheel: editor.viewInfo.scrollbar.handleMouseWheel,
mouseWheelScrollSensitivity: editor.viewInfo.scrollbar.mouseWheelScrollSensitivity
};
this.scrollbar.updateOptions(newOpts);
}
if (e.layoutInfo) {
this._setLayout();
}
return true;
}
public onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean {
return true;
}
public onThemeChanged(e: viewEvents.ViewThemeChangedEvent): boolean {
this.scrollbar.updateClassName('editor-scrollable' + ' ' + getThemeTypeSelector(this._context.theme.type));
return true;
}
// --- end event handlers
public prepareRender(ctx: RenderingContext): void {
// Nothing to do
}
public render(ctx: RestrictedRenderingContext): void {
this.scrollbar.renderNow();
}
}

View File

@@ -0,0 +1,17 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-editor .glyph-margin {
position: absolute;
top: 0;
}
/*
Keeping name short for faster parsing.
cgmr = core glyph margin rendering (div)
*/
.monaco-editor .margin-view-overlays .cgmr {
position: absolute;
}

View File

@@ -0,0 +1,200 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'vs/css!./glyphMargin';
import { DynamicViewOverlay } from 'vs/editor/browser/view/dynamicViewOverlay';
import { ViewContext } from 'vs/editor/common/view/viewContext';
import { RenderingContext } from 'vs/editor/common/view/renderingContext';
import * as viewEvents from 'vs/editor/common/view/viewEvents';
export class DecorationToRender {
_decorationToRenderBrand: void;
public startLineNumber: number;
public endLineNumber: number;
public className: string;
constructor(startLineNumber: number, endLineNumber: number, className: string) {
this.startLineNumber = +startLineNumber;
this.endLineNumber = +endLineNumber;
this.className = String(className);
}
}
export abstract class DedupOverlay extends DynamicViewOverlay {
protected _render(visibleStartLineNumber: number, visibleEndLineNumber: number, decorations: DecorationToRender[]): string[][] {
let output: string[][] = [];
for (let lineNumber = visibleStartLineNumber; lineNumber <= visibleEndLineNumber; lineNumber++) {
let lineIndex = lineNumber - visibleStartLineNumber;
output[lineIndex] = [];
}
if (decorations.length === 0) {
return output;
}
decorations.sort((a, b) => {
if (a.className === b.className) {
if (a.startLineNumber === b.startLineNumber) {
return a.endLineNumber - b.endLineNumber;
}
return a.startLineNumber - b.startLineNumber;
}
return (a.className < b.className ? -1 : 1);
});
let prevClassName: string = null;
let prevEndLineIndex = 0;
for (let i = 0, len = decorations.length; i < len; i++) {
let d = decorations[i];
let className = d.className;
let startLineIndex = Math.max(d.startLineNumber, visibleStartLineNumber) - visibleStartLineNumber;
let endLineIndex = Math.min(d.endLineNumber, visibleEndLineNumber) - visibleStartLineNumber;
if (prevClassName === className) {
startLineIndex = Math.max(prevEndLineIndex + 1, startLineIndex);
prevEndLineIndex = Math.max(prevEndLineIndex, endLineIndex);
} else {
prevClassName = className;
prevEndLineIndex = endLineIndex;
}
for (let i = startLineIndex; i <= prevEndLineIndex; i++) {
output[i].push(prevClassName);
}
}
return output;
}
}
export class GlyphMarginOverlay extends DedupOverlay {
private _context: ViewContext;
private _lineHeight: number;
private _glyphMargin: boolean;
private _glyphMarginLeft: number;
private _glyphMarginWidth: number;
private _renderResult: string[];
constructor(context: ViewContext) {
super();
this._context = context;
this._lineHeight = this._context.configuration.editor.lineHeight;
this._glyphMargin = this._context.configuration.editor.viewInfo.glyphMargin;
this._glyphMarginLeft = this._context.configuration.editor.layoutInfo.glyphMarginLeft;
this._glyphMarginWidth = this._context.configuration.editor.layoutInfo.glyphMarginWidth;
this._renderResult = null;
this._context.addEventHandler(this);
}
public dispose(): void {
this._context.removeEventHandler(this);
this._context = null;
this._renderResult = null;
super.dispose();
}
// --- begin event handlers
public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean {
if (e.lineHeight) {
this._lineHeight = this._context.configuration.editor.lineHeight;
}
if (e.viewInfo) {
this._glyphMargin = this._context.configuration.editor.viewInfo.glyphMargin;
}
if (e.layoutInfo) {
this._glyphMarginLeft = this._context.configuration.editor.layoutInfo.glyphMarginLeft;
this._glyphMarginWidth = this._context.configuration.editor.layoutInfo.glyphMarginWidth;
}
return true;
}
public onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean {
return true;
}
public onFlushed(e: viewEvents.ViewFlushedEvent): boolean {
return true;
}
public onLinesChanged(e: viewEvents.ViewLinesChangedEvent): boolean {
return true;
}
public onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): boolean {
return true;
}
public onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): boolean {
return true;
}
public onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean {
return e.scrollTopChanged;
}
public onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean {
return true;
}
// --- end event handlers
protected _getDecorations(ctx: RenderingContext): DecorationToRender[] {
let decorations = ctx.getDecorationsInViewport();
let r: DecorationToRender[] = [], rLen = 0;
for (let i = 0, len = decorations.length; i < len; i++) {
let d = decorations[i];
let glyphMarginClassName = d.source.options.glyphMarginClassName;
if (glyphMarginClassName) {
r[rLen++] = new DecorationToRender(d.range.startLineNumber, d.range.endLineNumber, glyphMarginClassName);
}
}
return r;
}
public prepareRender(ctx: RenderingContext): void {
if (!this._glyphMargin) {
this._renderResult = null;
return;
}
let visibleStartLineNumber = ctx.visibleRange.startLineNumber;
let visibleEndLineNumber = ctx.visibleRange.endLineNumber;
let toRender = this._render(visibleStartLineNumber, visibleEndLineNumber, this._getDecorations(ctx));
let lineHeight = this._lineHeight.toString();
let left = this._glyphMarginLeft.toString();
let width = this._glyphMarginWidth.toString();
let common = '" style="left:' + left + 'px;width:' + width + 'px' + ';height:' + lineHeight + 'px;"></div>';
let output: string[] = [];
for (let lineNumber = visibleStartLineNumber; lineNumber <= visibleEndLineNumber; lineNumber++) {
let lineIndex = lineNumber - visibleStartLineNumber;
let classNames = toRender[lineIndex];
if (classNames.length === 0) {
output[lineIndex] = '';
} else {
output[lineIndex] = (
'<div class="cgmr '
+ classNames.join(' ')
+ common
);
}
}
this._renderResult = output;
}
public render(startLineNumber: number, lineNumber: number): string {
if (!this._renderResult) {
return '';
}
let lineIndex = lineNumber - startLineNumber;
if (lineIndex < 0 || lineIndex >= this._renderResult.length) {
throw new Error('Unexpected render request');
}
return this._renderResult[lineIndex];
}
}

View File

@@ -0,0 +1,12 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/*
Keeping name short for faster parsing.
cigr = core ident guides rendering (div)
*/
.monaco-editor .lines-content .cigr {
position: absolute;
}

View File

@@ -0,0 +1,131 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'vs/css!./indentGuides';
import { DynamicViewOverlay } from 'vs/editor/browser/view/dynamicViewOverlay';
import { ViewContext } from 'vs/editor/common/view/viewContext';
import { RenderingContext } from 'vs/editor/common/view/renderingContext';
import * as viewEvents from 'vs/editor/common/view/viewEvents';
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { editorIndentGuides } from 'vs/editor/common/view/editorColorRegistry';
import * as dom from 'vs/base/browser/dom';
import { Position } from 'vs/editor/common/core/position';
export class IndentGuidesOverlay extends DynamicViewOverlay {
private _context: ViewContext;
private _lineHeight: number;
private _spaceWidth: number;
private _renderResult: string[];
private _enabled: boolean;
constructor(context: ViewContext) {
super();
this._context = context;
this._lineHeight = this._context.configuration.editor.lineHeight;
this._spaceWidth = this._context.configuration.editor.fontInfo.spaceWidth;
this._enabled = this._context.configuration.editor.viewInfo.renderIndentGuides;
this._renderResult = null;
this._context.addEventHandler(this);
}
public dispose(): void {
this._context.removeEventHandler(this);
this._context = null;
this._renderResult = null;
super.dispose();
}
// --- begin event handlers
public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean {
if (e.lineHeight) {
this._lineHeight = this._context.configuration.editor.lineHeight;
}
if (e.fontInfo) {
this._spaceWidth = this._context.configuration.editor.fontInfo.spaceWidth;
}
if (e.viewInfo) {
this._enabled = this._context.configuration.editor.viewInfo.renderIndentGuides;
}
return true;
}
public onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean {
// true for inline decorations
return true;
}
public onFlushed(e: viewEvents.ViewFlushedEvent): boolean {
return true;
}
public onLinesChanged(e: viewEvents.ViewLinesChangedEvent): boolean {
return true;
}
public onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): boolean {
return true;
}
public onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): boolean {
return true;
}
public onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean {
return e.scrollTopChanged;// || e.scrollWidthChanged;
}
public onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean {
return true;
}
// --- end event handlers
public prepareRender(ctx: RenderingContext): void {
if (!this._enabled) {
this._renderResult = null;
return;
}
const visibleStartLineNumber = ctx.visibleRange.startLineNumber;
const visibleEndLineNumber = ctx.visibleRange.endLineNumber;
const tabSize = this._context.model.getTabSize();
const tabWidth = tabSize * this._spaceWidth;
const lineHeight = this._lineHeight;
const indentGuideWidth = dom.computeScreenAwareSize(1);
let output: string[] = [];
for (let lineNumber = visibleStartLineNumber; lineNumber <= visibleEndLineNumber; lineNumber++) {
let lineIndex = lineNumber - visibleStartLineNumber;
let indent = this._context.model.getLineIndentGuide(lineNumber);
let result = '';
let leftMostVisiblePosition = ctx.visibleRangeForPosition(new Position(lineNumber, 1));
let left = leftMostVisiblePosition ? leftMostVisiblePosition.left : 0;
for (let i = 0; i < indent; i++) {
result += `<div class="cigr" style="left:${left}px;height:${lineHeight}px;width:${indentGuideWidth}px"></div>`;
left += tabWidth;
}
output[lineIndex] = result;
}
this._renderResult = output;
}
public render(startLineNumber: number, lineNumber: number): string {
if (!this._renderResult) {
return '';
}
let lineIndex = lineNumber - startLineNumber;
if (lineIndex < 0 || lineIndex >= this._renderResult.length) {
throw new Error('Unexpected render request');
}
return this._renderResult[lineIndex];
}
}
registerThemingParticipant((theme, collector) => {
let editorGuideColor = theme.getColor(editorIndentGuides);
if (editorGuideColor) {
collector.addRule(`.monaco-editor .lines-content .cigr { background-color: ${editorGuideColor}; }`);
}
});

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="30" height="42" viewBox="0 0 30 42" style="enable-background:new 0 0 30 42;"><polygon style="fill:#FFFFFF;stroke:#000000;stroke-width:2;" points="29,2.4 3.8,27.6 14.3,27.6 9,38.1 15.4,40.2 20.6,29.7 29,36"/></svg>

After

Width:  |  Height:  |  Size: 277 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="36" viewBox="0 0 24 36.1"><defs><style>.a{fill:#fff;}</style></defs><title>flipped-cursor-mac-2x</title><polygon points="8.6 33.1 11.8 23.9 2.2 23.9 23 2.5 23 31.3 17.4 26.1 14.2 35.1 8.6 33.1"/><path class="a" d="M22,29.1l-5-4.6-3.062,8.938-4.062-1.5L13,23H5L22,5M0,25H10.4l-3,8.3L15,36.1l3.125-7.662L24,33V0Z"/></svg>

After

Width:  |  Height:  |  Size: 378 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="18" viewBox="0 0 12 18"><style>.st0{fill:#fff}</style><title>flipped-cursor-mac</title><path d="M4.3 16.5l1.6-4.6H1.1L11.5 1.2v14.4L8.7 13l-1.6 4.5z"/><path class="st0" d="M11 14.5l-2.5-2.3L7 16.7 5 16l1.6-4.5h-4l8.5-9M0 12.5h5.2l-1.5 4.1L7.5 18 9 14.2l2.9 2.3V0L0 12.5z"/></svg>

After

Width:  |  Height:  |  Size: 338 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="21" x="0px" y="0px" viewBox="0 0 15 21" style="enable-background:new 0 0 15 21;"><polygon style="fill:#FFFFFF;stroke:#000000" points="14.5,1.2 1.9,13.8 7.1,13.8 4.5,19.1 7.7,20.1 10.3,14.9 14.5,18"/></svg>

After

Width:  |  Height:  |  Size: 264 B

View File

@@ -0,0 +1,38 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-editor .margin-view-overlays .line-numbers {
position: absolute;
text-align: right;
display: inline-block;
vertical-align: middle;
box-sizing: border-box;
cursor: default;
height: 100%;
}
.monaco-editor .relative-current-line-number {
text-align: left;
display: inline-block;
width: 100%;
}
.monaco-editor .margin-view-overlays .line-numbers {
cursor: -webkit-image-set(
url('flipped-cursor.svg') 1x,
url('flipped-cursor-2x.svg') 2x
) 30 0, default;
}
.monaco-editor.mac .margin-view-overlays .line-numbers {
cursor: -webkit-image-set(
url('flipped-cursor-mac.svg') 1x,
url('flipped-cursor-mac-2x.svg') 2x
) 24 3, default;
}
.monaco-editor .margin-view-overlays .line-numbers.lh-odd {
margin-top: 1px;
}

View File

@@ -0,0 +1,169 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'vs/css!./lineNumbers';
import { editorLineNumbers } from 'vs/editor/common/view/editorColorRegistry';
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import * as platform from 'vs/base/common/platform';
import { DynamicViewOverlay } from 'vs/editor/browser/view/dynamicViewOverlay';
import { ViewContext } from 'vs/editor/common/view/viewContext';
import { RenderingContext } from 'vs/editor/common/view/renderingContext';
import * as viewEvents from 'vs/editor/common/view/viewEvents';
import { Position } from 'vs/editor/common/core/position';
export class LineNumbersOverlay extends DynamicViewOverlay {
public static CLASS_NAME = 'line-numbers';
private _context: ViewContext;
private _lineHeight: number;
private _renderLineNumbers: boolean;
private _renderCustomLineNumbers: (lineNumber: number) => string;
private _renderRelativeLineNumbers: boolean;
private _lineNumbersLeft: number;
private _lineNumbersWidth: number;
private _lastCursorModelPosition: Position;
private _renderResult: string[];
constructor(context: ViewContext) {
super();
this._context = context;
this._readConfig();
this._lastCursorModelPosition = new Position(1, 1);
this._renderResult = null;
this._context.addEventHandler(this);
}
private _readConfig(): void {
const config = this._context.configuration.editor;
this._lineHeight = config.lineHeight;
this._renderLineNumbers = config.viewInfo.renderLineNumbers;
this._renderCustomLineNumbers = config.viewInfo.renderCustomLineNumbers;
this._renderRelativeLineNumbers = config.viewInfo.renderRelativeLineNumbers;
this._lineNumbersLeft = config.layoutInfo.lineNumbersLeft;
this._lineNumbersWidth = config.layoutInfo.lineNumbersWidth;
}
public dispose(): void {
this._context.removeEventHandler(this);
this._context = null;
this._renderResult = null;
super.dispose();
}
// --- begin event handlers
public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean {
this._readConfig();
return true;
}
public onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean {
const primaryViewPosition = e.selections[0].getPosition();
this._lastCursorModelPosition = this._context.model.coordinatesConverter.convertViewPositionToModelPosition(primaryViewPosition);
if (this._renderRelativeLineNumbers) {
return true;
}
return false;
}
public onFlushed(e: viewEvents.ViewFlushedEvent): boolean {
return true;
}
public onLinesChanged(e: viewEvents.ViewLinesChangedEvent): boolean {
return true;
}
public onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): boolean {
return true;
}
public onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): boolean {
return true;
}
public onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean {
return e.scrollTopChanged;
}
public onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean {
return true;
}
// --- end event handlers
private _getLineRenderLineNumber(viewLineNumber: number): string {
const modelPosition = this._context.model.coordinatesConverter.convertViewPositionToModelPosition(new Position(viewLineNumber, 1));
if (modelPosition.column !== 1) {
return '';
}
let modelLineNumber = modelPosition.lineNumber;
if (this._renderCustomLineNumbers) {
return this._renderCustomLineNumbers(modelLineNumber);
}
if (this._renderRelativeLineNumbers) {
let diff = Math.abs(this._lastCursorModelPosition.lineNumber - modelLineNumber);
if (diff === 0) {
return '<span class="relative-current-line-number">' + modelLineNumber + '</span>';
}
return String(diff);
}
return String(modelLineNumber);
}
public prepareRender(ctx: RenderingContext): void {
if (!this._renderLineNumbers) {
this._renderResult = null;
return;
}
let lineHeightClassName = (platform.isLinux ? (this._lineHeight % 2 === 0 ? ' lh-even' : ' lh-odd') : '');
let visibleStartLineNumber = ctx.visibleRange.startLineNumber;
let visibleEndLineNumber = ctx.visibleRange.endLineNumber;
let common = '<div class="' + LineNumbersOverlay.CLASS_NAME + lineHeightClassName + '" style="left:' + this._lineNumbersLeft.toString() + 'px;width:' + this._lineNumbersWidth.toString() + 'px;">';
let output: string[] = [];
for (let lineNumber = visibleStartLineNumber; lineNumber <= visibleEndLineNumber; lineNumber++) {
let lineIndex = lineNumber - visibleStartLineNumber;
let renderLineNumber = this._getLineRenderLineNumber(lineNumber);
if (renderLineNumber) {
output[lineIndex] = (
common
+ renderLineNumber
+ '</div>'
);
} else {
output[lineIndex] = '';
}
}
this._renderResult = output;
}
public render(startLineNumber: number, lineNumber: number): string {
if (!this._renderResult) {
return '';
}
let lineIndex = lineNumber - startLineNumber;
if (lineIndex < 0 || lineIndex >= this._renderResult.length) {
throw new Error('Unexpected render request');
}
return this._renderResult[lineIndex];
}
}
// theming
registerThemingParticipant((theme, collector) => {
let lineNumbers = theme.getColor(editorLineNumbers);
if (lineNumbers) {
collector.addRule(`.monaco-editor .line-numbers { color: ${lineNumbers}; }`);
}
});

View File

@@ -0,0 +1,146 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { HorizontalRange } from 'vs/editor/common/view/renderingContext';
class FloatHorizontalRange {
_floatHorizontalRangeBrand: void;
public readonly left: number;
public readonly width: number;
constructor(left: number, width: number) {
this.left = left;
this.width = width;
}
public toString(): string {
return `[${this.left},${this.width}]`;
}
public static compare(a: FloatHorizontalRange, b: FloatHorizontalRange): number {
return a.left - b.left;
}
}
export class RangeUtil {
/**
* Reusing the same range here
* because IE is buggy and constantly freezes when using a large number
* of ranges and calling .detach on them
*/
private static _handyReadyRange: Range;
private static _createRange(): Range {
if (!this._handyReadyRange) {
this._handyReadyRange = document.createRange();
}
return this._handyReadyRange;
}
private static _detachRange(range: Range, endNode: HTMLElement): void {
// Move range out of the span node, IE doesn't like having many ranges in
// the same spot and will act badly for lines containing dashes ('-')
range.selectNodeContents(endNode);
}
private static _readClientRects(startElement: Node, startOffset: number, endElement: Node, endOffset: number, endNode: HTMLElement): ClientRectList {
let range = this._createRange();
try {
range.setStart(startElement, startOffset);
range.setEnd(endElement, endOffset);
return range.getClientRects();
} catch (e) {
// This is life ...
return null;
} finally {
this._detachRange(range, endNode);
}
}
private static _mergeAdjacentRanges(ranges: FloatHorizontalRange[]): HorizontalRange[] {
if (ranges.length === 1) {
// There is nothing to merge
return [new HorizontalRange(ranges[0].left, ranges[0].width)];
}
ranges.sort(FloatHorizontalRange.compare);
let result: HorizontalRange[] = [], resultLen = 0;
let prevLeft = ranges[0].left;
let prevWidth = ranges[0].width;
for (let i = 1, len = ranges.length; i < len; i++) {
const range = ranges[i];
const myLeft = range.left;
const myWidth = range.width;
if (prevLeft + prevWidth + 0.9 /* account for browser's rounding errors*/ >= myLeft) {
prevWidth = Math.max(prevWidth, myLeft + myWidth - prevLeft);
} else {
result[resultLen++] = new HorizontalRange(prevLeft, prevWidth);
prevLeft = myLeft;
prevWidth = myWidth;
}
}
result[resultLen++] = new HorizontalRange(prevLeft, prevWidth);
return result;
}
private static _createHorizontalRangesFromClientRects(clientRects: ClientRectList, clientRectDeltaLeft: number): HorizontalRange[] {
if (!clientRects || clientRects.length === 0) {
return null;
}
// We go through FloatHorizontalRange because it has been observed in bi-di text
// that the clientRects are not coming in sorted from the browser
let result: FloatHorizontalRange[] = [];
for (let i = 0, len = clientRects.length; i < len; i++) {
const clientRect = clientRects[i];
result[i] = new FloatHorizontalRange(Math.max(0, clientRect.left - clientRectDeltaLeft), clientRect.width);
}
return this._mergeAdjacentRanges(result);
}
public static readHorizontalRanges(domNode: HTMLElement, startChildIndex: number, startOffset: number, endChildIndex: number, endOffset: number, clientRectDeltaLeft: number, endNode: HTMLElement): HorizontalRange[] {
// Panic check
let min = 0;
let max = domNode.children.length - 1;
if (min > max) {
return null;
}
startChildIndex = Math.min(max, Math.max(min, startChildIndex));
endChildIndex = Math.min(max, Math.max(min, endChildIndex));
// If crossing over to a span only to select offset 0, then use the previous span's maximum offset
// Chrome is buggy and doesn't handle 0 offsets well sometimes.
if (startChildIndex !== endChildIndex) {
if (endChildIndex > 0 && endOffset === 0) {
endChildIndex--;
endOffset = Number.MAX_VALUE;
}
}
let startElement = domNode.children[startChildIndex].firstChild;
let endElement = domNode.children[endChildIndex].firstChild;
if (!startElement || !endElement) {
return null;
}
startOffset = Math.min(startElement.textContent.length, Math.max(0, startOffset));
endOffset = Math.min(endElement.textContent.length, Math.max(0, endOffset));
let clientRects = this._readClientRects(startElement, startOffset, endElement, endOffset, endNode);
return this._createHorizontalRangesFromClientRects(clientRects, clientRectDeltaLeft);
}
}

View File

@@ -0,0 +1,613 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as browser from 'vs/base/browser/browser';
import * as platform from 'vs/base/common/platform';
import * as strings from 'vs/base/common/strings';
import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode';
import { IConfiguration } from 'vs/editor/common/editorCommon';
import { LineDecoration } from 'vs/editor/common/viewLayout/lineDecorations';
import { renderViewLine, RenderLineInput, CharacterMapping } from 'vs/editor/common/viewLayout/viewLineRenderer';
import { IVisibleLine } from 'vs/editor/browser/view/viewLayer';
import { RangeUtil } from 'vs/editor/browser/viewParts/lines/rangeUtil';
import { HorizontalRange } from 'vs/editor/common/view/renderingContext';
import { ViewportData } from 'vs/editor/common/viewLayout/viewLinesViewportData';
import { ThemeType, HIGH_CONTRAST } from 'vs/platform/theme/common/themeService';
import { IStringBuilder } from 'vs/editor/common/core/stringBuilder';
const canUseFastRenderedViewLine = (function () {
if (platform.isNative) {
// In VSCode we know very well when the zoom level changes
return true;
}
if (platform.isLinux || browser.isFirefox || browser.isSafari) {
// On Linux, it appears that zooming affects char widths (in pixels), which is unexpected.
// --
// Even though we read character widths correctly, having read them at a specific zoom level
// does not mean they are the same at the current zoom level.
// --
// This could be improved if we ever figure out how to get an event when browsers zoom,
// but until then we have to stick with reading client rects.
// --
// The same has been observed with Firefox on Windows7
// --
// The same has been oversved with Safari
return false;
}
return true;
})();
const alwaysRenderInlineSelection = (browser.isEdgeOrIE);
export class DomReadingContext {
private readonly _domNode: HTMLElement;
private _clientRectDeltaLeft: number;
private _clientRectDeltaLeftRead: boolean;
public get clientRectDeltaLeft(): number {
if (!this._clientRectDeltaLeftRead) {
this._clientRectDeltaLeftRead = true;
this._clientRectDeltaLeft = this._domNode.getBoundingClientRect().left;
}
return this._clientRectDeltaLeft;
}
public readonly endNode: HTMLElement;
constructor(domNode: HTMLElement, endNode: HTMLElement) {
this._domNode = domNode;
this._clientRectDeltaLeft = 0;
this._clientRectDeltaLeftRead = false;
this.endNode = endNode;
}
}
export class ViewLineOptions {
public readonly themeType: ThemeType;
public readonly renderWhitespace: 'none' | 'boundary' | 'all';
public readonly renderControlCharacters: boolean;
public readonly spaceWidth: number;
public readonly useMonospaceOptimizations: boolean;
public readonly lineHeight: number;
public readonly stopRenderingLineAfter: number;
public readonly fontLigatures: boolean;
constructor(config: IConfiguration, themeType: ThemeType) {
this.themeType = themeType;
this.renderWhitespace = config.editor.viewInfo.renderWhitespace;
this.renderControlCharacters = config.editor.viewInfo.renderControlCharacters;
this.spaceWidth = config.editor.fontInfo.spaceWidth;
this.useMonospaceOptimizations = (
config.editor.fontInfo.isMonospace
&& !config.editor.viewInfo.disableMonospaceOptimizations
);
this.lineHeight = config.editor.lineHeight;
this.stopRenderingLineAfter = config.editor.viewInfo.stopRenderingLineAfter;
this.fontLigatures = config.editor.viewInfo.fontLigatures;
}
public equals(other: ViewLineOptions): boolean {
return (
this.themeType === other.themeType
&& this.renderWhitespace === other.renderWhitespace
&& this.renderControlCharacters === other.renderControlCharacters
&& this.spaceWidth === other.spaceWidth
&& this.useMonospaceOptimizations === other.useMonospaceOptimizations
&& this.lineHeight === other.lineHeight
&& this.stopRenderingLineAfter === other.stopRenderingLineAfter
&& this.fontLigatures === other.fontLigatures
);
}
}
export class ViewLine implements IVisibleLine {
public static CLASS_NAME = 'view-line';
private _options: ViewLineOptions;
private _isMaybeInvalid: boolean;
private _renderedViewLine: IRenderedViewLine;
constructor(options: ViewLineOptions) {
this._options = options;
this._isMaybeInvalid = true;
this._renderedViewLine = null;
}
// --- begin IVisibleLineData
public getDomNode(): HTMLElement {
if (this._renderedViewLine && this._renderedViewLine.domNode) {
return this._renderedViewLine.domNode.domNode;
}
return null;
}
public setDomNode(domNode: HTMLElement): void {
if (this._renderedViewLine) {
this._renderedViewLine.domNode = createFastDomNode(domNode);
} else {
throw new Error('I have no rendered view line to set the dom node to...');
}
}
public onContentChanged(): void {
this._isMaybeInvalid = true;
}
public onTokensChanged(): void {
this._isMaybeInvalid = true;
}
public onDecorationsChanged(): void {
this._isMaybeInvalid = true;
}
public onOptionsChanged(newOptions: ViewLineOptions): void {
this._isMaybeInvalid = true;
this._options = newOptions;
}
public onSelectionChanged(): boolean {
if (alwaysRenderInlineSelection || this._options.themeType === HIGH_CONTRAST) {
this._isMaybeInvalid = true;
return true;
}
return false;
}
public renderLine(lineNumber: number, deltaTop: number, viewportData: ViewportData, sb: IStringBuilder): boolean {
if (this._isMaybeInvalid === false) {
// it appears that nothing relevant has changed
return false;
}
this._isMaybeInvalid = false;
const lineData = viewportData.getViewLineRenderingData(lineNumber);
const options = this._options;
const actualInlineDecorations = LineDecoration.filter(lineData.inlineDecorations, lineNumber, lineData.minColumn, lineData.maxColumn);
if (alwaysRenderInlineSelection || options.themeType === HIGH_CONTRAST) {
const selections = viewportData.selections;
for (let i = 0, len = selections.length; i < len; i++) {
const selection = selections[i];
if (selection.endLineNumber < lineNumber || selection.startLineNumber > lineNumber) {
// Selection does not intersect line
continue;
}
let startColumn = (selection.startLineNumber === lineNumber ? selection.startColumn : lineData.minColumn);
let endColumn = (selection.endLineNumber === lineNumber ? selection.endColumn : lineData.maxColumn);
if (startColumn < endColumn) {
actualInlineDecorations.push(new LineDecoration(startColumn, endColumn, 'inline-selected-text', false));
}
}
}
let renderLineInput = new RenderLineInput(
options.useMonospaceOptimizations,
lineData.content,
lineData.mightContainRTL,
lineData.minColumn - 1,
lineData.tokens,
actualInlineDecorations,
lineData.tabSize,
options.spaceWidth,
options.stopRenderingLineAfter,
options.renderWhitespace,
options.renderControlCharacters,
options.fontLigatures
);
if (this._renderedViewLine && this._renderedViewLine.input.equals(renderLineInput)) {
// no need to do anything, we have the same render input
return false;
}
sb.appendASCIIString('<div style="top:');
sb.appendASCIIString(String(deltaTop));
sb.appendASCIIString('px;height:');
sb.appendASCIIString(String(this._options.lineHeight));
sb.appendASCIIString('px;" class="');
sb.appendASCIIString(ViewLine.CLASS_NAME);
sb.appendASCIIString('">');
const output = renderViewLine(renderLineInput, sb);
sb.appendASCIIString('</div>');
let renderedViewLine: IRenderedViewLine = null;
if (canUseFastRenderedViewLine && options.useMonospaceOptimizations && !output.containsForeignElements) {
let isRegularASCII = true;
if (lineData.mightContainNonBasicASCII) {
isRegularASCII = strings.isBasicASCII(lineData.content);
}
if (isRegularASCII && lineData.content.length < 1000) {
// Browser rounding errors have been observed in Chrome and IE, so using the fast
// view line only for short lines. Please test before removing the length check...
renderedViewLine = new FastRenderedViewLine(
this._renderedViewLine ? this._renderedViewLine.domNode : null,
renderLineInput,
output.characterMapping
);
}
}
if (!renderedViewLine) {
renderedViewLine = createRenderedLine(
this._renderedViewLine ? this._renderedViewLine.domNode : null,
renderLineInput,
output.characterMapping,
output.containsRTL,
output.containsForeignElements
);
}
this._renderedViewLine = renderedViewLine;
return true;
}
public layoutLine(lineNumber: number, deltaTop: number): void {
if (this._renderedViewLine && this._renderedViewLine.domNode) {
this._renderedViewLine.domNode.setTop(deltaTop);
this._renderedViewLine.domNode.setHeight(this._options.lineHeight);
}
}
// --- end IVisibleLineData
public getWidth(): number {
if (!this._renderedViewLine) {
return 0;
}
return this._renderedViewLine.getWidth();
}
public getWidthIsFast(): boolean {
if (!this._renderedViewLine) {
return true;
}
return this._renderedViewLine.getWidthIsFast();
}
public getVisibleRangesForRange(startColumn: number, endColumn: number, context: DomReadingContext): HorizontalRange[] {
startColumn = Math.min(this._renderedViewLine.input.lineContent.length + 1, Math.max(1, startColumn));
endColumn = Math.min(this._renderedViewLine.input.lineContent.length + 1, Math.max(1, endColumn));
return this._renderedViewLine.getVisibleRangesForRange(startColumn, endColumn, context);
}
public getColumnOfNodeOffset(lineNumber: number, spanNode: HTMLElement, offset: number): number {
return this._renderedViewLine.getColumnOfNodeOffset(lineNumber, spanNode, offset);
}
}
interface IRenderedViewLine {
domNode: FastDomNode<HTMLElement>;
readonly input: RenderLineInput;
getWidth(): number;
getWidthIsFast(): boolean;
getVisibleRangesForRange(startColumn: number, endColumn: number, context: DomReadingContext): HorizontalRange[];
getColumnOfNodeOffset(lineNumber: number, spanNode: HTMLElement, offset: number): number;
}
/**
* A rendered line which is guaranteed to contain only regular ASCII and is rendered with a monospace font.
*/
class FastRenderedViewLine implements IRenderedViewLine {
public domNode: FastDomNode<HTMLElement>;
public readonly input: RenderLineInput;
private readonly _characterMapping: CharacterMapping;
private readonly _charWidth: number;
constructor(domNode: FastDomNode<HTMLElement>, renderLineInput: RenderLineInput, characterMapping: CharacterMapping) {
this.domNode = domNode;
this.input = renderLineInput;
this._characterMapping = characterMapping;
this._charWidth = renderLineInput.spaceWidth;
}
public getWidth(): number {
return this._getCharPosition(this._characterMapping.length);
}
public getWidthIsFast(): boolean {
return true;
}
public getVisibleRangesForRange(startColumn: number, endColumn: number, context: DomReadingContext): HorizontalRange[] {
startColumn = startColumn | 0; // @perf
endColumn = endColumn | 0; // @perf
const stopRenderingLineAfter = this.input.stopRenderingLineAfter | 0; // @perf
if (stopRenderingLineAfter !== -1 && startColumn > stopRenderingLineAfter && endColumn > stopRenderingLineAfter) {
// This range is obviously not visible
return null;
}
if (stopRenderingLineAfter !== -1 && startColumn > stopRenderingLineAfter) {
startColumn = stopRenderingLineAfter;
}
if (stopRenderingLineAfter !== -1 && endColumn > stopRenderingLineAfter) {
endColumn = stopRenderingLineAfter;
}
const startPosition = this._getCharPosition(startColumn);
const endPosition = this._getCharPosition(endColumn);
return [new HorizontalRange(startPosition, endPosition - startPosition)];
}
private _getCharPosition(column: number): number {
const charOffset = this._characterMapping.getAbsoluteOffsets();
if (charOffset.length === 0) {
// No characters on this line
return 0;
}
return Math.round(this._charWidth * charOffset[column - 1]);
}
public getColumnOfNodeOffset(lineNumber: number, spanNode: HTMLElement, offset: number): number {
let spanNodeTextContentLength = spanNode.textContent.length;
let spanIndex = -1;
while (spanNode) {
spanNode = <HTMLElement>spanNode.previousSibling;
spanIndex++;
}
let charOffset = this._characterMapping.partDataToCharOffset(spanIndex, spanNodeTextContentLength, offset);
return charOffset + 1;
}
}
/**
* Every time we render a line, we save what we have rendered in an instance of this class.
*/
class RenderedViewLine implements IRenderedViewLine {
public domNode: FastDomNode<HTMLElement>;
public readonly input: RenderLineInput;
protected readonly _characterMapping: CharacterMapping;
private readonly _isWhitespaceOnly: boolean;
private readonly _containsForeignElements: boolean;
private _cachedWidth: number;
/**
* This is a map that is used only when the line is guaranteed to have no RTL text.
*/
private _pixelOffsetCache: Int32Array;
constructor(domNode: FastDomNode<HTMLElement>, renderLineInput: RenderLineInput, characterMapping: CharacterMapping, containsRTL: boolean, containsForeignElements: boolean) {
this.domNode = domNode;
this.input = renderLineInput;
this._characterMapping = characterMapping;
this._isWhitespaceOnly = /^\s*$/.test(renderLineInput.lineContent);
this._containsForeignElements = containsForeignElements;
this._cachedWidth = -1;
this._pixelOffsetCache = null;
if (!containsRTL || this._characterMapping.length === 0 /* the line is empty */) {
this._pixelOffsetCache = new Int32Array(this._characterMapping.length + 1);
for (let column = 0, len = this._characterMapping.length; column <= len; column++) {
this._pixelOffsetCache[column] = -1;
}
}
}
// --- Reading from the DOM methods
protected _getReadingTarget(): HTMLElement {
return <HTMLSpanElement>this.domNode.domNode.firstChild;
}
/**
* Width of the line in pixels
*/
public getWidth(): number {
if (this._cachedWidth === -1) {
this._cachedWidth = this._getReadingTarget().offsetWidth;
}
return this._cachedWidth;
}
public getWidthIsFast(): boolean {
if (this._cachedWidth === -1) {
return false;
}
return true;
}
/**
* Visible ranges for a model range
*/
public getVisibleRangesForRange(startColumn: number, endColumn: number, context: DomReadingContext): HorizontalRange[] {
startColumn = startColumn | 0; // @perf
endColumn = endColumn | 0; // @perf
const stopRenderingLineAfter = this.input.stopRenderingLineAfter | 0; // @perf
if (stopRenderingLineAfter !== -1 && startColumn > stopRenderingLineAfter && endColumn > stopRenderingLineAfter) {
// This range is obviously not visible
return null;
}
if (stopRenderingLineAfter !== -1 && startColumn > stopRenderingLineAfter) {
startColumn = stopRenderingLineAfter;
}
if (stopRenderingLineAfter !== -1 && endColumn > stopRenderingLineAfter) {
endColumn = stopRenderingLineAfter;
}
if (this._pixelOffsetCache !== null) {
// the text is LTR
let startOffset = this._readPixelOffset(startColumn, context);
if (startOffset === -1) {
return null;
}
let endOffset = this._readPixelOffset(endColumn, context);
if (endOffset === -1) {
return null;
}
return [new HorizontalRange(startOffset, endOffset - startOffset)];
}
return this._readVisibleRangesForRange(startColumn, endColumn, context);
}
protected _readVisibleRangesForRange(startColumn: number, endColumn: number, context: DomReadingContext): HorizontalRange[] {
if (startColumn === endColumn) {
let pixelOffset = this._readPixelOffset(startColumn, context);
if (pixelOffset === -1) {
return null;
} else {
return [new HorizontalRange(pixelOffset, 0)];
}
} else {
return this._readRawVisibleRangesForRange(startColumn, endColumn, context);
}
}
protected _readPixelOffset(column: number, context: DomReadingContext): number {
if (this._characterMapping.length === 0) {
// This line has no content
if (!this._containsForeignElements) {
// We can assume the line is really empty
return 0;
}
}
if (this._pixelOffsetCache !== null) {
// the text is LTR
let cachedPixelOffset = this._pixelOffsetCache[column];
if (cachedPixelOffset !== -1) {
return cachedPixelOffset;
}
let result = this._actualReadPixelOffset(column, context);
this._pixelOffsetCache[column] = result;
return result;
}
return this._actualReadPixelOffset(column, context);
}
private _actualReadPixelOffset(column: number, context: DomReadingContext): number {
if (this._characterMapping.length === 0) {
// This line has no content
let r = RangeUtil.readHorizontalRanges(this._getReadingTarget(), 0, 0, 0, 0, context.clientRectDeltaLeft, context.endNode);
if (!r || r.length === 0) {
return -1;
}
return r[0].left;
}
if (column === this._characterMapping.length && this._isWhitespaceOnly && !this._containsForeignElements) {
// This branch helps in the case of whitespace only lines which have a width set
return this.getWidth();
}
let partData = this._characterMapping.charOffsetToPartData(column - 1);
let partIndex = CharacterMapping.getPartIndex(partData);
let charOffsetInPart = CharacterMapping.getCharIndex(partData);
let r = RangeUtil.readHorizontalRanges(this._getReadingTarget(), partIndex, charOffsetInPart, partIndex, charOffsetInPart, context.clientRectDeltaLeft, context.endNode);
if (!r || r.length === 0) {
return -1;
}
return r[0].left;
}
private _readRawVisibleRangesForRange(startColumn: number, endColumn: number, context: DomReadingContext): HorizontalRange[] {
if (startColumn === 1 && endColumn === this._characterMapping.length) {
// This branch helps IE with bidi text & gives a performance boost to other browsers when reading visible ranges for an entire line
return [new HorizontalRange(0, this.getWidth())];
}
let startPartData = this._characterMapping.charOffsetToPartData(startColumn - 1);
let startPartIndex = CharacterMapping.getPartIndex(startPartData);
let startCharOffsetInPart = CharacterMapping.getCharIndex(startPartData);
let endPartData = this._characterMapping.charOffsetToPartData(endColumn - 1);
let endPartIndex = CharacterMapping.getPartIndex(endPartData);
let endCharOffsetInPart = CharacterMapping.getCharIndex(endPartData);
return RangeUtil.readHorizontalRanges(this._getReadingTarget(), startPartIndex, startCharOffsetInPart, endPartIndex, endCharOffsetInPart, context.clientRectDeltaLeft, context.endNode);
}
/**
* Returns the column for the text found at a specific offset inside a rendered dom node
*/
public getColumnOfNodeOffset(lineNumber: number, spanNode: HTMLElement, offset: number): number {
let spanNodeTextContentLength = spanNode.textContent.length;
let spanIndex = -1;
while (spanNode) {
spanNode = <HTMLElement>spanNode.previousSibling;
spanIndex++;
}
let charOffset = this._characterMapping.partDataToCharOffset(spanIndex, spanNodeTextContentLength, offset);
return charOffset + 1;
}
}
class WebKitRenderedViewLine extends RenderedViewLine {
protected _readVisibleRangesForRange(startColumn: number, endColumn: number, context: DomReadingContext): HorizontalRange[] {
let output = super._readVisibleRangesForRange(startColumn, endColumn, context);
if (!output || output.length === 0 || startColumn === endColumn || (startColumn === 1 && endColumn === this._characterMapping.length)) {
return output;
}
// WebKit is buggy and returns an expanded range (to contain words in some cases)
// The last client rect is enlarged (I think)
// This is an attempt to patch things up
// Find position of previous column
let beforeEndPixelOffset = this._readPixelOffset(endColumn - 1, context);
// Find position of last column
let endPixelOffset = this._readPixelOffset(endColumn, context);
if (beforeEndPixelOffset !== -1 && endPixelOffset !== -1) {
let isLTR = (beforeEndPixelOffset <= endPixelOffset);
let lastRange = output[output.length - 1];
if (isLTR && lastRange.left < endPixelOffset) {
// Trim down the width of the last visible range to not go after the last column's position
lastRange.width = endPixelOffset - lastRange.left;
}
}
return output;
}
}
const createRenderedLine: (domNode: FastDomNode<HTMLElement>, renderLineInput: RenderLineInput, characterMapping: CharacterMapping, containsRTL: boolean, containsForeignElements: boolean) => RenderedViewLine = (function () {
if (browser.isWebKit) {
return createWebKitRenderedLine;
}
return createNormalRenderedLine;
})();
function createWebKitRenderedLine(domNode: FastDomNode<HTMLElement>, renderLineInput: RenderLineInput, characterMapping: CharacterMapping, containsRTL: boolean, containsForeignElements: boolean): RenderedViewLine {
return new WebKitRenderedViewLine(domNode, renderLineInput, characterMapping, containsRTL, containsForeignElements);
}
function createNormalRenderedLine(domNode: FastDomNode<HTMLElement>, renderLineInput: RenderLineInput, characterMapping: CharacterMapping, containsRTL: boolean, containsForeignElements: boolean): RenderedViewLine {
return new RenderedViewLine(domNode, renderLineInput, characterMapping, containsRTL, containsForeignElements);
}

View File

@@ -0,0 +1,55 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/* Uncomment to see lines flashing when they're painted */
/*.monaco-editor .view-lines > .view-line {
background-color: none;
animation-name: flash-background;
animation-duration: 800ms;
}
@keyframes flash-background {
0% { background-color: lightgreen; }
100% { background-color: none }
}*/
.monaco-editor .lines-content,
.monaco-editor .view-line,
.monaco-editor .view-lines {
-webkit-user-select: text;
-ms-user-select: text;
-khtml-user-select: text;
-moz-user-select: text;
-o-user-select: text;
user-select: text;
}
.monaco-editor.ie .lines-content,
.monaco-editor.ie .view-line,
.monaco-editor.ie .view-lines {
-ms-user-select: none;
user-select: none;
}
.monaco-editor .view-lines {
cursor: text;
white-space: nowrap;
}
.monaco-editor.vs-dark.mac .view-lines,
.monaco-editor.hc-black.mac .view-lines {
cursor: -webkit-image-set(url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAAL0lEQVQoz2NgCD3x//9/BhBYBWdhgFVAiVW4JBFKGIa4AqD0//9D3pt4I4tAdAMAHTQ/j5Zom30AAAAASUVORK5CYII=') 1x, url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAAz0lEQVRIx2NgYGBY/R8I/vx5eelX3n82IJ9FxGf6tksvf/8FiTMQAcAGQMDvSwu09abffY8QYSAScNk45G198eX//yev73/4///701eh//kZSARckrNBRvz//+8+6ZohwCzjGNjdgQxkAg7B9WADeBjIBqtJCbhRA0YNoIkBSNmaPEMoNmA0FkYNoFKhapJ6FGyAH3nauaSmPfwI0v/3OukVi0CIZ+F25KrtYcx/CTIy0e+rC7R1Z4KMICVTQQ14feVXIbR695u14+Ir4gwAAD49E54wc1kWAAAAAElFTkSuQmCC') 2x) 5 8, text;
}
.monaco-editor .view-line {
position: absolute;
width: 100%;
}
/* TODO@tokenization bootstrap fix */
/*.monaco-editor .view-line > span > span {
float: none;
min-height: inherit;
margin-left: inherit;
}*/

View File

@@ -0,0 +1,689 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'vs/css!./viewLines';
import { RunOnceScheduler } from 'vs/base/common/async';
import { FastDomNode } from 'vs/base/browser/fastDomNode';
import { Range } from 'vs/editor/common/core/range';
import { Position } from 'vs/editor/common/core/position';
import { VisibleLinesCollection, IVisibleLinesHost } from 'vs/editor/browser/view/viewLayer';
import { ViewLineOptions, DomReadingContext, ViewLine } from 'vs/editor/browser/viewParts/lines/viewLine';
import { Configuration } from 'vs/editor/browser/config/configuration';
import { ViewContext } from 'vs/editor/common/view/viewContext';
import { ViewportData } from 'vs/editor/common/viewLayout/viewLinesViewportData';
import { IViewLines, HorizontalRange, LineVisibleRanges } from 'vs/editor/common/view/renderingContext';
import { Viewport } from 'vs/editor/common/viewModel/viewModel';
import { ViewPart, PartFingerprint, PartFingerprints } from 'vs/editor/browser/view/viewPart';
import * as viewEvents from 'vs/editor/common/view/viewEvents';
import { ScrollType } from 'vs/editor/common/editorCommon';
class LastRenderedData {
private _currentVisibleRange: Range;
constructor() {
this._currentVisibleRange = new Range(1, 1, 1, 1);
}
public getCurrentVisibleRange(): Range {
return this._currentVisibleRange;
}
public setCurrentVisibleRange(currentVisibleRange: Range): void {
this._currentVisibleRange = currentVisibleRange;
}
}
class HorizontalRevealRequest {
public readonly lineNumber: number;
public readonly startColumn: number;
public readonly endColumn: number;
public readonly startScrollTop: number;
public readonly stopScrollTop: number;
public readonly scrollType: ScrollType;
constructor(lineNumber: number, startColumn: number, endColumn: number, startScrollTop: number, stopScrollTop: number, scrollType: ScrollType) {
this.lineNumber = lineNumber;
this.startColumn = startColumn;
this.endColumn = endColumn;
this.startScrollTop = startScrollTop;
this.stopScrollTop = stopScrollTop;
this.scrollType = scrollType;
}
}
export class ViewLines extends ViewPart implements IVisibleLinesHost<ViewLine>, IViewLines {
/**
* Adds this ammount of pixels to the right of lines (no-one wants to type near the edge of the viewport)
*/
private static HORIZONTAL_EXTRA_PX = 30;
private readonly _linesContent: FastDomNode<HTMLElement>;
private readonly _textRangeRestingSpot: HTMLElement;
private readonly _visibleLines: VisibleLinesCollection<ViewLine>;
private readonly domNode: FastDomNode<HTMLElement>;
// --- config
private _lineHeight: number;
private _typicalHalfwidthCharacterWidth: number;
private _isViewportWrapping: boolean;
private _revealHorizontalRightPadding: number;
private _canUseLayerHinting: boolean;
private _viewLineOptions: ViewLineOptions;
// --- width
private _maxLineWidth: number;
private _asyncUpdateLineWidths: RunOnceScheduler;
private _horizontalRevealRequest: HorizontalRevealRequest;
private _lastRenderedData: LastRenderedData;
constructor(context: ViewContext, linesContent: FastDomNode<HTMLElement>) {
super(context);
this._linesContent = linesContent;
this._textRangeRestingSpot = document.createElement('div');
this._visibleLines = new VisibleLinesCollection(this);
this.domNode = this._visibleLines.domNode;
const conf = this._context.configuration;
this._lineHeight = conf.editor.lineHeight;
this._typicalHalfwidthCharacterWidth = conf.editor.fontInfo.typicalHalfwidthCharacterWidth;
this._isViewportWrapping = conf.editor.wrappingInfo.isViewportWrapping;
this._revealHorizontalRightPadding = conf.editor.viewInfo.revealHorizontalRightPadding;
this._canUseLayerHinting = conf.editor.canUseLayerHinting;
this._viewLineOptions = new ViewLineOptions(conf, this._context.theme.type);
PartFingerprints.write(this.domNode, PartFingerprint.ViewLines);
this.domNode.setClassName('view-lines');
Configuration.applyFontInfo(this.domNode, conf.editor.fontInfo);
// --- width & height
this._maxLineWidth = 0;
this._asyncUpdateLineWidths = new RunOnceScheduler(() => {
this._updateLineWidthsSlow();
}, 200);
this._lastRenderedData = new LastRenderedData();
this._horizontalRevealRequest = null;
}
public dispose(): void {
this._asyncUpdateLineWidths.dispose();
super.dispose();
}
public getDomNode(): FastDomNode<HTMLElement> {
return this.domNode;
}
// ---- begin IVisibleLinesHost
public createVisibleLine(): ViewLine {
return new ViewLine(this._viewLineOptions);
}
// ---- end IVisibleLinesHost
// ---- begin view event handlers
public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean {
this._visibleLines.onConfigurationChanged(e);
if (e.wrappingInfo) {
this._maxLineWidth = 0;
}
const conf = this._context.configuration;
if (e.lineHeight) {
this._lineHeight = conf.editor.lineHeight;
}
if (e.fontInfo) {
this._typicalHalfwidthCharacterWidth = conf.editor.fontInfo.typicalHalfwidthCharacterWidth;
}
if (e.wrappingInfo) {
this._isViewportWrapping = conf.editor.wrappingInfo.isViewportWrapping;
}
if (e.viewInfo) {
this._revealHorizontalRightPadding = conf.editor.viewInfo.revealHorizontalRightPadding;
}
if (e.canUseLayerHinting) {
this._canUseLayerHinting = conf.editor.canUseLayerHinting;
}
if (e.fontInfo) {
Configuration.applyFontInfo(this.domNode, conf.editor.fontInfo);
}
this._onOptionsMaybeChanged();
if (e.layoutInfo) {
this._maxLineWidth = 0;
}
return true;
}
private _onOptionsMaybeChanged(): boolean {
const conf = this._context.configuration;
let newViewLineOptions = new ViewLineOptions(conf, this._context.theme.type);
if (!this._viewLineOptions.equals(newViewLineOptions)) {
this._viewLineOptions = newViewLineOptions;
let startLineNumber = this._visibleLines.getStartLineNumber();
let endLineNumber = this._visibleLines.getEndLineNumber();
for (let lineNumber = startLineNumber; lineNumber <= endLineNumber; lineNumber++) {
let line = this._visibleLines.getVisibleLine(lineNumber);
line.onOptionsChanged(this._viewLineOptions);
}
return true;
}
return false;
}
public onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean {
let rendStartLineNumber = this._visibleLines.getStartLineNumber();
let rendEndLineNumber = this._visibleLines.getEndLineNumber();
let r = false;
for (let lineNumber = rendStartLineNumber; lineNumber <= rendEndLineNumber; lineNumber++) {
r = this._visibleLines.getVisibleLine(lineNumber).onSelectionChanged() || r;
}
return r;
}
public onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean {
if (true/*e.inlineDecorationsChanged*/) {
let rendStartLineNumber = this._visibleLines.getStartLineNumber();
let rendEndLineNumber = this._visibleLines.getEndLineNumber();
for (let lineNumber = rendStartLineNumber; lineNumber <= rendEndLineNumber; lineNumber++) {
this._visibleLines.getVisibleLine(lineNumber).onDecorationsChanged();
}
}
return true;
}
public onFlushed(e: viewEvents.ViewFlushedEvent): boolean {
let shouldRender = this._visibleLines.onFlushed(e);
this._maxLineWidth = 0;
return shouldRender;
}
public onLinesChanged(e: viewEvents.ViewLinesChangedEvent): boolean {
return this._visibleLines.onLinesChanged(e);
}
public onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): boolean {
return this._visibleLines.onLinesDeleted(e);
}
public onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): boolean {
return this._visibleLines.onLinesInserted(e);
}
public onRevealRangeRequest(e: viewEvents.ViewRevealRangeRequestEvent): boolean {
// 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.range, e.verticalType);
// validate the new desired scroll top
let newScrollPosition = this._context.viewLayout.validateScrollPosition({ scrollTop: desiredScrollTop });
if (e.revealHorizontal) {
if (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 {
// We don't necessarily know the horizontal offset of this range since the line might not be in the view...
this._horizontalRevealRequest = new HorizontalRevealRequest(e.range.startLineNumber, e.range.startColumn, e.range.endColumn, this._context.viewLayout.getCurrentScrollTop(), newScrollPosition.scrollTop, e.scrollType);
}
} else {
this._horizontalRevealRequest = null;
}
const scrollTopDelta = Math.abs(this._context.viewLayout.getCurrentScrollTop() - newScrollPosition.scrollTop);
if (e.scrollType === ScrollType.Smooth && scrollTopDelta > this._lineHeight) {
this._context.viewLayout.setScrollPositionSmooth(newScrollPosition);
} else {
this._context.viewLayout.setScrollPositionNow(newScrollPosition);
}
return true;
}
public onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean {
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;
}
public onTokensChanged(e: viewEvents.ViewTokensChangedEvent): boolean {
return this._visibleLines.onTokensChanged(e);
}
public onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean {
return this._visibleLines.onZonesChanged(e);
}
public onThemeChanged(e: viewEvents.ViewThemeChangedEvent): boolean {
return this._onOptionsMaybeChanged();
}
// ---- end view event handlers
// ----------- HELPERS FOR OTHERS
public getPositionFromDOMInfo(spanNode: HTMLElement, offset: number): Position {
let viewLineDomNode = this._getViewLineDomNode(spanNode);
if (viewLineDomNode === null) {
// Couldn't find view line node
return null;
}
let lineNumber = this._getLineNumberFor(viewLineDomNode);
if (lineNumber === -1) {
// Couldn't find view line node
return null;
}
if (lineNumber < 1 || lineNumber > this._context.model.getLineCount()) {
// lineNumber is outside range
return null;
}
if (this._context.model.getLineMaxColumn(lineNumber) === 1) {
// Line is empty
return new Position(lineNumber, 1);
}
let rendStartLineNumber = this._visibleLines.getStartLineNumber();
let rendEndLineNumber = this._visibleLines.getEndLineNumber();
if (lineNumber < rendStartLineNumber || lineNumber > rendEndLineNumber) {
// Couldn't find line
return null;
}
let column = this._visibleLines.getVisibleLine(lineNumber).getColumnOfNodeOffset(lineNumber, spanNode, offset);
let minColumn = this._context.model.getLineMinColumn(lineNumber);
if (column < minColumn) {
column = minColumn;
}
return new Position(lineNumber, column);
}
private _getViewLineDomNode(node: HTMLElement): HTMLElement {
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.
*/
private _getLineNumberFor(domNode: HTMLElement): number {
let startLineNumber = this._visibleLines.getStartLineNumber();
let endLineNumber = this._visibleLines.getEndLineNumber();
for (let lineNumber = startLineNumber; lineNumber <= endLineNumber; lineNumber++) {
let line = this._visibleLines.getVisibleLine(lineNumber);
if (domNode === line.getDomNode()) {
return lineNumber;
}
}
return -1;
}
public getLineWidth(lineNumber: number): number {
let rendStartLineNumber = this._visibleLines.getStartLineNumber();
let rendEndLineNumber = this._visibleLines.getEndLineNumber();
if (lineNumber < rendStartLineNumber || lineNumber > rendEndLineNumber) {
// Couldn't find line
return -1;
}
return this._visibleLines.getVisibleLine(lineNumber).getWidth();
}
public linesVisibleRangesForRange(range: Range, includeNewLines: boolean): LineVisibleRanges[] {
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;
}
let originalEndLineNumber = range.endLineNumber;
range = Range.intersectRanges(range, this._lastRenderedData.getCurrentVisibleRange());
if (!range) {
return null;
}
let visibleRanges: LineVisibleRanges[] = [], visibleRangesLen = 0;
let domReadingContext = new DomReadingContext(this.domNode.domNode, this._textRangeRestingSpot);
let nextLineModelLineNumber: number;
if (includeNewLines) {
nextLineModelLineNumber = this._context.model.coordinatesConverter.convertViewPositionToModelPosition(new Position(range.startLineNumber, 1)).lineNumber;
}
let rendStartLineNumber = this._visibleLines.getStartLineNumber();
let rendEndLineNumber = this._visibleLines.getEndLineNumber();
for (let lineNumber = range.startLineNumber; lineNumber <= range.endLineNumber; lineNumber++) {
if (lineNumber < rendStartLineNumber || lineNumber > rendEndLineNumber) {
continue;
}
let startColumn = lineNumber === range.startLineNumber ? range.startColumn : 1;
let endColumn = lineNumber === range.endLineNumber ? range.endColumn : this._context.model.getLineMaxColumn(lineNumber);
let visibleRangesForLine = this._visibleLines.getVisibleLine(lineNumber).getVisibleRangesForRange(startColumn, endColumn, domReadingContext);
if (!visibleRangesForLine || visibleRangesForLine.length === 0) {
continue;
}
if (includeNewLines && lineNumber < originalEndLineNumber) {
let currentLineModelLineNumber = nextLineModelLineNumber;
nextLineModelLineNumber = this._context.model.coordinatesConverter.convertViewPositionToModelPosition(new Position(lineNumber + 1, 1)).lineNumber;
if (currentLineModelLineNumber !== nextLineModelLineNumber) {
visibleRangesForLine[visibleRangesForLine.length - 1].width += this._typicalHalfwidthCharacterWidth;
}
}
visibleRanges[visibleRangesLen++] = new LineVisibleRanges(lineNumber, visibleRangesForLine);
}
if (visibleRangesLen === 0) {
return null;
}
return visibleRanges;
}
public visibleRangesForRange2(range: Range): HorizontalRange[] {
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;
}
range = Range.intersectRanges(range, this._lastRenderedData.getCurrentVisibleRange());
if (!range) {
return null;
}
let result: HorizontalRange[] = [];
let domReadingContext = new DomReadingContext(this.domNode.domNode, this._textRangeRestingSpot);
let rendStartLineNumber = this._visibleLines.getStartLineNumber();
let rendEndLineNumber = this._visibleLines.getEndLineNumber();
for (let lineNumber = range.startLineNumber; lineNumber <= range.endLineNumber; lineNumber++) {
if (lineNumber < rendStartLineNumber || lineNumber > rendEndLineNumber) {
continue;
}
let startColumn = lineNumber === range.startLineNumber ? range.startColumn : 1;
let endColumn = lineNumber === range.endLineNumber ? range.endColumn : this._context.model.getLineMaxColumn(lineNumber);
let visibleRangesForLine = this._visibleLines.getVisibleLine(lineNumber).getVisibleRangesForRange(startColumn, endColumn, domReadingContext);
if (!visibleRangesForLine || visibleRangesForLine.length === 0) {
continue;
}
result = result.concat(visibleRangesForLine);
}
if (result.length === 0) {
return null;
}
return result;
}
// --- implementation
/**
* 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).
*/
private _updateLineWidthsFast(): boolean {
return this._updateLineWidths(true);
}
private _updateLineWidthsSlow(): void {
this._updateLineWidths(false);
}
private _updateLineWidths(fast: boolean): boolean {
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());
}
if (allWidthsComputed && rendStartLineNumber === 1 && rendEndLineNumber === this._context.model.getLineCount()) {
// we know the max line width for all the lines
this._maxLineWidth = 0;
}
this._ensureMaxLineWidth(localMaxLineWidth);
return allWidthsComputed;
}
public prepareRender(): void {
throw new Error('Not supported');
}
public render(): void {
throw new Error('Not supported');
}
public renderText(viewportData: ViewportData): void {
// (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 revealLineNumber = this._horizontalRevealRequest.lineNumber;
const revealStartColumn = this._horizontalRevealRequest.startColumn;
const revealEndColumn = this._horizontalRevealRequest.endColumn;
const scrollType = this._horizontalRevealRequest.scrollType;
// Check that we have the line that contains the horizontal range in the viewport
if (viewportData.startLineNumber <= revealLineNumber && revealLineNumber <= viewportData.endLineNumber) {
this._horizontalRevealRequest = null;
// allow `visibleRangesForRange2` to work
this.onDidRender();
// compute new scroll position
let newScrollLeft = this._computeScrollLeftToRevealRange(revealLineNumber, revealStartColumn, revealEndColumn);
let isViewportWrapping = this._isViewportWrapping;
if (!isViewportWrapping) {
// ensure `scrollWidth` is large enough
this._ensureMaxLineWidth(newScrollLeft.maxHorizontalOffset);
}
// set `scrollLeft`
if (scrollType === ScrollType.Smooth) {
this._context.viewLayout.setScrollPositionSmooth({
scrollLeft: newScrollLeft.scrollLeft
});
} else {
this._context.viewLayout.setScrollPositionNow({
scrollLeft: newScrollLeft.scrollLeft
});
}
}
}
// (3) handle scrolling
this._linesContent.setLayerHinting(this._canUseLayerHinting);
const adjustedScrollTop = this._context.viewLayout.getCurrentScrollTop() - viewportData.bigNumbersDelta;
this._linesContent.setTop(-adjustedScrollTop);
this._linesContent.setLeft(-this._context.viewLayout.getCurrentScrollLeft());
// 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();
}
}
// --- width
private _ensureMaxLineWidth(lineWidth: number): void {
let iLineWidth = Math.ceil(lineWidth);
if (this._maxLineWidth < iLineWidth) {
this._maxLineWidth = iLineWidth;
this._context.viewLayout.onMaxLineWidthChanged(this._maxLineWidth);
}
}
private _computeScrollTopToRevealRange(viewport: Viewport, range: Range, verticalType: viewEvents.VerticalRevealType): number {
let viewportStartY = viewport.top;
let viewportHeight = viewport.height;
let viewportEndY = viewportStartY + viewportHeight;
let boxStartY: number;
let boxEndY: number;
// Have a box that includes one extra line height (for the horizontal scrollbar)
boxStartY = this._context.viewLayout.getVerticalOffsetForLineNumber(range.startLineNumber);
boxEndY = this._context.viewLayout.getVerticalOffsetForLineNumber(range.endLineNumber) + this._lineHeight;
if (verticalType === viewEvents.VerticalRevealType.Simple || verticalType === 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
boxEndY += this._lineHeight;
}
let newScrollTop: number;
if (verticalType === viewEvents.VerticalRevealType.Center || verticalType === viewEvents.VerticalRevealType.CenterIfOutsideViewport) {
if (verticalType === 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
let boxMiddleY = (boxStartY + boxEndY) / 2;
newScrollTop = Math.max(0, boxMiddleY - viewportHeight / 2);
}
} else {
newScrollTop = this._computeMinimumScrolling(viewportStartY, viewportEndY, boxStartY, boxEndY, verticalType === viewEvents.VerticalRevealType.Top, verticalType === viewEvents.VerticalRevealType.Bottom);
}
return newScrollTop;
}
private _computeScrollLeftToRevealRange(lineNumber: number, startColumn: number, endColumn: number): { scrollLeft: number; maxHorizontalOffset: number; } {
let maxHorizontalOffset = 0;
let viewport = this._context.viewLayout.getCurrentViewport();
let viewportStartX = viewport.left;
let viewportEndX = viewportStartX + viewport.width;
let visibleRanges = this.visibleRangesForRange2(new Range(lineNumber, startColumn, lineNumber, endColumn));
let boxStartX = Number.MAX_VALUE;
let boxEndX = 0;
if (!visibleRanges) {
// Unknown
return {
scrollLeft: viewportStartX,
maxHorizontalOffset: maxHorizontalOffset
};
}
for (let i = 0; i < visibleRanges.length; i++) {
let visibleRange = visibleRanges[i];
if (visibleRange.left < boxStartX) {
boxStartX = visibleRange.left;
}
if (visibleRange.left + visibleRange.width > boxEndX) {
boxEndX = visibleRange.left + visibleRange.width;
}
}
maxHorizontalOffset = boxEndX;
boxStartX = Math.max(0, boxStartX - ViewLines.HORIZONTAL_EXTRA_PX);
boxEndX += this._revealHorizontalRightPadding;
let newScrollLeft = this._computeMinimumScrolling(viewportStartX, viewportEndX, boxStartX, boxEndX);
return {
scrollLeft: newScrollLeft,
maxHorizontalOffset: maxHorizontalOffset
};
}
private _computeMinimumScrolling(viewportStart: number, viewportEnd: number, boxStart: number, boxEnd: number, revealAtStart?: boolean, revealAtEnd?: boolean): number {
viewportStart = viewportStart | 0;
viewportEnd = viewportEnd | 0;
boxStart = boxStart | 0;
boxEnd = boxEnd | 0;
revealAtStart = !!revealAtStart;
revealAtEnd = !!revealAtEnd;
let viewportLength = viewportEnd - viewportStart;
let 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;
}
}

View File

@@ -0,0 +1,18 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-editor .lines-decorations {
position: absolute;
top: 0;
background: white;
}
/*
Keeping name short for faster parsing.
cldr = core lines decorations rendering (div)
*/
.monaco-editor .margin-view-overlays .cldr {
position: absolute;
height: 100%;
}

View File

@@ -0,0 +1,113 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'vs/css!./linesDecorations';
import { DecorationToRender, DedupOverlay } from 'vs/editor/browser/viewParts/glyphMargin/glyphMargin';
import { ViewContext } from 'vs/editor/common/view/viewContext';
import { RenderingContext } from 'vs/editor/common/view/renderingContext';
import * as viewEvents from 'vs/editor/common/view/viewEvents';
export class LinesDecorationsOverlay extends DedupOverlay {
private _context: ViewContext;
private _decorationsLeft: number;
private _decorationsWidth: number;
private _renderResult: string[];
constructor(context: ViewContext) {
super();
this._context = context;
this._decorationsLeft = this._context.configuration.editor.layoutInfo.decorationsLeft;
this._decorationsWidth = this._context.configuration.editor.layoutInfo.decorationsWidth;
this._renderResult = null;
this._context.addEventHandler(this);
}
public dispose(): void {
this._context.removeEventHandler(this);
this._context = null;
this._renderResult = null;
super.dispose();
}
// --- begin event handlers
public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean {
if (e.layoutInfo) {
this._decorationsLeft = this._context.configuration.editor.layoutInfo.decorationsLeft;
this._decorationsWidth = this._context.configuration.editor.layoutInfo.decorationsWidth;
}
return true;
}
public onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean {
return true;
}
public onFlushed(e: viewEvents.ViewFlushedEvent): boolean {
return true;
}
public onLinesChanged(e: viewEvents.ViewLinesChangedEvent): boolean {
return true;
}
public onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): boolean {
return true;
}
public onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): boolean {
return true;
}
public onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean {
return e.scrollTopChanged;
}
public onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean {
return true;
}
// --- end event handlers
protected _getDecorations(ctx: RenderingContext): DecorationToRender[] {
let decorations = ctx.getDecorationsInViewport();
let r: DecorationToRender[] = [], rLen = 0;
for (let i = 0, len = decorations.length; i < len; i++) {
let d = decorations[i];
let linesDecorationsClassName = d.source.options.linesDecorationsClassName;
if (linesDecorationsClassName) {
r[rLen++] = new DecorationToRender(d.range.startLineNumber, d.range.endLineNumber, linesDecorationsClassName);
}
}
return r;
}
public prepareRender(ctx: RenderingContext): void {
let visibleStartLineNumber = ctx.visibleRange.startLineNumber;
let visibleEndLineNumber = ctx.visibleRange.endLineNumber;
let toRender = this._render(visibleStartLineNumber, visibleEndLineNumber, this._getDecorations(ctx));
let left = this._decorationsLeft.toString();
let width = this._decorationsWidth.toString();
let common = '" style="left:' + left + 'px;width:' + width + 'px;"></div>';
let output: string[] = [];
for (let lineNumber = visibleStartLineNumber; lineNumber <= visibleEndLineNumber; lineNumber++) {
let lineIndex = lineNumber - visibleStartLineNumber;
let classNames = toRender[lineIndex];
let lineOutput = '';
for (let i = 0, len = classNames.length; i < len; i++) {
lineOutput += '<div class="cldr ' + classNames[i] + common;
}
output[lineIndex] = lineOutput;
}
this._renderResult = output;
}
public render(startLineNumber: number, lineNumber: number): string {
if (!this._renderResult) {
return '';
}
return this._renderResult[lineNumber - startLineNumber];
}
}

View File

@@ -0,0 +1,95 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode';
import { ViewPart } from 'vs/editor/browser/view/viewPart';
import { ViewContext } from 'vs/editor/common/view/viewContext';
import { RenderingContext, RestrictedRenderingContext } from 'vs/editor/common/view/renderingContext';
import * as viewEvents from 'vs/editor/common/view/viewEvents';
export class Margin extends ViewPart {
public static CLASS_NAME = 'glyph-margin';
private _domNode: FastDomNode<HTMLElement>;
private _canUseLayerHinting: boolean;
private _contentLeft: number;
private _glyphMarginLeft: number;
private _glyphMarginWidth: number;
private _glyphMarginBackgroundDomNode: FastDomNode<HTMLElement>;
constructor(context: ViewContext) {
super(context);
this._canUseLayerHinting = this._context.configuration.editor.canUseLayerHinting;
this._contentLeft = this._context.configuration.editor.layoutInfo.contentLeft;
this._glyphMarginLeft = this._context.configuration.editor.layoutInfo.glyphMarginLeft;
this._glyphMarginWidth = this._context.configuration.editor.layoutInfo.glyphMarginWidth;
this._domNode = this._createDomNode();
}
public dispose(): void {
super.dispose();
}
public getDomNode(): FastDomNode<HTMLElement> {
return this._domNode;
}
private _createDomNode(): FastDomNode<HTMLElement> {
let domNode = createFastDomNode(document.createElement('div'));
domNode.setClassName('margin');
domNode.setPosition('absolute');
domNode.setAttribute('role', 'presentation');
domNode.setAttribute('aria-hidden', 'true');
this._glyphMarginBackgroundDomNode = createFastDomNode(document.createElement('div'));
this._glyphMarginBackgroundDomNode.setClassName(Margin.CLASS_NAME);
domNode.appendChild(this._glyphMarginBackgroundDomNode);
return domNode;
}
// --- begin event handlers
public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean {
if (e.canUseLayerHinting) {
this._canUseLayerHinting = this._context.configuration.editor.canUseLayerHinting;
}
if (e.layoutInfo) {
this._contentLeft = this._context.configuration.editor.layoutInfo.contentLeft;
this._glyphMarginLeft = this._context.configuration.editor.layoutInfo.glyphMarginLeft;
this._glyphMarginWidth = this._context.configuration.editor.layoutInfo.glyphMarginWidth;
}
return true;
}
public onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean {
return super.onScrollChanged(e) || e.scrollTopChanged;
}
// --- end event handlers
public prepareRender(ctx: RenderingContext): void {
// Nothing to read
}
public render(ctx: RestrictedRenderingContext): void {
this._domNode.setLayerHinting(this._canUseLayerHinting);
const adjustedScrollTop = ctx.scrollTop - ctx.bigNumbersDelta;
this._domNode.setTop(-adjustedScrollTop);
let height = Math.min(ctx.scrollHeight, 1000000);
this._domNode.setHeight(height);
this._domNode.setWidth(this._contentLeft);
this._glyphMarginBackgroundDomNode.setLeft(this._glyphMarginLeft);
this._glyphMarginBackgroundDomNode.setWidth(this._glyphMarginWidth);
this._glyphMarginBackgroundDomNode.setHeight(height);
}
}

View File

@@ -0,0 +1,15 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/*
Keeping name short for faster parsing.
cmdr = core margin decorations rendering (div)
*/
.monaco-editor .margin-view-overlays .cmdr {
position: absolute;
left: 0;
width: 100%;
height: 100%;
}

View File

@@ -0,0 +1,99 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'vs/css!./marginDecorations';
import { DecorationToRender, DedupOverlay } from 'vs/editor/browser/viewParts/glyphMargin/glyphMargin';
import { ViewContext } from 'vs/editor/common/view/viewContext';
import { RenderingContext } from 'vs/editor/common/view/renderingContext';
import * as viewEvents from 'vs/editor/common/view/viewEvents';
export class MarginViewLineDecorationsOverlay extends DedupOverlay {
private _context: ViewContext;
private _renderResult: string[];
constructor(context: ViewContext) {
super();
this._context = context;
this._renderResult = null;
this._context.addEventHandler(this);
}
public dispose(): void {
this._context.removeEventHandler(this);
this._context = null;
this._renderResult = null;
super.dispose();
}
// --- begin event handlers
public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean {
return true;
}
public onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean {
return true;
}
public onFlushed(e: viewEvents.ViewFlushedEvent): boolean {
return true;
}
public onLinesChanged(e: viewEvents.ViewLinesChangedEvent): boolean {
return true;
}
public onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): boolean {
return true;
}
public onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): boolean {
return true;
}
public onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean {
return e.scrollTopChanged;
}
public onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean {
return true;
}
// --- end event handlers
protected _getDecorations(ctx: RenderingContext): DecorationToRender[] {
let decorations = ctx.getDecorationsInViewport();
let r: DecorationToRender[] = [], rLen = 0;
for (let i = 0, len = decorations.length; i < len; i++) {
let d = decorations[i];
let marginClassName = d.source.options.marginClassName;
if (marginClassName) {
r[rLen++] = new DecorationToRender(d.range.startLineNumber, d.range.endLineNumber, marginClassName);
}
}
return r;
}
public prepareRender(ctx: RenderingContext): void {
let visibleStartLineNumber = ctx.visibleRange.startLineNumber;
let visibleEndLineNumber = ctx.visibleRange.endLineNumber;
let toRender = this._render(visibleStartLineNumber, visibleEndLineNumber, this._getDecorations(ctx));
let output: string[] = [];
for (let lineNumber = visibleStartLineNumber; lineNumber <= visibleEndLineNumber; lineNumber++) {
let lineIndex = lineNumber - visibleStartLineNumber;
let classNames = toRender[lineIndex];
let lineOutput = '';
for (let i = 0, len = classNames.length; i < len; i++) {
lineOutput += '<div class="cmdr ' + classNames[i] + '" style=""></div>';
}
output[lineIndex] = lineOutput;
}
this._renderResult = output;
}
public render(startLineNumber: number, lineNumber: number): string {
if (!this._renderResult) {
return '';
}
return this._renderResult[lineNumber - startLineNumber];
}
}

View File

@@ -0,0 +1,27 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/* START cover the case that slider is visible on mouseover */
.monaco-editor .minimap.slider-mouseover .minimap-slider {
opacity: 0;
transition: opacity 100ms linear;
}
.monaco-editor .minimap.slider-mouseover:hover .minimap-slider {
opacity: 1;
}
.monaco-editor .minimap.slider-mouseover .minimap-slider.active {
opacity: 1;
}
/* END cover the case that slider is visible on mouseover */
.monaco-editor .minimap-shadow-hidden {
position: absolute;
width: 0;
}
.monaco-editor .minimap-shadow-visible {
position: absolute;
left: -6px;
width: 6px;
}

View File

@@ -0,0 +1,911 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'vs/css!./minimap';
import { ViewPart, PartFingerprint, PartFingerprints } from 'vs/editor/browser/view/viewPart';
import { ViewContext } from 'vs/editor/common/view/viewContext';
import { RenderingContext, RestrictedRenderingContext } from 'vs/editor/common/view/renderingContext';
import { getOrCreateMinimapCharRenderer } from 'vs/editor/common/view/runtimeMinimapCharRenderer';
import * as dom from 'vs/base/browser/dom';
import { MinimapCharRenderer, MinimapTokensColorTracker, Constants } from 'vs/editor/common/view/minimapCharRenderer';
import * as editorCommon from 'vs/editor/common/editorCommon';
import { CharCode } from 'vs/base/common/charCode';
import { ViewLineData } from 'vs/editor/common/viewModel/viewModel';
import { ColorId } from 'vs/editor/common/modes';
import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode';
import { IDisposable } from 'vs/base/common/lifecycle';
import { RenderedLinesCollection, ILine } from 'vs/editor/browser/view/viewLayer';
import { Range } from 'vs/editor/common/core/range';
import { RGBA8 } from 'vs/editor/common/core/rgba';
import * as viewEvents from 'vs/editor/common/view/viewEvents';
import { GlobalMouseMoveMonitor, IStandardMouseMoveEventData, standardMouseMoveMerger } from 'vs/base/browser/globalMouseMoveMonitor';
import * as platform from 'vs/base/common/platform';
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { scrollbarSliderBackground, scrollbarSliderHoverBackground, scrollbarSliderActiveBackground, scrollbarShadow } from 'vs/platform/theme/common/colorRegistry';
const enum RenderMinimap {
None = 0,
Small = 1,
Large = 2,
SmallBlocks = 3,
LargeBlocks = 4,
}
function getMinimapLineHeight(renderMinimap: RenderMinimap): number {
if (renderMinimap === RenderMinimap.Large) {
return Constants.x2_CHAR_HEIGHT;
}
if (renderMinimap === RenderMinimap.LargeBlocks) {
return Constants.x2_CHAR_HEIGHT + 2;
}
if (renderMinimap === RenderMinimap.Small) {
return Constants.x1_CHAR_HEIGHT;
}
// RenderMinimap.SmallBlocks
return Constants.x1_CHAR_HEIGHT + 1;
}
function getMinimapCharWidth(renderMinimap: RenderMinimap): number {
if (renderMinimap === RenderMinimap.Large) {
return Constants.x2_CHAR_WIDTH;
}
if (renderMinimap === RenderMinimap.LargeBlocks) {
return Constants.x2_CHAR_WIDTH;
}
if (renderMinimap === RenderMinimap.Small) {
return Constants.x1_CHAR_WIDTH;
}
// RenderMinimap.SmallBlocks
return Constants.x1_CHAR_WIDTH;
}
/**
* The orthogonal distance to the slider at which dragging "resets". This implements "snapping"
*/
const MOUSE_DRAG_RESET_DISTANCE = 140;
class MinimapOptions {
public readonly renderMinimap: RenderMinimap;
public readonly scrollBeyondLastLine: boolean;
public readonly showSlider: 'always' | 'mouseover';
public readonly pixelRatio: number;
public readonly typicalHalfwidthCharacterWidth: number;
public readonly lineHeight: number;
/**
* container dom node width (in CSS px)
*/
public readonly minimapWidth: number;
/**
* container dom node height (in CSS px)
*/
public readonly minimapHeight: number;
/**
* canvas backing store width (in device px)
*/
public readonly canvasInnerWidth: number;
/**
* canvas backing store height (in device px)
*/
public readonly canvasInnerHeight: number;
/**
* canvas width (in CSS px)
*/
public readonly canvasOuterWidth: number;
/**
* canvas height (in CSS px)
*/
public readonly canvasOuterHeight: number;
constructor(configuration: editorCommon.IConfiguration) {
const pixelRatio = configuration.editor.pixelRatio;
const layoutInfo = configuration.editor.layoutInfo;
const viewInfo = configuration.editor.viewInfo;
const fontInfo = configuration.editor.fontInfo;
this.renderMinimap = layoutInfo.renderMinimap | 0;
this.scrollBeyondLastLine = viewInfo.scrollBeyondLastLine;
this.showSlider = viewInfo.minimap.showSlider;
this.pixelRatio = pixelRatio;
this.typicalHalfwidthCharacterWidth = fontInfo.typicalHalfwidthCharacterWidth;
this.lineHeight = configuration.editor.lineHeight;
this.minimapWidth = layoutInfo.minimapWidth;
this.minimapHeight = layoutInfo.height;
this.canvasInnerWidth = Math.max(1, Math.floor(pixelRatio * this.minimapWidth));
this.canvasInnerHeight = Math.max(1, Math.floor(pixelRatio * this.minimapHeight));
this.canvasOuterWidth = this.canvasInnerWidth / pixelRatio;
this.canvasOuterHeight = this.canvasInnerHeight / pixelRatio;
}
public equals(other: MinimapOptions): boolean {
return (this.renderMinimap === other.renderMinimap
&& this.scrollBeyondLastLine === other.scrollBeyondLastLine
&& this.showSlider === other.showSlider
&& this.pixelRatio === other.pixelRatio
&& this.typicalHalfwidthCharacterWidth === other.typicalHalfwidthCharacterWidth
&& this.lineHeight === other.lineHeight
&& this.minimapWidth === other.minimapWidth
&& this.minimapHeight === other.minimapHeight
&& this.canvasInnerWidth === other.canvasInnerWidth
&& this.canvasInnerHeight === other.canvasInnerHeight
&& this.canvasOuterWidth === other.canvasOuterWidth
&& this.canvasOuterHeight === other.canvasOuterHeight
);
}
}
class MinimapLayout {
/**
* The given editor scrollTop (input).
*/
public readonly scrollTop: number;
/**
* The given editor scrollHeight (input).
*/
public readonly scrollHeight: number;
private readonly _computedSliderRatio: number;
/**
* slider dom node top (in CSS px)
*/
public readonly sliderTop: number;
/**
* slider dom node height (in CSS px)
*/
public readonly sliderHeight: number;
/**
* minimap render start line number.
*/
public readonly startLineNumber: number;
/**
* minimap render end line number.
*/
public readonly endLineNumber: number;
constructor(
scrollTop: number,
scrollHeight: number,
computedSliderRatio: number,
sliderTop: number,
sliderHeight: number,
startLineNumber: number,
endLineNumber: number
) {
this.scrollTop = scrollTop;
this.scrollHeight = scrollHeight;
this._computedSliderRatio = computedSliderRatio;
this.sliderTop = sliderTop;
this.sliderHeight = sliderHeight;
this.startLineNumber = startLineNumber;
this.endLineNumber = endLineNumber;
}
/**
* Compute a desired `scrollPosition` such that the slider moves by `delta`.
*/
public getDesiredScrollTopFromDelta(delta: number): number {
let desiredSliderPosition = this.sliderTop + delta;
return Math.round(desiredSliderPosition / this._computedSliderRatio);
}
public static create(
options: MinimapOptions,
viewportStartLineNumber: number,
viewportEndLineNumber: number,
viewportHeight: number,
viewportContainsWhitespaceGaps: boolean,
lineCount: number,
scrollTop: number,
scrollHeight: number,
previousLayout: MinimapLayout
): MinimapLayout {
const pixelRatio = options.pixelRatio;
const minimapLineHeight = getMinimapLineHeight(options.renderMinimap);
const minimapLinesFitting = Math.floor(options.canvasInnerHeight / minimapLineHeight);
const lineHeight = options.lineHeight;
// The visible line count in a viewport can change due to a number of reasons:
// a) with the same viewport width, different scroll positions can result in partial lines being visible:
// e.g. for a line height of 20, and a viewport height of 600
// * scrollTop = 0 => visible lines are [1, 30]
// * scrollTop = 10 => visible lines are [1, 31] (with lines 1 and 31 partially visible)
// * scrollTop = 20 => visible lines are [2, 31]
// b) whitespace gaps might make their way in the viewport (which results in a decrease in the visible line count)
// c) we could be in the scroll beyond last line case (which also results in a decrease in the visible line count, down to possibly only one line being visible)
// We must first establish a desirable slider height.
let sliderHeight: number;
if (viewportContainsWhitespaceGaps && viewportEndLineNumber !== lineCount) {
// case b) from above: there are whitespace gaps in the viewport.
// In this case, the height of the slider directly reflects the visible line count.
const viewportLineCount = viewportEndLineNumber - viewportStartLineNumber + 1;
sliderHeight = Math.floor(viewportLineCount * minimapLineHeight / pixelRatio);
} else {
// The slider has a stable height
const expectedViewportLineCount = viewportHeight / lineHeight;
sliderHeight = Math.floor(expectedViewportLineCount * minimapLineHeight / pixelRatio);
}
let maxMinimapSliderTop: number;
if (options.scrollBeyondLastLine) {
// The minimap slider, when dragged all the way down, will contain the last line at its top
maxMinimapSliderTop = (lineCount - 1) * minimapLineHeight / pixelRatio;
} else {
// The minimap slider, when dragged all the way down, will contain the last line at its bottom
maxMinimapSliderTop = Math.max(0, lineCount * minimapLineHeight / pixelRatio - sliderHeight);
}
maxMinimapSliderTop = Math.min(options.minimapHeight - sliderHeight, maxMinimapSliderTop);
// The slider can move from 0 to `maxMinimapSliderTop`
// in the same way `scrollTop` can move from 0 to `scrollHeight` - `viewportHeight`.
const computedSliderRatio = (maxMinimapSliderTop) / (scrollHeight - viewportHeight);
const sliderTop = (scrollTop * computedSliderRatio);
if (minimapLinesFitting >= lineCount) {
// All lines fit in the minimap
const startLineNumber = 1;
const endLineNumber = lineCount;
return new MinimapLayout(scrollTop, scrollHeight, computedSliderRatio, sliderTop, sliderHeight, startLineNumber, endLineNumber);
} else {
let startLineNumber = Math.max(1, Math.floor(viewportStartLineNumber - sliderTop * pixelRatio / minimapLineHeight));
// Avoid flickering caused by a partial viewport start line
// by being consistent w.r.t. the previous layout decision
if (previousLayout && previousLayout.scrollHeight === scrollHeight) {
if (previousLayout.scrollTop > scrollTop) {
// Scrolling up => never increase `startLineNumber`
startLineNumber = Math.min(startLineNumber, previousLayout.startLineNumber);
}
if (previousLayout.scrollTop < scrollTop) {
// Scrolling down => never decrease `startLineNumber`
startLineNumber = Math.max(startLineNumber, previousLayout.startLineNumber);
}
}
const endLineNumber = Math.min(lineCount, startLineNumber + minimapLinesFitting - 1);
return new MinimapLayout(scrollTop, scrollHeight, computedSliderRatio, sliderTop, sliderHeight, startLineNumber, endLineNumber);
}
}
}
class MinimapLine implements ILine {
public static INVALID = new MinimapLine(-1);
dy: number;
constructor(dy: number) {
this.dy = dy;
}
public onContentChanged(): void {
this.dy = -1;
}
public onTokensChanged(): void {
this.dy = -1;
}
}
class RenderData {
/**
* last rendered layout.
*/
public readonly renderedLayout: MinimapLayout;
private readonly _imageData: ImageData;
private readonly _renderedLines: RenderedLinesCollection<MinimapLine>;
constructor(
renderedLayout: MinimapLayout,
imageData: ImageData,
lines: MinimapLine[]
) {
this.renderedLayout = renderedLayout;
this._imageData = imageData;
this._renderedLines = new RenderedLinesCollection(
() => MinimapLine.INVALID
);
this._renderedLines._set(renderedLayout.startLineNumber, lines);
}
/**
* Check if the current RenderData matches accurately the new desired layout and no painting is needed.
*/
public linesEquals(layout: MinimapLayout): boolean {
if (this.renderedLayout.startLineNumber !== layout.startLineNumber) {
return false;
}
if (this.renderedLayout.endLineNumber !== layout.endLineNumber) {
return false;
}
const tmp = this._renderedLines._get();
const lines = tmp.lines;
for (let i = 0, len = lines.length; i < len; i++) {
if (lines[i].dy === -1) {
// This line is invalid
return false;
}
}
return true;
}
_get(): { imageData: ImageData; rendLineNumberStart: number; lines: MinimapLine[]; } {
let tmp = this._renderedLines._get();
return {
imageData: this._imageData,
rendLineNumberStart: tmp.rendLineNumberStart,
lines: tmp.lines
};
}
public onLinesChanged(e: viewEvents.ViewLinesChangedEvent): boolean {
return this._renderedLines.onLinesChanged(e.fromLineNumber, e.toLineNumber);
}
public onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): void {
this._renderedLines.onLinesDeleted(e.fromLineNumber, e.toLineNumber);
}
public onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): void {
this._renderedLines.onLinesInserted(e.fromLineNumber, e.toLineNumber);
}
public onTokensChanged(e: viewEvents.ViewTokensChangedEvent): boolean {
return this._renderedLines.onTokensChanged(e.ranges);
}
}
/**
* Some sort of double buffering.
*
* Keeps two buffers around that will be rotated for painting.
* Always gives a buffer that is filled with the background color.
*/
class MinimapBuffers {
private readonly _backgroundFillData: Uint8ClampedArray;
private readonly _buffers: [ImageData, ImageData];
private _lastUsedBuffer: number;
constructor(ctx: CanvasRenderingContext2D, WIDTH: number, HEIGHT: number, background: RGBA8) {
this._backgroundFillData = MinimapBuffers._createBackgroundFillData(WIDTH, HEIGHT, background);
this._buffers = [
ctx.createImageData(WIDTH, HEIGHT),
ctx.createImageData(WIDTH, HEIGHT)
];
this._lastUsedBuffer = 0;
}
public getBuffer(): ImageData {
// rotate buffers
this._lastUsedBuffer = 1 - this._lastUsedBuffer;
let result = this._buffers[this._lastUsedBuffer];
// fill with background color
result.data.set(this._backgroundFillData);
return result;
}
private static _createBackgroundFillData(WIDTH: number, HEIGHT: number, background: RGBA8): Uint8ClampedArray {
const backgroundR = background.r;
const backgroundG = background.g;
const backgroundB = background.b;
let result = new Uint8ClampedArray(WIDTH * HEIGHT * 4);
let offset = 0;
for (let i = 0; i < HEIGHT; i++) {
for (let j = 0; j < WIDTH; j++) {
result[offset] = backgroundR;
result[offset + 1] = backgroundG;
result[offset + 2] = backgroundB;
result[offset + 3] = 255;
offset += 4;
}
}
return result;
}
}
export class Minimap extends ViewPart {
private readonly _domNode: FastDomNode<HTMLElement>;
private readonly _shadow: FastDomNode<HTMLElement>;
private readonly _canvas: FastDomNode<HTMLCanvasElement>;
private readonly _slider: FastDomNode<HTMLElement>;
private readonly _sliderHorizontal: FastDomNode<HTMLElement>;
private readonly _tokensColorTracker: MinimapTokensColorTracker;
private readonly _mouseDownListener: IDisposable;
private readonly _sliderMouseMoveMonitor: GlobalMouseMoveMonitor<IStandardMouseMoveEventData>;
private readonly _sliderMouseDownListener: IDisposable;
private readonly _minimapCharRenderer: MinimapCharRenderer;
private _options: MinimapOptions;
private _lastRenderData: RenderData;
private _buffers: MinimapBuffers;
constructor(context: ViewContext) {
super(context);
this._options = new MinimapOptions(this._context.configuration);
this._lastRenderData = null;
this._buffers = null;
this._domNode = createFastDomNode(document.createElement('div'));
PartFingerprints.write(this._domNode, PartFingerprint.Minimap);
this._domNode.setClassName(this._getMinimapDomNodeClassName());
this._domNode.setPosition('absolute');
this._domNode.setAttribute('role', 'presentation');
this._domNode.setAttribute('aria-hidden', 'true');
this._domNode.setRight(this._context.configuration.editor.layoutInfo.verticalScrollbarWidth);
this._shadow = createFastDomNode(document.createElement('div'));
this._shadow.setClassName('minimap-shadow-hidden');
this._domNode.appendChild(this._shadow);
this._canvas = createFastDomNode(document.createElement('canvas'));
this._canvas.setPosition('absolute');
this._canvas.setLeft(0);
this._domNode.appendChild(this._canvas);
this._slider = createFastDomNode(document.createElement('div'));
this._slider.setPosition('absolute');
this._slider.setClassName('minimap-slider');
this._slider.setLayerHinting(true);
this._domNode.appendChild(this._slider);
this._sliderHorizontal = createFastDomNode(document.createElement('div'));
this._sliderHorizontal.setPosition('absolute');
this._sliderHorizontal.setClassName('minimap-slider-horizontal');
this._slider.appendChild(this._sliderHorizontal);
this._tokensColorTracker = MinimapTokensColorTracker.getInstance();
this._minimapCharRenderer = getOrCreateMinimapCharRenderer();
this._applyLayout();
this._mouseDownListener = dom.addStandardDisposableListener(this._canvas.domNode, 'mousedown', (e) => {
e.preventDefault();
const renderMinimap = this._options.renderMinimap;
if (renderMinimap === RenderMinimap.None) {
return;
}
if (!this._lastRenderData) {
return;
}
const minimapLineHeight = getMinimapLineHeight(renderMinimap);
const internalOffsetY = this._options.pixelRatio * e.browserEvent.offsetY;
const lineIndex = Math.floor(internalOffsetY / minimapLineHeight);
let lineNumber = lineIndex + this._lastRenderData.renderedLayout.startLineNumber;
lineNumber = Math.min(lineNumber, this._context.model.getLineCount());
this._context.privateViewEventBus.emit(new viewEvents.ViewRevealRangeRequestEvent(
new Range(lineNumber, 1, lineNumber, 1),
viewEvents.VerticalRevealType.Center,
false,
editorCommon.ScrollType.Smooth
));
});
this._sliderMouseMoveMonitor = new GlobalMouseMoveMonitor<IStandardMouseMoveEventData>();
this._sliderMouseDownListener = dom.addStandardDisposableListener(this._slider.domNode, 'mousedown', (e) => {
e.preventDefault();
if (e.leftButton && this._lastRenderData) {
const initialMousePosition = e.posy;
const initialMouseOrthogonalPosition = e.posx;
const initialSliderState = this._lastRenderData.renderedLayout;
this._slider.toggleClassName('active', true);
this._sliderMouseMoveMonitor.startMonitoring(
standardMouseMoveMerger,
(mouseMoveData: IStandardMouseMoveEventData) => {
const mouseOrthogonalDelta = Math.abs(mouseMoveData.posx - initialMouseOrthogonalPosition);
if (platform.isWindows && mouseOrthogonalDelta > MOUSE_DRAG_RESET_DISTANCE) {
// The mouse has wondered away from the scrollbar => reset dragging
this._context.viewLayout.setScrollPositionNow({
scrollTop: initialSliderState.scrollTop
});
return;
}
const mouseDelta = mouseMoveData.posy - initialMousePosition;
this._context.viewLayout.setScrollPositionNow({
scrollTop: initialSliderState.getDesiredScrollTopFromDelta(mouseDelta)
});
},
() => {
this._slider.toggleClassName('active', false);
}
);
}
});
}
public dispose(): void {
this._mouseDownListener.dispose();
this._sliderMouseMoveMonitor.dispose();
this._sliderMouseDownListener.dispose();
super.dispose();
}
private _getMinimapDomNodeClassName(): string {
if (this._options.showSlider === 'always') {
return 'minimap slider-always';
}
return 'minimap slider-mouseover';
}
public getDomNode(): FastDomNode<HTMLElement> {
return this._domNode;
}
private _applyLayout(): void {
this._domNode.setWidth(this._options.minimapWidth);
this._domNode.setHeight(this._options.minimapHeight);
this._shadow.setHeight(this._options.minimapHeight);
this._canvas.setWidth(this._options.canvasOuterWidth);
this._canvas.setHeight(this._options.canvasOuterHeight);
this._canvas.domNode.width = this._options.canvasInnerWidth;
this._canvas.domNode.height = this._options.canvasInnerHeight;
this._slider.setWidth(this._options.minimapWidth);
}
private _getBuffer(): ImageData {
if (!this._buffers) {
this._buffers = new MinimapBuffers(
this._canvas.domNode.getContext('2d'),
this._options.canvasInnerWidth,
this._options.canvasInnerHeight,
this._tokensColorTracker.getColor(ColorId.DefaultBackground)
);
}
return this._buffers.getBuffer();
}
private _onOptionsMaybeChanged(): boolean {
let opts = new MinimapOptions(this._context.configuration);
if (this._options.equals(opts)) {
return false;
}
this._options = opts;
this._lastRenderData = null;
this._buffers = null;
this._applyLayout();
this._domNode.setClassName(this._getMinimapDomNodeClassName());
return true;
}
// ---- begin view event handlers
public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean {
return this._onOptionsMaybeChanged();
}
public onFlushed(e: viewEvents.ViewFlushedEvent): boolean {
this._lastRenderData = null;
return true;
}
public onLinesChanged(e: viewEvents.ViewLinesChangedEvent): boolean {
if (this._lastRenderData) {
return this._lastRenderData.onLinesChanged(e);
}
return false;
}
public onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): boolean {
if (this._lastRenderData) {
this._lastRenderData.onLinesDeleted(e);
}
return true;
}
public onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): boolean {
if (this._lastRenderData) {
this._lastRenderData.onLinesInserted(e);
}
return true;
}
public onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean {
return true;
}
public onTokensChanged(e: viewEvents.ViewTokensChangedEvent): boolean {
if (this._lastRenderData) {
return this._lastRenderData.onTokensChanged(e);
}
return false;
}
public onTokensColorsChanged(e: viewEvents.ViewTokensColorsChangedEvent): boolean {
this._lastRenderData = null;
this._buffers = null;
return true;
}
public onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean {
this._lastRenderData = null;
return true;
}
// --- end event handlers
public prepareRender(ctx: RenderingContext): void {
// Nothing to read
}
public render(renderingCtx: RestrictedRenderingContext): void {
const renderMinimap = this._options.renderMinimap;
if (renderMinimap === RenderMinimap.None) {
this._shadow.setClassName('minimap-shadow-hidden');
return;
}
if (renderingCtx.scrollLeft + renderingCtx.viewportWidth >= renderingCtx.scrollWidth) {
this._shadow.setClassName('minimap-shadow-hidden');
} else {
this._shadow.setClassName('minimap-shadow-visible');
}
const layout = MinimapLayout.create(
this._options,
renderingCtx.visibleRange.startLineNumber,
renderingCtx.visibleRange.endLineNumber,
renderingCtx.viewportHeight,
(renderingCtx.viewportData.whitespaceViewportData.length > 0),
this._context.model.getLineCount(),
renderingCtx.scrollTop,
renderingCtx.scrollHeight,
this._lastRenderData ? this._lastRenderData.renderedLayout : null
);
this._slider.setTop(layout.sliderTop);
this._slider.setHeight(layout.sliderHeight);
// Compute horizontal slider coordinates
const scrollLeftChars = renderingCtx.scrollLeft / this._options.typicalHalfwidthCharacterWidth;
const horizontalSliderLeft = Math.min(this._options.minimapWidth, Math.round(scrollLeftChars * getMinimapCharWidth(this._options.renderMinimap) / this._options.pixelRatio));
this._sliderHorizontal.setLeft(horizontalSliderLeft);
this._sliderHorizontal.setWidth(this._options.minimapWidth - horizontalSliderLeft);
this._sliderHorizontal.setTop(0);
this._sliderHorizontal.setHeight(layout.sliderHeight);
this._lastRenderData = this.renderLines(layout);
}
private renderLines(layout: MinimapLayout): RenderData {
const renderMinimap = this._options.renderMinimap;
const startLineNumber = layout.startLineNumber;
const endLineNumber = layout.endLineNumber;
const minimapLineHeight = getMinimapLineHeight(renderMinimap);
// Check if nothing changed w.r.t. lines from last frame
if (this._lastRenderData && this._lastRenderData.linesEquals(layout)) {
const _lastData = this._lastRenderData._get();
// Nice!! Nothing changed from last frame
return new RenderData(layout, _lastData.imageData, _lastData.lines);
}
// Oh well!! We need to repaint some lines...
const imageData = this._getBuffer();
// Render untouched lines by using last rendered data.
let needed = Minimap._renderUntouchedLines(
imageData,
startLineNumber,
endLineNumber,
minimapLineHeight,
this._lastRenderData
);
// Fetch rendering info from view model for rest of lines that need rendering.
const lineInfo = this._context.model.getMinimapLinesRenderingData(startLineNumber, endLineNumber, needed);
const tabSize = lineInfo.tabSize;
const background = this._tokensColorTracker.getColor(ColorId.DefaultBackground);
const useLighterFont = this._tokensColorTracker.backgroundIsLight();
// Render the rest of lines
let dy = 0;
let renderedLines: MinimapLine[] = [];
for (let lineIndex = 0, lineCount = endLineNumber - startLineNumber + 1; lineIndex < lineCount; lineIndex++) {
if (needed[lineIndex]) {
Minimap._renderLine(
imageData,
background,
useLighterFont,
renderMinimap,
this._tokensColorTracker,
this._minimapCharRenderer,
dy,
tabSize,
lineInfo.data[lineIndex]
);
}
renderedLines[lineIndex] = new MinimapLine(dy);
dy += minimapLineHeight;
}
// Finally, paint to the canvas
const ctx = this._canvas.domNode.getContext('2d');
ctx.putImageData(imageData, 0, 0);
// Save rendered data for reuse on next frame if possible
return new RenderData(
layout,
imageData,
renderedLines
);
}
private static _renderUntouchedLines(
target: ImageData,
startLineNumber: number,
endLineNumber: number,
minimapLineHeight: number,
lastRenderData: RenderData,
): boolean[] {
let needed: boolean[] = [];
if (!lastRenderData) {
for (let i = 0, len = endLineNumber - startLineNumber + 1; i < len; i++) {
needed[i] = true;
}
return needed;
}
const _lastData = lastRenderData._get();
const lastTargetData = _lastData.imageData.data;
const lastStartLineNumber = _lastData.rendLineNumberStart;
const lastLines = _lastData.lines;
const lastLinesLength = lastLines.length;
const WIDTH = target.width;
const targetData = target.data;
let copySourceStart = -1;
let copySourceEnd = -1;
let copyDestStart = -1;
let copyDestEnd = -1;
let dest_dy = 0;
for (let lineNumber = startLineNumber; lineNumber <= endLineNumber; lineNumber++) {
const lineIndex = lineNumber - startLineNumber;
const lastLineIndex = lineNumber - lastStartLineNumber;
const source_dy = (lastLineIndex >= 0 && lastLineIndex < lastLinesLength ? lastLines[lastLineIndex].dy : -1);
if (source_dy === -1) {
needed[lineIndex] = true;
dest_dy += minimapLineHeight;
continue;
}
let sourceStart = source_dy * WIDTH * 4;
let sourceEnd = (source_dy + minimapLineHeight) * WIDTH * 4;
let destStart = dest_dy * WIDTH * 4;
let destEnd = (dest_dy + minimapLineHeight) * WIDTH * 4;
if (copySourceEnd === sourceStart && copyDestEnd === destStart) {
// contiguous zone => extend copy request
copySourceEnd = sourceEnd;
copyDestEnd = destEnd;
} else {
if (copySourceStart !== -1) {
// flush existing copy request
targetData.set(lastTargetData.subarray(copySourceStart, copySourceEnd), copyDestStart);
}
copySourceStart = sourceStart;
copySourceEnd = sourceEnd;
copyDestStart = destStart;
copyDestEnd = destEnd;
}
needed[lineIndex] = false;
dest_dy += minimapLineHeight;
}
if (copySourceStart !== -1) {
// flush existing copy request
targetData.set(lastTargetData.subarray(copySourceStart, copySourceEnd), copyDestStart);
}
return needed;
}
private static _renderLine(
target: ImageData,
backgroundColor: RGBA8,
useLighterFont: boolean,
renderMinimap: RenderMinimap,
colorTracker: MinimapTokensColorTracker,
minimapCharRenderer: MinimapCharRenderer,
dy: number,
tabSize: number,
lineData: ViewLineData
): void {
const content = lineData.content;
const tokens = lineData.tokens;
const charWidth = getMinimapCharWidth(renderMinimap);
const maxDx = target.width - charWidth;
let dx = 0;
let charIndex = 0;
let tabsCharDelta = 0;
for (let tokenIndex = 0, tokensLen = tokens.length; tokenIndex < tokensLen; tokenIndex++) {
const token = tokens[tokenIndex];
const tokenEndIndex = token.endIndex;
const tokenColorId = token.getForeground();
const tokenColor = colorTracker.getColor(tokenColorId);
for (; charIndex < tokenEndIndex; charIndex++) {
if (dx > maxDx) {
// hit edge of minimap
return;
}
const charCode = content.charCodeAt(charIndex);
if (charCode === CharCode.Tab) {
let insertSpacesCount = tabSize - (charIndex + tabsCharDelta) % tabSize;
tabsCharDelta += insertSpacesCount - 1;
// No need to render anything since tab is invisible
dx += insertSpacesCount * charWidth;
} else if (charCode === CharCode.Space) {
// No need to render anything since space is invisible
dx += charWidth;
} else {
if (renderMinimap === RenderMinimap.Large) {
minimapCharRenderer.x2RenderChar(target, dx, dy, charCode, tokenColor, backgroundColor, useLighterFont);
} else if (renderMinimap === RenderMinimap.Small) {
minimapCharRenderer.x1RenderChar(target, dx, dy, charCode, tokenColor, backgroundColor, useLighterFont);
} else if (renderMinimap === RenderMinimap.LargeBlocks) {
minimapCharRenderer.x2BlockRenderChar(target, dx, dy, tokenColor, backgroundColor, useLighterFont);
} else {
// RenderMinimap.SmallBlocks
minimapCharRenderer.x1BlockRenderChar(target, dx, dy, tokenColor, backgroundColor, useLighterFont);
}
dx += charWidth;
}
}
}
}
}
registerThemingParticipant((theme, collector) => {
const sliderBackground = theme.getColor(scrollbarSliderBackground);
if (sliderBackground) {
const halfSliderBackground = sliderBackground.transparent(0.5);
collector.addRule(`.monaco-editor .minimap-slider, .monaco-editor .minimap-slider .minimap-slider-horizontal { background: ${halfSliderBackground}; }`);
}
const sliderHoverBackground = theme.getColor(scrollbarSliderHoverBackground);
if (sliderHoverBackground) {
const halfSliderHoverBackground = sliderHoverBackground.transparent(0.5);
collector.addRule(`.monaco-editor .minimap-slider:hover, .monaco-editor .minimap-slider:hover .minimap-slider-horizontal { background: ${halfSliderHoverBackground}; }`);
}
const sliderActiveBackground = theme.getColor(scrollbarSliderActiveBackground);
if (sliderActiveBackground) {
const halfSliderActiveBackground = sliderActiveBackground.transparent(0.5);
collector.addRule(`.monaco-editor .minimap-slider.active, .monaco-editor .minimap-slider.active .minimap-slider-horizontal { background: ${halfSliderActiveBackground}; }`);
}
const shadow = theme.getColor(scrollbarShadow);
if (shadow) {
collector.addRule(`.monaco-editor .minimap-shadow-visible { box-shadow: ${shadow} -6px 0 6px -6px inset; }`);
}
});

View File

@@ -0,0 +1,9 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-editor .overlayWidgets {
position: absolute;
top: 0;
left:0;
}

View File

@@ -0,0 +1,152 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'vs/css!./overlayWidgets';
import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode';
import { IOverlayWidget, OverlayWidgetPositionPreference } from 'vs/editor/browser/editorBrowser';
import { ViewPart, PartFingerprint, PartFingerprints } from 'vs/editor/browser/view/viewPart';
import { ViewContext } from 'vs/editor/common/view/viewContext';
import { RenderingContext, RestrictedRenderingContext } from 'vs/editor/common/view/renderingContext';
import * as viewEvents from 'vs/editor/common/view/viewEvents';
interface IWidgetData {
widget: IOverlayWidget;
preference: OverlayWidgetPositionPreference;
domNode: FastDomNode<HTMLElement>;
}
interface IWidgetMap {
[key: string]: IWidgetData;
}
export class ViewOverlayWidgets extends ViewPart {
private _widgets: IWidgetMap;
private _domNode: FastDomNode<HTMLElement>;
private _verticalScrollbarWidth: number;
private _minimapWidth: number;
private _horizontalScrollbarHeight: number;
private _editorHeight: number;
private _editorWidth: number;
constructor(context: ViewContext) {
super(context);
this._widgets = {};
this._verticalScrollbarWidth = this._context.configuration.editor.layoutInfo.verticalScrollbarWidth;
this._minimapWidth = this._context.configuration.editor.layoutInfo.minimapWidth;
this._horizontalScrollbarHeight = this._context.configuration.editor.layoutInfo.horizontalScrollbarHeight;
this._editorHeight = this._context.configuration.editor.layoutInfo.height;
this._editorWidth = this._context.configuration.editor.layoutInfo.width;
this._domNode = createFastDomNode(document.createElement('div'));
PartFingerprints.write(this._domNode, PartFingerprint.OverlayWidgets);
this._domNode.setClassName('overlayWidgets');
}
public dispose(): void {
super.dispose();
this._widgets = null;
}
public getDomNode(): FastDomNode<HTMLElement> {
return this._domNode;
}
// ---- begin view event handlers
public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean {
if (e.layoutInfo) {
this._verticalScrollbarWidth = this._context.configuration.editor.layoutInfo.verticalScrollbarWidth;
this._minimapWidth = this._context.configuration.editor.layoutInfo.minimapWidth;
this._horizontalScrollbarHeight = this._context.configuration.editor.layoutInfo.horizontalScrollbarHeight;
this._editorHeight = this._context.configuration.editor.layoutInfo.height;
this._editorWidth = this._context.configuration.editor.layoutInfo.width;
return true;
}
return false;
}
// ---- end view event handlers
public addWidget(widget: IOverlayWidget): void {
const domNode = createFastDomNode(widget.getDomNode());
this._widgets[widget.getId()] = {
widget: widget,
preference: null,
domNode: domNode
};
// This is sync because a widget wants to be in the dom
domNode.setPosition('absolute');
domNode.setAttribute('widgetId', widget.getId());
this._domNode.appendChild(domNode);
this.setShouldRender();
}
public setWidgetPosition(widget: IOverlayWidget, preference: OverlayWidgetPositionPreference): boolean {
let widgetData = this._widgets[widget.getId()];
if (widgetData.preference === preference) {
return false;
}
widgetData.preference = preference;
this.setShouldRender();
return true;
}
public removeWidget(widget: IOverlayWidget): void {
let widgetId = widget.getId();
if (this._widgets.hasOwnProperty(widgetId)) {
const widgetData = this._widgets[widgetId];
const domNode = widgetData.domNode.domNode;
delete this._widgets[widgetId];
domNode.parentNode.removeChild(domNode);
this.setShouldRender();
}
}
private _renderWidget(widgetData: IWidgetData): void {
const domNode = widgetData.domNode;
if (widgetData.preference === null) {
domNode.unsetTop();
return;
}
if (widgetData.preference === OverlayWidgetPositionPreference.TOP_RIGHT_CORNER) {
domNode.setTop(0);
domNode.setRight((2 * this._verticalScrollbarWidth) + this._minimapWidth);
} else if (widgetData.preference === OverlayWidgetPositionPreference.BOTTOM_RIGHT_CORNER) {
let widgetHeight = domNode.domNode.clientHeight;
domNode.setTop((this._editorHeight - widgetHeight - 2 * this._horizontalScrollbarHeight));
domNode.setRight((2 * this._verticalScrollbarWidth) + this._minimapWidth);
} else if (widgetData.preference === OverlayWidgetPositionPreference.TOP_CENTER) {
domNode.setTop(0);
domNode.domNode.style.right = '50%';
}
}
public prepareRender(ctx: RenderingContext): void {
// Nothing to read
}
public render(ctx: RestrictedRenderingContext): void {
this._domNode.setWidth(this._editorWidth);
let keys = Object.keys(this._widgets);
for (let i = 0, len = keys.length; i < len; i++) {
let widgetId = keys[i];
this._renderWidget(this._widgets[widgetId]);
}
}
}

View File

@@ -0,0 +1,267 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as editorCommon from 'vs/editor/common/editorCommon';
import { ViewPart } from 'vs/editor/browser/view/viewPart';
import { OverviewRulerImpl } from 'vs/editor/browser/viewParts/overviewRuler/overviewRulerImpl';
import { ViewContext } from 'vs/editor/common/view/viewContext';
import { RenderingContext, RestrictedRenderingContext } from 'vs/editor/common/view/renderingContext';
import { Position } from 'vs/editor/common/core/position';
import { TokenizationRegistry } from 'vs/editor/common/modes';
import { IDisposable } from 'vs/base/common/lifecycle';
import * as viewEvents from 'vs/editor/common/view/viewEvents';
import { OverviewRulerZone } from 'vs/editor/common/view/overviewZoneManager';
import { editorOverviewRulerBorder, editorCursorForeground } from 'vs/editor/common/view/editorColorRegistry';
import { Color } from 'vs/base/common/color';
import { ThemeColor } from 'vs/platform/theme/common/themeService';
export class DecorationsOverviewRuler extends ViewPart {
static MIN_DECORATION_HEIGHT = 6;
static MAX_DECORATION_HEIGHT = 60;
private readonly _tokensColorTrackerListener: IDisposable;
private _overviewRuler: OverviewRulerImpl;
private _renderBorder: boolean;
private _borderColor: string;
private _cursorColor: string;
private _shouldUpdateDecorations: boolean;
private _shouldUpdateCursorPosition: boolean;
private _hideCursor: boolean;
private _cursorPositions: Position[];
private _zonesFromDecorations: OverviewRulerZone[];
private _zonesFromCursors: OverviewRulerZone[];
constructor(context: ViewContext) {
super(context);
this._overviewRuler = new OverviewRulerImpl(
1,
'decorationsOverviewRuler',
this._context.viewLayout.getScrollHeight(),
this._context.configuration.editor.lineHeight,
this._context.configuration.editor.pixelRatio,
DecorationsOverviewRuler.MIN_DECORATION_HEIGHT,
DecorationsOverviewRuler.MAX_DECORATION_HEIGHT,
(lineNumber: number) => this._context.viewLayout.getVerticalOffsetForLineNumber(lineNumber)
);
this._overviewRuler.setLanesCount(this._context.configuration.editor.viewInfo.overviewRulerLanes, false);
this._overviewRuler.setLayout(this._context.configuration.editor.layoutInfo.overviewRuler, false);
this._renderBorder = this._context.configuration.editor.viewInfo.overviewRulerBorder;
this._updateColors();
this._updateBackground(false);
this._tokensColorTrackerListener = TokenizationRegistry.onDidChange((e) => {
if (e.changedColorMap) {
this._updateBackground(true);
}
});
this._shouldUpdateDecorations = true;
this._zonesFromDecorations = [];
this._shouldUpdateCursorPosition = true;
this._hideCursor = this._context.configuration.editor.viewInfo.hideCursorInOverviewRuler;
this._zonesFromCursors = [];
this._cursorPositions = [];
}
public dispose(): void {
super.dispose();
this._overviewRuler.dispose();
this._tokensColorTrackerListener.dispose();
}
private _updateBackground(render: boolean): void {
const minimapEnabled = this._context.configuration.editor.viewInfo.minimap.enabled;
this._overviewRuler.setUseBackground((minimapEnabled ? TokenizationRegistry.getDefaultBackground() : null), render);
}
// ---- begin view event handlers
public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean {
let prevLanesCount = this._overviewRuler.getLanesCount();
let newLanesCount = this._context.configuration.editor.viewInfo.overviewRulerLanes;
if (prevLanesCount !== newLanesCount) {
this._overviewRuler.setLanesCount(newLanesCount, false);
}
if (e.lineHeight) {
this._overviewRuler.setLineHeight(this._context.configuration.editor.lineHeight, false);
}
if (e.pixelRatio) {
this._overviewRuler.setPixelRatio(this._context.configuration.editor.pixelRatio, false);
}
if (e.viewInfo) {
this._renderBorder = this._context.configuration.editor.viewInfo.overviewRulerBorder;
this._hideCursor = this._context.configuration.editor.viewInfo.hideCursorInOverviewRuler;
this._shouldUpdateCursorPosition = true;
this._updateBackground(false);
}
if (e.layoutInfo) {
this._overviewRuler.setLayout(this._context.configuration.editor.layoutInfo.overviewRuler, false);
}
return true;
}
public onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean {
this._shouldUpdateCursorPosition = true;
this._cursorPositions = [];
for (let i = 0, len = e.selections.length; i < len; i++) {
this._cursorPositions[i] = e.selections[i].getPosition();
}
return true;
}
public onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean {
this._shouldUpdateDecorations = true;
return true;
}
public onFlushed(e: viewEvents.ViewFlushedEvent): boolean {
this._shouldUpdateCursorPosition = true;
this._shouldUpdateDecorations = true;
return true;
}
public onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean {
this._overviewRuler.setScrollHeight(e.scrollHeight, false);
return super.onScrollChanged(e) || e.scrollHeightChanged;
}
public onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean {
return true;
}
public onThemeChanged(e: viewEvents.ViewThemeChangedEvent): boolean {
this._updateColors();
this._shouldUpdateDecorations = true;
this._shouldUpdateCursorPosition = true;
return true;
}
// ---- end view event handlers
public getDomNode(): HTMLElement {
return this._overviewRuler.getDomNode();
}
private _updateColors() {
let borderColor = this._context.theme.getColor(editorOverviewRulerBorder);
this._borderColor = borderColor ? borderColor.toString() : null;
let cursorColor = this._context.theme.getColor(editorCursorForeground);
this._cursorColor = cursorColor ? cursorColor.transparent(0.7).toString() : null;
this._overviewRuler.setThemeType(this._context.theme.type, false);
}
private _createZonesFromDecorations(): OverviewRulerZone[] {
let decorations = this._context.model.getAllOverviewRulerDecorations();
let zones: OverviewRulerZone[] = [];
for (let i = 0, len = decorations.length; i < len; i++) {
let dec = decorations[i];
let overviewRuler = dec.source.options.overviewRuler;
zones[i] = new OverviewRulerZone(
dec.range.startLineNumber,
dec.range.endLineNumber,
overviewRuler.position,
0,
this.resolveRulerColor(overviewRuler.color),
this.resolveRulerColor(overviewRuler.darkColor),
this.resolveRulerColor(overviewRuler.hcColor)
);
}
return zones;
}
private resolveRulerColor(color: string | ThemeColor): string {
if (editorCommon.isThemeColor(color)) {
let c = this._context.theme.getColor(color.id) || Color.transparent;
return c.toString();
}
return color;
}
private _createZonesFromCursors(): OverviewRulerZone[] {
let zones: OverviewRulerZone[] = [];
for (let i = 0, len = this._cursorPositions.length; i < len; i++) {
let cursor = this._cursorPositions[i];
zones[i] = new OverviewRulerZone(
cursor.lineNumber,
cursor.lineNumber,
editorCommon.OverviewRulerLane.Full,
2,
this._cursorColor,
this._cursorColor,
this._cursorColor
);
}
return zones;
}
public prepareRender(ctx: RenderingContext): void {
// Nothing to read
}
public render(ctx: RestrictedRenderingContext): void {
if (this._shouldUpdateDecorations || this._shouldUpdateCursorPosition) {
if (this._shouldUpdateDecorations) {
this._shouldUpdateDecorations = false;
this._zonesFromDecorations = this._createZonesFromDecorations();
}
if (this._shouldUpdateCursorPosition) {
this._shouldUpdateCursorPosition = false;
if (this._hideCursor) {
this._zonesFromCursors = [];
} else {
this._zonesFromCursors = this._createZonesFromCursors();
}
}
let allZones: OverviewRulerZone[] = [];
allZones = allZones.concat(this._zonesFromCursors);
allZones = allZones.concat(this._zonesFromDecorations);
this._overviewRuler.setZones(allZones, false);
}
let hasRendered = this._overviewRuler.render(false);
if (hasRendered && this._renderBorder && this._borderColor && this._overviewRuler.getLanesCount() > 0 && (this._zonesFromDecorations.length > 0 || this._zonesFromCursors.length > 0)) {
let ctx2 = this._overviewRuler.getDomNode().getContext('2d');
ctx2.beginPath();
ctx2.lineWidth = 1;
ctx2.strokeStyle = this._borderColor;
ctx2.moveTo(0, 0);
ctx2.lineTo(0, this._overviewRuler.getPixelHeight());
ctx2.stroke();
ctx2.moveTo(0, 0);
ctx2.lineTo(this._overviewRuler.getPixelWidth(), 0);
ctx2.stroke();
}
}
}

View File

@@ -0,0 +1,83 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { ViewEventHandler } from 'vs/editor/common/viewModel/viewEventHandler';
import { IOverviewRuler } from 'vs/editor/browser/editorBrowser';
import { OverviewRulerImpl } from 'vs/editor/browser/viewParts/overviewRuler/overviewRulerImpl';
import { ViewContext } from 'vs/editor/common/view/viewContext';
import * as viewEvents from 'vs/editor/common/view/viewEvents';
import { OverviewRulerPosition } from 'vs/editor/common/config/editorOptions';
import { OverviewRulerZone } from 'vs/editor/common/view/overviewZoneManager';
export class OverviewRuler extends ViewEventHandler implements IOverviewRuler {
private _context: ViewContext;
private _overviewRuler: OverviewRulerImpl;
constructor(context: ViewContext, cssClassName: string, minimumHeight: number, maximumHeight: number) {
super();
this._context = context;
this._overviewRuler = new OverviewRulerImpl(
0,
cssClassName,
this._context.viewLayout.getScrollHeight(),
this._context.configuration.editor.lineHeight,
this._context.configuration.editor.pixelRatio,
minimumHeight,
maximumHeight,
(lineNumber: number) => this._context.viewLayout.getVerticalOffsetForLineNumber(lineNumber)
);
this._context.addEventHandler(this);
}
public dispose(): void {
this._context.removeEventHandler(this);
this._overviewRuler.dispose();
super.dispose();
}
// ---- begin view event handlers
public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean {
if (e.lineHeight) {
this._overviewRuler.setLineHeight(this._context.configuration.editor.lineHeight, true);
}
if (e.pixelRatio) {
this._overviewRuler.setPixelRatio(this._context.configuration.editor.pixelRatio, true);
}
return true;
}
public onFlushed(e: viewEvents.ViewFlushedEvent): boolean {
return true;
}
public onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean {
this._overviewRuler.setScrollHeight(e.scrollHeight, true);
return super.onScrollChanged(e) || e.scrollHeightChanged;
}
public onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean {
return true;
}
// ---- end view event handlers
public getDomNode(): HTMLElement {
return this._overviewRuler.getDomNode();
}
public setLayout(position: OverviewRulerPosition): void {
this._overviewRuler.setLayout(position, true);
}
public setZones(zones: OverviewRulerZone[]): void {
this._overviewRuler.setZones(zones, true);
}
}

View File

@@ -0,0 +1,250 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode';
import { OverviewRulerLane } from 'vs/editor/common/editorCommon';
import { OverviewZoneManager, ColorZone, OverviewRulerZone } from 'vs/editor/common/view/overviewZoneManager';
import { Color } from 'vs/base/common/color';
import { OverviewRulerPosition } from 'vs/editor/common/config/editorOptions';
import { ThemeType, LIGHT } from 'vs/platform/theme/common/themeService';
export class OverviewRulerImpl {
private _canvasLeftOffset: number;
private _domNode: FastDomNode<HTMLCanvasElement>;
private _lanesCount: number;
private _zoneManager: OverviewZoneManager;
private _background: Color;
constructor(
canvasLeftOffset: number, cssClassName: string, scrollHeight: number, lineHeight: number,
pixelRatio: number, minimumHeight: number, maximumHeight: number,
getVerticalOffsetForLine: (lineNumber: number) => number
) {
this._canvasLeftOffset = canvasLeftOffset;
this._domNode = createFastDomNode(document.createElement('canvas'));
this._domNode.setClassName(cssClassName);
this._domNode.setPosition('absolute');
this._domNode.setLayerHinting(true);
this._lanesCount = 3;
this._background = null;
this._zoneManager = new OverviewZoneManager(getVerticalOffsetForLine);
this._zoneManager.setMinimumHeight(minimumHeight);
this._zoneManager.setMaximumHeight(maximumHeight);
this._zoneManager.setThemeType(LIGHT);
this._zoneManager.setDOMWidth(0);
this._zoneManager.setDOMHeight(0);
this._zoneManager.setOuterHeight(scrollHeight);
this._zoneManager.setLineHeight(lineHeight);
this._zoneManager.setPixelRatio(pixelRatio);
}
public dispose(): void {
this._zoneManager = null;
}
public setLayout(position: OverviewRulerPosition, render: boolean): void {
this._domNode.setTop(position.top);
this._domNode.setRight(position.right);
let hasChanged = false;
hasChanged = this._zoneManager.setDOMWidth(position.width) || hasChanged;
hasChanged = this._zoneManager.setDOMHeight(position.height) || hasChanged;
if (hasChanged) {
this._domNode.setWidth(this._zoneManager.getDOMWidth());
this._domNode.setHeight(this._zoneManager.getDOMHeight());
this._domNode.domNode.width = this._zoneManager.getCanvasWidth();
this._domNode.domNode.height = this._zoneManager.getCanvasHeight();
if (render) {
this.render(true);
}
}
}
public getLanesCount(): number {
return this._lanesCount;
}
public setLanesCount(newLanesCount: number, render: boolean): void {
this._lanesCount = newLanesCount;
if (render) {
this.render(true);
}
}
public setThemeType(themeType: ThemeType, render: boolean): void {
this._zoneManager.setThemeType(themeType);
if (render) {
this.render(true);
}
}
public setUseBackground(background: Color, render: boolean): void {
this._background = background;
if (render) {
this.render(true);
}
}
public getDomNode(): HTMLCanvasElement {
return this._domNode.domNode;
}
public getPixelWidth(): number {
return this._zoneManager.getCanvasWidth();
}
public getPixelHeight(): number {
return this._zoneManager.getCanvasHeight();
}
public setScrollHeight(scrollHeight: number, render: boolean): void {
this._zoneManager.setOuterHeight(scrollHeight);
if (render) {
this.render(true);
}
}
public setLineHeight(lineHeight: number, render: boolean): void {
this._zoneManager.setLineHeight(lineHeight);
if (render) {
this.render(true);
}
}
public setPixelRatio(pixelRatio: number, render: boolean): void {
this._zoneManager.setPixelRatio(pixelRatio);
this._domNode.setWidth(this._zoneManager.getDOMWidth());
this._domNode.setHeight(this._zoneManager.getDOMHeight());
this._domNode.domNode.width = this._zoneManager.getCanvasWidth();
this._domNode.domNode.height = this._zoneManager.getCanvasHeight();
if (render) {
this.render(true);
}
}
public setZones(zones: OverviewRulerZone[], render: boolean): void {
this._zoneManager.setZones(zones);
if (render) {
this.render(false);
}
}
public render(forceRender: boolean): boolean {
if (this._zoneManager.getOuterHeight() === 0) {
return false;
}
const width = this._zoneManager.getCanvasWidth();
const height = this._zoneManager.getCanvasHeight();
let colorZones = this._zoneManager.resolveColorZones();
let id2Color = this._zoneManager.getId2Color();
let ctx = this._domNode.domNode.getContext('2d');
if (this._background === null) {
ctx.clearRect(0, 0, width, height);
} else {
ctx.fillStyle = Color.Format.CSS.formatHex(this._background);
ctx.fillRect(0, 0, width, height);
}
if (colorZones.length > 0) {
let remainingWidth = width - this._canvasLeftOffset;
if (this._lanesCount >= 3) {
this._renderThreeLanes(ctx, colorZones, id2Color, remainingWidth);
} else if (this._lanesCount === 2) {
this._renderTwoLanes(ctx, colorZones, id2Color, remainingWidth);
} else if (this._lanesCount === 1) {
this._renderOneLane(ctx, colorZones, id2Color, remainingWidth);
}
}
return true;
}
private _renderOneLane(ctx: CanvasRenderingContext2D, colorZones: ColorZone[], id2Color: string[], w: number): void {
this._renderVerticalPatch(ctx, colorZones, id2Color, OverviewRulerLane.Left | OverviewRulerLane.Center | OverviewRulerLane.Right, this._canvasLeftOffset, w);
}
private _renderTwoLanes(ctx: CanvasRenderingContext2D, colorZones: ColorZone[], id2Color: string[], w: number): void {
let leftWidth = Math.floor(w / 2);
let rightWidth = w - leftWidth;
let leftOffset = this._canvasLeftOffset;
let rightOffset = this._canvasLeftOffset + leftWidth;
this._renderVerticalPatch(ctx, colorZones, id2Color, OverviewRulerLane.Left | OverviewRulerLane.Center, leftOffset, leftWidth);
this._renderVerticalPatch(ctx, colorZones, id2Color, OverviewRulerLane.Right, rightOffset, rightWidth);
}
private _renderThreeLanes(ctx: CanvasRenderingContext2D, colorZones: ColorZone[], id2Color: string[], w: number): void {
let leftWidth = Math.floor(w / 3);
let rightWidth = Math.floor(w / 3);
let centerWidth = w - leftWidth - rightWidth;
let leftOffset = this._canvasLeftOffset;
let centerOffset = this._canvasLeftOffset + leftWidth;
let rightOffset = this._canvasLeftOffset + leftWidth + centerWidth;
this._renderVerticalPatch(ctx, colorZones, id2Color, OverviewRulerLane.Left, leftOffset, leftWidth);
this._renderVerticalPatch(ctx, colorZones, id2Color, OverviewRulerLane.Center, centerOffset, centerWidth);
this._renderVerticalPatch(ctx, colorZones, id2Color, OverviewRulerLane.Right, rightOffset, rightWidth);
}
private _renderVerticalPatch(ctx: CanvasRenderingContext2D, colorZones: ColorZone[], id2Color: string[], laneMask: number, xpos: number, width: number): void {
let currentColorId = 0;
let currentFrom = 0;
let currentTo = 0;
for (let i = 0, len = colorZones.length; i < len; i++) {
let zone = colorZones[i];
if (!(zone.position & laneMask)) {
continue;
}
let zoneColorId = zone.colorId;
let zoneFrom = zone.from;
let zoneTo = zone.to;
if (zoneColorId !== currentColorId) {
ctx.fillRect(xpos, currentFrom, width, currentTo - currentFrom);
currentColorId = zoneColorId;
ctx.fillStyle = id2Color[currentColorId];
currentFrom = zoneFrom;
currentTo = zoneTo;
} else {
if (currentTo >= zoneFrom) {
currentTo = Math.max(currentTo, zoneTo);
} else {
ctx.fillRect(xpos, currentFrom, width, currentTo - currentFrom);
currentFrom = zoneFrom;
currentTo = zoneTo;
}
}
}
ctx.fillRect(xpos, currentFrom, width, currentTo - currentFrom);
}
}

View File

@@ -0,0 +1,9 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-editor .view-ruler {
position: absolute;
top: 0;
}

View File

@@ -0,0 +1,112 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'vs/css!./rulers';
import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode';
import { ViewPart } from 'vs/editor/browser/view/viewPart';
import { ViewContext } from 'vs/editor/common/view/viewContext';
import { RenderingContext, RestrictedRenderingContext } from 'vs/editor/common/view/renderingContext';
import * as viewEvents from 'vs/editor/common/view/viewEvents';
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { editorRuler } from 'vs/editor/common/view/editorColorRegistry';
import * as dom from 'vs/base/browser/dom';
export class Rulers extends ViewPart {
public domNode: FastDomNode<HTMLElement>;
private _renderedRulers: FastDomNode<HTMLElement>[];
private _rulers: number[];
private _height: number;
private _typicalHalfwidthCharacterWidth: number;
constructor(context: ViewContext) {
super(context);
this.domNode = createFastDomNode<HTMLElement>(document.createElement('div'));
this.domNode.setAttribute('role', 'presentation');
this.domNode.setAttribute('aria-hidden', 'true');
this.domNode.setClassName('view-rulers');
this._renderedRulers = [];
this._rulers = this._context.configuration.editor.viewInfo.rulers;
this._height = this._context.configuration.editor.layoutInfo.contentHeight;
this._typicalHalfwidthCharacterWidth = this._context.configuration.editor.fontInfo.typicalHalfwidthCharacterWidth;
}
public dispose(): void {
super.dispose();
}
// --- begin event handlers
public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean {
if (e.viewInfo || e.layoutInfo || e.fontInfo) {
this._rulers = this._context.configuration.editor.viewInfo.rulers;
this._height = this._context.configuration.editor.layoutInfo.contentHeight;
this._typicalHalfwidthCharacterWidth = this._context.configuration.editor.fontInfo.typicalHalfwidthCharacterWidth;
return true;
}
return false;
}
public onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean {
return e.scrollHeightChanged;
}
// --- end event handlers
public prepareRender(ctx: RenderingContext): void {
// Nothing to read
}
private _ensureRulersCount(): void {
const currentCount = this._renderedRulers.length;
const desiredCount = this._rulers.length;
if (currentCount === desiredCount) {
// Nothing to do
return;
}
if (currentCount < desiredCount) {
const rulerWidth = dom.computeScreenAwareSize(1);
let addCount = desiredCount - currentCount;
while (addCount > 0) {
let node = createFastDomNode(document.createElement('div'));
node.setClassName('view-ruler');
node.setWidth(rulerWidth);
this.domNode.appendChild(node);
this._renderedRulers.push(node);
addCount--;
}
return;
}
let removeCount = currentCount - desiredCount;
while (removeCount > 0) {
let node = this._renderedRulers.pop();
this.domNode.removeChild(node);
removeCount--;
}
}
public render(ctx: RestrictedRenderingContext): void {
this._ensureRulersCount();
for (let i = 0, len = this._rulers.length; i < len; i++) {
let node = this._renderedRulers[i];
node.setHeight(Math.min(ctx.scrollHeight, 1000000));
node.setLeft(this._rulers[i] * this._typicalHalfwidthCharacterWidth);
}
}
}
registerThemingParticipant((theme, collector) => {
let rulerColor = theme.getColor(editorRuler);
if (rulerColor) {
collector.addRule(`.monaco-editor .view-ruler { background-color: ${rulerColor}; }`);
}
});

View File

@@ -0,0 +1,11 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-editor .scroll-decoration {
position: absolute;
top: 0;
left: 0;
height: 6px;
}

View File

@@ -0,0 +1,99 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'vs/css!./scrollDecoration';
import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode';
import { ViewPart } from 'vs/editor/browser/view/viewPart';
import { ViewContext } from 'vs/editor/common/view/viewContext';
import { RenderingContext, RestrictedRenderingContext } from 'vs/editor/common/view/renderingContext';
import * as viewEvents from 'vs/editor/common/view/viewEvents';
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { scrollbarShadow } from 'vs/platform/theme/common/colorRegistry';
export class ScrollDecorationViewPart extends ViewPart {
private _domNode: FastDomNode<HTMLElement>;
private _scrollTop: number;
private _width: number;
private _shouldShow: boolean;
private _useShadows: boolean;
constructor(context: ViewContext) {
super(context);
this._scrollTop = 0;
this._width = 0;
this._updateWidth();
this._shouldShow = false;
this._useShadows = this._context.configuration.editor.viewInfo.scrollbar.useShadows;
this._domNode = createFastDomNode(document.createElement('div'));
this._domNode.setAttribute('role', 'presentation');
this._domNode.setAttribute('aria-hidden', 'true');
}
public dispose(): void {
super.dispose();
}
private _updateShouldShow(): boolean {
let newShouldShow = (this._useShadows && this._scrollTop > 0);
if (this._shouldShow !== newShouldShow) {
this._shouldShow = newShouldShow;
return true;
}
return false;
}
public getDomNode(): FastDomNode<HTMLElement> {
return this._domNode;
}
private _updateWidth(): boolean {
const layoutInfo = this._context.configuration.editor.layoutInfo;
let newWidth = layoutInfo.width - layoutInfo.minimapWidth;
if (this._width !== newWidth) {
this._width = newWidth;
return true;
}
return false;
}
// --- begin event handlers
public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean {
let shouldRender = false;
if (e.viewInfo) {
this._useShadows = this._context.configuration.editor.viewInfo.scrollbar.useShadows;
}
if (e.layoutInfo) {
shouldRender = this._updateWidth();
}
return this._updateShouldShow() || shouldRender;
}
public onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean {
this._scrollTop = e.scrollTop;
return this._updateShouldShow();
}
// --- end event handlers
public prepareRender(ctx: RenderingContext): void {
// Nothing to read
}
public render(ctx: RestrictedRenderingContext): void {
this._domNode.setWidth(this._width);
this._domNode.setClassName(this._shouldShow ? 'scroll-decoration' : '');
}
}
registerThemingParticipant((theme, collector) => {
let shadow = theme.getColor(scrollbarShadow);
if (shadow) {
collector.addRule(`.monaco-editor .scroll-decoration { box-shadow: ${shadow} 0 6px 6px -6px inset; }`);
}
});

View File

@@ -0,0 +1,22 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/*
Keeping name short for faster parsing.
cslr = core selections layer rendering (div)
*/
.monaco-editor .lines-content .cslr {
position: absolute;
}
.monaco-editor .top-left-radius { border-top-left-radius: 3px; }
.monaco-editor .bottom-left-radius { border-bottom-left-radius: 3px; }
.monaco-editor .top-right-radius { border-top-right-radius: 3px; }
.monaco-editor .bottom-right-radius { border-bottom-right-radius: 3px; }
.monaco-editor.hc-black .top-left-radius { border-top-left-radius: 0; }
.monaco-editor.hc-black .bottom-left-radius { border-bottom-left-radius: 0; }
.monaco-editor.hc-black .top-right-radius { border-top-right-radius: 0; }
.monaco-editor.hc-black .bottom-right-radius { border-bottom-right-radius: 0; }

View File

@@ -0,0 +1,409 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'vs/css!./selections';
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { editorSelectionBackground, editorInactiveSelection, editorSelectionForeground } from 'vs/platform/theme/common/colorRegistry';
import { DynamicViewOverlay } from 'vs/editor/browser/view/dynamicViewOverlay';
import { ViewContext } from 'vs/editor/common/view/viewContext';
import { HorizontalRange, LineVisibleRanges, RenderingContext } from 'vs/editor/common/view/renderingContext';
import { Range } from 'vs/editor/common/core/range';
import * as browser from 'vs/base/browser/browser';
import * as viewEvents from 'vs/editor/common/view/viewEvents';
const enum CornerStyle {
EXTERN,
INTERN,
FLAT
}
interface IVisibleRangeEndPointStyle {
top: CornerStyle;
bottom: CornerStyle;
}
class HorizontalRangeWithStyle {
public left: number;
public width: number;
public startStyle: IVisibleRangeEndPointStyle;
public endStyle: IVisibleRangeEndPointStyle;
constructor(other: HorizontalRange) {
this.left = other.left;
this.width = other.width;
this.startStyle = null;
this.endStyle = null;
}
}
class LineVisibleRangesWithStyle {
public lineNumber: number;
public ranges: HorizontalRangeWithStyle[];
constructor(lineNumber: number, ranges: HorizontalRangeWithStyle[]) {
this.lineNumber = lineNumber;
this.ranges = ranges;
}
}
function toStyledRange(item: HorizontalRange): HorizontalRangeWithStyle {
return new HorizontalRangeWithStyle(item);
}
function toStyled(item: LineVisibleRanges): LineVisibleRangesWithStyle {
return new LineVisibleRangesWithStyle(item.lineNumber, item.ranges.map(toStyledRange));
}
// TODO@Alex: Remove this once IE11 fixes Bug #524217
// The problem in IE11 is that it does some sort of auto-zooming to accomodate for displays with different pixel density.
// Unfortunately, this auto-zooming is buggy around dealing with rounded borders
const isIEWithZoomingIssuesNearRoundedBorders = browser.isEdgeOrIE;
export class SelectionsOverlay extends DynamicViewOverlay {
private static SELECTION_CLASS_NAME = 'selected-text';
private static SELECTION_TOP_LEFT = 'top-left-radius';
private static SELECTION_BOTTOM_LEFT = 'bottom-left-radius';
private static SELECTION_TOP_RIGHT = 'top-right-radius';
private static SELECTION_BOTTOM_RIGHT = 'bottom-right-radius';
private static EDITOR_BACKGROUND_CLASS_NAME = 'monaco-editor-background';
private static ROUNDED_PIECE_WIDTH = 10;
private _context: ViewContext;
private _lineHeight: number;
private _roundedSelection: boolean;
private _selections: Range[];
private _renderResult: string[];
constructor(context: ViewContext) {
super();
this._context = context;
this._lineHeight = this._context.configuration.editor.lineHeight;
this._roundedSelection = this._context.configuration.editor.viewInfo.roundedSelection;
this._selections = [];
this._renderResult = null;
this._context.addEventHandler(this);
}
public dispose(): void {
this._context.removeEventHandler(this);
this._context = null;
this._selections = null;
this._renderResult = null;
super.dispose();
}
// --- begin event handlers
public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean {
if (e.lineHeight) {
this._lineHeight = this._context.configuration.editor.lineHeight;
}
if (e.viewInfo) {
this._roundedSelection = this._context.configuration.editor.viewInfo.roundedSelection;
}
return true;
}
public onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean {
this._selections = e.selections.slice(0);
return true;
}
public onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean {
// true for inline decorations that can end up relayouting text
return true;//e.inlineDecorationsChanged;
}
public onFlushed(e: viewEvents.ViewFlushedEvent): boolean {
return true;
}
public onLinesChanged(e: viewEvents.ViewLinesChangedEvent): boolean {
return true;
}
public onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): boolean {
return true;
}
public onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): boolean {
return true;
}
public onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean {
return e.scrollTopChanged;
}
public onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean {
return true;
}
// --- end event handlers
private _visibleRangesHaveGaps(linesVisibleRanges: LineVisibleRangesWithStyle[]): boolean {
for (let i = 0, len = linesVisibleRanges.length; i < len; i++) {
let lineVisibleRanges = linesVisibleRanges[i];
if (lineVisibleRanges.ranges.length > 1) {
// There are two ranges on the same line
return true;
}
}
return false;
}
private _enrichVisibleRangesWithStyle(linesVisibleRanges: LineVisibleRangesWithStyle[], previousFrame: LineVisibleRangesWithStyle[]): void {
let previousFrameTop: HorizontalRangeWithStyle = null;
let previousFrameBottom: HorizontalRangeWithStyle = null;
if (previousFrame && previousFrame.length > 0 && linesVisibleRanges.length > 0) {
let topLineNumber = linesVisibleRanges[0].lineNumber;
for (let i = 0; !previousFrameTop && i < previousFrame.length; i++) {
if (previousFrame[i].lineNumber === topLineNumber) {
previousFrameTop = previousFrame[i].ranges[0];
}
}
let bottomLineNumber = linesVisibleRanges[linesVisibleRanges.length - 1].lineNumber;
for (let i = previousFrame.length - 1; !previousFrameBottom && i >= 0; i--) {
if (previousFrame[i].lineNumber === bottomLineNumber) {
previousFrameBottom = previousFrame[i].ranges[0];
}
}
if (previousFrameTop && !previousFrameTop.startStyle) {
previousFrameTop = null;
}
if (previousFrameBottom && !previousFrameBottom.startStyle) {
previousFrameBottom = null;
}
}
for (let i = 0, len = linesVisibleRanges.length; i < len; i++) {
// We know for a fact that there is precisely one range on each line
let curLineRange = linesVisibleRanges[i].ranges[0];
let curLeft = curLineRange.left;
let curRight = curLineRange.left + curLineRange.width;
let startStyle = {
top: CornerStyle.EXTERN,
bottom: CornerStyle.EXTERN
};
let endStyle = {
top: CornerStyle.EXTERN,
bottom: CornerStyle.EXTERN
};
if (i > 0) {
// Look above
let prevLeft = linesVisibleRanges[i - 1].ranges[0].left;
let prevRight = linesVisibleRanges[i - 1].ranges[0].left + linesVisibleRanges[i - 1].ranges[0].width;
if (curLeft === prevLeft) {
startStyle.top = CornerStyle.FLAT;
} else if (curLeft > prevLeft) {
startStyle.top = CornerStyle.INTERN;
}
if (curRight === prevRight) {
endStyle.top = CornerStyle.FLAT;
} else if (prevLeft < curRight && curRight < prevRight) {
endStyle.top = CornerStyle.INTERN;
}
} else if (previousFrameTop) {
// Accept some hick-ups near the viewport edges to save on repaints
startStyle.top = previousFrameTop.startStyle.top;
endStyle.top = previousFrameTop.endStyle.top;
}
if (i + 1 < len) {
// Look below
let nextLeft = linesVisibleRanges[i + 1].ranges[0].left;
let nextRight = linesVisibleRanges[i + 1].ranges[0].left + linesVisibleRanges[i + 1].ranges[0].width;
if (curLeft === nextLeft) {
startStyle.bottom = CornerStyle.FLAT;
} else if (nextLeft < curLeft && curLeft < nextRight) {
startStyle.bottom = CornerStyle.INTERN;
}
if (curRight === nextRight) {
endStyle.bottom = CornerStyle.FLAT;
} else if (curRight < nextRight) {
endStyle.bottom = CornerStyle.INTERN;
}
} else if (previousFrameBottom) {
// Accept some hick-ups near the viewport edges to save on repaints
startStyle.bottom = previousFrameBottom.startStyle.bottom;
endStyle.bottom = previousFrameBottom.endStyle.bottom;
}
curLineRange.startStyle = startStyle;
curLineRange.endStyle = endStyle;
}
}
private _getVisibleRangesWithStyle(selection: Range, ctx: RenderingContext, previousFrame: LineVisibleRangesWithStyle[]): LineVisibleRangesWithStyle[] {
let _linesVisibleRanges = ctx.linesVisibleRangesForRange(selection, true) || [];
let linesVisibleRanges = _linesVisibleRanges.map(toStyled);
let visibleRangesHaveGaps = this._visibleRangesHaveGaps(linesVisibleRanges);
if (!isIEWithZoomingIssuesNearRoundedBorders && !visibleRangesHaveGaps && this._roundedSelection) {
this._enrichVisibleRangesWithStyle(linesVisibleRanges, previousFrame);
}
// The visible ranges are sorted TOP-BOTTOM and LEFT-RIGHT
return linesVisibleRanges;
}
private _createSelectionPiece(top: number, height: string, className: string, left: number, width: number): string {
return (
'<div class="cslr '
+ className
+ '" style="top:'
+ top.toString()
+ 'px;left:'
+ left.toString()
+ 'px;width:'
+ width.toString()
+ 'px;height:'
+ height
+ 'px;"></div>'
);
}
private _actualRenderOneSelection(output2: string[], visibleStartLineNumber: number, hasMultipleSelections: boolean, visibleRanges: LineVisibleRangesWithStyle[]): void {
let visibleRangesHaveStyle = (visibleRanges.length > 0 && visibleRanges[0].ranges[0].startStyle);
let fullLineHeight = (this._lineHeight).toString();
let reducedLineHeight = (this._lineHeight - 1).toString();
let firstLineNumber = (visibleRanges.length > 0 ? visibleRanges[0].lineNumber : 0);
let lastLineNumber = (visibleRanges.length > 0 ? visibleRanges[visibleRanges.length - 1].lineNumber : 0);
for (let i = 0, len = visibleRanges.length; i < len; i++) {
let lineVisibleRanges = visibleRanges[i];
let lineNumber = lineVisibleRanges.lineNumber;
let lineIndex = lineNumber - visibleStartLineNumber;
let lineHeight = hasMultipleSelections ? (lineNumber === lastLineNumber || lineNumber === firstLineNumber ? reducedLineHeight : fullLineHeight) : fullLineHeight;
let top = hasMultipleSelections ? (lineNumber === firstLineNumber ? 1 : 0) : 0;
let lineOutput = '';
for (let j = 0, lenJ = lineVisibleRanges.ranges.length; j < lenJ; j++) {
let visibleRange = lineVisibleRanges.ranges[j];
if (visibleRangesHaveStyle) {
if (visibleRange.startStyle.top === CornerStyle.INTERN || visibleRange.startStyle.bottom === CornerStyle.INTERN) {
// Reverse rounded corner to the left
// First comes the selection (blue layer)
lineOutput += this._createSelectionPiece(top, lineHeight, SelectionsOverlay.SELECTION_CLASS_NAME, visibleRange.left - SelectionsOverlay.ROUNDED_PIECE_WIDTH, SelectionsOverlay.ROUNDED_PIECE_WIDTH);
// Second comes the background (white layer) with inverse border radius
let className = SelectionsOverlay.EDITOR_BACKGROUND_CLASS_NAME;
if (visibleRange.startStyle.top === CornerStyle.INTERN) {
className += ' ' + SelectionsOverlay.SELECTION_TOP_RIGHT;
}
if (visibleRange.startStyle.bottom === CornerStyle.INTERN) {
className += ' ' + SelectionsOverlay.SELECTION_BOTTOM_RIGHT;
}
lineOutput += this._createSelectionPiece(top, lineHeight, className, visibleRange.left - SelectionsOverlay.ROUNDED_PIECE_WIDTH, SelectionsOverlay.ROUNDED_PIECE_WIDTH);
}
if (visibleRange.endStyle.top === CornerStyle.INTERN || visibleRange.endStyle.bottom === CornerStyle.INTERN) {
// Reverse rounded corner to the right
// First comes the selection (blue layer)
lineOutput += this._createSelectionPiece(top, lineHeight, SelectionsOverlay.SELECTION_CLASS_NAME, visibleRange.left + visibleRange.width, SelectionsOverlay.ROUNDED_PIECE_WIDTH);
// Second comes the background (white layer) with inverse border radius
let className = SelectionsOverlay.EDITOR_BACKGROUND_CLASS_NAME;
if (visibleRange.endStyle.top === CornerStyle.INTERN) {
className += ' ' + SelectionsOverlay.SELECTION_TOP_LEFT;
}
if (visibleRange.endStyle.bottom === CornerStyle.INTERN) {
className += ' ' + SelectionsOverlay.SELECTION_BOTTOM_LEFT;
}
lineOutput += this._createSelectionPiece(top, lineHeight, className, visibleRange.left + visibleRange.width, SelectionsOverlay.ROUNDED_PIECE_WIDTH);
}
}
let className = SelectionsOverlay.SELECTION_CLASS_NAME;
if (visibleRangesHaveStyle) {
if (visibleRange.startStyle.top === CornerStyle.EXTERN) {
className += ' ' + SelectionsOverlay.SELECTION_TOP_LEFT;
}
if (visibleRange.startStyle.bottom === CornerStyle.EXTERN) {
className += ' ' + SelectionsOverlay.SELECTION_BOTTOM_LEFT;
}
if (visibleRange.endStyle.top === CornerStyle.EXTERN) {
className += ' ' + SelectionsOverlay.SELECTION_TOP_RIGHT;
}
if (visibleRange.endStyle.bottom === CornerStyle.EXTERN) {
className += ' ' + SelectionsOverlay.SELECTION_BOTTOM_RIGHT;
}
}
lineOutput += this._createSelectionPiece(top, lineHeight, className, visibleRange.left, visibleRange.width);
}
output2[lineIndex] += lineOutput;
}
}
private _previousFrameVisibleRangesWithStyle: LineVisibleRangesWithStyle[][] = [];
public prepareRender(ctx: RenderingContext): void {
let output: string[] = [];
let visibleStartLineNumber = ctx.visibleRange.startLineNumber;
let visibleEndLineNumber = ctx.visibleRange.endLineNumber;
for (let lineNumber = visibleStartLineNumber; lineNumber <= visibleEndLineNumber; lineNumber++) {
let lineIndex = lineNumber - visibleStartLineNumber;
output[lineIndex] = '';
}
let thisFrameVisibleRangesWithStyle: LineVisibleRangesWithStyle[][] = [];
for (let i = 0, len = this._selections.length; i < len; i++) {
let selection = this._selections[i];
if (selection.isEmpty()) {
thisFrameVisibleRangesWithStyle[i] = null;
continue;
}
let visibleRangesWithStyle = this._getVisibleRangesWithStyle(selection, ctx, this._previousFrameVisibleRangesWithStyle[i]);
thisFrameVisibleRangesWithStyle[i] = visibleRangesWithStyle;
this._actualRenderOneSelection(output, visibleStartLineNumber, this._selections.length > 1, visibleRangesWithStyle);
}
this._previousFrameVisibleRangesWithStyle = thisFrameVisibleRangesWithStyle;
this._renderResult = output;
}
public render(startLineNumber: number, lineNumber: number): string {
if (!this._renderResult) {
return '';
}
let lineIndex = lineNumber - startLineNumber;
if (lineIndex < 0 || lineIndex >= this._renderResult.length) {
throw new Error('Unexpected render request');
}
return this._renderResult[lineIndex];
}
}
registerThemingParticipant((theme, collector) => {
let editorSelectionColor = theme.getColor(editorSelectionBackground);
if (editorSelectionColor) {
collector.addRule(`.monaco-editor .focused .selected-text { background-color: ${editorSelectionColor}; }`);
}
let editorInactiveSelectionColor = theme.getColor(editorInactiveSelection);
if (editorInactiveSelectionColor) {
collector.addRule(`.monaco-editor .selected-text { background-color: ${editorInactiveSelectionColor}; }`);
}
let editorSelectionForegroundColor = theme.getColor(editorSelectionForeground);
if (editorSelectionForegroundColor) {
collector.addRule(`.monaco-editor .view-line span.inline-selected-text { color: ${editorSelectionForegroundColor}; }`);
}
});

View File

@@ -0,0 +1,203 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode';
import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
import { TextEditorCursorStyle } from 'vs/editor/common/config/editorOptions';
import { Configuration } from 'vs/editor/browser/config/configuration';
import { ViewContext } from 'vs/editor/common/view/viewContext';
import { RenderingContext, RestrictedRenderingContext } from 'vs/editor/common/view/renderingContext';
import * as viewEvents from 'vs/editor/common/view/viewEvents';
import * as dom from 'vs/base/browser/dom';
export interface IViewCursorRenderData {
domNode: HTMLElement;
position: Position;
contentLeft: number;
width: number;
height: number;
}
class ViewCursorRenderData {
public readonly top: number;
public readonly left: number;
public readonly width: number;
public readonly textContent: string;
constructor(top: number, left: number, width: number, textContent: string) {
this.top = top;
this.left = left;
this.width = width;
this.textContent = textContent;
}
}
export class ViewCursor {
private readonly _context: ViewContext;
private readonly _isSecondary: boolean;
private readonly _domNode: FastDomNode<HTMLElement>;
private _cursorStyle: TextEditorCursorStyle;
private _lineHeight: number;
private _typicalHalfwidthCharacterWidth: number;
private _isVisible: boolean;
private _position: Position;
private _isInEditableRange: boolean;
private _lastRenderedContent: string;
private _renderData: ViewCursorRenderData;
constructor(context: ViewContext, isSecondary: boolean) {
this._context = context;
this._isSecondary = isSecondary;
this._cursorStyle = this._context.configuration.editor.viewInfo.cursorStyle;
this._lineHeight = this._context.configuration.editor.lineHeight;
this._typicalHalfwidthCharacterWidth = this._context.configuration.editor.fontInfo.typicalHalfwidthCharacterWidth;
this._isVisible = true;
// Create the dom node
this._domNode = createFastDomNode(document.createElement('div'));
if (this._isSecondary) {
this._domNode.setClassName('cursor secondary');
} else {
this._domNode.setClassName('cursor');
}
this._domNode.setHeight(this._lineHeight);
this._domNode.setTop(0);
this._domNode.setLeft(0);
Configuration.applyFontInfo(this._domNode, this._context.configuration.editor.fontInfo);
this._domNode.setDisplay('none');
this.updatePosition(new Position(1, 1));
this._isInEditableRange = true;
this._lastRenderedContent = '';
this._renderData = null;
}
public getDomNode(): FastDomNode<HTMLElement> {
return this._domNode;
}
public getIsInEditableRange(): boolean {
return this._isInEditableRange;
}
public getPosition(): Position {
return this._position;
}
public show(): void {
if (!this._isVisible) {
this._domNode.setVisibility('inherit');
this._isVisible = true;
}
}
public hide(): void {
if (this._isVisible) {
this._domNode.setVisibility('hidden');
this._isVisible = false;
}
}
public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean {
if (e.lineHeight) {
this._lineHeight = this._context.configuration.editor.lineHeight;
}
if (e.viewInfo) {
this._cursorStyle = this._context.configuration.editor.viewInfo.cursorStyle;
}
if (e.fontInfo) {
Configuration.applyFontInfo(this._domNode, this._context.configuration.editor.fontInfo);
this._typicalHalfwidthCharacterWidth = this._context.configuration.editor.fontInfo.typicalHalfwidthCharacterWidth;
}
return true;
}
public onCursorPositionChanged(position: Position, isInEditableRange: boolean): boolean {
this.updatePosition(position);
this._isInEditableRange = isInEditableRange;
return true;
}
private _prepareRender(ctx: RenderingContext): ViewCursorRenderData {
if (this._cursorStyle === TextEditorCursorStyle.Line || this._cursorStyle === TextEditorCursorStyle.LineThin) {
const visibleRange = ctx.visibleRangeForPosition(this._position);
if (!visibleRange) {
// Outside viewport
return null;
}
let width: number;
if (this._cursorStyle === TextEditorCursorStyle.Line) {
width = dom.computeScreenAwareSize(2);
} else {
width = dom.computeScreenAwareSize(1);
}
const top = ctx.getVerticalOffsetForLineNumber(this._position.lineNumber) - ctx.bigNumbersDelta;
return new ViewCursorRenderData(top, visibleRange.left, width, '');
}
const visibleRangeForCharacter = ctx.linesVisibleRangesForRange(new Range(this._position.lineNumber, this._position.column, this._position.lineNumber, this._position.column + 1), false);
if (!visibleRangeForCharacter || visibleRangeForCharacter.length === 0 || visibleRangeForCharacter[0].ranges.length === 0) {
// Outside viewport
return null;
}
const range = visibleRangeForCharacter[0].ranges[0];
const width = range.width < 1 ? this._typicalHalfwidthCharacterWidth : range.width;
let textContent = '';
if (this._cursorStyle === TextEditorCursorStyle.Block) {
const lineContent = this._context.model.getLineContent(this._position.lineNumber);
textContent = lineContent.charAt(this._position.column - 1);
}
const top = ctx.getVerticalOffsetForLineNumber(this._position.lineNumber) - ctx.bigNumbersDelta;
return new ViewCursorRenderData(top, range.left, width, textContent);
}
public prepareRender(ctx: RenderingContext): void {
this._renderData = this._prepareRender(ctx);
}
public render(ctx: RestrictedRenderingContext): IViewCursorRenderData {
if (!this._renderData) {
this._domNode.setDisplay('none');
return null;
}
if (this._lastRenderedContent !== this._renderData.textContent) {
this._lastRenderedContent = this._renderData.textContent;
this._domNode.domNode.textContent = this._lastRenderedContent;
}
this._domNode.setDisplay('block');
this._domNode.setTop(this._renderData.top);
this._domNode.setLeft(this._renderData.left);
this._domNode.setWidth(this._renderData.width);
this._domNode.setLineHeight(this._lineHeight);
this._domNode.setHeight(this._lineHeight);
return {
domNode: this._domNode.domNode,
position: this._position,
contentLeft: this._renderData.left,
height: this._lineHeight,
width: 2
};
}
private updatePosition(newPosition: Position): void {
this._position = newPosition;
}
}

View File

@@ -0,0 +1,85 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-editor .cursors-layer {
position: absolute;
top: 0;
}
.monaco-editor .cursors-layer > .cursor {
position: absolute;
cursor: text;
}
.monaco-editor .cursors-layer > .cursor.secondary {
opacity: 0.6;
}
/* -- block-outline-style -- */
.monaco-editor .cursors-layer.cursor-block-outline-style > .cursor {
box-sizing: border-box;
background: transparent !important;
border-style: solid;
border-width: 1px;
}
/* -- underline-style -- */
.monaco-editor .cursors-layer.cursor-underline-style > .cursor {
border-bottom-width: 2px;
border-bottom-style: solid;
background: transparent !important;
box-sizing: border-box;
}
/* -- underline-thin-style -- */
.monaco-editor .cursors-layer.cursor-underline-thin-style > .cursor {
border-bottom-width: 1px;
border-bottom-style: solid;
background: transparent !important;
box-sizing: border-box;
}
@keyframes monaco-cursor-smooth {
0%,
20% {
opacity: 1;
}
60%,
100% {
opacity: 0;
}
}
@keyframes monaco-cursor-phase {
0%,
20% {
opacity: 1;
}
90%,
100% {
opacity: 0;
}
}
@keyframes monaco-cursor-expand {
0%,
20% {
transform: scaleY(1);
}
80%,
100% {
transform: scaleY(0);
}
}
.cursor-smooth {
animation: monaco-cursor-smooth 0.5s ease-in-out 0s 20 alternate;
}
.cursor-phase {
animation: monaco-cursor-phase 0.5s ease-in-out 0s 20 alternate;
}
.cursor-expand > .cursor {
animation: monaco-cursor-expand 0.5s ease-in-out 0s 20 alternate;
}

View File

@@ -0,0 +1,368 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'vs/css!./viewCursors';
import { ViewPart } from 'vs/editor/browser/view/viewPart';
import { Position } from 'vs/editor/common/core/position';
import { IViewCursorRenderData, ViewCursor } from 'vs/editor/browser/viewParts/viewCursors/viewCursor';
import { ViewContext } from 'vs/editor/common/view/viewContext';
import { RenderingContext, RestrictedRenderingContext } from 'vs/editor/common/view/renderingContext';
import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode';
import { TimeoutTimer, IntervalTimer } from 'vs/base/common/async';
import * as viewEvents from 'vs/editor/common/view/viewEvents';
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { editorCursorForeground, editorCursorBackground } from 'vs/editor/common/view/editorColorRegistry';
import { TextEditorCursorBlinkingStyle, TextEditorCursorStyle } from 'vs/editor/common/config/editorOptions';
export class ViewCursors extends ViewPart {
static BLINK_INTERVAL = 500;
private _readOnly: boolean;
private _cursorBlinking: TextEditorCursorBlinkingStyle;
private _cursorStyle: TextEditorCursorStyle;
private _selectionIsEmpty: boolean;
private _isVisible: boolean;
private _domNode: FastDomNode<HTMLElement>;
private _startCursorBlinkAnimation: TimeoutTimer;
private _cursorFlatBlinkInterval: IntervalTimer;
private _blinkingEnabled: boolean;
private _editorHasFocus: boolean;
private _primaryCursor: ViewCursor;
private _secondaryCursors: ViewCursor[];
private _renderData: IViewCursorRenderData[];
constructor(context: ViewContext) {
super(context);
this._readOnly = this._context.configuration.editor.readOnly;
this._cursorBlinking = this._context.configuration.editor.viewInfo.cursorBlinking;
this._cursorStyle = this._context.configuration.editor.viewInfo.cursorStyle;
this._selectionIsEmpty = true;
this._primaryCursor = new ViewCursor(this._context, false);
this._secondaryCursors = [];
this._renderData = [];
this._domNode = createFastDomNode(document.createElement('div'));
this._domNode.setAttribute('role', 'presentation');
this._domNode.setAttribute('aria-hidden', 'true');
this._updateDomClassName();
this._domNode.appendChild(this._primaryCursor.getDomNode());
this._startCursorBlinkAnimation = new TimeoutTimer();
this._cursorFlatBlinkInterval = new IntervalTimer();
this._blinkingEnabled = false;
this._editorHasFocus = false;
this._updateBlinking();
}
public dispose(): void {
super.dispose();
this._startCursorBlinkAnimation.dispose();
this._cursorFlatBlinkInterval.dispose();
}
public getDomNode(): FastDomNode<HTMLElement> {
return this._domNode;
}
// --- begin event handlers
public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean {
if (e.readOnly) {
this._readOnly = this._context.configuration.editor.readOnly;
}
if (e.viewInfo) {
this._cursorBlinking = this._context.configuration.editor.viewInfo.cursorBlinking;
this._cursorStyle = this._context.configuration.editor.viewInfo.cursorStyle;
}
this._primaryCursor.onConfigurationChanged(e);
this._updateBlinking();
if (e.viewInfo) {
this._updateDomClassName();
}
for (let i = 0, len = this._secondaryCursors.length; i < len; i++) {
this._secondaryCursors[i].onConfigurationChanged(e);
}
return true;
}
private _onCursorPositionChanged(position: Position, secondaryPositions: Position[], isInEditableRange: boolean): void {
this._primaryCursor.onCursorPositionChanged(position, isInEditableRange);
this._updateBlinking();
if (this._secondaryCursors.length < secondaryPositions.length) {
// Create new cursors
let addCnt = secondaryPositions.length - this._secondaryCursors.length;
for (let i = 0; i < addCnt; i++) {
let newCursor = new ViewCursor(this._context, true);
this._domNode.domNode.insertBefore(newCursor.getDomNode().domNode, this._primaryCursor.getDomNode().domNode.nextSibling);
this._secondaryCursors.push(newCursor);
}
} else if (this._secondaryCursors.length > secondaryPositions.length) {
// Remove some cursors
let removeCnt = this._secondaryCursors.length - secondaryPositions.length;
for (let i = 0; i < removeCnt; i++) {
this._domNode.removeChild(this._secondaryCursors[0].getDomNode());
this._secondaryCursors.splice(0, 1);
}
}
for (let i = 0; i < secondaryPositions.length; i++) {
this._secondaryCursors[i].onCursorPositionChanged(secondaryPositions[i], isInEditableRange);
}
}
public onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean {
let positions: Position[] = [];
for (let i = 0, len = e.selections.length; i < len; i++) {
positions[i] = e.selections[i].getPosition();
}
this._onCursorPositionChanged(positions[0], positions.slice(1), e.isInEditableRange);
const selectionIsEmpty = e.selections[0].isEmpty();
if (this._selectionIsEmpty !== selectionIsEmpty) {
this._selectionIsEmpty = selectionIsEmpty;
this._updateDomClassName();
}
return true;
}
public onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean {
// true for inline decorations that can end up relayouting text
return true;
}
public onFlushed(e: viewEvents.ViewFlushedEvent): boolean {
return true;
}
public onFocusChanged(e: viewEvents.ViewFocusChangedEvent): boolean {
this._editorHasFocus = e.isFocused;
this._updateBlinking();
return false;
}
public onLinesChanged(e: viewEvents.ViewLinesChangedEvent): boolean {
return true;
}
public onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): boolean {
return true;
}
public onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): boolean {
return true;
}
public onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean {
return true;
}
public onTokensChanged(e: viewEvents.ViewTokensChangedEvent): boolean {
let shouldRender = (position: Position) => {
for (let i = 0, len = e.ranges.length; i < len; i++) {
if (e.ranges[i].fromLineNumber <= position.lineNumber && position.lineNumber <= e.ranges[i].toLineNumber) {
return true;
}
}
return false;
};
if (shouldRender(this._primaryCursor.getPosition())) {
return true;
}
for (let i = 0; i < this._secondaryCursors.length; i++) {
if (shouldRender(this._secondaryCursors[i].getPosition())) {
return true;
}
}
return false;
}
public onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean {
return true;
}
// --- end event handlers
public getPosition(): Position {
return this._primaryCursor.getPosition();
}
// ---- blinking logic
private _getCursorBlinking(): TextEditorCursorBlinkingStyle {
if (!this._editorHasFocus) {
return TextEditorCursorBlinkingStyle.Hidden;
}
if (this._readOnly || !this._primaryCursor.getIsInEditableRange()) {
return TextEditorCursorBlinkingStyle.Solid;
}
return this._cursorBlinking;
}
private _updateBlinking(): void {
this._startCursorBlinkAnimation.cancel();
this._cursorFlatBlinkInterval.cancel();
let blinkingStyle = this._getCursorBlinking();
// hidden and solid are special as they involve no animations
let isHidden = (blinkingStyle === TextEditorCursorBlinkingStyle.Hidden);
let isSolid = (blinkingStyle === TextEditorCursorBlinkingStyle.Solid);
if (isHidden) {
this._hide();
} else {
this._show();
}
this._blinkingEnabled = false;
this._updateDomClassName();
if (!isHidden && !isSolid) {
if (blinkingStyle === TextEditorCursorBlinkingStyle.Blink) {
// flat blinking is handled by JavaScript to save battery life due to Chromium step timing issue https://bugs.chromium.org/p/chromium/issues/detail?id=361587
this._cursorFlatBlinkInterval.cancelAndSet(() => {
if (this._isVisible) {
this._hide();
} else {
this._show();
}
}, ViewCursors.BLINK_INTERVAL);
} else {
this._startCursorBlinkAnimation.setIfNotSet(() => {
this._blinkingEnabled = true;
this._updateDomClassName();
}, ViewCursors.BLINK_INTERVAL);
}
}
}
// --- end blinking logic
private _updateDomClassName(): void {
this._domNode.setClassName(this._getClassName());
}
private _getClassName(): string {
let result = 'cursors-layer';
if (!this._selectionIsEmpty) {
result += ' has-selection';
}
switch (this._cursorStyle) {
case TextEditorCursorStyle.Line:
result += ' cursor-line-style';
break;
case TextEditorCursorStyle.Block:
result += ' cursor-block-style';
break;
case TextEditorCursorStyle.Underline:
result += ' cursor-underline-style';
break;
case TextEditorCursorStyle.LineThin:
result += ' cursor-line-thin-style';
break;
case TextEditorCursorStyle.BlockOutline:
result += ' cursor-block-outline-style';
break;
case TextEditorCursorStyle.UnderlineThin:
result += ' cursor-underline-thin-style';
break;
default:
result += ' cursor-line-style';
}
if (this._blinkingEnabled) {
switch (this._getCursorBlinking()) {
case TextEditorCursorBlinkingStyle.Blink:
result += ' cursor-blink';
break;
case TextEditorCursorBlinkingStyle.Smooth:
result += ' cursor-smooth';
break;
case TextEditorCursorBlinkingStyle.Phase:
result += ' cursor-phase';
break;
case TextEditorCursorBlinkingStyle.Expand:
result += ' cursor-expand';
break;
case TextEditorCursorBlinkingStyle.Solid:
result += ' cursor-solid';
break;
default:
result += ' cursor-solid';
}
} else {
result += ' cursor-solid';
}
return result;
}
private _show(): void {
this._primaryCursor.show();
for (let i = 0, len = this._secondaryCursors.length; i < len; i++) {
this._secondaryCursors[i].show();
}
this._isVisible = true;
}
private _hide(): void {
this._primaryCursor.hide();
for (let i = 0, len = this._secondaryCursors.length; i < len; i++) {
this._secondaryCursors[i].hide();
}
this._isVisible = false;
}
// ---- IViewPart implementation
public prepareRender(ctx: RenderingContext): void {
this._primaryCursor.prepareRender(ctx);
for (let i = 0, len = this._secondaryCursors.length; i < len; i++) {
this._secondaryCursors[i].prepareRender(ctx);
}
}
public render(ctx: RestrictedRenderingContext): void {
let renderData: IViewCursorRenderData[] = [], renderDataLen = 0;
const primaryRenderData = this._primaryCursor.render(ctx);
if (primaryRenderData) {
renderData[renderDataLen++] = primaryRenderData;
}
for (let i = 0, len = this._secondaryCursors.length; i < len; i++) {
const secondaryRenderData = this._secondaryCursors[i].render(ctx);
if (secondaryRenderData) {
renderData[renderDataLen++] = secondaryRenderData;
}
}
this._renderData = renderData;
}
public getLastRenderData(): IViewCursorRenderData[] {
return this._renderData;
}
}
registerThemingParticipant((theme, collector) => {
let caret = theme.getColor(editorCursorForeground);
if (caret) {
let caretBackground = theme.getColor(editorCursorBackground);
if (!caretBackground) {
caretBackground = caret.opposite();
}
collector.addRule(`.monaco-editor .cursor { background-color: ${caret}; border-color: ${caret}; color: ${caretBackground}; }`);
if (theme.type === 'hc') {
collector.addRule(`.monaco-editor .cursors-layer.has-selection .cursor { border-left: 1px solid ${caretBackground}; border-right: 1px solid ${caretBackground}; }`);
}
}
});

View File

@@ -0,0 +1,353 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { onUnexpectedError } from 'vs/base/common/errors';
import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode';
import { IViewZone } from 'vs/editor/browser/editorBrowser';
import { ViewPart } from 'vs/editor/browser/view/viewPart';
import { ViewContext } from 'vs/editor/common/view/viewContext';
import { Position } from 'vs/editor/common/core/position';
import { RenderingContext, RestrictedRenderingContext } from 'vs/editor/common/view/renderingContext';
import { IViewWhitespaceViewportData } from 'vs/editor/common/viewModel/viewModel';
import * as viewEvents from 'vs/editor/common/view/viewEvents';
export interface IMyViewZone {
whitespaceId: number;
delegate: IViewZone;
isVisible: boolean;
domNode: FastDomNode<HTMLElement>;
marginDomNode: FastDomNode<HTMLElement>;
}
export interface IMyRenderData {
data: IViewWhitespaceViewportData[];
}
interface IComputedViewZoneProps {
afterViewLineNumber: number;
heightInPx: number;
}
export class ViewZones extends ViewPart {
private _zones: { [id: string]: IMyViewZone; };
private _lineHeight: number;
private _contentWidth: number;
private _contentLeft: number;
public domNode: FastDomNode<HTMLElement>;
public marginDomNode: FastDomNode<HTMLElement>;
constructor(context: ViewContext) {
super(context);
this._lineHeight = this._context.configuration.editor.lineHeight;
this._contentWidth = this._context.configuration.editor.layoutInfo.contentWidth;
this._contentLeft = this._context.configuration.editor.layoutInfo.contentLeft;
this.domNode = createFastDomNode(document.createElement('div'));
this.domNode.setClassName('view-zones');
this.domNode.setPosition('absolute');
this.domNode.setAttribute('role', 'presentation');
this.domNode.setAttribute('aria-hidden', 'true');
this.marginDomNode = createFastDomNode(document.createElement('div'));
this.marginDomNode.setClassName('margin-view-zones');
this.marginDomNode.setPosition('absolute');
this.marginDomNode.setAttribute('role', 'presentation');
this.marginDomNode.setAttribute('aria-hidden', 'true');
this._zones = {};
}
public dispose(): void {
super.dispose();
this._zones = {};
}
// ---- begin view event handlers
private _recomputeWhitespacesProps(): boolean {
let hadAChange = false;
let keys = Object.keys(this._zones);
for (let i = 0, len = keys.length; i < len; i++) {
let id = keys[i];
let zone = this._zones[id];
let props = this._computeWhitespaceProps(zone.delegate);
if (this._context.viewLayout.changeWhitespace(parseInt(id, 10), props.afterViewLineNumber, props.heightInPx)) {
this._safeCallOnComputedHeight(zone.delegate, props.heightInPx);
hadAChange = true;
}
}
return hadAChange;
}
public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean {
if (e.lineHeight) {
this._lineHeight = this._context.configuration.editor.lineHeight;
return this._recomputeWhitespacesProps();
}
if (e.layoutInfo) {
this._contentWidth = this._context.configuration.editor.layoutInfo.contentWidth;
this._contentLeft = this._context.configuration.editor.layoutInfo.contentLeft;
}
return true;
}
public onLineMappingChanged(e: viewEvents.ViewLineMappingChangedEvent): boolean {
return this._recomputeWhitespacesProps();
}
public onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): boolean {
return true;
}
public onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean {
return e.scrollTopChanged || e.scrollWidthChanged;
}
public onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean {
return true;
}
public onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): boolean {
return true;
}
// ---- end view event handlers
private _getZoneOrdinal(zone: IViewZone): number {
if (typeof zone.afterColumn !== 'undefined') {
return zone.afterColumn;
}
return 10000;
}
private _computeWhitespaceProps(zone: IViewZone): IComputedViewZoneProps {
if (zone.afterLineNumber === 0) {
return {
afterViewLineNumber: 0,
heightInPx: this._heightInPixels(zone)
};
}
let zoneAfterModelPosition: Position;
if (typeof zone.afterColumn !== 'undefined') {
zoneAfterModelPosition = this._context.model.validateModelPosition({
lineNumber: zone.afterLineNumber,
column: zone.afterColumn
});
} else {
let validAfterLineNumber = this._context.model.validateModelPosition({
lineNumber: zone.afterLineNumber,
column: 1
}).lineNumber;
zoneAfterModelPosition = new Position(
validAfterLineNumber,
this._context.model.getModelLineMaxColumn(validAfterLineNumber)
);
}
let zoneBeforeModelPosition: Position;
if (zoneAfterModelPosition.column === this._context.model.getModelLineMaxColumn(zoneAfterModelPosition.lineNumber)) {
zoneBeforeModelPosition = this._context.model.validateModelPosition({
lineNumber: zoneAfterModelPosition.lineNumber + 1,
column: 1
});
} else {
zoneBeforeModelPosition = this._context.model.validateModelPosition({
lineNumber: zoneAfterModelPosition.lineNumber,
column: zoneAfterModelPosition.column + 1
});
}
let viewPosition = this._context.model.coordinatesConverter.convertModelPositionToViewPosition(zoneAfterModelPosition);
let isVisible = this._context.model.coordinatesConverter.modelPositionIsVisible(zoneBeforeModelPosition);
return {
afterViewLineNumber: viewPosition.lineNumber,
heightInPx: (isVisible ? this._heightInPixels(zone) : 0)
};
}
public addZone(zone: IViewZone): number {
let props = this._computeWhitespaceProps(zone);
let whitespaceId = this._context.viewLayout.addWhitespace(props.afterViewLineNumber, this._getZoneOrdinal(zone), props.heightInPx);
let myZone: IMyViewZone = {
whitespaceId: whitespaceId,
delegate: zone,
isVisible: false,
domNode: createFastDomNode(zone.domNode),
marginDomNode: zone.marginDomNode ? createFastDomNode(zone.marginDomNode) : null
};
this._safeCallOnComputedHeight(myZone.delegate, props.heightInPx);
myZone.domNode.setPosition('absolute');
myZone.domNode.domNode.style.width = '100%';
myZone.domNode.setDisplay('none');
myZone.domNode.setAttribute('monaco-view-zone', myZone.whitespaceId.toString());
this.domNode.appendChild(myZone.domNode);
if (myZone.marginDomNode) {
myZone.marginDomNode.setPosition('absolute');
myZone.marginDomNode.domNode.style.width = '100%';
myZone.marginDomNode.setDisplay('none');
myZone.marginDomNode.setAttribute('monaco-view-zone', myZone.whitespaceId.toString());
this.marginDomNode.appendChild(myZone.marginDomNode);
}
this._zones[myZone.whitespaceId.toString()] = myZone;
this.setShouldRender();
return myZone.whitespaceId;
}
public removeZone(id: number): boolean {
if (this._zones.hasOwnProperty(id.toString())) {
let zone = this._zones[id.toString()];
delete this._zones[id.toString()];
this._context.viewLayout.removeWhitespace(zone.whitespaceId);
zone.domNode.removeAttribute('monaco-visible-view-zone');
zone.domNode.removeAttribute('monaco-view-zone');
zone.domNode.domNode.parentNode.removeChild(zone.domNode.domNode);
if (zone.marginDomNode) {
zone.marginDomNode.removeAttribute('monaco-visible-view-zone');
zone.marginDomNode.removeAttribute('monaco-view-zone');
zone.marginDomNode.domNode.parentNode.removeChild(zone.marginDomNode.domNode);
}
this.setShouldRender();
return true;
}
return false;
}
public layoutZone(id: number): boolean {
let changed = false;
if (this._zones.hasOwnProperty(id.toString())) {
let zone = this._zones[id.toString()];
let props = this._computeWhitespaceProps(zone.delegate);
// let newOrdinal = this._getZoneOrdinal(zone.delegate);
changed = this._context.viewLayout.changeWhitespace(zone.whitespaceId, props.afterViewLineNumber, props.heightInPx) || changed;
// TODO@Alex: change `newOrdinal` too
if (changed) {
this._safeCallOnComputedHeight(zone.delegate, props.heightInPx);
this.setShouldRender();
}
}
return changed;
}
public shouldSuppressMouseDownOnViewZone(id: number): boolean {
if (this._zones.hasOwnProperty(id.toString())) {
let zone = this._zones[id.toString()];
return zone.delegate.suppressMouseDown;
}
return false;
}
private _heightInPixels(zone: IViewZone): number {
if (typeof zone.heightInPx === 'number') {
return zone.heightInPx;
}
if (typeof zone.heightInLines === 'number') {
return this._lineHeight * zone.heightInLines;
}
return this._lineHeight;
}
private _safeCallOnComputedHeight(zone: IViewZone, height: number): void {
if (typeof zone.onComputedHeight === 'function') {
try {
zone.onComputedHeight(height);
} catch (e) {
onUnexpectedError(e);
}
}
}
private _safeCallOnDomNodeTop(zone: IViewZone, top: number): void {
if (typeof zone.onDomNodeTop === 'function') {
try {
zone.onDomNodeTop(top);
} catch (e) {
onUnexpectedError(e);
}
}
}
public prepareRender(ctx: RenderingContext): void {
// Nothing to read
}
public render(ctx: RestrictedRenderingContext): void {
const visibleWhitespaces = ctx.viewportData.whitespaceViewportData;
let visibleZones: { [id: string]: IViewWhitespaceViewportData; } = {};
let hasVisibleZone = false;
for (let i = 0, len = visibleWhitespaces.length; i < len; i++) {
visibleZones[visibleWhitespaces[i].id.toString()] = visibleWhitespaces[i];
hasVisibleZone = true;
}
let keys = Object.keys(this._zones);
for (let i = 0, len = keys.length; i < len; i++) {
let id = keys[i];
let zone = this._zones[id];
let newTop = 0;
let newHeight = 0;
let newDisplay = 'none';
if (visibleZones.hasOwnProperty(id)) {
newTop = visibleZones[id].verticalOffset - ctx.bigNumbersDelta;
newHeight = visibleZones[id].height;
newDisplay = 'block';
// zone is visible
if (!zone.isVisible) {
zone.domNode.setAttribute('monaco-visible-view-zone', 'true');
zone.isVisible = true;
}
this._safeCallOnDomNodeTop(zone.delegate, ctx.getScrolledTopFromAbsoluteTop(visibleZones[id].verticalOffset));
} else {
if (zone.isVisible) {
zone.domNode.removeAttribute('monaco-visible-view-zone');
zone.isVisible = false;
}
this._safeCallOnDomNodeTop(zone.delegate, ctx.getScrolledTopFromAbsoluteTop(-1000000));
}
zone.domNode.setTop(newTop);
zone.domNode.setHeight(newHeight);
zone.domNode.setDisplay(newDisplay);
if (zone.marginDomNode) {
zone.marginDomNode.setTop(newTop);
zone.marginDomNode.setHeight(newHeight);
zone.marginDomNode.setDisplay(newDisplay);
}
}
if (hasVisibleZone) {
this.domNode.setWidth(Math.max(ctx.scrollWidth, this._contentWidth));
this.marginDomNode.setWidth(this._contentLeft);
}
}
}