Merge branch 'master' into window_manipulation
This commit is contained in:
+8
-3
@@ -358,6 +358,9 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the dirty row service so we know which lines changed as a result of parsing
|
||||
this._dirtyRowService.clearRange();
|
||||
|
||||
// process big data in smaller chunks
|
||||
if (data.length > MAX_PARSEBUFFER_LENGTH) {
|
||||
for (let i = 0; i < data.length; i += MAX_PARSEBUFFER_LENGTH) {
|
||||
@@ -378,6 +381,8 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
if (buffer.x !== cursorStartX || buffer.y !== cursorStartY) {
|
||||
this._onCursorMove.fire();
|
||||
}
|
||||
|
||||
// Refresh any dirty rows accumulated as part of parsing
|
||||
this._terminal.refresh(this._dirtyRowService.start, this._dirtyRowService.end);
|
||||
}
|
||||
|
||||
@@ -1469,7 +1474,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
this._logService.debug('DECSET 1015 not supported (see #2507)');
|
||||
break;
|
||||
case 25: // show cursor
|
||||
this._terminal.cursorHidden = false;
|
||||
this._coreService.isCursorHidden = false;
|
||||
break;
|
||||
case 1048: // alt screen cursor
|
||||
this.saveCursor();
|
||||
@@ -1644,7 +1649,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
this._logService.debug('DECRST 1015 not supported (see #2507)');
|
||||
break;
|
||||
case 25: // hide cursor
|
||||
this._terminal.cursorHidden = true;
|
||||
this._coreService.isCursorHidden = true;
|
||||
break;
|
||||
case 1048: // alt screen cursor
|
||||
this.restoreCursor();
|
||||
@@ -1978,7 +1983,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* http://vt100.net/docs/vt220-rm/table4-10.html
|
||||
*/
|
||||
public softReset(params: IParams): void {
|
||||
this._terminal.cursorHidden = false;
|
||||
this._coreService.isCursorHidden = false;
|
||||
this._terminal.insertMode = false;
|
||||
this._terminal.originMode = false;
|
||||
this._terminal.wraparoundMode = true; // defaults: xterm - true, vt100 - false
|
||||
|
||||
@@ -163,17 +163,6 @@ describe('Terminal', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('reset', () => {
|
||||
it('should not affect cursorState', () => {
|
||||
term.cursorState = 1;
|
||||
term.reset();
|
||||
assert.equal(term.cursorState, 1);
|
||||
term.cursorState = 0;
|
||||
term.reset();
|
||||
assert.equal(term.cursorState, 0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('clear', () => {
|
||||
it('should clear a buffer equal to rows', () => {
|
||||
const promptLine = term.buffer.lines.get(term.buffer.ybase + term.buffer.y);
|
||||
|
||||
+33
-79
@@ -28,7 +28,7 @@ import { Viewport } from 'browser/Viewport';
|
||||
import { rightClickHandler, moveTextAreaUnderMouseCursor, handlePasteEvent, copyHandler, paste } from 'browser/Clipboard';
|
||||
import { C0 } from 'common/data/EscapeSequences';
|
||||
import { InputHandler } from './InputHandler';
|
||||
import { Renderer } from './renderer/Renderer';
|
||||
import { Renderer } from 'browser/renderer/Renderer';
|
||||
import { Linkifier } from 'browser/Linkifier';
|
||||
import { SelectionService } from 'browser/services/SelectionService';
|
||||
import * as Browser from 'common/Platform';
|
||||
@@ -38,7 +38,7 @@ import { SoundService } from 'browser/services/SoundService';
|
||||
import { MouseZoneManager } from 'browser/MouseZoneManager';
|
||||
import { AccessibilityManager } from './AccessibilityManager';
|
||||
import { ITheme, IMarker, IDisposable, ISelectionPosition } from 'xterm';
|
||||
import { DomRenderer } from './renderer/dom/DomRenderer';
|
||||
import { DomRenderer } from 'browser/renderer/dom/DomRenderer';
|
||||
import { IKeyboardEvent, KeyboardResultType, ICharset, IBufferLine, IAttributeData, CoreMouseEventType, CoreMouseButton, CoreMouseAction } from 'common/Types';
|
||||
import { evaluateKeyboardEvent } from 'common/input/Keyboard';
|
||||
import { EventEmitter, IEvent } from 'common/EventEmitter';
|
||||
@@ -48,7 +48,7 @@ import { ColorManager } from 'browser/ColorManager';
|
||||
import { RenderService } from 'browser/services/RenderService';
|
||||
import { IOptionsService, IBufferService, ICoreMouseService, ICoreService, ILogService, IDirtyRowService, IInstantiationService } from 'common/services/Services';
|
||||
import { OptionsService } from 'common/services/OptionsService';
|
||||
import { ICharSizeService, IRenderService, IMouseService, ISelectionService, ISoundService } from 'browser/services/Services';
|
||||
import { ICharSizeService, IRenderService, IMouseService, ISelectionService, ISoundService, ICoreBrowserService } from 'browser/services/Services';
|
||||
import { CharSizeService } from 'browser/services/CharSizeService';
|
||||
import { BufferService, MINIMUM_COLS, MINIMUM_ROWS } from 'common/services/BufferService';
|
||||
import { Disposable } from 'common/Lifecycle';
|
||||
@@ -63,6 +63,7 @@ import { DirtyRowService } from 'common/services/DirtyRowService';
|
||||
import { InstantiationService } from 'common/services/InstantiationService';
|
||||
import { CoreMouseService } from 'common/services/CoreMouseService';
|
||||
import { WriteBuffer } from 'common/input/WriteBuffer';
|
||||
import { CoreBrowserService } from 'browser/services/CoreBrowserService';
|
||||
|
||||
// Let it work inside Node.js for automated testing purposes.
|
||||
const document = (typeof window !== 'undefined') ? window.document : null;
|
||||
@@ -90,10 +91,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
// TODO: We should remove options once components adopt optionsService
|
||||
public get options(): ITerminalOptions { return this.optionsService.options; }
|
||||
|
||||
// TODO: This can be changed to an enum or boolean, 0 and 1 seem to be the only options
|
||||
public cursorState: number;
|
||||
public cursorHidden: boolean;
|
||||
|
||||
private _customKeyEventHandler: CustomKeyEventHandler;
|
||||
|
||||
// common services
|
||||
@@ -238,25 +235,17 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
return;
|
||||
}
|
||||
super.dispose();
|
||||
if (this._windowsMode) {
|
||||
this._windowsMode.dispose();
|
||||
this._windowsMode = undefined;
|
||||
}
|
||||
if (this._renderService) {
|
||||
this._renderService.dispose();
|
||||
}
|
||||
this._windowsMode?.dispose();
|
||||
this._windowsMode = undefined;
|
||||
this._renderService?.dispose();
|
||||
this._customKeyEventHandler = null;
|
||||
this.write = () => {};
|
||||
if (this.element && this.element.parentNode) {
|
||||
this.element.parentNode.removeChild(this.element);
|
||||
}
|
||||
this.element?.parentNode?.removeChild(this.element);
|
||||
}
|
||||
|
||||
private _setup(): void {
|
||||
this._parent = document ? document.body : null;
|
||||
|
||||
this.cursorState = 0;
|
||||
this.cursorHidden = false;
|
||||
this._customKeyEventHandler = null;
|
||||
|
||||
// modes
|
||||
@@ -323,10 +312,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
}
|
||||
}
|
||||
|
||||
public get isFocused(): boolean {
|
||||
return document.activeElement === this.textarea && document.hasFocus();
|
||||
}
|
||||
|
||||
private _setupOptionsListeners(): void {
|
||||
// TODO: These listeners should be owned by individual components
|
||||
this.optionsService.onOptionChange(key => {
|
||||
@@ -334,12 +319,8 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
case 'fontFamily':
|
||||
case 'fontSize':
|
||||
// When the font changes the size of the cells may change which requires a renderer clear
|
||||
if (this._renderService) {
|
||||
this._renderService.clear();
|
||||
}
|
||||
if (this._charSizeService) {
|
||||
this._charSizeService.measure();
|
||||
}
|
||||
this._renderService?.clear();
|
||||
this._charSizeService?.measure();
|
||||
break;
|
||||
case 'drawBoldTextInBrightColors':
|
||||
case 'letterSpacing':
|
||||
@@ -361,9 +342,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
break;
|
||||
case 'scrollback':
|
||||
this.buffers.resize(this.cols, this.rows);
|
||||
if (this.viewport) {
|
||||
this.viewport.syncScrollArea();
|
||||
}
|
||||
this.viewport?.syncScrollArea();
|
||||
break;
|
||||
case 'screenReaderMode':
|
||||
if (this.optionsService.options.screenReaderMode) {
|
||||
@@ -371,10 +350,8 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
this._accessibilityManager = new AccessibilityManager(this, this._renderService);
|
||||
}
|
||||
} else {
|
||||
if (this._accessibilityManager) {
|
||||
this._accessibilityManager.dispose();
|
||||
this._accessibilityManager = null;
|
||||
}
|
||||
this._accessibilityManager?.dispose();
|
||||
this._accessibilityManager = null;
|
||||
}
|
||||
break;
|
||||
case 'tabStopWidth': this.buffers.setupTabStops(); break;
|
||||
@@ -387,10 +364,8 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
this._windowsMode = applyWindowsMode(this);
|
||||
}
|
||||
} else {
|
||||
if (this._windowsMode) {
|
||||
this._windowsMode.dispose();
|
||||
this._windowsMode = undefined;
|
||||
}
|
||||
this._windowsMode?.dispose();
|
||||
this._windowsMode = undefined;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -551,6 +526,9 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
this.register(addDisposableDomListener(this.textarea, 'blur', () => this._onTextAreaBlur()));
|
||||
this._helperContainer.appendChild(this.textarea);
|
||||
|
||||
const coreBrowserService = this._instantiationService.createInstance(CoreBrowserService, this.textarea);
|
||||
this._instantiationService.setService(ICoreBrowserService, coreBrowserService);
|
||||
|
||||
this._charSizeService = this._instantiationService.createInstance(CharSizeService, this._document, this._helperContainer);
|
||||
this._instantiationService.setService(ICharSizeService, this._charSizeService);
|
||||
|
||||
@@ -651,8 +629,8 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
|
||||
private _createRenderer(): IRenderer {
|
||||
switch (this.options.rendererType) {
|
||||
case 'canvas': return new Renderer(this._colorManager.colors, this, this._bufferService, this._charSizeService, this.optionsService);
|
||||
case 'dom': return new DomRenderer(this, this._colorManager.colors, this._charSizeService, this.optionsService);
|
||||
case 'canvas': return this._instantiationService.createInstance(Renderer, this._colorManager.colors, this.screenElement, this.linkifier);
|
||||
case 'dom': return this._instantiationService.createInstance(DomRenderer, this._colorManager.colors, this.element, this.screenElement, this._viewportElement, this.linkifier);
|
||||
default: throw new Error(`Unrecognized rendererType "${this.options.rendererType}"`);
|
||||
}
|
||||
}
|
||||
@@ -663,15 +641,9 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
*/
|
||||
private _setTheme(theme: ITheme): void {
|
||||
this._theme = theme;
|
||||
if (this._colorManager) {
|
||||
this._colorManager.setTheme(theme);
|
||||
}
|
||||
if (this._renderService) {
|
||||
this._renderService.setColors(this._colorManager.colors);
|
||||
}
|
||||
if (this.viewport) {
|
||||
this.viewport.onThemeChange(this._colorManager.colors);
|
||||
}
|
||||
this._colorManager?.setTheme(theme);
|
||||
this._renderService?.setColors(this._colorManager.colors);
|
||||
this.viewport?.onThemeChange(this._colorManager.colors);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -938,9 +910,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
* @param end The row to end at (between start and this.rows - 1).
|
||||
*/
|
||||
public refresh(start: number, end: number): void {
|
||||
if (this._renderService) {
|
||||
this._renderService.refreshRows(start, end);
|
||||
}
|
||||
this._renderService?.refreshRows(start, end);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -949,9 +919,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
* @param end The row to end at (between start and this.rows - 1).
|
||||
*/
|
||||
private _queueLinkification(start: number, end: number): void {
|
||||
if (this.linkifier) {
|
||||
this.linkifier.linkifyRows(start, end);
|
||||
}
|
||||
this.linkifier?.linkifyRows(start, end);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -969,8 +937,8 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
* Display the cursor element
|
||||
*/
|
||||
public showCursor(): void {
|
||||
if (!this.cursorState) {
|
||||
this.cursorState = 1;
|
||||
if (!this._coreService.isCursorInitialized) {
|
||||
this._coreService.isCursorInitialized = true;
|
||||
this.refresh(this.buffer.y, this.buffer.y);
|
||||
}
|
||||
}
|
||||
@@ -1231,24 +1199,18 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
* Clears the current terminal selection.
|
||||
*/
|
||||
public clearSelection(): void {
|
||||
if (this._selectionService) {
|
||||
this._selectionService.clearSelection();
|
||||
}
|
||||
this._selectionService?.clearSelection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects all text within the terminal.
|
||||
*/
|
||||
public selectAll(): void {
|
||||
if (this._selectionService) {
|
||||
this._selectionService.selectAll();
|
||||
}
|
||||
this._selectionService?.selectAll();
|
||||
}
|
||||
|
||||
public selectLines(start: number, end: number): void {
|
||||
if (this._selectionService) {
|
||||
this._selectionService.selectLines(start, end);
|
||||
}
|
||||
this._selectionService?.selectLines(start, end);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1455,9 +1417,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
this._bufferService.resize(x, y);
|
||||
this.buffers.setupTabStops(this.cols);
|
||||
|
||||
if (this._charSizeService) {
|
||||
this._charSizeService.measure();
|
||||
}
|
||||
this._charSizeService?.measure();
|
||||
|
||||
// Sync the scroll area to make sure scroll events don't fire and scroll the viewport to an
|
||||
// invalid location
|
||||
@@ -1544,28 +1504,22 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
this.options.cols = this.cols;
|
||||
const customKeyEventHandler = this._customKeyEventHandler;
|
||||
const inputHandler = this._inputHandler;
|
||||
const cursorState = this.cursorState;
|
||||
const userScrolling = this._userScrolling;
|
||||
|
||||
this._setup();
|
||||
this._bufferService.reset();
|
||||
this._coreService.reset();
|
||||
this._coreMouseService.reset();
|
||||
if (this._selectionService) {
|
||||
this._selectionService.reset();
|
||||
}
|
||||
this._selectionService?.reset();
|
||||
|
||||
// reattach
|
||||
this._customKeyEventHandler = customKeyEventHandler;
|
||||
this._inputHandler = inputHandler;
|
||||
this.cursorState = cursorState;
|
||||
this._userScrolling = userScrolling;
|
||||
|
||||
// do a full screen refresh
|
||||
this.refresh(0, this.rows - 1);
|
||||
if (this.viewport) {
|
||||
this.viewport.syncScrollArea();
|
||||
}
|
||||
this.viewport?.syncScrollArea();
|
||||
}
|
||||
|
||||
// TODO: Remove cancel function and cancelEvents option
|
||||
|
||||
@@ -34,7 +34,6 @@ if (os.platform() === 'darwin') {
|
||||
// filter skipFilenames
|
||||
const FILES = TESTFILES.filter(value => SKIP_FILES.indexOf(value.split('/').slice(-1)[0]) === -1);
|
||||
|
||||
|
||||
describe('Escape Sequence Files', function(): void {
|
||||
this.timeout(1000);
|
||||
|
||||
@@ -44,6 +43,9 @@ describe('Escape Sequence Files', function(): void {
|
||||
let customHandler: IDisposable | undefined;
|
||||
|
||||
before(() => {
|
||||
if (process.platform === 'win32') {
|
||||
return;
|
||||
}
|
||||
ptyTerm = (pty as any).open({cols: COLS, rows: ROWS});
|
||||
slaveEnd = ptyTerm._slave;
|
||||
term = new Terminal({cols: COLS, rows: ROWS});
|
||||
@@ -51,12 +53,15 @@ describe('Escape Sequence Files', function(): void {
|
||||
});
|
||||
|
||||
after(() => {
|
||||
if (process.platform === 'win32') {
|
||||
return;
|
||||
}
|
||||
ptyTerm._master.end();
|
||||
ptyTerm._master.destroy();
|
||||
});
|
||||
|
||||
FILES.forEach(filename => {
|
||||
it(filename.split('/').slice(-1)[0], async () => {
|
||||
(process.platform === 'win32' ? it.skip : it)(filename.split('/').slice(-1)[0], async () => {
|
||||
// reset terminal and handler
|
||||
if (customHandler) {
|
||||
customHandler.dispose();
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { IRenderer, IRenderDimensions, CharacterJoinerHandler } from 'browser/renderer/Types';
|
||||
import { IRenderer, IRenderDimensions, CharacterJoinerHandler, IRequestRefreshRowsEvent } from 'browser/renderer/Types';
|
||||
import { IInputHandlingTerminal, ICompositionHelper, ITerminal, IBrowser, ITerminalOptions } from './Types';
|
||||
import { IBuffer, IBufferStringIterator, IBufferSet } from 'common/buffer/Types';
|
||||
import { IBufferLine, ICellData, IAttributeData, ICircularList, XtermListener, ICharset, CoreMouseEventType } from 'common/Types';
|
||||
@@ -361,6 +361,7 @@ export class MockBuffer implements IBuffer {
|
||||
}
|
||||
|
||||
export class MockRenderer implements IRenderer {
|
||||
onRequestRefreshRows: IEvent<IRequestRefreshRowsEvent>;
|
||||
onCanvasResize: IEvent<{ width: number; height: number; }>;
|
||||
onRender: IEvent<{ start: number; end: number; }>;
|
||||
dispose(): void {
|
||||
|
||||
Vendored
-4
@@ -37,7 +37,6 @@ export interface IInputHandlingTerminal {
|
||||
curAttrData: IAttributeData;
|
||||
mouseEvents: CoreMouseEventType;
|
||||
sendFocus: boolean;
|
||||
cursorHidden: boolean;
|
||||
|
||||
buffers: IBufferSet;
|
||||
buffer: IBuffer;
|
||||
@@ -153,11 +152,8 @@ export interface IInputHandler {
|
||||
export interface ITerminal extends IPublicTerminal, IElementAccessor, IBufferAccessor, ILinkifierAccessor {
|
||||
screenElement: HTMLElement;
|
||||
browser: IBrowser;
|
||||
cursorHidden: boolean;
|
||||
cursorState: number;
|
||||
buffer: IBuffer;
|
||||
buffers: IBufferSet;
|
||||
isFocused: boolean;
|
||||
viewport: IViewport;
|
||||
bracketedPasteMode: boolean;
|
||||
optionsService: IOptionsService;
|
||||
|
||||
@@ -82,12 +82,12 @@ export function moveTextAreaUnderMouseCursor(ev: MouseEvent, textarea: HTMLTextA
|
||||
// Reset the terminal textarea's styling
|
||||
// Timeout needs to be long enough for click event to be handled.
|
||||
setTimeout(() => {
|
||||
textarea.style.position = null;
|
||||
textarea.style.width = null;
|
||||
textarea.style.height = null;
|
||||
textarea.style.left = null;
|
||||
textarea.style.top = null;
|
||||
textarea.style.zIndex = null;
|
||||
textarea.style.position = '';
|
||||
textarea.style.width = '';
|
||||
textarea.style.height = '';
|
||||
textarea.style.left = '';
|
||||
textarea.style.top = '';
|
||||
textarea.style.zIndex = '';
|
||||
}, 200);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
/**
|
||||
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { assert } from 'chai';
|
||||
import { blend, fromCss, toPaddedHex, toCss, toRgba } from 'browser/Color';
|
||||
|
||||
describe('Color', () => {
|
||||
describe('blend', () => {
|
||||
it('should blend colors based on the alpha channel', () => {
|
||||
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF00', rgba: 0xFFFFFF00 }), { css: '#000000', rgba: 0x000000FF });
|
||||
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF10', rgba: 0xFFFFFF10 }), { css: '#101010', rgba: 0x101010FF });
|
||||
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF20', rgba: 0xFFFFFF20 }), { css: '#202020', rgba: 0x202020FF });
|
||||
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF30', rgba: 0xFFFFFF30 }), { css: '#303030', rgba: 0x303030FF });
|
||||
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF40', rgba: 0xFFFFFF40 }), { css: '#404040', rgba: 0x404040FF });
|
||||
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF50', rgba: 0xFFFFFF50 }), { css: '#505050', rgba: 0x505050FF });
|
||||
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF60', rgba: 0xFFFFFF60 }), { css: '#606060', rgba: 0x606060FF });
|
||||
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF70', rgba: 0xFFFFFF70 }), { css: '#707070', rgba: 0x707070FF });
|
||||
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF80', rgba: 0xFFFFFF80 }), { css: '#808080', rgba: 0x808080FF });
|
||||
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF90', rgba: 0xFFFFFF90 }), { css: '#909090', rgba: 0x909090FF });
|
||||
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFA0', rgba: 0xFFFFFFA0 }), { css: '#a0a0a0', rgba: 0xA0A0A0FF });
|
||||
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFB0', rgba: 0xFFFFFFB0 }), { css: '#b0b0b0', rgba: 0xB0B0B0FF });
|
||||
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFC0', rgba: 0xFFFFFFC0 }), { css: '#c0c0c0', rgba: 0xC0C0C0FF });
|
||||
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFD0', rgba: 0xFFFFFFD0 }), { css: '#d0d0d0', rgba: 0xD0D0D0FF });
|
||||
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFE0', rgba: 0xFFFFFFE0 }), { css: '#e0e0e0', rgba: 0xE0E0E0FF });
|
||||
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFF0', rgba: 0xFFFFFFF0 }), { css: '#f0f0f0', rgba: 0xF0F0F0FF });
|
||||
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFFF', rgba: 0xFFFFFFFF }), { css: '#FFFFFFFF', rgba: 0xFFFFFFFF });
|
||||
});
|
||||
});
|
||||
|
||||
describe('fromCss', () => {
|
||||
it('should covert a CSS string to an IColor', () => {
|
||||
assert.deepEqual(fromCss('#000000'), { css: '#000000', rgba: 0x000000FF });
|
||||
assert.deepEqual(fromCss('#101010'), { css: '#101010', rgba: 0x101010FF });
|
||||
assert.deepEqual(fromCss('#202020'), { css: '#202020', rgba: 0x202020FF });
|
||||
assert.deepEqual(fromCss('#303030'), { css: '#303030', rgba: 0x303030FF });
|
||||
assert.deepEqual(fromCss('#404040'), { css: '#404040', rgba: 0x404040FF });
|
||||
assert.deepEqual(fromCss('#505050'), { css: '#505050', rgba: 0x505050FF });
|
||||
assert.deepEqual(fromCss('#606060'), { css: '#606060', rgba: 0x606060FF });
|
||||
assert.deepEqual(fromCss('#707070'), { css: '#707070', rgba: 0x707070FF });
|
||||
assert.deepEqual(fromCss('#808080'), { css: '#808080', rgba: 0x808080FF });
|
||||
assert.deepEqual(fromCss('#909090'), { css: '#909090', rgba: 0x909090FF });
|
||||
assert.deepEqual(fromCss('#a0a0a0'), { css: '#a0a0a0', rgba: 0xa0a0a0FF });
|
||||
assert.deepEqual(fromCss('#b0b0b0'), { css: '#b0b0b0', rgba: 0xb0b0b0FF });
|
||||
assert.deepEqual(fromCss('#c0c0c0'), { css: '#c0c0c0', rgba: 0xc0c0c0FF });
|
||||
assert.deepEqual(fromCss('#d0d0d0'), { css: '#d0d0d0', rgba: 0xd0d0d0FF });
|
||||
assert.deepEqual(fromCss('#e0e0e0'), { css: '#e0e0e0', rgba: 0xe0e0e0FF });
|
||||
assert.deepEqual(fromCss('#f0f0f0'), { css: '#f0f0f0', rgba: 0xf0f0f0FF });
|
||||
assert.deepEqual(fromCss('#ffffff'), { css: '#ffffff', rgba: 0xffffffFF });
|
||||
});
|
||||
});
|
||||
|
||||
describe('toPaddedHex', () => {
|
||||
it('should convert numbers to 2-digit hex values', () => {
|
||||
assert.equal(toPaddedHex(0x00), '00');
|
||||
assert.equal(toPaddedHex(0x10), '10');
|
||||
assert.equal(toPaddedHex(0x20), '20');
|
||||
assert.equal(toPaddedHex(0x30), '30');
|
||||
assert.equal(toPaddedHex(0x40), '40');
|
||||
assert.equal(toPaddedHex(0x50), '50');
|
||||
assert.equal(toPaddedHex(0x60), '60');
|
||||
assert.equal(toPaddedHex(0x70), '70');
|
||||
assert.equal(toPaddedHex(0x80), '80');
|
||||
assert.equal(toPaddedHex(0x90), '90');
|
||||
assert.equal(toPaddedHex(0xa0), 'a0');
|
||||
assert.equal(toPaddedHex(0xb0), 'b0');
|
||||
assert.equal(toPaddedHex(0xc0), 'c0');
|
||||
assert.equal(toPaddedHex(0xd0), 'd0');
|
||||
assert.equal(toPaddedHex(0xe0), 'e0');
|
||||
assert.equal(toPaddedHex(0xf0), 'f0');
|
||||
assert.equal(toPaddedHex(0xff), 'ff');
|
||||
});
|
||||
});
|
||||
|
||||
describe('toCss', () => {
|
||||
it('should convert an rgb array to css hex string', () => {
|
||||
assert.equal(toCss(0x00, 0x00, 0x00), '#000000');
|
||||
assert.equal(toCss(0x10, 0x10, 0x10), '#101010');
|
||||
assert.equal(toCss(0x20, 0x20, 0x20), '#202020');
|
||||
assert.equal(toCss(0x30, 0x30, 0x30), '#303030');
|
||||
assert.equal(toCss(0x40, 0x40, 0x40), '#404040');
|
||||
assert.equal(toCss(0x50, 0x50, 0x50), '#505050');
|
||||
assert.equal(toCss(0x60, 0x60, 0x60), '#606060');
|
||||
assert.equal(toCss(0x70, 0x70, 0x70), '#707070');
|
||||
assert.equal(toCss(0x80, 0x80, 0x80), '#808080');
|
||||
assert.equal(toCss(0x90, 0x90, 0x90), '#909090');
|
||||
assert.equal(toCss(0xa0, 0xa0, 0xa0), '#a0a0a0');
|
||||
assert.equal(toCss(0xb0, 0xb0, 0xb0), '#b0b0b0');
|
||||
assert.equal(toCss(0xc0, 0xc0, 0xc0), '#c0c0c0');
|
||||
assert.equal(toCss(0xd0, 0xd0, 0xd0), '#d0d0d0');
|
||||
assert.equal(toCss(0xe0, 0xe0, 0xe0), '#e0e0e0');
|
||||
assert.equal(toCss(0xf0, 0xf0, 0xf0), '#f0f0f0');
|
||||
assert.equal(toCss(0xff, 0xff, 0xff), '#ffffff');
|
||||
});
|
||||
});
|
||||
|
||||
describe('toRgba', () => {
|
||||
it('should convert an rgb array to an rgba number', () => {
|
||||
assert.equal(toRgba(0x00, 0x00, 0x00), 0x000000FF);
|
||||
assert.equal(toRgba(0x10, 0x10, 0x10), 0x101010FF);
|
||||
assert.equal(toRgba(0x20, 0x20, 0x20), 0x202020FF);
|
||||
assert.equal(toRgba(0x30, 0x30, 0x30), 0x303030FF);
|
||||
assert.equal(toRgba(0x40, 0x40, 0x40), 0x404040FF);
|
||||
assert.equal(toRgba(0x50, 0x50, 0x50), 0x505050FF);
|
||||
assert.equal(toRgba(0x60, 0x60, 0x60), 0x606060FF);
|
||||
assert.equal(toRgba(0x70, 0x70, 0x70), 0x707070FF);
|
||||
assert.equal(toRgba(0x80, 0x80, 0x80), 0x808080FF);
|
||||
assert.equal(toRgba(0x90, 0x90, 0x90), 0x909090FF);
|
||||
assert.equal(toRgba(0xa0, 0xa0, 0xa0), 0xa0a0a0FF);
|
||||
assert.equal(toRgba(0xb0, 0xb0, 0xb0), 0xb0b0b0FF);
|
||||
assert.equal(toRgba(0xc0, 0xc0, 0xc0), 0xc0c0c0FF);
|
||||
assert.equal(toRgba(0xd0, 0xd0, 0xd0), 0xd0d0d0FF);
|
||||
assert.equal(toRgba(0xe0, 0xe0, 0xe0), 0xe0e0e0FF);
|
||||
assert.equal(toRgba(0xf0, 0xf0, 0xf0), 0xf0f0f0FF);
|
||||
assert.equal(toRgba(0xff, 0xff, 0xff), 0xffffffFF);
|
||||
});
|
||||
it('should convert an rgba array to an rgba number', () => {
|
||||
assert.equal(toRgba(0x00, 0x00, 0x00, 0x00), 0x00000000);
|
||||
assert.equal(toRgba(0x10, 0x10, 0x10, 0x10), 0x10101010);
|
||||
assert.equal(toRgba(0x20, 0x20, 0x20, 0x20), 0x20202020);
|
||||
assert.equal(toRgba(0x30, 0x30, 0x30, 0x30), 0x30303030);
|
||||
assert.equal(toRgba(0x40, 0x40, 0x40, 0x40), 0x40404040);
|
||||
assert.equal(toRgba(0x50, 0x50, 0x50, 0x50), 0x50505050);
|
||||
assert.equal(toRgba(0x60, 0x60, 0x60, 0x60), 0x60606060);
|
||||
assert.equal(toRgba(0x70, 0x70, 0x70, 0x70), 0x70707070);
|
||||
assert.equal(toRgba(0x80, 0x80, 0x80, 0x80), 0x80808080);
|
||||
assert.equal(toRgba(0x90, 0x90, 0x90, 0x90), 0x90909090);
|
||||
assert.equal(toRgba(0xa0, 0xa0, 0xa0, 0xa0), 0xa0a0a0a0);
|
||||
assert.equal(toRgba(0xb0, 0xb0, 0xb0, 0xb0), 0xb0b0b0b0);
|
||||
assert.equal(toRgba(0xc0, 0xc0, 0xc0, 0xc0), 0xc0c0c0c0);
|
||||
assert.equal(toRgba(0xd0, 0xd0, 0xd0, 0xd0), 0xd0d0d0d0);
|
||||
assert.equal(toRgba(0xe0, 0xe0, 0xe0, 0xe0), 0xe0e0e0e0);
|
||||
assert.equal(toRgba(0xf0, 0xf0, 0xf0, 0xf0), 0xf0f0f0f0);
|
||||
assert.equal(toRgba(0xff, 0xff, 0xff, 0xff), 0xffffffff);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { IColor } from 'browser/Types';
|
||||
|
||||
export function blend(bg: IColor, fg: IColor): IColor {
|
||||
const a = (fg.rgba & 0xFF) / 255;
|
||||
if (a === 1) {
|
||||
return {
|
||||
css: fg.css,
|
||||
rgba: fg.rgba
|
||||
};
|
||||
}
|
||||
const fgR = (fg.rgba >> 24) & 0xFF;
|
||||
const fgG = (fg.rgba >> 16) & 0xFF;
|
||||
const fgB = (fg.rgba >> 8) & 0xFF;
|
||||
const bgR = (bg.rgba >> 24) & 0xFF;
|
||||
const bgG = (bg.rgba >> 16) & 0xFF;
|
||||
const bgB = (bg.rgba >> 8) & 0xFF;
|
||||
const r = bgR + Math.round((fgR - bgR) * a);
|
||||
const g = bgG + Math.round((fgG - bgG) * a);
|
||||
const b = bgB + Math.round((fgB - bgB) * a);
|
||||
const css = toCss(r, g, b);
|
||||
const rgba = toRgba(r, g, b);
|
||||
return { css, rgba };
|
||||
}
|
||||
|
||||
export function fromCss(css: string): IColor {
|
||||
return {
|
||||
css,
|
||||
rgba: (parseInt(css.slice(1), 16) << 8 | 0xFF) >>> 0
|
||||
};
|
||||
}
|
||||
|
||||
export function toPaddedHex(c: number): string {
|
||||
const s = c.toString(16);
|
||||
return s.length < 2 ? '0' + s : s;
|
||||
}
|
||||
|
||||
export function toCss(r: number, g: number, b: number): string {
|
||||
return `#${toPaddedHex(r)}${toPaddedHex(g)}${toPaddedHex(b)}`;
|
||||
}
|
||||
|
||||
export function toRgba(r: number, g: number, b: number, a: number = 0xFF): number {
|
||||
// >>> 0 forces an unsigned int
|
||||
return (r << 24 | g << 16 | b << 8 | a) >>> 0;
|
||||
}
|
||||
+29
-40
@@ -5,14 +5,15 @@
|
||||
|
||||
import { IColorManager, IColor, IColorSet } from 'browser/Types';
|
||||
import { ITheme } from 'common/services/Services';
|
||||
import { fromCss, toCss, blend, toRgba } from 'browser/Color';
|
||||
|
||||
const DEFAULT_FOREGROUND = fromHex('#ffffff');
|
||||
const DEFAULT_BACKGROUND = fromHex('#000000');
|
||||
const DEFAULT_CURSOR = fromHex('#ffffff');
|
||||
const DEFAULT_CURSOR_ACCENT = fromHex('#000000');
|
||||
const DEFAULT_FOREGROUND = fromCss('#ffffff');
|
||||
const DEFAULT_BACKGROUND = fromCss('#000000');
|
||||
const DEFAULT_CURSOR = fromCss('#ffffff');
|
||||
const DEFAULT_CURSOR_ACCENT = fromCss('#000000');
|
||||
const DEFAULT_SELECTION = {
|
||||
css: 'rgba(255, 255, 255, 0.3)',
|
||||
rgba: 0xFFFFFF77
|
||||
rgba: 0xFFFFFF4D
|
||||
};
|
||||
|
||||
// An IIFE to generate DEFAULT_ANSI_COLORS. Do not mutate DEFAULT_ANSI_COLORS, instead make a copy
|
||||
@@ -20,23 +21,23 @@ const DEFAULT_SELECTION = {
|
||||
export const DEFAULT_ANSI_COLORS = (() => {
|
||||
const colors = [
|
||||
// dark:
|
||||
fromHex('#2e3436'),
|
||||
fromHex('#cc0000'),
|
||||
fromHex('#4e9a06'),
|
||||
fromHex('#c4a000'),
|
||||
fromHex('#3465a4'),
|
||||
fromHex('#75507b'),
|
||||
fromHex('#06989a'),
|
||||
fromHex('#d3d7cf'),
|
||||
fromCss('#2e3436'),
|
||||
fromCss('#cc0000'),
|
||||
fromCss('#4e9a06'),
|
||||
fromCss('#c4a000'),
|
||||
fromCss('#3465a4'),
|
||||
fromCss('#75507b'),
|
||||
fromCss('#06989a'),
|
||||
fromCss('#d3d7cf'),
|
||||
// bright:
|
||||
fromHex('#555753'),
|
||||
fromHex('#ef2929'),
|
||||
fromHex('#8ae234'),
|
||||
fromHex('#fce94f'),
|
||||
fromHex('#729fcf'),
|
||||
fromHex('#ad7fa8'),
|
||||
fromHex('#34e2e2'),
|
||||
fromHex('#eeeeec')
|
||||
fromCss('#555753'),
|
||||
fromCss('#ef2929'),
|
||||
fromCss('#8ae234'),
|
||||
fromCss('#fce94f'),
|
||||
fromCss('#729fcf'),
|
||||
fromCss('#ad7fa8'),
|
||||
fromCss('#34e2e2'),
|
||||
fromCss('#eeeeec')
|
||||
];
|
||||
|
||||
// Fill in the remaining 240 ANSI colors.
|
||||
@@ -47,37 +48,23 @@ export const DEFAULT_ANSI_COLORS = (() => {
|
||||
const g = v[(i / 6) % 6 | 0];
|
||||
const b = v[i % 6];
|
||||
colors.push({
|
||||
css: `#${toPaddedHex(r)}${toPaddedHex(g)}${toPaddedHex(b)}`,
|
||||
// Use >>> 0 to force a conversion to an unsigned int
|
||||
rgba: ((r << 24) | (g << 16) | (b << 8) | 0xFF) >>> 0
|
||||
css: toCss(r, g, b),
|
||||
rgba: toRgba(r, g, b)
|
||||
});
|
||||
}
|
||||
|
||||
// Generate greys (232-255)
|
||||
for (let i = 0; i < 24; i++) {
|
||||
const c = 8 + i * 10;
|
||||
const ch = toPaddedHex(c);
|
||||
colors.push({
|
||||
css: `#${ch}${ch}${ch}`,
|
||||
rgba: ((c << 24) | (c << 16) | (c << 8) | 0xFF) >>> 0
|
||||
css: toCss(c, c, c),
|
||||
rgba: toRgba(c, c, c)
|
||||
});
|
||||
}
|
||||
|
||||
return colors;
|
||||
})();
|
||||
|
||||
function fromHex(css: string): IColor {
|
||||
return {
|
||||
css,
|
||||
rgba: parseInt(css.slice(1), 16) << 8 | 0xFF
|
||||
};
|
||||
}
|
||||
|
||||
function toPaddedHex(c: number): string {
|
||||
const s = c.toString(16);
|
||||
return s.length < 2 ? '0' + s : s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Manages the source of truth for a terminal's colors.
|
||||
*/
|
||||
@@ -103,6 +90,7 @@ export class ColorManager implements IColorManager {
|
||||
cursor: DEFAULT_CURSOR,
|
||||
cursorAccent: DEFAULT_CURSOR_ACCENT,
|
||||
selection: DEFAULT_SELECTION,
|
||||
selectionOpaque: blend(DEFAULT_BACKGROUND, DEFAULT_SELECTION),
|
||||
ansi: DEFAULT_ANSI_COLORS.slice()
|
||||
};
|
||||
}
|
||||
@@ -118,6 +106,7 @@ export class ColorManager implements IColorManager {
|
||||
this.colors.cursor = this._parseColor(theme.cursor, DEFAULT_CURSOR, true);
|
||||
this.colors.cursorAccent = this._parseColor(theme.cursorAccent, DEFAULT_CURSOR_ACCENT, true);
|
||||
this.colors.selection = this._parseColor(theme.selection, DEFAULT_SELECTION, true);
|
||||
this.colors.selectionOpaque = blend(this.colors.background, this.colors.selection);
|
||||
this.colors.ansi[0] = this._parseColor(theme.black, DEFAULT_ANSI_COLORS[0]);
|
||||
this.colors.ansi[1] = this._parseColor(theme.red, DEFAULT_ANSI_COLORS[1]);
|
||||
this.colors.ansi[2] = this._parseColor(theme.green, DEFAULT_ANSI_COLORS[2]);
|
||||
@@ -184,7 +173,7 @@ export class ColorManager implements IColorManager {
|
||||
|
||||
return {
|
||||
css,
|
||||
rgba: (data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3]) >>> 0
|
||||
rgba: toRgba(data[0], data[1], data[2], data[3])
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,11 +175,9 @@ export class MouseZoneManager extends Disposable implements IMouseZoneManager {
|
||||
// Find the active zone, prevent event propagation if found to prevent other
|
||||
// components from handling the mouse event.
|
||||
const zone = this._findZoneEventAt(e);
|
||||
if (zone) {
|
||||
if (zone.willLinkActivate(e)) {
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
}
|
||||
if (zone?.willLinkActivate(e)) {
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Vendored
+2
@@ -21,6 +21,8 @@ export interface IColorSet {
|
||||
cursor: IColor;
|
||||
cursorAccent: IColor;
|
||||
selection: IColor;
|
||||
/** The selection blended on top of background. */
|
||||
selectionOpaque: IColor;
|
||||
ansi: IColor[];
|
||||
}
|
||||
|
||||
|
||||
@@ -60,9 +60,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
|
||||
public dispose(): void {
|
||||
this._container.removeChild(this._canvas);
|
||||
if (this._charAtlas) {
|
||||
this._charAtlas.dispose();
|
||||
}
|
||||
this._charAtlas?.dispose();
|
||||
}
|
||||
|
||||
private _initCanvas(): void {
|
||||
|
||||
@@ -3,13 +3,14 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { IRenderDimensions } from 'browser/renderer/Types';
|
||||
import { BaseRenderLayer } from '../browser/renderer/BaseRenderLayer';
|
||||
import { ITerminal } from '../Types';
|
||||
import { IRenderDimensions, IRequestRefreshRowsEvent } from 'browser/renderer/Types';
|
||||
import { BaseRenderLayer } from 'browser/renderer/BaseRenderLayer';
|
||||
import { ICellData } from 'common/Types';
|
||||
import { CellData } from 'common/buffer/CellData';
|
||||
import { IColorSet } from 'browser/Types';
|
||||
import { IBufferService, IOptionsService } from 'common/services/Services';
|
||||
import { IBufferService, IOptionsService, ICoreService } from 'common/services/Services';
|
||||
import { IEventEmitter } from 'common/EventEmitter';
|
||||
import { ICoreBrowserService } from 'browser/services/Services';
|
||||
|
||||
interface ICursorState {
|
||||
x: number;
|
||||
@@ -27,25 +28,27 @@ const BLINK_INTERVAL = 600;
|
||||
export class CursorRenderLayer extends BaseRenderLayer {
|
||||
private _state: ICursorState;
|
||||
private _cursorRenderers: {[key: string]: (x: number, y: number, cell: ICellData) => void};
|
||||
private _cursorBlinkStateManager: CursorBlinkStateManager;
|
||||
private _cursorBlinkStateManager: CursorBlinkStateManager | undefined;
|
||||
private _cell: ICellData = new CellData();
|
||||
|
||||
constructor(
|
||||
container: HTMLElement,
|
||||
zIndex: number,
|
||||
colors: IColorSet,
|
||||
private _terminal: ITerminal,
|
||||
rendererId: number,
|
||||
private _onRequestRefreshRowsEvent: IEventEmitter<IRequestRefreshRowsEvent>,
|
||||
readonly bufferService: IBufferService,
|
||||
readonly optionsService: IOptionsService
|
||||
readonly optionsService: IOptionsService,
|
||||
private readonly _coreService: ICoreService,
|
||||
private readonly _coreBrowserService: ICoreBrowserService
|
||||
) {
|
||||
super(container, 'cursor', zIndex, true, colors, rendererId, bufferService, optionsService);
|
||||
this._state = {
|
||||
x: null,
|
||||
y: null,
|
||||
isFocused: null,
|
||||
style: null,
|
||||
width: null
|
||||
x: 0,
|
||||
y: 0,
|
||||
isFocused: false,
|
||||
style: '',
|
||||
width: 0
|
||||
};
|
||||
this._cursorRenderers = {
|
||||
'bar': this._renderBarCursor.bind(this),
|
||||
@@ -59,11 +62,11 @@ export class CursorRenderLayer extends BaseRenderLayer {
|
||||
super.resize(dim);
|
||||
// Resizing the canvas discards the contents of the canvas so clear state
|
||||
this._state = {
|
||||
x: null,
|
||||
y: null,
|
||||
isFocused: null,
|
||||
style: null,
|
||||
width: null
|
||||
x: 0,
|
||||
y: 0,
|
||||
isFocused: false,
|
||||
style: '',
|
||||
width: 0
|
||||
};
|
||||
}
|
||||
|
||||
@@ -71,7 +74,7 @@ export class CursorRenderLayer extends BaseRenderLayer {
|
||||
this._clearCursor();
|
||||
if (this._cursorBlinkStateManager) {
|
||||
this._cursorBlinkStateManager.dispose();
|
||||
this._cursorBlinkStateManager = null;
|
||||
this._cursorBlinkStateManager = undefined;
|
||||
this.onOptionsChanged();
|
||||
}
|
||||
}
|
||||
@@ -80,33 +83,31 @@ export class CursorRenderLayer extends BaseRenderLayer {
|
||||
if (this._cursorBlinkStateManager) {
|
||||
this._cursorBlinkStateManager.pause();
|
||||
}
|
||||
this._terminal.refresh(this._bufferService.buffer.y, this._bufferService.buffer.y);
|
||||
this._onRequestRefreshRowsEvent.fire({ start: this._bufferService.buffer.y, end: this._bufferService.buffer.y });
|
||||
}
|
||||
|
||||
public onFocus(): void {
|
||||
if (this._cursorBlinkStateManager) {
|
||||
this._cursorBlinkStateManager.resume();
|
||||
} else {
|
||||
this._terminal.refresh(this._bufferService.buffer.y, this._bufferService.buffer.y);
|
||||
this._onRequestRefreshRowsEvent.fire({ start: this._bufferService.buffer.y, end: this._bufferService.buffer.y });
|
||||
}
|
||||
}
|
||||
|
||||
public onOptionsChanged(): void {
|
||||
if (this._optionsService.options.cursorBlink) {
|
||||
if (!this._cursorBlinkStateManager) {
|
||||
this._cursorBlinkStateManager = new CursorBlinkStateManager(this._terminal.isFocused, () => {
|
||||
this._cursorBlinkStateManager = new CursorBlinkStateManager(this._coreBrowserService.isFocused, () => {
|
||||
this._render(true);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (this._cursorBlinkStateManager) {
|
||||
this._cursorBlinkStateManager.dispose();
|
||||
this._cursorBlinkStateManager = null;
|
||||
}
|
||||
this._cursorBlinkStateManager?.dispose();
|
||||
this._cursorBlinkStateManager = undefined;
|
||||
}
|
||||
// Request a refresh from the terminal as management of rendering is being
|
||||
// moved back to the terminal
|
||||
this._terminal.refresh(this._bufferService.buffer.y, this._bufferService.buffer.y);
|
||||
this._onRequestRefreshRowsEvent.fire({ start: this._bufferService.buffer.y, end: this._bufferService.buffer.y });
|
||||
}
|
||||
|
||||
public onCursorMove(): void {
|
||||
@@ -125,7 +126,7 @@ export class CursorRenderLayer extends BaseRenderLayer {
|
||||
|
||||
private _render(triggeredByAnimationFrame: boolean): void {
|
||||
// Don't draw the cursor if it's hidden
|
||||
if (!this._terminal.cursorState || this._terminal.cursorHidden) {
|
||||
if (!this._coreService.isCursorInitialized || this._coreService.isCursorHidden) {
|
||||
this._clearCursor();
|
||||
return;
|
||||
}
|
||||
@@ -139,12 +140,12 @@ export class CursorRenderLayer extends BaseRenderLayer {
|
||||
return;
|
||||
}
|
||||
|
||||
this._bufferService.buffer.lines.get(cursorY).loadCell(this._bufferService.buffer.x, this._cell);
|
||||
this._bufferService.buffer.lines.get(cursorY)!.loadCell(this._bufferService.buffer.x, this._cell);
|
||||
if (this._cell.content === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._terminal.isFocused) {
|
||||
if (!this._coreBrowserService.isFocused) {
|
||||
this._clearCursor();
|
||||
this._ctx.save();
|
||||
this._ctx.fillStyle = this._colors.cursor.css;
|
||||
@@ -173,7 +174,7 @@ export class CursorRenderLayer extends BaseRenderLayer {
|
||||
// The cursor is already in the correct spot, don't redraw
|
||||
if (this._state.x === this._bufferService.buffer.x &&
|
||||
this._state.y === viewportRelativeCursorY &&
|
||||
this._state.isFocused === this._terminal.isFocused &&
|
||||
this._state.isFocused === this._coreBrowserService.isFocused &&
|
||||
this._state.style === this._optionsService.options.cursorStyle &&
|
||||
this._state.width === this._cell.getWidth()) {
|
||||
return;
|
||||
@@ -196,11 +197,11 @@ export class CursorRenderLayer extends BaseRenderLayer {
|
||||
if (this._state) {
|
||||
this._clearCells(this._state.x, this._state.y, this._state.width, 1);
|
||||
this._state = {
|
||||
x: null,
|
||||
y: null,
|
||||
isFocused: null,
|
||||
style: null,
|
||||
width: null
|
||||
x: 0,
|
||||
y: 0,
|
||||
isFocused: false,
|
||||
style: '',
|
||||
width: 0
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -239,16 +240,16 @@ export class CursorRenderLayer extends BaseRenderLayer {
|
||||
class CursorBlinkStateManager {
|
||||
public isCursorVisible: boolean;
|
||||
|
||||
private _animationFrame: number;
|
||||
private _blinkStartTimeout: number;
|
||||
private _blinkInterval: number;
|
||||
private _animationFrame: number | undefined;
|
||||
private _blinkStartTimeout: number | undefined;
|
||||
private _blinkInterval: number | undefined;
|
||||
|
||||
/**
|
||||
* The time at which the animation frame was restarted, this is used on the
|
||||
* next render to restart the timers so they don't need to restart the timers
|
||||
* multiple times over a short period.
|
||||
*/
|
||||
private _animationTimeRestarted: number;
|
||||
private _animationTimeRestarted: number | undefined;
|
||||
|
||||
constructor(
|
||||
isFocused: boolean,
|
||||
@@ -265,15 +266,15 @@ class CursorBlinkStateManager {
|
||||
public dispose(): void {
|
||||
if (this._blinkInterval) {
|
||||
window.clearInterval(this._blinkInterval);
|
||||
this._blinkInterval = null;
|
||||
this._blinkInterval = undefined;
|
||||
}
|
||||
if (this._blinkStartTimeout) {
|
||||
window.clearTimeout(this._blinkStartTimeout);
|
||||
this._blinkStartTimeout = null;
|
||||
this._blinkStartTimeout = undefined;
|
||||
}
|
||||
if (this._animationFrame) {
|
||||
window.cancelAnimationFrame(this._animationFrame);
|
||||
this._animationFrame = null;
|
||||
this._animationFrame = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,7 +289,7 @@ class CursorBlinkStateManager {
|
||||
if (!this._animationFrame) {
|
||||
this._animationFrame = window.requestAnimationFrame(() => {
|
||||
this._renderCallback();
|
||||
this._animationFrame = null;
|
||||
this._animationFrame = undefined;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -308,7 +309,7 @@ class CursorBlinkStateManager {
|
||||
// started
|
||||
if (this._animationTimeRestarted) {
|
||||
const time = BLINK_INTERVAL - (Date.now() - this._animationTimeRestarted);
|
||||
this._animationTimeRestarted = null;
|
||||
this._animationTimeRestarted = undefined;
|
||||
if (time > 0) {
|
||||
this._restartInterval(time);
|
||||
return;
|
||||
@@ -319,7 +320,7 @@ class CursorBlinkStateManager {
|
||||
this.isCursorVisible = false;
|
||||
this._animationFrame = window.requestAnimationFrame(() => {
|
||||
this._renderCallback();
|
||||
this._animationFrame = null;
|
||||
this._animationFrame = undefined;
|
||||
});
|
||||
|
||||
// Setup the blink interval
|
||||
@@ -329,7 +330,7 @@ class CursorBlinkStateManager {
|
||||
// calc time diff
|
||||
// Make restart interval do a setTimeout initially?
|
||||
const time = BLINK_INTERVAL - (Date.now() - this._animationTimeRestarted);
|
||||
this._animationTimeRestarted = null;
|
||||
this._animationTimeRestarted = undefined;
|
||||
this._restartInterval(time);
|
||||
return;
|
||||
}
|
||||
@@ -338,7 +339,7 @@ class CursorBlinkStateManager {
|
||||
this.isCursorVisible = !this.isCursorVisible;
|
||||
this._animationFrame = window.requestAnimationFrame(() => {
|
||||
this._renderCallback();
|
||||
this._animationFrame = null;
|
||||
this._animationFrame = undefined;
|
||||
});
|
||||
}, BLINK_INTERVAL);
|
||||
}, timeToStart);
|
||||
@@ -348,20 +349,20 @@ class CursorBlinkStateManager {
|
||||
this.isCursorVisible = true;
|
||||
if (this._blinkInterval) {
|
||||
window.clearInterval(this._blinkInterval);
|
||||
this._blinkInterval = null;
|
||||
this._blinkInterval = undefined;
|
||||
}
|
||||
if (this._blinkStartTimeout) {
|
||||
window.clearTimeout(this._blinkStartTimeout);
|
||||
this._blinkStartTimeout = null;
|
||||
this._blinkStartTimeout = undefined;
|
||||
}
|
||||
if (this._animationFrame) {
|
||||
window.cancelAnimationFrame(this._animationFrame);
|
||||
this._animationFrame = null;
|
||||
this._animationFrame = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
public resume(): void {
|
||||
this._animationTimeRestarted = null;
|
||||
this._animationTimeRestarted = undefined;
|
||||
this._restartInterval();
|
||||
this.restartBlinkAnimation();
|
||||
}
|
||||
@@ -3,18 +3,18 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { TextRenderLayer } from '../browser/renderer/TextRenderLayer';
|
||||
import { SelectionRenderLayer } from '../browser/renderer/SelectionRenderLayer';
|
||||
import { CursorRenderLayer } from './CursorRenderLayer';
|
||||
import { IRenderLayer, IRenderer, IRenderDimensions, CharacterJoinerHandler, ICharacterJoinerRegistry } from 'browser/renderer/Types';
|
||||
import { ITerminal } from '../Types';
|
||||
import { LinkRenderLayer } from '../browser/renderer/LinkRenderLayer';
|
||||
import { TextRenderLayer } from 'browser/renderer/TextRenderLayer';
|
||||
import { SelectionRenderLayer } from 'browser/renderer/SelectionRenderLayer';
|
||||
import { CursorRenderLayer } from 'browser/renderer/CursorRenderLayer';
|
||||
import { IRenderLayer, IRenderer, IRenderDimensions, CharacterJoinerHandler, ICharacterJoinerRegistry, IRequestRefreshRowsEvent } from 'browser/renderer/Types';
|
||||
import { LinkRenderLayer } from 'browser/renderer/LinkRenderLayer';
|
||||
import { CharacterJoinerRegistry } from 'browser/renderer/CharacterJoinerRegistry';
|
||||
import { Disposable } from 'common/Lifecycle';
|
||||
import { IColorSet } from 'browser/Types';
|
||||
import { ICharSizeService } from 'browser/services/Services';
|
||||
import { IBufferService, IOptionsService } from 'common/services/Services';
|
||||
import { IColorSet, ILinkifier } from 'browser/Types';
|
||||
import { ICharSizeService, ICoreBrowserService } from 'browser/services/Services';
|
||||
import { IBufferService, IOptionsService, ICoreService } from 'common/services/Services';
|
||||
import { removeTerminalFromCache } from 'browser/renderer/atlas/CharAtlasCache';
|
||||
import { EventEmitter, IEvent } from 'common/EventEmitter';
|
||||
|
||||
let nextRendererId = 1;
|
||||
|
||||
@@ -27,36 +27,42 @@ export class Renderer extends Disposable implements IRenderer {
|
||||
|
||||
public dimensions: IRenderDimensions;
|
||||
|
||||
private _onRequestRefreshRows = new EventEmitter<IRequestRefreshRowsEvent>();
|
||||
public get onRequestRefreshRows(): IEvent<IRequestRefreshRowsEvent> { return this._onRequestRefreshRows.event; }
|
||||
|
||||
constructor(
|
||||
private _colors: IColorSet,
|
||||
private readonly _terminal: ITerminal,
|
||||
readonly bufferService: IBufferService,
|
||||
private readonly _charSizeService: ICharSizeService,
|
||||
readonly optionsService: IOptionsService
|
||||
private readonly _screenElement: HTMLElement,
|
||||
private readonly _linkifier: ILinkifier,
|
||||
@IBufferService private readonly _bufferService: IBufferService,
|
||||
@ICharSizeService private readonly _charSizeService: ICharSizeService,
|
||||
@IOptionsService private readonly _optionsService: IOptionsService,
|
||||
@ICoreService readonly coreService: ICoreService,
|
||||
@ICoreBrowserService readonly coreBrowserService: ICoreBrowserService
|
||||
) {
|
||||
super();
|
||||
const allowTransparency = this._terminal.options.allowTransparency;
|
||||
this._characterJoinerRegistry = new CharacterJoinerRegistry(bufferService);
|
||||
const allowTransparency = this._optionsService.options.allowTransparency;
|
||||
this._characterJoinerRegistry = new CharacterJoinerRegistry(this._bufferService);
|
||||
|
||||
this._renderLayers = [
|
||||
new TextRenderLayer(this._terminal.screenElement, 0, this._colors, this._characterJoinerRegistry, allowTransparency, this._id, bufferService, optionsService),
|
||||
new SelectionRenderLayer(this._terminal.screenElement, 1, this._colors, this._id, bufferService, optionsService),
|
||||
new LinkRenderLayer(this._terminal.screenElement, 2, this._colors, this._id, this._terminal.linkifier, bufferService, optionsService),
|
||||
new CursorRenderLayer(this._terminal.screenElement, 3, this._colors, this._terminal, this._id, bufferService, optionsService)
|
||||
new TextRenderLayer(this._screenElement, 0, this._colors, this._characterJoinerRegistry, allowTransparency, this._id, this._bufferService, _optionsService),
|
||||
new SelectionRenderLayer(this._screenElement, 1, this._colors, this._id, this._bufferService, _optionsService),
|
||||
new LinkRenderLayer(this._screenElement, 2, this._colors, this._id, this._linkifier, this._bufferService, _optionsService),
|
||||
new CursorRenderLayer(this._screenElement, 3, this._colors, this._id, this._onRequestRefreshRows, this._bufferService, _optionsService, coreService, coreBrowserService)
|
||||
];
|
||||
this.dimensions = {
|
||||
scaledCharWidth: null,
|
||||
scaledCharHeight: null,
|
||||
scaledCellWidth: null,
|
||||
scaledCellHeight: null,
|
||||
scaledCharLeft: null,
|
||||
scaledCharTop: null,
|
||||
scaledCanvasWidth: null,
|
||||
scaledCanvasHeight: null,
|
||||
canvasWidth: null,
|
||||
canvasHeight: null,
|
||||
actualCellWidth: null,
|
||||
actualCellHeight: null
|
||||
scaledCharWidth: 0,
|
||||
scaledCharHeight: 0,
|
||||
scaledCellWidth: 0,
|
||||
scaledCellHeight: 0,
|
||||
scaledCharLeft: 0,
|
||||
scaledCharTop: 0,
|
||||
scaledCanvasWidth: 0,
|
||||
scaledCanvasHeight: 0,
|
||||
canvasWidth: 0,
|
||||
canvasHeight: 0,
|
||||
actualCellWidth: 0,
|
||||
actualCellHeight: 0
|
||||
};
|
||||
this._devicePixelRatio = window.devicePixelRatio;
|
||||
this._updateDimensions();
|
||||
@@ -74,7 +80,7 @@ export class Renderer extends Disposable implements IRenderer {
|
||||
// and the terminal needs to refreshed
|
||||
if (this._devicePixelRatio !== window.devicePixelRatio) {
|
||||
this._devicePixelRatio = window.devicePixelRatio;
|
||||
this.onResize(this._terminal.cols, this._terminal.rows);
|
||||
this.onResize(this._bufferService.cols, this._bufferService.rows);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,12 +102,12 @@ export class Renderer extends Disposable implements IRenderer {
|
||||
this._renderLayers.forEach(l => l.resize(this.dimensions));
|
||||
|
||||
// Resize the screen
|
||||
this._terminal.screenElement.style.width = `${this.dimensions.canvasWidth}px`;
|
||||
this._terminal.screenElement.style.height = `${this.dimensions.canvasHeight}px`;
|
||||
this._screenElement.style.width = `${this.dimensions.canvasWidth}px`;
|
||||
this._screenElement.style.height = `${this.dimensions.canvasHeight}px`;
|
||||
}
|
||||
|
||||
public onCharSizeChanged(): void {
|
||||
this.onResize(this._terminal.cols, this._terminal.rows);
|
||||
this.onResize(this._bufferService.cols, this._bufferService.rows);
|
||||
}
|
||||
|
||||
public onBlur(): void {
|
||||
@@ -163,23 +169,23 @@ export class Renderer extends Disposable implements IRenderer {
|
||||
// will be floored because since lineHeight can never be lower then 1, there
|
||||
// is a guarentee that the scaled line height will always be larger than
|
||||
// scaled char height.
|
||||
this.dimensions.scaledCellHeight = Math.floor(this.dimensions.scaledCharHeight * this._terminal.options.lineHeight);
|
||||
this.dimensions.scaledCellHeight = Math.floor(this.dimensions.scaledCharHeight * this._optionsService.options.lineHeight);
|
||||
|
||||
// Calculate the y coordinate within a cell that text should draw from in
|
||||
// order to draw in the center of a cell.
|
||||
this.dimensions.scaledCharTop = this._terminal.options.lineHeight === 1 ? 0 : Math.round((this.dimensions.scaledCellHeight - this.dimensions.scaledCharHeight) / 2);
|
||||
this.dimensions.scaledCharTop = this._optionsService.options.lineHeight === 1 ? 0 : Math.round((this.dimensions.scaledCellHeight - this.dimensions.scaledCharHeight) / 2);
|
||||
|
||||
// Calculate the scaled cell width, taking the letterSpacing into account.
|
||||
this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth + Math.round(this._terminal.options.letterSpacing);
|
||||
this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth + Math.round(this._optionsService.options.letterSpacing);
|
||||
|
||||
// Calculate the x coordinate with a cell that text should draw from in
|
||||
// order to draw in the center of a cell.
|
||||
this.dimensions.scaledCharLeft = Math.floor(this._terminal.options.letterSpacing / 2);
|
||||
this.dimensions.scaledCharLeft = Math.floor(this._optionsService.options.letterSpacing / 2);
|
||||
|
||||
// Recalculate the canvas dimensions; scaled* define the actual number of
|
||||
// pixel in the canvas
|
||||
this.dimensions.scaledCanvasHeight = this._terminal.rows * this.dimensions.scaledCellHeight;
|
||||
this.dimensions.scaledCanvasWidth = this._terminal.cols * this.dimensions.scaledCellWidth;
|
||||
this.dimensions.scaledCanvasHeight = this._bufferService.rows * this.dimensions.scaledCellHeight;
|
||||
this.dimensions.scaledCanvasWidth = this._bufferService.cols * this.dimensions.scaledCellWidth;
|
||||
|
||||
// The the size of the canvas on the page. It's very important that this
|
||||
// rounds to nearest integer and not ceils as browsers often set
|
||||
@@ -194,8 +200,8 @@ export class Renderer extends Disposable implements IRenderer {
|
||||
// account window.devicePixelRatio. ICharSizeService.width/height by itself
|
||||
// is insufficient when the page is not at 100% zoom level as it's measured
|
||||
// in CSS pixels, but the actual char size on the canvas can differ.
|
||||
this.dimensions.actualCellHeight = this.dimensions.canvasHeight / this._terminal.rows;
|
||||
this.dimensions.actualCellWidth = this.dimensions.canvasWidth / this._terminal.cols;
|
||||
this.dimensions.actualCellHeight = this.dimensions.canvasHeight / this._bufferService.rows;
|
||||
this.dimensions.actualCellWidth = this.dimensions.canvasWidth / this._bufferService.cols;
|
||||
}
|
||||
|
||||
public registerCharacterJoiner(handler: CharacterJoinerHandler): number {
|
||||
Vendored
+8
@@ -5,6 +5,7 @@
|
||||
|
||||
import { IDisposable } from 'common/Types';
|
||||
import { IColorSet } from 'browser/Types';
|
||||
import { IEvent } from 'common/EventEmitter';
|
||||
|
||||
export type CharacterJoinerHandler = (text: string) => [number, number][];
|
||||
|
||||
@@ -23,6 +24,11 @@ export interface IRenderDimensions {
|
||||
actualCellHeight: number;
|
||||
}
|
||||
|
||||
export interface IRequestRefreshRowsEvent {
|
||||
start: number;
|
||||
end: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Note that IRenderer implementations should emit the refresh event after
|
||||
* rendering rows to the screen.
|
||||
@@ -30,6 +36,8 @@ export interface IRenderDimensions {
|
||||
export interface IRenderer extends IDisposable {
|
||||
readonly dimensions: IRenderDimensions;
|
||||
|
||||
readonly onRequestRefreshRows: IEvent<IRequestRefreshRowsEvent>;
|
||||
|
||||
dispose(): void;
|
||||
setColors(colors: IColorSet): void;
|
||||
onDevicePixelRatioChange(): void;
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { IRenderer, IRenderDimensions, CharacterJoinerHandler } from 'browser/renderer/Types';
|
||||
import { ITerminal } from '../../Types';
|
||||
import { IRenderer, IRenderDimensions, CharacterJoinerHandler, IRequestRefreshRowsEvent } from 'browser/renderer/Types';
|
||||
import { BOLD_CLASS, ITALIC_CLASS, CURSOR_CLASS, CURSOR_STYLE_BLOCK_CLASS, CURSOR_BLINK_CLASS, CURSOR_STYLE_BAR_CLASS, CURSOR_STYLE_UNDERLINE_CLASS, DomRendererRowFactory } from 'browser/renderer/dom/DomRendererRowFactory';
|
||||
import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants';
|
||||
import { Disposable } from 'common/Lifecycle';
|
||||
import { IColorSet, ILinkifierEvent } from 'browser/Types';
|
||||
import { IColorSet, ILinkifierEvent, ILinkifier } from 'browser/Types';
|
||||
import { ICharSizeService } from 'browser/services/Services';
|
||||
import { IOptionsService } from 'common/services/Services';
|
||||
import { IOptionsService, IBufferService } from 'common/services/Services';
|
||||
import { EventEmitter, IEvent } from 'common/EventEmitter';
|
||||
|
||||
const TERMINAL_CLASS_PREFIX = 'xterm-dom-renderer-owner-';
|
||||
const ROW_CONTAINER_CLASS = 'xterm-rows';
|
||||
@@ -30,19 +30,26 @@ export class DomRenderer extends Disposable implements IRenderer {
|
||||
private _rowFactory: DomRendererRowFactory;
|
||||
private _terminalClass: number = nextTerminalId++;
|
||||
|
||||
private _themeStyleElement: HTMLStyleElement;
|
||||
private _dimensionsStyleElement: HTMLStyleElement;
|
||||
private _themeStyleElement!: HTMLStyleElement;
|
||||
private _dimensionsStyleElement!: HTMLStyleElement;
|
||||
private _rowContainer: HTMLElement;
|
||||
private _rowElements: HTMLElement[] = [];
|
||||
private _selectionContainer: HTMLElement;
|
||||
|
||||
public dimensions: IRenderDimensions;
|
||||
|
||||
private _onRequestRefreshRows = new EventEmitter<IRequestRefreshRowsEvent>();
|
||||
public get onRequestRefreshRows(): IEvent<IRequestRefreshRowsEvent> { return this._onRequestRefreshRows.event; }
|
||||
|
||||
constructor(
|
||||
private _terminal: ITerminal,
|
||||
private _colors: IColorSet,
|
||||
private _charSizeService: ICharSizeService,
|
||||
private _optionsService: IOptionsService
|
||||
private readonly _element: HTMLElement,
|
||||
private readonly _screenElement: HTMLElement,
|
||||
private readonly _viewportElement: HTMLElement,
|
||||
private readonly _linkifier: ILinkifier,
|
||||
@ICharSizeService private readonly _charSizeService: ICharSizeService,
|
||||
@IOptionsService private readonly _optionsService: IOptionsService,
|
||||
@IBufferService private readonly _bufferService: IBufferService
|
||||
) {
|
||||
super();
|
||||
|
||||
@@ -50,60 +57,60 @@ export class DomRenderer extends Disposable implements IRenderer {
|
||||
this._rowContainer.classList.add(ROW_CONTAINER_CLASS);
|
||||
this._rowContainer.style.lineHeight = 'normal';
|
||||
this._rowContainer.setAttribute('aria-hidden', 'true');
|
||||
this._refreshRowElements(this._terminal.cols, this._terminal.rows);
|
||||
this._refreshRowElements(this._bufferService.cols, this._bufferService.rows);
|
||||
this._selectionContainer = document.createElement('div');
|
||||
this._selectionContainer.classList.add(SELECTION_CLASS);
|
||||
this._selectionContainer.setAttribute('aria-hidden', 'true');
|
||||
|
||||
this.dimensions = {
|
||||
scaledCharWidth: null,
|
||||
scaledCharHeight: null,
|
||||
scaledCellWidth: null,
|
||||
scaledCellHeight: null,
|
||||
scaledCharLeft: null,
|
||||
scaledCharTop: null,
|
||||
scaledCanvasWidth: null,
|
||||
scaledCanvasHeight: null,
|
||||
canvasWidth: null,
|
||||
canvasHeight: null,
|
||||
actualCellWidth: null,
|
||||
actualCellHeight: null
|
||||
scaledCharWidth: 0,
|
||||
scaledCharHeight: 0,
|
||||
scaledCellWidth: 0,
|
||||
scaledCellHeight: 0,
|
||||
scaledCharLeft: 0,
|
||||
scaledCharTop: 0,
|
||||
scaledCanvasWidth: 0,
|
||||
scaledCanvasHeight: 0,
|
||||
canvasWidth: 0,
|
||||
canvasHeight: 0,
|
||||
actualCellWidth: 0,
|
||||
actualCellHeight: 0
|
||||
};
|
||||
this._updateDimensions();
|
||||
this._injectCss();
|
||||
|
||||
this._rowFactory = new DomRendererRowFactory(document, this._optionsService);
|
||||
|
||||
this._terminal.element.classList.add(TERMINAL_CLASS_PREFIX + this._terminalClass);
|
||||
this._terminal.screenElement.appendChild(this._rowContainer);
|
||||
this._terminal.screenElement.appendChild(this._selectionContainer);
|
||||
this._element.classList.add(TERMINAL_CLASS_PREFIX + this._terminalClass);
|
||||
this._screenElement.appendChild(this._rowContainer);
|
||||
this._screenElement.appendChild(this._selectionContainer);
|
||||
|
||||
this._terminal.linkifier.onLinkHover(e => this._onLinkHover(e));
|
||||
this._terminal.linkifier.onLinkLeave(e => this._onLinkLeave(e));
|
||||
this._linkifier.onLinkHover(e => this._onLinkHover(e));
|
||||
this._linkifier.onLinkLeave(e => this._onLinkLeave(e));
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._terminal.element.classList.remove(TERMINAL_CLASS_PREFIX + this._terminalClass);
|
||||
this._terminal.screenElement.removeChild(this._rowContainer);
|
||||
this._terminal.screenElement.removeChild(this._selectionContainer);
|
||||
this._terminal.screenElement.removeChild(this._themeStyleElement);
|
||||
this._terminal.screenElement.removeChild(this._dimensionsStyleElement);
|
||||
this._element.classList.remove(TERMINAL_CLASS_PREFIX + this._terminalClass);
|
||||
this._screenElement.removeChild(this._rowContainer);
|
||||
this._screenElement.removeChild(this._selectionContainer);
|
||||
this._screenElement.removeChild(this._themeStyleElement);
|
||||
this._screenElement.removeChild(this._dimensionsStyleElement);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
private _updateDimensions(): void {
|
||||
this.dimensions.scaledCharWidth = this._charSizeService.width * window.devicePixelRatio;
|
||||
this.dimensions.scaledCharHeight = Math.ceil(this._charSizeService.height * window.devicePixelRatio);
|
||||
this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth + Math.round(this._terminal.options.letterSpacing);
|
||||
this.dimensions.scaledCellHeight = Math.floor(this.dimensions.scaledCharHeight * this._terminal.options.lineHeight);
|
||||
this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth + Math.round(this._optionsService.options.letterSpacing);
|
||||
this.dimensions.scaledCellHeight = Math.floor(this.dimensions.scaledCharHeight * this._optionsService.options.lineHeight);
|
||||
this.dimensions.scaledCharLeft = 0;
|
||||
this.dimensions.scaledCharTop = 0;
|
||||
this.dimensions.scaledCanvasWidth = this.dimensions.scaledCellWidth * this._terminal.cols;
|
||||
this.dimensions.scaledCanvasHeight = this.dimensions.scaledCellHeight * this._terminal.rows;
|
||||
this.dimensions.scaledCanvasWidth = this.dimensions.scaledCellWidth * this._bufferService.cols;
|
||||
this.dimensions.scaledCanvasHeight = this.dimensions.scaledCellHeight * this._bufferService.rows;
|
||||
this.dimensions.canvasWidth = Math.round(this.dimensions.scaledCanvasWidth / window.devicePixelRatio);
|
||||
this.dimensions.canvasHeight = Math.round(this.dimensions.scaledCanvasHeight / window.devicePixelRatio);
|
||||
this.dimensions.actualCellWidth = this.dimensions.canvasWidth / this._terminal.cols;
|
||||
this.dimensions.actualCellHeight = this.dimensions.canvasHeight / this._terminal.rows;
|
||||
this.dimensions.actualCellWidth = this.dimensions.canvasWidth / this._bufferService.cols;
|
||||
this.dimensions.actualCellHeight = this.dimensions.canvasHeight / this._bufferService.rows;
|
||||
|
||||
this._rowElements.forEach(element => {
|
||||
element.style.width = `${this.dimensions.canvasWidth}px`;
|
||||
@@ -115,7 +122,7 @@ export class DomRenderer extends Disposable implements IRenderer {
|
||||
|
||||
if (!this._dimensionsStyleElement) {
|
||||
this._dimensionsStyleElement = document.createElement('style');
|
||||
this._terminal.screenElement.appendChild(this._dimensionsStyleElement);
|
||||
this._screenElement.appendChild(this._dimensionsStyleElement);
|
||||
}
|
||||
|
||||
const styles =
|
||||
@@ -128,9 +135,9 @@ export class DomRenderer extends Disposable implements IRenderer {
|
||||
|
||||
this._dimensionsStyleElement.innerHTML = styles;
|
||||
|
||||
this._selectionContainer.style.height = (<any>this._terminal)._viewportElement.style.height;
|
||||
this._terminal.screenElement.style.width = `${this.dimensions.canvasWidth}px`;
|
||||
this._terminal.screenElement.style.height = `${this.dimensions.canvasHeight}px`;
|
||||
this._selectionContainer.style.height = this._viewportElement.style.height;
|
||||
this._screenElement.style.width = `${this.dimensions.canvasWidth}px`;
|
||||
this._screenElement.style.height = `${this.dimensions.canvasHeight}px`;
|
||||
}
|
||||
|
||||
public setColors(colors: IColorSet): void {
|
||||
@@ -141,7 +148,7 @@ export class DomRenderer extends Disposable implements IRenderer {
|
||||
private _injectCss(): void {
|
||||
if (!this._themeStyleElement) {
|
||||
this._themeStyleElement = document.createElement('style');
|
||||
this._terminal.screenElement.appendChild(this._themeStyleElement);
|
||||
this._screenElement.appendChild(this._themeStyleElement);
|
||||
}
|
||||
|
||||
// Base CSS
|
||||
@@ -149,16 +156,16 @@ export class DomRenderer extends Disposable implements IRenderer {
|
||||
`${this._terminalSelector} .${ROW_CONTAINER_CLASS} {` +
|
||||
` color: ${this._colors.foreground.css};` +
|
||||
` background-color: ${this._colors.background.css};` +
|
||||
` font-family: ${this._terminal.options.fontFamily};` +
|
||||
` font-size: ${this._terminal.options.fontSize}px;` +
|
||||
` font-family: ${this._optionsService.options.fontFamily};` +
|
||||
` font-size: ${this._optionsService.options.fontSize}px;` +
|
||||
`}`;
|
||||
// Text styles
|
||||
styles +=
|
||||
`${this._terminalSelector} span:not(.${BOLD_CLASS}) {` +
|
||||
` font-weight: ${this._terminal.options.fontWeight};` +
|
||||
` font-weight: ${this._optionsService.options.fontWeight};` +
|
||||
`}` +
|
||||
`${this._terminalSelector} span.${BOLD_CLASS} {` +
|
||||
` font-weight: ${this._terminal.options.fontWeightBold};` +
|
||||
` font-weight: ${this._optionsService.options.fontWeightBold};` +
|
||||
`}` +
|
||||
`${this._terminalSelector} span.${ITALIC_CLASS} {` +
|
||||
` font-style: italic;` +
|
||||
@@ -242,7 +249,7 @@ export class DomRenderer extends Disposable implements IRenderer {
|
||||
}
|
||||
// Remove excess elements
|
||||
while (this._rowElements.length > rows) {
|
||||
this._rowContainer.removeChild(this._rowElements.pop());
|
||||
this._rowContainer.removeChild(this._rowElements.pop()!);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -275,13 +282,13 @@ export class DomRenderer extends Disposable implements IRenderer {
|
||||
}
|
||||
|
||||
// Translate from buffer position to viewport position
|
||||
const viewportStartRow = start[1] - this._terminal.buffer.ydisp;
|
||||
const viewportEndRow = end[1] - this._terminal.buffer.ydisp;
|
||||
const viewportStartRow = start[1] - this._bufferService.buffer.ydisp;
|
||||
const viewportEndRow = end[1] - this._bufferService.buffer.ydisp;
|
||||
const viewportCappedStartRow = Math.max(viewportStartRow, 0);
|
||||
const viewportCappedEndRow = Math.min(viewportEndRow, this._terminal.rows - 1);
|
||||
const viewportCappedEndRow = Math.min(viewportEndRow, this._bufferService.rows - 1);
|
||||
|
||||
// No need to draw the selection
|
||||
if (viewportCappedStartRow >= this._terminal.rows || viewportCappedEndRow < 0) {
|
||||
if (viewportCappedStartRow >= this._bufferService.rows || viewportCappedEndRow < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -295,15 +302,15 @@ export class DomRenderer extends Disposable implements IRenderer {
|
||||
} else {
|
||||
// Draw first row
|
||||
const startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0;
|
||||
const endCol = viewportCappedStartRow === viewportCappedEndRow ? end[0] : this._terminal.cols;
|
||||
const endCol = viewportCappedStartRow === viewportCappedEndRow ? end[0] : this._bufferService.cols;
|
||||
documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow, startCol, endCol));
|
||||
// Draw middle rows
|
||||
const middleRowsCount = viewportCappedEndRow - viewportCappedStartRow - 1;
|
||||
documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow + 1, 0, this._terminal.cols, middleRowsCount));
|
||||
documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow + 1, 0, this._bufferService.cols, middleRowsCount));
|
||||
// Draw final row
|
||||
if (viewportCappedStartRow !== viewportCappedEndRow) {
|
||||
// Only draw viewportEndRow if it's not the same as viewporttartRow
|
||||
const endCol = viewportEndRow === viewportCappedEndRow ? end[0] : this._terminal.cols;
|
||||
const endCol = viewportEndRow === viewportCappedEndRow ? end[0] : this._bufferService.cols;
|
||||
documentFragment.appendChild(this._createSelectionElement(viewportCappedEndRow, 0, endCol));
|
||||
}
|
||||
}
|
||||
@@ -333,7 +340,6 @@ export class DomRenderer extends Disposable implements IRenderer {
|
||||
// Force a refresh
|
||||
this._updateDimensions();
|
||||
this._injectCss();
|
||||
this._terminal.refresh(0, this._terminal.rows - 1);
|
||||
}
|
||||
|
||||
public clear(): void {
|
||||
@@ -341,20 +347,18 @@ export class DomRenderer extends Disposable implements IRenderer {
|
||||
}
|
||||
|
||||
public renderRows(start: number, end: number): void {
|
||||
const terminal = this._terminal;
|
||||
|
||||
const cursorAbsoluteY = terminal.buffer.ybase + terminal.buffer.y;
|
||||
const cursorX = this._terminal.buffer.x;
|
||||
const cursorBlink = this._terminal.options.cursorBlink;
|
||||
const cursorAbsoluteY = this._bufferService.buffer.ybase + this._bufferService.buffer.y;
|
||||
const cursorX = this._bufferService.buffer.x;
|
||||
const cursorBlink = this._optionsService.options.cursorBlink;
|
||||
|
||||
for (let y = start; y <= end; y++) {
|
||||
const rowElement = this._rowElements[y];
|
||||
rowElement.innerHTML = '';
|
||||
|
||||
const row = y + terminal.buffer.ydisp;
|
||||
const lineData = terminal.buffer.lines.get(row);
|
||||
const cursorStyle = terminal.options.cursorStyle;
|
||||
rowElement.appendChild(this._rowFactory.createRow(lineData, row === cursorAbsoluteY, cursorStyle, cursorX, cursorBlink, this.dimensions.actualCellWidth, terminal.cols));
|
||||
const row = y + this._bufferService.buffer.ydisp;
|
||||
const lineData = this._bufferService.buffer.lines.get(row);
|
||||
const cursorStyle = this._optionsService.options.cursorStyle;
|
||||
rowElement.appendChild(this._rowFactory.createRow(lineData!, row === cursorAbsoluteY, cursorStyle, cursorX, cursorBlink, this.dimensions.actualCellWidth, this._bufferService.cols));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { ICoreBrowserService } from './Services';
|
||||
|
||||
export class CoreBrowserService implements ICoreBrowserService {
|
||||
serviceBrand: any;
|
||||
|
||||
constructor(
|
||||
private _textarea: HTMLTextAreaElement
|
||||
) {
|
||||
}
|
||||
|
||||
public get isFocused(): boolean {
|
||||
return document.activeElement === this._textarea && document.hasFocus();
|
||||
}
|
||||
}
|
||||
@@ -51,6 +51,9 @@ export class RenderService extends Disposable implements IRenderService {
|
||||
this.register(optionsService.onOptionChange(() => this._renderer.onOptionsChanged()));
|
||||
this.register(charSizeService.onCharSizeChange(() => this.onCharSizeChanged()));
|
||||
|
||||
// No need to register this as renderer is explicitly disposed in RenderService.dispose
|
||||
this._renderer.onRequestRefreshRows(e => this.refreshRows(e.start, e.end));
|
||||
|
||||
// dprchange should handle this case, we need this as well for browsers that don't support the
|
||||
// matchMedia query.
|
||||
this.register(addDisposableDomListener(window, 'resize', () => this.onDevicePixelRatioChange()));
|
||||
@@ -92,6 +95,7 @@ export class RenderService extends Disposable implements IRenderService {
|
||||
|
||||
public changeOptions(): void {
|
||||
this._renderer.onOptionsChanged();
|
||||
this.refreshRows(0, this._rowCount - 1);
|
||||
this._fireOnCanvasResize();
|
||||
}
|
||||
|
||||
@@ -105,12 +109,14 @@ export class RenderService extends Disposable implements IRenderService {
|
||||
|
||||
public dispose(): void {
|
||||
this._renderer.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
public setRenderer(renderer: IRenderer): void {
|
||||
// TODO: RenderService should be the only one to dispose the renderer
|
||||
this._renderer.dispose();
|
||||
this._renderer = renderer;
|
||||
this._renderer.onRequestRefreshRows(e => this.refreshRows(e.start, e.end));
|
||||
this.refreshRows(0, this._rowCount - 1);
|
||||
}
|
||||
|
||||
|
||||
@@ -688,9 +688,7 @@ export class SelectionService implements ISelectionService {
|
||||
// reverseIndex) and delete in a splice is only ever used when the same
|
||||
// number of elements was just added. Given this is could actually be
|
||||
// beneficial to leave the selection as is for these cases.
|
||||
if (this._trimListener) {
|
||||
this._trimListener.dispose();
|
||||
}
|
||||
this._trimListener.dispose();
|
||||
this._trimListener = e.activeBuffer.lines.onTrim(amount => this._onTrim(amount));
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,13 @@ export interface ICharSizeService {
|
||||
measure(): void;
|
||||
}
|
||||
|
||||
export const ICoreBrowserService = createDecorator<ICoreBrowserService>('CoreBrowserService');
|
||||
export interface ICoreBrowserService {
|
||||
serviceBrand: any;
|
||||
|
||||
readonly isFocused: boolean;
|
||||
}
|
||||
|
||||
export const IMouseService = createDecorator<IMouseService>('MouseService');
|
||||
export interface IMouseService {
|
||||
serviceBrand: any;
|
||||
|
||||
@@ -43,6 +43,9 @@ export class MockCoreMouseService implements ICoreMouseService {
|
||||
}
|
||||
|
||||
export class MockCoreService implements ICoreService {
|
||||
isCursorInitialized: boolean = false;
|
||||
isCursorHidden: boolean = false;
|
||||
isFocused: boolean = false;
|
||||
serviceBrand: any;
|
||||
decPrivateModes: IDecPrivateModes = {} as any;
|
||||
onData: IEvent<string> = new EventEmitter<string>().event;
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { ICoreService } from 'common/services/Services';
|
||||
import { CoreService } from 'common/services/CoreService';
|
||||
import { MockBufferService, MockLogService, MockOptionsService } from 'common/TestUtils.test';
|
||||
import { assert } from 'chai';
|
||||
|
||||
describe('CoreService', () => {
|
||||
let coreService: ICoreService;
|
||||
|
||||
beforeEach(() => {
|
||||
coreService = new CoreService(
|
||||
() => {},
|
||||
new MockBufferService(80, 30),
|
||||
new MockLogService(),
|
||||
new MockOptionsService());
|
||||
});
|
||||
|
||||
describe('reset', () => {
|
||||
it('should not affect isCursorInitialized', () => {
|
||||
coreService.isCursorInitialized = true;
|
||||
coreService.reset();
|
||||
assert.equal(coreService.isCursorInitialized, true);
|
||||
coreService.isCursorInitialized = false;
|
||||
coreService.reset();
|
||||
assert.equal(coreService.isCursorInitialized, false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -15,6 +15,8 @@ const DEFAULT_DEC_PRIVATE_MODES: IDecPrivateModes = Object.freeze({
|
||||
export class CoreService implements ICoreService {
|
||||
serviceBrand: any;
|
||||
|
||||
public isCursorInitialized: boolean = false;
|
||||
public isCursorHidden: boolean = false;
|
||||
public decPrivateModes: IDecPrivateModes;
|
||||
|
||||
private _onData = new EventEmitter<string>();
|
||||
|
||||
@@ -58,6 +58,12 @@ export const ICoreService = createDecorator<ICoreService>('CoreService');
|
||||
export interface ICoreService {
|
||||
serviceBrand: any;
|
||||
|
||||
/**
|
||||
* Initially the cursor will not be visible until the first time the terminal
|
||||
* is focused.
|
||||
*/
|
||||
isCursorInitialized: boolean;
|
||||
isCursorHidden: boolean;
|
||||
readonly decPrivateModes: IDecPrivateModes;
|
||||
|
||||
readonly onData: IEvent<string>;
|
||||
|
||||
Reference in New Issue
Block a user