diff --git a/src/vs/base/browser/browser.ts b/src/vs/base/browser/browser.ts index 99fda39e773..f0b00dd77ae 100644 --- a/src/vs/base/browser/browser.ts +++ b/src/vs/base/browser/browser.ts @@ -14,9 +14,12 @@ class ZoomManager { public static INSTANCE = new ZoomManager(); private _zoomLevel: number = 0; + private _pixelRatioCache: number = 0; + private _pixelRatioComputed: boolean = false; private _onDidChangeZoomLevel: Emitter = new Emitter(); public onDidChangeZoomLevel:Event = this._onDidChangeZoomLevel.event; + public getZoomLevel(): number { return this._zoomLevel; } @@ -27,13 +30,36 @@ class ZoomManager { } this._zoomLevel = zoomLevel; + this._pixelRatioComputed = false; this._onDidChangeZoomLevel.fire(this._zoomLevel); } + + public getPixelRatio(): number { + if (!this._pixelRatioComputed) { + this._pixelRatioCache = this._computePixelRatio(); + this._pixelRatioComputed = true; + } + return this._pixelRatioCache; + } + + private _computePixelRatio(): number { + let ctx = document.createElement('canvas').getContext('2d'); + let dpr = window.devicePixelRatio || 1; + let bsr = (ctx).webkitBackingStorePixelRatio || + (ctx).mozBackingStorePixelRatio || + (ctx).msBackingStorePixelRatio || + (ctx).oBackingStorePixelRatio || + (ctx).backingStorePixelRatio || 1; + return dpr / bsr; + } } export function getZoomLevel(): number { return ZoomManager.INSTANCE.getZoomLevel(); } +export function getPixelRatio(): number { + return ZoomManager.INSTANCE.getPixelRatio(); +} export function setZoomLevel(zoomLevel:number): void { ZoomManager.INSTANCE.setZoomLevel(zoomLevel); } diff --git a/src/vs/editor/browser/editorBrowser.ts b/src/vs/editor/browser/editorBrowser.ts index bc092bf44c1..bf2f18a3b33 100644 --- a/src/vs/editor/browser/editorBrowser.ts +++ b/src/vs/editor/browser/editorBrowser.ts @@ -359,108 +359,14 @@ export interface IEditorContributionDescriptor { createInstance(instantiationService:IInstantiationService, editor:ICodeEditor): editorCommon.IEditorContribution; } -export class ColorZone { - _colorZoneBrand: void; - from: number; - to: number; - colorId: number; - position: editorCommon.OverviewRulerLane; - - constructor(from:number, to:number, colorId:number, position: editorCommon.OverviewRulerLane) { - this.from = from|0; - this.to = to|0; - this.colorId = colorId|0; - this.position = position|0; - } -} - -/** - * A zone in the overview ruler - */ -export class OverviewRulerZone { - _overviewRulerZoneBrand: void; - - startLineNumber: number; - endLineNumber: number; - position: editorCommon.OverviewRulerLane; - forceHeight: number; - - private _color: string; - private _darkColor: string; - - private _colorZones: ColorZone[]; - - constructor( - startLineNumber: number, endLineNumber: number, - position: editorCommon.OverviewRulerLane, - forceHeight: number, - color: string, darkColor: string - ) { - this.startLineNumber = startLineNumber; - this.endLineNumber = endLineNumber; - this.position = position; - this.forceHeight = forceHeight; - this._color = color; - this._darkColor = darkColor; - this._colorZones = null; - } - - public getColor(useDarkColor:boolean): string { - if (useDarkColor) { - return this._darkColor; - } - return this._color; - } - - public equals(other:OverviewRulerZone): boolean { - return ( - this.startLineNumber === other.startLineNumber - && this.endLineNumber === other.endLineNumber - && this.position === other.position - && this.forceHeight === other.forceHeight - && this._color === other._color - && this._darkColor === other._darkColor - ); - } - - public compareTo(other:OverviewRulerZone): number { - if (this.startLineNumber === other.startLineNumber) { - if (this.endLineNumber === other.endLineNumber) { - if (this.forceHeight === other.forceHeight) { - if (this.position === other.position) { - if (this._darkColor === other._darkColor) { - if (this._color === other._color) { - return 0; - } - return this._color < other._color ? -1 : 1; - } - return this._darkColor < other._darkColor ? -1 : 1; - } - return this.position - other.position; - } - return this.forceHeight - other.forceHeight; - } - return this.endLineNumber - other.endLineNumber; - } - return this.startLineNumber - other.startLineNumber; - } - - public setColorZones(colorZones:ColorZone[]): void { - this._colorZones = colorZones; - } - - public getColorZones(): ColorZone[] { - return this._colorZones; - } -} /** * An overview ruler */ export interface IOverviewRuler { getDomNode(): HTMLElement; dispose(): void; - setZones(zones:OverviewRulerZone[]): void; + setZones(zones:editorCommon.OverviewRulerZone[]): void; setLayout(position:editorCommon.OverviewRulerPosition): void; } /** diff --git a/src/vs/editor/browser/viewParts/overviewRuler/decorationsOverviewRuler.ts b/src/vs/editor/browser/viewParts/overviewRuler/decorationsOverviewRuler.ts index ba6c401e155..125f9e14295 100644 --- a/src/vs/editor/browser/viewParts/overviewRuler/decorationsOverviewRuler.ts +++ b/src/vs/editor/browser/viewParts/overviewRuler/decorationsOverviewRuler.ts @@ -6,7 +6,6 @@ import * as themes from 'vs/platform/theme/common/themes'; import * as editorCommon from 'vs/editor/common/editorCommon'; -import {OverviewRulerZone} from 'vs/editor/browser/editorBrowser'; 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'; @@ -28,8 +27,8 @@ export class DecorationsOverviewRuler extends ViewPart { private _hideCursor:boolean; private _cursorPositions: Position[]; - private _zonesFromDecorations: OverviewRulerZone[]; - private _zonesFromCursors: OverviewRulerZone[]; + private _zonesFromDecorations: editorCommon.OverviewRulerZone[]; + private _zonesFromCursors: editorCommon.OverviewRulerZone[]; constructor(context:ViewContext, scrollHeight:number, getVerticalOffsetForLine:(lineNumber:number)=>number) { super(context); @@ -138,14 +137,14 @@ export class DecorationsOverviewRuler extends ViewPart { return this._overviewRuler.getDomNode(); } - private _createZonesFromDecorations(): OverviewRulerZone[] { + private _createZonesFromDecorations(): editorCommon.OverviewRulerZone[] { let decorations = this._context.model.getAllDecorations(); - let zones:OverviewRulerZone[] = []; + let zones:editorCommon.OverviewRulerZone[] = []; for (let i = 0, len = decorations.length; i < len; i++) { let dec = decorations[i]; if (dec.options.overviewRuler.color) { - zones.push(new OverviewRulerZone( + zones.push(new editorCommon.OverviewRulerZone( dec.range.startLineNumber, dec.range.endLineNumber, dec.options.overviewRuler.position, @@ -159,13 +158,13 @@ export class DecorationsOverviewRuler extends ViewPart { return zones; } - private _createZonesFromCursors(): OverviewRulerZone[] { - let zones:OverviewRulerZone[] = []; + private _createZonesFromCursors(): editorCommon.OverviewRulerZone[] { + let zones:editorCommon.OverviewRulerZone[] = []; for (let i = 0, len = this._cursorPositions.length; i < len; i++) { let cursor = this._cursorPositions[i]; - zones.push(new OverviewRulerZone( + zones.push(new editorCommon.OverviewRulerZone( cursor.lineNumber, cursor.lineNumber, editorCommon.OverviewRulerLane.Full, @@ -202,7 +201,7 @@ export class DecorationsOverviewRuler extends ViewPart { } } - var allZones:OverviewRulerZone[] = []; + var allZones:editorCommon.OverviewRulerZone[] = []; allZones = allZones.concat(this._zonesFromCursors); allZones = allZones.concat(this._zonesFromDecorations); @@ -217,11 +216,11 @@ export class DecorationsOverviewRuler extends ViewPart { ctx2.lineWidth = 1; ctx2.strokeStyle = 'rgba(197,197,197,0.8)'; ctx2.moveTo(0, 0); - ctx2.lineTo(0, this._overviewRuler.getHeight()); + ctx2.lineTo(0, this._overviewRuler.getPixelHeight()); ctx2.stroke(); ctx2.moveTo(0, 0); - ctx2.lineTo(this._overviewRuler.getWidth(), 0); + ctx2.lineTo(this._overviewRuler.getPixelWidth(), 0); ctx2.stroke(); } } diff --git a/src/vs/editor/browser/viewParts/overviewRuler/overviewRuler.ts b/src/vs/editor/browser/viewParts/overviewRuler/overviewRuler.ts index a14bb490716..2e0e8fb674c 100644 --- a/src/vs/editor/browser/viewParts/overviewRuler/overviewRuler.ts +++ b/src/vs/editor/browser/viewParts/overviewRuler/overviewRuler.ts @@ -4,9 +4,9 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import {IConfigurationChangedEvent, OverviewRulerPosition, IScrollEvent} from 'vs/editor/common/editorCommon'; +import {IConfigurationChangedEvent, OverviewRulerPosition, OverviewRulerZone, IScrollEvent} from 'vs/editor/common/editorCommon'; import {ViewEventHandler} from 'vs/editor/common/viewModel/viewEventHandler'; -import {IOverviewRuler, OverviewRulerZone} from 'vs/editor/browser/editorBrowser'; +import {IOverviewRuler} from 'vs/editor/browser/editorBrowser'; import {OverviewRulerImpl} from 'vs/editor/browser/viewParts/overviewRuler/overviewRulerImpl'; import {ViewContext} from 'vs/editor/common/view/viewContext'; diff --git a/src/vs/editor/browser/viewParts/overviewRuler/overviewRulerImpl.ts b/src/vs/editor/browser/viewParts/overviewRuler/overviewRulerImpl.ts index c0e3c8c4526..6a7ba584760 100644 --- a/src/vs/editor/browser/viewParts/overviewRuler/overviewRulerImpl.ts +++ b/src/vs/editor/browser/viewParts/overviewRuler/overviewRulerImpl.ts @@ -5,269 +5,10 @@ 'use strict'; import {StyleMutator} from 'vs/base/browser/styleMutator'; -import {OverviewRulerPosition, OverviewRulerLane} from 'vs/editor/common/editorCommon'; -import {OverviewRulerZone, ColorZone} from 'vs/editor/browser/editorBrowser'; - -class ZoneManager { - - private _getVerticalOffsetForLine:(lineNumber:number)=>number; - private _zones: OverviewRulerZone[]; - private _colorZonesInvalid: boolean; - private _lineHeight: number; - private _width: number; - private _height: number; - private _outerHeight: number; - private _maximumHeight: number; - private _minimumHeight: number; - private _useDarkColor: boolean; - - private _lastAssignedId; - private _color2Id: { [color:string]: number; }; - private _id2Color: string[]; - - constructor(getVerticalOffsetForLine:(lineNumber:number)=>number) { - this._getVerticalOffsetForLine = getVerticalOffsetForLine; - this._zones = []; - this._colorZonesInvalid = false; - this._lineHeight = 0; - this._width = 0; - this._height = 0; - this._outerHeight = 0; - this._maximumHeight = 0; - this._minimumHeight = 0; - this._useDarkColor = false; - - this._lastAssignedId = 0; - this._color2Id = Object.create(null); - this._id2Color = []; - } - - public getId2Color(): string[] { - return this._id2Color; - } - - public setZones(newZones: OverviewRulerZone[]): void { - newZones.sort((a, b) => a.compareTo(b)); - - let oldZones = this._zones; - let oldIndex = 0; - let oldLength = this._zones.length; - let newIndex = 0; - let newLength = newZones.length; - - let result: OverviewRulerZone[] = []; - while (newIndex < newLength) { - let newZone = newZones[newIndex]; - - if (oldIndex >= oldLength) { - result.push(newZone); - newIndex++; - } else { - let oldZone = oldZones[oldIndex]; - let cmp = oldZone.compareTo(newZone); - if (cmp < 0) { - oldIndex++; - } else if (cmp > 0) { - result.push(newZone); - newIndex++; - } else { - // cmp === 0 - result.push(oldZone); - oldIndex++; - newIndex++; - } - } - } - - this._zones = result; - } - - public setLineHeight(lineHeight:number): boolean { - if (this._lineHeight === lineHeight) { - return false; - } - this._lineHeight = lineHeight; - this._colorZonesInvalid = true; - return true; - } - - public getWidth(): number { - return this._width; - } - - public setWidth(width:number): boolean { - if (this._width === width) { - return false; - } - this._width = width; - this._colorZonesInvalid = true; - return true; - } - - public getHeight(): number { - return this._height; - } - - public setHeight(height:number): boolean { - if (this._height === height) { - return false; - } - this._height = height; - this._colorZonesInvalid = true; - return true; - } - - public getOuterHeight(): number { - return this._outerHeight; - } - - public setOuterHeight(outerHeight:number): boolean { - if (this._outerHeight === outerHeight) { - return false; - } - this._outerHeight = outerHeight; - this._colorZonesInvalid = true; - return true; - } - - public setMaximumHeight(maximumHeight:number): boolean { - if (this._maximumHeight === maximumHeight) { - return false; - } - this._maximumHeight = maximumHeight; - this._colorZonesInvalid = true; - return true; - } - - public setMinimumHeight(minimumHeight:number): boolean { - if (this._minimumHeight === minimumHeight) { - return false; - } - this._minimumHeight = minimumHeight; - this._colorZonesInvalid = true; - return true; - } - - public setUseDarkColor(useDarkColor:boolean): boolean { - if (this._useDarkColor === useDarkColor) { - return false; - } - this._useDarkColor = useDarkColor; - this._colorZonesInvalid = true; - return true; - } - - public resolveColorZones(): ColorZone[] { - const colorZonesInvalid = this._colorZonesInvalid; - const lineHeight = Math.floor(this._lineHeight); // @perf - const totalHeight = Math.floor(this._height); // @perf - const maximumHeight = Math.floor(this._maximumHeight); // @perf - const minimumHeight = Math.floor(this._minimumHeight); // @perf - const useDarkColor = this._useDarkColor; // @perf - const outerHeight = Math.floor(this._outerHeight); // @perf - const heightRatio = totalHeight / outerHeight; - - let allColorZones: ColorZone[] = []; - for (let i = 0, len = this._zones.length; i < len; i++) { - let zone = this._zones[i]; - - if (!colorZonesInvalid) { - let colorZones = zone.getColorZones(); - if (colorZones) { - for (let j = 0, lenJ = colorZones.length; j < lenJ; j++) { - allColorZones.push(colorZones[j]); - } - continue; - } - } - - let y1 = Math.floor(this._getVerticalOffsetForLine(zone.startLineNumber)); - let y2 = Math.floor(this._getVerticalOffsetForLine(zone.endLineNumber)) + lineHeight; - - y1 = Math.floor(y1 * heightRatio); - y2 = Math.floor(y2 * heightRatio); - - let colorZones: ColorZone[] = []; - if (zone.forceHeight) { - y2 = y1 + zone.forceHeight; - colorZones.push(this.createZone(totalHeight, y1, y2, zone.forceHeight, zone.forceHeight, zone.getColor(useDarkColor), zone.position)); - } else { - // Figure out if we can render this in one continuous zone - let zoneLineNumbers = zone.endLineNumber - zone.startLineNumber + 1; - let zoneMaximumHeight = zoneLineNumbers * maximumHeight; - - if (y2 - y1 > zoneMaximumHeight) { - // We need to draw one zone per line - for (let lineNumber = zone.startLineNumber; lineNumber <= zone.endLineNumber; lineNumber++) { - y1 = Math.floor(this._getVerticalOffsetForLine(lineNumber)); - y2 = y1 + lineHeight; - - y1 = Math.floor(y1 * heightRatio); - y2 = Math.floor(y2 * heightRatio); - - colorZones.push(this.createZone(totalHeight, y1, y2, minimumHeight, maximumHeight, zone.getColor(useDarkColor), zone.position)); - } - } else { - colorZones.push(this.createZone(totalHeight, y1, y2, minimumHeight, zoneMaximumHeight, zone.getColor(useDarkColor), zone.position)); - } - } - - zone.setColorZones(colorZones); - for (let j = 0, lenJ = colorZones.length; j < lenJ; j++) { - allColorZones.push(colorZones[j]); - } - } - - this._colorZonesInvalid = false; - - let sortFunc = (a:ColorZone, b:ColorZone) => { - if (a.colorId === b.colorId) { - if (a.from === b.from) { - return a.to - b.to; - } - return a.from - b.from; - } - return a.colorId - b.colorId; - }; - - allColorZones.sort(sortFunc); - return allColorZones; - } - - public createZone(totalHeight:number, y1:number, y2:number, minimumHeight:number, maximumHeight:number, color:string, position:OverviewRulerLane): ColorZone { - totalHeight = Math.floor(totalHeight); // @perf - y1 = Math.floor(y1); // @perf - y2 = Math.floor(y2); // @perf - minimumHeight = Math.floor(minimumHeight); // @perf - maximumHeight = Math.floor(maximumHeight); // @perf - - let ycenter = Math.floor((y1 + y2) / 2); - let halfHeight = (y2 - ycenter); - - - if (halfHeight > maximumHeight / 2) { - halfHeight = maximumHeight / 2; - } - if (halfHeight < minimumHeight / 2) { - halfHeight = minimumHeight / 2; - } - - if (ycenter - halfHeight < 0) { - ycenter = halfHeight; - } - if (ycenter + halfHeight > totalHeight) { - ycenter = totalHeight - halfHeight; - } - - let colorId = this._color2Id[color]; - if (!colorId) { - colorId = (++this._lastAssignedId); - this._color2Id[color] = colorId; - this._id2Color[colorId] = color; - } - return new ColorZone(ycenter - halfHeight, ycenter + halfHeight, colorId, position); - } -} +import {OverviewRulerPosition, OverviewRulerLane, OverviewRulerZone, ColorZone} from 'vs/editor/common/editorCommon'; +import {IDisposable} from 'vs/base/common/lifecycle'; +import * as browser from 'vs/base/browser/browser'; +import {OverviewZoneManager} from 'vs/editor/common/view/overviewZoneManager'; export class OverviewRulerImpl { @@ -276,9 +17,11 @@ export class OverviewRulerImpl { private _canvasLeftOffset: number; private _domNode: HTMLCanvasElement; private _lanesCount:number; - private _zoneManager: ZoneManager; + private _zoneManager: OverviewZoneManager; private _canUseTranslate3d: boolean; + private _zoomListener: IDisposable; + constructor(canvasLeftOffset:number, cssClassName:string, scrollHeight:number, lineHeight:number, canUseTranslate3d:boolean, minimumHeight:number, maximumHeight:number, getVerticalOffsetForLine:(lineNumber:number)=>number) { this._canvasLeftOffset = canvasLeftOffset; @@ -290,17 +33,28 @@ export class OverviewRulerImpl { this._canUseTranslate3d = canUseTranslate3d; - this._zoneManager = new ZoneManager(getVerticalOffsetForLine); + this._zoneManager = new OverviewZoneManager(getVerticalOffsetForLine); this._zoneManager.setMinimumHeight(minimumHeight); this._zoneManager.setMaximumHeight(maximumHeight); this._zoneManager.setUseDarkColor(false); - this._zoneManager.setWidth(0); - this._zoneManager.setHeight(0); + this._zoneManager.setDOMWidth(0); + this._zoneManager.setDOMHeight(0); this._zoneManager.setOuterHeight(scrollHeight); this._zoneManager.setLineHeight(lineHeight); + + this._zoomListener = browser.onDidChangeZoomLevel(() => { + this._zoneManager.setPixelRatio(browser.getPixelRatio()); + this._domNode.style.width = this._zoneManager.getDOMWidth() + 'px'; + this._domNode.style.height = this._zoneManager.getDOMHeight() + 'px'; + this._domNode.width = this._zoneManager.getCanvasWidth(); + this._domNode.height = this._zoneManager.getCanvasHeight(); + this.render(true); + }); + this._zoneManager.setPixelRatio(browser.getPixelRatio()); } public dispose(): void { + this._zoomListener.dispose(); this._zoneManager = null; } @@ -309,12 +63,14 @@ export class OverviewRulerImpl { StyleMutator.setRight(this._domNode, position.right); let hasChanged = false; - hasChanged = this._zoneManager.setWidth(position.width) || hasChanged; - hasChanged = this._zoneManager.setHeight(position.height) || hasChanged; + hasChanged = this._zoneManager.setDOMWidth(position.width) || hasChanged; + hasChanged = this._zoneManager.setDOMHeight(position.height) || hasChanged; if (hasChanged) { - this._domNode.width = this._zoneManager.getWidth(); - this._domNode.height = this._zoneManager.getHeight(); + this._domNode.style.width = this._zoneManager.getDOMWidth() + 'px'; + this._domNode.style.height = this._zoneManager.getDOMHeight() + 'px'; + this._domNode.width = this._zoneManager.getCanvasWidth(); + this._domNode.height = this._zoneManager.getCanvasHeight(); if (render) { this.render(true); @@ -346,12 +102,12 @@ export class OverviewRulerImpl { return this._domNode; } - public getWidth(): number { - return this._zoneManager.getWidth(); + public getPixelWidth(): number { + return this._zoneManager.getCanvasWidth(); } - public getHeight(): number { - return this._zoneManager.getHeight(); + public getPixelHeight(): number { + return this._zoneManager.getCanvasHeight(); } public setScrollHeight(scrollHeight:number, render:boolean): void { @@ -395,8 +151,8 @@ export class OverviewRulerImpl { StyleMutator.setTransform(this._domNode, ''); } - const width = this._zoneManager.getWidth(); - const height = this._zoneManager.getHeight(); + const width = this._zoneManager.getCanvasWidth(); + const height = this._zoneManager.getCanvasHeight(); let colorZones = this._zoneManager.resolveColorZones(); let id2Color = this._zoneManager.getId2Color(); diff --git a/src/vs/editor/browser/widget/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditorWidget.ts index 6b11a0ccd5b..b14a431bc10 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget.ts @@ -30,7 +30,7 @@ import {Selection} from 'vs/editor/common/core/selection'; interface IEditorDiffDecorations { decorations:editorCommon.IModelDeltaDecoration[]; - overviewZones:editorBrowser.OverviewRulerZone[]; + overviewZones:editorCommon.OverviewRulerZone[]; } interface IEditorDiffDecorationsWithZones extends IEditorDiffDecorations { @@ -1476,7 +1476,7 @@ class DiffEdtorWidgetSideBySide extends DiffEditorWidgetStyle implements IDiffEd result.decorations.push(createDecoration(lineChange.originalStartLineNumber, 1, lineChange.originalEndLineNumber, Number.MAX_VALUE, 'char-delete', true)); } - result.overviewZones.push(new editorBrowser.OverviewRulerZone( + result.overviewZones.push(new editorCommon.OverviewRulerZone( lineChange.originalStartLineNumber, lineChange.originalEndLineNumber, editorCommon.OverviewRulerLane.Full, @@ -1541,7 +1541,7 @@ class DiffEdtorWidgetSideBySide extends DiffEditorWidgetStyle implements IDiffEd if (!isChangeOrDelete(lineChange) || !lineChange.charChanges) { result.decorations.push(createDecoration(lineChange.modifiedStartLineNumber, 1, lineChange.modifiedEndLineNumber, Number.MAX_VALUE, 'char-insert', true)); } - result.overviewZones.push(new editorBrowser.OverviewRulerZone( + result.overviewZones.push(new editorCommon.OverviewRulerZone( lineChange.modifiedStartLineNumber, lineChange.modifiedEndLineNumber, editorCommon.OverviewRulerLane.Full, @@ -1656,7 +1656,7 @@ class DiffEdtorWidgetInline extends DiffEditorWidgetStyle implements IDiffEditor // Add overview zones in the overview ruler if (isChangeOrDelete(lineChange)) { - result.overviewZones.push(new editorBrowser.OverviewRulerZone( + result.overviewZones.push(new editorCommon.OverviewRulerZone( lineChange.originalStartLineNumber, lineChange.originalEndLineNumber, editorCommon.OverviewRulerLane.Full, @@ -1694,7 +1694,7 @@ class DiffEdtorWidgetInline extends DiffEditorWidgetStyle implements IDiffEditor if (isChangeOrInsert(lineChange)) { result.decorations.push(createDecoration(lineChange.modifiedStartLineNumber, 1, lineChange.modifiedEndLineNumber, Number.MAX_VALUE, 'line-insert', true)); - result.overviewZones.push(new editorBrowser.OverviewRulerZone( + result.overviewZones.push(new editorCommon.OverviewRulerZone( lineChange.modifiedStartLineNumber, lineChange.modifiedEndLineNumber, editorCommon.OverviewRulerLane.Full, diff --git a/src/vs/editor/common/editorCommon.ts b/src/vs/editor/common/editorCommon.ts index c5ac18d23a5..ebee3689655 100644 --- a/src/vs/editor/common/editorCommon.ts +++ b/src/vs/editor/common/editorCommon.ts @@ -3600,3 +3600,100 @@ export function cursorStyleToString(cursorStyle:TextEditorCursorStyle): string { throw new Error('cursorStyleToString: Unknown cursorStyle'); } } + +export class ColorZone { + _colorZoneBrand: void; + + from: number; + to: number; + colorId: number; + position: OverviewRulerLane; + + constructor(from:number, to:number, colorId:number, position: OverviewRulerLane) { + this.from = from|0; + this.to = to|0; + this.colorId = colorId|0; + this.position = position|0; + } +} + +/** + * A zone in the overview ruler + */ +export class OverviewRulerZone { + _overviewRulerZoneBrand: void; + + startLineNumber: number; + endLineNumber: number; + position: OverviewRulerLane; + forceHeight: number; + + private _color: string; + private _darkColor: string; + + private _colorZones: ColorZone[]; + + constructor( + startLineNumber: number, endLineNumber: number, + position: OverviewRulerLane, + forceHeight: number, + color: string, darkColor: string + ) { + this.startLineNumber = startLineNumber; + this.endLineNumber = endLineNumber; + this.position = position; + this.forceHeight = forceHeight; + this._color = color; + this._darkColor = darkColor; + this._colorZones = null; + } + + public getColor(useDarkColor:boolean): string { + if (useDarkColor) { + return this._darkColor; + } + return this._color; + } + + public equals(other:OverviewRulerZone): boolean { + return ( + this.startLineNumber === other.startLineNumber + && this.endLineNumber === other.endLineNumber + && this.position === other.position + && this.forceHeight === other.forceHeight + && this._color === other._color + && this._darkColor === other._darkColor + ); + } + + public compareTo(other:OverviewRulerZone): number { + if (this.startLineNumber === other.startLineNumber) { + if (this.endLineNumber === other.endLineNumber) { + if (this.forceHeight === other.forceHeight) { + if (this.position === other.position) { + if (this._darkColor === other._darkColor) { + if (this._color === other._color) { + return 0; + } + return this._color < other._color ? -1 : 1; + } + return this._darkColor < other._darkColor ? -1 : 1; + } + return this.position - other.position; + } + return this.forceHeight - other.forceHeight; + } + return this.endLineNumber - other.endLineNumber; + } + return this.startLineNumber - other.startLineNumber; + } + + public setColorZones(colorZones:ColorZone[]): void { + this._colorZones = colorZones; + } + + public getColorZones(): ColorZone[] { + return this._colorZones; + } +} + diff --git a/src/vs/editor/common/view/overviewZoneManager.ts b/src/vs/editor/common/view/overviewZoneManager.ts new file mode 100644 index 00000000000..9c19fe5ff19 --- /dev/null +++ b/src/vs/editor/common/view/overviewZoneManager.ts @@ -0,0 +1,288 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import {OverviewRulerLane, OverviewRulerZone, ColorZone} from 'vs/editor/common/editorCommon'; + +export class OverviewZoneManager { + + private _getVerticalOffsetForLine:(lineNumber:number)=>number; + private _zones: OverviewRulerZone[]; + private _colorZonesInvalid: boolean; + private _lineHeight: number; + private _domWidth: number; + private _domHeight: number; + private _outerHeight: number; + private _maximumHeight: number; + private _minimumHeight: number; + private _useDarkColor: boolean; + private _pixelRatio: number; + + private _lastAssignedId; + private _color2Id: { [color:string]: number; }; + private _id2Color: string[]; + + constructor(getVerticalOffsetForLine:(lineNumber:number)=>number) { + this._getVerticalOffsetForLine = getVerticalOffsetForLine; + this._zones = []; + this._colorZonesInvalid = false; + this._lineHeight = 0; + this._domWidth = 0; + this._domHeight = 0; + this._outerHeight = 0; + this._maximumHeight = 0; + this._minimumHeight = 0; + this._useDarkColor = false; + this._pixelRatio = 1; + + this._lastAssignedId = 0; + this._color2Id = Object.create(null); + this._id2Color = []; + } + + public getId2Color(): string[] { + return this._id2Color; + } + + public setZones(newZones: OverviewRulerZone[]): void { + newZones.sort((a, b) => a.compareTo(b)); + + let oldZones = this._zones; + let oldIndex = 0; + let oldLength = this._zones.length; + let newIndex = 0; + let newLength = newZones.length; + + let result: OverviewRulerZone[] = []; + while (newIndex < newLength) { + let newZone = newZones[newIndex]; + + if (oldIndex >= oldLength) { + result.push(newZone); + newIndex++; + } else { + let oldZone = oldZones[oldIndex]; + let cmp = oldZone.compareTo(newZone); + if (cmp < 0) { + oldIndex++; + } else if (cmp > 0) { + result.push(newZone); + newIndex++; + } else { + // cmp === 0 + result.push(oldZone); + oldIndex++; + newIndex++; + } + } + } + + this._zones = result; + } + + public setLineHeight(lineHeight:number): boolean { + if (this._lineHeight === lineHeight) { + return false; + } + this._lineHeight = lineHeight; + this._colorZonesInvalid = true; + return true; + } + + public setPixelRatio(pixelRatio:number): void { + this._pixelRatio = pixelRatio; + this._colorZonesInvalid = true; + } + + public getDOMWidth(): number { + return this._domWidth; + } + + public getCanvasWidth(): number { + return this._domWidth * this._pixelRatio; + } + + public setDOMWidth(width:number): boolean { + if (this._domWidth === width) { + return false; + } + this._domWidth = width; + this._colorZonesInvalid = true; + return true; + } + + public getDOMHeight(): number { + return this._domHeight; + } + + public getCanvasHeight(): number { + return this._domHeight * this._pixelRatio; + } + + public setDOMHeight(height:number): boolean { + if (this._domHeight === height) { + return false; + } + this._domHeight = height; + this._colorZonesInvalid = true; + return true; + } + + public getOuterHeight(): number { + return this._outerHeight; + } + + public setOuterHeight(outerHeight:number): boolean { + if (this._outerHeight === outerHeight) { + return false; + } + this._outerHeight = outerHeight; + this._colorZonesInvalid = true; + return true; + } + + public setMaximumHeight(maximumHeight:number): boolean { + if (this._maximumHeight === maximumHeight) { + return false; + } + this._maximumHeight = maximumHeight; + this._colorZonesInvalid = true; + return true; + } + + public setMinimumHeight(minimumHeight:number): boolean { + if (this._minimumHeight === minimumHeight) { + return false; + } + this._minimumHeight = minimumHeight; + this._colorZonesInvalid = true; + return true; + } + + public setUseDarkColor(useDarkColor:boolean): boolean { + if (this._useDarkColor === useDarkColor) { + return false; + } + this._useDarkColor = useDarkColor; + this._colorZonesInvalid = true; + return true; + } + + public resolveColorZones(): ColorZone[] { + const colorZonesInvalid = this._colorZonesInvalid; + const lineHeight = Math.floor(this._lineHeight); // @perf + const totalHeight = Math.floor(this.getCanvasHeight()); // @perf + const maximumHeight = Math.floor(this._maximumHeight * this._pixelRatio); // @perf + const minimumHeight = Math.floor(this._minimumHeight * this._pixelRatio); // @perf + const useDarkColor = this._useDarkColor; // @perf + const outerHeight = Math.floor(this._outerHeight); // @perf + const heightRatio = totalHeight / outerHeight; + + let allColorZones: ColorZone[] = []; + for (let i = 0, len = this._zones.length; i < len; i++) { + let zone = this._zones[i]; + + if (!colorZonesInvalid) { + let colorZones = zone.getColorZones(); + if (colorZones) { + for (let j = 0, lenJ = colorZones.length; j < lenJ; j++) { + allColorZones.push(colorZones[j]); + } + continue; + } + } + + let colorZones: ColorZone[] = []; + if (zone.forceHeight) { + let forcedHeight = Math.floor(zone.forceHeight * this._pixelRatio); + + let y1 = Math.floor(this._getVerticalOffsetForLine(zone.startLineNumber)); + y1 = Math.floor(y1 * heightRatio); + + let y2 = y1 + forcedHeight; + colorZones.push(this.createZone(totalHeight, y1, y2, forcedHeight, forcedHeight, zone.getColor(useDarkColor), zone.position)); + } else { + let y1 = Math.floor(this._getVerticalOffsetForLine(zone.startLineNumber)); + let y2 = Math.floor(this._getVerticalOffsetForLine(zone.endLineNumber)) + lineHeight; + + y1 = Math.floor(y1 * heightRatio); + y2 = Math.floor(y2 * heightRatio); + + // Figure out if we can render this in one continuous zone + let zoneLineNumbers = zone.endLineNumber - zone.startLineNumber + 1; + let zoneMaximumHeight = zoneLineNumbers * maximumHeight; + + if (y2 - y1 > zoneMaximumHeight) { + // We need to draw one zone per line + for (let lineNumber = zone.startLineNumber; lineNumber <= zone.endLineNumber; lineNumber++) { + y1 = Math.floor(this._getVerticalOffsetForLine(lineNumber)); + y2 = y1 + lineHeight; + + y1 = Math.floor(y1 * heightRatio); + y2 = Math.floor(y2 * heightRatio); + + colorZones.push(this.createZone(totalHeight, y1, y2, minimumHeight, maximumHeight, zone.getColor(useDarkColor), zone.position)); + } + } else { + colorZones.push(this.createZone(totalHeight, y1, y2, minimumHeight, zoneMaximumHeight, zone.getColor(useDarkColor), zone.position)); + } + } + + zone.setColorZones(colorZones); + for (let j = 0, lenJ = colorZones.length; j < lenJ; j++) { + allColorZones.push(colorZones[j]); + } + } + + this._colorZonesInvalid = false; + + let sortFunc = (a:ColorZone, b:ColorZone) => { + if (a.colorId === b.colorId) { + if (a.from === b.from) { + return a.to - b.to; + } + return a.from - b.from; + } + return a.colorId - b.colorId; + }; + + allColorZones.sort(sortFunc); + return allColorZones; + } + + public createZone(totalHeight:number, y1:number, y2:number, minimumHeight:number, maximumHeight:number, color:string, position:OverviewRulerLane): ColorZone { + totalHeight = Math.floor(totalHeight); // @perf + y1 = Math.floor(y1); // @perf + y2 = Math.floor(y2); // @perf + minimumHeight = Math.floor(minimumHeight); // @perf + maximumHeight = Math.floor(maximumHeight); // @perf + + let ycenter = Math.floor((y1 + y2) / 2); + let halfHeight = (y2 - ycenter); + + + if (halfHeight > maximumHeight / 2) { + halfHeight = maximumHeight / 2; + } + if (halfHeight < minimumHeight / 2) { + halfHeight = minimumHeight / 2; + } + + if (ycenter - halfHeight < 0) { + ycenter = halfHeight; + } + if (ycenter + halfHeight > totalHeight) { + ycenter = totalHeight - halfHeight; + } + + let colorId = this._color2Id[color]; + if (!colorId) { + colorId = (++this._lastAssignedId); + this._color2Id[color] = colorId; + this._id2Color[colorId] = color; + } + return new ColorZone(ycenter - halfHeight, ycenter + halfHeight, colorId, position); + } +} diff --git a/src/vs/editor/test/common/view/overviewZoneManager.test.ts b/src/vs/editor/test/common/view/overviewZoneManager.test.ts new file mode 100644 index 00000000000..522b3241e47 --- /dev/null +++ b/src/vs/editor/test/common/view/overviewZoneManager.test.ts @@ -0,0 +1,100 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import * as assert from 'assert'; +import {OverviewRulerLane, OverviewRulerZone, ColorZone} from 'vs/editor/common/editorCommon'; +import {OverviewZoneManager} from 'vs/editor/common/view/overviewZoneManager'; + +suite('Editor View - OverviewZoneManager', () => { + + test('pixel ratio 1, dom height 600', () => { + const LINE_COUNT = 50; + const LINE_HEIGHT = 20; + let manager = new OverviewZoneManager((lineNumber) => LINE_HEIGHT * lineNumber); + manager.setMinimumHeight(6); + manager.setMaximumHeight(6); + manager.setUseDarkColor(false); + manager.setDOMWidth(30); + manager.setDOMHeight(600); + manager.setOuterHeight(LINE_COUNT * LINE_HEIGHT); + manager.setLineHeight(LINE_HEIGHT); + manager.setPixelRatio(1); + + manager.setZones([ + new OverviewRulerZone(1, 1, OverviewRulerLane.Full, 10, '1', '1'), + new OverviewRulerZone(10, 10, OverviewRulerLane.Full, 0, '2', '2'), + new OverviewRulerZone(30, 31, OverviewRulerLane.Full, 0, '3', '3'), + new OverviewRulerZone(50, 50, OverviewRulerLane.Full, 0, '4', '4'), + ]); + + // one line = 12, but cap is at 6 + assert.deepEqual(manager.resolveColorZones(), [ + new ColorZone(12, 22, 1, OverviewRulerLane.Full), // forced height of 10 + new ColorZone(123, 129, 2, OverviewRulerLane.Full), // 120 -> 132 + new ColorZone(363, 369, 3, OverviewRulerLane.Full), // 360 -> 372 [360 -> 384] + new ColorZone(375, 381, 3, OverviewRulerLane.Full), // 372 -> 384 [360 -> 384] + new ColorZone(594, 600, 4, OverviewRulerLane.Full), // 588 -> 600 + ]); + }); + + test('pixel ratio 1, dom height 300', () => { + const LINE_COUNT = 50; + const LINE_HEIGHT = 20; + let manager = new OverviewZoneManager((lineNumber) => LINE_HEIGHT * lineNumber); + manager.setMinimumHeight(6); + manager.setMaximumHeight(6); + manager.setUseDarkColor(false); + manager.setDOMWidth(30); + manager.setDOMHeight(300); + manager.setOuterHeight(LINE_COUNT * LINE_HEIGHT); + manager.setLineHeight(LINE_HEIGHT); + manager.setPixelRatio(1); + + manager.setZones([ + new OverviewRulerZone(1, 1, OverviewRulerLane.Full, 10, '1', '1'), + new OverviewRulerZone(10, 10, OverviewRulerLane.Full, 0, '2', '2'), + new OverviewRulerZone(30, 31, OverviewRulerLane.Full, 0, '3', '3'), + new OverviewRulerZone(50, 50, OverviewRulerLane.Full, 0, '4', '4'), + ]); + + // one line = 6, cap is at 6 + assert.deepEqual(manager.resolveColorZones(), [ + new ColorZone(6, 16, 1, OverviewRulerLane.Full), // forced height of 10 + new ColorZone(60, 66, 2, OverviewRulerLane.Full), // 60 -> 66 + new ColorZone(180, 192, 3, OverviewRulerLane.Full), // 180 -> 192 + new ColorZone(294, 300, 4, OverviewRulerLane.Full), // 294 -> 300 + ]); + }); + + test('pixel ratio 2, dom height 300', () => { + const LINE_COUNT = 50; + const LINE_HEIGHT = 20; + let manager = new OverviewZoneManager((lineNumber) => LINE_HEIGHT * lineNumber); + manager.setMinimumHeight(6); + manager.setMaximumHeight(6); + manager.setUseDarkColor(false); + manager.setDOMWidth(30); + manager.setDOMHeight(300); + manager.setOuterHeight(LINE_COUNT * LINE_HEIGHT); + manager.setLineHeight(LINE_HEIGHT); + manager.setPixelRatio(2); + + manager.setZones([ + new OverviewRulerZone(1, 1, OverviewRulerLane.Full, 10, '1', '1'), + new OverviewRulerZone(10, 10, OverviewRulerLane.Full, 0, '2', '2'), + new OverviewRulerZone(30, 31, OverviewRulerLane.Full, 0, '3', '3'), + new OverviewRulerZone(50, 50, OverviewRulerLane.Full, 0, '4', '4'), + ]); + + // one line = 6, cap is at 12 + assert.deepEqual(manager.resolveColorZones(), [ + new ColorZone(12, 32, 1, OverviewRulerLane.Full), // forced height of 10 => forced height of 20 + new ColorZone(120, 132, 2, OverviewRulerLane.Full), // 120 -> 132 + new ColorZone(360, 384, 3, OverviewRulerLane.Full), // 360 -> 384 + new ColorZone(588, 600, 4, OverviewRulerLane.Full), // 588 -> 600 + ]); + }); +});