Skip to content
Merged
Show file tree
Hide file tree
Changes from 68 commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
a5a9c3b
add scroll decorations
meganrogge Mar 8, 2022
4350c7f
get it to work
meganrogge Mar 8, 2022
3a499fc
clear on dispose
meganrogge Mar 9, 2022
785a819
clean up
meganrogge Mar 9, 2022
d46a685
more cleanup
meganrogge Mar 9, 2022
89112fb
refactor to return / store IDecorations
meganrogge Mar 9, 2022
249e73c
delete unused code
meganrogge Mar 9, 2022
1948ec8
only clear relevant part of canvas on marker dispose
meganrogge Mar 9, 2022
cfd7912
enable registering a decoration before attach to dom has happened
meganrogge Mar 9, 2022
e797070
call on render
meganrogge Mar 9, 2022
724bbeb
assign element in render call
meganrogge Mar 9, 2022
89a0cee
when alt buffer is active, hide decorations
meganrogge Mar 9, 2022
167a6fd
use position sticky
meganrogge Mar 9, 2022
bdfc82d
delete unused import
meganrogge Mar 10, 2022
2f85448
Update css/xterm.css
meganrogge Mar 11, 2022
98a03dd
re-arrange dom structure
meganrogge Mar 11, 2022
a14248d
Merge branch 'merogge/scroll-bar-decorations' of https://github.com/m…
meganrogge Mar 11, 2022
1250ab6
insert before
meganrogge Mar 11, 2022
4022277
scroll -> overviewRuler
meganrogge Mar 12, 2022
692df15
part 1 of massive refactor
meganrogge Mar 12, 2022
9e598d9
allow setting width
meganrogge Mar 14, 2022
3b279e4
round to the nearest pixel
meganrogge Mar 14, 2022
ced0662
large refactor, broken
meganrogge Mar 14, 2022
7dc3332
commit before reverting
meganrogge Mar 14, 2022
c8d1266
Fix buffer decoration onRender, start using internal decoration
Tyriar Mar 14, 2022
4b615f4
Reduce state in buffer decorations
Tyriar Mar 14, 2022
65b9a6c
Fix dependency injection for decoration service
Tyriar Mar 14, 2022
3dc4f1a
Remove old decoration elements
Tyriar Mar 14, 2022
fc09730
Tidy up DecorationService
Tyriar Mar 14, 2022
bfb0481
register service
meganrogge Mar 15, 2022
079bbc8
get it working in demo with overviewRulerwidth not considered"
meganrogge Mar 15, 2022
3e47c96
get demo to work the right way
meganrogge Mar 15, 2022
39f9c35
rename css, fix background color getting applied
meganrogge Mar 15, 2022
4ef5f6a
add listener for on option change
meganrogge Mar 15, 2022
520d5dd
delete unused css
meganrogge Mar 15, 2022
f98b4ce
properly dispose of buffer decoration
meganrogge Mar 15, 2022
80e7963
fix tests and add one
meganrogge Mar 15, 2022
e7e9d5c
remove test
meganrogge Mar 15, 2022
df929f2
add position
meganrogge Mar 15, 2022
bd21c6a
adjust test
meganrogge Mar 15, 2022
71649f5
fix tests
meganrogge Mar 15, 2022
adaee5e
use poll for instead
meganrogge Mar 15, 2022
1905d7d
add overview ruler tests
meganrogge Mar 15, 2022
21263a5
cleanup
meganrogge Mar 15, 2022
a93f81c
Update typings/xterm.d.ts
meganrogge Mar 15, 2022
8488f1a
Update src/browser/Decorations/OverviewRulerRenderer.ts
meganrogge Mar 15, 2022
5b87772
Update typings/xterm.d.ts
meganrogge Mar 15, 2022
45ecc83
Update typings/xterm.d.ts
meganrogge Mar 15, 2022
b146ffd
Update src/browser/Decorations/OverviewRulerRenderer.ts
meganrogge Mar 15, 2022
fdc2bc4
Update typings/xterm.d.ts
meganrogge Mar 15, 2022
cee2411
more cleanup
meganrogge Mar 15, 2022
1c38f85
Merge branch 'merogge/scroll-bar-decorations' of https://github.com/m…
meganrogge Mar 15, 2022
4cc0db4
Update src/browser/Decorations/OverviewRulerRenderer.ts
meganrogge Mar 15, 2022
e7baccd
Update src/browser/Terminal.ts
meganrogge Mar 15, 2022
9f6d4f5
Merge branch 'master' into merogge/scroll-bar-decorations
meganrogge Mar 15, 2022
a9a6920
more cleanup
meganrogge Mar 15, 2022
19b212d
Merge branch 'merogge/scroll-bar-decorations' of https://github.com/m…
meganrogge Mar 15, 2022
8ccbef8
only update canvas dimensions when needed
meganrogge Mar 15, 2022
53c432d
add condition for when to update anchor
meganrogge Mar 15, 2022
b05db37
adjust size based on position
meganrogge Mar 15, 2022
d92e636
Update src/browser/Terminal.ts
meganrogge Mar 15, 2022
035a7f7
Update src/browser/Terminal.ts
meganrogge Mar 15, 2022
5777fbc
Update src/browser/tsconfig.json
meganrogge Mar 15, 2022
6f5008d
Update typings/xterm.d.ts
meganrogge Mar 15, 2022
a4f830a
Update typings/xterm.d.ts
meganrogge Mar 15, 2022
22d889a
use this._width instead of options
meganrogge Mar 15, 2022
963ed59
tweak demo
meganrogge Mar 15, 2022
7f85922
Merge branch 'merogge/scroll-bar-decorations' of https://github.com/m…
meganrogge Mar 15, 2022
c277fb7
add overviewRuler to addDecoration
meganrogge Mar 16, 2022
a0a7abb
add overview options to decoration in demo
meganrogge Mar 16, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions css/xterm.css
Original file line number Diff line number Diff line change
Expand Up @@ -178,3 +178,10 @@
z-index: 6;
position: absolute;
}

