Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a28d9f1456 | ||
|
|
fb6b173fe7 | ||
|
|
1361ff8e22 | ||
|
|
0731f28615 | ||
|
|
f7b58d0718 | ||
|
|
d46003843b | ||
|
|
13619d427a | ||
|
|
f03d00a497 | ||
|
|
3d20c2f266 | ||
|
|
6f749b196a | ||
|
|
f5ba386c01 | ||
|
|
0079677e43 | ||
|
|
106eda7067 | ||
|
|
f13455965c | ||
|
|
5bc0ad879b | ||
|
|
72e85a352a | ||
|
|
49c1ca3f57 | ||
|
|
bcff69b79c | ||
|
|
3f7fec8c91 | ||
|
|
3479614c73 | ||
|
|
34f075a8cc | ||
|
|
ff165e8627 | ||
|
|
cdcbf60e44 | ||
|
|
2d4e617c95 | ||
|
|
6ea7758ab1 | ||
|
|
604959a319 | ||
|
|
6a3b39b428 | ||
|
|
9e80a12462 | ||
|
|
0e0ecc2d6a | ||
|
|
f872ced863 | ||
|
|
9a30b98880 | ||
|
|
f36bd24e1e | ||
|
|
23dd3d7a67 | ||
|
|
79965708ab | ||
|
|
80a83ff8d9 | ||
|
|
2e9177a032 | ||
|
|
bbb5daa575 | ||
|
|
739bd05037 | ||
|
|
f7197c10df | ||
|
|
e56f710cd1 | ||
|
|
18f3cc77e5 | ||
|
|
e3ded61122 | ||
|
|
f8e3fa5a99 | ||
|
|
05d308a37a | ||
|
|
cd5827e69b | ||
|
|
20048d31a1 | ||
|
|
598a729fc0 | ||
|
|
6ed5c9e64d | ||
|
|
1232c00dd1 | ||
|
|
5d87b54390 | ||
|
|
6733c6ce1f | ||
|
|
ba2d0e8df2 | ||
|
|
1722248065 | ||
|
|
dd0fcb6320 | ||
|
|
b965d0a0af | ||
|
|
48dab494db | ||
|
|
42eacba09a | ||
|
|
e2c993ebcd | ||
|
|
634f4e29aa | ||
|
|
59f75555bc | ||
|
|
cb5a452c90 | ||
|
|
58b9f712df | ||
|
|
f09918f0d2 | ||
|
|
4e35904392 | ||
|
|
82b3beb921 | ||
|
|
2806288489 | ||
|
|
0d498fcd52 | ||
|
|
83efc9afc4 | ||
|
|
393b411acc | ||
|
|
4effe1f1d9 | ||
|
|
de82bd00ae | ||
|
|
d222eec9f7 | ||
|
|
2cda163bcf | ||
|
|
9025d91f12 | ||
|
|
6810de85e1 | ||
|
|
4626e19b75 | ||
|
|
d75e0b99d5 | ||
|
|
73b4bfb5f8 | ||
|
|
bbafdd3d06 | ||
|
|
8ede1fc9d2 | ||
|
|
95cb6f30bb | ||
|
|
c94fdaecb2 | ||
|
|
e2b43a4ced | ||
|
|
1fd087e5d3 | ||
|
|
6df6aad303 | ||
|
|
5140c83649 | ||
|
|
1b6ce6dab6 | ||
|
|
1cc0d43aa4 | ||
|
|
0af4b17c20 | ||
|
|
2275601bf7 | ||
|
|
7cfc2ffa32 | ||
|
|
9e95024a9d | ||
|
|
87b76aadb7 | ||
|
|
adc657898c | ||
|
|
43b47abaa7 | ||
|
|
2ebc926f4e | ||
|
|
af58643ee7 | ||
|
|
9a358b2258 | ||
|
|
25fc01cb81 | ||
|
|
d3fe3a1960 | ||
|
|
01453e9d40 | ||
|
|
27f0eaf71d | ||
|
|
9bd52820e7 | ||
|
|
86c5aa023f | ||
|
|
4df8d3aada | ||
|
|
8fab079ad6 | ||
|
|
e59d1f7a98 | ||
|
|
5f817e88e7 | ||
|
|
38de6cb46a | ||
|
|
e287edb6fe | ||
|
|
29545a90c6 | ||
|
|
e849e3f206 | ||
|
|
c3011cd3ea | ||
|
|
e4770de9f2 | ||
|
|
99061c672c | ||
|
|
27fcd7c260 | ||
|
|
4c0e7b2462 | ||
|
|
decd81bc1e | ||
|
|
73970ec5a2 | ||
|
|
c62923fb1d | ||
|
|
c13b86a885 | ||
|
|
2564592c09 | ||
|
|
4b0cf2ffce | ||
|
|
fab039ddf3 | ||
|
|
3c0e142f73 | ||
|
|
80f7a37118 | ||
|
|
11de0d0ca0 | ||
|
|
5e815de82a | ||
|
|
0536862b8f | ||
|
|
b520e2fd35 | ||
|
|
740d4a45f9 | ||
|
|
cb53cdd728 | ||
|
|
2b92996d7c | ||
|
|
b8944beea9 | ||
|
|
67435edad7 | ||
|
|
fc9a0f15db | ||
|
|
8a57489966 | ||
|
|
3a8c1b3fcd | ||
|
|
0c2f079670 | ||
|
|
d266d995ab | ||
|
|
0ea2e9cfd6 | ||
|
|
9b8cb03c55 | ||
|
|
729f646a28 | ||
|
|
85c892c218 | ||
|
|
7f5e72dbdb | ||
|
|
baccca843c | ||
|
|
19381454ad | ||
|
|
f93b24acc7 | ||
|
|
6854def13c | ||
|
|
39151e4354 | ||
|
|
528f06bc62 | ||
|
|
5360277594 | ||
|
|
b957130787 | ||
|
|
f88dcd160c | ||
|
|
7d8f698096 | ||
|
|
d3f5a06517 | ||
|
|
280b698a50 | ||
|
|
8d12881a1c | ||
|
|
6480933861 |
@@ -13,6 +13,7 @@ npm-debug.log
|
|||||||
.env
|
.env
|
||||||
build/
|
build/
|
||||||
.vscode/
|
.vscode/
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
# Directories needed for code coverage
|
# Directories needed for code coverage
|
||||||
/coverage/
|
/coverage/
|
||||||
|
|||||||
+9
-8
@@ -1,18 +1,19 @@
|
|||||||
language: node_js
|
language: node_js
|
||||||
|
os:
|
||||||
|
- linux
|
||||||
|
- osx
|
||||||
node_js:
|
node_js:
|
||||||
- 6
|
- 6
|
||||||
|
before_install:
|
||||||
|
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test ; fi
|
||||||
|
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get -qq update ; fi
|
||||||
|
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get -qq install g++-4.8 ; fi
|
||||||
|
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then export CXX=g++-4.8 ; fi
|
||||||
|
- npm install -g npm@5.1.0
|
||||||
env:
|
env:
|
||||||
global:
|
|
||||||
- CXX=g++-4.8
|
|
||||||
matrix:
|
matrix:
|
||||||
- NPM_COMMAND=lint
|
- NPM_COMMAND=lint
|
||||||
- NPM_COMMAND=test
|
- NPM_COMMAND=test
|
||||||
addons:
|
|
||||||
apt:
|
|
||||||
sources:
|
|
||||||
- ubuntu-toolchain-r-test
|
|
||||||
packages:
|
|
||||||
- g++-4.8
|
|
||||||
notifications:
|
notifications:
|
||||||
email: false
|
email: false
|
||||||
script: npm run $NPM_COMMAND
|
script: npm run $NPM_COMMAND
|
||||||
|
|||||||
Vendored
+9
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"search.exclude": {
|
||||||
|
"**/node_modules": true,
|
||||||
|
"**/bower_components": true,
|
||||||
|
"**/dist": true,
|
||||||
|
"**/build": true,
|
||||||
|
"**/lib": true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ Alessandro Nadalin <alessandro.nadalin@gmail.com>
|
|||||||
Alexander Olsson <noseglid@gmail.com>
|
Alexander Olsson <noseglid@gmail.com>
|
||||||
Alexey Kontsevoy <alexey@gravitational.com>
|
Alexey Kontsevoy <alexey@gravitational.com>
|
||||||
Anish Athalye <me@anishathalye.com>
|
Anish Athalye <me@anishathalye.com>
|
||||||
|
Anthony Lapenna <lapenna.anthony@gmail.com>
|
||||||
Antonis Kalipetis <akalipetis@sourcelair.com>
|
Antonis Kalipetis <akalipetis@sourcelair.com>
|
||||||
Anton Skshidlevsky <meefik@gmail.com>
|
Anton Skshidlevsky <meefik@gmail.com>
|
||||||
Anton Yurovskykh <anton.yurovskykh@gmail.com>
|
Anton Yurovskykh <anton.yurovskykh@gmail.com>
|
||||||
@@ -21,6 +22,7 @@ Brian Mock <brian@mockbrian.com>
|
|||||||
Carson Anderson <carson@betterservers.com>
|
Carson Anderson <carson@betterservers.com>
|
||||||
CHaBou <chabup@delean.fr>
|
CHaBou <chabup@delean.fr>
|
||||||
Christian Budde Christensen <budde377@gmail.com>
|
Christian Budde Christensen <budde377@gmail.com>
|
||||||
|
Christof Marti <chrmarti@microsoft.com>
|
||||||
Christopher Jeffrey <chjjeffrey@gmail.com>
|
Christopher Jeffrey <chjjeffrey@gmail.com>
|
||||||
coderaiser <mnemonic.enemy@gmail.com>
|
coderaiser <mnemonic.enemy@gmail.com>
|
||||||
Daniel Imms <daimms@microsoft.com>
|
Daniel Imms <daimms@microsoft.com>
|
||||||
@@ -37,14 +39,20 @@ InDieTasten <indietasten@gmail.com>
|
|||||||
irokas <akasidiari@inf.uth.gr>
|
irokas <akasidiari@inf.uth.gr>
|
||||||
Jean Bruenn <himself@jeanbruenn.info>
|
Jean Bruenn <himself@jeanbruenn.info>
|
||||||
Jeremy Danyow <jedanyow@microsoft.com>
|
Jeremy Danyow <jedanyow@microsoft.com>
|
||||||
|
Joao Moreno <jomo@microsoft.com>
|
||||||
|
Joao Moreno <mail@joaomoreno.com>
|
||||||
|
Jon Masters <jon.masters@sky.com>
|
||||||
Jörg Breitbart <jerch@rockborn.de>
|
Jörg Breitbart <jerch@rockborn.de>
|
||||||
Justin Mecham <justin@mecham.me>
|
Justin Mecham <justin@mecham.me>
|
||||||
|
Kirill Merkushev <lanwen@yandex.ru>
|
||||||
Lucian Buzzo <lucian.buzzo@gmail.com>
|
Lucian Buzzo <lucian.buzzo@gmail.com>
|
||||||
Maël Nison <nison.mael@gmail.com>
|
Maël Nison <nison.mael@gmail.com>
|
||||||
Martin Chloride <i@martincl2.me>
|
Martin Chloride <i@martincl2.me>
|
||||||
Martin Wang <jiahaow@ca.ibm.com>
|
Martin Wang <jiahaow@ca.ibm.com>
|
||||||
|
Matt Bierner <matb@microsoft.com>
|
||||||
Michael Irwin <mikesir87@gmail.com>
|
Michael Irwin <mikesir87@gmail.com>
|
||||||
Mikko Karvonen <mikko.karvonen@arm.com>
|
Mikko Karvonen <mikko.karvonen@arm.com>
|
||||||
|
mofux <t.zilz@mofux.org>
|
||||||
Nicolas Ramz <nicolas.ramz@gmail.com>
|
Nicolas Ramz <nicolas.ramz@gmail.com>
|
||||||
Oleksandr Andriienko <oandriie@redhat.com>
|
Oleksandr Andriienko <oandriie@redhat.com>
|
||||||
Paris Kasidiaris <pariskasidiaris@gmail.com>
|
Paris Kasidiaris <pariskasidiaris@gmail.com>
|
||||||
@@ -54,7 +62,11 @@ Saswat Das <saswatds@users.noreply.github.com>
|
|||||||
Saul Costa <saul@codevolve.com>
|
Saul Costa <saul@codevolve.com>
|
||||||
Shuanglei Tao <tsl0922@gmail.com>
|
Shuanglei Tao <tsl0922@gmail.com>
|
||||||
Steven Silvester <steven.silvester@ieee.org>
|
Steven Silvester <steven.silvester@ieee.org>
|
||||||
|
stuicey <stuicey@users.noreply.github.com>
|
||||||
|
t-amqi <umberos@gmail.com>
|
||||||
Thanasis Daglis <thanasis@sourcelair.com>
|
Thanasis Daglis <thanasis@sourcelair.com>
|
||||||
|
thomas <thomas@kreativkonzentrat.de>
|
||||||
|
Thomas Zilz <t.zilz@mofux.org>
|
||||||
Tine Jozelj <tine.jozelj@outlook.com>
|
Tine Jozelj <tine.jozelj@outlook.com>
|
||||||
Tyler Jewell <tjewell@codenvy.com>
|
Tyler Jewell <tjewell@codenvy.com>
|
||||||
Vincent Woo <me@vincentwoo.com>
|
Vincent Woo <me@vincentwoo.com>
|
||||||
|
|||||||
@@ -30,14 +30,19 @@ Xterm.js is used in several world-class applications to provide great terminal e
|
|||||||
- [**Codenvy**](http://www.codenvy.com): Cloud workspaces for development teams.
|
- [**Codenvy**](http://www.codenvy.com): Cloud workspaces for development teams.
|
||||||
- [**CoderPad**](https://coderpad.io): Online interviewing platform for programmers. Run code in many programming languages, with results displayed by `xterm.js`.
|
- [**CoderPad**](https://coderpad.io): Online interviewing platform for programmers. Run code in many programming languages, with results displayed by `xterm.js`.
|
||||||
- [**WebSSH2**](https://github.com/billchurch/WebSSH2): A web based SSH2 client using `xterm.js`, socket.io, and ssh2.
|
- [**WebSSH2**](https://github.com/billchurch/WebSSH2): A web based SSH2 client using `xterm.js`, socket.io, and ssh2.
|
||||||
- [**Spyder Terminal**](https://github.com/spyder-ide/spyder-terminal): A full fledged system terminal embedded on Spyder IDE.
|
- [**Spyder Terminal**](https://github.com/spyder-ide/spyder-terminal): A full fledged system terminal embedded on Spyder IDE.
|
||||||
- [**Cloud Commander**](https://cloudcmd.io "Cloud Commander"): Orthodox web file manager with console and editor.
|
- [**Cloud Commander**](https://cloudcmd.io "Cloud Commander"): Orthodox web file manager with console and editor.
|
||||||
- [**Codevolve**](https://www.codevolve.com "Codevolve"): Online platform for interactive coding and web development courses. Live container-backed terminal uses `xterm.js`.
|
- [**Codevolve**](https://www.codevolve.com "Codevolve"): Online platform for interactive coding and web development courses. Live container-backed terminal uses `xterm.js`.
|
||||||
- [**RStudio**](https://www.rstudio.com/products/RStudio "RStudio"): RStudio is an integrated development environment (IDE) for R.
|
- [**RStudio**](https://www.rstudio.com/products/RStudio "RStudio"): RStudio is an integrated development environment (IDE) for R.
|
||||||
- [**Terminal for Atom**](https://github.com/jsmecham/atom-terminal-tab): A simple terminal for the Atom text editor.
|
- [**Terminal for Atom**](https://github.com/jsmecham/atom-terminal-tab): A simple terminal for the Atom text editor.
|
||||||
- [**Eclipse Orion**](https://orionhub.org): A modern, open source software development environment that runs in the cloud. Code, deploy and run in the cloud.
|
- [**Eclipse Orion**](https://orionhub.org): A modern, open source software development environment that runs in the cloud. Code, deploy and run in the cloud.
|
||||||
- [**Gravitational Teleport**](https://github.com/gravitational/teleport): Gravitational Teleport is a modern SSH server for remotely accessing clusters of Linux servers via SSH or HTTPS.
|
- [**Gravitational Teleport**](https://github.com/gravitational/teleport): Gravitational Teleport is a modern SSH server for remotely accessing clusters of Linux servers via SSH or HTTPS.
|
||||||
- [**Hexlet**](https://en.hexlet.io): Practical programming courses (JavaScript, PHP, Unix, databases, functional programming). A steady path from the first line of code to the first job.
|
- [**Hexlet**](https://en.hexlet.io): Practical programming courses (JavaScript, PHP, Unix, databases, functional programming). A steady path from the first line of code to the first job.
|
||||||
|
- [**Selenoid UI**](https://github.com/aerokube/selenoid-ui): Simple UI for the scallable golang implementation of Selenium Hub named Selenoid. We use XTerm for streaming logs over websockets from docker containers.
|
||||||
|
- [**Portainer**](https://portainer.io): Simple management UI for Docker.
|
||||||
|
- [**SSHy**](https://github.com/stuicey/SSHy): HTML5 Based SSHv2 Web Client with E2E encryption utilising `xterm.js`, SJCL & websockets.
|
||||||
|
- [**JupyterLab**](https://github.com/jupyterlab/jupyterlab): An extensible
|
||||||
|
computational environment for Jupyter, supporting interactive data science and scientific computing across all programming languages.
|
||||||
|
|
||||||
Do you use xterm.js in your application as well? Please [open a Pull Request](https://github.com/sourcelair/xterm.js/pulls) to include it here. We would love to have it in our list.
|
Do you use xterm.js in your application as well? Please [open a Pull Request](https://github.com/sourcelair/xterm.js/pulls) to include it here. We would love to have it in our list.
|
||||||
|
|
||||||
@@ -135,6 +140,6 @@ To contribute either code, documentation or issues to xterm.js please read the [
|
|||||||
|
|
||||||
If you contribute code to this project, you are implicitly allowing your code to be distributed under the MIT license. You are also implicitly verifying that all code is your original work.
|
If you contribute code to this project, you are implicitly allowing your code to be distributed under the MIT license. You are also implicitly verifying that all code is your original work.
|
||||||
|
|
||||||
Copyright (c) 2014-2016, SourceLair, Private Company ([www.sourcelair.com](https://www.sourcelair.com/home)) (MIT License)
|
Copyright (c) 2014-2017, SourceLair, Private Company ([www.sourcelair.com](https://www.sourcelair.com/home)) (MIT License)
|
||||||
|
|
||||||
Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
|
Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "xterm.js",
|
"name": "xterm.js",
|
||||||
"version": "2.8.0",
|
"version": "2.9.1",
|
||||||
"ignore": ["demo", "test", ".gitignore"],
|
"ignore": ["demo", "test", ".gitignore"],
|
||||||
"main": [
|
"main": [
|
||||||
"dist/xterm.js",
|
"dist/xterm.js",
|
||||||
|
|||||||
@@ -11,10 +11,18 @@
|
|||||||
<script src="/build/addons/attach/attach.js" ></script>
|
<script src="/build/addons/attach/attach.js" ></script>
|
||||||
<script src="/build/addons/fit/fit.js" ></script>
|
<script src="/build/addons/fit/fit.js" ></script>
|
||||||
<script src="/build/addons/fullscreen/fullscreen.js" ></script>
|
<script src="/build/addons/fullscreen/fullscreen.js" ></script>
|
||||||
|
<script src="/build/addons/search/search.js" ></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>xterm.js: xterm, in the browser</h1>
|
<h1>xterm.js: xterm, in the browser</h1>
|
||||||
<div id="terminal-container"></div>
|
<div id="terminal-container"></div>
|
||||||
|
<div>
|
||||||
|
<h2>Actions</h2>
|
||||||
|
<p>
|
||||||
|
<label>Find next <input id="find-next"/></label>
|
||||||
|
<label>Find previous <input id="find-previous"/></label>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h2>Options</h2>
|
<h2>Options</h2>
|
||||||
<p>
|
<p>
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ var term,
|
|||||||
charHeight;
|
charHeight;
|
||||||
|
|
||||||
var terminalContainer = document.getElementById('terminal-container'),
|
var terminalContainer = document.getElementById('terminal-container'),
|
||||||
|
actionElements = {
|
||||||
|
findNext: document.querySelector('#find-next'),
|
||||||
|
findPrevious: document.querySelector('#find-previous')
|
||||||
|
},
|
||||||
optionElements = {
|
optionElements = {
|
||||||
cursorBlink: document.querySelector('#option-cursor-blink'),
|
cursorBlink: document.querySelector('#option-cursor-blink'),
|
||||||
cursorStyle: document.querySelector('#option-cursor-style'),
|
cursorStyle: document.querySelector('#option-cursor-style'),
|
||||||
@@ -30,6 +34,19 @@ function setTerminalSize () {
|
|||||||
colsElement.addEventListener('change', setTerminalSize);
|
colsElement.addEventListener('change', setTerminalSize);
|
||||||
rowsElement.addEventListener('change', setTerminalSize);
|
rowsElement.addEventListener('change', setTerminalSize);
|
||||||
|
|
||||||
|
actionElements.findNext.addEventListener('keypress', function (e) {
|
||||||
|
if (e.key === "Enter") {
|
||||||
|
e.preventDefault();
|
||||||
|
term.findNext(actionElements.findNext.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
actionElements.findPrevious.addEventListener('keypress', function (e) {
|
||||||
|
if (e.key === "Enter") {
|
||||||
|
e.preventDefault();
|
||||||
|
term.findPrevious(actionElements.findPrevious.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
optionElements.cursorBlink.addEventListener('change', function () {
|
optionElements.cursorBlink.addEventListener('change', function () {
|
||||||
term.setOption('cursorBlink', optionElements.cursorBlink.checked);
|
term.setOption('cursorBlink', optionElements.cursorBlink.checked);
|
||||||
});
|
});
|
||||||
|
|||||||
Vendored
+17
-26
@@ -89,53 +89,40 @@
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.terminal.focus:not(.xterm-cursor-style-underline):not(.xterm-cursor-style-bar) .terminal-cursor {
|
.terminal .terminal-cursor {
|
||||||
background-color: #fff;
|
position: relative;
|
||||||
color: #000;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.terminal:not(.focus) .terminal-cursor {
|
.terminal:not(.focus) .terminal-cursor {
|
||||||
outline: 1px solid #fff;
|
outline: 1px solid #fff;
|
||||||
outline-offset: -1px;
|
outline-offset: -1px;
|
||||||
background-color: transparent;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.terminal:not(.xterm-cursor-style-underline):not(.xterm-cursor-style-bar).focus.xterm-cursor-blink-on .terminal-cursor {
|
.terminal.xterm-cursor-style-block.focus:not(.xterm-cursor-blink-on) .terminal-cursor {
|
||||||
background-color: transparent;
|
background-color: #fff;
|
||||||
color: inherit;
|
color: #000;
|
||||||
}
|
}
|
||||||
|
|
||||||
.terminal.xterm-cursor-style-bar .terminal-cursor,
|
.terminal.focus.xterm-cursor-style-bar:not(.xterm-cursor-blink-on) .terminal-cursor::before,
|
||||||
.terminal.xterm-cursor-style-underline .terminal-cursor {
|
.terminal.focus.xterm-cursor-style-underline:not(.xterm-cursor-blink-on) .terminal-cursor::before {
|
||||||
position: relative;
|
content: '';
|
||||||
}
|
|
||||||
.terminal.xterm-cursor-style-bar .terminal-cursor::before,
|
|
||||||
.terminal.xterm-cursor-style-underline .terminal-cursor::before {
|
|
||||||
content: "";
|
|
||||||
display: block;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
}
|
}
|
||||||
.terminal.xterm-cursor-style-bar .terminal-cursor::before {
|
|
||||||
|
.terminal.focus.xterm-cursor-style-bar:not(.xterm-cursor-blink-on) .terminal-cursor::before {
|
||||||
top: 0;
|
top: 0;
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
width: 1px;
|
width: 1px;
|
||||||
}
|
}
|
||||||
.terminal.xterm-cursor-style-underline .terminal-cursor::before {
|
|
||||||
|
.terminal.focus.xterm-cursor-style-underline:not(.xterm-cursor-blink-on) .terminal-cursor::before {
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
height: 1px;
|
height: 1px;
|
||||||
}
|
}
|
||||||
.terminal.xterm-cursor-style-bar.focus.xterm-cursor-blink.xterm-cursor-blink-on .terminal-cursor::before,
|
|
||||||
.terminal.xterm-cursor-style-underline.focus.xterm-cursor-blink.xterm-cursor-blink-on .terminal-cursor::before {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
.terminal.xterm-cursor-style-bar.focus.xterm-cursor-blink .terminal-cursor::before,
|
|
||||||
.terminal.xterm-cursor-style-underline.focus.xterm-cursor-blink .terminal-cursor::before {
|
|
||||||
background-color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.terminal .composition-view {
|
.terminal .composition-view {
|
||||||
background: #000;
|
background: #000;
|
||||||
@@ -217,6 +204,10 @@
|
|||||||
text-decoration: blink;
|
text-decoration: blink;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.terminal .xterm-blink.xterm-underline {
|
||||||
|
text-decoration: blink underline;
|
||||||
|
}
|
||||||
|
|
||||||
.terminal .xterm-hidden {
|
.terminal .xterm-hidden {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+701
-525
File diff suppressed because it is too large
Load Diff
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+69
-10
@@ -1,3 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* @license MIT
|
||||||
|
*/
|
||||||
|
|
||||||
const browserify = require('browserify');
|
const browserify = require('browserify');
|
||||||
const buffer = require('vinyl-buffer');
|
const buffer = require('vinyl-buffer');
|
||||||
const coveralls = require('gulp-coveralls');
|
const coveralls = require('gulp-coveralls');
|
||||||
@@ -10,13 +14,20 @@ const sorcery = require('sorcery');
|
|||||||
const source = require('vinyl-source-stream');
|
const source = require('vinyl-source-stream');
|
||||||
const sourcemaps = require('gulp-sourcemaps');
|
const sourcemaps = require('gulp-sourcemaps');
|
||||||
const ts = require('gulp-typescript');
|
const ts = require('gulp-typescript');
|
||||||
|
const util = require('gulp-util');
|
||||||
|
|
||||||
let buildDir = process.env.BUILD_DIR || 'build';
|
let buildDir = process.env.BUILD_DIR || 'build';
|
||||||
let tsProject = ts.createProject('tsconfig.json');
|
let tsProject = ts.createProject('tsconfig.json');
|
||||||
|
let tsProjectSearchAddon = ts.createProject('./src/addons/search/tsconfig.json');
|
||||||
let srcDir = tsProject.config.compilerOptions.rootDir;
|
let srcDir = tsProject.config.compilerOptions.rootDir;
|
||||||
let outDir = tsProject.config.compilerOptions.outDir;
|
let outDir = tsProject.config.compilerOptions.outDir;
|
||||||
|
|
||||||
|
// Under some environments like TravisCI, this comes out at absolute which can
|
||||||
|
// break the build. This ensures that the outDir is absolute.
|
||||||
|
if (outDir.indexOf(__dirname) !== 0) {
|
||||||
|
outDir = `${__dirname}/${outDir}`;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compile TypeScript sources to JavaScript files and create a source map file for each TypeScript
|
* Compile TypeScript sources to JavaScript files and create a source map file for each TypeScript
|
||||||
* file compiled.
|
* file compiled.
|
||||||
@@ -30,13 +41,17 @@ gulp.task('tsc', function () {
|
|||||||
let tsResult = tsProject.src().pipe(sourcemaps.init()).pipe(tsProject());
|
let tsResult = tsProject.src().pipe(sourcemaps.init()).pipe(tsProject());
|
||||||
let tsc = tsResult.js.pipe(sourcemaps.write('.', {includeContent: false, sourceRoot: ''})).pipe(gulp.dest(outDir));
|
let tsc = tsResult.js.pipe(sourcemaps.write('.', {includeContent: false, sourceRoot: ''})).pipe(gulp.dest(outDir));
|
||||||
|
|
||||||
|
fs.emptyDirSync(`${outDir}/addons/search`);
|
||||||
|
let tsResultSearchAddon = tsProjectSearchAddon.src().pipe(sourcemaps.init()).pipe(tsProjectSearchAddon());
|
||||||
|
let tscSearchAddon = tsResultSearchAddon.js.pipe(sourcemaps.write('.', {includeContent: false, sourceRoot: ''})).pipe(gulp.dest(`${outDir}/addons/search`));
|
||||||
|
|
||||||
// Copy all addons from ${srcDir}/ to ${outDir}/
|
// Copy all addons from ${srcDir}/ to ${outDir}/
|
||||||
let copyAddons = gulp.src(`${srcDir}/addons/**/*`).pipe(gulp.dest(`${outDir}/addons`));
|
let copyAddons = gulp.src([`${srcDir}/addons/**/*`, `!${srcDir}/addons/search`, `!${srcDir}/addons/search/**`]).pipe(gulp.dest(`${outDir}/addons`));
|
||||||
|
|
||||||
// Copy stylesheets from ${srcDir}/ to ${outDir}/
|
// Copy stylesheets from ${srcDir}/ to ${outDir}/
|
||||||
let copyStylesheets = gulp.src(`${srcDir}/**/*.css`).pipe(gulp.dest(outDir));
|
let copyStylesheets = gulp.src(`${srcDir}/**/*.css`).pipe(gulp.dest(outDir));
|
||||||
|
|
||||||
return merge(tsc, copyAddons, copyStylesheets);
|
return merge(tsc, tscSearchAddon, copyAddons, copyStylesheets);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -50,7 +65,7 @@ gulp.task('browserify', ['tsc'], function() {
|
|||||||
let browserifyOptions = {
|
let browserifyOptions = {
|
||||||
basedir: buildDir,
|
basedir: buildDir,
|
||||||
debug: true,
|
debug: true,
|
||||||
entries: [`../${outDir}/xterm.js`],
|
entries: [`${outDir}/xterm.js`],
|
||||||
standalone: 'Terminal',
|
standalone: 'Terminal',
|
||||||
cache: {},
|
cache: {},
|
||||||
packageCache: {}
|
packageCache: {}
|
||||||
@@ -63,13 +78,38 @@ gulp.task('browserify', ['tsc'], function() {
|
|||||||
.pipe(sourcemaps.write('./'))
|
.pipe(sourcemaps.write('./'))
|
||||||
.pipe(gulp.dest(buildDir));
|
.pipe(gulp.dest(buildDir));
|
||||||
|
|
||||||
// Copy all add-ons from ${outDir}/ to buildDir
|
|
||||||
let copyAddons = gulp.src(`${outDir}/addons/**/*`).pipe(gulp.dest(`${buildDir}/addons`));
|
|
||||||
|
|
||||||
// Copy stylesheets from ${outDir}/ to ${buildDir}/
|
// Copy stylesheets from ${outDir}/ to ${buildDir}/
|
||||||
let copyStylesheets = gulp.src(`${outDir}/**/*.css`).pipe(gulp.dest(buildDir));
|
let copyStylesheets = gulp.src(`${outDir}/**/*.css`).pipe(gulp.dest(buildDir));
|
||||||
|
|
||||||
return merge(bundleStream, copyAddons, copyStylesheets);
|
return merge(bundleStream, copyStylesheets);
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('browserify-addons', ['tsc'], function() {
|
||||||
|
let searchOptions = {
|
||||||
|
basedir: `${buildDir}/addons/search`,
|
||||||
|
debug: true,
|
||||||
|
entries: [`${outDir}/addons/search/search.js`],
|
||||||
|
cache: {},
|
||||||
|
packageCache: {}
|
||||||
|
};
|
||||||
|
let searchBundle = browserify(searchOptions)
|
||||||
|
.bundle()
|
||||||
|
.pipe(source('./addons/search/search.js'))
|
||||||
|
.pipe(buffer())
|
||||||
|
.pipe(sourcemaps.init({loadMaps: true, sourceRoot: ''}))
|
||||||
|
.pipe(sourcemaps.write('./'))
|
||||||
|
.pipe(gulp.dest(buildDir));
|
||||||
|
|
||||||
|
// Copy all add-ons from outDir to buildDir
|
||||||
|
let copyAddons = gulp.src([
|
||||||
|
// Copy JS addons
|
||||||
|
`${outDir}/addons/**/*`,
|
||||||
|
// Exclude TS addons from copy as they are being built via browserify
|
||||||
|
`!${outDir}/addons/search`,
|
||||||
|
`!${outDir}/addons/search/**`
|
||||||
|
]).pipe(gulp.dest(`${buildDir}/addons`));
|
||||||
|
|
||||||
|
return merge(searchBundle, copyAddons);
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('instrument-test', function () {
|
gulp.task('instrument-test', function () {
|
||||||
@@ -87,13 +127,32 @@ gulp.task('mocha', ['instrument-test'], function () {
|
|||||||
.pipe(istanbul.writeReports());
|
.pipe(istanbul.writeReports());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run single test file by file name(without file extension). Example of the command:
|
||||||
|
* gulp mocha-test --test InputHandler.test
|
||||||
|
*/
|
||||||
|
gulp.task('mocha-test', ['instrument-test'], function () {
|
||||||
|
let testName = util.env.test;
|
||||||
|
util.log("Run test by Name: " + testName);
|
||||||
|
return gulp.src([`${outDir}/${testName}.js`, `${outDir}/**/${testName}.js`], {read: false})
|
||||||
|
.pipe(mocha())
|
||||||
|
.once('error', () => process.exit(1))
|
||||||
|
.pipe(istanbul.writeReports());
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use `sorcery` to resolve the source map chain and point back to the TypeScript files.
|
* Use `sorcery` to resolve the source map chain and point back to the TypeScript files.
|
||||||
* (Without this task the source maps produced for the JavaScript bundle points into the
|
* (Without this task the source maps produced for the JavaScript bundle points into the
|
||||||
* compiled JavaScript files in ${outDir}/).
|
* compiled JavaScript files in ${outDir}/).
|
||||||
*/
|
*/
|
||||||
gulp.task('sorcery', ['browserify'], function () {
|
gulp.task('sorcery', ['browserify'], function () {
|
||||||
var chain = sorcery.loadSync(`${buildDir}/xterm.js`);
|
let chain = sorcery.loadSync(`${buildDir}/xterm.js`);
|
||||||
|
chain.apply();
|
||||||
|
chain.writeSync();
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('sorcery-addons', ['browserify-addons'], function () {
|
||||||
|
var chain = sorcery.loadSync(`${buildDir}/addons/search/search.js`);
|
||||||
chain.apply();
|
chain.apply();
|
||||||
chain.writeSync();
|
chain.writeSync();
|
||||||
});
|
});
|
||||||
@@ -106,6 +165,6 @@ gulp.task('coveralls', function () {
|
|||||||
.pipe(coveralls());
|
.pipe(coveralls());
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('build', ['sorcery']);
|
gulp.task('build', ['sorcery', 'sorcery-addons']);
|
||||||
gulp.task('test', ['mocha']);
|
gulp.task('test', ['mocha']);
|
||||||
gulp.task('default', ['build']);
|
gulp.task('default', ['build']);
|
||||||
|
|||||||
Generated
+12552
File diff suppressed because it is too large
Load Diff
+6
-6
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "xterm",
|
"name": "xterm",
|
||||||
"description": "Full xterm terminal, in your browser",
|
"description": "Full xterm terminal, in your browser",
|
||||||
"version": "2.8.0",
|
"version": "2.9.1",
|
||||||
"ignore": [
|
"ignore": [
|
||||||
"demo",
|
"demo",
|
||||||
"test",
|
"test",
|
||||||
@@ -36,29 +36,29 @@
|
|||||||
],
|
],
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/chai": "^3.4.34",
|
"@types/chai": "^3.4.34",
|
||||||
"@types/jsdom": "^2.0.30",
|
"@types/jsdom": "^11.0.1",
|
||||||
"@types/mocha": "^2.2.33",
|
"@types/mocha": "^2.2.33",
|
||||||
"@types/node": "^6.0.41",
|
"@types/node": "^6.0.41",
|
||||||
"browserify": "^13.1.0",
|
"browserify": "^13.3.0",
|
||||||
"chai": "3.5.0",
|
"chai": "3.5.0",
|
||||||
"docdash": "0.4.0",
|
"docdash": "0.4.0",
|
||||||
"express": "4.13.4",
|
"express": "4.13.4",
|
||||||
"express-ws": "2.0.0-rc.1",
|
"express-ws": "2.0.0-rc.1",
|
||||||
"fs-extra": "^1.0.0",
|
"fs-extra": "^1.0.0",
|
||||||
"glob": "^7.0.5",
|
"glob": "^7.0.5",
|
||||||
"gulp": "^3.9.1",
|
"gulp": "3.9.1",
|
||||||
"gulp-cli": "^1.2.2",
|
"gulp-cli": "^1.2.2",
|
||||||
"gulp-coveralls": "^0.1.4",
|
"gulp-coveralls": "^0.1.4",
|
||||||
"gulp-istanbul": "^1.1.1",
|
"gulp-istanbul": "^1.1.1",
|
||||||
"gulp-mocha": "^3.0.1",
|
"gulp-mocha": "^3.0.1",
|
||||||
"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",
|
||||||
"jsdoc": "3.4.3",
|
"jsdoc": "3.4.3",
|
||||||
"jsdom": "^9.11.0",
|
"jsdom": "^11.1.0",
|
||||||
"merge-stream": "^1.0.1",
|
"merge-stream": "^1.0.1",
|
||||||
"node-pty": "^0.4.1",
|
"node-pty": "^0.4.1",
|
||||||
"nodemon": "1.10.2",
|
"nodemon": "1.10.2",
|
||||||
"sleep": "^3.0.1",
|
|
||||||
"sorcery": "^0.10.0",
|
"sorcery": "^0.10.0",
|
||||||
"tslint": "^4.0.2",
|
"tslint": "^4.0.2",
|
||||||
"typescript": "~2.2.0",
|
"typescript": "~2.2.0",
|
||||||
|
|||||||
@@ -0,0 +1,147 @@
|
|||||||
|
/**
|
||||||
|
* @license MIT
|
||||||
|
*/
|
||||||
|
import { assert } from 'chai';
|
||||||
|
import { ITerminal } from './Interfaces';
|
||||||
|
import { Buffer } from './Buffer';
|
||||||
|
import { CircularList } from './utils/CircularList';
|
||||||
|
import { MockTerminal } from './utils/TestUtils';
|
||||||
|
|
||||||
|
const INIT_COLS = 80;
|
||||||
|
const INIT_ROWS = 24;
|
||||||
|
|
||||||
|
describe('Buffer', () => {
|
||||||
|
let terminal: ITerminal;
|
||||||
|
let buffer: Buffer;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
terminal = new MockTerminal();
|
||||||
|
terminal.cols = INIT_COLS;
|
||||||
|
terminal.rows = INIT_ROWS;
|
||||||
|
terminal.scrollback = 1000;
|
||||||
|
buffer = new Buffer(terminal);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('constructor', () => {
|
||||||
|
it('should create a CircularList with max length equal to scrollback, for its lines', () => {
|
||||||
|
assert.instanceOf(buffer.lines, CircularList);
|
||||||
|
assert.equal(buffer.lines.maxLength, terminal.scrollback);
|
||||||
|
});
|
||||||
|
it('should set the Buffer\'s scrollBottom value equal to the terminal\'s rows -1', () => {
|
||||||
|
assert.equal(buffer.scrollBottom, terminal.rows - 1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('fillViewportRows', () => {
|
||||||
|
it('should fill the buffer with blank lines based on the size of the viewport', () => {
|
||||||
|
const blankLineChar = terminal.blankLine()[0];
|
||||||
|
buffer.fillViewportRows();
|
||||||
|
assert.equal(buffer.lines.length, INIT_ROWS);
|
||||||
|
for (let y = 0; y < INIT_ROWS; y++) {
|
||||||
|
assert.equal(buffer.lines.get(y).length, INIT_COLS);
|
||||||
|
for (let x = 0; x < INIT_COLS; x++) {
|
||||||
|
assert.deepEqual(buffer.lines.get(y)[x], blankLineChar);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('resize', () => {
|
||||||
|
describe('column size is reduced', () => {
|
||||||
|
it('should not trim the data in the buffer', () => {
|
||||||
|
buffer.fillViewportRows();
|
||||||
|
buffer.resize(INIT_COLS / 2, INIT_ROWS);
|
||||||
|
assert.equal(buffer.lines.length, INIT_ROWS);
|
||||||
|
for (let i = 0; i < INIT_ROWS; i++) {
|
||||||
|
assert.equal(buffer.lines.get(i).length, INIT_COLS);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('column size is increased', () => {
|
||||||
|
it('should add pad columns', () => {
|
||||||
|
buffer.fillViewportRows();
|
||||||
|
buffer.resize(INIT_COLS + 10, INIT_ROWS);
|
||||||
|
assert.equal(buffer.lines.length, INIT_ROWS);
|
||||||
|
for (let i = 0; i < INIT_ROWS; i++) {
|
||||||
|
assert.equal(buffer.lines.get(i).length, INIT_COLS + 10);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('row size reduced', () => {
|
||||||
|
it('should trim blank lines from the end', () => {
|
||||||
|
buffer.fillViewportRows();
|
||||||
|
buffer.resize(INIT_COLS, INIT_ROWS - 10);
|
||||||
|
assert.equal(buffer.lines.length, INIT_ROWS - 10);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should move the viewport down when it\'s at the end', () => {
|
||||||
|
buffer.fillViewportRows();
|
||||||
|
// Set cursor y to have 5 blank lines below it
|
||||||
|
buffer.y = INIT_ROWS - 5 - 1;
|
||||||
|
buffer.resize(INIT_COLS, INIT_ROWS - 10);
|
||||||
|
// Trim 5 rows
|
||||||
|
assert.equal(buffer.lines.length, INIT_ROWS - 5);
|
||||||
|
// Shift the viewport down 5 rows
|
||||||
|
assert.equal(buffer.ydisp, 5);
|
||||||
|
assert.equal(buffer.ybase, 5);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('row size increased', () => {
|
||||||
|
describe('empty buffer', () => {
|
||||||
|
it('should add blank lines to end', () => {
|
||||||
|
buffer.fillViewportRows();
|
||||||
|
assert.equal(buffer.ydisp, 0);
|
||||||
|
buffer.resize(INIT_COLS, INIT_ROWS + 10);
|
||||||
|
assert.equal(buffer.ydisp, 0);
|
||||||
|
assert.equal(buffer.lines.length, INIT_ROWS + 10);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('filled buffer', () => {
|
||||||
|
it('should show more of the buffer above', () => {
|
||||||
|
buffer.fillViewportRows();
|
||||||
|
// Create 10 extra blank lines
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
|
buffer.lines.push(terminal.blankLine());
|
||||||
|
}
|
||||||
|
// Set cursor to the bottom of the buffer
|
||||||
|
buffer.y = INIT_ROWS - 1;
|
||||||
|
// Scroll down 10 lines
|
||||||
|
buffer.ybase = 10;
|
||||||
|
buffer.ydisp = 10;
|
||||||
|
assert.equal(buffer.lines.length, INIT_ROWS + 10);
|
||||||
|
buffer.resize(INIT_COLS, INIT_ROWS + 5);
|
||||||
|
// Should be should 5 more lines
|
||||||
|
assert.equal(buffer.ydisp, 5);
|
||||||
|
assert.equal(buffer.ybase, 5);
|
||||||
|
// Should not trim the buffer
|
||||||
|
assert.equal(buffer.lines.length, INIT_ROWS + 10);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show more of the buffer below when the viewport is at the top of the buffer', () => {
|
||||||
|
buffer.fillViewportRows();
|
||||||
|
// Create 10 extra blank lines
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
|
buffer.lines.push(terminal.blankLine());
|
||||||
|
}
|
||||||
|
// Set cursor to the bottom of the buffer
|
||||||
|
buffer.y = INIT_ROWS - 1;
|
||||||
|
// Scroll down 10 lines
|
||||||
|
buffer.ybase = 10;
|
||||||
|
buffer.ydisp = 0;
|
||||||
|
assert.equal(buffer.lines.length, INIT_ROWS + 10);
|
||||||
|
buffer.resize(INIT_COLS, INIT_ROWS + 5);
|
||||||
|
// The viewport should remain at the top
|
||||||
|
assert.equal(buffer.ydisp, 0);
|
||||||
|
// The buffer ybase should move up 5 lines
|
||||||
|
assert.equal(buffer.ybase, 5);
|
||||||
|
// Should not trim the buffer
|
||||||
|
assert.equal(buffer.lines.length, INIT_ROWS + 10);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
+148
@@ -0,0 +1,148 @@
|
|||||||
|
/**
|
||||||
|
* @license MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { ITerminal, IBuffer } from './Interfaces';
|
||||||
|
import { CircularList } from './utils/CircularList';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class represents a terminal buffer (an internal state of the terminal), where the
|
||||||
|
* following information is stored (in high-level):
|
||||||
|
* - text content of this particular buffer
|
||||||
|
* - cursor position
|
||||||
|
* - scroll position
|
||||||
|
*/
|
||||||
|
export class Buffer implements IBuffer {
|
||||||
|
private _lines: CircularList<[number, string, number][]>;
|
||||||
|
|
||||||
|
public ydisp: number;
|
||||||
|
public ybase: number;
|
||||||
|
public y: number;
|
||||||
|
public x: number;
|
||||||
|
public scrollBottom: number;
|
||||||
|
public scrollTop: number;
|
||||||
|
public tabs: any;
|
||||||
|
public savedY: number;
|
||||||
|
public savedX: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new Buffer.
|
||||||
|
* @param {Terminal} _terminal - The terminal the Buffer will belong to
|
||||||
|
* @param {number} ydisp - The scroll position of the Buffer in the viewport
|
||||||
|
* @param {number} ybase - The scroll position of the y cursor (ybase + y = the y position within the Buffer)
|
||||||
|
* @param {number} y - The cursor's y position after ybase
|
||||||
|
* @param {number} x - The cursor's x position after ybase
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
private _terminal: ITerminal
|
||||||
|
) {
|
||||||
|
this.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public get lines(): CircularList<[number, string, number][]> {
|
||||||
|
return this._lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fills the buffer's viewport with blank lines.
|
||||||
|
*/
|
||||||
|
public fillViewportRows(): void {
|
||||||
|
if (this._lines.length === 0) {
|
||||||
|
let i = this._terminal.rows;
|
||||||
|
while (i--) {
|
||||||
|
this.lines.push(this._terminal.blankLine());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the buffer to it's initial state, discarding all previous data.
|
||||||
|
*/
|
||||||
|
public clear(): void {
|
||||||
|
this.ydisp = 0;
|
||||||
|
this.ybase = 0;
|
||||||
|
this.y = 0;
|
||||||
|
this.x = 0;
|
||||||
|
this.scrollBottom = 0;
|
||||||
|
this.scrollTop = 0;
|
||||||
|
this.tabs = {};
|
||||||
|
this._lines = new CircularList<[number, string, number][]>(this._terminal.scrollback);
|
||||||
|
this.scrollBottom = this._terminal.rows - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resizes the buffer, adjusting its data accordingly.
|
||||||
|
* @param newCols The new number of columns.
|
||||||
|
* @param newRows The new number of rows.
|
||||||
|
*/
|
||||||
|
public resize(newCols: number, newRows: number): void {
|
||||||
|
// Don't resize the buffer if it's empty and hasn't been used yet.
|
||||||
|
if (this._lines.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deal with columns increasing (we don't do anything when columns reduce)
|
||||||
|
if (this._terminal.cols < newCols) {
|
||||||
|
const ch: [number, string, number] = [this._terminal.defAttr, ' ', 1]; // does xterm use the default attr?
|
||||||
|
for (let i = 0; i < this._lines.length; i++) {
|
||||||
|
if (this._lines.get(i) === undefined) {
|
||||||
|
this._lines.set(i, this._terminal.blankLine());
|
||||||
|
}
|
||||||
|
while (this._lines.get(i).length < newCols) {
|
||||||
|
this._lines.get(i).push(ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resize rows in both directions as needed
|
||||||
|
let addToY = 0;
|
||||||
|
if (this._terminal.rows < newRows) {
|
||||||
|
for (let y = this._terminal.rows; y < newRows; y++) {
|
||||||
|
if (this._lines.length < newRows + this.ybase) {
|
||||||
|
if (this.ybase > 0 && this._lines.length <= this.ybase + this.y + addToY + 1) {
|
||||||
|
// There is room above the buffer and there are no empty elements below the line,
|
||||||
|
// scroll up
|
||||||
|
this.ybase--;
|
||||||
|
addToY++;
|
||||||
|
if (this.ydisp > 0) {
|
||||||
|
// Viewport is at the top of the buffer, must increase downwards
|
||||||
|
this.ydisp--;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Add a blank line if there is no buffer left at the top to scroll to, or if there
|
||||||
|
// are blank lines after the cursor
|
||||||
|
this._lines.push(this._terminal.blankLine());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else { // (this._terminal.rows >= newRows)
|
||||||
|
for (let y = this._terminal.rows; y > newRows; y--) {
|
||||||
|
if (this._lines.length > newRows + this.ybase) {
|
||||||
|
if (this._lines.length > this.ybase + this.y + 1) {
|
||||||
|
// The line is a blank line below the cursor, remove it
|
||||||
|
this._lines.pop();
|
||||||
|
} else {
|
||||||
|
// The line is the cursor, scroll down
|
||||||
|
this.ybase++;
|
||||||
|
this.ydisp++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure that the cursor stays on screen
|
||||||
|
if (this.y >= newRows) {
|
||||||
|
this.y = newRows - 1;
|
||||||
|
}
|
||||||
|
if (addToY) {
|
||||||
|
this.y += addToY;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.x >= newCols) {
|
||||||
|
this.x = newCols - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.scrollTop = 0;
|
||||||
|
this.scrollBottom = newRows - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
/**
|
||||||
|
* @license MIT
|
||||||
|
*/
|
||||||
|
import { assert } from 'chai';
|
||||||
|
import { ITerminal } from './Interfaces';
|
||||||
|
import { BufferSet } from './BufferSet';
|
||||||
|
import { Buffer } from './Buffer';
|
||||||
|
import { MockTerminal } from './utils/TestUtils';
|
||||||
|
|
||||||
|
describe('BufferSet', () => {
|
||||||
|
let terminal: ITerminal;
|
||||||
|
let bufferSet: BufferSet;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
terminal = new MockTerminal();
|
||||||
|
terminal.cols = 80;
|
||||||
|
terminal.rows = 24;
|
||||||
|
terminal.scrollback = 1000;
|
||||||
|
bufferSet = new BufferSet(terminal);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('constructor', () => {
|
||||||
|
it('should create two different buffers: alt and normal', () => {
|
||||||
|
assert.instanceOf(bufferSet.normal, Buffer);
|
||||||
|
assert.instanceOf(bufferSet.alt, Buffer);
|
||||||
|
assert.notEqual(bufferSet.normal, bufferSet.alt);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('activateNormalBuffer', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
bufferSet.activateNormalBuffer();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set the normal buffer as the currently active buffer', () => {
|
||||||
|
assert.equal(bufferSet.active, bufferSet.normal);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('activateAltBuffer', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
bufferSet.activateAltBuffer();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set the alt buffer as the currently active buffer', () => {
|
||||||
|
assert.equal(bufferSet.active, bufferSet.alt);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
/**
|
||||||
|
* @license MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { ITerminal, IBufferSet } from './Interfaces';
|
||||||
|
import { Buffer } from './Buffer';
|
||||||
|
import { EventEmitter } from './EventEmitter';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The BufferSet represents the set of two buffers used by xterm terminals (normal and alt) and
|
||||||
|
* provides also utilities for working with them.
|
||||||
|
*/
|
||||||
|
export class BufferSet extends EventEmitter implements IBufferSet {
|
||||||
|
private _normal: Buffer;
|
||||||
|
private _alt: Buffer;
|
||||||
|
private _activeBuffer: Buffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new BufferSet for the given terminal.
|
||||||
|
* @param {Terminal} terminal - The terminal the BufferSet will belong to
|
||||||
|
*/
|
||||||
|
constructor(private _terminal: ITerminal) {
|
||||||
|
super();
|
||||||
|
this._normal = new Buffer(this._terminal);
|
||||||
|
this._normal.fillViewportRows();
|
||||||
|
this._alt = new Buffer(this._terminal);
|
||||||
|
this._activeBuffer = this._normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the alt Buffer of the BufferSet
|
||||||
|
* @returns {Buffer}
|
||||||
|
*/
|
||||||
|
public get alt(): Buffer {
|
||||||
|
return this._alt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the normal Buffer of the BufferSet
|
||||||
|
* @returns {Buffer}
|
||||||
|
*/
|
||||||
|
public get active(): Buffer {
|
||||||
|
return this._activeBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the currently active Buffer of the BufferSet
|
||||||
|
* @returns {Buffer}
|
||||||
|
*/
|
||||||
|
public get normal(): Buffer {
|
||||||
|
return this._normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the normal Buffer of the BufferSet as its currently active Buffer
|
||||||
|
*/
|
||||||
|
public activateNormalBuffer(): void {
|
||||||
|
// The alt buffer should always be cleared when we switch to the normal
|
||||||
|
// buffer. This frees up memory since the alt buffer should always be new
|
||||||
|
// when activated.
|
||||||
|
this._alt.clear();
|
||||||
|
|
||||||
|
this._activeBuffer = this._normal;
|
||||||
|
this.emit('activate', this._normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the alt Buffer of the BufferSet as its currently active Buffer
|
||||||
|
*/
|
||||||
|
public activateAltBuffer(): void {
|
||||||
|
// Since the alt buffer is always cleared when the normal buffer is
|
||||||
|
// activated, we want to fill it when switching to it.
|
||||||
|
this._alt.fillViewportRows();
|
||||||
|
|
||||||
|
this._activeBuffer = this._alt;
|
||||||
|
this.emit('activate', this._alt);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resizes both normal and alt buffers, adjusting their data accordingly.
|
||||||
|
* @param newCols The new number of columns.
|
||||||
|
* @param newRows The new number of rows.
|
||||||
|
*/
|
||||||
|
public resize(newCols: number, newRows: number): void {
|
||||||
|
this._normal.resize(newCols, newRows);
|
||||||
|
this._alt.resize(newCols, newRows);
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
-1
@@ -2,12 +2,14 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { IEventEmitter } from './Interfaces';
|
||||||
|
|
||||||
interface ListenerType {
|
interface ListenerType {
|
||||||
(): void;
|
(): void;
|
||||||
listener?: () => void;
|
listener?: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class EventEmitter {
|
export class EventEmitter implements IEventEmitter {
|
||||||
private _events: {[type: string]: ListenerType[]};
|
private _events: {[type: string]: ListenerType[]};
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|||||||
@@ -1,7 +1,23 @@
|
|||||||
import { assert } from 'chai';
|
import { assert } from 'chai';
|
||||||
import { InputHandler } from './InputHandler';
|
import { InputHandler } from './InputHandler';
|
||||||
|
import { wcwidth } from './InputHandler';
|
||||||
|
|
||||||
describe('InputHandler', () => {
|
describe('InputHandler', () => {
|
||||||
|
describe('save and restore cursor', () => {
|
||||||
|
let terminal = { buffer: { x: 1, y: 2 } };
|
||||||
|
let inputHandler = new InputHandler(terminal);
|
||||||
|
// Save cursor position
|
||||||
|
inputHandler.saveCursor([]);
|
||||||
|
assert.equal(terminal.buffer.x, 1);
|
||||||
|
assert.equal(terminal.buffer.y, 2);
|
||||||
|
// Change cursor position
|
||||||
|
terminal.buffer.x = 10;
|
||||||
|
terminal.buffer.y = 20;
|
||||||
|
// Restore cursor position
|
||||||
|
inputHandler.restoreCursor([]);
|
||||||
|
assert.equal(terminal.buffer.x, 1);
|
||||||
|
assert.equal(terminal.buffer.y, 2);
|
||||||
|
});
|
||||||
describe('setCursorStyle', () => {
|
describe('setCursorStyle', () => {
|
||||||
it('should call Terminal.setOption with correct params', () => {
|
it('should call Terminal.setOption with correct params', () => {
|
||||||
let options = {};
|
let options = {};
|
||||||
@@ -47,3 +63,139 @@ describe('InputHandler', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const old_wcwidth = (function(opts) {
|
||||||
|
// extracted from https://www.cl.cam.ac.uk/%7Emgk25/ucs/wcwidth.c
|
||||||
|
// combining characters
|
||||||
|
const COMBINING = [
|
||||||
|
[0x0300, 0x036F], [0x0483, 0x0486], [0x0488, 0x0489],
|
||||||
|
[0x0591, 0x05BD], [0x05BF, 0x05BF], [0x05C1, 0x05C2],
|
||||||
|
[0x05C4, 0x05C5], [0x05C7, 0x05C7], [0x0600, 0x0603],
|
||||||
|
[0x0610, 0x0615], [0x064B, 0x065E], [0x0670, 0x0670],
|
||||||
|
[0x06D6, 0x06E4], [0x06E7, 0x06E8], [0x06EA, 0x06ED],
|
||||||
|
[0x070F, 0x070F], [0x0711, 0x0711], [0x0730, 0x074A],
|
||||||
|
[0x07A6, 0x07B0], [0x07EB, 0x07F3], [0x0901, 0x0902],
|
||||||
|
[0x093C, 0x093C], [0x0941, 0x0948], [0x094D, 0x094D],
|
||||||
|
[0x0951, 0x0954], [0x0962, 0x0963], [0x0981, 0x0981],
|
||||||
|
[0x09BC, 0x09BC], [0x09C1, 0x09C4], [0x09CD, 0x09CD],
|
||||||
|
[0x09E2, 0x09E3], [0x0A01, 0x0A02], [0x0A3C, 0x0A3C],
|
||||||
|
[0x0A41, 0x0A42], [0x0A47, 0x0A48], [0x0A4B, 0x0A4D],
|
||||||
|
[0x0A70, 0x0A71], [0x0A81, 0x0A82], [0x0ABC, 0x0ABC],
|
||||||
|
[0x0AC1, 0x0AC5], [0x0AC7, 0x0AC8], [0x0ACD, 0x0ACD],
|
||||||
|
[0x0AE2, 0x0AE3], [0x0B01, 0x0B01], [0x0B3C, 0x0B3C],
|
||||||
|
[0x0B3F, 0x0B3F], [0x0B41, 0x0B43], [0x0B4D, 0x0B4D],
|
||||||
|
[0x0B56, 0x0B56], [0x0B82, 0x0B82], [0x0BC0, 0x0BC0],
|
||||||
|
[0x0BCD, 0x0BCD], [0x0C3E, 0x0C40], [0x0C46, 0x0C48],
|
||||||
|
[0x0C4A, 0x0C4D], [0x0C55, 0x0C56], [0x0CBC, 0x0CBC],
|
||||||
|
[0x0CBF, 0x0CBF], [0x0CC6, 0x0CC6], [0x0CCC, 0x0CCD],
|
||||||
|
[0x0CE2, 0x0CE3], [0x0D41, 0x0D43], [0x0D4D, 0x0D4D],
|
||||||
|
[0x0DCA, 0x0DCA], [0x0DD2, 0x0DD4], [0x0DD6, 0x0DD6],
|
||||||
|
[0x0E31, 0x0E31], [0x0E34, 0x0E3A], [0x0E47, 0x0E4E],
|
||||||
|
[0x0EB1, 0x0EB1], [0x0EB4, 0x0EB9], [0x0EBB, 0x0EBC],
|
||||||
|
[0x0EC8, 0x0ECD], [0x0F18, 0x0F19], [0x0F35, 0x0F35],
|
||||||
|
[0x0F37, 0x0F37], [0x0F39, 0x0F39], [0x0F71, 0x0F7E],
|
||||||
|
[0x0F80, 0x0F84], [0x0F86, 0x0F87], [0x0F90, 0x0F97],
|
||||||
|
[0x0F99, 0x0FBC], [0x0FC6, 0x0FC6], [0x102D, 0x1030],
|
||||||
|
[0x1032, 0x1032], [0x1036, 0x1037], [0x1039, 0x1039],
|
||||||
|
[0x1058, 0x1059], [0x1160, 0x11FF], [0x135F, 0x135F],
|
||||||
|
[0x1712, 0x1714], [0x1732, 0x1734], [0x1752, 0x1753],
|
||||||
|
[0x1772, 0x1773], [0x17B4, 0x17B5], [0x17B7, 0x17BD],
|
||||||
|
[0x17C6, 0x17C6], [0x17C9, 0x17D3], [0x17DD, 0x17DD],
|
||||||
|
[0x180B, 0x180D], [0x18A9, 0x18A9], [0x1920, 0x1922],
|
||||||
|
[0x1927, 0x1928], [0x1932, 0x1932], [0x1939, 0x193B],
|
||||||
|
[0x1A17, 0x1A18], [0x1B00, 0x1B03], [0x1B34, 0x1B34],
|
||||||
|
[0x1B36, 0x1B3A], [0x1B3C, 0x1B3C], [0x1B42, 0x1B42],
|
||||||
|
[0x1B6B, 0x1B73], [0x1DC0, 0x1DCA], [0x1DFE, 0x1DFF],
|
||||||
|
[0x200B, 0x200F], [0x202A, 0x202E], [0x2060, 0x2063],
|
||||||
|
[0x206A, 0x206F], [0x20D0, 0x20EF], [0x302A, 0x302F],
|
||||||
|
[0x3099, 0x309A], [0xA806, 0xA806], [0xA80B, 0xA80B],
|
||||||
|
[0xA825, 0xA826], [0xFB1E, 0xFB1E], [0xFE00, 0xFE0F],
|
||||||
|
[0xFE20, 0xFE23], [0xFEFF, 0xFEFF], [0xFFF9, 0xFFFB],
|
||||||
|
[0x10A01, 0x10A03], [0x10A05, 0x10A06], [0x10A0C, 0x10A0F],
|
||||||
|
[0x10A38, 0x10A3A], [0x10A3F, 0x10A3F], [0x1D167, 0x1D169],
|
||||||
|
[0x1D173, 0x1D182], [0x1D185, 0x1D18B], [0x1D1AA, 0x1D1AD],
|
||||||
|
[0x1D242, 0x1D244], [0xE0001, 0xE0001], [0xE0020, 0xE007F],
|
||||||
|
[0xE0100, 0xE01EF]
|
||||||
|
];
|
||||||
|
// binary search
|
||||||
|
function bisearch(ucs) {
|
||||||
|
let min = 0;
|
||||||
|
let max = COMBINING.length - 1;
|
||||||
|
let mid;
|
||||||
|
if (ucs < COMBINING[0][0] || ucs > COMBINING[max][1])
|
||||||
|
return false;
|
||||||
|
while (max >= min) {
|
||||||
|
mid = Math.floor((min + max) / 2);
|
||||||
|
if (ucs > COMBINING[mid][1])
|
||||||
|
min = mid + 1;
|
||||||
|
else if (ucs < COMBINING[mid][0])
|
||||||
|
max = mid - 1;
|
||||||
|
else
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
function wcwidth(ucs) {
|
||||||
|
// test for 8-bit control characters
|
||||||
|
if (ucs === 0)
|
||||||
|
return opts.nul;
|
||||||
|
if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0))
|
||||||
|
return opts.control;
|
||||||
|
// binary search in table of non-spacing characters
|
||||||
|
if (bisearch(ucs))
|
||||||
|
return 0;
|
||||||
|
// if we arrive here, ucs is not a combining or C0/C1 control character
|
||||||
|
if (isWide(ucs)) {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
function isWide(ucs) {
|
||||||
|
return (
|
||||||
|
ucs >= 0x1100 && (
|
||||||
|
ucs <= 0x115f || // Hangul Jamo init. consonants
|
||||||
|
ucs === 0x2329 ||
|
||||||
|
ucs === 0x232a ||
|
||||||
|
(ucs >= 0x2e80 && ucs <= 0xa4cf && ucs !== 0x303f) || // CJK..Yi
|
||||||
|
(ucs >= 0xac00 && ucs <= 0xd7a3) || // Hangul Syllables
|
||||||
|
(ucs >= 0xf900 && ucs <= 0xfaff) || // CJK Compat Ideographs
|
||||||
|
(ucs >= 0xfe10 && ucs <= 0xfe19) || // Vertical forms
|
||||||
|
(ucs >= 0xfe30 && ucs <= 0xfe6f) || // CJK Compat Forms
|
||||||
|
(ucs >= 0xff00 && ucs <= 0xff60) || // Fullwidth Forms
|
||||||
|
(ucs >= 0xffe0 && ucs <= 0xffe6) ||
|
||||||
|
(ucs >= 0x20000 && ucs <= 0x2fffd) ||
|
||||||
|
(ucs >= 0x30000 && ucs <= 0x3fffd)));
|
||||||
|
}
|
||||||
|
return wcwidth;
|
||||||
|
})({nul: 0, control: 0}); // configurable options
|
||||||
|
|
||||||
|
describe('wcwidth', () => {
|
||||||
|
it('same as old implementation for BMP and individual higher', (done) => {
|
||||||
|
for (let i = 0; i < 65536; ++i)
|
||||||
|
assert.equal(wcwidth(i), old_wcwidth(i));
|
||||||
|
// test some individual higher to fullfill branching
|
||||||
|
assert.equal(wcwidth(0x10A01), old_wcwidth(0x10A01));
|
||||||
|
assert.equal(wcwidth(0x30000), old_wcwidth(0x30000));
|
||||||
|
assert.equal(wcwidth(0x3fffe), old_wcwidth(0x3fffe));
|
||||||
|
done();
|
||||||
|
}).timeout(3000);
|
||||||
|
/*
|
||||||
|
it('new is at least 5 times faster', () => {
|
||||||
|
let start_new = new Date().getTime();
|
||||||
|
let x = 0;
|
||||||
|
for (let runs = 0; runs < 1; ++runs)
|
||||||
|
for (let i = 0; i < 65536; ++i)
|
||||||
|
x = wcwidth(i);
|
||||||
|
let end_new = new Date().getTime();
|
||||||
|
let start_old = new Date().getTime();
|
||||||
|
let y = 0;
|
||||||
|
for (let runs = 0; runs < 1; ++runs)
|
||||||
|
for (let i = 0; i < 65536; ++i)
|
||||||
|
y = old_wcwidth(i);
|
||||||
|
let end_old = new Date().getTime();
|
||||||
|
// console.log((end_new - start_new));
|
||||||
|
// console.log((end_old - start_old));
|
||||||
|
assert.equal(((end_new - start_new) * 5 < (end_old - start_old)), true);
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
});
|
||||||
|
|||||||
+328
-290
@@ -27,44 +27,48 @@ export class InputHandler implements IInputHandler {
|
|||||||
char = this._terminal.charset[char];
|
char = this._terminal.charset[char];
|
||||||
}
|
}
|
||||||
|
|
||||||
let row = this._terminal.y + this._terminal.ybase;
|
let row = this._terminal.buffer.y + this._terminal.buffer.ybase;
|
||||||
|
|
||||||
// insert combining char in last cell
|
// insert combining char in last cell
|
||||||
// FIXME: needs handling after cursor jumps
|
// FIXME: needs handling after cursor jumps
|
||||||
if (!ch_width && this._terminal.x) {
|
if (!ch_width && this._terminal.buffer.x) {
|
||||||
// dont overflow left
|
// dont overflow left
|
||||||
if (this._terminal.lines.get(row)[this._terminal.x - 1]) {
|
if (this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 1]) {
|
||||||
if (!this._terminal.lines.get(row)[this._terminal.x - 1][2]) {
|
if (!this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 1][2]) {
|
||||||
|
|
||||||
// found empty cell after fullwidth, need to go 2 cells back
|
// found empty cell after fullwidth, need to go 2 cells back
|
||||||
if (this._terminal.lines.get(row)[this._terminal.x - 2])
|
if (this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 2])
|
||||||
this._terminal.lines.get(row)[this._terminal.x - 2][1] += char;
|
this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 2][1] += char;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
this._terminal.lines.get(row)[this._terminal.x - 1][1] += char;
|
this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 1][1] += char;
|
||||||
}
|
}
|
||||||
this._terminal.updateRange(this._terminal.y);
|
this._terminal.updateRange(this._terminal.buffer.y);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// goto next line if ch would overflow
|
// goto next line if ch would overflow
|
||||||
// TODO: needs a global min terminal width of 2
|
// TODO: needs a global min terminal width of 2
|
||||||
if (this._terminal.x + ch_width - 1 >= this._terminal.cols) {
|
if (this._terminal.buffer.x + ch_width - 1 >= this._terminal.cols) {
|
||||||
// autowrap - DECAWM
|
// autowrap - DECAWM
|
||||||
if (this._terminal.wraparoundMode) {
|
if (this._terminal.wraparoundMode) {
|
||||||
this._terminal.x = 0;
|
this._terminal.buffer.x = 0;
|
||||||
this._terminal.y++;
|
this._terminal.buffer.y++;
|
||||||
if (this._terminal.y > this._terminal.scrollBottom) {
|
if (this._terminal.buffer.y > this._terminal.buffer.scrollBottom) {
|
||||||
this._terminal.y--;
|
this._terminal.buffer.y--;
|
||||||
this._terminal.scroll(true);
|
this._terminal.scroll(true);
|
||||||
|
} else {
|
||||||
|
// The line already exists (eg. the initial viewport), mark it as a
|
||||||
|
// wrapped line
|
||||||
|
this._terminal.buffer.lines.get(this._terminal.buffer.y).isWrapped = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (ch_width === 2) // FIXME: check for xterm behavior
|
if (ch_width === 2) // FIXME: check for xterm behavior
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
row = this._terminal.y + this._terminal.ybase;
|
row = this._terminal.buffer.y + this._terminal.buffer.ybase;
|
||||||
|
|
||||||
// insert mode: move characters to right
|
// insert mode: move characters to right
|
||||||
if (this._terminal.insertMode) {
|
if (this._terminal.insertMode) {
|
||||||
@@ -72,26 +76,26 @@ export class InputHandler implements IInputHandler {
|
|||||||
for (let moves = 0; moves < ch_width; ++moves) {
|
for (let moves = 0; moves < ch_width; ++moves) {
|
||||||
// remove last cell, if it's width is 0
|
// remove last cell, if it's width is 0
|
||||||
// we have to adjust the second last cell as well
|
// we have to adjust the second last cell as well
|
||||||
const removed = this._terminal.lines.get(this._terminal.y + this._terminal.ybase).pop();
|
const removed = this._terminal.buffer.lines.get(this._terminal.buffer.y + this._terminal.buffer.ybase).pop();
|
||||||
if (removed[2] === 0
|
if (removed[2] === 0
|
||||||
&& this._terminal.lines.get(row)[this._terminal.cols - 2]
|
&& this._terminal.buffer.lines.get(row)[this._terminal.cols - 2]
|
||||||
&& this._terminal.lines.get(row)[this._terminal.cols - 2][2] === 2) {
|
&& this._terminal.buffer.lines.get(row)[this._terminal.cols - 2][2] === 2) {
|
||||||
this._terminal.lines.get(row)[this._terminal.cols - 2] = [this._terminal.curAttr, ' ', 1];
|
this._terminal.buffer.lines.get(row)[this._terminal.cols - 2] = [this._terminal.curAttr, ' ', 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
// insert empty cell at cursor
|
// insert empty cell at cursor
|
||||||
this._terminal.lines.get(row).splice(this._terminal.x, 0, [this._terminal.curAttr, ' ', 1]);
|
this._terminal.buffer.lines.get(row).splice(this._terminal.buffer.x, 0, [this._terminal.curAttr, ' ', 1]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this._terminal.lines.get(row)[this._terminal.x] = [this._terminal.curAttr, char, ch_width];
|
this._terminal.buffer.lines.get(row)[this._terminal.buffer.x] = [this._terminal.curAttr, char, ch_width];
|
||||||
this._terminal.x++;
|
this._terminal.buffer.x++;
|
||||||
this._terminal.updateRange(this._terminal.y);
|
this._terminal.updateRange(this._terminal.buffer.y);
|
||||||
|
|
||||||
// fullwidth char - set next cell width to zero and advance cursor
|
// fullwidth char - set next cell width to zero and advance cursor
|
||||||
if (ch_width === 2) {
|
if (ch_width === 2) {
|
||||||
this._terminal.lines.get(row)[this._terminal.x] = [this._terminal.curAttr, '', 0];
|
this._terminal.buffer.lines.get(row)[this._terminal.buffer.x] = [this._terminal.curAttr, '', 0];
|
||||||
this._terminal.x++;
|
this._terminal.buffer.x++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -117,17 +121,23 @@ export class InputHandler implements IInputHandler {
|
|||||||
*/
|
*/
|
||||||
public lineFeed(): void {
|
public lineFeed(): void {
|
||||||
if (this._terminal.convertEol) {
|
if (this._terminal.convertEol) {
|
||||||
this._terminal.x = 0;
|
this._terminal.buffer.x = 0;
|
||||||
}
|
}
|
||||||
this._terminal.y++;
|
this._terminal.buffer.y++;
|
||||||
if (this._terminal.y > this._terminal.scrollBottom) {
|
if (this._terminal.buffer.y > this._terminal.buffer.scrollBottom) {
|
||||||
this._terminal.y--;
|
this._terminal.buffer.y--;
|
||||||
this._terminal.scroll();
|
this._terminal.scroll();
|
||||||
}
|
}
|
||||||
// If the end of the line is hit, prevent this action from wrapping around to the next line.
|
// If the end of the line is hit, prevent this action from wrapping around to the next line.
|
||||||
if (this._terminal.x >= this._terminal.cols) {
|
if (this._terminal.buffer.x >= this._terminal.cols) {
|
||||||
this._terminal.x--;
|
this._terminal.buffer.x--;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* This event is emitted whenever the terminal outputs a LF or NL.
|
||||||
|
*
|
||||||
|
* @event lineFeed
|
||||||
|
*/
|
||||||
|
this._terminal.emit('lineFeed');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -135,7 +145,7 @@ export class InputHandler implements IInputHandler {
|
|||||||
* Carriage Return (Ctrl-M).
|
* Carriage Return (Ctrl-M).
|
||||||
*/
|
*/
|
||||||
public carriageReturn(): void {
|
public carriageReturn(): void {
|
||||||
this._terminal.x = 0;
|
this._terminal.buffer.x = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -143,8 +153,8 @@ export class InputHandler implements IInputHandler {
|
|||||||
* Backspace (Ctrl-H).
|
* Backspace (Ctrl-H).
|
||||||
*/
|
*/
|
||||||
public backspace(): void {
|
public backspace(): void {
|
||||||
if (this._terminal.x > 0) {
|
if (this._terminal.buffer.x > 0) {
|
||||||
this._terminal.x--;
|
this._terminal.buffer.x--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,7 +163,7 @@ export class InputHandler implements IInputHandler {
|
|||||||
* Horizontal Tab (HT) (Ctrl-I).
|
* Horizontal Tab (HT) (Ctrl-I).
|
||||||
*/
|
*/
|
||||||
public tab(): void {
|
public tab(): void {
|
||||||
this._terminal.x = this._terminal.nextStop();
|
this._terminal.buffer.x = this._terminal.nextStop();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -184,13 +194,13 @@ export class InputHandler implements IInputHandler {
|
|||||||
param = params[0];
|
param = params[0];
|
||||||
if (param < 1) param = 1;
|
if (param < 1) param = 1;
|
||||||
|
|
||||||
row = this._terminal.y + this._terminal.ybase;
|
row = this._terminal.buffer.y + this._terminal.buffer.ybase;
|
||||||
j = this._terminal.x;
|
j = this._terminal.buffer.x;
|
||||||
ch = [this._terminal.eraseAttr(), ' ', 1]; // xterm
|
ch = [this._terminal.eraseAttr(), ' ', 1]; // xterm
|
||||||
|
|
||||||
while (param-- && j < this._terminal.cols) {
|
while (param-- && j < this._terminal.cols) {
|
||||||
this._terminal.lines.get(row).splice(j++, 0, ch);
|
this._terminal.buffer.lines.get(row).splice(j++, 0, ch);
|
||||||
this._terminal.lines.get(row).pop();
|
this._terminal.buffer.lines.get(row).pop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,9 +213,9 @@ export class InputHandler implements IInputHandler {
|
|||||||
if (param < 1) {
|
if (param < 1) {
|
||||||
param = 1;
|
param = 1;
|
||||||
}
|
}
|
||||||
this._terminal.y -= param;
|
this._terminal.buffer.y -= param;
|
||||||
if (this._terminal.y < 0) {
|
if (this._terminal.buffer.y < 0) {
|
||||||
this._terminal.y = 0;
|
this._terminal.buffer.y = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,13 +228,13 @@ export class InputHandler implements IInputHandler {
|
|||||||
if (param < 1) {
|
if (param < 1) {
|
||||||
param = 1;
|
param = 1;
|
||||||
}
|
}
|
||||||
this._terminal.y += param;
|
this._terminal.buffer.y += param;
|
||||||
if (this._terminal.y >= this._terminal.rows) {
|
if (this._terminal.buffer.y >= this._terminal.rows) {
|
||||||
this._terminal.y = this._terminal.rows - 1;
|
this._terminal.buffer.y = this._terminal.rows - 1;
|
||||||
}
|
}
|
||||||
// If the end of the line is hit, prevent this action from wrapping around to the next line.
|
// If the end of the line is hit, prevent this action from wrapping around to the next line.
|
||||||
if (this._terminal.x >= this._terminal.cols) {
|
if (this._terminal.buffer.x >= this._terminal.cols) {
|
||||||
this._terminal.x--;
|
this._terminal.buffer.x--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,9 +247,9 @@ export class InputHandler implements IInputHandler {
|
|||||||
if (param < 1) {
|
if (param < 1) {
|
||||||
param = 1;
|
param = 1;
|
||||||
}
|
}
|
||||||
this._terminal.x += param;
|
this._terminal.buffer.x += param;
|
||||||
if (this._terminal.x >= this._terminal.cols) {
|
if (this._terminal.buffer.x >= this._terminal.cols) {
|
||||||
this._terminal.x = this._terminal.cols - 1;
|
this._terminal.buffer.x = this._terminal.cols - 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,12 +263,12 @@ export class InputHandler implements IInputHandler {
|
|||||||
param = 1;
|
param = 1;
|
||||||
}
|
}
|
||||||
// If the end of the line is hit, prevent this action from wrapping around to the next line.
|
// If the end of the line is hit, prevent this action from wrapping around to the next line.
|
||||||
if (this._terminal.x >= this._terminal.cols) {
|
if (this._terminal.buffer.x >= this._terminal.cols) {
|
||||||
this._terminal.x--;
|
this._terminal.buffer.x--;
|
||||||
}
|
}
|
||||||
this._terminal.x -= param;
|
this._terminal.buffer.x -= param;
|
||||||
if (this._terminal.x < 0) {
|
if (this._terminal.buffer.x < 0) {
|
||||||
this._terminal.x = 0;
|
this._terminal.buffer.x = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -272,12 +282,12 @@ export class InputHandler implements IInputHandler {
|
|||||||
if (param < 1) {
|
if (param < 1) {
|
||||||
param = 1;
|
param = 1;
|
||||||
}
|
}
|
||||||
this._terminal.y += param;
|
this._terminal.buffer.y += param;
|
||||||
if (this._terminal.y >= this._terminal.rows) {
|
if (this._terminal.buffer.y >= this._terminal.rows) {
|
||||||
this._terminal.y = this._terminal.rows - 1;
|
this._terminal.buffer.y = this._terminal.rows - 1;
|
||||||
}
|
}
|
||||||
this._terminal.x = 0;
|
this._terminal.buffer.x = 0;
|
||||||
};
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -290,12 +300,12 @@ export class InputHandler implements IInputHandler {
|
|||||||
if (param < 1) {
|
if (param < 1) {
|
||||||
param = 1;
|
param = 1;
|
||||||
}
|
}
|
||||||
this._terminal.y -= param;
|
this._terminal.buffer.y -= param;
|
||||||
if (this._terminal.y < 0) {
|
if (this._terminal.buffer.y < 0) {
|
||||||
this._terminal.y = 0;
|
this._terminal.buffer.y = 0;
|
||||||
}
|
}
|
||||||
this._terminal.x = 0;
|
this._terminal.buffer.x = 0;
|
||||||
};
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -307,7 +317,7 @@ export class InputHandler implements IInputHandler {
|
|||||||
if (param < 1) {
|
if (param < 1) {
|
||||||
param = 1;
|
param = 1;
|
||||||
}
|
}
|
||||||
this._terminal.x = param - 1;
|
this._terminal.buffer.x = param - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -337,8 +347,8 @@ export class InputHandler implements IInputHandler {
|
|||||||
col = this._terminal.cols - 1;
|
col = this._terminal.cols - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._terminal.x = col;
|
this._terminal.buffer.x = col;
|
||||||
this._terminal.y = row;
|
this._terminal.buffer.y = row;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -348,7 +358,7 @@ export class InputHandler implements IInputHandler {
|
|||||||
public cursorForwardTab(params: number[]): void {
|
public cursorForwardTab(params: number[]): void {
|
||||||
let param = params[0] || 1;
|
let param = params[0] || 1;
|
||||||
while (param--) {
|
while (param--) {
|
||||||
this._terminal.x = this._terminal.nextStop();
|
this._terminal.buffer.x = this._terminal.nextStop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -368,15 +378,15 @@ export class InputHandler implements IInputHandler {
|
|||||||
let j;
|
let j;
|
||||||
switch (params[0]) {
|
switch (params[0]) {
|
||||||
case 0:
|
case 0:
|
||||||
this._terminal.eraseRight(this._terminal.x, this._terminal.y);
|
this._terminal.eraseRight(this._terminal.buffer.x, this._terminal.buffer.y);
|
||||||
j = this._terminal.y + 1;
|
j = this._terminal.buffer.y + 1;
|
||||||
for (; j < this._terminal.rows; j++) {
|
for (; j < this._terminal.rows; j++) {
|
||||||
this._terminal.eraseLine(j);
|
this._terminal.eraseLine(j);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
this._terminal.eraseLeft(this._terminal.x, this._terminal.y);
|
this._terminal.eraseLeft(this._terminal.buffer.x, this._terminal.buffer.y);
|
||||||
j = this._terminal.y;
|
j = this._terminal.buffer.y;
|
||||||
while (j--) {
|
while (j--) {
|
||||||
this._terminal.eraseLine(j);
|
this._terminal.eraseLine(j);
|
||||||
}
|
}
|
||||||
@@ -387,11 +397,13 @@ export class InputHandler implements IInputHandler {
|
|||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
// Clear scrollback (everything not in viewport)
|
// Clear scrollback (everything not in viewport)
|
||||||
const scrollBackSize = this._terminal.lines.length - this._terminal.rows;
|
const scrollBackSize = this._terminal.buffer.lines.length - this._terminal.rows;
|
||||||
if (scrollBackSize > 0) {
|
if (scrollBackSize > 0) {
|
||||||
this._terminal.lines.trimStart(scrollBackSize);
|
this._terminal.buffer.lines.trimStart(scrollBackSize);
|
||||||
this._terminal.ybase = Math.max(this._terminal.ybase - scrollBackSize, 0);
|
this._terminal.buffer.ybase = Math.max(this._terminal.buffer.ybase - scrollBackSize, 0);
|
||||||
this._terminal.ydisp = Math.max(this._terminal.ydisp - 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;
|
break;
|
||||||
}
|
}
|
||||||
@@ -411,13 +423,13 @@ export class InputHandler implements IInputHandler {
|
|||||||
public eraseInLine(params: number[]): void {
|
public eraseInLine(params: number[]): void {
|
||||||
switch (params[0]) {
|
switch (params[0]) {
|
||||||
case 0:
|
case 0:
|
||||||
this._terminal.eraseRight(this._terminal.x, this._terminal.y);
|
this._terminal.eraseRight(this._terminal.buffer.x, this._terminal.buffer.y);
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
this._terminal.eraseLeft(this._terminal.x, this._terminal.y);
|
this._terminal.eraseLeft(this._terminal.buffer.x, this._terminal.buffer.y);
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
this._terminal.eraseLine(this._terminal.y);
|
this._terminal.eraseLine(this._terminal.buffer.y);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -433,29 +445,29 @@ export class InputHandler implements IInputHandler {
|
|||||||
if (param < 1) {
|
if (param < 1) {
|
||||||
param = 1;
|
param = 1;
|
||||||
}
|
}
|
||||||
row = this._terminal.y + this._terminal.ybase;
|
row = this._terminal.buffer.y + this._terminal.buffer.ybase;
|
||||||
|
|
||||||
j = this._terminal.rows - 1 - this._terminal.scrollBottom;
|
j = this._terminal.rows - 1 - this._terminal.buffer.scrollBottom;
|
||||||
j = this._terminal.rows - 1 + this._terminal.ybase - j + 1;
|
j = this._terminal.rows - 1 + this._terminal.buffer.ybase - j + 1;
|
||||||
|
|
||||||
while (param--) {
|
while (param--) {
|
||||||
if (this._terminal.lines.length === this._terminal.lines.maxLength) {
|
if (this._terminal.buffer.lines.length === this._terminal.buffer.lines.maxLength) {
|
||||||
// Trim the start of lines to make room for the new line
|
// Trim the start of lines to make room for the new line
|
||||||
this._terminal.lines.trimStart(1);
|
this._terminal.buffer.lines.trimStart(1);
|
||||||
this._terminal.ybase--;
|
this._terminal.buffer.ybase--;
|
||||||
this._terminal.ydisp--;
|
this._terminal.buffer.ydisp--;
|
||||||
row--;
|
row--;
|
||||||
j--;
|
j--;
|
||||||
}
|
}
|
||||||
// test: echo -e '\e[44m\e[1L\e[0m'
|
// test: echo -e '\e[44m\e[1L\e[0m'
|
||||||
// blankLine(true) - xterm/linux behavior
|
// blankLine(true) - xterm/linux behavior
|
||||||
this._terminal.lines.splice(row, 0, this._terminal.blankLine(true));
|
this._terminal.buffer.lines.splice(row, 0, this._terminal.blankLine(true));
|
||||||
this._terminal.lines.splice(j, 1);
|
this._terminal.buffer.lines.splice(j, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// this.maxRange();
|
// this.maxRange();
|
||||||
this._terminal.updateRange(this._terminal.y);
|
this._terminal.updateRange(this._terminal.buffer.y);
|
||||||
this._terminal.updateRange(this._terminal.scrollBottom);
|
this._terminal.updateRange(this._terminal.buffer.scrollBottom);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -469,27 +481,27 @@ export class InputHandler implements IInputHandler {
|
|||||||
if (param < 1) {
|
if (param < 1) {
|
||||||
param = 1;
|
param = 1;
|
||||||
}
|
}
|
||||||
row = this._terminal.y + this._terminal.ybase;
|
row = this._terminal.buffer.y + this._terminal.buffer.ybase;
|
||||||
|
|
||||||
j = this._terminal.rows - 1 - this._terminal.scrollBottom;
|
j = this._terminal.rows - 1 - this._terminal.buffer.scrollBottom;
|
||||||
j = this._terminal.rows - 1 + this._terminal.ybase - j;
|
j = this._terminal.rows - 1 + this._terminal.buffer.ybase - j;
|
||||||
|
|
||||||
while (param--) {
|
while (param--) {
|
||||||
if (this._terminal.lines.length === this._terminal.lines.maxLength) {
|
if (this._terminal.buffer.lines.length === this._terminal.buffer.lines.maxLength) {
|
||||||
// Trim the start of lines to make room for the new line
|
// Trim the start of lines to make room for the new line
|
||||||
this._terminal.lines.trimStart(1);
|
this._terminal.buffer.lines.trimStart(1);
|
||||||
this._terminal.ybase -= 1;
|
this._terminal.buffer.ybase -= 1;
|
||||||
this._terminal.ydisp -= 1;
|
this._terminal.buffer.ydisp -= 1;
|
||||||
}
|
}
|
||||||
// test: echo -e '\e[44m\e[1M\e[0m'
|
// test: echo -e '\e[44m\e[1M\e[0m'
|
||||||
// blankLine(true) - xterm/linux behavior
|
// blankLine(true) - xterm/linux behavior
|
||||||
this._terminal.lines.splice(j + 1, 0, this._terminal.blankLine(true));
|
this._terminal.buffer.lines.splice(j + 1, 0, this._terminal.blankLine(true));
|
||||||
this._terminal.lines.splice(row, 1);
|
this._terminal.buffer.lines.splice(row, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// this.maxRange();
|
// this.maxRange();
|
||||||
this._terminal.updateRange(this._terminal.y);
|
this._terminal.updateRange(this._terminal.buffer.y);
|
||||||
this._terminal.updateRange(this._terminal.scrollBottom);
|
this._terminal.updateRange(this._terminal.buffer.scrollBottom);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -504,12 +516,12 @@ export class InputHandler implements IInputHandler {
|
|||||||
param = 1;
|
param = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
row = this._terminal.y + this._terminal.ybase;
|
row = this._terminal.buffer.y + this._terminal.buffer.ybase;
|
||||||
ch = [this._terminal.eraseAttr(), ' ', 1]; // xterm
|
ch = [this._terminal.eraseAttr(), ' ', 1]; // xterm
|
||||||
|
|
||||||
while (param--) {
|
while (param--) {
|
||||||
this._terminal.lines.get(row).splice(this._terminal.x, 1);
|
this._terminal.buffer.lines.get(row).splice(this._terminal.buffer.x, 1);
|
||||||
this._terminal.lines.get(row).push(ch);
|
this._terminal.buffer.lines.get(row).push(ch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -519,12 +531,12 @@ export class InputHandler implements IInputHandler {
|
|||||||
public scrollUp(params: number[]): void {
|
public scrollUp(params: number[]): void {
|
||||||
let param = params[0] || 1;
|
let param = params[0] || 1;
|
||||||
while (param--) {
|
while (param--) {
|
||||||
this._terminal.lines.splice(this._terminal.ybase + this._terminal.scrollTop, 1);
|
this._terminal.buffer.lines.splice(this._terminal.buffer.ybase + this._terminal.buffer.scrollTop, 1);
|
||||||
this._terminal.lines.splice(this._terminal.ybase + this._terminal.scrollBottom, 0, this._terminal.blankLine());
|
this._terminal.buffer.lines.splice(this._terminal.buffer.ybase + this._terminal.buffer.scrollBottom, 0, this._terminal.blankLine());
|
||||||
}
|
}
|
||||||
// this.maxRange();
|
// this.maxRange();
|
||||||
this._terminal.updateRange(this._terminal.scrollTop);
|
this._terminal.updateRange(this._terminal.buffer.scrollTop);
|
||||||
this._terminal.updateRange(this._terminal.scrollBottom);
|
this._terminal.updateRange(this._terminal.buffer.scrollBottom);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -533,12 +545,12 @@ export class InputHandler implements IInputHandler {
|
|||||||
public scrollDown(params: number[]): void {
|
public scrollDown(params: number[]): void {
|
||||||
let param = params[0] || 1;
|
let param = params[0] || 1;
|
||||||
while (param--) {
|
while (param--) {
|
||||||
this._terminal.lines.splice(this._terminal.ybase + this._terminal.scrollBottom, 1);
|
this._terminal.buffer.lines.splice(this._terminal.buffer.ybase + this._terminal.buffer.scrollBottom, 1);
|
||||||
this._terminal.lines.splice(this._terminal.ybase + this._terminal.scrollTop, 0, this._terminal.blankLine());
|
this._terminal.buffer.lines.splice(this._terminal.buffer.ybase + this._terminal.buffer.scrollTop, 0, this._terminal.blankLine());
|
||||||
}
|
}
|
||||||
// this.maxRange();
|
// this.maxRange();
|
||||||
this._terminal.updateRange(this._terminal.scrollTop);
|
this._terminal.updateRange(this._terminal.buffer.scrollTop);
|
||||||
this._terminal.updateRange(this._terminal.scrollBottom);
|
this._terminal.updateRange(this._terminal.buffer.scrollBottom);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -553,12 +565,12 @@ export class InputHandler implements IInputHandler {
|
|||||||
param = 1;
|
param = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
row = this._terminal.y + this._terminal.ybase;
|
row = this._terminal.buffer.y + this._terminal.buffer.ybase;
|
||||||
j = this._terminal.x;
|
j = this._terminal.buffer.x;
|
||||||
ch = [this._terminal.eraseAttr(), ' ', 1]; // xterm
|
ch = [this._terminal.eraseAttr(), ' ', 1]; // xterm
|
||||||
|
|
||||||
while (param-- && j < this._terminal.cols) {
|
while (param-- && j < this._terminal.cols) {
|
||||||
this._terminal.lines.get(row)[j++] = ch;
|
this._terminal.buffer.lines.get(row)[j++] = ch;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -568,7 +580,7 @@ export class InputHandler implements IInputHandler {
|
|||||||
public cursorBackwardTab(params: number[]): void {
|
public cursorBackwardTab(params: number[]): void {
|
||||||
let param = params[0] || 1;
|
let param = params[0] || 1;
|
||||||
while (param--) {
|
while (param--) {
|
||||||
this._terminal.x = this._terminal.prevStop();
|
this._terminal.buffer.x = this._terminal.prevStop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -581,9 +593,9 @@ export class InputHandler implements IInputHandler {
|
|||||||
if (param < 1) {
|
if (param < 1) {
|
||||||
param = 1;
|
param = 1;
|
||||||
}
|
}
|
||||||
this._terminal.x = param - 1;
|
this._terminal.buffer.x = param - 1;
|
||||||
if (this._terminal.x >= this._terminal.cols) {
|
if (this._terminal.buffer.x >= this._terminal.cols) {
|
||||||
this._terminal.x = this._terminal.cols - 1;
|
this._terminal.buffer.x = this._terminal.cols - 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -597,9 +609,9 @@ export class InputHandler implements IInputHandler {
|
|||||||
if (param < 1) {
|
if (param < 1) {
|
||||||
param = 1;
|
param = 1;
|
||||||
}
|
}
|
||||||
this._terminal.x += param;
|
this._terminal.buffer.x += param;
|
||||||
if (this._terminal.x >= this._terminal.cols) {
|
if (this._terminal.buffer.x >= this._terminal.cols) {
|
||||||
this._terminal.x = this._terminal.cols - 1;
|
this._terminal.buffer.x = this._terminal.cols - 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -608,11 +620,11 @@ export class InputHandler implements IInputHandler {
|
|||||||
*/
|
*/
|
||||||
public repeatPrecedingCharacter(params: number[]): void {
|
public repeatPrecedingCharacter(params: number[]): void {
|
||||||
let param = params[0] || 1
|
let param = params[0] || 1
|
||||||
, line = this._terminal.lines.get(this._terminal.ybase + this._terminal.y)
|
, line = this._terminal.buffer.lines.get(this._terminal.buffer.ybase + this._terminal.buffer.y)
|
||||||
, ch = line[this._terminal.x - 1] || [this._terminal.defAttr, ' ', 1];
|
, ch = line[this._terminal.buffer.x - 1] || [this._terminal.defAttr, ' ', 1];
|
||||||
|
|
||||||
while (param--) {
|
while (param--) {
|
||||||
line[this._terminal.x++] = ch;
|
line[this._terminal.buffer.x++] = ch;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -691,9 +703,9 @@ export class InputHandler implements IInputHandler {
|
|||||||
if (param < 1) {
|
if (param < 1) {
|
||||||
param = 1;
|
param = 1;
|
||||||
}
|
}
|
||||||
this._terminal.y = param - 1;
|
this._terminal.buffer.y = param - 1;
|
||||||
if (this._terminal.y >= this._terminal.rows) {
|
if (this._terminal.buffer.y >= this._terminal.rows) {
|
||||||
this._terminal.y = this._terminal.rows - 1;
|
this._terminal.buffer.y = this._terminal.rows - 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -707,13 +719,13 @@ export class InputHandler implements IInputHandler {
|
|||||||
if (param < 1) {
|
if (param < 1) {
|
||||||
param = 1;
|
param = 1;
|
||||||
}
|
}
|
||||||
this._terminal.y += param;
|
this._terminal.buffer.y += param;
|
||||||
if (this._terminal.y >= this._terminal.rows) {
|
if (this._terminal.buffer.y >= this._terminal.rows) {
|
||||||
this._terminal.y = this._terminal.rows - 1;
|
this._terminal.buffer.y = this._terminal.rows - 1;
|
||||||
}
|
}
|
||||||
// If the end of the line is hit, prevent this action from wrapping around to the next line.
|
// If the end of the line is hit, prevent this action from wrapping around to the next line.
|
||||||
if (this._terminal.x >= this._terminal.cols) {
|
if (this._terminal.buffer.x >= this._terminal.cols) {
|
||||||
this._terminal.x--;
|
this._terminal.buffer.x--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -726,14 +738,14 @@ export class InputHandler implements IInputHandler {
|
|||||||
if (params[0] < 1) params[0] = 1;
|
if (params[0] < 1) params[0] = 1;
|
||||||
if (params[1] < 1) params[1] = 1;
|
if (params[1] < 1) params[1] = 1;
|
||||||
|
|
||||||
this._terminal.y = params[0] - 1;
|
this._terminal.buffer.y = params[0] - 1;
|
||||||
if (this._terminal.y >= this._terminal.rows) {
|
if (this._terminal.buffer.y >= this._terminal.rows) {
|
||||||
this._terminal.y = this._terminal.rows - 1;
|
this._terminal.buffer.y = this._terminal.rows - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._terminal.x = params[1] - 1;
|
this._terminal.buffer.x = params[1] - 1;
|
||||||
if (this._terminal.x >= this._terminal.cols) {
|
if (this._terminal.buffer.x >= this._terminal.cols) {
|
||||||
this._terminal.x = this._terminal.cols - 1;
|
this._terminal.buffer.x = this._terminal.cols - 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -748,9 +760,9 @@ export class InputHandler implements IInputHandler {
|
|||||||
public tabClear(params: number[]): void {
|
public tabClear(params: number[]): void {
|
||||||
let param = params[0];
|
let param = params[0];
|
||||||
if (param <= 0) {
|
if (param <= 0) {
|
||||||
delete this._terminal.tabs[this._terminal.x];
|
delete this._terminal.buffer.tabs[this._terminal.buffer.x];
|
||||||
} else if (param === 3) {
|
} else if (param === 3) {
|
||||||
this._terminal.tabs = {};
|
this._terminal.buffer.tabs = {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -936,30 +948,14 @@ export class InputHandler implements IInputHandler {
|
|||||||
this._terminal.cursorHidden = false;
|
this._terminal.cursorHidden = false;
|
||||||
break;
|
break;
|
||||||
case 1049: // alt screen buffer cursor
|
case 1049: // alt screen buffer cursor
|
||||||
// this._terminal.saveCursor();
|
// TODO: Not sure if we need to save/restore after switching the buffer
|
||||||
; // FALL-THROUGH
|
// this.saveCursor(params);
|
||||||
|
// FALL-THROUGH
|
||||||
case 47: // alt screen buffer
|
case 47: // alt screen buffer
|
||||||
case 1047: // alt screen buffer
|
case 1047: // alt screen buffer
|
||||||
if (!this._terminal.normal) {
|
this._terminal.buffers.activateAltBuffer();
|
||||||
let normal = {
|
this._terminal.viewport.syncScrollArea();
|
||||||
lines: this._terminal.lines,
|
this._terminal.showCursor();
|
||||||
ybase: this._terminal.ybase,
|
|
||||||
ydisp: this._terminal.ydisp,
|
|
||||||
x: this._terminal.x,
|
|
||||||
y: this._terminal.y,
|
|
||||||
scrollTop: this._terminal.scrollTop,
|
|
||||||
scrollBottom: this._terminal.scrollBottom,
|
|
||||||
tabs: this._terminal.tabs
|
|
||||||
// XXX save charset(s) here?
|
|
||||||
// charset: this._terminal.charset,
|
|
||||||
// glevel: this._terminal.glevel,
|
|
||||||
// charsets: this._terminal.charsets
|
|
||||||
};
|
|
||||||
this._terminal.reset();
|
|
||||||
this._terminal.viewport.syncScrollArea();
|
|
||||||
this._terminal.normal = normal;
|
|
||||||
this._terminal.showCursor();
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1117,29 +1113,19 @@ export class InputHandler implements IInputHandler {
|
|||||||
this._terminal.cursorHidden = true;
|
this._terminal.cursorHidden = true;
|
||||||
break;
|
break;
|
||||||
case 1049: // alt screen buffer cursor
|
case 1049: // alt screen buffer cursor
|
||||||
; // FALL-THROUGH
|
// FALL-THROUGH
|
||||||
case 47: // normal screen buffer
|
case 47: // normal screen buffer
|
||||||
case 1047: // normal screen buffer - clearing it first
|
case 1047: // normal screen buffer - clearing it first
|
||||||
if (this._terminal.normal) {
|
// Ensure the selection manager has the correct buffer
|
||||||
this._terminal.lines = this._terminal.normal.lines;
|
this._terminal.buffers.activateNormalBuffer();
|
||||||
this._terminal.ybase = this._terminal.normal.ybase;
|
// TODO: Not sure if we need to save/restore after switching the buffer
|
||||||
this._terminal.ydisp = this._terminal.normal.ydisp;
|
// if (params[0] === 1049) {
|
||||||
this._terminal.x = this._terminal.normal.x;
|
// this.restoreCursor(params);
|
||||||
this._terminal.y = this._terminal.normal.y;
|
// }
|
||||||
this._terminal.scrollTop = this._terminal.normal.scrollTop;
|
this._terminal.selectionManager.setBuffer(this._terminal.buffer.lines);
|
||||||
this._terminal.scrollBottom = this._terminal.normal.scrollBottom;
|
this._terminal.refresh(0, this._terminal.rows - 1);
|
||||||
this._terminal.tabs = this._terminal.normal.tabs;
|
this._terminal.viewport.syncScrollArea();
|
||||||
this._terminal.normal = null;
|
this._terminal.showCursor();
|
||||||
// Ensure the selection manager has the correct buffer
|
|
||||||
this._terminal.selectionManager.setBuffer(this._terminal.lines);
|
|
||||||
// if (params === 1049) {
|
|
||||||
// this.x = this.savedX;
|
|
||||||
// this.y = this.savedY;
|
|
||||||
// }
|
|
||||||
this._terminal.refresh(0, this._terminal.rows - 1);
|
|
||||||
this._terminal.viewport.syncScrollArea();
|
|
||||||
this._terminal.showCursor();
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1359,9 +1345,9 @@ export class InputHandler implements IInputHandler {
|
|||||||
case 6:
|
case 6:
|
||||||
// cursor position
|
// cursor position
|
||||||
this._terminal.send(C0.ESC + '['
|
this._terminal.send(C0.ESC + '['
|
||||||
+ (this._terminal.y + 1)
|
+ (this._terminal.buffer.y + 1)
|
||||||
+ ';'
|
+ ';'
|
||||||
+ (this._terminal.x + 1)
|
+ (this._terminal.buffer.x + 1)
|
||||||
+ 'R');
|
+ 'R');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1372,9 +1358,9 @@ export class InputHandler implements IInputHandler {
|
|||||||
case 6:
|
case 6:
|
||||||
// cursor position
|
// cursor position
|
||||||
this._terminal.send(C0.ESC + '[?'
|
this._terminal.send(C0.ESC + '[?'
|
||||||
+ (this._terminal.y + 1)
|
+ (this._terminal.buffer.y + 1)
|
||||||
+ ';'
|
+ ';'
|
||||||
+ (this._terminal.x + 1)
|
+ (this._terminal.buffer.x + 1)
|
||||||
+ 'R');
|
+ 'R');
|
||||||
break;
|
break;
|
||||||
case 15:
|
case 15:
|
||||||
@@ -1409,10 +1395,10 @@ export class InputHandler implements IInputHandler {
|
|||||||
this._terminal.applicationKeypad = false; // ?
|
this._terminal.applicationKeypad = false; // ?
|
||||||
this._terminal.viewport.syncScrollArea();
|
this._terminal.viewport.syncScrollArea();
|
||||||
this._terminal.applicationCursor = false;
|
this._terminal.applicationCursor = false;
|
||||||
this._terminal.scrollTop = 0;
|
this._terminal.buffer.scrollTop = 0;
|
||||||
this._terminal.scrollBottom = this._terminal.rows - 1;
|
this._terminal.buffer.scrollBottom = this._terminal.rows - 1;
|
||||||
this._terminal.curAttr = this._terminal.defAttr;
|
this._terminal.curAttr = this._terminal.defAttr;
|
||||||
this._terminal.x = this._terminal.y = 0; // ?
|
this._terminal.buffer.x = this._terminal.buffer.y = 0; // ?
|
||||||
this._terminal.charset = null;
|
this._terminal.charset = null;
|
||||||
this._terminal.glevel = 0; // ??
|
this._terminal.glevel = 0; // ??
|
||||||
this._terminal.charsets = [null]; // ??
|
this._terminal.charsets = [null]; // ??
|
||||||
@@ -1456,10 +1442,10 @@ export class InputHandler implements IInputHandler {
|
|||||||
*/
|
*/
|
||||||
public setScrollRegion(params: number[]): void {
|
public setScrollRegion(params: number[]): void {
|
||||||
if (this._terminal.prefix) return;
|
if (this._terminal.prefix) return;
|
||||||
this._terminal.scrollTop = (params[0] || 1) - 1;
|
this._terminal.buffer.scrollTop = (params[0] || 1) - 1;
|
||||||
this._terminal.scrollBottom = (params[1] && params[1] <= this._terminal.rows ? params[1] : this._terminal.rows) - 1;
|
this._terminal.buffer.scrollBottom = (params[1] && params[1] <= this._terminal.rows ? params[1] : this._terminal.rows) - 1;
|
||||||
this._terminal.x = 0;
|
this._terminal.buffer.x = 0;
|
||||||
this._terminal.y = 0;
|
this._terminal.buffer.y = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1468,8 +1454,8 @@ export class InputHandler implements IInputHandler {
|
|||||||
* Save cursor (ANSI.SYS).
|
* Save cursor (ANSI.SYS).
|
||||||
*/
|
*/
|
||||||
public saveCursor(params: number[]): void {
|
public saveCursor(params: number[]): void {
|
||||||
this._terminal.savedX = this._terminal.x;
|
this._terminal.buffer.savedX = this._terminal.buffer.x;
|
||||||
this._terminal.savedY = this._terminal.y;
|
this._terminal.buffer.savedY = this._terminal.buffer.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1478,100 +1464,102 @@ export class InputHandler implements IInputHandler {
|
|||||||
* Restore cursor (ANSI.SYS).
|
* Restore cursor (ANSI.SYS).
|
||||||
*/
|
*/
|
||||||
public restoreCursor(params: number[]): void {
|
public restoreCursor(params: number[]): void {
|
||||||
this._terminal.x = this._terminal.savedX || 0;
|
this._terminal.buffer.x = this._terminal.buffer.savedX || 0;
|
||||||
this._terminal.y = this._terminal.savedY || 0;
|
this._terminal.buffer.y = this._terminal.buffer.savedY || 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const wcwidth = (function(opts) {
|
export const wcwidth = (function(opts) {
|
||||||
// extracted from https://www.cl.cam.ac.uk/%7Emgk25/ucs/wcwidth.c
|
// extracted from https://www.cl.cam.ac.uk/%7Emgk25/ucs/wcwidth.c
|
||||||
// combining characters
|
// combining characters
|
||||||
const COMBINING = [
|
const COMBINING_BMP = [
|
||||||
[0x0300, 0x036F], [0x0483, 0x0486], [0x0488, 0x0489],
|
[0x0300, 0x036F], [0x0483, 0x0486], [0x0488, 0x0489],
|
||||||
[0x0591, 0x05BD], [0x05BF, 0x05BF], [0x05C1, 0x05C2],
|
[0x0591, 0x05BD], [0x05BF, 0x05BF], [0x05C1, 0x05C2],
|
||||||
[0x05C4, 0x05C5], [0x05C7, 0x05C7], [0x0600, 0x0603],
|
[0x05C4, 0x05C5], [0x05C7, 0x05C7], [0x0600, 0x0603],
|
||||||
[0x0610, 0x0615], [0x064B, 0x065E], [0x0670, 0x0670],
|
[0x0610, 0x0615], [0x064B, 0x065E], [0x0670, 0x0670],
|
||||||
[0x06D6, 0x06E4], [0x06E7, 0x06E8], [0x06EA, 0x06ED],
|
[0x06D6, 0x06E4], [0x06E7, 0x06E8], [0x06EA, 0x06ED],
|
||||||
[0x070F, 0x070F], [0x0711, 0x0711], [0x0730, 0x074A],
|
[0x070F, 0x070F], [0x0711, 0x0711], [0x0730, 0x074A],
|
||||||
[0x07A6, 0x07B0], [0x07EB, 0x07F3], [0x0901, 0x0902],
|
[0x07A6, 0x07B0], [0x07EB, 0x07F3], [0x0901, 0x0902],
|
||||||
[0x093C, 0x093C], [0x0941, 0x0948], [0x094D, 0x094D],
|
[0x093C, 0x093C], [0x0941, 0x0948], [0x094D, 0x094D],
|
||||||
[0x0951, 0x0954], [0x0962, 0x0963], [0x0981, 0x0981],
|
[0x0951, 0x0954], [0x0962, 0x0963], [0x0981, 0x0981],
|
||||||
[0x09BC, 0x09BC], [0x09C1, 0x09C4], [0x09CD, 0x09CD],
|
[0x09BC, 0x09BC], [0x09C1, 0x09C4], [0x09CD, 0x09CD],
|
||||||
[0x09E2, 0x09E3], [0x0A01, 0x0A02], [0x0A3C, 0x0A3C],
|
[0x09E2, 0x09E3], [0x0A01, 0x0A02], [0x0A3C, 0x0A3C],
|
||||||
[0x0A41, 0x0A42], [0x0A47, 0x0A48], [0x0A4B, 0x0A4D],
|
[0x0A41, 0x0A42], [0x0A47, 0x0A48], [0x0A4B, 0x0A4D],
|
||||||
[0x0A70, 0x0A71], [0x0A81, 0x0A82], [0x0ABC, 0x0ABC],
|
[0x0A70, 0x0A71], [0x0A81, 0x0A82], [0x0ABC, 0x0ABC],
|
||||||
[0x0AC1, 0x0AC5], [0x0AC7, 0x0AC8], [0x0ACD, 0x0ACD],
|
[0x0AC1, 0x0AC5], [0x0AC7, 0x0AC8], [0x0ACD, 0x0ACD],
|
||||||
[0x0AE2, 0x0AE3], [0x0B01, 0x0B01], [0x0B3C, 0x0B3C],
|
[0x0AE2, 0x0AE3], [0x0B01, 0x0B01], [0x0B3C, 0x0B3C],
|
||||||
[0x0B3F, 0x0B3F], [0x0B41, 0x0B43], [0x0B4D, 0x0B4D],
|
[0x0B3F, 0x0B3F], [0x0B41, 0x0B43], [0x0B4D, 0x0B4D],
|
||||||
[0x0B56, 0x0B56], [0x0B82, 0x0B82], [0x0BC0, 0x0BC0],
|
[0x0B56, 0x0B56], [0x0B82, 0x0B82], [0x0BC0, 0x0BC0],
|
||||||
[0x0BCD, 0x0BCD], [0x0C3E, 0x0C40], [0x0C46, 0x0C48],
|
[0x0BCD, 0x0BCD], [0x0C3E, 0x0C40], [0x0C46, 0x0C48],
|
||||||
[0x0C4A, 0x0C4D], [0x0C55, 0x0C56], [0x0CBC, 0x0CBC],
|
[0x0C4A, 0x0C4D], [0x0C55, 0x0C56], [0x0CBC, 0x0CBC],
|
||||||
[0x0CBF, 0x0CBF], [0x0CC6, 0x0CC6], [0x0CCC, 0x0CCD],
|
[0x0CBF, 0x0CBF], [0x0CC6, 0x0CC6], [0x0CCC, 0x0CCD],
|
||||||
[0x0CE2, 0x0CE3], [0x0D41, 0x0D43], [0x0D4D, 0x0D4D],
|
[0x0CE2, 0x0CE3], [0x0D41, 0x0D43], [0x0D4D, 0x0D4D],
|
||||||
[0x0DCA, 0x0DCA], [0x0DD2, 0x0DD4], [0x0DD6, 0x0DD6],
|
[0x0DCA, 0x0DCA], [0x0DD2, 0x0DD4], [0x0DD6, 0x0DD6],
|
||||||
[0x0E31, 0x0E31], [0x0E34, 0x0E3A], [0x0E47, 0x0E4E],
|
[0x0E31, 0x0E31], [0x0E34, 0x0E3A], [0x0E47, 0x0E4E],
|
||||||
[0x0EB1, 0x0EB1], [0x0EB4, 0x0EB9], [0x0EBB, 0x0EBC],
|
[0x0EB1, 0x0EB1], [0x0EB4, 0x0EB9], [0x0EBB, 0x0EBC],
|
||||||
[0x0EC8, 0x0ECD], [0x0F18, 0x0F19], [0x0F35, 0x0F35],
|
[0x0EC8, 0x0ECD], [0x0F18, 0x0F19], [0x0F35, 0x0F35],
|
||||||
[0x0F37, 0x0F37], [0x0F39, 0x0F39], [0x0F71, 0x0F7E],
|
[0x0F37, 0x0F37], [0x0F39, 0x0F39], [0x0F71, 0x0F7E],
|
||||||
[0x0F80, 0x0F84], [0x0F86, 0x0F87], [0x0F90, 0x0F97],
|
[0x0F80, 0x0F84], [0x0F86, 0x0F87], [0x0F90, 0x0F97],
|
||||||
[0x0F99, 0x0FBC], [0x0FC6, 0x0FC6], [0x102D, 0x1030],
|
[0x0F99, 0x0FBC], [0x0FC6, 0x0FC6], [0x102D, 0x1030],
|
||||||
[0x1032, 0x1032], [0x1036, 0x1037], [0x1039, 0x1039],
|
[0x1032, 0x1032], [0x1036, 0x1037], [0x1039, 0x1039],
|
||||||
[0x1058, 0x1059], [0x1160, 0x11FF], [0x135F, 0x135F],
|
[0x1058, 0x1059], [0x1160, 0x11FF], [0x135F, 0x135F],
|
||||||
[0x1712, 0x1714], [0x1732, 0x1734], [0x1752, 0x1753],
|
[0x1712, 0x1714], [0x1732, 0x1734], [0x1752, 0x1753],
|
||||||
[0x1772, 0x1773], [0x17B4, 0x17B5], [0x17B7, 0x17BD],
|
[0x1772, 0x1773], [0x17B4, 0x17B5], [0x17B7, 0x17BD],
|
||||||
[0x17C6, 0x17C6], [0x17C9, 0x17D3], [0x17DD, 0x17DD],
|
[0x17C6, 0x17C6], [0x17C9, 0x17D3], [0x17DD, 0x17DD],
|
||||||
[0x180B, 0x180D], [0x18A9, 0x18A9], [0x1920, 0x1922],
|
[0x180B, 0x180D], [0x18A9, 0x18A9], [0x1920, 0x1922],
|
||||||
[0x1927, 0x1928], [0x1932, 0x1932], [0x1939, 0x193B],
|
[0x1927, 0x1928], [0x1932, 0x1932], [0x1939, 0x193B],
|
||||||
[0x1A17, 0x1A18], [0x1B00, 0x1B03], [0x1B34, 0x1B34],
|
[0x1A17, 0x1A18], [0x1B00, 0x1B03], [0x1B34, 0x1B34],
|
||||||
[0x1B36, 0x1B3A], [0x1B3C, 0x1B3C], [0x1B42, 0x1B42],
|
[0x1B36, 0x1B3A], [0x1B3C, 0x1B3C], [0x1B42, 0x1B42],
|
||||||
[0x1B6B, 0x1B73], [0x1DC0, 0x1DCA], [0x1DFE, 0x1DFF],
|
[0x1B6B, 0x1B73], [0x1DC0, 0x1DCA], [0x1DFE, 0x1DFF],
|
||||||
[0x200B, 0x200F], [0x202A, 0x202E], [0x2060, 0x2063],
|
[0x200B, 0x200F], [0x202A, 0x202E], [0x2060, 0x2063],
|
||||||
[0x206A, 0x206F], [0x20D0, 0x20EF], [0x302A, 0x302F],
|
[0x206A, 0x206F], [0x20D0, 0x20EF], [0x302A, 0x302F],
|
||||||
[0x3099, 0x309A], [0xA806, 0xA806], [0xA80B, 0xA80B],
|
[0x3099, 0x309A], [0xA806, 0xA806], [0xA80B, 0xA80B],
|
||||||
[0xA825, 0xA826], [0xFB1E, 0xFB1E], [0xFE00, 0xFE0F],
|
[0xA825, 0xA826], [0xFB1E, 0xFB1E], [0xFE00, 0xFE0F],
|
||||||
[0xFE20, 0xFE23], [0xFEFF, 0xFEFF], [0xFFF9, 0xFFFB],
|
[0xFE20, 0xFE23], [0xFEFF, 0xFEFF], [0xFFF9, 0xFFFB],
|
||||||
[0x10A01, 0x10A03], [0x10A05, 0x10A06], [0x10A0C, 0x10A0F],
|
];
|
||||||
[0x10A38, 0x10A3A], [0x10A3F, 0x10A3F], [0x1D167, 0x1D169],
|
const COMBINING_HIGH = [
|
||||||
[0x1D173, 0x1D182], [0x1D185, 0x1D18B], [0x1D1AA, 0x1D1AD],
|
[0x10A01, 0x10A03], [0x10A05, 0x10A06], [0x10A0C, 0x10A0F],
|
||||||
[0x1D242, 0x1D244], [0xE0001, 0xE0001], [0xE0020, 0xE007F],
|
[0x10A38, 0x10A3A], [0x10A3F, 0x10A3F], [0x1D167, 0x1D169],
|
||||||
[0xE0100, 0xE01EF]
|
[0x1D173, 0x1D182], [0x1D185, 0x1D18B], [0x1D1AA, 0x1D1AD],
|
||||||
];
|
[0x1D242, 0x1D244], [0xE0001, 0xE0001], [0xE0020, 0xE007F],
|
||||||
// binary search
|
[0xE0100, 0xE01EF]
|
||||||
function bisearch(ucs) {
|
];
|
||||||
let min = 0;
|
// binary search
|
||||||
let max = COMBINING.length - 1;
|
function bisearch(ucs, data) {
|
||||||
let mid;
|
let min = 0;
|
||||||
if (ucs < COMBINING[0][0] || ucs > COMBINING[max][1])
|
let max = data.length - 1;
|
||||||
|
let mid;
|
||||||
|
if (ucs < data[0][0] || ucs > data[max][1])
|
||||||
|
return false;
|
||||||
|
while (max >= min) {
|
||||||
|
mid = (min + max) >> 1;
|
||||||
|
if (ucs > data[mid][1])
|
||||||
|
min = mid + 1;
|
||||||
|
else if (ucs < data[mid][0])
|
||||||
|
max = mid - 1;
|
||||||
|
else
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
while (max >= min) {
|
|
||||||
mid = Math.floor((min + max) / 2);
|
|
||||||
if (ucs > COMBINING[mid][1])
|
|
||||||
min = mid + 1;
|
|
||||||
else if (ucs < COMBINING[mid][0])
|
|
||||||
max = mid - 1;
|
|
||||||
else
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return false;
|
function wcwidthBMP(ucs) {
|
||||||
}
|
// test for 8-bit control characters
|
||||||
function wcwidth(ucs) {
|
if (ucs === 0)
|
||||||
// test for 8-bit control characters
|
return opts.nul;
|
||||||
if (ucs === 0)
|
if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0))
|
||||||
return opts.nul;
|
return opts.control;
|
||||||
if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0))
|
// binary search in table of non-spacing characters
|
||||||
return opts.control;
|
if (bisearch(ucs, COMBINING_BMP))
|
||||||
// binary search in table of non-spacing characters
|
return 0;
|
||||||
if (bisearch(ucs))
|
// if we arrive here, ucs is not a combining or C0/C1 control character
|
||||||
return 0;
|
if (isWideBMP(ucs)) {
|
||||||
// if we arrive here, ucs is not a combining or C0/C1 control character
|
return 2;
|
||||||
if (isWide(ucs)) {
|
}
|
||||||
return 2;
|
return 1;
|
||||||
}
|
}
|
||||||
return 1;
|
function isWideBMP(ucs) {
|
||||||
}
|
return (
|
||||||
function isWide(ucs) {
|
ucs >= 0x1100 && (
|
||||||
return (
|
|
||||||
ucs >= 0x1100 && (
|
|
||||||
ucs <= 0x115f || // Hangul Jamo init. consonants
|
ucs <= 0x115f || // Hangul Jamo init. consonants
|
||||||
ucs === 0x2329 ||
|
ucs === 0x2329 ||
|
||||||
ucs === 0x232a ||
|
ucs === 0x232a ||
|
||||||
@@ -1581,9 +1569,59 @@ const wcwidth = (function(opts) {
|
|||||||
(ucs >= 0xfe10 && ucs <= 0xfe19) || // Vertical forms
|
(ucs >= 0xfe10 && ucs <= 0xfe19) || // Vertical forms
|
||||||
(ucs >= 0xfe30 && ucs <= 0xfe6f) || // CJK Compat Forms
|
(ucs >= 0xfe30 && ucs <= 0xfe6f) || // CJK Compat Forms
|
||||||
(ucs >= 0xff00 && ucs <= 0xff60) || // Fullwidth Forms
|
(ucs >= 0xff00 && ucs <= 0xff60) || // Fullwidth Forms
|
||||||
(ucs >= 0xffe0 && ucs <= 0xffe6) ||
|
(ucs >= 0xffe0 && ucs <= 0xffe6)));
|
||||||
(ucs >= 0x20000 && ucs <= 0x2fffd) ||
|
}
|
||||||
(ucs >= 0x30000 && ucs <= 0x3fffd)));
|
function wcwidthHigh(ucs) {
|
||||||
}
|
if (bisearch(ucs, COMBINING_HIGH))
|
||||||
return wcwidth;
|
return 0;
|
||||||
|
if ((ucs >= 0x20000 && ucs <= 0x2fffd) || (ucs >= 0x30000 && ucs <= 0x3fffd)) {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
const control = opts.control | 0;
|
||||||
|
let table = null;
|
||||||
|
function init_table() {
|
||||||
|
// lookup table for BMP
|
||||||
|
const CODEPOINTS = 65536; // BMP holds 65536 codepoints
|
||||||
|
const BITWIDTH = 2; // a codepoint can have a width of 0, 1 or 2
|
||||||
|
const ITEMSIZE = 32; // using uint32_t
|
||||||
|
const CONTAINERSIZE = CODEPOINTS * BITWIDTH / ITEMSIZE;
|
||||||
|
const CODEPOINTS_PER_ITEM = ITEMSIZE / BITWIDTH;
|
||||||
|
table = (typeof Uint32Array === 'undefined')
|
||||||
|
? new Array(CONTAINERSIZE)
|
||||||
|
: new Uint32Array(CONTAINERSIZE);
|
||||||
|
for (let i = 0; i < CONTAINERSIZE; ++i) {
|
||||||
|
let num = 0;
|
||||||
|
let pos = CODEPOINTS_PER_ITEM;
|
||||||
|
while (pos--)
|
||||||
|
num = (num << 2) | wcwidthBMP(CODEPOINTS_PER_ITEM * i + pos);
|
||||||
|
table[i] = num;
|
||||||
|
}
|
||||||
|
return table;
|
||||||
|
}
|
||||||
|
// get width from lookup table
|
||||||
|
// position in container : num / CODEPOINTS_PER_ITEM
|
||||||
|
// ==> n = table[Math.floor(num / 16)]
|
||||||
|
// ==> n = table[num >> 4]
|
||||||
|
// 16 codepoints per number: FFEEDDCCBBAA99887766554433221100
|
||||||
|
// position in number : (num % CODEPOINTS_PER_ITEM) * BITWIDTH
|
||||||
|
// ==> m = (n % 16) * 2
|
||||||
|
// ==> m = (num & 15) << 1
|
||||||
|
// right shift to position m
|
||||||
|
// ==> n = n >> m e.g. m=12 000000000000FFEEDDCCBBAA99887766
|
||||||
|
// we are only interested in 2 LSBs, cut off higher bits
|
||||||
|
// ==> n = n & 3 e.g. 000000000000000000000000000000XX
|
||||||
|
return function (num) {
|
||||||
|
num = num | 0; // get asm.js like optimization under V8
|
||||||
|
if (num < 32)
|
||||||
|
return control | 0;
|
||||||
|
if (num < 127)
|
||||||
|
return 1;
|
||||||
|
let t = table || init_table();
|
||||||
|
if (num < 65536)
|
||||||
|
return t[num >> 4] >> ((num & 15) << 1) & 3;
|
||||||
|
// do a full search for high codepoints
|
||||||
|
return wcwidthHigh(num);
|
||||||
|
};
|
||||||
})({nul: 0, control: 0}); // configurable options
|
})({nul: 0, control: 0}); // configurable options
|
||||||
|
|||||||
+36
-7
@@ -21,11 +21,9 @@ export interface ITerminal {
|
|||||||
element: HTMLElement;
|
element: HTMLElement;
|
||||||
rowContainer: HTMLElement;
|
rowContainer: HTMLElement;
|
||||||
selectionContainer: HTMLElement;
|
selectionContainer: HTMLElement;
|
||||||
|
selectionManager: ISelectionManager;
|
||||||
charMeasure: ICharMeasure;
|
charMeasure: ICharMeasure;
|
||||||
textarea: HTMLTextAreaElement;
|
textarea: HTMLTextAreaElement;
|
||||||
ybase: number;
|
|
||||||
ydisp: number;
|
|
||||||
lines: ICircularList<string>;
|
|
||||||
rows: number;
|
rows: number;
|
||||||
cols: number;
|
cols: number;
|
||||||
browser: IBrowser;
|
browser: IBrowser;
|
||||||
@@ -33,9 +31,10 @@ export interface ITerminal {
|
|||||||
children: HTMLElement[];
|
children: HTMLElement[];
|
||||||
cursorHidden: boolean;
|
cursorHidden: boolean;
|
||||||
cursorState: number;
|
cursorState: number;
|
||||||
x: number;
|
|
||||||
y: number;
|
|
||||||
defAttr: number;
|
defAttr: number;
|
||||||
|
scrollback: number;
|
||||||
|
buffers: IBufferSet;
|
||||||
|
buffer: IBuffer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Emit the 'data' event and populate the given data.
|
* Emit the 'data' event and populate the given data.
|
||||||
@@ -47,10 +46,35 @@ export interface ITerminal {
|
|||||||
cancel(ev: Event, force?: boolean);
|
cancel(ev: Event, force?: boolean);
|
||||||
log(text: string): void;
|
log(text: string): void;
|
||||||
emit(event: string, data: any);
|
emit(event: string, data: any);
|
||||||
|
reset(): void;
|
||||||
|
showCursor(): void;
|
||||||
|
blankLine(cur?: boolean, isWrapped?: boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IBuffer {
|
||||||
|
lines: ICircularList<[number, string, number][]>;
|
||||||
|
ydisp: number;
|
||||||
|
ybase: number;
|
||||||
|
y: number;
|
||||||
|
x: number;
|
||||||
|
tabs: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IBufferSet {
|
||||||
|
alt: IBuffer;
|
||||||
|
normal: IBuffer;
|
||||||
|
active: IBuffer;
|
||||||
|
|
||||||
|
activateNormalBuffer(): void;
|
||||||
|
activateAltBuffer(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ISelectionManager {
|
export interface ISelectionManager {
|
||||||
selectionText: string;
|
selectionText: string;
|
||||||
|
selectionStart: [number, number];
|
||||||
|
selectionEnd: [number, number];
|
||||||
|
|
||||||
|
setSelection(row: number, col: number, length: number);
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ICharMeasure {
|
export interface ICharMeasure {
|
||||||
@@ -66,11 +90,11 @@ export interface ILinkifier {
|
|||||||
deregisterLinkMatcher(matcherId: number): boolean;
|
deregisterLinkMatcher(matcherId: number): boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ICircularList<T> {
|
export interface ICircularList<T> extends IEventEmitter {
|
||||||
length: number;
|
length: number;
|
||||||
maxLength: number;
|
maxLength: number;
|
||||||
|
forEach: (callbackfn: (value: T, index: number) => void) => void;
|
||||||
|
|
||||||
forEach(callbackfn: (value: T, index: number, array: T[]) => void): void;
|
|
||||||
get(index: number): T;
|
get(index: number): T;
|
||||||
set(index: number, value: T): void;
|
set(index: number, value: T): void;
|
||||||
push(value: T): void;
|
push(value: T): void;
|
||||||
@@ -80,6 +104,11 @@ interface ICircularList<T> {
|
|||||||
shiftElements(start: number, count: number, offset: number): void;
|
shiftElements(start: number, count: number, offset: number): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IEventEmitter {
|
||||||
|
on(type, listener): void;
|
||||||
|
off(type, listener): void;
|
||||||
|
}
|
||||||
|
|
||||||
export interface LinkMatcherOptions {
|
export interface LinkMatcherOptions {
|
||||||
/**
|
/**
|
||||||
* The index of the link from the regex.match(text) call. This defaults to 0
|
* The index of the link from the regex.match(text) call. This defaults to 0
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ class TestLinkifier extends Linkifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe('Linkifier', () => {
|
describe('Linkifier', () => {
|
||||||
|
let dom: jsdom.JSDOM;
|
||||||
let window: Window;
|
let window: Window;
|
||||||
let document: Document;
|
let document: Document;
|
||||||
|
|
||||||
@@ -24,13 +25,11 @@ describe('Linkifier', () => {
|
|||||||
let rows: HTMLElement[];
|
let rows: HTMLElement[];
|
||||||
let linkifier: TestLinkifier;
|
let linkifier: TestLinkifier;
|
||||||
|
|
||||||
beforeEach(done => {
|
beforeEach(() => {
|
||||||
jsdom.env('', (err, w) => {
|
dom = new jsdom.JSDOM('');
|
||||||
window = w;
|
window = dom.window;
|
||||||
document = window.document;
|
document = window.document;
|
||||||
linkifier = new TestLinkifier();
|
linkifier = new TestLinkifier();
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function addRow(html: string) {
|
function addRow(html: string) {
|
||||||
|
|||||||
+10
-3
@@ -52,7 +52,7 @@ escapedStateHandler['c'] = (parser, terminal) => {
|
|||||||
};
|
};
|
||||||
escapedStateHandler['E'] = (parser, terminal) => {
|
escapedStateHandler['E'] = (parser, terminal) => {
|
||||||
// ESC E Next Line ( NEL is 0x85).
|
// ESC E Next Line ( NEL is 0x85).
|
||||||
terminal.x = 0;
|
terminal.buffer.x = 0;
|
||||||
terminal.index();
|
terminal.index();
|
||||||
parser.setState(ParserState.NORMAL);
|
parser.setState(ParserState.NORMAL);
|
||||||
};
|
};
|
||||||
@@ -184,6 +184,10 @@ export class Parser {
|
|||||||
public parse(data: string): ParserState {
|
public parse(data: string): ParserState {
|
||||||
let l = data.length, j, cs, ch, code, low;
|
let l = data.length, j, cs, ch, code, low;
|
||||||
|
|
||||||
|
if (this._terminal.debug) {
|
||||||
|
this._terminal.log('data: ' + data);
|
||||||
|
}
|
||||||
|
|
||||||
this._position = 0;
|
this._position = 0;
|
||||||
// apply leftover surrogate high from last write
|
// apply leftover surrogate high from last write
|
||||||
if (this._terminal.surrogate_high) {
|
if (this._terminal.surrogate_high) {
|
||||||
@@ -458,6 +462,9 @@ export class Parser {
|
|||||||
|
|
||||||
case ParserState.CSI:
|
case ParserState.CSI:
|
||||||
if (ch in csiStateHandler) {
|
if (ch in csiStateHandler) {
|
||||||
|
if (this._terminal.debug) {
|
||||||
|
this._terminal.log(`CSI ${this._terminal.prefix ? this._terminal.prefix : ''} ${this._terminal.params ? this._terminal.params.join(';') : ''} ${this._terminal.postfix ? this._terminal.postfix : ''} ${ch}`);
|
||||||
|
}
|
||||||
csiStateHandler[ch](this._inputHandler, this._terminal.params, this._terminal.prefix, this._terminal.postfix, this);
|
csiStateHandler[ch](this._inputHandler, this._terminal.params, this._terminal.prefix, this._terminal.postfix, this);
|
||||||
} else {
|
} else {
|
||||||
this._terminal.error('Unknown CSI code: %s.', ch);
|
this._terminal.error('Unknown CSI code: %s.', ch);
|
||||||
@@ -499,9 +506,9 @@ export class Parser {
|
|||||||
// DECSTBM
|
// DECSTBM
|
||||||
case 'r':
|
case 'r':
|
||||||
pt = ''
|
pt = ''
|
||||||
+ (this._terminal.scrollTop + 1)
|
+ (this._terminal.buffer.scrollTop + 1)
|
||||||
+ ';'
|
+ ';'
|
||||||
+ (this._terminal.scrollBottom + 1)
|
+ (this._terminal.buffer.scrollBottom + 1)
|
||||||
+ 'r';
|
+ 'r';
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|||||||
+75
-73
@@ -139,15 +139,15 @@ export class Renderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (; y <= end; y++) {
|
for (; y <= end; y++) {
|
||||||
let row = y + this._terminal.ydisp;
|
let row = y + this._terminal.buffer.ydisp;
|
||||||
|
|
||||||
let line = this._terminal.lines.get(row);
|
let line = this._terminal.buffer.lines.get(row);
|
||||||
|
|
||||||
let x;
|
let x;
|
||||||
if (this._terminal.y === y - (this._terminal.ybase - this._terminal.ydisp) &&
|
if (this._terminal.buffer.y === y - (this._terminal.buffer.ybase - this._terminal.buffer.ydisp) &&
|
||||||
this._terminal.cursorState &&
|
this._terminal.cursorState &&
|
||||||
!this._terminal.cursorHidden) {
|
!this._terminal.cursorHidden) {
|
||||||
x = this._terminal.x;
|
x = this._terminal.buffer.x;
|
||||||
} else {
|
} else {
|
||||||
x = -1;
|
x = -1;
|
||||||
}
|
}
|
||||||
@@ -170,16 +170,13 @@ export class Renderer {
|
|||||||
let data: any = line[i][0];
|
let data: any = line[i][0];
|
||||||
const ch = line[i][1];
|
const ch = line[i][1];
|
||||||
const ch_width: any = line[i][2];
|
const ch_width: any = line[i][2];
|
||||||
|
const isCursor: boolean = i === x;
|
||||||
if (!ch_width) {
|
if (!ch_width) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (i === x) {
|
if (data !== attr || isCursor) {
|
||||||
data = -1;
|
if (attr !== this._terminal.defAttr && !isCursor) {
|
||||||
}
|
|
||||||
|
|
||||||
if (data !== attr) {
|
|
||||||
if (attr !== this._terminal.defAttr) {
|
|
||||||
if (innerHTML) {
|
if (innerHTML) {
|
||||||
currentElement.innerHTML = innerHTML;
|
currentElement.innerHTML = innerHTML;
|
||||||
innerHTML = '';
|
innerHTML = '';
|
||||||
@@ -187,7 +184,7 @@ export class Renderer {
|
|||||||
documentFragment.appendChild(currentElement);
|
documentFragment.appendChild(currentElement);
|
||||||
currentElement = null;
|
currentElement = null;
|
||||||
}
|
}
|
||||||
if (data !== this._terminal.defAttr) {
|
if (data !== this._terminal.defAttr || isCursor) {
|
||||||
if (innerHTML && !currentElement) {
|
if (innerHTML && !currentElement) {
|
||||||
currentElement = this._spanElementObjectPool.acquire();
|
currentElement = this._spanElementObjectPool.acquire();
|
||||||
}
|
}
|
||||||
@@ -199,71 +196,73 @@ export class Renderer {
|
|||||||
documentFragment.appendChild(currentElement);
|
documentFragment.appendChild(currentElement);
|
||||||
}
|
}
|
||||||
currentElement = this._spanElementObjectPool.acquire();
|
currentElement = this._spanElementObjectPool.acquire();
|
||||||
if (data === -1) {
|
|
||||||
|
let bg = data & 0x1ff;
|
||||||
|
let fg = (data >> 9) & 0x1ff;
|
||||||
|
let flags = data >> 18;
|
||||||
|
|
||||||
|
if (isCursor) {
|
||||||
currentElement.classList.add('reverse-video');
|
currentElement.classList.add('reverse-video');
|
||||||
currentElement.classList.add('terminal-cursor');
|
currentElement.classList.add('terminal-cursor');
|
||||||
} else {
|
}
|
||||||
let bg = data & 0x1ff;
|
|
||||||
let fg = (data >> 9) & 0x1ff;
|
|
||||||
let flags = data >> 18;
|
|
||||||
|
|
||||||
if (flags & FLAGS.BOLD) {
|
if (flags & FLAGS.BOLD) {
|
||||||
if (!brokenBold) {
|
if (!brokenBold) {
|
||||||
currentElement.classList.add('xterm-bold');
|
currentElement.classList.add('xterm-bold');
|
||||||
}
|
|
||||||
// See: XTerm*boldColors
|
|
||||||
if (fg < 8) {
|
|
||||||
fg += 8;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
// See: XTerm*boldColors
|
||||||
if (flags & FLAGS.UNDERLINE) {
|
if (fg < 8) {
|
||||||
currentElement.classList.add('xterm-underline');
|
fg += 8;
|
||||||
}
|
|
||||||
|
|
||||||
if (flags & FLAGS.BLINK) {
|
|
||||||
currentElement.classList.add('xterm-blink');
|
|
||||||
}
|
|
||||||
|
|
||||||
// If inverse flag is on, then swap the foreground and background variables.
|
|
||||||
if (flags & FLAGS.INVERSE) {
|
|
||||||
let temp = bg;
|
|
||||||
bg = fg;
|
|
||||||
fg = temp;
|
|
||||||
// Should inverse just be before the above boldColors effect instead?
|
|
||||||
if ((flags & 1) && fg < 8) {
|
|
||||||
fg += 8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (flags & FLAGS.INVISIBLE) {
|
|
||||||
currentElement.classList.add('xterm-hidden');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Weird situation: Invert flag used black foreground and white background results
|
|
||||||
* in invalid background color, positioned at the 256 index of the 256 terminal
|
|
||||||
* color map. Pin the colors manually in such a case.
|
|
||||||
*
|
|
||||||
* Source: https://github.com/sourcelair/xterm.js/issues/57
|
|
||||||
*/
|
|
||||||
if (flags & FLAGS.INVERSE) {
|
|
||||||
if (bg === 257) {
|
|
||||||
bg = 15;
|
|
||||||
}
|
|
||||||
if (fg === 256) {
|
|
||||||
fg = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bg < 256) {
|
|
||||||
currentElement.classList.add(`xterm-bg-color-${bg}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fg < 256) {
|
|
||||||
currentElement.classList.add(`xterm-color-${fg}`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (flags & FLAGS.UNDERLINE) {
|
||||||
|
currentElement.classList.add('xterm-underline');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flags & FLAGS.BLINK) {
|
||||||
|
currentElement.classList.add('xterm-blink');
|
||||||
|
}
|
||||||
|
|
||||||
|
// If inverse flag is on, then swap the foreground and background variables.
|
||||||
|
if (flags & FLAGS.INVERSE) {
|
||||||
|
let temp = bg;
|
||||||
|
bg = fg;
|
||||||
|
fg = temp;
|
||||||
|
// Should inverse just be before the above boldColors effect instead?
|
||||||
|
if ((flags & 1) && fg < 8) {
|
||||||
|
fg += 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flags & FLAGS.INVISIBLE && !isCursor) {
|
||||||
|
currentElement.classList.add('xterm-hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Weird situation: Invert flag used black foreground and white background results
|
||||||
|
* in invalid background color, positioned at the 256 index of the 256 terminal
|
||||||
|
* color map. Pin the colors manually in such a case.
|
||||||
|
*
|
||||||
|
* Source: https://github.com/sourcelair/xterm.js/issues/57
|
||||||
|
*/
|
||||||
|
if (flags & FLAGS.INVERSE) {
|
||||||
|
if (bg === 257) {
|
||||||
|
bg = 15;
|
||||||
|
}
|
||||||
|
if (fg === 256) {
|
||||||
|
fg = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bg < 256) {
|
||||||
|
currentElement.classList.add(`xterm-bg-color-${bg}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fg < 256) {
|
||||||
|
currentElement.classList.add(`xterm-color-${fg}`);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -295,7 +294,10 @@ export class Renderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
attr = data;
|
// The cursor needs its own element, therefore we set attr to -1
|
||||||
|
// which will cause the next character to be rendered in a new element
|
||||||
|
attr = isCursor ? -1 : data;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (innerHTML && !currentElement) {
|
if (innerHTML && !currentElement) {
|
||||||
@@ -337,8 +339,8 @@ export class Renderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Translate from buffer position to viewport position
|
// Translate from buffer position to viewport position
|
||||||
const viewportStartRow = start[1] - this._terminal.ydisp;
|
const viewportStartRow = start[1] - this._terminal.buffer.ydisp;
|
||||||
const viewportEndRow = end[1] - this._terminal.ydisp;
|
const viewportEndRow = end[1] - this._terminal.buffer.ydisp;
|
||||||
const viewportCappedStartRow = Math.max(viewportStartRow, 0);
|
const viewportCappedStartRow = Math.max(viewportStartRow, 0);
|
||||||
const viewportCappedEndRow = Math.min(viewportEndRow, this._terminal.rows - 1);
|
const viewportCappedEndRow = Math.min(viewportEndRow, this._terminal.rows - 1);
|
||||||
|
|
||||||
|
|||||||
@@ -3,16 +3,18 @@
|
|||||||
*/
|
*/
|
||||||
import jsdom = require('jsdom');
|
import jsdom = require('jsdom');
|
||||||
import { assert } from 'chai';
|
import { assert } from 'chai';
|
||||||
import { ITerminal } from './Interfaces';
|
import { ITerminal, ICircularList } from './Interfaces';
|
||||||
import { CharMeasure } from './utils/CharMeasure';
|
import { CharMeasure } from './utils/CharMeasure';
|
||||||
import { CircularList } from './utils/CircularList';
|
import { CircularList } from './utils/CircularList';
|
||||||
import { SelectionManager } from './SelectionManager';
|
import { SelectionManager } from './SelectionManager';
|
||||||
import { SelectionModel } from './SelectionModel';
|
import { SelectionModel } from './SelectionModel';
|
||||||
|
import { BufferSet } from './BufferSet';
|
||||||
|
import { MockTerminal } from './utils/TestUtils';
|
||||||
|
|
||||||
class TestSelectionManager extends SelectionManager {
|
class TestSelectionManager extends SelectionManager {
|
||||||
constructor(
|
constructor(
|
||||||
terminal: ITerminal,
|
terminal: ITerminal,
|
||||||
buffer: CircularList<any>,
|
buffer: ICircularList<[number, string, number][]>,
|
||||||
rowContainer: HTMLElement,
|
rowContainer: HTMLElement,
|
||||||
charMeasure: CharMeasure
|
charMeasure: CharMeasure
|
||||||
) {
|
) {
|
||||||
@@ -31,23 +33,28 @@ class TestSelectionManager extends SelectionManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe('SelectionManager', () => {
|
describe('SelectionManager', () => {
|
||||||
|
let dom: jsdom.JSDOM;
|
||||||
let window: Window;
|
let window: Window;
|
||||||
let document: Document;
|
let document: Document;
|
||||||
|
|
||||||
let terminal: ITerminal;
|
let terminal: ITerminal;
|
||||||
let buffer: CircularList<any>;
|
let bufferLines: ICircularList<[number, string, number][]>;
|
||||||
let rowContainer: HTMLElement;
|
let rowContainer: HTMLElement;
|
||||||
let selectionManager: TestSelectionManager;
|
let selectionManager: TestSelectionManager;
|
||||||
|
|
||||||
beforeEach(done => {
|
beforeEach(() => {
|
||||||
jsdom.env('', (err, w) => {
|
dom = new jsdom.JSDOM('');
|
||||||
window = w;
|
window = dom.window;
|
||||||
document = window.document;
|
document = window.document;
|
||||||
buffer = new CircularList<any>(100);
|
rowContainer = document.createElement('div');
|
||||||
terminal = <any>{ cols: 80, rows: 2 };
|
terminal = new MockTerminal();
|
||||||
selectionManager = new TestSelectionManager(terminal, buffer, rowContainer, null);
|
terminal.cols = 80;
|
||||||
done();
|
terminal.rows = 2;
|
||||||
});
|
terminal.scrollback = 100;
|
||||||
|
terminal.buffers = new BufferSet(terminal);
|
||||||
|
terminal.buffer = terminal.buffers.active;
|
||||||
|
bufferLines = terminal.buffer.lines;
|
||||||
|
selectionManager = new TestSelectionManager(terminal, bufferLines, rowContainer, null);
|
||||||
});
|
});
|
||||||
|
|
||||||
function stringToRow(text: string): [number, string, number][] {
|
function stringToRow(text: string): [number, string, number][] {
|
||||||
@@ -60,7 +67,7 @@ describe('SelectionManager', () => {
|
|||||||
|
|
||||||
describe('_selectWordAt', () => {
|
describe('_selectWordAt', () => {
|
||||||
it('should expand selection for normal width chars', () => {
|
it('should expand selection for normal width chars', () => {
|
||||||
buffer.push(stringToRow('foo bar'));
|
bufferLines.set(0, stringToRow('foo bar'));
|
||||||
selectionManager.selectWordAt([0, 0]);
|
selectionManager.selectWordAt([0, 0]);
|
||||||
assert.equal(selectionManager.selectionText, 'foo');
|
assert.equal(selectionManager.selectionText, 'foo');
|
||||||
selectionManager.selectWordAt([1, 0]);
|
selectionManager.selectWordAt([1, 0]);
|
||||||
@@ -77,7 +84,7 @@ describe('SelectionManager', () => {
|
|||||||
assert.equal(selectionManager.selectionText, 'bar');
|
assert.equal(selectionManager.selectionText, 'bar');
|
||||||
});
|
});
|
||||||
it('should expand selection for whitespace', () => {
|
it('should expand selection for whitespace', () => {
|
||||||
buffer.push(stringToRow('a b'));
|
bufferLines.set(0, stringToRow('a b'));
|
||||||
selectionManager.selectWordAt([0, 0]);
|
selectionManager.selectWordAt([0, 0]);
|
||||||
assert.equal(selectionManager.selectionText, 'a');
|
assert.equal(selectionManager.selectionText, 'a');
|
||||||
selectionManager.selectWordAt([1, 0]);
|
selectionManager.selectWordAt([1, 0]);
|
||||||
@@ -91,7 +98,7 @@ describe('SelectionManager', () => {
|
|||||||
});
|
});
|
||||||
it('should expand selection for wide characters', () => {
|
it('should expand selection for wide characters', () => {
|
||||||
// Wide characters use a special format
|
// Wide characters use a special format
|
||||||
buffer.push([
|
bufferLines.set(0, [
|
||||||
[null, '中', 2],
|
[null, '中', 2],
|
||||||
[null, '', 0],
|
[null, '', 0],
|
||||||
[null, '文', 2],
|
[null, '文', 2],
|
||||||
@@ -143,7 +150,7 @@ describe('SelectionManager', () => {
|
|||||||
assert.equal(selectionManager.selectionText, 'foo');
|
assert.equal(selectionManager.selectionText, 'foo');
|
||||||
});
|
});
|
||||||
it('should select up to non-path characters that are commonly adjacent to paths', () => {
|
it('should select up to non-path characters that are commonly adjacent to paths', () => {
|
||||||
buffer.push(stringToRow('(cd)[ef]{gh}\'ij"'));
|
bufferLines.set(0, stringToRow('(cd)[ef]{gh}\'ij"'));
|
||||||
selectionManager.selectWordAt([0, 0]);
|
selectionManager.selectWordAt([0, 0]);
|
||||||
assert.equal(selectionManager.selectionText, '(cd');
|
assert.equal(selectionManager.selectionText, '(cd');
|
||||||
selectionManager.selectWordAt([1, 0]);
|
selectionManager.selectWordAt([1, 0]);
|
||||||
@@ -181,7 +188,7 @@ describe('SelectionManager', () => {
|
|||||||
|
|
||||||
describe('_selectLineAt', () => {
|
describe('_selectLineAt', () => {
|
||||||
it('should select the entire line', () => {
|
it('should select the entire line', () => {
|
||||||
buffer.push(stringToRow('foo bar'));
|
bufferLines.set(0, stringToRow('foo bar'));
|
||||||
selectionManager.selectLineAt(0);
|
selectionManager.selectLineAt(0);
|
||||||
assert.equal(selectionManager.selectionText, 'foo bar', 'The selected text is correct');
|
assert.equal(selectionManager.selectionText, 'foo bar', 'The selected text is correct');
|
||||||
assert.deepEqual(selectionManager.model.finalSelectionStart, [0, 0]);
|
assert.deepEqual(selectionManager.model.finalSelectionStart, [0, 0]);
|
||||||
@@ -191,13 +198,14 @@ describe('SelectionManager', () => {
|
|||||||
|
|
||||||
describe('selectAll', () => {
|
describe('selectAll', () => {
|
||||||
it('should select the entire buffer, beyond the viewport', () => {
|
it('should select the entire buffer, beyond the viewport', () => {
|
||||||
buffer.push(stringToRow('1'));
|
bufferLines.length = 5;
|
||||||
buffer.push(stringToRow('2'));
|
bufferLines.set(0, stringToRow('1'));
|
||||||
buffer.push(stringToRow('3'));
|
bufferLines.set(1, stringToRow('2'));
|
||||||
buffer.push(stringToRow('4'));
|
bufferLines.set(2, stringToRow('3'));
|
||||||
buffer.push(stringToRow('5'));
|
bufferLines.set(3, stringToRow('4'));
|
||||||
|
bufferLines.set(4, stringToRow('5'));
|
||||||
selectionManager.selectAll();
|
selectionManager.selectAll();
|
||||||
terminal.ybase = buffer.length - terminal.rows;
|
terminal.buffer.ybase = bufferLines.length - terminal.rows;
|
||||||
assert.equal(selectionManager.selectionText, '1\n2\n3\n4\n5');
|
assert.equal(selectionManager.selectionText, '1\n2\n3\n4\n5');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+108
-155
@@ -7,8 +7,9 @@ import * as Browser from './utils/Browser';
|
|||||||
import { CharMeasure } from './utils/CharMeasure';
|
import { CharMeasure } from './utils/CharMeasure';
|
||||||
import { CircularList } from './utils/CircularList';
|
import { CircularList } from './utils/CircularList';
|
||||||
import { EventEmitter } from './EventEmitter';
|
import { EventEmitter } from './EventEmitter';
|
||||||
import { ITerminal } from './Interfaces';
|
import { ITerminal, ICircularList } from './Interfaces';
|
||||||
import { SelectionModel } from './SelectionModel';
|
import { SelectionModel } from './SelectionModel';
|
||||||
|
import { translateBufferLineToString } from './utils/BufferLine';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The number of pixels the mouse needs to be above or below the viewport in
|
* The number of pixels the mouse needs to be above or below the viewport in
|
||||||
@@ -26,18 +27,6 @@ const DRAG_SCROLL_MAX_SPEED = 15;
|
|||||||
*/
|
*/
|
||||||
const DRAG_SCROLL_INTERVAL = 50;
|
const DRAG_SCROLL_INTERVAL = 50;
|
||||||
|
|
||||||
/**
|
|
||||||
* The amount of time before mousedown events are no longer stacked to create
|
|
||||||
* double/triple click events.
|
|
||||||
*/
|
|
||||||
const CLEAR_MOUSE_DOWN_TIME = 400;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The number of pixels in each direction that the mouse must move before
|
|
||||||
* mousedown events are no longer stacked to create double/triple click events.
|
|
||||||
*/
|
|
||||||
const CLEAR_MOUSE_DISTANCE = 10;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A string containing all characters that are considered word separated by the
|
* A string containing all characters that are considered word separated by the
|
||||||
* double click to select work logic.
|
* double click to select work logic.
|
||||||
@@ -86,23 +75,6 @@ export class SelectionManager extends EventEmitter {
|
|||||||
*/
|
*/
|
||||||
private _dragScrollAmount: number;
|
private _dragScrollAmount: number;
|
||||||
|
|
||||||
/**
|
|
||||||
* The last time the mousedown event fired, this is used to track double and
|
|
||||||
* triple clicks.
|
|
||||||
*/
|
|
||||||
private _lastMouseDownTime: number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The last position the mouse was clicked [x, y].
|
|
||||||
*/
|
|
||||||
private _lastMousePosition: [number, number];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The number of clicks of the mousedown event. This is used to keep track of
|
|
||||||
* double and triple clicks.
|
|
||||||
*/
|
|
||||||
private _clickCount: number;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current selection mode.
|
* The current selection mode.
|
||||||
*/
|
*/
|
||||||
@@ -119,14 +91,17 @@ export class SelectionManager extends EventEmitter {
|
|||||||
*/
|
*/
|
||||||
private _refreshAnimationFrame: number;
|
private _refreshAnimationFrame: number;
|
||||||
|
|
||||||
private _bufferTrimListener: any;
|
/**
|
||||||
|
* Whether selection is enabled.
|
||||||
|
*/
|
||||||
|
private _enabled = true;
|
||||||
|
|
||||||
private _mouseMoveListener: EventListener;
|
private _mouseMoveListener: EventListener;
|
||||||
private _mouseDownListener: EventListener;
|
|
||||||
private _mouseUpListener: EventListener;
|
private _mouseUpListener: EventListener;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private _terminal: ITerminal,
|
private _terminal: ITerminal,
|
||||||
private _buffer: CircularList<any>,
|
private _buffer: ICircularList<[number, string, number][]>,
|
||||||
private _rowContainer: HTMLElement,
|
private _rowContainer: HTMLElement,
|
||||||
private _charMeasure: CharMeasure
|
private _charMeasure: CharMeasure
|
||||||
) {
|
) {
|
||||||
@@ -135,7 +110,6 @@ export class SelectionManager extends EventEmitter {
|
|||||||
this.enable();
|
this.enable();
|
||||||
|
|
||||||
this._model = new SelectionModel(_terminal);
|
this._model = new SelectionModel(_terminal);
|
||||||
this._lastMouseDownTime = 0;
|
|
||||||
this._activeSelectionMode = SelectionMode.NORMAL;
|
this._activeSelectionMode = SelectionMode.NORMAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,10 +117,16 @@ export class SelectionManager extends EventEmitter {
|
|||||||
* Initializes listener variables.
|
* Initializes listener variables.
|
||||||
*/
|
*/
|
||||||
private _initListeners() {
|
private _initListeners() {
|
||||||
this._bufferTrimListener = (amount: number) => this._onTrim(amount);
|
|
||||||
this._mouseMoveListener = event => this._onMouseMove(<MouseEvent>event);
|
this._mouseMoveListener = event => this._onMouseMove(<MouseEvent>event);
|
||||||
this._mouseDownListener = event => this._onMouseDown(<MouseEvent>event);
|
|
||||||
this._mouseUpListener = event => this._onMouseUp(<MouseEvent>event);
|
this._mouseUpListener = event => this._onMouseUp(<MouseEvent>event);
|
||||||
|
|
||||||
|
this._rowContainer.addEventListener('mousedown', event => this._onMouseDown(<MouseEvent>event));
|
||||||
|
|
||||||
|
// Only adjust the selection on trim, shiftElements is rarely used (only in
|
||||||
|
// reverseIndex) and delete in a splice is only ever used when the same
|
||||||
|
// number of elements was just added. Given this is could actually be
|
||||||
|
// beneficial to leave the selection as is for these cases.
|
||||||
|
this._buffer.on('trim', (amount: number) => this._onTrim(amount));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -155,20 +135,14 @@ export class SelectionManager extends EventEmitter {
|
|||||||
*/
|
*/
|
||||||
public disable() {
|
public disable() {
|
||||||
this.clearSelection();
|
this.clearSelection();
|
||||||
this._buffer.off('trim', this._bufferTrimListener);
|
this._enabled = false;
|
||||||
this._rowContainer.removeEventListener('mousedown', this._mouseDownListener);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enable the selection manager.
|
* Enable the selection manager.
|
||||||
*/
|
*/
|
||||||
public enable() {
|
public enable() {
|
||||||
// Only adjust the selection on trim, shiftElements is rarely used (only in
|
this._enabled = true;
|
||||||
// reverseIndex) and delete in a splice is only ever used when the same
|
|
||||||
// number of elements was just added. Given this is could actually be
|
|
||||||
// beneficial to leave the selection as is for these cases.
|
|
||||||
this._buffer.on('trim', this._bufferTrimListener);
|
|
||||||
this._rowContainer.addEventListener('mousedown', this._mouseDownListener);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -176,11 +150,14 @@ export class SelectionManager extends EventEmitter {
|
|||||||
* switched in or out.
|
* switched in or out.
|
||||||
* @param buffer The active buffer.
|
* @param buffer The active buffer.
|
||||||
*/
|
*/
|
||||||
public setBuffer(buffer: CircularList<any>): void {
|
public setBuffer(buffer: ICircularList<[number, string, number][]>): void {
|
||||||
this._buffer = buffer;
|
this._buffer = buffer;
|
||||||
this.clearSelection();
|
this.clearSelection();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get selectionStart(): [number, number] { return this._model.finalSelectionStart; }
|
||||||
|
public get selectionEnd(): [number, number] { return this._model.finalSelectionEnd; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets whether there is an active text selection.
|
* Gets whether there is an active text selection.
|
||||||
*/
|
*/
|
||||||
@@ -206,13 +183,13 @@ export class SelectionManager extends EventEmitter {
|
|||||||
// Get first row
|
// Get first row
|
||||||
const startRowEndCol = start[1] === end[1] ? end[0] : null;
|
const startRowEndCol = start[1] === end[1] ? end[0] : null;
|
||||||
let result: string[] = [];
|
let result: string[] = [];
|
||||||
result.push(this._translateBufferLineToString(this._buffer.get(start[1]), true, start[0], startRowEndCol));
|
result.push(translateBufferLineToString(this._buffer.get(start[1]), true, start[0], startRowEndCol));
|
||||||
|
|
||||||
// Get middle rows
|
// Get middle rows
|
||||||
for (let i = start[1] + 1; i <= end[1] - 1; i++) {
|
for (let i = start[1] + 1; i <= end[1] - 1; i++) {
|
||||||
const bufferLine = this._buffer.get(i);
|
const bufferLine = this._buffer.get(i);
|
||||||
const lineText = this._translateBufferLineToString(bufferLine, true);
|
const lineText = translateBufferLineToString(bufferLine, true);
|
||||||
if (bufferLine.isWrapped) {
|
if ((<any>bufferLine).isWrapped) {
|
||||||
result[result.length - 1] += lineText;
|
result[result.length - 1] += lineText;
|
||||||
} else {
|
} else {
|
||||||
result.push(lineText);
|
result.push(lineText);
|
||||||
@@ -222,8 +199,8 @@ export class SelectionManager extends EventEmitter {
|
|||||||
// Get final row
|
// Get final row
|
||||||
if (start[1] !== end[1]) {
|
if (start[1] !== end[1]) {
|
||||||
const bufferLine = this._buffer.get(end[1]);
|
const bufferLine = this._buffer.get(end[1]);
|
||||||
const lineText = this._translateBufferLineToString(bufferLine, true, 0, end[0]);
|
const lineText = translateBufferLineToString(bufferLine, true, 0, end[0]);
|
||||||
if (bufferLine.isWrapped) {
|
if ((<any>bufferLine).isWrapped) {
|
||||||
result[result.length - 1] += lineText;
|
result[result.length - 1] += lineText;
|
||||||
} else {
|
} else {
|
||||||
result.push(lineText);
|
result.push(lineText);
|
||||||
@@ -248,55 +225,6 @@ export class SelectionManager extends EventEmitter {
|
|||||||
this.refresh();
|
this.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Translates a buffer line to a string, with optional start and end columns.
|
|
||||||
* Wide characters will count as two columns in the resulting string. This
|
|
||||||
* function is useful for getting the actual text underneath the raw selection
|
|
||||||
* position.
|
|
||||||
* @param line The line being translated.
|
|
||||||
* @param trimRight Whether to trim whitespace to the right.
|
|
||||||
* @param startCol The column to start at.
|
|
||||||
* @param endCol The column to end at.
|
|
||||||
*/
|
|
||||||
private _translateBufferLineToString(line: any, trimRight: boolean, startCol: number = 0, endCol: number = null): string {
|
|
||||||
// TODO: This function should live in a buffer or buffer line class
|
|
||||||
|
|
||||||
// Get full line
|
|
||||||
let lineString = '';
|
|
||||||
let widthAdjustedStartCol = startCol;
|
|
||||||
let widthAdjustedEndCol = endCol;
|
|
||||||
for (let i = 0; i < line.length; i++) {
|
|
||||||
const char = line[i];
|
|
||||||
lineString += char[LINE_DATA_CHAR_INDEX];
|
|
||||||
// Adjust start and end cols for wide characters if they affect their
|
|
||||||
// column indexes
|
|
||||||
if (char[LINE_DATA_WIDTH_INDEX] === 0) {
|
|
||||||
if (startCol >= i) {
|
|
||||||
widthAdjustedStartCol--;
|
|
||||||
}
|
|
||||||
if (endCol >= i) {
|
|
||||||
widthAdjustedEndCol--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate the final end col by trimming whitespace on the right of the
|
|
||||||
// line if needed.
|
|
||||||
let finalEndCol = widthAdjustedEndCol || line.length;
|
|
||||||
if (trimRight) {
|
|
||||||
const rightWhitespaceIndex = lineString.search(/\s+$/);
|
|
||||||
if (rightWhitespaceIndex !== -1) {
|
|
||||||
finalEndCol = Math.min(finalEndCol, rightWhitespaceIndex);
|
|
||||||
}
|
|
||||||
// Return the empty string if only trimmed whitespace is selected
|
|
||||||
if (finalEndCol <= widthAdjustedStartCol) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return lineString.substring(widthAdjustedStartCol, finalEndCol);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Queues a refresh, redrawing the selection on the next opportunity.
|
* Queues a refresh, redrawing the selection on the next opportunity.
|
||||||
* @param isNewSelection Whether the selection should be registered as a new
|
* @param isNewSelection Whether the selection should be registered as a new
|
||||||
@@ -352,11 +280,15 @@ export class SelectionManager extends EventEmitter {
|
|||||||
*/
|
*/
|
||||||
private _getMouseBufferCoords(event: MouseEvent): [number, number] {
|
private _getMouseBufferCoords(event: MouseEvent): [number, number] {
|
||||||
const coords = Mouse.getCoords(event, this._rowContainer, this._charMeasure, this._terminal.cols, this._terminal.rows, true);
|
const coords = Mouse.getCoords(event, this._rowContainer, this._charMeasure, this._terminal.cols, this._terminal.rows, true);
|
||||||
|
if (!coords) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// Convert to 0-based
|
// Convert to 0-based
|
||||||
coords[0]--;
|
coords[0]--;
|
||||||
coords[1]--;
|
coords[1]--;
|
||||||
// Convert viewport coords to buffer coords
|
// Convert viewport coords to buffer coords
|
||||||
coords[1] += this._terminal.ydisp;
|
coords[1] += this._terminal.buffer.ydisp;
|
||||||
return coords;
|
return coords;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -385,28 +317,45 @@ export class SelectionManager extends EventEmitter {
|
|||||||
* @param event The mousedown event.
|
* @param event The mousedown event.
|
||||||
*/
|
*/
|
||||||
private _onMouseDown(event: MouseEvent) {
|
private _onMouseDown(event: MouseEvent) {
|
||||||
|
// If we have selection, we want the context menu on right click even if the
|
||||||
|
// terminal is in mouse mode.
|
||||||
|
if (event.button === 2 && this.hasSelection) {
|
||||||
|
event.stopPropagation();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Only action the primary button
|
// Only action the primary button
|
||||||
if (event.button !== 0) {
|
if (event.button !== 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Allow selection when using a specific modifier key, even when disabled
|
||||||
|
if (!this._enabled) {
|
||||||
|
const shouldForceSelection = Browser.isMac && event.altKey;
|
||||||
|
|
||||||
|
if (!shouldForceSelection) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't send the mouse down event to the current process, we want to select
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
// Tell the browser not to start a regular selection
|
// Tell the browser not to start a regular selection
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
// Reset drag scroll state
|
// Reset drag scroll state
|
||||||
this._dragScrollAmount = 0;
|
this._dragScrollAmount = 0;
|
||||||
|
|
||||||
this._setMouseClickCount(event);
|
if (this._enabled && event.shiftKey) {
|
||||||
|
this._onIncrementalClick(event);
|
||||||
if (event.shiftKey) {
|
|
||||||
this._onShiftClick(event);
|
|
||||||
} else {
|
} else {
|
||||||
if (this._clickCount === 1) {
|
if (event.detail === 1) {
|
||||||
this._onSingleClick(event);
|
this._onSingleClick(event);
|
||||||
} else if (this._clickCount === 2) {
|
} else if (event.detail === 2) {
|
||||||
this._onDoubleClick(event);
|
this._onDoubleClick(event);
|
||||||
} else if (this._clickCount === 3) {
|
} else if (event.detail === 3) {
|
||||||
this._onTripleClick(event);
|
this._onTripleClick(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -435,11 +384,11 @@ export class SelectionManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs a shift click, setting the selection end position to the mouse
|
* Performs an incremental click, setting the selection end position to the mouse
|
||||||
* position.
|
* position.
|
||||||
* @param event The mouse event.
|
* @param event The mouse event.
|
||||||
*/
|
*/
|
||||||
private _onShiftClick(event: MouseEvent): void {
|
private _onIncrementalClick(event: MouseEvent): void {
|
||||||
if (this._model.selectionStart) {
|
if (this._model.selectionStart) {
|
||||||
this._model.selectionEnd = this._getMouseBufferCoords(event);
|
this._model.selectionEnd = this._getMouseBufferCoords(event);
|
||||||
}
|
}
|
||||||
@@ -454,15 +403,25 @@ export class SelectionManager extends EventEmitter {
|
|||||||
this._model.selectionStartLength = 0;
|
this._model.selectionStartLength = 0;
|
||||||
this._model.isSelectAllActive = false;
|
this._model.isSelectAllActive = false;
|
||||||
this._activeSelectionMode = SelectionMode.NORMAL;
|
this._activeSelectionMode = SelectionMode.NORMAL;
|
||||||
|
|
||||||
|
// Initialize the new selection
|
||||||
this._model.selectionStart = this._getMouseBufferCoords(event);
|
this._model.selectionStart = this._getMouseBufferCoords(event);
|
||||||
if (this._model.selectionStart) {
|
if (!this._model.selectionStart) {
|
||||||
this._model.selectionEnd = null;
|
return;
|
||||||
// If the mouse is over the second half of a wide character, adjust the
|
}
|
||||||
// selection to cover the whole character
|
this._model.selectionEnd = null;
|
||||||
const char = this._buffer.get(this._model.selectionStart[1])[this._model.selectionStart[0]];
|
|
||||||
if (char[LINE_DATA_WIDTH_INDEX] === 0) {
|
// Ensure the line exists
|
||||||
this._model.selectionStart[0]++;
|
const line = this._buffer.get(this._model.selectionStart[1]);
|
||||||
}
|
if (!line) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the mouse is over the second half of a wide character, adjust the
|
||||||
|
// selection to cover the whole character
|
||||||
|
const char = line[this._model.selectionStart[0]];
|
||||||
|
if (char[LINE_DATA_WIDTH_INDEX] === 0) {
|
||||||
|
this._model.selectionStart[0]++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -491,32 +450,6 @@ export class SelectionManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the number of clicks for the current mousedown event based on the time
|
|
||||||
* and position of the last mousedown event.
|
|
||||||
* @param event The mouse event.
|
|
||||||
*/
|
|
||||||
private _setMouseClickCount(event: MouseEvent): void {
|
|
||||||
let currentTime = (new Date()).getTime();
|
|
||||||
if (currentTime - this._lastMouseDownTime > CLEAR_MOUSE_DOWN_TIME || this._distanceFromLastMousePosition(event) > CLEAR_MOUSE_DISTANCE) {
|
|
||||||
this._clickCount = 0;
|
|
||||||
}
|
|
||||||
this._lastMouseDownTime = currentTime;
|
|
||||||
this._lastMousePosition = [event.pageX, event.pageY];
|
|
||||||
this._clickCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the maximum number of pixels in each direction the mouse has moved.
|
|
||||||
* @param event The mouse event.
|
|
||||||
*/
|
|
||||||
private _distanceFromLastMousePosition(event: MouseEvent): number {
|
|
||||||
const result = Math.max(
|
|
||||||
Math.abs(this._lastMousePosition[0] - event.pageX),
|
|
||||||
Math.abs(this._lastMousePosition[1] - event.pageY));
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles the mousemove event when the mouse button is down, recording the
|
* Handles the mousemove event when the mouse button is down, recording the
|
||||||
* end of the selection and refreshing the selection.
|
* end of the selection and refreshing the selection.
|
||||||
@@ -529,6 +462,10 @@ export class SelectionManager extends EventEmitter {
|
|||||||
|
|
||||||
// Set the initial selection end based on the mouse coordinates
|
// Set the initial selection end based on the mouse coordinates
|
||||||
this._model.selectionEnd = this._getMouseBufferCoords(event);
|
this._model.selectionEnd = this._getMouseBufferCoords(event);
|
||||||
|
if (!this._model.selectionEnd) {
|
||||||
|
this.refresh(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Select the entire line if line select mode is active.
|
// Select the entire line if line select mode is active.
|
||||||
if (this._activeSelectionMode === SelectionMode.LINE) {
|
if (this._activeSelectionMode === SelectionMode.LINE) {
|
||||||
@@ -564,8 +501,8 @@ export class SelectionManager extends EventEmitter {
|
|||||||
|
|
||||||
// Only draw here if the selection changes.
|
// Only draw here if the selection changes.
|
||||||
if (!previousSelectionEnd ||
|
if (!previousSelectionEnd ||
|
||||||
previousSelectionEnd[0] !== this._model.selectionEnd[0] ||
|
previousSelectionEnd[0] !== this._model.selectionEnd[0] ||
|
||||||
previousSelectionEnd[1] !== this._model.selectionEnd[1]) {
|
previousSelectionEnd[1] !== this._model.selectionEnd[1]) {
|
||||||
this.refresh(true);
|
this.refresh(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -579,9 +516,9 @@ export class SelectionManager extends EventEmitter {
|
|||||||
this._terminal.scrollDisp(this._dragScrollAmount, false);
|
this._terminal.scrollDisp(this._dragScrollAmount, false);
|
||||||
// Re-evaluate selection
|
// Re-evaluate selection
|
||||||
if (this._dragScrollAmount > 0) {
|
if (this._dragScrollAmount > 0) {
|
||||||
this._model.selectionEnd = [this._terminal.cols - 1, this._terminal.ydisp + this._terminal.rows];
|
this._model.selectionEnd = [this._terminal.cols - 1, this._terminal.buffer.ydisp + this._terminal.rows];
|
||||||
} else {
|
} else {
|
||||||
this._model.selectionEnd = [0, this._terminal.ydisp];
|
this._model.selectionEnd = [0, this._terminal.buffer.ydisp];
|
||||||
}
|
}
|
||||||
this.refresh();
|
this.refresh();
|
||||||
}
|
}
|
||||||
@@ -611,13 +548,25 @@ export class SelectionManager extends EventEmitter {
|
|||||||
return charIndex;
|
return charIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public setSelection(col: number, row: number, length: number): void {
|
||||||
|
this._model.clearSelection();
|
||||||
|
this._removeMouseDownListeners();
|
||||||
|
this._model.selectionStart = [col, row];
|
||||||
|
this._model.selectionStartLength = length;
|
||||||
|
this.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets positional information for the word at the coordinated specified.
|
* Gets positional information for the word at the coordinated specified.
|
||||||
* @param coords The coordinates to get the word at.
|
* @param coords The coordinates to get the word at.
|
||||||
*/
|
*/
|
||||||
private _getWordAt(coords: [number, number]): IWordPosition {
|
private _getWordAt(coords: [number, number]): IWordPosition {
|
||||||
const bufferLine = this._buffer.get(coords[1]);
|
const bufferLine = this._buffer.get(coords[1]);
|
||||||
const line = this._translateBufferLineToString(bufferLine, false);
|
if (!bufferLine) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const line = translateBufferLineToString(bufferLine, false);
|
||||||
|
|
||||||
// Get actual index, taking into consideration wide characters
|
// Get actual index, taking into consideration wide characters
|
||||||
let endIndex = this._convertViewportColToCharacterIndex(bufferLine, coords);
|
let endIndex = this._convertViewportColToCharacterIndex(bufferLine, coords);
|
||||||
@@ -676,7 +625,7 @@ export class SelectionManager extends EventEmitter {
|
|||||||
|
|
||||||
const start = startIndex + charOffset - leftWideCharCount;
|
const start = startIndex + charOffset - leftWideCharCount;
|
||||||
const length = Math.min(endIndex - startIndex + leftWideCharCount + rightWideCharCount + 1/*include endIndex char*/, this._terminal.cols);
|
const length = Math.min(endIndex - startIndex + leftWideCharCount + rightWideCharCount + 1/*include endIndex char*/, this._terminal.cols);
|
||||||
return {start, length};
|
return { start, length };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -685,8 +634,10 @@ export class SelectionManager extends EventEmitter {
|
|||||||
*/
|
*/
|
||||||
protected _selectWordAt(coords: [number, number]): void {
|
protected _selectWordAt(coords: [number, number]): void {
|
||||||
const wordPosition = this._getWordAt(coords);
|
const wordPosition = this._getWordAt(coords);
|
||||||
this._model.selectionStart = [wordPosition.start, coords[1]];
|
if (wordPosition) {
|
||||||
this._model.selectionStartLength = wordPosition.length;
|
this._model.selectionStart = [wordPosition.start, coords[1]];
|
||||||
|
this._model.selectionStartLength = wordPosition.length;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -695,7 +646,9 @@ export class SelectionManager extends EventEmitter {
|
|||||||
*/
|
*/
|
||||||
private _selectToWordAt(coords: [number, number]): void {
|
private _selectToWordAt(coords: [number, number]): void {
|
||||||
const wordPosition = this._getWordAt(coords);
|
const wordPosition = this._getWordAt(coords);
|
||||||
this._model.selectionEnd = [this._model.areSelectionValuesReversed() ? wordPosition.start : (wordPosition.start + wordPosition.length), coords[1]];
|
if (wordPosition) {
|
||||||
|
this._model.selectionEnd = [this._model.areSelectionValuesReversed() ? wordPosition.start : (wordPosition.start + wordPosition.length), coords[1]];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
import { assert } from 'chai';
|
import { assert } from 'chai';
|
||||||
import { ITerminal } from './Interfaces';
|
import { ITerminal } from './Interfaces';
|
||||||
import { SelectionModel } from './SelectionModel';
|
import { SelectionModel } from './SelectionModel';
|
||||||
|
import {BufferSet} from './BufferSet';
|
||||||
|
import { MockTerminal } from './utils/TestUtils';
|
||||||
|
|
||||||
class TestSelectionModel extends SelectionModel {
|
class TestSelectionModel extends SelectionModel {
|
||||||
constructor(
|
constructor(
|
||||||
@@ -21,7 +23,13 @@ describe('SelectionManager', () => {
|
|||||||
let model: TestSelectionModel;
|
let model: TestSelectionModel;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
terminal = <any>{ cols: 80, rows: 2, ybase: 0 };
|
terminal = new MockTerminal();
|
||||||
|
terminal.cols = 80;
|
||||||
|
terminal.rows = 2;
|
||||||
|
terminal.scrollback = 10;
|
||||||
|
terminal.buffers = new BufferSet(terminal);
|
||||||
|
terminal.buffer = terminal.buffers.active;
|
||||||
|
|
||||||
model = new TestSelectionModel(terminal);
|
model = new TestSelectionModel(terminal);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ export class SelectionModel {
|
|||||||
*/
|
*/
|
||||||
public get finalSelectionEnd(): [number, number] {
|
public get finalSelectionEnd(): [number, number] {
|
||||||
if (this.isSelectAllActive) {
|
if (this.isSelectAllActive) {
|
||||||
return [this._terminal.cols, this._terminal.ybase + this._terminal.rows - 1];
|
return [this._terminal.cols, this._terminal.buffer.ybase + this._terminal.rows - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.selectionStart) {
|
if (!this.selectionStart) {
|
||||||
|
|||||||
+11
-9
@@ -1,10 +1,10 @@
|
|||||||
import { assert } from 'chai';
|
import { assert } from 'chai';
|
||||||
import { Viewport } from './Viewport';
|
import { Viewport } from './Viewport';
|
||||||
|
import {BufferSet} from './BufferSet';
|
||||||
|
|
||||||
describe('Viewport', () => {
|
describe('Viewport', () => {
|
||||||
let terminal;
|
let terminal;
|
||||||
let viewportElement;
|
let viewportElement;
|
||||||
let selectionContainer;
|
|
||||||
let charMeasure;
|
let charMeasure;
|
||||||
let viewport;
|
let viewport;
|
||||||
let scrollAreaElement;
|
let scrollAreaElement;
|
||||||
@@ -13,7 +13,6 @@ describe('Viewport', () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
terminal = {
|
terminal = {
|
||||||
lines: [],
|
|
||||||
rows: 0,
|
rows: 0,
|
||||||
ydisp: 0,
|
ydisp: 0,
|
||||||
on: () => {},
|
on: () => {},
|
||||||
@@ -26,8 +25,11 @@ describe('Viewport', () => {
|
|||||||
style: {
|
style: {
|
||||||
height: 0
|
height: 0
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
scrollback: 10
|
||||||
};
|
};
|
||||||
|
terminal.buffers = new BufferSet(terminal);
|
||||||
|
terminal.buffer = terminal.buffers.active;
|
||||||
viewportElement = {
|
viewportElement = {
|
||||||
addEventListener: () => {},
|
addEventListener: () => {},
|
||||||
style: {
|
style: {
|
||||||
@@ -60,14 +62,14 @@ describe('Viewport', () => {
|
|||||||
}, 0);
|
}, 0);
|
||||||
});
|
});
|
||||||
it('should set the height of the viewport when the line-height changed', () => {
|
it('should set the height of the viewport when the line-height changed', () => {
|
||||||
terminal.lines.push('');
|
terminal.buffer.lines.push('');
|
||||||
terminal.lines.push('');
|
terminal.buffer.lines.push('');
|
||||||
terminal.rows = 1;
|
terminal.rows = 1;
|
||||||
viewport.refresh();
|
viewport.refresh();
|
||||||
assert.equal(viewportElement.style.height, 1 * CHARACTER_HEIGHT + 'px');
|
assert.equal(viewportElement.style.height, 1 * CHARACTER_HEIGHT + 'px');
|
||||||
charMeasure.height = 20;
|
charMeasure.height = 2 * CHARACTER_HEIGHT;
|
||||||
viewport.refresh();
|
viewport.refresh();
|
||||||
assert.equal(viewportElement.style.height, 20 + 'px');
|
assert.equal(viewportElement.style.height, 2 * CHARACTER_HEIGHT + 'px');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -75,13 +77,13 @@ describe('Viewport', () => {
|
|||||||
it('should sync the scroll area', done => {
|
it('should sync the scroll area', done => {
|
||||||
// Allow CharMeasure to be initialized
|
// Allow CharMeasure to be initialized
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
terminal.lines.push('');
|
terminal.buffer.lines.push('');
|
||||||
terminal.rows = 1;
|
terminal.rows = 1;
|
||||||
assert.equal(scrollAreaElement.style.height, 0 * CHARACTER_HEIGHT + 'px');
|
assert.equal(scrollAreaElement.style.height, 0 * CHARACTER_HEIGHT + 'px');
|
||||||
viewport.syncScrollArea();
|
viewport.syncScrollArea();
|
||||||
assert.equal(viewportElement.style.height, 1 * CHARACTER_HEIGHT + 'px');
|
assert.equal(viewportElement.style.height, 1 * CHARACTER_HEIGHT + 'px');
|
||||||
assert.equal(scrollAreaElement.style.height, 1 * CHARACTER_HEIGHT + 'px');
|
assert.equal(scrollAreaElement.style.height, 1 * CHARACTER_HEIGHT + 'px');
|
||||||
terminal.lines.push('');
|
terminal.buffer.lines.push('');
|
||||||
viewport.syncScrollArea();
|
viewport.syncScrollArea();
|
||||||
assert.equal(viewportElement.style.height, 1 * CHARACTER_HEIGHT + 'px');
|
assert.equal(viewportElement.style.height, 1 * CHARACTER_HEIGHT + 'px');
|
||||||
assert.equal(scrollAreaElement.style.height, 2 * CHARACTER_HEIGHT + 'px');
|
assert.equal(scrollAreaElement.style.height, 2 * CHARACTER_HEIGHT + 'px');
|
||||||
|
|||||||
+5
-7
@@ -20,7 +20,7 @@ export class Viewport {
|
|||||||
* @param terminal The terminal this viewport belongs to.
|
* @param terminal The terminal this viewport belongs to.
|
||||||
* @param viewportElement The DOM element acting as the viewport.
|
* @param viewportElement The DOM element acting as the viewport.
|
||||||
* @param scrollArea The DOM element acting as the scroll area.
|
* @param scrollArea The DOM element acting as the scroll area.
|
||||||
* @param charMeasureElement A DOM element used to measure the character size of. the terminal.
|
* @param charMeasure A DOM element used to measure the character size of. the terminal.
|
||||||
*/
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
private terminal: ITerminal,
|
private terminal: ITerminal,
|
||||||
@@ -43,8 +43,6 @@ export class Viewport {
|
|||||||
/**
|
/**
|
||||||
* Refreshes row height, setting line-height, viewport height and scroll area height if
|
* Refreshes row height, setting line-height, viewport height and scroll area height if
|
||||||
* necessary.
|
* necessary.
|
||||||
* @param charSize A character size measurement bounding rect object, if it doesn't exist it will
|
|
||||||
* be created.
|
|
||||||
*/
|
*/
|
||||||
private refresh(): void {
|
private refresh(): void {
|
||||||
if (this.charMeasure.height > 0) {
|
if (this.charMeasure.height > 0) {
|
||||||
@@ -68,9 +66,9 @@ export class Viewport {
|
|||||||
* Updates dimensions and synchronizes the scroll area if necessary.
|
* Updates dimensions and synchronizes the scroll area if necessary.
|
||||||
*/
|
*/
|
||||||
public syncScrollArea(): void {
|
public syncScrollArea(): void {
|
||||||
if (this.lastRecordedBufferLength !== this.terminal.lines.length) {
|
if (this.lastRecordedBufferLength !== this.terminal.buffer.lines.length) {
|
||||||
// If buffer height changed
|
// If buffer height changed
|
||||||
this.lastRecordedBufferLength = this.terminal.lines.length;
|
this.lastRecordedBufferLength = this.terminal.buffer.lines.length;
|
||||||
this.refresh();
|
this.refresh();
|
||||||
} else if (this.lastRecordedViewportHeight !== this.terminal.rows) {
|
} else if (this.lastRecordedViewportHeight !== this.terminal.rows) {
|
||||||
// If viewport height changed
|
// If viewport height changed
|
||||||
@@ -83,7 +81,7 @@ export class Viewport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Sync scrollTop
|
// Sync scrollTop
|
||||||
const scrollTop = this.terminal.ydisp * this.currentRowHeight;
|
const scrollTop = this.terminal.buffer.ydisp * this.currentRowHeight;
|
||||||
if (this.viewportElement.scrollTop !== scrollTop) {
|
if (this.viewportElement.scrollTop !== scrollTop) {
|
||||||
this.viewportElement.scrollTop = scrollTop;
|
this.viewportElement.scrollTop = scrollTop;
|
||||||
}
|
}
|
||||||
@@ -96,7 +94,7 @@ export class Viewport {
|
|||||||
*/
|
*/
|
||||||
private onScroll(ev: Event) {
|
private onScroll(ev: Event) {
|
||||||
const newRow = Math.round(this.viewportElement.scrollTop / this.currentRowHeight);
|
const newRow = Math.round(this.viewportElement.scrollTop / this.currentRowHeight);
|
||||||
const diff = newRow - this.terminal.ydisp;
|
const diff = newRow - this.terminal.buffer.ydisp;
|
||||||
this.terminal.scrollDisp(diff, true);
|
this.terminal.scrollDisp(diff, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,140 @@
|
|||||||
|
/**
|
||||||
|
* @license MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
// import { ITerminal } from '../../Interfaces';
|
||||||
|
// import { translateBufferLineToString } from '../../utils/BufferLine';
|
||||||
|
|
||||||
|
interface ISearchResult {
|
||||||
|
term: string;
|
||||||
|
col: number;
|
||||||
|
row: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class that knows how to search the terminal and how to display the results.
|
||||||
|
*/
|
||||||
|
export class SearchHelper {
|
||||||
|
constructor(private _terminal: any, private _translateBufferLineToString: any) {
|
||||||
|
// 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
|
||||||
|
// TODO: Support regex, case sensitivity, etc.
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the next instance of the term, then scroll to and select it. If it
|
||||||
|
* doesn't exist, do nothing.
|
||||||
|
* @param term Tne search term.
|
||||||
|
* @return Whether a result was found.
|
||||||
|
*/
|
||||||
|
public findNext(term: string): boolean {
|
||||||
|
if (!term || term.length === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let result: ISearchResult;
|
||||||
|
|
||||||
|
let startRow = this._terminal.buffer.ydisp;
|
||||||
|
if (this._terminal.selectionManager.selectionEnd) {
|
||||||
|
// Start from the selection end if there is a selection
|
||||||
|
startRow = this._terminal.selectionManager.selectionEnd[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search from ydisp + 1 to end
|
||||||
|
for (let y = startRow + 1; y < this._terminal.buffer.ybase + this._terminal.rows; y++) {
|
||||||
|
result = this._findInLine(term, y);
|
||||||
|
if (result) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search from the top to the current ydisp
|
||||||
|
if (!result) {
|
||||||
|
for (let y = 0; y < startRow; y++) {
|
||||||
|
result = this._findInLine(term, y);
|
||||||
|
if (result) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set selection and scroll if a result was found
|
||||||
|
return this._selectResult(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the previous instance of the term, then scroll to and select it. If it
|
||||||
|
* doesn't exist, do nothing.
|
||||||
|
* @param term Tne search term.
|
||||||
|
* @return Whether a result was found.
|
||||||
|
*/
|
||||||
|
public findPrevious(term: string): boolean {
|
||||||
|
if (!term || term.length === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let result: ISearchResult;
|
||||||
|
|
||||||
|
let startRow = this._terminal.buffer.ydisp;
|
||||||
|
if (this._terminal.selectionManager.selectionStart) {
|
||||||
|
// Start from the selection end if there is a selection
|
||||||
|
startRow = this._terminal.selectionManager.selectionStart[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search from ydisp + 1 to end
|
||||||
|
for (let y = startRow - 1; y >= 0; y--) {
|
||||||
|
result = this._findInLine(term, y);
|
||||||
|
if (result) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search from the top to the current ydisp
|
||||||
|
if (!result) {
|
||||||
|
for (let y = this._terminal.buffer.ybase + this._terminal.rows - 1; y > startRow; y--) {
|
||||||
|
result = this._findInLine(term, y);
|
||||||
|
if (result) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set selection and scroll if a result was found
|
||||||
|
return this._selectResult(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Searches a line for a search term.
|
||||||
|
* @param term Tne search term.
|
||||||
|
* @param y The line to search.
|
||||||
|
* @return The search result if it was found.
|
||||||
|
*/
|
||||||
|
private _findInLine(term: string, y: number): ISearchResult {
|
||||||
|
const bufferLine = this._terminal.buffer.lines.get(y);
|
||||||
|
const lowerStringLine = this._translateBufferLineToString(bufferLine, true).toLowerCase();
|
||||||
|
const lowerTerm = term.toLowerCase();
|
||||||
|
const searchIndex = lowerStringLine.indexOf(lowerTerm);
|
||||||
|
if (searchIndex >= 0) {
|
||||||
|
return {
|
||||||
|
term,
|
||||||
|
col: searchIndex,
|
||||||
|
row: y
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selects and scrolls to a result.
|
||||||
|
* @param result The result to select.
|
||||||
|
* @return Whethera result was selected.
|
||||||
|
*/
|
||||||
|
private _selectResult(result: ISearchResult): boolean {
|
||||||
|
if (!result) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this._terminal.selectionManager.setSelection(result.col, result.row, result.term.length);
|
||||||
|
this._terminal.scrollDisp(result.row - this._terminal.buffer.ydisp, false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
/**
|
||||||
|
* @license MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { SearchHelper } from './SearchHelper';
|
||||||
|
|
||||||
|
declare var exports: any;
|
||||||
|
declare var module: any;
|
||||||
|
declare var define: any;
|
||||||
|
declare var require: any;
|
||||||
|
declare var window: any;
|
||||||
|
|
||||||
|
(function (addon) {
|
||||||
|
if ('Terminal' in window) {
|
||||||
|
/**
|
||||||
|
* Plain browser environment
|
||||||
|
*/
|
||||||
|
addon(window.Terminal);
|
||||||
|
} else if (typeof exports === 'object' && typeof module === 'object') {
|
||||||
|
/**
|
||||||
|
* CommonJS environment
|
||||||
|
*/
|
||||||
|
module.exports = addon(require('../../xterm'));
|
||||||
|
} else if (typeof define == 'function') {
|
||||||
|
/**
|
||||||
|
* Require.js is available
|
||||||
|
*/
|
||||||
|
define(['../../xterm'], addon);
|
||||||
|
}
|
||||||
|
})((Terminal: any) => {
|
||||||
|
/**
|
||||||
|
* Find the next instance of the term, then scroll to and select it. If it
|
||||||
|
* doesn't exist, do nothing.
|
||||||
|
* @param term Tne search term.
|
||||||
|
* @return Whether a result was found.
|
||||||
|
*/
|
||||||
|
Terminal.prototype.findNext = function(term: string): boolean {
|
||||||
|
if (!this._searchHelper) {
|
||||||
|
this.searchHelper = new SearchHelper(this, Terminal.translateBufferLineToString);
|
||||||
|
}
|
||||||
|
return (<SearchHelper>this.searchHelper).findNext(term);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the previous instance of the term, then scroll to and select it. If it
|
||||||
|
* doesn't exist, do nothing.
|
||||||
|
* @param term Tne search term.
|
||||||
|
* @return Whether a result was found.
|
||||||
|
*/
|
||||||
|
Terminal.prototype.findPrevious = function(term: string): boolean {
|
||||||
|
if (!this._searchHelper) {
|
||||||
|
this.searchHelper = new SearchHelper(this, Terminal.translateBufferLineToString);
|
||||||
|
}
|
||||||
|
return (<SearchHelper>this.searchHelper).findPrevious(term);
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "commonjs",
|
||||||
|
"target": "es5",
|
||||||
|
"rootDir": ".",
|
||||||
|
"outDir": "../../../lib/addons/search/",
|
||||||
|
"sourceMap": true,
|
||||||
|
"removeComments": true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,6 @@ var glob = require('glob');
|
|||||||
var fs = require('fs');
|
var fs = require('fs');
|
||||||
var os = require('os');
|
var os = require('os');
|
||||||
var pty = require('node-pty');
|
var pty = require('node-pty');
|
||||||
var sleep = require('sleep');
|
|
||||||
var Terminal = require('../xterm');
|
var Terminal = require('../xterm');
|
||||||
|
|
||||||
if (os.platform() === 'win32') {
|
if (os.platform() === 'win32') {
|
||||||
@@ -25,17 +24,18 @@ var primitive_pty = pty.native.open(COLS, ROWS);
|
|||||||
// we just pipe the data from slave to master as a child program would do
|
// we just pipe the data from slave to master as a child program would do
|
||||||
// pty.js opens pipe fds with O_NONBLOCK
|
// pty.js opens pipe fds with O_NONBLOCK
|
||||||
// just wait 10ms instead of setting fds to blocking mode
|
// just wait 10ms instead of setting fds to blocking mode
|
||||||
function pty_write_read(s) {
|
function ptyWriteRead(s, cb) {
|
||||||
fs.writeSync(primitive_pty.slave, s);
|
fs.writeSync(primitive_pty.slave, s);
|
||||||
sleep.usleep(10000);
|
setTimeout(() => {
|
||||||
var b = Buffer(64000);
|
var b = Buffer(64000);
|
||||||
var bytes = fs.readSync(primitive_pty.master, b, 0, 64000);
|
var bytes = fs.readSync(primitive_pty.master, b, 0, 64000);
|
||||||
return b.toString('utf8', 0, bytes);
|
cb(b.toString('utf8', 0, bytes));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure raw pty is at x=0 and has no pending data
|
// make sure raw pty is at x=0 and has no pending data
|
||||||
function pty_reset() {
|
function ptyReset(cb) {
|
||||||
pty_write_read('\r\n');
|
ptyWriteRead('\r\n', cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* debug helpers */
|
/* debug helpers */
|
||||||
@@ -62,10 +62,10 @@ function formatError(in_, out_, expected) {
|
|||||||
function terminalToString(term) {
|
function terminalToString(term) {
|
||||||
var result = '';
|
var result = '';
|
||||||
var line_s = '';
|
var line_s = '';
|
||||||
for (var line = term.ybase; line < term.ybase + term.rows; line++) {
|
for (var line = term.buffer.ybase; line < term.buffer.ybase + term.rows; line++) {
|
||||||
line_s = '';
|
line_s = '';
|
||||||
for (var cell=0; cell<term.cols; ++cell) {
|
for (var cell=0; cell<term.cols; ++cell) {
|
||||||
line_s += term.lines.get(line)[cell][1];
|
line_s += term.buffer.lines.get(line)[cell][1];
|
||||||
}
|
}
|
||||||
// rtrim empty cells as xterm does
|
// rtrim empty cells as xterm does
|
||||||
line_s = line_s.replace(/\s+$/, '');
|
line_s = line_s.replace(/\s+$/, '');
|
||||||
@@ -97,35 +97,42 @@ describe('xterm output comparison', function() {
|
|||||||
51, 52, 54, 55, 56, 57, 58, 59, 60, 61,
|
51, 52, 54, 55, 56, 57, 58, 59, 60, 61,
|
||||||
63, 68
|
63, 68
|
||||||
];
|
];
|
||||||
|
if (os.platform() === 'darwin') {
|
||||||
|
// These are failing on macOS only
|
||||||
|
skip.push(3, 7, 11, 67);
|
||||||
|
}
|
||||||
for (var i = 0; i < files.length; i++) {
|
for (var i = 0; i < files.length; i++) {
|
||||||
if (skip.indexOf(i) >= 0) {
|
if (skip.indexOf(i) >= 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
(function(filename) {
|
(function(filename) {
|
||||||
it(filename.split('/').slice(-1)[0], function () {
|
it(filename.split('/').slice(-1)[0], done => {
|
||||||
pty_reset();
|
ptyReset(() => {
|
||||||
var in_file = fs.readFileSync(filename, 'utf8');
|
var in_file = fs.readFileSync(filename, 'utf8');
|
||||||
var from_pty = pty_write_read(in_file);
|
ptyWriteRead(in_file, from_pty => {
|
||||||
// uncomment this to get log from terminal
|
// uncomment this to get log from terminal
|
||||||
//console.log = function(){};
|
//console.log = function(){};
|
||||||
|
|
||||||
// Perform a synchronous .write(data)
|
// Perform a synchronous .write(data)
|
||||||
xterm.writeBuffer.push(from_pty);
|
xterm.writeBuffer.push(from_pty);
|
||||||
xterm.innerWrite();
|
xterm.innerWrite();
|
||||||
|
|
||||||
var from_emulator = terminalToString(xterm);
|
var from_emulator = terminalToString(xterm);
|
||||||
console.log = CONSOLE_LOG;
|
console.log = CONSOLE_LOG;
|
||||||
var expected = fs.readFileSync(filename.split('.')[0] + '.text', 'utf8');
|
var expected = fs.readFileSync(filename.split('.')[0] + '.text', 'utf8');
|
||||||
// Some of the tests have whitespace on the right of lines, we trim all the linex
|
// Some of the tests have whitespace on the right of lines, we trim all the linex
|
||||||
// from xterm.js so ignore this for now at least.
|
// from xterm.js so ignore this for now at least.
|
||||||
var expectedRightTrimmed = expected.split('\n').map(function (l) {
|
var expectedRightTrimmed = expected.split('\n').map(function (l) {
|
||||||
return l.replace(/\s+$/, '');
|
return l.replace(/\s+$/, '');
|
||||||
}).join('\n');
|
}).join('\n');
|
||||||
if (from_emulator != expectedRightTrimmed) {
|
if (from_emulator != expectedRightTrimmed) {
|
||||||
// uncomment to get noisy output
|
// uncomment to get noisy output
|
||||||
throw new Error(formatError(in_file, from_emulator, expected));
|
throw new Error(formatError(in_file, from_emulator, expected));
|
||||||
// throw new Error('mismatch');
|
// throw new Error('mismatch');
|
||||||
}
|
}
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
})(files[i]);
|
})(files[i]);
|
||||||
}
|
}
|
||||||
|
|||||||
+128
-128
@@ -64,15 +64,15 @@ describe('xterm.js', function() {
|
|||||||
|
|
||||||
describe('clear', function() {
|
describe('clear', function() {
|
||||||
it('should clear a buffer equal to rows', function() {
|
it('should clear a buffer equal to rows', function() {
|
||||||
var promptLine = xterm.lines.get(xterm.ybase + xterm.y);
|
var promptLine = xterm.buffer.lines.get(xterm.buffer.ybase + xterm.buffer.y);
|
||||||
xterm.clear();
|
xterm.clear();
|
||||||
assert.equal(xterm.y, 0);
|
assert.equal(xterm.buffer.y, 0);
|
||||||
assert.equal(xterm.ybase, 0);
|
assert.equal(xterm.buffer.ybase, 0);
|
||||||
assert.equal(xterm.ydisp, 0);
|
assert.equal(xterm.buffer.ydisp, 0);
|
||||||
assert.equal(xterm.lines.length, xterm.rows);
|
assert.equal(xterm.buffer.lines.length, xterm.rows);
|
||||||
assert.deepEqual(xterm.lines.get(0), promptLine);
|
assert.deepEqual(xterm.buffer.lines.get(0), promptLine);
|
||||||
for (var i = 1; i < xterm.rows; i++) {
|
for (var i = 1; i < xterm.rows; i++) {
|
||||||
assert.deepEqual(xterm.lines.get(i), xterm.blankLine());
|
assert.deepEqual(xterm.buffer.lines.get(i), xterm.blankLine());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
it('should clear a buffer larger than rows', function() {
|
it('should clear a buffer larger than rows', function() {
|
||||||
@@ -81,28 +81,28 @@ describe('xterm.js', function() {
|
|||||||
xterm.write('test\n');
|
xterm.write('test\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
var promptLine = xterm.lines.get(xterm.ybase + xterm.y);
|
var promptLine = xterm.buffer.lines.get(xterm.buffer.ybase + xterm.buffer.y);
|
||||||
xterm.clear();
|
xterm.clear();
|
||||||
assert.equal(xterm.y, 0);
|
assert.equal(xterm.buffer.y, 0);
|
||||||
assert.equal(xterm.ybase, 0);
|
assert.equal(xterm.buffer.ybase, 0);
|
||||||
assert.equal(xterm.ydisp, 0);
|
assert.equal(xterm.buffer.ydisp, 0);
|
||||||
assert.equal(xterm.lines.length, xterm.rows);
|
assert.equal(xterm.buffer.lines.length, xterm.rows);
|
||||||
assert.deepEqual(xterm.lines.get(0), promptLine);
|
assert.deepEqual(xterm.buffer.lines.get(0), promptLine);
|
||||||
for (var i = 1; i < xterm.rows; i++) {
|
for (var i = 1; i < xterm.rows; i++) {
|
||||||
assert.deepEqual(xterm.lines.get(i), xterm.blankLine());
|
assert.deepEqual(xterm.buffer.lines.get(i), xterm.blankLine());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
it('should not break the prompt when cleared twice', function() {
|
it('should not break the prompt when cleared twice', function() {
|
||||||
var promptLine = xterm.lines.get(xterm.ybase + xterm.y);
|
var promptLine = xterm.buffer.lines.get(xterm.buffer.ybase + xterm.buffer.y);
|
||||||
xterm.clear();
|
xterm.clear();
|
||||||
xterm.clear();
|
xterm.clear();
|
||||||
assert.equal(xterm.y, 0);
|
assert.equal(xterm.buffer.y, 0);
|
||||||
assert.equal(xterm.ybase, 0);
|
assert.equal(xterm.buffer.ybase, 0);
|
||||||
assert.equal(xterm.ydisp, 0);
|
assert.equal(xterm.buffer.ydisp, 0);
|
||||||
assert.equal(xterm.lines.length, xterm.rows);
|
assert.equal(xterm.buffer.lines.length, xterm.rows);
|
||||||
assert.deepEqual(xterm.lines.get(0), promptLine);
|
assert.deepEqual(xterm.buffer.lines.get(0), promptLine);
|
||||||
for (var i = 1; i < xterm.rows; i++) {
|
for (var i = 1; i < xterm.rows; i++) {
|
||||||
assert.deepEqual(xterm.lines.get(i), xterm.blankLine());
|
assert.deepEqual(xterm.buffer.lines.get(i), xterm.blankLine());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -117,29 +117,29 @@ describe('xterm.js', function() {
|
|||||||
startYDisp = xterm.rows + 1;
|
startYDisp = xterm.rows + 1;
|
||||||
});
|
});
|
||||||
it('should scroll a single line', function() {
|
it('should scroll a single line', function() {
|
||||||
assert.equal(xterm.ydisp, startYDisp);
|
assert.equal(xterm.buffer.ydisp, startYDisp);
|
||||||
xterm.scrollDisp(-1);
|
xterm.scrollDisp(-1);
|
||||||
assert.equal(xterm.ydisp, startYDisp - 1);
|
assert.equal(xterm.buffer.ydisp, startYDisp - 1);
|
||||||
xterm.scrollDisp(1);
|
xterm.scrollDisp(1);
|
||||||
assert.equal(xterm.ydisp, startYDisp);
|
assert.equal(xterm.buffer.ydisp, startYDisp);
|
||||||
});
|
});
|
||||||
it('should scroll multiple lines', function() {
|
it('should scroll multiple lines', function() {
|
||||||
assert.equal(xterm.ydisp, startYDisp);
|
assert.equal(xterm.buffer.ydisp, startYDisp);
|
||||||
xterm.scrollDisp(-5);
|
xterm.scrollDisp(-5);
|
||||||
assert.equal(xterm.ydisp, startYDisp - 5);
|
assert.equal(xterm.buffer.ydisp, startYDisp - 5);
|
||||||
xterm.scrollDisp(5);
|
xterm.scrollDisp(5);
|
||||||
assert.equal(xterm.ydisp, startYDisp);
|
assert.equal(xterm.buffer.ydisp, startYDisp);
|
||||||
});
|
});
|
||||||
it('should not scroll beyond the bounds of the buffer', function() {
|
it('should not scroll beyond the bounds of the buffer', function() {
|
||||||
assert.equal(xterm.ydisp, startYDisp);
|
assert.equal(xterm.buffer.ydisp, startYDisp);
|
||||||
xterm.scrollDisp(1);
|
xterm.scrollDisp(1);
|
||||||
assert.equal(xterm.ydisp, startYDisp);
|
assert.equal(xterm.buffer.ydisp, startYDisp);
|
||||||
for (var i = 0; i < startYDisp; i++) {
|
for (var i = 0; i < startYDisp; i++) {
|
||||||
xterm.scrollDisp(-1);
|
xterm.scrollDisp(-1);
|
||||||
}
|
}
|
||||||
assert.equal(xterm.ydisp, 0);
|
assert.equal(xterm.buffer.ydisp, 0);
|
||||||
xterm.scrollDisp(-1);
|
xterm.scrollDisp(-1);
|
||||||
assert.equal(xterm.ydisp, 0);
|
assert.equal(xterm.buffer.ydisp, 0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -152,18 +152,18 @@ describe('xterm.js', function() {
|
|||||||
startYDisp = (xterm.rows * 2) + 1;
|
startYDisp = (xterm.rows * 2) + 1;
|
||||||
});
|
});
|
||||||
it('should scroll a single page', function() {
|
it('should scroll a single page', function() {
|
||||||
assert.equal(xterm.ydisp, startYDisp);
|
assert.equal(xterm.buffer.ydisp, startYDisp);
|
||||||
xterm.scrollPages(-1);
|
xterm.scrollPages(-1);
|
||||||
assert.equal(xterm.ydisp, startYDisp - (xterm.rows - 1));
|
assert.equal(xterm.buffer.ydisp, startYDisp - (xterm.rows - 1));
|
||||||
xterm.scrollPages(1);
|
xterm.scrollPages(1);
|
||||||
assert.equal(xterm.ydisp, startYDisp);
|
assert.equal(xterm.buffer.ydisp, startYDisp);
|
||||||
});
|
});
|
||||||
it('should scroll a multiple pages', function() {
|
it('should scroll a multiple pages', function() {
|
||||||
assert.equal(xterm.ydisp, startYDisp);
|
assert.equal(xterm.buffer.ydisp, startYDisp);
|
||||||
xterm.scrollPages(-2);
|
xterm.scrollPages(-2);
|
||||||
assert.equal(xterm.ydisp, startYDisp - (xterm.rows - 1) * 2);
|
assert.equal(xterm.buffer.ydisp, startYDisp - (xterm.rows - 1) * 2);
|
||||||
xterm.scrollPages(2);
|
xterm.scrollPages(2);
|
||||||
assert.equal(xterm.ydisp, startYDisp);
|
assert.equal(xterm.buffer.ydisp, startYDisp);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -174,9 +174,9 @@ describe('xterm.js', function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
it('should scroll to the top', function() {
|
it('should scroll to the top', function() {
|
||||||
assert.notEqual(xterm.ydisp, 0);
|
assert.notEqual(xterm.buffer.ydisp, 0);
|
||||||
xterm.scrollToTop();
|
xterm.scrollToTop();
|
||||||
assert.equal(xterm.ydisp, 0);
|
assert.equal(xterm.buffer.ydisp, 0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -191,13 +191,13 @@ describe('xterm.js', function() {
|
|||||||
it('should scroll to the bottom', function() {
|
it('should scroll to the bottom', function() {
|
||||||
xterm.scrollDisp(-1);
|
xterm.scrollDisp(-1);
|
||||||
xterm.scrollToBottom();
|
xterm.scrollToBottom();
|
||||||
assert.equal(xterm.ydisp, startYDisp);
|
assert.equal(xterm.buffer.ydisp, startYDisp);
|
||||||
xterm.scrollPages(-1);
|
xterm.scrollPages(-1);
|
||||||
xterm.scrollToBottom();
|
xterm.scrollToBottom();
|
||||||
assert.equal(xterm.ydisp, startYDisp);
|
assert.equal(xterm.buffer.ydisp, startYDisp);
|
||||||
xterm.scrollToTop();
|
xterm.scrollToTop();
|
||||||
xterm.scrollToBottom();
|
xterm.scrollToBottom();
|
||||||
assert.equal(xterm.ydisp, startYDisp);
|
assert.equal(xterm.buffer.ydisp, startYDisp);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -214,13 +214,13 @@ describe('xterm.js', function() {
|
|||||||
stopPropagation: function(){}
|
stopPropagation: function(){}
|
||||||
};
|
};
|
||||||
|
|
||||||
xterm.ydisp = 0;
|
xterm.buffer.ydisp = 0;
|
||||||
xterm.ybase = 40;
|
xterm.buffer.ybase = 40;
|
||||||
assert.notEqual(xterm.ydisp, xterm.ybase);
|
assert.notEqual(xterm.buffer.ydisp, xterm.buffer.ybase);
|
||||||
xterm.keyDown(event);
|
xterm.keyDown(event);
|
||||||
|
|
||||||
// Ensure that now the terminal is scrolled to bottom
|
// Ensure that now the terminal is scrolled to bottom
|
||||||
assert.equal(xterm.ydisp, xterm.ybase);
|
assert.equal(xterm.buffer.ydisp, xterm.buffer.ybase);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not scroll down, when a custom keydown handler prevents the event', function () {
|
it('should not scroll down, when a custom keydown handler prevents the event', function () {
|
||||||
@@ -233,11 +233,11 @@ describe('xterm.js', function() {
|
|||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
assert.equal(xterm.ydisp, startYDisp);
|
assert.equal(xterm.buffer.ydisp, startYDisp);
|
||||||
xterm.scrollDisp(-1);
|
xterm.scrollDisp(-1);
|
||||||
assert.equal(xterm.ydisp, startYDisp - 1);
|
assert.equal(xterm.buffer.ydisp, startYDisp - 1);
|
||||||
xterm.keyDown({ keyCode: 0 });
|
xterm.keyDown({ keyCode: 0 });
|
||||||
assert.equal(xterm.ydisp, startYDisp - 1);
|
assert.equal(xterm.buffer.ydisp, startYDisp - 1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -575,48 +575,48 @@ describe('xterm.js', function() {
|
|||||||
var high = String.fromCharCode(0xD800);
|
var high = String.fromCharCode(0xD800);
|
||||||
for (var i=0xDC00; i<=0xDCFF; ++i) {
|
for (var i=0xDC00; i<=0xDCFF; ++i) {
|
||||||
xterm.write(high + String.fromCharCode(i));
|
xterm.write(high + String.fromCharCode(i));
|
||||||
var tchar = xterm.lines.get(0)[0];
|
var tchar = xterm.buffer.lines.get(0)[0];
|
||||||
expect(tchar[1]).eql(high + String.fromCharCode(i));
|
expect(tchar[1]).eql(high + String.fromCharCode(i));
|
||||||
expect(tchar[1].length).eql(2);
|
expect(tchar[1].length).eql(2);
|
||||||
expect(tchar[2]).eql(1);
|
expect(tchar[2]).eql(1);
|
||||||
expect(xterm.lines.get(0)[1][1]).eql(' ');
|
expect(xterm.buffer.lines.get(0)[1][1]).eql(' ');
|
||||||
xterm.reset();
|
xterm.reset();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
it('2 characters at last cell', function() {
|
it('2 characters at last cell', function() {
|
||||||
var high = String.fromCharCode(0xD800);
|
var high = String.fromCharCode(0xD800);
|
||||||
for (var i=0xDC00; i<=0xDCFF; ++i) {
|
for (var i=0xDC00; i<=0xDCFF; ++i) {
|
||||||
xterm.x = xterm.cols - 1;
|
xterm.buffer.x = xterm.cols - 1;
|
||||||
xterm.write(high + String.fromCharCode(i));
|
xterm.write(high + String.fromCharCode(i));
|
||||||
expect(xterm.lines.get(0)[xterm.x-1][1]).eql(high + String.fromCharCode(i));
|
expect(xterm.buffer.lines.get(0)[xterm.buffer.x-1][1]).eql(high + String.fromCharCode(i));
|
||||||
expect(xterm.lines.get(0)[xterm.x-1][1].length).eql(2);
|
expect(xterm.buffer.lines.get(0)[xterm.buffer.x-1][1].length).eql(2);
|
||||||
expect(xterm.lines.get(1)[0][1]).eql(' ');
|
expect(xterm.buffer.lines.get(1)[0][1]).eql(' ');
|
||||||
xterm.reset();
|
xterm.reset();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
it('2 characters per cell over line end with autowrap', function() {
|
it('2 characters per cell over line end with autowrap', function() {
|
||||||
var high = String.fromCharCode(0xD800);
|
var high = String.fromCharCode(0xD800);
|
||||||
for (var i=0xDC00; i<=0xDCFF; ++i) {
|
for (var i=0xDC00; i<=0xDCFF; ++i) {
|
||||||
xterm.x = xterm.cols - 1;
|
xterm.buffer.x = xterm.cols - 1;
|
||||||
xterm.wraparoundMode = true;
|
xterm.wraparoundMode = true;
|
||||||
xterm.write('a' + high + String.fromCharCode(i));
|
xterm.write('a' + high + String.fromCharCode(i));
|
||||||
expect(xterm.lines.get(0)[xterm.cols-1][1]).eql('a');
|
expect(xterm.buffer.lines.get(0)[xterm.cols-1][1]).eql('a');
|
||||||
expect(xterm.lines.get(1)[0][1]).eql(high + String.fromCharCode(i));
|
expect(xterm.buffer.lines.get(1)[0][1]).eql(high + String.fromCharCode(i));
|
||||||
expect(xterm.lines.get(1)[0][1].length).eql(2);
|
expect(xterm.buffer.lines.get(1)[0][1].length).eql(2);
|
||||||
expect(xterm.lines.get(1)[1][1]).eql(' ');
|
expect(xterm.buffer.lines.get(1)[1][1]).eql(' ');
|
||||||
xterm.reset();
|
xterm.reset();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
it('2 characters per cell over line end without autowrap', function() {
|
it('2 characters per cell over line end without autowrap', function() {
|
||||||
var high = String.fromCharCode(0xD800);
|
var high = String.fromCharCode(0xD800);
|
||||||
for (var i=0xDC00; i<=0xDCFF; ++i) {
|
for (var i=0xDC00; i<=0xDCFF; ++i) {
|
||||||
xterm.x = xterm.cols - 1;
|
xterm.buffer.x = xterm.cols - 1;
|
||||||
xterm.wraparoundMode = false;
|
xterm.wraparoundMode = false;
|
||||||
xterm.write('a' + high + String.fromCharCode(i));
|
xterm.write('a' + high + String.fromCharCode(i));
|
||||||
// auto wraparound mode should cut off the rest of the line
|
// auto wraparound mode should cut off the rest of the line
|
||||||
expect(xterm.lines.get(0)[xterm.cols-1][1]).eql('a');
|
expect(xterm.buffer.lines.get(0)[xterm.cols-1][1]).eql('a');
|
||||||
expect(xterm.lines.get(0)[xterm.cols-1][1].length).eql(1);
|
expect(xterm.buffer.lines.get(0)[xterm.cols-1][1].length).eql(1);
|
||||||
expect(xterm.lines.get(1)[1][1]).eql(' ');
|
expect(xterm.buffer.lines.get(1)[1][1]).eql(' ');
|
||||||
xterm.reset();
|
xterm.reset();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -625,11 +625,11 @@ describe('xterm.js', function() {
|
|||||||
for (var i=0xDC00; i<=0xDCFF; ++i) {
|
for (var i=0xDC00; i<=0xDCFF; ++i) {
|
||||||
xterm.write(high);
|
xterm.write(high);
|
||||||
xterm.write(String.fromCharCode(i));
|
xterm.write(String.fromCharCode(i));
|
||||||
var tchar = xterm.lines.get(0)[0];
|
var tchar = xterm.buffer.lines.get(0)[0];
|
||||||
expect(tchar[1]).eql(high + String.fromCharCode(i));
|
expect(tchar[1]).eql(high + String.fromCharCode(i));
|
||||||
expect(tchar[1].length).eql(2);
|
expect(tchar[1].length).eql(2);
|
||||||
expect(tchar[2]).eql(1);
|
expect(tchar[2]).eql(1);
|
||||||
expect(xterm.lines.get(0)[1][1]).eql(' ');
|
expect(xterm.buffer.lines.get(0)[1][1]).eql(' ');
|
||||||
xterm.reset();
|
xterm.reset();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -638,30 +638,30 @@ describe('xterm.js', function() {
|
|||||||
describe('unicode - combining characters', function() {
|
describe('unicode - combining characters', function() {
|
||||||
it('café', function () {
|
it('café', function () {
|
||||||
xterm.write('cafe\u0301');
|
xterm.write('cafe\u0301');
|
||||||
expect(xterm.lines.get(0)[3][1]).eql('e\u0301');
|
expect(xterm.buffer.lines.get(0)[3][1]).eql('e\u0301');
|
||||||
expect(xterm.lines.get(0)[3][1].length).eql(2);
|
expect(xterm.buffer.lines.get(0)[3][1].length).eql(2);
|
||||||
expect(xterm.lines.get(0)[3][2]).eql(1);
|
expect(xterm.buffer.lines.get(0)[3][2]).eql(1);
|
||||||
});
|
});
|
||||||
it('café - end of line', function() {
|
it('café - end of line', function() {
|
||||||
xterm.x = xterm.cols - 1 - 3;
|
xterm.buffer.x = xterm.cols - 1 - 3;
|
||||||
xterm.write('cafe\u0301');
|
xterm.write('cafe\u0301');
|
||||||
expect(xterm.lines.get(0)[xterm.cols-1][1]).eql('e\u0301');
|
expect(xterm.buffer.lines.get(0)[xterm.cols-1][1]).eql('e\u0301');
|
||||||
expect(xterm.lines.get(0)[xterm.cols-1][1].length).eql(2);
|
expect(xterm.buffer.lines.get(0)[xterm.cols-1][1].length).eql(2);
|
||||||
expect(xterm.lines.get(0)[xterm.cols-1][2]).eql(1);
|
expect(xterm.buffer.lines.get(0)[xterm.cols-1][2]).eql(1);
|
||||||
expect(xterm.lines.get(0)[1][1]).eql(' ');
|
expect(xterm.buffer.lines.get(0)[1][1]).eql(' ');
|
||||||
expect(xterm.lines.get(0)[1][1].length).eql(1);
|
expect(xterm.buffer.lines.get(0)[1][1].length).eql(1);
|
||||||
expect(xterm.lines.get(0)[1][2]).eql(1);
|
expect(xterm.buffer.lines.get(0)[1][2]).eql(1);
|
||||||
});
|
});
|
||||||
it('multiple combined é', function() {
|
it('multiple combined é', function() {
|
||||||
xterm.wraparoundMode = true;
|
xterm.wraparoundMode = true;
|
||||||
xterm.write(Array(100).join('e\u0301'));
|
xterm.write(Array(100).join('e\u0301'));
|
||||||
for (var i=0; i<xterm.cols; ++i) {
|
for (var i=0; i<xterm.cols; ++i) {
|
||||||
var tchar = xterm.lines.get(0)[i];
|
var tchar = xterm.buffer.lines.get(0)[i];
|
||||||
expect(tchar[1]).eql('e\u0301');
|
expect(tchar[1]).eql('e\u0301');
|
||||||
expect(tchar[1].length).eql(2);
|
expect(tchar[1].length).eql(2);
|
||||||
expect(tchar[2]).eql(1);
|
expect(tchar[2]).eql(1);
|
||||||
}
|
}
|
||||||
tchar = xterm.lines.get(1)[0];
|
tchar = xterm.buffer.lines.get(1)[0];
|
||||||
expect(tchar[1]).eql('e\u0301');
|
expect(tchar[1]).eql('e\u0301');
|
||||||
expect(tchar[1].length).eql(2);
|
expect(tchar[1].length).eql(2);
|
||||||
expect(tchar[2]).eql(1);
|
expect(tchar[2]).eql(1);
|
||||||
@@ -670,12 +670,12 @@ describe('xterm.js', function() {
|
|||||||
xterm.wraparoundMode = true;
|
xterm.wraparoundMode = true;
|
||||||
xterm.write(Array(100).join('\uD800\uDC00\u0301'));
|
xterm.write(Array(100).join('\uD800\uDC00\u0301'));
|
||||||
for (var i=0; i<xterm.cols; ++i) {
|
for (var i=0; i<xterm.cols; ++i) {
|
||||||
var tchar = xterm.lines.get(0)[i];
|
var tchar = xterm.buffer.lines.get(0)[i];
|
||||||
expect(tchar[1]).eql('\uD800\uDC00\u0301');
|
expect(tchar[1]).eql('\uD800\uDC00\u0301');
|
||||||
expect(tchar[1].length).eql(3);
|
expect(tchar[1].length).eql(3);
|
||||||
expect(tchar[2]).eql(1);
|
expect(tchar[2]).eql(1);
|
||||||
}
|
}
|
||||||
tchar = xterm.lines.get(1)[0];
|
tchar = xterm.buffer.lines.get(1)[0];
|
||||||
expect(tchar[1]).eql('\uD800\uDC00\u0301');
|
expect(tchar[1]).eql('\uD800\uDC00\u0301');
|
||||||
expect(tchar[1].length).eql(3);
|
expect(tchar[1].length).eql(3);
|
||||||
expect(tchar[2]).eql(1);
|
expect(tchar[2]).eql(1);
|
||||||
@@ -684,21 +684,21 @@ describe('xterm.js', function() {
|
|||||||
|
|
||||||
describe('unicode - fullwidth characters', function() {
|
describe('unicode - fullwidth characters', function() {
|
||||||
it('cursor movement even', function() {
|
it('cursor movement even', function() {
|
||||||
expect(xterm.x).eql(0);
|
expect(xterm.buffer.x).eql(0);
|
||||||
xterm.write('¥');
|
xterm.write('¥');
|
||||||
expect(xterm.x).eql(2);
|
expect(xterm.buffer.x).eql(2);
|
||||||
});
|
});
|
||||||
it('cursor movement odd', function() {
|
it('cursor movement odd', function() {
|
||||||
xterm.x = 1;
|
xterm.buffer.x = 1;
|
||||||
expect(xterm.x).eql(1);
|
expect(xterm.buffer.x).eql(1);
|
||||||
xterm.write('¥');
|
xterm.write('¥');
|
||||||
expect(xterm.x).eql(3);
|
expect(xterm.buffer.x).eql(3);
|
||||||
});
|
});
|
||||||
it('line of ¥ even', function() {
|
it('line of ¥ even', function() {
|
||||||
xterm.wraparoundMode = true;
|
xterm.wraparoundMode = true;
|
||||||
xterm.write(Array(50).join('¥'));
|
xterm.write(Array(50).join('¥'));
|
||||||
for (var i=0; i<xterm.cols; ++i) {
|
for (var i=0; i<xterm.cols; ++i) {
|
||||||
var tchar = xterm.lines.get(0)[i];
|
var tchar = xterm.buffer.lines.get(0)[i];
|
||||||
if (i % 2) {
|
if (i % 2) {
|
||||||
expect(tchar[1]).eql('');
|
expect(tchar[1]).eql('');
|
||||||
expect(tchar[1].length).eql(0);
|
expect(tchar[1].length).eql(0);
|
||||||
@@ -709,17 +709,17 @@ describe('xterm.js', function() {
|
|||||||
expect(tchar[2]).eql(2);
|
expect(tchar[2]).eql(2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tchar = xterm.lines.get(1)[0];
|
tchar = xterm.buffer.lines.get(1)[0];
|
||||||
expect(tchar[1]).eql('¥');
|
expect(tchar[1]).eql('¥');
|
||||||
expect(tchar[1].length).eql(1);
|
expect(tchar[1].length).eql(1);
|
||||||
expect(tchar[2]).eql(2);
|
expect(tchar[2]).eql(2);
|
||||||
});
|
});
|
||||||
it('line of ¥ odd', function() {
|
it('line of ¥ odd', function() {
|
||||||
xterm.wraparoundMode = true;
|
xterm.wraparoundMode = true;
|
||||||
xterm.x = 1;
|
xterm.buffer.x = 1;
|
||||||
xterm.write(Array(50).join('¥'));
|
xterm.write(Array(50).join('¥'));
|
||||||
for (var i=1; i<xterm.cols-1; ++i) {
|
for (var i=1; i<xterm.cols-1; ++i) {
|
||||||
var tchar = xterm.lines.get(0)[i];
|
var tchar = xterm.buffer.lines.get(0)[i];
|
||||||
if (!(i % 2)) {
|
if (!(i % 2)) {
|
||||||
expect(tchar[1]).eql('');
|
expect(tchar[1]).eql('');
|
||||||
expect(tchar[1].length).eql(0);
|
expect(tchar[1].length).eql(0);
|
||||||
@@ -730,21 +730,21 @@ describe('xterm.js', function() {
|
|||||||
expect(tchar[2]).eql(2);
|
expect(tchar[2]).eql(2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tchar = xterm.lines.get(0)[xterm.cols-1];
|
tchar = xterm.buffer.lines.get(0)[xterm.cols-1];
|
||||||
expect(tchar[1]).eql(' ');
|
expect(tchar[1]).eql(' ');
|
||||||
expect(tchar[1].length).eql(1);
|
expect(tchar[1].length).eql(1);
|
||||||
expect(tchar[2]).eql(1);
|
expect(tchar[2]).eql(1);
|
||||||
tchar = xterm.lines.get(1)[0];
|
tchar = xterm.buffer.lines.get(1)[0];
|
||||||
expect(tchar[1]).eql('¥');
|
expect(tchar[1]).eql('¥');
|
||||||
expect(tchar[1].length).eql(1);
|
expect(tchar[1].length).eql(1);
|
||||||
expect(tchar[2]).eql(2);
|
expect(tchar[2]).eql(2);
|
||||||
});
|
});
|
||||||
it('line of ¥ with combining odd', function() {
|
it('line of ¥ with combining odd', function() {
|
||||||
xterm.wraparoundMode = true;
|
xterm.wraparoundMode = true;
|
||||||
xterm.x = 1;
|
xterm.buffer.x = 1;
|
||||||
xterm.write(Array(50).join('¥\u0301'));
|
xterm.write(Array(50).join('¥\u0301'));
|
||||||
for (var i=1; i<xterm.cols-1; ++i) {
|
for (var i=1; i<xterm.cols-1; ++i) {
|
||||||
var tchar = xterm.lines.get(0)[i];
|
var tchar = xterm.buffer.lines.get(0)[i];
|
||||||
if (!(i % 2)) {
|
if (!(i % 2)) {
|
||||||
expect(tchar[1]).eql('');
|
expect(tchar[1]).eql('');
|
||||||
expect(tchar[1].length).eql(0);
|
expect(tchar[1].length).eql(0);
|
||||||
@@ -755,11 +755,11 @@ describe('xterm.js', function() {
|
|||||||
expect(tchar[2]).eql(2);
|
expect(tchar[2]).eql(2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tchar = xterm.lines.get(0)[xterm.cols-1];
|
tchar = xterm.buffer.lines.get(0)[xterm.cols-1];
|
||||||
expect(tchar[1]).eql(' ');
|
expect(tchar[1]).eql(' ');
|
||||||
expect(tchar[1].length).eql(1);
|
expect(tchar[1].length).eql(1);
|
||||||
expect(tchar[2]).eql(1);
|
expect(tchar[2]).eql(1);
|
||||||
tchar = xterm.lines.get(1)[0];
|
tchar = xterm.buffer.lines.get(1)[0];
|
||||||
expect(tchar[1]).eql('¥\u0301');
|
expect(tchar[1]).eql('¥\u0301');
|
||||||
expect(tchar[1].length).eql(2);
|
expect(tchar[1].length).eql(2);
|
||||||
expect(tchar[2]).eql(2);
|
expect(tchar[2]).eql(2);
|
||||||
@@ -768,7 +768,7 @@ describe('xterm.js', function() {
|
|||||||
xterm.wraparoundMode = true;
|
xterm.wraparoundMode = true;
|
||||||
xterm.write(Array(50).join('¥\u0301'));
|
xterm.write(Array(50).join('¥\u0301'));
|
||||||
for (var i=0; i<xterm.cols; ++i) {
|
for (var i=0; i<xterm.cols; ++i) {
|
||||||
var tchar = xterm.lines.get(0)[i];
|
var tchar = xterm.buffer.lines.get(0)[i];
|
||||||
if (i % 2) {
|
if (i % 2) {
|
||||||
expect(tchar[1]).eql('');
|
expect(tchar[1]).eql('');
|
||||||
expect(tchar[1].length).eql(0);
|
expect(tchar[1].length).eql(0);
|
||||||
@@ -779,17 +779,17 @@ describe('xterm.js', function() {
|
|||||||
expect(tchar[2]).eql(2);
|
expect(tchar[2]).eql(2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tchar = xterm.lines.get(1)[0];
|
tchar = xterm.buffer.lines.get(1)[0];
|
||||||
expect(tchar[1]).eql('¥\u0301');
|
expect(tchar[1]).eql('¥\u0301');
|
||||||
expect(tchar[1].length).eql(2);
|
expect(tchar[1].length).eql(2);
|
||||||
expect(tchar[2]).eql(2);
|
expect(tchar[2]).eql(2);
|
||||||
});
|
});
|
||||||
it('line of surrogate fullwidth with combining odd', function() {
|
it('line of surrogate fullwidth with combining odd', function() {
|
||||||
xterm.wraparoundMode = true;
|
xterm.wraparoundMode = true;
|
||||||
xterm.x = 1;
|
xterm.buffer.x = 1;
|
||||||
xterm.write(Array(50).join('\ud843\ude6d\u0301'));
|
xterm.write(Array(50).join('\ud843\ude6d\u0301'));
|
||||||
for (var i=1; i<xterm.cols-1; ++i) {
|
for (var i=1; i<xterm.cols-1; ++i) {
|
||||||
var tchar = xterm.lines.get(0)[i];
|
var tchar = xterm.buffer.lines.get(0)[i];
|
||||||
if (!(i % 2)) {
|
if (!(i % 2)) {
|
||||||
expect(tchar[1]).eql('');
|
expect(tchar[1]).eql('');
|
||||||
expect(tchar[1].length).eql(0);
|
expect(tchar[1].length).eql(0);
|
||||||
@@ -800,11 +800,11 @@ describe('xterm.js', function() {
|
|||||||
expect(tchar[2]).eql(2);
|
expect(tchar[2]).eql(2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tchar = xterm.lines.get(0)[xterm.cols-1];
|
tchar = xterm.buffer.lines.get(0)[xterm.cols-1];
|
||||||
expect(tchar[1]).eql(' ');
|
expect(tchar[1]).eql(' ');
|
||||||
expect(tchar[1].length).eql(1);
|
expect(tchar[1].length).eql(1);
|
||||||
expect(tchar[2]).eql(1);
|
expect(tchar[2]).eql(1);
|
||||||
tchar = xterm.lines.get(1)[0];
|
tchar = xterm.buffer.lines.get(1)[0];
|
||||||
expect(tchar[1]).eql('\ud843\ude6d\u0301');
|
expect(tchar[1]).eql('\ud843\ude6d\u0301');
|
||||||
expect(tchar[1].length).eql(3);
|
expect(tchar[1].length).eql(3);
|
||||||
expect(tchar[2]).eql(2);
|
expect(tchar[2]).eql(2);
|
||||||
@@ -813,7 +813,7 @@ describe('xterm.js', function() {
|
|||||||
xterm.wraparoundMode = true;
|
xterm.wraparoundMode = true;
|
||||||
xterm.write(Array(50).join('\ud843\ude6d\u0301'));
|
xterm.write(Array(50).join('\ud843\ude6d\u0301'));
|
||||||
for (var i=0; i<xterm.cols; ++i) {
|
for (var i=0; i<xterm.cols; ++i) {
|
||||||
var tchar = xterm.lines.get(0)[i];
|
var tchar = xterm.buffer.lines.get(0)[i];
|
||||||
if (i % 2) {
|
if (i % 2) {
|
||||||
expect(tchar[1]).eql('');
|
expect(tchar[1]).eql('');
|
||||||
expect(tchar[1].length).eql(0);
|
expect(tchar[1].length).eql(0);
|
||||||
@@ -824,7 +824,7 @@ describe('xterm.js', function() {
|
|||||||
expect(tchar[2]).eql(2);
|
expect(tchar[2]).eql(2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tchar = xterm.lines.get(1)[0];
|
tchar = xterm.buffer.lines.get(1)[0];
|
||||||
expect(tchar[1]).eql('\ud843\ude6d\u0301');
|
expect(tchar[1]).eql('\ud843\ude6d\u0301');
|
||||||
expect(tchar[1].length).eql(3);
|
expect(tchar[1].length).eql(3);
|
||||||
expect(tchar[2]).eql(2);
|
expect(tchar[2]).eql(2);
|
||||||
@@ -834,44 +834,44 @@ describe('xterm.js', function() {
|
|||||||
describe('insert mode', function() {
|
describe('insert mode', function() {
|
||||||
it('halfwidth - all', function () {
|
it('halfwidth - all', function () {
|
||||||
xterm.write(Array(9).join('0123456789').slice(-80));
|
xterm.write(Array(9).join('0123456789').slice(-80));
|
||||||
xterm.x = 10;
|
xterm.buffer.x = 10;
|
||||||
xterm.y = 0;
|
xterm.buffer.y = 0;
|
||||||
xterm.insertMode = true;
|
xterm.insertMode = true;
|
||||||
xterm.write('abcde');
|
xterm.write('abcde');
|
||||||
expect(xterm.lines.get(0).length).eql(xterm.cols);
|
expect(xterm.buffer.lines.get(0).length).eql(xterm.cols);
|
||||||
expect(xterm.lines.get(0)[10][1]).eql('a');
|
expect(xterm.buffer.lines.get(0)[10][1]).eql('a');
|
||||||
expect(xterm.lines.get(0)[14][1]).eql('e');
|
expect(xterm.buffer.lines.get(0)[14][1]).eql('e');
|
||||||
expect(xterm.lines.get(0)[15][1]).eql('0');
|
expect(xterm.buffer.lines.get(0)[15][1]).eql('0');
|
||||||
expect(xterm.lines.get(0)[79][1]).eql('4');
|
expect(xterm.buffer.lines.get(0)[79][1]).eql('4');
|
||||||
});
|
});
|
||||||
it('fullwidth - insert', function() {
|
it('fullwidth - insert', function() {
|
||||||
xterm.write(Array(9).join('0123456789').slice(-80));
|
xterm.write(Array(9).join('0123456789').slice(-80));
|
||||||
xterm.x = 10;
|
xterm.buffer.x = 10;
|
||||||
xterm.y = 0;
|
xterm.buffer.y = 0;
|
||||||
xterm.insertMode = true;
|
xterm.insertMode = true;
|
||||||
xterm.write('¥¥¥');
|
xterm.write('¥¥¥');
|
||||||
expect(xterm.lines.get(0).length).eql(xterm.cols);
|
expect(xterm.buffer.lines.get(0).length).eql(xterm.cols);
|
||||||
expect(xterm.lines.get(0)[10][1]).eql('¥');
|
expect(xterm.buffer.lines.get(0)[10][1]).eql('¥');
|
||||||
expect(xterm.lines.get(0)[11][1]).eql('');
|
expect(xterm.buffer.lines.get(0)[11][1]).eql('');
|
||||||
expect(xterm.lines.get(0)[14][1]).eql('¥');
|
expect(xterm.buffer.lines.get(0)[14][1]).eql('¥');
|
||||||
expect(xterm.lines.get(0)[15][1]).eql('');
|
expect(xterm.buffer.lines.get(0)[15][1]).eql('');
|
||||||
expect(xterm.lines.get(0)[79][1]).eql('3');
|
expect(xterm.buffer.lines.get(0)[79][1]).eql('3');
|
||||||
});
|
});
|
||||||
it('fullwidth - right border', function() {
|
it('fullwidth - right border', function() {
|
||||||
xterm.write(Array(41).join('¥'));
|
xterm.write(Array(41).join('¥'));
|
||||||
xterm.x = 10;
|
xterm.buffer.x = 10;
|
||||||
xterm.y = 0;
|
xterm.buffer.y = 0;
|
||||||
xterm.insertMode = true;
|
xterm.insertMode = true;
|
||||||
xterm.write('a');
|
xterm.write('a');
|
||||||
expect(xterm.lines.get(0).length).eql(xterm.cols);
|
expect(xterm.buffer.lines.get(0).length).eql(xterm.cols);
|
||||||
expect(xterm.lines.get(0)[10][1]).eql('a');
|
expect(xterm.buffer.lines.get(0)[10][1]).eql('a');
|
||||||
expect(xterm.lines.get(0)[11][1]).eql('¥');
|
expect(xterm.buffer.lines.get(0)[11][1]).eql('¥');
|
||||||
expect(xterm.lines.get(0)[79][1]).eql(' '); // fullwidth char got replaced
|
expect(xterm.buffer.lines.get(0)[79][1]).eql(' '); // fullwidth char got replaced
|
||||||
xterm.write('b');
|
xterm.write('b');
|
||||||
expect(xterm.lines.get(0).length).eql(xterm.cols);
|
expect(xterm.buffer.lines.get(0).length).eql(xterm.cols);
|
||||||
expect(xterm.lines.get(0)[11][1]).eql('b');
|
expect(xterm.buffer.lines.get(0)[11][1]).eql('b');
|
||||||
expect(xterm.lines.get(0)[12][1]).eql('¥');
|
expect(xterm.buffer.lines.get(0)[12][1]).eql('¥');
|
||||||
expect(xterm.lines.get(0)[79][1]).eql(''); // empty cell after fullwidth
|
expect(xterm.buffer.lines.get(0)[79][1]).eql(''); // empty cell after fullwidth
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
/**
|
||||||
|
* @license MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
// TODO: This module should be merged into a buffer or buffer line class
|
||||||
|
|
||||||
|
const LINE_DATA_CHAR_INDEX = 1;
|
||||||
|
const LINE_DATA_WIDTH_INDEX = 2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translates a buffer line to a string, with optional start and end columns.
|
||||||
|
* Wide characters will count as two columns in the resulting string. This
|
||||||
|
* function is useful for getting the actual text underneath the raw selection
|
||||||
|
* position.
|
||||||
|
* @param line The line being translated.
|
||||||
|
* @param trimRight Whether to trim whitespace to the right.
|
||||||
|
* @param startCol The column to start at.
|
||||||
|
* @param endCol The column to end at.
|
||||||
|
*/
|
||||||
|
export function translateBufferLineToString(line: any, trimRight: boolean, startCol: number = 0, endCol: number = null): string {
|
||||||
|
// Get full line
|
||||||
|
let lineString = '';
|
||||||
|
let widthAdjustedStartCol = startCol;
|
||||||
|
let widthAdjustedEndCol = endCol;
|
||||||
|
for (let i = 0; i < line.length; i++) {
|
||||||
|
const char = line[i];
|
||||||
|
lineString += char[LINE_DATA_CHAR_INDEX];
|
||||||
|
// Adjust start and end cols for wide characters if they affect their
|
||||||
|
// column indexes
|
||||||
|
if (char[LINE_DATA_WIDTH_INDEX] === 0) {
|
||||||
|
if (startCol >= i) {
|
||||||
|
widthAdjustedStartCol--;
|
||||||
|
}
|
||||||
|
if (endCol >= i) {
|
||||||
|
widthAdjustedEndCol--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the final end col by trimming whitespace on the right of the
|
||||||
|
// line if needed.
|
||||||
|
let finalEndCol = widthAdjustedEndCol || line.length;
|
||||||
|
if (trimRight) {
|
||||||
|
const rightWhitespaceIndex = lineString.search(/\s+$/);
|
||||||
|
if (rightWhitespaceIndex !== -1) {
|
||||||
|
finalEndCol = Math.min(finalEndCol, rightWhitespaceIndex);
|
||||||
|
}
|
||||||
|
// Return the empty string if only trimmed whitespace is selected
|
||||||
|
if (finalEndCol <= widthAdjustedStartCol) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return lineString.substring(widthAdjustedStartCol, finalEndCol);
|
||||||
|
}
|
||||||
@@ -7,21 +7,19 @@ import { ICharMeasure, ITerminal } from '../Interfaces';
|
|||||||
import { CharMeasure } from './CharMeasure';
|
import { CharMeasure } from './CharMeasure';
|
||||||
|
|
||||||
describe('CharMeasure', () => {
|
describe('CharMeasure', () => {
|
||||||
|
let dom: jsdom.JSDOM;
|
||||||
let window: Window;
|
let window: Window;
|
||||||
let document: Document;
|
let document: Document;
|
||||||
|
|
||||||
let container: HTMLElement;
|
let container: HTMLElement;
|
||||||
let charMeasure: ICharMeasure;
|
let charMeasure: ICharMeasure;
|
||||||
|
|
||||||
beforeEach(done => {
|
beforeEach(() => {
|
||||||
jsdom.env('', (err, w) => {
|
dom = new jsdom.JSDOM('');
|
||||||
window = w;
|
window = dom.window;
|
||||||
document = window.document;
|
document = window.document;
|
||||||
container = document.createElement('div');
|
container = document.createElement('div');
|
||||||
document.body.appendChild(container);
|
document.body.appendChild(container);
|
||||||
charMeasure = new CharMeasure(document, container);
|
charMeasure = new CharMeasure(document, container);
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('measure', () => {
|
describe('measure', () => {
|
||||||
|
|||||||
@@ -5,8 +5,9 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
import { EventEmitter } from '../EventEmitter';
|
import { EventEmitter } from '../EventEmitter';
|
||||||
|
import { ICircularList } from '../Interfaces';
|
||||||
|
|
||||||
export class CircularList<T> extends EventEmitter {
|
export class CircularList<T> extends EventEmitter implements ICircularList<T> {
|
||||||
private _array: T[];
|
private _array: T[];
|
||||||
private _startIndex: number;
|
private _startIndex: number;
|
||||||
private _length: number;
|
private _length: number;
|
||||||
|
|||||||
@@ -37,7 +37,15 @@ export function getCoordsRelativeToElement(event: MouseEvent, element: HTMLEleme
|
|||||||
* select that cell and the right half will select the next cell.
|
* select that cell and the right half will select the next cell.
|
||||||
*/
|
*/
|
||||||
export function getCoords(event: MouseEvent, rowContainer: HTMLElement, charMeasure: CharMeasure, colCount: number, rowCount: number, isSelection?: boolean): [number, number] {
|
export function getCoords(event: MouseEvent, rowContainer: HTMLElement, charMeasure: CharMeasure, colCount: number, rowCount: number, isSelection?: boolean): [number, number] {
|
||||||
|
// Coordinates cannot be measured if charMeasure has not been initialized
|
||||||
|
if (!charMeasure.width || !charMeasure.height) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const coords = getCoordsRelativeToElement(event, rowContainer);
|
const coords = getCoordsRelativeToElement(event, rowContainer);
|
||||||
|
if (!coords) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// Convert to cols/rows.
|
// Convert to cols/rows.
|
||||||
coords[0] = Math.ceil((coords[0] + (isSelection ? charMeasure.width / 2 : 0)) / charMeasure.width);
|
coords[0] = Math.ceil((coords[0] + (isSelection ? charMeasure.width / 2 : 0)) / charMeasure.width);
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import { ITerminal, IBuffer, IBufferSet, IBrowser, ICharMeasure, ISelectionManager } from '../Interfaces';
|
||||||
|
|
||||||
|
export class MockTerminal implements ITerminal {
|
||||||
|
public element: HTMLElement;
|
||||||
|
public rowContainer: HTMLElement;
|
||||||
|
public selectionContainer: HTMLElement;
|
||||||
|
public selectionManager: ISelectionManager;
|
||||||
|
public charMeasure: ICharMeasure;
|
||||||
|
public textarea: HTMLTextAreaElement;
|
||||||
|
public rows: number;
|
||||||
|
public cols: number;
|
||||||
|
public browser: IBrowser;
|
||||||
|
public writeBuffer: string[];
|
||||||
|
public children: HTMLElement[];
|
||||||
|
public cursorHidden: boolean;
|
||||||
|
public cursorState: number;
|
||||||
|
public defAttr: number;
|
||||||
|
public scrollback: number;
|
||||||
|
public buffers: IBufferSet;
|
||||||
|
public buffer: IBuffer;
|
||||||
|
|
||||||
|
handler(data: string) {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
on(event: string, callback: () => void) {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
scrollDisp(disp: number, suppressScrollEvent: boolean) {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
cancel(ev: Event, force?: boolean) {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
log(text: string): void {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
emit(event: string, data: any) {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
reset(): void {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
showCursor(): void {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
blankLine(cur?: boolean, isWrapped?: boolean) {
|
||||||
|
const line = [];
|
||||||
|
for (let i = 0; i < this.cols; i++) {
|
||||||
|
line.push([0, ' ', 1]);
|
||||||
|
}
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
}
|
||||||
+17
-26
@@ -89,53 +89,40 @@
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.terminal.focus:not(.xterm-cursor-style-underline):not(.xterm-cursor-style-bar) .terminal-cursor {
|
.terminal .terminal-cursor {
|
||||||
background-color: #fff;
|
position: relative;
|
||||||
color: #000;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.terminal:not(.focus) .terminal-cursor {
|
.terminal:not(.focus) .terminal-cursor {
|
||||||
outline: 1px solid #fff;
|
outline: 1px solid #fff;
|
||||||
outline-offset: -1px;
|
outline-offset: -1px;
|
||||||
background-color: transparent;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.terminal:not(.xterm-cursor-style-underline):not(.xterm-cursor-style-bar).focus.xterm-cursor-blink-on .terminal-cursor {
|
.terminal.xterm-cursor-style-block.focus:not(.xterm-cursor-blink-on) .terminal-cursor {
|
||||||
background-color: transparent;
|
background-color: #fff;
|
||||||
color: inherit;
|
color: #000;
|
||||||
}
|
}
|
||||||
|
|
||||||
.terminal.xterm-cursor-style-bar .terminal-cursor,
|
.terminal.focus.xterm-cursor-style-bar:not(.xterm-cursor-blink-on) .terminal-cursor::before,
|
||||||
.terminal.xterm-cursor-style-underline .terminal-cursor {
|
.terminal.focus.xterm-cursor-style-underline:not(.xterm-cursor-blink-on) .terminal-cursor::before {
|
||||||
position: relative;
|
content: '';
|
||||||
}
|
|
||||||
.terminal.xterm-cursor-style-bar .terminal-cursor::before,
|
|
||||||
.terminal.xterm-cursor-style-underline .terminal-cursor::before {
|
|
||||||
content: "";
|
|
||||||
display: block;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
}
|
}
|
||||||
.terminal.xterm-cursor-style-bar .terminal-cursor::before {
|
|
||||||
|
.terminal.focus.xterm-cursor-style-bar:not(.xterm-cursor-blink-on) .terminal-cursor::before {
|
||||||
top: 0;
|
top: 0;
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
width: 1px;
|
width: 1px;
|
||||||
}
|
}
|
||||||
.terminal.xterm-cursor-style-underline .terminal-cursor::before {
|
|
||||||
|
.terminal.focus.xterm-cursor-style-underline:not(.xterm-cursor-blink-on) .terminal-cursor::before {
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
height: 1px;
|
height: 1px;
|
||||||
}
|
}
|
||||||
.terminal.xterm-cursor-style-bar.focus.xterm-cursor-blink.xterm-cursor-blink-on .terminal-cursor::before,
|
|
||||||
.terminal.xterm-cursor-style-underline.focus.xterm-cursor-blink.xterm-cursor-blink-on .terminal-cursor::before {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
.terminal.xterm-cursor-style-bar.focus.xterm-cursor-blink .terminal-cursor::before,
|
|
||||||
.terminal.xterm-cursor-style-underline.focus.xterm-cursor-blink .terminal-cursor::before {
|
|
||||||
background-color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.terminal .composition-view {
|
.terminal .composition-view {
|
||||||
background: #000;
|
background: #000;
|
||||||
@@ -217,6 +204,10 @@
|
|||||||
text-decoration: blink;
|
text-decoration: blink;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.terminal .xterm-blink.xterm-underline {
|
||||||
|
text-decoration: blink underline;
|
||||||
|
}
|
||||||
|
|
||||||
.terminal .xterm-hidden {
|
.terminal .xterm-hidden {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
|
|||||||
+128
-204
@@ -10,6 +10,7 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { BufferSet } from './BufferSet';
|
||||||
import { CompositionHelper } from './CompositionHelper';
|
import { CompositionHelper } from './CompositionHelper';
|
||||||
import { EventEmitter } from './EventEmitter';
|
import { EventEmitter } from './EventEmitter';
|
||||||
import { Viewport } from './Viewport';
|
import { Viewport } from './Viewport';
|
||||||
@@ -26,6 +27,7 @@ import * as Browser from './utils/Browser';
|
|||||||
import * as Mouse from './utils/Mouse';
|
import * as Mouse from './utils/Mouse';
|
||||||
import { CHARSETS } from './Charsets';
|
import { CHARSETS } from './Charsets';
|
||||||
import { getRawByteCoords } from './utils/Mouse';
|
import { getRawByteCoords } from './utils/Mouse';
|
||||||
|
import { translateBufferLineToString } from './utils/BufferLine';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Terminal Emulation References:
|
* Terminal Emulation References:
|
||||||
@@ -140,33 +142,10 @@ function Terminal(options) {
|
|||||||
this.on('data', options.handler);
|
this.on('data', options.handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* The scroll position of the y cursor, ie. ybase + y = the y position within the entire
|
|
||||||
* buffer
|
|
||||||
*/
|
|
||||||
this.ybase = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The scroll position of the viewport
|
|
||||||
*/
|
|
||||||
this.ydisp = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The cursor's x position after ybase
|
|
||||||
*/
|
|
||||||
this.x = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The cursor's y position after ybase
|
|
||||||
*/
|
|
||||||
this.y = 0;
|
|
||||||
|
|
||||||
this.cursorState = 0;
|
this.cursorState = 0;
|
||||||
this.cursorHidden = false;
|
this.cursorHidden = false;
|
||||||
this.convertEol;
|
this.convertEol;
|
||||||
this.queue = '';
|
this.queue = '';
|
||||||
this.scrollTop = 0;
|
|
||||||
this.scrollBottom = this.rows - 1;
|
|
||||||
this.customKeyEventHandler = null;
|
this.customKeyEventHandler = null;
|
||||||
this.cursorBlinkInterval = null;
|
this.cursorBlinkInterval = null;
|
||||||
|
|
||||||
@@ -176,7 +155,6 @@ function Terminal(options) {
|
|||||||
this.originMode = false;
|
this.originMode = false;
|
||||||
this.insertMode = false;
|
this.insertMode = false;
|
||||||
this.wraparoundMode = true; // defaults: xterm - true, vt100 - false
|
this.wraparoundMode = true; // defaults: xterm - true, vt100 - false
|
||||||
this.normal = null;
|
|
||||||
|
|
||||||
// charset
|
// charset
|
||||||
this.charset = null;
|
this.charset = null;
|
||||||
@@ -242,21 +220,18 @@ function Terminal(options) {
|
|||||||
// leftover surrogate high from previous write invocation
|
// leftover surrogate high from previous write invocation
|
||||||
this.surrogate_high = '';
|
this.surrogate_high = '';
|
||||||
|
|
||||||
/**
|
// Create the terminal's buffers and set the current buffer
|
||||||
* An array of all lines in the entire buffer, including the prompt. The lines are array of
|
this.buffers = new BufferSet(this);
|
||||||
* characters which are 2-length arrays where [0] is an attribute and [1] is the character.
|
this.buffer = this.buffers.active; // Convenience shortcut;
|
||||||
*/
|
this.buffers.on('activate', function (buffer) {
|
||||||
this.lines = new CircularList(this.scrollback);
|
this._terminal.buffer = buffer;
|
||||||
var i = this.rows;
|
});
|
||||||
while (i--) {
|
|
||||||
this.lines.push(this.blankLine());
|
|
||||||
}
|
|
||||||
// Ensure the selection manager has the correct buffer
|
// Ensure the selection manager has the correct buffer
|
||||||
if (this.selectionManager) {
|
if (this.selectionManager) {
|
||||||
this.selectionManager.setBuffer(this.lines);
|
this.selectionManager.setBuffer(this.buffer.lines);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.tabs;
|
|
||||||
this.setupStops();
|
this.setupStops();
|
||||||
|
|
||||||
// Store if user went browsing history in scrollback
|
// Store if user went browsing history in scrollback
|
||||||
@@ -396,7 +371,7 @@ Terminal.prototype.focus = function() {
|
|||||||
* Retrieves an option's value from the terminal.
|
* Retrieves an option's value from the terminal.
|
||||||
* @param {string} key The option key.
|
* @param {string} key The option key.
|
||||||
*/
|
*/
|
||||||
Terminal.prototype.getOption = function(key, value) {
|
Terminal.prototype.getOption = function(key) {
|
||||||
if (!(key in Terminal.defaults)) {
|
if (!(key in Terminal.defaults)) {
|
||||||
throw new Error('No option with key "' + key + '"');
|
throw new Error('No option with key "' + key + '"');
|
||||||
}
|
}
|
||||||
@@ -429,17 +404,17 @@ Terminal.prototype.setOption = function(key, value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.options[key] !== value) {
|
if (this.options[key] !== value) {
|
||||||
if (this.lines.length > value) {
|
if (this.buffer.lines.length > value) {
|
||||||
const amountToTrim = this.lines.length - value;
|
const amountToTrim = this.buffer.lines.length - value;
|
||||||
const needsRefresh = (this.ydisp - amountToTrim < 0);
|
const needsRefresh = (this.buffer.ydisp - amountToTrim < 0);
|
||||||
this.lines.trimStart(amountToTrim);
|
this.buffer.lines.trimStart(amountToTrim);
|
||||||
this.ybase = Math.max(this.ybase - amountToTrim, 0);
|
this.buffer.ybase = Math.max(this.buffer.ybase - amountToTrim, 0);
|
||||||
this.ydisp = Math.max(this.ydisp - amountToTrim, 0);
|
this.buffer.ydisp = Math.max(this.buffer.ydisp - amountToTrim, 0);
|
||||||
if (needsRefresh) {
|
if (needsRefresh) {
|
||||||
this.refresh(0, this.rows - 1);
|
this.refresh(0, this.rows - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.lines.maxLength = value;
|
this.buffer.lines.maxLength = value;
|
||||||
this.viewport.syncScrollArea();
|
this.viewport.syncScrollArea();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -449,7 +424,7 @@ Terminal.prototype.setOption = function(key, value) {
|
|||||||
switch (key) {
|
switch (key) {
|
||||||
case 'cursorBlink': this.setCursorBlinking(value); break;
|
case 'cursorBlink': this.setCursorBlinking(value); break;
|
||||||
case 'cursorStyle':
|
case 'cursorStyle':
|
||||||
// Style 'block' applies with no class
|
this.element.classList.toggle(`xterm-cursor-style-block`, value === 'block');
|
||||||
this.element.classList.toggle(`xterm-cursor-style-underline`, value === 'underline');
|
this.element.classList.toggle(`xterm-cursor-style-underline`, value === 'underline');
|
||||||
this.element.classList.toggle(`xterm-cursor-style-bar`, value === 'bar');
|
this.element.classList.toggle(`xterm-cursor-style-bar`, value === 'bar');
|
||||||
break;
|
break;
|
||||||
@@ -512,7 +487,7 @@ Terminal.prototype.blur = function() {
|
|||||||
*/
|
*/
|
||||||
Terminal.bindBlur = function (term) {
|
Terminal.bindBlur = function (term) {
|
||||||
on(term.textarea, 'blur', function (ev) {
|
on(term.textarea, 'blur', function (ev) {
|
||||||
term.refresh(term.y, term.y);
|
term.refresh(term.buffer.y, term.buffer.y);
|
||||||
if (term.sendFocus) {
|
if (term.sendFocus) {
|
||||||
term.send(C0.ESC + '[O');
|
term.send(C0.ESC + '[O');
|
||||||
}
|
}
|
||||||
@@ -537,7 +512,7 @@ Terminal.prototype.initGlobal = function() {
|
|||||||
on(this.element, 'copy', event => {
|
on(this.element, 'copy', event => {
|
||||||
// If mouse events are active it means the selection manager is disabled and
|
// If mouse events are active it means the selection manager is disabled and
|
||||||
// copy should be handled by the host program.
|
// copy should be handled by the host program.
|
||||||
if (this.mouseEvents) {
|
if (!term.hasSelection()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
copyHandler(event, term, this.selectionManager);
|
copyHandler(event, term, this.selectionManager);
|
||||||
@@ -659,6 +634,7 @@ Terminal.prototype.open = function(parent, focus) {
|
|||||||
this.element.classList.add('terminal');
|
this.element.classList.add('terminal');
|
||||||
this.element.classList.add('xterm');
|
this.element.classList.add('xterm');
|
||||||
this.element.classList.add('xterm-theme-' + this.theme);
|
this.element.classList.add('xterm-theme-' + this.theme);
|
||||||
|
this.element.classList.add(`xterm-cursor-style-${this.options.cursorStyle}`);
|
||||||
this.setCursorBlinking(this.options.cursorBlink);
|
this.setCursorBlinking(this.options.cursorBlink);
|
||||||
|
|
||||||
this.element.setAttribute('tabindex', 0);
|
this.element.setAttribute('tabindex', 0);
|
||||||
@@ -724,7 +700,9 @@ Terminal.prototype.open = function(parent, focus) {
|
|||||||
|
|
||||||
this.viewport = new Viewport(this, this.viewportElement, this.viewportScrollArea, this.charMeasure);
|
this.viewport = new Viewport(this, this.viewportElement, this.viewportScrollArea, this.charMeasure);
|
||||||
this.renderer = new Renderer(this);
|
this.renderer = new Renderer(this);
|
||||||
this.selectionManager = new SelectionManager(this, this.lines, this.rowContainer, this.charMeasure);
|
this.selectionManager = new SelectionManager(
|
||||||
|
this, this.buffer.lines, this.rowContainer, this.charMeasure
|
||||||
|
);
|
||||||
this.selectionManager.on('refresh', data => {
|
this.selectionManager.on('refresh', data => {
|
||||||
this.renderer.refreshSelection(data.start, data.end);
|
this.renderer.refreshSelection(data.start, data.end);
|
||||||
});
|
});
|
||||||
@@ -764,15 +742,6 @@ Terminal.prototype.open = function(parent, focus) {
|
|||||||
this.focus();
|
this.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
on(this.element, 'click', function() {
|
|
||||||
var selection = document.getSelection(),
|
|
||||||
collapsed = selection.isCollapsed,
|
|
||||||
isRange = typeof collapsed == 'boolean' ? !collapsed : selection.type == 'Range';
|
|
||||||
if (!isRange) {
|
|
||||||
self.focus();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Listen for mouse events and translate
|
// Listen for mouse events and translate
|
||||||
// them into terminal mouse protocols.
|
// them into terminal mouse protocols.
|
||||||
this.bindMouse();
|
this.bindMouse();
|
||||||
@@ -1043,14 +1012,17 @@ Terminal.prototype.bindMouse = function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
on(el, 'mousedown', function(ev) {
|
on(el, 'mousedown', function(ev) {
|
||||||
|
|
||||||
|
// Prevent the focus on the textarea from getting lost
|
||||||
|
// and make sure we get focused on mousedown
|
||||||
|
ev.preventDefault();
|
||||||
|
self.focus();
|
||||||
|
|
||||||
if (!self.mouseEvents) return;
|
if (!self.mouseEvents) return;
|
||||||
|
|
||||||
// send the button
|
// send the button
|
||||||
sendButton(ev);
|
sendButton(ev);
|
||||||
|
|
||||||
// ensure focus
|
|
||||||
self.focus();
|
|
||||||
|
|
||||||
// fix for odd bug
|
// fix for odd bug
|
||||||
//if (self.vt200Mouse && !self.normalMouse) {
|
//if (self.vt200Mouse && !self.normalMouse) {
|
||||||
if (self.vt200Mouse) {
|
if (self.vt200Mouse) {
|
||||||
@@ -1155,7 +1127,7 @@ Terminal.prototype.queueLinkification = function(start, end) {
|
|||||||
Terminal.prototype.showCursor = function() {
|
Terminal.prototype.showCursor = function() {
|
||||||
if (!this.cursorState) {
|
if (!this.cursorState) {
|
||||||
this.cursorState = 1;
|
this.cursorState = 1;
|
||||||
this.refresh(this.y, this.y);
|
this.refresh(this.buffer.y, this.buffer.y);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1168,48 +1140,48 @@ Terminal.prototype.scroll = function(isWrapped) {
|
|||||||
var row;
|
var row;
|
||||||
|
|
||||||
// Make room for the new row in lines
|
// Make room for the new row in lines
|
||||||
if (this.lines.length === this.lines.maxLength) {
|
if (this.buffer.lines.length === this.buffer.lines.maxLength) {
|
||||||
this.lines.trimStart(1);
|
this.buffer.lines.trimStart(1);
|
||||||
this.ybase--;
|
this.buffer.ybase--;
|
||||||
if (this.ydisp !== 0) {
|
if (this.buffer.ydisp !== 0) {
|
||||||
this.ydisp--;
|
this.buffer.ydisp--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.ybase++;
|
this.buffer.ybase++;
|
||||||
|
|
||||||
// TODO: Why is this done twice?
|
// TODO: Why is this done twice?
|
||||||
if (!this.userScrolling) {
|
if (!this.userScrolling) {
|
||||||
this.ydisp = this.ybase;
|
this.buffer.ydisp = this.buffer.ybase;
|
||||||
}
|
}
|
||||||
|
|
||||||
// last line
|
// last line
|
||||||
row = this.ybase + this.rows - 1;
|
row = this.buffer.ybase + this.rows - 1;
|
||||||
|
|
||||||
// subtract the bottom scroll region
|
// subtract the bottom scroll region
|
||||||
row -= this.rows - 1 - this.scrollBottom;
|
row -= this.rows - 1 - this.buffer.scrollBottom;
|
||||||
|
|
||||||
if (row === this.lines.length) {
|
if (row === this.buffer.lines.length) {
|
||||||
// Optimization: pushing is faster than splicing when they amount to the same behavior
|
// Optimization: pushing is faster than splicing when they amount to the same behavior
|
||||||
this.lines.push(this.blankLine(undefined, isWrapped));
|
this.buffer.lines.push(this.blankLine(undefined, isWrapped));
|
||||||
} else {
|
} else {
|
||||||
// add our new line
|
// add our new line
|
||||||
this.lines.splice(row, 0, this.blankLine(undefined, isWrapped));
|
this.buffer.lines.splice(row, 0, this.blankLine(undefined, isWrapped));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.scrollTop !== 0) {
|
if (this.buffer.scrollTop !== 0) {
|
||||||
if (this.ybase !== 0) {
|
if (this.buffer.ybase !== 0) {
|
||||||
this.ybase--;
|
this.buffer.ybase--;
|
||||||
if (!this.userScrolling) {
|
if (!this.userScrolling) {
|
||||||
this.ydisp = this.ybase;
|
this.buffer.ydisp = this.buffer.ybase;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.lines.splice(this.ybase + this.scrollTop, 1);
|
this.buffer.lines.splice(this.buffer.ybase + this.buffer.scrollTop, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// this.maxRange();
|
// this.maxRange();
|
||||||
this.updateRange(this.scrollTop);
|
this.updateRange(this.buffer.scrollTop);
|
||||||
this.updateRange(this.scrollBottom);
|
this.updateRange(this.buffer.scrollBottom);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This event is emitted whenever the terminal is scrolled.
|
* This event is emitted whenever the terminal is scrolled.
|
||||||
@@ -1217,7 +1189,7 @@ Terminal.prototype.scroll = function(isWrapped) {
|
|||||||
*
|
*
|
||||||
* @event scroll
|
* @event scroll
|
||||||
*/
|
*/
|
||||||
this.emit('scroll', this.ydisp);
|
this.emit('scroll', this.buffer.ydisp);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1229,24 +1201,24 @@ Terminal.prototype.scroll = function(isWrapped) {
|
|||||||
*/
|
*/
|
||||||
Terminal.prototype.scrollDisp = function(disp, suppressScrollEvent) {
|
Terminal.prototype.scrollDisp = function(disp, suppressScrollEvent) {
|
||||||
if (disp < 0) {
|
if (disp < 0) {
|
||||||
if (this.ydisp === 0) {
|
if (this.buffer.ydisp === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.userScrolling = true;
|
this.userScrolling = true;
|
||||||
} else if (disp + this.ydisp >= this.ybase) {
|
} else if (disp + this.buffer.ydisp >= this.buffer.ybase) {
|
||||||
this.userScrolling = false;
|
this.userScrolling = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.ydisp += disp;
|
const oldYdisp = this.buffer.ydisp;
|
||||||
|
this.buffer.ydisp = Math.max(Math.min(this.buffer.ydisp + disp, this.buffer.ybase), 0);
|
||||||
|
|
||||||
if (this.ydisp > this.ybase) {
|
// No change occurred, don't trigger scroll/refresh
|
||||||
this.ydisp = this.ybase;
|
if (oldYdisp === this.buffer.ydisp) {
|
||||||
} else if (this.ydisp < 0) {
|
return;
|
||||||
this.ydisp = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!suppressScrollEvent) {
|
if (!suppressScrollEvent) {
|
||||||
this.emit('scroll', this.ydisp);
|
this.emit('scroll', this.buffer.ydisp);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.refresh(0, this.rows - 1);
|
this.refresh(0, this.rows - 1);
|
||||||
@@ -1264,14 +1236,14 @@ Terminal.prototype.scrollPages = function(pageCount) {
|
|||||||
* Scrolls the display of the terminal to the top.
|
* Scrolls the display of the terminal to the top.
|
||||||
*/
|
*/
|
||||||
Terminal.prototype.scrollToTop = function() {
|
Terminal.prototype.scrollToTop = function() {
|
||||||
this.scrollDisp(-this.ydisp);
|
this.scrollDisp(-this.buffer.ydisp);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Scrolls the display of the terminal to the bottom.
|
* Scrolls the display of the terminal to the bottom.
|
||||||
*/
|
*/
|
||||||
Terminal.prototype.scrollToBottom = function() {
|
Terminal.prototype.scrollToBottom = function() {
|
||||||
this.scrollDisp(this.ybase - this.ydisp);
|
this.scrollDisp(this.buffer.ybase - this.buffer.ydisp);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1315,8 +1287,8 @@ Terminal.prototype.innerWrite = function() {
|
|||||||
this.xoffSentToCatchUp = false;
|
this.xoffSentToCatchUp = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.refreshStart = this.y;
|
this.refreshStart = this.buffer.y;
|
||||||
this.refreshEnd = this.y;
|
this.refreshEnd = this.buffer.y;
|
||||||
|
|
||||||
// HACK: Set the parser state based on it's state at the time of return.
|
// HACK: Set the parser state based on it's state at the time of return.
|
||||||
// This works around the bug #662 which saw the parser state reset in the
|
// This works around the bug #662 which saw the parser state reset in the
|
||||||
@@ -1326,7 +1298,7 @@ Terminal.prototype.innerWrite = function() {
|
|||||||
var state = this.parser.parse(data);
|
var state = this.parser.parse(data);
|
||||||
this.parser.setState(state);
|
this.parser.setState(state);
|
||||||
|
|
||||||
this.updateRange(this.y);
|
this.updateRange(this.buffer.y);
|
||||||
this.refresh(this.refreshStart, this.refreshEnd);
|
this.refresh(this.refreshStart, this.refreshEnd);
|
||||||
}
|
}
|
||||||
if (this.writeBuffer.length > 0) {
|
if (this.writeBuffer.length > 0) {
|
||||||
@@ -1436,7 +1408,7 @@ Terminal.prototype.deregisterLinkMatcher = function(matcherId) {
|
|||||||
* Gets whether the terminal has an active selection.
|
* Gets whether the terminal has an active selection.
|
||||||
*/
|
*/
|
||||||
Terminal.prototype.hasSelection = function() {
|
Terminal.prototype.hasSelection = function() {
|
||||||
return this.selectionManager.hasSelection;
|
return this.selectionManager ? this.selectionManager.hasSelection : false;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1444,21 +1416,25 @@ Terminal.prototype.hasSelection = function() {
|
|||||||
* behavior outside of xterm.js.
|
* behavior outside of xterm.js.
|
||||||
*/
|
*/
|
||||||
Terminal.prototype.getSelection = function() {
|
Terminal.prototype.getSelection = function() {
|
||||||
return this.selectionManager.selectionText;
|
return this.selectionManager ? this.selectionManager.selectionText : '';
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clears the current terminal selection.
|
* Clears the current terminal selection.
|
||||||
*/
|
*/
|
||||||
Terminal.prototype.clearSelection = function() {
|
Terminal.prototype.clearSelection = function() {
|
||||||
this.selectionManager.clearSelection();
|
if (this.selectionManager) {
|
||||||
|
this.selectionManager.clearSelection();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Selects all text within the terminal.
|
* Selects all text within the terminal.
|
||||||
*/
|
*/
|
||||||
Terminal.prototype.selectAll = function() {
|
Terminal.prototype.selectAll = function() {
|
||||||
this.selectionManager.selectAll();
|
if (this.selectionManager) {
|
||||||
|
this.selectionManager.selectAll();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1475,7 +1451,7 @@ Terminal.prototype.keyDown = function(ev) {
|
|||||||
this.restartCursorBlinking();
|
this.restartCursorBlinking();
|
||||||
|
|
||||||
if (!this.compositionHelper.keydown.bind(this.compositionHelper)(ev)) {
|
if (!this.compositionHelper.keydown.bind(this.compositionHelper)(ev)) {
|
||||||
if (this.ybase !== this.ydisp) {
|
if (this.buffer.ybase !== this.buffer.ydisp) {
|
||||||
this.scrollToBottom();
|
this.scrollToBottom();
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -1939,96 +1915,36 @@ Terminal.prototype.resize = function(x, y) {
|
|||||||
, addToY;
|
, addToY;
|
||||||
|
|
||||||
if (x === this.cols && y === this.rows) {
|
if (x === this.cols && y === this.rows) {
|
||||||
|
// Check if we still need to measure the char size (fixes #785).
|
||||||
|
if (!this.charMeasure.width || !this.charMeasure.height) {
|
||||||
|
this.charMeasure.measure();
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (x < 1) x = 1;
|
if (x < 1) x = 1;
|
||||||
if (y < 1) y = 1;
|
if (y < 1) y = 1;
|
||||||
|
|
||||||
// resize cols
|
this.buffers.resize(x, y);
|
||||||
j = this.cols;
|
|
||||||
if (j < x) {
|
// Adjust rows in the DOM to accurately reflect the new dimensions
|
||||||
ch = [this.defAttr, ' ', 1]; // does xterm use the default attr?
|
while (this.children.length < y) {
|
||||||
i = this.lines.length;
|
this.insertRow();
|
||||||
while (i--) {
|
}
|
||||||
while (this.lines.get(i).length < x) {
|
while (this.children.length > y) {
|
||||||
this.lines.get(i).push(ch);
|
el = this.children.shift();
|
||||||
}
|
if (!el) continue;
|
||||||
}
|
el.parentNode.removeChild(el);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.cols = x;
|
this.cols = x;
|
||||||
this.setupStops(this.cols);
|
|
||||||
|
|
||||||
// resize rows
|
|
||||||
j = this.rows;
|
|
||||||
addToY = 0;
|
|
||||||
if (j < y) {
|
|
||||||
el = this.element;
|
|
||||||
while (j++ < y) {
|
|
||||||
// y is rows, not this.y
|
|
||||||
if (this.lines.length < y + this.ybase) {
|
|
||||||
if (this.ybase > 0 && this.lines.length <= this.ybase + this.y + addToY + 1) {
|
|
||||||
// There is room above the buffer and there are no empty elements below the line,
|
|
||||||
// scroll up
|
|
||||||
this.ybase--;
|
|
||||||
addToY++;
|
|
||||||
if (this.ydisp > 0) {
|
|
||||||
// Viewport is at the top of the buffer, must increase downwards
|
|
||||||
this.ydisp--;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Add a blank line if there is no buffer left at the top to scroll to, or if there
|
|
||||||
// are blank lines after the cursor
|
|
||||||
this.lines.push(this.blankLine());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (this.children.length < y) {
|
|
||||||
this.insertRow();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else { // (j > y)
|
|
||||||
while (j-- > y) {
|
|
||||||
if (this.lines.length > y + this.ybase) {
|
|
||||||
if (this.lines.length > this.ybase + this.y + 1) {
|
|
||||||
// The line is a blank line below the cursor, remove it
|
|
||||||
this.lines.pop();
|
|
||||||
} else {
|
|
||||||
// The line is the cursor, scroll down
|
|
||||||
this.ybase++;
|
|
||||||
this.ydisp++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (this.children.length > y) {
|
|
||||||
el = this.children.shift();
|
|
||||||
if (!el) continue;
|
|
||||||
el.parentNode.removeChild(el);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.rows = y;
|
this.rows = y;
|
||||||
|
this.setupStops(this.cols);
|
||||||
// Make sure that the cursor stays on screen
|
|
||||||
if (this.y >= y) {
|
|
||||||
this.y = y - 1;
|
|
||||||
}
|
|
||||||
if (addToY) {
|
|
||||||
this.y += addToY;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.x >= x) {
|
|
||||||
this.x = x - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.scrollTop = 0;
|
|
||||||
this.scrollBottom = y - 1;
|
|
||||||
|
|
||||||
this.charMeasure.measure();
|
this.charMeasure.measure();
|
||||||
|
|
||||||
this.refresh(0, this.rows - 1);
|
this.refresh(0, this.rows - 1);
|
||||||
|
|
||||||
this.normal = null;
|
|
||||||
|
|
||||||
this.geometry = [this.cols, this.rows];
|
this.geometry = [this.cols, this.rows];
|
||||||
this.emit('resize', {terminal: this, cols: x, rows: y});
|
this.emit('resize', {terminal: this, cols: x, rows: y});
|
||||||
};
|
};
|
||||||
@@ -2064,16 +1980,16 @@ Terminal.prototype.maxRange = function() {
|
|||||||
*/
|
*/
|
||||||
Terminal.prototype.setupStops = function(i) {
|
Terminal.prototype.setupStops = function(i) {
|
||||||
if (i != null) {
|
if (i != null) {
|
||||||
if (!this.tabs[i]) {
|
if (!this.buffer.tabs[i]) {
|
||||||
i = this.prevStop(i);
|
i = this.prevStop(i);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.tabs = {};
|
this.buffer.tabs = {};
|
||||||
i = 0;
|
i = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (; i < this.cols; i += this.getOption('tabStopWidth')) {
|
for (; i < this.cols; i += this.getOption('tabStopWidth')) {
|
||||||
this.tabs[i] = true;
|
this.buffer.tabs[i] = true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -2083,8 +1999,8 @@ Terminal.prototype.setupStops = function(i) {
|
|||||||
* @param {number} x The position to move the cursor to the previous tab stop.
|
* @param {number} x The position to move the cursor to the previous tab stop.
|
||||||
*/
|
*/
|
||||||
Terminal.prototype.prevStop = function(x) {
|
Terminal.prototype.prevStop = function(x) {
|
||||||
if (x == null) x = this.x;
|
if (x == null) x = this.buffer.x;
|
||||||
while (!this.tabs[--x] && x > 0);
|
while (!this.buffer.tabs[--x] && x > 0);
|
||||||
return x >= this.cols
|
return x >= this.cols
|
||||||
? this.cols - 1
|
? this.cols - 1
|
||||||
: x < 0 ? 0 : x;
|
: x < 0 ? 0 : x;
|
||||||
@@ -2096,8 +2012,8 @@ Terminal.prototype.prevStop = function(x) {
|
|||||||
* @param {number} x The position to move the cursor one tab stop forward.
|
* @param {number} x The position to move the cursor one tab stop forward.
|
||||||
*/
|
*/
|
||||||
Terminal.prototype.nextStop = function(x) {
|
Terminal.prototype.nextStop = function(x) {
|
||||||
if (x == null) x = this.x;
|
if (x == null) x = this.buffer.x;
|
||||||
while (!this.tabs[++x] && x < this.cols);
|
while (!this.buffer.tabs[++x] && x < this.cols);
|
||||||
return x >= this.cols
|
return x >= this.cols
|
||||||
? this.cols - 1
|
? this.cols - 1
|
||||||
: x < 0 ? 0 : x;
|
: x < 0 ? 0 : x;
|
||||||
@@ -2110,7 +2026,7 @@ Terminal.prototype.nextStop = function(x) {
|
|||||||
* @param {number} y The line in which to operate.
|
* @param {number} y The line in which to operate.
|
||||||
*/
|
*/
|
||||||
Terminal.prototype.eraseRight = function(x, y) {
|
Terminal.prototype.eraseRight = function(x, y) {
|
||||||
var line = this.lines.get(this.ybase + y);
|
var line = this.buffer.lines.get(this.buffer.ybase + y);
|
||||||
if (!line) {
|
if (!line) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -2129,7 +2045,7 @@ Terminal.prototype.eraseRight = function(x, y) {
|
|||||||
* @param {number} y The line in which to operate.
|
* @param {number} y The line in which to operate.
|
||||||
*/
|
*/
|
||||||
Terminal.prototype.eraseLeft = function(x, y) {
|
Terminal.prototype.eraseLeft = function(x, y) {
|
||||||
var line = this.lines.get(this.ybase + y);
|
var line = this.buffer.lines.get(this.buffer.ybase + y);
|
||||||
if (!line) {
|
if (!line) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -2145,20 +2061,20 @@ Terminal.prototype.eraseLeft = function(x, y) {
|
|||||||
* Clears the entire buffer, making the prompt line the new first line.
|
* Clears the entire buffer, making the prompt line the new first line.
|
||||||
*/
|
*/
|
||||||
Terminal.prototype.clear = function() {
|
Terminal.prototype.clear = function() {
|
||||||
if (this.ybase === 0 && this.y === 0) {
|
if (this.buffer.ybase === 0 && this.buffer.y === 0) {
|
||||||
// Don't clear if it's already clear
|
// Don't clear if it's already clear
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.lines.set(0, this.lines.get(this.ybase + this.y));
|
this.buffer.lines.set(0, this.buffer.lines.get(this.buffer.ybase + this.buffer.y));
|
||||||
this.lines.length = 1;
|
this.buffer.lines.length = 1;
|
||||||
this.ydisp = 0;
|
this.buffer.ydisp = 0;
|
||||||
this.ybase = 0;
|
this.buffer.ybase = 0;
|
||||||
this.y = 0;
|
this.buffer.y = 0;
|
||||||
for (var i = 1; i < this.rows; i++) {
|
for (var i = 1; i < this.rows; i++) {
|
||||||
this.lines.push(this.blankLine());
|
this.buffer.lines.push(this.blankLine());
|
||||||
}
|
}
|
||||||
this.refresh(0, this.rows - 1);
|
this.refresh(0, this.rows - 1);
|
||||||
this.emit('scroll', this.ydisp);
|
this.emit('scroll', this.buffer.ydisp);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -2210,7 +2126,7 @@ Terminal.prototype.ch = function(cur) {
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Evaluate if the current erminal is the given argument.
|
* Evaluate if the current terminal is the given argument.
|
||||||
* @param {object} term The terminal to evaluate
|
* @param {object} term The terminal to evaluate
|
||||||
*/
|
*/
|
||||||
Terminal.prototype.is = function(term) {
|
Terminal.prototype.is = function(term) {
|
||||||
@@ -2229,8 +2145,13 @@ Terminal.prototype.handler = function(data) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clear the selection if the selection manager is available and has an active selection
|
||||||
|
if (this.selectionManager && this.selectionManager.hasSelection) {
|
||||||
|
this.selectionManager.clearSelection();
|
||||||
|
}
|
||||||
|
|
||||||
// Input is being sent to the terminal, the terminal should focus the prompt.
|
// Input is being sent to the terminal, the terminal should focus the prompt.
|
||||||
if (this.ybase !== this.ydisp) {
|
if (this.buffer.ybase !== this.buffer.ydisp) {
|
||||||
this.scrollToBottom();
|
this.scrollToBottom();
|
||||||
}
|
}
|
||||||
this.emit('data', data);
|
this.emit('data', data);
|
||||||
@@ -2260,14 +2181,14 @@ Terminal.prototype.handleTitle = function(title) {
|
|||||||
* ESC D Index (IND is 0x84).
|
* ESC D Index (IND is 0x84).
|
||||||
*/
|
*/
|
||||||
Terminal.prototype.index = function() {
|
Terminal.prototype.index = function() {
|
||||||
this.y++;
|
this.buffer.y++;
|
||||||
if (this.y > this.scrollBottom) {
|
if (this.buffer.y > this.buffer.scrollBottom) {
|
||||||
this.y--;
|
this.buffer.y--;
|
||||||
this.scroll();
|
this.scroll();
|
||||||
}
|
}
|
||||||
// If the end of the line is hit, prevent this action from wrapping around to the next line.
|
// If the end of the line is hit, prevent this action from wrapping around to the next line.
|
||||||
if (this.x >= this.cols) {
|
if (this.buffer.x >= this.cols) {
|
||||||
this.x--;
|
this.buffer.x--;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -2279,16 +2200,16 @@ Terminal.prototype.index = function() {
|
|||||||
*/
|
*/
|
||||||
Terminal.prototype.reverseIndex = function() {
|
Terminal.prototype.reverseIndex = function() {
|
||||||
var j;
|
var j;
|
||||||
if (this.y === this.scrollTop) {
|
if (this.buffer.y === this.buffer.scrollTop) {
|
||||||
// possibly move the code below to term.reverseScroll();
|
// possibly move the code below to term.reverseScroll();
|
||||||
// test: echo -ne '\e[1;1H\e[44m\eM\e[0m'
|
// test: echo -ne '\e[1;1H\e[44m\eM\e[0m'
|
||||||
// blankLine(true) is xterm/linux behavior
|
// blankLine(true) is xterm/linux behavior
|
||||||
this.lines.shiftElements(this.y + this.ybase, this.rows - 1, 1);
|
this.buffer.lines.shiftElements(this.buffer.y + this.buffer.ybase, this.rows - 1, 1);
|
||||||
this.lines.set(this.y + this.ybase, this.blankLine(true));
|
this.buffer.lines.set(this.buffer.y + this.buffer.ybase, this.blankLine(true));
|
||||||
this.updateRange(this.scrollTop);
|
this.updateRange(this.buffer.scrollTop);
|
||||||
this.updateRange(this.scrollBottom);
|
this.updateRange(this.buffer.scrollBottom);
|
||||||
} else {
|
} else {
|
||||||
this.y--;
|
this.buffer.y--;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -2301,9 +2222,11 @@ Terminal.prototype.reset = function() {
|
|||||||
this.options.cols = this.cols;
|
this.options.cols = this.cols;
|
||||||
var customKeyEventHandler = this.customKeyEventHandler;
|
var customKeyEventHandler = this.customKeyEventHandler;
|
||||||
var cursorBlinkInterval = this.cursorBlinkInterval;
|
var cursorBlinkInterval = this.cursorBlinkInterval;
|
||||||
|
var inputHandler = this.inputHandler;
|
||||||
Terminal.call(this, this.options);
|
Terminal.call(this, this.options);
|
||||||
this.customKeyEventHandler = customKeyEventHandler;
|
this.customKeyEventHandler = customKeyEventHandler;
|
||||||
this.cursorBlinkInterval = cursorBlinkInterval;
|
this.cursorBlinkInterval = cursorBlinkInterval;
|
||||||
|
this.inputHandler = inputHandler;
|
||||||
this.refresh(0, this.rows - 1);
|
this.refresh(0, this.rows - 1);
|
||||||
this.viewport.syncScrollArea();
|
this.viewport.syncScrollArea();
|
||||||
};
|
};
|
||||||
@@ -2313,7 +2236,7 @@ Terminal.prototype.reset = function() {
|
|||||||
* ESC H Tab Set (HTS is 0x88).
|
* ESC H Tab Set (HTS is 0x88).
|
||||||
*/
|
*/
|
||||||
Terminal.prototype.tabSet = function() {
|
Terminal.prototype.tabSet = function() {
|
||||||
this.tabs[this.x] = true;
|
this.buffer.tabs[this.buffer.x] = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -2449,6 +2372,7 @@ function keys(obj) {
|
|||||||
* Expose
|
* Expose
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
Terminal.translateBufferLineToString = translateBufferLineToString;
|
||||||
Terminal.EventEmitter = EventEmitter;
|
Terminal.EventEmitter = EventEmitter;
|
||||||
Terminal.inherits = inherits;
|
Terminal.inherits = inherits;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user