Merge branch 'master' into 2333_sendfocus
This commit is contained in:
@@ -53,6 +53,7 @@ export class AccessibilityManager extends Disposable {
|
||||
) {
|
||||
super();
|
||||
this._accessibilityTreeRoot = document.createElement('div');
|
||||
this._accessibilityTreeRoot.setAttribute('role', 'document');
|
||||
this._accessibilityTreeRoot.classList.add('xterm-accessibility');
|
||||
|
||||
this._rowContainer = document.createElement('div');
|
||||
|
||||
@@ -732,80 +732,78 @@ describe('Terminal', () => {
|
||||
});
|
||||
|
||||
describe('unicode - surrogates', () => {
|
||||
it('2 characters per cell', async function (): Promise<void> {
|
||||
this.timeout(10000); // This is needed because istanbul patches code and slows it down
|
||||
const high = String.fromCharCode(0xD800);
|
||||
const cell = new CellData();
|
||||
for (let i = 0xDC00; i <= 0xDCFF; ++i) {
|
||||
await term.writeP(high + String.fromCharCode(i));
|
||||
const tchar = term.buffer.lines.get(0)!.loadCell(0, cell);
|
||||
assert.equal(tchar.getChars(), high + String.fromCharCode(i));
|
||||
assert.equal(tchar.getChars().length, 2);
|
||||
assert.equal(tchar.getWidth(), 1);
|
||||
assert.equal(term.buffer.lines.get(0)!.loadCell(1, cell).getChars(), '');
|
||||
term.reset();
|
||||
}
|
||||
});
|
||||
it('2 characters at last cell', async () => {
|
||||
const high = String.fromCharCode(0xD800);
|
||||
const cell = new CellData();
|
||||
for (let i = 0xDC00; i <= 0xDCFF; ++i) {
|
||||
term.buffer.x = term.cols - 1;
|
||||
await term.writeP(high + String.fromCharCode(i));
|
||||
assert.equal(term.buffer.lines.get(0)!.loadCell(term.buffer.x - 1, cell).getChars(), high + String.fromCharCode(i));
|
||||
assert.equal(term.buffer.lines.get(0)!.loadCell(term.buffer.x - 1, cell).getChars().length, 2);
|
||||
assert.equal(term.buffer.lines.get(1)!.loadCell(0, cell).getChars(), '');
|
||||
term.reset();
|
||||
}
|
||||
});
|
||||
it('2 characters per cell over line end with autowrap', async function (): Promise<void> {
|
||||
this.timeout(10000);
|
||||
const high = String.fromCharCode(0xD800);
|
||||
const cell = new CellData();
|
||||
for (let i = 0xDC00; i <= 0xDCFF; ++i) {
|
||||
term.buffer.x = term.cols - 1;
|
||||
|
||||
await term.writeP('a' + high + String.fromCharCode(i));
|
||||
assert.equal(term.buffer.lines.get(0)!.loadCell(term.cols - 1, cell).getChars(), 'a');
|
||||
assert.equal(term.buffer.lines.get(1)!.loadCell(0, cell).getChars(), high + String.fromCharCode(i));
|
||||
assert.equal(term.buffer.lines.get(1)!.loadCell(0, cell).getChars().length, 2);
|
||||
assert.equal(term.buffer.lines.get(1)!.loadCell(1, cell).getChars(), '');
|
||||
term.reset();
|
||||
}
|
||||
});
|
||||
it('2 characters per cell over line end without autowrap', async function (): Promise<void> {
|
||||
this.timeout(10000);
|
||||
const high = String.fromCharCode(0xD800);
|
||||
const cell = new CellData();
|
||||
for (let i = 0xDC00; i <= 0xDCFF; ++i) {
|
||||
term.buffer.x = term.cols - 1;
|
||||
await term.writeP('\x1b[?7l'); // Disable wraparound mode
|
||||
const width = wcwidth((0xD800 - 0xD800) * 0x400 + i - 0xDC00 + 0x10000);
|
||||
if (width !== 1) {
|
||||
continue;
|
||||
for (let i = 0xDC00; i <= 0xDCF0; i += 0x10) {
|
||||
const range = `0x${i.toString(16).toUpperCase()}-0x${(i + 0xF).toString(16).toUpperCase()}`;
|
||||
it(`${range}: 2 characters per cell`, async function (): Promise<void> {
|
||||
const high = String.fromCharCode(0xD800);
|
||||
const cell = new CellData();
|
||||
for (let j = i; j <= i + 0xF; j++) {
|
||||
await term.writeP(high + String.fromCharCode(j));
|
||||
const tchar = term.buffer.lines.get(0)!.loadCell(0, cell);
|
||||
assert.equal(tchar.getChars(), high + String.fromCharCode(j));
|
||||
assert.equal(tchar.getChars().length, 2);
|
||||
assert.equal(tchar.getWidth(), 1);
|
||||
assert.equal(term.buffer.lines.get(0)!.loadCell(1, cell).getChars(), '');
|
||||
term.reset();
|
||||
}
|
||||
await term.writeP('a' + high + String.fromCharCode(i));
|
||||
// auto wraparound mode should cut off the rest of the line
|
||||
assert.equal(term.buffer.lines.get(0)!.loadCell(term.cols - 1, cell).getChars(), high + String.fromCharCode(i));
|
||||
assert.equal(term.buffer.lines.get(0)!.loadCell(term.cols - 1, cell).getChars().length, 2);
|
||||
assert.equal(term.buffer.lines.get(1)!.loadCell(1, cell).getChars(), '');
|
||||
term.reset();
|
||||
}
|
||||
});
|
||||
it('splitted surrogates', async function (): Promise<void> {
|
||||
this.timeout(10000);
|
||||
const high = String.fromCharCode(0xD800);
|
||||
const cell = new CellData();
|
||||
for (let i = 0xDC00; i <= 0xDCFF; ++i) {
|
||||
await term.writeP(high + String.fromCharCode(i));
|
||||
const tchar = term.buffer.lines.get(0)!.loadCell(0, cell);
|
||||
assert.equal(tchar.getChars(), high + String.fromCharCode(i));
|
||||
assert.equal(tchar.getChars().length, 2);
|
||||
assert.equal(tchar.getWidth(), 1);
|
||||
assert.equal(term.buffer.lines.get(0)!.loadCell(1, cell).getChars(), '');
|
||||
term.reset();
|
||||
}
|
||||
});
|
||||
});
|
||||
it(`${range}: 2 characters at last cell`, async () => {
|
||||
const high = String.fromCharCode(0xD800);
|
||||
const cell = new CellData();
|
||||
term.buffer.x = term.cols - 1;
|
||||
for (let j = i; j <= i + 0xF; j++) {
|
||||
await term.writeP(high + String.fromCharCode(j));
|
||||
assert.equal(term.buffer.lines.get(0)!.loadCell(term.buffer.x - 1, cell).getChars(), high + String.fromCharCode(j));
|
||||
assert.equal(term.buffer.lines.get(0)!.loadCell(term.buffer.x - 1, cell).getChars().length, 2);
|
||||
assert.equal(term.buffer.lines.get(1)!.loadCell(0, cell).getChars(), '');
|
||||
term.reset();
|
||||
}
|
||||
});
|
||||
it(`${range}: 2 characters per cell over line end with autowrap`, async function (): Promise<void> {
|
||||
const high = String.fromCharCode(0xD800);
|
||||
const cell = new CellData();
|
||||
for (let j = i; j <= i + 0xF; j++) {
|
||||
term.buffer.x = term.cols - 1;
|
||||
await term.writeP('a' + high + String.fromCharCode(j));
|
||||
assert.equal(term.buffer.lines.get(0)!.loadCell(term.cols - 1, cell).getChars(), 'a');
|
||||
assert.equal(term.buffer.lines.get(1)!.loadCell(0, cell).getChars(), high + String.fromCharCode(j));
|
||||
assert.equal(term.buffer.lines.get(1)!.loadCell(0, cell).getChars().length, 2);
|
||||
assert.equal(term.buffer.lines.get(1)!.loadCell(1, cell).getChars(), '');
|
||||
term.reset();
|
||||
}
|
||||
});
|
||||
it(`${range}: 2 characters per cell over line end without autowrap`, async function (): Promise<void> {
|
||||
const high = String.fromCharCode(0xD800);
|
||||
const cell = new CellData();
|
||||
for (let j = i; j <= i + 0xF; j++) {
|
||||
term.buffer.x = term.cols - 1;
|
||||
await term.writeP('\x1b[?7l'); // Disable wraparound mode
|
||||
const width = wcwidth((0xD800 - 0xD800) * 0x400 + j - 0xDC00 + 0x10000);
|
||||
if (width !== 1) {
|
||||
continue;
|
||||
}
|
||||
await term.writeP('a' + high + String.fromCharCode(j));
|
||||
// auto wraparound mode should cut off the rest of the line
|
||||
assert.equal(term.buffer.lines.get(0)!.loadCell(term.cols - 1, cell).getChars(), high + String.fromCharCode(j));
|
||||
assert.equal(term.buffer.lines.get(0)!.loadCell(term.cols - 1, cell).getChars().length, 2);
|
||||
assert.equal(term.buffer.lines.get(1)!.loadCell(1, cell).getChars(), '');
|
||||
term.reset();
|
||||
}
|
||||
});
|
||||
it(`${range}: splitted surrogates`, async function (): Promise<void> {
|
||||
const high = String.fromCharCode(0xD800);
|
||||
const cell = new CellData();
|
||||
for (let j = i; j <= i + 0xF; j++) {
|
||||
await term.writeP(high + String.fromCharCode(j));
|
||||
const tchar = term.buffer.lines.get(0)!.loadCell(0, cell);
|
||||
assert.equal(tchar.getChars(), high + String.fromCharCode(j));
|
||||
assert.equal(tchar.getChars().length, 2);
|
||||
assert.equal(tchar.getWidth(), 1);
|
||||
assert.equal(term.buffer.lines.get(0)!.loadCell(1, cell).getChars(), '');
|
||||
term.reset();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('unicode - combining characters', () => {
|
||||
|
||||
+60
-2
@@ -94,6 +94,20 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
*/
|
||||
private _keyDownHandled: boolean = false;
|
||||
|
||||
/**
|
||||
* Records whether the keypress event has already been handled and triggered a data event, if so
|
||||
* the input event should not trigger a data event but should still print to the textarea so
|
||||
* screen readers will announce it.
|
||||
*/
|
||||
private _keyPressHandled: boolean = false;
|
||||
|
||||
/**
|
||||
* Records whether there has been a keydown event for a dead key without a corresponding keydown
|
||||
* event for the composed/alternative character. If we cancel the keydown event for the dead key,
|
||||
* no events will be emitted for the final character.
|
||||
*/
|
||||
private _unprocessedDeadKey: boolean = false;
|
||||
|
||||
public linkifier: ILinkifier;
|
||||
public linkifier2: ILinkifier2;
|
||||
public viewport: IViewport | undefined;
|
||||
@@ -384,6 +398,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
this.register(addDisposableDomListener(this.textarea!, 'compositionstart', () => this._compositionHelper!.compositionstart()));
|
||||
this.register(addDisposableDomListener(this.textarea!, 'compositionupdate', (e: CompositionEvent) => this._compositionHelper!.compositionupdate(e)));
|
||||
this.register(addDisposableDomListener(this.textarea!, 'compositionend', () => this._compositionHelper!.compositionend()));
|
||||
this.register(addDisposableDomListener(this.textarea!, 'input', (ev: InputEvent) => this._inputEvent(ev), true));
|
||||
this.register(this.onRender(() => this._compositionHelper!.updateCompositionElements()));
|
||||
this.register(this.onRender(e => this._queueLinkification(e.start, e.end)));
|
||||
}
|
||||
@@ -1026,6 +1041,10 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event.key === 'Dead' || event.key === 'AltGraph') {
|
||||
this._unprocessedDeadKey = true;
|
||||
}
|
||||
|
||||
const result = evaluateKeyboardEvent(event, this.coreService.decPrivateModes.applicationCursorKeys, this.browser.isMac, this.options.macOptionIsMeta);
|
||||
|
||||
this.updateCursorStyle(event);
|
||||
@@ -1053,6 +1072,11 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this._unprocessedDeadKey) {
|
||||
this._unprocessedDeadKey = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// If ctrl+c or enter is being sent, clear out the textarea. This is done so that screen readers
|
||||
// will announce deleted characters. This will not work 100% of the time but it should cover
|
||||
// most scenarios.
|
||||
@@ -1075,10 +1099,11 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
this._keyDownHandled = true;
|
||||
}
|
||||
|
||||
private _isThirdLevelShift(browser: IBrowser, ev: IKeyboardEvent): boolean {
|
||||
private _isThirdLevelShift(browser: IBrowser, ev: KeyboardEvent): boolean {
|
||||
const thirdLevelKey =
|
||||
(browser.isMac && !this.options.macOptionIsMeta && ev.altKey && !ev.ctrlKey && !ev.metaKey) ||
|
||||
(browser.isWindows && ev.altKey && ev.ctrlKey && !ev.metaKey);
|
||||
(browser.isWindows && ev.altKey && ev.ctrlKey && !ev.metaKey) ||
|
||||
(browser.isWindows && ev.getModifierState('AltGraph'));
|
||||
|
||||
if (ev.type === 'keypress') {
|
||||
return thirdLevelKey;
|
||||
@@ -1098,6 +1123,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
}
|
||||
|
||||
this.updateCursorStyle(ev);
|
||||
this._keyPressHandled = false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1109,6 +1135,8 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
protected _keyPress(ev: KeyboardEvent): boolean {
|
||||
let key;
|
||||
|
||||
this._keyPressHandled = false;
|
||||
|
||||
if (this._keyDownHandled) {
|
||||
return false;
|
||||
}
|
||||
@@ -1141,9 +1169,35 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
this._showCursor();
|
||||
this.coreService.triggerDataEvent(key, true);
|
||||
|
||||
this._keyPressHandled = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an input event.
|
||||
* Key Resources:
|
||||
* - https://developer.mozilla.org/en-US/docs/Web/API/InputEvent
|
||||
* @param ev The input event to be handled.
|
||||
*/
|
||||
protected _inputEvent(ev: InputEvent): boolean {
|
||||
// Only support emoji IMEs when screen reader mode is disabled as the event must bubble up to
|
||||
// support reading out character input which can doubling up input characters
|
||||
if (ev.data && ev.inputType === 'insertText' && !this.optionsService.options.screenReaderMode) {
|
||||
if (this._keyPressHandled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const text = ev.data;
|
||||
this.coreService.triggerDataEvent(text, true);
|
||||
|
||||
this.cancel(ev);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ring the bell.
|
||||
* Note: We could do sweet things with webaudio here
|
||||
@@ -1239,6 +1293,10 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
this.viewport?.syncScrollArea();
|
||||
}
|
||||
|
||||
public clearTextureAtlas(): void {
|
||||
this._renderService?.clearTextureAtlas();
|
||||
}
|
||||
|
||||
private _reportFocus(): void {
|
||||
if (this.element?.classList.contains('focus')) {
|
||||
this.coreService.triggerDataEvent(C0.ESC + '[I');
|
||||
|
||||
@@ -195,6 +195,9 @@ export class MockTerminal implements ITerminal {
|
||||
public reset(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public clearTextureAtlas(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public refresh(start: number, end: number): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
@@ -374,6 +377,9 @@ export class MockRenderService implements IRenderService {
|
||||
public refreshRows(start: number, end: number): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public clearTextureAtlas(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public resize(cols: number, rows: number): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
Vendored
+1
@@ -79,6 +79,7 @@ export interface IPublicTerminal extends IDisposable {
|
||||
write(data: string | Uint8Array, callback?: () => void): void;
|
||||
paste(data: string): void;
|
||||
refresh(start: number, end: number): void;
|
||||
clearTextureAtlas(): void;
|
||||
reset(): void;
|
||||
}
|
||||
|
||||
|
||||
+31
-9
@@ -8,6 +8,8 @@ import { addDisposableDomListener } from 'browser/Lifecycle';
|
||||
import { IColorSet, IViewport } from 'browser/Types';
|
||||
import { ICharSizeService, IRenderService } from 'browser/services/Services';
|
||||
import { IBufferService, IOptionsService } from 'common/services/Services';
|
||||
import { IBuffer } from 'common/buffer/Types';
|
||||
import { IRenderDimensions } from 'browser/renderer/Types';
|
||||
|
||||
const FALLBACK_SCROLL_BAR_WIDTH = 15;
|
||||
|
||||
@@ -18,11 +20,15 @@ const FALLBACK_SCROLL_BAR_WIDTH = 15;
|
||||
export class Viewport extends Disposable implements IViewport {
|
||||
public scrollBarWidth: number = 0;
|
||||
private _currentRowHeight: number = 0;
|
||||
private _currentScaledCellHeight: number = 0;
|
||||
private _lastRecordedBufferLength: number = 0;
|
||||
private _lastRecordedViewportHeight: number = 0;
|
||||
private _lastRecordedBufferHeight: number = 0;
|
||||
private _lastTouchY: number = 0;
|
||||
private _lastScrollTop: number = 0;
|
||||
private _lastHadScrollBar: boolean = false;
|
||||
private _activeBuffer: IBuffer;
|
||||
private _renderDimensions: IRenderDimensions;
|
||||
|
||||
// Stores a partial line amount when scrolling, this is used to keep track of how much of a line
|
||||
// is scrolled so we can "scroll" over partial lines and feel natural on touchpads. This is a
|
||||
@@ -47,8 +53,15 @@ export class Viewport extends Disposable implements IViewport {
|
||||
// Unfortunately the overlay scrollbar would be hidden underneath the screen element in that case,
|
||||
// therefore we account for a standard amount to make it visible
|
||||
this.scrollBarWidth = (this._viewportElement.offsetWidth - this._scrollArea.offsetWidth) || FALLBACK_SCROLL_BAR_WIDTH;
|
||||
this._lastHadScrollBar = true;
|
||||
this.register(addDisposableDomListener(this._viewportElement, 'scroll', this._onScroll.bind(this)));
|
||||
|
||||
// Track properties used in performance critical code manually to avoid using slow getters
|
||||
this._activeBuffer = this._bufferService.buffer;
|
||||
this.register(this._bufferService.buffers.onBufferActivate(e => this._activeBuffer = e.activeBuffer));
|
||||
this._renderDimensions = this._renderService.dimensions;
|
||||
this.register(this._renderService.onDimensionsChange(e => this._renderDimensions = e));
|
||||
|
||||
// Perform this async to ensure the ICharSizeService is ready.
|
||||
setTimeout(() => this.syncScrollArea(), 0);
|
||||
}
|
||||
@@ -77,6 +90,7 @@ export class Viewport extends Disposable implements IViewport {
|
||||
private _innerRefresh(): void {
|
||||
if (this._charSizeService.height > 0) {
|
||||
this._currentRowHeight = this._renderService.dimensions.scaledCellHeight / window.devicePixelRatio;
|
||||
this._currentScaledCellHeight = this._renderService.dimensions.scaledCellHeight;
|
||||
this._lastRecordedViewportHeight = this._viewportElement.offsetHeight;
|
||||
const newBufferHeight = Math.round(this._currentRowHeight * this._lastRecordedBufferLength) + (this._lastRecordedViewportHeight - this._renderService.dimensions.canvasHeight);
|
||||
if (this._lastRecordedBufferHeight !== newBufferHeight) {
|
||||
@@ -94,8 +108,18 @@ export class Viewport extends Disposable implements IViewport {
|
||||
this._viewportElement.scrollTop = scrollTop;
|
||||
}
|
||||
|
||||
// Update scroll bar width
|
||||
if (this._optionsService.options.scrollback === 0) {
|
||||
this.scrollBarWidth = 0;
|
||||
} else {
|
||||
this.scrollBarWidth = (this._viewportElement.offsetWidth - this._scrollArea.offsetWidth) || FALLBACK_SCROLL_BAR_WIDTH;
|
||||
}
|
||||
this._lastHadScrollBar = this.scrollBarWidth > 0;
|
||||
|
||||
this._viewportElement.style.width = (this._renderService.dimensions.actualCellWidth * (this._bufferService.cols) + this.scrollBarWidth).toString() + 'px';
|
||||
this._refreshAnimationFrame = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates dimensions and synchronizes the scroll area if necessary.
|
||||
*/
|
||||
@@ -114,23 +138,21 @@ export class Viewport extends Disposable implements IViewport {
|
||||
}
|
||||
|
||||
// If the buffer position doesn't match last scroll top
|
||||
const newScrollTop = this._bufferService.buffer.ydisp * this._currentRowHeight;
|
||||
if (this._lastScrollTop !== newScrollTop) {
|
||||
this._refresh(immediate);
|
||||
return;
|
||||
}
|
||||
|
||||
// If element's scroll top changed, this can happen when hiding the element
|
||||
if (this._lastScrollTop !== this._viewportElement.scrollTop) {
|
||||
if (this._lastScrollTop !== this._activeBuffer.ydisp * this._currentRowHeight) {
|
||||
this._refresh(immediate);
|
||||
return;
|
||||
}
|
||||
|
||||
// If row height changed
|
||||
if (this._renderService.dimensions.scaledCellHeight / window.devicePixelRatio !== this._currentRowHeight) {
|
||||
if (this._renderDimensions.scaledCellHeight !== this._currentScaledCellHeight) {
|
||||
this._refresh(immediate);
|
||||
return;
|
||||
}
|
||||
|
||||
// If the scroll bar visibility changed
|
||||
if (this._lastHadScrollBar !== (this._optionsService.options.scrollback > 0)) {
|
||||
this._refresh(immediate);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -222,6 +222,9 @@ export class Terminal implements ITerminalApi {
|
||||
public reset(): void {
|
||||
this._core.reset();
|
||||
}
|
||||
public clearTextureAtlas(): void {
|
||||
this._core.clearTextureAtlas();
|
||||
}
|
||||
public loadAddon(addon: ITerminalAddon): void {
|
||||
return this._addonManager.loadAddon(this, addon);
|
||||
}
|
||||
|
||||
@@ -138,6 +138,10 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
|
||||
public abstract reset(): void;
|
||||
|
||||
public clearTextureAtlas(): void {
|
||||
this._charAtlas?.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills 1+ cells completely. This uses the existing fillStyle on the context.
|
||||
* @param x The column to start at.
|
||||
|
||||
@@ -149,6 +149,12 @@ export class Renderer extends Disposable implements IRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
public clearTextureAtlas(): void {
|
||||
for (const layer of this._renderLayers) {
|
||||
layer.clearTextureAtlas();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recalculates the character and canvas dimensions.
|
||||
*/
|
||||
|
||||
Vendored
+6
@@ -52,6 +52,7 @@ export interface IRenderer extends IDisposable {
|
||||
onOptionsChanged(): void;
|
||||
clear(): void;
|
||||
renderRows(start: number, end: number): void;
|
||||
clearTextureAtlas?(): void;
|
||||
}
|
||||
|
||||
export interface IRenderLayer extends IDisposable {
|
||||
@@ -100,4 +101,9 @@ export interface IRenderLayer extends IDisposable {
|
||||
* Clear the state of the render layer.
|
||||
*/
|
||||
reset(): void;
|
||||
|
||||
/**
|
||||
* Clears the texture atlas.
|
||||
*/
|
||||
clearTextureAtlas(): void;
|
||||
}
|
||||
|
||||
@@ -28,6 +28,8 @@ export abstract class BaseCharAtlas implements IDisposable {
|
||||
*/
|
||||
private _doWarmUp(): void { }
|
||||
|
||||
public clear(): void { }
|
||||
|
||||
/**
|
||||
* Called when we start drawing a new frame.
|
||||
*
|
||||
|
||||
@@ -119,6 +119,16 @@ export class DynamicCharAtlas extends BaseCharAtlas {
|
||||
this._drawToCacheCount = 0;
|
||||
}
|
||||
|
||||
public clear(): void {
|
||||
if (this._cacheMap.size > 0) {
|
||||
const capacity = this._width * this._height;
|
||||
this._cacheMap = new LRUMap(capacity);
|
||||
this._cacheMap.prealloc(capacity);
|
||||
}
|
||||
this._cacheCtx.clearRect(0, 0, TEXTURE_WIDTH, TEXTURE_HEIGHT);
|
||||
this._tmpCtx.clearRect(0, 0, this._config.scaledCharWidth, this._config.scaledCharHeight);
|
||||
}
|
||||
|
||||
public draw(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
glyph: IGlyphIdentifier,
|
||||
@@ -266,11 +276,10 @@ export class DynamicCharAtlas extends BaseCharAtlas {
|
||||
}
|
||||
// Draw the character
|
||||
this._tmpCtx.fillText(glyph.chars, 0, this._config.scaledCharHeight);
|
||||
this._tmpCtx.restore();
|
||||
|
||||
// clear the background from the character to avoid issues with drawing over the previous
|
||||
// character if it extends past it's bounds
|
||||
const imageData = this._tmpCtx.getImageData(
|
||||
let imageData = this._tmpCtx.getImageData(
|
||||
0, 0, this._config.scaledCharWidth, this._config.scaledCharHeight
|
||||
);
|
||||
let isEmpty = false;
|
||||
@@ -278,6 +287,27 @@ export class DynamicCharAtlas extends BaseCharAtlas {
|
||||
isEmpty = clearColor(imageData, backgroundColor);
|
||||
}
|
||||
|
||||
// If this charcater is underscore and empty, shift it up until it is visible, try for a maximum
|
||||
// of 5 pixels.
|
||||
if (isEmpty && glyph.chars === '_' && !this._config.allowTransparency) {
|
||||
for (let offset = 1; offset <= 5; offset++) {
|
||||
// Draw the character
|
||||
this._tmpCtx.fillText(glyph.chars, 0, this._config.scaledCharHeight - offset);
|
||||
|
||||
// clear the background from the character to avoid issues with drawing over the previous
|
||||
// character if it extends past it's bounds
|
||||
imageData = this._tmpCtx.getImageData(
|
||||
0, 0, this._config.scaledCharWidth, this._config.scaledCharHeight
|
||||
);
|
||||
isEmpty = clearColor(imageData, backgroundColor);
|
||||
if (!isEmpty) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this._tmpCtx.restore();
|
||||
|
||||
// copy the data from imageData to _cacheCanvas
|
||||
const x = this._toCoordinateX(index);
|
||||
const y = this._toCoordinateY(index);
|
||||
|
||||
@@ -10,7 +10,7 @@ import { NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR, DEFAULT_ATTR, FgFlags,
|
||||
import { BufferLine, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
|
||||
import { IBufferLine } from 'common/Types';
|
||||
import { CellData } from 'common/buffer/CellData';
|
||||
import { MockOptionsService } from 'common/TestUtils.test';
|
||||
import { MockCoreService, MockOptionsService } from 'common/TestUtils.test';
|
||||
import { css } from 'browser/Color';
|
||||
import { MockCharacterJoinerService } from 'browser/TestUtils.test';
|
||||
|
||||
@@ -21,30 +21,36 @@ describe('DomRendererRowFactory', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
dom = new jsdom.JSDOM('');
|
||||
rowFactory = new DomRendererRowFactory(dom.window.document, {
|
||||
background: css.toColor('#010101'),
|
||||
foreground: css.toColor('#020202'),
|
||||
ansi: [
|
||||
// dark:
|
||||
css.toColor('#2e3436'),
|
||||
css.toColor('#cc0000'),
|
||||
css.toColor('#4e9a06'),
|
||||
css.toColor('#c4a000'),
|
||||
css.toColor('#3465a4'),
|
||||
css.toColor('#75507b'),
|
||||
css.toColor('#06989a'),
|
||||
css.toColor('#d3d7cf'),
|
||||
// bright:
|
||||
css.toColor('#555753'),
|
||||
css.toColor('#ef2929'),
|
||||
css.toColor('#8ae234'),
|
||||
css.toColor('#fce94f'),
|
||||
css.toColor('#729fcf'),
|
||||
css.toColor('#ad7fa8'),
|
||||
css.toColor('#34e2e2'),
|
||||
css.toColor('#eeeeec')
|
||||
]
|
||||
} as any, new MockCharacterJoinerService(), new MockOptionsService({ drawBoldTextInBrightColors: true }));
|
||||
rowFactory = new DomRendererRowFactory(
|
||||
dom.window.document,
|
||||
{
|
||||
background: css.toColor('#010101'),
|
||||
foreground: css.toColor('#020202'),
|
||||
ansi: [
|
||||
// dark:
|
||||
css.toColor('#2e3436'),
|
||||
css.toColor('#cc0000'),
|
||||
css.toColor('#4e9a06'),
|
||||
css.toColor('#c4a000'),
|
||||
css.toColor('#3465a4'),
|
||||
css.toColor('#75507b'),
|
||||
css.toColor('#06989a'),
|
||||
css.toColor('#d3d7cf'),
|
||||
// bright:
|
||||
css.toColor('#555753'),
|
||||
css.toColor('#ef2929'),
|
||||
css.toColor('#8ae234'),
|
||||
css.toColor('#fce94f'),
|
||||
css.toColor('#729fcf'),
|
||||
css.toColor('#ad7fa8'),
|
||||
css.toColor('#34e2e2'),
|
||||
css.toColor('#eeeeec')
|
||||
]
|
||||
} as any,
|
||||
new MockCharacterJoinerService(),
|
||||
new MockOptionsService({ drawBoldTextInBrightColors: true }),
|
||||
new MockCoreService()
|
||||
);
|
||||
lineData = createEmptyLineData(2);
|
||||
});
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import { IBufferLine } from 'common/Types';
|
||||
import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants';
|
||||
import { NULL_CELL_CODE, WHITESPACE_CELL_CHAR, Attributes } from 'common/buffer/Constants';
|
||||
import { CellData } from 'common/buffer/CellData';
|
||||
import { IOptionsService } from 'common/services/Services';
|
||||
import { ICoreService, IOptionsService } from 'common/services/Services';
|
||||
import { color, rgba } from 'browser/Color';
|
||||
import { IColorSet, IColor } from 'browser/Types';
|
||||
import { ICharacterJoinerService } from 'browser/services/Services';
|
||||
@@ -31,7 +31,8 @@ export class DomRendererRowFactory {
|
||||
private readonly _document: Document,
|
||||
private _colors: IColorSet,
|
||||
@ICharacterJoinerService private readonly _characterJoinerService: ICharacterJoinerService,
|
||||
@IOptionsService private readonly _optionsService: IOptionsService
|
||||
@IOptionsService private readonly _optionsService: IOptionsService,
|
||||
@ICoreService private readonly _coreService: ICoreService
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -110,7 +111,7 @@ export class DomRendererRowFactory {
|
||||
}
|
||||
}
|
||||
|
||||
if (isCursorRow && x === cursorX) {
|
||||
if (!this._coreService.isCursorHidden && isCursorRow && x === cursorX) {
|
||||
charElement.classList.add(CURSOR_CLASS);
|
||||
|
||||
if (cursorBlink) {
|
||||
|
||||
@@ -168,6 +168,11 @@ export class RenderService extends Disposable implements IRenderService {
|
||||
}
|
||||
}
|
||||
|
||||
public clearTextureAtlas(): void {
|
||||
this._renderer?.clearTextureAtlas?.();
|
||||
this._fullRefresh();
|
||||
}
|
||||
|
||||
public setColors(colors: IColorSet): void {
|
||||
this._renderer.setColors(colors);
|
||||
this._fullRefresh();
|
||||
|
||||
@@ -53,6 +53,7 @@ export interface IRenderService extends IDisposable {
|
||||
dimensions: IRenderDimensions;
|
||||
|
||||
refreshRows(start: number, end: number): void;
|
||||
clearTextureAtlas(): void;
|
||||
resize(cols: number, rows: number): void;
|
||||
changeOptions(): void;
|
||||
setRenderer(renderer: IRenderer): void;
|
||||
|
||||
Reference in New Issue
Block a user