Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d19769bd0 | ||
|
|
a5e43ad8cc | ||
|
|
ac69ef5e01 | ||
|
|
b28be877bd | ||
|
|
217ed05606 | ||
|
|
02aa803889 | ||
|
|
9489bd8c5c | ||
|
|
324dc8d4dc | ||
|
|
70ecec3c92 | ||
|
|
e0997b51e1 |
@@ -8,19 +8,12 @@ RUN apt-get update \
|
||||
# Verify git and process tools are installed
|
||||
RUN apt-get install -y git procps
|
||||
|
||||
# Install yarn, puppeteer deps
|
||||
# Install yarn
|
||||
RUN apt-get install -y curl apt-transport-https lsb-release \
|
||||
&& curl -sS https://dl.yarnpkg.com/$(lsb_release -is | tr '[:upper:]' '[:lower:]')/pubkey.gpg | apt-key add - 2>/dev/null \
|
||||
&& echo "deb https://dl.yarnpkg.com/$(lsb_release -is | tr '[:upper:]' '[:lower:]')/ stable main" | tee /etc/apt/sources.list.d/yarn.list \
|
||||
&& apt-get update \
|
||||
&& apt-get -y install --no-install-recommends \
|
||||
yarn fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst ttf-freefont \
|
||||
# https://github.com/Googlechrome/puppeteer/issues/290#issuecomment-322921352
|
||||
gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 \
|
||||
libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 \
|
||||
libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 \
|
||||
libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 \
|
||||
ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget
|
||||
&& apt-get -y install --no-install-recommends yarn
|
||||
|
||||
# Clean up
|
||||
RUN apt-get autoremove -y \
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Question
|
||||
url: https://stackoverflow.com/questions/tagged/xtermjs
|
||||
about: Please ask and answer questions here.
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
name: Question
|
||||
about: The issue tracker is not for questions. Please ask questions on https://stackoverflow.com/questions/tagged/xtermjs
|
||||
---
|
||||
|
||||
🛑 The issue tracker is not for questions 🛑
|
||||
|
||||
If you have a question, please ask it on https://stackoverflow.com/questions/tagged/xtermjs.
|
||||
@@ -4,7 +4,6 @@ node_modules/
|
||||
lib/
|
||||
out/
|
||||
out-test/
|
||||
.nyc_output/
|
||||
Makefile.gyp
|
||||
*.Makefile
|
||||
*.target.gyp.mk
|
||||
|
||||
Vendored
+1
-2
@@ -1,4 +1,3 @@
|
||||
{
|
||||
"typescript.preferences.quoteStyle": "single",
|
||||
"typescript.preferences.importModuleSpecifier": "non-relative"
|
||||
"typescript.preferences.quoteStyle": "single"
|
||||
}
|
||||
|
||||
@@ -52,7 +52,3 @@ By contributing code to xterm.js you:
|
||||
holder has explicitly granted the right to use it like this,
|
||||
through a compatible open source license or through a direct
|
||||
agreement with you.)
|
||||
|
||||
### Test coverage
|
||||
|
||||
One area that always needs attention is improving out unit test coverage, you can view the code coverage report on [Azure Pipelines](https://dev.azure.com/xtermjs/xterm.js/_build/latest?definitionId=3) by clicking the Code Coverage tab.
|
||||
|
||||
@@ -10,7 +10,7 @@ Xterm.js is a front-end component written in TypeScript that lets applications b
|
||||
- **Performant**: Xterm.js is *really* fast, it even includes a GPU-accelerated renderer.
|
||||
- **Rich unicode support**: Supports CJK, emojis and IMEs.
|
||||
- **Self-contained**: Requires zero dependencies to work.
|
||||
- **Accessible**: Screen reader and minimum contrast ratio support can be turned on
|
||||
- **Accessible**: Screen reader support can be turned on using the `screenReaderMode` option.
|
||||
- **And much more**: Links, theming, addons, well documented API, etc.
|
||||
|
||||
## What xterm.js is not
|
||||
@@ -110,7 +110,7 @@ Xterm.js is used in several world-class applications to provide great terminal e
|
||||
- [**WebSSH2**](https://github.com/billchurch/WebSSH2): A web based SSH2 client using xterm.js, socket.io, and ssh2.
|
||||
- [**Spyder Terminal**](https://github.com/spyder-ide/spyder-terminal): A full fledged system terminal embedded on Spyder IDE.
|
||||
- [**Cloud Commander**](https://cloudcmd.io "Cloud Commander"): Orthodox web file manager with console and editor.
|
||||
- [**Next Tech**](https://next.tech "Next Tech"): Online platform for interactive coding and web development courses. Live container-backed terminal uses xterm.js.
|
||||
- [**Codevolve**](https://www.codevolve.com "Codevolve"): Online platform for interactive coding and web development courses. Live container-backed terminal uses xterm.js.
|
||||
- [**RStudio**](https://www.rstudio.com/products/RStudio "RStudio"): RStudio is an integrated development environment (IDE) for R.
|
||||
- [**Terminal for Atom**](https://github.com/jsmecham/atom-terminal-tab): A simple terminal for the Atom text editor.
|
||||
- [**Eclipse Orion**](https://orionhub.org): A modern, open source software development environment that runs in the cloud. Code, deploy and run in the cloud.
|
||||
@@ -154,8 +154,6 @@ Xterm.js is used in several world-class applications to provide great terminal e
|
||||
- [**PHP App Server**](https://github.com/cubiclesoft/php-app-server/): Create lightweight, installable almost-native applications for desktop OSes. ExecTerminal (nicely wraps the xterm.js Terminal), TerminalManager, and RunProcessSDK are self-contained, reusable ES5+ compliant Javascript components.
|
||||
- [**NgTerminal**](https://github.com/qwefgh90/ng-terminal): NgTerminal is a web terminal that leverages xterm.js on Angular 7+. You can easily add it into your application by adding `<ng-terminal></ng-terminal>` into your component.
|
||||
- [**tty-share**](https://tty-share.com): Extremely simple terminal sharing over the Internet.
|
||||
- [**Ten Hands**](https://github.com/saisandeepvaddi/ten-hands): One place to run your command-line tasks.
|
||||
- [**WebAssembly.sh**](https://webassembly.sh): A WebAssembly WASI browser terminal
|
||||
|
||||
[And much more...](https://github.com/xtermjs/xterm.js/network/dependents)
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ npm install --save xterm-addon-attach
|
||||
|
||||
```ts
|
||||
import { Terminal } from 'xterm';
|
||||
import { AttachAddon } from 'xterm-addon-attach';
|
||||
import { FitAddon } from 'xterm-addon-attach';
|
||||
|
||||
const terminal = new Terminal();
|
||||
const attachAddon = new AttachAddon(webSocket);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "xterm-addon-attach",
|
||||
"version": "0.4.0",
|
||||
"version": "0.2.0",
|
||||
"author": {
|
||||
"name": "The xterm.js authors",
|
||||
"url": "https://xtermjs.org/"
|
||||
@@ -16,6 +16,6 @@
|
||||
"prepublishOnly": "npm run package"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"xterm": "^4.0.0"
|
||||
"xterm": "^3.14.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,8 @@ describe('AttachAddon', () => {
|
||||
this.timeout(20000);
|
||||
browser = await puppeteer.launch({
|
||||
headless: process.argv.indexOf('--headless') !== -1,
|
||||
args: [`--window-size=${width},${height}`, `--no-sandbox`]
|
||||
slowMo: 80,
|
||||
args: [`--window-size=${width},${height}`]
|
||||
});
|
||||
page = (await browser.pages())[0];
|
||||
await page.setViewport({ width, height });
|
||||
@@ -42,7 +43,7 @@ describe('AttachAddon', () => {
|
||||
const server = new WebSocket.Server({ port });
|
||||
server.on('connection', socket => socket.send('foo'));
|
||||
await page.evaluate(`window.term.loadAddon(new window.AttachAddon(new WebSocket('ws://localhost:${port}')))`);
|
||||
await pollFor(page, `window.term.buffer.getLine(0).translateToString(true)`, 'foo');
|
||||
assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'foo');
|
||||
server.close();
|
||||
});
|
||||
|
||||
@@ -53,8 +54,8 @@ describe('AttachAddon', () => {
|
||||
const server = new WebSocket.Server({ port });
|
||||
const data = new Uint8Array([102, 111, 111]);
|
||||
server.on('connection', socket => socket.send(data));
|
||||
await page.evaluate(`window.term.loadAddon(new window.AttachAddon(new WebSocket('ws://localhost:${port}')))`);
|
||||
await pollFor(page, `window.term.buffer.getLine(0).translateToString(true)`, 'foo');
|
||||
await page.evaluate(`window.term.loadAddon(new window.AttachAddon(new WebSocket('ws://localhost:${port}'), { inputUtf8: true }))`);
|
||||
assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'foo');
|
||||
server.close();
|
||||
});
|
||||
});
|
||||
@@ -68,12 +69,3 @@ async function openTerminal(options: ITerminalOptions = {}): Promise<void> {
|
||||
await page.waitForSelector('.xterm-text-layer');
|
||||
}
|
||||
}
|
||||
|
||||
async function pollFor(page: puppeteer.Page, fn: string, val: any): Promise<void> {
|
||||
const result = await page.evaluate(fn);
|
||||
if (result !== val) {
|
||||
return new Promise<void>(r => {
|
||||
setTimeout(() => r(pollFor(page, fn, val)), 10);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,11 +9,13 @@ import { Terminal, IDisposable, ITerminalAddon } from 'xterm';
|
||||
|
||||
interface IAttachOptions {
|
||||
bidirectional?: boolean;
|
||||
inputUtf8?: boolean;
|
||||
}
|
||||
|
||||
export class AttachAddon implements ITerminalAddon {
|
||||
private _socket: WebSocket;
|
||||
private _bidirectional: boolean;
|
||||
private _utf8: boolean;
|
||||
private _disposables: IDisposable[] = [];
|
||||
|
||||
constructor(socket: WebSocket, options?: IAttachOptions) {
|
||||
@@ -21,19 +23,20 @@ export class AttachAddon implements ITerminalAddon {
|
||||
// always set binary type to arraybuffer, we do not handle blobs
|
||||
this._socket.binaryType = 'arraybuffer';
|
||||
this._bidirectional = (options && options.bidirectional === false) ? false : true;
|
||||
this._utf8 = !!(options && options.inputUtf8);
|
||||
}
|
||||
|
||||
public activate(terminal: Terminal): void {
|
||||
this._disposables.push(
|
||||
addSocketListener(this._socket, 'message', ev => {
|
||||
const data: ArrayBuffer | string = ev.data;
|
||||
terminal.write(typeof data === 'string' ? data : new Uint8Array(data));
|
||||
})
|
||||
);
|
||||
if (this._utf8) {
|
||||
this._disposables.push(addSocketListener(this._socket, 'message',
|
||||
(ev: MessageEvent | Event | CloseEvent) => terminal.writeUtf8(new Uint8Array((ev as any).data as ArrayBuffer))));
|
||||
} else {
|
||||
this._disposables.push(addSocketListener(this._socket, 'message',
|
||||
(ev: MessageEvent | Event | CloseEvent) => terminal.write((ev as any).data as string)));
|
||||
}
|
||||
|
||||
if (this._bidirectional) {
|
||||
this._disposables.push(terminal.onData(data => this._sendData(data)));
|
||||
this._disposables.push(terminal.onBinary(data => this._sendBinary(data)));
|
||||
}
|
||||
|
||||
this._disposables.push(addSocketListener(this._socket, 'close', () => this.dispose()));
|
||||
@@ -52,20 +55,9 @@ export class AttachAddon implements ITerminalAddon {
|
||||
}
|
||||
this._socket.send(data);
|
||||
}
|
||||
|
||||
private _sendBinary(data: string): void {
|
||||
if (this._socket.readyState !== 1) {
|
||||
return;
|
||||
}
|
||||
const buffer = new Uint8Array(data.length);
|
||||
for (let i = 0; i < data.length; ++i) {
|
||||
buffer[i] = data.charCodeAt(i) & 255;
|
||||
}
|
||||
this._socket.send(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
function addSocketListener<K extends keyof WebSocketEventMap>(socket: WebSocket, type: K, handler: (this: WebSocket, ev: WebSocketEventMap[K]) => any): IDisposable {
|
||||
function addSocketListener(socket: WebSocket, type: string, handler: (this: WebSocket, ev: MessageEvent | Event | CloseEvent) => any): IDisposable {
|
||||
socket.addEventListener(type, handler);
|
||||
return {
|
||||
dispose: () => {
|
||||
|
||||
@@ -11,6 +11,14 @@ declare module 'xterm-addon-attach' {
|
||||
* Whether input should be written to the backend. Defaults to `true`.
|
||||
*/
|
||||
bidirectional?: boolean;
|
||||
|
||||
/**
|
||||
* Whether to use UTF8 binary transport for incoming messages. Defaults to `false`.
|
||||
* Note: This must be in line with the server side of the websocket.
|
||||
* Always send string messages from the backend if this options is false,
|
||||
* otherwise always binary UTF8 data.
|
||||
*/
|
||||
inputUtf8?: boolean;
|
||||
}
|
||||
|
||||
export class AttachAddon implements ITerminalAddon {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "xterm-addon-fit",
|
||||
"version": "0.3.0",
|
||||
"version": "0.2.0",
|
||||
"author": {
|
||||
"name": "The xterm.js authors",
|
||||
"url": "https://xtermjs.org/"
|
||||
@@ -16,6 +16,6 @@
|
||||
"prepublishOnly": "npm run package"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"xterm": "^4.0.0"
|
||||
"xterm": "^3.14.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,118 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import * as puppeteer from 'puppeteer';
|
||||
import { assert } from 'chai';
|
||||
import { ITerminalOptions } from 'xterm';
|
||||
|
||||
const APP = 'http://127.0.0.1:3000/test';
|
||||
|
||||
let browser: puppeteer.Browser;
|
||||
let page: puppeteer.Page;
|
||||
const width = 1024;
|
||||
const height = 768;
|
||||
|
||||
describe('FitAddon', () => {
|
||||
before(async function(): Promise<any> {
|
||||
this.timeout(20000);
|
||||
browser = await puppeteer.launch({
|
||||
headless: process.argv.indexOf('--headless') !== -1,
|
||||
args: [`--window-size=${width},${height}`, `--no-sandbox`]
|
||||
});
|
||||
page = (await browser.pages())[0];
|
||||
await page.setViewport({ width, height });
|
||||
await page.goto(APP);
|
||||
await openTerminal();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await browser.close();
|
||||
});
|
||||
|
||||
it('no terminal', async function(): Promise<any> {
|
||||
await page.evaluate(`window.fit = new FitAddon();`);
|
||||
assert.equal(await page.evaluate(`window.fit.proposeDimensions()`), undefined);
|
||||
});
|
||||
|
||||
describe('proposeDimensions', () => {
|
||||
afterEach(async () => {
|
||||
return unloadFit();
|
||||
});
|
||||
|
||||
it('default', async function(): Promise<any> {
|
||||
await loadFit();
|
||||
assert.deepEqual(await page.evaluate(`window.fit.proposeDimensions()`), {
|
||||
cols: 87,
|
||||
rows: 26
|
||||
});
|
||||
});
|
||||
|
||||
it('width', async function(): Promise<any> {
|
||||
await loadFit(1008);
|
||||
assert.deepEqual(await page.evaluate(`window.fit.proposeDimensions()`), {
|
||||
cols: 110,
|
||||
rows: 26
|
||||
});
|
||||
});
|
||||
|
||||
it('small', async function(): Promise<any> {
|
||||
await loadFit(1, 1);
|
||||
assert.deepEqual(await page.evaluate(`window.fit.proposeDimensions()`), {
|
||||
cols: 2,
|
||||
rows: 1
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('fit', () => {
|
||||
afterEach(async () => {
|
||||
return unloadFit();
|
||||
});
|
||||
|
||||
it('default', async function(): Promise<any> {
|
||||
await loadFit();
|
||||
await page.evaluate(`window.fit.fit()`);
|
||||
assert.equal(await page.evaluate(`window.term.cols`), 87);
|
||||
assert.equal(await page.evaluate(`window.term.rows`), 26);
|
||||
});
|
||||
|
||||
it('width', async function(): Promise<any> {
|
||||
await loadFit(1008);
|
||||
await page.evaluate(`window.fit.fit()`);
|
||||
assert.equal(await page.evaluate(`window.term.cols`), 110);
|
||||
assert.equal(await page.evaluate(`window.term.rows`), 26);
|
||||
});
|
||||
|
||||
it('small', async function(): Promise<any> {
|
||||
await loadFit(1, 1);
|
||||
await page.evaluate(`window.fit.fit()`);
|
||||
assert.equal(await page.evaluate(`window.term.cols`), 2);
|
||||
assert.equal(await page.evaluate(`window.term.rows`), 1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
async function loadFit(width: number = 800, height: number = 450): Promise<void> {
|
||||
await page.evaluate(`
|
||||
window.fit = new FitAddon();
|
||||
window.term.loadAddon(window.fit);
|
||||
document.querySelector('#terminal-container').style.width='${width}px';
|
||||
document.querySelector('#terminal-container').style.height='${height}px';
|
||||
`);
|
||||
}
|
||||
|
||||
async function unloadFit(): Promise<void> {
|
||||
await page.evaluate(`window.fit.dispose();`);
|
||||
}
|
||||
|
||||
async function openTerminal(options: ITerminalOptions = {}): Promise<void> {
|
||||
await page.evaluate(`window.term = new Terminal(${JSON.stringify(options)})`);
|
||||
await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`);
|
||||
if (options.rendererType === 'dom') {
|
||||
await page.waitForSelector('.xterm-rows');
|
||||
} else {
|
||||
await page.waitForSelector('.xterm-text-layer');
|
||||
}
|
||||
}
|
||||
@@ -17,9 +17,6 @@ interface ITerminalDimensions {
|
||||
cols: number;
|
||||
}
|
||||
|
||||
const MINIMUM_COLS = 2;
|
||||
const MINIMUM_ROWS = 1;
|
||||
|
||||
export class FitAddon implements ITerminalAddon {
|
||||
private _terminal: Terminal | undefined;
|
||||
|
||||
@@ -52,7 +49,7 @@ export class FitAddon implements ITerminalAddon {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!this._terminal.element || !this._terminal.element.parentElement) {
|
||||
if (!this._terminal.element.parentElement) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -74,8 +71,8 @@ export class FitAddon implements ITerminalAddon {
|
||||
const availableHeight = parentElementHeight - elementPaddingVer;
|
||||
const availableWidth = parentElementWidth - elementPaddingHor - core.viewport.scrollBarWidth;
|
||||
const geometry = {
|
||||
cols: Math.max(MINIMUM_COLS, Math.floor(availableWidth / core._renderService.dimensions.actualCellWidth)),
|
||||
rows: Math.max(MINIMUM_ROWS, Math.floor(availableHeight / core._renderService.dimensions.actualCellHeight))
|
||||
cols: Math.floor(availableWidth / core._renderService.dimensions.actualCellWidth),
|
||||
rows: Math.floor(availableHeight / core._renderService.dimensions.actualCellHeight)
|
||||
};
|
||||
return geometry;
|
||||
}
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
/usr/bin/cmake -E cmake_progress_report /home/yan/ssd/searchtrunk/tmp/release/CMakeFiles
|
||||
make[3]: Entering directory `/home/yan/ssd/searchtrunk/tmp/release'
|
||||
make[3]: Nothing to be done for `libsrc/wrap_curl/CMakeFiles/wrap_curl.dir/build'.
|
||||
make[3]: Leaving directory `/home/yan/ssd/searchtrunk/tmp/release'
|
||||
[ 0%] /usr/bin/cmake -E cmake_progress_report /home/yan/ssd/searchtrunk/tmp/release/CMakeFiles
|
||||
[ 0%] [ 0%] /usr/bin/cmake -E cmake_progress_report /home/yan/ssd/searchtrunk/tmp/release/CMakeFiles
|
||||
Built target config_utils
|
||||
[ 0%] make[3]: Entering directory `/home/yan/ssd/searchtrunk/tmp/release'
|
||||
/usr/bin/cmake -E cmake_progress_report /home/yan/ssd/searchtrunk/tmp/release/CMakeFiles
|
||||
Built target omega
|
||||
make[3]: Entering directory `/home/yan/ssd/searchtrunk/tmp/release'
|
||||
cd /home/yan/ssd/searchtrunk/tmp/release && /usr/bin/cmake -E cmake_depends "Unix Makefiles" /home/yan/ssd/searchtrunk /home/yan/ssd/searchtrunk/projects/protogen/cpp /home/yan/ssd/searchtrunk/tmp/release /home/yan/ssd/searchtrunk/tmp/release/projects/protogen/cpp /home/yan/ssd/searchtrunk/tmp/release/projects/protogen/cpp/CMakeFiles/protogen.dir/DependInfo.cmake --color=
|
||||
/usr/bin/cmake -E cmake_progress_report /home/yan/ssd/searchtrunk/tmp/release/CMakeFiles
|
||||
Built target morpheus
|
||||
make[3]: Leaving directory `/home/yan/ssd/searchtrunk/tmp/release'
|
||||
make -f libsrc/base/CMakeFiles/base.dir/build.make libsrc/base/CMakeFiles/base.dir/build
|
||||
make[3]: Entering directory `/home/yan/ssd/searchtrunk/tmp/release'
|
||||
make[3]: Nothing to be done for `libsrc/coroutine/CMakeFiles/coroutine.dir/build'.
|
||||
Built target wrap_curl
|
||||
make[3]: Leaving directory `/home/yan/ssd/searchtrunk/tmp/release'
|
||||
/usr/bin/cmake -E cmake_progress_report /home/yan/ssd/searchtrunk/tmp/release/CMakeFiles
|
||||
/usr/bin/cmake -E cmake_progress_report /home/yan/ssd/searchtrunk/tmp/release/CMakeFiles
|
||||
[ 0%] [ 0%] [ 0%] [ 0%] Building CXX object libsrc/image_crop/CMakeFiles/image_crop.dir/src/crop_context.cpp.o
|
||||
cd /home/yan/ssd/searchtrunk/tmp/release/libsrc/image_crop && /opt/rh/devtoolset-7/root/usr/bin/c++ -DGOGO_USE_LIBEVENT2=1 -DGOGO_USE_OPENSSL_SNI=1 -DHAVE_BOOST -DHAVE_GLIBC -DHAVE_MALLOC_TRIM -DHAVE_PREAD -DHAVE_READAHEAD -D_FILE_OFFSET_BITS=64 -D_STAT_LEMM -pipe -Wall -Wextra -Werror=multichar -Wno-deprecated -Wno-unused-parameter -pthread -fPIC -Woverloaded-virtual -Wnon-virtual-dtor -Werror -std=gnu++11 -Wno-unknown-pragmas -DBOOST_FILESYSTEM_VERSION=3 -DBOOST_NO_CXX11_HDR_CODECVT -DMAGICKCORE_QUANTUM_DEPTH=8 -DMAGICKCORE_HDRI_ENABLE=0 -Wno-unused-local-typedefs -D_GLIBCXX_USE_CXX11_ABI=0 -std=gnu++14 -DCXXHASH128_EXIST -static-libstdc++ -Wno-implicit-fallthrough -Wno-stringop-overflow -O3 -DNDEBUG -DBOOST_UBLAS_NDEBUG -g0 -march=core2 -mssse3 -msse4.1 -I/home/yan/ssd/searchtrunk/tmp/release/__inc -I/home/yan/ssd/searchtrunk/libsrc/image_crop/src -I/home/yan/ssd/searchtrunk/include -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/boost_1_69_0/include -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/icu-49.1.2/include -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/protobuf-3.4.0/include -I/home/yan/ssd/searchtrunk/tmp/release -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/opencv-3.0.0m2/include -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/dlib-19.7p1/include -Wno-array-bounds -o CMakeFiles/image_crop.dir/src/crop_context.cpp.o -c /home/yan/ssd/searchtrunk/libsrc/image_crop/src/crop_context.cpp
|
||||
Building CXX object libsrc/image_crop/CMakeFiles/image_crop.dir/src/detector.cpp.o
|
||||
Building CXX object libsrc/image_crop/CMakeFiles/image_crop.dir/src/image_crop.cpp.o
|
||||
cd /home/yan/ssd/searchtrunk/tmp/release/libsrc/image_crop && /opt/rh/devtoolset-7/root/usr/bin/c++ -DGOGO_USE_LIBEVENT2=1 -DGOGO_USE_OPENSSL_SNI=1 -DHAVE_BOOST -DHAVE_GLIBC -DHAVE_MALLOC_TRIM -DHAVE_PREAD -DHAVE_READAHEAD -D_FILE_OFFSET_BITS=64 -D_STAT_LEMM -pipe -Wall -Wextra -Werror=multichar -Wno-deprecated -Wno-unused-parameter -pthread -fPIC -Woverloaded-virtual -Wnon-virtual-dtor -Werror -std=gnu++11 -Wno-unknown-pragmas -DBOOST_FILESYSTEM_VERSION=3 -DBOOST_NO_CXX11_HDR_CODECVT -DMAGICKCORE_QUANTUM_DEPTH=8 -DMAGICKCORE_HDRI_ENABLE=0 -Wno-unused-local-typedefs -D_GLIBCXX_USE_CXX11_ABI=0 -std=gnu++14 -DCXXHASH128_EXIST -static-libstdc++ -Wno-implicit-fallthrough -Wno-stringop-overflow -O3 -DNDEBUG -DBOOST_UBLAS_NDEBUG -g0 -march=core2 -mssse3 -msse4.1 -I/home/yan/ssd/searchtrunk/tmp/release/__inc -I/home/yan/ssd/searchtrunk/libsrc/image_crop/src -I/home/yan/ssd/searchtrunk/include -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/boost_1_69_0/include -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/icu-49.1.2/include -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/protobuf-3.4.0/include -I/home/yan/ssd/searchtrunk/tmp/release -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/opencv-3.0.0m2/include -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/dlib-19.7p1/include -Wno-array-bounds -o CMakeFiles/image_crop.dir/src/detector.cpp.o -c /home/yan/ssd/searchtrunk/libsrc/image_crop/src/detector.cpp
|
||||
Built target coroutine
|
||||
cd /home/yan/ssd/searchtrunk/tmp/release/libsrc/image_crop && /opt/rh/devtoolset-7/root/usr/bin/c++ -DGOGO_USE_LIBEVENT2=1 -DGOGO_USE_OPENSSL_SNI=1 -DHAVE_BOOST -DHAVE_GLIBC -DHAVE_MALLOC_TRIM -DHAVE_PREAD -DHAVE_READAHEAD -D_FILE_OFFSET_BITS=64 -D_STAT_LEMM -pipe -Wall -Wextra -Werror=multichar -Wno-deprecated -Wno-unused-parameter -pthread -fPIC -Woverloaded-virtual -Wnon-virtual-dtor -Werror -std=gnu++11 -Wno-unknown-pragmas -DBOOST_FILESYSTEM_VERSION=3 -DBOOST_NO_CXX11_HDR_CODECVT -DMAGICKCORE_QUANTUM_DEPTH=8 -DMAGICKCORE_HDRI_ENABLE=0 -Wno-unused-local-typedefs -D_GLIBCXX_USE_CXX11_ABI=0 -std=gnu++14 -DCXXHASH128_EXIST -static-libstdc++ -Wno-implicit-fallthrough -Wno-stringop-overflow -O3 -DNDEBUG -DBOOST_UBLAS_NDEBUG -g0 -march=core2 -mssse3 -msse4.1 -I/home/yan/ssd/searchtrunk/tmp/release/__inc -I/home/yan/ssd/searchtrunk/libsrc/image_crop/src -I/home/yan/ssd/searchtrunk/include -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/boost_1_69_0/include -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/icu-49.1.2/include -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/protobuf-3.4.0/include -I/home/yan/ssd/searchtrunk/tmp/release -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/opencv-3.0.0m2/include -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/dlib-19.7p1/include -Wno-array-bounds -o CMakeFiles/image_crop.dir/src/image_crop.cpp.o -c /home/yan/ssd/searchtrunk/libsrc/image_crop/src/image_crop.cpp
|
||||
make[3]: Entering directory `/home/yan/ssd/searchtrunk/tmp/release'
|
||||
make[3]: Nothing to be done for `libsrc/base/CMakeFiles/base.dir/build'.
|
||||
make[3]: Leaving directory `/home/yan/ssd/searchtrunk/tmp/release'
|
||||
/usr/bin/cmake -E cmake_progress_report /home/yan/ssd/searchtrunk/tmp/release/CMakeFiles
|
||||
make[3]: Leaving directory `/home/yan/ssd/searchtrunk/tmp/release'
|
||||
make -f projects/protogen/cpp/CMakeFiles/protogen.dir/build.make projects/protogen/cpp/CMakeFiles/protogen.dir/build
|
||||
[ 0%] Built target base
|
||||
In file included from /home/yan/ssd/searchtrunk/include/image_crop/detector.hpp:3:0,
|
||||
from /home/yan/ssd/searchtrunk/libsrc/image_crop/src/detector.cpp:2:
|
||||
/home/yan/ssd/searchtrunk/include/image_crop/crop_context.hpp:6:10: fatal error: opencv2/dnn.hpp: No such file or directory
|
||||
#include <opencv2/dnn.hpp>
|
||||
^~~~~~~~~~~~~~~~~
|
||||
compilation terminated.
|
||||
make[3]: *** [libsrc/image_crop/CMakeFiles/image_crop.dir/src/detector.cpp.o] Error 1
|
||||
make[3]: *** Waiting for unfinished jobs....
|
||||
In file included from /home/yan/ssd/searchtrunk/include/image_crop/image_crop.hpp:3:0,
|
||||
from /home/yan/ssd/searchtrunk/libsrc/image_crop/src/image_crop.cpp:1:
|
||||
/home/yan/ssd/searchtrunk/include/image_crop/crop_context.hpp:6:10: fatal error: opencv2/dnn.hpp: No such file or directory
|
||||
#include <opencv2/dnn.hpp>
|
||||
^~~~~~~~~~~~~~~~~
|
||||
compilation terminated.
|
||||
make[3]: *** [libsrc/image_crop/CMakeFiles/image_crop.dir/src/image_crop.cpp.o] Error 1
|
||||
In file included from /home/yan/ssd/searchtrunk/libsrc/image_crop/src/crop_context.cpp:1:0:
|
||||
/home/yan/ssd/searchtrunk/include/image_crop/crop_context.hpp:6:10: fatal error: opencv2/dnn.hpp: No such file or directory
|
||||
#include <opencv2/dnn.hpp>
|
||||
^~~~~~~~~~~~~~~~~
|
||||
compilation terminated.
|
||||
make[3]: *** [libsrc/image_crop/CMakeFiles/image_crop.dir/src/crop_context.cpp.o] Error 1
|
||||
make[3]: Leaving directory `/home/yan/ssd/searchtrunk/tmp/release'
|
||||
make[2]: *** [libsrc/image_crop/CMakeFiles/image_crop.dir/all] Error 2
|
||||
make[2]: *** Waiting for unfinished jobs....
|
||||
make[3]: Entering directory `/home/yan/ssd/searchtrunk/tmp/release'
|
||||
make[3]: Nothing to be done for `projects/protogen/cpp/CMakeFiles/protogen.dir/build'.
|
||||
make[3]: Leaving directory `/home/yan/ssd/searchtrunk/tmp/release'
|
||||
/usr/bin/cmake -E cmake_progress_report /home/yan/ssd/searchtrunk/tmp/release/CMakeFiles 63 64 65 66
|
||||
[ 25%] Built target protogen
|
||||
make[2]: Leaving directory `/home/yan/ssd/searchtrunk/tmp/release'
|
||||
make[1]: *** [all] Error 2
|
||||
make[1]: Leaving directory `/home/yan/ssd/searchtrunk/tmp/release/projects/projectX'
|
||||
make: *** [install] Error 2
|
||||
yan@yPC:~/ssd/searchtrunk/projects/projectX$
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "xterm-addon-search",
|
||||
"version": "0.4.0",
|
||||
"version": "0.2.0",
|
||||
"author": {
|
||||
"name": "The xterm.js authors",
|
||||
"url": "https://xtermjs.org/"
|
||||
@@ -16,6 +16,6 @@
|
||||
"prepublishOnly": "npm run package"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"xterm": "^4.0.0"
|
||||
"xterm": "^3.14.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,6 @@
|
||||
import * as puppeteer from 'puppeteer';
|
||||
import { assert } from 'chai';
|
||||
import { ITerminalOptions } from 'xterm';
|
||||
import { readFile } from 'fs';
|
||||
import { resolve } from 'path';
|
||||
|
||||
const APP = 'http://127.0.0.1:3000/test';
|
||||
|
||||
@@ -17,12 +15,13 @@ const width = 800;
|
||||
const height = 600;
|
||||
|
||||
describe('Search Tests', function (): void {
|
||||
this.timeout(20000);
|
||||
this.timeout(200000);
|
||||
|
||||
before(async function (): Promise<any> {
|
||||
browser = await puppeteer.launch({
|
||||
headless: process.argv.indexOf('--headless') !== -1,
|
||||
args: [`--window-size=${width},${height}`, `--no-sandbox`]
|
||||
slowMo: 80,
|
||||
args: [`--window-size=${width},${height}`]
|
||||
});
|
||||
page = (await browser.pages())[0];
|
||||
await page.setViewport({ width, height });
|
||||
@@ -92,101 +91,13 @@ describe('Search Tests', function (): void {
|
||||
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
|
||||
assert.deepEqual(line.substring(selectionPosition.startColumn, selectionPosition.endColumn), 'package.jsonc');
|
||||
});
|
||||
it('Simple Regex', async () => {
|
||||
it ('Simple Regex', async () => {
|
||||
await writeSync('abc123defABCD');
|
||||
await page.evaluate(`window.search.findNext('[a-z]+', {regex: true})`);
|
||||
assert.deepEqual(await page.evaluate(`window.term.getSelection()`), 'abc');
|
||||
await page.evaluate(`window.search.findNext('[A-Z]+', {regex: true, caseSensitive: true})`);
|
||||
assert.deepEqual(await page.evaluate(`window.term.getSelection()`), 'ABCD');
|
||||
});
|
||||
|
||||
it('Search for single result twice should not unselect it', async () => {
|
||||
await writeSync('abc def');
|
||||
assert.deepEqual(await page.evaluate(`window.search.findNext('abc')`), true);
|
||||
assert.deepEqual(await page.evaluate(`window.term.getSelection()`), 'abc');
|
||||
assert.deepEqual(await page.evaluate(`window.search.findNext('abc')`), true);
|
||||
assert.deepEqual(await page.evaluate(`window.term.getSelection()`), 'abc');
|
||||
});
|
||||
|
||||
describe('Regression tests', () => {
|
||||
describe('#2444 wrapped line content not being found', () => {
|
||||
let fixture: string;
|
||||
before(async () => {
|
||||
const rawFixture = await new Promise<Buffer>(r => readFile(resolve(__dirname, '../fixtures/issue-2444'), (err, data) => r(data)));
|
||||
fixture = rawFixture.toString()
|
||||
.replace(/\n/g, '\\n\\r')
|
||||
.replace(/'/g, '\\\'');
|
||||
});
|
||||
it('should find all occurrences using findNext', async () => {
|
||||
await writeSync(fixture);
|
||||
assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true);
|
||||
let selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
|
||||
assert.deepEqual(selectionPosition, { startColumn: 24, startRow: 53, endColumn: 30, endRow: 53 });
|
||||
assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true);
|
||||
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
|
||||
assert.deepEqual(selectionPosition, { startColumn: 24, startRow: 76, endColumn: 30, endRow: 76 });
|
||||
assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true);
|
||||
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
|
||||
assert.deepEqual(selectionPosition, { startColumn: 24, startRow: 96, endColumn: 30, endRow: 96 });
|
||||
assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true);
|
||||
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
|
||||
assert.deepEqual(selectionPosition, { startColumn: 1, startRow: 114, endColumn: 7, endRow: 114 });
|
||||
assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true);
|
||||
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
|
||||
assert.deepEqual(selectionPosition, { startColumn: 11, startRow: 115, endColumn: 17, endRow: 115 });
|
||||
assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true);
|
||||
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
|
||||
assert.deepEqual(selectionPosition, { startColumn: 1, startRow: 126, endColumn: 7, endRow: 126 });
|
||||
assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true);
|
||||
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
|
||||
assert.deepEqual(selectionPosition, { startColumn: 11, startRow: 127, endColumn: 17, endRow: 127 });
|
||||
assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true);
|
||||
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
|
||||
assert.deepEqual(selectionPosition, { startColumn: 1, startRow: 135, endColumn: 7, endRow: 135 });
|
||||
assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true);
|
||||
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
|
||||
assert.deepEqual(selectionPosition, { startColumn: 11, startRow: 136, endColumn: 17, endRow: 136 });
|
||||
// Wrap around to first result
|
||||
assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true);
|
||||
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
|
||||
assert.deepEqual(selectionPosition, { startColumn: 24, startRow: 53, endColumn: 30, endRow: 53 });
|
||||
});
|
||||
it('should find all occurrences using findPrevious', async () => {
|
||||
await writeSync(fixture);
|
||||
assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true);
|
||||
let selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
|
||||
assert.deepEqual(selectionPosition, { startColumn: 11, startRow: 136, endColumn: 17, endRow: 136 });
|
||||
assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true);
|
||||
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
|
||||
assert.deepEqual(selectionPosition, { startColumn: 1, startRow: 135, endColumn: 7, endRow: 135 });
|
||||
assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true);
|
||||
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
|
||||
assert.deepEqual(selectionPosition, { startColumn: 11, startRow: 127, endColumn: 17, endRow: 127 });
|
||||
assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true);
|
||||
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
|
||||
assert.deepEqual(selectionPosition, { startColumn: 1, startRow: 126, endColumn: 7, endRow: 126 });
|
||||
assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true);
|
||||
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
|
||||
assert.deepEqual(selectionPosition, { startColumn: 11, startRow: 115, endColumn: 17, endRow: 115 });
|
||||
assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true);
|
||||
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
|
||||
assert.deepEqual(selectionPosition, { startColumn: 1, startRow: 114, endColumn: 7, endRow: 114 });
|
||||
assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true);
|
||||
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
|
||||
assert.deepEqual(selectionPosition, { startColumn: 24, startRow: 96, endColumn: 30, endRow: 96 });
|
||||
assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true);
|
||||
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
|
||||
assert.deepEqual(selectionPosition, { startColumn: 24, startRow: 76, endColumn: 30, endRow: 76 });
|
||||
assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true);
|
||||
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
|
||||
assert.deepEqual(selectionPosition, { startColumn: 24, startRow: 53, endColumn: 30, endRow: 53 });
|
||||
// Wrap around to first result
|
||||
assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true);
|
||||
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
|
||||
assert.deepEqual(selectionPosition, { startColumn: 11, startRow: 136, endColumn: 17, endRow: 136 });
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
async function openTerminal(options: ITerminalOptions = {}): Promise<void> {
|
||||
@@ -200,7 +111,12 @@ async function openTerminal(options: ITerminalOptions = {}): Promise<void> {
|
||||
}
|
||||
|
||||
async function writeSync(data: string): Promise<void> {
|
||||
return page.evaluate(`new Promise(resolve => window.term.write('${data}', resolve))`);
|
||||
await page.evaluate(`window.term.write('${data}');`);
|
||||
while (true) {
|
||||
if (await page.evaluate(`window.term._core.writeBuffer.length === 0`)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function makeData(length: number): string {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { Terminal, IDisposable, ITerminalAddon, ISelectionPosition } from 'xterm';
|
||||
import { Terminal, IDisposable, ITerminalAddon } from 'xterm';
|
||||
|
||||
export interface ISearchOptions {
|
||||
regex?: boolean;
|
||||
@@ -12,11 +12,6 @@ export interface ISearchOptions {
|
||||
incremental?: boolean;
|
||||
}
|
||||
|
||||
export interface ISearchPosition {
|
||||
startCol: number;
|
||||
startRow: number;
|
||||
}
|
||||
|
||||
export interface ISearchResult {
|
||||
term: string;
|
||||
col: number;
|
||||
@@ -64,35 +59,29 @@ export class SearchAddon implements ITerminalAddon {
|
||||
|
||||
let startCol = 0;
|
||||
let startRow = 0;
|
||||
let currentSelection: ISelectionPosition | undefined;
|
||||
|
||||
if (this._terminal.hasSelection()) {
|
||||
const incremental = searchOptions ? searchOptions.incremental : false;
|
||||
// Start from the selection end if there is a selection
|
||||
// For incremental search, use existing row
|
||||
currentSelection = this._terminal.getSelectionPosition()!;
|
||||
const currentSelection = this._terminal.getSelectionPosition()!;
|
||||
startRow = incremental ? currentSelection.startRow : currentSelection.endRow;
|
||||
startCol = incremental ? currentSelection.startColumn : currentSelection.endColumn;
|
||||
}
|
||||
|
||||
this._initLinesCache();
|
||||
|
||||
const searchPosition: ISearchPosition = {
|
||||
startRow,
|
||||
startCol
|
||||
};
|
||||
|
||||
// Search startRow
|
||||
let result = this._findInLine(term, searchPosition, searchOptions);
|
||||
let result = this._findInLine(term, startRow, startCol, searchOptions);
|
||||
|
||||
// Search from startRow + 1 to end
|
||||
if (!result) {
|
||||
|
||||
for (let y = startRow + 1; y < this._terminal.buffer.baseY + this._terminal.rows; y++) {
|
||||
searchPosition.startRow = y;
|
||||
searchPosition.startCol = 0;
|
||||
|
||||
// If the current line is wrapped line, increase index of column to ignore the previous scan
|
||||
// Otherwise, reset beginning column index to zero with set new unwrapped line index
|
||||
result = this._findInLine(term, searchPosition, searchOptions);
|
||||
result = this._findInLine(term, y, 0, searchOptions);
|
||||
if (result) {
|
||||
break;
|
||||
}
|
||||
@@ -101,18 +90,13 @@ export class SearchAddon implements ITerminalAddon {
|
||||
// If we hit the bottom and didn't search from the very top wrap back up
|
||||
if (!result && startRow !== 0) {
|
||||
for (let y = 0; y < startRow; y++) {
|
||||
searchPosition.startRow = y;
|
||||
searchPosition.startCol = 0;
|
||||
result = this._findInLine(term, searchPosition, searchOptions);
|
||||
result = this._findInLine(term, y, 0, searchOptions);
|
||||
if (result) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If there is only one result, return true.
|
||||
if (!result && currentSelection) return true;
|
||||
|
||||
// Set selection and scroll if a result was found
|
||||
return this._selectResult(result);
|
||||
}
|
||||
@@ -137,37 +121,31 @@ export class SearchAddon implements ITerminalAddon {
|
||||
const isReverseSearch = true;
|
||||
let startRow = this._terminal.buffer.baseY + this._terminal.rows;
|
||||
let startCol = this._terminal.cols;
|
||||
let result: ISearchResult | undefined;
|
||||
let result: ISearchResult | undefined = undefined;
|
||||
const incremental = searchOptions ? searchOptions.incremental : false;
|
||||
let currentSelection: ISelectionPosition | undefined;
|
||||
if (this._terminal.hasSelection()) {
|
||||
currentSelection = this._terminal.getSelectionPosition()!;
|
||||
const currentSelection = this._terminal.getSelectionPosition()!;
|
||||
// Start from selection start if there is a selection
|
||||
startRow = currentSelection.startRow;
|
||||
startCol = currentSelection.startColumn;
|
||||
}
|
||||
|
||||
this._initLinesCache();
|
||||
const searchPosition: ISearchPosition = {
|
||||
startRow,
|
||||
startCol
|
||||
};
|
||||
|
||||
if (incremental) {
|
||||
result = this._findInLine(term, searchPosition, searchOptions, false);
|
||||
result = this._findInLine(term, startRow, startCol, searchOptions, false);
|
||||
if (!(result && result.row === startRow && result.col === startCol)) {
|
||||
result = this._findInLine(term, searchPosition, searchOptions, true);
|
||||
result = this._findInLine(term, startRow, startCol, searchOptions, true);
|
||||
}
|
||||
} else {
|
||||
result = this._findInLine(term, searchPosition, searchOptions, isReverseSearch);
|
||||
result = this._findInLine(term, startRow, startCol, searchOptions, isReverseSearch);
|
||||
}
|
||||
|
||||
// Search from startRow - 1 to top
|
||||
if (!result) {
|
||||
searchPosition.startCol = Math.max(searchPosition.startCol, this._terminal.cols);
|
||||
startCol = this._terminal.cols;
|
||||
for (let y = startRow - 1; y >= 0; y--) {
|
||||
searchPosition.startRow = y;
|
||||
result = this._findInLine(term, searchPosition, searchOptions, isReverseSearch);
|
||||
result = this._findInLine(term, y, startCol, searchOptions, isReverseSearch);
|
||||
if (result) {
|
||||
break;
|
||||
}
|
||||
@@ -176,17 +154,13 @@ export class SearchAddon implements ITerminalAddon {
|
||||
// If we hit the top and didn't search from the very bottom wrap back down
|
||||
if (!result && startRow !== (this._terminal.buffer.baseY + this._terminal.rows)) {
|
||||
for (let y = (this._terminal.buffer.baseY + this._terminal.rows); y > startRow; y--) {
|
||||
searchPosition.startRow = y;
|
||||
result = this._findInLine(term, searchPosition, searchOptions, isReverseSearch);
|
||||
result = this._findInLine(term, y, startCol, searchOptions, isReverseSearch);
|
||||
if (result) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If there is only one result, return true.
|
||||
if (!result && currentSelection) return true;
|
||||
|
||||
// Set selection and scroll if a result was found
|
||||
return this._selectResult(result);
|
||||
}
|
||||
@@ -239,28 +213,18 @@ export class SearchAddon implements ITerminalAddon {
|
||||
* started on an earlier line then it is skipped since it will be properly searched when the terminal line that the
|
||||
* text starts on is searched.
|
||||
* @param term The search term.
|
||||
* @param position The position to start the search.
|
||||
* @param row The line to start the search from.
|
||||
* @param col The column to start the search from.
|
||||
* @param searchOptions Search options.
|
||||
* @return The search result if it was found.
|
||||
*/
|
||||
protected _findInLine(term: string, searchPosition: ISearchPosition, searchOptions: ISearchOptions = {}, isReverseSearch: boolean = false): ISearchResult | undefined {
|
||||
protected _findInLine(term: string, row: number, col: number, searchOptions: ISearchOptions = {}, isReverseSearch: boolean = false): ISearchResult | undefined {
|
||||
const terminal = this._terminal!;
|
||||
let row = searchPosition.startRow;
|
||||
const col = searchPosition.startCol;
|
||||
|
||||
// Ignore wrapped lines, only consider on unwrapped line (first row of command string).
|
||||
const firstLine = terminal.buffer.getLine(row);
|
||||
if (firstLine && firstLine.isWrapped) {
|
||||
if (isReverseSearch) {
|
||||
searchPosition.startCol += terminal.cols;
|
||||
return;
|
||||
}
|
||||
|
||||
// This will iterate until we find the line start.
|
||||
// When we find it, we will search using the calculated start column.
|
||||
searchPosition.startRow--;
|
||||
searchPosition.startCol += terminal.cols;
|
||||
return this._findInLine(term, searchPosition, searchOptions);
|
||||
return;
|
||||
}
|
||||
let stringLine = this._linesCache ? this._linesCache[row] : void 0;
|
||||
if (stringLine === void 0) {
|
||||
@@ -380,7 +344,7 @@ export class SearchAddon implements ITerminalAddon {
|
||||
}
|
||||
terminal.select(result.col, result.row, result.term.length);
|
||||
// If it is not in the viewport then we scroll else it just gets selected
|
||||
if (result.row >= (terminal.buffer.viewportY + terminal.rows) || result.row < terminal.buffer.viewportY) {
|
||||
if (result.row > (terminal.buffer.viewportY + terminal.rows) || result.row < terminal.buffer.viewportY) {
|
||||
let scroll = result.row - terminal.buffer.viewportY;
|
||||
scroll = scroll - Math.floor(terminal.rows / 2);
|
||||
terminal.scrollLines(scroll);
|
||||
|
||||
@@ -17,7 +17,7 @@ declare module 'xterm-addon-search' {
|
||||
|
||||
/**
|
||||
* Whether to search for a whole word, the result is only valid if it's
|
||||
* surrounded in "non-word" characters such as `_`, `(`, `)` or space.
|
||||
* suppounded in "non-word" characters such as `_`, `(`, `)` or space.
|
||||
*/
|
||||
wholeWord?: boolean;
|
||||
|
||||
@@ -27,7 +27,7 @@ declare module 'xterm-addon-search' {
|
||||
caseSensitive?: boolean;
|
||||
|
||||
/**
|
||||
* Whether to do an incremental search, this will expand the selection if it
|
||||
* Whether to do an indcremental search, this will expand the selection if it
|
||||
* still matches the term the user typed. Note that this only affects
|
||||
* `findNext`, not `findPrevious`.
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "xterm-addon-web-links",
|
||||
"version": "0.2.1",
|
||||
"version": "0.2.0",
|
||||
"author": {
|
||||
"name": "The xterm.js authors",
|
||||
"url": "https://xtermjs.org/"
|
||||
@@ -16,6 +16,6 @@
|
||||
"prepublishOnly": "npm run package"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"xterm": "^4.0.0"
|
||||
"xterm": "^3.14.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,8 @@ describe('WebLinksAddon', () => {
|
||||
this.timeout(10000);
|
||||
browser = await puppeteer.launch({
|
||||
headless: process.argv.indexOf('--headless') !== -1,
|
||||
args: [`--window-size=${width},${height}`, `--no-sandbox`]
|
||||
slowMo: 80,
|
||||
args: [`--window-size=${width},${height}`]
|
||||
});
|
||||
page = (await browser.pages())[0];
|
||||
await page.setViewport({ width, height });
|
||||
@@ -53,21 +54,22 @@ describe('WebLinksAddon', () => {
|
||||
async function testHostName(hostname: string): Promise<void> {
|
||||
await openTerminal({ rendererType: 'dom' });
|
||||
await page.evaluate(`window.term.loadAddon(new window.WebLinksAddon())`);
|
||||
const data = ` http://${hostname} \\r\\n` +
|
||||
` http://${hostname}/a~b#c~d?e~f \\r\\n` +
|
||||
` http://${hostname}/colon:test \\r\\n` +
|
||||
` http://${hostname}/colon:test: \\r\\n` +
|
||||
`"http://${hostname}/"\\r\\n` +
|
||||
`\\'http://${hostname}/\\'\\r\\n` +
|
||||
`http://${hostname}/subpath/+/id`;
|
||||
await writeSync(page, data);
|
||||
await pollForLinkAtCell(3, 1, `http://${hostname}`);
|
||||
await pollForLinkAtCell(3, 2, `http://${hostname}/a~b#c~d?e~f`);
|
||||
await pollForLinkAtCell(3, 3, `http://${hostname}/colon:test`);
|
||||
await pollForLinkAtCell(3, 4, `http://${hostname}/colon:test`);
|
||||
await pollForLinkAtCell(2, 5, `http://${hostname}/`);
|
||||
await pollForLinkAtCell(2, 6, `http://${hostname}/`);
|
||||
await pollForLinkAtCell(1, 7, `http://${hostname}/subpath/+/id`);
|
||||
await page.evaluate(`
|
||||
window.term.writeln(' http://${hostname} ');
|
||||
window.term.writeln(' http://${hostname}/a~b#c~d?e~f ');
|
||||
window.term.writeln(' http://${hostname}/colon:test ');
|
||||
window.term.writeln(' http://${hostname}/colon:test: ');
|
||||
window.term.writeln('"http://${hostname}/"');
|
||||
window.term.writeln('\\'http://${hostname}/\\'');
|
||||
window.term.writeln('http://${hostname}/subpath/+/id');
|
||||
`);
|
||||
assert.equal(await getLinkAtCell(3, 1), `http://${hostname}`);
|
||||
assert.equal(await getLinkAtCell(3, 2), `http://${hostname}/a~b#c~d?e~f`);
|
||||
assert.equal(await getLinkAtCell(3, 3), `http://${hostname}/colon:test`);
|
||||
assert.equal(await getLinkAtCell(3, 4), `http://${hostname}/colon:test`);
|
||||
assert.equal(await getLinkAtCell(2, 5), `http://${hostname}/`);
|
||||
assert.equal(await getLinkAtCell(2, 6), `http://${hostname}/`);
|
||||
assert.equal(await getLinkAtCell(1, 7), `http://${hostname}/subpath/+/id`);
|
||||
}
|
||||
|
||||
async function openTerminal(options: ITerminalOptions = {}): Promise<void> {
|
||||
@@ -80,30 +82,8 @@ async function openTerminal(options: ITerminalOptions = {}): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
async function pollForLinkAtCell(col: number, row: number, value: string): Promise<void> {
|
||||
async function getLinkAtCell(col: number, row: number): Promise<string> {
|
||||
const rowSelector = `.xterm-rows > :nth-child(${row})`;
|
||||
// Ensure the hover element exists before trying to hover it
|
||||
await pollFor(page, `!!document.querySelector('${rowSelector} > :nth-child(${col})')`, true);
|
||||
await pollFor(page, `document.querySelectorAll('${rowSelector} > span[style]').length >= ${value.length}`, true, async () => page.hover(`${rowSelector} > :nth-child(${col})`));
|
||||
assert.equal(await page.evaluate(`Array.prototype.reduce.call(document.querySelectorAll('${rowSelector} > span[style]'), (a, b) => a + b.textContent, '');`), value);
|
||||
}
|
||||
|
||||
async function pollFor(page: puppeteer.Page, fn: string, val: any, preFn?: () => Promise<void>): Promise<void> {
|
||||
if (preFn) {
|
||||
await preFn();
|
||||
}
|
||||
const result = await page.evaluate(fn);
|
||||
if (result !== val) {
|
||||
return new Promise<void>(r => {
|
||||
setTimeout(() => r(pollFor(page, fn, val, preFn)), 10);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function writeSync(page: puppeteer.Page, data: string): Promise<void> {
|
||||
await page.evaluate(`
|
||||
window.ready = false;
|
||||
window.term.write('${data}', () => window.ready = true);
|
||||
`);
|
||||
await pollFor(page, 'window.ready', true);
|
||||
await page.hover(`${rowSelector} > :nth-child(${col})`);
|
||||
return await page.evaluate(`Array.prototype.reduce.call(document.querySelectorAll('${rowSelector} > span[style]'), (a, b) => a + b.textContent, '');`);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "xterm-addon-webgl",
|
||||
"version": "0.4.0",
|
||||
"version": "0.2.0",
|
||||
"author": {
|
||||
"name": "The xterm.js authors",
|
||||
"url": "https://xtermjs.org/"
|
||||
@@ -16,6 +16,6 @@
|
||||
"prepublishOnly": "npm run package"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"xterm": "^4.0.0"
|
||||
"xterm": "^3.14.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { CellData } from 'common/buffer/CellData';
|
||||
import { FLAGS } from './Constants';
|
||||
import { IBufferLine } from 'common/Types';
|
||||
|
||||
export function getCompatAttr(bufferLine: IBufferLine, index: number): number {
|
||||
// TODO: Need to move WebGL over to the new system and remove this block
|
||||
const cell = new CellData();
|
||||
bufferLine.loadCell(index, cell);
|
||||
const oldBg = cell.getBgColor() === -1 ? 256 : cell.getBgColor();
|
||||
const oldFg = cell.getFgColor() === -1 ? 256 : cell.getFgColor();
|
||||
const oldAttr =
|
||||
(cell.isBold() ? FLAGS.BOLD : 0) |
|
||||
(cell.isUnderline() ? FLAGS.UNDERLINE : 0) |
|
||||
(cell.isBlink() ? FLAGS.BLINK : 0) |
|
||||
(cell.isInverse() ? FLAGS.INVERSE : 0) |
|
||||
(cell.isDim() ? FLAGS.DIM : 0) |
|
||||
(cell.isItalic() ? FLAGS.ITALIC : 0);
|
||||
const attrCompat =
|
||||
oldBg |
|
||||
(oldFg << 9) |
|
||||
(oldAttr << 18);
|
||||
return attrCompat;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* @license MIT
|
||||
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
|
||||
*/
|
||||
|
||||
import { IColor } from 'browser/Types';
|
||||
|
||||
export function getLuminance(color: IColor): number {
|
||||
// Coefficients taken from: https://www.w3.org/TR/AERT/#color-contrast
|
||||
const r = color.rgba >> 24 & 0xff;
|
||||
const g = color.rgba >> 16 & 0xff;
|
||||
const b = color.rgba >> 8 & 0xff;
|
||||
return (0.299 * r + 0.587 * g + 0.114 * b) / 255;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
// TODO: Should be removed after chardata workaround is fixed
|
||||
export const enum FLAGS {
|
||||
BOLD = 1,
|
||||
UNDERLINE = 2,
|
||||
BLINK = 4,
|
||||
INVERSE = 8,
|
||||
INVISIBLE = 16,
|
||||
DIM = 32,
|
||||
ITALIC = 64
|
||||
}
|
||||
@@ -6,10 +6,12 @@
|
||||
import { createProgram, PROJECTION_MATRIX, throwIfFalsy } from './WebglUtils';
|
||||
import { WebglCharAtlas } from './atlas/WebglCharAtlas';
|
||||
import { IWebGL2RenderingContext, IWebGLVertexArrayObject, IRenderModel, IRasterizedGlyph } from './Types';
|
||||
import { COMBINED_CHAR_BIT_MASK, RENDER_MODEL_INDICIES_PER_CELL, RENDER_MODEL_FG_OFFSET } from './RenderModel';
|
||||
import { INDICIES_PER_CELL } from './WebglRenderer';
|
||||
import { COMBINED_CHAR_BIT_MASK } from './RenderModel';
|
||||
import { fill } from 'common/TypedArrayUtils';
|
||||
import { slice } from './TypedArray';
|
||||
import { NULL_CELL_CODE, WHITESPACE_CELL_CODE, Attributes } from 'common/buffer/Constants';
|
||||
import { NULL_CELL_CODE, WHITESPACE_CELL_CODE } from 'common/buffer/Constants';
|
||||
import { getLuminance } from './ColorUtils';
|
||||
import { Terminal, IBufferLine } from 'xterm';
|
||||
import { IColorSet } from 'browser/Types';
|
||||
import { IRenderDimensions } from 'browser/renderer/Types';
|
||||
@@ -101,7 +103,11 @@ export class GlyphRenderer {
|
||||
private _dimensions: IRenderDimensions
|
||||
) {
|
||||
const gl = this._gl;
|
||||
|
||||
const program = throwIfFalsy(createProgram(gl, vertexShaderSource, fragmentShaderSource));
|
||||
if (program === undefined) {
|
||||
throw new Error('Could not create WebGL program');
|
||||
}
|
||||
this._program = program;
|
||||
|
||||
// Uniform locations
|
||||
@@ -166,11 +172,11 @@ export class GlyphRenderer {
|
||||
return this._atlas ? this._atlas.beginFrame() : true;
|
||||
}
|
||||
|
||||
public updateCell(x: number, y: number, code: number, bg: number, fg: number, chars: string): void {
|
||||
this._updateCell(this._vertices.attributes, x, y, code, bg, fg, chars);
|
||||
public updateCell(x: number, y: number, code: number, attr: number, bg: number, fg: number, chars: string): void {
|
||||
this._updateCell(this._vertices.attributes, x, y, code, attr, bg, fg, chars);
|
||||
}
|
||||
|
||||
private _updateCell(array: Float32Array, x: number, y: number, code: number | undefined, bg: number, fg: number, chars?: string): void {
|
||||
private _updateCell(array: Float32Array, x: number, y: number, code: number | undefined, attr: number, bg: number, fg: number, chars?: string): void {
|
||||
const terminal = this._terminal;
|
||||
|
||||
const i = (y * terminal.cols + x) * INDICES_PER_CELL;
|
||||
@@ -183,12 +189,12 @@ export class GlyphRenderer {
|
||||
|
||||
let rasterizedGlyph: IRasterizedGlyph;
|
||||
if (!this._atlas) {
|
||||
return;
|
||||
throw new Error('atlas must be set before updating cell');
|
||||
}
|
||||
if (chars && chars.length > 1) {
|
||||
rasterizedGlyph = this._atlas.getRasterizedGlyphCombinedChar(chars, bg, fg);
|
||||
rasterizedGlyph = this._atlas.getRasterizedGlyphCombinedChar(chars, attr, bg, fg);
|
||||
} else {
|
||||
rasterizedGlyph = this._atlas.getRasterizedGlyph(code, bg, fg);
|
||||
rasterizedGlyph = this._atlas.getRasterizedGlyph(code, attr, bg, fg);
|
||||
}
|
||||
|
||||
// Fill empty if no glyph was found
|
||||
@@ -217,51 +223,58 @@ export class GlyphRenderer {
|
||||
|
||||
this._vertices.selectionAttributes = slice(this._vertices.attributes, 0);
|
||||
|
||||
const bg = (this._colors.selectionOpaque.rgba >>> 8) | Attributes.CM_RGB;
|
||||
// TODO: Make fg and bg configurable, currently since the buffer doesn't
|
||||
// support truecolor the char atlas cannot store it.
|
||||
const lumi = getLuminance(this._colors.background);
|
||||
const fg = lumi > 0.5 ? 7 : 0;
|
||||
const bg = lumi > 0.5 ? 0 : 7;
|
||||
|
||||
if (columnSelectMode) {
|
||||
const startCol = model.selection.startCol;
|
||||
const width = model.selection.endCol - startCol;
|
||||
const height = model.selection.viewportCappedEndRow - model.selection.viewportCappedStartRow + 1;
|
||||
for (let y = model.selection.viewportCappedStartRow; y < model.selection.viewportCappedStartRow + height; y++) {
|
||||
this._updateSelectionRange(startCol, startCol + width, y, model, bg);
|
||||
this._updateSelectionRange(startCol, startCol + width, y, model, bg, fg);
|
||||
}
|
||||
} else {
|
||||
// Draw first row
|
||||
const startCol = model.selection.viewportStartRow === model.selection.viewportCappedStartRow ? model.selection.startCol : 0;
|
||||
const startRowEndCol = model.selection.viewportCappedStartRow === model.selection.viewportCappedEndRow ? model.selection.endCol : terminal.cols;
|
||||
this._updateSelectionRange(startCol, startRowEndCol, model.selection.viewportCappedStartRow, model, bg);
|
||||
this._updateSelectionRange(startCol, startRowEndCol, model.selection.viewportCappedStartRow, model, bg, fg);
|
||||
|
||||
// Draw middle rows
|
||||
const middleRowsCount = Math.max(model.selection.viewportCappedEndRow - model.selection.viewportCappedStartRow - 1, 0);
|
||||
for (let y = model.selection.viewportCappedStartRow + 1; y <= model.selection.viewportCappedStartRow + middleRowsCount; y++) {
|
||||
this._updateSelectionRange(0, startRowEndCol, y, model, bg);
|
||||
this._updateSelectionRange(0, startRowEndCol, y, model, bg, fg);
|
||||
}
|
||||
|
||||
// Draw final row
|
||||
if (model.selection.viewportCappedStartRow !== model.selection.viewportCappedEndRow) {
|
||||
// Only draw viewportEndRow if it's not the same as viewportStartRow
|
||||
const endCol = model.selection.viewportEndRow === model.selection.viewportCappedEndRow ? model.selection.endCol : terminal.cols;
|
||||
this._updateSelectionRange(0, endCol, model.selection.viewportCappedEndRow, model, bg);
|
||||
this._updateSelectionRange(0, endCol, model.selection.viewportCappedEndRow, model, bg, fg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _updateSelectionRange(startCol: number, endCol: number, y: number, model: IRenderModel, bg: number): void {
|
||||
private _updateSelectionRange(startCol: number, endCol: number, y: number, model: IRenderModel, bg: number, fg: number): void {
|
||||
const terminal = this._terminal;
|
||||
const row = y + terminal.buffer.viewportY;
|
||||
let line: IBufferLine | undefined;
|
||||
for (let x = startCol; x < endCol; x++) {
|
||||
const offset = (y * this._terminal.cols + x) * RENDER_MODEL_INDICIES_PER_CELL;
|
||||
const offset = (y * this._terminal.cols + x) * INDICIES_PER_CELL;
|
||||
// Because the cache uses attr as a lookup key it needs to contain the selection colors as well
|
||||
let attr = model.cells[offset + 1];
|
||||
attr = attr & ~0x3ffff | bg << 9 | fg;
|
||||
const code = model.cells[offset];
|
||||
if (code & COMBINED_CHAR_BIT_MASK) {
|
||||
if (!line) {
|
||||
line = terminal.buffer.getLine(row);
|
||||
}
|
||||
const chars = line!.getCell(x)!.char;
|
||||
this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], bg, model.cells[offset + RENDER_MODEL_FG_OFFSET], chars);
|
||||
this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], attr, bg, fg, chars);
|
||||
} else {
|
||||
this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], bg, model.cells[offset + RENDER_MODEL_FG_OFFSET]);
|
||||
this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], attr, bg, fg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,11 +6,12 @@
|
||||
import { createProgram, expandFloat32Array, PROJECTION_MATRIX, throwIfFalsy } from './WebglUtils';
|
||||
import { IRenderModel, IWebGLVertexArrayObject, IWebGL2RenderingContext, ISelectionRenderModel } from './Types';
|
||||
import { fill } from 'common/TypedArrayUtils';
|
||||
import { Attributes, FgFlags } from 'common/buffer/Constants';
|
||||
import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants';
|
||||
import { is256Color } from './atlas/CharAtlasUtils';
|
||||
import { DEFAULT_COLOR } from 'common/buffer/Constants';
|
||||
import { Terminal } from 'xterm';
|
||||
import { IColorSet, IColor } from 'browser/Types';
|
||||
import { IRenderDimensions } from 'browser/renderer/Types';
|
||||
import { RENDER_MODEL_BG_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_INDICIES_PER_CELL } from './RenderModel';
|
||||
|
||||
const enum VertexAttribLocations {
|
||||
POSITION = 0,
|
||||
@@ -22,13 +23,13 @@ const enum VertexAttribLocations {
|
||||
const vertexShaderSource = `#version 300 es
|
||||
layout (location = ${VertexAttribLocations.POSITION}) in vec2 a_position;
|
||||
layout (location = ${VertexAttribLocations.SIZE}) in vec2 a_size;
|
||||
layout (location = ${VertexAttribLocations.COLOR}) in vec4 a_color;
|
||||
layout (location = ${VertexAttribLocations.COLOR}) in vec3 a_color;
|
||||
layout (location = ${VertexAttribLocations.UNIT_QUAD}) in vec2 a_unitquad;
|
||||
|
||||
uniform mat4 u_projection;
|
||||
uniform vec2 u_resolution;
|
||||
|
||||
out vec4 v_color;
|
||||
out vec3 v_color;
|
||||
|
||||
void main() {
|
||||
vec2 zeroToOne = (a_position + (a_unitquad * a_size)) / u_resolution;
|
||||
@@ -39,12 +40,12 @@ void main() {
|
||||
const fragmentShaderSource = `#version 300 es
|
||||
precision lowp float;
|
||||
|
||||
in vec4 v_color;
|
||||
in vec3 v_color;
|
||||
|
||||
out vec4 outColor;
|
||||
|
||||
void main() {
|
||||
outColor = v_color;
|
||||
outColor = vec4(v_color, 1);
|
||||
}`;
|
||||
|
||||
interface IVertices {
|
||||
@@ -155,7 +156,7 @@ export class RectangleRenderer {
|
||||
|
||||
private _updateCachedColors(): void {
|
||||
this._bgFloat = this._colorToFloat32Array(this._colors.background);
|
||||
this._selectionFloat = this._colorToFloat32Array(this._colors.selectionOpaque);
|
||||
this._selectionFloat = this._colorToFloat32Array(this._colors.selection);
|
||||
}
|
||||
|
||||
private _updateViewportRectangle(): void {
|
||||
@@ -246,73 +247,47 @@ export class RectangleRenderer {
|
||||
|
||||
for (let y = 0; y < terminal.rows; y++) {
|
||||
let currentStartX = -1;
|
||||
let currentBg = 0;
|
||||
let currentFg = 0;
|
||||
let currentInverse = false;
|
||||
let currentBg = DEFAULT_COLOR;
|
||||
for (let x = 0; x < terminal.cols; x++) {
|
||||
const modelIndex = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL;
|
||||
const bg = model.cells[modelIndex + RENDER_MODEL_BG_OFFSET];
|
||||
const fg = model.cells[modelIndex + RENDER_MODEL_FG_OFFSET];
|
||||
const inverse = !!(fg & FgFlags.INVERSE);
|
||||
if (bg !== currentBg || (fg !== currentFg && (currentInverse || inverse))) {
|
||||
const modelIndex = ((y * terminal.cols) + x) * 4;
|
||||
const bg = model.cells[modelIndex + 2];
|
||||
if (bg !== currentBg) {
|
||||
// A rectangle needs to be drawn if going from non-default to another color
|
||||
if (currentBg !== 0 || (currentInverse && currentFg !== 0)) {
|
||||
if (currentBg !== DEFAULT_COLOR) {
|
||||
const offset = rectangleCount++ * INDICES_PER_RECTANGLE;
|
||||
this._updateRectangle(vertices, offset, currentFg, currentBg, currentStartX, x, y);
|
||||
this._updateRectangle(vertices, offset, currentBg, currentStartX, x, y);
|
||||
}
|
||||
currentStartX = x;
|
||||
currentBg = bg;
|
||||
currentFg = fg;
|
||||
currentInverse = inverse;
|
||||
}
|
||||
}
|
||||
// Finish rectangle if it's still going
|
||||
if (currentBg !== 0 || (currentInverse && currentFg !== 0)) {
|
||||
if (currentBg !== DEFAULT_COLOR) {
|
||||
const offset = rectangleCount++ * INDICES_PER_RECTANGLE;
|
||||
this._updateRectangle(vertices, offset, currentFg, currentBg, currentStartX, terminal.cols, y);
|
||||
this._updateRectangle(vertices, offset, currentBg, currentStartX, terminal.cols, y);
|
||||
}
|
||||
}
|
||||
vertices.count = rectangleCount;
|
||||
}
|
||||
|
||||
private _updateRectangle(vertices: IVertices, offset: number, fg: number, bg: number, startX: number, endX: number, y: number): void {
|
||||
let rgba: number | undefined;
|
||||
if (fg & FgFlags.INVERSE) {
|
||||
switch (fg & Attributes.CM_MASK) {
|
||||
case Attributes.CM_P16:
|
||||
case Attributes.CM_P256:
|
||||
rgba = this._colors.ansi[fg & Attributes.PCOLOR_MASK].rgba;
|
||||
break;
|
||||
case Attributes.CM_RGB:
|
||||
rgba = (fg & Attributes.RGB_MASK) << 8;
|
||||
break;
|
||||
case Attributes.CM_DEFAULT:
|
||||
default:
|
||||
rgba = this._colors.foreground.rgba;
|
||||
}
|
||||
private _updateRectangle(vertices: IVertices, offset: number, bg: number, startX: number, endX: number, y: number): void {
|
||||
let color: IColor | null = null;
|
||||
if (bg === INVERTED_DEFAULT_COLOR) {
|
||||
color = this._colors.foreground;
|
||||
} else if (is256Color(bg)) {
|
||||
color = this._colors.ansi[bg];
|
||||
} else {
|
||||
switch (bg & Attributes.CM_MASK) {
|
||||
case Attributes.CM_P16:
|
||||
case Attributes.CM_P256:
|
||||
rgba = this._colors.ansi[bg & Attributes.PCOLOR_MASK].rgba;
|
||||
break;
|
||||
case Attributes.CM_RGB:
|
||||
rgba = (bg & Attributes.RGB_MASK) << 8;
|
||||
break;
|
||||
case Attributes.CM_DEFAULT:
|
||||
default:
|
||||
rgba = this._colors.background.rgba;
|
||||
}
|
||||
// TODO: Add support for true color
|
||||
color = this._colors.foreground;
|
||||
}
|
||||
|
||||
if (vertices.attributes.length < offset + 4) {
|
||||
vertices.attributes = expandFloat32Array(vertices.attributes, this._terminal.rows * this._terminal.cols * INDICES_PER_RECTANGLE);
|
||||
}
|
||||
const x1 = startX * this._dimensions.scaledCellWidth;
|
||||
const y1 = y * this._dimensions.scaledCellHeight;
|
||||
const r = ((rgba >> 24) & 0xFF) / 255;
|
||||
const g = ((rgba >> 16) & 0xFF) / 255;
|
||||
const b = ((rgba >> 8 ) & 0xFF) / 255;
|
||||
const r = ((color.rgba >> 24) & 0xFF) / 255;
|
||||
const g = ((color.rgba >> 16) & 0xFF) / 255;
|
||||
const b = ((color.rgba >> 8 ) & 0xFF) / 255;
|
||||
|
||||
this._addRectangle(vertices.attributes, offset, x1, y1, (endX - startX) * this._dimensions.scaledCellWidth, this._dimensions.scaledCellHeight, r, g, b, 1);
|
||||
}
|
||||
|
||||
@@ -6,9 +6,7 @@
|
||||
import { IRenderModel, ISelectionRenderModel } from './Types';
|
||||
import { fill } from 'common/TypedArrayUtils';
|
||||
|
||||
export const RENDER_MODEL_INDICIES_PER_CELL = 3;
|
||||
export const RENDER_MODEL_BG_OFFSET = 1;
|
||||
export const RENDER_MODEL_FG_OFFSET = 2;
|
||||
export const RENDER_MODEL_INDICIES_PER_CELL = 4;
|
||||
|
||||
export const COMBINED_CHAR_BIT_MASK = 0x80000000;
|
||||
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
export interface IRasterizedGlyphSet {
|
||||
[bg: number]: { [fg: number]: IRasterizedGlyph } | undefined;
|
||||
[flags: number]: IRasterizedGlyph;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,28 +9,20 @@ import { IRenderService } from 'browser/services/Services';
|
||||
import { IColorSet } from 'browser/Types';
|
||||
|
||||
export class WebglAddon implements ITerminalAddon {
|
||||
private _terminal?: Terminal;
|
||||
|
||||
constructor(
|
||||
private _preserveDrawingBuffer?: boolean
|
||||
) {}
|
||||
|
||||
public activate(terminal: Terminal): void {
|
||||
if (!terminal.element) {
|
||||
throw new Error('Cannot activate WebglAddon before Terminal.open');
|
||||
throw new Error('Cannot activate WebglRendererAddon before Terminal.open');
|
||||
}
|
||||
this._terminal = terminal;
|
||||
const renderService: IRenderService = (<any>terminal)._core._renderService;
|
||||
const colors: IColorSet = (<any>terminal)._core._colorManager.colors;
|
||||
renderService.setRenderer(new WebglRenderer(terminal, colors, this._preserveDrawingBuffer));
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
if (!this._terminal) {
|
||||
throw new Error('Cannot dispose WebglAddon because it is activated');
|
||||
}
|
||||
const renderService: IRenderService = (<any>this._terminal)._core._renderService;
|
||||
renderService.setRenderer((<any>this._terminal)._core._createRenderer());
|
||||
renderService.onResize(this._terminal.cols, this._terminal.rows);
|
||||
throw new Error('WebglRendererAddon.dispose Not yet implemented');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,9 @@
|
||||
*/
|
||||
|
||||
import * as puppeteer from 'puppeteer';
|
||||
import { assert } from 'chai';
|
||||
import { ITerminalOptions } from '../../../src/Types';
|
||||
import { ITheme } from 'xterm';
|
||||
import { assert } from 'chai';
|
||||
import deepEqual = require('deep-equal');
|
||||
|
||||
const APP = 'http://127.0.0.1:3000/test';
|
||||
|
||||
@@ -17,20 +16,31 @@ const width = 800;
|
||||
const height = 600;
|
||||
|
||||
describe('WebGL Renderer Integration Tests', function(): void {
|
||||
it('dispose removes renderer canvases', async () => {
|
||||
await setupBrowser();
|
||||
assert.equal(await page.evaluate(`document.querySelectorAll('.xterm canvas').length`), 3);
|
||||
await page.evaluate(`addon.dispose()`);
|
||||
assert.equal(await page.evaluate(`document.querySelectorAll('.xterm canvas').length`), 0);
|
||||
await browser.close();
|
||||
this.timeout(20000);
|
||||
|
||||
before(async function(): Promise<any> {
|
||||
browser = await puppeteer.launch({
|
||||
headless: process.argv.indexOf('--headless') !== -1,
|
||||
slowMo: 80,
|
||||
args: [`--window-size=${width},${height}`]
|
||||
});
|
||||
page = (await browser.pages())[0];
|
||||
await page.setViewport({ width, height });
|
||||
await page.goto(APP);
|
||||
await openTerminal();
|
||||
await page.evaluate(`window.term.loadAddon(new WebglAddon(true));`);
|
||||
});
|
||||
|
||||
describe('colors', () => {
|
||||
before(async () => setupBrowser());
|
||||
after(async () => browser.close());
|
||||
beforeEach(async () => page.evaluate(`window.term.reset()`));
|
||||
after(() => {
|
||||
browser.close();
|
||||
});
|
||||
|
||||
it('foreground 0-15', async () => {
|
||||
beforeEach(async () => {
|
||||
await page.evaluate(`window.term.reset()`);
|
||||
});
|
||||
|
||||
describe('WebGL Renderer', () => {
|
||||
it('foreground colors normal', async function(): Promise<any> {
|
||||
const theme: ITheme = {
|
||||
black: '#010203',
|
||||
red: '#040506',
|
||||
@@ -43,112 +53,17 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
};
|
||||
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
|
||||
await writeSync(`\\x1b[30m█\\x1b[31m█\\x1b[32m█\\x1b[33m█\\x1b[34m█\\x1b[35m█\\x1b[36m█\\x1b[37m█`);
|
||||
await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]);
|
||||
await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]);
|
||||
await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]);
|
||||
await pollFor(page, () => getCellColor(4, 1), [10, 11, 12, 255]);
|
||||
await pollFor(page, () => getCellColor(5, 1), [13, 14, 15, 255]);
|
||||
await pollFor(page, () => getCellColor(6, 1), [16, 17, 18, 255]);
|
||||
await pollFor(page, () => getCellColor(7, 1), [19, 20, 21, 255]);
|
||||
await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]);
|
||||
assert.deepEqual(await getCellColor(1, 1), [1, 2, 3, 255]);
|
||||
assert.deepEqual(await getCellColor(2, 1), [4, 5, 6, 255]);
|
||||
assert.deepEqual(await getCellColor(3, 1), [7, 8, 9, 255]);
|
||||
assert.deepEqual(await getCellColor(4, 1), [10, 11, 12, 255]);
|
||||
assert.deepEqual(await getCellColor(5, 1), [13, 14, 15, 255]);
|
||||
assert.deepEqual(await getCellColor(6, 1), [16, 17, 18, 255]);
|
||||
assert.deepEqual(await getCellColor(7, 1), [19, 20, 21, 255]);
|
||||
assert.deepEqual(await getCellColor(8, 1), [22, 23, 24, 255]);
|
||||
});
|
||||
|
||||
it('foreground 0-7 drawBoldTextInBrightColors', async () => {
|
||||
const theme: ITheme = {
|
||||
brightBlack: '#010203',
|
||||
brightRed: '#040506',
|
||||
brightGreen: '#070809',
|
||||
brightYellow: '#0a0b0c',
|
||||
brightBlue: '#0d0e0f',
|
||||
brightMagenta: '#101112',
|
||||
brightCyan: '#131415',
|
||||
brightWhite: '#161718'
|
||||
};
|
||||
await page.evaluate(`
|
||||
window.term.setOption('theme', ${JSON.stringify(theme)});
|
||||
window.term.setOption('drawBoldTextInBrightColors', true);
|
||||
`);
|
||||
await writeSync(`\\x1b[1;30m█\\x1b[1;31m█\\x1b[1;32m█\\x1b[1;33m█\\x1b[1;34m█\\x1b[1;35m█\\x1b[1;36m█\\x1b[1;37m█`);
|
||||
await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]);
|
||||
await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]);
|
||||
await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]);
|
||||
await pollFor(page, () => getCellColor(4, 1), [10, 11, 12, 255]);
|
||||
await pollFor(page, () => getCellColor(5, 1), [13, 14, 15, 255]);
|
||||
await pollFor(page, () => getCellColor(6, 1), [16, 17, 18, 255]);
|
||||
await pollFor(page, () => getCellColor(7, 1), [19, 20, 21, 255]);
|
||||
await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]);
|
||||
});
|
||||
|
||||
it('background 0-15', async () => {
|
||||
const theme: ITheme = {
|
||||
black: '#010203',
|
||||
red: '#040506',
|
||||
green: '#070809',
|
||||
yellow: '#0a0b0c',
|
||||
blue: '#0d0e0f',
|
||||
magenta: '#101112',
|
||||
cyan: '#131415',
|
||||
white: '#161718'
|
||||
};
|
||||
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
|
||||
await writeSync(`\\x1b[40m \\x1b[41m \\x1b[42m \\x1b[43m \\x1b[44m \\x1b[45m \\x1b[46m \\x1b[47m `);
|
||||
await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]);
|
||||
await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]);
|
||||
await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]);
|
||||
await pollFor(page, () => getCellColor(4, 1), [10, 11, 12, 255]);
|
||||
await pollFor(page, () => getCellColor(5, 1), [13, 14, 15, 255]);
|
||||
await pollFor(page, () => getCellColor(6, 1), [16, 17, 18, 255]);
|
||||
await pollFor(page, () => getCellColor(7, 1), [19, 20, 21, 255]);
|
||||
await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]);
|
||||
});
|
||||
|
||||
it('foreground 0-15 inverse', async () => {
|
||||
const theme: ITheme = {
|
||||
black: '#010203',
|
||||
red: '#040506',
|
||||
green: '#070809',
|
||||
yellow: '#0a0b0c',
|
||||
blue: '#0d0e0f',
|
||||
magenta: '#101112',
|
||||
cyan: '#131415',
|
||||
white: '#161718'
|
||||
};
|
||||
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
|
||||
await writeSync(`\\x1b[7;30m \\x1b[7;31m \\x1b[7;32m \\x1b[7;33m \\x1b[7;34m \\x1b[7;35m \\x1b[7;36m \\x1b[7;37m `);
|
||||
await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]);
|
||||
await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]);
|
||||
await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]);
|
||||
await pollFor(page, () => getCellColor(4, 1), [10, 11, 12, 255]);
|
||||
await pollFor(page, () => getCellColor(5, 1), [13, 14, 15, 255]);
|
||||
await pollFor(page, () => getCellColor(6, 1), [16, 17, 18, 255]);
|
||||
await pollFor(page, () => getCellColor(7, 1), [19, 20, 21, 255]);
|
||||
await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]);
|
||||
});
|
||||
|
||||
it('background 0-15 inverse', async () => {
|
||||
const theme: ITheme = {
|
||||
black: '#010203',
|
||||
red: '#040506',
|
||||
green: '#070809',
|
||||
yellow: '#0a0b0c',
|
||||
blue: '#0d0e0f',
|
||||
magenta: '#101112',
|
||||
cyan: '#131415',
|
||||
white: '#161718'
|
||||
};
|
||||
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
|
||||
await writeSync(`\\x1b[7;40m█\\x1b[7;41m█\\x1b[7;42m█\\x1b[7;43m█\\x1b[7;44m█\\x1b[7;45m█\\x1b[7;46m█\\x1b[7;47m█`);
|
||||
await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]);
|
||||
await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]);
|
||||
await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]);
|
||||
await pollFor(page, () => getCellColor(4, 1), [10, 11, 12, 255]);
|
||||
await pollFor(page, () => getCellColor(5, 1), [13, 14, 15, 255]);
|
||||
await pollFor(page, () => getCellColor(6, 1), [16, 17, 18, 255]);
|
||||
await pollFor(page, () => getCellColor(7, 1), [19, 20, 21, 255]);
|
||||
await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]);
|
||||
});
|
||||
|
||||
it('foreground 0-15 bright', async () => {
|
||||
it('foreground colors bright', async function(): Promise<any> {
|
||||
const theme: ITheme = {
|
||||
brightBlack: '#010203',
|
||||
brightRed: '#040506',
|
||||
@@ -161,17 +76,40 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
};
|
||||
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
|
||||
await writeSync(`\\x1b[90m█\\x1b[91m█\\x1b[92m█\\x1b[93m█\\x1b[94m█\\x1b[95m█\\x1b[96m█\\x1b[97m█`);
|
||||
await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]);
|
||||
await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]);
|
||||
await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]);
|
||||
await pollFor(page, () => getCellColor(4, 1), [10, 11, 12, 255]);
|
||||
await pollFor(page, () => getCellColor(5, 1), [13, 14, 15, 255]);
|
||||
await pollFor(page, () => getCellColor(6, 1), [16, 17, 18, 255]);
|
||||
await pollFor(page, () => getCellColor(7, 1), [19, 20, 21, 255]);
|
||||
await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]);
|
||||
assert.deepEqual(await getCellColor(1, 1), [1, 2, 3, 255]);
|
||||
assert.deepEqual(await getCellColor(2, 1), [4, 5, 6, 255]);
|
||||
assert.deepEqual(await getCellColor(3, 1), [7, 8, 9, 255]);
|
||||
assert.deepEqual(await getCellColor(4, 1), [10, 11, 12, 255]);
|
||||
assert.deepEqual(await getCellColor(5, 1), [13, 14, 15, 255]);
|
||||
assert.deepEqual(await getCellColor(6, 1), [16, 17, 18, 255]);
|
||||
assert.deepEqual(await getCellColor(7, 1), [19, 20, 21, 255]);
|
||||
assert.deepEqual(await getCellColor(8, 1), [22, 23, 24, 255]);
|
||||
});
|
||||
|
||||
it('background 0-15 bright', async () => {
|
||||
it('background colors normal', async function(): Promise<any> {
|
||||
const theme: ITheme = {
|
||||
black: '#010203',
|
||||
red: '#040506',
|
||||
green: '#070809',
|
||||
yellow: '#0a0b0c',
|
||||
blue: '#0d0e0f',
|
||||
magenta: '#101112',
|
||||
cyan: '#131415',
|
||||
white: '#161718'
|
||||
};
|
||||
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
|
||||
await writeSync(`\\x1b[40m \\x1b[41m \\x1b[42m \\x1b[43m \\x1b[44m \\x1b[45m \\x1b[46m \\x1b[47m `);
|
||||
assert.deepEqual(await getCellColor(1, 1), [1, 2, 3, 255]);
|
||||
assert.deepEqual(await getCellColor(2, 1), [4, 5, 6, 255]);
|
||||
assert.deepEqual(await getCellColor(3, 1), [7, 8, 9, 255]);
|
||||
assert.deepEqual(await getCellColor(4, 1), [10, 11, 12, 255]);
|
||||
assert.deepEqual(await getCellColor(5, 1), [13, 14, 15, 255]);
|
||||
assert.deepEqual(await getCellColor(6, 1), [16, 17, 18, 255]);
|
||||
assert.deepEqual(await getCellColor(7, 1), [19, 20, 21, 255]);
|
||||
assert.deepEqual(await getCellColor(8, 1), [22, 23, 24, 255]);
|
||||
});
|
||||
|
||||
it('background colors bright', async function(): Promise<any> {
|
||||
const theme: ITheme = {
|
||||
brightBlack: '#010203',
|
||||
brightRed: '#040506',
|
||||
@@ -184,538 +122,14 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
};
|
||||
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
|
||||
await writeSync(`\\x1b[100m \\x1b[101m \\x1b[102m \\x1b[103m \\x1b[104m \\x1b[105m \\x1b[106m \\x1b[107m `);
|
||||
await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]);
|
||||
await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]);
|
||||
await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]);
|
||||
await pollFor(page, () => getCellColor(4, 1), [10, 11, 12, 255]);
|
||||
await pollFor(page, () => getCellColor(5, 1), [13, 14, 15, 255]);
|
||||
await pollFor(page, () => getCellColor(6, 1), [16, 17, 18, 255]);
|
||||
await pollFor(page, () => getCellColor(7, 1), [19, 20, 21, 255]);
|
||||
await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]);
|
||||
});
|
||||
|
||||
it('foreground 16-255', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 240 / 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
data += `\\x1b[38;5;${16 + y * 16 + x}m█\x1b[0m`;
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(data);
|
||||
for (let y = 0; y < 240 / 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const cssColor = COLORS_16_TO_255[y * 16 + x];
|
||||
const r = parseInt(cssColor.substr(1, 2), 16);
|
||||
const g = parseInt(cssColor.substr(3, 2), 16);
|
||||
const b = parseInt(cssColor.substr(5, 2), 16);
|
||||
await pollFor(page, () => getCellColor(x + 1, y + 1), [r, g, b, 255]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('background 16-255', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 240 / 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
data += `\\x1b[48;5;${16 + y * 16 + x}m \x1b[0m`;
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(data);
|
||||
for (let y = 0; y < 240 / 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const cssColor = COLORS_16_TO_255[y * 16 + x];
|
||||
const r = parseInt(cssColor.substr(1, 2), 16);
|
||||
const g = parseInt(cssColor.substr(3, 2), 16);
|
||||
const b = parseInt(cssColor.substr(5, 2), 16);
|
||||
await pollFor(page, () => getCellColor(x + 1, y + 1), [r, g, b, 255]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('foreground 16-255 inverse', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 240 / 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
data += `\\x1b[7;38;5;${16 + y * 16 + x}m \x1b[0m`;
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(data);
|
||||
for (let y = 0; y < 240 / 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const cssColor = COLORS_16_TO_255[y * 16 + x];
|
||||
const r = parseInt(cssColor.substr(1, 2), 16);
|
||||
const g = parseInt(cssColor.substr(3, 2), 16);
|
||||
const b = parseInt(cssColor.substr(5, 2), 16);
|
||||
await pollFor(page, () => getCellColor(x + 1, y + 1), [r, g, b, 255]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('background 16-255 inverse', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 240 / 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
data += `\\x1b[7;48;5;${16 + y * 16 + x}m█\x1b[0m`;
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(data);
|
||||
for (let y = 0; y < 240 / 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const cssColor = COLORS_16_TO_255[y * 16 + x];
|
||||
const r = parseInt(cssColor.substr(1, 2), 16);
|
||||
const g = parseInt(cssColor.substr(3, 2), 16);
|
||||
const b = parseInt(cssColor.substr(5, 2), 16);
|
||||
await pollFor(page, () => getCellColor(x + 1, y + 1), [r, g, b, 255]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('foreground true color red', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
data += `\\x1b[38;2;${i};0;0m█\x1b[0m`;
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(data);
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
await pollFor(page, () => getCellColor(x + 1, y + 1), [i, 0, 0, 255]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('background true color red', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
data += `\\x1b[48;2;${i};0;0m \x1b[0m`;
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(data);
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
await pollFor(page, () => getCellColor(x + 1, y + 1), [i, 0, 0, 255]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('foreground true color green', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
data += `\\x1b[38;2;0;${i};0m█\x1b[0m`;
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(data);
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
await pollFor(page, () => getCellColor(x + 1, y + 1), [0, i, 0, 255]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('background true color green', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
data += `\\x1b[48;2;0;${i};0m \x1b[0m`;
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(data);
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
await pollFor(page, () => getCellColor(x + 1, y + 1), [0, i, 0, 255]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('foreground true color blue', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
data += `\\x1b[38;2;0;0;${i}m█\x1b[0m`;
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(data);
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
await pollFor(page, () => getCellColor(x + 1, y + 1), [0, 0, i, 255]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('background true color blue', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
data += `\\x1b[48;2;0;0;${i}m \x1b[0m`;
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(data);
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
await pollFor(page, () => getCellColor(x + 1, y + 1), [0, 0, i, 255]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('foreground true color grey', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
data += `\\x1b[38;2;${i};${i};${i}m█\x1b[0m`;
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(data);
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
await pollFor(page, () => getCellColor(x + 1, y + 1), [i, i, i, 255]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('background true color grey', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
data += `\\x1b[48;2;${i};${i};${i}m \x1b[0m`;
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(data);
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
await pollFor(page, () => getCellColor(x + 1, y + 1), [i, i, i, 255]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('foreground true color red inverse', async function(): Promise<void> {
|
||||
let data = '';
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
data += `\x1b[7;38;2;${i};0;0m \x1b[0m`;
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(data);
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
await pollFor(page, () => getCellColor(x + 1, y + 1), [i, 0, 0, 255]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('background true color red inverse', async function(): Promise<void> {
|
||||
let data = '';
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
data += `\\x1b[7;48;2;${i};0;0m█\\x1b[0m`;
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(data);
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
await pollFor(page, () => getCellColor(x + 1, y + 1), [i, 0, 0, 255]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('foreground true color green inverse', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
data += `\\x1b[7;38;2;0;${i};0m \x1b[0m`;
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(data);
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
await pollFor(page, () => getCellColor(x + 1, y + 1), [0, i, 0, 255]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('background true color green inverse', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
data += `\\x1b[7;48;2;0;${i};0m█\x1b[0m`;
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(data);
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
await pollFor(page, () => getCellColor(x + 1, y + 1), [0, i, 0, 255]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('foreground true color blue inverse', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
data += `\\x1b[7;38;2;0;0;${i}m \x1b[0m`;
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(data);
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
await pollFor(page, () => getCellColor(x + 1, y + 1), [0, 0, i, 255]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('background true color blue inverse', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
data += `\\x1b[7;48;2;0;0;${i}m█\x1b[0m`;
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(data);
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
await pollFor(page, () => getCellColor(x + 1, y + 1), [0, 0, i, 255]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('foreground true color grey inverse', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
data += `\\x1b[7;38;2;${i};${i};${i}m \x1b[0m`;
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(data);
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
await pollFor(page, () => getCellColor(x + 1, y + 1), [i, i, i, 255]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('background true color grey inverse', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
data += `\\x1b[7;48;2;${i};${i};${i}m█\x1b[0m`;
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(data);
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
await pollFor(page, () => getCellColor(x + 1, y + 1), [i, i, i, 255]);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('minimumContrastRatio', async () => {
|
||||
before(async () => setupBrowser());
|
||||
after(async () => browser.close());
|
||||
beforeEach(async () => page.evaluate(`window.term.reset()`));
|
||||
|
||||
it('should adjust 0-15 colors on black background', async () => {
|
||||
const theme: ITheme = {
|
||||
black: '#2e3436',
|
||||
red: '#cc0000',
|
||||
green: '#4e9a06',
|
||||
yellow: '#c4a000',
|
||||
blue: '#3465a4',
|
||||
magenta: '#75507b',
|
||||
cyan: '#06989a',
|
||||
white: '#d3d7cf',
|
||||
brightBlack: '#555753',
|
||||
brightRed: '#ef2929',
|
||||
brightGreen: '#8ae234',
|
||||
brightYellow: '#fce94f',
|
||||
brightBlue: '#729fcf',
|
||||
brightMagenta: '#ad7fa8',
|
||||
brightCyan: '#34e2e2',
|
||||
brightWhite: '#eeeeec'
|
||||
};
|
||||
await page.evaluate(`
|
||||
window.term.setOption('theme', ${JSON.stringify(theme)});
|
||||
window.term.setOption('minimumContrastRatio', 1);
|
||||
`);
|
||||
await writeSync(
|
||||
`\\x1b[30m█\\x1b[31m█\\x1b[32m█\\x1b[33m█\\x1b[34m█\\x1b[35m█\\x1b[36m█\\x1b[37m█\\r\\n` +
|
||||
`\\x1b[90m█\\x1b[91m█\\x1b[92m█\\x1b[93m█\\x1b[94m█\\x1b[95m█\\x1b[96m█\\x1b[97m█`
|
||||
);
|
||||
// Validate before minimumContrastRatio is applied
|
||||
await pollFor(page, () => getCellColor(1, 1), [0x2e, 0x34, 0x36, 255]);
|
||||
await pollFor(page, () => getCellColor(2, 1), [0xcc, 0x00, 0x00, 255]);
|
||||
await pollFor(page, () => getCellColor(3, 1), [0x4e, 0x9a, 0x06, 255]);
|
||||
await pollFor(page, () => getCellColor(4, 1), [0xc4, 0xa0, 0x00, 255]);
|
||||
await pollFor(page, () => getCellColor(5, 1), [0x34, 0x65, 0xa4, 255]);
|
||||
await pollFor(page, () => getCellColor(6, 1), [0x75, 0x50, 0x7b, 255]);
|
||||
await pollFor(page, () => getCellColor(7, 1), [0x06, 0x98, 0x9a, 255]);
|
||||
await pollFor(page, () => getCellColor(8, 1), [0xd3, 0xd7, 0xcf, 255]);
|
||||
await pollFor(page, () => getCellColor(1, 2), [0x55, 0x57, 0x53, 255]);
|
||||
await pollFor(page, () => getCellColor(2, 2), [0xef, 0x29, 0x29, 255]);
|
||||
await pollFor(page, () => getCellColor(3, 2), [0x8a, 0xe2, 0x34, 255]);
|
||||
await pollFor(page, () => getCellColor(4, 2), [0xfc, 0xe9, 0x4f, 255]);
|
||||
await pollFor(page, () => getCellColor(5, 2), [0x72, 0x9f, 0xcf, 255]);
|
||||
await pollFor(page, () => getCellColor(6, 2), [0xad, 0x7f, 0xa8, 255]);
|
||||
await pollFor(page, () => getCellColor(7, 2), [0x34, 0xe2, 0xe2, 255]);
|
||||
await pollFor(page, () => getCellColor(8, 2), [0xee, 0xee, 0xec, 255]);
|
||||
// Setting and check for minimum contrast values, note that these are note
|
||||
// exact to the contrast ratio, if the increase luminance algorithm
|
||||
// changes then these will probably fail
|
||||
await page.evaluate(`window.term.setOption('minimumContrastRatio', 10);`);
|
||||
await pollFor(page, () => getCellColor(1, 1), [176, 180, 180, 255]);
|
||||
await pollFor(page, () => getCellColor(2, 1), [238, 158, 158, 255]);
|
||||
await pollFor(page, () => getCellColor(3, 1), [197, 223, 171, 255]);
|
||||
await pollFor(page, () => getCellColor(4, 1), [235, 221, 158, 255]);
|
||||
await pollFor(page, () => getCellColor(5, 1), [124, 156, 198, 255]);
|
||||
await pollFor(page, () => getCellColor(6, 1), [183, 165, 187, 255]);
|
||||
await pollFor(page, () => getCellColor(7, 1), [110, 197, 198, 255]);
|
||||
await pollFor(page, () => getCellColor(8, 1), [211, 215, 207, 255]);
|
||||
await pollFor(page, () => getCellColor(1, 2), [183, 185, 183, 255]);
|
||||
await pollFor(page, () => getCellColor(2, 2), [249, 156, 156, 255]);
|
||||
await pollFor(page, () => getCellColor(3, 2), [138, 226, 52, 255]);
|
||||
await pollFor(page, () => getCellColor(4, 2), [252, 233, 79, 255]);
|
||||
await pollFor(page, () => getCellColor(5, 2), [114, 159, 207, 255]);
|
||||
await pollFor(page, () => getCellColor(6, 2), [190, 152, 185, 255]);
|
||||
// Unchanged
|
||||
await pollFor(page, () => getCellColor(7, 2), [0x34, 0xe2, 0xe2, 255]);
|
||||
await pollFor(page, () => getCellColor(8, 2), [0xee, 0xee, 0xec, 255]);
|
||||
});
|
||||
|
||||
it('should adjust 0-15 colors on white background', async () => {
|
||||
const theme: ITheme = {
|
||||
background: '#ffffff',
|
||||
black: '#2e3436',
|
||||
red: '#cc0000',
|
||||
green: '#4e9a06',
|
||||
yellow: '#c4a000',
|
||||
blue: '#3465a4',
|
||||
magenta: '#75507b',
|
||||
cyan: '#06989a',
|
||||
white: '#d3d7cf',
|
||||
brightBlack: '#555753',
|
||||
brightRed: '#ef2929',
|
||||
brightGreen: '#8ae234',
|
||||
brightYellow: '#fce94f',
|
||||
brightBlue: '#729fcf',
|
||||
brightMagenta: '#ad7fa8',
|
||||
brightCyan: '#34e2e2',
|
||||
brightWhite: '#eeeeec'
|
||||
};
|
||||
await page.evaluate(`
|
||||
window.term.setOption('theme', ${JSON.stringify(theme)});
|
||||
window.term.setOption('minimumContrastRatio', 1);
|
||||
`);
|
||||
await writeSync(
|
||||
`\\x1b[30m█\\x1b[31m█\\x1b[32m█\\x1b[33m█\\x1b[34m█\\x1b[35m█\\x1b[36m█\\x1b[37m█\\r\\n` +
|
||||
`\\x1b[90m█\\x1b[91m█\\x1b[92m█\\x1b[93m█\\x1b[94m█\\x1b[95m█\\x1b[96m█\\x1b[97m█`
|
||||
);
|
||||
// Validate before minimumContrastRatio is applied
|
||||
await pollFor(page, () => getCellColor(1, 1), [0x2e, 0x34, 0x36, 255]);
|
||||
await pollFor(page, () => getCellColor(2, 1), [0xcc, 0x00, 0x00, 255]);
|
||||
await pollFor(page, () => getCellColor(3, 1), [0x4e, 0x9a, 0x06, 255]);
|
||||
await pollFor(page, () => getCellColor(4, 1), [0xc4, 0xa0, 0x00, 255]);
|
||||
await pollFor(page, () => getCellColor(5, 1), [0x34, 0x65, 0xa4, 255]);
|
||||
await pollFor(page, () => getCellColor(6, 1), [0x75, 0x50, 0x7b, 255]);
|
||||
await pollFor(page, () => getCellColor(7, 1), [0x06, 0x98, 0x9a, 255]);
|
||||
await pollFor(page, () => getCellColor(8, 1), [0xd3, 0xd7, 0xcf, 255]);
|
||||
await pollFor(page, () => getCellColor(1, 2), [0x55, 0x57, 0x53, 255]);
|
||||
await pollFor(page, () => getCellColor(2, 2), [0xef, 0x29, 0x29, 255]);
|
||||
await pollFor(page, () => getCellColor(3, 2), [0x8a, 0xe2, 0x34, 255]);
|
||||
await pollFor(page, () => getCellColor(4, 2), [0xfc, 0xe9, 0x4f, 255]);
|
||||
await pollFor(page, () => getCellColor(5, 2), [0x72, 0x9f, 0xcf, 255]);
|
||||
await pollFor(page, () => getCellColor(6, 2), [0xad, 0x7f, 0xa8, 255]);
|
||||
await pollFor(page, () => getCellColor(7, 2), [0x34, 0xe2, 0xe2, 255]);
|
||||
await pollFor(page, () => getCellColor(8, 2), [0xee, 0xee, 0xec, 255]);
|
||||
// Setting and check for minimum contrast values, note that these are note
|
||||
// exact to the contrast ratio, if the increase luminance algorithm
|
||||
// changes then these will probably fail
|
||||
await page.evaluate(`window.term.setOption('minimumContrastRatio', 10);`);
|
||||
await pollFor(page, () => getCellColor(1, 1), [46, 52, 54, 255]);
|
||||
await pollFor(page, () => getCellColor(2, 1), [132, 0, 0, 255]);
|
||||
await pollFor(page, () => getCellColor(3, 1), [78, 154, 6, 255]);
|
||||
await pollFor(page, () => getCellColor(4, 1), [114, 93, 0, 255]);
|
||||
await pollFor(page, () => getCellColor(5, 1), [19, 40, 68, 255]);
|
||||
await pollFor(page, () => getCellColor(6, 1), [60, 40, 64, 255]);
|
||||
await pollFor(page, () => getCellColor(7, 1), [0, 71, 72, 255]);
|
||||
await pollFor(page, () => getCellColor(8, 1), [64, 64, 63, 255]);
|
||||
await pollFor(page, () => getCellColor(1, 2), [61, 63, 59, 255]);
|
||||
await pollFor(page, () => getCellColor(2, 2), [125, 19, 19, 255]);
|
||||
await pollFor(page, () => getCellColor(3, 2), [89, 146, 32, 255]);
|
||||
await pollFor(page, () => getCellColor(4, 2), [105, 98, 32, 255]);
|
||||
await pollFor(page, () => getCellColor(5, 2), [36, 52, 70, 255]);
|
||||
await pollFor(page, () => getCellColor(6, 2), [64, 45, 63, 255]);
|
||||
await pollFor(page, () => getCellColor(7, 2), [13, 67, 67, 255]);
|
||||
await pollFor(page, () => getCellColor(8, 2), [64, 64, 64, 255]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('allowTransparency', async () => {
|
||||
before(async () => setupBrowser({ rendererType: 'dom', allowTransparency: true}));
|
||||
after(async () => browser.close());
|
||||
beforeEach(async () => page.evaluate(`window.term.reset()`));
|
||||
it('transparent background inverse', async () => {
|
||||
const theme: ITheme = {
|
||||
background: '#ff000080'
|
||||
};
|
||||
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
|
||||
const data = `\\x1b[7m█\x1b[0m`;
|
||||
await writeSync(data);
|
||||
// Inverse background should be opaque
|
||||
await pollFor(page, () => getCellColor(1, 1), [255, 0, 0, 255]);
|
||||
assert.deepEqual(await getCellColor(1, 1), [1, 2, 3, 255]);
|
||||
assert.deepEqual(await getCellColor(2, 1), [4, 5, 6, 255]);
|
||||
assert.deepEqual(await getCellColor(3, 1), [7, 8, 9, 255]);
|
||||
assert.deepEqual(await getCellColor(4, 1), [10, 11, 12, 255]);
|
||||
assert.deepEqual(await getCellColor(5, 1), [13, 14, 15, 255]);
|
||||
assert.deepEqual(await getCellColor(6, 1), [16, 17, 18, 255]);
|
||||
assert.deepEqual(await getCellColor(7, 1), [19, 20, 21, 255]);
|
||||
assert.deepEqual(await getCellColor(8, 1), [22, 23, 24, 255]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -731,7 +145,12 @@ async function openTerminal(options: ITerminalOptions = {}): Promise<void> {
|
||||
}
|
||||
|
||||
async function writeSync(data: string): Promise<void> {
|
||||
return page.evaluate(`new Promise(resolve => window.term.write('${data}', resolve))`);
|
||||
await page.evaluate(`window.term.write('${data}');`);
|
||||
while (true) {
|
||||
if (await page.evaluate(`window.term._core.writeBuffer.length === 0`)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function getCellColor(col: number, row: number): Promise<number[]> {
|
||||
@@ -747,48 +166,3 @@ async function getCellColor(col: number, row: number): Promise<number[]> {
|
||||
`);
|
||||
return await page.evaluate(`Array.from(window.result)`);
|
||||
}
|
||||
|
||||
async function setupBrowser(options: ITerminalOptions = { rendererType: 'dom' }): Promise<void> {
|
||||
browser = await puppeteer.launch({
|
||||
headless: process.argv.indexOf('--headless') !== -1,
|
||||
args: [`--window-size=${width},${height}`, `--no-sandbox`]
|
||||
});
|
||||
page = (await browser.pages())[0];
|
||||
await page.setViewport({ width, height });
|
||||
await page.goto(APP);
|
||||
await openTerminal(options);
|
||||
await page.evaluate(`
|
||||
window.addon = new WebglAddon(true);
|
||||
window.term.loadAddon(window.addon);
|
||||
`);
|
||||
}
|
||||
|
||||
export async function pollFor<T>(page: puppeteer.Page, evalOrFn: string | (() => Promise<T>), val: T, preFn?: () => Promise<void>): Promise<void> {
|
||||
if (preFn) {
|
||||
await preFn();
|
||||
}
|
||||
const result = typeof evalOrFn === 'string' ? await page.evaluate(evalOrFn) : await evalOrFn();
|
||||
if (!deepEqual(result, val)) {
|
||||
return new Promise<void>(r => {
|
||||
setTimeout(() => r(pollFor(page, evalOrFn, val, preFn)), 1);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const COLORS_16_TO_255 = [
|
||||
'#000000', '#00005f', '#000087', '#0000af', '#0000d7', '#0000ff', '#005f00', '#005f5f', '#005f87', '#005faf', '#005fd7', '#005fff', '#008700', '#00875f', '#008787', '#0087af',
|
||||
'#0087d7', '#0087ff', '#00af00', '#00af5f', '#00af87', '#00afaf', '#00afd7', '#00afff', '#00d700', '#00d75f', '#00d787', '#00d7af', '#00d7d7', '#00d7ff', '#00ff00', '#00ff5f',
|
||||
'#00ff87', '#00ffaf', '#00ffd7', '#00ffff', '#5f0000', '#5f005f', '#5f0087', '#5f00af', '#5f00d7', '#5f00ff', '#5f5f00', '#5f5f5f', '#5f5f87', '#5f5faf', '#5f5fd7', '#5f5fff',
|
||||
'#5f8700', '#5f875f', '#5f8787', '#5f87af', '#5f87d7', '#5f87ff', '#5faf00', '#5faf5f', '#5faf87', '#5fafaf', '#5fafd7', '#5fafff', '#5fd700', '#5fd75f', '#5fd787', '#5fd7af',
|
||||
'#5fd7d7', '#5fd7ff', '#5fff00', '#5fff5f', '#5fff87', '#5fffaf', '#5fffd7', '#5fffff', '#870000', '#87005f', '#870087', '#8700af', '#8700d7', '#8700ff', '#875f00', '#875f5f',
|
||||
'#875f87', '#875faf', '#875fd7', '#875fff', '#878700', '#87875f', '#878787', '#8787af', '#8787d7', '#8787ff', '#87af00', '#87af5f', '#87af87', '#87afaf', '#87afd7', '#87afff',
|
||||
'#87d700', '#87d75f', '#87d787', '#87d7af', '#87d7d7', '#87d7ff', '#87ff00', '#87ff5f', '#87ff87', '#87ffaf', '#87ffd7', '#87ffff', '#af0000', '#af005f', '#af0087', '#af00af',
|
||||
'#af00d7', '#af00ff', '#af5f00', '#af5f5f', '#af5f87', '#af5faf', '#af5fd7', '#af5fff', '#af8700', '#af875f', '#af8787', '#af87af', '#af87d7', '#af87ff', '#afaf00', '#afaf5f',
|
||||
'#afaf87', '#afafaf', '#afafd7', '#afafff', '#afd700', '#afd75f', '#afd787', '#afd7af', '#afd7d7', '#afd7ff', '#afff00', '#afff5f', '#afff87', '#afffaf', '#afffd7', '#afffff',
|
||||
'#d70000', '#d7005f', '#d70087', '#d700af', '#d700d7', '#d700ff', '#d75f00', '#d75f5f', '#d75f87', '#d75faf', '#d75fd7', '#d75fff', '#d78700', '#d7875f', '#d78787', '#d787af',
|
||||
'#d787d7', '#d787ff', '#d7af00', '#d7af5f', '#d7af87', '#d7afaf', '#d7afd7', '#d7afff', '#d7d700', '#d7d75f', '#d7d787', '#d7d7af', '#d7d7d7', '#d7d7ff', '#d7ff00', '#d7ff5f',
|
||||
'#d7ff87', '#d7ffaf', '#d7ffd7', '#d7ffff', '#ff0000', '#ff005f', '#ff0087', '#ff00af', '#ff00d7', '#ff00ff', '#ff5f00', '#ff5f5f', '#ff5f87', '#ff5faf', '#ff5fd7', '#ff5fff',
|
||||
'#ff8700', '#ff875f', '#ff8787', '#ff87af', '#ff87d7', '#ff87ff', '#ffaf00', '#ffaf5f', '#ffaf87', '#ffafaf', '#ffafd7', '#ffafff', '#ffd700', '#ffd75f', '#ffd787', '#ffd7af',
|
||||
'#ffd7d7', '#ffd7ff', '#ffff00', '#ffff5f', '#ffff87', '#ffffaf', '#ffffd7', '#ffffff', '#080808', '#121212', '#1c1c1c', '#262626', '#303030', '#3a3a3a', '#444444', '#4e4e4e',
|
||||
'#585858', '#626262', '#6c6c6c', '#767676', '#808080', '#8a8a8a', '#949494', '#9e9e9e', '#a8a8a8', '#b2b2b2', '#bcbcbc', '#c6c6c6', '#d0d0d0', '#dadada', '#e4e4e4', '#eeeeee'
|
||||
];
|
||||
|
||||
@@ -11,15 +11,19 @@ import { acquireCharAtlas } from './atlas/CharAtlasCache';
|
||||
import { WebglCharAtlas } from './atlas/WebglCharAtlas';
|
||||
import { RectangleRenderer } from './RectangleRenderer';
|
||||
import { IWebGL2RenderingContext } from './Types';
|
||||
import { RenderModel, COMBINED_CHAR_BIT_MASK, RENDER_MODEL_BG_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_INDICIES_PER_CELL } from './RenderModel';
|
||||
import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants';
|
||||
import { RenderModel, COMBINED_CHAR_BIT_MASK } from './RenderModel';
|
||||
import { Disposable } from 'common/Lifecycle';
|
||||
import { NULL_CELL_CODE } from 'common/buffer/Constants';
|
||||
import { Terminal, IEvent } from 'xterm';
|
||||
import { DEFAULT_COLOR, CHAR_DATA_CHAR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_ATTR_INDEX, NULL_CELL_CODE } from 'common/buffer/Constants';
|
||||
import { Terminal } from 'xterm';
|
||||
import { getLuminance } from './ColorUtils';
|
||||
import { IRenderLayer } from './renderLayer/Types';
|
||||
import { IRenderDimensions, IRenderer, IRequestRefreshRowsEvent } from 'browser/renderer/Types';
|
||||
import { IRenderDimensions, IRenderer } from 'browser/renderer/Types';
|
||||
import { IColorSet } from 'browser/Types';
|
||||
import { EventEmitter } from 'common/EventEmitter';
|
||||
import { CellData } from 'common/buffer/CellData';
|
||||
import { FLAGS } from './Constants';
|
||||
import { getCompatAttr } from './CharDataCompat';
|
||||
|
||||
export const INDICIES_PER_CELL = 4;
|
||||
|
||||
export class WebglRenderer extends Disposable implements IRenderer {
|
||||
private _renderLayers: IRenderLayer[];
|
||||
@@ -27,7 +31,6 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
private _devicePixelRatio: number;
|
||||
|
||||
private _model: RenderModel = new RenderModel();
|
||||
private _workCell: CellData = new CellData();
|
||||
|
||||
private _canvas: HTMLCanvasElement;
|
||||
private _gl: IWebGL2RenderingContext;
|
||||
@@ -37,10 +40,6 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
public dimensions: IRenderDimensions;
|
||||
|
||||
private _core: ITerminal;
|
||||
private _isAttached: boolean;
|
||||
|
||||
private _onRequestRefreshRows = new EventEmitter<IRequestRefreshRowsEvent>();
|
||||
public get onRequestRefreshRows(): IEvent<IRequestRefreshRowsEvent> { return this._onRequestRefreshRows.event; }
|
||||
|
||||
constructor(
|
||||
private _terminal: Terminal,
|
||||
@@ -51,9 +50,11 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
|
||||
this._core = (<any>this._terminal)._core;
|
||||
|
||||
this._applyBgLuminanceBasedSelection();
|
||||
|
||||
this._renderLayers = [
|
||||
new LinkRenderLayer(this._core.screenElement, 2, this._colors, this._core),
|
||||
new CursorRenderLayer(this._core.screenElement, 3, this._colors, this._onRequestRefreshRows)
|
||||
new CursorRenderLayer(this._core.screenElement, 3, this._colors)
|
||||
];
|
||||
this.dimensions = {
|
||||
scaledCharWidth: 0,
|
||||
@@ -90,8 +91,6 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
|
||||
// Update dimensions and acquire char atlas
|
||||
this.onCharSizeChanged();
|
||||
|
||||
this._isAttached = document.body.contains(this._core.screenElement);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
@@ -100,8 +99,20 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
private _applyBgLuminanceBasedSelection(): void {
|
||||
// HACK: This is needed until webgl renderer adds support for selection colors
|
||||
if (getLuminance(this._colors.background) > 0.5) {
|
||||
this._colors.selection = { css: '#000', rgba: 255 };
|
||||
} else {
|
||||
this._colors.selection = { css: '#fff', rgba: 4294967295 };
|
||||
}
|
||||
}
|
||||
|
||||
public setColors(colors: IColorSet): void {
|
||||
this._colors = colors;
|
||||
|
||||
this._applyBgLuminanceBasedSelection();
|
||||
|
||||
// Clear layers and force a full render
|
||||
this._renderLayers.forEach(l => {
|
||||
l.setColors(this._terminal, this._colors);
|
||||
@@ -174,7 +185,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
this._rectangleRenderer.updateSelection(this._model.selection, columnSelectMode);
|
||||
this._glyphRenderer.updateSelection(this._model, columnSelectMode);
|
||||
|
||||
this._onRequestRefreshRows.fire({ start: 0, end: this._terminal.rows - 1 });
|
||||
// TODO: #2102 Should this move to RenderCoordinator?
|
||||
this._core.refresh(0, this._terminal.rows - 1);
|
||||
}
|
||||
|
||||
public onCursorMove(): void {
|
||||
@@ -194,8 +206,6 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
*/
|
||||
private _refreshCharAtlas(): void {
|
||||
if (this.dimensions.scaledCharWidth <= 0 && this.dimensions.scaledCharHeight <= 0) {
|
||||
// Mark as not attached so char atlas gets refreshed on next render
|
||||
this._isAttached = false;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -221,16 +231,6 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
}
|
||||
|
||||
public renderRows(start: number, end: number): void {
|
||||
if (!this._isAttached) {
|
||||
if (document.body.contains(this._core.screenElement) && (<any>this._core)._charSizeService.width && (<any>this._core)._charSizeService.height) {
|
||||
this._updateDimensions();
|
||||
this._refreshCharAtlas();
|
||||
this._isAttached = true;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Update render layers
|
||||
this._renderLayers.forEach(l => l.onGridChanged(this._terminal, start, end));
|
||||
|
||||
@@ -255,34 +255,52 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
const line = terminal.buffer.lines.get(row)!;
|
||||
this._model.lineLengths[y] = 0;
|
||||
for (let x = 0; x < terminal.cols; x++) {
|
||||
line.loadCell(x, this._workCell);
|
||||
|
||||
const chars = this._workCell.getChars();
|
||||
let code = this._workCell.getCode();
|
||||
const i = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL;
|
||||
const charData = line.get(x);
|
||||
const chars = charData[CHAR_DATA_CHAR_INDEX];
|
||||
let code = charData[CHAR_DATA_CODE_INDEX];
|
||||
const attr = getCompatAttr(line, x); // charData[CHAR_DATA_ATTR_INDEX];
|
||||
const i = ((y * terminal.cols) + x) * INDICIES_PER_CELL;
|
||||
|
||||
if (code !== NULL_CELL_CODE) {
|
||||
this._model.lineLengths[y] = x + 1;
|
||||
}
|
||||
|
||||
// Nothing has changed, no updates needed
|
||||
if (this._model.cells[i] === code &&
|
||||
this._model.cells[i + RENDER_MODEL_BG_OFFSET] === this._workCell.bg &&
|
||||
this._model.cells[i + RENDER_MODEL_FG_OFFSET] === this._workCell.fg) {
|
||||
if (this._model.cells[i] === code && this._model.cells[i + 1] === attr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Resolve bg and fg and cache in the model
|
||||
const flags = attr >> 18;
|
||||
let bg = attr & 0x1ff;
|
||||
let fg = (attr >> 9) & 0x1ff;
|
||||
|
||||
// If inverse flag is on, the foreground should become the background.
|
||||
if (flags & FLAGS.INVERSE) {
|
||||
const temp = bg;
|
||||
bg = fg;
|
||||
fg = temp;
|
||||
if (fg === DEFAULT_COLOR) {
|
||||
fg = INVERTED_DEFAULT_COLOR;
|
||||
}
|
||||
if (bg === DEFAULT_COLOR) {
|
||||
bg = INVERTED_DEFAULT_COLOR;
|
||||
}
|
||||
}
|
||||
const drawInBrightColor = terminal.options.drawBoldTextInBrightColors && !!(flags & FLAGS.BOLD) && fg < 8 && fg !== INVERTED_DEFAULT_COLOR;
|
||||
fg += drawInBrightColor ? 8 : 0;
|
||||
|
||||
// Flag combined chars with a bit mask so they're easily identifiable
|
||||
if (chars.length > 1) {
|
||||
code = code | COMBINED_CHAR_BIT_MASK;
|
||||
}
|
||||
|
||||
// Cache the results in the model
|
||||
this._model.cells[i] = code;
|
||||
this._model.cells[i + RENDER_MODEL_BG_OFFSET] = this._workCell.bg;
|
||||
this._model.cells[i + RENDER_MODEL_FG_OFFSET] = this._workCell.fg;
|
||||
this._model.cells[i ] = code;
|
||||
this._model.cells[i + 1] = attr;
|
||||
this._model.cells[i + 2] = bg;
|
||||
this._model.cells[i + 3] = fg;
|
||||
|
||||
this._glyphRenderer.updateCell(x, y, code, this._workCell.bg, this._workCell.fg, chars);
|
||||
this._glyphRenderer.updateCell(x, y, code, attr, bg, fg, chars);
|
||||
}
|
||||
}
|
||||
this._rectangleRenderer.updateBackgrounds(this._model);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import { ICharAtlasConfig } from './Types';
|
||||
import { Attributes } from 'common/buffer/Constants';
|
||||
import { DEFAULT_COLOR } from 'common/buffer/Constants';
|
||||
import { Terminal, FontWeight } from 'xterm';
|
||||
import { IColorSet, IColor } from 'browser/Types';
|
||||
|
||||
@@ -21,11 +21,9 @@ export function generateConfig(scaledCharWidth: number, scaledCharHeight: number
|
||||
cursor: NULL_COLOR,
|
||||
cursorAccent: NULL_COLOR,
|
||||
selection: NULL_COLOR,
|
||||
selectionOpaque: NULL_COLOR,
|
||||
// For the static char atlas, we only use the first 16 colors, but we need all 256 for the
|
||||
// dynamic character atlas.
|
||||
ansi: colors.ansi.slice(),
|
||||
contrastCache: colors.contrastCache
|
||||
ansi: colors.ansi.slice()
|
||||
};
|
||||
return {
|
||||
devicePixelRatio: window.devicePixelRatio,
|
||||
@@ -36,8 +34,6 @@ export function generateConfig(scaledCharWidth: number, scaledCharHeight: number
|
||||
fontWeight: terminal.getOption('fontWeight') as FontWeight,
|
||||
fontWeightBold: terminal.getOption('fontWeightBold') as FontWeight,
|
||||
allowTransparency: terminal.getOption('allowTransparency'),
|
||||
drawBoldTextInBrightColors: terminal.getOption('drawBoldTextInBrightColors'),
|
||||
minimumContrastRatio: terminal.getOption('minimumContrastRatio'),
|
||||
colors: clonedColors
|
||||
};
|
||||
}
|
||||
@@ -56,12 +52,10 @@ export function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean
|
||||
a.allowTransparency === b.allowTransparency &&
|
||||
a.scaledCharWidth === b.scaledCharWidth &&
|
||||
a.scaledCharHeight === b.scaledCharHeight &&
|
||||
a.drawBoldTextInBrightColors === b.drawBoldTextInBrightColors &&
|
||||
a.minimumContrastRatio === b.minimumContrastRatio &&
|
||||
a.colors.foreground === b.colors.foreground &&
|
||||
a.colors.background === b.colors.background;
|
||||
}
|
||||
|
||||
export function is256Color(colorCode: number): boolean {
|
||||
return (colorCode & Attributes.CM_MASK) === Attributes.CM_P16 || (colorCode & Attributes.CM_MASK) === Attributes.CM_P256;
|
||||
return colorCode < DEFAULT_COLOR;
|
||||
}
|
||||
|
||||
@@ -25,7 +25,5 @@ export interface ICharAtlasConfig {
|
||||
scaledCharWidth: number;
|
||||
scaledCharHeight: number;
|
||||
allowTransparency: boolean;
|
||||
drawBoldTextInBrightColors: boolean;
|
||||
minimumContrastRatio: number;
|
||||
colors: IColorSet;
|
||||
}
|
||||
|
||||
@@ -4,14 +4,14 @@
|
||||
*/
|
||||
|
||||
import { ICharAtlasConfig } from './Types';
|
||||
import { DIM_OPACITY } from 'browser/renderer/atlas/Constants';
|
||||
import { DIM_OPACITY, INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants';
|
||||
import { IRasterizedGlyph, IBoundingBox, IRasterizedGlyphSet } from '../Types';
|
||||
import { DEFAULT_COLOR, Attributes } from 'common/buffer/Constants';
|
||||
import { DEFAULT_COLOR, DEFAULT_ATTR } from 'common/buffer/Constants';
|
||||
import { is256Color } from './CharAtlasUtils';
|
||||
import { throwIfFalsy } from '../WebglUtils';
|
||||
import { IColor } from 'browser/Types';
|
||||
import { FLAGS } from '../Constants';
|
||||
import { IDisposable } from 'xterm';
|
||||
import { AttributeData } from 'common/buffer/AttributeData';
|
||||
import { toCss, ensureContrastRatioRgba } from 'browser/Color';
|
||||
|
||||
// In practice we're probably never going to exhaust a texture this large. For debugging purposes,
|
||||
// however, it can be useful to set this to a really tiny value, to verify that LRU eviction works.
|
||||
@@ -68,12 +68,8 @@ export class WebglCharAtlas implements IDisposable {
|
||||
public hasCanvasChanged = false;
|
||||
|
||||
private _workBoundingBox: IBoundingBox = { top: 0, left: 0, bottom: 0, right: 0 };
|
||||
private _workAttributeData: AttributeData = new AttributeData();
|
||||
|
||||
constructor(
|
||||
document: Document,
|
||||
private _config: ICharAtlasConfig
|
||||
) {
|
||||
constructor(document: Document, private _config: ICharAtlasConfig) {
|
||||
this.cacheCanvas = document.createElement('canvas');
|
||||
this.cacheCanvas.width = TEXTURE_WIDTH;
|
||||
this.cacheCanvas.height = TEXTURE_HEIGHT;
|
||||
@@ -107,11 +103,9 @@ export class WebglCharAtlas implements IDisposable {
|
||||
protected _doWarmUp(): void {
|
||||
// Pre-fill with ASCII 33-126
|
||||
for (let i = 33; i < 126; i++) {
|
||||
const rasterizedGlyph = this._drawToCache(i, DEFAULT_COLOR, DEFAULT_COLOR);
|
||||
const rasterizedGlyph = this._drawToCache(i, DEFAULT_ATTR, DEFAULT_COLOR, DEFAULT_COLOR);
|
||||
this._cacheMap[i] = {
|
||||
[DEFAULT_COLOR]: {
|
||||
[DEFAULT_COLOR]: rasterizedGlyph
|
||||
}
|
||||
[DEFAULT_ATTR]: rasterizedGlyph
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -129,23 +123,16 @@ export class WebglCharAtlas implements IDisposable {
|
||||
return false;
|
||||
}
|
||||
|
||||
public getRasterizedGlyphCombinedChar(chars: string, bg: number, fg: number): IRasterizedGlyph {
|
||||
public getRasterizedGlyphCombinedChar(chars: string, attr: number, bg: number, fg: number): IRasterizedGlyph {
|
||||
let rasterizedGlyphSet = this._cacheMapCombined[chars];
|
||||
if (!rasterizedGlyphSet) {
|
||||
rasterizedGlyphSet = {};
|
||||
this._cacheMapCombined[chars] = rasterizedGlyphSet;
|
||||
}
|
||||
let rasterizedGlyph: IRasterizedGlyph | undefined;
|
||||
const rasterizedGlyphSetBg = rasterizedGlyphSet[bg];
|
||||
if (rasterizedGlyphSetBg) {
|
||||
rasterizedGlyph = rasterizedGlyphSetBg[fg];
|
||||
}
|
||||
let rasterizedGlyph = rasterizedGlyphSet[attr];
|
||||
if (!rasterizedGlyph) {
|
||||
rasterizedGlyph = this._drawToCache(chars, bg, fg);
|
||||
if (!rasterizedGlyphSet[bg]) {
|
||||
rasterizedGlyphSet[bg] = {};
|
||||
}
|
||||
rasterizedGlyphSet[bg]![fg] = rasterizedGlyph;
|
||||
rasterizedGlyph = this._drawToCache(chars, attr, bg, fg);
|
||||
rasterizedGlyphSet[attr] = rasterizedGlyph;
|
||||
}
|
||||
return rasterizedGlyph;
|
||||
}
|
||||
@@ -153,23 +140,16 @@ export class WebglCharAtlas implements IDisposable {
|
||||
/**
|
||||
* Gets the glyphs texture coords, drawing the texture if it's not already
|
||||
*/
|
||||
public getRasterizedGlyph(code: number, bg: number, fg: number): IRasterizedGlyph {
|
||||
public getRasterizedGlyph(code: number, attr: number, bg: number, fg: number): IRasterizedGlyph {
|
||||
let rasterizedGlyphSet = this._cacheMap[code];
|
||||
if (!rasterizedGlyphSet) {
|
||||
rasterizedGlyphSet = {};
|
||||
this._cacheMap[code] = rasterizedGlyphSet;
|
||||
}
|
||||
let rasterizedGlyph: IRasterizedGlyph | undefined;
|
||||
const rasterizedGlyphSetBg = rasterizedGlyphSet[bg];
|
||||
if (rasterizedGlyphSetBg) {
|
||||
rasterizedGlyph = rasterizedGlyphSetBg[fg];
|
||||
}
|
||||
let rasterizedGlyph = rasterizedGlyphSet[attr];
|
||||
if (!rasterizedGlyph) {
|
||||
rasterizedGlyph = this._drawToCache(code, bg, fg);
|
||||
if (!rasterizedGlyphSet[bg]) {
|
||||
rasterizedGlyphSet[bg] = {};
|
||||
}
|
||||
rasterizedGlyphSet[bg]![fg] = rasterizedGlyph;
|
||||
rasterizedGlyph = this._drawToCache(code, attr, bg, fg);
|
||||
rasterizedGlyphSet[attr] = rasterizedGlyph;
|
||||
}
|
||||
return rasterizedGlyph;
|
||||
}
|
||||
@@ -181,160 +161,48 @@ export class WebglCharAtlas implements IDisposable {
|
||||
return this._config.colors.ansi[idx];
|
||||
}
|
||||
|
||||
private _getBackgroundColor(bgColorMode: number, bgColor: number, inverse: boolean): IColor {
|
||||
private _getBackgroundColor(bg: number): IColor {
|
||||
if (this._config.allowTransparency) {
|
||||
// The background color might have some transparency, so we need to render it as fully
|
||||
// transparent in the atlas. Otherwise we'd end up drawing the transparent background twice
|
||||
// around the anti-aliased edges of the glyph, and it would look too dark.
|
||||
return TRANSPARENT_COLOR;
|
||||
} else if (bg === INVERTED_DEFAULT_COLOR) {
|
||||
return this._config.colors.foreground;
|
||||
} else if (is256Color(bg)) {
|
||||
return this._getColorFromAnsiIndex(bg);
|
||||
}
|
||||
|
||||
switch (bgColorMode) {
|
||||
case Attributes.CM_P16:
|
||||
case Attributes.CM_P256:
|
||||
return this._getColorFromAnsiIndex(bgColor);
|
||||
case Attributes.CM_RGB:
|
||||
const arr = AttributeData.toColorRGB(bgColor);
|
||||
// TODO: This object creation is slow
|
||||
return {
|
||||
rgba: bgColor << 8,
|
||||
css: `#${toPaddedHex(arr[0])}${toPaddedHex(arr[1])}${toPaddedHex(arr[2])}`
|
||||
};
|
||||
case Attributes.CM_DEFAULT:
|
||||
default:
|
||||
if (inverse) {
|
||||
return this._config.colors.foreground;
|
||||
}
|
||||
return this._config.colors.background;
|
||||
}
|
||||
// TODO: Support true color
|
||||
return this._config.colors.background;
|
||||
}
|
||||
|
||||
private _getForegroundCss(bg: number, bgColorMode: number, bgColor: number, fg: number, fgColorMode: number, fgColor: number, inverse: boolean, bold: boolean): string {
|
||||
const minimumContrastCss = this._getMinimumContrastCss(bg, bgColorMode, bgColor, fg, fgColorMode, fgColor, inverse, bold);
|
||||
if (minimumContrastCss) {
|
||||
return minimumContrastCss;
|
||||
}
|
||||
|
||||
switch (fgColorMode) {
|
||||
case Attributes.CM_P16:
|
||||
case Attributes.CM_P256:
|
||||
if (this._config.drawBoldTextInBrightColors && bold && fgColor < 8) {
|
||||
fgColor += 8;
|
||||
}
|
||||
return this._getColorFromAnsiIndex(fgColor).css;
|
||||
case Attributes.CM_RGB:
|
||||
const arr = AttributeData.toColorRGB(fgColor);
|
||||
return toCss(arr[0], arr[1], arr[2]);
|
||||
case Attributes.CM_DEFAULT:
|
||||
default:
|
||||
if (inverse) {
|
||||
const bg = this._config.colors.background.css;
|
||||
if (bg.length === 9) {
|
||||
// Remove bg alpha channel if present
|
||||
return bg.substr(0, 7);
|
||||
}
|
||||
return bg;
|
||||
}
|
||||
return this._config.colors.foreground.css;
|
||||
private _getForegroundColor(fg: number): IColor {
|
||||
if (fg === INVERTED_DEFAULT_COLOR) {
|
||||
return this._config.colors.background;
|
||||
} else if (is256Color(fg)) {
|
||||
return this._getColorFromAnsiIndex(fg);
|
||||
}
|
||||
// TODO: Support true color
|
||||
return this._config.colors.foreground;
|
||||
}
|
||||
|
||||
private _resolveBackgroundRgba(bgColorMode: number, bgColor: number, inverse: boolean): number {
|
||||
switch (bgColorMode) {
|
||||
case Attributes.CM_P16:
|
||||
case Attributes.CM_P256:
|
||||
return this._getColorFromAnsiIndex(bgColor).rgba;
|
||||
case Attributes.CM_RGB:
|
||||
return bgColor << 8;
|
||||
case Attributes.CM_DEFAULT:
|
||||
default:
|
||||
if (inverse) {
|
||||
return this._config.colors.foreground.rgba;
|
||||
}
|
||||
return this._config.colors.background.rgba;
|
||||
}
|
||||
}
|
||||
|
||||
private _resolveForegroundRgba(fgColorMode: number, fgColor: number, inverse: boolean, bold: boolean): number {
|
||||
switch (fgColorMode) {
|
||||
case Attributes.CM_P16:
|
||||
case Attributes.CM_P256:
|
||||
if (this._config.drawBoldTextInBrightColors && bold && fgColor < 8) {
|
||||
fgColor += 8;
|
||||
}
|
||||
return this._getColorFromAnsiIndex(fgColor).rgba;
|
||||
case Attributes.CM_RGB:
|
||||
return fgColor << 8;
|
||||
case Attributes.CM_DEFAULT:
|
||||
default:
|
||||
if (inverse) {
|
||||
return this._config.colors.background.rgba;
|
||||
}
|
||||
return this._config.colors.foreground.rgba;
|
||||
}
|
||||
}
|
||||
|
||||
private _getMinimumContrastCss(bg: number, bgColorMode: number, bgColor: number, fg: number, fgColorMode: number, fgColor: number, inverse: boolean, bold: boolean): string | undefined {
|
||||
if (this._config.minimumContrastRatio === 1) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Try get from cache first
|
||||
const adjustedColor = this._config.colors.contrastCache.getCss(bg, fg);
|
||||
if (adjustedColor !== undefined) {
|
||||
return adjustedColor || undefined;
|
||||
}
|
||||
|
||||
const bgRgba = this._resolveBackgroundRgba(bgColorMode, bgColor, inverse);
|
||||
const fgRgba = this._resolveForegroundRgba(fgColorMode, fgColor, inverse, bold);
|
||||
const result = ensureContrastRatioRgba(bgRgba, fgRgba, this._config.minimumContrastRatio);
|
||||
|
||||
if (!result) {
|
||||
this._config.colors.contrastCache.setCss(bg, fg, null);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const css = toCss(
|
||||
(result >> 24) & 0xFF,
|
||||
(result >> 16) & 0xFF,
|
||||
(result >> 8) & 0xFF
|
||||
);
|
||||
this._config.colors.contrastCache.setCss(bg, fg, css);
|
||||
|
||||
return css;
|
||||
}
|
||||
|
||||
private _drawToCache(code: number, bg: number, fg: number): IRasterizedGlyph;
|
||||
private _drawToCache(chars: string, bg: number, fg: number): IRasterizedGlyph;
|
||||
private _drawToCache(codeOrChars: number | string, bg: number, fg: number): IRasterizedGlyph {
|
||||
private _drawToCache(code: number, attr: number, bg: number, fg: number): IRasterizedGlyph;
|
||||
private _drawToCache(chars: string, attr: number, bg: number, fg: number): IRasterizedGlyph;
|
||||
private _drawToCache(codeOrChars: number | string, attr: number, bg: number, fg: number): IRasterizedGlyph {
|
||||
const chars = typeof codeOrChars === 'number' ? String.fromCharCode(codeOrChars) : codeOrChars;
|
||||
|
||||
this.hasCanvasChanged = true;
|
||||
|
||||
const flags = attr >> 18;
|
||||
|
||||
const bold = !!(flags & FLAGS.BOLD);
|
||||
const dim = !!(flags & FLAGS.DIM);
|
||||
const italic = !!(flags & FLAGS.ITALIC);
|
||||
|
||||
this._tmpCtx.save();
|
||||
|
||||
this._workAttributeData.fg = fg;
|
||||
this._workAttributeData.bg = bg;
|
||||
|
||||
const bold = !!this._workAttributeData.isBold();
|
||||
const inverse = !!this._workAttributeData.isInverse();
|
||||
const dim = !!this._workAttributeData.isDim();
|
||||
const italic = !!this._workAttributeData.isItalic();
|
||||
let fgColor = this._workAttributeData.getFgColor();
|
||||
let fgColorMode = this._workAttributeData.getFgColorMode();
|
||||
let bgColor = this._workAttributeData.getBgColor();
|
||||
let bgColorMode = this._workAttributeData.getBgColorMode();
|
||||
if (inverse) {
|
||||
const temp = fgColor;
|
||||
fgColor = bgColor;
|
||||
bgColor = temp;
|
||||
const temp2 = fgColorMode;
|
||||
fgColorMode = bgColorMode;
|
||||
bgColorMode = temp2;
|
||||
}
|
||||
|
||||
// draw the background
|
||||
const backgroundColor = this._getBackgroundColor(bgColorMode, bgColor, inverse);
|
||||
const backgroundColor = this._getBackgroundColor(bg);
|
||||
// Use a 'copy' composite operation to clear any existing glyph out of _tmpCtxWithAlpha, regardless of
|
||||
// transparency in backgroundColor
|
||||
this._tmpCtx.globalCompositeOperation = 'copy';
|
||||
@@ -349,7 +217,7 @@ export class WebglCharAtlas implements IDisposable {
|
||||
`${fontStyle} ${fontWeight} ${this._config.fontSize * this._config.devicePixelRatio}px ${this._config.fontFamily}`;
|
||||
this._tmpCtx.textBaseline = 'top';
|
||||
|
||||
this._tmpCtx.fillStyle = this._getForegroundCss(bg, bgColorMode, bgColor, fg, fgColorMode, fgColor, inverse, bold);
|
||||
this._tmpCtx.fillStyle = this._getForegroundColor(fg).css;
|
||||
|
||||
// Apply alpha to dim the character
|
||||
if (dim) {
|
||||
@@ -530,8 +398,3 @@ function clearColor(imageData: ImageData, color: IColor): boolean {
|
||||
}
|
||||
return isEmpty;
|
||||
}
|
||||
|
||||
function toPaddedHex(c: number): string {
|
||||
const s = c.toString(16);
|
||||
return s.length < 2 ? '0' + s : s;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
|
||||
import { IRenderLayer } from './Types';
|
||||
import { IGlyphIdentifier } from '../atlas/Types';
|
||||
import { acquireCharAtlas } from '../atlas/CharAtlasCache';
|
||||
import { Terminal } from 'xterm';
|
||||
import { IColorSet } from 'browser/Types';
|
||||
@@ -24,6 +25,19 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
|
||||
protected _charAtlas: WebglCharAtlas | undefined;
|
||||
|
||||
/**
|
||||
* An object that's reused when drawing glyphs in order to reduce GC.
|
||||
*/
|
||||
private _currentGlyphIdentifier: IGlyphIdentifier = {
|
||||
chars: '',
|
||||
code: 0,
|
||||
bg: 0,
|
||||
fg: 0,
|
||||
bold: false,
|
||||
dim: false,
|
||||
italic: false
|
||||
};
|
||||
|
||||
constructor(
|
||||
private _container: HTMLElement,
|
||||
id: string,
|
||||
|
||||
@@ -8,8 +8,7 @@ import { BaseRenderLayer } from './BaseRenderLayer';
|
||||
import { ICellData } from 'common/Types';
|
||||
import { CellData } from 'common/buffer/CellData';
|
||||
import { IColorSet } from 'browser/Types';
|
||||
import { IRenderDimensions, IRequestRefreshRowsEvent } from 'browser/renderer/Types';
|
||||
import { IEventEmitter } from 'common/EventEmitter';
|
||||
import { IRenderDimensions } from 'browser/renderer/Types';
|
||||
|
||||
interface ICursorState {
|
||||
x: number;
|
||||
@@ -30,12 +29,7 @@ export class CursorRenderLayer extends BaseRenderLayer {
|
||||
private _cursorBlinkStateManager: CursorBlinkStateManager | undefined;
|
||||
private _cell: ICellData = new CellData();
|
||||
|
||||
constructor(
|
||||
container: HTMLElement,
|
||||
zIndex: number,
|
||||
colors: IColorSet,
|
||||
private _onRequestRefreshRowsEvent: IEventEmitter<IRequestRefreshRowsEvent>
|
||||
) {
|
||||
constructor(container: HTMLElement, zIndex: number, colors: IColorSet) {
|
||||
super(container, 'cursor', zIndex, true, colors);
|
||||
this._state = {
|
||||
x: 0,
|
||||
@@ -76,14 +70,14 @@ export class CursorRenderLayer extends BaseRenderLayer {
|
||||
if (this._cursorBlinkStateManager) {
|
||||
this._cursorBlinkStateManager.pause();
|
||||
}
|
||||
this._onRequestRefreshRowsEvent.fire({ start: terminal.buffer.cursorY, end: terminal.buffer.cursorY });
|
||||
terminal.refresh(terminal.buffer.cursorY, terminal.buffer.cursorY);
|
||||
}
|
||||
|
||||
public onFocus(terminal: Terminal): void {
|
||||
if (this._cursorBlinkStateManager) {
|
||||
this._cursorBlinkStateManager.resume(terminal);
|
||||
} else {
|
||||
this._onRequestRefreshRowsEvent.fire({ start: terminal.buffer.cursorY, end: terminal.buffer.cursorY });
|
||||
terminal.refresh(terminal.buffer.cursorY, terminal.buffer.cursorY);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,12 +89,13 @@ export class CursorRenderLayer extends BaseRenderLayer {
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this._cursorBlinkStateManager?.dispose();
|
||||
this._cursorBlinkStateManager = undefined;
|
||||
if (this._cursorBlinkStateManager) {
|
||||
this._cursorBlinkStateManager.dispose();
|
||||
}
|
||||
// Request a refresh from the terminal as management of rendering is being
|
||||
// moved back to the terminal
|
||||
terminal.refresh(terminal.buffer.cursorY, terminal.buffer.cursorY);
|
||||
}
|
||||
// Request a refresh from the terminal as management of rendering is being
|
||||
// moved back to the terminal
|
||||
this._onRequestRefreshRowsEvent.fire({ start: terminal.buffer.cursorY, end: terminal.buffer.cursorY });
|
||||
}
|
||||
|
||||
public onCursorMove(terminal: Terminal): void {
|
||||
@@ -120,7 +115,7 @@ export class CursorRenderLayer extends BaseRenderLayer {
|
||||
private _render(terminal: Terminal, triggeredByAnimationFrame: boolean): void {
|
||||
// Don't draw the cursor if it's hidden
|
||||
// TODO: Need to expose API for this
|
||||
if (!(terminal as any)._core._coreService.isCursorInitialized || (terminal as any)._core._coreService.isCursorHidden) {
|
||||
if (!(terminal as any)._core.cursorState || (terminal as any)._core.cursorHidden) {
|
||||
this._clearCursor();
|
||||
return;
|
||||
}
|
||||
@@ -144,17 +139,12 @@ export class CursorRenderLayer extends BaseRenderLayer {
|
||||
this._clearCursor();
|
||||
this._ctx.save();
|
||||
this._ctx.fillStyle = this._colors.cursor.css;
|
||||
const cursorStyle = terminal.getOption('cursorStyle');
|
||||
if (cursorStyle && cursorStyle !== 'block') {
|
||||
this._cursorRenderers[cursorStyle](terminal, terminal.buffer.cursorX, viewportRelativeCursorY, this._cell);
|
||||
} else {
|
||||
this._renderBlurCursor(terminal, terminal.buffer.cursorX, viewportRelativeCursorY, this._cell);
|
||||
}
|
||||
this._renderBlurCursor(terminal, terminal.buffer.cursorX, viewportRelativeCursorY, this._cell);
|
||||
this._ctx.restore();
|
||||
this._state.x = terminal.buffer.cursorX;
|
||||
this._state.y = viewportRelativeCursorY;
|
||||
this._state.isFocused = false;
|
||||
this._state.style = cursorStyle;
|
||||
this._state.style = terminal.getOption('cursorStyle');
|
||||
this._state.width = this._cell.getWidth();
|
||||
return;
|
||||
}
|
||||
|
||||
+36
-57
@@ -7,78 +7,59 @@ jobs:
|
||||
- job: Linux
|
||||
pool:
|
||||
vmImage: 'ubuntu-16.04'
|
||||
variables:
|
||||
PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: 1
|
||||
steps:
|
||||
- task: NodeTool@0
|
||||
inputs:
|
||||
versionSpec: '8.x'
|
||||
displayName: 'Install Node.js'
|
||||
- task: YarnInstaller@3
|
||||
- task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2
|
||||
inputs:
|
||||
versionSpec: '1.x'
|
||||
versionSpec: "1.9.4"
|
||||
displayName: 'Install Yarn'
|
||||
- task: CacheBeta@1
|
||||
inputs:
|
||||
key: yarn2 | $(Agent.OS) | yarn.lock
|
||||
path: node_modules
|
||||
displayName: Cache node modules
|
||||
- script: yarn --frozen-lockfile
|
||||
- script: |
|
||||
yarn
|
||||
displayName: 'Install dependencies and build'
|
||||
- script: |
|
||||
yarn test-unit-coverage --forbid-only
|
||||
./node_modules/.bin/nyc report --reporter=cobertura
|
||||
yarn test-unit
|
||||
displayName: 'Unit tests'
|
||||
- script: yarn lint
|
||||
- script: |
|
||||
yarn lint
|
||||
displayName: 'Lint'
|
||||
- task: PublishCodeCoverageResults@1
|
||||
inputs:
|
||||
codeCoverageTool: Cobertura
|
||||
summaryFileLocation: '$(System.DefaultWorkingDirectory)/**/*coverage.xml'
|
||||
displayName: 'Publish coverage'
|
||||
|
||||
- job: macOS
|
||||
pool:
|
||||
vmImage: 'xcode9-macos10.13'
|
||||
variables:
|
||||
PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: 1
|
||||
steps:
|
||||
- task: NodeTool@0
|
||||
inputs:
|
||||
versionSpec: '8.x'
|
||||
displayName: 'Install Node.js'
|
||||
- task: CacheBeta@1
|
||||
inputs:
|
||||
key: yarn2 | $(Agent.OS) | yarn.lock
|
||||
path: node_modules
|
||||
displayName: Cache node modules
|
||||
- script: yarn --frozen-lockfile
|
||||
- script: |
|
||||
yarn
|
||||
displayName: 'Install dependencies and build'
|
||||
- script: yarn test-unit --forbid-only
|
||||
- script: |
|
||||
yarn test-unit
|
||||
displayName: 'Unit tests'
|
||||
- script: yarn lint
|
||||
- script: |
|
||||
yarn lint
|
||||
displayName: 'Lint'
|
||||
|
||||
- job: Windows
|
||||
pool:
|
||||
vmImage: 'vs2017-win2016'
|
||||
variables:
|
||||
PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: 1
|
||||
steps:
|
||||
- task: NodeTool@0
|
||||
inputs:
|
||||
versionSpec: '8.x'
|
||||
displayName: 'Install Node.js'
|
||||
- task: CacheBeta@1
|
||||
inputs:
|
||||
key: yarn2 | $(Agent.OS) | yarn.lock
|
||||
path: node_modules
|
||||
displayName: Cache node modules
|
||||
- script: yarn --frozen-lockfile
|
||||
- script: |
|
||||
yarn
|
||||
displayName: 'Install dependencies and build'
|
||||
- script: yarn test-unit --forbid-only
|
||||
- script: |
|
||||
yarn test-unit
|
||||
displayName: 'Unit tests'
|
||||
- script: yarn lint
|
||||
- script: |
|
||||
yarn lint
|
||||
displayName: 'Lint'
|
||||
|
||||
- job: Linux_IntegrationTests
|
||||
@@ -89,16 +70,17 @@ jobs:
|
||||
inputs:
|
||||
versionSpec: '8.x'
|
||||
displayName: 'Install Node.js'
|
||||
- task: YarnInstaller@3
|
||||
- task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2
|
||||
inputs:
|
||||
versionSpec: '1.x'
|
||||
versionSpec: "1.9.4"
|
||||
displayName: 'Install Yarn'
|
||||
- script: yarn --frozen-lockfile
|
||||
- script: |
|
||||
yarn
|
||||
displayName: 'Install dependencies and build'
|
||||
- script: |
|
||||
yarn start &
|
||||
sleep 10
|
||||
yarn test-api --headless --forbid-only
|
||||
yarn test-api --headless
|
||||
displayName: 'Linux Integration tests'
|
||||
|
||||
- job: macOS_IntegrationTests
|
||||
@@ -109,12 +91,13 @@ jobs:
|
||||
inputs:
|
||||
versionSpec: '8.x'
|
||||
displayName: 'Install Node.js'
|
||||
- script: yarn --frozen-lockfile
|
||||
- script: |
|
||||
yarn
|
||||
displayName: 'Install dependencies and build'
|
||||
- script: |
|
||||
yarn start &
|
||||
sleep 10
|
||||
yarn test-api --headless --forbid-only
|
||||
yarn test-api --headless
|
||||
displayName: 'MacOS Integration tests'
|
||||
|
||||
- job: Release
|
||||
@@ -124,26 +107,22 @@ jobs:
|
||||
- Windows
|
||||
- Linux_IntegrationTests
|
||||
- macOS_IntegrationTests
|
||||
condition: and(succeeded(), or(eq(variables['Build.SourceBranch'], 'refs/heads/master'), eq(variables['FORCE_RELEASE'], 'true')))
|
||||
condition: and(succeeded(), or(eq(variables['Build.SourceBranch'], 'refs/heads/master'), eq(variables['Build.SourceBranch'], 'refs/heads/release/*')))
|
||||
pool:
|
||||
vmImage: 'ubuntu-16.04'
|
||||
variables:
|
||||
PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: 1
|
||||
steps:
|
||||
- task: NodeTool@0
|
||||
inputs:
|
||||
versionSpec: '8.x'
|
||||
displayName: 'Install Node.js'
|
||||
- task: YarnInstaller@3
|
||||
- task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2
|
||||
inputs:
|
||||
versionSpec: '1.x'
|
||||
versionSpec: "1.9.4"
|
||||
displayName: 'Install Yarn'
|
||||
- task: CacheBeta@1
|
||||
inputs:
|
||||
key: yarn2 | $(Agent.OS) | yarn.lock
|
||||
path: node_modules
|
||||
displayName: Cache node modules
|
||||
- script: yarn --frozen-lockfile
|
||||
- script: |
|
||||
yarn
|
||||
BUILD_DIR=dist npm run build
|
||||
displayName: 'Install dependencies and build'
|
||||
- script: NPM_AUTH_TOKEN="$(NPM_AUTH_TOKEN)" node ./bin/publish.js
|
||||
displayName: 'Package and publish to npm'
|
||||
- script: |
|
||||
NPM_AUTH_TOKEN="$(NPM_AUTH_TOKEN)" node ./bin/publish.js
|
||||
displayName: 'Publish to npm'
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*
|
||||
* Script to initialize addon packages under "addons/" with outer deps.
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
const cp = require('child_process');
|
||||
const fs = require('fs');
|
||||
|
||||
const PACKAGE_ROOT = path.join(__dirname, '..');
|
||||
|
||||
// install addon deps
|
||||
const addonsPath = path.join(PACKAGE_ROOT, 'addons');
|
||||
if (fs.existsSync(addonsPath)) {
|
||||
console.log('pulling addon dependencies...');
|
||||
|
||||
// whether to use yarn or npm
|
||||
let hasYarn = false;
|
||||
try {
|
||||
cp.execSync('yarn --version').toString();
|
||||
hasYarn = true;
|
||||
} catch(e) {}
|
||||
|
||||
// walk all addon folders
|
||||
fs.readdir(addonsPath, (err, files) => {
|
||||
files.forEach(folder => {
|
||||
const addonPath = path.join(addonsPath, folder);
|
||||
|
||||
// install only if there are dependencies listed
|
||||
let packageJson;
|
||||
try {
|
||||
packageJson = require(path.join(addonPath, 'package.json'));
|
||||
} catch (e) {
|
||||
// swallow as changing branches can leave folders around
|
||||
}
|
||||
if (packageJson
|
||||
&& (
|
||||
(packageJson.devDependencies && Object.keys(packageJson.devDependencies).length)
|
||||
|| (packageJson.dependencies && Object.keys(packageJson.dependencies).length)
|
||||
)
|
||||
)
|
||||
{
|
||||
console.log('Preparing', folder);
|
||||
if (hasYarn) {
|
||||
cp.execSync('yarn', {cwd: addonPath});
|
||||
} else {
|
||||
cp.execSync('npm install', {cwd: addonPath});
|
||||
}
|
||||
} else {
|
||||
console.log('Skipped', folder);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
+4
-22
@@ -5,7 +5,6 @@
|
||||
|
||||
const cp = require('child_process');
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
|
||||
// Setup auth
|
||||
@@ -19,9 +18,8 @@ if (isDryRun) {
|
||||
const changedFiles = getChangedFilesInCommit('HEAD');
|
||||
|
||||
// Publish xterm if any files were changed outside of the addons directory
|
||||
let isStableRelease = false;
|
||||
if (changedFiles.some(e => e.search(/^addons\//) === -1)) {
|
||||
isStableRelease = checkAndPublishPackage(path.resolve(__dirname, '..'));
|
||||
checkAndPublishPackage(path.resolve(__dirname, '..'));
|
||||
}
|
||||
|
||||
// Publish addons if any files were changed inside of the addon
|
||||
@@ -41,11 +39,6 @@ addonPackageDirs.forEach(p => {
|
||||
}
|
||||
});
|
||||
|
||||
// Publish website if it's a stable release
|
||||
if (isStableRelease) {
|
||||
updateWebsite();
|
||||
}
|
||||
|
||||
function checkAndPublishPackage(packageDir) {
|
||||
const packageJson = require(path.join(packageDir, 'package.json'));
|
||||
|
||||
@@ -83,8 +76,6 @@ function checkAndPublishPackage(packageDir) {
|
||||
}
|
||||
|
||||
console.groupEnd();
|
||||
|
||||
return isStableRelease;
|
||||
}
|
||||
|
||||
function getNextBetaVersion(packageJson) {
|
||||
@@ -97,7 +88,7 @@ function getNextBetaVersion(packageJson) {
|
||||
const nextStableVersion = `${stableVersion[0]}.${parseInt(stableVersion[1]) + 1}.0`;
|
||||
const publishedVersions = getPublishedVersions(packageJson, nextStableVersion, tag);
|
||||
if (publishedVersions.length === 0) {
|
||||
return `${nextStableVersion}-${tag}.1`;
|
||||
return `${nextStableVersion}-${tag}1`;
|
||||
}
|
||||
const latestPublishedVersion = publishedVersions.sort((a, b) => {
|
||||
const aVersion = parseInt(a.substr(a.search(/[0-9]+$/)));
|
||||
@@ -105,14 +96,14 @@ function getNextBetaVersion(packageJson) {
|
||||
return aVersion > bVersion ? -1 : 1;
|
||||
})[0];
|
||||
const latestTagVersion = parseInt(latestPublishedVersion.substr(latestPublishedVersion.search(/[0-9]+$/)), 10);
|
||||
return `${nextStableVersion}-${tag}.${latestTagVersion + 1}`;
|
||||
return `${nextStableVersion}-${tag}${latestTagVersion + 1}`;
|
||||
}
|
||||
|
||||
function getPublishedVersions(packageJson, version, tag) {
|
||||
const versionsProcess = cp.spawnSync('npm', ['view', packageJson.name, 'versions', '--json']);
|
||||
const versionsJson = JSON.parse(versionsProcess.stdout);
|
||||
if (tag) {
|
||||
return versionsJson.filter(v => !v.search(new RegExp(`${version}-${tag}.[0-9]+`)));
|
||||
return versionsJson.filter(v => !v.search(new RegExp(`${version}-${tag}[0-9]+`)));
|
||||
}
|
||||
return versionsJson;
|
||||
}
|
||||
@@ -124,12 +115,3 @@ function getChangedFilesInCommit(commit) {
|
||||
const changedFiles = output.split('\n').filter(e => e.length > 0);
|
||||
return changedFiles;
|
||||
}
|
||||
|
||||
function updateWebsite() {
|
||||
console.log('Updating website');
|
||||
const cwd = fs.mkdtempSync(path.join(os.tmpdir(), 'website-'));
|
||||
const packageJson = require(path.join(path.resolve(__dirname, '..'), 'package.json'));
|
||||
if (!isDryRun) {
|
||||
cp.spawnSync('sh', [path.join(__dirname, 'update-website.sh'), packageJson.version], { cwd, stdio: [process.stdin, process.stdout, process.stderr] });
|
||||
}
|
||||
}
|
||||
|
||||
+6
-39
@@ -6,8 +6,6 @@
|
||||
const cp = require('child_process');
|
||||
const path = require('path');
|
||||
|
||||
const COVERAGE_LINES_THRESHOLD = 60;
|
||||
|
||||
// Add `out` to the NODE_PATH so absolute paths can be resolved.
|
||||
const env = { ...process.env };
|
||||
env.NODE_PATH = path.resolve(__dirname, '../out');
|
||||
@@ -17,42 +15,15 @@ let testFiles = [
|
||||
'./out/**/*test.js'
|
||||
];
|
||||
|
||||
let flagArgs = [];
|
||||
|
||||
// ability to inject particular test files via
|
||||
// yarn test [testFileA testFileB ...]
|
||||
if (process.argv.length > 2) {
|
||||
const args = process.argv.slice(2);
|
||||
flagArgs = args.filter(e => e.startsWith('--'));
|
||||
// ability to inject particular test files via
|
||||
// yarn test [testFileA testFileB ...]
|
||||
files = args.filter(e => !e.startsWith('--'));
|
||||
if (files.length) {
|
||||
testFiles = files;
|
||||
}
|
||||
}
|
||||
|
||||
const checkCoverage = flagArgs.indexOf('--coverage') >= 0;
|
||||
|
||||
if (checkCoverage) {
|
||||
flagArgs.splice(flagArgs.indexOf('--coverage'), 1);
|
||||
const executable = npmBinScript('nyc');
|
||||
const args = ['--check-coverage', `--lines=${COVERAGE_LINES_THRESHOLD}`, npmBinScript('mocha'), ...testFiles, ...flagArgs];
|
||||
console.info('executable', executable);
|
||||
console.info('args', args);
|
||||
const run = cp.spawnSync(
|
||||
executable,
|
||||
args,
|
||||
{
|
||||
cwd: path.resolve(__dirname, '..'),
|
||||
env,
|
||||
stdio: 'inherit'
|
||||
}
|
||||
);
|
||||
process.exit(run.status);
|
||||
testFiles = process.argv.slice(2);
|
||||
}
|
||||
|
||||
const run = cp.spawnSync(
|
||||
npmBinScript('mocha'),
|
||||
[...testFiles, ...flagArgs],
|
||||
path.resolve(__dirname, '../node_modules/.bin/mocha'),
|
||||
testFiles,
|
||||
{
|
||||
cwd: path.resolve(__dirname, '..'),
|
||||
env,
|
||||
@@ -60,8 +31,4 @@ const run = cp.spawnSync(
|
||||
}
|
||||
);
|
||||
|
||||
function npmBinScript(script) {
|
||||
return path.resolve(__dirname, `../node_modules/.bin/` + (process.platform === 'win32' ? `${script}.cmd` : script));
|
||||
}
|
||||
|
||||
process.exit(run.status);
|
||||
process.exit(run.status);
|
||||
@@ -1,41 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Name the arguments
|
||||
VERSION=$1
|
||||
|
||||
# Clone docs repo and update the documentation
|
||||
git clone https://github.com/xtermjs/xtermjs.org
|
||||
cd xtermjs.org
|
||||
yarn
|
||||
./bin/update-docs
|
||||
|
||||
# Add changes to index and only proceed if there are changes to commit
|
||||
touch test-file
|
||||
git add .
|
||||
if ! git diff-index --quiet HEAD --; then
|
||||
|
||||
# Delete the upstream branch if it exists for some reason
|
||||
export BRANCH_NAME=update-$VERSION
|
||||
git branch -D $BRANCH_NAME || true
|
||||
git push origin :$BRANCH_NAME || true
|
||||
|
||||
# Create commit and push it to update-x.y.z
|
||||
git checkout -b $BRANCH_NAME
|
||||
git config --global user.name Daniel Imms
|
||||
git config --global user.email tyriar@tyriar.com
|
||||
git commit -m 'Update docs for v$VERSION'
|
||||
git push --set-upstream origin update-4.2.0
|
||||
git push -f
|
||||
|
||||
# Create a PR in the GitHub repo
|
||||
curl \
|
||||
-H "Authorization: token $GITHUB_TOKEN" \
|
||||
-X POST \
|
||||
-d "{\"title\":\"Update docs for v$VERSION\",\"base\":\"master\",\"head\":\"xtermjs:$BRANCH_NAME\"}" \
|
||||
https://api.github.com/repos/xtermjs/xtermjs.org/pulls
|
||||
|
||||
else
|
||||
|
||||
echo "No changes to commit"
|
||||
|
||||
fi
|
||||
+22
-79
@@ -39,39 +39,13 @@ export interface IWindowWithTerminal extends Window {
|
||||
declare let window: IWindowWithTerminal;
|
||||
|
||||
let term;
|
||||
let fitAddon: FitAddon;
|
||||
let searchAddon: SearchAddon;
|
||||
let protocol;
|
||||
let socketURL;
|
||||
let socket;
|
||||
let pid;
|
||||
|
||||
type AddonType = 'attach' | 'fit' | 'search' | 'web-links' | 'webgl';
|
||||
|
||||
interface IDemoAddon<T extends AddonType> {
|
||||
name: T;
|
||||
canChange: boolean;
|
||||
ctor:
|
||||
T extends 'attach' ? typeof AttachAddon :
|
||||
T extends 'fit' ? typeof FitAddon :
|
||||
T extends 'search' ? typeof SearchAddon :
|
||||
T extends 'web-links' ? typeof WebLinksAddon :
|
||||
typeof WebglAddon;
|
||||
instance?:
|
||||
T extends 'attach' ? AttachAddon :
|
||||
T extends 'fit' ? FitAddon :
|
||||
T extends 'search' ? SearchAddon :
|
||||
T extends 'web-links' ? WebLinksAddon :
|
||||
T extends 'webgl' ? WebglAddon :
|
||||
never;
|
||||
}
|
||||
|
||||
const addons: { [T in AddonType]: IDemoAddon<T>} = {
|
||||
attach: { name: 'attach', ctor: AttachAddon, canChange: false },
|
||||
fit: { name: 'fit', ctor: FitAddon, canChange: false },
|
||||
search: { name: 'search', ctor: SearchAddon, canChange: true },
|
||||
'web-links': { name: 'web-links', ctor: WebLinksAddon, canChange: true },
|
||||
webgl: { name: 'webgl', ctor: WebglAddon, canChange: true }
|
||||
};
|
||||
|
||||
const terminalContainer = document.getElementById('terminal-container');
|
||||
const actionElements = {
|
||||
findNext: <HTMLInputElement>document.querySelector('#find-next'),
|
||||
@@ -118,6 +92,7 @@ if (document.location.pathname === '/test') {
|
||||
} else {
|
||||
createTerminal();
|
||||
document.getElementById('dispose').addEventListener('click', disposeRecreateButtonHandler);
|
||||
document.getElementById('webgl').addEventListener('click', () => term.loadAddon(new WebglAddon()));
|
||||
}
|
||||
|
||||
function createTerminal(): void {
|
||||
@@ -133,12 +108,11 @@ function createTerminal(): void {
|
||||
|
||||
// Load addons
|
||||
const typedTerm = term as TerminalType;
|
||||
addons['web-links'].instance = new WebLinksAddon();
|
||||
addons.search.instance = new SearchAddon();
|
||||
addons.fit.instance = new FitAddon();
|
||||
typedTerm.loadAddon(addons['web-links'].instance);
|
||||
typedTerm.loadAddon(addons.search.instance);
|
||||
typedTerm.loadAddon(addons.fit.instance);
|
||||
typedTerm.loadAddon(new WebLinksAddon());
|
||||
searchAddon = new SearchAddon();
|
||||
typedTerm.loadAddon(searchAddon);
|
||||
fitAddon = new FitAddon();
|
||||
typedTerm.loadAddon(fitAddon);
|
||||
|
||||
window.term = term; // Expose `term` to window for debugging purposes
|
||||
term.onResize((size: { cols: number, rows: number }) => {
|
||||
@@ -155,17 +129,17 @@ function createTerminal(): void {
|
||||
socketURL = protocol + location.hostname + ((location.port) ? (':' + location.port) : '') + '/terminals/';
|
||||
|
||||
term.open(terminalContainer);
|
||||
addons.fit.instance!.fit();
|
||||
fitAddon.fit();
|
||||
term.focus();
|
||||
|
||||
addDomListener(paddingElement, 'change', setPadding);
|
||||
|
||||
addDomListener(actionElements.findNext, 'keyup', (e) => {
|
||||
addons.search.instance.findNext(actionElements.findNext.value, getSearchOptions(e));
|
||||
searchAddon.findNext(actionElements.findNext.value, getSearchOptions(e));
|
||||
});
|
||||
|
||||
addDomListener(actionElements.findPrevious, 'keyup', (e) => {
|
||||
addons.search.instance.findPrevious(actionElements.findPrevious.value, getSearchOptions(e));
|
||||
searchAddon.findPrevious(actionElements.findPrevious.value, getSearchOptions(e));
|
||||
});
|
||||
|
||||
// fit is called within a setTimeout, cols and rows need this.
|
||||
@@ -193,10 +167,15 @@ function createTerminal(): void {
|
||||
}
|
||||
|
||||
function runRealTerminal(): void {
|
||||
addons.attach.instance = new AttachAddon(socket);
|
||||
term.loadAddon(addons.attach.instance);
|
||||
/**
|
||||
* The demo defaults to string transport by default.
|
||||
* To run it with UTF8 binary transport, swap comment on
|
||||
* the lines below. (Must also be switched in server.js)
|
||||
*/
|
||||
term.loadAddon(new AttachAddon(socket));
|
||||
// term.loadAddon(new AttachAddon(socket, {inputUtf8: true}));
|
||||
|
||||
term._initialized = true;
|
||||
initAddons(term);
|
||||
}
|
||||
|
||||
function runFakeTerminal(): void {
|
||||
@@ -205,7 +184,6 @@ function runFakeTerminal(): void {
|
||||
}
|
||||
|
||||
term._initialized = true;
|
||||
initAddons(term);
|
||||
|
||||
term.prompt = () => {
|
||||
term.write('\r\n$ ');
|
||||
@@ -250,7 +228,6 @@ function initOptions(term: TerminalType): void {
|
||||
bellSound: null,
|
||||
bellStyle: ['none', 'sound'],
|
||||
cursorStyle: ['block', 'underline', 'bar'],
|
||||
fastScrollModifier: ['alt', 'ctrl', 'shift', undefined],
|
||||
fontFamily: null,
|
||||
fontWeight: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'],
|
||||
fontWeightBold: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'],
|
||||
@@ -283,7 +260,7 @@ function initOptions(term: TerminalType): void {
|
||||
});
|
||||
html += '</div><div class="option-group">';
|
||||
numberOptions.forEach(o => {
|
||||
html += `<div class="option"><label>${o} <input id="opt-${o}" type="number" value="${term.getOption(o)}" step="${o === 'lineHeight' || o === 'scrollSensitivity' ? '0.1' : '1'}"/></label></div>`;
|
||||
html += `<div class="option"><label>${o} <input id="opt-${o}" type="number" value="${term.getOption(o)}" step="${o === 'lineHeight' ? '0.1' : '1'}"/></label></div>`;
|
||||
});
|
||||
html += '</div><div class="option-group">';
|
||||
Object.keys(stringOptions).forEach(o => {
|
||||
@@ -312,7 +289,7 @@ function initOptions(term: TerminalType): void {
|
||||
console.log('change', o, input.value);
|
||||
if (o === 'cols' || o === 'rows') {
|
||||
updateTerminalSize();
|
||||
} else if (o === 'lineHeight' || o === 'scrollSensitivity') {
|
||||
} else if (o === 'lineHeight') {
|
||||
term.setOption(o, parseFloat(input.value));
|
||||
updateTerminalSize();
|
||||
} else {
|
||||
@@ -329,40 +306,6 @@ function initOptions(term: TerminalType): void {
|
||||
});
|
||||
}
|
||||
|
||||
function initAddons(term: TerminalType): void {
|
||||
const fragment = document.createDocumentFragment();
|
||||
Object.keys(addons).forEach((name: AddonType) => {
|
||||
const addon = addons[name];
|
||||
const checkbox = document.createElement('input') as HTMLInputElement;
|
||||
checkbox.type = 'checkbox';
|
||||
checkbox.checked = !!addon.instance;
|
||||
if (!addon.canChange) {
|
||||
checkbox.disabled = true;
|
||||
}
|
||||
checkbox.addEventListener('change', () => {
|
||||
if (checkbox.checked) {
|
||||
addon.instance = new addon.ctor();
|
||||
term.loadAddon(addon.instance);
|
||||
} else {
|
||||
addon.instance!.dispose();
|
||||
addon.instance = undefined;
|
||||
}
|
||||
});
|
||||
const label = document.createElement('label');
|
||||
label.classList.add('addon');
|
||||
if (!addon.canChange) {
|
||||
label.title = 'This addon is needed for the demo to operate';
|
||||
}
|
||||
label.appendChild(checkbox);
|
||||
label.appendChild(document.createTextNode(name));
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.classList.add('addon');
|
||||
wrapper.appendChild(label);
|
||||
fragment.appendChild(wrapper);
|
||||
});
|
||||
document.getElementById('addons-container').appendChild(fragment);
|
||||
}
|
||||
|
||||
function addDomListener(element: HTMLElement, type: string, handler: (...args: any[]) => any): void {
|
||||
element.addEventListener(type, handler);
|
||||
term._core.register({ dispose: () => element.removeEventListener(type, handler) });
|
||||
@@ -375,5 +318,5 @@ function updateTerminalSize(): void {
|
||||
const height = (rows * term._core._renderService.dimensions.actualCellHeight).toString() + 'px';
|
||||
terminalContainer.style.width = width;
|
||||
terminalContainer.style.height = height;
|
||||
addons.fit.instance.fit();
|
||||
fitAddon.fit();
|
||||
}
|
||||
|
||||
+1
-5
@@ -26,11 +26,6 @@
|
||||
<p>These options can be set in the <code>Terminal</code> constructor or using the <code>Terminal.setOption</code> function.</p>
|
||||
<div id="options-container"></div>
|
||||
</div>
|
||||
<div>
|
||||
<h3>Addons</h3>
|
||||
<p>Addons can be loaded and unloaded on a particular terminal to extend its functionality.</p>
|
||||
<div id="addons-container"></div>
|
||||
</div>
|
||||
<div>
|
||||
<h3>Style</h3>
|
||||
<div style="display: inline-block; margin-right: 16px;">
|
||||
@@ -41,6 +36,7 @@
|
||||
<hr/>
|
||||
<p><strong>Attention:</strong> The demo is a barebones implementation and is designed for the development and evaluation of xterm.js only. Exposing the demo to the public as is would introduce security risks for the host.</p>
|
||||
<button id="dispose" title="This is used to testing memory leaks">Dispose terminal</button>
|
||||
<button id="webgl">Enable WebGL Renderer</button>
|
||||
<script src="dist/client-bundle.js" defer ></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+14
-12
@@ -3,8 +3,12 @@ var expressWs = require('express-ws');
|
||||
var os = require('os');
|
||||
var pty = require('node-pty');
|
||||
|
||||
// Whether to use binary transport.
|
||||
const USE_BINARY = os.platform() !== "win32";
|
||||
/**
|
||||
* Whether to use UTF8 binary transport.
|
||||
* (Must also be switched in client.ts)
|
||||
*/
|
||||
const USE_BINARY_UTF8 = false;
|
||||
|
||||
|
||||
function startServer() {
|
||||
var app = express();
|
||||
@@ -14,26 +18,24 @@ function startServer() {
|
||||
logs = {};
|
||||
|
||||
app.use('/xterm.css', express.static(__dirname + '/../css/xterm.css'));
|
||||
app.get('/logo.png', (req, res) => { // lgtm [js/missing-rate-limiting]
|
||||
res.sendFile(__dirname + '/logo.png');
|
||||
});
|
||||
app.get('/logo.png', (req, res) => res.sendFile(__dirname + '/logo.png'));
|
||||
|
||||
app.get('/', (req, res) => { // lgtm [js/missing-rate-limiting]
|
||||
app.get('/', function(req, res){
|
||||
res.sendFile(__dirname + '/index.html');
|
||||
});
|
||||
|
||||
app.get('/test', (req, res) => { // lgtm [js/missing-rate-limiting]
|
||||
app.get('/test', function(req, res){
|
||||
res.sendFile(__dirname + '/test.html');
|
||||
});
|
||||
|
||||
app.get('/style.css', (req, res) => { // lgtm [js/missing-rate-limiting]
|
||||
app.get('/style.css', function(req, res){
|
||||
res.sendFile(__dirname + '/style.css');
|
||||
});
|
||||
|
||||
app.use('/dist', express.static(__dirname + '/dist'));
|
||||
app.use('/src', express.static(__dirname + '/src'));
|
||||
|
||||
app.post('/terminals', (req, res) => {
|
||||
app.post('/terminals', function (req, res) {
|
||||
const env = Object.assign({}, process.env);
|
||||
env['COLORTERM'] = 'truecolor';
|
||||
var cols = parseInt(req.query.cols),
|
||||
@@ -44,7 +46,7 @@ function startServer() {
|
||||
rows: rows || 24,
|
||||
cwd: env.PWD,
|
||||
env: env,
|
||||
encoding: USE_BINARY ? null : 'utf8'
|
||||
encoding: USE_BINARY_UTF8 ? null : 'utf8'
|
||||
});
|
||||
|
||||
console.log('Created terminal with PID: ' + term.pid);
|
||||
@@ -57,7 +59,7 @@ function startServer() {
|
||||
res.end();
|
||||
});
|
||||
|
||||
app.post('/terminals/:pid/size', (req, res) => {
|
||||
app.post('/terminals/:pid/size', function (req, res) {
|
||||
var pid = parseInt(req.params.pid),
|
||||
cols = parseInt(req.query.cols),
|
||||
rows = parseInt(req.query.rows),
|
||||
@@ -106,7 +108,7 @@ function startServer() {
|
||||
}
|
||||
};
|
||||
}
|
||||
const send = USE_BINARY ? bufferUtf8(ws, 5) : buffer(ws, 5);
|
||||
const send = USE_BINARY_UTF8 ? bufferUtf8(ws, 5) : buffer(ws, 5);
|
||||
|
||||
term.on('data', function(data) {
|
||||
try {
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB[10;15r[5;5H[5 A[12;12H[5 A[20;20H[5 A
|
||||
@@ -1,25 +0,0 @@
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvw
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvw
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvw
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvw
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvw
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvw
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
@@ -1,25 +0,0 @@
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB[10;15r[5;5H[5 @[12;12H[5 @[20;20H[5 @
|
||||
@@ -1,25 +0,0 @@
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
fghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
fghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
fghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
fghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
fghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
fghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
@@ -1,25 +0,0 @@
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB[10;15r[5;5H[5'}[12;12H[5'}[20;20H[5'}
|
||||
@@ -1,25 +0,0 @@
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijk lmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvw
|
||||
abcdefghijk lmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvw
|
||||
abcdefghijk lmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvw
|
||||
abcdefghijk lmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvw
|
||||
abcdefghijk lmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvw
|
||||
abcdefghijk lmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvw
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
@@ -1,25 +0,0 @@
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB[10;15r[5;5H[5'~[12;12H[5'~[20;20H[5'~
|
||||
@@ -1,25 +0,0 @@
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijkqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijkqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijkqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijkqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijkqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijkqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
|
||||
+4
-10
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "xterm",
|
||||
"description": "Full xterm terminal, in your browser",
|
||||
"version": "4.3.0",
|
||||
"version": "4.0.2",
|
||||
"main": "lib/xterm.js",
|
||||
"style": "css/xterm.css",
|
||||
"types": "typings/xterm.d.ts",
|
||||
@@ -11,16 +11,13 @@
|
||||
"prepackage": "npm run build",
|
||||
"package": "webpack",
|
||||
"start": "node demo/start",
|
||||
"lint": "tslint 'src/**/*.ts' 'addons/*/src/**/*.ts'",
|
||||
"lint": "tslint 'src/**/*.ts' 'addons/**/*.ts'",
|
||||
"test": "npm run test-unit",
|
||||
"posttest": "npm run lint",
|
||||
"test-api": "mocha \"**/*.api.js\"",
|
||||
"test-unit": "node ./bin/test.js",
|
||||
"test-unit-coverage": "node ./bin/test.js --coverage",
|
||||
"build": "tsc -b ./tsconfig.all.json",
|
||||
"prepare": "npm run setup",
|
||||
"setup": "npm run build",
|
||||
"presetup": "node ./bin/install-addons.js",
|
||||
"prepare": "npm run build",
|
||||
"prepublishOnly": "npm run package",
|
||||
"watch": "tsc -b -w ./tsconfig.all.json --preserveWatchOutput",
|
||||
"benchmark": "NODE_PATH=./out xterm-benchmark -r 5 -c test/benchmark/benchmark.json",
|
||||
@@ -30,7 +27,6 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/chai": "^3.4.34",
|
||||
"@types/deep-equal": "^1.0.1",
|
||||
"@types/glob": "^5.0.35",
|
||||
"@types/jsdom": "11.0.1",
|
||||
"@types/mocha": "^2.2.33",
|
||||
@@ -40,20 +36,18 @@
|
||||
"@types/webpack": "^4.4.11",
|
||||
"@types/ws": "^6.0.1",
|
||||
"chai": "3.5.0",
|
||||
"deep-equal": "^1.1.0",
|
||||
"express": "^4.17.1",
|
||||
"express-ws": "^4.0.0",
|
||||
"glob": "^7.0.5",
|
||||
"jsdom": "^11.11.0",
|
||||
"mocha": "^6.1.4",
|
||||
"node-pty": "0.7.6",
|
||||
"nyc": "13",
|
||||
"puppeteer": "^1.15.0",
|
||||
"source-map-loader": "^0.2.4",
|
||||
"ts-loader": "^6.0.4",
|
||||
"tslint": "^5.18.0",
|
||||
"tslint-consistent-codestyle": "^1.13.0",
|
||||
"typescript": "3.7",
|
||||
"typescript": "3.5",
|
||||
"utf8": "^3.0.0",
|
||||
"webpack": "^4.35.3",
|
||||
"webpack-cli": "^3.1.0",
|
||||
|
||||
@@ -25,14 +25,6 @@ function getCursor(term: TestTerminal): number[] {
|
||||
];
|
||||
}
|
||||
|
||||
function getLines(term: TestTerminal, limit: number = term.rows): string[] {
|
||||
const res: string[] = [];
|
||||
for (let i = 0; i < limit; ++i) {
|
||||
res.push(term.buffer.lines.get(i).translateToString(true));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
describe('InputHandler', () => {
|
||||
describe('save and restore cursor', () => {
|
||||
const terminal = new MockInputHandlingTerminal();
|
||||
@@ -1133,6 +1125,13 @@ describe('InputHandler', () => {
|
||||
beforeEach(() => {
|
||||
term = new TestTerminal({cols: 10, rows: 10});
|
||||
});
|
||||
function getLines(term: TestTerminal, limit: number = term.rows): string[] {
|
||||
const res: string[] = [];
|
||||
for (let i = 0; i < limit; ++i) {
|
||||
res.push(term.buffer.lines.get(i).translateToString(true));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
it('scrollUp', () => {
|
||||
term.writeSync('0\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\x1b[2;4r\x1b[2Sm');
|
||||
assert.deepEqual(getLines(term), ['m', '3', '', '', '4', '5', '6', '7', '8', '9']);
|
||||
@@ -1192,78 +1191,4 @@ describe('InputHandler', () => {
|
||||
assert.deepEqual(getLines(term), ['0', '1', 'n', 'm', '', '', '6', '7', '8', '9']);
|
||||
});
|
||||
});
|
||||
describe('SL/SR/DECIC/DECDC', () => {
|
||||
let term: TestTerminal;
|
||||
beforeEach(() => {
|
||||
term = new TestTerminal({cols: 5, rows: 5, scrollback: 1});
|
||||
});
|
||||
it('SL (scrollLeft)', () => {
|
||||
term.writeSync('12345'.repeat(6));
|
||||
term.writeSync('\x1b[ @');
|
||||
assert.deepEqual(getLines(term, term.rows + 1), ['12345', '2345', '2345', '2345', '2345', '2345']);
|
||||
term.writeSync('\x1b[0 @');
|
||||
assert.deepEqual(getLines(term, term.rows + 1), ['12345', '345', '345', '345', '345', '345']);
|
||||
term.writeSync('\x1b[2 @');
|
||||
assert.deepEqual(getLines(term, term.rows + 1), ['12345', '5', '5', '5', '5', '5']);
|
||||
});
|
||||
it('SR (scrollRight)', () => {
|
||||
term.writeSync('12345'.repeat(6));
|
||||
term.writeSync('\x1b[ A');
|
||||
assert.deepEqual(getLines(term, term.rows + 1), ['12345', ' 1234', ' 1234', ' 1234', ' 1234', ' 1234']);
|
||||
term.writeSync('\x1b[0 A');
|
||||
assert.deepEqual(getLines(term, term.rows + 1), ['12345', ' 123', ' 123', ' 123', ' 123', ' 123']);
|
||||
term.writeSync('\x1b[2 A');
|
||||
assert.deepEqual(getLines(term, term.rows + 1), ['12345', ' 1', ' 1', ' 1', ' 1', ' 1']);
|
||||
});
|
||||
it('insertColumns (DECIC)', () => {
|
||||
term.writeSync('12345'.repeat(6));
|
||||
term.writeSync('\x1b[3;3H');
|
||||
term.writeSync('\x1b[\'}');
|
||||
assert.deepEqual(getLines(term, term.rows + 1), ['12345', '12 34', '12 34', '12 34', '12 34', '12 34']);
|
||||
term.reset();
|
||||
term.writeSync('12345'.repeat(6));
|
||||
term.writeSync('\x1b[3;3H');
|
||||
term.writeSync('\x1b[1\'}');
|
||||
assert.deepEqual(getLines(term, term.rows + 1), ['12345', '12 34', '12 34', '12 34', '12 34', '12 34']);
|
||||
term.reset();
|
||||
term.writeSync('12345'.repeat(6));
|
||||
term.writeSync('\x1b[3;3H');
|
||||
term.writeSync('\x1b[2\'}');
|
||||
assert.deepEqual(getLines(term, term.rows + 1), ['12345', '12 3', '12 3', '12 3', '12 3', '12 3']);
|
||||
});
|
||||
it('deleteColumns (DECDC)', () => {
|
||||
term.writeSync('12345'.repeat(6));
|
||||
term.writeSync('\x1b[3;3H');
|
||||
term.writeSync('\x1b[\'~');
|
||||
assert.deepEqual(getLines(term, term.rows + 1), ['12345', '1245', '1245', '1245', '1245', '1245']);
|
||||
term.reset();
|
||||
term.writeSync('12345'.repeat(6));
|
||||
term.writeSync('\x1b[3;3H');
|
||||
term.writeSync('\x1b[1\'~');
|
||||
assert.deepEqual(getLines(term, term.rows + 1), ['12345', '1245', '1245', '1245', '1245', '1245']);
|
||||
term.reset();
|
||||
term.writeSync('12345'.repeat(6));
|
||||
term.writeSync('\x1b[3;3H');
|
||||
term.writeSync('\x1b[2\'~');
|
||||
assert.deepEqual(getLines(term, term.rows + 1), ['12345', '125', '125', '125', '125', '125']);
|
||||
});
|
||||
});
|
||||
it('should parse big chunks in smaller subchunks', () => {
|
||||
// max single chunk size is hardcoded as 131072
|
||||
const calls: any[] = [];
|
||||
const term = new TestTerminal({cols: 10, rows: 10});
|
||||
(term as any)._inputHandler._parser.parse = (data: Uint32Array, length: number) => {
|
||||
calls.push([data.length, length]);
|
||||
};
|
||||
term.writeSync('12345');
|
||||
term.writeSync('a'.repeat(10000));
|
||||
term.writeSync('a'.repeat(200000));
|
||||
term.writeSync('a'.repeat(300000));
|
||||
assert.deepEqual(calls, [
|
||||
[4096, 5],
|
||||
[10000, 10000],
|
||||
[131072, 131072], [131072, 200000 - 131072],
|
||||
[131072, 131072], [131072, 131072], [131072, 300000 - 131072 - 131072]
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
+42
-150
@@ -28,11 +28,6 @@ import { DcsHandler } from 'common/parser/DcsParser';
|
||||
*/
|
||||
const GLEVEL: {[key: string]: number} = {'(': 0, ')': 1, '*': 2, '+': 3, '-': 1, '.': 2};
|
||||
|
||||
/**
|
||||
* Max length of the UTF32 input buffer. Real memory consumption is 4 times higher.
|
||||
*/
|
||||
const MAX_PARSEBUFFER_LENGTH = 131072;
|
||||
|
||||
|
||||
/**
|
||||
* DCS subparser implementations
|
||||
@@ -180,9 +175,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* CSI handler
|
||||
*/
|
||||
this._parser.setCsiHandler({final: '@'}, params => this.insertChars(params));
|
||||
this._parser.setCsiHandler({intermediates: ' ', final: '@'}, params => this.scrollLeft(params));
|
||||
this._parser.setCsiHandler({final: 'A'}, params => this.cursorUp(params));
|
||||
this._parser.setCsiHandler({intermediates: ' ', final: 'A'}, params => this.scrollRight(params));
|
||||
this._parser.setCsiHandler({final: 'B'}, params => this.cursorDown(params));
|
||||
this._parser.setCsiHandler({final: 'C'}, params => this.cursorForward(params));
|
||||
this._parser.setCsiHandler({final: 'D'}, params => this.cursorBackward(params));
|
||||
@@ -223,8 +216,6 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
this._parser.setCsiHandler({final: 'r'}, params => this.setScrollRegion(params));
|
||||
this._parser.setCsiHandler({final: 's'}, params => this.saveCursor(params));
|
||||
this._parser.setCsiHandler({final: 'u'}, params => this.restoreCursor(params));
|
||||
this._parser.setCsiHandler({intermediates: '\'', final: '}'}, params => this.insertColumns(params));
|
||||
this._parser.setCsiHandler({intermediates: '\'', final: '~'}, params => this.deleteColumns(params));
|
||||
|
||||
/**
|
||||
* execute handler
|
||||
@@ -333,46 +324,40 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
public parse(data: string | Uint8Array): void {
|
||||
public parse(data: string): void {
|
||||
let buffer = this._bufferService.buffer;
|
||||
const cursorStartX = buffer.x;
|
||||
const cursorStartY = buffer.y;
|
||||
|
||||
this._logService.debug('parsing data', data);
|
||||
|
||||
// resize input buffer if needed
|
||||
if (this._parseBuffer.length < data.length) {
|
||||
if (this._parseBuffer.length < MAX_PARSEBUFFER_LENGTH) {
|
||||
this._parseBuffer = new Uint32Array(Math.min(data.length, MAX_PARSEBUFFER_LENGTH));
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the dirty row service so we know which lines changed as a result of parsing
|
||||
this._dirtyRowService.clearRange();
|
||||
|
||||
// process big data in smaller chunks
|
||||
if (data.length > MAX_PARSEBUFFER_LENGTH) {
|
||||
for (let i = 0; i < data.length; i += MAX_PARSEBUFFER_LENGTH) {
|
||||
const end = i + MAX_PARSEBUFFER_LENGTH < data.length ? i + MAX_PARSEBUFFER_LENGTH : data.length;
|
||||
const len = (typeof data === 'string')
|
||||
? this._stringDecoder.decode(data.substring(i, end), this._parseBuffer)
|
||||
: this._utf8Decoder.decode(data.subarray(i, end), this._parseBuffer);
|
||||
this._parser.parse(this._parseBuffer, len);
|
||||
}
|
||||
} else {
|
||||
const len = (typeof data === 'string')
|
||||
? this._stringDecoder.decode(data, this._parseBuffer)
|
||||
: this._utf8Decoder.decode(data, this._parseBuffer);
|
||||
this._parser.parse(this._parseBuffer, len);
|
||||
this._parseBuffer = new Uint32Array(data.length);
|
||||
}
|
||||
this._parser.parse(this._parseBuffer, this._stringDecoder.decode(data, this._parseBuffer));
|
||||
|
||||
buffer = this._bufferService.buffer;
|
||||
if (buffer.x !== cursorStartX || buffer.y !== cursorStartY) {
|
||||
this._onCursorMove.fire();
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh any dirty rows accumulated as part of parsing
|
||||
this._terminal.refresh(this._dirtyRowService.start, this._dirtyRowService.end);
|
||||
public parseUtf8(data: Uint8Array): void {
|
||||
let buffer = this._bufferService.buffer;
|
||||
const cursorStartX = buffer.x;
|
||||
const cursorStartY = buffer.y;
|
||||
|
||||
this._logService.debug('parsing data', data);
|
||||
|
||||
if (this._parseBuffer.length < data.length) {
|
||||
this._parseBuffer = new Uint32Array(data.length);
|
||||
}
|
||||
this._parser.parse(this._parseBuffer, this._utf8Decoder.decode(data, this._parseBuffer));
|
||||
|
||||
buffer = this._bufferService.buffer;
|
||||
if (buffer.x !== cursorStartX || buffer.y !== cursorStartY) {
|
||||
this._onCursorMove.fire();
|
||||
}
|
||||
}
|
||||
|
||||
public print(data: Uint32Array, start: number, end: number): void {
|
||||
@@ -546,7 +531,6 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
// make buffer local for faster access
|
||||
const buffer = this._bufferService.buffer;
|
||||
|
||||
this._dirtyRowService.markDirty(buffer.y);
|
||||
if (this._optionsService.options.convertEol) {
|
||||
buffer.x = 0;
|
||||
}
|
||||
@@ -561,7 +545,6 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
if (buffer.x >= this._bufferService.cols) {
|
||||
buffer.x--;
|
||||
}
|
||||
this._dirtyRowService.markDirty(buffer.y);
|
||||
|
||||
this._onLineFeed.fire();
|
||||
}
|
||||
@@ -626,14 +609,12 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
this._bufferService.buffer.y = this._terminal.originMode
|
||||
? Math.min(this._bufferService.buffer.scrollBottom, Math.max(this._bufferService.buffer.scrollTop, this._bufferService.buffer.y))
|
||||
: Math.min(this._bufferService.rows - 1, Math.max(0, this._bufferService.buffer.y));
|
||||
this._dirtyRowService.markDirty(this._bufferService.buffer.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set absolute cursor position.
|
||||
*/
|
||||
private _setCursor(x: number, y: number): void {
|
||||
this._dirtyRowService.markDirty(this._bufferService.buffer.y);
|
||||
if (this._terminal.originMode) {
|
||||
this._bufferService.buffer.x = x;
|
||||
this._bufferService.buffer.y = this._bufferService.buffer.scrollTop + y;
|
||||
@@ -642,7 +623,6 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
this._bufferService.buffer.y = y;
|
||||
}
|
||||
this._restrictCursor();
|
||||
this._dirtyRowService.markDirty(this._bufferService.buffer.y);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1066,108 +1046,18 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* CSI Ps T Scroll down Ps lines (default = 1) (SD).
|
||||
*/
|
||||
public scrollDown(params: IParams): void {
|
||||
let param = params.params[0] || 1;
|
||||
if (params.length < 2) {
|
||||
let param = params.params[0] || 1;
|
||||
|
||||
// make buffer local for faster access
|
||||
const buffer = this._bufferService.buffer;
|
||||
// make buffer local for faster access
|
||||
const buffer = this._bufferService.buffer;
|
||||
|
||||
while (param--) {
|
||||
buffer.lines.splice(buffer.ybase + buffer.scrollBottom, 1);
|
||||
buffer.lines.splice(buffer.ybase + buffer.scrollTop, 0, buffer.getBlankLine(DEFAULT_ATTR_DATA));
|
||||
while (param--) {
|
||||
buffer.lines.splice(buffer.ybase + buffer.scrollBottom, 1);
|
||||
buffer.lines.splice(buffer.ybase + buffer.scrollTop, 0, buffer.getBlankLine(this._terminal.eraseAttrData()));
|
||||
}
|
||||
this._dirtyRowService.markRangeDirty(buffer.scrollTop, buffer.scrollBottom);
|
||||
}
|
||||
this._dirtyRowService.markRangeDirty(buffer.scrollTop, buffer.scrollBottom);
|
||||
}
|
||||
|
||||
/**
|
||||
* CSI Ps SP @ Scroll left Ps columns (default = 1) (SL) ECMA-48
|
||||
*
|
||||
* Notation: (Pn)
|
||||
* Representation: CSI Pn 02/00 04/00
|
||||
* Parameter default value: Pn = 1
|
||||
* SL causes the data in the presentation component to be moved by n character positions
|
||||
* if the line orientation is horizontal, or by n line positions if the line orientation
|
||||
* is vertical, such that the data appear to move to the left; where n equals the value of Pn.
|
||||
* The active presentation position is not affected by this control function.
|
||||
*
|
||||
* Supported:
|
||||
* - always left shift (no line orientation setting respected)
|
||||
*/
|
||||
public scrollLeft(params: IParams): void {
|
||||
const buffer = this._bufferService.buffer;
|
||||
if (buffer.y > buffer.scrollBottom || buffer.y < buffer.scrollTop) {
|
||||
return;
|
||||
}
|
||||
const param = params.params[0] || 1;
|
||||
for (let y = buffer.scrollTop; y <= buffer.scrollBottom; ++y) {
|
||||
const line = buffer.lines.get(buffer.ybase + y);
|
||||
line.deleteCells(0, param, buffer.getNullCell(this._terminal.eraseAttrData()));
|
||||
line.isWrapped = false;
|
||||
}
|
||||
this._dirtyRowService.markRangeDirty(buffer.scrollTop, buffer.scrollBottom);
|
||||
}
|
||||
|
||||
/**
|
||||
* CSI Ps SP A Scroll right Ps columns (default = 1) (SR) ECMA-48
|
||||
*
|
||||
* Notation: (Pn)
|
||||
* Representation: CSI Pn 02/00 04/01
|
||||
* Parameter default value: Pn = 1
|
||||
* SR causes the data in the presentation component to be moved by n character positions
|
||||
* if the line orientation is horizontal, or by n line positions if the line orientation
|
||||
* is vertical, such that the data appear to move to the right; where n equals the value of Pn.
|
||||
* The active presentation position is not affected by this control function.
|
||||
*
|
||||
* Supported:
|
||||
* - always right shift (no line orientation setting respected)
|
||||
*/
|
||||
public scrollRight(params: IParams): void {
|
||||
const buffer = this._bufferService.buffer;
|
||||
if (buffer.y > buffer.scrollBottom || buffer.y < buffer.scrollTop) {
|
||||
return;
|
||||
}
|
||||
const param = params.params[0] || 1;
|
||||
for (let y = buffer.scrollTop; y <= buffer.scrollBottom; ++y) {
|
||||
const line = buffer.lines.get(buffer.ybase + y);
|
||||
line.insertCells(0, param, buffer.getNullCell(this._terminal.eraseAttrData()));
|
||||
line.isWrapped = false;
|
||||
}
|
||||
this._dirtyRowService.markRangeDirty(buffer.scrollTop, buffer.scrollBottom);
|
||||
}
|
||||
|
||||
/**
|
||||
* CSI Pm ' }
|
||||
* Insert Ps Column(s) (default = 1) (DECIC), VT420 and up.
|
||||
*/
|
||||
public insertColumns(params: IParams): void {
|
||||
const buffer = this._bufferService.buffer;
|
||||
if (buffer.y > buffer.scrollBottom || buffer.y < buffer.scrollTop) {
|
||||
return;
|
||||
}
|
||||
const param = params.params[0] || 1;
|
||||
for (let y = buffer.scrollTop; y <= buffer.scrollBottom; ++y) {
|
||||
const line = this._bufferService.buffer.lines.get(buffer.ybase + y);
|
||||
line.insertCells(buffer.x, param, buffer.getNullCell(this._terminal.eraseAttrData()));
|
||||
line.isWrapped = false;
|
||||
}
|
||||
this._dirtyRowService.markRangeDirty(buffer.scrollTop, buffer.scrollBottom);
|
||||
}
|
||||
|
||||
/**
|
||||
* CSI Pm ' ~
|
||||
* Delete Ps Column(s) (default = 1) (DECDC), VT420 and up.
|
||||
*/
|
||||
public deleteColumns(params: IParams): void {
|
||||
const buffer = this._bufferService.buffer;
|
||||
if (buffer.y > buffer.scrollBottom || buffer.y < buffer.scrollTop) {
|
||||
return;
|
||||
}
|
||||
const param = params.params[0] || 1;
|
||||
for (let y = buffer.scrollTop; y <= buffer.scrollBottom; ++y) {
|
||||
const line = buffer.lines.get(buffer.ybase + y);
|
||||
line.deleteCells(buffer.x, param, buffer.getNullCell(this._terminal.eraseAttrData()));
|
||||
line.isWrapped = false;
|
||||
}
|
||||
this._dirtyRowService.markRangeDirty(buffer.scrollTop, buffer.scrollBottom);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1444,17 +1334,19 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
// focusout: ^[[O
|
||||
this._terminal.sendFocus = true;
|
||||
break;
|
||||
case 1005: // utf8 ext mode mouse - removed in #2507
|
||||
this._logService.debug('DECSET 1005 not supported (see #2507)');
|
||||
case 1005: // utf8 ext mode mouse
|
||||
// for wide terminals
|
||||
// simply encodes large values as utf8 characters
|
||||
this._coreMouseService.activeEncoding = 'UTF8';
|
||||
break;
|
||||
case 1006: // sgr ext mode mouse
|
||||
this._coreMouseService.activeEncoding = 'SGR';
|
||||
break;
|
||||
case 1015: // urxvt ext mode mouse - removed in #2507
|
||||
this._logService.debug('DECSET 1015 not supported (see #2507)');
|
||||
case 1015: // urxvt ext mode mouse
|
||||
this._coreMouseService.activeEncoding = 'URXVT';
|
||||
break;
|
||||
case 25: // show cursor
|
||||
this._coreService.isCursorHidden = false;
|
||||
this._terminal.cursorHidden = false;
|
||||
break;
|
||||
case 1048: // alt screen cursor
|
||||
this.saveCursor();
|
||||
@@ -1615,17 +1507,17 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
case 1004: // send focusin/focusout events
|
||||
this._terminal.sendFocus = false;
|
||||
break;
|
||||
case 1005: // utf8 ext mode mouse - removed in #2507
|
||||
this._logService.debug('DECRST 1005 not supported (see #2507)');
|
||||
case 1005: // utf8 ext mode mouse
|
||||
this._coreMouseService.activeEncoding = 'DEFAULT';
|
||||
break;
|
||||
case 1006: // sgr ext mode mouse
|
||||
this._coreMouseService.activeEncoding = 'DEFAULT';
|
||||
break;
|
||||
case 1015: // urxvt ext mode mouse - removed in #2507
|
||||
this._logService.debug('DECRST 1015 not supported (see #2507)');
|
||||
case 1015: // urxvt ext mode mouse
|
||||
this._coreMouseService.activeEncoding = 'DEFAULT';
|
||||
break;
|
||||
case 25: // hide cursor
|
||||
this._coreService.isCursorHidden = true;
|
||||
this._terminal.cursorHidden = true;
|
||||
break;
|
||||
case 1048: // alt screen cursor
|
||||
this.restoreCursor();
|
||||
@@ -1959,7 +1851,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* http://vt100.net/docs/vt220-rm/table4-10.html
|
||||
*/
|
||||
public softReset(params: IParams): void {
|
||||
this._coreService.isCursorHidden = false;
|
||||
this._terminal.cursorHidden = false;
|
||||
this._terminal.insertMode = false;
|
||||
this._terminal.originMode = false;
|
||||
this._terminal.wraparoundMode = true; // defaults: xterm - true, vt100 - false
|
||||
|
||||
+53
-37
@@ -29,6 +29,11 @@ describe('Terminal', () => {
|
||||
(<any>term).renderer = new MockRenderer();
|
||||
term.viewport = new MockViewport();
|
||||
(<any>term)._compositionHelper = new MockCompositionHelper();
|
||||
// Force synchronous writes
|
||||
term.write = (data) => {
|
||||
term.writeBuffer.push(data);
|
||||
(<any>term)._innerWrite();
|
||||
};
|
||||
(<any>term).element = {
|
||||
classList: {
|
||||
toggle: () => { },
|
||||
@@ -54,18 +59,18 @@ describe('Terminal', () => {
|
||||
// });
|
||||
it('should fire the onCursorMove event', (done) => {
|
||||
term.onCursorMove(() => done());
|
||||
term.writeSync('foo');
|
||||
term.write('foo');
|
||||
});
|
||||
it('should fire the onLineFeed event', (done) => {
|
||||
term.onLineFeed(() => done());
|
||||
term.writeSync('\n');
|
||||
term.write('\n');
|
||||
});
|
||||
it('should fire a scroll event when scrollback is created', (done) => {
|
||||
term.onScroll(() => done());
|
||||
term.writeSync('\n'.repeat(INIT_ROWS));
|
||||
term.write('\n'.repeat(INIT_ROWS));
|
||||
});
|
||||
it('should fire a scroll event when scrollback is cleared', (done) => {
|
||||
term.writeSync('\n'.repeat(INIT_ROWS));
|
||||
term.write('\n'.repeat(INIT_ROWS));
|
||||
term.onScroll(() => done());
|
||||
term.clear();
|
||||
});
|
||||
@@ -163,6 +168,17 @@ describe('Terminal', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('reset', () => {
|
||||
it('should not affect cursorState', () => {
|
||||
term.cursorState = 1;
|
||||
term.reset();
|
||||
assert.equal(term.cursorState, 1);
|
||||
term.cursorState = 0;
|
||||
term.reset();
|
||||
assert.equal(term.cursorState, 0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('clear', () => {
|
||||
it('should clear a buffer equal to rows', () => {
|
||||
const promptLine = term.buffer.lines.get(term.buffer.ybase + term.buffer.y);
|
||||
@@ -179,7 +195,7 @@ describe('Terminal', () => {
|
||||
it('should clear a buffer larger than rows', () => {
|
||||
// Fill the buffer with dummy rows
|
||||
for (let i = 0; i < term.rows * 2; i++) {
|
||||
term.writeSync('test\n');
|
||||
term.write('test\n');
|
||||
}
|
||||
|
||||
const promptLine = term.buffer.lines.get(term.buffer.ybase + term.buffer.y);
|
||||
@@ -228,7 +244,7 @@ describe('Terminal', () => {
|
||||
assert.equal(e, '\x1b[200~foo\x1b[201~');
|
||||
done();
|
||||
});
|
||||
term.writeSync('\x1b[?2004h');
|
||||
term.write('\x1b[?2004h');
|
||||
term.paste('foo');
|
||||
});
|
||||
});
|
||||
@@ -238,7 +254,7 @@ describe('Terminal', () => {
|
||||
let startYDisp: number;
|
||||
beforeEach(() => {
|
||||
for (let i = 0; i < INIT_ROWS * 2; i++) {
|
||||
term.writeSync('test\r\n');
|
||||
term.writeln('test');
|
||||
}
|
||||
startYDisp = INIT_ROWS + 1;
|
||||
});
|
||||
@@ -273,7 +289,7 @@ describe('Terminal', () => {
|
||||
let startYDisp: number;
|
||||
beforeEach(() => {
|
||||
for (let i = 0; i < term.rows * 3; i++) {
|
||||
term.writeSync('test\r\n');
|
||||
term.writeln('test');
|
||||
}
|
||||
startYDisp = (term.rows * 2) + 1;
|
||||
});
|
||||
@@ -296,7 +312,7 @@ describe('Terminal', () => {
|
||||
describe('scrollToTop', () => {
|
||||
beforeEach(() => {
|
||||
for (let i = 0; i < term.rows * 3; i++) {
|
||||
term.writeSync('test\r\n');
|
||||
term.writeln('test');
|
||||
}
|
||||
});
|
||||
it('should scroll to the top', () => {
|
||||
@@ -310,7 +326,7 @@ describe('Terminal', () => {
|
||||
let startYDisp: number;
|
||||
beforeEach(() => {
|
||||
for (let i = 0; i < term.rows * 3; i++) {
|
||||
term.writeSync('test\r\n');
|
||||
term.writeln('test');
|
||||
}
|
||||
startYDisp = (term.rows * 2) + 1;
|
||||
});
|
||||
@@ -331,7 +347,7 @@ describe('Terminal', () => {
|
||||
let startYDisp: number;
|
||||
beforeEach(() => {
|
||||
for (let i = 0; i < term.rows * 3; i++) {
|
||||
term.writeSync('test\r\n');
|
||||
term.writeln('test');
|
||||
}
|
||||
startYDisp = (term.rows * 2) + 1;
|
||||
});
|
||||
@@ -376,7 +392,7 @@ describe('Terminal', () => {
|
||||
it('should not scroll down, when a custom keydown handler prevents the event', () => {
|
||||
// Add some output to the terminal
|
||||
for (let i = 0; i < term.rows * 3; i++) {
|
||||
term.writeSync('test\r\n');
|
||||
term.writeln('test');
|
||||
}
|
||||
const startYDisp = (term.rows * 2) + 1;
|
||||
term.attachCustomKeyEventHandler(() => {
|
||||
@@ -721,7 +737,7 @@ describe('Terminal', () => {
|
||||
const high = String.fromCharCode(0xD800);
|
||||
const cell = new CellData();
|
||||
for (let i = 0xDC00; i <= 0xDCFF; ++i) {
|
||||
term.writeSync(high + String.fromCharCode(i));
|
||||
term.write(high + String.fromCharCode(i));
|
||||
const tchar = term.buffer.lines.get(0).loadCell(0, cell);
|
||||
expect(tchar.getChars()).eql(high + String.fromCharCode(i));
|
||||
expect(tchar.getChars().length).eql(2);
|
||||
@@ -735,7 +751,7 @@ describe('Terminal', () => {
|
||||
const cell = new CellData();
|
||||
for (let i = 0xDC00; i <= 0xDCFF; ++i) {
|
||||
term.buffer.x = term.cols - 1;
|
||||
term.writeSync(high + String.fromCharCode(i));
|
||||
term.write(high + String.fromCharCode(i));
|
||||
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('');
|
||||
@@ -748,7 +764,7 @@ describe('Terminal', () => {
|
||||
for (let i = 0xDC00; i <= 0xDCFF; ++i) {
|
||||
term.buffer.x = term.cols - 1;
|
||||
term.wraparoundMode = true;
|
||||
term.writeSync('a' + high + String.fromCharCode(i));
|
||||
term.write('a' + high + String.fromCharCode(i));
|
||||
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);
|
||||
@@ -766,7 +782,7 @@ describe('Terminal', () => {
|
||||
if (width !== 1) {
|
||||
continue;
|
||||
}
|
||||
term.writeSync('a' + high + String.fromCharCode(i));
|
||||
term.write('a' + high + String.fromCharCode(i));
|
||||
// auto wraparound mode should cut off the rest of the line
|
||||
expect(term.buffer.lines.get(0).loadCell(term.cols - 1, cell).getChars()).eql(high + String.fromCharCode(i));
|
||||
expect(term.buffer.lines.get(0).loadCell(term.cols - 1, cell).getChars().length).eql(2);
|
||||
@@ -778,8 +794,8 @@ describe('Terminal', () => {
|
||||
const high = String.fromCharCode(0xD800);
|
||||
const cell = new CellData();
|
||||
for (let i = 0xDC00; i <= 0xDCFF; ++i) {
|
||||
term.writeSync(high);
|
||||
term.writeSync(String.fromCharCode(i));
|
||||
term.write(high);
|
||||
term.write(String.fromCharCode(i));
|
||||
const tchar = term.buffer.lines.get(0).loadCell(0, cell);
|
||||
expect(tchar.getChars()).eql(high + String.fromCharCode(i));
|
||||
expect(tchar.getChars().length).eql(2);
|
||||
@@ -793,7 +809,7 @@ describe('Terminal', () => {
|
||||
describe('unicode - combining characters', () => {
|
||||
const cell = new CellData();
|
||||
it('café', () => {
|
||||
term.writeSync('cafe\u0301');
|
||||
term.write('cafe\u0301');
|
||||
term.buffer.lines.get(0).loadCell(3, cell);
|
||||
expect(cell.getChars()).eql('e\u0301');
|
||||
expect(cell.getChars().length).eql(2);
|
||||
@@ -801,7 +817,7 @@ describe('Terminal', () => {
|
||||
});
|
||||
it('café - end of line', () => {
|
||||
term.buffer.x = term.cols - 1 - 3;
|
||||
term.writeSync('cafe\u0301');
|
||||
term.write('cafe\u0301');
|
||||
term.buffer.lines.get(0).loadCell(term.cols - 1, cell);
|
||||
expect(cell.getChars()).eql('e\u0301');
|
||||
expect(cell.getChars().length).eql(2);
|
||||
@@ -813,7 +829,7 @@ describe('Terminal', () => {
|
||||
});
|
||||
it('multiple combined é', () => {
|
||||
term.wraparoundMode = true;
|
||||
term.writeSync(Array(100).join('e\u0301'));
|
||||
term.write(Array(100).join('e\u0301'));
|
||||
for (let i = 0; i < term.cols; ++i) {
|
||||
term.buffer.lines.get(0).loadCell(i, cell);
|
||||
expect(cell.getChars()).eql('e\u0301');
|
||||
@@ -827,7 +843,7 @@ describe('Terminal', () => {
|
||||
});
|
||||
it('multiple surrogate with combined', () => {
|
||||
term.wraparoundMode = true;
|
||||
term.writeSync(Array(100).join('\uD800\uDC00\u0301'));
|
||||
term.write(Array(100).join('\uD800\uDC00\u0301'));
|
||||
for (let i = 0; i < term.cols; ++i) {
|
||||
term.buffer.lines.get(0).loadCell(i, cell);
|
||||
expect(cell.getChars()).eql('\uD800\uDC00\u0301');
|
||||
@@ -845,18 +861,18 @@ describe('Terminal', () => {
|
||||
const cell = new CellData();
|
||||
it('cursor movement even', () => {
|
||||
expect(term.buffer.x).eql(0);
|
||||
term.writeSync('¥');
|
||||
term.write('¥');
|
||||
expect(term.buffer.x).eql(2);
|
||||
});
|
||||
it('cursor movement odd', () => {
|
||||
term.buffer.x = 1;
|
||||
expect(term.buffer.x).eql(1);
|
||||
term.writeSync('¥');
|
||||
term.write('¥');
|
||||
expect(term.buffer.x).eql(3);
|
||||
});
|
||||
it('line of ¥ even', () => {
|
||||
term.wraparoundMode = true;
|
||||
term.writeSync(Array(50).join('¥'));
|
||||
term.write(Array(50).join('¥'));
|
||||
for (let i = 0; i < term.cols; ++i) {
|
||||
term.buffer.lines.get(0).loadCell(i, cell);
|
||||
if (i % 2) {
|
||||
@@ -877,7 +893,7 @@ describe('Terminal', () => {
|
||||
it('line of ¥ odd', () => {
|
||||
term.wraparoundMode = true;
|
||||
term.buffer.x = 1;
|
||||
term.writeSync(Array(50).join('¥'));
|
||||
term.write(Array(50).join('¥'));
|
||||
for (let i = 1; i < term.cols - 1; ++i) {
|
||||
term.buffer.lines.get(0).loadCell(i, cell);
|
||||
if (!(i % 2)) {
|
||||
@@ -902,7 +918,7 @@ describe('Terminal', () => {
|
||||
it('line of ¥ with combining odd', () => {
|
||||
term.wraparoundMode = true;
|
||||
term.buffer.x = 1;
|
||||
term.writeSync(Array(50).join('¥\u0301'));
|
||||
term.write(Array(50).join('¥\u0301'));
|
||||
for (let i = 1; i < term.cols - 1; ++i) {
|
||||
term.buffer.lines.get(0).loadCell(i, cell);
|
||||
if (!(i % 2)) {
|
||||
@@ -926,7 +942,7 @@ describe('Terminal', () => {
|
||||
});
|
||||
it('line of ¥ with combining even', () => {
|
||||
term.wraparoundMode = true;
|
||||
term.writeSync(Array(50).join('¥\u0301'));
|
||||
term.write(Array(50).join('¥\u0301'));
|
||||
for (let i = 0; i < term.cols; ++i) {
|
||||
term.buffer.lines.get(0).loadCell(i, cell);
|
||||
if (i % 2) {
|
||||
@@ -947,7 +963,7 @@ describe('Terminal', () => {
|
||||
it('line of surrogate fullwidth with combining odd', () => {
|
||||
term.wraparoundMode = true;
|
||||
term.buffer.x = 1;
|
||||
term.writeSync(Array(50).join('\ud843\ude6d\u0301'));
|
||||
term.write(Array(50).join('\ud843\ude6d\u0301'));
|
||||
for (let i = 1; i < term.cols - 1; ++i) {
|
||||
term.buffer.lines.get(0).loadCell(i, cell);
|
||||
if (!(i % 2)) {
|
||||
@@ -971,7 +987,7 @@ describe('Terminal', () => {
|
||||
});
|
||||
it('line of surrogate fullwidth with combining even', () => {
|
||||
term.wraparoundMode = true;
|
||||
term.writeSync(Array(50).join('\ud843\ude6d\u0301'));
|
||||
term.write(Array(50).join('\ud843\ude6d\u0301'));
|
||||
for (let i = 0; i < term.cols; ++i) {
|
||||
term.buffer.lines.get(0).loadCell(i, cell);
|
||||
if (i % 2) {
|
||||
@@ -994,11 +1010,11 @@ describe('Terminal', () => {
|
||||
describe('insert mode', () => {
|
||||
const cell = new CellData();
|
||||
it('halfwidth - all', () => {
|
||||
term.writeSync(Array(9).join('0123456789').slice(-80));
|
||||
term.write(Array(9).join('0123456789').slice(-80));
|
||||
term.buffer.x = 10;
|
||||
term.buffer.y = 0;
|
||||
term.insertMode = true;
|
||||
term.writeSync('abcde');
|
||||
term.write('abcde');
|
||||
expect(term.buffer.lines.get(0).length).eql(term.cols);
|
||||
expect(term.buffer.lines.get(0).loadCell(10, cell).getChars()).eql('a');
|
||||
expect(term.buffer.lines.get(0).loadCell(14, cell).getChars()).eql('e');
|
||||
@@ -1006,11 +1022,11 @@ describe('Terminal', () => {
|
||||
expect(term.buffer.lines.get(0).loadCell(79, cell).getChars()).eql('4');
|
||||
});
|
||||
it('fullwidth - insert', () => {
|
||||
term.writeSync(Array(9).join('0123456789').slice(-80));
|
||||
term.write(Array(9).join('0123456789').slice(-80));
|
||||
term.buffer.x = 10;
|
||||
term.buffer.y = 0;
|
||||
term.insertMode = true;
|
||||
term.writeSync('¥¥¥');
|
||||
term.write('¥¥¥');
|
||||
expect(term.buffer.lines.get(0).length).eql(term.cols);
|
||||
expect(term.buffer.lines.get(0).loadCell(10, cell).getChars()).eql('¥');
|
||||
expect(term.buffer.lines.get(0).loadCell(11, cell).getChars()).eql('');
|
||||
@@ -1019,16 +1035,16 @@ describe('Terminal', () => {
|
||||
expect(term.buffer.lines.get(0).loadCell(79, cell).getChars()).eql('3');
|
||||
});
|
||||
it('fullwidth - right border', () => {
|
||||
term.writeSync(Array(41).join('¥'));
|
||||
term.write(Array(41).join('¥'));
|
||||
term.buffer.x = 10;
|
||||
term.buffer.y = 0;
|
||||
term.insertMode = true;
|
||||
term.writeSync('a');
|
||||
term.write('a');
|
||||
expect(term.buffer.lines.get(0).length).eql(term.cols);
|
||||
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.writeSync('b');
|
||||
term.write('b');
|
||||
expect(term.buffer.lines.get(0).length).eql(term.cols);
|
||||
expect(term.buffer.lines.get(0).loadCell(11, cell).getChars()).eql('b');
|
||||
expect(term.buffer.lines.get(0).loadCell(12, cell).getChars()).eql('¥');
|
||||
|
||||
+304
-69
@@ -28,7 +28,7 @@ import { Viewport } from 'browser/Viewport';
|
||||
import { rightClickHandler, moveTextAreaUnderMouseCursor, handlePasteEvent, copyHandler, paste } from 'browser/Clipboard';
|
||||
import { C0 } from 'common/data/EscapeSequences';
|
||||
import { InputHandler } from './InputHandler';
|
||||
import { Renderer } from 'browser/renderer/Renderer';
|
||||
import { Renderer } from './renderer/Renderer';
|
||||
import { Linkifier } from 'browser/Linkifier';
|
||||
import { SelectionService } from 'browser/services/SelectionService';
|
||||
import * as Browser from 'common/Platform';
|
||||
@@ -38,17 +38,17 @@ import { SoundService } from 'browser/services/SoundService';
|
||||
import { MouseZoneManager } from 'browser/MouseZoneManager';
|
||||
import { AccessibilityManager } from './AccessibilityManager';
|
||||
import { ITheme, IMarker, IDisposable, ISelectionPosition } from 'xterm';
|
||||
import { DomRenderer } from 'browser/renderer/dom/DomRenderer';
|
||||
import { DomRenderer } from './renderer/dom/DomRenderer';
|
||||
import { IKeyboardEvent, KeyboardResultType, ICharset, IBufferLine, IAttributeData, CoreMouseEventType, CoreMouseButton, CoreMouseAction } from 'common/Types';
|
||||
import { evaluateKeyboardEvent } from 'common/input/Keyboard';
|
||||
import { EventEmitter, IEvent } from 'common/EventEmitter';
|
||||
import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
|
||||
import { handleWindowsModeLineFeed } from 'common/WindowsMode';
|
||||
import { applyWindowsMode } from './WindowsMode';
|
||||
import { ColorManager } from 'browser/ColorManager';
|
||||
import { RenderService } from 'browser/services/RenderService';
|
||||
import { IOptionsService, IBufferService, ICoreMouseService, ICoreService, ILogService, IDirtyRowService, IInstantiationService } from 'common/services/Services';
|
||||
import { OptionsService } from 'common/services/OptionsService';
|
||||
import { ICharSizeService, IRenderService, IMouseService, ISelectionService, ISoundService, ICoreBrowserService } from 'browser/services/Services';
|
||||
import { ICharSizeService, IRenderService, IMouseService, ISelectionService, ISoundService } from 'browser/services/Services';
|
||||
import { CharSizeService } from 'browser/services/CharSizeService';
|
||||
import { BufferService, MINIMUM_COLS, MINIMUM_ROWS } from 'common/services/BufferService';
|
||||
import { Disposable } from 'common/Lifecycle';
|
||||
@@ -62,12 +62,25 @@ import { ILinkifier, IMouseZoneManager, LinkMatcherHandler, ILinkMatcherOptions,
|
||||
import { DirtyRowService } from 'common/services/DirtyRowService';
|
||||
import { InstantiationService } from 'common/services/InstantiationService';
|
||||
import { CoreMouseService } from 'common/services/CoreMouseService';
|
||||
import { WriteBuffer } from 'common/input/WriteBuffer';
|
||||
import { CoreBrowserService } from 'browser/services/CoreBrowserService';
|
||||
|
||||
// Let it work inside Node.js for automated testing purposes.
|
||||
const document = (typeof window !== 'undefined') ? window.document : null;
|
||||
|
||||
/**
|
||||
* The amount of write requests to queue before sending an XOFF signal to the
|
||||
* pty process. This number must be small in order for ^C and similar sequences
|
||||
* to be responsive.
|
||||
*/
|
||||
const WRITE_BUFFER_PAUSE_THRESHOLD = 5;
|
||||
|
||||
/**
|
||||
* The max number of ms to spend on writes before allowing the renderer to
|
||||
* catch up with a 0ms setTimeout. A value of < 33 to keep us close to
|
||||
* 30fps, and a value of < 16 to try to run at 60fps. Of course, the real FPS
|
||||
* depends on the time it takes for the renderer to draw the frame.
|
||||
*/
|
||||
const WRITE_TIMEOUT_MS = 12;
|
||||
const WRITE_BUFFER_LENGTH_THRESHOLD = 50;
|
||||
|
||||
export class Terminal extends Disposable implements ITerminal, IDisposable, IInputHandlingTerminal {
|
||||
public textarea: HTMLTextAreaElement;
|
||||
@@ -77,7 +90,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
/**
|
||||
* The HTMLElement that the terminal is created in, set by Terminal.open.
|
||||
*/
|
||||
private _parent: HTMLElement | null;
|
||||
private _parent: HTMLElement;
|
||||
private _document: Document;
|
||||
private _viewportScrollArea: HTMLElement;
|
||||
private _viewportElement: HTMLElement;
|
||||
@@ -91,6 +104,10 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
// TODO: We should remove options once components adopt optionsService
|
||||
public get options(): ITerminalOptions { return this.optionsService.options; }
|
||||
|
||||
// TODO: This can be changed to an enum or boolean, 0 and 1 seem to be the only options
|
||||
public cursorState: number;
|
||||
public cursorHidden: boolean;
|
||||
|
||||
private _customKeyEventHandler: CustomKeyEventHandler;
|
||||
|
||||
// common services
|
||||
@@ -136,8 +153,21 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
public params: (string | number)[];
|
||||
public currentParam: string | number;
|
||||
|
||||
// write buffer
|
||||
private _writeBuffer: WriteBuffer;
|
||||
// user input states
|
||||
public writeBuffer: string[];
|
||||
public writeBufferUtf8: Uint8Array[];
|
||||
private _writeInProgress: boolean;
|
||||
|
||||
/**
|
||||
* Whether _xterm.js_ sent XOFF in order to catch up with the pty process.
|
||||
* This is a distinct state from writeStopped so that if the user requested
|
||||
* XOFF via ^S that it will not automatically resume when the writeBuffer goes
|
||||
* below threshold.
|
||||
*/
|
||||
private _xoffSentToCatchUp: boolean;
|
||||
|
||||
/** Whether writing has been stopped as a result of XOFF */
|
||||
// private _writeStopped: boolean;
|
||||
|
||||
// Store if user went browsing history in scrollback
|
||||
private _userScrolling: boolean;
|
||||
@@ -169,8 +199,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
public get onCursorMove(): IEvent<void> { return this._onCursorMove.event; }
|
||||
private _onData = new EventEmitter<string>();
|
||||
public get onData(): IEvent<string> { return this._onData.event; }
|
||||
private _onBinary = new EventEmitter<string>();
|
||||
public get onBinary(): IEvent<string> { return this._onBinary.event; }
|
||||
private _onKey = new EventEmitter<{ key: string, domEvent: KeyboardEvent }>();
|
||||
public get onKey(): IEvent<{ key: string, domEvent: KeyboardEvent }> { return this._onKey.event; }
|
||||
private _onLineFeed = new EventEmitter<void>();
|
||||
@@ -223,7 +251,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
this._coreService = this._instantiationService.createInstance(CoreService, () => this.scrollToBottom());
|
||||
this._instantiationService.setService(ICoreService, this._coreService);
|
||||
this._coreService.onData(e => this._onData.fire(e));
|
||||
this._coreService.onBinary(e => this._onBinary.fire(e));
|
||||
this._coreMouseService = this._instantiationService.createInstance(CoreMouseService);
|
||||
this._instantiationService.setService(ICoreMouseService, this._coreMouseService);
|
||||
this._dirtyRowService = this._instantiationService.createInstance(DirtyRowService);
|
||||
@@ -231,8 +258,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
|
||||
this._setupOptionsListeners();
|
||||
this._setup();
|
||||
|
||||
this._writeBuffer = new WriteBuffer(data => this._inputHandler.parse(data));
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
@@ -240,17 +265,25 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
return;
|
||||
}
|
||||
super.dispose();
|
||||
this._windowsMode?.dispose();
|
||||
this._windowsMode = undefined;
|
||||
this._renderService?.dispose();
|
||||
if (this._windowsMode) {
|
||||
this._windowsMode.dispose();
|
||||
this._windowsMode = undefined;
|
||||
}
|
||||
if (this._renderService) {
|
||||
this._renderService.dispose();
|
||||
}
|
||||
this._customKeyEventHandler = null;
|
||||
this.write = () => {};
|
||||
this.element?.parentNode?.removeChild(this.element);
|
||||
if (this.element && this.element.parentNode) {
|
||||
this.element.parentNode.removeChild(this.element);
|
||||
}
|
||||
}
|
||||
|
||||
private _setup(): void {
|
||||
this._parent = document ? document.body : null;
|
||||
|
||||
this.cursorState = 0;
|
||||
this.cursorHidden = false;
|
||||
this._customKeyEventHandler = null;
|
||||
|
||||
// modes
|
||||
@@ -273,6 +306,13 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
this.params = [];
|
||||
this.currentParam = 0;
|
||||
|
||||
// user input states
|
||||
this.writeBuffer = [];
|
||||
this.writeBufferUtf8 = [];
|
||||
this._writeInProgress = false;
|
||||
|
||||
this._xoffSentToCatchUp = false;
|
||||
// this._writeStopped = false;
|
||||
this._userScrolling = false;
|
||||
|
||||
// Register input handler and refire/handle events
|
||||
@@ -284,13 +324,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
this.linkifier = this.linkifier || new Linkifier(this._bufferService, this._logService);
|
||||
|
||||
if (this.options.windowsMode) {
|
||||
this._enableWindowsMode();
|
||||
}
|
||||
}
|
||||
|
||||
private _enableWindowsMode(): void {
|
||||
if (!this._windowsMode) {
|
||||
this._windowsMode = this.onLineFeed(handleWindowsModeLineFeed.bind(null, this._bufferService));
|
||||
this._windowsMode = applyWindowsMode(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -323,6 +357,10 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
}
|
||||
}
|
||||
|
||||
public get isFocused(): boolean {
|
||||
return document.activeElement === this.textarea && document.hasFocus();
|
||||
}
|
||||
|
||||
private _setupOptionsListeners(): void {
|
||||
// TODO: These listeners should be owned by individual components
|
||||
this.optionsService.onOptionChange(key => {
|
||||
@@ -330,15 +368,18 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
case 'fontFamily':
|
||||
case 'fontSize':
|
||||
// When the font changes the size of the cells may change which requires a renderer clear
|
||||
this._renderService?.clear();
|
||||
this._charSizeService?.measure();
|
||||
if (this._renderService) {
|
||||
this._renderService.clear();
|
||||
}
|
||||
if (this._charSizeService) {
|
||||
this._charSizeService.measure();
|
||||
}
|
||||
break;
|
||||
case 'drawBoldTextInBrightColors':
|
||||
case 'letterSpacing':
|
||||
case 'lineHeight':
|
||||
case 'fontWeight':
|
||||
case 'fontWeightBold':
|
||||
case 'minimumContrastRatio':
|
||||
// When the font changes the size of the cells may change which requires a renderer clear
|
||||
if (this._renderService) {
|
||||
this._renderService.clear();
|
||||
@@ -354,7 +395,9 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
break;
|
||||
case 'scrollback':
|
||||
this.buffers.resize(this.cols, this.rows);
|
||||
this.viewport?.syncScrollArea();
|
||||
if (this.viewport) {
|
||||
this.viewport.syncScrollArea();
|
||||
}
|
||||
break;
|
||||
case 'screenReaderMode':
|
||||
if (this.optionsService.options.screenReaderMode) {
|
||||
@@ -362,20 +405,38 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
this._accessibilityManager = new AccessibilityManager(this, this._renderService);
|
||||
}
|
||||
} else {
|
||||
this._accessibilityManager?.dispose();
|
||||
this._accessibilityManager = null;
|
||||
if (this._accessibilityManager) {
|
||||
this._accessibilityManager.dispose();
|
||||
this._accessibilityManager = null;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'tabStopWidth': this.buffers.setupTabStops(); break;
|
||||
case 'theme':
|
||||
this._setTheme(this.optionsService.options.theme);
|
||||
break;
|
||||
case 'scrollback':
|
||||
const newBufferLength = this.rows + this.optionsService.options.scrollback;
|
||||
if (this.buffer.lines.length > newBufferLength) {
|
||||
const amountToTrim = this.buffer.lines.length - newBufferLength;
|
||||
const needsRefresh = (this.buffer.ydisp - amountToTrim < 0);
|
||||
this.buffer.lines.trimStart(amountToTrim);
|
||||
this.buffer.ybase = Math.max(this.buffer.ybase - amountToTrim, 0);
|
||||
this.buffer.ydisp = Math.max(this.buffer.ydisp - amountToTrim, 0);
|
||||
if (needsRefresh) {
|
||||
this.refresh(0, this.rows - 1);
|
||||
}
|
||||
}
|
||||
case 'windowsMode':
|
||||
if (this.optionsService.options.windowsMode) {
|
||||
this._enableWindowsMode();
|
||||
if (!this._windowsMode) {
|
||||
this._windowsMode = applyWindowsMode(this);
|
||||
}
|
||||
} else {
|
||||
this._windowsMode?.dispose();
|
||||
this._windowsMode = undefined;
|
||||
if (this._windowsMode) {
|
||||
this._windowsMode.dispose();
|
||||
this._windowsMode = undefined;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -491,10 +552,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
throw new Error('Terminal requires a parent element.');
|
||||
}
|
||||
|
||||
if (!document.body.contains(parent)) {
|
||||
this._logService.warn('Terminal.open was called on an element that was not attached to the DOM');
|
||||
}
|
||||
|
||||
this._document = this._parent.ownerDocument;
|
||||
|
||||
// Create main element container
|
||||
@@ -536,9 +593,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
this.register(addDisposableDomListener(this.textarea, 'blur', () => this._onTextAreaBlur()));
|
||||
this._helperContainer.appendChild(this.textarea);
|
||||
|
||||
const coreBrowserService = this._instantiationService.createInstance(CoreBrowserService, this.textarea);
|
||||
this._instantiationService.setService(ICoreBrowserService, coreBrowserService);
|
||||
|
||||
this._charSizeService = this._instantiationService.createInstance(CharSizeService, this._document, this._helperContainer);
|
||||
this._instantiationService.setService(ICharSizeService, this._charSizeService);
|
||||
|
||||
@@ -553,7 +607,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
this._theme = this.options.theme || this._theme;
|
||||
this.options.theme = undefined;
|
||||
this._colorManager = new ColorManager(document, this.options.allowTransparency);
|
||||
this.optionsService.onOptionChange(e => this._colorManager.onOptionsChange(e));
|
||||
this._colorManager.setTheme(this._theme);
|
||||
|
||||
const renderer = this._createRenderer();
|
||||
@@ -640,8 +693,8 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
|
||||
private _createRenderer(): IRenderer {
|
||||
switch (this.options.rendererType) {
|
||||
case 'canvas': return this._instantiationService.createInstance(Renderer, this._colorManager.colors, this.screenElement, this.linkifier);
|
||||
case 'dom': return this._instantiationService.createInstance(DomRenderer, this._colorManager.colors, this.element, this.screenElement, this._viewportElement, this.linkifier);
|
||||
case 'canvas': return new Renderer(this._colorManager.colors, this, this._bufferService, this._charSizeService, this.optionsService);
|
||||
case 'dom': return new DomRenderer(this, this._colorManager.colors, this._charSizeService, this.optionsService);
|
||||
default: throw new Error(`Unrecognized rendererType "${this.options.rendererType}"`);
|
||||
}
|
||||
}
|
||||
@@ -652,9 +705,15 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
*/
|
||||
private _setTheme(theme: ITheme): void {
|
||||
this._theme = theme;
|
||||
this._colorManager?.setTheme(theme);
|
||||
this._renderService?.setColors(this._colorManager.colors);
|
||||
this.viewport?.onThemeChange(this._colorManager.colors);
|
||||
if (this._colorManager) {
|
||||
this._colorManager.setTheme(theme);
|
||||
}
|
||||
if (this._renderService) {
|
||||
this._renderService.setColors(this._colorManager.colors);
|
||||
}
|
||||
if (this.viewport) {
|
||||
this.viewport.onThemeChange(this._colorManager.colors);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -921,7 +980,9 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
* @param end The row to end at (between start and this.rows - 1).
|
||||
*/
|
||||
public refresh(start: number, end: number): void {
|
||||
this._renderService?.refreshRows(start, end);
|
||||
if (this._renderService) {
|
||||
this._renderService.refreshRows(start, end);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -930,7 +991,9 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
* @param end The row to end at (between start and this.rows - 1).
|
||||
*/
|
||||
private _queueLinkification(start: number, end: number): void {
|
||||
this.linkifier?.linkifyRows(start, end);
|
||||
if (this.linkifier) {
|
||||
this.linkifier.linkifyRows(start, end);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -948,8 +1011,8 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
* Display the cursor element
|
||||
*/
|
||||
public showCursor(): void {
|
||||
if (!this._coreService.isCursorInitialized) {
|
||||
this._coreService.isCursorInitialized = true;
|
||||
if (!this.cursorState) {
|
||||
this.cursorState = 1;
|
||||
this.refresh(this.buffer.y, this.buffer.y);
|
||||
}
|
||||
}
|
||||
@@ -1081,6 +1144,168 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes raw utf8 bytes to the terminal.
|
||||
* @param data UintArray with UTF8 bytes to write to the terminal.
|
||||
*/
|
||||
public writeUtf8(data: Uint8Array): void {
|
||||
// Ensure the terminal isn't disposed
|
||||
if (this._isDisposed) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignore falsy data values
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.writeBufferUtf8.push(data);
|
||||
|
||||
// Send XOFF to pause the pty process if the write buffer becomes too large so
|
||||
// xterm.js can catch up before more data is sent. This is necessary in order
|
||||
// to keep signals such as ^C responsive.
|
||||
if (this.options.useFlowControl && !this._xoffSentToCatchUp && this.writeBufferUtf8.length >= WRITE_BUFFER_PAUSE_THRESHOLD) {
|
||||
// XOFF - stop pty pipe
|
||||
// XON will be triggered by emulator before processing data chunk
|
||||
this._coreService.triggerDataEvent(C0.DC3);
|
||||
this._xoffSentToCatchUp = true;
|
||||
}
|
||||
|
||||
if (!this._writeInProgress && this.writeBufferUtf8.length > 0) {
|
||||
// Kick off a write which will write all data in sequence recursively
|
||||
this._writeInProgress = true;
|
||||
// Kick off an async innerWrite so more writes can come in while processing data
|
||||
setTimeout(() => {
|
||||
this._innerWriteUtf8();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected _innerWriteUtf8(bufferOffset: number = 0): void {
|
||||
// Ensure the terminal isn't disposed
|
||||
if (this._isDisposed) {
|
||||
this.writeBufferUtf8 = [];
|
||||
}
|
||||
|
||||
const startTime = Date.now();
|
||||
while (this.writeBufferUtf8.length > bufferOffset) {
|
||||
const data = this.writeBufferUtf8[bufferOffset];
|
||||
bufferOffset++;
|
||||
|
||||
// If XOFF was sent in order to catch up with the pty process, resume it if
|
||||
// we reached the end of the writeBuffer to allow more data to come in.
|
||||
if (this._xoffSentToCatchUp && this.writeBufferUtf8.length === bufferOffset) {
|
||||
this._coreService.triggerDataEvent(C0.DC1);
|
||||
this._xoffSentToCatchUp = false;
|
||||
}
|
||||
|
||||
this._inputHandler.parseUtf8(data);
|
||||
|
||||
this.refresh(this._dirtyRowService.start, this._dirtyRowService.end);
|
||||
|
||||
if (Date.now() - startTime >= WRITE_TIMEOUT_MS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (this.writeBufferUtf8.length > bufferOffset) {
|
||||
// Allow renderer to catch up before processing the next batch
|
||||
// trim already processed chunks if we are above threshold
|
||||
if (bufferOffset > WRITE_BUFFER_LENGTH_THRESHOLD) {
|
||||
this.writeBufferUtf8 = this.writeBufferUtf8.slice(bufferOffset);
|
||||
bufferOffset = 0;
|
||||
}
|
||||
setTimeout(() => this._innerWriteUtf8(bufferOffset), 0);
|
||||
} else {
|
||||
this._writeInProgress = false;
|
||||
this.writeBufferUtf8 = [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes text to the terminal.
|
||||
* @param data The text to write to the terminal.
|
||||
*/
|
||||
public write(data: string): void {
|
||||
// Ensure the terminal isn't disposed
|
||||
if (this._isDisposed) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignore falsy data values (including the empty string)
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.writeBuffer.push(data);
|
||||
|
||||
// Send XOFF to pause the pty process if the write buffer becomes too large so
|
||||
// xterm.js can catch up before more data is sent. This is necessary in order
|
||||
// to keep signals such as ^C responsive.
|
||||
if (this.options.useFlowControl && !this._xoffSentToCatchUp && this.writeBuffer.length >= WRITE_BUFFER_PAUSE_THRESHOLD) {
|
||||
// XOFF - stop pty pipe
|
||||
// XON will be triggered by emulator before processing data chunk
|
||||
this._coreService.triggerDataEvent(C0.DC3);
|
||||
this._xoffSentToCatchUp = true;
|
||||
}
|
||||
|
||||
if (!this._writeInProgress && this.writeBuffer.length > 0) {
|
||||
// Kick off a write which will write all data in sequence recursively
|
||||
this._writeInProgress = true;
|
||||
// Kick off an async innerWrite so more writes can come in while processing data
|
||||
setTimeout(() => {
|
||||
this._innerWrite();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected _innerWrite(bufferOffset: number = 0): void {
|
||||
// Ensure the terminal isn't disposed
|
||||
if (this._isDisposed) {
|
||||
this.writeBuffer = [];
|
||||
}
|
||||
|
||||
const startTime = Date.now();
|
||||
while (this.writeBuffer.length > bufferOffset) {
|
||||
const data = this.writeBuffer[bufferOffset];
|
||||
bufferOffset++;
|
||||
|
||||
// If XOFF was sent in order to catch up with the pty process, resume it if
|
||||
// we reached the end of the writeBuffer to allow more data to come in.
|
||||
if (this._xoffSentToCatchUp && this.writeBuffer.length === bufferOffset) {
|
||||
this._coreService.triggerDataEvent(C0.DC1);
|
||||
this._xoffSentToCatchUp = false;
|
||||
}
|
||||
|
||||
this._inputHandler.parse(data);
|
||||
|
||||
this.refresh(this._dirtyRowService.start, this._dirtyRowService.end);
|
||||
|
||||
if (Date.now() - startTime >= WRITE_TIMEOUT_MS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (this.writeBuffer.length > bufferOffset) {
|
||||
// Allow renderer to catch up before processing the next batch
|
||||
// trim already processed chunks if we are above threshold
|
||||
if (bufferOffset > WRITE_BUFFER_LENGTH_THRESHOLD) {
|
||||
this.writeBuffer = this.writeBuffer.slice(bufferOffset);
|
||||
bufferOffset = 0;
|
||||
}
|
||||
setTimeout(() => this._innerWrite(bufferOffset), 0);
|
||||
} else {
|
||||
this._writeInProgress = false;
|
||||
this.writeBuffer = [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes text to the terminal, followed by a break line character (\n).
|
||||
* @param data The text to write to the terminal.
|
||||
*/
|
||||
public writeln(data: string): void {
|
||||
this.write(data + '\r\n');
|
||||
}
|
||||
|
||||
public paste(data: string): void {
|
||||
paste(data, this.textarea, this.bracketedPasteMode, this._coreService);
|
||||
}
|
||||
@@ -1210,18 +1435,24 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
* Clears the current terminal selection.
|
||||
*/
|
||||
public clearSelection(): void {
|
||||
this._selectionService?.clearSelection();
|
||||
if (this._selectionService) {
|
||||
this._selectionService.clearSelection();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects all text within the terminal.
|
||||
*/
|
||||
public selectAll(): void {
|
||||
this._selectionService?.selectAll();
|
||||
if (this._selectionService) {
|
||||
this._selectionService.selectAll();
|
||||
}
|
||||
}
|
||||
|
||||
public selectLines(start: number, end: number): void {
|
||||
this._selectionService?.selectLines(start, end);
|
||||
if (this._selectionService) {
|
||||
this._selectionService.selectLines(start, end);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1428,11 +1659,9 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
this._bufferService.resize(x, y);
|
||||
this.buffers.setupTabStops(this.cols);
|
||||
|
||||
this._charSizeService?.measure();
|
||||
|
||||
// Sync the scroll area to make sure scroll events don't fire and scroll the viewport to an
|
||||
// invalid location
|
||||
this.viewport.syncScrollArea(true);
|
||||
if (this._charSizeService) {
|
||||
this._charSizeService.measure();
|
||||
}
|
||||
|
||||
this.refresh(0, this.rows - 1);
|
||||
this._onResize.fire({ cols: x, rows: y });
|
||||
@@ -1513,22 +1742,36 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
this.options.cols = this.cols;
|
||||
const customKeyEventHandler = this._customKeyEventHandler;
|
||||
const inputHandler = this._inputHandler;
|
||||
const cursorState = this.cursorState;
|
||||
const writeBuffer = this.writeBuffer;
|
||||
const writeBufferUtf8 = this.writeBufferUtf8;
|
||||
const writeInProgress = this._writeInProgress;
|
||||
const xoffSentToCatchUp = this._xoffSentToCatchUp;
|
||||
const userScrolling = this._userScrolling;
|
||||
|
||||
this._setup();
|
||||
this._bufferService.reset();
|
||||
this._coreService.reset();
|
||||
this._coreMouseService.reset();
|
||||
this._selectionService?.reset();
|
||||
if (this._selectionService) {
|
||||
this._selectionService.reset();
|
||||
}
|
||||
|
||||
// reattach
|
||||
this._customKeyEventHandler = customKeyEventHandler;
|
||||
this._inputHandler = inputHandler;
|
||||
this.cursorState = cursorState;
|
||||
this.writeBuffer = writeBuffer;
|
||||
this.writeBufferUtf8 = writeBufferUtf8;
|
||||
this._writeInProgress = writeInProgress;
|
||||
this._xoffSentToCatchUp = xoffSentToCatchUp;
|
||||
this._userScrolling = userScrolling;
|
||||
|
||||
// do a full screen refresh
|
||||
this.refresh(0, this.rows - 1);
|
||||
this.viewport?.syncScrollArea();
|
||||
if (this.viewport) {
|
||||
this.viewport.syncScrollArea();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Remove cancel function and cancelEvents option
|
||||
@@ -1552,14 +1795,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
// return this.options.bellStyle === 'sound' ||
|
||||
// this.options.bellStyle === 'both';
|
||||
}
|
||||
|
||||
public write(data: string | Uint8Array, callback?: () => void): void {
|
||||
this._writeBuffer.write(data, callback);
|
||||
}
|
||||
|
||||
public writeSync(data: string | Uint8Array): void {
|
||||
this._writeBuffer.writeSync(data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -34,6 +34,7 @@ if (os.platform() === 'darwin') {
|
||||
// filter skipFilenames
|
||||
const FILES = TESTFILES.filter(value => SKIP_FILES.indexOf(value.split('/').slice(-1)[0]) === -1);
|
||||
|
||||
|
||||
describe('Escape Sequence Files', function(): void {
|
||||
this.timeout(1000);
|
||||
|
||||
@@ -43,9 +44,6 @@ describe('Escape Sequence Files', function(): void {
|
||||
let customHandler: IDisposable | undefined;
|
||||
|
||||
before(() => {
|
||||
if (process.platform === 'win32') {
|
||||
return;
|
||||
}
|
||||
ptyTerm = (pty as any).open({cols: COLS, rows: ROWS});
|
||||
slaveEnd = ptyTerm._slave;
|
||||
term = new Terminal({cols: COLS, rows: ROWS});
|
||||
@@ -53,15 +51,12 @@ describe('Escape Sequence Files', function(): void {
|
||||
});
|
||||
|
||||
after(() => {
|
||||
if (process.platform === 'win32') {
|
||||
return;
|
||||
}
|
||||
ptyTerm._master.end();
|
||||
ptyTerm._master.destroy();
|
||||
});
|
||||
|
||||
FILES.forEach(filename => {
|
||||
(process.platform === 'win32' ? it.skip : it)(filename.split('/').slice(-1)[0], async () => {
|
||||
it(filename.split('/').slice(-1)[0], async () => {
|
||||
// reset terminal and handler
|
||||
if (customHandler) {
|
||||
customHandler.dispose();
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { IRenderer, IRenderDimensions, CharacterJoinerHandler, IRequestRefreshRowsEvent } from 'browser/renderer/Types';
|
||||
import { IRenderer, IRenderDimensions, CharacterJoinerHandler } from 'browser/renderer/Types';
|
||||
import { IInputHandlingTerminal, ICompositionHelper, ITerminal, IBrowser, ITerminalOptions } from './Types';
|
||||
import { IBuffer, IBufferStringIterator, IBufferSet } from 'common/buffer/Types';
|
||||
import { IBufferLine, ICellData, IAttributeData, ICircularList, XtermListener, ICharset, CoreMouseEventType } from 'common/Types';
|
||||
@@ -19,6 +19,10 @@ import { IParams, IFunctionIdentifier } from 'common/parser/Types';
|
||||
import { ISelectionService } from 'browser/services/Services';
|
||||
|
||||
export class TestTerminal extends Terminal {
|
||||
writeSync(data: string): void {
|
||||
this.writeBuffer.push(data);
|
||||
this._innerWrite();
|
||||
}
|
||||
keyDown(ev: any): boolean { return this._keyDown(ev); }
|
||||
keyPress(ev: any): boolean { return this._keyPress(ev); }
|
||||
}
|
||||
@@ -32,7 +36,6 @@ export class MockTerminal implements ITerminal {
|
||||
onLineFeed: IEvent<void>;
|
||||
onSelectionChange: IEvent<void>;
|
||||
onData: IEvent<string>;
|
||||
onBinary: IEvent<string>;
|
||||
onTitleChange: IEvent<string>;
|
||||
onScroll: IEvent<number>;
|
||||
onKey: IEvent<{ key: string; domEvent: KeyboardEvent; }>;
|
||||
@@ -363,7 +366,6 @@ export class MockBuffer implements IBuffer {
|
||||
}
|
||||
|
||||
export class MockRenderer implements IRenderer {
|
||||
onRequestRefreshRows: IEvent<IRequestRefreshRowsEvent>;
|
||||
onCanvasResize: IEvent<{ width: number; height: number; }>;
|
||||
onRender: IEvent<{ start: number; end: number; }>;
|
||||
dispose(): void {
|
||||
|
||||
Vendored
+12
-9
@@ -38,6 +38,7 @@ export interface IInputHandlingTerminal {
|
||||
savedCols: number;
|
||||
mouseEvents: CoreMouseEventType;
|
||||
sendFocus: boolean;
|
||||
cursorHidden: boolean;
|
||||
|
||||
buffers: IBufferSet;
|
||||
buffer: IBuffer;
|
||||
@@ -72,7 +73,8 @@ export interface ICompositionHelper {
|
||||
* Calls the parser and handles actions generated by the parser.
|
||||
*/
|
||||
export interface IInputHandler {
|
||||
parse(data: string | Uint8Array): void;
|
||||
parse(data: string): void;
|
||||
parseUtf8(data: Uint8Array): void;
|
||||
print(data: Uint32Array, start: number, end: number): void;
|
||||
|
||||
/** C0 BEL */ bell(): void;
|
||||
@@ -84,9 +86,7 @@ export interface IInputHandler {
|
||||
/** C0 SI */ shiftIn(): void;
|
||||
|
||||
/** CSI @ */ insertChars(params: IParams): void;
|
||||
/** CSI SP @ */ scrollLeft(params: IParams): void;
|
||||
/** CSI A */ cursorUp(params: IParams): void;
|
||||
/** CSI SP A */ scrollRight(params: IParams): void;
|
||||
/** CSI B */ cursorDown(params: IParams): void;
|
||||
/** CSI C */ cursorForward(params: IParams): void;
|
||||
/** CSI D */ cursorBackward(params: IParams): void;
|
||||
@@ -122,8 +122,6 @@ export interface IInputHandler {
|
||||
/** CSI r */ setScrollRegion(params: IParams, collect?: string): void;
|
||||
/** CSI s */ saveCursor(params: IParams): void;
|
||||
/** CSI u */ restoreCursor(params: IParams): void;
|
||||
/** CSI ' } */ insertColumns(params: IParams): void;
|
||||
/** CSI ' ~ */ deleteColumns(params: IParams): void;
|
||||
/** OSC 0
|
||||
OSC 2 */ setTitle(data: string): void;
|
||||
/** ESC E */ nextLine(): void;
|
||||
@@ -153,8 +151,12 @@ export interface IInputHandler {
|
||||
export interface ITerminal extends IPublicTerminal, IElementAccessor, IBufferAccessor, ILinkifierAccessor {
|
||||
screenElement: HTMLElement;
|
||||
browser: IBrowser;
|
||||
writeBuffer: string[];
|
||||
cursorHidden: boolean;
|
||||
cursorState: number;
|
||||
buffer: IBuffer;
|
||||
buffers: IBufferSet;
|
||||
isFocused: boolean;
|
||||
viewport: IViewport;
|
||||
bracketedPasteMode: boolean;
|
||||
optionsService: IOptionsService;
|
||||
@@ -173,14 +175,13 @@ export interface ITerminal extends IPublicTerminal, IElementAccessor, IBufferAcc
|
||||
|
||||
// Portions of the public API that are required by the internal Terminal
|
||||
export interface IPublicTerminal extends IDisposable {
|
||||
textarea: HTMLTextAreaElement | undefined;
|
||||
textarea: HTMLTextAreaElement;
|
||||
rows: number;
|
||||
cols: number;
|
||||
buffer: IBuffer;
|
||||
markers: IMarker[];
|
||||
onCursorMove: IEvent<void>;
|
||||
onData: IEvent<string>;
|
||||
onBinary: IEvent<string>;
|
||||
onKey: IEvent<{ key: string, domEvent: KeyboardEvent }>;
|
||||
onLineFeed: IEvent<void>;
|
||||
onScroll: IEvent<number>;
|
||||
@@ -191,6 +192,7 @@ export interface IPublicTerminal extends IDisposable {
|
||||
blur(): void;
|
||||
focus(): void;
|
||||
resize(columns: number, rows: number): void;
|
||||
writeln(data: string): void;
|
||||
open(parent: HTMLElement): void;
|
||||
attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void;
|
||||
addCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean): IDisposable;
|
||||
@@ -216,7 +218,8 @@ export interface IPublicTerminal extends IDisposable {
|
||||
scrollToBottom(): void;
|
||||
scrollToLine(line: number): void;
|
||||
clear(): void;
|
||||
write(data: string | Uint8Array, callback?: () => void): void;
|
||||
write(data: string): void;
|
||||
writeUtf8(data: Uint8Array): void;
|
||||
paste(data: string): void;
|
||||
refresh(start: number, end: number): void;
|
||||
reset(): void;
|
||||
@@ -227,7 +230,7 @@ export interface IBufferAccessor {
|
||||
}
|
||||
|
||||
export interface IElementAccessor {
|
||||
readonly element: HTMLElement | undefined;
|
||||
readonly element: HTMLElement;
|
||||
}
|
||||
|
||||
export interface ILinkifierAccessor {
|
||||
|
||||
@@ -3,10 +3,11 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { IDisposable } from 'xterm';
|
||||
import { ITerminal } from './Types';
|
||||
import { CHAR_DATA_CODE_INDEX, NULL_CELL_CODE, WHITESPACE_CELL_CODE } from 'common/buffer/Constants';
|
||||
import { IBufferService } from 'common/services/Services';
|
||||
|
||||
export function handleWindowsModeLineFeed(bufferService: IBufferService): void {
|
||||
export function applyWindowsMode(terminal: ITerminal): IDisposable {
|
||||
// Winpty does not support wraparound mode which means that lines will never
|
||||
// be marked as wrapped. This causes issues for things like copying a line
|
||||
// retaining the wrapped new line characters or if consumers are listening
|
||||
@@ -17,11 +18,11 @@ export function handleWindowsModeLineFeed(bufferService: IBufferService): void {
|
||||
// space. This is certainly not without its problems, but generally on
|
||||
// Windows when text reaches the end of the terminal it's likely going to be
|
||||
// wrapped.
|
||||
const line = bufferService.buffer.lines.get(bufferService.buffer.ybase + bufferService.buffer.y - 1);
|
||||
const lastChar = line?.get(bufferService.cols - 1);
|
||||
return terminal.onLineFeed(() => {
|
||||
const line = terminal.buffer.lines.get(terminal.buffer.ybase + terminal.buffer.y - 1);
|
||||
const lastChar = line.get(terminal.cols - 1);
|
||||
|
||||
const nextLine = bufferService.buffer.lines.get(bufferService.buffer.ybase + bufferService.buffer.y);
|
||||
if (nextLine && lastChar) {
|
||||
const nextLine = terminal.buffer.lines.get(terminal.buffer.ybase + terminal.buffer.y);
|
||||
nextLine.isWrapped = (lastChar[CHAR_DATA_CODE_INDEX] !== NULL_CELL_CODE && lastChar[CHAR_DATA_CODE_INDEX] !== WHITESPACE_CELL_CODE);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -82,12 +82,12 @@ export function moveTextAreaUnderMouseCursor(ev: MouseEvent, textarea: HTMLTextA
|
||||
// Reset the terminal textarea's styling
|
||||
// Timeout needs to be long enough for click event to be handled.
|
||||
setTimeout(() => {
|
||||
textarea.style.position = '';
|
||||
textarea.style.width = '';
|
||||
textarea.style.height = '';
|
||||
textarea.style.left = '';
|
||||
textarea.style.top = '';
|
||||
textarea.style.zIndex = '';
|
||||
textarea.style.position = null;
|
||||
textarea.style.width = null;
|
||||
textarea.style.height = null;
|
||||
textarea.style.left = null;
|
||||
textarea.style.top = null;
|
||||
textarea.style.zIndex = null;
|
||||
}, 200);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,267 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { assert } from 'chai';
|
||||
import { blend, fromCss, toPaddedHex, toCss, toRgba, fromRgba, opaque, rgbRelativeLuminance, contrastRatio, ensureContrastRatioRgba } from 'browser/Color';
|
||||
|
||||
describe('Color', () => {
|
||||
describe('blend', () => {
|
||||
it('should blend colors based on the alpha channel', () => {
|
||||
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF00', rgba: 0xFFFFFF00 }), { css: '#000000', rgba: 0x000000FF });
|
||||
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF10', rgba: 0xFFFFFF10 }), { css: '#101010', rgba: 0x101010FF });
|
||||
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF20', rgba: 0xFFFFFF20 }), { css: '#202020', rgba: 0x202020FF });
|
||||
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF30', rgba: 0xFFFFFF30 }), { css: '#303030', rgba: 0x303030FF });
|
||||
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF40', rgba: 0xFFFFFF40 }), { css: '#404040', rgba: 0x404040FF });
|
||||
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF50', rgba: 0xFFFFFF50 }), { css: '#505050', rgba: 0x505050FF });
|
||||
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF60', rgba: 0xFFFFFF60 }), { css: '#606060', rgba: 0x606060FF });
|
||||
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF70', rgba: 0xFFFFFF70 }), { css: '#707070', rgba: 0x707070FF });
|
||||
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF80', rgba: 0xFFFFFF80 }), { css: '#808080', rgba: 0x808080FF });
|
||||
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF90', rgba: 0xFFFFFF90 }), { css: '#909090', rgba: 0x909090FF });
|
||||
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFA0', rgba: 0xFFFFFFA0 }), { css: '#a0a0a0', rgba: 0xA0A0A0FF });
|
||||
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFB0', rgba: 0xFFFFFFB0 }), { css: '#b0b0b0', rgba: 0xB0B0B0FF });
|
||||
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFC0', rgba: 0xFFFFFFC0 }), { css: '#c0c0c0', rgba: 0xC0C0C0FF });
|
||||
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFD0', rgba: 0xFFFFFFD0 }), { css: '#d0d0d0', rgba: 0xD0D0D0FF });
|
||||
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFE0', rgba: 0xFFFFFFE0 }), { css: '#e0e0e0', rgba: 0xE0E0E0FF });
|
||||
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFF0', rgba: 0xFFFFFFF0 }), { css: '#f0f0f0', rgba: 0xF0F0F0FF });
|
||||
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFFF', rgba: 0xFFFFFFFF }), { css: '#FFFFFFFF', rgba: 0xFFFFFFFF });
|
||||
});
|
||||
});
|
||||
|
||||
describe('fromCss', () => {
|
||||
it('should covert a CSS string to an IColor', () => {
|
||||
assert.deepEqual(fromCss('#000000'), { css: '#000000', rgba: 0x000000FF });
|
||||
assert.deepEqual(fromCss('#101010'), { css: '#101010', rgba: 0x101010FF });
|
||||
assert.deepEqual(fromCss('#202020'), { css: '#202020', rgba: 0x202020FF });
|
||||
assert.deepEqual(fromCss('#303030'), { css: '#303030', rgba: 0x303030FF });
|
||||
assert.deepEqual(fromCss('#404040'), { css: '#404040', rgba: 0x404040FF });
|
||||
assert.deepEqual(fromCss('#505050'), { css: '#505050', rgba: 0x505050FF });
|
||||
assert.deepEqual(fromCss('#606060'), { css: '#606060', rgba: 0x606060FF });
|
||||
assert.deepEqual(fromCss('#707070'), { css: '#707070', rgba: 0x707070FF });
|
||||
assert.deepEqual(fromCss('#808080'), { css: '#808080', rgba: 0x808080FF });
|
||||
assert.deepEqual(fromCss('#909090'), { css: '#909090', rgba: 0x909090FF });
|
||||
assert.deepEqual(fromCss('#a0a0a0'), { css: '#a0a0a0', rgba: 0xa0a0a0FF });
|
||||
assert.deepEqual(fromCss('#b0b0b0'), { css: '#b0b0b0', rgba: 0xb0b0b0FF });
|
||||
assert.deepEqual(fromCss('#c0c0c0'), { css: '#c0c0c0', rgba: 0xc0c0c0FF });
|
||||
assert.deepEqual(fromCss('#d0d0d0'), { css: '#d0d0d0', rgba: 0xd0d0d0FF });
|
||||
assert.deepEqual(fromCss('#e0e0e0'), { css: '#e0e0e0', rgba: 0xe0e0e0FF });
|
||||
assert.deepEqual(fromCss('#f0f0f0'), { css: '#f0f0f0', rgba: 0xf0f0f0FF });
|
||||
assert.deepEqual(fromCss('#ffffff'), { css: '#ffffff', rgba: 0xffffffFF });
|
||||
});
|
||||
});
|
||||
|
||||
describe('toPaddedHex', () => {
|
||||
it('should convert numbers to 2-digit hex values', () => {
|
||||
assert.equal(toPaddedHex(0x00), '00');
|
||||
assert.equal(toPaddedHex(0x10), '10');
|
||||
assert.equal(toPaddedHex(0x20), '20');
|
||||
assert.equal(toPaddedHex(0x30), '30');
|
||||
assert.equal(toPaddedHex(0x40), '40');
|
||||
assert.equal(toPaddedHex(0x50), '50');
|
||||
assert.equal(toPaddedHex(0x60), '60');
|
||||
assert.equal(toPaddedHex(0x70), '70');
|
||||
assert.equal(toPaddedHex(0x80), '80');
|
||||
assert.equal(toPaddedHex(0x90), '90');
|
||||
assert.equal(toPaddedHex(0xa0), 'a0');
|
||||
assert.equal(toPaddedHex(0xb0), 'b0');
|
||||
assert.equal(toPaddedHex(0xc0), 'c0');
|
||||
assert.equal(toPaddedHex(0xd0), 'd0');
|
||||
assert.equal(toPaddedHex(0xe0), 'e0');
|
||||
assert.equal(toPaddedHex(0xf0), 'f0');
|
||||
assert.equal(toPaddedHex(0xff), 'ff');
|
||||
});
|
||||
});
|
||||
|
||||
describe('toCss', () => {
|
||||
it('should convert an rgb array to css hex string', () => {
|
||||
assert.equal(toCss(0x00, 0x00, 0x00), '#000000');
|
||||
assert.equal(toCss(0x10, 0x10, 0x10), '#101010');
|
||||
assert.equal(toCss(0x20, 0x20, 0x20), '#202020');
|
||||
assert.equal(toCss(0x30, 0x30, 0x30), '#303030');
|
||||
assert.equal(toCss(0x40, 0x40, 0x40), '#404040');
|
||||
assert.equal(toCss(0x50, 0x50, 0x50), '#505050');
|
||||
assert.equal(toCss(0x60, 0x60, 0x60), '#606060');
|
||||
assert.equal(toCss(0x70, 0x70, 0x70), '#707070');
|
||||
assert.equal(toCss(0x80, 0x80, 0x80), '#808080');
|
||||
assert.equal(toCss(0x90, 0x90, 0x90), '#909090');
|
||||
assert.equal(toCss(0xa0, 0xa0, 0xa0), '#a0a0a0');
|
||||
assert.equal(toCss(0xb0, 0xb0, 0xb0), '#b0b0b0');
|
||||
assert.equal(toCss(0xc0, 0xc0, 0xc0), '#c0c0c0');
|
||||
assert.equal(toCss(0xd0, 0xd0, 0xd0), '#d0d0d0');
|
||||
assert.equal(toCss(0xe0, 0xe0, 0xe0), '#e0e0e0');
|
||||
assert.equal(toCss(0xf0, 0xf0, 0xf0), '#f0f0f0');
|
||||
assert.equal(toCss(0xff, 0xff, 0xff), '#ffffff');
|
||||
});
|
||||
});
|
||||
|
||||
describe('toRgba', () => {
|
||||
it('should convert an rgb array to an rgba number', () => {
|
||||
assert.equal(toRgba(0x00, 0x00, 0x00), 0x000000FF);
|
||||
assert.equal(toRgba(0x10, 0x10, 0x10), 0x101010FF);
|
||||
assert.equal(toRgba(0x20, 0x20, 0x20), 0x202020FF);
|
||||
assert.equal(toRgba(0x30, 0x30, 0x30), 0x303030FF);
|
||||
assert.equal(toRgba(0x40, 0x40, 0x40), 0x404040FF);
|
||||
assert.equal(toRgba(0x50, 0x50, 0x50), 0x505050FF);
|
||||
assert.equal(toRgba(0x60, 0x60, 0x60), 0x606060FF);
|
||||
assert.equal(toRgba(0x70, 0x70, 0x70), 0x707070FF);
|
||||
assert.equal(toRgba(0x80, 0x80, 0x80), 0x808080FF);
|
||||
assert.equal(toRgba(0x90, 0x90, 0x90), 0x909090FF);
|
||||
assert.equal(toRgba(0xa0, 0xa0, 0xa0), 0xa0a0a0FF);
|
||||
assert.equal(toRgba(0xb0, 0xb0, 0xb0), 0xb0b0b0FF);
|
||||
assert.equal(toRgba(0xc0, 0xc0, 0xc0), 0xc0c0c0FF);
|
||||
assert.equal(toRgba(0xd0, 0xd0, 0xd0), 0xd0d0d0FF);
|
||||
assert.equal(toRgba(0xe0, 0xe0, 0xe0), 0xe0e0e0FF);
|
||||
assert.equal(toRgba(0xf0, 0xf0, 0xf0), 0xf0f0f0FF);
|
||||
assert.equal(toRgba(0xff, 0xff, 0xff), 0xffffffFF);
|
||||
});
|
||||
it('should convert an rgba array to an rgba number', () => {
|
||||
assert.equal(toRgba(0x00, 0x00, 0x00, 0x00), 0x00000000);
|
||||
assert.equal(toRgba(0x10, 0x10, 0x10, 0x10), 0x10101010);
|
||||
assert.equal(toRgba(0x20, 0x20, 0x20, 0x20), 0x20202020);
|
||||
assert.equal(toRgba(0x30, 0x30, 0x30, 0x30), 0x30303030);
|
||||
assert.equal(toRgba(0x40, 0x40, 0x40, 0x40), 0x40404040);
|
||||
assert.equal(toRgba(0x50, 0x50, 0x50, 0x50), 0x50505050);
|
||||
assert.equal(toRgba(0x60, 0x60, 0x60, 0x60), 0x60606060);
|
||||
assert.equal(toRgba(0x70, 0x70, 0x70, 0x70), 0x70707070);
|
||||
assert.equal(toRgba(0x80, 0x80, 0x80, 0x80), 0x80808080);
|
||||
assert.equal(toRgba(0x90, 0x90, 0x90, 0x90), 0x90909090);
|
||||
assert.equal(toRgba(0xa0, 0xa0, 0xa0, 0xa0), 0xa0a0a0a0);
|
||||
assert.equal(toRgba(0xb0, 0xb0, 0xb0, 0xb0), 0xb0b0b0b0);
|
||||
assert.equal(toRgba(0xc0, 0xc0, 0xc0, 0xc0), 0xc0c0c0c0);
|
||||
assert.equal(toRgba(0xd0, 0xd0, 0xd0, 0xd0), 0xd0d0d0d0);
|
||||
assert.equal(toRgba(0xe0, 0xe0, 0xe0, 0xe0), 0xe0e0e0e0);
|
||||
assert.equal(toRgba(0xf0, 0xf0, 0xf0, 0xf0), 0xf0f0f0f0);
|
||||
assert.equal(toRgba(0xff, 0xff, 0xff, 0xff), 0xffffffff);
|
||||
});
|
||||
});
|
||||
|
||||
describe('fromRgba', () => {
|
||||
it('should convert an rgba number to an rgba array', () => {
|
||||
assert.deepEqual(fromRgba(0x00000000), [0x00, 0x00, 0x00, 0x00]);
|
||||
assert.deepEqual(fromRgba(0x10101010), [0x10, 0x10, 0x10, 0x10]);
|
||||
assert.deepEqual(fromRgba(0x20202020), [0x20, 0x20, 0x20, 0x20]);
|
||||
assert.deepEqual(fromRgba(0x30303030), [0x30, 0x30, 0x30, 0x30]);
|
||||
assert.deepEqual(fromRgba(0x40404040), [0x40, 0x40, 0x40, 0x40]);
|
||||
assert.deepEqual(fromRgba(0x50505050), [0x50, 0x50, 0x50, 0x50]);
|
||||
assert.deepEqual(fromRgba(0x60606060), [0x60, 0x60, 0x60, 0x60]);
|
||||
assert.deepEqual(fromRgba(0x70707070), [0x70, 0x70, 0x70, 0x70]);
|
||||
assert.deepEqual(fromRgba(0x80808080), [0x80, 0x80, 0x80, 0x80]);
|
||||
assert.deepEqual(fromRgba(0x90909090), [0x90, 0x90, 0x90, 0x90]);
|
||||
assert.deepEqual(fromRgba(0xa0a0a0a0), [0xa0, 0xa0, 0xa0, 0xa0]);
|
||||
assert.deepEqual(fromRgba(0xb0b0b0b0), [0xb0, 0xb0, 0xb0, 0xb0]);
|
||||
assert.deepEqual(fromRgba(0xc0c0c0c0), [0xc0, 0xc0, 0xc0, 0xc0]);
|
||||
assert.deepEqual(fromRgba(0xd0d0d0d0), [0xd0, 0xd0, 0xd0, 0xd0]);
|
||||
assert.deepEqual(fromRgba(0xe0e0e0e0), [0xe0, 0xe0, 0xe0, 0xe0]);
|
||||
assert.deepEqual(fromRgba(0xf0f0f0f0), [0xf0, 0xf0, 0xf0, 0xf0]);
|
||||
assert.deepEqual(fromRgba(0xffffffff), [0xff, 0xff, 0xff, 0xff]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('opaque', () => {
|
||||
it('should make the color opaque', () => {
|
||||
assert.deepEqual(opaque({ css: '#00000000', rgba: 0x00000000 }), { css: '#000000', rgba: 0x000000FF });
|
||||
assert.deepEqual(opaque({ css: '#10101010', rgba: 0x10101010 }), { css: '#101010', rgba: 0x101010FF });
|
||||
assert.deepEqual(opaque({ css: '#20202020', rgba: 0x20202020 }), { css: '#202020', rgba: 0x202020FF });
|
||||
assert.deepEqual(opaque({ css: '#30303030', rgba: 0x30303030 }), { css: '#303030', rgba: 0x303030FF });
|
||||
assert.deepEqual(opaque({ css: '#40404040', rgba: 0x40404040 }), { css: '#404040', rgba: 0x404040FF });
|
||||
assert.deepEqual(opaque({ css: '#50505050', rgba: 0x50505050 }), { css: '#505050', rgba: 0x505050FF });
|
||||
assert.deepEqual(opaque({ css: '#60606060', rgba: 0x60606060 }), { css: '#606060', rgba: 0x606060FF });
|
||||
assert.deepEqual(opaque({ css: '#70707070', rgba: 0x70707070 }), { css: '#707070', rgba: 0x707070FF });
|
||||
assert.deepEqual(opaque({ css: '#80808080', rgba: 0x80808080 }), { css: '#808080', rgba: 0x808080FF });
|
||||
assert.deepEqual(opaque({ css: '#90909090', rgba: 0x90909090 }), { css: '#909090', rgba: 0x909090FF });
|
||||
assert.deepEqual(opaque({ css: '#a0a0a0a0', rgba: 0xa0a0a0a0 }), { css: '#a0a0a0', rgba: 0xa0a0a0FF });
|
||||
assert.deepEqual(opaque({ css: '#b0b0b0b0', rgba: 0xb0b0b0b0 }), { css: '#b0b0b0', rgba: 0xb0b0b0FF });
|
||||
assert.deepEqual(opaque({ css: '#c0c0c0c0', rgba: 0xc0c0c0c0 }), { css: '#c0c0c0', rgba: 0xc0c0c0FF });
|
||||
assert.deepEqual(opaque({ css: '#d0d0d0d0', rgba: 0xd0d0d0d0 }), { css: '#d0d0d0', rgba: 0xd0d0d0FF });
|
||||
assert.deepEqual(opaque({ css: '#e0e0e0e0', rgba: 0xe0e0e0e0 }), { css: '#e0e0e0', rgba: 0xe0e0e0FF });
|
||||
assert.deepEqual(opaque({ css: '#f0f0f0f0', rgba: 0xf0f0f0f0 }), { css: '#f0f0f0', rgba: 0xf0f0f0FF });
|
||||
assert.deepEqual(opaque({ css: '#ffffffff', rgba: 0xffffffff }), { css: '#ffffff', rgba: 0xffffffFF });
|
||||
});
|
||||
});
|
||||
|
||||
describe('rgbRelativeLuminance', () => {
|
||||
it('should calculate the relative luminance of the color', () => {
|
||||
assert.equal(rgbRelativeLuminance(0x000000), 0);
|
||||
assert.equal(rgbRelativeLuminance(0x101010).toFixed(4), '0.0052');
|
||||
assert.equal(rgbRelativeLuminance(0x202020).toFixed(4), '0.0144');
|
||||
assert.equal(rgbRelativeLuminance(0x303030).toFixed(4), '0.0296');
|
||||
assert.equal(rgbRelativeLuminance(0x404040).toFixed(4), '0.0513');
|
||||
assert.equal(rgbRelativeLuminance(0x505050).toFixed(4), '0.0802');
|
||||
assert.equal(rgbRelativeLuminance(0x606060).toFixed(4), '0.1170');
|
||||
assert.equal(rgbRelativeLuminance(0x707070).toFixed(4), '0.1620');
|
||||
assert.equal(rgbRelativeLuminance(0x808080).toFixed(4), '0.2159');
|
||||
assert.equal(rgbRelativeLuminance(0x909090).toFixed(4), '0.2789');
|
||||
assert.equal(rgbRelativeLuminance(0xA0A0A0).toFixed(4), '0.3515');
|
||||
assert.equal(rgbRelativeLuminance(0xB0B0B0).toFixed(4), '0.4342');
|
||||
assert.equal(rgbRelativeLuminance(0xC0C0C0).toFixed(4), '0.5271');
|
||||
assert.equal(rgbRelativeLuminance(0xD0D0D0).toFixed(4), '0.6308');
|
||||
assert.equal(rgbRelativeLuminance(0xE0E0E0).toFixed(4), '0.7454');
|
||||
assert.equal(rgbRelativeLuminance(0xF0F0F0).toFixed(4), '0.8714');
|
||||
assert.equal(rgbRelativeLuminance(0xFFFFFF), 1);
|
||||
});
|
||||
});
|
||||
describe('contrastRatio', () => {
|
||||
it('should calculate the relative luminance of the color', () => {
|
||||
assert.equal(contrastRatio(0, 0), 1);
|
||||
assert.equal(contrastRatio(0, 0.5), 11);
|
||||
assert.equal(contrastRatio(0, 1), 21);
|
||||
});
|
||||
it('should work regardless of the parameter order', () => {
|
||||
assert.equal(contrastRatio(0, 1), 21);
|
||||
assert.equal(contrastRatio(1, 0), 21);
|
||||
});
|
||||
});
|
||||
describe('ensureContrastRatioRgba', () => {
|
||||
it('should return undefined if the color already meets the contrast ratio (black bg)', () => {
|
||||
assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 1), undefined);
|
||||
assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 2), undefined);
|
||||
assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 3), undefined);
|
||||
});
|
||||
it('should return a color that meets the contrast ratio (black bg)', () => {
|
||||
assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 4), 0x707070ff);
|
||||
assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 5), 0x7f7f7fff);
|
||||
assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 6), 0x8c8c8cff);
|
||||
assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 7), 0x989898ff);
|
||||
assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 8), 0xa3a3a3ff);
|
||||
assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 9), 0xadadadff);
|
||||
assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 10), 0xb6b6b6ff);
|
||||
assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 11), 0xbebebeff);
|
||||
assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 12), 0xc5c5c5ff);
|
||||
assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 13), 0xd1d1d1ff);
|
||||
assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 14), 0xd6d6d6ff);
|
||||
assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 15), 0xdbdbdbff);
|
||||
assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 16), 0xe3e3e3ff);
|
||||
assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 17), 0xe9e9e9ff);
|
||||
assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 18), 0xeeeeeeff);
|
||||
assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 19), 0xf4f4f4ff);
|
||||
assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 20), 0xfafafaff);
|
||||
assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 21), 0xffffffff);
|
||||
});
|
||||
it('should return undefined if the color already meets the contrast ratio (white bg)', () => {
|
||||
assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 1), undefined);
|
||||
assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 2), undefined);
|
||||
assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 3), undefined);
|
||||
assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 4), undefined);
|
||||
assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 5), undefined);
|
||||
assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 6), undefined);
|
||||
});
|
||||
it('should return a color that meets the contrast ratio (white bg)', () => {
|
||||
assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 7), 0x565656ff);
|
||||
assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 8), 0x4d4d4dff);
|
||||
assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 9), 0x454545ff);
|
||||
assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 10), 0x3e3e3eff);
|
||||
assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 11), 0x373737ff);
|
||||
assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 12), 0x313131ff);
|
||||
assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 13), 0x313131ff);
|
||||
assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 14), 0x272727ff);
|
||||
assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 15), 0x232323ff);
|
||||
assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 16), 0x1f1f1fff);
|
||||
assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 17), 0x1b1b1bff);
|
||||
assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 18), 0x151515ff);
|
||||
assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 19), 0x101010ff);
|
||||
assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 20), 0x080808ff);
|
||||
assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 21), 0x000000ff);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,181 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { IColor } from 'browser/Types';
|
||||
|
||||
export function blend(bg: IColor, fg: IColor): IColor {
|
||||
const a = (fg.rgba & 0xFF) / 255;
|
||||
if (a === 1) {
|
||||
return {
|
||||
css: fg.css,
|
||||
rgba: fg.rgba
|
||||
};
|
||||
}
|
||||
const fgR = (fg.rgba >> 24) & 0xFF;
|
||||
const fgG = (fg.rgba >> 16) & 0xFF;
|
||||
const fgB = (fg.rgba >> 8) & 0xFF;
|
||||
const bgR = (bg.rgba >> 24) & 0xFF;
|
||||
const bgG = (bg.rgba >> 16) & 0xFF;
|
||||
const bgB = (bg.rgba >> 8) & 0xFF;
|
||||
const r = bgR + Math.round((fgR - bgR) * a);
|
||||
const g = bgG + Math.round((fgG - bgG) * a);
|
||||
const b = bgB + Math.round((fgB - bgB) * a);
|
||||
const css = toCss(r, g, b);
|
||||
const rgba = toRgba(r, g, b);
|
||||
return { css, rgba };
|
||||
}
|
||||
|
||||
export function fromCss(css: string): IColor {
|
||||
return {
|
||||
css,
|
||||
rgba: (parseInt(css.slice(1), 16) << 8 | 0xFF) >>> 0
|
||||
};
|
||||
}
|
||||
|
||||
export function toPaddedHex(c: number): string {
|
||||
const s = c.toString(16);
|
||||
return s.length < 2 ? '0' + s : s;
|
||||
}
|
||||
|
||||
export function toCss(r: number, g: number, b: number, a?: number): string {
|
||||
if (a !== undefined) {
|
||||
return `#${toPaddedHex(r)}${toPaddedHex(g)}${toPaddedHex(b)}${toPaddedHex(a)}`;
|
||||
}
|
||||
return `#${toPaddedHex(r)}${toPaddedHex(g)}${toPaddedHex(b)}`;
|
||||
}
|
||||
|
||||
export function toRgba(r: number, g: number, b: number, a: number = 0xFF): number {
|
||||
// >>> 0 forces an unsigned int
|
||||
return (r << 24 | g << 16 | b << 8 | a) >>> 0;
|
||||
}
|
||||
|
||||
export function fromRgba(value: number): [number, number, number, number] {
|
||||
return [(value >> 24) & 0xFF, (value >> 16) & 0xFF, (value >> 8) & 0xFF, value & 0xFF];
|
||||
}
|
||||
|
||||
export function opaque(color: IColor): IColor {
|
||||
const rgba = (color.rgba | 0xFF) >>> 0;
|
||||
const [r, g, b] = fromRgba(rgba);
|
||||
return {
|
||||
css: toCss(r, g, b),
|
||||
rgba
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the relative luminance of an RGB color, this is useful in determining the contrast ratio
|
||||
* between two colors.
|
||||
* @param rgb The color to use.
|
||||
* @see https://www.w3.org/TR/WCAG20/#relativeluminancedef
|
||||
*/
|
||||
export function rgbRelativeLuminance(rgb: number): number {
|
||||
return rgbRelativeLuminance2(
|
||||
(rgb >> 16) & 0xFF,
|
||||
(rgb >> 8 ) & 0xFF,
|
||||
(rgb ) & 0xFF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the relative luminance of an RGB color, this is useful in determining the contrast ratio
|
||||
* between two colors.
|
||||
* @param r The red channel (0x00 to 0xFF).
|
||||
* @param g The green channel (0x00 to 0xFF).
|
||||
* @param b The blue channel (0x00 to 0xFF).
|
||||
* @see https://www.w3.org/TR/WCAG20/#relativeluminancedef
|
||||
*/
|
||||
export function rgbRelativeLuminance2(r: number, g: number, b: number): number {
|
||||
const rs = r / 255;
|
||||
const gs = g / 255;
|
||||
const bs = b / 255;
|
||||
const rr = rs <= 0.03928 ? rs / 12.92 : Math.pow((rs + 0.055) / 1.055, 2.4);
|
||||
const rg = gs <= 0.03928 ? gs / 12.92 : Math.pow((gs + 0.055) / 1.055, 2.4);
|
||||
const rb = bs <= 0.03928 ? bs / 12.92 : Math.pow((bs + 0.055) / 1.055, 2.4);
|
||||
return rr * 0.2126 + rg * 0.7152 + rb * 0.0722;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the contrast ratio between two relative luminance values.
|
||||
* @param l1 The first relative luminance.
|
||||
* @param l2 The first relative luminance.
|
||||
* @see https://www.w3.org/TR/WCAG20/#contrast-ratiodef
|
||||
*/
|
||||
export function contrastRatio(l1: number, l2: number): number {
|
||||
if (l1 < l2) {
|
||||
return (l2 + 0.05) / (l1 + 0.05);
|
||||
}
|
||||
return (l1 + 0.05) / (l2 + 0.05);
|
||||
}
|
||||
|
||||
export function rgbaToColor(r: number, g: number, b: number): IColor {
|
||||
return {
|
||||
css: toCss(r, g, b),
|
||||
rgba: toRgba(r, g, b)
|
||||
};
|
||||
}
|
||||
|
||||
export function ensureContrastRatioRgba(bgRgba: number, fgRgba: number, ratio: number): number | undefined {
|
||||
const bgL = rgbRelativeLuminance(bgRgba >> 8);
|
||||
const fgL = rgbRelativeLuminance(fgRgba >> 8);
|
||||
const cr = contrastRatio(bgL, fgL);
|
||||
if (cr < ratio) {
|
||||
if (fgL < bgL) {
|
||||
return reduceLuminance(bgRgba, fgRgba, ratio);
|
||||
}
|
||||
return increaseLuminance(bgRgba, fgRgba, ratio);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function ensureContrastRatio(bg: IColor, fg: IColor, ratio: number): IColor | undefined {
|
||||
const result = ensureContrastRatioRgba(bg.rgba, fg.rgba, ratio);
|
||||
if (!result) {
|
||||
return undefined;
|
||||
}
|
||||
return rgbaToColor(
|
||||
(result >> 24 & 0xFF),
|
||||
(result >> 16 & 0xFF),
|
||||
(result >> 8 & 0xFF)
|
||||
);
|
||||
}
|
||||
|
||||
export function reduceLuminance(bgRgba: number, fgRgba: number, ratio: number): number {
|
||||
// This is a naive but fast approach to reducing luminance as converting to
|
||||
// HSL and back is expensive
|
||||
const bgR = (bgRgba >> 24) & 0xFF;
|
||||
const bgG = (bgRgba >> 16) & 0xFF;
|
||||
const bgB = (bgRgba >> 8) & 0xFF;
|
||||
let fgR = (fgRgba >> 24) & 0xFF;
|
||||
let fgG = (fgRgba >> 16) & 0xFF;
|
||||
let fgB = (fgRgba >> 8) & 0xFF;
|
||||
let cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB));
|
||||
while (cr < ratio && (fgR > 0 || fgG > 0 || fgB > 0)) {
|
||||
// Reduce by 10% until the ratio is hit
|
||||
fgR -= Math.max(0, Math.ceil(fgR * 0.1));
|
||||
fgG -= Math.max(0, Math.ceil(fgG * 0.1));
|
||||
fgB -= Math.max(0, Math.ceil(fgB * 0.1));
|
||||
cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB));
|
||||
}
|
||||
return (fgR << 24 | fgG << 16 | fgB << 8 | 0xFF) >>> 0;
|
||||
}
|
||||
|
||||
export function increaseLuminance(bgRgba: number, fgRgba: number, ratio: number): number {
|
||||
// This is a naive but fast approach to increasing luminance as converting to
|
||||
// HSL and back is expensive
|
||||
const bgR = (bgRgba >> 24) & 0xFF;
|
||||
const bgG = (bgRgba >> 16) & 0xFF;
|
||||
const bgB = (bgRgba >> 8) & 0xFF;
|
||||
let fgR = (fgRgba >> 24) & 0xFF;
|
||||
let fgG = (fgRgba >> 16) & 0xFF;
|
||||
let fgB = (fgRgba >> 8) & 0xFF;
|
||||
let cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB));
|
||||
while (cr < ratio && (fgR < 0xFF || fgG < 0xFF || fgB < 0xFF)) {
|
||||
// Increase by 10% until the ratio is hit
|
||||
fgR = Math.min(0xFF, fgR + Math.ceil((255 - fgR) * 0.1));
|
||||
fgG = Math.min(0xFF, fgG + Math.ceil((255 - fgG) * 0.1));
|
||||
fgB = Math.min(0xFF, fgB + Math.ceil((255 - fgB) * 0.1));
|
||||
cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB));
|
||||
}
|
||||
return (fgR << 24 | fgG << 16 | fgB << 8 | 0xFF) >>> 0;
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { assert } from 'chai';
|
||||
import { ColorContrastCache } from 'browser/ColorContrastCache';
|
||||
|
||||
describe('ColorContrastCache', () => {
|
||||
let cache: ColorContrastCache;
|
||||
|
||||
beforeEach(() => {
|
||||
cache = new ColorContrastCache();
|
||||
});
|
||||
|
||||
it('should save and get color values', () => {
|
||||
assert.strictEqual(cache.getColor(0x01, 0x00), undefined);
|
||||
cache.setColor(0x01, 0x01, null);
|
||||
assert.strictEqual(cache.getColor(0x01, 0x01), null);
|
||||
cache.setColor(0x01, 0x02, { css: '#030303', rgba: 0x030303ff});
|
||||
assert.deepEqual(cache.getColor(0x01, 0x02), { css: '#030303', rgba: 0x030303ff});
|
||||
});
|
||||
|
||||
it('should save and get css values', () => {
|
||||
assert.strictEqual(cache.getCss(0x01, 0x00), undefined);
|
||||
cache.setCss(0x01, 0x01, null);
|
||||
assert.strictEqual(cache.getCss(0x01, 0x01), null);
|
||||
cache.setCss(0x01, 0x02, '#030303');
|
||||
assert.deepEqual(cache.getCss(0x01, 0x02), '#030303');
|
||||
});
|
||||
|
||||
it('should clear all values on clear', () => {
|
||||
cache.setColor(0x01, 0x01, null);
|
||||
cache.setColor(0x01, 0x02, { css: '#030303', rgba: 0x030303ff});
|
||||
cache.setCss(0x01, 0x01, null);
|
||||
cache.setCss(0x01, 0x02, '#030303');
|
||||
cache.clear();
|
||||
assert.strictEqual(cache.getColor(0x01, 0x01), undefined);
|
||||
assert.strictEqual(cache.getColor(0x01, 0x02), undefined);
|
||||
assert.strictEqual(cache.getCss(0x01, 0x01), undefined);
|
||||
assert.strictEqual(cache.getCss(0x01, 0x02), undefined);
|
||||
});
|
||||
});
|
||||
@@ -1,38 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { IColor, IColorContrastCache } from 'browser/Types';
|
||||
|
||||
export class ColorContrastCache implements IColorContrastCache {
|
||||
private _color: { [bg: number]: { [fg: number]: IColor | null | undefined } | undefined } = {};
|
||||
private _rgba: { [bg: number]: { [fg: number]: string | null | undefined } | undefined } = {};
|
||||
|
||||
public clear(): void {
|
||||
this._color = {};
|
||||
this._rgba = {};
|
||||
}
|
||||
|
||||
public setCss(bg: number, fg: number, value: string | null): void {
|
||||
if (!this._rgba[bg]) {
|
||||
this._rgba[bg] = {};
|
||||
}
|
||||
this._rgba[bg]![fg] = value;
|
||||
}
|
||||
|
||||
public getCss(bg: number, fg: number): string | null | undefined {
|
||||
return this._rgba[bg] ? this._rgba[bg]![fg] : undefined;
|
||||
}
|
||||
|
||||
public setColor(bg: number, fg: number, value: IColor | null): void {
|
||||
if (!this._color[bg]) {
|
||||
this._color[bg] = {};
|
||||
}
|
||||
this._color[bg]![fg] = value;
|
||||
}
|
||||
|
||||
public getColor(bg: number, fg: number): IColor | null | undefined {
|
||||
return this._color[bg] ? this._color[bg]![fg] : undefined;
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,7 @@ describe('ColorManager', () => {
|
||||
describe('constructor', () => {
|
||||
it('should fill all colors with values', () => {
|
||||
for (const key of Object.keys(cm.colors)) {
|
||||
if (key !== 'ansi' && key !== 'contrastCache') {
|
||||
if (key !== 'ansi') {
|
||||
// A #rrggbb or rgba(...)
|
||||
assert.ok((<any>cm.colors)[key].css.length >= 7);
|
||||
}
|
||||
|
||||
+64
-92
@@ -3,18 +3,16 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { IColorManager, IColor, IColorSet, IColorContrastCache } from 'browser/Types';
|
||||
import { IColorManager, IColor, IColorSet } from 'browser/Types';
|
||||
import { ITheme } from 'common/services/Services';
|
||||
import { fromCss, toCss, blend, toRgba, toPaddedHex } from 'browser/Color';
|
||||
import { ColorContrastCache } from 'browser/ColorContrastCache';
|
||||
|
||||
const DEFAULT_FOREGROUND = fromCss('#ffffff');
|
||||
const DEFAULT_BACKGROUND = fromCss('#000000');
|
||||
const DEFAULT_CURSOR = fromCss('#ffffff');
|
||||
const DEFAULT_CURSOR_ACCENT = fromCss('#000000');
|
||||
const DEFAULT_FOREGROUND = fromHex('#ffffff');
|
||||
const DEFAULT_BACKGROUND = fromHex('#000000');
|
||||
const DEFAULT_CURSOR = fromHex('#ffffff');
|
||||
const DEFAULT_CURSOR_ACCENT = fromHex('#000000');
|
||||
const DEFAULT_SELECTION = {
|
||||
css: 'rgba(255, 255, 255, 0.3)',
|
||||
rgba: 0xFFFFFF4D
|
||||
rgba: 0xFFFFFF77
|
||||
};
|
||||
|
||||
// An IIFE to generate DEFAULT_ANSI_COLORS. Do not mutate DEFAULT_ANSI_COLORS, instead make a copy
|
||||
@@ -22,23 +20,23 @@ const DEFAULT_SELECTION = {
|
||||
export const DEFAULT_ANSI_COLORS = (() => {
|
||||
const colors = [
|
||||
// dark:
|
||||
fromCss('#2e3436'),
|
||||
fromCss('#cc0000'),
|
||||
fromCss('#4e9a06'),
|
||||
fromCss('#c4a000'),
|
||||
fromCss('#3465a4'),
|
||||
fromCss('#75507b'),
|
||||
fromCss('#06989a'),
|
||||
fromCss('#d3d7cf'),
|
||||
fromHex('#2e3436'),
|
||||
fromHex('#cc0000'),
|
||||
fromHex('#4e9a06'),
|
||||
fromHex('#c4a000'),
|
||||
fromHex('#3465a4'),
|
||||
fromHex('#75507b'),
|
||||
fromHex('#06989a'),
|
||||
fromHex('#d3d7cf'),
|
||||
// bright:
|
||||
fromCss('#555753'),
|
||||
fromCss('#ef2929'),
|
||||
fromCss('#8ae234'),
|
||||
fromCss('#fce94f'),
|
||||
fromCss('#729fcf'),
|
||||
fromCss('#ad7fa8'),
|
||||
fromCss('#34e2e2'),
|
||||
fromCss('#eeeeec')
|
||||
fromHex('#555753'),
|
||||
fromHex('#ef2929'),
|
||||
fromHex('#8ae234'),
|
||||
fromHex('#fce94f'),
|
||||
fromHex('#729fcf'),
|
||||
fromHex('#ad7fa8'),
|
||||
fromHex('#34e2e2'),
|
||||
fromHex('#eeeeec')
|
||||
];
|
||||
|
||||
// Fill in the remaining 240 ANSI colors.
|
||||
@@ -49,23 +47,37 @@ export const DEFAULT_ANSI_COLORS = (() => {
|
||||
const g = v[(i / 6) % 6 | 0];
|
||||
const b = v[i % 6];
|
||||
colors.push({
|
||||
css: toCss(r, g, b),
|
||||
rgba: toRgba(r, g, b)
|
||||
css: `#${toPaddedHex(r)}${toPaddedHex(g)}${toPaddedHex(b)}`,
|
||||
// Use >>> 0 to force a conversion to an unsigned int
|
||||
rgba: ((r << 24) | (g << 16) | (b << 8) | 0xFF) >>> 0
|
||||
});
|
||||
}
|
||||
|
||||
// Generate greys (232-255)
|
||||
for (let i = 0; i < 24; i++) {
|
||||
const c = 8 + i * 10;
|
||||
const ch = toPaddedHex(c);
|
||||
colors.push({
|
||||
css: toCss(c, c, c),
|
||||
rgba: toRgba(c, c, c)
|
||||
css: `#${ch}${ch}${ch}`,
|
||||
rgba: ((c << 24) | (c << 16) | (c << 8) | 0xFF) >>> 0
|
||||
});
|
||||
}
|
||||
|
||||
return colors;
|
||||
})();
|
||||
|
||||
function fromHex(css: string): IColor {
|
||||
return {
|
||||
css,
|
||||
rgba: parseInt(css.slice(1), 16) << 8 | 0xFF
|
||||
};
|
||||
}
|
||||
|
||||
function toPaddedHex(c: number): string {
|
||||
const s = c.toString(16);
|
||||
return s.length < 2 ? '0' + s : s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Manages the source of truth for a terminal's colors.
|
||||
*/
|
||||
@@ -73,7 +85,6 @@ export class ColorManager implements IColorManager {
|
||||
public colors: IColorSet;
|
||||
private _ctx: CanvasRenderingContext2D;
|
||||
private _litmusColor: CanvasGradient;
|
||||
private _contrastCache: IColorContrastCache;
|
||||
|
||||
constructor(document: Document, public allowTransparency: boolean) {
|
||||
const canvas = document.createElement('canvas');
|
||||
@@ -86,25 +97,16 @@ export class ColorManager implements IColorManager {
|
||||
this._ctx = ctx;
|
||||
this._ctx.globalCompositeOperation = 'copy';
|
||||
this._litmusColor = this._ctx.createLinearGradient(0, 0, 1, 1);
|
||||
this._contrastCache = new ColorContrastCache();
|
||||
this.colors = {
|
||||
foreground: DEFAULT_FOREGROUND,
|
||||
background: DEFAULT_BACKGROUND,
|
||||
cursor: DEFAULT_CURSOR,
|
||||
cursorAccent: DEFAULT_CURSOR_ACCENT,
|
||||
selection: DEFAULT_SELECTION,
|
||||
selectionOpaque: blend(DEFAULT_BACKGROUND, DEFAULT_SELECTION),
|
||||
ansi: DEFAULT_ANSI_COLORS.slice(),
|
||||
contrastCache: this._contrastCache
|
||||
ansi: DEFAULT_ANSI_COLORS.slice()
|
||||
};
|
||||
}
|
||||
|
||||
public onOptionsChange(key: string): void {
|
||||
if (key === 'minimumContrastRatio') {
|
||||
this._contrastCache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the terminal's theme.
|
||||
* @param theme The theme to use. If a partial theme is provided then default
|
||||
@@ -116,7 +118,6 @@ export class ColorManager implements IColorManager {
|
||||
this.colors.cursor = this._parseColor(theme.cursor, DEFAULT_CURSOR, true);
|
||||
this.colors.cursorAccent = this._parseColor(theme.cursorAccent, DEFAULT_CURSOR_ACCENT, true);
|
||||
this.colors.selection = this._parseColor(theme.selection, DEFAULT_SELECTION, true);
|
||||
this.colors.selectionOpaque = blend(this.colors.background, this.colors.selection);
|
||||
this.colors.ansi[0] = this._parseColor(theme.black, DEFAULT_ANSI_COLORS[0]);
|
||||
this.colors.ansi[1] = this._parseColor(theme.red, DEFAULT_ANSI_COLORS[1]);
|
||||
this.colors.ansi[2] = this._parseColor(theme.green, DEFAULT_ANSI_COLORS[2]);
|
||||
@@ -133,8 +134,6 @@ export class ColorManager implements IColorManager {
|
||||
this.colors.ansi[13] = this._parseColor(theme.brightMagenta, DEFAULT_ANSI_COLORS[13]);
|
||||
this.colors.ansi[14] = this._parseColor(theme.brightCyan, DEFAULT_ANSI_COLORS[14]);
|
||||
this.colors.ansi[15] = this._parseColor(theme.brightWhite, DEFAULT_ANSI_COLORS[15]);
|
||||
// Clear our the cache
|
||||
this._contrastCache.clear();
|
||||
}
|
||||
|
||||
private _parseColor(
|
||||
@@ -159,60 +158,33 @@ export class ColorManager implements IColorManager {
|
||||
this._ctx.fillRect(0, 0, 1, 1);
|
||||
const data = this._ctx.getImageData(0, 0, 1, 1).data;
|
||||
|
||||
// Check if the printed color was transparent
|
||||
if (data[3] !== 0xFF) {
|
||||
if (!allowTransparency) {
|
||||
// Ideally we'd just ignore the alpha channel, but...
|
||||
//
|
||||
// Browsers may not give back exactly the same RGB values we put in, because most/all
|
||||
// convert the color to a pre-multiplied representation. getImageData converts that back to
|
||||
// a un-premultipled representation, but the precision loss may make the RGB channels unuable
|
||||
// on their own.
|
||||
//
|
||||
// E.g. In Chrome #12345610 turns into #10305010, and in the extreme case, 0xFFFFFF00 turns
|
||||
// into 0x00000000.
|
||||
//
|
||||
// "Note: Due to the lossy nature of converting to and from premultiplied alpha color values,
|
||||
// pixels that have just been set using putImageData() might be returned to an equivalent
|
||||
// getImageData() as different values."
|
||||
// -- https://html.spec.whatwg.org/multipage/canvas.html#pixel-manipulation
|
||||
//
|
||||
// So let's just use the fallback color in this case instead.
|
||||
console.warn(
|
||||
`Color: ${css} is using transparency, but allowTransparency is false. ` +
|
||||
`Using fallback ${fallback.css}.`
|
||||
);
|
||||
return fallback;
|
||||
}
|
||||
let r: number;
|
||||
let g: number;
|
||||
let b: number;
|
||||
let a: number;
|
||||
let rgba: number;
|
||||
if (css.length === 5) {
|
||||
const num = parseInt(css.substr(1), 16);
|
||||
r = ((num >> 12) & 0xF) * 16;
|
||||
g = ((num >> 8) & 0xF) * 16;
|
||||
b = ((num >> 4) & 0xF) * 16;
|
||||
a = (num & 0xF) * 16;
|
||||
rgba = toRgba(r, g, b, a);
|
||||
} else {
|
||||
rgba = parseInt(css.substr(1), 16);
|
||||
r = (rgba >> 24) & 0xFF;
|
||||
g = (rgba >> 16) & 0xFF;
|
||||
b = (rgba >> 8) & 0xFF;
|
||||
a = (rgba ) & 0xFF;
|
||||
}
|
||||
|
||||
return {
|
||||
rgba,
|
||||
css: toCss(r, g, b, a)
|
||||
};
|
||||
if (!allowTransparency && data[3] !== 0xFF) {
|
||||
// Ideally we'd just ignore the alpha channel, but...
|
||||
//
|
||||
// Browsers may not give back exactly the same RGB values we put in, because most/all
|
||||
// convert the color to a pre-multiplied representation. getImageData converts that back to
|
||||
// a un-premultipled representation, but the precision loss may make the RGB channels unuable
|
||||
// on their own.
|
||||
//
|
||||
// E.g. In Chrome #12345610 turns into #10305010, and in the extreme case, 0xFFFFFF00 turns
|
||||
// into 0x00000000.
|
||||
//
|
||||
// "Note: Due to the lossy nature of converting to and from premultiplied alpha color values,
|
||||
// pixels that have just been set using putImageData() might be returned to an equivalent
|
||||
// getImageData() as different values."
|
||||
// -- https://html.spec.whatwg.org/multipage/canvas.html#pixel-manipulation
|
||||
//
|
||||
// So let's just use the fallback color in this case instead.
|
||||
console.warn(
|
||||
`Color: ${css} is using transparency, but allowTransparency is false. ` +
|
||||
`Using fallback ${fallback.css}.`
|
||||
);
|
||||
return fallback;
|
||||
}
|
||||
|
||||
return {
|
||||
css,
|
||||
rgba: toRgba(data[0], data[1], data[2], data[3])
|
||||
rgba: (data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3]) >>> 0
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -306,9 +306,7 @@ export class Linkifier implements ILinkifier {
|
||||
e => {
|
||||
this._onLinkTooltip.fire(this._createLinkHoverEvent(x1, y1, x2, y2, fg));
|
||||
if (matcher.hoverTooltipCallback) {
|
||||
// Note that IViewportRange use 1-based coordinates to align with escape sequences such
|
||||
// as CUP which use 1,1 as the default for row/col
|
||||
matcher.hoverTooltipCallback(e, uri, { start: { x: x1, y: y1 }, end: { x: x2, y: y2 } });
|
||||
matcher.hoverTooltipCallback(e, uri);
|
||||
}
|
||||
},
|
||||
() => {
|
||||
|
||||
@@ -175,9 +175,11 @@ export class MouseZoneManager extends Disposable implements IMouseZoneManager {
|
||||
// Find the active zone, prevent event propagation if found to prevent other
|
||||
// components from handling the mouse event.
|
||||
const zone = this._findZoneEventAt(e);
|
||||
if (zone?.willLinkActivate(e)) {
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
if (zone) {
|
||||
if (zone.willLinkActivate(e)) {
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Vendored
+3
-26
@@ -8,7 +8,6 @@ import { IDisposable } from 'common/Types';
|
||||
|
||||
export interface IColorManager {
|
||||
colors: IColorSet;
|
||||
onOptionsChange(key: string): void;
|
||||
}
|
||||
|
||||
export interface IColor {
|
||||
@@ -22,18 +21,7 @@ export interface IColorSet {
|
||||
cursor: IColor;
|
||||
cursorAccent: IColor;
|
||||
selection: IColor;
|
||||
/** The selection blended on top of background. */
|
||||
selectionOpaque: IColor;
|
||||
ansi: IColor[];
|
||||
contrastCache: IColorContrastCache;
|
||||
}
|
||||
|
||||
export interface IColorContrastCache {
|
||||
clear(): void;
|
||||
setCss(bg: number, fg: number, value: string | null): void;
|
||||
getCss(bg: number, fg: number): string | null | undefined;
|
||||
setColor(bg: number, fg: number, value: IColor | null): void;
|
||||
getColor(bg: number, fg: number): IColor | null | undefined;
|
||||
}
|
||||
|
||||
export interface IPartialColorSet {
|
||||
@@ -47,7 +35,7 @@ export interface IPartialColorSet {
|
||||
|
||||
export interface IViewport extends IDisposable {
|
||||
scrollBarWidth: number;
|
||||
syncScrollArea(immediate?: boolean): void;
|
||||
syncScrollArea(): void;
|
||||
getLinesScrolled(ev: WheelEvent): number;
|
||||
onWheel(ev: WheelEvent): boolean;
|
||||
onTouchStart(ev: TouchEvent): void;
|
||||
@@ -55,25 +43,14 @@ export interface IViewport extends IDisposable {
|
||||
onThemeChange(colors: IColorSet): void;
|
||||
}
|
||||
|
||||
export interface IViewportRange {
|
||||
start: IViewportRangePosition;
|
||||
end: IViewportRangePosition;
|
||||
}
|
||||
|
||||
export interface IViewportRangePosition {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
export type LinkMatcherHandler = (event: MouseEvent, uri: string) => void;
|
||||
export type LinkMatcherHoverTooltipCallback = (event: MouseEvent, uri: string, position: IViewportRange) => void;
|
||||
export type LinkMatcherValidationCallback = (uri: string, callback: (isValid: boolean) => void) => void;
|
||||
|
||||
export interface ILinkMatcher {
|
||||
id: number;
|
||||
regex: RegExp;
|
||||
handler: LinkMatcherHandler;
|
||||
hoverTooltipCallback?: LinkMatcherHoverTooltipCallback;
|
||||
hoverTooltipCallback?: LinkMatcherHandler;
|
||||
hoverLeaveCallback?: () => void;
|
||||
matchIndex?: number;
|
||||
validationCallback?: LinkMatcherValidationCallback;
|
||||
@@ -119,7 +96,7 @@ export interface ILinkMatcherOptions {
|
||||
/**
|
||||
* A callback that fires when the mouse hovers over a link.
|
||||
*/
|
||||
tooltipCallback?: LinkMatcherHoverTooltipCallback;
|
||||
tooltipCallback?: LinkMatcherHandler;
|
||||
/**
|
||||
* A callback that fires when the mouse leaves a link that was hovered.
|
||||
*/
|
||||
|
||||
+11
-30
@@ -7,7 +7,7 @@ import { Disposable } from 'common/Lifecycle';
|
||||
import { addDisposableDomListener } from 'browser/Lifecycle';
|
||||
import { IColorSet, IViewport } from 'browser/Types';
|
||||
import { ICharSizeService, IRenderService } from 'browser/services/Services';
|
||||
import { IBufferService, IOptionsService } from 'common/services/Services';
|
||||
import { IBufferService } from 'common/services/Services';
|
||||
|
||||
const FALLBACK_SCROLL_BAR_WIDTH = 15;
|
||||
|
||||
@@ -37,7 +37,6 @@ export class Viewport extends Disposable implements IViewport {
|
||||
private readonly _viewportElement: HTMLElement,
|
||||
private readonly _scrollArea: HTMLElement,
|
||||
@IBufferService private readonly _bufferService: IBufferService,
|
||||
@IOptionsService private readonly _optionsService: IOptionsService,
|
||||
@ICharSizeService private readonly _charSizeService: ICharSizeService,
|
||||
@IRenderService private readonly _renderService: IRenderService
|
||||
) {
|
||||
@@ -61,14 +60,7 @@ export class Viewport extends Disposable implements IViewport {
|
||||
* Refreshes row height, setting line-height, viewport height and scroll area height if
|
||||
* necessary.
|
||||
*/
|
||||
private _refresh(immediate: boolean): void {
|
||||
if (immediate) {
|
||||
this._innerRefresh();
|
||||
if (this._refreshAnimationFrame !== null) {
|
||||
cancelAnimationFrame(this._refreshAnimationFrame);
|
||||
}
|
||||
return;
|
||||
}
|
||||
private _refresh(): void {
|
||||
if (this._refreshAnimationFrame === null) {
|
||||
this._refreshAnimationFrame = requestAnimationFrame(() => this._innerRefresh());
|
||||
}
|
||||
@@ -96,39 +88,40 @@ export class Viewport extends Disposable implements IViewport {
|
||||
|
||||
this._refreshAnimationFrame = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates dimensions and synchronizes the scroll area if necessary.
|
||||
*/
|
||||
public syncScrollArea(immediate: boolean = false): void {
|
||||
public syncScrollArea(): void {
|
||||
// If buffer height changed
|
||||
if (this._lastRecordedBufferLength !== this._bufferService.buffer.lines.length) {
|
||||
this._lastRecordedBufferLength = this._bufferService.buffer.lines.length;
|
||||
this._refresh(immediate);
|
||||
this._refresh();
|
||||
return;
|
||||
}
|
||||
|
||||
// If viewport height changed
|
||||
if (this._lastRecordedViewportHeight !== this._renderService.dimensions.canvasHeight) {
|
||||
this._refresh(immediate);
|
||||
this._refresh();
|
||||
return;
|
||||
}
|
||||
|
||||
// If the buffer position doesn't match last scroll top
|
||||
const newScrollTop = this._bufferService.buffer.ydisp * this._currentRowHeight;
|
||||
if (this._lastScrollTop !== newScrollTop) {
|
||||
this._refresh(immediate);
|
||||
this._refresh();
|
||||
return;
|
||||
}
|
||||
|
||||
// If element's scroll top changed, this can happen when hiding the element
|
||||
if (this._lastScrollTop !== this._viewportElement.scrollTop) {
|
||||
this._refresh(immediate);
|
||||
this._refresh();
|
||||
return;
|
||||
}
|
||||
|
||||
// If row height changed
|
||||
if (this._renderService.dimensions.scaledCellHeight / window.devicePixelRatio !== this._currentRowHeight) {
|
||||
this._refresh(immediate);
|
||||
this._refresh();
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -198,7 +191,7 @@ export class Viewport extends Disposable implements IViewport {
|
||||
}
|
||||
|
||||
// Fallback to WheelEvent.DOM_DELTA_PIXEL
|
||||
let amount = this._applyScrollModifier(ev.deltaY, ev);
|
||||
let amount = ev.deltaY;
|
||||
if (ev.deltaMode === WheelEvent.DOM_DELTA_LINE) {
|
||||
amount *= this._currentRowHeight;
|
||||
} else if (ev.deltaMode === WheelEvent.DOM_DELTA_PAGE) {
|
||||
@@ -219,7 +212,7 @@ export class Viewport extends Disposable implements IViewport {
|
||||
}
|
||||
|
||||
// Fallback to WheelEvent.DOM_DELTA_LINE
|
||||
let amount = this._applyScrollModifier(ev.deltaY, ev);
|
||||
let amount = ev.deltaY;
|
||||
if (ev.deltaMode === WheelEvent.DOM_DELTA_PIXEL) {
|
||||
amount /= this._currentRowHeight + 0.0; // Prevent integer division
|
||||
this._wheelPartialScroll += amount;
|
||||
@@ -231,18 +224,6 @@ export class Viewport extends Disposable implements IViewport {
|
||||
return amount;
|
||||
}
|
||||
|
||||
private _applyScrollModifier(amount: number, ev: WheelEvent): number {
|
||||
const modifier = this._optionsService.options.fastScrollModifier;
|
||||
// Multiply the scroll speed when the modifier is down
|
||||
if ((modifier === 'alt' && ev.altKey) ||
|
||||
(modifier === 'ctrl' && ev.ctrlKey) ||
|
||||
(modifier === 'shift' && ev.shiftKey)) {
|
||||
return amount * this._optionsService.options.fastScrollSensitivity * this._optionsService.options.scrollSensitivity;
|
||||
}
|
||||
|
||||
return amount * this._optionsService.options.scrollSensitivity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the touchstart event, recording the touch occurred.
|
||||
* @param ev The touch event.
|
||||
|
||||
@@ -5,17 +5,16 @@
|
||||
|
||||
import { IRenderDimensions, IRenderLayer } from 'browser/renderer/Types';
|
||||
import { ICellData } from 'common/Types';
|
||||
import { DEFAULT_COLOR, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE, Attributes } from 'common/buffer/Constants';
|
||||
import { DEFAULT_COLOR, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE } from 'common/buffer/Constants';
|
||||
import { IGlyphIdentifier } from 'browser/renderer/atlas/Types';
|
||||
import { DIM_OPACITY, INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants';
|
||||
import { BaseCharAtlas } from 'browser/renderer/atlas/BaseCharAtlas';
|
||||
import { acquireCharAtlas } from 'browser/renderer/atlas/CharAtlasCache';
|
||||
import { AttributeData } from 'common/buffer/AttributeData';
|
||||
import { IColorSet, IColor } from 'browser/Types';
|
||||
import { IColorSet } from 'browser/Types';
|
||||
import { CellData } from 'common/buffer/CellData';
|
||||
import { IBufferService, IOptionsService } from 'common/services/Services';
|
||||
import { throwIfFalsy } from 'browser/renderer/RendererUtils';
|
||||
import { toCss, ensureContrastRatioRgba, opaque } from 'browser/Color';
|
||||
|
||||
export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
private _canvas: HTMLCanvasElement;
|
||||
@@ -61,7 +60,9 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
|
||||
public dispose(): void {
|
||||
this._container.removeChild(this._canvas);
|
||||
this._charAtlas?.dispose();
|
||||
if (this._charAtlas) {
|
||||
this._charAtlas.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private _initCanvas(): void {
|
||||
@@ -263,14 +264,13 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
* @param bold Whether the text is bold.
|
||||
*/
|
||||
protected _drawChars(cell: ICellData, x: number, y: number): void {
|
||||
const contrastColor = this._getContrastColor(cell);
|
||||
|
||||
// skip cache right away if we draw in RGB
|
||||
// Note: to avoid bad runtime JoinedCellData will be skipped
|
||||
// in the cache handler itself (atlasDidDraw == false) and
|
||||
// fall through to uncached later down below
|
||||
if (contrastColor || cell.isFgRGB() || cell.isBgRGB()) {
|
||||
this._drawUncachedChars(cell, x, y, contrastColor);
|
||||
if (cell.isFgRGB() || cell.isBgRGB()) {
|
||||
this._drawUncachedChars(cell, x, y);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -284,7 +284,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
fg = (cell.isFgDefault()) ? DEFAULT_COLOR : cell.getFgColor();
|
||||
}
|
||||
|
||||
const drawInBrightColor = this._optionsService.options.drawBoldTextInBrightColors && cell.isBold() && fg < 8;
|
||||
const drawInBrightColor = this._optionsService.options.drawBoldTextInBrightColors && cell.isBold() && fg < 8 && fg !== INVERTED_DEFAULT_COLOR;
|
||||
|
||||
fg += drawInBrightColor ? 8 : 0;
|
||||
this._currentGlyphIdentifier.chars = cell.getChars() || WHITESPACE_CELL_CHAR;
|
||||
@@ -316,29 +316,21 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
* @param x The column to draw at.
|
||||
* @param y The row to draw at.
|
||||
*/
|
||||
private _drawUncachedChars(cell: ICellData, x: number, y: number, fgOverride?: IColor): void {
|
||||
private _drawUncachedChars(cell: ICellData, x: number, y: number): void {
|
||||
this._ctx.save();
|
||||
this._ctx.font = this._getFont(!!cell.isBold(), !!cell.isItalic());
|
||||
this._ctx.textBaseline = 'middle';
|
||||
|
||||
if (cell.isInverse()) {
|
||||
if (fgOverride) {
|
||||
this._ctx.fillStyle = fgOverride.css;
|
||||
} else if (cell.isBgDefault()) {
|
||||
this._ctx.fillStyle = opaque(this._colors.background).css;
|
||||
if (cell.isBgDefault()) {
|
||||
this._ctx.fillStyle = this._colors.background.css;
|
||||
} else if (cell.isBgRGB()) {
|
||||
this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getBgColor()).join(',')})`;
|
||||
} else {
|
||||
let bg = cell.getBgColor();
|
||||
if (this._optionsService.options.drawBoldTextInBrightColors && cell.isBold() && bg < 8) {
|
||||
bg += 8;
|
||||
}
|
||||
this._ctx.fillStyle = this._colors.ansi[bg].css;
|
||||
this._ctx.fillStyle = this._colors.ansi[cell.getBgColor()].css;
|
||||
}
|
||||
} else {
|
||||
if (fgOverride) {
|
||||
this._ctx.fillStyle = fgOverride.css;
|
||||
} else if (cell.isFgDefault()) {
|
||||
if (cell.isFgDefault()) {
|
||||
this._ctx.fillStyle = this._colors.foreground.css;
|
||||
} else if (cell.isFgRGB()) {
|
||||
this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getFgColor()).join(',')})`;
|
||||
@@ -389,88 +381,5 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
|
||||
return `${fontStyle} ${fontWeight} ${this._optionsService.options.fontSize * window.devicePixelRatio}px ${this._optionsService.options.fontFamily}`;
|
||||
}
|
||||
|
||||
private _getContrastColor(cell: CellData): IColor | undefined {
|
||||
if (this._optionsService.options.minimumContrastRatio === 1) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Try get from cache first
|
||||
const adjustedColor = this._colors.contrastCache.getColor(cell.bg, cell.fg);
|
||||
if (adjustedColor !== undefined) {
|
||||
return adjustedColor || undefined;
|
||||
}
|
||||
|
||||
let fgColor = cell.getFgColor();
|
||||
let fgColorMode = cell.getFgColorMode();
|
||||
let bgColor = cell.getBgColor();
|
||||
let bgColorMode = cell.getBgColorMode();
|
||||
const isInverse = !!cell.isInverse();
|
||||
const isBold = !!cell.isInverse();
|
||||
if (isInverse) {
|
||||
const temp = fgColor;
|
||||
fgColor = bgColor;
|
||||
bgColor = temp;
|
||||
const temp2 = fgColorMode;
|
||||
fgColorMode = bgColorMode;
|
||||
bgColorMode = temp2;
|
||||
}
|
||||
|
||||
const bgRgba = this._resolveBackgroundRgba(bgColorMode, bgColor, isInverse);
|
||||
const fgRgba = this._resolveForegroundRgba(fgColorMode, fgColor, isInverse, isBold);
|
||||
const result = ensureContrastRatioRgba(bgRgba, fgRgba, this._optionsService.options.minimumContrastRatio);
|
||||
|
||||
if (!result) {
|
||||
this._colors.contrastCache.setColor(cell.bg, cell.fg, null);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const color: IColor = {
|
||||
css: toCss(
|
||||
(result >> 24) & 0xFF,
|
||||
(result >> 16) & 0xFF,
|
||||
(result >> 8) & 0xFF
|
||||
),
|
||||
rgba: result
|
||||
};
|
||||
this._colors.contrastCache.setColor(cell.bg, cell.fg, color);
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
private _resolveBackgroundRgba(bgColorMode: number, bgColor: number, inverse: boolean): number {
|
||||
switch (bgColorMode) {
|
||||
case Attributes.CM_P16:
|
||||
case Attributes.CM_P256:
|
||||
return this._colors.ansi[bgColor].rgba;
|
||||
case Attributes.CM_RGB:
|
||||
return bgColor << 8;
|
||||
case Attributes.CM_DEFAULT:
|
||||
default:
|
||||
if (inverse) {
|
||||
return this._colors.foreground.rgba;
|
||||
}
|
||||
return this._colors.background.rgba;
|
||||
}
|
||||
}
|
||||
|
||||
private _resolveForegroundRgba(fgColorMode: number, fgColor: number, inverse: boolean, bold: boolean): number {
|
||||
switch (fgColorMode) {
|
||||
case Attributes.CM_P16:
|
||||
case Attributes.CM_P256:
|
||||
if (this._optionsService.options.drawBoldTextInBrightColors && bold && fgColor < 8) {
|
||||
fgColor += 8;
|
||||
}
|
||||
return this._colors.ansi[fgColor].rgba;
|
||||
case Attributes.CM_RGB:
|
||||
return fgColor << 8;
|
||||
case Attributes.CM_DEFAULT:
|
||||
default:
|
||||
if (inverse) {
|
||||
return this._colors.background.rgba;
|
||||
}
|
||||
return this._colors.foreground.rgba;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -303,6 +303,7 @@ export class CharacterJoinerRegistry implements ICharacterJoinerRegistry {
|
||||
// current range
|
||||
ranges[i - 1][1] = Math.max(newRange[1], range[1]);
|
||||
ranges.splice(i, 1);
|
||||
inRange = false;
|
||||
return ranges;
|
||||
}
|
||||
|
||||
|
||||
@@ -227,11 +227,7 @@ export class TextRenderLayer extends BaseRenderLayer {
|
||||
} else if (cell.isBgRGB()) {
|
||||
this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getBgColor()).join(',')})`;
|
||||
} else {
|
||||
let bg = cell.getBgColor();
|
||||
if (this._optionsService.options.drawBoldTextInBrightColors && cell.isBold() && bg < 8) {
|
||||
bg += 8;
|
||||
}
|
||||
this._ctx.fillStyle = this._colors.ansi[bg].css;
|
||||
this._ctx.fillStyle = this._colors.ansi[cell.getBgColor()].css;
|
||||
}
|
||||
} else {
|
||||
if (cell.isFgDefault()) {
|
||||
|
||||
Vendored
-8
@@ -5,7 +5,6 @@
|
||||
|
||||
import { IDisposable } from 'common/Types';
|
||||
import { IColorSet } from 'browser/Types';
|
||||
import { IEvent } from 'common/EventEmitter';
|
||||
|
||||
export type CharacterJoinerHandler = (text: string) => [number, number][];
|
||||
|
||||
@@ -24,11 +23,6 @@ export interface IRenderDimensions {
|
||||
actualCellHeight: number;
|
||||
}
|
||||
|
||||
export interface IRequestRefreshRowsEvent {
|
||||
start: number;
|
||||
end: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Note that IRenderer implementations should emit the refresh event after
|
||||
* rendering rows to the screen.
|
||||
@@ -36,8 +30,6 @@ export interface IRequestRefreshRowsEvent {
|
||||
export interface IRenderer extends IDisposable {
|
||||
readonly dimensions: IRenderDimensions;
|
||||
|
||||
readonly onRequestRefreshRows: IEvent<IRequestRefreshRowsEvent>;
|
||||
|
||||
dispose(): void;
|
||||
setColors(colors: IColorSet): void;
|
||||
onDevicePixelRatioChange(): void;
|
||||
|
||||
@@ -11,7 +11,6 @@ import { LRUMap } from 'browser/renderer/atlas/LRUMap';
|
||||
import { isFirefox, isSafari } from 'common/Platform';
|
||||
import { IColor } from 'browser/Types';
|
||||
import { throwIfFalsy } from 'browser/renderer/RendererUtils';
|
||||
import { opaque } from 'browser/Color';
|
||||
|
||||
// In practice we're probably never going to exhaust a texture this large. For debugging purposes,
|
||||
// however, it can be useful to set this to a really tiny value, to verify that LRU eviction works.
|
||||
@@ -223,7 +222,7 @@ export class DynamicCharAtlas extends BaseCharAtlas {
|
||||
|
||||
private _getForegroundColor(glyph: IGlyphIdentifier): IColor {
|
||||
if (glyph.fg === INVERTED_DEFAULT_COLOR) {
|
||||
return opaque(this._config.colors.background);
|
||||
return this._config.colors.background;
|
||||
} else if (glyph.fg < 256) {
|
||||
// 256 color support
|
||||
return this._getColorFromAnsiIndex(glyph.fg);
|
||||
|
||||
@@ -11,7 +11,6 @@ import { BufferLine, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
|
||||
import { IBufferLine } from 'common/Types';
|
||||
import { CellData } from 'common/buffer/CellData';
|
||||
import { MockOptionsService } from 'common/TestUtils.test';
|
||||
import { fromCss } from 'browser/Color';
|
||||
|
||||
describe('DomRendererRowFactory', () => {
|
||||
let dom: jsdom.JSDOM;
|
||||
@@ -20,30 +19,7 @@ describe('DomRendererRowFactory', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
dom = new jsdom.JSDOM('');
|
||||
rowFactory = new DomRendererRowFactory(dom.window.document, new MockOptionsService({ drawBoldTextInBrightColors: true }), {
|
||||
background: fromCss('#010101'),
|
||||
foreground: fromCss('#020202'),
|
||||
ansi: [
|
||||
// dark:
|
||||
fromCss('#2e3436'),
|
||||
fromCss('#cc0000'),
|
||||
fromCss('#4e9a06'),
|
||||
fromCss('#c4a000'),
|
||||
fromCss('#3465a4'),
|
||||
fromCss('#75507b'),
|
||||
fromCss('#06989a'),
|
||||
fromCss('#d3d7cf'),
|
||||
// bright:
|
||||
fromCss('#555753'),
|
||||
fromCss('#ef2929'),
|
||||
fromCss('#8ae234'),
|
||||
fromCss('#fce94f'),
|
||||
fromCss('#729fcf'),
|
||||
fromCss('#ad7fa8'),
|
||||
fromCss('#34e2e2'),
|
||||
fromCss('#eeeeec')
|
||||
]
|
||||
} as any);
|
||||
rowFactory = new DomRendererRowFactory(dom.window.document, new MockOptionsService({ drawBoldTextInBrightColors: true }));
|
||||
lineData = createEmptyLineData(2);
|
||||
});
|
||||
|
||||
@@ -166,7 +142,7 @@ describe('DomRendererRowFactory', () => {
|
||||
lineData.setCell(0, cell);
|
||||
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
|
||||
assert.equal(getFragmentHtml(fragment),
|
||||
'<span class="xterm-fg-1 xterm-bg-2">a</span>'
|
||||
'<span class="xterm-bg-2 xterm-fg-1">a</span>'
|
||||
);
|
||||
});
|
||||
|
||||
@@ -177,7 +153,7 @@ describe('DomRendererRowFactory', () => {
|
||||
lineData.setCell(0, cell);
|
||||
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
|
||||
assert.equal(getFragmentHtml(fragment),
|
||||
'<span class="xterm-fg-1 xterm-bg-257">a</span>'
|
||||
'<span class="xterm-bg-257 xterm-fg-1">a</span>'
|
||||
);
|
||||
});
|
||||
|
||||
@@ -187,7 +163,7 @@ describe('DomRendererRowFactory', () => {
|
||||
lineData.setCell(0, cell);
|
||||
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
|
||||
assert.equal(getFragmentHtml(fragment),
|
||||
'<span class="xterm-fg-257 xterm-bg-1">a</span>'
|
||||
'<span class="xterm-bg-1 xterm-fg-257">a</span>'
|
||||
);
|
||||
});
|
||||
|
||||
@@ -212,7 +188,7 @@ describe('DomRendererRowFactory', () => {
|
||||
lineData.setCell(0, cell);
|
||||
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
|
||||
assert.equal(getFragmentHtml(fragment),
|
||||
'<span style="color:#010203;background-color:#040506;">a</span>'
|
||||
'<span style="color:rgb(1,2,3);background-color:rgb(4,5,6);">a</span>'
|
||||
);
|
||||
});
|
||||
|
||||
@@ -223,7 +199,7 @@ describe('DomRendererRowFactory', () => {
|
||||
lineData.setCell(0, cell);
|
||||
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
|
||||
assert.equal(getFragmentHtml(fragment),
|
||||
'<span style="color:#040506;background-color:#010203;">a</span>'
|
||||
'<span style="background-color:rgb(1,2,3);color:rgb(4,5,6);">a</span>'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,11 +5,10 @@
|
||||
|
||||
import { IBufferLine } from 'common/Types';
|
||||
import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants';
|
||||
import { NULL_CELL_CODE, WHITESPACE_CELL_CHAR, Attributes } from 'common/buffer/Constants';
|
||||
import { AttributeData } from 'common/buffer/AttributeData';
|
||||
import { NULL_CELL_CODE, WHITESPACE_CELL_CHAR } from 'common/buffer/Constants';
|
||||
import { CellData } from 'common/buffer/CellData';
|
||||
import { IOptionsService } from 'common/services/Services';
|
||||
import { ensureContrastRatio, rgbaToColor } from 'browser/Color';
|
||||
import { IColorSet, IColor } from 'browser/Types';
|
||||
import { ITerminalOptions, IOptionsService } from 'common/services/Services';
|
||||
|
||||
export const BOLD_CLASS = 'xterm-bold';
|
||||
export const DIM_CLASS = 'xterm-dim';
|
||||
@@ -25,16 +24,11 @@ export class DomRendererRowFactory {
|
||||
private _workCell: CellData = new CellData();
|
||||
|
||||
constructor(
|
||||
private readonly _document: Document,
|
||||
private readonly _optionsService: IOptionsService,
|
||||
private _colors: IColorSet
|
||||
private _document: Document,
|
||||
private _optionsService: IOptionsService
|
||||
) {
|
||||
}
|
||||
|
||||
public setColors(colors: IColorSet): void {
|
||||
this._colors = colors;
|
||||
}
|
||||
|
||||
public createRow(lineData: IBufferLine, isCursorRow: boolean, cursorStyle: string | undefined, cursorX: number, cursorBlink: boolean, cellWidth: number, cols: number): DocumentFragment {
|
||||
const fragment = this._document.createDocumentFragment();
|
||||
|
||||
@@ -103,101 +97,36 @@ export class DomRendererRowFactory {
|
||||
|
||||
charElement.textContent = this._workCell.getChars() || WHITESPACE_CELL_CHAR;
|
||||
|
||||
let fg = this._workCell.getFgColor();
|
||||
let fgColorMode = this._workCell.getFgColorMode();
|
||||
let bg = this._workCell.getBgColor();
|
||||
let bgColorMode = this._workCell.getBgColorMode();
|
||||
const isInverse = !!this._workCell.isInverse();
|
||||
if (isInverse) {
|
||||
const temp = fg;
|
||||
fg = bg;
|
||||
bg = temp;
|
||||
const temp2 = fgColorMode;
|
||||
fgColorMode = bgColorMode;
|
||||
bgColorMode = temp2;
|
||||
const swapColor = this._workCell.isInverse();
|
||||
|
||||
// fg
|
||||
if (this._workCell.isFgRGB()) {
|
||||
let style = charElement.getAttribute('style') || '';
|
||||
style += `${swapColor ? 'background-' : ''}color:rgb(${(AttributeData.toColorRGB(this._workCell.getFgColor())).join(',')});`;
|
||||
charElement.setAttribute('style', style);
|
||||
} else if (this._workCell.isFgPalette()) {
|
||||
let fg = this._workCell.getFgColor();
|
||||
if (this._workCell.isBold() && fg < 8 && !swapColor && this._optionsService.options.drawBoldTextInBrightColors) {
|
||||
fg += 8;
|
||||
}
|
||||
charElement.classList.add(`xterm-${swapColor ? 'b' : 'f'}g-${fg}`);
|
||||
} else if (swapColor) {
|
||||
charElement.classList.add(`xterm-bg-${INVERTED_DEFAULT_COLOR}`);
|
||||
}
|
||||
|
||||
// Foreground
|
||||
switch (fgColorMode) {
|
||||
case Attributes.CM_P16:
|
||||
case Attributes.CM_P256:
|
||||
if (this._workCell.isBold() && fg < 8 && this._optionsService.options.drawBoldTextInBrightColors) {
|
||||
fg += 8;
|
||||
}
|
||||
if (!this._applyMinimumContrast(charElement, this._colors.background, this._colors.ansi[fg])) {
|
||||
charElement.classList.add(`xterm-fg-${fg}`);
|
||||
}
|
||||
break;
|
||||
case Attributes.CM_RGB:
|
||||
const color = rgbaToColor(
|
||||
(fg >> 16) & 0xFF,
|
||||
(fg >> 8) & 0xFF,
|
||||
(fg ) & 0xFF
|
||||
);
|
||||
if (!this._applyMinimumContrast(charElement, this._colors.background, color)) {
|
||||
this._addStyle(charElement, `color:#${padStart(fg.toString(16), '0', 6)}`);
|
||||
}
|
||||
break;
|
||||
case Attributes.CM_DEFAULT:
|
||||
default:
|
||||
if (!this._applyMinimumContrast(charElement, this._colors.background, this._colors.foreground)) {
|
||||
if (isInverse) {
|
||||
charElement.classList.add(`xterm-fg-${INVERTED_DEFAULT_COLOR}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Background
|
||||
switch (bgColorMode) {
|
||||
case Attributes.CM_P16:
|
||||
case Attributes.CM_P256:
|
||||
charElement.classList.add(`xterm-bg-${bg}`);
|
||||
break;
|
||||
case Attributes.CM_RGB:
|
||||
this._addStyle(charElement, `background-color:#${padStart(bg.toString(16), '0', 6)}`);
|
||||
break;
|
||||
case Attributes.CM_DEFAULT:
|
||||
default:
|
||||
if (isInverse) {
|
||||
charElement.classList.add(`xterm-bg-${INVERTED_DEFAULT_COLOR}`);
|
||||
}
|
||||
// bg
|
||||
if (this._workCell.isBgRGB()) {
|
||||
let style = charElement.getAttribute('style') || '';
|
||||
style += `${swapColor ? '' : 'background-'}color:rgb(${(AttributeData.toColorRGB(this._workCell.getBgColor())).join(',')});`;
|
||||
charElement.setAttribute('style', style);
|
||||
} else if (this._workCell.isBgPalette()) {
|
||||
charElement.classList.add(`xterm-${swapColor ? 'f' : 'b'}g-${this._workCell.getBgColor()}`);
|
||||
} else if (swapColor) {
|
||||
charElement.classList.add(`xterm-fg-${INVERTED_DEFAULT_COLOR}`);
|
||||
}
|
||||
|
||||
fragment.appendChild(charElement);
|
||||
}
|
||||
return fragment;
|
||||
}
|
||||
|
||||
private _applyMinimumContrast(element: HTMLElement, bg: IColor, fg: IColor): boolean {
|
||||
if (this._optionsService.options.minimumContrastRatio === 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Try get from cache first
|
||||
let adjustedColor = this._colors.contrastCache.getColor(this._workCell.bg, this._workCell.fg);
|
||||
|
||||
// Calculate and store in cache
|
||||
if (adjustedColor === undefined) {
|
||||
adjustedColor = ensureContrastRatio(bg, fg, this._optionsService.options.minimumContrastRatio);
|
||||
this._colors.contrastCache.setColor(this._workCell.bg, this._workCell.fg, adjustedColor ?? null);
|
||||
}
|
||||
|
||||
if (adjustedColor) {
|
||||
this._addStyle(element, `color:${adjustedColor.css}`);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private _addStyle(element: HTMLElement, style: string): void {
|
||||
element.setAttribute('style', `${element.getAttribute('style') || ''}${style};`);
|
||||
}
|
||||
}
|
||||
|
||||
function padStart(text: string, padChar: string, length: number): string {
|
||||
while (text.length < length) {
|
||||
text = padChar + text;
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { ICoreBrowserService } from './Services';
|
||||
|
||||
export class CoreBrowserService implements ICoreBrowserService {
|
||||
serviceBrand: any;
|
||||
|
||||
constructor(
|
||||
private _textarea: HTMLTextAreaElement
|
||||
) {
|
||||
}
|
||||
|
||||
public get isFocused(): boolean {
|
||||
return document.activeElement === this._textarea && document.hasFocus();
|
||||
}
|
||||
}
|
||||
@@ -51,9 +51,6 @@ export class RenderService extends Disposable implements IRenderService {
|
||||
this.register(optionsService.onOptionChange(() => this._renderer.onOptionsChanged()));
|
||||
this.register(charSizeService.onCharSizeChange(() => this.onCharSizeChanged()));
|
||||
|
||||
// No need to register this as renderer is explicitly disposed in RenderService.dispose
|
||||
this._renderer.onRequestRefreshRows(e => this.refreshRows(e.start, e.end));
|
||||
|
||||
// dprchange should handle this case, we need this as well for browsers that don't support the
|
||||
// matchMedia query.
|
||||
this.register(addDisposableDomListener(window, 'resize', () => this.onDevicePixelRatioChange()));
|
||||
@@ -95,7 +92,6 @@ export class RenderService extends Disposable implements IRenderService {
|
||||
|
||||
public changeOptions(): void {
|
||||
this._renderer.onOptionsChanged();
|
||||
this.refreshRows(0, this._rowCount - 1);
|
||||
this._fireOnCanvasResize();
|
||||
}
|
||||
|
||||
@@ -109,14 +105,12 @@ export class RenderService extends Disposable implements IRenderService {
|
||||
|
||||
public dispose(): void {
|
||||
this._renderer.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
public setRenderer(renderer: IRenderer): void {
|
||||
// TODO: RenderService should be the only one to dispose the renderer
|
||||
this._renderer.dispose();
|
||||
this._renderer = renderer;
|
||||
this._renderer.onRequestRefreshRows(e => this.refreshRows(e.start, e.end));
|
||||
this.refreshRows(0, this._rowCount - 1);
|
||||
}
|
||||
|
||||
|
||||
@@ -210,7 +210,7 @@ export class SelectionService implements ISelectionService {
|
||||
for (let i = start[1] + 1; i <= end[1] - 1; i++) {
|
||||
const bufferLine = buffer.lines.get(i);
|
||||
const lineText = buffer.translateBufferLineToString(i, true);
|
||||
if (bufferLine && bufferLine.isWrapped) {
|
||||
if (bufferLine!.isWrapped) {
|
||||
result[result.length - 1] += lineText;
|
||||
} else {
|
||||
result.push(lineText);
|
||||
@@ -221,7 +221,7 @@ export class SelectionService implements ISelectionService {
|
||||
if (start[1] !== end[1]) {
|
||||
const bufferLine = buffer.lines.get(end[1]);
|
||||
const lineText = buffer.translateBufferLineToString(end[1], true, 0, end[0]);
|
||||
if (bufferLine && bufferLine!.isWrapped) {
|
||||
if (bufferLine!.isWrapped) {
|
||||
result[result.length - 1] += lineText;
|
||||
} else {
|
||||
result.push(lineText);
|
||||
@@ -688,7 +688,9 @@ export class SelectionService implements ISelectionService {
|
||||
// reverseIndex) and delete in a splice is only ever used when the same
|
||||
// number of elements was just added. Given this is could actually be
|
||||
// beneficial to leave the selection as is for these cases.
|
||||
this._trimListener.dispose();
|
||||
if (this._trimListener) {
|
||||
this._trimListener.dispose();
|
||||
}
|
||||
this._trimListener = e.activeBuffer.lines.onTrim(amount => this._onTrim(amount));
|
||||
}
|
||||
|
||||
|
||||
@@ -23,13 +23,6 @@ export interface ICharSizeService {
|
||||
measure(): void;
|
||||
}
|
||||
|
||||
export const ICoreBrowserService = createDecorator<ICoreBrowserService>('CoreBrowserService');
|
||||
export interface ICoreBrowserService {
|
||||
serviceBrand: any;
|
||||
|
||||
readonly isFocused: boolean;
|
||||
}
|
||||
|
||||
export const IMouseService = createDecorator<IMouseService>('MouseService');
|
||||
export interface IMouseService {
|
||||
serviceBrand: any;
|
||||
|
||||
@@ -16,13 +16,11 @@ export interface IEvent<T> {
|
||||
export interface IEventEmitter<T> {
|
||||
event: IEvent<T>;
|
||||
fire(data: T): void;
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
export class EventEmitter<T> implements IEventEmitter<T> {
|
||||
private _listeners: IListener<T>[] = [];
|
||||
private _event?: IEvent<T>;
|
||||
private _disposed: boolean = false;
|
||||
|
||||
public get event(): IEvent<T> {
|
||||
if (!this._event) {
|
||||
@@ -30,12 +28,10 @@ export class EventEmitter<T> implements IEventEmitter<T> {
|
||||
this._listeners.push(listener);
|
||||
const disposable = {
|
||||
dispose: () => {
|
||||
if (!this._disposed) {
|
||||
for (let i = 0; i < this._listeners.length; i++) {
|
||||
if (this._listeners[i] === listener) {
|
||||
this._listeners.splice(i, 1);
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < this._listeners.length; i++) {
|
||||
if (this._listeners[i] === listener) {
|
||||
this._listeners.splice(i, 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -55,11 +51,4 @@ export class EventEmitter<T> implements IEventEmitter<T> {
|
||||
queue[i].call(undefined, data);
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
if (this._listeners) {
|
||||
this._listeners.length = 0;
|
||||
}
|
||||
this._disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,17 +43,12 @@ export class MockCoreMouseService implements ICoreMouseService {
|
||||
}
|
||||
|
||||
export class MockCoreService implements ICoreService {
|
||||
isCursorInitialized: boolean = false;
|
||||
isCursorHidden: boolean = false;
|
||||
isFocused: boolean = false;
|
||||
serviceBrand: any;
|
||||
decPrivateModes: IDecPrivateModes = {} as any;
|
||||
onData: IEvent<string> = new EventEmitter<string>().event;
|
||||
onUserInput: IEvent<void> = new EventEmitter<void>().event;
|
||||
onBinary: IEvent<string> = new EventEmitter<string>().event;
|
||||
reset(): void {}
|
||||
triggerDataEvent(data: string, wasUserInput?: boolean): void {}
|
||||
triggerBinaryEvent(data: string): void {}
|
||||
}
|
||||
|
||||
export class MockDirtyRowService implements IDirtyRowService {
|
||||
|
||||
Vendored
-3
@@ -249,8 +249,5 @@ export interface ICoreMouseProtocol {
|
||||
* The tracking encoding can be registered and activated at the CoreMouseService.
|
||||
* If a ICoreMouseEvent passes all procotol restrictions it will be encoded
|
||||
* with the active encoding and sent out.
|
||||
* Note: Returning an empty string will supress sending a mouse report,
|
||||
* which can be used to skip creating falsey reports in limited encodings
|
||||
* (DEFAULT only supports up to 223 1-based as coord value).
|
||||
*/
|
||||
export type CoreMouseEncoding = (event: ICoreMouseEvent) => string;
|
||||
|
||||
+12
-18
@@ -167,25 +167,19 @@ export class Buffer implements IBuffer {
|
||||
if (this._rows < newRows) {
|
||||
for (let y = this._rows; y < newRows; y++) {
|
||||
if (this.lines.length < newRows + this.ybase) {
|
||||
if (this._optionsService.options.windowsMode) {
|
||||
// Just add the new missing rows on Windows as conpty reprints the screen with it's
|
||||
// view of the world. Once a line enters scrollback for conpty it remains there
|
||||
this.lines.push(new BufferLine(newCols, nullCell));
|
||||
} else {
|
||||
if (this.ybase > 0 && this.lines.length <= this.ybase + this.y + addToY + 1) {
|
||||
// There is room above the buffer and there are no empty elements below the line,
|
||||
// scroll up
|
||||
this.ybase--;
|
||||
addToY++;
|
||||
if (this.ydisp > 0) {
|
||||
// Viewport is at the top of the buffer, must increase downwards
|
||||
this.ydisp--;
|
||||
}
|
||||
} 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, nullCell));
|
||||
if (this.ybase > 0 && this.lines.length <= this.ybase + this.y + addToY + 1) {
|
||||
// There is room above the buffer and there are no empty elements below the line,
|
||||
// scroll up
|
||||
this.ybase--;
|
||||
addToY++;
|
||||
if (this.ydisp > 0) {
|
||||
// Viewport is at the top of the buffer, must increase downwards
|
||||
this.ydisp--;
|
||||
}
|
||||
} 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, nullCell));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { CharData, IBufferLine, ICellData } from 'common/Types';
|
||||
import { stringFromCodePoint } from 'common/input/TextDecoder';
|
||||
import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_ATTR_INDEX, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, WHITESPACE_CELL_CHAR, Content } from 'common/buffer/Constants';
|
||||
import { DEFAULT_COLOR, CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_ATTR_INDEX, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, WHITESPACE_CELL_CHAR, Content } from 'common/buffer/Constants';
|
||||
import { CellData } from 'common/buffer/CellData';
|
||||
import { AttributeData } from 'common/buffer/AttributeData';
|
||||
|
||||
|
||||
@@ -29,7 +29,6 @@ export class Marker extends Disposable implements IMarker {
|
||||
return;
|
||||
}
|
||||
this.isDisposed = true;
|
||||
this.line = -1;
|
||||
// Emit before super.dispose such that dispose listeners get a change to react
|
||||
this._onDispose.fire();
|
||||
}
|
||||
|
||||
@@ -108,12 +108,6 @@ describe('Keyboard', () => {
|
||||
it('should return \\x1b[5C for alt+right', () => {
|
||||
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 39 }, { isMac: false }).key, '\x1b[1;5C'); // CSI 5 C
|
||||
});
|
||||
it('should return \\x1b[5D for alt+up', () => {
|
||||
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 38 }, { isMac: false }).key, '\x1b[1;5A'); // CSI 5 D
|
||||
});
|
||||
it('should return \\x1b[5C for alt+down', () => {
|
||||
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 40 }, { isMac: false }).key, '\x1b[1;5B'); // CSI 5 C
|
||||
});
|
||||
it('should return \\x1ba for alt+a', () => {
|
||||
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 65 }, { isMac: false }).key, '\x1ba');
|
||||
});
|
||||
@@ -126,12 +120,6 @@ describe('Keyboard', () => {
|
||||
it('should return \\x1bf for alt+right', () => {
|
||||
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 39 }, { isMac: true }).key, '\x1bf'); // CSI 5 C
|
||||
});
|
||||
it('should return \\x1bb for alt+up', () => {
|
||||
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 38 }, { isMac: true }).key, '\x1b[1;3A'); // CSI 5 D
|
||||
});
|
||||
it('should return \\x1bf for alt+down', () => {
|
||||
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 40 }, { isMac: true }).key, '\x1b[1;3B'); // CSI 5 C
|
||||
});
|
||||
it('should return undefined for alt+a', () => {
|
||||
assert.strictEqual(testEvaluateKeyboardEvent({ altKey: true, keyCode: 65 }, { isMac: true }).key, undefined), { isMac: true };
|
||||
});
|
||||
|
||||
@@ -113,16 +113,13 @@ export function evaluateKeyboardEvent(
|
||||
break;
|
||||
case 37:
|
||||
// left-arrow
|
||||
if (ev.metaKey) {
|
||||
break;
|
||||
}
|
||||
if (modifiers) {
|
||||
result.key = C0.ESC + '[1;' + (modifiers + 1) + 'D';
|
||||
// HACK: Make Alt + left-arrow behave like Ctrl + left-arrow: move one word backwards
|
||||
// http://unix.stackexchange.com/a/108106
|
||||
// macOS uses different escape sequences than linux
|
||||
if (result.key === C0.ESC + '[1;3D') {
|
||||
result.key = C0.ESC + (isMac ? 'b' : '[1;5D');
|
||||
result.key = isMac ? C0.ESC + 'b' : C0.ESC + '[1;5D';
|
||||
}
|
||||
} else if (applicationCursorMode) {
|
||||
result.key = C0.ESC + 'OD';
|
||||
@@ -132,16 +129,13 @@ export function evaluateKeyboardEvent(
|
||||
break;
|
||||
case 39:
|
||||
// right-arrow
|
||||
if (ev.metaKey) {
|
||||
break;
|
||||
}
|
||||
if (modifiers) {
|
||||
result.key = C0.ESC + '[1;' + (modifiers + 1) + 'C';
|
||||
// HACK: Make Alt + right-arrow behave like Ctrl + right-arrow: move one word forward
|
||||
// http://unix.stackexchange.com/a/108106
|
||||
// macOS uses different escape sequences than linux
|
||||
if (result.key === C0.ESC + '[1;3C') {
|
||||
result.key = C0.ESC + (isMac ? 'f' : '[1;5C');
|
||||
result.key = isMac ? C0.ESC + 'f' : C0.ESC + '[1;5C';
|
||||
}
|
||||
} else if (applicationCursorMode) {
|
||||
result.key = C0.ESC + 'OC';
|
||||
@@ -151,15 +145,11 @@ export function evaluateKeyboardEvent(
|
||||
break;
|
||||
case 38:
|
||||
// up-arrow
|
||||
if (ev.metaKey) {
|
||||
break;
|
||||
}
|
||||
if (modifiers) {
|
||||
result.key = C0.ESC + '[1;' + (modifiers + 1) + 'A';
|
||||
// HACK: Make Alt + up-arrow behave like Ctrl + up-arrow
|
||||
// http://unix.stackexchange.com/a/108106
|
||||
// macOS uses different escape sequences than linux
|
||||
if (!isMac && result.key === C0.ESC + '[1;3A') {
|
||||
if (result.key === C0.ESC + '[1;3A') {
|
||||
result.key = C0.ESC + '[1;5A';
|
||||
}
|
||||
} else if (applicationCursorMode) {
|
||||
@@ -170,15 +160,11 @@ export function evaluateKeyboardEvent(
|
||||
break;
|
||||
case 40:
|
||||
// down-arrow
|
||||
if (ev.metaKey) {
|
||||
break;
|
||||
}
|
||||
if (modifiers) {
|
||||
result.key = C0.ESC + '[1;' + (modifiers + 1) + 'B';
|
||||
// HACK: Make Alt + down-arrow behave like Ctrl + down-arrow
|
||||
// http://unix.stackexchange.com/a/108106
|
||||
// macOS uses different escape sequences than linux
|
||||
if (!isMac && result.key === C0.ESC + '[1;3B') {
|
||||
if (result.key === C0.ESC + '[1;3B') {
|
||||
result.key = C0.ESC + '[1;5B';
|
||||
}
|
||||
} else if (applicationCursorMode) {
|
||||
@@ -330,18 +316,23 @@ export function evaluateKeyboardEvent(
|
||||
if (ev.keyCode >= 65 && ev.keyCode <= 90) {
|
||||
result.key = String.fromCharCode(ev.keyCode - 64);
|
||||
} else if (ev.keyCode === 32) {
|
||||
result.key = C0.NUL;
|
||||
// NUL
|
||||
result.key = String.fromCharCode(0);
|
||||
} else if (ev.keyCode >= 51 && ev.keyCode <= 55) {
|
||||
// escape, file sep, group sep, record sep, unit sep
|
||||
result.key = String.fromCharCode(ev.keyCode - 51 + 27);
|
||||
} else if (ev.keyCode === 56) {
|
||||
result.key = C0.DEL;
|
||||
// delete
|
||||
result.key = String.fromCharCode(127);
|
||||
} else if (ev.keyCode === 219) {
|
||||
result.key = C0.ESC;
|
||||
// ^[ - Control Sequence Introducer (CSI)
|
||||
result.key = String.fromCharCode(27);
|
||||
} else if (ev.keyCode === 220) {
|
||||
result.key = C0.FS;
|
||||
// ^\ - String Terminator (ST)
|
||||
result.key = String.fromCharCode(28);
|
||||
} else if (ev.keyCode === 221) {
|
||||
result.key = C0.GS;
|
||||
// ^] - Operating System Command (OSC)
|
||||
result.key = String.fromCharCode(29);
|
||||
}
|
||||
} else if ((!isMac || macOptionIsMeta) && ev.altKey && !ev.metaKey) {
|
||||
// On macOS this is a third level shift when !macOptionIsMeta. Use <Esc> instead.
|
||||
|
||||
@@ -7,7 +7,6 @@ import { assert } from 'chai';
|
||||
import { StringToUtf32, stringFromCodePoint, Utf8ToUtf32, utf32ToString } from 'common/input/TextDecoder';
|
||||
import { encode } from 'utf8';
|
||||
|
||||
|
||||
// convert UTF32 codepoints to string
|
||||
function toString(data: Uint32Array, length: number): string {
|
||||
if ((String as any).fromCodePoint) {
|
||||
@@ -215,16 +214,6 @@ describe('text encodings', () => {
|
||||
}
|
||||
assert(decoded, 'Ä€𝄞Ö𝄞€Ü𝄞€');
|
||||
});
|
||||
it('test break after 3 bytes - issue #2495', () => {
|
||||
const decoder = new Utf8ToUtf32();
|
||||
const target = new Uint32Array(5);
|
||||
const utf8Data = fromByteString('\xf0\xa0\x9c\x8e');
|
||||
let written = decoder.decode(utf8Data.slice(0, 3), target);
|
||||
assert.equal(written, 0);
|
||||
written = decoder.decode(utf8Data.slice(3), target);
|
||||
assert.equal(written, 1);
|
||||
assert(toString(target, written), '𠜎');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -194,7 +194,7 @@ export class Utf8ToUtf32 {
|
||||
target[size++] = cp;
|
||||
}
|
||||
} else {
|
||||
if (cp < 0x010000 || cp > 0x10FFFF) {
|
||||
if (codepoint < 0x010000 || codepoint > 0x10FFFF) {
|
||||
// illegal codepoint
|
||||
} else {
|
||||
target[size++] = cp;
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { assert } from 'chai';
|
||||
import { WriteBuffer } from './WriteBuffer';
|
||||
|
||||
declare let Buffer: any;
|
||||
|
||||
function toBytes(s: string): Uint8Array {
|
||||
return Buffer.from(s);
|
||||
}
|
||||
|
||||
function fromBytes(bytes: Uint8Array): string {
|
||||
return bytes.toString();
|
||||
}
|
||||
|
||||
describe('WriteBuffer', () => {
|
||||
let wb: WriteBuffer;
|
||||
let stack: (string | Uint8Array)[] = [];
|
||||
let cbStack: string[] = [];
|
||||
beforeEach(() => {
|
||||
stack = [];
|
||||
cbStack = [];
|
||||
wb = new WriteBuffer(data => { stack.push(data); });
|
||||
});
|
||||
describe('write input', () => {
|
||||
it('string', done => {
|
||||
wb.write('a._');
|
||||
wb.write('b.x', () => { cbStack.push('b'); });
|
||||
wb.write('c._');
|
||||
wb.write('d.x', () => { cbStack.push('d'); });
|
||||
wb.write('e', () => {
|
||||
assert.deepEqual(stack, ['a._', 'b.x', 'c._', 'd.x', 'e']);
|
||||
assert.deepEqual(cbStack, ['b', 'd']);
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('bytes', done => {
|
||||
wb.write(toBytes('a._'));
|
||||
wb.write(toBytes('b.x'), () => { cbStack.push('b'); });
|
||||
wb.write(toBytes('c._'));
|
||||
wb.write(toBytes('d.x'), () => { cbStack.push('d'); });
|
||||
wb.write(toBytes('e'), () => {
|
||||
assert.deepEqual(stack.map(val => typeof val === 'string' ? '' : fromBytes(val)), ['a._', 'b.x', 'c._', 'd.x', 'e']);
|
||||
assert.deepEqual(cbStack, ['b', 'd']);
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('string/bytes mixed', done => {
|
||||
wb.write('a._');
|
||||
wb.write('b.x', () => { cbStack.push('b'); });
|
||||
wb.write(toBytes('c._'));
|
||||
wb.write(toBytes('d.x'), () => { cbStack.push('d'); });
|
||||
wb.write(toBytes('e'), () => {
|
||||
assert.deepEqual(stack.map(val => typeof val === 'string' ? val : fromBytes(val)), ['a._', 'b.x', 'c._', 'd.x', 'e']);
|
||||
assert.deepEqual(cbStack, ['b', 'd']);
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('write callback works for empty chunks', done => {
|
||||
wb.write('a', () => { cbStack.push('a'); });
|
||||
wb.write('', () => { cbStack.push('b'); });
|
||||
wb.write(toBytes('c'), () => { cbStack.push('c'); });
|
||||
wb.write(new Uint8Array(0), () => { cbStack.push('d'); });
|
||||
wb.write('e', () => {
|
||||
assert.deepEqual(stack.map(val => typeof val === 'string' ? val : fromBytes(val)), ['a', '', 'c', '', 'e']);
|
||||
assert.deepEqual(cbStack, ['a', 'b', 'c', 'd']);
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('writeSync', done => {
|
||||
wb.write('a', () => { cbStack.push('a'); });
|
||||
wb.write('b', () => { cbStack.push('b'); });
|
||||
wb.write('c', () => { cbStack.push('c'); });
|
||||
wb.writeSync('d');
|
||||
assert.deepEqual(stack, ['a', 'b', 'c', 'd']);
|
||||
assert.deepEqual(cbStack, ['a', 'b', 'c']);
|
||||
wb.write('x', () => { cbStack.push('x'); });
|
||||
wb.write('', () => {
|
||||
assert.deepEqual(stack, ['a', 'b', 'c', 'd', 'x', '']);
|
||||
assert.deepEqual(cbStack, ['a', 'b', 'c', 'x']);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,110 +0,0 @@
|
||||
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
declare const setTimeout: (handler: () => void, timeout?: number) => void;
|
||||
|
||||
/**
|
||||
* Safety watermark to avoid memory exhaustion and browser engine crash on fast data input.
|
||||
* Enable flow control to avoid this limit and make sure that your backend correctly
|
||||
* propagates this to the underlying pty. (see docs for further instructions)
|
||||
* Since this limit is meant as a safety parachute to prevent browser crashs,
|
||||
* it is set to a very high number. Typically xterm.js gets unresponsive with
|
||||
* a 100 times lower number (>500 kB).
|
||||
*/
|
||||
const DISCARD_WATERMARK = 50000000; // ~50 MB
|
||||
|
||||
/**
|
||||
* The max number of ms to spend on writes before allowing the renderer to
|
||||
* catch up with a 0ms setTimeout. A value of < 33 to keep us close to
|
||||
* 30fps, and a value of < 16 to try to run at 60fps. Of course, the real FPS
|
||||
* depends on the time it takes for the renderer to draw the frame.
|
||||
*/
|
||||
const WRITE_TIMEOUT_MS = 12;
|
||||
|
||||
/**
|
||||
* Threshold of max held chunks in the write buffer, that were already processed.
|
||||
* This is a tradeoff between extensive write buffer shifts (bad runtime) and high
|
||||
* memory consumption by data thats not used anymore.
|
||||
*/
|
||||
const WRITE_BUFFER_LENGTH_THRESHOLD = 50;
|
||||
|
||||
export class WriteBuffer {
|
||||
private _writeBuffer: (string | Uint8Array)[] = [];
|
||||
private _callbacks: ((() => void) | undefined)[] = [];
|
||||
private _pendingData = 0;
|
||||
private _bufferOffset = 0;
|
||||
|
||||
constructor(private _action: (data: string | Uint8Array) => void) { }
|
||||
|
||||
public writeSync(data: string | Uint8Array): void {
|
||||
// force sync processing on pending data chunks to avoid in-band data scrambling
|
||||
// does the same as innerWrite but without event loop
|
||||
if (this._writeBuffer.length) {
|
||||
for (let i = this._bufferOffset; i < this._writeBuffer.length; ++i) {
|
||||
const data = this._writeBuffer[i];
|
||||
const cb = this._callbacks[i];
|
||||
this._action(data);
|
||||
if (cb) cb();
|
||||
}
|
||||
// reset all to avoid reprocessing of chunks with scheduled innerWrite call
|
||||
this._writeBuffer = [];
|
||||
this._callbacks = [];
|
||||
this._pendingData = 0;
|
||||
// stop scheduled innerWrite by offset > length condition
|
||||
this._bufferOffset = 0x7FFFFFFF;
|
||||
}
|
||||
// handle current data chunk
|
||||
this._action(data);
|
||||
}
|
||||
|
||||
public write(data: string | Uint8Array, callback?: () => void): void {
|
||||
if (this._pendingData > DISCARD_WATERMARK) {
|
||||
throw new Error('write data discarded, use flow control to avoid losing data');
|
||||
}
|
||||
|
||||
// schedule chunk processing for next event loop run
|
||||
if (!this._writeBuffer.length) {
|
||||
this._bufferOffset = 0;
|
||||
setTimeout(() => this._innerWrite());
|
||||
}
|
||||
|
||||
this._pendingData += data.length;
|
||||
this._writeBuffer.push(data);
|
||||
this._callbacks.push(callback);
|
||||
}
|
||||
|
||||
protected _innerWrite(): void {
|
||||
const startTime = Date.now();
|
||||
while (this._writeBuffer.length > this._bufferOffset) {
|
||||
const data = this._writeBuffer[this._bufferOffset];
|
||||
const cb = this._callbacks[this._bufferOffset];
|
||||
this._bufferOffset++;
|
||||
|
||||
this._action(data);
|
||||
this._pendingData -= data.length;
|
||||
if (cb) cb();
|
||||
|
||||
if (Date.now() - startTime >= WRITE_TIMEOUT_MS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (this._writeBuffer.length > this._bufferOffset) {
|
||||
// Allow renderer to catch up before processing the next batch
|
||||
// trim already processed chunks if we are above threshold
|
||||
if (this._bufferOffset > WRITE_BUFFER_LENGTH_THRESHOLD) {
|
||||
this._writeBuffer = this._writeBuffer.slice(this._bufferOffset);
|
||||
this._callbacks = this._callbacks.slice(this._bufferOffset);
|
||||
this._bufferOffset = 0;
|
||||
}
|
||||
setTimeout(() => this._innerWrite(), 0);
|
||||
} else {
|
||||
this._writeBuffer = [];
|
||||
this._callbacks = [];
|
||||
this._pendingData = 0;
|
||||
this._bufferOffset = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,9 +32,9 @@ describe('CoreMouseService', () => {
|
||||
const cms = new CoreMouseService(bufferService, coreService);
|
||||
assert.deepEqual(Object.keys((cms as any)._protocols), ['NONE', 'X10', 'VT200', 'DRAG', 'ANY']);
|
||||
});
|
||||
it('default encodings - DEFAULT, SGR', () => {
|
||||
it('default encodings - DEFAULT, UTF8, SGR, URXVT', () => {
|
||||
const cms = new CoreMouseService(bufferService, coreService);
|
||||
assert.deepEqual(Object.keys((cms as any)._encodings), ['DEFAULT', 'SGR']);
|
||||
assert.deepEqual(Object.keys((cms as any)._encodings), ['DEFAULT', 'UTF8', 'SGR', 'URXVT']);
|
||||
});
|
||||
it('protocol/encoding setter, reset', () => {
|
||||
const cms = new CoreMouseService(bufferService, coreService);
|
||||
@@ -79,7 +79,6 @@ describe('CoreMouseService', () => {
|
||||
cms = new CoreMouseService(bufferService, coreService);
|
||||
reports = [];
|
||||
coreService.triggerDataEvent = (data: string, userInput?: boolean) => reports.push(data);
|
||||
coreService.triggerBinaryEvent = (data: string) => reports.push(data);
|
||||
});
|
||||
it('NONE', () => {
|
||||
assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), false);
|
||||
@@ -144,14 +143,22 @@ describe('CoreMouseService', () => {
|
||||
cms.activeProtocol = 'ANY';
|
||||
for (let i = 0; i < bufferService.cols; ++i) {
|
||||
assert.equal(cms.triggerMouseEvent({ col: i, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), true);
|
||||
if (i > 222) {
|
||||
// supress mouse reports if we are out of addressible range (max. 222)
|
||||
assert.deepEqual(toBytes(reports.pop()), []);
|
||||
} else {
|
||||
// capped at 95
|
||||
if (i < 95) {
|
||||
assert.deepEqual(toBytes(reports.pop()), [0x1b, 0x5b, 0x4d, 0x20, i + 33, 0x21]);
|
||||
} else {
|
||||
assert.deepEqual(toBytes(reports.pop()), [0x1b, 0x5b, 0x4d, 0x20, 0x7f, 0x21]);
|
||||
}
|
||||
}
|
||||
});
|
||||
it('UTF8 encoding', () => {
|
||||
cms.activeProtocol = 'ANY';
|
||||
cms.activeEncoding = 'UTF8';
|
||||
for (let i = 0; i < bufferService.cols; ++i) {
|
||||
assert.equal(cms.triggerMouseEvent({ col: i, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), true);
|
||||
assert.deepEqual(toBytes(reports.pop()), [0x1b, 0x5b, 0x4d, 0x20, i + 33, 0x21]);
|
||||
}
|
||||
});
|
||||
it('SGR encoding', () => {
|
||||
cms.activeProtocol = 'ANY';
|
||||
cms.activeEncoding = 'SGR';
|
||||
@@ -160,6 +167,14 @@ describe('CoreMouseService', () => {
|
||||
assert.deepEqual(reports.pop(), `\x1b[<0;${i + 1};1M`);
|
||||
}
|
||||
});
|
||||
it('URXVT', () => {
|
||||
cms.activeProtocol = 'ANY';
|
||||
cms.activeEncoding = 'URXVT';
|
||||
for (let i = 0; i < bufferService.cols; ++i) {
|
||||
assert.equal(cms.triggerMouseEvent({ col: i, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), true);
|
||||
assert.deepEqual(reports.pop(), `\x1b[32;${i + 1};1M`);
|
||||
}
|
||||
});
|
||||
});
|
||||
it('eventCodes with modifiers (DEFAULT encoding)', () => {
|
||||
// TODO: implement AUX button tests
|
||||
|
||||
@@ -121,17 +121,26 @@ const DEFAULT_ENCODINGS: {[key: string]: CoreMouseEncoding} = {
|
||||
/**
|
||||
* DEFAULT - CSI M Pb Px Py
|
||||
* Single byte encoding for coords and event code.
|
||||
* Can encode values up to 223 (1-based).
|
||||
* Can encode values up to 223. The Encoding of higher
|
||||
* values is not UTF-8 compatible (and currently limited
|
||||
* to 95 in xterm.js).
|
||||
*/
|
||||
DEFAULT: (e: ICoreMouseEvent) => {
|
||||
const params = [eventCode(e, false) + 32, e.col + 32, e.row + 32];
|
||||
// supress mouse report if we exceed addressible range
|
||||
// Note this is handled differently by emulators
|
||||
// - xterm: sends 0;0 coords instead
|
||||
// - vte, konsole: no report
|
||||
if (params[0] > 255 || params[1] > 255 || params[2] > 255) {
|
||||
return '';
|
||||
}
|
||||
let params = [eventCode(e, false) + 32, e.col + 32, e.row + 32];
|
||||
// FIXME: we are currently limited to ASCII range
|
||||
params = params.map(v => (v > 127) ? 127 : v);
|
||||
// FIXED: params = params.map(v => (v > 255) ? 0 : value);
|
||||
return `\x1b[M${S(params[0])}${S(params[1])}${S(params[2])}`;
|
||||
},
|
||||
/**
|
||||
* UTF8 - CSI M Pb Px Py
|
||||
* Same as DEFAULT, but with optional 2-byte UTF8
|
||||
* encoding for values > 223 (can encode up to 2015).
|
||||
*/
|
||||
UTF8: (e: ICoreMouseEvent) => {
|
||||
let params = [eventCode(e, false) + 32, e.col + 32, e.row + 32];
|
||||
// limit to 2-byte UTF8
|
||||
params = params.map(v => (v > 2047) ? 0 : v);
|
||||
return `\x1b[M${S(params[0])}${S(params[1])}${S(params[2])}`;
|
||||
},
|
||||
/**
|
||||
@@ -142,6 +151,14 @@ const DEFAULT_ENCODINGS: {[key: string]: CoreMouseEncoding} = {
|
||||
SGR: (e: ICoreMouseEvent) => {
|
||||
const final = (e.action === CoreMouseAction.UP && e.button !== CoreMouseButton.WHEEL) ? 'm' : 'M';
|
||||
return `\x1b[<${eventCode(e, true)};${e.col};${e.row}${final}`;
|
||||
},
|
||||
/**
|
||||
* URXVT - CSI Pb ; Px ; Py M
|
||||
* Same button encoding as default, decimal encoding for coords.
|
||||
* Ambiguity with other sequences, should not be used.
|
||||
*/
|
||||
URXVT: (e: ICoreMouseEvent) => {
|
||||
return `\x1b[${eventCode(e, false) + 32};${e.col};${e.row}M`;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -150,7 +167,7 @@ const DEFAULT_ENCODINGS: {[key: string]: CoreMouseEncoding} = {
|
||||
*
|
||||
* Provides mouse tracking reports with different protocols and encodings.
|
||||
* - protocols: NONE (default), X10, VT200, DRAG, ANY
|
||||
* - encodings: DEFAULT, SGR (UTF8, URXVT removed in #2507)
|
||||
* - encodings: DEFAULT, SGR, UTF8, URXVT
|
||||
*
|
||||
* Custom protocols/encodings can be added by `addProtocol` / `addEncoding`.
|
||||
* To activate a protocol/encoding, set `activeProtocol` / `activeEncoding`.
|
||||
@@ -268,14 +285,7 @@ export class CoreMouseService implements ICoreMouseService {
|
||||
|
||||
// encode report and send
|
||||
const report = this._encodings[this._activeEncoding](e);
|
||||
if (report) {
|
||||
// always send DEFAULT as binary data
|
||||
if (this._activeEncoding === 'DEFAULT') {
|
||||
this._coreService.triggerBinaryEvent(report);
|
||||
} else {
|
||||
this._coreService.triggerDataEvent(report, true);
|
||||
}
|
||||
}
|
||||
this._coreService.triggerDataEvent(report, true);
|
||||
|
||||
this._lastEvent = e;
|
||||
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { ICoreService } from 'common/services/Services';
|
||||
import { CoreService } from 'common/services/CoreService';
|
||||
import { MockBufferService, MockLogService, MockOptionsService } from 'common/TestUtils.test';
|
||||
import { assert } from 'chai';
|
||||
|
||||
describe('CoreService', () => {
|
||||
let coreService: ICoreService;
|
||||
|
||||
beforeEach(() => {
|
||||
coreService = new CoreService(
|
||||
() => {},
|
||||
new MockBufferService(80, 30),
|
||||
new MockLogService(),
|
||||
new MockOptionsService());
|
||||
});
|
||||
|
||||
describe('reset', () => {
|
||||
it('should not affect isCursorInitialized', () => {
|
||||
coreService.isCursorInitialized = true;
|
||||
coreService.reset();
|
||||
assert.equal(coreService.isCursorInitialized, true);
|
||||
coreService.isCursorInitialized = false;
|
||||
coreService.reset();
|
||||
assert.equal(coreService.isCursorInitialized, false);
|
||||
});
|
||||
});
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user