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
|
||||
build/
|
||||
.vscode/
|
||||
.DS_Store
|
||||
|
||||
# Directories needed for code coverage
|
||||
/coverage/
|
||||
|
||||
+9
-8
@@ -1,18 +1,19 @@
|
||||
language: node_js
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
node_js:
|
||||
- 6
|
||||
before_install:
|
||||
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test ; fi
|
||||
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get -qq update ; fi
|
||||
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get -qq install g++-4.8 ; fi
|
||||
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then export CXX=g++-4.8 ; fi
|
||||
- npm install -g npm@5.1.0
|
||||
env:
|
||||
global:
|
||||
- CXX=g++-4.8
|
||||
matrix:
|
||||
- NPM_COMMAND=lint
|
||||
- NPM_COMMAND=test
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
packages:
|
||||
- g++-4.8
|
||||
notifications:
|
||||
email: false
|
||||
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>
|
||||
Alexey Kontsevoy <alexey@gravitational.com>
|
||||
Anish Athalye <me@anishathalye.com>
|
||||
Anthony Lapenna <lapenna.anthony@gmail.com>
|
||||
Antonis Kalipetis <akalipetis@sourcelair.com>
|
||||
Anton Skshidlevsky <meefik@gmail.com>
|
||||
Anton Yurovskykh <anton.yurovskykh@gmail.com>
|
||||
@@ -21,6 +22,7 @@ Brian Mock <brian@mockbrian.com>
|
||||
Carson Anderson <carson@betterservers.com>
|
||||
CHaBou <chabup@delean.fr>
|
||||
Christian Budde Christensen <budde377@gmail.com>
|
||||
Christof Marti <chrmarti@microsoft.com>
|
||||
Christopher Jeffrey <chjjeffrey@gmail.com>
|
||||
coderaiser <mnemonic.enemy@gmail.com>
|
||||
Daniel Imms <daimms@microsoft.com>
|
||||
@@ -37,14 +39,20 @@ InDieTasten <indietasten@gmail.com>
|
||||
irokas <akasidiari@inf.uth.gr>
|
||||
Jean Bruenn <himself@jeanbruenn.info>
|
||||
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>
|
||||
Justin Mecham <justin@mecham.me>
|
||||
Kirill Merkushev <lanwen@yandex.ru>
|
||||
Lucian Buzzo <lucian.buzzo@gmail.com>
|
||||
Maël Nison <nison.mael@gmail.com>
|
||||
Martin Chloride <i@martincl2.me>
|
||||
Martin Wang <jiahaow@ca.ibm.com>
|
||||
Matt Bierner <matb@microsoft.com>
|
||||
Michael Irwin <mikesir87@gmail.com>
|
||||
Mikko Karvonen <mikko.karvonen@arm.com>
|
||||
mofux <t.zilz@mofux.org>
|
||||
Nicolas Ramz <nicolas.ramz@gmail.com>
|
||||
Oleksandr Andriienko <oandriie@redhat.com>
|
||||
Paris Kasidiaris <pariskasidiaris@gmail.com>
|
||||
@@ -54,7 +62,11 @@ Saswat Das <saswatds@users.noreply.github.com>
|
||||
Saul Costa <saul@codevolve.com>
|
||||
Shuanglei Tao <tsl0922@gmail.com>
|
||||
Steven Silvester <steven.silvester@ieee.org>
|
||||
stuicey <stuicey@users.noreply.github.com>
|
||||
t-amqi <umberos@gmail.com>
|
||||
Thanasis Daglis <thanasis@sourcelair.com>
|
||||
thomas <thomas@kreativkonzentrat.de>
|
||||
Thomas Zilz <t.zilz@mofux.org>
|
||||
Tine Jozelj <tine.jozelj@outlook.com>
|
||||
Tyler Jewell <tjewell@codenvy.com>
|
||||
Vincent Woo <me@vincentwoo.com>
|
||||
|
||||
@@ -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.
|
||||
- [**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.
|
||||
- [**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.
|
||||
- [**Codevolve**](https://www.codevolve.com "Codevolve"): Online platform for interactive coding and web development courses. Live container-backed terminal uses `xterm.js`.
|
||||
- [**RStudio**](https://www.rstudio.com/products/RStudio "RStudio"): RStudio is an integrated development environment (IDE) for R.
|
||||
- [**Terminal for Atom**](https://github.com/jsmecham/atom-terminal-tab): A simple terminal for the Atom text editor.
|
||||
- [**Eclipse Orion**](https://orionhub.org): A modern, open source software development environment that runs in the cloud. Code, deploy and run in the cloud.
|
||||
- [**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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
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)
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "xterm.js",
|
||||
"version": "2.8.0",
|
||||
"version": "2.9.1",
|
||||
"ignore": ["demo", "test", ".gitignore"],
|
||||
"main": [
|
||||
"dist/xterm.js",
|
||||
|
||||
@@ -11,10 +11,18 @@
|
||||
<script src="/build/addons/attach/attach.js" ></script>
|
||||
<script src="/build/addons/fit/fit.js" ></script>
|
||||
<script src="/build/addons/fullscreen/fullscreen.js" ></script>
|
||||
<script src="/build/addons/search/search.js" ></script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>xterm.js: xterm, in the browser</h1>
|
||||
<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>
|
||||
<h2>Options</h2>
|
||||
<p>
|
||||
|
||||
@@ -7,6 +7,10 @@ var term,
|
||||
charHeight;
|
||||
|
||||
var terminalContainer = document.getElementById('terminal-container'),
|
||||
actionElements = {
|
||||
findNext: document.querySelector('#find-next'),
|
||||
findPrevious: document.querySelector('#find-previous')
|
||||
},
|
||||
optionElements = {
|
||||
cursorBlink: document.querySelector('#option-cursor-blink'),
|
||||
cursorStyle: document.querySelector('#option-cursor-style'),
|
||||
@@ -30,6 +34,19 @@ function setTerminalSize () {
|
||||
colsElement.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 () {
|
||||
term.setOption('cursorBlink', optionElements.cursorBlink.checked);
|
||||
});
|
||||
|
||||
Vendored
+17
-26
@@ -89,53 +89,40 @@
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.terminal.focus:not(.xterm-cursor-style-underline):not(.xterm-cursor-style-bar) .terminal-cursor {
|
||||
background-color: #fff;
|
||||
color: #000;
|
||||
.terminal .terminal-cursor {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.terminal:not(.focus) .terminal-cursor {
|
||||
outline: 1px solid #fff;
|
||||
outline-offset: -1px;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.terminal:not(.xterm-cursor-style-underline):not(.xterm-cursor-style-bar).focus.xterm-cursor-blink-on .terminal-cursor {
|
||||
background-color: transparent;
|
||||
color: inherit;
|
||||
.terminal.xterm-cursor-style-block.focus:not(.xterm-cursor-blink-on) .terminal-cursor {
|
||||
background-color: #fff;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.terminal.xterm-cursor-style-bar .terminal-cursor,
|
||||
.terminal.xterm-cursor-style-underline .terminal-cursor {
|
||||
position: relative;
|
||||
}
|
||||
.terminal.xterm-cursor-style-bar .terminal-cursor::before,
|
||||
.terminal.xterm-cursor-style-underline .terminal-cursor::before {
|
||||
content: "";
|
||||
display: block;
|
||||
.terminal.focus.xterm-cursor-style-bar:not(.xterm-cursor-blink-on) .terminal-cursor::before,
|
||||
.terminal.focus.xterm-cursor-style-underline:not(.xterm-cursor-blink-on) .terminal-cursor::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
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;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
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;
|
||||
left: 0;
|
||||
right: 0;
|
||||
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 {
|
||||
background: #000;
|
||||
@@ -217,6 +204,10 @@
|
||||
text-decoration: blink;
|
||||
}
|
||||
|
||||
.terminal .xterm-blink.xterm-underline {
|
||||
text-decoration: blink underline;
|
||||
}
|
||||
|
||||
.terminal .xterm-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 buffer = require('vinyl-buffer');
|
||||
const coveralls = require('gulp-coveralls');
|
||||
@@ -10,13 +14,20 @@ const sorcery = require('sorcery');
|
||||
const source = require('vinyl-source-stream');
|
||||
const sourcemaps = require('gulp-sourcemaps');
|
||||
const ts = require('gulp-typescript');
|
||||
|
||||
const util = require('gulp-util');
|
||||
|
||||
let buildDir = process.env.BUILD_DIR || 'build';
|
||||
let tsProject = ts.createProject('tsconfig.json');
|
||||
let tsProjectSearchAddon = ts.createProject('./src/addons/search/tsconfig.json');
|
||||
let srcDir = tsProject.config.compilerOptions.rootDir;
|
||||
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
|
||||
* file compiled.
|
||||
@@ -30,13 +41,17 @@ gulp.task('tsc', function () {
|
||||
let tsResult = tsProject.src().pipe(sourcemaps.init()).pipe(tsProject());
|
||||
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}/
|
||||
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}/
|
||||
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 = {
|
||||
basedir: buildDir,
|
||||
debug: true,
|
||||
entries: [`../${outDir}/xterm.js`],
|
||||
entries: [`${outDir}/xterm.js`],
|
||||
standalone: 'Terminal',
|
||||
cache: {},
|
||||
packageCache: {}
|
||||
@@ -63,13 +78,38 @@ gulp.task('browserify', ['tsc'], function() {
|
||||
.pipe(sourcemaps.write('./'))
|
||||
.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}/
|
||||
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 () {
|
||||
@@ -87,13 +127,32 @@ gulp.task('mocha', ['instrument-test'], function () {
|
||||
.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.
|
||||
* (Without this task the source maps produced for the JavaScript bundle points into the
|
||||
* compiled JavaScript files in ${outDir}/).
|
||||
*/
|
||||
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.writeSync();
|
||||
});
|
||||
@@ -106,6 +165,6 @@ gulp.task('coveralls', function () {
|
||||
.pipe(coveralls());
|
||||
});
|
||||
|
||||
gulp.task('build', ['sorcery']);
|
||||
gulp.task('build', ['sorcery', 'sorcery-addons']);
|
||||
gulp.task('test', ['mocha']);
|
||||
gulp.task('default', ['build']);
|
||||
|
||||
Generated
+12552
File diff suppressed because it is too large
Load Diff
+6
-6
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "xterm",
|
||||
"description": "Full xterm terminal, in your browser",
|
||||
"version": "2.8.0",
|
||||
"version": "2.9.1",
|
||||
"ignore": [
|
||||
"demo",
|
||||
"test",
|
||||
@@ -36,29 +36,29 @@
|
||||
],
|
||||
"devDependencies": {
|
||||
"@types/chai": "^3.4.34",
|
||||
"@types/jsdom": "^2.0.30",
|
||||
"@types/jsdom": "^11.0.1",
|
||||
"@types/mocha": "^2.2.33",
|
||||
"@types/node": "^6.0.41",
|
||||
"browserify": "^13.1.0",
|
||||
"browserify": "^13.3.0",
|
||||
"chai": "3.5.0",
|
||||
"docdash": "0.4.0",
|
||||
"express": "4.13.4",
|
||||
"express-ws": "2.0.0-rc.1",
|
||||
"fs-extra": "^1.0.0",
|
||||
"glob": "^7.0.5",
|
||||
"gulp": "^3.9.1",
|
||||
"gulp": "3.9.1",
|
||||
"gulp-cli": "^1.2.2",
|
||||
"gulp-coveralls": "^0.1.4",
|
||||
"gulp-istanbul": "^1.1.1",
|
||||
"gulp-mocha": "^3.0.1",
|
||||
"gulp-sourcemaps": "1.9.1",
|
||||
"gulp-typescript": "^3.1.3",
|
||||
"gulp-util": "3.0.8",
|
||||
"jsdoc": "3.4.3",
|
||||
"jsdom": "^9.11.0",
|
||||
"jsdom": "^11.1.0",
|
||||
"merge-stream": "^1.0.1",
|
||||
"node-pty": "^0.4.1",
|
||||
"nodemon": "1.10.2",
|
||||
"sleep": "^3.0.1",
|
||||
"sorcery": "^0.10.0",
|
||||
"tslint": "^4.0.2",
|
||||
"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
|
||||
*/
|
||||
|
||||
import { IEventEmitter } from './Interfaces';
|
||||
|
||||
interface ListenerType {
|
||||
(): void;
|
||||
listener?: () => void;
|
||||
};
|
||||
|
||||
export class EventEmitter {
|
||||
export class EventEmitter implements IEventEmitter {
|
||||
private _events: {[type: string]: ListenerType[]};
|
||||
|
||||
constructor() {
|
||||
|
||||
@@ -1,7 +1,23 @@
|
||||
import { assert } from 'chai';
|
||||
import { InputHandler } from './InputHandler';
|
||||
import { wcwidth } from './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', () => {
|
||||
it('should call Terminal.setOption with correct params', () => {
|
||||
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];
|
||||
}
|
||||
|
||||
let row = this._terminal.y + this._terminal.ybase;
|
||||
let row = this._terminal.buffer.y + this._terminal.buffer.ybase;
|
||||
|
||||
// insert combining char in last cell
|
||||
// FIXME: needs handling after cursor jumps
|
||||
if (!ch_width && this._terminal.x) {
|
||||
if (!ch_width && this._terminal.buffer.x) {
|
||||
// dont overflow left
|
||||
if (this._terminal.lines.get(row)[this._terminal.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]) {
|
||||
if (!this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 1][2]) {
|
||||
|
||||
// found empty cell after fullwidth, need to go 2 cells back
|
||||
if (this._terminal.lines.get(row)[this._terminal.x - 2])
|
||||
this._terminal.lines.get(row)[this._terminal.x - 2][1] += char;
|
||||
if (this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 2])
|
||||
this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 2][1] += char;
|
||||
|
||||
} 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;
|
||||
}
|
||||
|
||||
// goto next line if ch would overflow
|
||||
// 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
|
||||
if (this._terminal.wraparoundMode) {
|
||||
this._terminal.x = 0;
|
||||
this._terminal.y++;
|
||||
if (this._terminal.y > this._terminal.scrollBottom) {
|
||||
this._terminal.y--;
|
||||
this._terminal.buffer.x = 0;
|
||||
this._terminal.buffer.y++;
|
||||
if (this._terminal.buffer.y > this._terminal.buffer.scrollBottom) {
|
||||
this._terminal.buffer.y--;
|
||||
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 {
|
||||
if (ch_width === 2) // FIXME: check for xterm behavior
|
||||
return;
|
||||
}
|
||||
}
|
||||
row = this._terminal.y + this._terminal.ybase;
|
||||
row = this._terminal.buffer.y + this._terminal.buffer.ybase;
|
||||
|
||||
// insert mode: move characters to right
|
||||
if (this._terminal.insertMode) {
|
||||
@@ -72,26 +76,26 @@ export class InputHandler implements IInputHandler {
|
||||
for (let moves = 0; moves < ch_width; ++moves) {
|
||||
// remove last cell, if it's width is 0
|
||||
// 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
|
||||
&& this._terminal.lines.get(row)[this._terminal.cols - 2]
|
||||
&& this._terminal.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.buffer.lines.get(row)[this._terminal.cols - 2][2] === 2) {
|
||||
this._terminal.buffer.lines.get(row)[this._terminal.cols - 2] = [this._terminal.curAttr, ' ', 1];
|
||||
}
|
||||
|
||||
// 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.x++;
|
||||
this._terminal.updateRange(this._terminal.y);
|
||||
this._terminal.buffer.lines.get(row)[this._terminal.buffer.x] = [this._terminal.curAttr, char, ch_width];
|
||||
this._terminal.buffer.x++;
|
||||
this._terminal.updateRange(this._terminal.buffer.y);
|
||||
|
||||
// fullwidth char - set next cell width to zero and advance cursor
|
||||
if (ch_width === 2) {
|
||||
this._terminal.lines.get(row)[this._terminal.x] = [this._terminal.curAttr, '', 0];
|
||||
this._terminal.x++;
|
||||
this._terminal.buffer.lines.get(row)[this._terminal.buffer.x] = [this._terminal.curAttr, '', 0];
|
||||
this._terminal.buffer.x++;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -117,17 +121,23 @@ export class InputHandler implements IInputHandler {
|
||||
*/
|
||||
public lineFeed(): void {
|
||||
if (this._terminal.convertEol) {
|
||||
this._terminal.x = 0;
|
||||
this._terminal.buffer.x = 0;
|
||||
}
|
||||
this._terminal.y++;
|
||||
if (this._terminal.y > this._terminal.scrollBottom) {
|
||||
this._terminal.y--;
|
||||
this._terminal.buffer.y++;
|
||||
if (this._terminal.buffer.y > this._terminal.buffer.scrollBottom) {
|
||||
this._terminal.buffer.y--;
|
||||
this._terminal.scroll();
|
||||
}
|
||||
// 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) {
|
||||
this._terminal.x--;
|
||||
if (this._terminal.buffer.x >= this._terminal.cols) {
|
||||
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).
|
||||
*/
|
||||
public carriageReturn(): void {
|
||||
this._terminal.x = 0;
|
||||
this._terminal.buffer.x = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -143,8 +153,8 @@ export class InputHandler implements IInputHandler {
|
||||
* Backspace (Ctrl-H).
|
||||
*/
|
||||
public backspace(): void {
|
||||
if (this._terminal.x > 0) {
|
||||
this._terminal.x--;
|
||||
if (this._terminal.buffer.x > 0) {
|
||||
this._terminal.buffer.x--;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,7 +163,7 @@ export class InputHandler implements IInputHandler {
|
||||
* Horizontal Tab (HT) (Ctrl-I).
|
||||
*/
|
||||
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];
|
||||
if (param < 1) param = 1;
|
||||
|
||||
row = this._terminal.y + this._terminal.ybase;
|
||||
j = this._terminal.x;
|
||||
row = this._terminal.buffer.y + this._terminal.buffer.ybase;
|
||||
j = this._terminal.buffer.x;
|
||||
ch = [this._terminal.eraseAttr(), ' ', 1]; // xterm
|
||||
|
||||
while (param-- && j < this._terminal.cols) {
|
||||
this._terminal.lines.get(row).splice(j++, 0, ch);
|
||||
this._terminal.lines.get(row).pop();
|
||||
this._terminal.buffer.lines.get(row).splice(j++, 0, ch);
|
||||
this._terminal.buffer.lines.get(row).pop();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,9 +213,9 @@ export class InputHandler implements IInputHandler {
|
||||
if (param < 1) {
|
||||
param = 1;
|
||||
}
|
||||
this._terminal.y -= param;
|
||||
if (this._terminal.y < 0) {
|
||||
this._terminal.y = 0;
|
||||
this._terminal.buffer.y -= param;
|
||||
if (this._terminal.buffer.y < 0) {
|
||||
this._terminal.buffer.y = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,13 +228,13 @@ export class InputHandler implements IInputHandler {
|
||||
if (param < 1) {
|
||||
param = 1;
|
||||
}
|
||||
this._terminal.y += param;
|
||||
if (this._terminal.y >= this._terminal.rows) {
|
||||
this._terminal.y = this._terminal.rows - 1;
|
||||
this._terminal.buffer.y += param;
|
||||
if (this._terminal.buffer.y >= this._terminal.rows) {
|
||||
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 (this._terminal.x >= this._terminal.cols) {
|
||||
this._terminal.x--;
|
||||
if (this._terminal.buffer.x >= this._terminal.cols) {
|
||||
this._terminal.buffer.x--;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,9 +247,9 @@ export class InputHandler implements IInputHandler {
|
||||
if (param < 1) {
|
||||
param = 1;
|
||||
}
|
||||
this._terminal.x += param;
|
||||
if (this._terminal.x >= this._terminal.cols) {
|
||||
this._terminal.x = this._terminal.cols - 1;
|
||||
this._terminal.buffer.x += param;
|
||||
if (this._terminal.buffer.x >= this._terminal.cols) {
|
||||
this._terminal.buffer.x = this._terminal.cols - 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,12 +263,12 @@ export class InputHandler implements IInputHandler {
|
||||
param = 1;
|
||||
}
|
||||
// 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) {
|
||||
this._terminal.x--;
|
||||
if (this._terminal.buffer.x >= this._terminal.cols) {
|
||||
this._terminal.buffer.x--;
|
||||
}
|
||||
this._terminal.x -= param;
|
||||
if (this._terminal.x < 0) {
|
||||
this._terminal.x = 0;
|
||||
this._terminal.buffer.x -= param;
|
||||
if (this._terminal.buffer.x < 0) {
|
||||
this._terminal.buffer.x = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -272,12 +282,12 @@ export class InputHandler implements IInputHandler {
|
||||
if (param < 1) {
|
||||
param = 1;
|
||||
}
|
||||
this._terminal.y += param;
|
||||
if (this._terminal.y >= this._terminal.rows) {
|
||||
this._terminal.y = this._terminal.rows - 1;
|
||||
this._terminal.buffer.y += param;
|
||||
if (this._terminal.buffer.y >= this._terminal.rows) {
|
||||
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) {
|
||||
param = 1;
|
||||
}
|
||||
this._terminal.y -= param;
|
||||
if (this._terminal.y < 0) {
|
||||
this._terminal.y = 0;
|
||||
this._terminal.buffer.y -= param;
|
||||
if (this._terminal.buffer.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) {
|
||||
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;
|
||||
}
|
||||
|
||||
this._terminal.x = col;
|
||||
this._terminal.y = row;
|
||||
this._terminal.buffer.x = col;
|
||||
this._terminal.buffer.y = row;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -348,7 +358,7 @@ export class InputHandler implements IInputHandler {
|
||||
public cursorForwardTab(params: number[]): void {
|
||||
let param = params[0] || 1;
|
||||
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;
|
||||
switch (params[0]) {
|
||||
case 0:
|
||||
this._terminal.eraseRight(this._terminal.x, this._terminal.y);
|
||||
j = this._terminal.y + 1;
|
||||
this._terminal.eraseRight(this._terminal.buffer.x, this._terminal.buffer.y);
|
||||
j = this._terminal.buffer.y + 1;
|
||||
for (; j < this._terminal.rows; j++) {
|
||||
this._terminal.eraseLine(j);
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
this._terminal.eraseLeft(this._terminal.x, this._terminal.y);
|
||||
j = this._terminal.y;
|
||||
this._terminal.eraseLeft(this._terminal.buffer.x, this._terminal.buffer.y);
|
||||
j = this._terminal.buffer.y;
|
||||
while (j--) {
|
||||
this._terminal.eraseLine(j);
|
||||
}
|
||||
@@ -387,11 +397,13 @@ export class InputHandler implements IInputHandler {
|
||||
break;
|
||||
case 3:
|
||||
// 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) {
|
||||
this._terminal.lines.trimStart(scrollBackSize);
|
||||
this._terminal.ybase = Math.max(this._terminal.ybase - scrollBackSize, 0);
|
||||
this._terminal.ydisp = Math.max(this._terminal.ydisp - scrollBackSize, 0);
|
||||
this._terminal.buffer.lines.trimStart(scrollBackSize);
|
||||
this._terminal.buffer.ybase = Math.max(this._terminal.buffer.ybase - scrollBackSize, 0);
|
||||
this._terminal.buffer.ydisp = Math.max(this._terminal.buffer.ydisp - scrollBackSize, 0);
|
||||
// Force a scroll event to refresh viewport
|
||||
this._terminal.emit('scroll', 0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -411,13 +423,13 @@ export class InputHandler implements IInputHandler {
|
||||
public eraseInLine(params: number[]): void {
|
||||
switch (params[0]) {
|
||||
case 0:
|
||||
this._terminal.eraseRight(this._terminal.x, this._terminal.y);
|
||||
this._terminal.eraseRight(this._terminal.buffer.x, this._terminal.buffer.y);
|
||||
break;
|
||||
case 1:
|
||||
this._terminal.eraseLeft(this._terminal.x, this._terminal.y);
|
||||
this._terminal.eraseLeft(this._terminal.buffer.x, this._terminal.buffer.y);
|
||||
break;
|
||||
case 2:
|
||||
this._terminal.eraseLine(this._terminal.y);
|
||||
this._terminal.eraseLine(this._terminal.buffer.y);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -433,29 +445,29 @@ export class InputHandler implements IInputHandler {
|
||||
if (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.ybase - j + 1;
|
||||
j = this._terminal.rows - 1 - this._terminal.buffer.scrollBottom;
|
||||
j = this._terminal.rows - 1 + this._terminal.buffer.ybase - j + 1;
|
||||
|
||||
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
|
||||
this._terminal.lines.trimStart(1);
|
||||
this._terminal.ybase--;
|
||||
this._terminal.ydisp--;
|
||||
this._terminal.buffer.lines.trimStart(1);
|
||||
this._terminal.buffer.ybase--;
|
||||
this._terminal.buffer.ydisp--;
|
||||
row--;
|
||||
j--;
|
||||
}
|
||||
// test: echo -e '\e[44m\e[1L\e[0m'
|
||||
// blankLine(true) - xterm/linux behavior
|
||||
this._terminal.lines.splice(row, 0, this._terminal.blankLine(true));
|
||||
this._terminal.lines.splice(j, 1);
|
||||
this._terminal.buffer.lines.splice(row, 0, this._terminal.blankLine(true));
|
||||
this._terminal.buffer.lines.splice(j, 1);
|
||||
}
|
||||
|
||||
// this.maxRange();
|
||||
this._terminal.updateRange(this._terminal.y);
|
||||
this._terminal.updateRange(this._terminal.scrollBottom);
|
||||
this._terminal.updateRange(this._terminal.buffer.y);
|
||||
this._terminal.updateRange(this._terminal.buffer.scrollBottom);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -469,27 +481,27 @@ export class InputHandler implements IInputHandler {
|
||||
if (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.ybase - j;
|
||||
j = this._terminal.rows - 1 - this._terminal.buffer.scrollBottom;
|
||||
j = this._terminal.rows - 1 + this._terminal.buffer.ybase - j;
|
||||
|
||||
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
|
||||
this._terminal.lines.trimStart(1);
|
||||
this._terminal.ybase -= 1;
|
||||
this._terminal.ydisp -= 1;
|
||||
this._terminal.buffer.lines.trimStart(1);
|
||||
this._terminal.buffer.ybase -= 1;
|
||||
this._terminal.buffer.ydisp -= 1;
|
||||
}
|
||||
// test: echo -e '\e[44m\e[1M\e[0m'
|
||||
// blankLine(true) - xterm/linux behavior
|
||||
this._terminal.lines.splice(j + 1, 0, this._terminal.blankLine(true));
|
||||
this._terminal.lines.splice(row, 1);
|
||||
this._terminal.buffer.lines.splice(j + 1, 0, this._terminal.blankLine(true));
|
||||
this._terminal.buffer.lines.splice(row, 1);
|
||||
}
|
||||
|
||||
// this.maxRange();
|
||||
this._terminal.updateRange(this._terminal.y);
|
||||
this._terminal.updateRange(this._terminal.scrollBottom);
|
||||
this._terminal.updateRange(this._terminal.buffer.y);
|
||||
this._terminal.updateRange(this._terminal.buffer.scrollBottom);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -504,12 +516,12 @@ export class InputHandler implements IInputHandler {
|
||||
param = 1;
|
||||
}
|
||||
|
||||
row = this._terminal.y + this._terminal.ybase;
|
||||
row = this._terminal.buffer.y + this._terminal.buffer.ybase;
|
||||
ch = [this._terminal.eraseAttr(), ' ', 1]; // xterm
|
||||
|
||||
while (param--) {
|
||||
this._terminal.lines.get(row).splice(this._terminal.x, 1);
|
||||
this._terminal.lines.get(row).push(ch);
|
||||
this._terminal.buffer.lines.get(row).splice(this._terminal.buffer.x, 1);
|
||||
this._terminal.buffer.lines.get(row).push(ch);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -519,12 +531,12 @@ export class InputHandler implements IInputHandler {
|
||||
public scrollUp(params: number[]): void {
|
||||
let param = params[0] || 1;
|
||||
while (param--) {
|
||||
this._terminal.lines.splice(this._terminal.ybase + this._terminal.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.scrollTop, 1);
|
||||
this._terminal.buffer.lines.splice(this._terminal.buffer.ybase + this._terminal.buffer.scrollBottom, 0, this._terminal.blankLine());
|
||||
}
|
||||
// this.maxRange();
|
||||
this._terminal.updateRange(this._terminal.scrollTop);
|
||||
this._terminal.updateRange(this._terminal.scrollBottom);
|
||||
this._terminal.updateRange(this._terminal.buffer.scrollTop);
|
||||
this._terminal.updateRange(this._terminal.buffer.scrollBottom);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -533,12 +545,12 @@ export class InputHandler implements IInputHandler {
|
||||
public scrollDown(params: number[]): void {
|
||||
let param = params[0] || 1;
|
||||
while (param--) {
|
||||
this._terminal.lines.splice(this._terminal.ybase + this._terminal.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.scrollBottom, 1);
|
||||
this._terminal.buffer.lines.splice(this._terminal.buffer.ybase + this._terminal.buffer.scrollTop, 0, this._terminal.blankLine());
|
||||
}
|
||||
// this.maxRange();
|
||||
this._terminal.updateRange(this._terminal.scrollTop);
|
||||
this._terminal.updateRange(this._terminal.scrollBottom);
|
||||
this._terminal.updateRange(this._terminal.buffer.scrollTop);
|
||||
this._terminal.updateRange(this._terminal.buffer.scrollBottom);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -553,12 +565,12 @@ export class InputHandler implements IInputHandler {
|
||||
param = 1;
|
||||
}
|
||||
|
||||
row = this._terminal.y + this._terminal.ybase;
|
||||
j = this._terminal.x;
|
||||
row = this._terminal.buffer.y + this._terminal.buffer.ybase;
|
||||
j = this._terminal.buffer.x;
|
||||
ch = [this._terminal.eraseAttr(), ' ', 1]; // xterm
|
||||
|
||||
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 {
|
||||
let param = params[0] || 1;
|
||||
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) {
|
||||
param = 1;
|
||||
}
|
||||
this._terminal.x = param - 1;
|
||||
if (this._terminal.x >= this._terminal.cols) {
|
||||
this._terminal.x = this._terminal.cols - 1;
|
||||
this._terminal.buffer.x = param - 1;
|
||||
if (this._terminal.buffer.x >= this._terminal.cols) {
|
||||
this._terminal.buffer.x = this._terminal.cols - 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -597,9 +609,9 @@ export class InputHandler implements IInputHandler {
|
||||
if (param < 1) {
|
||||
param = 1;
|
||||
}
|
||||
this._terminal.x += param;
|
||||
if (this._terminal.x >= this._terminal.cols) {
|
||||
this._terminal.x = this._terminal.cols - 1;
|
||||
this._terminal.buffer.x += param;
|
||||
if (this._terminal.buffer.x >= this._terminal.cols) {
|
||||
this._terminal.buffer.x = this._terminal.cols - 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -608,11 +620,11 @@ export class InputHandler implements IInputHandler {
|
||||
*/
|
||||
public repeatPrecedingCharacter(params: number[]): void {
|
||||
let param = params[0] || 1
|
||||
, line = this._terminal.lines.get(this._terminal.ybase + this._terminal.y)
|
||||
, ch = line[this._terminal.x - 1] || [this._terminal.defAttr, ' ', 1];
|
||||
, line = this._terminal.buffer.lines.get(this._terminal.buffer.ybase + this._terminal.buffer.y)
|
||||
, ch = line[this._terminal.buffer.x - 1] || [this._terminal.defAttr, ' ', 1];
|
||||
|
||||
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) {
|
||||
param = 1;
|
||||
}
|
||||
this._terminal.y = param - 1;
|
||||
if (this._terminal.y >= this._terminal.rows) {
|
||||
this._terminal.y = this._terminal.rows - 1;
|
||||
this._terminal.buffer.y = param - 1;
|
||||
if (this._terminal.buffer.y >= this._terminal.rows) {
|
||||
this._terminal.buffer.y = this._terminal.rows - 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -707,13 +719,13 @@ export class InputHandler implements IInputHandler {
|
||||
if (param < 1) {
|
||||
param = 1;
|
||||
}
|
||||
this._terminal.y += param;
|
||||
if (this._terminal.y >= this._terminal.rows) {
|
||||
this._terminal.y = this._terminal.rows - 1;
|
||||
this._terminal.buffer.y += param;
|
||||
if (this._terminal.buffer.y >= this._terminal.rows) {
|
||||
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 (this._terminal.x >= this._terminal.cols) {
|
||||
this._terminal.x--;
|
||||
if (this._terminal.buffer.x >= this._terminal.cols) {
|
||||
this._terminal.buffer.x--;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -726,14 +738,14 @@ export class InputHandler implements IInputHandler {
|
||||
if (params[0] < 1) params[0] = 1;
|
||||
if (params[1] < 1) params[1] = 1;
|
||||
|
||||
this._terminal.y = params[0] - 1;
|
||||
if (this._terminal.y >= this._terminal.rows) {
|
||||
this._terminal.y = this._terminal.rows - 1;
|
||||
this._terminal.buffer.y = params[0] - 1;
|
||||
if (this._terminal.buffer.y >= this._terminal.rows) {
|
||||
this._terminal.buffer.y = this._terminal.rows - 1;
|
||||
}
|
||||
|
||||
this._terminal.x = params[1] - 1;
|
||||
if (this._terminal.x >= this._terminal.cols) {
|
||||
this._terminal.x = this._terminal.cols - 1;
|
||||
this._terminal.buffer.x = params[1] - 1;
|
||||
if (this._terminal.buffer.x >= this._terminal.cols) {
|
||||
this._terminal.buffer.x = this._terminal.cols - 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -748,9 +760,9 @@ export class InputHandler implements IInputHandler {
|
||||
public tabClear(params: number[]): void {
|
||||
let param = params[0];
|
||||
if (param <= 0) {
|
||||
delete this._terminal.tabs[this._terminal.x];
|
||||
delete this._terminal.buffer.tabs[this._terminal.buffer.x];
|
||||
} else if (param === 3) {
|
||||
this._terminal.tabs = {};
|
||||
this._terminal.buffer.tabs = {};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -936,30 +948,14 @@ export class InputHandler implements IInputHandler {
|
||||
this._terminal.cursorHidden = false;
|
||||
break;
|
||||
case 1049: // alt screen buffer cursor
|
||||
// this._terminal.saveCursor();
|
||||
; // FALL-THROUGH
|
||||
// TODO: Not sure if we need to save/restore after switching the buffer
|
||||
// this.saveCursor(params);
|
||||
// FALL-THROUGH
|
||||
case 47: // alt screen buffer
|
||||
case 1047: // alt screen buffer
|
||||
if (!this._terminal.normal) {
|
||||
let normal = {
|
||||
lines: this._terminal.lines,
|
||||
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();
|
||||
}
|
||||
this._terminal.buffers.activateAltBuffer();
|
||||
this._terminal.viewport.syncScrollArea();
|
||||
this._terminal.showCursor();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1117,29 +1113,19 @@ export class InputHandler implements IInputHandler {
|
||||
this._terminal.cursorHidden = true;
|
||||
break;
|
||||
case 1049: // alt screen buffer cursor
|
||||
; // FALL-THROUGH
|
||||
// FALL-THROUGH
|
||||
case 47: // normal screen buffer
|
||||
case 1047: // normal screen buffer - clearing it first
|
||||
if (this._terminal.normal) {
|
||||
this._terminal.lines = this._terminal.normal.lines;
|
||||
this._terminal.ybase = this._terminal.normal.ybase;
|
||||
this._terminal.ydisp = this._terminal.normal.ydisp;
|
||||
this._terminal.x = this._terminal.normal.x;
|
||||
this._terminal.y = this._terminal.normal.y;
|
||||
this._terminal.scrollTop = this._terminal.normal.scrollTop;
|
||||
this._terminal.scrollBottom = this._terminal.normal.scrollBottom;
|
||||
this._terminal.tabs = this._terminal.normal.tabs;
|
||||
this._terminal.normal = null;
|
||||
// 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();
|
||||
}
|
||||
// Ensure the selection manager has the correct buffer
|
||||
this._terminal.buffers.activateNormalBuffer();
|
||||
// TODO: Not sure if we need to save/restore after switching the buffer
|
||||
// if (params[0] === 1049) {
|
||||
// this.restoreCursor(params);
|
||||
// }
|
||||
this._terminal.selectionManager.setBuffer(this._terminal.buffer.lines);
|
||||
this._terminal.refresh(0, this._terminal.rows - 1);
|
||||
this._terminal.viewport.syncScrollArea();
|
||||
this._terminal.showCursor();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1359,9 +1345,9 @@ export class InputHandler implements IInputHandler {
|
||||
case 6:
|
||||
// cursor position
|
||||
this._terminal.send(C0.ESC + '['
|
||||
+ (this._terminal.y + 1)
|
||||
+ (this._terminal.buffer.y + 1)
|
||||
+ ';'
|
||||
+ (this._terminal.x + 1)
|
||||
+ (this._terminal.buffer.x + 1)
|
||||
+ 'R');
|
||||
break;
|
||||
}
|
||||
@@ -1372,9 +1358,9 @@ export class InputHandler implements IInputHandler {
|
||||
case 6:
|
||||
// cursor position
|
||||
this._terminal.send(C0.ESC + '[?'
|
||||
+ (this._terminal.y + 1)
|
||||
+ (this._terminal.buffer.y + 1)
|
||||
+ ';'
|
||||
+ (this._terminal.x + 1)
|
||||
+ (this._terminal.buffer.x + 1)
|
||||
+ 'R');
|
||||
break;
|
||||
case 15:
|
||||
@@ -1409,10 +1395,10 @@ export class InputHandler implements IInputHandler {
|
||||
this._terminal.applicationKeypad = false; // ?
|
||||
this._terminal.viewport.syncScrollArea();
|
||||
this._terminal.applicationCursor = false;
|
||||
this._terminal.scrollTop = 0;
|
||||
this._terminal.scrollBottom = this._terminal.rows - 1;
|
||||
this._terminal.buffer.scrollTop = 0;
|
||||
this._terminal.buffer.scrollBottom = this._terminal.rows - 1;
|
||||
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.glevel = 0; // ??
|
||||
this._terminal.charsets = [null]; // ??
|
||||
@@ -1456,10 +1442,10 @@ export class InputHandler implements IInputHandler {
|
||||
*/
|
||||
public setScrollRegion(params: number[]): void {
|
||||
if (this._terminal.prefix) return;
|
||||
this._terminal.scrollTop = (params[0] || 1) - 1;
|
||||
this._terminal.scrollBottom = (params[1] && params[1] <= this._terminal.rows ? params[1] : this._terminal.rows) - 1;
|
||||
this._terminal.x = 0;
|
||||
this._terminal.y = 0;
|
||||
this._terminal.buffer.scrollTop = (params[0] || 1) - 1;
|
||||
this._terminal.buffer.scrollBottom = (params[1] && params[1] <= this._terminal.rows ? params[1] : this._terminal.rows) - 1;
|
||||
this._terminal.buffer.x = 0;
|
||||
this._terminal.buffer.y = 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -1468,8 +1454,8 @@ export class InputHandler implements IInputHandler {
|
||||
* Save cursor (ANSI.SYS).
|
||||
*/
|
||||
public saveCursor(params: number[]): void {
|
||||
this._terminal.savedX = this._terminal.x;
|
||||
this._terminal.savedY = this._terminal.y;
|
||||
this._terminal.buffer.savedX = this._terminal.buffer.x;
|
||||
this._terminal.buffer.savedY = this._terminal.buffer.y;
|
||||
}
|
||||
|
||||
|
||||
@@ -1478,100 +1464,102 @@ export class InputHandler implements IInputHandler {
|
||||
* Restore cursor (ANSI.SYS).
|
||||
*/
|
||||
public restoreCursor(params: number[]): void {
|
||||
this._terminal.x = this._terminal.savedX || 0;
|
||||
this._terminal.y = this._terminal.savedY || 0;
|
||||
this._terminal.buffer.x = this._terminal.buffer.savedX || 0;
|
||||
this._terminal.buffer.y = this._terminal.buffer.savedY || 0;
|
||||
}
|
||||
}
|
||||
|
||||
const 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])
|
||||
export const wcwidth = (function(opts) {
|
||||
// extracted from https://www.cl.cam.ac.uk/%7Emgk25/ucs/wcwidth.c
|
||||
// combining characters
|
||||
const COMBINING_BMP = [
|
||||
[0x0300, 0x036F], [0x0483, 0x0486], [0x0488, 0x0489],
|
||||
[0x0591, 0x05BD], [0x05BF, 0x05BF], [0x05C1, 0x05C2],
|
||||
[0x05C4, 0x05C5], [0x05C7, 0x05C7], [0x0600, 0x0603],
|
||||
[0x0610, 0x0615], [0x064B, 0x065E], [0x0670, 0x0670],
|
||||
[0x06D6, 0x06E4], [0x06E7, 0x06E8], [0x06EA, 0x06ED],
|
||||
[0x070F, 0x070F], [0x0711, 0x0711], [0x0730, 0x074A],
|
||||
[0x07A6, 0x07B0], [0x07EB, 0x07F3], [0x0901, 0x0902],
|
||||
[0x093C, 0x093C], [0x0941, 0x0948], [0x094D, 0x094D],
|
||||
[0x0951, 0x0954], [0x0962, 0x0963], [0x0981, 0x0981],
|
||||
[0x09BC, 0x09BC], [0x09C1, 0x09C4], [0x09CD, 0x09CD],
|
||||
[0x09E2, 0x09E3], [0x0A01, 0x0A02], [0x0A3C, 0x0A3C],
|
||||
[0x0A41, 0x0A42], [0x0A47, 0x0A48], [0x0A4B, 0x0A4D],
|
||||
[0x0A70, 0x0A71], [0x0A81, 0x0A82], [0x0ABC, 0x0ABC],
|
||||
[0x0AC1, 0x0AC5], [0x0AC7, 0x0AC8], [0x0ACD, 0x0ACD],
|
||||
[0x0AE2, 0x0AE3], [0x0B01, 0x0B01], [0x0B3C, 0x0B3C],
|
||||
[0x0B3F, 0x0B3F], [0x0B41, 0x0B43], [0x0B4D, 0x0B4D],
|
||||
[0x0B56, 0x0B56], [0x0B82, 0x0B82], [0x0BC0, 0x0BC0],
|
||||
[0x0BCD, 0x0BCD], [0x0C3E, 0x0C40], [0x0C46, 0x0C48],
|
||||
[0x0C4A, 0x0C4D], [0x0C55, 0x0C56], [0x0CBC, 0x0CBC],
|
||||
[0x0CBF, 0x0CBF], [0x0CC6, 0x0CC6], [0x0CCC, 0x0CCD],
|
||||
[0x0CE2, 0x0CE3], [0x0D41, 0x0D43], [0x0D4D, 0x0D4D],
|
||||
[0x0DCA, 0x0DCA], [0x0DD2, 0x0DD4], [0x0DD6, 0x0DD6],
|
||||
[0x0E31, 0x0E31], [0x0E34, 0x0E3A], [0x0E47, 0x0E4E],
|
||||
[0x0EB1, 0x0EB1], [0x0EB4, 0x0EB9], [0x0EBB, 0x0EBC],
|
||||
[0x0EC8, 0x0ECD], [0x0F18, 0x0F19], [0x0F35, 0x0F35],
|
||||
[0x0F37, 0x0F37], [0x0F39, 0x0F39], [0x0F71, 0x0F7E],
|
||||
[0x0F80, 0x0F84], [0x0F86, 0x0F87], [0x0F90, 0x0F97],
|
||||
[0x0F99, 0x0FBC], [0x0FC6, 0x0FC6], [0x102D, 0x1030],
|
||||
[0x1032, 0x1032], [0x1036, 0x1037], [0x1039, 0x1039],
|
||||
[0x1058, 0x1059], [0x1160, 0x11FF], [0x135F, 0x135F],
|
||||
[0x1712, 0x1714], [0x1732, 0x1734], [0x1752, 0x1753],
|
||||
[0x1772, 0x1773], [0x17B4, 0x17B5], [0x17B7, 0x17BD],
|
||||
[0x17C6, 0x17C6], [0x17C9, 0x17D3], [0x17DD, 0x17DD],
|
||||
[0x180B, 0x180D], [0x18A9, 0x18A9], [0x1920, 0x1922],
|
||||
[0x1927, 0x1928], [0x1932, 0x1932], [0x1939, 0x193B],
|
||||
[0x1A17, 0x1A18], [0x1B00, 0x1B03], [0x1B34, 0x1B34],
|
||||
[0x1B36, 0x1B3A], [0x1B3C, 0x1B3C], [0x1B42, 0x1B42],
|
||||
[0x1B6B, 0x1B73], [0x1DC0, 0x1DCA], [0x1DFE, 0x1DFF],
|
||||
[0x200B, 0x200F], [0x202A, 0x202E], [0x2060, 0x2063],
|
||||
[0x206A, 0x206F], [0x20D0, 0x20EF], [0x302A, 0x302F],
|
||||
[0x3099, 0x309A], [0xA806, 0xA806], [0xA80B, 0xA80B],
|
||||
[0xA825, 0xA826], [0xFB1E, 0xFB1E], [0xFE00, 0xFE0F],
|
||||
[0xFE20, 0xFE23], [0xFEFF, 0xFEFF], [0xFFF9, 0xFFFB],
|
||||
];
|
||||
const COMBINING_HIGH = [
|
||||
[0x10A01, 0x10A03], [0x10A05, 0x10A06], [0x10A0C, 0x10A0F],
|
||||
[0x10A38, 0x10A3A], [0x10A3F, 0x10A3F], [0x1D167, 0x1D169],
|
||||
[0x1D173, 0x1D182], [0x1D185, 0x1D18B], [0x1D1AA, 0x1D1AD],
|
||||
[0x1D242, 0x1D244], [0xE0001, 0xE0001], [0xE0020, 0xE007F],
|
||||
[0xE0100, 0xE01EF]
|
||||
];
|
||||
// binary search
|
||||
function bisearch(ucs, data) {
|
||||
let min = 0;
|
||||
let max = data.length - 1;
|
||||
let mid;
|
||||
if (ucs < data[0][0] || ucs > data[max][1])
|
||||
return false;
|
||||
while (max >= min) {
|
||||
mid = (min + max) >> 1;
|
||||
if (ucs > data[mid][1])
|
||||
min = mid + 1;
|
||||
else if (ucs < data[mid][0])
|
||||
max = mid - 1;
|
||||
else
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
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;
|
||||
function wcwidthBMP(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, COMBINING_BMP))
|
||||
return 0;
|
||||
// if we arrive here, ucs is not a combining or C0/C1 control character
|
||||
if (isWideBMP(ucs)) {
|
||||
return 2;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
function isWide(ucs) {
|
||||
return (
|
||||
ucs >= 0x1100 && (
|
||||
function isWideBMP(ucs) {
|
||||
return (
|
||||
ucs >= 0x1100 && (
|
||||
ucs <= 0x115f || // Hangul Jamo init. consonants
|
||||
ucs === 0x2329 ||
|
||||
ucs === 0x232a ||
|
||||
@@ -1581,9 +1569,59 @@ const wcwidth = (function(opts) {
|
||||
(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;
|
||||
(ucs >= 0xffe0 && ucs <= 0xffe6)));
|
||||
}
|
||||
function wcwidthHigh(ucs) {
|
||||
if (bisearch(ucs, COMBINING_HIGH))
|
||||
return 0;
|
||||
if ((ucs >= 0x20000 && ucs <= 0x2fffd) || (ucs >= 0x30000 && ucs <= 0x3fffd)) {
|
||||
return 2;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
const control = opts.control | 0;
|
||||
let table = 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
|
||||
|
||||
+36
-7
@@ -21,11 +21,9 @@ export interface ITerminal {
|
||||
element: HTMLElement;
|
||||
rowContainer: HTMLElement;
|
||||
selectionContainer: HTMLElement;
|
||||
selectionManager: ISelectionManager;
|
||||
charMeasure: ICharMeasure;
|
||||
textarea: HTMLTextAreaElement;
|
||||
ybase: number;
|
||||
ydisp: number;
|
||||
lines: ICircularList<string>;
|
||||
rows: number;
|
||||
cols: number;
|
||||
browser: IBrowser;
|
||||
@@ -33,9 +31,10 @@ export interface ITerminal {
|
||||
children: HTMLElement[];
|
||||
cursorHidden: boolean;
|
||||
cursorState: number;
|
||||
x: number;
|
||||
y: number;
|
||||
defAttr: number;
|
||||
scrollback: number;
|
||||
buffers: IBufferSet;
|
||||
buffer: IBuffer;
|
||||
|
||||
/**
|
||||
* Emit the 'data' event and populate the given data.
|
||||
@@ -47,10 +46,35 @@ export interface ITerminal {
|
||||
cancel(ev: Event, force?: boolean);
|
||||
log(text: string): void;
|
||||
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 {
|
||||
selectionText: string;
|
||||
selectionStart: [number, number];
|
||||
selectionEnd: [number, number];
|
||||
|
||||
setSelection(row: number, col: number, length: number);
|
||||
}
|
||||
|
||||
export interface ICharMeasure {
|
||||
@@ -66,11 +90,11 @@ export interface ILinkifier {
|
||||
deregisterLinkMatcher(matcherId: number): boolean;
|
||||
}
|
||||
|
||||
interface ICircularList<T> {
|
||||
export interface ICircularList<T> extends IEventEmitter {
|
||||
length: 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;
|
||||
set(index: number, value: T): void;
|
||||
push(value: T): void;
|
||||
@@ -80,6 +104,11 @@ interface ICircularList<T> {
|
||||
shiftElements(start: number, count: number, offset: number): void;
|
||||
}
|
||||
|
||||
export interface IEventEmitter {
|
||||
on(type, listener): void;
|
||||
off(type, listener): void;
|
||||
}
|
||||
|
||||
export interface LinkMatcherOptions {
|
||||
/**
|
||||
* 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', () => {
|
||||
let dom: jsdom.JSDOM;
|
||||
let window: Window;
|
||||
let document: Document;
|
||||
|
||||
@@ -24,13 +25,11 @@ describe('Linkifier', () => {
|
||||
let rows: HTMLElement[];
|
||||
let linkifier: TestLinkifier;
|
||||
|
||||
beforeEach(done => {
|
||||
jsdom.env('', (err, w) => {
|
||||
window = w;
|
||||
document = window.document;
|
||||
linkifier = new TestLinkifier();
|
||||
done();
|
||||
});
|
||||
beforeEach(() => {
|
||||
dom = new jsdom.JSDOM('');
|
||||
window = dom.window;
|
||||
document = window.document;
|
||||
linkifier = new TestLinkifier();
|
||||
});
|
||||
|
||||
function addRow(html: string) {
|
||||
|
||||
+10
-3
@@ -52,7 +52,7 @@ escapedStateHandler['c'] = (parser, terminal) => {
|
||||
};
|
||||
escapedStateHandler['E'] = (parser, terminal) => {
|
||||
// ESC E Next Line ( NEL is 0x85).
|
||||
terminal.x = 0;
|
||||
terminal.buffer.x = 0;
|
||||
terminal.index();
|
||||
parser.setState(ParserState.NORMAL);
|
||||
};
|
||||
@@ -184,6 +184,10 @@ export class Parser {
|
||||
public parse(data: string): ParserState {
|
||||
let l = data.length, j, cs, ch, code, low;
|
||||
|
||||
if (this._terminal.debug) {
|
||||
this._terminal.log('data: ' + data);
|
||||
}
|
||||
|
||||
this._position = 0;
|
||||
// apply leftover surrogate high from last write
|
||||
if (this._terminal.surrogate_high) {
|
||||
@@ -458,6 +462,9 @@ export class Parser {
|
||||
|
||||
case ParserState.CSI:
|
||||
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);
|
||||
} else {
|
||||
this._terminal.error('Unknown CSI code: %s.', ch);
|
||||
@@ -499,9 +506,9 @@ export class Parser {
|
||||
// DECSTBM
|
||||
case 'r':
|
||||
pt = ''
|
||||
+ (this._terminal.scrollTop + 1)
|
||||
+ (this._terminal.buffer.scrollTop + 1)
|
||||
+ ';'
|
||||
+ (this._terminal.scrollBottom + 1)
|
||||
+ (this._terminal.buffer.scrollBottom + 1)
|
||||
+ 'r';
|
||||
break;
|
||||
|
||||
|
||||
+75
-73
@@ -139,15 +139,15 @@ export class Renderer {
|
||||
}
|
||||
|
||||
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;
|
||||
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.cursorHidden) {
|
||||
x = this._terminal.x;
|
||||
x = this._terminal.buffer.x;
|
||||
} else {
|
||||
x = -1;
|
||||
}
|
||||
@@ -170,16 +170,13 @@ export class Renderer {
|
||||
let data: any = line[i][0];
|
||||
const ch = line[i][1];
|
||||
const ch_width: any = line[i][2];
|
||||
const isCursor: boolean = i === x;
|
||||
if (!ch_width) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i === x) {
|
||||
data = -1;
|
||||
}
|
||||
|
||||
if (data !== attr) {
|
||||
if (attr !== this._terminal.defAttr) {
|
||||
if (data !== attr || isCursor) {
|
||||
if (attr !== this._terminal.defAttr && !isCursor) {
|
||||
if (innerHTML) {
|
||||
currentElement.innerHTML = innerHTML;
|
||||
innerHTML = '';
|
||||
@@ -187,7 +184,7 @@ export class Renderer {
|
||||
documentFragment.appendChild(currentElement);
|
||||
currentElement = null;
|
||||
}
|
||||
if (data !== this._terminal.defAttr) {
|
||||
if (data !== this._terminal.defAttr || isCursor) {
|
||||
if (innerHTML && !currentElement) {
|
||||
currentElement = this._spanElementObjectPool.acquire();
|
||||
}
|
||||
@@ -199,71 +196,73 @@ export class Renderer {
|
||||
documentFragment.appendChild(currentElement);
|
||||
}
|
||||
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('terminal-cursor');
|
||||
} else {
|
||||
let bg = data & 0x1ff;
|
||||
let fg = (data >> 9) & 0x1ff;
|
||||
let flags = data >> 18;
|
||||
}
|
||||
|
||||
if (flags & FLAGS.BOLD) {
|
||||
if (!brokenBold) {
|
||||
currentElement.classList.add('xterm-bold');
|
||||
}
|
||||
// See: XTerm*boldColors
|
||||
if (fg < 8) {
|
||||
fg += 8;
|
||||
}
|
||||
if (flags & FLAGS.BOLD) {
|
||||
if (!brokenBold) {
|
||||
currentElement.classList.add('xterm-bold');
|
||||
}
|
||||
|
||||
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) {
|
||||
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}`);
|
||||
// See: XTerm*boldColors
|
||||
if (fg < 8) {
|
||||
fg += 8;
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -337,8 +339,8 @@ export class Renderer {
|
||||
}
|
||||
|
||||
// Translate from buffer position to viewport position
|
||||
const viewportStartRow = start[1] - this._terminal.ydisp;
|
||||
const viewportEndRow = end[1] - this._terminal.ydisp;
|
||||
const viewportStartRow = start[1] - this._terminal.buffer.ydisp;
|
||||
const viewportEndRow = end[1] - this._terminal.buffer.ydisp;
|
||||
const viewportCappedStartRow = Math.max(viewportStartRow, 0);
|
||||
const viewportCappedEndRow = Math.min(viewportEndRow, this._terminal.rows - 1);
|
||||
|
||||
|
||||
@@ -3,16 +3,18 @@
|
||||
*/
|
||||
import jsdom = require('jsdom');
|
||||
import { assert } from 'chai';
|
||||
import { ITerminal } from './Interfaces';
|
||||
import { ITerminal, ICircularList } from './Interfaces';
|
||||
import { CharMeasure } from './utils/CharMeasure';
|
||||
import { CircularList } from './utils/CircularList';
|
||||
import { SelectionManager } from './SelectionManager';
|
||||
import { SelectionModel } from './SelectionModel';
|
||||
import { BufferSet } from './BufferSet';
|
||||
import { MockTerminal } from './utils/TestUtils';
|
||||
|
||||
class TestSelectionManager extends SelectionManager {
|
||||
constructor(
|
||||
terminal: ITerminal,
|
||||
buffer: CircularList<any>,
|
||||
buffer: ICircularList<[number, string, number][]>,
|
||||
rowContainer: HTMLElement,
|
||||
charMeasure: CharMeasure
|
||||
) {
|
||||
@@ -31,23 +33,28 @@ class TestSelectionManager extends SelectionManager {
|
||||
}
|
||||
|
||||
describe('SelectionManager', () => {
|
||||
let dom: jsdom.JSDOM;
|
||||
let window: Window;
|
||||
let document: Document;
|
||||
|
||||
let terminal: ITerminal;
|
||||
let buffer: CircularList<any>;
|
||||
let bufferLines: ICircularList<[number, string, number][]>;
|
||||
let rowContainer: HTMLElement;
|
||||
let selectionManager: TestSelectionManager;
|
||||
|
||||
beforeEach(done => {
|
||||
jsdom.env('', (err, w) => {
|
||||
window = w;
|
||||
document = window.document;
|
||||
buffer = new CircularList<any>(100);
|
||||
terminal = <any>{ cols: 80, rows: 2 };
|
||||
selectionManager = new TestSelectionManager(terminal, buffer, rowContainer, null);
|
||||
done();
|
||||
});
|
||||
beforeEach(() => {
|
||||
dom = new jsdom.JSDOM('');
|
||||
window = dom.window;
|
||||
document = window.document;
|
||||
rowContainer = document.createElement('div');
|
||||
terminal = new MockTerminal();
|
||||
terminal.cols = 80;
|
||||
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][] {
|
||||
@@ -60,7 +67,7 @@ describe('SelectionManager', () => {
|
||||
|
||||
describe('_selectWordAt', () => {
|
||||
it('should expand selection for normal width chars', () => {
|
||||
buffer.push(stringToRow('foo bar'));
|
||||
bufferLines.set(0, stringToRow('foo bar'));
|
||||
selectionManager.selectWordAt([0, 0]);
|
||||
assert.equal(selectionManager.selectionText, 'foo');
|
||||
selectionManager.selectWordAt([1, 0]);
|
||||
@@ -77,7 +84,7 @@ describe('SelectionManager', () => {
|
||||
assert.equal(selectionManager.selectionText, 'bar');
|
||||
});
|
||||
it('should expand selection for whitespace', () => {
|
||||
buffer.push(stringToRow('a b'));
|
||||
bufferLines.set(0, stringToRow('a b'));
|
||||
selectionManager.selectWordAt([0, 0]);
|
||||
assert.equal(selectionManager.selectionText, 'a');
|
||||
selectionManager.selectWordAt([1, 0]);
|
||||
@@ -91,7 +98,7 @@ describe('SelectionManager', () => {
|
||||
});
|
||||
it('should expand selection for wide characters', () => {
|
||||
// Wide characters use a special format
|
||||
buffer.push([
|
||||
bufferLines.set(0, [
|
||||
[null, '中', 2],
|
||||
[null, '', 0],
|
||||
[null, '文', 2],
|
||||
@@ -143,7 +150,7 @@ describe('SelectionManager', () => {
|
||||
assert.equal(selectionManager.selectionText, 'foo');
|
||||
});
|
||||
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]);
|
||||
assert.equal(selectionManager.selectionText, '(cd');
|
||||
selectionManager.selectWordAt([1, 0]);
|
||||
@@ -181,7 +188,7 @@ describe('SelectionManager', () => {
|
||||
|
||||
describe('_selectLineAt', () => {
|
||||
it('should select the entire line', () => {
|
||||
buffer.push(stringToRow('foo bar'));
|
||||
bufferLines.set(0, stringToRow('foo bar'));
|
||||
selectionManager.selectLineAt(0);
|
||||
assert.equal(selectionManager.selectionText, 'foo bar', 'The selected text is correct');
|
||||
assert.deepEqual(selectionManager.model.finalSelectionStart, [0, 0]);
|
||||
@@ -191,13 +198,14 @@ describe('SelectionManager', () => {
|
||||
|
||||
describe('selectAll', () => {
|
||||
it('should select the entire buffer, beyond the viewport', () => {
|
||||
buffer.push(stringToRow('1'));
|
||||
buffer.push(stringToRow('2'));
|
||||
buffer.push(stringToRow('3'));
|
||||
buffer.push(stringToRow('4'));
|
||||
buffer.push(stringToRow('5'));
|
||||
bufferLines.length = 5;
|
||||
bufferLines.set(0, stringToRow('1'));
|
||||
bufferLines.set(1, stringToRow('2'));
|
||||
bufferLines.set(2, stringToRow('3'));
|
||||
bufferLines.set(3, stringToRow('4'));
|
||||
bufferLines.set(4, stringToRow('5'));
|
||||
selectionManager.selectAll();
|
||||
terminal.ybase = buffer.length - terminal.rows;
|
||||
terminal.buffer.ybase = bufferLines.length - terminal.rows;
|
||||
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 { CircularList } from './utils/CircularList';
|
||||
import { EventEmitter } from './EventEmitter';
|
||||
import { ITerminal } from './Interfaces';
|
||||
import { ITerminal, ICircularList } from './Interfaces';
|
||||
import { SelectionModel } from './SelectionModel';
|
||||
import { translateBufferLineToString } from './utils/BufferLine';
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* 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
|
||||
* double click to select work logic.
|
||||
@@ -86,23 +75,6 @@ export class SelectionManager extends EventEmitter {
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
@@ -119,14 +91,17 @@ export class SelectionManager extends EventEmitter {
|
||||
*/
|
||||
private _refreshAnimationFrame: number;
|
||||
|
||||
private _bufferTrimListener: any;
|
||||
/**
|
||||
* Whether selection is enabled.
|
||||
*/
|
||||
private _enabled = true;
|
||||
|
||||
private _mouseMoveListener: EventListener;
|
||||
private _mouseDownListener: EventListener;
|
||||
private _mouseUpListener: EventListener;
|
||||
|
||||
constructor(
|
||||
private _terminal: ITerminal,
|
||||
private _buffer: CircularList<any>,
|
||||
private _buffer: ICircularList<[number, string, number][]>,
|
||||
private _rowContainer: HTMLElement,
|
||||
private _charMeasure: CharMeasure
|
||||
) {
|
||||
@@ -135,7 +110,6 @@ export class SelectionManager extends EventEmitter {
|
||||
this.enable();
|
||||
|
||||
this._model = new SelectionModel(_terminal);
|
||||
this._lastMouseDownTime = 0;
|
||||
this._activeSelectionMode = SelectionMode.NORMAL;
|
||||
}
|
||||
|
||||
@@ -143,10 +117,16 @@ export class SelectionManager extends EventEmitter {
|
||||
* Initializes listener variables.
|
||||
*/
|
||||
private _initListeners() {
|
||||
this._bufferTrimListener = (amount: number) => this._onTrim(amount);
|
||||
this._mouseMoveListener = event => this._onMouseMove(<MouseEvent>event);
|
||||
this._mouseDownListener = event => this._onMouseDown(<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() {
|
||||
this.clearSelection();
|
||||
this._buffer.off('trim', this._bufferTrimListener);
|
||||
this._rowContainer.removeEventListener('mousedown', this._mouseDownListener);
|
||||
this._enabled = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable the selection manager.
|
||||
*/
|
||||
public enable() {
|
||||
// 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', this._bufferTrimListener);
|
||||
this._rowContainer.addEventListener('mousedown', this._mouseDownListener);
|
||||
this._enabled = true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -176,11 +150,14 @@ export class SelectionManager extends EventEmitter {
|
||||
* switched in or out.
|
||||
* @param buffer The active buffer.
|
||||
*/
|
||||
public setBuffer(buffer: CircularList<any>): void {
|
||||
public setBuffer(buffer: ICircularList<[number, string, number][]>): void {
|
||||
this._buffer = buffer;
|
||||
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.
|
||||
*/
|
||||
@@ -206,13 +183,13 @@ export class SelectionManager extends EventEmitter {
|
||||
// Get first row
|
||||
const startRowEndCol = start[1] === end[1] ? end[0] : null;
|
||||
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
|
||||
for (let i = start[1] + 1; i <= end[1] - 1; i++) {
|
||||
const bufferLine = this._buffer.get(i);
|
||||
const lineText = this._translateBufferLineToString(bufferLine, true);
|
||||
if (bufferLine.isWrapped) {
|
||||
const lineText = translateBufferLineToString(bufferLine, true);
|
||||
if ((<any>bufferLine).isWrapped) {
|
||||
result[result.length - 1] += lineText;
|
||||
} else {
|
||||
result.push(lineText);
|
||||
@@ -222,8 +199,8 @@ export class SelectionManager extends EventEmitter {
|
||||
// Get final row
|
||||
if (start[1] !== end[1]) {
|
||||
const bufferLine = this._buffer.get(end[1]);
|
||||
const lineText = this._translateBufferLineToString(bufferLine, true, 0, end[0]);
|
||||
if (bufferLine.isWrapped) {
|
||||
const lineText = translateBufferLineToString(bufferLine, true, 0, end[0]);
|
||||
if ((<any>bufferLine).isWrapped) {
|
||||
result[result.length - 1] += lineText;
|
||||
} else {
|
||||
result.push(lineText);
|
||||
@@ -248,55 +225,6 @@ export class SelectionManager extends EventEmitter {
|
||||
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.
|
||||
* @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] {
|
||||
const coords = Mouse.getCoords(event, this._rowContainer, this._charMeasure, this._terminal.cols, this._terminal.rows, true);
|
||||
if (!coords) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Convert to 0-based
|
||||
coords[0]--;
|
||||
coords[1]--;
|
||||
// Convert viewport coords to buffer coords
|
||||
coords[1] += this._terminal.ydisp;
|
||||
coords[1] += this._terminal.buffer.ydisp;
|
||||
return coords;
|
||||
}
|
||||
|
||||
@@ -385,28 +317,45 @@ export class SelectionManager extends EventEmitter {
|
||||
* @param event The mousedown event.
|
||||
*/
|
||||
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
|
||||
if (event.button !== 0) {
|
||||
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
|
||||
event.preventDefault();
|
||||
|
||||
// Reset drag scroll state
|
||||
this._dragScrollAmount = 0;
|
||||
|
||||
this._setMouseClickCount(event);
|
||||
|
||||
if (event.shiftKey) {
|
||||
this._onShiftClick(event);
|
||||
if (this._enabled && event.shiftKey) {
|
||||
this._onIncrementalClick(event);
|
||||
} else {
|
||||
if (this._clickCount === 1) {
|
||||
this._onSingleClick(event);
|
||||
} else if (this._clickCount === 2) {
|
||||
this._onDoubleClick(event);
|
||||
} else if (this._clickCount === 3) {
|
||||
this._onTripleClick(event);
|
||||
if (event.detail === 1) {
|
||||
this._onSingleClick(event);
|
||||
} else if (event.detail === 2) {
|
||||
this._onDoubleClick(event);
|
||||
} else if (event.detail === 3) {
|
||||
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.
|
||||
* @param event The mouse event.
|
||||
*/
|
||||
private _onShiftClick(event: MouseEvent): void {
|
||||
private _onIncrementalClick(event: MouseEvent): void {
|
||||
if (this._model.selectionStart) {
|
||||
this._model.selectionEnd = this._getMouseBufferCoords(event);
|
||||
}
|
||||
@@ -454,15 +403,25 @@ export class SelectionManager extends EventEmitter {
|
||||
this._model.selectionStartLength = 0;
|
||||
this._model.isSelectAllActive = false;
|
||||
this._activeSelectionMode = SelectionMode.NORMAL;
|
||||
|
||||
// Initialize the new selection
|
||||
this._model.selectionStart = this._getMouseBufferCoords(event);
|
||||
if (this._model.selectionStart) {
|
||||
this._model.selectionEnd = null;
|
||||
// If the mouse is over the second half of a wide character, adjust the
|
||||
// selection to cover the whole character
|
||||
const char = this._buffer.get(this._model.selectionStart[1])[this._model.selectionStart[0]];
|
||||
if (char[LINE_DATA_WIDTH_INDEX] === 0) {
|
||||
this._model.selectionStart[0]++;
|
||||
}
|
||||
if (!this._model.selectionStart) {
|
||||
return;
|
||||
}
|
||||
this._model.selectionEnd = null;
|
||||
|
||||
// Ensure the line exists
|
||||
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
|
||||
* 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
|
||||
this._model.selectionEnd = this._getMouseBufferCoords(event);
|
||||
if (!this._model.selectionEnd) {
|
||||
this.refresh(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Select the entire line if line select mode is active.
|
||||
if (this._activeSelectionMode === SelectionMode.LINE) {
|
||||
@@ -564,8 +501,8 @@ export class SelectionManager extends EventEmitter {
|
||||
|
||||
// Only draw here if the selection changes.
|
||||
if (!previousSelectionEnd ||
|
||||
previousSelectionEnd[0] !== this._model.selectionEnd[0] ||
|
||||
previousSelectionEnd[1] !== this._model.selectionEnd[1]) {
|
||||
previousSelectionEnd[0] !== this._model.selectionEnd[0] ||
|
||||
previousSelectionEnd[1] !== this._model.selectionEnd[1]) {
|
||||
this.refresh(true);
|
||||
}
|
||||
}
|
||||
@@ -579,9 +516,9 @@ export class SelectionManager extends EventEmitter {
|
||||
this._terminal.scrollDisp(this._dragScrollAmount, false);
|
||||
// Re-evaluate selection
|
||||
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 {
|
||||
this._model.selectionEnd = [0, this._terminal.ydisp];
|
||||
this._model.selectionEnd = [0, this._terminal.buffer.ydisp];
|
||||
}
|
||||
this.refresh();
|
||||
}
|
||||
@@ -611,13 +548,25 @@ export class SelectionManager extends EventEmitter {
|
||||
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.
|
||||
* @param coords The coordinates to get the word at.
|
||||
*/
|
||||
private _getWordAt(coords: [number, number]): IWordPosition {
|
||||
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
|
||||
let endIndex = this._convertViewportColToCharacterIndex(bufferLine, coords);
|
||||
@@ -676,7 +625,7 @@ export class SelectionManager extends EventEmitter {
|
||||
|
||||
const start = startIndex + charOffset - leftWideCharCount;
|
||||
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 {
|
||||
const wordPosition = this._getWordAt(coords);
|
||||
this._model.selectionStart = [wordPosition.start, coords[1]];
|
||||
this._model.selectionStartLength = wordPosition.length;
|
||||
if (wordPosition) {
|
||||
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 {
|
||||
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 { ITerminal } from './Interfaces';
|
||||
import { SelectionModel } from './SelectionModel';
|
||||
import {BufferSet} from './BufferSet';
|
||||
import { MockTerminal } from './utils/TestUtils';
|
||||
|
||||
class TestSelectionModel extends SelectionModel {
|
||||
constructor(
|
||||
@@ -21,7 +23,13 @@ describe('SelectionManager', () => {
|
||||
let model: TestSelectionModel;
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ export class SelectionModel {
|
||||
*/
|
||||
public get finalSelectionEnd(): [number, number] {
|
||||
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) {
|
||||
|
||||
+11
-9
@@ -1,10 +1,10 @@
|
||||
import { assert } from 'chai';
|
||||
import { Viewport } from './Viewport';
|
||||
import {BufferSet} from './BufferSet';
|
||||
|
||||
describe('Viewport', () => {
|
||||
let terminal;
|
||||
let viewportElement;
|
||||
let selectionContainer;
|
||||
let charMeasure;
|
||||
let viewport;
|
||||
let scrollAreaElement;
|
||||
@@ -13,7 +13,6 @@ describe('Viewport', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
terminal = {
|
||||
lines: [],
|
||||
rows: 0,
|
||||
ydisp: 0,
|
||||
on: () => {},
|
||||
@@ -26,8 +25,11 @@ describe('Viewport', () => {
|
||||
style: {
|
||||
height: 0
|
||||
}
|
||||
}
|
||||
},
|
||||
scrollback: 10
|
||||
};
|
||||
terminal.buffers = new BufferSet(terminal);
|
||||
terminal.buffer = terminal.buffers.active;
|
||||
viewportElement = {
|
||||
addEventListener: () => {},
|
||||
style: {
|
||||
@@ -60,14 +62,14 @@ describe('Viewport', () => {
|
||||
}, 0);
|
||||
});
|
||||
it('should set the height of the viewport when the line-height changed', () => {
|
||||
terminal.lines.push('');
|
||||
terminal.lines.push('');
|
||||
terminal.buffer.lines.push('');
|
||||
terminal.buffer.lines.push('');
|
||||
terminal.rows = 1;
|
||||
viewport.refresh();
|
||||
assert.equal(viewportElement.style.height, 1 * CHARACTER_HEIGHT + 'px');
|
||||
charMeasure.height = 20;
|
||||
charMeasure.height = 2 * CHARACTER_HEIGHT;
|
||||
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 => {
|
||||
// Allow CharMeasure to be initialized
|
||||
setTimeout(() => {
|
||||
terminal.lines.push('');
|
||||
terminal.buffer.lines.push('');
|
||||
terminal.rows = 1;
|
||||
assert.equal(scrollAreaElement.style.height, 0 * CHARACTER_HEIGHT + 'px');
|
||||
viewport.syncScrollArea();
|
||||
assert.equal(viewportElement.style.height, 1 * CHARACTER_HEIGHT + 'px');
|
||||
assert.equal(scrollAreaElement.style.height, 1 * CHARACTER_HEIGHT + 'px');
|
||||
terminal.lines.push('');
|
||||
terminal.buffer.lines.push('');
|
||||
viewport.syncScrollArea();
|
||||
assert.equal(viewportElement.style.height, 1 * 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 viewportElement The DOM element acting as the viewport.
|
||||
* @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(
|
||||
private terminal: ITerminal,
|
||||
@@ -43,8 +43,6 @@ export class Viewport {
|
||||
/**
|
||||
* Refreshes row height, setting line-height, viewport height and scroll area height if
|
||||
* necessary.
|
||||
* @param charSize A character size measurement bounding rect object, if it doesn't exist it will
|
||||
* be created.
|
||||
*/
|
||||
private refresh(): void {
|
||||
if (this.charMeasure.height > 0) {
|
||||
@@ -68,9 +66,9 @@ export class Viewport {
|
||||
* Updates dimensions and synchronizes the scroll area if necessary.
|
||||
*/
|
||||
public syncScrollArea(): void {
|
||||
if (this.lastRecordedBufferLength !== this.terminal.lines.length) {
|
||||
if (this.lastRecordedBufferLength !== this.terminal.buffer.lines.length) {
|
||||
// If buffer height changed
|
||||
this.lastRecordedBufferLength = this.terminal.lines.length;
|
||||
this.lastRecordedBufferLength = this.terminal.buffer.lines.length;
|
||||
this.refresh();
|
||||
} else if (this.lastRecordedViewportHeight !== this.terminal.rows) {
|
||||
// If viewport height changed
|
||||
@@ -83,7 +81,7 @@ export class Viewport {
|
||||
}
|
||||
|
||||
// Sync scrollTop
|
||||
const scrollTop = this.terminal.ydisp * this.currentRowHeight;
|
||||
const scrollTop = this.terminal.buffer.ydisp * this.currentRowHeight;
|
||||
if (this.viewportElement.scrollTop !== scrollTop) {
|
||||
this.viewportElement.scrollTop = scrollTop;
|
||||
}
|
||||
@@ -96,7 +94,7 @@ export class Viewport {
|
||||
*/
|
||||
private onScroll(ev: Event) {
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 os = require('os');
|
||||
var pty = require('node-pty');
|
||||
var sleep = require('sleep');
|
||||
var Terminal = require('../xterm');
|
||||
|
||||
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
|
||||
// pty.js opens pipe fds with O_NONBLOCK
|
||||
// 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);
|
||||
sleep.usleep(10000);
|
||||
var b = Buffer(64000);
|
||||
var bytes = fs.readSync(primitive_pty.master, b, 0, 64000);
|
||||
return b.toString('utf8', 0, bytes);
|
||||
setTimeout(() => {
|
||||
var b = Buffer(64000);
|
||||
var bytes = fs.readSync(primitive_pty.master, b, 0, 64000);
|
||||
cb(b.toString('utf8', 0, bytes));
|
||||
});
|
||||
}
|
||||
|
||||
// make sure raw pty is at x=0 and has no pending data
|
||||
function pty_reset() {
|
||||
pty_write_read('\r\n');
|
||||
function ptyReset(cb) {
|
||||
ptyWriteRead('\r\n', cb);
|
||||
}
|
||||
|
||||
/* debug helpers */
|
||||
@@ -62,10 +62,10 @@ function formatError(in_, out_, expected) {
|
||||
function terminalToString(term) {
|
||||
var result = '';
|
||||
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 = '';
|
||||
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
|
||||
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,
|
||||
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++) {
|
||||
if (skip.indexOf(i) >= 0) {
|
||||
continue;
|
||||
}
|
||||
(function(filename) {
|
||||
it(filename.split('/').slice(-1)[0], function () {
|
||||
pty_reset();
|
||||
var in_file = fs.readFileSync(filename, 'utf8');
|
||||
var from_pty = pty_write_read(in_file);
|
||||
// uncomment this to get log from terminal
|
||||
//console.log = function(){};
|
||||
it(filename.split('/').slice(-1)[0], done => {
|
||||
ptyReset(() => {
|
||||
var in_file = fs.readFileSync(filename, 'utf8');
|
||||
ptyWriteRead(in_file, from_pty => {
|
||||
// uncomment this to get log from terminal
|
||||
//console.log = function(){};
|
||||
|
||||
// Perform a synchronous .write(data)
|
||||
xterm.writeBuffer.push(from_pty);
|
||||
xterm.innerWrite();
|
||||
// Perform a synchronous .write(data)
|
||||
xterm.writeBuffer.push(from_pty);
|
||||
xterm.innerWrite();
|
||||
|
||||
var from_emulator = terminalToString(xterm);
|
||||
console.log = CONSOLE_LOG;
|
||||
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
|
||||
// from xterm.js so ignore this for now at least.
|
||||
var expectedRightTrimmed = expected.split('\n').map(function (l) {
|
||||
return l.replace(/\s+$/, '');
|
||||
}).join('\n');
|
||||
if (from_emulator != expectedRightTrimmed) {
|
||||
// uncomment to get noisy output
|
||||
throw new Error(formatError(in_file, from_emulator, expected));
|
||||
// throw new Error('mismatch');
|
||||
}
|
||||
var from_emulator = terminalToString(xterm);
|
||||
console.log = CONSOLE_LOG;
|
||||
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
|
||||
// from xterm.js so ignore this for now at least.
|
||||
var expectedRightTrimmed = expected.split('\n').map(function (l) {
|
||||
return l.replace(/\s+$/, '');
|
||||
}).join('\n');
|
||||
if (from_emulator != expectedRightTrimmed) {
|
||||
// uncomment to get noisy output
|
||||
throw new Error(formatError(in_file, from_emulator, expected));
|
||||
// throw new Error('mismatch');
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
})(files[i]);
|
||||
}
|
||||
|
||||
+128
-128
@@ -64,15 +64,15 @@ describe('xterm.js', function() {
|
||||
|
||||
describe('clear', 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();
|
||||
assert.equal(xterm.y, 0);
|
||||
assert.equal(xterm.ybase, 0);
|
||||
assert.equal(xterm.ydisp, 0);
|
||||
assert.equal(xterm.lines.length, xterm.rows);
|
||||
assert.deepEqual(xterm.lines.get(0), promptLine);
|
||||
assert.equal(xterm.buffer.y, 0);
|
||||
assert.equal(xterm.buffer.ybase, 0);
|
||||
assert.equal(xterm.buffer.ydisp, 0);
|
||||
assert.equal(xterm.buffer.lines.length, xterm.rows);
|
||||
assert.deepEqual(xterm.buffer.lines.get(0), promptLine);
|
||||
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() {
|
||||
@@ -81,28 +81,28 @@ describe('xterm.js', function() {
|
||||
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();
|
||||
assert.equal(xterm.y, 0);
|
||||
assert.equal(xterm.ybase, 0);
|
||||
assert.equal(xterm.ydisp, 0);
|
||||
assert.equal(xterm.lines.length, xterm.rows);
|
||||
assert.deepEqual(xterm.lines.get(0), promptLine);
|
||||
assert.equal(xterm.buffer.y, 0);
|
||||
assert.equal(xterm.buffer.ybase, 0);
|
||||
assert.equal(xterm.buffer.ydisp, 0);
|
||||
assert.equal(xterm.buffer.lines.length, xterm.rows);
|
||||
assert.deepEqual(xterm.buffer.lines.get(0), promptLine);
|
||||
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() {
|
||||
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.ybase, 0);
|
||||
assert.equal(xterm.ydisp, 0);
|
||||
assert.equal(xterm.lines.length, xterm.rows);
|
||||
assert.deepEqual(xterm.lines.get(0), promptLine);
|
||||
assert.equal(xterm.buffer.y, 0);
|
||||
assert.equal(xterm.buffer.ybase, 0);
|
||||
assert.equal(xterm.buffer.ydisp, 0);
|
||||
assert.equal(xterm.buffer.lines.length, xterm.rows);
|
||||
assert.deepEqual(xterm.buffer.lines.get(0), promptLine);
|
||||
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;
|
||||
});
|
||||
it('should scroll a single line', function() {
|
||||
assert.equal(xterm.ydisp, startYDisp);
|
||||
assert.equal(xterm.buffer.ydisp, startYDisp);
|
||||
xterm.scrollDisp(-1);
|
||||
assert.equal(xterm.ydisp, startYDisp - 1);
|
||||
assert.equal(xterm.buffer.ydisp, startYDisp - 1);
|
||||
xterm.scrollDisp(1);
|
||||
assert.equal(xterm.ydisp, startYDisp);
|
||||
assert.equal(xterm.buffer.ydisp, startYDisp);
|
||||
});
|
||||
it('should scroll multiple lines', function() {
|
||||
assert.equal(xterm.ydisp, startYDisp);
|
||||
assert.equal(xterm.buffer.ydisp, startYDisp);
|
||||
xterm.scrollDisp(-5);
|
||||
assert.equal(xterm.ydisp, startYDisp - 5);
|
||||
assert.equal(xterm.buffer.ydisp, startYDisp - 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() {
|
||||
assert.equal(xterm.ydisp, startYDisp);
|
||||
assert.equal(xterm.buffer.ydisp, startYDisp);
|
||||
xterm.scrollDisp(1);
|
||||
assert.equal(xterm.ydisp, startYDisp);
|
||||
assert.equal(xterm.buffer.ydisp, startYDisp);
|
||||
for (var i = 0; i < startYDisp; i++) {
|
||||
xterm.scrollDisp(-1);
|
||||
}
|
||||
assert.equal(xterm.ydisp, 0);
|
||||
assert.equal(xterm.buffer.ydisp, 0);
|
||||
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;
|
||||
});
|
||||
it('should scroll a single page', function() {
|
||||
assert.equal(xterm.ydisp, startYDisp);
|
||||
assert.equal(xterm.buffer.ydisp, startYDisp);
|
||||
xterm.scrollPages(-1);
|
||||
assert.equal(xterm.ydisp, startYDisp - (xterm.rows - 1));
|
||||
assert.equal(xterm.buffer.ydisp, startYDisp - (xterm.rows - 1));
|
||||
xterm.scrollPages(1);
|
||||
assert.equal(xterm.ydisp, startYDisp);
|
||||
assert.equal(xterm.buffer.ydisp, startYDisp);
|
||||
});
|
||||
it('should scroll a multiple pages', function() {
|
||||
assert.equal(xterm.ydisp, startYDisp);
|
||||
assert.equal(xterm.buffer.ydisp, startYDisp);
|
||||
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);
|
||||
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() {
|
||||
assert.notEqual(xterm.ydisp, 0);
|
||||
assert.notEqual(xterm.buffer.ydisp, 0);
|
||||
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() {
|
||||
xterm.scrollDisp(-1);
|
||||
xterm.scrollToBottom();
|
||||
assert.equal(xterm.ydisp, startYDisp);
|
||||
assert.equal(xterm.buffer.ydisp, startYDisp);
|
||||
xterm.scrollPages(-1);
|
||||
xterm.scrollToBottom();
|
||||
assert.equal(xterm.ydisp, startYDisp);
|
||||
assert.equal(xterm.buffer.ydisp, startYDisp);
|
||||
xterm.scrollToTop();
|
||||
xterm.scrollToBottom();
|
||||
assert.equal(xterm.ydisp, startYDisp);
|
||||
assert.equal(xterm.buffer.ydisp, startYDisp);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -214,13 +214,13 @@ describe('xterm.js', function() {
|
||||
stopPropagation: function(){}
|
||||
};
|
||||
|
||||
xterm.ydisp = 0;
|
||||
xterm.ybase = 40;
|
||||
assert.notEqual(xterm.ydisp, xterm.ybase);
|
||||
xterm.buffer.ydisp = 0;
|
||||
xterm.buffer.ybase = 40;
|
||||
assert.notEqual(xterm.buffer.ydisp, xterm.buffer.ybase);
|
||||
xterm.keyDown(event);
|
||||
|
||||
// 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 () {
|
||||
@@ -233,11 +233,11 @@ describe('xterm.js', function() {
|
||||
return false;
|
||||
});
|
||||
|
||||
assert.equal(xterm.ydisp, startYDisp);
|
||||
assert.equal(xterm.buffer.ydisp, startYDisp);
|
||||
xterm.scrollDisp(-1);
|
||||
assert.equal(xterm.ydisp, startYDisp - 1);
|
||||
assert.equal(xterm.buffer.ydisp, startYDisp - 1);
|
||||
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);
|
||||
for (var i=0xDC00; i<=0xDCFF; ++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].length).eql(2);
|
||||
expect(tchar[2]).eql(1);
|
||||
expect(xterm.lines.get(0)[1][1]).eql(' ');
|
||||
expect(xterm.buffer.lines.get(0)[1][1]).eql(' ');
|
||||
xterm.reset();
|
||||
}
|
||||
});
|
||||
it('2 characters at last cell', function() {
|
||||
var high = String.fromCharCode(0xD800);
|
||||
for (var i=0xDC00; i<=0xDCFF; ++i) {
|
||||
xterm.x = xterm.cols - 1;
|
||||
xterm.buffer.x = xterm.cols - 1;
|
||||
xterm.write(high + String.fromCharCode(i));
|
||||
expect(xterm.lines.get(0)[xterm.x-1][1]).eql(high + String.fromCharCode(i));
|
||||
expect(xterm.lines.get(0)[xterm.x-1][1].length).eql(2);
|
||||
expect(xterm.lines.get(1)[0][1]).eql(' ');
|
||||
expect(xterm.buffer.lines.get(0)[xterm.buffer.x-1][1]).eql(high + String.fromCharCode(i));
|
||||
expect(xterm.buffer.lines.get(0)[xterm.buffer.x-1][1].length).eql(2);
|
||||
expect(xterm.buffer.lines.get(1)[0][1]).eql(' ');
|
||||
xterm.reset();
|
||||
}
|
||||
});
|
||||
it('2 characters per cell over line end with autowrap', function() {
|
||||
var high = String.fromCharCode(0xD800);
|
||||
for (var i=0xDC00; i<=0xDCFF; ++i) {
|
||||
xterm.x = xterm.cols - 1;
|
||||
xterm.buffer.x = xterm.cols - 1;
|
||||
xterm.wraparoundMode = true;
|
||||
xterm.write('a' + high + String.fromCharCode(i));
|
||||
expect(xterm.lines.get(0)[xterm.cols-1][1]).eql('a');
|
||||
expect(xterm.lines.get(1)[0][1]).eql(high + String.fromCharCode(i));
|
||||
expect(xterm.lines.get(1)[0][1].length).eql(2);
|
||||
expect(xterm.lines.get(1)[1][1]).eql(' ');
|
||||
expect(xterm.buffer.lines.get(0)[xterm.cols-1][1]).eql('a');
|
||||
expect(xterm.buffer.lines.get(1)[0][1]).eql(high + String.fromCharCode(i));
|
||||
expect(xterm.buffer.lines.get(1)[0][1].length).eql(2);
|
||||
expect(xterm.buffer.lines.get(1)[1][1]).eql(' ');
|
||||
xterm.reset();
|
||||
}
|
||||
});
|
||||
it('2 characters per cell over line end without autowrap', function() {
|
||||
var high = String.fromCharCode(0xD800);
|
||||
for (var i=0xDC00; i<=0xDCFF; ++i) {
|
||||
xterm.x = xterm.cols - 1;
|
||||
xterm.buffer.x = xterm.cols - 1;
|
||||
xterm.wraparoundMode = false;
|
||||
xterm.write('a' + high + String.fromCharCode(i));
|
||||
// auto wraparound mode should cut off the rest of the line
|
||||
expect(xterm.lines.get(0)[xterm.cols-1][1]).eql('a');
|
||||
expect(xterm.lines.get(0)[xterm.cols-1][1].length).eql(1);
|
||||
expect(xterm.lines.get(1)[1][1]).eql(' ');
|
||||
expect(xterm.buffer.lines.get(0)[xterm.cols-1][1]).eql('a');
|
||||
expect(xterm.buffer.lines.get(0)[xterm.cols-1][1].length).eql(1);
|
||||
expect(xterm.buffer.lines.get(1)[1][1]).eql(' ');
|
||||
xterm.reset();
|
||||
}
|
||||
});
|
||||
@@ -625,11 +625,11 @@ describe('xterm.js', function() {
|
||||
for (var i=0xDC00; i<=0xDCFF; ++i) {
|
||||
xterm.write(high);
|
||||
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].length).eql(2);
|
||||
expect(tchar[2]).eql(1);
|
||||
expect(xterm.lines.get(0)[1][1]).eql(' ');
|
||||
expect(xterm.buffer.lines.get(0)[1][1]).eql(' ');
|
||||
xterm.reset();
|
||||
}
|
||||
});
|
||||
@@ -638,30 +638,30 @@ describe('xterm.js', function() {
|
||||
describe('unicode - combining characters', function() {
|
||||
it('café', function () {
|
||||
xterm.write('cafe\u0301');
|
||||
expect(xterm.lines.get(0)[3][1]).eql('e\u0301');
|
||||
expect(xterm.lines.get(0)[3][1].length).eql(2);
|
||||
expect(xterm.lines.get(0)[3][2]).eql(1);
|
||||
expect(xterm.buffer.lines.get(0)[3][1]).eql('e\u0301');
|
||||
expect(xterm.buffer.lines.get(0)[3][1].length).eql(2);
|
||||
expect(xterm.buffer.lines.get(0)[3][2]).eql(1);
|
||||
});
|
||||
it('café - end of line', function() {
|
||||
xterm.x = xterm.cols - 1 - 3;
|
||||
xterm.buffer.x = xterm.cols - 1 - 3;
|
||||
xterm.write('cafe\u0301');
|
||||
expect(xterm.lines.get(0)[xterm.cols-1][1]).eql('e\u0301');
|
||||
expect(xterm.lines.get(0)[xterm.cols-1][1].length).eql(2);
|
||||
expect(xterm.lines.get(0)[xterm.cols-1][2]).eql(1);
|
||||
expect(xterm.lines.get(0)[1][1]).eql(' ');
|
||||
expect(xterm.lines.get(0)[1][1].length).eql(1);
|
||||
expect(xterm.lines.get(0)[1][2]).eql(1);
|
||||
expect(xterm.buffer.lines.get(0)[xterm.cols-1][1]).eql('e\u0301');
|
||||
expect(xterm.buffer.lines.get(0)[xterm.cols-1][1].length).eql(2);
|
||||
expect(xterm.buffer.lines.get(0)[xterm.cols-1][2]).eql(1);
|
||||
expect(xterm.buffer.lines.get(0)[1][1]).eql(' ');
|
||||
expect(xterm.buffer.lines.get(0)[1][1].length).eql(1);
|
||||
expect(xterm.buffer.lines.get(0)[1][2]).eql(1);
|
||||
});
|
||||
it('multiple combined é', function() {
|
||||
xterm.wraparoundMode = true;
|
||||
xterm.write(Array(100).join('e\u0301'));
|
||||
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].length).eql(2);
|
||||
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].length).eql(2);
|
||||
expect(tchar[2]).eql(1);
|
||||
@@ -670,12 +670,12 @@ describe('xterm.js', function() {
|
||||
xterm.wraparoundMode = true;
|
||||
xterm.write(Array(100).join('\uD800\uDC00\u0301'));
|
||||
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].length).eql(3);
|
||||
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].length).eql(3);
|
||||
expect(tchar[2]).eql(1);
|
||||
@@ -684,21 +684,21 @@ describe('xterm.js', function() {
|
||||
|
||||
describe('unicode - fullwidth characters', function() {
|
||||
it('cursor movement even', function() {
|
||||
expect(xterm.x).eql(0);
|
||||
expect(xterm.buffer.x).eql(0);
|
||||
xterm.write('¥');
|
||||
expect(xterm.x).eql(2);
|
||||
expect(xterm.buffer.x).eql(2);
|
||||
});
|
||||
it('cursor movement odd', function() {
|
||||
xterm.x = 1;
|
||||
expect(xterm.x).eql(1);
|
||||
xterm.buffer.x = 1;
|
||||
expect(xterm.buffer.x).eql(1);
|
||||
xterm.write('¥');
|
||||
expect(xterm.x).eql(3);
|
||||
expect(xterm.buffer.x).eql(3);
|
||||
});
|
||||
it('line of ¥ even', function() {
|
||||
xterm.wraparoundMode = true;
|
||||
xterm.write(Array(50).join('¥'));
|
||||
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) {
|
||||
expect(tchar[1]).eql('');
|
||||
expect(tchar[1].length).eql(0);
|
||||
@@ -709,17 +709,17 @@ describe('xterm.js', function() {
|
||||
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].length).eql(1);
|
||||
expect(tchar[2]).eql(2);
|
||||
});
|
||||
it('line of ¥ odd', function() {
|
||||
xterm.wraparoundMode = true;
|
||||
xterm.x = 1;
|
||||
xterm.buffer.x = 1;
|
||||
xterm.write(Array(50).join('¥'));
|
||||
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)) {
|
||||
expect(tchar[1]).eql('');
|
||||
expect(tchar[1].length).eql(0);
|
||||
@@ -730,21 +730,21 @@ describe('xterm.js', function() {
|
||||
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].length).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].length).eql(1);
|
||||
expect(tchar[2]).eql(2);
|
||||
});
|
||||
it('line of ¥ with combining odd', function() {
|
||||
xterm.wraparoundMode = true;
|
||||
xterm.x = 1;
|
||||
xterm.buffer.x = 1;
|
||||
xterm.write(Array(50).join('¥\u0301'));
|
||||
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)) {
|
||||
expect(tchar[1]).eql('');
|
||||
expect(tchar[1].length).eql(0);
|
||||
@@ -755,11 +755,11 @@ describe('xterm.js', function() {
|
||||
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].length).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].length).eql(2);
|
||||
expect(tchar[2]).eql(2);
|
||||
@@ -768,7 +768,7 @@ describe('xterm.js', function() {
|
||||
xterm.wraparoundMode = true;
|
||||
xterm.write(Array(50).join('¥\u0301'));
|
||||
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) {
|
||||
expect(tchar[1]).eql('');
|
||||
expect(tchar[1].length).eql(0);
|
||||
@@ -779,17 +779,17 @@ describe('xterm.js', function() {
|
||||
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].length).eql(2);
|
||||
expect(tchar[2]).eql(2);
|
||||
});
|
||||
it('line of surrogate fullwidth with combining odd', function() {
|
||||
xterm.wraparoundMode = true;
|
||||
xterm.x = 1;
|
||||
xterm.buffer.x = 1;
|
||||
xterm.write(Array(50).join('\ud843\ude6d\u0301'));
|
||||
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)) {
|
||||
expect(tchar[1]).eql('');
|
||||
expect(tchar[1].length).eql(0);
|
||||
@@ -800,11 +800,11 @@ describe('xterm.js', function() {
|
||||
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].length).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].length).eql(3);
|
||||
expect(tchar[2]).eql(2);
|
||||
@@ -813,7 +813,7 @@ describe('xterm.js', function() {
|
||||
xterm.wraparoundMode = true;
|
||||
xterm.write(Array(50).join('\ud843\ude6d\u0301'));
|
||||
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) {
|
||||
expect(tchar[1]).eql('');
|
||||
expect(tchar[1].length).eql(0);
|
||||
@@ -824,7 +824,7 @@ describe('xterm.js', function() {
|
||||
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].length).eql(3);
|
||||
expect(tchar[2]).eql(2);
|
||||
@@ -834,44 +834,44 @@ describe('xterm.js', function() {
|
||||
describe('insert mode', function() {
|
||||
it('halfwidth - all', function () {
|
||||
xterm.write(Array(9).join('0123456789').slice(-80));
|
||||
xterm.x = 10;
|
||||
xterm.y = 0;
|
||||
xterm.buffer.x = 10;
|
||||
xterm.buffer.y = 0;
|
||||
xterm.insertMode = true;
|
||||
xterm.write('abcde');
|
||||
expect(xterm.lines.get(0).length).eql(xterm.cols);
|
||||
expect(xterm.lines.get(0)[10][1]).eql('a');
|
||||
expect(xterm.lines.get(0)[14][1]).eql('e');
|
||||
expect(xterm.lines.get(0)[15][1]).eql('0');
|
||||
expect(xterm.lines.get(0)[79][1]).eql('4');
|
||||
expect(xterm.buffer.lines.get(0).length).eql(xterm.cols);
|
||||
expect(xterm.buffer.lines.get(0)[10][1]).eql('a');
|
||||
expect(xterm.buffer.lines.get(0)[14][1]).eql('e');
|
||||
expect(xterm.buffer.lines.get(0)[15][1]).eql('0');
|
||||
expect(xterm.buffer.lines.get(0)[79][1]).eql('4');
|
||||
});
|
||||
it('fullwidth - insert', function() {
|
||||
xterm.write(Array(9).join('0123456789').slice(-80));
|
||||
xterm.x = 10;
|
||||
xterm.y = 0;
|
||||
xterm.buffer.x = 10;
|
||||
xterm.buffer.y = 0;
|
||||
xterm.insertMode = true;
|
||||
xterm.write('¥¥¥');
|
||||
expect(xterm.lines.get(0).length).eql(xterm.cols);
|
||||
expect(xterm.lines.get(0)[10][1]).eql('¥');
|
||||
expect(xterm.lines.get(0)[11][1]).eql('');
|
||||
expect(xterm.lines.get(0)[14][1]).eql('¥');
|
||||
expect(xterm.lines.get(0)[15][1]).eql('');
|
||||
expect(xterm.lines.get(0)[79][1]).eql('3');
|
||||
expect(xterm.buffer.lines.get(0).length).eql(xterm.cols);
|
||||
expect(xterm.buffer.lines.get(0)[10][1]).eql('¥');
|
||||
expect(xterm.buffer.lines.get(0)[11][1]).eql('');
|
||||
expect(xterm.buffer.lines.get(0)[14][1]).eql('¥');
|
||||
expect(xterm.buffer.lines.get(0)[15][1]).eql('');
|
||||
expect(xterm.buffer.lines.get(0)[79][1]).eql('3');
|
||||
});
|
||||
it('fullwidth - right border', function() {
|
||||
xterm.write(Array(41).join('¥'));
|
||||
xterm.x = 10;
|
||||
xterm.y = 0;
|
||||
xterm.buffer.x = 10;
|
||||
xterm.buffer.y = 0;
|
||||
xterm.insertMode = true;
|
||||
xterm.write('a');
|
||||
expect(xterm.lines.get(0).length).eql(xterm.cols);
|
||||
expect(xterm.lines.get(0)[10][1]).eql('a');
|
||||
expect(xterm.lines.get(0)[11][1]).eql('¥');
|
||||
expect(xterm.lines.get(0)[79][1]).eql(' '); // fullwidth char got replaced
|
||||
expect(xterm.buffer.lines.get(0).length).eql(xterm.cols);
|
||||
expect(xterm.buffer.lines.get(0)[10][1]).eql('a');
|
||||
expect(xterm.buffer.lines.get(0)[11][1]).eql('¥');
|
||||
expect(xterm.buffer.lines.get(0)[79][1]).eql(' '); // fullwidth char got replaced
|
||||
xterm.write('b');
|
||||
expect(xterm.lines.get(0).length).eql(xterm.cols);
|
||||
expect(xterm.lines.get(0)[11][1]).eql('b');
|
||||
expect(xterm.lines.get(0)[12][1]).eql('¥');
|
||||
expect(xterm.lines.get(0)[79][1]).eql(''); // empty cell after fullwidth
|
||||
expect(xterm.buffer.lines.get(0).length).eql(xterm.cols);
|
||||
expect(xterm.buffer.lines.get(0)[11][1]).eql('b');
|
||||
expect(xterm.buffer.lines.get(0)[12][1]).eql('¥');
|
||||
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';
|
||||
|
||||
describe('CharMeasure', () => {
|
||||
let dom: jsdom.JSDOM;
|
||||
let window: Window;
|
||||
let document: Document;
|
||||
|
||||
let container: HTMLElement;
|
||||
let charMeasure: ICharMeasure;
|
||||
|
||||
beforeEach(done => {
|
||||
jsdom.env('', (err, w) => {
|
||||
window = w;
|
||||
document = window.document;
|
||||
container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
charMeasure = new CharMeasure(document, container);
|
||||
done();
|
||||
});
|
||||
beforeEach(() => {
|
||||
dom = new jsdom.JSDOM('');
|
||||
window = dom.window;
|
||||
document = window.document;
|
||||
container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
charMeasure = new CharMeasure(document, container);
|
||||
});
|
||||
|
||||
describe('measure', () => {
|
||||
|
||||
@@ -5,8 +5,9 @@
|
||||
* @license MIT
|
||||
*/
|
||||
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 _startIndex: 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.
|
||||
*/
|
||||
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);
|
||||
if (!coords) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Convert to cols/rows.
|
||||
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;
|
||||
}
|
||||
|
||||
.terminal.focus:not(.xterm-cursor-style-underline):not(.xterm-cursor-style-bar) .terminal-cursor {
|
||||
background-color: #fff;
|
||||
color: #000;
|
||||
.terminal .terminal-cursor {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.terminal:not(.focus) .terminal-cursor {
|
||||
outline: 1px solid #fff;
|
||||
outline-offset: -1px;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.terminal:not(.xterm-cursor-style-underline):not(.xterm-cursor-style-bar).focus.xterm-cursor-blink-on .terminal-cursor {
|
||||
background-color: transparent;
|
||||
color: inherit;
|
||||
.terminal.xterm-cursor-style-block.focus:not(.xterm-cursor-blink-on) .terminal-cursor {
|
||||
background-color: #fff;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.terminal.xterm-cursor-style-bar .terminal-cursor,
|
||||
.terminal.xterm-cursor-style-underline .terminal-cursor {
|
||||
position: relative;
|
||||
}
|
||||
.terminal.xterm-cursor-style-bar .terminal-cursor::before,
|
||||
.terminal.xterm-cursor-style-underline .terminal-cursor::before {
|
||||
content: "";
|
||||
display: block;
|
||||
.terminal.focus.xterm-cursor-style-bar:not(.xterm-cursor-blink-on) .terminal-cursor::before,
|
||||
.terminal.focus.xterm-cursor-style-underline:not(.xterm-cursor-blink-on) .terminal-cursor::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
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;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
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;
|
||||
left: 0;
|
||||
right: 0;
|
||||
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 {
|
||||
background: #000;
|
||||
@@ -217,6 +204,10 @@
|
||||
text-decoration: blink;
|
||||
}
|
||||
|
||||
.terminal .xterm-blink.xterm-underline {
|
||||
text-decoration: blink underline;
|
||||
}
|
||||
|
||||
.terminal .xterm-hidden {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
+128
-204
@@ -10,6 +10,7 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { BufferSet } from './BufferSet';
|
||||
import { CompositionHelper } from './CompositionHelper';
|
||||
import { EventEmitter } from './EventEmitter';
|
||||
import { Viewport } from './Viewport';
|
||||
@@ -26,6 +27,7 @@ import * as Browser from './utils/Browser';
|
||||
import * as Mouse from './utils/Mouse';
|
||||
import { CHARSETS } from './Charsets';
|
||||
import { getRawByteCoords } from './utils/Mouse';
|
||||
import { translateBufferLineToString } from './utils/BufferLine';
|
||||
|
||||
/**
|
||||
* Terminal Emulation References:
|
||||
@@ -140,33 +142,10 @@ function Terminal(options) {
|
||||
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.cursorHidden = false;
|
||||
this.convertEol;
|
||||
this.queue = '';
|
||||
this.scrollTop = 0;
|
||||
this.scrollBottom = this.rows - 1;
|
||||
this.customKeyEventHandler = null;
|
||||
this.cursorBlinkInterval = null;
|
||||
|
||||
@@ -176,7 +155,6 @@ function Terminal(options) {
|
||||
this.originMode = false;
|
||||
this.insertMode = false;
|
||||
this.wraparoundMode = true; // defaults: xterm - true, vt100 - false
|
||||
this.normal = null;
|
||||
|
||||
// charset
|
||||
this.charset = null;
|
||||
@@ -242,21 +220,18 @@ function Terminal(options) {
|
||||
// leftover surrogate high from previous write invocation
|
||||
this.surrogate_high = '';
|
||||
|
||||
/**
|
||||
* An array of all lines in the entire buffer, including the prompt. The lines are array of
|
||||
* characters which are 2-length arrays where [0] is an attribute and [1] is the character.
|
||||
*/
|
||||
this.lines = new CircularList(this.scrollback);
|
||||
var i = this.rows;
|
||||
while (i--) {
|
||||
this.lines.push(this.blankLine());
|
||||
}
|
||||
// Create the terminal's buffers and set the current buffer
|
||||
this.buffers = new BufferSet(this);
|
||||
this.buffer = this.buffers.active; // Convenience shortcut;
|
||||
this.buffers.on('activate', function (buffer) {
|
||||
this._terminal.buffer = buffer;
|
||||
});
|
||||
|
||||
// Ensure the selection manager has the correct buffer
|
||||
if (this.selectionManager) {
|
||||
this.selectionManager.setBuffer(this.lines);
|
||||
this.selectionManager.setBuffer(this.buffer.lines);
|
||||
}
|
||||
|
||||
this.tabs;
|
||||
this.setupStops();
|
||||
|
||||
// Store if user went browsing history in scrollback
|
||||
@@ -396,7 +371,7 @@ Terminal.prototype.focus = function() {
|
||||
* Retrieves an option's value from the terminal.
|
||||
* @param {string} key The option key.
|
||||
*/
|
||||
Terminal.prototype.getOption = function(key, value) {
|
||||
Terminal.prototype.getOption = function(key) {
|
||||
if (!(key in Terminal.defaults)) {
|
||||
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.lines.length > value) {
|
||||
const amountToTrim = this.lines.length - value;
|
||||
const needsRefresh = (this.ydisp - amountToTrim < 0);
|
||||
this.lines.trimStart(amountToTrim);
|
||||
this.ybase = Math.max(this.ybase - amountToTrim, 0);
|
||||
this.ydisp = Math.max(this.ydisp - amountToTrim, 0);
|
||||
if (this.buffer.lines.length > value) {
|
||||
const amountToTrim = this.buffer.lines.length - value;
|
||||
const needsRefresh = (this.buffer.ydisp - amountToTrim < 0);
|
||||
this.buffer.lines.trimStart(amountToTrim);
|
||||
this.buffer.ybase = Math.max(this.buffer.ybase - amountToTrim, 0);
|
||||
this.buffer.ydisp = Math.max(this.buffer.ydisp - amountToTrim, 0);
|
||||
if (needsRefresh) {
|
||||
this.refresh(0, this.rows - 1);
|
||||
}
|
||||
}
|
||||
this.lines.maxLength = value;
|
||||
this.buffer.lines.maxLength = value;
|
||||
this.viewport.syncScrollArea();
|
||||
}
|
||||
break;
|
||||
@@ -449,7 +424,7 @@ Terminal.prototype.setOption = function(key, value) {
|
||||
switch (key) {
|
||||
case 'cursorBlink': this.setCursorBlinking(value); break;
|
||||
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-bar`, value === 'bar');
|
||||
break;
|
||||
@@ -512,7 +487,7 @@ Terminal.prototype.blur = function() {
|
||||
*/
|
||||
Terminal.bindBlur = function (term) {
|
||||
on(term.textarea, 'blur', function (ev) {
|
||||
term.refresh(term.y, term.y);
|
||||
term.refresh(term.buffer.y, term.buffer.y);
|
||||
if (term.sendFocus) {
|
||||
term.send(C0.ESC + '[O');
|
||||
}
|
||||
@@ -537,7 +512,7 @@ Terminal.prototype.initGlobal = function() {
|
||||
on(this.element, 'copy', event => {
|
||||
// If mouse events are active it means the selection manager is disabled and
|
||||
// copy should be handled by the host program.
|
||||
if (this.mouseEvents) {
|
||||
if (!term.hasSelection()) {
|
||||
return;
|
||||
}
|
||||
copyHandler(event, term, this.selectionManager);
|
||||
@@ -659,6 +634,7 @@ Terminal.prototype.open = function(parent, focus) {
|
||||
this.element.classList.add('terminal');
|
||||
this.element.classList.add('xterm');
|
||||
this.element.classList.add('xterm-theme-' + this.theme);
|
||||
this.element.classList.add(`xterm-cursor-style-${this.options.cursorStyle}`);
|
||||
this.setCursorBlinking(this.options.cursorBlink);
|
||||
|
||||
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.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.renderer.refreshSelection(data.start, data.end);
|
||||
});
|
||||
@@ -764,15 +742,6 @@ Terminal.prototype.open = function(parent, 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
|
||||
// them into terminal mouse protocols.
|
||||
this.bindMouse();
|
||||
@@ -1043,14 +1012,17 @@ Terminal.prototype.bindMouse = function() {
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
// send the button
|
||||
sendButton(ev);
|
||||
|
||||
// ensure focus
|
||||
self.focus();
|
||||
|
||||
// fix for odd bug
|
||||
//if (self.vt200Mouse && !self.normalMouse) {
|
||||
if (self.vt200Mouse) {
|
||||
@@ -1155,7 +1127,7 @@ Terminal.prototype.queueLinkification = function(start, end) {
|
||||
Terminal.prototype.showCursor = function() {
|
||||
if (!this.cursorState) {
|
||||
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;
|
||||
|
||||
// Make room for the new row in lines
|
||||
if (this.lines.length === this.lines.maxLength) {
|
||||
this.lines.trimStart(1);
|
||||
this.ybase--;
|
||||
if (this.ydisp !== 0) {
|
||||
this.ydisp--;
|
||||
if (this.buffer.lines.length === this.buffer.lines.maxLength) {
|
||||
this.buffer.lines.trimStart(1);
|
||||
this.buffer.ybase--;
|
||||
if (this.buffer.ydisp !== 0) {
|
||||
this.buffer.ydisp--;
|
||||
}
|
||||
}
|
||||
|
||||
this.ybase++;
|
||||
this.buffer.ybase++;
|
||||
|
||||
// TODO: Why is this done twice?
|
||||
if (!this.userScrolling) {
|
||||
this.ydisp = this.ybase;
|
||||
this.buffer.ydisp = this.buffer.ybase;
|
||||
}
|
||||
|
||||
// last line
|
||||
row = this.ybase + this.rows - 1;
|
||||
row = this.buffer.ybase + this.rows - 1;
|
||||
|
||||
// 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
|
||||
this.lines.push(this.blankLine(undefined, isWrapped));
|
||||
this.buffer.lines.push(this.blankLine(undefined, isWrapped));
|
||||
} else {
|
||||
// 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.ybase !== 0) {
|
||||
this.ybase--;
|
||||
if (this.buffer.scrollTop !== 0) {
|
||||
if (this.buffer.ybase !== 0) {
|
||||
this.buffer.ybase--;
|
||||
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.updateRange(this.scrollTop);
|
||||
this.updateRange(this.scrollBottom);
|
||||
this.updateRange(this.buffer.scrollTop);
|
||||
this.updateRange(this.buffer.scrollBottom);
|
||||
|
||||
/**
|
||||
* This event is emitted whenever the terminal is scrolled.
|
||||
@@ -1217,7 +1189,7 @@ Terminal.prototype.scroll = function(isWrapped) {
|
||||
*
|
||||
* @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) {
|
||||
if (disp < 0) {
|
||||
if (this.ydisp === 0) {
|
||||
if (this.buffer.ydisp === 0) {
|
||||
return;
|
||||
}
|
||||
this.userScrolling = true;
|
||||
} else if (disp + this.ydisp >= this.ybase) {
|
||||
} else if (disp + this.buffer.ydisp >= this.buffer.ybase) {
|
||||
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) {
|
||||
this.ydisp = this.ybase;
|
||||
} else if (this.ydisp < 0) {
|
||||
this.ydisp = 0;
|
||||
// No change occurred, don't trigger scroll/refresh
|
||||
if (oldYdisp === this.buffer.ydisp) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!suppressScrollEvent) {
|
||||
this.emit('scroll', this.ydisp);
|
||||
this.emit('scroll', this.buffer.ydisp);
|
||||
}
|
||||
|
||||
this.refresh(0, this.rows - 1);
|
||||
@@ -1264,14 +1236,14 @@ Terminal.prototype.scrollPages = function(pageCount) {
|
||||
* Scrolls the display of the terminal to the top.
|
||||
*/
|
||||
Terminal.prototype.scrollToTop = function() {
|
||||
this.scrollDisp(-this.ydisp);
|
||||
this.scrollDisp(-this.buffer.ydisp);
|
||||
};
|
||||
|
||||
/**
|
||||
* Scrolls the display of the terminal to the bottom.
|
||||
*/
|
||||
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.refreshStart = this.y;
|
||||
this.refreshEnd = this.y;
|
||||
this.refreshStart = this.buffer.y;
|
||||
this.refreshEnd = this.buffer.y;
|
||||
|
||||
// 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
|
||||
@@ -1326,7 +1298,7 @@ Terminal.prototype.innerWrite = function() {
|
||||
var state = this.parser.parse(data);
|
||||
this.parser.setState(state);
|
||||
|
||||
this.updateRange(this.y);
|
||||
this.updateRange(this.buffer.y);
|
||||
this.refresh(this.refreshStart, this.refreshEnd);
|
||||
}
|
||||
if (this.writeBuffer.length > 0) {
|
||||
@@ -1436,7 +1408,7 @@ Terminal.prototype.deregisterLinkMatcher = function(matcherId) {
|
||||
* Gets whether the terminal has an active selection.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
Terminal.prototype.getSelection = function() {
|
||||
return this.selectionManager.selectionText;
|
||||
return this.selectionManager ? this.selectionManager.selectionText : '';
|
||||
};
|
||||
|
||||
/**
|
||||
* Clears the current terminal selection.
|
||||
*/
|
||||
Terminal.prototype.clearSelection = function() {
|
||||
this.selectionManager.clearSelection();
|
||||
if (this.selectionManager) {
|
||||
this.selectionManager.clearSelection();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Selects all text within the terminal.
|
||||
*/
|
||||
Terminal.prototype.selectAll = function() {
|
||||
this.selectionManager.selectAll();
|
||||
if (this.selectionManager) {
|
||||
this.selectionManager.selectAll();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -1475,7 +1451,7 @@ Terminal.prototype.keyDown = function(ev) {
|
||||
this.restartCursorBlinking();
|
||||
|
||||
if (!this.compositionHelper.keydown.bind(this.compositionHelper)(ev)) {
|
||||
if (this.ybase !== this.ydisp) {
|
||||
if (this.buffer.ybase !== this.buffer.ydisp) {
|
||||
this.scrollToBottom();
|
||||
}
|
||||
return false;
|
||||
@@ -1939,96 +1915,36 @@ Terminal.prototype.resize = function(x, y) {
|
||||
, addToY;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (x < 1) x = 1;
|
||||
if (y < 1) y = 1;
|
||||
|
||||
// resize cols
|
||||
j = this.cols;
|
||||
if (j < x) {
|
||||
ch = [this.defAttr, ' ', 1]; // does xterm use the default attr?
|
||||
i = this.lines.length;
|
||||
while (i--) {
|
||||
while (this.lines.get(i).length < x) {
|
||||
this.lines.get(i).push(ch);
|
||||
}
|
||||
}
|
||||
this.buffers.resize(x, y);
|
||||
|
||||
// Adjust rows in the DOM to accurately reflect the new dimensions
|
||||
while (this.children.length < y) {
|
||||
this.insertRow();
|
||||
}
|
||||
while (this.children.length > y) {
|
||||
el = this.children.shift();
|
||||
if (!el) continue;
|
||||
el.parentNode.removeChild(el);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
// 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.setupStops(this.cols);
|
||||
|
||||
this.charMeasure.measure();
|
||||
|
||||
this.refresh(0, this.rows - 1);
|
||||
|
||||
this.normal = null;
|
||||
|
||||
this.geometry = [this.cols, this.rows];
|
||||
this.emit('resize', {terminal: this, cols: x, rows: y});
|
||||
};
|
||||
@@ -2064,16 +1980,16 @@ Terminal.prototype.maxRange = function() {
|
||||
*/
|
||||
Terminal.prototype.setupStops = function(i) {
|
||||
if (i != null) {
|
||||
if (!this.tabs[i]) {
|
||||
if (!this.buffer.tabs[i]) {
|
||||
i = this.prevStop(i);
|
||||
}
|
||||
} else {
|
||||
this.tabs = {};
|
||||
this.buffer.tabs = {};
|
||||
i = 0;
|
||||
}
|
||||
|
||||
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.
|
||||
*/
|
||||
Terminal.prototype.prevStop = function(x) {
|
||||
if (x == null) x = this.x;
|
||||
while (!this.tabs[--x] && x > 0);
|
||||
if (x == null) x = this.buffer.x;
|
||||
while (!this.buffer.tabs[--x] && x > 0);
|
||||
return x >= this.cols
|
||||
? this.cols - 1
|
||||
: 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.
|
||||
*/
|
||||
Terminal.prototype.nextStop = function(x) {
|
||||
if (x == null) x = this.x;
|
||||
while (!this.tabs[++x] && x < this.cols);
|
||||
if (x == null) x = this.buffer.x;
|
||||
while (!this.buffer.tabs[++x] && x < this.cols);
|
||||
return x >= this.cols
|
||||
? this.cols - 1
|
||||
: x < 0 ? 0 : x;
|
||||
@@ -2110,7 +2026,7 @@ Terminal.prototype.nextStop = function(x) {
|
||||
* @param {number} y The line in which to operate.
|
||||
*/
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
@@ -2129,7 +2045,7 @@ Terminal.prototype.eraseRight = function(x, y) {
|
||||
* @param {number} y The line in which to operate.
|
||||
*/
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
@@ -2145,20 +2061,20 @@ Terminal.prototype.eraseLeft = function(x, y) {
|
||||
* Clears the entire buffer, making the prompt line the new first line.
|
||||
*/
|
||||
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
|
||||
return;
|
||||
}
|
||||
this.lines.set(0, this.lines.get(this.ybase + this.y));
|
||||
this.lines.length = 1;
|
||||
this.ydisp = 0;
|
||||
this.ybase = 0;
|
||||
this.y = 0;
|
||||
this.buffer.lines.set(0, this.buffer.lines.get(this.buffer.ybase + this.buffer.y));
|
||||
this.buffer.lines.length = 1;
|
||||
this.buffer.ydisp = 0;
|
||||
this.buffer.ybase = 0;
|
||||
this.buffer.y = 0;
|
||||
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.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
|
||||
*/
|
||||
Terminal.prototype.is = function(term) {
|
||||
@@ -2229,8 +2145,13 @@ Terminal.prototype.handler = function(data) {
|
||||
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.
|
||||
if (this.ybase !== this.ydisp) {
|
||||
if (this.buffer.ybase !== this.buffer.ydisp) {
|
||||
this.scrollToBottom();
|
||||
}
|
||||
this.emit('data', data);
|
||||
@@ -2260,14 +2181,14 @@ Terminal.prototype.handleTitle = function(title) {
|
||||
* ESC D Index (IND is 0x84).
|
||||
*/
|
||||
Terminal.prototype.index = function() {
|
||||
this.y++;
|
||||
if (this.y > this.scrollBottom) {
|
||||
this.y--;
|
||||
this.buffer.y++;
|
||||
if (this.buffer.y > this.buffer.scrollBottom) {
|
||||
this.buffer.y--;
|
||||
this.scroll();
|
||||
}
|
||||
// If the end of the line is hit, prevent this action from wrapping around to the next line.
|
||||
if (this.x >= this.cols) {
|
||||
this.x--;
|
||||
if (this.buffer.x >= this.cols) {
|
||||
this.buffer.x--;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2279,16 +2200,16 @@ Terminal.prototype.index = function() {
|
||||
*/
|
||||
Terminal.prototype.reverseIndex = function() {
|
||||
var j;
|
||||
if (this.y === this.scrollTop) {
|
||||
if (this.buffer.y === this.buffer.scrollTop) {
|
||||
// possibly move the code below to term.reverseScroll();
|
||||
// test: echo -ne '\e[1;1H\e[44m\eM\e[0m'
|
||||
// blankLine(true) is xterm/linux behavior
|
||||
this.lines.shiftElements(this.y + this.ybase, this.rows - 1, 1);
|
||||
this.lines.set(this.y + this.ybase, this.blankLine(true));
|
||||
this.updateRange(this.scrollTop);
|
||||
this.updateRange(this.scrollBottom);
|
||||
this.buffer.lines.shiftElements(this.buffer.y + this.buffer.ybase, this.rows - 1, 1);
|
||||
this.buffer.lines.set(this.buffer.y + this.buffer.ybase, this.blankLine(true));
|
||||
this.updateRange(this.buffer.scrollTop);
|
||||
this.updateRange(this.buffer.scrollBottom);
|
||||
} else {
|
||||
this.y--;
|
||||
this.buffer.y--;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2301,9 +2222,11 @@ Terminal.prototype.reset = function() {
|
||||
this.options.cols = this.cols;
|
||||
var customKeyEventHandler = this.customKeyEventHandler;
|
||||
var cursorBlinkInterval = this.cursorBlinkInterval;
|
||||
var inputHandler = this.inputHandler;
|
||||
Terminal.call(this, this.options);
|
||||
this.customKeyEventHandler = customKeyEventHandler;
|
||||
this.cursorBlinkInterval = cursorBlinkInterval;
|
||||
this.inputHandler = inputHandler;
|
||||
this.refresh(0, this.rows - 1);
|
||||
this.viewport.syncScrollArea();
|
||||
};
|
||||
@@ -2313,7 +2236,7 @@ Terminal.prototype.reset = function() {
|
||||
* ESC H Tab Set (HTS is 0x88).
|
||||
*/
|
||||
Terminal.prototype.tabSet = function() {
|
||||
this.tabs[this.x] = true;
|
||||
this.buffer.tabs[this.buffer.x] = true;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -2449,6 +2372,7 @@ function keys(obj) {
|
||||
* Expose
|
||||
*/
|
||||
|
||||
Terminal.translateBufferLineToString = translateBufferLineToString;
|
||||
Terminal.EventEmitter = EventEmitter;
|
||||
Terminal.inherits = inherits;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user