Merge branch 'master' into cleanbuild

This commit is contained in:
Daniel Imms
2021-10-25 02:47:52 -07:00
committed by GitHub
48 changed files with 771 additions and 606 deletions
+2 -1
View File
@@ -4,6 +4,7 @@
"appPort": 3000,
"extensions": [
"dbaeumer.vscode-eslint",
"editorconfig.editorconfig"
"editorconfig.editorconfig",
"hbenl.vscode-mocha-test-adapter"
]
}
+2
View File
@@ -183,6 +183,8 @@ Xterm.js is used in several world-class applications to provide great terminal e
- [**HashiCorp Nomad**](https://www.nomadproject.io/): A container orchestrator with the ability to connect to remote tasks via a web interface using websockets and xterm.js.
- [**TermPair**](https://github.com/cs01/termpair): View and control terminals from your browser with end-to-end encryption
- [**gdbgui**](https://github.com/cs01/gdbgui): Browser-based frontend to gdb (gnu debugger)
- [**goormIDE**](https://ide.goorm.io/): Run almost every programming languages with real-time collaboration, live pair programming, and built-in messenger.
- [**FleetDeck**](https://fleetdeck.io): Remote desktop & virtual terminal
- [And much more...](https://github.com/xtermjs/xterm.js/network/dependents)
Do you use xterm.js in your application as well? Please [open a Pull Request](https://github.com/sourcelair/xterm.js/pulls) to include it here. We would love to have it on our list. Note: Please add any new contributions to the end of the list only.
+1 -1
View File
@@ -36,7 +36,7 @@
},
"devDependencies": {
"@types/sinon": "^5.0.1",
"axios": "^0.18.0",
"axios": "^0.21.2",
"mkdirp": "0.5.5",
"sinon": "6.3.5",
"yauzl": "^2.10.0"
+9 -24
View File
@@ -45,23 +45,17 @@ array-from@^2.1.1:
resolved "https://registry.yarnpkg.com/array-from/-/array-from-2.1.1.tgz#cfe9d8c26628b9dc5aecc62a9f5d8f1f352c1195"
integrity sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=
axios@^0.18.0:
version "0.18.1"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.18.1.tgz#ff3f0de2e7b5d180e757ad98000f1081b87bcea3"
axios@^0.21.2:
version "0.21.2"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.2.tgz#21297d5084b2aeeb422f5d38e7be4fbb82239017"
integrity sha512-87otirqUw3e8CzHTMO+/9kh/FSgXt/eVDvipijwDtEuwbkySWZ9SBm6VEubmJ/kLKEoLQV/POhxXFb66bfekfg==
dependencies:
follow-redirects "1.5.10"
is-buffer "^2.0.2"
follow-redirects "^1.14.0"
buffer-crc32@~0.2.3:
version "0.2.13"
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
debug@=3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
dependencies:
ms "2.0.0"
diff@^3.5.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
@@ -72,11 +66,10 @@ fd-slicer@~1.1.0:
dependencies:
pend "~1.2.0"
follow-redirects@1.5.10:
version "1.5.10"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a"
dependencies:
debug "=3.1.0"
follow-redirects@^1.14.0:
version "1.14.3"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.3.tgz#6ada78118d8d24caee595595accdc0ac6abd022e"
integrity sha512-3MkHxknWMUtb23apkgz/83fDoe+y+qr0TdgacGIA7bew+QLBo3vdgEN2xEsuXNivpFy4CyDhBBZnNZOtalmenw==
font-finder@^1.0.3:
version "1.0.4"
@@ -110,10 +103,6 @@ has-flag@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
is-buffer@^2.0.2:
version "2.0.3"
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.3.tgz#4ecf3fcf749cbd1e472689e109ac66261a25e725"
isarray@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
@@ -163,10 +152,6 @@ mkdirp@0.5.5:
dependencies:
minimist "^1.2.5"
ms@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
nise@^1.4.5:
version "1.5.3"
resolved "https://registry.yarnpkg.com/nise/-/nise-1.5.3.tgz#9d2cfe37d44f57317766c6e9408a359c5d3ac1f7"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "xterm-addon-search",
"version": "0.8.0",
"version": "0.8.1",
"author": {
"name": "The xterm.js authors",
"url": "https://xtermjs.org/"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "xterm-addon-serialize",
"version": "0.5.0",
"version": "0.6.0",
"author": {
"name": "The xterm.js authors",
"url": "https://xtermjs.org/"
@@ -72,7 +72,8 @@ function equalFlags(cell1: IBufferCell, cell2: IBufferCell): boolean {
&& cell1.isBlink() === cell2.isBlink()
&& cell1.isInvisible() === cell2.isInvisible()
&& cell1.isItalic() === cell2.isItalic()
&& cell1.isDim() === cell2.isDim();
&& cell1.isDim() === cell2.isDim()
&& cell1.isStrikethrough() === cell2.isStrikethrough();
}
class StringSerializeHandler extends BaseSerializeHandler {
@@ -160,7 +161,7 @@ class StringSerializeHandler extends BaseSerializeHandler {
if (
// you must output character to cause overflow, control sequence can't do this
nextRowFirstChar.getChars() &&
isNextRowFirstCharDoubleWidth ? this._nullCellCount <= 1 : this._nullCellCount <= 0
isNextRowFirstCharDoubleWidth ? this._nullCellCount <= 1 : this._nullCellCount <= 0
) {
if (
// the last character can't be null,
@@ -259,6 +260,7 @@ class StringSerializeHandler extends BaseSerializeHandler {
if (cell.isInvisible() !== oldCell.isInvisible()) { sgrSeq.push(cell.isInvisible() ? 8 : 28); }
if (cell.isItalic() !== oldCell.isItalic()) { sgrSeq.push(cell.isItalic() ? 3 : 23); }
if (cell.isDim() !== oldCell.isDim()) { sgrSeq.push(cell.isDim() ? 2 : 22); }
if (cell.isStrikethrough() !== oldCell.isStrikethrough()) { sgrSeq.push(cell.isStrikethrough() ? 9 : 29); }
}
}
}
@@ -433,26 +435,37 @@ export class SerializeAddon implements ITerminalAddon {
return content;
}
public serialize(scrollback?: number): string {
public serialize(options?: ISerializeOptions): string {
// TODO: Add combinedData support
if (!this._terminal) {
throw new Error('Cannot use addon until it has been loaded');
}
// Normal buffer
let content = this._serializeBuffer(this._terminal, this._terminal.buffer.normal, scrollback);
let content = this._serializeBuffer(this._terminal, this._terminal.buffer.normal, options?.scrollback);
// Alternate buffer
if (this._terminal.buffer.active.type === 'alternate') {
const alternativeScreenContent = this._serializeBuffer(this._terminal, this._terminal.buffer.alternate, undefined);
content += `\u001b[?1049h\u001b[H${alternativeScreenContent}`;
if (!options?.excludeAltBuffer) {
if (this._terminal.buffer.active.type === 'alternate') {
const alternativeScreenContent = this._serializeBuffer(this._terminal, this._terminal.buffer.alternate, undefined);
content += `\u001b[?1049h\u001b[H${alternativeScreenContent}`;
}
}
// Modes
content += this._serializeModes(this._terminal);
if (!options?.excludeModes) {
content += this._serializeModes(this._terminal);
}
return content;
}
public dispose(): void { }
}
interface ISerializeOptions {
scrollback?: number;
excludeModes?: boolean;
excludeAltBuffer?: boolean;
}
@@ -146,7 +146,7 @@ describe('SerializeAddon', () => {
const cols = 10;
const lines = newArray<string>((index: number) => digitsString(cols, index), rows);
await writeSync(page, lines.join('\\r\\n'));
assert.equal(await page.evaluate(`serializeAddon.serialize(${halfScrollback});`), lines.slice(halfScrollback, rows).join('\r\n'));
assert.equal(await page.evaluate(`serializeAddon.serialize({ scrollback: ${halfScrollback} });`), lines.slice(halfScrollback, rows).join('\r\n'));
});
it('serialize 0 rows of scrollback', async function(): Promise<any> {
@@ -154,7 +154,19 @@ describe('SerializeAddon', () => {
const cols = 10;
const lines = newArray<string>((index: number) => digitsString(cols, index), rows);
await writeSync(page, lines.join('\\r\\n'));
assert.equal(await page.evaluate(`serializeAddon.serialize(0);`), lines.slice(rows - 10, rows).join('\r\n'));
assert.equal(await page.evaluate(`serializeAddon.serialize({ scrollback: 0 });`), lines.slice(rows - 10, rows).join('\r\n'));
});
it('serialize exclude modes', async () => {
await writeSync(page, 'before\\x1b[?1hafter');
assert.equal(await page.evaluate(`serializeAddon.serialize();`), 'beforeafter\x1b[?1h');
assert.equal(await page.evaluate(`serializeAddon.serialize({ excludeModes: true });`), 'beforeafter');
});
it('serialize exclude alt buffer', async () => {
await writeSync(page, 'normal\\x1b[?1049h\\x1b[Halt');
assert.equal(await page.evaluate(`serializeAddon.serialize();`), 'normal\x1b[?1049h\x1b[Halt');
assert.equal(await page.evaluate(`serializeAddon.serialize({ excludeAltBuffer: true });`), 'normal');
});
it('serialize all rows of content with color16', async function(): Promise<any> {
@@ -184,11 +196,13 @@ describe('SerializeAddon', () => {
sgr(UNDERLINED) + line,
sgr(BLINK) + line,
sgr(INVISIBLE) + line,
sgr(STRIKETHROUGH) + line,
sgr(NO_INVERSE) + line,
sgr(NO_BOLD) + line,
sgr(NO_UNDERLINED) + line,
sgr(NO_BLINK) + line,
sgr(NO_INVISIBLE) + line
sgr(NO_INVISIBLE) + line,
sgr(NO_STRIKETHROUGH) + line
];
const rows = lines.length;
await writeSync(page, lines.join('\\r\\n'));
@@ -579,20 +593,20 @@ const BG_RGB_GREEN = '48;2;0;255;0';
const BG_RGB_YELLOW = '48;2;255;255;0';
const BG_RESET = '49';
const INVERSE = '7';
const BOLD = '1';
const DIM = '2';
const ITALIC = '3';
const UNDERLINED = '4';
const BLINK = '5';
const INVERSE = '7';
const INVISIBLE = '8';
const STRIKETHROUGH = '9';
const NO_INVERSE = '27';
const NO_BOLD = '22';
const NO_DIM = '22';
const NO_ITALIC = '23';
const NO_UNDERLINED = '24';
const NO_BLINK = '25';
const NO_INVERSE = '27';
const NO_INVISIBLE = '28';
const ITALIC = '3';
const DIM = '2';
const NO_ITALIC = '23';
const NO_DIM = '22';
const NO_STRIKETHROUGH = '29';
@@ -7,14 +7,14 @@ import { Terminal, ITerminalAddon } from 'xterm';
declare module 'xterm-addon-serialize' {
/**
* An xterm.js addon that enables web links.
* An xterm.js addon that enables serialization of terminal contents.
*/
export class SerializeAddon implements ITerminalAddon {
constructor();
/**
* Activates the addon
* Activates the addon.
* @param terminal The terminal the addon is being loaded in.
*/
public activate(terminal: Terminal): void;
@@ -24,15 +24,36 @@ declare module 'xterm-addon-serialize' {
* the state. The cursor will also be positioned to the correct cell. When restoring a terminal
* it is best to do before `Terminal.open` is called to avoid wasting CPU cycles rendering
* incomplete frames.
* @param scrollback The number of rows in scrollback buffer to serialize, starting from the
* bottom of the scrollback buffer. This defaults to the all available rows in the scrollback
* buffer.
*
* It's recommended that you write the serialized data into a terminal of the same size in which
* it originated from and then resize it after if needed.
*
* @param options Custom options to allow control over what gets serialized.
*/
public serialize(scrollback?: number): string;
public serialize(options?: ISerializeOptions): string;
/**
* Disposes the addon.
*/
public dispose(): void;
}
export interface ISerializeOptions {
/**
* The number of rows in the scrollback buffer to serialize, starting from the bottom of the
* scrollback buffer. When not specified, all available rows in the scrollback buffer will be
* serialized.
*/
scrollback?: number;
/**
* Whether to exclude the terminal modes from the serialization. False by default.
*/
excludeModes?: boolean;
/**
* Whether to exclude the alt buffer from the serialization. False by default.
*/
excludeAltBuffer?: boolean;
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "xterm-addon-unicode11",
"version": "0.2.0",
"version": "0.3.0",
"author": {
"name": "The xterm.js authors",
"url": "https://xtermjs.org/"
@@ -29,7 +29,11 @@ const strictUrlRegex = new RegExp(start + protocolClause + bodyClause + end);
function handleLink(event: MouseEvent, uri: string): void {
const newWindow = window.open();
if (newWindow) {
newWindow.opener = null;
try {
newWindow.opener = null;
} catch {
// no-op, Electron can throw
}
newWindow.location.href = uri;
} else {
console.warn('Opening link blocked as opener could not be cleared');
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "xterm-addon-webgl",
"version": "0.11.1",
"version": "0.11.2",
"author": {
"name": "The xterm.js authors",
"url": "https://xtermjs.org/"
@@ -8,6 +8,7 @@ import { WebglRenderer } from './WebglRenderer';
import { ICharacterJoinerService, IRenderService } from 'browser/services/Services';
import { IColorSet } from 'browser/Types';
import { EventEmitter } from 'common/EventEmitter';
import { isSafari } from 'common/Platform';
export class WebglAddon implements ITerminalAddon {
private _terminal?: Terminal;
@@ -23,6 +24,9 @@ export class WebglAddon implements ITerminalAddon {
if (!terminal.element) {
throw new Error('Cannot activate WebglAddon before Terminal.open');
}
if (isSafari) {
throw new Error('Webgl is not currently supported on Safari');
}
this._terminal = terminal;
const renderService: IRenderService = (terminal as any)._core._renderService;
const characterJoinerService: ICharacterJoinerService = (terminal as any)._core._characterJoinerService;
@@ -60,7 +60,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
this._renderLayers = [
new LinkRenderLayer(this._core.screenElement!, 2, this._colors, this._core),
new CursorRenderLayer(this._core.screenElement!, 3, this._colors, this._core, this._onRequestRedraw)
new CursorRenderLayer(_terminal, this._core.screenElement!, 3, this._colors, this._core, this._onRequestRedraw)
];
this.dimensions = {
scaledCharWidth: 0,
@@ -108,7 +108,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
for (const l of this._renderLayers) {
l.dispose();
}
this._core.screenElement!.removeChild(this._canvas);
this._canvas.parentElement?.removeChild(this._canvas);
super.dispose();
}
@@ -31,6 +31,7 @@ export class CursorRenderLayer extends BaseRenderLayer {
private _cell: ICellData = new CellData();
constructor(
terminal: Terminal,
container: HTMLElement,
zIndex: number,
colors: IColorSet,
@@ -50,7 +51,7 @@ export class CursorRenderLayer extends BaseRenderLayer {
'block': this._renderBlockCursor.bind(this),
'underline': this._renderUnderlineCursor.bind(this)
};
// TODO: Consider initial options? Maybe onOptionsChanged should be called at the end of open?
this.onOptionsChanged(terminal);
}
public resize(terminal: Terminal, dim: IRenderDimensions): void {
@@ -67,25 +68,18 @@ export class CursorRenderLayer extends BaseRenderLayer {
public reset(terminal: Terminal): void {
this._clearCursor();
if (this._cursorBlinkStateManager) {
this._cursorBlinkStateManager.dispose();
this.onOptionsChanged(terminal);
}
this._cursorBlinkStateManager?.restartBlinkAnimation(terminal);
this.onOptionsChanged(terminal);
}
public onBlur(terminal: Terminal): void {
if (this._cursorBlinkStateManager) {
this._cursorBlinkStateManager.pause();
}
this._cursorBlinkStateManager?.pause();
this._onRequestRefreshRowsEvent.fire({ start: terminal.buffer.active.cursorY, end: terminal.buffer.active.cursorY });
}
public onFocus(terminal: Terminal): void {
if (this._cursorBlinkStateManager) {
this._cursorBlinkStateManager.resume(terminal);
} else {
this._onRequestRefreshRowsEvent.fire({ start: terminal.buffer.active.cursorY, end: terminal.buffer.active.cursorY });
}
this._cursorBlinkStateManager?.resume(terminal);
this._onRequestRefreshRowsEvent.fire({ start: terminal.buffer.active.cursorY, end: terminal.buffer.active.cursorY });
}
public onOptionsChanged(terminal: Terminal): void {
@@ -105,9 +99,7 @@ export class CursorRenderLayer extends BaseRenderLayer {
}
public onCursorMove(terminal: Terminal): void {
if (this._cursorBlinkStateManager) {
this._cursorBlinkStateManager.restartBlinkAnimation(terminal);
}
this._cursorBlinkStateManager?.restartBlinkAnimation(terminal);
}
public onGridChanged(terminal: Terminal, startRow: number, endRow: number): void {
@@ -302,6 +294,7 @@ class CursorBlinkStateManager {
// Clear any existing interval
if (this._blinkInterval) {
window.clearInterval(this._blinkInterval);
this._blinkInterval = undefined;
}
// Setup the initial timeout which will hide the cursor, this is done before
+1 -1
View File
@@ -147,7 +147,7 @@ jobs:
- Windows_IntegrationTests
condition: and(succeeded(), or(eq(variables['Build.SourceBranch'], 'refs/heads/master'), eq(variables['FORCE_RELEASE'], 'true')))
pool:
vmImage: 'ubuntu-16.04'
vmImage: 'ubuntu-18.04'
steps:
- task: NodeTool@0
inputs:
+56 -12
View File
@@ -148,6 +148,7 @@ if (document.location.pathname === '/test') {
document.getElementById('dispose').addEventListener('click', disposeRecreateButtonHandler);
document.getElementById('serialize').addEventListener('click', serializeButtonHandler);
document.getElementById('custom-glyph').addEventListener('click', writeCustomGlyphHandler);
document.getElementById('load-test').addEventListener('click', loadTest);
}
function createTerminal(): void {
@@ -292,11 +293,11 @@ function initOptions(term: TerminalType): void {
rendererType: ['dom', 'canvas'],
wordSeparator: null
};
const options = Object.keys((<any>term)._core.options);
const options = Object.keys(term.options);
const booleanOptions = [];
const numberOptions = [];
options.filter(o => blacklistedOptions.indexOf(o) === -1).forEach(o => {
switch (typeof term.getOption(o)) {
switch (typeof term.options[o]) {
case 'boolean':
booleanOptions.push(o);
break;
@@ -313,18 +314,18 @@ function initOptions(term: TerminalType): void {
let html = '';
html += '<div class="option-group">';
booleanOptions.forEach(o => {
html += `<div class="option"><label><input id="opt-${o}" type="checkbox" ${term.getOption(o) ? 'checked' : ''}/> ${o}</label></div>`;
html += `<div class="option"><label><input id="opt-${o}" type="checkbox" ${term.options[o] ? 'checked' : ''}/> ${o}</label></div>`;
});
html += '</div><div class="option-group">';
numberOptions.forEach(o => {
html += `<div class="option"><label>${o} <input id="opt-${o}" type="number" value="${term.getOption(o)}" step="${o === 'lineHeight' || o === 'scrollSensitivity' ? '0.1' : '1'}"/></label></div>`;
html += `<div class="option"><label>${o} <input id="opt-${o}" type="number" value="${term.options[o]}" step="${o === 'lineHeight' || o === 'scrollSensitivity' ? '0.1' : '1'}"/></label></div>`;
});
html += '</div><div class="option-group">';
Object.keys(stringOptions).forEach(o => {
if (stringOptions[o]) {
html += `<div class="option"><label>${o} <select id="opt-${o}">${stringOptions[o].map(v => `<option ${term.getOption(o) === v ? 'selected' : ''}>${v}</option>`).join('')}</select></label></div>`;
html += `<div class="option"><label>${o} <select id="opt-${o}">${stringOptions[o].map(v => `<option ${term.options[o] === v ? 'selected' : ''}>${v}</option>`).join('')}</select></label></div>`;
} else {
html += `<div class="option"><label>${o} <input id="opt-${o}" type="text" value="${term.getOption(o)}"/></label></div>`;
html += `<div class="option"><label>${o} <input id="opt-${o}" type="text" value="${term.options[o]}"/></label></div>`;
}
});
html += '</div>';
@@ -337,7 +338,7 @@ function initOptions(term: TerminalType): void {
const input = <HTMLInputElement>document.getElementById(`opt-${o}`);
addDomListener(input, 'change', () => {
console.log('change', o, input.checked);
term.setOption(o, input.checked);
term.options[o] = input.checked;
});
});
numberOptions.forEach(o => {
@@ -346,14 +347,17 @@ function initOptions(term: TerminalType): void {
console.log('change', o, input.value);
if (o === 'cols' || o === 'rows') {
updateTerminalSize();
} else if (o === 'lineHeight' || o === 'scrollSensitivity') {
term.setOption(o, parseFloat(input.value));
} else if (o === 'lineHeight') {
term.options.lineHeight = parseFloat(input.value);
updateTerminalSize();
} else if (o === 'scrollSensitivity') {
term.options.scrollSensitivity = parseFloat(input.value);
updateTerminalSize();
} else if(o === 'scrollback') {
term.setOption(o, parseInt(input.value));
term.options.scrollback = parseInt(input.value);
setTimeout(() => updateTerminalSize(), 5);
} else {
term.setOption(o, parseInt(input.value));
term.options[o] = parseInt(input.value);
}
});
});
@@ -361,7 +365,7 @@ function initOptions(term: TerminalType): void {
const input = <HTMLInputElement>document.getElementById(`opt-${o}`);
addDomListener(input, 'change', () => {
console.log('change', o, input.value);
term.setOption(o, input.value);
term.options[o] = input.value;
});
});
}
@@ -376,6 +380,9 @@ function initAddons(term: TerminalType): void {
if (!addon.canChange) {
checkbox.disabled = true;
}
if(name === 'unicode11' && checkbox.checked) {
term.unicode.activeVersion = '11';
}
addDomListener(checkbox, 'change', () => {
if (checkbox.checked) {
addon.instance = new addon.ctor();
@@ -384,10 +391,14 @@ function initAddons(term: TerminalType): void {
setTimeout(() => {
document.body.appendChild((addon.instance as WebglAddon).textureAtlas);
}, 0);
} else if (name === 'unicode11') {
term.unicode.activeVersion = '11';
}
} else {
if (name === 'webgl') {
document.body.removeChild((addon.instance as WebglAddon).textureAtlas);
} else if (name === 'unicode11') {
term.unicode.activeVersion = '6';
}
addon.instance!.dispose();
addon.instance = undefined;
@@ -481,3 +492,36 @@ function writeCustomGlyphHandler() {
term.write(' ╚══╩══╝ └──┴──┘ ╰──┴──╯ ╰──┴──╯ ┗━━┻━━┛ └╌╌┘ ╎ ┗╍╍┛ ┋ ▁▂▃▄▅▆▇█\n\r');
window.scrollTo(0, 0);
}
function loadTest() {
const isWebglEnabled = !!addons.webgl.instance;
const testData = [];
let byteCount = 0;
for (let i = 0; i < 50; i++) {
const count = 1 + Math.floor(Math.random() * 79);
byteCount += count + 2;
const data = new Uint8Array(count + 2);
data[0] = 0x0A; // \n
for (let i = 1; i < count + 1; i++) {
data[i] = 0x61 + Math.floor(Math.random() * (0x7A - 0x61));
}
// End each line with \r so the cursor remains constant, this is what ls/tree do and improves
// performance significantly due to the cursor DOM element not needing to change
data[data.length - 1] = 0x0D; // \r
testData.push(data);
}
const start = performance.now();
for (let i = 0; i < 1024; i++) {
for (const d of testData) {
term.write(d);
}
}
// Wait for all data to be parsed before evaluating time
term.write('', () => {
const time = Math.round(performance.now() - start);
const mbs = ((byteCount / 1024) * (1 / (time / 1000))).toFixed(2);
term.write(`\n\r\nWrote ${byteCount}kB in ${time}ms (${mbs}MB/s) using the (${isWebglEnabled ? 'webgl' : 'canvas'} renderer)`);
// Send ^C to get a new prompt
term._core._onData.fire('\x03');
});
}
+1
View File
@@ -63,6 +63,7 @@
<div style="display: inline-block; margin-right: 16px;">
<button id="dispose" title="This is used to testing memory leaks">Dispose terminal</button>
<button id="custom-glyph" title="Write custom box drawing and block element characters to the terminal">Test custom glyphs</button>
<button id="load-test" title="Write several MB of data to simulate a lot of data coming from the process">Load test</button>
</div>
</div>
</div>
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "xterm",
"description": "Full xterm terminal, in your browser",
"version": "4.13.0",
"version": "4.14.1",
"main": "lib/xterm.js",
"style": "css/xterm.css",
"types": "typings/xterm.d.ts",
+1
View File
@@ -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');
+71 -73
View File
@@ -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', () => {
+40 -27
View File
@@ -152,7 +152,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
* @alias module:xterm/src/xterm
*/
constructor(
options: ITerminalOptions = {}
options: Partial<ITerminalOptions> = {}
) {
super(options);
@@ -164,6 +164,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
// Setup InputHandler listeners
this.register(this._inputHandler.onRequestBell(() => this.bell()));
this.register(this._inputHandler.onRequestRefreshRows((start, end) => this.refresh(start, end)));
this.register(this._inputHandler.onRequestSendFocus(() => this._reportFocus()));
this.register(this._inputHandler.onRequestReset(() => this.reset()));
this.register(this._inputHandler.onRequestWindowsOptionsReport(type => this._reportWindowsOptions(type)));
this.register(this._inputHandler.onAnsiColorChange((event) => this._changeAnsiColor(event)));
@@ -493,7 +494,8 @@ export class Terminal extends CoreTerminal implements ITerminal {
this.viewport = this._instantiationService.createInstance(Viewport,
(amount: number) => this.scrollLines(amount, true, ScrollSource.VIEWPORT),
this._viewportElement,
this._viewportScrollArea
this._viewportScrollArea,
this.element
);
this.viewport.onThemeChange(this._colorManager.colors);
this.register(this._inputHandler.onRequestSyncScrollBar(() => this.viewport!.syncScrollArea()));
@@ -698,8 +700,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
},
wheel: (ev: WheelEvent) => {
sendEvent(ev);
ev.preventDefault();
return this.cancel(ev);
return this.cancel(ev, true);
},
mousedrag: (ev: MouseEvent) => {
// deal only with move while a button is held
@@ -794,33 +795,31 @@ export class Terminal extends CoreTerminal implements ITerminal {
}));
this.register(addDisposableDomListener(el, 'wheel', (ev: WheelEvent) => {
if (!requestedEvents.wheel) {
// do nothing, if app side handles wheel itself
if (requestedEvents.wheel) return;
if (!this.buffer.hasScrollback) {
// Convert wheel events into up/down events when the buffer does not have scrollback, this
// enables scrolling in apps hosted in the alt buffer such as vim or tmux.
if (!this.buffer.hasScrollback) {
const amount = this.viewport!.getLinesScrolled(ev);
const amount = this.viewport!.getLinesScrolled(ev);
// Do nothing if there's no vertical scroll
if (amount === 0) {
return;
}
// Construct and send sequences
const sequence = C0.ESC + (this.coreService.decPrivateModes.applicationCursorKeys ? 'O' : '[') + (ev.deltaY < 0 ? 'A' : 'B');
let data = '';
for (let i = 0; i < Math.abs(amount); i++) {
data += sequence;
}
this.coreService.triggerDataEvent(data, true);
// Do nothing if there's no vertical scroll
if (amount === 0) {
return;
}
return;
}
}, { passive: true }));
// allow wheel scrolling in
// the shell for example
this.register(addDisposableDomListener(el, 'wheel', (ev: WheelEvent) => {
if (requestedEvents.wheel) return;
// Construct and send sequences
const sequence = C0.ESC + (this.coreService.decPrivateModes.applicationCursorKeys ? 'O' : '[') + (ev.deltaY < 0 ? 'A' : 'B');
let data = '';
for (let i = 0; i < Math.abs(amount); i++) {
data += sequence;
}
this.coreService.triggerDataEvent(data, true);
return this.cancel(ev, true);
}
// normal viewport scrolling
// conditionally stop event, if the viewport still had rows to scroll within
if (!this.viewport!.onWheel(ev)) {
return this.cancel(ev);
}
@@ -1180,7 +1179,9 @@ export class Terminal extends CoreTerminal implements ITerminal {
* @param ev The input event to be handled.
*/
protected _inputEvent(ev: InputEvent): boolean {
if (ev.data && ev.inputType === 'insertText') {
// 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;
}
@@ -1290,6 +1291,18 @@ 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');
} else {
this.coreService.triggerDataEvent(C0.ESC + '[O');
}
}
private _reportWindowsOptions(type: WindowsOptionsReportType): void {
if (!this._renderService) {
return;
+6
View File
@@ -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.');
}
+1
View File
@@ -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;
}
+18 -10
View File
@@ -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,12 +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
@@ -37,6 +42,7 @@ export class Viewport extends Disposable implements IViewport {
private readonly _scrollLines: (amount: number) => void,
private readonly _viewportElement: HTMLElement,
private readonly _scrollArea: HTMLElement,
private readonly _element: HTMLElement,
@IBufferService private readonly _bufferService: IBufferService,
@IOptionsService private readonly _optionsService: IOptionsService,
@ICharSizeService private readonly _charSizeService: ICharSizeService,
@@ -51,6 +57,12 @@ export class Viewport extends Disposable implements IViewport {
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);
}
@@ -79,6 +91,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) {
@@ -104,7 +117,9 @@ export class Viewport extends Disposable implements IViewport {
}
this._lastHadScrollBar = this.scrollBarWidth > 0;
this._viewportElement.style.width = (this._renderService.dimensions.actualCellWidth * (this._bufferService.cols) + this.scrollBarWidth).toString() + 'px';
const elementStyle = window.getComputedStyle(this._element);
const elementPadding = parseInt(elementStyle.paddingLeft) + parseInt(elementStyle.paddingRight);
this._viewportElement.style.width = (this._renderService.dimensions.actualCellWidth * (this._bufferService.cols) + this.scrollBarWidth + (this._lastHadScrollBar ? elementPadding : 0)).toString() + 'px';
this._refreshAnimationFrame = null;
}
@@ -126,20 +141,13 @@ 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;
}
+6
View File
@@ -89,6 +89,9 @@ export class Terminal implements ITerminalApi {
wraparoundMode: m.wraparound
};
}
public get options(): ITerminalOptions {
return this._core.options;
}
public blur(): void {
this._core.blur();
}
@@ -222,6 +225,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);
}
+4
View File
@@ -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.
+7 -17
View File
@@ -55,7 +55,6 @@ export class CursorRenderLayer extends BaseRenderLayer {
'block': this._renderBlockCursor.bind(this),
'underline': this._renderUnderlineCursor.bind(this)
};
// TODO: Consider initial options? Maybe onOptionsChanged should be called at the end of open?
}
public dispose(): void {
@@ -80,26 +79,18 @@ export class CursorRenderLayer extends BaseRenderLayer {
public reset(): void {
this._clearCursor();
if (this._cursorBlinkStateManager) {
this._cursorBlinkStateManager.dispose();
this._cursorBlinkStateManager = undefined;
this.onOptionsChanged();
}
this._cursorBlinkStateManager?.restartBlinkAnimation();
this.onOptionsChanged();
}
public onBlur(): void {
if (this._cursorBlinkStateManager) {
this._cursorBlinkStateManager.pause();
}
this._cursorBlinkStateManager?.pause();
this._onRequestRedraw.fire({ start: this._bufferService.buffer.y, end: this._bufferService.buffer.y });
}
public onFocus(): void {
if (this._cursorBlinkStateManager) {
this._cursorBlinkStateManager.resume();
} else {
this._onRequestRedraw.fire({ start: this._bufferService.buffer.y, end: this._bufferService.buffer.y });
}
this._cursorBlinkStateManager?.resume();
this._onRequestRedraw.fire({ start: this._bufferService.buffer.y, end: this._bufferService.buffer.y });
}
public onOptionsChanged(): void {
@@ -119,9 +110,7 @@ export class CursorRenderLayer extends BaseRenderLayer {
}
public onCursorMove(): void {
if (this._cursorBlinkStateManager) {
this._cursorBlinkStateManager.restartBlinkAnimation();
}
this._cursorBlinkStateManager?.restartBlinkAnimation();
}
public onGridChanged(startRow: number, endRow: number): void {
@@ -313,6 +302,7 @@ class CursorBlinkStateManager {
// Clear any existing interval
if (this._blinkInterval) {
window.clearInterval(this._blinkInterval);
this._blinkInterval = undefined;
}
// Setup the initial timeout which will hide the cursor, this is done before
+6
View File
@@ -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.
*/
+6
View File
@@ -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,
@@ -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) {
+5
View File
@@ -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();
+1
View File
@@ -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;
+2 -1
View File
@@ -86,9 +86,10 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal {
public get cols(): number { return this._bufferService.cols; }
public get rows(): number { return this._bufferService.rows; }
public get buffers(): IBufferSet { return this._bufferService.buffers; }
public get options(): ITerminalOptions { return this.optionsService.publicOptions; }
constructor(
options: ITerminalOptions
options: Partial<ITerminalOptions>
) {
super();
+41 -41
View File
@@ -77,54 +77,54 @@ describe('InputHandler', () => {
optionsService.options.scrollback = 1;
bufferService.reset();
});
it('SL (scrollLeft)', () => {
inputHandler.parseP('12345'.repeat(6));
inputHandler.parseP('\x1b[ @');
it('SL (scrollLeft)', async () => {
await inputHandler.parseP('12345'.repeat(6));
await inputHandler.parseP('\x1b[ @');
assert.deepEqual(getLines(bufferService, 6), ['12345', '2345', '2345', '2345', '2345', '2345']);
inputHandler.parseP('\x1b[0 @');
await inputHandler.parseP('\x1b[0 @');
assert.deepEqual(getLines(bufferService, 6), ['12345', '345', '345', '345', '345', '345']);
inputHandler.parseP('\x1b[2 @');
await inputHandler.parseP('\x1b[2 @');
assert.deepEqual(getLines(bufferService, 6), ['12345', '5', '5', '5', '5', '5']);
});
it('SR (scrollRight)', () => {
inputHandler.parseP('12345'.repeat(6));
inputHandler.parseP('\x1b[ A');
it('SR (scrollRight)', async () => {
await inputHandler.parseP('12345'.repeat(6));
await inputHandler.parseP('\x1b[ A');
assert.deepEqual(getLines(bufferService, 6), ['12345', ' 1234', ' 1234', ' 1234', ' 1234', ' 1234']);
inputHandler.parseP('\x1b[0 A');
await inputHandler.parseP('\x1b[0 A');
assert.deepEqual(getLines(bufferService, 6), ['12345', ' 123', ' 123', ' 123', ' 123', ' 123']);
inputHandler.parseP('\x1b[2 A');
await inputHandler.parseP('\x1b[2 A');
assert.deepEqual(getLines(bufferService, 6), ['12345', ' 1', ' 1', ' 1', ' 1', ' 1']);
});
it('insertColumns (DECIC)', () => {
inputHandler.parseP('12345'.repeat(6));
inputHandler.parseP('\x1b[3;3H');
inputHandler.parseP('\x1b[\'}');
it('insertColumns (DECIC)', async () => {
await inputHandler.parseP('12345'.repeat(6));
await inputHandler.parseP('\x1b[3;3H');
await inputHandler.parseP('\x1b[\'}');
assert.deepEqual(getLines(bufferService, 6), ['12345', '12 34', '12 34', '12 34', '12 34', '12 34']);
bufferService.reset();
inputHandler.parseP('12345'.repeat(6));
inputHandler.parseP('\x1b[3;3H');
inputHandler.parseP('\x1b[1\'}');
await inputHandler.parseP('12345'.repeat(6));
await inputHandler.parseP('\x1b[3;3H');
await inputHandler.parseP('\x1b[1\'}');
assert.deepEqual(getLines(bufferService, 6), ['12345', '12 34', '12 34', '12 34', '12 34', '12 34']);
bufferService.reset();
inputHandler.parseP('12345'.repeat(6));
inputHandler.parseP('\x1b[3;3H');
inputHandler.parseP('\x1b[2\'}');
await inputHandler.parseP('12345'.repeat(6));
await inputHandler.parseP('\x1b[3;3H');
await inputHandler.parseP('\x1b[2\'}');
assert.deepEqual(getLines(bufferService, 6), ['12345', '12 3', '12 3', '12 3', '12 3', '12 3']);
});
it('deleteColumns (DECDC)', () => {
inputHandler.parseP('12345'.repeat(6));
inputHandler.parseP('\x1b[3;3H');
inputHandler.parseP('\x1b[\'~');
it('deleteColumns (DECDC)', async () => {
await inputHandler.parseP('12345'.repeat(6));
await inputHandler.parseP('\x1b[3;3H');
await inputHandler.parseP('\x1b[\'~');
assert.deepEqual(getLines(bufferService, 6), ['12345', '1245', '1245', '1245', '1245', '1245']);
bufferService.reset();
inputHandler.parseP('12345'.repeat(6));
inputHandler.parseP('\x1b[3;3H');
inputHandler.parseP('\x1b[1\'~');
await inputHandler.parseP('12345'.repeat(6));
await inputHandler.parseP('\x1b[3;3H');
await inputHandler.parseP('\x1b[1\'~');
assert.deepEqual(getLines(bufferService, 6), ['12345', '1245', '1245', '1245', '1245', '1245']);
bufferService.reset();
inputHandler.parseP('12345'.repeat(6));
inputHandler.parseP('\x1b[3;3H');
inputHandler.parseP('\x1b[2\'~');
await inputHandler.parseP('12345'.repeat(6));
await inputHandler.parseP('\x1b[3;3H');
await inputHandler.parseP('\x1b[2\'~');
assert.deepEqual(getLines(bufferService, 6), ['12345', '125', '125', '125', '125', '125']);
});
});
@@ -137,41 +137,41 @@ describe('InputHandler', () => {
bufferService.reset();
});
describe('reverseWraparound set', () => {
it('should not reverse outside of scroll margins', () => {
it('should not reverse outside of scroll margins', async () => {
// prepare buffer content
inputHandler.parseP('#####abcdefghijklmnopqrstuvwxy');
await inputHandler.parseP('#####abcdefghijklmnopqrstuvwxy');
assert.deepEqual(getLines(bufferService, 6), ['#####', 'abcde', 'fghij', 'klmno', 'pqrst', 'uvwxy']);
assert.equal(bufferService.buffers.active.ydisp, 1);
assert.equal(bufferService.buffers.active.x, 5);
assert.equal(bufferService.buffers.active.y, 4);
inputHandler.parseP(ttyBS.repeat(100));
await inputHandler.parseP(ttyBS.repeat(100));
assert.deepEqual(getLines(bufferService, 6), ['#####', 'abcde', 'fghij', 'klmno', 'pqrst', ' y']);
inputHandler.parseP('\x1b[?45h');
inputHandler.parseP('uvwxy');
await inputHandler.parseP('\x1b[?45h');
await inputHandler.parseP('uvwxy');
// set top/bottom to 1/3 (0-based)
inputHandler.parseP('\x1b[2;4r');
await inputHandler.parseP('\x1b[2;4r');
// place cursor below scroll bottom
bufferService.buffers.active.x = 5;
bufferService.buffers.active.y = 4;
inputHandler.parseP(ttyBS.repeat(100));
await inputHandler.parseP(ttyBS.repeat(100));
assert.deepEqual(getLines(bufferService, 6), ['#####', 'abcde', 'fghij', 'klmno', 'pqrst', ' ']);
inputHandler.parseP('uvwxy');
await inputHandler.parseP('uvwxy');
// place cursor within scroll margins
bufferService.buffers.active.x = 5;
bufferService.buffers.active.y = 3;
inputHandler.parseP(ttyBS.repeat(100));
await inputHandler.parseP(ttyBS.repeat(100));
assert.deepEqual(getLines(bufferService, 6), ['#####', 'abcde', ' ', ' ', ' ', 'uvwxy']);
assert.equal(bufferService.buffers.active.x, 0);
assert.equal(bufferService.buffers.active.y, bufferService.buffers.active.scrollTop); // stops at 0, scrollTop
inputHandler.parseP('fghijklmnopqrst');
await inputHandler.parseP('fghijklmnopqrst');
// place cursor above scroll top
bufferService.buffers.active.x = 5;
bufferService.buffers.active.y = 0;
inputHandler.parseP(ttyBS.repeat(100));
await inputHandler.parseP(ttyBS.repeat(100));
assert.deepEqual(getLines(bufferService, 6), ['#####', ' ', 'fghij', 'klmno', 'pqrst', 'uvwxy']);
});
});
+209 -229
View File
@@ -20,6 +20,7 @@ import { AttributeData } from 'common/buffer/AttributeData';
import { ICoreService, IBufferService, IOptionsService, ILogService, IDirtyRowService, ICoreMouseService, ICharsetService, IUnicodeService, LogLevelEnum } from 'common/services/Services';
import { OscHandler } from 'common/parser/OscParser';
import { DcsHandler } from 'common/parser/DcsParser';
import { IBuffer } from 'common/buffer/Types';
/**
* Map collect to glevel. Used in `selectCharset`.
@@ -234,12 +235,16 @@ export class InputHandler extends Disposable implements IInputHandler {
private _curAttrData: IAttributeData = DEFAULT_ATTR_DATA.clone();
private _eraseAttrDataInternal: IAttributeData = DEFAULT_ATTR_DATA.clone();
private _activeBuffer: IBuffer;
private _onRequestBell = new EventEmitter<void>();
public get onRequestBell(): IEvent<void> { return this._onRequestBell.event; }
private _onRequestRefreshRows = new EventEmitter<number, number>();
public get onRequestRefreshRows(): IEvent<number, number> { return this._onRequestRefreshRows.event; }
private _onRequestReset = new EventEmitter<void>();
public get onRequestReset(): IEvent<void> { return this._onRequestReset.event; }
private _onRequestSendFocus = new EventEmitter<void>();
public get onRequestSendFocus(): IEvent<void> { return this._onRequestSendFocus.event; }
private _onRequestSyncScrollBar = new EventEmitter<void>();
public get onRequestSyncScrollBar(): IEvent<void> { return this._onRequestSyncScrollBar.event; }
private _onRequestWindowsOptionsReport = new EventEmitter<WindowsOptionsReportType>();
@@ -282,6 +287,10 @@ export class InputHandler extends Disposable implements IInputHandler {
super();
this.register(this._parser);
// 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));
/**
* custom fallback handlers
*/
@@ -508,9 +517,8 @@ export class InputHandler extends Disposable implements IInputHandler {
*/
public parse(data: string | Uint8Array, promiseResult?: boolean): void | Promise<boolean> {
let result: void | Promise<boolean>;
let buffer = this._bufferService.buffer;
let cursorStartX = buffer.x;
let cursorStartY = buffer.y;
let cursorStartX = this._activeBuffer.x;
let cursorStartY = this._activeBuffer.y;
let start = 0;
const wasPaused = this._parseStack.paused;
@@ -569,8 +577,7 @@ export class InputHandler extends Disposable implements IInputHandler {
}
}
buffer = this._bufferService.buffer;
if (buffer.x !== cursorStartX || buffer.y !== cursorStartY) {
if (this._activeBuffer.x !== cursorStartX || this._activeBuffer.y !== cursorStartY) {
this._onCursorMove.fire();
}
@@ -581,20 +588,19 @@ export class InputHandler extends Disposable implements IInputHandler {
public print(data: Uint32Array, start: number, end: number): void {
let code: number;
let chWidth: number;
const buffer = this._bufferService.buffer;
const charset = this._charsetService.charset;
const screenReaderMode = this._optionsService.options.screenReaderMode;
const cols = this._bufferService.cols;
const wraparoundMode = this._coreService.decPrivateModes.wraparound;
const insertMode = this._coreService.modes.insertMode;
const curAttr = this._curAttrData;
let bufferRow = buffer.lines.get(buffer.ybase + buffer.y)!;
let bufferRow = this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y)!;
this._dirtyRowService.markDirty(buffer.y);
this._dirtyRowService.markDirty(this._activeBuffer.y);
// handle wide chars: reset start_cell-1 if we would overwrite the second cell of a wide char
if (buffer.x && end - start > 0 && bufferRow.getWidth(buffer.x - 1) === 2) {
bufferRow.setCellFromCodePoint(buffer.x - 1, 0, 1, curAttr.fg, curAttr.bg, curAttr.extended);
if (this._activeBuffer.x && end - start > 0 && bufferRow.getWidth(this._activeBuffer.x - 1) === 2) {
bufferRow.setCellFromCodePoint(this._activeBuffer.x - 1, 0, 1, curAttr.fg, curAttr.bg, curAttr.extended);
}
for (let pos = start; pos < end; ++pos) {
@@ -619,17 +625,17 @@ export class InputHandler extends Disposable implements IInputHandler {
}
// insert combining char at last cursor position
// buffer.x should never be 0 for a combining char
// this._activeBuffer.x should never be 0 for a combining char
// since they always follow a cell consuming char
// therefore we can test for buffer.x to avoid overflow left
if (!chWidth && buffer.x) {
if (!bufferRow.getWidth(buffer.x - 1)) {
// therefore we can test for this._activeBuffer.x to avoid overflow left
if (!chWidth && this._activeBuffer.x) {
if (!bufferRow.getWidth(this._activeBuffer.x - 1)) {
// found empty cell after fullwidth, need to go 2 cells back
// it is save to step 2 cells back here
// since an empty cell is only set by fullwidth chars
bufferRow.addCodepointToCell(buffer.x - 2, code);
bufferRow.addCodepointToCell(this._activeBuffer.x - 2, code);
} else {
bufferRow.addCodepointToCell(buffer.x - 1, code);
bufferRow.addCodepointToCell(this._activeBuffer.x - 1, code);
}
continue;
}
@@ -637,31 +643,31 @@ export class InputHandler extends Disposable implements IInputHandler {
// goto next line if ch would overflow
// NOTE: To avoid costly width checks here,
// the terminal does not allow a cols < 2.
if (buffer.x + chWidth - 1 >= cols) {
if (this._activeBuffer.x + chWidth - 1 >= cols) {
// autowrap - DECAWM
// automatically wraps to the beginning of the next line
if (wraparoundMode) {
// clear left over cells to the right
while (buffer.x < cols) {
bufferRow.setCellFromCodePoint(buffer.x++, 0, 1, curAttr.fg, curAttr.bg, curAttr.extended);
while (this._activeBuffer.x < cols) {
bufferRow.setCellFromCodePoint(this._activeBuffer.x++, 0, 1, curAttr.fg, curAttr.bg, curAttr.extended);
}
buffer.x = 0;
buffer.y++;
if (buffer.y === buffer.scrollBottom + 1) {
buffer.y--;
this._activeBuffer.x = 0;
this._activeBuffer.y++;
if (this._activeBuffer.y === this._activeBuffer.scrollBottom + 1) {
this._activeBuffer.y--;
this._bufferService.scroll(this._eraseAttrData(), true);
} else {
if (buffer.y >= this._bufferService.rows) {
buffer.y = this._bufferService.rows - 1;
if (this._activeBuffer.y >= this._bufferService.rows) {
this._activeBuffer.y = this._bufferService.rows - 1;
}
// The line already exists (eg. the initial viewport), mark it as a
// wrapped line
buffer.lines.get(buffer.ybase + buffer.y)!.isWrapped = true;
this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y)!.isWrapped = true;
}
// row changed, get it again
bufferRow = buffer.lines.get(buffer.ybase + buffer.y)!;
bufferRow = this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y)!;
} else {
buffer.x = cols - 1;
this._activeBuffer.x = cols - 1;
if (chWidth === 2) {
// FIXME: check for xterm behavior
// What to do here? We got a wide char that does not fit into last cell
@@ -673,7 +679,7 @@ export class InputHandler extends Disposable implements IInputHandler {
// insert mode: move characters to right
if (insertMode) {
// right shift cells according to the width
bufferRow.insertCells(buffer.x, chWidth, buffer.getNullCell(curAttr), curAttr);
bufferRow.insertCells(this._activeBuffer.x, chWidth, this._activeBuffer.getNullCell(curAttr), curAttr);
// test last cell - since the last cell has only room for
// a halfwidth char any fullwidth shifted there is lost
// and will be set to empty cell
@@ -683,15 +689,15 @@ export class InputHandler extends Disposable implements IInputHandler {
}
// write current char to buffer and advance cursor
bufferRow.setCellFromCodePoint(buffer.x++, code, chWidth, curAttr.fg, curAttr.bg, curAttr.extended);
bufferRow.setCellFromCodePoint(this._activeBuffer.x++, code, chWidth, curAttr.fg, curAttr.bg, curAttr.extended);
// fullwidth char - also set next cell to placeholder stub and advance cursor
// for graphemes bigger than fullwidth we can simply loop to zero
// we already made sure above, that buffer.x + chWidth will not overflow right
// we already made sure above, that this._activeBuffer.x + chWidth will not overflow right
if (chWidth > 0) {
while (--chWidth) {
// other than a regular empty cell a cell following a wide char has no width
bufferRow.setCellFromCodePoint(buffer.x++, 0, 0, curAttr.fg, curAttr.bg, curAttr.extended);
bufferRow.setCellFromCodePoint(this._activeBuffer.x++, 0, 0, curAttr.fg, curAttr.bg, curAttr.extended);
}
}
}
@@ -700,7 +706,7 @@ export class InputHandler extends Disposable implements IInputHandler {
// - fullwidth + surrogates: reset
// - combining: only base char gets carried on (bug in xterm?)
if (end - start > 0) {
bufferRow.loadCell(buffer.x - 1, this._workCell);
bufferRow.loadCell(this._activeBuffer.x - 1, this._workCell);
if (this._workCell.getWidth() === 2 || this._workCell.getCode() > 0xFFFF) {
this._parser.precedingCodepoint = 0;
} else if (this._workCell.isCombined()) {
@@ -711,11 +717,11 @@ export class InputHandler extends Disposable implements IInputHandler {
}
// handle wide chars: reset cell to the right if it is second cell of a wide char
if (buffer.x < cols && end - start > 0 && bufferRow.getWidth(buffer.x) === 0 && !bufferRow.hasContent(buffer.x)) {
bufferRow.setCellFromCodePoint(buffer.x, 0, 1, curAttr.fg, curAttr.bg, curAttr.extended);
if (this._activeBuffer.x < cols && end - start > 0 && bufferRow.getWidth(this._activeBuffer.x) === 0 && !bufferRow.hasContent(this._activeBuffer.x)) {
bufferRow.setCellFromCodePoint(this._activeBuffer.x, 0, 1, curAttr.fg, curAttr.bg, curAttr.extended);
}
this._dirtyRowService.markDirty(buffer.y);
this._dirtyRowService.markDirty(this._activeBuffer.y);
}
/**
@@ -779,25 +785,22 @@ export class InputHandler extends Disposable implements IInputHandler {
* @vt: #Y C0 FF "Form Feed" "\f, \x0C" "Treated as LF."
*/
public lineFeed(): boolean {
// make buffer local for faster access
const buffer = this._bufferService.buffer;
this._dirtyRowService.markDirty(buffer.y);
this._dirtyRowService.markDirty(this._activeBuffer.y);
if (this._optionsService.options.convertEol) {
buffer.x = 0;
this._activeBuffer.x = 0;
}
buffer.y++;
if (buffer.y === buffer.scrollBottom + 1) {
buffer.y--;
this._activeBuffer.y++;
if (this._activeBuffer.y === this._activeBuffer.scrollBottom + 1) {
this._activeBuffer.y--;
this._bufferService.scroll(this._eraseAttrData());
} else if (buffer.y >= this._bufferService.rows) {
buffer.y = this._bufferService.rows - 1;
} else if (this._activeBuffer.y >= this._bufferService.rows) {
this._activeBuffer.y = this._bufferService.rows - 1;
}
// If the end of the line is hit, prevent this action from wrapping around to the next line.
if (buffer.x >= this._bufferService.cols) {
buffer.x--;
if (this._activeBuffer.x >= this._bufferService.cols) {
this._activeBuffer.x--;
}
this._dirtyRowService.markDirty(buffer.y);
this._dirtyRowService.markDirty(this._activeBuffer.y);
this._onLineFeed.fire();
return true;
@@ -810,7 +813,7 @@ export class InputHandler extends Disposable implements IInputHandler {
* @vt: #Y C0 CR "Carriage Return" "\r, \x0D" "Move the cursor to the beginning of the row."
*/
public carriageReturn(): boolean {
this._bufferService.buffer.x = 0;
this._activeBuffer.x = 0;
return true;
}
@@ -826,13 +829,11 @@ export class InputHandler extends Disposable implements IInputHandler {
* with the cursor, thus at the home position (top-leftmost cell) this has no effect.
*/
public backspace(): boolean {
const buffer = this._bufferService.buffer;
// reverse wrap-around is disabled
if (!this._coreService.decPrivateModes.reverseWraparound) {
this._restrictCursor();
if (buffer.x > 0) {
buffer.x--;
if (this._activeBuffer.x > 0) {
this._activeBuffer.x--;
}
return true;
}
@@ -842,8 +843,8 @@ export class InputHandler extends Disposable implements IInputHandler {
// to be at x=cols to be able to address the last cell of a row by BS
this._restrictCursor(this._bufferService.cols);
if (buffer.x > 0) {
buffer.x--;
if (this._activeBuffer.x > 0) {
this._activeBuffer.x--;
} else {
/**
* reverse wrap-around handling:
@@ -853,21 +854,21 @@ export class InputHandler extends Disposable implements IInputHandler {
* - cannot peek into scrollbuffer
* - any cursor movement sequence keeps working as expected
*/
if (buffer.x === 0
&& buffer.y > buffer.scrollTop
&& buffer.y <= buffer.scrollBottom
&& buffer.lines.get(buffer.ybase + buffer.y)?.isWrapped)
if (this._activeBuffer.x === 0
&& this._activeBuffer.y > this._activeBuffer.scrollTop
&& this._activeBuffer.y <= this._activeBuffer.scrollBottom
&& this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y)?.isWrapped)
{
buffer.lines.get(buffer.ybase + buffer.y)!.isWrapped = false;
buffer.y--;
buffer.x = this._bufferService.cols - 1;
this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y)!.isWrapped = false;
this._activeBuffer.y--;
this._activeBuffer.x = this._bufferService.cols - 1;
// find last taken cell - last cell can have 3 different states:
// - hasContent(true) + hasWidth(1): narrow char - we are done
// - hasWidth(0): second part of wide char - we are done
// - hasContent(false) + hasWidth(1): empty cell due to early wrapping wide char, go one cell further back
const line = buffer.lines.get(buffer.ybase + buffer.y)!;
if (line.hasWidth(buffer.x) && !line.hasContent(buffer.x)) {
buffer.x--;
const line = this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y)!;
if (line.hasWidth(this._activeBuffer.x) && !line.hasContent(this._activeBuffer.x)) {
this._activeBuffer.x--;
// We do this only once, since width=1 + hasContent=false currently happens only once before
// early wrapping of a wide char.
// This needs to be fixed once we support graphemes taking more than 2 cells.
@@ -885,13 +886,13 @@ export class InputHandler extends Disposable implements IInputHandler {
* @vt: #Y C0 HT "Horizontal Tabulation" "\t, \x09" "Move the cursor to the next character tab stop."
*/
public tab(): boolean {
if (this._bufferService.buffer.x >= this._bufferService.cols) {
if (this._activeBuffer.x >= this._bufferService.cols) {
return true;
}
const originalX = this._bufferService.buffer.x;
this._bufferService.buffer.x = this._bufferService.buffer.nextStop();
const originalX = this._activeBuffer.x;
this._activeBuffer.x = this._activeBuffer.nextStop();
if (this._optionsService.options.screenReaderMode) {
this._onA11yTab.fire(this._bufferService.buffer.x - originalX);
this._onA11yTab.fire(this._activeBuffer.x - originalX);
}
return true;
}
@@ -924,27 +925,27 @@ export class InputHandler extends Disposable implements IInputHandler {
* Restrict cursor to viewport size / scroll margin (origin mode).
*/
private _restrictCursor(maxCol: number = this._bufferService.cols - 1): void {
this._bufferService.buffer.x = Math.min(maxCol, Math.max(0, this._bufferService.buffer.x));
this._bufferService.buffer.y = this._coreService.decPrivateModes.origin
? Math.min(this._bufferService.buffer.scrollBottom, Math.max(this._bufferService.buffer.scrollTop, this._bufferService.buffer.y))
: Math.min(this._bufferService.rows - 1, Math.max(0, this._bufferService.buffer.y));
this._dirtyRowService.markDirty(this._bufferService.buffer.y);
this._activeBuffer.x = Math.min(maxCol, Math.max(0, this._activeBuffer.x));
this._activeBuffer.y = this._coreService.decPrivateModes.origin
? Math.min(this._activeBuffer.scrollBottom, Math.max(this._activeBuffer.scrollTop, this._activeBuffer.y))
: Math.min(this._bufferService.rows - 1, Math.max(0, this._activeBuffer.y));
this._dirtyRowService.markDirty(this._activeBuffer.y);
}
/**
* Set absolute cursor position.
*/
private _setCursor(x: number, y: number): void {
this._dirtyRowService.markDirty(this._bufferService.buffer.y);
this._dirtyRowService.markDirty(this._activeBuffer.y);
if (this._coreService.decPrivateModes.origin) {
this._bufferService.buffer.x = x;
this._bufferService.buffer.y = this._bufferService.buffer.scrollTop + y;
this._activeBuffer.x = x;
this._activeBuffer.y = this._activeBuffer.scrollTop + y;
} else {
this._bufferService.buffer.x = x;
this._bufferService.buffer.y = y;
this._activeBuffer.x = x;
this._activeBuffer.y = y;
}
this._restrictCursor();
this._dirtyRowService.markDirty(this._bufferService.buffer.y);
this._dirtyRowService.markDirty(this._activeBuffer.y);
}
/**
@@ -954,7 +955,7 @@ export class InputHandler extends Disposable implements IInputHandler {
// for relative changes we have to make sure we are within 0 .. cols/rows - 1
// before calculating the new position
this._restrictCursor();
this._setCursor(this._bufferService.buffer.x + x, this._bufferService.buffer.y + y);
this._setCursor(this._activeBuffer.x + x, this._activeBuffer.y + y);
}
/**
@@ -966,7 +967,7 @@ export class InputHandler extends Disposable implements IInputHandler {
*/
public cursorUp(params: IParams): boolean {
// stop at scrollTop
const diffToTop = this._bufferService.buffer.y - this._bufferService.buffer.scrollTop;
const diffToTop = this._activeBuffer.y - this._activeBuffer.scrollTop;
if (diffToTop >= 0) {
this._moveCursor(0, -Math.min(diffToTop, params.params[0] || 1));
} else {
@@ -984,7 +985,7 @@ export class InputHandler extends Disposable implements IInputHandler {
*/
public cursorDown(params: IParams): boolean {
// stop at scrollBottom
const diffToBottom = this._bufferService.buffer.scrollBottom - this._bufferService.buffer.y;
const diffToBottom = this._activeBuffer.scrollBottom - this._activeBuffer.y;
if (diffToBottom >= 0) {
this._moveCursor(0, Math.min(diffToBottom, params.params[0] || 1));
} else {
@@ -1025,7 +1026,7 @@ export class InputHandler extends Disposable implements IInputHandler {
*/
public cursorNextLine(params: IParams): boolean {
this.cursorDown(params);
this._bufferService.buffer.x = 0;
this._activeBuffer.x = 0;
return true;
}
@@ -1039,7 +1040,7 @@ export class InputHandler extends Disposable implements IInputHandler {
*/
public cursorPrecedingLine(params: IParams): boolean {
this.cursorUp(params);
this._bufferService.buffer.x = 0;
this._activeBuffer.x = 0;
return true;
}
@@ -1050,7 +1051,7 @@ export class InputHandler extends Disposable implements IInputHandler {
* @vt: #Y CSI CHA "Cursor Horizontal Absolute" "CSI Ps G" "Move cursor to `Ps`-th column of the active row (default=1)."
*/
public cursorCharAbsolute(params: IParams): boolean {
this._setCursor((params.params[0] || 1) - 1, this._bufferService.buffer.y);
this._setCursor((params.params[0] || 1) - 1, this._activeBuffer.y);
return true;
}
@@ -1081,7 +1082,7 @@ export class InputHandler extends Disposable implements IInputHandler {
* @vt: #Y CSI HPA "Horizontal Position Absolute" "CSI Ps ` " "Same as CHA."
*/
public charPosAbsolute(params: IParams): boolean {
this._setCursor((params.params[0] || 1) - 1, this._bufferService.buffer.y);
this._setCursor((params.params[0] || 1) - 1, this._activeBuffer.y);
return true;
}
@@ -1103,7 +1104,7 @@ export class InputHandler extends Disposable implements IInputHandler {
* @vt: #Y CSI VPA "Vertical Position Absolute" "CSI Ps d" "Move cursor to `Ps`-th row (default=1)."
*/
public linePosAbsolute(params: IParams): boolean {
this._setCursor(this._bufferService.buffer.x, (params.params[0] || 1) - 1);
this._setCursor(this._activeBuffer.x, (params.params[0] || 1) - 1);
return true;
}
@@ -1146,9 +1147,9 @@ export class InputHandler extends Disposable implements IInputHandler {
public tabClear(params: IParams): boolean {
const param = params.params[0];
if (param === 0) {
delete this._bufferService.buffer.tabs[this._bufferService.buffer.x];
delete this._activeBuffer.tabs[this._activeBuffer.x];
} else if (param === 3) {
this._bufferService.buffer.tabs = {};
this._activeBuffer.tabs = {};
}
return true;
}
@@ -1160,12 +1161,12 @@ export class InputHandler extends Disposable implements IInputHandler {
* @vt: #Y CSI CHT "Cursor Horizontal Tabulation" "CSI Ps I" "Move cursor `Ps` times tabs forward (default=1)."
*/
public cursorForwardTab(params: IParams): boolean {
if (this._bufferService.buffer.x >= this._bufferService.cols) {
if (this._activeBuffer.x >= this._bufferService.cols) {
return true;
}
let param = params.params[0] || 1;
while (param--) {
this._bufferService.buffer.x = this._bufferService.buffer.nextStop();
this._activeBuffer.x = this._activeBuffer.nextStop();
}
return true;
}
@@ -1176,16 +1177,13 @@ export class InputHandler extends Disposable implements IInputHandler {
* @vt: #Y CSI CBT "Cursor Backward Tabulation" "CSI Ps Z" "Move cursor `Ps` tabs backward (default=1)."
*/
public cursorBackwardTab(params: IParams): boolean {
if (this._bufferService.buffer.x >= this._bufferService.cols) {
if (this._activeBuffer.x >= this._bufferService.cols) {
return true;
}
let param = params.params[0] || 1;
// make buffer local for faster access
const buffer = this._bufferService.buffer;
while (param--) {
buffer.x = buffer.prevStop();
this._activeBuffer.x = this._activeBuffer.prevStop();
}
return true;
}
@@ -1199,11 +1197,11 @@ export class InputHandler extends Disposable implements IInputHandler {
* @param end end - 1 is last erased cell
*/
private _eraseInBufferLine(y: number, start: number, end: number, clearWrap: boolean = false): void {
const line = this._bufferService.buffer.lines.get(this._bufferService.buffer.ybase + y)!;
const line = this._activeBuffer.lines.get(this._activeBuffer.ybase + y)!;
line.replaceCells(
start,
end,
this._bufferService.buffer.getNullCell(this._eraseAttrData()),
this._activeBuffer.getNullCell(this._eraseAttrData()),
this._eraseAttrData()
);
if (clearWrap) {
@@ -1217,8 +1215,8 @@ export class InputHandler extends Disposable implements IInputHandler {
* @param y row index
*/
private _resetBufferLine(y: number): void {
const line = this._bufferService.buffer.lines.get(this._bufferService.buffer.ybase + y)!;
line.fill(this._bufferService.buffer.getNullCell(this._eraseAttrData()));
const line = this._activeBuffer.lines.get(this._activeBuffer.ybase + y)!;
line.fill(this._activeBuffer.getNullCell(this._eraseAttrData()));
line.isWrapped = false;
}
@@ -1251,22 +1249,22 @@ export class InputHandler extends Disposable implements IInputHandler {
let j;
switch (params.params[0]) {
case 0:
j = this._bufferService.buffer.y;
j = this._activeBuffer.y;
this._dirtyRowService.markDirty(j);
this._eraseInBufferLine(j++, this._bufferService.buffer.x, this._bufferService.cols, this._bufferService.buffer.x === 0);
this._eraseInBufferLine(j++, this._activeBuffer.x, this._bufferService.cols, this._activeBuffer.x === 0);
for (; j < this._bufferService.rows; j++) {
this._resetBufferLine(j);
}
this._dirtyRowService.markDirty(j);
break;
case 1:
j = this._bufferService.buffer.y;
j = this._activeBuffer.y;
this._dirtyRowService.markDirty(j);
// Deleted front part of line and everything before. This line will no longer be wrapped.
this._eraseInBufferLine(j, 0, this._bufferService.buffer.x + 1, true);
if (this._bufferService.buffer.x + 1 >= this._bufferService.cols) {
this._eraseInBufferLine(j, 0, this._activeBuffer.x + 1, true);
if (this._activeBuffer.x + 1 >= this._bufferService.cols) {
// Deleted entire previous line. This next line can no longer be wrapped.
this._bufferService.buffer.lines.get(j + 1)!.isWrapped = false;
this._activeBuffer.lines.get(j + 1)!.isWrapped = false;
}
while (j--) {
this._resetBufferLine(j);
@@ -1283,11 +1281,11 @@ export class InputHandler extends Disposable implements IInputHandler {
break;
case 3:
// Clear scrollback (everything not in viewport)
const scrollBackSize = this._bufferService.buffer.lines.length - this._bufferService.rows;
const scrollBackSize = this._activeBuffer.lines.length - this._bufferService.rows;
if (scrollBackSize > 0) {
this._bufferService.buffer.lines.trimStart(scrollBackSize);
this._bufferService.buffer.ybase = Math.max(this._bufferService.buffer.ybase - scrollBackSize, 0);
this._bufferService.buffer.ydisp = Math.max(this._bufferService.buffer.ydisp - scrollBackSize, 0);
this._activeBuffer.lines.trimStart(scrollBackSize);
this._activeBuffer.ybase = Math.max(this._activeBuffer.ybase - scrollBackSize, 0);
this._activeBuffer.ydisp = Math.max(this._activeBuffer.ydisp - scrollBackSize, 0);
// Force a scroll event to refresh viewport
this._onScroll.fire(0);
}
@@ -1322,16 +1320,16 @@ export class InputHandler extends Disposable implements IInputHandler {
this._restrictCursor(this._bufferService.cols);
switch (params.params[0]) {
case 0:
this._eraseInBufferLine(this._bufferService.buffer.y, this._bufferService.buffer.x, this._bufferService.cols);
this._eraseInBufferLine(this._activeBuffer.y, this._activeBuffer.x, this._bufferService.cols);
break;
case 1:
this._eraseInBufferLine(this._bufferService.buffer.y, 0, this._bufferService.buffer.x + 1);
this._eraseInBufferLine(this._activeBuffer.y, 0, this._activeBuffer.x + 1);
break;
case 2:
this._eraseInBufferLine(this._bufferService.buffer.y, 0, this._bufferService.cols);
this._eraseInBufferLine(this._activeBuffer.y, 0, this._bufferService.cols);
break;
}
this._dirtyRowService.markDirty(this._bufferService.buffer.y);
this._dirtyRowService.markDirty(this._activeBuffer.y);
return true;
}
@@ -1348,26 +1346,23 @@ export class InputHandler extends Disposable implements IInputHandler {
this._restrictCursor();
let param = params.params[0] || 1;
// make buffer local for faster access
const buffer = this._bufferService.buffer;
if (buffer.y > buffer.scrollBottom || buffer.y < buffer.scrollTop) {
if (this._activeBuffer.y > this._activeBuffer.scrollBottom || this._activeBuffer.y < this._activeBuffer.scrollTop) {
return true;
}
const row: number = buffer.ybase + buffer.y;
const row: number = this._activeBuffer.ybase + this._activeBuffer.y;
const scrollBottomRowsOffset = this._bufferService.rows - 1 - buffer.scrollBottom;
const scrollBottomAbsolute = this._bufferService.rows - 1 + buffer.ybase - scrollBottomRowsOffset + 1;
const scrollBottomRowsOffset = this._bufferService.rows - 1 - this._activeBuffer.scrollBottom;
const scrollBottomAbsolute = this._bufferService.rows - 1 + this._activeBuffer.ybase - scrollBottomRowsOffset + 1;
while (param--) {
// test: echo -e '\e[44m\e[1L\e[0m'
// blankLine(true) - xterm/linux behavior
buffer.lines.splice(scrollBottomAbsolute - 1, 1);
buffer.lines.splice(row, 0, buffer.getBlankLine(this._eraseAttrData()));
this._activeBuffer.lines.splice(scrollBottomAbsolute - 1, 1);
this._activeBuffer.lines.splice(row, 0, this._activeBuffer.getBlankLine(this._eraseAttrData()));
}
this._dirtyRowService.markRangeDirty(buffer.y, buffer.scrollBottom);
buffer.x = 0; // see https://vt100.net/docs/vt220-rm/chapter4.html - vt220 only?
this._dirtyRowService.markRangeDirty(this._activeBuffer.y, this._activeBuffer.scrollBottom);
this._activeBuffer.x = 0; // see https://vt100.net/docs/vt220-rm/chapter4.html - vt220 only?
return true;
}
@@ -1384,27 +1379,24 @@ export class InputHandler extends Disposable implements IInputHandler {
this._restrictCursor();
let param = params.params[0] || 1;
// make buffer local for faster access
const buffer = this._bufferService.buffer;
if (buffer.y > buffer.scrollBottom || buffer.y < buffer.scrollTop) {
if (this._activeBuffer.y > this._activeBuffer.scrollBottom || this._activeBuffer.y < this._activeBuffer.scrollTop) {
return true;
}
const row: number = buffer.ybase + buffer.y;
const row: number = this._activeBuffer.ybase + this._activeBuffer.y;
let j: number;
j = this._bufferService.rows - 1 - buffer.scrollBottom;
j = this._bufferService.rows - 1 + buffer.ybase - j;
j = this._bufferService.rows - 1 - this._activeBuffer.scrollBottom;
j = this._bufferService.rows - 1 + this._activeBuffer.ybase - j;
while (param--) {
// test: echo -e '\e[44m\e[1M\e[0m'
// blankLine(true) - xterm/linux behavior
buffer.lines.splice(row, 1);
buffer.lines.splice(j, 0, buffer.getBlankLine(this._eraseAttrData()));
this._activeBuffer.lines.splice(row, 1);
this._activeBuffer.lines.splice(j, 0, this._activeBuffer.getBlankLine(this._eraseAttrData()));
}
this._dirtyRowService.markRangeDirty(buffer.y, buffer.scrollBottom);
buffer.x = 0; // see https://vt100.net/docs/vt220-rm/chapter4.html - vt220 only?
this._dirtyRowService.markRangeDirty(this._activeBuffer.y, this._activeBuffer.scrollBottom);
this._activeBuffer.x = 0; // see https://vt100.net/docs/vt220-rm/chapter4.html - vt220 only?
return true;
}
@@ -1421,15 +1413,15 @@ export class InputHandler extends Disposable implements IInputHandler {
*/
public insertChars(params: IParams): boolean {
this._restrictCursor();
const line = this._bufferService.buffer.lines.get(this._bufferService.buffer.ybase + this._bufferService.buffer.y);
const line = this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y);
if (line) {
line.insertCells(
this._bufferService.buffer.x,
this._activeBuffer.x,
params.params[0] || 1,
this._bufferService.buffer.getNullCell(this._eraseAttrData()),
this._activeBuffer.getNullCell(this._eraseAttrData()),
this._eraseAttrData()
);
this._dirtyRowService.markDirty(this._bufferService.buffer.y);
this._dirtyRowService.markDirty(this._activeBuffer.y);
}
return true;
}
@@ -1447,15 +1439,15 @@ export class InputHandler extends Disposable implements IInputHandler {
*/
public deleteChars(params: IParams): boolean {
this._restrictCursor();
const line = this._bufferService.buffer.lines.get(this._bufferService.buffer.ybase + this._bufferService.buffer.y);
const line = this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y);
if (line) {
line.deleteCells(
this._bufferService.buffer.x,
this._activeBuffer.x,
params.params[0] || 1,
this._bufferService.buffer.getNullCell(this._eraseAttrData()),
this._activeBuffer.getNullCell(this._eraseAttrData()),
this._eraseAttrData()
);
this._dirtyRowService.markDirty(this._bufferService.buffer.y);
this._dirtyRowService.markDirty(this._activeBuffer.y);
}
return true;
}
@@ -1471,14 +1463,11 @@ export class InputHandler extends Disposable implements IInputHandler {
public scrollUp(params: IParams): boolean {
let param = params.params[0] || 1;
// make buffer local for faster access
const buffer = this._bufferService.buffer;
while (param--) {
buffer.lines.splice(buffer.ybase + buffer.scrollTop, 1);
buffer.lines.splice(buffer.ybase + buffer.scrollBottom, 0, buffer.getBlankLine(this._eraseAttrData()));
this._activeBuffer.lines.splice(this._activeBuffer.ybase + this._activeBuffer.scrollTop, 1);
this._activeBuffer.lines.splice(this._activeBuffer.ybase + this._activeBuffer.scrollBottom, 0, this._activeBuffer.getBlankLine(this._eraseAttrData()));
}
this._dirtyRowService.markRangeDirty(buffer.scrollTop, buffer.scrollBottom);
this._dirtyRowService.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom);
return true;
}
@@ -1490,14 +1479,11 @@ export class InputHandler extends Disposable implements IInputHandler {
public scrollDown(params: IParams): boolean {
let param = params.params[0] || 1;
// make buffer local for faster access
const buffer = this._bufferService.buffer;
while (param--) {
buffer.lines.splice(buffer.ybase + buffer.scrollBottom, 1);
buffer.lines.splice(buffer.ybase + buffer.scrollTop, 0, buffer.getBlankLine(DEFAULT_ATTR_DATA));
this._activeBuffer.lines.splice(this._activeBuffer.ybase + this._activeBuffer.scrollBottom, 1);
this._activeBuffer.lines.splice(this._activeBuffer.ybase + this._activeBuffer.scrollTop, 0, this._activeBuffer.getBlankLine(DEFAULT_ATTR_DATA));
}
this._dirtyRowService.markRangeDirty(buffer.scrollTop, buffer.scrollBottom);
this._dirtyRowService.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom);
return true;
}
@@ -1520,17 +1506,16 @@ export class InputHandler extends Disposable implements IInputHandler {
* SL has no effect outside of the scroll margins.
*/
public scrollLeft(params: IParams): boolean {
const buffer = this._bufferService.buffer;
if (buffer.y > buffer.scrollBottom || buffer.y < buffer.scrollTop) {
if (this._activeBuffer.y > this._activeBuffer.scrollBottom || this._activeBuffer.y < this._activeBuffer.scrollTop) {
return true;
}
const param = params.params[0] || 1;
for (let y = buffer.scrollTop; y <= buffer.scrollBottom; ++y) {
const line = buffer.lines.get(buffer.ybase + y)!;
line.deleteCells(0, param, buffer.getNullCell(this._eraseAttrData()), this._eraseAttrData());
for (let y = this._activeBuffer.scrollTop; y <= this._activeBuffer.scrollBottom; ++y) {
const line = this._activeBuffer.lines.get(this._activeBuffer.ybase + y)!;
line.deleteCells(0, param, this._activeBuffer.getNullCell(this._eraseAttrData()), this._eraseAttrData());
line.isWrapped = false;
}
this._dirtyRowService.markRangeDirty(buffer.scrollTop, buffer.scrollBottom);
this._dirtyRowService.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom);
return true;
}
@@ -1554,17 +1539,16 @@ export class InputHandler extends Disposable implements IInputHandler {
* SL has no effect outside of the scroll margins.
*/
public scrollRight(params: IParams): boolean {
const buffer = this._bufferService.buffer;
if (buffer.y > buffer.scrollBottom || buffer.y < buffer.scrollTop) {
if (this._activeBuffer.y > this._activeBuffer.scrollBottom || this._activeBuffer.y < this._activeBuffer.scrollTop) {
return true;
}
const param = params.params[0] || 1;
for (let y = buffer.scrollTop; y <= buffer.scrollBottom; ++y) {
const line = buffer.lines.get(buffer.ybase + y)!;
line.insertCells(0, param, buffer.getNullCell(this._eraseAttrData()), this._eraseAttrData());
for (let y = this._activeBuffer.scrollTop; y <= this._activeBuffer.scrollBottom; ++y) {
const line = this._activeBuffer.lines.get(this._activeBuffer.ybase + y)!;
line.insertCells(0, param, this._activeBuffer.getNullCell(this._eraseAttrData()), this._eraseAttrData());
line.isWrapped = false;
}
this._dirtyRowService.markRangeDirty(buffer.scrollTop, buffer.scrollBottom);
this._dirtyRowService.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom);
return true;
}
@@ -1578,17 +1562,16 @@ export class InputHandler extends Disposable implements IInputHandler {
* DECIC has no effect outside the scrolling margins.
*/
public insertColumns(params: IParams): boolean {
const buffer = this._bufferService.buffer;
if (buffer.y > buffer.scrollBottom || buffer.y < buffer.scrollTop) {
if (this._activeBuffer.y > this._activeBuffer.scrollBottom || this._activeBuffer.y < this._activeBuffer.scrollTop) {
return true;
}
const param = params.params[0] || 1;
for (let y = buffer.scrollTop; y <= buffer.scrollBottom; ++y) {
const line = this._bufferService.buffer.lines.get(buffer.ybase + y)!;
line.insertCells(buffer.x, param, buffer.getNullCell(this._eraseAttrData()), this._eraseAttrData());
for (let y = this._activeBuffer.scrollTop; y <= this._activeBuffer.scrollBottom; ++y) {
const line = this._activeBuffer.lines.get(this._activeBuffer.ybase + y)!;
line.insertCells(this._activeBuffer.x, param, this._activeBuffer.getNullCell(this._eraseAttrData()), this._eraseAttrData());
line.isWrapped = false;
}
this._dirtyRowService.markRangeDirty(buffer.scrollTop, buffer.scrollBottom);
this._dirtyRowService.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom);
return true;
}
@@ -1602,17 +1585,16 @@ export class InputHandler extends Disposable implements IInputHandler {
* DECDC has no effect outside the scrolling margins.
*/
public deleteColumns(params: IParams): boolean {
const buffer = this._bufferService.buffer;
if (buffer.y > buffer.scrollBottom || buffer.y < buffer.scrollTop) {
if (this._activeBuffer.y > this._activeBuffer.scrollBottom || this._activeBuffer.y < this._activeBuffer.scrollTop) {
return true;
}
const param = params.params[0] || 1;
for (let y = buffer.scrollTop; y <= buffer.scrollBottom; ++y) {
const line = buffer.lines.get(buffer.ybase + y)!;
line.deleteCells(buffer.x, param, buffer.getNullCell(this._eraseAttrData()), this._eraseAttrData());
for (let y = this._activeBuffer.scrollTop; y <= this._activeBuffer.scrollBottom; ++y) {
const line = this._activeBuffer.lines.get(this._activeBuffer.ybase + y)!;
line.deleteCells(this._activeBuffer.x, param, this._activeBuffer.getNullCell(this._eraseAttrData()), this._eraseAttrData());
line.isWrapped = false;
}
this._dirtyRowService.markRangeDirty(buffer.scrollTop, buffer.scrollBottom);
this._dirtyRowService.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom);
return true;
}
@@ -1626,15 +1608,15 @@ export class InputHandler extends Disposable implements IInputHandler {
*/
public eraseChars(params: IParams): boolean {
this._restrictCursor();
const line = this._bufferService.buffer.lines.get(this._bufferService.buffer.ybase + this._bufferService.buffer.y);
const line = this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y);
if (line) {
line.replaceCells(
this._bufferService.buffer.x,
this._bufferService.buffer.x + (params.params[0] || 1),
this._bufferService.buffer.getNullCell(this._eraseAttrData()),
this._activeBuffer.x,
this._activeBuffer.x + (params.params[0] || 1),
this._activeBuffer.getNullCell(this._eraseAttrData()),
this._eraseAttrData()
);
this._dirtyRowService.markDirty(this._bufferService.buffer.y);
this._dirtyRowService.markDirty(this._activeBuffer.y);
}
return true;
}
@@ -1976,6 +1958,7 @@ export class InputHandler extends Disposable implements IInputHandler {
// focusin: ^[[I
// focusout: ^[[O
this._coreService.decPrivateModes.sendFocus = true;
this._onRequestSendFocus.fire();
break;
case 1005: // utf8 ext mode mouse - removed in #2507
this._logService.debug('DECSET 1005 not supported (see #2507)');
@@ -2570,8 +2553,8 @@ export class InputHandler extends Disposable implements IInputHandler {
break;
case 6:
// cursor position
const y = this._bufferService.buffer.y + 1;
const x = this._bufferService.buffer.x + 1;
const y = this._activeBuffer.y + 1;
const x = this._activeBuffer.x + 1;
this._coreService.triggerDataEvent(`${C0.ESC}[${y};${x}R`);
break;
}
@@ -2585,8 +2568,8 @@ export class InputHandler extends Disposable implements IInputHandler {
switch (params.params[0]) {
case 6:
// cursor position
const y = this._bufferService.buffer.y + 1;
const x = this._bufferService.buffer.x + 1;
const y = this._activeBuffer.y + 1;
const x = this._activeBuffer.x + 1;
this._coreService.triggerDataEvent(`${C0.ESC}[?${y};${x}R`);
break;
case 15:
@@ -2631,18 +2614,18 @@ export class InputHandler extends Disposable implements IInputHandler {
public softReset(params: IParams): boolean {
this._coreService.isCursorHidden = false;
this._onRequestSyncScrollBar.fire();
this._bufferService.buffer.scrollTop = 0;
this._bufferService.buffer.scrollBottom = this._bufferService.rows - 1;
this._activeBuffer.scrollTop = 0;
this._activeBuffer.scrollBottom = this._bufferService.rows - 1;
this._curAttrData = DEFAULT_ATTR_DATA.clone();
this._coreService.reset();
this._charsetService.reset();
// reset DECSC data
this._bufferService.buffer.savedX = 0;
this._bufferService.buffer.savedY = this._bufferService.buffer.ybase;
this._bufferService.buffer.savedCurAttrData.fg = this._curAttrData.fg;
this._bufferService.buffer.savedCurAttrData.bg = this._curAttrData.bg;
this._bufferService.buffer.savedCharset = this._charsetService.charset;
this._activeBuffer.savedX = 0;
this._activeBuffer.savedY = this._activeBuffer.ybase;
this._activeBuffer.savedCurAttrData.fg = this._curAttrData.fg;
this._activeBuffer.savedCurAttrData.bg = this._curAttrData.bg;
this._activeBuffer.savedCharset = this._charsetService.charset;
// reset DECOM
this._coreService.decPrivateModes.origin = false;
@@ -2705,8 +2688,8 @@ export class InputHandler extends Disposable implements IInputHandler {
}
if (bottom > top) {
this._bufferService.buffer.scrollTop = top - 1;
this._bufferService.buffer.scrollBottom = bottom - 1;
this._activeBuffer.scrollTop = top - 1;
this._activeBuffer.scrollBottom = bottom - 1;
this._setCursor(0, 0);
}
return true;
@@ -2801,11 +2784,11 @@ export class InputHandler extends Disposable implements IInputHandler {
* @vt: #Y ESC SC "Save Cursor" "ESC 7" "Save cursor position, charmap and text attributes."
*/
public saveCursor(params?: IParams): boolean {
this._bufferService.buffer.savedX = this._bufferService.buffer.x;
this._bufferService.buffer.savedY = this._bufferService.buffer.ybase + this._bufferService.buffer.y;
this._bufferService.buffer.savedCurAttrData.fg = this._curAttrData.fg;
this._bufferService.buffer.savedCurAttrData.bg = this._curAttrData.bg;
this._bufferService.buffer.savedCharset = this._charsetService.charset;
this._activeBuffer.savedX = this._activeBuffer.x;
this._activeBuffer.savedY = this._activeBuffer.ybase + this._activeBuffer.y;
this._activeBuffer.savedCurAttrData.fg = this._curAttrData.fg;
this._activeBuffer.savedCurAttrData.bg = this._curAttrData.bg;
this._activeBuffer.savedCharset = this._charsetService.charset;
return true;
}
@@ -2819,13 +2802,13 @@ export class InputHandler extends Disposable implements IInputHandler {
* @vt: #Y ESC RC "Restore Cursor" "ESC 8" "Restore cursor position, charmap and text attributes."
*/
public restoreCursor(params?: IParams): boolean {
this._bufferService.buffer.x = this._bufferService.buffer.savedX || 0;
this._bufferService.buffer.y = Math.max(this._bufferService.buffer.savedY - this._bufferService.buffer.ybase, 0);
this._curAttrData.fg = this._bufferService.buffer.savedCurAttrData.fg;
this._curAttrData.bg = this._bufferService.buffer.savedCurAttrData.bg;
this._activeBuffer.x = this._activeBuffer.savedX || 0;
this._activeBuffer.y = Math.max(this._activeBuffer.savedY - this._activeBuffer.ybase, 0);
this._curAttrData.fg = this._activeBuffer.savedCurAttrData.fg;
this._curAttrData.bg = this._activeBuffer.savedCurAttrData.bg;
this._charsetService.charset = (this as any)._savedCharset;
if (this._bufferService.buffer.savedCharset) {
this._charsetService.charset = this._bufferService.buffer.savedCharset;
if (this._activeBuffer.savedCharset) {
this._charsetService.charset = this._activeBuffer.savedCharset;
}
this._restrictCursor();
return true;
@@ -2907,7 +2890,7 @@ export class InputHandler extends Disposable implements IInputHandler {
* @vt: #Y ESC NEL "Next Line" "ESC E" "Move the cursor to the beginning of the next row."
*/
public nextLine(): boolean {
this._bufferService.buffer.x = 0;
this._activeBuffer.x = 0;
this.index();
return true;
}
@@ -2987,13 +2970,12 @@ export class InputHandler extends Disposable implements IInputHandler {
*/
public index(): boolean {
this._restrictCursor();
const buffer = this._bufferService.buffer;
this._bufferService.buffer.y++;
if (buffer.y === buffer.scrollBottom + 1) {
buffer.y--;
this._activeBuffer.y++;
if (this._activeBuffer.y === this._activeBuffer.scrollBottom + 1) {
this._activeBuffer.y--;
this._bufferService.scroll(this._eraseAttrData());
} else if (buffer.y >= this._bufferService.rows) {
buffer.y = this._bufferService.rows - 1;
} else if (this._activeBuffer.y >= this._bufferService.rows) {
this._activeBuffer.y = this._bufferService.rows - 1;
}
this._restrictCursor();
return true;
@@ -3010,7 +2992,7 @@ export class InputHandler extends Disposable implements IInputHandler {
* @vt: #Y ESC HTS "Horizontal Tabulation Set" "ESC H" "Places a tab stop at the current cursor position."
*/
public tabSet(): boolean {
this._bufferService.buffer.tabs[this._bufferService.buffer.x] = true;
this._activeBuffer.tabs[this._activeBuffer.x] = true;
return true;
}
@@ -3025,17 +3007,16 @@ export class InputHandler extends Disposable implements IInputHandler {
*/
public reverseIndex(): boolean {
this._restrictCursor();
const buffer = this._bufferService.buffer;
if (buffer.y === buffer.scrollTop) {
if (this._activeBuffer.y === this._activeBuffer.scrollTop) {
// possibly move the code below to term.reverseScroll();
// test: echo -ne '\e[1;1H\e[44m\eM\e[0m'
// blankLine(true) is xterm/linux behavior
const scrollRegionHeight = buffer.scrollBottom - buffer.scrollTop;
buffer.lines.shiftElements(buffer.ybase + buffer.y, scrollRegionHeight, 1);
buffer.lines.set(buffer.ybase + buffer.y, buffer.getBlankLine(this._eraseAttrData()));
this._dirtyRowService.markRangeDirty(buffer.scrollTop, buffer.scrollBottom);
const scrollRegionHeight = this._activeBuffer.scrollBottom - this._activeBuffer.scrollTop;
this._activeBuffer.lines.shiftElements(this._activeBuffer.ybase + this._activeBuffer.y, scrollRegionHeight, 1);
this._activeBuffer.lines.set(this._activeBuffer.ybase + this._activeBuffer.y, this._activeBuffer.getBlankLine(this._eraseAttrData()));
this._dirtyRowService.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom);
} else {
buffer.y--;
this._activeBuffer.y--;
this._restrictCursor(); // quickfix to not run out of bounds
}
return true;
@@ -3096,12 +3077,11 @@ export class InputHandler extends Disposable implements IInputHandler {
cell.fg = this._curAttrData.fg;
cell.bg = this._curAttrData.bg;
const buffer = this._bufferService.buffer;
this._setCursor(0, 0);
for (let yOffset = 0; yOffset < this._bufferService.rows; ++yOffset) {
const row = buffer.ybase + buffer.y + yOffset;
const line = buffer.lines.get(row);
const row = this._activeBuffer.ybase + this._activeBuffer.y + yOffset;
const line = this._activeBuffer.lines.get(row);
if (line) {
line.fill(cell);
line.isWrapped = false;
+5 -3
View File
@@ -3,7 +3,7 @@
* @license MIT
*/
import { IBufferService, ICoreService, ILogService, IOptionsService, ITerminalOptions, IPartialTerminalOptions, IDirtyRowService, ICoreMouseService, ICharsetService, IUnicodeService, IUnicodeVersionProvider, LogLevelEnum } from 'common/services/Services';
import { IBufferService, ICoreService, ILogService, IOptionsService, ITerminalOptions, IDirtyRowService, ICoreMouseService, ICharsetService, IUnicodeService, IUnicodeVersionProvider, LogLevelEnum } from 'common/services/Services';
import { IEvent, EventEmitter } from 'common/EventEmitter';
import { clone } from 'common/Clone';
import { DEFAULT_OPTIONS } from 'common/services/OptionsService';
@@ -121,11 +121,13 @@ export class MockLogService implements ILogService {
export class MockOptionsService implements IOptionsService {
public serviceBrand: any;
public options: ITerminalOptions = clone(DEFAULT_OPTIONS);
public publicOptions: ITerminalOptions = clone(DEFAULT_OPTIONS);
public onOptionChange: IEvent<string> = new EventEmitter<string>().event;
constructor(testOptions?: IPartialTerminalOptions) {
constructor(testOptions?: Partial<ITerminalOptions>) {
if (testOptions) {
for (const key of Object.keys(testOptions)) {
this.options[key] = (testOptions as any)[key];
this.options[key] = testOptions[key];
this.publicOptions[key] = testOptions[key];
}
}
}
+1
View File
@@ -16,6 +16,7 @@ export interface ICoreTerminal {
optionsService: IOptionsService;
unicodeService: IUnicodeService;
buffers: IBufferSet;
options: ITerminalOptions;
registerCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean | Promise<boolean>): IDisposable;
registerDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: IParams) => boolean | Promise<boolean>): IDisposable;
registerEscHandler(id: IFunctionIdentifier, callback: () => boolean | Promise<boolean>): IDisposable;
+4
View File
@@ -42,6 +42,10 @@ export class BufferSet extends Disposable implements IBufferSet {
// See http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-The-Alternate-Screen-Buffer
this._alt = new Buffer(false, this._optionsService, this._bufferService);
this._activeBuffer = this._normal;
this._onBufferActivate.fire({
activeBuffer: this._normal,
inactiveBuffer: this._alt
});
this.setupTabStops();
}
+2 -2
View File
@@ -36,8 +36,8 @@ export class BufferService extends Disposable implements IBufferService {
@IOptionsService private _optionsService: IOptionsService
) {
super();
this.cols = Math.max(_optionsService.options.cols, MINIMUM_COLS);
this.rows = Math.max(_optionsService.options.rows, MINIMUM_ROWS);
this.cols = Math.max(_optionsService.options.cols || 0, MINIMUM_COLS);
this.rows = Math.max(_optionsService.options.rows || 0, MINIMUM_ROWS);
this.buffers = new BufferSet(_optionsService, this);
}
@@ -15,6 +15,16 @@ describe('OptionsService', () => {
afterEach(() => {
console.error = originalError;
});
it('uses default value if invalid constructor option values passed for cols/rows', () => {
const optionsService = new OptionsService({ cols: undefined, rows: undefined });
assert.equal(optionsService.getOption('rows'), DEFAULT_OPTIONS.rows);
assert.equal(optionsService.getOption('cols'), DEFAULT_OPTIONS.cols);
});
it('uses values from constructor option values if correctly passed', () => {
const optionsService = new OptionsService({ cols: 80, rows: 25 });
assert.equal(optionsService.getOption('rows'), 25);
assert.equal(optionsService.getOption('cols'), 80);
});
it('uses default value if invalid constructor option value passed', () => {
assert.equal(new OptionsService({tabStopWidth: 0}).getOption('tabStopWidth'), DEFAULT_OPTIONS.tabStopWidth);
});
+58 -35
View File
@@ -3,10 +3,9 @@
* @license MIT
*/
import { IOptionsService, ITerminalOptions, IPartialTerminalOptions, FontWeight } from 'common/services/Services';
import { IOptionsService, ITerminalOptions, FontWeight } from 'common/services/Services';
import { EventEmitter, IEvent } from 'common/EventEmitter';
import { isMac } from 'common/Platform';
import { clone } from 'common/Clone';
// Source: https://freesound.org/people/altemark/sounds/45759/
// This sound is released under the Creative Commons Attribution 3.0 Unported
@@ -14,15 +13,14 @@ import { clone } from 'common/Clone';
// made, apart from the conversion to base64.
export const DEFAULT_BELL_SOUND = 'data:audio/mp3;base64,SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU4LjMyLjEwNAAAAAAAAAAAAAAA//tQxAADB8AhSmxhIIEVCSiJrDCQBTcu3UrAIwUdkRgQbFAZC1CQEwTJ9mjRvBA4UOLD8nKVOWfh+UlK3z/177OXrfOdKl7pyn3Xf//WreyTRUoAWgBgkOAGbZHBgG1OF6zM82DWbZaUmMBptgQhGjsyYqc9ae9XFz280948NMBWInljyzsNRFLPWdnZGWrddDsjK1unuSrVN9jJsK8KuQtQCtMBjCEtImISdNKJOopIpBFpNSMbIHCSRpRR5iakjTiyzLhchUUBwCgyKiweBv/7UsQbg8isVNoMPMjAAAA0gAAABEVFGmgqK////9bP/6XCykxBTUUzLjEwMKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq';
// TODO: Freeze?
export const DEFAULT_OPTIONS: ITerminalOptions = Object.freeze({
export const DEFAULT_OPTIONS: Readonly<ITerminalOptions> = {
cols: 80,
rows: 24,
cursorBlink: false,
cursorStyle: 'block',
cursorWidth: 1,
customGlyphs: true,
bellSound: DEFAULT_BELL_SOUND,
bellSound: DEFAULT_BELL_SOUND,
bellStyle: 'none',
drawBoldTextInBrightColors: true,
fastScrollModifier: 'alt',
@@ -55,7 +53,7 @@ export const DEFAULT_OPTIONS: ITerminalOptions = Object.freeze({
convertEol: false,
termName: 'xterm',
cancelEvents: false
});
};
const FONT_WEIGHT_OPTIONS: Extract<FontWeight, string>[] = ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'];
@@ -67,45 +65,68 @@ const CONSTRUCTOR_ONLY_OPTIONS = ['cols', 'rows'];
export class OptionsService implements IOptionsService {
public serviceBrand: any;
private _options: ITerminalOptions;
public options: ITerminalOptions;
public publicOptions: ITerminalOptions;
private _onOptionChange = new EventEmitter<string>();
public get onOptionChange(): IEvent<string> { return this._onOptionChange.event; }
constructor(options: IPartialTerminalOptions) {
this.options = clone(DEFAULT_OPTIONS);
for (const k of Object.keys(options)) {
if (k in this.options) {
constructor(options: Partial<ITerminalOptions>) {
// set the default value of each option
this._options = { ...DEFAULT_OPTIONS };
for (const key in options) {
if (key in this._options) {
try {
const newValue = options[k as keyof IPartialTerminalOptions] as any;
this.options[k] = this._sanitizeAndValidateOption(k, newValue);
const newValue = options[key];
this._options[key] = this._sanitizeAndValidateOption(key, newValue);
} catch (e) {
console.error(e);
}
}
}
// set up getters and setters for each option
this.options = this._setupOptions(this._options, false);
this.publicOptions = this._setupOptions(this._options, true);
}
private _setupOptions(options: ITerminalOptions, isPublic: boolean): ITerminalOptions {
const copiedOptions = { ... options };
for (const propName in copiedOptions) {
Object.defineProperty(copiedOptions, propName, {
get: () => {
if (!(propName in DEFAULT_OPTIONS)) {
throw new Error(`No option with key "${propName}"`);
}
return this._options[propName];
},
set: (value: any) => {
if (!(propName in DEFAULT_OPTIONS)) {
throw new Error(`No option with key "${propName}"`);
}
// Throw an error if any constructor only option is modified
// from terminal.options
// Modifications from anywhere else are allowed
if (isPublic && CONSTRUCTOR_ONLY_OPTIONS.includes(propName)) {
throw new Error(`Option "${propName}" can only be set in the constructor`);
}
value = this._sanitizeAndValidateOption(propName, value);
// Don't fire an option change event if they didn't change
if (this._options[propName] !== value) {
this._options[propName] = value;
this._onOptionChange.fire(propName);
}
}
});
}
return copiedOptions;
}
public setOption(key: string, value: any): void {
if (!(key in DEFAULT_OPTIONS)) {
throw new Error('No option with key "' + key + '"');
}
if (CONSTRUCTOR_ONLY_OPTIONS.includes(key)) {
throw new Error(`Option "${key}" can only be set in the constructor`);
}
if (this.options[key] === value) {
return;
}
value = this._sanitizeAndValidateOption(key, value);
// Don't fire an option change event if they didn't change
if (this.options[key] === value) {
return;
}
this.options[key] = value;
this._onOptionChange.fire(key);
this.publicOptions[key] = value;
}
private _sanitizeAndValidateOption(key: string, value: any): any {
@@ -149,15 +170,17 @@ export class OptionsService implements IOptionsService {
if (value <= 0) {
throw new Error(`${key} cannot be less than or equal to 0, value: ${value}`);
}
case 'rows':
case 'cols':
if (!value && value !== 0) {
throw new Error(`${key} must be numeric, value: ${value}`);
}
break;
}
return value;
}
public getOption(key: string): any {
if (!(key in DEFAULT_OPTIONS)) {
throw new Error(`No option with key "${key}"`);
}
return this.options[key];
return this.publicOptions[key];
}
}
+1 -35
View File
@@ -181,6 +181,7 @@ export interface IOptionsService {
serviceBrand: undefined;
readonly options: ITerminalOptions;
readonly publicOptions: ITerminalOptions;
readonly onOptionChange: IEvent<string>;
@@ -199,41 +200,6 @@ export enum LogLevelEnum {
}
export type RendererType = 'dom' | 'canvas';
export interface IPartialTerminalOptions {
altClickMovesCursor?: boolean;
allowTransparency?: boolean;
bellSound?: string;
bellStyle?: 'none' | 'sound' /* | 'visual' | 'both' */;
cols?: number;
cursorBlink?: boolean;
cursorStyle?: 'block' | 'underline' | 'bar';
cursorWidth?: number;
disableStdin?: boolean;
drawBoldTextInBrightColors?: boolean;
fastScrollModifier?: 'alt' | 'ctrl' | 'shift';
fastScrollSensitivity?: number;
fontSize?: number;
fontFamily?: string;
fontWeight?: FontWeight;
fontWeightBold?: FontWeight;
letterSpacing?: number;
lineHeight?: number;
logLevel?: LogLevel;
macOptionIsMeta?: boolean;
macOptionClickForcesSelection?: boolean;
rendererType?: RendererType;
rightClickSelectsWord?: boolean;
rows?: number;
screenReaderMode?: boolean;
scrollback?: number;
scrollSensitivity?: number;
tabStopWidth?: number;
theme?: ITheme;
windowsMode?: boolean;
wordSeparator?: string;
windowOptions?: IWindowOptions;
}
export interface ITerminalOptions {
allowProposedApi: boolean;
allowTransparency: boolean;
+7 -5
View File
@@ -84,7 +84,7 @@ declare module 'xterm-headless' {
* line height and letter spacing is used. Note that this doesn't work with the DOM renderer
* which renders all characters using the font. The default is true.
*/
customGlyphs?: boolean;
customGlyphs?: boolean;
/**
* Whether input should be disabled.
@@ -1085,18 +1085,20 @@ declare module 'xterm-headless' {
/** Whether the cell has the bold attribute (CSI 1 m). */
isBold(): number;
/** Whether the cell has the inverse attribute (CSI 3 m). */
/** Whether the cell has the italic attribute (CSI 3 m). */
isItalic(): number;
/** Whether the cell has the inverse attribute (CSI 2 m). */
/** Whether the cell has the dim attribute (CSI 2 m). */
isDim(): number;
/** Whether the cell has the underline attribute (CSI 4 m). */
isUnderline(): number;
/** Whether the cell has the inverse attribute (CSI 5 m). */
/** Whether the cell has the blink attribute (CSI 5 m). */
isBlink(): number;
/** Whether the cell has the inverse attribute (CSI 7 m). */
isInverse(): number;
/** Whether the cell has the inverse attribute (CSI 8 m). */
/** Whether the cell has the invisible attribute (CSI 8 m). */
isInvisible(): number;
/** Whether the cell has the strikethrough attribute (CSI 9 m). */
isStrikethrough(): number;
/** Whether the cell is using the RGB foreground color mode. */
isFgRGB(): boolean;
+42 -12
View File
@@ -635,6 +635,11 @@ declare module 'xterm' {
*/
readonly modes: IModes;
/**
* Get the terminal options
*/
readonly options: ITerminalOptions;
/**
* Natural language strings that can be localized.
*/
@@ -964,26 +969,31 @@ declare module 'xterm' {
/**
* Retrieves an option's value from the terminal.
* @param key The option key.
* @deprecated Use `options` instead.
*/
getOption(key: 'bellSound' | 'bellStyle' | 'cursorStyle' | 'fontFamily' | 'logLevel' | 'rendererType' | 'termName' | 'wordSeparator'): string;
/**
* Retrieves an option's value from the terminal.
* @param key The option key.
* @deprecated Use `options` instead.
*/
getOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'disableStdin' | 'macOptionIsMeta' | 'rightClickSelectsWord' | 'popOnBell' | 'visualBell' | 'windowsMode'): boolean;
/**
* Retrieves an option's value from the terminal.
* @param key The option key.
* @deprecated Use `options` instead.
*/
getOption(key: 'cols' | 'fontSize' | 'letterSpacing' | 'lineHeight' | 'rows' | 'tabStopWidth' | 'scrollback'): number;
/**
* Retrieves an option's value from the terminal.
* @param key The option key.
* @deprecated Use `options` instead.
*/
getOption(key: 'fontWeight' | 'fontWeightBold'): FontWeight;
/**
* Retrieves an option's value from the terminal.
* @param key The option key.
* @deprecated Use `options` instead.
*/
getOption(key: string): any;
@@ -991,60 +1001,70 @@ declare module 'xterm' {
* Sets an option on the terminal.
* @param key The option key.
* @param value The option value.
* @deprecated Use `options` instead.
*/
setOption(key: 'fontFamily' | 'termName' | 'bellSound' | 'wordSeparator', value: string): void;
/**
* Sets an option on the terminal.
* @param key The option key.
* @param value The option value.
*/
* Sets an option on the terminal.
* @param key The option key.
* @param value The option value.
* @deprecated Use `options` instead.
*/
setOption(key: 'fontWeight' | 'fontWeightBold', value: null | 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900' | number): void;
/**
* Sets an option on the terminal.
* @param key The option key.
* @param value The option value.
*/
* Sets an option on the terminal.
* @param key The option key.
* @param value The option value.
* @deprecated Use `options` instead.
*/
setOption(key: 'logLevel', value: LogLevel): void;
/**
* Sets an option on the terminal.
* @param key The option key.
* @param value The option value.
* @deprecated Use `options` instead.
*/
setOption(key: 'bellStyle', value: null | 'none' | 'visual' | 'sound' | 'both'): void;
/**
* Sets an option on the terminal.
* @param key The option key.
* @param value The option value.
* @deprecated Use `options` instead.
*/
setOption(key: 'cursorStyle', value: null | 'block' | 'underline' | 'bar'): void;
/**
* Sets an option on the terminal.
* @param key The option key.
* @param value The option value.
* @deprecated Use `options` instead.
*/
setOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'disableStdin' | 'macOptionIsMeta' | 'popOnBell' | 'rightClickSelectsWord' | 'visualBell' | 'windowsMode', value: boolean): void;
/**
* Sets an option on the terminal.
* @param key The option key.
* @param value The option value.
* @deprecated Use `options` instead.
*/
setOption(key: 'fontSize' | 'letterSpacing' | 'lineHeight' | 'tabStopWidth' | 'scrollback', value: number): void;
/**
* Sets an option on the terminal.
* @param key The option key.
* @param value The option value.
* @deprecated Use `options` instead.
*/
setOption(key: 'theme', value: ITheme): void;
/**
* Sets an option on the terminal.
* @param key The option key.
* @param value The option value.
* @deprecated Use `options` instead.
*/
setOption(key: 'cols' | 'rows', value: number): void;
/**
* Sets an option on the terminal.
* @param key The option key.
* @param value The option value.
* @deprecated Use `options` instead.
*/
setOption(key: string, value: any): void;
@@ -1056,6 +1076,14 @@ declare module 'xterm' {
*/
refresh(start: number, end: number): void;
/**
* Clears the texture atlas of the canvas renderer if it's active. Doing this will force a
* redraw of all glyphs which can workaround issues causing the texture to become corrupt, for
* example Chromium/Nvidia has an issue where the texture gets messed up when resuming the OS
* from sleep.
*/
clearTextureAtlas(): void;
/**
* Perform a full reset (RIS, aka '\x1bc').
*/
@@ -1444,18 +1472,20 @@ declare module 'xterm' {
/** Whether the cell has the bold attribute (CSI 1 m). */
isBold(): number;
/** Whether the cell has the inverse attribute (CSI 3 m). */
/** Whether the cell has the italic attribute (CSI 3 m). */
isItalic(): number;
/** Whether the cell has the inverse attribute (CSI 2 m). */
/** Whether the cell has the dim attribute (CSI 2 m). */
isDim(): number;
/** Whether the cell has the underline attribute (CSI 4 m). */
isUnderline(): number;
/** Whether the cell has the inverse attribute (CSI 5 m). */
/** Whether the cell has the blink attribute (CSI 5 m). */
isBlink(): number;
/** Whether the cell has the inverse attribute (CSI 7 m). */
isInverse(): number;
/** Whether the cell has the inverse attribute (CSI 8 m). */
/** Whether the cell has the invisible attribute (CSI 8 m). */
isInvisible(): number;
/** Whether the cell has the strikethrough attribute (CSI 9 m). */
isStrikethrough(): number;
/** Whether the cell is using the RGB foreground color mode. */
isFgRGB(): boolean;