.xterm-decoration-overview-ruler {
z-index: 7;
position: absolute;
top: 0;
right: 0;
}
18 changes: 16 additions & 2 deletions demo/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ if (document.location.pathname === '/test') {
document.getElementById('custom-glyph').addEventListener('click', writeCustomGlyphHandler);
document.getElementById('load-test').addEventListener('click', loadTest);
document.getElementById('add-decoration').addEventListener('click', addDecoration);
document.getElementById('add-overview-ruler').addEventListener('click', addOverviewRuler);
}

function createTerminal(): void {
Expand Down Expand Up @@ -546,7 +547,20 @@ function loadTest() {
function addDecoration() {
const marker = term.addMarker(1);
const decoration = term.registerDecoration({ marker });
decoration.onRender(() => {
decoration.element.style.backgroundColor = 'red';
decoration.onRender((e) => {
if (e.classList.value === 'xterm-decoration') {
e.style.backgroundColor = 'red';
}
});
}

function addOverviewRuler() {
term.options['overviewRulerWidth'] = 15;
term.registerDecoration({marker: term.addMarker(1), overviewRulerOptions: { color: '#ef2929' }});
term.registerDecoration({marker: term.addMarker(3), overviewRulerOptions: { color: '#8ae234' }});
term.registerDecoration({marker: term.addMarker(5), overviewRulerOptions: { color: '#729fcf' }});
term.registerDecoration({marker: term.addMarker(7), overviewRulerOptions: { color: '#ef2929', position: 'left' }});
term.registerDecoration({marker: term.addMarker(7), overviewRulerOptions: { color: '#8ae234', position: 'center' }});
term.registerDecoration({marker: term.addMarker(7), overviewRulerOptions: { color: '#729fcf', position: 'right' }});
}

1 change: 1 addition & 0 deletions demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ <h3>Test</h3>
<button id="custom-glyph" title="Write custom box drawing and block element characters to the terminal">Test custom glyphs</button>
<button id="load-test" title="Write several MB of data to simulate a lot of data coming from the process">Load test</button>
<button id="add-decoration" title="Add a decoration to the terminal">Decoration</button>
<button id="add-overview-ruler" title="Add an overview ruler to the terminal">Add Overview Ruler</button>
</div>
</div>
</div>
Expand Down
113 changes: 113 additions & 0 deletions src/browser/Decorations/BufferDecorationRenderer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { addDisposableDomListener } from 'browser/Lifecycle';
import { IRenderService } from 'browser/services/Services';
import { Disposable } from 'common/Lifecycle';
import { IBufferService, IDecorationService, IInternalDecoration } from 'common/services/Services';

export class BufferDecorationRenderer extends Disposable {
private readonly _container: HTMLElement;
private readonly _decorationElements: Map<IInternalDecoration, HTMLElement> = new Map();

private _animationFrame: number | undefined;
private _altBufferIsActive: boolean = false;

constructor(
private readonly _screenElement: HTMLElement,
@IBufferService private readonly _bufferService: IBufferService,
@IDecorationService private readonly _decorationService: IDecorationService,
@IRenderService private readonly _renderService: IRenderService
) {
super();

this._container = document.createElement('div');
this._container.classList.add('xterm-decoration-container');
this._screenElement.appendChild(this._container);

this.register(this._renderService.onRenderedBufferChange(() => this._queueRefresh()));
this.register(this._renderService.onDimensionsChange(() => this._queueRefresh()));
this.register(addDisposableDomListener(window, 'resize', () => this._queueRefresh()));
this.register(this._bufferService.buffers.onBufferActivate(() => {
this._altBufferIsActive = this._bufferService.buffer === this._bufferService.buffers.alt;
}));
this.register(this._decorationService.onDecorationRegistered(() => this._queueRefresh()));
this.register(this._decorationService.onDecorationRemoved(decoration => this._removeDecoration(decoration)));
}

public override dispose(): void {
this._container.remove();
this._decorationElements.clear();
super.dispose();
}

private _queueRefresh(): void {
if (this._animationFrame !== undefined) {
return;
}
this._animationFrame = window.requestAnimationFrame(() => {
this.refreshDecorations();
this._animationFrame = undefined;
});
}

public refreshDecorations(): void {
for (const decoration of this._decorationService.decorations) {
this._renderDecoration(decoration);
}
}

private _renderDecoration(decoration: IInternalDecoration): void {
let element = this._decorationElements.get(decoration);
if (!element) {
element = this._createElement(decoration);
decoration.onDispose(() => this._removeDecoration(decoration));
decoration.marker.onDispose(() => decoration.dispose());
decoration.element = element;
this._decorationElements.set(decoration, element);
this._container.appendChild(element);
}
this._refreshStyle(decoration, element);
decoration.onRenderEmitter.fire(element);
}

private _createElement(decoration: IInternalDecoration): HTMLElement {
const element = document.createElement('div');
element.classList.add('xterm-decoration');
element.style.width = `${(decoration.options.width || 1) * this._renderService.dimensions.actualCellWidth}px`;
element.style.height = `${(decoration.options.height || 1) * this._renderService.dimensions.actualCellHeight}px`;
element.style.top = `${(decoration.marker.line - this._bufferService.buffers.active.ydisp) * this._renderService.dimensions.actualCellHeight}px`;
element.style.lineHeight = `${this._renderService.dimensions.actualCellHeight}px`;

const x = decoration.options.x ?? 0;
if (x && x > this._bufferService.cols) {
// exceeded the container width, so hide
element.style.display = 'none';
}
if ((decoration.options.anchor || 'left') === 'right') {
element.style.right = x ? `${x * this._renderService.dimensions.actualCellWidth}px` : '';
} else {
element.style.left = x ? `${x * this._renderService.dimensions.actualCellWidth}px` : '';
}

return element;
}

private _refreshStyle(decoration: IInternalDecoration, element: HTMLElement): void {
const line = decoration.marker.line - this._bufferService.buffers.active.ydisp;
if (line < 0 || line > this._bufferService.rows) {
// outside of viewport
element.style.display = 'none';
} else {
element.style.top = `${line * this._renderService.dimensions.actualCellHeight}px`;
element.style.display = this._altBufferIsActive ? 'none' : 'block';
}
}

private _removeDecoration(decoration: IInternalDecoration): void {
this._decorationElements.get(decoration)?.remove();
this._decorationElements.delete(decoration);
}
}
140 changes: 140 additions & 0 deletions src/browser/Decorations/OverviewRulerRenderer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { addDisposableDomListener } from 'browser/Lifecycle';
import { IRenderService } from 'browser/services/Services';
import { Disposable } from 'common/Lifecycle';
import { IBufferService, IDecorationService, IInternalDecoration, IOptionsService } from 'common/services/Services';

// This is used to reduce memory usage
// when refreshStyle is called
// by storing and updating
// the sizes of the decorations to be drawn
const renderSizes = new Uint16Array(3);
const enum SizeIndex {
OUTER_SIZE = 0,
INNER_SIZE = 0
}

export class OverviewRulerRenderer extends Disposable {
private readonly _canvas: HTMLCanvasElement;
private readonly _ctx: CanvasRenderingContext2D;
private readonly _decorationElements: Map<IInternalDecoration, HTMLElement> = new Map();
private get _width(): number {
return this._optionsService.options.overviewRulerWidth || 0;
}
private _animationFrame: number | undefined;

constructor(
private readonly _viewportElement: HTMLElement,
private readonly _screenElement: HTMLElement,
@IBufferService private readonly _bufferService: IBufferService,
@IDecorationService private readonly _decorationService: IDecorationService,
@IRenderService private readonly _renderService: IRenderService,
@IOptionsService private readonly _optionsService: IOptionsService
) {
super();
this._canvas = document.createElement('canvas');
this._canvas.classList.add('xterm-decoration-overview-ruler');
this._viewportElement.parentElement?.insertBefore(this._canvas, this._viewportElement);
const ctx = this._canvas.getContext('2d');
if (!ctx) {
throw new Error('Ctx cannot be null');
} else {
this._ctx = ctx;
}
this._queueRefresh(true);
this.register(this._bufferService.buffers.onBufferActivate(() => {
this._canvas!.style.display = this._bufferService.buffer === this._bufferService.buffers.alt ? 'none' : 'block';
}));
this.register(this._renderService.onRenderedBufferChange(() => this._queueRefresh()));
this.register(this._renderService.onDimensionsChange(() => this._queueRefresh(true, true)));
this.register(addDisposableDomListener(window, 'resize', () => this._queueRefresh(true)));
this.register(this._decorationService.onDecorationRegistered(() => this._queueRefresh(undefined, true)));
this.register(this._decorationService.onDecorationRemoved(decoration => this._removeDecoration(decoration)));
this.register(this._optionsService.onOptionChange(o => {
if (o === 'overviewRulerWidth') {
renderSizes[SizeIndex.OUTER_SIZE] = Math.floor(this._width / 3);
renderSizes[SizeIndex.INNER_SIZE] = Math.ceil(this._width / 3);
this._queueRefresh();
}
}));
renderSizes[SizeIndex.OUTER_SIZE] = Math.floor(this._width / 3);
renderSizes[SizeIndex.INNER_SIZE] = Math.ceil(this._width / 3);
}

public override dispose(): void {
for (const decoration of this._decorationElements) {
this._ctx?.clearRect(
0,
Math.round(this._canvas.height * (decoration[0].marker.line / this._bufferService.buffers.active.lines.length)),
this._canvas.width,
window.devicePixelRatio
);
}
this._decorationElements.clear();
this._canvas?.remove();
super.dispose();
}

private _refreshStyle(decoration: IInternalDecoration, updateAnchor?: boolean): void {
if (updateAnchor) {
if (decoration.options.anchor === 'right') {
this._canvas.style.right = decoration.options.x ? `${decoration.options.x * this._renderService.dimensions.actualCellWidth}px` : '';
} else {
this._canvas.style.left = decoration.options.x ? `${decoration.options.x * this._renderService.dimensions.actualCellWidth}px` : '';
}
}
if (!decoration.options.overviewRulerOptions) {
this._decorationElements.delete(decoration);
return;
}
this._ctx.lineWidth = !decoration.options.overviewRulerOptions.position ? 2 : 6;
this._ctx.strokeStyle = decoration.options.overviewRulerOptions.color;
this._ctx.strokeRect(
!decoration.options.overviewRulerOptions.position || decoration.options.overviewRulerOptions.position === 'left' ? 0 : decoration.options.overviewRulerOptions.position === 'right' ? renderSizes[SizeIndex.OUTER_SIZE] + renderSizes[SizeIndex.INNER_SIZE]: renderSizes[SizeIndex.OUTER_SIZE],
Math.round(this._canvas.height * (decoration.options.marker.line / this._bufferService.buffers.active.lines.length)),
!decoration.options.overviewRulerOptions.position ? this._width : decoration.options.overviewRulerOptions.position === 'center' ? renderSizes[SizeIndex.INNER_SIZE] : renderSizes[SizeIndex.OUTER_SIZE],
window.devicePixelRatio
);
}

private _refreshDecorations(updateCanvasDimensions?: boolean, updateAnchor?: boolean): void {
if (updateCanvasDimensions) {
this._canvas.style.width = `${this._width}px`;
this._canvas.style.height = `${this._screenElement.clientHeight}px`;
this._canvas.width = Math.floor((this._width)* window.devicePixelRatio);
this._canvas.height = Math.floor(this._screenElement.clientHeight * window.devicePixelRatio);
}
this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);
for (const decoration of this._decorationService.decorations) {
this._renderDecoration(decoration, updateAnchor);
}
}

private _renderDecoration(decoration: IInternalDecoration, updateAnchor?: boolean): void {
const element = this._decorationElements.get(decoration);
if (!element) {
this._decorationElements.set(decoration, this._canvas);
}
this._refreshStyle(decoration, updateAnchor);
decoration.onRenderEmitter.fire(this._canvas);
}

private _queueRefresh(updateCanvasDimensions?: boolean, updateAnchor?: boolean): void {
if (this._animationFrame !== undefined) {
return;
}
this._animationFrame = window.requestAnimationFrame(() => {
this._refreshDecorations(updateCanvasDimensions, updateAnchor);
this._animationFrame = undefined;
});
}

private _removeDecoration(decoration: IInternalDecoration): void {
this._decorationElements.get(decoration)?.remove();
this._decorationElements.delete(decoration);
}
}
Loading