mirror of
https://github.com/wavetermdev/xterm.js.git
synced 2026-08-05 13:43:48 -07:00
Merge remote-tracking branch 'origin/master' into 426_alt_scroll
This commit is contained in:
@@ -1,12 +1,15 @@
|
||||
List of xterm.js contributors. Updated before every release.
|
||||
|
||||
Adrian Labbé <adrilabbelol@gmail.com>
|
||||
Aleksandr Andrienko <aandrienko@codenvy.com>
|
||||
Aleksandr Andriienko <oandriie@redhat.com>
|
||||
Alessandro Nadalin <alessandro.nadalin@gmail.com>
|
||||
Alexander Olsson <noseglid@gmail.com>
|
||||
Alexey Kontsevoy <alexey@gravitational.com>
|
||||
Andres Mejia <amejia004@gmail.com>
|
||||
Anish Athalye <me@anishathalye.com>
|
||||
Anthony Lapenna <lapenna.anthony@gmail.com>
|
||||
Antonin Stefanutti <antonin@stefanutti.fr>
|
||||
Antonis Kalipetis <akalipetis@sourcelair.com>
|
||||
Anton Skshidlevsky <meefik@gmail.com>
|
||||
Anton Yurovskykh <anton.yurovskykh@gmail.com>
|
||||
@@ -19,6 +22,7 @@ Bill Church <billchurch@users.noreply.github.com>
|
||||
Bob Reid <bobreid@Bobs-MacBook-Pro.local>
|
||||
bottleofwater <nison.mael+bottleofwater@gmail.com>
|
||||
Brian Mock <brian@mockbrian.com>
|
||||
Bruno Ribeiro <b.m.fernandes.ribeiro@gmail.com>
|
||||
Bruno Ribeito <b.m.fernandes.ribeiro@gmail.com>
|
||||
Carson Anderson <carson@betterservers.com>
|
||||
CHaBou <chabup@delean.fr>
|
||||
@@ -26,6 +30,7 @@ Christian Budde Christensen <budde377@gmail.com>
|
||||
Christof Marti <chrmarti@microsoft.com>
|
||||
Christopher Jeffrey <chjjeffrey@gmail.com>
|
||||
coderaiser <mnemonic.enemy@gmail.com>
|
||||
Damien Tournoud <damien@platform.sh>
|
||||
Dan Brown <jdanbrown@gmail.com>
|
||||
Daniel Griffen <daniel@griffen.io>
|
||||
Daniel Imms <daimms@microsoft.com>
|
||||
@@ -46,20 +51,25 @@ imoses <ido@twiggle.com>
|
||||
InDieTasten <indietasten@gmail.com>
|
||||
irokas <akasidiari@inf.uth.gr>
|
||||
Jakob Gillich <jakob@gillich.me>
|
||||
Jan Kuri <jkuri88@gmail.com>
|
||||
Jean Bruenn <himself@jeanbruenn.info>
|
||||
Jeremy Danyow <jedanyow@microsoft.com>
|
||||
Jianhui Zhao <jianhuizhao329@gmail.com>
|
||||
Joao Moreno <jomo@microsoft.com>
|
||||
Joao Moreno <mail@joaomoreno.com>
|
||||
Johannes Zellner <johannes@nebulon.de>
|
||||
Jon Masters <jon.masters@sky.com>
|
||||
Jörg Breitbart <jerch@rockborn.de>
|
||||
Justin Luk <jluk@users.noreply.github.com>
|
||||
Justin Mecham <justin@mecham.me>
|
||||
Kirill Merkushev <lanwen@yandex.ru>
|
||||
Krasimir Tsonev <krasimir@outset.ws>
|
||||
Luca <LucaT1@users.noreply.github.com>
|
||||
Lucian Buzzo <lucian.buzzo@gmail.com>
|
||||
Lukas Drgon <lukas.drgon@gmail.com>
|
||||
Maël Nison <nison.mael@gmail.com>
|
||||
Marc Dumais <marc.dumais@ericsson.com>
|
||||
Markus F.X.J. Oberhumer <markus@oberhumer.com>
|
||||
Martin Chloride <i@martincl2.me>
|
||||
Martin Koppehel <martin.koppehel@st.ovgu.de>
|
||||
Martin Wang <jiahaow@ca.ibm.com>
|
||||
@@ -75,10 +85,12 @@ Paris Kasidiaris <paris@sourcelair.com>
|
||||
Peter Baumgarten <me@peterbaumgarten.com>
|
||||
Rick Baker <rick@ricktbaker.com>
|
||||
runarberg <runar@greenqloud.com>
|
||||
Saad Malik <simfox3@gmail.com>
|
||||
Samuel Williams <samuel.williams@oriontransfer.co.nz>
|
||||
Saswat Das <saswatds@users.noreply.github.com>
|
||||
Saul Costa <saul@codevolve.com>
|
||||
Shuanglei Tao <tsl0922@gmail.com>
|
||||
sitzmar <sitzmar@gmail.com>
|
||||
Steven Silvester <steven.silvester@ieee.org>
|
||||
stuicey <stuicey@users.noreply.github.com>
|
||||
t-amqi <umberos@gmail.com>
|
||||
@@ -90,3 +102,4 @@ Tyler Jewell <tjewell@codenvy.com>
|
||||
Vincent Woo <me@vincentwoo.com>
|
||||
yutaka <shigure@refy.net>
|
||||
YuviPanda <yuvipanda@gmail.com>
|
||||
ZHAO Xudong <zxdong@gmail.com>
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
FROM node:6
|
||||
FROM node:8
|
||||
MAINTAINER Paris Kasidiaris <paris@sourcelair.com>
|
||||
|
||||
# Set the working directory
|
||||
|
||||
@@ -24,7 +24,7 @@ It enables applications to provide fully featured terminals to their users and c
|
||||
First you need to install the module, we ship exclusively through [npm](https://www.npmjs.com/) so you need that installed and then add xterm.js as a dependency by running:
|
||||
|
||||
```
|
||||
npm install
|
||||
npm install xterm
|
||||
```
|
||||
|
||||
To start using xterm.js on your browser, add the `xterm.js` and `xterm.css` to the head of your html page. Then create a `<div id="terminal"></div>` onto which xterm can attach itself.
|
||||
@@ -110,7 +110,7 @@ Xterm.js is used in several world-class applications to provide great terminal e
|
||||
- [**Selenoid UI**](https://github.com/aerokube/selenoid-ui): Simple UI for the scallable golang implementation of Selenium Hub named Selenoid. We use XTerm for streaming logs over websockets from docker containers.
|
||||
- [**Portainer**](https://portainer.io): Simple management UI for Docker.
|
||||
- [**SSHy**](https://github.com/stuicey/SSHy): HTML5 Based SSHv2 Web Client with E2E encryption utilising `xterm.js`, SJCL & websockets.
|
||||
- [**JupyterLab**](https://github.com/jupyterlab/jupyterlab): An extensible
|
||||
- [**JupyterLab**](https://github.com/jupyterlab/jupyterlab): An extensible
|
||||
computational environment for Jupyter, supporting interactive data science and scientific computing across all programming languages.
|
||||
- [**Theia**](https://github.com/theia-ide/theia): Theia is a cloud & desktop IDE framework implemented in TypeScript.
|
||||
- [**Opshell**](https://github.com/ricktbaker/opshell) Ops Helper tool to make life easier working with AWS instances across multiple organizations.
|
||||
@@ -126,32 +126,66 @@ computational environment for Jupyter, supporting interactive data science and s
|
||||
- [**Pisth**](https://github.com/ColdGrub1384/Pisth): An SFTP and SSH client for iOS
|
||||
- [**abstruse**](https://github.com/bleenco/abstruse): Abstruse CI is a continuous integration platform based on Node.JS and Docker.
|
||||
- [**Microsoft SQL Operations Studio**](https://github.com/Microsoft/sqlopsstudio): A data management tool that enables working with SQL Server, Azure SQL DB and SQL DW from Windows, macOS and Linux
|
||||
- [**FreeMAN**](https://github.com/matthew-matvei/freeman): A free, cross-platform file manager for power users
|
||||
|
||||
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
|
||||
|
||||
### Linux or macOS
|
||||
Xterm.js ships with a barebones demo implementation, designed for the development and evaluation of the library only. Exposing the demo to the public as is would introduce security risks for the host.
|
||||
|
||||
First, be sure that a C++ compiler such as GCC-C++ or Clang is installed, then run these commands:
|
||||
Below you can find instructions on how to run the demo on different platforms.
|
||||
|
||||
### SourceLair
|
||||
|
||||
SourceLair will run the demo and builder in parallel automatically. Just make sure to choose the "Node.js" project type, when cloning the xterm.js repo (or just use this shortcut; https://lair.io/xtermjs/xtermjs).
|
||||
|
||||
Then open your project's [Public URL](https://help.sourcelair.com/projects/the-public-url/) to access the demo.
|
||||
|
||||
### Docker
|
||||
|
||||
First, make sure you have Docker Engine 1.13.0 (or newer) and Docker Compose 1.10.0 (or newer). To run the demo and builder in parallel, run the following command in your terminal:
|
||||
|
||||
```
|
||||
docker-compose up
|
||||
```
|
||||
|
||||
Then open http://0.0.0.0:3000 in a web browser to access the demo. If you prefer a different port than `3000` to access the xterm.js demo, then set the `XTERMJS_PORT` environment variable to the desired port.
|
||||
|
||||
### Foreman (or other Procfile runner)
|
||||
|
||||
First, be sure that a C++ compiler such as GCC-C++ or Clang is installed, then run the following commands in your terminal:
|
||||
|
||||
```
|
||||
npm install
|
||||
npm start
|
||||
foreman start # Replace foreman with "honcho", "forego" etc. depending on your runner
|
||||
```
|
||||
|
||||
Then open http://0.0.0.0:3000 in a web browser.
|
||||
Then open http://0.0.0.0:3000 in a web browser to access the demo.
|
||||
|
||||
### Linux or macOS
|
||||
|
||||
First, be sure that a C++ compiler such as GCC-C++ or Clang is installed, then run the following commands in your terminal:
|
||||
|
||||
```
|
||||
npm install
|
||||
npm start # Run this in its own terminal
|
||||
npm run watch # Run this in its own terminal
|
||||
```
|
||||
|
||||
Then open http://0.0.0.0:3000 in a web browser to access the demo.
|
||||
|
||||
### Windows
|
||||
|
||||
First, ensure [node-gyp](https://github.com/nodejs/node-gyp) is installed and configured correctly, then run these commands.
|
||||
First, ensure [node-gyp](https://github.com/nodejs/node-gyp) is installed and configured correctly, then run the following commands in your terminal:
|
||||
|
||||
```
|
||||
npm install
|
||||
npm start
|
||||
npm start # Run this in its own terminal
|
||||
npm run watch # Run this in its own terminal
|
||||
```
|
||||
|
||||
Then open http://127.0.0.1:3000 in a web browser.
|
||||
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.*
|
||||
|
||||
@@ -175,6 +209,6 @@ To contribute either code, documentation or issues to xterm.js please read the [
|
||||
|
||||
If you contribute code to this project, you are implicitly allowing your code to be distributed under the MIT license. You are also implicitly verifying that all code is your original work.
|
||||
|
||||
Copyright (c) 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)
|
||||
|
||||
+10
-1
@@ -64,10 +64,19 @@
|
||||
<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 xterm.js evaluation purposes only. Exposing the demo to the public as is would introduce security risks for the host.</p>
|
||||
<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>
|
||||
|
||||
+18
-8
@@ -30,26 +30,31 @@ var terminalContainer = document.getElementById('terminal-container'),
|
||||
macOptionIsMeta: document.querySelector('#option-mac-option-is-meta'),
|
||||
scrollback: document.querySelector('#option-scrollback'),
|
||||
tabstopwidth: document.querySelector('#option-tabstopwidth'),
|
||||
bellStyle: document.querySelector('#option-bell-style')
|
||||
bellStyle: document.querySelector('#option-bell-style'),
|
||||
screenReaderMode: document.querySelector('#option-screen-reader-mode')
|
||||
},
|
||||
colsElement = document.getElementById('cols'),
|
||||
rowsElement = document.getElementById('rows');
|
||||
rowsElement = document.getElementById('rows'),
|
||||
paddingElement = document.getElementById('padding');
|
||||
|
||||
function setTerminalSize() {
|
||||
var cols = parseInt(colsElement.value, 10);
|
||||
var rows = parseInt(rowsElement.value, 10);
|
||||
var viewportElement = document.querySelector('.xterm-viewport');
|
||||
var scrollBarWidth = viewportElement.offsetWidth - viewportElement.clientWidth;
|
||||
var width = (cols * term.renderer.dimensions.actualCellWidth + 20 /*room for scrollbar*/).toString() + 'px';
|
||||
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.resize(cols, rows);
|
||||
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") {
|
||||
@@ -82,6 +87,9 @@ optionElements.scrollback.addEventListener('change', function () {
|
||||
optionElements.tabstopwidth.addEventListener('change', function () {
|
||||
term.setOption('tabStopWidth', parseInt(optionElements.tabstopwidth.value, 10));
|
||||
});
|
||||
optionElements.screenReaderMode.addEventListener('change', function () {
|
||||
term.setOption('screenReaderMode', optionElements.screenReaderMode.checked);
|
||||
});
|
||||
|
||||
createTerminal();
|
||||
|
||||
@@ -94,7 +102,8 @@ function createTerminal() {
|
||||
macOptionIsMeta: optionElements.macOptionIsMeta.enabled,
|
||||
cursorBlink: optionElements.cursorBlink.checked,
|
||||
scrollback: parseInt(optionElements.scrollback.value, 10),
|
||||
tabStopWidth: parseInt(optionElements.tabstopwidth.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) {
|
||||
@@ -119,6 +128,7 @@ function createTerminal() {
|
||||
setTimeout(function () {
|
||||
colsElement.value = term.cols;
|
||||
rowsElement.value = term.rows;
|
||||
paddingElement.value = 0;
|
||||
|
||||
// Set terminal size again to set the specific dimensions on the demo
|
||||
setTerminalSize();
|
||||
|
||||
@@ -7,3 +7,10 @@ services:
|
||||
- ./:/usr/src/app
|
||||
ports:
|
||||
- ${XTERMJS_PORT:3000}:3000
|
||||
command: ["npm", "start"]
|
||||
|
||||
watch:
|
||||
build: .
|
||||
volumes:
|
||||
- ./:/usr/src/app
|
||||
command: ["npm", "run", "watch"]
|
||||
|
||||
@@ -148,6 +148,7 @@ namespace methods_core {
|
||||
const r24: string = t.getOption('fontWeight');
|
||||
const r25: string = t.getOption('fontWeightBold');
|
||||
const r26: boolean = t.getOption('allowTransparency');
|
||||
const r27: boolean = t.getOption('rightClickSelectsWord');
|
||||
}
|
||||
{
|
||||
const t: Terminal = new Terminal();
|
||||
@@ -187,6 +188,7 @@ namespace methods_core {
|
||||
t.setOption('fontFamily', 'foo');
|
||||
t.setOption('theme', {background: '#ff0000'});
|
||||
t.setOption('macOptionIsMeta', true);
|
||||
t.setOption('rightClickSelectsWord', false);
|
||||
}
|
||||
}
|
||||
namespace scrolling {
|
||||
|
||||
@@ -192,6 +192,10 @@ gulp.task('webpack', ['build'], function() {
|
||||
.pipe(gulp.dest('demo/dist/'));
|
||||
});
|
||||
|
||||
gulp.task('watch', ['webpack'], () => {
|
||||
gulp.watch(['./src/*', './src/**/*'], ['webpack']);
|
||||
});
|
||||
|
||||
/**
|
||||
* Submit coverage results to coveralls.io
|
||||
*/
|
||||
|
||||
Generated
+3522
-4
File diff suppressed because it is too large
Load Diff
+4
-4
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "xterm",
|
||||
"description": "Full xterm terminal, in your browser",
|
||||
"version": "3.1.0-master",
|
||||
"version": "3.1.0",
|
||||
"ignore": [
|
||||
"demo",
|
||||
"test",
|
||||
@@ -67,7 +67,7 @@
|
||||
"nodemon": "1.10.2",
|
||||
"sorcery": "^0.10.0",
|
||||
"tslint": "^5.9.1",
|
||||
"typescript": "~2.4.0",
|
||||
"typescript": "~2.7.1",
|
||||
"vinyl-buffer": "^1.0.0",
|
||||
"vinyl-source-stream": "^1.1.0",
|
||||
"webpack": "^3.10.0",
|
||||
@@ -75,7 +75,6 @@
|
||||
"zmodem.js": "^0.1.5"
|
||||
},
|
||||
"scripts": {
|
||||
"prestart": "gulp webpack",
|
||||
"start": "node demo/app",
|
||||
"prestart-zmodem": "npm run build",
|
||||
"start-zmodem": "node build/addons/zmodem/demo/app",
|
||||
@@ -86,7 +85,8 @@
|
||||
"build": "gulp build",
|
||||
"prepublish": "npm run build",
|
||||
"coveralls": "gulp coveralls",
|
||||
"webpack": "gulp webpack"
|
||||
"webpack": "gulp webpack",
|
||||
"watch": "gulp watch"
|
||||
},
|
||||
"dependencies": {}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,287 @@
|
||||
/**
|
||||
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import * as Strings from './Strings';
|
||||
import { ITerminal, IBuffer } from './Types';
|
||||
import { isMac } from './shared/utils/Browser';
|
||||
import { RenderDebouncer } from './utils/RenderDebouncer';
|
||||
import { addDisposableListener } from './utils/Dom';
|
||||
import { IDisposable } from 'xterm';
|
||||
|
||||
const MAX_ROWS_TO_READ = 20;
|
||||
const ACTIVE_ITEM_ID_PREFIX = 'xterm-active-item-';
|
||||
|
||||
enum BoundaryPosition {
|
||||
Top,
|
||||
Bottom
|
||||
}
|
||||
|
||||
export class AccessibilityManager implements IDisposable {
|
||||
private _accessibilityTreeRoot: HTMLElement;
|
||||
private _rowContainer: HTMLElement;
|
||||
private _rowElements: HTMLElement[] = [];
|
||||
private _liveRegion: HTMLElement;
|
||||
private _liveRegionLineCount: number = 0;
|
||||
|
||||
private _renderRowsDebouncer: RenderDebouncer;
|
||||
|
||||
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
|
||||
* not announced (added to live region) because it has already been announced
|
||||
* by the textarea event (which cannot be canceled). There are some race
|
||||
* condition cases if there is typing while data is streaming, but this covers
|
||||
* the main case of typing into the prompt and inputting the answer to a
|
||||
* question (Y/N, etc.).
|
||||
*/
|
||||
private _charsToConsume: string[] = [];
|
||||
|
||||
constructor(private _terminal: ITerminal) {
|
||||
this._accessibilityTreeRoot = document.createElement('div');
|
||||
this._accessibilityTreeRoot.classList.add('xterm-accessibility');
|
||||
|
||||
this._rowContainer = document.createElement('div');
|
||||
this._rowContainer.classList.add('xterm-accessibility-tree');
|
||||
for (let i = 0; i < this._terminal.rows; i++) {
|
||||
this._rowElements[i] = this._createAccessibilityTreeNode();
|
||||
this._rowContainer.appendChild(this._rowElements[i]);
|
||||
}
|
||||
|
||||
this._topBoundaryFocusListener = e => this._onBoundaryFocus(e, BoundaryPosition.Top);
|
||||
this._bottomBoundaryFocusListener = e => this._onBoundaryFocus(e, BoundaryPosition.Bottom);
|
||||
this._rowElements[0].addEventListener('focus', this._topBoundaryFocusListener);
|
||||
this._rowElements[this._rowElements.length - 1].addEventListener('focus', this._bottomBoundaryFocusListener);
|
||||
|
||||
this._refreshRowsDimensions();
|
||||
this._accessibilityTreeRoot.appendChild(this._rowContainer);
|
||||
|
||||
this._renderRowsDebouncer = new RenderDebouncer(this._terminal, this._renderRows.bind(this));
|
||||
this._refreshRows();
|
||||
|
||||
this._liveRegion = document.createElement('div');
|
||||
this._liveRegion.classList.add('live-region');
|
||||
this._liveRegion.setAttribute('aria-live', 'assertive');
|
||||
this._accessibilityTreeRoot.appendChild(this._liveRegion);
|
||||
|
||||
this._terminal.element.insertAdjacentElement('afterbegin', this._accessibilityTreeRoot);
|
||||
|
||||
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._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._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._disposables.push(addDisposableListener(window, 'resize', () => this._refreshRowsDimensions()));
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._terminal.element.removeChild(this._accessibilityTreeRoot);
|
||||
this._disposables.forEach(d => d.dispose());
|
||||
this._disposables = null;
|
||||
this._accessibilityTreeRoot = null;
|
||||
this._rowContainer = null;
|
||||
this._liveRegion = null;
|
||||
this._rowContainer = null;
|
||||
this._rowElements = null;
|
||||
}
|
||||
|
||||
private _onBoundaryFocus(e: FocusEvent, position: BoundaryPosition): void {
|
||||
const boundaryElement = <HTMLElement>e.target;
|
||||
const beforeBoundaryElement = this._rowElements[position === BoundaryPosition.Top ? 1 : this._rowElements.length - 2];
|
||||
|
||||
// Don't scroll if the buffer top has reached the end in that direction
|
||||
const posInSet = boundaryElement.getAttribute('aria-posinset');
|
||||
const lastRowPos = position === BoundaryPosition.Top ? '1' : `${this._terminal.buffer.lines.length}`;
|
||||
if (posInSet === lastRowPos) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't scroll when the last focused item was not the second row (focus is going the other
|
||||
// direction)
|
||||
if (e.relatedTarget !== beforeBoundaryElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove old boundary element from array
|
||||
let topBoundaryElement: HTMLElement;
|
||||
let bottomBoundaryElement: HTMLElement;
|
||||
if (position === BoundaryPosition.Top) {
|
||||
topBoundaryElement = boundaryElement;
|
||||
bottomBoundaryElement = this._rowElements.pop();
|
||||
this._rowContainer.removeChild(bottomBoundaryElement);
|
||||
} else {
|
||||
topBoundaryElement = this._rowElements.shift();
|
||||
bottomBoundaryElement = boundaryElement;
|
||||
this._rowContainer.removeChild(topBoundaryElement);
|
||||
}
|
||||
|
||||
// Remove listeners from old boundary elements
|
||||
topBoundaryElement.removeEventListener('focus', this._topBoundaryFocusListener);
|
||||
bottomBoundaryElement.removeEventListener('focus', this._bottomBoundaryFocusListener);
|
||||
|
||||
// Add new element to array/DOM
|
||||
if (position === BoundaryPosition.Top) {
|
||||
const newElement = this._createAccessibilityTreeNode();
|
||||
this._rowElements.unshift(newElement);
|
||||
this._rowContainer.insertAdjacentElement('afterbegin', newElement);
|
||||
} else {
|
||||
const newElement = this._createAccessibilityTreeNode();
|
||||
this._rowElements.push(newElement);
|
||||
this._rowContainer.appendChild(newElement);
|
||||
}
|
||||
|
||||
// Add listeners to new boundary elements
|
||||
this._rowElements[0].addEventListener('focus', this._topBoundaryFocusListener);
|
||||
this._rowElements[this._rowElements.length - 1].addEventListener('focus', this._bottomBoundaryFocusListener);
|
||||
|
||||
// Scroll up
|
||||
this._terminal.scrollLines(position === BoundaryPosition.Top ? -1 : 1);
|
||||
|
||||
// Focus new boundary before element
|
||||
this._rowElements[position === BoundaryPosition.Top ? 1 : this._rowElements.length - 2].focus();
|
||||
|
||||
// Prevent the standard behavior
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
}
|
||||
|
||||
private _onResize(cols: number, rows: number): void {
|
||||
// Remove bottom boundary listener
|
||||
this._rowElements[this._rowElements.length - 1].removeEventListener('focus', this._bottomBoundaryFocusListener);
|
||||
|
||||
// Grow rows as required
|
||||
for (let i = this._rowContainer.children.length; i < this._terminal.rows; i++) {
|
||||
this._rowElements[i] = this._createAccessibilityTreeNode();
|
||||
this._rowContainer.appendChild(this._rowElements[i]);
|
||||
}
|
||||
// Shrink rows as required
|
||||
while (this._rowElements.length > rows) {
|
||||
this._rowContainer.removeChild(this._rowElements.pop());
|
||||
}
|
||||
|
||||
// Add bottom boundary listener
|
||||
this._rowElements[this._rowElements.length - 1].addEventListener('focus', this._bottomBoundaryFocusListener);
|
||||
|
||||
this._refreshRowsDimensions();
|
||||
}
|
||||
|
||||
public _createAccessibilityTreeNode(): HTMLElement {
|
||||
const element = document.createElement('div');
|
||||
element.setAttribute('role', 'listitem');
|
||||
element.tabIndex = -1;
|
||||
this._refreshRowDimensions(element);
|
||||
return element;
|
||||
}
|
||||
|
||||
private _onTab(spaceCount: number): void {
|
||||
for (let i = 0; i < spaceCount; i++) {
|
||||
this._onChar(' ');
|
||||
}
|
||||
}
|
||||
|
||||
private _onChar(char: string): void {
|
||||
if (this._liveRegionLineCount < MAX_ROWS_TO_READ + 1) {
|
||||
if (this._charsToConsume.length > 0) {
|
||||
// Have the screen reader ignore the char if it was just input
|
||||
const shiftedChar = this._charsToConsume.shift();
|
||||
if (shiftedChar !== char) {
|
||||
this._announceCharacter(char);
|
||||
}
|
||||
} else {
|
||||
this._announceCharacter(char);
|
||||
}
|
||||
|
||||
if (char === '\n') {
|
||||
this._liveRegionLineCount++;
|
||||
if (this._liveRegionLineCount === MAX_ROWS_TO_READ + 1) {
|
||||
this._liveRegion.textContent += Strings.tooMuchOutput;
|
||||
}
|
||||
}
|
||||
|
||||
// Only detach/attach on mac as otherwise messages can go unaccounced
|
||||
if (isMac) {
|
||||
if (this._liveRegion.textContent.length > 0 && !this._liveRegion.parentNode) {
|
||||
setTimeout(() => {
|
||||
this._accessibilityTreeRoot.appendChild(this._liveRegion);
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _clearLiveRegion(): void {
|
||||
this._liveRegion.textContent = '';
|
||||
this._liveRegionLineCount = 0;
|
||||
|
||||
// Only detach/attach on mac as otherwise messages can go unaccounced
|
||||
if (isMac) {
|
||||
if (this._liveRegion.parentNode) {
|
||||
this._accessibilityTreeRoot.removeChild(this._liveRegion);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _onKey(keyChar: string): void {
|
||||
this._clearLiveRegion();
|
||||
this._charsToConsume.push(keyChar);
|
||||
}
|
||||
|
||||
private _refreshRows(start?: number, end?: number): void {
|
||||
this._renderRowsDebouncer.refresh(start, end);
|
||||
}
|
||||
|
||||
private _renderRows(start: number, end: number): void {
|
||||
const buffer: IBuffer = this._terminal.buffer;
|
||||
const setSize = buffer.lines.length.toString();
|
||||
for (let i = start; i <= end; i++) {
|
||||
const lineData = buffer.translateBufferLineToString(buffer.ydisp + i, true);
|
||||
const posInSet = (buffer.ydisp + i + 1).toString();
|
||||
const element = this._rowElements[i];
|
||||
element.textContent = lineData.length === 0 ? Strings.blankLine : lineData;
|
||||
element.setAttribute('aria-posinset', posInSet);
|
||||
element.setAttribute('aria-setsize', setSize);
|
||||
}
|
||||
}
|
||||
|
||||
private _refreshRowsDimensions(): void {
|
||||
if (!this._terminal.renderer.dimensions.actualCellHeight) {
|
||||
return;
|
||||
}
|
||||
const buffer: IBuffer = this._terminal.buffer;
|
||||
for (let i = 0; i < this._terminal.rows; i++) {
|
||||
this._refreshRowDimensions(this._rowElements[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private _refreshRowDimensions(element: HTMLElement): void {
|
||||
element.style.height = `${this._terminal.renderer.dimensions.actualCellHeight}px`;
|
||||
}
|
||||
|
||||
private _announceCharacter(char: string): void {
|
||||
if (char === ' ') {
|
||||
// Always use nbsp for spaces in order to preserve the space between characters in
|
||||
// voiceover's caption window
|
||||
this._liveRegion.innerHTML += ' ';
|
||||
} else {
|
||||
this._liveRegion.textContent += char;
|
||||
}
|
||||
}
|
||||
}
|
||||
+25
-5
@@ -3,10 +3,11 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { IEventEmitter } from 'xterm';
|
||||
import { XtermListener } from './Types';
|
||||
import { IEventEmitter, IDisposable } from 'xterm';
|
||||
|
||||
export class EventEmitter implements IEventEmitter {
|
||||
private _events: {[type: string]: ((...args: any[]) => void)[]};
|
||||
private _events: {[type: string]: XtermListener[]};
|
||||
|
||||
constructor() {
|
||||
// Restore the previous events if available, this will happen if the
|
||||
@@ -14,12 +15,31 @@ export class EventEmitter implements IEventEmitter {
|
||||
this._events = this._events || {};
|
||||
}
|
||||
|
||||
public on(type: string, listener: ((...args: any[]) => void)): void {
|
||||
public on(type: string, listener: XtermListener): void {
|
||||
this._events[type] = this._events[type] || [];
|
||||
this._events[type].push(listener);
|
||||
}
|
||||
|
||||
public off(type: string, listener: ((...args: any[]) => void)): void {
|
||||
/**
|
||||
* Adds a disposabe listener to the EventEmitter, returning the disposable.
|
||||
* @param type The event type.
|
||||
* @param handler The handler for the listener.
|
||||
*/
|
||||
public addDisposableListener(type: string, handler: XtermListener): IDisposable {
|
||||
this.on(type, handler);
|
||||
return {
|
||||
dispose: () => {
|
||||
if (!handler) {
|
||||
// Already disposed
|
||||
return;
|
||||
}
|
||||
this.off(type, handler);
|
||||
handler = null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public off(type: string, listener: XtermListener): void {
|
||||
if (!this._events[type]) {
|
||||
return;
|
||||
}
|
||||
@@ -51,7 +71,7 @@ export class EventEmitter implements IEventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
public listeners(type: string): ((...args: any[]) => void)[] {
|
||||
public listeners(type: string): XtermListener[] {
|
||||
return this._events[type] || [];
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,10 @@ export class InputHandler implements IInputHandler {
|
||||
char = this._terminal.charset[char];
|
||||
}
|
||||
|
||||
if (this._terminal.options.screenReaderMode) {
|
||||
this._terminal.emit('a11y.char', char);
|
||||
}
|
||||
|
||||
let row = this._terminal.buffer.y + this._terminal.buffer.ybase;
|
||||
|
||||
// insert combining char in last cell
|
||||
@@ -162,7 +166,11 @@ export class InputHandler implements IInputHandler {
|
||||
* Horizontal Tab (HT) (Ctrl-I).
|
||||
*/
|
||||
public tab(): void {
|
||||
const originalX = this._terminal.buffer.x;
|
||||
this._terminal.buffer.x = this._terminal.buffer.nextStop();
|
||||
if (this._terminal.options.screenReaderMode) {
|
||||
this._terminal.emit('a11y.tab', this._terminal.buffer.x - originalX);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -150,6 +150,7 @@ export class Linkifier extends EventEmitter implements ILinkifier {
|
||||
validationCallback: options.validationCallback,
|
||||
hoverTooltipCallback: options.tooltipCallback,
|
||||
hoverLeaveCallback: options.leaveCallback,
|
||||
willLinkActivate: options.willLinkActivate,
|
||||
priority: options.priority || 0
|
||||
};
|
||||
this._addLinkMatcherToList(matcher);
|
||||
@@ -290,6 +291,12 @@ export class Linkifier extends EventEmitter implements ILinkifier {
|
||||
if (matcher.hoverLeaveCallback) {
|
||||
matcher.hoverLeaveCallback();
|
||||
}
|
||||
},
|
||||
e => {
|
||||
if (matcher.willLinkActivate) {
|
||||
return matcher.willLinkActivate(e, uri);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ class TestSelectionManager extends SelectionManager {
|
||||
public get model(): SelectionModel { return this._model; }
|
||||
|
||||
public selectLineAt(line: number): void { this._selectLineAt(line); }
|
||||
public selectWordAt(coords: [number, number]): void { this._selectWordAt(coords); }
|
||||
public selectWordAt(coords: [number, number]): void { this._selectWordAt(coords, true); }
|
||||
|
||||
// Disable DOM interaction
|
||||
public enable(): void {}
|
||||
|
||||
+45
-9
@@ -3,7 +3,7 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { ITerminal, ICircularList, ISelectionManager, IBuffer, LineData, CharData } from './Types';
|
||||
import { ITerminal, ICircularList, ISelectionManager, IBuffer, LineData, CharData, XtermListener } from './Types';
|
||||
import { MouseHelper } from './utils/MouseHelper';
|
||||
import * as Browser from './shared/utils/Browser';
|
||||
import { CharMeasure } from './utils/CharMeasure';
|
||||
@@ -101,7 +101,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
|
||||
|
||||
private _mouseMoveListener: EventListener;
|
||||
private _mouseUpListener: EventListener;
|
||||
private _trimListener: (...args: any[]) => void;
|
||||
private _trimListener: XtermListener;
|
||||
|
||||
private _mouseDownTimeStamp: number;
|
||||
|
||||
@@ -253,6 +253,37 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
|
||||
this.emit('refresh', { start: this._model.finalSelectionStart, end: this._model.finalSelectionEnd });
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current click was inside the current selection
|
||||
* @param event The mouse event
|
||||
*/
|
||||
public isClickInSelection(event: MouseEvent): boolean {
|
||||
const coords = this._getMouseBufferCoords(event);
|
||||
const start = this._model.finalSelectionStart;
|
||||
const end = this._model.finalSelectionEnd;
|
||||
|
||||
if (!start || !end) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (coords[1] > start[1] && coords[1] < end[1]) ||
|
||||
(start[1] === end[1] && coords[1] === start[1] && coords[0] > start[0] && coords[0] < end[0]) ||
|
||||
(start[1] < end[1] && coords[1] === end[1] && coords[0] < end[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects word at the current mouse event coordinates.
|
||||
* @param event The mouse event.
|
||||
*/
|
||||
public selectWordAtCursor(event: MouseEvent): void {
|
||||
const coords = this._getMouseBufferCoords(event);
|
||||
if (coords) {
|
||||
this._selectWordAt(coords, false);
|
||||
this._model.selectionEnd = null;
|
||||
this.refresh(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects all text within the terminal.
|
||||
*/
|
||||
@@ -278,7 +309,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
|
||||
* @param event The mouse event.
|
||||
*/
|
||||
private _getMouseBufferCoords(event: MouseEvent): [number, number] {
|
||||
const coords = this._terminal.mouseHelper.getCoords(event, this._terminal.element, this._charMeasure, this._terminal.options.lineHeight, this._terminal.cols, this._terminal.rows, true);
|
||||
const coords = this._terminal.mouseHelper.getCoords(event, this._terminal.screenElement, this._charMeasure, this._terminal.options.lineHeight, this._terminal.cols, this._terminal.rows, true);
|
||||
if (!coords) {
|
||||
return null;
|
||||
}
|
||||
@@ -298,7 +329,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
|
||||
* @param event The mouse event.
|
||||
*/
|
||||
private _getMouseEventScrollAmount(event: MouseEvent): number {
|
||||
let offset = MouseHelper.getCoordsRelativeToElement(event, this._terminal.element)[1];
|
||||
let offset = MouseHelper.getCoordsRelativeToElement(event, this._terminal.screenElement)[1];
|
||||
const terminalHeight = this._terminal.rows * Math.ceil(this._charMeasure.height * this._terminal.options.lineHeight);
|
||||
if (offset >= 0 && offset <= terminalHeight) {
|
||||
return 0;
|
||||
@@ -445,7 +476,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
|
||||
const coords = this._getMouseBufferCoords(event);
|
||||
if (coords) {
|
||||
this._activeSelectionMode = SelectionMode.WORD;
|
||||
this._selectWordAt(coords);
|
||||
this._selectWordAt(coords, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -602,7 +633,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
|
||||
* Gets positional information for the word at the coordinated specified.
|
||||
* @param coords The coordinates to get the word at.
|
||||
*/
|
||||
private _getWordAt(coords: [number, number]): IWordPosition {
|
||||
private _getWordAt(coords: [number, number], allowWhitespaceOnlySelection: boolean): IWordPosition {
|
||||
const bufferLine = this._buffer.lines.get(coords[1]);
|
||||
if (!bufferLine) {
|
||||
return null;
|
||||
@@ -708,15 +739,20 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
|
||||
- leftLongCharOffset // The number of additional chars left of the initial char added by columns with strings longer than 1 (emojis)
|
||||
- rightLongCharOffset); // The number of additional chars right of the initial char (inclusive) added by columns with strings longer than 1 (emojis)
|
||||
|
||||
if (!allowWhitespaceOnlySelection && line.slice(startIndex, endIndex).trim() === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return { start, length };
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects the word at the coordinates specified.
|
||||
* @param coords The coordinates to get the word at.
|
||||
* @param allowWhitespaceOnlySelection If whitespace should be selected
|
||||
*/
|
||||
protected _selectWordAt(coords: [number, number]): void {
|
||||
const wordPosition = this._getWordAt(coords);
|
||||
protected _selectWordAt(coords: [number, number], allowWhitespaceOnlySelection: boolean): void {
|
||||
const wordPosition = this._getWordAt(coords, allowWhitespaceOnlySelection);
|
||||
if (wordPosition) {
|
||||
this._model.selectionStart = [wordPosition.start, coords[1]];
|
||||
this._model.selectionStartLength = wordPosition.length;
|
||||
@@ -728,7 +764,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
|
||||
* @param coords The coordinates to get the word at.
|
||||
*/
|
||||
private _selectToWordAt(coords: [number, number]): void {
|
||||
const wordPosition = this._getWordAt(coords);
|
||||
const wordPosition = this._getWordAt(coords, true);
|
||||
if (wordPosition) {
|
||||
this._model.selectionEnd = [this._model.areSelectionValuesReversed() ? wordPosition.start : (wordPosition.start + wordPosition.length), coords[1]];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { ITerminal, ISoundManager } from './Types';
|
||||
|
||||
// Source: https://freesound.org/people/altemark/sounds/45759/
|
||||
// This sound is released under the Creative Commons Attribution 3.0 Unported
|
||||
// (CC BY 3.0) license. It was created by 'altemark'. No modifications have been
|
||||
// made, apart from the conversion to base64.
|
||||
export const DEFAULT_BELL_SOUND = 'data:audio/wav;base64,UklGRigBAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQQBAADpAFgCwAMlBZoG/wdmCcoKRAypDQ8PbRDBEQQTOxRtFYcWlBePGIUZXhoiG88bcBz7HHIdzh0WHlMeZx51HmkeUx4WHs8dah0AHXwc3hs9G4saxRnyGBIYGBcQFv8U4RPAEoYRQBACD70NWwwHC6gJOwjWBloF7gOBAhABkf8b/qv8R/ve+Xf4Ife79W/0JfPZ8Z/wde9N7ijtE+wU6xvqM+lb6H7nw+YX5mrlxuQz5Mzje+Ma49fioeKD4nXiYeJy4pHitOL04j/jn+MN5IPkFOWs5U3mDefM55/ogOl36m7rdOyE7abuyu8D8Unyj/Pg9D/2qfcb+Yn6/vuK/Qj/lAAlAg==';
|
||||
|
||||
export class SoundManager implements ISoundManager {
|
||||
private _audioContext: AudioContext;
|
||||
|
||||
constructor(
|
||||
private _terminal: ITerminal
|
||||
) {
|
||||
}
|
||||
|
||||
public playBellSound(): void {
|
||||
const audioContextCtor: typeof AudioContext = (<any>window).AudioContext || (<any>window).webkitAudioContext;
|
||||
if (!this._audioContext && audioContextCtor) {
|
||||
this._audioContext = new audioContextCtor();
|
||||
}
|
||||
|
||||
if (this._audioContext) {
|
||||
const bellAudioSource = this._audioContext.createBufferSource();
|
||||
const context = this._audioContext;
|
||||
this._audioContext.decodeAudioData(this.base64ToArrayBuffer(this.removeMimeType(this._terminal.options.bellSound)), (buffer) => {
|
||||
bellAudioSource.buffer = buffer;
|
||||
bellAudioSource.connect(context.destination);
|
||||
bellAudioSource.start(0);
|
||||
});
|
||||
} else {
|
||||
console.warn('Sorry, but the Web Audio API is not supported by your browser. Please, consider upgrading to the latest version');
|
||||
}
|
||||
}
|
||||
|
||||
private base64ToArrayBuffer(base64: string): ArrayBuffer {
|
||||
const binaryString = window.atob(base64);
|
||||
const len = binaryString.length;
|
||||
const bytes = new Uint8Array(len);
|
||||
|
||||
for (let i = 0; i < len; i++) {
|
||||
bytes[i] = binaryString.charCodeAt(i);
|
||||
}
|
||||
|
||||
return bytes.buffer;
|
||||
}
|
||||
|
||||
private removeMimeType(dataURI: string): string {
|
||||
// Split the input to get the mime-type and the data itself
|
||||
const splitUri = dataURI.split(',');
|
||||
|
||||
// Return only the data
|
||||
return splitUri[1];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
export let blankLine = 'Blank line';
|
||||
export let promptLabel = 'Terminal input';
|
||||
export let tooMuchOutput = 'Too much output to announce, navigate to rows manually to read';
|
||||
@@ -578,6 +578,99 @@ describe('term.js addons', () => {
|
||||
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', () => {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user