Compare commits

..
4 Commits
Author SHA1 Message Date
Daniel Imms 062b45ce0b v3.9.2 2019-01-08 08:14:29 -08:00
Daniel Imms f3bfcc368c Remove request term info handler
We're not supporting it anyway, no point having it.
2019-01-07 14:47:51 -08:00
Daniel Imms d306a96918 v3.9.1 2018-12-12 09:35:17 -08:00
Daniel Imms 24eb0130f5 Remove triple slash directive error in addon types
Fixes #1830
2018-12-12 09:34:20 -08:00
58 changed files with 974 additions and 1200 deletions
+2 -2
View File
@@ -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.
+1
View File
@@ -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
-44
View File
@@ -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
}
]
}
+134
View File
@@ -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>
+39 -20
View File
@@ -2,20 +2,23 @@
[![Build Status](https://dev.azure.com/xtermjs/xterm.js/_apis/build/status/xtermjs.xterm.js)](https://dev.azure.com/xtermjs/xterm.js/_build/latest?definitionId=3) [![Build Status](https://dev.azure.com/xtermjs/xterm.js/_apis/build/status/xtermjs.xterm.js)](https://dev.azure.com/xtermjs/xterm.js/_build/latest?definitionId=3)
[![Coverage Status](https://coveralls.io/repos/github/xtermjs/xterm.js/badge.svg?branch=master)](https://coveralls.io/github/xtermjs/xterm.js?branch=master) [![Coverage Status](https://coveralls.io/repos/github/xtermjs/xterm.js/badge.svg?branch=master)](https://coveralls.io/github/xtermjs/xterm.js?branch=master)
[![Gitter](https://badges.gitter.im/sourcelair/xterm.js.svg)](https://gitter.im/sourcelair/xterm.js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
[![jsDelivr Hits](https://data.jsdelivr.com/v1/package/npm/xterm/badge?style=rounded)](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.
@@ -163,7 +182,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
+7
View File
@@ -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
+3
View File
@@ -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
+21 -19
View File
@@ -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);
} }
}); });
@@ -233,7 +235,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 +265,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));
} }
}); });
}); });
+84 -90
View File
@@ -1,106 +1,100 @@
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); function buffer(socket, timeout) {
terminals[term.pid] = term; let s = '';
logs[term.pid] = ''; let sender = null;
term.on('data', function(data) { return (data) => {
logs[term.pid] += data; s += data;
}); if (!sender) {
res.send(term.pid.toString()); sender = setTimeout(() => {
res.end(); socket.send(s);
}); s = '';
sender = null;
app.post('/terminals/:pid/size', function (req, res) { }, timeout);
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); const send = buffer(ws, 5);
});
ws.on('close', function () { term.on('data', function(data) {
term.kill(); try {
console.log('Closed terminal ' + term.pid); send(data);
// Clean things up } catch (ex) {
delete terminals[term.pid]; // The WebSocket is not open, ignore
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
View File
@@ -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"
} }
] ]
}, },
+1 -7
View File
@@ -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 {
{ {
+4 -3
View File
@@ -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.9.2",
"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,13 +28,14 @@
"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",
@@ -47,7 +48,6 @@
}, },
"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", "pretest": "npm run layering",
@@ -57,6 +57,7 @@
"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",
+7 -35
View File
@@ -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;
@@ -355,7 +355,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 +508,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',
+60 -10
View File
@@ -17,13 +17,9 @@ 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
@@ -252,7 +248,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 +271,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 +488,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};
+1 -132
View File
@@ -5,7 +5,7 @@
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 } from './Buffer';
class TestBufferLine extends BufferLine { class TestBufferLine extends BufferLine {
@@ -200,135 +200,4 @@ describe('BufferLine', function(): void {
chai.expect(line.toArray()).eql(Array(7).fill([1, 'a', 0, 'a'.charCodeAt(0)])); 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('');
});
});
}); });
+4 -51
View File
@@ -3,7 +3,7 @@
* @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.
@@ -107,28 +107,6 @@ export class BufferLineJSArray implements IBufferLine {
newLine.copyFrom(this); newLine.copyFrom(this);
return newLine; 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 */ /** typed array slots taken by one cell */
@@ -141,9 +119,6 @@ const enum Cell {
WIDTH = 2 WIDTH = 2
} }
/** single vs. combined char distinction */
const IS_COMBINED_BIT_MASK = 0x80000000;
/** /**
* Typed array based bufferline implementation. * Typed array based bufferline implementation.
*/ */
@@ -169,11 +144,11 @@ export class BufferLine implements IBufferLine {
const stringData = this._data[index * CELL_SIZE + Cell.STRING]; const stringData = this._data[index * CELL_SIZE + Cell.STRING];
return [ return [
this._data[index * CELL_SIZE + Cell.FLAGS], this._data[index * CELL_SIZE + Cell.FLAGS],
(stringData & IS_COMBINED_BIT_MASK) (stringData & 0x80000000)
? this._combined[index] ? this._combined[index]
: (stringData) ? String.fromCharCode(stringData) : '', : (stringData) ? String.fromCharCode(stringData) : '',
this._data[index * CELL_SIZE + Cell.WIDTH], this._data[index * CELL_SIZE + Cell.WIDTH],
(stringData & IS_COMBINED_BIT_MASK) (stringData & 0x80000000)
? this._combined[index].charCodeAt(this._combined[index].length - 1) ? this._combined[index].charCodeAt(this._combined[index].length - 1)
: stringData : stringData
]; ];
@@ -183,7 +158,7 @@ export class BufferLine implements IBufferLine {
this._data[index * CELL_SIZE + Cell.FLAGS] = value[0]; this._data[index * CELL_SIZE + Cell.FLAGS] = value[0];
if (value[1].length > 1) { if (value[1].length > 1) {
this._combined[index] = value[1]; this._combined[index] = value[1];
this._data[index * CELL_SIZE + Cell.STRING] = index | IS_COMBINED_BIT_MASK; this._data[index * CELL_SIZE + Cell.STRING] = index | 0x80000000;
} else { } else {
this._data[index * CELL_SIZE + Cell.STRING] = value[1].charCodeAt(0); this._data[index * CELL_SIZE + Cell.STRING] = value[1].charCodeAt(0);
} }
@@ -294,26 +269,4 @@ export class BufferLine implements IBufferLine {
newLine.isWrapped = this.isWrapped; newLine.isWrapped = this.isWrapped;
return newLine; 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 -1
View File
@@ -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;
+1 -1
View File
@@ -3,7 +3,7 @@
* @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, wcwidth } from './CharWidth';
import { IBuffer } from './Types'; import { IBuffer } from './Types';
-134
View File
@@ -1169,73 +1169,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 +1196,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 {
+20 -81
View File
@@ -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.
@@ -75,8 +67,7 @@ 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.push.apply(EXECUTABLES, 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;
@@ -286,8 +276,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 +301,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 +321,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 +391,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 +423,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 +463,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 +517,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 +532,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;
+310 -138
View File
@@ -5,11 +5,131 @@
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, CHAR_DATA_ATTR_INDEX, DEFAULT_ATTR } 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);
}
public insertChars(params: number[]): void {
let param = params[0];
if (param < 1) param = 1;
// make buffer local for faster access
const buffer = this._terminal.buffer;
const row = buffer.y + buffer.ybase;
let j = buffer.x;
while (param-- && j < this._terminal.cols) {
buffer.lines.get(row).insertCells(j++, 1, [this._terminal.eraseAttr(), NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]);
}
}
public deleteChars(params: number[]): void {
let param: number = params[0];
if (param < 1) {
param = 1;
}
// make buffer local for faster access
const buffer = this._terminal.buffer;
const row = buffer.y + buffer.ybase;
while (param--) {
buffer.lines.get(row).deleteCells(buffer.x, 1, [this._terminal.eraseAttr(), NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]);
}
this._terminal.updateRange(buffer.y);
}
}
describe('InputHandler', () => { describe('InputHandler', () => {
describe('save and restore cursor', () => { describe('save and restore cursor', () => {
const terminal = new MockInputHandlingTerminal(); const terminal = new MockInputHandlingTerminal();
@@ -88,250 +208,296 @@ describe('InputHandler', () => {
}); });
}); });
describe('regression tests', function(): void { describe('regression tests', function(): void {
function termContent(term: Terminal, trim: boolean): string[] { 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;
} }
it('insertChars', function(): void { it('insertChars', 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);
// 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;
oldInputHandler.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;
oldInputHandler.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;
oldInputHandler.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;
oldInputHandler.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);
const oldInputHandler = new OldInputHandler(term);
// 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;
oldInputHandler.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;
oldInputHandler.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;
oldInputHandler.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;
oldInputHandler.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 // reset and add a wrapped line
term.buffer.y = 0; termNew.buffer.y = 0;
term.buffer.x = 0; termNew.buffer.x = 0;
inputHandler.parse(Array(term.cols + 1).join('a')); // line 0 inputHandlerNew.parse(Array(termNew.cols + 1).join('a')); // line 0
inputHandler.parse(Array(term.cols + 10).join('a')); // line 1 and 2 inputHandlerNew.parse(Array(termNew.cols + 10).join('a')); // line 1 and 2
for (let i = 3; i < term.rows; ++i) inputHandler.parse(Array(term.cols + 1).join('a')); for (let i = 3; i < termOld.rows; ++i) inputHandlerNew.parse(Array(termNew.cols + 1).join('a'));
// params[1] left and above with wrap // params[1] left and above with wrap
// confirm precondition that line 2 is wrapped // confirm precondition that line 2 is wrapped
expect(term.buffer.lines.get(2).isWrapped).true; expect(termNew.buffer.lines.get(2).isWrapped).true;
term.buffer.y = 2; termNew.buffer.y = 2;
term.buffer.x = 40; termNew.buffer.x = 40;
inputHandler.eraseInDisplay([1]); inputHandlerNew.eraseInDisplay([1]);
expect(term.buffer.lines.get(2).isWrapped).false; expect(termNew.buffer.lines.get(2).isWrapped).false;
// reset and add a wrapped line // reset and add a wrapped line
term.buffer.y = 0; termNew.buffer.y = 0;
term.buffer.x = 0; termNew.buffer.x = 0;
inputHandler.parse(Array(term.cols + 1).join('a')); // line 0 inputHandlerNew.parse(Array(termNew.cols + 1).join('a')); // line 0
inputHandler.parse(Array(term.cols + 10).join('a')); // line 1 and 2 inputHandlerNew.parse(Array(termNew.cols + 10).join('a')); // line 1 and 2
for (let i = 3; i < term.rows; ++i) inputHandler.parse(Array(term.cols + 1).join('a')); for (let i = 3; i < termOld.rows; ++i) inputHandlerNew.parse(Array(termNew.cols + 1).join('a'));
// params[1] left and above with wrap // params[1] left and above with wrap
// confirm precondition that line 2 is wrapped // confirm precondition that line 2 is wrapped
expect(term.buffer.lines.get(2).isWrapped).true; expect(termNew.buffer.lines.get(2).isWrapped).true;
term.buffer.y = 1; termNew.buffer.y = 1;
term.buffer.x = 90; // Cursor is beyond last column termNew.buffer.x = 90; // Cursor is beyond last column
inputHandler.eraseInDisplay([1]); inputHandlerNew.eraseInDisplay([1]);
expect(term.buffer.lines.get(2).isWrapped).false; expect(termNew.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 = '';
for (let i = 0; i < termConverting.cols; ++i) {
s += termConverting.buffer.lines.get(1).get(i)[CHAR_DATA_CHAR_INDEX];
}
expect(s).equals('World ');
}); });
describe('print', () => { describe('print', () => {
it('should not cause an infinite loop (regression test)', () => { it('should not cause an infinite loop (regression test)', () => {
@@ -345,28 +511,34 @@ describe('InputHandler', () => {
let term: Terminal; let term: Terminal;
let handler: InputHandler; let handler: InputHandler;
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;
}
beforeEach(() => { beforeEach(() => {
term = new Terminal(); term = new Terminal();
handler = new InputHandler(term); handler = new InputHandler(term);
}); });
it('should handle DECSET/DECRST 47 (alt screen buffer)', () => { it('should handle DECSET/DECRST 47 (alt screen buffer)', () => {
handler.parse('\x1b[?47h\r\n\x1b[31mJUNK\x1b[?47lTEST'); handler.parse('\x1b[?47h\r\n\x1b[31mJUNK\x1b[?47lTEST');
expect(term.buffer.translateBufferLineToString(0, true)).to.equal(''); expect(lineContent(term.buffer.lines.get(0))).to.equal(Array(term.cols + 1).join(' '));
expect(term.buffer.translateBufferLineToString(1, true)).to.equal(' TEST'); expect(lineContent(term.buffer.lines.get(1))).to.equal(' TEST' + Array(term.cols - 7).join(' '));
// Text color of 'TEST' should be red // Text color of 'TEST' should be red
expect((term.buffer.lines.get(1).get(4)[CHAR_DATA_ATTR_INDEX] >> 9) & 0x1ff).to.equal(1); expect((term.buffer.lines.get(1).get(4)[CHAR_DATA_ATTR_INDEX] >> 9) & 0x1ff).to.equal(1);
}); });
it('should handle DECSET/DECRST 1047 (alt screen buffer)', () => { it('should handle DECSET/DECRST 1047 (alt screen buffer)', () => {
handler.parse('\x1b[?1047h\r\n\x1b[31mJUNK\x1b[?1047lTEST'); handler.parse('\x1b[?1047h\r\n\x1b[31mJUNK\x1b[?1047lTEST');
expect(term.buffer.translateBufferLineToString(0, true)).to.equal(''); expect(lineContent(term.buffer.lines.get(0))).to.equal(Array(term.cols + 1).join(' '));
expect(term.buffer.translateBufferLineToString(1, true)).to.equal(' TEST'); expect(lineContent(term.buffer.lines.get(1))).to.equal(' TEST' + Array(term.cols - 7).join(' '));
// Text color of 'TEST' should be red // Text color of 'TEST' should be red
expect((term.buffer.lines.get(1).get(4)[CHAR_DATA_ATTR_INDEX] >> 9) & 0x1ff).to.equal(1); expect((term.buffer.lines.get(1).get(4)[CHAR_DATA_ATTR_INDEX] >> 9) & 0x1ff).to.equal(1);
}); });
it('should handle DECSET/DECRST 1048 (alt screen cursor)', () => { it('should handle DECSET/DECRST 1048 (alt screen cursor)', () => {
handler.parse('\x1b[?1048h\r\n\x1b[31mJUNK\x1b[?1048lTEST'); handler.parse('\x1b[?1048h\r\n\x1b[31mJUNK\x1b[?1048lTEST');
expect(term.buffer.translateBufferLineToString(0, true)).to.equal('TEST'); expect(lineContent(term.buffer.lines.get(0))).to.equal('TEST' + Array(term.cols - 3).join(' '));
expect(term.buffer.translateBufferLineToString(1, true)).to.equal('JUNK'); expect(lineContent(term.buffer.lines.get(1))).to.equal('JUNK' + Array(term.cols - 3).join(' '));
// Text color of 'TEST' should be default // Text color of 'TEST' should be default
expect(term.buffer.lines.get(0).get(0)[CHAR_DATA_ATTR_INDEX]).to.equal(DEFAULT_ATTR); expect(term.buffer.lines.get(0).get(0)[CHAR_DATA_ATTR_INDEX]).to.equal(DEFAULT_ATTR);
// Text color of 'JUNK' should be red // Text color of 'JUNK' should be red
@@ -374,18 +546,18 @@ describe('InputHandler', () => {
}); });
it('should handle DECSET/DECRST 1049 (alt screen buffer+cursor)', () => { it('should handle DECSET/DECRST 1049 (alt screen buffer+cursor)', () => {
handler.parse('\x1b[?1049h\r\n\x1b[31mJUNK\x1b[?1049lTEST'); handler.parse('\x1b[?1049h\r\n\x1b[31mJUNK\x1b[?1049lTEST');
expect(term.buffer.translateBufferLineToString(0, true)).to.equal('TEST'); expect(lineContent(term.buffer.lines.get(0))).to.equal('TEST' + Array(term.cols - 3).join(' '));
expect(term.buffer.translateBufferLineToString(1, true)).to.equal(''); expect(lineContent(term.buffer.lines.get(1))).to.equal(Array(term.cols + 1).join(' '));
// Text color of 'TEST' should be default // Text color of 'TEST' should be default
expect(term.buffer.lines.get(0).get(0)[CHAR_DATA_ATTR_INDEX]).to.equal(DEFAULT_ATTR); expect(term.buffer.lines.get(0).get(0)[CHAR_DATA_ATTR_INDEX]).to.equal(DEFAULT_ATTR);
}); });
it('should handle DECSET/DECRST 1049 - maintains saved cursor for alt buffer', () => { it('should handle DECSET/DECRST 1049 - maintains saved cursor for alt buffer', () => {
handler.parse('\x1b[?1049h\r\n\x1b[31m\x1b[s\x1b[?1049lTEST'); handler.parse('\x1b[?1049h\r\n\x1b[31m\x1b[s\x1b[?1049lTEST');
expect(term.buffer.translateBufferLineToString(0, true)).to.equal('TEST'); expect(lineContent(term.buffer.lines.get(0))).to.equal('TEST' + Array(term.cols - 3).join(' '));
// Text color of 'TEST' should be default // Text color of 'TEST' should be default
expect(term.buffer.lines.get(0).get(0)[CHAR_DATA_ATTR_INDEX]).to.equal(DEFAULT_ATTR); expect(term.buffer.lines.get(0).get(0)[CHAR_DATA_ATTR_INDEX]).to.equal(DEFAULT_ATTR);
handler.parse('\x1b[?1049h\x1b[uTEST'); handler.parse('\x1b[?1049h\x1b[uTEST');
expect(term.buffer.translateBufferLineToString(1, true)).to.equal('TEST'); expect(lineContent(term.buffer.lines.get(1))).to.equal('TEST' + Array(term.cols - 3).join(' '));
// Text color of 'TEST' should be red // Text color of 'TEST' should be red
expect((term.buffer.lines.get(1).get(0)[CHAR_DATA_ATTR_INDEX] >> 9) & 0x1ff).to.equal(1); expect((term.buffer.lines.get(1).get(0)[CHAR_DATA_ATTR_INDEX] >> 9) & 0x1ff).to.equal(1);
}); });
+1 -30
View File
@@ -12,7 +12,6 @@ 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';
/** /**
@@ -25,26 +24,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 +66,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}\\`);
} }
} }
} }
@@ -288,7 +267,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 {
@@ -466,13 +444,6 @@ export class InputHandler extends Disposable implements IInputHandler {
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).
+1 -1
View File
@@ -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';
-8
View File
@@ -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]);
+1 -1
View File
@@ -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 {
+2 -2
View File
@@ -5,7 +5,7 @@
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 './core/Platform';
import { CharMeasure } from './ui/CharMeasure'; import { CharMeasure } from './ui/CharMeasure';
import { EventEmitter } from './common/EventEmitter'; import { EventEmitter } from './common/EventEmitter';
@@ -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
+1 -1
View File
@@ -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(
+2 -2
View File
@@ -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+$/, '');
+22 -22
View File
@@ -5,7 +5,7 @@
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';
const INIT_COLS = 80; const INIT_COLS = 80;
@@ -462,7 +462,7 @@ describe('term.js addons', () => {
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)', () => {
@@ -491,7 +491,7 @@ 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');
}); });
@@ -509,7 +509,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');
}); });
}); });
@@ -530,9 +530,9 @@ describe('term.js addons', () => {
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)', () => {
@@ -560,7 +560,7 @@ 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');
}); });
@@ -578,7 +578,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 +776,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 +787,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 +800,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 +813,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 +826,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 +845,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 +928,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 +953,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 +998,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 +1063,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');
+2 -12
View File
@@ -39,7 +39,8 @@ import { CharMeasure } from './ui/CharMeasure';
import * as Browser from './core/Platform'; import * as Browser from './core/Platform';
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,6 @@ 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';
// 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;
@@ -464,7 +464,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);
@@ -1413,15 +1412,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.
+1 -5
View File
@@ -245,7 +245,7 @@ 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, 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, colCount: number, rowCount: number): { x: number, y: number };
} }
@@ -492,8 +492,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;
@@ -526,8 +524,6 @@ export interface IBufferLine {
fill(fillCharData: CharData): void; fill(fillCharData: CharData): void;
copyFrom(line: IBufferLine): void; copyFrom(line: IBufferLine): void;
clone(): IBufferLine; clone(): IBufferLine;
getTrimmedLength(): number;
translateToString(trimRight?: boolean, startCol?: number, endCol?: number): string;
} }
export interface IBufferLineConstructor { export interface IBufferLineConstructor {
-1
View File
@@ -22,7 +22,6 @@ export function toggleFullScreen(term: Terminal, fullscreen: boolean): void {
fn = term.element.classList.add; fn = term.element.classList.add;
} }
fn = fn.bind(term.element.classList);
fn('fullscreen'); fn('fullscreen');
} }
-5
View File
@@ -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 {
+54 -137
View File
@@ -4,24 +4,16 @@
*/ */
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
} }
/** /**
@@ -32,47 +24,32 @@ export class SearchHelper implements ISearchHelper {
* @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
if (this._terminal.getSelection().length !== 0) { if (this._terminal.getSelection().length !== 0) {
startRow = incremental ? selectionManager.selectionStart[1] : selectionManager.selectionEnd[1]; startRow = this._terminal._core.selectionManager.selectionEnd[1];
startCol = incremental ? selectionManager.selectionStart[0] : selectionManager.selectionEnd[0];
} }
} }
this._initLinesCache(); // Search from ydisp + 1 to end
for (let y = startRow + 1; y < this._terminal._core.buffer.ybase + this._terminal.rows; y++) {
// Search startRow result = this._findInLine(term, y, searchOptions);
result = this._findInLine(term, startRow, startCol, searchOptions); if (result) {
break;
// Search from startRow + 1 to end
if (!result) {
for (let y = startRow + 1; y < this._terminal._core.buffer.ybase + this._terminal.rows; y++) {
result = this._findInLine(term, y, 0, searchOptions);
if (result) {
break;
}
} }
} }
// Search from the top to the startRow (search the whole startRow again in // Search from the top to the current ydisp
// case startCol > 0)
if (!result) { if (!result) {
for (let y = 0; y <= startRow; y++) { for (let y = 0; y < startRow; y++) {
result = this._findInLine(term, y, 0, searchOptions); result = this._findInLine(term, y, searchOptions);
if (result) { if (result) {
break; break;
} }
@@ -91,47 +68,32 @@ export class SearchHelper implements ISearchHelper {
* @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
if (selectionManager.selectionStart) {
// Start from the selection start if there is a selection
if (this._terminal.getSelection().length !== 0) { if (this._terminal.getSelection().length !== 0) {
startRow = selectionManager.selectionStart[1]; startRow = this._terminal._core.selectionManager.selectionStart[1];
startCol = selectionManager.selectionStart[0];
} }
} }
this._initLinesCache(); // Search from ydisp + 1 to end
for (let y = startRow - 1; y >= 0; y--) {
// Search startRow result = this._findInLine(term, y, searchOptions);
result = this._findInLine(term, startRow, startCol, searchOptions, isReverseSearch); if (result) {
break;
// Search from startRow - 1 to top
if (!result) {
for (let y = startRow - 1; y >= 0; y--) {
result = this._findInLine(term, y, this._terminal._core.buffer.lines.get(y).length, searchOptions, isReverseSearch);
if (result) {
break;
}
} }
} }
// Search from the bottom to startRow (search the whole startRow again in // Search from the top to the current ydisp
// case startCol > 0)
if (!result) { if (!result) {
const searchFrom = this._terminal._core.buffer.ybase + this._terminal.rows - 1; for (let y = this._terminal._core.buffer.ybase + this._terminal.rows - 1; y > startRow; y--) {
for (let y = searchFrom; y >= startRow; y--) { result = this._findInLine(term, y, searchOptions);
result = this._findInLine(term, y, this._terminal._core.buffer.lines.get(y).length, searchOptions, isReverseSearch);
if (result) { if (result) {
break; break;
} }
@@ -142,28 +104,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 +111,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)));
} }
/** /**
@@ -181,88 +121,65 @@ export class SearchHelper implements ISearchHelper {
* 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 The 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
+4 -66
View File
@@ -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);
} }
} }
@@ -121,6 +118,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 +247,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);
});
}); });
}); });
-1
View File
@@ -3,7 +3,6 @@
"module": "commonjs", "module": "commonjs",
"target": "es5", "target": "es5",
"lib": [ "lib": [
"dom",
"es5" "es5"
], ],
"rootDir": ".", "rootDir": ".",
-24
View File
@@ -63,28 +63,4 @@ describe('webLinks addon', () => {
assert.equal(uri, 'http://foo.com/colon:test'); 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/');
});
}); });
+1 -1
View File
@@ -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\\.\\-%~:]*)*([^:\\s])';
const queryStringHashFragmentCharacterSet = '[0-9\\w\\[\\]\\(\\)\\/\\?\\!#@$%&\'*+,:;~\\=\\.\\-]*'; const queryStringHashFragmentCharacterSet = '[0-9\\w\\[\\]\\(\\)\\/\\?\\!#@$%&\'*+,:;~\\=\\.\\-]*';
const queryStringClause = '(\\?' + queryStringHashFragmentCharacterSet + ')?'; const queryStringClause = '(\\?' + queryStringHashFragmentCharacterSet + ')?';
const hashFragmentClause = '(#' + queryStringHashFragmentCharacterSet + ')?'; const hashFragmentClause = '(#' + queryStringHashFragmentCharacterSet + ')?';
+2 -3
View File
@@ -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;
} }
-11
View File
@@ -281,16 +281,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, '!');
});
}); });
}); });
-4
View File
@@ -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;
} }
-6
View File
@@ -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);
} }
+4 -4
View File
@@ -236,12 +236,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,7 +295,7 @@ 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;
@@ -316,7 +316,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 -1
View File
@@ -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';
+3 -3
View File
@@ -3,7 +3,7 @@
* @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, DEFAULT_COLOR } from './atlas/Types';
@@ -73,11 +73,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];
+2 -2
View File
@@ -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 -5
View File
@@ -29,7 +29,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 +37,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 +48,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 +64,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 +80,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();
} }
} }
+2 -2
View File
@@ -250,7 +250,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 +259,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
+3 -5
View File
@@ -364,11 +364,9 @@ export class DomRenderer extends EventEmitter implements IRenderer {
return; return;
} }
const span = <HTMLElement>row.children[x]; const span = <HTMLElement>row.children[x];
if (span) { span.style.textDecoration = enabled ? 'underline' : 'none';
span.style.textDecoration = enabled ? 'underline' : 'none'; x = (x + 1) % cols;
} if (x === 0) {
if (++x >= cols) {
x = 0;
y++; y++;
} }
} }
+2 -2
View File
@@ -3,7 +3,7 @@
* @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, CHAR_DATA_CODE_INDEX, NULL_CELL_CODE } 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'; import { DEFAULT_COLOR, INVERTED_DEFAULT_COLOR } from '../atlas/Types';
@@ -41,7 +41,7 @@ export class DomRendererRowFactory {
for (let x = 0; x < lineLength; x++) { 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 = charData[CHAR_DATA_CHAR_INDEX];
const attr = charData[CHAR_DATA_ATTR_INDEX]; const attr = charData[CHAR_DATA_ATTR_INDEX];
const width = charData[CHAR_DATA_WIDTH_INDEX]; const width = charData[CHAR_DATA_WIDTH_INDEX];
+1 -1
View File
@@ -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
@@ -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', () => {
+2 -2
View File
@@ -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, 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, 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, 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, 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, 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, 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, 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, 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,40 @@
* @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 setRenderer(renderer: IRenderer): void {
this._renderer = renderer; this._renderer = renderer;
} }
public static getCoordsRelativeToElement(event: {clientX: number, clientY: number}, element: HTMLElement): [number, number] { public static getCoordsRelativeToElement(event: {pageX: number, pageY: number}, element: HTMLElement): [number, number] {
const rect = element.getBoundingClientRect(); // Ignore browsers that don't support MouseEvent.pageX
return [event.clientX - rect.left, event.clientY - rect.top]; if (event.pageX === null || event.pageX === undefined) {
return null;
}
const originalElement = element;
let x = event.pageX;
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 +52,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, 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;
@@ -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.');
} }
-23
View File
@@ -481,29 +481,6 @@ declare module 'xterm' {
*/ */
attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void; attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void;
/**
* (EXPERIMENTAL) Adds a handler for CSI escape sequences.
* @param flag The flag should be one-character string, which specifies the
* final character (e.g "m" for SGR) of the CSI sequence.
* @param callback The function to handle the escape sequence. The callback
* is called with the numerical params, as well as the special characters
* (e.g. "$" for DECSCPP). Return true if the sequence was handled; false if
* we should try a previous handler (set by addCsiHandler or setCsiHandler).
* The most recently-added handler is tried first.
* @return An IDisposable you can call to remove this handler.
*/
addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): IDisposable;
/**
* (EXPERIMENTAL) Adds a handler for OSC escape sequences.
* @param ident The number (first parameter) of the sequence.
* @param callback The function to handle the escape sequence. The callback
* is called with OSC data string. Return true if the sequence was handled;
* false if we should try a previous handler (set by addOscHandler or
* setOscHandler). The most recently-added handler is tried first.
* @return An IDisposable you can call to remove this handler.
*/
addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable;
/** /**
* (EXPERIMENTAL) Registers a link matcher, allowing custom link patterns to * (EXPERIMENTAL) Registers a link matcher, allowing custom link patterns to
* be matched and handled. * be matched and handled.
+111 -11
View File
@@ -290,6 +290,13 @@ acorn-globals@^4.1.0:
dependencies: dependencies:
acorn "^5.0.0" acorn "^5.0.0"
acorn-jsx@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-3.0.1.tgz#afdf9488fb1ecefc8348f6fb22f464e32a58b36b"
integrity sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=
dependencies:
acorn "^3.0.4"
acorn-node@^1.2.0, acorn-node@^1.3.0, acorn-node@^1.5.2: acorn-node@^1.2.0, acorn-node@^1.3.0, acorn-node@^1.5.2:
version "1.5.2" version "1.5.2"
resolved "https://registry.yarnpkg.com/acorn-node/-/acorn-node-1.5.2.tgz#2ca723df19d997b05824b69f6c7fb091fc42c322" resolved "https://registry.yarnpkg.com/acorn-node/-/acorn-node-1.5.2.tgz#2ca723df19d997b05824b69f6c7fb091fc42c322"
@@ -304,6 +311,11 @@ acorn@4.X:
resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.13.tgz#105495ae5361d697bd195c825192e1ad7f253787" resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.13.tgz#105495ae5361d697bd195c825192e1ad7f253787"
integrity sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c= integrity sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=
acorn@^3.0.4, acorn@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a"
integrity sha1-ReN/s56No/JbruP/U2niu18iAXo=
acorn@^5.0.0, acorn@^5.2.1, acorn@^5.3.0, acorn@^5.7.1: acorn@^5.0.0, acorn@^5.2.1, acorn@^5.3.0, acorn@^5.7.1:
version "5.7.1" version "5.7.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.1.tgz#f095829297706a7c9776958c0afc8930a9b9d9d8" resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.1.tgz#f095829297706a7c9776958c0afc8930a9b9d9d8"
@@ -737,6 +749,11 @@ bluebird@^3.5.1:
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9"
integrity sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA== integrity sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==
bluebird@~3.4.6:
version "3.4.7"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.4.7.tgz#f72d760be09b7f76d08ed8fae98b289a8d05fab3"
integrity sha1-9y12C+Cbf3bQjtj66Ysomo0F+rM=
bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0:
version "4.11.8" version "4.11.8"
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f"
@@ -1037,6 +1054,13 @@ caseless@~0.12.0:
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=
catharsis@~0.8.8:
version "0.8.9"
resolved "https://registry.yarnpkg.com/catharsis/-/catharsis-0.8.9.tgz#98cc890ca652dd2ef0e70b37925310ff9e90fc8b"
integrity sha1-mMyJDKZS3S7w5ws3klMQ/56Q/Is=
dependencies:
underscore-contrib "~0.3.0"
center-align@^0.1.1: center-align@^0.1.1:
version "0.1.3" version "0.1.3"
resolved "https://registry.yarnpkg.com/center-align/-/center-align-0.1.3.tgz#aa0d32629b6ee972200411cbd4461c907bc2b7ad" resolved "https://registry.yarnpkg.com/center-align/-/center-align-0.1.3.tgz#aa0d32629b6ee972200411cbd4461c907bc2b7ad"
@@ -1976,7 +2000,7 @@ escape-html@~1.0.3:
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=
escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5, escape-string-regexp@~1.0.5:
version "1.0.5" version "1.0.5"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
@@ -2001,6 +2025,14 @@ eslint-scope@^4.0.0:
esrecurse "^4.1.0" esrecurse "^4.1.0"
estraverse "^4.1.1" estraverse "^4.1.1"
espree@~3.1.7:
version "3.1.7"
resolved "https://registry.yarnpkg.com/espree/-/espree-3.1.7.tgz#fd5deec76a97a5120a9cd3a7cb1177a0923b11d2"
integrity sha1-/V3ux2qXpRIKnNOnyxF3oJI7EdI=
dependencies:
acorn "^3.3.0"
acorn-jsx "^3.0.0"
esprima@^3.1.3: esprima@^3.1.3:
version "3.1.3" version "3.1.3"
resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633"
@@ -3583,11 +3615,34 @@ js-yaml@^3.11.0, js-yaml@^3.7.0:
argparse "^1.0.7" argparse "^1.0.7"
esprima "^4.0.0" esprima "^4.0.0"
js2xmlparser@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/js2xmlparser/-/js2xmlparser-1.0.0.tgz#5a170f2e8d6476ce45405e04823242513782fe30"
integrity sha1-WhcPLo1kds5FQF4EgjJCUTeC/jA=
jsbn@~0.1.0: jsbn@~0.1.0:
version "0.1.1" version "0.1.1"
resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM=
jsdoc@3.4.3:
version "3.4.3"
resolved "https://registry.yarnpkg.com/jsdoc/-/jsdoc-3.4.3.tgz#e5740d6145c681f6679e6c17783a88dbdd97ccd3"
integrity sha1-5XQNYUXGgfZnnmwXeDqI292XzNM=
dependencies:
bluebird "~3.4.6"
catharsis "~0.8.8"
escape-string-regexp "~1.0.5"
espree "~3.1.7"
js2xmlparser "~1.0.0"
klaw "~1.3.0"
marked "~0.3.6"
mkdirp "~0.5.1"
requizzle "~0.2.1"
strip-json-comments "~2.0.1"
taffydb "2.6.2"
underscore "~1.8.3"
jsdom@^11.11.0: jsdom@^11.11.0:
version "11.11.0" version "11.11.0"
resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-11.11.0.tgz#df486efad41aee96c59ad7a190e2449c7eb1110e" resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-11.11.0.tgz#df486efad41aee96c59ad7a190e2449c7eb1110e"
@@ -3725,7 +3780,7 @@ kind-of@^6.0.0, kind-of@^6.0.2:
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051"
integrity sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA== integrity sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==
klaw@^1.0.0: klaw@^1.0.0, klaw@~1.3.0:
version "1.3.1" version "1.3.1"
resolved "https://registry.yarnpkg.com/klaw/-/klaw-1.3.1.tgz#4088433b46b3b1ba259d78785d8e96f73ba02439" resolved "https://registry.yarnpkg.com/klaw/-/klaw-1.3.1.tgz#4088433b46b3b1ba259d78785d8e96f73ba02439"
integrity sha1-QIhDO0azsbolnXh4XY6W9zugJDk= integrity sha1-QIhDO0azsbolnXh4XY6W9zugJDk=
@@ -3839,6 +3894,16 @@ loader-utils@^1.0.2, loader-utils@^1.1.0:
emojis-list "^2.0.0" emojis-list "^2.0.0"
json5 "^0.5.0" json5 "^0.5.0"
loader-utils@~0.2.2:
version "0.2.17"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-0.2.17.tgz#f86e6374d43205a6e6c60e9196f17c0299bfb348"
integrity sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=
dependencies:
big.js "^3.1.3"
emojis-list "^2.0.0"
json5 "^0.5.0"
object-assign "^4.0.1"
locate-path@^2.0.0: locate-path@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e"
@@ -4128,6 +4193,11 @@ map-visit@^1.0.0:
dependencies: dependencies:
object-visit "^1.0.0" object-visit "^1.0.0"
marked@~0.3.6:
version "0.3.19"
resolved "https://registry.yarnpkg.com/marked/-/marked-0.3.19.tgz#5d47f709c4c9fc3c216b6d46127280f40b39d790"
integrity sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg==
matchdep@^1.0.0: matchdep@^1.0.0:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/matchdep/-/matchdep-1.0.1.tgz#a57a33804491fbae208aba8f68380437abc2dca5" resolved "https://registry.yarnpkg.com/matchdep/-/matchdep-1.0.1.tgz#a57a33804491fbae208aba8f68380437abc2dca5"
@@ -4361,7 +4431,7 @@ mixin-deep@^1.2.0:
for-in "^1.0.2" for-in "^1.0.2"
is-extendable "^1.0.1" is-extendable "^1.0.1"
mkdirp@0.5.1, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0: mkdirp@0.5.1, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1:
version "0.5.1" version "0.5.1"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=
@@ -4469,9 +4539,9 @@ nanomatch@^1.2.9:
to-regex "^3.0.1" to-regex "^3.0.1"
natives@^1.1.0: natives@^1.1.0:
version "1.1.6" version "1.1.4"
resolved "https://registry.yarnpkg.com/natives/-/natives-1.1.6.tgz#a603b4a498ab77173612b9ea1acdec4d980f00bb" resolved "https://registry.yarnpkg.com/natives/-/natives-1.1.4.tgz#2f0f224fc9a7dd53407c7667c84cf8dbe773de58"
integrity sha512-6+TDFewD4yxY14ptjKaS63GVdtKiES1pTPyxn9Jb0rBqPMZ7VcCiooEhPNsr+mqHtMGxa/5c/HhcC4uPEUw/nA== integrity sha512-Q29yeg9aFKwhLVdkTAejM/HvYG0Y1Am1+HUkFQGn5k2j8GS+v60TVmZh6nujpEAj/qql+wGUrlryO8bF+b1jEg==
needle@^2.2.1: needle@^2.2.1:
version "2.2.1" version "2.2.1"
@@ -5592,6 +5662,13 @@ require-main-filename@^1.0.1:
resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1"
integrity sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE= integrity sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=
requizzle@~0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/requizzle/-/requizzle-0.2.1.tgz#6943c3530c4d9a7e46f1cddd51c158fc670cdbde"
integrity sha1-aUPDUwxNmn5G8c3dUcFY/GcM294=
dependencies:
underscore "~1.6.0"
resolve-cwd@^2.0.0: resolve-cwd@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a"
@@ -5962,13 +6039,14 @@ source-list-map@^2.0.0:
resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.0.tgz#aaa47403f7b245a92fbc97ea08f250d6087ed085" resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.0.tgz#aaa47403f7b245a92fbc97ea08f250d6087ed085"
integrity sha512-I2UmuJSRr/T8jisiROLU3A3ltr+swpniSmNPI4Ml3ZCX6tVnDsuZzK7F2hl5jTqbZBWCEKlj5HRQiPExXLgE8A== integrity sha512-I2UmuJSRr/T8jisiROLU3A3ltr+swpniSmNPI4Ml3ZCX6tVnDsuZzK7F2hl5jTqbZBWCEKlj5HRQiPExXLgE8A==
source-map-loader@^0.2.4: source-map-loader@^0.2.3:
version "0.2.4" version "0.2.3"
resolved "https://registry.yarnpkg.com/source-map-loader/-/source-map-loader-0.2.4.tgz#c18b0dc6e23bf66f6792437557c569a11e072271" resolved "https://registry.yarnpkg.com/source-map-loader/-/source-map-loader-0.2.3.tgz#d4b0c8cd47d54edce3e6bfa0f523f452b5b0e521"
integrity sha512-OU6UJUty+i2JDpTItnizPrlpOIBLmQbWMuBg9q5bVtnHACqw1tn9nNwqJLbv0/00JjnJb/Ee5g5WS5vrRv7zIQ== integrity sha512-MYbFX9DYxmTQFfy2v8FC1XZwpwHKYxg3SK8Wb7VPBKuhDjz8gi9re2819MsG4p49HDyiOSUKlmZ+nQBArW5CGw==
dependencies: dependencies:
async "^2.5.0" async "^2.5.0"
loader-utils "^1.1.0" loader-utils "~0.2.2"
source-map "~0.6.1"
source-map-resolve@^0.5.0, source-map-resolve@^0.5.1: source-map-resolve@^0.5.0, source-map-resolve@^0.5.1:
version "0.5.2" version "0.5.2"
@@ -6346,6 +6424,11 @@ syntax-error@^1.1.1:
dependencies: dependencies:
acorn-node "^1.2.0" acorn-node "^1.2.0"
taffydb@2.6.2:
version "2.6.2"
resolved "https://registry.yarnpkg.com/taffydb/-/taffydb-2.6.2.tgz#7cbcb64b5a141b6a2efc2c5d2c67b4e150b2a268"
integrity sha1-fLy2S1oUG2ou/CxdLGe04VCyomg=
tapable@^1.0.0: tapable@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.0.0.tgz#cbb639d9002eed9c6b5975eb20598d7936f1f9f2" resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.0.0.tgz#cbb639d9002eed9c6b5975eb20598d7936f1f9f2"
@@ -6705,6 +6788,23 @@ undefsafe@0.0.3:
resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-0.0.3.tgz#ecca3a03e56b9af17385baac812ac83b994a962f" resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-0.0.3.tgz#ecca3a03e56b9af17385baac812ac83b994a962f"
integrity sha1-7Mo6A+VrmvFzhbqsgSrIO5lKli8= integrity sha1-7Mo6A+VrmvFzhbqsgSrIO5lKli8=
underscore-contrib@~0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/underscore-contrib/-/underscore-contrib-0.3.0.tgz#665b66c24783f8fa2b18c9f8cbb0e2c7d48c26c7"
integrity sha1-ZltmwkeD+PorGMn4y7Dix9SMJsc=
dependencies:
underscore "1.6.0"
underscore@1.6.0, underscore@~1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.6.0.tgz#8b38b10cacdef63337b8b24e4ff86d45aea529a8"
integrity sha1-izixDKze9jM3uLJOT/htRa6lKag=
underscore@~1.8.3:
version "1.8.3"
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.8.3.tgz#4f3fb53b106e6097fcf9cb4109f2a5e9bdfa5022"
integrity sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=
union-value@^1.0.0: union-value@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.0.tgz#5c71c34cb5bad5dcebe3ea0cd08207ba5aa1aea4" resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.0.tgz#5c71c34cb5bad5dcebe3ea0cd08207ba5aa1aea4"