mirror of
https://github.com/wavetermdev/xterm.js.git
synced 2026-08-05 13:43:48 -07:00
large refactor, broken
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { IDecorationService, IRenderService } from 'browser/services/Services';
|
||||
import { IEvent, EventEmitter } from 'common/EventEmitter';
|
||||
import { Disposable } from 'common/Lifecycle';
|
||||
import { IBufferService } from 'common/services/Services';
|
||||
import { IMarker } from 'common/Types';
|
||||
import { IDecoration, IDecorationOptions } from 'xterm';
|
||||
|
||||
export interface IDecorationRenderer {
|
||||
refreshDecorations(shouldRecreate?: boolean): void;
|
||||
renderDecoration(decoration: IDecoration, decorationOptions: IDecorationOptions): void;
|
||||
}
|
||||
|
||||
export class BufferDecorationRenderer extends Disposable implements IDecorationRenderer {
|
||||
private _decorationContainer: HTMLElement;
|
||||
private readonly _decorations: BufferDecoration[] = [];
|
||||
private _altBufferIsActive: boolean = false;
|
||||
|
||||
constructor(
|
||||
@IBufferService private readonly _bufferService: IBufferService,
|
||||
@IRenderService private readonly _renderService: IRenderService,
|
||||
private readonly _decorationService: IDecorationService,
|
||||
private readonly _screenElement: HTMLElement) {
|
||||
super();
|
||||
this._decorationContainer = document.createElement('div');
|
||||
this._decorationContainer.classList.add('xterm-decoration-container');
|
||||
this._screenElement.appendChild(this._decorationContainer);
|
||||
this.register(this._renderService.onRenderedBufferChange(() => this.refreshDecorations()));
|
||||
this.register(this._renderService.onDimensionsChange(() => this.refreshDecorations()));
|
||||
this.register(addDisposableDomListener(window, 'resize', () => this.refreshDecorations()));
|
||||
this.register(this._bufferService.buffers.onBufferActivate(() => {
|
||||
this._altBufferIsActive = this._bufferService.buffer === this._bufferService.buffers.alt;
|
||||
}));
|
||||
this.register(this._decorationService.onDecorationRegistered(options => this.renderDecoration(options)));
|
||||
}
|
||||
public refreshDecorations(shouldRecreate?: boolean): void {
|
||||
if (!this._renderService) {
|
||||
return;
|
||||
}
|
||||
for (const decoration of this._decorations) {
|
||||
decoration.render(this._decorationContainer, this._renderService, shouldRecreate);
|
||||
}
|
||||
}
|
||||
|
||||
public renderDecoration(decorationOptions: IDecorationOptions): void {
|
||||
const decoration = new BufferDecoration(this._bufferService, decorationOptions);
|
||||
if (this._decorationContainer && decoration.element && !this._decorationContainer.contains(decoration.element)) {
|
||||
this._decorationContainer.append(decoration.element);
|
||||
}
|
||||
(decoration as BufferDecoration).render(this._decorationContainer, this._renderService, true);
|
||||
}
|
||||
|
||||
public override dispose(): void {
|
||||
if (this._screenElement && this._decorationContainer && this._screenElement.contains(this._decorationContainer)) {
|
||||
this._screenElement.removeChild(this._decorationContainer);
|
||||
}
|
||||
for (const bufferDecoration of this._decorations) {
|
||||
bufferDecoration.dispose();
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
export class BufferDecoration extends Disposable implements IDecoration {
|
||||
private readonly _marker: IMarker;
|
||||
private _element: HTMLElement | undefined;
|
||||
private _container: HTMLElement | undefined;
|
||||
private _altBufferIsActive: boolean = false;
|
||||
|
||||
public isDisposed: boolean = false;
|
||||
|
||||
public get element(): HTMLElement | undefined { return this._element; }
|
||||
public get marker(): IMarker { return this._marker; }
|
||||
|
||||
private _onDispose = new EventEmitter<void>();
|
||||
public get onDispose(): IEvent<void> { return this._onDispose.event; }
|
||||
|
||||
private _onRender = new EventEmitter<HTMLElement>();
|
||||
public get onRender(): IEvent<HTMLElement> { return this._onRender.event; }
|
||||
|
||||
|
||||
public x: number;
|
||||
public anchor: 'left' | 'right';
|
||||
public width: number;
|
||||
public height: number;
|
||||
|
||||
constructor(
|
||||
private readonly _bufferService: IBufferService,
|
||||
options: IDecorationOptions
|
||||
) {
|
||||
super();
|
||||
this.x = options.x ?? 0;
|
||||
this._marker = options.marker;
|
||||
this._marker.onDispose(() => this.dispose());
|
||||
this.anchor = options.anchor || 'left';
|
||||
this.width = options.width || 1;
|
||||
this.height = options.height || 1;
|
||||
}
|
||||
|
||||
public render(container: HTMLElement, renderService: IRenderService, shouldRecreate?: boolean): void {
|
||||
this._container = container;
|
||||
if (!this._element || shouldRecreate) {
|
||||
this._createElement(renderService, shouldRecreate);
|
||||
}
|
||||
this._refreshStyle(renderService);
|
||||
if (this._element) {
|
||||
this._onRender.fire(this._element);
|
||||
}
|
||||
}
|
||||
|
||||
private _createElement(renderService: IRenderService, shouldRecreate?: boolean): void {
|
||||
if (shouldRecreate && this._element && this._container && this._container.contains(this._element)) {
|
||||
this._container.removeChild(this._element);
|
||||
}
|
||||
this._element = document.createElement('div');
|
||||
this._element.classList.add('xterm-decoration');
|
||||
this._element.style.width = `${this.width * renderService.dimensions.actualCellWidth}px`;
|
||||
this._element.style.height = `${this.height * renderService.dimensions.actualCellHeight}px`;
|
||||
this._element.style.top = `${(this.marker.line - this._bufferService.buffers.active.ydisp) * renderService.dimensions.actualCellHeight}px`;
|
||||
this._element.style.lineHeight = `${renderService.dimensions.actualCellHeight}px`;
|
||||
|
||||
if (this.x && this.x > this._bufferService.cols) {
|
||||
// exceeded the container width, so hide
|
||||
this._element.style.display = 'none';
|
||||
}
|
||||
if (this.anchor === 'right') {
|
||||
this._element.style.right = this.x ? `${this.x * renderService.dimensions.actualCellWidth}px` : '';
|
||||
} else {
|
||||
this._element.style.left = this.x ? `${this.x * renderService.dimensions.actualCellWidth}px` : '';
|
||||
}
|
||||
}
|
||||
|
||||
private _refreshStyle(renderService: IRenderService): void {
|
||||
if (!this._element) {
|
||||
return;
|
||||
}
|
||||
const line = this.marker.line - this._bufferService.buffers.active.ydisp;
|
||||
if (line < 0 || line > this._bufferService.rows) {
|
||||
// outside of viewport
|
||||
this._element.style.display = 'none';
|
||||
} else {
|
||||
this._element.style.top = `${line * renderService.dimensions.actualCellHeight}px`;
|
||||
this._element.style.display = this._altBufferIsActive ? 'none' : 'block';
|
||||
}
|
||||
}
|
||||
|
||||
public override dispose(): void {
|
||||
if (this.isDisposed || !this._container) {
|
||||
return;
|
||||
}
|
||||
if (this._element && this._container.contains(this._element)) {
|
||||
this._container.removeChild(this._element);
|
||||
}
|
||||
this.isDisposed = true;
|
||||
this._onDispose.fire();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { IDecorationRenderer } from 'browser/Decorations/BufferDecorationRenderer';
|
||||
import { IDecorationService, IRenderService } from 'browser/services/Services';
|
||||
import { EventEmitter, IEvent } from 'common/EventEmitter';
|
||||
import { Disposable } from 'common/Lifecycle';
|
||||
import { IBufferService, IInstantiationService } from 'common/services/Services';
|
||||
import { IDecorationOptions, IDecoration, IMarker } from 'xterm';
|
||||
const enum ScrollbarConstants {
|
||||
WIDTH = 7
|
||||
}
|
||||
|
||||
export class OverviewRulerRenderer extends Disposable implements IDecorationRenderer {
|
||||
private _canvas: HTMLCanvasElement;
|
||||
private _ctx: CanvasRenderingContext2D | null;
|
||||
private _decorations: ScrollbarDecoration[] = [];
|
||||
private _width: number | undefined;
|
||||
private _anchor: 'right' | 'left' | undefined;
|
||||
private _x: number | undefined;
|
||||
|
||||
constructor(
|
||||
@IBufferService private readonly _bufferService: IBufferService,
|
||||
@IRenderService private readonly _renderService: IRenderService,
|
||||
@IDecorationService private readonly _decorationService: IDecorationService,
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||
private readonly _viewportElement: HTMLElement,
|
||||
private readonly _screenElement: HTMLElement
|
||||
) {
|
||||
super();
|
||||
this._canvas = document.createElement('canvas');
|
||||
this._canvas.classList.add('xterm-decoration-scrollbar');
|
||||
this._viewportElement.parentElement?.insertBefore(this._canvas, this._viewportElement);
|
||||
this._ctx = this._canvas.getContext('2d');
|
||||
this.refreshDecorations();
|
||||
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.refreshDecorations()));
|
||||
this.register(this._renderService.onDimensionsChange(() => this.refreshDecorations()));
|
||||
this.register(addDisposableDomListener(window, 'resize', () => this.refreshDecorations()));
|
||||
this.register(this._decorationService.onDecorationRegistered(e => this.renderDecoration(e)));
|
||||
this.register(this._decorationService.onDecorationRemoved(d => d.dispose()));
|
||||
}
|
||||
public renderDecoration(decorationOptions: IDecorationOptions): void {
|
||||
if (!this._ctx || !decorationOptions.overviewRulerItemColor) {
|
||||
return;
|
||||
}
|
||||
const decoration = this._instantiationService.createInstance(ScrollbarDecoration, { marker: decorationOptions.marker, overviewRulerItemColor: decorationOptions.overviewRulerItemColor });
|
||||
|
||||
this._ctx.lineWidth = 1;
|
||||
this._ctx.strokeStyle = decorationOptions.overviewRulerItemColor;
|
||||
this._ctx.strokeRect(
|
||||
0,
|
||||
Math.round(this._canvas.height * (decoration.marker.line / this._bufferService.buffers.active.lines.length)),
|
||||
this._canvas.width,
|
||||
window.devicePixelRatio
|
||||
);
|
||||
}
|
||||
|
||||
public refreshDecorations(): void {
|
||||
if (!this._ctx) {
|
||||
return;
|
||||
}
|
||||
this._canvas.style.width = `${this._width || ScrollbarConstants.WIDTH}px`;
|
||||
this._canvas.style.height = `${this._screenElement.clientHeight}px`;
|
||||
this._canvas.width = Math.floor((this._width || ScrollbarConstants.WIDTH)* window.devicePixelRatio);
|
||||
this._canvas.height = Math.floor(this._screenElement.clientHeight * window.devicePixelRatio);
|
||||
if (this._anchor === 'right') {
|
||||
this._canvas.style.right = this._x ? `${this._x * this._renderService.dimensions.actualCellWidth}px` : '';
|
||||
} else {
|
||||
this._canvas.style.left = this._x ? `${this._x * this._renderService.dimensions.actualCellWidth}px` : '';
|
||||
}
|
||||
this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);
|
||||
for (const decoration of this._decorations) {
|
||||
decoration.render(this._ctx, this._canvas);
|
||||
}
|
||||
}
|
||||
public override dispose(): void {
|
||||
for (const decoration of this._decorations) {
|
||||
decoration.dispose();
|
||||
}
|
||||
this._decorations = [];
|
||||
this._canvas?.remove();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
class ScrollbarDecoration extends Disposable implements IDecoration {
|
||||
private readonly _marker: IMarker;
|
||||
private _ctx: CanvasRenderingContext2D | undefined;
|
||||
private _canvas: HTMLCanvasElement | undefined;
|
||||
private _color: string | undefined;
|
||||
|
||||
public isDisposed: boolean = false;
|
||||
|
||||
public get element(): HTMLCanvasElement | undefined { return this._canvas; }
|
||||
public get marker(): IMarker { return this._marker; }
|
||||
public get color(): string | undefined { return this._color; }
|
||||
|
||||
private _onDispose = new EventEmitter<void>();
|
||||
public get onDispose(): IEvent<void> { return this._onDispose.event; }
|
||||
|
||||
private _onRender = new EventEmitter<HTMLElement>();
|
||||
public get onRender(): IEvent<HTMLElement> { return this._onRender.event; }
|
||||
|
||||
constructor(
|
||||
options: IDecorationOptions,
|
||||
@IBufferService private readonly _bufferService: IBufferService
|
||||
) {
|
||||
super();
|
||||
this._marker = options.marker;
|
||||
this._color = options.overviewRulerItemColor;
|
||||
this._marker.onDispose(() => this.dispose());
|
||||
}
|
||||
|
||||
public render(ctx: CanvasRenderingContext2D, canvas: HTMLCanvasElement): void {
|
||||
if (!this.color) {
|
||||
throw new Error('No color was provided for the overview ruler decoraiton');
|
||||
}
|
||||
this._ctx = ctx;
|
||||
this._canvas = canvas;
|
||||
ctx.lineWidth = 1;
|
||||
ctx.strokeStyle = this.color;
|
||||
ctx.strokeRect(
|
||||
0,
|
||||
Math.round(canvas.height * (this.marker.line / this._bufferService.buffers.active.lines.length)),
|
||||
canvas.width,
|
||||
window.devicePixelRatio
|
||||
);
|
||||
this._onRender.fire(canvas);
|
||||
}
|
||||
|
||||
public override dispose(): void {
|
||||
if (this._isDisposed || !this._canvas || !this._ctx) {
|
||||
return;
|
||||
}
|
||||
this._ctx.clearRect(
|
||||
0,
|
||||
Math.round(this._canvas.height * (this.marker.line / this._bufferService.buffers.active.lines.length)),
|
||||
this._canvas.width,
|
||||
window.devicePixelRatio
|
||||
);
|
||||
this.isDisposed = true;
|
||||
this._onDispose.fire();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
+21
-7
@@ -45,7 +45,7 @@ import { EventEmitter, IEvent, forwardEvent } from 'common/EventEmitter';
|
||||
import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
|
||||
import { ColorManager } from 'browser/ColorManager';
|
||||
import { RenderService } from 'browser/services/RenderService';
|
||||
import { ICharSizeService, IRenderService, IMouseService, ISelectionService, ISoundService, ICoreBrowserService, ICharacterJoinerService, IDecorationService } from 'browser/services/Services';
|
||||
import { ICharSizeService, IRenderService, IMouseService, ISelectionService, ISoundService, ICoreBrowserService, ICharacterJoinerService } from 'browser/services/Services';
|
||||
import { CharSizeService } from 'browser/services/CharSizeService';
|
||||
import { IBuffer } from 'common/buffer/Types';
|
||||
import { MouseService } from 'browser/services/MouseService';
|
||||
@@ -55,7 +55,10 @@ import { CoreTerminal } from 'common/CoreTerminal';
|
||||
import { color, rgba } from 'browser/Color';
|
||||
import { CharacterJoinerService } from 'browser/services/CharacterJoinerService';
|
||||
import { toRgbString } from 'common/input/XParseColor';
|
||||
import { DecorationService } from 'browser/services/DecorationService';
|
||||
import { BufferDecorationRenderer } from 'browser/Decorations/BufferDecorationRenderer';
|
||||
import { OverviewRulerRenderer } from 'browser/Decorations/OverviewRulerRenderer';
|
||||
// import { IDecorationService } from 'common/services/Services';
|
||||
import { DecorationService } from 'common/services/DecorationService';
|
||||
|
||||
// Let it work inside Node.js for automated testing purposes.
|
||||
const document: Document = (typeof window !== 'undefined') ? window.document : null as any;
|
||||
@@ -71,6 +74,9 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
private _helperContainer: HTMLElement | undefined;
|
||||
private _compositionView: HTMLElement | undefined;
|
||||
|
||||
private _overviewRulerRenderer: OverviewRulerRenderer | undefined;
|
||||
private _bufferDecorationRenderer: BufferDecorationRenderer | undefined;
|
||||
|
||||
// private _visualBellTimer: number;
|
||||
|
||||
public browser: IBrowser = Browser as any;
|
||||
@@ -81,6 +87,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
private _charSizeService: ICharSizeService | undefined;
|
||||
private _mouseService: IMouseService | undefined;
|
||||
private _renderService: IRenderService | undefined;
|
||||
private _decorationService: DecorationService | undefined;
|
||||
private _characterJoinerService: ICharacterJoinerService | undefined;
|
||||
private _selectionService: ISelectionService | undefined;
|
||||
private _soundService: ISoundService | undefined;
|
||||
@@ -109,7 +116,6 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
public linkifier: ILinkifier;
|
||||
public linkifier2: ILinkifier2;
|
||||
public viewport: IViewport | undefined;
|
||||
public decorationService: IDecorationService;
|
||||
private _compositionHelper: ICompositionHelper | undefined;
|
||||
private _mouseZoneManager: IMouseZoneManager | undefined;
|
||||
private _accessibilityManager: AccessibilityManager | undefined;
|
||||
@@ -159,7 +165,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
|
||||
this.linkifier = this._instantiationService.createInstance(Linkifier);
|
||||
this.linkifier2 = this.register(this._instantiationService.createInstance(Linkifier2));
|
||||
this.decorationService = this.register(this._instantiationService.createInstance(DecorationService));
|
||||
this._decorationService = this._instantiationService.createInstance(DecorationService);
|
||||
|
||||
// Setup InputHandler listeners
|
||||
this.register(this._inputHandler.onRequestBell(() => this.bell()));
|
||||
@@ -577,8 +583,16 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
this.register(this.onScroll(() => this._mouseZoneManager!.clearAll()));
|
||||
this.linkifier.attachToDom(this.element, this._mouseZoneManager);
|
||||
this.linkifier2.attachToDom(this.screenElement, this._mouseService, this._renderService);
|
||||
|
||||
this.decorationService.attachToDom(this._renderService, this.screenElement, this._viewportElement);
|
||||
if (this._decorationService) {
|
||||
this._bufferDecorationRenderer = new BufferDecorationRenderer(this._bufferService, this._renderService, this._decorationService, this.screenElement);
|
||||
}
|
||||
// if (this.options.overviewRulerWidth && this._decorationService) {
|
||||
// this._overviewRulerRenderer = new OverviewRulerRenderer(this._bufferService, this._renderService, this._decorationService, this._viewportElement, this.screenElement);
|
||||
// }
|
||||
// this.optionsService.onOptionChange(() => {
|
||||
// if (!this._overviewRulerRenderer && this.options.overviewRulerWidth && this._renderService && this._viewportElement && this.screenElement && this._decorationService) {
|
||||
// this._overviewRulerRenderer = new OverviewRulerRenderer(this._bufferService, this._renderService, this._decorationService, this._viewportElement, this.screenElement);
|
||||
// }});
|
||||
// This event listener must be registered aftre MouseZoneManager is created
|
||||
this.register(addDisposableDomListener(this.element, 'mousedown', (e: MouseEvent) => this._selectionService!.onMouseDown(e)));
|
||||
|
||||
@@ -1004,7 +1018,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
}
|
||||
|
||||
public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined {
|
||||
return this.decorationService!.registerDecoration(decorationOptions);
|
||||
return this._decorationService!.registerDecoration(decorationOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Vendored
+6
@@ -9,6 +9,7 @@ import { ICoreTerminal, CharData, ITerminalOptions } from 'common/Types';
|
||||
import { IMouseService, IRenderService } from './services/Services';
|
||||
import { IBuffer } from 'common/buffer/Types';
|
||||
import { IFunctionIdentifier, IParams } from 'common/parser/Types';
|
||||
import { createDecorator } from 'common/services/ServiceRegistry';
|
||||
|
||||
export interface ITerminal extends IPublicTerminal, ICoreTerminal {
|
||||
element: HTMLElement | undefined;
|
||||
@@ -205,6 +206,11 @@ export interface ILinkifier {
|
||||
registerLinkMatcher(regex: RegExp, handler: LinkMatcherHandler, options?: ILinkMatcherOptions): number;
|
||||
deregisterLinkMatcher(matcherId: number): boolean;
|
||||
}
|
||||
export interface IDecorationService extends IDisposable {
|
||||
readonly onDecorationRegistered: IEvent<IDecorationOptions>;
|
||||
readonly onDecorationRemoved: IEvent<IDecoration>;
|
||||
registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined;
|
||||
}
|
||||
|
||||
interface ILinkState {
|
||||
decorations: ILinkDecorations;
|
||||
|
||||
@@ -1,361 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2022 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { addDisposableDomListener } from 'browser/Lifecycle';
|
||||
import { IDecorationService, IRenderService } from 'browser/services/Services';
|
||||
import { EventEmitter, IEvent } from 'common/EventEmitter';
|
||||
import { Disposable } from 'common/Lifecycle';
|
||||
import { IBufferService, IInstantiationService } from 'common/services/Services';
|
||||
import { IDecorationOptions, IDecoration, IMarker } from 'xterm';
|
||||
|
||||
const enum ScrollbarConstants {
|
||||
WIDTH = 7
|
||||
}
|
||||
|
||||
interface IDecorationRenderer {
|
||||
refreshDecorations(shouldRecreate?: boolean): void;
|
||||
registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined;
|
||||
}
|
||||
|
||||
class BufferDecorationRenderer extends Disposable implements IDecorationRenderer {
|
||||
private _decorationContainer: HTMLElement | undefined;
|
||||
private readonly _decorations: BufferDecoration[] = [];
|
||||
private _renderService: IRenderService | undefined;
|
||||
|
||||
constructor(
|
||||
@IBufferService private readonly _bufferService: IBufferService,
|
||||
private readonly _screenElement: HTMLElement) {
|
||||
super();
|
||||
}
|
||||
public refreshDecorations(shouldRecreate?: boolean): void {
|
||||
if (!this._renderService) {
|
||||
return;
|
||||
}
|
||||
for (const decoration of this._decorations) {
|
||||
decoration.render(this._renderService, shouldRecreate);
|
||||
}
|
||||
}
|
||||
public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined {
|
||||
if (this._screenElement && !this._decorationContainer) {
|
||||
this._decorationContainer = document.createElement('div');
|
||||
this._decorationContainer.classList.add('xterm-decoration-container');
|
||||
this._screenElement.appendChild(this._decorationContainer);
|
||||
}
|
||||
const decoration = new BufferDecoration(this._bufferService, decorationOptions, this._decorationContainer);
|
||||
this._decorations.push(decoration);
|
||||
decoration.onDispose(() => this._decorations.splice(this._decorations.indexOf(decoration), 1));
|
||||
this.refreshDecorations();
|
||||
return decoration;
|
||||
}
|
||||
public override dispose(): void {
|
||||
if (this._screenElement && this._decorationContainer && this._screenElement.contains(this._decorationContainer)) {
|
||||
this._screenElement.removeChild(this._decorationContainer);
|
||||
}
|
||||
for (const bufferDecoration of this._decorations) {
|
||||
bufferDecoration.dispose();
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
public attachToDom(renderService: IRenderService): void {
|
||||
this._renderService = renderService;
|
||||
}
|
||||
}
|
||||
|
||||
class OverviewRulerRenderer extends Disposable implements IDecorationRenderer {
|
||||
private _canvas: HTMLCanvasElement | undefined;
|
||||
private _ctx: CanvasRenderingContext2D | null = null;
|
||||
private _decorations: ScrollbarDecoration[] = [];
|
||||
private _width: number | undefined;
|
||||
private _anchor: 'right' | 'left' | undefined;
|
||||
private _x: number | undefined;
|
||||
|
||||
constructor(
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||
@IBufferService private readonly _bufferService: IBufferService,
|
||||
@IRenderService private readonly _renderService: IRenderService,
|
||||
private readonly _viewportElement: HTMLElement,
|
||||
private readonly _screenElement: HTMLElement
|
||||
) {
|
||||
super();
|
||||
this.register(this._bufferService.buffers.onBufferActivate(() => {
|
||||
this._canvas!.style.display = this._bufferService.buffer === this._bufferService.buffers.alt ? 'none' : 'block';
|
||||
}));
|
||||
}
|
||||
public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined {
|
||||
if (!this._viewportElement.parentElement) {
|
||||
return;
|
||||
}
|
||||
if (!this._canvas) {
|
||||
this._canvas = document.createElement('canvas');
|
||||
this._canvas.classList.add('xterm-decoration-scrollbar');
|
||||
this._viewportElement.parentElement.insertBefore(this._canvas, this._viewportElement);
|
||||
}
|
||||
this._width = decorationOptions.width;
|
||||
this._anchor = decorationOptions.anchor;
|
||||
this._x = decorationOptions.x;
|
||||
|
||||
if (!this._ctx) {
|
||||
this._ctx = this._canvas.getContext('2d');
|
||||
this.refreshDecorations();
|
||||
}
|
||||
const decoration = this._instantiationService.createInstance(ScrollbarDecoration, { marker: decorationOptions.marker, overviewRulerItemColor: decorationOptions.overviewRulerItemColor }, this._canvas, this._ctx!);
|
||||
decoration.onDispose(() => this._decorations.splice(this._decorations.indexOf(decoration), 1));
|
||||
this._decorations.push(decoration);
|
||||
return decoration;
|
||||
}
|
||||
public refreshDecorations(): void {
|
||||
if (!this._canvas || !this._ctx || !this._screenElement || !this._renderService) {
|
||||
return;
|
||||
}
|
||||
this._canvas.style.width = `${this._width || ScrollbarConstants.WIDTH}px`;
|
||||
this._canvas.style.height = `${this._screenElement.clientHeight}px`;
|
||||
this._canvas.width = Math.floor((this._width || ScrollbarConstants.WIDTH)* window.devicePixelRatio);
|
||||
this._canvas.height = Math.floor(this._screenElement.clientHeight * window.devicePixelRatio);
|
||||
if (this._anchor === 'right') {
|
||||
this._canvas.style.right = this._x ? `${this._x * this._renderService.dimensions.actualCellWidth}px` : '';
|
||||
} else {
|
||||
this._canvas.style.left = this._x ? `${this._x * this._renderService.dimensions.actualCellWidth}px` : '';
|
||||
}
|
||||
this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);
|
||||
for (const decoration of this._decorations) {
|
||||
decoration.render();
|
||||
}
|
||||
}
|
||||
public override dispose(): void {
|
||||
for (const decoration of this._decorations) {
|
||||
decoration.dispose();
|
||||
}
|
||||
this._decorations = [];
|
||||
this._canvas?.remove();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
export class DecorationService extends Disposable implements IDecorationService {
|
||||
|
||||
private _renderService: IRenderService | undefined;
|
||||
private _animationFrame: number | undefined;
|
||||
|
||||
private _screenElement: HTMLElement | undefined;
|
||||
private _viewportElement: HTMLElement | undefined;
|
||||
|
||||
private _overviewRulerRenderer: OverviewRulerRenderer | undefined;
|
||||
private _bufferDecorationRenderer: BufferDecorationRenderer | undefined;
|
||||
|
||||
constructor(@IInstantiationService private readonly _instantiationService: IInstantiationService, @IBufferService private readonly _bufferService: IBufferService) {
|
||||
super();
|
||||
}
|
||||
|
||||
public attachToDom(renderService: IRenderService, screenElement: HTMLElement, viewportElement: HTMLElement): void {
|
||||
this._renderService = renderService;
|
||||
this._screenElement = screenElement;
|
||||
this._viewportElement = viewportElement;
|
||||
this.register(this._renderService.onRenderedBufferChange(() => this._queueRefresh()));
|
||||
this.register(this._renderService.onDimensionsChange(() => this._refresh(true)));
|
||||
this.register(addDisposableDomListener(window, 'resize', () => this._queueRefresh()));
|
||||
if (!this._bufferDecorationRenderer && this._viewportElement && this._screenElement) {
|
||||
// TODO: allow registering before the viewport element exists
|
||||
this._bufferDecorationRenderer = new BufferDecorationRenderer(this._bufferService, this._screenElement);
|
||||
this._bufferDecorationRenderer.attachToDom(renderService);
|
||||
}
|
||||
}
|
||||
|
||||
public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined {
|
||||
if (decorationOptions.marker.isDisposed || !this._renderService) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (decorationOptions.overviewRulerItemColor && this._viewportElement && this._screenElement) {
|
||||
if (!this._overviewRulerRenderer) {
|
||||
this._overviewRulerRenderer = new OverviewRulerRenderer(this._instantiationService, this._bufferService, this._renderService, this._viewportElement, this._screenElement);
|
||||
}
|
||||
return this._overviewRulerRenderer.registerDecoration(decorationOptions);
|
||||
}
|
||||
return this._bufferDecorationRenderer?.registerDecoration(decorationOptions);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._overviewRulerRenderer?.dispose();
|
||||
this._bufferDecorationRenderer?.dispose();
|
||||
}
|
||||
|
||||
private _refresh(shouldRecreate?: boolean): void {
|
||||
this._bufferDecorationRenderer?.refreshDecorations(shouldRecreate);
|
||||
this._overviewRulerRenderer?.refreshDecorations();
|
||||
}
|
||||
|
||||
private _queueRefresh(): void {
|
||||
if (this._animationFrame !== undefined) {
|
||||
return;
|
||||
}
|
||||
this._animationFrame = window.requestAnimationFrame(() => {
|
||||
this._refresh();
|
||||
this._animationFrame = undefined;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class ScrollbarDecoration extends Disposable implements IDecoration {
|
||||
private readonly _marker: IMarker;
|
||||
private _element: HTMLCanvasElement | undefined;
|
||||
private _color: string | undefined;
|
||||
|
||||
public isDisposed: boolean = false;
|
||||
|
||||
public get element(): HTMLCanvasElement | undefined { return this._element; }
|
||||
public get marker(): IMarker { return this._marker; }
|
||||
public get color(): string | undefined { return this._color; }
|
||||
|
||||
private _onDispose = new EventEmitter<void>();
|
||||
public get onDispose(): IEvent<void> { return this._onDispose.event; }
|
||||
|
||||
private _onRender = new EventEmitter<HTMLElement>();
|
||||
public get onRender(): IEvent<HTMLElement> { return this._onRender.event; }
|
||||
|
||||
constructor(
|
||||
options: IDecorationOptions,
|
||||
private readonly _canvas: HTMLCanvasElement,
|
||||
private readonly _ctx: CanvasRenderingContext2D,
|
||||
@IBufferService private readonly _bufferService: IBufferService
|
||||
) {
|
||||
super();
|
||||
this._marker = options.marker;
|
||||
this._color = options.overviewRulerItemColor;
|
||||
this._marker.onDispose(() => this.dispose());
|
||||
this.render();
|
||||
}
|
||||
public render(): void {
|
||||
if (!this.color) {
|
||||
throw new Error('No color was provided for the overview ruler decoraiton');
|
||||
}
|
||||
if (!this._element) {
|
||||
this._element = this._canvas;
|
||||
}
|
||||
this._ctx.lineWidth = 1;
|
||||
this._ctx.strokeStyle = this.color;
|
||||
this._ctx.strokeRect(
|
||||
0,
|
||||
Math.round(this._element.height * (this.marker.line / this._bufferService.buffers.active.lines.length)),
|
||||
this._element.width,
|
||||
window.devicePixelRatio
|
||||
);
|
||||
this._onRender.fire(this._element);
|
||||
}
|
||||
|
||||
public override dispose(): void {
|
||||
if (this._isDisposed || !this._element) {
|
||||
return;
|
||||
}
|
||||
this._ctx.clearRect(
|
||||
0,
|
||||
Math.round(this._element.height * (this.marker.line / this._bufferService.buffers.active.lines.length)),
|
||||
this._element.width,
|
||||
window.devicePixelRatio
|
||||
);
|
||||
this.isDisposed = true;
|
||||
this._onDispose.fire();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
export class BufferDecoration extends Disposable implements IDecoration {
|
||||
private readonly _marker: IMarker;
|
||||
private _element: HTMLElement | undefined;
|
||||
|
||||
public isDisposed: boolean = false;
|
||||
|
||||
public get element(): HTMLElement | undefined { return this._element; }
|
||||
public get marker(): IMarker { return this._marker; }
|
||||
|
||||
private _onDispose = new EventEmitter<void>();
|
||||
public get onDispose(): IEvent<void> { return this._onDispose.event; }
|
||||
|
||||
private _onRender = new EventEmitter<HTMLElement>();
|
||||
public get onRender(): IEvent<HTMLElement> { return this._onRender.event; }
|
||||
|
||||
private _altBufferIsActive: boolean = false;
|
||||
|
||||
public x: number;
|
||||
public anchor: 'left' | 'right';
|
||||
public width: number;
|
||||
public height: number;
|
||||
|
||||
constructor(
|
||||
private readonly _bufferService: IBufferService,
|
||||
options: IDecorationOptions,
|
||||
private readonly _container?: HTMLElement
|
||||
) {
|
||||
super();
|
||||
this.x = options.x ?? 0;
|
||||
this._marker = options.marker;
|
||||
this._marker.onDispose(() => this.dispose());
|
||||
this.anchor = options.anchor || 'left';
|
||||
this.width = options.width || 1;
|
||||
this.height = options.height || 1;
|
||||
this.register(this._bufferService.buffers.onBufferActivate(() => {
|
||||
this._altBufferIsActive = this._bufferService.buffer === this._bufferService.buffers.alt;
|
||||
}));
|
||||
}
|
||||
|
||||
public render(renderService: IRenderService, shouldRecreate?: boolean): void {
|
||||
if (!this._element || shouldRecreate) {
|
||||
this._createElement(renderService, shouldRecreate);
|
||||
}
|
||||
if (this._container && this._element && !this._container.contains(this._element)) {
|
||||
this._container.append(this._element);
|
||||
}
|
||||
this._refreshStyle(renderService);
|
||||
if (this._element) {
|
||||
this._onRender.fire(this._element);
|
||||
}
|
||||
}
|
||||
|
||||
private _createElement(renderService: IRenderService, shouldRecreate?: boolean): void {
|
||||
if (shouldRecreate && this._element && this._container && this._container.contains(this._element)) {
|
||||
this._container.removeChild(this._element);
|
||||
}
|
||||
this._element = document.createElement('div');
|
||||
this._element.classList.add('xterm-decoration');
|
||||
this._element.style.width = `${this.width * renderService.dimensions.actualCellWidth}px`;
|
||||
this._element.style.height = `${this.height * renderService.dimensions.actualCellHeight}px`;
|
||||
this._element.style.top = `${(this.marker.line - this._bufferService.buffers.active.ydisp) * renderService.dimensions.actualCellHeight}px`;
|
||||
this._element.style.lineHeight = `${renderService.dimensions.actualCellHeight}px`;
|
||||
|
||||
if (this.x && this.x > this._bufferService.cols) {
|
||||
// exceeded the container width, so hide
|
||||
this._element.style.display = 'none';
|
||||
}
|
||||
if (this.anchor === 'right') {
|
||||
this._element.style.right = this.x ? `${this.x * renderService.dimensions.actualCellWidth}px` : '';
|
||||
} else {
|
||||
this._element.style.left = this.x ? `${this.x * renderService.dimensions.actualCellWidth}px` : '';
|
||||
}
|
||||
}
|
||||
|
||||
private _refreshStyle(renderService: IRenderService): void {
|
||||
if (!this._element) {
|
||||
return;
|
||||
}
|
||||
const line = this.marker.line - this._bufferService.buffers.active.ydisp;
|
||||
if (line < 0 || line > this._bufferService.rows) {
|
||||
// outside of viewport
|
||||
this._element.style.display = 'none';
|
||||
} else {
|
||||
this._element.style.top = `${line * renderService.dimensions.actualCellHeight}px`;
|
||||
this._element.style.display = this._altBufferIsActive ? 'none' : 'block';
|
||||
}
|
||||
}
|
||||
|
||||
public override dispose(): void {
|
||||
if (this.isDisposed || !this._container) {
|
||||
return;
|
||||
}
|
||||
if (this._element && this._container.contains(this._element)) {
|
||||
this._container.removeChild(this._element);
|
||||
}
|
||||
this.isDisposed = true;
|
||||
this._onDispose.fire();
|
||||
}
|
||||
}
|
||||
@@ -115,10 +115,9 @@ export interface ICharacterJoinerService {
|
||||
deregister(joinerId: number): boolean;
|
||||
getJoinedCharacters(row: number): [number, number][];
|
||||
}
|
||||
|
||||
|
||||
export const IDecorationService = createDecorator<IDecorationService>('DecorationService');
|
||||
export interface IDecorationService extends IDisposable {
|
||||
attachToDom(renderService: IRenderService, screenElement: HTMLElement, viewportElement: HTMLElement): void;
|
||||
readonly onDecorationRegistered: IEvent<IDecorationOptions>;
|
||||
readonly onDecorationRemoved: IEvent<IDecoration>;
|
||||
registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined;
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
"include": [
|
||||
"./**/*",
|
||||
"../../typings/xterm.d.ts"
|
||||
],
|
||||
],
|
||||
"references": [
|
||||
{ "path": "../common" }
|
||||
]
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* Copyright (c) 2022 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
|
||||
import { EventEmitter } from 'common/EventEmitter';
|
||||
import { Disposable } from 'common/Lifecycle';
|
||||
import { IBufferService, IInstantiationService, IDecorationService } from 'common/services/Services';
|
||||
import { IDecorationOptions, IDecoration, IMarker, IDisposable, IEvent } from 'xterm';
|
||||
|
||||
export class DecorationService extends Disposable implements IDecorationService {
|
||||
private _animationFrame: number | undefined;
|
||||
private _onDecorationRegistered = this.register(new EventEmitter<IDecorationOptions>());
|
||||
public get onDecorationRegistered(): IEvent<IDecorationOptions> { return this._onDecorationRegistered.event; }
|
||||
private _onDecorationRemoved = this.register(new EventEmitter<IDecoration>());
|
||||
public get onDecorationRemoved(): IEvent<IDecoration> { return this._onDecorationRemoved.event; }
|
||||
private _decorations: IDecoration[] = [];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
public registerDecoration(options: IDecorationOptions): IDecoration | undefined {
|
||||
if (options.marker.isDisposed) {
|
||||
return undefined;
|
||||
}
|
||||
const decoration = new Decoration(options);
|
||||
if (decoration) {
|
||||
decoration.onDispose(() => {
|
||||
if (decoration) {
|
||||
this._decorations.splice(this._decorations.indexOf(decoration), 1);
|
||||
}
|
||||
});
|
||||
this._decorations.push(decoration);
|
||||
this._onDecorationRegistered.fire(options);
|
||||
decoration.onRender(d => {
|
||||
decoration.setElement(d);
|
||||
});
|
||||
}
|
||||
return decoration;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
for (const decoration of this._decorations) {
|
||||
this._onDecorationRemoved.fire(decoration);
|
||||
decoration.dispose();
|
||||
}
|
||||
this._decorations = [];
|
||||
}
|
||||
|
||||
private _queueRefresh(): void {
|
||||
if (this._animationFrame !== undefined) {
|
||||
return;
|
||||
}
|
||||
this._animationFrame = window.requestAnimationFrame(() => {
|
||||
// this._refresh();
|
||||
this._animationFrame = undefined;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class Decoration implements IDecoration {
|
||||
public marker: IMarker;
|
||||
private _onRender = new EventEmitter<HTMLElement>();
|
||||
public get onRender(): IEvent<HTMLElement> { return this._onRender.event; }
|
||||
private _onDispose = new EventEmitter<void>();
|
||||
public get onDispose(): IEvent<void> { return this._onDispose.event; }
|
||||
public element: HTMLElement | undefined;
|
||||
public isDisposed: boolean = false;
|
||||
public dispose(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
constructor(decorationOptions?: IDecorationOptions) {
|
||||
this.marker = decorationOptions?.marker!;
|
||||
this.element = undefined;
|
||||
}
|
||||
public setElement(element: HTMLElement): void {
|
||||
this.element = element;
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,9 @@
|
||||
|
||||
import { IEvent } from 'common/EventEmitter';
|
||||
import { IBuffer, IBufferSet } from 'common/buffer/Types';
|
||||
import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEncoding, ICoreMouseProtocol, CoreMouseEventType, ICharset, IWindowOptions, IModes, IAttributeData, ScrollSource } from 'common/Types';
|
||||
import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEncoding, ICoreMouseProtocol, CoreMouseEventType, ICharset, IWindowOptions, IModes, IAttributeData, ScrollSource, IDisposable } from 'common/Types';
|
||||
import { createDecorator } from 'common/services/ServiceRegistry';
|
||||
import { IDecorationOptions, IDecoration } from 'xterm';
|
||||
|
||||
export const IBufferService = createDecorator<IBufferService>('BufferService');
|
||||
export interface IBufferService {
|
||||
@@ -245,6 +246,7 @@ export interface ITerminalOptions {
|
||||
windowsMode: boolean;
|
||||
windowOptions: IWindowOptions;
|
||||
wordSeparator: string;
|
||||
overviewRulerWidth?: number;
|
||||
|
||||
[key: string]: any;
|
||||
cancelEvents: boolean;
|
||||
@@ -298,3 +300,8 @@ export interface IUnicodeVersionProvider {
|
||||
readonly version: string;
|
||||
wcwidth(ucs: number): 0 | 1 | 2;
|
||||
}
|
||||
export interface IDecorationService extends IDisposable {
|
||||
readonly onDecorationRegistered: IEvent<IDecorationOptions>;
|
||||
readonly onDecorationRemoved: IEvent<IDecoration>;
|
||||
registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user