diff --git a/src/browser/Decorations/ColorZoneStore.ts b/src/browser/Decorations/ColorZoneStore.ts new file mode 100644 index 00000000..c5823234 --- /dev/null +++ b/src/browser/Decorations/ColorZoneStore.ts @@ -0,0 +1,94 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IInternalDecoration } from 'common/services/Services'; +import { IDecorationOverviewRulerOptions } from 'xterm'; + +export interface IColorZoneStore { + readonly zones: IColorZone[]; + clear(): void; + addDecoration(decoration: IInternalDecoration): void; + /** + * Sets the amount of padding in lines that will be added between zones, if new lines intersect + * the padding they will be merged into the same zone. + */ + setPadding(padding: { [position: string]: number }): void; +} + +export interface IColorZone { + /** Color in a format supported by canvas' fillStyle. */ + color: string; + position: 'full' | 'left' | 'center' | 'right' | undefined; + startBufferLine: number; + endBufferLine: number; +} + +export class ColorZoneStore implements IColorZoneStore { + private _zones: IColorZone[] = []; + public get zones(): IColorZone[] { return this._zones; } + + private _linePadding: { [position: string]: number } = { + full: 0, + left: 0, + center: 0, + right: 0 + }; + + public clear(): void { + this._zones.length = 0; + } + + public addDecoration(decoration: IInternalDecoration): void { + if (!decoration.options.overviewRulerOptions) { + return; + } + for (const z of this._zones) { + if (z.color === decoration.options.overviewRulerOptions.color && + z.position === decoration.options.overviewRulerOptions.position) { + if (this._lineIntersectsZone(z, decoration.marker.line)) { + console.log('intersects, skip'); + // this._addLineToZone(z, decoration.marker.line); + return; + } + if (this._lineAdjacentToZone(z, decoration.marker.line, decoration.options.overviewRulerOptions.position)) { + this._addLineToZone(z, decoration.marker.line); + console.log('adjacent, add'); + return; + } + } + } + // TODO: Track zones in an object pool to reduce GC + this._zones.push({ + color: decoration.options.overviewRulerOptions.color, + position: decoration.options.overviewRulerOptions.position, + startBufferLine: decoration.marker.line, + endBufferLine: decoration.marker.line + }); + } + + public setPadding(padding: { [position: string]: number }): void { + console.log('padding', padding); + this._linePadding = padding; + } + + private _lineIntersectsZone(zone: IColorZone, line: number): boolean { + return ( + line >= zone.startBufferLine && + line <= zone.endBufferLine + ); + } + + private _lineAdjacentToZone(zone: IColorZone, line: number, position: IColorZone['position']): boolean { + return ( + (line >= zone.startBufferLine - 1 - this._linePadding[position || 'full'] * 2) && + (line <= zone.endBufferLine + 1 + this._linePadding[position || 'full'] * 2) + ); + } + + private _addLineToZone(zone: IColorZone, line: number): void { + zone.startBufferLine = Math.min(zone.startBufferLine, line); + zone.endBufferLine = Math.max(zone.endBufferLine, line); + } +} diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index 1407f4bb..8e55dec5 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { ColorZoneStore, IColorZoneStore } from 'browser/Decorations/ColorZoneStore'; import { addDisposableDomListener } from 'browser/Lifecycle'; import { IRenderService } from 'browser/services/Services'; import { Disposable } from 'common/Lifecycle'; @@ -33,6 +34,7 @@ export class OverviewRulerRenderer extends Disposable { private readonly _canvas: HTMLCanvasElement; private readonly _ctx: CanvasRenderingContext2D; private readonly _decorationElements: Map = new Map(); + private readonly _colorZoneStore: IColorZoneStore = new ColorZoneStore(); private get _width(): number { return this._optionsService.options.overviewRulerWidth || 0; } @@ -40,6 +42,7 @@ export class OverviewRulerRenderer extends Disposable { private _shouldUpdateDimensions: boolean | undefined = true; private _shouldUpdateAnchor: boolean | undefined = true; + private _lastKnownBufferLength: number = 0; private _containerHeight: number | undefined; @@ -84,6 +87,11 @@ export class OverviewRulerRenderer extends Disposable { this.register(this._bufferService.buffers.onBufferActivate(() => { this._canvas!.style.display = this._bufferService.buffer === this._bufferService.buffers.alt ? 'none' : 'block'; })); + this.register(this._bufferService.onScroll(() => { + if (this._lastKnownBufferLength !== this._bufferService.buffers.normal.lines.length) { + this._refreshColorZonePadding(); + } + })); } /** * On dimension change, update canvas dimensions @@ -140,6 +148,17 @@ export class OverviewRulerRenderer extends Disposable { drawX.right = drawWidth.left + drawWidth.center; } + private _refreshColorZonePadding(): void { + const nonFullPadding = Math.ceil(this._bufferService.buffers.active.lines.length / (this._canvas.height - 1) * (drawHeight.full / 2)); + this._colorZoneStore.setPadding({ + full: Math.ceil(this._bufferService.buffers.active.lines.length / (this._canvas.height - 1) * (drawHeight.full / 2)), + left: nonFullPadding, + center: nonFullPadding, + right: nonFullPadding + }); + this._lastKnownBufferLength = this._bufferService.buffers.normal.lines.length; + } + private _refreshStyle(decoration: IInternalDecoration): void { if (!decoration.options.overviewRulerOptions) { this._decorationElements.delete(decoration); @@ -151,7 +170,7 @@ export class OverviewRulerRenderer extends Disposable { /* x */ drawX[decoration.options.overviewRulerOptions.position!], /* y */ Math.round( (this._canvas.height - 1) * // -1 to ensure at least 2px are allowed for decoration on last line - (decoration.options.marker.line / this._bufferService.buffers.active.lines.length) - drawHeight[decoration.options.overviewRulerOptions.position!] / 2 + (decoration.options.marker.line / this._bufferService.buffers.active.lines.length) - drawHeight[decoration.options.overviewRulerOptions.position!] / 2 ), /* w */ drawWidth[decoration.options.overviewRulerOptions.position!], /* h */ drawHeight[decoration.options.overviewRulerOptions.position!] @@ -164,6 +183,7 @@ export class OverviewRulerRenderer extends Disposable { this._canvas.style.height = `${this._screenElement.clientHeight}px`; this._canvas.height = Math.round(this._screenElement.clientHeight * window.devicePixelRatio); this._refreshDrawConstants(); + this._refreshColorZonePadding(); } private _refreshDecorations(): void { @@ -171,7 +191,9 @@ export class OverviewRulerRenderer extends Disposable { this._refreshCanvasDimensions(); } this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); + this._colorZoneStore.clear(); for (const decoration of this._decorationService.decorations) { + this._colorZoneStore.addDecoration(decoration); if (decoration.options.overviewRulerOptions && decoration.options.overviewRulerOptions.position !== 'full') { this._renderDecoration(decoration); } @@ -181,6 +203,7 @@ export class OverviewRulerRenderer extends Disposable { this._renderDecoration(decoration); } } + console.log('zones', this._colorZoneStore.zones); this._shouldUpdateDimensions = false; this._shouldUpdateAnchor = false; }