diff --git a/.vscode/launch.json b/.vscode/launch.json
index c7bf7381..e5bad7b3 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -23,10 +23,7 @@
"type": "chrome",
"request": "launch",
"name": "Demo Client",
- "url": "http://0.0.0.0:3000",
- "windows": {
- "url": "http://127.0.0.1:3000"
- },
+ "url": "http://127.0.0.1:3000",
"webRoot": "${workspaceFolder}/"
},
{
@@ -38,7 +35,11 @@
"run",
"start-debug"
],
- "port": 9229
+ "port": 9229,
+ "serverReadyAction": {
+ "action": "openExternally",
+ "pattern": "App listening to (http://.*?:[0-9]+)"
+ }
}
]
}
diff --git a/README.md b/README.md
index 5378f443..40de04cf 100644
--- a/README.md
+++ b/README.md
@@ -136,7 +136,7 @@ Xterm.js is used in several world-class applications to provide great terminal e
- [**Kubebox**](https://github.com/astefanutti/kubebox): Terminal console for Kubernetes clusters.
- [**Azure Cloud Shell**](https://shell.azure.com): Azure Cloud Shell is a Microsoft-managed admin machine built on Azure, for Azure.
- [**atom-xterm**](https://atom.io/packages/atom-xterm): Atom plugin for providing terminals inside your Atom workspace.
-- [**rtty**](https://github.com/zhaojh329/rtty): A reverse proxy WebTTY. It is composed of the client and the server.
+- [**rtty**](https://github.com/zhaojh329/rtty): Access your terminals from anywhere via the web.
- [**Pisth**](https://github.com/ColdGrub1384/Pisth): An SFTP and SSH client for iOS.
- [**abstruse**](https://github.com/bleenco/abstruse): Abstruse CI is a continuous integration platform based on Node.JS and Docker.
- [**Azure Data Studio**](https://github.com/Microsoft/azuredatastudio): A data management tool that enables working with SQL Server, Azure SQL DB and SQL DW from Windows, macOS and Linux.
@@ -154,6 +154,7 @@ Xterm.js is used in several world-class applications to provide great terminal e
- [**webssh**](https://github.com/huashengdun/webssh): Web based ssh client.
- [**info-beamer hosted**](https://info-beamer.com): Uses xterm.js to manage digital signage devices from the web dashboard.
- [**Jumpserver**](https://github.com/jumpserver/luna): Jumpserver Luna project, Jumpserver is a bastion server project, Luna use xterm.js for web terminal emulation.
+- [**LxdMosaic**](https://github.com/turtle0x1/LxdMosaic): Uses xterm.js to give terminal access to containers through LXD
[And much more...](https://github.com/xtermjs/xterm.js/network/dependents)
diff --git a/demo/client.ts b/demo/client.ts
index f83d3af1..b6b4cbdf 100644
--- a/demo/client.ts
+++ b/demo/client.ts
@@ -30,7 +30,10 @@ Terminal.applyAddon(fit);
Terminal.applyAddon(fullscreen);
Terminal.applyAddon(search);
Terminal.applyAddon(webLinks);
-Terminal.applyAddon(winptyCompat);
+const isWindows = ['Windows', 'Win16', 'Win32', 'WinCE'].indexOf(navigator.platform) >= 0;
+if (isWindows) {
+ Terminal.applyAddon(winptyCompat);
+}
let term;
@@ -99,7 +102,9 @@ function createTerminal(): void {
socketURL = protocol + location.hostname + ((location.port) ? (':' + location.port) : '') + '/terminals/';
term.open(terminalContainer);
- term.winptyCompatInit();
+ if (isWindows) {
+ term.winptyCompatInit();
+ }
term.webLinksInit();
term.fit();
term.focus();
diff --git a/demo/index.html b/demo/index.html
index 12f7cb98..370a51ed 100644
--- a/demo/index.html
+++ b/demo/index.html
@@ -2,8 +2,8 @@
Attention: The demo is a barebones implementation and is designed for xterm.js evaluation purposes only. Exposing the demo to the public as is would introduce security risks for the host.
-
* ZMODEM file transfers are supported via an addon. To try it out, install lrzsz onto the remote peer, then run rz to send from your browser or sz <file> to send from the remote peer.
-
-
-
diff --git a/demo/zmodem/main.js b/demo/zmodem/main.js
deleted file mode 100644
index 619ef2b8..00000000
--- a/demo/zmodem/main.js
+++ /dev/null
@@ -1,388 +0,0 @@
-"use strict";
-
-var term,
- protocol,
- socketURL,
- socket,
- pid;
-
-Terminal.applyAddon(fit);
-Terminal.applyAddon(attach);
-Terminal.applyAddon(zmodem);
-Terminal.applyAddon(search);
-
-var terminalContainer = document.getElementById('terminal-container'),
- actionElements = {
- findNext: document.querySelector('#find-next'),
- findPrevious: document.querySelector('#find-previous')
- },
- optionElements = {
- cursorBlink: document.querySelector('#option-cursor-blink'),
- cursorStyle: document.querySelector('#option-cursor-style'),
- scrollback: document.querySelector('#option-scrollback'),
- tabstopwidth: document.querySelector('#option-tabstopwidth'),
- bellStyle: document.querySelector('#option-bell-style')
- },
- colsElement = document.getElementById('cols'),
- rowsElement = document.getElementById('rows');
-
-function setTerminalSize() {
- var cols = parseInt(colsElement.value, 10);
- var rows = parseInt(rowsElement.value, 10);
- var viewportElement = document.querySelector('.xterm-viewport');
- var scrollBarWidth = viewportElement.offsetWidth - viewportElement.clientWidth;
- var width = (cols * term.charMeasure.width + 20 /*room for scrollbar*/).toString() + 'px';
- var height = (rows * term.charMeasure.height).toString() + 'px';
-
- terminalContainer.style.width = width;
- terminalContainer.style.height = height;
- term.resize(cols, rows);
-}
-
-colsElement.addEventListener('change', setTerminalSize);
-rowsElement.addEventListener('change', setTerminalSize);
-
-actionElements.findNext.addEventListener('keypress', function (e) {
- if (e.key === "Enter") {
- e.preventDefault();
- term.findNext(actionElements.findNext.value);
- }
-});
-actionElements.findPrevious.addEventListener('keypress', function (e) {
- if (e.key === "Enter") {
- e.preventDefault();
- term.findPrevious(actionElements.findPrevious.value);
- }
-});
-
-optionElements.cursorBlink.addEventListener('change', function () {
- term.setOption('cursorBlink', optionElements.cursorBlink.checked);
-});
-optionElements.cursorStyle.addEventListener('change', function () {
- term.setOption('cursorStyle', optionElements.cursorStyle.value);
-});
-optionElements.bellStyle.addEventListener('change', function () {
- term.setOption('bellStyle', optionElements.bellStyle.value);
-});
-optionElements.scrollback.addEventListener('change', function () {
- term.setOption('scrollback', parseInt(optionElements.scrollback.value, 10));
-});
-optionElements.tabstopwidth.addEventListener('change', function () {
- term.setOption('tabStopWidth', parseInt(optionElements.tabstopwidth.value, 10));
-});
-
-createTerminal();
-
-function createTerminal() {
- // Clean terminal
- while (terminalContainer.children.length) {
- terminalContainer.removeChild(terminalContainer.children[0]);
- }
- term = new Terminal({
- cursorBlink: optionElements.cursorBlink.checked,
- scrollback: parseInt(optionElements.scrollback.value, 10),
- tabStopWidth: parseInt(optionElements.tabstopwidth.value, 10)
- });
- term.on('resize', function (size) {
- if (!pid) {
- return;
- }
- var cols = size.cols,
- rows = size.rows,
- url = '/terminals/' + pid + '/size?cols=' + cols + '&rows=' + rows;
-
- fetch(url, {method: 'POST'});
- });
- protocol = (location.protocol === 'https:') ? 'wss://' : 'ws://';
- socketURL = protocol + location.hostname + ((location.port) ? (':' + location.port) : '') + '/terminals/';
-
- term.open(terminalContainer);
- term.fit();
-
- // fit is called within a setTimeout, cols and rows need this.
- setTimeout(function () {
- colsElement.value = term.cols;
- rowsElement.value = term.rows;
-
- // Set terminal size again to set the specific dimensions on the demo
- setTerminalSize();
-
- fetch('/terminals?cols=' + term.cols + '&rows=' + term.rows, {method: 'POST'}).then(function (res) {
-
- res.text().then(function (pid) {
- window.pid = pid;
- socketURL += pid;
- socket = new WebSocket(socketURL);
- socket.onopen = runRealTerminal;
- socket.onclose = runFakeTerminal;
- socket.onerror = runFakeTerminal;
-
- term.zmodemAttach(socket, {
- noTerminalWriteOutsideSession: true,
- } );
-
- term.on("zmodemRetract", () => {
- start_form.style.display = "none";
- start_form.onsubmit = null;
- });
-
- term.on("zmodemDetect", (detection) => {
- function do_zmodem() {
- term.detach();
- let zsession = detection.confirm();
-
- var promise;
-
- if (zsession.type === "receive") {
- promise = _handle_receive_session(zsession);
- }
- else {
- promise = _handle_send_session(zsession);
- }
-
- promise.catch( console.error.bind(console) ).then( () => {
- term.attach(socket);
- } );
- }
-
- if (_auto_zmodem()) {
- do_zmodem();
- }
- else {
- start_form.style.display = "";
- start_form.onsubmit = function(e) {
- start_form.style.display = "none";
-
- if (document.getElementById("zmstart_yes").checked) {
- do_zmodem();
- }
- else {
- detection.deny();
- }
- };
- }
- });
- });
- });
- }, 0);
-}
-
-//----------------------------------------------------------------------
-// UI STUFF
-
-function _show_file_info(xfer) {
- var file_info = xfer.get_details();
-
- document.getElementById("name").textContent = file_info.name;
- document.getElementById("size").textContent = file_info.size;
- document.getElementById("mtime").textContent = file_info.mtime;
- document.getElementById("files_remaining").textContent = file_info.files_remaining;
- document.getElementById("bytes_remaining").textContent = file_info.bytes_remaining;
-
- document.getElementById("mode").textContent = "0" + file_info.mode.toString(8);
-
- var xfer_opts = xfer.get_options();
- ["conversion", "management", "transport", "sparse"].forEach( (lbl) => {
- document.getElementById(`zfile_${lbl}`).textContent = xfer_opts[lbl];
- } );
-
- document.getElementById("zm_file").style.display = "";
-}
-function _hide_file_info() {
- document.getElementById("zm_file").style.display = "none";
-}
-
-function _save_to_disk(xfer, buffer) {
- return Zmodem.Browser.save_to_disk(buffer, xfer.get_details().name);
-}
-
-var skipper_button = document.getElementById("zm_progress_skipper");
-var skipper_button_orig_text = skipper_button.textContent;
-
-function _show_progress() {
- skipper_button.disabled = false;
- skipper_button.textContent = skipper_button_orig_text;
-
- document.getElementById("bytes_received").textContent = 0;
- document.getElementById("percent_received").textContent = 0;
-
- document.getElementById("zm_progress").style.display = "";
-}
-
-function _update_progress(xfer) {
- var total_in = xfer.get_offset();
-
- document.getElementById("bytes_received").textContent = total_in;
-
- var percent_received = 100 * total_in / xfer.get_details().size;
- document.getElementById("percent_received").textContent = percent_received.toFixed(2);
-}
-
-function _hide_progress() {
- document.getElementById("zm_progress").style.display = "none";
-}
-
-var start_form = document.getElementById("zm_start");
-
-function _auto_zmodem() {
- return document.getElementById("zmodem-auto").checked;
-}
-
-// END UI STUFF
-//----------------------------------------------------------------------
-
-function _handle_receive_session(zsession) {
- zsession.on("offer", function(xfer) {
- current_receive_xfer = xfer;
-
- _show_file_info(xfer);
-
- var offer_form = document.getElementById("zm_offer");
-
- function on_form_submit() {
- offer_form.style.display = "none";
-
- //START
- //if (offer_form.zmaccept.value) {
- if (_auto_zmodem() || document.getElementById("zmaccept_yes").checked) {
- _show_progress();
-
- var FILE_BUFFER = [];
- xfer.on("input", (payload) => {
- _update_progress(xfer);
- FILE_BUFFER.push( new Uint8Array(payload) );
- });
- xfer.accept().then(
- () => {
- _save_to_disk(xfer, FILE_BUFFER);
- },
- console.error.bind(console)
- );
- }
- else {
- xfer.skip();
- }
- //END
- }
-
- if (_auto_zmodem()) {
- on_form_submit();
- }
- else {
- offer_form.onsubmit = on_form_submit;
- offer_form.style.display = "";
- }
- } );
-
- var promise = new Promise( (res) => {
- zsession.on("session_end", () => {
- _hide_file_info();
- _hide_progress();
- res();
- } );
- } );
-
- zsession.start();
-
- return promise;
-}
-
-function _handle_send_session(zsession) {
- var choose_form = document.getElementById("zm_choose");
- choose_form.style.display = "";
-
- var file_el = document.getElementById("zm_files");
-
- var promise = new Promise( (res) => {
- file_el.onchange = function(e) {
- choose_form.style.display = "none";
-
- var files_obj = file_el.files;
-
- Zmodem.Browser.send_files(
- zsession,
- files_obj,
- {
- on_offer_response(obj, xfer) {
- if (xfer) _show_progress();
- //console.log("offer", xfer ? "accepted" : "skipped");
- },
- on_progress(obj, xfer) {
- _update_progress(xfer);
- },
- on_file_complete(obj) {
- //console.log("COMPLETE", obj);
- _hide_progress();
- },
- }
- ).then(_hide_progress).then(
- zsession.close.bind(zsession),
- console.error.bind(console)
- ).then( () => {
- _hide_file_info();
- _hide_progress();
- res();
- } );
- };
- } );
-
- return promise;
-}
-
-//This is here to allow canceling of an in-progress ZMODEM transfer.
-var current_receive_xfer;
-
-//Called from HTML directly.
-function skip_current_file() {
- current_receive_xfer.skip();
-
- skipper_button.disabled = true;
- skipper_button.textContent = "Waiting for server to acknowledge skip …";
-}
-
-function runRealTerminal() {
- term.attach(socket);
-
- term._initialized = true;
-}
-
-function runFakeTerminal() {
- if (term._initialized) {
- return;
- }
-
- term._initialized = true;
-
- var shellprompt = '$ ';
-
- term.prompt = function () {
- term.write('\r\n' + shellprompt);
- };
-
- term.writeln('Welcome to xterm.js');
- term.writeln('This is a local terminal emulation, without a real terminal in the back-end.');
- term.writeln('Type some keys and commands to play around.');
- term.writeln('');
- term.prompt();
-
- term.on('key', function (key, ev) {
- var printable = (
- !ev.altKey && !ev.altGraphKey && !ev.ctrlKey && !ev.metaKey
- );
-
- if (ev.keyCode == 13) {
- term.prompt();
- } else if (ev.keyCode == 8) {
- // Do not delete the prompt
- if (term.x > 2) {
- term.write('\b \b');
- }
- } else if (printable) {
- term.write(key);
- }
- });
-
- term.on('paste', function (data, ev) {
- term.write(data);
- });
-}
diff --git a/gulpfile.js b/gulpfile.js
index 9af8d6e4..bbb4d6e1 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -16,9 +16,9 @@ const ts = require('gulp-typescript');
const util = require('gulp-util');
const buildDir = process.env.BUILD_DIR || 'build';
-const tsProject = ts.createProject('tsconfig.json');
-let srcDir = tsProject.config.compilerOptions.rootDir;
-let outDir = tsProject.config.compilerOptions.outDir;
+const tsProject = ts.createProject('src/tsconfig.json');
+let srcDir = './src';
+let outDir = './lib';
const addons = fs.readdirSync(`${__dirname}/src/addons`);
@@ -61,7 +61,7 @@ gulp.task('browserify', function() {
};
let bundleStream = browserify(browserifyOptions)
.bundle()
- .pipe(source('xterm.js'))
+ .pipe(source(`xterm.js`))
.pipe(buffer())
.pipe(sourcemaps.init({loadMaps: true, sourceRoot: '..'}))
.pipe(sourcemaps.write('./'))
@@ -136,6 +136,6 @@ gulp.task('sorcery-addons', ['browserify-addons'], function () {
})
});
-gulp.task('build', ['sorcery', 'sorcery-addons']);
+gulp.task('build', ['css', 'sorcery', 'sorcery-addons']);
gulp.task('test', ['mocha']);
gulp.task('default', ['build']);
diff --git a/package.json b/package.json
index c5fad515..539b96da 100644
--- a/package.json
+++ b/package.json
@@ -15,7 +15,6 @@
"@types/webpack": "^4.4.11",
"browserify": "^13.3.0",
"chai": "3.5.0",
- "concurrently": "^3.5.1",
"coveralls": "^3.0.1",
"express": "4.13.4",
"express-ws": "2.0.0-rc.1",
@@ -38,7 +37,7 @@
"ts-loader": "^4.5.0",
"tslint": "^5.9.1",
"tslint-consistent-codestyle": "^1.13.0",
- "typescript": "3.1",
+ "typescript": "3.4",
"vinyl-buffer": "^1.0.0",
"vinyl-source-stream": "^1.1.0",
"webpack": "^4.17.1",
@@ -50,20 +49,16 @@
"start-debug": "node --inspect-brk demo/start",
"start-zmodem": "node demo/zmodem/app",
"lint": "tslint 'src/**/*.ts' './demo/**/*.ts'",
- "pretest": "npm run layering",
"test": "npm run mocha",
"posttest": "npm run lint",
"test-debug": "node --inspect-brk node_modules/.bin/gulp test",
"test-suite": "gulp mocha-suite --test",
"test-coverage": "nyc -x gulpfile.js -x '**/*test*' npm run mocha",
"mocha": "gulp test",
- "tsc": "tsc",
- "prebuild": "concurrently --kill-others-on-fail --names \"lib,attach,fit,fullscreen,search,terminado,webLinks,winptyCompat,zmodem,css\" \"tsc\" \"tsc -p ./src/addons/attach\" \"tsc -p ./src/addons/fit\" \"tsc -p ./src/addons/fullscreen\" \"tsc -p ./src/addons/search\" \"tsc -p ./src/addons/terminado\" \"tsc -p ./src/addons/webLinks\" \"tsc -p ./src/addons/winptyCompat\" \"tsc -p ./src/addons/zmodem\" \"gulp css\"",
+ "prebuild": "tsc -b ./src/tsconfig.all.json",
"build": "gulp build",
"prepublish": "npm run build",
"coveralls": "nyc report --reporter=text-lcov | coveralls",
- "watch": "concurrently --kill-others-on-fail --names \"lib,css\" \"tsc -w\" \"gulp watch-css\"",
- "watch-addons": "concurrently --kill-others-on-fail --names \"attach,fit,fullscreen,search,terminado,webLinks,winptyCompat,zmodem\" \"tsc -w -p ./src/addons/attach\" \"tsc -w -p ./src/addons/fit\" \"tsc -w -p ./src/addons/fullscreen\" \"tsc -w -p ./src/addons/search\" \"tsc -w -p ./src/addons/terminado\" \"tsc -w -p ./src/addons/webLinks\" \"tsc -w -p ./src/addons/winptyCompat\" \"tsc -w -p ./src/addons/zmodem\"",
- "layering": "concurrently --kill-others-on-fail --names \"common,core\" \"tsc -p ./src/common\" \"tsc -p ./src/core\""
+ "watch": "tsc -b -w ./src/tsconfig.all.json --preserveWatchOutput"
}
}
diff --git a/src/Buffer.test.ts b/src/Buffer.test.ts
index 53de19b7..59475adb 100644
--- a/src/Buffer.test.ts
+++ b/src/Buffer.test.ts
@@ -5,10 +5,10 @@
import { assert, expect } from 'chai';
import { ITerminal } from './Types';
-import { Buffer, DEFAULT_ATTR, CHAR_DATA_CHAR_INDEX } from './Buffer';
+import { Buffer, DEFAULT_ATTR } from './Buffer';
import { CircularList } from './common/CircularList';
import { MockTerminal, TestTerminal } from './ui/TestUtils.test';
-import { BufferLine } from './BufferLine';
+import { BufferLine, CellData } from './BufferLine';
const INIT_COLS = 80;
const INIT_ROWS = 24;
@@ -37,13 +37,13 @@ describe('Buffer', () => {
describe('fillViewportRows', () => {
it('should fill the buffer with blank lines based on the size of the viewport', () => {
- const blankLineChar = buffer.getBlankLine(DEFAULT_ATTR).get(0);
+ const blankLineChar = buffer.getBlankLine(DEFAULT_ATTR).loadCell(0, new CellData()).getAsCharData;
buffer.fillViewportRows();
assert.equal(buffer.lines.length, INIT_ROWS);
for (let y = 0; y < INIT_ROWS; y++) {
assert.equal(buffer.lines.get(y).length, INIT_COLS);
for (let x = 0; x < INIT_COLS; x++) {
- assert.deepEqual(buffer.lines.get(y).get(x), blankLineChar);
+ assert.deepEqual(buffer.lines.get(y).loadCell(x, new CellData()).getAsCharData, blankLineChar);
}
}
});
@@ -155,15 +155,15 @@ describe('Buffer', () => {
assert.equal(buffer.lines.maxLength, INIT_ROWS);
buffer.y = INIT_ROWS - 1;
buffer.fillViewportRows();
- let chData = buffer.lines.get(5).get(0);
+ let chData = buffer.lines.get(5).loadCell(0, new CellData()).getAsCharData();
chData[1] = 'a';
- buffer.lines.get(5).set(0, chData);
- chData = buffer.lines.get(INIT_ROWS - 1).get(0);
+ buffer.lines.get(5).setCell(0, CellData.fromCharData(chData));
+ chData = buffer.lines.get(INIT_ROWS - 1).loadCell(0, new CellData()).getAsCharData();
chData[1] = 'b';
- buffer.lines.get(INIT_ROWS - 1).set(0, chData);
+ buffer.lines.get(INIT_ROWS - 1).setCell(0, CellData.fromCharData(chData));
buffer.resize(INIT_COLS, INIT_ROWS - 5);
- assert.equal(buffer.lines.get(0).get(0)[1], 'a');
- assert.equal(buffer.lines.get(INIT_ROWS - 1 - 5).get(0)[1], 'b');
+ assert.equal(buffer.lines.get(0).loadCell(0, new CellData()).getAsCharData()[1], 'a');
+ assert.equal(buffer.lines.get(INIT_ROWS - 1 - 5).loadCell(0, new CellData()).getAsCharData()[1], 'b');
});
});
});
@@ -1045,10 +1045,10 @@ describe('Buffer', () => {
describe ('translateBufferLineToString', () => {
it('should handle selecting a section of ascii text', () => {
const line = new BufferLine(4);
- line.set(0, [ null, 'a', 1, 'a'.charCodeAt(0)]);
- line.set(1, [ null, 'b', 1, 'b'.charCodeAt(0)]);
- line.set(2, [ null, 'c', 1, 'c'.charCodeAt(0)]);
- line.set(3, [ null, 'd', 1, 'd'.charCodeAt(0)]);
+ line.setCell(0, CellData.fromCharData([ null, 'a', 1, 'a'.charCodeAt(0)]));
+ line.setCell(1, CellData.fromCharData([ null, 'b', 1, 'b'.charCodeAt(0)]));
+ line.setCell(2, CellData.fromCharData([ null, 'c', 1, 'c'.charCodeAt(0)]));
+ line.setCell(3, CellData.fromCharData([ null, 'd', 1, 'd'.charCodeAt(0)]));
buffer.lines.set(0, line);
const str = buffer.translateBufferLineToString(0, true, 0, 2);
@@ -1057,9 +1057,9 @@ describe('Buffer', () => {
it('should handle a cut-off double width character by including it', () => {
const line = new BufferLine(3);
- line.set(0, [ null, '語', 2, 35486 ]);
- line.set(1, [ null, '', 0, null]);
- line.set(2, [ null, 'a', 1, 'a'.charCodeAt(0)]);
+ line.setCell(0, CellData.fromCharData([ null, '語', 2, 35486 ]));
+ line.setCell(1, CellData.fromCharData([ null, '', 0, null]));
+ line.setCell(2, CellData.fromCharData([ null, 'a', 1, 'a'.charCodeAt(0)]));
buffer.lines.set(0, line);
const str1 = buffer.translateBufferLineToString(0, true, 0, 1);
@@ -1068,9 +1068,9 @@ describe('Buffer', () => {
it('should handle a zero width character in the middle of the string by not including it', () => {
const line = new BufferLine(3);
- line.set(0, [ null, '語', 2, '語'.charCodeAt(0) ]);
- line.set(1, [ null, '', 0, null]);
- line.set(2, [ null, 'a', 1, 'a'.charCodeAt(0)]);
+ line.setCell(0, CellData.fromCharData([ null, '語', 2, '語'.charCodeAt(0) ]));
+ line.setCell(1, CellData.fromCharData([ null, '', 0, null]));
+ line.setCell(2, CellData.fromCharData([ null, 'a', 1, 'a'.charCodeAt(0)]));
buffer.lines.set(0, line);
const str0 = buffer.translateBufferLineToString(0, true, 0, 1);
@@ -1085,8 +1085,8 @@ describe('Buffer', () => {
it('should handle single width emojis', () => {
const line = new BufferLine(2);
- line.set(0, [ null, '😁', 1, '😁'.charCodeAt(0) ]);
- line.set(1, [ null, 'a', 1, 'a'.charCodeAt(0)]);
+ line.setCell(0, CellData.fromCharData([ null, '😁', 1, '😁'.charCodeAt(0) ]));
+ line.setCell(1, CellData.fromCharData([ null, 'a', 1, 'a'.charCodeAt(0)]));
buffer.lines.set(0, line);
const str1 = buffer.translateBufferLineToString(0, true, 0, 1);
@@ -1098,8 +1098,8 @@ describe('Buffer', () => {
it('should handle double width emojis', () => {
const line = new BufferLine(2);
- line.set(0, [ null, '😁', 2, '😁'.charCodeAt(0) ]);
- line.set(1, [ null, '', 0, null]);
+ line.setCell(0, CellData.fromCharData([ null, '😁', 2, '😁'.charCodeAt(0) ]));
+ line.setCell(1, CellData.fromCharData([ null, '', 0, null]));
buffer.lines.set(0, line);
const str1 = buffer.translateBufferLineToString(0, true, 0, 1);
@@ -1109,9 +1109,9 @@ describe('Buffer', () => {
assert.equal(str2, '😁');
const line2 = new BufferLine(3);
- line2.set(0, [ null, '😁', 2, '😁'.charCodeAt(0) ]);
- line2.set(1, [ null, '', 0, null]);
- line2.set(2, [ null, 'a', 1, 'a'.charCodeAt(0)]);
+ line2.setCell(0, CellData.fromCharData([ null, '😁', 2, '😁'.charCodeAt(0) ]));
+ line2.setCell(1, CellData.fromCharData([ null, '', 0, null]));
+ line2.setCell(2, CellData.fromCharData([ null, 'a', 1, 'a'.charCodeAt(0)]));
buffer.lines.set(0, line2);
const str3 = buffer.translateBufferLineToString(0, true, 0, 3);
@@ -1264,7 +1264,7 @@ describe('Buffer', () => {
assert.equal(input, s);
const stringIndex = s.match(/😃/).index;
const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, stringIndex);
- assert(terminal.buffer.lines.get(bufferIndex[0]).get(bufferIndex[1])[CHAR_DATA_CHAR_INDEX], '😃');
+ assert(terminal.buffer.lines.get(bufferIndex[0]).loadCell(bufferIndex[1], new CellData()).getChars(), '😃');
});
it('multiline fullwidth chars with offset 1 (currently tests for broken behavior)', () => {
@@ -1291,7 +1291,7 @@ describe('Buffer', () => {
assert.equal(input, s);
for (let i = 0; i < input.length; ++i) {
const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i, true);
- assert.equal(input[i], terminal.buffer.lines.get(bufferIndex[0]).get(bufferIndex[1])[CHAR_DATA_CHAR_INDEX]);
+ assert.equal(input[i], terminal.buffer.lines.get(bufferIndex[0]).loadCell(bufferIndex[1], new CellData()).getChars());
}
});
@@ -1309,7 +1309,7 @@ describe('Buffer', () => {
: (i % 3 === 1)
? input.substr(i, 2)
: input.substr(i - 1, 2),
- terminal.buffer.lines.get(bufferIndex[0]).get(bufferIndex[1])[CHAR_DATA_CHAR_INDEX]);
+ terminal.buffer.lines.get(bufferIndex[0]).loadCell(bufferIndex[1], new CellData()).getChars());
}
});
diff --git a/src/Buffer.ts b/src/Buffer.ts
index 5eea5d9a..9cc1adba 100644
--- a/src/Buffer.ts
+++ b/src/Buffer.ts
@@ -3,13 +3,14 @@
* @license MIT
*/
-import { IMarker } from 'xterm';
-import { BufferLine } from './BufferLine';
-import { reflowLargerApplyNewLayout, reflowLargerCreateNewLayout, reflowLargerGetLinesToRemove, reflowSmallerGetNewLineLengths } from './BufferReflow';
-import { CircularList, IDeleteEvent, IInsertEvent } from './common/CircularList';
+import { CircularList, IInsertEvent, IDeleteEvent } from './common/CircularList';
+import { ITerminal, IBuffer, IBufferLine, BufferIndex, IBufferStringIterator, IBufferStringIteratorResult, ICellData } from './Types';
import { EventEmitter } from './common/EventEmitter';
+import { IMarker } from 'xterm';
+import { BufferLine, CellData } from './BufferLine';
+import { reflowLargerApplyNewLayout, reflowLargerCreateNewLayout, reflowLargerGetLinesToRemove, reflowSmallerGetNewLineLengths } from './BufferReflow';
import { DEFAULT_COLOR } from './renderer/atlas/Types';
-import { BufferIndex, CharData, IBuffer, IBufferLine, IBufferStringIterator, IBufferStringIteratorResult, ITerminal } from './Types';
+
export const DEFAULT_ATTR = (0 << 18) | (DEFAULT_COLOR << 9) | (256 << 0);
export const CHAR_DATA_ATTR_INDEX = 0;
@@ -18,16 +19,24 @@ export const CHAR_DATA_WIDTH_INDEX = 2;
export const CHAR_DATA_CODE_INDEX = 3;
export const MAX_BUFFER_SIZE = 4294967295; // 2^32 - 1
+/**
+ * Null cell - a real empty cell (containing nothing).
+ * Note that code should always be 0 for a null cell as
+ * several test condition of the buffer line rely on this.
+ */
export const NULL_CELL_CHAR = '';
export const NULL_CELL_WIDTH = 1;
export const NULL_CELL_CODE = 0;
+/**
+ * Whitespace cell.
+ * This is meant as a replacement for empty cells when needed
+ * during rendering lines to preserve correct aligment.
+ */
export const WHITESPACE_CELL_CHAR = ' ';
export const WHITESPACE_CELL_WIDTH = 1;
export const WHITESPACE_CELL_CODE = 32;
-export const FILL_CHAR_DATA: CharData = [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE];
-
/**
* This class represents a terminal buffer (an internal state of the terminal), where the
* following information is stored (in high-level):
@@ -48,6 +57,8 @@ export class Buffer implements IBuffer {
public savedX: number;
public savedCurAttr: number;
public markers: Marker[] = [];
+ private _nullCell: ICellData = CellData.fromCharData([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]);
+ private _whitespaceCell: ICellData = CellData.fromCharData([0, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_WIDTH, WHITESPACE_CELL_CODE]);
private _cols: number;
private _rows: number;
@@ -66,9 +77,20 @@ export class Buffer implements IBuffer {
this.clear();
}
+ public getNullCell(fg: number = 0, bg: number = 0): ICellData {
+ this._nullCell.fg = fg;
+ this._nullCell.bg = bg;
+ return this._nullCell;
+ }
+
+ public getWhitespaceCell(fg: number = 0, bg: number = 0): ICellData {
+ this._whitespaceCell.fg = fg;
+ this._whitespaceCell.bg = bg;
+ return this._whitespaceCell;
+ }
+
public getBlankLine(attr: number, isWrapped?: boolean): IBufferLine {
- const fillCharData: CharData = [attr, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE];
- return new BufferLine(this._cols, fillCharData, isWrapped);
+ return new BufferLine(this._terminal.cols, this.getNullCell(attr), isWrapped);
}
public get hasScrollback(): boolean {
@@ -131,6 +153,9 @@ export class Buffer implements IBuffer {
* @param newRows The new number of rows.
*/
public resize(newCols: number, newRows: number): void {
+ // store reference to null cell with default attrs
+ const nullCell = this.getNullCell(DEFAULT_ATTR);
+
// Increase max length if needed before adjustments to allow space to fill
// as required.
const newMaxLength = this._getCorrectBufferLength(newRows);
@@ -144,7 +169,7 @@ export class Buffer implements IBuffer {
// Deal with columns increasing (reducing needs to happen after reflow)
if (this._cols < newCols) {
for (let i = 0; i < this.lines.length; i++) {
- this.lines.get(i).resize(newCols, FILL_CHAR_DATA);
+ this.lines.get(i).resize(newCols, nullCell);
}
}
@@ -165,7 +190,7 @@ export class Buffer implements IBuffer {
} else {
// Add a blank line if there is no buffer left at the top to scroll to, or if there
// are blank lines after the cursor
- this.lines.push(new BufferLine(newCols, FILL_CHAR_DATA));
+ this.lines.push(new BufferLine(newCols, nullCell));
}
}
}
@@ -217,7 +242,7 @@ export class Buffer implements IBuffer {
// Trim the end of the line off if cols shrunk
if (this._cols > newCols) {
for (let i = 0; i < this.lines.length; i++) {
- this.lines.get(i).resize(newCols, FILL_CHAR_DATA);
+ this.lines.get(i).resize(newCols, nullCell);
}
}
}
@@ -253,6 +278,7 @@ export class Buffer implements IBuffer {
}
private _reflowLargerAdjustViewport(newCols: number, newRows: number, countRemoved: number): void {
+ const nullCell = this.getNullCell(DEFAULT_ATTR);
// Adjust viewport based on number of items removed
let viewportAdjustments = countRemoved;
while (viewportAdjustments-- > 0) {
@@ -262,7 +288,7 @@ export class Buffer implements IBuffer {
}
if (this.lines.length < newRows) {
// Add an extra row at the bottom of the viewport
- this.lines.push(new BufferLine(newCols, FILL_CHAR_DATA));
+ this.lines.push(new BufferLine(newCols, nullCell));
}
} else {
if (this.ydisp === this.ybase) {
@@ -274,6 +300,7 @@ export class Buffer implements IBuffer {
}
private _reflowSmaller(newCols: number, newRows: number): void {
+ const nullCell = this.getNullCell(DEFAULT_ATTR);
// Gather all BufferLines that need to be inserted into the Buffer here so that they can be
// batched up and only committed once
const toInsert = [];
@@ -356,7 +383,7 @@ export class Buffer implements IBuffer {
// Null out the end of the line ends if a wide character wrapped to the following line
for (let i = 0; i < wrappedLines.length; i++) {
if (destLineLengths[i] < newCols) {
- wrappedLines[i].set(destLineLengths[i], FILL_CHAR_DATA);
+ wrappedLines[i].setCell(destLineLengths[i], nullCell);
}
}
diff --git a/src/BufferLine.test.ts b/src/BufferLine.test.ts
index 0ef29505..29a783ae 100644
--- a/src/BufferLine.test.ts
+++ b/src/BufferLine.test.ts
@@ -3,7 +3,7 @@
* @license MIT
*/
import * as chai from 'chai';
-import { BufferLine } from './BufferLine';
+import { BufferLine, CellData, ContentMasks } from './BufferLine';
import { CharData, IBufferLine } from './Types';
import { NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, DEFAULT_ATTR } from './Buffer';
@@ -16,12 +16,38 @@ class TestBufferLine extends BufferLine {
public toArray(): CharData[] {
const result = [];
for (let i = 0; i < this.length; ++i) {
- result.push(this.get(i));
+ result.push(this.loadCell(i, new CellData()).getAsCharData());
}
return result;
}
}
+describe('CellData', () => {
+ it('CharData <--> CellData equality', () => {
+ const cell = new CellData();
+ // ASCII
+ cell.setFromCharData([123, 'a', 1, 'a'.charCodeAt(0)]);
+ chai.assert.deepEqual(cell.getAsCharData(), [123, 'a', 1, 'a'.charCodeAt(0)]);
+ chai.assert.equal(cell.isCombined(), 0);
+ // combining
+ cell.setFromCharData([123, 'e\u0301', 1, '\u0301'.charCodeAt(0)]);
+ chai.assert.deepEqual(cell.getAsCharData(), [123, 'e\u0301', 1, '\u0301'.charCodeAt(0)]);
+ chai.assert.equal(cell.isCombined(), ContentMasks.IS_COMBINED);
+ // surrogate
+ cell.setFromCharData([123, '𝄞', 1, 0x1D11E]);
+ chai.assert.deepEqual(cell.getAsCharData(), [123, '𝄞', 1, 0x1D11E]);
+ chai.assert.equal(cell.isCombined(), 0);
+ // surrogate + combining
+ cell.setFromCharData([123, '𓂀\u0301', 1, '𓂀\u0301'.charCodeAt(2)]);
+ chai.assert.deepEqual(cell.getAsCharData(), [123, '𓂀\u0301', 1, '𓂀\u0301'.charCodeAt(2)]);
+ chai.assert.equal(cell.isCombined(), ContentMasks.IS_COMBINED);
+ // wide char
+ cell.setFromCharData([123, '1', 2, '1'.charCodeAt(0)]);
+ chai.assert.deepEqual(cell.getAsCharData(), [123, '1', 2, '1'.charCodeAt(0)]);
+ chai.assert.equal(cell.isCombined(), 0);
+ });
+});
+
describe('BufferLine', function(): void {
it('ctor', function(): void {
let line: IBufferLine = new TestBufferLine(0);
@@ -29,23 +55,23 @@ describe('BufferLine', function(): void {
chai.expect(line.isWrapped).equals(false);
line = new TestBufferLine(10);
chai.expect(line.length).equals(10);
- chai.expect(line.get(0)).eql([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]);
+ chai.expect(line.loadCell(0, new CellData()).getAsCharData()).eql([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]);
chai.expect(line.isWrapped).equals(false);
line = new TestBufferLine(10, null, true);
chai.expect(line.length).equals(10);
- chai.expect(line.get(0)).eql([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]);
+ chai.expect(line.loadCell(0, new CellData()).getAsCharData()).eql([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]);
chai.expect(line.isWrapped).equals(true);
- line = new TestBufferLine(10, [123, 'a', 456, 'a'.charCodeAt(0)], true);
+ line = new TestBufferLine(10, CellData.fromCharData([123, 'a', 456, 'a'.charCodeAt(0)]), true);
chai.expect(line.length).equals(10);
- chai.expect(line.get(0)).eql([123, 'a', 456, 'a'.charCodeAt(0)]);
+ chai.expect(line.loadCell(0, new CellData()).getAsCharData()).eql([123, 'a', 456, 'a'.charCodeAt(0)]);
chai.expect(line.isWrapped).equals(true);
});
it('insertCells', function(): void {
const line = new TestBufferLine(3);
- line.set(0, [1, 'a', 0, 'a'.charCodeAt(0)]);
- line.set(1, [2, 'b', 0, 'b'.charCodeAt(0)]);
- line.set(2, [3, 'c', 0, 'c'.charCodeAt(0)]);
- line.insertCells(1, 3, [4, 'd', 0, 'd'.charCodeAt(0)]);
+ line.setCell(0, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]));
+ line.setCell(1, CellData.fromCharData([2, 'b', 0, 'b'.charCodeAt(0)]));
+ line.setCell(2, CellData.fromCharData([3, 'c', 0, 'c'.charCodeAt(0)]));
+ line.insertCells(1, 3, CellData.fromCharData([4, 'd', 0, 'd'.charCodeAt(0)]));
chai.expect(line.toArray()).eql([
[1, 'a', 0, 'a'.charCodeAt(0)],
[4, 'd', 0, 'd'.charCodeAt(0)],
@@ -54,12 +80,12 @@ describe('BufferLine', function(): void {
});
it('deleteCells', function(): void {
const line = new TestBufferLine(5);
- line.set(0, [1, 'a', 0, 'a'.charCodeAt(0)]);
- line.set(1, [2, 'b', 0, 'b'.charCodeAt(0)]);
- line.set(2, [3, 'c', 0, 'c'.charCodeAt(0)]);
- line.set(3, [4, 'd', 0, 'd'.charCodeAt(0)]);
- line.set(4, [5, 'e', 0, 'e'.charCodeAt(0)]);
- line.deleteCells(1, 2, [6, 'f', 0, 'f'.charCodeAt(0)]);
+ line.setCell(0, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]));
+ line.setCell(1, CellData.fromCharData([2, 'b', 0, 'b'.charCodeAt(0)]));
+ line.setCell(2, CellData.fromCharData([3, 'c', 0, 'c'.charCodeAt(0)]));
+ line.setCell(3, CellData.fromCharData([4, 'd', 0, 'd'.charCodeAt(0)]));
+ line.setCell(4, CellData.fromCharData([5, 'e', 0, 'e'.charCodeAt(0)]));
+ line.deleteCells(1, 2, CellData.fromCharData([6, 'f', 0, 'f'.charCodeAt(0)]));
chai.expect(line.toArray()).eql([
[1, 'a', 0, 'a'.charCodeAt(0)],
[4, 'd', 0, 'd'.charCodeAt(0)],
@@ -70,12 +96,12 @@ describe('BufferLine', function(): void {
});
it('replaceCells', function(): void {
const line = new TestBufferLine(5);
- line.set(0, [1, 'a', 0, 'a'.charCodeAt(0)]);
- line.set(1, [2, 'b', 0, 'b'.charCodeAt(0)]);
- line.set(2, [3, 'c', 0, 'c'.charCodeAt(0)]);
- line.set(3, [4, 'd', 0, 'd'.charCodeAt(0)]);
- line.set(4, [5, 'e', 0, 'e'.charCodeAt(0)]);
- line.replaceCells(2, 4, [6, 'f', 0, 'f'.charCodeAt(0)]);
+ line.setCell(0, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]));
+ line.setCell(1, CellData.fromCharData([2, 'b', 0, 'b'.charCodeAt(0)]));
+ line.setCell(2, CellData.fromCharData([3, 'c', 0, 'c'.charCodeAt(0)]));
+ line.setCell(3, CellData.fromCharData([4, 'd', 0, 'd'.charCodeAt(0)]));
+ line.setCell(4, CellData.fromCharData([5, 'e', 0, 'e'.charCodeAt(0)]));
+ line.replaceCells(2, 4, CellData.fromCharData([6, 'f', 0, 'f'.charCodeAt(0)]));
chai.expect(line.toArray()).eql([
[1, 'a', 0, 'a'.charCodeAt(0)],
[2, 'b', 0, 'b'.charCodeAt(0)],
@@ -86,12 +112,12 @@ describe('BufferLine', function(): void {
});
it('fill', function(): void {
const line = new TestBufferLine(5);
- line.set(0, [1, 'a', 0, 'a'.charCodeAt(0)]);
- line.set(1, [2, 'b', 0, 'b'.charCodeAt(0)]);
- line.set(2, [3, 'c', 0, 'c'.charCodeAt(0)]);
- line.set(3, [4, 'd', 0, 'd'.charCodeAt(0)]);
- line.set(4, [5, 'e', 0, 'e'.charCodeAt(0)]);
- line.fill([123, 'z', 0, 'z'.charCodeAt(0)]);
+ line.setCell(0, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]));
+ line.setCell(1, CellData.fromCharData([2, 'b', 0, 'b'.charCodeAt(0)]));
+ line.setCell(2, CellData.fromCharData([3, 'c', 0, 'c'.charCodeAt(0)]));
+ line.setCell(3, CellData.fromCharData([4, 'd', 0, 'd'.charCodeAt(0)]));
+ line.setCell(4, CellData.fromCharData([5, 'e', 0, 'e'.charCodeAt(0)]));
+ line.fill(CellData.fromCharData([123, 'z', 0, 'z'.charCodeAt(0)]));
chai.expect(line.toArray()).eql([
[123, 'z', 0, 'z'.charCodeAt(0)],
[123, 'z', 0, 'z'.charCodeAt(0)],
@@ -102,11 +128,11 @@ describe('BufferLine', function(): void {
});
it('clone', function(): void {
const line = new TestBufferLine(5, null, true);
- line.set(0, [1, 'a', 0, 'a'.charCodeAt(0)]);
- line.set(1, [2, 'b', 0, 'b'.charCodeAt(0)]);
- line.set(2, [3, 'c', 0, 'c'.charCodeAt(0)]);
- line.set(3, [4, 'd', 0, 'd'.charCodeAt(0)]);
- line.set(4, [5, 'e', 0, 'e'.charCodeAt(0)]);
+ line.setCell(0, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]));
+ line.setCell(1, CellData.fromCharData([2, 'b', 0, 'b'.charCodeAt(0)]));
+ line.setCell(2, CellData.fromCharData([3, 'c', 0, 'c'.charCodeAt(0)]));
+ line.setCell(3, CellData.fromCharData([4, 'd', 0, 'd'.charCodeAt(0)]));
+ line.setCell(4, CellData.fromCharData([5, 'e', 0, 'e'.charCodeAt(0)]));
const line2 = line.clone();
chai.expect(TestBufferLine.prototype.toArray.apply(line2)).eql(line.toArray());
chai.expect(line2.length).equals(line.length);
@@ -114,12 +140,12 @@ describe('BufferLine', function(): void {
});
it('copyFrom', function(): void {
const line = new TestBufferLine(5);
- line.set(0, [1, 'a', 0, 'a'.charCodeAt(0)]);
- line.set(1, [2, 'b', 0, 'b'.charCodeAt(0)]);
- line.set(2, [3, 'c', 0, 'c'.charCodeAt(0)]);
- line.set(3, [4, 'd', 0, 'd'.charCodeAt(0)]);
- line.set(4, [5, 'e', 0, 'e'.charCodeAt(0)]);
- const line2 = new TestBufferLine(5, [1, 'a', 0, 'a'.charCodeAt(0)], true);
+ line.setCell(0, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]));
+ line.setCell(1, CellData.fromCharData([2, 'b', 0, 'b'.charCodeAt(0)]));
+ line.setCell(2, CellData.fromCharData([3, 'c', 0, 'c'.charCodeAt(0)]));
+ line.setCell(3, CellData.fromCharData([4, 'd', 0, 'd'.charCodeAt(0)]));
+ line.setCell(4, CellData.fromCharData([5, 'e', 0, 'e'.charCodeAt(0)]));
+ const line2 = new TestBufferLine(5, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), true);
line2.copyFrom(line);
chai.expect(line2.toArray()).eql(line.toArray());
chai.expect(line2.length).equals(line.length);
@@ -129,9 +155,9 @@ describe('BufferLine', function(): void {
// CHAR_DATA_CODE_INDEX resembles current behavior in InputHandler.print
// --> set code to the last charCodeAt value of the string
// Note: needs to be fixed once the string pointer is in place
- const line = new TestBufferLine(2, [1, 'e\u0301', 0, '\u0301'.charCodeAt(0)]);
+ const line = new TestBufferLine(2, CellData.fromCharData([1, 'e\u0301', 0, '\u0301'.charCodeAt(0)]));
chai.expect(line.toArray()).eql([[1, 'e\u0301', 0, '\u0301'.charCodeAt(0)], [1, 'e\u0301', 0, '\u0301'.charCodeAt(0)]]);
- const line2 = new TestBufferLine(5, [1, 'a', 0, '\u0301'.charCodeAt(0)], true);
+ const line2 = new TestBufferLine(5, CellData.fromCharData([1, 'a', 0, '\u0301'.charCodeAt(0)]), true);
line2.copyFrom(line);
chai.expect(line2.toArray()).eql(line.toArray());
const line3 = line.clone();
@@ -139,81 +165,81 @@ describe('BufferLine', function(): void {
});
describe('resize', function(): void {
it('enlarge(false)', function(): void {
- const line = new TestBufferLine(5, [1, 'a', 0, 'a'.charCodeAt(0)], false);
- line.resize(10, [1, 'a', 0, 'a'.charCodeAt(0)]);
+ const line = new TestBufferLine(5, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), false);
+ line.resize(10, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]));
chai.expect(line.toArray()).eql(Array(10).fill([1, 'a', 0, 'a'.charCodeAt(0)]));
});
it('enlarge(true)', function(): void {
- const line = new TestBufferLine(5, [1, 'a', 0, 'a'.charCodeAt(0)], false);
- line.resize(10, [1, 'a', 0, 'a'.charCodeAt(0)]);
+ const line = new TestBufferLine(5, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), false);
+ line.resize(10, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]));
chai.expect(line.toArray()).eql(Array(10).fill([1, 'a', 0, 'a'.charCodeAt(0)]));
});
it('shrink(true) - should apply new size', function(): void {
- const line = new TestBufferLine(10, [1, 'a', 0, 'a'.charCodeAt(0)], false);
- line.resize(5, [1, 'a', 0, 'a'.charCodeAt(0)]);
+ const line = new TestBufferLine(10, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), false);
+ line.resize(5, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]));
chai.expect(line.toArray()).eql(Array(5).fill([1, 'a', 0, 'a'.charCodeAt(0)]));
});
it('shrink to 0 length', function(): void {
- const line = new TestBufferLine(10, [1, 'a', 0, 'a'.charCodeAt(0)], false);
- line.resize(0, [1, 'a', 0, 'a'.charCodeAt(0)]);
+ const line = new TestBufferLine(10, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), false);
+ line.resize(0, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]));
chai.expect(line.toArray()).eql(Array(0).fill([1, 'a', 0, 'a'.charCodeAt(0)]));
});
it('should remove combining data on replaced cells after shrinking then enlarging', () => {
- const line = new TestBufferLine(10, [1, 'a', 0, 'a'.charCodeAt(0)], false);
+ const line = new TestBufferLine(10, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), false);
line.set(2, [ null, '😁', 1, '😁'.charCodeAt(0) ]);
line.set(9, [ null, '😁', 1, '😁'.charCodeAt(0) ]);
chai.expect(line.translateToString()).eql('aa😁aaaaaa😁');
chai.expect(Object.keys(line.combined).length).eql(2);
- line.resize(5, [1, 'a', 0, 'a'.charCodeAt(0)]);
+ line.resize(5, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]));
chai.expect(line.translateToString()).eql('aa😁aa');
- line.resize(10, [1, 'a', 0, 'a'.charCodeAt(0)]);
+ line.resize(10, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]));
chai.expect(line.translateToString()).eql('aa😁aaaaaaa');
chai.expect(Object.keys(line.combined).length).eql(1);
});
});
describe('getTrimLength', function(): void {
it('empty line', function(): void {
- const line = new TestBufferLine(10, [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE], false);
+ const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false);
chai.expect(line.getTrimmedLength()).equal(0);
});
it('ASCII', function(): void {
- const line = new TestBufferLine(10, [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE], false);
- line.set(0, [1, 'a', 1, 'a'.charCodeAt(0)]);
- line.set(2, [1, 'a', 1, 'a'.charCodeAt(0)]);
+ const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false);
+ line.setCell(0, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
+ line.setCell(2, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.expect(line.getTrimmedLength()).equal(3);
});
it('surrogate', function(): void {
- const line = new TestBufferLine(10, [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE], false);
- line.set(0, [1, 'a', 1, 'a'.charCodeAt(0)]);
- line.set(2, [1, '𝄞', 1, '𝄞'.charCodeAt(0)]);
+ const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false);
+ line.setCell(0, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
+ line.setCell(2, CellData.fromCharData([1, '𝄞', 1, '𝄞'.charCodeAt(0)]));
chai.expect(line.getTrimmedLength()).equal(3);
});
it('combining', function(): void {
- const line = new TestBufferLine(10, [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE], false);
- line.set(0, [1, 'a', 1, 'a'.charCodeAt(0)]);
- line.set(2, [1, 'e\u0301', 1, '\u0301'.charCodeAt(0)]);
+ const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false);
+ line.setCell(0, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
+ line.setCell(2, CellData.fromCharData([1, 'e\u0301', 1, '\u0301'.charCodeAt(0)]));
chai.expect(line.getTrimmedLength()).equal(3);
});
it('fullwidth', function(): void {
- const line = new TestBufferLine(10, [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE], false);
- line.set(0, [1, 'a', 1, 'a'.charCodeAt(0)]);
- line.set(2, [1, '1', 2, '1'.charCodeAt(0)]);
- line.set(3, [0, '', 0, undefined]);
+ const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false);
+ line.setCell(0, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
+ line.setCell(2, CellData.fromCharData([1, '1', 2, '1'.charCodeAt(0)]));
+ line.setCell(3, CellData.fromCharData([0, '', 0, undefined]));
chai.expect(line.getTrimmedLength()).equal(4); // also counts null cell after fullwidth
});
});
describe('translateToString with and w\'o trimming', function(): void {
it('empty line', function(): void {
- const line = new TestBufferLine(10, [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE], false);
+ const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false);
chai.expect(line.translateToString(false)).equal(' ');
chai.expect(line.translateToString(true)).equal('');
});
it('ASCII', function(): void {
- const line = new TestBufferLine(10, [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE], false);
- line.set(0, [1, 'a', 1, 'a'.charCodeAt(0)]);
- line.set(2, [1, 'a', 1, 'a'.charCodeAt(0)]);
- line.set(4, [1, 'a', 1, 'a'.charCodeAt(0)]);
- line.set(5, [1, 'a', 1, 'a'.charCodeAt(0)]);
+ const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false);
+ line.setCell(0, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
+ line.setCell(2, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
+ line.setCell(4, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
+ line.setCell(5, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.expect(line.translateToString(false)).equal('a a aa ');
chai.expect(line.translateToString(true)).equal('a a aa');
chai.expect(line.translateToString(false, 0, 5)).equal('a a a');
@@ -225,11 +251,11 @@ describe('BufferLine', function(): void {
});
it('surrogate', function(): void {
- const line = new TestBufferLine(10, [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE], false);
- line.set(0, [1, 'a', 1, 'a'.charCodeAt(0)]);
- line.set(2, [1, '𝄞', 1, '𝄞'.charCodeAt(0)]);
- line.set(4, [1, '𝄞', 1, '𝄞'.charCodeAt(0)]);
- line.set(5, [1, '𝄞', 1, '𝄞'.charCodeAt(0)]);
+ const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false);
+ line.setCell(0, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
+ line.setCell(2, CellData.fromCharData([1, '𝄞', 1, '𝄞'.charCodeAt(0)]));
+ line.setCell(4, CellData.fromCharData([1, '𝄞', 1, '𝄞'.charCodeAt(0)]));
+ line.setCell(5, CellData.fromCharData([1, '𝄞', 1, '𝄞'.charCodeAt(0)]));
chai.expect(line.translateToString(false)).equal('a 𝄞 𝄞𝄞 ');
chai.expect(line.translateToString(true)).equal('a 𝄞 𝄞𝄞');
chai.expect(line.translateToString(false, 0, 5)).equal('a 𝄞 𝄞');
@@ -240,11 +266,11 @@ describe('BufferLine', function(): void {
chai.expect(line.translateToString(true, 0, 3)).equal('a 𝄞');
});
it('combining', function(): void {
- const line = new TestBufferLine(10, [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE], false);
- line.set(0, [1, 'a', 1, 'a'.charCodeAt(0)]);
- line.set(2, [1, 'e\u0301', 1, '\u0301'.charCodeAt(0)]);
- line.set(4, [1, 'e\u0301', 1, '\u0301'.charCodeAt(0)]);
- line.set(5, [1, 'e\u0301', 1, '\u0301'.charCodeAt(0)]);
+ const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false);
+ line.setCell(0, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
+ line.setCell(2, CellData.fromCharData([1, 'e\u0301', 1, '\u0301'.charCodeAt(0)]));
+ line.setCell(4, CellData.fromCharData([1, 'e\u0301', 1, '\u0301'.charCodeAt(0)]));
+ line.setCell(5, CellData.fromCharData([1, 'e\u0301', 1, '\u0301'.charCodeAt(0)]));
chai.expect(line.translateToString(false)).equal('a e\u0301 e\u0301e\u0301 ');
chai.expect(line.translateToString(true)).equal('a e\u0301 e\u0301e\u0301');
chai.expect(line.translateToString(false, 0, 5)).equal('a e\u0301 e\u0301');
@@ -255,14 +281,14 @@ describe('BufferLine', function(): void {
chai.expect(line.translateToString(true, 0, 3)).equal('a e\u0301');
});
it('fullwidth', function(): void {
- const line = new TestBufferLine(10, [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE], false);
- line.set(0, [1, 'a', 1, 'a'.charCodeAt(0)]);
- line.set(2, [1, '1', 2, '1'.charCodeAt(0)]);
- line.set(3, [0, '', 0, undefined]);
- line.set(5, [1, '1', 2, '1'.charCodeAt(0)]);
- line.set(6, [0, '', 0, undefined]);
- line.set(7, [1, '1', 2, '1'.charCodeAt(0)]);
- line.set(8, [0, '', 0, undefined]);
+ const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false);
+ line.setCell(0, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
+ line.setCell(2, CellData.fromCharData([1, '1', 2, '1'.charCodeAt(0)]));
+ line.setCell(3, CellData.fromCharData([0, '', 0, undefined]));
+ line.setCell(5, CellData.fromCharData([1, '1', 2, '1'.charCodeAt(0)]));
+ line.setCell(6, CellData.fromCharData([0, '', 0, undefined]));
+ line.setCell(7, CellData.fromCharData([1, '1', 2, '1'.charCodeAt(0)]));
+ line.setCell(8, CellData.fromCharData([0, '', 0, undefined]));
chai.expect(line.translateToString(false)).equal('a 1 11 ');
chai.expect(line.translateToString(true)).equal('a 1 11');
chai.expect(line.translateToString(false, 0, 7)).equal('a 1 1');
@@ -279,12 +305,12 @@ describe('BufferLine', function(): void {
chai.expect(line.translateToString(true, 0, 2)).equal('a ');
});
it('space at end', function(): void {
- const line = new TestBufferLine(10, [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE], false);
- line.set(0, [1, 'a', 1, 'a'.charCodeAt(0)]);
- line.set(2, [1, 'a', 1, 'a'.charCodeAt(0)]);
- line.set(4, [1, 'a', 1, 'a'.charCodeAt(0)]);
- line.set(5, [1, 'a', 1, 'a'.charCodeAt(0)]);
- line.set(6, [1, ' ', 1, ' '.charCodeAt(0)]);
+ const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false);
+ line.setCell(0, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
+ line.setCell(2, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
+ line.setCell(4, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
+ line.setCell(5, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
+ line.setCell(6, CellData.fromCharData([1, ' ', 1, ' '.charCodeAt(0)]));
chai.expect(line.translateToString(false)).equal('a a aa ');
chai.expect(line.translateToString(true)).equal('a a aa ');
});
@@ -292,14 +318,52 @@ describe('BufferLine', function(): void {
// sanity check - broken line with invalid out of bound null width cells
// this can atm happen with deleting/inserting chars in inputhandler by "breaking"
// fullwidth pairs --> needs to be fixed after settling BufferLine impl
- const line = new TestBufferLine(10, [DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE], false);
+ const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false);
chai.expect(line.translateToString(false)).equal(' ');
chai.expect(line.translateToString(true)).equal('');
});
it('should work with endCol=0', () => {
- const line = new TestBufferLine(10, [DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE], false);
- line.set(0, [1, 'a', 1, 'a'.charCodeAt(0)]);
+ const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false);
+ line.setCell(0, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.expect(line.translateToString(true, 0, 0)).equal('');
});
});
+ describe('addCharToCell', () => {
+ it('should set width to 1 for empty cell', () => {
+ const line = new TestBufferLine(3, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false);
+ line.addCodepointToCell(0, '\u0301'.charCodeAt(0));
+ const cell = line.loadCell(0, new CellData());
+ // chars contains single combining char
+ // width is set to 1
+ chai.assert.deepEqual(cell.getAsCharData(), [DEFAULT_ATTR, '\u0301', 1, 0x0301]);
+ // do not account a single combining char as combined
+ chai.assert.equal(cell.isCombined(), 0);
+ });
+ it('should add char to combining string in cell', () => {
+ const line = new TestBufferLine(3, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false);
+ const cell = line .loadCell(0, new CellData());
+ cell.setFromCharData([123, 'e\u0301', 1, 'e\u0301'.charCodeAt(1)]);
+ line.setCell(0, cell);
+ line.addCodepointToCell(0, '\u0301'.charCodeAt(0));
+ line.loadCell(0, cell);
+ // chars contains 3 chars
+ // width is set to 1
+ chai.assert.deepEqual(cell.getAsCharData(), [123, 'e\u0301\u0301', 1, 0x0301]);
+ // do not account a single combining char as combined
+ chai.assert.equal(cell.isCombined(), ContentMasks.IS_COMBINED);
+ });
+ it('should create combining string on taken cell', () => {
+ const line = new TestBufferLine(3, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false);
+ const cell = line .loadCell(0, new CellData());
+ cell.setFromCharData([123, 'e', 1, 'e'.charCodeAt(1)]);
+ line.setCell(0, cell);
+ line.addCodepointToCell(0, '\u0301'.charCodeAt(0));
+ line.loadCell(0, cell);
+ // chars contains 2 chars
+ // width is set to 1
+ chai.assert.deepEqual(cell.getAsCharData(), [123, 'e\u0301', 1, 0x0301]);
+ // do not account a single combining char as combined
+ chai.assert.equal(cell.isCombined(), ContentMasks.IS_COMBINED);
+ });
+ });
});
diff --git a/src/BufferLine.ts b/src/BufferLine.ts
index 6bc30586..c4aa55be 100644
--- a/src/BufferLine.ts
+++ b/src/BufferLine.ts
@@ -2,112 +2,405 @@
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
* @license MIT
*/
-import { CharData, IBufferLine } from './Types';
-import { NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR, WHITESPACE_CELL_CHAR } from './Buffer';
+import { CharData, IBufferLine, ICellData } from './Types';
+import { NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR, CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, WHITESPACE_CELL_CHAR, CHAR_DATA_ATTR_INDEX } from './Buffer';
+import { stringFromCodePoint } from './core/input/TextDecoder';
+
+
+/**
+ * buffer memory layout:
+ *
+ * | uint32_t | uint32_t | uint32_t |
+ * | `content` | `FG` | `BG` |
+ * | wcwidth(2) comb(1) codepoint(21) | flags(8) R(8) G(8) B(8) | flags(8) R(8) G(8) B(8) |
+ */
/** typed array slots taken by one cell */
const CELL_SIZE = 3;
-/** cell member indices */
+/**
+ * Cell member indices.
+ *
+ * Direct access:
+ * `content = data[column * CELL_SIZE + Cell.CONTENT];`
+ * `fg = data[column * CELL_SIZE + Cell.FG];`
+ * `bg = data[column * CELL_SIZE + Cell.BG];`
+ */
const enum Cell {
- FLAGS = 0,
- STRING = 1,
- WIDTH = 2
+ CONTENT = 0,
+ FG = 1, // currently simply holds all known attrs
+ BG = 2 // currently unused
+}
+
+/**
+ * Bitmasks for accessing data in `content`.
+ */
+export const enum ContentMasks {
+ /**
+ * bit 1..21 codepoint, max allowed in UTF32 is 0x10FFFF (21 bits taken)
+ * read: `codepoint = content & Content.codepointMask;`
+ * write: `content |= codepoint & Content.codepointMask;`
+ * shortcut if precondition `codepoint <= 0x10FFFF` is met:
+ * `content |= codepoint;`
+ */
+ CODEPOINT = 0x1FFFFF,
+
+ /**
+ * bit 22 flag indication whether a cell contains combined content
+ * read: `isCombined = content & Content.isCombined;`
+ * set: `content |= Content.isCombined;`
+ * clear: `content &= ~Content.isCombined;`
+ */
+ IS_COMBINED = 0x200000, // 1 << 21
+
+ /**
+ * bit 1..22 mask to check whether a cell contains any string data
+ * we need to check for codepoint and isCombined bits to see
+ * whether a cell contains anything
+ * read: `isEmtpy = !(content & Content.hasContent)`
+ */
+ HAS_CONTENT = 0x3FFFFF,
+
+ /**
+ * bit 23..24 wcwidth value of cell, takes 2 bits (ranges from 0..2)
+ * read: `width = (content & Content.widthMask) >> Content.widthShift;`
+ * `hasWidth = content & Content.widthMask;`
+ * as long as wcwidth is highest value in `content`:
+ * `width = content >> Content.widthShift;`
+ * write: `content |= (width << Content.widthShift) & Content.widthMask;`
+ * shortcut if precondition `0 <= width <= 3` is met:
+ * `content |= width << Content.widthShift;`
+ */
+ WIDTH = 0xC00000 // 3 << 22
+}
+
+const WIDTH_MASK_SHIFT = 22;
+
+/**
+ * CellData - represents a single Cell in the terminal buffer.
+ */
+export class CellData implements ICellData {
+
+ /** Helper to create CellData from CharData. */
+ public static fromCharData(value: CharData): CellData {
+ const obj = new CellData();
+ obj.setFromCharData(value);
+ return obj;
+ }
+
+ /** Primitives from terminal buffer. */
+ public content: number = 0;
+ public fg: number = 0;
+ public bg: number = 0;
+ public combinedData: string = '';
+
+ /** Whether cell contains a combined string. */
+ public isCombined(): number {
+ return this.content & ContentMasks.IS_COMBINED;
+ }
+
+ /** Width of the cell. */
+ public getWidth(): number {
+ return this.content >> WIDTH_MASK_SHIFT;
+ }
+
+ /** JS string of the content. */
+ public getChars(): string {
+ if (this.content & ContentMasks.IS_COMBINED) {
+ return this.combinedData;
+ }
+ if (this.content & ContentMasks.CODEPOINT) {
+ return stringFromCodePoint(this.content & ContentMasks.CODEPOINT);
+ }
+ return '';
+ }
+
+ /**
+ * Codepoint of cell
+ * Note this returns the UTF32 codepoint of single chars,
+ * if content is a combined string it returns the codepoint
+ * of the last char in string to be in line with code in CharData.
+ * */
+ public getCode(): number {
+ return (this.isCombined())
+ ? this.combinedData.charCodeAt(this.combinedData.length - 1)
+ : this.content & ContentMasks.CODEPOINT;
+ }
+
+ /** Set data from CharData */
+ public setFromCharData(value: CharData): void {
+ this.fg = value[CHAR_DATA_ATTR_INDEX];
+ this.bg = 0;
+ let combined = false;
+
+ // surrogates and combined strings need special treatment
+ if (value[CHAR_DATA_CHAR_INDEX].length > 2) {
+ combined = true;
+ } else if (value[CHAR_DATA_CHAR_INDEX].length === 2) {
+ const code = value[CHAR_DATA_CHAR_INDEX].charCodeAt(0);
+ // if the 2-char string is a surrogate create single codepoint
+ // everything else is combined
+ if (0xD800 <= code && code <= 0xDBFF) {
+ const second = value[CHAR_DATA_CHAR_INDEX].charCodeAt(1);
+ if (0xDC00 <= second && second <= 0xDFFF) {
+ this.content = ((code - 0xD800) * 0x400 + second - 0xDC00 + 0x10000) | (value[CHAR_DATA_WIDTH_INDEX] << WIDTH_MASK_SHIFT);
+ } else {
+ combined = true;
+ }
+ } else {
+ combined = true;
+ }
+ } else {
+ this.content = value[CHAR_DATA_CHAR_INDEX].charCodeAt(0) | (value[CHAR_DATA_WIDTH_INDEX] << WIDTH_MASK_SHIFT);
+ }
+ if (combined) {
+ this.combinedData = value[CHAR_DATA_CHAR_INDEX];
+ this.content = ContentMasks.IS_COMBINED | (value[CHAR_DATA_WIDTH_INDEX] << WIDTH_MASK_SHIFT);
+ }
+ }
+
+ /** Get data as CharData. */
+ public getAsCharData(): CharData {
+ return [this.fg, this.getChars(), this.getWidth(), this.getCode()];
+ }
}
-/** single vs. combined char distinction */
-const IS_COMBINED_BIT_MASK = 0x80000000;
/**
* Typed array based bufferline implementation.
+ *
+ * There are 2 ways to insert data into the cell buffer:
+ * - `setCellFromCodepoint` + `addCodepointToCell`
+ * Use these for data that is already UTF32.
+ * Used during normal input in `InputHandler` for faster buffer access.
+ * - `setCell`
+ * This method takes a CellData object and stores the data in the buffer.
+ * Use `CellData.fromCharData` to create the CellData object (e.g. from JS string).
+ *
+ * To retrieve data from the buffer use either one of the primitive methods
+ * (if only one particular value is needed) or `loadCell`. For `loadCell` in a loop
+ * memory allocs / GC pressure can be greatly reduced by reusing the CellData object.
*/
export class BufferLine implements IBufferLine {
protected _data: Uint32Array | null = null;
protected _combined: {[index: number]: string} = {};
public length: number;
- constructor(cols: number, fillCharData?: CharData, public isWrapped: boolean = false) {
- if (!fillCharData) {
- fillCharData = [0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE];
- }
+ constructor(cols: number, fillCellData?: ICellData, public isWrapped: boolean = false) {
if (cols) {
this._data = new Uint32Array(cols * CELL_SIZE);
+ const cell = fillCellData || CellData.fromCharData([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]);
for (let i = 0; i < cols; ++i) {
- this.set(i, fillCharData);
+ this.setCell(i, cell);
}
}
this.length = cols;
}
+ /**
+ * Get cell data CharData.
+ * @deprecated
+ */
public get(index: number): CharData {
- const stringData = this._data[index * CELL_SIZE + Cell.STRING];
+ const content = this._data[index * CELL_SIZE + Cell.CONTENT];
+ const cp = content & ContentMasks.CODEPOINT;
return [
- this._data[index * CELL_SIZE + Cell.FLAGS],
- (stringData & IS_COMBINED_BIT_MASK)
+ this._data[index * CELL_SIZE + Cell.FG],
+ (content & ContentMasks.IS_COMBINED)
? this._combined[index]
- : (stringData) ? String.fromCharCode(stringData) : '',
- this._data[index * CELL_SIZE + Cell.WIDTH],
- (stringData & IS_COMBINED_BIT_MASK)
+ : (cp) ? stringFromCodePoint(cp) : '',
+ content >> WIDTH_MASK_SHIFT,
+ (content & ContentMasks.IS_COMBINED)
? this._combined[index].charCodeAt(this._combined[index].length - 1)
- : stringData
+ : cp
];
}
- public getWidth(index: number): number {
- return this._data[index * CELL_SIZE + Cell.WIDTH];
- }
-
+ /**
+ * Set cell data from CharData.
+ * @deprecated
+ */
public set(index: number, value: CharData): void {
- this._data[index * CELL_SIZE + Cell.FLAGS] = value[0];
- if (value[1].length > 1) {
+ this._data[index * CELL_SIZE + Cell.FG] = value[CHAR_DATA_ATTR_INDEX];
+ if (value[CHAR_DATA_CHAR_INDEX].length > 1) {
this._combined[index] = value[1];
- this._data[index * CELL_SIZE + Cell.STRING] = index | IS_COMBINED_BIT_MASK;
+ this._data[index * CELL_SIZE + Cell.CONTENT] = index | ContentMasks.IS_COMBINED | (value[CHAR_DATA_WIDTH_INDEX] << WIDTH_MASK_SHIFT);
} else {
- this._data[index * CELL_SIZE + Cell.STRING] = value[1].charCodeAt(0);
+ this._data[index * CELL_SIZE + Cell.CONTENT] = value[CHAR_DATA_CHAR_INDEX].charCodeAt(0) | (value[CHAR_DATA_WIDTH_INDEX] << WIDTH_MASK_SHIFT);
}
- this._data[index * CELL_SIZE + Cell.WIDTH] = value[2];
}
- public insertCells(pos: number, n: number, fillCharData: CharData): void {
+ /**
+ * primitive getters
+ * use these when only one value is needed, otherwise use `loadCell`
+ */
+ public getWidth(index: number): number {
+ return this._data[index * CELL_SIZE + Cell.CONTENT] >> WIDTH_MASK_SHIFT;
+ }
+
+ /** Test whether content has width. */
+ public hasWidth(index: number): number {
+ return this._data[index * CELL_SIZE + Cell.CONTENT] & ContentMasks.WIDTH;
+ }
+
+ /** Get FG cell component. */
+ public getFg(index: number): number {
+ return this._data[index * CELL_SIZE + Cell.FG];
+ }
+
+ /** Get BG cell component. */
+ public getBg(index: number): number {
+ return this._data[index * CELL_SIZE + Cell.BG];
+ }
+
+ /**
+ * Test whether contains any chars.
+ * Basically an empty has no content, but other cells might differ in FG/BG
+ * from real empty cells.
+ * */
+ public hasContent(index: number): number {
+ return this._data[index * CELL_SIZE + Cell.CONTENT] & ContentMasks.HAS_CONTENT;
+ }
+
+ /**
+ * Get codepoint of the cell.
+ * To be in line with `code` in CharData this either returns
+ * a single UTF32 codepoint or the last codepoint of a combined string.
+ */
+ public getCodePoint(index: number): number {
+ const content = this._data[index * CELL_SIZE + Cell.CONTENT];
+ if (content & ContentMasks.IS_COMBINED) {
+ return this._combined[index].charCodeAt(this._combined[index].length - 1);
+ }
+ return content & ContentMasks.CODEPOINT;
+ }
+
+ /** Test whether the cell contains a combined string. */
+ public isCombined(index: number): number {
+ return this._data[index * CELL_SIZE + Cell.CONTENT] & ContentMasks.IS_COMBINED;
+ }
+
+ /** Returns the string content of the cell. */
+ public getString(index: number): string {
+ const content = this._data[index * CELL_SIZE + Cell.CONTENT];
+ if (content & ContentMasks.IS_COMBINED) {
+ return this._combined[index];
+ }
+ if (content & ContentMasks.CODEPOINT) {
+ return stringFromCodePoint(content & ContentMasks.CODEPOINT);
+ }
+ // return empty string for empty cells
+ return '';
+ }
+
+ /**
+ * Load data at `index` into `cell`. This is used to access cells in a way that's more friendly
+ * to GC as it significantly reduced the amount of new objects/references needed.
+ */
+ public loadCell(index: number, cell: ICellData): ICellData {
+ const startIndex = index * CELL_SIZE;
+ cell.content = this._data[startIndex + Cell.CONTENT];
+ cell.fg = this._data[startIndex + Cell.FG];
+ cell.bg = this._data[startIndex + Cell.BG];
+ if (cell.content & ContentMasks.IS_COMBINED) {
+ cell.combinedData = this._combined[index];
+ }
+ return cell;
+ }
+
+ /**
+ * Set data at `index` to `cell`.
+ */
+ public setCell(index: number, cell: ICellData): void {
+ if (cell.content & ContentMasks.IS_COMBINED) {
+ this._combined[index] = cell.combinedData;
+ }
+ this._data[index * CELL_SIZE + Cell.CONTENT] = cell.content;
+ this._data[index * CELL_SIZE + Cell.FG] = cell.fg;
+ this._data[index * CELL_SIZE + Cell.BG] = cell.bg;
+ }
+
+ /**
+ * Set cell data from input handler.
+ * Since the input handler see the incoming chars as UTF32 codepoints,
+ * it gets an optimized access method.
+ */
+ public setCellFromCodePoint(index: number, codePoint: number, width: number, fg: number, bg: number): void {
+ this._data[index * CELL_SIZE + Cell.CONTENT] = codePoint | (width << WIDTH_MASK_SHIFT);
+ this._data[index * CELL_SIZE + Cell.FG] = fg;
+ this._data[index * CELL_SIZE + Cell.BG] = bg;
+ }
+
+ /**
+ * Add a codepoint to a cell from input handler.
+ * During input stage combining chars with a width of 0 follow and stack
+ * onto a leading char. Since we already set the attrs
+ * by the previous `setDataFromCodePoint` call, we can omit it here.
+ */
+ public addCodepointToCell(index: number, codePoint: number): void {
+ let content = this._data[index * CELL_SIZE + Cell.CONTENT];
+ if (content & ContentMasks.IS_COMBINED) {
+ // we already have a combined string, simply add
+ this._combined[index] += stringFromCodePoint(codePoint);
+ } else {
+ if (content & ContentMasks.CODEPOINT) {
+ // normal case for combining chars:
+ // - move current leading char + new one into combined string
+ // - set combined flag
+ this._combined[index] = stringFromCodePoint(content & ContentMasks.CODEPOINT) + stringFromCodePoint(codePoint);
+ content &= ~ContentMasks.CODEPOINT; // set codepoint in buffer to 0
+ content |= ContentMasks.IS_COMBINED;
+ } else {
+ // should not happen - we actually have no data in the cell yet
+ // simply set the data in the cell buffer with a width of 1
+ content = codePoint | (1 << WIDTH_MASK_SHIFT);
+ }
+ this._data[index * CELL_SIZE + Cell.CONTENT] = content;
+ }
+ }
+
+ public insertCells(pos: number, n: number, fillCellData: ICellData): void {
pos %= this.length;
if (n < this.length - pos) {
+ const cell = new CellData();
for (let i = this.length - pos - n - 1; i >= 0; --i) {
- this.set(pos + n + i, this.get(pos + i));
+ this.setCell(pos + n + i, this.loadCell(pos + i, cell));
}
for (let i = 0; i < n; ++i) {
- this.set(pos + i, fillCharData);
+ this.setCell(pos + i, fillCellData);
}
} else {
for (let i = pos; i < this.length; ++i) {
- this.set(i, fillCharData);
+ this.setCell(i, fillCellData);
}
}
}
- public deleteCells(pos: number, n: number, fillCharData: CharData): void {
+ public deleteCells(pos: number, n: number, fillCellData: ICellData): void {
pos %= this.length;
if (n < this.length - pos) {
+ const cell = new CellData();
for (let i = 0; i < this.length - pos - n; ++i) {
- this.set(pos + i, this.get(pos + n + i));
+ this.setCell(pos + i, this.loadCell(pos + n + i, cell));
}
for (let i = this.length - n; i < this.length; ++i) {
- this.set(i, fillCharData);
+ this.setCell(i, fillCellData);
}
} else {
for (let i = pos; i < this.length; ++i) {
- this.set(i, fillCharData);
+ this.setCell(i, fillCellData);
}
}
}
- public replaceCells(start: number, end: number, fillCharData: CharData): void {
+ public replaceCells(start: number, end: number, fillCellData: ICellData): void {
while (start < end && start < this.length) {
- this.set(start++, fillCharData);
+ this.setCell(start++, fillCellData);
}
}
- public resize(cols: number, fillCharData: CharData): void {
+ public resize(cols: number, fillCellData: ICellData): void {
if (cols === this.length) {
return;
}
@@ -122,7 +415,7 @@ export class BufferLine implements IBufferLine {
}
this._data = data;
for (let i = this.length; i < cols; ++i) {
- this.set(i, fillCharData);
+ this.setCell(i, fillCellData);
}
} else {
if (cols) {
@@ -146,10 +439,10 @@ export class BufferLine implements IBufferLine {
}
/** fill a line with fillCharData */
- public fill(fillCharData: CharData): void {
+ public fill(fillCellData: ICellData): void {
this._combined = {};
for (let i = 0; i < this.length; ++i) {
- this.set(i, fillCharData);
+ this.setCell(i, fillCellData);
}
}
@@ -172,8 +465,6 @@ export class BufferLine implements IBufferLine {
/** create a new clone */
public clone(): IBufferLine {
const newLine = new BufferLine(0);
- // creation of new typed array from another is actually pretty slow :(
- // still faster than copying values one by one
newLine._data = new Uint32Array(this._data);
newLine.length = this.length;
for (const el in this._combined) {
@@ -185,8 +476,8 @@ export class BufferLine implements IBufferLine {
public getTrimmedLength(): number {
for (let i = this.length - 1; i >= 0; --i) {
- if (this._data[i * CELL_SIZE + Cell.STRING] !== 0) { // 0 ==> ''.charCodeAt(0) ==> NaN ==> 0
- return i + this._data[i * CELL_SIZE + Cell.WIDTH];
+ if ((this._data[i * CELL_SIZE + Cell.CONTENT] & ContentMasks.HAS_CONTENT)) {
+ return i + (this._data[i * CELL_SIZE + Cell.CONTENT] >> WIDTH_MASK_SHIFT);
}
}
return 0;
@@ -224,9 +515,10 @@ export class BufferLine implements IBufferLine {
}
let result = '';
while (startCol < endCol) {
- const stringData = this._data[startCol * CELL_SIZE + Cell.STRING];
- result += (stringData & IS_COMBINED_BIT_MASK) ? this._combined[startCol] : (stringData) ? String.fromCharCode(stringData) : WHITESPACE_CELL_CHAR;
- startCol += this._data[startCol * CELL_SIZE + Cell.WIDTH] || 1;
+ const content = this._data[startCol * CELL_SIZE + Cell.CONTENT];
+ const cp = content & ContentMasks.CODEPOINT;
+ result += (content & ContentMasks.IS_COMBINED) ? this._combined[startCol] : (cp) ? stringFromCodePoint(cp) : WHITESPACE_CELL_CHAR;
+ startCol += (content >> WIDTH_MASK_SHIFT) || 1; // always advance by 1
}
return result;
}
diff --git a/src/BufferReflow.ts b/src/BufferReflow.ts
index 24ab69e6..d27d7c48 100644
--- a/src/BufferReflow.ts
+++ b/src/BufferReflow.ts
@@ -3,10 +3,10 @@
* @license MIT
*/
-import { FILL_CHAR_DATA } from './Buffer';
-import { BufferLine } from './BufferLine';
+import { BufferLine, CellData } from './BufferLine';
import { CircularList, IDeleteEvent } from './common/CircularList';
import { IBufferLine } from './Types';
+import { NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, DEFAULT_ATTR } from './Buffer';
export interface INewLayoutResult {
layout: number[];
@@ -20,6 +20,7 @@ export interface INewLayoutResult {
* @param newCols The columns after resize.
*/
export function reflowLargerGetLinesToRemove(lines: CircularList, newCols: number, bufferAbsoluteY: number): number[] {
+ const nullCell = CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]);
// Gather all BufferLines that need to be removed from the Buffer here so that they can be
// batched up and only committed once
const toRemove: number[] = [];
@@ -75,13 +76,13 @@ export function reflowLargerGetLinesToRemove(lines: CircularList, n
if (wrappedLines[destLineIndex - 1].getWidth(newCols - 1) === 2) {
wrappedLines[destLineIndex].copyCellsFrom(wrappedLines[destLineIndex - 1], newCols - 1, destCol++, 1, false);
// Null out the end of the last row
- wrappedLines[destLineIndex - 1].set(newCols - 1, FILL_CHAR_DATA);
+ wrappedLines[destLineIndex - 1].setCell(newCols - 1, nullCell);
}
}
}
// Clear out remaining cells or fragments could remain;
- wrappedLines[destLineIndex].replaceCells(destCol, newCols, FILL_CHAR_DATA);
+ wrappedLines[destLineIndex].replaceCells(destCol, newCols, nullCell);
// Work backwards and remove any rows at the end that only contain null cells
let countToRemove = 0;
diff --git a/src/CharWidth.test.ts b/src/CharWidth.test.ts
index 0747fdf1..8608c6fa 100644
--- a/src/CharWidth.test.ts
+++ b/src/CharWidth.test.ts
@@ -8,6 +8,7 @@ import { assert } from 'chai';
import { getStringCellWidth, wcwidth } from './CharWidth';
import { IBuffer } from './Types';
import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from './Buffer';
+import { CellData } from './BufferLine';
describe('getStringCellWidth', function(): void {
@@ -22,7 +23,7 @@ describe('getStringCellWidth', function(): void {
for (let i = start; i < end; ++i) {
const line = buffer.lines.get(i);
for (let j = 0; j < line.length; ++j) { // TODO: change to trimBorder with multiline
- const ch = line.get(j);
+ const ch = line.loadCell(j, new CellData()).getAsCharData();
result += ch[CHAR_DATA_WIDTH_INDEX];
// return on sentinel
if (ch[CHAR_DATA_CHAR_INDEX] === sentinel) {
diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts
index a7963a2c..24eb0884 100644
--- a/src/InputHandler.test.ts
+++ b/src/InputHandler.test.ts
@@ -6,9 +6,10 @@
import { assert, expect } from 'chai';
import { InputHandler } from './InputHandler';
import { MockInputHandlingTerminal } from './ui/TestUtils.test';
-import { CHAR_DATA_ATTR_INDEX, DEFAULT_ATTR } from './Buffer';
+import { DEFAULT_ATTR } from './Buffer';
import { Terminal } from './Terminal';
import { IBufferLine } from './Types';
+import { CellData } from './BufferLine';
describe('InputHandler', () => {
describe('save and restore cursor', () => {
@@ -356,45 +357,45 @@ describe('InputHandler', () => {
expect(term.buffer.translateBufferLineToString(0, true)).to.equal('');
expect(term.buffer.translateBufferLineToString(1, true)).to.equal(' TEST');
// Text color of 'TEST' should be red
- expect((term.buffer.lines.get(1).get(4)[CHAR_DATA_ATTR_INDEX] >> 9) & 0x1ff).to.equal(1);
+ expect((term.buffer.lines.get(1).loadCell(4, new CellData()).fg >> 9) & 0x1ff).to.equal(1);
});
it('should handle DECSET/DECRST 1047 (alt screen buffer)', () => {
handler.parse('\x1b[?1047h\r\n\x1b[31mJUNK\x1b[?1047lTEST');
expect(term.buffer.translateBufferLineToString(0, true)).to.equal('');
expect(term.buffer.translateBufferLineToString(1, true)).to.equal(' TEST');
// Text color of 'TEST' should be red
- expect((term.buffer.lines.get(1).get(4)[CHAR_DATA_ATTR_INDEX] >> 9) & 0x1ff).to.equal(1);
+ expect((term.buffer.lines.get(1).loadCell(4, new CellData()).fg >> 9) & 0x1ff).to.equal(1);
});
it('should handle DECSET/DECRST 1048 (alt screen cursor)', () => {
handler.parse('\x1b[?1048h\r\n\x1b[31mJUNK\x1b[?1048lTEST');
expect(term.buffer.translateBufferLineToString(0, true)).to.equal('TEST');
expect(term.buffer.translateBufferLineToString(1, true)).to.equal('JUNK');
// Text color of 'TEST' should be default
- expect(term.buffer.lines.get(0).get(0)[CHAR_DATA_ATTR_INDEX]).to.equal(DEFAULT_ATTR);
+ expect(term.buffer.lines.get(0).loadCell(0, new CellData()).fg).to.equal(DEFAULT_ATTR);
// Text color of 'JUNK' should be red
- expect((term.buffer.lines.get(1).get(0)[CHAR_DATA_ATTR_INDEX] >> 9) & 0x1ff).to.equal(1);
+ expect((term.buffer.lines.get(1).loadCell(0, new CellData()).fg >> 9) & 0x1ff).to.equal(1);
});
it('should handle DECSET/DECRST 1049 (alt screen buffer+cursor)', () => {
handler.parse('\x1b[?1049h\r\n\x1b[31mJUNK\x1b[?1049lTEST');
expect(term.buffer.translateBufferLineToString(0, true)).to.equal('TEST');
expect(term.buffer.translateBufferLineToString(1, true)).to.equal('');
// Text color of 'TEST' should be default
- expect(term.buffer.lines.get(0).get(0)[CHAR_DATA_ATTR_INDEX]).to.equal(DEFAULT_ATTR);
+ expect(term.buffer.lines.get(0).loadCell(0, new CellData()).fg).to.equal(DEFAULT_ATTR);
});
it('should handle DECSET/DECRST 1049 - maintains saved cursor for alt buffer', () => {
handler.parse('\x1b[?1049h\r\n\x1b[31m\x1b[s\x1b[?1049lTEST');
expect(term.buffer.translateBufferLineToString(0, true)).to.equal('TEST');
// Text color of 'TEST' should be default
- expect(term.buffer.lines.get(0).get(0)[CHAR_DATA_ATTR_INDEX]).to.equal(DEFAULT_ATTR);
+ expect(term.buffer.lines.get(0).loadCell(0, new CellData()).fg).to.equal(DEFAULT_ATTR);
handler.parse('\x1b[?1049h\x1b[uTEST');
expect(term.buffer.translateBufferLineToString(1, true)).to.equal('TEST');
// Text color of 'TEST' should be red
- expect((term.buffer.lines.get(1).get(0)[CHAR_DATA_ATTR_INDEX] >> 9) & 0x1ff).to.equal(1);
+ expect((term.buffer.lines.get(1).loadCell(0, new CellData()).fg >> 9) & 0x1ff).to.equal(1);
});
it('should handle DECSET/DECRST 1049 - clears alt buffer with erase attributes', () => {
handler.parse('\x1b[42m\x1b[?1049h');
// Buffer should be filled with green background
- expect(term.buffer.lines.get(20).get(10)[CHAR_DATA_ATTR_INDEX] & 0x1ff).to.equal(2);
+ expect(term.buffer.lines.get(20).loadCell(10, new CellData()).fg & 0x1ff).to.equal(2);
});
});
});
diff --git a/src/InputHandler.ts b/src/InputHandler.ts
index 7405ff9f..37c9bbe5 100644
--- a/src/InputHandler.ts
+++ b/src/InputHandler.ts
@@ -7,7 +7,7 @@
import { IInputHandler, IDcsHandler, IEscapeSequenceParser, IBuffer, IInputHandlingTerminal } from './Types';
import { C0, C1 } from './common/data/EscapeSequences';
import { CHARSETS, DEFAULT_CHARSET } from './core/data/Charsets';
-import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CODE_INDEX, DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE } from './Buffer';
+import { DEFAULT_ATTR, NULL_CELL_WIDTH, NULL_CELL_CODE } from './Buffer';
import { FLAGS } from './renderer/Types';
import { wcwidth } from './CharWidth';
import { EscapeSequenceParser } from './EscapeSequenceParser';
@@ -16,6 +16,7 @@ import { IDisposable } from 'xterm';
import { Disposable } from './common/Lifecycle';
import { concat } from './common/TypedArrayUtils';
import { StringToUtf32, stringFromCodePoint, utf32ToString } from './core/input/TextDecoder';
+import { CellData } from './BufferLine';
/**
* Map collect to glevel. Used in `selectCharset`.
@@ -105,6 +106,7 @@ class DECRQSS implements IDcsHandler {
export class InputHandler extends Disposable implements IInputHandler {
private _parseBuffer: Uint32Array = new Uint32Array(4096);
private _stringDecoder: StringToUtf32 = new StringToUtf32();
+ private _workCell: CellData = new CellData();
constructor(
protected _terminal: IInputHandlingTerminal,
@@ -301,9 +303,6 @@ export class InputHandler extends Disposable implements IInputHandler {
if (this._parseBuffer.length < data.length) {
this._parseBuffer = new Uint32Array(data.length);
}
- for (let i = 0; i < data.length; ++i) {
- this._parseBuffer[i] = data.charCodeAt(i);
- }
this._parser.parse(this._parseBuffer, this._stringDecoder.decode(data, this._parseBuffer));
buffer = this._terminal.buffer;
@@ -314,7 +313,6 @@ export class InputHandler extends Disposable implements IInputHandler {
public print(data: Uint32Array, start: number, end: number): void {
let code: number;
- let char: string;
let chWidth: number;
const buffer: IBuffer = this._terminal.buffer;
const charset: ICharset = this._terminal.charset;
@@ -328,25 +326,23 @@ export class InputHandler extends Disposable implements IInputHandler {
this._terminal.updateRange(buffer.y);
for (let pos = start; pos < end; ++pos) {
code = data[pos];
- char = stringFromCodePoint(code);
// calculate print space
// expensive call, therefore we save width in line buffer
chWidth = wcwidth(code);
// get charset replacement character
- // charset are only defined for ASCII, therefore we only
+ // charset is only defined for ASCII, therefore we only
// search for an replacement char if code < 127
if (code < 127 && charset) {
- const ch = charset[char];
+ const ch = charset[String.fromCharCode(code)];
if (ch) {
code = ch.charCodeAt(0);
- char = ch;
}
}
if (screenReaderMode) {
- this._terminal.emit('a11y.char', char);
+ this._terminal.emit('a11y.char', stringFromCodePoint(code));
}
// insert combining char at last cursor position
@@ -355,23 +351,13 @@ export class InputHandler extends Disposable implements IInputHandler {
// since they always follow a cell consuming char
// therefore we can test for buffer.x to avoid overflow left
if (!chWidth && buffer.x) {
- const chMinusOne = bufferRow.get(buffer.x - 1);
- if (chMinusOne) {
- if (!chMinusOne[CHAR_DATA_WIDTH_INDEX]) {
- // 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
- const chMinusTwo = bufferRow.get(buffer.x - 2);
- if (chMinusTwo) {
- chMinusTwo[CHAR_DATA_CHAR_INDEX] += char;
- chMinusTwo[CHAR_DATA_CODE_INDEX] = code;
- bufferRow.set(buffer.x - 2, chMinusTwo); // must be set explicitly now
- }
- } else {
- chMinusOne[CHAR_DATA_CHAR_INDEX] += char;
- chMinusOne[CHAR_DATA_CODE_INDEX] = code;
- bufferRow.set(buffer.x - 1, chMinusOne); // must be set explicitly now
- }
+ if (!bufferRow.getWidth(buffer.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);
+ } else {
+ bufferRow.addCodepointToCell(buffer.x - 1, code);
}
continue;
}
@@ -410,25 +396,25 @@ 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, [curAttr, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]);
+ bufferRow.insertCells(buffer.x, chWidth, buffer.getNullCell(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 eraseChar
- const lastCell = bufferRow.get(cols - 1);
- if (lastCell[CHAR_DATA_WIDTH_INDEX] === 2) {
- bufferRow.set(cols - 1, [curAttr, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]);
+ // and will be set to empty cell
+ if (bufferRow.getWidth(cols - 1) === 2) {
+ bufferRow.setCellFromCodePoint(cols - 1, NULL_CELL_CODE, NULL_CELL_WIDTH, curAttr, 0);
}
}
// write current char to buffer and advance cursor
- bufferRow.set(buffer.x++, [curAttr, char, chWidth, code]);
+ bufferRow.setCellFromCodePoint(buffer.x++, code, chWidth, curAttr, 0);
// 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
if (chWidth > 0) {
while (--chWidth) {
- bufferRow.set(buffer.x++, [curAttr, '', 0, undefined]);
+ // other than a regular empty cell a cell following a wide char has no width
+ bufferRow.setCellFromCodePoint(buffer.x++, 0, 0, curAttr, 0);
}
}
}
@@ -534,7 +520,7 @@ export class InputHandler extends Disposable implements IInputHandler {
this._terminal.buffer.lines.get(this._terminal.buffer.y + this._terminal.buffer.ybase).insertCells(
this._terminal.buffer.x,
params[0] || 1,
- [this._terminal.eraseAttr(), NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]
+ this._terminal.buffer.getNullCell(this._terminal.eraseAttr())
);
this._terminal.updateRange(this._terminal.buffer.y);
}
@@ -708,7 +694,7 @@ export class InputHandler extends Disposable implements IInputHandler {
line.replaceCells(
start,
end,
- [this._terminal.eraseAttr(), NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]
+ this._terminal.buffer.getNullCell(this._terminal.eraseAttr())
);
if (clearWrap) {
line.isWrapped = false;
@@ -877,7 +863,7 @@ export class InputHandler extends Disposable implements IInputHandler {
this._terminal.buffer.lines.get(this._terminal.buffer.y + this._terminal.buffer.ybase).deleteCells(
this._terminal.buffer.x,
params[0] || 1,
- [this._terminal.eraseAttr(), NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]
+ this._terminal.buffer.getNullCell(this._terminal.eraseAttr())
);
this._terminal.updateRange(this._terminal.buffer.y);
}
@@ -928,7 +914,7 @@ export class InputHandler extends Disposable implements IInputHandler {
this._terminal.buffer.lines.get(this._terminal.buffer.y + this._terminal.buffer.ybase).replaceCells(
this._terminal.buffer.x,
this._terminal.buffer.x + (params[0] || 1),
- [this._terminal.eraseAttr(), NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]
+ this._terminal.buffer.getNullCell(this._terminal.eraseAttr())
);
}
@@ -984,9 +970,10 @@ export class InputHandler extends Disposable implements IInputHandler {
// make buffer local for faster access
const buffer = this._terminal.buffer;
const line = buffer.lines.get(buffer.ybase + buffer.y);
+ line.loadCell(buffer.x - 1, this._workCell);
line.replaceCells(buffer.x,
buffer.x + (params[0] || 1),
- line.get(buffer.x - 1) || [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]
+ (this._workCell.content !== undefined) ? this._workCell : buffer.getNullCell(DEFAULT_ATTR)
);
// FIXME: no updateRange here?
}
diff --git a/src/Linkifier.test.ts b/src/Linkifier.test.ts
index 07dbf1b3..c7bbbeb8 100644
--- a/src/Linkifier.test.ts
+++ b/src/Linkifier.test.ts
@@ -9,7 +9,7 @@ import { ILinkMatcher, ITerminal, IBufferLine } from './Types';
import { Linkifier } from './Linkifier';
import { MockBuffer, MockTerminal, TestTerminal } from './ui/TestUtils.test';
import { CircularList } from './common/CircularList';
-import { BufferLine } from './BufferLine';
+import { BufferLine, CellData } from './BufferLine';
class TestLinkifier extends Linkifier {
constructor(terminal: ITerminal) {
@@ -53,7 +53,7 @@ describe('Linkifier', () => {
function stringToRow(text: string): IBufferLine {
const result = new BufferLine(text.length);
for (let i = 0; i < text.length; i++) {
- result.set(i, [0, text.charAt(i), 1, text.charCodeAt(i)]);
+ result.setCell(i, CellData.fromCharData([0, text.charAt(i), 1, text.charCodeAt(i)]));
}
return result;
}
diff --git a/src/Linkifier.ts b/src/Linkifier.ts
index 53247c95..80399904 100644
--- a/src/Linkifier.ts
+++ b/src/Linkifier.ts
@@ -7,7 +7,6 @@ import { IMouseZoneManager } from './ui/Types';
import { ILinkHoverEvent, ILinkMatcher, LinkMatcherHandler, LinkHoverEventTypes, ILinkMatcherOptions, ILinkifier, ITerminal, IBufferStringIteratorResult } from './Types';
import { MouseZone } from './ui/MouseZoneManager';
import { EventEmitter } from './common/EventEmitter';
-import { CHAR_DATA_ATTR_INDEX } from './Buffer';
import { getStringCellWidth } from './CharWidth';
/**
@@ -232,10 +231,9 @@ export class Linkifier extends EventEmitter implements ILinkifier {
}
const line = this._terminal.buffer.lines.get(bufferIndex[0]);
- const char = line.get(bufferIndex[1]);
+ const attr = line.getFg(bufferIndex[1]);
let fg: number | undefined;
- if (char) {
- const attr: number = char[CHAR_DATA_ATTR_INDEX];
+ if (attr) {
fg = (attr >> 9) & 0x1ff;
}
diff --git a/src/SelectionManager.test.ts b/src/SelectionManager.test.ts
index 21bc6754..d65f9716 100644
--- a/src/SelectionManager.test.ts
+++ b/src/SelectionManager.test.ts
@@ -10,7 +10,7 @@ import { SelectionModel } from './SelectionModel';
import { BufferSet } from './BufferSet';
import { ITerminal, IBuffer, IBufferLine } from './Types';
import { MockTerminal } from './ui/TestUtils.test';
-import { BufferLine } from './BufferLine';
+import { BufferLine, CellData } from './BufferLine';
class TestMockTerminal extends MockTerminal {
emit(event: string, data: any): void {}
@@ -57,14 +57,14 @@ describe('SelectionManager', () => {
function stringToRow(text: string): IBufferLine {
const result = new BufferLine(text.length);
for (let i = 0; i < text.length; i++) {
- result.set(i, [0, text.charAt(i), 1, text.charCodeAt(i)]);
+ result.setCell(i, CellData.fromCharData([0, text.charAt(i), 1, text.charCodeAt(i)]));
}
return result;
}
function stringArrayToRow(chars: string[]): IBufferLine {
const line = new BufferLine(chars.length);
- chars.map((c, idx) => line.set(idx, [0, c, 1, c.charCodeAt(0)]));
+ chars.map((c, idx) => line.setCell(idx, CellData.fromCharData([0, c, 1, c.charCodeAt(0)])));
return line;
}
@@ -119,7 +119,7 @@ describe('SelectionManager', () => {
[null, 'o', 1, 'o'.charCodeAt(0)]
];
const line = new BufferLine(data.length);
- for (let i = 0; i < data.length; ++i) line.set(i, data[i]);
+ for (let i = 0; i < data.length; ++i) line.setCell(i, CellData.fromCharData(data[i]));
buffer.lines.set(0, line);
// Ensure wide characters take up 2 columns
selectionManager.selectWordAt([0, 0]);
diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts
index f93328fe..361b1123 100644
--- a/src/SelectionManager.ts
+++ b/src/SelectionManager.ts
@@ -3,15 +3,15 @@
* @license MIT
*/
-import { ITerminal, ISelectionManager, IBuffer, CharData, IBufferLine } from './Types';
+import { ITerminal, ISelectionManager, IBuffer, IBufferLine } from './Types';
import { XtermListener } from './common/Types';
import { MouseHelper } from './ui/MouseHelper';
import * as Browser from './core/Platform';
import { CharMeasure } from './ui/CharMeasure';
import { EventEmitter } from './common/EventEmitter';
import { SelectionModel } from './SelectionModel';
-import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX, CHAR_DATA_CODE_INDEX } from './Buffer';
import { AltClickHandler } from './handlers/AltClickHandler';
+import { CellData } from './BufferLine';
/**
* The number of pixels the mouse needs to be above or below the viewport in
@@ -103,6 +103,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
private _mouseMoveListener: EventListener;
private _mouseUpListener: EventListener;
private _trimListener: XtermListener;
+ private _workCell: CellData = new CellData();
private _mouseDownTimeStamp: number;
@@ -506,8 +507,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
// If the mouse is over the second half of a wide character, adjust the
// selection to cover the whole character
- const char = line.get(this._model.selectionStart[0]);
- if (char[CHAR_DATA_WIDTH_INDEX] === 0) {
+ if (line.hasWidth(this._model.selectionStart[0]) === 0) {
this._model.selectionStart[0]++;
}
}
@@ -596,8 +596,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
// selection. Note that selections at the very end of the line will never
// have a character.
if (this._model.selectionEnd[1] < this._buffer.lines.length) {
- const char = this._buffer.lines.get(this._model.selectionEnd[1]).get(this._model.selectionEnd[0]);
- if (char && char[CHAR_DATA_WIDTH_INDEX] === 0) {
+ if (this._buffer.lines.get(this._model.selectionEnd[1]).hasWidth(this._model.selectionEnd[0]) === 0) {
this._model.selectionEnd[0]++;
}
}
@@ -670,16 +669,16 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
private _convertViewportColToCharacterIndex(bufferLine: IBufferLine, coords: [number, number]): number {
let charIndex = coords[0];
for (let i = 0; coords[0] >= i; i++) {
- const char = bufferLine.get(i);
- if (char[CHAR_DATA_WIDTH_INDEX] === 0) {
+ const length = bufferLine.loadCell(i, this._workCell).getChars().length;
+ if (this._workCell.getWidth() === 0) {
// Wide characters aren't included in the line string so decrement the
// index so the index is back on the wide character.
charIndex--;
- } else if (char[CHAR_DATA_CHAR_INDEX].length > 1 && coords[0] !== i) {
+ } else if (length > 1 && coords[0] !== i) {
// Emojis take up multiple characters, so adjust accordingly. For these
// we don't want ot include the character at the column as we're
// returning the start index in the string, not the end index.
- charIndex += char[CHAR_DATA_CHAR_INDEX].length - 1;
+ charIndex += length - 1;
}
}
return charIndex;
@@ -739,48 +738,51 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
// Consider the initial position, skip it and increment the wide char
// variable
- if (bufferLine.get(startCol)[CHAR_DATA_WIDTH_INDEX] === 0) {
+ if (bufferLine.getWidth(startCol) === 0) {
leftWideCharCount++;
startCol--;
}
- if (bufferLine.get(endCol)[CHAR_DATA_WIDTH_INDEX] === 2) {
+ if (bufferLine.getWidth(endCol) === 2) {
rightWideCharCount++;
endCol++;
}
// Adjust the end index for characters whose length are > 1 (emojis)
- if (bufferLine.get(endCol)[CHAR_DATA_CHAR_INDEX].length > 1) {
- rightLongCharOffset += bufferLine.get(endCol)[CHAR_DATA_CHAR_INDEX].length - 1;
- endIndex += bufferLine.get(endCol)[CHAR_DATA_CHAR_INDEX].length - 1;
+ const length = bufferLine.getString(endCol).length;
+ if (length > 1) {
+ rightLongCharOffset += length - 1;
+ endIndex += length - 1;
}
// Expand the string in both directions until a space is hit
- while (startCol > 0 && startIndex > 0 && !this._isCharWordSeparator(bufferLine.get(startCol - 1))) {
- const char = bufferLine.get(startCol - 1);
- if (char[CHAR_DATA_WIDTH_INDEX] === 0) {
+ while (startCol > 0 && startIndex > 0 && !this._isCharWordSeparator(bufferLine.loadCell(startCol - 1, this._workCell))) {
+ bufferLine.loadCell(startCol - 1, this._workCell);
+ const length = this._workCell.getChars().length;
+ if (this._workCell.getWidth() === 0) {
// If the next character is a wide char, record it and skip the column
leftWideCharCount++;
startCol--;
- } else if (char[CHAR_DATA_CHAR_INDEX].length > 1) {
+ } else if (length > 1) {
// If the next character's string is longer than 1 char (eg. emoji),
// adjust the index
- leftLongCharOffset += char[CHAR_DATA_CHAR_INDEX].length - 1;
- startIndex -= char[CHAR_DATA_CHAR_INDEX].length - 1;
+ leftLongCharOffset += length - 1;
+ startIndex -= length - 1;
}
startIndex--;
startCol--;
}
- while (endCol < bufferLine.length && endIndex + 1 < line.length && !this._isCharWordSeparator(bufferLine.get(endCol + 1))) {
- const char = bufferLine.get(endCol + 1);
- if (char[CHAR_DATA_WIDTH_INDEX] === 2) {
+ while (endCol < bufferLine.length && endIndex + 1 < line.length && !this._isCharWordSeparator(bufferLine.loadCell(endCol + 1, this._workCell))) {
+ bufferLine.loadCell(endCol + 1, this._workCell);
+ const length = this._workCell.getChars().length;
+ if (this._workCell.getWidth() === 2) {
// If the next character is a wide char, record it and skip the column
rightWideCharCount++;
endCol++;
- } else if (char[CHAR_DATA_CHAR_INDEX].length > 1) {
+ } else if (length > 1) {
// If the next character's string is longer than 1 char (eg. emoji),
// adjust the index
- rightLongCharOffset += char[CHAR_DATA_CHAR_INDEX].length - 1;
- endIndex += char[CHAR_DATA_CHAR_INDEX].length - 1;
+ rightLongCharOffset += length - 1;
+ endIndex += length - 1;
}
endIndex++;
endCol++;
@@ -814,9 +816,9 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
// Recurse upwards if the line is wrapped and the word wraps to the above line
if (followWrappedLinesAbove) {
- if (start === 0 && bufferLine.get(0)[CHAR_DATA_CODE_INDEX] !== 32 /*' '*/) {
+ if (start === 0 && bufferLine.getCodePoint(0) !== 32 /*' '*/) {
const previousBufferLine = this._buffer.lines.get(coords[1] - 1);
- if (previousBufferLine && bufferLine.isWrapped && previousBufferLine.get(this._terminal.cols - 1)[CHAR_DATA_CODE_INDEX] !== 32 /*' '*/) {
+ if (previousBufferLine && bufferLine.isWrapped && previousBufferLine.getCodePoint(this._terminal.cols - 1) !== 32 /*' '*/) {
const previousLineWordPosition = this._getWordAt([this._terminal.cols - 1, coords[1] - 1], false, true, false);
if (previousLineWordPosition) {
const offset = this._terminal.cols - previousLineWordPosition.start;
@@ -829,9 +831,9 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
// Recurse downwards if the line is wrapped and the word wraps to the next line
if (followWrappedLinesBelow) {
- if (start + length === this._terminal.cols && bufferLine.get(this._terminal.cols - 1)[CHAR_DATA_CODE_INDEX] !== 32 /*' '*/) {
+ if (start + length === this._terminal.cols && bufferLine.getCodePoint(this._terminal.cols - 1) !== 32 /*' '*/) {
const nextBufferLine = this._buffer.lines.get(coords[1] + 1);
- if (nextBufferLine && nextBufferLine.isWrapped && nextBufferLine.get(0)[CHAR_DATA_CODE_INDEX] !== 32 /*' '*/) {
+ if (nextBufferLine && nextBufferLine.isWrapped && nextBufferLine.getCodePoint(0) !== 32 /*' '*/) {
const nextLineWordPosition = this._getWordAt([0, coords[1] + 1], false, false, true);
if (nextLineWordPosition) {
length += nextLineWordPosition.length;
@@ -894,13 +896,13 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
* word logic.
* @param char The character to check.
*/
- private _isCharWordSeparator(charData: CharData): boolean {
+ private _isCharWordSeparator(cell: CellData): boolean {
// Zero width characters are never separators as they are always to the
// right of wide characters
- if (charData[CHAR_DATA_WIDTH_INDEX] === 0) {
+ if (cell.getWidth() === 0) {
return false;
}
- return WORD_SEPARATORS.indexOf(charData[CHAR_DATA_CHAR_INDEX]) >= 0;
+ return WORD_SEPARATORS.indexOf(cell.getChars()) >= 0;
}
/**
diff --git a/src/Terminal.integration.ts b/src/Terminal.integration.ts
index d2a5cd7c..10043006 100644
--- a/src/Terminal.integration.ts
+++ b/src/Terminal.integration.ts
@@ -13,8 +13,9 @@ import * as path from 'path';
import * as pty from 'node-pty';
import { assert } from 'chai';
import { Terminal } from './Terminal';
-import { CHAR_DATA_CHAR_INDEX, WHITESPACE_CELL_CHAR } from './Buffer';
+import { WHITESPACE_CELL_CHAR } from './Buffer';
import { IViewport } from './Types';
+import { CellData } from './BufferLine';
class TestTerminal extends Terminal {
innerWrite(): void { this._innerWrite(); }
@@ -67,7 +68,7 @@ function terminalToString(term: Terminal): string {
for (let line = term.buffer.ybase; line < term.buffer.ybase + term.rows; line++) {
lineText = '';
for (let cell = 0; cell < term.cols; ++cell) {
- lineText += term.buffer.lines.get(line).get(cell)[CHAR_DATA_CHAR_INDEX] || WHITESPACE_CELL_CHAR;
+ lineText += term.buffer.lines.get(line).loadCell(cell, new CellData()).getChars() || WHITESPACE_CELL_CHAR;
}
// rtrim empty cells as xterm does
lineText = lineText.replace(/\s+$/, '');
diff --git a/src/Terminal.test.ts b/src/Terminal.test.ts
index fdc9678b..08bceb34 100644
--- a/src/Terminal.test.ts
+++ b/src/Terminal.test.ts
@@ -6,7 +6,8 @@
import { assert, expect } from 'chai';
import { Terminal } from './Terminal';
import { MockViewport, MockCompositionHelper, MockRenderer } from './ui/TestUtils.test';
-import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, DEFAULT_ATTR } from './Buffer';
+import { DEFAULT_ATTR } from './Buffer';
+import { CellData } from './BufferLine';
const INIT_COLS = 80;
const INIT_ROWS = 24;
@@ -227,7 +228,7 @@ describe('term.js addons', () => {
});
describe('setOption', () => {
- it('should set the option correctly', () => {
+ it('should set option correctly', () => {
term.setOption('cursorBlink', true);
assert.equal(term.options.cursorBlink, true);
term.setOption('cursorBlink', false);
@@ -455,62 +456,62 @@ describe('term.js addons', () => {
describe('scroll() function', () => {
describe('when scrollback > 0', () => {
it('should create a new line and scroll', () => {
- term.buffer.lines.get(0).set(0, [0, 'a', 0, 'a'.charCodeAt(0)]);
- term.buffer.lines.get(INIT_ROWS - 1).set(0, [0, 'b', 0, 'b'.charCodeAt(0)]);
+ term.buffer.lines.get(0).setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)]));
+ term.buffer.lines.get(INIT_ROWS - 1).setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)]));
term.buffer.y = INIT_ROWS - 1; // Move cursor to last line
term.scroll();
assert.equal(term.buffer.lines.length, INIT_ROWS + 1);
- assert.equal(term.buffer.lines.get(0).get(0)[CHAR_DATA_CHAR_INDEX], 'a');
- assert.equal(term.buffer.lines.get(INIT_ROWS - 1).get(0)[CHAR_DATA_CHAR_INDEX], 'b');
- assert.equal(term.buffer.lines.get(INIT_ROWS).get(0)[CHAR_DATA_CHAR_INDEX], '');
+ assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).getChars(), 'a');
+ assert.equal(term.buffer.lines.get(INIT_ROWS - 1).loadCell(0, new CellData()).getChars(), 'b');
+ assert.equal(term.buffer.lines.get(INIT_ROWS).loadCell(0, new CellData()).getChars(), '');
});
it('should properly scroll inside a scroll region (scrollTop set)', () => {
- term.buffer.lines.get(0).set(0, [0, 'a', 0, 'a'.charCodeAt(0)]);
- term.buffer.lines.get(1).set(0, [0, 'b', 0, 'b'.charCodeAt(0)]);
- term.buffer.lines.get(2).set(0, [0, 'c', 0, 'c'.charCodeAt(0)]);
+ term.buffer.lines.get(0).setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)]));
+ term.buffer.lines.get(1).setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)]));
+ term.buffer.lines.get(2).setCell(0, CellData.fromCharData([0, 'c', 0, 'c'.charCodeAt(0)]));
term.buffer.y = INIT_ROWS - 1; // Move cursor to last line
term.buffer.scrollTop = 1;
term.scroll();
assert.equal(term.buffer.lines.length, INIT_ROWS);
- assert.equal(term.buffer.lines.get(0).get(0)[CHAR_DATA_CHAR_INDEX], 'a');
- assert.equal(term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX], 'c');
+ assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).getChars(), 'a');
+ assert.equal(term.buffer.lines.get(1).loadCell(0, new CellData()).getChars(), 'c');
});
it('should properly scroll inside a scroll region (scrollBottom set)', () => {
- term.buffer.lines.get(0).set(0, [0, 'a', 0, 'a'.charCodeAt(0)]);
- term.buffer.lines.get(1).set(0, [0, 'b', 0, 'b'.charCodeAt(0)]);
- term.buffer.lines.get(2).set(0, [0, 'c', 0, 'c'.charCodeAt(0)]);
- term.buffer.lines.get(3).set(0, [0, 'd', 0, 'd'.charCodeAt(0)]);
- term.buffer.lines.get(4).set(0, [0, 'e', 0, 'e'.charCodeAt(0)]);
+ term.buffer.lines.get(0).setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)]));
+ term.buffer.lines.get(1).setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)]));
+ term.buffer.lines.get(2).setCell(0, CellData.fromCharData([0, 'c', 0, 'c'.charCodeAt(0)]));
+ term.buffer.lines.get(3).setCell(0, CellData.fromCharData([0, 'd', 0, 'd'.charCodeAt(0)]));
+ term.buffer.lines.get(4).setCell(0, CellData.fromCharData([0, 'e', 0, 'e'.charCodeAt(0)]));
term.buffer.y = 3;
term.buffer.scrollBottom = 3;
term.scroll();
assert.equal(term.buffer.lines.length, INIT_ROWS + 1);
- assert.equal(term.buffer.lines.get(0).get(0)[CHAR_DATA_CHAR_INDEX], 'a', '\'a\' should be pushed to the scrollback');
- assert.equal(term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX], 'b');
- assert.equal(term.buffer.lines.get(2).get(0)[CHAR_DATA_CHAR_INDEX], 'c');
- assert.equal(term.buffer.lines.get(3).get(0)[CHAR_DATA_CHAR_INDEX], 'd');
- assert.equal(term.buffer.lines.get(4).get(0)[CHAR_DATA_CHAR_INDEX], '', 'a blank line should be added at scrollBottom\'s index');
- assert.equal(term.buffer.lines.get(5).get(0)[CHAR_DATA_CHAR_INDEX], 'e');
+ assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).getChars(), 'a', '\'a\' should be pushed to the scrollback');
+ assert.equal(term.buffer.lines.get(1).loadCell(0, new CellData()).getChars(), 'b');
+ assert.equal(term.buffer.lines.get(2).loadCell(0, new CellData()).getChars(), 'c');
+ assert.equal(term.buffer.lines.get(3).loadCell(0, new CellData()).getChars(), 'd');
+ assert.equal(term.buffer.lines.get(4).loadCell(0, new CellData()).getChars(), '', 'a blank line should be added at scrollBottom\'s index');
+ assert.equal(term.buffer.lines.get(5).loadCell(0, new CellData()).getChars(), 'e');
});
it('should properly scroll inside a scroll region (scrollTop and scrollBottom set)', () => {
- term.buffer.lines.get(0).set(0, [0, 'a', 0, 'a'.charCodeAt(0)]);
- term.buffer.lines.get(1).set(0, [0, 'b', 0, 'b'.charCodeAt(0)]);
- term.buffer.lines.get(2).set(0, [0, 'c', 0, 'c'.charCodeAt(0)]);
- term.buffer.lines.get(3).set(0, [0, 'd', 0, 'd'.charCodeAt(0)]);
- term.buffer.lines.get(4).set(0, [0, 'e', 0, 'e'.charCodeAt(0)]);
+ term.buffer.lines.get(0).setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)]));
+ term.buffer.lines.get(1).setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)]));
+ term.buffer.lines.get(2).setCell(0, CellData.fromCharData([0, 'c', 0, 'c'.charCodeAt(0)]));
+ term.buffer.lines.get(3).setCell(0, CellData.fromCharData([0, 'd', 0, 'd'.charCodeAt(0)]));
+ term.buffer.lines.get(4).setCell(0, CellData.fromCharData([0, 'e', 0, 'e'.charCodeAt(0)]));
term.buffer.y = INIT_ROWS - 1; // Move cursor to last line
term.buffer.scrollTop = 1;
term.buffer.scrollBottom = 3;
term.scroll();
assert.equal(term.buffer.lines.length, INIT_ROWS);
- assert.equal(term.buffer.lines.get(0).get(0)[CHAR_DATA_CHAR_INDEX], 'a');
- assert.equal(term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX], 'c', '\'b\' should be removed from the buffer');
- assert.equal(term.buffer.lines.get(2).get(0)[CHAR_DATA_CHAR_INDEX], 'd');
- assert.equal(term.buffer.lines.get(3).get(0)[CHAR_DATA_CHAR_INDEX], '', 'a blank line should be added at scrollBottom\'s index');
- assert.equal(term.buffer.lines.get(4).get(0)[CHAR_DATA_CHAR_INDEX], 'e');
+ assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).getChars(), 'a');
+ assert.equal(term.buffer.lines.get(1).loadCell(0, new CellData()).getChars(), 'c', '\'b\' should be removed from the buffer');
+ assert.equal(term.buffer.lines.get(2).loadCell(0, new CellData()).getChars(), 'd');
+ assert.equal(term.buffer.lines.get(3).loadCell(0, new CellData()).getChars(), '', 'a blank line should be added at scrollBottom\'s index');
+ assert.equal(term.buffer.lines.get(4).loadCell(0, new CellData()).getChars(), 'e');
});
});
@@ -521,65 +522,65 @@ describe('term.js addons', () => {
});
it('should create a new line and shift everything up', () => {
- term.buffer.lines.get(0).set(0, [0, 'a', 0, 'a'.charCodeAt(0)]);
- term.buffer.lines.get(1).set(0, [0, 'b', 0, 'b'.charCodeAt(0)]);
- term.buffer.lines.get(INIT_ROWS - 1).set(0, [0, 'c', 0, 'c'.charCodeAt(0)]);
+ term.buffer.lines.get(0).setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)]));
+ term.buffer.lines.get(1).setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)]));
+ term.buffer.lines.get(INIT_ROWS - 1).setCell(0, CellData.fromCharData([0, 'c', 0, 'c'.charCodeAt(0)]));
term.buffer.y = INIT_ROWS - 1; // Move cursor to last line
assert.equal(term.buffer.lines.length, INIT_ROWS);
term.scroll();
assert.equal(term.buffer.lines.length, INIT_ROWS);
// 'a' gets pushed out of buffer
- assert.equal(term.buffer.lines.get(0).get(0)[CHAR_DATA_CHAR_INDEX], 'b');
- assert.equal(term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX], '');
- assert.equal(term.buffer.lines.get(INIT_ROWS - 2).get(0)[CHAR_DATA_CHAR_INDEX], 'c');
- assert.equal(term.buffer.lines.get(INIT_ROWS - 1).get(0)[CHAR_DATA_CHAR_INDEX], '');
+ assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).getChars(), 'b');
+ assert.equal(term.buffer.lines.get(1).loadCell(0, new CellData()).getChars(), '');
+ assert.equal(term.buffer.lines.get(INIT_ROWS - 2).loadCell(0, new CellData()).getChars(), 'c');
+ assert.equal(term.buffer.lines.get(INIT_ROWS - 1).loadCell(0, new CellData()).getChars(), '');
});
it('should properly scroll inside a scroll region (scrollTop set)', () => {
- term.buffer.lines.get(0).set(0, [0, 'a', 0, 'a'.charCodeAt(0)]);
- term.buffer.lines.get(1).set(0, [0, 'b', 0, 'b'.charCodeAt(0)]);
- term.buffer.lines.get(2).set(0, [0, 'c', 0, 'c'.charCodeAt(0)]);
+ term.buffer.lines.get(0).setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)]));
+ term.buffer.lines.get(1).setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)]));
+ term.buffer.lines.get(2).setCell(0, CellData.fromCharData([0, 'c', 0, 'c'.charCodeAt(0)]));
term.buffer.y = INIT_ROWS - 1; // Move cursor to last line
term.buffer.scrollTop = 1;
term.scroll();
assert.equal(term.buffer.lines.length, INIT_ROWS);
- assert.equal(term.buffer.lines.get(0).get(0)[CHAR_DATA_CHAR_INDEX], 'a');
- assert.equal(term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX], 'c');
+ assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).getChars(), 'a');
+ assert.equal(term.buffer.lines.get(1).loadCell(0, new CellData()).getChars(), 'c');
});
it('should properly scroll inside a scroll region (scrollBottom set)', () => {
- term.buffer.lines.get(0).set(0, [0, 'a', 0, 'a'.charCodeAt(0)]);
- term.buffer.lines.get(1).set(0, [0, 'b', 0, 'b'.charCodeAt(0)]);
- term.buffer.lines.get(2).set(0, [0, 'c', 0, 'c'.charCodeAt(0)]);
- term.buffer.lines.get(3).set(0, [0, 'd', 0, 'd'.charCodeAt(0)]);
- term.buffer.lines.get(4).set(0, [0, 'e', 0, 'e'.charCodeAt(0)]);
+ term.buffer.lines.get(0).setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)]));
+ term.buffer.lines.get(1).setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)]));
+ term.buffer.lines.get(2).setCell(0, CellData.fromCharData([0, 'c', 0, 'c'.charCodeAt(0)]));
+ term.buffer.lines.get(3).setCell(0, CellData.fromCharData([0, 'd', 0, 'd'.charCodeAt(0)]));
+ term.buffer.lines.get(4).setCell(0, CellData.fromCharData([0, 'e', 0, 'e'.charCodeAt(0)]));
term.buffer.y = 3;
term.buffer.scrollBottom = 3;
term.scroll();
assert.equal(term.buffer.lines.length, INIT_ROWS);
- assert.equal(term.buffer.lines.get(0).get(0)[CHAR_DATA_CHAR_INDEX], 'b');
- assert.equal(term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX], 'c');
- assert.equal(term.buffer.lines.get(2).get(0)[CHAR_DATA_CHAR_INDEX], 'd');
- assert.equal(term.buffer.lines.get(3).get(0)[CHAR_DATA_CHAR_INDEX], '', 'a blank line should be added at scrollBottom\'s index');
- assert.equal(term.buffer.lines.get(4).get(0)[CHAR_DATA_CHAR_INDEX], 'e');
+ assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).getChars(), 'b');
+ assert.equal(term.buffer.lines.get(1).loadCell(0, new CellData()).getChars(), 'c');
+ assert.equal(term.buffer.lines.get(2).loadCell(0, new CellData()).getChars(), 'd');
+ assert.equal(term.buffer.lines.get(3).loadCell(0, new CellData()).getChars(), '', 'a blank line should be added at scrollBottom\'s index');
+ assert.equal(term.buffer.lines.get(4).loadCell(0, new CellData()).getChars(), 'e');
});
it('should properly scroll inside a scroll region (scrollTop and scrollBottom set)', () => {
- term.buffer.lines.get(0).set(0, [0, 'a', 0, 'a'.charCodeAt(0)]);
- term.buffer.lines.get(1).set(0, [0, 'b', 0, 'b'.charCodeAt(0)]);
- term.buffer.lines.get(2).set(0, [0, 'c', 0, 'c'.charCodeAt(0)]);
- term.buffer.lines.get(3).set(0, [0, 'd', 0, 'd'.charCodeAt(0)]);
- term.buffer.lines.get(4).set(0, [0, 'e', 0, 'e'.charCodeAt(0)]);
+ term.buffer.lines.get(0).setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)]));
+ term.buffer.lines.get(1).setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)]));
+ term.buffer.lines.get(2).setCell(0, CellData.fromCharData([0, 'c', 0, 'c'.charCodeAt(0)]));
+ term.buffer.lines.get(3).setCell(0, CellData.fromCharData([0, 'd', 0, 'd'.charCodeAt(0)]));
+ term.buffer.lines.get(4).setCell(0, CellData.fromCharData([0, 'e', 0, 'e'.charCodeAt(0)]));
term.buffer.y = INIT_ROWS - 1; // Move cursor to last line
term.buffer.scrollTop = 1;
term.buffer.scrollBottom = 3;
term.scroll();
assert.equal(term.buffer.lines.length, INIT_ROWS);
- assert.equal(term.buffer.lines.get(0).get(0)[CHAR_DATA_CHAR_INDEX], 'a');
- assert.equal(term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX], 'c', '\'b\' should be removed from the buffer');
- assert.equal(term.buffer.lines.get(2).get(0)[CHAR_DATA_CHAR_INDEX], 'd');
- assert.equal(term.buffer.lines.get(3).get(0)[CHAR_DATA_CHAR_INDEX], '', 'a blank line should be added at scrollBottom\'s index');
- assert.equal(term.buffer.lines.get(4).get(0)[CHAR_DATA_CHAR_INDEX], 'e');
+ assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).getChars(), 'a');
+ assert.equal(term.buffer.lines.get(1).loadCell(0, new CellData()).getChars(), 'c', '\'b\' should be removed from the buffer');
+ assert.equal(term.buffer.lines.get(2).loadCell(0, new CellData()).getChars(), 'd');
+ assert.equal(term.buffer.lines.get(3).loadCell(0, new CellData()).getChars(), '', 'a blank line should be added at scrollBottom\'s index');
+ assert.equal(term.buffer.lines.get(4).loadCell(0, new CellData()).getChars(), 'e');
});
});
});
@@ -770,116 +771,126 @@ describe('term.js addons', () => {
it('2 characters per cell', function (): 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) {
term.write(high + String.fromCharCode(i));
- const tchar = term.buffer.lines.get(0).get(0);
- expect(tchar[CHAR_DATA_CHAR_INDEX]).eql(high + String.fromCharCode(i));
- expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(2);
- expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1);
- expect(term.buffer.lines.get(0).get(1)[CHAR_DATA_CHAR_INDEX]).eql('');
+ const tchar = term.buffer.lines.get(0).loadCell(0, cell);
+ expect(tchar.getChars()).eql(high + String.fromCharCode(i));
+ expect(tchar.getChars().length).eql(2);
+ expect(tchar.getWidth()).eql(1);
+ expect(term.buffer.lines.get(0).loadCell(1, cell).getChars()).eql('');
term.reset();
}
});
it('2 characters at last cell', () => {
const high = String.fromCharCode(0xD800);
+ const cell = new CellData();
for (let i = 0xDC00; i <= 0xDCFF; ++i) {
term.buffer.x = term.cols - 1;
term.write(high + String.fromCharCode(i));
- expect(term.buffer.lines.get(0).get(term.buffer.x - 1)[CHAR_DATA_CHAR_INDEX]).eql(high + String.fromCharCode(i));
- expect(term.buffer.lines.get(0).get(term.buffer.x - 1)[CHAR_DATA_CHAR_INDEX].length).eql(2);
- expect(term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX]).eql('');
+ expect(term.buffer.lines.get(0).loadCell(term.buffer.x - 1, cell).getChars()).eql(high + String.fromCharCode(i));
+ expect(term.buffer.lines.get(0).loadCell(term.buffer.x - 1, cell).getChars().length).eql(2);
+ expect(term.buffer.lines.get(1).loadCell(0, cell).getChars()).eql('');
term.reset();
}
});
it('2 characters per cell over line end with autowrap', () => {
const high = String.fromCharCode(0xD800);
+ const cell = new CellData();
for (let i = 0xDC00; i <= 0xDCFF; ++i) {
term.buffer.x = term.cols - 1;
term.wraparoundMode = true;
term.write('a' + high + String.fromCharCode(i));
- expect(term.buffer.lines.get(0).get(term.cols - 1)[CHAR_DATA_CHAR_INDEX]).eql('a');
- expect(term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX]).eql(high + String.fromCharCode(i));
- expect(term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX].length).eql(2);
- expect(term.buffer.lines.get(1).get(1)[CHAR_DATA_CHAR_INDEX]).eql('');
+ expect(term.buffer.lines.get(0).loadCell(term.cols - 1, cell).getChars()).eql('a');
+ expect(term.buffer.lines.get(1).loadCell(0, cell).getChars()).eql(high + String.fromCharCode(i));
+ expect(term.buffer.lines.get(1).loadCell(0, cell).getChars().length).eql(2);
+ expect(term.buffer.lines.get(1).loadCell(1, cell).getChars()).eql('');
term.reset();
}
});
it('2 characters per cell over line end without autowrap', () => {
const high = String.fromCharCode(0xD800);
+ const cell = new CellData();
for (let i = 0xDC00; i <= 0xDCFF; ++i) {
term.buffer.x = term.cols - 1;
term.wraparoundMode = false;
term.write('a' + high + String.fromCharCode(i));
// auto wraparound mode should cut off the rest of the line
- expect(term.buffer.lines.get(0).get(term.cols - 1)[CHAR_DATA_CHAR_INDEX]).eql('a');
- expect(term.buffer.lines.get(0).get(term.cols - 1)[CHAR_DATA_CHAR_INDEX].length).eql(1);
- expect(term.buffer.lines.get(1).get(1)[CHAR_DATA_CHAR_INDEX]).eql('');
+ expect(term.buffer.lines.get(0).loadCell(term.cols - 1, cell).getChars()).eql('a');
+ expect(term.buffer.lines.get(0).loadCell(term.cols - 1, cell).getChars().length).eql(1);
+ expect(term.buffer.lines.get(1).loadCell(1, cell).getChars()).eql('');
term.reset();
}
});
it('splitted surrogates', () => {
const high = String.fromCharCode(0xD800);
+ const cell = new CellData();
for (let i = 0xDC00; i <= 0xDCFF; ++i) {
term.write(high);
term.write(String.fromCharCode(i));
- const tchar = term.buffer.lines.get(0).get(0);
- expect(tchar[CHAR_DATA_CHAR_INDEX]).eql(high + String.fromCharCode(i));
- expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(2);
- expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1);
- expect(term.buffer.lines.get(0).get(1)[CHAR_DATA_CHAR_INDEX]).eql('');
+ const tchar = term.buffer.lines.get(0).loadCell(0, cell);
+ expect(tchar.getChars()).eql(high + String.fromCharCode(i));
+ expect(tchar.getChars().length).eql(2);
+ expect(tchar.getWidth()).eql(1);
+ expect(term.buffer.lines.get(0).loadCell(1, cell).getChars()).eql('');
term.reset();
}
});
});
describe('unicode - combining characters', () => {
+ const cell = new CellData();
it('café', () => {
term.write('cafe\u0301');
- expect(term.buffer.lines.get(0).get(3)[CHAR_DATA_CHAR_INDEX]).eql('e\u0301');
- expect(term.buffer.lines.get(0).get(3)[CHAR_DATA_CHAR_INDEX].length).eql(2);
- expect(term.buffer.lines.get(0).get(3)[CHAR_DATA_WIDTH_INDEX]).eql(1);
+ term.buffer.lines.get(0).loadCell(3, cell);
+ expect(cell.getChars()).eql('e\u0301');
+ expect(cell.getChars().length).eql(2);
+ expect(cell.getWidth()).eql(1);
});
it('café - end of line', () => {
term.buffer.x = term.cols - 1 - 3;
term.write('cafe\u0301');
- expect(term.buffer.lines.get(0).get(term.cols - 1)[CHAR_DATA_CHAR_INDEX]).eql('e\u0301');
- expect(term.buffer.lines.get(0).get(term.cols - 1)[CHAR_DATA_CHAR_INDEX].length).eql(2);
- expect(term.buffer.lines.get(0).get(term.cols - 1)[CHAR_DATA_WIDTH_INDEX]).eql(1);
- expect(term.buffer.lines.get(0).get(1)[CHAR_DATA_CHAR_INDEX]).eql('');
- expect(term.buffer.lines.get(0).get(1)[CHAR_DATA_CHAR_INDEX].length).eql(0);
- expect(term.buffer.lines.get(0).get(1)[CHAR_DATA_WIDTH_INDEX]).eql(1);
+ term.buffer.lines.get(0).loadCell(term.cols - 1, cell);
+ expect(cell.getChars()).eql('e\u0301');
+ expect(cell.getChars().length).eql(2);
+ expect(cell.getWidth()).eql(1);
+ term.buffer.lines.get(0).loadCell(1, cell);
+ expect(cell.getChars()).eql('');
+ expect(cell.getChars().length).eql(0);
+ expect(cell.getWidth()).eql(1);
});
it('multiple combined é', () => {
term.wraparoundMode = true;
term.write(Array(100).join('e\u0301'));
for (let i = 0; i < term.cols; ++i) {
- const tchar = term.buffer.lines.get(0).get(i);
- expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('e\u0301');
- expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(2);
- expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1);
+ term.buffer.lines.get(0).loadCell(i, cell);
+ expect(cell.getChars()).eql('e\u0301');
+ expect(cell.getChars().length).eql(2);
+ expect(cell.getWidth()).eql(1);
}
- const tchar = term.buffer.lines.get(1).get(0);
- expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('e\u0301');
- expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(2);
- expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1);
+ term.buffer.lines.get(1).loadCell(0, cell);
+ expect(cell.getChars()).eql('e\u0301');
+ expect(cell.getChars().length).eql(2);
+ expect(cell.getWidth()).eql(1);
});
it('multiple surrogate with combined', () => {
term.wraparoundMode = true;
term.write(Array(100).join('\uD800\uDC00\u0301'));
for (let i = 0; i < term.cols; ++i) {
- const tchar = term.buffer.lines.get(0).get(i);
- expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('\uD800\uDC00\u0301');
- expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(3);
- expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1);
+ term.buffer.lines.get(0).loadCell(i, cell);
+ expect(cell.getChars()).eql('\uD800\uDC00\u0301');
+ expect(cell.getChars().length).eql(3);
+ expect(cell.getWidth()).eql(1);
}
- const tchar = term.buffer.lines.get(1).get(0);
- expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('\uD800\uDC00\u0301');
- expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(3);
- expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1);
+ term.buffer.lines.get(1).loadCell(0, cell);
+ expect(cell.getChars()).eql('\uD800\uDC00\u0301');
+ expect(cell.getChars().length).eql(3);
+ expect(cell.getWidth()).eql(1);
});
});
describe('unicode - fullwidth characters', () => {
+ const cell = new CellData();
it('cursor movement even', () => {
expect(term.buffer.x).eql(0);
term.write('¥');
@@ -895,140 +906,141 @@ describe('term.js addons', () => {
term.wraparoundMode = true;
term.write(Array(50).join('¥'));
for (let i = 0; i < term.cols; ++i) {
- const tchar = term.buffer.lines.get(0).get(i);
+ term.buffer.lines.get(0).loadCell(i, cell);
if (i % 2) {
- expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('');
- expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(0);
- expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(0);
+ expect(cell.getChars()).eql('');
+ expect(cell.getChars().length).eql(0);
+ expect(cell.getWidth()).eql(0);
} else {
- expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('¥');
- expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(1);
- expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2);
+ expect(cell.getChars()).eql('¥');
+ expect(cell.getChars().length).eql(1);
+ expect(cell.getWidth()).eql(2);
}
}
- const tchar = term.buffer.lines.get(1).get(0);
- expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('¥');
- expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(1);
- expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2);
+ term.buffer.lines.get(1).loadCell(0, cell);
+ expect(cell.getChars()).eql('¥');
+ expect(cell.getChars().length).eql(1);
+ expect(cell.getWidth()).eql(2);
});
it('line of ¥ odd', () => {
term.wraparoundMode = true;
term.buffer.x = 1;
term.write(Array(50).join('¥'));
for (let i = 1; i < term.cols - 1; ++i) {
- const tchar = term.buffer.lines.get(0).get(i);
+ term.buffer.lines.get(0).loadCell(i, cell);
if (!(i % 2)) {
- expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('');
- expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(0);
- expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(0);
+ expect(cell.getChars()).eql('');
+ expect(cell.getChars().length).eql(0);
+ expect(cell.getWidth()).eql(0);
} else {
- expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('¥');
- expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(1);
- expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2);
+ expect(cell.getChars()).eql('¥');
+ expect(cell.getChars().length).eql(1);
+ expect(cell.getWidth()).eql(2);
}
}
- let tchar = term.buffer.lines.get(0).get(term.cols - 1);
- expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('');
- expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(0);
- expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1);
- tchar = term.buffer.lines.get(1).get(0);
- expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('¥');
- expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(1);
- expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2);
+ term.buffer.lines.get(0).loadCell(term.cols - 1, cell);
+ expect(cell.getChars()).eql('');
+ expect(cell.getChars().length).eql(0);
+ expect(cell.getWidth()).eql(1);
+ term.buffer.lines.get(1).loadCell(0, cell);
+ expect(cell.getChars()).eql('¥');
+ expect(cell.getChars().length).eql(1);
+ expect(cell.getWidth()).eql(2);
});
it('line of ¥ with combining odd', () => {
term.wraparoundMode = true;
term.buffer.x = 1;
term.write(Array(50).join('¥\u0301'));
for (let i = 1; i < term.cols - 1; ++i) {
- const tchar = term.buffer.lines.get(0).get(i);
+ term.buffer.lines.get(0).loadCell(i, cell);
if (!(i % 2)) {
- expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('');
- expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(0);
- expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(0);
+ expect(cell.getChars()).eql('');
+ expect(cell.getChars().length).eql(0);
+ expect(cell.getWidth()).eql(0);
} else {
- expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('¥\u0301');
- expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(2);
- expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2);
+ expect(cell.getChars()).eql('¥\u0301');
+ expect(cell.getChars().length).eql(2);
+ expect(cell.getWidth()).eql(2);
}
}
- let tchar = term.buffer.lines.get(0).get(term.cols - 1);
- expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('');
- expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(0);
- expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1);
- tchar = term.buffer.lines.get(1).get(0);
- expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('¥\u0301');
- expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(2);
- expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2);
+ term.buffer.lines.get(0).loadCell(term.cols - 1, cell);
+ expect(cell.getChars()).eql('');
+ expect(cell.getChars().length).eql(0);
+ expect(cell.getWidth()).eql(1);
+ term.buffer.lines.get(1).loadCell(0, cell);
+ expect(cell.getChars()).eql('¥\u0301');
+ expect(cell.getChars().length).eql(2);
+ expect(cell.getWidth()).eql(2);
});
it('line of ¥ with combining even', () => {
term.wraparoundMode = true;
term.write(Array(50).join('¥\u0301'));
for (let i = 0; i < term.cols; ++i) {
- const tchar = term.buffer.lines.get(0).get(i);
+ term.buffer.lines.get(0).loadCell(i, cell);
if (i % 2) {
- expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('');
- expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(0);
- expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(0);
+ expect(cell.getChars()).eql('');
+ expect(cell.getChars().length).eql(0);
+ expect(cell.getWidth()).eql(0);
} else {
- expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('¥\u0301');
- expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(2);
- expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2);
+ expect(cell.getChars()).eql('¥\u0301');
+ expect(cell.getChars().length).eql(2);
+ expect(cell.getWidth()).eql(2);
}
}
- const tchar = term.buffer.lines.get(1).get(0);
- expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('¥\u0301');
- expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(2);
- expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2);
+ term.buffer.lines.get(1).loadCell(0, cell);
+ expect(cell.getChars()).eql('¥\u0301');
+ expect(cell.getChars().length).eql(2);
+ expect(cell.getWidth()).eql(2);
});
it('line of surrogate fullwidth with combining odd', () => {
term.wraparoundMode = true;
term.buffer.x = 1;
term.write(Array(50).join('\ud843\ude6d\u0301'));
for (let i = 1; i < term.cols - 1; ++i) {
- const tchar = term.buffer.lines.get(0).get(i);
+ term.buffer.lines.get(0).loadCell(i, cell);
if (!(i % 2)) {
- expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('');
- expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(0);
- expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(0);
+ expect(cell.getChars()).eql('');
+ expect(cell.getChars().length).eql(0);
+ expect(cell.getWidth()).eql(0);
} else {
- expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('\ud843\ude6d\u0301');
- expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(3);
- expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2);
+ expect(cell.getChars()).eql('\ud843\ude6d\u0301');
+ expect(cell.getChars().length).eql(3);
+ expect(cell.getWidth()).eql(2);
}
}
- let tchar = term.buffer.lines.get(0).get(term.cols - 1);
- expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('');
- expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(0);
- expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1);
- tchar = term.buffer.lines.get(1).get(0);
- expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('\ud843\ude6d\u0301');
- expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(3);
- expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2);
+ term.buffer.lines.get(0).loadCell(term.cols - 1, cell);
+ expect(cell.getChars()).eql('');
+ expect(cell.getChars().length).eql(0);
+ expect(cell.getWidth()).eql(1);
+ term.buffer.lines.get(1).loadCell(0, cell);
+ expect(cell.getChars()).eql('\ud843\ude6d\u0301');
+ expect(cell.getChars().length).eql(3);
+ expect(cell.getWidth()).eql(2);
});
it('line of surrogate fullwidth with combining even', () => {
term.wraparoundMode = true;
term.write(Array(50).join('\ud843\ude6d\u0301'));
for (let i = 0; i < term.cols; ++i) {
- const tchar = term.buffer.lines.get(0).get(i);
+ term.buffer.lines.get(0).loadCell(i, cell);
if (i % 2) {
- expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('');
- expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(0);
- expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(0);
+ expect(cell.getChars()).eql('');
+ expect(cell.getChars().length).eql(0);
+ expect(cell.getWidth()).eql(0);
} else {
- expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('\ud843\ude6d\u0301');
- expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(3);
- expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2);
+ expect(cell.getChars()).eql('\ud843\ude6d\u0301');
+ expect(cell.getChars().length).eql(3);
+ expect(cell.getWidth()).eql(2);
}
}
- const tchar = term.buffer.lines.get(1).get(0);
- expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('\ud843\ude6d\u0301');
- expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(3);
- expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2);
+ term.buffer.lines.get(1).loadCell(0, cell);
+ expect(cell.getChars()).eql('\ud843\ude6d\u0301');
+ expect(cell.getChars().length).eql(3);
+ expect(cell.getWidth()).eql(2);
});
});
describe('insert mode', () => {
+ const cell = new CellData();
it('halfwidth - all', () => {
term.write(Array(9).join('0123456789').slice(-80));
term.buffer.x = 10;
@@ -1036,10 +1048,10 @@ describe('term.js addons', () => {
term.insertMode = true;
term.write('abcde');
expect(term.buffer.lines.get(0).length).eql(term.cols);
- expect(term.buffer.lines.get(0).get(10)[CHAR_DATA_CHAR_INDEX]).eql('a');
- expect(term.buffer.lines.get(0).get(14)[CHAR_DATA_CHAR_INDEX]).eql('e');
- expect(term.buffer.lines.get(0).get(15)[CHAR_DATA_CHAR_INDEX]).eql('0');
- expect(term.buffer.lines.get(0).get(79)[CHAR_DATA_CHAR_INDEX]).eql('4');
+ expect(term.buffer.lines.get(0).loadCell(10, cell).getChars()).eql('a');
+ expect(term.buffer.lines.get(0).loadCell(14, cell).getChars()).eql('e');
+ expect(term.buffer.lines.get(0).loadCell(15, cell).getChars()).eql('0');
+ expect(term.buffer.lines.get(0).loadCell(79, cell).getChars()).eql('4');
});
it('fullwidth - insert', () => {
term.write(Array(9).join('0123456789').slice(-80));
@@ -1048,11 +1060,11 @@ describe('term.js addons', () => {
term.insertMode = true;
term.write('¥¥¥');
expect(term.buffer.lines.get(0).length).eql(term.cols);
- expect(term.buffer.lines.get(0).get(10)[CHAR_DATA_CHAR_INDEX]).eql('¥');
- expect(term.buffer.lines.get(0).get(11)[CHAR_DATA_CHAR_INDEX]).eql('');
- expect(term.buffer.lines.get(0).get(14)[CHAR_DATA_CHAR_INDEX]).eql('¥');
- expect(term.buffer.lines.get(0).get(15)[CHAR_DATA_CHAR_INDEX]).eql('');
- expect(term.buffer.lines.get(0).get(79)[CHAR_DATA_CHAR_INDEX]).eql('3');
+ expect(term.buffer.lines.get(0).loadCell(10, cell).getChars()).eql('¥');
+ expect(term.buffer.lines.get(0).loadCell(11, cell).getChars()).eql('');
+ expect(term.buffer.lines.get(0).loadCell(14, cell).getChars()).eql('¥');
+ expect(term.buffer.lines.get(0).loadCell(15, cell).getChars()).eql('');
+ expect(term.buffer.lines.get(0).loadCell(79, cell).getChars()).eql('3');
});
it('fullwidth - right border', () => {
term.write(Array(41).join('¥'));
@@ -1061,14 +1073,14 @@ describe('term.js addons', () => {
term.insertMode = true;
term.write('a');
expect(term.buffer.lines.get(0).length).eql(term.cols);
- expect(term.buffer.lines.get(0).get(10)[CHAR_DATA_CHAR_INDEX]).eql('a');
- expect(term.buffer.lines.get(0).get(11)[CHAR_DATA_CHAR_INDEX]).eql('¥');
- expect(term.buffer.lines.get(0).get(79)[CHAR_DATA_CHAR_INDEX]).eql(''); // fullwidth char got replaced
+ expect(term.buffer.lines.get(0).loadCell(10, cell).getChars()).eql('a');
+ expect(term.buffer.lines.get(0).loadCell(11, cell).getChars()).eql('¥');
+ expect(term.buffer.lines.get(0).loadCell(79, cell).getChars()).eql(''); // fullwidth char got replaced
term.write('b');
expect(term.buffer.lines.get(0).length).eql(term.cols);
- expect(term.buffer.lines.get(0).get(11)[CHAR_DATA_CHAR_INDEX]).eql('b');
- expect(term.buffer.lines.get(0).get(12)[CHAR_DATA_CHAR_INDEX]).eql('¥');
- expect(term.buffer.lines.get(0).get(79)[CHAR_DATA_CHAR_INDEX]).eql(''); // empty cell after fullwidth
+ expect(term.buffer.lines.get(0).loadCell(11, cell).getChars()).eql('b');
+ expect(term.buffer.lines.get(0).loadCell(12, cell).getChars()).eql('¥');
+ expect(term.buffer.lines.get(0).loadCell(79, cell).getChars()).eql(''); // empty cell after fullwidth
});
});
});
diff --git a/src/Terminal.ts b/src/Terminal.ts
index 0ced61c7..f011d2e4 100644
--- a/src/Terminal.ts
+++ b/src/Terminal.ts
@@ -25,7 +25,7 @@ import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminalOptions
import { IMouseZoneManager } from './ui/Types';
import { IRenderer } from './renderer/Types';
import { BufferSet } from './BufferSet';
-import { Buffer, MAX_BUFFER_SIZE, DEFAULT_ATTR, NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR, CHAR_DATA_ATTR_INDEX } from './Buffer';
+import { Buffer, MAX_BUFFER_SIZE, DEFAULT_ATTR, NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR } from './Buffer';
import { CompositionHelper } from './CompositionHelper';
import { EventEmitter } from './common/EventEmitter';
import { Viewport } from './Viewport';
@@ -344,7 +344,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
*/
public focus(): void {
if (this.textarea) {
- this.textarea.focus();
+ this.textarea.focus({ preventScroll: true });
}
}
@@ -1183,7 +1183,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
public scroll(isWrapped: boolean = false): void {
let newLine: IBufferLine;
newLine = this._blankLine;
- if (!newLine || newLine.length !== this.cols || newLine.get(0)[CHAR_DATA_ATTR_INDEX] !== this.eraseAttr()) {
+ if (!newLine || newLine.length !== this.cols || newLine.getFg(0) !== this.eraseAttr()) {
newLine = this.buffer.getBlankLine(this.eraseAttr(), isWrapped);
this._blankLine = newLine;
}
diff --git a/src/Types.ts b/src/Types.ts
index 888a321f..02ed41e2 100644
--- a/src/Types.ts
+++ b/src/Types.ts
@@ -298,6 +298,8 @@ export interface IBuffer {
getBlankLine(attr: number, isWrapped?: boolean): IBufferLine;
stringIndexToBufferIndex(lineIndex: number, stringIndex: number): number[];
iterator(trimRight: boolean, startIndex?: number, endIndex?: number, startOverscan?: number, endOverscan?: number): IBufferStringIterator;
+ getNullCell(fg?: number, bg?: number): ICellData;
+ getWhitespaceCell(fg?: number, bg?: number): ICellData;
}
export interface IBufferSet extends IEventEmitter {
@@ -520,6 +522,20 @@ export interface IEscapeSequenceParser extends IDisposable {
clearErrorHandler(): void;
}
+/** Cell data */
+export interface ICellData {
+ content: number;
+ fg: number;
+ bg: number;
+ combinedData: string;
+ isCombined(): number;
+ getWidth(): number;
+ getChars(): string;
+ getCode(): number;
+ setFromCharData(value: CharData): void;
+ getAsCharData(): CharData;
+}
+
/**
* Interface for a line in the terminal buffer.
*/
@@ -528,13 +544,27 @@ export interface IBufferLine {
isWrapped: boolean;
get(index: number): CharData;
set(index: number, value: CharData): void;
- insertCells(pos: number, n: number, ch: CharData): void;
- deleteCells(pos: number, n: number, fill: CharData): void;
- replaceCells(start: number, end: number, fill: CharData): void;
- resize(cols: number, fill: CharData): void;
- fill(fillCharData: CharData): void;
+ loadCell(index: number, cell: ICellData): ICellData;
+ setCell(index: number, cell: ICellData): void;
+ setCellFromCodePoint(index: number, codePoint: number, width: number, fg: number, bg: number): void;
+ addCodepointToCell(index: number, codePoint: number): void;
+ insertCells(pos: number, n: number, ch: ICellData): void;
+ deleteCells(pos: number, n: number, fill: ICellData): void;
+ replaceCells(start: number, end: number, fill: ICellData): void;
+ resize(cols: number, fill: ICellData): void;
+ fill(fillCellData: ICellData): void;
copyFrom(line: IBufferLine): void;
clone(): IBufferLine;
getTrimmedLength(): number;
translateToString(trimRight?: boolean, startCol?: number, endCol?: number): string;
+
+ /* direct access to cell attrs */
+ getWidth(index: number): number;
+ hasWidth(index: number): number;
+ getFg(index: number): number;
+ getBg(index: number): number;
+ hasContent(index: number): number;
+ getCodePoint(index: number): number;
+ isCombined(index: number): number;
+ getString(index: number): string;
}
diff --git a/src/addons/attach/tsconfig.json b/src/addons/attach/tsconfig.json
index 359fbd24..2f39102c 100644
--- a/src/addons/attach/tsconfig.json
+++ b/src/addons/attach/tsconfig.json
@@ -10,8 +10,7 @@
"outDir": "../../../lib/addons/attach/",
"sourceMap": true,
"removeComments": true,
- "declaration": true,
- "preserveWatchOutput": true
+ "declaration": true
},
"include": [
"**/*.ts",
diff --git a/src/addons/fit/tsconfig.json b/src/addons/fit/tsconfig.json
index 489ccdfe..3458d23a 100644
--- a/src/addons/fit/tsconfig.json
+++ b/src/addons/fit/tsconfig.json
@@ -11,7 +11,6 @@
"sourceMap": true,
"removeComments": true,
"declaration": true,
- "preserveWatchOutput": true,
"types": [
"../../node_modules/@types/mocha"
]
diff --git a/src/addons/fullscreen/tsconfig.json b/src/addons/fullscreen/tsconfig.json
index 05e6df68..0c74c25c 100644
--- a/src/addons/fullscreen/tsconfig.json
+++ b/src/addons/fullscreen/tsconfig.json
@@ -11,7 +11,6 @@
"sourceMap": true,
"removeComments": true,
"declaration": true,
- "preserveWatchOutput": true,
"types": [
"../../node_modules/@types/mocha"
]
diff --git a/src/addons/search/tsconfig.json b/src/addons/search/tsconfig.json
index 87899cda..6a1611a5 100644
--- a/src/addons/search/tsconfig.json
+++ b/src/addons/search/tsconfig.json
@@ -11,7 +11,6 @@
"sourceMap": true,
"removeComments": true,
"declaration": true,
- "preserveWatchOutput": true,
"types": [
"../../node_modules/@types/mocha"
]
diff --git a/src/addons/terminado/tsconfig.json b/src/addons/terminado/tsconfig.json
index 91c18314..e2e19445 100644
--- a/src/addons/terminado/tsconfig.json
+++ b/src/addons/terminado/tsconfig.json
@@ -10,7 +10,6 @@
"sourceMap": true,
"removeComments": true,
"declaration": true,
- "preserveWatchOutput": true,
"types": [
"../../node_modules/@types/mocha"
]
diff --git a/src/addons/webLinks/tsconfig.json b/src/addons/webLinks/tsconfig.json
index 18105aa2..9c4f1176 100644
--- a/src/addons/webLinks/tsconfig.json
+++ b/src/addons/webLinks/tsconfig.json
@@ -11,7 +11,6 @@
"sourceMap": true,
"removeComments": true,
"declaration": true,
- "preserveWatchOutput": true,
"types": [
"../../node_modules/@types/mocha"
]
diff --git a/src/addons/winptyCompat/tsconfig.json b/src/addons/winptyCompat/tsconfig.json
index 9fc4d25e..fa48c963 100644
--- a/src/addons/winptyCompat/tsconfig.json
+++ b/src/addons/winptyCompat/tsconfig.json
@@ -10,7 +10,6 @@
"sourceMap": true,
"removeComments": true,
"declaration": true,
- "preserveWatchOutput": true,
"types": [
"../../node_modules/@types/mocha"
]
diff --git a/src/addons/winptyCompat/winptyCompat.ts b/src/addons/winptyCompat/winptyCompat.ts
index d162f4e9..58f59fd9 100644
--- a/src/addons/winptyCompat/winptyCompat.ts
+++ b/src/addons/winptyCompat/winptyCompat.ts
@@ -13,12 +13,6 @@ const WHITESPACE_CELL_CODE = 32;
export function winptyCompatInit(terminal: Terminal): void {
const addonTerminal = terminal;
- // Don't do anything when the platform is not Windows
- const isWindows = ['Windows', 'Win16', 'Win32', 'WinCE'].indexOf(navigator.platform) >= 0;
- if (!isWindows) {
- return;
- }
-
(addonTerminal._core as any).isWinptyCompatEnabled = true;
// Winpty does not support wraparound mode which means that lines will never
diff --git a/src/addons/zmodem/tsconfig.json b/src/addons/zmodem/tsconfig.json
index 2b49f537..7d821b7c 100644
--- a/src/addons/zmodem/tsconfig.json
+++ b/src/addons/zmodem/tsconfig.json
@@ -10,7 +10,6 @@
"sourceMap": true,
"removeComments": true,
"declaration": true,
- "preserveWatchOutput": true,
"types": [
"../../node_modules/@types/mocha"
]
diff --git a/src/common/tsconfig.json b/src/common/tsconfig.json
index 19dd0273..b40bb2f5 100644
--- a/src/common/tsconfig.json
+++ b/src/common/tsconfig.json
@@ -1,17 +1,7 @@
{
+ "extends": "../tsconfig-library-base",
"compilerOptions": {
- "target": "es5",
- "lib": [
- "es5"
- ],
- "rootDir": ".",
- "noEmit": true,
- "strict": true,
- "pretty": true,
- "types": [
- "../../node_modules/@types/mocha",
- "../../"
- ]
+ "outDir": "../../lib"
},
"include": [
"./**/*"
diff --git a/src/core/tsconfig.json b/src/core/tsconfig.json
index 4f024a28..41e41f0c 100644
--- a/src/core/tsconfig.json
+++ b/src/core/tsconfig.json
@@ -1,20 +1,12 @@
{
+ "extends": "../tsconfig-library-base",
"compilerOptions": {
- "target": "es5",
- "lib": [
- "es5"
- ],
- "rootDir": ".",
- "noEmit": true,
- "strict": true,
- "pretty": true,
- "types": [
- "../../node_modules/@types/mocha",
- "../../"
- ]
+ "outDir": "../../lib"
},
"include": [
- "./**/*",
- "../common/**/*"
+ "./**/*"
+ ],
+ "references": [
+ { "path": "../common" }
]
}
diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts
index a609d79c..7bd38bb1 100644
--- a/src/renderer/BaseRenderLayer.ts
+++ b/src/renderer/BaseRenderLayer.ts
@@ -4,12 +4,12 @@
*/
import { IRenderLayer, IColorSet, IRenderDimensions } from './Types';
-import { CharData, ITerminal } from '../Types';
+import { ITerminal } from '../Types';
import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, IGlyphIdentifier } from './atlas/Types';
import BaseCharAtlas from './atlas/BaseCharAtlas';
import { acquireCharAtlas } from './atlas/CharAtlasCache';
-import { CHAR_DATA_CHAR_INDEX } from '../Buffer';
import { is256Color } from './atlas/CharAtlasUtils';
+import { CellData } from '../BufferLine';
export abstract class BaseRenderLayer implements IRenderLayer {
private _canvas: HTMLCanvasElement;
@@ -229,17 +229,17 @@ export abstract class BaseRenderLayer implements IRenderLayer {
* ensure that it fits with the cell, including the cell to the right if it's
* a wide character. This uses the existing fillStyle on the context.
* @param terminal The terminal.
- * @param charData The char data for the character to draw.
+ * @param cell The cell data for the character to draw.
* @param x The column to draw at.
* @param y The row to draw at.
* @param color The color of the character.
*/
- protected fillCharTrueColor(terminal: ITerminal, charData: CharData, x: number, y: number): void {
+ protected fillCharTrueColor(terminal: ITerminal, cell: CellData, x: number, y: number): void {
this._ctx.font = this._getFont(terminal, false, false);
this._ctx.textBaseline = 'middle';
this._clipRow(terminal, y);
this._ctx.fillText(
- charData[CHAR_DATA_CHAR_INDEX],
+ cell.getChars(),
x * this._scaledCellWidth + this._scaledCharLeft,
y * this._scaledCellHeight + this._scaledCharTop + this._scaledCharHeight / 2);
}
diff --git a/src/renderer/CharacterJoinerRegistry.test.ts b/src/renderer/CharacterJoinerRegistry.test.ts
index 0c29566a..effdbfaa 100644
--- a/src/renderer/CharacterJoinerRegistry.test.ts
+++ b/src/renderer/CharacterJoinerRegistry.test.ts
@@ -5,7 +5,7 @@ import { CircularList } from '../common/CircularList';
import { ICharacterJoinerRegistry } from './Types';
import { CharacterJoinerRegistry } from './CharacterJoinerRegistry';
-import { BufferLine } from '../BufferLine';
+import { BufferLine, CellData } from '../BufferLine';
import { IBufferLine } from '../Types';
describe('CharacterJoinerRegistry', () => {
@@ -24,18 +24,18 @@ describe('CharacterJoinerRegistry', () => {
lines.set(4, new BufferLine(0));
lines.set(5, lineData([['a', 0x11111111], [' -> b -> c -> '], ['d', 0x22222222]]));
const line6 = lineData([['wi']]);
- line6.resize(line6.length + 1, [0, '¥', 2, '¥'.charCodeAt(0)]);
- line6.resize(line6.length + 1, [0, '', 0, null]);
+ line6.resize(line6.length + 1, CellData.fromCharData([0, '¥', 2, '¥'.charCodeAt(0)]));
+ line6.resize(line6.length + 1, CellData.fromCharData([0, '', 0, null]));
let sub = lineData([['deemo']]);
let oldSize = line6.length;
- line6.resize(oldSize + sub.length, [0, '', 0, 0]);
- for (let i = 0; i < sub.length; ++i) line6.set(i + oldSize, sub.get(i));
- line6.resize(line6.length + 1, [0, '\xf0\x9f\x98\x81', 1, 128513]);
- line6.resize(line6.length + 1, [0, ' ', 1, ' '.charCodeAt(0)]);
+ line6.resize(oldSize + sub.length, CellData.fromCharData([0, '', 0, 0]));
+ for (let i = 0; i < sub.length; ++i) line6.setCell(i + oldSize, sub.loadCell(i, new CellData()));
+ line6.resize(line6.length + 1, CellData.fromCharData([0, '\xf0\x9f\x98\x81', 1, 128513]));
+ line6.resize(line6.length + 1, CellData.fromCharData([0, ' ', 1, ' '.charCodeAt(0)]));
sub = lineData([['jiabc']]);
oldSize = line6.length;
- line6.resize(oldSize + sub.length, [0, '', 0, 0]);
- for (let i = 0; i < sub.length; ++i) line6.set(i + oldSize, sub.get(i));
+ line6.resize(oldSize + sub.length, CellData.fromCharData([0, '', 0, 0]));
+ for (let i = 0; i < sub.length; ++i) line6.setCell(i + oldSize, sub.loadCell(i, new CellData()));
lines.set(6, line6);
(terminal.buffer).setLines(lines);
@@ -273,8 +273,8 @@ function lineData(data: IPartialLineData[]): IBufferLine {
const line = data[i][0];
const attr = (data[i][1] || 0);
const offset = tline.length;
- tline.resize(tline.length + line.split('').length, [0, '', 0, 0]);
- line.split('').map((char, idx) => tline.set(idx + offset, [attr, char, 1, char.charCodeAt(0)]));
+ tline.resize(tline.length + line.split('').length, CellData.fromCharData([0, '', 0, 0]));
+ line.split('').map((char, idx) => tline.setCell(idx + offset, CellData.fromCharData([attr, char, 1, char.charCodeAt(0)])));
}
return tline;
}
diff --git a/src/renderer/CharacterJoinerRegistry.ts b/src/renderer/CharacterJoinerRegistry.ts
index dc9e95dd..4a899d72 100644
--- a/src/renderer/CharacterJoinerRegistry.ts
+++ b/src/renderer/CharacterJoinerRegistry.ts
@@ -1,11 +1,12 @@
-import { CHAR_DATA_ATTR_INDEX, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from '../Buffer';
import { ITerminal, IBufferLine } from '../Types';
import { ICharacterJoinerRegistry, ICharacterJoiner } from './Types';
+import { CellData } from '../BufferLine';
export class CharacterJoinerRegistry implements ICharacterJoinerRegistry {
private _characterJoiners: ICharacterJoiner[] = [];
private _nextCharacterJoinerId: number = 0;
+ private _workCell: CellData = new CellData();
constructor(private _terminal: ITerminal) {
}
@@ -51,13 +52,13 @@ export class CharacterJoinerRegistry implements ICharacterJoinerRegistry {
let rangeStartColumn = 0;
let currentStringIndex = 0;
let rangeStartStringIndex = 0;
- let rangeAttr = line.get(0)[CHAR_DATA_ATTR_INDEX] >> 9;
+ let rangeAttr = line.getFg(0) >> 9;
for (let x = 0; x < this._terminal.cols; x++) {
- const charData = line.get(x);
- const chars = charData[CHAR_DATA_CHAR_INDEX];
- const width = charData[CHAR_DATA_WIDTH_INDEX];
- const attr = charData[CHAR_DATA_ATTR_INDEX] >> 9;
+ line.loadCell(x, this._workCell);
+ const chars = this._workCell.getChars();
+ const width = this._workCell.getWidth();
+ const attr = this._workCell.fg >> 9;
if (width === 0) {
// If this character is of width 0, skip it.
@@ -152,9 +153,8 @@ export class CharacterJoinerRegistry implements ICharacterJoinerRegistry {
}
for (let x = startCol; x < this._terminal.cols; x++) {
- const charData = line.get(x);
- const width = charData[CHAR_DATA_WIDTH_INDEX];
- const length = charData[CHAR_DATA_CHAR_INDEX].length;
+ const width = line.getWidth(x);
+ const length = line.getString(x).length;
// We skip zero-width characters when creating the string to join the text
// so we do the same here
diff --git a/src/renderer/CursorRenderLayer.ts b/src/renderer/CursorRenderLayer.ts
index 08a14739..c3b751fa 100644
--- a/src/renderer/CursorRenderLayer.ts
+++ b/src/renderer/CursorRenderLayer.ts
@@ -3,10 +3,10 @@
* @license MIT
*/
-import { CHAR_DATA_WIDTH_INDEX } from '../Buffer';
import { IColorSet, IRenderDimensions } from './Types';
import { BaseRenderLayer } from './BaseRenderLayer';
-import { CharData, ITerminal } from '../Types';
+import { ITerminal, ICellData } from '../Types';
+import { CellData } from '../BufferLine';
interface ICursorState {
x: number;
@@ -23,8 +23,9 @@ const BLINK_INTERVAL = 600;
export class CursorRenderLayer extends BaseRenderLayer {
private _state: ICursorState;
- private _cursorRenderers: {[key: string]: (terminal: ITerminal, x: number, y: number, charData: CharData) => void};
+ private _cursorRenderers: {[key: string]: (terminal: ITerminal, x: number, y: number, cell: ICellData) => void};
private _cursorBlinkStateManager: CursorBlinkStateManager;
+ private _cell: ICellData = new CellData();
constructor(container: HTMLElement, zIndex: number, colors: IColorSet) {
super(container, 'cursor', zIndex, true, colors);
@@ -127,8 +128,8 @@ export class CursorRenderLayer extends BaseRenderLayer {
return;
}
- const charData = terminal.buffer.lines.get(cursorY).get(terminal.buffer.x);
- if (!charData) {
+ terminal.buffer.lines.get(cursorY).loadCell(terminal.buffer.x, this._cell);
+ if (this._cell.content === undefined) {
return;
}
@@ -136,13 +137,13 @@ export class CursorRenderLayer extends BaseRenderLayer {
this._clearCursor();
this._ctx.save();
this._ctx.fillStyle = this._colors.cursor.css;
- this._renderBlurCursor(terminal, terminal.buffer.x, viewportRelativeCursorY, charData);
+ this._renderBlurCursor(terminal, terminal.buffer.x, viewportRelativeCursorY, this._cell);
this._ctx.restore();
this._state.x = terminal.buffer.x;
this._state.y = viewportRelativeCursorY;
this._state.isFocused = false;
this._state.style = terminal.options.cursorStyle;
- this._state.width = charData[CHAR_DATA_WIDTH_INDEX];
+ this._state.width = this._cell.getWidth();
return;
}
@@ -158,21 +159,21 @@ export class CursorRenderLayer extends BaseRenderLayer {
this._state.y === viewportRelativeCursorY &&
this._state.isFocused === terminal.isFocused &&
this._state.style === terminal.options.cursorStyle &&
- this._state.width === charData[CHAR_DATA_WIDTH_INDEX]) {
+ this._state.width === this._cell.getWidth()) {
return;
}
this._clearCursor();
}
this._ctx.save();
- this._cursorRenderers[terminal.options.cursorStyle || 'block'](terminal, terminal.buffer.x, viewportRelativeCursorY, charData);
+ this._cursorRenderers[terminal.options.cursorStyle || 'block'](terminal, terminal.buffer.x, viewportRelativeCursorY, this._cell);
this._ctx.restore();
this._state.x = terminal.buffer.x;
this._state.y = viewportRelativeCursorY;
this._state.isFocused = false;
this._state.style = terminal.options.cursorStyle;
- this._state.width = charData[CHAR_DATA_WIDTH_INDEX];
+ this._state.width = this._cell.getWidth();
}
private _clearCursor(): void {
@@ -188,33 +189,33 @@ export class CursorRenderLayer extends BaseRenderLayer {
}
}
- private _renderBarCursor(terminal: ITerminal, x: number, y: number, charData: CharData): void {
+ private _renderBarCursor(terminal: ITerminal, x: number, y: number, cell: ICellData): void {
this._ctx.save();
this._ctx.fillStyle = this._colors.cursor.css;
this.fillLeftLineAtCell(x, y);
this._ctx.restore();
}
- private _renderBlockCursor(terminal: ITerminal, x: number, y: number, charData: CharData): void {
+ private _renderBlockCursor(terminal: ITerminal, x: number, y: number, cell: ICellData): void {
this._ctx.save();
this._ctx.fillStyle = this._colors.cursor.css;
- this.fillCells(x, y, charData[CHAR_DATA_WIDTH_INDEX], 1);
+ this.fillCells(x, y, cell.getWidth(), 1);
this._ctx.fillStyle = this._colors.cursorAccent.css;
- this.fillCharTrueColor(terminal, charData, x, y);
+ this.fillCharTrueColor(terminal, cell, x, y);
this._ctx.restore();
}
- private _renderUnderlineCursor(terminal: ITerminal, x: number, y: number, charData: CharData): void {
+ private _renderUnderlineCursor(terminal: ITerminal, x: number, y: number, cell: ICellData): void {
this._ctx.save();
this._ctx.fillStyle = this._colors.cursor.css;
this.fillBottomLineAtCells(x, y);
this._ctx.restore();
}
- private _renderBlurCursor(terminal: ITerminal, x: number, y: number, charData: CharData): void {
+ private _renderBlurCursor(terminal: ITerminal, x: number, y: number, cell: ICellData): void {
this._ctx.save();
this._ctx.strokeStyle = this._colors.cursor.css;
- this.strokeRectAtCell(x, y, charData[CHAR_DATA_WIDTH_INDEX], 1);
+ this.strokeRectAtCell(x, y, cell.getWidth(), 1);
this._ctx.restore();
}
}
diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts
index b8ef87aa..2c1b516a 100644
--- a/src/renderer/Renderer.ts
+++ b/src/renderer/Renderer.ts
@@ -85,6 +85,7 @@ export class Renderer extends EventEmitter implements IRenderer {
this._isPaused = entry.intersectionRatio === 0;
if (!this._isPaused && this._needsFullRefresh) {
this._terminal.refresh(0, this._terminal.rows - 1);
+ this._needsFullRefresh = false;
}
}
diff --git a/src/renderer/TextRenderLayer.ts b/src/renderer/TextRenderLayer.ts
index ade2dd4c..f56ccf3a 100644
--- a/src/renderer/TextRenderLayer.ts
+++ b/src/renderer/TextRenderLayer.ts
@@ -3,13 +3,14 @@
* @license MIT
*/
-import { CHAR_DATA_ATTR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, NULL_CELL_CODE, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE } from '../Buffer';
+import { NULL_CELL_CODE, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE } from '../Buffer';
import { FLAGS, IColorSet, IRenderDimensions, ICharacterJoinerRegistry } from './Types';
import { CharData, ITerminal } from '../Types';
import { INVERTED_DEFAULT_COLOR, DEFAULT_COLOR } from './atlas/Types';
import { GridCache } from './GridCache';
import { BaseRenderLayer } from './BaseRenderLayer';
import { is256Color } from './atlas/CharAtlasUtils';
+import { CellData } from '../BufferLine';
/**
* This CharData looks like a null character, which will forc a clear and render
@@ -24,6 +25,7 @@ export class TextRenderLayer extends BaseRenderLayer {
private _characterFont: string;
private _characterOverlapCache: { [key: string]: boolean } = {};
private _characterJoinerRegistry: ICharacterJoinerRegistry;
+ private _workCell = new CellData();
constructor(container: HTMLElement, zIndex: number, colors: IColorSet, characterJoinerRegistry: ICharacterJoinerRegistry, alpha: boolean) {
super(container, 'text', zIndex, alpha, colors);
@@ -72,14 +74,14 @@ export class TextRenderLayer extends BaseRenderLayer {
const line = terminal.buffer.lines.get(row);
const joinedRanges = joinerRegistry ? joinerRegistry.getJoinedCharacters(row) : [];
for (let x = 0; x < terminal.cols; x++) {
- const charData = line.get(x);
- let code: number = charData[CHAR_DATA_CODE_INDEX] || WHITESPACE_CELL_CODE;
+ line.loadCell(x, this._workCell);
+ let code: number = this._workCell.getCode() || WHITESPACE_CELL_CODE;
// Can either represent character(s) for a single cell or multiple cells
// if indicated by a character joiner.
- let chars: string = charData[CHAR_DATA_CHAR_INDEX] || WHITESPACE_CELL_CHAR;
- const attr: number = charData[CHAR_DATA_ATTR_INDEX];
- let width: number = charData[CHAR_DATA_WIDTH_INDEX];
+ let chars = this._workCell.getChars() || WHITESPACE_CELL_CHAR;
+ const attr = this._workCell.fg;
+ let width = this._workCell.getWidth();
// If true, indicates that the current character(s) to draw were joined.
let isJoined = false;
@@ -117,7 +119,7 @@ export class TextRenderLayer extends BaseRenderLayer {
// right is a space, take ownership of the cell to the right. We skip
// this check for joined characters because their rendering likely won't
// yield the same result as rendering the last character individually.
- if (!isJoined && this._isOverlapping(charData)) {
+ if (!isJoined && this._isOverlapping(chars, width, code)) {
// If the character is overlapping, we want to force a re-render on every
// frame. This is specifically to work around the case where two
// overlaping chars `a` and `b` are adjacent, the cursor is moved to b and a
@@ -125,7 +127,7 @@ export class TextRenderLayer extends BaseRenderLayer {
// get removed, and `a` would not re-render because it thinks it's
// already in the correct state.
// this._state.cache[x][y] = OVERLAP_OWNED_CHAR_DATA;
- if (lastCharX < line.length - 1 && line.get(lastCharX + 1)[CHAR_DATA_CODE_INDEX] === NULL_CELL_CODE) {
+ if (lastCharX < line.length - 1 && line.loadCell(lastCharX + 1, this._workCell).getCode() === NULL_CELL_CODE) {
width = 2;
// this._clearChar(x + 1, y);
// The overlapping char's char data will force a clear and render when the
@@ -271,21 +273,19 @@ export class TextRenderLayer extends BaseRenderLayer {
/**
* Whether a character is overlapping to the next cell.
*/
- private _isOverlapping(charData: CharData): boolean {
+ private _isOverlapping(char: string, width: number, code: number): boolean {
// Only single cell characters can be overlapping, rendering issues can
// occur without this check
- if (charData[CHAR_DATA_WIDTH_INDEX] !== 1) {
+ if (width !== 1) {
return false;
}
// We assume that any ascii character will not overlap
- const code = charData[CHAR_DATA_CODE_INDEX];
if (code < 256) {
return false;
}
// Deliver from cache if available
- const char = charData[CHAR_DATA_CHAR_INDEX];
if (this._characterOverlapCache.hasOwnProperty(char)) {
return this._characterOverlapCache[char];
}
diff --git a/src/renderer/dom/DomRenderer.ts b/src/renderer/dom/DomRenderer.ts
index c5ef212d..78ccc620 100644
--- a/src/renderer/dom/DomRenderer.ts
+++ b/src/renderer/dom/DomRenderer.ts
@@ -9,7 +9,7 @@ import { ITheme } from 'xterm';
import { EventEmitter } from '../../common/EventEmitter';
import { ColorManager } from '../ColorManager';
import { RenderDebouncer } from '../../ui/RenderDebouncer';
-import { BOLD_CLASS, ITALIC_CLASS, CURSOR_CLASS, CURSOR_STYLE_BLOCK_CLASS, CURSOR_STYLE_BAR_CLASS, CURSOR_STYLE_UNDERLINE_CLASS, DomRendererRowFactory } from './DomRendererRowFactory';
+import { BOLD_CLASS, ITALIC_CLASS, CURSOR_CLASS, CURSOR_STYLE_BLOCK_CLASS, CURSOR_BLINK_CLASS, CURSOR_STYLE_BAR_CLASS, CURSOR_STYLE_UNDERLINE_CLASS, DomRendererRowFactory } from './DomRendererRowFactory';
import { INVERTED_DEFAULT_COLOR } from '../atlas/Types';
const TERMINAL_CLASS_PREFIX = 'xterm-dom-renderer-owner-';
@@ -75,7 +75,7 @@ export class DomRenderer extends EventEmitter implements IRenderer {
this._updateDimensions();
this._renderDebouncer = new RenderDebouncer(this._terminal, this._renderRows.bind(this));
- this._rowFactory = new DomRendererRowFactory(document);
+ this._rowFactory = new DomRendererRowFactory(_terminal.options, document);
this._terminal.element.classList.add(TERMINAL_CLASS_PREFIX + this._terminalClass);
this._terminal.screenElement.appendChild(this._rowContainer);
@@ -165,12 +165,22 @@ export class DomRenderer extends EventEmitter implements IRenderer {
`${this._terminalSelector} span.${ITALIC_CLASS} {` +
` font-style: italic;` +
`}`;
+ // Blink animation
+ styles +=
+ `@keyframes blink {` +
+ ` 0 % { opacity: 1.0; }` +
+ ` 50% { opacity: 0.0; }` +
+ ` 100 % { opacity: 1.0; }` +
+ `}`;
// Cursor
styles +=
`${this._terminalSelector} .${ROW_CONTAINER_CLASS}:not(.${FOCUS_CLASS}) .${CURSOR_CLASS} {` +
` outline: 1px solid ${this.colorManager.colors.cursor.css};` +
` outline-offset: -1px;` +
`}` +
+ `${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${CURSOR_CLASS}.${CURSOR_BLINK_CLASS} {` +
+ ` animation: blink 1s step-end infinite;` +
+ `}` +
`${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${CURSOR_CLASS}.${CURSOR_STYLE_BLOCK_CLASS} {` +
` background-color: ${this.colorManager.colors.cursor.css};` +
` color: ${this.colorManager.colors.cursorAccent.css};` +
@@ -328,6 +338,7 @@ export class DomRenderer extends EventEmitter implements IRenderer {
const cursorAbsoluteY = terminal.buffer.ybase + terminal.buffer.y;
const cursorX = this._terminal.buffer.x;
+ const cursorBlink = this._terminal.options.cursorBlink;
for (let y = start; y <= end; y++) {
const rowElement = this._rowElements[y];
@@ -336,7 +347,7 @@ export class DomRenderer extends EventEmitter implements IRenderer {
const row = y + terminal.buffer.ydisp;
const lineData = terminal.buffer.lines.get(row);
const cursorStyle = terminal.options.cursorStyle;
- rowElement.appendChild(this._rowFactory.createRow(lineData, row === cursorAbsoluteY, cursorStyle, cursorX, this.dimensions.actualCellWidth, terminal.cols));
+ rowElement.appendChild(this._rowFactory.createRow(lineData, row === cursorAbsoluteY, cursorStyle, cursorX, cursorBlink, this.dimensions.actualCellWidth, terminal.cols));
}
this._terminal.emit('refresh', {start, end});
diff --git a/src/renderer/dom/DomRendererRowFactory.test.ts b/src/renderer/dom/DomRendererRowFactory.test.ts
index 67342da0..5ca008bc 100644
--- a/src/renderer/dom/DomRendererRowFactory.test.ts
+++ b/src/renderer/dom/DomRendererRowFactory.test.ts
@@ -8,34 +8,39 @@ import { assert } from 'chai';
import { DomRendererRowFactory } from './DomRendererRowFactory';
import { DEFAULT_ATTR, NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR } from '../../Buffer';
import { FLAGS } from '../Types';
-import { BufferLine } from '../../BufferLine';
-import { IBufferLine } from '../../Types';
+import { BufferLine, CellData } from '../../BufferLine';
+import { IBufferLine, ITerminalOptions } from '../../Types';
import { DEFAULT_COLOR } from '../atlas/Types';
describe('DomRendererRowFactory', () => {
let dom: jsdom.JSDOM;
+ const options: ITerminalOptions = {};
let rowFactory: DomRendererRowFactory;
let lineData: IBufferLine;
beforeEach(() => {
dom = new jsdom.JSDOM('');
- rowFactory = new DomRendererRowFactory(dom.window.document);
+
+ options.enableBold = true;
+ options.drawBoldTextInBrightColors = true;
+
+ rowFactory = new DomRendererRowFactory(options, dom.window.document);
lineData = createEmptyLineData(2);
});
describe('createRow', () => {
it('should not create anything for an empty row', () => {
- const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20);
+ const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
''
);
});
it('should set correct attributes for double width characters', () => {
- lineData.set(0, [DEFAULT_ATTR, '語', 2, '語'.charCodeAt(0)]);
+ lineData.setCell(0, CellData.fromCharData([DEFAULT_ATTR, '語', 2, '語'.charCodeAt(0)]));
// There should be no element for the following "empty" cell
- lineData.set(1, [DEFAULT_ATTR, '', 0, undefined]);
- const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20);
+ lineData.setCell(1, CellData.fromCharData([DEFAULT_ATTR, '', 0, undefined]));
+ const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
'語'
);
@@ -43,17 +48,24 @@ describe('DomRendererRowFactory', () => {
it('should add class for cursor and cursor style', () => {
for (const style of ['block', 'bar', 'underline']) {
- const fragment = rowFactory.createRow(lineData, true, style, 0, 5, 20);
+ const fragment = rowFactory.createRow(lineData, true, style, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
``
);
}
});
+ it('should add class for cursor blink', () => {
+ const fragment = rowFactory.createRow(lineData, true, 'block', 0, true, 5, 20);
+ assert.equal(getFragmentHtml(fragment),
+ ``
+ );
+ });
+
it('should not render cells that go beyond the terminal\'s columns', () => {
- lineData.set(0, [DEFAULT_ATTR, 'a', 1, 'a'.charCodeAt(0)]);
- lineData.set(1, [DEFAULT_ATTR, 'b', 1, 'b'.charCodeAt(0)]);
- const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 1);
+ lineData.setCell(0, CellData.fromCharData([DEFAULT_ATTR, 'a', 1, 'a'.charCodeAt(0)]));
+ lineData.setCell(1, CellData.fromCharData([DEFAULT_ATTR, 'b', 1, 'b'.charCodeAt(0)]));
+ const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 1);
assert.equal(getFragmentHtml(fragment),
'a'
);
@@ -61,16 +73,16 @@ describe('DomRendererRowFactory', () => {
describe('attributes', () => {
it('should add class for bold', () => {
- lineData.set(0, [DEFAULT_ATTR | (FLAGS.BOLD << 18), 'a', 1, 'a'.charCodeAt(0)]);
- const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20);
+ lineData.setCell(0, CellData.fromCharData([DEFAULT_ATTR | (FLAGS.BOLD << 18), 'a', 1, 'a'.charCodeAt(0)]));
+ const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
'a'
);
});
it('should add class for italic', () => {
- lineData.set(0, [DEFAULT_ATTR | (FLAGS.ITALIC << 18), 'a', 1, 'a'.charCodeAt(0)]);
- const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20);
+ lineData.setCell(0, CellData.fromCharData([DEFAULT_ATTR | (FLAGS.ITALIC << 18), 'a', 1, 'a'.charCodeAt(0)]));
+ const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
'a'
);
@@ -79,8 +91,8 @@ describe('DomRendererRowFactory', () => {
it('should add classes for 256 foreground colors', () => {
const defaultAttrNoFgColor = (0 << 9) | (DEFAULT_COLOR << 0);
for (let i = 0; i < 256; i++) {
- lineData.set(0, [defaultAttrNoFgColor | (i << 9), 'a', 1, 'a'.charCodeAt(0)]);
- const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20);
+ lineData.setCell(0, CellData.fromCharData([defaultAttrNoFgColor | (i << 9), 'a', 1, 'a'.charCodeAt(0)]));
+ const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
`a`
);
@@ -90,8 +102,8 @@ describe('DomRendererRowFactory', () => {
it('should add classes for 256 background colors', () => {
const defaultAttrNoBgColor = (DEFAULT_ATTR << 9) | (0 << 0);
for (let i = 0; i < 256; i++) {
- lineData.set(0, [defaultAttrNoBgColor | (i << 0), 'a', 1, 'a'.charCodeAt(0)]);
- const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20);
+ lineData.setCell(0, CellData.fromCharData([defaultAttrNoBgColor | (i << 0), 'a', 1, 'a'.charCodeAt(0)]));
+ const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
`a`
);
@@ -99,24 +111,24 @@ describe('DomRendererRowFactory', () => {
});
it('should correctly invert colors', () => {
- lineData.set(0, [(FLAGS.INVERSE << 18) | (2 << 9) | (1 << 0), 'a', 1, 'a'.charCodeAt(0)]);
- const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20);
+ lineData.setCell(0, CellData.fromCharData([(FLAGS.INVERSE << 18) | (2 << 9) | (1 << 0), 'a', 1, 'a'.charCodeAt(0)]));
+ const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
'a'
);
});
it('should correctly invert default fg color', () => {
- lineData.set(0, [(FLAGS.INVERSE << 18) | (DEFAULT_ATTR << 9) | (1 << 0), 'a', 1, 'a'.charCodeAt(0)]);
- const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20);
+ lineData.setCell(0, CellData.fromCharData([(FLAGS.INVERSE << 18) | (DEFAULT_ATTR << 9) | (1 << 0), 'a', 1, 'a'.charCodeAt(0)]));
+ const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
'a'
);
});
it('should correctly invert default bg color', () => {
- lineData.set(0, [(FLAGS.INVERSE << 18) | (1 << 9) | (DEFAULT_COLOR << 0), 'a', 1, 'a'.charCodeAt(0)]);
- const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20);
+ lineData.setCell(0, CellData.fromCharData([(FLAGS.INVERSE << 18) | (1 << 9) | (DEFAULT_COLOR << 0), 'a', 1, 'a'.charCodeAt(0)]));
+ const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
'a'
);
@@ -124,8 +136,8 @@ describe('DomRendererRowFactory', () => {
it('should turn bold fg text bright', () => {
for (let i = 0; i < 8; i++) {
- lineData.set(0, [(FLAGS.BOLD << 18) | (i << 9) | (DEFAULT_COLOR << 0), 'a', 1, 'a'.charCodeAt(0)]);
- const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20);
+ lineData.setCell(0, CellData.fromCharData([(FLAGS.BOLD << 18) | (i << 9) | (DEFAULT_COLOR << 0), 'a', 1, 'a'.charCodeAt(0)]));
+ const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
`a`
);
@@ -143,7 +155,7 @@ describe('DomRendererRowFactory', () => {
function createEmptyLineData(cols: number): IBufferLine {
const lineData = new BufferLine(cols);
for (let i = 0; i < cols; i++) {
- lineData.set(i, [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]);
+ lineData.setCell(i, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]));
}
return lineData;
}
diff --git a/src/renderer/dom/DomRendererRowFactory.ts b/src/renderer/dom/DomRendererRowFactory.ts
index 8bcde39a..47232981 100644
--- a/src/renderer/dom/DomRendererRowFactory.ts
+++ b/src/renderer/dom/DomRendererRowFactory.ts
@@ -3,25 +3,30 @@
* @license MIT
*/
-import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_ATTR_INDEX, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CODE_INDEX, NULL_CELL_CODE, WHITESPACE_CELL_CHAR } from '../../Buffer';
+import { NULL_CELL_CODE, WHITESPACE_CELL_CHAR } from '../../Buffer';
import { FLAGS } from '../Types';
-import { IBufferLine } from '../../Types';
+import { IBufferLine, ITerminalOptions } from '../../Types';
import { DEFAULT_COLOR, INVERTED_DEFAULT_COLOR } from '../atlas/Types';
+import { CellData } from '../../BufferLine';
export const BOLD_CLASS = 'xterm-bold';
export const ITALIC_CLASS = 'xterm-italic';
export const CURSOR_CLASS = 'xterm-cursor';
+export const CURSOR_BLINK_CLASS = 'xterm-cursor-blink';
export const CURSOR_STYLE_BLOCK_CLASS = 'xterm-cursor-block';
export const CURSOR_STYLE_BAR_CLASS = 'xterm-cursor-bar';
export const CURSOR_STYLE_UNDERLINE_CLASS = 'xterm-cursor-underline';
export class DomRendererRowFactory {
+ private _workCell: CellData = new CellData();
+
constructor(
+ private _terminalOptions: ITerminalOptions,
private _document: Document
) {
}
- public createRow(lineData: IBufferLine, isCursorRow: boolean, cursorStyle: string | undefined, cursorX: number, cellWidth: number, cols: number): DocumentFragment {
+ public createRow(lineData: IBufferLine, isCursorRow: boolean, cursorStyle: string | undefined, cursorX: number, cursorBlink: boolean, cellWidth: number, cols: number): DocumentFragment {
const fragment = this._document.createDocumentFragment();
// Find the line length first, this prevents the need to output a bunch of
@@ -31,19 +36,16 @@ export class DomRendererRowFactory {
// the viewport).
let lineLength = 0;
for (let x = Math.min(lineData.length, cols) - 1; x >= 0; x--) {
- const charData = lineData.get(x);
- const code = charData[CHAR_DATA_CODE_INDEX];
- if (code !== NULL_CELL_CODE || (isCursorRow && x === cursorX)) {
+ if (lineData.loadCell(x, this._workCell).getCode() !== NULL_CELL_CODE || (isCursorRow && x === cursorX)) {
lineLength = x + 1;
break;
}
}
for (let x = 0; x < lineLength; x++) {
- const charData = lineData.get(x);
- const char = charData[CHAR_DATA_CHAR_INDEX] || WHITESPACE_CELL_CHAR;
- const attr = charData[CHAR_DATA_ATTR_INDEX];
- const width = charData[CHAR_DATA_WIDTH_INDEX];
+ lineData.loadCell(x, this._workCell);
+ const attr = this._workCell.fg;
+ const width = this._workCell.getWidth();
// The character to the left is a wide character, drawing is owned by the char at x-1
if (width === 0) {
@@ -62,6 +64,10 @@ export class DomRendererRowFactory {
if (isCursorRow && x === cursorX) {
charElement.classList.add(CURSOR_CLASS);
+ if (cursorBlink) {
+ charElement.classList.add(CURSOR_BLINK_CLASS);
+ }
+
switch (cursorStyle) {
case 'bar':
charElement.classList.add(CURSOR_STYLE_BAR_CLASS);
@@ -88,10 +94,10 @@ export class DomRendererRowFactory {
}
}
- if (flags & FLAGS.BOLD) {
+ if (flags & FLAGS.BOLD && this._terminalOptions.enableBold) {
// Convert the FG color to the bold variant. This should not happen when
// the fg is the inverse default color as there is no bold variant.
- if (fg < 8) {
+ if (fg < 8 && this._terminalOptions.drawBoldTextInBrightColors) {
fg += 8;
}
charElement.classList.add(BOLD_CLASS);
@@ -101,7 +107,7 @@ export class DomRendererRowFactory {
charElement.classList.add(ITALIC_CLASS);
}
- charElement.textContent = char;
+ charElement.textContent = this._workCell.getChars() || WHITESPACE_CELL_CHAR;
if (fg !== DEFAULT_COLOR) {
charElement.classList.add(`xterm-fg-${fg}`);
}
diff --git a/src/tsconfig-base.json b/src/tsconfig-base.json
new file mode 100644
index 00000000..5c6afcc5
--- /dev/null
+++ b/src/tsconfig-base.json
@@ -0,0 +1,15 @@
+{
+ "compilerOptions": {
+ "target": "es5",
+ "lib": [ "es5" ],
+ "rootDir": ".",
+
+ "sourceMap": true,
+ "removeComments": true,
+ "pretty": true,
+
+ "incremental": true,
+
+ "skipLibCheck": true
+ }
+}
diff --git a/src/tsconfig-library-base.json b/src/tsconfig-library-base.json
new file mode 100644
index 00000000..c82e0873
--- /dev/null
+++ b/src/tsconfig-library-base.json
@@ -0,0 +1,11 @@
+{
+ "extends": "./tsconfig-base.json",
+ "compilerOptions": {
+ "types": [
+ "../../node_modules/@types/mocha",
+ "../../"
+ ],
+ "composite": true,
+ "strict": true
+ }
+}
diff --git a/src/tsconfig.all.json b/src/tsconfig.all.json
new file mode 100644
index 00000000..bee5df32
--- /dev/null
+++ b/src/tsconfig.all.json
@@ -0,0 +1,16 @@
+{
+ "files": [],
+ "include": [],
+ "references": [
+ { "path": "." },
+ { "path": "./addons/attach" },
+ { "path": "./addons/fit" },
+ { "path": "./addons/fullscreen" },
+ { "path": "./addons/search" },
+ { "path": "./addons/terminado" },
+ { "path": "./addons/webLinks" },
+ { "path": "./addons/winptyCompat" },
+ { "path": "./addons/zmodem" }
+ ]
+}
+
\ No newline at end of file
diff --git a/tsconfig.json b/src/tsconfig.json
similarity index 53%
rename from tsconfig.json
rename to src/tsconfig.json
index 2d1d6e35..0aa3abb8 100644
--- a/tsconfig.json
+++ b/src/tsconfig.json
@@ -1,7 +1,7 @@
{
+ "extends": "./tsconfig-base",
"compilerOptions": {
"module": "commonjs",
- "target": "es5",
"lib": [
"dom",
"es5",
@@ -9,19 +9,22 @@
"scripthost",
"es2015.promise"
],
- "rootDir": "src",
- "outDir": "lib",
- "sourceMap": true,
- "removeComments": true,
- "preserveWatchOutput": true,
+ "rootDir": ".",
+ "outDir": "../lib",
+
"noUnusedLocals": true,
"noImplicitAny": true
},
"include": [
- "src/**/*",
- "typings/xterm.d.ts"
+ "./**/*",
+ "../typings/xterm.d.ts"
],
"exclude": [
- "src/addons/**/*"
+ "./addons/**/*"
+ ],
+ "references": [
+ { "path": "./common" },
+ { "path": "./core" }
]
}
+
\ No newline at end of file
diff --git a/src/ui/MouseZoneManager.ts b/src/ui/MouseZoneManager.ts
index 79022723..372dccc5 100644
--- a/src/ui/MouseZoneManager.ts
+++ b/src/ui/MouseZoneManager.ts
@@ -29,6 +29,7 @@ export class MouseZoneManager extends Disposable implements IMouseZoneManager {
private _tooltipTimeout: number = null;
private _currentZone: IMouseZone = null;
private _lastHoverCoords: [number, number] = [null, null];
+ private _initialSelectionLength: number;
constructor(
private _terminal: ITerminal
@@ -157,6 +158,10 @@ export class MouseZoneManager extends Disposable implements IMouseZoneManager {
}
private _onMouseDown(e: MouseEvent): void {
+ // Store current terminal selection length, to check if we're performing
+ // a selection operation
+ this._initialSelectionLength = this._terminal.getSelection().length;
+
// Ignore the event if there are no zones active
if (!this._areZonesActive) {
return;
@@ -186,9 +191,12 @@ export class MouseZoneManager extends Disposable implements IMouseZoneManager {
}
private _onClick(e: MouseEvent): void {
- // Find the active zone and click it if found
+ // Find the active zone and click it if found and no selection was
+ // being performed
const zone = this._findZoneEventAt(e);
- if (zone) {
+ const currentSelectionLength = this._terminal.getSelection().length;
+
+ if (zone && currentSelectionLength === this._initialSelectionLength) {
zone.clickCallback(e);
e.preventDefault();
e.stopImmediatePropagation();
diff --git a/src/ui/TestUtils.test.ts b/src/ui/TestUtils.test.ts
index e6e4aaa3..9d525fbf 100644
--- a/src/ui/TestUtils.test.ts
+++ b/src/ui/TestUtils.test.ts
@@ -4,7 +4,7 @@
*/
import { IColorSet, IRenderer, IRenderDimensions, IColorManager } from '../renderer/Types';
-import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminal, IBuffer, IBufferSet, IBrowser, ICharMeasure, ISelectionManager, ITerminalOptions, ILinkifier, IMouseHelper, ILinkMatcherOptions, CharacterJoinerHandler, IBufferLine, IBufferStringIterator } from '../Types';
+import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminal, IBuffer, IBufferSet, IBrowser, ICharMeasure, ISelectionManager, ITerminalOptions, ILinkifier, IMouseHelper, ILinkMatcherOptions, CharacterJoinerHandler, IBufferLine, IBufferStringIterator, ICellData } from '../Types';
import { ICircularList, XtermListener } from '../common/Types';
import { Buffer } from '../Buffer';
import * as Browser from '../core/Platform';
@@ -334,6 +334,12 @@ export class MockBuffer implements IBuffer {
iterator(trimRight: boolean, startIndex?: number, endIndex?: number): IBufferStringIterator {
return Buffer.prototype.iterator.apply(this, arguments);
}
+ getNullCell(fg: number = 0, bg: number = 0): ICellData {
+ throw new Error('Method not implemented.');
+ }
+ getWhitespaceCell(fg: number = 0, bg: number = 0): ICellData {
+ throw new Error('Method not implemented.');
+ }
}
export class MockRenderer implements IRenderer {
diff --git a/yarn.lock b/yarn.lock
index 5555321d..440aa4f8 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1275,11 +1275,6 @@ combined-stream@1.0.6, combined-stream@~1.0.5:
dependencies:
delayed-stream "~1.0.0"
-commander@2.6.0:
- version "2.6.0"
- resolved "https://registry.yarnpkg.com/commander/-/commander-2.6.0.tgz#9df7e52fb2a0cb0fb89058ee80c3104225f37e1d"
- integrity sha1-nfflL7Kgyw+4kFjugMMQQiXzfh0=
-
commander@2.9.0:
version "2.9.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4"
@@ -1338,21 +1333,6 @@ concat-with-sourcemaps@^1.0.0:
dependencies:
source-map "^0.6.1"
-concurrently@^3.5.1:
- version "3.6.0"
- resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-3.6.0.tgz#c25e34b156a9d5bd4f256a0d85f6192438ae481f"
- integrity sha512-6XiIYtYzmGEccNZFkih5JOH92jLA4ulZArAYy5j1uDSdrPLB3KzdE8GW7t2fHPcg9ry2+5LP9IEYzXzxw9lFdA==
- dependencies:
- chalk "^2.4.1"
- commander "2.6.0"
- date-fns "^1.23.0"
- lodash "^4.5.1"
- read-pkg "^3.0.0"
- rx "2.3.24"
- spawn-command "^0.0.2-1"
- supports-color "^3.2.3"
- tree-kill "^1.1.0"
-
configstore@^1.0.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/configstore/-/configstore-1.4.0.tgz#c35781d0501d268c25c54b8b17f6240e8a4fb021"
@@ -1595,11 +1575,6 @@ data-urls@^1.0.0:
whatwg-mimetype "^2.0.0"
whatwg-url "^6.4.0"
-date-fns@^1.23.0:
- version "1.29.0"
- resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.29.0.tgz#12e609cdcb935127311d04d33334e2960a2a54e6"
- integrity sha512-lbTXWZ6M20cWH8N9S6afb0SBm6tMk+uUg6z3MqHPKE9atmsY3kJkTm8vKe93izJ2B2+q5MV990sM2CHgtAZaOw==
-
date-now@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b"
@@ -1933,7 +1908,7 @@ errno@^0.1.3, errno@~0.1.7:
dependencies:
prr "~1.0.1"
-error-ex@^1.2.0, error-ex@^1.3.1:
+error-ex@^1.2.0:
version "1.3.2"
resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf"
integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==
@@ -3625,7 +3600,7 @@ jsesc@^1.3.0:
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b"
integrity sha1-RsP+yMGJKxKwgz25vHYiF226s0s=
-json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2:
+json-parse-better-errors@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9"
integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==
@@ -3815,16 +3790,6 @@ load-json-file@^1.0.0:
pinkie-promise "^2.0.0"
strip-bom "^2.0.0"
-load-json-file@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b"
- integrity sha1-L19Fq5HjMhYjT9U62rZo607AmTs=
- dependencies:
- graceful-fs "^4.1.2"
- parse-json "^4.0.0"
- pify "^3.0.0"
- strip-bom "^3.0.0"
-
loader-runner@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.3.0.tgz#f482aea82d543e07921700d5a46ef26fdac6b8a2"
@@ -4037,7 +4002,7 @@ lodash.templatesettings@^3.0.0:
lodash._reinterpolate "^3.0.0"
lodash.escape "^3.0.0"
-lodash@^4.13.1, lodash@^4.17.10, lodash@^4.17.4, lodash@^4.5.1:
+lodash@^4.13.1, lodash@^4.17.10, lodash@^4.17.4:
version "4.17.10"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7"
integrity sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==
@@ -4984,14 +4949,6 @@ parse-json@^2.2.0:
dependencies:
error-ex "^1.2.0"
-parse-json@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0"
- integrity sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=
- dependencies:
- error-ex "^1.3.1"
- json-parse-better-errors "^1.0.1"
-
parse-passwd@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6"
@@ -5092,13 +5049,6 @@ path-type@^1.0.0:
pify "^2.0.0"
pinkie-promise "^2.0.0"
-path-type@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f"
- integrity sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==
- dependencies:
- pify "^3.0.0"
-
pause-stream@0.0.11:
version "0.0.11"
resolved "https://registry.yarnpkg.com/pause-stream/-/pause-stream-0.0.11.tgz#fe5a34b0cbce12b5aa6a2b403ee2e73b602f1445"
@@ -5384,15 +5334,6 @@ read-pkg@^1.0.0:
normalize-package-data "^2.3.2"
path-type "^1.0.0"
-read-pkg@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389"
- integrity sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=
- dependencies:
- load-json-file "^4.0.0"
- normalize-package-data "^2.3.2"
- path-type "^3.0.0"
-
"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6:
version "2.3.6"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf"
@@ -5688,11 +5629,6 @@ run-queue@^1.0.0, run-queue@^1.0.3:
dependencies:
aproba "^1.1.1"
-rx@2.3.24:
- version "2.3.24"
- resolved "https://registry.yarnpkg.com/rx/-/rx-2.3.24.tgz#14f950a4217d7e35daa71bbcbe58eff68ea4b2b7"
- integrity sha1-FPlQpCF9fjXapxu8vljv9o6ksrc=
-
rxjs@^6.1.0:
version "6.3.1"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.3.1.tgz#878a1a8c64b8a5da11dcf74b5033fe944cdafb84"
@@ -6025,11 +5961,6 @@ sparkles@^1.0.0:
resolved "https://registry.yarnpkg.com/sparkles/-/sparkles-1.0.1.tgz#008db65edce6c50eec0c5e228e1945061dd0437c"
integrity sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw==
-spawn-command@^0.0.2-1:
- version "0.0.2-1"
- resolved "https://registry.yarnpkg.com/spawn-command/-/spawn-command-0.0.2-1.tgz#62f5e9466981c1b796dc5929937e11c9c6921bd0"
- integrity sha1-YvXpRmmBwbeW3Fkpk34RycaSG9A=
-
spawn-wrap@^1.4.2:
version "1.4.2"
resolved "https://registry.yarnpkg.com/spawn-wrap/-/spawn-wrap-1.4.2.tgz#cff58e73a8224617b6561abdc32586ea0c82248c"
@@ -6271,11 +6202,6 @@ strip-bom@^1.0.0:
first-chunk-stream "^1.0.0"
is-utf8 "^0.2.0"
-strip-bom@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
- integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=
-
strip-eof@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"
@@ -6305,7 +6231,7 @@ supports-color@^2.0.0:
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=
-supports-color@^3.1.2, supports-color@^3.2.3:
+supports-color@^3.1.2:
version "3.2.3"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6"
integrity sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=
@@ -6521,11 +6447,6 @@ tr46@^1.0.1:
dependencies:
punycode "^2.1.0"
-tree-kill@^1.1.0:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.0.tgz#5846786237b4239014f05db156b643212d4c6f36"
- integrity sha512-DlX6dR0lOIRDFxI0mjL9IYg6OTncLm/Zt+JiBhE5OlFcAR8yc9S7FFXU9so0oda47frdM/JFsk7UjNt9vscKcg==
-
trim-right@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003"
@@ -6633,10 +6554,10 @@ typedarray@^0.0.6, typedarray@~0.0.5:
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
-typescript@3.1:
- version "3.1.6"
- resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.1.6.tgz#b6543a83cfc8c2befb3f4c8fba6896f5b0c9be68"
- integrity sha512-tDMYfVtvpb96msS1lDX9MEdHrW4yOuZ4Kdc4Him9oU796XldPYF/t2+uKoX0BBa0hXXwDlqYQbXY5Rzjzc5hBA==
+typescript@3.4:
+ version "3.4.1"
+ resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.4.1.tgz#b6691be11a881ffa9a05765a205cb7383f3b63c6"
+ integrity sha512-3NSMb2VzDQm8oBTLH6Nj55VVtUEpe/rgkIzMir0qVoLyjDZlnMBva0U6vDiV3IH+sl/Yu6oP5QwsAQtHPmDd2Q==
uglify-es@^3.3.4:
version "3.3.9"