mirror of
https://github.com/wavetermdev/xterm.js.git
synced 2026-08-05 13:43:48 -07:00
Merge branch 'master' into clusters
This commit is contained in:
@@ -11,6 +11,8 @@ import { IRasterizedGlyph, IRenderDimensions, ITextureAtlas } from 'browser/rend
|
||||
import { Disposable, toDisposable } from 'common/Lifecycle';
|
||||
import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils';
|
||||
import { TextureAtlas } from 'browser/renderer/shared/TextureAtlas';
|
||||
import { ILogService } from 'common/services/Services';
|
||||
import { traceCall } from 'common/services/LogService';
|
||||
|
||||
interface IVertices {
|
||||
attributes: Float32Array;
|
||||
@@ -212,6 +214,7 @@ export class GlyphRenderer extends Disposable {
|
||||
return this._atlas ? this._atlas.beginFrame() : true;
|
||||
}
|
||||
|
||||
@traceCall
|
||||
public updateCell(x: number, y: number, code: number, bg: number, fg: number, ext: number, chars: string, lastBg: number): void {
|
||||
// Since this function is called for every cell (`rows*cols`), it must be very optimized. It
|
||||
// should not instantiate any variables unless a new glyph is drawn to the cache where the
|
||||
|
||||
@@ -8,9 +8,10 @@ import { ITerminal } from 'browser/Types';
|
||||
import { EventEmitter, forwardEvent } from 'common/EventEmitter';
|
||||
import { Disposable, toDisposable } from 'common/Lifecycle';
|
||||
import { getSafariVersion, isSafari } from 'common/Platform';
|
||||
import { ICoreService, IDecorationService, IOptionsService } from 'common/services/Services';
|
||||
import { ICoreService, IDecorationService, ILogService, IOptionsService } from 'common/services/Services';
|
||||
import { ITerminalAddon, Terminal } from 'xterm';
|
||||
import { WebglRenderer } from './WebglRenderer';
|
||||
import { setTraceLogger } from 'common/services/LogService';
|
||||
|
||||
export class WebglAddon extends Disposable implements ITerminalAddon {
|
||||
private _terminal?: Terminal;
|
||||
@@ -51,8 +52,13 @@ export class WebglAddon extends Disposable implements ITerminalAddon {
|
||||
const charSizeService: ICharSizeService = unsafeCore._charSizeService;
|
||||
const coreBrowserService: ICoreBrowserService = unsafeCore._coreBrowserService;
|
||||
const decorationService: IDecorationService = unsafeCore._decorationService;
|
||||
const logService: ILogService = unsafeCore._logService;
|
||||
const themeService: IThemeService = unsafeCore._themeService;
|
||||
|
||||
// Set trace logger just in case it hasn't been yet which could happen when the addon is
|
||||
// bundled separately to the core module
|
||||
setTraceLogger(logService);
|
||||
|
||||
this._renderer = this.register(new WebglRenderer(
|
||||
terminal,
|
||||
characterJoinerService,
|
||||
|
||||
@@ -16,8 +16,8 @@ import { AttributeData } from 'common/buffer/AttributeData';
|
||||
import { CellData } from 'common/buffer/CellData';
|
||||
import { Attributes, Content, NULL_CELL_CHAR, NULL_CELL_CODE } from 'common/buffer/Constants';
|
||||
import { EventEmitter, forwardEvent } from 'common/EventEmitter';
|
||||
import { Disposable, getDisposeArrayDisposable, toDisposable } from 'common/Lifecycle';
|
||||
import { ICoreService, IDecorationService, IOptionsService } from 'common/services/Services';
|
||||
import { Disposable, MutableDisposable, getDisposeArrayDisposable, toDisposable } from 'common/Lifecycle';
|
||||
import { ICoreService, IDecorationService, ILogService, IOptionsService } from 'common/services/Services';
|
||||
import { CharData, IBufferLine, ICellData } from 'common/Types';
|
||||
import { IDisposable, Terminal } from 'xterm';
|
||||
import { GlyphRenderer } from './GlyphRenderer';
|
||||
@@ -27,10 +27,11 @@ import { LinkRenderLayer } from './renderLayer/LinkRenderLayer';
|
||||
import { IRenderLayer } from './renderLayer/Types';
|
||||
import { COMBINED_CHAR_BIT_MASK, RenderModel, RENDER_MODEL_BG_OFFSET, RENDER_MODEL_EXT_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_INDICIES_PER_CELL } from './RenderModel';
|
||||
import { IWebGL2RenderingContext } from './Types';
|
||||
import { traceCall } from 'common/services/LogService';
|
||||
|
||||
export class WebglRenderer extends Disposable implements IRenderer {
|
||||
private _renderLayers: IRenderLayer[];
|
||||
private _cursorBlinkStateManager: CursorBlinkStateManager | undefined;
|
||||
private _cursorBlinkStateManager: MutableDisposable<CursorBlinkStateManager> = new MutableDisposable();
|
||||
private _charAtlasDisposable: IDisposable | undefined;
|
||||
private _charAtlas: ITextureAtlas | undefined;
|
||||
private _devicePixelRatio: number;
|
||||
@@ -202,7 +203,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
for (const l of this._renderLayers) {
|
||||
l.handleBlur(this._terminal);
|
||||
}
|
||||
this._cursorBlinkStateManager?.pause();
|
||||
this._cursorBlinkStateManager.value?.pause();
|
||||
// Request a redraw for active/inactive selection background
|
||||
this._requestRedrawViewport();
|
||||
}
|
||||
@@ -211,7 +212,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
for (const l of this._renderLayers) {
|
||||
l.handleFocus(this._terminal);
|
||||
}
|
||||
this._cursorBlinkStateManager?.resume();
|
||||
this._cursorBlinkStateManager.value?.resume();
|
||||
// Request a redraw for active/inactive selection background
|
||||
this._requestRedrawViewport();
|
||||
}
|
||||
@@ -228,7 +229,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
for (const l of this._renderLayers) {
|
||||
l.handleCursorMove(this._terminal);
|
||||
}
|
||||
this._cursorBlinkStateManager?.restartBlinkAnimation();
|
||||
this._cursorBlinkStateManager.value?.restartBlinkAnimation();
|
||||
}
|
||||
|
||||
private _handleOptionsChanged(): void {
|
||||
@@ -311,7 +312,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
l.reset(this._terminal);
|
||||
}
|
||||
|
||||
this._cursorBlinkStateManager?.restartBlinkAnimation();
|
||||
this._cursorBlinkStateManager.value?.restartBlinkAnimation();
|
||||
this._updateCursorBlink();
|
||||
}
|
||||
|
||||
@@ -323,6 +324,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
return false;
|
||||
}
|
||||
|
||||
@traceCall
|
||||
public renderRows(start: number, end: number): void {
|
||||
if (!this._isAttached) {
|
||||
if (this._coreBrowserService.window.document.body.contains(this._core.screenElement!) && this._charSizeService.width && this._charSizeService.height) {
|
||||
@@ -357,21 +359,18 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
// Render
|
||||
this._rectangleRenderer?.renderBackgrounds();
|
||||
this._glyphRenderer?.render(this._model);
|
||||
if (!this._cursorBlinkStateManager || this._cursorBlinkStateManager.isCursorVisible) {
|
||||
if (!this._cursorBlinkStateManager.value || this._cursorBlinkStateManager.value.isCursorVisible) {
|
||||
this._rectangleRenderer?.renderCursor();
|
||||
}
|
||||
}
|
||||
|
||||
private _updateCursorBlink(): void {
|
||||
if (this._terminal.options.cursorBlink) {
|
||||
if (!this._cursorBlinkStateManager) {
|
||||
this._cursorBlinkStateManager = this.register(new CursorBlinkStateManager(() => {
|
||||
this._requestRedrawCursor();
|
||||
}, this._coreBrowserService));
|
||||
}
|
||||
this._cursorBlinkStateManager.value = new CursorBlinkStateManager(() => {
|
||||
this._requestRedrawCursor();
|
||||
}, this._coreBrowserService);
|
||||
} else {
|
||||
this._cursorBlinkStateManager?.dispose();
|
||||
this._cursorBlinkStateManager = undefined;
|
||||
this._cursorBlinkStateManager.clear();
|
||||
}
|
||||
// Request a refresh from the terminal as management of rendering is being
|
||||
// moved back to the terminal
|
||||
@@ -406,7 +405,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
const isCursorVisible =
|
||||
this._coreService.isCursorInitialized &&
|
||||
!this._coreService.isCursorHidden &&
|
||||
(!this._cursorBlinkStateManager || this._cursorBlinkStateManager.isCursorVisible);
|
||||
(!this._cursorBlinkStateManager.value || this._cursorBlinkStateManager.value.isCursorVisible);
|
||||
this._model.cursor = undefined;
|
||||
let modelUpdated = false;
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
},
|
||||
"strict": true,
|
||||
"downlevelIteration": true,
|
||||
"experimentalDecorators": true,
|
||||
"types": [
|
||||
"../../../node_modules/@types/mocha"
|
||||
]
|
||||
|
||||
+1
-1
@@ -433,7 +433,7 @@ function initOptions(term: TerminalType): void {
|
||||
fontFamily: null,
|
||||
fontWeight: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'],
|
||||
fontWeightBold: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'],
|
||||
logLevel: ['debug', 'info', 'warn', 'error', 'off'],
|
||||
logLevel: ['trace', 'debug', 'info', 'warn', 'error', 'off'],
|
||||
theme: ['default', 'xtermjs', 'sapphire', 'light'],
|
||||
wordSeparator: null
|
||||
};
|
||||
|
||||
@@ -16,6 +16,7 @@ import { IdleTaskQueue } from 'common/TaskQueue';
|
||||
import { IBoundingBox, ICharAtlasConfig, IRasterizedGlyph, IRequestRedrawEvent, ITextureAtlas } from 'browser/renderer/shared/Types';
|
||||
import { EventEmitter } from 'common/EventEmitter';
|
||||
import { IColorContrastCache } from 'browser/Types';
|
||||
import { traceCall } from 'common/services/LogService';
|
||||
|
||||
/**
|
||||
* A shared object which is used to draw nothing for a particular cell.
|
||||
@@ -425,6 +426,7 @@ export class TextureAtlas implements ITextureAtlas {
|
||||
return this._config.colors.contrastCache;
|
||||
}
|
||||
|
||||
@traceCall
|
||||
private _drawToCache(codeOrChars: number | string, bg: number, fg: number, ext: number, restrictToCellHeight: boolean = false): IRasterizedGlyph {
|
||||
const chars = typeof codeOrChars === 'number' ? String.fromCharCode(codeOrChars) : codeOrChars;
|
||||
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
*/
|
||||
|
||||
import { assert } from 'chai';
|
||||
import { Disposable } from 'common/Lifecycle';
|
||||
import { Disposable, MutableDisposable } from 'common/Lifecycle';
|
||||
import { IDisposable } from 'common/Types';
|
||||
|
||||
class TestDisposable extends Disposable {
|
||||
public get isDisposed(): boolean {
|
||||
@@ -43,3 +44,52 @@ describe('Disposable', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('MutableDisposable', () => {
|
||||
const mutable = new MutableDisposable();
|
||||
class TrackedDisposable extends Disposable {
|
||||
public get isDisposed(): boolean { return this._isDisposed; }
|
||||
}
|
||||
describe('value', () => {
|
||||
it('should set the value', () => {
|
||||
const d1 = new TrackedDisposable();
|
||||
mutable.value = d1;
|
||||
assert.strictEqual(mutable.value, d1);
|
||||
assert.isFalse(d1.isDisposed);
|
||||
});
|
||||
it('should dispose of any previous value', () => {
|
||||
const d1 = new TrackedDisposable();
|
||||
const d2 = new TrackedDisposable();
|
||||
mutable.value = d1;
|
||||
mutable.value = d2;
|
||||
assert.strictEqual(mutable.value, d2);
|
||||
assert.isTrue(d1.isDisposed);
|
||||
assert.isFalse(d2.isDisposed);
|
||||
});
|
||||
});
|
||||
describe('clear', () => {
|
||||
it('should clear and dispose of the object', () => {
|
||||
const d1 = new TrackedDisposable();
|
||||
mutable.value = d1;
|
||||
mutable.clear();
|
||||
assert.strictEqual(mutable.value, undefined);
|
||||
assert.isTrue(d1.isDisposed);
|
||||
});
|
||||
});
|
||||
it('dispose', () => {
|
||||
it('should dispose of the object', () => {
|
||||
const d1 = new TrackedDisposable();
|
||||
mutable.value = d1;
|
||||
mutable.dispose();
|
||||
assert.strictEqual(mutable.value, undefined);
|
||||
assert.isTrue(d1.isDisposed);
|
||||
});
|
||||
it('should prevent using the MutableDisposable again', () => {
|
||||
const d1 = new TrackedDisposable();
|
||||
mutable.value = d1;
|
||||
mutable.dispose();
|
||||
mutable.value = new TrackedDisposable();
|
||||
assert.strictEqual(mutable.value, undefined);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -50,6 +50,42 @@ export abstract class Disposable implements IDisposable {
|
||||
}
|
||||
}
|
||||
|
||||
export class MutableDisposable<T extends IDisposable> implements IDisposable {
|
||||
private _value?: T;
|
||||
private _isDisposed = false;
|
||||
|
||||
/**
|
||||
* Gets the value if it exists.
|
||||
*/
|
||||
public get value(): T | undefined {
|
||||
return this._isDisposed ? undefined : this._value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value, disposing of the old value if it exists.
|
||||
*/
|
||||
public set value(value: T | undefined) {
|
||||
if (this._isDisposed || value === this._value) {
|
||||
return;
|
||||
}
|
||||
this._value?.dispose();
|
||||
this._value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the stored value and disposes of the previously stored value.
|
||||
*/
|
||||
public clear(): void {
|
||||
this.value = undefined;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._isDisposed = true;
|
||||
this._value?.dispose();
|
||||
this._value = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap a function in a disposable.
|
||||
*/
|
||||
|
||||
@@ -105,6 +105,7 @@ export class MockCoreService implements ICoreService {
|
||||
export class MockLogService implements ILogService {
|
||||
public serviceBrand: any;
|
||||
public logLevel = LogLevelEnum.DEBUG;
|
||||
public trace(message: any, ...optionalParams: any[]): void { }
|
||||
public debug(message: any, ...optionalParams: any[]): void { }
|
||||
public info(message: any, ...optionalParams: any[]): void { }
|
||||
public warn(message: any, ...optionalParams: any[]): void { }
|
||||
|
||||
@@ -21,6 +21,7 @@ interface IConsole {
|
||||
declare const console: IConsole;
|
||||
|
||||
const optionsKeyToLogLevel: { [key: string]: LogLevelEnum } = {
|
||||
trace: LogLevelEnum.TRACE,
|
||||
debug: LogLevelEnum.DEBUG,
|
||||
info: LogLevelEnum.INFO,
|
||||
warn: LogLevelEnum.WARN,
|
||||
@@ -42,6 +43,9 @@ export class LogService extends Disposable implements ILogService {
|
||||
super();
|
||||
this._updateLogLevel();
|
||||
this.register(this._optionsService.onSpecificOptionChange('logLevel', () => this._updateLogLevel()));
|
||||
|
||||
// For trace logging, assume the latest created log service is valid
|
||||
traceLogger = this;
|
||||
}
|
||||
|
||||
private _updateLogLevel(): void {
|
||||
@@ -61,6 +65,12 @@ export class LogService extends Disposable implements ILogService {
|
||||
type.call(console, (this._optionsService.options.logger ? '' : LOG_PREFIX) + message, ...optionalParams);
|
||||
}
|
||||
|
||||
public trace(message: string, ...optionalParams: any[]): void {
|
||||
if (this._logLevel <= LogLevelEnum.TRACE) {
|
||||
this._log(this._optionsService.options.logger?.trace.bind(this._optionsService.options.logger) ?? console.log, message, optionalParams);
|
||||
}
|
||||
}
|
||||
|
||||
public debug(message: string, ...optionalParams: any[]): void {
|
||||
if (this._logLevel <= LogLevelEnum.DEBUG) {
|
||||
this._log(this._optionsService.options.logger?.debug.bind(this._optionsService.options.logger) ?? console.log, message, optionalParams);
|
||||
@@ -85,3 +95,30 @@ export class LogService extends Disposable implements ILogService {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let traceLogger: ILogService;
|
||||
export function setTraceLogger(logger: ILogService): void {
|
||||
traceLogger = logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* A decorator that can be used to automatically log trace calls to the decorated function.
|
||||
*/
|
||||
export function traceCall(_target: any, key: string, descriptor: any): any {
|
||||
if (typeof descriptor.value !== 'function') {
|
||||
throw new Error('not supported');
|
||||
}
|
||||
const fnKey = 'value';
|
||||
const fn = descriptor.value;
|
||||
descriptor[fnKey] = function (...args: any[]) {
|
||||
// Early exit
|
||||
if (traceLogger.logLevel !== LogLevelEnum.TRACE) {
|
||||
return fn.apply(this, args);
|
||||
}
|
||||
|
||||
traceLogger.trace(`GlyphRenderer#${fn.name}(${args.map(e => JSON.stringify(e)).join(', ')})`);
|
||||
const result = fn.apply(this, args);
|
||||
traceLogger.trace(`GlyphRenderer#${fn.name} return`, result);
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -142,11 +142,12 @@ export interface IInstantiationService {
|
||||
}
|
||||
|
||||
export enum LogLevelEnum {
|
||||
DEBUG = 0,
|
||||
INFO = 1,
|
||||
WARN = 2,
|
||||
ERROR = 3,
|
||||
OFF = 4
|
||||
TRACE = 0,
|
||||
DEBUG = 1,
|
||||
INFO = 2,
|
||||
WARN = 3,
|
||||
ERROR = 4,
|
||||
OFF = 5
|
||||
}
|
||||
|
||||
export const ILogService = createDecorator<ILogService>('LogService');
|
||||
@@ -155,6 +156,7 @@ export interface ILogService {
|
||||
|
||||
readonly logLevel: LogLevelEnum;
|
||||
|
||||
trace(message: any, ...optionalParams: any[]): void;
|
||||
debug(message: any, ...optionalParams: any[]): void;
|
||||
info(message: any, ...optionalParams: any[]): void;
|
||||
warn(message: any, ...optionalParams: any[]): void;
|
||||
@@ -201,7 +203,7 @@ export interface IOptionsService {
|
||||
}
|
||||
|
||||
export type FontWeight = 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900' | number;
|
||||
export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'off';
|
||||
export type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'off';
|
||||
|
||||
export interface ITerminalOptions {
|
||||
allowProposedApi?: boolean;
|
||||
|
||||
Vendored
+13
-7
@@ -11,7 +11,7 @@ declare module 'xterm-headless' {
|
||||
/**
|
||||
* A string representing log level.
|
||||
*/
|
||||
export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'off';
|
||||
export type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'off';
|
||||
|
||||
/**
|
||||
* An object containing options for the terminal.
|
||||
@@ -100,11 +100,12 @@ declare module 'xterm-headless' {
|
||||
* What log level to use, this will log for all levels below and including
|
||||
* what is set:
|
||||
*
|
||||
* 1. debug
|
||||
* 2. info (default)
|
||||
* 3. warn
|
||||
* 4. error
|
||||
* 5. off
|
||||
* 1. trace
|
||||
* 2. debug
|
||||
* 3. info (default)
|
||||
* 4. warn
|
||||
* 5. error
|
||||
* 6. off
|
||||
*/
|
||||
logLevel?: LogLevel;
|
||||
|
||||
@@ -314,9 +315,14 @@ declare module 'xterm-headless' {
|
||||
* A replacement logger for `console`.
|
||||
*/
|
||||
export interface ILogger {
|
||||
/**
|
||||
* Log a trace message, this will only be called if
|
||||
* {@link ITerminalOptions.logLevel} is set to trace.
|
||||
*/
|
||||
trace(message: string, ...args: any[]): void;
|
||||
/**
|
||||
* Log a debug message, this will only be called if
|
||||
* {@link ITerminalOptions.logLevel} is set to debug.
|
||||
* {@link ITerminalOptions.logLevel} is set to debug or below.
|
||||
*/
|
||||
debug(message: string, ...args: any[]): void;
|
||||
/**
|
||||
|
||||
Vendored
+13
-7
@@ -18,7 +18,7 @@ declare module 'xterm' {
|
||||
/**
|
||||
* A string representing log level.
|
||||
*/
|
||||
export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'off';
|
||||
export type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'off';
|
||||
|
||||
/**
|
||||
* An object containing options for the terminal.
|
||||
@@ -158,11 +158,12 @@ declare module 'xterm' {
|
||||
* What log level to use, this will log for all levels below and including
|
||||
* what is set:
|
||||
*
|
||||
* 1. debug
|
||||
* 2. info (default)
|
||||
* 3. warn
|
||||
* 4. error
|
||||
* 5. off
|
||||
* 1. trace
|
||||
* 2. debug
|
||||
* 3. info (default)
|
||||
* 4. warn
|
||||
* 5. error
|
||||
* 6. off
|
||||
*/
|
||||
logLevel?: LogLevel;
|
||||
|
||||
@@ -391,9 +392,14 @@ declare module 'xterm' {
|
||||
* A replacement logger for `console`.
|
||||
*/
|
||||
export interface ILogger {
|
||||
/**
|
||||
* Log a trace message, this will only be called if
|
||||
* {@link ITerminalOptions.logLevel} is set to trace.
|
||||
*/
|
||||
trace(message: string, ...args: any[]): void;
|
||||
/**
|
||||
* Log a debug message, this will only be called if
|
||||
* {@link ITerminalOptions.logLevel} is set to debug.
|
||||
* {@link ITerminalOptions.logLevel} is set to debug or below.
|
||||
*/
|
||||
debug(message: string, ...args: any[]): void;
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user