Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
711752a56a | ||
|
|
3592c641cd |
@@ -1,8 +1,8 @@
|
|||||||
---
|
---
|
||||||
name: Question
|
name: Question
|
||||||
about: The issue tracker is not for questions. Please ask questions on https://stackoverflow.com/questions/tagged/xtermjs
|
about: The issue tracker is not for questions. Please ask questions on https://stackoverflow.com/questions/tagged/xtermjs or https://gitter.im/sourcelair/xterm.js.
|
||||||
---
|
---
|
||||||
|
|
||||||
🛑 The issue tracker is not for questions 🛑
|
🛑 The issue tracker is not for questions 🛑
|
||||||
|
|
||||||
If you have a question, please ask it on https://stackoverflow.com/questions/tagged/xtermjs.
|
If you have a question, please ask it on https://stackoverflow.com/questions/tagged/xtermjs or https://gitter.im/sourcelair/xterm.js.
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ npm-debug.log
|
|||||||
/.idea/
|
/.idea/
|
||||||
.env
|
.env
|
||||||
build/
|
build/
|
||||||
|
.vscode/
|
||||||
.DS_Store
|
.DS_Store
|
||||||
fixtures/typings-test/*.js
|
fixtures/typings-test/*.js
|
||||||
package-lock.json
|
package-lock.json
|
||||||
|
|||||||
Vendored
-44
@@ -1,44 +0,0 @@
|
|||||||
{
|
|
||||||
"version": "0.2.0",
|
|
||||||
"configurations": [
|
|
||||||
{
|
|
||||||
"type": "node",
|
|
||||||
"request": "launch",
|
|
||||||
"name": "Unit Tests",
|
|
||||||
"cwd": "${workspaceRoot}",
|
|
||||||
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/mocha",
|
|
||||||
"windows": {
|
|
||||||
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/mocha.cmd"
|
|
||||||
},
|
|
||||||
"runtimeArgs": [
|
|
||||||
"--colors",
|
|
||||||
"--recursive",
|
|
||||||
"${workspaceRoot}/lib"
|
|
||||||
],
|
|
||||||
"sourceMaps": true,
|
|
||||||
"outFiles": [ "${workspaceRoot}/lib/**/*.js" ],
|
|
||||||
"internalConsoleOptions": "openOnSessionStart"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "chrome",
|
|
||||||
"request": "launch",
|
|
||||||
"name": "Demo Client",
|
|
||||||
"url": "http://0.0.0.0:3000",
|
|
||||||
"windows": {
|
|
||||||
"url": "http://127.0.0.1:3000"
|
|
||||||
},
|
|
||||||
"webRoot": "${workspaceFolder}/"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "node",
|
|
||||||
"request": "launch",
|
|
||||||
"name": "Demo Server",
|
|
||||||
"runtimeExecutable": "npm",
|
|
||||||
"runtimeArgs": [
|
|
||||||
"run",
|
|
||||||
"start-debug"
|
|
||||||
],
|
|
||||||
"port": 9229
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
List of xterm.js contributors. Updated before every release.
|
||||||
|
|
||||||
|
7PH <b.raymond@cern.ch>
|
||||||
|
7PH <b.raymond@protonmail.com>
|
||||||
|
Adrian Labbé <adrilabbelol@gmail.com>
|
||||||
|
Aleksandr Andrienko <aandrienko@codenvy.com>
|
||||||
|
Aleksandr Andriienko <oandriie@redhat.com>
|
||||||
|
Alessandro Nadalin <alessandro.nadalin@gmail.com>
|
||||||
|
Alexander Olsson <noseglid@gmail.com>
|
||||||
|
Alexandre Petit-Pas <petitpasalexandre@gmail.com>
|
||||||
|
Alexey Kontsevoy <alexey@gravitational.com>
|
||||||
|
Andres Mejia <amejia004@gmail.com>
|
||||||
|
Anish Athalye <me@anishathalye.com>
|
||||||
|
Anthony Lapenna <lapenna.anthony@gmail.com>
|
||||||
|
Antonin Stefanutti <antonin@stefanutti.fr>
|
||||||
|
Antonis Kalipetis <akalipetis@sourcelair.com>
|
||||||
|
Anton Skshidlevsky <meefik@gmail.com>
|
||||||
|
Anton Yurovskykh <anton.yurovskykh@gmail.com>
|
||||||
|
Artem Arbatskiy <artyom.arbatskiy@gmail.com>
|
||||||
|
Austin Robertson <austinrobertson@gmail.com>
|
||||||
|
ayapi <colors.aya@gmail.com>
|
||||||
|
Ben Hall <ben@benhall.me.uk>
|
||||||
|
Benjamin Fischer <benjamin.fischer@rwth-aachen.de>
|
||||||
|
Benjamin Raymond <b.raymond@cern.ch>
|
||||||
|
Benjamin Woodruff <github@benjam.info>
|
||||||
|
Bill Church <billchurch@users.noreply.github.com>
|
||||||
|
Bob Reid <bobreid@Bobs-MacBook-Pro.local>
|
||||||
|
bottleofwater <nison.mael+bottleofwater@gmail.com>
|
||||||
|
Brandon Bayer <b@bayer.ws>
|
||||||
|
Brian Mock <brian@mockbrian.com>
|
||||||
|
Bruno Ribeiro <b.m.fernandes.ribeiro@gmail.com>
|
||||||
|
Bruno Ribeito <b.m.fernandes.ribeiro@gmail.com>
|
||||||
|
Carson Anderson <carson@betterservers.com>
|
||||||
|
CHaBou <chabup@delean.fr>
|
||||||
|
Christian Budde Christensen <budde377@gmail.com>
|
||||||
|
Christof Marti <chrmarti@microsoft.com>
|
||||||
|
Christopher Jeffrey <chjjeffrey@gmail.com>
|
||||||
|
coderaiser <mnemonic.enemy@gmail.com>
|
||||||
|
Damien Tournoud <damien@platform.sh>
|
||||||
|
Dan Brown <jdanbrown@gmail.com>
|
||||||
|
Daniel Griffen <dagriffe@microsoft.com>
|
||||||
|
Daniel Griffen <daniel@griffen.io>
|
||||||
|
Daniel Imms <daimms@microsoft.com>
|
||||||
|
Daniel Risacher <drisacher@gmail.com>
|
||||||
|
Dan Kaplun <dbkaplun@twitch.tv>
|
||||||
|
Darin Morrison <freebroccolo@users.noreply.github.com>
|
||||||
|
dcylabs <dcylabs@gmail.com>
|
||||||
|
Dominik Csapak <d.csapak@proxmox.com>
|
||||||
|
Edgar Andrés Margffoy Tuay <andfoy@gmail.com>
|
||||||
|
Elliot Saba <staticfloat@gmail.com>
|
||||||
|
Exile <exile@chamber.ee>
|
||||||
|
Felipe Gasper <felipe@felipegasper.com>
|
||||||
|
Felix <30559812+felixse@users.noreply.github.com>
|
||||||
|
ficristo <ficristo.work@gmail.com>
|
||||||
|
Gary Ritchie <gary@rstudio.com>
|
||||||
|
hiro-su <h.sugipon@gmail.com>
|
||||||
|
Ian Lewis <ianlewis@google.com>
|
||||||
|
imoses <ido@twiggle.com>
|
||||||
|
InDieTasten <indietasten@gmail.com>
|
||||||
|
irokas <akasidiari@inf.uth.gr>
|
||||||
|
Jakob Gillich <jakob@gillich.me>
|
||||||
|
Jan Kuri <jkuri88@gmail.com>
|
||||||
|
Jean Bruenn <himself@jeanbruenn.info>
|
||||||
|
Jeff Principe <princjef@gmail.com>
|
||||||
|
Jeremy Danyow <jedanyow@microsoft.com>
|
||||||
|
Jianhui Zhao <jianhuizhao329@gmail.com>
|
||||||
|
Joao Moreno <jomo@microsoft.com>
|
||||||
|
Joao Moreno <mail@joaomoreno.com>
|
||||||
|
Johannes Zellner <johannes@nebulon.de>
|
||||||
|
Jon Austin <jon.i.austin@gmail.com>
|
||||||
|
Jon Kohler <jon@nutanix.com>
|
||||||
|
Jon Masters <jon.masters@sky.com>
|
||||||
|
Jörg Breitbart <jerch@rockborn.de>
|
||||||
|
Jose Anton <roke22@gmail.com>
|
||||||
|
jpoth <poth.john@gmail.com>
|
||||||
|
Justin Luk <jluk@users.noreply.github.com>
|
||||||
|
Justin Mecham <justin@mecham.me>
|
||||||
|
Kirill Merkushev <lanwen@yandex.ru>
|
||||||
|
Krasimir Tsonev <krasimir@outset.ws>
|
||||||
|
Ledion Bitincka <lbitincka@gmail.com>
|
||||||
|
Linus Unnebäck <linus@folkdatorn.se>
|
||||||
|
Luca <LucaT1@users.noreply.github.com>
|
||||||
|
Lucian Buzzo <lucian.buzzo@gmail.com>
|
||||||
|
Lukas Drgon <lukas.drgon@gmail.com>
|
||||||
|
Lukas Geiger <lukas.geiger94@gmail.com>
|
||||||
|
Maël Nison <nison.mael@gmail.com>
|
||||||
|
Marc Dumais <marc.dumais@ericsson.com>
|
||||||
|
Marek Libra <mlibra@redhat.com>
|
||||||
|
Markus F.X.J. Oberhumer <markus@oberhumer.com>
|
||||||
|
Martin Chloride <i@martincl2.me>
|
||||||
|
Martin Koppehel <martin.koppehel@st.ovgu.de>
|
||||||
|
Martin Wang <jiahaow@ca.ibm.com>
|
||||||
|
Matt Bierner <matb@microsoft.com>
|
||||||
|
Matthew James <matthew.d.james87@gmail.com>
|
||||||
|
Michael Irwin <mikesir87@gmail.com>
|
||||||
|
Mikko Karvonen <mikko.karvonen@arm.com>
|
||||||
|
mofux <t.zilz@mofux.org>
|
||||||
|
muji <muji@tmpfs.org>
|
||||||
|
Nicolas Ramz <nicolas.ramz@gmail.com>
|
||||||
|
npezza93 <npezza93@gmail.com>
|
||||||
|
Oleksandr Andriienko <oandriie@redhat.com>
|
||||||
|
Paris Kasidiaris <pariskasidiaris@gmail.com>
|
||||||
|
Paris Kasidiaris <paris@sourcelair.com>
|
||||||
|
Peng Xiao <pengxiao@outlook.com>
|
||||||
|
Peter Baumgarten <me@peterbaumgarten.com>
|
||||||
|
Philip Olson <philip.olson@protonmail.ch>
|
||||||
|
PowerHat <31401273+7PH@users.noreply.github.com>
|
||||||
|
PowerHat <b.raymond@protonmail.com>
|
||||||
|
pro-src <34285059+pro-src@users.noreply.github.com>
|
||||||
|
pro-src <rodneyd.teal@gmail.com>
|
||||||
|
Rick Baker <rick@ricktbaker.com>
|
||||||
|
runarberg <runar@greenqloud.com>
|
||||||
|
Saad Malik <simfox3@gmail.com>
|
||||||
|
Samuel Williams <samuel.williams@oriontransfer.co.nz>
|
||||||
|
Saswat Das <saswatds@users.noreply.github.com>
|
||||||
|
Saul Costa <saul@codevolve.com>
|
||||||
|
Segev Finer <segev208@gmail.com>
|
||||||
|
Shuanglei Tao <tsl0922@gmail.com>
|
||||||
|
sitzmar <sitzmar@gmail.com>
|
||||||
|
Steven Silvester <steven.silvester@ieee.org>
|
||||||
|
stuicey <stuicey@users.noreply.github.com>
|
||||||
|
Sven-Hendrik Haase <svenstaro@gmail.com>
|
||||||
|
t-amqi <umberos@gmail.com>
|
||||||
|
Thanasis Daglis <thanasis@sourcelair.com>
|
||||||
|
thomas <thomas@kreativkonzentrat.de>
|
||||||
|
Thomas Zilz <t.zilz@mofux.org>
|
||||||
|
Tine Jozelj <tine.jozelj@outlook.com>
|
||||||
|
Tyler Jewell <tjewell@codenvy.com>
|
||||||
|
Vincent Woo <me@vincentwoo.com>
|
||||||
|
Viraj Sinha <virajosinha@gmail.com>
|
||||||
|
yutaka <shigure@refy.net>
|
||||||
|
YuviPanda <yuvipanda@gmail.com>
|
||||||
|
ZHAO Xudong <zxdong@gmail.com>
|
||||||
|
赵雪珂 <zhaoxueke@onething.net>
|
||||||
+1
-2
@@ -30,8 +30,7 @@ opening an issue, read these pointers.
|
|||||||
|
|
||||||
## Contributing code
|
## Contributing code
|
||||||
|
|
||||||
You can find issues to work on by looking at the [help wanted](https://github.com/xtermjs/xterm.js/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) or [good first issue](https://github.com/xtermjs/xterm.js/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) issues. It's a good idea to comment on the issue saying that you're taking it, just in case someone else comes along and you duplicate work. Once you have your issue, here are the steps to contribute:
|
- Make sure you have a [GitHub account](https://github.com/join)
|
||||||
|
|
||||||
- Fork [xterm.js](https://github.com/sourcelair/xterm.js/)
|
- Fork [xterm.js](https://github.com/sourcelair/xterm.js/)
|
||||||
([how to fork a repo](https://help.github.com/articles/fork-a-repo))
|
([how to fork a repo](https://help.github.com/articles/fork-a-repo))
|
||||||
- Get the [xterm.js demo](https://github.com/xtermjs/xterm.js/wiki/Contributing#running-the-demo) running
|
- Get the [xterm.js demo](https://github.com/xtermjs/xterm.js/wiki/Contributing#running-the-demo) running
|
||||||
|
|||||||
+1
-1
@@ -16,4 +16,4 @@ RUN npm install
|
|||||||
COPY . /usr/src/app
|
COPY . /usr/src/app
|
||||||
|
|
||||||
# Run the tests and build, to make sure everything is working nicely
|
# Run the tests and build, to make sure everything is working nicely
|
||||||
RUN npm run build && npm run test
|
RUN npm run build && npm run webpack && npm run test
|
||||||
|
|||||||
@@ -2,20 +2,23 @@
|
|||||||
|
|
||||||
[](https://dev.azure.com/xtermjs/xterm.js/_build/latest?definitionId=3)
|
[](https://dev.azure.com/xtermjs/xterm.js/_build/latest?definitionId=3)
|
||||||
[](https://coveralls.io/github/xtermjs/xterm.js?branch=master)
|
[](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 front-end component written in TypeScript that lets applications bring fully-featured terminals to their users in the browser. It's used by popular projects such as VS Code, Hyper and Theia.
|
Xterm.js is a terminal front-end component written in JavaScript that works in the browser.
|
||||||
|
|
||||||
|
It enables applications to provide fully featured terminals to their users and create great development experiences.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
- **Text-based application support**: Use xterm.js to work with applications like `bash`, `git` etc.
|
||||||
- **Terminal apps just work**: Xterm.js works with most terminal apps such as `bash`, `vim` and `tmux`, this includes support for curses-based apps and mouse event support
|
- **Curses-based application support**: Use xterm.js to work with applications like `vim`, `tmux` etc.
|
||||||
- **Perfomant**: Xterm.js is *really* fast, it even includes a GPU-accelerated renderer
|
- **Mouse events support**: Xterm.js captures mouse events like click and scroll and passes them to the terminal's back-end controlling process
|
||||||
- **Rich unicode support**: Supports CJK, emojis and IMEs
|
- **CJK (Chinese, Japanese, Korean) character support**: Xterm.js renders CJK characters seamlessly
|
||||||
- **Self-contained**: Requires zero dependencies to work
|
- **IME support**: Insert international (including CJK) characters using IME input with your keyboard
|
||||||
- **Accessible**: Screen reader support can be turned on using the `screenReaderMode` option
|
- **Self-contained library**: Xterm.js works on its own. It does not require any external libraries like jQuery or React to work
|
||||||
- **And much more**: Links, theming, addons, well documented API, etc.
|
- **Modular, event-based API**: Lets you build addons and themes with ease
|
||||||
|
|
||||||
## What xterm.js is not
|
## What xterm.js is not
|
||||||
|
|
||||||
- Xterm.js is not a terminal application that you can download and use on your computer
|
- Xterm.js is not a terminal application that you can download and use on your computer
|
||||||
- Xterm.js is not `bash`. Xterm.js can be connected to processes like `bash` and let you interact with them (provide input, receive output)
|
- Xterm.js is not `bash`. Xterm.js can be connected to processes like `bash` and let you interact with them (provide input, receive output)
|
||||||
|
|
||||||
@@ -27,7 +30,7 @@ First you need to install the module, we ship exclusively through [npm](https://
|
|||||||
npm install xterm
|
npm install xterm
|
||||||
```
|
```
|
||||||
|
|
||||||
To start using xterm.js on your browser, add the `xterm.js` and `xterm.css` to the head of your html page. Then create a `<div id="terminal"></div>` onto which xterm can attach itself. Finally instantiate the `Terminal` object and then call the `open` function with the DOM object of the `div`.
|
To start using xterm.js on your browser, add the `xterm.js` and `xterm.css` to the head of your html page. Then create a `<div id="terminal"></div>` onto which xterm can attach itself.
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
@@ -47,24 +50,33 @@ To start using xterm.js on your browser, add the `xterm.js` and `xterm.css` to t
|
|||||||
</html>
|
</html>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Finally instantiate the `Terminal` object and then call the `open` function with the DOM object of the `div`.
|
||||||
|
|
||||||
### Importing
|
### Importing
|
||||||
|
|
||||||
The recommended way to load xterm.js is via the ES6 module syntax:
|
The proposed way to load xterm.js is via the ES6 module syntax.
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
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 the [public API](https://github.com/xtermjs/xterm.js/blob/master/typings/xterm.d.ts).
|
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.
|
||||||
|
|
||||||
To use an addon, just import the JavaScript module and pass it to `Terminal`'s `applyAddon` method:
|
To use an addon, just import the JavaScript module and pass it to `Terminal`'s `applyAddon` method:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
import { Terminal } from 'xterm';
|
import { Terminal } from xterm;
|
||||||
import * as fit from 'xterm/lib/addons/fit/fit';
|
import * as fit from 'xterm/lib/addons/fit/fit';
|
||||||
|
|
||||||
|
|
||||||
Terminal.applyAddon(fit);
|
Terminal.applyAddon(fit);
|
||||||
|
|
||||||
var xterm = new Terminal(); // Instantiate the terminal
|
var xterm = new Terminal(); // Instantiate the terminal
|
||||||
@@ -75,16 +87,25 @@ 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. `(xterm as any).fit()`. Alternatively, you can import the addon function and enhance the terminal on demand. This has better typing support and is friendly to treeshaking.
|
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()`.
|
||||||
|
|
||||||
|
Alternatively, you can import addon function and enhance the terminal on demand. This would have better typing support and is friendly to treeshaking. E.g.:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { Terminal } from 'xterm';
|
import { Terminal } from 'xterm';
|
||||||
import { fit } from 'xterm/lib/addons/fit/fit';
|
import { fit } from 'xterm/lib/addons/fit/fit';
|
||||||
const xterm = new Terminal();
|
const xterm = new Terminal();
|
||||||
|
|
||||||
fit(xterm); // Fit the terminal when necessary
|
// Fit the terminal when necessary:
|
||||||
|
fit(xterm);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Third party addons
|
||||||
|
|
||||||
|
There are also the following third party addons available:
|
||||||
|
|
||||||
|
- [xterm-webfont](https://www.npmjs.com/package/xterm-webfont)
|
||||||
|
|
||||||
## Browser Support
|
## Browser Support
|
||||||
|
|
||||||
Since xterm.js is typically implemented as a developer tool, only modern browsers are supported officially. Here is a list of the versions we aim to support:
|
Since xterm.js is typically implemented as a developer tool, only modern browsers are supported officially. Here is a list of the versions we aim to support:
|
||||||
@@ -95,13 +116,11 @@ Since xterm.js is typically implemented as a developer tool, only modern browser
|
|||||||
- Safari latest
|
- Safari latest
|
||||||
- IE11
|
- IE11
|
||||||
|
|
||||||
Xterm.js works seamlessly in [Electron](https://electronjs.org/) apps and may even work on earlier versions of the browsers, these are the versions we strive to keep working.
|
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
|
## 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.
|
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.
|
||||||
|
|
||||||
Note that some APIs are marked *experimental*, these are added to enable experimentation with new ideas without committing to support it like a normal [semver](https://semver.org/) API. Note that these APIs can change radically between versions so be sure to read release notes if you plan on using experimental APIs.
|
|
||||||
|
|
||||||
## 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.
|
||||||
@@ -140,7 +159,7 @@ computational environment for Jupyter, supporting interactive data science and s
|
|||||||
- [**rtty**](https://github.com/zhaojh329/rtty): A reverse proxy WebTTY. It is composed of the client and the server.
|
- [**rtty**](https://github.com/zhaojh329/rtty): A reverse proxy WebTTY. It is composed of the client and the server.
|
||||||
- [**Pisth**](https://github.com/ColdGrub1384/Pisth): An SFTP and SSH client for iOS
|
- [**Pisth**](https://github.com/ColdGrub1384/Pisth): An SFTP and SSH client for iOS
|
||||||
- [**abstruse**](https://github.com/bleenco/abstruse): Abstruse CI is a continuous integration platform based on Node.JS and Docker.
|
- [**abstruse**](https://github.com/bleenco/abstruse): Abstruse CI is a continuous integration platform based on Node.JS and Docker.
|
||||||
- [**Azure Data Studio**](https://github.com/Microsoft/azuredatastudio): A data management tool that enables working with SQL Server, Azure SQL DB and SQL DW from Windows, macOS and Linux.
|
- [**Microsoft SQL Operations Studio**](https://github.com/Microsoft/sqlopsstudio): A data management tool that enables working with SQL Server, Azure SQL DB and SQL DW from Windows, macOS and Linux
|
||||||
- [**FreeMAN**](https://github.com/matthew-matvei/freeman): A free, cross-platform file manager for power users
|
- [**FreeMAN**](https://github.com/matthew-matvei/freeman): A free, cross-platform file manager for power users
|
||||||
- [**Fluent Terminal**](https://github.com/felixse/FluentTerminal): A terminal emulator based on UWP and web technologies.
|
- [**Fluent Terminal**](https://github.com/felixse/FluentTerminal): A terminal emulator based on UWP and web technologies.
|
||||||
- [**Hyper**](https://hyper.is): A terminal built on web technologies
|
- [**Hyper**](https://hyper.is): A terminal built on web technologies
|
||||||
@@ -152,8 +171,6 @@ computational environment for Jupyter, supporting interactive data science and s
|
|||||||
- [**SSH Web Client**](https://github.com/roke22/PHP-SSH2-Web-Client): SSH Web Client with PHP.
|
- [**SSH Web Client**](https://github.com/roke22/PHP-SSH2-Web-Client): SSH Web Client with PHP.
|
||||||
- [**Shellvault**](https://www.shellvault.io): The cloud-based SSH terminal you can access from anywhere.
|
- [**Shellvault**](https://www.shellvault.io): The cloud-based SSH terminal you can access from anywhere.
|
||||||
- [**Juno**](http://junolab.org/): A flexible Julia IDE, based on Atom.
|
- [**Juno**](http://junolab.org/): A flexible Julia IDE, based on Atom.
|
||||||
- [**webssh**](https://github.com/huashengdun/webssh): Web based ssh client.
|
|
||||||
- [**info-beamer hosted**](https://info-beamer.com): Uses Xterm.js to manage digital signage devices from the web dashboard.
|
|
||||||
|
|
||||||
[And much more...](https://github.com/xtermjs/xterm.js/network/dependents)
|
[And much more...](https://github.com/xtermjs/xterm.js/network/dependents)
|
||||||
|
|
||||||
@@ -163,7 +180,7 @@ Do you use xterm.js in your application as well? Please [open a Pull Request](ht
|
|||||||
|
|
||||||
Xterm.js follows a monthly release cycle roughly.
|
Xterm.js follows a monthly release cycle roughly.
|
||||||
|
|
||||||
All current and past releases are available on this repo's [Releases page](https://github.com/sourcelair/xterm.js/releases), while a rough roadmap is available by looking through [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
|
## Contributing
|
||||||
|
|
||||||
|
|||||||
Executable
+7
@@ -0,0 +1,7 @@
|
|||||||
|
#! /usr/bin/env sh
|
||||||
|
|
||||||
|
tail --lines=+3 AUTHORS > AUTHORS.tmp
|
||||||
|
git log --format='%aN <%aE>' >> AUTHORS.tmp
|
||||||
|
echo "List of xterm.js contributors. Updated before every release.\n" > AUTHORS
|
||||||
|
sort -u AUTHORS.tmp >> AUTHORS
|
||||||
|
rm -f AUTHORS.tmp
|
||||||
@@ -17,6 +17,9 @@ CURRENT_PACKAGE_JSON_VERSION=$(cat package.json \
|
|||||||
export BUILD_DIR=dist
|
export BUILD_DIR=dist
|
||||||
npm run build
|
npm run build
|
||||||
|
|
||||||
|
# Update AUTHORS file
|
||||||
|
sh bin/generate-authors
|
||||||
|
|
||||||
# Update version in package.json
|
# Update version in package.json
|
||||||
sed -i "s/\"version\": \"$CURRENT_PACKAGE_JSON_VERSION\"/\"version\": \"$NEW_VERSION\"/g" package.json
|
sed -i "s/\"version\": \"$CURRENT_PACKAGE_JSON_VERSION\"/\"version\": \"$NEW_VERSION\"/g" package.json
|
||||||
|
|
||||||
|
|||||||
+22
-21
@@ -14,7 +14,6 @@ import * as fullscreen from '../lib/addons/fullscreen/fullscreen';
|
|||||||
import * as search from '../lib/addons/search/search';
|
import * as search from '../lib/addons/search/search';
|
||||||
import * as webLinks from '../lib/addons/webLinks/webLinks';
|
import * as webLinks from '../lib/addons/webLinks/webLinks';
|
||||||
import * as winptyCompat from '../lib/addons/winptyCompat/winptyCompat';
|
import * as winptyCompat from '../lib/addons/winptyCompat/winptyCompat';
|
||||||
import { ISearchOptions } from '../lib/addons/search/Interfaces';
|
|
||||||
|
|
||||||
// Pulling in the module's types relies on the <reference> above, it's looks a
|
// Pulling in the module's types relies on the <reference> above, it's looks a
|
||||||
// little weird here as we're importing "this" module
|
// little weird here as we're importing "this" module
|
||||||
@@ -51,14 +50,6 @@ function setPadding(): void {
|
|||||||
term.fit();
|
term.fit();
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSearchOptions(): ISearchOptions {
|
|
||||||
return {
|
|
||||||
regex: (document.getElementById('regex') as HTMLInputElement).checked,
|
|
||||||
wholeWord: (document.getElementById('whole-word') as HTMLInputElement).checked,
|
|
||||||
caseSensitive: (document.getElementById('case-sensitive') as HTMLInputElement).checked
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
createTerminal();
|
createTerminal();
|
||||||
|
|
||||||
const disposeRecreateButtonHandler = () => {
|
const disposeRecreateButtonHandler = () => {
|
||||||
@@ -106,15 +97,26 @@ function createTerminal(): void {
|
|||||||
|
|
||||||
addDomListener(paddingElement, 'change', setPadding);
|
addDomListener(paddingElement, 'change', setPadding);
|
||||||
|
|
||||||
addDomListener(actionElements.findNext, 'keyup', (e) => {
|
addDomListener(actionElements.findNext, 'keypress', (e) => {
|
||||||
const searchOptions = getSearchOptions();
|
if (e.key === 'Enter') {
|
||||||
searchOptions.incremental = e.key !== `Enter`;
|
e.preventDefault();
|
||||||
term.findNext(actionElements.findNext.value, searchOptions);
|
const searchOptions = {
|
||||||
|
regex: (document.getElementById('regex') as HTMLInputElement).checked,
|
||||||
|
wholeWord: (document.getElementById('whole-word') as HTMLInputElement).checked,
|
||||||
|
caseSensitive: (document.getElementById('case-sensitive') as HTMLInputElement).checked
|
||||||
|
};
|
||||||
|
term.findNext(actionElements.findNext.value, searchOptions);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
addDomListener(actionElements.findPrevious, 'keypress', (e) => {
|
||||||
addDomListener(actionElements.findPrevious, 'keyup', (e) => {
|
if (e.key === 'Enter') {
|
||||||
if (e.key === `Enter`) {
|
e.preventDefault();
|
||||||
term.findPrevious(actionElements.findPrevious.value, getSearchOptions());
|
const searchOptions = {
|
||||||
|
regex: (document.getElementById('regex') as HTMLInputElement).checked,
|
||||||
|
wholeWord: (document.getElementById('whole-word') as HTMLInputElement).checked,
|
||||||
|
caseSensitive: (document.getElementById('case-sensitive') as HTMLInputElement).checked
|
||||||
|
};
|
||||||
|
term.findPrevious(actionElements.findPrevious.value, searchOptions);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -205,8 +207,7 @@ function initOptions(term: TerminalType): void {
|
|||||||
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'],
|
rendererType: ['dom', 'canvas']
|
||||||
experimentalBufferLineImpl: ['JsArray', 'TypedArray']
|
|
||||||
};
|
};
|
||||||
const options = Object.keys((<any>term)._core.options);
|
const options = Object.keys((<any>term)._core.options);
|
||||||
const booleanOptions = [];
|
const booleanOptions = [];
|
||||||
@@ -233,7 +234,7 @@ function initOptions(term: TerminalType): void {
|
|||||||
});
|
});
|
||||||
html += '</div><div class="option-group">';
|
html += '</div><div class="option-group">';
|
||||||
numberOptions.forEach(o => {
|
numberOptions.forEach(o => {
|
||||||
html += `<div class="option"><label>${o} <input id="opt-${o}" type="number" value="${term.getOption(o)}" step="${o === 'lineHeight' ? '0.1' : '1'}"/></label></div>`;
|
html += `<div class="option"><label>${o} <input id="opt-${o}" type="number" value="${term.getOption(o)}"/></label></div>`;
|
||||||
});
|
});
|
||||||
html += '</div><div class="option-group">';
|
html += '</div><div class="option-group">';
|
||||||
Object.keys(stringOptions).forEach(o => {
|
Object.keys(stringOptions).forEach(o => {
|
||||||
@@ -263,7 +264,7 @@ function initOptions(term: TerminalType): void {
|
|||||||
if (o === 'cols' || o === 'rows') {
|
if (o === 'cols' || o === 'rows') {
|
||||||
updateTerminalSize();
|
updateTerminalSize();
|
||||||
} else {
|
} else {
|
||||||
term.setOption(o, o === 'lineHeight' ? parseFloat(input.value) : parseInt(input.value, 10));
|
term.setOption(o, parseInt(input.value, 10));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+68
-90
@@ -1,106 +1,84 @@
|
|||||||
var express = require('express');
|
var express = require('express');
|
||||||
var expressWs = require('express-ws');
|
var app = express();
|
||||||
|
var expressWs = require('express-ws')(app);
|
||||||
var os = require('os');
|
var os = require('os');
|
||||||
var pty = require('node-pty');
|
var pty = require('node-pty');
|
||||||
|
|
||||||
function startServer() {
|
var terminals = {},
|
||||||
var app = express();
|
logs = {};
|
||||||
expressWs(app);
|
|
||||||
|
|
||||||
var terminals = {},
|
app.use('/build', express.static(__dirname + '/../build'));
|
||||||
logs = {};
|
|
||||||
|
|
||||||
app.use('/build', express.static(__dirname + '/../build'));
|
app.get('/', function(req, res){
|
||||||
|
res.sendFile(__dirname + '/index.html');
|
||||||
|
});
|
||||||
|
|
||||||
app.get('/', function(req, res){
|
app.get('/style.css', function(req, res){
|
||||||
res.sendFile(__dirname + '/index.html');
|
res.sendFile(__dirname + '/style.css');
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/dist/client-bundle.js', function(req, res){
|
||||||
|
res.sendFile(__dirname + '/dist/client-bundle.js');
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post('/terminals', function (req, res) {
|
||||||
|
var cols = parseInt(req.query.cols),
|
||||||
|
rows = parseInt(req.query.rows),
|
||||||
|
term = pty.spawn(process.platform === 'win32' ? 'cmd.exe' : 'bash', [], {
|
||||||
|
name: 'xterm-color',
|
||||||
|
cols: cols || 80,
|
||||||
|
rows: rows || 24,
|
||||||
|
cwd: process.env.PWD,
|
||||||
|
env: process.env
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('Created terminal with PID: ' + term.pid);
|
||||||
|
terminals[term.pid] = term;
|
||||||
|
logs[term.pid] = '';
|
||||||
|
term.on('data', function(data) {
|
||||||
|
logs[term.pid] += data;
|
||||||
});
|
});
|
||||||
|
res.send(term.pid.toString());
|
||||||
|
res.end();
|
||||||
|
});
|
||||||
|
|
||||||
app.get('/style.css', function(req, res){
|
app.post('/terminals/:pid/size', function (req, res) {
|
||||||
res.sendFile(__dirname + '/style.css');
|
var pid = parseInt(req.params.pid),
|
||||||
});
|
cols = parseInt(req.query.cols),
|
||||||
|
rows = parseInt(req.query.rows),
|
||||||
|
term = terminals[pid];
|
||||||
|
|
||||||
app.get('/dist/client-bundle.js', function(req, res){
|
term.resize(cols, rows);
|
||||||
res.sendFile(__dirname + '/dist/client-bundle.js');
|
console.log('Resized terminal ' + pid + ' to ' + cols + ' cols and ' + rows + ' rows.');
|
||||||
});
|
res.end();
|
||||||
|
});
|
||||||
|
|
||||||
app.post('/terminals', function (req, res) {
|
app.ws('/terminals/:pid', function (ws, req) {
|
||||||
var cols = parseInt(req.query.cols),
|
var term = terminals[parseInt(req.params.pid)];
|
||||||
rows = parseInt(req.query.rows),
|
console.log('Connected to terminal ' + term.pid);
|
||||||
term = pty.spawn(process.platform === 'win32' ? 'cmd.exe' : 'bash', [], {
|
ws.send(logs[term.pid]);
|
||||||
name: 'xterm-color',
|
|
||||||
cols: cols || 80,
|
|
||||||
rows: rows || 24,
|
|
||||||
cwd: process.env.PWD,
|
|
||||||
env: process.env
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('Created terminal with PID: ' + term.pid);
|
term.on('data', function(data) {
|
||||||
terminals[term.pid] = term;
|
try {
|
||||||
logs[term.pid] = '';
|
ws.send(data);
|
||||||
term.on('data', function(data) {
|
} catch (ex) {
|
||||||
logs[term.pid] += data;
|
// The WebSocket is not open, ignore
|
||||||
});
|
|
||||||
res.send(term.pid.toString());
|
|
||||||
res.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
app.post('/terminals/:pid/size', function (req, res) {
|
|
||||||
var pid = parseInt(req.params.pid),
|
|
||||||
cols = parseInt(req.query.cols),
|
|
||||||
rows = parseInt(req.query.rows),
|
|
||||||
term = terminals[pid];
|
|
||||||
|
|
||||||
term.resize(cols, rows);
|
|
||||||
console.log('Resized terminal ' + pid + ' to ' + cols + ' cols and ' + rows + ' rows.');
|
|
||||||
res.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
app.ws('/terminals/:pid', function (ws, req) {
|
|
||||||
var term = terminals[parseInt(req.params.pid)];
|
|
||||||
console.log('Connected to terminal ' + term.pid);
|
|
||||||
ws.send(logs[term.pid]);
|
|
||||||
|
|
||||||
function buffer(socket, timeout) {
|
|
||||||
let s = '';
|
|
||||||
let sender = null;
|
|
||||||
return (data) => {
|
|
||||||
s += data;
|
|
||||||
if (!sender) {
|
|
||||||
sender = setTimeout(() => {
|
|
||||||
socket.send(s);
|
|
||||||
s = '';
|
|
||||||
sender = null;
|
|
||||||
}, timeout);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
const send = buffer(ws, 5);
|
|
||||||
|
|
||||||
term.on('data', function(data) {
|
|
||||||
try {
|
|
||||||
send(data);
|
|
||||||
} catch (ex) {
|
|
||||||
// The WebSocket is not open, ignore
|
|
||||||
}
|
|
||||||
});
|
|
||||||
ws.on('message', function(msg) {
|
|
||||||
term.write(msg);
|
|
||||||
});
|
|
||||||
ws.on('close', function () {
|
|
||||||
term.kill();
|
|
||||||
console.log('Closed terminal ' + term.pid);
|
|
||||||
// Clean things up
|
|
||||||
delete terminals[term.pid];
|
|
||||||
delete logs[term.pid];
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
ws.on('message', function(msg) {
|
||||||
|
term.write(msg);
|
||||||
|
});
|
||||||
|
ws.on('close', function () {
|
||||||
|
term.kill();
|
||||||
|
console.log('Closed terminal ' + term.pid);
|
||||||
|
// Clean things up
|
||||||
|
delete terminals[term.pid];
|
||||||
|
delete logs[term.pid];
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
var port = process.env.PORT || 3000,
|
var port = process.env.PORT || 3000,
|
||||||
host = os.platform() === 'win32' ? '127.0.0.1' : '0.0.0.0';
|
host = os.platform() === 'win32' ? '127.0.0.1' : '0.0.0.0';
|
||||||
|
|
||||||
console.log('App listening to http://' + host + ':' + port);
|
console.log('App listening to http://' + host + ':' + port);
|
||||||
app.listen(port, host);
|
app.listen(port, host);
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = startServer;
|
|
||||||
|
|||||||
+2
-7
@@ -8,9 +8,9 @@
|
|||||||
const cp = require('child_process');
|
const cp = require('child_process');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const webpack = require('webpack');
|
const webpack = require('webpack');
|
||||||
const startServer = require('./server.js');
|
|
||||||
|
|
||||||
startServer();
|
// Launch server
|
||||||
|
cp.spawn('node', [path.resolve(__dirname, 'server.js')], { stdio: 'inherit' });
|
||||||
|
|
||||||
// Build/watch client source
|
// Build/watch client source
|
||||||
const clientConfig = {
|
const clientConfig = {
|
||||||
@@ -22,11 +22,6 @@ const clientConfig = {
|
|||||||
test: /\.tsx?$/,
|
test: /\.tsx?$/,
|
||||||
use: 'ts-loader',
|
use: 'ts-loader',
|
||||||
exclude: /node_modules/
|
exclude: /node_modules/
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.js$/,
|
|
||||||
use: ["source-map-loader"],
|
|
||||||
enforce: "pre"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
/// <reference path="../../typings/xterm.d.ts" />
|
/// <reference path="../../typings/xterm.d.ts" />
|
||||||
|
|
||||||
import { Terminal, IDisposable } from 'xterm';
|
import { Terminal } from 'xterm';
|
||||||
|
|
||||||
namespace constructor {
|
namespace constructor {
|
||||||
{
|
{
|
||||||
@@ -119,12 +119,6 @@ namespace methods_core {
|
|||||||
const t: Terminal = new Terminal();
|
const t: Terminal = new Terminal();
|
||||||
t.attachCustomKeyEventHandler((e: KeyboardEvent) => true);
|
t.attachCustomKeyEventHandler((e: KeyboardEvent) => true);
|
||||||
t.attachCustomKeyEventHandler((e: KeyboardEvent) => false);
|
t.attachCustomKeyEventHandler((e: KeyboardEvent) => false);
|
||||||
const d1: IDisposable = t.addCsiHandler("x",
|
|
||||||
(params: number[], collect: string): boolean => params[0]===1);
|
|
||||||
d1.dispose();
|
|
||||||
const d2: IDisposable = t.addOscHandler(199,
|
|
||||||
(data: string): boolean => true);
|
|
||||||
d2.dispose();
|
|
||||||
}
|
}
|
||||||
namespace options {
|
namespace options {
|
||||||
{
|
{
|
||||||
|
|||||||
+7
-7
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "xterm",
|
"name": "xterm",
|
||||||
"description": "Full xterm terminal, in your browser",
|
"description": "Full xterm terminal, in your browser",
|
||||||
"version": "3.10.0",
|
"version": "3.8.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",
|
||||||
@@ -28,17 +28,18 @@
|
|||||||
"gulp-sourcemaps": "1.9.1",
|
"gulp-sourcemaps": "1.9.1",
|
||||||
"gulp-typescript": "^3.1.3",
|
"gulp-typescript": "^3.1.3",
|
||||||
"gulp-util": "3.0.8",
|
"gulp-util": "3.0.8",
|
||||||
|
"jsdoc": "3.4.3",
|
||||||
"jsdom": "^11.11.0",
|
"jsdom": "^11.11.0",
|
||||||
"merge-stream": "^1.0.1",
|
"merge-stream": "^1.0.1",
|
||||||
"node-pty": "0.7.6",
|
"node-pty": "0.7.6",
|
||||||
"nodemon": "1.10.2",
|
"nodemon": "1.10.2",
|
||||||
"nyc": "^11.8.0",
|
"nyc": "^11.8.0",
|
||||||
"sorcery": "^0.10.0",
|
"sorcery": "^0.10.0",
|
||||||
"source-map-loader": "^0.2.4",
|
"source-map-loader": "^0.2.3",
|
||||||
"ts-loader": "^4.5.0",
|
"ts-loader": "^4.5.0",
|
||||||
"tslint": "^5.9.1",
|
"tslint": "^5.9.1",
|
||||||
"tslint-consistent-codestyle": "^1.13.0",
|
"tslint-consistent-codestyle": "^1.13.0",
|
||||||
"typescript": "3.1",
|
"typescript": "3.0",
|
||||||
"vinyl-buffer": "^1.0.0",
|
"vinyl-buffer": "^1.0.0",
|
||||||
"vinyl-source-stream": "^1.1.0",
|
"vinyl-source-stream": "^1.1.0",
|
||||||
"webpack": "^4.17.1",
|
"webpack": "^4.17.1",
|
||||||
@@ -47,23 +48,22 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node demo/start",
|
"start": "node demo/start",
|
||||||
"start-debug": "node --inspect-brk demo/start",
|
|
||||||
"start-zmodem": "node demo/zmodem/app",
|
"start-zmodem": "node demo/zmodem/app",
|
||||||
"lint": "tslint 'src/**/*.ts' './demo/**/*.ts'",
|
"lint": "tslint 'src/**/*.ts' './demo/**/*.ts'",
|
||||||
"pretest": "npm run layering",
|
|
||||||
"test": "npm run mocha",
|
"test": "npm run mocha",
|
||||||
"posttest": "npm run lint",
|
"posttest": "npm run lint",
|
||||||
"test-debug": "node --inspect-brk node_modules/.bin/gulp test",
|
"test-debug": "node --inspect-brk node_modules/.bin/gulp test",
|
||||||
"test-suite": "gulp mocha-suite --test",
|
"test-suite": "gulp mocha-suite --test",
|
||||||
"test-coverage": "nyc -x gulpfile.js -x '**/*test*' npm run mocha",
|
"test-coverage": "nyc -x gulpfile.js -x '**/*test*' npm run mocha",
|
||||||
"mocha": "gulp test",
|
"mocha": "gulp test",
|
||||||
|
"build:docs": "jsdoc -c jsdoc.json",
|
||||||
"tsc": "tsc",
|
"tsc": "tsc",
|
||||||
"prebuild": "concurrently --kill-others-on-fail --names \"lib,attach,fit,fullscreen,search,terminado,webLinks,winptyCompat,zmodem,css\" \"tsc\" \"tsc -p ./src/addons/attach\" \"tsc -p ./src/addons/fit\" \"tsc -p ./src/addons/fullscreen\" \"tsc -p ./src/addons/search\" \"tsc -p ./src/addons/terminado\" \"tsc -p ./src/addons/webLinks\" \"tsc -p ./src/addons/winptyCompat\" \"tsc -p ./src/addons/zmodem\" \"gulp css\"",
|
"prebuild": "concurrently --kill-others-on-fail --names \"lib,attach,fit,fullscreen,search,terminado,webLinks,winptyCompat,zmodem,css\" \"tsc\" \"tsc -p ./src/addons/attach\" \"tsc -p ./src/addons/fit\" \"tsc -p ./src/addons/fullscreen\" \"tsc -p ./src/addons/search\" \"tsc -p ./src/addons/terminado\" \"tsc -p ./src/addons/webLinks\" \"tsc -p ./src/addons/winptyCompat\" \"tsc -p ./src/addons/zmodem\" \"gulp css\"",
|
||||||
"build": "gulp build",
|
"build": "gulp build",
|
||||||
"prepublish": "npm run build",
|
"prepublish": "npm run build",
|
||||||
"coveralls": "nyc report --reporter=text-lcov | coveralls",
|
"coveralls": "nyc report --reporter=text-lcov | coveralls",
|
||||||
|
"webpack": "gulp webpack",
|
||||||
"watch": "concurrently --kill-others-on-fail --names \"lib,css\" \"tsc -w\" \"gulp watch-css\"",
|
"watch": "concurrently --kill-others-on-fail --names \"lib,css\" \"tsc -w\" \"gulp watch-css\"",
|
||||||
"watch-addons": "concurrently --kill-others-on-fail --names \"attach,fit,fullscreen,search,terminado,webLinks,winptyCompat,zmodem\" \"tsc -w -p ./src/addons/attach\" \"tsc -w -p ./src/addons/fit\" \"tsc -w -p ./src/addons/fullscreen\" \"tsc -w -p ./src/addons/search\" \"tsc -w -p ./src/addons/terminado\" \"tsc -w -p ./src/addons/webLinks\" \"tsc -w -p ./src/addons/winptyCompat\" \"tsc -w -p ./src/addons/zmodem\"",
|
"watch-addons": "concurrently --kill-others-on-fail --names \"attach,fit,fullscreen,search,terminado,webLinks,winptyCompat,zmodem\" \"tsc -w -p ./src/addons/attach\" \"tsc -w -p ./src/addons/fit\" \"tsc -w -p ./src/addons/fullscreen\" \"tsc -w -p ./src/addons/search\" \"tsc -w -p ./src/addons/terminado\" \"tsc -w -p ./src/addons/webLinks\" \"tsc -w -p ./src/addons/winptyCompat\" \"tsc -w -p ./src/addons/zmodem\""
|
||||||
"layering": "concurrently --kill-others-on-fail --names \"common,core\" \"tsc -p ./src/common\" \"tsc -p ./src/core\""
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
import * as Strings from './Strings';
|
import * as Strings from './Strings';
|
||||||
import { ITerminal, IBuffer } from './Types';
|
import { ITerminal, IBuffer } from './Types';
|
||||||
import { isMac } from './core/Platform';
|
import { isMac } from './shared/utils/Browser';
|
||||||
import { RenderDebouncer } from './ui/RenderDebouncer';
|
import { RenderDebouncer } from './ui/RenderDebouncer';
|
||||||
import { addDisposableDomListener } from './ui/Lifecycle';
|
import { addDisposableDomListener } from './ui/Lifecycle';
|
||||||
import { Disposable } from './common/Lifecycle';
|
import { Disposable } from './common/Lifecycle';
|
||||||
|
|||||||
+35
-67
@@ -7,7 +7,7 @@ import { assert, expect } from 'chai';
|
|||||||
import { ITerminal } from './Types';
|
import { ITerminal } from './Types';
|
||||||
import { Buffer, DEFAULT_ATTR, CHAR_DATA_CHAR_INDEX } from './Buffer';
|
import { Buffer, DEFAULT_ATTR, CHAR_DATA_CHAR_INDEX } from './Buffer';
|
||||||
import { CircularList } from './common/CircularList';
|
import { CircularList } from './common/CircularList';
|
||||||
import { MockTerminal, TestTerminal } from './ui/TestUtils.test';
|
import { MockTerminal, TestTerminal } from './utils/TestUtils.test';
|
||||||
import { BufferLine } from './BufferLine';
|
import { BufferLine } from './BufferLine';
|
||||||
|
|
||||||
const INIT_COLS = 80;
|
const INIT_COLS = 80;
|
||||||
@@ -37,7 +37,7 @@ describe('Buffer', () => {
|
|||||||
|
|
||||||
describe('fillViewportRows', () => {
|
describe('fillViewportRows', () => {
|
||||||
it('should fill the buffer with blank lines based on the size of the viewport', () => {
|
it('should fill the buffer with blank lines based on the size of the viewport', () => {
|
||||||
const blankLineChar = buffer.getBlankLine(DEFAULT_ATTR).get(0);
|
const blankLineChar = BufferLine.blankLine(terminal.cols, DEFAULT_ATTR).get(0);
|
||||||
buffer.fillViewportRows();
|
buffer.fillViewportRows();
|
||||||
assert.equal(buffer.lines.length, INIT_ROWS);
|
assert.equal(buffer.lines.length, INIT_ROWS);
|
||||||
for (let y = 0; y < INIT_ROWS; y++) {
|
for (let y = 0; y < INIT_ROWS; y++) {
|
||||||
@@ -155,12 +155,8 @@ describe('Buffer', () => {
|
|||||||
assert.equal(buffer.lines.maxLength, INIT_ROWS);
|
assert.equal(buffer.lines.maxLength, INIT_ROWS);
|
||||||
buffer.y = INIT_ROWS - 1;
|
buffer.y = INIT_ROWS - 1;
|
||||||
buffer.fillViewportRows();
|
buffer.fillViewportRows();
|
||||||
let chData = buffer.lines.get(5).get(0);
|
buffer.lines.get(5).get(0)[1] = 'a';
|
||||||
chData[1] = 'a';
|
buffer.lines.get(INIT_ROWS - 1).get(0)[1] = 'b';
|
||||||
buffer.lines.get(5).set(0, chData);
|
|
||||||
chData = buffer.lines.get(INIT_ROWS - 1).get(0);
|
|
||||||
chData[1] = 'b';
|
|
||||||
buffer.lines.get(INIT_ROWS - 1).set(0, chData);
|
|
||||||
buffer.resize(INIT_COLS, INIT_ROWS - 5);
|
buffer.resize(INIT_COLS, INIT_ROWS - 5);
|
||||||
assert.equal(buffer.lines.get(0).get(0)[1], 'a');
|
assert.equal(buffer.lines.get(0).get(0)[1], 'a');
|
||||||
assert.equal(buffer.lines.get(INIT_ROWS - 1 - 5).get(0)[1], 'b');
|
assert.equal(buffer.lines.get(INIT_ROWS - 1 - 5).get(0)[1], 'b');
|
||||||
@@ -184,7 +180,7 @@ describe('Buffer', () => {
|
|||||||
buffer.fillViewportRows();
|
buffer.fillViewportRows();
|
||||||
// Create 10 extra blank lines
|
// Create 10 extra blank lines
|
||||||
for (let i = 0; i < 10; i++) {
|
for (let i = 0; i < 10; i++) {
|
||||||
buffer.lines.push(buffer.getBlankLine(DEFAULT_ATTR));
|
buffer.lines.push(BufferLine.blankLine(terminal.cols, DEFAULT_ATTR));
|
||||||
}
|
}
|
||||||
// Set cursor to the bottom of the buffer
|
// Set cursor to the bottom of the buffer
|
||||||
buffer.y = INIT_ROWS - 1;
|
buffer.y = INIT_ROWS - 1;
|
||||||
@@ -204,7 +200,7 @@ describe('Buffer', () => {
|
|||||||
buffer.fillViewportRows();
|
buffer.fillViewportRows();
|
||||||
// Create 10 extra blank lines
|
// Create 10 extra blank lines
|
||||||
for (let i = 0; i < 10; i++) {
|
for (let i = 0; i < 10; i++) {
|
||||||
buffer.lines.push(buffer.getBlankLine(DEFAULT_ATTR));
|
buffer.lines.push(BufferLine.blankLine(terminal.cols, DEFAULT_ATTR));
|
||||||
}
|
}
|
||||||
// Set cursor to the bottom of the buffer
|
// Set cursor to the bottom of the buffer
|
||||||
buffer.y = INIT_ROWS - 1;
|
buffer.y = INIT_ROWS - 1;
|
||||||
@@ -277,11 +273,11 @@ describe('Buffer', () => {
|
|||||||
|
|
||||||
describe ('translateBufferLineToString', () => {
|
describe ('translateBufferLineToString', () => {
|
||||||
it('should handle selecting a section of ascii text', () => {
|
it('should handle selecting a section of ascii text', () => {
|
||||||
const line = new BufferLine(4);
|
const line = new BufferLine();
|
||||||
line.set(0, [ null, 'a', 1, 'a'.charCodeAt(0)]);
|
line.push([ null, 'a', 1, 'a'.charCodeAt(0)]);
|
||||||
line.set(1, [ null, 'b', 1, 'b'.charCodeAt(0)]);
|
line.push([ null, 'b', 1, 'b'.charCodeAt(0)]);
|
||||||
line.set(2, [ null, 'c', 1, 'c'.charCodeAt(0)]);
|
line.push([ null, 'c', 1, 'c'.charCodeAt(0)]);
|
||||||
line.set(3, [ null, 'd', 1, 'd'.charCodeAt(0)]);
|
line.push([ null, 'd', 1, 'd'.charCodeAt(0)]);
|
||||||
buffer.lines.set(0, line);
|
buffer.lines.set(0, line);
|
||||||
|
|
||||||
const str = buffer.translateBufferLineToString(0, true, 0, 2);
|
const str = buffer.translateBufferLineToString(0, true, 0, 2);
|
||||||
@@ -289,10 +285,10 @@ describe('Buffer', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should handle a cut-off double width character by including it', () => {
|
it('should handle a cut-off double width character by including it', () => {
|
||||||
const line = new BufferLine(3);
|
const line = new BufferLine();
|
||||||
line.set(0, [ null, '語', 2, 35486 ]);
|
line.push([ null, '語', 2, 35486 ]);
|
||||||
line.set(1, [ null, '', 0, null]);
|
line.push([ null, '', 0, null]);
|
||||||
line.set(2, [ null, 'a', 1, 'a'.charCodeAt(0)]);
|
line.push([ null, 'a', 1, 'a'.charCodeAt(0)]);
|
||||||
buffer.lines.set(0, line);
|
buffer.lines.set(0, line);
|
||||||
|
|
||||||
const str1 = buffer.translateBufferLineToString(0, true, 0, 1);
|
const str1 = buffer.translateBufferLineToString(0, true, 0, 1);
|
||||||
@@ -300,10 +296,10 @@ describe('Buffer', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should handle a zero width character in the middle of the string by not including it', () => {
|
it('should handle a zero width character in the middle of the string by not including it', () => {
|
||||||
const line = new BufferLine(3);
|
const line = new BufferLine();
|
||||||
line.set(0, [ null, '語', 2, '語'.charCodeAt(0) ]);
|
line.push([ null, '語', 2, '語'.charCodeAt(0) ]);
|
||||||
line.set(1, [ null, '', 0, null]);
|
line.push([ null, '', 0, null]);
|
||||||
line.set(2, [ null, 'a', 1, 'a'.charCodeAt(0)]);
|
line.push([ null, 'a', 1, 'a'.charCodeAt(0)]);
|
||||||
buffer.lines.set(0, line);
|
buffer.lines.set(0, line);
|
||||||
|
|
||||||
const str0 = buffer.translateBufferLineToString(0, true, 0, 1);
|
const str0 = buffer.translateBufferLineToString(0, true, 0, 1);
|
||||||
@@ -317,9 +313,9 @@ describe('Buffer', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should handle single width emojis', () => {
|
it('should handle single width emojis', () => {
|
||||||
const line = new BufferLine(2);
|
const line = new BufferLine();
|
||||||
line.set(0, [ null, '😁', 1, '😁'.charCodeAt(0) ]);
|
line.push([ null, '😁', 1, '😁'.charCodeAt(0) ]);
|
||||||
line.set(1, [ null, 'a', 1, 'a'.charCodeAt(0)]);
|
line.push([ null, 'a', 1, 'a'.charCodeAt(0)]);
|
||||||
buffer.lines.set(0, line);
|
buffer.lines.set(0, line);
|
||||||
|
|
||||||
const str1 = buffer.translateBufferLineToString(0, true, 0, 1);
|
const str1 = buffer.translateBufferLineToString(0, true, 0, 1);
|
||||||
@@ -330,9 +326,9 @@ describe('Buffer', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should handle double width emojis', () => {
|
it('should handle double width emojis', () => {
|
||||||
const line = new BufferLine(2);
|
const line = new BufferLine();
|
||||||
line.set(0, [ null, '😁', 2, '😁'.charCodeAt(0) ]);
|
line.push([ null, '😁', 2, '😁'.charCodeAt(0) ]);
|
||||||
line.set(1, [ null, '', 0, null]);
|
line.push([ null, '', 0, null]);
|
||||||
buffer.lines.set(0, line);
|
buffer.lines.set(0, line);
|
||||||
|
|
||||||
const str1 = buffer.translateBufferLineToString(0, true, 0, 1);
|
const str1 = buffer.translateBufferLineToString(0, true, 0, 1);
|
||||||
@@ -341,10 +337,10 @@ describe('Buffer', () => {
|
|||||||
const str2 = buffer.translateBufferLineToString(0, true, 0, 2);
|
const str2 = buffer.translateBufferLineToString(0, true, 0, 2);
|
||||||
assert.equal(str2, '😁');
|
assert.equal(str2, '😁');
|
||||||
|
|
||||||
const line2 = new BufferLine(3);
|
const line2 = new BufferLine();
|
||||||
line2.set(0, [ null, '😁', 2, '😁'.charCodeAt(0) ]);
|
line2.push([ null, '😁', 2, '😁'.charCodeAt(0) ]);
|
||||||
line2.set(1, [ null, '', 0, null]);
|
line2.push([ null, '', 0, null]);
|
||||||
line2.set(2, [ null, 'a', 1, 'a'.charCodeAt(0)]);
|
line2.push([ null, 'a', 1, 'a'.charCodeAt(0)]);
|
||||||
buffer.lines.set(0, line2);
|
buffer.lines.set(0, line2);
|
||||||
|
|
||||||
const str3 = buffer.translateBufferLineToString(0, true, 0, 3);
|
const str3 = buffer.translateBufferLineToString(0, true, 0, 3);
|
||||||
@@ -355,7 +351,7 @@ describe('Buffer', () => {
|
|||||||
let terminal: TestTerminal;
|
let terminal: TestTerminal;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
terminal = new TestTerminal({rows: 5, cols: 10, scrollback: 5});
|
terminal = new TestTerminal({rows: 5, cols: 10});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('multiline ascii', () => {
|
it('multiline ascii', () => {
|
||||||
@@ -508,46 +504,18 @@ describe('Buffer', () => {
|
|||||||
// the dangling last cell is wrongly added in the string
|
// the dangling last cell is wrongly added in the string
|
||||||
// --> fixable after resolving #1685
|
// --> fixable after resolving #1685
|
||||||
terminal.writeSync(input);
|
terminal.writeSync(input);
|
||||||
const s = terminal.buffer.iterator(true).next().content;
|
// TODO: reenable after fix
|
||||||
assert.equal(input, s);
|
// const s = terminal.buffer.contents(true).toArray()[0];
|
||||||
|
// assert.equal(input, s);
|
||||||
for (let i = 10; i < input.length; ++i) {
|
for (let i = 10; i < input.length; ++i) {
|
||||||
const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i);
|
const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i + 1); // TODO: remove +1 after fix
|
||||||
const j = (i - 0) << 1;
|
const j = (i - 0) << 1;
|
||||||
assert.deepEqual([(j / terminal.cols) | 0, j % terminal.cols], bufferIndex);
|
assert.deepEqual([(j / terminal.cols) | 0, j % terminal.cols], bufferIndex);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('test fully wrapped buffer up to last char', () => {
|
|
||||||
const input = Array(6).join('1234567890');
|
|
||||||
terminal.writeSync(input);
|
|
||||||
const s = terminal.buffer.iterator(true).next().content;
|
|
||||||
assert.equal(input, s);
|
|
||||||
for (let i = 0; i < input.length; ++i) {
|
|
||||||
const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i);
|
|
||||||
assert.equal(input[i], terminal.buffer.lines.get(bufferIndex[0]).get(bufferIndex[1])[CHAR_DATA_CHAR_INDEX]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it('test fully wrapped buffer up to last char with full width odd', () => {
|
|
||||||
const input = 'a¥\u0301a¥\u0301a¥\u0301a¥\u0301a¥\u0301a¥\u0301a¥\u0301a¥\u0301'
|
|
||||||
+ 'a¥\u0301a¥\u0301a¥\u0301a¥\u0301a¥\u0301a¥\u0301a¥\u0301';
|
|
||||||
terminal.writeSync(input);
|
|
||||||
const s = terminal.buffer.iterator(true).next().content;
|
|
||||||
assert.equal(input, s);
|
|
||||||
for (let i = 0; i < input.length; ++i) {
|
|
||||||
const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i);
|
|
||||||
assert.equal(
|
|
||||||
(!(i % 3))
|
|
||||||
? input[i]
|
|
||||||
: (i % 3 === 1)
|
|
||||||
? input.substr(i, 2)
|
|
||||||
: input.substr(i - 1, 2),
|
|
||||||
terminal.buffer.lines.get(bufferIndex[0]).get(bufferIndex[1])[CHAR_DATA_CHAR_INDEX]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
describe('BufferStringIterator', function(): void {
|
describe('BufferStringIterator', function(): void {
|
||||||
it('iterator does not overflow buffer limits', function(): void {
|
it('iterator does not ovrflow buffer limits', function(): void {
|
||||||
const terminal = new TestTerminal({rows: 5, cols: 10, scrollback: 5});
|
const terminal = new TestTerminal({rows: 5, cols: 10, scrollback: 5});
|
||||||
const data = [
|
const data = [
|
||||||
'aaaaaaaaaa',
|
'aaaaaaaaaa',
|
||||||
|
|||||||
+69
-56
@@ -4,26 +4,21 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { CircularList } from './common/CircularList';
|
import { CircularList } from './common/CircularList';
|
||||||
import { CharData, ITerminal, IBuffer, IBufferLine, BufferIndex, IBufferStringIterator, IBufferStringIteratorResult, IBufferLineConstructor } from './Types';
|
import { CharData, ITerminal, IBuffer, IBufferLine, BufferIndex, IBufferStringIterator, IBufferStringIteratorResult } from './Types';
|
||||||
import { EventEmitter } from './common/EventEmitter';
|
import { EventEmitter } from './common/EventEmitter';
|
||||||
import { IMarker } from 'xterm';
|
import { IMarker } from 'xterm';
|
||||||
import { BufferLine, BufferLineJSArray } from './BufferLine';
|
import { BufferLine } from './BufferLine';
|
||||||
import { DEFAULT_COLOR } from './renderer/atlas/Types';
|
|
||||||
|
|
||||||
export const DEFAULT_ATTR = (0 << 18) | (DEFAULT_COLOR << 9) | (256 << 0);
|
export const DEFAULT_ATTR = (0 << 18) | (257 << 9) | (256 << 0);
|
||||||
export const CHAR_DATA_ATTR_INDEX = 0;
|
export const CHAR_DATA_ATTR_INDEX = 0;
|
||||||
export const CHAR_DATA_CHAR_INDEX = 1;
|
export const CHAR_DATA_CHAR_INDEX = 1;
|
||||||
export const CHAR_DATA_WIDTH_INDEX = 2;
|
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_CHAR = ' ';
|
||||||
export const NULL_CELL_WIDTH = 1;
|
export const NULL_CELL_WIDTH = 1;
|
||||||
export const NULL_CELL_CODE = 0;
|
export const NULL_CELL_CODE = 32;
|
||||||
|
|
||||||
export const WHITESPACE_CELL_CHAR = ' ';
|
|
||||||
export const WHITESPACE_CELL_WIDTH = 1;
|
|
||||||
export const WHITESPACE_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
|
||||||
@@ -43,9 +38,7 @@ export class Buffer implements IBuffer {
|
|||||||
public tabs: any;
|
public tabs: any;
|
||||||
public savedY: number;
|
public savedY: number;
|
||||||
public savedX: number;
|
public savedX: number;
|
||||||
public savedCurAttr: number;
|
|
||||||
public markers: Marker[] = [];
|
public markers: Marker[] = [];
|
||||||
private _bufferLineConstructor: IBufferLineConstructor;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new Buffer.
|
* Create a new Buffer.
|
||||||
@@ -60,37 +53,6 @@ export class Buffer implements IBuffer {
|
|||||||
this.clear();
|
this.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public setBufferLineFactory(type: string): void {
|
|
||||||
if (type === 'JsArray') {
|
|
||||||
if (this._bufferLineConstructor !== BufferLineJSArray) {
|
|
||||||
this._bufferLineConstructor = BufferLineJSArray;
|
|
||||||
this._recreateLines();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (this._bufferLineConstructor !== BufferLine) {
|
|
||||||
this._bufferLineConstructor = BufferLine;
|
|
||||||
this._recreateLines();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _recreateLines(): void {
|
|
||||||
if (!this.lines) return;
|
|
||||||
for (let i = 0; i < this.lines.length; ++i) {
|
|
||||||
const oldLine = this.lines.get(i);
|
|
||||||
const newLine = new this._bufferLineConstructor(oldLine.length);
|
|
||||||
for (let j = 0; j < oldLine.length; ++j) {
|
|
||||||
newLine.set(j, oldLine.get(j));
|
|
||||||
}
|
|
||||||
this.lines.set(i, newLine);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public getBlankLine(attr: number, isWrapped?: boolean): IBufferLine {
|
|
||||||
const fillCharData: CharData = [attr, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE];
|
|
||||||
return new this._bufferLineConstructor(this._terminal.cols, fillCharData, isWrapped);
|
|
||||||
}
|
|
||||||
|
|
||||||
public get hasScrollback(): boolean {
|
public get hasScrollback(): boolean {
|
||||||
return this._hasScrollback && this.lines.maxLength > this._terminal.rows;
|
return this._hasScrollback && this.lines.maxLength > this._terminal.rows;
|
||||||
}
|
}
|
||||||
@@ -119,14 +81,11 @@ export class Buffer implements IBuffer {
|
|||||||
/**
|
/**
|
||||||
* Fills the buffer's viewport with blank lines.
|
* Fills the buffer's viewport with blank lines.
|
||||||
*/
|
*/
|
||||||
public fillViewportRows(fillAttr?: number): void {
|
public fillViewportRows(): void {
|
||||||
if (this.lines.length === 0) {
|
if (this.lines.length === 0) {
|
||||||
if (fillAttr === undefined) {
|
|
||||||
fillAttr = DEFAULT_ATTR;
|
|
||||||
}
|
|
||||||
let i = this._terminal.rows;
|
let i = this._terminal.rows;
|
||||||
while (i--) {
|
while (i--) {
|
||||||
this.lines.push(this.getBlankLine(fillAttr));
|
this.lines.push(BufferLine.blankLine(this._terminal.cols, DEFAULT_ATTR));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -135,7 +94,6 @@ export class Buffer implements IBuffer {
|
|||||||
* Clears the buffer to it's initial state, discarding all previous data.
|
* Clears the buffer to it's initial state, discarding all previous data.
|
||||||
*/
|
*/
|
||||||
public clear(): void {
|
public clear(): void {
|
||||||
this.setBufferLineFactory(this._terminal.options.experimentalBufferLineImpl);
|
|
||||||
this.ydisp = 0;
|
this.ydisp = 0;
|
||||||
this.ybase = 0;
|
this.ybase = 0;
|
||||||
this.y = 0;
|
this.y = 0;
|
||||||
@@ -166,7 +124,9 @@ export class Buffer implements IBuffer {
|
|||||||
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, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]; // does xterm use the default attr?
|
||||||
for (let i = 0; i < this.lines.length; i++) {
|
for (let i = 0; i < this.lines.length; i++) {
|
||||||
this.lines.get(i).resize(newCols, ch);
|
while (this.lines.get(i).length < newCols) {
|
||||||
|
this.lines.get(i).push(ch);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,8 +147,7 @@ export class Buffer implements IBuffer {
|
|||||||
} else {
|
} else {
|
||||||
// Add a blank line if there is no buffer left at the top to scroll to, or if there
|
// Add a blank line if there is no buffer left at the top to scroll to, or if there
|
||||||
// are blank lines after the cursor
|
// are blank lines after the cursor
|
||||||
const fillCharData: CharData = [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE];
|
this.lines.push(BufferLine.blankLine(newCols, DEFAULT_ATTR));
|
||||||
this.lines.push(new this._bufferLineConstructor(newCols, fillCharData));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -252,7 +211,7 @@ export class Buffer implements IBuffer {
|
|||||||
while (stringIndex) {
|
while (stringIndex) {
|
||||||
const line = this.lines.get(lineIndex);
|
const line = this.lines.get(lineIndex);
|
||||||
if (!line) {
|
if (!line) {
|
||||||
return [-1, -1];
|
[-1, -1];
|
||||||
}
|
}
|
||||||
for (let i = 0; i < line.length; ++i) {
|
for (let i = 0; i < line.length; ++i) {
|
||||||
stringIndex -= line.get(i)[CHAR_DATA_CHAR_INDEX].length;
|
stringIndex -= line.get(i)[CHAR_DATA_CHAR_INDEX].length;
|
||||||
@@ -275,12 +234,65 @@ export class Buffer implements IBuffer {
|
|||||||
* @param startCol The column to start at.
|
* @param startCol The column to start at.
|
||||||
* @param endCol The column to end at.
|
* @param endCol The column to end at.
|
||||||
*/
|
*/
|
||||||
public translateBufferLineToString(lineIndex: number, trimRight: boolean, startCol: number = 0, endCol?: number): string {
|
public translateBufferLineToString(lineIndex: number, trimRight: boolean, startCol: number = 0, endCol: number = null): string {
|
||||||
|
// Get full line
|
||||||
|
let lineString = '';
|
||||||
const line = this.lines.get(lineIndex);
|
const line = this.lines.get(lineIndex);
|
||||||
if (!line) {
|
if (!line) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
return line.translateToString(trimRight, startCol, endCol);
|
|
||||||
|
// Initialize column and index values. Column values represent the actual
|
||||||
|
// cell column, indexes represent the index in the string. Indexes are
|
||||||
|
// needed here because some chars are 0 characters long (eg. after wide
|
||||||
|
// chars) and some chars are longer than 1 characters long (eg. emojis).
|
||||||
|
let startIndex = startCol;
|
||||||
|
// Only set endCol to the line length when it is null. 0 is a valid column.
|
||||||
|
if (endCol === null) {
|
||||||
|
endCol = line.length;
|
||||||
|
}
|
||||||
|
let endIndex = endCol;
|
||||||
|
|
||||||
|
for (let i = 0; i < line.length; i++) {
|
||||||
|
const char = line.get(i);
|
||||||
|
lineString += char[CHAR_DATA_CHAR_INDEX];
|
||||||
|
// Adjust start and end cols for wide characters if they affect their
|
||||||
|
// column indexes
|
||||||
|
if (char[CHAR_DATA_WIDTH_INDEX] === 0) {
|
||||||
|
if (startCol >= i) {
|
||||||
|
startIndex--;
|
||||||
|
}
|
||||||
|
if (endCol > i) {
|
||||||
|
endIndex--;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Adjust the columns to take glyphs that are represented by multiple
|
||||||
|
// code points into account.
|
||||||
|
if (char[CHAR_DATA_CHAR_INDEX].length > 1) {
|
||||||
|
if (startCol > i) {
|
||||||
|
startIndex += char[CHAR_DATA_CHAR_INDEX].length - 1;
|
||||||
|
}
|
||||||
|
if (endCol > i) {
|
||||||
|
endIndex += char[CHAR_DATA_CHAR_INDEX].length - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the final end col by trimming whitespace on the right of the
|
||||||
|
// line if needed.
|
||||||
|
if (trimRight) {
|
||||||
|
const rightWhitespaceIndex = lineString.search(/\s+$/);
|
||||||
|
if (rightWhitespaceIndex !== -1) {
|
||||||
|
endIndex = Math.min(endIndex, rightWhitespaceIndex);
|
||||||
|
}
|
||||||
|
// Return the empty string if only trimmed whitespace is selected
|
||||||
|
if (endIndex <= startIndex) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return lineString.substring(startIndex, endIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getWrappedRangeForLine(y: number): { first: number, last: number } {
|
public getWrappedRangeForLine(y: number): { first: number, last: number } {
|
||||||
@@ -439,7 +451,8 @@ export class BufferStringIterator implements IBufferStringIterator {
|
|||||||
range.last = Math.min(range.last, this._buffer.lines.length);
|
range.last = Math.min(range.last, this._buffer.lines.length);
|
||||||
let result = '';
|
let result = '';
|
||||||
for (let i = range.first; i <= range.last; ++i) {
|
for (let i = range.first; i <= range.last; ++i) {
|
||||||
result += this._buffer.translateBufferLineToString(i, this._trimRight);
|
// TODO: always apply trimRight after fixing #1685
|
||||||
|
result += this._buffer.translateBufferLineToString(i, (this._trimRight) ? i === range.last : false);
|
||||||
}
|
}
|
||||||
this._current = range.last + 1;
|
this._current = range.last + 1;
|
||||||
return {range: range, content: result};
|
return {range: range, content: result};
|
||||||
|
|||||||
+63
-300
@@ -5,330 +5,93 @@
|
|||||||
import * as chai from 'chai';
|
import * as chai from 'chai';
|
||||||
import { BufferLine } from './BufferLine';
|
import { BufferLine } from './BufferLine';
|
||||||
import { CharData, IBufferLine } from './Types';
|
import { CharData, IBufferLine } from './Types';
|
||||||
import { NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, DEFAULT_ATTR } from './Buffer';
|
import { NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, CHAR_DATA_ATTR_INDEX, CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CODE_INDEX } from './Buffer';
|
||||||
|
|
||||||
|
|
||||||
class TestBufferLine extends BufferLine {
|
class TestBufferLine extends BufferLine {
|
||||||
public toArray(): CharData[] {
|
public toArray(): CharData[] {
|
||||||
const result = [];
|
return this._data;
|
||||||
for (let i = 0; i < this.length; ++i) {
|
|
||||||
result.push(this.get(i));
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('BufferLine', function(): void {
|
describe('BufferLine', function(): void {
|
||||||
it('ctor', function(): void {
|
it('ctor', function(): void {
|
||||||
let line: IBufferLine = new TestBufferLine(0);
|
let line: IBufferLine = new TestBufferLine();
|
||||||
chai.expect(line.length).equals(0);
|
chai.expect(line.length).equals(0);
|
||||||
|
chai.expect(line.pop()).equals(undefined);
|
||||||
chai.expect(line.isWrapped).equals(false);
|
chai.expect(line.isWrapped).equals(false);
|
||||||
line = new TestBufferLine(10);
|
line = new TestBufferLine(10);
|
||||||
chai.expect(line.length).equals(10);
|
chai.expect(line.length).equals(10);
|
||||||
chai.expect(line.get(0)).eql([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]);
|
chai.expect(line.pop()).eql([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]);
|
||||||
chai.expect(line.isWrapped).equals(false);
|
chai.expect(line.isWrapped).equals(false);
|
||||||
line = new TestBufferLine(10, null, true);
|
line = new TestBufferLine(10, null, true);
|
||||||
chai.expect(line.length).equals(10);
|
chai.expect(line.length).equals(10);
|
||||||
chai.expect(line.get(0)).eql([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]);
|
chai.expect(line.pop()).eql([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]);
|
||||||
chai.expect(line.isWrapped).equals(true);
|
chai.expect(line.isWrapped).equals(true);
|
||||||
line = new TestBufferLine(10, [123, 'a', 456, 'a'.charCodeAt(0)], true);
|
line = new TestBufferLine(10, [123, 'a', 456, 789], true);
|
||||||
chai.expect(line.length).equals(10);
|
chai.expect(line.length).equals(10);
|
||||||
chai.expect(line.get(0)).eql([123, 'a', 456, 'a'.charCodeAt(0)]);
|
chai.expect(line.pop()).eql([123, 'a', 456, 789]);
|
||||||
chai.expect(line.isWrapped).equals(true);
|
chai.expect(line.isWrapped).equals(true);
|
||||||
});
|
});
|
||||||
|
it('splice', function(): void {
|
||||||
|
const line = new TestBufferLine();
|
||||||
|
const data: CharData[] = [
|
||||||
|
[1, 'a', 0, 0],
|
||||||
|
[2, 'b', 0, 0],
|
||||||
|
[3, 'c', 0, 0]
|
||||||
|
];
|
||||||
|
for (let i = 0; i < data.length; ++i) line.push(data[i]);
|
||||||
|
chai.expect(line.length).equals(data.length);
|
||||||
|
const removed1 = line.splice(1, 1, [4, 'd', 0, 0]);
|
||||||
|
const removed2 = data.splice(1, 1, [4, 'd', 0, 0]);
|
||||||
|
chai.expect(removed1).eql(removed2);
|
||||||
|
chai.expect(line.toArray()).eql(data);
|
||||||
|
});
|
||||||
|
it('TerminalLine.blankLine', function(): void {
|
||||||
|
const line = TestBufferLine.blankLine(5, 123);
|
||||||
|
chai.expect(line.length).equals(5);
|
||||||
|
chai.expect(line.isWrapped).equals(false);
|
||||||
|
const ch = line.get(0);
|
||||||
|
chai.expect(ch[CHAR_DATA_ATTR_INDEX]).equals(123);
|
||||||
|
chai.expect(ch[CHAR_DATA_CHAR_INDEX]).equals(NULL_CELL_CHAR);
|
||||||
|
chai.expect(ch[CHAR_DATA_WIDTH_INDEX]).equals(NULL_CELL_WIDTH);
|
||||||
|
chai.expect(ch[CHAR_DATA_CODE_INDEX]).equals(NULL_CELL_CODE);
|
||||||
|
});
|
||||||
it('insertCells', function(): void {
|
it('insertCells', function(): void {
|
||||||
const line = new TestBufferLine(3);
|
const line = new TestBufferLine();
|
||||||
line.set(0, [1, 'a', 0, 'a'.charCodeAt(0)]);
|
const data: CharData[] = [
|
||||||
line.set(1, [2, 'b', 0, 'b'.charCodeAt(0)]);
|
[1, 'a', 0, 0],
|
||||||
line.set(2, [3, 'c', 0, 'c'.charCodeAt(0)]);
|
[2, 'b', 0, 0],
|
||||||
line.insertCells(1, 3, [4, 'd', 0, 'd'.charCodeAt(0)]);
|
[3, 'c', 0, 0]
|
||||||
chai.expect(line.toArray()).eql([
|
];
|
||||||
[1, 'a', 0, 'a'.charCodeAt(0)],
|
for (let i = 0; i < data.length; ++i) line.push(data[i]);
|
||||||
[4, 'd', 0, 'd'.charCodeAt(0)],
|
line.insertCells(1, 3, [4, 'd', 0, 0]);
|
||||||
[4, 'd', 0, 'd'.charCodeAt(0)]
|
chai.expect(line.toArray()).eql([[1, 'a', 0, 0], [4, 'd', 0, 0], [4, 'd', 0, 0]]);
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
it('deleteCells', function(): void {
|
it('deleteCells', function(): void {
|
||||||
const line = new TestBufferLine(5);
|
const line = new TestBufferLine();
|
||||||
line.set(0, [1, 'a', 0, 'a'.charCodeAt(0)]);
|
const data: CharData[] = [
|
||||||
line.set(1, [2, 'b', 0, 'b'.charCodeAt(0)]);
|
[1, 'a', 0, 0],
|
||||||
line.set(2, [3, 'c', 0, 'c'.charCodeAt(0)]);
|
[2, 'b', 0, 0],
|
||||||
line.set(3, [4, 'd', 0, 'd'.charCodeAt(0)]);
|
[3, 'c', 0, 0],
|
||||||
line.set(4, [5, 'e', 0, 'e'.charCodeAt(0)]);
|
[4, 'd', 0, 0],
|
||||||
line.deleteCells(1, 2, [6, 'f', 0, 'f'.charCodeAt(0)]);
|
[5, 'e', 0, 0]
|
||||||
chai.expect(line.toArray()).eql([
|
];
|
||||||
[1, 'a', 0, 'a'.charCodeAt(0)],
|
for (let i = 0; i < data.length; ++i) line.push(data[i]);
|
||||||
[4, 'd', 0, 'd'.charCodeAt(0)],
|
line.deleteCells(1, 2, [6, 'f', 0, 0]);
|
||||||
[5, 'e', 0, 'e'.charCodeAt(0)],
|
chai.expect(line.toArray()).eql([[1, 'a', 0, 0], [4, 'd', 0, 0], [5, 'e', 0, 0], [6, 'f', 0, 0], [6, 'f', 0, 0]]);
|
||||||
[6, 'f', 0, 'f'.charCodeAt(0)],
|
|
||||||
[6, 'f', 0, 'f'.charCodeAt(0)]
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
it('replaceCells', function(): void {
|
it('replaceCells', function(): void {
|
||||||
const line = new TestBufferLine(5);
|
const line = new TestBufferLine();
|
||||||
line.set(0, [1, 'a', 0, 'a'.charCodeAt(0)]);
|
const data: CharData[] = [
|
||||||
line.set(1, [2, 'b', 0, 'b'.charCodeAt(0)]);
|
[1, 'a', 0, 0],
|
||||||
line.set(2, [3, 'c', 0, 'c'.charCodeAt(0)]);
|
[2, 'b', 0, 0],
|
||||||
line.set(3, [4, 'd', 0, 'd'.charCodeAt(0)]);
|
[3, 'c', 0, 0],
|
||||||
line.set(4, [5, 'e', 0, 'e'.charCodeAt(0)]);
|
[4, 'd', 0, 0],
|
||||||
line.replaceCells(2, 4, [6, 'f', 0, 'f'.charCodeAt(0)]);
|
[5, 'e', 0, 0]
|
||||||
chai.expect(line.toArray()).eql([
|
];
|
||||||
[1, 'a', 0, 'a'.charCodeAt(0)],
|
for (let i = 0; i < data.length; ++i) line.push(data[i]);
|
||||||
[2, 'b', 0, 'b'.charCodeAt(0)],
|
line.replaceCells(2, 4, [6, 'f', 0, 0]);
|
||||||
[6, 'f', 0, 'f'.charCodeAt(0)],
|
chai.expect(line.toArray()).eql([[1, 'a', 0, 0], [2, 'b', 0, 0], [6, 'f', 0, 0], [6, 'f', 0, 0], [5, 'e', 0, 0]]);
|
||||||
[6, 'f', 0, 'f'.charCodeAt(0)],
|
|
||||||
[5, 'e', 0, 'e'.charCodeAt(0)]
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
it('fill', function(): void {
|
|
||||||
const line = new TestBufferLine(5);
|
|
||||||
line.set(0, [1, 'a', 0, 'a'.charCodeAt(0)]);
|
|
||||||
line.set(1, [2, 'b', 0, 'b'.charCodeAt(0)]);
|
|
||||||
line.set(2, [3, 'c', 0, 'c'.charCodeAt(0)]);
|
|
||||||
line.set(3, [4, 'd', 0, 'd'.charCodeAt(0)]);
|
|
||||||
line.set(4, [5, 'e', 0, 'e'.charCodeAt(0)]);
|
|
||||||
line.fill([123, 'z', 0, 'z'.charCodeAt(0)]);
|
|
||||||
chai.expect(line.toArray()).eql([
|
|
||||||
[123, 'z', 0, 'z'.charCodeAt(0)],
|
|
||||||
[123, 'z', 0, 'z'.charCodeAt(0)],
|
|
||||||
[123, 'z', 0, 'z'.charCodeAt(0)],
|
|
||||||
[123, 'z', 0, 'z'.charCodeAt(0)],
|
|
||||||
[123, 'z', 0, 'z'.charCodeAt(0)]
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
it('clone', function(): void {
|
|
||||||
const line = new TestBufferLine(5, null, true);
|
|
||||||
line.set(0, [1, 'a', 0, 'a'.charCodeAt(0)]);
|
|
||||||
line.set(1, [2, 'b', 0, 'b'.charCodeAt(0)]);
|
|
||||||
line.set(2, [3, 'c', 0, 'c'.charCodeAt(0)]);
|
|
||||||
line.set(3, [4, 'd', 0, 'd'.charCodeAt(0)]);
|
|
||||||
line.set(4, [5, 'e', 0, 'e'.charCodeAt(0)]);
|
|
||||||
const line2 = line.clone();
|
|
||||||
chai.expect(TestBufferLine.prototype.toArray.apply(line2)).eql(line.toArray());
|
|
||||||
chai.expect(line2.length).equals(line.length);
|
|
||||||
chai.expect(line2.isWrapped).equals(line.isWrapped);
|
|
||||||
});
|
|
||||||
it('copyFrom', function(): void {
|
|
||||||
const line = new TestBufferLine(5);
|
|
||||||
line.set(0, [1, 'a', 0, 'a'.charCodeAt(0)]);
|
|
||||||
line.set(1, [2, 'b', 0, 'b'.charCodeAt(0)]);
|
|
||||||
line.set(2, [3, 'c', 0, 'c'.charCodeAt(0)]);
|
|
||||||
line.set(3, [4, 'd', 0, 'd'.charCodeAt(0)]);
|
|
||||||
line.set(4, [5, 'e', 0, 'e'.charCodeAt(0)]);
|
|
||||||
const line2 = new TestBufferLine(5, [1, 'a', 0, 'a'.charCodeAt(0)], true);
|
|
||||||
line2.copyFrom(line);
|
|
||||||
chai.expect(line2.toArray()).eql(line.toArray());
|
|
||||||
chai.expect(line2.length).equals(line.length);
|
|
||||||
chai.expect(line2.isWrapped).equals(line.isWrapped);
|
|
||||||
});
|
|
||||||
it('should support combining chars', function(): void {
|
|
||||||
// CHAR_DATA_CODE_INDEX resembles current behavior in InputHandler.print
|
|
||||||
// --> set code to the last charCodeAt value of the string
|
|
||||||
// Note: needs to be fixed once the string pointer is in place
|
|
||||||
const line = new TestBufferLine(2, [1, 'e\u0301', 0, '\u0301'.charCodeAt(0)]);
|
|
||||||
chai.expect(line.toArray()).eql([[1, 'e\u0301', 0, '\u0301'.charCodeAt(0)], [1, 'e\u0301', 0, '\u0301'.charCodeAt(0)]]);
|
|
||||||
const line2 = new TestBufferLine(5, [1, 'a', 0, '\u0301'.charCodeAt(0)], true);
|
|
||||||
line2.copyFrom(line);
|
|
||||||
chai.expect(line2.toArray()).eql(line.toArray());
|
|
||||||
const line3 = line.clone();
|
|
||||||
chai.expect(TestBufferLine.prototype.toArray.apply(line3)).eql(line.toArray());
|
|
||||||
});
|
|
||||||
describe('resize', function(): void {
|
|
||||||
it('enlarge(false)', function(): void {
|
|
||||||
const line = new TestBufferLine(5, [1, 'a', 0, 'a'.charCodeAt(0)], false);
|
|
||||||
line.resize(10, [1, 'a', 0, 'a'.charCodeAt(0)]);
|
|
||||||
chai.expect(line.toArray()).eql(Array(10).fill([1, 'a', 0, 'a'.charCodeAt(0)]));
|
|
||||||
});
|
|
||||||
it('enlarge(true)', function(): void {
|
|
||||||
const line = new TestBufferLine(5, [1, 'a', 0, 'a'.charCodeAt(0)], false);
|
|
||||||
line.resize(10, [1, 'a', 0, 'a'.charCodeAt(0)], true);
|
|
||||||
chai.expect(line.toArray()).eql(Array(10).fill([1, 'a', 0, 'a'.charCodeAt(0)]));
|
|
||||||
});
|
|
||||||
it('shrink(true) - should apply new size', function(): void {
|
|
||||||
const line = new TestBufferLine(10, [1, 'a', 0, 'a'.charCodeAt(0)], false);
|
|
||||||
line.resize(5, [1, 'a', 0, 'a'.charCodeAt(0)], true);
|
|
||||||
chai.expect(line.toArray()).eql(Array(5).fill([1, 'a', 0, 'a'.charCodeAt(0)]));
|
|
||||||
});
|
|
||||||
it('shrink(false) - should not apply new size', function(): void {
|
|
||||||
const line = new TestBufferLine(10, [1, 'a', 0, 'a'.charCodeAt(0)], false);
|
|
||||||
line.resize(5, [1, 'a', 0, 'a'.charCodeAt(0)], false);
|
|
||||||
chai.expect(line.toArray()).eql(Array(10).fill([1, 'a', 0, 'a'.charCodeAt(0)]));
|
|
||||||
});
|
|
||||||
it('shrink(false) + shrink(false) - should not apply new size', function(): void {
|
|
||||||
const line = new TestBufferLine(20, [1, 'a', 0, 'a'.charCodeAt(0)], false);
|
|
||||||
line.resize(10, [1, 'a', 0, 'a'.charCodeAt(0)], false);
|
|
||||||
line.resize(5, [1, 'a', 0, 'a'.charCodeAt(0)], false);
|
|
||||||
chai.expect(line.toArray()).eql(Array(20).fill([1, 'a', 0, 'a'.charCodeAt(0)]));
|
|
||||||
});
|
|
||||||
it('shrink(false) + enlarge(false) to smaller than before', function(): void {
|
|
||||||
const line = new TestBufferLine(20, [1, 'a', 0, 'a'.charCodeAt(0)], false);
|
|
||||||
line.resize(10, [1, 'a', 0, 'a'.charCodeAt(0)], false);
|
|
||||||
line.resize(15, [1, 'a', 0, 'a'.charCodeAt(0)]);
|
|
||||||
chai.expect(line.toArray()).eql(Array(20).fill([1, 'a', 0, 'a'.charCodeAt(0)]));
|
|
||||||
});
|
|
||||||
it('shrink(false) + enlarge(false) to bigger than before', function(): void {
|
|
||||||
const line = new TestBufferLine(20, [1, 'a', 0, 'a'.charCodeAt(0)], false);
|
|
||||||
line.resize(10, [1, 'a', 0, 'a'.charCodeAt(0)], false);
|
|
||||||
line.resize(25, [1, 'a', 0, 'a'.charCodeAt(0)]);
|
|
||||||
chai.expect(line.toArray()).eql(Array(25).fill([1, 'a', 0, 'a'.charCodeAt(0)]));
|
|
||||||
});
|
|
||||||
it('shrink(false) + resize shrink=true should enforce shrinking', function(): void {
|
|
||||||
const line = new TestBufferLine(20, [1, 'a', 0, 'a'.charCodeAt(0)], false);
|
|
||||||
line.resize(10, [1, 'a', 0, 'a'.charCodeAt(0)], false);
|
|
||||||
line.resize(10, [1, 'a', 0, 'a'.charCodeAt(0)], true);
|
|
||||||
chai.expect(line.toArray()).eql(Array(10).fill([1, 'a', 0, 'a'.charCodeAt(0)]));
|
|
||||||
});
|
|
||||||
it('enlarge from 0 length', function(): void {
|
|
||||||
const line = new TestBufferLine(0, [1, 'a', 0, 'a'.charCodeAt(0)], false);
|
|
||||||
line.resize(10, [1, 'a', 0, 'a'.charCodeAt(0)], false);
|
|
||||||
chai.expect(line.toArray()).eql(Array(10).fill([1, 'a', 0, 'a'.charCodeAt(0)]));
|
|
||||||
});
|
|
||||||
it('shrink to 0 length', function(): void {
|
|
||||||
const line = new TestBufferLine(10, [1, 'a', 0, 'a'.charCodeAt(0)], false);
|
|
||||||
line.resize(0, [1, 'a', 0, 'a'.charCodeAt(0)], true);
|
|
||||||
chai.expect(line.toArray()).eql(Array(0).fill([1, 'a', 0, 'a'.charCodeAt(0)]));
|
|
||||||
});
|
|
||||||
it('shrink(false) to 0 and enlarge to different sizes', function(): void {
|
|
||||||
const line = new TestBufferLine(10, [1, 'a', 0, 'a'.charCodeAt(0)], false);
|
|
||||||
line.resize(0, [1, 'a', 0, 'a'.charCodeAt(0)], false);
|
|
||||||
chai.expect(line.toArray()).eql(Array(10).fill([1, 'a', 0, 'a'.charCodeAt(0)]));
|
|
||||||
line.resize(5, [1, 'a', 0, 'a'.charCodeAt(0)], false);
|
|
||||||
chai.expect(line.toArray()).eql(Array(10).fill([1, 'a', 0, 'a'.charCodeAt(0)]));
|
|
||||||
line.resize(7, [1, 'a', 0, 'a'.charCodeAt(0)], false);
|
|
||||||
chai.expect(line.toArray()).eql(Array(10).fill([1, 'a', 0, 'a'.charCodeAt(0)]));
|
|
||||||
line.resize(7, [1, 'a', 0, 'a'.charCodeAt(0)], true);
|
|
||||||
chai.expect(line.toArray()).eql(Array(7).fill([1, 'a', 0, 'a'.charCodeAt(0)]));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe('getTrimLength', function(): void {
|
|
||||||
it('empty line', function(): void {
|
|
||||||
const line = new TestBufferLine(10, [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE], false);
|
|
||||||
chai.expect(line.getTrimmedLength()).equal(0);
|
|
||||||
});
|
|
||||||
it('ASCII', function(): void {
|
|
||||||
const line = new TestBufferLine(10, [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE], false);
|
|
||||||
line.set(0, [1, 'a', 1, 'a'.charCodeAt(0)]);
|
|
||||||
line.set(2, [1, 'a', 1, 'a'.charCodeAt(0)]);
|
|
||||||
chai.expect(line.getTrimmedLength()).equal(3);
|
|
||||||
});
|
|
||||||
it('surrogate', function(): void {
|
|
||||||
const line = new TestBufferLine(10, [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE], false);
|
|
||||||
line.set(0, [1, 'a', 1, 'a'.charCodeAt(0)]);
|
|
||||||
line.set(2, [1, '𝄞', 1, '𝄞'.charCodeAt(0)]);
|
|
||||||
chai.expect(line.getTrimmedLength()).equal(3);
|
|
||||||
});
|
|
||||||
it('combining', function(): void {
|
|
||||||
const line = new TestBufferLine(10, [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE], false);
|
|
||||||
line.set(0, [1, 'a', 1, 'a'.charCodeAt(0)]);
|
|
||||||
line.set(2, [1, 'e\u0301', 1, '\u0301'.charCodeAt(0)]);
|
|
||||||
chai.expect(line.getTrimmedLength()).equal(3);
|
|
||||||
});
|
|
||||||
it('fullwidth', function(): void {
|
|
||||||
const line = new TestBufferLine(10, [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE], false);
|
|
||||||
line.set(0, [1, 'a', 1, 'a'.charCodeAt(0)]);
|
|
||||||
line.set(2, [1, '1', 2, '1'.charCodeAt(0)]);
|
|
||||||
line.set(3, [0, '', 0, undefined]);
|
|
||||||
chai.expect(line.getTrimmedLength()).equal(4); // also counts null cell after fullwidth
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe('translateToString with and w\'o trimming', function(): void {
|
|
||||||
it('empty line', function(): void {
|
|
||||||
const line = new TestBufferLine(10, [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE], false);
|
|
||||||
chai.expect(line.translateToString(false)).equal(' ');
|
|
||||||
chai.expect(line.translateToString(true)).equal('');
|
|
||||||
});
|
|
||||||
it('ASCII', function(): void {
|
|
||||||
const line = new TestBufferLine(10, [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE], false);
|
|
||||||
line.set(0, [1, 'a', 1, 'a'.charCodeAt(0)]);
|
|
||||||
line.set(2, [1, 'a', 1, 'a'.charCodeAt(0)]);
|
|
||||||
line.set(4, [1, 'a', 1, 'a'.charCodeAt(0)]);
|
|
||||||
line.set(5, [1, 'a', 1, 'a'.charCodeAt(0)]);
|
|
||||||
chai.expect(line.translateToString(false)).equal('a a aa ');
|
|
||||||
chai.expect(line.translateToString(true)).equal('a a aa');
|
|
||||||
chai.expect(line.translateToString(false, 0, 5)).equal('a a a');
|
|
||||||
chai.expect(line.translateToString(false, 0, 4)).equal('a a ');
|
|
||||||
chai.expect(line.translateToString(false, 0, 3)).equal('a a');
|
|
||||||
chai.expect(line.translateToString(true, 0, 5)).equal('a a a');
|
|
||||||
chai.expect(line.translateToString(true, 0, 4)).equal('a a ');
|
|
||||||
chai.expect(line.translateToString(true, 0, 3)).equal('a a');
|
|
||||||
|
|
||||||
});
|
|
||||||
it('surrogate', function(): void {
|
|
||||||
const line = new TestBufferLine(10, [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE], false);
|
|
||||||
line.set(0, [1, 'a', 1, 'a'.charCodeAt(0)]);
|
|
||||||
line.set(2, [1, '𝄞', 1, '𝄞'.charCodeAt(0)]);
|
|
||||||
line.set(4, [1, '𝄞', 1, '𝄞'.charCodeAt(0)]);
|
|
||||||
line.set(5, [1, '𝄞', 1, '𝄞'.charCodeAt(0)]);
|
|
||||||
chai.expect(line.translateToString(false)).equal('a 𝄞 𝄞𝄞 ');
|
|
||||||
chai.expect(line.translateToString(true)).equal('a 𝄞 𝄞𝄞');
|
|
||||||
chai.expect(line.translateToString(false, 0, 5)).equal('a 𝄞 𝄞');
|
|
||||||
chai.expect(line.translateToString(false, 0, 4)).equal('a 𝄞 ');
|
|
||||||
chai.expect(line.translateToString(false, 0, 3)).equal('a 𝄞');
|
|
||||||
chai.expect(line.translateToString(true, 0, 5)).equal('a 𝄞 𝄞');
|
|
||||||
chai.expect(line.translateToString(true, 0, 4)).equal('a 𝄞 ');
|
|
||||||
chai.expect(line.translateToString(true, 0, 3)).equal('a 𝄞');
|
|
||||||
});
|
|
||||||
it('combining', function(): void {
|
|
||||||
const line = new TestBufferLine(10, [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE], false);
|
|
||||||
line.set(0, [1, 'a', 1, 'a'.charCodeAt(0)]);
|
|
||||||
line.set(2, [1, 'e\u0301', 1, '\u0301'.charCodeAt(0)]);
|
|
||||||
line.set(4, [1, 'e\u0301', 1, '\u0301'.charCodeAt(0)]);
|
|
||||||
line.set(5, [1, 'e\u0301', 1, '\u0301'.charCodeAt(0)]);
|
|
||||||
chai.expect(line.translateToString(false)).equal('a e\u0301 e\u0301e\u0301 ');
|
|
||||||
chai.expect(line.translateToString(true)).equal('a e\u0301 e\u0301e\u0301');
|
|
||||||
chai.expect(line.translateToString(false, 0, 5)).equal('a e\u0301 e\u0301');
|
|
||||||
chai.expect(line.translateToString(false, 0, 4)).equal('a e\u0301 ');
|
|
||||||
chai.expect(line.translateToString(false, 0, 3)).equal('a e\u0301');
|
|
||||||
chai.expect(line.translateToString(true, 0, 5)).equal('a e\u0301 e\u0301');
|
|
||||||
chai.expect(line.translateToString(true, 0, 4)).equal('a e\u0301 ');
|
|
||||||
chai.expect(line.translateToString(true, 0, 3)).equal('a e\u0301');
|
|
||||||
});
|
|
||||||
it('fullwidth', function(): void {
|
|
||||||
const line = new TestBufferLine(10, [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE], false);
|
|
||||||
line.set(0, [1, 'a', 1, 'a'.charCodeAt(0)]);
|
|
||||||
line.set(2, [1, '1', 2, '1'.charCodeAt(0)]);
|
|
||||||
line.set(3, [0, '', 0, undefined]);
|
|
||||||
line.set(5, [1, '1', 2, '1'.charCodeAt(0)]);
|
|
||||||
line.set(6, [0, '', 0, undefined]);
|
|
||||||
line.set(7, [1, '1', 2, '1'.charCodeAt(0)]);
|
|
||||||
line.set(8, [0, '', 0, undefined]);
|
|
||||||
chai.expect(line.translateToString(false)).equal('a 1 11 ');
|
|
||||||
chai.expect(line.translateToString(true)).equal('a 1 11');
|
|
||||||
chai.expect(line.translateToString(false, 0, 7)).equal('a 1 1');
|
|
||||||
chai.expect(line.translateToString(false, 0, 6)).equal('a 1 1');
|
|
||||||
chai.expect(line.translateToString(false, 0, 5)).equal('a 1 ');
|
|
||||||
chai.expect(line.translateToString(false, 0, 4)).equal('a 1');
|
|
||||||
chai.expect(line.translateToString(false, 0, 3)).equal('a 1');
|
|
||||||
chai.expect(line.translateToString(false, 0, 2)).equal('a ');
|
|
||||||
chai.expect(line.translateToString(true, 0, 7)).equal('a 1 1');
|
|
||||||
chai.expect(line.translateToString(true, 0, 6)).equal('a 1 1');
|
|
||||||
chai.expect(line.translateToString(true, 0, 5)).equal('a 1 ');
|
|
||||||
chai.expect(line.translateToString(true, 0, 4)).equal('a 1');
|
|
||||||
chai.expect(line.translateToString(true, 0, 3)).equal('a 1');
|
|
||||||
chai.expect(line.translateToString(true, 0, 2)).equal('a ');
|
|
||||||
});
|
|
||||||
it('space at end', function(): void {
|
|
||||||
const line = new TestBufferLine(10, [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE], false);
|
|
||||||
line.set(0, [1, 'a', 1, 'a'.charCodeAt(0)]);
|
|
||||||
line.set(2, [1, 'a', 1, 'a'.charCodeAt(0)]);
|
|
||||||
line.set(4, [1, 'a', 1, 'a'.charCodeAt(0)]);
|
|
||||||
line.set(5, [1, 'a', 1, 'a'.charCodeAt(0)]);
|
|
||||||
line.set(6, [1, ' ', 1, ' '.charCodeAt(0)]);
|
|
||||||
chai.expect(line.translateToString(false)).equal('a a aa ');
|
|
||||||
chai.expect(line.translateToString(true)).equal('a a aa ');
|
|
||||||
});
|
|
||||||
it('should always return some sane value', function(): void {
|
|
||||||
// sanity check - broken line with invalid out of bound null width cells
|
|
||||||
// this can atm happen with deleting/inserting chars in inputhandler by "breaking"
|
|
||||||
// fullwidth pairs --> needs to be fixed after settling BufferLine impl
|
|
||||||
const line = new TestBufferLine(10, [DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE], false);
|
|
||||||
chai.expect(line.translateToString(false)).equal(' ');
|
|
||||||
chai.expect(line.translateToString(true)).equal('');
|
|
||||||
});
|
|
||||||
it('should work with endCol=0', () => {
|
|
||||||
const line = new TestBufferLine(10, [DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE], false);
|
|
||||||
line.set(0, [1, 'a', 1, 'a'.charCodeAt(0)]);
|
|
||||||
chai.expect(line.translateToString(true, 0, 0)).equal('');
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+39
-275
@@ -3,47 +3,34 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
import { CharData, IBufferLine } from './Types';
|
import { CharData, IBufferLine } from './Types';
|
||||||
import { NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR, CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, WHITESPACE_CELL_CHAR } from './Buffer';
|
import { NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR } from './Buffer';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class representing a terminal line.
|
* Class representing a terminal line.
|
||||||
*
|
|
||||||
* @deprecated to be removed with one of the next releases
|
|
||||||
*/
|
*/
|
||||||
export class BufferLineJSArray implements IBufferLine {
|
export class BufferLine implements IBufferLine {
|
||||||
|
static blankLine(cols: number, attr: number, isWrapped?: boolean): IBufferLine {
|
||||||
|
const ch: CharData = [attr, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE];
|
||||||
|
return new BufferLine(cols, ch, isWrapped);
|
||||||
|
}
|
||||||
protected _data: CharData[];
|
protected _data: CharData[];
|
||||||
public isWrapped = false;
|
public isWrapped = false;
|
||||||
public length: number;
|
public length: number;
|
||||||
|
|
||||||
constructor(cols: number, fillCharData?: CharData, isWrapped?: boolean) {
|
constructor(cols?: number, ch?: CharData, isWrapped?: boolean) {
|
||||||
this._data = [];
|
this._data = [];
|
||||||
if (!fillCharData) {
|
this.length = this._data.length;
|
||||||
fillCharData = [0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE];
|
if (cols) {
|
||||||
}
|
if (!ch) {
|
||||||
for (let i = 0; i < cols; i++) {
|
ch = [0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE];
|
||||||
this._push(fillCharData); // Note: the ctor ch is not cloned (resembles old behavior)
|
}
|
||||||
|
for (let i = 0; i < cols; i++) {
|
||||||
|
this.push(ch); // Note: the ctor ch is not cloned (resembles old behavior)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (isWrapped) {
|
if (isWrapped) {
|
||||||
this.isWrapped = true;
|
this.isWrapped = true;
|
||||||
}
|
}
|
||||||
this.length = this._data.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _pop(): CharData | undefined {
|
|
||||||
const data = this._data.pop();
|
|
||||||
this.length = this._data.length;
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _push(data: CharData): void {
|
|
||||||
this._data.push(data);
|
|
||||||
this.length = this._data.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _splice(start: number, deleteCount: number, ...items: CharData[]): CharData[] {
|
|
||||||
const removed = this._data.splice(start, deleteCount, ...items);
|
|
||||||
this.length = this._data.length;
|
|
||||||
return removed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public get(index: number): CharData {
|
public get(index: number): CharData {
|
||||||
@@ -54,266 +41,43 @@ export class BufferLineJSArray implements IBufferLine {
|
|||||||
this._data[index] = data;
|
this._data[index] = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public pop(): CharData | undefined {
|
||||||
|
const data = this._data.pop();
|
||||||
|
this.length = this._data.length;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public push(data: CharData): void {
|
||||||
|
this._data.push(data);
|
||||||
|
this.length = this._data.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public splice(start: number, deleteCount: number, ...items: CharData[]): CharData[] {
|
||||||
|
const removed = this._data.splice(start, deleteCount, ...items);
|
||||||
|
this.length = this._data.length;
|
||||||
|
return removed;
|
||||||
|
}
|
||||||
|
|
||||||
/** insert n cells ch at pos, right cells are lost (stable length) */
|
/** insert n cells ch at pos, right cells are lost (stable length) */
|
||||||
public insertCells(pos: number, n: number, ch: CharData): void {
|
public insertCells(pos: number, n: number, ch: CharData): void {
|
||||||
while (n--) {
|
while (n--) {
|
||||||
this._splice(pos, 0, ch);
|
this.splice(pos, 0, ch);
|
||||||
this._pop();
|
this.pop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** delete n cells at pos, right side is filled with fill (stable length) */
|
/** delete n cells at pos, right side is filled with fill (stable length) */
|
||||||
public deleteCells(pos: number, n: number, fillCharData: CharData): void {
|
public deleteCells(pos: number, n: number, fill: CharData): void {
|
||||||
while (n--) {
|
while (n--) {
|
||||||
this._splice(pos, 1);
|
this.splice(pos, 1);
|
||||||
this._push(fillCharData);
|
this.push(fill);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** replace cells from pos to pos + n - 1 with fill */
|
/** replace cells from pos to pos + n - 1 with fill */
|
||||||
public replaceCells(start: number, end: number, fillCharData: CharData): void {
|
public replaceCells(start: number, end: number, fill: CharData): void {
|
||||||
while (start < end && start < this.length) {
|
while (start < end && start < this.length) {
|
||||||
this.set(start++, fillCharData); // Note: fill is not cloned (resembles old behavior)
|
this.set(start++, fill); // Note: fill is not cloned (resembles old behavior)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** resize line to cols filling new cells with fill */
|
|
||||||
public resize(cols: number, fillCharData: CharData, shrink: boolean = false): void {
|
|
||||||
while (this._data.length < cols) {
|
|
||||||
this._data.push(fillCharData);
|
|
||||||
}
|
|
||||||
if (shrink) {
|
|
||||||
while (this._data.length > cols) {
|
|
||||||
this._data.pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.length = this._data.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
public fill(fillCharData: CharData): void {
|
|
||||||
for (let i = 0; i < this.length; ++i) {
|
|
||||||
this.set(i, fillCharData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public copyFrom(line: BufferLineJSArray): void {
|
|
||||||
this._data = line._data.slice(0);
|
|
||||||
this.length = line.length;
|
|
||||||
this.isWrapped = line.isWrapped;
|
|
||||||
}
|
|
||||||
|
|
||||||
public clone(): IBufferLine {
|
|
||||||
const newLine = new BufferLineJSArray(0);
|
|
||||||
newLine.copyFrom(this);
|
|
||||||
return newLine;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getTrimmedLength(): number {
|
|
||||||
for (let i = this.length - 1; i >= 0; --i) {
|
|
||||||
const ch = this.get(i);
|
|
||||||
if (ch[CHAR_DATA_CHAR_INDEX] !== '') {
|
|
||||||
return i + ch[CHAR_DATA_WIDTH_INDEX];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public translateToString(trimRight: boolean = false, startCol: number = 0, endCol: number = this.length): string {
|
|
||||||
if (trimRight) {
|
|
||||||
endCol = Math.min(endCol, this.getTrimmedLength());
|
|
||||||
}
|
|
||||||
let result = '';
|
|
||||||
while (startCol < endCol) {
|
|
||||||
result += this.get(startCol)[CHAR_DATA_CHAR_INDEX] || WHITESPACE_CELL_CHAR;
|
|
||||||
startCol += this.get(startCol)[CHAR_DATA_WIDTH_INDEX] || 1;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** typed array slots taken by one cell */
|
|
||||||
const CELL_SIZE = 3;
|
|
||||||
|
|
||||||
/** cell member indices */
|
|
||||||
const enum Cell {
|
|
||||||
FLAGS = 0,
|
|
||||||
STRING = 1,
|
|
||||||
WIDTH = 2
|
|
||||||
}
|
|
||||||
|
|
||||||
/** single vs. combined char distinction */
|
|
||||||
const IS_COMBINED_BIT_MASK = 0x80000000;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Typed array based bufferline implementation.
|
|
||||||
*/
|
|
||||||
export class BufferLine implements IBufferLine {
|
|
||||||
protected _data: Uint32Array | null = null;
|
|
||||||
protected _combined: {[index: number]: string} = {};
|
|
||||||
public length: number;
|
|
||||||
|
|
||||||
constructor(cols: number, fillCharData?: CharData, public isWrapped: boolean = false) {
|
|
||||||
if (!fillCharData) {
|
|
||||||
fillCharData = [0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE];
|
|
||||||
}
|
|
||||||
if (cols) {
|
|
||||||
this._data = new Uint32Array(cols * CELL_SIZE);
|
|
||||||
for (let i = 0; i < cols; ++i) {
|
|
||||||
this.set(i, fillCharData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.length = cols;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get(index: number): CharData {
|
|
||||||
const stringData = this._data[index * CELL_SIZE + Cell.STRING];
|
|
||||||
return [
|
|
||||||
this._data[index * CELL_SIZE + Cell.FLAGS],
|
|
||||||
(stringData & IS_COMBINED_BIT_MASK)
|
|
||||||
? this._combined[index]
|
|
||||||
: (stringData) ? String.fromCharCode(stringData) : '',
|
|
||||||
this._data[index * CELL_SIZE + Cell.WIDTH],
|
|
||||||
(stringData & IS_COMBINED_BIT_MASK)
|
|
||||||
? this._combined[index].charCodeAt(this._combined[index].length - 1)
|
|
||||||
: stringData
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public set(index: number, value: CharData): void {
|
|
||||||
this._data[index * CELL_SIZE + Cell.FLAGS] = value[0];
|
|
||||||
if (value[1].length > 1) {
|
|
||||||
this._combined[index] = value[1];
|
|
||||||
this._data[index * CELL_SIZE + Cell.STRING] = index | IS_COMBINED_BIT_MASK;
|
|
||||||
} else {
|
|
||||||
this._data[index * CELL_SIZE + Cell.STRING] = value[1].charCodeAt(0);
|
|
||||||
}
|
|
||||||
this._data[index * CELL_SIZE + Cell.WIDTH] = value[2];
|
|
||||||
}
|
|
||||||
|
|
||||||
public insertCells(pos: number, n: number, fillCharData: CharData): void {
|
|
||||||
pos %= this.length;
|
|
||||||
if (n < this.length - pos) {
|
|
||||||
for (let i = this.length - pos - n - 1; i >= 0; --i) {
|
|
||||||
this.set(pos + n + i, this.get(pos + i));
|
|
||||||
}
|
|
||||||
for (let i = 0; i < n; ++i) {
|
|
||||||
this.set(pos + i, fillCharData);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (let i = pos; i < this.length; ++i) {
|
|
||||||
this.set(i, fillCharData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public deleteCells(pos: number, n: number, fillCharData: CharData): void {
|
|
||||||
pos %= this.length;
|
|
||||||
if (n < this.length - pos) {
|
|
||||||
for (let i = 0; i < this.length - pos - n; ++i) {
|
|
||||||
this.set(pos + i, this.get(pos + n + i));
|
|
||||||
}
|
|
||||||
for (let i = this.length - n; i < this.length; ++i) {
|
|
||||||
this.set(i, fillCharData);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (let i = pos; i < this.length; ++i) {
|
|
||||||
this.set(i, fillCharData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public replaceCells(start: number, end: number, fillCharData: CharData): void {
|
|
||||||
while (start < end && start < this.length) {
|
|
||||||
this.set(start++, fillCharData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public resize(cols: number, fillCharData: CharData, shrink: boolean = false): void {
|
|
||||||
if (cols === this.length || (!shrink && cols < this.length)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (cols > this.length) {
|
|
||||||
const data = new Uint32Array(cols * CELL_SIZE);
|
|
||||||
if (this.length) {
|
|
||||||
if (cols * CELL_SIZE < this._data.length) {
|
|
||||||
data.set(this._data.subarray(0, cols * CELL_SIZE));
|
|
||||||
} else {
|
|
||||||
data.set(this._data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this._data = data;
|
|
||||||
for (let i = this.length; i < cols; ++i) {
|
|
||||||
this.set(i, fillCharData);
|
|
||||||
}
|
|
||||||
} else if (shrink) {
|
|
||||||
if (cols) {
|
|
||||||
const data = new Uint32Array(cols * CELL_SIZE);
|
|
||||||
data.set(this._data.subarray(0, cols * CELL_SIZE));
|
|
||||||
this._data = data;
|
|
||||||
} else {
|
|
||||||
this._data = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.length = cols;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** fill a line with fillCharData */
|
|
||||||
public fill(fillCharData: CharData): void {
|
|
||||||
this._combined = {};
|
|
||||||
for (let i = 0; i < this.length; ++i) {
|
|
||||||
this.set(i, fillCharData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** alter to a full copy of line */
|
|
||||||
public copyFrom(line: BufferLine): void {
|
|
||||||
if (this.length !== line.length) {
|
|
||||||
this._data = new Uint32Array(line._data);
|
|
||||||
} else {
|
|
||||||
// use high speed copy if lengths are equal
|
|
||||||
this._data.set(line._data);
|
|
||||||
}
|
|
||||||
this.length = line.length;
|
|
||||||
this._combined = {};
|
|
||||||
for (const el in line._combined) {
|
|
||||||
this._combined[el] = line._combined[el];
|
|
||||||
}
|
|
||||||
this.isWrapped = line.isWrapped;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** create a new clone */
|
|
||||||
public clone(): IBufferLine {
|
|
||||||
const newLine = new BufferLine(0);
|
|
||||||
// creation of new typed array from another is actually pretty slow :(
|
|
||||||
// still faster than copying values one by one
|
|
||||||
newLine._data = new Uint32Array(this._data);
|
|
||||||
newLine.length = this.length;
|
|
||||||
for (const el in this._combined) {
|
|
||||||
newLine._combined[el] = this._combined[el];
|
|
||||||
}
|
|
||||||
newLine.isWrapped = this.isWrapped;
|
|
||||||
return newLine;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getTrimmedLength(): number {
|
|
||||||
for (let i = this.length - 1; i >= 0; --i) {
|
|
||||||
if (this._data[i * CELL_SIZE + Cell.STRING] !== 0) { // 0 ==> ''.charCodeAt(0) ==> NaN ==> 0
|
|
||||||
return i + this._data[i * CELL_SIZE + Cell.WIDTH];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public translateToString(trimRight: boolean = false, startCol: number = 0, endCol: number = this.length): string {
|
|
||||||
if (trimRight) {
|
|
||||||
endCol = Math.min(endCol, this.getTrimmedLength());
|
|
||||||
}
|
|
||||||
let result = '';
|
|
||||||
while (startCol < endCol) {
|
|
||||||
const stringData = this._data[startCol * CELL_SIZE + Cell.STRING];
|
|
||||||
result += (stringData & IS_COMBINED_BIT_MASK) ? this._combined[startCol] : (stringData) ? String.fromCharCode(stringData) : WHITESPACE_CELL_CHAR;
|
|
||||||
startCol += this._data[startCol * CELL_SIZE + Cell.WIDTH] || 1;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-27
@@ -7,7 +7,7 @@ import { assert } from 'chai';
|
|||||||
import { ITerminal } from './Types';
|
import { ITerminal } from './Types';
|
||||||
import { BufferSet } from './BufferSet';
|
import { BufferSet } from './BufferSet';
|
||||||
import { Buffer } from './Buffer';
|
import { Buffer } from './Buffer';
|
||||||
import { MockTerminal } from './ui/TestUtils.test';
|
import { MockTerminal } from './utils/TestUtils.test';
|
||||||
|
|
||||||
describe('BufferSet', () => {
|
describe('BufferSet', () => {
|
||||||
let terminal: ITerminal;
|
let terminal: ITerminal;
|
||||||
@@ -48,30 +48,4 @@ describe('BufferSet', () => {
|
|||||||
assert.equal(bufferSet.active, bufferSet.alt);
|
assert.equal(bufferSet.active, bufferSet.alt);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('cursor handling when swapping buffers', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
bufferSet.normal.x = 0;
|
|
||||||
bufferSet.normal.y = 0;
|
|
||||||
bufferSet.alt.x = 0;
|
|
||||||
bufferSet.alt.y = 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should keep the cursor stationary when activating alt buffer', () => {
|
|
||||||
bufferSet.activateNormalBuffer();
|
|
||||||
bufferSet.active.x = 30;
|
|
||||||
bufferSet.active.y = 10;
|
|
||||||
bufferSet.activateAltBuffer();
|
|
||||||
assert.equal(bufferSet.active.x, 30);
|
|
||||||
assert.equal(bufferSet.active.y, 10);
|
|
||||||
});
|
|
||||||
it('should keep the cursor stationary when activating normal buffer', () => {
|
|
||||||
bufferSet.activateAltBuffer();
|
|
||||||
bufferSet.active.x = 30;
|
|
||||||
bufferSet.active.y = 10;
|
|
||||||
bufferSet.activateNormalBuffer();
|
|
||||||
assert.equal(bufferSet.active.x, 30);
|
|
||||||
assert.equal(bufferSet.active.y, 10);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
+2
-6
@@ -61,8 +61,6 @@ export class BufferSet extends EventEmitter implements IBufferSet {
|
|||||||
if (this._activeBuffer === this._normal) {
|
if (this._activeBuffer === this._normal) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._normal.x = this._alt.x;
|
|
||||||
this._normal.y = this._alt.y;
|
|
||||||
// The alt buffer should always be cleared when we switch to the normal
|
// The alt buffer should always be cleared when we switch to the normal
|
||||||
// buffer. This frees up memory since the alt buffer should always be new
|
// buffer. This frees up memory since the alt buffer should always be new
|
||||||
// when activated.
|
// when activated.
|
||||||
@@ -77,15 +75,13 @@ export class BufferSet extends EventEmitter implements IBufferSet {
|
|||||||
/**
|
/**
|
||||||
* Sets the alt Buffer of the BufferSet as its currently active Buffer
|
* Sets the alt Buffer of the BufferSet as its currently active Buffer
|
||||||
*/
|
*/
|
||||||
public activateAltBuffer(fillAttr?: number): void {
|
public activateAltBuffer(): void {
|
||||||
if (this._activeBuffer === this._alt) {
|
if (this._activeBuffer === this._alt) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Since the alt buffer is always cleared when the normal buffer is
|
// Since the alt buffer is always cleared when the normal buffer is
|
||||||
// activated, we want to fill it when switching to it.
|
// activated, we want to fill it when switching to it.
|
||||||
this._alt.fillViewportRows(fillAttr);
|
this._alt.fillViewportRows();
|
||||||
this._alt.x = this._normal.x;
|
|
||||||
this._alt.y = this._normal.y;
|
|
||||||
this._activeBuffer = this._alt;
|
this._activeBuffer = this._alt;
|
||||||
this.emit('activate', {
|
this.emit('activate', {
|
||||||
activeBuffer: this._alt,
|
activeBuffer: this._alt,
|
||||||
|
|||||||
+2
-177
@@ -3,9 +3,9 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { TestTerminal } from './ui/TestUtils.test';
|
import { TestTerminal } from './utils/TestUtils.test';
|
||||||
import { assert } from 'chai';
|
import { assert } from 'chai';
|
||||||
import { getStringCellWidth, wcwidth } from './CharWidth';
|
import { getStringCellWidth } from './CharWidth';
|
||||||
import { IBuffer } from './Types';
|
import { IBuffer } from './Types';
|
||||||
import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from './Buffer';
|
import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from './Buffer';
|
||||||
|
|
||||||
@@ -77,178 +77,3 @@ describe('getStringCellWidth', function(): void {
|
|||||||
});
|
});
|
||||||
// TODO: multiline tests once #1685 is resolved
|
// TODO: multiline tests once #1685 is resolved
|
||||||
});
|
});
|
||||||
|
|
||||||
it('wcwidth should match all values from the old implementation', function(): void {
|
|
||||||
// old implementation
|
|
||||||
const wcwidthOld = (function(opts: {nul: number, control: number}): (ucs: number) => number {
|
|
||||||
// extracted from https://www.cl.cam.ac.uk/%7Emgk25/ucs/wcwidth.c
|
|
||||||
// combining characters
|
|
||||||
const COMBINING_BMP = [
|
|
||||||
[0x0300, 0x036F], [0x0483, 0x0486], [0x0488, 0x0489],
|
|
||||||
[0x0591, 0x05BD], [0x05BF, 0x05BF], [0x05C1, 0x05C2],
|
|
||||||
[0x05C4, 0x05C5], [0x05C7, 0x05C7], [0x0600, 0x0603],
|
|
||||||
[0x0610, 0x0615], [0x064B, 0x065E], [0x0670, 0x0670],
|
|
||||||
[0x06D6, 0x06E4], [0x06E7, 0x06E8], [0x06EA, 0x06ED],
|
|
||||||
[0x070F, 0x070F], [0x0711, 0x0711], [0x0730, 0x074A],
|
|
||||||
[0x07A6, 0x07B0], [0x07EB, 0x07F3], [0x0901, 0x0902],
|
|
||||||
[0x093C, 0x093C], [0x0941, 0x0948], [0x094D, 0x094D],
|
|
||||||
[0x0951, 0x0954], [0x0962, 0x0963], [0x0981, 0x0981],
|
|
||||||
[0x09BC, 0x09BC], [0x09C1, 0x09C4], [0x09CD, 0x09CD],
|
|
||||||
[0x09E2, 0x09E3], [0x0A01, 0x0A02], [0x0A3C, 0x0A3C],
|
|
||||||
[0x0A41, 0x0A42], [0x0A47, 0x0A48], [0x0A4B, 0x0A4D],
|
|
||||||
[0x0A70, 0x0A71], [0x0A81, 0x0A82], [0x0ABC, 0x0ABC],
|
|
||||||
[0x0AC1, 0x0AC5], [0x0AC7, 0x0AC8], [0x0ACD, 0x0ACD],
|
|
||||||
[0x0AE2, 0x0AE3], [0x0B01, 0x0B01], [0x0B3C, 0x0B3C],
|
|
||||||
[0x0B3F, 0x0B3F], [0x0B41, 0x0B43], [0x0B4D, 0x0B4D],
|
|
||||||
[0x0B56, 0x0B56], [0x0B82, 0x0B82], [0x0BC0, 0x0BC0],
|
|
||||||
[0x0BCD, 0x0BCD], [0x0C3E, 0x0C40], [0x0C46, 0x0C48],
|
|
||||||
[0x0C4A, 0x0C4D], [0x0C55, 0x0C56], [0x0CBC, 0x0CBC],
|
|
||||||
[0x0CBF, 0x0CBF], [0x0CC6, 0x0CC6], [0x0CCC, 0x0CCD],
|
|
||||||
[0x0CE2, 0x0CE3], [0x0D41, 0x0D43], [0x0D4D, 0x0D4D],
|
|
||||||
[0x0DCA, 0x0DCA], [0x0DD2, 0x0DD4], [0x0DD6, 0x0DD6],
|
|
||||||
[0x0E31, 0x0E31], [0x0E34, 0x0E3A], [0x0E47, 0x0E4E],
|
|
||||||
[0x0EB1, 0x0EB1], [0x0EB4, 0x0EB9], [0x0EBB, 0x0EBC],
|
|
||||||
[0x0EC8, 0x0ECD], [0x0F18, 0x0F19], [0x0F35, 0x0F35],
|
|
||||||
[0x0F37, 0x0F37], [0x0F39, 0x0F39], [0x0F71, 0x0F7E],
|
|
||||||
[0x0F80, 0x0F84], [0x0F86, 0x0F87], [0x0F90, 0x0F97],
|
|
||||||
[0x0F99, 0x0FBC], [0x0FC6, 0x0FC6], [0x102D, 0x1030],
|
|
||||||
[0x1032, 0x1032], [0x1036, 0x1037], [0x1039, 0x1039],
|
|
||||||
[0x1058, 0x1059], [0x1160, 0x11FF], [0x135F, 0x135F],
|
|
||||||
[0x1712, 0x1714], [0x1732, 0x1734], [0x1752, 0x1753],
|
|
||||||
[0x1772, 0x1773], [0x17B4, 0x17B5], [0x17B7, 0x17BD],
|
|
||||||
[0x17C6, 0x17C6], [0x17C9, 0x17D3], [0x17DD, 0x17DD],
|
|
||||||
[0x180B, 0x180D], [0x18A9, 0x18A9], [0x1920, 0x1922],
|
|
||||||
[0x1927, 0x1928], [0x1932, 0x1932], [0x1939, 0x193B],
|
|
||||||
[0x1A17, 0x1A18], [0x1B00, 0x1B03], [0x1B34, 0x1B34],
|
|
||||||
[0x1B36, 0x1B3A], [0x1B3C, 0x1B3C], [0x1B42, 0x1B42],
|
|
||||||
[0x1B6B, 0x1B73], [0x1DC0, 0x1DCA], [0x1DFE, 0x1DFF],
|
|
||||||
[0x200B, 0x200F], [0x202A, 0x202E], [0x2060, 0x2063],
|
|
||||||
[0x206A, 0x206F], [0x20D0, 0x20EF], [0x302A, 0x302F],
|
|
||||||
[0x3099, 0x309A], [0xA806, 0xA806], [0xA80B, 0xA80B],
|
|
||||||
[0xA825, 0xA826], [0xFB1E, 0xFB1E], [0xFE00, 0xFE0F],
|
|
||||||
[0xFE20, 0xFE23], [0xFEFF, 0xFEFF], [0xFFF9, 0xFFFB]
|
|
||||||
];
|
|
||||||
const COMBINING_HIGH = [
|
|
||||||
[0x10A01, 0x10A03], [0x10A05, 0x10A06], [0x10A0C, 0x10A0F],
|
|
||||||
[0x10A38, 0x10A3A], [0x10A3F, 0x10A3F], [0x1D167, 0x1D169],
|
|
||||||
[0x1D173, 0x1D182], [0x1D185, 0x1D18B], [0x1D1AA, 0x1D1AD],
|
|
||||||
[0x1D242, 0x1D244], [0xE0001, 0xE0001], [0xE0020, 0xE007F],
|
|
||||||
[0xE0100, 0xE01EF]
|
|
||||||
];
|
|
||||||
// binary search
|
|
||||||
function bisearch(ucs: number, data: number[][]): boolean {
|
|
||||||
let min = 0;
|
|
||||||
let max = data.length - 1;
|
|
||||||
let mid;
|
|
||||||
if (ucs < data[0][0] || ucs > data[max][1]) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
while (max >= min) {
|
|
||||||
mid = (min + max) >> 1;
|
|
||||||
if (ucs > data[mid][1]) {
|
|
||||||
min = mid + 1;
|
|
||||||
} else if (ucs < data[mid][0]) {
|
|
||||||
max = mid - 1;
|
|
||||||
} else {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
function wcwidthBMP(ucs: number): number {
|
|
||||||
// test for 8-bit control characters
|
|
||||||
if (ucs === 0) {
|
|
||||||
return opts.nul;
|
|
||||||
}
|
|
||||||
if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0)) {
|
|
||||||
return opts.control;
|
|
||||||
}
|
|
||||||
// binary search in table of non-spacing characters
|
|
||||||
if (bisearch(ucs, COMBINING_BMP)) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
// if we arrive here, ucs is not a combining or C0/C1 control character
|
|
||||||
if (isWideBMP(ucs)) {
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
function isWideBMP(ucs: number): boolean {
|
|
||||||
return (
|
|
||||||
ucs >= 0x1100 && (
|
|
||||||
ucs <= 0x115f || // Hangul Jamo init. consonants
|
|
||||||
ucs === 0x2329 ||
|
|
||||||
ucs === 0x232a ||
|
|
||||||
(ucs >= 0x2e80 && ucs <= 0xa4cf && ucs !== 0x303f) || // CJK..Yi
|
|
||||||
(ucs >= 0xac00 && ucs <= 0xd7a3) || // Hangul Syllables
|
|
||||||
(ucs >= 0xf900 && ucs <= 0xfaff) || // CJK Compat Ideographs
|
|
||||||
(ucs >= 0xfe10 && ucs <= 0xfe19) || // Vertical forms
|
|
||||||
(ucs >= 0xfe30 && ucs <= 0xfe6f) || // CJK Compat Forms
|
|
||||||
(ucs >= 0xff00 && ucs <= 0xff60) || // Fullwidth Forms
|
|
||||||
(ucs >= 0xffe0 && ucs <= 0xffe6)));
|
|
||||||
}
|
|
||||||
function wcwidthHigh(ucs: number): 0 | 1 | 2 {
|
|
||||||
if (bisearch(ucs, COMBINING_HIGH)) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
if ((ucs >= 0x20000 && ucs <= 0x2fffd) || (ucs >= 0x30000 && ucs <= 0x3fffd)) {
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
const control = opts.control | 0;
|
|
||||||
let table: number[] | Uint32Array = null;
|
|
||||||
function initTable(): number[] | Uint32Array {
|
|
||||||
// lookup table for BMP
|
|
||||||
const CODEPOINTS = 65536; // BMP holds 65536 codepoints
|
|
||||||
const BITWIDTH = 2; // a codepoint can have a width of 0, 1 or 2
|
|
||||||
const ITEMSIZE = 32; // using uint32_t
|
|
||||||
const CONTAINERSIZE = CODEPOINTS * BITWIDTH / ITEMSIZE;
|
|
||||||
const CODEPOINTS_PER_ITEM = ITEMSIZE / BITWIDTH;
|
|
||||||
table = (typeof Uint32Array === 'undefined')
|
|
||||||
? new Array(CONTAINERSIZE)
|
|
||||||
: new Uint32Array(CONTAINERSIZE);
|
|
||||||
for (let i = 0; i < CONTAINERSIZE; ++i) {
|
|
||||||
let num = 0;
|
|
||||||
let pos = CODEPOINTS_PER_ITEM;
|
|
||||||
while (pos--) {
|
|
||||||
num = (num << 2) | wcwidthBMP(CODEPOINTS_PER_ITEM * i + pos);
|
|
||||||
}
|
|
||||||
table[i] = num;
|
|
||||||
}
|
|
||||||
return table;
|
|
||||||
}
|
|
||||||
// get width from lookup table
|
|
||||||
// position in container : num / CODEPOINTS_PER_ITEM
|
|
||||||
// ==> n = table[Math.floor(num / 16)]
|
|
||||||
// ==> n = table[num >> 4]
|
|
||||||
// 16 codepoints per number: FFEEDDCCBBAA99887766554433221100
|
|
||||||
// position in number : (num % CODEPOINTS_PER_ITEM) * BITWIDTH
|
|
||||||
// ==> m = (n % 16) * 2
|
|
||||||
// ==> m = (num & 15) << 1
|
|
||||||
// right shift to position m
|
|
||||||
// ==> n = n >> m e.g. m=12 000000000000FFEEDDCCBBAA99887766
|
|
||||||
// we are only interested in 2 LSBs, cut off higher bits
|
|
||||||
// ==> n = n & 3 e.g. 000000000000000000000000000000XX
|
|
||||||
return function (num: number): number {
|
|
||||||
num = num | 0; // get asm.js like optimization under V8
|
|
||||||
if (num < 32) {
|
|
||||||
return control | 0;
|
|
||||||
}
|
|
||||||
if (num < 127) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
const t = table || initTable();
|
|
||||||
if (num < 65536) {
|
|
||||||
return t[num >> 4] >> ((num & 15) << 1) & 3;
|
|
||||||
}
|
|
||||||
// do a full search for high codepoints
|
|
||||||
return wcwidthHigh(num);
|
|
||||||
};
|
|
||||||
})({nul: 0, control: 0}); // configurable options
|
|
||||||
|
|
||||||
// test full BMP range old vs new implmenetation
|
|
||||||
for (let i = 0; i < 65536; ++i) {
|
|
||||||
assert.equal(wcwidth(i), wcwidthOld(i), `mismatch for i: ${i}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|||||||
+166
-144
@@ -3,139 +3,171 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { fill } from './common/TypedArrayUtils';
|
|
||||||
|
|
||||||
export const wcwidth = (function(opts: {nul: number, control: number}): (ucs: number) => number {
|
export const wcwidth = (function(opts: {nul: number, control: number}): (ucs: number) => number {
|
||||||
// extracted from https://www.cl.cam.ac.uk/%7Emgk25/ucs/wcwidth.c
|
// extracted from https://www.cl.cam.ac.uk/%7Emgk25/ucs/wcwidth.c
|
||||||
// combining characters
|
// combining characters
|
||||||
const COMBINING_BMP = [
|
const COMBINING_BMP = [
|
||||||
[0x0300, 0x036F], [0x0483, 0x0486], [0x0488, 0x0489],
|
[0x0300, 0x036F], [0x0483, 0x0486], [0x0488, 0x0489],
|
||||||
[0x0591, 0x05BD], [0x05BF, 0x05BF], [0x05C1, 0x05C2],
|
[0x0591, 0x05BD], [0x05BF, 0x05BF], [0x05C1, 0x05C2],
|
||||||
[0x05C4, 0x05C5], [0x05C7, 0x05C7], [0x0600, 0x0603],
|
[0x05C4, 0x05C5], [0x05C7, 0x05C7], [0x0600, 0x0603],
|
||||||
[0x0610, 0x0615], [0x064B, 0x065E], [0x0670, 0x0670],
|
[0x0610, 0x0615], [0x064B, 0x065E], [0x0670, 0x0670],
|
||||||
[0x06D6, 0x06E4], [0x06E7, 0x06E8], [0x06EA, 0x06ED],
|
[0x06D6, 0x06E4], [0x06E7, 0x06E8], [0x06EA, 0x06ED],
|
||||||
[0x070F, 0x070F], [0x0711, 0x0711], [0x0730, 0x074A],
|
[0x070F, 0x070F], [0x0711, 0x0711], [0x0730, 0x074A],
|
||||||
[0x07A6, 0x07B0], [0x07EB, 0x07F3], [0x0901, 0x0902],
|
[0x07A6, 0x07B0], [0x07EB, 0x07F3], [0x0901, 0x0902],
|
||||||
[0x093C, 0x093C], [0x0941, 0x0948], [0x094D, 0x094D],
|
[0x093C, 0x093C], [0x0941, 0x0948], [0x094D, 0x094D],
|
||||||
[0x0951, 0x0954], [0x0962, 0x0963], [0x0981, 0x0981],
|
[0x0951, 0x0954], [0x0962, 0x0963], [0x0981, 0x0981],
|
||||||
[0x09BC, 0x09BC], [0x09C1, 0x09C4], [0x09CD, 0x09CD],
|
[0x09BC, 0x09BC], [0x09C1, 0x09C4], [0x09CD, 0x09CD],
|
||||||
[0x09E2, 0x09E3], [0x0A01, 0x0A02], [0x0A3C, 0x0A3C],
|
[0x09E2, 0x09E3], [0x0A01, 0x0A02], [0x0A3C, 0x0A3C],
|
||||||
[0x0A41, 0x0A42], [0x0A47, 0x0A48], [0x0A4B, 0x0A4D],
|
[0x0A41, 0x0A42], [0x0A47, 0x0A48], [0x0A4B, 0x0A4D],
|
||||||
[0x0A70, 0x0A71], [0x0A81, 0x0A82], [0x0ABC, 0x0ABC],
|
[0x0A70, 0x0A71], [0x0A81, 0x0A82], [0x0ABC, 0x0ABC],
|
||||||
[0x0AC1, 0x0AC5], [0x0AC7, 0x0AC8], [0x0ACD, 0x0ACD],
|
[0x0AC1, 0x0AC5], [0x0AC7, 0x0AC8], [0x0ACD, 0x0ACD],
|
||||||
[0x0AE2, 0x0AE3], [0x0B01, 0x0B01], [0x0B3C, 0x0B3C],
|
[0x0AE2, 0x0AE3], [0x0B01, 0x0B01], [0x0B3C, 0x0B3C],
|
||||||
[0x0B3F, 0x0B3F], [0x0B41, 0x0B43], [0x0B4D, 0x0B4D],
|
[0x0B3F, 0x0B3F], [0x0B41, 0x0B43], [0x0B4D, 0x0B4D],
|
||||||
[0x0B56, 0x0B56], [0x0B82, 0x0B82], [0x0BC0, 0x0BC0],
|
[0x0B56, 0x0B56], [0x0B82, 0x0B82], [0x0BC0, 0x0BC0],
|
||||||
[0x0BCD, 0x0BCD], [0x0C3E, 0x0C40], [0x0C46, 0x0C48],
|
[0x0BCD, 0x0BCD], [0x0C3E, 0x0C40], [0x0C46, 0x0C48],
|
||||||
[0x0C4A, 0x0C4D], [0x0C55, 0x0C56], [0x0CBC, 0x0CBC],
|
[0x0C4A, 0x0C4D], [0x0C55, 0x0C56], [0x0CBC, 0x0CBC],
|
||||||
[0x0CBF, 0x0CBF], [0x0CC6, 0x0CC6], [0x0CCC, 0x0CCD],
|
[0x0CBF, 0x0CBF], [0x0CC6, 0x0CC6], [0x0CCC, 0x0CCD],
|
||||||
[0x0CE2, 0x0CE3], [0x0D41, 0x0D43], [0x0D4D, 0x0D4D],
|
[0x0CE2, 0x0CE3], [0x0D41, 0x0D43], [0x0D4D, 0x0D4D],
|
||||||
[0x0DCA, 0x0DCA], [0x0DD2, 0x0DD4], [0x0DD6, 0x0DD6],
|
[0x0DCA, 0x0DCA], [0x0DD2, 0x0DD4], [0x0DD6, 0x0DD6],
|
||||||
[0x0E31, 0x0E31], [0x0E34, 0x0E3A], [0x0E47, 0x0E4E],
|
[0x0E31, 0x0E31], [0x0E34, 0x0E3A], [0x0E47, 0x0E4E],
|
||||||
[0x0EB1, 0x0EB1], [0x0EB4, 0x0EB9], [0x0EBB, 0x0EBC],
|
[0x0EB1, 0x0EB1], [0x0EB4, 0x0EB9], [0x0EBB, 0x0EBC],
|
||||||
[0x0EC8, 0x0ECD], [0x0F18, 0x0F19], [0x0F35, 0x0F35],
|
[0x0EC8, 0x0ECD], [0x0F18, 0x0F19], [0x0F35, 0x0F35],
|
||||||
[0x0F37, 0x0F37], [0x0F39, 0x0F39], [0x0F71, 0x0F7E],
|
[0x0F37, 0x0F37], [0x0F39, 0x0F39], [0x0F71, 0x0F7E],
|
||||||
[0x0F80, 0x0F84], [0x0F86, 0x0F87], [0x0F90, 0x0F97],
|
[0x0F80, 0x0F84], [0x0F86, 0x0F87], [0x0F90, 0x0F97],
|
||||||
[0x0F99, 0x0FBC], [0x0FC6, 0x0FC6], [0x102D, 0x1030],
|
[0x0F99, 0x0FBC], [0x0FC6, 0x0FC6], [0x102D, 0x1030],
|
||||||
[0x1032, 0x1032], [0x1036, 0x1037], [0x1039, 0x1039],
|
[0x1032, 0x1032], [0x1036, 0x1037], [0x1039, 0x1039],
|
||||||
[0x1058, 0x1059], [0x1160, 0x11FF], [0x135F, 0x135F],
|
[0x1058, 0x1059], [0x1160, 0x11FF], [0x135F, 0x135F],
|
||||||
[0x1712, 0x1714], [0x1732, 0x1734], [0x1752, 0x1753],
|
[0x1712, 0x1714], [0x1732, 0x1734], [0x1752, 0x1753],
|
||||||
[0x1772, 0x1773], [0x17B4, 0x17B5], [0x17B7, 0x17BD],
|
[0x1772, 0x1773], [0x17B4, 0x17B5], [0x17B7, 0x17BD],
|
||||||
[0x17C6, 0x17C6], [0x17C9, 0x17D3], [0x17DD, 0x17DD],
|
[0x17C6, 0x17C6], [0x17C9, 0x17D3], [0x17DD, 0x17DD],
|
||||||
[0x180B, 0x180D], [0x18A9, 0x18A9], [0x1920, 0x1922],
|
[0x180B, 0x180D], [0x18A9, 0x18A9], [0x1920, 0x1922],
|
||||||
[0x1927, 0x1928], [0x1932, 0x1932], [0x1939, 0x193B],
|
[0x1927, 0x1928], [0x1932, 0x1932], [0x1939, 0x193B],
|
||||||
[0x1A17, 0x1A18], [0x1B00, 0x1B03], [0x1B34, 0x1B34],
|
[0x1A17, 0x1A18], [0x1B00, 0x1B03], [0x1B34, 0x1B34],
|
||||||
[0x1B36, 0x1B3A], [0x1B3C, 0x1B3C], [0x1B42, 0x1B42],
|
[0x1B36, 0x1B3A], [0x1B3C, 0x1B3C], [0x1B42, 0x1B42],
|
||||||
[0x1B6B, 0x1B73], [0x1DC0, 0x1DCA], [0x1DFE, 0x1DFF],
|
[0x1B6B, 0x1B73], [0x1DC0, 0x1DCA], [0x1DFE, 0x1DFF],
|
||||||
[0x200B, 0x200F], [0x202A, 0x202E], [0x2060, 0x2063],
|
[0x200B, 0x200F], [0x202A, 0x202E], [0x2060, 0x2063],
|
||||||
[0x206A, 0x206F], [0x20D0, 0x20EF], [0x302A, 0x302F],
|
[0x206A, 0x206F], [0x20D0, 0x20EF], [0x302A, 0x302F],
|
||||||
[0x3099, 0x309A], [0xA806, 0xA806], [0xA80B, 0xA80B],
|
[0x3099, 0x309A], [0xA806, 0xA806], [0xA80B, 0xA80B],
|
||||||
[0xA825, 0xA826], [0xFB1E, 0xFB1E], [0xFE00, 0xFE0F],
|
[0xA825, 0xA826], [0xFB1E, 0xFB1E], [0xFE00, 0xFE0F],
|
||||||
[0xFE20, 0xFE23], [0xFEFF, 0xFEFF], [0xFFF9, 0xFFFB]
|
[0xFE20, 0xFE23], [0xFEFF, 0xFEFF], [0xFFF9, 0xFFFB]
|
||||||
];
|
];
|
||||||
const COMBINING_HIGH = [
|
const COMBINING_HIGH = [
|
||||||
[0x10A01, 0x10A03], [0x10A05, 0x10A06], [0x10A0C, 0x10A0F],
|
[0x10A01, 0x10A03], [0x10A05, 0x10A06], [0x10A0C, 0x10A0F],
|
||||||
[0x10A38, 0x10A3A], [0x10A3F, 0x10A3F], [0x1D167, 0x1D169],
|
[0x10A38, 0x10A3A], [0x10A3F, 0x10A3F], [0x1D167, 0x1D169],
|
||||||
[0x1D173, 0x1D182], [0x1D185, 0x1D18B], [0x1D1AA, 0x1D1AD],
|
[0x1D173, 0x1D182], [0x1D185, 0x1D18B], [0x1D1AA, 0x1D1AD],
|
||||||
[0x1D242, 0x1D244], [0xE0001, 0xE0001], [0xE0020, 0xE007F],
|
[0x1D242, 0x1D244], [0xE0001, 0xE0001], [0xE0020, 0xE007F],
|
||||||
[0xE0100, 0xE01EF]
|
[0xE0100, 0xE01EF]
|
||||||
];
|
];
|
||||||
// binary search
|
// binary search
|
||||||
function bisearch(ucs: number, data: number[][]): boolean {
|
function bisearch(ucs: number, data: number[][]): boolean {
|
||||||
let min = 0;
|
let min = 0;
|
||||||
let max = data.length - 1;
|
let max = data.length - 1;
|
||||||
let mid;
|
let mid;
|
||||||
if (ucs < data[0][0] || ucs > data[max][1]) {
|
if (ucs < data[0][0] || ucs > data[max][1]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
while (max >= min) {
|
||||||
|
mid = (min + max) >> 1;
|
||||||
|
if (ucs > data[mid][1]) {
|
||||||
|
min = mid + 1;
|
||||||
|
} else if (ucs < data[mid][0]) {
|
||||||
|
max = mid - 1;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
while (max >= min) {
|
function wcwidthBMP(ucs: number): number {
|
||||||
mid = (min + max) >> 1;
|
// test for 8-bit control characters
|
||||||
if (ucs > data[mid][1]) {
|
if (ucs === 0) {
|
||||||
min = mid + 1;
|
return opts.nul;
|
||||||
} else if (ucs < data[mid][0]) {
|
}
|
||||||
max = mid - 1;
|
if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0)) {
|
||||||
} else {
|
return opts.control;
|
||||||
return true;
|
}
|
||||||
|
// binary search in table of non-spacing characters
|
||||||
|
if (bisearch(ucs, COMBINING_BMP)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
// if we arrive here, ucs is not a combining or C0/C1 control character
|
||||||
|
if (isWideBMP(ucs)) {
|
||||||
|
return 2;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
function wcwidthHigh(ucs: number): 0 | 1 | 2 {
|
|
||||||
if (bisearch(ucs, COMBINING_HIGH)) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
if ((ucs >= 0x20000 && ucs <= 0x2fffd) || (ucs >= 0x30000 && ucs <= 0x3fffd)) {
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
const control = opts.control | 0;
|
|
||||||
|
|
||||||
// create lookup table for BMP plane
|
|
||||||
const table = new Uint8Array(65536);
|
|
||||||
fill(table, 1);
|
|
||||||
table[0] = opts.nul;
|
|
||||||
// control chars
|
|
||||||
fill(table, opts.control, 1, 32);
|
|
||||||
fill(table, opts.control, 0x7f, 0xa0);
|
|
||||||
|
|
||||||
// apply wide char rules first
|
|
||||||
// wide chars
|
|
||||||
fill(table, 2, 0x1100, 0x1160);
|
|
||||||
table[0x2329] = 2;
|
|
||||||
table[0x232a] = 2;
|
|
||||||
fill(table, 2, 0x2e80, 0xa4d0);
|
|
||||||
table[0x303f] = 1; // wrongly in last line
|
|
||||||
|
|
||||||
fill(table, 2, 0xac00, 0xd7a4);
|
|
||||||
fill(table, 2, 0xf900, 0xfb00);
|
|
||||||
fill(table, 2, 0xfe10, 0xfe1a);
|
|
||||||
fill(table, 2, 0xfe30, 0xfe70);
|
|
||||||
fill(table, 2, 0xff00, 0xff61);
|
|
||||||
fill(table, 2, 0xffe0, 0xffe7);
|
|
||||||
|
|
||||||
// apply combining last to ensure we overwrite
|
|
||||||
// wrongly wide set chars:
|
|
||||||
// the original algo evals combining first and falls
|
|
||||||
// through to wide check so we simply do here the opposite
|
|
||||||
// combining 0
|
|
||||||
for (let r = 0; r < COMBINING_BMP.length; ++r) {
|
|
||||||
fill(table, 0, COMBINING_BMP[r][0], COMBINING_BMP[r][1] + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return function (num: number): number {
|
|
||||||
if (num < 32) {
|
|
||||||
return control | 0;
|
|
||||||
}
|
|
||||||
if (num < 127) {
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
if (num < 65536) {
|
function isWideBMP(ucs: number): boolean {
|
||||||
return table[num];
|
return (
|
||||||
|
ucs >= 0x1100 && (
|
||||||
|
ucs <= 0x115f || // Hangul Jamo init. consonants
|
||||||
|
ucs === 0x2329 ||
|
||||||
|
ucs === 0x232a ||
|
||||||
|
(ucs >= 0x2e80 && ucs <= 0xa4cf && ucs !== 0x303f) || // CJK..Yi
|
||||||
|
(ucs >= 0xac00 && ucs <= 0xd7a3) || // Hangul Syllables
|
||||||
|
(ucs >= 0xf900 && ucs <= 0xfaff) || // CJK Compat Ideographs
|
||||||
|
(ucs >= 0xfe10 && ucs <= 0xfe19) || // Vertical forms
|
||||||
|
(ucs >= 0xfe30 && ucs <= 0xfe6f) || // CJK Compat Forms
|
||||||
|
(ucs >= 0xff00 && ucs <= 0xff60) || // Fullwidth Forms
|
||||||
|
(ucs >= 0xffe0 && ucs <= 0xffe6)));
|
||||||
}
|
}
|
||||||
// do a full search for high codepoints
|
function wcwidthHigh(ucs: number): 0 | 1 | 2 {
|
||||||
return wcwidthHigh(num);
|
if (bisearch(ucs, COMBINING_HIGH)) {
|
||||||
};
|
return 0;
|
||||||
|
}
|
||||||
|
if ((ucs >= 0x20000 && ucs <= 0x2fffd) || (ucs >= 0x30000 && ucs <= 0x3fffd)) {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
const control = opts.control | 0;
|
||||||
|
let table: number[] | Uint32Array = null;
|
||||||
|
function initTable(): number[] | Uint32Array {
|
||||||
|
// lookup table for BMP
|
||||||
|
const CODEPOINTS = 65536; // BMP holds 65536 codepoints
|
||||||
|
const BITWIDTH = 2; // a codepoint can have a width of 0, 1 or 2
|
||||||
|
const ITEMSIZE = 32; // using uint32_t
|
||||||
|
const CONTAINERSIZE = CODEPOINTS * BITWIDTH / ITEMSIZE;
|
||||||
|
const CODEPOINTS_PER_ITEM = ITEMSIZE / BITWIDTH;
|
||||||
|
table = (typeof Uint32Array === 'undefined')
|
||||||
|
? new Array(CONTAINERSIZE)
|
||||||
|
: new Uint32Array(CONTAINERSIZE);
|
||||||
|
for (let i = 0; i < CONTAINERSIZE; ++i) {
|
||||||
|
let num = 0;
|
||||||
|
let pos = CODEPOINTS_PER_ITEM;
|
||||||
|
while (pos--) {
|
||||||
|
num = (num << 2) | wcwidthBMP(CODEPOINTS_PER_ITEM * i + pos);
|
||||||
|
}
|
||||||
|
table[i] = num;
|
||||||
|
}
|
||||||
|
return table;
|
||||||
|
}
|
||||||
|
// get width from lookup table
|
||||||
|
// position in container : num / CODEPOINTS_PER_ITEM
|
||||||
|
// ==> n = table[Math.floor(num / 16)]
|
||||||
|
// ==> n = table[num >> 4]
|
||||||
|
// 16 codepoints per number: FFEEDDCCBBAA99887766554433221100
|
||||||
|
// position in number : (num % CODEPOINTS_PER_ITEM) * BITWIDTH
|
||||||
|
// ==> m = (n % 16) * 2
|
||||||
|
// ==> m = (num & 15) << 1
|
||||||
|
// right shift to position m
|
||||||
|
// ==> n = n >> m e.g. m=12 000000000000FFEEDDCCBBAA99887766
|
||||||
|
// we are only interested in 2 LSBs, cut off higher bits
|
||||||
|
// ==> n = n & 3 e.g. 000000000000000000000000000000XX
|
||||||
|
return function (num: number): number {
|
||||||
|
num = num | 0; // get asm.js like optimization under V8
|
||||||
|
if (num < 32) {
|
||||||
|
return control | 0;
|
||||||
|
}
|
||||||
|
if (num < 127) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
const t = table || initTable();
|
||||||
|
if (num < 65536) {
|
||||||
|
return t[num >> 4] >> ((num & 15) << 1) & 3;
|
||||||
|
}
|
||||||
|
// do a full search for high codepoints
|
||||||
|
return wcwidthHigh(num);
|
||||||
|
};
|
||||||
})({nul: 0, control: 0}); // configurable options
|
})({nul: 0, control: 0}); // configurable options
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -143,27 +175,17 @@ export const wcwidth = (function(opts: {nul: number, control: number}): (ucs: nu
|
|||||||
*/
|
*/
|
||||||
export function getStringCellWidth(s: string): number {
|
export function getStringCellWidth(s: string): number {
|
||||||
let result = 0;
|
let result = 0;
|
||||||
const length = s.length;
|
for (let i = 0; i < s.length; ++i) {
|
||||||
for (let i = 0; i < length; ++i) {
|
|
||||||
let code = s.charCodeAt(i);
|
let code = s.charCodeAt(i);
|
||||||
// surrogate pair first
|
|
||||||
if (0xD800 <= code && code <= 0xDBFF) {
|
if (0xD800 <= code && code <= 0xDBFF) {
|
||||||
if (++i >= length) {
|
const low = s.charCodeAt(i + 1);
|
||||||
// this should not happen with strings retrieved from
|
if (isNaN(low)) {
|
||||||
// Buffer.translateToString as it converts from UTF-32
|
return result;
|
||||||
// and therefore always should contain the second part
|
|
||||||
// for any other string we still have to handle it somehow:
|
|
||||||
// simply treat the lonely surrogate first as a single char (UCS-2 behavior)
|
|
||||||
return result + wcwidth(code);
|
|
||||||
}
|
|
||||||
const second = s.charCodeAt(i);
|
|
||||||
// convert surrogate pair to high codepoint only for valid second part (UTF-16)
|
|
||||||
// otherwise treat them independently (UCS-2 behavior)
|
|
||||||
if (0xDC00 <= second && second <= 0xDFFF) {
|
|
||||||
code = (code - 0xD800) * 0x400 + second - 0xDC00 + 0x10000;
|
|
||||||
} else {
|
|
||||||
result += wcwidth(second);
|
|
||||||
}
|
}
|
||||||
|
code = ((code - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000;
|
||||||
|
}
|
||||||
|
if (0xDC00 <= code && code <= 0xDFFF) {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
result += wcwidth(code);
|
result += wcwidth(code);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -369,8 +369,7 @@ describe('EscapeSequenceParser', function (): void {
|
|||||||
parser.currentState = ParserState.ESCAPE_INTERMEDIATE;
|
parser.currentState = ParserState.ESCAPE_INTERMEDIATE;
|
||||||
parser.parse(collect[i]);
|
parser.parse(collect[i]);
|
||||||
chai.expect(parser.currentState).equal(ParserState.GROUND);
|
chai.expect(parser.currentState).equal(ParserState.GROUND);
|
||||||
// '\x5c' --> ESC + \ (7bit ST) parser does not expose this as it already got handled
|
testTerminal.compare([['esc', '', collect[i]]]);
|
||||||
testTerminal.compare((collect[i] === '\x5c') ? [] : [['esc', '', collect[i]]]);
|
|
||||||
parser.reset();
|
parser.reset();
|
||||||
testTerminal.clear();
|
testTerminal.clear();
|
||||||
}
|
}
|
||||||
@@ -1052,13 +1051,6 @@ describe('EscapeSequenceParser', function (): void {
|
|||||||
['csi', '<', [0, 0], 'c']
|
['csi', '<', [0, 0], 'c']
|
||||||
], null);
|
], null);
|
||||||
});
|
});
|
||||||
it('7bit ST should be swallowed', function(): void {
|
|
||||||
test('abc\x9d123tzf\x1b\\defg', [
|
|
||||||
['print', 'abc'],
|
|
||||||
['osc', '123tzf'],
|
|
||||||
['print', 'defg']
|
|
||||||
], null);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1097,7 +1089,7 @@ describe('EscapeSequenceParser', function (): void {
|
|||||||
parser.reset();
|
parser.reset();
|
||||||
testTerminal.clear();
|
testTerminal.clear();
|
||||||
parser.currentState = ParserState.GROUND;
|
parser.currentState = ParserState.GROUND;
|
||||||
parser.parse('\x9c');
|
parser.parse('\x1e');
|
||||||
chai.expect(parser.currentState).equal(ParserState.GROUND);
|
chai.expect(parser.currentState).equal(ParserState.GROUND);
|
||||||
testTerminal.compare([]);
|
testTerminal.compare([]);
|
||||||
parser.reset();
|
parser.reset();
|
||||||
@@ -1169,73 +1161,6 @@ describe('EscapeSequenceParser', function (): void {
|
|||||||
parser2.parse(INPUT);
|
parser2.parse(INPUT);
|
||||||
chai.expect(csi).eql([]);
|
chai.expect(csi).eql([]);
|
||||||
});
|
});
|
||||||
describe('CSI custom handlers', () => {
|
|
||||||
it('Prevent fallback', () => {
|
|
||||||
const csiCustom: [string, number[], string][] = [];
|
|
||||||
parser2.setCsiHandler('m', (params, collect) => csi.push(['m', params, collect]));
|
|
||||||
parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params, collect]); return true; });
|
|
||||||
parser2.parse(INPUT);
|
|
||||||
chai.expect(csi).eql([], 'Should not fallback to original handler');
|
|
||||||
chai.expect(csiCustom).eql([['m', [1, 31], ''], ['m', [0], '']]);
|
|
||||||
});
|
|
||||||
it('Allow fallback', () => {
|
|
||||||
const csiCustom: [string, number[], string][] = [];
|
|
||||||
parser2.setCsiHandler('m', (params, collect) => csi.push(['m', params, collect]));
|
|
||||||
parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params, collect]); return false; });
|
|
||||||
parser2.parse(INPUT);
|
|
||||||
chai.expect(csi).eql([['m', [1, 31], ''], ['m', [0], '']], 'Should fallback to original handler');
|
|
||||||
chai.expect(csiCustom).eql([['m', [1, 31], ''], ['m', [0], '']]);
|
|
||||||
});
|
|
||||||
it('Multiple custom handlers fallback once', () => {
|
|
||||||
const csiCustom: [string, number[], string][] = [];
|
|
||||||
const csiCustom2: [string, number[], string][] = [];
|
|
||||||
parser2.setCsiHandler('m', (params, collect) => csi.push(['m', params, collect]));
|
|
||||||
parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params, collect]); return true; });
|
|
||||||
parser2.addCsiHandler('m', (params, collect) => { csiCustom2.push(['m', params, collect]); return false; });
|
|
||||||
parser2.parse(INPUT);
|
|
||||||
chai.expect(csi).eql([], 'Should not fallback to original handler');
|
|
||||||
chai.expect(csiCustom).eql([['m', [1, 31], ''], ['m', [0], '']]);
|
|
||||||
chai.expect(csiCustom2).eql([['m', [1, 31], ''], ['m', [0], '']]);
|
|
||||||
});
|
|
||||||
it('Multiple custom handlers no fallback', () => {
|
|
||||||
const csiCustom: [string, number[], string][] = [];
|
|
||||||
const csiCustom2: [string, number[], string][] = [];
|
|
||||||
parser2.setCsiHandler('m', (params, collect) => csi.push(['m', params, collect]));
|
|
||||||
parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params, collect]); return true; });
|
|
||||||
parser2.addCsiHandler('m', (params, collect) => { csiCustom2.push(['m', params, collect]); return true; });
|
|
||||||
parser2.parse(INPUT);
|
|
||||||
chai.expect(csi).eql([], 'Should not fallback to original handler');
|
|
||||||
chai.expect(csiCustom).eql([], 'Should not fallback once');
|
|
||||||
chai.expect(csiCustom2).eql([['m', [1, 31], ''], ['m', [0], '']]);
|
|
||||||
});
|
|
||||||
it('Execution order should go from latest handler down to the original', () => {
|
|
||||||
const order: number[] = [];
|
|
||||||
parser2.setCsiHandler('m', () => order.push(1));
|
|
||||||
parser2.addCsiHandler('m', () => { order.push(2); return false; });
|
|
||||||
parser2.addCsiHandler('m', () => { order.push(3); return false; });
|
|
||||||
parser2.parse('\x1b[0m');
|
|
||||||
chai.expect(order).eql([3, 2, 1]);
|
|
||||||
});
|
|
||||||
it('Dispose should work', () => {
|
|
||||||
const csiCustom: [string, number[], string][] = [];
|
|
||||||
parser2.setCsiHandler('m', (params, collect) => csi.push(['m', params, collect]));
|
|
||||||
const customHandler = parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params, collect]); return true; });
|
|
||||||
customHandler.dispose();
|
|
||||||
parser2.parse(INPUT);
|
|
||||||
chai.expect(csi).eql([['m', [1, 31], ''], ['m', [0], '']]);
|
|
||||||
chai.expect(csiCustom).eql([], 'Should not use custom handler as it was disposed');
|
|
||||||
});
|
|
||||||
it('Should not corrupt the parser when dispose is called twice', () => {
|
|
||||||
const csiCustom: [string, number[], string][] = [];
|
|
||||||
parser2.setCsiHandler('m', (params, collect) => csi.push(['m', params, collect]));
|
|
||||||
const customHandler = parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params, collect]); return true; });
|
|
||||||
customHandler.dispose();
|
|
||||||
customHandler.dispose();
|
|
||||||
parser2.parse(INPUT);
|
|
||||||
chai.expect(csi).eql([['m', [1, 31], ''], ['m', [0], '']]);
|
|
||||||
chai.expect(csiCustom).eql([], 'Should not use custom handler as it was disposed');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it('EXECUTE handler', function (): void {
|
it('EXECUTE handler', function (): void {
|
||||||
parser2.setExecuteHandler('\n', function (): void {
|
parser2.setExecuteHandler('\n', function (): void {
|
||||||
exe.push('\n');
|
exe.push('\n');
|
||||||
@@ -1263,73 +1188,6 @@ describe('EscapeSequenceParser', function (): void {
|
|||||||
parser2.parse(INPUT);
|
parser2.parse(INPUT);
|
||||||
chai.expect(osc).eql([]);
|
chai.expect(osc).eql([]);
|
||||||
});
|
});
|
||||||
describe('OSC custom handlers', () => {
|
|
||||||
it('Prevent fallback', () => {
|
|
||||||
const oscCustom: [number, string][] = [];
|
|
||||||
parser2.setOscHandler(1, data => osc.push([1, data]));
|
|
||||||
parser2.addOscHandler(1, data => { oscCustom.push([1, data]); return true; });
|
|
||||||
parser2.parse(INPUT);
|
|
||||||
chai.expect(osc).eql([], 'Should not fallback to original handler');
|
|
||||||
chai.expect(oscCustom).eql([[1, 'foo=bar']]);
|
|
||||||
});
|
|
||||||
it('Allow fallback', () => {
|
|
||||||
const oscCustom: [number, string][] = [];
|
|
||||||
parser2.setOscHandler(1, data => osc.push([1, data]));
|
|
||||||
parser2.addOscHandler(1, data => { oscCustom.push([1, data]); return false; });
|
|
||||||
parser2.parse(INPUT);
|
|
||||||
chai.expect(osc).eql([[1, 'foo=bar']], 'Should fallback to original handler');
|
|
||||||
chai.expect(oscCustom).eql([[1, 'foo=bar']]);
|
|
||||||
});
|
|
||||||
it('Multiple custom handlers fallback once', () => {
|
|
||||||
const oscCustom: [number, string][] = [];
|
|
||||||
const oscCustom2: [number, string][] = [];
|
|
||||||
parser2.setOscHandler(1, data => osc.push([1, data]));
|
|
||||||
parser2.addOscHandler(1, data => { oscCustom.push([1, data]); return true; });
|
|
||||||
parser2.addOscHandler(1, data => { oscCustom2.push([1, data]); return false; });
|
|
||||||
parser2.parse(INPUT);
|
|
||||||
chai.expect(osc).eql([], 'Should not fallback to original handler');
|
|
||||||
chai.expect(oscCustom).eql([[1, 'foo=bar']]);
|
|
||||||
chai.expect(oscCustom2).eql([[1, 'foo=bar']]);
|
|
||||||
});
|
|
||||||
it('Multiple custom handlers no fallback', () => {
|
|
||||||
const oscCustom: [number, string][] = [];
|
|
||||||
const oscCustom2: [number, string][] = [];
|
|
||||||
parser2.setOscHandler(1, data => osc.push([1, data]));
|
|
||||||
parser2.addOscHandler(1, data => { oscCustom.push([1, data]); return true; });
|
|
||||||
parser2.addOscHandler(1, data => { oscCustom2.push([1, data]); return true; });
|
|
||||||
parser2.parse(INPUT);
|
|
||||||
chai.expect(osc).eql([], 'Should not fallback to original handler');
|
|
||||||
chai.expect(oscCustom).eql([], 'Should not fallback once');
|
|
||||||
chai.expect(oscCustom2).eql([[1, 'foo=bar']]);
|
|
||||||
});
|
|
||||||
it('Execution order should go from latest handler down to the original', () => {
|
|
||||||
const order: number[] = [];
|
|
||||||
parser2.setOscHandler(1, () => order.push(1));
|
|
||||||
parser2.addOscHandler(1, () => { order.push(2); return false; });
|
|
||||||
parser2.addOscHandler(1, () => { order.push(3); return false; });
|
|
||||||
parser2.parse('\x1b]1;foo=bar\x1b\\');
|
|
||||||
chai.expect(order).eql([3, 2, 1]);
|
|
||||||
});
|
|
||||||
it('Dispose should work', () => {
|
|
||||||
const oscCustom: [number, string][] = [];
|
|
||||||
parser2.setOscHandler(1, data => osc.push([1, data]));
|
|
||||||
const customHandler = parser2.addOscHandler(1, data => { oscCustom.push([1, data]); return true; });
|
|
||||||
customHandler.dispose();
|
|
||||||
parser2.parse(INPUT);
|
|
||||||
chai.expect(osc).eql([[1, 'foo=bar']]);
|
|
||||||
chai.expect(oscCustom).eql([], 'Should not use custom handler as it was disposed');
|
|
||||||
});
|
|
||||||
it('Should not corrupt the parser when dispose is called twice', () => {
|
|
||||||
const oscCustom: [number, string][] = [];
|
|
||||||
parser2.setOscHandler(1, data => osc.push([1, data]));
|
|
||||||
const customHandler = parser2.addOscHandler(1, data => { oscCustom.push([1, data]); return true; });
|
|
||||||
customHandler.dispose();
|
|
||||||
customHandler.dispose();
|
|
||||||
parser2.parse(INPUT);
|
|
||||||
chai.expect(osc).eql([[1, 'foo=bar']]);
|
|
||||||
chai.expect(oscCustom).eql([], 'Should not use custom handler as it was disposed');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it('DCS handler', function (): void {
|
it('DCS handler', function (): void {
|
||||||
parser2.setDcsHandler('+p', {
|
parser2.setDcsHandler('+p', {
|
||||||
hook: function (collect: string, params: number[], flag: number): void {
|
hook: function (collect: string, params: number[], flag: number): void {
|
||||||
|
|||||||
+21
-85
@@ -4,16 +4,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { ParserState, ParserAction, IParsingState, IDcsHandler, IEscapeSequenceParser } from './Types';
|
import { ParserState, ParserAction, IParsingState, IDcsHandler, IEscapeSequenceParser } from './Types';
|
||||||
import { IDisposable } from 'xterm';
|
|
||||||
import { Disposable } from './common/Lifecycle';
|
import { Disposable } from './common/Lifecycle';
|
||||||
|
|
||||||
interface IHandlerCollection<T> {
|
|
||||||
[key: string]: T[];
|
|
||||||
}
|
|
||||||
|
|
||||||
type CsiHandler = (params: number[], collect: string) => boolean | void;
|
|
||||||
type OscHandler = (data: string) => boolean | void;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an array filled with numbers between the low and high parameters (right exclusive).
|
* Returns an array filled with numbers between the low and high parameters (right exclusive).
|
||||||
* @param low The low number.
|
* @param low The low number.
|
||||||
@@ -74,9 +66,8 @@ export class TransitionTable {
|
|||||||
const PRINTABLES = r(0x20, 0x7f);
|
const PRINTABLES = r(0x20, 0x7f);
|
||||||
const EXECUTABLES = r(0x00, 0x18);
|
const EXECUTABLES = r(0x00, 0x18);
|
||||||
EXECUTABLES.push(0x19);
|
EXECUTABLES.push(0x19);
|
||||||
EXECUTABLES.push.apply(EXECUTABLES, r(0x1c, 0x20));
|
EXECUTABLES.concat(r(0x1c, 0x20));
|
||||||
// Pseudo-character placeholder for printable non-ascii characters.
|
const DEFAULT_TRANSITION = ParserAction.ERROR << 4 | ParserState.GROUND;
|
||||||
const NON_ASCII_PRINTABLE = 0xA0;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* VT500 compatible transition table.
|
* VT500 compatible transition table.
|
||||||
@@ -88,10 +79,10 @@ export const VT500_TRANSITION_TABLE = (function (): TransitionTable {
|
|||||||
const states: number[] = r(ParserState.GROUND, ParserState.DCS_PASSTHROUGH + 1);
|
const states: number[] = r(ParserState.GROUND, ParserState.DCS_PASSTHROUGH + 1);
|
||||||
let state: any;
|
let state: any;
|
||||||
|
|
||||||
// table with default transition
|
// table with default transition [any] --> DEFAULT_TRANSITION
|
||||||
for (state in states) {
|
for (state in states) {
|
||||||
// NOTE: table lookup is capped at 0xa0 in parse to keep the table small
|
// NOTE: table lookup is capped at 0xa0 in parse to keep the table small
|
||||||
for (let code = 0; code <= NON_ASCII_PRINTABLE; ++code) {
|
for (let code = 0; code < 160; ++code) {
|
||||||
table.add(code, state, ParserAction.ERROR, ParserState.GROUND);
|
table.add(code, state, ParserAction.ERROR, ParserState.GROUND);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -193,7 +184,6 @@ export const VT500_TRANSITION_TABLE = (function (): TransitionTable {
|
|||||||
table.addMany(PRINTABLES, ParserState.DCS_PASSTHROUGH, ParserAction.DCS_PUT, ParserState.DCS_PASSTHROUGH);
|
table.addMany(PRINTABLES, ParserState.DCS_PASSTHROUGH, ParserAction.DCS_PUT, ParserState.DCS_PASSTHROUGH);
|
||||||
table.add(0x7f, ParserState.DCS_PASSTHROUGH, ParserAction.IGNORE, ParserState.DCS_PASSTHROUGH);
|
table.add(0x7f, ParserState.DCS_PASSTHROUGH, ParserAction.IGNORE, ParserState.DCS_PASSTHROUGH);
|
||||||
table.addMany([0x1b, 0x9c], ParserState.DCS_PASSTHROUGH, ParserAction.DCS_UNHOOK, ParserState.GROUND);
|
table.addMany([0x1b, 0x9c], ParserState.DCS_PASSTHROUGH, ParserAction.DCS_UNHOOK, ParserState.GROUND);
|
||||||
table.add(NON_ASCII_PRINTABLE, ParserState.OSC_STRING, ParserAction.OSC_PUT, ParserState.OSC_STRING);
|
|
||||||
return table;
|
return table;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
@@ -230,9 +220,9 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
|||||||
// handler lookup containers
|
// handler lookup containers
|
||||||
protected _printHandler: (data: string, start: number, end: number) => void;
|
protected _printHandler: (data: string, start: number, end: number) => void;
|
||||||
protected _executeHandlers: any;
|
protected _executeHandlers: any;
|
||||||
protected _csiHandlers: IHandlerCollection<CsiHandler>;
|
protected _csiHandlers: any;
|
||||||
protected _escHandlers: any;
|
protected _escHandlers: any;
|
||||||
protected _oscHandlers: IHandlerCollection<OscHandler>;
|
protected _oscHandlers: any;
|
||||||
protected _dcsHandlers: any;
|
protected _dcsHandlers: any;
|
||||||
protected _activeDcsHandler: IDcsHandler | null;
|
protected _activeDcsHandler: IDcsHandler | null;
|
||||||
protected _errorHandler: (state: IParsingState) => IParsingState;
|
protected _errorHandler: (state: IParsingState) => IParsingState;
|
||||||
@@ -271,9 +261,6 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
|||||||
this._dcsHandlers = Object.create(null);
|
this._dcsHandlers = Object.create(null);
|
||||||
this._activeDcsHandler = null;
|
this._activeDcsHandler = null;
|
||||||
this._errorHandler = this._errorHandlerFb;
|
this._errorHandler = this._errorHandlerFb;
|
||||||
|
|
||||||
// swallow 7bit ST (ESC+\)
|
|
||||||
this.setEscHandler('\\', () => {});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public dispose(): void {
|
public dispose(): void {
|
||||||
@@ -286,8 +273,8 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
|||||||
this._errorHandlerFb = null;
|
this._errorHandlerFb = null;
|
||||||
this._printHandler = null;
|
this._printHandler = null;
|
||||||
this._executeHandlers = null;
|
this._executeHandlers = null;
|
||||||
this._escHandlers = null;
|
|
||||||
this._csiHandlers = null;
|
this._csiHandlers = null;
|
||||||
|
this._escHandlers = null;
|
||||||
this._oscHandlers = null;
|
this._oscHandlers = null;
|
||||||
this._dcsHandlers = null;
|
this._dcsHandlers = null;
|
||||||
this._activeDcsHandler = null;
|
this._activeDcsHandler = null;
|
||||||
@@ -311,24 +298,8 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
|||||||
this._executeHandlerFb = callback;
|
this._executeHandlerFb = callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
addCsiHandler(flag: string, callback: CsiHandler): IDisposable {
|
|
||||||
const index = flag.charCodeAt(0);
|
|
||||||
if (this._csiHandlers[index] === undefined) {
|
|
||||||
this._csiHandlers[index] = [];
|
|
||||||
}
|
|
||||||
const handlerList = this._csiHandlers[index];
|
|
||||||
handlerList.push(callback);
|
|
||||||
return {
|
|
||||||
dispose: () => {
|
|
||||||
const handlerIndex = handlerList.indexOf(callback);
|
|
||||||
if (handlerIndex !== -1) {
|
|
||||||
handlerList.splice(handlerIndex, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
setCsiHandler(flag: string, callback: (params: number[], collect: string) => void): void {
|
setCsiHandler(flag: string, callback: (params: number[], collect: string) => void): void {
|
||||||
this._csiHandlers[flag.charCodeAt(0)] = [callback];
|
this._csiHandlers[flag.charCodeAt(0)] = callback;
|
||||||
}
|
}
|
||||||
clearCsiHandler(flag: string): void {
|
clearCsiHandler(flag: string): void {
|
||||||
if (this._csiHandlers[flag.charCodeAt(0)]) delete this._csiHandlers[flag.charCodeAt(0)];
|
if (this._csiHandlers[flag.charCodeAt(0)]) delete this._csiHandlers[flag.charCodeAt(0)];
|
||||||
@@ -347,23 +318,8 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
|||||||
this._escHandlerFb = callback;
|
this._escHandlerFb = callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable {
|
|
||||||
if (this._oscHandlers[ident] === undefined) {
|
|
||||||
this._oscHandlers[ident] = [];
|
|
||||||
}
|
|
||||||
const handlerList = this._oscHandlers[ident];
|
|
||||||
handlerList.push(callback);
|
|
||||||
return {
|
|
||||||
dispose: () => {
|
|
||||||
const handlerIndex = handlerList.indexOf(callback);
|
|
||||||
if (handlerIndex !== -1) {
|
|
||||||
handlerList.splice(handlerIndex, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
setOscHandler(ident: number, callback: (data: string) => void): void {
|
setOscHandler(ident: number, callback: (data: string) => void): void {
|
||||||
this._oscHandlers[ident] = [callback];
|
this._oscHandlers[ident] = callback;
|
||||||
}
|
}
|
||||||
clearOscHandler(ident: number): void {
|
clearOscHandler(ident: number): void {
|
||||||
if (this._oscHandlers[ident]) delete this._oscHandlers[ident];
|
if (this._oscHandlers[ident]) delete this._oscHandlers[ident];
|
||||||
@@ -432,7 +388,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
|||||||
}
|
}
|
||||||
|
|
||||||
// normal transition & action lookup
|
// normal transition & action lookup
|
||||||
transition = table[currentState << 8 | (code < 0xa0 ? code : NON_ASCII_PRINTABLE)];
|
transition = (code < 0xa0) ? (table[currentState << 8 | code]) : DEFAULT_TRANSITION;
|
||||||
switch (transition >> 4) {
|
switch (transition >> 4) {
|
||||||
case ParserAction.PRINT:
|
case ParserAction.PRINT:
|
||||||
print = (~print) ? print : i;
|
print = (~print) ? print : i;
|
||||||
@@ -464,6 +420,10 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
|||||||
case ParserState.GROUND:
|
case ParserState.GROUND:
|
||||||
print = (~print) ? print : i;
|
print = (~print) ? print : i;
|
||||||
break;
|
break;
|
||||||
|
case ParserState.OSC_STRING:
|
||||||
|
osc += String.fromCharCode(code);
|
||||||
|
transition |= ParserState.OSC_STRING;
|
||||||
|
break;
|
||||||
case ParserState.CSI_IGNORE:
|
case ParserState.CSI_IGNORE:
|
||||||
transition |= ParserState.CSI_IGNORE;
|
transition |= ParserState.CSI_IGNORE;
|
||||||
break;
|
break;
|
||||||
@@ -500,17 +460,9 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case ParserAction.CSI_DISPATCH:
|
case ParserAction.CSI_DISPATCH:
|
||||||
// Trigger CSI Handler
|
callback = this._csiHandlers[code];
|
||||||
const handlers = this._csiHandlers[code];
|
if (callback) callback(params, collect);
|
||||||
let j = handlers ? handlers.length - 1 : -1;
|
else this._csiHandlerFb(collect, params, code);
|
||||||
for (; j >= 0; j--) {
|
|
||||||
if (handlers[j](params, collect)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (j < 0) {
|
|
||||||
this._csiHandlerFb(collect, params, code);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case ParserAction.PARAM:
|
case ParserAction.PARAM:
|
||||||
if (code === 0x3b) params.push(0);
|
if (code === 0x3b) params.push(0);
|
||||||
@@ -562,15 +514,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
|||||||
osc = '';
|
osc = '';
|
||||||
break;
|
break;
|
||||||
case ParserAction.OSC_PUT:
|
case ParserAction.OSC_PUT:
|
||||||
for (let j = i + 1; ; j++) {
|
osc += data.charAt(i);
|
||||||
if (j >= l
|
|
||||||
|| (code = data.charCodeAt(j)) < 0x20
|
|
||||||
|| (code > 0x7f && code <= 0x9f)) {
|
|
||||||
osc += data.substring(i, j);
|
|
||||||
i = j - 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case ParserAction.OSC_END:
|
case ParserAction.OSC_END:
|
||||||
if (osc && code !== 0x18 && code !== 0x1a) {
|
if (osc && code !== 0x18 && code !== 0x1a) {
|
||||||
@@ -585,17 +529,9 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
|||||||
// or with an explicit NaN OSC handler
|
// or with an explicit NaN OSC handler
|
||||||
const identifier = parseInt(osc.substring(0, idx));
|
const identifier = parseInt(osc.substring(0, idx));
|
||||||
const content = osc.substring(idx + 1);
|
const content = osc.substring(idx + 1);
|
||||||
// Trigger OSC Handler
|
callback = this._oscHandlers[identifier];
|
||||||
const handlers = this._oscHandlers[identifier];
|
if (callback) callback(content);
|
||||||
let j = handlers ? handlers.length - 1 : -1;
|
else this._oscHandlerFb(identifier, content);
|
||||||
for (; j >= 0; j--) {
|
|
||||||
if (handlers[j](content)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (j < 0) {
|
|
||||||
this._oscHandlerFb(identifier, content);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (code === 0x1b) transition |= ParserState.ESCAPE;
|
if (code === 0x1b) transition |= ParserState.ESCAPE;
|
||||||
|
|||||||
+280
-200
@@ -5,11 +5,101 @@
|
|||||||
|
|
||||||
import { assert, expect } from 'chai';
|
import { assert, expect } from 'chai';
|
||||||
import { InputHandler } from './InputHandler';
|
import { InputHandler } from './InputHandler';
|
||||||
import { MockInputHandlingTerminal } from './ui/TestUtils.test';
|
import { MockInputHandlingTerminal } from './utils/TestUtils.test';
|
||||||
import { CHAR_DATA_ATTR_INDEX, DEFAULT_ATTR } from './Buffer';
|
import { NULL_CELL_CHAR, NULL_CELL_CODE, NULL_CELL_WIDTH, CHAR_DATA_CHAR_INDEX } from './Buffer';
|
||||||
import { Terminal } from './Terminal';
|
import { Terminal } from './Terminal';
|
||||||
import { IBufferLine } from './Types';
|
import { IBufferLine } from './Types';
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: This and the sections related to this object in associated tests can be
|
||||||
|
// removed safely after InputHandler refactors are finished
|
||||||
|
class OldInputHandler extends InputHandler {
|
||||||
|
public eraseInLine(params: number[]): void {
|
||||||
|
switch (params[0]) {
|
||||||
|
case 0:
|
||||||
|
this.eraseRight(this._terminal.buffer.x, this._terminal.buffer.y);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
this.eraseLeft(this._terminal.buffer.x, this._terminal.buffer.y);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
this.eraseLine(this._terminal.buffer.y);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public eraseInDisplay(params: number[]): void {
|
||||||
|
let j;
|
||||||
|
switch (params[0]) {
|
||||||
|
case 0:
|
||||||
|
this.eraseRight(this._terminal.buffer.x, this._terminal.buffer.y);
|
||||||
|
j = this._terminal.buffer.y + 1;
|
||||||
|
for (; j < this._terminal.rows; j++) {
|
||||||
|
this.eraseLine(j);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
this.eraseLeft(this._terminal.buffer.x, this._terminal.buffer.y);
|
||||||
|
j = this._terminal.buffer.y;
|
||||||
|
while (j--) {
|
||||||
|
this.eraseLine(j);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
j = this._terminal.rows;
|
||||||
|
while (j--) this.eraseLine(j);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
// Clear scrollback (everything not in viewport)
|
||||||
|
const scrollBackSize = this._terminal.buffer.lines.length - this._terminal.rows;
|
||||||
|
if (scrollBackSize > 0) {
|
||||||
|
this._terminal.buffer.lines.trimStart(scrollBackSize);
|
||||||
|
this._terminal.buffer.ybase = Math.max(this._terminal.buffer.ybase - scrollBackSize, 0);
|
||||||
|
this._terminal.buffer.ydisp = Math.max(this._terminal.buffer.ydisp - scrollBackSize, 0);
|
||||||
|
// Force a scroll event to refresh viewport
|
||||||
|
this._terminal.emit('scroll', 0);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 y The line in which to operate.
|
||||||
|
*/
|
||||||
|
public eraseRight(x: number, y: number): void {
|
||||||
|
const line = this._terminal.buffer.lines.get(this._terminal.buffer.ybase + y);
|
||||||
|
if (!line) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
line.replaceCells(x, this._terminal.cols, [this._terminal.eraseAttr(), NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]);
|
||||||
|
this._terminal.updateRange(y);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 y The line in which to operate.
|
||||||
|
*/
|
||||||
|
public eraseLeft(x: number, y: number): void {
|
||||||
|
const line = this._terminal.buffer.lines.get(this._terminal.buffer.ybase + y);
|
||||||
|
if (!line) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
line.replaceCells(0, x + 1, [this._terminal.eraseAttr(), NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]);
|
||||||
|
this._terminal.updateRange(y);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erase all content in the given line
|
||||||
|
* @param y The line to erase all of its contents.
|
||||||
|
*/
|
||||||
|
public eraseLine(y: number): void {
|
||||||
|
this.eraseRight(0, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
describe('InputHandler', () => {
|
describe('InputHandler', () => {
|
||||||
describe('save and restore cursor', () => {
|
describe('save and restore cursor', () => {
|
||||||
const terminal = new MockInputHandlingTerminal();
|
const terminal = new MockInputHandlingTerminal();
|
||||||
@@ -88,9 +178,17 @@ describe('InputHandler', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('regression tests', function(): void {
|
describe('regression tests', function(): void {
|
||||||
function termContent(term: Terminal, trim: boolean): string[] {
|
type CharData = [number, string, number, number];
|
||||||
|
|
||||||
|
function lineContent(line: IBufferLine): string {
|
||||||
|
let content = '';
|
||||||
|
for (let i = 0; i < line.length; ++i) content += line.get(i)[CHAR_DATA_CHAR_INDEX];
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
function termContent(term: Terminal): string[] {
|
||||||
const result = [];
|
const result = [];
|
||||||
for (let i = 0; i < term.rows; ++i) result.push(term.buffer.lines.get(i).translateToString(trim));
|
for (let i = 0; i < term.rows; ++i) result.push(lineContent(term.buffer.lines.get(i)));
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,301 +196,283 @@ describe('InputHandler', () => {
|
|||||||
const term = new Terminal();
|
const term = new Terminal();
|
||||||
const inputHandler = new InputHandler(term);
|
const inputHandler = new InputHandler(term);
|
||||||
|
|
||||||
|
// old variant of the method
|
||||||
|
function insertChars(params: number[]): void {
|
||||||
|
let param = params[0];
|
||||||
|
if (param < 1) param = 1;
|
||||||
|
|
||||||
|
// make buffer local for faster access
|
||||||
|
const buffer = term.buffer;
|
||||||
|
|
||||||
|
const row = buffer.y + buffer.ybase;
|
||||||
|
let j = buffer.x;
|
||||||
|
const ch: CharData = [term.eraseAttr(), NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]; // xterm
|
||||||
|
while (param-- && j < term.cols) {
|
||||||
|
buffer.lines.get(row).splice(j++, 0, ch);
|
||||||
|
buffer.lines.get(row).pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// insert some data in first and second line
|
// insert some data in first and second line
|
||||||
inputHandler.parse(Array(term.cols - 9).join('a'));
|
inputHandler.parse(Array(term.cols - 9).join('a'));
|
||||||
inputHandler.parse('1234567890');
|
inputHandler.parse('1234567890');
|
||||||
inputHandler.parse(Array(term.cols - 9).join('a'));
|
inputHandler.parse(Array(term.cols - 9).join('a'));
|
||||||
inputHandler.parse('1234567890');
|
inputHandler.parse('1234567890');
|
||||||
const line1: IBufferLine = term.buffer.lines.get(0);
|
const line1: IBufferLine = term.buffer.lines.get(0); // line for old variant
|
||||||
expect(line1.translateToString(false)).equals(Array(term.cols - 9).join('a') + '1234567890');
|
const line2: IBufferLine = term.buffer.lines.get(1); // line for new variant
|
||||||
|
expect(lineContent(line1)).equals(Array(term.cols - 9).join('a') + '1234567890');
|
||||||
|
expect(lineContent(line2)).equals(Array(term.cols - 9).join('a') + '1234567890');
|
||||||
|
|
||||||
// insert one char from params = [0]
|
// insert one char from params = [0]
|
||||||
term.buffer.y = 0;
|
term.buffer.y = 0;
|
||||||
term.buffer.x = 70;
|
term.buffer.x = 70;
|
||||||
|
insertChars([0]);
|
||||||
|
expect(lineContent(line1)).equals(Array(term.cols - 9).join('a') + ' 123456789');
|
||||||
|
term.buffer.y = 1;
|
||||||
|
term.buffer.x = 70;
|
||||||
inputHandler.insertChars([0]);
|
inputHandler.insertChars([0]);
|
||||||
expect(line1.translateToString(false)).equals(Array(term.cols - 9).join('a') + ' 123456789');
|
expect(lineContent(line2)).equals(Array(term.cols - 9).join('a') + ' 123456789');
|
||||||
|
expect(lineContent(line2)).equals(lineContent(line1));
|
||||||
|
|
||||||
// insert one char from params = [1]
|
// insert one char from params = [1]
|
||||||
term.buffer.y = 0;
|
term.buffer.y = 0;
|
||||||
term.buffer.x = 70;
|
term.buffer.x = 70;
|
||||||
|
insertChars([1]);
|
||||||
|
expect(lineContent(line1)).equals(Array(term.cols - 9).join('a') + ' 12345678');
|
||||||
|
term.buffer.y = 1;
|
||||||
|
term.buffer.x = 70;
|
||||||
inputHandler.insertChars([1]);
|
inputHandler.insertChars([1]);
|
||||||
expect(line1.translateToString(false)).equals(Array(term.cols - 9).join('a') + ' 12345678');
|
expect(lineContent(line2)).equals(Array(term.cols - 9).join('a') + ' 12345678');
|
||||||
|
expect(lineContent(line2)).equals(lineContent(line1));
|
||||||
|
|
||||||
// insert two chars from params = [2]
|
// insert two chars from params = [2]
|
||||||
term.buffer.y = 0;
|
term.buffer.y = 0;
|
||||||
term.buffer.x = 70;
|
term.buffer.x = 70;
|
||||||
|
insertChars([2]);
|
||||||
|
expect(lineContent(line1)).equals(Array(term.cols - 9).join('a') + ' 123456');
|
||||||
|
term.buffer.y = 1;
|
||||||
|
term.buffer.x = 70;
|
||||||
inputHandler.insertChars([2]);
|
inputHandler.insertChars([2]);
|
||||||
expect(line1.translateToString(false)).equals(Array(term.cols - 9).join('a') + ' 123456');
|
expect(lineContent(line2)).equals(Array(term.cols - 9).join('a') + ' 123456');
|
||||||
|
expect(lineContent(line2)).equals(lineContent(line1));
|
||||||
|
|
||||||
// insert 10 chars from params = [10]
|
// insert 10 chars from params = [10]
|
||||||
term.buffer.y = 0;
|
term.buffer.y = 0;
|
||||||
term.buffer.x = 70;
|
term.buffer.x = 70;
|
||||||
|
insertChars([10]);
|
||||||
|
expect(lineContent(line1)).equals(Array(term.cols - 9).join('a') + ' ');
|
||||||
|
term.buffer.y = 1;
|
||||||
|
term.buffer.x = 70;
|
||||||
inputHandler.insertChars([10]);
|
inputHandler.insertChars([10]);
|
||||||
expect(line1.translateToString(false)).equals(Array(term.cols - 9).join('a') + ' ');
|
expect(lineContent(line2)).equals(Array(term.cols - 9).join('a') + ' ');
|
||||||
expect(line1.translateToString(true)).equals(Array(term.cols - 9).join('a'));
|
expect(lineContent(line2)).equals(lineContent(line1));
|
||||||
});
|
});
|
||||||
it('deleteChars', function(): void {
|
it('deleteChars', function(): void {
|
||||||
const term = new Terminal();
|
const term = new Terminal();
|
||||||
const inputHandler = new InputHandler(term);
|
const inputHandler = new InputHandler(term);
|
||||||
|
|
||||||
|
// old variant of the method
|
||||||
|
function deleteChars(params: number[]): void {
|
||||||
|
let param: number = params[0];
|
||||||
|
if (param < 1) {
|
||||||
|
param = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// make buffer local for faster access
|
||||||
|
const buffer = term.buffer;
|
||||||
|
|
||||||
|
const row = buffer.y + buffer.ybase;
|
||||||
|
const ch: CharData = [term.eraseAttr(), NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]; // xterm
|
||||||
|
while (param--) {
|
||||||
|
buffer.lines.get(row).splice(buffer.x, 1);
|
||||||
|
buffer.lines.get(row).push(ch);
|
||||||
|
}
|
||||||
|
term.updateRange(buffer.y);
|
||||||
|
}
|
||||||
|
|
||||||
// insert some data in first and second line
|
// insert some data in first and second line
|
||||||
inputHandler.parse(Array(term.cols - 9).join('a'));
|
inputHandler.parse(Array(term.cols - 9).join('a'));
|
||||||
inputHandler.parse('1234567890');
|
inputHandler.parse('1234567890');
|
||||||
inputHandler.parse(Array(term.cols - 9).join('a'));
|
inputHandler.parse(Array(term.cols - 9).join('a'));
|
||||||
inputHandler.parse('1234567890');
|
inputHandler.parse('1234567890');
|
||||||
const line1: IBufferLine = term.buffer.lines.get(0);
|
const line1: IBufferLine = term.buffer.lines.get(0); // line for old variant
|
||||||
expect(line1.translateToString(false)).equals(Array(term.cols - 9).join('a') + '1234567890');
|
const line2: IBufferLine = term.buffer.lines.get(1); // line for new variant
|
||||||
|
expect(lineContent(line1)).equals(Array(term.cols - 9).join('a') + '1234567890');
|
||||||
|
expect(lineContent(line2)).equals(Array(term.cols - 9).join('a') + '1234567890');
|
||||||
|
|
||||||
// delete one char from params = [0]
|
// delete one char from params = [0]
|
||||||
term.buffer.y = 0;
|
term.buffer.y = 0;
|
||||||
term.buffer.x = 70;
|
term.buffer.x = 70;
|
||||||
|
deleteChars([0]);
|
||||||
|
expect(lineContent(line1)).equals(Array(term.cols - 9).join('a') + '234567890 ');
|
||||||
|
term.buffer.y = 1;
|
||||||
|
term.buffer.x = 70;
|
||||||
inputHandler.deleteChars([0]);
|
inputHandler.deleteChars([0]);
|
||||||
expect(line1.translateToString(false)).equals(Array(term.cols - 9).join('a') + '234567890 ');
|
expect(lineContent(line2)).equals(Array(term.cols - 9).join('a') + '234567890 ');
|
||||||
expect(line1.translateToString(true)).equals(Array(term.cols - 9).join('a') + '234567890');
|
expect(lineContent(line2)).equals(lineContent(line1));
|
||||||
|
|
||||||
// insert one char from params = [1]
|
// insert one char from params = [1]
|
||||||
term.buffer.y = 0;
|
term.buffer.y = 0;
|
||||||
term.buffer.x = 70;
|
term.buffer.x = 70;
|
||||||
|
deleteChars([1]);
|
||||||
|
expect(lineContent(line1)).equals(Array(term.cols - 9).join('a') + '34567890 ');
|
||||||
|
term.buffer.y = 1;
|
||||||
|
term.buffer.x = 70;
|
||||||
inputHandler.deleteChars([1]);
|
inputHandler.deleteChars([1]);
|
||||||
expect(line1.translateToString(false)).equals(Array(term.cols - 9).join('a') + '34567890 ');
|
expect(lineContent(line2)).equals(Array(term.cols - 9).join('a') + '34567890 ');
|
||||||
expect(line1.translateToString(true)).equals(Array(term.cols - 9).join('a') + '34567890');
|
expect(lineContent(line2)).equals(lineContent(line1));
|
||||||
|
|
||||||
// insert two chars from params = [2]
|
// insert two chars from params = [2]
|
||||||
term.buffer.y = 0;
|
term.buffer.y = 0;
|
||||||
term.buffer.x = 70;
|
term.buffer.x = 70;
|
||||||
|
deleteChars([2]);
|
||||||
|
expect(lineContent(line1)).equals(Array(term.cols - 9).join('a') + '567890 ');
|
||||||
|
term.buffer.y = 1;
|
||||||
|
term.buffer.x = 70;
|
||||||
inputHandler.deleteChars([2]);
|
inputHandler.deleteChars([2]);
|
||||||
expect(line1.translateToString(false)).equals(Array(term.cols - 9).join('a') + '567890 ');
|
expect(lineContent(line2)).equals(Array(term.cols - 9).join('a') + '567890 ');
|
||||||
expect(line1.translateToString(true)).equals(Array(term.cols - 9).join('a') + '567890');
|
expect(lineContent(line2)).equals(lineContent(line1));
|
||||||
|
|
||||||
// insert 10 chars from params = [10]
|
// insert 10 chars from params = [10]
|
||||||
term.buffer.y = 0;
|
term.buffer.y = 0;
|
||||||
term.buffer.x = 70;
|
term.buffer.x = 70;
|
||||||
|
deleteChars([10]);
|
||||||
|
expect(lineContent(line1)).equals(Array(term.cols - 9).join('a') + ' ');
|
||||||
|
term.buffer.y = 1;
|
||||||
|
term.buffer.x = 70;
|
||||||
inputHandler.deleteChars([10]);
|
inputHandler.deleteChars([10]);
|
||||||
expect(line1.translateToString(false)).equals(Array(term.cols - 9).join('a') + ' ');
|
expect(lineContent(line2)).equals(Array(term.cols - 9).join('a') + ' ');
|
||||||
expect(line1.translateToString(true)).equals(Array(term.cols - 9).join('a'));
|
expect(lineContent(line2)).equals(lineContent(line1));
|
||||||
});
|
});
|
||||||
it('eraseInLine', function(): void {
|
it('eraseInLine', function(): void {
|
||||||
const term = new Terminal();
|
const term = new Terminal();
|
||||||
const inputHandler = new InputHandler(term);
|
const inputHandler = new InputHandler(term);
|
||||||
|
const oldInputHandler = new OldInputHandler(term);
|
||||||
|
|
||||||
// fill 6 lines to test 3 different states
|
// fill 6 lines to test 3 different states
|
||||||
inputHandler.parse(Array(term.cols + 1).join('a'));
|
inputHandler.parse(Array(term.cols + 1).join('a'));
|
||||||
inputHandler.parse(Array(term.cols + 1).join('a'));
|
inputHandler.parse(Array(term.cols + 1).join('a'));
|
||||||
inputHandler.parse(Array(term.cols + 1).join('a'));
|
inputHandler.parse(Array(term.cols + 1).join('a'));
|
||||||
|
inputHandler.parse(Array(term.cols + 1).join('a'));
|
||||||
|
inputHandler.parse(Array(term.cols + 1).join('a'));
|
||||||
|
inputHandler.parse(Array(term.cols + 1).join('a'));
|
||||||
|
|
||||||
// params[0] - right erase
|
// params[0] - right erase
|
||||||
term.buffer.y = 0;
|
term.buffer.y = 0;
|
||||||
term.buffer.x = 70;
|
term.buffer.x = 70;
|
||||||
inputHandler.eraseInLine([0]);
|
oldInputHandler.eraseInLine([0]);
|
||||||
expect(term.buffer.lines.get(0).translateToString(false)).equals(Array(71).join('a') + ' ');
|
expect(lineContent(term.buffer.lines.get(0))).equals(Array(71).join('a') + ' ');
|
||||||
|
|
||||||
// params[1] - left erase
|
|
||||||
term.buffer.y = 1;
|
term.buffer.y = 1;
|
||||||
term.buffer.x = 70;
|
term.buffer.x = 70;
|
||||||
inputHandler.eraseInLine([1]);
|
inputHandler.eraseInLine([0]);
|
||||||
expect(term.buffer.lines.get(1).translateToString(false)).equals(Array(71).join(' ') + ' aaaaaaaaa');
|
expect(lineContent(term.buffer.lines.get(1))).equals(Array(71).join('a') + ' ');
|
||||||
|
|
||||||
// params[1] - left erase
|
// params[1] - left erase
|
||||||
term.buffer.y = 2;
|
term.buffer.y = 2;
|
||||||
term.buffer.x = 70;
|
term.buffer.x = 70;
|
||||||
|
oldInputHandler.eraseInLine([1]);
|
||||||
|
expect(lineContent(term.buffer.lines.get(2))).equals(Array(71).join(' ') + ' aaaaaaaaa');
|
||||||
|
term.buffer.y = 3;
|
||||||
|
term.buffer.x = 70;
|
||||||
|
inputHandler.eraseInLine([1]);
|
||||||
|
expect(lineContent(term.buffer.lines.get(3))).equals(Array(71).join(' ') + ' aaaaaaaaa');
|
||||||
|
|
||||||
|
// params[1] - left erase
|
||||||
|
term.buffer.y = 4;
|
||||||
|
term.buffer.x = 70;
|
||||||
|
oldInputHandler.eraseInLine([2]);
|
||||||
|
expect(lineContent(term.buffer.lines.get(4))).equals(Array(term.cols + 1).join(' '));
|
||||||
|
term.buffer.y = 5;
|
||||||
|
term.buffer.x = 70;
|
||||||
inputHandler.eraseInLine([2]);
|
inputHandler.eraseInLine([2]);
|
||||||
expect(term.buffer.lines.get(2).translateToString(false)).equals(Array(term.cols + 1).join(' '));
|
expect(lineContent(term.buffer.lines.get(5))).equals(Array(term.cols + 1).join(' '));
|
||||||
|
|
||||||
});
|
});
|
||||||
it('eraseInDisplay', function(): void {
|
it('eraseInDisplay', function(): void {
|
||||||
const term = new Terminal({cols: 80, rows: 7});
|
const termOld = new Terminal();
|
||||||
const inputHandler = new InputHandler(term);
|
const inputHandlerOld = new OldInputHandler(termOld);
|
||||||
|
const termNew = new Terminal();
|
||||||
|
const inputHandlerNew = new InputHandler(termNew);
|
||||||
|
|
||||||
// fill display with a's
|
// fill display with a's
|
||||||
for (let i = 0; i < term.rows; ++i) inputHandler.parse(Array(term.cols + 1).join('a'));
|
for (let i = 0; i < termOld.rows; ++i) inputHandlerOld.parse(Array(termOld.cols + 1).join('a'));
|
||||||
|
for (let i = 0; i < termNew.rows; ++i) inputHandlerNew.parse(Array(termOld.cols + 1).join('a'));
|
||||||
|
const data = [];
|
||||||
|
for (let i = 0; i < termOld.rows; ++i) data.push(Array(termOld.cols + 1).join('a'));
|
||||||
|
expect(termContent(termOld)).eql(data);
|
||||||
|
expect(termContent(termOld)).eql(termContent(termNew));
|
||||||
|
|
||||||
// params [0] - right and below erase
|
// params [0] - right and below erase
|
||||||
term.buffer.y = 5;
|
termOld.buffer.y = 5;
|
||||||
term.buffer.x = 40;
|
termOld.buffer.x = 40;
|
||||||
inputHandler.eraseInDisplay([0]);
|
inputHandlerOld.eraseInDisplay([0]);
|
||||||
expect(termContent(term, false)).eql([
|
termNew.buffer.y = 5;
|
||||||
Array(term.cols + 1).join('a'),
|
termNew.buffer.x = 40;
|
||||||
Array(term.cols + 1).join('a'),
|
inputHandlerNew.eraseInDisplay([0]);
|
||||||
Array(term.cols + 1).join('a'),
|
expect(termContent(termNew)).eql(termContent(termOld));
|
||||||
Array(term.cols + 1).join('a'),
|
|
||||||
Array(term.cols + 1).join('a'),
|
|
||||||
Array(40 + 1).join('a') + Array(term.cols - 40 + 1).join(' '),
|
|
||||||
Array(term.cols + 1).join(' ')
|
|
||||||
]);
|
|
||||||
expect(termContent(term, true)).eql([
|
|
||||||
Array(term.cols + 1).join('a'),
|
|
||||||
Array(term.cols + 1).join('a'),
|
|
||||||
Array(term.cols + 1).join('a'),
|
|
||||||
Array(term.cols + 1).join('a'),
|
|
||||||
Array(term.cols + 1).join('a'),
|
|
||||||
Array(40 + 1).join('a'),
|
|
||||||
''
|
|
||||||
]);
|
|
||||||
|
|
||||||
// reset
|
// reset
|
||||||
term.buffer.y = 0;
|
termOld.buffer.y = 0;
|
||||||
term.buffer.x = 0;
|
termOld.buffer.x = 0;
|
||||||
for (let i = 0; i < term.rows; ++i) inputHandler.parse(Array(term.cols + 1).join('a'));
|
termNew.buffer.y = 0;
|
||||||
|
termNew.buffer.x = 0;
|
||||||
|
for (let i = 0; i < termOld.rows; ++i) inputHandlerOld.parse(Array(termOld.cols + 1).join('a'));
|
||||||
|
for (let i = 0; i < termNew.rows; ++i) inputHandlerNew.parse(Array(termOld.cols + 1).join('a'));
|
||||||
|
|
||||||
// params [1] - left and above
|
// params [1] - left and above
|
||||||
term.buffer.y = 5;
|
termOld.buffer.y = 5;
|
||||||
term.buffer.x = 40;
|
termOld.buffer.x = 40;
|
||||||
inputHandler.eraseInDisplay([1]);
|
inputHandlerOld.eraseInDisplay([1]);
|
||||||
expect(termContent(term, false)).eql([
|
termNew.buffer.y = 5;
|
||||||
Array(term.cols + 1).join(' '),
|
termNew.buffer.x = 40;
|
||||||
Array(term.cols + 1).join(' '),
|
inputHandlerNew.eraseInDisplay([1]);
|
||||||
Array(term.cols + 1).join(' '),
|
expect(termContent(termNew)).eql(termContent(termOld));
|
||||||
Array(term.cols + 1).join(' '),
|
|
||||||
Array(term.cols + 1).join(' '),
|
|
||||||
Array(41 + 1).join(' ') + Array(term.cols - 41 + 1).join('a'),
|
|
||||||
Array(term.cols + 1).join('a')
|
|
||||||
]);
|
|
||||||
expect(termContent(term, true)).eql([
|
|
||||||
'',
|
|
||||||
'',
|
|
||||||
'',
|
|
||||||
'',
|
|
||||||
'',
|
|
||||||
Array(41 + 1).join(' ') + Array(term.cols - 41 + 1).join('a'),
|
|
||||||
Array(term.cols + 1).join('a')
|
|
||||||
]);
|
|
||||||
|
|
||||||
// reset
|
// reset
|
||||||
term.buffer.y = 0;
|
termOld.buffer.y = 0;
|
||||||
term.buffer.x = 0;
|
termOld.buffer.x = 0;
|
||||||
for (let i = 0; i < term.rows; ++i) inputHandler.parse(Array(term.cols + 1).join('a'));
|
termNew.buffer.y = 0;
|
||||||
|
termNew.buffer.x = 0;
|
||||||
|
for (let i = 0; i < termOld.rows; ++i) inputHandlerOld.parse(Array(termOld.cols + 1).join('a'));
|
||||||
|
for (let i = 0; i < termNew.rows; ++i) inputHandlerNew.parse(Array(termOld.cols + 1).join('a'));
|
||||||
|
|
||||||
// params [2] - whole screen
|
// params [2] - whole screen
|
||||||
term.buffer.y = 5;
|
termOld.buffer.y = 5;
|
||||||
term.buffer.x = 40;
|
termOld.buffer.x = 40;
|
||||||
inputHandler.eraseInDisplay([2]);
|
inputHandlerOld.eraseInDisplay([2]);
|
||||||
expect(termContent(term, false)).eql([
|
termNew.buffer.y = 5;
|
||||||
Array(term.cols + 1).join(' '),
|
termNew.buffer.x = 40;
|
||||||
Array(term.cols + 1).join(' '),
|
inputHandlerNew.eraseInDisplay([2]);
|
||||||
Array(term.cols + 1).join(' '),
|
expect(termContent(termNew)).eql(termContent(termOld));
|
||||||
Array(term.cols + 1).join(' '),
|
|
||||||
Array(term.cols + 1).join(' '),
|
|
||||||
Array(term.cols + 1).join(' '),
|
|
||||||
Array(term.cols + 1).join(' ')
|
|
||||||
]);
|
|
||||||
expect(termContent(term, true)).eql([
|
|
||||||
'',
|
|
||||||
'',
|
|
||||||
'',
|
|
||||||
'',
|
|
||||||
'',
|
|
||||||
'',
|
|
||||||
''
|
|
||||||
]);
|
|
||||||
|
|
||||||
// reset and add a wrapped line
|
|
||||||
term.buffer.y = 0;
|
|
||||||
term.buffer.x = 0;
|
|
||||||
inputHandler.parse(Array(term.cols + 1).join('a')); // line 0
|
|
||||||
inputHandler.parse(Array(term.cols + 10).join('a')); // line 1 and 2
|
|
||||||
for (let i = 3; i < term.rows; ++i) inputHandler.parse(Array(term.cols + 1).join('a'));
|
|
||||||
|
|
||||||
// params[1] left and above with wrap
|
|
||||||
// confirm precondition that line 2 is wrapped
|
|
||||||
expect(term.buffer.lines.get(2).isWrapped).true;
|
|
||||||
term.buffer.y = 2;
|
|
||||||
term.buffer.x = 40;
|
|
||||||
inputHandler.eraseInDisplay([1]);
|
|
||||||
expect(term.buffer.lines.get(2).isWrapped).false;
|
|
||||||
|
|
||||||
// reset and add a wrapped line
|
|
||||||
term.buffer.y = 0;
|
|
||||||
term.buffer.x = 0;
|
|
||||||
inputHandler.parse(Array(term.cols + 1).join('a')); // line 0
|
|
||||||
inputHandler.parse(Array(term.cols + 10).join('a')); // line 1 and 2
|
|
||||||
for (let i = 3; i < term.rows; ++i) inputHandler.parse(Array(term.cols + 1).join('a'));
|
|
||||||
|
|
||||||
// params[1] left and above with wrap
|
|
||||||
// confirm precondition that line 2 is wrapped
|
|
||||||
expect(term.buffer.lines.get(2).isWrapped).true;
|
|
||||||
term.buffer.y = 1;
|
|
||||||
term.buffer.x = 90; // Cursor is beyond last column
|
|
||||||
inputHandler.eraseInDisplay([1]);
|
|
||||||
expect(term.buffer.lines.get(2).isWrapped).false;
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('convertEol setting', function(): void {
|
it('convertEol setting', function(): void {
|
||||||
// not converting
|
// not converting
|
||||||
|
let s = '';
|
||||||
const termNotConverting = new Terminal({cols: 15, rows: 10});
|
const termNotConverting = new Terminal({cols: 15, rows: 10});
|
||||||
(termNotConverting as any)._inputHandler.parse('Hello\nWorld');
|
(termNotConverting as any)._inputHandler.parse('Hello\nWorld');
|
||||||
expect(termNotConverting.buffer.lines.get(0).translateToString(false)).equals('Hello ');
|
for (let i = 0; i < termNotConverting.cols; ++i) {
|
||||||
expect(termNotConverting.buffer.lines.get(1).translateToString(false)).equals(' World ');
|
s += termNotConverting.buffer.lines.get(0).get(i)[CHAR_DATA_CHAR_INDEX];
|
||||||
expect(termNotConverting.buffer.lines.get(0).translateToString(true)).equals('Hello');
|
}
|
||||||
expect(termNotConverting.buffer.lines.get(1).translateToString(true)).equals(' World');
|
expect(s).equals('Hello ');
|
||||||
|
s = '';
|
||||||
|
for (let i = 0; i < termNotConverting.cols; ++i) {
|
||||||
|
s += termNotConverting.buffer.lines.get(1).get(i)[CHAR_DATA_CHAR_INDEX];
|
||||||
|
}
|
||||||
|
expect(s).equals(' World ');
|
||||||
|
|
||||||
// converting
|
// converting
|
||||||
|
s = '';
|
||||||
const termConverting = new Terminal({cols: 15, rows: 10, convertEol: true});
|
const termConverting = new Terminal({cols: 15, rows: 10, convertEol: true});
|
||||||
(termConverting as any)._inputHandler.parse('Hello\nWorld');
|
(termConverting as any)._inputHandler.parse('Hello\nWorld');
|
||||||
expect(termConverting.buffer.lines.get(0).translateToString(false)).equals('Hello ');
|
for (let i = 0; i < termConverting.cols; ++i) {
|
||||||
expect(termConverting.buffer.lines.get(1).translateToString(false)).equals('World ');
|
s += termConverting.buffer.lines.get(0).get(i)[CHAR_DATA_CHAR_INDEX];
|
||||||
expect(termConverting.buffer.lines.get(0).translateToString(true)).equals('Hello');
|
}
|
||||||
expect(termConverting.buffer.lines.get(1).translateToString(true)).equals('World');
|
expect(s).equals('Hello ');
|
||||||
});
|
s = '';
|
||||||
describe('print', () => {
|
for (let i = 0; i < termConverting.cols; ++i) {
|
||||||
it('should not cause an infinite loop (regression test)', () => {
|
s += termConverting.buffer.lines.get(1).get(i)[CHAR_DATA_CHAR_INDEX];
|
||||||
const term = new Terminal();
|
}
|
||||||
const inputHandler = new InputHandler(term);
|
expect(s).equals('World ');
|
||||||
inputHandler.print(String.fromCharCode(0x200B), 0, 1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('alt screen', () => {
|
|
||||||
let term: Terminal;
|
|
||||||
let handler: InputHandler;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
term = new Terminal();
|
|
||||||
handler = new InputHandler(term);
|
|
||||||
});
|
|
||||||
it('should handle DECSET/DECRST 47 (alt screen buffer)', () => {
|
|
||||||
handler.parse('\x1b[?47h\r\n\x1b[31mJUNK\x1b[?47lTEST');
|
|
||||||
expect(term.buffer.translateBufferLineToString(0, true)).to.equal('');
|
|
||||||
expect(term.buffer.translateBufferLineToString(1, true)).to.equal(' TEST');
|
|
||||||
// Text color of 'TEST' should be red
|
|
||||||
expect((term.buffer.lines.get(1).get(4)[CHAR_DATA_ATTR_INDEX] >> 9) & 0x1ff).to.equal(1);
|
|
||||||
});
|
|
||||||
it('should handle DECSET/DECRST 1047 (alt screen buffer)', () => {
|
|
||||||
handler.parse('\x1b[?1047h\r\n\x1b[31mJUNK\x1b[?1047lTEST');
|
|
||||||
expect(term.buffer.translateBufferLineToString(0, true)).to.equal('');
|
|
||||||
expect(term.buffer.translateBufferLineToString(1, true)).to.equal(' TEST');
|
|
||||||
// Text color of 'TEST' should be red
|
|
||||||
expect((term.buffer.lines.get(1).get(4)[CHAR_DATA_ATTR_INDEX] >> 9) & 0x1ff).to.equal(1);
|
|
||||||
});
|
|
||||||
it('should handle DECSET/DECRST 1048 (alt screen cursor)', () => {
|
|
||||||
handler.parse('\x1b[?1048h\r\n\x1b[31mJUNK\x1b[?1048lTEST');
|
|
||||||
expect(term.buffer.translateBufferLineToString(0, true)).to.equal('TEST');
|
|
||||||
expect(term.buffer.translateBufferLineToString(1, true)).to.equal('JUNK');
|
|
||||||
// Text color of 'TEST' should be default
|
|
||||||
expect(term.buffer.lines.get(0).get(0)[CHAR_DATA_ATTR_INDEX]).to.equal(DEFAULT_ATTR);
|
|
||||||
// Text color of 'JUNK' should be red
|
|
||||||
expect((term.buffer.lines.get(1).get(0)[CHAR_DATA_ATTR_INDEX] >> 9) & 0x1ff).to.equal(1);
|
|
||||||
});
|
|
||||||
it('should handle DECSET/DECRST 1049 (alt screen buffer+cursor)', () => {
|
|
||||||
handler.parse('\x1b[?1049h\r\n\x1b[31mJUNK\x1b[?1049lTEST');
|
|
||||||
expect(term.buffer.translateBufferLineToString(0, true)).to.equal('TEST');
|
|
||||||
expect(term.buffer.translateBufferLineToString(1, true)).to.equal('');
|
|
||||||
// Text color of 'TEST' should be default
|
|
||||||
expect(term.buffer.lines.get(0).get(0)[CHAR_DATA_ATTR_INDEX]).to.equal(DEFAULT_ATTR);
|
|
||||||
});
|
|
||||||
it('should handle DECSET/DECRST 1049 - maintains saved cursor for alt buffer', () => {
|
|
||||||
handler.parse('\x1b[?1049h\r\n\x1b[31m\x1b[s\x1b[?1049lTEST');
|
|
||||||
expect(term.buffer.translateBufferLineToString(0, true)).to.equal('TEST');
|
|
||||||
// Text color of 'TEST' should be default
|
|
||||||
expect(term.buffer.lines.get(0).get(0)[CHAR_DATA_ATTR_INDEX]).to.equal(DEFAULT_ATTR);
|
|
||||||
handler.parse('\x1b[?1049h\x1b[uTEST');
|
|
||||||
expect(term.buffer.translateBufferLineToString(1, true)).to.equal('TEST');
|
|
||||||
// Text color of 'TEST' should be red
|
|
||||||
expect((term.buffer.lines.get(1).get(0)[CHAR_DATA_ATTR_INDEX] >> 9) & 0x1ff).to.equal(1);
|
|
||||||
});
|
|
||||||
it('should handle DECSET/DECRST 1049 - clears alt buffer with erase attributes', () => {
|
|
||||||
handler.parse('\x1b[42m\x1b[?1049h');
|
|
||||||
// Buffer should be filled with green background
|
|
||||||
expect(term.buffer.lines.get(20).get(10)[CHAR_DATA_ATTR_INDEX] & 0x1ff).to.equal(2);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+63
-129
@@ -12,8 +12,8 @@ import { FLAGS } from './renderer/Types';
|
|||||||
import { wcwidth } from './CharWidth';
|
import { wcwidth } from './CharWidth';
|
||||||
import { EscapeSequenceParser } from './EscapeSequenceParser';
|
import { EscapeSequenceParser } from './EscapeSequenceParser';
|
||||||
import { ICharset } from './core/Types';
|
import { ICharset } from './core/Types';
|
||||||
import { IDisposable } from 'xterm';
|
|
||||||
import { Disposable } from './common/Lifecycle';
|
import { Disposable } from './common/Lifecycle';
|
||||||
|
import { BufferLine } from './BufferLine';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Map collect to glevel. Used in `selectCharset`.
|
* Map collect to glevel. Used in `selectCharset`.
|
||||||
@@ -25,26 +25,6 @@ const GLEVEL: {[key: string]: number} = {'(': 0, ')': 1, '*': 2, '+': 3, '-': 1,
|
|||||||
* DCS subparser implementations
|
* DCS subparser implementations
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
|
||||||
* DCS + q Pt ST (xterm)
|
|
||||||
* Request Terminfo String
|
|
||||||
* not supported
|
|
||||||
*/
|
|
||||||
class RequestTerminfo implements IDcsHandler {
|
|
||||||
private _data: string;
|
|
||||||
constructor(private _terminal: any) { }
|
|
||||||
hook(collect: string, params: number[], flag: number): void {
|
|
||||||
this._data = '';
|
|
||||||
}
|
|
||||||
put(data: string, start: number, end: number): void {
|
|
||||||
this._data += data.substring(start, end);
|
|
||||||
}
|
|
||||||
unhook(): void {
|
|
||||||
// invalid: DCS 0 + r Pt ST
|
|
||||||
this._terminal.handler(`${C0.ESC}P0+r${this._data}${C0.ESC}\\`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DCS $ q Pt ST
|
* DCS $ q Pt ST
|
||||||
* DECRQSS (https://vt100.net/docs/vt510-rm/DECRQSS.html)
|
* DECRQSS (https://vt100.net/docs/vt510-rm/DECRQSS.html)
|
||||||
@@ -87,7 +67,7 @@ class DECRQSS implements IDcsHandler {
|
|||||||
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.handler(`${C0.ESC}P0$r${C0.ESC}\\`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -114,7 +94,7 @@ class DECRQSS implements IDcsHandler {
|
|||||||
* each function's header comment.
|
* each function's header comment.
|
||||||
*/
|
*/
|
||||||
export class InputHandler extends Disposable implements IInputHandler {
|
export class InputHandler extends Disposable implements IInputHandler {
|
||||||
private _surrogateFirst: string;
|
private _surrogateHigh: string;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected _terminal: IInputHandlingTerminal,
|
protected _terminal: IInputHandlingTerminal,
|
||||||
@@ -124,7 +104,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|||||||
|
|
||||||
this.register(this._parser);
|
this.register(this._parser);
|
||||||
|
|
||||||
this._surrogateFirst = '';
|
this._surrogateHigh = '';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* custom fallback handlers
|
* custom fallback handlers
|
||||||
@@ -288,7 +268,6 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|||||||
* DCS handler
|
* DCS handler
|
||||||
*/
|
*/
|
||||||
this._parser.setDcsHandler('$q', new DECRQSS(this._terminal));
|
this._parser.setDcsHandler('$q', new DECRQSS(this._terminal));
|
||||||
this._parser.setDcsHandler('+q', new RequestTerminfo(this._terminal));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public dispose(): void {
|
public dispose(): void {
|
||||||
@@ -312,9 +291,9 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// apply leftover surrogate high from last write
|
// apply leftover surrogate high from last write
|
||||||
if (this._surrogateFirst) {
|
if (this._surrogateHigh) {
|
||||||
data = this._surrogateFirst + data;
|
data = this._surrogateHigh + data;
|
||||||
this._surrogateFirst = '';
|
this._surrogateHigh = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
this._parser.parse(data);
|
this._parser.parse(data);
|
||||||
@@ -328,6 +307,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|||||||
public print(data: string, start: number, end: number): void {
|
public print(data: string, start: number, end: number): void {
|
||||||
let char: string;
|
let char: string;
|
||||||
let code: number;
|
let code: number;
|
||||||
|
let low: number;
|
||||||
let chWidth: number;
|
let chWidth: number;
|
||||||
const buffer: IBuffer = this._terminal.buffer;
|
const buffer: IBuffer = this._terminal.buffer;
|
||||||
const charset: ICharset = this._terminal.charset;
|
const charset: ICharset = this._terminal.charset;
|
||||||
@@ -345,25 +325,20 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|||||||
|
|
||||||
// surrogate pair handling
|
// surrogate pair handling
|
||||||
if (0xD800 <= code && code <= 0xDBFF) {
|
if (0xD800 <= code && code <= 0xDBFF) {
|
||||||
if (++stringPosition >= end) {
|
// we got a surrogate high
|
||||||
// end of input:
|
// get surrogate low (next 2 bytes)
|
||||||
// handle pairs as true UTF-16 and wait for the second part
|
low = data.charCodeAt(stringPosition + 1);
|
||||||
// since we expect the input comming from a stream there is
|
if (isNaN(low)) {
|
||||||
// a small chance that the surrogate pair got split
|
// end of data stream, save surrogate high
|
||||||
// therefore we dont process the first char here, instead
|
this._surrogateHigh = char;
|
||||||
// it gets added as first char to the next processed chunk
|
|
||||||
this._surrogateFirst = char;
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const second = data.charCodeAt(stringPosition);
|
code = ((code - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000;
|
||||||
// if the second part is in surrogate pair range create the high codepoint
|
char += data.charAt(stringPosition + 1);
|
||||||
// otherwise fall back to UCS-2 behavior (handle codepoints independently)
|
}
|
||||||
if (0xDC00 <= second && second <= 0xDFFF) {
|
// surrogate low - already handled above
|
||||||
code = (code - 0xD800) * 0x400 + second - 0xDC00 + 0x10000;
|
if (0xDC00 <= code && code <= 0xDFFF) {
|
||||||
char += data.charAt(stringPosition);
|
continue;
|
||||||
} else {
|
|
||||||
stringPosition--;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// calculate print space
|
// calculate print space
|
||||||
@@ -396,12 +371,10 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|||||||
if (chMinusTwo) {
|
if (chMinusTwo) {
|
||||||
chMinusTwo[CHAR_DATA_CHAR_INDEX] += char;
|
chMinusTwo[CHAR_DATA_CHAR_INDEX] += char;
|
||||||
chMinusTwo[CHAR_DATA_CODE_INDEX] = code;
|
chMinusTwo[CHAR_DATA_CODE_INDEX] = code;
|
||||||
bufferRow.set(buffer.x - 2, chMinusTwo); // must be set explicitly now
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
chMinusOne[CHAR_DATA_CHAR_INDEX] += char;
|
chMinusOne[CHAR_DATA_CHAR_INDEX] += char;
|
||||||
chMinusOne[CHAR_DATA_CODE_INDEX] = code;
|
chMinusOne[CHAR_DATA_CODE_INDEX] = code;
|
||||||
bufferRow.set(buffer.x - 1, chMinusOne); // must be set explicitly now
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
@@ -409,9 +382,6 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|||||||
|
|
||||||
// goto next line if ch would overflow
|
// goto next line if ch would overflow
|
||||||
// TODO: needs a global min terminal width of 2
|
// TODO: needs a global min terminal width of 2
|
||||||
// FIXME: additionally ensure chWidth fits into a line
|
|
||||||
// --> maybe forbid cols<xy at higher level as it would
|
|
||||||
// introduce a bad runtime penalty here
|
|
||||||
if (buffer.x + chWidth - 1 >= cols) {
|
if (buffer.x + chWidth - 1 >= cols) {
|
||||||
// autowrap - DECAWM
|
// autowrap - DECAWM
|
||||||
// automatically wraps to the beginning of the next line
|
// automatically wraps to the beginning of the next line
|
||||||
@@ -439,15 +409,23 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// insert mode: move characters to right
|
// insert mode: move characters to right
|
||||||
|
// To achieve insert, we remove cells from the right
|
||||||
|
// and insert empty ones at cursor position
|
||||||
if (insertMode) {
|
if (insertMode) {
|
||||||
// right shift cells according to the width
|
// do this twice for a fullwidth char
|
||||||
bufferRow.insertCells(buffer.x, chWidth, [curAttr, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]);
|
for (let moves = 0; moves < chWidth; ++moves) {
|
||||||
// test last cell - since the last cell has only room for
|
// remove last cell
|
||||||
// a halfwidth char any fullwidth shifted there is lost
|
// if it's width is 0, we have to adjust the second last cell as well
|
||||||
// and will be set to eraseChar
|
const removed = bufferRow.pop();
|
||||||
const lastCell = bufferRow.get(cols - 1);
|
const chMinusTwo = bufferRow.get(buffer.x - 2);
|
||||||
if (lastCell[CHAR_DATA_WIDTH_INDEX] === 2) {
|
if (removed[CHAR_DATA_WIDTH_INDEX] === 0
|
||||||
bufferRow.set(cols - 1, [curAttr, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]);
|
&& chMinusTwo
|
||||||
|
&& chMinusTwo[CHAR_DATA_WIDTH_INDEX] === 2) {
|
||||||
|
bufferRow.set(this._terminal.cols - 2, [curAttr, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// insert empty cell at cursor
|
||||||
|
bufferRow.splice(buffer.x, 0, [curAttr, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -455,24 +433,13 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|||||||
bufferRow.set(buffer.x++, [curAttr, char, chWidth, code]);
|
bufferRow.set(buffer.x++, [curAttr, char, chWidth, code]);
|
||||||
|
|
||||||
// fullwidth char - also set next cell to placeholder stub and advance cursor
|
// fullwidth char - also set next cell to placeholder stub and advance cursor
|
||||||
// for graphemes bigger than fullwidth we can simply loop to zero
|
if (chWidth === 2) {
|
||||||
// we already made sure above, that buffer.x + chWidth will not overflow right
|
bufferRow.set(buffer.x++, [curAttr, '', 0, undefined]);
|
||||||
if (chWidth > 0) {
|
|
||||||
while (--chWidth) {
|
|
||||||
bufferRow.set(buffer.x++, [curAttr, '', 0, undefined]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this._terminal.updateRange(buffer.y);
|
this._terminal.updateRange(buffer.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): IDisposable {
|
|
||||||
return this._parser.addCsiHandler(flag, callback);
|
|
||||||
}
|
|
||||||
addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable {
|
|
||||||
return this._parser.addOscHandler(ident, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* BEL
|
* BEL
|
||||||
* Bell (Ctrl-G).
|
* Bell (Ctrl-G).
|
||||||
@@ -734,25 +701,12 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|||||||
* @param start first cell index to be erased
|
* @param start first cell index to be erased
|
||||||
* @param end end - 1 is last erased cell
|
* @param end end - 1 is last erased cell
|
||||||
*/
|
*/
|
||||||
private _eraseInBufferLine(y: number, start: number, end: number, clearWrap: boolean = false): void {
|
private _eraseInBufferLine(y: number, start: number, end: number): void {
|
||||||
const line = this._terminal.buffer.lines.get(this._terminal.buffer.ybase + y);
|
this._terminal.buffer.lines.get(this._terminal.buffer.ybase + y).replaceCells(
|
||||||
line.replaceCells(
|
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
[this._terminal.eraseAttr(), NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]
|
[this._terminal.eraseAttr(), NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]
|
||||||
);
|
);
|
||||||
if (clearWrap) {
|
|
||||||
line.isWrapped = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper method to reset cells in a terminal row.
|
|
||||||
* The cell gets replaced with the eraseChar of the terminal and the isWrapped property is set to false.
|
|
||||||
* @param y row index
|
|
||||||
*/
|
|
||||||
private _resetBufferLine(y: number): void {
|
|
||||||
this._eraseInBufferLine(y, 0, this._terminal.cols, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -773,23 +727,18 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|||||||
case 0:
|
case 0:
|
||||||
j = this._terminal.buffer.y;
|
j = this._terminal.buffer.y;
|
||||||
this._terminal.updateRange(j);
|
this._terminal.updateRange(j);
|
||||||
this._eraseInBufferLine(j++, this._terminal.buffer.x, this._terminal.cols, this._terminal.buffer.x === 0);
|
this._eraseInBufferLine(j++, this._terminal.buffer.x, this._terminal.cols);
|
||||||
for (; j < this._terminal.rows; j++) {
|
for (; j < this._terminal.rows; j++) {
|
||||||
this._resetBufferLine(j);
|
this._eraseInBufferLine(j, 0, this._terminal.cols);
|
||||||
}
|
}
|
||||||
this._terminal.updateRange(j);
|
this._terminal.updateRange(j);
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
j = this._terminal.buffer.y;
|
j = this._terminal.buffer.y;
|
||||||
this._terminal.updateRange(j);
|
this._terminal.updateRange(j);
|
||||||
// Deleted front part of line and everything before. This line will no longer be wrapped.
|
this._eraseInBufferLine(j, 0, this._terminal.buffer.x + 1);
|
||||||
this._eraseInBufferLine(j, 0, this._terminal.buffer.x + 1, true);
|
|
||||||
if (this._terminal.buffer.x + 1 >= this._terminal.cols) {
|
|
||||||
// Deleted entire previous line. This next line can no longer be wrapped.
|
|
||||||
this._terminal.buffer.lines.get(j + 1).isWrapped = false;
|
|
||||||
}
|
|
||||||
while (j--) {
|
while (j--) {
|
||||||
this._resetBufferLine(j);
|
this._eraseInBufferLine(j, 0, this._terminal.cols);
|
||||||
}
|
}
|
||||||
this._terminal.updateRange(0);
|
this._terminal.updateRange(0);
|
||||||
break;
|
break;
|
||||||
@@ -797,7 +746,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|||||||
j = this._terminal.rows;
|
j = this._terminal.rows;
|
||||||
this._terminal.updateRange(j - 1);
|
this._terminal.updateRange(j - 1);
|
||||||
while (j--) {
|
while (j--) {
|
||||||
this._resetBufferLine(j);
|
this._eraseInBufferLine(j, 0, this._terminal.cols);
|
||||||
}
|
}
|
||||||
this._terminal.updateRange(0);
|
this._terminal.updateRange(0);
|
||||||
break;
|
break;
|
||||||
@@ -862,7 +811,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|||||||
// test: echo -e '\e[44m\e[1L\e[0m'
|
// test: echo -e '\e[44m\e[1L\e[0m'
|
||||||
// blankLine(true) - xterm/linux behavior
|
// blankLine(true) - xterm/linux behavior
|
||||||
buffer.lines.splice(scrollBottomAbsolute - 1, 1);
|
buffer.lines.splice(scrollBottomAbsolute - 1, 1);
|
||||||
buffer.lines.splice(row, 0, buffer.getBlankLine(this._terminal.eraseAttr()));
|
buffer.lines.splice(row, 0, BufferLine.blankLine(this._terminal.cols, this._terminal.eraseAttr()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// this.maxRange();
|
// this.maxRange();
|
||||||
@@ -892,7 +841,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|||||||
// test: echo -e '\e[44m\e[1M\e[0m'
|
// test: echo -e '\e[44m\e[1M\e[0m'
|
||||||
// blankLine(true) - xterm/linux behavior
|
// blankLine(true) - xterm/linux behavior
|
||||||
buffer.lines.splice(row, 1);
|
buffer.lines.splice(row, 1);
|
||||||
buffer.lines.splice(j, 0, buffer.getBlankLine(this._terminal.eraseAttr()));
|
buffer.lines.splice(j, 0, BufferLine.blankLine(this._terminal.cols, this._terminal.eraseAttr()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// this.maxRange();
|
// this.maxRange();
|
||||||
@@ -924,7 +873,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|||||||
|
|
||||||
while (param--) {
|
while (param--) {
|
||||||
buffer.lines.splice(buffer.ybase + buffer.scrollTop, 1);
|
buffer.lines.splice(buffer.ybase + buffer.scrollTop, 1);
|
||||||
buffer.lines.splice(buffer.ybase + buffer.scrollBottom, 0, buffer.getBlankLine(DEFAULT_ATTR));
|
buffer.lines.splice(buffer.ybase + buffer.scrollBottom, 0, BufferLine.blankLine(this._terminal.cols, DEFAULT_ATTR));
|
||||||
}
|
}
|
||||||
// this.maxRange();
|
// this.maxRange();
|
||||||
this._terminal.updateRange(buffer.scrollTop);
|
this._terminal.updateRange(buffer.scrollTop);
|
||||||
@@ -943,7 +892,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|||||||
|
|
||||||
while (param--) {
|
while (param--) {
|
||||||
buffer.lines.splice(buffer.ybase + buffer.scrollBottom, 1);
|
buffer.lines.splice(buffer.ybase + buffer.scrollBottom, 1);
|
||||||
buffer.lines.splice(buffer.ybase + buffer.scrollBottom, 0, buffer.getBlankLine(DEFAULT_ATTR));
|
buffer.lines.splice(buffer.ybase + buffer.scrollBottom, 0, BufferLine.blankLine(this._terminal.cols, DEFAULT_ATTR));
|
||||||
}
|
}
|
||||||
// this.maxRange();
|
// this.maxRange();
|
||||||
this._terminal.updateRange(buffer.scrollTop);
|
this._terminal.updateRange(buffer.scrollTop);
|
||||||
@@ -1292,9 +1241,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|||||||
case 66:
|
case 66:
|
||||||
this._terminal.log('Serial port requested application keypad.');
|
this._terminal.log('Serial port requested application keypad.');
|
||||||
this._terminal.applicationKeypad = true;
|
this._terminal.applicationKeypad = true;
|
||||||
if (this._terminal.viewport) {
|
this._terminal.viewport.syncScrollArea();
|
||||||
this._terminal.viewport.syncScrollArea();
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case 9: // X10 Mouse
|
case 9: // X10 Mouse
|
||||||
// no release, no motion, no wheel, no modifiers.
|
// no release, no motion, no wheel, no modifiers.
|
||||||
@@ -1343,19 +1290,14 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|||||||
case 25: // show cursor
|
case 25: // show cursor
|
||||||
this._terminal.cursorHidden = false;
|
this._terminal.cursorHidden = false;
|
||||||
break;
|
break;
|
||||||
case 1048: // alt screen cursor
|
|
||||||
this.saveCursor(params);
|
|
||||||
break;
|
|
||||||
case 1049: // alt screen buffer cursor
|
case 1049: // alt screen buffer cursor
|
||||||
this.saveCursor(params);
|
// TODO: Not sure if we need to save/restore after switching the buffer
|
||||||
|
// this.saveCursor(params);
|
||||||
// FALL-THROUGH
|
// FALL-THROUGH
|
||||||
case 47: // alt screen buffer
|
case 47: // alt screen buffer
|
||||||
case 1047: // alt screen buffer
|
case 1047: // alt screen buffer
|
||||||
this._terminal.buffers.activateAltBuffer(this._terminal.eraseAttr());
|
this._terminal.buffers.activateAltBuffer();
|
||||||
this._terminal.refresh(0, this._terminal.rows - 1);
|
this._terminal.viewport.syncScrollArea();
|
||||||
if (this._terminal.viewport) {
|
|
||||||
this._terminal.viewport.syncScrollArea();
|
|
||||||
}
|
|
||||||
this._terminal.showCursor();
|
this._terminal.showCursor();
|
||||||
break;
|
break;
|
||||||
case 2004: // bracketed paste mode (https://cirw.in/blog/bracketed-paste)
|
case 2004: // bracketed paste mode (https://cirw.in/blog/bracketed-paste)
|
||||||
@@ -1488,9 +1430,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|||||||
case 66:
|
case 66:
|
||||||
this._terminal.log('Switching back to normal keypad.');
|
this._terminal.log('Switching back to normal keypad.');
|
||||||
this._terminal.applicationKeypad = false;
|
this._terminal.applicationKeypad = false;
|
||||||
if (this._terminal.viewport) {
|
this._terminal.viewport.syncScrollArea();
|
||||||
this._terminal.viewport.syncScrollArea();
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case 9: // X10 Mouse
|
case 9: // X10 Mouse
|
||||||
case 1000: // vt200 mouse
|
case 1000: // vt200 mouse
|
||||||
@@ -1518,22 +1458,18 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|||||||
case 25: // hide cursor
|
case 25: // hide cursor
|
||||||
this._terminal.cursorHidden = true;
|
this._terminal.cursorHidden = true;
|
||||||
break;
|
break;
|
||||||
case 1048: // alt screen cursor
|
|
||||||
this.restoreCursor(params);
|
|
||||||
break;
|
|
||||||
case 1049: // alt screen buffer cursor
|
case 1049: // alt screen buffer cursor
|
||||||
// FALL-THROUGH
|
// FALL-THROUGH
|
||||||
case 47: // normal screen buffer
|
case 47: // normal screen buffer
|
||||||
case 1047: // normal screen buffer - clearing it first
|
case 1047: // normal screen buffer - clearing it first
|
||||||
// Ensure the selection manager has the correct buffer
|
// Ensure the selection manager has the correct buffer
|
||||||
this._terminal.buffers.activateNormalBuffer();
|
this._terminal.buffers.activateNormalBuffer();
|
||||||
if (params[0] === 1049) {
|
// TODO: Not sure if we need to save/restore after switching the buffer
|
||||||
this.restoreCursor(params);
|
// if (params[0] === 1049) {
|
||||||
}
|
// this.restoreCursor(params);
|
||||||
|
// }
|
||||||
this._terminal.refresh(0, this._terminal.rows - 1);
|
this._terminal.refresh(0, this._terminal.rows - 1);
|
||||||
if (this._terminal.viewport) {
|
this._terminal.viewport.syncScrollArea();
|
||||||
this._terminal.viewport.syncScrollArea();
|
|
||||||
}
|
|
||||||
this._terminal.showCursor();
|
this._terminal.showCursor();
|
||||||
break;
|
break;
|
||||||
case 2004: // bracketed paste mode (https://cirw.in/blog/bracketed-paste)
|
case 2004: // bracketed paste mode (https://cirw.in/blog/bracketed-paste)
|
||||||
@@ -1812,9 +1748,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|||||||
this._terminal.originMode = false;
|
this._terminal.originMode = false;
|
||||||
this._terminal.wraparoundMode = true; // defaults: xterm - true, vt100 - false
|
this._terminal.wraparoundMode = true; // defaults: xterm - true, vt100 - false
|
||||||
this._terminal.applicationKeypad = false; // ?
|
this._terminal.applicationKeypad = false; // ?
|
||||||
if (this._terminal.viewport) {
|
this._terminal.viewport.syncScrollArea();
|
||||||
this._terminal.viewport.syncScrollArea();
|
|
||||||
}
|
|
||||||
this._terminal.applicationCursor = false;
|
this._terminal.applicationCursor = false;
|
||||||
this._terminal.buffer.scrollTop = 0;
|
this._terminal.buffer.scrollTop = 0;
|
||||||
this._terminal.buffer.scrollBottom = this._terminal.rows - 1;
|
this._terminal.buffer.scrollBottom = this._terminal.rows - 1;
|
||||||
@@ -1881,7 +1815,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|||||||
public saveCursor(params: number[]): void {
|
public saveCursor(params: number[]): void {
|
||||||
this._terminal.buffer.savedX = this._terminal.buffer.x;
|
this._terminal.buffer.savedX = this._terminal.buffer.x;
|
||||||
this._terminal.buffer.savedY = this._terminal.buffer.y;
|
this._terminal.buffer.savedY = this._terminal.buffer.y;
|
||||||
this._terminal.buffer.savedCurAttr = this._terminal.curAttr;
|
this._terminal.savedCurAttr = this._terminal.curAttr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1893,7 +1827,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|||||||
public restoreCursor(params: number[]): void {
|
public restoreCursor(params: number[]): void {
|
||||||
this._terminal.buffer.x = this._terminal.buffer.savedX || 0;
|
this._terminal.buffer.x = this._terminal.buffer.savedX || 0;
|
||||||
this._terminal.buffer.y = this._terminal.buffer.savedY || 0;
|
this._terminal.buffer.y = this._terminal.buffer.savedY || 0;
|
||||||
this._terminal.curAttr = this._terminal.buffer.savedCurAttr || DEFAULT_ATTR;
|
this._terminal.curAttr = this._terminal.savedCurAttr || DEFAULT_ATTR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { assert } from 'chai';
|
|||||||
import { IMouseZoneManager, IMouseZone } from './ui/Types';
|
import { IMouseZoneManager, IMouseZone } from './ui/Types';
|
||||||
import { ILinkMatcher, ITerminal, IBufferLine } from './Types';
|
import { ILinkMatcher, ITerminal, IBufferLine } from './Types';
|
||||||
import { Linkifier } from './Linkifier';
|
import { Linkifier } from './Linkifier';
|
||||||
import { MockBuffer, MockTerminal, TestTerminal } from './ui/TestUtils.test';
|
import { MockBuffer, MockTerminal, TestTerminal } from './utils/TestUtils.test';
|
||||||
import { CircularList } from './common/CircularList';
|
import { CircularList } from './common/CircularList';
|
||||||
import { BufferLine } from './BufferLine';
|
import { BufferLine } from './BufferLine';
|
||||||
|
|
||||||
@@ -51,9 +51,9 @@ describe('Linkifier', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
function stringToRow(text: string): IBufferLine {
|
function stringToRow(text: string): IBufferLine {
|
||||||
const result = new BufferLine(text.length);
|
const result = new BufferLine();
|
||||||
for (let i = 0; i < text.length; i++) {
|
for (let i = 0; i < text.length; i++) {
|
||||||
result.set(i, [0, text.charAt(i), 1, text.charCodeAt(i)]);
|
result.push([0, text.charAt(i), 1, text.charCodeAt(i)]);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -219,17 +219,9 @@ export class Linkifier extends EventEmitter implements ILinkifier {
|
|||||||
// also correct regex and string search offsets for the next loop run
|
// also correct regex and string search offsets for the next loop run
|
||||||
stringIndex = text.indexOf(uri, stringIndex + 1);
|
stringIndex = text.indexOf(uri, stringIndex + 1);
|
||||||
rex.lastIndex = stringIndex + uri.length;
|
rex.lastIndex = stringIndex + uri.length;
|
||||||
if (stringIndex < 0) {
|
|
||||||
// invalid stringIndex (should not have happened)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the buffer index as [absolute row, col] for the match
|
// get the buffer index as [absolute row, col] for the match
|
||||||
const bufferIndex = this._terminal.buffer.stringIndexToBufferIndex(rowIndex, stringIndex);
|
const bufferIndex = this._terminal.buffer.stringIndexToBufferIndex(rowIndex, stringIndex);
|
||||||
if (bufferIndex[0] < 0) {
|
|
||||||
// invalid bufferIndex (should not have happened)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
const line = this._terminal.buffer.lines.get(bufferIndex[0]);
|
const line = this._terminal.buffer.lines.get(bufferIndex[0]);
|
||||||
const char = line.get(bufferIndex[1]);
|
const char = line.get(bufferIndex[1]);
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { SelectionManager, SelectionMode } from './SelectionManager';
|
|||||||
import { SelectionModel } from './SelectionModel';
|
import { SelectionModel } from './SelectionModel';
|
||||||
import { BufferSet } from './BufferSet';
|
import { BufferSet } from './BufferSet';
|
||||||
import { ITerminal, IBuffer, IBufferLine } from './Types';
|
import { ITerminal, IBuffer, IBufferLine } from './Types';
|
||||||
import { MockTerminal } from './ui/TestUtils.test';
|
import { MockTerminal } from './utils/TestUtils.test';
|
||||||
import { BufferLine } from './BufferLine';
|
import { BufferLine } from './BufferLine';
|
||||||
|
|
||||||
class TestMockTerminal extends MockTerminal {
|
class TestMockTerminal extends MockTerminal {
|
||||||
@@ -30,7 +30,6 @@ class TestSelectionManager extends SelectionManager {
|
|||||||
|
|
||||||
public selectLineAt(line: number): void { this._selectLineAt(line); }
|
public selectLineAt(line: number): void { this._selectLineAt(line); }
|
||||||
public selectWordAt(coords: [number, number]): void { this._selectWordAt(coords, true); }
|
public selectWordAt(coords: [number, number]): void { this._selectWordAt(coords, true); }
|
||||||
public areCoordsInSelection(coords: [number, number], start: [number, number], end: [number, number]): boolean { return this._areCoordsInSelection(coords, start, end); }
|
|
||||||
|
|
||||||
// Disable DOM interaction
|
// Disable DOM interaction
|
||||||
public enable(): void {}
|
public enable(): void {}
|
||||||
@@ -55,16 +54,16 @@ describe('SelectionManager', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
function stringToRow(text: string): IBufferLine {
|
function stringToRow(text: string): IBufferLine {
|
||||||
const result = new BufferLine(text.length);
|
const result = new BufferLine();
|
||||||
for (let i = 0; i < text.length; i++) {
|
for (let i = 0; i < text.length; i++) {
|
||||||
result.set(i, [0, text.charAt(i), 1, text.charCodeAt(i)]);
|
result.push([0, text.charAt(i), 1, text.charCodeAt(i)]);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
function stringArrayToRow(chars: string[]): IBufferLine {
|
function stringArrayToRow(chars: string[]): IBufferLine {
|
||||||
const line = new BufferLine(chars.length);
|
const line = new BufferLine();
|
||||||
chars.map((c, idx) => line.set(idx, [0, c, 1, c.charCodeAt(0)]));
|
chars.map(c => line.push([0, c, 1, c.charCodeAt(0)]));
|
||||||
return line;
|
return line;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,6 +100,7 @@ describe('SelectionManager', () => {
|
|||||||
});
|
});
|
||||||
it('should expand selection for wide characters', () => {
|
it('should expand selection for wide characters', () => {
|
||||||
// Wide characters use a special format
|
// Wide characters use a special format
|
||||||
|
const line = new BufferLine();
|
||||||
const data: [number, string, number, number][] = [
|
const data: [number, string, number, number][] = [
|
||||||
[null, '中', 2, '中'.charCodeAt(0)],
|
[null, '中', 2, '中'.charCodeAt(0)],
|
||||||
[null, '', 0, null],
|
[null, '', 0, null],
|
||||||
@@ -118,8 +118,7 @@ describe('SelectionManager', () => {
|
|||||||
[null, 'o', 1, 'o'.charCodeAt(0)],
|
[null, 'o', 1, 'o'.charCodeAt(0)],
|
||||||
[null, 'o', 1, 'o'.charCodeAt(0)]
|
[null, 'o', 1, 'o'.charCodeAt(0)]
|
||||||
];
|
];
|
||||||
const line = new BufferLine(data.length);
|
for (let i = 0; i < data.length; ++i) line.push(data[i]);
|
||||||
for (let i = 0; i < data.length; ++i) line.set(i, data[i]);
|
|
||||||
buffer.lines.set(0, line);
|
buffer.lines.set(0, line);
|
||||||
// Ensure wide characters take up 2 columns
|
// Ensure wide characters take up 2 columns
|
||||||
selectionManager.selectWordAt([0, 0]);
|
selectionManager.selectWordAt([0, 0]);
|
||||||
@@ -479,17 +478,5 @@ describe('SelectionManager', () => {
|
|||||||
assert.equal(selectionManager.selectionText, 'a\n😁\nc');
|
assert.equal(selectionManager.selectionText, 'a\n😁\nc');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('_areCoordsInSelection', () => {
|
|
||||||
it('should return whether coords are in the selection', () => {
|
|
||||||
assert.isFalse(selectionManager.areCoordsInSelection([0, 0], [2, 0], [2, 1]));
|
|
||||||
assert.isFalse(selectionManager.areCoordsInSelection([1, 0], [2, 0], [2, 1]));
|
|
||||||
assert.isTrue(selectionManager.areCoordsInSelection([2, 0], [2, 0], [2, 1]));
|
|
||||||
assert.isTrue(selectionManager.areCoordsInSelection([10, 0], [2, 0], [2, 1]));
|
|
||||||
assert.isTrue(selectionManager.areCoordsInSelection([0, 1], [2, 0], [2, 1]));
|
|
||||||
assert.isTrue(selectionManager.areCoordsInSelection([1, 1], [2, 0], [2, 1]));
|
|
||||||
assert.isFalse(selectionManager.areCoordsInSelection([2, 1], [2, 0], [2, 1]));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+6
-11
@@ -5,8 +5,8 @@
|
|||||||
|
|
||||||
import { ITerminal, ISelectionManager, IBuffer, CharData, IBufferLine } from './Types';
|
import { ITerminal, ISelectionManager, IBuffer, CharData, IBufferLine } from './Types';
|
||||||
import { XtermListener } from './common/Types';
|
import { XtermListener } from './common/Types';
|
||||||
import { MouseHelper } from './ui/MouseHelper';
|
import { MouseHelper } from './utils/MouseHelper';
|
||||||
import * as Browser from './core/Platform';
|
import * as Browser from './shared/utils/Browser';
|
||||||
import { CharMeasure } from './ui/CharMeasure';
|
import { CharMeasure } from './ui/CharMeasure';
|
||||||
import { EventEmitter } from './common/EventEmitter';
|
import { EventEmitter } from './common/EventEmitter';
|
||||||
import { SelectionModel } from './SelectionModel';
|
import { SelectionModel } from './SelectionModel';
|
||||||
@@ -198,7 +198,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Get first row
|
// Get first row
|
||||||
const startRowEndCol = start[1] === end[1] ? end[0] : undefined;
|
const startRowEndCol = start[1] === end[1] ? end[0] : null;
|
||||||
result.push(this._buffer.translateBufferLineToString(start[1], true, start[0], startRowEndCol));
|
result.push(this._buffer.translateBufferLineToString(start[1], true, start[0], startRowEndCol));
|
||||||
|
|
||||||
// Get middle rows
|
// Get middle rows
|
||||||
@@ -289,14 +289,9 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this._areCoordsInSelection(coords, start, end);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected _areCoordsInSelection(coords: [number, number], start: [number, number], end: [number, number]): boolean {
|
|
||||||
return (coords[1] > start[1] && coords[1] < end[1]) ||
|
return (coords[1] > start[1] && coords[1] < end[1]) ||
|
||||||
(start[1] === end[1] && coords[1] === start[1] && coords[0] >= start[0] && coords[0] < end[0]) ||
|
(start[1] === end[1] && coords[1] === start[1] && coords[0] > start[0] && coords[0] < end[0]) ||
|
||||||
(start[1] < end[1] && coords[1] === end[1] && coords[0] < end[0]) ||
|
(start[1] < end[1] && coords[1] === end[1] && coords[0] < end[0]);
|
||||||
(start[1] < end[1] && coords[1] === start[1] && coords[0] >= start[0]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -347,7 +342,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
|
|||||||
* @param event The mouse event.
|
* @param event The mouse event.
|
||||||
*/
|
*/
|
||||||
private _getMouseBufferCoords(event: MouseEvent): [number, number] {
|
private _getMouseBufferCoords(event: MouseEvent): [number, number] {
|
||||||
const coords = this._terminal.mouseHelper.getCoords(event, this._terminal.screenElement, this._charMeasure, this._terminal.cols, this._terminal.rows, true);
|
const coords = this._terminal.mouseHelper.getCoords(event, this._terminal.screenElement, this._charMeasure, this._terminal.options.lineHeight, this._terminal.cols, this._terminal.rows, true);
|
||||||
if (!coords) {
|
if (!coords) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { assert } from 'chai';
|
|||||||
import { ITerminal } from './Types';
|
import { ITerminal } from './Types';
|
||||||
import { SelectionModel } from './SelectionModel';
|
import { SelectionModel } from './SelectionModel';
|
||||||
import { BufferSet } from './BufferSet';
|
import { BufferSet } from './BufferSet';
|
||||||
import { MockTerminal } from './ui/TestUtils.test';
|
import { MockTerminal } from './utils/TestUtils.test';
|
||||||
|
|
||||||
class TestSelectionModel extends SelectionModel {
|
class TestSelectionModel extends SelectionModel {
|
||||||
constructor(
|
constructor(
|
||||||
|
|||||||
+16
-22
@@ -12,19 +12,7 @@ import { ITerminal, ISoundManager } from './Types';
|
|||||||
export const DEFAULT_BELL_SOUND = 'data:audio/wav;base64,UklGRigBAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQQBAADpAFgCwAMlBZoG/wdmCcoKRAypDQ8PbRDBEQQTOxRtFYcWlBePGIUZXhoiG88bcBz7HHIdzh0WHlMeZx51HmkeUx4WHs8dah0AHXwc3hs9G4saxRnyGBIYGBcQFv8U4RPAEoYRQBACD70NWwwHC6gJOwjWBloF7gOBAhABkf8b/qv8R/ve+Xf4Ife79W/0JfPZ8Z/wde9N7ijtE+wU6xvqM+lb6H7nw+YX5mrlxuQz5Mzje+Ma49fioeKD4nXiYeJy4pHitOL04j/jn+MN5IPkFOWs5U3mDefM55/ogOl36m7rdOyE7abuyu8D8Unyj/Pg9D/2qfcb+Yn6/vuK/Qj/lAAlAg==';
|
export const DEFAULT_BELL_SOUND = 'data:audio/wav;base64,UklGRigBAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQQBAADpAFgCwAMlBZoG/wdmCcoKRAypDQ8PbRDBEQQTOxRtFYcWlBePGIUZXhoiG88bcBz7HHIdzh0WHlMeZx51HmkeUx4WHs8dah0AHXwc3hs9G4saxRnyGBIYGBcQFv8U4RPAEoYRQBACD70NWwwHC6gJOwjWBloF7gOBAhABkf8b/qv8R/ve+Xf4Ife79W/0JfPZ8Z/wde9N7ijtE+wU6xvqM+lb6H7nw+YX5mrlxuQz5Mzje+Ma49fioeKD4nXiYeJy4pHitOL04j/jn+MN5IPkFOWs5U3mDefM55/ogOl36m7rdOyE7abuyu8D8Unyj/Pg9D/2qfcb+Yn6/vuK/Qj/lAAlAg==';
|
||||||
|
|
||||||
export class SoundManager implements ISoundManager {
|
export class SoundManager implements ISoundManager {
|
||||||
private static _audioContext: AudioContext;
|
private _audioContext: AudioContext;
|
||||||
|
|
||||||
static get audioContext(): AudioContext | null {
|
|
||||||
if (!SoundManager._audioContext) {
|
|
||||||
const audioContextCtor: typeof AudioContext = (<any>window).AudioContext || (<any>window).webkitAudioContext;
|
|
||||||
if (!audioContextCtor) {
|
|
||||||
console.warn('Web Audio API is not supported by this browser. Consider upgrading to the latest version');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
SoundManager._audioContext = new audioContextCtor();
|
|
||||||
}
|
|
||||||
return SoundManager._audioContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private _terminal: ITerminal
|
private _terminal: ITerminal
|
||||||
@@ -32,16 +20,22 @@ export class SoundManager implements ISoundManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public playBellSound(): void {
|
public playBellSound(): void {
|
||||||
const ctx = SoundManager.audioContext;
|
const audioContextCtor: typeof AudioContext = (<any>window).AudioContext || (<any>window).webkitAudioContext;
|
||||||
if (!ctx) {
|
if (!this._audioContext && audioContextCtor) {
|
||||||
return;
|
this._audioContext = new audioContextCtor();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._audioContext) {
|
||||||
|
const bellAudioSource = this._audioContext.createBufferSource();
|
||||||
|
const context = this._audioContext;
|
||||||
|
this._audioContext.decodeAudioData(this._base64ToArrayBuffer(this._removeMimeType(this._terminal.options.bellSound)), (buffer) => {
|
||||||
|
bellAudioSource.buffer = buffer;
|
||||||
|
bellAudioSource.connect(context.destination);
|
||||||
|
bellAudioSource.start(0);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.warn('Sorry, but the Web Audio API is not supported by your browser. Please, consider upgrading to the latest version');
|
||||||
}
|
}
|
||||||
const bellAudioSource = ctx.createBufferSource();
|
|
||||||
ctx.decodeAudioData(this._base64ToArrayBuffer(this._removeMimeType(this._terminal.options.bellSound)), (buffer) => {
|
|
||||||
bellAudioSource.buffer = buffer;
|
|
||||||
bellAudioSource.connect(ctx.destination);
|
|
||||||
bellAudioSource.start(0);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _base64ToArrayBuffer(base64: string): ArrayBuffer {
|
private _base64ToArrayBuffer(base64: string): ArrayBuffer {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import * as path from 'path';
|
|||||||
import * as pty from 'node-pty';
|
import * as pty from 'node-pty';
|
||||||
import { assert } from 'chai';
|
import { assert } from 'chai';
|
||||||
import { Terminal } from './Terminal';
|
import { Terminal } from './Terminal';
|
||||||
import { CHAR_DATA_CHAR_INDEX, WHITESPACE_CELL_CHAR } from './Buffer';
|
import { CHAR_DATA_CHAR_INDEX } from './Buffer';
|
||||||
import { IViewport } from './Types';
|
import { IViewport } from './Types';
|
||||||
|
|
||||||
class TestTerminal extends Terminal {
|
class TestTerminal extends Terminal {
|
||||||
@@ -67,7 +67,7 @@ function terminalToString(term: Terminal): string {
|
|||||||
for (let line = term.buffer.ybase; line < term.buffer.ybase + term.rows; line++) {
|
for (let line = term.buffer.ybase; line < term.buffer.ybase + term.rows; line++) {
|
||||||
lineText = '';
|
lineText = '';
|
||||||
for (let cell = 0; cell < term.cols; ++cell) {
|
for (let cell = 0; cell < term.cols; ++cell) {
|
||||||
lineText += term.buffer.lines.get(line).get(cell)[CHAR_DATA_CHAR_INDEX] || WHITESPACE_CELL_CHAR;
|
lineText += term.buffer.lines.get(line).get(cell)[CHAR_DATA_CHAR_INDEX];
|
||||||
}
|
}
|
||||||
// rtrim empty cells as xterm does
|
// rtrim empty cells as xterm does
|
||||||
lineText = lineText.replace(/\s+$/, '');
|
lineText = lineText.replace(/\s+$/, '');
|
||||||
|
|||||||
+57
-56
@@ -5,8 +5,9 @@
|
|||||||
|
|
||||||
import { assert, expect } from 'chai';
|
import { assert, expect } from 'chai';
|
||||||
import { Terminal } from './Terminal';
|
import { Terminal } from './Terminal';
|
||||||
import { MockViewport, MockCompositionHelper, MockRenderer } from './ui/TestUtils.test';
|
import { MockViewport, MockCompositionHelper, MockRenderer } from './utils/TestUtils.test';
|
||||||
import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, DEFAULT_ATTR } from './Buffer';
|
import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, DEFAULT_ATTR } from './Buffer';
|
||||||
|
import { BufferLine } from './BufferLine';
|
||||||
|
|
||||||
const INIT_COLS = 80;
|
const INIT_COLS = 80;
|
||||||
const INIT_ROWS = 24;
|
const INIT_ROWS = 24;
|
||||||
@@ -259,7 +260,7 @@ describe('term.js addons', () => {
|
|||||||
assert.equal(term.buffer.lines.length, term.rows);
|
assert.equal(term.buffer.lines.length, term.rows);
|
||||||
assert.deepEqual(term.buffer.lines.get(0), promptLine);
|
assert.deepEqual(term.buffer.lines.get(0), promptLine);
|
||||||
for (let i = 1; i < term.rows; i++) {
|
for (let i = 1; i < term.rows; i++) {
|
||||||
assert.deepEqual(term.buffer.lines.get(i), term.buffer.getBlankLine(DEFAULT_ATTR));
|
assert.deepEqual(term.buffer.lines.get(i), BufferLine.blankLine(term.cols, DEFAULT_ATTR));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
it('should clear a buffer larger than rows', () => {
|
it('should clear a buffer larger than rows', () => {
|
||||||
@@ -276,7 +277,7 @@ describe('term.js addons', () => {
|
|||||||
assert.equal(term.buffer.lines.length, term.rows);
|
assert.equal(term.buffer.lines.length, term.rows);
|
||||||
assert.deepEqual(term.buffer.lines.get(0), promptLine);
|
assert.deepEqual(term.buffer.lines.get(0), promptLine);
|
||||||
for (let i = 1; i < term.rows; i++) {
|
for (let i = 1; i < term.rows; i++) {
|
||||||
assert.deepEqual(term.buffer.lines.get(i), term.buffer.getBlankLine(DEFAULT_ATTR));
|
assert.deepEqual(term.buffer.lines.get(i), BufferLine.blankLine(term.cols, DEFAULT_ATTR));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
it('should not break the prompt when cleared twice', () => {
|
it('should not break the prompt when cleared twice', () => {
|
||||||
@@ -289,7 +290,7 @@ describe('term.js addons', () => {
|
|||||||
assert.equal(term.buffer.lines.length, term.rows);
|
assert.equal(term.buffer.lines.length, term.rows);
|
||||||
assert.deepEqual(term.buffer.lines.get(0), promptLine);
|
assert.deepEqual(term.buffer.lines.get(0), promptLine);
|
||||||
for (let i = 1; i < term.rows; i++) {
|
for (let i = 1; i < term.rows; i++) {
|
||||||
assert.deepEqual(term.buffer.lines.get(i), term.buffer.getBlankLine(DEFAULT_ATTR));
|
assert.deepEqual(term.buffer.lines.get(i), BufferLine.blankLine(term.cols, DEFAULT_ATTR));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -455,20 +456,20 @@ describe('term.js addons', () => {
|
|||||||
describe('scroll() function', () => {
|
describe('scroll() function', () => {
|
||||||
describe('when scrollback > 0', () => {
|
describe('when scrollback > 0', () => {
|
||||||
it('should create a new line and scroll', () => {
|
it('should create a new line and scroll', () => {
|
||||||
term.buffer.lines.get(0).set(0, [0, 'a', 0, 'a'.charCodeAt(0)]);
|
term.buffer.lines.get(0).get(0)[CHAR_DATA_CHAR_INDEX] = 'a';
|
||||||
term.buffer.lines.get(INIT_ROWS - 1).set(0, [0, 'b', 0, 'b'.charCodeAt(0)]);
|
term.buffer.lines.get(INIT_ROWS - 1).get(0)[CHAR_DATA_CHAR_INDEX] = 'b';
|
||||||
term.buffer.y = INIT_ROWS - 1; // Move cursor to last line
|
term.buffer.y = INIT_ROWS - 1; // Move cursor to last line
|
||||||
term.scroll();
|
term.scroll();
|
||||||
assert.equal(term.buffer.lines.length, INIT_ROWS + 1);
|
assert.equal(term.buffer.lines.length, INIT_ROWS + 1);
|
||||||
assert.equal(term.buffer.lines.get(0).get(0)[CHAR_DATA_CHAR_INDEX], 'a');
|
assert.equal(term.buffer.lines.get(0).get(0)[CHAR_DATA_CHAR_INDEX], 'a');
|
||||||
assert.equal(term.buffer.lines.get(INIT_ROWS - 1).get(0)[CHAR_DATA_CHAR_INDEX], 'b');
|
assert.equal(term.buffer.lines.get(INIT_ROWS - 1).get(0)[CHAR_DATA_CHAR_INDEX], 'b');
|
||||||
assert.equal(term.buffer.lines.get(INIT_ROWS).get(0)[CHAR_DATA_CHAR_INDEX], '');
|
assert.equal(term.buffer.lines.get(INIT_ROWS).get(0)[CHAR_DATA_CHAR_INDEX], ' ');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should properly scroll inside a scroll region (scrollTop set)', () => {
|
it('should properly scroll inside a scroll region (scrollTop set)', () => {
|
||||||
term.buffer.lines.get(0).set(0, [0, 'a', 0, 'a'.charCodeAt(0)]);
|
term.buffer.lines.get(0).get(0)[CHAR_DATA_CHAR_INDEX] = 'a';
|
||||||
term.buffer.lines.get(1).set(0, [0, 'b', 0, 'b'.charCodeAt(0)]);
|
term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX] = 'b';
|
||||||
term.buffer.lines.get(2).set(0, [0, 'c', 0, 'c'.charCodeAt(0)]);
|
term.buffer.lines.get(2).get(0)[CHAR_DATA_CHAR_INDEX] = 'c';
|
||||||
term.buffer.y = INIT_ROWS - 1; // Move cursor to last line
|
term.buffer.y = INIT_ROWS - 1; // Move cursor to last line
|
||||||
term.buffer.scrollTop = 1;
|
term.buffer.scrollTop = 1;
|
||||||
term.scroll();
|
term.scroll();
|
||||||
@@ -478,11 +479,11 @@ describe('term.js addons', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should properly scroll inside a scroll region (scrollBottom set)', () => {
|
it('should properly scroll inside a scroll region (scrollBottom set)', () => {
|
||||||
term.buffer.lines.get(0).set(0, [0, 'a', 0, 'a'.charCodeAt(0)]);
|
term.buffer.lines.get(0).get(0)[CHAR_DATA_CHAR_INDEX] = 'a';
|
||||||
term.buffer.lines.get(1).set(0, [0, 'b', 0, 'b'.charCodeAt(0)]);
|
term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX] = 'b';
|
||||||
term.buffer.lines.get(2).set(0, [0, 'c', 0, 'c'.charCodeAt(0)]);
|
term.buffer.lines.get(2).get(0)[CHAR_DATA_CHAR_INDEX] = 'c';
|
||||||
term.buffer.lines.get(3).set(0, [0, 'd', 0, 'd'.charCodeAt(0)]);
|
term.buffer.lines.get(3).get(0)[CHAR_DATA_CHAR_INDEX] = 'd';
|
||||||
term.buffer.lines.get(4).set(0, [0, 'e', 0, 'e'.charCodeAt(0)]);
|
term.buffer.lines.get(4).get(0)[CHAR_DATA_CHAR_INDEX] = 'e';
|
||||||
term.buffer.y = 3;
|
term.buffer.y = 3;
|
||||||
term.buffer.scrollBottom = 3;
|
term.buffer.scrollBottom = 3;
|
||||||
term.scroll();
|
term.scroll();
|
||||||
@@ -491,16 +492,16 @@ describe('term.js addons', () => {
|
|||||||
assert.equal(term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX], 'b');
|
assert.equal(term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX], 'b');
|
||||||
assert.equal(term.buffer.lines.get(2).get(0)[CHAR_DATA_CHAR_INDEX], 'c');
|
assert.equal(term.buffer.lines.get(2).get(0)[CHAR_DATA_CHAR_INDEX], 'c');
|
||||||
assert.equal(term.buffer.lines.get(3).get(0)[CHAR_DATA_CHAR_INDEX], 'd');
|
assert.equal(term.buffer.lines.get(3).get(0)[CHAR_DATA_CHAR_INDEX], 'd');
|
||||||
assert.equal(term.buffer.lines.get(4).get(0)[CHAR_DATA_CHAR_INDEX], '', 'a blank line should be added at scrollBottom\'s index');
|
assert.equal(term.buffer.lines.get(4).get(0)[CHAR_DATA_CHAR_INDEX], ' ', 'a blank line should be added at scrollBottom\'s index');
|
||||||
assert.equal(term.buffer.lines.get(5).get(0)[CHAR_DATA_CHAR_INDEX], 'e');
|
assert.equal(term.buffer.lines.get(5).get(0)[CHAR_DATA_CHAR_INDEX], 'e');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should properly scroll inside a scroll region (scrollTop and scrollBottom set)', () => {
|
it('should properly scroll inside a scroll region (scrollTop and scrollBottom set)', () => {
|
||||||
term.buffer.lines.get(0).set(0, [0, 'a', 0, 'a'.charCodeAt(0)]);
|
term.buffer.lines.get(0).get(0)[CHAR_DATA_CHAR_INDEX] = 'a';
|
||||||
term.buffer.lines.get(1).set(0, [0, 'b', 0, 'b'.charCodeAt(0)]);
|
term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX] = 'b';
|
||||||
term.buffer.lines.get(2).set(0, [0, 'c', 0, 'c'.charCodeAt(0)]);
|
term.buffer.lines.get(2).get(0)[CHAR_DATA_CHAR_INDEX] = 'c';
|
||||||
term.buffer.lines.get(3).set(0, [0, 'd', 0, 'd'.charCodeAt(0)]);
|
term.buffer.lines.get(3).get(0)[CHAR_DATA_CHAR_INDEX] = 'd';
|
||||||
term.buffer.lines.get(4).set(0, [0, 'e', 0, 'e'.charCodeAt(0)]);
|
term.buffer.lines.get(4).get(0)[CHAR_DATA_CHAR_INDEX] = 'e';
|
||||||
term.buffer.y = INIT_ROWS - 1; // Move cursor to last line
|
term.buffer.y = INIT_ROWS - 1; // Move cursor to last line
|
||||||
term.buffer.scrollTop = 1;
|
term.buffer.scrollTop = 1;
|
||||||
term.buffer.scrollBottom = 3;
|
term.buffer.scrollBottom = 3;
|
||||||
@@ -509,7 +510,7 @@ describe('term.js addons', () => {
|
|||||||
assert.equal(term.buffer.lines.get(0).get(0)[CHAR_DATA_CHAR_INDEX], 'a');
|
assert.equal(term.buffer.lines.get(0).get(0)[CHAR_DATA_CHAR_INDEX], 'a');
|
||||||
assert.equal(term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX], 'c', '\'b\' should be removed from the buffer');
|
assert.equal(term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX], 'c', '\'b\' should be removed from the buffer');
|
||||||
assert.equal(term.buffer.lines.get(2).get(0)[CHAR_DATA_CHAR_INDEX], 'd');
|
assert.equal(term.buffer.lines.get(2).get(0)[CHAR_DATA_CHAR_INDEX], 'd');
|
||||||
assert.equal(term.buffer.lines.get(3).get(0)[CHAR_DATA_CHAR_INDEX], '', 'a blank line should be added at scrollBottom\'s index');
|
assert.equal(term.buffer.lines.get(3).get(0)[CHAR_DATA_CHAR_INDEX], ' ', 'a blank line should be added at scrollBottom\'s index');
|
||||||
assert.equal(term.buffer.lines.get(4).get(0)[CHAR_DATA_CHAR_INDEX], 'e');
|
assert.equal(term.buffer.lines.get(4).get(0)[CHAR_DATA_CHAR_INDEX], 'e');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -521,24 +522,24 @@ describe('term.js addons', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should create a new line and shift everything up', () => {
|
it('should create a new line and shift everything up', () => {
|
||||||
term.buffer.lines.get(0).set(0, [0, 'a', 0, 'a'.charCodeAt(0)]);
|
term.buffer.lines.get(0).get(0)[CHAR_DATA_CHAR_INDEX] = 'a';
|
||||||
term.buffer.lines.get(1).set(0, [0, 'b', 0, 'b'.charCodeAt(0)]);
|
term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX] = 'b';
|
||||||
term.buffer.lines.get(INIT_ROWS - 1).set(0, [0, 'c', 0, 'c'.charCodeAt(0)]);
|
term.buffer.lines.get(INIT_ROWS - 1).get(0)[CHAR_DATA_CHAR_INDEX] = 'c';
|
||||||
term.buffer.y = INIT_ROWS - 1; // Move cursor to last line
|
term.buffer.y = INIT_ROWS - 1; // Move cursor to last line
|
||||||
assert.equal(term.buffer.lines.length, INIT_ROWS);
|
assert.equal(term.buffer.lines.length, INIT_ROWS);
|
||||||
term.scroll();
|
term.scroll();
|
||||||
assert.equal(term.buffer.lines.length, INIT_ROWS);
|
assert.equal(term.buffer.lines.length, INIT_ROWS);
|
||||||
// 'a' gets pushed out of buffer
|
// 'a' gets pushed out of buffer
|
||||||
assert.equal(term.buffer.lines.get(0).get(0)[CHAR_DATA_CHAR_INDEX], 'b');
|
assert.equal(term.buffer.lines.get(0).get(0)[CHAR_DATA_CHAR_INDEX], 'b');
|
||||||
assert.equal(term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX], '');
|
assert.equal(term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX], ' ');
|
||||||
assert.equal(term.buffer.lines.get(INIT_ROWS - 2).get(0)[CHAR_DATA_CHAR_INDEX], 'c');
|
assert.equal(term.buffer.lines.get(INIT_ROWS - 2).get(0)[CHAR_DATA_CHAR_INDEX], 'c');
|
||||||
assert.equal(term.buffer.lines.get(INIT_ROWS - 1).get(0)[CHAR_DATA_CHAR_INDEX], '');
|
assert.equal(term.buffer.lines.get(INIT_ROWS - 1).get(0)[CHAR_DATA_CHAR_INDEX], ' ');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should properly scroll inside a scroll region (scrollTop set)', () => {
|
it('should properly scroll inside a scroll region (scrollTop set)', () => {
|
||||||
term.buffer.lines.get(0).set(0, [0, 'a', 0, 'a'.charCodeAt(0)]);
|
term.buffer.lines.get(0).get(0)[CHAR_DATA_CHAR_INDEX] = 'a';
|
||||||
term.buffer.lines.get(1).set(0, [0, 'b', 0, 'b'.charCodeAt(0)]);
|
term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX] = 'b';
|
||||||
term.buffer.lines.get(2).set(0, [0, 'c', 0, 'c'.charCodeAt(0)]);
|
term.buffer.lines.get(2).get(0)[CHAR_DATA_CHAR_INDEX] = 'c';
|
||||||
term.buffer.y = INIT_ROWS - 1; // Move cursor to last line
|
term.buffer.y = INIT_ROWS - 1; // Move cursor to last line
|
||||||
term.buffer.scrollTop = 1;
|
term.buffer.scrollTop = 1;
|
||||||
term.scroll();
|
term.scroll();
|
||||||
@@ -548,11 +549,11 @@ describe('term.js addons', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should properly scroll inside a scroll region (scrollBottom set)', () => {
|
it('should properly scroll inside a scroll region (scrollBottom set)', () => {
|
||||||
term.buffer.lines.get(0).set(0, [0, 'a', 0, 'a'.charCodeAt(0)]);
|
term.buffer.lines.get(0).get(0)[CHAR_DATA_CHAR_INDEX] = 'a';
|
||||||
term.buffer.lines.get(1).set(0, [0, 'b', 0, 'b'.charCodeAt(0)]);
|
term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX] = 'b';
|
||||||
term.buffer.lines.get(2).set(0, [0, 'c', 0, 'c'.charCodeAt(0)]);
|
term.buffer.lines.get(2).get(0)[CHAR_DATA_CHAR_INDEX] = 'c';
|
||||||
term.buffer.lines.get(3).set(0, [0, 'd', 0, 'd'.charCodeAt(0)]);
|
term.buffer.lines.get(3).get(0)[CHAR_DATA_CHAR_INDEX] = 'd';
|
||||||
term.buffer.lines.get(4).set(0, [0, 'e', 0, 'e'.charCodeAt(0)]);
|
term.buffer.lines.get(4).get(0)[CHAR_DATA_CHAR_INDEX] = 'e';
|
||||||
term.buffer.y = 3;
|
term.buffer.y = 3;
|
||||||
term.buffer.scrollBottom = 3;
|
term.buffer.scrollBottom = 3;
|
||||||
term.scroll();
|
term.scroll();
|
||||||
@@ -560,16 +561,16 @@ describe('term.js addons', () => {
|
|||||||
assert.equal(term.buffer.lines.get(0).get(0)[CHAR_DATA_CHAR_INDEX], 'b');
|
assert.equal(term.buffer.lines.get(0).get(0)[CHAR_DATA_CHAR_INDEX], 'b');
|
||||||
assert.equal(term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX], 'c');
|
assert.equal(term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX], 'c');
|
||||||
assert.equal(term.buffer.lines.get(2).get(0)[CHAR_DATA_CHAR_INDEX], 'd');
|
assert.equal(term.buffer.lines.get(2).get(0)[CHAR_DATA_CHAR_INDEX], 'd');
|
||||||
assert.equal(term.buffer.lines.get(3).get(0)[CHAR_DATA_CHAR_INDEX], '', 'a blank line should be added at scrollBottom\'s index');
|
assert.equal(term.buffer.lines.get(3).get(0)[CHAR_DATA_CHAR_INDEX], ' ', 'a blank line should be added at scrollBottom\'s index');
|
||||||
assert.equal(term.buffer.lines.get(4).get(0)[CHAR_DATA_CHAR_INDEX], 'e');
|
assert.equal(term.buffer.lines.get(4).get(0)[CHAR_DATA_CHAR_INDEX], 'e');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should properly scroll inside a scroll region (scrollTop and scrollBottom set)', () => {
|
it('should properly scroll inside a scroll region (scrollTop and scrollBottom set)', () => {
|
||||||
term.buffer.lines.get(0).set(0, [0, 'a', 0, 'a'.charCodeAt(0)]);
|
term.buffer.lines.get(0).get(0)[CHAR_DATA_CHAR_INDEX] = 'a';
|
||||||
term.buffer.lines.get(1).set(0, [0, 'b', 0, 'b'.charCodeAt(0)]);
|
term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX] = 'b';
|
||||||
term.buffer.lines.get(2).set(0, [0, 'c', 0, 'c'.charCodeAt(0)]);
|
term.buffer.lines.get(2).get(0)[CHAR_DATA_CHAR_INDEX] = 'c';
|
||||||
term.buffer.lines.get(3).set(0, [0, 'd', 0, 'd'.charCodeAt(0)]);
|
term.buffer.lines.get(3).get(0)[CHAR_DATA_CHAR_INDEX] = 'd';
|
||||||
term.buffer.lines.get(4).set(0, [0, 'e', 0, 'e'.charCodeAt(0)]);
|
term.buffer.lines.get(4).get(0)[CHAR_DATA_CHAR_INDEX] = 'e';
|
||||||
term.buffer.y = INIT_ROWS - 1; // Move cursor to last line
|
term.buffer.y = INIT_ROWS - 1; // Move cursor to last line
|
||||||
term.buffer.scrollTop = 1;
|
term.buffer.scrollTop = 1;
|
||||||
term.buffer.scrollBottom = 3;
|
term.buffer.scrollBottom = 3;
|
||||||
@@ -578,7 +579,7 @@ describe('term.js addons', () => {
|
|||||||
assert.equal(term.buffer.lines.get(0).get(0)[CHAR_DATA_CHAR_INDEX], 'a');
|
assert.equal(term.buffer.lines.get(0).get(0)[CHAR_DATA_CHAR_INDEX], 'a');
|
||||||
assert.equal(term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX], 'c', '\'b\' should be removed from the buffer');
|
assert.equal(term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX], 'c', '\'b\' should be removed from the buffer');
|
||||||
assert.equal(term.buffer.lines.get(2).get(0)[CHAR_DATA_CHAR_INDEX], 'd');
|
assert.equal(term.buffer.lines.get(2).get(0)[CHAR_DATA_CHAR_INDEX], 'd');
|
||||||
assert.equal(term.buffer.lines.get(3).get(0)[CHAR_DATA_CHAR_INDEX], '', 'a blank line should be added at scrollBottom\'s index');
|
assert.equal(term.buffer.lines.get(3).get(0)[CHAR_DATA_CHAR_INDEX], ' ', 'a blank line should be added at scrollBottom\'s index');
|
||||||
assert.equal(term.buffer.lines.get(4).get(0)[CHAR_DATA_CHAR_INDEX], 'e');
|
assert.equal(term.buffer.lines.get(4).get(0)[CHAR_DATA_CHAR_INDEX], 'e');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -776,7 +777,7 @@ describe('term.js addons', () => {
|
|||||||
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql(high + String.fromCharCode(i));
|
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql(high + String.fromCharCode(i));
|
||||||
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(2);
|
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(2);
|
||||||
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1);
|
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1);
|
||||||
expect(term.buffer.lines.get(0).get(1)[CHAR_DATA_CHAR_INDEX]).eql('');
|
expect(term.buffer.lines.get(0).get(1)[CHAR_DATA_CHAR_INDEX]).eql(' ');
|
||||||
term.reset();
|
term.reset();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -787,7 +788,7 @@ describe('term.js addons', () => {
|
|||||||
term.write(high + String.fromCharCode(i));
|
term.write(high + String.fromCharCode(i));
|
||||||
expect(term.buffer.lines.get(0).get(term.buffer.x - 1)[CHAR_DATA_CHAR_INDEX]).eql(high + String.fromCharCode(i));
|
expect(term.buffer.lines.get(0).get(term.buffer.x - 1)[CHAR_DATA_CHAR_INDEX]).eql(high + String.fromCharCode(i));
|
||||||
expect(term.buffer.lines.get(0).get(term.buffer.x - 1)[CHAR_DATA_CHAR_INDEX].length).eql(2);
|
expect(term.buffer.lines.get(0).get(term.buffer.x - 1)[CHAR_DATA_CHAR_INDEX].length).eql(2);
|
||||||
expect(term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX]).eql('');
|
expect(term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX]).eql(' ');
|
||||||
term.reset();
|
term.reset();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -800,7 +801,7 @@ describe('term.js addons', () => {
|
|||||||
expect(term.buffer.lines.get(0).get(term.cols - 1)[CHAR_DATA_CHAR_INDEX]).eql('a');
|
expect(term.buffer.lines.get(0).get(term.cols - 1)[CHAR_DATA_CHAR_INDEX]).eql('a');
|
||||||
expect(term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX]).eql(high + String.fromCharCode(i));
|
expect(term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX]).eql(high + String.fromCharCode(i));
|
||||||
expect(term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX].length).eql(2);
|
expect(term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX].length).eql(2);
|
||||||
expect(term.buffer.lines.get(1).get(1)[CHAR_DATA_CHAR_INDEX]).eql('');
|
expect(term.buffer.lines.get(1).get(1)[CHAR_DATA_CHAR_INDEX]).eql(' ');
|
||||||
term.reset();
|
term.reset();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -813,7 +814,7 @@ describe('term.js addons', () => {
|
|||||||
// auto wraparound mode should cut off the rest of the line
|
// auto wraparound mode should cut off the rest of the line
|
||||||
expect(term.buffer.lines.get(0).get(term.cols - 1)[CHAR_DATA_CHAR_INDEX]).eql('a');
|
expect(term.buffer.lines.get(0).get(term.cols - 1)[CHAR_DATA_CHAR_INDEX]).eql('a');
|
||||||
expect(term.buffer.lines.get(0).get(term.cols - 1)[CHAR_DATA_CHAR_INDEX].length).eql(1);
|
expect(term.buffer.lines.get(0).get(term.cols - 1)[CHAR_DATA_CHAR_INDEX].length).eql(1);
|
||||||
expect(term.buffer.lines.get(1).get(1)[CHAR_DATA_CHAR_INDEX]).eql('');
|
expect(term.buffer.lines.get(1).get(1)[CHAR_DATA_CHAR_INDEX]).eql(' ');
|
||||||
term.reset();
|
term.reset();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -826,7 +827,7 @@ describe('term.js addons', () => {
|
|||||||
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql(high + String.fromCharCode(i));
|
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql(high + String.fromCharCode(i));
|
||||||
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(2);
|
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(2);
|
||||||
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1);
|
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1);
|
||||||
expect(term.buffer.lines.get(0).get(1)[CHAR_DATA_CHAR_INDEX]).eql('');
|
expect(term.buffer.lines.get(0).get(1)[CHAR_DATA_CHAR_INDEX]).eql(' ');
|
||||||
term.reset();
|
term.reset();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -845,8 +846,8 @@ describe('term.js addons', () => {
|
|||||||
expect(term.buffer.lines.get(0).get(term.cols - 1)[CHAR_DATA_CHAR_INDEX]).eql('e\u0301');
|
expect(term.buffer.lines.get(0).get(term.cols - 1)[CHAR_DATA_CHAR_INDEX]).eql('e\u0301');
|
||||||
expect(term.buffer.lines.get(0).get(term.cols - 1)[CHAR_DATA_CHAR_INDEX].length).eql(2);
|
expect(term.buffer.lines.get(0).get(term.cols - 1)[CHAR_DATA_CHAR_INDEX].length).eql(2);
|
||||||
expect(term.buffer.lines.get(0).get(term.cols - 1)[CHAR_DATA_WIDTH_INDEX]).eql(1);
|
expect(term.buffer.lines.get(0).get(term.cols - 1)[CHAR_DATA_WIDTH_INDEX]).eql(1);
|
||||||
expect(term.buffer.lines.get(0).get(1)[CHAR_DATA_CHAR_INDEX]).eql('');
|
expect(term.buffer.lines.get(0).get(1)[CHAR_DATA_CHAR_INDEX]).eql(' ');
|
||||||
expect(term.buffer.lines.get(0).get(1)[CHAR_DATA_CHAR_INDEX].length).eql(0);
|
expect(term.buffer.lines.get(0).get(1)[CHAR_DATA_CHAR_INDEX].length).eql(1);
|
||||||
expect(term.buffer.lines.get(0).get(1)[CHAR_DATA_WIDTH_INDEX]).eql(1);
|
expect(term.buffer.lines.get(0).get(1)[CHAR_DATA_WIDTH_INDEX]).eql(1);
|
||||||
});
|
});
|
||||||
it('multiple combined é', () => {
|
it('multiple combined é', () => {
|
||||||
@@ -928,8 +929,8 @@ describe('term.js addons', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
let tchar = term.buffer.lines.get(0).get(term.cols - 1);
|
let tchar = term.buffer.lines.get(0).get(term.cols - 1);
|
||||||
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('');
|
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql(' ');
|
||||||
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(0);
|
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(1);
|
||||||
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1);
|
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1);
|
||||||
tchar = term.buffer.lines.get(1).get(0);
|
tchar = term.buffer.lines.get(1).get(0);
|
||||||
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('¥');
|
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('¥');
|
||||||
@@ -953,8 +954,8 @@ describe('term.js addons', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
let tchar = term.buffer.lines.get(0).get(term.cols - 1);
|
let tchar = term.buffer.lines.get(0).get(term.cols - 1);
|
||||||
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('');
|
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql(' ');
|
||||||
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(0);
|
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(1);
|
||||||
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1);
|
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1);
|
||||||
tchar = term.buffer.lines.get(1).get(0);
|
tchar = term.buffer.lines.get(1).get(0);
|
||||||
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('¥\u0301');
|
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('¥\u0301');
|
||||||
@@ -998,8 +999,8 @@ describe('term.js addons', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
let tchar = term.buffer.lines.get(0).get(term.cols - 1);
|
let tchar = term.buffer.lines.get(0).get(term.cols - 1);
|
||||||
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('');
|
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql(' ');
|
||||||
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(0);
|
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(1);
|
||||||
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1);
|
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1);
|
||||||
tchar = term.buffer.lines.get(1).get(0);
|
tchar = term.buffer.lines.get(1).get(0);
|
||||||
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('\ud843\ude6d\u0301');
|
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('\ud843\ude6d\u0301');
|
||||||
@@ -1063,7 +1064,7 @@ describe('term.js addons', () => {
|
|||||||
expect(term.buffer.lines.get(0).length).eql(term.cols);
|
expect(term.buffer.lines.get(0).length).eql(term.cols);
|
||||||
expect(term.buffer.lines.get(0).get(10)[CHAR_DATA_CHAR_INDEX]).eql('a');
|
expect(term.buffer.lines.get(0).get(10)[CHAR_DATA_CHAR_INDEX]).eql('a');
|
||||||
expect(term.buffer.lines.get(0).get(11)[CHAR_DATA_CHAR_INDEX]).eql('¥');
|
expect(term.buffer.lines.get(0).get(11)[CHAR_DATA_CHAR_INDEX]).eql('¥');
|
||||||
expect(term.buffer.lines.get(0).get(79)[CHAR_DATA_CHAR_INDEX]).eql(''); // fullwidth char got replaced
|
expect(term.buffer.lines.get(0).get(79)[CHAR_DATA_CHAR_INDEX]).eql(' '); // fullwidth char got replaced
|
||||||
term.write('b');
|
term.write('b');
|
||||||
expect(term.buffer.lines.get(0).length).eql(term.cols);
|
expect(term.buffer.lines.get(0).length).eql(term.cols);
|
||||||
expect(term.buffer.lines.get(0).get(11)[CHAR_DATA_CHAR_INDEX]).eql('b');
|
expect(term.buffer.lines.get(0).get(11)[CHAR_DATA_CHAR_INDEX]).eql('b');
|
||||||
|
|||||||
+23
-61
@@ -21,25 +21,26 @@
|
|||||||
* 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, CharacterJoinerHandler, IBufferLine } from './Types';
|
import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminalOptions, ITerminal, IBrowser, ILinkifier, ILinkMatcherOptions, CustomKeyEventHandler, LinkMatcherHandler, CharData, CharacterJoinerHandler } 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, CHAR_DATA_ATTR_INDEX } from './Buffer';
|
import { Buffer, MAX_BUFFER_SIZE, DEFAULT_ATTR, NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR } from './Buffer';
|
||||||
import { CompositionHelper } from './CompositionHelper';
|
import { CompositionHelper } from './CompositionHelper';
|
||||||
import { EventEmitter } from './common/EventEmitter';
|
import { EventEmitter } from './common/EventEmitter';
|
||||||
import { Viewport } from './Viewport';
|
import { Viewport } from './Viewport';
|
||||||
import { rightClickHandler, moveTextAreaUnderMouseCursor, pasteHandler, copyHandler } from './ui/Clipboard';
|
import { rightClickHandler, moveTextAreaUnderMouseCursor, pasteHandler, copyHandler } from './handlers/Clipboard';
|
||||||
import { C0 } from './common/data/EscapeSequences';
|
import { C0 } from './common/data/EscapeSequences';
|
||||||
import { InputHandler } from './InputHandler';
|
import { InputHandler } from './InputHandler';
|
||||||
import { Renderer } from './renderer/Renderer';
|
import { Renderer } from './renderer/Renderer';
|
||||||
import { Linkifier } from './Linkifier';
|
import { Linkifier } from './Linkifier';
|
||||||
import { SelectionManager } from './SelectionManager';
|
import { SelectionManager } from './SelectionManager';
|
||||||
import { CharMeasure } from './ui/CharMeasure';
|
import { CharMeasure } from './ui/CharMeasure';
|
||||||
import * as Browser from './core/Platform';
|
import * as Browser from './shared/utils/Browser';
|
||||||
import { addDisposableDomListener } from './ui/Lifecycle';
|
import { addDisposableDomListener } from './ui/Lifecycle';
|
||||||
import * as Strings from './Strings';
|
import * as Strings from './Strings';
|
||||||
import { MouseHelper } from './ui/MouseHelper';
|
import { MouseHelper } from './utils/MouseHelper';
|
||||||
|
import { clone } from './utils/Clone';
|
||||||
import { DEFAULT_BELL_SOUND, SoundManager } from './SoundManager';
|
import { DEFAULT_BELL_SOUND, SoundManager } from './SoundManager';
|
||||||
import { DEFAULT_ANSI_COLORS } from './renderer/ColorManager';
|
import { DEFAULT_ANSI_COLORS } from './renderer/ColorManager';
|
||||||
import { MouseZoneManager } from './ui/MouseZoneManager';
|
import { MouseZoneManager } from './ui/MouseZoneManager';
|
||||||
@@ -51,7 +52,7 @@ import { DomRenderer } from './renderer/dom/DomRenderer';
|
|||||||
import { IKeyboardEvent } from './common/Types';
|
import { IKeyboardEvent } from './common/Types';
|
||||||
import { evaluateKeyboardEvent } from './core/input/Keyboard';
|
import { evaluateKeyboardEvent } from './core/input/Keyboard';
|
||||||
import { KeyboardResultType, ICharset } from './core/Types';
|
import { KeyboardResultType, ICharset } from './core/Types';
|
||||||
import { clone } from './common/Clone';
|
import { BufferLine } from './BufferLine';
|
||||||
|
|
||||||
// Let it work inside Node.js for automated testing purposes.
|
// Let it work inside Node.js for automated testing purposes.
|
||||||
const document = (typeof window !== 'undefined') ? window.document : null;
|
const document = (typeof window !== 'undefined') ? window.document : null;
|
||||||
@@ -105,8 +106,7 @@ const DEFAULT_OPTIONS: ITerminalOptions = {
|
|||||||
tabStopWidth: 8,
|
tabStopWidth: 8,
|
||||||
theme: null,
|
theme: null,
|
||||||
rightClickSelectsWord: Browser.isMac,
|
rightClickSelectsWord: Browser.isMac,
|
||||||
rendererType: 'canvas',
|
rendererType: 'canvas'
|
||||||
experimentalBufferLineImpl: 'TypedArray'
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export class Terminal extends EventEmitter implements ITerminal, IDisposable, IInputHandlingTerminal {
|
export class Terminal extends EventEmitter implements ITerminal, IDisposable, IInputHandlingTerminal {
|
||||||
@@ -170,6 +170,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
|||||||
public savedCols: number;
|
public savedCols: number;
|
||||||
|
|
||||||
public curAttr: number;
|
public curAttr: number;
|
||||||
|
public savedCurAttr: number;
|
||||||
|
|
||||||
public params: (string | number)[];
|
public params: (string | number)[];
|
||||||
public currentParam: string | number;
|
public currentParam: string | number;
|
||||||
@@ -207,9 +208,6 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
|||||||
private _screenDprMonitor: ScreenDprMonitor;
|
private _screenDprMonitor: ScreenDprMonitor;
|
||||||
private _theme: ITheme;
|
private _theme: ITheme;
|
||||||
|
|
||||||
// bufferline to clone/copy from for new blank lines
|
|
||||||
private _blankLine: IBufferLine = null;
|
|
||||||
|
|
||||||
public cols: number;
|
public cols: number;
|
||||||
public rows: number;
|
public rows: number;
|
||||||
|
|
||||||
@@ -344,7 +342,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
|||||||
}
|
}
|
||||||
|
|
||||||
public get isFocused(): boolean {
|
public get isFocused(): boolean {
|
||||||
return document.activeElement === this.textarea && document.hasFocus();
|
return document.activeElement === this.textarea;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -464,7 +462,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);
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
case 'rendererType':
|
case 'rendererType':
|
||||||
if (this.renderer) {
|
if (this.renderer) {
|
||||||
this.unregister(this.renderer);
|
this.unregister(this.renderer);
|
||||||
@@ -476,7 +473,6 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
|||||||
if (this._theme) {
|
if (this._theme) {
|
||||||
this.renderer.setTheme(this._theme);
|
this.renderer.setTheme(this._theme);
|
||||||
}
|
}
|
||||||
this.mouseHelper.setRenderer(this.renderer);
|
|
||||||
break;
|
break;
|
||||||
case 'scrollback':
|
case 'scrollback':
|
||||||
this.buffers.resize(this.cols, this.rows);
|
this.buffers.resize(this.cols, this.rows);
|
||||||
@@ -497,11 +493,6 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'tabStopWidth': this.buffers.setupTabStops(); break;
|
case 'tabStopWidth': this.buffers.setupTabStops(); break;
|
||||||
case 'experimentalBufferLineImpl':
|
|
||||||
this.buffers.normal.setBufferLineFactory(value);
|
|
||||||
this.buffers.alt.setBufferLineFactory(value);
|
|
||||||
this._blankLine = null;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
// Inform renderer of changes
|
// Inform renderer of changes
|
||||||
if (this.renderer) {
|
if (this.renderer) {
|
||||||
@@ -808,7 +799,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
|||||||
button = getButton(ev);
|
button = getButton(ev);
|
||||||
|
|
||||||
// get mouse coordinates
|
// get mouse coordinates
|
||||||
pos = self.mouseHelper.getRawByteCoords(ev, self.screenElement, self.charMeasure, self.cols, self.rows);
|
pos = self.mouseHelper.getRawByteCoords(ev, self.screenElement, self.charMeasure, self.options.lineHeight, self.cols, self.rows);
|
||||||
if (!pos) return;
|
if (!pos) return;
|
||||||
|
|
||||||
sendEvent(button, pos);
|
sendEvent(button, pos);
|
||||||
@@ -834,7 +825,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
|||||||
// ^[[M 3<^[[M@4<^[[M@5<^[[M@6<^[[M@7<^[[M#7<
|
// ^[[M 3<^[[M@4<^[[M@5<^[[M@6<^[[M@7<^[[M#7<
|
||||||
function sendMove(ev: MouseEvent): void {
|
function sendMove(ev: MouseEvent): void {
|
||||||
let button = pressed;
|
let button = pressed;
|
||||||
const pos = self.mouseHelper.getRawByteCoords(ev, self.screenElement, self.charMeasure, self.cols, self.rows);
|
const pos = self.mouseHelper.getRawByteCoords(ev, self.screenElement, self.charMeasure, self.options.lineHeight, self.cols, self.rows);
|
||||||
if (!pos) return;
|
if (!pos) return;
|
||||||
|
|
||||||
// buttons marked as motions
|
// buttons marked as motions
|
||||||
@@ -989,7 +980,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
|||||||
: 65;
|
: 65;
|
||||||
break;
|
break;
|
||||||
case 'wheel':
|
case 'wheel':
|
||||||
button = (<WheelEvent>ev).deltaY < 0
|
button = (<WheelEvent>ev).wheelDeltaY > 0
|
||||||
? 64
|
? 64
|
||||||
: 65;
|
: 65;
|
||||||
break;
|
break;
|
||||||
@@ -1158,9 +1149,9 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
|||||||
*/
|
*/
|
||||||
public updateCursorStyle(ev: KeyboardEvent): void {
|
public updateCursorStyle(ev: KeyboardEvent): void {
|
||||||
if (this.selectionManager && this.selectionManager.shouldColumnSelect(ev)) {
|
if (this.selectionManager && this.selectionManager.shouldColumnSelect(ev)) {
|
||||||
this.element.classList.add('column-select');
|
this.element.classList.add('xterm-cursor-crosshair');
|
||||||
} else {
|
} else {
|
||||||
this.element.classList.remove('column-select');
|
this.element.classList.remove('xterm-cursor-crosshair');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1178,40 +1169,20 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
|||||||
* Scroll the terminal down 1 row, creating a blank line.
|
* Scroll the terminal down 1 row, creating a blank line.
|
||||||
* @param isWrapped Whether the new line is wrapped from the previous line.
|
* @param isWrapped Whether the new line is wrapped from the previous line.
|
||||||
*/
|
*/
|
||||||
public scroll(isWrapped: boolean = false): void {
|
public scroll(isWrapped?: boolean): void {
|
||||||
let newLine: IBufferLine;
|
const newLine = BufferLine.blankLine(this.cols, DEFAULT_ATTR, isWrapped);
|
||||||
const useRecycling = this.options.experimentalBufferLineImpl !== 'JsArray';
|
|
||||||
if (useRecycling) {
|
|
||||||
newLine = this._blankLine;
|
|
||||||
if (!newLine || newLine.length !== this.cols || newLine.get(0)[CHAR_DATA_ATTR_INDEX] !== this.eraseAttr()) {
|
|
||||||
newLine = this.buffer.getBlankLine(this.eraseAttr(), isWrapped);
|
|
||||||
this._blankLine = newLine;
|
|
||||||
}
|
|
||||||
newLine.isWrapped = isWrapped;
|
|
||||||
} else {
|
|
||||||
newLine = this.buffer.getBlankLine(this.eraseAttr(), isWrapped);
|
|
||||||
}
|
|
||||||
|
|
||||||
const topRow = this.buffer.ybase + this.buffer.scrollTop;
|
const topRow = this.buffer.ybase + this.buffer.scrollTop;
|
||||||
const bottomRow = this.buffer.ybase + this.buffer.scrollBottom;
|
const bottomRow = this.buffer.ybase + this.buffer.scrollBottom;
|
||||||
|
|
||||||
if (this.buffer.scrollTop === 0) {
|
if (this.buffer.scrollTop === 0) {
|
||||||
// Determine whether the buffer is going to be trimmed after insertion.
|
// Determine whether the buffer is going to be trimmed after insertion.
|
||||||
const willBufferBeTrimmed = this.buffer.lines.isFull;
|
const willBufferBeTrimmed = this.buffer.lines.length === this.buffer.lines.maxLength;
|
||||||
|
|
||||||
// Insert the line using the fastest method
|
// Insert the line using the fastest method
|
||||||
if (bottomRow === this.buffer.lines.length - 1) {
|
if (bottomRow === this.buffer.lines.length - 1) {
|
||||||
if (useRecycling) {
|
this.buffer.lines.push(newLine);
|
||||||
if (willBufferBeTrimmed) {
|
|
||||||
this.buffer.lines.recycle().copyFrom(newLine);
|
|
||||||
} else {
|
|
||||||
this.buffer.lines.push(newLine.clone());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.buffer.lines.push(newLine);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
this.buffer.lines.splice(bottomRow + 1, 0, (useRecycling) ? newLine.clone() : newLine);
|
this.buffer.lines.splice(bottomRow + 1, 0, newLine);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only adjust ybase and ydisp when the buffer is not trimmed
|
// Only adjust ybase and ydisp when the buffer is not trimmed
|
||||||
@@ -1233,7 +1204,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
|||||||
// scrollback, instead we can just shift them in-place.
|
// scrollback, instead we can just shift them in-place.
|
||||||
const scrollRegionHeight = bottomRow - topRow + 1/*as it's zero-based*/;
|
const scrollRegionHeight = bottomRow - topRow + 1/*as it's zero-based*/;
|
||||||
this.buffer.lines.shiftElements(topRow + 1, scrollRegionHeight - 1, -1);
|
this.buffer.lines.shiftElements(topRow + 1, scrollRegionHeight - 1, -1);
|
||||||
this.buffer.lines.set(bottomRow, (useRecycling) ? newLine.clone() : newLine);
|
this.buffer.lines.set(bottomRow, newLine);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move the viewport to the bottom of the buffer unless the user is
|
// Move the viewport to the bottom of the buffer unless the user is
|
||||||
@@ -1413,15 +1384,6 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
|||||||
this._customKeyEventHandler = customKeyEventHandler;
|
this._customKeyEventHandler = customKeyEventHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Add handler for CSI escape sequence. See xterm.d.ts for details. */
|
|
||||||
public addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): IDisposable {
|
|
||||||
return this._inputHandler.addCsiHandler(flag, callback);
|
|
||||||
}
|
|
||||||
/** Add handler for OSC escape sequence. See xterm.d.ts for details. */
|
|
||||||
public addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable {
|
|
||||||
return this._inputHandler.addOscHandler(ident, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers a link matcher, allowing custom link patterns to be matched and
|
* Registers a link matcher, allowing custom link patterns to be matched and
|
||||||
* handled.
|
* handled.
|
||||||
@@ -1760,7 +1722,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
|||||||
this.buffer.ybase = 0;
|
this.buffer.ybase = 0;
|
||||||
this.buffer.y = 0;
|
this.buffer.y = 0;
|
||||||
for (let i = 1; i < this.rows; i++) {
|
for (let i = 1; i < this.rows; i++) {
|
||||||
this.buffer.lines.push(this.buffer.getBlankLine(DEFAULT_ATTR));
|
this.buffer.lines.push(BufferLine.blankLine(this.cols, DEFAULT_ATTR));
|
||||||
}
|
}
|
||||||
this.refresh(0, this.rows - 1);
|
this.refresh(0, this.rows - 1);
|
||||||
this.emit('scroll', this.buffer.ydisp);
|
this.emit('scroll', this.buffer.ydisp);
|
||||||
@@ -1852,7 +1814,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
|||||||
// blankLine(true) is xterm/linux behavior
|
// blankLine(true) is xterm/linux behavior
|
||||||
const scrollRegionHeight = this.buffer.scrollBottom - this.buffer.scrollTop;
|
const scrollRegionHeight = this.buffer.scrollBottom - this.buffer.scrollTop;
|
||||||
this.buffer.lines.shiftElements(this.buffer.y + this.buffer.ybase, scrollRegionHeight, 1);
|
this.buffer.lines.shiftElements(this.buffer.y + this.buffer.ybase, scrollRegionHeight, 1);
|
||||||
this.buffer.lines.set(this.buffer.y + this.buffer.ybase, this.buffer.getBlankLine(this.eraseAttr()));
|
this.buffer.lines.set(this.buffer.y + this.buffer.ybase, BufferLine.blankLine(this.cols, this.eraseAttr()));
|
||||||
this.updateRange(this.buffer.scrollTop);
|
this.updateRange(this.buffer.scrollTop);
|
||||||
this.updateRange(this.buffer.scrollBottom);
|
this.updateRange(this.buffer.scrollBottom);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+7
-17
@@ -49,6 +49,7 @@ 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;
|
||||||
@@ -245,8 +246,8 @@ export interface ILinkifierAccessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface IMouseHelper {
|
export interface IMouseHelper {
|
||||||
getCoords(event: { clientX: number, clientY: number }, element: HTMLElement, charMeasure: ICharMeasure, colCount: number, rowCount: number, isSelection?: boolean): [number, number];
|
getCoords(event: { pageX: number, pageY: number }, element: HTMLElement, charMeasure: ICharMeasure, lineHeight: number, colCount: number, rowCount: number, isSelection?: boolean): [number, number];
|
||||||
getRawByteCoords(event: MouseEvent, element: HTMLElement, charMeasure: ICharMeasure, colCount: number, rowCount: number): { x: number, y: number };
|
getRawByteCoords(event: MouseEvent, element: HTMLElement, charMeasure: ICharMeasure, lineHeight: number, colCount: number, rowCount: number): { x: number, y: number };
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ICharMeasure {
|
export interface ICharMeasure {
|
||||||
@@ -289,13 +290,11 @@ export interface IBuffer {
|
|||||||
hasScrollback: boolean;
|
hasScrollback: boolean;
|
||||||
savedY: number;
|
savedY: number;
|
||||||
savedX: number;
|
savedX: number;
|
||||||
savedCurAttr: number;
|
|
||||||
isCursorInViewport: boolean;
|
isCursorInViewport: boolean;
|
||||||
translateBufferLineToString(lineIndex: number, trimRight: boolean, startCol?: number, endCol?: number): string;
|
translateBufferLineToString(lineIndex: number, trimRight: boolean, startCol?: number, endCol?: number): string;
|
||||||
getWrappedRangeForLine(y: number): { first: number, last: number };
|
getWrappedRangeForLine(y: number): { first: number, last: number };
|
||||||
nextStop(x?: number): number;
|
nextStop(x?: number): number;
|
||||||
prevStop(x?: number): number;
|
prevStop(x?: number): number;
|
||||||
getBlankLine(attr: number, isWrapped?: boolean): IBufferLine;
|
|
||||||
stringIndexToBufferIndex(lineIndex: number, stringIndex: number): number[];
|
stringIndexToBufferIndex(lineIndex: number, stringIndex: number): number[];
|
||||||
iterator(trimRight: boolean, startIndex?: number, endIndex?: number, startOverscan?: number, endOverscan?: number): IBufferStringIterator;
|
iterator(trimRight: boolean, startIndex?: number, endIndex?: number, startOverscan?: number, endOverscan?: number): IBufferStringIterator;
|
||||||
}
|
}
|
||||||
@@ -306,7 +305,7 @@ export interface IBufferSet extends IEventEmitter {
|
|||||||
active: IBuffer;
|
active: IBuffer;
|
||||||
|
|
||||||
activateNormalBuffer(): void;
|
activateNormalBuffer(): void;
|
||||||
activateAltBuffer(fillAttr?: number): void;
|
activateAltBuffer(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ISelectionManager {
|
export interface ISelectionManager {
|
||||||
@@ -492,8 +491,6 @@ export interface IEscapeSequenceParser extends IDisposable {
|
|||||||
setCsiHandler(flag: string, callback: (params: number[], collect: string) => void): void;
|
setCsiHandler(flag: string, callback: (params: number[], collect: string) => void): void;
|
||||||
clearCsiHandler(flag: string): void;
|
clearCsiHandler(flag: string): void;
|
||||||
setCsiHandlerFallback(callback: (collect: string, params: number[], flag: number) => void): void;
|
setCsiHandlerFallback(callback: (collect: string, params: number[], flag: number) => void): void;
|
||||||
addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): IDisposable;
|
|
||||||
addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable;
|
|
||||||
|
|
||||||
setEscHandler(collectAndFlag: string, callback: () => void): void;
|
setEscHandler(collectAndFlag: string, callback: () => void): void;
|
||||||
clearEscHandler(collectAndFlag: string): void;
|
clearEscHandler(collectAndFlag: string): void;
|
||||||
@@ -519,17 +516,10 @@ export interface IBufferLine {
|
|||||||
isWrapped: boolean;
|
isWrapped: boolean;
|
||||||
get(index: number): CharData;
|
get(index: number): CharData;
|
||||||
set(index: number, value: CharData): void;
|
set(index: number, value: CharData): void;
|
||||||
|
pop(): CharData | undefined;
|
||||||
|
push(data: CharData): void;
|
||||||
|
splice(start: number, deleteCount: number, ...items: CharData[]): CharData[];
|
||||||
insertCells(pos: number, n: number, ch: CharData): void;
|
insertCells(pos: number, n: number, ch: CharData): void;
|
||||||
deleteCells(pos: number, n: number, fill: CharData): void;
|
deleteCells(pos: number, n: number, fill: CharData): void;
|
||||||
replaceCells(start: number, end: number, fill: CharData): void;
|
replaceCells(start: number, end: number, fill: CharData): void;
|
||||||
resize(cols: number, fill: CharData, shrink?: boolean): void;
|
|
||||||
fill(fillCharData: CharData): void;
|
|
||||||
copyFrom(line: IBufferLine): void;
|
|
||||||
clone(): IBufferLine;
|
|
||||||
getTrimmedLength(): number;
|
|
||||||
translateToString(trimRight?: boolean, startCol?: number, endCol?: number): string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IBufferLineConstructor {
|
|
||||||
new(cols: number, fillCharData?: CharData, isWrapped?: boolean): IBufferLine;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { assert } from 'chai';
|
import { assert, expect } from 'chai';
|
||||||
|
|
||||||
import * as attach from './attach';
|
import * as attach from './attach';
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { assert } from 'chai';
|
import { assert, expect } from 'chai';
|
||||||
|
|
||||||
import * as fit from './fit';
|
import * as fit from './fit';
|
||||||
|
|
||||||
|
|||||||
@@ -4,17 +4,14 @@
|
|||||||
"target": "es5",
|
"target": "es5",
|
||||||
"lib": [
|
"lib": [
|
||||||
"dom",
|
"dom",
|
||||||
"es5"
|
"es6",
|
||||||
],
|
],
|
||||||
"rootDir": ".",
|
"rootDir": ".",
|
||||||
"outDir": "../../../lib/addons/fit/",
|
"outDir": "../../../lib/addons/fit/",
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"removeComments": true,
|
"removeComments": true,
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"preserveWatchOutput": true,
|
"preserveWatchOutput": true
|
||||||
"types": [
|
|
||||||
"../../node_modules/@types/mocha"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"**/*.ts",
|
"**/*.ts",
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { assert } from 'chai';
|
import { assert, expect } from 'chai';
|
||||||
|
|
||||||
import * as fullscreen from './fullscreen';
|
import * as fullscreen from './fullscreen';
|
||||||
|
|
||||||
|
|||||||
@@ -11,19 +11,17 @@ import { Terminal } from 'xterm';
|
|||||||
* @param fullscreen Toggle fullscreen on (true) or off (false)
|
* @param fullscreen Toggle fullscreen on (true) or off (false)
|
||||||
*/
|
*/
|
||||||
export function toggleFullScreen(term: Terminal, fullscreen: boolean): void {
|
export function toggleFullScreen(term: Terminal, fullscreen: boolean): void {
|
||||||
let fn: (...tokens: string[]) => void;
|
let fn: string;
|
||||||
|
|
||||||
if (typeof fullscreen === 'undefined') {
|
if (typeof fullscreen === 'undefined') {
|
||||||
fn = (term.element.classList.contains('fullscreen')) ?
|
fn = (term.element.classList.contains('fullscreen')) ? 'remove' : 'add';
|
||||||
term.element.classList.remove : term.element.classList.add;
|
|
||||||
} else if (!fullscreen) {
|
} else if (!fullscreen) {
|
||||||
fn = term.element.classList.remove;
|
fn = 'remove';
|
||||||
} else {
|
} else {
|
||||||
fn = term.element.classList.add;
|
fn = 'add';
|
||||||
}
|
}
|
||||||
|
|
||||||
fn = fn.bind(term.element.classList);
|
term.element.classList[fn]('fullscreen');
|
||||||
fn('fullscreen');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function apply(terminalConstructor: typeof Terminal): void {
|
export function apply(terminalConstructor: typeof Terminal): void {
|
||||||
|
|||||||
@@ -4,17 +4,14 @@
|
|||||||
"target": "es5",
|
"target": "es5",
|
||||||
"lib": [
|
"lib": [
|
||||||
"dom",
|
"dom",
|
||||||
"es5"
|
"es6",
|
||||||
],
|
],
|
||||||
"rootDir": ".",
|
"rootDir": ".",
|
||||||
"outDir": "../../../lib/addons/fullscreen/",
|
"outDir": "../../../lib/addons/fullscreen/",
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"removeComments": true,
|
"removeComments": true,
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"preserveWatchOutput": true,
|
"preserveWatchOutput": true
|
||||||
"types": [
|
|
||||||
"../../node_modules/@types/mocha"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"**/*.ts",
|
"**/*.ts",
|
||||||
|
|||||||
@@ -25,11 +25,6 @@ export interface ISearchOptions {
|
|||||||
regex?: boolean;
|
regex?: boolean;
|
||||||
wholeWord?: boolean;
|
wholeWord?: boolean;
|
||||||
caseSensitive?: boolean;
|
caseSensitive?: boolean;
|
||||||
/**
|
|
||||||
* Use this when you want the selection to expand if it still matches as the
|
|
||||||
* user types. Note that this only affects findNext.
|
|
||||||
*/
|
|
||||||
incremental?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ISearchResult {
|
export interface ISearchResult {
|
||||||
|
|||||||
@@ -4,75 +4,50 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { ISearchHelper, ISearchAddonTerminal, ISearchOptions, ISearchResult } from './Interfaces';
|
import { ISearchHelper, ISearchAddonTerminal, ISearchOptions, ISearchResult } from './Interfaces';
|
||||||
|
const nonWordCharacters = ' ~!@#$%^&*()_+`-=[]{}|\;:"\',./<>?';
|
||||||
const NON_WORD_CHARACTERS = ' ~!@#$%^&*()+`-=[]{}|\;:"\',./<>?';
|
|
||||||
const LINES_CACHE_TIME_TO_LIVE = 15 * 1000; // 15 secs
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A class that knows how to search the terminal and how to display the results.
|
* A class that knows how to search the terminal and how to display the results.
|
||||||
*/
|
*/
|
||||||
export class SearchHelper implements ISearchHelper {
|
export class SearchHelper implements ISearchHelper {
|
||||||
/**
|
|
||||||
* translateBufferLineToStringWithWrap is a fairly expensive call.
|
|
||||||
* We memoize the calls into an array that has a time based ttl.
|
|
||||||
* _linesCache is also invalidated when the terminal cursor moves.
|
|
||||||
*/
|
|
||||||
private _linesCache: string[] = null;
|
|
||||||
private _linesCacheTimeoutId = 0;
|
|
||||||
|
|
||||||
constructor(private _terminal: ISearchAddonTerminal) {
|
constructor(private _terminal: ISearchAddonTerminal) {
|
||||||
this._destroyLinesCache = this._destroyLinesCache.bind(this);
|
// TODO: Search for multiple instances on 1 line
|
||||||
|
// TODO: Don't use the actual selection, instead use a "find selection" so multiple instances can be highlighted
|
||||||
|
// TODO: Highlight other instances in the viewport
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find the next instance of the term, then scroll to and select it. If it
|
* Find the next instance of the term, then scroll to and select it. If it
|
||||||
* doesn't exist, do nothing.
|
* doesn't exist, do nothing.
|
||||||
* @param term The search term.
|
* @param term Tne search term.
|
||||||
* @param searchOptions Search options.
|
* @param searchOptions Search options.
|
||||||
* @return Whether a result was found.
|
* @return Whether a result was found.
|
||||||
*/
|
*/
|
||||||
public findNext(term: string, searchOptions?: ISearchOptions): boolean {
|
public findNext(term: string, searchOptions?: ISearchOptions): boolean {
|
||||||
const selectionManager = this._terminal._core.selectionManager;
|
|
||||||
const {incremental} = searchOptions;
|
|
||||||
let result: ISearchResult;
|
|
||||||
|
|
||||||
if (!term || term.length === 0) {
|
if (!term || term.length === 0) {
|
||||||
selectionManager.clearSelection();
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let startCol: number = 0;
|
let result: ISearchResult;
|
||||||
|
|
||||||
let startRow = this._terminal._core.buffer.ydisp;
|
let startRow = this._terminal._core.buffer.ydisp;
|
||||||
|
if (this._terminal._core.selectionManager.selectionEnd) {
|
||||||
if (selectionManager.selectionEnd) {
|
|
||||||
// Start from the selection end if there is a selection
|
// Start from the selection end if there is a selection
|
||||||
// For incremental search, use existing row
|
startRow = this._terminal._core.selectionManager.selectionEnd[1];
|
||||||
if (this._terminal.getSelection().length !== 0) {
|
}
|
||||||
startRow = incremental ? selectionManager.selectionStart[1] : selectionManager.selectionEnd[1];
|
|
||||||
startCol = incremental ? selectionManager.selectionStart[0] : selectionManager.selectionEnd[0];
|
// Search from ydisp + 1 to end
|
||||||
|
for (let y = startRow + 1; y < this._terminal._core.buffer.ybase + this._terminal.rows; y++) {
|
||||||
|
result = this._findInLine(term, y, searchOptions);
|
||||||
|
if (result) {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this._initLinesCache();
|
// Search from the top to the current ydisp
|
||||||
|
|
||||||
// Search startRow
|
|
||||||
result = this._findInLine(term, startRow, startCol, searchOptions);
|
|
||||||
|
|
||||||
// Search from startRow + 1 to end
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
for (let y = startRow + 1; y < this._terminal._core.buffer.ybase + this._terminal.rows; y++) {
|
for (let y = 0; y < startRow; y++) {
|
||||||
result = this._findInLine(term, y, 0, searchOptions);
|
result = this._findInLine(term, y, searchOptions);
|
||||||
if (result) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Search from the top to the startRow (search the whole startRow again in
|
|
||||||
// case startCol > 0)
|
|
||||||
if (!result) {
|
|
||||||
for (let y = 0; y <= startRow; y++) {
|
|
||||||
result = this._findInLine(term, y, 0, searchOptions);
|
|
||||||
if (result) {
|
if (result) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -86,52 +61,35 @@ export class SearchHelper implements ISearchHelper {
|
|||||||
/**
|
/**
|
||||||
* Find the previous instance of the term, then scroll to and select it. If it
|
* Find the previous instance of the term, then scroll to and select it. If it
|
||||||
* doesn't exist, do nothing.
|
* doesn't exist, do nothing.
|
||||||
* @param term The search term.
|
* @param term Tne search term.
|
||||||
* @param searchOptions Search options.
|
* @param searchOptions Search options.
|
||||||
* @return Whether a result was found.
|
* @return Whether a result was found.
|
||||||
*/
|
*/
|
||||||
public findPrevious(term: string, searchOptions?: ISearchOptions): boolean {
|
public findPrevious(term: string, searchOptions?: ISearchOptions): boolean {
|
||||||
const selectionManager = this._terminal._core.selectionManager;
|
|
||||||
let result: ISearchResult;
|
|
||||||
|
|
||||||
if (!term || term.length === 0) {
|
if (!term || term.length === 0) {
|
||||||
selectionManager.clearSelection();
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isReverseSearch = true;
|
let result: ISearchResult;
|
||||||
|
|
||||||
let startRow = this._terminal._core.buffer.ydisp;
|
let startRow = this._terminal._core.buffer.ydisp;
|
||||||
let startCol: number = this._terminal._core.buffer.lines.get(startRow).length;
|
if (this._terminal._core.selectionManager.selectionStart) {
|
||||||
|
// Start from the selection end if there is a selection
|
||||||
|
startRow = this._terminal._core.selectionManager.selectionStart[1];
|
||||||
|
}
|
||||||
|
|
||||||
if (selectionManager.selectionStart) {
|
// Search from ydisp + 1 to end
|
||||||
// Start from the selection start if there is a selection
|
for (let y = startRow - 1; y >= 0; y--) {
|
||||||
if (this._terminal.getSelection().length !== 0) {
|
result = this._findInLine(term, y, searchOptions);
|
||||||
startRow = selectionManager.selectionStart[1];
|
if (result) {
|
||||||
startCol = selectionManager.selectionStart[0];
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this._initLinesCache();
|
// Search from the top to the current ydisp
|
||||||
|
|
||||||
// Search startRow
|
|
||||||
result = this._findInLine(term, startRow, startCol, searchOptions, isReverseSearch);
|
|
||||||
|
|
||||||
// Search from startRow - 1 to top
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
for (let y = startRow - 1; y >= 0; y--) {
|
for (let y = this._terminal._core.buffer.ybase + this._terminal.rows - 1; y > startRow; y--) {
|
||||||
result = this._findInLine(term, y, this._terminal._core.buffer.lines.get(y).length, searchOptions, isReverseSearch);
|
result = this._findInLine(term, y, searchOptions);
|
||||||
if (result) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Search from the bottom to startRow (search the whole startRow again in
|
|
||||||
// case startCol > 0)
|
|
||||||
if (!result) {
|
|
||||||
const searchFrom = this._terminal._core.buffer.ybase + this._terminal.rows - 1;
|
|
||||||
for (let y = searchFrom; y >= startRow; y--) {
|
|
||||||
result = this._findInLine(term, y, this._terminal._core.buffer.lines.get(y).length, searchOptions, isReverseSearch);
|
|
||||||
if (result) {
|
if (result) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -142,28 +100,6 @@ export class SearchHelper implements ISearchHelper {
|
|||||||
return this._selectResult(result);
|
return this._selectResult(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets up a line cache with a ttl
|
|
||||||
*/
|
|
||||||
private _initLinesCache(): void {
|
|
||||||
if (!this._linesCache) {
|
|
||||||
this._linesCache = new Array(this._terminal._core.buffer.length);
|
|
||||||
this._terminal.on('cursormove', this._destroyLinesCache);
|
|
||||||
}
|
|
||||||
|
|
||||||
window.clearTimeout(this._linesCacheTimeoutId);
|
|
||||||
this._linesCacheTimeoutId = window.setTimeout(() => this._destroyLinesCache(), LINES_CACHE_TIME_TO_LIVE);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _destroyLinesCache(): void {
|
|
||||||
this._linesCache = null;
|
|
||||||
this._terminal.off('cursormove', this._destroyLinesCache);
|
|
||||||
if (this._linesCacheTimeoutId) {
|
|
||||||
window.clearTimeout(this._linesCacheTimeoutId);
|
|
||||||
this._linesCacheTimeoutId = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A found substring is a whole word if it doesn't have an alphanumeric character directly adjacent to it.
|
* A found substring is a whole word if it doesn't have an alphanumeric character directly adjacent to it.
|
||||||
* @param searchIndex starting indext of the potential whole word substring
|
* @param searchIndex starting indext of the potential whole word substring
|
||||||
@@ -171,8 +107,8 @@ export class SearchHelper implements ISearchHelper {
|
|||||||
* @param term the substring that starts at searchIndex
|
* @param term the substring that starts at searchIndex
|
||||||
*/
|
*/
|
||||||
private _isWholeWord(searchIndex: number, line: string, term: string): boolean {
|
private _isWholeWord(searchIndex: number, line: string, term: string): boolean {
|
||||||
return (((searchIndex === 0) || (NON_WORD_CHARACTERS.indexOf(line[searchIndex - 1]) !== -1)) &&
|
return (((searchIndex === 0) || (nonWordCharacters.indexOf(line[searchIndex - 1]) !== -1)) &&
|
||||||
(((searchIndex + term.length) === line.length) || (NON_WORD_CHARACTERS.indexOf(line[searchIndex + term.length]) !== -1)));
|
(((searchIndex + term.length) === line.length) || (nonWordCharacters.indexOf(line[searchIndex + term.length]) !== -1)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -180,89 +116,66 @@ export class SearchHelper implements ISearchHelper {
|
|||||||
* subsequent terminal lines if the text is wrapped. If the provided line number is part of a wrapped text line that
|
* subsequent terminal lines if the text is wrapped. If the provided line number is part of a wrapped text line that
|
||||||
* started on an earlier line then it is skipped since it will be properly searched when the terminal line that the
|
* started on an earlier line then it is skipped since it will be properly searched when the terminal line that the
|
||||||
* text starts on is searched.
|
* text starts on is searched.
|
||||||
* @param term The search term.
|
* @param term Tne search term.
|
||||||
* @param row The line to start the search from.
|
* @param y The line to search.
|
||||||
* @param col The column to start the search from.
|
|
||||||
* @param searchOptions Search options.
|
* @param searchOptions Search options.
|
||||||
* @return The search result if it was found.
|
* @return The search result if it was found.
|
||||||
*/
|
*/
|
||||||
protected _findInLine(term: string, row: number, col: number, searchOptions: ISearchOptions = {}, isReverseSearch: boolean = false): ISearchResult {
|
protected _findInLine(term: string, y: number, searchOptions: ISearchOptions = {}): ISearchResult {
|
||||||
if (this._terminal._core.buffer.lines.get(row).isWrapped) {
|
if (this._terminal._core.buffer.lines.get(y).isWrapped) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let stringLine = this._linesCache ? this._linesCache[row] : void 0;
|
const stringLine = this.translateBufferLineToStringWithWrap(y, true);
|
||||||
if (stringLine === void 0) {
|
|
||||||
stringLine = this.translateBufferLineToStringWithWrap(row, true);
|
|
||||||
if (this._linesCache) {
|
|
||||||
this._linesCache[row] = stringLine;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const searchTerm = searchOptions.caseSensitive ? term : term.toLowerCase();
|
|
||||||
const searchStringLine = searchOptions.caseSensitive ? stringLine : stringLine.toLowerCase();
|
const searchStringLine = searchOptions.caseSensitive ? stringLine : stringLine.toLowerCase();
|
||||||
|
const searchTerm = searchOptions.caseSensitive ? term : term.toLowerCase();
|
||||||
|
let searchIndex = -1;
|
||||||
|
|
||||||
let resultIndex = -1;
|
|
||||||
if (searchOptions.regex) {
|
if (searchOptions.regex) {
|
||||||
const searchRegex = RegExp(searchTerm, 'g');
|
const searchRegex = RegExp(searchTerm, 'g');
|
||||||
let foundTerm: RegExpExecArray;
|
const foundTerm = searchRegex.exec(searchStringLine);
|
||||||
if (isReverseSearch) {
|
if (foundTerm && foundTerm[0].length > 0) {
|
||||||
// This loop will get the resultIndex of the _last_ regex match in the range 0..col
|
searchIndex = searchRegex.lastIndex - foundTerm[0].length;
|
||||||
while (foundTerm = searchRegex.exec(searchStringLine.slice(0, col))) {
|
term = foundTerm[0];
|
||||||
resultIndex = searchRegex.lastIndex - foundTerm[0].length;
|
|
||||||
term = foundTerm[0];
|
|
||||||
searchRegex.lastIndex -= (term.length - 1);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
foundTerm = searchRegex.exec(searchStringLine.slice(col));
|
|
||||||
if (foundTerm && foundTerm[0].length > 0) {
|
|
||||||
resultIndex = col + (searchRegex.lastIndex - foundTerm[0].length);
|
|
||||||
term = foundTerm[0];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (isReverseSearch) {
|
searchIndex = searchStringLine.indexOf(searchTerm);
|
||||||
if (col - searchTerm.length >= 0) {
|
|
||||||
resultIndex = searchStringLine.lastIndexOf(searchTerm, col - searchTerm.length);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
resultIndex = searchStringLine.indexOf(searchTerm, col);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resultIndex >= 0) {
|
if (searchIndex >= 0) {
|
||||||
// Adjust the row number and search index if needed since a "line" of text can span multiple rows
|
// Adjust the row number and search index if needed since a "line" of text can span multiple rows
|
||||||
if (resultIndex >= this._terminal.cols) {
|
if (searchIndex >= this._terminal.cols) {
|
||||||
row += Math.floor(resultIndex / this._terminal.cols);
|
y += Math.floor(searchIndex / this._terminal.cols);
|
||||||
resultIndex = resultIndex % this._terminal.cols;
|
searchIndex = searchIndex % this._terminal.cols;
|
||||||
}
|
}
|
||||||
if (searchOptions.wholeWord && !this._isWholeWord(resultIndex, searchStringLine, term)) {
|
if (searchOptions.wholeWord && !this._isWholeWord(searchIndex, searchStringLine, term)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const line = this._terminal._core.buffer.lines.get(row);
|
const line = this._terminal._core.buffer.lines.get(y);
|
||||||
|
|
||||||
for (let i = 0; i < resultIndex; i++) {
|
for (let i = 0; i < searchIndex; i++) {
|
||||||
const charData = line.get(i);
|
const charData = line.get(i);
|
||||||
// Adjust the searchIndex to normalize emoji into single chars
|
// Adjust the searchIndex to normalize emoji into single chars
|
||||||
const char = charData[1/*CHAR_DATA_CHAR_INDEX*/];
|
const char = charData[1/*CHAR_DATA_CHAR_INDEX*/];
|
||||||
if (char.length > 1) {
|
if (char.length > 1) {
|
||||||
resultIndex -= char.length - 1;
|
searchIndex -= char.length - 1;
|
||||||
}
|
}
|
||||||
// Adjust the searchIndex for empty characters following wide unicode
|
// Adjust the searchIndex for empty characters following wide unicode
|
||||||
// chars (eg. CJK)
|
// chars (eg. CJK)
|
||||||
const charWidth = charData[2/*CHAR_DATA_WIDTH_INDEX*/];
|
const charWidth = charData[2/*CHAR_DATA_WIDTH_INDEX*/];
|
||||||
if (charWidth === 0) {
|
if (charWidth === 0) {
|
||||||
resultIndex++;
|
searchIndex++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
term,
|
term,
|
||||||
col: resultIndex,
|
col: searchIndex,
|
||||||
row
|
row: y
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Translates a buffer line to a string, including subsequent lines if they are wraps.
|
* Translates a buffer line to a string, including subsequent lines if they are wraps.
|
||||||
* Wide characters will count as two columns in the resulting string. This
|
* Wide characters will count as two columns in the resulting string. This
|
||||||
@@ -278,7 +191,7 @@ export class SearchHelper implements ISearchHelper {
|
|||||||
do {
|
do {
|
||||||
const nextLine = this._terminal._core.buffer.lines.get(lineIndex + 1);
|
const nextLine = this._terminal._core.buffer.lines.get(lineIndex + 1);
|
||||||
lineWrapsToNext = nextLine ? nextLine.isWrapped : false;
|
lineWrapsToNext = nextLine ? nextLine.isWrapped : false;
|
||||||
lineString += this._terminal._core.buffer.translateBufferLineToString(lineIndex, !lineWrapsToNext && trimRight).substring(0, this._terminal.cols);
|
lineString += this._terminal._core.buffer.translateBufferLineToString(lineIndex, !lineWrapsToNext && trimRight);
|
||||||
lineIndex++;
|
lineIndex++;
|
||||||
} while (lineWrapsToNext);
|
} while (lineWrapsToNext);
|
||||||
|
|
||||||
@@ -292,7 +205,6 @@ export class SearchHelper implements ISearchHelper {
|
|||||||
*/
|
*/
|
||||||
private _selectResult(result: ISearchResult): boolean {
|
private _selectResult(result: ISearchResult): boolean {
|
||||||
if (!result) {
|
if (!result) {
|
||||||
this._terminal.clearSelection();
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
this._terminal._core.selectionManager.setSelection(result.col, result.row, result.term.length);
|
this._terminal._core.selectionManager.setSelection(result.col, result.row, result.term.length);
|
||||||
|
|||||||
@@ -2,13 +2,13 @@
|
|||||||
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
|
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
|
||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
declare var require: any;
|
|
||||||
|
|
||||||
import { assert, expect } from 'chai';
|
import { assert, expect } from 'chai';
|
||||||
import * as search from './search';
|
import * as search from './search';
|
||||||
import { SearchHelper } from './SearchHelper';
|
import { SearchHelper } from './SearchHelper';
|
||||||
import { ISearchOptions, ISearchResult } from './Interfaces';
|
import { ISearchOptions, ISearchResult } from './Interfaces';
|
||||||
|
|
||||||
|
|
||||||
class MockTerminalPlain {}
|
class MockTerminalPlain {}
|
||||||
|
|
||||||
class MockTerminal {
|
class MockTerminal {
|
||||||
@@ -16,7 +16,7 @@ class MockTerminal {
|
|||||||
public searchHelper: TestSearchHelper;
|
public searchHelper: TestSearchHelper;
|
||||||
public cols: number;
|
public cols: number;
|
||||||
constructor(options: any) {
|
constructor(options: any) {
|
||||||
this._core = new (require('../../../lib/Terminal')).Terminal(options);
|
this._core = new (require('../../../lib/Terminal').Terminal)(options);
|
||||||
this.searchHelper = new TestSearchHelper(this as any);
|
this.searchHelper = new TestSearchHelper(this as any);
|
||||||
this.cols = options.cols;
|
this.cols = options.cols;
|
||||||
}
|
}
|
||||||
@@ -29,11 +29,8 @@ class MockTerminal {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class TestSearchHelper extends SearchHelper {
|
class TestSearchHelper extends SearchHelper {
|
||||||
public findInLine(term: string, rowNumber: number, searchOptions?: ISearchOptions): ISearchResult {
|
public findInLine(term: string, y: number, searchOptions?: ISearchOptions): ISearchResult {
|
||||||
return this._findInLine(term, rowNumber, 0, searchOptions);
|
return this._findInLine(term, y, searchOptions);
|
||||||
}
|
|
||||||
public findFromIndex(term: string, row: number, col: number, searchOptions?: ISearchOptions, isReverseSearch?: boolean): ISearchResult {
|
|
||||||
return this._findInLine(term, row, col, searchOptions, isReverseSearch);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,9 +82,6 @@ describe('search addon', () => {
|
|||||||
expect(hello3).eql(undefined);
|
expect(hello3).eql(undefined);
|
||||||
expect(llo).eql(undefined);
|
expect(llo).eql(undefined);
|
||||||
expect(goodbye).eql({col: 0, row: 5, term: 'goodbye'});
|
expect(goodbye).eql({col: 0, row: 5, term: 'goodbye'});
|
||||||
term.core.resize(9, 5);
|
|
||||||
const hello0Resize = term.searchHelper.findInLine('Hello', 0);
|
|
||||||
expect(hello0Resize).eql({col: 8, row: 0, term: 'Hello'});
|
|
||||||
});
|
});
|
||||||
it('should respect search regex', () => {
|
it('should respect search regex', () => {
|
||||||
search.apply(<any>MockTerminal);
|
search.apply(<any>MockTerminal);
|
||||||
@@ -121,6 +115,8 @@ describe('search addon', () => {
|
|||||||
it('should not select empty lines', () => {
|
it('should not select empty lines', () => {
|
||||||
search.apply(<any>MockTerminal);
|
search.apply(<any>MockTerminal);
|
||||||
const term = new MockTerminal({cols: 20, rows: 3});
|
const term = new MockTerminal({cols: 20, rows: 3});
|
||||||
|
term.core.write(' ');
|
||||||
|
term.pushWriteData();
|
||||||
const line = term.searchHelper.findInLine('^.*$', 0, { regex: true });
|
const line = term.searchHelper.findInLine('^.*$', 0, { regex: true });
|
||||||
expect(line).eql(undefined);
|
expect(line).eql(undefined);
|
||||||
});
|
});
|
||||||
@@ -248,66 +244,5 @@ describe('search addon', () => {
|
|||||||
expect(hello4).eql(undefined);
|
expect(hello4).eql(undefined);
|
||||||
expect(hello5).eql(undefined);
|
expect(hello5).eql(undefined);
|
||||||
});
|
});
|
||||||
it('should find multiple matches in line', function(): void {
|
|
||||||
search.apply(<any>MockTerminal);
|
|
||||||
const term = new MockTerminal({cols: 20, rows: 5});
|
|
||||||
term.core.write('helloooo helloooo\r\naaaAAaaAAA');
|
|
||||||
term.pushWriteData();
|
|
||||||
const searchOptions = {
|
|
||||||
regex: false,
|
|
||||||
wholeWord: false,
|
|
||||||
caseSensitive: false
|
|
||||||
};
|
|
||||||
const find0 = term.searchHelper.findFromIndex('hello', 0, 0, searchOptions);
|
|
||||||
const find1 = term.searchHelper.findFromIndex('hello', 0, find0.col + find0.term.length, searchOptions);
|
|
||||||
const find2 = term.searchHelper.findFromIndex('aaaa', 1, 0, searchOptions);
|
|
||||||
const find3 = term.searchHelper.findFromIndex('aaaa', 1, find2.col + find2.term.length, searchOptions);
|
|
||||||
const find4 = term.searchHelper.findFromIndex('aaaa', 1, find3.col + find3.term.length, searchOptions);
|
|
||||||
expect(find0).eql({col: 0, row: 0, term: 'hello'});
|
|
||||||
expect(find1).eql({col: 9, row: 0, term: 'hello'});
|
|
||||||
expect(find2).eql({col: 0, row: 1, term: 'aaaa'});
|
|
||||||
expect(find3).eql({col: 4, row: 1, term: 'aaaa'});
|
|
||||||
expect(find4).eql(undefined);
|
|
||||||
});
|
|
||||||
it('should find multiple matches in line - reverse search', function(): void {
|
|
||||||
search.apply(<any>MockTerminal);
|
|
||||||
const term = new MockTerminal({cols: 20, rows: 5});
|
|
||||||
term.core.write('it is what it is');
|
|
||||||
term.pushWriteData();
|
|
||||||
const searchOptions = {
|
|
||||||
regex: false,
|
|
||||||
wholeWord: false,
|
|
||||||
caseSensitive: false
|
|
||||||
};
|
|
||||||
const isReverseSearch = true;
|
|
||||||
const find0 = term.searchHelper.findFromIndex('is', 0, 16, searchOptions, isReverseSearch);
|
|
||||||
const find1 = term.searchHelper.findFromIndex('is', 0, find0.col, searchOptions, isReverseSearch);
|
|
||||||
const find2 = term.searchHelper.findFromIndex('it', 0, 16, searchOptions, isReverseSearch);
|
|
||||||
const find3 = term.searchHelper.findFromIndex('it', 0, find2.col, searchOptions, isReverseSearch);
|
|
||||||
expect(find0).eql({col: 14, row: 0, term: 'is'});
|
|
||||||
expect(find1).eql({col: 3, row: 0, term: 'is'});
|
|
||||||
expect(find2).eql({col: 11, row: 0, term: 'it'});
|
|
||||||
expect(find3).eql({col: 0, row: 0, term: 'it'});
|
|
||||||
});
|
|
||||||
it('should find multiple matches in line - reverse search with regex', function(): void {
|
|
||||||
search.apply(<any>MockTerminal);
|
|
||||||
const term = new MockTerminal({cols: 20, rows: 5});
|
|
||||||
term.core.write('zzzABCzzzzABCABC');
|
|
||||||
term.pushWriteData();
|
|
||||||
const searchOptions = {
|
|
||||||
regex: true,
|
|
||||||
wholeWord: false,
|
|
||||||
caseSensitive: true
|
|
||||||
};
|
|
||||||
const isReverseSearch = true;
|
|
||||||
const find0 = term.searchHelper.findFromIndex('[A-Z]{3}', 0, 16, searchOptions, isReverseSearch);
|
|
||||||
const find1 = term.searchHelper.findFromIndex('[A-Z]{3}', 0, find0.col, searchOptions, isReverseSearch);
|
|
||||||
const find2 = term.searchHelper.findFromIndex('[A-Z]{3}', 0, find1.col, searchOptions, isReverseSearch);
|
|
||||||
const find3 = term.searchHelper.findFromIndex('[A-Z]{3}', 0, find2.col, searchOptions, isReverseSearch);
|
|
||||||
expect(find0).eql({col: 13, row: 0, term: 'ABC'});
|
|
||||||
expect(find1).eql({col: 10, row: 0, term: 'ABC'});
|
|
||||||
expect(find2).eql({col: 3, row: 0, term: 'ABC'});
|
|
||||||
expect(find3).eql(undefined);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { ISearchAddonTerminal, ISearchOptions } from './Interfaces';
|
|||||||
/**
|
/**
|
||||||
* Find the next instance of the term, then scroll to and select it. If it
|
* Find the next instance of the term, then scroll to and select it. If it
|
||||||
* doesn't exist, do nothing.
|
* doesn't exist, do nothing.
|
||||||
* @param term The search term.
|
* @param term Tne search term.
|
||||||
* @param searchOptions Search options
|
* @param searchOptions Search options
|
||||||
* @return Whether a result was found.
|
* @return Whether a result was found.
|
||||||
*/
|
*/
|
||||||
@@ -25,7 +25,7 @@ export function findNext(terminal: Terminal, term: string, searchOptions: ISearc
|
|||||||
/**
|
/**
|
||||||
* Find the previous instance of the term, then scroll to and select it. If it
|
* Find the previous instance of the term, then scroll to and select it. If it
|
||||||
* doesn't exist, do nothing.
|
* doesn't exist, do nothing.
|
||||||
* @param term The search term.
|
* @param term Tne search term.
|
||||||
* @param searchOptions Search options
|
* @param searchOptions Search options
|
||||||
* @return Whether a result was found.
|
* @return Whether a result was found.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -4,17 +4,14 @@
|
|||||||
"target": "es5",
|
"target": "es5",
|
||||||
"lib": [
|
"lib": [
|
||||||
"dom",
|
"dom",
|
||||||
"es5"
|
"es6",
|
||||||
],
|
],
|
||||||
"rootDir": ".",
|
"rootDir": ".",
|
||||||
"outDir": "../../../lib/addons/search/",
|
"outDir": "../../../lib/addons/search/",
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"removeComments": true,
|
"removeComments": true,
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"preserveWatchOutput": true,
|
"preserveWatchOutput": true
|
||||||
"types": [
|
|
||||||
"../../node_modules/@types/mocha"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"**/*.ts",
|
"**/*.ts",
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { assert } from 'chai';
|
import { assert, expect } from 'chai';
|
||||||
|
|
||||||
import * as terminado from './terminado';
|
import * as terminado from './terminado';
|
||||||
|
|
||||||
|
|||||||
@@ -3,17 +3,15 @@
|
|||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
"target": "es5",
|
"target": "es5",
|
||||||
"lib": [
|
"lib": [
|
||||||
"es5"
|
"dom",
|
||||||
|
"es6",
|
||||||
],
|
],
|
||||||
"rootDir": ".",
|
"rootDir": ".",
|
||||||
"outDir": "../../../lib/addons/terminado/",
|
"outDir": "../../../lib/addons/terminado/",
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"removeComments": true,
|
"removeComments": true,
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"preserveWatchOutput": true,
|
"preserveWatchOutput": true
|
||||||
"types": [
|
|
||||||
"../../node_modules/@types/mocha"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"**/*.ts",
|
"**/*.ts",
|
||||||
|
|||||||
@@ -4,17 +4,14 @@
|
|||||||
"target": "es5",
|
"target": "es5",
|
||||||
"lib": [
|
"lib": [
|
||||||
"dom",
|
"dom",
|
||||||
"es5",
|
"es6",
|
||||||
],
|
],
|
||||||
"rootDir": ".",
|
"rootDir": ".",
|
||||||
"outDir": "../../../lib/addons/webLinks/",
|
"outDir": "../../../lib/addons/webLinks/",
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"removeComments": true,
|
"removeComments": true,
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"preserveWatchOutput": true,
|
"preserveWatchOutput": true
|
||||||
"types": [
|
|
||||||
"../../node_modules/@types/mocha"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"**/*.ts",
|
"**/*.ts",
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { assert } from 'chai';
|
import { assert, expect } from 'chai';
|
||||||
|
|
||||||
import * as webLinks from './webLinks';
|
import * as webLinks from './webLinks';
|
||||||
|
|
||||||
@@ -39,52 +39,4 @@ describe('webLinks addon', () => {
|
|||||||
|
|
||||||
assert.equal(uri, 'http://foo.com/a~b#c~d?e~f');
|
assert.equal(uri, 'http://foo.com/a~b#c~d?e~f');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow : character in URI path', () => {
|
|
||||||
const term = new MockTerminal();
|
|
||||||
webLinks.webLinksInit(<any>term);
|
|
||||||
|
|
||||||
const row = ' http://foo.com/colon:test ';
|
|
||||||
|
|
||||||
const match = row.match(term.regex);
|
|
||||||
const uri = match[term.options.matchIndex];
|
|
||||||
|
|
||||||
assert.equal(uri, 'http://foo.com/colon:test');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not allow : character at the end of a URI path', () => {
|
|
||||||
const term = new MockTerminal();
|
|
||||||
webLinks.webLinksInit(<any>term);
|
|
||||||
|
|
||||||
const row = ' http://foo.com/colon:test: ';
|
|
||||||
|
|
||||||
const match = row.match(term.regex);
|
|
||||||
const uri = match[term.options.matchIndex];
|
|
||||||
|
|
||||||
assert.equal(uri, 'http://foo.com/colon:test');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not allow " character at the end of a URI enclosed with ""', () => {
|
|
||||||
const term = new MockTerminal();
|
|
||||||
webLinks.webLinksInit(<any>term);
|
|
||||||
|
|
||||||
const row = '"http://foo.com/"';
|
|
||||||
|
|
||||||
const match = row.match(term.regex);
|
|
||||||
const uri = match[term.options.matchIndex];
|
|
||||||
|
|
||||||
assert.equal(uri, 'http://foo.com/');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not allow \' character at the end of a URI enclosed with \'\'', () => {
|
|
||||||
const term = new MockTerminal();
|
|
||||||
webLinks.webLinksInit(<any>term);
|
|
||||||
|
|
||||||
const row = '\'http://foo.com/\'';
|
|
||||||
|
|
||||||
const match = row.match(term.regex);
|
|
||||||
const uri = match[term.options.matchIndex];
|
|
||||||
|
|
||||||
assert.equal(uri, 'http://foo.com/');
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ const ipClause = '((\\d{1,3}\\.){3}\\d{1,3})';
|
|||||||
const localHostClause = '(localhost)';
|
const localHostClause = '(localhost)';
|
||||||
const portClause = '(:\\d{1,5})';
|
const portClause = '(:\\d{1,5})';
|
||||||
const hostClause = '((' + domainBodyClause + '\\.' + tldClause + ')|' + ipClause + '|' + localHostClause + ')' + portClause + '?';
|
const hostClause = '((' + domainBodyClause + '\\.' + tldClause + ')|' + ipClause + '|' + localHostClause + ')' + portClause + '?';
|
||||||
const pathClause = '(\\/[\\/\\w\\.\\-%~:]*)*([^:"\'\\s])';
|
const pathClause = '(\\/[\\/\\w\\.\\-%~]*)*';
|
||||||
const queryStringHashFragmentCharacterSet = '[0-9\\w\\[\\]\\(\\)\\/\\?\\!#@$%&\'*+,:;~\\=\\.\\-]*';
|
const queryStringHashFragmentCharacterSet = '[0-9\\w\\[\\]\\(\\)\\/\\?\\!#@$%&\'*+,:;~\\=\\.\\-]*';
|
||||||
const queryStringClause = '(\\?' + queryStringHashFragmentCharacterSet + ')?';
|
const queryStringClause = '(\\?' + queryStringHashFragmentCharacterSet + ')?';
|
||||||
const hashFragmentClause = '(#' + queryStringHashFragmentCharacterSet + ')?';
|
const hashFragmentClause = '(#' + queryStringHashFragmentCharacterSet + ')?';
|
||||||
|
|||||||
@@ -3,17 +3,15 @@
|
|||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
"target": "es5",
|
"target": "es5",
|
||||||
"lib": [
|
"lib": [
|
||||||
"es5"
|
"dom",
|
||||||
|
"es6",
|
||||||
],
|
],
|
||||||
"rootDir": ".",
|
"rootDir": ".",
|
||||||
"outDir": "../../../lib/addons/winptyCompat/",
|
"outDir": "../../../lib/addons/winptyCompat/",
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"removeComments": true,
|
"removeComments": true,
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"preserveWatchOutput": true,
|
"preserveWatchOutput": true
|
||||||
"types": [
|
|
||||||
"../../node_modules/@types/mocha"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"**/*.ts",
|
"**/*.ts",
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { assert } from 'chai';
|
import { assert, expect } from 'chai';
|
||||||
|
|
||||||
import * as winptyCompat from './winptyCompat';
|
import * as winptyCompat from './winptyCompat';
|
||||||
|
|
||||||
|
|||||||
@@ -7,8 +7,7 @@ import { Terminal } from 'xterm';
|
|||||||
import { IWinptyCompatAddonTerminal } from './Interfaces';
|
import { IWinptyCompatAddonTerminal } from './Interfaces';
|
||||||
|
|
||||||
const CHAR_DATA_CODE_INDEX = 3;
|
const CHAR_DATA_CODE_INDEX = 3;
|
||||||
const NULL_CELL_CODE = 0;
|
const NULL_CELL_CODE = 32;
|
||||||
const WHITESPACE_CELL_CODE = 32;
|
|
||||||
|
|
||||||
export function winptyCompatInit(terminal: Terminal): void {
|
export function winptyCompatInit(terminal: Terminal): void {
|
||||||
const addonTerminal = <IWinptyCompatAddonTerminal>terminal;
|
const addonTerminal = <IWinptyCompatAddonTerminal>terminal;
|
||||||
@@ -33,7 +32,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.get(addonTerminal.cols - 1);
|
const lastChar = line.get(addonTerminal.cols - 1);
|
||||||
|
|
||||||
if (lastChar[CHAR_DATA_CODE_INDEX] !== NULL_CELL_CODE && lastChar[CHAR_DATA_CODE_INDEX] !== WHITESPACE_CELL_CODE) {
|
if (lastChar[CHAR_DATA_CODE_INDEX] !== NULL_CELL_CODE) {
|
||||||
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);
|
||||||
nextLine.isWrapped = true;
|
nextLine.isWrapped = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,17 +3,15 @@
|
|||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
"target": "es5",
|
"target": "es5",
|
||||||
"lib": [
|
"lib": [
|
||||||
"es5"
|
"dom",
|
||||||
|
"es6",
|
||||||
],
|
],
|
||||||
"rootDir": ".",
|
"rootDir": ".",
|
||||||
"outDir": "../../../lib/addons/zmodem/",
|
"outDir": "../../../lib/addons/zmodem/",
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"removeComments": true,
|
"removeComments": true,
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"preserveWatchOutput": true,
|
"preserveWatchOutput": true
|
||||||
"types": [
|
|
||||||
"../../node_modules/@types/mocha"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"**/*.ts",
|
"**/*.ts",
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { assert } from 'chai';
|
import { assert, expect } from 'chai';
|
||||||
|
|
||||||
import * as zmodem from './zmodem';
|
import * as zmodem from './zmodem';
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ import { Terminal } from 'xterm';
|
|||||||
* via `detach()` and a re-`attach()`.)
|
* via `detach()` and a re-`attach()`.)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
let zmodem: any;
|
let zmodem;
|
||||||
|
|
||||||
export interface IZmodemOptions {
|
export interface IZmodemOptions {
|
||||||
noTerminalWriteOutsideSession?: boolean;
|
noTerminalWriteOutsideSession?: boolean;
|
||||||
@@ -44,7 +44,7 @@ function zmodemAttach(ws: WebSocket, opts: IZmodemOptions = {}): void {
|
|||||||
const term = this;
|
const term = this;
|
||||||
const senderFunc = (octets: ArrayLike<number>) => ws.send(new Uint8Array(octets));
|
const senderFunc = (octets: ArrayLike<number>) => ws.send(new Uint8Array(octets));
|
||||||
|
|
||||||
let zsentry: any;
|
let zsentry;
|
||||||
|
|
||||||
function shouldWrite(): boolean {
|
function shouldWrite(): boolean {
|
||||||
return !!zsentry.get_confirmed_session() || !opts.noTerminalWriteOutsideSession;
|
return !!zsentry.get_confirmed_session() || !opts.noTerminalWriteOutsideSession;
|
||||||
|
|||||||
@@ -90,34 +90,16 @@ export class CircularList<T> extends EventEmitter implements ICircularList<T> {
|
|||||||
public push(value: T): void {
|
public push(value: T): void {
|
||||||
this._array[this._getCyclicIndex(this._length)] = value;
|
this._array[this._getCyclicIndex(this._length)] = value;
|
||||||
if (this._length === this._maxLength) {
|
if (this._length === this._maxLength) {
|
||||||
this._startIndex = ++this._startIndex % this._maxLength;
|
this._startIndex++;
|
||||||
|
if (this._startIndex === this._maxLength) {
|
||||||
|
this._startIndex = 0;
|
||||||
|
}
|
||||||
this.emit('trim', 1);
|
this.emit('trim', 1);
|
||||||
} else {
|
} else {
|
||||||
this._length++;
|
this._length++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Advance ringbuffer index and return current element for recycling.
|
|
||||||
* Note: The buffer must be full for this method to work.
|
|
||||||
* @throws When the buffer is not full.
|
|
||||||
*/
|
|
||||||
public recycle(): T {
|
|
||||||
if (this._length !== this._maxLength) {
|
|
||||||
throw new Error('Can only recycle when the buffer is full');
|
|
||||||
}
|
|
||||||
this._startIndex = ++this._startIndex % this._maxLength;
|
|
||||||
this.emit('trim', 1);
|
|
||||||
return this._array[this._getCyclicIndex(this._length - 1)]!;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ringbuffer is at max length.
|
|
||||||
*/
|
|
||||||
public get isFull(): boolean {
|
|
||||||
return this._length === this._maxLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes and returns the last value on the list.
|
* Removes and returns the last value on the list.
|
||||||
* @return The popped value.
|
* @return The popped value.
|
||||||
@@ -154,10 +136,10 @@ export class CircularList<T> extends EventEmitter implements ICircularList<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Adjust length as needed
|
// Adjust length as needed
|
||||||
if (this._length + items.length > this._maxLength) {
|
if (this._length + items.length > this.maxLength) {
|
||||||
const countToTrim = (this._length + items.length) - this._maxLength;
|
const countToTrim = (this._length + items.length) - this.maxLength;
|
||||||
this._startIndex += countToTrim;
|
this._startIndex += countToTrim;
|
||||||
this._length = this._maxLength;
|
this._length = this.maxLength;
|
||||||
this.emit('trim', countToTrim);
|
this.emit('trim', countToTrim);
|
||||||
} else {
|
} else {
|
||||||
this._length += items.length;
|
this._length += items.length;
|
||||||
@@ -196,7 +178,7 @@ export class CircularList<T> extends EventEmitter implements ICircularList<T> {
|
|||||||
const expandListBy = (start + count + offset) - this._length;
|
const expandListBy = (start + count + offset) - this._length;
|
||||||
if (expandListBy > 0) {
|
if (expandListBy > 0) {
|
||||||
this._length += expandListBy;
|
this._length += expandListBy;
|
||||||
while (this._length > this._maxLength) {
|
while (this._length > this.maxLength) {
|
||||||
this._length--;
|
this._length--;
|
||||||
this._startIndex++;
|
this._startIndex++;
|
||||||
this.emit('trim', 1);
|
this.emit('trim', 1);
|
||||||
@@ -216,6 +198,6 @@ export class CircularList<T> extends EventEmitter implements ICircularList<T> {
|
|||||||
* @returns The cyclic index.
|
* @returns The cyclic index.
|
||||||
*/
|
*/
|
||||||
private _getCyclicIndex(index: number): number {
|
private _getCyclicIndex(index: number): number {
|
||||||
return (this._startIndex + index) % this._maxLength;
|
return (this._startIndex + index) % this.maxLength;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export class EventEmitter extends Disposable implements IEventEmitter, IDisposab
|
|||||||
super();
|
super();
|
||||||
// Restore the previous events if available, this will happen if the
|
// Restore the previous events if available, this will happen if the
|
||||||
// constructor is called multiple times on the same object (terminal reset).
|
// constructor is called multiple times on the same object (terminal reset).
|
||||||
this._events = (<any>this)._events || {};
|
this._events = this._events || {};
|
||||||
}
|
}
|
||||||
|
|
||||||
public on(type: string, listener: XtermListener): void {
|
public on(type: string, listener: XtermListener): void {
|
||||||
|
|||||||
@@ -1,89 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
|
|
||||||
* @license MIT
|
|
||||||
*/
|
|
||||||
import { assert } from 'chai';
|
|
||||||
import { fillFallback } from './TypedArrayUtils';
|
|
||||||
|
|
||||||
type TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray
|
|
||||||
| Int8Array | Int16Array | Int32Array
|
|
||||||
| Float32Array | Float64Array;
|
|
||||||
|
|
||||||
describe('polyfill conformance tests', function(): void {
|
|
||||||
|
|
||||||
function deepEquals(a: TypedArray, b: TypedArray): void {
|
|
||||||
assert.equal(a.length, b.length);
|
|
||||||
for (let i = 0; i < a.length; ++i) {
|
|
||||||
assert.equal(a[i], b[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('TypedArray.fill', function(): void {
|
|
||||||
it('should work with all typed array types', function(): void {
|
|
||||||
const u81 = new Uint8Array(5);
|
|
||||||
const u82 = new Uint8Array(5);
|
|
||||||
deepEquals(fillFallback(u81, 2), u82.fill(2));
|
|
||||||
deepEquals(fillFallback(u81, -1), u82.fill(-1));
|
|
||||||
const u161 = new Uint16Array(5);
|
|
||||||
const u162 = new Uint16Array(5);
|
|
||||||
deepEquals(fillFallback(u161, 2), u162.fill(2));
|
|
||||||
deepEquals(fillFallback(u161, 65535), u162.fill(65535));
|
|
||||||
deepEquals(fillFallback(u161, -1), u162.fill(-1));
|
|
||||||
const u321 = new Uint32Array(5);
|
|
||||||
const u322 = new Uint32Array(5);
|
|
||||||
deepEquals(fillFallback(u321, 2), u322.fill(2));
|
|
||||||
deepEquals(fillFallback(u321, 65537), u322.fill(65537));
|
|
||||||
deepEquals(fillFallback(u321, -1), u322.fill(-1));
|
|
||||||
const i81 = new Int8Array(5);
|
|
||||||
const i82 = new Int8Array(5);
|
|
||||||
deepEquals(fillFallback(i81, 2), i82.fill(2));
|
|
||||||
deepEquals(fillFallback(i81, -1), i82.fill(-1));
|
|
||||||
const i161 = new Int16Array(5);
|
|
||||||
const i162 = new Int16Array(5);
|
|
||||||
deepEquals(fillFallback(i161, 2), i162.fill(2));
|
|
||||||
deepEquals(fillFallback(i161, 65535), i162.fill(65535));
|
|
||||||
deepEquals(fillFallback(i161, -1), i162.fill(-1));
|
|
||||||
const i321 = new Int32Array(5);
|
|
||||||
const i322 = new Int32Array(5);
|
|
||||||
deepEquals(fillFallback(i321, 2), i322.fill(2));
|
|
||||||
deepEquals(fillFallback(i321, 65537), i322.fill(65537));
|
|
||||||
deepEquals(fillFallback(i321, -1), i322.fill(-1));
|
|
||||||
const f321 = new Float32Array(5);
|
|
||||||
const f322 = new Float32Array(5);
|
|
||||||
deepEquals(fillFallback(f321, 1.2345), f322.fill(1.2345));
|
|
||||||
const f641 = new Float64Array(5);
|
|
||||||
const f642 = new Float64Array(5);
|
|
||||||
deepEquals(fillFallback(f641, 1.2345), f642.fill(1.2345));
|
|
||||||
const u8Clamped1 = new Uint8ClampedArray(5);
|
|
||||||
const u8Clamped2 = new Uint8ClampedArray(5);
|
|
||||||
deepEquals(fillFallback(u8Clamped1, 2), u8Clamped2.fill(2));
|
|
||||||
deepEquals(fillFallback(u8Clamped1, 257), u8Clamped2.fill(257));
|
|
||||||
});
|
|
||||||
it('start offset', function(): void {
|
|
||||||
for (let i = -2; i < 10; ++i) {
|
|
||||||
const u81 = new Uint8Array(5);
|
|
||||||
const u83 = new Uint8Array(5);
|
|
||||||
deepEquals(fillFallback(u81, 2, i), u83.fill(2, i));
|
|
||||||
deepEquals(fillFallback(u81, -1, i), u83.fill(-1, i));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
it('end offset', function(): void {
|
|
||||||
for (let i = -2; i < 10; ++i) {
|
|
||||||
const u81 = new Uint8Array(5);
|
|
||||||
const u83 = new Uint8Array(5);
|
|
||||||
deepEquals(fillFallback(u81, 2, 0, i), u83.fill(2, 0, i));
|
|
||||||
deepEquals(fillFallback(u81, -1, 0, i), u83.fill(-1, 0, i));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
it('start/end offset', function(): void {
|
|
||||||
for (let i = -2; i < 10; ++i) {
|
|
||||||
for (let j = -2; j < 10; ++j) {
|
|
||||||
const u81 = new Uint8Array(5);
|
|
||||||
const u83 = new Uint8Array(5);
|
|
||||||
deepEquals(fillFallback(u81, 2, i, j), u83.fill(2, i, j));
|
|
||||||
deepEquals(fillFallback(u81, -1, i, j), u83.fill(-1, i, j));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
|
|
||||||
* @license MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* polyfill for TypedArray.fill
|
|
||||||
* This is needed to support .fill in all safari versions and IE 11.
|
|
||||||
*/
|
|
||||||
|
|
||||||
type TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray
|
|
||||||
| Int8Array | Int16Array | Int32Array
|
|
||||||
| Float32Array | Float64Array;
|
|
||||||
|
|
||||||
export function fill<T extends TypedArray>(array: T, value: number, start?: number, end?: number): T {
|
|
||||||
// all modern engines that support .fill
|
|
||||||
if (array.fill) {
|
|
||||||
return array.fill(value, start, end) as T;
|
|
||||||
}
|
|
||||||
return fillFallback(array, value, start, end);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fillFallback<T extends TypedArray>(array: T, value: number, start: number = 0, end: number = array.length): T {
|
|
||||||
// safari and IE 11
|
|
||||||
// since IE 11 does not support Array.prototype.fill either
|
|
||||||
// we cannot use the suggested polyfill from MDN
|
|
||||||
// instead we simply fall back to looping
|
|
||||||
if (start >= array.length) {
|
|
||||||
return array;
|
|
||||||
}
|
|
||||||
start = (array.length + start) % array.length;
|
|
||||||
if (end >= array.length) {
|
|
||||||
end = array.length;
|
|
||||||
} else {
|
|
||||||
end = (array.length + end) % array.length;
|
|
||||||
}
|
|
||||||
for (let i = start; i < end; ++i) {
|
|
||||||
array[i] = value;
|
|
||||||
}
|
|
||||||
return array;
|
|
||||||
}
|
|
||||||
@@ -24,12 +24,10 @@ export interface IKeyboardEvent {
|
|||||||
export interface ICircularList<T> extends IEventEmitter {
|
export interface ICircularList<T> extends IEventEmitter {
|
||||||
length: number;
|
length: number;
|
||||||
maxLength: number;
|
maxLength: number;
|
||||||
isFull: boolean;
|
|
||||||
|
|
||||||
get(index: number): T | undefined;
|
get(index: number): T | undefined;
|
||||||
set(index: number, value: T): void;
|
set(index: number, value: T): void;
|
||||||
push(value: T): void;
|
push(value: T): void;
|
||||||
recycle(): T | undefined;
|
|
||||||
pop(): T | undefined;
|
pop(): T | undefined;
|
||||||
splice(start: number, deleteCount: number, ...items: T[]): void;
|
splice(start: number, deleteCount: number, ...items: T[]): void;
|
||||||
trimStart(count: number): void;
|
trimStart(count: number): void;
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"target": "es5",
|
|
||||||
"lib": [
|
|
||||||
"es5"
|
|
||||||
],
|
|
||||||
"rootDir": ".",
|
|
||||||
"noEmit": true,
|
|
||||||
"strict": true,
|
|
||||||
"pretty": true,
|
|
||||||
"types": [
|
|
||||||
"../../node_modules/@types/mocha",
|
|
||||||
"../../"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"include": [
|
|
||||||
"./**/*"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -10,12 +10,12 @@ import { ICharset } from '../Types';
|
|||||||
* to be represented within the terminal with only 8-bit encoding. See ISO 2022
|
* to be represented within the terminal with only 8-bit encoding. See ISO 2022
|
||||||
* for a discussion on character sets. Only VT100 character sets are supported.
|
* for a discussion on character sets. Only VT100 character sets are supported.
|
||||||
*/
|
*/
|
||||||
export const CHARSETS: { [key: string]: ICharset | null } = {};
|
export const CHARSETS: { [key: string]: ICharset } = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default character set, US.
|
* The default character set, US.
|
||||||
*/
|
*/
|
||||||
export const DEFAULT_CHARSET: ICharset | null = CHARSETS['B'];
|
export const DEFAULT_CHARSET: ICharset = CHARSETS['B'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DEC Special Character and Line Drawing Set.
|
* DEC Special Character and Line Drawing Set.
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
import { assert } from 'chai';
|
import { assert } from 'chai';
|
||||||
import { evaluateKeyboardEvent } from './Keyboard';
|
import { evaluateKeyboardEvent } from './Keyboard';
|
||||||
import { IKeyboardResult } from '../Types';
|
import { IKeyboardResult } from '../Types';
|
||||||
import { IKeyboardEvent } from '../../common/Types';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A helper function for testing which allows passing in a partial event and defaults will be filled
|
* A helper function for testing which allows passing in a partial event and defaults will be filled
|
||||||
@@ -21,12 +20,12 @@ function testEvaluateKeyboardEvent(partialEvent: {
|
|||||||
isMac?: boolean;
|
isMac?: boolean;
|
||||||
macOptionIsMeta?: boolean;
|
macOptionIsMeta?: boolean;
|
||||||
} = {}): IKeyboardResult {
|
} = {}): IKeyboardResult {
|
||||||
const event: IKeyboardEvent = {
|
const event = {
|
||||||
altKey: partialEvent.altKey || false,
|
altKey: partialEvent.altKey || false,
|
||||||
ctrlKey: partialEvent.ctrlKey || false,
|
ctrlKey: partialEvent.ctrlKey || false,
|
||||||
shiftKey: partialEvent.shiftKey || false,
|
shiftKey: partialEvent.shiftKey || false,
|
||||||
metaKey: partialEvent.metaKey || false,
|
metaKey: partialEvent.metaKey || false,
|
||||||
keyCode: partialEvent.keyCode !== undefined ? partialEvent.keyCode : 0,
|
keyCode: partialEvent.keyCode !== undefined ? partialEvent.keyCode : undefined,
|
||||||
key: partialEvent.key || '',
|
key: partialEvent.key || '',
|
||||||
type: partialEvent.type || ''
|
type: partialEvent.type || ''
|
||||||
};
|
};
|
||||||
@@ -281,16 +280,5 @@ describe('Keyboard', () => {
|
|||||||
assert.equal(testEvaluateKeyboardEvent({ keyCode: 0, key: 'UIKeyInputDownArrow' }).key, '\x1b[B');
|
assert.equal(testEvaluateKeyboardEvent({ keyCode: 0, key: 'UIKeyInputDownArrow' }).key, '\x1b[B');
|
||||||
assert.equal(testEvaluateKeyboardEvent({ keyCode: 0, key: 'UIKeyInputDownArrow' }, { applicationCursorMode: true }).key, '\x1bOB');
|
assert.equal(testEvaluateKeyboardEvent({ keyCode: 0, key: 'UIKeyInputDownArrow' }, { applicationCursorMode: true }).key, '\x1bOB');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle lowercase letters', () => {
|
|
||||||
assert.equal(testEvaluateKeyboardEvent({ keyCode: 65, key: 'a' }).key, 'a');
|
|
||||||
assert.equal(testEvaluateKeyboardEvent({ keyCode: 189, key: '-' }).key, '-');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle uppercase letters', () => {
|
|
||||||
assert.equal(testEvaluateKeyboardEvent({ shiftKey: true, keyCode: 65, key: 'A' }).key, 'A');
|
|
||||||
assert.equal(testEvaluateKeyboardEvent({ shiftKey: true, keyCode: 49, key: '!' }).key, '!');
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -349,10 +349,6 @@ export function evaluateKeyboardEvent(
|
|||||||
if (ev.keyCode === 65) { // cmd + a
|
if (ev.keyCode === 65) { // cmd + a
|
||||||
result.type = KeyboardResultType.SELECT_ALL;
|
result.type = KeyboardResultType.SELECT_ALL;
|
||||||
}
|
}
|
||||||
} else if (ev.key && !ev.ctrlKey && !ev.altKey && !ev.metaKey &&
|
|
||||||
ev.keyCode >= 48 && ev.keyCode !== 144 && ev.keyCode !== 145) {
|
|
||||||
// Include only keys that that result in a character; don't include num lock and scroll lock
|
|
||||||
result.key = ev.key;
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"target": "es5",
|
|
||||||
"lib": [
|
|
||||||
"es5"
|
|
||||||
],
|
|
||||||
"rootDir": ".",
|
|
||||||
"noEmit": true,
|
|
||||||
"strict": true,
|
|
||||||
"pretty": true,
|
|
||||||
"types": [
|
|
||||||
"../../node_modules/@types/mocha",
|
|
||||||
"../../"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"include": [
|
|
||||||
"./**/*",
|
|
||||||
"../common/**/*"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -33,6 +33,7 @@ export class AltClickHandler {
|
|||||||
this._mouseEvent,
|
this._mouseEvent,
|
||||||
this._terminal.element,
|
this._terminal.element,
|
||||||
this._terminal.charMeasure,
|
this._terminal.charMeasure,
|
||||||
|
this._terminal.options.lineHeight,
|
||||||
this._terminal.cols,
|
this._terminal.cols,
|
||||||
this._terminal.rows,
|
this._terminal.rows,
|
||||||
false
|
false
|
||||||
|
|||||||
@@ -59,12 +59,6 @@ export class Terminal implements ITerminalApi {
|
|||||||
public attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void {
|
public attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void {
|
||||||
this._core.attachCustomKeyEventHandler(customKeyEventHandler);
|
this._core.attachCustomKeyEventHandler(customKeyEventHandler);
|
||||||
}
|
}
|
||||||
public addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): IDisposable {
|
|
||||||
return this._core.addCsiHandler(flag, callback);
|
|
||||||
}
|
|
||||||
public addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable {
|
|
||||||
return this._core.addOscHandler(ident, callback);
|
|
||||||
}
|
|
||||||
public registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions): number {
|
public registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions): number {
|
||||||
return this._core.registerLinkMatcher(regex, handler, options);
|
return this._core.registerLinkMatcher(regex, handler, options);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, IGlyphIdentifier } from './atlas/T
|
|||||||
import BaseCharAtlas from './atlas/BaseCharAtlas';
|
import BaseCharAtlas from './atlas/BaseCharAtlas';
|
||||||
import { acquireCharAtlas } from './atlas/CharAtlasCache';
|
import { acquireCharAtlas } from './atlas/CharAtlasCache';
|
||||||
import { CHAR_DATA_CHAR_INDEX } from '../Buffer';
|
import { CHAR_DATA_CHAR_INDEX } from '../Buffer';
|
||||||
import { is256Color } from './atlas/CharAtlasUtils';
|
|
||||||
|
|
||||||
export abstract class BaseRenderLayer implements IRenderLayer {
|
export abstract class BaseRenderLayer implements IRenderLayer {
|
||||||
private _canvas: HTMLCanvasElement;
|
private _canvas: HTMLCanvasElement;
|
||||||
@@ -236,12 +235,12 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
|||||||
*/
|
*/
|
||||||
protected fillCharTrueColor(terminal: ITerminal, charData: CharData, x: number, y: number): void {
|
protected fillCharTrueColor(terminal: ITerminal, charData: CharData, x: number, y: number): void {
|
||||||
this._ctx.font = this._getFont(terminal, false, false);
|
this._ctx.font = this._getFont(terminal, false, false);
|
||||||
this._ctx.textBaseline = 'middle';
|
this._ctx.textBaseline = 'top';
|
||||||
this._clipRow(terminal, y);
|
this._clipRow(terminal, y);
|
||||||
this._ctx.fillText(
|
this._ctx.fillText(
|
||||||
charData[CHAR_DATA_CHAR_INDEX],
|
charData[CHAR_DATA_CHAR_INDEX],
|
||||||
x * this._scaledCellWidth + this._scaledCharLeft,
|
x * this._scaledCellWidth + this._scaledCharLeft,
|
||||||
(y + 0.5) * this._scaledCellHeight + this._scaledCharTop);
|
y * this._scaledCellHeight + this._scaledCharTop);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -295,11 +294,11 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
|||||||
private _drawUncachedChars(terminal: ITerminal, chars: string, width: number, fg: number, x: number, y: number, bold: boolean, dim: boolean, italic: boolean): void {
|
private _drawUncachedChars(terminal: ITerminal, chars: 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 = 'middle';
|
this._ctx.textBaseline = 'top';
|
||||||
|
|
||||||
if (fg === INVERTED_DEFAULT_COLOR) {
|
if (fg === INVERTED_DEFAULT_COLOR) {
|
||||||
this._ctx.fillStyle = this._colors.background.css;
|
this._ctx.fillStyle = this._colors.background.css;
|
||||||
} else if (is256Color(fg)) {
|
} else if (fg < 256) {
|
||||||
// 256 color support
|
// 256 color support
|
||||||
this._ctx.fillStyle = this._colors.ansi[fg].css;
|
this._ctx.fillStyle = this._colors.ansi[fg].css;
|
||||||
} else {
|
} else {
|
||||||
@@ -316,7 +315,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
|||||||
this._ctx.fillText(
|
this._ctx.fillText(
|
||||||
chars,
|
chars,
|
||||||
x * this._scaledCellWidth + this._scaledCharLeft,
|
x * this._scaledCellWidth + this._scaledCharLeft,
|
||||||
(y + 0.5) * this._scaledCellHeight + this._scaledCharTop);
|
y * this._scaledCellHeight + this._scaledCharTop);
|
||||||
this._ctx.restore();
|
this._ctx.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { assert } from 'chai';
|
import { assert } from 'chai';
|
||||||
|
|
||||||
import { MockTerminal, MockBuffer } from '../ui/TestUtils.test';
|
import { MockTerminal, MockBuffer } from '../utils/TestUtils.test';
|
||||||
import { CircularList } from '../common/CircularList';
|
import { CircularList } from '../common/CircularList';
|
||||||
|
|
||||||
import { ICharacterJoinerRegistry } from './Types';
|
import { ICharacterJoinerRegistry } from './Types';
|
||||||
@@ -21,21 +21,17 @@ describe('CharacterJoinerRegistry', () => {
|
|||||||
lines.set(2, lineData([['a -> b -', 0xFFFFFFFF], ['> c -> d', 0]]));
|
lines.set(2, lineData([['a -> b -', 0xFFFFFFFF], ['> c -> d', 0]]));
|
||||||
|
|
||||||
lines.set(3, lineData([['no joined ranges']]));
|
lines.set(3, lineData([['no joined ranges']]));
|
||||||
lines.set(4, new BufferLine(0));
|
lines.set(4, new BufferLine());
|
||||||
lines.set(5, lineData([['a', 0x11111111], [' -> b -> c -> '], ['d', 0x22222222]]));
|
lines.set(5, lineData([['a', 0x11111111], [' -> b -> c -> '], ['d', 0x22222222]]));
|
||||||
const line6 = lineData([['wi']]);
|
const line6 = lineData([['wi']]);
|
||||||
line6.resize(line6.length + 1, [0, '¥', 2, '¥'.charCodeAt(0)]);
|
line6.push([0, '¥', 2, '¥'.charCodeAt(0)]);
|
||||||
line6.resize(line6.length + 1, [0, '', 0, null]);
|
line6.push([0, '', 0, null]);
|
||||||
let sub = lineData([['deemo']]);
|
let sub = lineData([['deemo']]);
|
||||||
let oldSize = line6.length;
|
for (let i = 0; i < sub.length; ++i) line6.push(sub.get(i));
|
||||||
line6.resize(oldSize + sub.length, [0, '', 0, 0]);
|
line6.push([0, '\xf0\x9f\x98\x81', 1, 128513]);
|
||||||
for (let i = 0; i < sub.length; ++i) line6.set(i + oldSize, sub.get(i));
|
line6.push([0, ' ', 1, ' '.charCodeAt(0)]);
|
||||||
line6.resize(line6.length + 1, [0, '\xf0\x9f\x98\x81', 1, 128513]);
|
|
||||||
line6.resize(line6.length + 1, [0, ' ', 1, ' '.charCodeAt(0)]);
|
|
||||||
sub = lineData([['jiabc']]);
|
sub = lineData([['jiabc']]);
|
||||||
oldSize = line6.length;
|
for (let i = 0; i < sub.length; ++i) line6.push(sub.get(i));
|
||||||
line6.resize(oldSize + sub.length, [0, '', 0, 0]);
|
|
||||||
for (let i = 0; i < sub.length; ++i) line6.set(i + oldSize, sub.get(i));
|
|
||||||
lines.set(6, line6);
|
lines.set(6, line6);
|
||||||
|
|
||||||
(<MockBuffer>terminal.buffer).setLines(lines);
|
(<MockBuffer>terminal.buffer).setLines(lines);
|
||||||
@@ -268,13 +264,11 @@ describe('CharacterJoinerRegistry', () => {
|
|||||||
type IPartialLineData = ([string] | [string, number]);
|
type IPartialLineData = ([string] | [string, number]);
|
||||||
|
|
||||||
function lineData(data: IPartialLineData[]): IBufferLine {
|
function lineData(data: IPartialLineData[]): IBufferLine {
|
||||||
const tline = new BufferLine(0);
|
const tline = new BufferLine();
|
||||||
for (let i = 0; i < data.length; ++i) {
|
for (let i = 0; i < data.length; ++i) {
|
||||||
const line = data[i][0];
|
const line = data[i][0];
|
||||||
const attr = <number>(data[i][1] || 0);
|
const attr = <number>(data[i][1] || 0);
|
||||||
const offset = tline.length;
|
line.split('').map(char => tline.push([attr, char, 1, char.charCodeAt(0)]));
|
||||||
tline.resize(tline.length + line.split('').length, [0, '', 0, 0]);
|
|
||||||
line.split('').map((char, idx) => tline.set(idx + offset, [attr, char, 1, char.charCodeAt(0)]));
|
|
||||||
}
|
}
|
||||||
return tline;
|
return tline;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,8 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { IColorManager, IColor, IColorSet } from './Types';
|
import { IColorManager } from './Types';
|
||||||
|
import { IColor, IColorSet } from '../shared/Types';
|
||||||
import { ITheme } from 'xterm';
|
import { ITheme } from 'xterm';
|
||||||
|
|
||||||
const DEFAULT_FOREGROUND = fromHex('#ffffff');
|
const DEFAULT_FOREGROUND = fromHex('#ffffff');
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import { ILinkHoverEvent, ITerminal, ILinkifierAccessor, LinkHoverEventTypes } f
|
|||||||
import { IColorSet, IRenderDimensions } from './Types';
|
import { IColorSet, IRenderDimensions } from './Types';
|
||||||
import { BaseRenderLayer } from './BaseRenderLayer';
|
import { BaseRenderLayer } from './BaseRenderLayer';
|
||||||
import { INVERTED_DEFAULT_COLOR } from './atlas/Types';
|
import { INVERTED_DEFAULT_COLOR } from './atlas/Types';
|
||||||
import { is256Color } from './atlas/CharAtlasUtils';
|
|
||||||
|
|
||||||
export class LinkRenderLayer extends BaseRenderLayer {
|
export class LinkRenderLayer extends BaseRenderLayer {
|
||||||
private _state: ILinkHoverEvent = null;
|
private _state: ILinkHoverEvent = null;
|
||||||
@@ -43,7 +42,7 @@ export class LinkRenderLayer extends BaseRenderLayer {
|
|||||||
private _onLinkHover(e: ILinkHoverEvent): void {
|
private _onLinkHover(e: ILinkHoverEvent): void {
|
||||||
if (e.fg === INVERTED_DEFAULT_COLOR) {
|
if (e.fg === INVERTED_DEFAULT_COLOR) {
|
||||||
this._ctx.fillStyle = this._colors.background.css;
|
this._ctx.fillStyle = this._colors.background.css;
|
||||||
} else if (is256Color(e.fg)) {
|
} else if (e.fg < 256) {
|
||||||
// 256 color support
|
// 256 color support
|
||||||
this._ctx.fillStyle = this._colors.ansi[e.fg].css;
|
this._ctx.fillStyle = this._colors.ansi[e.fg].css;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -3,13 +3,12 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { CHAR_DATA_ATTR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, NULL_CELL_CODE, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE } from '../Buffer';
|
import { CHAR_DATA_ATTR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, NULL_CELL_CODE } from '../Buffer';
|
||||||
import { FLAGS, IColorSet, IRenderDimensions, ICharacterJoinerRegistry } from './Types';
|
import { FLAGS, IColorSet, IRenderDimensions, ICharacterJoinerRegistry } from './Types';
|
||||||
import { CharData, ITerminal } from '../Types';
|
import { CharData, ITerminal } from '../Types';
|
||||||
import { INVERTED_DEFAULT_COLOR, DEFAULT_COLOR } from './atlas/Types';
|
import { INVERTED_DEFAULT_COLOR } from './atlas/Types';
|
||||||
import { GridCache } from './GridCache';
|
import { GridCache } from './GridCache';
|
||||||
import { BaseRenderLayer } from './BaseRenderLayer';
|
import { BaseRenderLayer } from './BaseRenderLayer';
|
||||||
import { is256Color } from './atlas/CharAtlasUtils';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This CharData looks like a null character, which will forc a clear and render
|
* This CharData looks like a null character, which will forc a clear and render
|
||||||
@@ -73,11 +72,11 @@ export class TextRenderLayer extends BaseRenderLayer {
|
|||||||
const joinedRanges = joinerRegistry ? joinerRegistry.getJoinedCharacters(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.get(x);
|
const charData = line.get(x);
|
||||||
let code: number = <number>charData[CHAR_DATA_CODE_INDEX] || WHITESPACE_CELL_CODE;
|
let code: number = <number>charData[CHAR_DATA_CODE_INDEX];
|
||||||
|
|
||||||
// Can either represent character(s) for a single cell or multiple cells
|
// Can either represent character(s) for a single cell or multiple cells
|
||||||
// if indicated by a character joiner.
|
// if indicated by a character joiner.
|
||||||
let chars: string = charData[CHAR_DATA_CHAR_INDEX] || WHITESPACE_CELL_CHAR;
|
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];
|
||||||
|
|
||||||
@@ -144,10 +143,10 @@ export class TextRenderLayer extends BaseRenderLayer {
|
|||||||
const temp = bg;
|
const temp = bg;
|
||||||
bg = fg;
|
bg = fg;
|
||||||
fg = temp;
|
fg = temp;
|
||||||
if (fg === DEFAULT_COLOR) {
|
if (fg === 256) {
|
||||||
fg = INVERTED_DEFAULT_COLOR;
|
fg = INVERTED_DEFAULT_COLOR;
|
||||||
}
|
}
|
||||||
if (bg === DEFAULT_COLOR) {
|
if (bg === 257) {
|
||||||
bg = INVERTED_DEFAULT_COLOR;
|
bg = INVERTED_DEFAULT_COLOR;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -187,7 +186,7 @@ export class TextRenderLayer extends BaseRenderLayer {
|
|||||||
let nextFillStyle = null; // null represents default background color
|
let nextFillStyle = null; // null represents default background color
|
||||||
if (bg === INVERTED_DEFAULT_COLOR) {
|
if (bg === INVERTED_DEFAULT_COLOR) {
|
||||||
nextFillStyle = this._colors.foreground.css;
|
nextFillStyle = this._colors.foreground.css;
|
||||||
} else if (is256Color(bg)) {
|
} else if (bg < 256) {
|
||||||
nextFillStyle = this._colors.ansi[bg].css;
|
nextFillStyle = this._colors.ansi[bg].css;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,7 +230,7 @@ export class TextRenderLayer extends BaseRenderLayer {
|
|||||||
this._ctx.save();
|
this._ctx.save();
|
||||||
if (fg === INVERTED_DEFAULT_COLOR) {
|
if (fg === INVERTED_DEFAULT_COLOR) {
|
||||||
this._ctx.fillStyle = this._colors.background.css;
|
this._ctx.fillStyle = this._colors.background.css;
|
||||||
} else if (is256Color(fg)) {
|
} else if (fg < 256) {
|
||||||
// 256 color support
|
// 256 color support
|
||||||
this._ctx.fillStyle = this._colors.ansi[fg].css;
|
this._ctx.fillStyle = this._colors.ansi[fg].css;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+4
-14
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
import { ITerminal, CharacterJoinerHandler } from '../Types';
|
import { ITerminal, CharacterJoinerHandler } from '../Types';
|
||||||
import { IEventEmitter, ITheme, IDisposable } from 'xterm';
|
import { IEventEmitter, ITheme, IDisposable } from 'xterm';
|
||||||
|
import { IColorSet } from '../shared/Types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flags used to render terminal text properly.
|
* Flags used to render terminal text properly.
|
||||||
@@ -47,6 +48,9 @@ export interface IColorManager {
|
|||||||
colors: IColorSet;
|
colors: IColorSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: We should probably rewrite the imports for IColorSet, but there's a lot of them
|
||||||
|
export { IColorSet };
|
||||||
|
|
||||||
export interface IRenderDimensions {
|
export interface IRenderDimensions {
|
||||||
scaledCharWidth: number;
|
scaledCharWidth: number;
|
||||||
scaledCharHeight: number;
|
scaledCharHeight: number;
|
||||||
@@ -130,17 +134,3 @@ export interface ICharacterJoinerRegistry {
|
|||||||
deregisterCharacterJoiner(joinerId: number): boolean;
|
deregisterCharacterJoiner(joinerId: number): boolean;
|
||||||
getJoinedCharacters(row: number): [number, number][];
|
getJoinedCharacters(row: number): [number, number][];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IColor {
|
|
||||||
css: string;
|
|
||||||
rgba: number; // 32-bit int with rgba in each byte
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IColorSet {
|
|
||||||
foreground: IColor;
|
|
||||||
background: IColor;
|
|
||||||
cursor: IColor;
|
|
||||||
cursorAccent: IColor;
|
|
||||||
selection: IColor;
|
|
||||||
ansi: IColor[];
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -5,12 +5,12 @@
|
|||||||
|
|
||||||
import { ITerminal } from '../../Types';
|
import { ITerminal } from '../../Types';
|
||||||
import { IColorSet } from '../Types';
|
import { IColorSet } from '../Types';
|
||||||
|
import { ICharAtlasConfig } from '../../shared/atlas/Types';
|
||||||
import { generateConfig, configEquals } from './CharAtlasUtils';
|
import { generateConfig, configEquals } from './CharAtlasUtils';
|
||||||
import BaseCharAtlas from './BaseCharAtlas';
|
import BaseCharAtlas from './BaseCharAtlas';
|
||||||
import DynamicCharAtlas from './DynamicCharAtlas';
|
import DynamicCharAtlas from './DynamicCharAtlas';
|
||||||
import NoneCharAtlas from './NoneCharAtlas';
|
import NoneCharAtlas from './NoneCharAtlas';
|
||||||
import StaticCharAtlas from './StaticCharAtlas';
|
import StaticCharAtlas from './StaticCharAtlas';
|
||||||
import { ICharAtlasConfig } from './Types';
|
|
||||||
|
|
||||||
const charAtlasImplementations = {
|
const charAtlasImplementations = {
|
||||||
'none': NoneCharAtlas,
|
'none': NoneCharAtlas,
|
||||||
@@ -42,6 +42,8 @@ export function acquireCharAtlas(
|
|||||||
): BaseCharAtlas {
|
): BaseCharAtlas {
|
||||||
const newConfig = generateConfig(scaledCharWidth, scaledCharHeight, terminal, colors);
|
const newConfig = generateConfig(scaledCharWidth, scaledCharHeight, terminal, colors);
|
||||||
|
|
||||||
|
// TODO: Currently if a terminal changes configs it will not free the entry reference (until it's disposed)
|
||||||
|
|
||||||
// Check to see if the terminal already owns this config
|
// Check to see if the terminal already owns this config
|
||||||
for (let i = 0; i < charAtlasCache.length; i++) {
|
for (let i = 0; i < charAtlasCache.length; i++) {
|
||||||
const entry = charAtlasCache[i];
|
const entry = charAtlasCache[i];
|
||||||
@@ -52,7 +54,6 @@ export function acquireCharAtlas(
|
|||||||
}
|
}
|
||||||
// The configs differ, release the terminal from the entry
|
// The configs differ, release the terminal from the entry
|
||||||
if (entry.ownedBy.length === 1) {
|
if (entry.ownedBy.length === 1) {
|
||||||
entry.atlas.dispose();
|
|
||||||
charAtlasCache.splice(i, 1);
|
charAtlasCache.splice(i, 1);
|
||||||
} else {
|
} else {
|
||||||
entry.ownedBy.splice(ownedByIndex, 1);
|
entry.ownedBy.splice(ownedByIndex, 1);
|
||||||
@@ -93,7 +94,6 @@ export function removeTerminalFromCache(terminal: ITerminal): void {
|
|||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
if (charAtlasCache[i].ownedBy.length === 1) {
|
if (charAtlasCache[i].ownedBy.length === 1) {
|
||||||
// Remove the cache entry if it's the only terminal
|
// Remove the cache entry if it's the only terminal
|
||||||
charAtlasCache[i].atlas.dispose();
|
|
||||||
charAtlasCache.splice(i, 1);
|
charAtlasCache.splice(i, 1);
|
||||||
} else {
|
} else {
|
||||||
// Remove the reference from the cache entry
|
// Remove the reference from the cache entry
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
import { ITerminal } from '../../Types';
|
import { ITerminal } from '../../Types';
|
||||||
import { IColorSet } from '../Types';
|
import { IColorSet } from '../Types';
|
||||||
import { DEFAULT_COLOR, ICharAtlasConfig } from './Types';
|
import { ICharAtlasConfig } from '../../shared/atlas/Types';
|
||||||
|
|
||||||
export function generateConfig(scaledCharWidth: number, scaledCharHeight: number, terminal: ITerminal, colors: IColorSet): ICharAtlasConfig {
|
export function generateConfig(scaledCharWidth: number, scaledCharHeight: number, terminal: ITerminal, colors: IColorSet): ICharAtlasConfig {
|
||||||
// null out some fields that don't matter
|
// null out some fields that don't matter
|
||||||
@@ -51,7 +51,3 @@ export function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean
|
|||||||
a.colors.foreground === b.colors.foreground &&
|
a.colors.foreground === b.colors.foreground &&
|
||||||
a.colors.background === b.colors.background;
|
a.colors.background === b.colors.background;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function is256Color(colorCode: number): boolean {
|
|
||||||
return colorCode < DEFAULT_COLOR;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -3,13 +3,14 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { DIM_OPACITY, IGlyphIdentifier, INVERTED_DEFAULT_COLOR, ICharAtlasConfig } from './Types';
|
import { DIM_OPACITY, IGlyphIdentifier, INVERTED_DEFAULT_COLOR } from './Types';
|
||||||
|
import { ICharAtlasConfig } from '../../shared/atlas/Types';
|
||||||
|
import { IColor } from '../../shared/Types';
|
||||||
import BaseCharAtlas from './BaseCharAtlas';
|
import BaseCharAtlas from './BaseCharAtlas';
|
||||||
import { DEFAULT_ANSI_COLORS } from '../ColorManager';
|
import { DEFAULT_ANSI_COLORS } from '../ColorManager';
|
||||||
import { clearColor } from './CharAtlasGenerator';
|
import { clearColor } from '../../shared/atlas/CharAtlasGenerator';
|
||||||
import LRUMap from './LRUMap';
|
import LRUMap from './LRUMap';
|
||||||
import { isFirefox, isSafari } from '../../core/Platform';
|
import { isFirefox, isSafari } from '../../shared/utils/Browser';
|
||||||
import { IColor } from '../Types';
|
|
||||||
|
|
||||||
// In practice we're probably never going to exhaust a texture this large. For debugging purposes,
|
// In practice we're probably never going to exhaust a texture this large. For debugging purposes,
|
||||||
// however, it can be useful to set this to a really tiny value, to verify that LRU eviction works.
|
// however, it can be useful to set this to a really tiny value, to verify that LRU eviction works.
|
||||||
@@ -41,7 +42,7 @@ interface IGlyphCacheValue {
|
|||||||
inBitmap: boolean;
|
inBitmap: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getGlyphCacheKey(glyph: IGlyphIdentifier): number {
|
function getGlyphCacheKey(glyph: IGlyphIdentifier): number {
|
||||||
// Note that this only returns a valid key when code < 256
|
// Note that this only returns a valid key when code < 256
|
||||||
// Layout:
|
// Layout:
|
||||||
// 0b00000000000000000000000000000001: italic (1)
|
// 0b00000000000000000000000000000001: italic (1)
|
||||||
@@ -128,17 +129,12 @@ export default class DynamicCharAtlas extends BaseCharAtlas {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exit early for uncachable glyphs
|
|
||||||
if (!this._canCache(glyph)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const glyphKey = getGlyphCacheKey(glyph);
|
const glyphKey = getGlyphCacheKey(glyph);
|
||||||
const cacheValue = this._cacheMap.get(glyphKey);
|
const cacheValue = this._cacheMap.get(glyphKey);
|
||||||
if (cacheValue !== null && cacheValue !== undefined) {
|
if (cacheValue !== null && cacheValue !== undefined) {
|
||||||
this._drawFromCache(ctx, cacheValue, x, y);
|
this._drawFromCache(ctx, cacheValue, x, y);
|
||||||
return true;
|
return true;
|
||||||
} else if (this._drawToCacheCount < FRAME_CACHE_DRAW_LIMIT) {
|
} else if (this._canCache(glyph) && this._drawToCacheCount < FRAME_CACHE_DRAW_LIMIT) {
|
||||||
let index;
|
let index;
|
||||||
if (this._cacheMap.size < this._cacheMap.capacity) {
|
if (this._cacheMap.size < this._cacheMap.capacity) {
|
||||||
index = this._cacheMap.size;
|
index = this._cacheMap.size;
|
||||||
@@ -250,7 +246,7 @@ export default class DynamicCharAtlas extends BaseCharAtlas {
|
|||||||
const fontStyle = glyph.italic ? 'italic' : '';
|
const fontStyle = glyph.italic ? 'italic' : '';
|
||||||
this._tmpCtx.font =
|
this._tmpCtx.font =
|
||||||
`${fontStyle} ${fontWeight} ${this._config.fontSize * this._config.devicePixelRatio}px ${this._config.fontFamily}`;
|
`${fontStyle} ${fontWeight} ${this._config.fontSize * this._config.devicePixelRatio}px ${this._config.fontFamily}`;
|
||||||
this._tmpCtx.textBaseline = 'middle';
|
this._tmpCtx.textBaseline = 'top';
|
||||||
|
|
||||||
this._tmpCtx.fillStyle = this._getForegroundColor(glyph).css;
|
this._tmpCtx.fillStyle = this._getForegroundColor(glyph).css;
|
||||||
|
|
||||||
@@ -259,7 +255,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, this._config.scaledCharHeight / 2);
|
this._tmpCtx.fillText(glyph.chars, 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
|
||||||
|
|||||||
@@ -5,7 +5,8 @@
|
|||||||
* A dummy CharAtlas implementation that always fails to draw characters.
|
* A dummy CharAtlas implementation that always fails to draw characters.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { IGlyphIdentifier, ICharAtlasConfig } from './Types';
|
import { IGlyphIdentifier } from './Types';
|
||||||
|
import { ICharAtlasConfig } from '../../shared/atlas/Types';
|
||||||
import BaseCharAtlas from './BaseCharAtlas';
|
import BaseCharAtlas from './BaseCharAtlas';
|
||||||
|
|
||||||
export default class NoneCharAtlas extends BaseCharAtlas {
|
export default class NoneCharAtlas extends BaseCharAtlas {
|
||||||
|
|||||||
@@ -3,10 +3,10 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { DIM_OPACITY, IGlyphIdentifier, DEFAULT_COLOR, ICharAtlasConfig, CHAR_ATLAS_CELL_SPACING } from './Types';
|
import { DIM_OPACITY, IGlyphIdentifier } from './Types';
|
||||||
import { generateStaticCharAtlasTexture } from './CharAtlasGenerator';
|
import { CHAR_ATLAS_CELL_SPACING, ICharAtlasConfig } from '../../shared/atlas/Types';
|
||||||
|
import { generateStaticCharAtlasTexture } from '../../shared/atlas/CharAtlasGenerator';
|
||||||
import BaseCharAtlas from './BaseCharAtlas';
|
import BaseCharAtlas from './BaseCharAtlas';
|
||||||
import { is256Color } from './CharAtlasUtils';
|
|
||||||
|
|
||||||
export default class StaticCharAtlas extends BaseCharAtlas {
|
export default class StaticCharAtlas extends BaseCharAtlas {
|
||||||
private _texture: HTMLCanvasElement | ImageBitmap;
|
private _texture: HTMLCanvasElement | ImageBitmap;
|
||||||
@@ -41,8 +41,8 @@ export default class StaticCharAtlas extends BaseCharAtlas {
|
|||||||
const isAscii = glyph.code < 256;
|
const isAscii = glyph.code < 256;
|
||||||
// A color is basic if it is one of the 4 bit ANSI colors.
|
// A color is basic if it is one of the 4 bit ANSI colors.
|
||||||
const isBasicColor = glyph.fg < 16;
|
const isBasicColor = glyph.fg < 16;
|
||||||
const isDefaultColor = glyph.fg === DEFAULT_COLOR;
|
const isDefaultColor = glyph.fg >= 256;
|
||||||
const isDefaultBackground = glyph.bg === DEFAULT_COLOR;
|
const isDefaultBackground = glyph.bg >= 256;
|
||||||
return isAscii && (isBasicColor || isDefaultColor) && isDefaultBackground && !glyph.italic;
|
return isAscii && (isBasicColor || isDefaultColor) && isDefaultBackground && !glyph.italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,9 +58,9 @@ export default class StaticCharAtlas extends BaseCharAtlas {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let colorIndex = 0;
|
let colorIndex = 0;
|
||||||
if (is256Color(glyph.fg)) {
|
if (glyph.fg < 256) {
|
||||||
colorIndex = 2 + glyph.fg + (glyph.bold ? 16 : 0);
|
colorIndex = 2 + glyph.fg + (glyph.bold ? 16 : 0);
|
||||||
} else if (glyph.fg === DEFAULT_COLOR) {
|
} else {
|
||||||
// If default color and bold
|
// If default color and bold
|
||||||
if (glyph.bold) {
|
if (glyph.bold) {
|
||||||
colorIndex = 1;
|
colorIndex = 1;
|
||||||
|
|||||||
@@ -3,15 +3,9 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { FontWeight } from 'xterm';
|
export const INVERTED_DEFAULT_COLOR = -1;
|
||||||
import { IColorSet } from '../Types';
|
|
||||||
|
|
||||||
export const DEFAULT_COLOR = 256;
|
|
||||||
export const INVERTED_DEFAULT_COLOR = 257;
|
|
||||||
export const DIM_OPACITY = 0.5;
|
export const DIM_OPACITY = 0.5;
|
||||||
|
|
||||||
export const CHAR_ATLAS_CELL_SPACING = 1;
|
|
||||||
|
|
||||||
export interface IGlyphIdentifier {
|
export interface IGlyphIdentifier {
|
||||||
chars: string;
|
chars: string;
|
||||||
code: number;
|
code: number;
|
||||||
@@ -21,16 +15,3 @@ export interface IGlyphIdentifier {
|
|||||||
dim: boolean;
|
dim: boolean;
|
||||||
italic: boolean;
|
italic: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ICharAtlasConfig {
|
|
||||||
type: 'none' | 'static' | 'dynamic';
|
|
||||||
devicePixelRatio: number;
|
|
||||||
fontSize: number;
|
|
||||||
fontFamily: string;
|
|
||||||
fontWeight: FontWeight;
|
|
||||||
fontWeightBold: FontWeight;
|
|
||||||
scaledCharWidth: number;
|
|
||||||
scaledCharHeight: number;
|
|
||||||
allowTransparency: boolean;
|
|
||||||
colors: IColorSet;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import { EventEmitter } from '../../common/EventEmitter';
|
|||||||
import { ColorManager } from '../ColorManager';
|
import { ColorManager } from '../ColorManager';
|
||||||
import { RenderDebouncer } from '../../ui/RenderDebouncer';
|
import { RenderDebouncer } from '../../ui/RenderDebouncer';
|
||||||
import { BOLD_CLASS, ITALIC_CLASS, CURSOR_CLASS, CURSOR_STYLE_BLOCK_CLASS, CURSOR_STYLE_BAR_CLASS, CURSOR_STYLE_UNDERLINE_CLASS, DomRendererRowFactory } from './DomRendererRowFactory';
|
import { BOLD_CLASS, ITALIC_CLASS, CURSOR_CLASS, CURSOR_STYLE_BLOCK_CLASS, CURSOR_STYLE_BAR_CLASS, CURSOR_STYLE_UNDERLINE_CLASS, DomRendererRowFactory } from './DomRendererRowFactory';
|
||||||
import { INVERTED_DEFAULT_COLOR } from '../atlas/Types';
|
|
||||||
|
|
||||||
const TERMINAL_CLASS_PREFIX = 'xterm-dom-renderer-owner-';
|
const TERMINAL_CLASS_PREFIX = 'xterm-dom-renderer-owner-';
|
||||||
const ROW_CONTAINER_CLASS = 'xterm-rows';
|
const ROW_CONTAINER_CLASS = 'xterm-rows';
|
||||||
@@ -95,25 +94,22 @@ export class DomRenderer extends EventEmitter implements IRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _updateDimensions(): void {
|
private _updateDimensions(): void {
|
||||||
this.dimensions.scaledCharWidth = Math.floor(this._terminal.charMeasure.width * window.devicePixelRatio);
|
this.dimensions.scaledCharWidth = this._terminal.charMeasure.width * window.devicePixelRatio;
|
||||||
this.dimensions.scaledCharHeight = Math.ceil(this._terminal.charMeasure.height * window.devicePixelRatio);
|
this.dimensions.scaledCharHeight = this._terminal.charMeasure.height * window.devicePixelRatio;
|
||||||
this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth + Math.round(this._terminal.options.letterSpacing);
|
this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth;
|
||||||
this.dimensions.scaledCellHeight = Math.floor(this.dimensions.scaledCharHeight * this._terminal.options.lineHeight);
|
this.dimensions.scaledCellHeight = this.dimensions.scaledCharHeight;
|
||||||
this.dimensions.scaledCharLeft = 0;
|
this.dimensions.scaledCharLeft = 0;
|
||||||
this.dimensions.scaledCharTop = 0;
|
this.dimensions.scaledCharTop = 0;
|
||||||
this.dimensions.scaledCanvasWidth = this.dimensions.scaledCellWidth * this._terminal.cols;
|
this.dimensions.scaledCanvasWidth = this.dimensions.scaledCellWidth * this._terminal.cols;
|
||||||
this.dimensions.scaledCanvasHeight = this.dimensions.scaledCellHeight * this._terminal.rows;
|
this.dimensions.scaledCanvasHeight = this.dimensions.scaledCellHeight * this._terminal.rows;
|
||||||
this.dimensions.canvasWidth = Math.round(this.dimensions.scaledCanvasWidth / window.devicePixelRatio);
|
this.dimensions.canvasWidth = this._terminal.charMeasure.width * this._terminal.cols;
|
||||||
this.dimensions.canvasHeight = Math.round(this.dimensions.scaledCanvasHeight / window.devicePixelRatio);
|
this.dimensions.canvasHeight = this._terminal.charMeasure.height * this._terminal.rows;
|
||||||
this.dimensions.actualCellWidth = this.dimensions.canvasWidth / this._terminal.cols;
|
this.dimensions.actualCellWidth = this._terminal.charMeasure.width;
|
||||||
this.dimensions.actualCellHeight = this.dimensions.canvasHeight / this._terminal.rows;
|
this.dimensions.actualCellHeight = this._terminal.charMeasure.height;
|
||||||
|
|
||||||
this._rowElements.forEach(element => {
|
this._rowElements.forEach(element => {
|
||||||
element.style.width = `${this.dimensions.canvasWidth}px`;
|
element.style.width = `${this.dimensions.canvasWidth}px`;
|
||||||
element.style.height = `${this.dimensions.actualCellHeight}px`;
|
element.style.height = `${this._terminal.charMeasure.height}px`;
|
||||||
element.style.lineHeight = `${this.dimensions.actualCellHeight}px`;
|
|
||||||
// Make sure rows don't overflow onto following row
|
|
||||||
element.style.overflow = 'hidden';
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!this._dimensionsStyleElement) {
|
if (!this._dimensionsStyleElement) {
|
||||||
@@ -126,14 +122,14 @@ export class DomRenderer extends EventEmitter implements IRenderer {
|
|||||||
` display: inline-block;` +
|
` display: inline-block;` +
|
||||||
` height: 100%;` +
|
` height: 100%;` +
|
||||||
` vertical-align: top;` +
|
` vertical-align: top;` +
|
||||||
` width: ${this.dimensions.actualCellWidth}px` +
|
` width: ${this._terminal.charMeasure.width}px` +
|
||||||
`}`;
|
`}`;
|
||||||
|
|
||||||
this._dimensionsStyleElement.innerHTML = styles;
|
this._dimensionsStyleElement.innerHTML = styles;
|
||||||
|
|
||||||
this._selectionContainer.style.height = (<any>this._terminal)._viewportElement.style.height;
|
this._selectionContainer.style.height = (<any>this._terminal)._viewportElement.style.height;
|
||||||
this._terminal.screenElement.style.width = `${this.dimensions.canvasWidth}px`;
|
this._rowContainer.style.width = `${this.dimensions.canvasWidth}px`;
|
||||||
this._terminal.screenElement.style.height = `${this.dimensions.canvasHeight}px`;
|
this._rowContainer.style.height = `${this.dimensions.canvasHeight}px`;
|
||||||
}
|
}
|
||||||
|
|
||||||
public setTheme(theme: ITheme | undefined): IColorSet {
|
public setTheme(theme: ITheme | undefined): IColorSet {
|
||||||
@@ -200,9 +196,6 @@ export class DomRenderer extends EventEmitter implements IRenderer {
|
|||||||
`${this._terminalSelector} .${FG_CLASS_PREFIX}${i} { color: ${c.css}; }` +
|
`${this._terminalSelector} .${FG_CLASS_PREFIX}${i} { color: ${c.css}; }` +
|
||||||
`${this._terminalSelector} .${BG_CLASS_PREFIX}${i} { background-color: ${c.css}; }`;
|
`${this._terminalSelector} .${BG_CLASS_PREFIX}${i} { background-color: ${c.css}; }`;
|
||||||
});
|
});
|
||||||
styles +=
|
|
||||||
`${this._terminalSelector} .${FG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR} { color: ${this.colorManager.colors.background.css}; }` +
|
|
||||||
`${this._terminalSelector} .${BG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR} { background-color: ${this.colorManager.colors.foreground.css}; }`;
|
|
||||||
|
|
||||||
this._themeStyleElement.innerHTML = styles;
|
this._themeStyleElement.innerHTML = styles;
|
||||||
return this.colorManager.colors;
|
return this.colorManager.colors;
|
||||||
@@ -297,10 +290,10 @@ export class DomRenderer extends EventEmitter implements IRenderer {
|
|||||||
*/
|
*/
|
||||||
private _createSelectionElement(row: number, colStart: number, colEnd: number, rowCount: number = 1): HTMLElement {
|
private _createSelectionElement(row: number, colStart: number, colEnd: number, rowCount: number = 1): HTMLElement {
|
||||||
const element = document.createElement('div');
|
const element = document.createElement('div');
|
||||||
element.style.height = `${rowCount * this.dimensions.actualCellHeight}px`;
|
element.style.height = `${rowCount * this._terminal.charMeasure.height}px`;
|
||||||
element.style.top = `${row * this.dimensions.actualCellHeight}px`;
|
element.style.top = `${row * this._terminal.charMeasure.height}px`;
|
||||||
element.style.left = `${colStart * this.dimensions.actualCellWidth}px`;
|
element.style.left = `${colStart * this._terminal.charMeasure.width}px`;
|
||||||
element.style.width = `${this.dimensions.actualCellWidth * (colEnd - colStart)}px`;
|
element.style.width = `${this._terminal.charMeasure.width * (colEnd - colStart)}px`;
|
||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -336,7 +329,7 @@ export class DomRenderer extends EventEmitter implements IRenderer {
|
|||||||
const row = y + terminal.buffer.ydisp;
|
const row = y + terminal.buffer.ydisp;
|
||||||
const lineData = terminal.buffer.lines.get(row);
|
const lineData = terminal.buffer.lines.get(row);
|
||||||
const cursorStyle = terminal.options.cursorStyle;
|
const cursorStyle = terminal.options.cursorStyle;
|
||||||
rowElement.appendChild(this._rowFactory.createRow(lineData, row === cursorAbsoluteY, cursorStyle, cursorX, this.dimensions.actualCellWidth, terminal.cols));
|
rowElement.appendChild(this._rowFactory.createRow(lineData, row === cursorAbsoluteY, cursorStyle, cursorX, terminal.charMeasure.width, terminal.cols));
|
||||||
}
|
}
|
||||||
|
|
||||||
this._terminal.emit('refresh', {start, end});
|
this._terminal.emit('refresh', {start, end});
|
||||||
@@ -359,16 +352,10 @@ export class DomRenderer extends EventEmitter implements IRenderer {
|
|||||||
|
|
||||||
private _setCellUnderline(x: number, x2: number, y: number, y2: number, cols: number, enabled: boolean): void {
|
private _setCellUnderline(x: number, x2: number, y: number, y2: number, cols: number, enabled: boolean): void {
|
||||||
while (x !== x2 || y !== y2) {
|
while (x !== x2 || y !== y2) {
|
||||||
const row = this._rowElements[y];
|
const span = <HTMLElement>this._rowElements[y].children[x];
|
||||||
if (!row) {
|
span.style.textDecoration = enabled ? 'underline' : 'none';
|
||||||
return;
|
x = (x + 1) % cols;
|
||||||
}
|
if (x === 0) {
|
||||||
const span = <HTMLElement>row.children[x];
|
|
||||||
if (span) {
|
|
||||||
span.style.textDecoration = enabled ? 'underline' : 'none';
|
|
||||||
}
|
|
||||||
if (++x >= cols) {
|
|
||||||
x = 0;
|
|
||||||
y++;
|
y++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import { DEFAULT_ATTR, NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR } from '.
|
|||||||
import { FLAGS } from '../Types';
|
import { FLAGS } from '../Types';
|
||||||
import { BufferLine } from '../../BufferLine';
|
import { BufferLine } from '../../BufferLine';
|
||||||
import { IBufferLine } from '../../Types';
|
import { IBufferLine } from '../../Types';
|
||||||
import { DEFAULT_COLOR } from '../atlas/Types';
|
|
||||||
|
|
||||||
describe('DomRendererRowFactory', () => {
|
describe('DomRendererRowFactory', () => {
|
||||||
let dom: jsdom.JSDOM;
|
let dom: jsdom.JSDOM;
|
||||||
@@ -24,10 +23,11 @@ describe('DomRendererRowFactory', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('createRow', () => {
|
describe('createRow', () => {
|
||||||
it('should not create anything for an empty row', () => {
|
it('should create an element for every character in the row', () => {
|
||||||
const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20);
|
const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20);
|
||||||
assert.equal(getFragmentHtml(fragment),
|
assert.equal(getFragmentHtml(fragment),
|
||||||
''
|
'<span> </span>' +
|
||||||
|
'<span> </span>'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -45,7 +45,8 @@ describe('DomRendererRowFactory', () => {
|
|||||||
for (const style of ['block', 'bar', 'underline']) {
|
for (const style of ['block', 'bar', 'underline']) {
|
||||||
const fragment = rowFactory.createRow(lineData, true, style, 0, 5, 20);
|
const fragment = rowFactory.createRow(lineData, true, style, 0, 5, 20);
|
||||||
assert.equal(getFragmentHtml(fragment),
|
assert.equal(getFragmentHtml(fragment),
|
||||||
`<span class="xterm-cursor xterm-cursor-${style}"> </span>`
|
`<span class="xterm-cursor xterm-cursor-${style}"> </span>` +
|
||||||
|
'<span> </span>'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -64,7 +65,8 @@ describe('DomRendererRowFactory', () => {
|
|||||||
lineData.set(0, [DEFAULT_ATTR | (FLAGS.BOLD << 18), 'a', 1, 'a'.charCodeAt(0)]);
|
lineData.set(0, [DEFAULT_ATTR | (FLAGS.BOLD << 18), 'a', 1, 'a'.charCodeAt(0)]);
|
||||||
const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20);
|
const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20);
|
||||||
assert.equal(getFragmentHtml(fragment),
|
assert.equal(getFragmentHtml(fragment),
|
||||||
'<span class="xterm-bold">a</span>'
|
'<span class="xterm-bold">a</span>' +
|
||||||
|
'<span> </span>'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -72,28 +74,31 @@ describe('DomRendererRowFactory', () => {
|
|||||||
lineData.set(0, [DEFAULT_ATTR | (FLAGS.ITALIC << 18), 'a', 1, 'a'.charCodeAt(0)]);
|
lineData.set(0, [DEFAULT_ATTR | (FLAGS.ITALIC << 18), 'a', 1, 'a'.charCodeAt(0)]);
|
||||||
const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20);
|
const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20);
|
||||||
assert.equal(getFragmentHtml(fragment),
|
assert.equal(getFragmentHtml(fragment),
|
||||||
'<span class="xterm-italic">a</span>'
|
'<span class="xterm-italic">a</span>' +
|
||||||
|
'<span> </span>'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should add classes for 256 foreground colors', () => {
|
it('should add classes for 256 foreground colors', () => {
|
||||||
const defaultAttrNoFgColor = (0 << 9) | (DEFAULT_COLOR << 0);
|
const defaultAttrNoFgColor = (0 << 9) | (256 << 0);
|
||||||
for (let i = 0; i < 256; i++) {
|
for (let i = 0; i < 256; i++) {
|
||||||
lineData.set(0, [defaultAttrNoFgColor | (i << 9), 'a', 1, 'a'.charCodeAt(0)]);
|
lineData.set(0, [defaultAttrNoFgColor | (i << 9), 'a', 1, 'a'.charCodeAt(0)]);
|
||||||
const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20);
|
const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20);
|
||||||
assert.equal(getFragmentHtml(fragment),
|
assert.equal(getFragmentHtml(fragment),
|
||||||
`<span class="xterm-fg-${i}">a</span>`
|
`<span class="xterm-fg-${i}">a</span>` +
|
||||||
|
'<span> </span>'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should add classes for 256 background colors', () => {
|
it('should add classes for 256 background colors', () => {
|
||||||
const defaultAttrNoBgColor = (DEFAULT_ATTR << 9) | (0 << 0);
|
const defaultAttrNoBgColor = (257 << 9) | (0 << 0);
|
||||||
for (let i = 0; i < 256; i++) {
|
for (let i = 0; i < 256; i++) {
|
||||||
lineData.set(0, [defaultAttrNoBgColor | (i << 0), 'a', 1, 'a'.charCodeAt(0)]);
|
lineData.set(0, [defaultAttrNoBgColor | (i << 0), 'a', 1, 'a'.charCodeAt(0)]);
|
||||||
const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20);
|
const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20);
|
||||||
assert.equal(getFragmentHtml(fragment),
|
assert.equal(getFragmentHtml(fragment),
|
||||||
`<span class="xterm-bg-${i}">a</span>`
|
`<span class="xterm-bg-${i}">a</span>` +
|
||||||
|
'<span> </span>'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -102,32 +107,36 @@ describe('DomRendererRowFactory', () => {
|
|||||||
lineData.set(0, [(FLAGS.INVERSE << 18) | (2 << 9) | (1 << 0), 'a', 1, 'a'.charCodeAt(0)]);
|
lineData.set(0, [(FLAGS.INVERSE << 18) | (2 << 9) | (1 << 0), 'a', 1, 'a'.charCodeAt(0)]);
|
||||||
const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20);
|
const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20);
|
||||||
assert.equal(getFragmentHtml(fragment),
|
assert.equal(getFragmentHtml(fragment),
|
||||||
'<span class="xterm-fg-1 xterm-bg-2">a</span>'
|
'<span class="xterm-fg-1 xterm-bg-2">a</span>' +
|
||||||
|
'<span> </span>'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should correctly invert default fg color', () => {
|
it('should correctly invert default fg color', () => {
|
||||||
lineData.set(0, [(FLAGS.INVERSE << 18) | (DEFAULT_ATTR << 9) | (1 << 0), 'a', 1, 'a'.charCodeAt(0)]);
|
lineData.set(0, [(FLAGS.INVERSE << 18) | (257 << 9) | (1 << 0), 'a', 1, 'a'.charCodeAt(0)]);
|
||||||
const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20);
|
const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20);
|
||||||
assert.equal(getFragmentHtml(fragment),
|
assert.equal(getFragmentHtml(fragment),
|
||||||
'<span class="xterm-fg-1 xterm-bg-257">a</span>'
|
'<span class="xterm-fg-1 xterm-bg-15">a</span>' +
|
||||||
|
'<span> </span>'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should correctly invert default bg color', () => {
|
it('should correctly invert default bg color', () => {
|
||||||
lineData.set(0, [(FLAGS.INVERSE << 18) | (1 << 9) | (DEFAULT_COLOR << 0), 'a', 1, 'a'.charCodeAt(0)]);
|
lineData.set(0, [(FLAGS.INVERSE << 18) | (1 << 9) | (256 << 0), 'a', 1, 'a'.charCodeAt(0)]);
|
||||||
const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20);
|
const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20);
|
||||||
assert.equal(getFragmentHtml(fragment),
|
assert.equal(getFragmentHtml(fragment),
|
||||||
'<span class="xterm-fg-257 xterm-bg-1">a</span>'
|
'<span class="xterm-fg-0 xterm-bg-1">a</span>' +
|
||||||
|
'<span> </span>'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should turn bold fg text bright', () => {
|
it('should turn bold fg text bright', () => {
|
||||||
for (let i = 0; i < 8; i++) {
|
for (let i = 0; i < 8; i++) {
|
||||||
lineData.set(0, [(FLAGS.BOLD << 18) | (i << 9) | (DEFAULT_COLOR << 0), 'a', 1, 'a'.charCodeAt(0)]);
|
lineData.set(0, [(FLAGS.BOLD << 18) | (i << 9) | (256 << 0), 'a', 1, 'a'.charCodeAt(0)]);
|
||||||
const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20);
|
const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20);
|
||||||
assert.equal(getFragmentHtml(fragment),
|
assert.equal(getFragmentHtml(fragment),
|
||||||
`<span class="xterm-bold xterm-fg-${i + 8}">a</span>`
|
`<span class="xterm-bold xterm-fg-${i + 8}">a</span>` +
|
||||||
|
'<span> </span>'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -141,9 +150,9 @@ describe('DomRendererRowFactory', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function createEmptyLineData(cols: number): IBufferLine {
|
function createEmptyLineData(cols: number): IBufferLine {
|
||||||
const lineData = new BufferLine(cols);
|
const lineData = new BufferLine();
|
||||||
for (let i = 0; i < cols; i++) {
|
for (let i = 0; i < cols; i++) {
|
||||||
lineData.set(i, [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]);
|
lineData.push([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]);
|
||||||
}
|
}
|
||||||
return lineData;
|
return lineData;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,9 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_ATTR_INDEX, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CODE_INDEX, NULL_CELL_CODE, WHITESPACE_CELL_CHAR } from '../../Buffer';
|
import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_ATTR_INDEX, CHAR_DATA_WIDTH_INDEX } from '../../Buffer';
|
||||||
import { FLAGS } from '../Types';
|
import { FLAGS } from '../Types';
|
||||||
import { IBufferLine } from '../../Types';
|
import { IBufferLine } from '../../Types';
|
||||||
import { DEFAULT_COLOR, INVERTED_DEFAULT_COLOR } from '../atlas/Types';
|
|
||||||
|
|
||||||
export const BOLD_CLASS = 'xterm-bold';
|
export const BOLD_CLASS = 'xterm-bold';
|
||||||
export const ITALIC_CLASS = 'xterm-italic';
|
export const ITALIC_CLASS = 'xterm-italic';
|
||||||
@@ -23,27 +22,18 @@ export class DomRendererRowFactory {
|
|||||||
|
|
||||||
public createRow(lineData: IBufferLine, isCursorRow: boolean, cursorStyle: string | undefined, cursorX: number, cellWidth: number, cols: number): DocumentFragment {
|
public createRow(lineData: IBufferLine, isCursorRow: boolean, cursorStyle: string | undefined, cursorX: number, cellWidth: number, cols: number): DocumentFragment {
|
||||||
const fragment = this._document.createDocumentFragment();
|
const fragment = this._document.createDocumentFragment();
|
||||||
|
let colCount = 0;
|
||||||
|
|
||||||
// Find the line length first, this prevents the need to output a bunch of
|
for (let x = 0; x < lineData.length; x++) {
|
||||||
// empty cells at the end. This cannot easily be integrated into the main
|
// Don't allow any buffer to the right to be displayed
|
||||||
// loop below because of the colCount feature (which can be removed after we
|
if (colCount >= cols) {
|
||||||
// properly support reflow and disallow data to go beyond the right-side of
|
continue;
|
||||||
// the viewport).
|
|
||||||
let lineLength = 0;
|
|
||||||
for (let x = Math.min(lineData.length, cols) - 1; x >= 0; x--) {
|
|
||||||
const charData = lineData.get(x);
|
|
||||||
const code = charData[CHAR_DATA_CODE_INDEX];
|
|
||||||
if (code !== NULL_CELL_CODE || (isCursorRow && x === cursorX)) {
|
|
||||||
lineLength = x + 1;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
for (let x = 0; x < lineLength; x++) {
|
|
||||||
const charData = lineData.get(x);
|
const charData = lineData.get(x);
|
||||||
const char = charData[CHAR_DATA_CHAR_INDEX] || WHITESPACE_CELL_CHAR;
|
const char: string = charData[CHAR_DATA_CHAR_INDEX];
|
||||||
const attr = charData[CHAR_DATA_ATTR_INDEX];
|
const attr: number = charData[CHAR_DATA_ATTR_INDEX];
|
||||||
const width = charData[CHAR_DATA_WIDTH_INDEX];
|
const width: number = charData[CHAR_DATA_WIDTH_INDEX];
|
||||||
|
|
||||||
// The character to the left is a wide character, drawing is owned by the char at x-1
|
// The character to the left is a wide character, drawing is owned by the char at x-1
|
||||||
if (width === 0) {
|
if (width === 0) {
|
||||||
@@ -80,17 +70,16 @@ export class DomRendererRowFactory {
|
|||||||
const temp = bg;
|
const temp = bg;
|
||||||
bg = fg;
|
bg = fg;
|
||||||
fg = temp;
|
fg = temp;
|
||||||
if (fg === DEFAULT_COLOR) {
|
if (fg === 256) {
|
||||||
fg = INVERTED_DEFAULT_COLOR;
|
fg = 0;
|
||||||
}
|
}
|
||||||
if (bg === DEFAULT_COLOR) {
|
if (bg === 257) {
|
||||||
bg = INVERTED_DEFAULT_COLOR;
|
bg = 15;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (flags & FLAGS.BOLD) {
|
if (flags & FLAGS.BOLD) {
|
||||||
// Convert the FG color to the bold variant. This should not happen when
|
// Convert the FG color to the bold variant
|
||||||
// the fg is the inverse default color as there is no bold variant.
|
|
||||||
if (fg < 8) {
|
if (fg < 8) {
|
||||||
fg += 8;
|
fg += 8;
|
||||||
}
|
}
|
||||||
@@ -102,13 +91,14 @@ export class DomRendererRowFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
charElement.textContent = char;
|
charElement.textContent = char;
|
||||||
if (fg !== DEFAULT_COLOR) {
|
if (fg !== 257) {
|
||||||
charElement.classList.add(`xterm-fg-${fg}`);
|
charElement.classList.add(`xterm-fg-${fg}`);
|
||||||
}
|
}
|
||||||
if (bg !== DEFAULT_COLOR) {
|
if (bg !== 256) {
|
||||||
charElement.classList.add(`xterm-bg-${bg}`);
|
charElement.classList.add(`xterm-bg-${bg}`);
|
||||||
}
|
}
|
||||||
fragment.appendChild(charElement);
|
fragment.appendChild(charElement);
|
||||||
|
colCount += width;
|
||||||
}
|
}
|
||||||
return fragment;
|
return fragment;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
||||||
|
* @license MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface IColor {
|
||||||
|
css: string;
|
||||||
|
rgba: number; // 32-bit int with rgba in each byte
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IColorSet {
|
||||||
|
foreground: IColor;
|
||||||
|
background: IColor;
|
||||||
|
cursor: IColor;
|
||||||
|
cursorAccent: IColor;
|
||||||
|
selection: IColor;
|
||||||
|
ansi: IColor[];
|
||||||
|
}
|
||||||
@@ -4,9 +4,18 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { FontWeight } from 'xterm';
|
import { FontWeight } from 'xterm';
|
||||||
import { isFirefox, isSafari } from '../../core/Platform';
|
import { CHAR_ATLAS_CELL_SPACING, ICharAtlasConfig } from './Types';
|
||||||
import { IColor } from '../Types';
|
import { IColor } from '../Types';
|
||||||
import { ICharAtlasConfig, CHAR_ATLAS_CELL_SPACING } from './Types';
|
import { isFirefox, isSafari } from '../utils/Browser';
|
||||||
|
|
||||||
|
declare const Promise: any;
|
||||||
|
|
||||||
|
export interface IOffscreenCanvas {
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
getContext(type: '2d', config?: Canvas2DContextAttributes): CanvasRenderingContext2D;
|
||||||
|
transferToImageBitmap(): ImageBitmap;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a char atlas.
|
* Generates a char atlas.
|
||||||
@@ -14,7 +23,7 @@ import { ICharAtlasConfig, CHAR_ATLAS_CELL_SPACING } from './Types';
|
|||||||
* @param canvasFactory A function to generate a canvas with a width or height.
|
* @param canvasFactory A function to generate a canvas with a width or height.
|
||||||
* @param config The config for the new char atlas.
|
* @param config The config for the new char atlas.
|
||||||
*/
|
*/
|
||||||
export function generateStaticCharAtlasTexture(context: Window, canvasFactory: (width: number, height: number) => HTMLCanvasElement, config: ICharAtlasConfig): HTMLCanvasElement | Promise<ImageBitmap> {
|
export function generateStaticCharAtlasTexture(context: Window, canvasFactory: (width: number, height: number) => HTMLCanvasElement | IOffscreenCanvas, config: ICharAtlasConfig): HTMLCanvasElement | Promise<ImageBitmap> {
|
||||||
const cellWidth = config.scaledCharWidth + CHAR_ATLAS_CELL_SPACING;
|
const cellWidth = config.scaledCharWidth + CHAR_ATLAS_CELL_SPACING;
|
||||||
const cellHeight = config.scaledCharHeight + CHAR_ATLAS_CELL_SPACING;
|
const cellHeight = config.scaledCharHeight + CHAR_ATLAS_CELL_SPACING;
|
||||||
const canvas = canvasFactory(
|
const canvas = canvasFactory(
|
||||||
@@ -29,7 +38,7 @@ export function generateStaticCharAtlasTexture(context: Window, canvasFactory: (
|
|||||||
ctx.save();
|
ctx.save();
|
||||||
ctx.fillStyle = config.colors.foreground.css;
|
ctx.fillStyle = config.colors.foreground.css;
|
||||||
ctx.font = getFont(config.fontWeight, config);
|
ctx.font = getFont(config.fontWeight, config);
|
||||||
ctx.textBaseline = 'middle';
|
ctx.textBaseline = 'top';
|
||||||
|
|
||||||
// Default color
|
// Default color
|
||||||
for (let i = 0; i < 256; i++) {
|
for (let i = 0; i < 256; i++) {
|
||||||
@@ -37,7 +46,7 @@ export function generateStaticCharAtlasTexture(context: Window, canvasFactory: (
|
|||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.rect(i * cellWidth, 0, cellWidth, cellHeight);
|
ctx.rect(i * cellWidth, 0, cellWidth, cellHeight);
|
||||||
ctx.clip();
|
ctx.clip();
|
||||||
ctx.fillText(String.fromCharCode(i), i * cellWidth, cellHeight / 2);
|
ctx.fillText(String.fromCharCode(i), i * cellWidth, 0);
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
}
|
}
|
||||||
// Default color bold
|
// Default color bold
|
||||||
@@ -48,7 +57,7 @@ export function generateStaticCharAtlasTexture(context: Window, canvasFactory: (
|
|||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.rect(i * cellWidth, cellHeight, cellWidth, cellHeight);
|
ctx.rect(i * cellWidth, cellHeight, cellWidth, cellHeight);
|
||||||
ctx.clip();
|
ctx.clip();
|
||||||
ctx.fillText(String.fromCharCode(i), i * cellWidth, cellHeight * 1.5);
|
ctx.fillText(String.fromCharCode(i), i * cellWidth, cellHeight);
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
}
|
}
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
@@ -64,7 +73,7 @@ export function generateStaticCharAtlasTexture(context: Window, canvasFactory: (
|
|||||||
ctx.rect(i * cellWidth, y, cellWidth, cellHeight);
|
ctx.rect(i * cellWidth, y, cellWidth, cellHeight);
|
||||||
ctx.clip();
|
ctx.clip();
|
||||||
ctx.fillStyle = config.colors.ansi[colorIndex].css;
|
ctx.fillStyle = config.colors.ansi[colorIndex].css;
|
||||||
ctx.fillText(String.fromCharCode(i), i * cellWidth, y + cellHeight / 2);
|
ctx.fillText(String.fromCharCode(i), i * cellWidth, y);
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -80,7 +89,7 @@ export function generateStaticCharAtlasTexture(context: Window, canvasFactory: (
|
|||||||
ctx.rect(i * cellWidth, y, cellWidth, cellHeight);
|
ctx.rect(i * cellWidth, y, cellWidth, cellHeight);
|
||||||
ctx.clip();
|
ctx.clip();
|
||||||
ctx.fillStyle = config.colors.ansi[colorIndex].css;
|
ctx.fillStyle = config.colors.ansi[colorIndex].css;
|
||||||
ctx.fillText(String.fromCharCode(i), i * cellWidth, y + cellHeight / 2);
|
ctx.fillText(String.fromCharCode(i), i * cellWidth, y);
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -92,7 +101,12 @@ export function generateStaticCharAtlasTexture(context: Window, canvasFactory: (
|
|||||||
// performance (tested on v55).
|
// performance (tested on v55).
|
||||||
if (!('createImageBitmap' in context) || isFirefox || isSafari) {
|
if (!('createImageBitmap' in context) || isFirefox || isSafari) {
|
||||||
// Don't attempt to clear background colors if createImageBitmap is not supported
|
// Don't attempt to clear background colors if createImageBitmap is not supported
|
||||||
return canvas;
|
if (canvas instanceof HTMLCanvasElement) {
|
||||||
|
// Just return the HTMLCanvas if it's a HTMLCanvasElement
|
||||||
|
return canvas;
|
||||||
|
}
|
||||||
|
// Transfer to an ImageBitmap is this is an OffscreenCanvas
|
||||||
|
return new Promise((r: (bitmap: ImageBitmap) => void) => r(canvas.transferToImageBitmap()));
|
||||||
}
|
}
|
||||||
|
|
||||||
const charAtlasImageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
const charAtlasImageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
||||||
|
* @license MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { FontWeight } from 'xterm';
|
||||||
|
import { IColorSet } from '../Types';
|
||||||
|
|
||||||
|
export const CHAR_ATLAS_CELL_SPACING = 1;
|
||||||
|
|
||||||
|
export interface ICharAtlasConfig {
|
||||||
|
type: 'none' | 'static' | 'dynamic';
|
||||||
|
devicePixelRatio: number;
|
||||||
|
fontSize: number;
|
||||||
|
fontFamily: string;
|
||||||
|
fontWeight: FontWeight;
|
||||||
|
fontWeightBold: FontWeight;
|
||||||
|
scaledCharWidth: number;
|
||||||
|
scaledCharHeight: number;
|
||||||
|
allowTransparency: boolean;
|
||||||
|
colors: IColorSet;
|
||||||
|
}
|
||||||
@@ -38,7 +38,7 @@ export class CharMeasure extends EventEmitter implements ICharMeasure {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public measure(options: ITerminalOptions): void {
|
public measure(options: ITerminalOptions): void {
|
||||||
this._measureElement.style.fontFamily = options.fontFamily;
|
this._measureElement.style.fontFamily = options.fontFamily;
|
||||||
this._measureElement.style.fontSize = `${options.fontSize}px`;
|
this._measureElement.style.fontSize = `${options.fontSize}px`;
|
||||||
const geometry = this._measureElement.getBoundingClientRect();
|
const geometry = this._measureElement.getBoundingClientRect();
|
||||||
// The element is likely currently display:none, we should retain the
|
// The element is likely currently display:none, we should retain the
|
||||||
|
|||||||
@@ -180,7 +180,7 @@ export class MouseZoneManager extends Disposable implements IMouseZoneManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _findZoneEventAt(e: MouseEvent): IMouseZone {
|
private _findZoneEventAt(e: MouseEvent): IMouseZone {
|
||||||
const coords = this._terminal.mouseHelper.getCoords(e, this._terminal.screenElement, this._terminal.charMeasure, this._terminal.cols, this._terminal.rows);
|
const coords = this._terminal.mouseHelper.getCoords(e, this._terminal.screenElement, this._terminal.charMeasure, this._terminal.options.lineHeight, this._terminal.cols, this._terminal.rows);
|
||||||
if (!coords) {
|
if (!coords) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export type ScreenDprListener = (newDevicePixelRatio?: number, oldDevicePixelRat
|
|||||||
*/
|
*/
|
||||||
export class ScreenDprMonitor extends Disposable {
|
export class ScreenDprMonitor extends Disposable {
|
||||||
private _currentDevicePixelRatio: number;
|
private _currentDevicePixelRatio: number;
|
||||||
private _outerListener: (this: MediaQueryList, ev: MediaQueryListEvent) => any;
|
private _outerListener: MediaQueryListListener;
|
||||||
private _listener: ScreenDprListener;
|
private _listener: ScreenDprListener;
|
||||||
private _resolutionMediaMatchList: MediaQueryList;
|
private _resolutionMediaMatchList: MediaQueryList;
|
||||||
|
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ describe('clone', () => {
|
|||||||
test.a.b.c.d.e.f = 'bar';
|
test.a.b.c.d.e.f = 'bar';
|
||||||
|
|
||||||
// The values at a greater depth then 5 should not be cloned
|
// The values at a greater depth then 5 should not be cloned
|
||||||
assert.equal((cloned as any).a.b.c.d.e.f, 'bar');
|
assert.equal(cloned.a.b.c.d.e.f, 'bar');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow an optional maximum depth to be set', () => {
|
it('should allow an optional maximum depth to be set', () => {
|
||||||
@@ -118,7 +118,7 @@ describe('clone', () => {
|
|||||||
test.a.b.c = 'bar';
|
test.a.b.c = 'bar';
|
||||||
|
|
||||||
// The values at a greater depth then 2 should not be cloned
|
// The values at a greater depth then 2 should not be cloned
|
||||||
assert.equal((cloned as any).a.b.c, 'bar');
|
assert.equal(cloned.a.b.c, 'bar');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not throw when cloning a recursive reference', () => {
|
it('should not throw when cloning a recursive reference', () => {
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
/*
|
/*
|
||||||
* A simple utility for cloning values
|
* A simple utility for cloning values
|
||||||
*/
|
*/
|
||||||
export function clone<T>(val: T, depth: number = 5): T | null {
|
export const clone = <T>(val: T, depth: number = 5): T => {
|
||||||
if (typeof val !== 'object') {
|
if (typeof val !== 'object') {
|
||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
@@ -25,4 +25,4 @@ export function clone<T>(val: T, depth: number = 5): T | null {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return clonedObject as T;
|
return clonedObject as T;
|
||||||
}
|
};
|
||||||
@@ -37,28 +37,34 @@ describe('MouseHelper.getCoords', () => {
|
|||||||
describe('when charMeasure is not initialized', () => {
|
describe('when charMeasure is not initialized', () => {
|
||||||
it('should return null', () => {
|
it('should return null', () => {
|
||||||
charMeasure = new MockCharMeasure();
|
charMeasure = new MockCharMeasure();
|
||||||
assert.equal(mouseHelper.getCoords({ clientX: 0, clientY: 0 }, document.createElement('div'), charMeasure, 10, 10), null);
|
assert.equal(mouseHelper.getCoords({ pageX: 0, pageY: 0 }, document.createElement('div'), charMeasure, 1, 10, 10), null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when pageX/pageY are not supported', () => {
|
||||||
|
it('should return null', () => {
|
||||||
|
assert.equal(mouseHelper.getCoords({ pageX: undefined, pageY: undefined }, document.createElement('div'), charMeasure, 1, 10, 10), null);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return the cell that was clicked', () => {
|
it('should return the cell that was clicked', () => {
|
||||||
let coords: [number, number];
|
let coords: [number, number];
|
||||||
coords = mouseHelper.getCoords({ clientX: CHAR_WIDTH / 2, clientY: CHAR_HEIGHT / 2 }, document.createElement('div'), charMeasure, 10, 10);
|
coords = mouseHelper.getCoords({ pageX: CHAR_WIDTH / 2, pageY: CHAR_HEIGHT / 2 }, document.createElement('div'), charMeasure, 1, 10, 10);
|
||||||
assert.deepEqual(coords, [1, 1]);
|
assert.deepEqual(coords, [1, 1]);
|
||||||
coords = mouseHelper.getCoords({ clientX: CHAR_WIDTH, clientY: CHAR_HEIGHT }, document.createElement('div'), charMeasure, 10, 10);
|
coords = mouseHelper.getCoords({ pageX: CHAR_WIDTH, pageY: CHAR_HEIGHT }, document.createElement('div'), charMeasure, 1, 10, 10);
|
||||||
assert.deepEqual(coords, [1, 1]);
|
assert.deepEqual(coords, [1, 1]);
|
||||||
coords = mouseHelper.getCoords({ clientX: CHAR_WIDTH, clientY: CHAR_HEIGHT + 1 }, document.createElement('div'), charMeasure, 10, 10);
|
coords = mouseHelper.getCoords({ pageX: CHAR_WIDTH, pageY: CHAR_HEIGHT + 1 }, document.createElement('div'), charMeasure, 1, 10, 10);
|
||||||
assert.deepEqual(coords, [1, 2]);
|
assert.deepEqual(coords, [1, 2]);
|
||||||
coords = mouseHelper.getCoords({ clientX: CHAR_WIDTH + 1, clientY: CHAR_HEIGHT }, document.createElement('div'), charMeasure, 10, 10);
|
coords = mouseHelper.getCoords({ pageX: CHAR_WIDTH + 1, pageY: CHAR_HEIGHT }, document.createElement('div'), charMeasure, 1, 10, 10);
|
||||||
assert.deepEqual(coords, [2, 1]);
|
assert.deepEqual(coords, [2, 1]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should ensure the coordinates are returned within the terminal bounds', () => {
|
it('should ensure the coordinates are returned within the terminal bounds', () => {
|
||||||
let coords: [number, number];
|
let coords: [number, number];
|
||||||
coords = mouseHelper.getCoords({ clientX: -1, clientY: -1 }, document.createElement('div'), charMeasure, 10, 10);
|
coords = mouseHelper.getCoords({ pageX: -1, pageY: -1 }, document.createElement('div'), charMeasure, 1, 10, 10);
|
||||||
assert.deepEqual(coords, [1, 1]);
|
assert.deepEqual(coords, [1, 1]);
|
||||||
// Event are double the cols/rows
|
// Event are double the cols/rows
|
||||||
coords = mouseHelper.getCoords({ clientX: CHAR_WIDTH * 20, clientY: CHAR_HEIGHT * 20 }, document.createElement('div'), charMeasure, 10, 10);
|
coords = mouseHelper.getCoords({ pageX: CHAR_WIDTH * 20, pageY: CHAR_HEIGHT * 20 }, document.createElement('div'), charMeasure, 1, 10, 10);
|
||||||
assert.deepEqual(coords, [10, 10], 'coordinates should never come back as larger than the terminal');
|
assert.deepEqual(coords, [10, 10], 'coordinates should never come back as larger than the terminal');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -3,19 +3,36 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ICharMeasure, IMouseHelper } from '../Types';
|
import { ICharMeasure } from '../Types';
|
||||||
import { IRenderer } from '../renderer/Types';
|
import { IRenderer } from '../renderer/Types';
|
||||||
|
|
||||||
export class MouseHelper implements IMouseHelper {
|
export class MouseHelper {
|
||||||
constructor(private _renderer: IRenderer) {}
|
constructor(private _renderer: IRenderer) {}
|
||||||
|
|
||||||
public setRenderer(renderer: IRenderer): void {
|
public static getCoordsRelativeToElement(event: {pageX: number, pageY: number}, element: HTMLElement): [number, number] {
|
||||||
this._renderer = renderer;
|
// Ignore browsers that don't support MouseEvent.pageX
|
||||||
}
|
if (event.pageX === null || event.pageX === undefined) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public static getCoordsRelativeToElement(event: {clientX: number, clientY: number}, element: HTMLElement): [number, number] {
|
const originalElement = element;
|
||||||
const rect = element.getBoundingClientRect();
|
let x = event.pageX;
|
||||||
return [event.clientX - rect.left, event.clientY - rect.top];
|
let y = event.pageY;
|
||||||
|
|
||||||
|
// Converts the coordinates from being relative to the document to being
|
||||||
|
// relative to the terminal.
|
||||||
|
while (element) {
|
||||||
|
x -= element.offsetLeft;
|
||||||
|
y -= element.offsetTop;
|
||||||
|
element = <HTMLElement>element.offsetParent;
|
||||||
|
}
|
||||||
|
element = originalElement;
|
||||||
|
while (element && element !== element.ownerDocument.body) {
|
||||||
|
x += element.scrollLeft;
|
||||||
|
y += element.scrollTop;
|
||||||
|
element = <HTMLElement>element.parentElement;
|
||||||
|
}
|
||||||
|
return [x, y];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -31,7 +48,7 @@ export class MouseHelper implements IMouseHelper {
|
|||||||
* apply an offset to the x value such that the left half of the cell will
|
* apply an offset to the x value such that the left half of the cell will
|
||||||
* select that cell and the right half will select the next cell.
|
* select that cell and the right half will select the next cell.
|
||||||
*/
|
*/
|
||||||
public getCoords(event: {clientX: number, clientY: number}, element: HTMLElement, charMeasure: ICharMeasure, colCount: number, rowCount: number, isSelection?: boolean): [number, number] {
|
public getCoords(event: {pageX: number, pageY: number}, element: HTMLElement, charMeasure: ICharMeasure, lineHeight: number, colCount: number, rowCount: number, isSelection?: boolean): [number, number] {
|
||||||
// Coordinates cannot be measured if charMeasure has not been initialized
|
// Coordinates cannot be measured if charMeasure has not been initialized
|
||||||
if (!charMeasure.width || !charMeasure.height) {
|
if (!charMeasure.width || !charMeasure.height) {
|
||||||
return null;
|
return null;
|
||||||
@@ -64,8 +81,8 @@ export class MouseHelper implements IMouseHelper {
|
|||||||
* @param colCount The number of columns in the terminal.
|
* @param colCount The number of columns in the terminal.
|
||||||
* @param rowCount The number of rows in the terminal.
|
* @param rowCount The number of rows in the terminal.
|
||||||
*/
|
*/
|
||||||
public getRawByteCoords(event: MouseEvent, element: HTMLElement, charMeasure: ICharMeasure, colCount: number, rowCount: number): { x: number, y: number } {
|
public getRawByteCoords(event: MouseEvent, element: HTMLElement, charMeasure: ICharMeasure, lineHeight: number, colCount: number, rowCount: number): { x: number, y: number } {
|
||||||
const coords = this.getCoords(event, element, charMeasure, colCount, rowCount);
|
const coords = this.getCoords(event, element, charMeasure, lineHeight, colCount, rowCount);
|
||||||
let x = coords[0];
|
let x = coords[0];
|
||||||
let y = coords[1];
|
let y = coords[1];
|
||||||
|
|
||||||
@@ -7,7 +7,7 @@ import { IColorSet, IRenderer, IRenderDimensions, IColorManager } from '../rende
|
|||||||
import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminal, IBuffer, IBufferSet, IBrowser, ICharMeasure, ISelectionManager, ITerminalOptions, ILinkifier, IMouseHelper, ILinkMatcherOptions, CharacterJoinerHandler, IBufferLine, IBufferStringIterator } from '../Types';
|
import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminal, IBuffer, IBufferSet, IBrowser, ICharMeasure, ISelectionManager, ITerminalOptions, ILinkifier, IMouseHelper, ILinkMatcherOptions, CharacterJoinerHandler, IBufferLine, IBufferStringIterator } from '../Types';
|
||||||
import { ICircularList, XtermListener } from '../common/Types';
|
import { ICircularList, XtermListener } from '../common/Types';
|
||||||
import { Buffer } from '../Buffer';
|
import { Buffer } from '../Buffer';
|
||||||
import * as Browser from '../core/Platform';
|
import * as Browser from '../shared/utils/Browser';
|
||||||
import { ITheme, IDisposable, IMarker } from 'xterm';
|
import { ITheme, IDisposable, IMarker } from 'xterm';
|
||||||
import { Terminal } from '../Terminal';
|
import { Terminal } from '../Terminal';
|
||||||
|
|
||||||
@@ -54,12 +54,6 @@ export class MockTerminal implements ITerminal {
|
|||||||
attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void {
|
attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void {
|
||||||
throw new Error('Method not implemented.');
|
throw new Error('Method not implemented.');
|
||||||
}
|
}
|
||||||
addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): IDisposable {
|
|
||||||
throw new Error('Method not implemented.');
|
|
||||||
}
|
|
||||||
addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable {
|
|
||||||
throw new Error('Method not implemented.');
|
|
||||||
}
|
|
||||||
registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => boolean | void, options?: ILinkMatcherOptions): number {
|
registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => boolean | void, options?: ILinkMatcherOptions): number {
|
||||||
throw new Error('Method not implemented.');
|
throw new Error('Method not implemented.');
|
||||||
}
|
}
|
||||||
@@ -127,7 +121,7 @@ export class MockTerminal implements ITerminal {
|
|||||||
handler(data: string): void {
|
handler(data: string): void {
|
||||||
throw new Error('Method not implemented.');
|
throw new Error('Method not implemented.');
|
||||||
}
|
}
|
||||||
on(event: string, callback: (...args: any[]) => void): void {
|
on(event: string, callback: () => void): void {
|
||||||
throw new Error('Method not implemented.');
|
throw new Error('Method not implemented.');
|
||||||
}
|
}
|
||||||
off(type: string, listener: XtermListener): void {
|
off(type: string, listener: XtermListener): void {
|
||||||
@@ -188,6 +182,7 @@ export class MockInputHandlingTerminal implements IInputHandlingTerminal {
|
|||||||
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;
|
||||||
@@ -309,7 +304,6 @@ export class MockBuffer implements IBuffer {
|
|||||||
scrollTop: number;
|
scrollTop: number;
|
||||||
savedY: number;
|
savedY: number;
|
||||||
savedX: number;
|
savedX: number;
|
||||||
savedCurAttr: number;
|
|
||||||
translateBufferLineToString(lineIndex: number, trimRight: boolean, startCol?: number, endCol?: number): string {
|
translateBufferLineToString(lineIndex: number, trimRight: boolean, startCol?: number, endCol?: number): string {
|
||||||
return Buffer.prototype.translateBufferLineToString.apply(this, arguments);
|
return Buffer.prototype.translateBufferLineToString.apply(this, arguments);
|
||||||
}
|
}
|
||||||
@@ -325,9 +319,6 @@ export class MockBuffer implements IBuffer {
|
|||||||
setLines(lines: ICircularList<IBufferLine>): void {
|
setLines(lines: ICircularList<IBufferLine>): void {
|
||||||
this.lines = lines;
|
this.lines = lines;
|
||||||
}
|
}
|
||||||
getBlankLine(attr: number, isWrapped?: boolean): IBufferLine {
|
|
||||||
return Buffer.prototype.getBlankLine.apply(this, arguments);
|
|
||||||
}
|
|
||||||
stringIndexToBufferIndex(lineIndex: number, stringIndex: number): number[] {
|
stringIndexToBufferIndex(lineIndex: number, stringIndex: number): number[] {
|
||||||
return Buffer.prototype.stringIndexToBufferIndex.apply(this, arguments);
|
return Buffer.prototype.stringIndexToBufferIndex.apply(this, arguments);
|
||||||
}
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user