Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
602eea4bfa | ||
|
|
ab7da8193c |
@@ -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 \
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"dockerFile": "Dockerfile",
|
||||
"appPort": 3000,
|
||||
"extensions": [
|
||||
"dbaeumer.vscode-eslint",
|
||||
"editorconfig.editorconfig"
|
||||
"editorconfig.editorconfig",
|
||||
"ms-vscode.vscode-typescript-tslint-plugin"
|
||||
]
|
||||
}
|
||||
|
||||
-150
@@ -1,150 +0,0 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es6": true,
|
||||
"node": true
|
||||
},
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"project": [
|
||||
"src/browser/tsconfig.json",
|
||||
"src/common/tsconfig.json",
|
||||
"src/tsconfig.json",
|
||||
"test/api/tsconfig.json",
|
||||
"test/benchmark/tsconfig.json",
|
||||
"addons/xterm-addon-attach/src/tsconfig.json",
|
||||
"addons/xterm-addon-fit/src/tsconfig.json",
|
||||
"addons/xterm-addon-search/src/tsconfig.json",
|
||||
"addons/xterm-addon-unicode11/src/tsconfig.json",
|
||||
"addons/xterm-addon-web-links/src/tsconfig.json",
|
||||
"addons/xterm-addon-webgl/src/tsconfig.json",
|
||||
"addons/xterm-addon-serialize/src/tsconfig.json",
|
||||
"addons/xterm-addon-serialize/benchmark/tsconfig.json"
|
||||
],
|
||||
"sourceType": "module"
|
||||
},
|
||||
"ignorePatterns": [
|
||||
"**/typings/*.d.ts"
|
||||
],
|
||||
"plugins": [
|
||||
"@typescript-eslint"
|
||||
],
|
||||
"rules": {
|
||||
"@typescript-eslint/array-type": [
|
||||
"error",
|
||||
{
|
||||
"default": "array-simple",
|
||||
"readonly": "generic"
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/class-name-casing": "error",
|
||||
"@typescript-eslint/consistent-type-definitions": "error",
|
||||
"@typescript-eslint/explicit-function-return-type": [
|
||||
"error",
|
||||
{
|
||||
"allowExpressions": true
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/indent": [
|
||||
"error",
|
||||
2
|
||||
],
|
||||
"@typescript-eslint/interface-name-prefix": [
|
||||
"error",
|
||||
"always"
|
||||
],
|
||||
"@typescript-eslint/member-delimiter-style": [
|
||||
"error",
|
||||
{
|
||||
"multiline": {
|
||||
"delimiter": "semi",
|
||||
"requireLast": true
|
||||
},
|
||||
"singleline": {
|
||||
"delimiter": "comma",
|
||||
"requireLast": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/naming-convention": [
|
||||
"error",
|
||||
{ "selector": "default", "format": ["camelCase"] },
|
||||
// variableLike
|
||||
{ "selector": "variable", "format": ["camelCase", "UPPER_CASE"] },
|
||||
{ "selector": "variable", "filter": "^I.+Service$", "format": ["PascalCase"], "prefix": ["I"] },
|
||||
// memberLike
|
||||
{ "selector": "memberLike", "modifiers": ["private"], "format": ["camelCase"], "leadingUnderscore": "require" },
|
||||
{ "selector": "memberLike", "modifiers": ["protected"], "format": ["camelCase"], "leadingUnderscore": "require" },
|
||||
{ "selector": "enumMember", "format": ["UPPER_CASE"] },
|
||||
// memberLike - Allow enum-like objects to use UPPER_CASE
|
||||
{ "selector": "property", "modifiers": ["public"], "format": ["camelCase", "UPPER_CASE"] },
|
||||
{ "selector": "method", "modifiers": ["public"], "format": ["camelCase", "UPPER_CASE"] },
|
||||
// typeLike
|
||||
{ "selector": "typeLike", "format": ["PascalCase"] },
|
||||
{ "selector": "interface", "format": ["PascalCase"], "prefix": ["I"] }
|
||||
],
|
||||
"@typescript-eslint/prefer-namespace-keyword": "error",
|
||||
"@typescript-eslint/type-annotation-spacing": "error",
|
||||
"@typescript-eslint/quotes": [
|
||||
"error",
|
||||
"single",
|
||||
{ "allowTemplateLiterals": true }
|
||||
],
|
||||
"@typescript-eslint/semi": [
|
||||
"error",
|
||||
"always"
|
||||
],
|
||||
"comma-dangle": [
|
||||
"error",
|
||||
{
|
||||
"objects": "never",
|
||||
"arrays": "never",
|
||||
"functions": "never"
|
||||
}
|
||||
],
|
||||
"curly": [
|
||||
"error",
|
||||
"multi-line"
|
||||
],
|
||||
"eol-last": "error",
|
||||
"eqeqeq": [
|
||||
"error",
|
||||
"always"
|
||||
],
|
||||
"keyword-spacing": "error",
|
||||
"new-parens": "error",
|
||||
"no-duplicate-imports": "error",
|
||||
"no-else-return": [
|
||||
"error",
|
||||
{
|
||||
"allowElseIf": false
|
||||
}
|
||||
],
|
||||
"no-eval": "error",
|
||||
"no-irregular-whitespace": "error",
|
||||
"no-restricted-imports": [
|
||||
"error",
|
||||
{
|
||||
"patterns": [
|
||||
".*\\/out\\/.*"
|
||||
]
|
||||
}
|
||||
],
|
||||
"no-trailing-spaces": "error",
|
||||
"no-unsafe-finally": "error",
|
||||
"no-var": "error",
|
||||
"one-var": [
|
||||
"error",
|
||||
"never"
|
||||
],
|
||||
"prefer-const": "error",
|
||||
"spaced-comment": [
|
||||
"error",
|
||||
"always",
|
||||
{
|
||||
"markers": ["/"],
|
||||
"exceptions": ["-"]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
+5
-7
@@ -2,9 +2,6 @@ node_modules/
|
||||
*.swp
|
||||
.lock-wscript
|
||||
lib/
|
||||
out/
|
||||
out-test/
|
||||
.nyc_output/
|
||||
Makefile.gyp
|
||||
*.Makefile
|
||||
*.target.gyp.mk
|
||||
@@ -16,12 +13,13 @@ npm-debug.log
|
||||
.env
|
||||
build/
|
||||
.DS_Store
|
||||
fixtures/typings-test/*.js
|
||||
package-lock.json
|
||||
|
||||
# Directories needed for code coverage
|
||||
coverage/
|
||||
.nyc_output/
|
||||
|
||||
# Keep bundled code out of Git
|
||||
dist/
|
||||
demo/dist/
|
||||
|
||||
# dont commit benchmark folders
|
||||
.benchmark/
|
||||
timeline/
|
||||
|
||||
+1
-8
@@ -6,9 +6,6 @@
|
||||
!*.js
|
||||
!*.json
|
||||
|
||||
# Whitelist - css/
|
||||
!css/**/*.css
|
||||
|
||||
# Whitelist - dist/
|
||||
!dist/**/*.js
|
||||
!dist/**/*.js.map
|
||||
@@ -36,13 +33,11 @@
|
||||
!typings/*.d.ts
|
||||
|
||||
# Blacklist - (normal behavior) these will override any whitelist
|
||||
*.d.ts.map
|
||||
*.test.ts
|
||||
*.test.d.ts
|
||||
*.test.js
|
||||
*.test.js.map
|
||||
lib/test/
|
||||
webpack.config.js
|
||||
|
||||
docs/
|
||||
/.idea/
|
||||
@@ -50,7 +45,5 @@ docs/
|
||||
bin/
|
||||
build/
|
||||
fixtures/
|
||||
coverage/
|
||||
demo/
|
||||
.devcontainer/
|
||||
out/
|
||||
addons/
|
||||
|
||||
Vendored
+1
-22
@@ -13,28 +13,7 @@
|
||||
"runtimeArgs": [
|
||||
"--colors",
|
||||
"--recursive",
|
||||
"${workspaceRoot}/out/**/*.test.js"
|
||||
],
|
||||
"env": {
|
||||
"NODE_PATH": "${workspaceRoot}/out"
|
||||
},
|
||||
"sourceMaps": true,
|
||||
"outFiles": [ "${workspaceRoot}/out/**/*.js" ],
|
||||
"internalConsoleOptions": "openOnSessionStart"
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Integration Tests",
|
||||
"cwd": "${workspaceRoot}",
|
||||
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/mocha",
|
||||
"windows": {
|
||||
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/mocha.cmd"
|
||||
},
|
||||
"runtimeArgs": [
|
||||
"--colors",
|
||||
"--recursive",
|
||||
"${workspaceRoot}/out/**/*.api.js"
|
||||
"${workspaceRoot}/lib"
|
||||
],
|
||||
"sourceMaps": true,
|
||||
"outFiles": [ "${workspaceRoot}/lib/**/*.js" ],
|
||||
|
||||
Vendored
+1
-2
@@ -1,4 +1,3 @@
|
||||
{
|
||||
"typescript.preferences.importModuleSpecifier": "non-relative",
|
||||
"typescript.preferences.quoteStyle": "single"
|
||||
"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.
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
# [](https://xtermjs.org)
|
||||
|
||||
[](https://dev.azure.com/xtermjs/xterm.js/_build/latest?definitionId=3)
|
||||
[](https://coveralls.io/github/xtermjs/xterm.js?branch=master)
|
||||
|
||||
Xterm.js is a front-end component written in TypeScript that lets applications bring fully-featured terminals to their users in the browser. It's used by popular projects such as VS Code, Hyper and Theia.
|
||||
|
||||
## Features
|
||||
|
||||
- **Terminal apps just work**: Xterm.js works with most terminal apps such as `bash`, `vim` and `tmux`, this includes support for curses-based apps and mouse event support.
|
||||
- **Performant**: Xterm.js is *really* fast, it even includes a GPU-accelerated renderer.
|
||||
- **Perfomant**: 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
|
||||
@@ -32,8 +33,8 @@ To start using xterm.js on your browser, add the `xterm.js` and `xterm.css` to t
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="node_modules/xterm/css/xterm.css" />
|
||||
<script src="node_modules/xterm/lib/xterm.js"></script>
|
||||
<link rel="stylesheet" href="node_modules/xterm/dist/xterm.css" />
|
||||
<script src="node_modules/xterm/dist/xterm.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="terminal"></div>
|
||||
@@ -56,38 +57,43 @@ import { Terminal } from 'xterm';
|
||||
|
||||
### Addons
|
||||
|
||||
⚠️ *This section describes the new addon format introduced in v3.14.0, see [here](https://github.com/xtermjs/xterm.js/blob/3.14.2/README.md#addons) for the instructions on the old format*
|
||||
Addons are JavaScript modules that extend the `Terminal` prototype with new methods and attributes to provide additional functionality. There are a handful available in the main repository in the `src/addons` directory and you can even write your own by using the [public API](https://github.com/xtermjs/xterm.js/blob/master/typings/xterm.d.ts).
|
||||
|
||||
Addons are separate modules that extend the `Terminal` by building on the [xterm.js API](https://github.com/xtermjs/xterm.js/blob/master/typings/xterm.d.ts). To use an addon you first need to install it in your project:
|
||||
To use an addon, just import the JavaScript module and pass it to `Terminal`'s `applyAddon` method:
|
||||
|
||||
```bash
|
||||
npm i -S xterm-addon-web-links
|
||||
```
|
||||
|
||||
Then import the addon, instantiate it and call `Terminal.loadAddon`:
|
||||
|
||||
```ts
|
||||
```javascript
|
||||
import { Terminal } from 'xterm';
|
||||
import { WebLinksAddon } from 'xterm-addon-web-links';
|
||||
import * as fit from 'xterm/lib/addons/fit/fit';
|
||||
|
||||
const terminal = new Terminal();
|
||||
// Load WebLinksAddon on terminal, this is all that's needed to get web links
|
||||
// working in the terminal.
|
||||
terminal.loadAddon(new WebLinksAddon());
|
||||
Terminal.applyAddon(fit);
|
||||
|
||||
var xterm = new Terminal(); // Instantiate the terminal
|
||||
xterm.fit(); // Use the `fit` method, provided by the `fit` addon
|
||||
```
|
||||
|
||||
The xterm.js team maintains the following addons but they can be built by anyone:
|
||||
You will also need to include the addon's CSS file if it has one in the folder.
|
||||
|
||||
- [`xterm-addon-attach`](https://github.com/xtermjs/xterm.js/tree/master/addons/xterm-addon-attach): Attaches to a server running a process via a websocket
|
||||
- [`xterm-addon-fit`](https://github.com/xtermjs/xterm.js/tree/master/addons/xterm-addon-fit): Fits the terminal to the containing element
|
||||
- [`xterm-addon-search`](https://github.com/xtermjs/xterm.js/tree/master/addons/xterm-addon-search): Adds search functionality
|
||||
- [`xterm-addon-web-links`](https://github.com/xtermjs/xterm.js/tree/master/addons/xterm-addon-web-links): Adds web link detection and interaction
|
||||
#### Importing Addons in TypeScript
|
||||
|
||||
There are currently no typings for addons if they are accessed via extending Terminal prototype, so you will need to upcast if using TypeScript, eg. `(xterm as any).fit()`. Alternatively, you can import the addon function and enhance the terminal on demand. This has better typing support and is friendly to treeshaking.
|
||||
|
||||
```typescript
|
||||
import { Terminal } from 'xterm';
|
||||
import { fit } from 'xterm/lib/addons/fit/fit';
|
||||
const xterm = new Terminal();
|
||||
|
||||
fit(xterm); // Fit the terminal when necessary
|
||||
```
|
||||
|
||||
## Browser Support
|
||||
|
||||
Since xterm.js is typically implemented as a developer tool, only modern browsers are supported officially. Specifically the latest versions of *Chrome*, *Edge*, *Firefox* and *Safari*.
|
||||
Since xterm.js is typically implemented as a developer tool, only modern browsers are supported officially. Here is a list of the versions we aim to support:
|
||||
|
||||
We also partially support *Intenet Explorer 11*, meaning xterm.js should work for the most part, but we reserve the right to not provide workarounds specifically for it unless it's absolutely necessary to get the basic input/output flow working.
|
||||
- Chrome latest
|
||||
- Edge latest
|
||||
- Firefox latest
|
||||
- Safari latest
|
||||
- IE11
|
||||
|
||||
Xterm.js works seamlessly in [Electron](https://electronjs.org/) apps and may even work on earlier versions of the browsers, these are the versions we strive to keep working.
|
||||
|
||||
@@ -110,7 +116,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.
|
||||
@@ -152,15 +158,6 @@ Xterm.js is used in several world-class applications to provide great terminal e
|
||||
- [**CodeInterview.io**](https://codeinterview.io): A coding interview platform in 25+ languages and many web frameworks. Uses xterm.js to provide shell access.
|
||||
- [**Bastillion**](https://www.bastillion.io): Bastillion is an open-source web-based SSH console that centrally manages administrative access to systems.
|
||||
- [**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
|
||||
- [**Gus**](https://gus.jp): A shared coding pad where you can run Python with xterm.js
|
||||
- [**Linode**](https://linode.com): Linode uses xterm.js to provide users a web console for their Linode instances.
|
||||
- [**FluffOS**](https://www.fluffos.info): Active maintained LPMUD driver with websocket support.
|
||||
- [**x-terminal**](https://atom.io/packages/x-terminal): Atom plugin for providing terminals inside your Atom workspace.
|
||||
- [**CoCalc**](https://cocalc.com/): Lots of free software pre-installed, to chat, collaborate, develop, program, publish, research, share, teach, in C++, HTML, Julia, Jupyter, LaTeX, Markdown, Python, R, SageMath, Scala, ...
|
||||
|
||||
[And much more...](https://github.com/xtermjs/xterm.js/network/dependents)
|
||||
|
||||
@@ -190,6 +187,6 @@ You can read the [guide on the wiki](https://github.com/xtermjs/xterm.js/wiki/Co
|
||||
|
||||
If you contribute code to this project, you are implicitly allowing your code to be distributed under the MIT license. You are also implicitly verifying that all code is your original work.
|
||||
|
||||
Copyright (c) 2017-2019, [The xterm.js authors](https://github.com/xtermjs/xterm.js/graphs/contributors) (MIT License)<br>
|
||||
Copyright (c) 2017-2018, [The xterm.js authors](https://github.com/xtermjs/xterm.js/graphs/contributors) (MIT License)<br>
|
||||
Copyright (c) 2014-2017, SourceLair, Private Company ([www.sourcelair.com](https://www.sourcelair.com/home)) (MIT License)<br>
|
||||
Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
lib
|
||||
node_modules
|
||||
@@ -1,5 +0,0 @@
|
||||
**/*.api.js
|
||||
**/*.api.ts
|
||||
tsconfig.json
|
||||
.yarnrc
|
||||
webpack.config.js
|
||||
@@ -1,19 +0,0 @@
|
||||
Copyright (c) 2017, The xterm.js authors (https://github.com/xtermjs/xterm.js)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
@@ -1,22 +0,0 @@
|
||||
## xterm-addon-attach
|
||||
|
||||
An addon for [xterm.js](https://github.com/xtermjs/xterm.js) that enables attaching to a web socket. This addon requires xterm.js v4+.
|
||||
|
||||
### Install
|
||||
|
||||
```bash
|
||||
npm install --save xterm-addon-attach
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
```ts
|
||||
import { Terminal } from 'xterm';
|
||||
import { AttachAddon } from 'xterm-addon-attach';
|
||||
|
||||
const terminal = new Terminal();
|
||||
const attachAddon = new AttachAddon(webSocket);
|
||||
terminal.loadAddon(attachAddon);
|
||||
```
|
||||
|
||||
See the full [API](https://github.com/xtermjs/xterm.js/blob/master/addons/xterm-addon-attach/typings/xterm-addon-attach.d.ts) for more advanced usage.
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"name": "xterm-addon-attach",
|
||||
"version": "0.5.0",
|
||||
"author": {
|
||||
"name": "The xterm.js authors",
|
||||
"url": "https://xtermjs.org/"
|
||||
},
|
||||
"main": "lib/xterm-addon-attach.js",
|
||||
"types": "typings/xterm-addon-attach.d.ts",
|
||||
"repository": "https://github.com/xtermjs/xterm.js",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build": "../../node_modules/.bin/tsc -p src",
|
||||
"prepackage": "npm run build",
|
||||
"package": "../../node_modules/.bin/webpack",
|
||||
"prepublishOnly": "npm run package"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"xterm": "^4.0.0"
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import WebSocket = require('ws');
|
||||
import { openTerminal, pollFor, getBrowserType } from '../../../out-test/api/TestUtils';
|
||||
import { Browser, Page } from 'playwright-core';
|
||||
|
||||
const APP = 'http://127.0.0.1:3000/test';
|
||||
|
||||
let browser: Browser;
|
||||
let page: Page;
|
||||
const width = 800;
|
||||
const height = 600;
|
||||
|
||||
describe('AttachAddon', () => {
|
||||
before(async function(): Promise<any> {
|
||||
const browserType = getBrowserType();
|
||||
browser = await browserType.launch({ dumpio: true,
|
||||
headless: process.argv.indexOf('--headless') !== -1
|
||||
});
|
||||
page = await (await browser.newContext()).newPage();
|
||||
await page.setViewportSize({ width, height });
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await browser.close();
|
||||
});
|
||||
|
||||
beforeEach(async () => await page.goto(APP));
|
||||
|
||||
it('string', async function(): Promise<any> {
|
||||
await openTerminal(page, { rendererType: 'dom' });
|
||||
const port = 8080;
|
||||
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.active.getLine(0).translateToString(true)`, 'foo');
|
||||
server.close();
|
||||
});
|
||||
|
||||
it('utf8', async function(): Promise<any> {
|
||||
await openTerminal(page, { rendererType: 'dom' });
|
||||
const port = 8080;
|
||||
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.active.getLine(0).translateToString(true)`, 'foo');
|
||||
server.close();
|
||||
});
|
||||
});
|
||||
@@ -1,79 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2014, 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*
|
||||
* Implements the attach method, that attaches the terminal to a WebSocket stream.
|
||||
*/
|
||||
|
||||
import { Terminal, IDisposable, ITerminalAddon } from 'xterm';
|
||||
|
||||
interface IAttachOptions {
|
||||
bidirectional?: boolean;
|
||||
}
|
||||
|
||||
export class AttachAddon implements ITerminalAddon {
|
||||
private _socket: WebSocket;
|
||||
private _bidirectional: boolean;
|
||||
private _disposables: IDisposable[] = [];
|
||||
|
||||
constructor(socket: WebSocket, options?: IAttachOptions) {
|
||||
this._socket = socket;
|
||||
// always set binary type to arraybuffer, we do not handle blobs
|
||||
this._socket.binaryType = 'arraybuffer';
|
||||
this._bidirectional = (options && options.bidirectional === false) ? false : true;
|
||||
}
|
||||
|
||||
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._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()));
|
||||
this._disposables.push(addSocketListener(this._socket, 'error', () => this.dispose()));
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._disposables.forEach(d => d.dispose());
|
||||
}
|
||||
|
||||
private _sendData(data: string): void {
|
||||
// TODO: do something better than just swallowing
|
||||
// the data if the socket is not in a working condition
|
||||
if (this._socket.readyState !== 1) {
|
||||
return;
|
||||
}
|
||||
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 {
|
||||
socket.addEventListener(type, handler);
|
||||
return {
|
||||
dispose: () => {
|
||||
if (!handler) {
|
||||
// Already disposed
|
||||
return;
|
||||
}
|
||||
socket.removeEventListener(type, handler);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { Terminal, ILinkMatcherOptions, ITerminalAddon } from 'xterm';
|
||||
|
||||
declare module 'xterm-addon-attach' {
|
||||
export interface IAttachOptions {
|
||||
/**
|
||||
* Whether input should be written to the backend. Defaults to `true`.
|
||||
*/
|
||||
bidirectional?: boolean;
|
||||
}
|
||||
|
||||
export class AttachAddon implements ITerminalAddon {
|
||||
constructor(socket: WebSocket, options?: IAttachOptions);
|
||||
public activate(terminal: Terminal): void;
|
||||
public dispose(): void;
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
|
||||
const addonName = 'AttachAddon';
|
||||
const mainFile = 'xterm-addon-attach.js';
|
||||
|
||||
module.exports = {
|
||||
entry: `./out/${addonName}.js`,
|
||||
devtool: 'source-map',
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
use: ["source-map-loader"],
|
||||
enforce: "pre",
|
||||
exclude: /node_modules/
|
||||
}
|
||||
]
|
||||
},
|
||||
output: {
|
||||
filename: mainFile,
|
||||
path: path.resolve('./lib'),
|
||||
library: addonName,
|
||||
libraryTarget: 'umd'
|
||||
},
|
||||
mode: 'production'
|
||||
};
|
||||
@@ -1,2 +0,0 @@
|
||||
lib
|
||||
node_modules
|
||||
@@ -1,5 +0,0 @@
|
||||
**/*.api.js
|
||||
**/*.api.ts
|
||||
tsconfig.json
|
||||
.yarnrc
|
||||
webpack.config.js
|
||||
@@ -1,19 +0,0 @@
|
||||
Copyright (c) 2019, The xterm.js authors (https://github.com/xtermjs/xterm.js)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
@@ -1,24 +0,0 @@
|
||||
## xterm-addon-fit
|
||||
|
||||
An addon for [xterm.js](https://github.com/xtermjs/xterm.js) that enables fitting the terminal's dimensions to a containing element. This addon requires xterm.js v4+.
|
||||
|
||||
### Install
|
||||
|
||||
```bash
|
||||
npm install --save xterm-addon-fit
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
```ts
|
||||
import { Terminal } from 'xterm';
|
||||
import { FitAddon } from 'xterm-addon-fit';
|
||||
|
||||
const terminal = new Terminal();
|
||||
const fitAddon = new FitAddon();
|
||||
terminal.loadAddon(fitAddon);
|
||||
terminal.open(containerElement);
|
||||
fitAddon.fit();
|
||||
```
|
||||
|
||||
See the full [API](https://github.com/xtermjs/xterm.js/blob/master/addons/xterm-addon-fit/typings/xterm-addon-fit.d.ts) for more advanced usage.
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"name": "xterm-addon-fit",
|
||||
"version": "0.3.0",
|
||||
"author": {
|
||||
"name": "The xterm.js authors",
|
||||
"url": "https://xtermjs.org/"
|
||||
},
|
||||
"main": "lib/xterm-addon-fit.js",
|
||||
"types": "typings/xterm-addon-fit.d.ts",
|
||||
"repository": "https://github.com/xtermjs/xterm.js",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build": "../../node_modules/.bin/tsc -p src",
|
||||
"prepackage": "npm run build",
|
||||
"package": "../../node_modules/.bin/webpack",
|
||||
"prepublishOnly": "npm run package"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"xterm": "^4.0.0"
|
||||
}
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { assert } from 'chai';
|
||||
import { openTerminal, getBrowserType } from '../../../out-test/api/TestUtils';
|
||||
import { Browser, Page } from 'playwright-core';
|
||||
|
||||
const APP = 'http://127.0.0.1:3000/test';
|
||||
|
||||
let browser: Browser;
|
||||
let page: Page;
|
||||
const width = 1024;
|
||||
const height = 768;
|
||||
|
||||
describe('FitAddon', () => {
|
||||
before(async function(): Promise<any> {
|
||||
const browserType = getBrowserType();
|
||||
browser = await browserType.launch({ dumpio: true,
|
||||
headless: process.argv.indexOf('--headless') !== -1
|
||||
});
|
||||
page = await (await browser.newContext()).newPage();
|
||||
await page.setViewportSize({ width, height });
|
||||
await page.goto(APP);
|
||||
await openTerminal(page);
|
||||
});
|
||||
|
||||
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();
|
||||
const dimensions: {cols: number, rows: number} = await page.evaluate(`window.fit.proposeDimensions()`);
|
||||
assert.equal(dimensions.cols, 87);
|
||||
assert.isAbove(dimensions.rows, 24);
|
||||
assert.isBelow(dimensions.rows, 29);
|
||||
});
|
||||
|
||||
it('width', async function(): Promise<any> {
|
||||
await loadFit(1008);
|
||||
const dimensions: {cols: number, rows: number} = await page.evaluate(`window.fit.proposeDimensions()`);
|
||||
assert.equal(dimensions.cols, 110);
|
||||
assert.isAbove(dimensions.rows, 24);
|
||||
assert.isBelow(dimensions.rows, 29);
|
||||
});
|
||||
|
||||
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()`);
|
||||
const cols: number = await page.evaluate(`window.term.cols`);
|
||||
const rows: number = await page.evaluate(`window.term.rows`);
|
||||
assert.equal(cols, 87);
|
||||
assert.isAbove(rows, 24);
|
||||
assert.isBelow(rows, 29);
|
||||
});
|
||||
|
||||
it('width', async function(): Promise<any> {
|
||||
await loadFit(1008);
|
||||
await page.evaluate(`window.fit.fit()`);
|
||||
const cols: number = await page.evaluate(`window.term.cols`);
|
||||
const rows: number = await page.evaluate(`window.term.rows`);
|
||||
assert.equal(cols, 110);
|
||||
assert.isAbove(rows, 24);
|
||||
assert.isBelow(rows, 29);
|
||||
});
|
||||
|
||||
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();`);
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { Terminal, ITerminalAddon } from 'xterm';
|
||||
|
||||
interface ITerminalDimensions {
|
||||
/**
|
||||
* The number of rows in the terminal.
|
||||
*/
|
||||
rows: number;
|
||||
|
||||
/**
|
||||
* The number of columns in the terminal.
|
||||
*/
|
||||
cols: number;
|
||||
}
|
||||
|
||||
const MINIMUM_COLS = 2;
|
||||
const MINIMUM_ROWS = 1;
|
||||
|
||||
export class FitAddon implements ITerminalAddon {
|
||||
private _terminal: Terminal | undefined;
|
||||
|
||||
constructor() {}
|
||||
|
||||
public activate(terminal: Terminal): void {
|
||||
this._terminal = terminal;
|
||||
}
|
||||
|
||||
public dispose(): void {}
|
||||
|
||||
public fit(): void {
|
||||
const dims = this.proposeDimensions();
|
||||
if (!dims || !this._terminal) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Remove reliance on private API
|
||||
const core = (this._terminal as any)._core;
|
||||
|
||||
// Force a full render
|
||||
if (this._terminal.rows !== dims.rows || this._terminal.cols !== dims.cols) {
|
||||
core._renderService.clear();
|
||||
this._terminal.resize(dims.cols, dims.rows);
|
||||
}
|
||||
}
|
||||
|
||||
public proposeDimensions(): ITerminalDimensions | undefined {
|
||||
if (!this._terminal) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!this._terminal.element || !this._terminal.element.parentElement) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// TODO: Remove reliance on private API
|
||||
const core = (this._terminal as any)._core;
|
||||
|
||||
const parentElementStyle = window.getComputedStyle(this._terminal.element.parentElement);
|
||||
const parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height'));
|
||||
const parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width')));
|
||||
const elementStyle = window.getComputedStyle(this._terminal.element);
|
||||
const elementPadding = {
|
||||
top: parseInt(elementStyle.getPropertyValue('padding-top')),
|
||||
bottom: parseInt(elementStyle.getPropertyValue('padding-bottom')),
|
||||
right: parseInt(elementStyle.getPropertyValue('padding-right')),
|
||||
left: parseInt(elementStyle.getPropertyValue('padding-left'))
|
||||
};
|
||||
const elementPaddingVer = elementPadding.top + elementPadding.bottom;
|
||||
const elementPaddingHor = elementPadding.right + elementPadding.left;
|
||||
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))
|
||||
};
|
||||
return geometry;
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { Terminal, ITerminalAddon } from 'xterm';
|
||||
|
||||
declare module 'xterm-addon-fit' {
|
||||
/**
|
||||
* An xterm.js addon that enables resizing the terminal to the dimensions of
|
||||
* its containing element.
|
||||
*/
|
||||
export class FitAddon implements ITerminalAddon {
|
||||
/**
|
||||
* Creates a new fit addon.
|
||||
*/
|
||||
constructor();
|
||||
|
||||
/**
|
||||
* Activates the addon
|
||||
* @param terminal The terminal the addon is being loaded in.
|
||||
*/
|
||||
public activate(terminal: Terminal): void;
|
||||
|
||||
/**
|
||||
* Disposes the addon.
|
||||
*/
|
||||
public dispose(): void;
|
||||
|
||||
/**
|
||||
* Resizes the terminal to the dimensions of its containing element.
|
||||
*/
|
||||
public fit(): void;
|
||||
|
||||
/**
|
||||
* Gets the proposed dimensions that will be used for a fit.
|
||||
*/
|
||||
public proposeDimensions(): ITerminalDimensions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reprepresents the dimensions of a terminal.
|
||||
*/
|
||||
export interface ITerminalDimensions {
|
||||
/**
|
||||
* The number of rows in the terminal.
|
||||
*/
|
||||
rows: number;
|
||||
|
||||
/**
|
||||
* The number of columns in the terminal.
|
||||
*/
|
||||
cols: number;
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
|
||||
const addonName = 'FitAddon';
|
||||
const mainFile = 'xterm-addon-fit.js';
|
||||
|
||||
module.exports = {
|
||||
entry: `./out/${addonName}.js`,
|
||||
devtool: 'source-map',
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
use: ["source-map-loader"],
|
||||
enforce: "pre",
|
||||
exclude: /node_modules/
|
||||
}
|
||||
]
|
||||
},
|
||||
output: {
|
||||
filename: mainFile,
|
||||
path: path.resolve('./lib'),
|
||||
library: addonName,
|
||||
libraryTarget: 'umd'
|
||||
},
|
||||
mode: 'production'
|
||||
};
|
||||
@@ -1,2 +0,0 @@
|
||||
lib
|
||||
node_modules
|
||||
@@ -1,5 +0,0 @@
|
||||
**/*.api.js
|
||||
**/*.api.ts
|
||||
tsconfig.json
|
||||
.yarnrc
|
||||
webpack.config.js
|
||||
@@ -1,19 +0,0 @@
|
||||
Copyright (c) 2017, The xterm.js authors (https://github.com/xtermjs/xterm.js)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
@@ -1,23 +0,0 @@
|
||||
## xterm-addon-search
|
||||
|
||||
An addon for [xterm.js](https://github.com/xtermjs/xterm.js) that enables searching the buffer. This addon requires xterm.js v4+.
|
||||
|
||||
### Install
|
||||
|
||||
```bash
|
||||
npm install --save xterm-addon-search
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
```ts
|
||||
import { Terminal } from 'xterm';
|
||||
import { SearchAddon } from 'xterm-addon-search';
|
||||
|
||||
const terminal = new Terminal();
|
||||
const searchAddon = new SearchAddon();
|
||||
terminal.loadAddon(searchAddon);
|
||||
searchAddon.findNext('foo');
|
||||
```
|
||||
|
||||
See the full [API](https://github.com/xtermjs/xterm.js/blob/master/addons/xterm-addon-search/typings/xterm-addon-search.d.ts) for more advanced usage.
|
||||
@@ -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,21 +0,0 @@
|
||||
{
|
||||
"name": "xterm-addon-search",
|
||||
"version": "0.6.0",
|
||||
"author": {
|
||||
"name": "The xterm.js authors",
|
||||
"url": "https://xtermjs.org/"
|
||||
},
|
||||
"main": "lib/xterm-addon-search.js",
|
||||
"types": "typings/xterm-addon-search.d.ts",
|
||||
"repository": "https://github.com/xtermjs/xterm.js",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build": "../../node_modules/.bin/tsc -p src",
|
||||
"prepackage": "npm run build",
|
||||
"package": "../../node_modules/.bin/webpack",
|
||||
"prepublishOnly": "npm run package"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"xterm": "^4.0.0"
|
||||
}
|
||||
}
|
||||
@@ -1,197 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { assert } from 'chai';
|
||||
import { readFile } from 'fs';
|
||||
import { resolve } from 'path';
|
||||
import { openTerminal, writeSync, getBrowserType } from '../../../out-test/api/TestUtils';
|
||||
import { Browser, Page } from 'playwright-core';
|
||||
|
||||
const APP = 'http://127.0.0.1:3000/test';
|
||||
|
||||
let browser: Browser;
|
||||
let page: Page;
|
||||
const width = 800;
|
||||
const height = 600;
|
||||
|
||||
describe('Search Tests', function(): void {
|
||||
before(async function(): Promise<any> {
|
||||
const browserType = getBrowserType();
|
||||
browser = await browserType.launch({ dumpio: true,
|
||||
headless: process.argv.indexOf('--headless') !== -1
|
||||
});
|
||||
page = await (await browser.newContext()).newPage();
|
||||
await page.setViewportSize({ width, height });
|
||||
await page.goto(APP);
|
||||
await openTerminal(page);
|
||||
await page.evaluate(`window.search = new SearchAddon();`);
|
||||
await page.evaluate(`window.term.loadAddon(window.search);`);
|
||||
});
|
||||
|
||||
after(() => {
|
||||
browser.close();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await page.evaluate(`window.term.reset()`);
|
||||
});
|
||||
|
||||
it('Simple Search', async () => {
|
||||
await writeSync(page, 'dafhdjfldshafhldsahfkjhldhjkftestlhfdsakjfhdjhlfdsjkafhjdlk');
|
||||
assert.deepEqual(await page.evaluate(`window.search.findNext('test')`), true);
|
||||
assert.deepEqual(await page.evaluate(`window.term.getSelection()`), 'test');
|
||||
});
|
||||
|
||||
it('Scrolling Search', async () => {
|
||||
let dataString = '';
|
||||
for (let i = 0; i < 100; i++) {
|
||||
if (i === 52) {
|
||||
dataString += '$^1_3{}test$#';
|
||||
}
|
||||
dataString += makeData(50);
|
||||
}
|
||||
await writeSync(page, dataString);
|
||||
assert.deepEqual(await page.evaluate(`window.search.findNext('$^1_3{}test$#')`), true);
|
||||
assert.deepEqual(await page.evaluate(`window.term.getSelection()`), '$^1_3{}test$#');
|
||||
});
|
||||
it('Incremental Find Previous', async () => {
|
||||
await page.evaluate(`window.term.writeln('package.jsonc\\n')`);
|
||||
await writeSync(page, 'package.json pack package.lock');
|
||||
await page.evaluate(`window.search.findPrevious('pack', {incremental: true})`);
|
||||
let line: string = await page.evaluate(`window.term.buffer.active.getLine(window.term.getSelectionPosition().startRow).translateToString()`);
|
||||
let selectionPosition: { startColumn: number, startRow: number, endColumn: number, endRow: number } = await page.evaluate(`window.term.getSelectionPosition()`);
|
||||
// We look further ahead in the line to ensure that pack was selected from package.lock
|
||||
assert.deepEqual(line.substring(selectionPosition.startColumn, selectionPosition.endColumn + 8), 'package.lock');
|
||||
await page.evaluate(`window.search.findPrevious('package.j', {incremental: true})`);
|
||||
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
|
||||
assert.deepEqual(line.substring(selectionPosition.startColumn, selectionPosition.endColumn + 3), 'package.json');
|
||||
await page.evaluate(`window.search.findPrevious('package.jsonc', {incremental: true})`);
|
||||
// We have to reevaluate line because it should have switched starting rows at this point
|
||||
line = await page.evaluate(`window.term.buffer.active.getLine(window.term.getSelectionPosition().startRow).translateToString()`);
|
||||
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
|
||||
assert.deepEqual(line.substring(selectionPosition.startColumn, selectionPosition.endColumn), 'package.jsonc');
|
||||
});
|
||||
it('Incremental Find Next', async () => {
|
||||
await page.evaluate(`window.term.writeln('package.lock pack package.json package.ups\\n')`);
|
||||
await writeSync(page, 'package.jsonc');
|
||||
await page.evaluate(`window.search.findNext('pack', {incremental: true})`);
|
||||
let line: string = await page.evaluate(`window.term.buffer.active.getLine(window.term.getSelectionPosition().startRow).translateToString()`);
|
||||
let selectionPosition: { startColumn: number, startRow: number, endColumn: number, endRow: number } = await page.evaluate(`window.term.getSelectionPosition()`);
|
||||
// We look further ahead in the line to ensure that pack was selected from package.lock
|
||||
assert.deepEqual(line.substring(selectionPosition.startColumn, selectionPosition.endColumn + 8), 'package.lock');
|
||||
await page.evaluate(`window.search.findNext('package.j', {incremental: true})`);
|
||||
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
|
||||
assert.deepEqual(line.substring(selectionPosition.startColumn, selectionPosition.endColumn + 3), 'package.json');
|
||||
await page.evaluate(`window.search.findNext('package.jsonc', {incremental: true})`);
|
||||
// We have to reevaluate line because it should have switched starting rows at this point
|
||||
line = await page.evaluate(`window.term.buffer.active.getLine(window.term.getSelectionPosition().startRow).translateToString()`);
|
||||
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
|
||||
assert.deepEqual(line.substring(selectionPosition.startColumn, selectionPosition.endColumn), 'package.jsonc');
|
||||
});
|
||||
it('Simple Regex', async () => {
|
||||
await writeSync(page, '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(page, '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(page, 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(page, 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 });
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function makeData(length: number): string {
|
||||
let result = '';
|
||||
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += characters.charAt(Math.floor(Math.random() * characters.length));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { Terminal, ILinkMatcherOptions, IDisposable, ITerminalAddon } from 'xterm';
|
||||
|
||||
declare module 'xterm-addon-search' {
|
||||
/**
|
||||
* Options for a search.
|
||||
*/
|
||||
export interface ISearchOptions {
|
||||
/**
|
||||
* Whether the search term is a regex.
|
||||
*/
|
||||
regex?: boolean;
|
||||
|
||||
/**
|
||||
* Whether to search for a whole word, the result is only valid if it's
|
||||
* surrounded in "non-word" characters such as `_`, `(`, `)` or space.
|
||||
*/
|
||||
wholeWord?: boolean;
|
||||
|
||||
/**
|
||||
* Whether the search is case sensitive.
|
||||
*/
|
||||
caseSensitive?: boolean;
|
||||
|
||||
/**
|
||||
* Whether to do an incremental search, this will expand the selection if it
|
||||
* still matches the term the user typed. Note that this only affects
|
||||
* `findNext`, not `findPrevious`.
|
||||
*/
|
||||
incremental?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* An xterm.js addon that provides search functionality.
|
||||
*/
|
||||
export class SearchAddon implements ITerminalAddon {
|
||||
/**
|
||||
* Activates the addon
|
||||
* @param terminal The terminal the addon is being loaded in.
|
||||
*/
|
||||
public activate(terminal: Terminal): void;
|
||||
|
||||
/**
|
||||
* Disposes the addon.
|
||||
*/
|
||||
public dispose(): void;
|
||||
|
||||
/**
|
||||
* Search forwards for the next result that matches the search term and
|
||||
* options.
|
||||
* @param term The search term.
|
||||
* @param searchOptions The options for the search.
|
||||
*/
|
||||
public findNext(term: string, searchOptions?: ISearchOptions): boolean;
|
||||
|
||||
/**
|
||||
* Search backwards for the previous result that matches the search term and
|
||||
* options.
|
||||
* @param term The search term.
|
||||
* @param searchOptions The options for the search.
|
||||
*/
|
||||
public findPrevious(term: string, searchOptions?: ISearchOptions): boolean;
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
|
||||
const addonName = 'SearchAddon';
|
||||
const mainFile = 'xterm-addon-search.js';
|
||||
|
||||
module.exports = {
|
||||
entry: `./out/${addonName}.js`,
|
||||
devtool: 'source-map',
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
use: ["source-map-loader"],
|
||||
enforce: "pre",
|
||||
exclude: /node_modules/
|
||||
}
|
||||
]
|
||||
},
|
||||
output: {
|
||||
filename: mainFile,
|
||||
path: path.resolve('./lib'),
|
||||
library: addonName,
|
||||
libraryTarget: 'umd'
|
||||
},
|
||||
mode: 'production'
|
||||
};
|
||||
@@ -1,3 +0,0 @@
|
||||
lib
|
||||
node_modules
|
||||
out-benchmark
|
||||
@@ -1,5 +0,0 @@
|
||||
**/*.api.js
|
||||
**/*.api.ts
|
||||
tsconfig.json
|
||||
.yarnrc
|
||||
webpack.config.js
|
||||
@@ -1,42 +0,0 @@
|
||||
## xterm-addon-serialize
|
||||
|
||||
An addon for [xterm.js](https://github.com/xtermjs/xterm.js) that enables xterm.js to serialize a terminal framebuffer into string or html. This addon requires xterm.js v4+.
|
||||
|
||||
⚠️ This is an experimental addon that is still under construction ⚠️
|
||||
|
||||
### Install
|
||||
|
||||
```bash
|
||||
npm install --save xterm-addon-serialize
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
```ts
|
||||
import { Terminal } from "xterm";
|
||||
import { SerializeAddon } from "xterm-addon-serialize";
|
||||
|
||||
const terminal = new Terminal();
|
||||
const serializeAddon = new SerializeAddon();
|
||||
terminal.loadAddon(serializeAddon);
|
||||
|
||||
terminal.write("something...", () => {
|
||||
console.log(serializeAddon.serialize());
|
||||
});
|
||||
```
|
||||
|
||||
See the full [API](https://github.com/xtermjs/xterm.js/blob/master/addons/xterm-addon-serialize/typings/xterm-addon-serialize.d.ts) for more advanced usage.
|
||||
|
||||
### Benchmark
|
||||
|
||||
⚠️ Ensure you have `lolcat`, `hexdump` programs installed in your computer
|
||||
|
||||
```shell
|
||||
$ git clone https://github.com/xtermjs/xterm.js.git
|
||||
$ cd xterm.js
|
||||
$ yarn
|
||||
$ cd addons/xterm-addon-serialize
|
||||
$ yarn benchmark && yarn benchmark-baseline
|
||||
$ # change some code in `xterm-addon-serialize`
|
||||
$ yarn benchmark-eval
|
||||
```
|
||||
@@ -1,65 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { perfContext, before, ThroughputRuntimeCase } from 'xterm-benchmark';
|
||||
|
||||
import { spawn } from 'node-pty';
|
||||
import { Utf8ToUtf32, stringFromCodePoint } from 'common/input/TextDecoder';
|
||||
import { Terminal } from 'public/Terminal';
|
||||
import { SerializeAddon } from 'SerializeAddon';
|
||||
|
||||
class TestTerminal extends Terminal {
|
||||
writeSync(data: string): void {
|
||||
(this as any)._core.writeSync(data);
|
||||
}
|
||||
}
|
||||
|
||||
perfContext('Terminal: sh -c "dd if=/dev/random count=40 bs=1k | hexdump | lolcat -f"', () => {
|
||||
let content = '';
|
||||
let contentUtf8: Uint8Array;
|
||||
|
||||
before(async () => {
|
||||
const p = spawn('sh', ['-c', 'dd if=/dev/random count=40 bs=1k | hexdump | lolcat -f'], {
|
||||
name: 'xterm-256color',
|
||||
cols: 80,
|
||||
rows: 25,
|
||||
cwd: process.env.HOME,
|
||||
env: process.env,
|
||||
encoding: (null as unknown as string) // needs to be fixed in node-pty
|
||||
});
|
||||
const chunks: Buffer[] = [];
|
||||
let length = 0;
|
||||
p.on('data', data => {
|
||||
chunks.push(data as unknown as Buffer);
|
||||
length += data.length;
|
||||
});
|
||||
await new Promise(resolve => p.on('exit', () => resolve()));
|
||||
contentUtf8 = Buffer.concat(chunks, length);
|
||||
// translate to content string
|
||||
const buffer = new Uint32Array(contentUtf8.length);
|
||||
const decoder = new Utf8ToUtf32();
|
||||
const codepoints = decoder.decode(contentUtf8, buffer);
|
||||
for (let i = 0; i < codepoints; ++i) {
|
||||
content += stringFromCodePoint(buffer[i]);
|
||||
// peek into content to force flat repr in v8
|
||||
if (!(i % 10000000)) {
|
||||
content[i];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
perfContext('serialize', () => {
|
||||
let terminal: TestTerminal;
|
||||
const serializeAddon = new SerializeAddon();
|
||||
before(() => {
|
||||
terminal = new TestTerminal({ cols: 80, rows: 25, scrollback: 5000 });
|
||||
serializeAddon.activate(terminal);
|
||||
terminal.writeSync(content);
|
||||
});
|
||||
new ThroughputRuntimeCase('', () => {
|
||||
return { payloadSize: serializeAddon.serialize().length };
|
||||
}, { fork: false }).showAverageThroughput();
|
||||
});
|
||||
});
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"APP_PATH": ".benchmark",
|
||||
"evalConfig": {
|
||||
"tolerance": {
|
||||
"*": [0.75, 1.5],
|
||||
"*.dev": [0.01, 1.5],
|
||||
"*.cv": [0.01, 1.5],
|
||||
"EscapeSequenceParser.benchmark.js.*.averageThroughput.mean": [0.9, 5]
|
||||
},
|
||||
"skip": [
|
||||
"*.median",
|
||||
"*.runs",
|
||||
"*.dev",
|
||||
"*.cv",
|
||||
"EscapeSequenceParser.benchmark.js.*.averageRuntime",
|
||||
"Terminal.benchmark.js.*.averageRuntime"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["dom", "es6"],
|
||||
"outDir": "../out-benchmark",
|
||||
"types": ["../../../node_modules/@types/node"],
|
||||
"moduleResolution": "node",
|
||||
"strict": false,
|
||||
"target": "es2015",
|
||||
"module": "commonjs",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"common/*": ["../../../src/common/*"],
|
||||
"browser/*": ["../../../src/browser/*"],
|
||||
"public/*": ["../../../src/public/*"],
|
||||
"Terminal": ["../../../src/Terminal"],
|
||||
"SerializeAddon": ["../src/SerializeAddon"]
|
||||
}
|
||||
},
|
||||
"include": ["../**/*", "../../../typings/xterm.d.ts", "../../../out/**/*"],
|
||||
"exclude": ["../../../**/*test.ts", "../../**/*api.ts"],
|
||||
"references": [
|
||||
{ "path": "../../../src/common" },
|
||||
{ "path": "../../../src/browser" }
|
||||
]
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"name": "xterm-addon-serialize",
|
||||
"version": "0.2.0",
|
||||
"author": {
|
||||
"name": "The xterm.js authors",
|
||||
"url": "https://xtermjs.org/"
|
||||
},
|
||||
"main": "lib/xterm-addon-serialize.js",
|
||||
"types": "typings/xterm-addon-serialize.d.ts",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build": "../../node_modules/.bin/tsc -p src",
|
||||
"prepackage": "npm run build",
|
||||
"package": "../../node_modules/.bin/webpack",
|
||||
"prepublishOnly": "npm run package",
|
||||
"benchmark": "NODE_PATH=../../out:./out:./out-benchmark/ ../../node_modules/.bin/xterm-benchmark -r 5 -c benchmark/benchmark.json",
|
||||
"benchmark-baseline": "NODE_PATH=../../out:./out:./out-benchmark/ ../../node_modules/.bin/xterm-benchmark -r 5 -c benchmark/benchmark.json --baseline out-benchmark/addons/xterm-addon-serialize/benchmark/*benchmark.js",
|
||||
"benchmark-eval": "NODE_PATH=../../out:./out:./out-benchmark/ ../../node_modules/.bin/xterm-benchmark -r 5 -c benchmark/benchmark.json --eval out-benchmark/addons/xterm-addon-serialize/benchmark/*benchmark.js"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"xterm": "^4.0.0"
|
||||
}
|
||||
}
|
||||
@@ -1,340 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { assert } from 'chai';
|
||||
import { openTerminal, writeSync, getBrowserType } from '../../../out-test/api/TestUtils';
|
||||
import { Browser, Page } from 'playwright-core';
|
||||
|
||||
const APP = 'http://127.0.0.1:3000/test';
|
||||
|
||||
let browser: Browser;
|
||||
let page: Page;
|
||||
const width = 800;
|
||||
const height = 600;
|
||||
|
||||
describe('SerializeAddon', () => {
|
||||
before(async function(): Promise<any> {
|
||||
const browserType = getBrowserType();
|
||||
browser = await browserType.launch({ dumpio: true,
|
||||
headless: process.argv.indexOf('--headless') !== -1
|
||||
});
|
||||
page = await (await browser.newContext()).newPage();
|
||||
await page.setViewportSize({ width, height });
|
||||
await page.goto(APP);
|
||||
await openTerminal(page, { rows: 10, cols: 10, rendererType: 'dom' });
|
||||
await page.evaluate(`
|
||||
window.serializeAddon = new SerializeAddon();
|
||||
window.term.loadAddon(window.serializeAddon);
|
||||
`);
|
||||
});
|
||||
|
||||
after(async () => await browser.close());
|
||||
beforeEach(async () => await page.evaluate(`window.term.reset()`));
|
||||
|
||||
it('empty content', async function(): Promise<any> {
|
||||
const rows = 10;
|
||||
const cols = 10;
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize();`), '');
|
||||
});
|
||||
|
||||
it('trim last empty lines', async function(): Promise<any> {
|
||||
const cols = 10;
|
||||
const lines = [
|
||||
'',
|
||||
'',
|
||||
digitsString(cols),
|
||||
digitsString(cols),
|
||||
'',
|
||||
'',
|
||||
digitsString(cols),
|
||||
digitsString(cols),
|
||||
'',
|
||||
'',
|
||||
''
|
||||
];
|
||||
await writeSync(page, lines.join('\\r\\n'));
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.slice(0, 8).join('\r\n'));
|
||||
});
|
||||
|
||||
it('digits content', async function(): Promise<any> {
|
||||
const rows = 10;
|
||||
const cols = 10;
|
||||
const digitsLine = digitsString(cols);
|
||||
const lines = newArray<string>(digitsLine, rows);
|
||||
await writeSync(page, lines.join('\\r\\n'));
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
|
||||
});
|
||||
|
||||
it('serialize half rows of content', async function(): Promise<any> {
|
||||
const rows = 10;
|
||||
const halfRows = rows >> 1;
|
||||
const cols = 10;
|
||||
const lines = newArray<string>((index: number) => digitsString(cols, index), rows);
|
||||
await writeSync(page, lines.join('\\r\\n'));
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize(${halfRows});`), lines.slice(halfRows, 2 * halfRows).join('\r\n'));
|
||||
});
|
||||
|
||||
it('serialize 0 rows of content', async function(): Promise<any> {
|
||||
const rows = 10;
|
||||
const cols = 10;
|
||||
const lines = newArray<string>((index: number) => digitsString(cols, index), rows);
|
||||
await writeSync(page, lines.join('\\r\\n'));
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize(0);`), '');
|
||||
});
|
||||
|
||||
it('serialize all rows of content with color16', async function(): Promise<any> {
|
||||
const cols = 10;
|
||||
const color16 = [
|
||||
30, 31, 32, 33, 34, 35, 36, 37, // Set foreground color
|
||||
90, 91, 92, 93, 94, 95, 96, 97,
|
||||
40, 41, 42, 43, 44, 45, 46, 47, // Set background color
|
||||
100, 101, 103, 104, 105, 106, 107
|
||||
];
|
||||
const rows = color16.length;
|
||||
const lines = newArray<string>(
|
||||
(index: number) => digitsString(cols, index, `\x1b[${color16[index % color16.length]}m`),
|
||||
rows
|
||||
);
|
||||
await writeSync(page, lines.join('\\r\\n'));
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
|
||||
});
|
||||
|
||||
it('serialize all rows of content with fg/bg flags', async function(): Promise<any> {
|
||||
const cols = 10;
|
||||
const line = '+'.repeat(cols);
|
||||
const lines: string[] = [
|
||||
mkSGR(FG_P16_GREEN) + line, // Workaround: If we clear all flags a the end, serialize will use \x1b[0m to clear instead of the sepcific disable sequence
|
||||
mkSGR(INVERSE) + line,
|
||||
mkSGR(BOLD) + line,
|
||||
mkSGR(UNDERLINED) + line,
|
||||
mkSGR(BLINK) + line,
|
||||
mkSGR(INVISIBLE) + line,
|
||||
mkSGR(NO_INVERSE) + line,
|
||||
mkSGR(NO_BOLD) + line,
|
||||
mkSGR(NO_UNDERLINED) + line,
|
||||
mkSGR(NO_BLINK) + line,
|
||||
mkSGR(NO_INVISIBLE) + line
|
||||
];
|
||||
const rows = lines.length;
|
||||
await writeSync(page, lines.join('\\r\\n'));
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
|
||||
});
|
||||
|
||||
it('serialize all rows of content with color256', async function(): Promise<any> {
|
||||
const rows = 32;
|
||||
const cols = 10;
|
||||
const lines = newArray<string>(
|
||||
(index: number) => digitsString(cols, index, `\x1b[38;5;${16 + index}m`),
|
||||
rows
|
||||
);
|
||||
await writeSync(page, lines.join('\\r\\n'));
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
|
||||
});
|
||||
|
||||
it('serialize all rows of content with color16 and style separately', async function(): Promise<any> {
|
||||
const cols = 10;
|
||||
const line = '+'.repeat(cols);
|
||||
const lines: string[] = [
|
||||
mkSGR(FG_P16_RED) + line, // fg Red,
|
||||
mkSGR(UNDERLINED) + line, // fg Red, Underlined
|
||||
mkSGR(FG_P16_GREEN) + line, // fg Green, Underlined
|
||||
mkSGR(INVERSE) + line, // fg Green, Underlined, Inverse
|
||||
mkSGR(NO_INVERSE) + line, // fg Green, Underlined
|
||||
mkSGR(INVERSE) + line, // fg Green, Underlined, Inverse
|
||||
mkSGR(BG_P16_YELLOW) + line, // fg Green, bg Yellow, Underlined, Inverse
|
||||
mkSGR(FG_RESET) + line, // bg Yellow, Underlined, Inverse
|
||||
mkSGR(BG_RESET) + line, // Underlined, Inverse
|
||||
mkSGR(NORMAL) + line // Back to normal
|
||||
];
|
||||
await writeSync(page, lines.join('\\r\\n'));
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
|
||||
});
|
||||
|
||||
it('serialize all rows of content with color16 and style together', async function(): Promise<any> {
|
||||
const cols = 10;
|
||||
const line = '+'.repeat(cols);
|
||||
const lines: string[] = [
|
||||
mkSGR(FG_P16_RED) + line, // fg Red
|
||||
mkSGR(FG_P16_GREEN, BG_P16_YELLOW) + line, // fg Green, bg Yellow
|
||||
mkSGR(UNDERLINED, ITALIC) + line, // fg Green, bg Yellow, Underlined, Italic
|
||||
mkSGR(NO_UNDERLINED, NO_ITALIC) + line, // fg Green, bg Yellow
|
||||
mkSGR(FG_RESET, ITALIC) + line, // bg Yellow, Italic
|
||||
mkSGR(BG_RESET) + line, // Italic
|
||||
mkSGR(NORMAL) + line, // Back to normal
|
||||
mkSGR(FG_P16_RED) + line, // fg Red
|
||||
mkSGR(FG_P16_GREEN, BG_P16_YELLOW) + line, // fg Green, bg Yellow
|
||||
mkSGR(UNDERLINED, ITALIC) + line, // fg Green, bg Yellow, Underlined, Italic
|
||||
mkSGR(NO_UNDERLINED, NO_ITALIC) + line, // fg Green, bg Yellow
|
||||
mkSGR(FG_RESET, ITALIC) + line, // bg Yellow, Italic
|
||||
mkSGR(BG_RESET) + line // Italic
|
||||
];
|
||||
await writeSync(page, lines.join('\\r\\n'));
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
|
||||
});
|
||||
|
||||
it('serialize all rows of content with color256 and style separately', async function(): Promise<any> {
|
||||
const cols = 10;
|
||||
const line = '+'.repeat(cols);
|
||||
const lines: string[] = [
|
||||
mkSGR(FG_P256_RED) + line, // fg Red 256,
|
||||
mkSGR(UNDERLINED) + line, // fg Red 256, Underlined
|
||||
mkSGR(FG_P256_GREEN) + line, // fg Green 256, Underlined
|
||||
mkSGR(INVERSE) + line, // fg Green 256, Underlined, Inverse
|
||||
mkSGR(NO_INVERSE) + line, // fg Green 256, Underlined
|
||||
mkSGR(INVERSE) + line, // fg Green 256, Underlined, Inverse
|
||||
mkSGR(BG_P256_YELLOW) + line, // fg Green 256, bg Yellow 256, Underlined, Inverse
|
||||
mkSGR(FG_RESET) + line, // bg Yellow 256, Underlined, Inverse
|
||||
mkSGR(BG_RESET) + line, // Underlined, Inverse
|
||||
mkSGR(NORMAL) + line // Back to normal
|
||||
];
|
||||
await writeSync(page, lines.join('\\r\\n'));
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
|
||||
});
|
||||
|
||||
it('serialize all rows of content with color256 and style together', async function(): Promise<any> {
|
||||
const cols = 10;
|
||||
const line = '+'.repeat(cols);
|
||||
const lines: string[] = [
|
||||
mkSGR(FG_P256_RED) + line, // fg Red 256
|
||||
mkSGR(FG_P256_GREEN, BG_P256_YELLOW) + line, // fg Green 256, bg Yellow 256
|
||||
mkSGR(UNDERLINED, ITALIC) + line, // fg Green 256, bg Yellow 256, Underlined, Italic
|
||||
mkSGR(NO_UNDERLINED, NO_ITALIC) + line, // fg Green 256, bg Yellow 256
|
||||
mkSGR(FG_RESET, ITALIC) + line, // bg Yellow 256, Italic
|
||||
mkSGR(BG_RESET) + line, // Italic
|
||||
mkSGR(NORMAL) + line, // Back to normal
|
||||
mkSGR(FG_P256_RED) + line, // fg Red 256
|
||||
mkSGR(FG_P256_GREEN, BG_P256_YELLOW) + line, // fg Green 256, bg Yellow 256
|
||||
mkSGR(UNDERLINED, ITALIC) + line, // fg Green 256, bg Yellow 256, Underlined, Italic
|
||||
mkSGR(NO_UNDERLINED, NO_ITALIC) + line, // fg Green 256, bg Yellow 256
|
||||
mkSGR(FG_RESET, ITALIC) + line, // bg Yellow 256, Italic
|
||||
mkSGR(BG_RESET) + line // Italic
|
||||
];
|
||||
await writeSync(page, lines.join('\\r\\n'));
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
|
||||
});
|
||||
|
||||
it('serialize all rows of content with colorRGB and style separately', async function(): Promise<any> {
|
||||
const cols = 10;
|
||||
const line = '+'.repeat(cols);
|
||||
const lines: string[] = [
|
||||
mkSGR(FG_RGB_RED) + line, // fg Red RGB,
|
||||
mkSGR(UNDERLINED) + line, // fg Red RGB, Underlined
|
||||
mkSGR(FG_RGB_GREEN) + line, // fg Green RGB, Underlined
|
||||
mkSGR(INVERSE) + line, // fg Green RGB, Underlined, Inverse
|
||||
mkSGR(NO_INVERSE) + line, // fg Green RGB, Underlined
|
||||
mkSGR(INVERSE) + line, // fg Green RGB, Underlined, Inverse
|
||||
mkSGR(BG_RGB_YELLOW) + line, // fg Green RGB, bg Yellow RGB, Underlined, Inverse
|
||||
mkSGR(FG_RESET) + line, // bg Yellow RGB, Underlined, Inverse
|
||||
mkSGR(BG_RESET) + line, // Underlined, Inverse
|
||||
mkSGR(NORMAL) + line // Back to normal
|
||||
];
|
||||
await writeSync(page, lines.join('\\r\\n'));
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
|
||||
});
|
||||
|
||||
it('serialize all rows of content with colorRGB and style together', async function(): Promise<any> {
|
||||
const cols = 10;
|
||||
const line = '+'.repeat(cols);
|
||||
const lines: string[] = [
|
||||
mkSGR(FG_RGB_RED) + line, // fg Red RGB
|
||||
mkSGR(FG_RGB_GREEN, BG_RGB_YELLOW) + line, // fg Green RGB, bg Yellow RGB
|
||||
mkSGR(UNDERLINED, ITALIC) + line, // fg Green RGB, bg Yellow RGB, Underlined, Italic
|
||||
mkSGR(NO_UNDERLINED, NO_ITALIC) + line, // fg Green RGB, bg Yellow RGB
|
||||
mkSGR(FG_RESET, ITALIC) + line, // bg Yellow RGB, Italic
|
||||
mkSGR(BG_RESET) + line, // Italic
|
||||
mkSGR(NORMAL) + line, // Back to normal
|
||||
mkSGR(FG_RGB_RED) + line, // fg Red RGB
|
||||
mkSGR(FG_RGB_GREEN, BG_RGB_YELLOW) + line, // fg Green RGB, bg Yellow RGB
|
||||
mkSGR(UNDERLINED, ITALIC) + line, // fg Green RGB, bg Yellow RGB, Underlined, Italic
|
||||
mkSGR(NO_UNDERLINED, NO_ITALIC) + line, // fg Green RGB, bg Yellow RGB
|
||||
mkSGR(FG_RESET, ITALIC) + line, // bg Yellow RGB, Italic
|
||||
mkSGR(BG_RESET) + line // Italic
|
||||
];
|
||||
await writeSync(page, lines.join('\\r\\n'));
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
|
||||
});
|
||||
|
||||
it('serialize tabs correctly', async () => {
|
||||
const lines = [
|
||||
'a\tb',
|
||||
'aa\tc',
|
||||
'aaa\td'
|
||||
];
|
||||
const expected = [
|
||||
'a\x1b[7Cb',
|
||||
'aa\x1b[6Cc',
|
||||
'aaa\x1b[5Cd'
|
||||
];
|
||||
await writeSync(page, lines.join('\\r\\n'));
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize();`), expected.join('\r\n'));
|
||||
});
|
||||
});
|
||||
|
||||
function newArray<T>(initial: T | ((index: number) => T), count: number): T[] {
|
||||
const array: T[] = new Array<T>(count);
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
if (typeof initial === 'function') {
|
||||
array[i] = (<(index: number) => T>initial)(i);
|
||||
} else {
|
||||
array[i] = <T>initial;
|
||||
}
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
function digitsString(length: number, from: number = 0, sgr: string = ''): string {
|
||||
let s = sgr;
|
||||
for (let i = 0; i < length; i++) {
|
||||
s += `${(from++) % 10}`;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
function mkSGR(...seq: string[]): string {
|
||||
return `\x1b[${seq.join(';')}m`;
|
||||
}
|
||||
|
||||
const NORMAL = '0';
|
||||
|
||||
const FG_P16_RED = '31';
|
||||
const FG_P16_GREEN = '32';
|
||||
const FG_P16_YELLOW = '33';
|
||||
const FG_P256_RED = '38;5;196';
|
||||
const FG_P256_GREEN = '38;5;46';
|
||||
const FG_P256_YELLOW = '38;5;226';
|
||||
const FG_RGB_RED = '38;2;255;0;0';
|
||||
const FG_RGB_GREEN = '38;2;0;255;0';
|
||||
const FG_RGB_YELLOW = '38;2;255;255;0';
|
||||
const FG_RESET = '39';
|
||||
|
||||
|
||||
const BG_P16_RED = '41';
|
||||
const BG_P16_GREEN = '42';
|
||||
const BG_P16_YELLOW = '43';
|
||||
const BG_P256_RED = '48;5;196';
|
||||
const BG_P256_GREEN = '48;5;46';
|
||||
const BG_P256_YELLOW = '48;5;226';
|
||||
const BG_RGB_RED = '48;2;255;0;0';
|
||||
const BG_RGB_GREEN = '48;2;0;255;0';
|
||||
const BG_RGB_YELLOW = '48;2;255;255;0';
|
||||
const BG_RESET = '49';
|
||||
|
||||
const INVERSE = '7';
|
||||
const BOLD = '1';
|
||||
const UNDERLINED = '4';
|
||||
const BLINK = '5';
|
||||
const INVISIBLE = '8';
|
||||
|
||||
const NO_INVERSE = '27';
|
||||
const NO_BOLD = '22';
|
||||
const NO_UNDERLINED = '24';
|
||||
const NO_BLINK = '25';
|
||||
const NO_INVISIBLE = '28';
|
||||
|
||||
const ITALIC = '3';
|
||||
const DIM = '2';
|
||||
|
||||
const NO_ITALIC = '23';
|
||||
const NO_DIM = '22';
|
||||
@@ -1,187 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*
|
||||
* (EXPERIMENTAL) This Addon is still under development
|
||||
*/
|
||||
|
||||
import { Terminal, ITerminalAddon, IBuffer, IBufferCell } from 'xterm';
|
||||
|
||||
function constrain(value: number, low: number, high: number): number {
|
||||
return Math.max(low, Math.min(value, high));
|
||||
}
|
||||
|
||||
// TODO: Refine this template class later
|
||||
abstract class BaseSerializeHandler {
|
||||
constructor(private _buffer: IBuffer) { }
|
||||
|
||||
serialize(startRow: number, endRow: number): string {
|
||||
// we need two of them to flip between old and new cell
|
||||
const cell1 = this._buffer.getNullCell();
|
||||
const cell2 = this._buffer.getNullCell();
|
||||
let oldCell = cell1;
|
||||
|
||||
this._beforeSerialize(endRow - startRow);
|
||||
|
||||
for (let row = startRow; row < endRow; row++) {
|
||||
const line = this._buffer.getLine(row);
|
||||
if (line) {
|
||||
for (let col = 0; col < line.length; col++) {
|
||||
const c = line.getCell(col, oldCell === cell1 ? cell2 : cell1);
|
||||
if (!c) {
|
||||
console.warn(`Can't get cell at row=${row}, col=${col}`);
|
||||
continue;
|
||||
}
|
||||
this._nextCell(c, oldCell, row, col);
|
||||
oldCell = c;
|
||||
}
|
||||
}
|
||||
this._rowEnd(row);
|
||||
}
|
||||
|
||||
this._afterSerialize();
|
||||
|
||||
return this._serializeString();
|
||||
}
|
||||
|
||||
protected _nextCell(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void { }
|
||||
protected _rowEnd(row: number): void { }
|
||||
protected _beforeSerialize(rows: number): void { }
|
||||
protected _afterSerialize(): void { }
|
||||
protected _serializeString(): string { return ''; }
|
||||
}
|
||||
|
||||
function equalFg(cell1: IBufferCell, cell2: IBufferCell): boolean {
|
||||
return cell1.getFgColorMode() === cell2.getFgColorMode()
|
||||
&& cell1.getFgColor() === cell2.getFgColor();
|
||||
}
|
||||
|
||||
function equalBg(cell1: IBufferCell, cell2: IBufferCell): boolean {
|
||||
return cell1.getBgColorMode() === cell2.getBgColorMode()
|
||||
&& cell1.getBgColor() === cell2.getBgColor();
|
||||
}
|
||||
|
||||
function equalFlags(cell1: IBufferCell, cell2: IBufferCell): boolean {
|
||||
return cell1.isInverse() === cell2.isInverse()
|
||||
&& cell1.isBold() === cell2.isBold()
|
||||
&& cell1.isUnderline() === cell2.isUnderline()
|
||||
&& cell1.isBlink() === cell2.isBlink()
|
||||
&& cell1.isInvisible() === cell2.isInvisible()
|
||||
&& cell1.isItalic() === cell2.isItalic()
|
||||
&& cell1.isDim() === cell2.isDim();
|
||||
}
|
||||
|
||||
class StringSerializeHandler extends BaseSerializeHandler {
|
||||
private _rowIndex: number = 0;
|
||||
private _allRows: string[] = new Array<string>();
|
||||
private _currentRow: string = '';
|
||||
private _nullCellCount: number = 0;
|
||||
|
||||
constructor(buffer: IBuffer) {
|
||||
super(buffer);
|
||||
}
|
||||
|
||||
protected _beforeSerialize(rows: number): void {
|
||||
this._allRows = new Array<string>(rows);
|
||||
}
|
||||
|
||||
protected _rowEnd(row: number): void {
|
||||
this._allRows[this._rowIndex++] = this._currentRow;
|
||||
this._currentRow = '';
|
||||
this._nullCellCount = 0;
|
||||
}
|
||||
|
||||
protected _nextCell(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void {
|
||||
const sgrSeq: number[] = [];
|
||||
const fgChanged = !equalFg(cell, oldCell);
|
||||
const bgChanged = !equalBg(cell, oldCell);
|
||||
const flagsChanged = !equalFlags(cell, oldCell);
|
||||
|
||||
if (fgChanged || bgChanged || flagsChanged) {
|
||||
if (cell.isAttributeDefault()) {
|
||||
this._currentRow += '\x1b[0m';
|
||||
} else {
|
||||
if (fgChanged) {
|
||||
const color = cell.getFgColor();
|
||||
if (cell.isFgRGB()) { sgrSeq.push(38, 2, (color >>> 16) & 0xFF, (color >>> 8) & 0xFF, color & 0xFF); }
|
||||
else if (cell.isFgPalette()) {
|
||||
if (color >= 16) { sgrSeq.push(38, 5, color); }
|
||||
else { sgrSeq.push(color & 8 ? 90 + (color & 7) : 30 + (color & 7)); }
|
||||
}
|
||||
else { sgrSeq.push(39); }
|
||||
}
|
||||
if (bgChanged) {
|
||||
const color = cell.getBgColor();
|
||||
if (cell.isBgRGB()) { sgrSeq.push(48, 2, (color >>> 16) & 0xFF, (color >>> 8) & 0xFF, color & 0xFF); }
|
||||
else if (cell.isBgPalette()) {
|
||||
if (color >= 16) { sgrSeq.push(48, 5, color); }
|
||||
else { sgrSeq.push(color & 8 ? 100 + (color & 7) : 40 + (color & 7)); }
|
||||
}
|
||||
else { sgrSeq.push(49); }
|
||||
}
|
||||
if (flagsChanged) {
|
||||
if (cell.isInverse() !== oldCell.isInverse()) { sgrSeq.push(cell.isInverse() ? 7 : 27); }
|
||||
if (cell.isBold() !== oldCell.isBold()) { sgrSeq.push(cell.isBold() ? 1 : 22); }
|
||||
if (cell.isUnderline() !== oldCell.isUnderline()) { sgrSeq.push(cell.isUnderline() ? 4 : 24); }
|
||||
if (cell.isBlink() !== oldCell.isBlink()) { sgrSeq.push(cell.isBlink() ? 5 : 25); }
|
||||
if (cell.isInvisible() !== oldCell.isInvisible()) { sgrSeq.push(cell.isInvisible() ? 8 : 28); }
|
||||
if (cell.isItalic() !== oldCell.isItalic()) { sgrSeq.push(cell.isItalic() ? 3 : 23); }
|
||||
if (cell.isDim() !== oldCell.isDim()) { sgrSeq.push(cell.isDim() ? 2 : 22); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sgrSeq.length) {
|
||||
this._currentRow += `\x1b[${sgrSeq.join(';')}m`;
|
||||
}
|
||||
|
||||
// Count number of null cells encountered after the last non-null cell and move the cursor
|
||||
// if a non-null cell is found (eg. \t or cursor move)
|
||||
if (cell.getChars() === '') {
|
||||
this._nullCellCount++;
|
||||
} else if (this._nullCellCount > 0) {
|
||||
this._currentRow += `\x1b[${this._nullCellCount}C`;
|
||||
this._nullCellCount = 0;
|
||||
}
|
||||
|
||||
this._currentRow += cell.getChars();
|
||||
}
|
||||
|
||||
protected _serializeString(): string {
|
||||
let rowEnd = this._allRows.length;
|
||||
for (; rowEnd > 0; rowEnd--) {
|
||||
if (this._allRows[rowEnd - 1]) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return this._allRows.slice(0, rowEnd).join('\r\n');
|
||||
}
|
||||
}
|
||||
|
||||
export class SerializeAddon implements ITerminalAddon {
|
||||
private _terminal: Terminal | undefined;
|
||||
|
||||
constructor() { }
|
||||
|
||||
public activate(terminal: Terminal): void {
|
||||
this._terminal = terminal;
|
||||
}
|
||||
|
||||
public serialize(rows?: number): string {
|
||||
// TODO: Add re-position cursor support
|
||||
// TODO: Add word wrap mode support
|
||||
// TODO: Add combinedData support
|
||||
if (!this._terminal) {
|
||||
throw new Error('Cannot use addon until it has been loaded');
|
||||
}
|
||||
|
||||
const maxRows = this._terminal.buffer.active.length;
|
||||
const handler = new StringSerializeHandler(this._terminal.buffer.active);
|
||||
|
||||
rows = (rows === undefined) ? maxRows : constrain(rows, 0, maxRows);
|
||||
|
||||
return handler.serialize(maxRows - rows, maxRows);
|
||||
}
|
||||
|
||||
public dispose(): void { }
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es5",
|
||||
"lib": [
|
||||
"dom",
|
||||
"es2015"
|
||||
],
|
||||
"rootDir": ".",
|
||||
"outDir": "../out",
|
||||
"sourceMap": true,
|
||||
"removeComments": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"common/*": [
|
||||
"../../../src/common/*"
|
||||
]
|
||||
},
|
||||
"strict": true,
|
||||
"types": [
|
||||
"../../../node_modules/@types/mocha",
|
||||
"../../../out-test/api/TestUtils"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"./**/*",
|
||||
"../../../typings/xterm.d.ts"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../../src/common"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
|
||||
import { Terminal, ITerminalAddon } from 'xterm';
|
||||
|
||||
declare module 'xterm-addon-serialize' {
|
||||
/**
|
||||
* An xterm.js addon that enables web links.
|
||||
*/
|
||||
export class SerializeAddon implements ITerminalAddon {
|
||||
|
||||
constructor();
|
||||
|
||||
/**
|
||||
* Activates the addon
|
||||
* @param terminal The terminal the addon is being loaded in.
|
||||
*/
|
||||
public activate(terminal: Terminal): void;
|
||||
|
||||
/**
|
||||
* Serializes terminal rows into a string that can be written back to the terminal
|
||||
* to restore the state. The cursor will also be positioned to the correct cell.
|
||||
* When restoring a terminal it is best to do before `Terminal.open` is called
|
||||
* to avoid wasting CPU cycles rendering incomplete frames.
|
||||
* @param rows The number of rows to serialize, starting from the bottom of the
|
||||
* terminal. This defaults to the number of rows in the viewport.
|
||||
*/
|
||||
public serialize(rows?: number): string;
|
||||
|
||||
/**
|
||||
* Disposes the addon.
|
||||
*/
|
||||
public dispose(): void;
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
|
||||
const addonName = 'SerializeAddon';
|
||||
const mainFile = 'xterm-addon-serialize.js';
|
||||
|
||||
module.exports = {
|
||||
entry: `./out/${addonName}.js`,
|
||||
devtool: 'source-map',
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
use: ["source-map-loader"],
|
||||
enforce: "pre",
|
||||
exclude: /node_modules/
|
||||
}
|
||||
]
|
||||
},
|
||||
output: {
|
||||
filename: mainFile,
|
||||
path: path.resolve('./lib'),
|
||||
library: addonName,
|
||||
libraryTarget: 'umd'
|
||||
},
|
||||
mode: 'production'
|
||||
};
|
||||
@@ -1,2 +0,0 @@
|
||||
lib
|
||||
node_modules
|
||||
@@ -1,5 +0,0 @@
|
||||
**/*.api.js
|
||||
**/*.api.ts
|
||||
tsconfig.json
|
||||
.yarnrc
|
||||
webpack.config.js
|
||||
@@ -1,19 +0,0 @@
|
||||
Copyright (c) 2019, The xterm.js authors (https://github.com/xtermjs/xterm.js)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
@@ -1,23 +0,0 @@
|
||||
## xterm-addon-unicode11
|
||||
|
||||
An addon providing Unicode version 11 rules for xterm.js.
|
||||
|
||||
### Install
|
||||
|
||||
```bash
|
||||
npm install --save xterm-addon-unicode11
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
```ts
|
||||
import { Terminal } from 'xterm';
|
||||
import { Unicode11Addon } from 'xterm-addon-unicode11';
|
||||
|
||||
const terminal = new Terminal();
|
||||
const unicode11Addon = new Unicode11Addon();
|
||||
terminal.loadAddon(unicode11Addon);
|
||||
|
||||
// activate the new version
|
||||
terminal.unicode.activeVersion = '11';
|
||||
```
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"name": "xterm-addon-unicode11",
|
||||
"version": "0.1.1",
|
||||
"author": {
|
||||
"name": "The xterm.js authors",
|
||||
"url": "https://xtermjs.org/"
|
||||
},
|
||||
"main": "lib/xterm-addon-unicode11.js",
|
||||
"types": "typings/xterm-addon-unicode11.d.ts",
|
||||
"repository": "https://github.com/xtermjs/xterm.js",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build": "../../node_modules/.bin/tsc -p src",
|
||||
"prepackage": "npm run build",
|
||||
"package": "../../node_modules/.bin/webpack",
|
||||
"prepublishOnly": "npm run package"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"xterm": "^4.0.0"
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { assert } from 'chai';
|
||||
import { openTerminal, getBrowserType } from '../../../out-test/api/TestUtils';
|
||||
import { Browser, Page } from 'playwright-core';
|
||||
|
||||
const APP = 'http://127.0.0.1:3000/test';
|
||||
|
||||
let browser: Browser;
|
||||
let page: Page;
|
||||
const width = 800;
|
||||
const height = 600;
|
||||
|
||||
describe('Unicode11Addon', () => {
|
||||
before(async function(): Promise<any> {
|
||||
const browserType = getBrowserType();
|
||||
browser = await browserType.launch({ dumpio: true,
|
||||
headless: process.argv.indexOf('--headless') !== -1
|
||||
});
|
||||
page = await (await browser.newContext()).newPage();
|
||||
await page.setViewportSize({ width, height });
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await browser.close();
|
||||
});
|
||||
|
||||
beforeEach(async function(): Promise<any> {
|
||||
await page.goto(APP);
|
||||
await openTerminal(page);
|
||||
});
|
||||
|
||||
it('wcwidth V11 emoji test', async () => {
|
||||
await page.evaluate(`
|
||||
window.unicode11 = new Unicode11Addon();
|
||||
window.term.loadAddon(window.unicode11);
|
||||
`);
|
||||
// should have loaded '11'
|
||||
assert.deepEqual(await page.evaluate(`window.term.unicode.versions`), ['6', '11']);
|
||||
// switch should not throw
|
||||
await page.evaluate(`window.term.unicode.activeVersion = '11';`);
|
||||
assert.deepEqual(await page.evaluate(`window.term.unicode.activeVersion`), '11');
|
||||
// v6: 10, V11: 20
|
||||
assert.deepEqual(await page.evaluate(`window.term._core.unicodeService.getStringCellWidth('🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣')`), 20);
|
||||
});
|
||||
});
|
||||
@@ -1,16 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*
|
||||
* UnicodeVersionProvider for V11.
|
||||
*/
|
||||
|
||||
import { Terminal, ITerminalAddon } from 'xterm';
|
||||
import { UnicodeV11 } from './UnicodeV11';
|
||||
|
||||
export class Unicode11Addon implements ITerminalAddon {
|
||||
public activate(terminal: Terminal): void {
|
||||
terminal.unicode.register(new UnicodeV11());
|
||||
}
|
||||
public dispose(): void { }
|
||||
}
|
||||
@@ -1,222 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { IUnicodeVersionProvider } from 'xterm';
|
||||
import { fill } from 'common/TypedArrayUtils';
|
||||
|
||||
type CharWidth = 0 | 1 | 2;
|
||||
|
||||
const BMP_COMBINING = [
|
||||
[0x0300, 0x036F], [0x0483, 0x0489], [0x0591, 0x05BD],
|
||||
[0x05BF, 0x05BF], [0x05C1, 0x05C2], [0x05C4, 0x05C5],
|
||||
[0x05C7, 0x05C7], [0x0600, 0x0605], [0x0610, 0x061A],
|
||||
[0x061C, 0x061C], [0x064B, 0x065F], [0x0670, 0x0670],
|
||||
[0x06D6, 0x06DD], [0x06DF, 0x06E4], [0x06E7, 0x06E8],
|
||||
[0x06EA, 0x06ED], [0x070F, 0x070F], [0x0711, 0x0711],
|
||||
[0x0730, 0x074A], [0x07A6, 0x07B0], [0x07EB, 0x07F3],
|
||||
[0x07FD, 0x07FD], [0x0816, 0x0819], [0x081B, 0x0823],
|
||||
[0x0825, 0x0827], [0x0829, 0x082D], [0x0859, 0x085B],
|
||||
[0x08D3, 0x0902], [0x093A, 0x093A], [0x093C, 0x093C],
|
||||
[0x0941, 0x0948], [0x094D, 0x094D], [0x0951, 0x0957],
|
||||
[0x0962, 0x0963], [0x0981, 0x0981], [0x09BC, 0x09BC],
|
||||
[0x09C1, 0x09C4], [0x09CD, 0x09CD], [0x09E2, 0x09E3],
|
||||
[0x09FE, 0x09FE], [0x0A01, 0x0A02], [0x0A3C, 0x0A3C],
|
||||
[0x0A41, 0x0A42], [0x0A47, 0x0A48], [0x0A4B, 0x0A4D],
|
||||
[0x0A51, 0x0A51], [0x0A70, 0x0A71], [0x0A75, 0x0A75],
|
||||
[0x0A81, 0x0A82], [0x0ABC, 0x0ABC], [0x0AC1, 0x0AC5],
|
||||
[0x0AC7, 0x0AC8], [0x0ACD, 0x0ACD], [0x0AE2, 0x0AE3],
|
||||
[0x0AFA, 0x0AFF], [0x0B01, 0x0B01], [0x0B3C, 0x0B3C],
|
||||
[0x0B3F, 0x0B3F], [0x0B41, 0x0B44], [0x0B4D, 0x0B4D],
|
||||
[0x0B56, 0x0B56], [0x0B62, 0x0B63], [0x0B82, 0x0B82],
|
||||
[0x0BC0, 0x0BC0], [0x0BCD, 0x0BCD], [0x0C00, 0x0C00],
|
||||
[0x0C04, 0x0C04], [0x0C3E, 0x0C40], [0x0C46, 0x0C48],
|
||||
[0x0C4A, 0x0C4D], [0x0C55, 0x0C56], [0x0C62, 0x0C63],
|
||||
[0x0C81, 0x0C81], [0x0CBC, 0x0CBC], [0x0CBF, 0x0CBF],
|
||||
[0x0CC6, 0x0CC6], [0x0CCC, 0x0CCD], [0x0CE2, 0x0CE3],
|
||||
[0x0D00, 0x0D01], [0x0D3B, 0x0D3C], [0x0D41, 0x0D44],
|
||||
[0x0D4D, 0x0D4D], [0x0D62, 0x0D63], [0x0DCA, 0x0DCA],
|
||||
[0x0DD2, 0x0DD4], [0x0DD6, 0x0DD6], [0x0E31, 0x0E31],
|
||||
[0x0E34, 0x0E3A], [0x0E47, 0x0E4E], [0x0EB1, 0x0EB1],
|
||||
[0x0EB4, 0x0EBC], [0x0EC8, 0x0ECD], [0x0F18, 0x0F19],
|
||||
[0x0F35, 0x0F35], [0x0F37, 0x0F37], [0x0F39, 0x0F39],
|
||||
[0x0F71, 0x0F7E], [0x0F80, 0x0F84], [0x0F86, 0x0F87],
|
||||
[0x0F8D, 0x0F97], [0x0F99, 0x0FBC], [0x0FC6, 0x0FC6],
|
||||
[0x102D, 0x1030], [0x1032, 0x1037], [0x1039, 0x103A],
|
||||
[0x103D, 0x103E], [0x1058, 0x1059], [0x105E, 0x1060],
|
||||
[0x1071, 0x1074], [0x1082, 0x1082], [0x1085, 0x1086],
|
||||
[0x108D, 0x108D], [0x109D, 0x109D], [0x1160, 0x11FF],
|
||||
[0x135D, 0x135F], [0x1712, 0x1714], [0x1732, 0x1734],
|
||||
[0x1752, 0x1753], [0x1772, 0x1773], [0x17B4, 0x17B5],
|
||||
[0x17B7, 0x17BD], [0x17C6, 0x17C6], [0x17C9, 0x17D3],
|
||||
[0x17DD, 0x17DD], [0x180B, 0x180E], [0x1885, 0x1886],
|
||||
[0x18A9, 0x18A9], [0x1920, 0x1922], [0x1927, 0x1928],
|
||||
[0x1932, 0x1932], [0x1939, 0x193B], [0x1A17, 0x1A18],
|
||||
[0x1A1B, 0x1A1B], [0x1A56, 0x1A56], [0x1A58, 0x1A5E],
|
||||
[0x1A60, 0x1A60], [0x1A62, 0x1A62], [0x1A65, 0x1A6C],
|
||||
[0x1A73, 0x1A7C], [0x1A7F, 0x1A7F], [0x1AB0, 0x1ABE],
|
||||
[0x1B00, 0x1B03], [0x1B34, 0x1B34], [0x1B36, 0x1B3A],
|
||||
[0x1B3C, 0x1B3C], [0x1B42, 0x1B42], [0x1B6B, 0x1B73],
|
||||
[0x1B80, 0x1B81], [0x1BA2, 0x1BA5], [0x1BA8, 0x1BA9],
|
||||
[0x1BAB, 0x1BAD], [0x1BE6, 0x1BE6], [0x1BE8, 0x1BE9],
|
||||
[0x1BED, 0x1BED], [0x1BEF, 0x1BF1], [0x1C2C, 0x1C33],
|
||||
[0x1C36, 0x1C37], [0x1CD0, 0x1CD2], [0x1CD4, 0x1CE0],
|
||||
[0x1CE2, 0x1CE8], [0x1CED, 0x1CED], [0x1CF4, 0x1CF4],
|
||||
[0x1CF8, 0x1CF9], [0x1DC0, 0x1DF9], [0x1DFB, 0x1DFF],
|
||||
[0x200B, 0x200F], [0x202A, 0x202E], [0x2060, 0x2064],
|
||||
[0x2066, 0x206F], [0x20D0, 0x20F0], [0x2CEF, 0x2CF1],
|
||||
[0x2D7F, 0x2D7F], [0x2DE0, 0x2DFF], [0x302A, 0x302D],
|
||||
[0x3099, 0x309A], [0xA66F, 0xA672], [0xA674, 0xA67D],
|
||||
[0xA69E, 0xA69F], [0xA6F0, 0xA6F1], [0xA802, 0xA802],
|
||||
[0xA806, 0xA806], [0xA80B, 0xA80B], [0xA825, 0xA826],
|
||||
[0xA8C4, 0xA8C5], [0xA8E0, 0xA8F1], [0xA8FF, 0xA8FF],
|
||||
[0xA926, 0xA92D], [0xA947, 0xA951], [0xA980, 0xA982],
|
||||
[0xA9B3, 0xA9B3], [0xA9B6, 0xA9B9], [0xA9BC, 0xA9BD],
|
||||
[0xA9E5, 0xA9E5], [0xAA29, 0xAA2E], [0xAA31, 0xAA32],
|
||||
[0xAA35, 0xAA36], [0xAA43, 0xAA43], [0xAA4C, 0xAA4C],
|
||||
[0xAA7C, 0xAA7C], [0xAAB0, 0xAAB0], [0xAAB2, 0xAAB4],
|
||||
[0xAAB7, 0xAAB8], [0xAABE, 0xAABF], [0xAAC1, 0xAAC1],
|
||||
[0xAAEC, 0xAAED], [0xAAF6, 0xAAF6], [0xABE5, 0xABE5],
|
||||
[0xABE8, 0xABE8], [0xABED, 0xABED], [0xFB1E, 0xFB1E],
|
||||
[0xFE00, 0xFE0F], [0xFE20, 0xFE2F], [0xFEFF, 0xFEFF],
|
||||
[0xFFF9, 0xFFFB]
|
||||
];
|
||||
const HIGH_COMBINING = [
|
||||
[0x101FD, 0x101FD], [0x102E0, 0x102E0],
|
||||
[0x10376, 0x1037A], [0x10A01, 0x10A03], [0x10A05, 0x10A06],
|
||||
[0x10A0C, 0x10A0F], [0x10A38, 0x10A3A], [0x10A3F, 0x10A3F],
|
||||
[0x10AE5, 0x10AE6], [0x10D24, 0x10D27], [0x10F46, 0x10F50],
|
||||
[0x11001, 0x11001], [0x11038, 0x11046], [0x1107F, 0x11081],
|
||||
[0x110B3, 0x110B6], [0x110B9, 0x110BA], [0x110BD, 0x110BD],
|
||||
[0x110CD, 0x110CD], [0x11100, 0x11102], [0x11127, 0x1112B],
|
||||
[0x1112D, 0x11134], [0x11173, 0x11173], [0x11180, 0x11181],
|
||||
[0x111B6, 0x111BE], [0x111C9, 0x111CC], [0x1122F, 0x11231],
|
||||
[0x11234, 0x11234], [0x11236, 0x11237], [0x1123E, 0x1123E],
|
||||
[0x112DF, 0x112DF], [0x112E3, 0x112EA], [0x11300, 0x11301],
|
||||
[0x1133B, 0x1133C], [0x11340, 0x11340], [0x11366, 0x1136C],
|
||||
[0x11370, 0x11374], [0x11438, 0x1143F], [0x11442, 0x11444],
|
||||
[0x11446, 0x11446], [0x1145E, 0x1145E], [0x114B3, 0x114B8],
|
||||
[0x114BA, 0x114BA], [0x114BF, 0x114C0], [0x114C2, 0x114C3],
|
||||
[0x115B2, 0x115B5], [0x115BC, 0x115BD], [0x115BF, 0x115C0],
|
||||
[0x115DC, 0x115DD], [0x11633, 0x1163A], [0x1163D, 0x1163D],
|
||||
[0x1163F, 0x11640], [0x116AB, 0x116AB], [0x116AD, 0x116AD],
|
||||
[0x116B0, 0x116B5], [0x116B7, 0x116B7], [0x1171D, 0x1171F],
|
||||
[0x11722, 0x11725], [0x11727, 0x1172B], [0x1182F, 0x11837],
|
||||
[0x11839, 0x1183A], [0x119D4, 0x119D7], [0x119DA, 0x119DB],
|
||||
[0x119E0, 0x119E0], [0x11A01, 0x11A0A], [0x11A33, 0x11A38],
|
||||
[0x11A3B, 0x11A3E], [0x11A47, 0x11A47], [0x11A51, 0x11A56],
|
||||
[0x11A59, 0x11A5B], [0x11A8A, 0x11A96], [0x11A98, 0x11A99],
|
||||
[0x11C30, 0x11C36], [0x11C38, 0x11C3D], [0x11C3F, 0x11C3F],
|
||||
[0x11C92, 0x11CA7], [0x11CAA, 0x11CB0], [0x11CB2, 0x11CB3],
|
||||
[0x11CB5, 0x11CB6], [0x11D31, 0x11D36], [0x11D3A, 0x11D3A],
|
||||
[0x11D3C, 0x11D3D], [0x11D3F, 0x11D45], [0x11D47, 0x11D47],
|
||||
[0x11D90, 0x11D91], [0x11D95, 0x11D95], [0x11D97, 0x11D97],
|
||||
[0x11EF3, 0x11EF4], [0x13430, 0x13438], [0x16AF0, 0x16AF4],
|
||||
[0x16B30, 0x16B36], [0x16F4F, 0x16F4F], [0x16F8F, 0x16F92],
|
||||
[0x1BC9D, 0x1BC9E], [0x1BCA0, 0x1BCA3], [0x1D167, 0x1D169],
|
||||
[0x1D173, 0x1D182], [0x1D185, 0x1D18B], [0x1D1AA, 0x1D1AD],
|
||||
[0x1D242, 0x1D244], [0x1DA00, 0x1DA36], [0x1DA3B, 0x1DA6C],
|
||||
[0x1DA75, 0x1DA75], [0x1DA84, 0x1DA84], [0x1DA9B, 0x1DA9F],
|
||||
[0x1DAA1, 0x1DAAF], [0x1E000, 0x1E006], [0x1E008, 0x1E018],
|
||||
[0x1E01B, 0x1E021], [0x1E023, 0x1E024], [0x1E026, 0x1E02A],
|
||||
[0x1E130, 0x1E136], [0x1E2EC, 0x1E2EF], [0x1E8D0, 0x1E8D6],
|
||||
[0x1E944, 0x1E94A], [0xE0001, 0xE0001], [0xE0020, 0xE007F],
|
||||
[0xE0100, 0xE01EF]
|
||||
];
|
||||
const BMP_WIDE = [
|
||||
[0x1100, 0x115F], [0x231A, 0x231B], [0x2329, 0x232A],
|
||||
[0x23E9, 0x23EC], [0x23F0, 0x23F0], [0x23F3, 0x23F3],
|
||||
[0x25FD, 0x25FE], [0x2614, 0x2615], [0x2648, 0x2653],
|
||||
[0x267F, 0x267F], [0x2693, 0x2693], [0x26A1, 0x26A1],
|
||||
[0x26AA, 0x26AB], [0x26BD, 0x26BE], [0x26C4, 0x26C5],
|
||||
[0x26CE, 0x26CE], [0x26D4, 0x26D4], [0x26EA, 0x26EA],
|
||||
[0x26F2, 0x26F3], [0x26F5, 0x26F5], [0x26FA, 0x26FA],
|
||||
[0x26FD, 0x26FD], [0x2705, 0x2705], [0x270A, 0x270B],
|
||||
[0x2728, 0x2728], [0x274C, 0x274C], [0x274E, 0x274E],
|
||||
[0x2753, 0x2755], [0x2757, 0x2757], [0x2795, 0x2797],
|
||||
[0x27B0, 0x27B0], [0x27BF, 0x27BF], [0x2B1B, 0x2B1C],
|
||||
[0x2B50, 0x2B50], [0x2B55, 0x2B55], [0x2E80, 0x2E99],
|
||||
[0x2E9B, 0x2EF3], [0x2F00, 0x2FD5], [0x2FF0, 0x2FFB],
|
||||
[0x3000, 0x3029], [0x302E, 0x303E], [0x3041, 0x3096],
|
||||
[0x309B, 0x30FF], [0x3105, 0x312F], [0x3131, 0x318E],
|
||||
[0x3190, 0x31BA], [0x31C0, 0x31E3], [0x31F0, 0x321E],
|
||||
[0x3220, 0x3247], [0x3250, 0x4DBF], [0x4E00, 0xA48C],
|
||||
[0xA490, 0xA4C6], [0xA960, 0xA97C], [0xAC00, 0xD7A3],
|
||||
[0xF900, 0xFAFF], [0xFE10, 0xFE19], [0xFE30, 0xFE52],
|
||||
[0xFE54, 0xFE66], [0xFE68, 0xFE6B], [0xFF01, 0xFF60],
|
||||
[0xFFE0, 0xFFE6]
|
||||
];
|
||||
const HIGH_WIDE = [
|
||||
[0x16FE0, 0x16FE3], [0x17000, 0x187F7],
|
||||
[0x18800, 0x18AF2], [0x1B000, 0x1B11E], [0x1B150, 0x1B152],
|
||||
[0x1B164, 0x1B167], [0x1B170, 0x1B2FB], [0x1F004, 0x1F004],
|
||||
[0x1F0CF, 0x1F0CF], [0x1F18E, 0x1F18E], [0x1F191, 0x1F19A],
|
||||
[0x1F200, 0x1F202], [0x1F210, 0x1F23B], [0x1F240, 0x1F248],
|
||||
[0x1F250, 0x1F251], [0x1F260, 0x1F265], [0x1F300, 0x1F320],
|
||||
[0x1F32D, 0x1F335], [0x1F337, 0x1F37C], [0x1F37E, 0x1F393],
|
||||
[0x1F3A0, 0x1F3CA], [0x1F3CF, 0x1F3D3], [0x1F3E0, 0x1F3F0],
|
||||
[0x1F3F4, 0x1F3F4], [0x1F3F8, 0x1F43E], [0x1F440, 0x1F440],
|
||||
[0x1F442, 0x1F4FC], [0x1F4FF, 0x1F53D], [0x1F54B, 0x1F54E],
|
||||
[0x1F550, 0x1F567], [0x1F57A, 0x1F57A], [0x1F595, 0x1F596],
|
||||
[0x1F5A4, 0x1F5A4], [0x1F5FB, 0x1F64F], [0x1F680, 0x1F6C5],
|
||||
[0x1F6CC, 0x1F6CC], [0x1F6D0, 0x1F6D2], [0x1F6D5, 0x1F6D5],
|
||||
[0x1F6EB, 0x1F6EC], [0x1F6F4, 0x1F6FA], [0x1F7E0, 0x1F7EB],
|
||||
[0x1F90D, 0x1F971], [0x1F973, 0x1F976], [0x1F97A, 0x1F9A2],
|
||||
[0x1F9A5, 0x1F9AA], [0x1F9AE, 0x1F9CA], [0x1F9CD, 0x1F9FF],
|
||||
[0x1FA70, 0x1FA73], [0x1FA78, 0x1FA7A], [0x1FA80, 0x1FA82],
|
||||
[0x1FA90, 0x1FA95], [0x20000, 0x2FFFD], [0x30000, 0x3FFFD]
|
||||
];
|
||||
|
||||
// BMP lookup table, lazy initialized during first addon loading
|
||||
let table: Uint8Array;
|
||||
|
||||
function bisearch(ucs: number, data: number[][]): boolean {
|
||||
let min = 0;
|
||||
let max = data.length - 1;
|
||||
let mid;
|
||||
if (ucs < data[0][0] || ucs > data[max][1]) {
|
||||
return false;
|
||||
}
|
||||
while (max >= min) {
|
||||
mid = (min + max) >> 1;
|
||||
if (ucs > data[mid][1]) {
|
||||
min = mid + 1;
|
||||
} else if (ucs < data[mid][0]) {
|
||||
max = mid - 1;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
export class UnicodeV11 implements IUnicodeVersionProvider {
|
||||
public readonly version = '11';
|
||||
|
||||
constructor() {
|
||||
if (!table) {
|
||||
table = new Uint8Array(65536);
|
||||
fill(table, 1);
|
||||
table[0] = 0;
|
||||
fill(table, 0, 1, 32);
|
||||
fill(table, 0, 0x7f, 0xa0);
|
||||
for (let r = 0; r < BMP_COMBINING.length; ++r) {
|
||||
fill(table, 0, BMP_COMBINING[r][0], BMP_COMBINING[r][1] + 1);
|
||||
}
|
||||
for (let r = 0; r < BMP_WIDE.length; ++r) {
|
||||
fill(table, 2, BMP_WIDE[r][0], BMP_WIDE[r][1] + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public wcwidth(num: number): CharWidth {
|
||||
if (num < 32) return 0;
|
||||
if (num < 127) return 1;
|
||||
if (num < 65536) return table[num] as CharWidth;
|
||||
if (bisearch(num, HIGH_COMBINING)) return 0;
|
||||
if (bisearch(num, HIGH_WIDE)) return 2;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es2015",
|
||||
"lib": [
|
||||
"dom",
|
||||
"es2015"
|
||||
],
|
||||
"rootDir": ".",
|
||||
"outDir": "../out",
|
||||
"sourceMap": true,
|
||||
"removeComments": true,
|
||||
"strict": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"common/*": [
|
||||
"../../../src/common/*"
|
||||
]
|
||||
},
|
||||
"types": [
|
||||
"../../../node_modules/@types/mocha",
|
||||
"../../../out-test/api/TestUtils"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"./**/*",
|
||||
"../../../typings/xterm.d.ts"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../../src/common"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { Terminal, ITerminalAddon } from 'xterm';
|
||||
|
||||
declare module 'xterm-addon-unicode11' {
|
||||
export class Unicode11Addon implements ITerminalAddon {
|
||||
constructor();
|
||||
public activate(terminal: Terminal): void;
|
||||
public dispose(): void;
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
|
||||
const addonName = 'Unicode11Addon';
|
||||
const mainFile = 'xterm-addon-unicode11.js';
|
||||
|
||||
module.exports = {
|
||||
entry: `./out/${addonName}.js`,
|
||||
devtool: 'source-map',
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
use: ["source-map-loader"],
|
||||
enforce: "pre",
|
||||
exclude: /node_modules/
|
||||
}
|
||||
]
|
||||
},
|
||||
resolve: {
|
||||
modules: ['./node_modules'],
|
||||
extensions: [ '.js' ],
|
||||
alias: {
|
||||
common: path.resolve('../../out/common')
|
||||
}
|
||||
},
|
||||
output: {
|
||||
filename: mainFile,
|
||||
path: path.resolve('./lib'),
|
||||
library: addonName,
|
||||
libraryTarget: 'umd'
|
||||
},
|
||||
mode: 'production'
|
||||
};
|
||||
@@ -1,2 +0,0 @@
|
||||
lib
|
||||
node_modules
|
||||
@@ -1,5 +0,0 @@
|
||||
**/*.api.js
|
||||
**/*.api.ts
|
||||
tsconfig.json
|
||||
.yarnrc
|
||||
webpack.config.js
|
||||
@@ -1,19 +0,0 @@
|
||||
Copyright (c) 2017, The xterm.js authors (https://github.com/xtermjs/xterm.js)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
@@ -1,21 +0,0 @@
|
||||
## xterm-addon-web-links
|
||||
|
||||
An addon for [xterm.js](https://github.com/xtermjs/xterm.js) that enables web links. This addon requires xterm.js v4+.
|
||||
|
||||
### Install
|
||||
|
||||
```bash
|
||||
npm install --save xterm-addon-web-links
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
```ts
|
||||
import { Terminal } from 'xterm';
|
||||
import { WebLinksAddon } from 'xterm-addon-web-links';
|
||||
|
||||
const terminal = new Terminal();
|
||||
terminal.loadAddon(new WebLinksAddon());
|
||||
```
|
||||
|
||||
See the full [API](https://github.com/xtermjs/xterm.js/blob/master/addons/xterm-addon-web-links/typings/xterm-addon-web-links.d.ts) for more advanced usage.
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"name": "xterm-addon-web-links",
|
||||
"version": "0.3.0",
|
||||
"author": {
|
||||
"name": "The xterm.js authors",
|
||||
"url": "https://xtermjs.org/"
|
||||
},
|
||||
"main": "lib/xterm-addon-web-links.js",
|
||||
"types": "typings/xterm-addon-web-links.d.ts",
|
||||
"repository": "https://github.com/xtermjs/xterm.js",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build": "../../node_modules/.bin/tsc -p src",
|
||||
"prepackage": "npm run build",
|
||||
"package": "../../node_modules/.bin/webpack",
|
||||
"prepublishOnly": "npm run package"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"xterm": "^4.0.0"
|
||||
}
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { ILinkProvider, IBufferCellPosition, ILink, Terminal, IBuffer } from 'xterm';
|
||||
|
||||
export class WebLinkProvider implements ILinkProvider {
|
||||
|
||||
constructor(
|
||||
private readonly _terminal: Terminal,
|
||||
private readonly _regex: RegExp,
|
||||
private readonly _handler: (event: MouseEvent, uri: string) => void
|
||||
) {
|
||||
|
||||
}
|
||||
|
||||
provideLink(position: IBufferCellPosition, callback: (link: ILink | undefined) => void): void {
|
||||
callback(LinkComputer.computeLink(position, this._regex, this._terminal, this._handler));
|
||||
}
|
||||
}
|
||||
|
||||
export class LinkComputer {
|
||||
public static computeLink(position: IBufferCellPosition, regex: RegExp, terminal: Terminal, handler: (event: MouseEvent, uri: string) => void): ILink | undefined {
|
||||
const rex = new RegExp(regex.source, (regex.flags || '') + 'g');
|
||||
|
||||
const [line, startLineIndex] = LinkComputer._translateBufferLineToStringWithWrap(position.y - 1, false, terminal);
|
||||
|
||||
let match;
|
||||
let stringIndex = -1;
|
||||
|
||||
while ((match = rex.exec(line)) !== null) {
|
||||
const text = match[1];
|
||||
if (!text) {
|
||||
// something matched but does not comply with the given matchIndex
|
||||
// since this is most likely a bug the regex itself we simply do nothing here
|
||||
console.log('match found without corresponding matchIndex');
|
||||
break;
|
||||
}
|
||||
|
||||
// Get index, match.index is for the outer match which includes negated chars
|
||||
// therefore we cannot use match.index directly, instead we search the position
|
||||
// of the match group in text again
|
||||
// also correct regex and string search offsets for the next loop run
|
||||
stringIndex = line.indexOf(text, stringIndex + 1);
|
||||
rex.lastIndex = stringIndex + text.length;
|
||||
if (stringIndex < 0) {
|
||||
// invalid stringIndex (should not have happened)
|
||||
break;
|
||||
}
|
||||
|
||||
let endX = stringIndex + text.length;
|
||||
let endY = startLineIndex + 1;
|
||||
|
||||
while (endX > terminal.cols) {
|
||||
endX -= terminal.cols;
|
||||
endY++;
|
||||
}
|
||||
|
||||
const range = {
|
||||
start: {
|
||||
x: stringIndex + 1,
|
||||
y: startLineIndex + 1
|
||||
},
|
||||
end: {
|
||||
x: endX,
|
||||
y: endY
|
||||
}
|
||||
};
|
||||
|
||||
return { range, text, activate: handler };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the entire line for the buffer line
|
||||
* @param line The line being translated.
|
||||
* @param trimRight Whether to trim whitespace to the right.
|
||||
* @param terminal The terminal
|
||||
*/
|
||||
private static _translateBufferLineToStringWithWrap(lineIndex: number, trimRight: boolean, terminal: Terminal): [string, number] {
|
||||
let lineString = '';
|
||||
let lineWrapsToNext: boolean;
|
||||
let prevLinesToWrap: boolean;
|
||||
|
||||
do {
|
||||
const line = terminal.buffer.active.getLine(lineIndex);
|
||||
if (!line) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (line.isWrapped) {
|
||||
lineIndex--;
|
||||
}
|
||||
|
||||
prevLinesToWrap = line.isWrapped;
|
||||
} while (prevLinesToWrap);
|
||||
|
||||
const startLineIndex = lineIndex;
|
||||
|
||||
do {
|
||||
const nextLine = terminal.buffer.active.getLine(lineIndex + 1);
|
||||
lineWrapsToNext = nextLine ? nextLine.isWrapped : false;
|
||||
const line = terminal.buffer.active.getLine(lineIndex);
|
||||
if (!line) {
|
||||
break;
|
||||
}
|
||||
lineString += line.translateToString(!lineWrapsToNext && trimRight).substring(0, terminal.cols);
|
||||
lineIndex++;
|
||||
} while (lineWrapsToNext);
|
||||
|
||||
return [lineString, startLineIndex];
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { assert } from 'chai';
|
||||
import { openTerminal, pollFor, writeSync, getBrowserType } from '../../../out-test/api/TestUtils';
|
||||
import { Browser, Page } from 'playwright-core';
|
||||
|
||||
const APP = 'http://127.0.0.1:3000/test';
|
||||
|
||||
let browser: Browser;
|
||||
let page: Page;
|
||||
const width = 800;
|
||||
const height = 600;
|
||||
|
||||
describe('WebLinksAddon', () => {
|
||||
before(async function(): Promise<any> {
|
||||
const browserType = getBrowserType();
|
||||
browser = await browserType.launch({ dumpio: true,
|
||||
headless: process.argv.indexOf('--headless') !== -1
|
||||
});
|
||||
page = await (await browser.newContext()).newPage();
|
||||
await page.setViewportSize({ width, height });
|
||||
});
|
||||
|
||||
after(async () => await browser.close());
|
||||
beforeEach(async () => await page.goto(APP));
|
||||
|
||||
it('.com', async function(): Promise<any> {
|
||||
await testHostName('foo.com');
|
||||
});
|
||||
|
||||
it('.com.au', async function(): Promise<any> {
|
||||
await testHostName('foo.com.au');
|
||||
});
|
||||
|
||||
it('.io', async function(): Promise<any> {
|
||||
await testHostName('foo.io');
|
||||
});
|
||||
});
|
||||
|
||||
async function testHostName(hostname: string): Promise<void> {
|
||||
await openTerminal(page, { rendererType: 'dom', cols: 40 });
|
||||
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`);
|
||||
}
|
||||
|
||||
async function pollForLinkAtCell(col: number, row: number, value: string): Promise<void> {
|
||||
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);
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { Terminal, ILinkMatcherOptions, ITerminalAddon, ILinkProvider, IDisposable } from 'xterm';
|
||||
import { WebLinkProvider } from './WebLinkProvider';
|
||||
|
||||
const protocolClause = '(https?:\\/\\/)';
|
||||
const domainCharacterSet = '[\\da-z\\.-]+';
|
||||
const negatedDomainCharacterSet = '[^\\da-z\\.-]+';
|
||||
const domainBodyClause = '(' + domainCharacterSet + ')';
|
||||
const tldClause = '([a-z\\.]{2,6})';
|
||||
const ipClause = '((\\d{1,3}\\.){3}\\d{1,3})';
|
||||
const localHostClause = '(localhost)';
|
||||
const portClause = '(:\\d{1,5})';
|
||||
const hostClause = '((' + domainBodyClause + '\\.' + tldClause + ')|' + ipClause + '|' + localHostClause + ')' + portClause + '?';
|
||||
const pathCharacterSet = '(\\/[\\/\\w\\.\\-%~:+]*)*([^:"\'\\s])';
|
||||
const pathClause = '(' + pathCharacterSet + ')?';
|
||||
const queryStringHashFragmentCharacterSet = '[0-9\\w\\[\\]\\(\\)\\/\\?\\!#@$%&\'*+,:;~\\=\\.\\-]*';
|
||||
const queryStringClause = '(\\?' + queryStringHashFragmentCharacterSet + ')?';
|
||||
const hashFragmentClause = '(#' + queryStringHashFragmentCharacterSet + ')?';
|
||||
const negatedPathCharacterSet = '[^\\/\\w\\.\\-%]+';
|
||||
const bodyClause = hostClause + pathClause + queryStringClause + hashFragmentClause;
|
||||
const start = '(?:^|' + negatedDomainCharacterSet + ')(';
|
||||
const end = ')($|' + negatedPathCharacterSet + ')';
|
||||
const strictUrlRegex = new RegExp(start + protocolClause + bodyClause + end);
|
||||
|
||||
function handleLink(event: MouseEvent, uri: string): void {
|
||||
const newWindow = window.open();
|
||||
if (newWindow) {
|
||||
newWindow.opener = null;
|
||||
newWindow.location.href = uri;
|
||||
} else {
|
||||
console.warn('Opening link blocked as opener could not be cleared');
|
||||
}
|
||||
}
|
||||
|
||||
export class WebLinksAddon implements ITerminalAddon {
|
||||
private _linkMatcherId: number | undefined;
|
||||
private _terminal: Terminal | undefined;
|
||||
private _linkProvider: IDisposable | undefined;
|
||||
|
||||
constructor(
|
||||
private _handler: (event: MouseEvent, uri: string) => void = handleLink,
|
||||
private _options: ILinkMatcherOptions = {},
|
||||
private _useLinkProvider: boolean = false
|
||||
) {
|
||||
this._options.matchIndex = 1;
|
||||
}
|
||||
|
||||
public activate(terminal: Terminal): void {
|
||||
this._terminal = terminal;
|
||||
|
||||
if (this._useLinkProvider && 'registerLinkProvider' in this._terminal) {
|
||||
this._linkProvider = this._terminal.registerLinkProvider(new WebLinkProvider(this._terminal, strictUrlRegex, this._handler));
|
||||
} else {
|
||||
// TODO: This should be removed eventually
|
||||
this._linkMatcherId = (this._terminal as Terminal).registerLinkMatcher(strictUrlRegex, this._handler, this._options);
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
if (this._linkMatcherId !== undefined && this._terminal !== undefined) {
|
||||
this._terminal.deregisterLinkMatcher(this._linkMatcherId);
|
||||
}
|
||||
|
||||
this._linkProvider?.dispose();
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
|
||||
import { Terminal, ILinkMatcherOptions, ITerminalAddon } from 'xterm';
|
||||
|
||||
declare module 'xterm-addon-web-links' {
|
||||
/**
|
||||
* An xterm.js addon that enables web links.
|
||||
*/
|
||||
export class WebLinksAddon implements ITerminalAddon {
|
||||
/**
|
||||
* Creates a new web links addon.
|
||||
* @param handler The callback when the link is called.
|
||||
* @param options Options for the link matcher.
|
||||
* @param useLinkProvider Whether to use the new link provider API to create
|
||||
* the links. This is an option because use of both link matcher (old) and
|
||||
* link provider (new) may cause issues. Link provider will eventually be
|
||||
* the default and only option.
|
||||
*/
|
||||
constructor(handler?: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions, useLinkProvider?: boolean);
|
||||
|
||||
/**
|
||||
* Activates the addon
|
||||
* @param terminal The terminal the addon is being loaded in.
|
||||
*/
|
||||
public activate(terminal: Terminal): void;
|
||||
|
||||
/**
|
||||
* Disposes the addon.
|
||||
*/
|
||||
public dispose(): void;
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
|
||||
const addonName = 'WebLinksAddon';
|
||||
const mainFile = 'xterm-addon-web-links.js';
|
||||
|
||||
module.exports = {
|
||||
entry: `./out/${addonName}.js`,
|
||||
devtool: 'source-map',
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
use: ["source-map-loader"],
|
||||
enforce: "pre",
|
||||
exclude: /node_modules/
|
||||
}
|
||||
]
|
||||
},
|
||||
output: {
|
||||
filename: mainFile,
|
||||
path: path.resolve('./lib'),
|
||||
library: addonName,
|
||||
libraryTarget: 'umd'
|
||||
},
|
||||
mode: 'production'
|
||||
};
|
||||
@@ -1,2 +0,0 @@
|
||||
lib
|
||||
node_modules
|
||||
@@ -1,5 +0,0 @@
|
||||
**/*.api.js
|
||||
**/*.api.ts
|
||||
tsconfig.json
|
||||
.yarnrc
|
||||
webpack.config.js
|
||||
@@ -1,19 +0,0 @@
|
||||
Copyright (c) 2018, The xterm.js authors (https://github.com/xtermjs/xterm.js)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
@@ -1,23 +0,0 @@
|
||||
## xterm-addon-webgl
|
||||
|
||||
An addon for [xterm.js](https://github.com/xtermjs/xterm.js) that enables a WebGL-based renderer. This addon requires xterm.js v4+.
|
||||
|
||||
⚠️ This is an experimental addon that is [missing some features and may be unstable](https://github.com/xtermjs/xterm.js/issues?q=is%3Aopen+is%3Aissue+label%3Aarea%2Faddon%2Fwebgl) ⚠️
|
||||
|
||||
### Install
|
||||
|
||||
```bash
|
||||
npm install --save xterm-addon-webgl
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
```ts
|
||||
import { Terminal } from 'xterm';
|
||||
import { WebglAddon } from 'xterm-addon-webgl';
|
||||
|
||||
const terminal = new Terminal();
|
||||
terminal.loadAddon(new WebglAddon());
|
||||
```
|
||||
|
||||
See the full [API](https://github.com/xtermjs/xterm.js/blob/master/addons/xterm-addon-webgl/typings/xterm-addon-webgl.d.ts) for more advanced usage.
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"name": "xterm-addon-webgl",
|
||||
"version": "0.6.0",
|
||||
"author": {
|
||||
"name": "The xterm.js authors",
|
||||
"url": "https://xtermjs.org/"
|
||||
},
|
||||
"main": "lib/xterm-addon-webgl.js",
|
||||
"types": "typings/xterm-addon-webgl.d.ts",
|
||||
"repository": "https://github.com/xtermjs/xterm.js",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build": "../../node_modules/.bin/tsc -p src",
|
||||
"prepackage": "npm run build",
|
||||
"package": "../../node_modules/.bin/webpack",
|
||||
"prepublishOnly": "npm run package"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"xterm": "^4.0.0"
|
||||
}
|
||||
}
|
||||
@@ -1,393 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
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, RENDER_MODEL_BG_OFFSET } from './RenderModel';
|
||||
import { fill } from 'common/TypedArrayUtils';
|
||||
import { slice } from './TypedArray';
|
||||
import { NULL_CELL_CODE, WHITESPACE_CELL_CODE, Attributes, FgFlags } from 'common/buffer/Constants';
|
||||
import { Terminal, IBufferLine } from 'xterm';
|
||||
import { IColorSet, IColor } from 'browser/Types';
|
||||
import { IRenderDimensions } from 'browser/renderer/Types';
|
||||
import { AttributeData } from 'common/buffer/AttributeData';
|
||||
|
||||
interface IVertices {
|
||||
attributes: Float32Array;
|
||||
/**
|
||||
* These buffers are the ones used to bind to WebGL, the reason there are
|
||||
* multiple is to allow double buffering to work as you cannot modify the
|
||||
* buffer while it's being used by the GPU. Having multiple lets us start
|
||||
* working on the next frame.
|
||||
*/
|
||||
attributesBuffers: Float32Array[];
|
||||
selectionAttributes: Float32Array;
|
||||
count: number;
|
||||
}
|
||||
|
||||
const enum VertexAttribLocations {
|
||||
UNIT_QUAD = 0,
|
||||
CELL_POSITION = 1,
|
||||
OFFSET = 2,
|
||||
SIZE = 3,
|
||||
TEXCOORD = 4,
|
||||
TEXSIZE = 5
|
||||
}
|
||||
|
||||
const vertexShaderSource = `#version 300 es
|
||||
layout (location = ${VertexAttribLocations.UNIT_QUAD}) in vec2 a_unitquad;
|
||||
layout (location = ${VertexAttribLocations.CELL_POSITION}) in vec2 a_cellpos;
|
||||
layout (location = ${VertexAttribLocations.OFFSET}) in vec2 a_offset;
|
||||
layout (location = ${VertexAttribLocations.SIZE}) in vec2 a_size;
|
||||
layout (location = ${VertexAttribLocations.TEXCOORD}) in vec2 a_texcoord;
|
||||
layout (location = ${VertexAttribLocations.TEXSIZE}) in vec2 a_texsize;
|
||||
|
||||
uniform mat4 u_projection;
|
||||
uniform vec2 u_resolution;
|
||||
|
||||
out vec2 v_texcoord;
|
||||
|
||||
void main() {
|
||||
vec2 zeroToOne = (a_offset / u_resolution) + a_cellpos + (a_unitquad * a_size);
|
||||
gl_Position = u_projection * vec4(zeroToOne, 0.0, 1.0);
|
||||
v_texcoord = a_texcoord + a_unitquad * a_texsize;
|
||||
}`;
|
||||
|
||||
const fragmentShaderSource = `#version 300 es
|
||||
precision lowp float;
|
||||
|
||||
in vec2 v_texcoord;
|
||||
|
||||
uniform sampler2D u_texture;
|
||||
|
||||
out vec4 outColor;
|
||||
|
||||
void main() {
|
||||
outColor = texture(u_texture, v_texcoord);
|
||||
}`;
|
||||
|
||||
const INDICES_PER_CELL = 10;
|
||||
const BYTES_PER_CELL = INDICES_PER_CELL * Float32Array.BYTES_PER_ELEMENT;
|
||||
const CELL_POSITION_INDICES = 2;
|
||||
|
||||
export class GlyphRenderer {
|
||||
private _atlas: WebglCharAtlas | undefined;
|
||||
|
||||
private _program: WebGLProgram;
|
||||
private _vertexArrayObject: IWebGLVertexArrayObject;
|
||||
private _projectionLocation: WebGLUniformLocation;
|
||||
private _resolutionLocation: WebGLUniformLocation;
|
||||
private _textureLocation: WebGLUniformLocation;
|
||||
private _atlasTexture: WebGLTexture;
|
||||
private _attributesBuffer: WebGLBuffer;
|
||||
private _activeBuffer: number = 0;
|
||||
|
||||
private _vertices: IVertices = {
|
||||
count: 0,
|
||||
attributes: new Float32Array(0),
|
||||
attributesBuffers: [
|
||||
new Float32Array(0),
|
||||
new Float32Array(0)
|
||||
],
|
||||
selectionAttributes: new Float32Array(0)
|
||||
};
|
||||
|
||||
constructor(
|
||||
private _terminal: Terminal,
|
||||
private _colors: IColorSet,
|
||||
private _gl: IWebGL2RenderingContext,
|
||||
private _dimensions: IRenderDimensions
|
||||
) {
|
||||
const gl = this._gl;
|
||||
const program = throwIfFalsy(createProgram(gl, vertexShaderSource, fragmentShaderSource));
|
||||
this._program = program;
|
||||
|
||||
// Uniform locations
|
||||
this._projectionLocation = throwIfFalsy(gl.getUniformLocation(this._program, 'u_projection'));
|
||||
this._resolutionLocation = throwIfFalsy(gl.getUniformLocation(this._program, 'u_resolution'));
|
||||
this._textureLocation = throwIfFalsy(gl.getUniformLocation(this._program, 'u_texture'));
|
||||
|
||||
// Create and set the vertex array object
|
||||
this._vertexArrayObject = gl.createVertexArray();
|
||||
gl.bindVertexArray(this._vertexArrayObject);
|
||||
|
||||
// Setup a_unitquad, this defines the 4 vertices of a rectangle
|
||||
const unitQuadVertices = new Float32Array([0, 0, 1, 0, 0, 1, 1, 1]);
|
||||
const unitQuadVerticesBuffer = gl.createBuffer();
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, unitQuadVerticesBuffer);
|
||||
gl.bufferData(gl.ARRAY_BUFFER, unitQuadVertices, gl.STATIC_DRAW);
|
||||
gl.enableVertexAttribArray(VertexAttribLocations.UNIT_QUAD);
|
||||
gl.vertexAttribPointer(VertexAttribLocations.UNIT_QUAD, 2, this._gl.FLOAT, false, 0, 0);
|
||||
|
||||
// Setup the unit quad element array buffer, this points to indices in
|
||||
// unitQuadVertuces to allow is to draw 2 triangles from the vertices
|
||||
const unitQuadElementIndices = new Uint8Array([0, 1, 3, 0, 2, 3]);
|
||||
const elementIndicesBuffer = gl.createBuffer();
|
||||
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, elementIndicesBuffer);
|
||||
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, unitQuadElementIndices, gl.STATIC_DRAW);
|
||||
|
||||
// Setup attributes
|
||||
this._attributesBuffer = throwIfFalsy(gl.createBuffer());
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, this._attributesBuffer);
|
||||
gl.enableVertexAttribArray(VertexAttribLocations.OFFSET);
|
||||
gl.vertexAttribPointer(VertexAttribLocations.OFFSET, 2, gl.FLOAT, false, BYTES_PER_CELL, 0);
|
||||
gl.vertexAttribDivisor(VertexAttribLocations.OFFSET, 1);
|
||||
gl.enableVertexAttribArray(VertexAttribLocations.SIZE);
|
||||
gl.vertexAttribPointer(VertexAttribLocations.SIZE, 2, gl.FLOAT, false, BYTES_PER_CELL, 2 * Float32Array.BYTES_PER_ELEMENT);
|
||||
gl.vertexAttribDivisor(VertexAttribLocations.SIZE, 1);
|
||||
gl.enableVertexAttribArray(VertexAttribLocations.TEXCOORD);
|
||||
gl.vertexAttribPointer(VertexAttribLocations.TEXCOORD, 2, gl.FLOAT, false, BYTES_PER_CELL, 4 * Float32Array.BYTES_PER_ELEMENT);
|
||||
gl.vertexAttribDivisor(VertexAttribLocations.TEXCOORD, 1);
|
||||
gl.enableVertexAttribArray(VertexAttribLocations.TEXSIZE);
|
||||
gl.vertexAttribPointer(VertexAttribLocations.TEXSIZE, 2, gl.FLOAT, false, BYTES_PER_CELL, 6 * Float32Array.BYTES_PER_ELEMENT);
|
||||
gl.vertexAttribDivisor(VertexAttribLocations.TEXSIZE, 1);
|
||||
gl.enableVertexAttribArray(VertexAttribLocations.CELL_POSITION);
|
||||
gl.vertexAttribPointer(VertexAttribLocations.CELL_POSITION, 2, gl.FLOAT, false, BYTES_PER_CELL, 8 * Float32Array.BYTES_PER_ELEMENT);
|
||||
gl.vertexAttribDivisor(VertexAttribLocations.CELL_POSITION, 1);
|
||||
|
||||
// Setup empty texture atlas
|
||||
this._atlasTexture = throwIfFalsy(gl.createTexture());
|
||||
gl.bindTexture(gl.TEXTURE_2D, this._atlasTexture);
|
||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([0, 0, 255, 255]));
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
||||
|
||||
// Allow drawing of transparent texture
|
||||
gl.enable(gl.BLEND);
|
||||
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
// Set viewport
|
||||
this.onResize();
|
||||
}
|
||||
|
||||
public beginFrame(): boolean {
|
||||
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);
|
||||
}
|
||||
|
||||
private _updateCell(array: Float32Array, x: number, y: number, code: number | undefined, bg: number, fg: number, chars?: string): void {
|
||||
const terminal = this._terminal;
|
||||
|
||||
const i = (y * terminal.cols + x) * INDICES_PER_CELL;
|
||||
|
||||
// Exit early if this is a null/space character
|
||||
if (code === NULL_CELL_CODE || code === WHITESPACE_CELL_CODE || code === undefined/* This is used for the right side of wide chars */) {
|
||||
fill(array, 0, i, i + INDICES_PER_CELL - 1 - CELL_POSITION_INDICES);
|
||||
return;
|
||||
}
|
||||
|
||||
let rasterizedGlyph: IRasterizedGlyph;
|
||||
if (!this._atlas) {
|
||||
return;
|
||||
}
|
||||
if (chars && chars.length > 1) {
|
||||
rasterizedGlyph = this._atlas.getRasterizedGlyphCombinedChar(chars, bg, fg);
|
||||
} else {
|
||||
rasterizedGlyph = this._atlas.getRasterizedGlyph(code, bg, fg);
|
||||
}
|
||||
|
||||
// Fill empty if no glyph was found
|
||||
if (!rasterizedGlyph) {
|
||||
fill(array, 0, i, i + INDICES_PER_CELL - 1 - CELL_POSITION_INDICES);
|
||||
return;
|
||||
}
|
||||
|
||||
// a_origin
|
||||
array[i ] = -rasterizedGlyph.offset.x + this._dimensions.scaledCharLeft;
|
||||
array[i + 1] = -rasterizedGlyph.offset.y + this._dimensions.scaledCharTop;
|
||||
// a_size
|
||||
array[i + 2] = rasterizedGlyph.size.x / this._dimensions.scaledCanvasWidth;
|
||||
array[i + 3] = rasterizedGlyph.size.y / this._dimensions.scaledCanvasHeight;
|
||||
// a_texcoord
|
||||
array[i + 4] = rasterizedGlyph.texturePositionClipSpace.x;
|
||||
array[i + 5] = rasterizedGlyph.texturePositionClipSpace.y;
|
||||
// a_texsize
|
||||
array[i + 6] = rasterizedGlyph.sizeClipSpace.x;
|
||||
array[i + 7] = rasterizedGlyph.sizeClipSpace.y;
|
||||
// a_cellpos only changes on resize
|
||||
}
|
||||
|
||||
public updateSelection(model: IRenderModel, columnSelectMode: boolean): void {
|
||||
const terminal = this._terminal;
|
||||
|
||||
this._vertices.selectionAttributes = slice(this._vertices.attributes, 0);
|
||||
|
||||
const bg = (this._colors.selectionOpaque.rgba >>> 8) | Attributes.CM_RGB;
|
||||
|
||||
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);
|
||||
}
|
||||
} 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);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _updateSelectionRange(startCol: number, endCol: number, y: number, model: IRenderModel, bg: number): void {
|
||||
const terminal = this._terminal;
|
||||
const row = y + terminal.buffer.active.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 code = model.cells[offset];
|
||||
let fg = model.cells[offset + RENDER_MODEL_FG_OFFSET];
|
||||
if (fg & FgFlags.INVERSE) {
|
||||
const workCell = new AttributeData();
|
||||
workCell.fg = fg;
|
||||
workCell.bg = model.cells[offset + RENDER_MODEL_BG_OFFSET];
|
||||
// Get attributes from fg (excluding inverse) and resolve inverse by pullibng rgb colors
|
||||
// from bg. This is needed since the inverse fg color should be based on the original bg
|
||||
// color, not on the selection color
|
||||
fg = (fg & ~(Attributes.CM_MASK | Attributes.RGB_MASK | FgFlags.INVERSE));
|
||||
switch (workCell.getBgColorMode()) {
|
||||
case Attributes.CM_P16:
|
||||
case Attributes.CM_P256:
|
||||
const c = this._getColorFromAnsiIndex(workCell.getBgColor()).rgba;
|
||||
fg |= (c >> 8) & Attributes.RED_MASK | (c >> 8) & Attributes.GREEN_MASK | (c >> 8) & Attributes.BLUE_MASK;
|
||||
case Attributes.CM_RGB:
|
||||
const arr = AttributeData.toColorRGB(workCell.getBgColor());
|
||||
fg |= arr[0] << Attributes.RED_SHIFT | arr[1] << Attributes.GREEN_SHIFT | arr[2] << Attributes.BLUE_SHIFT;
|
||||
case Attributes.CM_DEFAULT:
|
||||
default:
|
||||
const c2 = this._colors.background.rgba;
|
||||
fg |= (c2 >> 8) & Attributes.RED_MASK | (c2 >> 8) & Attributes.GREEN_MASK | (c2 >> 8) & Attributes.BLUE_MASK;
|
||||
}
|
||||
fg |= Attributes.CM_RGB;
|
||||
}
|
||||
if (code & COMBINED_CHAR_BIT_MASK) {
|
||||
if (!line) {
|
||||
line = terminal.buffer.active.getLine(row);
|
||||
}
|
||||
const chars = line!.getCell(x)!.getChars();
|
||||
this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], bg, fg, chars);
|
||||
} else {
|
||||
this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], bg, fg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _getColorFromAnsiIndex(idx: number): IColor {
|
||||
if (idx >= this._colors.ansi.length) {
|
||||
throw new Error('No color found for idx ' + idx);
|
||||
}
|
||||
return this._colors.ansi[idx];
|
||||
}
|
||||
|
||||
public onResize(): void {
|
||||
const terminal = this._terminal;
|
||||
const gl = this._gl;
|
||||
|
||||
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
|
||||
|
||||
// Update vertices
|
||||
const newCount = terminal.cols * terminal.rows * INDICES_PER_CELL;
|
||||
if (this._vertices.count !== newCount) {
|
||||
this._vertices.count = newCount;
|
||||
this._vertices.attributes = new Float32Array(newCount);
|
||||
for (let i = 0; i < this._vertices.attributesBuffers.length; i++) {
|
||||
this._vertices.attributesBuffers[i] = new Float32Array(newCount);
|
||||
}
|
||||
|
||||
let i = 0;
|
||||
for (let y = 0; y < terminal.rows; y++) {
|
||||
for (let x = 0; x < terminal.cols; x++) {
|
||||
this._vertices.attributes[i + 8] = x / terminal.cols;
|
||||
this._vertices.attributes[i + 9] = y / terminal.rows;
|
||||
i += INDICES_PER_CELL;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public setColors(): void {
|
||||
}
|
||||
|
||||
public render(renderModel: IRenderModel, isSelectionVisible: boolean): void {
|
||||
if (!this._atlas) {
|
||||
return;
|
||||
}
|
||||
|
||||
const gl = this._gl;
|
||||
|
||||
gl.useProgram(this._program);
|
||||
gl.bindVertexArray(this._vertexArrayObject);
|
||||
|
||||
// Alternate buffers each frame as the active buffer gets locked while it's in use by the GPU
|
||||
this._activeBuffer = (this._activeBuffer + 1) % 2;
|
||||
const activeBuffer = this._vertices.attributesBuffers[this._activeBuffer];
|
||||
|
||||
// Copy data for each cell of each line up to its line length (the last non-whitespace cell)
|
||||
// from the attributes buffer into activeBuffer, which is the one that gets bound to the GPU.
|
||||
// The reasons for this are as follows:
|
||||
// - So the active buffer can be alternated so we don't get blocked on rendering finishing
|
||||
// - To copy either the normal attributes buffer or the selection attributes buffer when there
|
||||
// is a selection
|
||||
// - So we don't send vertices for all the line-ending whitespace to the GPU
|
||||
let bufferLength = 0;
|
||||
for (let y = 0; y < renderModel.lineLengths.length; y++) {
|
||||
const si = y * this._terminal.cols * INDICES_PER_CELL;
|
||||
const sub = (isSelectionVisible ? this._vertices.selectionAttributes : this._vertices.attributes).subarray(si, si + renderModel.lineLengths[y] * INDICES_PER_CELL);
|
||||
activeBuffer.set(sub, bufferLength);
|
||||
bufferLength += sub.length;
|
||||
}
|
||||
|
||||
// Bind the attributes buffer
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, this._attributesBuffer);
|
||||
gl.bufferData(gl.ARRAY_BUFFER, activeBuffer.subarray(0, bufferLength), gl.STREAM_DRAW);
|
||||
|
||||
// Bind the texture atlas if it's changed
|
||||
if (this._atlas.hasCanvasChanged) {
|
||||
this._atlas.hasCanvasChanged = false;
|
||||
gl.uniform1i(this._textureLocation, 0);
|
||||
gl.activeTexture(gl.TEXTURE0 + 0);
|
||||
gl.bindTexture(gl.TEXTURE_2D, this._atlasTexture);
|
||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this._atlas.cacheCanvas);
|
||||
gl.generateMipmap(gl.TEXTURE_2D);
|
||||
}
|
||||
|
||||
// Set uniforms
|
||||
gl.uniformMatrix4fv(this._projectionLocation, false, PROJECTION_MATRIX);
|
||||
gl.uniform2f(this._resolutionLocation, gl.canvas.width, gl.canvas.height);
|
||||
|
||||
// Draw the viewport
|
||||
gl.drawElementsInstanced(gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0, bufferLength / INDICES_PER_CELL);
|
||||
}
|
||||
|
||||
public setAtlas(atlas: WebglCharAtlas): void {
|
||||
const gl = this._gl;
|
||||
this._atlas = atlas;
|
||||
|
||||
gl.bindTexture(gl.TEXTURE_2D, this._atlasTexture);
|
||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, atlas.cacheCanvas);
|
||||
gl.generateMipmap(gl.TEXTURE_2D);
|
||||
}
|
||||
|
||||
public setDimensions(dimensions: IRenderDimensions): void {
|
||||
this._dimensions = dimensions;
|
||||
}
|
||||
}
|
||||
@@ -1,350 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
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 { 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,
|
||||
SIZE = 1,
|
||||
COLOR = 2,
|
||||
UNIT_QUAD = 3
|
||||
}
|
||||
|
||||
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.UNIT_QUAD}) in vec2 a_unitquad;
|
||||
|
||||
uniform mat4 u_projection;
|
||||
uniform vec2 u_resolution;
|
||||
|
||||
out vec4 v_color;
|
||||
|
||||
void main() {
|
||||
vec2 zeroToOne = (a_position + (a_unitquad * a_size)) / u_resolution;
|
||||
gl_Position = u_projection * vec4(zeroToOne, 0.0, 1.0);
|
||||
v_color = a_color;
|
||||
}`;
|
||||
|
||||
const fragmentShaderSource = `#version 300 es
|
||||
precision lowp float;
|
||||
|
||||
in vec4 v_color;
|
||||
|
||||
out vec4 outColor;
|
||||
|
||||
void main() {
|
||||
outColor = v_color;
|
||||
}`;
|
||||
|
||||
interface IVertices {
|
||||
attributes: Float32Array;
|
||||
selection: Float32Array;
|
||||
count: number;
|
||||
}
|
||||
|
||||
const INDICES_PER_RECTANGLE = 8;
|
||||
const BYTES_PER_RECTANGLE = INDICES_PER_RECTANGLE * Float32Array.BYTES_PER_ELEMENT;
|
||||
|
||||
const INITIAL_BUFFER_RECTANGLE_CAPACITY = 20 * INDICES_PER_RECTANGLE;
|
||||
|
||||
export class RectangleRenderer {
|
||||
|
||||
private _program: WebGLProgram;
|
||||
private _vertexArrayObject: IWebGLVertexArrayObject;
|
||||
private _resolutionLocation: WebGLUniformLocation;
|
||||
private _attributesBuffer: WebGLBuffer;
|
||||
private _projectionLocation: WebGLUniformLocation;
|
||||
private _bgFloat!: Float32Array;
|
||||
private _selectionFloat!: Float32Array;
|
||||
|
||||
private _vertices: IVertices = {
|
||||
count: 0,
|
||||
attributes: new Float32Array(INITIAL_BUFFER_RECTANGLE_CAPACITY),
|
||||
selection: new Float32Array(3 * INDICES_PER_RECTANGLE)
|
||||
};
|
||||
|
||||
constructor(
|
||||
private _terminal: Terminal,
|
||||
private _colors: IColorSet,
|
||||
private _gl: IWebGL2RenderingContext,
|
||||
private _dimensions: IRenderDimensions
|
||||
) {
|
||||
const gl = this._gl;
|
||||
|
||||
this._program = throwIfFalsy(createProgram(gl, vertexShaderSource, fragmentShaderSource));
|
||||
|
||||
// Uniform locations
|
||||
this._resolutionLocation = throwIfFalsy(gl.getUniformLocation(this._program, 'u_resolution'));
|
||||
this._projectionLocation = throwIfFalsy(gl.getUniformLocation(this._program, 'u_projection'));
|
||||
|
||||
// Create and set the vertex array object
|
||||
this._vertexArrayObject = gl.createVertexArray();
|
||||
gl.bindVertexArray(this._vertexArrayObject);
|
||||
|
||||
// Setup a_unitquad, this defines the 4 vertices of a rectangle
|
||||
const unitQuadVertices = new Float32Array([0, 0, 1, 0, 0, 1, 1, 1]);
|
||||
const unitQuadVerticesBuffer = gl.createBuffer();
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, unitQuadVerticesBuffer);
|
||||
gl.bufferData(gl.ARRAY_BUFFER, unitQuadVertices, gl.STATIC_DRAW);
|
||||
gl.enableVertexAttribArray(VertexAttribLocations.UNIT_QUAD);
|
||||
gl.vertexAttribPointer(VertexAttribLocations.UNIT_QUAD, 2, this._gl.FLOAT, false, 0, 0);
|
||||
|
||||
// Setup the unit quad element array buffer, this points to indices in
|
||||
// unitQuadVertuces to allow is to draw 2 triangles from the vertices
|
||||
const unitQuadElementIndices = new Uint8Array([0, 1, 3, 0, 2, 3]);
|
||||
const elementIndicesBuffer = gl.createBuffer();
|
||||
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, elementIndicesBuffer);
|
||||
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, unitQuadElementIndices, gl.STATIC_DRAW);
|
||||
|
||||
// Setup attributes
|
||||
this._attributesBuffer = throwIfFalsy(gl.createBuffer());
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, this._attributesBuffer);
|
||||
gl.enableVertexAttribArray(VertexAttribLocations.POSITION);
|
||||
gl.vertexAttribPointer(VertexAttribLocations.POSITION, 2, gl.FLOAT, false, BYTES_PER_RECTANGLE, 0);
|
||||
gl.vertexAttribDivisor(VertexAttribLocations.POSITION, 1);
|
||||
gl.enableVertexAttribArray(VertexAttribLocations.SIZE);
|
||||
gl.vertexAttribPointer(VertexAttribLocations.SIZE, 2, gl.FLOAT, false, BYTES_PER_RECTANGLE, 2 * Float32Array.BYTES_PER_ELEMENT);
|
||||
gl.vertexAttribDivisor(VertexAttribLocations.SIZE, 1);
|
||||
gl.enableVertexAttribArray(VertexAttribLocations.COLOR);
|
||||
gl.vertexAttribPointer(VertexAttribLocations.COLOR, 4, gl.FLOAT, false, BYTES_PER_RECTANGLE, 4 * Float32Array.BYTES_PER_ELEMENT);
|
||||
gl.vertexAttribDivisor(VertexAttribLocations.COLOR, 1);
|
||||
|
||||
this._updateCachedColors();
|
||||
}
|
||||
|
||||
public render(): void {
|
||||
const gl = this._gl;
|
||||
|
||||
gl.useProgram(this._program);
|
||||
|
||||
gl.bindVertexArray(this._vertexArrayObject);
|
||||
|
||||
gl.uniformMatrix4fv(this._projectionLocation, false, PROJECTION_MATRIX);
|
||||
gl.uniform2f(this._resolutionLocation, gl.canvas.width, gl.canvas.height);
|
||||
|
||||
// Bind attributes buffer and draw
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, this._attributesBuffer);
|
||||
gl.bufferData(gl.ARRAY_BUFFER, this._vertices.attributes, gl.DYNAMIC_DRAW);
|
||||
gl.drawElementsInstanced(this._gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0, this._vertices.count);
|
||||
|
||||
// Bind selection buffer and draw
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, this._attributesBuffer);
|
||||
gl.bufferData(gl.ARRAY_BUFFER, this._vertices.selection, gl.DYNAMIC_DRAW);
|
||||
gl.drawElementsInstanced(this._gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0, 3);
|
||||
}
|
||||
|
||||
public onResize(): void {
|
||||
this._updateViewportRectangle();
|
||||
}
|
||||
|
||||
public setColors(): void {
|
||||
this._updateCachedColors();
|
||||
this._updateViewportRectangle();
|
||||
}
|
||||
|
||||
private _updateCachedColors(): void {
|
||||
this._bgFloat = this._colorToFloat32Array(this._colors.background);
|
||||
this._selectionFloat = this._colorToFloat32Array(this._colors.selectionOpaque);
|
||||
}
|
||||
|
||||
private _updateViewportRectangle(): void {
|
||||
// Set first rectangle that clears the screen
|
||||
this._addRectangleFloat(
|
||||
this._vertices.attributes,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
this._terminal.cols * this._dimensions.scaledCellWidth,
|
||||
this._terminal.rows * this._dimensions.scaledCellHeight,
|
||||
this._bgFloat
|
||||
);
|
||||
}
|
||||
|
||||
public updateSelection(model: ISelectionRenderModel, columnSelectMode: boolean): void {
|
||||
const terminal = this._terminal;
|
||||
|
||||
if (!model.hasSelection) {
|
||||
fill(this._vertices.selection, 0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (columnSelectMode) {
|
||||
const startCol = model.startCol;
|
||||
const width = model.endCol - startCol;
|
||||
const height = model.viewportCappedEndRow - model.viewportCappedStartRow + 1;
|
||||
this._addRectangleFloat(
|
||||
this._vertices.selection,
|
||||
0,
|
||||
startCol * this._dimensions.scaledCellWidth,
|
||||
model.viewportCappedStartRow * this._dimensions.scaledCellHeight,
|
||||
width * this._dimensions.scaledCellWidth,
|
||||
height * this._dimensions.scaledCellHeight,
|
||||
this._selectionFloat
|
||||
);
|
||||
fill(this._vertices.selection, 0, INDICES_PER_RECTANGLE);
|
||||
} else {
|
||||
// Draw first row
|
||||
const startCol = model.viewportStartRow === model.viewportCappedStartRow ? model.startCol : 0;
|
||||
const startRowEndCol = model.viewportCappedStartRow === model.viewportCappedEndRow ? model.endCol : terminal.cols;
|
||||
this._addRectangleFloat(
|
||||
this._vertices.selection,
|
||||
0,
|
||||
startCol * this._dimensions.scaledCellWidth,
|
||||
model.viewportCappedStartRow * this._dimensions.scaledCellHeight,
|
||||
(startRowEndCol - startCol) * this._dimensions.scaledCellWidth,
|
||||
this._dimensions.scaledCellHeight,
|
||||
this._selectionFloat
|
||||
);
|
||||
|
||||
// Draw middle rows
|
||||
const middleRowsCount = Math.max(model.viewportCappedEndRow - model.viewportCappedStartRow - 1, 0);
|
||||
this._addRectangleFloat(
|
||||
this._vertices.selection,
|
||||
INDICES_PER_RECTANGLE,
|
||||
0,
|
||||
(model.viewportCappedStartRow + 1) * this._dimensions.scaledCellHeight,
|
||||
terminal.cols * this._dimensions.scaledCellWidth,
|
||||
middleRowsCount * this._dimensions.scaledCellHeight,
|
||||
this._selectionFloat
|
||||
);
|
||||
|
||||
// Draw final row
|
||||
if (model.viewportCappedStartRow !== model.viewportCappedEndRow) {
|
||||
// Only draw viewportEndRow if it's not the same as viewportStartRow
|
||||
const endCol = model.viewportEndRow === model.viewportCappedEndRow ? model.endCol : terminal.cols;
|
||||
this._addRectangleFloat(
|
||||
this._vertices.selection,
|
||||
INDICES_PER_RECTANGLE * 2,
|
||||
0,
|
||||
model.viewportCappedEndRow * this._dimensions.scaledCellHeight,
|
||||
endCol * this._dimensions.scaledCellWidth,
|
||||
this._dimensions.scaledCellHeight,
|
||||
this._selectionFloat
|
||||
);
|
||||
} else {
|
||||
fill(this._vertices.selection, 0, INDICES_PER_RECTANGLE * 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public updateBackgrounds(model: IRenderModel): void {
|
||||
const terminal = this._terminal;
|
||||
const vertices = this._vertices;
|
||||
|
||||
let rectangleCount = 1;
|
||||
|
||||
for (let y = 0; y < terminal.rows; y++) {
|
||||
let currentStartX = -1;
|
||||
let currentBg = 0;
|
||||
let currentFg = 0;
|
||||
let currentInverse = false;
|
||||
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))) {
|
||||
// A rectangle needs to be drawn if going from non-default to another color
|
||||
if (currentBg !== 0 || (currentInverse && currentFg !== 0)) {
|
||||
const offset = rectangleCount++ * INDICES_PER_RECTANGLE;
|
||||
this._updateRectangle(vertices, offset, currentFg, 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)) {
|
||||
const offset = rectangleCount++ * INDICES_PER_RECTANGLE;
|
||||
this._updateRectangle(vertices, offset, currentFg, 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;
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
this._addRectangle(vertices.attributes, offset, x1, y1, (endX - startX) * this._dimensions.scaledCellWidth, this._dimensions.scaledCellHeight, r, g, b, 1);
|
||||
}
|
||||
|
||||
private _addRectangle(array: Float32Array, offset: number, x1: number, y1: number, width: number, height: number, r: number, g: number, b: number, a: number): void {
|
||||
array[offset ] = x1;
|
||||
array[offset + 1] = y1;
|
||||
array[offset + 2] = width;
|
||||
array[offset + 3] = height;
|
||||
array[offset + 4] = r;
|
||||
array[offset + 5] = g;
|
||||
array[offset + 6] = b;
|
||||
array[offset + 7] = a;
|
||||
}
|
||||
|
||||
private _addRectangleFloat(array: Float32Array, offset: number, x1: number, y1: number, width: number, height: number, color: Float32Array): void {
|
||||
array[offset ] = x1;
|
||||
array[offset + 1] = y1;
|
||||
array[offset + 2] = width;
|
||||
array[offset + 3] = height;
|
||||
array[offset + 4] = color[0];
|
||||
array[offset + 5] = color[1];
|
||||
array[offset + 6] = color[2];
|
||||
array[offset + 7] = color[3];
|
||||
}
|
||||
|
||||
private _colorToFloat32Array(color: IColor): Float32Array {
|
||||
return new Float32Array([
|
||||
((color.rgba >> 24) & 0xFF) / 255,
|
||||
((color.rgba >> 16) & 0xFF) / 255,
|
||||
((color.rgba >> 8 ) & 0xFF) / 255,
|
||||
((color.rgba ) & 0xFF) / 255
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
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 COMBINED_CHAR_BIT_MASK = 0x80000000;
|
||||
|
||||
export class RenderModel implements IRenderModel {
|
||||
public cells: Uint32Array;
|
||||
public lineLengths: Uint32Array;
|
||||
public selection: ISelectionRenderModel;
|
||||
|
||||
constructor() {
|
||||
this.cells = new Uint32Array(0);
|
||||
this.lineLengths = new Uint32Array(0);
|
||||
this.selection = {
|
||||
hasSelection: false,
|
||||
viewportStartRow: 0,
|
||||
viewportEndRow: 0,
|
||||
viewportCappedStartRow: 0,
|
||||
viewportCappedEndRow: 0,
|
||||
startCol: 0,
|
||||
endCol: 0
|
||||
};
|
||||
}
|
||||
|
||||
public resize(cols: number, rows: number): void {
|
||||
const indexCount = cols * rows * RENDER_MODEL_INDICIES_PER_CELL;
|
||||
if (indexCount !== this.cells.length) {
|
||||
this.cells = new Uint32Array(indexCount);
|
||||
this.lineLengths = new Uint32Array(rows);
|
||||
}
|
||||
}
|
||||
|
||||
public clear(): void {
|
||||
fill(this.cells, 0, 0);
|
||||
fill(this.lineLengths, 0, 0);
|
||||
this.clearSelection();
|
||||
}
|
||||
|
||||
public clearSelection(): void {
|
||||
this.selection.hasSelection = false;
|
||||
this.selection.viewportStartRow = 0;
|
||||
this.selection.viewportEndRow = 0;
|
||||
this.selection.viewportCappedStartRow = 0;
|
||||
this.selection.viewportCappedEndRow = 0;
|
||||
this.selection.startCol = 0;
|
||||
this.selection.endCol = 0;
|
||||
}
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
import { assert } from 'chai';
|
||||
import { sliceFallback } from './TypedArray';
|
||||
|
||||
type TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray | Int8Array | Int16Array | Int32Array | Float32Array | Float64Array;
|
||||
|
||||
function deepEquals(a: TypedArray, b: TypedArray): void {
|
||||
assert.equal(a.length, b.length);
|
||||
for (let i = 0; i < a.length; ++i) {
|
||||
assert.equal(a[i], b[i]);
|
||||
}
|
||||
}
|
||||
|
||||
describe('polyfill conformance tests', function(): void {
|
||||
describe('TypedArray.slice', () => {
|
||||
describe('should work with all typed array types', () => {
|
||||
it('Uint8Array', () => {
|
||||
const a = new Uint8Array(5);
|
||||
deepEquals(sliceFallback(a, 2), a.slice(2));
|
||||
deepEquals(sliceFallback(a, 65535), a.slice(65535));
|
||||
deepEquals(sliceFallback(a, -1), a.slice(-1));
|
||||
});
|
||||
it('Uint16Array', () => {
|
||||
const u161 = new Uint16Array(5);
|
||||
const u162 = new Uint16Array(5);
|
||||
deepEquals(sliceFallback(u161, 2), u162.slice(2));
|
||||
deepEquals(sliceFallback(u161, 65535), u162.slice(65535));
|
||||
deepEquals(sliceFallback(u161, -1), u162.slice(-1));
|
||||
});
|
||||
it('Uint32Array', () => {
|
||||
const u321 = new Uint32Array(5);
|
||||
const u322 = new Uint32Array(5);
|
||||
deepEquals(sliceFallback(u321, 2), u322.slice(2));
|
||||
deepEquals(sliceFallback(u321, 65537), u322.slice(65537));
|
||||
deepEquals(sliceFallback(u321, -1), u322.slice(-1));
|
||||
});
|
||||
it('Int8Array', () => {
|
||||
const i81 = new Int8Array(5);
|
||||
const i82 = new Int8Array(5);
|
||||
deepEquals(sliceFallback(i81, 2), i82.slice(2));
|
||||
deepEquals(sliceFallback(i81, 65537), i82.slice(65537));
|
||||
deepEquals(sliceFallback(i81, -1), i82.slice(-1));
|
||||
});
|
||||
it('Int16Array', () => {
|
||||
const i161 = new Int16Array(5);
|
||||
const i162 = new Int16Array(5);
|
||||
deepEquals(sliceFallback(i161, 2), i162.slice(2));
|
||||
deepEquals(sliceFallback(i161, 65535), i162.slice(65535));
|
||||
deepEquals(sliceFallback(i161, -1), i162.slice(-1));
|
||||
});
|
||||
it('Int32Array', () => {
|
||||
const i321 = new Int32Array(5);
|
||||
const i322 = new Int32Array(5);
|
||||
deepEquals(sliceFallback(i321, 2), i322.slice(2));
|
||||
deepEquals(sliceFallback(i321, 65537), i322.slice(65537));
|
||||
deepEquals(sliceFallback(i321, -1), i322.slice(-1));
|
||||
});
|
||||
it('Float32Array', () => {
|
||||
const f321 = new Float32Array(5);
|
||||
const f322 = new Float32Array(5);
|
||||
deepEquals(sliceFallback(f321, 2), f322.slice(2));
|
||||
deepEquals(sliceFallback(f321, 65537), f322.slice(65537));
|
||||
deepEquals(sliceFallback(f321, -1), f322.slice(-1));
|
||||
});
|
||||
it('Float64Array', () => {
|
||||
const f641 = new Float64Array(5);
|
||||
const f642 = new Float64Array(5);
|
||||
deepEquals(sliceFallback(f641, 2), f642.slice(2));
|
||||
deepEquals(sliceFallback(f641, 65537), f642.slice(65537));
|
||||
deepEquals(sliceFallback(f641, -1), f642.slice(-1));
|
||||
});
|
||||
it('Uint8ClampedArray', () => {
|
||||
const u8Clamped1 = new Uint8ClampedArray(5);
|
||||
const u8Clamped2 = new Uint8ClampedArray(5);
|
||||
deepEquals(sliceFallback(u8Clamped1, 2), u8Clamped2.slice(2));
|
||||
deepEquals(sliceFallback(u8Clamped1, 65537), u8Clamped2.slice(65537));
|
||||
deepEquals(sliceFallback(u8Clamped1, -1), u8Clamped2.slice(-1));
|
||||
});
|
||||
});
|
||||
it('start', () => {
|
||||
const arr = new Uint32Array([1, 2, 3, 4, 5]);
|
||||
deepEquals(sliceFallback(arr, -1), arr.slice(-1));
|
||||
deepEquals(sliceFallback(arr, 0), arr.slice(0));
|
||||
deepEquals(sliceFallback(arr, 1), arr.slice(1));
|
||||
deepEquals(sliceFallback(arr, 2), arr.slice(2));
|
||||
deepEquals(sliceFallback(arr, 3), arr.slice(3));
|
||||
deepEquals(sliceFallback(arr, 4), arr.slice(4));
|
||||
deepEquals(sliceFallback(arr, 5), arr.slice(5));
|
||||
});
|
||||
it('end', () => {
|
||||
const arr = new Uint32Array([1, 2, 3, 4, 5]);
|
||||
deepEquals(sliceFallback(arr, -1, -2), arr.slice(-1, -2));
|
||||
deepEquals(sliceFallback(arr, 0, -2), arr.slice(0, -2));
|
||||
deepEquals(sliceFallback(arr, 1, -2), arr.slice(1, -2));
|
||||
deepEquals(sliceFallback(arr, 2, -2), arr.slice(2, -2));
|
||||
deepEquals(sliceFallback(arr, 3, -2), arr.slice(3, -2));
|
||||
deepEquals(sliceFallback(arr, 4, -2), arr.slice(4, -2));
|
||||
deepEquals(sliceFallback(arr, 5, -2), arr.slice(5, -2));
|
||||
|
||||
deepEquals(sliceFallback(arr, -1, 3), arr.slice(-1, 3));
|
||||
deepEquals(sliceFallback(arr, 0, 3), arr.slice(0, 3));
|
||||
deepEquals(sliceFallback(arr, 1, 3), arr.slice(1, 3));
|
||||
deepEquals(sliceFallback(arr, 2, 3), arr.slice(2, 3));
|
||||
deepEquals(sliceFallback(arr, 3, 3), arr.slice(3, 3));
|
||||
deepEquals(sliceFallback(arr, 4, 3), arr.slice(4, 3));
|
||||
deepEquals(sliceFallback(arr, 5, 3), arr.slice(5, 3));
|
||||
|
||||
deepEquals(sliceFallback(arr, -1, 8), arr.slice(-1, 8));
|
||||
deepEquals(sliceFallback(arr, 0, 8), arr.slice(0, 8));
|
||||
deepEquals(sliceFallback(arr, 1, 8), arr.slice(1, 8));
|
||||
deepEquals(sliceFallback(arr, 2, 8), arr.slice(2, 8));
|
||||
deepEquals(sliceFallback(arr, 3, 8), arr.slice(3, 8));
|
||||
deepEquals(sliceFallback(arr, 4, 8), arr.slice(4, 8));
|
||||
deepEquals(sliceFallback(arr, 5, 8), arr.slice(5, 8));
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,32 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
export type TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray | Int8Array | Int16Array | Int32Array | Float32Array | Float64Array;
|
||||
|
||||
export function slice<T extends TypedArray>(array: T, start?: number, end?: number): T {
|
||||
// all modern engines that support .slice
|
||||
if (array.slice) {
|
||||
return array.slice(start, end) as T;
|
||||
}
|
||||
return sliceFallback(array, start, end);
|
||||
}
|
||||
|
||||
export function sliceFallback<T extends TypedArray>(array: T, start: number = 0, end: number = array.length): T {
|
||||
if (start < 0) {
|
||||
start = (array.length + start) % array.length;
|
||||
}
|
||||
if (end >= array.length) {
|
||||
end = array.length;
|
||||
} else {
|
||||
end = (array.length + end) % array.length;
|
||||
}
|
||||
start = Math.min(start, end);
|
||||
|
||||
const result: T = new (array.constructor as any)(end - start);
|
||||
for (let i = 0; i < end - start; ++i) {
|
||||
result[i] = array[i + start];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
-75
@@ -1,75 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
export interface IRasterizedGlyphSet {
|
||||
[bg: number]: { [fg: number]: IRasterizedGlyph } | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a rasterized glyph within a texture atlas. Some numbers are
|
||||
* tracked in CSS pixels as well in order to reduce calculations during the
|
||||
* render loop.
|
||||
*/
|
||||
export interface IRasterizedGlyph {
|
||||
/**
|
||||
* The x and y offset between the glyph's top/left and the top/left of a cell
|
||||
* in pixels.
|
||||
*/
|
||||
offset: IVector;
|
||||
/**
|
||||
* the x and y position of the glyph in the texture in pixels.
|
||||
*/
|
||||
texturePosition: IVector;
|
||||
/**
|
||||
* the x and y position of the glyph in the texture in clip space coordinates.
|
||||
*/
|
||||
texturePositionClipSpace: IVector;
|
||||
/**
|
||||
* The width and height of the glyph in the texture in pixels.
|
||||
*/
|
||||
size: IVector;
|
||||
/**
|
||||
* The width and height of the glyph in the texture in clip space coordinates.
|
||||
*/
|
||||
sizeClipSpace: IVector;
|
||||
}
|
||||
|
||||
export interface IVector {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
export interface IBoundingBox {
|
||||
top: number;
|
||||
left: number;
|
||||
right: number;
|
||||
bottom: number;
|
||||
}
|
||||
|
||||
export interface IRenderModel {
|
||||
cells: Uint32Array;
|
||||
lineLengths: Uint32Array;
|
||||
selection: ISelectionRenderModel;
|
||||
}
|
||||
|
||||
export interface ISelectionRenderModel {
|
||||
hasSelection: boolean;
|
||||
viewportStartRow: number;
|
||||
viewportEndRow: number;
|
||||
viewportCappedStartRow: number;
|
||||
viewportCappedEndRow: number;
|
||||
startCol: number;
|
||||
endCol: number;
|
||||
}
|
||||
|
||||
export interface IWebGL2RenderingContext extends WebGLRenderingContext {
|
||||
vertexAttribDivisor(index: number, divisor: number): void;
|
||||
createVertexArray(): IWebGLVertexArrayObject;
|
||||
bindVertexArray(vao: IWebGLVertexArrayObject): void;
|
||||
drawElementsInstanced(mode: number, count: number, type: number, offset: number, instanceCount: number): void;
|
||||
}
|
||||
|
||||
export interface IWebGLVertexArrayObject {
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { Terminal, ITerminalAddon } from 'xterm';
|
||||
import { WebglRenderer } from './WebglRenderer';
|
||||
import { IRenderService } from 'browser/services/Services';
|
||||
import { IColorSet } from 'browser/Types';
|
||||
|
||||
export class WebglAddon implements ITerminalAddon {
|
||||
private _terminal?: Terminal;
|
||||
private _renderer?: WebglRenderer;
|
||||
|
||||
constructor(
|
||||
private _preserveDrawingBuffer?: boolean
|
||||
) {}
|
||||
|
||||
public activate(terminal: Terminal): void {
|
||||
if (!terminal.element) {
|
||||
throw new Error('Cannot activate WebglAddon before Terminal.open');
|
||||
}
|
||||
this._terminal = terminal;
|
||||
const renderService: IRenderService = (<any>terminal)._core._renderService;
|
||||
const colors: IColorSet = (<any>terminal)._core._colorManager.colors;
|
||||
this._renderer = new WebglRenderer(terminal, colors, this._preserveDrawingBuffer);
|
||||
renderService.setRenderer(this._renderer);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
if (!this._terminal) {
|
||||
throw new Error('Cannot dispose WebglAddon because it is activated');
|
||||
}
|
||||
const renderService: IRenderService = (this._terminal as any)._core._renderService;
|
||||
renderService.setRenderer((this._terminal as any)._core._createRenderer());
|
||||
renderService.onResize(this._terminal.cols, this._terminal.rows);
|
||||
this._renderer = undefined;
|
||||
}
|
||||
|
||||
public get textureAtlas(): HTMLCanvasElement | undefined {
|
||||
return this._renderer?.textureAtlas;
|
||||
}
|
||||
}
|
||||
@@ -1,931 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { ITerminalOptions } from '../../../src/Types';
|
||||
import { ITheme } from 'xterm';
|
||||
import { assert } from 'chai';
|
||||
import { openTerminal, pollFor, writeSync, getBrowserType } from '../../../out-test/api/TestUtils';
|
||||
import { Browser, Page } from 'playwright-core';
|
||||
|
||||
const APP = 'http://127.0.0.1:3000/test';
|
||||
|
||||
let browser: Browser;
|
||||
let page: Page;
|
||||
const width = 800;
|
||||
const height = 600;
|
||||
|
||||
let itWebgl: (expectation: string, callback?: (this: Mocha.ITestCallbackContext, done: MochaDone) => any) => Mocha.ITest | void;
|
||||
|
||||
describe('WebGL Renderer Integration Tests', async () => {
|
||||
const browserType = getBrowserType();
|
||||
const isHeadless = process.argv.indexOf('--headless') !== -1;
|
||||
// Firefox works only in non-headless mode https://github.com/microsoft/playwright/issues/1032
|
||||
const areTestsEnabled = browserType.name() === 'chromium' || (browserType.name() === 'firefox' && !isHeadless);
|
||||
itWebgl = areTestsEnabled ? it : it.skip;
|
||||
|
||||
itWebgl('dispose removes renderer canvases', async function(): Promise<void> {
|
||||
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();
|
||||
});
|
||||
|
||||
describe('colors', () => {
|
||||
if (areTestsEnabled) {
|
||||
before(async () => setupBrowser());
|
||||
after(async () => browser.close());
|
||||
beforeEach(async () => page.evaluate(`window.term.reset()`));
|
||||
}
|
||||
|
||||
itWebgl('foreground 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(page, `\\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]);
|
||||
});
|
||||
|
||||
itWebgl('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(page, `\\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]);
|
||||
});
|
||||
|
||||
itWebgl('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(page, `\\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]);
|
||||
});
|
||||
|
||||
itWebgl('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(page, `\\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]);
|
||||
});
|
||||
|
||||
itWebgl('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(page, `\\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]);
|
||||
});
|
||||
|
||||
itWebgl('foreground 0-15 inivisible', 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(page, `\\x1b[8;30m \\x1b[8;31m \\x1b[8;32m \\x1b[8;33m \\x1b[8;34m \\x1b[8;35m \\x1b[8;36m \\x1b[8;37m `);
|
||||
await pollFor(page, () => getCellColor(1, 1), [0, 0, 0, 255]);
|
||||
await pollFor(page, () => getCellColor(2, 1), [0, 0, 0, 255]);
|
||||
await pollFor(page, () => getCellColor(3, 1), [0, 0, 0, 255]);
|
||||
await pollFor(page, () => getCellColor(4, 1), [0, 0, 0, 255]);
|
||||
await pollFor(page, () => getCellColor(5, 1), [0, 0, 0, 255]);
|
||||
await pollFor(page, () => getCellColor(6, 1), [0, 0, 0, 255]);
|
||||
await pollFor(page, () => getCellColor(7, 1), [0, 0, 0, 255]);
|
||||
await pollFor(page, () => getCellColor(8, 1), [0, 0, 0, 255]);
|
||||
});
|
||||
|
||||
itWebgl('background 0-15 inivisible', 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(page, `\\x1b[8;40m█\\x1b[8;41m█\\x1b[8;42m█\\x1b[8;43m█\\x1b[8;44m█\\x1b[8;45m█\\x1b[8;46m█\\x1b[8;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]);
|
||||
});
|
||||
|
||||
itWebgl('foreground 0-15 bright', 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)});`);
|
||||
await writeSync(page, `\\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]);
|
||||
});
|
||||
|
||||
itWebgl('background 0-15 bright', 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)});`);
|
||||
await writeSync(page, `\\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]);
|
||||
});
|
||||
|
||||
itWebgl('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(page, 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]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
itWebgl('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(page, 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]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
itWebgl('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(page, 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]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
itWebgl('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(page, 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]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
itWebgl('foreground 16-255 invisible', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 240 / 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
data += `\\x1b[8;38;5;${16 + y * 16 + x}m \x1b[0m`;
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(page, 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), [0, 0, 0, 255]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
itWebgl('background 16-255 invisible', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 240 / 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
data += `\\x1b[8;48;5;${16 + y * 16 + x}m█\x1b[0m`;
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(page, 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]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
itWebgl('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(page, 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]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
itWebgl('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(page, 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]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
itWebgl('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(page, 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]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
itWebgl('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(page, 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]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
itWebgl('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(page, 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]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
itWebgl('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(page, 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]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
itWebgl('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(page, 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]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
itWebgl('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(page, 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]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
itWebgl('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(page, 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]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
itWebgl('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(page, 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]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
itWebgl('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(page, 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]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
itWebgl('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(page, 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]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
itWebgl('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(page, 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]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
itWebgl('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(page, 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]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
itWebgl('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(page, 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]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
itWebgl('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(page, 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]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
itWebgl('foreground true color grey invisible', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
data += `\\x1b[8;38;2;${i};${i};${i}m \x1b[0m`;
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(page, 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, 0, 255]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
itWebgl('background true color grey invisible', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
data += `\\x1b[8;48;2;${i};${i};${i}m█\x1b[0m`;
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(page, 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 () => {
|
||||
if (areTestsEnabled) {
|
||||
before(async () => setupBrowser());
|
||||
after(async () => browser.close());
|
||||
beforeEach(async () => page.evaluate(`window.term.reset()`));
|
||||
}
|
||||
|
||||
itWebgl('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(page,
|
||||
`\\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]);
|
||||
});
|
||||
|
||||
itWebgl('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(page,
|
||||
`\\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('selection', async () => {
|
||||
if (areTestsEnabled) {
|
||||
before(async () => setupBrowser());
|
||||
after(async () => browser.close());
|
||||
beforeEach(async () => page.evaluate(`window.term.reset()`));
|
||||
}
|
||||
|
||||
itWebgl('should resolve the inverse foreground color based on the original background color, not the selection', async () => {
|
||||
const theme: ITheme = {
|
||||
foreground: '#FF0000',
|
||||
background: '#00FF00',
|
||||
selection: '#0000FF'
|
||||
};
|
||||
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
|
||||
await writeSync(page, ` █\\x1b[7m█\\x1b[0m`);
|
||||
await pollFor(page, () => getCellColor(1, 1), [0, 255, 0, 255]);
|
||||
await pollFor(page, () => getCellColor(2, 1), [255, 0, 0, 255]);
|
||||
await pollFor(page, () => getCellColor(3, 1), [0, 255, 0, 255]);
|
||||
await page.evaluate(`window.term.selectAll()`);
|
||||
// Selection only cell needs to be first to ensure renderer has kicked in
|
||||
await pollFor(page, () => getCellColor(1, 1), [0, 0, 255, 255]);
|
||||
await pollFor(page, () => getCellColor(2, 1), [255, 0, 0, 255]);
|
||||
await pollFor(page, () => getCellColor(3, 1), [0, 255, 0, 255]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('allowTransparency', async () => {
|
||||
if (areTestsEnabled) {
|
||||
before(async () => setupBrowser({ rendererType: 'dom', allowTransparency: true }));
|
||||
after(async () => browser.close());
|
||||
beforeEach(async () => page.evaluate(`window.term.reset()`));
|
||||
}
|
||||
|
||||
itWebgl('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(page, data);
|
||||
// Inverse background should be opaque
|
||||
await pollFor(page, () => getCellColor(1, 1), [255, 0, 0, 255]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
async function getCellColor(col: number, row: number): Promise<number[]> {
|
||||
await page.evaluate(`
|
||||
window.gl = window.term._core._renderService._renderer._gl;
|
||||
window.result = new Uint8Array(4);
|
||||
window.d = window.term._core._renderService.dimensions;
|
||||
window.gl.readPixels(
|
||||
Math.floor((${col - 0.5}) * window.d.scaledCellWidth),
|
||||
Math.floor(window.gl.drawingBufferHeight - 1 - (${row - 0.5}) * window.d.scaledCellHeight),
|
||||
1, 1, window.gl.RGBA, window.gl.UNSIGNED_BYTE, window.result
|
||||
);
|
||||
`);
|
||||
return await page.evaluate(`Array.from(window.result)`);
|
||||
}
|
||||
|
||||
async function setupBrowser(options: ITerminalOptions = { rendererType: 'dom' }): Promise<void> {
|
||||
const browserType = getBrowserType();
|
||||
browser = await browserType.launch({ dumpio: true,
|
||||
headless: process.argv.indexOf('--headless') !== -1
|
||||
});
|
||||
page = await (await browser.newContext()).newPage();
|
||||
await page.setViewportSize({ width, height });
|
||||
await page.goto(APP);
|
||||
await openTerminal(page, options);
|
||||
await page.evaluate(`
|
||||
window.addon = new WebglAddon(true);
|
||||
window.term.loadAddon(window.addon);
|
||||
`);
|
||||
}
|
||||
|
||||
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'
|
||||
];
|
||||
@@ -1,396 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { ITerminal } from '../../../src/Types';
|
||||
import { GlyphRenderer } from './GlyphRenderer';
|
||||
import { LinkRenderLayer } from './renderLayer/LinkRenderLayer';
|
||||
import { CursorRenderLayer } from './renderLayer/CursorRenderLayer';
|
||||
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 { Disposable } from 'common/Lifecycle';
|
||||
import { NULL_CELL_CODE } from 'common/buffer/Constants';
|
||||
import { Terminal, IEvent } from 'xterm';
|
||||
import { IRenderLayer } from './renderLayer/Types';
|
||||
import { IRenderDimensions, IRenderer, IRequestRefreshRowsEvent } from 'browser/renderer/Types';
|
||||
import { IColorSet } from 'browser/Types';
|
||||
import { EventEmitter } from 'common/EventEmitter';
|
||||
import { CellData } from 'common/buffer/CellData';
|
||||
|
||||
export class WebglRenderer extends Disposable implements IRenderer {
|
||||
private _renderLayers: IRenderLayer[];
|
||||
private _charAtlas: WebglCharAtlas | undefined;
|
||||
private _devicePixelRatio: number;
|
||||
|
||||
private _model: RenderModel = new RenderModel();
|
||||
private _workCell: CellData = new CellData();
|
||||
|
||||
private _canvas: HTMLCanvasElement;
|
||||
private _gl: IWebGL2RenderingContext;
|
||||
private _rectangleRenderer: RectangleRenderer;
|
||||
private _glyphRenderer: GlyphRenderer;
|
||||
|
||||
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,
|
||||
private _colors: IColorSet,
|
||||
preserveDrawingBuffer?: boolean
|
||||
) {
|
||||
super();
|
||||
|
||||
this._core = (this._terminal as any)._core;
|
||||
|
||||
this._renderLayers = [
|
||||
new LinkRenderLayer(this._core.screenElement, 2, this._colors, this._core),
|
||||
new CursorRenderLayer(this._core.screenElement, 3, this._colors, this._onRequestRefreshRows)
|
||||
];
|
||||
this.dimensions = {
|
||||
scaledCharWidth: 0,
|
||||
scaledCharHeight: 0,
|
||||
scaledCellWidth: 0,
|
||||
scaledCellHeight: 0,
|
||||
scaledCharLeft: 0,
|
||||
scaledCharTop: 0,
|
||||
scaledCanvasWidth: 0,
|
||||
scaledCanvasHeight: 0,
|
||||
canvasWidth: 0,
|
||||
canvasHeight: 0,
|
||||
actualCellWidth: 0,
|
||||
actualCellHeight: 0
|
||||
};
|
||||
this._devicePixelRatio = window.devicePixelRatio;
|
||||
this._updateDimensions();
|
||||
|
||||
this._canvas = document.createElement('canvas');
|
||||
|
||||
const contextAttributes = {
|
||||
antialias: false,
|
||||
depth: false,
|
||||
preserveDrawingBuffer
|
||||
};
|
||||
this._gl = this._canvas.getContext('webgl2', contextAttributes) as IWebGL2RenderingContext;
|
||||
if (!this._gl) {
|
||||
throw new Error('WebGL2 not supported ' + this._gl);
|
||||
}
|
||||
this._core.screenElement.appendChild(this._canvas);
|
||||
|
||||
this._rectangleRenderer = new RectangleRenderer(this._terminal, this._colors, this._gl, this.dimensions);
|
||||
this._glyphRenderer = new GlyphRenderer(this._terminal, this._colors, this._gl, this.dimensions);
|
||||
|
||||
// Update dimensions and acquire char atlas
|
||||
this.onCharSizeChanged();
|
||||
|
||||
this._isAttached = document.body.contains(this._core.screenElement);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._renderLayers.forEach(l => l.dispose());
|
||||
this._core.screenElement.removeChild(this._canvas);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
public get textureAtlas(): HTMLCanvasElement | undefined {
|
||||
return this._charAtlas?.cacheCanvas;
|
||||
}
|
||||
|
||||
public setColors(colors: IColorSet): void {
|
||||
this._colors = colors;
|
||||
// Clear layers and force a full render
|
||||
this._renderLayers.forEach(l => {
|
||||
l.setColors(this._terminal, this._colors);
|
||||
l.reset(this._terminal);
|
||||
});
|
||||
|
||||
this._rectangleRenderer.setColors();
|
||||
this._glyphRenderer.setColors();
|
||||
|
||||
this._refreshCharAtlas();
|
||||
|
||||
// Force a full refresh
|
||||
this._model.clear();
|
||||
}
|
||||
|
||||
public onDevicePixelRatioChange(): void {
|
||||
// If the device pixel ratio changed, the char atlas needs to be regenerated
|
||||
// and the terminal needs to refreshed
|
||||
if (this._devicePixelRatio !== window.devicePixelRatio) {
|
||||
this._devicePixelRatio = window.devicePixelRatio;
|
||||
this.onResize(this._terminal.cols, this._terminal.rows);
|
||||
}
|
||||
}
|
||||
|
||||
public onResize(cols: number, rows: number): void {
|
||||
// Update character and canvas dimensions
|
||||
this._updateDimensions();
|
||||
|
||||
this._model.resize(this._terminal.cols, this._terminal.rows);
|
||||
this._rectangleRenderer.onResize();
|
||||
|
||||
// Resize all render layers
|
||||
this._renderLayers.forEach(l => l.resize(this._terminal, this.dimensions));
|
||||
|
||||
// Resize the canvas
|
||||
this._canvas.width = this.dimensions.scaledCanvasWidth;
|
||||
this._canvas.height = this.dimensions.scaledCanvasHeight;
|
||||
this._canvas.style.width = `${this.dimensions.canvasWidth}px`;
|
||||
this._canvas.style.height = `${this.dimensions.canvasHeight}px`;
|
||||
|
||||
// Resize the screen
|
||||
this._core.screenElement.style.width = `${this.dimensions.canvasWidth}px`;
|
||||
this._core.screenElement.style.height = `${this.dimensions.canvasHeight}px`;
|
||||
this._glyphRenderer.setDimensions(this.dimensions);
|
||||
this._glyphRenderer.onResize();
|
||||
|
||||
this._refreshCharAtlas();
|
||||
|
||||
// Force a full refresh
|
||||
this._model.clear();
|
||||
}
|
||||
|
||||
public onCharSizeChanged(): void {
|
||||
this.onResize(this._terminal.cols, this._terminal.rows);
|
||||
}
|
||||
|
||||
public onBlur(): void {
|
||||
this._renderLayers.forEach(l => l.onBlur(this._terminal));
|
||||
}
|
||||
|
||||
public onFocus(): void {
|
||||
this._renderLayers.forEach(l => l.onFocus(this._terminal));
|
||||
}
|
||||
|
||||
public onSelectionChanged(start: [number, number], end: [number, number], columnSelectMode: boolean): void {
|
||||
this._renderLayers.forEach(l => l.onSelectionChanged(this._terminal, start, end, columnSelectMode));
|
||||
|
||||
this._updateSelectionModel(start, end);
|
||||
|
||||
this._rectangleRenderer.updateSelection(this._model.selection, columnSelectMode);
|
||||
this._glyphRenderer.updateSelection(this._model, columnSelectMode);
|
||||
|
||||
this._onRequestRefreshRows.fire({ start: 0, end: this._terminal.rows - 1 });
|
||||
}
|
||||
|
||||
public onCursorMove(): void {
|
||||
this._renderLayers.forEach(l => l.onCursorMove(this._terminal));
|
||||
}
|
||||
|
||||
public onOptionsChanged(): void {
|
||||
this._renderLayers.forEach(l => l.onOptionsChanged(this._terminal));
|
||||
this._updateDimensions();
|
||||
this._refreshCharAtlas();
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the char atlas, aquiring a new one if necessary.
|
||||
* @param terminal The terminal.
|
||||
* @param colorSet The color set to use for the char atlas.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
const atlas = acquireCharAtlas(this._terminal, this._colors, this.dimensions.scaledCharWidth, this.dimensions.scaledCharHeight);
|
||||
if (!('getRasterizedGlyph' in atlas)) {
|
||||
throw new Error('The webgl renderer only works with the webgl char atlas');
|
||||
}
|
||||
this._charAtlas = atlas as WebglCharAtlas;
|
||||
this._charAtlas.warmUp();
|
||||
this._glyphRenderer.setAtlas(this._charAtlas);
|
||||
}
|
||||
|
||||
public clear(): void {
|
||||
this._renderLayers.forEach(l => l.reset(this._terminal));
|
||||
}
|
||||
|
||||
public registerCharacterJoiner(handler: (text: string) => [number, number][]): number {
|
||||
return -1;
|
||||
}
|
||||
|
||||
public deregisterCharacterJoiner(joinerId: number): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
public renderRows(start: number, end: number): void {
|
||||
if (!this._isAttached) {
|
||||
if (document.body.contains(this._core.screenElement) && (this._core as any)._charSizeService.width && (this._core as any)._charSizeService.height) {
|
||||
this._updateDimensions();
|
||||
this._refreshCharAtlas();
|
||||
this._isAttached = true;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Update render layers
|
||||
this._renderLayers.forEach(l => l.onGridChanged(this._terminal, start, end));
|
||||
|
||||
// Tell renderer the frame is beginning
|
||||
if (this._glyphRenderer.beginFrame()) {
|
||||
this._model.clear();
|
||||
}
|
||||
|
||||
// Update model to reflect what's drawn
|
||||
this._updateModel(start, end);
|
||||
|
||||
// Render
|
||||
this._rectangleRenderer.render();
|
||||
this._glyphRenderer.render(this._model, this._model.selection.hasSelection);
|
||||
}
|
||||
|
||||
private _updateModel(start: number, end: number): void {
|
||||
const terminal = this._core;
|
||||
|
||||
for (let y = start; y <= end; y++) {
|
||||
const row = y + terminal.buffer.ydisp;
|
||||
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;
|
||||
|
||||
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) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 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._glyphRenderer.updateCell(x, y, code, this._workCell.bg, this._workCell.fg, chars);
|
||||
}
|
||||
}
|
||||
this._rectangleRenderer.updateBackgrounds(this._model);
|
||||
}
|
||||
|
||||
private _updateSelectionModel(start: [number, number], end: [number, number]): void {
|
||||
const terminal = this._terminal;
|
||||
|
||||
// Selection does not exist
|
||||
if (!start || !end || (start[0] === end[0] && start[1] === end[1])) {
|
||||
this._model.clearSelection();
|
||||
return;
|
||||
}
|
||||
|
||||
// Translate from buffer position to viewport position
|
||||
const viewportStartRow = start[1] - terminal.buffer.active.viewportY;
|
||||
const viewportEndRow = end[1] - terminal.buffer.active.viewportY;
|
||||
const viewportCappedStartRow = Math.max(viewportStartRow, 0);
|
||||
const viewportCappedEndRow = Math.min(viewportEndRow, terminal.rows - 1);
|
||||
|
||||
// No need to draw the selection
|
||||
if (viewportCappedStartRow >= terminal.rows || viewportCappedEndRow < 0) {
|
||||
this._model.clearSelection();
|
||||
return;
|
||||
}
|
||||
|
||||
this._model.selection.hasSelection = true;
|
||||
this._model.selection.viewportStartRow = viewportStartRow;
|
||||
this._model.selection.viewportEndRow = viewportEndRow;
|
||||
this._model.selection.viewportCappedStartRow = viewportCappedStartRow;
|
||||
this._model.selection.viewportCappedEndRow = viewportCappedEndRow;
|
||||
this._model.selection.startCol = start[0];
|
||||
this._model.selection.endCol = end[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Recalculates the character and canvas dimensions.
|
||||
*/
|
||||
private _updateDimensions(): void {
|
||||
// TODO: Acquire CharSizeService properly
|
||||
|
||||
// Perform a new measure if the CharMeasure dimensions are not yet available
|
||||
if (!(this._core as any)._charSizeService.width || !(this._core as any)._charSizeService.height) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate the scaled character width. Width is floored as it must be
|
||||
// drawn to an integer grid in order for the CharAtlas "stamps" to not be
|
||||
// blurry. When text is drawn to the grid not using the CharAtlas, it is
|
||||
// clipped to ensure there is no overlap with the next cell.
|
||||
|
||||
// NOTE: ceil fixes sometime, floor does others :s
|
||||
|
||||
this.dimensions.scaledCharWidth = Math.floor((this._core as any)._charSizeService.width * this._devicePixelRatio);
|
||||
|
||||
// Calculate the scaled character height. Height is ceiled in case
|
||||
// devicePixelRatio is a floating point number in order to ensure there is
|
||||
// enough space to draw the character to the cell.
|
||||
this.dimensions.scaledCharHeight = Math.ceil((this._core as any)._charSizeService.height * this._devicePixelRatio);
|
||||
|
||||
// Calculate the scaled cell height, if lineHeight is not 1 then the value
|
||||
// will be floored because since lineHeight can never be lower then 1, there
|
||||
// is a guarentee that the scaled line height will always be larger than
|
||||
// scaled char height.
|
||||
this.dimensions.scaledCellHeight = Math.floor(this.dimensions.scaledCharHeight * this._terminal.getOption('lineHeight'));
|
||||
|
||||
// Calculate the y coordinate within a cell that text should draw from in
|
||||
// order to draw in the center of a cell.
|
||||
this.dimensions.scaledCharTop = this._terminal.getOption('lineHeight') === 1 ? 0 : Math.round((this.dimensions.scaledCellHeight - this.dimensions.scaledCharHeight) / 2);
|
||||
|
||||
// Calculate the scaled cell width, taking the letterSpacing into account.
|
||||
this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth + Math.round(this._terminal.getOption('letterSpacing'));
|
||||
|
||||
// Calculate the x coordinate with a cell that text should draw from in
|
||||
// order to draw in the center of a cell.
|
||||
this.dimensions.scaledCharLeft = Math.floor(this._terminal.getOption('letterSpacing') / 2);
|
||||
|
||||
// Recalculate the canvas dimensions; scaled* define the actual number of
|
||||
// pixel in the canvas
|
||||
this.dimensions.scaledCanvasHeight = this._terminal.rows * this.dimensions.scaledCellHeight;
|
||||
this.dimensions.scaledCanvasWidth = this._terminal.cols * this.dimensions.scaledCellWidth;
|
||||
|
||||
// The the size of the canvas on the page. It's very important that this
|
||||
// rounds to nearest integer and not ceils as browsers often set
|
||||
// window.devicePixelRatio as something like 1.100000023841858, when it's
|
||||
// actually 1.1. Ceiling causes blurriness as the backing canvas image is 1
|
||||
// pixel too large for the canvas element size.
|
||||
this.dimensions.canvasHeight = Math.round(this.dimensions.scaledCanvasHeight / this._devicePixelRatio);
|
||||
this.dimensions.canvasWidth = Math.round(this.dimensions.scaledCanvasWidth / this._devicePixelRatio);
|
||||
|
||||
// this.dimensions.scaledCanvasHeight = this.dimensions.canvasHeight * devicePixelRatio;
|
||||
// this.dimensions.scaledCanvasWidth = this.dimensions.canvasWidth * devicePixelRatio;
|
||||
|
||||
// Get the _actual_ dimensions of an individual cell. This needs to be
|
||||
// derived from the canvasWidth/Height calculated above which takes into
|
||||
// account window.devicePixelRatio. CharMeasure.width/height by itself is
|
||||
// insufficient when the page is not at 100% zoom level as CharMeasure is
|
||||
// measured in CSS pixels, but the actual char size on the canvas can
|
||||
// differ.
|
||||
// this.dimensions.actualCellHeight = this.dimensions.canvasHeight / this._terminal.rows;
|
||||
// this.dimensions.actualCellWidth = this.dimensions.canvasWidth / this._terminal.cols;
|
||||
|
||||
// This fixes 110% and 125%, not 150% or 175% though
|
||||
this.dimensions.actualCellHeight = this.dimensions.scaledCellHeight / this._devicePixelRatio;
|
||||
this.dimensions.actualCellWidth = this.dimensions.scaledCellWidth / this._devicePixelRatio;
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
/**
|
||||
* A matrix that when multiplies will translate 0-1 coordinates (left to right,
|
||||
* top to bottom) to clip space.
|
||||
*/
|
||||
export const PROJECTION_MATRIX = new Float32Array([
|
||||
2, 0, 0, 0,
|
||||
0, -2, 0, 0,
|
||||
0, 0, 1, 0,
|
||||
-1, 1, 0, 1
|
||||
]);
|
||||
|
||||
export function createProgram(gl: WebGLRenderingContext, vertexSource: string, fragmentSource: string): WebGLProgram | undefined {
|
||||
const program = throwIfFalsy(gl.createProgram());
|
||||
gl.attachShader(program, throwIfFalsy(createShader(gl, gl.VERTEX_SHADER, vertexSource)));
|
||||
gl.attachShader(program, throwIfFalsy(createShader(gl, gl.FRAGMENT_SHADER, fragmentSource)));
|
||||
gl.linkProgram(program);
|
||||
const success = gl.getProgramParameter(program, gl.LINK_STATUS);
|
||||
if (success) {
|
||||
return program;
|
||||
}
|
||||
|
||||
console.error(gl.getProgramInfoLog(program));
|
||||
gl.deleteProgram(program);
|
||||
}
|
||||
|
||||
export function createShader(gl: WebGLRenderingContext, type: number, source: string): WebGLShader | undefined {
|
||||
const shader = throwIfFalsy(gl.createShader(type));
|
||||
gl.shaderSource(shader, source);
|
||||
gl.compileShader(shader);
|
||||
const success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
|
||||
if (success) {
|
||||
return shader;
|
||||
}
|
||||
|
||||
console.error(gl.getShaderInfoLog(shader));
|
||||
gl.deleteShader(shader);
|
||||
}
|
||||
|
||||
export function expandFloat32Array(source: Float32Array, max: number): Float32Array {
|
||||
const newLength = Math.min(source.length * 2, max);
|
||||
const newArray = new Float32Array(newLength);
|
||||
for (let i = 0; i < source.length; i++) {
|
||||
newArray[i] = source[i];
|
||||
}
|
||||
return newArray;
|
||||
}
|
||||
|
||||
export function throwIfFalsy<T>(value: T | undefined | null): T {
|
||||
if (!value) {
|
||||
throw new Error('value must not be falsy');
|
||||
}
|
||||
return value;
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { ICharAtlasConfig } from './Types';
|
||||
import { Attributes } from 'common/buffer/Constants';
|
||||
import { Terminal, FontWeight } from 'xterm';
|
||||
import { IColorSet, IColor } from 'browser/Types';
|
||||
|
||||
const NULL_COLOR: IColor = {
|
||||
css: '',
|
||||
rgba: 0
|
||||
};
|
||||
|
||||
export function generateConfig(scaledCharWidth: number, scaledCharHeight: number, terminal: Terminal, colors: IColorSet): ICharAtlasConfig {
|
||||
// null out some fields that don't matter
|
||||
const clonedColors: IColorSet = {
|
||||
foreground: colors.foreground,
|
||||
background: colors.background,
|
||||
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
|
||||
};
|
||||
return {
|
||||
devicePixelRatio: window.devicePixelRatio,
|
||||
scaledCharWidth,
|
||||
scaledCharHeight,
|
||||
fontFamily: terminal.getOption('fontFamily'),
|
||||
fontSize: terminal.getOption('fontSize'),
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
export function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean {
|
||||
for (let i = 0; i < a.colors.ansi.length; i++) {
|
||||
if (a.colors.ansi[i].rgba !== b.colors.ansi[i].rgba) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return a.devicePixelRatio === b.devicePixelRatio &&
|
||||
a.fontFamily === b.fontFamily &&
|
||||
a.fontSize === b.fontSize &&
|
||||
a.fontWeight === b.fontWeight &&
|
||||
a.fontWeightBold === b.fontWeightBold &&
|
||||
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;
|
||||
}
|
||||
@@ -1,539 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { ICharAtlasConfig } from './Types';
|
||||
import { DIM_OPACITY } from 'browser/renderer/atlas/Constants';
|
||||
import { IRasterizedGlyph, IBoundingBox, IRasterizedGlyphSet } from '../Types';
|
||||
import { DEFAULT_COLOR, Attributes } from 'common/buffer/Constants';
|
||||
import { throwIfFalsy } from '../WebglUtils';
|
||||
import { IColor } from 'browser/Types';
|
||||
import { IDisposable } from 'xterm';
|
||||
import { AttributeData } from 'common/buffer/AttributeData';
|
||||
import { channels, rgba } 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.
|
||||
const TEXTURE_WIDTH = 1024;
|
||||
const TEXTURE_HEIGHT = 1024;
|
||||
|
||||
/**
|
||||
* The amount of the texture to be filled before throwing it away and starting
|
||||
* again. Since the throw away and individual glyph draws don't cost too much,
|
||||
* this prevent juggling multiple textures in the GL context.
|
||||
*/
|
||||
const TEXTURE_CAPACITY = Math.floor(TEXTURE_HEIGHT * 0.8);
|
||||
|
||||
const TRANSPARENT_COLOR = {
|
||||
css: 'rgba(0, 0, 0, 0)',
|
||||
rgba: 0
|
||||
};
|
||||
|
||||
/**
|
||||
* A shared object which is used to draw nothing for a particular cell.
|
||||
*/
|
||||
const NULL_RASTERIZED_GLYPH: IRasterizedGlyph = {
|
||||
offset: { x: 0, y: 0 },
|
||||
texturePosition: { x: 0, y: 0 },
|
||||
texturePositionClipSpace: { x: 0, y: 0 },
|
||||
size: { x: 0, y: 0 },
|
||||
sizeClipSpace: { x: 0, y: 0 }
|
||||
};
|
||||
|
||||
const TMP_CANVAS_GLYPH_PADDING = 2;
|
||||
|
||||
export class WebglCharAtlas implements IDisposable {
|
||||
private _didWarmUp: boolean = false;
|
||||
|
||||
private _cacheMap: { [code: number]: IRasterizedGlyphSet } = {};
|
||||
private _cacheMapCombined: { [chars: string]: IRasterizedGlyphSet } = {};
|
||||
|
||||
// The texture that the atlas is drawn to
|
||||
public cacheCanvas: HTMLCanvasElement;
|
||||
private _cacheCtx: CanvasRenderingContext2D;
|
||||
|
||||
private _tmpCanvas: HTMLCanvasElement;
|
||||
// A temporary context that glyphs are drawn to before being transfered to the atlas.
|
||||
private _tmpCtx: CanvasRenderingContext2D;
|
||||
|
||||
// Since glyphs are expected to be around the same height, the packing
|
||||
// strategy used it to fill a row with glyphs while keeping track of the
|
||||
// tallest glyph in the row. Once the row is full a new row is started at
|
||||
// (0,lastRow+lastRowTallestGlyph).
|
||||
private _currentRowY: number = 0;
|
||||
private _currentRowX: number = 0;
|
||||
private _currentRowHeight: number = 0;
|
||||
|
||||
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
|
||||
) {
|
||||
this.cacheCanvas = document.createElement('canvas');
|
||||
this.cacheCanvas.width = TEXTURE_WIDTH;
|
||||
this.cacheCanvas.height = TEXTURE_HEIGHT;
|
||||
// The canvas needs alpha because we use clearColor to convert the background color to alpha.
|
||||
// It might also contain some characters with transparent backgrounds if allowTransparency is
|
||||
// set.
|
||||
this._cacheCtx = throwIfFalsy(this.cacheCanvas.getContext('2d', {alpha: true}));
|
||||
|
||||
this._tmpCanvas = document.createElement('canvas');
|
||||
this._tmpCanvas.width = this._config.scaledCharWidth * 2 + TMP_CANVAS_GLYPH_PADDING * 2;
|
||||
this._tmpCanvas.height = this._config.scaledCharHeight + TMP_CANVAS_GLYPH_PADDING * 2;
|
||||
this._tmpCtx = throwIfFalsy(this._tmpCanvas.getContext('2d', {alpha: this._config.allowTransparency}));
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
if (this.cacheCanvas.parentElement) {
|
||||
this.cacheCanvas.parentElement.removeChild(this.cacheCanvas);
|
||||
}
|
||||
}
|
||||
|
||||
public warmUp(): void {
|
||||
if (!this._didWarmUp) {
|
||||
this._doWarmUp();
|
||||
this._didWarmUp = true;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
this._cacheMap[i] = {
|
||||
[DEFAULT_COLOR]: {
|
||||
[DEFAULT_COLOR]: rasterizedGlyph
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public beginFrame(): boolean {
|
||||
if (this._currentRowY > TEXTURE_CAPACITY) {
|
||||
this._cacheCtx.clearRect(0, 0, TEXTURE_WIDTH, TEXTURE_HEIGHT);
|
||||
this._cacheMap = {};
|
||||
this._currentRowHeight = 0;
|
||||
this._currentRowX = 0;
|
||||
this._currentRowY = 0;
|
||||
this._doWarmUp();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public getRasterizedGlyphCombinedChar(chars: string, 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];
|
||||
}
|
||||
if (!rasterizedGlyph) {
|
||||
rasterizedGlyph = this._drawToCache(chars, bg, fg);
|
||||
if (!rasterizedGlyphSet[bg]) {
|
||||
rasterizedGlyphSet[bg] = {};
|
||||
}
|
||||
rasterizedGlyphSet[bg]![fg] = rasterizedGlyph;
|
||||
}
|
||||
return rasterizedGlyph;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the glyphs texture coords, drawing the texture if it's not already
|
||||
*/
|
||||
public getRasterizedGlyph(code: 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];
|
||||
}
|
||||
if (!rasterizedGlyph) {
|
||||
rasterizedGlyph = this._drawToCache(code, bg, fg);
|
||||
if (!rasterizedGlyphSet[bg]) {
|
||||
rasterizedGlyphSet[bg] = {};
|
||||
}
|
||||
rasterizedGlyphSet[bg]![fg] = rasterizedGlyph;
|
||||
}
|
||||
return rasterizedGlyph;
|
||||
}
|
||||
|
||||
private _getColorFromAnsiIndex(idx: number): IColor {
|
||||
if (idx >= this._config.colors.ansi.length) {
|
||||
throw new Error('No color found for idx ' + idx);
|
||||
}
|
||||
return this._config.colors.ansi[idx];
|
||||
}
|
||||
|
||||
private _getBackgroundColor(bgColorMode: number, bgColor: number, inverse: boolean): 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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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 channels.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 _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 = rgba.ensureContrastRatio(bgRgba, fgRgba, this._config.minimumContrastRatio);
|
||||
|
||||
if (!result) {
|
||||
this._config.colors.contrastCache.setCss(bg, fg, null);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const css = channels.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 {
|
||||
const chars = typeof codeOrChars === 'number' ? String.fromCharCode(codeOrChars) : codeOrChars;
|
||||
|
||||
this.hasCanvasChanged = true;
|
||||
|
||||
this._tmpCtx.save();
|
||||
|
||||
this._workAttributeData.fg = fg;
|
||||
this._workAttributeData.bg = bg;
|
||||
|
||||
const invisible = !!this._workAttributeData.isInvisible();
|
||||
if (invisible) {
|
||||
return NULL_RASTERIZED_GLYPH;
|
||||
}
|
||||
|
||||
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);
|
||||
// Use a 'copy' composite operation to clear any existing glyph out of _tmpCtxWithAlpha, regardless of
|
||||
// transparency in backgroundColor
|
||||
this._tmpCtx.globalCompositeOperation = 'copy';
|
||||
this._tmpCtx.fillStyle = backgroundColor.css;
|
||||
this._tmpCtx.fillRect(0, 0, this._tmpCanvas.width, this._tmpCanvas.height);
|
||||
this._tmpCtx.globalCompositeOperation = 'source-over';
|
||||
|
||||
// draw the foreground/glyph
|
||||
const fontWeight = bold ? this._config.fontWeightBold : this._config.fontWeight;
|
||||
const fontStyle = italic ? 'italic' : '';
|
||||
this._tmpCtx.font =
|
||||
`${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);
|
||||
|
||||
// Apply alpha to dim the character
|
||||
if (dim) {
|
||||
this._tmpCtx.globalAlpha = DIM_OPACITY;
|
||||
}
|
||||
|
||||
// Draw the character
|
||||
this._tmpCtx.fillText(chars, TMP_CANVAS_GLYPH_PADDING, TMP_CANVAS_GLYPH_PADDING);
|
||||
this._tmpCtx.restore();
|
||||
|
||||
// clear the background from the character to avoid issues with drawing over the previous
|
||||
// character if it extends past it's bounds
|
||||
const imageData = this._tmpCtx.getImageData(
|
||||
0, 0, this._tmpCanvas.width, this._tmpCanvas.height
|
||||
);
|
||||
|
||||
// TODO: Support transparency
|
||||
// let isEmpty = false;
|
||||
// if (!this._config.allowTransparency) {
|
||||
// isEmpty = clearColor(imageData, backgroundColor);
|
||||
// }
|
||||
|
||||
// Clear out the background color and determine if the glyph is empty.
|
||||
const isEmpty = clearColor(imageData, backgroundColor);
|
||||
|
||||
// Handle empty glyphs
|
||||
if (isEmpty) {
|
||||
return NULL_RASTERIZED_GLYPH;
|
||||
}
|
||||
|
||||
const rasterizedGlyph = this._findGlyphBoundingBox(imageData, this._workBoundingBox);
|
||||
const clippedImageData = this._clipImageData(imageData, this._workBoundingBox);
|
||||
|
||||
// Check if there is enough room in the current row and go to next if needed
|
||||
if (this._currentRowX + this._config.scaledCharWidth > TEXTURE_WIDTH) {
|
||||
this._currentRowX = 0;
|
||||
this._currentRowY += this._currentRowHeight;
|
||||
this._currentRowHeight = 0;
|
||||
}
|
||||
|
||||
// Record texture position
|
||||
rasterizedGlyph.texturePosition.x = this._currentRowX;
|
||||
rasterizedGlyph.texturePosition.y = this._currentRowY;
|
||||
rasterizedGlyph.texturePositionClipSpace.x = this._currentRowX / TEXTURE_WIDTH;
|
||||
rasterizedGlyph.texturePositionClipSpace.y = this._currentRowY / TEXTURE_HEIGHT;
|
||||
|
||||
// Update atlas current row
|
||||
this._currentRowHeight = Math.max(this._currentRowHeight, rasterizedGlyph.size.y);
|
||||
this._currentRowX += rasterizedGlyph.size.x;
|
||||
|
||||
// putImageData doesn't do any blending, so it will overwrite any existing cache entry for us
|
||||
this._cacheCtx.putImageData(clippedImageData, rasterizedGlyph.texturePosition.x, rasterizedGlyph.texturePosition.y);
|
||||
|
||||
return rasterizedGlyph;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an ImageData object, find the bounding box of the non-transparent
|
||||
* portion of the texture and return an IRasterizedGlyph with these
|
||||
* dimensions.
|
||||
* @param imageData The image data to read.
|
||||
* @param boundingBox An IBoundingBox to put the clipped bounding box values.
|
||||
*/
|
||||
private _findGlyphBoundingBox(imageData: ImageData, boundingBox: IBoundingBox): IRasterizedGlyph {
|
||||
boundingBox.top = 0;
|
||||
let found = false;
|
||||
for (let y = 0; y < this._tmpCanvas.height; y++) {
|
||||
for (let x = 0; x < this._tmpCanvas.width; x++) {
|
||||
const alphaOffset = y * this._tmpCanvas.width * 4 + x * 4 + 3;
|
||||
if (imageData.data[alphaOffset] !== 0) {
|
||||
boundingBox.top = y;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
boundingBox.left = 0;
|
||||
found = false;
|
||||
for (let x = 0; x < this._tmpCanvas.width; x++) {
|
||||
for (let y = 0; y < this._tmpCanvas.height; y++) {
|
||||
const alphaOffset = y * this._tmpCanvas.width * 4 + x * 4 + 3;
|
||||
if (imageData.data[alphaOffset] !== 0) {
|
||||
boundingBox.left = x;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
boundingBox.right = this._tmpCanvas.width;
|
||||
found = false;
|
||||
for (let x = this._tmpCanvas.width - 1; x >= 0; x--) {
|
||||
for (let y = 0; y < this._tmpCanvas.height; y++) {
|
||||
const alphaOffset = y * this._tmpCanvas.width * 4 + x * 4 + 3;
|
||||
if (imageData.data[alphaOffset] !== 0) {
|
||||
boundingBox.right = x;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
boundingBox.bottom = this._tmpCanvas.height;
|
||||
found = false;
|
||||
for (let y = this._tmpCanvas.height - 1; y >= 0; y--) {
|
||||
for (let x = 0; x < this._tmpCanvas.width; x++) {
|
||||
const alphaOffset = y * this._tmpCanvas.width * 4 + x * 4 + 3;
|
||||
if (imageData.data[alphaOffset] !== 0) {
|
||||
boundingBox.bottom = y;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return {
|
||||
texturePosition: { x: 0, y: 0 },
|
||||
texturePositionClipSpace: { x: 0, y: 0 },
|
||||
size: {
|
||||
x: boundingBox.right - boundingBox.left + 1,
|
||||
y: boundingBox.bottom - boundingBox.top + 1
|
||||
},
|
||||
sizeClipSpace: {
|
||||
x: (boundingBox.right - boundingBox.left + 1) / TEXTURE_WIDTH,
|
||||
y: (boundingBox.bottom - boundingBox.top + 1) / TEXTURE_HEIGHT
|
||||
},
|
||||
offset: {
|
||||
x: -boundingBox.left + TMP_CANVAS_GLYPH_PADDING,
|
||||
y: -boundingBox.top + TMP_CANVAS_GLYPH_PADDING
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private _clipImageData(imageData: ImageData, boundingBox: IBoundingBox): ImageData {
|
||||
const width = boundingBox.right - boundingBox.left + 1;
|
||||
const height = boundingBox.bottom - boundingBox.top + 1;
|
||||
const clippedData = new Uint8ClampedArray(width * height * 4);
|
||||
for (let y = boundingBox.top; y <= boundingBox.bottom; y++) {
|
||||
for (let x = boundingBox.left; x <= boundingBox.right; x++) {
|
||||
const oldOffset = y * this._tmpCanvas.width * 4 + x * 4;
|
||||
const newOffset = (y - boundingBox.top) * width * 4 + (x - boundingBox.left) * 4;
|
||||
clippedData[newOffset] = imageData.data[oldOffset];
|
||||
clippedData[newOffset + 1] = imageData.data[oldOffset + 1];
|
||||
clippedData[newOffset + 2] = imageData.data[oldOffset + 2];
|
||||
clippedData[newOffset + 3] = imageData.data[oldOffset + 3];
|
||||
}
|
||||
}
|
||||
return new ImageData(clippedData, width, height);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a partiicular rgb color in an ImageData completely transparent.
|
||||
* @returns True if the result is "empty", meaning all pixels are fully transparent.
|
||||
*/
|
||||
function clearColor(imageData: ImageData, color: IColor): boolean {
|
||||
let isEmpty = true;
|
||||
const r = color.rgba >>> 24;
|
||||
const g = color.rgba >>> 16 & 0xFF;
|
||||
const b = color.rgba >>> 8 & 0xFF;
|
||||
for (let offset = 0; offset < imageData.data.length; offset += 4) {
|
||||
if (imageData.data[offset] === r &&
|
||||
imageData.data[offset + 1] === g &&
|
||||
imageData.data[offset + 2] === b) {
|
||||
imageData.data[offset + 3] = 0;
|
||||
} else {
|
||||
isEmpty = false;
|
||||
}
|
||||
}
|
||||
return isEmpty;
|
||||
}
|
||||
|
||||
function toPaddedHex(c: number): string {
|
||||
const s = c.toString(16);
|
||||
return s.length < 2 ? '0' + s : s;
|
||||
}
|
||||
@@ -1,262 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { IRenderLayer } from './Types';
|
||||
import { acquireCharAtlas } from '../atlas/CharAtlasCache';
|
||||
import { Terminal } from 'xterm';
|
||||
import { IColorSet } from 'browser/Types';
|
||||
import { IRenderDimensions } from 'browser/renderer/Types';
|
||||
import { CellData } from 'common/buffer/CellData';
|
||||
import { WebglCharAtlas } from 'atlas/WebglCharAtlas';
|
||||
import { throwIfFalsy } from '../WebglUtils';
|
||||
|
||||
export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
private _canvas: HTMLCanvasElement;
|
||||
protected _ctx!: CanvasRenderingContext2D;
|
||||
private _scaledCharWidth: number = 0;
|
||||
private _scaledCharHeight: number = 0;
|
||||
private _scaledCellWidth: number = 0;
|
||||
private _scaledCellHeight: number = 0;
|
||||
private _scaledCharLeft: number = 0;
|
||||
private _scaledCharTop: number = 0;
|
||||
|
||||
protected _charAtlas: WebglCharAtlas | undefined;
|
||||
|
||||
constructor(
|
||||
private _container: HTMLElement,
|
||||
id: string,
|
||||
zIndex: number,
|
||||
private _alpha: boolean,
|
||||
protected _colors: IColorSet
|
||||
) {
|
||||
this._canvas = document.createElement('canvas');
|
||||
this._canvas.classList.add(`xterm-${id}-layer`);
|
||||
this._canvas.style.zIndex = zIndex.toString();
|
||||
this._initCanvas();
|
||||
this._container.appendChild(this._canvas);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._container.removeChild(this._canvas);
|
||||
if (this._charAtlas) {
|
||||
this._charAtlas.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private _initCanvas(): void {
|
||||
this._ctx = throwIfFalsy(this._canvas.getContext('2d', {alpha: this._alpha}));
|
||||
// Draw the background if this is an opaque layer
|
||||
if (!this._alpha) {
|
||||
this._clearAll();
|
||||
}
|
||||
}
|
||||
|
||||
public onOptionsChanged(terminal: Terminal): void {}
|
||||
public onBlur(terminal: Terminal): void {}
|
||||
public onFocus(terminal: Terminal): void {}
|
||||
public onCursorMove(terminal: Terminal): void {}
|
||||
public onGridChanged(terminal: Terminal, startRow: number, endRow: number): void {}
|
||||
public onSelectionChanged(terminal: Terminal, start: [number, number], end: [number, number], columnSelectMode: boolean = false): void {}
|
||||
|
||||
public setColors(terminal: Terminal, colorSet: IColorSet): void {
|
||||
this._refreshCharAtlas(terminal, colorSet);
|
||||
}
|
||||
|
||||
protected _setTransparency(terminal: Terminal, alpha: boolean): void {
|
||||
// Do nothing when alpha doesn't change
|
||||
if (alpha === this._alpha) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create new canvas and replace old one
|
||||
const oldCanvas = this._canvas;
|
||||
this._alpha = alpha;
|
||||
// Cloning preserves properties
|
||||
this._canvas = this._canvas.cloneNode() as HTMLCanvasElement;
|
||||
this._initCanvas();
|
||||
this._container.replaceChild(this._canvas, oldCanvas);
|
||||
|
||||
// Regenerate char atlas and force a full redraw
|
||||
this._refreshCharAtlas(terminal, this._colors);
|
||||
this.onGridChanged(terminal, 0, terminal.rows - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the char atlas, aquiring a new one if necessary.
|
||||
* @param terminal The terminal.
|
||||
* @param colorSet The color set to use for the char atlas.
|
||||
*/
|
||||
private _refreshCharAtlas(terminal: Terminal, colorSet: IColorSet): void {
|
||||
if (this._scaledCharWidth <= 0 && this._scaledCharHeight <= 0) {
|
||||
return;
|
||||
}
|
||||
this._charAtlas = acquireCharAtlas(terminal, colorSet, this._scaledCharWidth, this._scaledCharHeight);
|
||||
this._charAtlas.warmUp();
|
||||
}
|
||||
|
||||
public resize(terminal: Terminal, dim: IRenderDimensions): void {
|
||||
this._scaledCellWidth = dim.scaledCellWidth;
|
||||
this._scaledCellHeight = dim.scaledCellHeight;
|
||||
this._scaledCharWidth = dim.scaledCharWidth;
|
||||
this._scaledCharHeight = dim.scaledCharHeight;
|
||||
this._scaledCharLeft = dim.scaledCharLeft;
|
||||
this._scaledCharTop = dim.scaledCharTop;
|
||||
this._canvas.width = dim.scaledCanvasWidth;
|
||||
this._canvas.height = dim.scaledCanvasHeight;
|
||||
this._canvas.style.width = `${dim.canvasWidth}px`;
|
||||
this._canvas.style.height = `${dim.canvasHeight}px`;
|
||||
|
||||
// Draw the background if this is an opaque layer
|
||||
if (!this._alpha) {
|
||||
this._clearAll();
|
||||
}
|
||||
|
||||
this._refreshCharAtlas(terminal, this._colors);
|
||||
}
|
||||
|
||||
public abstract reset(terminal: Terminal): void;
|
||||
|
||||
/**
|
||||
* Fills 1+ cells completely. This uses the existing fillStyle on the context.
|
||||
* @param x The column to start at.
|
||||
* @param y The row to start at
|
||||
* @param width The number of columns to fill.
|
||||
* @param height The number of rows to fill.
|
||||
*/
|
||||
protected _fillCells(x: number, y: number, width: number, height: number): void {
|
||||
this._ctx.fillRect(
|
||||
x * this._scaledCellWidth,
|
||||
y * this._scaledCellHeight,
|
||||
width * this._scaledCellWidth,
|
||||
height * this._scaledCellHeight);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills a 1px line (2px on HDPI) at the bottom of the cell. This uses the
|
||||
* existing fillStyle on the context.
|
||||
* @param x The column to fill.
|
||||
* @param y The row to fill.
|
||||
*/
|
||||
protected _fillBottomLineAtCells(x: number, y: number, width: number = 1): void {
|
||||
this._ctx.fillRect(
|
||||
x * this._scaledCellWidth,
|
||||
(y + 1) * this._scaledCellHeight - window.devicePixelRatio - 1 /* Ensure it's drawn within the cell */,
|
||||
width * this._scaledCellWidth,
|
||||
window.devicePixelRatio);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills a 1px line (2px on HDPI) at the left of the cell. This uses the
|
||||
* existing fillStyle on the context.
|
||||
* @param x The column to fill.
|
||||
* @param y The row to fill.
|
||||
*/
|
||||
protected _fillLeftLineAtCell(x: number, y: number, width: number): void {
|
||||
this._ctx.fillRect(
|
||||
x * this._scaledCellWidth,
|
||||
y * this._scaledCellHeight,
|
||||
window.devicePixelRatio * width,
|
||||
this._scaledCellHeight);
|
||||
}
|
||||
|
||||
/**
|
||||
* Strokes a 1px rectangle (2px on HDPI) around a cell. This uses the existing
|
||||
* strokeStyle on the context.
|
||||
* @param x The column to fill.
|
||||
* @param y The row to fill.
|
||||
*/
|
||||
protected _strokeRectAtCell(x: number, y: number, width: number, height: number): void {
|
||||
this._ctx.lineWidth = window.devicePixelRatio;
|
||||
this._ctx.strokeRect(
|
||||
x * this._scaledCellWidth + window.devicePixelRatio / 2,
|
||||
y * this._scaledCellHeight + (window.devicePixelRatio / 2),
|
||||
width * this._scaledCellWidth - window.devicePixelRatio,
|
||||
(height * this._scaledCellHeight) - window.devicePixelRatio);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the entire canvas.
|
||||
*/
|
||||
protected _clearAll(): void {
|
||||
if (this._alpha) {
|
||||
this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);
|
||||
} else {
|
||||
this._ctx.fillStyle = this._colors.background.css;
|
||||
this._ctx.fillRect(0, 0, this._canvas.width, this._canvas.height);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears 1+ cells completely.
|
||||
* @param x The column to start at.
|
||||
* @param y The row to start at.
|
||||
* @param width The number of columns to clear.
|
||||
* @param height The number of rows to clear.
|
||||
*/
|
||||
protected _clearCells(x: number, y: number, width: number, height: number): void {
|
||||
if (this._alpha) {
|
||||
this._ctx.clearRect(
|
||||
x * this._scaledCellWidth,
|
||||
y * this._scaledCellHeight,
|
||||
width * this._scaledCellWidth,
|
||||
height * this._scaledCellHeight);
|
||||
} else {
|
||||
this._ctx.fillStyle = this._colors.background.css;
|
||||
this._ctx.fillRect(
|
||||
x * this._scaledCellWidth,
|
||||
y * this._scaledCellHeight,
|
||||
width * this._scaledCellWidth,
|
||||
height * this._scaledCellHeight);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a truecolor character at the cell. The character will be clipped to
|
||||
* ensure that it fits with the cell, including the cell to the right if it's
|
||||
* a wide character. This uses the existing fillStyle on the context.
|
||||
* @param terminal The terminal.
|
||||
* @param cell The cell data for the character to draw.
|
||||
* @param x The column to draw at.
|
||||
* @param y The row to draw at.
|
||||
* @param color The color of the character.
|
||||
*/
|
||||
protected _fillCharTrueColor(terminal: Terminal, cell: CellData, x: number, y: number): void {
|
||||
this._ctx.font = this._getFont(terminal, false, false);
|
||||
this._ctx.textBaseline = 'middle';
|
||||
this._clipRow(terminal, y);
|
||||
this._ctx.fillText(
|
||||
cell.getChars(),
|
||||
x * this._scaledCellWidth + this._scaledCharLeft,
|
||||
y * this._scaledCellHeight + this._scaledCharTop + this._scaledCharHeight / 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clips a row to ensure no pixels will be drawn outside the cells in the row.
|
||||
* @param terminal The terminal.
|
||||
* @param y The row to clip.
|
||||
*/
|
||||
private _clipRow(terminal: Terminal, y: number): void {
|
||||
this._ctx.beginPath();
|
||||
this._ctx.rect(
|
||||
0,
|
||||
y * this._scaledCellHeight,
|
||||
terminal.cols * this._scaledCellWidth,
|
||||
this._scaledCellHeight);
|
||||
this._ctx.clip();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current font.
|
||||
* @param terminal The terminal.
|
||||
* @param isBold If we should use the bold fontWeight.
|
||||
*/
|
||||
protected _getFont(terminal: Terminal, isBold: boolean, isItalic: boolean): string {
|
||||
const fontWeight = isBold ? terminal.getOption('fontWeightBold') : terminal.getOption('fontWeight');
|
||||
const fontStyle = isItalic ? 'italic' : '';
|
||||
|
||||
return `${fontStyle} ${fontWeight} ${terminal.getOption('fontSize') * window.devicePixelRatio}px ${terminal.getOption('fontFamily')}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { IDisposable, Terminal } from 'xterm';
|
||||
import { IColorSet } from 'browser/Types';
|
||||
import { IRenderDimensions } from 'browser/renderer/Types';
|
||||
|
||||
export interface IRenderLayer extends IDisposable {
|
||||
/**
|
||||
* Called when the terminal loses focus.
|
||||
*/
|
||||
onBlur(terminal: Terminal): void;
|
||||
|
||||
/**
|
||||
* * Called when the terminal gets focus.
|
||||
*/
|
||||
onFocus(terminal: Terminal): void;
|
||||
|
||||
/**
|
||||
* Called when the cursor is moved.
|
||||
*/
|
||||
onCursorMove(terminal: Terminal): void;
|
||||
|
||||
/**
|
||||
* Called when options change.
|
||||
*/
|
||||
onOptionsChanged(terminal: Terminal): void;
|
||||
|
||||
/**
|
||||
* Called when the theme changes.
|
||||
*/
|
||||
setColors(terminal: Terminal, colorSet: IColorSet): void;
|
||||
|
||||
/**
|
||||
* Called when the data in the grid has changed (or needs to be rendered
|
||||
* again).
|
||||
*/
|
||||
onGridChanged(terminal: Terminal, startRow: number, endRow: number): void;
|
||||
|
||||
/**
|
||||
* Calls when the selection changes.
|
||||
*/
|
||||
onSelectionChanged(terminal: Terminal, start: [number, number], end: [number, number], columnSelectMode: boolean): void;
|
||||
|
||||
/**
|
||||
* Registers a handler to join characters to render as a group
|
||||
*/
|
||||
registerCharacterJoiner?(handler: (text: string) => [number, number][]): void;
|
||||
|
||||
/**
|
||||
* Deregisters the specified character joiner handler
|
||||
*/
|
||||
deregisterCharacterJoiner?(joinerId: number): void;
|
||||
|
||||
/**
|
||||
* Resize the render layer.
|
||||
*/
|
||||
resize(terminal: Terminal, dim: IRenderDimensions): void;
|
||||
|
||||
/**
|
||||
* Clear the state of the render layer.
|
||||
*/
|
||||
reset(terminal: Terminal): void;
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es5",
|
||||
"lib": [
|
||||
"dom",
|
||||
"es6",
|
||||
],
|
||||
"rootDir": ".",
|
||||
"outDir": "../out",
|
||||
"sourceMap": true,
|
||||
"removeComments": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"common/*": [
|
||||
"../../../src/common/*"
|
||||
],
|
||||
"browser/*": [
|
||||
"../../../src/browser/*"
|
||||
]
|
||||
},
|
||||
"strict": true,
|
||||
"types": [
|
||||
"../../../node_modules/@types/mocha",
|
||||
"../../../out-test/api/TestUtils"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"./**/*",
|
||||
"../../../typings/xterm.d.ts"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../../src/common"
|
||||
},
|
||||
{
|
||||
"path": "../../../src/browser"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { Terminal, IDisposable, ITerminalAddon } from 'xterm';
|
||||
|
||||
declare module 'xterm-addon-webgl' {
|
||||
/**
|
||||
* An xterm.js addon that provides search functionality.
|
||||
*/
|
||||
export class WebglAddon implements ITerminalAddon {
|
||||
public textureAtlas?: HTMLCanvasElement;
|
||||
|
||||
constructor(preserveDrawingBuffer?: boolean);
|
||||
|
||||
/**
|
||||
* Activates the addon
|
||||
* @param terminal The terminal the addon is being loaded in.
|
||||
*/
|
||||
public activate(terminal: Terminal): void;
|
||||
|
||||
/**
|
||||
* Disposes the addon.
|
||||
*/
|
||||
public dispose(): void;
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
|
||||
const addonName = 'WebglAddon';
|
||||
const mainFile = 'xterm-addon-webgl.js';
|
||||
|
||||
module.exports = {
|
||||
entry: `./out/${addonName}.js`,
|
||||
devtool: 'source-map',
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
use: ["source-map-loader"],
|
||||
enforce: "pre",
|
||||
exclude: /node_modules/
|
||||
}
|
||||
]
|
||||
},
|
||||
resolve: {
|
||||
modules: ['./node_modules'],
|
||||
extensions: [ '.js' ],
|
||||
alias: {
|
||||
common: path.resolve('../../out/common'),
|
||||
browser: path.resolve('../../out/browser')
|
||||
}
|
||||
},
|
||||
output: {
|
||||
filename: mainFile,
|
||||
path: path.resolve('./lib'),
|
||||
library: addonName,
|
||||
libraryTarget: 'umd'
|
||||
},
|
||||
mode: 'production'
|
||||
};
|
||||
+42
-119
@@ -6,56 +6,48 @@
|
||||
jobs:
|
||||
- job: Linux
|
||||
pool:
|
||||
vmImage: 'ubuntu-18.04'
|
||||
vmImage: 'ubuntu-16.04'
|
||||
steps:
|
||||
- task: NodeTool@0
|
||||
inputs:
|
||||
versionSpec: '10.x'
|
||||
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
|
||||
EXIT_CODE=$?
|
||||
./node_modules/.bin/nyc report --reporter=cobertura
|
||||
exit $EXIT_CODE
|
||||
displayName: 'Unit tests'
|
||||
- script: yarn lint
|
||||
yarn mocha
|
||||
displayName: 'Test'
|
||||
- script: |
|
||||
yarn lint
|
||||
displayName: 'Lint'
|
||||
- task: PublishCodeCoverageResults@1
|
||||
inputs:
|
||||
codeCoverageTool: Cobertura
|
||||
summaryFileLocation: '$(System.DefaultWorkingDirectory)/**/*coverage.xml'
|
||||
displayName: 'Publish coverage'
|
||||
|
||||
- job: macOS
|
||||
pool:
|
||||
vmImage: 'macOS-10.15'
|
||||
vmImage: 'xcode9-macos10.13'
|
||||
steps:
|
||||
- task: NodeTool@0
|
||||
inputs:
|
||||
versionSpec: '10.x'
|
||||
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
|
||||
displayName: 'Unit tests'
|
||||
- script: yarn lint
|
||||
- script: |
|
||||
yarn mocha
|
||||
displayName: 'Test'
|
||||
- script: |
|
||||
yarn lint
|
||||
displayName: 'Lint'
|
||||
- script: |
|
||||
yarn test-coverage
|
||||
export COVERALLS_GIT_BRANCH=$BUILD_SOURCEBRANCH
|
||||
yarn coveralls
|
||||
displayName: 'Generate and publish coverage'
|
||||
|
||||
- job: Windows
|
||||
pool:
|
||||
@@ -63,108 +55,39 @@ jobs:
|
||||
steps:
|
||||
- task: NodeTool@0
|
||||
inputs:
|
||||
versionSpec: '10.x'
|
||||
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
|
||||
displayName: 'Unit tests'
|
||||
- script: yarn lint
|
||||
- script: |
|
||||
yarn mocha
|
||||
displayName: 'Test'
|
||||
- script: |
|
||||
yarn lint
|
||||
displayName: 'Lint'
|
||||
|
||||
- job: Linux_IntegrationTests
|
||||
pool:
|
||||
vmImage: 'ubuntu-18.04'
|
||||
steps:
|
||||
- script: |
|
||||
# source: https://github.com/microsoft/playwright/issues/1041
|
||||
sudo apt update
|
||||
sudo apt install libwoff1 libopus0 libwebp6 libwebpdemux2 libenchant1c2a libgudev-1.0-0 libsecret-1-0 libhyphen0 libgdk-pixbuf2.0-0 libegl1 libnotify4 libxslt1.1 libevent-2.1-6 libgles2 libgl1 libegl1 libvpx5
|
||||
# for chromium
|
||||
sudo apt install libnss3 libxss1 libasound2
|
||||
# for firefox
|
||||
sudo apt install libdbus-glib-1-2 libxt6
|
||||
displayName: Install required packages
|
||||
- task: NodeTool@0
|
||||
inputs:
|
||||
versionSpec: '10.x'
|
||||
displayName: 'Install Node.js'
|
||||
- task: YarnInstaller@3
|
||||
inputs:
|
||||
versionSpec: '1.x'
|
||||
displayName: 'Install Yarn'
|
||||
- task: CacheBeta@1
|
||||
inputs:
|
||||
key: yarn2 | $(Agent.OS) | yarn.lock
|
||||
path: node_modules
|
||||
displayName: Cache node modules
|
||||
- script: yarn --frozen-lockfile
|
||||
displayName: 'Install dependencies and build'
|
||||
- script: |
|
||||
yarn start &
|
||||
sleep 10
|
||||
displayName: 'Start test server'
|
||||
- script: yarn test-api-chromium --headless --forbid-only
|
||||
displayName: 'Integration tests (Chromium)'
|
||||
- script: xvfb-run --auto-servernum -- bash -c "yarn test-api-firefox --headless --forbid-only"
|
||||
displayName: 'Integration tests (Firefox)'
|
||||
|
||||
- job: macOS_IntegrationTests
|
||||
pool:
|
||||
vmImage: 'macOS-10.15'
|
||||
steps:
|
||||
- task: NodeTool@0
|
||||
inputs:
|
||||
versionSpec: '10.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
|
||||
displayName: 'Install dependencies and build'
|
||||
- script: |
|
||||
yarn start &
|
||||
sleep 10
|
||||
displayName: 'Start test server'
|
||||
- script: yarn test-api-chromium --headless --forbid-only
|
||||
displayName: 'Integration tests (Chromium)'
|
||||
- script: yarn test-api-firefox --headless --forbid-only
|
||||
displayName: 'Integration tests (Firefox)'
|
||||
- script: yarn test-api-webkit --headless --forbid-only
|
||||
displayName: 'Integration tests (Webkit)'
|
||||
|
||||
- job: Release
|
||||
dependsOn:
|
||||
- Linux
|
||||
- macOS
|
||||
- Windows
|
||||
- Linux_IntegrationTests
|
||||
- macOS_IntegrationTests
|
||||
condition: and(succeeded(), or(eq(variables['Build.SourceBranch'], 'refs/heads/master'), eq(variables['FORCE_RELEASE'], 'true')))
|
||||
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/master'))
|
||||
pool:
|
||||
vmImage: 'ubuntu-16.04'
|
||||
steps:
|
||||
- task: NodeTool@0
|
||||
inputs:
|
||||
versionSpec: '10.x'
|
||||
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,23 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2020 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
const playwright = require('playwright-core');
|
||||
const fs = require('fs');
|
||||
|
||||
// Default to chromium
|
||||
let browserType = playwright['chromium'];
|
||||
const index = process.argv.indexOf('--browser');
|
||||
if (index !== -1 && process.argv.length > index + 1 && typeof process.argv[index + 1] === 'string') {
|
||||
const string = process.argv[index + 1];
|
||||
if (string === 'firefox' || string === 'webkit') {
|
||||
browserType = playwright[string];
|
||||
}
|
||||
}
|
||||
|
||||
const exists = fs.existsSync(browserType.executablePath());
|
||||
if (!exists) {
|
||||
console.log(`Downloading ${browserType.name()}`);
|
||||
browserType.downloadBrowserIfNeeded().then(() => process.exit(0));
|
||||
}
|
||||
@@ -1,509 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*
|
||||
* Script to extract vt features documented in docstrings.
|
||||
*/
|
||||
const fs = require('fs');
|
||||
const Mustache = require('mustache');
|
||||
|
||||
/**
|
||||
* regexp to fetch all comments
|
||||
* Fetches all multiline comments and single lines containing '// @vt:'.
|
||||
*/
|
||||
const REX_COMMENTS = /^\s*?[/][*][*]([\s\S]*?)[*][/]|^\s*?\/\/ ([@]vt[:].*?)$/mug;
|
||||
|
||||
/**
|
||||
* regexp to parse the @vt line
|
||||
* expected data - "@vt: <status> <kind> <mnemonic> "<name>" "<sequence>" "<short description>"
|
||||
*/
|
||||
const REX_VT_LINE = /^[@]vt\:\s*(\w+|#\w+|#\w+\[.*?\])\s*(\w+)\s*(\w+)\s*"(.*?)"\s*"(.*?)"\s*"(.*?)".*$/;
|
||||
|
||||
// known vt command types
|
||||
const TYPES = [
|
||||
'C0',
|
||||
'C1',
|
||||
'ESC',
|
||||
'CSI',
|
||||
'DCS',
|
||||
'OSC',
|
||||
'APC',
|
||||
'PM',
|
||||
'SOS'
|
||||
];
|
||||
|
||||
const MARKDOWN_TMPL = `---
|
||||
title: Supported Terminal Sequences
|
||||
category: API
|
||||
---
|
||||
|
||||
{::options parse_block_html="true" /}
|
||||
|
||||
# Supported Terminal Sequences
|
||||
|
||||
xterm.js version: {{version}}
|
||||
|
||||
## Table of Contents
|
||||
|
||||
<nav>
|
||||
|
||||
- [General notes](#general-notes)
|
||||
{{#C0.length}}
|
||||
- [C0](#c0)
|
||||
{{/C0.length}}
|
||||
{{#C1.length}}
|
||||
- [C1](#c1)
|
||||
{{/C1.length}}
|
||||
{{#CSI.length}}
|
||||
- [CSI](#csi)
|
||||
{{/CSI.length}}
|
||||
{{#DCS.length}}
|
||||
- [DCS](#dcs)
|
||||
{{/DCS.length}}
|
||||
{{#ESC.length}}
|
||||
- [ESC](#esc)
|
||||
{{/ESC.length}}
|
||||
{{#OSC.length}}
|
||||
- [OSC](#osc)
|
||||
{{/OSC.length}}
|
||||
|
||||
</nav>
|
||||
|
||||
|
||||
## General notes
|
||||
|
||||
This document lists xterm.js' support of terminal sequences. The sequences are grouped by their sequence type:
|
||||
|
||||
- C0: single byte command (7bit control codes, byte range \`\\x00\` .. \`\\x1F\`, \`\\x7F\`)
|
||||
- C1: single byte command (8bit control codes, byte range \`\\x80\` .. \`\\x9F\`)
|
||||
- ESC: sequence starting with \`ESC\` (\`\\x1B\`)
|
||||
- CSI - Control Sequence Introducer: sequence starting with \`ESC [\` (7bit) or CSI (\`\\x9B\`, 8bit)
|
||||
- DCS - Device Control String: sequence starting with \`ESC P\` (7bit) or DCS (\`\\x90\`, 8bit)
|
||||
- OSC - Operating System Command: sequence starting with \`ESC ]\` (7bit) or OSC (\`\\x9D\`, 8bit)
|
||||
|
||||
Application Program Command (APC), Privacy Message (PM) and Start of String (SOS) are recognized but not supported,
|
||||
any sequence of these types will be ignored. They are also not hookable by the API.
|
||||
|
||||
Note that the list only contains sequences implemented in xterm.js' core codebase. Missing sequences are either
|
||||
not supported or unstable/experimental. Furthermore addons or integrations can provide additional custom sequences.
|
||||
|
||||
To denote the sequences the tables use the same abbreviations as xterm does:
|
||||
- \`Ps\`: A single (usually optional) numeric parameter, composed of one or more decimal digits.
|
||||
- \`Pm\`: A multiple numeric parameter composed of any number of single numeric parameters, separated by ; character(s),
|
||||
e.g. \` Ps ; Ps ; ... \`.
|
||||
- \`Pt\`: A text parameter composed of printable characters. Note that for most commands with \`Pt\` only
|
||||
ASCII printables are specified to work. Additionally the parser will let pass any codepoint greater than C1 as printable.
|
||||
|
||||
|
||||
|
||||
{{#C0.length}}
|
||||
## C0
|
||||
|
||||
| Mnemonic | Name | Sequence | Short Description | Support |
|
||||
| -------- | ---- | -------- | ----------------- | ------- |
|
||||
{{#C0}}
|
||||
| {{mnemonic}} | {{name}} | \`{{sequence}}\` | {{{shortDescription}}} {{#longDescription.length}}_[more](#{{longTarget}}){: .link-details}_{{/longDescription.length}} | {{{status}}} |
|
||||
{{/C0}}
|
||||
|
||||
{{#C0.hasLongDescriptions}}
|
||||
{{#C0}}
|
||||
{{#longDescription.length}}
|
||||
<section class="sequence-details">
|
||||
|
||||
### {{name}}
|
||||
{{#longDescription}}
|
||||
{{{.}}}
|
||||
{{/longDescription}}
|
||||
|
||||
</section>
|
||||
{{/longDescription.length}}
|
||||
{{/C0}}
|
||||
{{/C0.hasLongDescriptions}}
|
||||
|
||||
{{/C0.length}}
|
||||
|
||||
|
||||
{{#C1.length}}
|
||||
## C1
|
||||
|
||||
| Mnemonic | Name | Sequence | Short Description | Support |
|
||||
| -------- | ---- | -------- | ----------------- | ------- |
|
||||
{{#C1}}
|
||||
| {{mnemonic}} | {{name}} | \`{{sequence}}\` | {{{shortDescription}}} {{#longDescription.length}}_[more](#{{longTarget}}){: .link-details}_{{/longDescription.length}} | {{{status}}} |
|
||||
{{/C1}}
|
||||
|
||||
{{#C1.hasLongDescriptions}}
|
||||
{{#C1}}
|
||||
{{#longDescription.length}}
|
||||
<section class="sequence-details">
|
||||
|
||||
### {{name}}
|
||||
{{#longDescription}}
|
||||
{{{.}}}
|
||||
{{/longDescription}}
|
||||
|
||||
</section>
|
||||
{{/longDescription.length}}
|
||||
{{/C1}}
|
||||
{{/C1.hasLongDescriptions}}
|
||||
|
||||
{{/C1.length}}
|
||||
|
||||
|
||||
{{#CSI.length}}
|
||||
## CSI
|
||||
|
||||
| Mnemonic | Name | Sequence | Short Description | Support |
|
||||
| -------- | ---- | -------- | ----------------- | ------- |
|
||||
{{#CSI}}
|
||||
| {{mnemonic}} | {{name}} | \`\`{{{sequence}}}\`\` | {{{shortDescription}}} {{#longDescription.length}}_[more](#{{longTarget}}){: .link-details}_{{/longDescription.length}} | {{{status}}} |
|
||||
{{/CSI}}
|
||||
|
||||
{{#CSI.hasLongDescriptions}}
|
||||
{{#CSI}}
|
||||
{{#longDescription.length}}
|
||||
<section class="sequence-details">
|
||||
|
||||
### {{name}}
|
||||
{{#longDescription}}
|
||||
{{{.}}}
|
||||
{{/longDescription}}
|
||||
|
||||
</section>
|
||||
{{/longDescription.length}}
|
||||
{{/CSI}}
|
||||
{{/CSI.hasLongDescriptions}}
|
||||
|
||||
{{/CSI.length}}
|
||||
|
||||
|
||||
{{#DCS.length}}
|
||||
## DCS
|
||||
|
||||
| Mnemonic | Name | Sequence | Short Description | Support |
|
||||
| -------- | ---- | -------- | ----------------- | ------- |
|
||||
{{#DCS}}
|
||||
| {{mnemonic}} | {{name}} | \`{{sequence}}\` | {{{shortDescription}}} {{#longDescription.length}}_[more](#{{longTarget}}){: .link-details}_{{/longDescription.length}} | {{{status}}} |
|
||||
{{/DCS}}
|
||||
|
||||
{{#DCS.hasLongDescriptions}}
|
||||
{{#DCS}}
|
||||
{{#longDescription.length}}
|
||||
<section class="sequence-details">
|
||||
|
||||
### {{name}}
|
||||
{{#longDescription}}
|
||||
{{{.}}}
|
||||
{{/longDescription}}
|
||||
|
||||
</section>
|
||||
{{/longDescription.length}}
|
||||
{{/DCS}}
|
||||
{{/DCS.hasLongDescriptions}}
|
||||
|
||||
{{/DCS.length}}
|
||||
|
||||
|
||||
{{#ESC.length}}
|
||||
## ESC
|
||||
|
||||
| Mnemonic | Name | Sequence | Short Description | Support |
|
||||
| -------- | ---- | -------- | ----------------- | ------- |
|
||||
{{#ESC}}
|
||||
| {{mnemonic}} | {{name}} | \`{{sequence}}\` | {{{shortDescription}}} {{#longDescription.length}}_[more](#{{longTarget}}){: .link-details}_{{/longDescription.length}} | {{{status}}} |
|
||||
{{/ESC}}
|
||||
|
||||
{{#ESC.hasLongDescriptions}}
|
||||
{{#ESC}}
|
||||
{{#longDescription.length}}
|
||||
<section class="sequence-details">
|
||||
|
||||
### {{name}}
|
||||
{{#longDescription}}
|
||||
{{{.}}}
|
||||
{{/longDescription}}
|
||||
|
||||
</section>
|
||||
{{/longDescription.length}}
|
||||
{{/ESC}}
|
||||
{{/ESC.hasLongDescriptions}}
|
||||
|
||||
{{/ESC.length}}
|
||||
|
||||
|
||||
{{#OSC.length}}
|
||||
## OSC
|
||||
|
||||
**Note**: Other than listed in the table, the parser recognizes both ST (ECMA-48) and BEL (xterm) as OSC sequence finalizer.
|
||||
|
||||
| Identifier | Sequence | Short Description | Support |
|
||||
| ---------- | -------- | ----------------- | ------- |
|
||||
{{#OSC}}
|
||||
| {{mnemonic}} | \`{{sequence}}\` | {{{shortDescription}}} {{#longDescription.length}}_[more](#{{longTarget}}){: .link-details}_{{/longDescription.length}} | {{{status}}} |
|
||||
{{/OSC}}
|
||||
|
||||
{{#OSC.hasLongDescriptions}}
|
||||
{{#OSC}}
|
||||
{{#longDescription.length}}
|
||||
<section class="sequence-details">
|
||||
|
||||
### {{name}}
|
||||
{{#longDescription}}
|
||||
{{{.}}}
|
||||
{{/longDescription}}
|
||||
|
||||
</section>
|
||||
{{/longDescription.length}}
|
||||
{{/OSC}}
|
||||
{{/OSC.hasLongDescriptions}}
|
||||
|
||||
{{/OSC.length}}
|
||||
|
||||
<script type="text/javascript">
|
||||
const linkStates = {};
|
||||
|
||||
function hideDetailSections() {
|
||||
for (let section of document.getElementsByClassName('sequence-details')) section.style.display = 'none';
|
||||
}
|
||||
|
||||
function decorateDetailLinks() {
|
||||
for (let link of document.getElementsByClassName('link-details')) {
|
||||
link.addEventListener("click", toggleDetails, false);
|
||||
if (linkStates[link.href] === undefined) {
|
||||
linkStates[link.href] = {open: false, links: [], trElem: null};
|
||||
}
|
||||
linkStates[link.href].links.push(link);
|
||||
}
|
||||
}
|
||||
|
||||
function openDetails(link) {
|
||||
const closestTr = link.closest('tr');
|
||||
if (!closestTr || !closestTr.parentNode) return;
|
||||
const closestSection = document.getElementById(link.href.split('#')[1]).closest('section');
|
||||
if (!closestSection || !closestSection.innerHTML) return;
|
||||
const content = closestSection.innerHTML.split('</h3>')[1];
|
||||
|
||||
const newRow = closestTr.parentNode.insertRow(closestTr.rowIndex);
|
||||
const cell = newRow.insertCell(0);
|
||||
cell.innerHTML = '<div style="font-style: italic">' + content + '</div>';
|
||||
cell.colSpan = closestTr.children.length;
|
||||
const state = linkStates[link.href];
|
||||
state.trElem = newRow;
|
||||
state.open = true;
|
||||
if (link.innerHTML === 'more') link.innerHTML = 'less';
|
||||
}
|
||||
|
||||
function closeDetails(link) {
|
||||
const state = linkStates[link.href];
|
||||
if (state.trElem && state.trElem.rowIndex !== -1) {
|
||||
if (state.trElem.parentNode) state.trElem.parentNode.deleteRow(state.trElem.rowIndex-1); // why -1 here?
|
||||
state.trElem.remove();
|
||||
state.trElem = null;
|
||||
}
|
||||
state.open = false;
|
||||
if (link.innerHTML === 'less') link.innerHTML = 'more';
|
||||
}
|
||||
|
||||
function toggleDetails(ev) {
|
||||
if (!ev.target || !ev.target.href) return;
|
||||
const isOpen = linkStates[ev.target.href].open;
|
||||
if (isOpen === undefined) return;
|
||||
if (isOpen) closeDetails(ev.target);
|
||||
else openDetails(ev.target);
|
||||
ev.preventDefault();
|
||||
return false;
|
||||
}
|
||||
|
||||
function load() {
|
||||
hideDetailSections();
|
||||
decorateDetailLinks();
|
||||
}
|
||||
load(); // we are inlined behind all needed data, thus dont wait for onload (avoids flickering)
|
||||
</script>
|
||||
`
|
||||
|
||||
// support status marcos
|
||||
// applied for:
|
||||
// - status field of single @vt line
|
||||
// - all lines in long description
|
||||
const MACRO = [
|
||||
// #Y - supported
|
||||
[/#Y/g, s => '<span title="supported">✓</span>'],
|
||||
// #N - unsupported
|
||||
[/#N/g, s => '<span title="unsupported">✗</span>'],
|
||||
// #P[reason] - partial support with a reason as title
|
||||
[/#P\[(.*?)\]/g, (s, p1) => `<span title="${p1}" style="text-decoration: underline">Partial</span>`],
|
||||
// #B[reason] - supported but broken in a certain way, reason in title
|
||||
[/#B\[(.*?)\]/g, (s, p1) => `<span title="${p1}" style="text-decoration: underline">Broken</span>`]
|
||||
];
|
||||
|
||||
function applyMacros(s) {
|
||||
for (let i = 0; i < MACRO.length; ++i) {
|
||||
s = s.replace(MACRO[i][0], MACRO[i][1]);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
function replaceStatus(s) {
|
||||
if (s === 'supported') return '<span title="supported">✓</span>';
|
||||
if (s === 'unsupported') return '<span title="unsupported">✗</span>';
|
||||
return s;
|
||||
}
|
||||
|
||||
function createAnchorSlug(s) {
|
||||
return s.toLowerCase().split(' ').join('-');
|
||||
}
|
||||
|
||||
function empty(ar) {
|
||||
return !ar.filter(Boolean, ar).length;
|
||||
}
|
||||
|
||||
function* parseMultiLineGen(filename, s) {
|
||||
if (!~s.indexOf('@vt:')) {
|
||||
return;
|
||||
}
|
||||
const lines = s.split('\n').map(el => el.trim().replace(/[*]/, '').replace(/\s/, ''));
|
||||
let grabLine = false;
|
||||
let longDescription = [];
|
||||
let feature = undefined;
|
||||
let noLineCount = 0;
|
||||
for (const line of lines) {
|
||||
if (grabLine) {
|
||||
if (!line) noLineCount++;
|
||||
if (noLineCount >= 2) {
|
||||
if (feature) {
|
||||
feature.longDescription = empty(longDescription) ? [] : longDescription;
|
||||
feature.longTarget = createAnchorSlug(feature.name);
|
||||
yield feature;
|
||||
}
|
||||
grabLine = false;
|
||||
longDescription = [];
|
||||
feature = undefined;
|
||||
noLineCount = 0;
|
||||
}
|
||||
else if (line.indexOf('@vt:') === 0) {
|
||||
if (feature) {
|
||||
feature.longDescription = empty(longDescription) ? [] : longDescription;
|
||||
feature.longTarget = createAnchorSlug(feature.name);
|
||||
yield feature;
|
||||
}
|
||||
grabLine = true;
|
||||
longDescription = [];
|
||||
feature = undefined;
|
||||
noLineCount = 0;
|
||||
} else {
|
||||
//longDescription.push(line);
|
||||
longDescription.push(applyMacros(line));
|
||||
if (line) noLineCount = 0;
|
||||
}
|
||||
}
|
||||
if (line.indexOf('@vt:') === 0) {
|
||||
feature = parseSingleLine(filename, line);
|
||||
grabLine = true;
|
||||
}
|
||||
}
|
||||
if (grabLine && feature) {
|
||||
feature.longDescription = empty(longDescription) ? [] : longDescription;
|
||||
feature.longTarget = createAnchorSlug(feature.name);
|
||||
yield feature;
|
||||
}
|
||||
}
|
||||
|
||||
function parseSingleLine(filename, s) {
|
||||
const line = s.trim();
|
||||
const match = line.match(REX_VT_LINE);
|
||||
if (match !== null) {
|
||||
if (!~TYPES.indexOf(match[2])) {
|
||||
throw new Error(`unkown vt-command type "${match[2]}" specified in "${filename}"`);
|
||||
}
|
||||
return {
|
||||
status: match[1],
|
||||
type: match[2],
|
||||
mnemonic: match[3],
|
||||
name: match[4],
|
||||
sequence: match[5],
|
||||
shortDescription: match[6],
|
||||
longDescription: [],
|
||||
longTarget: '',
|
||||
source: filename
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function getSorter(entry) {
|
||||
switch (entry) {
|
||||
case 'C0':
|
||||
case 'C1':
|
||||
// NOTE: expects hex value notation at last position in sequence
|
||||
return (a, b) => parseInt(a.sequence.slice(-2), 16) - parseInt(b.sequence.slice(-2), 16);
|
||||
case 'OSC':
|
||||
// NOTE: expects the decimal function identifier in mnemonic
|
||||
return (a, b) => parseInt(a.mnemonic) - parseInt(b.mnemonic);
|
||||
case 'DCS':
|
||||
return (a, b) => a.mnemonic > b.mnemonic;
|
||||
case 'CSI':
|
||||
case 'ESC':
|
||||
// default sort order by final byte
|
||||
return (a, b) => {
|
||||
// ugly hack to fix sorting of workaround in HPA sequence "CSI Ps ` "
|
||||
// for HPA compare with length - 2 instead
|
||||
const HPA = 'CSI Ps ` ';
|
||||
const aa = a.sequence === HPA ? a.sequence.slice(0, -1) : a.sequence;
|
||||
const bb = b.sequence === HPA ? b.sequence.slice(0, -1) : b.sequence;
|
||||
return aa.charCodeAt(aa.length - 1) - bb.charCodeAt(bb.length - 1);
|
||||
};
|
||||
default:
|
||||
return (a, b) => a.sequence > b.sequence;
|
||||
}
|
||||
};
|
||||
|
||||
function postProcessData(features) {
|
||||
const featureTable = {};
|
||||
for (const feature of features) {
|
||||
// feature.status = replaceStatus(feature.status);
|
||||
feature.status = applyMacros(feature.status);
|
||||
if (featureTable[feature.type] === undefined) {
|
||||
featureTable[feature.type] = [];
|
||||
}
|
||||
featureTable[feature.type].push(feature);
|
||||
if (feature.longDescription.length) {
|
||||
featureTable[feature.type].hasLongDescriptions = true;
|
||||
}
|
||||
}
|
||||
for (const entry in featureTable) {
|
||||
featureTable[entry].sort(getSorter(entry));
|
||||
}
|
||||
// console.error(featureTable);
|
||||
featureTable.version = require('../package.json').version;
|
||||
console.log(Mustache.render(MARKDOWN_TMPL, featureTable));
|
||||
}
|
||||
|
||||
function main(filenames) {
|
||||
// console.error(filenames);
|
||||
let leftToProcess = filenames.length;
|
||||
const features = [];
|
||||
for (const filename of filenames) {
|
||||
fs.readFile(filename, 'utf-8', (err, data) => {
|
||||
let match;
|
||||
while ((match = REX_COMMENTS.exec(data)) !== null) {
|
||||
if (match.index === REX_COMMENTS.lastIndex) {
|
||||
REX_COMMENTS.lastIndex++;
|
||||
}
|
||||
if (match[1]) {
|
||||
for (let feature of parseMultiLineGen(filename, match[1])) {
|
||||
if (feature) features.push(feature);
|
||||
}
|
||||
} else {
|
||||
const feature = parseSingleLine(filename, match[2]);
|
||||
if (feature) features.push(feature);
|
||||
}
|
||||
}
|
||||
leftToProcess--;
|
||||
if (!leftToProcess) {
|
||||
postProcessData(features);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
main(process.argv.slice(2))
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
Executable
+24
@@ -0,0 +1,24 @@
|
||||
#! /usr/bin/env sh
|
||||
|
||||
# Usage: ./bin/prepare-release x.y.z
|
||||
# x.y.z should be semver (e.g. 1.0.0)
|
||||
|
||||
set -e
|
||||
|
||||
NEW_VERSION=$1
|
||||
CURRENT_PACKAGE_JSON_VERSION=$(cat package.json \
|
||||
| grep version \
|
||||
| head -1 \
|
||||
| awk -F: '{ print $2 }' \
|
||||
| sed 's/[",]//g' \
|
||||
| tr -d '[[:space:]]')
|
||||
|
||||
# Build xterm.js into `dist`
|
||||
export BUILD_DIR=dist
|
||||
npm run build
|
||||
|
||||
# Update version in package.json
|
||||
sed -i "s/\"version\": \"$CURRENT_PACKAGE_JSON_VERSION\"/\"version\": \"$NEW_VERSION\"/g" package.json
|
||||
|
||||
git commit -S -s -a -m "Bump version to $NEW_VERSION"
|
||||
git tag $NEW_VERSION
|
||||
+28
-102
@@ -5,101 +5,44 @@
|
||||
|
||||
const cp = require('child_process');
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
const packageJson = require('../package.json');
|
||||
|
||||
// Setup auth
|
||||
fs.writeFileSync(`${process.env['HOME']}/.npmrc`, `//registry.npmjs.org/:_authToken=${process.env['NPM_AUTH_TOKEN']}`);
|
||||
|
||||
const isDryRun = process.argv.indexOf('--dry') !== -1;
|
||||
if (isDryRun) {
|
||||
console.log('Publish dry run');
|
||||
// Determine if this is a stable or beta release
|
||||
const publishedVersions = getPublishedVersions();
|
||||
const isStableRelease = publishedVersions.indexOf(packageJson.version) === -1;
|
||||
|
||||
// Get the next version
|
||||
let nextVersion = isStableRelease ? packageJson.version : getNextBetaVersion();
|
||||
console.log(`Publishing version: ${nextVersion}`);
|
||||
|
||||
// Set the version in package.json
|
||||
const packageJsonFile = path.resolve(__dirname, '..', 'package.json');
|
||||
packageJson.version = nextVersion;
|
||||
fs.writeFileSync(packageJsonFile, JSON.stringify(packageJson, null, 2));
|
||||
|
||||
// Publish
|
||||
const args = ['publish'];
|
||||
if (!isStableRelease) {
|
||||
args.push('--tag', 'beta');
|
||||
}
|
||||
const result = cp.spawn('npm', args, { stdio: 'inherit' });
|
||||
result.on('exit', code => process.exit(code));
|
||||
|
||||
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, '..'));
|
||||
}
|
||||
|
||||
// Publish addons if any files were changed inside of the addon
|
||||
const addonPackageDirs = [
|
||||
path.resolve(__dirname, '../addons/xterm-addon-attach'),
|
||||
path.resolve(__dirname, '../addons/xterm-addon-fit'),
|
||||
path.resolve(__dirname, '../addons/xterm-addon-search'),
|
||||
path.resolve(__dirname, '../addons/xterm-addon-serialize'),
|
||||
path.resolve(__dirname, '../addons/xterm-addon-unicode11'),
|
||||
path.resolve(__dirname, '../addons/xterm-addon-web-links'),
|
||||
path.resolve(__dirname, '../addons/xterm-addon-webgl')
|
||||
];
|
||||
console.log(`Checking if addons need to be published`);
|
||||
addonPackageDirs.forEach(p => {
|
||||
const addon = path.basename(p);
|
||||
if (changedFiles.some(e => e.indexOf(addon) !== -1)) {
|
||||
console.log(`Try publish ${addon}`);
|
||||
checkAndPublishPackage(p);
|
||||
}
|
||||
});
|
||||
|
||||
// Publish website if it's a stable release
|
||||
if (isStableRelease) {
|
||||
updateWebsite();
|
||||
}
|
||||
|
||||
function checkAndPublishPackage(packageDir) {
|
||||
const packageJson = require(path.join(packageDir, 'package.json'));
|
||||
|
||||
// Determine if this is a stable or beta release
|
||||
const publishedVersions = getPublishedVersions(packageJson);
|
||||
const isStableRelease = publishedVersions.indexOf(packageJson.version) === -1;
|
||||
|
||||
// Get the next version
|
||||
let nextVersion = isStableRelease ? packageJson.version : getNextBetaVersion(packageJson);
|
||||
console.log(`Publishing version: ${nextVersion}`);
|
||||
|
||||
// Set the version in package.json
|
||||
const packageJsonFile = path.join(packageDir, 'package.json');
|
||||
packageJson.version = nextVersion;
|
||||
console.log(`Set version of ${packageJsonFile} to ${nextVersion}`);
|
||||
if (!isDryRun) {
|
||||
fs.writeFileSync(packageJsonFile, JSON.stringify(packageJson, null, 2));
|
||||
}
|
||||
|
||||
// Publish
|
||||
const args = ['publish'];
|
||||
if (!isStableRelease) {
|
||||
args.push('--tag', 'beta');
|
||||
}
|
||||
console.log(`Spawn: npm ${args.join(' ')}`);
|
||||
if (!isDryRun) {
|
||||
const result = cp.spawnSync('npm', args, {
|
||||
cwd: packageDir,
|
||||
stdio: 'inherit'
|
||||
});
|
||||
if (result.status) {
|
||||
console.error(`Spawn exited with code ${result.status}`);
|
||||
process.exit(result.status);
|
||||
}
|
||||
}
|
||||
|
||||
console.groupEnd();
|
||||
|
||||
return isStableRelease;
|
||||
}
|
||||
|
||||
function getNextBetaVersion(packageJson) {
|
||||
function getNextBetaVersion() {
|
||||
if (!/^[0-9]+\.[0-9]+\.[0-9]+$/.exec(packageJson.version)) {
|
||||
console.error('The package.json version must be of the form x.y.z');
|
||||
process.exit(1);
|
||||
}
|
||||
const tag = 'beta';
|
||||
const stableVersion = packageJson.version.split('.');
|
||||
const nextStableVersion = `${stableVersion[0]}.${parseInt(stableVersion[1]) + 1}.0`;
|
||||
const publishedVersions = getPublishedVersions(packageJson, nextStableVersion, tag);
|
||||
const nextStableVersion = `${stableVersion[0]}.${parseInt(stableVersion[1]) + 1}.${stableVersion[2]}`;
|
||||
const publishedVersions = getPublishedVersions(nextStableVersion, tag);
|
||||
if (publishedVersions.length === 0) {
|
||||
return `${nextStableVersion}-${tag}.1`;
|
||||
return `${packageJson.version}-${tag}1`;
|
||||
}
|
||||
const latestPublishedVersion = publishedVersions.sort((a, b) => {
|
||||
const aVersion = parseInt(a.substr(a.search(/[0-9]+$/)));
|
||||
@@ -107,31 +50,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']);
|
||||
function getPublishedVersions(version, tag) {
|
||||
const versionsProcess = cp.spawnSync('npm', ['view', 'xterm', '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;
|
||||
}
|
||||
|
||||
function getChangedFilesInCommit(commit) {
|
||||
const args = ['log', '-m', '-1', '--name-only', `--pretty=format:`, commit];
|
||||
const result = cp.spawnSync('git', args);
|
||||
const output = result.stdout.toString();
|
||||
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] });
|
||||
}
|
||||
}
|
||||
|
||||
Executable
+22
@@ -0,0 +1,22 @@
|
||||
#! /usr/bin/env sh
|
||||
|
||||
# Usage: ./bin/release x.y.z
|
||||
# x.y.z should be semver (e.g. 1.0.0)
|
||||
|
||||
set -e
|
||||
|
||||
if [ -z "$1" ];
|
||||
then
|
||||
echo "No version supplied. Please a version argument\n"
|
||||
echo "Usage: $0 VERSION\n"
|
||||
echo "Example: $0 1.0.0"
|
||||
exit
|
||||
fi
|
||||
|
||||
NEW_VERSION=$1
|
||||
|
||||
./bin/prepare-release $NEW_VERSION
|
||||
|
||||
git push && \
|
||||
git push --tags && \
|
||||
npm publish
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user