Compare commits

..
2 Commits
Author SHA1 Message Date
Paris Kasidiaris 536425394c Bump version to 3.4.1
Signed-off-by: Paris Kasidiaris <paris@sourcelair.com>
2018-05-21 16:29:57 +00:00
Daniel Imms fe576b895c Save and restore correctly when drawing from static cache
Fixes #1424
2018-05-21 09:22:34 -07:00
100 changed files with 2272 additions and 5482 deletions
+11
View File
@@ -0,0 +1,11 @@
<!-- Enter your issue description here -->
## Details
- Browser and browser version:
- OS version:
- xterm.js version:
### Steps to reproduce
1.
2.
-17
View File
@@ -1,17 +0,0 @@
---
name: Bug report
about: Create a bug report
---
<!-- ⚠️ Please search existing issues to avoid creating duplicates. ⚠️ -->
<!-- Describe the bug here. -->
## Details
- Browser and browser version:
- OS version:
- xterm.js version:
### Steps to reproduce
1.
2.
@@ -1,7 +0,0 @@
---
name: Feature request
about: Suggest a feature or enhancement
---
<!-- ⚠️ Please search existing issues to avoid creating duplicates. ⚠️ -->
<!-- Describe the enhancement here. -->
-8
View File
@@ -1,8 +0,0 @@
---
name: Question
about: The issue tracker is not for questions. Please ask questions on https://stackoverflow.com/questions/tagged/xtermjs or https://gitter.im/sourcelair/xterm.js.
---
🛑 The issue tracker is not for questions 🛑
If you have a question, please ask it on https://stackoverflow.com/questions/tagged/xtermjs or https://gitter.im/sourcelair/xterm.js.
+1 -2
View File
@@ -18,8 +18,7 @@ fixtures/typings-test/*.js
package-lock.json
# Directories needed for code coverage
coverage/
.nyc_output/
/coverage/
# Keep bundled code out of Git
dist/
+1 -1
View File
@@ -13,7 +13,7 @@ env:
matrix:
- NPM_COMMAND=tsc
- NPM_COMMAND=lint
- NPM_COMMAND=test-coverage
- NPM_COMMAND=test
notifications:
email: false
script: npm run $NPM_COMMAND
+23
View File
@@ -0,0 +1,23 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Mocha Tests",
"cwd": "${workspaceRoot}",
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/mocha",
"windows": {
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/mocha.cmd"
},
"runtimeArgs": [
"--colors",
"--recursive",
"${workspaceRoot}/lib"
],
"sourceMaps": true,
"outFiles": [ "${workspaceRoot}/lib/**/*.js" ],
"internalConsoleOptions": "openOnSessionStart"
}
]
}
+8
View File
@@ -0,0 +1,8 @@
{
"search.exclude": {
"**/node_modules": true,
"**/dist": true,
"**/build": true,
"**/lib": true
}
}
-10
View File
@@ -1,7 +1,5 @@
List of xterm.js contributors. Updated before every release.
7PH <b.raymond@cern.ch>
7PH <b.raymond@protonmail.com>
Adrian Labbé <adrilabbelol@gmail.com>
Aleksandr Andrienko <aandrienko@codenvy.com>
Aleksandr Andriienko <oandriie@redhat.com>
@@ -21,7 +19,6 @@ Austin Robertson <austinrobertson@gmail.com>
ayapi <colors.aya@gmail.com>
Ben Hall <ben@benhall.me.uk>
Benjamin Fischer <benjamin.fischer@rwth-aachen.de>
Benjamin Raymond <b.raymond@cern.ch>
Benjamin Woodruff <github@benjam.info>
Bill Church <billchurch@users.noreply.github.com>
Bob Reid <bobreid@Bobs-MacBook-Pro.local>
@@ -67,7 +64,6 @@ Joao Moreno <jomo@microsoft.com>
Joao Moreno <mail@joaomoreno.com>
Johannes Zellner <johannes@nebulon.de>
Jon Austin <jon.i.austin@gmail.com>
Jon Kohler <jon@nutanix.com>
Jon Masters <jon.masters@sky.com>
Jörg Breitbart <jerch@rockborn.de>
jpoth <poth.john@gmail.com>
@@ -80,7 +76,6 @@ Linus Unnebäck <linus@folkdatorn.se>
Luca <LucaT1@users.noreply.github.com>
Lucian Buzzo <lucian.buzzo@gmail.com>
Lukas Drgon <lukas.drgon@gmail.com>
Lukas Geiger <lukas.geiger94@gmail.com>
Maël Nison <nison.mael@gmail.com>
Marc Dumais <marc.dumais@ericsson.com>
Marek Libra <mlibra@redhat.com>
@@ -93,7 +88,6 @@ Matthew James <matthew.d.james87@gmail.com>
Michael Irwin <mikesir87@gmail.com>
Mikko Karvonen <mikko.karvonen@arm.com>
mofux <t.zilz@mofux.org>
muji <muji@tmpfs.org>
Nicolas Ramz <nicolas.ramz@gmail.com>
npezza93 <npezza93@gmail.com>
Oleksandr Andriienko <oandriie@redhat.com>
@@ -102,7 +96,6 @@ Paris Kasidiaris <paris@sourcelair.com>
Peng Xiao <pengxiao@outlook.com>
Peter Baumgarten <me@peterbaumgarten.com>
Philip Olson <philip.olson@protonmail.ch>
PowerHat <31401273+7PH@users.noreply.github.com>
pro-src <34285059+pro-src@users.noreply.github.com>
pro-src <rodneyd.teal@gmail.com>
Rick Baker <rick@ricktbaker.com>
@@ -115,7 +108,6 @@ Shuanglei Tao <tsl0922@gmail.com>
sitzmar <sitzmar@gmail.com>
Steven Silvester <steven.silvester@ieee.org>
stuicey <stuicey@users.noreply.github.com>
Sven-Hendrik Haase <svenstaro@gmail.com>
t-amqi <umberos@gmail.com>
Thanasis Daglis <thanasis@sourcelair.com>
thomas <thomas@kreativkonzentrat.de>
@@ -123,8 +115,6 @@ Thomas Zilz <t.zilz@mofux.org>
Tine Jozelj <tine.jozelj@outlook.com>
Tyler Jewell <tjewell@codenvy.com>
Vincent Woo <me@vincentwoo.com>
Viraj Sinha <virajosinha@gmail.com>
yutaka <shigure@refy.net>
YuviPanda <yuvipanda@gmail.com>
ZHAO Xudong <zxdong@gmail.com>
赵雪珂 <zhaoxueke@onething.net>
-73
View File
@@ -1,73 +0,0 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of experience,
education, socio-economic status, nationality, personal appearance, race,
religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting [@Tyriar](https://twitter.com/Tyriar) or [@pariskasid](https://twitter.com/pariskasid) on Twitter. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
+6 -20
View File
@@ -1,6 +1,6 @@
# [![xterm.js logo](logo-full.png)](https://xtermjs.org)
[![Travis CI build status](https://api.travis-ci.org/xtermjs/xterm.js.svg)](https://travis-ci.org/xtermjs/xterm.js) [![VSTS Build status](https://xtermjs.visualstudio.com/_apis/public/build/definitions/3e323cf7-5760-460d-af64-ee5675baf366/1/badge)](https://xtermjs.visualstudio.com/xterm.js/_build/index?definitionId=1) [![Coverage Status](https://coveralls.io/repos/github/xtermjs/xterm.js/badge.svg?branch=master)](https://coveralls.io/github/xtermjs/xterm.js?branch=master) [![Gitter](https://badges.gitter.im/sourcelair/xterm.js.svg)](https://gitter.im/sourcelair/xterm.js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![jsDelivr Hits](https://data.jsdelivr.com/v1/package/npm/xterm/badge?style=rounded)](https://www.jsdelivr.com/package/npm/xterm)
[![xterm.js build status](https://api.travis-ci.org/xtermjs/xterm.js.svg)](https://travis-ci.org/xtermjs/xterm.js) [![Coverage Status](https://coveralls.io/repos/github/xtermjs/xterm.js/badge.svg?branch=master)](https://coveralls.io/github/xtermjs/xterm.js?branch=master) [![Gitter](https://badges.gitter.im/sourcelair/xterm.js.svg)](https://gitter.im/sourcelair/xterm.js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![jsDelivr Hits](https://data.jsdelivr.com/v1/package/npm/xterm/badge?style=rounded)](https://www.jsdelivr.com/package/npm/xterm)
Xterm.js is a terminal front-end component written in JavaScript that works in the browser.
@@ -39,7 +39,7 @@ To start using xterm.js on your browser, add the `xterm.js` and `xterm.css` to t
<body>
<div id="terminal"></div>
<script>
var term = new Terminal();
var term = new Terminal();
term.open(document.getElementById('terminal'));
      term.write('Hello from \x1B[1;3;31mxterm.js\x1B[0m $ ')
</script>
@@ -149,12 +149,8 @@ computational environment for Jupyter, supporting interactive data science and s
- [**Fluent Terminal**](https://github.com/felixse/FluentTerminal): A terminal emulator based on UWP and web technologies.
- [**Hyper**](https://hyper.is): A terminal built on web technologies
- [**Diag**](https://diag.ai): A better way to troubleshoot problems faster. Capture, share and reapply troubleshooting knowledge so you can focus on solving problems that matter.
- [**GoTTY**](https://github.com/yudai/gotty): A simple command line tool that shares your terminal as a web application based on xterm.js.
- [**genact**](https://github.com/svenstaro/genact): A nonsense activity generator.
- [**cPanel & WHM**](https://cpanel.com): The hosting platform of choice.
- [**Nutanix**](https://github.com/nutanix): Nutanix Enterprise Cloud uses xterm in the webssh functionality within Nutanix Calm, and is also looking to move our old noserial (termjs) functionality to xterm.js
Do you use xterm.js in your application as well? Please [open a Pull Request](https://github.com/sourcelair/xterm.js/pulls) to include it here. We would love to have it in our list. Note: Please add any new contributions to the end of the list only.
Do you use xterm.js in your application as well? Please [open a Pull Request](https://github.com/sourcelair/xterm.js/pulls) to include it here. We would love to have it in our list.
## Demo
@@ -227,16 +223,6 @@ Then open http://127.0.0.1:3000 in a web browser to access the demo.
*Note: Do not use ConEmu, as it seems to break the demo for some reason.*
## Testing
Tests are run using the following npm scripts:
- `npm test`: This will run both unit tests and the linter
- `npm run test-suite <file>`: This will run all tests within a particular file, &lt;file&gt; is the test file name excluding the extension (eg. "Linkifier.test")
- `npm run test-debug`: This will run unit tests with `--inspect-brk`, this can then be automatically debugged using [VS Code auto attach](https://code.visualstudio.com/docs/nodejs/nodejs-debugging#_auto-attach-feature) or manually attached to by a debugger
- `npm run test-coverage`: This will run all unit tests and produce a coverage report
- `npm run lint`: This will run the linter only
## Releases
Xterm.js follows a monthly release cycle roughly.
@@ -251,12 +237,12 @@ To contribute either code, documentation or issues to xterm.js please read the [
### Code structure
`src/` is roughly split up into areas of functionality such as `renderer/` that handles all rendering and `utils/` which provides general utility functions. The `shared/` folder contains code that can be used from either the main thread or a web worker thread, all code inside a `shared/` folder should only ever import other code from a `shared/` folder to minimize the amount of code ran when launching a web worker.
`src/` is roughly split up into areas of functionality such as `renderer/` that handles all rendering and `utils/` which provides general utility functions. The `shared/` folder contains code that can be used from either the main thread or a web worker thread, all code inside a `shared/` folder should only ever import other code from a `shared/` folder to minimize the amount of code run what launching a web worker.
## License Agreement
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) 2017-2018, [The xterm.js authors](https://github.com/xtermjs/xterm.js/graphs/contributors) (MIT License)<br>
Copyright (c) 2014-2017, SourceLair, Private Company ([www.sourcelair.com](https://www.sourcelair.com/home)) (MIT License)<br>
Copyright (c) 2017-2018, [The xterm.js authors](https://github.com/xtermjs/xterm.js/graphs/contributors) (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)
+92 -35
View File
@@ -1,38 +1,95 @@
<!doctype html>
<html>
<head>
<title>xterm.js demo</title>
<link rel="stylesheet" href="/build/xterm.css" />
<link rel="stylesheet" href="/build/addons/fullscreen/fullscreen.css" />
<link rel="stylesheet" href="style.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/es6-promise/4.1.1/es6-promise.auto.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fetch/1.0.0/fetch.min.js"></script>
</head>
<body>
<h1 style="color: #2D2E2C">xterm.js: A terminal for the <em style="color: #5DA5D5">web</em></h1>
<div id="terminal-container"></div>
<div>
<h3>Actions</h3>
<p>
<label>Find next <input id="find-next"/></label>
<label>Find previous <input id="find-previous"/></label>
</p>
</div>
<div>
<h3>Options</h3>
<p>These options can be set in the <code>Terminal</code> constructor or using the <code>Terminal.setOption</code> function.</p>
<div id="options-container"></div>
</div>
<div>
<h3>Style</h3>
<div style="display: inline-block; margin-right: 16px;">
<label for="padding">Padding</label>
<input type="number" id="padding" />
</div>
</div>
<hr/>
<p><strong>Attention:</strong> The demo is a barebones implementation and is designed for the development and evaluation of xterm.js only. Exposing the demo to the public as is would introduce security risks for the host.</p>
<button id="dispose" title="This is used to testing memory leaks">Dispose terminal</button>
<script src="dist/bundle.js" defer ></script>
</body>
<head>
<title>xterm.js demo</title>
<link rel="stylesheet" href="/build/xterm.css" />
<link rel="stylesheet" href="/build/addons/fullscreen/fullscreen.css" />
<link rel="stylesheet" href="style.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/es6-promise/4.1.1/es6-promise.auto.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fetch/1.0.0/fetch.min.js"></script>
</head>
<body>
<h1 style="color: #2D2E2C">xterm.js: A terminal for the <em style="color: #5DA5D5">web</em></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>
<label><input type="checkbox" id="option-cursor-blink"> cursorBlink</label>
</p>
<p>
<label><input type="checkbox" id="option-mac-option-is-meta"> macOptionIsMeta</label>
</p>
<p>
<label><input type="checkbox" id="option-transparency"> transparency</label>
</p>
<p>
<label>
cursorStyle
<select id="option-cursor-style">
<option value="block">block</option>
<option value="underline">underline</option>
<option value="bar">bar</option>
</select>
</label>
</p>
<p>
<label>
bellStyle
<select id="option-bell-style">
<option value="">none</option>
<option value="sound">sound</option>
<option value="visual">visual</option>
<option value="both">both</option>
</select>
</label>
</p>
<p>
<label>scrollback <input type="number" id="option-scrollback" value="1000" /></label>
</p>
<p>
<label>tabStopWidth <input type="number" id="option-tabstopwidth" value="8" /></label>
</p>
<p>
<label>
experimentalCharAtlas
<select id="option-experimental-char-atlas">
<option value="static" selected>static</option>
<option value="dynamic">dynamic</option>
<option value="none">none</option>
</select>
</label>
</p>
<div>
<h3>Size</h3>
<div>
<div style="display: inline-block; margin-right: 16px;">
<label for="cols">Columns</label>
<input type="number" id="cols" />
</div>
<div style="display: inline-block; margin-right: 16px;">
<label for="rows">Rows</label>
<input type="number" id="rows" />
</div>
<div style="display: inline-block; margin-right: 16px;">
<label for="padding">Padding</label>
<input type="number" id="padding" />
</div>
</div>
</div>
<div>
<h3>Accessibility</h3>
<p>
<label><input type="checkbox" id="option-screen-reader-mode"> screenReaderMode</label>
</p>
</div>
<p><strong>Attention:</strong> The demo is a barebones implementation and is designed for the development and evaluation of xterm.js only. Exposing the demo to the public as is would introduce security risks for the host.</p>
<script src="dist/bundle.js" defer ></script>
</body>
</html>
+83 -147
View File
@@ -26,38 +26,97 @@ var terminalContainer = document.getElementById('terminal-container'),
findNext: document.querySelector('#find-next'),
findPrevious: document.querySelector('#find-previous')
},
optionElements = {
cursorBlink: document.querySelector('#option-cursor-blink'),
cursorStyle: document.querySelector('#option-cursor-style'),
macOptionIsMeta: document.querySelector('#option-mac-option-is-meta'),
scrollback: document.querySelector('#option-scrollback'),
transparency: document.querySelector('#option-transparency'),
tabstopwidth: document.querySelector('#option-tabstopwidth'),
experimentalCharAtlas: document.querySelector('#option-experimental-char-atlas'),
bellStyle: document.querySelector('#option-bell-style'),
screenReaderMode: document.querySelector('#option-screen-reader-mode')
},
colsElement = document.getElementById('cols'),
rowsElement = document.getElementById('rows'),
paddingElement = document.getElementById('padding');
function setTerminalSize() {
var cols = parseInt(colsElement.value, 10);
var rows = parseInt(rowsElement.value, 10);
var width = (cols * term.renderer.dimensions.actualCellWidth + term.viewport.scrollBarWidth).toString() + 'px';
var height = (rows * term.renderer.dimensions.actualCellHeight).toString() + 'px';
terminalContainer.style.width = width;
terminalContainer.style.height = height;
term.fit();
}
function setPadding() {
term.element.style.padding = parseInt(paddingElement.value, 10).toString() + 'px';
term.fit();
}
colsElement.addEventListener('change', setTerminalSize);
rowsElement.addEventListener('change', setTerminalSize);
paddingElement.addEventListener('change', setPadding);
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);
});
optionElements.macOptionIsMeta.addEventListener('change', function () {
term.setOption('macOptionIsMeta', optionElements.macOptionIsMeta.checked);
});
optionElements.transparency.addEventListener('change', function () {
var checked = optionElements.transparency.checked;
term.setOption('allowTransparency', checked);
term.setOption('theme', checked ? {background: 'rgba(0, 0, 0, .5)'} : {});
});
optionElements.cursorStyle.addEventListener('change', function () {
term.setOption('cursorStyle', optionElements.cursorStyle.value);
});
optionElements.bellStyle.addEventListener('change', function () {
term.setOption('bellStyle', optionElements.bellStyle.value);
});
optionElements.scrollback.addEventListener('change', function () {
term.setOption('scrollback', parseInt(optionElements.scrollback.value, 10));
});
optionElements.tabstopwidth.addEventListener('change', function () {
term.setOption('tabStopWidth', parseInt(optionElements.tabstopwidth.value, 10));
});
optionElements.experimentalCharAtlas.addEventListener('change', function () {
term.setOption('experimentalCharAtlas', optionElements.experimentalCharAtlas.value);
});
optionElements.screenReaderMode.addEventListener('change', function () {
term.setOption('screenReaderMode', optionElements.screenReaderMode.checked);
});
createTerminal();
const disposeRecreateButtonHandler = () => {
// If the terminal exists dispose of it, otherwise recreate it
if (term) {
term.dispose();
term = null;
window.term = null;
socket = null;
document.getElementById('dispose').innerHTML = 'Recreate Terminal';
}
else {
createTerminal();
document.getElementById('dispose').innerHTML = 'Dispose terminal';
}
};
document.getElementById('dispose').addEventListener('click', disposeRecreateButtonHandler);
function createTerminal() {
// Clean terminal
while (terminalContainer.children.length) {
terminalContainer.removeChild(terminalContainer.children[0]);
}
term = new Terminal({});
term = new Terminal({
macOptionIsMeta: optionElements.macOptionIsMeta.enabled,
cursorBlink: optionElements.cursorBlink.checked,
scrollback: parseInt(optionElements.scrollback.value, 10),
tabStopWidth: parseInt(optionElements.tabstopwidth.value, 10),
screenReaderMode: optionElements.screenReaderMode.checked
});
window.term = term; // Expose `term` to window for debugging purposes
term.on('resize', function (size) {
if (!pid) {
@@ -78,30 +137,14 @@ function createTerminal() {
term.fit();
term.focus();
addDomListener(paddingElement, 'change', setPadding);
addDomListener(actionElements.findNext, 'keypress', function (e) {
if (e.key === "Enter") {
e.preventDefault();
term.findNext(actionElements.findNext.value);
}
});
addDomListener(actionElements.findPrevious, 'keypress', function (e) {
if (e.key === "Enter") {
e.preventDefault();
term.findPrevious(actionElements.findPrevious.value);
}
});
// fit is called within a setTimeout, cols and rows need this.
setTimeout(function () {
initOptions(term);
document.getElementById(`opt-cols`).value = term.cols;
document.getElementById(`opt-rows`).value = term.rows;
colsElement.value = term.cols;
rowsElement.value = term.rows;
paddingElement.value = 0;
// Set terminal size again to set the specific dimensions on the demo
updateTerminalSize();
setTerminalSize();
fetch('/terminals?cols=' + term.cols + '&rows=' + term.rows, {method: 'POST'}).then(function (res) {
@@ -141,7 +184,7 @@ function runFakeTerminal() {
term.writeln('');
term.prompt();
term._core.register(term.addDisposableListener('key', function (key, ev) {
term.on('key', function (key, ev) {
var printable = (
!ev.altKey && !ev.altGraphKey && !ev.ctrlKey && !ev.metaKey
);
@@ -156,116 +199,9 @@ function runFakeTerminal() {
} else if (printable) {
term.write(key);
}
}));
});
term._core.register(term.addDisposableListener('paste', function (data, ev) {
term.on('paste', function (data, ev) {
term.write(data);
}));
}
function initOptions(term) {
var blacklistedOptions = [
// Internal only options
'cancelEvents',
'convertEol',
'debug',
'handler',
'screenKeys',
'termName',
'useFlowControl',
// Complex option
'theme',
// Only in constructor
'rendererType'
];
var stringOptions = {
bellSound: null,
bellStyle: ['none', 'sound'],
cursorStyle: ['block', 'underline', 'bar'],
experimentalCharAtlas: ['none', 'static', 'dynamic'],
fontFamily: null,
fontWeight: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'],
fontWeightBold: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900']
};
var options = Object.keys(term._core.options);
var booleanOptions = [];
var numberOptions = [];
options.filter(o => blacklistedOptions.indexOf(o) === -1).forEach(o => {
switch (typeof term.getOption(o)) {
case 'boolean':
booleanOptions.push(o);
break;
case 'number':
numberOptions.push(o);
break;
default:
if (Object.keys(stringOptions).indexOf(o) === -1) {
console.warn(`Unrecognized option: "${o}"`);
}
}
});
var html = '';
html += '<div class="option-group">';
booleanOptions.forEach(o => {
html += `<div class="option"><label><input id="opt-${o}" type="checkbox" ${term.getOption(o) ? 'checked' : ''}/> ${o}</label></div>`;
});
html += '</div><div class="option-group">';
numberOptions.forEach(o => {
html += `<div class="option"><label>${o} <input id="opt-${o}" type="number" value="${term.getOption(o)}"/></label></div>`;
});
html += '</div><div class="option-group">';
Object.keys(stringOptions).forEach(o => {
if (stringOptions[o]) {
html += `<div class="option"><label>${o} <select id="opt-${o}">${stringOptions[o].map(v => `<option ${term.getOption(o) === v ? 'selected' : ''}>${v}</option>`).join('')}</select></label></div>`;
} else {
html += `<div class="option"><label>${o} <input id="opt-${o}" type="text" value="${term.getOption(o)}"/></label></div>`
}
});
html += '</div>';
var container = document.getElementById('options-container');
container.innerHTML = html;
// Attach listeners
booleanOptions.forEach(o => {
var input = document.getElementById(`opt-${o}`);
addDomListener(input, 'change', () => {
console.log('change', o, input.checked);
term.setOption(o, input.checked);
});
});
numberOptions.forEach(o => {
var input = document.getElementById(`opt-${o}`);
addDomListener(input, 'change', () => {
console.log('change', o, input.value);
if (o === 'cols' || o === 'rows') {
updateTerminalSize();
} else {
term.setOption(o, parseInt(input.value, 10));
}
});
});
Object.keys(stringOptions).forEach(o => {
var input = document.getElementById(`opt-${o}`);
addDomListener(input, 'change', () => {
console.log('change', o, input.value);
term.setOption(o, input.value);
});
});
}
function addDomListener(element, type, handler) {
element.addEventListener(type, handler);
term._core.register({ dispose: () => element.removeEventListener(type, handler) });
}
function updateTerminalSize() {
var cols = parseInt(document.getElementById(`opt-cols`).value, 10);
var rows = parseInt(document.getElementById(`opt-rows`).value, 10);
var width = (cols * term._core.renderer.dimensions.actualCellWidth + term._core.viewport.scrollBarWidth).toString() + 'px';
var height = (rows * term._core.renderer.dimensions.actualCellHeight).toString() + 'px';
terminalContainer.style.width = width;
terminalContainer.style.height = height;
term.fit();
}
-16
View File
@@ -14,19 +14,3 @@ h1 {
margin: 0 auto;
padding: 2px;
}
p {
font-size: 0.9em;
font-style: italic
}
#option-container {
display: flex;
justify-content: center;
}
.option-group {
display: inline-block;
padding-left: 20px;
vertical-align: top;
}
+80 -24
View File
@@ -4,9 +4,11 @@
const browserify = require('browserify');
const buffer = require('vinyl-buffer');
const coveralls = require('gulp-coveralls');
const fs = require('fs-extra');
const gulp = require('gulp');
const path = require('path');
const istanbul = require('gulp-istanbul');
const merge = require('merge-stream');
const mocha = require('gulp-mocha');
const sorcery = require('sorcery');
@@ -18,37 +20,69 @@ const webpack = require('webpack-stream');
const buildDir = process.env.BUILD_DIR || 'build';
const tsProject = ts.createProject('tsconfig.json');
let srcDir = tsProject.config.compilerOptions.rootDir;
const srcDir = tsProject.config.compilerOptions.rootDir;
let outDir = tsProject.config.compilerOptions.outDir;
const addons = fs.readdirSync(`${__dirname}/src/addons`);
const TEST_PATHS = [
`${outDir}/*test.js`,
`${outDir}/**/*test.js`,
`${outDir}/*integration.js`,
`${outDir}/**/*integration.js`
];
// Under some environments like TravisCI, this comes out at absolute which can
// break the build. This ensures that the outDir is absolute.
if (path.normalize(outDir).indexOf(__dirname) !== 0) {
outDir = `${__dirname}/${path.normalize(outDir)}`;
}
gulp.task('css', function() {
return gulp.src(`${srcDir}/**/*.css`).pipe(gulp.dest(outDir));
});
/**
* Compile TypeScript sources to JavaScript files and create a source map file for each TypeScript
* file compiled.
*/
gulp.task('tsc', function () {
// Remove the ${outDir}/ directory to prevent confusion if files were deleted in ${srcDir}/
fs.emptyDirSync(`${outDir}`);
gulp.task('watch-css', function() {
return gulp.watch(`${srcDir}/**/*.css`, ['css']);
// Build all TypeScript files (including tests) to ${outDir}/, based on the configuration defined in
// `tsconfig.json`.
let tsResult = tsProject.src().pipe(sourcemaps.init()).pipe(tsProject());
let tsc = merge(
tsResult.js.pipe(sourcemaps.write('.', {includeContent: false, sourceRoot: ''})).pipe(gulp.dest(outDir)),
tsResult.dts.pipe(sourcemaps.write('.', {includeContent: false, sourceRoot: ''})).pipe(gulp.dest(outDir))
);
let addonStreams = addons.map(function(addon) {
fs.emptyDirSync(`${outDir}/addons/${addon}`);
let tsProjectAddon = ts.createProject(`./src/addons/${addon}/tsconfig.json`);
let tsResultAddon = tsProjectAddon.src().pipe(sourcemaps.init()).pipe(tsProjectAddon());
let tscAddon = merge(
tsResultAddon.js
.pipe(sourcemaps.write('.', {includeContent: false, sourceRoot: ''}))
.pipe(gulp.dest(`${outDir}/addons/${addon}`)),
tsResultAddon.dts
.pipe(sourcemaps.write('.', {includeContent: false, sourceRoot: ''}))
.pipe(gulp.dest(`${outDir}/addons/${addon}`))
)
return tscAddon;
});
// Copy all addons from ${srcDir}/ to ${outDir}/
let copyAddons = gulp.src([
`${srcDir}/addons/**/**`
]).pipe(gulp.dest(`${outDir}/addons`));
// Copy stylesheets from ${srcDir}/ to ${outDir}/
let copyStylesheets = gulp.src(`${srcDir}/**/*.css`).pipe(gulp.dest(outDir));
// Join all streams into a single array
let streams = [tsc].concat(addonStreams).concat([copyAddons, copyStylesheets]);
return merge.apply(this, streams);
});
/**
* Bundle JavaScript files produced by the `tsc` task, into a single file named `xterm.js` with
* Browserify.
*/
gulp.task('browserify', function() {
gulp.task('browserify', ['tsc'], function() {
// Ensure that the build directory exists
fs.ensureDirSync(buildDir);
@@ -74,7 +108,7 @@ gulp.task('browserify', function() {
return merge(bundleStream, copyStylesheets);
});
gulp.task('browserify-addons', function() {
gulp.task('browserify-addons', ['tsc'], function() {
const bundles = addons.map((addon) => {
const addonOptions = {
basedir: `${buildDir}/addons/${addon}`,
@@ -100,22 +134,37 @@ gulp.task('browserify-addons', function() {
return merge(...bundles);
});
gulp.task('mocha', function () {
return gulp.src(TEST_PATHS, {read: false})
gulp.task('instrument-test', function () {
return gulp.src([`${outDir}/**/*.js`])
// Covering files
.pipe(istanbul())
// Force `require` to return covered files
.pipe(istanbul.hookRequire());
});
gulp.task('mocha', ['instrument-test'], function () {
return gulp.src([
`${outDir}/*test.js`,
`${outDir}/**/*test.js`,
`${outDir}/*integration.js`,
`${outDir}/**/*integration.js`
], {read: false})
.pipe(mocha())
.once('error', () => process.exit(1));
.once('error', () => process.exit(1))
.pipe(istanbul.writeReports());
});
/**
* Run single test suite (file) by file name (without file extension). Example of the command:
* gulp mocha-suite --test InputHandler.test
* Run single test file by file name(without file extension). Example of the command:
* gulp mocha-test --test InputHandler.test
*/
gulp.task('mocha-suite', [], function () {
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));
.once('error', () => process.exit(1))
.pipe(istanbul.writeReports());
});
/**
@@ -143,9 +192,16 @@ gulp.task('webpack', ['build'], function() {
.pipe(gulp.dest('demo/dist/'));
});
gulp.task('watch', ['webpack'], () => {
gulp.watch(['./src/*', './src/**/*'], ['webpack']);
});
gulp.task('watch-demo', ['webpack'], () => {
gulp.watch(['./demo/*', './lib/**/*'], ['webpack']);
/**
* Submit coverage results to coveralls.io
*/
gulp.task('coveralls', function () {
gulp.src('coverage/**/lcov.info')
.pipe(coveralls());
});
gulp.task('build', ['sorcery', 'sorcery-addons']);
+12 -17
View File
@@ -1,21 +1,19 @@
{
"name": "xterm",
"description": "Full xterm terminal, in your browser",
"version": "3.5.0",
"main": "lib/public/Terminal.js",
"version": "3.4.1",
"main": "lib/Terminal.js",
"types": "typings/xterm.d.ts",
"repository": "https://github.com/xtermjs/xterm.js",
"license": "MIT",
"devDependencies": {
"@types/chai": "^3.4.34",
"@types/glob": "^5.0.35",
"@types/jsdom": "11.0.1",
"@types/jsdom": "^11.0.1",
"@types/mocha": "^2.2.33",
"@types/node": "6.0.108",
"@types/text-encoding": "0.0.32",
"browserify": "^13.3.0",
"chai": "3.5.0",
"concurrently": "^3.5.1",
"coveralls": "^3.0.1",
"express": "4.13.4",
"express-ws": "2.0.0-rc.1",
"fs-extra": "^1.0.0",
@@ -23,21 +21,22 @@
"gulp": "3.9.1",
"gulp-cli": "^1.2.2",
"gulp-concat": "^2.6.1",
"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": "^11.11.0",
"jsdom": "^11.1.0",
"merge-stream": "^1.0.1",
"node-pty": "0.7.6",
"node-pty": "^0.7.2",
"nodemon": "1.10.2",
"npm-run-all": "^4.1.2",
"nyc": "^11.8.0",
"sorcery": "^0.10.0",
"tslint": "^5.9.1",
"tslint-consistent-codestyle": "^1.13.0",
"typescript": "2.8.3",
"typescript": "~2.7.1",
"vinyl-buffer": "^1.0.0",
"vinyl-source-stream": "^1.1.0",
"webpack": "^3.10.0",
@@ -45,21 +44,17 @@
"zmodem.js": "^0.1.5"
},
"scripts": {
"start": "concurrently --kill-others-on-fail --names \"demo,server\" \"gulp watch-demo\" \"node demo/app\"",
"start": "node demo/app",
"start-zmodem": "node demo/zmodem/app",
"lint": "tslint 'src/**/*.ts'",
"test": "npm-run-all mocha lint",
"test-debug": "node --inspect-brk node_modules/.bin/gulp test",
"test-suite": "gulp mocha-suite --test",
"test-coverage": "nyc -x gulpfile.js -x '**/*test*' npm run mocha",
"mocha": "gulp test",
"build:docs": "jsdoc -c jsdoc.json",
"tsc": "tsc",
"prebuild": "concurrently --kill-others-on-fail --names \"lib,attach,fit,fullscreen,search,terminado,webLinks,winptyCompat,zmodem,css\" \"tsc\" \"tsc -p ./src/addons/attach\" \"tsc -p ./src/addons/fit\" \"tsc -p ./src/addons/fullscreen\" \"tsc -p ./src/addons/search\" \"tsc -p ./src/addons/terminado\" \"tsc -p ./src/addons/webLinks\" \"tsc -p ./src/addons/winptyCompat\" \"tsc -p ./src/addons/zmodem\" \"gulp css\"",
"build": "gulp build",
"prepublish": "npm run build",
"coveralls": "nyc report --reporter=text-lcov | coveralls",
"coveralls": "gulp coveralls",
"webpack": "gulp webpack",
"watch": "concurrently --kill-others-on-fail --names \"lib,attach,fit,fullscreen,search,terminado,webLinks,winptyCompat,zmodem,css\" \"tsc -w\" \"tsc -w -p ./src/addons/attach\" \"tsc -w -p ./src/addons/fit\" \"tsc -w -p ./src/addons/fullscreen\" \"tsc -w -p ./src/addons/search\" \"tsc -w -p ./src/addons/terminado\" \"tsc -w -p ./src/addons/webLinks\" \"tsc -w -p ./src/addons/winptyCompat\" \"tsc -w -p ./src/addons/zmodem\" \"gulp watch-css\""
"watch": "gulp watch"
}
}
+21 -19
View File
@@ -6,9 +6,9 @@
import * as Strings from './Strings';
import { ITerminal, IBuffer } from './Types';
import { isMac } from './shared/utils/Browser';
import { RenderDebouncer } from './ui/RenderDebouncer';
import { addDisposableDomListener } from './ui/Lifecycle';
import { Disposable } from './common/Lifecycle';
import { RenderDebouncer } from './utils/RenderDebouncer';
import { addDisposableListener } from './utils/Dom';
import { IDisposable } from 'xterm';
const MAX_ROWS_TO_READ = 20;
@@ -17,7 +17,7 @@ const enum BoundaryPosition {
BOTTOM
}
export class AccessibilityManager extends Disposable {
export class AccessibilityManager implements IDisposable {
private _accessibilityTreeRoot: HTMLElement;
private _rowContainer: HTMLElement;
private _rowElements: HTMLElement[];
@@ -29,6 +29,8 @@ export class AccessibilityManager extends Disposable {
private _topBoundaryFocusListener: (e: FocusEvent) => void;
private _bottomBoundaryFocusListener: (e: FocusEvent) => void;
private _disposables: IDisposable[] = [];
/**
* This queue has a character pushed to it for keys that are pressed, if the
* next character added to the terminal is equal to the key char then it is
@@ -41,7 +43,6 @@ export class AccessibilityManager extends Disposable {
private _charsToConsume: string[] = [];
constructor(private _terminal: ITerminal) {
super();
this._accessibilityTreeRoot = document.createElement('div');
this._accessibilityTreeRoot.classList.add('xterm-accessibility');
@@ -71,28 +72,29 @@ export class AccessibilityManager extends Disposable {
this._terminal.element.insertAdjacentElement('afterbegin', this._accessibilityTreeRoot);
this.register(this._renderRowsDebouncer);
this.register(this._terminal.addDisposableListener('resize', data => this._onResize(data.cols, data.rows)));
this.register(this._terminal.addDisposableListener('refresh', data => this._refreshRows(data.start, data.end)));
this.register(this._terminal.addDisposableListener('scroll', data => this._refreshRows()));
this._disposables.push(this._renderRowsDebouncer);
this._disposables.push(this._terminal.addDisposableListener('resize', data => this._onResize(data.cols, data.rows)));
this._disposables.push(this._terminal.addDisposableListener('refresh', data => this._refreshRows(data.start, data.end)));
this._disposables.push(this._terminal.addDisposableListener('scroll', data => this._refreshRows()));
// Line feed is an issue as the prompt won't be read out after a command is run
this.register(this._terminal.addDisposableListener('a11y.char', (char) => this._onChar(char)));
this.register(this._terminal.addDisposableListener('linefeed', () => this._onChar('\n')));
this.register(this._terminal.addDisposableListener('a11y.tab', spaceCount => this._onTab(spaceCount)));
this.register(this._terminal.addDisposableListener('key', keyChar => this._onKey(keyChar)));
this.register(this._terminal.addDisposableListener('blur', () => this._clearLiveRegion()));
this._disposables.push(this._terminal.addDisposableListener('a11y.char', (char) => this._onChar(char)));
this._disposables.push(this._terminal.addDisposableListener('linefeed', () => this._onChar('\n')));
this._disposables.push(this._terminal.addDisposableListener('a11y.tab', spaceCount => this._onTab(spaceCount)));
this._disposables.push(this._terminal.addDisposableListener('key', keyChar => this._onKey(keyChar)));
this._disposables.push(this._terminal.addDisposableListener('blur', () => this._clearLiveRegion()));
// TODO: Maybe renderer should fire an event on terminal when the characters change and that
// should be listened to instead? That would mean that the order of events are always
// guarenteed
this.register(this._terminal.addDisposableListener('dprchange', () => this._refreshRowsDimensions()));
this.register(this._terminal.renderer.addDisposableListener('resize', () => this._refreshRowsDimensions()));
this._disposables.push(this._terminal.addDisposableListener('dprchange', () => this._refreshRowsDimensions()));
this._disposables.push(this._terminal.renderer.addDisposableListener('resize', () => this._refreshRowsDimensions()));
// This shouldn't be needed on modern browsers but is present in case the
// media query that drives the dprchange event isn't supported
this.register(addDisposableDomListener(window, 'resize', () => this._refreshRowsDimensions()));
this._disposables.push(addDisposableListener(window, 'resize', () => this._refreshRowsDimensions()));
}
public dispose(): void {
super.dispose();
this._disposables.forEach(d => d.dispose());
this._disposables.length = 0;
this._terminal.element.removeChild(this._accessibilityTreeRoot);
this._rowElements.length = 0;
}
@@ -177,7 +179,7 @@ export class AccessibilityManager extends Disposable {
this._refreshRowsDimensions();
}
private _createAccessibilityTreeNode(): HTMLElement {
public _createAccessibilityTreeNode(): HTMLElement {
const element = document.createElement('div');
element.setAttribute('role', 'listitem');
element.tabIndex = -1;
+1 -135
View File
@@ -6,7 +6,7 @@
import { assert } from 'chai';
import { ITerminal } from './Types';
import { Buffer } from './Buffer';
import { CircularList } from './common/CircularList';
import { CircularList } from './utils/CircularList';
import { MockTerminal } from './utils/TestUtils.test';
const INIT_COLS = 80;
@@ -48,63 +48,6 @@ describe('Buffer', () => {
});
});
describe('getWrappedRangeForLine', () => {
describe('non-wrapped', () => {
it('should return a single row for the first row', () => {
buffer.fillViewportRows();
assert.deepEqual(buffer.getWrappedRangeForLine(0), { first: 0, last: 0 });
});
it('should return a single row for a middle row', () => {
buffer.fillViewportRows();
assert.deepEqual(buffer.getWrappedRangeForLine(12), { first: 12, last: 12 });
});
it('should return a single row for the last row', () => {
buffer.fillViewportRows();
assert.deepEqual(buffer.getWrappedRangeForLine(buffer.lines.length - 1), { first: 23, last: 23 });
});
});
describe('wrapped', () => {
it('should return a range for the first row', () => {
buffer.fillViewportRows();
(<any> buffer.lines.get(1)).isWrapped = true;
assert.deepEqual(buffer.getWrappedRangeForLine(0), { first: 0, last: 1 });
});
it('should return a range for a middle row wrapping upwards', () => {
buffer.fillViewportRows();
(<any> buffer.lines.get(12)).isWrapped = true;
assert.deepEqual(buffer.getWrappedRangeForLine(12), { first: 11, last: 12 });
});
it('should return a range for a middle row wrapping downwards', () => {
buffer.fillViewportRows();
(<any> buffer.lines.get(13)).isWrapped = true;
assert.deepEqual(buffer.getWrappedRangeForLine(12), { first: 12, last: 13 });
});
it('should return a range for a middle row wrapping both ways', () => {
buffer.fillViewportRows();
(<any> buffer.lines.get(11)).isWrapped = true;
(<any> buffer.lines.get(12)).isWrapped = true;
(<any> buffer.lines.get(13)).isWrapped = true;
(<any> buffer.lines.get(14)).isWrapped = true;
assert.deepEqual(buffer.getWrappedRangeForLine(12), { first: 10, last: 14 });
});
it('should return a range for the last row', () => {
buffer.fillViewportRows();
(<any> buffer.lines.get(23)).isWrapped = true;
assert.deepEqual(buffer.getWrappedRangeForLine(buffer.lines.length - 1), { first: 22, last: 23 });
});
it('should return a range for a row that wraps upward to first row', () => {
buffer.fillViewportRows();
(<any> buffer.lines.get(1)).isWrapped = true;
assert.deepEqual(buffer.getWrappedRangeForLine(1), { first: 0, last: 1 });
});
it('should return a range for a row that wraps downward to last row', () => {
buffer.fillViewportRows();
(<any> buffer.lines.get(buffer.lines.length - 1)).isWrapped = true;
assert.deepEqual(buffer.getWrappedRangeForLine(buffer.lines.length - 2), { first: 22, last: 23 });
});
});
});
describe('resize', () => {
describe('column size is reduced', () => {
it('should not trim the data in the buffer', () => {
@@ -269,81 +212,4 @@ describe('Buffer', () => {
assert.equal(buffer.markers.length, 0);
});
});
describe ('translateBufferLineToString', () => {
it('should handle selecting a section of ascii text', () => {
buffer.lines.set(0, [
[ null, 'a', 1, 'a'.charCodeAt(0)],
[ null, 'b', 1, 'b'.charCodeAt(0)],
[ null, 'c', 1, 'c'.charCodeAt(0)],
[ null, 'd', 1, 'd'.charCodeAt(0)]
]);
const str = buffer.translateBufferLineToString(0, true, 0, 2);
assert.equal(str, 'ab');
});
it('should handle a cut-off double width character by including it', () => {
buffer.lines.set(0, [
[ null, '語', 2, 35486 ],
[ null, '', 0, null],
[ null, 'a', 1, 'a'.charCodeAt(0)]
]);
const str1 = buffer.translateBufferLineToString(0, true, 0, 1);
assert.equal(str1, '語');
});
it('should handle a zero width character in the middle of the string by not including it', () => {
buffer.lines.set(0, [
[ null, '語', 2, '語'.charCodeAt(0) ],
[ null, '', 0, null],
[ null, 'a', 1, 'a'.charCodeAt(0)]
]);
const str0 = buffer.translateBufferLineToString(0, true, 0, 1);
assert.equal(str0, '語');
const str1 = buffer.translateBufferLineToString(0, true, 0, 2);
assert.equal(str1, '語');
const str2 = buffer.translateBufferLineToString(0, true, 0, 3);
assert.equal(str2, '語a');
});
it('should handle single width emojis', () => {
buffer.lines.set(0, [
[ null, '😁', 1, '😁'.charCodeAt(0) ],
[ null, 'a', 1, 'a'.charCodeAt(0)]
]);
const str1 = buffer.translateBufferLineToString(0, true, 0, 1);
assert.equal(str1, '😁');
const str2 = buffer.translateBufferLineToString(0, true, 0, 2);
assert.equal(str2, '😁a');
});
it('should handle double width emojis', () => {
buffer.lines.set(0, [
[ null, '😁', 2, '😁'.charCodeAt(0) ],
[ null, '', 0, null]
]);
const str1 = buffer.translateBufferLineToString(0, true, 0, 1);
assert.equal(str1, '😁');
const str2 = buffer.translateBufferLineToString(0, true, 0, 2);
assert.equal(str2, '😁');
buffer.lines.set(0, [
[ null, '😁', 2, '😁'.charCodeAt(0) ],
[ null, '', 0, null],
[ null, 'a', 1, 'a'.charCodeAt(0)]
]);
const str3 = buffer.translateBufferLineToString(0, true, 0, 3);
assert.equal(str3, '😁a');
});
});
});
+11 -25
View File
@@ -3,12 +3,11 @@
* @license MIT
*/
import { CircularList } from './common/CircularList';
import { CircularList } from './utils/CircularList';
import { LineData, CharData, ITerminal, IBuffer } from './Types';
import { EventEmitter } from './EventEmitter';
import { IMarker } from 'xterm';
import { IDisposable, IMarker } from 'xterm';
export const DEFAULT_ATTR = (0 << 18) | (257 << 9) | (256 << 0);
export const CHAR_DATA_ATTR_INDEX = 0;
export const CHAR_DATA_CHAR_INDEX = 1;
export const CHAR_DATA_WIDTH_INDEX = 2;
@@ -117,7 +116,7 @@ export class Buffer implements IBuffer {
if (this.lines.length > 0) {
// Deal with columns increasing (we don't do anything when columns reduce)
if (this._terminal.cols < newCols) {
const ch: CharData = [DEFAULT_ATTR, ' ', 1, 32]; // does xterm use the default attr?
const ch: CharData = [this._terminal.defAttr, ' ', 1, 32]; // does xterm use the default attr?
for (let i = 0; i < this.lines.length; i++) {
while (this.lines.get(i).length < newCols) {
this.lines.get(i).push(ch);
@@ -227,7 +226,7 @@ export class Buffer implements IBuffer {
if (startCol >= i) {
startIndex--;
}
if (endCol > i) {
if (endCol >= i) {
endIndex--;
}
} else {
@@ -260,20 +259,6 @@ export class Buffer implements IBuffer {
return lineString.substring(startIndex, endIndex);
}
public getWrappedRangeForLine(y: number): { first: number, last: number } {
let first = y;
let last = y;
// Scan upwards for wrapped lines
while (first > 0 && (<any>this.lines.get(first)).isWrapped) {
first--;
}
// Scan downwards for wrapped lines
while (last + 1 < this.lines.length && (<any>this.lines.get(last + 1)).isWrapped) {
last++;
}
return { first, last };
}
/**
* Setup the tab stops.
* @param i The index to start setting up tab stops from.
@@ -320,14 +305,14 @@ export class Buffer implements IBuffer {
public addMarker(y: number): Marker {
const marker = new Marker(y);
this.markers.push(marker);
marker.register(this.lines.addDisposableListener('trim', amount => {
marker.disposables.push(this.lines.addDisposableListener('trim', amount => {
marker.line -= amount;
// The marker should be disposed when the line is trimmed from the buffer
if (marker.line < 0) {
marker.dispose();
}
}));
marker.register(marker.addDisposableListener('dispose', () => this._removeMarker(marker)));
marker.on('dispose', () => this._removeMarker(marker));
return marker;
}
@@ -338,10 +323,11 @@ export class Buffer implements IBuffer {
}
export class Marker extends EventEmitter implements IMarker {
private static _nextId = 1;
private static NEXT_ID = 1;
private _id: number = Marker._nextId++;
private _id: number = Marker.NEXT_ID++;
public isDisposed: boolean = false;
public disposables: IDisposable[] = [];
public get id(): number { return this._id; }
@@ -356,8 +342,8 @@ export class Marker extends EventEmitter implements IMarker {
return;
}
this.isDisposed = true;
// Emit before super.dispose such that dispose listeners get a change to react
this.disposables.forEach(d => d.dispose());
this.disposables.length = 0;
this.emit('dispose');
super.dispose();
}
}
+2 -2
View File
@@ -121,7 +121,7 @@ export const wcwidth = (function(opts: {nul: number, control: number}): (ucs: nu
}
const control = opts.control | 0;
let table: number[] | Uint32Array = null;
function initTable(): number[] | Uint32Array {
function init_table(): number[] | Uint32Array {
// lookup table for BMP
const CODEPOINTS = 65536; // BMP holds 65536 codepoints
const BITWIDTH = 2; // a codepoint can have a width of 0, 1 or 2
@@ -161,7 +161,7 @@ export const wcwidth = (function(opts: {nul: number, control: number}): (ucs: nu
if (num < 127) {
return 1;
}
const t = table || initTable();
let t = table || init_table();
if (num < 65536) {
return t[num >> 4] >> ((num & 15) << 1) & 3;
}
@@ -3,7 +3,7 @@
* @license MIT
*/
import { ICharset } from '../Types';
import { ICharset } from './Types';
/**
* The character sets supported by the terminal. These enable several languages
+32 -33
View File
@@ -5,14 +5,13 @@
import { assert } from 'chai';
import { CompositionHelper } from './CompositionHelper';
import { ITerminal } from './Types';
describe('CompositionHelper', () => {
let terminal: ITerminal;
let compositionHelper: CompositionHelper;
let compositionView: HTMLElement;
let textarea: HTMLTextAreaElement;
let handledText: string;
let terminal;
let compositionHelper;
let compositionView;
let textarea;
let handledText;
beforeEach(() => {
compositionView = {
@@ -28,14 +27,14 @@ describe('CompositionHelper', () => {
top: 0
},
textContent: ''
} as any;
};
textarea = {
value: '',
style: {
left: 0,
top: 0
}
} as any;
};
terminal = {
element: {
querySelector: () => {
@@ -55,7 +54,7 @@ describe('CompositionHelper', () => {
options: {
lineHeight: 1
}
} as any;
};
handledText = '';
compositionHelper = new CompositionHelper(textarea, compositionView, terminal);
});
@@ -64,7 +63,7 @@ describe('CompositionHelper', () => {
it('Should insert simple characters', (done) => {
// First character 'ㅇ'
compositionHelper.compositionstart();
compositionHelper.compositionupdate(<CompositionEvent><CompositionEvent>{ data: 'ㅇ' });
compositionHelper.compositionupdate({ data: 'ㅇ' });
textarea.value = 'ㅇ';
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionend();
@@ -72,7 +71,7 @@ describe('CompositionHelper', () => {
assert.equal(handledText, 'ㅇ');
// Second character 'ㅇ'
compositionHelper.compositionstart();
compositionHelper.compositionupdate(<CompositionEvent><CompositionEvent>{ data: 'ㅇ' });
compositionHelper.compositionupdate({ data: 'ㅇ' });
textarea.value = 'ㅇㅇ';
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionend();
@@ -88,13 +87,13 @@ describe('CompositionHelper', () => {
it('Should insert complex characters', (done) => {
// First character '앙'
compositionHelper.compositionstart();
compositionHelper.compositionupdate(<CompositionEvent>{ data: 'ㅇ' });
compositionHelper.compositionupdate({ data: 'ㅇ' });
textarea.value = 'ㅇ';
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionupdate(<CompositionEvent>{ data: '아' });
compositionHelper.compositionupdate({ data: '아' });
textarea.value = '아';
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionupdate(<CompositionEvent>{ data: '앙' });
compositionHelper.compositionupdate({ data: '앙' });
textarea.value = '앙';
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionend();
@@ -102,13 +101,13 @@ describe('CompositionHelper', () => {
assert.equal(handledText, '앙');
// Second character '앙'
compositionHelper.compositionstart();
compositionHelper.compositionupdate(<CompositionEvent>{ data: 'ㅇ' });
compositionHelper.compositionupdate({ data: 'ㅇ' });
textarea.value = '앙ㅇ';
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionupdate(<CompositionEvent>{ data: '아' });
compositionHelper.compositionupdate({ data: '아' });
textarea.value = '앙아';
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionupdate(<CompositionEvent>{ data: '앙' });
compositionHelper.compositionupdate({ data: '앙' });
textarea.value = '앙앙';
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionend();
@@ -128,19 +127,19 @@ describe('CompositionHelper', () => {
it('Should insert complex characters that change with following character', (done) => {
// First character '아'
compositionHelper.compositionstart();
compositionHelper.compositionupdate(<CompositionEvent>{ data: 'ㅇ' });
compositionHelper.compositionupdate({ data: 'ㅇ' });
textarea.value = 'ㅇ';
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionupdate(<CompositionEvent>{ data: '아' });
compositionHelper.compositionupdate({ data: '아' });
textarea.value = '아';
setTimeout(() => { // wait for any textarea updates
// Start second character '아' in first character
compositionHelper.compositionupdate(<CompositionEvent>{ data: '앙' });
compositionHelper.compositionupdate({ data: '앙' });
textarea.value = '앙';
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionend();
compositionHelper.compositionstart();
compositionHelper.compositionupdate(<CompositionEvent>{ data: '아' });
compositionHelper.compositionupdate({ data: '아' });
textarea.value = '아아';
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionend();
@@ -157,14 +156,14 @@ describe('CompositionHelper', () => {
it('Should insert multi-characters compositions', (done) => {
// First character 'だ'
compositionHelper.compositionstart();
compositionHelper.compositionupdate(<CompositionEvent>{ data: 'd' });
compositionHelper.compositionupdate({ data: 'd' });
textarea.value = 'd';
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionupdate(<CompositionEvent>{ data: 'だ' });
compositionHelper.compositionupdate({ data: 'だ' });
textarea.value = 'だ';
setTimeout(() => { // wait for any textarea updates
// Second character 'あ'
compositionHelper.compositionupdate(<CompositionEvent>{ data: 'だあ' });
compositionHelper.compositionupdate({ data: 'だあ' });
textarea.value = 'だあ';
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionend();
@@ -180,18 +179,18 @@ describe('CompositionHelper', () => {
it('Should insert multi-character compositions that are converted to other characters with the same length', (done) => {
// First character 'だ'
compositionHelper.compositionstart();
compositionHelper.compositionupdate(<CompositionEvent>{ data: 'd' });
compositionHelper.compositionupdate({ data: 'd' });
textarea.value = 'd';
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionupdate(<CompositionEvent>{ data: 'だ' });
compositionHelper.compositionupdate({ data: 'だ' });
textarea.value = 'だ';
setTimeout(() => { // wait for any textarea updates
// Second character 'ー'
compositionHelper.compositionupdate(<CompositionEvent>{ data: 'だー' });
compositionHelper.compositionupdate({ data: 'だー' });
textarea.value = 'だー';
setTimeout(() => { // wait for any textarea updates
// Convert to katakana 'ダー'
compositionHelper.compositionupdate(<CompositionEvent>{ data: 'ダー' });
compositionHelper.compositionupdate({ data: 'ダー' });
textarea.value = 'ダー';
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionend();
@@ -208,18 +207,18 @@ describe('CompositionHelper', () => {
it('Should insert multi-character compositions that are converted to other characters with different lengths', (done) => {
// First character 'い'
compositionHelper.compositionstart();
compositionHelper.compositionupdate(<CompositionEvent>{ data: 'い' });
compositionHelper.compositionupdate({ data: 'い' });
textarea.value = 'い';
setTimeout(() => { // wait for any textarea updates
// Second character 'ま'
compositionHelper.compositionupdate(<CompositionEvent>{ data: 'いm' });
compositionHelper.compositionupdate({ data: 'いm' });
textarea.value = 'いm';
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionupdate(<CompositionEvent>{ data: 'いま' });
compositionHelper.compositionupdate({ data: 'いま' });
textarea.value = 'いま';
setTimeout(() => { // wait for any textarea updates
// Convert to kanji '今'
compositionHelper.compositionupdate(<CompositionEvent>{ data: '今' });
compositionHelper.compositionupdate({ data: '今' });
textarea.value = '今';
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionend();
@@ -236,7 +235,7 @@ describe('CompositionHelper', () => {
it('Should insert non-composition characters input immediately after composition characters', (done) => {
// First character 'ㅇ'
compositionHelper.compositionstart();
compositionHelper.compositionupdate(<CompositionEvent>{ data: 'ㅇ' });
compositionHelper.compositionupdate({ data: 'ㅇ' });
textarea.value = 'ㅇ';
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionend();
File diff suppressed because it is too large Load Diff
-565
View File
@@ -1,565 +0,0 @@
/**
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { ParserState, ParserAction, IParsingState, IDcsHandler, IEscapeSequenceParser } from './Types';
import { Disposable } from './common/Lifecycle';
/**
* Returns an array filled with numbers between the low and high parameters (right exclusive).
* @param low The low number.
* @param high The high number.
*/
function r(low: number, high: number): number[] {
let c = high - low;
const arr = new Array(c);
while (c--) {
arr[c] = --high;
}
return arr;
}
/**
* Transition table for EscapeSequenceParser.
* NOTE: data in the underlying table is packed like this:
* currentState << 8 | characterCode --> action << 4 | nextState
*/
export class TransitionTable {
public table: Uint8Array | number[];
constructor(length: number) {
this.table = (typeof Uint8Array === 'undefined')
? new Array(length)
: new Uint8Array(length);
}
/**
* Add a transition to the transition table.
* @param code input character code
* @param state current parser state
* @param action parser action to be done
* @param next next parser state
*/
add(code: number, state: number, action: number | null, next: number | null): void {
this.table[state << 8 | code] = ((action | 0) << 4) | ((next === undefined) ? state : next);
}
/**
* Add transitions for multiple input character codes.
* @param codes input character code array
* @param state current parser state
* @param action parser action to be done
* @param next next parser state
*/
addMany(codes: number[], state: number, action: number | null, next: number | null): void {
for (let i = 0; i < codes.length; i++) {
this.add(codes[i], state, action, next);
}
}
}
/**
* Default definitions for the VT500_TRANSITION_TABLE.
*/
const PRINTABLES = r(0x20, 0x7f);
const EXECUTABLES = r(0x00, 0x18);
EXECUTABLES.push(0x19);
EXECUTABLES.concat(r(0x1c, 0x20));
const DEFAULT_TRANSITION = ParserAction.ERROR << 4 | ParserState.GROUND;
/**
* VT500 compatible transition table.
* Taken from https://vt100.net/emu/dec_ansi_parser.
*/
export const VT500_TRANSITION_TABLE = (function (): TransitionTable {
const table: TransitionTable = new TransitionTable(4095);
const states: number[] = r(ParserState.GROUND, ParserState.DCS_PASSTHROUGH + 1);
let state: any;
// table with default transition [any] --> DEFAULT_TRANSITION
for (state in states) {
// NOTE: table lookup is capped at 0xa0 in parse to keep the table small
for (let code = 0; code < 160; ++code) {
table.add(code, state, ParserAction.ERROR, ParserState.GROUND);
}
}
// printables
table.addMany(PRINTABLES, ParserState.GROUND, ParserAction.PRINT, ParserState.GROUND);
// global anywhere rules
for (state in states) {
table.addMany([0x18, 0x1a, 0x99, 0x9a], state, ParserAction.EXECUTE, ParserState.GROUND);
table.addMany(r(0x80, 0x90), state, ParserAction.EXECUTE, ParserState.GROUND);
table.addMany(r(0x90, 0x98), state, ParserAction.EXECUTE, ParserState.GROUND);
table.add(0x9c, state, ParserAction.IGNORE, ParserState.GROUND); // ST as terminator
table.add(0x1b, state, ParserAction.CLEAR, ParserState.ESCAPE); // ESC
table.add(0x9d, state, ParserAction.OSC_START, ParserState.OSC_STRING); // OSC
table.addMany([0x98, 0x9e, 0x9f], state, ParserAction.IGNORE, ParserState.SOS_PM_APC_STRING);
table.add(0x9b, state, ParserAction.CLEAR, ParserState.CSI_ENTRY); // CSI
table.add(0x90, state, ParserAction.CLEAR, ParserState.DCS_ENTRY); // DCS
}
// rules for executables and 7f
table.addMany(EXECUTABLES, ParserState.GROUND, ParserAction.EXECUTE, ParserState.GROUND);
table.addMany(EXECUTABLES, ParserState.ESCAPE, ParserAction.EXECUTE, ParserState.ESCAPE);
table.add(0x7f, ParserState.ESCAPE, ParserAction.IGNORE, ParserState.ESCAPE);
table.addMany(EXECUTABLES, ParserState.OSC_STRING, ParserAction.IGNORE, ParserState.OSC_STRING);
table.addMany(EXECUTABLES, ParserState.CSI_ENTRY, ParserAction.EXECUTE, ParserState.CSI_ENTRY);
table.add(0x7f, ParserState.CSI_ENTRY, ParserAction.IGNORE, ParserState.CSI_ENTRY);
table.addMany(EXECUTABLES, ParserState.CSI_PARAM, ParserAction.EXECUTE, ParserState.CSI_PARAM);
table.add(0x7f, ParserState.CSI_PARAM, ParserAction.IGNORE, ParserState.CSI_PARAM);
table.addMany(EXECUTABLES, ParserState.CSI_IGNORE, ParserAction.EXECUTE, ParserState.CSI_IGNORE);
table.addMany(EXECUTABLES, ParserState.CSI_INTERMEDIATE, ParserAction.EXECUTE, ParserState.CSI_INTERMEDIATE);
table.add(0x7f, ParserState.CSI_INTERMEDIATE, ParserAction.IGNORE, ParserState.CSI_INTERMEDIATE);
table.addMany(EXECUTABLES, ParserState.ESCAPE_INTERMEDIATE, ParserAction.EXECUTE, ParserState.ESCAPE_INTERMEDIATE);
table.add(0x7f, ParserState.ESCAPE_INTERMEDIATE, ParserAction.IGNORE, ParserState.ESCAPE_INTERMEDIATE);
// osc
table.add(0x5d, ParserState.ESCAPE, ParserAction.OSC_START, ParserState.OSC_STRING);
table.addMany(PRINTABLES, ParserState.OSC_STRING, ParserAction.OSC_PUT, ParserState.OSC_STRING);
table.add(0x7f, ParserState.OSC_STRING, ParserAction.OSC_PUT, ParserState.OSC_STRING);
table.addMany([0x9c, 0x1b, 0x18, 0x1a, 0x07], ParserState.OSC_STRING, ParserAction.OSC_END, ParserState.GROUND);
table.addMany(r(0x1c, 0x20), ParserState.OSC_STRING, ParserAction.IGNORE, ParserState.OSC_STRING);
// sos/pm/apc does nothing
table.addMany([0x58, 0x5e, 0x5f], ParserState.ESCAPE, ParserAction.IGNORE, ParserState.SOS_PM_APC_STRING);
table.addMany(PRINTABLES, ParserState.SOS_PM_APC_STRING, ParserAction.IGNORE, ParserState.SOS_PM_APC_STRING);
table.addMany(EXECUTABLES, ParserState.SOS_PM_APC_STRING, ParserAction.IGNORE, ParserState.SOS_PM_APC_STRING);
table.add(0x9c, ParserState.SOS_PM_APC_STRING, ParserAction.IGNORE, ParserState.GROUND);
// csi entries
table.add(0x5b, ParserState.ESCAPE, ParserAction.CLEAR, ParserState.CSI_ENTRY);
table.addMany(r(0x40, 0x7f), ParserState.CSI_ENTRY, ParserAction.CSI_DISPATCH, ParserState.GROUND);
table.addMany(r(0x30, 0x3a), ParserState.CSI_ENTRY, ParserAction.PARAM, ParserState.CSI_PARAM);
table.add(0x3b, ParserState.CSI_ENTRY, ParserAction.PARAM, ParserState.CSI_PARAM);
table.addMany([0x3c, 0x3d, 0x3e, 0x3f], ParserState.CSI_ENTRY, ParserAction.COLLECT, ParserState.CSI_PARAM);
table.addMany(r(0x30, 0x3a), ParserState.CSI_PARAM, ParserAction.PARAM, ParserState.CSI_PARAM);
table.add(0x3b, ParserState.CSI_PARAM, ParserAction.PARAM, ParserState.CSI_PARAM);
table.addMany(r(0x40, 0x7f), ParserState.CSI_PARAM, ParserAction.CSI_DISPATCH, ParserState.GROUND);
table.addMany([0x3a, 0x3c, 0x3d, 0x3e, 0x3f], ParserState.CSI_PARAM, ParserAction.IGNORE, ParserState.CSI_IGNORE);
table.addMany(r(0x20, 0x40), ParserState.CSI_IGNORE, ParserAction.IGNORE, ParserState.CSI_IGNORE);
table.add(0x7f, ParserState.CSI_IGNORE, ParserAction.IGNORE, ParserState.CSI_IGNORE);
table.addMany(r(0x40, 0x7f), ParserState.CSI_IGNORE, ParserAction.IGNORE, ParserState.GROUND);
table.add(0x3a, ParserState.CSI_ENTRY, ParserAction.IGNORE, ParserState.CSI_IGNORE);
table.addMany(r(0x20, 0x30), ParserState.CSI_ENTRY, ParserAction.COLLECT, ParserState.CSI_INTERMEDIATE);
table.addMany(r(0x20, 0x30), ParserState.CSI_INTERMEDIATE, ParserAction.COLLECT, ParserState.CSI_INTERMEDIATE);
table.addMany(r(0x30, 0x40), ParserState.CSI_INTERMEDIATE, ParserAction.IGNORE, ParserState.CSI_IGNORE);
table.addMany(r(0x40, 0x7f), ParserState.CSI_INTERMEDIATE, ParserAction.CSI_DISPATCH, ParserState.GROUND);
table.addMany(r(0x20, 0x30), ParserState.CSI_PARAM, ParserAction.COLLECT, ParserState.CSI_INTERMEDIATE);
// esc_intermediate
table.addMany(r(0x20, 0x30), ParserState.ESCAPE, ParserAction.COLLECT, ParserState.ESCAPE_INTERMEDIATE);
table.addMany(r(0x20, 0x30), ParserState.ESCAPE_INTERMEDIATE, ParserAction.COLLECT, ParserState.ESCAPE_INTERMEDIATE);
table.addMany(r(0x30, 0x7f), ParserState.ESCAPE_INTERMEDIATE, ParserAction.ESC_DISPATCH, ParserState.GROUND);
table.addMany(r(0x30, 0x50), ParserState.ESCAPE, ParserAction.ESC_DISPATCH, ParserState.GROUND);
table.addMany(r(0x51, 0x58), ParserState.ESCAPE, ParserAction.ESC_DISPATCH, ParserState.GROUND);
table.addMany([0x59, 0x5a, 0x5c], ParserState.ESCAPE, ParserAction.ESC_DISPATCH, ParserState.GROUND);
table.addMany(r(0x60, 0x7f), ParserState.ESCAPE, ParserAction.ESC_DISPATCH, ParserState.GROUND);
// dcs entry
table.add(0x50, ParserState.ESCAPE, ParserAction.CLEAR, ParserState.DCS_ENTRY);
table.addMany(EXECUTABLES, ParserState.DCS_ENTRY, ParserAction.IGNORE, ParserState.DCS_ENTRY);
table.add(0x7f, ParserState.DCS_ENTRY, ParserAction.IGNORE, ParserState.DCS_ENTRY);
table.addMany(r(0x1c, 0x20), ParserState.DCS_ENTRY, ParserAction.IGNORE, ParserState.DCS_ENTRY);
table.addMany(r(0x20, 0x30), ParserState.DCS_ENTRY, ParserAction.COLLECT, ParserState.DCS_INTERMEDIATE);
table.add(0x3a, ParserState.DCS_ENTRY, ParserAction.IGNORE, ParserState.DCS_IGNORE);
table.addMany(r(0x30, 0x3a), ParserState.DCS_ENTRY, ParserAction.PARAM, ParserState.DCS_PARAM);
table.add(0x3b, ParserState.DCS_ENTRY, ParserAction.PARAM, ParserState.DCS_PARAM);
table.addMany([0x3c, 0x3d, 0x3e, 0x3f], ParserState.DCS_ENTRY, ParserAction.COLLECT, ParserState.DCS_PARAM);
table.addMany(EXECUTABLES, ParserState.DCS_IGNORE, ParserAction.IGNORE, ParserState.DCS_IGNORE);
table.addMany(r(0x20, 0x80), ParserState.DCS_IGNORE, ParserAction.IGNORE, ParserState.DCS_IGNORE);
table.addMany(r(0x1c, 0x20), ParserState.DCS_IGNORE, ParserAction.IGNORE, ParserState.DCS_IGNORE);
table.addMany(EXECUTABLES, ParserState.DCS_PARAM, ParserAction.IGNORE, ParserState.DCS_PARAM);
table.add(0x7f, ParserState.DCS_PARAM, ParserAction.IGNORE, ParserState.DCS_PARAM);
table.addMany(r(0x1c, 0x20), ParserState.DCS_PARAM, ParserAction.IGNORE, ParserState.DCS_PARAM);
table.addMany(r(0x30, 0x3a), ParserState.DCS_PARAM, ParserAction.PARAM, ParserState.DCS_PARAM);
table.add(0x3b, ParserState.DCS_PARAM, ParserAction.PARAM, ParserState.DCS_PARAM);
table.addMany([0x3a, 0x3c, 0x3d, 0x3e, 0x3f], ParserState.DCS_PARAM, ParserAction.IGNORE, ParserState.DCS_IGNORE);
table.addMany(r(0x20, 0x30), ParserState.DCS_PARAM, ParserAction.COLLECT, ParserState.DCS_INTERMEDIATE);
table.addMany(EXECUTABLES, ParserState.DCS_INTERMEDIATE, ParserAction.IGNORE, ParserState.DCS_INTERMEDIATE);
table.add(0x7f, ParserState.DCS_INTERMEDIATE, ParserAction.IGNORE, ParserState.DCS_INTERMEDIATE);
table.addMany(r(0x1c, 0x20), ParserState.DCS_INTERMEDIATE, ParserAction.IGNORE, ParserState.DCS_INTERMEDIATE);
table.addMany(r(0x20, 0x30), ParserState.DCS_INTERMEDIATE, ParserAction.COLLECT, ParserState.DCS_INTERMEDIATE);
table.addMany(r(0x30, 0x40), ParserState.DCS_INTERMEDIATE, ParserAction.IGNORE, ParserState.DCS_IGNORE);
table.addMany(r(0x40, 0x7f), ParserState.DCS_INTERMEDIATE, ParserAction.DCS_HOOK, ParserState.DCS_PASSTHROUGH);
table.addMany(r(0x40, 0x7f), ParserState.DCS_PARAM, ParserAction.DCS_HOOK, ParserState.DCS_PASSTHROUGH);
table.addMany(r(0x40, 0x7f), ParserState.DCS_ENTRY, ParserAction.DCS_HOOK, ParserState.DCS_PASSTHROUGH);
table.addMany(EXECUTABLES, ParserState.DCS_PASSTHROUGH, ParserAction.DCS_PUT, ParserState.DCS_PASSTHROUGH);
table.addMany(PRINTABLES, ParserState.DCS_PASSTHROUGH, ParserAction.DCS_PUT, ParserState.DCS_PASSTHROUGH);
table.add(0x7f, ParserState.DCS_PASSTHROUGH, ParserAction.IGNORE, ParserState.DCS_PASSTHROUGH);
table.addMany([0x1b, 0x9c], ParserState.DCS_PASSTHROUGH, ParserAction.DCS_UNHOOK, ParserState.GROUND);
return table;
})();
/**
* Dummy DCS handler as default fallback.
*/
class DcsDummy implements IDcsHandler {
hook(collect: string, params: number[], flag: number): void { }
put(data: string, start: number, end: number): void { }
unhook(): void { }
}
/**
* EscapeSequenceParser.
* This class implements the ANSI/DEC compatible parser described by
* Paul Williams (https://vt100.net/emu/dec_ansi_parser).
* To implement custom ANSI compliant escape sequences it is not needed to
* alter this parser, instead consider registering a custom handler.
* For non ANSI compliant sequences change the transition table with
* the optional `transitions` contructor argument and
* reimplement the `parse` method.
* NOTE: The parameter element notation is currently not supported.
* TODO: implement error recovery hook via error handler return values
*/
export class EscapeSequenceParser extends Disposable implements IEscapeSequenceParser {
public initialState: number;
public currentState: number;
// buffers over several parse calls
protected _osc: string;
protected _params: number[];
protected _collect: string;
// handler lookup containers
protected _printHandler: (data: string, start: number, end: number) => void;
protected _executeHandlers: any;
protected _csiHandlers: any;
protected _escHandlers: any;
protected _oscHandlers: any;
protected _dcsHandlers: any;
protected _activeDcsHandler: IDcsHandler | null;
protected _errorHandler: (state: IParsingState) => IParsingState;
// fallback handlers
protected _printHandlerFb: (data: string, start: number, end: number) => void;
protected _executeHandlerFb: (code: number) => void;
protected _csiHandlerFb: (collect: string, params: number[], flag: number) => void;
protected _escHandlerFb: (collect: string, flag: number) => void;
protected _oscHandlerFb: (identifier: number, data: string) => void;
protected _dcsHandlerFb: IDcsHandler;
protected _errorHandlerFb: (state: IParsingState) => IParsingState;
constructor(readonly TRANSITIONS: TransitionTable = VT500_TRANSITION_TABLE) {
super();
this.initialState = ParserState.GROUND;
this.currentState = this.initialState;
this._osc = '';
this._params = [0];
this._collect = '';
// set default fallback handlers and handler lookup containers
this._printHandlerFb = (data, start, end): void => { };
this._executeHandlerFb = (code: number): void => { };
this._csiHandlerFb = (collect: string, params: number[], flag: number): void => { };
this._escHandlerFb = (collect: string, flag: number): void => { };
this._oscHandlerFb = (identifier: number, data: string): void => { };
this._dcsHandlerFb = new DcsDummy();
this._errorHandlerFb = (state: IParsingState): IParsingState => state;
this._printHandler = this._printHandlerFb;
this._executeHandlers = Object.create(null);
this._csiHandlers = Object.create(null);
this._escHandlers = Object.create(null);
this._oscHandlers = Object.create(null);
this._dcsHandlers = Object.create(null);
this._activeDcsHandler = null;
this._errorHandler = this._errorHandlerFb;
}
public dispose(): void {
this._printHandlerFb = null;
this._executeHandlerFb = null;
this._csiHandlerFb = null;
this._escHandlerFb = null;
this._oscHandlerFb = null;
this._dcsHandlerFb = null;
this._errorHandlerFb = null;
this._printHandler = null;
this._executeHandlers = null;
this._csiHandlers = null;
this._escHandlers = null;
this._oscHandlers = null;
this._dcsHandlers = null;
this._activeDcsHandler = null;
this._errorHandler = null;
}
setPrintHandler(callback: (data: string, start: number, end: number) => void): void {
this._printHandler = callback;
}
clearPrintHandler(): void {
this._printHandler = this._printHandlerFb;
}
setExecuteHandler(flag: string, callback: () => void): void {
this._executeHandlers[flag.charCodeAt(0)] = callback;
}
clearExecuteHandler(flag: string): void {
if (this._executeHandlers[flag.charCodeAt(0)]) delete this._executeHandlers[flag.charCodeAt(0)];
}
setExecuteHandlerFallback(callback: (code: number) => void): void {
this._executeHandlerFb = callback;
}
setCsiHandler(flag: string, callback: (params: number[], collect: string) => void): void {
this._csiHandlers[flag.charCodeAt(0)] = callback;
}
clearCsiHandler(flag: string): void {
if (this._csiHandlers[flag.charCodeAt(0)]) delete this._csiHandlers[flag.charCodeAt(0)];
}
setCsiHandlerFallback(callback: (collect: string, params: number[], flag: number) => void): void {
this._csiHandlerFb = callback;
}
setEscHandler(collectAndFlag: string, callback: () => void): void {
this._escHandlers[collectAndFlag] = callback;
}
clearEscHandler(collectAndFlag: string): void {
if (this._escHandlers[collectAndFlag]) delete this._escHandlers[collectAndFlag];
}
setEscHandlerFallback(callback: (collect: string, flag: number) => void): void {
this._escHandlerFb = callback;
}
setOscHandler(ident: number, callback: (data: string) => void): void {
this._oscHandlers[ident] = callback;
}
clearOscHandler(ident: number): void {
if (this._oscHandlers[ident]) delete this._oscHandlers[ident];
}
setOscHandlerFallback(callback: (identifier: number, data: string) => void): void {
this._oscHandlerFb = callback;
}
setDcsHandler(collectAndFlag: string, handler: IDcsHandler): void {
this._dcsHandlers[collectAndFlag] = handler;
}
clearDcsHandler(collectAndFlag: string): void {
if (this._dcsHandlers[collectAndFlag]) delete this._dcsHandlers[collectAndFlag];
}
setDcsHandlerFallback(handler: IDcsHandler): void {
this._dcsHandlerFb = handler;
}
setErrorHandler(callback: (state: IParsingState) => IParsingState): void {
this._errorHandler = callback;
}
clearErrorHandler(): void {
this._errorHandler = this._errorHandlerFb;
}
reset(): void {
this.currentState = this.initialState;
this._osc = '';
this._params = [0];
this._collect = '';
this._activeDcsHandler = null;
}
parse(data: string): void {
let code = 0;
let transition = 0;
let error = false;
let currentState = this.currentState;
let print = -1;
let dcs = -1;
let osc = this._osc;
let collect = this._collect;
let params = this._params;
const table: Uint8Array | number[] = this.TRANSITIONS.table;
let dcsHandler: IDcsHandler | null = this._activeDcsHandler;
let callback: Function | null = null;
// process input string
const l = data.length;
for (let i = 0; i < l; ++i) {
code = data.charCodeAt(i);
// shortcut for most chars (print action)
if (currentState === ParserState.GROUND && code > 0x1f && code < 0x80) {
print = (~print) ? print : i;
do code = data.charCodeAt(++i);
while (i < l && code > 0x1f && code < 0x80);
i--;
continue;
}
// shortcut for CSI params
if (currentState === ParserState.CSI_PARAM && (code > 0x2f && code < 0x39)) {
params[params.length - 1] = params[params.length - 1] * 10 + code - 48;
continue;
}
// normal transition & action lookup
transition = (code < 0xa0) ? (table[currentState << 8 | code]) : DEFAULT_TRANSITION;
switch (transition >> 4) {
case ParserAction.PRINT:
print = (~print) ? print : i;
break;
case ParserAction.EXECUTE:
if (~print) {
this._printHandler(data, print, i);
print = -1;
}
callback = this._executeHandlers[code];
if (callback) callback();
else this._executeHandlerFb(code);
break;
case ParserAction.IGNORE:
// handle leftover print or dcs chars
if (~print) {
this._printHandler(data, print, i);
print = -1;
} else if (~dcs) {
dcsHandler.put(data, dcs, i);
dcs = -1;
}
break;
case ParserAction.ERROR:
// chars higher than 0x9f are handled by this action
// to keep the transition table small
if (code > 0x9f) {
switch (currentState) {
case ParserState.GROUND:
print = (~print) ? print : i;
break;
case ParserState.OSC_STRING:
osc += String.fromCharCode(code);
transition |= ParserState.OSC_STRING;
break;
case ParserState.CSI_IGNORE:
transition |= ParserState.CSI_IGNORE;
break;
case ParserState.DCS_IGNORE:
transition |= ParserState.DCS_IGNORE;
break;
case ParserState.DCS_PASSTHROUGH:
dcs = (~dcs) ? dcs : i;
transition |= ParserState.DCS_PASSTHROUGH;
break;
default:
error = true;
}
} else {
error = true;
}
// if we end up here a real error happened
if (error) {
const inject: IParsingState = this._errorHandler(
{
position: i,
code,
currentState,
print,
dcs,
osc,
collect,
params,
abort: false
});
if (inject.abort) return;
// TODO: inject return values
error = false;
}
break;
case ParserAction.CSI_DISPATCH:
callback = this._csiHandlers[code];
if (callback) callback(params, collect);
else this._csiHandlerFb(collect, params, code);
break;
case ParserAction.PARAM:
if (code === 0x3b) params.push(0);
else params[params.length - 1] = params[params.length - 1] * 10 + code - 48;
break;
case ParserAction.COLLECT:
collect += String.fromCharCode(code);
break;
case ParserAction.ESC_DISPATCH:
callback = this._escHandlers[collect + String.fromCharCode(code)];
if (callback) callback(collect, code);
else this._escHandlerFb(collect, code);
break;
case ParserAction.CLEAR:
if (~print) {
this._printHandler(data, print, i);
print = -1;
}
osc = '';
params = [0];
collect = '';
dcs = -1;
break;
case ParserAction.DCS_HOOK:
dcsHandler = this._dcsHandlers[collect + String.fromCharCode(code)];
if (!dcsHandler) dcsHandler = this._dcsHandlerFb;
dcsHandler.hook(collect, params, code);
break;
case ParserAction.DCS_PUT:
dcs = (~dcs) ? dcs : i;
break;
case ParserAction.DCS_UNHOOK:
if (dcsHandler) {
if (~dcs) dcsHandler.put(data, dcs, i);
dcsHandler.unhook();
dcsHandler = null;
}
if (code === 0x1b) transition |= ParserState.ESCAPE;
osc = '';
params = [0];
collect = '';
dcs = -1;
break;
case ParserAction.OSC_START:
if (~print) {
this._printHandler(data, print, i);
print = -1;
}
osc = '';
break;
case ParserAction.OSC_PUT:
osc += data.charAt(i);
break;
case ParserAction.OSC_END:
if (osc && code !== 0x18 && code !== 0x1a) {
// NOTE: OSC subparsing is not part of the original parser
// we do basic identifier parsing here to offer a jump table for OSC as well
const idx = osc.indexOf(';');
if (idx === -1) {
this._oscHandlerFb(-1, osc); // this is an error (malformed OSC)
} else {
// Note: NaN is not handled here
// either catch it with the fallback handler
// or with an explicit NaN OSC handler
const identifier = parseInt(osc.substring(0, idx));
const content = osc.substring(idx + 1);
callback = this._oscHandlers[identifier];
if (callback) callback(content);
else this._oscHandlerFb(identifier, content);
}
}
if (code === 0x1b) transition |= ParserState.ESCAPE;
osc = '';
params = [0];
collect = '';
dcs = -1;
break;
}
currentState = transition & 15;
}
// push leftover pushable buffers to terminal
if (currentState === ParserState.GROUND && ~print) {
this._printHandler(data, print, data.length);
} else if (currentState === ParserState.DCS_PASSTHROUGH && ~dcs && dcsHandler) {
dcsHandler.put(data, dcs, data.length);
}
// save non pushable buffers
this._osc = osc;
this._collect = collect;
this._params = params;
// save active dcs handler reference
this._activeDcsHandler = dcsHandler;
// save state
this.currentState = currentState;
}
}
@@ -77,74 +77,3 @@ export namespace C0 {
/** Delete (Caret = ^?) */
export const DEL = '\x7f';
}
/**
* C1 control codes
* See = https://en.wikipedia.org/wiki/C0_and_C1_control_codes
*/
export namespace C1 {
/** padding character */
export const PAD = '\x80';
/** High Octet Preset */
export const HOP = '\x81';
/** Break Permitted Here */
export const BPH = '\x82';
/** No Break Here */
export const NBH = '\x83';
/** Index */
export const IND = '\x84';
/** Next Line */
export const NEL = '\x85';
/** Start of Selected Area */
export const SSA = '\x86';
/** End of Selected Area */
export const ESA = '\x87';
/** Horizontal Tabulation Set */
export const HTS = '\x88';
/** Horizontal Tabulation With Justification */
export const HTJ = '\x89';
/** Vertical Tabulation Set */
export const VTS = '\x8a';
/** Partial Line Down */
export const PLD = '\x8b';
/** Partial Line Up */
export const PLU = '\x8c';
/** Reverse Index */
export const RI = '\x8d';
/** Single-Shift 2 */
export const SS2 = '\x8e';
/** Single-Shift 3 */
export const SS3 = '\x8f';
/** Device Control String */
export const DCS = '\x90';
/** Private Use 1 */
export const PU1 = '\x91';
/** Private Use 2 */
export const PU2 = '\x92';
/** Set Transmit State */
export const STS = '\x93';
/** Destructive backspace, intended to eliminate ambiguity about meaning of BS. */
export const CCH = '\x94';
/** Message Waiting */
export const MW = '\x95';
/** Start of Protected Area */
export const SPA = '\x96';
/** End of Protected Area */
export const EPA = '\x97';
/** Start of String */
export const SOS = '\x98';
/** Single Graphic Character Introducer */
export const SGCI = '\x99';
/** Single Character Introducer */
export const SCI = '\x9a';
/** Control Sequence Introducer */
export const CSI = '\x9b';
/** String Terminator */
export const ST = '\x9c';
/** Operating System Command */
export const OSC = '\x9d';
/** Privacy Message */
export const PM = '\x9e';
/** Application Program Command */
export const APC = '\x9f';
}
+3 -7
View File
@@ -5,13 +5,11 @@
import { XtermListener } from './Types';
import { IEventEmitter, IDisposable } from 'xterm';
import { Disposable } from './common/Lifecycle';
export class EventEmitter extends Disposable implements IEventEmitter, IDisposable {
export class EventEmitter implements IEventEmitter, IDisposable {
private _events: {[type: string]: XtermListener[]};
constructor() {
super();
// Restore the previous events if available, this will happen if the
// constructor is called multiple times on the same object (terminal reset).
this._events = this._events || {};
@@ -28,7 +26,6 @@ export class EventEmitter extends Disposable implements IEventEmitter, IDisposab
* @param handler The handler for the listener.
*/
public addDisposableListener(type: string, handler: XtermListener): IDisposable {
// TODO: Rename addDisposableEventListener to more easily disambiguate from Dom listener
this.on(type, handler);
return {
dispose: () => {
@@ -47,7 +44,7 @@ export class EventEmitter extends Disposable implements IEventEmitter, IDisposab
return;
}
const obj = this._events[type];
let obj = this._events[type];
let i = obj.length;
while (i--) {
@@ -68,7 +65,7 @@ export class EventEmitter extends Disposable implements IEventEmitter, IDisposab
if (!this._events[type]) {
return;
}
const obj = this._events[type];
let obj = this._events[type];
for (let i = 0; i < obj.length; i++) {
obj[i].apply(this, args);
}
@@ -79,7 +76,6 @@ export class EventEmitter extends Disposable implements IEventEmitter, IDisposab
}
public dispose(): void {
super.dispose();
this._events = {};
}
}
+16 -21
View File
@@ -9,78 +9,73 @@ import { MockInputHandlingTerminal } from './utils/TestUtils.test';
describe('InputHandler', () => {
describe('save and restore cursor', () => {
const terminal = new MockInputHandlingTerminal();
let terminal = new MockInputHandlingTerminal();
terminal.buffer.x = 1;
terminal.buffer.y = 2;
terminal.curAttr = 3;
const inputHandler = new InputHandler(terminal);
let inputHandler = new InputHandler(terminal);
// Save cursor position
inputHandler.saveCursor([]);
assert.equal(terminal.buffer.x, 1);
assert.equal(terminal.buffer.y, 2);
assert.equal(terminal.curAttr, 3);
// Change cursor position
terminal.buffer.x = 10;
terminal.buffer.y = 20;
terminal.curAttr = 30;
// Restore cursor position
inputHandler.restoreCursor([]);
assert.equal(terminal.buffer.x, 1);
assert.equal(terminal.buffer.y, 2);
assert.equal(terminal.curAttr, 3);
});
describe('setCursorStyle', () => {
it('should call Terminal.setOption with correct params', () => {
const terminal = new MockInputHandlingTerminal();
const inputHandler = new InputHandler(terminal);
const collect = ' ';
let terminal = new MockInputHandlingTerminal();
let inputHandler = new InputHandler(terminal);
inputHandler.setCursorStyle([0], collect);
inputHandler.setCursorStyle([0]);
assert.equal(terminal.options['cursorStyle'], 'block');
assert.equal(terminal.options['cursorBlink'], true);
terminal.options = {};
inputHandler.setCursorStyle([1], collect);
inputHandler.setCursorStyle([1]);
assert.equal(terminal.options['cursorStyle'], 'block');
assert.equal(terminal.options['cursorBlink'], true);
terminal.options = {};
inputHandler.setCursorStyle([2], collect);
inputHandler.setCursorStyle([2]);
assert.equal(terminal.options['cursorStyle'], 'block');
assert.equal(terminal.options['cursorBlink'], false);
terminal.options = {};
inputHandler.setCursorStyle([3], collect);
inputHandler.setCursorStyle([3]);
assert.equal(terminal.options['cursorStyle'], 'underline');
assert.equal(terminal.options['cursorBlink'], true);
terminal.options = {};
inputHandler.setCursorStyle([4], collect);
inputHandler.setCursorStyle([4]);
assert.equal(terminal.options['cursorStyle'], 'underline');
assert.equal(terminal.options['cursorBlink'], false);
terminal.options = {};
inputHandler.setCursorStyle([5], collect);
inputHandler.setCursorStyle([5]);
assert.equal(terminal.options['cursorStyle'], 'bar');
assert.equal(terminal.options['cursorBlink'], true);
terminal.options = {};
inputHandler.setCursorStyle([6], collect);
inputHandler.setCursorStyle([6]);
assert.equal(terminal.options['cursorStyle'], 'bar');
assert.equal(terminal.options['cursorBlink'], false);
});
});
describe('setMode', () => {
it('should toggle Terminal.bracketedPasteMode', () => {
const terminal = new MockInputHandlingTerminal();
const collect = '?';
let terminal = new MockInputHandlingTerminal();
terminal.prefix = '?';
terminal.bracketedPasteMode = false;
const inputHandler = new InputHandler(terminal);
let inputHandler = new InputHandler(terminal);
// Set bracketed paste mode
inputHandler.setMode([2004], collect);
inputHandler.setMode([2004]);
assert.equal(terminal.bracketedPasteMode, true);
// Reset bracketed paste mode
inputHandler.resetMode([2004], collect);
inputHandler.resetMode([2004]);
assert.equal(terminal.bracketedPasteMode, false);
});
});
+118 -600
View File
@@ -4,106 +4,12 @@
* @license MIT
*/
import { CharData, IInputHandler, IDcsHandler, IEscapeSequenceParser, IBuffer } from './Types';
import { C0, C1 } from './common/data/EscapeSequences';
import { CHARSETS, DEFAULT_CHARSET } from './core/data/Charsets';
import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CODE_INDEX, DEFAULT_ATTR } from './Buffer';
import { CharData, IInputHandler, IInputHandlingTerminal } from './Types';
import { C0 } from './EscapeSequences';
import { DEFAULT_CHARSET } from './Charsets';
import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX } from './Buffer';
import { FLAGS } from './renderer/Types';
import { wcwidth } from './CharWidth';
import { EscapeSequenceParser } from './EscapeSequenceParser';
import { ICharset } from './core/Types';
import { Disposable } from './common/Lifecycle';
/**
* Map collect to glevel. Used in `selectCharset`.
*/
const GLEVEL: {[key: string]: number} = {'(': 0, ')': 1, '*': 2, '+': 3, '-': 1, '.': 2};
/**
* DCS subparser implementations
*/
/**
* DCS + q Pt ST (xterm)
* Request Terminfo String
* not supported
*/
class RequestTerminfo implements IDcsHandler {
private _data: string;
constructor(private _terminal: any) { }
hook(collect: string, params: number[], flag: number): void {
this._data = '';
}
put(data: string, start: number, end: number): void {
this._data += data.substring(start, end);
}
unhook(): void {
// invalid: DCS 0 + r Pt ST
this._terminal.send(`${C0.ESC}P0+r${this._data}${C0.ESC}\\`);
}
}
/**
* DCS $ q Pt ST
* DECRQSS (https://vt100.net/docs/vt510-rm/DECRQSS.html)
* Request Status String (DECRQSS), VT420 and up.
* Response: DECRPSS (https://vt100.net/docs/vt510-rm/DECRPSS.html)
*/
class DECRQSS implements IDcsHandler {
private _data: string;
constructor(private _terminal: any) { }
hook(collect: string, params: number[], flag: number): void {
// reset data
this._data = '';
}
put(data: string, start: number, end: number): void {
this._data += data.substring(start, end);
}
unhook(): void {
switch (this._data) {
// valid: DCS 1 $ r Pt ST (xterm)
case '"q': // DECSCA
return this._terminal.send(`${C0.ESC}P1$r0"q${C0.ESC}\\`);
case '"p': // DECSCL
return this._terminal.send(`${C0.ESC}P1$r61"p${C0.ESC}\\`);
case 'r': // DECSTBM
const pt = '' + (this._terminal.buffer.scrollTop + 1) +
';' + (this._terminal.buffer.scrollBottom + 1) + 'r';
return this._terminal.send(`${C0.ESC}P1$r${pt}${C0.ESC}\\`);
case 'm': // SGR
// TODO: report real settings instead of 0m
return this._terminal.send(`${C0.ESC}P1$r0m${C0.ESC}\\`);
case ' q': // DECSCUSR
const STYLES: {[key: string]: number} = {'block': 2, 'underline': 4, 'bar': 6};
let style = STYLES[this._terminal.getOption('cursorStyle')];
style -= this._terminal.getOption('cursorBlink');
return this._terminal.send(`${C0.ESC}P1$r${style} q${C0.ESC}\\`);
default:
// invalid: DCS 0 $ r Pt ST (xterm)
this._terminal.error('Unknown DCS $q %s', this._data);
this._terminal.send(`${C0.ESC}P0$r${this._data}${C0.ESC}\\`);
}
}
}
/**
* DCS Ps; Ps| Pt ST
* DECUDK (https://vt100.net/docs/vt510-rm/DECUDK.html)
* not supported
*/
/**
* DCS + p Pt ST (xterm)
* Set Terminfo Data
* not supported
*/
/**
* The terminal's standard implementation of IInputHandler, this handles all
@@ -112,291 +18,54 @@ class DECRQSS implements IDcsHandler {
* Refer to http://invisible-island.net/xterm/ctlseqs/ctlseqs.html to understand
* each function's header comment.
*/
export class InputHandler extends Disposable implements IInputHandler {
private _surrogateHigh: string;
export class InputHandler implements IInputHandler {
constructor(private _terminal: IInputHandlingTerminal) { }
constructor(
private _terminal: any, // TODO: reestablish IInputHandlingTerminal here
private _parser: IEscapeSequenceParser = new EscapeSequenceParser())
{
super();
public addChar(char: string, code: number): void {
if (char >= ' ') {
this.register(this._parser);
this._surrogateHigh = '';
/**
* custom fallback handlers
*/
this._parser.setCsiHandlerFallback((collect: string, params: number[], flag: number) => {
this._terminal.error('Unknown CSI code: ', collect, params, String.fromCharCode(flag));
});
this._parser.setEscHandlerFallback((collect: string, flag: number) => {
this._terminal.error('Unknown ESC code: ', collect, String.fromCharCode(flag));
});
this._parser.setExecuteHandlerFallback((code: number) => {
this._terminal.error('Unknown EXECUTE code: ', code);
});
this._parser.setOscHandlerFallback((identifier: number, data: string) => {
this._terminal.error('Unknown OSC code: ', identifier, data);
});
/**
* print handler
*/
this._parser.setPrintHandler((data, start, end): void => this.print(data, start, end));
/**
* CSI handler
*/
this._parser.setCsiHandler('@', (params, collect) => this.insertChars(params));
this._parser.setCsiHandler('A', (params, collect) => this.cursorUp(params));
this._parser.setCsiHandler('B', (params, collect) => this.cursorDown(params));
this._parser.setCsiHandler('C', (params, collect) => this.cursorForward(params));
this._parser.setCsiHandler('D', (params, collect) => this.cursorBackward(params));
this._parser.setCsiHandler('E', (params, collect) => this.cursorNextLine(params));
this._parser.setCsiHandler('F', (params, collect) => this.cursorPrecedingLine(params));
this._parser.setCsiHandler('G', (params, collect) => this.cursorCharAbsolute(params));
this._parser.setCsiHandler('H', (params, collect) => this.cursorPosition(params));
this._parser.setCsiHandler('I', (params, collect) => this.cursorForwardTab(params));
this._parser.setCsiHandler('J', (params, collect) => this.eraseInDisplay(params));
this._parser.setCsiHandler('K', (params, collect) => this.eraseInLine(params));
this._parser.setCsiHandler('L', (params, collect) => this.insertLines(params));
this._parser.setCsiHandler('M', (params, collect) => this.deleteLines(params));
this._parser.setCsiHandler('P', (params, collect) => this.deleteChars(params));
this._parser.setCsiHandler('S', (params, collect) => this.scrollUp(params));
this._parser.setCsiHandler('T', (params, collect) => this.scrollDown(params, collect));
this._parser.setCsiHandler('X', (params, collect) => this.eraseChars(params));
this._parser.setCsiHandler('Z', (params, collect) => this.cursorBackwardTab(params));
this._parser.setCsiHandler('`', (params, collect) => this.charPosAbsolute(params));
this._parser.setCsiHandler('a', (params, collect) => this.hPositionRelative(params));
this._parser.setCsiHandler('b', (params, collect) => this.repeatPrecedingCharacter(params));
this._parser.setCsiHandler('c', (params, collect) => this.sendDeviceAttributes(params, collect));
this._parser.setCsiHandler('d', (params, collect) => this.linePosAbsolute(params));
this._parser.setCsiHandler('e', (params, collect) => this.vPositionRelative(params));
this._parser.setCsiHandler('f', (params, collect) => this.hVPosition(params));
this._parser.setCsiHandler('g', (params, collect) => this.tabClear(params));
this._parser.setCsiHandler('h', (params, collect) => this.setMode(params, collect));
this._parser.setCsiHandler('l', (params, collect) => this.resetMode(params, collect));
this._parser.setCsiHandler('m', (params, collect) => this.charAttributes(params));
this._parser.setCsiHandler('n', (params, collect) => this.deviceStatus(params, collect));
this._parser.setCsiHandler('p', (params, collect) => this.softReset(params, collect));
this._parser.setCsiHandler('q', (params, collect) => this.setCursorStyle(params, collect));
this._parser.setCsiHandler('r', (params, collect) => this.setScrollRegion(params, collect));
this._parser.setCsiHandler('s', (params, collect) => this.saveCursor(params));
this._parser.setCsiHandler('u', (params, collect) => this.restoreCursor(params));
/**
* execute handler
*/
this._parser.setExecuteHandler(C0.BEL, () => this.bell());
this._parser.setExecuteHandler(C0.LF, () => this.lineFeed());
this._parser.setExecuteHandler(C0.VT, () => this.lineFeed());
this._parser.setExecuteHandler(C0.FF, () => this.lineFeed());
this._parser.setExecuteHandler(C0.CR, () => this.carriageReturn());
this._parser.setExecuteHandler(C0.BS, () => this.backspace());
this._parser.setExecuteHandler(C0.HT, () => this.tab());
this._parser.setExecuteHandler(C0.SO, () => this.shiftOut());
this._parser.setExecuteHandler(C0.SI, () => this.shiftIn());
// FIXME: What do to with missing? Old code just added those to print.
// some C1 control codes - FIXME: should those be enabled by default?
this._parser.setExecuteHandler(C1.IND, () => this.index());
this._parser.setExecuteHandler(C1.NEL, () => this.nextLine());
this._parser.setExecuteHandler(C1.HTS, () => this.tabSet());
/**
* OSC handler
*/
// 0 - icon name + title
this._parser.setOscHandler(0, (data) => this.setTitle(data));
// 1 - icon name
// 2 - title
this._parser.setOscHandler(2, (data) => this.setTitle(data));
// 3 - set property X in the form "prop=value"
// 4 - Change Color Number
// 5 - Change Special Color Number
// 6 - Enable/disable Special Color Number c
// 7 - current directory? (not in xterm spec, see https://gitlab.com/gnachman/iterm2/issues/3939)
// 10 - Change VT100 text foreground color to Pt.
// 11 - Change VT100 text background color to Pt.
// 12 - Change text cursor color to Pt.
// 13 - Change mouse foreground color to Pt.
// 14 - Change mouse background color to Pt.
// 15 - Change Tektronix foreground color to Pt.
// 16 - Change Tektronix background color to Pt.
// 17 - Change highlight background color to Pt.
// 18 - Change Tektronix cursor color to Pt.
// 19 - Change highlight foreground color to Pt.
// 46 - Change Log File to Pt.
// 50 - Set Font to Pt.
// 51 - reserved for Emacs shell.
// 52 - Manipulate Selection Data.
// 104 ; c - Reset Color Number c.
// 105 ; c - Reset Special Color Number c.
// 106 ; c; f - Enable/disable Special Color Number c.
// 110 - Reset VT100 text foreground color.
// 111 - Reset VT100 text background color.
// 112 - Reset text cursor color.
// 113 - Reset mouse foreground color.
// 114 - Reset mouse background color.
// 115 - Reset Tektronix foreground color.
// 116 - Reset Tektronix background color.
// 117 - Reset highlight color.
// 118 - Reset Tektronix cursor color.
// 119 - Reset highlight foreground color.
/**
* ESC handlers
*/
this._parser.setEscHandler('7', () => this.saveCursor([]));
this._parser.setEscHandler('8', () => this.restoreCursor([]));
this._parser.setEscHandler('D', () => this.index());
this._parser.setEscHandler('E', () => this.nextLine());
this._parser.setEscHandler('H', () => this.tabSet());
this._parser.setEscHandler('M', () => this.reverseIndex());
this._parser.setEscHandler('=', () => this.keypadApplicationMode());
this._parser.setEscHandler('>', () => this.keypadNumericMode());
this._parser.setEscHandler('c', () => this.reset());
this._parser.setEscHandler('n', () => this.setgLevel(2));
this._parser.setEscHandler('o', () => this.setgLevel(3));
this._parser.setEscHandler('|', () => this.setgLevel(3));
this._parser.setEscHandler('}', () => this.setgLevel(2));
this._parser.setEscHandler('~', () => this.setgLevel(1));
this._parser.setEscHandler('%@', () => this.selectDefaultCharset());
this._parser.setEscHandler('%G', () => this.selectDefaultCharset());
for (const flag in CHARSETS) {
this._parser.setEscHandler('(' + flag, () => this.selectCharset('(' + flag));
this._parser.setEscHandler(')' + flag, () => this.selectCharset(')' + flag));
this._parser.setEscHandler('*' + flag, () => this.selectCharset('*' + flag));
this._parser.setEscHandler('+' + flag, () => this.selectCharset('+' + flag));
this._parser.setEscHandler('-' + flag, () => this.selectCharset('-' + flag));
this._parser.setEscHandler('.' + flag, () => this.selectCharset('.' + flag));
this._parser.setEscHandler('/' + flag, () => this.selectCharset('/' + flag)); // TODO: supported?
}
/**
* error handler
*/
this._parser.setErrorHandler((state) => {
this._terminal.error('Parsing error: ', state);
return state;
});
/**
* DCS handler
*/
this._parser.setDcsHandler('$q', new DECRQSS(this._terminal));
this._parser.setDcsHandler('+q', new RequestTerminfo(this._terminal));
}
public dispose(): void {
super.dispose();
this._terminal = null;
}
public parse(data: string): void {
let buffer = this._terminal.buffer;
const cursorStartX = buffer.x;
const cursorStartY = buffer.y;
if (this._terminal.debug) {
this._terminal.log('data: ' + data);
}
// apply leftover surrogate high from last write
if (this._surrogateHigh) {
data = this._surrogateHigh + data;
this._surrogateHigh = '';
}
this._parser.parse(data);
buffer = this._terminal.buffer;
if (buffer.x !== cursorStartX || buffer.y !== cursorStartY) {
this._terminal.emit('cursormove');
}
}
public print(data: string, start: number, end: number): void {
let char: string;
let code: number;
let low: number;
let chWidth: number;
const buffer: IBuffer = this._terminal.buffer;
const charset: ICharset = this._terminal.charset;
const screenReaderMode: boolean = this._terminal.options.screenReaderMode;
const cols: number = this._terminal.cols;
const wraparoundMode: boolean = this._terminal.wraparoundMode;
const insertMode: boolean = this._terminal.insertMode;
const curAttr: number = this._terminal.curAttr;
let bufferRow = buffer.lines.get(buffer.y + buffer.ybase);
this._terminal.updateRange(buffer.y);
for (let stringPosition = start; stringPosition < end; ++stringPosition) {
char = data.charAt(stringPosition);
code = data.charCodeAt(stringPosition);
// surrogate pair handling
if (0xD800 <= code && code <= 0xDBFF) {
// we got a surrogate high
// get surrogate low (next 2 bytes)
low = data.charCodeAt(stringPosition + 1);
if (isNaN(low)) {
// end of data stream, save surrogate high
this._surrogateHigh = char;
continue;
}
code = ((code - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000;
char += data.charAt(stringPosition + 1);
}
// surrogate low - already handled above
if (0xDC00 <= code && code <= 0xDFFF) {
continue;
}
// make buffer local for faster access
const buffer = this._terminal.buffer;
// calculate print space
// expensive call, therefore we save width in line buffer
chWidth = wcwidth(code);
const chWidth = wcwidth(code);
// get charset replacement character
// FIXME: Should code be replaced as well?
if (charset) {
char = charset[char] || char;
if (this._terminal.charset && this._terminal.charset[char]) {
char = this._terminal.charset[char];
}
if (screenReaderMode) {
if (this._terminal.options.screenReaderMode) {
this._terminal.emit('a11y.char', char);
}
// insert combining char at last cursor position
let row = buffer.y + buffer.ybase;
// insert combining char in last cell
// FIXME: needs handling after cursor jumps
// buffer.x should never be 0 for a combining char
// since they always follow a cell consuming char
// therefore we can test for buffer.x to avoid overflow left
if (!chWidth && buffer.x) {
if (bufferRow[buffer.x - 1]) {
if (!bufferRow[buffer.x - 1][CHAR_DATA_WIDTH_INDEX]) {
// dont overflow left
if (buffer.lines.get(row)[buffer.x - 1]) {
if (!buffer.lines.get(row)[buffer.x - 1][CHAR_DATA_WIDTH_INDEX]) {
// found empty cell after fullwidth, need to go 2 cells back
// it is save to step 2 cells back here
// since an empty cell is only set by fullwidth chars
if (bufferRow[buffer.x - 2]) {
bufferRow[buffer.x - 2][CHAR_DATA_CHAR_INDEX] += char;
bufferRow[buffer.x - 2][CHAR_DATA_CODE_INDEX] = code;
if (buffer.lines.get(row)[buffer.x - 2]) {
buffer.lines.get(row)[buffer.x - 2][CHAR_DATA_CHAR_INDEX] += char;
buffer.lines.get(row)[buffer.x - 2][3] = char.charCodeAt(0);
}
} else {
bufferRow[buffer.x - 1][CHAR_DATA_CHAR_INDEX] += char;
bufferRow[buffer.x - 1][CHAR_DATA_CODE_INDEX] = code;
buffer.lines.get(row)[buffer.x - 1][CHAR_DATA_CHAR_INDEX] += char;
buffer.lines.get(row)[buffer.x - 1][3] = char.charCodeAt(0);
}
this._terminal.updateRange(buffer.y);
}
continue;
return;
}
// goto next line if ch would overflow
// TODO: needs a global min terminal width of 2
if (buffer.x + chWidth - 1 >= cols) {
if (buffer.x + chWidth - 1 >= this._terminal.cols) {
// autowrap - DECAWM
// automatically wraps to the beginning of the next line
if (wraparoundMode) {
if (this._terminal.wraparoundMode) {
buffer.x = 0;
buffer.y++;
if (buffer.y > buffer.scrollBottom) {
@@ -407,47 +76,42 @@ export class InputHandler extends Disposable implements IInputHandler {
// wrapped line
(<any>buffer.lines.get(buffer.y)).isWrapped = true;
}
// row changed, get it again
bufferRow = buffer.lines.get(buffer.y + buffer.ybase);
} else {
if (chWidth === 2) {
// FIXME: check for xterm behavior
// What to do here? We got a wide char that does not fit into last cell
continue;
if (chWidth === 2) { // FIXME: check for xterm behavior
return;
}
// FIXME: Do we have to set buffer.x to cols - 1, if not wrapping?
}
}
row = buffer.y + buffer.ybase;
// insert mode: move characters to right
// To achieve insert, we remove cells from the right
// and insert empty ones at cursor position
if (insertMode) {
if (this._terminal.insertMode) {
// do this twice for a fullwidth char
for (let moves = 0; moves < chWidth; ++moves) {
// remove last cell
// if it's width is 0, we have to adjust the second last cell as well
const removed = bufferRow.pop();
// remove last cell, if it's width is 0
// we have to adjust the second last cell as well
const removed = buffer.lines.get(buffer.y + buffer.ybase).pop();
if (removed[CHAR_DATA_WIDTH_INDEX] === 0
&& bufferRow[this._terminal.cols - 2]
&& bufferRow[this._terminal.cols - 2][CHAR_DATA_WIDTH_INDEX] === 2) {
bufferRow[this._terminal.cols - 2] = [curAttr, ' ', 1, 32 /* ' '.charCodeAt(0) */ ];
&& buffer.lines.get(row)[this._terminal.cols - 2]
&& buffer.lines.get(row)[this._terminal.cols - 2][CHAR_DATA_WIDTH_INDEX] === 2) {
buffer.lines.get(row)[this._terminal.cols - 2] = [this._terminal.curAttr, ' ', 1, ' '.charCodeAt(0)];
}
// insert empty cell at cursor
bufferRow.splice(buffer.x, 0, [curAttr, ' ', 1, 32 /* ' '.charCodeAt(0) */ ]);
buffer.lines.get(row).splice(buffer.x, 0, [this._terminal.curAttr, ' ', 1, ' '.charCodeAt(0)]);
}
}
// write current char to buffer and advance cursor
bufferRow[buffer.x++] = [curAttr, char, chWidth, code];
buffer.lines.get(row)[buffer.x] = [this._terminal.curAttr, char, chWidth, char.charCodeAt(0)];
buffer.x++;
this._terminal.updateRange(buffer.y);
// fullwidth char - also set next cell to placeholder stub and advance cursor
// fullwidth char - set next cell width to zero and advance cursor
if (chWidth === 2) {
bufferRow[buffer.x++] = [curAttr, '', 0, undefined];
buffer.lines.get(row)[buffer.x] = [this._terminal.curAttr, '', 0, undefined];
buffer.x++;
}
}
this._terminal.updateRange(buffer.y);
}
/**
@@ -797,10 +461,10 @@ export class InputHandler extends Disposable implements IInputHandler {
// make buffer local for faster access
const buffer = this._terminal.buffer;
const row: number = buffer.y + buffer.ybase;
let row: number = buffer.y + buffer.ybase;
const scrollBottomRowsOffset = this._terminal.rows - 1 - buffer.scrollBottom;
const scrollBottomAbsolute = this._terminal.rows - 1 + buffer.ybase - scrollBottomRowsOffset + 1;
let scrollBottomRowsOffset = this._terminal.rows - 1 - buffer.scrollBottom;
let scrollBottomAbsolute = this._terminal.rows - 1 + buffer.ybase - scrollBottomRowsOffset + 1;
while (param--) {
// test: echo -e '\e[44m\e[1L\e[0m'
// blankLine(true) - xterm/linux behavior
@@ -887,21 +551,19 @@ export class InputHandler extends Disposable implements IInputHandler {
/**
* CSI Ps T Scroll down Ps lines (default = 1) (SD).
*/
public scrollDown(params: number[], collect?: string): void {
if (params.length < 2 && !collect) {
let param = params[0] || 1;
public scrollDown(params: number[]): void {
let param = params[0] || 1;
// make buffer local for faster access
const buffer = this._terminal.buffer;
// make buffer local for faster access
const buffer = this._terminal.buffer;
while (param--) {
buffer.lines.splice(buffer.ybase + buffer.scrollBottom, 1);
buffer.lines.splice(buffer.ybase + buffer.scrollTop, 0, this._terminal.blankLine());
}
// this.maxRange();
this._terminal.updateRange(buffer.scrollTop);
this._terminal.updateRange(buffer.scrollBottom);
while (param--) {
buffer.lines.splice(buffer.ybase + buffer.scrollBottom, 1);
buffer.lines.splice(buffer.ybase + buffer.scrollTop, 0, this._terminal.blankLine());
}
// this.maxRange();
this._terminal.updateRange(buffer.scrollTop);
this._terminal.updateRange(buffer.scrollBottom);
}
/**
@@ -960,7 +622,7 @@ export class InputHandler extends Disposable implements IInputHandler {
* [columns] (default = [row,col+1]) (HPR)
* reuse CSI Ps C ?
*/
public hPositionRelative(params: number[]): void {
public HPositionRelative(params: number[]): void {
let param = params[0];
if (param < 1) {
param = 1;
@@ -981,7 +643,7 @@ export class InputHandler extends Disposable implements IInputHandler {
const buffer = this._terminal.buffer;
const line = buffer.lines.get(buffer.ybase + buffer.y);
const ch = line[buffer.x - 1] || [DEFAULT_ATTR, ' ', 1, 32];
const ch = line[buffer.x - 1] || [this._terminal.defAttr, ' ', 1, 32];
while (param--) {
line[buffer.x++] = ch;
@@ -1025,18 +687,18 @@ export class InputHandler extends Disposable implements IInputHandler {
* xterm/charproc.c - line 2012, for more information.
* vim responds with ^[[?0c or ^[[?1c after the terminal's response (?)
*/
public sendDeviceAttributes(params: number[], collect?: string): void {
public sendDeviceAttributes(params: number[]): void {
if (params[0] > 0) {
return;
}
if (!collect) {
if (!this._terminal.prefix) {
if (this._terminal.is('xterm') || this._terminal.is('rxvt-unicode') || this._terminal.is('screen')) {
this._terminal.send(C0.ESC + '[?1;2c');
} else if (this._terminal.is('linux')) {
this._terminal.send(C0.ESC + '[?6c');
}
} else if (collect === '>') {
} else if (this._terminal.prefix === '>') {
// xterm and urxvt
// seem to spit this
// out around ~370 times (?).
@@ -1074,7 +736,7 @@ export class InputHandler extends Disposable implements IInputHandler {
* [rows] (default = [row+1,column])
* reuse CSI Ps B ?
*/
public vPositionRelative(params: number[]): void {
public VPositionRelative(params: number[]): void {
let param = params[0];
if (param < 1) {
param = 1;
@@ -1094,7 +756,7 @@ export class InputHandler extends Disposable implements IInputHandler {
* Horizontal and Vertical Position [row;column] (default =
* [1,1]) (HVP).
*/
public hVPosition(params: number[]): void {
public HVPosition(params: number[]): void {
if (params[0] < 1) params[0] = 1;
if (params[1] < 1) params[1] = 1;
@@ -1118,7 +780,7 @@ export class InputHandler extends Disposable implements IInputHandler {
* http://vt100.net/annarbor/aaa-ug/section6.html
*/
public tabClear(params: number[]): void {
const param = params[0];
let param = params[0];
if (param <= 0) {
delete this._terminal.buffer.tabs[this._terminal.buffer.x];
} else if (param === 3) {
@@ -1212,7 +874,7 @@ export class InputHandler extends Disposable implements IInputHandler {
* Modes:
* http: *vt100.net/docs/vt220-rm/chapter4.html
*/
public setMode(params: number[], collect?: string): void {
public setMode(params: number[]): void {
if (params.length > 1) {
for (let i = 0; i < params.length; i++) {
this.setMode([params[i]]);
@@ -1221,7 +883,7 @@ export class InputHandler extends Disposable implements IInputHandler {
return;
}
if (!collect) {
if (!this._terminal.prefix) {
switch (params[0]) {
case 4:
this._terminal.insertMode = true;
@@ -1230,7 +892,7 @@ export class InputHandler extends Disposable implements IInputHandler {
// this._t.convertEol = true;
break;
}
} else if (collect === '?') {
} else if (this._terminal.prefix === '?') {
switch (params[0]) {
case 1:
this._terminal.applicationCursor = true;
@@ -1406,7 +1068,7 @@ export class InputHandler extends Disposable implements IInputHandler {
* Ps = 1 0 6 1 -> Reset keyboard emulation to Sun/PC style.
* Ps = 2 0 0 4 -> Reset bracketed paste mode.
*/
public resetMode(params: number[], collect?: string): void {
public resetMode(params: number[]): void {
if (params.length > 1) {
for (let i = 0; i < params.length; i++) {
this.resetMode([params[i]]);
@@ -1415,7 +1077,7 @@ export class InputHandler extends Disposable implements IInputHandler {
return;
}
if (!collect) {
if (!this._terminal.prefix) {
switch (params[0]) {
case 4:
this._terminal.insertMode = false;
@@ -1424,7 +1086,7 @@ export class InputHandler extends Disposable implements IInputHandler {
// this._t.convertEol = false;
break;
}
} else if (collect === '?') {
} else if (this._terminal.prefix === '?') {
switch (params[0]) {
case 1:
this._terminal.applicationCursor = false;
@@ -1564,7 +1226,7 @@ export class InputHandler extends Disposable implements IInputHandler {
public charAttributes(params: number[]): void {
// Optimize a single SGR0.
if (params.length === 1 && params[0] === 0) {
this._terminal.curAttr = DEFAULT_ATTR;
this._terminal.curAttr = this._terminal.defAttr;
return;
}
@@ -1592,9 +1254,9 @@ export class InputHandler extends Disposable implements IInputHandler {
bg = p - 100;
} else if (p === 0) {
// default
flags = DEFAULT_ATTR >> 18;
fg = (DEFAULT_ATTR >> 9) & 0x1ff;
bg = DEFAULT_ATTR & 0x1ff;
flags = this._terminal.defAttr >> 18;
fg = (this._terminal.defAttr >> 9) & 0x1ff;
bg = this._terminal.defAttr & 0x1ff;
// flags = 0;
// fg = 0x1ff;
// bg = 0x1ff;
@@ -1638,10 +1300,10 @@ export class InputHandler extends Disposable implements IInputHandler {
flags &= ~FLAGS.INVISIBLE;
} else if (p === 39) {
// reset fg
fg = (DEFAULT_ATTR >> 9) & 0x1ff;
fg = (this._terminal.defAttr >> 9) & 0x1ff;
} else if (p === 49) {
// reset bg
bg = DEFAULT_ATTR & 0x1ff;
bg = this._terminal.defAttr & 0x1ff;
} else if (p === 38) {
// fg color 256
if (params[i + 1] === 2) {
@@ -1674,8 +1336,8 @@ export class InputHandler extends Disposable implements IInputHandler {
}
} else if (p === 100) {
// reset fg/bg
fg = (DEFAULT_ATTR >> 9) & 0x1ff;
bg = DEFAULT_ATTR & 0x1ff;
fg = (this._terminal.defAttr >> 9) & 0x1ff;
bg = this._terminal.defAttr & 0x1ff;
} else {
this._terminal.error('Unknown SGR attribute: %d.', p);
}
@@ -1707,8 +1369,8 @@ export class InputHandler extends Disposable implements IInputHandler {
* CSI ? 5 3 n Locator available, if compiled-in, or
* CSI ? 5 0 n No Locator, if not.
*/
public deviceStatus(params: number[], collect?: string): void {
if (!collect) {
public deviceStatus(params: number[]): void {
if (!this._terminal.prefix) {
switch (params[0]) {
case 5:
// status report
@@ -1723,7 +1385,7 @@ export class InputHandler extends Disposable implements IInputHandler {
+ 'R');
break;
}
} else if (collect === '?') {
} else if (this._terminal.prefix === '?') {
// modern xterm doesnt seem to
// respond to any of these except ?6, 6, and 5
switch (params[0]) {
@@ -1759,23 +1421,21 @@ export class InputHandler extends Disposable implements IInputHandler {
* CSI ! p Soft terminal reset (DECSTR).
* http://vt100.net/docs/vt220-rm/table4-10.html
*/
public softReset(params: number[], collect?: string): void {
if (collect === '!') {
this._terminal.cursorHidden = false;
this._terminal.insertMode = false;
this._terminal.originMode = false;
this._terminal.wraparoundMode = true; // defaults: xterm - true, vt100 - false
this._terminal.applicationKeypad = false; // ?
this._terminal.viewport.syncScrollArea();
this._terminal.applicationCursor = false;
this._terminal.buffer.scrollTop = 0;
this._terminal.buffer.scrollBottom = this._terminal.rows - 1;
this._terminal.curAttr = DEFAULT_ATTR;
this._terminal.buffer.x = this._terminal.buffer.y = 0; // ?
this._terminal.charset = null;
this._terminal.glevel = 0; // ??
this._terminal.charsets = [null]; // ??
}
public softReset(params: number[]): void {
this._terminal.cursorHidden = false;
this._terminal.insertMode = false;
this._terminal.originMode = false;
this._terminal.wraparoundMode = true; // defaults: xterm - true, vt100 - false
this._terminal.applicationKeypad = false; // ?
this._terminal.viewport.syncScrollArea();
this._terminal.applicationCursor = false;
this._terminal.buffer.scrollTop = 0;
this._terminal.buffer.scrollBottom = this._terminal.rows - 1;
this._terminal.curAttr = this._terminal.defAttr;
this._terminal.buffer.x = this._terminal.buffer.y = 0; // ?
this._terminal.charset = null;
this._terminal.glevel = 0; // ??
this._terminal.charsets = [null]; // ??
}
/**
@@ -1788,26 +1448,24 @@ export class InputHandler extends Disposable implements IInputHandler {
* Ps = 5 -> blinking bar (xterm).
* Ps = 6 -> steady bar (xterm).
*/
public setCursorStyle(params?: number[], collect?: string): void {
if (collect === ' ') {
const param = params[0] < 1 ? 1 : params[0];
switch (param) {
case 1:
case 2:
this._terminal.setOption('cursorStyle', 'block');
break;
case 3:
case 4:
this._terminal.setOption('cursorStyle', 'underline');
break;
case 5:
case 6:
this._terminal.setOption('cursorStyle', 'bar');
break;
}
const isBlinking = param % 2 === 1;
this._terminal.setOption('cursorBlink', isBlinking);
public setCursorStyle(params?: number[]): void {
const param = params[0] < 1 ? 1 : params[0];
switch (param) {
case 1:
case 2:
this._terminal.setOption('cursorStyle', 'block');
break;
case 3:
case 4:
this._terminal.setOption('cursorStyle', 'underline');
break;
case 5:
case 6:
this._terminal.setOption('cursorStyle', 'bar');
break;
}
const isBlinking = param % 2 === 1;
this._terminal.setOption('cursorBlink', isBlinking);
}
/**
@@ -1816,8 +1474,8 @@ export class InputHandler extends Disposable implements IInputHandler {
* dow) (DECSTBM).
* CSI ? Pm r
*/
public setScrollRegion(params: number[], collect?: string): void {
if (collect) return;
public setScrollRegion(params: number[]): void {
if (this._terminal.prefix) return;
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;
@@ -1827,160 +1485,20 @@ export class InputHandler extends Disposable implements IInputHandler {
/**
* CSI s
* ESC 7
* Save cursor (ANSI.SYS).
*/
public saveCursor(params: number[]): void {
this._terminal.buffer.savedX = this._terminal.buffer.x;
this._terminal.buffer.savedY = this._terminal.buffer.y;
this._terminal.savedCurAttr = this._terminal.curAttr;
}
/**
* CSI u
* ESC 8
* Restore cursor (ANSI.SYS).
*/
public restoreCursor(params: number[]): void {
this._terminal.buffer.x = this._terminal.buffer.savedX || 0;
this._terminal.buffer.y = this._terminal.buffer.savedY || 0;
this._terminal.curAttr = this._terminal.savedCurAttr || DEFAULT_ATTR;
}
/**
* OSC 0; <data> ST (set icon name + window title)
* OSC 2; <data> ST (set window title)
* Proxy to set window title. Icon name is not supported.
*/
public setTitle(data: string): void {
this._terminal.handleTitle(data);
}
/**
* ESC E
* C1.NEL
* DEC mnemonic: NEL (https://vt100.net/docs/vt510-rm/NEL)
* Moves cursor to first position on next line.
*/
public nextLine(): void {
this._terminal.buffer.x = 0;
this.index();
}
/**
* ESC =
* DEC mnemonic: DECKPAM (https://vt100.net/docs/vt510-rm/DECKPAM.html)
* Enables the numeric keypad to send application sequences to the host.
*/
public keypadApplicationMode(): void {
this._terminal.log('Serial port requested application keypad.');
this._terminal.applicationKeypad = true;
if (this._terminal.viewport) {
this._terminal.viewport.syncScrollArea();
}
}
/**
* ESC >
* DEC mnemonic: DECKPNM (https://vt100.net/docs/vt510-rm/DECKPNM.html)
* Enables the keypad to send numeric characters to the host.
*/
public keypadNumericMode(): void {
this._terminal.log('Switching back to normal keypad.');
this._terminal.applicationKeypad = false;
if (this._terminal.viewport) {
this._terminal.viewport.syncScrollArea();
}
}
/**
* ESC % @
* ESC % G
* Select default character set. UTF-8 is not supported (string are unicode anyways)
* therefore ESC % G does the same.
*/
public selectDefaultCharset(): void {
this._terminal.setgLevel(0);
this._terminal.setgCharset(0, DEFAULT_CHARSET); // US (default)
}
/**
* ESC ( C
* Designate G0 Character Set, VT100, ISO 2022.
* ESC ) C
* Designate G1 Character Set (ISO 2022, VT100).
* ESC * C
* Designate G2 Character Set (ISO 2022, VT220).
* ESC + C
* Designate G3 Character Set (ISO 2022, VT220).
* ESC - C
* Designate G1 Character Set (VT300).
* ESC . C
* Designate G2 Character Set (VT300).
* ESC / C
* Designate G3 Character Set (VT300). C = A -> ISO Latin-1 Supplemental. - Supported?
*/
public selectCharset(collectAndFlag: string): void {
if (collectAndFlag.length !== 2) return this.selectDefaultCharset();
if (collectAndFlag[0] === '/') return; // TODO: Is this supported?
this._terminal.setgCharset(GLEVEL[collectAndFlag[0]], CHARSETS[collectAndFlag[1]] || DEFAULT_CHARSET);
}
/**
* ESC D
* C1.IND
* DEC mnemonic: IND (https://vt100.net/docs/vt510-rm/IND.html)
* Moves the cursor down one line in the same column.
*/
public index(): void {
this._terminal.index(); // TODO: save to move from terminal?
}
/**
* ESC H
* C1.HTS
* DEC mnemonic: HTS (https://vt100.net/docs/vt510-rm/HTS.html)
* Sets a horizontal tab stop at the column position indicated by
* the value of the active column when the terminal receives an HTS.
*/
public tabSet(): void {
this._terminal.tabSet(); // TODO: save to move from terminal?
}
/**
* ESC M
* C1.RI
* DEC mnemonic: HTS
* Moves the cursor up one line in the same column. If the cursor is at the top margin,
* the page scrolls down.
*/
public reverseIndex(): void {
this._terminal.reverseIndex(); // TODO: save to move from terminal?
}
/**
* ESC c
* DEC mnemonic: RIS (https://vt100.net/docs/vt510-rm/RIS.html)
* Reset to initial state.
*/
public reset(): void {
this._parser.reset();
this._terminal.reset(); // TODO: save to move from terminal?
}
/**
* ESC n
* ESC o
* ESC |
* ESC }
* ESC ~
* DEC mnemonic: LS (https://vt100.net/docs/vt510-rm/LS.html)
* When you use a locking shift, the character set remains in GL or GR until
* you use another locking shift. (partly supported)
*/
public setgLevel(level: number): void {
this._terminal.setgLevel(level); // TODO: save to move from terminal?
}
}
+6 -8
View File
@@ -4,16 +4,16 @@
*/
import { assert } from 'chai';
import { IMouseZoneManager, IMouseZone } from './ui/Types';
import { IMouseZoneManager, IMouseZone } from './input/Types';
import { ILinkMatcher, LineData, ITerminal } from './Types';
import { Linkifier } from './Linkifier';
import { MockBuffer, MockTerminal } from './utils/TestUtils.test';
import { CircularList } from './common/CircularList';
import { CircularList } from './utils/CircularList';
class TestLinkifier extends Linkifier {
constructor(terminal: ITerminal) {
super(terminal);
(<any>Linkifier).TIME_BEFORE_LINKIFY = 0;
constructor(_terminal: ITerminal) {
super(_terminal);
Linkifier.TIME_BEFORE_LINKIFY = 0;
}
public get linkMatchers(): ILinkMatcher[] { return this._linkMatchers; }
@@ -21,8 +21,6 @@ class TestLinkifier extends Linkifier {
}
class TestMouseZoneManager implements IMouseZoneManager {
dispose(): void {
}
public clears: number = 0;
public zones: IMouseZone[] = [];
add(zone: IMouseZone): void {
@@ -49,7 +47,7 @@ describe('Linkifier', () => {
});
function stringToRow(text: string): LineData {
const result: LineData = [];
let result: LineData = [];
for (let i = 0; i < text.length; i++) {
result.push([0, text.charAt(i), 1, text.charCodeAt(i)]);
}
+5 -5
View File
@@ -3,9 +3,9 @@
* @license MIT
*/
import { IMouseZoneManager } from './ui/Types';
import { IMouseZoneManager } from './input/Types';
import { ILinkHoverEvent, ILinkMatcher, LinkMatcherHandler, LinkHoverEventTypes, ILinkMatcherOptions, ILinkifier, ITerminal } from './Types';
import { MouseZone } from './ui/MouseZoneManager';
import { MouseZone } from './input/MouseZoneManager';
import { EventEmitter } from './EventEmitter';
/**
@@ -17,7 +17,7 @@ export class Linkifier extends EventEmitter implements ILinkifier {
* the costly operation of searching every row multiple times, potentially a
* huge amount of times.
*/
protected static readonly TIME_BEFORE_LINKIFY = 200;
protected static TIME_BEFORE_LINKIFY = 200;
protected _linkMatchers: ILinkMatcher[] = [];
@@ -199,11 +199,11 @@ export class Linkifier extends EventEmitter implements ILinkifier {
*/
private _doLinkifyRow(rowIndex: number, text: string, matcher: ILinkMatcher, offset: number = 0): void {
// Find the first match
const match = text.match(matcher.regex);
let match = text.match(matcher.regex);
if (!match || match.length === 0) {
return;
}
const uri = match[typeof matcher.matchIndex !== 'number' ? 0 : matcher.matchIndex];
let uri = match[typeof matcher.matchIndex !== 'number' ? 0 : matcher.matchIndex];
// Get index, match.index is for the outer match which includes negated chars
const index = text.indexOf(uri);
+673
View File
@@ -0,0 +1,673 @@
/**
* Copyright (c) 2014 The xterm.js authors. All rights reserved.
* Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
* @license MIT
*/
import { C0 } from './EscapeSequences';
import { IInputHandler, IInputHandlingTerminal } from './Types';
import { CHARSETS, DEFAULT_CHARSET } from './Charsets';
const normalStateHandler: {[key: string]: (parser: Parser, handler: IInputHandler) => void} = {};
normalStateHandler[C0.BEL] = (parser, handler) => handler.bell();
normalStateHandler[C0.LF] = (parser, handler) => handler.lineFeed();
normalStateHandler[C0.VT] = normalStateHandler[C0.LF];
normalStateHandler[C0.FF] = normalStateHandler[C0.LF];
normalStateHandler[C0.CR] = (parser, handler) => handler.carriageReturn();
normalStateHandler[C0.BS] = (parser, handler) => handler.backspace();
normalStateHandler[C0.HT] = (parser, handler) => handler.tab();
normalStateHandler[C0.SO] = (parser, handler) => handler.shiftOut();
normalStateHandler[C0.SI] = (parser, handler) => handler.shiftIn();
normalStateHandler[C0.ESC] = (parser, handler) => parser.setState(ParserState.ESCAPED);
// TODO: Remove terminal when parser owns params and currentParam
const escapedStateHandler: {[key: string]: (parser: Parser, terminal: any) => void} = {};
escapedStateHandler['['] = (parser, terminal) => {
// ESC [ Control Sequence Introducer (CSI is 0x9b)
terminal.params = [];
terminal.currentParam = 0;
parser.setState(ParserState.CSI_PARAM);
};
escapedStateHandler[']'] = (parser, terminal) => {
// ESC ] Operating System Command (OSC is 0x9d)
terminal.params = [];
terminal.currentParam = 0;
parser.setState(ParserState.OSC);
};
escapedStateHandler['P'] = (parser, terminal) => {
// ESC P Device Control String (DCS is 0x90)
terminal.params = [];
terminal.currentParam = 0;
parser.setState(ParserState.DCS);
};
escapedStateHandler['_'] = (parser, terminal) => {
// ESC _ Application Program Command ( APC is 0x9f).
parser.setState(ParserState.IGNORE);
};
escapedStateHandler['^'] = (parser, terminal) => {
// ESC ^ Privacy Message ( PM is 0x9e).
parser.setState(ParserState.IGNORE);
};
escapedStateHandler['c'] = (parser, terminal) => {
// ESC c Full Reset (RIS).
terminal.reset();
};
escapedStateHandler['E'] = (parser, terminal) => {
// ESC E Next Line ( NEL is 0x85).
terminal.buffer.x = 0;
terminal.index();
parser.setState(ParserState.NORMAL);
};
escapedStateHandler['D'] = (parser, terminal) => {
// ESC D Index ( IND is 0x84).
terminal.index();
parser.setState(ParserState.NORMAL);
};
escapedStateHandler['M'] = (parser, terminal) => {
// ESC M Reverse Index ( RI is 0x8d).
terminal.reverseIndex();
parser.setState(ParserState.NORMAL);
};
escapedStateHandler['%'] = (parser, terminal) => {
// ESC % Select default/utf-8 character set.
// @ = default, G = utf-8
terminal.setgLevel(0);
terminal.setgCharset(0, DEFAULT_CHARSET); // US (default)
parser.setState(ParserState.NORMAL);
parser.skipNextChar();
};
escapedStateHandler[C0.CAN] = (parser) => parser.setState(ParserState.NORMAL);
const csiParamStateHandler: {[key: string]: (parser: Parser) => void} = {};
csiParamStateHandler['?'] = (parser) => parser.setPrefix('?');
csiParamStateHandler['>'] = (parser) => parser.setPrefix('>');
csiParamStateHandler['!'] = (parser) => parser.setPrefix('!');
csiParamStateHandler['0'] = (parser) => parser.setParam(parser.getParam() * 10);
csiParamStateHandler['1'] = (parser) => parser.setParam(parser.getParam() * 10 + 1);
csiParamStateHandler['2'] = (parser) => parser.setParam(parser.getParam() * 10 + 2);
csiParamStateHandler['3'] = (parser) => parser.setParam(parser.getParam() * 10 + 3);
csiParamStateHandler['4'] = (parser) => parser.setParam(parser.getParam() * 10 + 4);
csiParamStateHandler['5'] = (parser) => parser.setParam(parser.getParam() * 10 + 5);
csiParamStateHandler['6'] = (parser) => parser.setParam(parser.getParam() * 10 + 6);
csiParamStateHandler['7'] = (parser) => parser.setParam(parser.getParam() * 10 + 7);
csiParamStateHandler['8'] = (parser) => parser.setParam(parser.getParam() * 10 + 8);
csiParamStateHandler['9'] = (parser) => parser.setParam(parser.getParam() * 10 + 9);
csiParamStateHandler['$'] = (parser) => parser.setPostfix('$');
csiParamStateHandler['"'] = (parser) => parser.setPostfix('"');
csiParamStateHandler[' '] = (parser) => parser.setPostfix(' ');
csiParamStateHandler['\''] = (parser) => parser.setPostfix('\'');
csiParamStateHandler[';'] = (parser) => parser.finalizeParam();
csiParamStateHandler[C0.CAN] = (parser) => parser.setState(ParserState.NORMAL);
const csiStateHandler: {[key: string]: (handler: IInputHandler, params: number[], prefix: string, postfix: string, parser: Parser) => void} = {};
csiStateHandler['@'] = (handler, params, prefix) => handler.insertChars(params);
csiStateHandler['A'] = (handler, params, prefix) => handler.cursorUp(params);
csiStateHandler['B'] = (handler, params, prefix) => handler.cursorDown(params);
csiStateHandler['C'] = (handler, params, prefix) => handler.cursorForward(params);
csiStateHandler['D'] = (handler, params, prefix) => handler.cursorBackward(params);
csiStateHandler['E'] = (handler, params, prefix) => handler.cursorNextLine(params);
csiStateHandler['F'] = (handler, params, prefix) => handler.cursorPrecedingLine(params);
csiStateHandler['G'] = (handler, params, prefix) => handler.cursorCharAbsolute(params);
csiStateHandler['H'] = (handler, params, prefix) => handler.cursorPosition(params);
csiStateHandler['I'] = (handler, params, prefix) => handler.cursorForwardTab(params);
csiStateHandler['J'] = (handler, params, prefix) => handler.eraseInDisplay(params);
csiStateHandler['K'] = (handler, params, prefix) => handler.eraseInLine(params);
csiStateHandler['L'] = (handler, params, prefix) => handler.insertLines(params);
csiStateHandler['M'] = (handler, params, prefix) => handler.deleteLines(params);
csiStateHandler['P'] = (handler, params, prefix) => handler.deleteChars(params);
csiStateHandler['S'] = (handler, params, prefix) => handler.scrollUp(params);
csiStateHandler['T'] = (handler, params, prefix) => {
if (params.length < 2 && !prefix) {
handler.scrollDown(params);
}
};
csiStateHandler['X'] = (handler, params, prefix) => handler.eraseChars(params);
csiStateHandler['Z'] = (handler, params, prefix) => handler.cursorBackwardTab(params);
csiStateHandler['`'] = (handler, params, prefix) => handler.charPosAbsolute(params);
csiStateHandler['a'] = (handler, params, prefix) => handler.HPositionRelative(params);
csiStateHandler['b'] = (handler, params, prefix) => handler.repeatPrecedingCharacter(params);
csiStateHandler['c'] = (handler, params, prefix) => handler.sendDeviceAttributes(params);
csiStateHandler['d'] = (handler, params, prefix) => handler.linePosAbsolute(params);
csiStateHandler['e'] = (handler, params, prefix) => handler.VPositionRelative(params);
csiStateHandler['f'] = (handler, params, prefix) => handler.HVPosition(params);
csiStateHandler['g'] = (handler, params, prefix) => handler.tabClear(params);
csiStateHandler['h'] = (handler, params, prefix) => handler.setMode(params);
csiStateHandler['l'] = (handler, params, prefix) => handler.resetMode(params);
csiStateHandler['m'] = (handler, params, prefix) => handler.charAttributes(params);
csiStateHandler['n'] = (handler, params, prefix) => handler.deviceStatus(params);
csiStateHandler['p'] = (handler, params, prefix) => {
switch (prefix) {
case '!': handler.softReset(params); break;
}
};
csiStateHandler['q'] = (handler, params, prefix, postfix) => {
if (postfix === ' ') {
handler.setCursorStyle(params);
}
};
csiStateHandler['r'] = (handler, params) => handler.setScrollRegion(params);
csiStateHandler['s'] = (handler, params) => handler.saveCursor(params);
csiStateHandler['u'] = (handler, params) => handler.restoreCursor(params);
csiStateHandler[C0.CAN] = (handler, params, prefix, postfix, parser) => parser.setState(ParserState.NORMAL);
export const enum ParserState {
NORMAL = 0,
ESCAPED = 1,
CSI_PARAM = 2,
CSI = 3,
OSC = 4,
CHARSET = 5,
DCS = 6,
IGNORE = 7
}
/**
* The terminal's parser, all input into the terminal goes through the parser
* which parses and defers the actual input handling the the IInputHandler
* specified in the constructor.
*/
export class Parser {
private _state: ParserState;
private _position: number;
// TODO: Remove terminal when handler can do everything
constructor(
private _inputHandler: IInputHandler,
private _terminal: any
) {
this._state = ParserState.NORMAL;
}
/**
* Parse and handle data.
*
* @param data The data to parse.
*/
public parse(data: string): ParserState {
const l = data.length;
let cs;
let ch;
let code;
let low;
const cursorStartX = this._terminal.buffer.x;
const cursorStartY = this._terminal.buffer.y;
if (this._terminal.debug) {
this._terminal.log('data: ' + data);
}
this._position = 0;
// apply leftover surrogate high from last write
if (this._terminal.surrogate_high) {
data = this._terminal.surrogate_high + data;
this._terminal.surrogate_high = '';
}
for (; this._position < l; this._position++) {
ch = data[this._position];
// FIXME: higher chars than 0xa0 are not allowed in escape sequences
// --> maybe move to default
code = data.charCodeAt(this._position);
if (0xD800 <= code && code <= 0xDBFF) {
// we got a surrogate high
// get surrogate low (next 2 bytes)
low = data.charCodeAt(this._position + 1);
if (isNaN(low)) {
// end of data stream, save surrogate high
this._terminal.surrogate_high = ch;
continue;
}
code = ((code - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000;
ch += data.charAt(this._position + 1);
}
// surrogate low - already handled above
if (0xDC00 <= code && code <= 0xDFFF) {
continue;
}
switch (this._state) {
case ParserState.NORMAL:
if (ch in normalStateHandler) {
normalStateHandler[ch](this, this._inputHandler);
} else {
this._inputHandler.addChar(ch, code);
}
break;
case ParserState.ESCAPED:
if (ch in escapedStateHandler) {
escapedStateHandler[ch](this, this._terminal);
// Skip switch as it was just handled
break;
}
switch (ch) {
// ESC (,),*,+,-,. Designate G0-G2 Character Set.
case '(': // <-- this seems to get all the attention
case ')':
case '*':
case '+':
case '-':
case '.':
switch (ch) {
case '(':
this._terminal.gcharset = 0;
break;
case ')':
this._terminal.gcharset = 1;
break;
case '*':
this._terminal.gcharset = 2;
break;
case '+':
this._terminal.gcharset = 3;
break;
case '-':
this._terminal.gcharset = 1;
break;
case '.':
this._terminal.gcharset = 2;
break;
}
this._state = ParserState.CHARSET;
break;
// Designate G3 Character Set (VT300).
// A = ISO Latin-1 Supplemental.
// Not implemented.
case '/':
this._terminal.gcharset = 3;
this._state = ParserState.CHARSET;
this._position--;
break;
// ESC N
// Single Shift Select of G2 Character Set
// ( SS2 is 0x8e). This affects next character only.
case 'N':
this._state = ParserState.NORMAL;
break;
// ESC O
// Single Shift Select of G3 Character Set
// ( SS3 is 0x8f). This affects next character only.
case 'O':
this._state = ParserState.NORMAL;
break;
// ESC n
// Invoke the G2 Character Set as GL (LS2).
case 'n':
this._terminal.setgLevel(2);
this._state = ParserState.NORMAL;
break;
// ESC o
// Invoke the G3 Character Set as GL (LS3).
case 'o':
this._terminal.setgLevel(3);
this._state = ParserState.NORMAL;
break;
// ESC |
// Invoke the G3 Character Set as GR (LS3R).
case '|':
this._terminal.setgLevel(3);
this._state = ParserState.NORMAL;
break;
// ESC }
// Invoke the G2 Character Set as GR (LS2R).
case '}':
this._terminal.setgLevel(2);
this._state = ParserState.NORMAL;
break;
// ESC ~
// Invoke the G1 Character Set as GR (LS1R).
case '~':
this._terminal.setgLevel(1);
this._state = ParserState.NORMAL;
break;
// ESC 7 Save Cursor (DECSC).
case '7':
this._inputHandler.saveCursor();
this._state = ParserState.NORMAL;
break;
// ESC 8 Restore Cursor (DECRC).
case '8':
this._inputHandler.restoreCursor();
this._state = ParserState.NORMAL;
break;
// ESC # 3 DEC line height/width
case '#':
this._state = ParserState.NORMAL;
this._position++;
break;
// ESC H Tab Set (HTS is 0x88).
case 'H':
(<IInputHandlingTerminal>this._terminal).tabSet();
this._state = ParserState.NORMAL;
break;
// ESC = Application Keypad (DECKPAM).
case '=':
this._terminal.log('Serial port requested application keypad.');
this._terminal.applicationKeypad = true;
if (this._terminal.viewport) {
this._terminal.viewport.syncScrollArea();
}
this._state = ParserState.NORMAL;
break;
// ESC > Normal Keypad (DECKPNM).
case '>':
this._terminal.log('Switching back to normal keypad.');
this._terminal.applicationKeypad = false;
if (this._terminal.viewport) {
this._terminal.viewport.syncScrollArea();
}
this._state = ParserState.NORMAL;
break;
default:
this._state = ParserState.NORMAL;
this._terminal.error('Unknown ESC control: %s.', ch);
break;
}
break;
case ParserState.CHARSET:
if (ch in CHARSETS) {
cs = CHARSETS[ch];
if (ch === '/') { // ISOLatin is actually /A
this.skipNextChar();
}
} else {
cs = DEFAULT_CHARSET;
}
this._terminal.setgCharset(this._terminal.gcharset, cs);
this._terminal.gcharset = null;
this._state = ParserState.NORMAL;
break;
case ParserState.OSC:
// OSC Ps ; Pt ST
// OSC Ps ; Pt BEL
// Set Text Parameters.
if (ch === C0.ESC || ch === C0.BEL) {
if (ch === C0.ESC) this._position++;
this._terminal.params.push(this._terminal.currentParam);
switch (this._terminal.params[0]) {
case 0:
case 1:
case 2:
if (this._terminal.params[1]) {
this._terminal.title = this._terminal.params[1];
this._terminal.handleTitle(this._terminal.title);
}
break;
case 3:
// set X property
break;
case 4:
case 5:
// change dynamic colors
break;
case 10:
case 11:
case 12:
case 13:
case 14:
case 15:
case 16:
case 17:
case 18:
case 19:
// change dynamic ui colors
break;
case 46:
// change log file
break;
case 50:
// dynamic font
break;
case 51:
// emacs shell
break;
case 52:
// manipulate selection data
break;
case 104:
case 105:
case 110:
case 111:
case 112:
case 113:
case 114:
case 115:
case 116:
case 117:
case 118:
// reset colors
break;
}
this._terminal.params = [];
this._terminal.currentParam = 0;
this._state = ParserState.NORMAL;
} else {
if (!this._terminal.params.length) {
if (ch >= '0' && ch <= '9') {
this._terminal.currentParam =
this._terminal.currentParam * 10 + ch.charCodeAt(0) - 48;
} else if (ch === ';') {
this._terminal.params.push(this._terminal.currentParam);
this._terminal.currentParam = '';
}
} else {
this._terminal.currentParam += ch;
}
}
break;
case ParserState.CSI_PARAM:
if (ch in csiParamStateHandler) {
csiParamStateHandler[ch](this);
break;
}
this.finalizeParam();
// Fall through the CSI as this character should be the CSI code.
this._state = ParserState.CSI;
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);
}
this._state = ParserState.NORMAL;
this._terminal.prefix = '';
this._terminal.postfix = '';
break;
case ParserState.DCS:
if (ch === C0.ESC || ch === C0.BEL) {
if (ch === C0.ESC) this._position++;
let pt;
let valid: boolean;
switch (this._terminal.prefix) {
// User-Defined Keys (DECUDK).
case '':
break;
// Request Status String (DECRQSS).
// test: echo -e '\eP$q"p\e\\'
case '$q':
pt = this._terminal.currentParam;
valid = false;
switch (pt) {
// DECSCA
case '"q':
pt = '0"q';
break;
// DECSCL
case '"p':
pt = '61"p';
break;
// DECSTBM
case 'r':
pt = ''
+ (this._terminal.buffer.scrollTop + 1)
+ ';'
+ (this._terminal.buffer.scrollBottom + 1)
+ 'r';
break;
// SGR
case 'm':
pt = '0m';
break;
default:
this._terminal.error('Unknown DCS Pt: %s.', pt);
pt = '';
break;
}
this._terminal.send(C0.ESC + 'P' + +valid + '$r' + pt + C0.ESC + '\\');
break;
// Set Termcap/Terminfo Data (xterm, experimental).
case '+p':
break;
// Request Termcap/Terminfo String (xterm, experimental)
// Regular xterm does not even respond to this sequence.
// This can cause a small glitch in vim.
// test: echo -ne '\eP+q6b64\e\\'
case '+q':
pt = this._terminal.currentParam;
valid = false;
this._terminal.send(C0.ESC + 'P' + +valid + '+r' + pt + C0.ESC + '\\');
break;
default:
this._terminal.error('Unknown DCS prefix: %s.', this._terminal.prefix);
break;
}
this._terminal.currentParam = 0;
this._terminal.prefix = '';
this._state = ParserState.NORMAL;
} else if (!this._terminal.currentParam) {
if (!this._terminal.prefix && ch !== '$' && ch !== '+') {
this._terminal.currentParam = ch;
} else if (this._terminal.prefix.length === 2) {
this._terminal.currentParam = ch;
} else {
this._terminal.prefix += ch;
}
} else {
this._terminal.currentParam += ch;
}
break;
case ParserState.IGNORE:
// For PM and APC.
if (ch === C0.ESC || ch === C0.BEL) {
if (ch === C0.ESC) this._position++;
this._state = ParserState.NORMAL;
}
break;
}
}
// Fire the cursormove event if it's moved. This is done inside the parser
// as a render cannot happen in the middle of a parsing round.
if (this._terminal.buffer.x !== cursorStartX || this._terminal.buffer.y !== cursorStartY) {
this._terminal.emit('cursormove');
}
return this._state;
}
/**
* Set the parser's current parsing state.
*
* @param state The new state.
*/
public setState(state: ParserState): void {
this._state = state;
}
/**
* Sets the parsier's current prefix. CSI codes can have prefixes of '?', '>'
* or '!'.
*
* @param prefix The prefix.
*/
public setPrefix(prefix: string): void {
this._terminal.prefix = prefix;
}
/**
* Sets the parsier's current prefix. CSI codes can have postfixes of '$',
* '"', ' ', '\''.
*
* @param postfix The postfix.
*/
public setPostfix(postfix: string): void {
this._terminal.postfix = postfix;
}
/**
* Sets the parser's current parameter.
*
* @param param the parameter.
*/
public setParam(param: number): void {
this._terminal.currentParam = param;
}
/**
* Gets the parser's current parameter.
*/
public getParam(): number {
return this._terminal.currentParam;
}
/**
* Finalizes the parser's current parameter, adding it to the list of
* parameters and setting the new current parameter to 0.
*/
public finalizeParam(): void {
this._terminal.params.push(this._terminal.currentParam);
this._terminal.currentParam = 0;
}
/**
* Tell the parser to skip the next character.
*/
public skipNextChar(): void {
this._position++;
}
/**
* Tell the parser to repeat parsing the current character (for example if it
* needs parsing using a different state.
*/
// public repeatChar(): void {
// this._position--;
// }
}
+3 -72
View File
@@ -4,8 +4,8 @@
*/
import { assert } from 'chai';
import { CharMeasure } from './ui/CharMeasure';
import { SelectionManager, SelectionMode } from './SelectionManager';
import { CharMeasure } from './utils/CharMeasure';
import { SelectionManager } from './SelectionManager';
import { SelectionModel } from './SelectionModel';
import { BufferSet } from './BufferSet';
import { LineData, CharData, ITerminal, IBuffer } from './Types';
@@ -25,8 +25,6 @@ class TestSelectionManager extends SelectionManager {
public get model(): SelectionModel { return this._model; }
public set selectionMode(mode: SelectionMode) { this._activeSelectionMode = mode; }
public selectLineAt(line: number): void { this._selectLineAt(line); }
public selectWordAt(coords: [number, number]): void { this._selectWordAt(coords, true); }
@@ -53,7 +51,7 @@ describe('SelectionManager', () => {
});
function stringToRow(text: string): LineData {
const result: LineData = [];
let result: LineData = [];
for (let i = 0; i < text.length; i++) {
result.push([0, text.charAt(i), 1, text.charCodeAt(i)]);
}
@@ -300,16 +298,6 @@ describe('SelectionManager', () => {
assert.deepEqual(selectionManager.model.finalSelectionStart, [0, 0]);
assert.deepEqual(selectionManager.model.finalSelectionEnd, [terminal.cols, 0], 'The actual selection spans the entire column');
});
it('should select the entire wrapped line', () => {
buffer.lines.set(0, stringToRow('foo'));
const line2 = stringToRow('bar');
(<any>line2).isWrapped = true;
buffer.lines.set(1, line2);
selectionManager.selectLineAt(0);
assert.equal(selectionManager.selectionText, 'foobar', 'The selected text is correct');
assert.deepEqual(selectionManager.model.finalSelectionStart, [0, 0]);
assert.deepEqual(selectionManager.model.finalSelectionEnd, [terminal.cols, 1], 'The actual selection spans the entire column');
});
});
describe('selectAll', () => {
@@ -380,61 +368,4 @@ describe('SelectionManager', () => {
assert.equal(selectionManager.hasSelection, true);
});
});
describe('column selection', () => {
it('should select a column of text', () => {
buffer.lines.length = 3;
buffer.lines.set(0, stringToRow('abcdefghij'));
buffer.lines.set(1, stringToRow('klmnopqrst'));
buffer.lines.set(2, stringToRow('uvwxyz'));
selectionManager.selectionMode = SelectionMode.COLUMN;
selectionManager.model.selectionStart = [2, 0];
selectionManager.model.selectionEnd = [4, 2];
assert.equal(selectionManager.selectionText, 'cd\nmn\nwx');
});
it('should select a column of text without chopping up double width characters', () => {
buffer.lines.length = 3;
buffer.lines.set(0, stringToRow('a'));
buffer.lines.set(1, stringToRow('語'));
buffer.lines.set(2, stringToRow('b'));
selectionManager.selectionMode = SelectionMode.COLUMN;
selectionManager.model.selectionStart = [0, 0];
selectionManager.model.selectionEnd = [1, 2];
assert.equal(selectionManager.selectionText, 'a\n語\nb');
});
it('should select a column of text with single character emojis', () => {
buffer.lines.length = 3;
buffer.lines.set(0, stringToRow('a'));
buffer.lines.set(1, stringToRow('☃'));
buffer.lines.set(2, stringToRow('c'));
selectionManager.selectionMode = SelectionMode.COLUMN;
selectionManager.model.selectionStart = [0, 0];
selectionManager.model.selectionEnd = [1, 2];
assert.equal(selectionManager.selectionText, 'a\n☃\nc');
});
it('should select a column of text with double character emojis', () => {
// TODO the case this is testing works for me in the demo webapp,
// but doing it programmatically fails.
buffer.lines.length = 3;
buffer.lines.set(0, stringToRow('a '));
buffer.lines.set(1, stringArrayToRow(['😁', ' ']));
buffer.lines.set(2, stringToRow('c '));
selectionManager.selectionMode = SelectionMode.COLUMN;
selectionManager.model.selectionStart = [0, 0];
selectionManager.model.selectionEnd = [1, 2];
assert.equal(selectionManager.selectionText, 'a\n😁\nc');
});
});
});
+30 -67
View File
@@ -6,7 +6,7 @@
import { ITerminal, ISelectionManager, IBuffer, CharData, XtermListener } from './Types';
import { MouseHelper } from './utils/MouseHelper';
import * as Browser from './shared/utils/Browser';
import { CharMeasure } from './ui/CharMeasure';
import { CharMeasure } from './utils/CharMeasure';
import { EventEmitter } from './EventEmitter';
import { SelectionModel } from './SelectionModel';
import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from './Buffer';
@@ -54,11 +54,10 @@ interface IWordPosition {
/**
* A selection mode, this drives how the selection behaves on mouse move.
*/
export const enum SelectionMode {
const enum SelectionMode {
NORMAL,
WORD,
LINE,
COLUMN
LINE
}
/**
@@ -81,7 +80,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
/**
* The current selection mode.
*/
protected _activeSelectionMode: SelectionMode;
private _activeSelectionMode: SelectionMode;
/**
* A setInterval timer that is active while the mouse is down whose callback
@@ -117,11 +116,6 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
this._activeSelectionMode = SelectionMode.NORMAL;
}
public dispose(): void {
super.dispose();
this._removeMouseDownListeners();
}
private get _buffer(): IBuffer {
return this._terminal.buffers.active;
}
@@ -183,43 +177,30 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
return '';
}
const result: string[] = [];
// Get first row
const startRowEndCol = start[1] === end[1] ? end[0] : null;
let result: string[] = [];
result.push(this._buffer.translateBufferLineToString(start[1], true, start[0], startRowEndCol));
if (this._activeSelectionMode === SelectionMode.COLUMN) {
// Ignore zero width selections
if (start[0] === end[0]) {
return '';
}
for (let i = start[1]; i <= end[1]; i++) {
const lineText = this._buffer.translateBufferLineToString(i, true, start[0], end[0]);
// Get middle rows
for (let i = start[1] + 1; i <= end[1] - 1; i++) {
const bufferLine = this._buffer.lines.get(i);
const lineText = this._buffer.translateBufferLineToString(i, true);
if ((<any>bufferLine).isWrapped) {
result[result.length - 1] += lineText;
} else {
result.push(lineText);
}
} else {
// Get first row
const startRowEndCol = start[1] === end[1] ? end[0] : null;
result.push(this._buffer.translateBufferLineToString(start[1], true, start[0], startRowEndCol));
}
// Get middle rows
for (let i = start[1] + 1; i <= end[1] - 1; i++) {
const bufferLine = this._buffer.lines.get(i);
const lineText = this._buffer.translateBufferLineToString(i, true);
if ((<any>bufferLine).isWrapped) {
result[result.length - 1] += lineText;
} else {
result.push(lineText);
}
}
// Get final row
if (start[1] !== end[1]) {
const bufferLine = this._buffer.lines.get(end[1]);
const lineText = this._buffer.translateBufferLineToString(end[1], true, 0, end[0]);
if ((<any>bufferLine).isWrapped) {
result[result.length - 1] += lineText;
} else {
result.push(lineText);
}
// Get final row
if (start[1] !== end[1]) {
const bufferLine = this._buffer.lines.get(end[1]);
const lineText = this._buffer.translateBufferLineToString(end[1], true, 0, end[0]);
if ((<any>bufferLine).isWrapped) {
result[result.length - 1] += lineText;
} else {
result.push(lineText);
}
}
@@ -268,11 +249,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
*/
private _refresh(): void {
this._refreshAnimationFrame = null;
this.emit('refresh', {
start: this._model.finalSelectionStart,
end: this._model.finalSelectionEnd,
columnSelectMode: this._activeSelectionMode === SelectionMode.COLUMN
});
this.emit('refresh', { start: this._model.finalSelectionStart, end: this._model.finalSelectionEnd });
}
/**
@@ -381,11 +358,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
* @param event The mouse event.
*/
public shouldForceSelection(event: MouseEvent): boolean {
if (Browser.isMac) {
return event.altKey && this._terminal.options.macOptionClickForcesSelection;
}
return event.shiftKey;
return Browser.isMac ? event.altKey : event.shiftKey;
}
/**
@@ -476,7 +449,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
private _onSingleClick(event: MouseEvent): void {
this._model.selectionStartLength = 0;
this._model.isSelectAllActive = false;
this._activeSelectionMode = this.shouldColumnSelect(event) ? SelectionMode.COLUMN : SelectionMode.NORMAL;
this._activeSelectionMode = SelectionMode.NORMAL;
// Initialize the new selection
this._model.selectionStart = this._getMouseBufferCoords(event);
@@ -529,14 +502,6 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
}
}
/**
* Returns whether the selection manager should operate in column select mode
* @param event the mouse or keyboard event
*/
public shouldColumnSelect(event: KeyboardEvent | MouseEvent): boolean {
return event.altKey && !(Browser.isMac && this._terminal.options.macOptionClickForcesSelection);
}
/**
* Handles the mousemove event when the mouse button is down, recording the
* end of the selection and refreshing the selection.
@@ -621,7 +586,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
* @param event The mouseup event.
*/
private _onMouseUp(event: MouseEvent): void {
const timeElapsed = event.timeStamp - this._mouseDownTimeStamp;
let timeElapsed = event.timeStamp - this._mouseDownTimeStamp;
this._removeMouseDownListeners();
@@ -838,9 +803,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
* @param line The line index.
*/
protected _selectLineAt(line: number): void {
const wrappedRange = this._buffer.getWrappedRangeForLine(line);
this._model.selectionStart = [0, wrappedRange.first];
this._model.selectionEnd = [this._terminal.cols, wrappedRange.last];
this._model.selectionStartLength = 0;
this._model.selectionStart = [0, line];
this._model.selectionStartLength = this._terminal.cols;
}
}
+19 -25
View File
@@ -14,11 +14,6 @@ import * as pty from 'node-pty';
import { assert } from 'chai';
import { Terminal } from './Terminal';
import { CHAR_DATA_CHAR_INDEX } from './Buffer';
import { IViewport } from './Types';
class TestTerminal extends Terminal {
innerWrite(): void { this._innerWrite(); }
}
let primitivePty: any;
@@ -29,8 +24,8 @@ let primitivePty: any;
function ptyWriteRead(data: string, cb: (result: string) => void): void {
fs.writeSync(primitivePty.slave, data);
setTimeout(() => {
const b = new Buffer(64000);
const bytes = fs.readSync(primitivePty.master, b, 0, 64000, null);
let b = new Buffer(64000);
let bytes = fs.readSync(primitivePty.master, b, 0, 64000, null);
cb(b.toString('utf8', 0, bytes));
});
}
@@ -50,7 +45,7 @@ function formatError(input: string, output: string, expected: string): string {
return '\x1b[33m' + (' ' + counter).slice(-2) + color + s;
};
}
const line80 = '12345678901234567890123456789012345678901234567890123456789012345678901234567890';
let line80 = '12345678901234567890123456789012345678901234567890123456789012345678901234567890';
let s = '';
s += '\n\x1b[34m' + JSON.stringify(input);
s += '\n\x1b[33m ' + line80 + '\n';
@@ -79,34 +74,34 @@ function terminalToString(term: Terminal): string {
// Skip tests on Windows since pty.open isn't supported
if (os.platform() !== 'win32') {
const consoleLog = console.log;
let CONSOLE_LOG = console.log;
// expect files need terminal at 80x25!
const cols = 80;
const rows = 25;
let COLS = 80;
let ROWS = 25;
/** some helpers for pty interaction */
// we need a pty in between to get the termios decorations
// for the basic test cases a raw pty device is enough
primitivePty = (<any>pty).native.open(cols, rows);
primitivePty = pty.native.open(COLS, ROWS);
/** tests */
describe('xterm output comparison', () => {
let xterm: TestTerminal;
let xterm;
beforeEach(() => {
xterm = new TestTerminal({ cols: cols, rows: rows });
xterm = new Terminal({ cols: COLS, rows: ROWS });
xterm.refresh = () => {};
xterm.viewport = <IViewport>{
xterm.viewport = {
syncScrollArea: () => {}
};
});
// omit stack trace for escape sequence files
Error.stackTraceLimit = 0;
const files = glob.sync('**/escape_sequence_files/*.in', { cwd: path.join(__dirname, '..')});
let files = glob.sync('**/escape_sequence_files/*.in', { cwd: path.join(__dirname, '..')});
// only successful tests for now
const skip = [
let skip = [
10, 16, 17, 19, 32, 33, 34, 35, 36, 39,
40, 42, 43, 44, 45, 46, 47, 48, 49, 50,
51, 52, 54, 55, 56, 57, 58, 59, 60, 61,
@@ -123,22 +118,21 @@ if (os.platform() !== 'win32') {
((filename: string) => {
it(filename.split('/').slice(-1)[0], done => {
ptyReset(() => {
const inFile = fs.readFileSync(filename, 'utf8');
let inFile = fs.readFileSync(filename, 'utf8');
ptyWriteRead(inFile, fromPty => {
// uncomment this to get log from terminal
// console.log = function(){};
// Perform a synchronous .write(data)
xterm.writeBuffer.push(fromPty);
xterm.innerWrite();
const fromEmulator = terminalToString(xterm);
console.log = consoleLog;
const expected = fs.readFileSync(filename.split('.')[0] + '.text', 'utf8');
xterm._innerWrite();
let fromEmulator = terminalToString(xterm);
console.log = CONSOLE_LOG;
let 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.
const expectedRightTrimmed = expected.split('\n').map(l => l.replace(/\s+$/, '')).join('\n');
let expectedRightTrimmed = expected.split('\n').map(l => l.replace(/\s+$/, '')).join('\n');
if (fromEmulator !== expectedRightTrimmed) {
// uncomment to get noisy output
throw new Error(formatError(inFile, fromEmulator, expected));
@@ -161,7 +155,7 @@ describe('typings', () => {
tsc += '.cmd';
}
const fixtureDir = path.join(__dirname, '..', 'fixtures', 'typings-test');
const result = cp.spawnSync(tsc, { cwd: fixtureDir });
let result = cp.spawnSync(tsc, { cwd: fixtureDir });
assert.equal(result.status, 0, `build did not succeed:\nstdout: ${result.stdout.toString()}\nstderr: ${result.stderr.toString()}\n`);
// Clean up
fs.unlinkSync(path.join(fixtureDir, 'typings-test.js'));
+297 -53
View File
@@ -5,6 +5,7 @@
import { assert, expect } from 'chai';
import { Terminal } from './Terminal';
import * as attach from './addons/attach/attach';
import { MockViewport, MockCompositionHelper, MockRenderer } from './utils/TestUtils.test';
import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX } from './Buffer';
@@ -12,6 +13,7 @@ const INIT_COLS = 80;
const INIT_ROWS = 24;
class TestTerminal extends Terminal {
public evaluateKeyEscapeSequence(ev: any): {cancel: boolean, key: string, scrollLines: number} { return this._evaluateKeyEscapeSequence(<KeyboardEvent>ev); }
public keyDown(ev: any): boolean { return this._keyDown(ev); }
public keyPress(ev: any): boolean { return this._keyPress(ev); }
}
@@ -51,6 +53,13 @@ describe('term.js addons', () => {
});
});
it('should apply addons with Terminal.applyAddon', () => {
Terminal.applyAddon(attach);
// Test that addon was applied successfully, adding attach to Terminal's
// prototype.
assert.equal(typeof (<any>Terminal).prototype.attach, 'function');
});
describe('getOption', () => {
it('should retrieve the option correctly', () => {
// In the `options` namespace.
@@ -68,13 +77,13 @@ describe('term.js addons', () => {
});
describe('attachCustomKeyEventHandler', () => {
const evKeyDown = <KeyboardEvent>{
let evKeyDown = <KeyboardEvent>{
preventDefault: () => {},
stopPropagation: () => {},
type: 'keydown',
keyCode: 77
};
const evKeyPress = <KeyboardEvent>{
let evKeyPress = <KeyboardEvent>{
preventDefault: () => {},
stopPropagation: () => {},
type: 'keypress',
@@ -120,20 +129,9 @@ describe('term.js addons', () => {
});
});
describe('reset', () => {
it('should not affect cursorState', () => {
term.cursorState = 1;
term.reset();
assert.equal(term.cursorState, 1);
term.cursorState = 0;
term.reset();
assert.equal(term.cursorState, 0);
});
});
describe('clear', () => {
it('should clear a buffer equal to rows', () => {
const promptLine = term.buffer.lines.get(term.buffer.ybase + term.buffer.y);
let promptLine = term.buffer.lines.get(term.buffer.ybase + term.buffer.y);
term.clear();
assert.equal(term.buffer.y, 0);
assert.equal(term.buffer.ybase, 0);
@@ -150,7 +148,7 @@ describe('term.js addons', () => {
term.write('test\n');
}
const promptLine = term.buffer.lines.get(term.buffer.ybase + term.buffer.y);
let promptLine = term.buffer.lines.get(term.buffer.ybase + term.buffer.y);
term.clear();
assert.equal(term.buffer.y, 0);
assert.equal(term.buffer.ybase, 0);
@@ -162,7 +160,7 @@ describe('term.js addons', () => {
}
});
it('should not break the prompt when cleared twice', () => {
const promptLine = term.buffer.lines.get(term.buffer.ybase + term.buffer.y);
let promptLine = term.buffer.lines.get(term.buffer.ybase + term.buffer.y);
term.clear();
term.clear();
assert.equal(term.buffer.y, 0);
@@ -178,7 +176,7 @@ describe('term.js addons', () => {
describe('scroll', () => {
describe('scrollLines', () => {
let startYDisp: number;
let startYDisp;
beforeEach(() => {
for (let i = 0; i < term.rows * 2; i++) {
term.writeln('test');
@@ -213,7 +211,7 @@ describe('term.js addons', () => {
});
describe('scrollPages', () => {
let startYDisp: number;
let startYDisp;
beforeEach(() => {
for (let i = 0; i < term.rows * 3; i++) {
term.writeln('test');
@@ -250,7 +248,7 @@ describe('term.js addons', () => {
});
describe('scrollToBottom', () => {
let startYDisp: number;
let startYDisp;
beforeEach(() => {
for (let i = 0; i < term.rows * 3; i++) {
term.writeln('test');
@@ -271,7 +269,7 @@ describe('term.js addons', () => {
});
describe('scrollToLine', () => {
let startYDisp: number;
let startYDisp;
beforeEach(() => {
for (let i = 0; i < term.rows * 3; i++) {
term.writeln('test');
@@ -298,19 +296,22 @@ describe('term.js addons', () => {
});
});
describe('keyPress', () => {
describe('keyDown', () => {
it('should scroll down, when a key is pressed and terminal is scrolled up', () => {
const event = <KeyboardEvent>{
// Override _evaluateKeyEscapeSequence to return cancel code
(<any>term)._evaluateKeyEscapeSequence = () => {
return { key: 'a' };
};
let event = <KeyboardEvent>{
type: 'keydown',
key: 'a',
keyCode: 65,
keyCode: 0,
preventDefault: () => {},
stopPropagation: () => {}
};
term.buffer.ydisp = 0;
term.buffer.ybase = 40;
term.keyPress(event);
term.keyDown(event);
// Ensure that now the terminal is scrolled to bottom
assert.equal(term.buffer.ydisp, term.buffer.ybase);
@@ -321,7 +322,7 @@ describe('term.js addons', () => {
for (let i = 0; i < term.rows * 3; i++) {
term.writeln('test');
}
const startYDisp = (term.rows * 2) + 1;
let startYDisp = (term.rows * 2) + 1;
term.attachCustomKeyEventHandler(() => {
return false;
});
@@ -329,7 +330,7 @@ describe('term.js addons', () => {
assert.equal(term.buffer.ydisp, startYDisp);
term.scrollLines(-1);
assert.equal(term.buffer.ydisp, startYDisp - 1);
term.keyPress(<KeyboardEvent>{ keyCode: 0 });
term.keyDown(<KeyboardEvent>{ keyCode: 0 });
assert.equal(term.buffer.ydisp, startYDisp - 1);
});
});
@@ -467,9 +468,252 @@ describe('term.js addons', () => {
});
});
describe('evaluateKeyEscapeSequence', () => {
it('should return the correct escape sequence for unmodified keys', () => {
// Backspace
assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 8 }).key, '\x7f'); // ^?
// Tab
assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 9 }).key, '\t');
// Return/enter
assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 13 }).key, '\r'); // CR
// Escape
assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 27 }).key, '\x1b');
// Page up, page down
assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 33 }).key, '\x1b[5~'); // CSI 5 ~
assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 34 }).key, '\x1b[6~'); // CSI 6 ~
// End, Home
assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 35 }).key, '\x1b[F'); // SS3 F
assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 36 }).key, '\x1b[H'); // SS3 H
// Left, up, right, down arrows
assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 37 }).key, '\x1b[D'); // CSI D
assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 38 }).key, '\x1b[A'); // CSI A
assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 39 }).key, '\x1b[C'); // CSI C
assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 40 }).key, '\x1b[B'); // CSI B
// Insert
assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 45 }).key, '\x1b[2~'); // CSI 2 ~
// Delete
assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 46 }).key, '\x1b[3~'); // CSI 3 ~
// F1-F12
assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 112 }).key, '\x1bOP'); // SS3 P
assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 113 }).key, '\x1bOQ'); // SS3 Q
assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 114 }).key, '\x1bOR'); // SS3 R
assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 115 }).key, '\x1bOS'); // SS3 S
assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 116 }).key, '\x1b[15~'); // CSI 1 5 ~
assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 117 }).key, '\x1b[17~'); // CSI 1 7 ~
assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 118 }).key, '\x1b[18~'); // CSI 1 8 ~
assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 119 }).key, '\x1b[19~'); // CSI 1 9 ~
assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 120 }).key, '\x1b[20~'); // CSI 2 0 ~
assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 121 }).key, '\x1b[21~'); // CSI 2 1 ~
assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 122 }).key, '\x1b[23~'); // CSI 2 3 ~
assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 123 }).key, '\x1b[24~'); // CSI 2 4 ~
});
it('should return \\x1b[3;5~ for ctrl+delete', () => {
assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 46 }).key, '\x1b[3;5~');
});
it('should return \\x1b[3;2~ for shift+delete', () => {
assert.equal(term.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 46 }).key, '\x1b[3;2~');
});
it('should return \\x1b[3;3~ for alt+delete', () => {
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 46 }).key, '\x1b[3;3~');
});
it('should return \\x1b[5D for ctrl+left', () => {
assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 37 }).key, '\x1b[1;5D'); // CSI 5 D
});
it('should return \\x1b[5C for ctrl+right', () => {
assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 39 }).key, '\x1b[1;5C'); // CSI 5 C
});
it('should return \\x1b[5A for ctrl+up', () => {
assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 38 }).key, '\x1b[1;5A'); // CSI 5 A
});
it('should return \\x1b[5B for ctrl+down', () => {
assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 40 }).key, '\x1b[1;5B'); // CSI 5 B
});
describe('On non-macOS platforms', () => {
beforeEach(() => {
term.browser.isMac = false;
});
// Evalueate alt + arrow key movement, which is a feature of terminal emulators but not VT100
// http://unix.stackexchange.com/a/108106
it('should return \\x1b[5D for alt+left', () => {
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 37 }).key, '\x1b[1;5D'); // CSI 5 D
});
it('should return \\x1b[5C for alt+right', () => {
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 39 }).key, '\x1b[1;5C'); // CSI 5 C
});
it('should return \\x1ba for alt+a', () => {
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 65 }).key, '\x1ba');
});
});
describe('On macOS platforms', () => {
beforeEach(() => {
term.browser.isMac = true;
});
it('should return \\x1bb for alt+left', () => {
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 37 }).key, '\x1bb'); // CSI 5 D
});
it('should return \\x1bf for alt+right', () => {
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 39 }).key, '\x1bf'); // CSI 5 C
});
it('should return undefined for alt+a', () => {
assert.strictEqual(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 65 }).key, undefined);
});
});
describe('with macOptionIsMeta', () => {
beforeEach(() => {
term.browser.isMac = true;
term.setOption('macOptionIsMeta', true);
});
it('should return \\x1ba for alt+a', () => {
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 65 }).key, '\x1ba');
});
});
it('should return \\x1b[5A for alt+up', () => {
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 38 }).key, '\x1b[1;5A'); // CSI 5 A
});
it('should return \\x1b[5B for alt+down', () => {
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 40 }).key, '\x1b[1;5B'); // CSI 5 B
});
it('should return the correct escape sequence for modified F1-F12 keys', () => {
assert.equal(term.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 112 }).key, '\x1b[1;2P');
assert.equal(term.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 113 }).key, '\x1b[1;2Q');
assert.equal(term.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 114 }).key, '\x1b[1;2R');
assert.equal(term.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 115 }).key, '\x1b[1;2S');
assert.equal(term.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 116 }).key, '\x1b[15;2~');
assert.equal(term.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 117 }).key, '\x1b[17;2~');
assert.equal(term.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 118 }).key, '\x1b[18;2~');
assert.equal(term.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 119 }).key, '\x1b[19;2~');
assert.equal(term.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 120 }).key, '\x1b[20;2~');
assert.equal(term.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 121 }).key, '\x1b[21;2~');
assert.equal(term.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 122 }).key, '\x1b[23;2~');
assert.equal(term.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 123 }).key, '\x1b[24;2~');
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 112 }).key, '\x1b[1;3P');
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 113 }).key, '\x1b[1;3Q');
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 114 }).key, '\x1b[1;3R');
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 115 }).key, '\x1b[1;3S');
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 116 }).key, '\x1b[15;3~');
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 117 }).key, '\x1b[17;3~');
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 118 }).key, '\x1b[18;3~');
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 119 }).key, '\x1b[19;3~');
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 120 }).key, '\x1b[20;3~');
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 121 }).key, '\x1b[21;3~');
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 122 }).key, '\x1b[23;3~');
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 123 }).key, '\x1b[24;3~');
assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 112 }).key, '\x1b[1;5P');
assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 113 }).key, '\x1b[1;5Q');
assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 114 }).key, '\x1b[1;5R');
assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 115 }).key, '\x1b[1;5S');
assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 116 }).key, '\x1b[15;5~');
assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 117 }).key, '\x1b[17;5~');
assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 118 }).key, '\x1b[18;5~');
assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 119 }).key, '\x1b[19;5~');
assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 120 }).key, '\x1b[20;5~');
assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 121 }).key, '\x1b[21;5~');
assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 122 }).key, '\x1b[23;5~');
assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 123 }).key, '\x1b[24;5~');
});
// Characters using ctrl+alt sequences
it('should return proper sequence for ctrl+alt+a', () => {
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, ctrlKey: true, keyCode: 65 }).key, '\x1b\x01');
});
// Characters using alt sequences (numbers)
it('should return proper sequences for alt+0', () => {
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 48 }).key, '\x1b0');
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 48 }).key, '\x1b)');
});
it('should return proper sequences for alt+1', () => {
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 49 }).key, '\x1b1');
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 49 }).key, '\x1b!');
});
it('should return proper sequences for alt+2', () => {
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 50 }).key, '\x1b2');
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 50 }).key, '\x1b@');
});
it('should return proper sequences for alt+3', () => {
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 51 }).key, '\x1b3');
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 51 }).key, '\x1b#');
});
it('should return proper sequences for alt+4', () => {
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 52 }).key, '\x1b4');
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 52 }).key, '\x1b$');
});
it('should return proper sequences for alt+5', () => {
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 53 }).key, '\x1b5');
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 53 }).key, '\x1b%');
});
it('should return proper sequences for alt+6', () => {
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 54 }).key, '\x1b6');
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 54 }).key, '\x1b^');
});
it('should return proper sequences for alt+7', () => {
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 55 }).key, '\x1b7');
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 55 }).key, '\x1b&');
});
it('should return proper sequences for alt+8', () => {
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 56 }).key, '\x1b8');
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 56 }).key, '\x1b*');
});
it('should return proper sequences for alt+9', () => {
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 57 }).key, '\x1b9');
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 57 }).key, '\x1b(');
});
// Characters using alt sequences (special chars)
it('should return proper sequences for alt+;', () => {
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 186 }).key, '\x1b;');
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 186 }).key, '\x1b:');
});
it('should return proper sequences for alt+=', () => {
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 187 }).key, '\x1b=');
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 187 }).key, '\x1b+');
});
it('should return proper sequences for alt+,', () => {
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 188 }).key, '\x1b,');
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 188 }).key, '\x1b<');
});
it('should return proper sequences for alt+-', () => {
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 189 }).key, '\x1b-');
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 189 }).key, '\x1b_');
});
it('should return proper sequences for alt+.', () => {
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 190 }).key, '\x1b.');
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 190 }).key, '\x1b>');
});
it('should return proper sequences for alt+/', () => {
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 191 }).key, '\x1b/');
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 191 }).key, '\x1b?');
});
it('should return proper sequences for alt+~', () => {
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 192 }).key, '\x1b`');
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 192 }).key, '\x1b~');
});
it('should return proper sequences for alt+[', () => {
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 219 }).key, '\x1b[');
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 219 }).key, '\x1b{');
});
it('should return proper sequences for alt+\\', () => {
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 220 }).key, '\x1b\\');
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 220 }).key, '\x1b|');
});
it('should return proper sequences for alt+]', () => {
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 221 }).key, '\x1b]');
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 221 }).key, '\x1b}');
});
it('should return proper sequences for alt+\'', () => {
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 222 }).key, '\x1b\'');
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 222 }).key, '\x1b"');
});
});
describe('Third level shift', () => {
let evKeyDown: any;
let evKeyPress: any;
let evKeyDown;
let evKeyPress;
beforeEach(() => {
term.handler = () => {};
@@ -540,11 +784,11 @@ describe('term.js addons', () => {
});
it('should emit key with alt + key on keyPress', (done) => {
const keys = ['@', '@', '\\', '\\', '|', '|'];
let keys = ['@', '@', '\\', '\\', '|', '|'];
term.on('keypress', (key) => {
if (key) {
const index = keys.indexOf(key);
let index = keys.indexOf(key);
assert(index !== -1, 'Emitted wrong key: ' + key);
keys.splice(index, 1);
}
@@ -606,11 +850,11 @@ describe('term.js addons', () => {
});
it('should emit key with alt + ctrl + key on keyPress', (done) => {
const keys = ['@', '@', '\\', '\\', '|', '|'];
let keys = ['@', '@', '\\', '\\', '|', '|'];
term.on('keypress', (key) => {
if (key) {
const index = keys.indexOf(key);
let index = keys.indexOf(key);
assert(index !== -1, 'Emitted wrong key: ' + key);
keys.splice(index, 1);
}
@@ -651,10 +895,10 @@ describe('term.js addons', () => {
describe('unicode - surrogates', () => {
it('2 characters per cell', function (): void {
this.timeout(10000); // This is needed because istanbul patches code and slows it down
const high = String.fromCharCode(0xD800);
let high = String.fromCharCode(0xD800);
for (let i = 0xDC00; i <= 0xDCFF; ++i) {
term.write(high + String.fromCharCode(i));
const tchar = term.buffer.lines.get(0)[0];
let tchar = term.buffer.lines.get(0)[0];
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql(high + String.fromCharCode(i));
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(2);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1);
@@ -663,7 +907,7 @@ describe('term.js addons', () => {
}
});
it('2 characters at last cell', () => {
const high = String.fromCharCode(0xD800);
let high = String.fromCharCode(0xD800);
for (let i = 0xDC00; i <= 0xDCFF; ++i) {
term.buffer.x = term.cols - 1;
term.write(high + String.fromCharCode(i));
@@ -674,7 +918,7 @@ describe('term.js addons', () => {
}
});
it('2 characters per cell over line end with autowrap', () => {
const high = String.fromCharCode(0xD800);
let high = String.fromCharCode(0xD800);
for (let i = 0xDC00; i <= 0xDCFF; ++i) {
term.buffer.x = term.cols - 1;
term.wraparoundMode = true;
@@ -687,7 +931,7 @@ describe('term.js addons', () => {
}
});
it('2 characters per cell over line end without autowrap', () => {
const high = String.fromCharCode(0xD800);
let high = String.fromCharCode(0xD800);
for (let i = 0xDC00; i <= 0xDCFF; ++i) {
term.buffer.x = term.cols - 1;
term.wraparoundMode = false;
@@ -700,11 +944,11 @@ describe('term.js addons', () => {
}
});
it('splitted surrogates', () => {
const high = String.fromCharCode(0xD800);
let high = String.fromCharCode(0xD800);
for (let i = 0xDC00; i <= 0xDCFF; ++i) {
term.write(high);
term.write(String.fromCharCode(i));
const tchar = term.buffer.lines.get(0)[0];
let tchar = term.buffer.lines.get(0)[0];
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql(high + String.fromCharCode(i));
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(2);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1);
@@ -735,12 +979,12 @@ describe('term.js addons', () => {
term.wraparoundMode = true;
term.write(Array(100).join('e\u0301'));
for (let i = 0; i < term.cols; ++i) {
const tchar = term.buffer.lines.get(0)[i];
let tchar = term.buffer.lines.get(0)[i];
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('e\u0301');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(2);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1);
}
const tchar = term.buffer.lines.get(1)[0];
let tchar = term.buffer.lines.get(1)[0];
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('e\u0301');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(2);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1);
@@ -749,12 +993,12 @@ describe('term.js addons', () => {
term.wraparoundMode = true;
term.write(Array(100).join('\uD800\uDC00\u0301'));
for (let i = 0; i < term.cols; ++i) {
const tchar = term.buffer.lines.get(0)[i];
let tchar = term.buffer.lines.get(0)[i];
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('\uD800\uDC00\u0301');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(3);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1);
}
const tchar = term.buffer.lines.get(1)[0];
let tchar = term.buffer.lines.get(1)[0];
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('\uD800\uDC00\u0301');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(3);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1);
@@ -777,7 +1021,7 @@ describe('term.js addons', () => {
term.wraparoundMode = true;
term.write(Array(50).join('¥'));
for (let i = 0; i < term.cols; ++i) {
const tchar = term.buffer.lines.get(0)[i];
let tchar = term.buffer.lines.get(0)[i];
if (i % 2) {
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(0);
@@ -788,7 +1032,7 @@ describe('term.js addons', () => {
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2);
}
}
const tchar = term.buffer.lines.get(1)[0];
let tchar = term.buffer.lines.get(1)[0];
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('¥');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(1);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2);
@@ -798,7 +1042,7 @@ describe('term.js addons', () => {
term.buffer.x = 1;
term.write(Array(50).join('¥'));
for (let i = 1; i < term.cols - 1; ++i) {
const tchar = term.buffer.lines.get(0)[i];
let tchar = term.buffer.lines.get(0)[i];
if (!(i % 2)) {
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(0);
@@ -823,7 +1067,7 @@ describe('term.js addons', () => {
term.buffer.x = 1;
term.write(Array(50).join('¥\u0301'));
for (let i = 1; i < term.cols - 1; ++i) {
const tchar = term.buffer.lines.get(0)[i];
let tchar = term.buffer.lines.get(0)[i];
if (!(i % 2)) {
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(0);
@@ -847,7 +1091,7 @@ describe('term.js addons', () => {
term.wraparoundMode = true;
term.write(Array(50).join('¥\u0301'));
for (let i = 0; i < term.cols; ++i) {
const tchar = term.buffer.lines.get(0)[i];
let tchar = term.buffer.lines.get(0)[i];
if (i % 2) {
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(0);
@@ -858,7 +1102,7 @@ describe('term.js addons', () => {
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2);
}
}
const tchar = term.buffer.lines.get(1)[0];
let tchar = term.buffer.lines.get(1)[0];
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('¥\u0301');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(2);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2);
@@ -868,7 +1112,7 @@ describe('term.js addons', () => {
term.buffer.x = 1;
term.write(Array(50).join('\ud843\ude6d\u0301'));
for (let i = 1; i < term.cols - 1; ++i) {
const tchar = term.buffer.lines.get(0)[i];
let tchar = term.buffer.lines.get(0)[i];
if (!(i % 2)) {
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(0);
@@ -892,7 +1136,7 @@ describe('term.js addons', () => {
term.wraparoundMode = true;
term.write(Array(50).join('\ud843\ude6d\u0301'));
for (let i = 0; i < term.cols; ++i) {
const tchar = term.buffer.lines.get(0)[i];
let tchar = term.buffer.lines.get(0)[i];
if (i % 2) {
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(0);
@@ -903,7 +1147,7 @@ describe('term.js addons', () => {
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2);
}
}
const tchar = term.buffer.lines.get(1)[0];
let tchar = term.buffer.lines.get(1)[0];
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('\ud843\ude6d\u0301');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(3);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2);
+543 -208
View File
File diff suppressed because it is too large Load Diff
+24 -175
View File
@@ -3,10 +3,9 @@
* @license MIT
*/
import { Terminal as PublicTerminal, ITerminalOptions as IPublicTerminalOptions, IEventEmitter, IDisposable } from 'xterm';
import { Terminal as PublicTerminal, ITerminalOptions as IPublicTerminalOptions, IEventEmitter } from 'xterm';
import { IColorSet, IRenderer } from './renderer/Types';
import { IMouseZoneManager } from './ui/Types';
import { ICharset } from './core/Types';
import { IMouseZoneManager } from './input/Types';
export type CustomKeyEventHandler = (event: KeyboardEvent) => boolean;
@@ -44,7 +43,9 @@ export interface IInputHandlingTerminal extends IEventEmitter {
insertMode: boolean;
wraparoundMode: boolean;
bracketedPasteMode: boolean;
defAttr: number;
curAttr: number;
prefix: string;
savedCols: number;
x10Mouse: boolean;
vt200Mouse: boolean;
@@ -86,7 +87,7 @@ export interface IInputHandlingTerminal extends IEventEmitter {
tabSet(): void;
}
export interface IViewport extends IDisposable {
export interface IViewport {
scrollBarWidth: number;
syncScrollArea(): void;
getLinesScrolled(ev: WheelEvent): number;
@@ -105,11 +106,10 @@ export interface ICompositionHelper {
}
/**
* Calls the parser and handles actions generated by the parser.
* Handles actions generated by the parser.
*/
export interface IInputHandler {
parse(data: string): void;
print(data: string, start: number, end: number): void;
addChar(char: string, code: number): void;
/** C0 BEL */ bell(): void;
/** C0 LF */ lineFeed(): void;
@@ -135,49 +135,26 @@ export interface IInputHandler {
/** CSI M */ deleteLines(params?: number[]): void;
/** CSI P */ deleteChars(params?: number[]): void;
/** CSI S */ scrollUp(params?: number[]): void;
/** CSI T */ scrollDown(params?: number[], collect?: string): void;
/** CSI T */ scrollDown(params?: number[]): void;
/** CSI X */ eraseChars(params?: number[]): void;
/** CSI Z */ cursorBackwardTab(params?: number[]): void;
/** CSI ` */ charPosAbsolute(params?: number[]): void;
/** CSI a */ hPositionRelative(params?: number[]): void;
/** CSI a */ HPositionRelative(params?: number[]): void;
/** CSI b */ repeatPrecedingCharacter(params?: number[]): void;
/** CSI c */ sendDeviceAttributes(params?: number[], collect?: string): void;
/** CSI c */ sendDeviceAttributes(params?: number[]): void;
/** CSI d */ linePosAbsolute(params?: number[]): void;
/** CSI e */ vPositionRelative(params?: number[]): void;
/** CSI f */ hVPosition(params?: number[]): void;
/** CSI e */ VPositionRelative(params?: number[]): void;
/** CSI f */ HVPosition(params?: number[]): void;
/** CSI g */ tabClear(params?: number[]): void;
/** CSI h */ setMode(params?: number[], collect?: string): void;
/** CSI l */ resetMode(params?: number[], collect?: string): void;
/** CSI h */ setMode(params?: number[]): void;
/** CSI l */ resetMode(params?: number[]): void;
/** CSI m */ charAttributes(params?: number[]): void;
/** CSI n */ deviceStatus(params?: number[], collect?: string): void;
/** CSI p */ softReset(params?: number[], collect?: string): void;
/** CSI q */ setCursorStyle(params?: number[], collect?: string): void;
/** CSI r */ setScrollRegion(params?: number[], collect?: string): void;
/** CSI n */ deviceStatus(params?: number[]): void;
/** CSI p */ softReset(params?: number[]): void;
/** CSI q */ setCursorStyle(params?: number[]): void;
/** CSI r */ setScrollRegion(params?: number[]): void;
/** CSI s */ saveCursor(params?: number[]): void;
/** CSI u */ restoreCursor(params?: number[]): void;
/** OSC 0
OSC 2 */ setTitle(data: string): void;
/** ESC E */ nextLine(): void;
/** ESC = */ keypadApplicationMode(): void;
/** ESC > */ keypadNumericMode(): void;
/** ESC % G
ESC % @ */ selectDefaultCharset(): void;
/** ESC ( C
ESC ) C
ESC * C
ESC + C
ESC - C
ESC . C
ESC / C */ selectCharset(collectAndFlag: string): void;
/** ESC D */ index(): void;
/** ESC H */ tabSet(): void;
/** ESC M */ reverseIndex(): void;
/** ESC c */ reset(): void;
/** ESC n
ESC o
ESC |
ESC }
ESC ~ */ setgLevel(level: number): void;
}
export interface ILinkMatcher {
@@ -192,6 +169,10 @@ export interface ILinkMatcher {
willLinkActivate?: (event: MouseEvent, uri: string) => boolean;
}
export interface ICharset {
[key: string]: string;
}
export interface ILinkHoverEvent {
x1: number;
y1: number;
@@ -209,6 +190,7 @@ export interface ITerminal extends PublicTerminal, IElementAccessor, IBufferAcce
writeBuffer: string[];
cursorHidden: boolean;
cursorState: number;
defAttr: number;
options: ITerminalOptions;
buffer: IBuffer;
buffers: IBufferSet;
@@ -244,7 +226,7 @@ export interface ILinkifierAccessor {
}
export interface IMouseHelper {
getCoords(event: { pageX: number, pageY: number }, element: HTMLElement, charMeasure: ICharMeasure, lineHeight: number, colCount: number, rowCount: number, isSelection?: boolean): [number, number];
getCoords(event: {pageX: number, pageY: number}, element: HTMLElement, charMeasure: ICharMeasure, lineHeight: number, colCount: number, rowCount: number, isSelection?: boolean): [number, number];
getRawByteCoords(event: MouseEvent, element: HTMLElement, charMeasure: ICharMeasure, lineHeight: number, colCount: number, rowCount: number): { x: number, y: number };
}
@@ -256,7 +238,6 @@ export interface ICharMeasure {
// TODO: The options that are not in the public API should be reviewed
export interface ITerminalOptions extends IPublicTerminalOptions {
[key: string]: any;
cancelEvents?: boolean;
convertEol?: boolean;
debug?: boolean;
@@ -280,7 +261,6 @@ export interface IBuffer {
savedX: number;
isCursorInViewport: boolean;
translateBufferLineToString(lineIndex: number, trimRight: boolean, startCol?: number, endCol?: number): string;
getWrappedRangeForLine(y: number): { first: number, last: number };
nextStop(x?: number): number;
prevStop(x?: number): number;
}
@@ -375,134 +355,3 @@ export interface IBrowser {
export interface ISoundManager {
playBellSound(): void;
}
/**
* Internal states of EscapeSequenceParser.
*/
export const enum ParserState {
GROUND = 0,
ESCAPE = 1,
ESCAPE_INTERMEDIATE = 2,
CSI_ENTRY = 3,
CSI_PARAM = 4,
CSI_INTERMEDIATE = 5,
CSI_IGNORE = 6,
SOS_PM_APC_STRING = 7,
OSC_STRING = 8,
DCS_ENTRY = 9,
DCS_PARAM = 10,
DCS_IGNORE = 11,
DCS_INTERMEDIATE = 12,
DCS_PASSTHROUGH = 13
}
/**
* Internal actions of EscapeSequenceParser.
*/
export const enum ParserAction {
IGNORE = 0,
ERROR = 1,
PRINT = 2,
EXECUTE = 3,
OSC_START = 4,
OSC_PUT = 5,
OSC_END = 6,
CSI_DISPATCH = 7,
PARAM = 8,
COLLECT = 9,
ESC_DISPATCH = 10,
CLEAR = 11,
DCS_HOOK = 12,
DCS_PUT = 13,
DCS_UNHOOK = 14
}
/**
* Internal state of EscapeSequenceParser.
* Used as argument of the error handler to allow
* introspection at runtime on parse errors.
* Return it with altered values to recover from
* faulty states (not yet supported).
* Set `abort` to `true` to abort the current parsing.
*/
export interface IParsingState {
// position in parse string
position: number;
// actual character code
code: number;
// current parser state
currentState: ParserState;
// print buffer start index (-1 for not set)
print: number;
// dcs buffer start index (-1 for not set)
dcs: number;
// osc string buffer
osc: string;
// collect buffer with intermediate characters
collect: string;
// params buffer
params: number[];
// should abort (default: false)
abort: boolean;
}
/**
* DCS handler signature for EscapeSequenceParser.
* EscapeSequenceParser handles DCS commands via separate
* subparsers that get hook/unhooked and can handle
* arbitrary amount of print data.
* On entering a DSC sequence `hook` is called by
* `EscapeSequenceParser`. Use it to initialize or reset
* states needed to handle the current DCS sequence.
* EscapeSequenceParser will call `put` several times if the
* parsed string got splitted, therefore you might have to collect
* `data` until `unhook` is called. `unhook` marks the end
* of the current DCS sequence.
*/
export interface IDcsHandler {
hook(collect: string, params: number[], flag: number): void;
put(data: string, start: number, end: number): void;
unhook(): void;
}
/**
* EscapeSequenceParser interface.
*/
export interface IEscapeSequenceParser extends IDisposable {
/**
* Reset the parser to its initial state (handlers are kept).
*/
reset(): void;
/**
* Parse string `data`.
* @param data The data to parse.
*/
parse(data: string): void;
setPrintHandler(callback: (data: string, start: number, end: number) => void): void;
clearPrintHandler(): void;
setExecuteHandler(flag: string, callback: () => void): void;
clearExecuteHandler(flag: string): void;
setExecuteHandlerFallback(callback: (code: number) => void): void;
setCsiHandler(flag: string, callback: (params: number[], collect: string) => void): void;
clearCsiHandler(flag: string): void;
setCsiHandlerFallback(callback: (collect: string, params: number[], flag: number) => void): void;
setEscHandler(collectAndFlag: string, callback: () => void): void;
clearEscHandler(collectAndFlag: string): void;
setEscHandlerFallback(callback: (collect: string, flag: number) => void): void;
setOscHandler(ident: number, callback: (data: string) => void): void;
clearOscHandler(ident: number): void;
setOscHandlerFallback(callback: (identifier: number, data: string) => void): void;
setDcsHandler(collectAndFlag: string, handler: IDcsHandler): void;
clearDcsHandler(collectAndFlag: string): void;
setDcsHandlerFallback(handler: IDcsHandler): void;
setErrorHandler(callback: (state: IParsingState) => IParsingState): void;
clearErrorHandler(): void;
}
+4 -8
View File
@@ -5,9 +5,7 @@
import { IColorSet } from './renderer/Types';
import { ITerminal, IViewport } from './Types';
import { CharMeasure } from './ui/CharMeasure';
import { Disposable } from './common/Lifecycle';
import { addDisposableDomListener } from './ui/Lifecycle';
import { CharMeasure } from './utils/CharMeasure';
const FALLBACK_SCROLL_BAR_WIDTH = 15;
@@ -15,7 +13,7 @@ const FALLBACK_SCROLL_BAR_WIDTH = 15;
* Represents the viewport of a terminal, the visible area within the larger buffer of output.
* Logic for the virtual scroll bar is included in this object.
*/
export class Viewport extends Disposable implements IViewport {
export class Viewport implements IViewport {
public scrollBarWidth: number = 0;
private _currentRowHeight: number = 0;
private _lastRecordedBufferLength: number = 0;
@@ -41,13 +39,11 @@ export class Viewport extends Disposable implements IViewport {
private _scrollArea: HTMLElement,
private _charMeasure: CharMeasure
) {
super();
// Measure the width of the scrollbar. If it is 0 we can assume it's an OSX overlay scrollbar.
// Unfortunately the overlay scrollbar would be hidden underneath the screen element in that case,
// therefore we account for a standard amount to make it visible
this.scrollBarWidth = (this._viewportElement.offsetWidth - this._scrollArea.offsetWidth) || FALLBACK_SCROLL_BAR_WIDTH;
this.register(addDisposableDomListener(this._viewportElement, 'scroll', this._onScroll.bind(this)));
this._viewportElement.addEventListener('scroll', this._onScroll.bind(this));
// Perform this async to ensure the CharMeasure is ready.
setTimeout(() => this.syncScrollArea(), 0);
@@ -184,7 +180,7 @@ export class Viewport extends Disposable implements IViewport {
* @param ev The touch event.
*/
public onTouchMove(ev: TouchEvent): void {
const deltaY = this._lastTouchY - ev.touches[0].pageY;
let deltaY = this._lastTouchY - ev.touches[0].pageY;
this._lastTouchY = ev.touches[0].pageY;
if (deltaY === 0) {
return;
+1 -5
View File
@@ -5,13 +5,9 @@
* Implements the attach method, that attaches the terminal to a WebSocket stream.
*/
import { Terminal, IDisposable } from 'xterm';
import { Terminal } from 'xterm';
export interface IAttachAddonTerminal extends Terminal {
_core: {
register<T extends IDisposable>(d: T): void;
};
__socket?: WebSocket;
__attachSocketBuffer?: string;
+10 -23
View File
@@ -5,7 +5,9 @@
* Implements the attach method, that attaches the terminal to a WebSocket stream.
*/
import { Terminal, IDisposable } from 'xterm';
/// <reference path="../../../typings/xterm.d.ts"/>
import { Terminal } from 'xterm';
import { IAttachAddonTerminal } from './Interfaces';
/**
@@ -36,11 +38,10 @@ export function attach(term: Terminal, socket: WebSocket, bidirectional: boolean
}
};
// TODO: This should be typed but there seem to be issues importing the type
let myTextDecoder: any;
let myTextDecoder;
addonTerminal.__getMessage = function(ev: MessageEvent): void {
let str: string;
let str;
if (typeof ev.data === 'object') {
if (!myTextDecoder) {
@@ -50,7 +51,7 @@ export function attach(term: Terminal, socket: WebSocket, bidirectional: boolean
str = myTextDecoder.decode(ev.data);
displayData(str);
} else {
const fileReader = new FileReader();
let fileReader = new FileReader();
fileReader.addEventListener('load', () => {
str = myTextDecoder.decode(this.result);
@@ -87,28 +88,14 @@ export function attach(term: Terminal, socket: WebSocket, bidirectional: boolean
socket.send(data);
};
addonTerminal._core.register(addSocketListener(socket, 'message', addonTerminal.__getMessage));
socket.addEventListener('message', addonTerminal.__getMessage);
if (bidirectional) {
addonTerminal._core.register(addonTerminal.addDisposableListener('data', addonTerminal.__sendData));
addonTerminal.on('data', addonTerminal.__sendData);
}
addonTerminal._core.register(addSocketListener(socket, 'close', () => detach(addonTerminal, socket)));
addonTerminal._core.register(addSocketListener(socket, 'error', () => detach(addonTerminal, socket)));
}
function addSocketListener(socket: WebSocket, type: string, handler: (this: WebSocket, ev: Event) => any): IDisposable {
socket.addEventListener(type, handler);
return {
dispose: () => {
if (!handler) {
// Already disposed
return;
}
socket.removeEventListener(type, handler);
handler = null;
}
};
socket.addEventListener('close', () => detach(addonTerminal, socket));
socket.addEventListener('error', () => detach(addonTerminal, socket));
}
/**
+2 -7
View File
@@ -6,11 +6,6 @@
"outDir": "../../../lib/addons/attach/",
"sourceMap": true,
"removeComments": true,
"declaration": true,
"preserveWatchOutput": true
},
"include": [
"**/*.ts",
"../../../typings/xterm.d.ts"
]
"declaration": true
}
}
-3
View File
@@ -1,3 +0,0 @@
## fit addon
The fit addon adjusts the dimensions of the terminal to match best fit its parent element container. `fit` will only work when the element is visible.
+6 -4
View File
@@ -13,6 +13,8 @@
* row and truncate its width with the current number of columns).
*/
/// <reference path="../../../typings/xterm.d.ts"/>
import { Terminal } from 'xterm';
export interface IGeometry {
@@ -37,10 +39,10 @@ export function proposeGeometry(term: Terminal): IGeometry {
const elementPaddingVer = elementPadding.top + elementPadding.bottom;
const elementPaddingHor = elementPadding.right + elementPadding.left;
const availableHeight = parentElementHeight - elementPaddingVer;
const availableWidth = parentElementWidth - elementPaddingHor - (<any>term)._core.viewport.scrollBarWidth;
const availableWidth = parentElementWidth - elementPaddingHor - (<any>term).viewport.scrollBarWidth;
const geometry = {
cols: Math.floor(availableWidth / (<any>term)._core.renderer.dimensions.actualCellWidth),
rows: Math.floor(availableHeight / (<any>term)._core.renderer.dimensions.actualCellHeight)
cols: Math.floor(availableWidth / (<any>term).renderer.dimensions.actualCellWidth),
rows: Math.floor(availableHeight / (<any>term).renderer.dimensions.actualCellHeight)
};
return geometry;
}
@@ -50,7 +52,7 @@ export function fit(term: Terminal): void {
if (geometry) {
// Force a full render
if (term.rows !== geometry.rows || term.cols !== geometry.cols) {
(<any>term)._core.renderer.clear();
(<any>term).renderer.clear();
term.resize(geometry.cols, geometry.rows);
}
}
+2 -7
View File
@@ -6,11 +6,6 @@
"outDir": "../../../lib/addons/fit/",
"sourceMap": true,
"removeComments": true,
"declaration": true,
"preserveWatchOutput": true
},
"include": [
"**/*.ts",
"../../../typings/xterm.d.ts"
]
"declaration": true
}
}
+2
View File
@@ -3,6 +3,8 @@
* @license MIT
*/
/// <reference path="../../../typings/xterm.d.ts"/>
import { Terminal } from 'xterm';
/**
+2 -7
View File
@@ -6,11 +6,6 @@
"outDir": "../../../lib/addons/fullscreen/",
"sourceMap": true,
"removeComments": true,
"declaration": true,
"preserveWatchOutput": true
},
"include": [
"**/*.ts",
"../../../typings/xterm.d.ts"
]
"declaration": true
}
}
+4 -7
View File
@@ -5,15 +5,12 @@
import { Terminal } from 'xterm';
// TODO: Don't rely on this private API
export interface ITerminalCore {
buffer: any;
selectionManager: any;
}
export interface ISearchAddonTerminal extends Terminal {
__searchHelper?: ISearchHelper;
_core: ITerminalCore;
// TODO: Reuse ITerminal from core
buffer: any;
selectionManager: any;
}
export interface ISearchHelper {
+12 -12
View File
@@ -35,14 +35,14 @@ export class SearchHelper implements ISearchHelper {
let result: ISearchResult;
let startRow = this._terminal._core.buffer.ydisp;
if (this._terminal._core.selectionManager.selectionEnd) {
let startRow = this._terminal.buffer.ydisp;
if (this._terminal.selectionManager.selectionEnd) {
// Start from the selection end if there is a selection
startRow = this._terminal._core.selectionManager.selectionEnd[1];
startRow = this._terminal.selectionManager.selectionEnd[1];
}
// Search from ydisp + 1 to end
for (let y = startRow + 1; y < this._terminal._core.buffer.ybase + this._terminal.rows; y++) {
for (let y = startRow + 1; y < this._terminal.buffer.ybase + this._terminal.rows; y++) {
result = this._findInLine(term, y);
if (result) {
break;
@@ -76,10 +76,10 @@ export class SearchHelper implements ISearchHelper {
let result: ISearchResult;
let startRow = this._terminal._core.buffer.ydisp;
if (this._terminal._core.selectionManager.selectionStart) {
let startRow = this._terminal.buffer.ydisp;
if (this._terminal.selectionManager.selectionStart) {
// Start from the selection end if there is a selection
startRow = this._terminal._core.selectionManager.selectionStart[1];
startRow = this._terminal.selectionManager.selectionStart[1];
}
// Search from ydisp + 1 to end
@@ -92,7 +92,7 @@ export class SearchHelper implements ISearchHelper {
// Search from the top to the current ydisp
if (!result) {
for (let y = this._terminal._core.buffer.ybase + this._terminal.rows - 1; y > startRow; y--) {
for (let y = this._terminal.buffer.ybase + this._terminal.rows - 1; y > startRow; y--) {
result = this._findInLine(term, y);
if (result) {
break;
@@ -111,11 +111,11 @@ export class SearchHelper implements ISearchHelper {
* @return The search result if it was found.
*/
private _findInLine(term: string, y: number): ISearchResult {
const lowerStringLine = this._terminal._core.buffer.translateBufferLineToString(y, true).toLowerCase();
const lowerStringLine = this._terminal.buffer.translateBufferLineToString(y, true).toLowerCase();
const lowerTerm = term.toLowerCase();
let searchIndex = lowerStringLine.indexOf(lowerTerm);
if (searchIndex >= 0) {
const line = this._terminal._core.buffer.lines.get(y);
const line = this._terminal.buffer.lines.get(y);
for (let i = 0; i < searchIndex; i++) {
const charData = line[i];
// Adjust the searchIndex to normalize emoji into single chars
@@ -147,8 +147,8 @@ export class SearchHelper implements ISearchHelper {
if (!result) {
return false;
}
this._terminal._core.selectionManager.setSelection(result.col, result.row, result.term.length);
this._terminal.scrollLines(result.row - this._terminal._core.buffer.ydisp);
this._terminal.selectionManager.setSelection(result.col, result.row, result.term.length);
this._terminal.scrollLines(result.row - this._terminal.buffer.ydisp);
return true;
}
}
+2
View File
@@ -3,6 +3,8 @@
* @license MIT
*/
/// <reference path="../../../typings/xterm.d.ts"/>
import { SearchHelper } from './SearchHelper';
import { Terminal } from 'xterm';
import { ISearchAddonTerminal } from './Interfaces';
+2 -7
View File
@@ -6,11 +6,6 @@
"outDir": "../../../lib/addons/search/",
"sourceMap": true,
"removeComments": true,
"declaration": true,
"preserveWatchOutput": true
},
"include": [
"**/*.ts",
"../../../typings/xterm.d.ts"
]
"declaration": true
}
}
+2
View File
@@ -6,6 +6,8 @@
* WebSocket stream.
*/
/// <reference path="../../../typings/xterm.d.ts"/>
import { Terminal } from 'xterm';
import { ITerminadoAddonTerminal } from './Interfaces';
+2 -7
View File
@@ -6,11 +6,6 @@
"outDir": "../../../lib/addons/terminado/",
"sourceMap": true,
"removeComments": true,
"declaration": true,
"preserveWatchOutput": true
},
"include": [
"**/*.ts",
"../../../typings/xterm.d.ts"
]
"declaration": true
}
}
+2 -7
View File
@@ -6,11 +6,6 @@
"outDir": "../../../lib/addons/webLinks/",
"sourceMap": true,
"removeComments": true,
"declaration": true,
"preserveWatchOutput": true
},
"include": [
"**/*.ts",
"../../../typings/xterm.d.ts"
]
"declaration": true
}
}
+2 -2
View File
@@ -34,8 +34,8 @@ describe('webLinks addon', () => {
const row = ' http://foo.com/a~b#c~d?e~f ';
const match = row.match(term.regex);
const uri = match[term.options.matchIndex];
let match = row.match(term.regex);
let uri = match[term.options.matchIndex];
assert.equal(uri, 'http://foo.com/a~b#c~d?e~f');
});
+2
View File
@@ -3,6 +3,8 @@
* @license MIT
*/
/// <reference path="../../../typings/xterm.d.ts"/>
import { Terminal, ILinkMatcherOptions } from 'xterm';
const protocolClause = '(https?:\\/\\/)';
+1 -5
View File
@@ -5,10 +5,6 @@
import { Terminal } from 'xterm';
export interface ITerminalCore {
export interface IWinptyCompatAddonTerminal extends Terminal {
buffer: any;
}
export interface IWinptyCompatAddonTerminal extends Terminal {
_core: ITerminalCore;
}
+2 -7
View File
@@ -6,11 +6,6 @@
"outDir": "../../../lib/addons/winptyCompat/",
"sourceMap": true,
"removeComments": true,
"declaration": true,
"preserveWatchOutput": true
},
"include": [
"**/*.ts",
"../../../typings/xterm.d.ts"
]
"declaration": true
}
}
+4 -2
View File
@@ -3,6 +3,8 @@
* @license MIT
*/
/// <reference path="../../../typings/xterm.d.ts"/>
import { Terminal } from 'xterm';
import { IWinptyCompatAddonTerminal } from './Interfaces';
@@ -26,11 +28,11 @@ export function winptyCompatInit(terminal: Terminal): void {
// Windows when text reaches the end of the terminal it's likely going to be
// wrapped.
addonTerminal.on('linefeed', () => {
const line = addonTerminal._core.buffer.lines.get(addonTerminal._core.buffer.ybase + addonTerminal._core.buffer.y - 1);
const line = addonTerminal.buffer.lines.get(addonTerminal.buffer.ybase + addonTerminal.buffer.y - 1);
const lastChar = line[addonTerminal.cols - 1];
if (lastChar[3] !== 32 /* ' ' */) {
const nextLine = addonTerminal._core.buffer.lines.get(addonTerminal._core.buffer.ybase + addonTerminal._core.buffer.y);
const nextLine = addonTerminal.buffer.lines.get(addonTerminal.buffer.ybase + addonTerminal.buffer.y);
(<any>nextLine).isWrapped = true;
}
});
+2 -7
View File
@@ -6,11 +6,6 @@
"outDir": "../../../lib/addons/zmodem/",
"sourceMap": true,
"removeComments": true,
"declaration": true,
"preserveWatchOutput": true
},
"include": [
"**/*.ts",
"../../../typings/xterm.d.ts"
]
"declaration": true
}
}
+5 -3
View File
@@ -3,6 +3,8 @@
* @license MIT
*/
/// <reference path="../../../typings/xterm.d.ts"/>
import { Terminal } from 'xterm';
/**
@@ -46,13 +48,13 @@ function zmodemAttach(ws: WebSocket, opts: IZmodemOptions = {}): void {
let zsentry;
function shouldWrite(): boolean {
function _shouldWrite(): boolean {
return !!zsentry.get_confirmed_session() || !opts.noTerminalWriteOutsideSession;
}
zsentry = new zmodem.Sentry({
to_terminal: (octets: ArrayLike<number>) => {
if (shouldWrite()) {
if (_shouldWrite()) {
term.write(
String.fromCharCode.apply(String, octets)
);
@@ -70,7 +72,7 @@ function zmodemAttach(ws: WebSocket, opts: IZmodemOptions = {}): void {
// may be specific to xterm.js’s demo, ultimately we
// should reject anything that isn’t binary.
if (typeof evt.data === 'string') {
if (shouldWrite()) {
if (_shouldWrite()) {
term.write(evt.data);
}
}
-33
View File
@@ -1,33 +0,0 @@
/**
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { IDisposable } from 'xterm';
/**
* A base class that can be extended to provide convenience methods for managing the lifecycle of an
* object and its components.
*/
export abstract class Disposable implements IDisposable {
protected _disposables: IDisposable[] = [];
constructor() {
}
/**
* Disposes the object, triggering the `dispose` method on all registered IDisposables.
*/
public dispose(): void {
this._disposables.forEach(d => d.dispose());
this._disposables.length = 0;
}
/**
* Registers a disposable object.
* @param d The disposable to register.
*/
public register<T extends IDisposable>(d: T): void {
this._disposables.push(d);
}
}
-18
View File
@@ -1,18 +0,0 @@
/**
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
* @license MIT
*/
/**
* A keyboard event interface which does not depend on the DOM, KeyboardEvent implicitly extends
* this event.
*/
export interface IKeyboardEvent {
altKey: boolean;
ctrlKey: boolean;
shiftKey: boolean;
metaKey: boolean;
keyCode: number;
key: string;
type: string;
}
-21
View File
@@ -1,21 +0,0 @@
/**
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
* @license MIT
*/
export const enum KeyboardResultType {
SEND_KEY,
SELECT_ALL,
PAGE_UP,
PAGE_DOWN
}
export interface IKeyboardResult {
type: KeyboardResultType;
cancel: boolean;
key: string | undefined;
}
export interface ICharset {
[key: string]: string;
}
-284
View File
@@ -1,284 +0,0 @@
import { assert } from 'chai';
import { evaluateKeyboardEvent } from './Keyboard';
import { IKeyboardResult } from '../Types';
/**
* A helper function for testing which allows passing in a partial event and defaults will be filled
* in on it.
*/
function testEvaluateKeyboardEvent(partialEvent: {
altKey?: boolean;
ctrlKey?: boolean;
shiftKey?: boolean;
metaKey?: boolean;
keyCode?: number;
key?: string;
type?: string;
}, partialOptions: {
applicationCursorMode?: boolean;
isMac?: boolean;
macOptionIsMeta?: boolean;
} = {}): IKeyboardResult {
const event = {
altKey: partialEvent.altKey || false,
ctrlKey: partialEvent.ctrlKey || false,
shiftKey: partialEvent.shiftKey || false,
metaKey: partialEvent.metaKey || false,
keyCode: partialEvent.keyCode !== undefined ? partialEvent.keyCode : undefined,
key: partialEvent.key || '',
type: partialEvent.type || ''
};
const options = {
applicationCursorMode: partialOptions.applicationCursorMode || false,
isMac: partialOptions.isMac || false,
macOptionIsMeta: partialOptions.macOptionIsMeta || false
};
return evaluateKeyboardEvent(event, options.applicationCursorMode, options.isMac, options.macOptionIsMeta);
}
describe('Keyboard', () => {
describe('evaluateKeyEscapeSequence', () => {
it('should return the correct escape sequence for unmodified keys', () => {
// Backspace
assert.equal(testEvaluateKeyboardEvent({ keyCode: 8 }).key, '\x7f'); // ^?
// Tab
assert.equal(testEvaluateKeyboardEvent({ keyCode: 9 }).key, '\t');
// Return/enter
assert.equal(testEvaluateKeyboardEvent({ keyCode: 13 }).key, '\r'); // CR
// Escape
assert.equal(testEvaluateKeyboardEvent({ keyCode: 27 }).key, '\x1b');
// Page up, page down
assert.equal(testEvaluateKeyboardEvent({ keyCode: 33 }).key, '\x1b[5~'); // CSI 5 ~
assert.equal(testEvaluateKeyboardEvent({ keyCode: 34 }).key, '\x1b[6~'); // CSI 6 ~
// End, Home
assert.equal(testEvaluateKeyboardEvent({ keyCode: 35 }).key, '\x1b[F'); // SS3 F
assert.equal(testEvaluateKeyboardEvent({ keyCode: 36 }).key, '\x1b[H'); // SS3 H
// Left, up, right, down arrows
assert.equal(testEvaluateKeyboardEvent({ keyCode: 37 }).key, '\x1b[D'); // CSI D
assert.equal(testEvaluateKeyboardEvent({ keyCode: 38 }).key, '\x1b[A'); // CSI A
assert.equal(testEvaluateKeyboardEvent({ keyCode: 39 }).key, '\x1b[C'); // CSI C
assert.equal(testEvaluateKeyboardEvent({ keyCode: 40 }).key, '\x1b[B'); // CSI B
// Insert
assert.equal(testEvaluateKeyboardEvent({ keyCode: 45 }).key, '\x1b[2~'); // CSI 2 ~
// Delete
assert.equal(testEvaluateKeyboardEvent({ keyCode: 46 }).key, '\x1b[3~'); // CSI 3 ~
// F1-F12
assert.equal(testEvaluateKeyboardEvent({ keyCode: 112 }).key, '\x1bOP'); // SS3 P
assert.equal(testEvaluateKeyboardEvent({ keyCode: 113 }).key, '\x1bOQ'); // SS3 Q
assert.equal(testEvaluateKeyboardEvent({ keyCode: 114 }).key, '\x1bOR'); // SS3 R
assert.equal(testEvaluateKeyboardEvent({ keyCode: 115 }).key, '\x1bOS'); // SS3 S
assert.equal(testEvaluateKeyboardEvent({ keyCode: 116 }).key, '\x1b[15~'); // CSI 1 5 ~
assert.equal(testEvaluateKeyboardEvent({ keyCode: 117 }).key, '\x1b[17~'); // CSI 1 7 ~
assert.equal(testEvaluateKeyboardEvent({ keyCode: 118 }).key, '\x1b[18~'); // CSI 1 8 ~
assert.equal(testEvaluateKeyboardEvent({ keyCode: 119 }).key, '\x1b[19~'); // CSI 1 9 ~
assert.equal(testEvaluateKeyboardEvent({ keyCode: 120 }).key, '\x1b[20~'); // CSI 2 0 ~
assert.equal(testEvaluateKeyboardEvent({ keyCode: 121 }).key, '\x1b[21~'); // CSI 2 1 ~
assert.equal(testEvaluateKeyboardEvent({ keyCode: 122 }).key, '\x1b[23~'); // CSI 2 3 ~
assert.equal(testEvaluateKeyboardEvent({ keyCode: 123 }).key, '\x1b[24~'); // CSI 2 4 ~
});
it('should return \\x1b[3;5~ for ctrl+delete', () => {
assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, keyCode: 46 }).key, '\x1b[3;5~');
});
it('should return \\x1b[3;2~ for shift+delete', () => {
assert.equal(testEvaluateKeyboardEvent({ shiftKey: true, keyCode: 46 }).key, '\x1b[3;2~');
});
it('should return \\x1b[3;3~ for alt+delete', () => {
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 46 }).key, '\x1b[3;3~');
});
it('should return \\x1b[5D for ctrl+left', () => {
assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, keyCode: 37 }).key, '\x1b[1;5D'); // CSI 5 D
});
it('should return \\x1b[5C for ctrl+right', () => {
assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, keyCode: 39 }).key, '\x1b[1;5C'); // CSI 5 C
});
it('should return \\x1b[5A for ctrl+up', () => {
assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, keyCode: 38 }).key, '\x1b[1;5A'); // CSI 5 A
});
it('should return \\x1b[5B for ctrl+down', () => {
assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, keyCode: 40 }).key, '\x1b[1;5B'); // CSI 5 B
});
describe('On non-macOS platforms', () => {
// Evalueate alt + arrow key movement, which is a feature of terminal emulators but not VT100
// http://unix.stackexchange.com/a/108106
it('should return \\x1b[5D for alt+left', () => {
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 37 }, { isMac: false }).key, '\x1b[1;5D'); // CSI 5 D
});
it('should return \\x1b[5C for alt+right', () => {
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 39 }, { isMac: false }).key, '\x1b[1;5C'); // CSI 5 C
});
it('should return \\x1ba for alt+a', () => {
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 65 }, { isMac: false }).key, '\x1ba');
});
});
describe('On macOS platforms', () => {
it('should return \\x1bb for alt+left', () => {
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 37 }, { isMac: true }).key, '\x1bb'); // CSI 5 D
});
it('should return \\x1bf for alt+right', () => {
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 39 }, { isMac: true }).key, '\x1bf'); // CSI 5 C
});
it('should return undefined for alt+a', () => {
assert.strictEqual(testEvaluateKeyboardEvent({ altKey: true, keyCode: 65 }, { isMac: true }).key, undefined), { isMac: true };
});
});
describe('with macOptionIsMeta', () => {
it('should return \\x1ba for alt+a', () => {
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 65 }, { isMac: true, macOptionIsMeta: true }).key, '\x1ba');
});
});
it('should return \\x1b[5A for alt+up', () => {
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 38 }).key, '\x1b[1;5A'); // CSI 5 A
});
it('should return \\x1b[5B for alt+down', () => {
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 40 }).key, '\x1b[1;5B'); // CSI 5 B
});
it('should return the correct escape sequence for modified F1-F12 keys', () => {
assert.equal(testEvaluateKeyboardEvent({ shiftKey: true, keyCode: 112 }).key, '\x1b[1;2P');
assert.equal(testEvaluateKeyboardEvent({ shiftKey: true, keyCode: 113 }).key, '\x1b[1;2Q');
assert.equal(testEvaluateKeyboardEvent({ shiftKey: true, keyCode: 114 }).key, '\x1b[1;2R');
assert.equal(testEvaluateKeyboardEvent({ shiftKey: true, keyCode: 115 }).key, '\x1b[1;2S');
assert.equal(testEvaluateKeyboardEvent({ shiftKey: true, keyCode: 116 }).key, '\x1b[15;2~');
assert.equal(testEvaluateKeyboardEvent({ shiftKey: true, keyCode: 117 }).key, '\x1b[17;2~');
assert.equal(testEvaluateKeyboardEvent({ shiftKey: true, keyCode: 118 }).key, '\x1b[18;2~');
assert.equal(testEvaluateKeyboardEvent({ shiftKey: true, keyCode: 119 }).key, '\x1b[19;2~');
assert.equal(testEvaluateKeyboardEvent({ shiftKey: true, keyCode: 120 }).key, '\x1b[20;2~');
assert.equal(testEvaluateKeyboardEvent({ shiftKey: true, keyCode: 121 }).key, '\x1b[21;2~');
assert.equal(testEvaluateKeyboardEvent({ shiftKey: true, keyCode: 122 }).key, '\x1b[23;2~');
assert.equal(testEvaluateKeyboardEvent({ shiftKey: true, keyCode: 123 }).key, '\x1b[24;2~');
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 112 }).key, '\x1b[1;3P');
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 113 }).key, '\x1b[1;3Q');
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 114 }).key, '\x1b[1;3R');
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 115 }).key, '\x1b[1;3S');
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 116 }).key, '\x1b[15;3~');
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 117 }).key, '\x1b[17;3~');
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 118 }).key, '\x1b[18;3~');
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 119 }).key, '\x1b[19;3~');
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 120 }).key, '\x1b[20;3~');
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 121 }).key, '\x1b[21;3~');
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 122 }).key, '\x1b[23;3~');
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 123 }).key, '\x1b[24;3~');
assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, keyCode: 112 }).key, '\x1b[1;5P');
assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, keyCode: 113 }).key, '\x1b[1;5Q');
assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, keyCode: 114 }).key, '\x1b[1;5R');
assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, keyCode: 115 }).key, '\x1b[1;5S');
assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, keyCode: 116 }).key, '\x1b[15;5~');
assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, keyCode: 117 }).key, '\x1b[17;5~');
assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, keyCode: 118 }).key, '\x1b[18;5~');
assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, keyCode: 119 }).key, '\x1b[19;5~');
assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, keyCode: 120 }).key, '\x1b[20;5~');
assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, keyCode: 121 }).key, '\x1b[21;5~');
assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, keyCode: 122 }).key, '\x1b[23;5~');
assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, keyCode: 123 }).key, '\x1b[24;5~');
});
// Characters using ctrl+alt sequences
it('should return proper sequence for ctrl+alt+a', () => {
assert.equal(testEvaluateKeyboardEvent({ altKey: true, ctrlKey: true, keyCode: 65 }).key, '\x1b\x01');
});
// Characters using alt sequences (numbers)
it('should return proper sequences for alt+0', () => {
assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 48 }).key, '\x1b0');
assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true, keyCode: 48 }).key, '\x1b)');
});
it('should return proper sequences for alt+1', () => {
assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 49 }).key, '\x1b1');
assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true, keyCode: 49 }).key, '\x1b!');
});
it('should return proper sequences for alt+2', () => {
assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 50 }).key, '\x1b2');
assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true, keyCode: 50 }).key, '\x1b@');
});
it('should return proper sequences for alt+3', () => {
assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 51 }).key, '\x1b3');
assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true, keyCode: 51 }).key, '\x1b#');
});
it('should return proper sequences for alt+4', () => {
assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 52 }).key, '\x1b4');
assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true, keyCode: 52 }).key, '\x1b$');
});
it('should return proper sequences for alt+5', () => {
assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 53 }).key, '\x1b5');
assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true, keyCode: 53 }).key, '\x1b%');
});
it('should return proper sequences for alt+6', () => {
assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 54 }).key, '\x1b6');
assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true, keyCode: 54 }).key, '\x1b^');
});
it('should return proper sequences for alt+7', () => {
assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 55 }).key, '\x1b7');
assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true, keyCode: 55 }).key, '\x1b&');
});
it('should return proper sequences for alt+8', () => {
assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 56 }).key, '\x1b8');
assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true, keyCode: 56 }).key, '\x1b*');
});
it('should return proper sequences for alt+9', () => {
assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 57 }).key, '\x1b9');
assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true, keyCode: 57 }).key, '\x1b(');
});
// Characters using alt sequences (special chars)
it('should return proper sequences for alt+;', () => {
assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 186 }).key, '\x1b;');
assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true, keyCode: 186 }).key, '\x1b:');
});
it('should return proper sequences for alt+=', () => {
assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 187 }).key, '\x1b=');
assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true, keyCode: 187 }).key, '\x1b+');
});
it('should return proper sequences for alt+,', () => {
assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 188 }).key, '\x1b,');
assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true, keyCode: 188 }).key, '\x1b<');
});
it('should return proper sequences for alt+-', () => {
assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 189 }).key, '\x1b-');
assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true, keyCode: 189 }).key, '\x1b_');
});
it('should return proper sequences for alt+.', () => {
assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 190 }).key, '\x1b.');
assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true, keyCode: 190 }).key, '\x1b>');
});
it('should return proper sequences for alt+/', () => {
assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 191 }).key, '\x1b/');
assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true, keyCode: 191 }).key, '\x1b?');
});
it('should return proper sequences for alt+~', () => {
assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 192 }).key, '\x1b`');
assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true, keyCode: 192 }).key, '\x1b~');
});
it('should return proper sequences for alt+[', () => {
assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 219 }).key, '\x1b[');
assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true, keyCode: 219 }).key, '\x1b{');
});
it('should return proper sequences for alt+\\', () => {
assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 220 }).key, '\x1b\\');
assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true, keyCode: 220 }).key, '\x1b|');
});
it('should return proper sequences for alt+]', () => {
assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 221 }).key, '\x1b]');
assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true, keyCode: 221 }).key, '\x1b}');
});
it('should return proper sequences for alt+\'', () => {
assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 222 }).key, '\x1b\'');
assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true, keyCode: 222 }).key, '\x1b"');
});
it('should handle mobile arrow events', () => {
assert.equal(testEvaluateKeyboardEvent({ keyCode: 0, key: 'UIKeyInputUpArrow' }).key, '\x1b[A');
assert.equal(testEvaluateKeyboardEvent({ keyCode: 0, key: 'UIKeyInputUpArrow' }, { applicationCursorMode: true }).key, '\x1bOA');
assert.equal(testEvaluateKeyboardEvent({ keyCode: 0, key: 'UIKeyInputLeftArrow' }).key, '\x1b[D');
assert.equal(testEvaluateKeyboardEvent({ keyCode: 0, key: 'UIKeyInputLeftArrow' }, { applicationCursorMode: true }).key, '\x1bOD');
assert.equal(testEvaluateKeyboardEvent({ keyCode: 0, key: 'UIKeyInputRightArrow' }).key, '\x1b[C');
assert.equal(testEvaluateKeyboardEvent({ keyCode: 0, key: 'UIKeyInputRightArrow' }, { applicationCursorMode: true }).key, '\x1bOC');
assert.equal(testEvaluateKeyboardEvent({ keyCode: 0, key: 'UIKeyInputDownArrow' }).key, '\x1b[B');
assert.equal(testEvaluateKeyboardEvent({ keyCode: 0, key: 'UIKeyInputDownArrow' }, { applicationCursorMode: true }).key, '\x1bOB');
});
});
});
-357
View File
@@ -1,357 +0,0 @@
/**
* Copyright (c) 2014 The xterm.js authors. All rights reserved.
* Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
* @license MIT
*/
import { IKeyboardEvent } from '../../common/Types';
import { IKeyboardResult, KeyboardResultType } from '../Types';
import { C0 } from '../../common/data/EscapeSequences';
// reg + shift key mappings for digits and special chars
const KEYCODE_KEY_MAPPINGS: { [key: number]: [string, string]} = {
// digits 0-9
48: ['0', ')'],
49: ['1', '!'],
50: ['2', '@'],
51: ['3', '#'],
52: ['4', '$'],
53: ['5', '%'],
54: ['6', '^'],
55: ['7', '&'],
56: ['8', '*'],
57: ['9', '('],
// special chars
186: [';', ':'],
187: ['=', '+'],
188: [',', '<'],
189: ['-', '_'],
190: ['.', '>'],
191: ['/', '?'],
192: ['`', '~'],
219: ['[', '{'],
220: ['\\', '|'],
221: [']', '}'],
222: ['\'', '"']
};
export function evaluateKeyboardEvent(
ev: IKeyboardEvent,
applicationCursorMode: boolean,
isMac: boolean,
macOptionIsMeta: boolean
): IKeyboardResult {
const result: IKeyboardResult = {
type: KeyboardResultType.SEND_KEY,
// Whether to cancel event propogation (NOTE: this may not be needed since the event is
// canceled at the end of keyDown
cancel: false,
// The new key even to emit
key: undefined
};
const modifiers = (ev.shiftKey ? 1 : 0) | (ev.altKey ? 2 : 0) | (ev.ctrlKey ? 4 : 0) | (ev.metaKey ? 8 : 0);
switch (ev.keyCode) {
case 0:
if (ev.key === 'UIKeyInputUpArrow') {
if (applicationCursorMode) {
result.key = C0.ESC + 'OA';
} else {
result.key = C0.ESC + '[A';
}
}
else if (ev.key === 'UIKeyInputLeftArrow') {
if (applicationCursorMode) {
result.key = C0.ESC + 'OD';
} else {
result.key = C0.ESC + '[D';
}
}
else if (ev.key === 'UIKeyInputRightArrow') {
if (applicationCursorMode) {
result.key = C0.ESC + 'OC';
} else {
result.key = C0.ESC + '[C';
}
}
else if (ev.key === 'UIKeyInputDownArrow') {
if (applicationCursorMode) {
result.key = C0.ESC + 'OB';
} else {
result.key = C0.ESC + '[B';
}
}
break;
case 8:
// backspace
if (ev.shiftKey) {
result.key = C0.BS; // ^H
break;
} else if (ev.altKey) {
result.key = C0.ESC + C0.DEL; // \e ^?
break;
}
result.key = C0.DEL; // ^?
break;
case 9:
// tab
if (ev.shiftKey) {
result.key = C0.ESC + '[Z';
break;
}
result.key = C0.HT;
result.cancel = true;
break;
case 13:
// return/enter
result.key = C0.CR;
result.cancel = true;
break;
case 27:
// escape
result.key = C0.ESC;
result.cancel = true;
break;
case 37:
// left-arrow
if (modifiers) {
result.key = C0.ESC + '[1;' + (modifiers + 1) + 'D';
// HACK: Make Alt + left-arrow behave like Ctrl + left-arrow: move one word backwards
// http://unix.stackexchange.com/a/108106
// macOS uses different escape sequences than linux
if (result.key === C0.ESC + '[1;3D') {
result.key = isMac ? C0.ESC + 'b' : C0.ESC + '[1;5D';
}
} else if (applicationCursorMode) {
result.key = C0.ESC + 'OD';
} else {
result.key = C0.ESC + '[D';
}
break;
case 39:
// right-arrow
if (modifiers) {
result.key = C0.ESC + '[1;' + (modifiers + 1) + 'C';
// HACK: Make Alt + right-arrow behave like Ctrl + right-arrow: move one word forward
// http://unix.stackexchange.com/a/108106
// macOS uses different escape sequences than linux
if (result.key === C0.ESC + '[1;3C') {
result.key = isMac ? C0.ESC + 'f' : C0.ESC + '[1;5C';
}
} else if (applicationCursorMode) {
result.key = C0.ESC + 'OC';
} else {
result.key = C0.ESC + '[C';
}
break;
case 38:
// up-arrow
if (modifiers) {
result.key = C0.ESC + '[1;' + (modifiers + 1) + 'A';
// HACK: Make Alt + up-arrow behave like Ctrl + up-arrow
// http://unix.stackexchange.com/a/108106
if (result.key === C0.ESC + '[1;3A') {
result.key = C0.ESC + '[1;5A';
}
} else if (applicationCursorMode) {
result.key = C0.ESC + 'OA';
} else {
result.key = C0.ESC + '[A';
}
break;
case 40:
// down-arrow
if (modifiers) {
result.key = C0.ESC + '[1;' + (modifiers + 1) + 'B';
// HACK: Make Alt + down-arrow behave like Ctrl + down-arrow
// http://unix.stackexchange.com/a/108106
if (result.key === C0.ESC + '[1;3B') {
result.key = C0.ESC + '[1;5B';
}
} else if (applicationCursorMode) {
result.key = C0.ESC + 'OB';
} else {
result.key = C0.ESC + '[B';
}
break;
case 45:
// insert
if (!ev.shiftKey && !ev.ctrlKey) {
// <Ctrl> or <Shift> + <Insert> are used to
// copy-paste on some systems.
result.key = C0.ESC + '[2~';
}
break;
case 46:
// delete
if (modifiers) {
result.key = C0.ESC + '[3;' + (modifiers + 1) + '~';
} else {
result.key = C0.ESC + '[3~';
}
break;
case 36:
// home
if (modifiers) {
result.key = C0.ESC + '[1;' + (modifiers + 1) + 'H';
} else if (applicationCursorMode) {
result.key = C0.ESC + 'OH';
} else {
result.key = C0.ESC + '[H';
}
break;
case 35:
// end
if (modifiers) {
result.key = C0.ESC + '[1;' + (modifiers + 1) + 'F';
} else if (applicationCursorMode) {
result.key = C0.ESC + 'OF';
} else {
result.key = C0.ESC + '[F';
}
break;
case 33:
// page up
if (ev.shiftKey) {
result.type = KeyboardResultType.PAGE_UP;
} else {
result.key = C0.ESC + '[5~';
}
break;
case 34:
// page down
if (ev.shiftKey) {
result.type = KeyboardResultType.PAGE_DOWN;
} else {
result.key = C0.ESC + '[6~';
}
break;
case 112:
// F1-F12
if (modifiers) {
result.key = C0.ESC + '[1;' + (modifiers + 1) + 'P';
} else {
result.key = C0.ESC + 'OP';
}
break;
case 113:
if (modifiers) {
result.key = C0.ESC + '[1;' + (modifiers + 1) + 'Q';
} else {
result.key = C0.ESC + 'OQ';
}
break;
case 114:
if (modifiers) {
result.key = C0.ESC + '[1;' + (modifiers + 1) + 'R';
} else {
result.key = C0.ESC + 'OR';
}
break;
case 115:
if (modifiers) {
result.key = C0.ESC + '[1;' + (modifiers + 1) + 'S';
} else {
result.key = C0.ESC + 'OS';
}
break;
case 116:
if (modifiers) {
result.key = C0.ESC + '[15;' + (modifiers + 1) + '~';
} else {
result.key = C0.ESC + '[15~';
}
break;
case 117:
if (modifiers) {
result.key = C0.ESC + '[17;' + (modifiers + 1) + '~';
} else {
result.key = C0.ESC + '[17~';
}
break;
case 118:
if (modifiers) {
result.key = C0.ESC + '[18;' + (modifiers + 1) + '~';
} else {
result.key = C0.ESC + '[18~';
}
break;
case 119:
if (modifiers) {
result.key = C0.ESC + '[19;' + (modifiers + 1) + '~';
} else {
result.key = C0.ESC + '[19~';
}
break;
case 120:
if (modifiers) {
result.key = C0.ESC + '[20;' + (modifiers + 1) + '~';
} else {
result.key = C0.ESC + '[20~';
}
break;
case 121:
if (modifiers) {
result.key = C0.ESC + '[21;' + (modifiers + 1) + '~';
} else {
result.key = C0.ESC + '[21~';
}
break;
case 122:
if (modifiers) {
result.key = C0.ESC + '[23;' + (modifiers + 1) + '~';
} else {
result.key = C0.ESC + '[23~';
}
break;
case 123:
if (modifiers) {
result.key = C0.ESC + '[24;' + (modifiers + 1) + '~';
} else {
result.key = C0.ESC + '[24~';
}
break;
default:
// a-z and space
if (ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey) {
if (ev.keyCode >= 65 && ev.keyCode <= 90) {
result.key = String.fromCharCode(ev.keyCode - 64);
} else if (ev.keyCode === 32) {
// NUL
result.key = String.fromCharCode(0);
} else if (ev.keyCode >= 51 && ev.keyCode <= 55) {
// escape, file sep, group sep, record sep, unit sep
result.key = String.fromCharCode(ev.keyCode - 51 + 27);
} else if (ev.keyCode === 56) {
// delete
result.key = String.fromCharCode(127);
} else if (ev.keyCode === 219) {
// ^[ - Control Sequence Introducer (CSI)
result.key = String.fromCharCode(27);
} else if (ev.keyCode === 220) {
// ^\ - String Terminator (ST)
result.key = String.fromCharCode(28);
} else if (ev.keyCode === 221) {
// ^] - Operating System Command (OSC)
result.key = String.fromCharCode(29);
}
} else if ((!isMac || macOptionIsMeta) && ev.altKey && !ev.metaKey) {
// On macOS this is a third level shift when !macOptionIsMeta. Use <Esc> instead.
const keyMapping = KEYCODE_KEY_MAPPINGS[ev.keyCode];
const key = keyMapping && keyMapping[!ev.shiftKey ? 0 : 1];
if (key) {
result.key = C0.ESC + key;
} else if (ev.keyCode >= 65 && ev.keyCode <= 90) {
const keyCode = ev.ctrlKey ? ev.keyCode - 64 : ev.keyCode + 32;
result.key = C0.ESC + String.fromCharCode(keyCode);
}
} else if (isMac && !ev.altKey && !ev.ctrlKey && ev.metaKey) {
if (ev.keyCode === 65) { // cmd + a
result.type = KeyboardResultType.SELECT_ALL;
}
}
break;
}
return result;
}
+11 -11
View File
@@ -4,7 +4,7 @@
*/
import { ITerminal, ICircularList, LineData } from '../Types';
import { C0 } from '../common/data/EscapeSequences';
import { C0 } from '../EscapeSequences';
const enum Direction {
UP = 'A',
@@ -28,7 +28,7 @@ export class AltClickHandler {
this._startCol = this._terminal.buffer.x;
this._startRow = this._terminal.buffer.y;
const coordinates = this._terminal.mouseHelper.getCoords(
let coordinates = this._terminal.mouseHelper.getCoords(
this._mouseEvent,
this._terminal.element,
this._terminal.charMeasure,
@@ -89,10 +89,10 @@ export class AltClickHandler {
* ignoring wrapped rows
*/
private _moveToRequestedRow(): string {
const startRow = this._startRow - this._wrappedRowsForRow(this._startRow);
const endRow = this._endRow - this._wrappedRowsForRow(this._endRow);
let startRow = this._startRow - this._wrappedRowsForRow(this._startRow);
let endRow = this._endRow - this._wrappedRowsForRow(this._endRow);
const rowsToMove = Math.abs(startRow - endRow) - this._wrappedRowsCount();
let rowsToMove = Math.abs(startRow - endRow) - this._wrappedRowsCount();
return repeat(rowsToMove, this._sequence(this._verticalDirection()));
}
@@ -108,8 +108,8 @@ export class AltClickHandler {
startRow = this._startRow;
}
const endRow = this._endRow;
const direction = this._horizontalDirection();
let endRow = this._endRow;
let direction = this._horizontalDirection();
return repeat(this._bufferLine(
this._startCol, startRow, this._endCol, endRow,
@@ -118,7 +118,7 @@ export class AltClickHandler {
}
private _moveHorizontallyOnly(): string {
const direction = this._horizontalDirection();
let direction = this._horizontalDirection();
return repeat(Math.abs(this._startCol - this._endCol), this._sequence(direction));
}
@@ -132,11 +132,11 @@ export class AltClickHandler {
*/
private _wrappedRowsCount(): number {
let wrappedRows = 0;
const startRow = this._startRow - this._wrappedRowsForRow(this._startRow);
const endRow = this._endRow - this._wrappedRowsForRow(this._endRow);
let startRow = this._startRow - this._wrappedRowsForRow(this._startRow);
let endRow = this._endRow - this._wrappedRowsForRow(this._endRow);
for (let i = 0; i < Math.abs(startRow - endRow); i++) {
const direction = this._verticalDirection() === Direction.UP ? -1 : 1;
let direction = this._verticalDirection() === Direction.UP ? -1 : 1;
if ((<any>this._lines.get(startRow + (direction * i))).isWrapped) {
wrappedRows++;
+1 -1
View File
@@ -58,7 +58,7 @@ export function pasteHandler(ev: ClipboardEvent, term: ITerminal): void {
let text: string;
const dispatchPaste = function(text: string): void {
let dispatchPaste = function(text: string): void {
text = prepareTextForTerminal(text);
text = bracketTextForPaste(text, term.bracketedPasteMode);
term.handler(text);
@@ -5,8 +5,6 @@
import { ITerminal } from '../Types';
import { IMouseZoneManager, IMouseZone } from './Types';
import { Disposable } from '../common/Lifecycle';
import { addDisposableDomListener } from './Lifecycle';
const HOVER_DURATION = 500;
@@ -18,7 +16,7 @@ const HOVER_DURATION = 500;
* needed to support was single-line links which never overlap. Improvements can
* be made in the future.
*/
export class MouseZoneManager extends Disposable implements IMouseZoneManager {
export class MouseZoneManager implements IMouseZoneManager {
private _zones: IMouseZone[] = [];
private _areZonesActive: boolean = false;
@@ -32,20 +30,13 @@ export class MouseZoneManager extends Disposable implements IMouseZoneManager {
constructor(
private _terminal: ITerminal
) {
super();
this.register(addDisposableDomListener(this._terminal.element, 'mousedown', e => this._onMouseDown(e)));
this._terminal.element.addEventListener('mousedown', e => this._onMouseDown(e));
// These events are expensive, only listen to it when mouse zones are active
this._mouseMoveListener = e => this._onMouseMove(e);
this._clickListener = e => this._onClick(e);
}
public dispose(): void {
super.dispose();
this._deactivate();
}
public add(zone: IMouseZone): void {
this._zones.push(zone);
if (this._zones.length === 1) {
+1 -3
View File
@@ -3,9 +3,7 @@
* @license MIT
*/
import { IDisposable } from 'xterm';
export interface IMouseZoneManager extends IDisposable {
export interface IMouseZoneManager {
add(zone: IMouseZone): void;
clearAll(start?: number, end?: number): void;
}
-17
View File
@@ -1,17 +0,0 @@
/**
* Copyright (c) 2016 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { assert } from 'chai';
import { Terminal } from './Terminal';
import * as attach from '../addons/attach/attach';
describe('Terminal', () => {
it('should apply addons with Terminal.applyAddon', () => {
Terminal.applyAddon(attach);
// Test that addon was applied successfully, adding attach to Terminal's
// prototype.
assert.equal(typeof (<any>Terminal).prototype.attach, 'function');
});
});
-148
View File
@@ -1,148 +0,0 @@
/**
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings } from 'xterm';
import { ITerminal } from '../Types';
import { Terminal as TerminalCore } from '../Terminal';
import * as Strings from '../Strings';
export class Terminal implements ITerminalApi {
private _core: ITerminal;
constructor(options?: ITerminalOptions) {
this._core = new TerminalCore(options);
}
public get element(): HTMLElement { return this._core.element; }
public get textarea(): HTMLTextAreaElement { return this._core.textarea; }
public get rows(): number { return this._core.rows; }
public get cols(): number { return this._core.cols; }
public get markers(): IMarker[] { return this._core.markers; }
public blur(): void {
this._core.blur();
}
public focus(): void {
this._core.focus();
}
public on(type: 'blur' | 'focus' | 'linefeed' | 'selection', listener: () => void): void;
public on(type: 'data', listener: (...args: any[]) => void): void;
public on(type: 'key', listener: (key?: string, event?: KeyboardEvent) => void): void;
public on(type: 'keypress' | 'keydown', listener: (event?: KeyboardEvent) => void): void;
public on(type: 'refresh', listener: (data?: { start: number; end: number; }) => void): void;
public on(type: 'resize', listener: (data?: { cols: number; rows: number; }) => void): void;
public on(type: 'scroll', listener: (ydisp?: number) => void): void;
public on(type: 'title', listener: (title?: string) => void): void;
public on(type: string, listener: (...args: any[]) => void): void;
public on(type: any, listener: any): void {
this._core.on(type, listener);
}
public off(type: string, listener: (...args: any[]) => void): void {
this._core.off(type, listener);
}
public emit(type: string, data?: any): void {
this._core.emit(type, data);
}
public addDisposableListener(type: string, handler: (...args: any[]) => void): IDisposable {
return this._core.addDisposableListener(type, handler);
}
public resize(columns: number, rows: number): void {
this._core.resize(columns, rows);
}
public writeln(data: string): void {
this._core.writeln(data);
}
public open(parent: HTMLElement): void {
this._core.open(parent);
}
public attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void {
this._core.attachCustomKeyEventHandler(customKeyEventHandler);
}
public registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions): number {
return this._core.registerLinkMatcher(regex, handler, options);
}
public deregisterLinkMatcher(matcherId: number): void {
this._core.deregisterLinkMatcher(matcherId);
}
public addMarker(cursorYOffset: number): IMarker {
return this._core.addMarker(cursorYOffset);
}
public hasSelection(): boolean {
return this._core.hasSelection();
}
public getSelection(): string {
return this._core.getSelection();
}
public clearSelection(): void {
this._core.clearSelection();
}
public selectAll(): void {
this._core.selectAll();
}
public selectLines(start: number, end: number): void {
this._core.selectLines(start, end);
}
public dispose(): void {
this._core.dispose();
}
public destroy(): void {
this._core.destroy();
}
public scrollLines(amount: number): void {
this._core.scrollLines(amount);
}
public scrollPages(pageCount: number): void {
this._core.scrollPages(pageCount);
}
public scrollToTop(): void {
this._core.scrollToTop();
}
public scrollToBottom(): void {
this._core.scrollToBottom();
}
public scrollToLine(line: number): void {
this._core.scrollToLine(line);
}
public clear(): void {
this._core.clear();
}
public write(data: string): void {
this._core.write(data);
}
public getOption(key: 'bellSound' | 'bellStyle' | 'cursorStyle' | 'fontFamily' | 'fontWeight' | 'fontWeightBold' | 'rendererType' | 'termName'): string;
public getOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'debug' | 'disableStdin' | 'enableBold' | 'macOptionIsMeta' | 'rightClickSelectsWord' | 'popOnBell' | 'screenKeys' | 'useFlowControl' | 'visualBell'): boolean;
public getOption(key: 'colors'): string[];
public getOption(key: 'cols' | 'fontSize' | 'letterSpacing' | 'lineHeight' | 'rows' | 'tabStopWidth' | 'scrollback'): number;
public getOption(key: 'handler'): (data: string) => void;
public getOption(key: string): any;
public getOption(key: any): any {
return this._core.getOption(key);
}
public setOption(key: 'bellSound' | 'fontFamily' | 'termName', value: string): void;
public setOption(key: 'fontWeight' | 'fontWeightBold', value: 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900'): void;
public setOption(key: 'bellStyle', value: 'none' | 'visual' | 'sound' | 'both'): void;
public setOption(key: 'cursorStyle', value: 'block' | 'underline' | 'bar'): void;
public setOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'debug' | 'disableStdin' | 'enableBold' | 'macOptionIsMeta' | 'rightClickSelectsWord' | 'popOnBell' | 'screenKeys' | 'useFlowControl' | 'visualBell', value: boolean): void;
public setOption(key: 'colors', value: string[]): void;
public setOption(key: 'fontSize' | 'letterSpacing' | 'lineHeight' | 'tabStopWidth' | 'scrollback', value: number): void;
public setOption(key: 'handler', value: (data: string) => void): void;
public setOption(key: 'theme', value: ITheme): void;
public setOption(key: 'cols' | 'rows', value: number): void;
public setOption(key: string, value: any): void;
public setOption(key: any, value: any): void {
this._core.setOption(key, value);
}
public refresh(start: number, end: number): void {
this._core.refresh(start, end);
}
public reset(): void {
this._core.reset();
}
public static applyAddon(addon: any): void {
addon.apply(Terminal);
}
public static get strings(): ILocalizableStrings {
return Strings;
}
}
+1 -1
View File
@@ -49,7 +49,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
public onFocus(terminal: ITerminal): void {}
public onCursorMove(terminal: ITerminal): void {}
public onGridChanged(terminal: ITerminal, startRow: number, endRow: number): void {}
public onSelectionChanged(terminal: ITerminal, start: [number, number], end: [number, number], columnSelectMode: boolean = false): void {}
public onSelectionChanged(terminal: ITerminal, start: [number, number], end: [number, number]): void {}
public onThemeChanged(terminal: ITerminal, colorSet: IColorSet): void {
this._refreshCharAtlas(terminal, colorSet);
+2 -2
View File
@@ -33,10 +33,10 @@ describe('ColorManager', () => {
describe('constructor', () => {
it('should fill all colors with values', () => {
for (const key of Object.keys(cm.colors)) {
for (let key of Object.keys(cm.colors)) {
if (key !== 'ansi') {
// A #rrggbb or rgba(...)
assert.ok((<any>cm.colors)[key].css.length >= 7);
assert.ok(cm.colors[key].css.length >= 7);
}
}
assert.equal(cm.colors.ansi.length, 256);
+2 -2
View File
@@ -42,7 +42,7 @@ export const DEFAULT_ANSI_COLORS = (() => {
// Fill in the remaining 240 ANSI colors.
// Generate colors (16-231)
const v = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff];
let v = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff];
for (let i = 0; i < 216; i++) {
const r = v[(i / 36) % 6 | 0];
const g = v[(i / 6) % 6 | 0];
@@ -75,7 +75,7 @@ function fromHex(css: string): IColor {
}
function toPaddedHex(c: number): string {
const s = c.toString(16);
let s = c.toString(16);
return s.length < 2 ? '0' + s : s;
}
+4 -6
View File
@@ -11,8 +11,8 @@ import { IRenderLayer, IColorSet, IRenderer, IRenderDimensions } from './Types';
import { ITerminal } from '../Types';
import { LinkRenderLayer } from './LinkRenderLayer';
import { EventEmitter } from '../EventEmitter';
import { RenderDebouncer } from '../ui/RenderDebouncer';
import { ScreenDprMonitor } from '../ui/ScreenDprMonitor';
import { RenderDebouncer } from '../utils/RenderDebouncer';
import { ScreenDprMonitor } from '../utils/ScreenDprMonitor';
import { ITheme } from 'xterm';
export class Renderer extends EventEmitter implements IRenderer {
@@ -62,14 +62,12 @@ export class Renderer extends EventEmitter implements IRenderer {
this._renderDebouncer = new RenderDebouncer(this._terminal, this._renderRows.bind(this));
this._screenDprMonitor = new ScreenDprMonitor();
this._screenDprMonitor.setListener(() => this.onWindowResize(window.devicePixelRatio));
this.register(this._screenDprMonitor);
// Detect whether IntersectionObserver is detected and enable renderer pause
// and resume based on terminal visibility if so
if ('IntersectionObserver' in window) {
const observer = new IntersectionObserver(e => this.onIntersectionChange(e[0]), {threshold: 0});
observer.observe(this._terminal.element);
this.register({ dispose: () => observer.disconnect() });
}
}
@@ -143,8 +141,8 @@ export class Renderer extends EventEmitter implements IRenderer {
this._runOperation(l => l.onFocus(this._terminal));
}
public onSelectionChanged(start: [number, number], end: [number, number], columnSelectMode: boolean = false): void {
this._runOperation(l => l.onSelectionChanged(this._terminal, start, end, columnSelectMode));
public onSelectionChanged(start: [number, number], end: [number, number]): void {
this._runOperation(l => l.onSelectionChanged(this._terminal, start, end));
}
public onCursorMove(): void {
+24 -56
View File
@@ -7,46 +7,39 @@ import { ITerminal } from '../Types';
import { IColorSet, IRenderDimensions } from './Types';
import { BaseRenderLayer } from './BaseRenderLayer';
interface ISelectionState {
start: [number, number];
end: [number, number];
columnSelectMode: boolean;
ydisp: number;
}
export class SelectionRenderLayer extends BaseRenderLayer {
private _state: ISelectionState;
private _state: {start: [number, number], end: [number, number]};
constructor(container: HTMLElement, zIndex: number, colors: IColorSet) {
super(container, 'selection', zIndex, true, colors);
this._clearState();
}
private _clearState(): void {
this._state = {
start: null,
end: null,
columnSelectMode: null,
ydisp: null
end: null
};
}
public resize(terminal: ITerminal, dim: IRenderDimensions): void {
super.resize(terminal, dim);
// Resizing the canvas discards the contents of the canvas so clear state
this._clearState();
this._state = {
start: null,
end: null
};
}
public reset(terminal: ITerminal): void {
if (this._state.start && this._state.end) {
this._clearState();
this._state = {
start: null,
end: null
};
this.clearAll();
}
}
public onSelectionChanged(terminal: ITerminal, start: [number, number], end: [number, number], columnSelectMode: boolean): void {
public onSelectionChanged(terminal: ITerminal, start: [number, number], end: [number, number]): void {
// Selection has not changed
if (!this._didStateChange(start, end, columnSelectMode, terminal.buffer.ydisp)) {
if (this._state.start === start || this._state.end === end) {
return;
}
@@ -69,50 +62,25 @@ export class SelectionRenderLayer extends BaseRenderLayer {
return;
}
// Draw first row
const startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0;
const startRowEndCol = viewportCappedStartRow === viewportCappedEndRow ? end[0] : terminal.cols;
this._ctx.fillStyle = this._colors.selection.css;
this.fillCells(startCol, viewportCappedStartRow, startRowEndCol - startCol, 1);
if (columnSelectMode) {
const startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0;
const width = end[0] - startCol;
const height = viewportCappedEndRow - viewportCappedStartRow + 1;
this.fillCells(startCol, viewportCappedStartRow, width, height);
} else {
// Draw first row
const startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0;
const startRowEndCol = viewportCappedStartRow === viewportCappedEndRow ? end[0] : terminal.cols;
this.fillCells(startCol, viewportCappedStartRow, startRowEndCol - startCol, 1);
// Draw middle rows
const middleRowsCount = Math.max(viewportCappedEndRow - viewportCappedStartRow - 1, 0);
this.fillCells(0, viewportCappedStartRow + 1, terminal.cols, middleRowsCount);
// Draw middle rows
const middleRowsCount = Math.max(viewportCappedEndRow - viewportCappedStartRow - 1, 0);
this.fillCells(0, viewportCappedStartRow + 1, terminal.cols, middleRowsCount);
// Draw final row
if (viewportCappedStartRow !== viewportCappedEndRow) {
// Only draw viewportEndRow if it's not the same as viewportStartRow
const endCol = viewportEndRow === viewportCappedEndRow ? end[0] : terminal.cols;
this.fillCells(0, viewportCappedEndRow, endCol, 1);
}
// Draw final row
if (viewportCappedStartRow !== viewportCappedEndRow) {
// Only draw viewportEndRow if it's not the same as viewportStartRow
const endCol = viewportEndRow === viewportCappedEndRow ? end[0] : terminal.cols;
this.fillCells(0, viewportCappedEndRow, endCol, 1);
}
// Save state for next render
this._state.start = [start[0], start[1]];
this._state.end = [end[0], end[1]];
this._state.columnSelectMode = columnSelectMode;
this._state.ydisp = terminal.buffer.ydisp;
}
private _didStateChange(start: [number, number], end: [number, number], columnSelectMode: boolean, ydisp: number): boolean {
return !this._areCoordinatesEqual(start, this._state.start) ||
!this._areCoordinatesEqual(end, this._state.end) ||
columnSelectMode !== this._state.columnSelectMode ||
ydisp !== this._state.ydisp;
}
private _areCoordinatesEqual(coord1: [number, number], coord2: [number, number]): boolean {
if (!coord1 || !coord2) {
return false;
}
return coord1[0] === coord2[0] && coord1[1] === coord2[1];
}
}
+1 -3
View File
@@ -208,9 +208,7 @@ export class TextRenderLayer extends BaseRenderLayer {
return;
}
if (this._charAtlas) {
this._charAtlas.beginFrame();
}
this._charAtlas.beginFrame();
this.clearCells(0, firstRow, terminal.cols, lastRow - firstRow + 1);
this._drawBackground(terminal, firstRow, lastRow);
+4 -9
View File
@@ -4,7 +4,7 @@
*/
import { ITerminal } from '../Types';
import { IEventEmitter, ITheme, IDisposable } from 'xterm';
import { IEventEmitter, ITheme } from 'xterm';
import { IColorSet } from '../shared/Types';
/**
@@ -20,22 +20,17 @@ export const enum FLAGS {
ITALIC = 64
}
/**
* Note that IRenderer implementations should emit the refresh event after
* rendering rows to the screen.
*/
export interface IRenderer extends IEventEmitter, IDisposable {
export interface IRenderer extends IEventEmitter {
dimensions: IRenderDimensions;
colorManager: IColorManager;
dispose(): void;
setTheme(theme: ITheme): IColorSet;
onWindowResize(devicePixelRatio: number): void;
onResize(cols: number, rows: number): void;
onCharSizeChanged(): void;
onBlur(): void;
onFocus(): void;
onSelectionChanged(start: [number, number], end: [number, number], columnSelectMode: boolean): void;
onSelectionChanged(start: [number, number], end: [number, number]): void;
onCursorMove(): void;
onOptionsChanged(): void;
clear(): void;
@@ -99,7 +94,7 @@ export interface IRenderLayer {
/**
* Calls when the selection changes.
*/
onSelectionChanged(terminal: ITerminal, start: [number, number], end: [number, number], columnSelectMode: boolean): void;
onSelectionChanged(terminal: ITerminal, start: [number, number], end: [number, number]): void;
/**
* Resize the render layer.
+1 -1
View File
@@ -9,7 +9,7 @@ import { ICharAtlasConfig } from '../../shared/atlas/Types';
export function generateConfig(scaledCharWidth: number, scaledCharHeight: number, terminal: ITerminal, colors: IColorSet): ICharAtlasConfig {
// null out some fields that don't matter
const clonedColors = <IColorSet>{
const clonedColors = {
foreground: colors.foreground,
background: colors.background,
cursor: null,
+1 -1
View File
@@ -11,7 +11,7 @@ interface ILinkedListNode<T> {
}
export default class LRUMap<T> {
private _map: { [key: string]: ILinkedListNode<T> } = {};
private _map = {};
private _head: ILinkedListNode<T> = null;
private _tail: ILinkedListNode<T> = null;
private _nodePool: ILinkedListNode<T>[] = [];
+1 -1
View File
@@ -26,7 +26,7 @@ export default class StaticCharAtlas extends BaseCharAtlas {
return canvas;
}
protected _doWarmUp(): void {
public _doWarmUp(): void {
const result = generateStaticCharAtlasTexture(window, this._canvasFactory, this._config);
if (result instanceof HTMLCanvasElement) {
this._texture = result;
-322
View File
@@ -1,322 +0,0 @@
/**
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { IRenderer, IRenderDimensions, IColorSet } from '../Types';
import { ITerminal } from '../../Types';
import { ITheme } from 'xterm';
import { EventEmitter } from '../../EventEmitter';
import { ColorManager } from '../ColorManager';
import { RenderDebouncer } from '../../ui/RenderDebouncer';
import { BOLD_CLASS, ITALIC_CLASS, CURSOR_CLASS, DomRendererRowFactory } from './DomRendererRowFactory';
const TERMINAL_CLASS_PREFIX = 'xterm-dom-renderer-owner-';
const ROW_CONTAINER_CLASS = 'xterm-rows';
const FG_CLASS_PREFIX = 'xterm-fg-';
const BG_CLASS_PREFIX = 'xterm-bg-';
const FOCUS_CLASS = 'xterm-focus';
const SELECTION_CLASS = 'xterm-selection';
let nextTerminalId = 1;
// TODO: Pull into an addon when TS composite projects allow easier sharing of code (not just
// interfaces) between core and addons
/**
* A fallback renderer for when canvas is slow. This is not meant to be
* particularly fast or feature complete, more just stable and usable for when
* canvas is not an option.
*/
export class DomRenderer extends EventEmitter implements IRenderer {
private _renderDebouncer: RenderDebouncer;
private _rowFactory: DomRendererRowFactory;
private _terminalClass: number = nextTerminalId++;
private _themeStyleElement: HTMLStyleElement;
private _dimensionsStyleElement: HTMLStyleElement;
private _rowContainer: HTMLElement;
private _rowElements: HTMLElement[] = [];
private _selectionContainer: HTMLElement;
public dimensions: IRenderDimensions;
public colorManager: ColorManager;
constructor(private _terminal: ITerminal, theme: ITheme | undefined) {
super();
const allowTransparency = this._terminal.options.allowTransparency;
this.colorManager = new ColorManager(document, allowTransparency);
this.setTheme(theme);
this._rowContainer = document.createElement('div');
this._rowContainer.classList.add(ROW_CONTAINER_CLASS);
this._rowContainer.style.lineHeight = 'normal';
this._rowContainer.setAttribute('aria-hidden', 'true');
this._refreshRowElements(this._terminal.rows, this._terminal.cols);
this._selectionContainer = document.createElement('div');
this._selectionContainer.classList.add(SELECTION_CLASS);
this._selectionContainer.setAttribute('aria-hidden', 'true');
this.dimensions = {
scaledCharWidth: null,
scaledCharHeight: null,
scaledCellWidth: null,
scaledCellHeight: null,
scaledCharLeft: null,
scaledCharTop: null,
scaledCanvasWidth: null,
scaledCanvasHeight: null,
canvasWidth: null,
canvasHeight: null,
actualCellWidth: null,
actualCellHeight: null
};
this._updateDimensions();
this._renderDebouncer = new RenderDebouncer(this._terminal, this._renderRows.bind(this));
this._rowFactory = new DomRendererRowFactory(document);
this._terminal.element.classList.add(TERMINAL_CLASS_PREFIX + this._terminalClass);
this._terminal.screenElement.appendChild(this._rowContainer);
this._terminal.screenElement.appendChild(this._selectionContainer);
}
private _updateDimensions(): void {
this.dimensions.scaledCharWidth = this._terminal.charMeasure.width * window.devicePixelRatio;
this.dimensions.scaledCharHeight = this._terminal.charMeasure.height * window.devicePixelRatio;
this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth;
this.dimensions.scaledCellHeight = this.dimensions.scaledCharHeight;
this.dimensions.scaledCharLeft = 0;
this.dimensions.scaledCharTop = 0;
this.dimensions.scaledCanvasWidth = this.dimensions.scaledCellWidth * this._terminal.cols;
this.dimensions.scaledCanvasHeight = this.dimensions.scaledCellHeight * this._terminal.rows;
this.dimensions.canvasWidth = this._terminal.charMeasure.width * this._terminal.cols;
this.dimensions.canvasHeight = this._terminal.charMeasure.height * this._terminal.rows;
this.dimensions.actualCellWidth = this._terminal.charMeasure.width;
this.dimensions.actualCellHeight = this._terminal.charMeasure.height;
this._rowElements.forEach(element => {
element.style.width = `${this.dimensions.canvasWidth}px`;
element.style.height = `${this._terminal.charMeasure.height}px`;
});
if (!this._dimensionsStyleElement) {
this._dimensionsStyleElement = document.createElement('style');
this._terminal.screenElement.appendChild(this._dimensionsStyleElement);
}
const styles =
`${this._terminalSelector} .${ROW_CONTAINER_CLASS} span {` +
` display: inline-block;` +
` height: 100%;` +
` vertical-align: top;` +
` width: ${this._terminal.charMeasure.width}px` +
`}`;
this._dimensionsStyleElement.innerHTML = styles;
this._selectionContainer.style.height = (<any>this._terminal)._viewportElement.style.height;
this._rowContainer.style.width = `${this.dimensions.canvasWidth}px`;
this._rowContainer.style.height = `${this.dimensions.canvasHeight}px`;
}
public setTheme(theme: ITheme | undefined): IColorSet {
if (theme) {
this.colorManager.setTheme(theme);
}
if (!this._themeStyleElement) {
this._themeStyleElement = document.createElement('style');
this._terminal.screenElement.appendChild(this._themeStyleElement);
}
// Base CSS
let styles =
`${this._terminalSelector} .${ROW_CONTAINER_CLASS} {` +
` color: ${this.colorManager.colors.foreground.css};` +
` background-color: ${this.colorManager.colors.background.css};` +
` font-family: ${this._terminal.getOption('fontFamily')};` +
` font-size: ${this._terminal.getOption('fontSize')}px;` +
`}`;
// Text styles
styles +=
`${this._terminalSelector} span:not(.${BOLD_CLASS}) {` +
` font-weight: ${this._terminal.options.fontWeight};` +
`}` +
`${this._terminalSelector} span.${BOLD_CLASS} {` +
` font-weight: ${this._terminal.options.fontWeightBold};` +
`}` +
`${this._terminalSelector} span.${ITALIC_CLASS} {` +
` font-style: italic;` +
`}`;
// Cursor
styles +=
`${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${CURSOR_CLASS} {` +
` background-color: ${this.colorManager.colors.cursor.css};` +
` color: ${this.colorManager.colors.cursorAccent.css};` +
`}` +
`${this._terminalSelector} .${ROW_CONTAINER_CLASS}:not(.${FOCUS_CLASS}) .${CURSOR_CLASS} {` +
` outline: 1px solid #fff;` +
` outline-offset: -1px;` +
`}`;
// Selection
styles +=
`${this._terminalSelector} .${SELECTION_CLASS} {` +
` position: absolute;` +
` top: 0;` +
` left: 0;` +
` z-index: 1;` +
` pointer-events: none;` +
`}` +
`${this._terminalSelector} .${SELECTION_CLASS} div {` +
` position: absolute;` +
` background-color: ${this.colorManager.colors.selection.css};` +
`}`;
// Colors
this.colorManager.colors.ansi.forEach((c, i) => {
styles +=
`${this._terminalSelector} .${FG_CLASS_PREFIX}${i} { color: ${c.css}; }` +
`${this._terminalSelector} .${BG_CLASS_PREFIX}${i} { background-color: ${c.css}; }`;
});
this._themeStyleElement.innerHTML = styles;
return this.colorManager.colors;
}
public onWindowResize(devicePixelRatio: number): void {
this._updateDimensions();
}
private _refreshRowElements(cols: number, rows: number): void {
// Add missing elements
for (let i = this._rowElements.length; i <= rows; i++) {
const row = document.createElement('div');
this._rowContainer.appendChild(row);
this._rowElements.push(row);
}
// Remove excess elements
while (this._rowElements.length > rows) {
this._rowContainer.removeChild(this._rowElements.pop());
}
}
public onResize(cols: number, rows: number): void {
this._refreshRowElements(cols, rows);
this._updateDimensions();
}
public onCharSizeChanged(): void {
this._updateDimensions();
}
public onBlur(): void {
this._rowContainer.classList.remove(FOCUS_CLASS);
}
public onFocus(): void {
this._rowContainer.classList.add(FOCUS_CLASS);
}
public onSelectionChanged(start: [number, number], end: [number, number], columnSelectMode: boolean): void {
// Remove all selections
while (this._selectionContainer.children.length) {
this._selectionContainer.removeChild(this._selectionContainer.children[0]);
}
// Selection does not exist
if (!start || !end) {
return;
}
// Translate from buffer position to viewport position
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);
// No need to draw the selection
if (viewportCappedStartRow >= this._terminal.rows || viewportCappedEndRow < 0) {
return;
}
// Create the selections
const documentFragment = document.createDocumentFragment();
const startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0;
if (columnSelectMode) {
documentFragment.appendChild(
this._createSelectionElement(viewportCappedStartRow, startCol, end[0], viewportCappedEndRow - viewportStartRow + 1)
);
} else {
// Draw first row
const endCol = viewportCappedStartRow === viewportCappedEndRow ? end[0] : this._terminal.cols;
documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow, startCol, endCol));
// Draw middle rows
const middleRowsCount = viewportCappedEndRow - viewportCappedStartRow - 1;
documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow + 1, 0, this._terminal.cols, middleRowsCount));
// Draw final row
if (viewportCappedStartRow !== viewportCappedEndRow) {
// Only draw viewportEndRow if it's not the same as viewporttartRow
const endCol = viewportEndRow === viewportCappedEndRow ? end[0] : this._terminal.cols;
documentFragment.appendChild(this._createSelectionElement(viewportCappedEndRow, 0, endCol));
}
}
this._selectionContainer.appendChild(documentFragment);
}
/**
* Creates a selection element at the specified position.
* @param row The row of the selection.
* @param colStart The start column.
* @param colEnd The end columns.
*/
private _createSelectionElement(row: number, colStart: number, colEnd: number, rowCount: number = 1): HTMLElement {
const element = document.createElement('div');
element.style.height = `${rowCount * this._terminal.charMeasure.height}px`;
element.style.top = `${row * this._terminal.charMeasure.height}px`;
element.style.left = `${colStart * this._terminal.charMeasure.width}px`;
element.style.width = `${this._terminal.charMeasure.width * (colEnd - colStart)}px`;
return element;
}
public onCursorMove(): void {
// No-op, the cursor is drawn when rows are drawn
}
public onOptionsChanged(): void {
// Force a refresh
this._updateDimensions();
this.setTheme(undefined);
this._terminal.refresh(0, this._terminal.rows - 1);
}
public clear(): void {
this._rowElements.forEach(e => e.innerHTML = '');
}
public refreshRows(start: number, end: number): void {
this._renderDebouncer.refresh(start, end);
}
private _renderRows(start: number, end: number): void {
const terminal = this._terminal;
const cursorAbsoluteY = terminal.buffer.ybase + terminal.buffer.y;
const cursorX = this._terminal.buffer.x;
for (let y = start; y <= end; y++) {
const rowElement = this._rowElements[y];
rowElement.innerHTML = '';
const row = y + terminal.buffer.ydisp;
const lineData = terminal.buffer.lines.get(row);
rowElement.appendChild(this._rowFactory.createRow(lineData, row === cursorAbsoluteY, cursorX, terminal.charMeasure.width, terminal.cols));
}
this._terminal.emit('refresh', {start, end});
}
private get _terminalSelector(): string {
return `.${TERMINAL_CLASS_PREFIX}${this._terminalClass}`;
}
}
@@ -1,156 +0,0 @@
/**
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
* @license MIT
*/
import jsdom = require('jsdom');
import { assert } from 'chai';
import { DomRendererRowFactory } from './DomRendererRowFactory';
import { LineData } from '../../Types';
import { DEFAULT_ATTR } from '../../Buffer';
import { FLAGS } from '../Types';
describe('DomRendererRowFactory', () => {
let dom: jsdom.JSDOM;
let rowFactory: DomRendererRowFactory;
let lineData: LineData;
beforeEach(() => {
dom = new jsdom.JSDOM('');
rowFactory = new DomRendererRowFactory(dom.window.document);
lineData = createEmptyLineData(2);
});
describe('createRow', () => {
it('should create an element for every character in the row', () => {
const fragment = rowFactory.createRow(lineData, false, 0, 5, 20);
assert.equal(getFragmentHtml(fragment),
'<span> </span>' +
'<span> </span>'
);
});
it('should set correct attributes for double width characters', () => {
lineData[0] = [DEFAULT_ATTR, '語', 2, '語'.charCodeAt(0)];
// There should be no element for the following "empty" cell
lineData[1] = [DEFAULT_ATTR, '', 0, undefined];
const fragment = rowFactory.createRow(lineData, false, 0, 5, 20);
assert.equal(getFragmentHtml(fragment),
'<span style="width: 10px;">語</span>'
);
});
it('should add class for cursor', () => {
const fragment = rowFactory.createRow(lineData, true, 0, 5, 20);
assert.equal(getFragmentHtml(fragment),
'<span class="xterm-cursor"> </span>' +
'<span> </span>'
);
});
it('should not render cells that go beyond the terminal\'s columns', () => {
lineData[0] = [DEFAULT_ATTR, 'a', 1, 'a'.charCodeAt(0)];
lineData[1] = [DEFAULT_ATTR, 'b', 1, 'b'.charCodeAt(0)];
const fragment = rowFactory.createRow(lineData, false, 0, 5, 1);
assert.equal(getFragmentHtml(fragment),
'<span>a</span>'
);
});
describe('attributes', () => {
it('should add class for bold', () => {
lineData[0] = [DEFAULT_ATTR | (FLAGS.BOLD << 18), 'a', 1, 'a'.charCodeAt(0)];
const fragment = rowFactory.createRow(lineData, false, 0, 5, 20);
assert.equal(getFragmentHtml(fragment),
'<span class="xterm-bold">a</span>' +
'<span> </span>'
);
});
it('should add class for italic', () => {
lineData[0] = [DEFAULT_ATTR | (FLAGS.ITALIC << 18), 'a', 1, 'a'.charCodeAt(0)];
const fragment = rowFactory.createRow(lineData, false, 0, 5, 20);
assert.equal(getFragmentHtml(fragment),
'<span class="xterm-italic">a</span>' +
'<span> </span>'
);
});
it('should add classes for 256 foreground colors', () => {
const defaultAttrNoFgColor = (0 << 9) | (256 << 0);
for (let i = 0; i < 256; i++) {
lineData[0] = [defaultAttrNoFgColor | (i << 9), 'a', 1, 'a'.charCodeAt(0)];
const fragment = rowFactory.createRow(lineData, false, 0, 5, 20);
assert.equal(getFragmentHtml(fragment),
`<span class="xterm-fg-${i}">a</span>` +
'<span> </span>'
);
}
});
it('should add classes for 256 background colors', () => {
const defaultAttrNoBgColor = (257 << 9) | (0 << 0);
for (let i = 0; i < 256; i++) {
lineData[0] = [defaultAttrNoBgColor | (i << 0), 'a', 1, 'a'.charCodeAt(0)];
const fragment = rowFactory.createRow(lineData, false, 0, 5, 20);
assert.equal(getFragmentHtml(fragment),
`<span class="xterm-bg-${i}">a</span>` +
'<span> </span>'
);
}
});
it('should correctly invert colors', () => {
lineData[0] = [(FLAGS.INVERSE << 18) | (2 << 9) | (1 << 0), 'a', 1, 'a'.charCodeAt(0)];
const fragment = rowFactory.createRow(lineData, false, 0, 5, 20);
assert.equal(getFragmentHtml(fragment),
'<span class="xterm-fg-1 xterm-bg-2">a</span>' +
'<span> </span>'
);
});
it('should correctly invert default fg color', () => {
lineData[0] = [(FLAGS.INVERSE << 18) | (257 << 9) | (1 << 0), 'a', 1, 'a'.charCodeAt(0)];
const fragment = rowFactory.createRow(lineData, false, 0, 5, 20);
assert.equal(getFragmentHtml(fragment),
'<span class="xterm-fg-1 xterm-bg-15">a</span>' +
'<span> </span>'
);
});
it('should correctly invert default bg color', () => {
lineData[0] = [(FLAGS.INVERSE << 18) | (1 << 9) | (256 << 0), 'a', 1, 'a'.charCodeAt(0)];
const fragment = rowFactory.createRow(lineData, false, 0, 5, 20);
assert.equal(getFragmentHtml(fragment),
'<span class="xterm-fg-0 xterm-bg-1">a</span>' +
'<span> </span>'
);
});
it('should turn bold fg text bright', () => {
for (let i = 0; i < 8; i++) {
lineData[0] = [(FLAGS.BOLD << 18) | (i << 9) | (256 << 0), 'a', 1, 'a'.charCodeAt(0)];
const fragment = rowFactory.createRow(lineData, false, 0, 5, 20);
assert.equal(getFragmentHtml(fragment),
`<span class="xterm-bold xterm-fg-${i + 8}">a</span>` +
'<span> </span>'
);
}
});
});
});
function getFragmentHtml(fragment: DocumentFragment): string {
const element = dom.window.document.createElement('div');
element.appendChild(fragment);
return element.innerHTML;
}
function createEmptyLineData(cols: number): LineData {
const lineData: LineData = [];
for (let i = 0; i < cols; i++) {
lineData.push([DEFAULT_ATTR, ' ', 1, 32 /* ' '.charCodeAt(0) */]);
}
return lineData;
}
});
-90
View File
@@ -1,90 +0,0 @@
/**
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { LineData } from '../../Types';
import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_ATTR_INDEX, CHAR_DATA_WIDTH_INDEX } from '../../Buffer';
import { FLAGS } from '../Types';
export const BOLD_CLASS = 'xterm-bold';
export const ITALIC_CLASS = 'xterm-italic';
export const CURSOR_CLASS = 'xterm-cursor';
export class DomRendererRowFactory {
constructor(
private _document: Document
) {
}
public createRow(lineData: LineData, isCursorRow: boolean, cursorX: number, cellWidth: number, cols: number): DocumentFragment {
const fragment = this._document.createDocumentFragment();
let colCount = 0;
for (let x = 0; x < lineData.length; x++) {
// Don't allow any buffer to the right to be displayed
if (colCount >= cols) {
continue;
}
const charData = lineData[x];
const char: string = charData[CHAR_DATA_CHAR_INDEX];
const attr: number = charData[CHAR_DATA_ATTR_INDEX];
const width: number = charData[CHAR_DATA_WIDTH_INDEX];
// The character to the left is a wide character, drawing is owned by the char at x-1
if (width === 0) {
continue;
}
const charElement = this._document.createElement('span');
if (width > 1) {
charElement.style.width = `${cellWidth * width}px`;
}
const flags = attr >> 18;
let bg = attr & 0x1ff;
let fg = (attr >> 9) & 0x1ff;
if (isCursorRow && x === cursorX) {
charElement.classList.add(CURSOR_CLASS);
}
// If inverse flag is on, the foreground should become the background.
if (flags & FLAGS.INVERSE) {
const temp = bg;
bg = fg;
fg = temp;
if (fg === 256) {
fg = 0;
}
if (bg === 257) {
bg = 15;
}
}
if (flags & FLAGS.BOLD) {
// Convert the FG color to the bold variant
if (fg < 8) {
fg += 8;
}
charElement.classList.add(BOLD_CLASS);
}
if (flags & FLAGS.ITALIC) {
charElement.classList.add(ITALIC_CLASS);
}
charElement.textContent = char;
if (fg !== 257) {
charElement.classList.add(`xterm-fg-${fg}`);
}
if (bg !== 256) {
charElement.classList.add(`xterm-bg-${bg}`);
}
fragment.appendChild(charElement);
colCount += width;
}
return fragment;
}
}
+3 -3
View File
@@ -6,7 +6,7 @@
import { FontWeight } from 'xterm';
import { CHAR_ATLAS_CELL_SPACING, ICharAtlasConfig } from './Types';
import { IColor } from '../Types';
import { isFirefox, isSafari } from '../utils/Browser';
import { isFirefox } from '../utils/Browser';
declare const Promise: any;
@@ -99,14 +99,14 @@ export function generateStaticCharAtlasTexture(context: Window, canvasFactory: (
// if support is lacking as drawImage works there too. Firefox is also
// included here as ImageBitmap appears both buggy and has horrible
// performance (tested on v55).
if (!('createImageBitmap' in context) || isFirefox || isSafari) {
if (!('createImageBitmap' in context) || isFirefox) {
// Don't attempt to clear background colors if createImageBitmap is not supported
if (canvas instanceof HTMLCanvasElement) {
// Just return the HTMLCanvas if it's a HTMLCanvasElement
return canvas;
}
// Transfer to an ImageBitmap is this is an OffscreenCanvas
return new Promise((r: (bitmap: ImageBitmap) => void) => r(canvas.transferToImageBitmap()));
return new Promise(r => r(canvas.transferToImageBitmap()));
}
const charAtlasImageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
-1
View File
@@ -8,7 +8,6 @@ const userAgent = (isNode) ? 'node' : navigator.userAgent;
const platform = (isNode) ? 'node' : navigator.platform;
export const isFirefox = !!~userAgent.indexOf('Firefox');
export const isSafari = /^((?!chrome|android).)*safari/i.test(userAgent);
export const isMSIE = !!~userAgent.indexOf('MSIE') || !!~userAgent.indexOf('Trident');
// Find the users platform. We use this to interpret the meta key
@@ -36,7 +36,7 @@ export class CircularList<T> extends EventEmitter implements ICircularList<T> {
// Reconstruct array, starting at index 0. Only transfer values from the
// indexes 0 to length.
const newArray = new Array<T>(newMaxLength);
let newArray = new Array<T>(newMaxLength);
for (let i = 0; i < Math.min(newMaxLength, this.length); i++) {
newArray[i] = this._array[this._getCyclicIndex(i)];
}
+1 -1
View File
@@ -39,7 +39,7 @@ describe('clone', () => {
});
it('should clone null values', () => {
const test: any = {
const test = {
a: null
};
+1 -1
View File
@@ -10,7 +10,7 @@ import { IDisposable } from 'xterm';
* @param type The event type.
* @param handler The handler for the listener.
*/
export function addDisposableDomListener(
export function addDisposableListener(
node: Element | Window | Document,
type: string,
handler: (e: any) => void,
@@ -3,8 +3,6 @@
* @license MIT
*/
import { Disposable } from '../common/Lifecycle';
export type ScreenDprListener = (newDevicePixelRatio?: number, oldDevicePixelRatio?: number) => void;
/**
@@ -17,7 +15,7 @@ export type ScreenDprListener = (newDevicePixelRatio?: number, oldDevicePixelRat
* The listener should fire on both window zoom changes and switching to a
* monitor with a different DPI.
*/
export class ScreenDprMonitor extends Disposable {
export class ScreenDprMonitor {
private _currentDevicePixelRatio: number;
private _outerListener: MediaQueryListListener;
private _listener: ScreenDprListener;
@@ -35,11 +33,6 @@ export class ScreenDprMonitor extends Disposable {
this._updateDpr();
}
public dispose(): void {
super.dispose();
this.clearListener();
}
private _updateDpr(): void {
// Clear listeners for old DPR
if (this._resolutionMediaMatchList) {
+4 -11
View File
@@ -107,6 +107,7 @@ export class MockTerminal implements ITerminal {
children: HTMLElement[];
cursorHidden: boolean;
cursorState: number;
defAttr: number;
scrollback: number;
buffers: IBufferSet;
buffer: IBuffer;
@@ -181,8 +182,9 @@ export class MockInputHandlingTerminal implements IInputHandlingTerminal {
insertMode: boolean;
wraparoundMode: boolean;
bracketedPasteMode: boolean;
defAttr: number;
curAttr: number;
savedCurAttr: number;
prefix: string;
savedCols: number;
x10Mouse: boolean;
vt200Mouse: boolean;
@@ -266,7 +268,7 @@ export class MockInputHandlingTerminal implements IInputHandlingTerminal {
throw new Error('Method not implemented.');
}
setOption(key: string, value: any): void {
(<any>this.options)[key] = value;
this.options[key] = value;
}
on(type: string, listener: XtermListener): void {
throw new Error('Method not implemented.');
@@ -301,9 +303,6 @@ export class MockBuffer implements IBuffer {
translateBufferLineToString(lineIndex: number, trimRight: boolean, startCol?: number, endCol?: number): string {
return Buffer.prototype.translateBufferLineToString.apply(this, arguments);
}
getWrappedRangeForLine(y: number): { first: number; last: number; } {
throw new Error('Method not implemented.');
}
nextStop(x?: number): number {
throw new Error('Method not implemented.');
}
@@ -316,9 +315,6 @@ export class MockBuffer implements IBuffer {
}
export class MockRenderer implements IRenderer {
dispose(): void {
throw new Error('Method not implemented.');
}
colorManager: IColorManager;
on(type: string, listener: XtermListener): void {
throw new Error('Method not implemented.');
@@ -347,9 +343,6 @@ export class MockRenderer implements IRenderer {
}
export class MockViewport implements IViewport {
dispose(): void {
throw new Error('Method not implemented.');
}
scrollBarWidth: number = 0;
onThemeChanged(colors: IColorSet): void {
throw new Error('Method not implemented.');
-5
View File
@@ -139,11 +139,6 @@
cursor: pointer;
}
.xterm.xterm-cursor-crosshair {
/* Column selection mode */
cursor: crosshair;
}
.xterm .xterm-accessibility,
.xterm .xterm-message {
position: absolute;
+1 -1
View File
@@ -5,6 +5,6 @@
* This file is the entry point for browserify.
*/
import { Terminal } from './public/Terminal';
import { Terminal } from './Terminal';
module.exports = Terminal;
+6 -9
View File
@@ -3,22 +3,19 @@
"module": "commonjs",
"target": "es5",
"lib": [
"dom",
"es5",
"scripthost",
"es2015.promise"
"DOM",
"ES5",
"ScriptHost",
"ES2015.Promise"
],
"rootDir": "src",
"outDir": "lib",
"sourceMap": true,
"removeComments": true,
"preserveWatchOutput": true,
"noUnusedLocals": true,
"noImplicitAny": true
"noUnusedLocals": true
},
"include": [
"src/**/*",
"typings/xterm.d.ts"
"src/**/*"
],
"exclude": [
"src/addons/**/*"
+2 -13
View File
@@ -31,7 +31,6 @@
"parameter"
],
"eofline": true,
"new-parens": true,
"no-duplicate-imports": true,
"no-eval": true,
"no-internal-module": true,
@@ -94,23 +93,13 @@
"naming-convention": [
true,
{"type": "default", "format": "camelCase", "leadingUnderscore": "forbid"},
{"type": "type", "format": "PascalCase"},
{"type": "class", "format": "PascalCase"},
{"type": "property", "modifiers": ["const"], "format": "UPPER_CASE"},
{"type": "member", "modifiers": ["protected"], "format": "camelCase", "leadingUnderscore": "allow"},
// TODO: Change allow to require when there aren't many PRs out
// {"type": "member", "modifiers": ["protected"], "format": "camelCase", "leadingUnderscore": "require"},
{"type": "member", "modifiers": ["private"], "format": "camelCase", "leadingUnderscore": "require"},
{"type": "variable", "modifiers": ["const"], "format": ["camelCase", "UPPER_CASE"]},
{"type": "interface", "prefix": "I"}
{"type": "property", "modifiers": ["public", "static", "const"], "format": "UPPER_CASE"}
],
"no-else-after-return": {
"options": "allow-else-if"
},
"prefer-const-enum": [
true
],
"prefer-const": true
]
}
}
+1 -32
View File
@@ -13,11 +13,6 @@ declare module 'xterm' {
*/
export type FontWeight = 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900';
/**
* A string representing a renderer type.
*/
export type RendererType = 'dom' | 'canvas';
/**
* An object containing start up options for the terminal.
*/
@@ -124,32 +119,6 @@ declare module 'xterm' {
*/
macOptionIsMeta?: boolean;
/**
* Whether holding a modifier key will force normal selection behavior,
* regardless of whether the terminal is in mouse events mode. This will
* also prevent mouse events from being emitted by the terminal. For example,
* this allows you to use xterm.js' regular selection inside tmux with
* mouse mode enabled.
*/
macOptionClickForcesSelection?: boolean;
/**
* (EXPERIMENTAL) The type of renderer to use, this allows using the
* fallback DOM renderer when canvas is too slow for the environment. The
* following features do not work when the DOM renderer is used:
*
* - Links
* - Line height
* - Letter spacing
* - Cursor blink
* - Cursor style
*
* This option is marked as experiemental because it will eventually be
* moved to an addon. You can only set this option in the constructor (not
* setOption).
*/
rendererType?: RendererType;
/**
* Whether to select the word under the cursor on right click, this is
* standard behavior in a lot of macOS applications.
@@ -561,7 +530,7 @@ declare module 'xterm' {
* Retrieves an option's value from the terminal.
* @param key The option key.
*/
getOption(key: 'bellSound' | 'bellStyle' | 'cursorStyle' | 'fontFamily' | 'fontWeight' | 'fontWeightBold'| 'rendererType' | 'termName'): string;
getOption(key: 'bellSound' | 'bellStyle' | 'cursorStyle' | 'fontFamily' | 'fontWeight' | 'fontWeightBold'| 'termName'): string;
/**
* Retrieves an option's value from the terminal.
* @param key The option key.