Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d2b3af5aa8 | ||
|
|
5f7eb6e51d |
+19
@@ -0,0 +1,19 @@
|
|||||||
|
language: node_js
|
||||||
|
os:
|
||||||
|
- linux
|
||||||
|
node_js:
|
||||||
|
- 6
|
||||||
|
before_install:
|
||||||
|
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test ; fi
|
||||||
|
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get -qq update ; fi
|
||||||
|
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get -qq install g++-4.8 ; fi
|
||||||
|
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then export CXX=g++-4.8 ; fi
|
||||||
|
- npm install -g npm@5.1.0
|
||||||
|
env:
|
||||||
|
matrix:
|
||||||
|
- NPM_COMMAND=tsc
|
||||||
|
- NPM_COMMAND=lint
|
||||||
|
- NPM_COMMAND=test-coverage
|
||||||
|
notifications:
|
||||||
|
email: false
|
||||||
|
script: npm run $NPM_COMMAND
|
||||||
@@ -61,7 +61,6 @@ irokas <akasidiari@inf.uth.gr>
|
|||||||
Jakob Gillich <jakob@gillich.me>
|
Jakob Gillich <jakob@gillich.me>
|
||||||
Jan Kuri <jkuri88@gmail.com>
|
Jan Kuri <jkuri88@gmail.com>
|
||||||
Jean Bruenn <himself@jeanbruenn.info>
|
Jean Bruenn <himself@jeanbruenn.info>
|
||||||
Jeff Principe <princjef@gmail.com>
|
|
||||||
Jeremy Danyow <jedanyow@microsoft.com>
|
Jeremy Danyow <jedanyow@microsoft.com>
|
||||||
Jianhui Zhao <jianhuizhao329@gmail.com>
|
Jianhui Zhao <jianhuizhao329@gmail.com>
|
||||||
Joao Moreno <jomo@microsoft.com>
|
Joao Moreno <jomo@microsoft.com>
|
||||||
@@ -71,7 +70,6 @@ Jon Austin <jon.i.austin@gmail.com>
|
|||||||
Jon Kohler <jon@nutanix.com>
|
Jon Kohler <jon@nutanix.com>
|
||||||
Jon Masters <jon.masters@sky.com>
|
Jon Masters <jon.masters@sky.com>
|
||||||
Jörg Breitbart <jerch@rockborn.de>
|
Jörg Breitbart <jerch@rockborn.de>
|
||||||
Jose Anton <roke22@gmail.com>
|
|
||||||
jpoth <poth.john@gmail.com>
|
jpoth <poth.john@gmail.com>
|
||||||
Justin Luk <jluk@users.noreply.github.com>
|
Justin Luk <jluk@users.noreply.github.com>
|
||||||
Justin Mecham <justin@mecham.me>
|
Justin Mecham <justin@mecham.me>
|
||||||
@@ -105,7 +103,6 @@ Peng Xiao <pengxiao@outlook.com>
|
|||||||
Peter Baumgarten <me@peterbaumgarten.com>
|
Peter Baumgarten <me@peterbaumgarten.com>
|
||||||
Philip Olson <philip.olson@protonmail.ch>
|
Philip Olson <philip.olson@protonmail.ch>
|
||||||
PowerHat <31401273+7PH@users.noreply.github.com>
|
PowerHat <31401273+7PH@users.noreply.github.com>
|
||||||
PowerHat <b.raymond@protonmail.com>
|
|
||||||
pro-src <34285059+pro-src@users.noreply.github.com>
|
pro-src <34285059+pro-src@users.noreply.github.com>
|
||||||
pro-src <rodneyd.teal@gmail.com>
|
pro-src <rodneyd.teal@gmail.com>
|
||||||
Rick Baker <rick@ricktbaker.com>
|
Rick Baker <rick@ricktbaker.com>
|
||||||
@@ -114,7 +111,6 @@ Saad Malik <simfox3@gmail.com>
|
|||||||
Samuel Williams <samuel.williams@oriontransfer.co.nz>
|
Samuel Williams <samuel.williams@oriontransfer.co.nz>
|
||||||
Saswat Das <saswatds@users.noreply.github.com>
|
Saswat Das <saswatds@users.noreply.github.com>
|
||||||
Saul Costa <saul@codevolve.com>
|
Saul Costa <saul@codevolve.com>
|
||||||
Segev Finer <segev208@gmail.com>
|
|
||||||
Shuanglei Tao <tsl0922@gmail.com>
|
Shuanglei Tao <tsl0922@gmail.com>
|
||||||
sitzmar <sitzmar@gmail.com>
|
sitzmar <sitzmar@gmail.com>
|
||||||
Steven Silvester <steven.silvester@ieee.org>
|
Steven Silvester <steven.silvester@ieee.org>
|
||||||
|
|||||||
@@ -50,3 +50,7 @@ By contributing code to xterm.js you
|
|||||||
holder has explicitly granted the right to use it like this,
|
holder has explicitly granted the right to use it like this,
|
||||||
through a compatible open source license or through a direct
|
through a compatible open source license or through a direct
|
||||||
agreement with you.)
|
agreement with you.)
|
||||||
|
|
||||||
|
### Third party dependencies
|
||||||
|
|
||||||
|
We prefer to not include any non-dev third party dependencies in order to keep our code minimal, performant and secure. If you plan on adding a dependency on a third party library it's a good idea to discuss the need in an issue with the maintainers first.
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
# [](https://xtermjs.org)
|
# [](https://xtermjs.org)
|
||||||
|
|
||||||
[](https://xtermjs.visualstudio.com/xterm.js/_build/index?definitionId=1)
|
[](https://travis-ci.org/xtermjs/xterm.js) [](https://xtermjs.visualstudio.com/xterm.js/_build/index?definitionId=1) [](https://coveralls.io/github/xtermjs/xterm.js?branch=master) [](https://gitter.im/sourcelair/xterm.js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [](https://www.jsdelivr.com/package/npm/xterm)
|
||||||
[](https://coveralls.io/github/xtermjs/xterm.js?branch=master)
|
|
||||||
[](https://gitter.im/sourcelair/xterm.js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
|
||||||
[](https://www.jsdelivr.com/package/npm/xterm)
|
|
||||||
|
|
||||||
Xterm.js is a terminal front-end component written in JavaScript that works in the browser.
|
Xterm.js is a terminal front-end component written in JavaScript that works in the browser.
|
||||||
|
|
||||||
@@ -60,12 +57,6 @@ The proposed way to load xterm.js is via the ES6 module syntax.
|
|||||||
import { Terminal } from 'xterm';
|
import { Terminal } from 'xterm';
|
||||||
```
|
```
|
||||||
|
|
||||||
### API
|
|
||||||
|
|
||||||
The full API for xterm.js is contained within the [TypeScript declaration file](https://github.com/xtermjs/xterm.js/blob/master/typings/xterm.d.ts), use the branch/tag picker in GitHub (`w`) to navigate to the correct version of the API.
|
|
||||||
|
|
||||||
Note that some APIs are marked *experimental*, these are added so we can experiment with new ideas without committing to support it like a normal semver API. Note that these APIs can change radically between versions so be sure to read release notes if you plan on using experimental APIs.
|
|
||||||
|
|
||||||
### Addons
|
### Addons
|
||||||
|
|
||||||
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 xterm.js' public API.
|
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 xterm.js' public API.
|
||||||
@@ -83,8 +74,6 @@ var xterm = new Terminal(); // Instantiate the terminal
|
|||||||
xterm.fit(); // Use the `fit` method, provided by the `fit` addon
|
xterm.fit(); // Use the `fit` method, provided by the `fit` addon
|
||||||
```
|
```
|
||||||
|
|
||||||
You will also need to include the addon's CSS file if it has one in the folder.
|
|
||||||
|
|
||||||
#### Importing Addons in TypeScript
|
#### 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. `(<any>xterm).fit()`.
|
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. `(<any>xterm).fit()`.
|
||||||
@@ -118,10 +107,6 @@ Since xterm.js is typically implemented as a developer tool, only modern browser
|
|||||||
|
|
||||||
Xterm.js works seamlessly in Electron apps and may even work on earlier versions of the browsers but these are the browsers we strive to keep working.
|
Xterm.js works seamlessly in Electron apps and may even work on earlier versions of the browsers but these are the browsers we strive to keep working.
|
||||||
|
|
||||||
## API
|
|
||||||
|
|
||||||
The current full API documentation is available in the [TypeScript declaration file on the repository](https://github.com/xtermjs/xterm.js/blob/master/typings/xterm.d.ts), switch the tag (press `w` when viewing the file) to point at the specific version tag you're using.
|
|
||||||
|
|
||||||
## Real-world uses
|
## Real-world uses
|
||||||
Xterm.js is used in several world-class applications to provide great terminal experiences.
|
Xterm.js is used in several world-class applications to provide great terminal experiences.
|
||||||
|
|
||||||
@@ -168,21 +153,105 @@ computational environment for Jupyter, supporting interactive data science and s
|
|||||||
- [**genact**](https://github.com/svenstaro/genact): A nonsense activity generator.
|
- [**genact**](https://github.com/svenstaro/genact): A nonsense activity generator.
|
||||||
- [**cPanel & WHM**](https://cpanel.com): The hosting platform of choice.
|
- [**cPanel & WHM**](https://cpanel.com): The hosting platform of choice.
|
||||||
- [**Nutanix**](https://github.com/nutanix): Nutanix Enterprise Cloud uses xterm in the webssh functionality within Nutanix Calm, and is also looking to move our old noserial (termjs) functionality to xterm.js
|
- [**Nutanix**](https://github.com/nutanix): Nutanix Enterprise Cloud uses xterm in the webssh functionality within Nutanix Calm, and is also looking to move our old noserial (termjs) functionality to xterm.js
|
||||||
- [**SSH Web Client**](https://github.com/roke22/PHP-SSH2-Web-Client): SSH Web Client with PHP.
|
|
||||||
|
|
||||||
[And much more...](https://github.com/xtermjs/xterm.js/network/dependents)
|
|
||||||
|
|
||||||
Do you use xterm.js in your application as well? Please [open a Pull Request](https://github.com/sourcelair/xterm.js/pulls) to include it here. We would love to have it in our list. Note: Please add any new contributions to the end of the list only.
|
Do you use xterm.js in your application as well? Please [open a Pull Request](https://github.com/sourcelair/xterm.js/pulls) to include it here. We would love to have it in our list. Note: Please add any new contributions to the end of the list only.
|
||||||
|
|
||||||
|
## Demo
|
||||||
|
|
||||||
|
Xterm.js ships with a barebones demo implementation, designed for the development and evaluation of the library only. Exposing the demo to the public as is would introduce security risks for the host.
|
||||||
|
|
||||||
|
Below you can find instructions on how to run the demo on different platforms.
|
||||||
|
|
||||||
|
### SourceLair
|
||||||
|
|
||||||
|
SourceLair will run the demo and builder in parallel automatically. Just make sure to choose the "Node.js" project type, when cloning the xterm.js repo (or just use this shortcut; https://lair.io/xtermjs/xtermjs).
|
||||||
|
|
||||||
|
Then open your project's [Public URL](https://help.sourcelair.com/projects/the-public-url/) to access the demo.
|
||||||
|
|
||||||
|
### Docker
|
||||||
|
|
||||||
|
First, make sure you have Docker Engine 1.13.0 (or newer) and Docker Compose 1.10.0 (or newer).
|
||||||
|
|
||||||
|
Xterm.js [provides a pre-built Docker image](https://hub.docker.com/r/xtermjs/xterm.js/) to help run the demo easily (Git tags are built as [tagged Docker images](https://hub.docker.com/r/xtermjs/xterm.js/tags/) too).
|
||||||
|
|
||||||
|
To run the just demo (with no editing access). run the following command in your terminal:
|
||||||
|
|
||||||
|
```
|
||||||
|
docker run -p 3000:3000 xtermjs/xterm.js
|
||||||
|
```
|
||||||
|
|
||||||
|
Then open http://0.0.0.0:3000 in a web browser to access the demo.
|
||||||
|
|
||||||
|
To run the demo and builder in parallel, run the following command in your terminal:
|
||||||
|
|
||||||
|
```
|
||||||
|
docker-compose up
|
||||||
|
```
|
||||||
|
|
||||||
|
Then open http://0.0.0.0:3000 in a web browser to access the demo. If you prefer a different port than `3000` to access the xterm.js demo, then set the `XTERMJS_PORT` environment variable to the desired port.
|
||||||
|
|
||||||
|
### Foreman (or other Procfile runner)
|
||||||
|
|
||||||
|
First, be sure that a C++ compiler such as GCC-C++ or Clang is installed, then run the following commands in your terminal:
|
||||||
|
|
||||||
|
```
|
||||||
|
npm install
|
||||||
|
foreman start # Replace foreman with "honcho", "forego" etc. depending on your runner
|
||||||
|
```
|
||||||
|
|
||||||
|
Then open http://0.0.0.0:3000 in a web browser to access the demo.
|
||||||
|
|
||||||
|
### Linux or macOS
|
||||||
|
|
||||||
|
First, be sure that a C++ compiler such as GCC-C++ or Clang is installed, then run the following commands in your terminal:
|
||||||
|
|
||||||
|
```
|
||||||
|
npm install
|
||||||
|
npm start # Run this in its own terminal
|
||||||
|
npm run watch # Run this in its own terminal
|
||||||
|
```
|
||||||
|
|
||||||
|
Then open http://0.0.0.0:3000 in a web browser to access the demo.
|
||||||
|
|
||||||
|
### Windows
|
||||||
|
|
||||||
|
First, ensure [node-gyp](https://github.com/nodejs/node-gyp) is installed and configured correctly, then run the following commands in your terminal:
|
||||||
|
|
||||||
|
```
|
||||||
|
npm install
|
||||||
|
npm start # Run this in its own terminal
|
||||||
|
npm run watch # Run this in its own terminal
|
||||||
|
```
|
||||||
|
|
||||||
|
Then open http://127.0.0.1:3000 in a web browser to access the demo.
|
||||||
|
|
||||||
|
*Note: Do not use ConEmu, as it seems to break the demo for some reason.*
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
Tests are run using the following npm scripts:
|
||||||
|
|
||||||
|
- `npm test`: This will run both unit tests and the linter
|
||||||
|
- `npm run test-suite <file>`: This will run all tests within a particular file, <file> is the test file name excluding the extension (eg. "Linkifier.test")
|
||||||
|
- `npm run test-debug`: This will run unit tests with `--inspect-brk`, this can then be automatically debugged using [VS Code auto attach](https://code.visualstudio.com/docs/nodejs/nodejs-debugging#_auto-attach-feature) or manually attached to by a debugger
|
||||||
|
- `npm run test-coverage`: This will run all unit tests and produce a coverage report
|
||||||
|
- `npm run lint`: This will run the linter only
|
||||||
|
|
||||||
## Releases
|
## Releases
|
||||||
|
|
||||||
Xterm.js follows a monthly release cycle roughly.
|
Xterm.js follows a monthly release cycle roughly.
|
||||||
|
|
||||||
The existing releases are available at this GitHub repo's [Releases](https://github.com/sourcelair/xterm.js/releases), while the roadmap is available as [Milestones](https://github.com/sourcelair/xterm.js/milestones).
|
The existing releases are available at this GitHub repo's [Releases](https://github.com/sourcelair/xterm.js/releases), while the roadmap is available as [Milestones](https://github.com/sourcelair/xterm.js/milestones).
|
||||||
|
|
||||||
## Contributing
|
## Development and Contribution
|
||||||
|
|
||||||
You can read the [guide on the wiki](https://github.com/xtermjs/xterm.js/wiki/Contributing) to learn how to contribute and setup xterm.js for development.
|
Xterm.js is maintained by [SourceLair](https://www.sourcelair.com/) and a few external contributors, but we would love to receive contributions from everyone!
|
||||||
|
|
||||||
|
To contribute either code, documentation or issues to xterm.js please read the [Contributing document](CONTRIBUTING.md) beforehand. The development of xterm.js does not require any special tool. All you need is an editor that supports JavaScript/TypeScript and a browser. You will need Node.js installed locally to get all the features working in the demo.
|
||||||
|
|
||||||
|
### Code structure
|
||||||
|
|
||||||
|
`src/` is roughly split up into areas of functionality such as `renderer/` that handles all rendering and `utils/` which provides general utility functions. The `shared/` folder contains code that can be used from either the main thread or a web worker thread, all code inside a `shared/` folder should only ever import other code from a `shared/` folder to minimize the amount of code ran when launching a web worker.
|
||||||
|
|
||||||
## License Agreement
|
## License Agreement
|
||||||
|
|
||||||
|
|||||||
+4
-3
@@ -174,7 +174,9 @@ function initOptions(term) {
|
|||||||
'termName',
|
'termName',
|
||||||
'useFlowControl',
|
'useFlowControl',
|
||||||
// Complex option
|
// Complex option
|
||||||
'theme'
|
'theme',
|
||||||
|
// Only in constructor
|
||||||
|
'rendererType'
|
||||||
];
|
];
|
||||||
var stringOptions = {
|
var stringOptions = {
|
||||||
bellSound: null,
|
bellSound: null,
|
||||||
@@ -183,8 +185,7 @@ function initOptions(term) {
|
|||||||
experimentalCharAtlas: ['none', 'static', 'dynamic'],
|
experimentalCharAtlas: ['none', 'static', 'dynamic'],
|
||||||
fontFamily: null,
|
fontFamily: null,
|
||||||
fontWeight: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'],
|
fontWeight: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'],
|
||||||
fontWeightBold: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'],
|
fontWeightBold: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900']
|
||||||
rendererType: ['dom', 'canvas']
|
|
||||||
};
|
};
|
||||||
var options = Object.keys(term._core.options);
|
var options = Object.keys(term._core.options);
|
||||||
var booleanOptions = [];
|
var booleanOptions = [];
|
||||||
|
|||||||
+1
-2
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "xterm",
|
"name": "xterm",
|
||||||
"description": "Full xterm terminal, in your browser",
|
"description": "Full xterm terminal, in your browser",
|
||||||
"version": "3.6.0",
|
"version": "3.5.1",
|
||||||
"main": "lib/public/Terminal.js",
|
"main": "lib/public/Terminal.js",
|
||||||
"types": "typings/xterm.d.ts",
|
"types": "typings/xterm.d.ts",
|
||||||
"repository": "https://github.com/xtermjs/xterm.js",
|
"repository": "https://github.com/xtermjs/xterm.js",
|
||||||
@@ -35,7 +35,6 @@
|
|||||||
"npm-run-all": "^4.1.2",
|
"npm-run-all": "^4.1.2",
|
||||||
"nyc": "^11.8.0",
|
"nyc": "^11.8.0",
|
||||||
"sorcery": "^0.10.0",
|
"sorcery": "^0.10.0",
|
||||||
"source-map-loader": "^0.2.3",
|
|
||||||
"tslint": "^5.9.1",
|
"tslint": "^5.9.1",
|
||||||
"tslint-consistent-codestyle": "^1.13.0",
|
"tslint-consistent-codestyle": "^1.13.0",
|
||||||
"typescript": "2.8.3",
|
"typescript": "2.8.3",
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ export class AccessibilityManager extends Disposable {
|
|||||||
this._terminal.element.insertAdjacentElement('afterbegin', this._accessibilityTreeRoot);
|
this._terminal.element.insertAdjacentElement('afterbegin', this._accessibilityTreeRoot);
|
||||||
|
|
||||||
this.register(this._renderRowsDebouncer);
|
this.register(this._renderRowsDebouncer);
|
||||||
this.register(this._terminal.addDisposableListener('resize', data => this._onResize(data.rows)));
|
this.register(this._terminal.addDisposableListener('resize', data => this._onResize(data.cols, data.rows)));
|
||||||
this.register(this._terminal.addDisposableListener('refresh', data => this._refreshRows(data.start, data.end)));
|
this.register(this._terminal.addDisposableListener('refresh', data => this._refreshRows(data.start, data.end)));
|
||||||
this.register(this._terminal.addDisposableListener('scroll', data => this._refreshRows()));
|
this.register(this._terminal.addDisposableListener('scroll', data => this._refreshRows()));
|
||||||
// Line feed is an issue as the prompt won't be read out after a command is run
|
// Line feed is an issue as the prompt won't be read out after a command is run
|
||||||
@@ -157,7 +157,7 @@ export class AccessibilityManager extends Disposable {
|
|||||||
e.stopImmediatePropagation();
|
e.stopImmediatePropagation();
|
||||||
}
|
}
|
||||||
|
|
||||||
private _onResize(rows: number): void {
|
private _onResize(cols: number, rows: number): void {
|
||||||
// Remove bottom boundary listener
|
// Remove bottom boundary listener
|
||||||
this._rowElements[this._rowElements.length - 1].removeEventListener('focus', this._bottomBoundaryFocusListener);
|
this._rowElements[this._rowElements.length - 1].removeEventListener('focus', this._bottomBoundaryFocusListener);
|
||||||
|
|
||||||
@@ -259,9 +259,6 @@ export class AccessibilityManager extends Disposable {
|
|||||||
if (!this._terminal.renderer.dimensions.actualCellHeight) {
|
if (!this._terminal.renderer.dimensions.actualCellHeight) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this._rowElements.length !== this._terminal.rows) {
|
|
||||||
this._onResize(this._terminal.rows);
|
|
||||||
}
|
|
||||||
for (let i = 0; i < this._terminal.rows; i++) {
|
for (let i = 0; i < this._terminal.rows; i++) {
|
||||||
this._refreshRowDimensions(this._rowElements[i]);
|
this._refreshRowDimensions(this._rowElements[i]);
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-5
@@ -15,10 +15,6 @@ export const CHAR_DATA_WIDTH_INDEX = 2;
|
|||||||
export const CHAR_DATA_CODE_INDEX = 3;
|
export const CHAR_DATA_CODE_INDEX = 3;
|
||||||
export const MAX_BUFFER_SIZE = 4294967295; // 2^32 - 1
|
export const MAX_BUFFER_SIZE = 4294967295; // 2^32 - 1
|
||||||
|
|
||||||
export const NULL_CELL_CHAR = ' ';
|
|
||||||
export const NULL_CELL_WIDTH = 1;
|
|
||||||
export const NULL_CELL_CODE = 32;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class represents a terminal buffer (an internal state of the terminal), where the
|
* This class represents a terminal buffer (an internal state of the terminal), where the
|
||||||
* following information is stored (in high-level):
|
* following information is stored (in high-level):
|
||||||
@@ -121,7 +117,7 @@ export class Buffer implements IBuffer {
|
|||||||
if (this.lines.length > 0) {
|
if (this.lines.length > 0) {
|
||||||
// Deal with columns increasing (we don't do anything when columns reduce)
|
// Deal with columns increasing (we don't do anything when columns reduce)
|
||||||
if (this._terminal.cols < newCols) {
|
if (this._terminal.cols < newCols) {
|
||||||
const ch: CharData = [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]; // does xterm use the default attr?
|
const ch: CharData = [DEFAULT_ATTR, ' ', 1, 32]; // does xterm use the default attr?
|
||||||
for (let i = 0; i < this.lines.length; i++) {
|
for (let i = 0; i < this.lines.length; i++) {
|
||||||
while (this.lines.get(i).length < newCols) {
|
while (this.lines.get(i).length < newCols) {
|
||||||
this.lines.get(i).push(ch);
|
this.lines.get(i).push(ch);
|
||||||
|
|||||||
+4
-1
@@ -18,7 +18,7 @@ export class BufferSet extends EventEmitter implements IBufferSet {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new BufferSet for the given terminal.
|
* Create a new BufferSet for the given terminal.
|
||||||
* @param _terminal - The terminal the BufferSet will belong to
|
* @param {Terminal} terminal - The terminal the BufferSet will belong to
|
||||||
*/
|
*/
|
||||||
constructor(private _terminal: ITerminal) {
|
constructor(private _terminal: ITerminal) {
|
||||||
super();
|
super();
|
||||||
@@ -35,6 +35,7 @@ export class BufferSet extends EventEmitter implements IBufferSet {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the alt Buffer of the BufferSet
|
* Returns the alt Buffer of the BufferSet
|
||||||
|
* @returns {Buffer}
|
||||||
*/
|
*/
|
||||||
public get alt(): Buffer {
|
public get alt(): Buffer {
|
||||||
return this._alt;
|
return this._alt;
|
||||||
@@ -42,6 +43,7 @@ export class BufferSet extends EventEmitter implements IBufferSet {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the normal Buffer of the BufferSet
|
* Returns the normal Buffer of the BufferSet
|
||||||
|
* @returns {Buffer}
|
||||||
*/
|
*/
|
||||||
public get active(): Buffer {
|
public get active(): Buffer {
|
||||||
return this._activeBuffer;
|
return this._activeBuffer;
|
||||||
@@ -49,6 +51,7 @@ export class BufferSet extends EventEmitter implements IBufferSet {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the currently active Buffer of the BufferSet
|
* Returns the currently active Buffer of the BufferSet
|
||||||
|
* @returns {Buffer}
|
||||||
*/
|
*/
|
||||||
public get normal(): Buffer {
|
public get normal(): Buffer {
|
||||||
return this._normal;
|
return this._normal;
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ export class CompositionHelper {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles the compositionupdate event, updating the composition view.
|
* Handles the compositionupdate event, updating the composition view.
|
||||||
* @param ev The event.
|
* @param {CompositionEvent} ev The event.
|
||||||
*/
|
*/
|
||||||
public compositionupdate(ev: CompositionEvent): void {
|
public compositionupdate(ev: CompositionEvent): void {
|
||||||
this._compositionView.textContent = ev.data;
|
this._compositionView.textContent = ev.data;
|
||||||
|
|||||||
@@ -375,8 +375,8 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
|||||||
// shortcut for most chars (print action)
|
// shortcut for most chars (print action)
|
||||||
if (currentState === ParserState.GROUND && code > 0x1f && code < 0x80) {
|
if (currentState === ParserState.GROUND && code > 0x1f && code < 0x80) {
|
||||||
print = (~print) ? print : i;
|
print = (~print) ? print : i;
|
||||||
do i++;
|
do code = data.charCodeAt(++i);
|
||||||
while (i < l && data.charCodeAt(i) > 0x1f && data.charCodeAt(i) < 0x80);
|
while (i < l && code > 0x1f && code < 0x80);
|
||||||
i--;
|
i--;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
+42
-45
@@ -4,10 +4,10 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { CharData, IInputHandler, IDcsHandler, IEscapeSequenceParser, IBuffer, IInputHandlingTerminal } from './Types';
|
import { CharData, IInputHandler, IDcsHandler, IEscapeSequenceParser, IBuffer } from './Types';
|
||||||
import { C0, C1 } from './common/data/EscapeSequences';
|
import { C0, C1 } from './common/data/EscapeSequences';
|
||||||
import { CHARSETS, DEFAULT_CHARSET } from './core/data/Charsets';
|
import { CHARSETS, DEFAULT_CHARSET } from './core/data/Charsets';
|
||||||
import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CODE_INDEX, DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE } from './Buffer';
|
import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CODE_INDEX, DEFAULT_ATTR } from './Buffer';
|
||||||
import { FLAGS } from './renderer/Types';
|
import { FLAGS } from './renderer/Types';
|
||||||
import { wcwidth } from './CharWidth';
|
import { wcwidth } from './CharWidth';
|
||||||
import { EscapeSequenceParser } from './EscapeSequenceParser';
|
import { EscapeSequenceParser } from './EscapeSequenceParser';
|
||||||
@@ -40,7 +40,7 @@ class RequestTerminfo implements IDcsHandler {
|
|||||||
}
|
}
|
||||||
unhook(): void {
|
unhook(): void {
|
||||||
// invalid: DCS 0 + r Pt ST
|
// invalid: DCS 0 + r Pt ST
|
||||||
this._terminal.handler(`${C0.ESC}P0+r${this._data}${C0.ESC}\\`);
|
this._terminal.send(`${C0.ESC}P0+r${this._data}${C0.ESC}\\`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,25 +68,25 @@ class DECRQSS implements IDcsHandler {
|
|||||||
switch (this._data) {
|
switch (this._data) {
|
||||||
// valid: DCS 1 $ r Pt ST (xterm)
|
// valid: DCS 1 $ r Pt ST (xterm)
|
||||||
case '"q': // DECSCA
|
case '"q': // DECSCA
|
||||||
return this._terminal.handler(`${C0.ESC}P1$r0"q${C0.ESC}\\`);
|
return this._terminal.send(`${C0.ESC}P1$r0"q${C0.ESC}\\`);
|
||||||
case '"p': // DECSCL
|
case '"p': // DECSCL
|
||||||
return this._terminal.handler(`${C0.ESC}P1$r61"p${C0.ESC}\\`);
|
return this._terminal.send(`${C0.ESC}P1$r61"p${C0.ESC}\\`);
|
||||||
case 'r': // DECSTBM
|
case 'r': // DECSTBM
|
||||||
const pt = '' + (this._terminal.buffer.scrollTop + 1) +
|
const pt = '' + (this._terminal.buffer.scrollTop + 1) +
|
||||||
';' + (this._terminal.buffer.scrollBottom + 1) + 'r';
|
';' + (this._terminal.buffer.scrollBottom + 1) + 'r';
|
||||||
return this._terminal.handler(`${C0.ESC}P1$r${pt}${C0.ESC}\\`);
|
return this._terminal.send(`${C0.ESC}P1$r${pt}${C0.ESC}\\`);
|
||||||
case 'm': // SGR
|
case 'm': // SGR
|
||||||
// TODO: report real settings instead of 0m
|
// TODO: report real settings instead of 0m
|
||||||
return this._terminal.handler(`${C0.ESC}P1$r0m${C0.ESC}\\`);
|
return this._terminal.send(`${C0.ESC}P1$r0m${C0.ESC}\\`);
|
||||||
case ' q': // DECSCUSR
|
case ' q': // DECSCUSR
|
||||||
const STYLES: {[key: string]: number} = {'block': 2, 'underline': 4, 'bar': 6};
|
const STYLES: {[key: string]: number} = {'block': 2, 'underline': 4, 'bar': 6};
|
||||||
let style = STYLES[this._terminal.getOption('cursorStyle')];
|
let style = STYLES[this._terminal.getOption('cursorStyle')];
|
||||||
style -= this._terminal.getOption('cursorBlink');
|
style -= this._terminal.getOption('cursorBlink');
|
||||||
return this._terminal.handler(`${C0.ESC}P1$r${style} q${C0.ESC}\\`);
|
return this._terminal.send(`${C0.ESC}P1$r${style} q${C0.ESC}\\`);
|
||||||
default:
|
default:
|
||||||
// invalid: DCS 0 $ r Pt ST (xterm)
|
// invalid: DCS 0 $ r Pt ST (xterm)
|
||||||
this._terminal.error('Unknown DCS $q %s', this._data);
|
this._terminal.error('Unknown DCS $q %s', this._data);
|
||||||
this._terminal.handler(`${C0.ESC}P0$r${this._data}${C0.ESC}\\`);
|
this._terminal.send(`${C0.ESC}P0$r${this._data}${C0.ESC}\\`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -116,7 +116,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|||||||
private _surrogateHigh: string;
|
private _surrogateHigh: string;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private _terminal: IInputHandlingTerminal,
|
private _terminal: any, // TODO: reestablish IInputHandlingTerminal here
|
||||||
private _parser: IEscapeSequenceParser = new EscapeSequenceParser())
|
private _parser: IEscapeSequenceParser = new EscapeSequenceParser())
|
||||||
{
|
{
|
||||||
super();
|
super();
|
||||||
@@ -129,16 +129,16 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|||||||
* custom fallback handlers
|
* custom fallback handlers
|
||||||
*/
|
*/
|
||||||
this._parser.setCsiHandlerFallback((collect: string, params: number[], flag: number) => {
|
this._parser.setCsiHandlerFallback((collect: string, params: number[], flag: number) => {
|
||||||
this._terminal.error('Unknown CSI code: ', { collect, params, flag: String.fromCharCode(flag) });
|
this._terminal.error('Unknown CSI code: ', collect, params, String.fromCharCode(flag));
|
||||||
});
|
});
|
||||||
this._parser.setEscHandlerFallback((collect: string, flag: number) => {
|
this._parser.setEscHandlerFallback((collect: string, flag: number) => {
|
||||||
this._terminal.error('Unknown ESC code: ', { collect, flag: String.fromCharCode(flag) });
|
this._terminal.error('Unknown ESC code: ', collect, String.fromCharCode(flag));
|
||||||
});
|
});
|
||||||
this._parser.setExecuteHandlerFallback((code: number) => {
|
this._parser.setExecuteHandlerFallback((code: number) => {
|
||||||
this._terminal.error('Unknown EXECUTE code: ', { code });
|
this._terminal.error('Unknown EXECUTE code: ', code);
|
||||||
});
|
});
|
||||||
this._parser.setOscHandlerFallback((identifier: number, data: string) => {
|
this._parser.setOscHandlerFallback((identifier: number, data: string) => {
|
||||||
this._terminal.error('Unknown OSC code: ', { identifier, data });
|
this._terminal.error('Unknown OSC code: ', identifier, data);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -296,17 +296,10 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public parse(data: string): void {
|
public parse(data: string): void {
|
||||||
// Ensure the terminal is not disposed
|
|
||||||
if (!this._terminal) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let buffer = this._terminal.buffer;
|
let buffer = this._terminal.buffer;
|
||||||
const cursorStartX = buffer.x;
|
const cursorStartX = buffer.x;
|
||||||
const cursorStartY = buffer.y;
|
const cursorStartY = buffer.y;
|
||||||
|
if (this._terminal.debug) {
|
||||||
// TODO: Consolidate debug/logging #1560
|
|
||||||
if ((<any>this._terminal).debug) {
|
|
||||||
this._terminal.log('data: ' + data);
|
this._terminal.log('data: ' + data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -438,11 +431,11 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|||||||
if (removed[CHAR_DATA_WIDTH_INDEX] === 0
|
if (removed[CHAR_DATA_WIDTH_INDEX] === 0
|
||||||
&& bufferRow[this._terminal.cols - 2]
|
&& bufferRow[this._terminal.cols - 2]
|
||||||
&& bufferRow[this._terminal.cols - 2][CHAR_DATA_WIDTH_INDEX] === 2) {
|
&& bufferRow[this._terminal.cols - 2][CHAR_DATA_WIDTH_INDEX] === 2) {
|
||||||
bufferRow[this._terminal.cols - 2] = [curAttr, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE];
|
bufferRow[this._terminal.cols - 2] = [curAttr, ' ', 1, 32 /* ' '.charCodeAt(0) */ ];
|
||||||
}
|
}
|
||||||
|
|
||||||
// insert empty cell at cursor
|
// insert empty cell at cursor
|
||||||
bufferRow.splice(buffer.x, 0, [curAttr, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]);
|
bufferRow.splice(buffer.x, 0, [curAttr, ' ', 1, 32 /* ' '.charCodeAt(0) */ ]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -554,7 +547,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|||||||
|
|
||||||
const row = buffer.y + buffer.ybase;
|
const row = buffer.y + buffer.ybase;
|
||||||
let j = buffer.x;
|
let j = buffer.x;
|
||||||
const ch: CharData = [this._terminal.eraseAttr(), NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]; // xterm
|
const ch: CharData = [this._terminal.eraseAttr(), ' ', 1, 32]; // xterm
|
||||||
|
|
||||||
while (param-- && j < this._terminal.cols) {
|
while (param-- && j < this._terminal.cols) {
|
||||||
buffer.lines.get(row).splice(j++, 0, ch);
|
buffer.lines.get(row).splice(j++, 0, ch);
|
||||||
@@ -864,7 +857,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|||||||
const buffer = this._terminal.buffer;
|
const buffer = this._terminal.buffer;
|
||||||
|
|
||||||
const row = buffer.y + buffer.ybase;
|
const row = buffer.y + buffer.ybase;
|
||||||
const ch: CharData = [this._terminal.eraseAttr(), NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]; // xterm
|
const ch: CharData = [this._terminal.eraseAttr(), ' ', 1, 32]; // xterm
|
||||||
|
|
||||||
while (param--) {
|
while (param--) {
|
||||||
buffer.lines.get(row).splice(buffer.x, 1);
|
buffer.lines.get(row).splice(buffer.x, 1);
|
||||||
@@ -926,7 +919,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|||||||
|
|
||||||
const row = buffer.y + buffer.ybase;
|
const row = buffer.y + buffer.ybase;
|
||||||
let j = buffer.x;
|
let j = buffer.x;
|
||||||
const ch: CharData = [this._terminal.eraseAttr(), NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]; // xterm
|
const ch: CharData = [this._terminal.eraseAttr(), ' ', 1, 32]; // xterm
|
||||||
|
|
||||||
while (param-- && j < this._terminal.cols) {
|
while (param-- && j < this._terminal.cols) {
|
||||||
buffer.lines.get(row)[j++] = ch;
|
buffer.lines.get(row)[j++] = ch;
|
||||||
@@ -988,7 +981,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|||||||
const buffer = this._terminal.buffer;
|
const buffer = this._terminal.buffer;
|
||||||
|
|
||||||
const line = buffer.lines.get(buffer.ybase + buffer.y);
|
const line = buffer.lines.get(buffer.ybase + buffer.y);
|
||||||
const ch = line[buffer.x - 1] || [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE];
|
const ch = line[buffer.x - 1] || [DEFAULT_ATTR, ' ', 1, 32];
|
||||||
|
|
||||||
while (param--) {
|
while (param--) {
|
||||||
line[buffer.x++] = ch;
|
line[buffer.x++] = ch;
|
||||||
@@ -1039,24 +1032,24 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|||||||
|
|
||||||
if (!collect) {
|
if (!collect) {
|
||||||
if (this._terminal.is('xterm') || this._terminal.is('rxvt-unicode') || this._terminal.is('screen')) {
|
if (this._terminal.is('xterm') || this._terminal.is('rxvt-unicode') || this._terminal.is('screen')) {
|
||||||
this._terminal.handler(C0.ESC + '[?1;2c');
|
this._terminal.send(C0.ESC + '[?1;2c');
|
||||||
} else if (this._terminal.is('linux')) {
|
} else if (this._terminal.is('linux')) {
|
||||||
this._terminal.handler(C0.ESC + '[?6c');
|
this._terminal.send(C0.ESC + '[?6c');
|
||||||
}
|
}
|
||||||
} else if (collect === '>') {
|
} else if (collect === '>') {
|
||||||
// xterm and urxvt
|
// xterm and urxvt
|
||||||
// seem to spit this
|
// seem to spit this
|
||||||
// out around ~370 times (?).
|
// out around ~370 times (?).
|
||||||
if (this._terminal.is('xterm')) {
|
if (this._terminal.is('xterm')) {
|
||||||
this._terminal.handler(C0.ESC + '[>0;276;0c');
|
this._terminal.send(C0.ESC + '[>0;276;0c');
|
||||||
} else if (this._terminal.is('rxvt-unicode')) {
|
} else if (this._terminal.is('rxvt-unicode')) {
|
||||||
this._terminal.handler(C0.ESC + '[>85;95;0c');
|
this._terminal.send(C0.ESC + '[>85;95;0c');
|
||||||
} else if (this._terminal.is('linux')) {
|
} else if (this._terminal.is('linux')) {
|
||||||
// not supported by linux console.
|
// not supported by linux console.
|
||||||
// linux console echoes parameters.
|
// linux console echoes parameters.
|
||||||
this._terminal.handler(params[0] + 'c');
|
this._terminal.send(params[0] + 'c');
|
||||||
} else if (this._terminal.is('screen')) {
|
} else if (this._terminal.is('screen')) {
|
||||||
this._terminal.handler(C0.ESC + '[>83;40003;0c');
|
this._terminal.send(C0.ESC + '[>83;40003;0c');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1719,13 +1712,15 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|||||||
switch (params[0]) {
|
switch (params[0]) {
|
||||||
case 5:
|
case 5:
|
||||||
// status report
|
// status report
|
||||||
this._terminal.emit('data', `${C0.ESC}[0n`);
|
this._terminal.send(C0.ESC + '[0n');
|
||||||
break;
|
break;
|
||||||
case 6:
|
case 6:
|
||||||
// cursor position
|
// cursor position
|
||||||
const y = this._terminal.buffer.y + 1;
|
this._terminal.send(C0.ESC + '['
|
||||||
const x = this._terminal.buffer.x + 1;
|
+ (this._terminal.buffer.y + 1)
|
||||||
this._terminal.emit('data', `${C0.ESC}[${y};${x}R`);
|
+ ';'
|
||||||
|
+ (this._terminal.buffer.x + 1)
|
||||||
|
+ 'R');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else if (collect === '?') {
|
} else if (collect === '?') {
|
||||||
@@ -1734,25 +1729,27 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|||||||
switch (params[0]) {
|
switch (params[0]) {
|
||||||
case 6:
|
case 6:
|
||||||
// cursor position
|
// cursor position
|
||||||
const y = this._terminal.buffer.y + 1;
|
this._terminal.send(C0.ESC + '[?'
|
||||||
const x = this._terminal.buffer.x + 1;
|
+ (this._terminal.buffer.y + 1)
|
||||||
this._terminal.emit('data', `${C0.ESC}[?${y};${x}R`);
|
+ ';'
|
||||||
|
+ (this._terminal.buffer.x + 1)
|
||||||
|
+ 'R');
|
||||||
break;
|
break;
|
||||||
case 15:
|
case 15:
|
||||||
// no printer
|
// no printer
|
||||||
// this.handler(C0.ESC + '[?11n');
|
// this.send(C0.ESC + '[?11n');
|
||||||
break;
|
break;
|
||||||
case 25:
|
case 25:
|
||||||
// dont support user defined keys
|
// dont support user defined keys
|
||||||
// this.handler(C0.ESC + '[?21n');
|
// this.send(C0.ESC + '[?21n');
|
||||||
break;
|
break;
|
||||||
case 26:
|
case 26:
|
||||||
// north american keyboard
|
// north american keyboard
|
||||||
// this.handler(C0.ESC + '[?27;1;0;0n');
|
// this.send(C0.ESC + '[?27;1;0;0n');
|
||||||
break;
|
break;
|
||||||
case 53:
|
case 53:
|
||||||
// no dec locator/mouse
|
// no dec locator/mouse
|
||||||
// this.handler(C0.ESC + '[?50n');
|
// this.send(C0.ESC + '[?50n');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-10
@@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { IMouseZoneManager } from './ui/Types';
|
import { IMouseZoneManager } from './ui/Types';
|
||||||
import { ILinkHoverEvent, ILinkMatcher, LinkMatcherHandler, LinkHoverEventTypes, ILinkMatcherOptions, ILinkifier, ITerminal, LineData } from './Types';
|
import { ILinkHoverEvent, ILinkMatcher, LinkMatcherHandler, LinkHoverEventTypes, ILinkMatcherOptions, ILinkifier, ITerminal } from './Types';
|
||||||
import { MouseZone } from './ui/MouseZoneManager';
|
import { MouseZone } from './ui/MouseZoneManager';
|
||||||
import { EventEmitter } from './EventEmitter';
|
import { EventEmitter } from './EventEmitter';
|
||||||
|
|
||||||
@@ -169,18 +169,10 @@ export class Linkifier extends EventEmitter implements ILinkifier {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// If the first row is wrapped, backtrack to find the origin row and linkify that
|
// If the first row is wrapped, backtrack to find the origin row and linkify that
|
||||||
let line: LineData;
|
|
||||||
|
|
||||||
do {
|
do {
|
||||||
rowIndex--;
|
rowIndex--;
|
||||||
absoluteRowIndex--;
|
absoluteRowIndex--;
|
||||||
line = this._terminal.buffer.lines.get(absoluteRowIndex);
|
} while ((<any>this._terminal.buffer.lines.get(absoluteRowIndex)).isWrapped);
|
||||||
|
|
||||||
if (!line) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
} while ((<any>line).isWrapped);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Construct full unwrapped line text
|
// Construct full unwrapped line text
|
||||||
|
|||||||
@@ -183,42 +183,6 @@ describe('SelectionManager', () => {
|
|||||||
selectionManager.selectWordAt([15, 0]);
|
selectionManager.selectWordAt([15, 0]);
|
||||||
assert.equal(selectionManager.selectionText, 'ij"');
|
assert.equal(selectionManager.selectionText, 'ij"');
|
||||||
});
|
});
|
||||||
it('should expand upwards or downards for wrapped lines', () => {
|
|
||||||
buffer.lines.set(0, stringToRow(' foo'));
|
|
||||||
buffer.lines.set(1, stringToRow('bar '));
|
|
||||||
(<any>buffer.lines.get(1)).isWrapped = true;
|
|
||||||
selectionManager.selectWordAt([1, 1]);
|
|
||||||
assert.equal(selectionManager.selectionText, 'foobar');
|
|
||||||
selectionManager.model.clearSelection();
|
|
||||||
selectionManager.selectWordAt([78, 0]);
|
|
||||||
assert.equal(selectionManager.selectionText, 'foobar');
|
|
||||||
});
|
|
||||||
it('should expand both upwards and downwards for word wrapped over many lines', () => {
|
|
||||||
const expectedText = 'fooaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccbar';
|
|
||||||
buffer.lines.set(0, stringToRow(' foo'));
|
|
||||||
buffer.lines.set(1, stringToRow('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'));
|
|
||||||
buffer.lines.set(2, stringToRow('bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'));
|
|
||||||
buffer.lines.set(3, stringToRow('cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc'));
|
|
||||||
buffer.lines.set(4, stringToRow('bar '));
|
|
||||||
(<any>buffer.lines.get(1)).isWrapped = true;
|
|
||||||
(<any>buffer.lines.get(2)).isWrapped = true;
|
|
||||||
(<any>buffer.lines.get(3)).isWrapped = true;
|
|
||||||
(<any>buffer.lines.get(4)).isWrapped = true;
|
|
||||||
selectionManager.selectWordAt([78, 0]);
|
|
||||||
assert.equal(selectionManager.selectionText, expectedText);
|
|
||||||
selectionManager.model.clearSelection();
|
|
||||||
selectionManager.selectWordAt([40, 1]);
|
|
||||||
assert.equal(selectionManager.selectionText, expectedText);
|
|
||||||
selectionManager.model.clearSelection();
|
|
||||||
selectionManager.selectWordAt([40, 2]);
|
|
||||||
assert.equal(selectionManager.selectionText, expectedText);
|
|
||||||
selectionManager.model.clearSelection();
|
|
||||||
selectionManager.selectWordAt([40, 3]);
|
|
||||||
assert.equal(selectionManager.selectionText, expectedText);
|
|
||||||
selectionManager.model.clearSelection();
|
|
||||||
selectionManager.selectWordAt([1, 4]);
|
|
||||||
assert.equal(selectionManager.selectionText, expectedText);
|
|
||||||
});
|
|
||||||
describe('emoji', () => {
|
describe('emoji', () => {
|
||||||
it('should treat a single emoji as a word when wrapped in spaces', () => {
|
it('should treat a single emoji as a word when wrapped in spaces', () => {
|
||||||
buffer.lines.set(0, stringToRow(' ⚽ a')); // The a is here to prevent the space being trimmed in selectionText
|
buffer.lines.set(0, stringToRow(' ⚽ a')); // The a is here to prevent the space being trimmed in selectionText
|
||||||
|
|||||||
+14
-78
@@ -9,7 +9,7 @@ import * as Browser from './shared/utils/Browser';
|
|||||||
import { CharMeasure } from './ui/CharMeasure';
|
import { CharMeasure } from './ui/CharMeasure';
|
||||||
import { EventEmitter } from './EventEmitter';
|
import { EventEmitter } from './EventEmitter';
|
||||||
import { SelectionModel } from './SelectionModel';
|
import { SelectionModel } from './SelectionModel';
|
||||||
import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX, CHAR_DATA_CODE_INDEX } from './Buffer';
|
import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from './Buffer';
|
||||||
import { AltClickHandler } from './handlers/AltClickHandler';
|
import { AltClickHandler } from './handlers/AltClickHandler';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -451,10 +451,8 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
|
|||||||
* Removes the listeners that are registered when mousedown is triggered.
|
* Removes the listeners that are registered when mousedown is triggered.
|
||||||
*/
|
*/
|
||||||
private _removeMouseDownListeners(): void {
|
private _removeMouseDownListeners(): void {
|
||||||
if (this._terminal.element.ownerDocument) {
|
this._terminal.element.ownerDocument.removeEventListener('mousemove', this._mouseMoveListener);
|
||||||
this._terminal.element.ownerDocument.removeEventListener('mousemove', this._mouseMoveListener);
|
this._terminal.element.ownerDocument.removeEventListener('mouseup', this._mouseUpListener);
|
||||||
this._terminal.element.ownerDocument.removeEventListener('mouseup', this._mouseUpListener);
|
|
||||||
}
|
|
||||||
clearInterval(this._dragScrollIntervalTimer);
|
clearInterval(this._dragScrollIntervalTimer);
|
||||||
this._dragScrollIntervalTimer = null;
|
this._dragScrollIntervalTimer = null;
|
||||||
}
|
}
|
||||||
@@ -576,14 +574,11 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
|
|||||||
this._dragScrollAmount = this._getMouseEventScrollAmount(event);
|
this._dragScrollAmount = this._getMouseEventScrollAmount(event);
|
||||||
|
|
||||||
// If the cursor was above or below the viewport, make sure it's at the
|
// If the cursor was above or below the viewport, make sure it's at the
|
||||||
// start or end of the viewport respectively. This should only happen when
|
// start or end of the viewport respectively.
|
||||||
// NOT in column select mode.
|
if (this._dragScrollAmount > 0) {
|
||||||
if (this._activeSelectionMode !== SelectionMode.COLUMN) {
|
this._model.selectionEnd[0] = this._terminal.cols;
|
||||||
if (this._dragScrollAmount > 0) {
|
} else if (this._dragScrollAmount < 0) {
|
||||||
this._model.selectionEnd[0] = this._terminal.cols;
|
this._model.selectionEnd[0] = 0;
|
||||||
} else if (this._dragScrollAmount < 0) {
|
|
||||||
this._model.selectionEnd[0] = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the character is a wide character include the cell to the right in the
|
// If the character is a wide character include the cell to the right in the
|
||||||
@@ -612,19 +607,10 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
|
|||||||
if (this._dragScrollAmount) {
|
if (this._dragScrollAmount) {
|
||||||
this._terminal.scrollLines(this._dragScrollAmount, false);
|
this._terminal.scrollLines(this._dragScrollAmount, false);
|
||||||
// Re-evaluate selection
|
// Re-evaluate selection
|
||||||
// If the cursor was above or below the viewport, make sure it's at the
|
|
||||||
// start or end of the viewport respectively. This should only happen when
|
|
||||||
// NOT in column select mode.
|
|
||||||
if (this._dragScrollAmount > 0) {
|
if (this._dragScrollAmount > 0) {
|
||||||
if (this._activeSelectionMode !== SelectionMode.COLUMN) {
|
this._model.selectionEnd = [this._terminal.cols - 1, Math.min(this._terminal.buffer.ydisp + this._terminal.rows, this._terminal.buffer.lines.length - 1)];
|
||||||
this._model.selectionEnd[0] = this._terminal.cols;
|
|
||||||
}
|
|
||||||
this._model.selectionEnd[1] = Math.min(this._terminal.buffer.ydisp + this._terminal.rows, this._terminal.buffer.lines.length - 1);
|
|
||||||
} else {
|
} else {
|
||||||
if (this._activeSelectionMode !== SelectionMode.COLUMN) {
|
this._model.selectionEnd = [0, this._terminal.buffer.ydisp];
|
||||||
this._model.selectionEnd[0] = 0;
|
|
||||||
}
|
|
||||||
this._model.selectionEnd[1] = this._terminal.buffer.ydisp;
|
|
||||||
}
|
}
|
||||||
this.refresh();
|
this.refresh();
|
||||||
}
|
}
|
||||||
@@ -691,7 +677,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
|
|||||||
* Gets positional information for the word at the coordinated specified.
|
* Gets positional information for the word at the coordinated specified.
|
||||||
* @param coords The coordinates to get the word at.
|
* @param coords The coordinates to get the word at.
|
||||||
*/
|
*/
|
||||||
private _getWordAt(coords: [number, number], allowWhitespaceOnlySelection: boolean, followWrappedLinesAbove: boolean = true, followWrappedLinesBelow: boolean = true): IWordPosition {
|
private _getWordAt(coords: [number, number], allowWhitespaceOnlySelection: boolean): IWordPosition {
|
||||||
// Ensure coords are within viewport (eg. not within scroll bar)
|
// Ensure coords are within viewport (eg. not within scroll bar)
|
||||||
if (coords[0] >= this._terminal.cols) {
|
if (coords[0] >= this._terminal.cols) {
|
||||||
return null;
|
return null;
|
||||||
@@ -786,7 +772,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
|
|||||||
|
|
||||||
// Calculate the start _column_, converting the the string indexes back to
|
// Calculate the start _column_, converting the the string indexes back to
|
||||||
// column coordinates.
|
// column coordinates.
|
||||||
let start =
|
const start =
|
||||||
startIndex // The index of the selection's start char in the line string
|
startIndex // The index of the selection's start char in the line string
|
||||||
+ charOffset // The difference between the initial char's column and index
|
+ charOffset // The difference between the initial char's column and index
|
||||||
- leftWideCharCount // The number of wide chars left of the initial char
|
- leftWideCharCount // The number of wide chars left of the initial char
|
||||||
@@ -794,7 +780,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
|
|||||||
|
|
||||||
// Calculate the length in _columns_, converting the the string indexes back
|
// Calculate the length in _columns_, converting the the string indexes back
|
||||||
// to column coordinates.
|
// to column coordinates.
|
||||||
let length = Math.min(this._terminal.cols, // Disallow lengths larger than the terminal cols
|
const length = Math.min(this._terminal.cols, // Disallow lengths larger than the terminal cols
|
||||||
endIndex // The index of the selection's end char in the line string
|
endIndex // The index of the selection's end char in the line string
|
||||||
- startIndex // The index of the selection's start char in the line string
|
- startIndex // The index of the selection's start char in the line string
|
||||||
+ leftWideCharCount // The number of wide chars left of the initial char
|
+ leftWideCharCount // The number of wide chars left of the initial char
|
||||||
@@ -806,34 +792,6 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recurse upwards if the line is wrapped and the word wraps to the above line
|
|
||||||
if (followWrappedLinesAbove) {
|
|
||||||
if (start === 0 && bufferLine[0][CHAR_DATA_CODE_INDEX] !== 32 /*' '*/) {
|
|
||||||
const previousBufferLine = this._buffer.lines.get(coords[1] - 1);
|
|
||||||
if (previousBufferLine && (<any>bufferLine).isWrapped && previousBufferLine[this._terminal.cols - 1][CHAR_DATA_CODE_INDEX] !== 32 /*' '*/) {
|
|
||||||
const previousLineWordPosition = this._getWordAt([this._terminal.cols - 1, coords[1] - 1], false, true, false);
|
|
||||||
if (previousLineWordPosition) {
|
|
||||||
const offset = this._terminal.cols - previousLineWordPosition.start;
|
|
||||||
start -= offset;
|
|
||||||
length += offset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Recurse downwards if the line is wrapped and the word wraps to the next line
|
|
||||||
if (followWrappedLinesBelow) {
|
|
||||||
if (start + length === this._terminal.cols && bufferLine[this._terminal.cols - 1][CHAR_DATA_CODE_INDEX] !== 32 /*' '*/) {
|
|
||||||
const nextBufferLine = this._buffer.lines.get(coords[1] + 1);
|
|
||||||
if (nextBufferLine && (<any>nextBufferLine).isWrapped && nextBufferLine[0][CHAR_DATA_CODE_INDEX] !== 32 /*' '*/) {
|
|
||||||
const nextLineWordPosition = this._getWordAt([0, coords[1] + 1], false, false, true);
|
|
||||||
if (nextLineWordPosition) {
|
|
||||||
length += nextLineWordPosition.length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return { start, length };
|
return { start, length };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -845,11 +803,6 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
|
|||||||
protected _selectWordAt(coords: [number, number], allowWhitespaceOnlySelection: boolean): void {
|
protected _selectWordAt(coords: [number, number], allowWhitespaceOnlySelection: boolean): void {
|
||||||
const wordPosition = this._getWordAt(coords, allowWhitespaceOnlySelection);
|
const wordPosition = this._getWordAt(coords, allowWhitespaceOnlySelection);
|
||||||
if (wordPosition) {
|
if (wordPosition) {
|
||||||
// Adjust negative start value
|
|
||||||
while (wordPosition.start < 0) {
|
|
||||||
wordPosition.start += this._terminal.cols;
|
|
||||||
coords[1]--;
|
|
||||||
}
|
|
||||||
this._model.selectionStart = [wordPosition.start, coords[1]];
|
this._model.selectionStart = [wordPosition.start, coords[1]];
|
||||||
this._model.selectionStartLength = wordPosition.length;
|
this._model.selectionStartLength = wordPosition.length;
|
||||||
}
|
}
|
||||||
@@ -862,24 +815,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
|
|||||||
private _selectToWordAt(coords: [number, number]): void {
|
private _selectToWordAt(coords: [number, number]): void {
|
||||||
const wordPosition = this._getWordAt(coords, true);
|
const wordPosition = this._getWordAt(coords, true);
|
||||||
if (wordPosition) {
|
if (wordPosition) {
|
||||||
let endRow = coords[1];
|
this._model.selectionEnd = [this._model.areSelectionValuesReversed() ? wordPosition.start : (wordPosition.start + wordPosition.length), coords[1]];
|
||||||
|
|
||||||
// Adjust negative start value
|
|
||||||
while (wordPosition.start < 0) {
|
|
||||||
wordPosition.start += this._terminal.cols;
|
|
||||||
endRow--;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Adjust wrapped length value, this only needs to happen when values are reversed as in that
|
|
||||||
// case we're interested in the start of the word, not the end
|
|
||||||
if (!this._model.areSelectionValuesReversed()) {
|
|
||||||
while (wordPosition.start + wordPosition.length > this._terminal.cols) {
|
|
||||||
wordPosition.length -= this._terminal.cols;
|
|
||||||
endRow++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this._model.selectionEnd = [this._model.areSelectionValuesReversed() ? wordPosition.start : wordPosition.start + wordPosition.length, endRow];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -128,11 +128,6 @@ describe('SelectionManager', () => {
|
|||||||
model.selectionEnd = [3, 2];
|
model.selectionEnd = [3, 2];
|
||||||
assert.deepEqual(model.finalSelectionEnd, [4, 2]);
|
assert.deepEqual(model.finalSelectionEnd, [4, 2]);
|
||||||
});
|
});
|
||||||
it('should return the end on a different row when start + length overflows onto a following row', () => {
|
|
||||||
model.selectionStart = [78, 2];
|
|
||||||
model.selectionStartLength = 4;
|
|
||||||
assert.deepEqual(model.finalSelectionEnd, [2, 3]);
|
|
||||||
});
|
|
||||||
it('should return selection end if selection end is after selection start + length', () => {
|
it('should return selection end if selection end is after selection start + length', () => {
|
||||||
model.selectionStart = [2, 2];
|
model.selectionStart = [2, 2];
|
||||||
model.selectionStartLength = 2;
|
model.selectionStartLength = 2;
|
||||||
|
|||||||
@@ -76,13 +76,9 @@ export class SelectionModel {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use the selection start + length if the end doesn't exist or they're reversed
|
// Use the selection start if the end doesn't exist or they're reversed
|
||||||
if (!this.selectionEnd || this.areSelectionValuesReversed()) {
|
if (!this.selectionEnd || this.areSelectionValuesReversed()) {
|
||||||
const startPlusLength = this.selectionStart[0] + this.selectionStartLength;
|
return [this.selectionStart[0] + this.selectionStartLength, this.selectionStart[1]];
|
||||||
if (startPlusLength > this._terminal.cols) {
|
|
||||||
return [startPlusLength % this._terminal.cols, this.selectionStart[1] + Math.floor(startPlusLength / this._terminal.cols)];
|
|
||||||
}
|
|
||||||
return [startPlusLength, this.selectionStart[1]];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure the the word/line is selected after a double/triple click
|
// Ensure the the word/line is selected after a double/triple click
|
||||||
|
|||||||
+71
-103
@@ -21,11 +21,11 @@
|
|||||||
* http://linux.die.net/man/7/urxvt
|
* http://linux.die.net/man/7/urxvt
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminalOptions, ITerminal, IBrowser, ILinkifier, ILinkMatcherOptions, CustomKeyEventHandler, LinkMatcherHandler, CharData, LineData, CharacterJoinerHandler } from './Types';
|
import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminalOptions, ITerminal, IBrowser, ILinkifier, ILinkMatcherOptions, CustomKeyEventHandler, LinkMatcherHandler, CharData, LineData } from './Types';
|
||||||
import { IMouseZoneManager } from './ui/Types';
|
import { IMouseZoneManager } from './ui/Types';
|
||||||
import { IRenderer } from './renderer/Types';
|
import { IRenderer } from './renderer/Types';
|
||||||
import { BufferSet } from './BufferSet';
|
import { BufferSet } from './BufferSet';
|
||||||
import { Buffer, MAX_BUFFER_SIZE, DEFAULT_ATTR, NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR } from './Buffer';
|
import { Buffer, MAX_BUFFER_SIZE, DEFAULT_ATTR } from './Buffer';
|
||||||
import { CompositionHelper } from './CompositionHelper';
|
import { CompositionHelper } from './CompositionHelper';
|
||||||
import { EventEmitter } from './EventEmitter';
|
import { EventEmitter } from './EventEmitter';
|
||||||
import { Viewport } from './Viewport';
|
import { Viewport } from './Viewport';
|
||||||
@@ -72,7 +72,7 @@ const WRITE_BATCH_SIZE = 300;
|
|||||||
/**
|
/**
|
||||||
* The set of options that only have an effect when set in the Terminal constructor.
|
* The set of options that only have an effect when set in the Terminal constructor.
|
||||||
*/
|
*/
|
||||||
const CONSTRUCTOR_ONLY_OPTIONS = ['cols', 'rows'];
|
const CONSTRUCTOR_ONLY_OPTIONS = ['cols', 'rows', 'rendererType'];
|
||||||
|
|
||||||
const DEFAULT_OPTIONS: ITerminalOptions = {
|
const DEFAULT_OPTIONS: ITerminalOptions = {
|
||||||
cols: 80,
|
cols: 80,
|
||||||
@@ -135,6 +135,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
|||||||
public cursorHidden: boolean;
|
public cursorHidden: boolean;
|
||||||
public convertEol: boolean;
|
public convertEol: boolean;
|
||||||
|
|
||||||
|
private _sendDataQueue: string;
|
||||||
private _customKeyEventHandler: CustomKeyEventHandler;
|
private _customKeyEventHandler: CustomKeyEventHandler;
|
||||||
|
|
||||||
// modes
|
// modes
|
||||||
@@ -206,7 +207,6 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
|||||||
public mouseHelper: MouseHelper;
|
public mouseHelper: MouseHelper;
|
||||||
private _accessibilityManager: AccessibilityManager;
|
private _accessibilityManager: AccessibilityManager;
|
||||||
private _screenDprMonitor: ScreenDprMonitor;
|
private _screenDprMonitor: ScreenDprMonitor;
|
||||||
private _theme: ITheme;
|
|
||||||
|
|
||||||
public cols: number;
|
public cols: number;
|
||||||
public rows: number;
|
public rows: number;
|
||||||
@@ -214,7 +214,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
|||||||
/**
|
/**
|
||||||
* Creates a new `Terminal` object.
|
* Creates a new `Terminal` object.
|
||||||
*
|
*
|
||||||
* @param options An object containing a set of options, the available options are:
|
* @param {object} options An object containing a set of options, the available options are:
|
||||||
* - `cursorBlink` (boolean): Whether the terminal cursor blinks
|
* - `cursorBlink` (boolean): Whether the terminal cursor blinks
|
||||||
* - `cols` (number): The number of columns of the terminal (horizontal size)
|
* - `cols` (number): The number of columns of the terminal (horizontal size)
|
||||||
* - `rows` (number): The number of rows of the terminal (vertical size)
|
* - `rows` (number): The number of rows of the terminal (vertical size)
|
||||||
@@ -270,6 +270,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
|||||||
|
|
||||||
this.cursorState = 0;
|
this.cursorState = 0;
|
||||||
this.cursorHidden = false;
|
this.cursorHidden = false;
|
||||||
|
this._sendDataQueue = '';
|
||||||
this._customKeyEventHandler = null;
|
this._customKeyEventHandler = null;
|
||||||
|
|
||||||
// modes
|
// modes
|
||||||
@@ -347,7 +348,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves an option's value from the terminal.
|
* Retrieves an option's value from the terminal.
|
||||||
* @param key The option key.
|
* @param {string} key The option key.
|
||||||
*/
|
*/
|
||||||
public getOption(key: string): any {
|
public getOption(key: string): any {
|
||||||
if (!(key in DEFAULT_OPTIONS)) {
|
if (!(key in DEFAULT_OPTIONS)) {
|
||||||
@@ -359,8 +360,8 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets an option on the terminal.
|
* Sets an option on the terminal.
|
||||||
* @param key The option key.
|
* @param {string} key The option key.
|
||||||
* @param value The option value.
|
* @param {any} value The option value.
|
||||||
*/
|
*/
|
||||||
public setOption(key: string, value: any): void {
|
public setOption(key: string, value: any): void {
|
||||||
if (!(key in DEFAULT_OPTIONS)) {
|
if (!(key in DEFAULT_OPTIONS)) {
|
||||||
@@ -369,9 +370,6 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
|||||||
if (CONSTRUCTOR_ONLY_OPTIONS.indexOf(key) !== -1) {
|
if (CONSTRUCTOR_ONLY_OPTIONS.indexOf(key) !== -1) {
|
||||||
console.error(`Option "${key}" can only be set in the constructor`);
|
console.error(`Option "${key}" can only be set in the constructor`);
|
||||||
}
|
}
|
||||||
if (this.options[key] === value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case 'bellStyle':
|
case 'bellStyle':
|
||||||
if (!value) {
|
if (!value) {
|
||||||
@@ -398,11 +396,6 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
|||||||
console.warn(`${key} cannot be less than 1, value: ${value}`);
|
console.warn(`${key} cannot be less than 1, value: ${value}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case 'rendererType':
|
|
||||||
if (!value) {
|
|
||||||
value = 'canvas';
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'tabStopWidth':
|
case 'tabStopWidth':
|
||||||
if (value < 1) {
|
if (value < 1) {
|
||||||
console.warn(`${key} cannot be less than 1, value: ${value}`);
|
console.warn(`${key} cannot be less than 1, value: ${value}`);
|
||||||
@@ -462,18 +455,6 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
|||||||
this.renderer.onResize(this.cols, this.rows);
|
this.renderer.onResize(this.cols, this.rows);
|
||||||
this.refresh(0, this.rows - 1);
|
this.refresh(0, this.rows - 1);
|
||||||
}
|
}
|
||||||
case 'rendererType':
|
|
||||||
if (this.renderer) {
|
|
||||||
this.unregister(this.renderer);
|
|
||||||
this.renderer.dispose();
|
|
||||||
this.renderer = null;
|
|
||||||
}
|
|
||||||
this._setupRenderer();
|
|
||||||
this.renderer.onCharSizeChanged();
|
|
||||||
if (this._theme) {
|
|
||||||
this.renderer.setTheme(this._theme);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'scrollback':
|
case 'scrollback':
|
||||||
this.buffers.resize(this.cols, this.rows);
|
this.buffers.resize(this.cols, this.rows);
|
||||||
if (this.viewport) {
|
if (this.viewport) {
|
||||||
@@ -505,7 +486,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
|||||||
*/
|
*/
|
||||||
private _onTextAreaFocus(): void {
|
private _onTextAreaFocus(): void {
|
||||||
if (this.sendFocus) {
|
if (this.sendFocus) {
|
||||||
this.handler(C0.ESC + '[I');
|
this.send(C0.ESC + '[I');
|
||||||
}
|
}
|
||||||
this.element.classList.add('focus');
|
this.element.classList.add('focus');
|
||||||
this.showCursor();
|
this.showCursor();
|
||||||
@@ -529,7 +510,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
|||||||
this.textarea.value = '';
|
this.textarea.value = '';
|
||||||
this.refresh(this.buffer.y, this.buffer.y);
|
this.refresh(this.buffer.y, this.buffer.y);
|
||||||
if (this.sendFocus) {
|
if (this.sendFocus) {
|
||||||
this.handler(C0.ESC + '[O');
|
this.send(C0.ESC + '[O');
|
||||||
}
|
}
|
||||||
this.element.classList.remove('focus');
|
this.element.classList.remove('focus');
|
||||||
this.emit('blur');
|
this.emit('blur');
|
||||||
@@ -621,7 +602,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
|||||||
/**
|
/**
|
||||||
* Opens the terminal within an element.
|
* Opens the terminal within an element.
|
||||||
*
|
*
|
||||||
* @param parent The element to create the terminal within.
|
* @param {HTMLElement} parent The element to create the terminal within.
|
||||||
*/
|
*/
|
||||||
public open(parent: HTMLElement): void {
|
public open(parent: HTMLElement): void {
|
||||||
this._parent = parent || this._parent;
|
this._parent = parent || this._parent;
|
||||||
@@ -693,8 +674,12 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
|||||||
// Performance: Add viewport and helper elements from the fragment
|
// Performance: Add viewport and helper elements from the fragment
|
||||||
this.element.appendChild(fragment);
|
this.element.appendChild(fragment);
|
||||||
|
|
||||||
this._setupRenderer();
|
switch (this.options.rendererType) {
|
||||||
this._theme = this.options.theme;
|
case 'canvas': this.renderer = new Renderer(this, this.options.theme); break;
|
||||||
|
case 'dom': this.renderer = new DomRenderer(this, this.options.theme); break;
|
||||||
|
default: throw new Error(`Unrecognized rendererType "${this.options.rendererType}"`);
|
||||||
|
}
|
||||||
|
this.register(this.renderer);
|
||||||
this.options.theme = null;
|
this.options.theme = null;
|
||||||
this.viewport = new Viewport(this, this._viewportElement, this._viewportScrollArea, this.charMeasure);
|
this.viewport = new Viewport(this, this._viewportElement, this._viewportScrollArea, this.charMeasure);
|
||||||
this.viewport.onThemeChanged(this.renderer.colorManager.colors);
|
this.viewport.onThemeChanged(this.renderer.colorManager.colors);
|
||||||
@@ -751,21 +736,11 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _setupRenderer(): void {
|
|
||||||
switch (this.options.rendererType) {
|
|
||||||
case 'canvas': this.renderer = new Renderer(this, this.options.theme); break;
|
|
||||||
case 'dom': this.renderer = new DomRenderer(this, this.options.theme); break;
|
|
||||||
default: throw new Error(`Unrecognized rendererType "${this.options.rendererType}"`);
|
|
||||||
}
|
|
||||||
this.register(this.renderer);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the theme on the renderer. The renderer must have been initialized.
|
* Sets the theme on the renderer. The renderer must have been initialized.
|
||||||
* @param theme The theme to set.
|
* @param theme The theme to ste.
|
||||||
*/
|
*/
|
||||||
private _setTheme(theme: ITheme): void {
|
private _setTheme(theme: ITheme): void {
|
||||||
this._theme = theme;
|
|
||||||
const colors = this.renderer.setTheme(theme);
|
const colors = this.renderer.setTheme(theme);
|
||||||
if (this.viewport) {
|
if (this.viewport) {
|
||||||
this.viewport.onThemeChanged(colors);
|
this.viewport.onThemeChanged(colors);
|
||||||
@@ -885,7 +860,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
|||||||
else if (button === 3) return;
|
else if (button === 3) return;
|
||||||
else data += '0';
|
else data += '0';
|
||||||
data += '~[' + pos.x + ',' + pos.y + ']\r';
|
data += '~[' + pos.x + ',' + pos.y + ']\r';
|
||||||
self.handler(data);
|
self.send(data);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -898,7 +873,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
|||||||
else if (button === 1) button = 4;
|
else if (button === 1) button = 4;
|
||||||
else if (button === 2) button = 6;
|
else if (button === 2) button = 6;
|
||||||
else if (button === 3) button = 3;
|
else if (button === 3) button = 3;
|
||||||
self.handler(C0.ESC + '['
|
self.send(C0.ESC + '['
|
||||||
+ button
|
+ button
|
||||||
+ ';'
|
+ ';'
|
||||||
+ (button === 3 ? 4 : 0)
|
+ (button === 3 ? 4 : 0)
|
||||||
@@ -918,14 +893,14 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
|||||||
pos.y -= 32;
|
pos.y -= 32;
|
||||||
pos.x++;
|
pos.x++;
|
||||||
pos.y++;
|
pos.y++;
|
||||||
self.handler(C0.ESC + '[' + button + ';' + pos.x + ';' + pos.y + 'M');
|
self.send(C0.ESC + '[' + button + ';' + pos.x + ';' + pos.y + 'M');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (self.sgrMouse) {
|
if (self.sgrMouse) {
|
||||||
pos.x -= 32;
|
pos.x -= 32;
|
||||||
pos.y -= 32;
|
pos.y -= 32;
|
||||||
self.handler(C0.ESC + '[<'
|
self.send(C0.ESC + '[<'
|
||||||
+ (((button & 3) === 3 ? button & ~3 : button) - 32)
|
+ (((button & 3) === 3 ? button & ~3 : button) - 32)
|
||||||
+ ';'
|
+ ';'
|
||||||
+ pos.x
|
+ pos.x
|
||||||
@@ -941,7 +916,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
|||||||
encode(data, pos.x);
|
encode(data, pos.x);
|
||||||
encode(data, pos.y);
|
encode(data, pos.y);
|
||||||
|
|
||||||
self.handler(C0.ESC + '[M' + String.fromCharCode.apply(String, data));
|
self.send(C0.ESC + '[M' + String.fromCharCode.apply(String, data));
|
||||||
}
|
}
|
||||||
|
|
||||||
function getButton(ev: MouseEvent): number {
|
function getButton(ev: MouseEvent): number {
|
||||||
@@ -1090,7 +1065,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
|||||||
for (let i = 0; i < Math.abs(amount); i++) {
|
for (let i = 0; i < Math.abs(amount); i++) {
|
||||||
data += sequence;
|
data += sequence;
|
||||||
}
|
}
|
||||||
this.handler(data);
|
this.send(data);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1123,8 +1098,8 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
|||||||
/**
|
/**
|
||||||
* Tells the renderer to refresh terminal content between two rows (inclusive) at the next
|
* Tells the renderer to refresh terminal content between two rows (inclusive) at the next
|
||||||
* opportunity.
|
* opportunity.
|
||||||
* @param start The row to start from (between 0 and this.rows - 1).
|
* @param {number} start The row to start from (between 0 and this.rows - 1).
|
||||||
* @param end The row to end at (between start and this.rows - 1).
|
* @param {number} end The row to end at (between start and this.rows - 1).
|
||||||
*/
|
*/
|
||||||
public refresh(start: number, end: number): void {
|
public refresh(start: number, end: number): void {
|
||||||
if (this.renderer) {
|
if (this.renderer) {
|
||||||
@@ -1134,8 +1109,8 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Queues linkification for the specified rows.
|
* Queues linkification for the specified rows.
|
||||||
* @param start The row to start from (between 0 and this.rows - 1).
|
* @param {number} start The row to start from (between 0 and this.rows - 1).
|
||||||
* @param end The row to end at (between start and this.rows - 1).
|
* @param {number} end The row to end at (between start and this.rows - 1).
|
||||||
*/
|
*/
|
||||||
private _queueLinkification(start: number, end: number): void {
|
private _queueLinkification(start: number, end: number): void {
|
||||||
if (this.linkifier) {
|
if (this.linkifier) {
|
||||||
@@ -1227,8 +1202,8 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Scroll the display of the terminal
|
* Scroll the display of the terminal
|
||||||
* @param disp The number of lines to scroll down (negative scroll up).
|
* @param {number} disp The number of lines to scroll down (negative scroll up).
|
||||||
* @param suppressScrollEvent Don't emit the scroll event as scrollLines. This is used
|
* @param {boolean} suppressScrollEvent Don't emit the scroll event as scrollLines. This is used
|
||||||
* to avoid unwanted events being handled by the viewport when the event was triggered from the
|
* to avoid unwanted events being handled by the viewport when the event was triggered from the
|
||||||
* viewport originally.
|
* viewport originally.
|
||||||
*/
|
*/
|
||||||
@@ -1259,7 +1234,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Scroll the display of the terminal by a number of pages.
|
* Scroll the display of the terminal by a number of pages.
|
||||||
* @param pageCount The number of pages to scroll (negative scrolls up).
|
* @param {number} pageCount The number of pages to scroll (negative scrolls up).
|
||||||
*/
|
*/
|
||||||
public scrollPages(pageCount: number): void {
|
public scrollPages(pageCount: number): void {
|
||||||
this.scrollLines(pageCount * (this.rows - 1));
|
this.scrollLines(pageCount * (this.rows - 1));
|
||||||
@@ -1288,14 +1263,9 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Writes text to the terminal.
|
* Writes text to the terminal.
|
||||||
* @param data The text to write to the terminal.
|
* @param {string} data The text to write to the terminal.
|
||||||
*/
|
*/
|
||||||
public write(data: string): void {
|
public write(data: string): void {
|
||||||
// Ensure the terminal isn't disposed
|
|
||||||
if (this._isDisposed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ignore falsy data values (including the empty string)
|
// Ignore falsy data values (including the empty string)
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return;
|
return;
|
||||||
@@ -1309,7 +1279,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
|||||||
if (this.options.useFlowControl && !this._xoffSentToCatchUp && this.writeBuffer.length >= WRITE_BUFFER_PAUSE_THRESHOLD) {
|
if (this.options.useFlowControl && !this._xoffSentToCatchUp && this.writeBuffer.length >= WRITE_BUFFER_PAUSE_THRESHOLD) {
|
||||||
// XOFF - stop pty pipe
|
// XOFF - stop pty pipe
|
||||||
// XON will be triggered by emulator before processing data chunk
|
// XON will be triggered by emulator before processing data chunk
|
||||||
this.handler(C0.DC3);
|
this.send(C0.DC3);
|
||||||
this._xoffSentToCatchUp = true;
|
this._xoffSentToCatchUp = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1324,11 +1294,6 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected _innerWrite(): void {
|
protected _innerWrite(): void {
|
||||||
// Ensure the terminal isn't disposed
|
|
||||||
if (this._isDisposed) {
|
|
||||||
this.writeBuffer = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const writeBatch = this.writeBuffer.splice(0, WRITE_BATCH_SIZE);
|
const writeBatch = this.writeBuffer.splice(0, WRITE_BATCH_SIZE);
|
||||||
while (writeBatch.length > 0) {
|
while (writeBatch.length > 0) {
|
||||||
const data = writeBatch.shift();
|
const data = writeBatch.shift();
|
||||||
@@ -1336,7 +1301,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
|||||||
// If XOFF was sent in order to catch up with the pty process, resume it if
|
// If XOFF was sent in order to catch up with the pty process, resume it if
|
||||||
// the writeBuffer is empty to allow more data to come in.
|
// the writeBuffer is empty to allow more data to come in.
|
||||||
if (this._xoffSentToCatchUp && writeBatch.length === 0 && this.writeBuffer.length === 0) {
|
if (this._xoffSentToCatchUp && writeBatch.length === 0 && this.writeBuffer.length === 0) {
|
||||||
this.handler(C0.DC1);
|
this.send(C0.DC1);
|
||||||
this._xoffSentToCatchUp = false;
|
this._xoffSentToCatchUp = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1364,7 +1329,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Writes text to the terminal, followed by a break line character (\n).
|
* Writes text to the terminal, followed by a break line character (\n).
|
||||||
* @param data The text to write to the terminal.
|
* @param {string} data The text to write to the terminal.
|
||||||
*/
|
*/
|
||||||
public writeln(data: string): void {
|
public writeln(data: string): void {
|
||||||
this.write(data + '\r\n');
|
this.write(data + '\r\n');
|
||||||
@@ -1409,18 +1374,6 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public registerCharacterJoiner(handler: CharacterJoinerHandler): number {
|
|
||||||
const joinerId = this.renderer.registerCharacterJoiner(handler);
|
|
||||||
this.refresh(0, this.rows - 1);
|
|
||||||
return joinerId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public deregisterCharacterJoiner(joinerId: number): void {
|
|
||||||
if (this.renderer.deregisterCharacterJoiner(joinerId)) {
|
|
||||||
this.refresh(0, this.rows - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public get markers(): IMarker[] {
|
public get markers(): IMarker[] {
|
||||||
return this.buffer.markers;
|
return this.buffer.markers;
|
||||||
}
|
}
|
||||||
@@ -1477,7 +1430,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
|||||||
* Handle a keydown event
|
* Handle a keydown event
|
||||||
* Key Resources:
|
* Key Resources:
|
||||||
* - https://developer.mozilla.org/en-US/docs/DOM/KeyboardEvent
|
* - https://developer.mozilla.org/en-US/docs/DOM/KeyboardEvent
|
||||||
* @param ev The keydown event to be handled.
|
* @param {KeyboardEvent} ev The keydown event to be handled.
|
||||||
*/
|
*/
|
||||||
protected _keyDown(event: KeyboardEvent): boolean {
|
protected _keyDown(event: KeyboardEvent): boolean {
|
||||||
if (this._customKeyEventHandler && this._customKeyEventHandler(event) === false) {
|
if (this._customKeyEventHandler && this._customKeyEventHandler(event) === false) {
|
||||||
@@ -1574,7 +1527,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
|||||||
* Handle a keypress event.
|
* Handle a keypress event.
|
||||||
* Key Resources:
|
* Key Resources:
|
||||||
* - https://developer.mozilla.org/en-US/docs/DOM/KeyboardEvent
|
* - https://developer.mozilla.org/en-US/docs/DOM/KeyboardEvent
|
||||||
* @param ev The keypress event to be handled.
|
* @param {KeyboardEvent} ev The keypress event to be handled.
|
||||||
*/
|
*/
|
||||||
protected _keyPress(ev: KeyboardEvent): boolean {
|
protected _keyPress(ev: KeyboardEvent): boolean {
|
||||||
let key;
|
let key;
|
||||||
@@ -1611,6 +1564,21 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send data for handling to the terminal
|
||||||
|
* @param {string} data
|
||||||
|
*/
|
||||||
|
public send(data: string): void {
|
||||||
|
if (!this._sendDataQueue) {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.handler(this._sendDataQueue);
|
||||||
|
this._sendDataQueue = '';
|
||||||
|
}, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._sendDataQueue += data;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ring the bell.
|
* Ring the bell.
|
||||||
* Note: We could do sweet things with webaudio here
|
* Note: We could do sweet things with webaudio here
|
||||||
@@ -1651,8 +1619,8 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
|||||||
/**
|
/**
|
||||||
* Resizes the terminal.
|
* Resizes the terminal.
|
||||||
*
|
*
|
||||||
* @param x The number of columns to resize to.
|
* @param {number} x The number of columns to resize to.
|
||||||
* @param y The number of rows to resize to.
|
* @param {number} y The number of rows to resize to.
|
||||||
*/
|
*/
|
||||||
public resize(x: number, y: number): void {
|
public resize(x: number, y: number): void {
|
||||||
if (isNaN(x) || isNaN(y)) {
|
if (isNaN(x) || isNaN(y)) {
|
||||||
@@ -1686,7 +1654,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the range of rows to refresh
|
* Updates the range of rows to refresh
|
||||||
* @param y The number of rows to refresh next.
|
* @param {number} y The number of rows to refresh next.
|
||||||
*/
|
*/
|
||||||
public updateRange(y: number): void {
|
public updateRange(y: number): void {
|
||||||
if (y < this._refreshStart) this._refreshStart = y;
|
if (y < this._refreshStart) this._refreshStart = y;
|
||||||
@@ -1709,15 +1677,15 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Erase in the identified line everything from "x" to the end of the line (right).
|
* Erase in the identified line everything from "x" to the end of the line (right).
|
||||||
* @param x The column from which to start erasing to the end of the line.
|
* @param {number} x The column from which to start erasing to the end of the line.
|
||||||
* @param y The line in which to operate.
|
* @param {number} y The line in which to operate.
|
||||||
*/
|
*/
|
||||||
public eraseRight(x: number, y: number): void {
|
public eraseRight(x: number, y: number): void {
|
||||||
const line = this.buffer.lines.get(this.buffer.ybase + y);
|
const line = this.buffer.lines.get(this.buffer.ybase + y);
|
||||||
if (!line) {
|
if (!line) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const ch: CharData = [this.eraseAttr(), NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]; // xterm
|
const ch: CharData = [this.eraseAttr(), ' ', 1, 32 /* ' '.charCodeAt(0) */]; // xterm
|
||||||
for (; x < this.cols; x++) {
|
for (; x < this.cols; x++) {
|
||||||
line[x] = ch;
|
line[x] = ch;
|
||||||
}
|
}
|
||||||
@@ -1726,15 +1694,15 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Erase in the identified line everything from "x" to the start of the line (left).
|
* Erase in the identified line everything from "x" to the start of the line (left).
|
||||||
* @param x The column from which to start erasing to the start of the line.
|
* @param {number} x The column from which to start erasing to the start of the line.
|
||||||
* @param y The line in which to operate.
|
* @param {number} y The line in which to operate.
|
||||||
*/
|
*/
|
||||||
public eraseLeft(x: number, y: number): void {
|
public eraseLeft(x: number, y: number): void {
|
||||||
const line = this.buffer.lines.get(this.buffer.ybase + y);
|
const line = this.buffer.lines.get(this.buffer.ybase + y);
|
||||||
if (!line) {
|
if (!line) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const ch: CharData = [this.eraseAttr(), NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]; // xterm
|
const ch: CharData = [this.eraseAttr(), ' ', 1, 32 /* ' '.charCodeAt(0) */]; // xterm
|
||||||
x++;
|
x++;
|
||||||
while (x--) {
|
while (x--) {
|
||||||
line[x] = ch;
|
line[x] = ch;
|
||||||
@@ -1764,7 +1732,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Erase all content in the given line
|
* Erase all content in the given line
|
||||||
* @param y The line to erase all of its contents.
|
* @param {number} y The line to erase all of its contents.
|
||||||
*/
|
*/
|
||||||
public eraseLine(y: number): void {
|
public eraseLine(y: number): void {
|
||||||
this.eraseRight(0, y);
|
this.eraseRight(0, y);
|
||||||
@@ -1772,15 +1740,15 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the data array of a blank line
|
* Return the data array of a blank line
|
||||||
* @param cur First bunch of data for each "blank" character.
|
* @param {boolean} cur First bunch of data for each "blank" character.
|
||||||
* @param isWrapped Whether the new line is wrapped from the previous line.
|
* @param {boolean} isWrapped Whether the new line is wrapped from the previous line.
|
||||||
* @param cols The number of columns in the terminal, if this is not
|
* @param {boolean} cols The number of columns in the terminal, if this is not
|
||||||
* set, the terminal's current column count would be used.
|
* set, the terminal's current column count would be used.
|
||||||
*/
|
*/
|
||||||
public blankLine(cur?: boolean, isWrapped?: boolean, cols?: number): LineData {
|
public blankLine(cur?: boolean, isWrapped?: boolean, cols?: number): LineData {
|
||||||
const attr = cur ? this.eraseAttr() : DEFAULT_ATTR;
|
const attr = cur ? this.eraseAttr() : DEFAULT_ATTR;
|
||||||
|
|
||||||
const ch: CharData = [attr, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]; // width defaults to 1 halfwidth character
|
const ch: CharData = [attr, ' ', 1, 32 /* ' '.charCodeAt(0) */]; // width defaults to 1 halfwidth character
|
||||||
const line: LineData = [];
|
const line: LineData = [];
|
||||||
|
|
||||||
// TODO: It is not ideal that this is a property on an array, a buffer line
|
// TODO: It is not ideal that this is a property on an array, a buffer line
|
||||||
@@ -1803,9 +1771,9 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
|||||||
*/
|
*/
|
||||||
public ch(cur?: boolean): CharData {
|
public ch(cur?: boolean): CharData {
|
||||||
if (cur) {
|
if (cur) {
|
||||||
return [this.eraseAttr(), NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE];
|
return [this.eraseAttr(), ' ', 1, 32 /* ' '.charCodeAt(0) */];
|
||||||
}
|
}
|
||||||
return [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE];
|
return [DEFAULT_ATTR, ' ', 1, 32 /* ' '.charCodeAt(0) */];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1818,7 +1786,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Emit the 'data' event and populate the given data.
|
* Emit the 'data' event and populate the given data.
|
||||||
* @param data The data to populate in the event.
|
* @param {string} data The data to populate in the event.
|
||||||
*/
|
*/
|
||||||
public handler(data: string): void {
|
public handler(data: string): void {
|
||||||
// Prevents all events to pty process if stdin is disabled
|
// Prevents all events to pty process if stdin is disabled
|
||||||
@@ -1840,7 +1808,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Emit the 'title' event and populate the given title.
|
* Emit the 'title' event and populate the given title.
|
||||||
* @param title The title to populate in the event.
|
* @param {string} title The title to populate in the event.
|
||||||
*/
|
*/
|
||||||
public handleTitle(title: string): void {
|
public handleTitle(title: string): void {
|
||||||
/**
|
/**
|
||||||
|
|||||||
+2
-7
@@ -18,8 +18,6 @@ export type LineData = CharData[];
|
|||||||
export type LinkMatcherHandler = (event: MouseEvent, uri: string) => void;
|
export type LinkMatcherHandler = (event: MouseEvent, uri: string) => void;
|
||||||
export type LinkMatcherValidationCallback = (uri: string, callback: (isValid: boolean) => void) => void;
|
export type LinkMatcherValidationCallback = (uri: string, callback: (isValid: boolean) => void) => void;
|
||||||
|
|
||||||
export type CharacterJoinerHandler = (text: string) => [number, number][];
|
|
||||||
|
|
||||||
export const enum LinkHoverEventTypes {
|
export const enum LinkHoverEventTypes {
|
||||||
HOVER = 'linkhover',
|
HOVER = 'linkhover',
|
||||||
TOOLTIP = 'linktooltip',
|
TOOLTIP = 'linktooltip',
|
||||||
@@ -47,7 +45,6 @@ export interface IInputHandlingTerminal extends IEventEmitter {
|
|||||||
wraparoundMode: boolean;
|
wraparoundMode: boolean;
|
||||||
bracketedPasteMode: boolean;
|
bracketedPasteMode: boolean;
|
||||||
curAttr: number;
|
curAttr: number;
|
||||||
savedCurAttr: number;
|
|
||||||
savedCols: number;
|
savedCols: number;
|
||||||
x10Mouse: boolean;
|
x10Mouse: boolean;
|
||||||
vt200Mouse: boolean;
|
vt200Mouse: boolean;
|
||||||
@@ -76,6 +73,7 @@ export interface IInputHandlingTerminal extends IEventEmitter {
|
|||||||
eraseLeft(x: number, y: number): void;
|
eraseLeft(x: number, y: number): void;
|
||||||
blankLine(cur?: boolean, isWrapped?: boolean): LineData;
|
blankLine(cur?: boolean, isWrapped?: boolean): LineData;
|
||||||
is(term: string): boolean;
|
is(term: string): boolean;
|
||||||
|
send(data: string): void;
|
||||||
setgCharset(g: number, charset: ICharset): void;
|
setgCharset(g: number, charset: ICharset): void;
|
||||||
resize(x: number, y: number): void;
|
resize(x: number, y: number): void;
|
||||||
log(text: string, data?: any): void;
|
log(text: string, data?: any): void;
|
||||||
@@ -86,10 +84,6 @@ export interface IInputHandlingTerminal extends IEventEmitter {
|
|||||||
error(text: string, data?: any): void;
|
error(text: string, data?: any): void;
|
||||||
setOption(key: string, value: any): void;
|
setOption(key: string, value: any): void;
|
||||||
tabSet(): void;
|
tabSet(): void;
|
||||||
handler(data: string): void;
|
|
||||||
handleTitle(title: string): void;
|
|
||||||
index(): void;
|
|
||||||
reverseIndex(): void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IViewport extends IDisposable {
|
export interface IViewport extends IDisposable {
|
||||||
@@ -229,6 +223,7 @@ export interface ITerminal extends PublicTerminal, IElementAccessor, IBufferAcce
|
|||||||
* @param data The data to populate in the event.
|
* @param data The data to populate in the event.
|
||||||
*/
|
*/
|
||||||
handler(data: string): void;
|
handler(data: string): void;
|
||||||
|
send(data: string): void;
|
||||||
scrollLines(disp: number, suppressScrollEvent?: boolean): void;
|
scrollLines(disp: number, suppressScrollEvent?: boolean): void;
|
||||||
cancel(ev: Event, force?: boolean): boolean | void;
|
cancel(ev: Event, force?: boolean): boolean | void;
|
||||||
log(text: string): void;
|
log(text: string): void;
|
||||||
|
|||||||
@@ -6,9 +6,6 @@
|
|||||||
import { Terminal } from 'xterm';
|
import { Terminal } from 'xterm';
|
||||||
import { IWinptyCompatAddonTerminal } from './Interfaces';
|
import { IWinptyCompatAddonTerminal } from './Interfaces';
|
||||||
|
|
||||||
const CHAR_DATA_CODE_INDEX = 3;
|
|
||||||
const NULL_CELL_CODE = 32;
|
|
||||||
|
|
||||||
export function winptyCompatInit(terminal: Terminal): void {
|
export function winptyCompatInit(terminal: Terminal): void {
|
||||||
const addonTerminal = <IWinptyCompatAddonTerminal>terminal;
|
const addonTerminal = <IWinptyCompatAddonTerminal>terminal;
|
||||||
|
|
||||||
@@ -32,7 +29,7 @@ export function winptyCompatInit(terminal: Terminal): void {
|
|||||||
const line = addonTerminal._core.buffer.lines.get(addonTerminal._core.buffer.ybase + addonTerminal._core.buffer.y - 1);
|
const line = addonTerminal._core.buffer.lines.get(addonTerminal._core.buffer.ybase + addonTerminal._core.buffer.y - 1);
|
||||||
const lastChar = line[addonTerminal.cols - 1];
|
const lastChar = line[addonTerminal.cols - 1];
|
||||||
|
|
||||||
if (lastChar[CHAR_DATA_CODE_INDEX] !== NULL_CELL_CODE) {
|
if (lastChar[3] !== 32 /* ' ' */) {
|
||||||
const nextLine = addonTerminal._core.buffer.lines.get(addonTerminal._core.buffer.ybase + addonTerminal._core.buffer.y);
|
const nextLine = addonTerminal._core.buffer.lines.get(addonTerminal._core.buffer.ybase + addonTerminal._core.buffer.y);
|
||||||
(<any>nextLine).isWrapped = true;
|
(<any>nextLine).isWrapped = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
|
|
||||||
* @license MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { assert } from 'chai';
|
|
||||||
import { Disposable } from './Lifecycle';
|
|
||||||
|
|
||||||
class TestDisposable extends Disposable {
|
|
||||||
public get isDisposed(): boolean {
|
|
||||||
return this._isDisposed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('Disposable', () => {
|
|
||||||
describe('register', () => {
|
|
||||||
it('should register disposables', () => {
|
|
||||||
const d = new TestDisposable();
|
|
||||||
const d2 = {
|
|
||||||
dispose: () => { throw new Error(); }
|
|
||||||
};
|
|
||||||
d.register(d2);
|
|
||||||
assert.throws(() => d.dispose());
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe('unregister', () => {
|
|
||||||
it('should unregister disposables', () => {
|
|
||||||
const d = new TestDisposable();
|
|
||||||
const d2 = {
|
|
||||||
dispose: () => { throw new Error(); }
|
|
||||||
};
|
|
||||||
d.register(d2);
|
|
||||||
d.unregister(d2);
|
|
||||||
assert.doesNotThrow(() => d.dispose());
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe('dispose', () => {
|
|
||||||
it('should set is disposed flag', () => {
|
|
||||||
const d = new TestDisposable();
|
|
||||||
assert.isFalse(d.isDisposed);
|
|
||||||
d.dispose();
|
|
||||||
assert.isTrue(d.isDisposed);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -11,7 +11,6 @@ import { IDisposable } from 'xterm';
|
|||||||
*/
|
*/
|
||||||
export abstract class Disposable implements IDisposable {
|
export abstract class Disposable implements IDisposable {
|
||||||
protected _disposables: IDisposable[] = [];
|
protected _disposables: IDisposable[] = [];
|
||||||
protected _isDisposed: boolean = false;
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
}
|
}
|
||||||
@@ -20,7 +19,6 @@ export abstract class Disposable implements IDisposable {
|
|||||||
* Disposes the object, triggering the `dispose` method on all registered IDisposables.
|
* Disposes the object, triggering the `dispose` method on all registered IDisposables.
|
||||||
*/
|
*/
|
||||||
public dispose(): void {
|
public dispose(): void {
|
||||||
this._isDisposed = true;
|
|
||||||
this._disposables.forEach(d => d.dispose());
|
this._disposables.forEach(d => d.dispose());
|
||||||
this._disposables.length = 0;
|
this._disposables.length = 0;
|
||||||
}
|
}
|
||||||
@@ -32,16 +30,4 @@ export abstract class Disposable implements IDisposable {
|
|||||||
public register<T extends IDisposable>(d: T): void {
|
public register<T extends IDisposable>(d: T): void {
|
||||||
this._disposables.push(d);
|
this._disposables.push(d);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Unregisters a disposable object if it has been registered, if not do
|
|
||||||
* nothing.
|
|
||||||
* @param d The disposable to unregister.
|
|
||||||
*/
|
|
||||||
public unregister<T extends IDisposable>(d: T): void {
|
|
||||||
const index = this._disposables.indexOf(d);
|
|
||||||
if (index !== -1) {
|
|
||||||
this._disposables.splice(index, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export class AltClickHandler {
|
|||||||
*/
|
*/
|
||||||
public move(): void {
|
public move(): void {
|
||||||
if (this._mouseEvent.altKey && this._endCol !== undefined && this._endRow !== undefined) {
|
if (this._mouseEvent.altKey && this._endCol !== undefined && this._endRow !== undefined) {
|
||||||
this._terminal.handler(this._arrowSequences());
|
this._terminal.send(this._arrowSequences());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export function bracketTextForPaste(text: string, bracketedPasteMode: boolean):
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Binds copy functionality to the given terminal.
|
* Binds copy functionality to the given terminal.
|
||||||
* @param ev The original copy event to be handled
|
* @param {ClipboardEvent} ev The original copy event to be handled
|
||||||
*/
|
*/
|
||||||
export function copyHandler(ev: ClipboardEvent, term: ITerminal, selectionManager: ISelectionManager): void {
|
export function copyHandler(ev: ClipboardEvent, term: ITerminal, selectionManager: ISelectionManager): void {
|
||||||
if (term.browser.isMSIE) {
|
if (term.browser.isMSIE) {
|
||||||
@@ -50,8 +50,8 @@ export function copyHandler(ev: ClipboardEvent, term: ITerminal, selectionManage
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Redirect the clipboard's data to the terminal's input handler.
|
* Redirect the clipboard's data to the terminal's input handler.
|
||||||
* @param ev The original paste event to be handled
|
* @param {ClipboardEvent} ev The original paste event to be handled
|
||||||
* @param term The terminal on which to apply the handled paste event
|
* @param {Terminal} term The terminal on which to apply the handled paste event
|
||||||
*/
|
*/
|
||||||
export function pasteHandler(ev: ClipboardEvent, term: ITerminal): void {
|
export function pasteHandler(ev: ClipboardEvent, term: ITerminal): void {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
|
|||||||
@@ -65,12 +65,6 @@ export class Terminal implements ITerminalApi {
|
|||||||
public deregisterLinkMatcher(matcherId: number): void {
|
public deregisterLinkMatcher(matcherId: number): void {
|
||||||
this._core.deregisterLinkMatcher(matcherId);
|
this._core.deregisterLinkMatcher(matcherId);
|
||||||
}
|
}
|
||||||
public registerCharacterJoiner(handler: (text: string) => [number, number][]): number {
|
|
||||||
return this._core.registerCharacterJoiner(handler);
|
|
||||||
}
|
|
||||||
public deregisterCharacterJoiner(joinerId: number): void {
|
|
||||||
this._core.deregisterCharacterJoiner(joinerId);
|
|
||||||
}
|
|
||||||
public addMarker(cursorYOffset: number): IMarker {
|
public addMarker(cursorYOffset: number): IMarker {
|
||||||
return this._core.addMarker(cursorYOffset);
|
return this._core.addMarker(cursorYOffset);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,10 +36,6 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
|||||||
this._container.appendChild(this._canvas);
|
this._container.appendChild(this._canvas);
|
||||||
}
|
}
|
||||||
|
|
||||||
public dispose(): void {
|
|
||||||
this._container.removeChild(this._canvas);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _initCanvas(): void {
|
private _initCanvas(): void {
|
||||||
this._ctx = this._canvas.getContext('2d', {alpha: this._alpha});
|
this._ctx = this._canvas.getContext('2d', {alpha: this._alpha});
|
||||||
// Draw the background if this is an opaque layer
|
// Draw the background if this is an opaque layer
|
||||||
@@ -228,12 +224,12 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Draws one or more characters at a cell. If possible this will draw using
|
* Draws a character at a cell. If possible this will draw using the character
|
||||||
* the character atlas to reduce draw time.
|
* atlas to reduce draw time.
|
||||||
* @param terminal The terminal.
|
* @param terminal The terminal.
|
||||||
* @param chars The character or characters.
|
* @param char The character.
|
||||||
* @param code The character code.
|
* @param code The character code.
|
||||||
* @param width The width of the characters.
|
* @param width The width of the character.
|
||||||
* @param x The column to draw at.
|
* @param x The column to draw at.
|
||||||
* @param y The row to draw at.
|
* @param y The row to draw at.
|
||||||
* @param fg The foreground color, in the format stored within the attributes.
|
* @param fg The foreground color, in the format stored within the attributes.
|
||||||
@@ -241,34 +237,33 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
|||||||
* This is used to validate whether a cached image can be used.
|
* This is used to validate whether a cached image can be used.
|
||||||
* @param bold Whether the text is bold.
|
* @param bold Whether the text is bold.
|
||||||
*/
|
*/
|
||||||
protected drawChars(terminal: ITerminal, chars: string, code: number, width: number, x: number, y: number, fg: number, bg: number, bold: boolean, dim: boolean, italic: boolean): void {
|
protected drawChar(terminal: ITerminal, char: string, code: number, width: number, x: number, y: number, fg: number, bg: number, bold: boolean, dim: boolean, italic: boolean): void {
|
||||||
const drawInBrightColor = terminal.options.drawBoldTextInBrightColors && bold && fg < 8 && fg !== INVERTED_DEFAULT_COLOR;
|
const drawInBrightColor = terminal.options.drawBoldTextInBrightColors && bold && fg < 8;
|
||||||
|
|
||||||
fg += drawInBrightColor ? 8 : 0;
|
fg += drawInBrightColor ? 8 : 0;
|
||||||
const atlasDidDraw = this._charAtlas && this._charAtlas.draw(
|
const atlasDidDraw = this._charAtlas && this._charAtlas.draw(
|
||||||
this._ctx,
|
this._ctx,
|
||||||
{chars, code, bg, fg, bold: bold && terminal.options.enableBold, dim, italic},
|
{char, code, bg, fg, bold: bold && terminal.options.enableBold, dim, italic},
|
||||||
x * this._scaledCellWidth + this._scaledCharLeft,
|
x * this._scaledCellWidth + this._scaledCharLeft,
|
||||||
y * this._scaledCellHeight + this._scaledCharTop
|
y * this._scaledCellHeight + this._scaledCharTop
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!atlasDidDraw) {
|
if (!atlasDidDraw) {
|
||||||
this._drawUncachedChars(terminal, chars, width, fg, x, y, bold && terminal.options.enableBold, dim, italic);
|
this._drawUncachedChar(terminal, char, width, fg, x, y, bold && terminal.options.enableBold, dim, italic);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Draws one or more characters at one or more cells. The character(s) will be
|
* Draws a character at a cell. The character will be clipped to
|
||||||
* clipped to ensure that they fit with the cell(s), including the cell to the
|
* ensure that it fits with the cell, including the cell to the right if it's
|
||||||
* right if the last character is a wide character.
|
* a wide character.
|
||||||
* @param terminal The terminal.
|
* @param terminal The terminal.
|
||||||
* @param chars The character.
|
* @param char The character.
|
||||||
* @param width The width of the character.
|
* @param width The width of the character.
|
||||||
* @param fg The foreground color, in the format stored within the attributes.
|
* @param fg The foreground color, in the format stored within the attributes.
|
||||||
* @param x The column to draw at.
|
* @param x The column to draw at.
|
||||||
* @param y The row to draw at.
|
* @param y The row to draw at.
|
||||||
*/
|
*/
|
||||||
private _drawUncachedChars(terminal: ITerminal, chars: string, width: number, fg: number, x: number, y: number, bold: boolean, dim: boolean, italic: boolean): void {
|
private _drawUncachedChar(terminal: ITerminal, char: string, width: number, fg: number, x: number, y: number, bold: boolean, dim: boolean, italic: boolean): void {
|
||||||
this._ctx.save();
|
this._ctx.save();
|
||||||
this._ctx.font = this._getFont(terminal, bold, italic);
|
this._ctx.font = this._getFont(terminal, bold, italic);
|
||||||
this._ctx.textBaseline = 'top';
|
this._ctx.textBaseline = 'top';
|
||||||
@@ -290,7 +285,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
|||||||
}
|
}
|
||||||
// Draw the character
|
// Draw the character
|
||||||
this._ctx.fillText(
|
this._ctx.fillText(
|
||||||
chars,
|
char,
|
||||||
x * this._scaledCellWidth + this._scaledCharLeft,
|
x * this._scaledCellWidth + this._scaledCharLeft,
|
||||||
y * this._scaledCellHeight + this._scaledCharTop);
|
y * this._scaledCellHeight + this._scaledCharTop);
|
||||||
this._ctx.restore();
|
this._ctx.restore();
|
||||||
|
|||||||
@@ -1,278 +0,0 @@
|
|||||||
import { assert } from 'chai';
|
|
||||||
|
|
||||||
import { LineData, CharData } from '../Types';
|
|
||||||
import { MockTerminal, MockBuffer } from '../utils/TestUtils.test';
|
|
||||||
import { CircularList } from '../common/CircularList';
|
|
||||||
|
|
||||||
import { ICharacterJoinerRegistry } from './Types';
|
|
||||||
import { CharacterJoinerRegistry } from './CharacterJoinerRegistry';
|
|
||||||
|
|
||||||
describe('CharacterJoinerRegistry', () => {
|
|
||||||
let registry: ICharacterJoinerRegistry;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
const terminal = new MockTerminal();
|
|
||||||
terminal.cols = 16;
|
|
||||||
terminal.buffer = new MockBuffer();
|
|
||||||
const lines = new CircularList<LineData>(7);
|
|
||||||
lines.set(0, lineData('a -> b -> c -> d'));
|
|
||||||
lines.set(1, lineData('a -> b => c -> d'));
|
|
||||||
lines.set(2, [...lineData('a -> b -', 0xFFFFFFFF), ...lineData('> c -> d', 0)]);
|
|
||||||
lines.set(3, lineData('no joined ranges'));
|
|
||||||
lines.set(4, []);
|
|
||||||
lines.set(5, [...lineData('a', 0x11111111), ...lineData(' -> b -> c -> '), ...lineData('d', 0x22222222)]);
|
|
||||||
lines.set(6, [
|
|
||||||
...lineData('wi'),
|
|
||||||
[0, '¥', 2, '¥'.charCodeAt(0)],
|
|
||||||
[0, '', 0, null],
|
|
||||||
...lineData('deemo'),
|
|
||||||
[0, '\xf0\x9f\x98\x81', 1, 128513],
|
|
||||||
[0, ' ', 1, ' '.charCodeAt(0)],
|
|
||||||
...lineData('jiabc')
|
|
||||||
]);
|
|
||||||
(<MockBuffer>terminal.buffer).setLines(lines);
|
|
||||||
terminal.buffer.ydisp = 0;
|
|
||||||
registry = new CharacterJoinerRegistry(terminal);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('has no joiners upon creation', () => {
|
|
||||||
assert.deepEqual(registry.getJoinedCharacters(0), []);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns ranges matched by the registered joiners', () => {
|
|
||||||
registry.registerCharacterJoiner(substringJoiner('->'));
|
|
||||||
assert.deepEqual(
|
|
||||||
registry.getJoinedCharacters(0),
|
|
||||||
[[2, 4], [7, 9], [12, 14]]
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('processes the input using all provided joiners', () => {
|
|
||||||
registry.registerCharacterJoiner(substringJoiner('->'));
|
|
||||||
assert.deepEqual(
|
|
||||||
registry.getJoinedCharacters(1),
|
|
||||||
[[2, 4], [12, 14]]
|
|
||||||
);
|
|
||||||
|
|
||||||
registry.registerCharacterJoiner(substringJoiner('=>'));
|
|
||||||
assert.deepEqual(
|
|
||||||
registry.getJoinedCharacters(1),
|
|
||||||
[[2, 4], [7, 9], [12, 14]]
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('removes deregistered joiners from future calls', () => {
|
|
||||||
const joiner1 = registry.registerCharacterJoiner(substringJoiner('->'));
|
|
||||||
const joiner2 = registry.registerCharacterJoiner(substringJoiner('=>'));
|
|
||||||
assert.deepEqual(
|
|
||||||
registry.getJoinedCharacters(1),
|
|
||||||
[[2, 4], [7, 9], [12, 14]]
|
|
||||||
);
|
|
||||||
|
|
||||||
registry.deregisterCharacterJoiner(joiner1);
|
|
||||||
assert.deepEqual(
|
|
||||||
registry.getJoinedCharacters(1),
|
|
||||||
[[7, 9]]
|
|
||||||
);
|
|
||||||
|
|
||||||
registry.deregisterCharacterJoiner(joiner2);
|
|
||||||
assert.deepEqual(
|
|
||||||
registry.getJoinedCharacters(1),
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('doesn\'t process joins on differently-styled characters', () => {
|
|
||||||
registry.registerCharacterJoiner(substringJoiner('->'));
|
|
||||||
assert.deepEqual(
|
|
||||||
registry.getJoinedCharacters(2),
|
|
||||||
[[2, 4], [12, 14]]
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns an empty list of ranges if there is nothing to be joined', () => {
|
|
||||||
registry.registerCharacterJoiner(substringJoiner('->'));
|
|
||||||
assert.deepEqual(
|
|
||||||
registry.getJoinedCharacters(3),
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns an empty list of ranges if the line is empty', () => {
|
|
||||||
registry.registerCharacterJoiner(substringJoiner('->'));
|
|
||||||
assert.deepEqual(
|
|
||||||
registry.getJoinedCharacters(4),
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns false when trying to deregister a joiner that does not exist', () => {
|
|
||||||
registry.registerCharacterJoiner(substringJoiner('->'));
|
|
||||||
assert.deepEqual(registry.deregisterCharacterJoiner(123), false);
|
|
||||||
assert.deepEqual(
|
|
||||||
registry.getJoinedCharacters(0),
|
|
||||||
[[2, 4], [7, 9], [12, 14]]
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('doesn\'t process same-styled ranges that only have one character', () => {
|
|
||||||
registry.registerCharacterJoiner(substringJoiner('a'));
|
|
||||||
registry.registerCharacterJoiner(substringJoiner('b'));
|
|
||||||
registry.registerCharacterJoiner(substringJoiner('d'));
|
|
||||||
assert.deepEqual(
|
|
||||||
registry.getJoinedCharacters(5),
|
|
||||||
[[5, 6]]
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('handles ranges that extend all the way to the end of the line', () => {
|
|
||||||
registry.registerCharacterJoiner(substringJoiner('-> d'));
|
|
||||||
assert.deepEqual(
|
|
||||||
registry.getJoinedCharacters(2),
|
|
||||||
[[12, 16]]
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('handles adjacent ranges', () => {
|
|
||||||
registry.registerCharacterJoiner(substringJoiner('->'));
|
|
||||||
registry.registerCharacterJoiner(substringJoiner('> c '));
|
|
||||||
assert.deepEqual(
|
|
||||||
registry.getJoinedCharacters(2),
|
|
||||||
[[2, 4], [8, 12], [12, 14]]
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('handles fullwidth characters in the middle of ranges', () => {
|
|
||||||
registry.registerCharacterJoiner(substringJoiner('wi¥de'));
|
|
||||||
assert.deepEqual(
|
|
||||||
registry.getJoinedCharacters(6),
|
|
||||||
[[0, 6]]
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('handles fullwidth characters at the end of ranges', () => {
|
|
||||||
registry.registerCharacterJoiner(substringJoiner('wi¥'));
|
|
||||||
assert.deepEqual(
|
|
||||||
registry.getJoinedCharacters(6),
|
|
||||||
[[0, 4]]
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('handles emojis in the middle of ranges', () => {
|
|
||||||
registry.registerCharacterJoiner(substringJoiner('emo\xf0\x9f\x98\x81 ji'));
|
|
||||||
assert.deepEqual(
|
|
||||||
registry.getJoinedCharacters(6),
|
|
||||||
[[6, 13]]
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('handles emojis at the end of ranges', () => {
|
|
||||||
registry.registerCharacterJoiner(substringJoiner('emo\xf0\x9f\x98\x81 '));
|
|
||||||
assert.deepEqual(
|
|
||||||
registry.getJoinedCharacters(6),
|
|
||||||
[[6, 11]]
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('handles ranges after wide and emoji characters', () => {
|
|
||||||
registry.registerCharacterJoiner(substringJoiner('abc'));
|
|
||||||
assert.deepEqual(
|
|
||||||
registry.getJoinedCharacters(6),
|
|
||||||
[[13, 16]]
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('range merging', () => {
|
|
||||||
it('inserts a new range before the existing ones', () => {
|
|
||||||
registry.registerCharacterJoiner(() => [[1, 2], [2, 3]]);
|
|
||||||
registry.registerCharacterJoiner(() => [[0, 1]]);
|
|
||||||
assert.deepEqual(
|
|
||||||
registry.getJoinedCharacters(0),
|
|
||||||
[[0, 1], [1, 2], [2, 3]]
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('inserts in between two ranges', () => {
|
|
||||||
registry.registerCharacterJoiner(() => [[0, 2], [4, 6]]);
|
|
||||||
registry.registerCharacterJoiner(() => [[2, 4]]);
|
|
||||||
assert.deepEqual(
|
|
||||||
registry.getJoinedCharacters(0),
|
|
||||||
[[0, 2], [2, 4], [4, 6]]
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('inserts after the last range', () => {
|
|
||||||
registry.registerCharacterJoiner(() => [[0, 2], [4, 6]]);
|
|
||||||
registry.registerCharacterJoiner(() => [[6, 8]]);
|
|
||||||
assert.deepEqual(
|
|
||||||
registry.getJoinedCharacters(0),
|
|
||||||
[[0, 2], [4, 6], [6, 8]]
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('extends the beginning of a range', () => {
|
|
||||||
registry.registerCharacterJoiner(() => [[0, 2], [4, 6]]);
|
|
||||||
registry.registerCharacterJoiner(() => [[3, 5]]);
|
|
||||||
assert.deepEqual(
|
|
||||||
registry.getJoinedCharacters(0),
|
|
||||||
[[0, 2], [3, 6]]
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('extends the end of a range', () => {
|
|
||||||
registry.registerCharacterJoiner(() => [[0, 2], [4, 6]]);
|
|
||||||
registry.registerCharacterJoiner(() => [[1, 4]]);
|
|
||||||
assert.deepEqual(
|
|
||||||
registry.getJoinedCharacters(0),
|
|
||||||
[[0, 4], [4, 6]]
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('extends the last range', () => {
|
|
||||||
registry.registerCharacterJoiner(() => [[0, 2], [4, 6]]);
|
|
||||||
registry.registerCharacterJoiner(() => [[5, 7]]);
|
|
||||||
assert.deepEqual(
|
|
||||||
registry.getJoinedCharacters(0),
|
|
||||||
[[0, 2], [4, 7]]
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('connects two ranges', () => {
|
|
||||||
registry.registerCharacterJoiner(() => [[0, 2], [4, 6]]);
|
|
||||||
registry.registerCharacterJoiner(() => [[1, 5]]);
|
|
||||||
assert.deepEqual(
|
|
||||||
registry.getJoinedCharacters(0),
|
|
||||||
[[0, 6]]
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('connects more than two ranges', () => {
|
|
||||||
registry.registerCharacterJoiner(() => [[0, 2], [4, 6], [8, 10], [12, 14]]);
|
|
||||||
registry.registerCharacterJoiner(() => [[1, 10]]);
|
|
||||||
assert.deepEqual(
|
|
||||||
registry.getJoinedCharacters(0),
|
|
||||||
[[0, 10], [12, 14]]
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
function lineData(line: string, attr: number = 0): LineData {
|
|
||||||
return line.split('').map<CharData>(char => [attr, char, 1, char.charCodeAt(0)]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function substringJoiner(substring: string): (sequence: string) => [number, number][] {
|
|
||||||
return (sequence: string): [number, number][] => {
|
|
||||||
const ranges: [number, number][] = [];
|
|
||||||
let searchIndex = 0;
|
|
||||||
let matchIndex = -1;
|
|
||||||
|
|
||||||
while ((matchIndex = sequence.indexOf(substring, searchIndex)) !== -1) {
|
|
||||||
const matchEndIndex = matchIndex + substring.length;
|
|
||||||
searchIndex = matchEndIndex;
|
|
||||||
ranges.push([matchIndex, matchEndIndex]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ranges;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,276 +0,0 @@
|
|||||||
import { CHAR_DATA_ATTR_INDEX, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from '../Buffer';
|
|
||||||
import { ITerminal, LineData } from '../Types';
|
|
||||||
import { ICharacterJoinerRegistry, ICharacterJoiner } from './Types';
|
|
||||||
|
|
||||||
export class CharacterJoinerRegistry implements ICharacterJoinerRegistry {
|
|
||||||
|
|
||||||
private _characterJoiners: ICharacterJoiner[] = [];
|
|
||||||
private _nextCharacterJoinerId: number = 0;
|
|
||||||
|
|
||||||
constructor(private _terminal: ITerminal) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public registerCharacterJoiner(handler: (text: string) => [number, number][]): number {
|
|
||||||
const joiner: ICharacterJoiner = {
|
|
||||||
id: this._nextCharacterJoinerId++,
|
|
||||||
handler
|
|
||||||
};
|
|
||||||
|
|
||||||
this._characterJoiners.push(joiner);
|
|
||||||
return joiner.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public deregisterCharacterJoiner(joinerId: number): boolean {
|
|
||||||
for (let i = 0; i < this._characterJoiners.length; i++) {
|
|
||||||
if (this._characterJoiners[i].id === joinerId) {
|
|
||||||
this._characterJoiners.splice(i, 1);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getJoinedCharacters(row: number): [number, number][] {
|
|
||||||
if (this._characterJoiners.length === 0) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const line = this._terminal.buffer.lines.get(row);
|
|
||||||
if (line.length === 0) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const ranges: [number, number][] = [];
|
|
||||||
const lineStr = this._terminal.buffer.translateBufferLineToString(row, true);
|
|
||||||
|
|
||||||
// Because some cells can be represented by multiple javascript characters,
|
|
||||||
// we track the cell and the string indexes separately. This allows us to
|
|
||||||
// translate the string ranges we get from the joiners back into cell ranges
|
|
||||||
// for use when rendering
|
|
||||||
let rangeStartColumn = 0;
|
|
||||||
let currentStringIndex = 0;
|
|
||||||
let rangeStartStringIndex = 0;
|
|
||||||
let rangeAttr = line[0][CHAR_DATA_ATTR_INDEX] >> 9;
|
|
||||||
|
|
||||||
for (let x = 0; x < this._terminal.cols; x++) {
|
|
||||||
const charData = line[x];
|
|
||||||
const chars = charData[CHAR_DATA_CHAR_INDEX];
|
|
||||||
const width = charData[CHAR_DATA_WIDTH_INDEX];
|
|
||||||
const attr = charData[CHAR_DATA_ATTR_INDEX] >> 9;
|
|
||||||
|
|
||||||
if (width === 0) {
|
|
||||||
// If this character is of width 0, skip it.
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// End of range
|
|
||||||
if (attr !== rangeAttr) {
|
|
||||||
// If we ended up with a sequence of more than one character,
|
|
||||||
// look for ranges to join.
|
|
||||||
if (x - rangeStartColumn > 1) {
|
|
||||||
const joinedRanges = this._getJoinedRanges(
|
|
||||||
lineStr,
|
|
||||||
rangeStartStringIndex,
|
|
||||||
currentStringIndex,
|
|
||||||
line,
|
|
||||||
rangeStartColumn
|
|
||||||
);
|
|
||||||
for (let i = 0; i < joinedRanges.length; i++) {
|
|
||||||
ranges.push(joinedRanges[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset our markers for a new range.
|
|
||||||
rangeStartColumn = x;
|
|
||||||
rangeStartStringIndex = currentStringIndex;
|
|
||||||
rangeAttr = attr;
|
|
||||||
}
|
|
||||||
|
|
||||||
currentStringIndex += chars.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process any trailing ranges.
|
|
||||||
if (this._terminal.cols - rangeStartColumn > 1) {
|
|
||||||
const joinedRanges = this._getJoinedRanges(
|
|
||||||
lineStr,
|
|
||||||
rangeStartStringIndex,
|
|
||||||
currentStringIndex,
|
|
||||||
line,
|
|
||||||
rangeStartColumn
|
|
||||||
);
|
|
||||||
for (let i = 0; i < joinedRanges.length; i++) {
|
|
||||||
ranges.push(joinedRanges[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ranges;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given a segment of a line of text, find all ranges of text that should be
|
|
||||||
* joined in a single rendering unit. Ranges are internally converted to
|
|
||||||
* column ranges, rather than string ranges.
|
|
||||||
* @param line String representation of the full line of text
|
|
||||||
* @param startIndex Start position of the range to search in the string (inclusive)
|
|
||||||
* @param endIndex End position of the range to search in the string (exclusive)
|
|
||||||
*/
|
|
||||||
private _getJoinedRanges(line: string, startIndex: number, endIndex: number, lineData: LineData, startCol: number): [number, number][] {
|
|
||||||
const text = line.substring(startIndex, endIndex);
|
|
||||||
// At this point we already know that there is at least one joiner so
|
|
||||||
// we can just pull its value and assign it directly rather than
|
|
||||||
// merging it into an empty array, which incurs unnecessary writes.
|
|
||||||
const joinedRanges: [number, number][] = this._characterJoiners[0].handler(text);
|
|
||||||
for (let i = 1; i < this._characterJoiners.length; i++) {
|
|
||||||
// We merge any overlapping ranges across the different joiners
|
|
||||||
const joinerRanges = this._characterJoiners[i].handler(text);
|
|
||||||
for (let j = 0; j < joinerRanges.length; j++) {
|
|
||||||
CharacterJoinerRegistry._mergeRanges(joinedRanges, joinerRanges[j]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this._stringRangesToCellRanges(joinedRanges, lineData, startCol);
|
|
||||||
return joinedRanges;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Modifies the provided ranges in-place to adjust for variations between
|
|
||||||
* string length and cell width so that the range represents a cell range,
|
|
||||||
* rather than the string range the joiner provides.
|
|
||||||
* @param ranges String ranges containing start (inclusive) and end (exclusive) index
|
|
||||||
* @param line Cell data for the relevant line in the terminal
|
|
||||||
* @param startCol Offset within the line to start from
|
|
||||||
*/
|
|
||||||
private _stringRangesToCellRanges(ranges: [number, number][], line: LineData, startCol: number): void {
|
|
||||||
let currentRangeIndex = 0;
|
|
||||||
let currentRangeStarted = false;
|
|
||||||
let currentStringIndex = 0;
|
|
||||||
let currentRange = ranges[currentRangeIndex];
|
|
||||||
|
|
||||||
// If we got through all of the ranges, stop searching
|
|
||||||
if (!currentRange) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let x = startCol; x < this._terminal.cols; x++) {
|
|
||||||
const charData = line[x];
|
|
||||||
const width = charData[CHAR_DATA_WIDTH_INDEX];
|
|
||||||
const length = charData[CHAR_DATA_CHAR_INDEX].length;
|
|
||||||
|
|
||||||
// We skip zero-width characters when creating the string to join the text
|
|
||||||
// so we do the same here
|
|
||||||
if (width === 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Adjust the start of the range
|
|
||||||
if (!currentRangeStarted && currentRange[0] <= currentStringIndex) {
|
|
||||||
currentRange[0] = x;
|
|
||||||
currentRangeStarted = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Adjust the end of the range
|
|
||||||
if (currentRange[1] <= currentStringIndex) {
|
|
||||||
currentRange[1] = x;
|
|
||||||
|
|
||||||
// We're finished with this range, so we move to the next one
|
|
||||||
currentRange = ranges[++currentRangeIndex];
|
|
||||||
|
|
||||||
// If there are no more ranges left, stop searching
|
|
||||||
if (!currentRange) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ranges can be on adjacent characters. Because the end index of the
|
|
||||||
// ranges are exclusive, this means that the index for the start of a
|
|
||||||
// range can be the same as the end index of the previous range. To
|
|
||||||
// account for the start of the next range, we check here just in case.
|
|
||||||
if (currentRange[0] <= currentStringIndex) {
|
|
||||||
currentRange[0] = x;
|
|
||||||
currentRangeStarted = true;
|
|
||||||
} else {
|
|
||||||
currentRangeStarted = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Adjust the string index based on the character length to line up with
|
|
||||||
// the column adjustment
|
|
||||||
currentStringIndex += length;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there is still a range left at the end, it must extend all the way to
|
|
||||||
// the end of the line.
|
|
||||||
if (currentRange) {
|
|
||||||
currentRange[1] = this._terminal.cols;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Merges the range defined by the provided start and end into the list of
|
|
||||||
* existing ranges. The merge is done in place on the existing range for
|
|
||||||
* performance and is also returned.
|
|
||||||
* @param ranges Existing range list
|
|
||||||
* @param newRange Tuple of two numbers representing the new range to merge in.
|
|
||||||
* @returns The ranges input with the new range merged in place
|
|
||||||
*/
|
|
||||||
private static _mergeRanges(ranges: [number, number][], newRange: [number, number]): [number, number][] {
|
|
||||||
let inRange = false;
|
|
||||||
for (let i = 0; i < ranges.length; i++) {
|
|
||||||
const range = ranges[i];
|
|
||||||
if (!inRange) {
|
|
||||||
if (newRange[1] <= range[0]) {
|
|
||||||
// Case 1: New range is before the search range
|
|
||||||
ranges.splice(i, 0, newRange);
|
|
||||||
return ranges;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newRange[1] <= range[1]) {
|
|
||||||
// Case 2: New range is either wholly contained within the
|
|
||||||
// search range or overlaps with the front of it
|
|
||||||
range[0] = Math.min(newRange[0], range[0]);
|
|
||||||
return ranges;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newRange[0] < range[1]) {
|
|
||||||
// Case 3: New range either wholly contains the search range
|
|
||||||
// or overlaps with the end of it
|
|
||||||
range[0] = Math.min(newRange[0], range[0]);
|
|
||||||
inRange = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Case 4: New range starts after the search range
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
if (newRange[1] <= range[0]) {
|
|
||||||
// Case 5: New range extends from previous range but doesn't
|
|
||||||
// reach the current one
|
|
||||||
ranges[i - 1][1] = newRange[1];
|
|
||||||
return ranges;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newRange[1] <= range[1]) {
|
|
||||||
// Case 6: New range extends from prvious range into the
|
|
||||||
// current range
|
|
||||||
ranges[i - 1][1] = Math.max(newRange[1], range[1]);
|
|
||||||
ranges.splice(i, 1);
|
|
||||||
inRange = false;
|
|
||||||
return ranges;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Case 7: New range extends from previous range past the
|
|
||||||
// end of the current range
|
|
||||||
ranges.splice(i, 1);
|
|
||||||
i--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (inRange) {
|
|
||||||
// Case 8: New range extends past the last existing range
|
|
||||||
ranges[ranges.length - 1][1] = newRange[1];
|
|
||||||
} else {
|
|
||||||
// Case 9: New range starts after the last existing range
|
|
||||||
ranges.push(newRange);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ranges;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,14 +7,13 @@ import { TextRenderLayer } from './TextRenderLayer';
|
|||||||
import { SelectionRenderLayer } from './SelectionRenderLayer';
|
import { SelectionRenderLayer } from './SelectionRenderLayer';
|
||||||
import { CursorRenderLayer } from './CursorRenderLayer';
|
import { CursorRenderLayer } from './CursorRenderLayer';
|
||||||
import { ColorManager } from './ColorManager';
|
import { ColorManager } from './ColorManager';
|
||||||
import { IRenderLayer, IColorSet, IRenderer, IRenderDimensions, ICharacterJoinerRegistry } from './Types';
|
import { IRenderLayer, IColorSet, IRenderer, IRenderDimensions } from './Types';
|
||||||
import { ITerminal, CharacterJoinerHandler } from '../Types';
|
import { ITerminal } from '../Types';
|
||||||
import { LinkRenderLayer } from './LinkRenderLayer';
|
import { LinkRenderLayer } from './LinkRenderLayer';
|
||||||
import { EventEmitter } from '../EventEmitter';
|
import { EventEmitter } from '../EventEmitter';
|
||||||
import { RenderDebouncer } from '../ui/RenderDebouncer';
|
import { RenderDebouncer } from '../ui/RenderDebouncer';
|
||||||
import { ScreenDprMonitor } from '../ui/ScreenDprMonitor';
|
import { ScreenDprMonitor } from '../ui/ScreenDprMonitor';
|
||||||
import { ITheme } from 'xterm';
|
import { ITheme } from 'xterm';
|
||||||
import { CharacterJoinerRegistry } from '../renderer/CharacterJoinerRegistry';
|
|
||||||
|
|
||||||
export class Renderer extends EventEmitter implements IRenderer {
|
export class Renderer extends EventEmitter implements IRenderer {
|
||||||
private _renderDebouncer: RenderDebouncer;
|
private _renderDebouncer: RenderDebouncer;
|
||||||
@@ -24,7 +23,6 @@ export class Renderer extends EventEmitter implements IRenderer {
|
|||||||
private _screenDprMonitor: ScreenDprMonitor;
|
private _screenDprMonitor: ScreenDprMonitor;
|
||||||
private _isPaused: boolean = false;
|
private _isPaused: boolean = false;
|
||||||
private _needsFullRefresh: boolean = false;
|
private _needsFullRefresh: boolean = false;
|
||||||
private _characterJoinerRegistry: ICharacterJoinerRegistry;
|
|
||||||
|
|
||||||
public colorManager: ColorManager;
|
public colorManager: ColorManager;
|
||||||
public dimensions: IRenderDimensions;
|
public dimensions: IRenderDimensions;
|
||||||
@@ -33,13 +31,12 @@ export class Renderer extends EventEmitter implements IRenderer {
|
|||||||
super();
|
super();
|
||||||
const allowTransparency = this._terminal.options.allowTransparency;
|
const allowTransparency = this._terminal.options.allowTransparency;
|
||||||
this.colorManager = new ColorManager(document, allowTransparency);
|
this.colorManager = new ColorManager(document, allowTransparency);
|
||||||
this._characterJoinerRegistry = new CharacterJoinerRegistry(_terminal);
|
|
||||||
if (theme) {
|
if (theme) {
|
||||||
this.colorManager.setTheme(theme);
|
this.colorManager.setTheme(theme);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._renderLayers = [
|
this._renderLayers = [
|
||||||
new TextRenderLayer(this._terminal.screenElement, 0, this.colorManager.colors, this._characterJoinerRegistry, allowTransparency),
|
new TextRenderLayer(this._terminal.screenElement, 0, this.colorManager.colors, allowTransparency),
|
||||||
new SelectionRenderLayer(this._terminal.screenElement, 1, this.colorManager.colors),
|
new SelectionRenderLayer(this._terminal.screenElement, 1, this.colorManager.colors),
|
||||||
new LinkRenderLayer(this._terminal.screenElement, 2, this.colorManager.colors, this._terminal),
|
new LinkRenderLayer(this._terminal.screenElement, 2, this.colorManager.colors, this._terminal),
|
||||||
new CursorRenderLayer(this._terminal.screenElement, 3, this.colorManager.colors)
|
new CursorRenderLayer(this._terminal.screenElement, 3, this.colorManager.colors)
|
||||||
@@ -70,17 +67,12 @@ export class Renderer extends EventEmitter implements IRenderer {
|
|||||||
// Detect whether IntersectionObserver is detected and enable renderer pause
|
// Detect whether IntersectionObserver is detected and enable renderer pause
|
||||||
// and resume based on terminal visibility if so
|
// and resume based on terminal visibility if so
|
||||||
if ('IntersectionObserver' in window) {
|
if ('IntersectionObserver' in window) {
|
||||||
const observer = new IntersectionObserver(e => this.onIntersectionChange(e[0]), { threshold: 0 });
|
const observer = new IntersectionObserver(e => this.onIntersectionChange(e[0]), {threshold: 0});
|
||||||
observer.observe(this._terminal.element);
|
observer.observe(this._terminal.element);
|
||||||
this.register({ dispose: () => observer.disconnect() });
|
this.register({ dispose: () => observer.disconnect() });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public dispose(): void {
|
|
||||||
super.dispose();
|
|
||||||
this._renderLayers.forEach(l => l.dispose());
|
|
||||||
}
|
|
||||||
|
|
||||||
public onIntersectionChange(entry: IntersectionObserverEntry): void {
|
public onIntersectionChange(entry: IntersectionObserverEntry): void {
|
||||||
this._isPaused = entry.intersectionRatio === 0;
|
this._isPaused = entry.intersectionRatio === 0;
|
||||||
if (!this._isPaused && this._needsFullRefresh) {
|
if (!this._isPaused && this._needsFullRefresh) {
|
||||||
@@ -179,8 +171,8 @@ export class Renderer extends EventEmitter implements IRenderer {
|
|||||||
/**
|
/**
|
||||||
* Queues a refresh between two rows (inclusive), to be done on next animation
|
* Queues a refresh between two rows (inclusive), to be done on next animation
|
||||||
* frame.
|
* frame.
|
||||||
* @param start The start row.
|
* @param {number} start The start row.
|
||||||
* @param end The end row.
|
* @param {number} end The end row.
|
||||||
*/
|
*/
|
||||||
public refreshRows(start: number, end: number): void {
|
public refreshRows(start: number, end: number): void {
|
||||||
if (this._isPaused) {
|
if (this._isPaused) {
|
||||||
@@ -196,7 +188,7 @@ export class Renderer extends EventEmitter implements IRenderer {
|
|||||||
*/
|
*/
|
||||||
private _renderRows(start: number, end: number): void {
|
private _renderRows(start: number, end: number): void {
|
||||||
this._renderLayers.forEach(l => l.onGridChanged(this._terminal, start, end));
|
this._renderLayers.forEach(l => l.onGridChanged(this._terminal, start, end));
|
||||||
this._terminal.emit('refresh', { start, end });
|
this._terminal.emit('refresh', {start, end});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -258,12 +250,4 @@ export class Renderer extends EventEmitter implements IRenderer {
|
|||||||
this.dimensions.actualCellHeight = this.dimensions.canvasHeight / this._terminal.rows;
|
this.dimensions.actualCellHeight = this.dimensions.canvasHeight / this._terminal.rows;
|
||||||
this.dimensions.actualCellWidth = this.dimensions.canvasWidth / this._terminal.cols;
|
this.dimensions.actualCellWidth = this.dimensions.canvasWidth / this._terminal.cols;
|
||||||
}
|
}
|
||||||
|
|
||||||
public registerCharacterJoiner(handler: CharacterJoinerHandler): number {
|
|
||||||
return this._characterJoinerRegistry.registerCharacterJoiner(handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
public deregisterCharacterJoiner(joinerId: number): boolean {
|
|
||||||
return this._characterJoinerRegistry.deregisterCharacterJoiner(joinerId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ export class SelectionRenderLayer extends BaseRenderLayer {
|
|||||||
this._ctx.fillStyle = this._colors.selection.css;
|
this._ctx.fillStyle = this._colors.selection.css;
|
||||||
|
|
||||||
if (columnSelectMode) {
|
if (columnSelectMode) {
|
||||||
const startCol = start[0];
|
const startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0;
|
||||||
const width = end[0] - startCol;
|
const width = end[0] - startCol;
|
||||||
const height = viewportCappedEndRow - viewportCappedStartRow + 1;
|
const height = viewportCappedEndRow - viewportCappedStartRow + 1;
|
||||||
this.fillCells(startCol, viewportCappedStartRow, width, height);
|
this.fillCells(startCol, viewportCappedStartRow, width, height);
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { CHAR_DATA_ATTR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, NULL_CELL_CODE } from '../Buffer';
|
import { CHAR_DATA_ATTR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX } from '../Buffer';
|
||||||
import { FLAGS, IColorSet, IRenderDimensions, ICharacterJoinerRegistry } from './Types';
|
import { FLAGS, IColorSet, IRenderDimensions } from './Types';
|
||||||
import { CharData, ITerminal } from '../Types';
|
import { CharData, ITerminal } from '../Types';
|
||||||
import { INVERTED_DEFAULT_COLOR } from './atlas/Types';
|
import { INVERTED_DEFAULT_COLOR } from './atlas/Types';
|
||||||
import { GridCache } from './GridCache';
|
import { GridCache } from './GridCache';
|
||||||
@@ -22,12 +22,10 @@ export class TextRenderLayer extends BaseRenderLayer {
|
|||||||
private _characterWidth: number;
|
private _characterWidth: number;
|
||||||
private _characterFont: string;
|
private _characterFont: string;
|
||||||
private _characterOverlapCache: { [key: string]: boolean } = {};
|
private _characterOverlapCache: { [key: string]: boolean } = {};
|
||||||
private _characterJoinerRegistry: ICharacterJoinerRegistry;
|
|
||||||
|
|
||||||
constructor(container: HTMLElement, zIndex: number, colors: IColorSet, characterJoinerRegistry: ICharacterJoinerRegistry, alpha: boolean) {
|
constructor(container: HTMLElement, zIndex: number, colors: IColorSet, alpha: boolean) {
|
||||||
super(container, 'text', zIndex, alpha, colors);
|
super(container, 'text', zIndex, alpha, colors);
|
||||||
this._state = new GridCache<CharData>();
|
this._state = new GridCache<CharData>();
|
||||||
this._characterJoinerRegistry = characterJoinerRegistry;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public resize(terminal: ITerminal, dim: IRenderDimensions): void {
|
public resize(terminal: ITerminal, dim: IRenderDimensions): void {
|
||||||
@@ -54,10 +52,9 @@ export class TextRenderLayer extends BaseRenderLayer {
|
|||||||
terminal: ITerminal,
|
terminal: ITerminal,
|
||||||
firstRow: number,
|
firstRow: number,
|
||||||
lastRow: number,
|
lastRow: number,
|
||||||
joinerRegistry: ICharacterJoinerRegistry | null,
|
|
||||||
callback: (
|
callback: (
|
||||||
code: number,
|
code: number,
|
||||||
chars: string,
|
char: string,
|
||||||
width: number,
|
width: number,
|
||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
@@ -69,54 +66,22 @@ export class TextRenderLayer extends BaseRenderLayer {
|
|||||||
for (let y = firstRow; y <= lastRow; y++) {
|
for (let y = firstRow; y <= lastRow; y++) {
|
||||||
const row = y + terminal.buffer.ydisp;
|
const row = y + terminal.buffer.ydisp;
|
||||||
const line = terminal.buffer.lines.get(row);
|
const line = terminal.buffer.lines.get(row);
|
||||||
const joinedRanges = joinerRegistry ? joinerRegistry.getJoinedCharacters(row) : [];
|
|
||||||
for (let x = 0; x < terminal.cols; x++) {
|
for (let x = 0; x < terminal.cols; x++) {
|
||||||
const charData = line[x];
|
const charData = line[x];
|
||||||
let code: number = <number>charData[CHAR_DATA_CODE_INDEX];
|
const code: number = <number>charData[CHAR_DATA_CODE_INDEX];
|
||||||
|
const char: string = charData[CHAR_DATA_CHAR_INDEX];
|
||||||
// Can either represent character(s) for a single cell or multiple cells
|
|
||||||
// if indicated by a character joiner.
|
|
||||||
let chars: string = charData[CHAR_DATA_CHAR_INDEX];
|
|
||||||
const attr: number = charData[CHAR_DATA_ATTR_INDEX];
|
const attr: number = charData[CHAR_DATA_ATTR_INDEX];
|
||||||
let width: number = charData[CHAR_DATA_WIDTH_INDEX];
|
let width: number = charData[CHAR_DATA_WIDTH_INDEX];
|
||||||
|
|
||||||
// If true, indicates that the current character(s) to draw were joined.
|
|
||||||
let isJoined = false;
|
|
||||||
let lastCharX = x;
|
|
||||||
|
|
||||||
// The character to the left is a wide character, drawing is owned by
|
// The character to the left is a wide character, drawing is owned by
|
||||||
// the char at x-1
|
// the char at x-1
|
||||||
if (width === 0) {
|
if (width === 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process any joined character ranges as needed. Because of how the
|
// If the character is an overlapping char and the character to the right is a
|
||||||
// ranges are produced, we know that they are valid for the characters
|
// space, take ownership of the cell to the right.
|
||||||
// and attributes of our input.
|
if (this._isOverlapping(charData)) {
|
||||||
if (joinedRanges.length > 0 && x === joinedRanges[0][0]) {
|
|
||||||
isJoined = true;
|
|
||||||
const range = joinedRanges.shift();
|
|
||||||
|
|
||||||
// We already know the exact start and end column of the joined range,
|
|
||||||
// so we get the string and width representing it directly
|
|
||||||
chars = terminal.buffer.translateBufferLineToString(
|
|
||||||
row,
|
|
||||||
true,
|
|
||||||
range[0],
|
|
||||||
range[1]
|
|
||||||
);
|
|
||||||
width = range[1] - range[0];
|
|
||||||
code = Infinity;
|
|
||||||
|
|
||||||
// Skip over the cells occupied by this range in the loop
|
|
||||||
lastCharX = range[1] - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the character is an overlapping char and the character to the
|
|
||||||
// right is a space, take ownership of the cell to the right. We skip
|
|
||||||
// this check for joined characters because their rendering likely won't
|
|
||||||
// yield the same result as rendering the last character individually.
|
|
||||||
if (!isJoined && this._isOverlapping(charData)) {
|
|
||||||
// If the character is overlapping, we want to force a re-render on every
|
// If the character is overlapping, we want to force a re-render on every
|
||||||
// frame. This is specifically to work around the case where two
|
// frame. This is specifically to work around the case where two
|
||||||
// overlaping chars `a` and `b` are adjacent, the cursor is moved to b and a
|
// overlaping chars `a` and `b` are adjacent, the cursor is moved to b and a
|
||||||
@@ -124,7 +89,7 @@ export class TextRenderLayer extends BaseRenderLayer {
|
|||||||
// get removed, and `a` would not re-render because it thinks it's
|
// get removed, and `a` would not re-render because it thinks it's
|
||||||
// already in the correct state.
|
// already in the correct state.
|
||||||
// this._state.cache[x][y] = OVERLAP_OWNED_CHAR_DATA;
|
// this._state.cache[x][y] = OVERLAP_OWNED_CHAR_DATA;
|
||||||
if (lastCharX < line.length - 1 && line[lastCharX + 1][CHAR_DATA_CODE_INDEX] === NULL_CELL_CODE) {
|
if (x < line.length - 1 && line[x + 1][CHAR_DATA_CODE_INDEX] === 32 /*' '*/) {
|
||||||
width = 2;
|
width = 2;
|
||||||
// this._clearChar(x + 1, y);
|
// this._clearChar(x + 1, y);
|
||||||
// The overlapping char's char data will force a clear and render when the
|
// The overlapping char's char data will force a clear and render when the
|
||||||
@@ -151,18 +116,7 @@ export class TextRenderLayer extends BaseRenderLayer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
callback(
|
callback(code, char, width, x, y, fg, bg, flags);
|
||||||
code,
|
|
||||||
chars,
|
|
||||||
width,
|
|
||||||
x,
|
|
||||||
y,
|
|
||||||
fg,
|
|
||||||
bg,
|
|
||||||
flags
|
|
||||||
);
|
|
||||||
|
|
||||||
x = lastCharX;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -180,7 +134,7 @@ export class TextRenderLayer extends BaseRenderLayer {
|
|||||||
|
|
||||||
ctx.save();
|
ctx.save();
|
||||||
|
|
||||||
this._forEachCell(terminal, firstRow, lastRow, null, (code, chars, width, x, y, fg, bg, flags) => {
|
this._forEachCell(terminal, firstRow, lastRow, (code, char, width, x, y, fg, bg, flags) => {
|
||||||
// libvte and xterm both draw the background (but not foreground) of invisible characters,
|
// libvte and xterm both draw the background (but not foreground) of invisible characters,
|
||||||
// so we should too.
|
// so we should too.
|
||||||
let nextFillStyle = null; // null represents default background color
|
let nextFillStyle = null; // null represents default background color
|
||||||
@@ -222,7 +176,7 @@ export class TextRenderLayer extends BaseRenderLayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _drawForeground(terminal: ITerminal, firstRow: number, lastRow: number): void {
|
private _drawForeground(terminal: ITerminal, firstRow: number, lastRow: number): void {
|
||||||
this._forEachCell(terminal, firstRow, lastRow, this._characterJoinerRegistry, (code, chars, width, x, y, fg, bg, flags) => {
|
this._forEachCell(terminal, firstRow, lastRow, (code, char, width, x, y, fg, bg, flags) => {
|
||||||
if (flags & FLAGS.INVISIBLE) {
|
if (flags & FLAGS.INVISIBLE) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -236,11 +190,11 @@ export class TextRenderLayer extends BaseRenderLayer {
|
|||||||
} else {
|
} else {
|
||||||
this._ctx.fillStyle = this._colors.foreground.css;
|
this._ctx.fillStyle = this._colors.foreground.css;
|
||||||
}
|
}
|
||||||
this.fillBottomLineAtCells(x, y, width);
|
this.fillBottomLineAtCells(x, y);
|
||||||
this._ctx.restore();
|
this._ctx.restore();
|
||||||
}
|
}
|
||||||
this.drawChars(
|
this.drawChar(
|
||||||
terminal, chars, code,
|
terminal, char, code,
|
||||||
width, x, y,
|
width, x, y,
|
||||||
fg, bg,
|
fg, bg,
|
||||||
!!(flags & FLAGS.BOLD), !!(flags & FLAGS.DIM), !!(flags & FLAGS.ITALIC)
|
!!(flags & FLAGS.BOLD), !!(flags & FLAGS.DIM), !!(flags & FLAGS.ITALIC)
|
||||||
|
|||||||
+2
-25
@@ -3,7 +3,7 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ITerminal, CharacterJoinerHandler } from '../Types';
|
import { ITerminal } from '../Types';
|
||||||
import { IEventEmitter, ITheme, IDisposable } from 'xterm';
|
import { IEventEmitter, ITheme, IDisposable } from 'xterm';
|
||||||
import { IColorSet } from '../shared/Types';
|
import { IColorSet } from '../shared/Types';
|
||||||
|
|
||||||
@@ -40,8 +40,6 @@ export interface IRenderer extends IEventEmitter, IDisposable {
|
|||||||
onOptionsChanged(): void;
|
onOptionsChanged(): void;
|
||||||
clear(): void;
|
clear(): void;
|
||||||
refreshRows(start: number, end: number): void;
|
refreshRows(start: number, end: number): void;
|
||||||
registerCharacterJoiner(handler: CharacterJoinerHandler): number;
|
|
||||||
deregisterCharacterJoiner(joinerId: number): boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IColorManager {
|
export interface IColorManager {
|
||||||
@@ -66,7 +64,7 @@ export interface IRenderDimensions {
|
|||||||
actualCellHeight: number;
|
actualCellHeight: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IRenderLayer extends IDisposable {
|
export interface IRenderLayer {
|
||||||
/**
|
/**
|
||||||
* Called when the terminal loses focus.
|
* Called when the terminal loses focus.
|
||||||
*/
|
*/
|
||||||
@@ -103,16 +101,6 @@ export interface IRenderLayer extends IDisposable {
|
|||||||
*/
|
*/
|
||||||
onSelectionChanged(terminal: ITerminal, start: [number, number], end: [number, number], columnSelectMode: boolean): void;
|
onSelectionChanged(terminal: ITerminal, start: [number, number], end: [number, number], columnSelectMode: boolean): void;
|
||||||
|
|
||||||
/**
|
|
||||||
* Registers a handler to join characters to render as a group
|
|
||||||
*/
|
|
||||||
registerCharacterJoiner?(joiner: ICharacterJoiner): void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deregisters the specified character joiner handler
|
|
||||||
*/
|
|
||||||
deregisterCharacterJoiner?(joinerId: number): void;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resize the render layer.
|
* Resize the render layer.
|
||||||
*/
|
*/
|
||||||
@@ -123,14 +111,3 @@ export interface IRenderLayer extends IDisposable {
|
|||||||
*/
|
*/
|
||||||
reset(terminal: ITerminal): void;
|
reset(terminal: ITerminal): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ICharacterJoiner {
|
|
||||||
id: number;
|
|
||||||
handler: CharacterJoinerHandler;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ICharacterJoinerRegistry {
|
|
||||||
registerCharacterJoiner(handler: (text: string) => [number, number][]): number;
|
|
||||||
deregisterCharacterJoiner(joinerId: number): boolean;
|
|
||||||
getJoinedCharacters(row: number): [number, number][];
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ interface IGlyphCacheValue {
|
|||||||
|
|
||||||
function getGlyphCacheKey(glyph: IGlyphIdentifier): string {
|
function getGlyphCacheKey(glyph: IGlyphIdentifier): string {
|
||||||
const styleFlags = (glyph.bold ? 0 : 4) + (glyph.dim ? 0 : 2) + (glyph.italic ? 0 : 1);
|
const styleFlags = (glyph.bold ? 0 : 4) + (glyph.dim ? 0 : 2) + (glyph.italic ? 0 : 1);
|
||||||
return `${glyph.bg}_${glyph.fg}_${styleFlags}${glyph.chars}`;
|
return `${glyph.bg}_${glyph.fg}_${styleFlags}${glyph.char}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class DynamicCharAtlas extends BaseCharAtlas {
|
export default class DynamicCharAtlas extends BaseCharAtlas {
|
||||||
@@ -216,7 +216,7 @@ export default class DynamicCharAtlas extends BaseCharAtlas {
|
|||||||
this._tmpCtx.globalAlpha = DIM_OPACITY;
|
this._tmpCtx.globalAlpha = DIM_OPACITY;
|
||||||
}
|
}
|
||||||
// Draw the character
|
// Draw the character
|
||||||
this._tmpCtx.fillText(glyph.chars, 0, 0);
|
this._tmpCtx.fillText(glyph.char, 0, 0);
|
||||||
this._tmpCtx.restore();
|
this._tmpCtx.restore();
|
||||||
|
|
||||||
// clear the background from the character to avoid issues with drawing over the previous
|
// clear the background from the character to avoid issues with drawing over the previous
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export const INVERTED_DEFAULT_COLOR = -1;
|
|||||||
export const DIM_OPACITY = 0.5;
|
export const DIM_OPACITY = 0.5;
|
||||||
|
|
||||||
export interface IGlyphIdentifier {
|
export interface IGlyphIdentifier {
|
||||||
chars: string;
|
char: string;
|
||||||
code: number;
|
code: number;
|
||||||
bg: number;
|
bg: number;
|
||||||
fg: number;
|
fg: number;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { IRenderer, IRenderDimensions, IColorSet } from '../Types';
|
import { IRenderer, IRenderDimensions, IColorSet } from '../Types';
|
||||||
import { ITerminal, CharacterJoinerHandler } from '../../Types';
|
import { ITerminal } from '../../Types';
|
||||||
import { ITheme } from 'xterm';
|
import { ITheme } from 'xterm';
|
||||||
import { EventEmitter } from '../../EventEmitter';
|
import { EventEmitter } from '../../EventEmitter';
|
||||||
import { ColorManager } from '../ColorManager';
|
import { ColorManager } from '../ColorManager';
|
||||||
@@ -52,7 +52,7 @@ export class DomRenderer extends EventEmitter implements IRenderer {
|
|||||||
this._rowContainer.classList.add(ROW_CONTAINER_CLASS);
|
this._rowContainer.classList.add(ROW_CONTAINER_CLASS);
|
||||||
this._rowContainer.style.lineHeight = 'normal';
|
this._rowContainer.style.lineHeight = 'normal';
|
||||||
this._rowContainer.setAttribute('aria-hidden', 'true');
|
this._rowContainer.setAttribute('aria-hidden', 'true');
|
||||||
this._refreshRowElements(this._terminal.cols, this._terminal.rows);
|
this._refreshRowElements(this._terminal.rows, this._terminal.cols);
|
||||||
this._selectionContainer = document.createElement('div');
|
this._selectionContainer = document.createElement('div');
|
||||||
this._selectionContainer.classList.add(SELECTION_CLASS);
|
this._selectionContainer.classList.add(SELECTION_CLASS);
|
||||||
this._selectionContainer.setAttribute('aria-hidden', 'true');
|
this._selectionContainer.setAttribute('aria-hidden', 'true');
|
||||||
@@ -81,15 +81,6 @@ export class DomRenderer extends EventEmitter implements IRenderer {
|
|||||||
this._terminal.screenElement.appendChild(this._selectionContainer);
|
this._terminal.screenElement.appendChild(this._selectionContainer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public dispose(): void {
|
|
||||||
this._terminal.element.classList.remove(TERMINAL_CLASS_PREFIX + this._terminalClass);
|
|
||||||
this._terminal.screenElement.removeChild(this._rowContainer);
|
|
||||||
this._terminal.screenElement.removeChild(this._selectionContainer);
|
|
||||||
this._terminal.screenElement.removeChild(this._themeStyleElement);
|
|
||||||
this._terminal.screenElement.removeChild(this._dimensionsStyleElement);
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
private _updateDimensions(): void {
|
private _updateDimensions(): void {
|
||||||
this.dimensions.scaledCharWidth = this._terminal.charMeasure.width * window.devicePixelRatio;
|
this.dimensions.scaledCharWidth = this._terminal.charMeasure.width * window.devicePixelRatio;
|
||||||
this.dimensions.scaledCharHeight = this._terminal.charMeasure.height * window.devicePixelRatio;
|
this.dimensions.scaledCharHeight = this._terminal.charMeasure.height * window.devicePixelRatio;
|
||||||
@@ -250,14 +241,14 @@ export class DomRenderer extends EventEmitter implements IRenderer {
|
|||||||
|
|
||||||
// Create the selections
|
// Create the selections
|
||||||
const documentFragment = document.createDocumentFragment();
|
const documentFragment = document.createDocumentFragment();
|
||||||
|
const startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0;
|
||||||
|
|
||||||
if (columnSelectMode) {
|
if (columnSelectMode) {
|
||||||
documentFragment.appendChild(
|
documentFragment.appendChild(
|
||||||
this._createSelectionElement(viewportCappedStartRow, start[0], end[0], viewportCappedEndRow - viewportCappedStartRow + 1)
|
this._createSelectionElement(viewportCappedStartRow, startCol, end[0], viewportCappedEndRow - viewportStartRow + 1)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// Draw first row
|
// Draw first row
|
||||||
const startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0;
|
|
||||||
const endCol = viewportCappedStartRow === viewportCappedEndRow ? end[0] : this._terminal.cols;
|
const endCol = viewportCappedStartRow === viewportCappedEndRow ? end[0] : this._terminal.cols;
|
||||||
documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow, startCol, endCol));
|
documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow, startCol, endCol));
|
||||||
// Draw middle rows
|
// Draw middle rows
|
||||||
@@ -328,7 +319,4 @@ export class DomRenderer extends EventEmitter implements IRenderer {
|
|||||||
private get _terminalSelector(): string {
|
private get _terminalSelector(): string {
|
||||||
return `.${TERMINAL_CLASS_PREFIX}${this._terminalClass}`;
|
return `.${TERMINAL_CLASS_PREFIX}${this._terminalClass}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
public registerCharacterJoiner(handler: CharacterJoinerHandler): number { return -1; }
|
|
||||||
public deregisterCharacterJoiner(joinerId: number): boolean { return false; }
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import jsdom = require('jsdom');
|
|||||||
import { assert } from 'chai';
|
import { assert } from 'chai';
|
||||||
import { DomRendererRowFactory } from './DomRendererRowFactory';
|
import { DomRendererRowFactory } from './DomRendererRowFactory';
|
||||||
import { LineData } from '../../Types';
|
import { LineData } from '../../Types';
|
||||||
import { DEFAULT_ATTR, NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR } from '../../Buffer';
|
import { DEFAULT_ATTR } from '../../Buffer';
|
||||||
import { FLAGS } from '../Types';
|
import { FLAGS } from '../Types';
|
||||||
|
|
||||||
describe('DomRendererRowFactory', () => {
|
describe('DomRendererRowFactory', () => {
|
||||||
@@ -149,7 +149,7 @@ describe('DomRendererRowFactory', () => {
|
|||||||
function createEmptyLineData(cols: number): LineData {
|
function createEmptyLineData(cols: number): LineData {
|
||||||
const lineData: LineData = [];
|
const lineData: LineData = [];
|
||||||
for (let i = 0; i < cols; i++) {
|
for (let i = 0; i < cols; i++) {
|
||||||
lineData.push([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]);
|
lineData.push([DEFAULT_ATTR, ' ', 1, 32 /* ' '.charCodeAt(0) */]);
|
||||||
}
|
}
|
||||||
return lineData;
|
return lineData;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ export const isLinux = platform.indexOf('Linux') >= 0;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Return if the given array contains the given element
|
* Return if the given array contains the given element
|
||||||
* @param arr The array to search for the given element.
|
* @param {Array} array The array to search for the given element.
|
||||||
* @param el The element to look for into the array
|
* @param {Object} el The element to look for into the array
|
||||||
*/
|
*/
|
||||||
function contains(arr: any[], el: any): boolean {
|
function contains(arr: any[], el: any): boolean {
|
||||||
return arr.indexOf(el) >= 0;
|
return arr.indexOf(el) >= 0;
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { IColorSet, IRenderer, IRenderDimensions, IColorManager } from '../renderer/Types';
|
import { IColorSet, IRenderer, IRenderDimensions, IColorManager } from '../renderer/Types';
|
||||||
import { LineData, IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminal, IBuffer, IBufferSet, IBrowser, ICharMeasure, ISelectionManager, ITerminalOptions, ICircularList, ILinkifier, IMouseHelper, ILinkMatcherOptions, XtermListener, CharacterJoinerHandler } from '../Types';
|
import { LineData, IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminal, IBuffer, IBufferSet, IBrowser, ICharMeasure, ISelectionManager, ITerminalOptions, ICircularList, ILinkifier, IMouseHelper, ILinkMatcherOptions, XtermListener } from '../Types';
|
||||||
import { Buffer, NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR } from '../Buffer';
|
import { Buffer } from '../Buffer';
|
||||||
import * as Browser from '../shared/utils/Browser';
|
import * as Browser from '../shared/utils/Browser';
|
||||||
import { ITheme, IDisposable, IMarker } from 'xterm';
|
import { ITheme, IDisposable, IMarker } from 'xterm';
|
||||||
|
|
||||||
@@ -84,6 +84,9 @@ export class MockTerminal implements ITerminal {
|
|||||||
write(data: string): void {
|
write(data: string): void {
|
||||||
throw new Error('Method not implemented.');
|
throw new Error('Method not implemented.');
|
||||||
}
|
}
|
||||||
|
send(data: string): void {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
bracketedPasteMode: boolean;
|
bracketedPasteMode: boolean;
|
||||||
mouseHelper: IMouseHelper;
|
mouseHelper: IMouseHelper;
|
||||||
renderer: IRenderer;
|
renderer: IRenderer;
|
||||||
@@ -149,12 +152,10 @@ export class MockTerminal implements ITerminal {
|
|||||||
const line: LineData = [];
|
const line: LineData = [];
|
||||||
cols = cols || this.cols;
|
cols = cols || this.cols;
|
||||||
for (let i = 0; i < cols; i++) {
|
for (let i = 0; i < cols; i++) {
|
||||||
line.push([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]);
|
line.push([0, ' ', 1, 32]);
|
||||||
}
|
}
|
||||||
return line;
|
return line;
|
||||||
}
|
}
|
||||||
registerCharacterJoiner(handler: CharacterJoinerHandler): number { return 0; }
|
|
||||||
deregisterCharacterJoiner(joinerId: number): void { }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MockCharMeasure implements ICharMeasure {
|
export class MockCharMeasure implements ICharMeasure {
|
||||||
@@ -237,6 +238,9 @@ export class MockInputHandlingTerminal implements IInputHandlingTerminal {
|
|||||||
is(term: string): boolean {
|
is(term: string): boolean {
|
||||||
throw new Error('Method not implemented.');
|
throw new Error('Method not implemented.');
|
||||||
}
|
}
|
||||||
|
send(data: string): void {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
setgCharset(g: number, charset: { [key: string]: string; }): void {
|
setgCharset(g: number, charset: { [key: string]: string; }): void {
|
||||||
throw new Error('Method not implemented.');
|
throw new Error('Method not implemented.');
|
||||||
}
|
}
|
||||||
@@ -279,18 +283,6 @@ export class MockInputHandlingTerminal implements IInputHandlingTerminal {
|
|||||||
tabSet(): void {
|
tabSet(): void {
|
||||||
throw new Error('Method not implemented.');
|
throw new Error('Method not implemented.');
|
||||||
}
|
}
|
||||||
handler(data: string): void {
|
|
||||||
throw new Error('Method not implemented.');
|
|
||||||
}
|
|
||||||
handleTitle(title: string): void {
|
|
||||||
throw new Error('Method not implemented.');
|
|
||||||
}
|
|
||||||
index(): void {
|
|
||||||
throw new Error('Method not implemented.');
|
|
||||||
}
|
|
||||||
reverseIndex(): void {
|
|
||||||
throw new Error('Method not implemented.');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MockBuffer implements IBuffer {
|
export class MockBuffer implements IBuffer {
|
||||||
@@ -352,8 +344,6 @@ export class MockRenderer implements IRenderer {
|
|||||||
onWindowResize(devicePixelRatio: number): void {}
|
onWindowResize(devicePixelRatio: number): void {}
|
||||||
clear(): void {}
|
clear(): void {}
|
||||||
refreshRows(start: number, end: number): void {}
|
refreshRows(start: number, end: number): void {}
|
||||||
registerCharacterJoiner(handler: CharacterJoinerHandler): number { return 0; }
|
|
||||||
deregisterCharacterJoiner(): boolean { return true; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MockViewport implements IViewport {
|
export class MockViewport implements IViewport {
|
||||||
|
|||||||
Vendored
-45
@@ -416,13 +416,6 @@ declare module 'xterm' {
|
|||||||
*/
|
*/
|
||||||
off(type: 'blur' | 'focus' | 'linefeed' | 'selection' | 'data' | 'key' | 'keypress' | 'keydown' | 'refresh' | 'resize' | 'scroll' | 'title' | string, listener: (...args: any[]) => void): void;
|
off(type: 'blur' | 'focus' | 'linefeed' | 'selection' | 'data' | 'key' | 'keypress' | 'keydown' | 'refresh' | 'resize' | 'scroll' | 'title' | string, listener: (...args: any[]) => void): void;
|
||||||
|
|
||||||
/**
|
|
||||||
* Emits an event on the terminal.
|
|
||||||
* @param type The type of event
|
|
||||||
* @param data data associated with the event.
|
|
||||||
* @deprecated This is being removed from the API with no replacement, see
|
|
||||||
* issue #1505.
|
|
||||||
*/
|
|
||||||
emit(type: string, data?: any): void;
|
emit(type: string, data?: any): void;
|
||||||
|
|
||||||
addDisposableListener(type: string, handler: (...args: any[]) => void): IDisposable;
|
addDisposableListener(type: string, handler: (...args: any[]) => void): IDisposable;
|
||||||
@@ -477,44 +470,6 @@ declare module 'xterm' {
|
|||||||
*/
|
*/
|
||||||
deregisterLinkMatcher(matcherId: number): void;
|
deregisterLinkMatcher(matcherId: number): void;
|
||||||
|
|
||||||
/**
|
|
||||||
* (EXPERIMENTAL) Registers a character joiner, allowing custom sequences of
|
|
||||||
* characters to be rendered as a single unit. This is useful in particular
|
|
||||||
* for rendering ligatures and graphemes, among other things.
|
|
||||||
*
|
|
||||||
* Each registered character joiner is called with a string of text
|
|
||||||
* representing a portion of a line in the terminal that can be rendered as
|
|
||||||
* a single unit. The joiner must return a sorted array, where each entry is
|
|
||||||
* itself an array of length two, containing the start (inclusive) and end
|
|
||||||
* (exclusive) index of a substring of the input that should be rendered as
|
|
||||||
* a single unit. When multiple joiners are provided, the results of each
|
|
||||||
* are collected. If there are any overlapping substrings between them, they
|
|
||||||
* are combined into one larger unit that is drawn together.
|
|
||||||
*
|
|
||||||
* All character joiners that are registered get called every time a line is
|
|
||||||
* rendered in the terminal, so it is essential for the handler function to
|
|
||||||
* run as quickly as possible to avoid slowdowns when rendering. Similarly,
|
|
||||||
* joiners should strive to return the smallest possible substrings to
|
|
||||||
* render together, since they aren't drawn as optimally as individual
|
|
||||||
* characters.
|
|
||||||
*
|
|
||||||
* NOTE: character joiners are only used by the canvas renderer.
|
|
||||||
*
|
|
||||||
* @param handler The function that determines character joins. It is called
|
|
||||||
* with a string of text that is eligible for joining and returns an array
|
|
||||||
* where each entry is an array containing the start (inclusive) and end
|
|
||||||
* (exclusive) indexes of ranges that should be rendered as a single unit.
|
|
||||||
* @return The ID of the new joiner, this can be used to deregister
|
|
||||||
*/
|
|
||||||
registerCharacterJoiner(handler: (text: string) => [number, number][]): number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* (EXPERIMENTAL) Deregisters the character joiner if one was registered.
|
|
||||||
* NOTE: character joiners are only used by the canvas renderer.
|
|
||||||
* @param joinerId The character joiner's ID (returned after register)
|
|
||||||
*/
|
|
||||||
deregisterCharacterJoiner(joinerId: number): void;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* (EXPERIMENTAL) Adds a marker to the normal buffer and returns it. If the
|
* (EXPERIMENTAL) Adds a marker to the normal buffer and returns it. If the
|
||||||
* alt buffer is active, undefined is returned.
|
* alt buffer is active, undefined is returned.
|
||||||
|
|||||||
+1
-10
@@ -6,14 +6,5 @@ module.exports = {
|
|||||||
path: path.resolve(__dirname, 'demo/dist'),
|
path: path.resolve(__dirname, 'demo/dist'),
|
||||||
filename: 'bundle.js'
|
filename: 'bundle.js'
|
||||||
},
|
},
|
||||||
devtool: 'source-map',
|
devtool: 'source-map'
|
||||||
module: {
|
|
||||||
rules: [
|
|
||||||
{
|
|
||||||
test: /\.js$/,
|
|
||||||
use: ["source-map-loader"],
|
|
||||||
enforce: "pre"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user