Compare commits

..
80 Commits
Author SHA1 Message Date
Paris Kasidiaris 2221f70ff0 Bump version to 2.6.0
Signed-off-by: Paris Kasidiaris <paris@sourcelair.com>
2017-05-04 15:38:52 +00:00
Daniel Imms daad8d9c04 Merge pull request #652 from Tyriar/651_fix_travis_fail
Force failing mocha tests to return a exit code of 1
2017-05-04 08:35:37 -07:00
Daniel Imms 03d26e39c7 Force failing mocha tests to return a exit code of 1
Fixes #651
2017-05-03 10:06:53 -07:00
Paris Kasidiaris 64d3c089a2 Merge pull request #647 from Martin1994/orion_use
Add "Eclipse Orion" to "Real-World Uses".
2017-05-02 00:37:38 +03:00
Martin Chloride 938c011e63 Added "Eclipse Orion" to "Real-World Uses". 2017-05-01 17:12:07 -04:00
Paris Kasidiaris 66f6b88333 Merge pull request #645 from sourcelair/issue-#640-do-not-focus-on-open
Add `focus` argument in `Terminal.prototype.open`
2017-05-01 01:07:00 +03:00
Paris Kasidiaris 35f746c925 Add focus argument in Terminal.prototype.open
Fix #640
2017-04-26 10:37:07 +00:00
Paris Kasidiaris 944da28089 Merge pull request #643 from jsmecham/patch-1
Add "Terminal for Atom" to Real-World Uses
2017-04-24 17:43:20 +03:00
Justin Mecham 8f3e2b645a Added "Terminal for Atom" to Real-World Uses. 2017-04-23 14:36:01 -04:00
Daniel Imms 82b2a5305f Merge pull request #639 from Tyriar/638_retain_cursor_blink_interface
Keep cursor blink interval after a reset
2017-04-21 17:59:51 -07:00
Daniel Imms 6a2852ee26 Merge branch 'master' into 638_retain_cursor_blink_interface 2017-04-21 17:53:49 -07:00
Daniel Imms 526d6c4da9 Merge pull request #641 from Tyriar/support_demo_cursorStyle
Support cursorStyle selection in demo
2017-04-20 10:33:30 -07:00
Daniel Imms 46d62f4972 Support cursorStyle selection in demo 2017-04-19 18:16:48 -07:00
Daniel Imms 5cafcdb695 Keep cursor blink interval after a reset
Fixes #638
2017-04-19 11:22:18 -07:00
Paris Kasidiaris 2674258ead Merge pull request #635 from rstudio/master
Update README with RStudio under real-world uses.
2017-04-15 01:15:27 +03:00
Gary Ritchie b38be3a1d0 Update README with RStudio under real-world uses. 2017-04-14 10:50:15 -07:00
Daniel Imms 18a9f05626 Merge pull request #628 from saulcosta18/patch-1
Added Codevolve.com to README
2017-04-06 13:01:16 -07:00
Saul Costa bf34980c43 Added Codevolve.com to README
We use xterm.js for our courses at Codevolve.
2017-04-06 12:50:41 -07:00
Paris Kasidiaris d2f8f9dc1a Bump version to 2.5.0
Signed-off-by: Paris Kasidiaris <paris@sourcelair.com>
2017-04-05 18:48:57 +00:00
Daniel Imms df13469b6e Merge pull request #627 from Tyriar/591_element_on_validation
Give API access to valid link elements
2017-04-05 11:34:34 -07:00
Daniel Imms 584ec681af Merge remote-tracking branch 'ups/master' into 591_element_on_validation 2017-04-04 09:50:04 -07:00
Daniel Imms 0c5b5cca4a Merge pull request #623 from Tyriar/612_multiple_links_in_row
Support multiple link matches in a single row
2017-04-04 09:41:19 -07:00
Daniel Imms 64663bf4a8 Fix tests 2017-04-04 09:35:52 -07:00
Daniel Imms fe8334a873 Merge remote-tracking branch 'ups/master' into 612_multiple_links_in_row 2017-04-04 09:33:53 -07:00
Daniel Imms c74fd40e2a Merge pull request #619 from Tyriar/618_register_link_before_dom
Allow links to be registered before terminal is attached to DOM
2017-04-04 09:13:09 -07:00
Daniel Imms 0f8ff67451 Merge branch 'master' into 618_register_link_before_dom 2017-04-04 09:02:37 -07:00
Daniel Imms 8b0032c210 Merge branch 'master' into 612_multiple_links_in_row 2017-04-04 09:02:35 -07:00
Daniel Imms d9254ef2d9 Merge pull request #626 from Tyriar/625_cursor_blink_perf
Use setInterval over animation for cursor blink
2017-04-04 09:02:27 -07:00
Daniel Imms 66be05a962 Merge branch 'master' into 612_multiple_links_in_row 2017-04-04 09:00:47 -07:00
Daniel Imms 0e85a09dd3 Merge branch 'master' into 618_register_link_before_dom 2017-04-04 08:58:43 -07:00
Daniel Imms 0c4ba9fd67 Clear cursor blink interval on blur 2017-04-03 15:09:38 -07:00
Daniel Imms aca81c764a Use setInterval over animation for cursor blink
Fixes #625
2017-04-03 14:49:21 -07:00
Daniel Imms 6b3ddb97d1 Merge pull request #624 from InDieTasten/patch-1
Fix typos in comment
2017-03-31 13:27:29 -07:00
InDieTasten 26ccf2a316 Fix typos in comment 2017-03-31 22:16:11 +02:00
Daniel Imms 361894194a Merge branch 'master' into 612_multiple_links_in_row 2017-03-31 03:06:23 -07:00
Daniel Imms 8c2db8ddb8 More tests 2017-03-31 02:10:07 -07:00
Daniel Imms 5546baa927 Fix edge case 2017-03-31 01:54:21 -07:00
Daniel Imms d814009721 Add tests 2017-03-31 01:48:51 -07:00
Daniel Imms 08fd050c3f Support multiple link matches in a single row
Fixes #612
2017-03-31 01:48:35 -07:00
Daniel Imms 0b096d7d55 Merge pull request #621 from coderaiser/patch-1
Added Cloud Commander to real world uses
2017-03-30 11:31:11 -07:00
coderaiser 44cfb2dc87 Added Cloud Commander to real world uses 2017-03-30 14:26:56 +03:00
Daniel Imms b0624cadb7 Allow links to be registered before terminal is attached to DOM
Fixes #618
2017-03-24 11:04:04 -07:00
Paris Kasidiaris 045f361b0d Merge pull request #616 from LucianBuzzo/crop-lines
Prevent line data from being discarded on resize.
2017-03-20 09:52:22 +00:00
Lucian Buzzo cb77b9dc1a Prevent line data from being discarded on resize. 2017-03-19 08:26:37 +00:00
Paris Kasidiaris 0723dac596 Merge pull request #615 from andfoy/patch-1
Added Spyder IDE Terminal as a xterm case-use application
2017-03-19 00:26:41 +00:00
Edgar Andrés Margffoy Tuay 365f9b4c1b Added Spyder IDE as a xterm case-use application 2017-03-18 18:05:05 -05:00
Daniel Imms 2cf90d90f7 Merge pull request #610 from Tyriar/non_draggable_links
Ensure terminal links are not draggable
2017-03-17 09:22:05 -07:00
Daniel Imms da14f64d1b Merge branch 'master' into non_draggable_links 2017-03-17 02:27:07 -07:00
Paris Kasidiaris ef1b1c8ffc Merge pull request #608 from sourcelair/add-logo
Add xterm.js logo
2017-03-17 08:18:08 +00:00
Daniel Imms adde8ab2c3 Ensure terminal links are not draggable 2017-03-16 12:07:25 -07:00
Paris Kasidiaris 22cc8079dc Add xterm.js logo 2017-03-16 08:54:07 +00:00
Daniel Imms 6d25706300 Merge pull request #606 from Tyriar/vscode22640_tilde_path
Allow ~ char in linkify path and query fragments
2017-03-15 18:01:47 -07:00
Paris Kasidiaris 81419c980b Merge pull request #603 from sourcelair/fix-blurred-terminal-cursor
Fix cursor style on blurred terminals
2017-03-15 22:09:18 +00:00
Paris Kasidiaris 097e21f641 Merge pull request #604 from sourcelair/rebuild-on-css
Rebuild xterm.js even when CSS changes
2017-03-15 22:09:03 +00:00
Daniel Imms b68180b97b Allow ~ char in linkify path and query fragments 2017-03-15 13:55:57 -07:00
Daniel Imms d9059d18c0 Merge pull request #600 from sourcelair/Tyriar-patch-1
Polish development and contribution section
2017-03-15 10:53:03 -07:00
Paris Kasidiaris 5225a6e27a Rebuild xterm.js even when CSS changes
Otherwise you had to run `npm build` manually every time you changed a CSS file to see the updated result in the demo.
2017-03-15 18:36:06 +02:00
Paris Kasidiaris 9055bace96 Fix cursor style on blurred terminals 2017-03-15 18:28:17 +02:00
Paris Kasidiaris eb25e88754 Merge pull request #598 from mikesir87/fix-fit-calculation
Fixed characterHeight calculation, which could cause overflowing
2017-03-15 14:34:13 +00:00
Michael Irwin 0cb8ecc6f7 Use boundingClientRect instead of style to get actual height
PR #598
2017-03-15 09:06:56 -04:00
Daniel Imms 8cf76cc6e9 Polish development and contribution section 2017-03-14 15:15:36 -07:00
Michael Irwin 3c3a646d42 Fixed characterHeight calculation, which could cause overflowing
The previous calculation was simply using the height of the letter
W, but wasn't taking into account that each row has a lineHeight,
which might be greater. If the lineHeight is .4px off, after many
rows, it will cause the proposedGeometry to have an extra row,
potentially causing hidden rows (based on layout).
2017-03-14 17:02:53 -04:00
Paris Kasidiaris ff29fd69a3 Merge pull request #572 from AndrienkoAleksandr/gulpImprove
Use tsconfig.json data in gulpfile.js instead of hardcoded values.
2017-03-11 13:21:47 +00:00
Daniel Imms 91994a399c Merge pull request #582 from tmyt/propose/row-height
Support checking height in checkBoldBroken
2017-03-10 12:53:12 -08:00
Daniel Imms bef7137c92 Fix bug 2017-03-09 10:47:09 -08:00
Daniel Imms 11f62bab34 Expose setHypertextValidationCallback 2017-03-09 10:39:08 -08:00
Daniel Imms b66776a047 Include element in validationCallback
Fixes #591
2017-03-09 10:23:59 -08:00
Daniel Imms 2c151f444c Merge pull request #589 from warpdesign/patch-1
FIXED: typo
2017-03-08 15:52:55 -08:00
Nicolas Ramz d8f8b060cb FIXED: typo
`<div id="terminal"></div>` wasn't used, instead, a new one was created and used since getElementById('#terminal') returned null.
2017-03-09 00:47:29 +01:00
Daniel Imms ea9376019c Merge pull request #586 from AndrienkoAleksandr/cleanUpTsConfig
Clean up tsconfig.json.
2017-03-08 12:02:09 -08:00
Daniel Imms 008d1df106 Merge remote-tracking branch 'up/master' into cleanUpTsConfig 2017-03-08 11:47:56 -08:00
Daniel Imms ec91a6c7f4 Merge pull request #588 from Tyriar/587_csi_3_J
Implement CSI 3 J - Erase saved lines
2017-03-08 11:08:46 -08:00
yutaka 4b2ae6a79f Use rendered height for detect broken bold font. 2017-03-08 11:30:23 +00:00
yutaka 2ec756fd6b Use offsetWidth instead of scrollWidth
scrollWidth does not work on Chrome with `display: inline` element.
2017-03-08 05:11:26 +00:00
yutaka f0d5b40121 Detect bold font was broken correctly. 2017-03-08 04:54:18 +00:00
yutaka 31a0996b2f Revert "Set charMeasure.height values to each row height"
This reverts commit 43cb4f471f.
2017-03-08 00:27:22 +00:00
Daniel Imms 0bbd8f0a16 Implement CSI 3 J - Erase saved lines
Fixes #587
2017-03-07 14:01:42 -08:00
Aleksandr Andrienko 3d721a95bb Clean up tsconfig.json.
'include' field in the tsconfig.json defines source scope (top level of this scope is "src" folder), so we do not need 'exclude' folders in the root of the project, because this folders had already excluded by 'include' scope.

Signed-off-by: Aleksandr Andrienko <aandrienko@codenvy.com>
2017-03-06 13:17:01 +02:00
Aleksandr Andrienko dc3a136704 Use tsconfig.json data in gulpfile.js instead of hardcoded values.
Use tsconfig.json data in gulpfile.js instead of hardcoded values. Little code clean up

Signed-off-by: Aleksandr Andrienko <aandrienko@codenvy.com>
2017-03-06 12:25:22 +02:00
yutaka 43cb4f471f Set charMeasure.height values to each row height 2017-03-03 07:57:05 +00:00
22 changed files with 568 additions and 302 deletions
+9
View File
@@ -22,24 +22,33 @@ Daniel Imms <daimms@microsoft.com>
Daniel Risacher <drisacher@gmail.com>
Dan Kaplun <dbkaplun@twitch.tv>
Darin Morrison <freebroccolo@users.noreply.github.com>
Edgar Andrés Margffoy Tuay <andfoy@gmail.com>
Elliot Saba <staticfloat@gmail.com>
Gary Ritchie <gary@rstudio.com>
hiro-su <h.sugipon@gmail.com>
Ian Lewis <ianlewis@google.com>
imoses <ido@twiggle.com>
InDieTasten <indietasten@gmail.com>
Jean Bruenn <himself@jeanbruenn.info>
Jörg Breitbart <jerch@rockborn.de>
Justin Mecham <justin@mecham.me>
Lucian Buzzo <lucian.buzzo@gmail.com>
Maël Nison <nison.mael@gmail.com>
Martin Chloride <i@martincl2.me>
Martin Wang <jiahaow@ca.ibm.com>
Michael Irwin <mikesir87@gmail.com>
Mikko Karvonen <mikko.karvonen@arm.com>
Nicolas Ramz <nicolas.ramz@gmail.com>
Paris Kasidiaris <pariskasidiaris@gmail.com>
Paris Kasidiaris <paris@sourcelair.com>
runarberg <runar@greenqloud.com>
Saswat Das <saswatds@users.noreply.github.com>
Saul Costa <saul@codevolve.com>
Shuanglei Tao <tsl0922@gmail.com>
Steven Silvester <steven.silvester@ieee.org>
Thanasis Daglis <thanasis@sourcelair.com>
Tine Jozelj <tine.jozelj@outlook.com>
Tyler Jewell <tjewell@codenvy.com>
Vincent Woo <me@vincentwoo.com>
yutaka <shigure@refy.net>
YuviPanda <yuvipanda@gmail.com>
+9 -23
View File
@@ -1,4 +1,4 @@
# xterm.js
# [![xterm.js logo](logo.png)](https://xtermjs.org)
[![xterm.js build status](https://api.travis-ci.org/sourcelair/xterm.js.svg)](https://travis-ci.org/sourcelair/xterm.js) [![Coverage Status](https://coveralls.io/repos/github/sourcelair/xterm.js/badge.svg)](https://coveralls.io/github/sourcelair/xterm.js) [![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)
@@ -30,6 +30,12 @@ Xterm.js is used in several world-class applications to provide great terminal e
- [**Codenvy**](http://www.codenvy.com): Cloud workspaces for development teams.
- [**CoderPad**](https://coderpad.io): Online interviewing platform for programmers. Run code in many programming languages, with results displayed by `xterm.js`.
- [**WebSSH2**](https://github.com/billchurch/WebSSH2): A web based SSH2 client using `xterm.js`, socket.io, and ssh2.
- [**Spyder Terminal**](https://github.com/spyder-ide/spyder-terminal): A full fledged system terminal embedded on Spyder IDE.
- [**Cloud Commander**](https://cloudcmd.io "Cloud Commander"): Orthodox web file manager with console and editor.
- [**Codevolve**](https://www.codevolve.com "Codevolve"): Online platform for interactive coding and web development courses. Live container-backed terminal uses `xterm.js`.
- [**RStudio**](https://www.rstudio.com/products/RStudio "RStudio"): RStudio is an integrated development environment (IDE) for R.
- [**Terminal for Atom**](https://github.com/jsmecham/atom-terminal-tab): A simple terminal for the Atom text editor.
- [**Eclipse Orion**](https://orionhub.org): A modern, open source software development environment that runs in the cloud. Code, deploy and run in the cloud.
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.
@@ -86,7 +92,7 @@ To start using xterm.js on your browser, add the `xterm.js` and `xterm.css` to t
<div id="terminal"></div>
<script>
var term = new Terminal();
term.open(document.getElementById('#terminal'));
term.open(document.getElementById('terminal'));
term.write('Hello from \033[1;3;31mxterm.js\033[0m $ ')
</script>
</body>
@@ -121,27 +127,7 @@ The existing releases are available at this GitHub repo's [Releases](https://git
Xterm.js is maintained by [SourceLair](https://www.sourcelair.com/) and a few external contributors, but we would love to receive contributions from everyone!
To contribute either code, documentation or issues to xterm.js please read the [Contributing document](CONTRIBUTING.md) before.
The development of xterm.js does not require any special tool. All you need is an editor that supports JavaScript and a browser (if you would like to run the demo you will need Node.js to get all features).
It is recommended though to use a development tool that uses xterm.js internally, to develop for xterm.js. [Eating our own dogfood](https://en.wikipedia.org/wiki/Eating_your_own_dog_food) has been proved extremely beneficial for this project. Known tools that use xterm.js internally are:
#### [SourceLair](https://www.sourcelair.com)
Visit https://lair.io/sourcelair/xterm and follow the instructions. All development will happen in your browser.
#### [Visual Studio Code](http://code.visualstudio.com/)
[Download Visual Studio Code](http://code.visualstudio.com/Download), clone xterm.js and you are all set.
#### [Eclipse Che](http://www.eclipse.org/che)
You can start Eclipse Che with `docker run eclipse/che start`.
#### [Codenvy](http://www.codenvy.io)
You can create a trial account or install an enterprise version with `docker run codenvy/cli start`.
To contribute either code, documentation or issues to xterm.js please read the [Contributing document](CONTRIBUTING.md) beforehand. The development of xterm.js does not require any special tool. All you need is an editor that supports JavaScript/TypeScript and a browser. You will need Node.js installed locally to get all the features working in the demo.
## License Agreement
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "xterm.js",
"version": "2.4.0",
"version": "2.6.0",
"ignore": ["demo", "test", ".gitignore"],
"main": [
"dist/xterm.js",
+10
View File
@@ -19,6 +19,16 @@
<p>
<label><input type="checkbox" id="option-cursor-blink"> cursorBlink</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>scrollback <input type="number" id="option-scrollback" value="1000" /></label>
</p>
+4
View File
@@ -9,6 +9,7 @@ var term,
var terminalContainer = document.getElementById('terminal-container'),
optionElements = {
cursorBlink: document.querySelector('#option-cursor-blink'),
cursorStyle: document.querySelector('#option-cursor-style'),
scrollback: document.querySelector('#option-scrollback'),
tabstopwidth: document.querySelector('#option-tabstopwidth')
},
@@ -32,6 +33,9 @@ rowsElement.addEventListener('change', setTerminalSize);
optionElements.cursorBlink.addEventListener('change', function () {
term.setOption('cursorBlink', optionElements.cursorBlink.checked);
});
optionElements.cursorStyle.addEventListener('change', function () {
term.setOption('cursorStyle', optionElements.cursorStyle.value);
});
optionElements.scrollback.addEventListener('change', function () {
term.setOption('scrollback', parseInt(optionElements.scrollback.value, 10));
});
+1 -1
View File
@@ -56,7 +56,7 @@
subjectRow.innerHTML = 'W'; // Common character for measuring width, although on monospace
characterWidth = subjectRow.getBoundingClientRect().width;
subjectRow.style.display = ''; // Revert style before calculating height, since they differ.
characterHeight = parseInt(subjectRow.offsetHeight);
characterHeight = subjectRow.getBoundingClientRect().height;
subjectRow.innerHTML = contentBuffer;
rows = parseInt(availableHeight / characterHeight);
+9 -16
View File
@@ -86,7 +86,7 @@
text-decoration: none;
}
.terminal:not(.xterm-cursor-style-underline):not(.xterm-cursor-style-bar) .terminal-cursor {
.terminal.focus:not(.xterm-cursor-style-underline):not(.xterm-cursor-style-bar) .terminal-cursor {
background-color: #fff;
color: #000;
}
@@ -97,16 +97,9 @@
background-color: transparent;
}
.terminal:not(.xterm-cursor-style-underline):not(.xterm-cursor-style-bar).focus.xterm-cursor-blink .terminal-cursor {
animation: xterm-cursor-blink 1.2s infinite step-end;
}
@keyframes xterm-cursor-blink {
0% { }
50% {
background-color: transparent;
color: inherit;
}
.terminal:not(.xterm-cursor-style-underline):not(.xterm-cursor-style-bar).focus.xterm-cursor-blink-on .terminal-cursor {
background-color: transparent;
color: inherit;
}
.terminal.xterm-cursor-style-bar .terminal-cursor,
@@ -132,13 +125,13 @@
right: 0;
height: 1px;
}
.terminal.xterm-cursor-style-bar.focus.xterm-cursor-blink.xterm-cursor-blink-on .terminal-cursor::before,
.terminal.xterm-cursor-style-underline.focus.xterm-cursor-blink.xterm-cursor-blink-on .terminal-cursor::before {
background-color: transparent;
}
.terminal.xterm-cursor-style-bar.focus.xterm-cursor-blink .terminal-cursor::before,
.terminal.xterm-cursor-style-underline.focus.xterm-cursor-blink .terminal-cursor::before {
animation: xterm-cursor-non-bar-blink 1.2s infinite step-end;
}
@keyframes xterm-cursor-non-bar-blink {
0% { background-color: #fff; }
50% { background-color: transparent; }
background-color: #fff;
}
.terminal .composition-view {
+163 -84
View File
@@ -1,5 +1,6 @@
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Terminal = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CHARSETS = {};
exports.DEFAULT_CHARSET = exports.CHARSETS['B'];
exports.CHARSETS['0'] = {
@@ -162,6 +163,7 @@ exports.CHARSETS['='] = {
},{}],2:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var CompositionHelper = (function () {
function CompositionHelper(textarea, compositionView, terminal) {
this.textarea = textarea;
@@ -288,6 +290,7 @@ exports.CompositionHelper = CompositionHelper;
},{}],3:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var C0;
(function (C0) {
C0.NUL = '\x00';
@@ -331,6 +334,7 @@ var C0;
},{}],4:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
;
var EventEmitter = (function () {
function EventEmitter() {
@@ -388,6 +392,7 @@ exports.EventEmitter = EventEmitter;
},{}],5:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var EscapeSequences_1 = require("./EscapeSequences");
var Charsets_1 = require("./Charsets");
var InputHandler = (function () {
@@ -632,7 +637,12 @@ var InputHandler = (function () {
this._terminal.eraseLine(j);
break;
case 3:
;
var scrollBackSize = this._terminal.lines.length - this._terminal.rows;
if (scrollBackSize > 0) {
this._terminal.lines.trimStart(scrollBackSize);
this._terminal.ybase = Math.max(this._terminal.ybase - scrollBackSize, 0);
this._terminal.ydisp = Math.max(this._terminal.ydisp - scrollBackSize, 0);
}
break;
}
};
@@ -1314,6 +1324,7 @@ var wcwidth = (function (opts) {
},{"./Charsets":1,"./EscapeSequences":3}],6:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var INVALID_LINK_CLASS = 'xterm-invalid-link';
var protocolClause = '(https?:\\/\\/)';
var domainCharacterSet = '[\\da-z\\.-]+';
@@ -1324,8 +1335,8 @@ var ipClause = '((\\d{1,3}\\.){3}\\d{1,3})';
var localHostClause = '(localhost)';
var portClause = '(:\\d{1,5})';
var hostClause = '((' + domainBodyClause + '\\.' + tldClause + ')|' + ipClause + '|' + localHostClause + ')' + portClause + '?';
var pathClause = '(\\/[\\/\\w\\.\\-%]*)*';
var queryStringHashFragmentCharacterSet = '[0-9\\w\\[\\]\\(\\)\\/\\?\\!#@$%&\'*+,:;\\=\\.\\-]*';
var pathClause = '(\\/[\\/\\w\\.\\-%~]*)*';
var queryStringHashFragmentCharacterSet = '[0-9\\w\\[\\]\\(\\)\\/\\?\\!#@$%&\'*+,:;~\\=\\.\\-]*';
var queryStringClause = '(\\?' + queryStringHashFragmentCharacterSet + ')?';
var hashFragmentClause = '(#' + queryStringHashFragmentCharacterSet + ')?';
var negatedPathCharacterSet = '[^\\/\\w\\.\\-%]+';
@@ -1335,24 +1346,32 @@ var end = ')($|' + negatedPathCharacterSet + ')';
var strictUrlRegex = new RegExp(start + protocolClause + bodyClause + end);
var HYPERTEXT_LINK_MATCHER_ID = 0;
var Linkifier = (function () {
function Linkifier(document, rows) {
function Linkifier() {
this._nextLinkMatcherId = HYPERTEXT_LINK_MATCHER_ID;
this._document = document;
this._rows = rows;
this._rowTimeoutIds = [];
this._linkMatchers = [];
this.registerLinkMatcher(strictUrlRegex, null, { matchIndex: 1 });
}
Linkifier.prototype.attachToDom = function (document, rows) {
this._document = document;
this._rows = rows;
};
Linkifier.prototype.linkifyRow = function (rowIndex) {
if (!this._document) {
return;
}
var timeoutId = this._rowTimeoutIds[rowIndex];
if (timeoutId) {
clearTimeout(timeoutId);
}
this._rowTimeoutIds[rowIndex] = setTimeout(this._linkifyRow.bind(this, rowIndex), Linkifier.TIME_BEFORE_LINKIFY);
};
Linkifier.prototype.attachHypertextLinkHandler = function (handler) {
Linkifier.prototype.setHypertextLinkHandler = function (handler) {
this._linkMatchers[HYPERTEXT_LINK_MATCHER_ID].handler = handler;
};
Linkifier.prototype.setHypertextValidationCallback = function (callback) {
this._linkMatchers[HYPERTEXT_LINK_MATCHER_ID].validationCallback = callback;
};
Linkifier.prototype.registerLinkMatcher = function (regex, handler, options) {
if (options === void 0) { options = {}; }
if (this._nextLinkMatcherId !== HYPERTEXT_LINK_MATCHER_ID && !handler) {
@@ -1397,35 +1416,42 @@ var Linkifier = (function () {
return;
}
var text = row.textContent;
var _loop_1 = function (i) {
var matcher = this_1._linkMatchers[i];
var uri = this_1._findLinkMatch(text, matcher.regex, matcher.matchIndex);
if (uri) {
var linkElement_1 = this_1._doLinkifyRow(rowIndex, uri, matcher.handler, matcher.id === HYPERTEXT_LINK_MATCHER_ID);
if (linkElement_1 && matcher.validationCallback) {
matcher.validationCallback(uri, function (isValid) {
if (!isValid) {
linkElement_1.classList.add(INVALID_LINK_CLASS);
}
});
}
return { value: void 0 };
}
};
var this_1 = this;
for (var i = 0; i < this._linkMatchers.length; i++) {
var state_1 = _loop_1(i);
if (typeof state_1 === "object")
return state_1.value;
var matcher = this._linkMatchers[i];
var linkElements = this._doLinkifyRow(row, matcher);
if (linkElements.length > 0) {
if (matcher.validationCallback) {
var _loop_1 = function (j) {
var element = linkElements[j];
matcher.validationCallback(element.textContent, element, function (isValid) {
if (!isValid) {
element.classList.add(INVALID_LINK_CLASS);
}
});
};
for (var j = 0; j < linkElements.length; j++) {
_loop_1(j);
}
}
return;
}
}
};
Linkifier.prototype._doLinkifyRow = function (rowIndex, uri, handler, isHttpLinkMatcher) {
var nodes = this._rows[rowIndex].childNodes;
Linkifier.prototype._doLinkifyRow = function (row, matcher) {
var result = [];
var isHttpLinkMatcher = matcher.id === HYPERTEXT_LINK_MATCHER_ID;
var nodes = row.childNodes;
var match = row.textContent.match(matcher.regex);
if (!match || match.length === 0) {
return result;
}
var uri = match[typeof matcher.matchIndex !== 'number' ? 0 : matcher.matchIndex];
var rowStartIndex = match.index + uri.length;
for (var i = 0; i < nodes.length; i++) {
var node = nodes[i];
var searchIndex = node.textContent.indexOf(uri);
if (searchIndex >= 0) {
var linkElement = this._createAnchorElement(uri, handler, isHttpLinkMatcher);
var linkElement = this._createAnchorElement(uri, matcher.handler, isHttpLinkMatcher);
if (node.textContent.length === uri.length) {
if (node.nodeType === 3) {
this._replaceNode(node, linkElement);
@@ -1433,29 +1459,31 @@ var Linkifier = (function () {
else {
var element = node;
if (element.nodeName === 'A') {
return;
return result;
}
element.innerHTML = '';
element.appendChild(linkElement);
}
}
else {
this._replaceNodeSubstringWithNode(node, linkElement, uri, searchIndex);
var nodesAdded = this._replaceNodeSubstringWithNode(node, linkElement, uri, searchIndex);
i += nodesAdded;
}
return linkElement;
result.push(linkElement);
match = row.textContent.substring(rowStartIndex).match(matcher.regex);
if (!match || match.length === 0) {
return result;
}
uri = match[typeof matcher.matchIndex !== 'number' ? 0 : matcher.matchIndex];
rowStartIndex += match.index + uri.length;
}
}
};
Linkifier.prototype._findLinkMatch = function (text, regex, matchIndex) {
var match = text.match(regex);
if (!match || match.length === 0) {
return null;
}
return match[typeof matchIndex !== 'number' ? 0 : matchIndex];
return result;
};
Linkifier.prototype._createAnchorElement = function (uri, handler, isHypertextLinkHandler) {
var element = this._document.createElement('a');
element.textContent = uri;
element.draggable = false;
if (isHypertextLinkHandler) {
element.href = uri;
element.target = '_blank';
@@ -1491,27 +1519,28 @@ var Linkifier = (function () {
if (node.nodeType !== 3) {
node = node.childNodes[0];
}
if (node.childNodes.length === 0 && node.nodeType !== Node.TEXT_NODE) {
if (node.childNodes.length === 0 && node.nodeType !== 3) {
throw new Error('targetNode must be a text node or only contain a single text node');
}
var fullText = node.textContent;
if (substringIndex === 0) {
var rightText = fullText.substring(substring.length);
var rightTextNode = this._document.createTextNode(rightText);
this._replaceNode(node, newNode, rightTextNode);
var rightText_1 = fullText.substring(substring.length);
var rightTextNode_1 = this._document.createTextNode(rightText_1);
this._replaceNode(node, newNode, rightTextNode_1);
return 0;
}
else if (substringIndex === targetNode.textContent.length - substring.length) {
var leftText = fullText.substring(0, substringIndex);
var leftTextNode = this._document.createTextNode(leftText);
this._replaceNode(node, leftTextNode, newNode);
}
else {
var leftText = fullText.substring(0, substringIndex);
var leftTextNode = this._document.createTextNode(leftText);
var rightText = fullText.substring(substringIndex + substring.length);
var rightTextNode = this._document.createTextNode(rightText);
this._replaceNode(node, leftTextNode, newNode, rightTextNode);
if (substringIndex === targetNode.textContent.length - substring.length) {
var leftText_1 = fullText.substring(0, substringIndex);
var leftTextNode_1 = this._document.createTextNode(leftText_1);
this._replaceNode(node, leftTextNode_1, newNode);
return 0;
}
var leftText = fullText.substring(0, substringIndex);
var leftTextNode = this._document.createTextNode(leftText);
var rightText = fullText.substring(substringIndex + substring.length);
var rightTextNode = this._document.createTextNode(rightText);
this._replaceNode(node, leftTextNode, newNode, rightTextNode);
return 1;
};
return Linkifier;
}());
@@ -1522,6 +1551,7 @@ exports.Linkifier = Linkifier;
},{}],7:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var EscapeSequences_1 = require("./EscapeSequences");
var Charsets_1 = require("./Charsets");
var normalStateHandler = {};
@@ -1997,6 +2027,7 @@ exports.Parser = Parser;
},{"./Charsets":1,"./EscapeSequences":3}],8:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var MAX_REFRESH_FRAME_SKIP = 5;
var FLAGS;
(function (FLAGS) {
@@ -2015,7 +2046,7 @@ var Renderer = (function () {
this._refreshFramesSkipped = 0;
this._refreshAnimationFrame = null;
if (brokenBold === null) {
brokenBold = checkBoldBroken(this._terminal.document);
brokenBold = checkBoldBroken(this._terminal.element);
}
}
Renderer.prototype.queueRefresh = function (start, end) {
@@ -2192,22 +2223,25 @@ var Renderer = (function () {
return Renderer;
}());
exports.Renderer = Renderer;
function checkBoldBroken(document) {
var body = document.getElementsByTagName('body')[0];
function checkBoldBroken(terminal) {
var document = terminal.ownerDocument;
var el = document.createElement('span');
el.innerHTML = 'hello world';
body.appendChild(el);
var w1 = el.scrollWidth;
terminal.appendChild(el);
var w1 = el.offsetWidth;
var h1 = el.offsetHeight;
el.style.fontWeight = 'bold';
var w2 = el.scrollWidth;
body.removeChild(el);
return w1 !== w2;
var w2 = el.offsetWidth;
var h2 = el.offsetHeight;
terminal.removeChild(el);
return w1 !== w2 || h1 !== h2;
}
},{}],9:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var Viewport = (function () {
function Viewport(terminal, viewportElement, scrollArea, charMeasure) {
var _this = this;
@@ -2285,6 +2319,7 @@ exports.Viewport = Viewport;
},{}],10:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
function prepareTextForClipboard(text) {
var space = String.fromCharCode(32), nonBreakingSpace = String.fromCharCode(160), allNonBreakingSpaces = new RegExp(nonBreakingSpace, 'g'), processedText = text.split('\n').map(function (line) {
var processedLine = line.replace(/\s+$/g, '').replace(allNonBreakingSpaces, space);
@@ -2366,6 +2401,7 @@ exports.rightClickHandler = rightClickHandler;
},{}],11:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var Generic_1 = require("./Generic");
var isNode = (typeof navigator === 'undefined') ? true : false;
var userAgent = (isNode) ? 'node' : navigator.userAgent;
@@ -2381,11 +2417,17 @@ exports.isMSWindows = Generic_1.contains(['Windows', 'Win16', 'Win32', 'WinCE'],
},{"./Generic":14}],12:[function(require,module,exports){
"use strict";
var __extends = (this && this.__extends) || function (d, b) {
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
var __extends = (this && this.__extends) || (function () {
var extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
var EventEmitter_js_1 = require("../EventEmitter.js");
var CharMeasure = (function (_super) {
__extends(CharMeasure, _super);
@@ -2444,6 +2486,7 @@ exports.CharMeasure = CharMeasure;
},{"../EventEmitter.js":4}],13:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var CircularList = (function () {
function CircularList(maxLength) {
this._array = new Array(maxLength);
@@ -2582,6 +2625,7 @@ exports.CircularList = CircularList;
},{}],14:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
function contains(arr, el) {
return arr.indexOf(el) >= 0;
}
@@ -2592,6 +2636,7 @@ exports.contains = contains;
},{}],15:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var CompositionHelper_1 = require("./CompositionHelper");
var EventEmitter_1 = require("./EventEmitter");
var Viewport_1 = require("./Viewport");
@@ -2607,6 +2652,7 @@ var Browser = require("./utils/Browser");
var document = (typeof window != 'undefined') ? window.document : null;
var WRITE_BUFFER_PAUSE_THRESHOLD = 5;
var WRITE_BATCH_SIZE = 300;
var CURSOR_BLINK_INTERVAL = 600;
function Terminal(options) {
var self = this;
if (!(this instanceof Terminal)) {
@@ -2664,6 +2710,7 @@ function Terminal(options) {
this.scrollTop = 0;
this.scrollBottom = this.rows - 1;
this.customKeydownHandler = null;
this.cursorBlinkInterval = null;
this.applicationKeypad = false;
this.applicationCursor = false;
this.originMode = false;
@@ -2702,8 +2749,7 @@ function Terminal(options) {
this.inputHandler = new InputHandler_1.InputHandler(this);
this.parser = new Parser_1.Parser(this.inputHandler, this);
this.renderer = this.renderer || null;
this.linkifier = this.linkifier || null;
;
this.linkifier = this.linkifier || new Linkifier_1.Linkifier();
this.writeBuffer = [];
this.writeInProgress = false;
this.xoffSentToCatchUp = false;
@@ -2835,7 +2881,7 @@ Terminal.prototype.setOption = function (key, value) {
this.options[key] = value;
switch (key) {
case 'cursorBlink':
this.element.classList.toggle('xterm-cursor-blink', value);
this.setCursorBlinking(value);
break;
case 'cursorStyle':
this.element.classList.toggle("xterm-cursor-style-underline", value === 'underline');
@@ -2846,6 +2892,26 @@ Terminal.prototype.setOption = function (key, value) {
break;
}
};
Terminal.prototype.restartCursorBlinking = function () {
this.setCursorBlinking(this.options.cursorBlink);
};
Terminal.prototype.setCursorBlinking = function (enabled) {
this.element.classList.toggle('xterm-cursor-blink', enabled);
this.clearCursorBlinkingInterval();
if (enabled) {
var self = this;
this.cursorBlinkInterval = setInterval(function () {
self.element.classList.toggle('xterm-cursor-blink-on');
}, CURSOR_BLINK_INTERVAL);
}
};
Terminal.prototype.clearCursorBlinkingInterval = function () {
this.element.classList.remove('xterm-cursor-blink-on');
if (this.cursorBlinkInterval) {
clearInterval(this.cursorBlinkInterval);
this.cursorBlinkInterval = null;
}
};
Terminal.bindFocus = function (term) {
on(term.textarea, 'focus', function (ev) {
if (term.sendFocus) {
@@ -2853,6 +2919,7 @@ Terminal.bindFocus = function (term) {
}
term.element.classList.add('focus');
term.showCursor();
term.restartCursorBlinking.apply(term);
Terminal.focus = term;
term.emit('focus', { terminal: term });
});
@@ -2867,6 +2934,7 @@ Terminal.bindBlur = function (term) {
term.send(EscapeSequences_1.C0.ESC + '[O');
}
term.element.classList.remove('focus');
term.clearCursorBlinkingInterval.apply(term);
Terminal.focus = null;
term.emit('blur', { terminal: term });
});
@@ -2940,7 +3008,7 @@ Terminal.prototype.insertRow = function (row) {
this.children.push(row);
return row;
};
Terminal.prototype.open = function (parent) {
Terminal.prototype.open = function (parent, focus) {
var self = this, i = 0, div;
this.parent = parent || this.parent;
if (!this.parent) {
@@ -2953,7 +3021,7 @@ Terminal.prototype.open = function (parent) {
this.element.classList.add('terminal');
this.element.classList.add('xterm');
this.element.classList.add('xterm-theme-' + this.theme);
this.element.classList.toggle('xterm-cursor-blink', this.options.cursorBlink);
this.setCursorBlinking(this.options.cursorBlink);
this.element.style.height;
this.element.setAttribute('tabindex', 0);
this.viewportElement = document.createElement('div');
@@ -2966,7 +3034,7 @@ Terminal.prototype.open = function (parent) {
this.rowContainer.classList.add('xterm-rows');
this.element.appendChild(this.rowContainer);
this.children = [];
this.linkifier = new Linkifier_1.Linkifier(document, this.children);
this.linkifier.attachToDom(document, this.children);
this.helperContainer = document.createElement('div');
this.helperContainer.classList.add('xterm-helpers');
this.element.appendChild(this.helperContainer);
@@ -3002,7 +3070,16 @@ Terminal.prototype.open = function (parent) {
this.renderer = new Renderer_1.Renderer(this);
this.refresh(0, this.rows - 1);
this.initGlobal();
this.focus();
if (typeof focus == 'undefined') {
var message = 'You did not pass the `focus` argument in `Terminal.prototype.open()`.\n';
message += 'The `focus` argument now defaults to `true` but starting with xterm.js 3.0 ';
message += 'it will default to `false`.';
console.warn(message);
focus = true;
}
if (focus) {
this.focus();
}
on(this.element, 'click', function () {
var selection = document.getSelection(), collapsed = selection.isCollapsed, isRange = typeof collapsed == 'boolean' ? !collapsed : selection.type == 'Range';
if (!isRange) {
@@ -3395,11 +3472,18 @@ Terminal.prototype.writeln = function (data) {
Terminal.prototype.attachCustomKeydownHandler = function (customKeydownHandler) {
this.customKeydownHandler = customKeydownHandler;
};
Terminal.prototype.attachHypertextLinkHandler = function (handler) {
Terminal.prototype.setHypertextLinkHandler = function (handler) {
if (!this.linkifier) {
throw new Error('Cannot attach a hypertext link handler before Terminal.open is called');
}
this.linkifier.attachHypertextLinkHandler(handler);
this.linkifier.setHypertextLinkHandler(handler);
this.refresh(0, this.rows - 1);
};
Terminal.prototype.setHypertextValidationCallback = function (handler) {
if (!this.linkifier) {
throw new Error('Cannot attach a hypertext validation callback before Terminal.open is called');
}
this.linkifier.setHypertextValidationCallback(handler);
this.refresh(0, this.rows - 1);
};
Terminal.prototype.registerLinkMatcher = function (regex, handler, options) {
@@ -3420,6 +3504,7 @@ Terminal.prototype.keyDown = function (ev) {
if (this.customKeydownHandler && this.customKeydownHandler(ev) === false) {
return false;
}
this.restartCursorBlinking();
if (!this.compositionHelper.keydown.bind(this.compositionHelper)(ev)) {
if (this.ybase !== this.ydisp) {
this.scrollToBottom();
@@ -3814,14 +3899,6 @@ Terminal.prototype.resize = function (x, y) {
}
}
}
else {
i = this.lines.length;
while (i--) {
while (this.lines.get(i).length > x) {
this.lines.get(i).pop();
}
}
}
this.cols = x;
this.setupStops(this.cols);
j = this.rows;
@@ -4023,8 +4100,10 @@ Terminal.prototype.reset = function () {
this.options.rows = this.rows;
this.options.cols = this.cols;
var customKeydownHandler = this.customKeydownHandler;
var cursorBlinkInterval = this.cursorBlinkInterval;
Terminal.call(this, this.options);
this.customKeydownHandler = customKeydownHandler;
this.cursorBlinkInterval = cursorBlinkInterval;
this.refresh(0, this.rows - 1);
this.viewport.syncScrollArea();
};
+1 -1
View File
File diff suppressed because one or more lines are too long
+21 -19
View File
@@ -13,27 +13,28 @@ const ts = require('gulp-typescript');
let buildDir = process.env.BUILD_DIR || 'build';
let tsProject = ts.createProject('tsconfig.json');
let srcDir = tsProject.config.compilerOptions.rootDir;
let outDir = tsProject.config.compilerOptions.outDir;
/**
* Compile TypeScript sources to JavaScript files and create a source map file for each TypeScript
* file compiled.
*/
gulp.task('tsc', function () {
// Remove the lib/ directory to prevent confusion if files were deleted in src/
fs.emptyDirSync('lib');
// Remove the ${outDir}/ directory to prevent confusion if files were deleted in ${srcDir}/
fs.emptyDirSync(`${outDir}`);
// Build all TypeScript files (including tests) to lib/, based on the configuration defined in
// Build all TypeScript files (including tests) to ${outDir}/, based on the configuration defined in
// `tsconfig.json`.
let tsProject = ts.createProject('tsconfig.json');
let tsResult = tsProject.src().pipe(sourcemaps.init()).pipe(tsProject());
let tsc = tsResult.js.pipe(sourcemaps.write('.', {includeContent: false, sourceRoot: ''})).pipe(gulp.dest('lib'));
let tsc = tsResult.js.pipe(sourcemaps.write('.', {includeContent: false, sourceRoot: ''})).pipe(gulp.dest(outDir));
// Copy all addons from src/ to lib/
let copyAddons = gulp.src('src/addons/**/*').pipe(gulp.dest('lib/addons'));
// Copy all addons from ${srcDir}/ to ${outDir}/
let copyAddons = gulp.src(`${srcDir}/addons/**/*`).pipe(gulp.dest(`${outDir}/addons`));
// Copy stylesheets from src/ to lib/
let copyStylesheets = gulp.src('src/**/*.css').pipe(gulp.dest('lib'));
// Copy stylesheets from ${srcDir}/ to ${outDir}/
let copyStylesheets = gulp.src(`${srcDir}/**/*.css`).pipe(gulp.dest(outDir));
return merge(tsc, copyAddons, copyStylesheets);
});
@@ -49,7 +50,7 @@ gulp.task('browserify', ['tsc'], function() {
let browserifyOptions = {
basedir: buildDir,
debug: true,
entries: ['../lib/xterm.js'],
entries: [`../${outDir}/xterm.js`],
standalone: 'Terminal',
cache: {},
packageCache: {}
@@ -62,17 +63,17 @@ gulp.task('browserify', ['tsc'], function() {
.pipe(sourcemaps.write('./'))
.pipe(gulp.dest(buildDir));
// Copy all add-ons from lib/ to buildDir
let copyAddons = gulp.src('lib/addons/**/*').pipe(gulp.dest(`${buildDir}/addons`));
// Copy all add-ons from ${outDir}/ to buildDir
let copyAddons = gulp.src(`${outDir}/addons/**/*`).pipe(gulp.dest(`${buildDir}/addons`));
// Copy stylesheets from src/ to lib/
let copyStylesheets = gulp.src('lib/**/*.css').pipe(gulp.dest(buildDir));
// Copy stylesheets from ${outDir}/ to ${buildDir}/
let copyStylesheets = gulp.src(`${outDir}/**/*.css`).pipe(gulp.dest(buildDir));
return merge(bundleStream, copyAddons, copyStylesheets);
});
gulp.task('instrument-test', function () {
return gulp.src(['lib/**/*.js'])
return gulp.src([`${outDir}/**/*.js`])
// Covering files
.pipe(istanbul())
// Force `require` to return covered files
@@ -80,19 +81,20 @@ gulp.task('instrument-test', function () {
});
gulp.task('mocha', ['instrument-test'], function () {
return gulp.src(['lib/*test.js', 'lib/**/*test.js'], {read: false})
return gulp.src([`${outDir}/*test.js`, `${outDir}/**/*test.js`], {read: false})
.pipe(mocha())
.once('error', () => process.exit(1))
.pipe(istanbul.writeReports());
});
/**
* Use `sorcery` to resolve the source map chain and point back to the TypeScript files.
* (Without this task the source maps produced for the JavaScript bundle points into the
* compiled JavaScript files in lib/).
* compiled JavaScript files in ${outDir}/).
*/
gulp.task('sorcery', ['browserify'], function () {
var chain = sorcery.loadSync(`${buildDir}/xterm.js`);
var map = chain.apply();
chain.apply();
chain.writeSync();
});
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "xterm",
"description": "Full xterm terminal, in your browser",
"version": "2.4.0",
"version": "2.6.0",
"ignore": [
"demo",
"test",
@@ -68,7 +68,7 @@
"scripts": {
"prestart": "npm run build",
"start": "node demo/app",
"dev": "nodemon -e js,ts --watch src --watch demo --exec npm start",
"dev": "nodemon -e js,ts,css --watch src --watch demo --exec npm start",
"lint": "tslint src/*.ts src/**/*.ts",
"test": "gulp test",
"build:docs": "jsdoc -c jsdoc.json",
+7 -1
View File
@@ -385,7 +385,13 @@ export class InputHandler implements IInputHandler {
while (j--) this._terminal.eraseLine(j);
break;
case 3:
; // no saved lines
// Clear scrollback (everything not in viewport)
const scrollBackSize = this._terminal.lines.length - this._terminal.rows;
if (scrollBackSize > 0) {
this._terminal.lines.trimStart(scrollBackSize);
this._terminal.ybase = Math.max(this._terminal.ybase - scrollBackSize, 0);
this._terminal.ydisp = Math.max(this._terminal.ydisp - scrollBackSize, 0);
}
break;
}
}
+139 -51
View File
@@ -8,9 +8,9 @@ import { Linkifier } from './Linkifier';
import { LinkMatcher } from './Types';
class TestLinkifier extends Linkifier {
constructor(document: Document, rows: HTMLElement[]) {
constructor() {
Linkifier.TIME_BEFORE_LINKIFY = 0;
super(document, rows);
super();
}
public get linkMatchers(): LinkMatcher[] { return this._linkMatchers; }
@@ -25,75 +25,163 @@ describe('Linkifier', () => {
let linkifier: TestLinkifier;
beforeEach(done => {
rows = [];
jsdom.env('', (err, w) => {
window = w;
document = window.document;
linkifier = new TestLinkifier(document, rows);
container = document.createElement('div');
document.body.appendChild(container);
linkifier = new TestLinkifier();
done();
});
});
function addRow(text: string) {
function addRow(html: string) {
const element = document.createElement('div');
element.textContent = text;
element.innerHTML = html;
container.appendChild(element);
rows.push(element);
}
function clickElement(element: Node) {
const event = document.createEvent('MouseEvent');
event.initMouseEvent('click', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
element.dispatchEvent(event);
}
describe('validationCallback', () => {
it('should enable link if true', done => {
addRow('test');
linkifier.registerLinkMatcher(/test/, () => done(), {
validationCallback: (url, cb) => {
cb(true);
assert.equal((<HTMLElement>rows[0].firstChild).tagName, 'A');
setTimeout(() => clickElement(rows[0].firstChild), 0);
}
describe('before attachToDom', () => {
it('should allow link matcher registration', done => {
assert.doesNotThrow(() => {
const linkMatcherId = linkifier.registerLinkMatcher(/foo/, () => {});
assert.isTrue(linkifier.deregisterLinkMatcher(linkMatcherId));
done();
});
linkifier.linkifyRow(0);
});
it('should disable link if false', done => {
addRow('test');
linkifier.registerLinkMatcher(/test/, () => assert.fail(), {
validationCallback: (url, cb) => {
cb(false);
assert.equal((<HTMLElement>rows[0].firstChild).tagName, 'A');
setTimeout(() => clickElement(rows[0].firstChild), 0);
}
});
linkifier.linkifyRow(0);
// Allow time for the click to be performed
setTimeout(() => done(), 10);
});
});
describe('priority', () => {
it('should order the list from highest priority to lowest #1', () => {
const aId = linkifier.registerLinkMatcher(/a/, () => {}, { priority: 1 });
const bId = linkifier.registerLinkMatcher(/b/, () => {}, { priority: -1 });
assert.deepEqual(linkifier.linkMatchers.map(lm => lm.id), [aId, 0, bId]);
describe('after attachToDom', () => {
beforeEach(() => {
rows = [];
linkifier.attachToDom(document, rows);
container = document.createElement('div');
document.body.appendChild(container);
});
it('should order the list from highest priority to lowest #2', () => {
const aId = linkifier.registerLinkMatcher(/a/, () => {}, { priority: -1 });
const bId = linkifier.registerLinkMatcher(/b/, () => {}, { priority: 1 });
assert.deepEqual(linkifier.linkMatchers.map(lm => lm.id), [bId, 0, aId]);
function clickElement(element: Node) {
const event = document.createEvent('MouseEvent');
event.initMouseEvent('click', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
element.dispatchEvent(event);
}
function assertLinkifiesEntireRow(uri: string, done: MochaDone) {
addRow(uri);
linkifier.linkifyRow(0);
setTimeout(() => {
assert.equal((<HTMLElement>rows[0].firstChild).tagName, 'A');
assert.equal((<HTMLElement>rows[0].firstChild).textContent, uri);
done();
}, 0);
}
describe('http links', () => {
function assertLinkifiesEntireRow(uri: string, done: MochaDone) {
addRow(uri);
linkifier.linkifyRow(0);
setTimeout(() => {
assert.equal((<HTMLElement>rows[0].firstChild).tagName, 'A');
assert.equal((<HTMLElement>rows[0].firstChild).textContent, uri);
done();
}, 0);
}
it('should allow ~ character in URI path', done => assertLinkifiesEntireRow('http://foo.com/a~b#c~d?e~f', done));
});
it('should order items of equal priority in the order they are added', () => {
const aId = linkifier.registerLinkMatcher(/a/, () => {}, { priority: 0 });
const bId = linkifier.registerLinkMatcher(/b/, () => {}, { priority: 0 });
assert.deepEqual(linkifier.linkMatchers.map(lm => lm.id), [0, aId, bId]);
describe('link matcher', () => {
function assertLinkifiesRow(rowText: string, linkMatcherRegex: RegExp, expectedHtml: string, done: MochaDone) {
addRow(rowText);
linkifier.registerLinkMatcher(linkMatcherRegex, () => {});
linkifier.linkifyRow(0);
// Allow linkify to happen
setTimeout(() => {
assert.equal(rows[0].innerHTML, expectedHtml);
done();
}, 0);
}
it('should match a single link', done => {
assertLinkifiesRow('foo', /foo/, '<a>foo</a>', done);
});
it('should match a single link at the start of a text node', done => {
assertLinkifiesRow('foo bar', /foo/, '<a>foo</a> bar', done);
});
it('should match a single link in the middle of a text node', done => {
assertLinkifiesRow('foo bar baz', /bar/, 'foo <a>bar</a> baz', done);
});
it('should match a single link at the end of a text node', done => {
assertLinkifiesRow('foo bar', /bar/, 'foo <a>bar</a>', done);
});
it('should match a link after a link at the start of a text node', done => {
assertLinkifiesRow('foo bar', /foo|bar/, '<a>foo</a> <a>bar</a>', done);
});
it('should match a link after a link in the middle of a text node', done => {
assertLinkifiesRow('foo bar baz', /bar|baz/, 'foo <a>bar</a> <a>baz</a>', done);
});
it('should match a link immediately after a link at the end of a text node', done => {
assertLinkifiesRow('<span>foo bar</span>baz', /bar|baz/, '<span>foo <a>bar</a></span><a>baz</a>', done);
});
});
describe('validationCallback', () => {
it('should enable link if true', done => {
addRow('test');
linkifier.registerLinkMatcher(/test/, () => done(), {
validationCallback: (url, element, cb) => {
cb(true);
assert.equal((<HTMLElement>rows[0].firstChild).tagName, 'A');
setTimeout(() => clickElement(rows[0].firstChild), 0);
}
});
linkifier.linkifyRow(0);
});
it('should disable link if false', done => {
addRow('test');
linkifier.registerLinkMatcher(/test/, () => assert.fail(), {
validationCallback: (url, element, cb) => {
cb(false);
assert.equal((<HTMLElement>rows[0].firstChild).tagName, 'A');
setTimeout(() => clickElement(rows[0].firstChild), 0);
}
});
linkifier.linkifyRow(0);
// Allow time for the click to be performed
setTimeout(() => done(), 10);
});
it('should trigger for multiple link matches on one row', done => {
addRow('test test');
let count = 0;
linkifier.registerLinkMatcher(/test/, () => assert.fail(), {
validationCallback: (url, element, cb) => {
count += 1;
if (count === 2) {
done();
}
cb(false);
}
});
linkifier.linkifyRow(0);
});
});
describe('priority', () => {
it('should order the list from highest priority to lowest #1', () => {
const aId = linkifier.registerLinkMatcher(/a/, () => {}, { priority: 1 });
const bId = linkifier.registerLinkMatcher(/b/, () => {}, { priority: -1 });
assert.deepEqual(linkifier.linkMatchers.map(lm => lm.id), [aId, 0, bId]);
});
it('should order the list from highest priority to lowest #2', () => {
const aId = linkifier.registerLinkMatcher(/a/, () => {}, { priority: -1 });
const bId = linkifier.registerLinkMatcher(/b/, () => {}, { priority: 1 });
assert.deepEqual(linkifier.linkMatchers.map(lm => lm.id), [bId, 0, aId]);
});
it('should order items of equal priority in the order they are added', () => {
const aId = linkifier.registerLinkMatcher(/a/, () => {}, { priority: 0 });
const bId = linkifier.registerLinkMatcher(/b/, () => {}, { priority: 0 });
assert.deepEqual(linkifier.linkMatchers.map(lm => lm.id), [0, aId, bId]);
});
});
});
});
+89 -51
View File
@@ -16,8 +16,8 @@ const ipClause = '((\\d{1,3}\\.){3}\\d{1,3})';
const localHostClause = '(localhost)';
const portClause = '(:\\d{1,5})';
const hostClause = '((' + domainBodyClause + '\\.' + tldClause + ')|' + ipClause + '|' + localHostClause + ')' + portClause + '?';
const pathClause = '(\\/[\\/\\w\\.\\-%]*)*';
const queryStringHashFragmentCharacterSet = '[0-9\\w\\[\\]\\(\\)\\/\\?\\!#@$%&\'*+,:;\\=\\.\\-]*';
const pathClause = '(\\/[\\/\\w\\.\\-%~]*)*';
const queryStringHashFragmentCharacterSet = '[0-9\\w\\[\\]\\(\\)\\/\\?\\!#@$%&\'*+,:;~\\=\\.\\-]*';
const queryStringClause = '(\\?' + queryStringHashFragmentCharacterSet + ')?';
const hashFragmentClause = '(#' + queryStringHashFragmentCharacterSet + ')?';
const negatedPathCharacterSet = '[^\\/\\w\\.\\-%]+';
@@ -37,8 +37,8 @@ const HYPERTEXT_LINK_MATCHER_ID = 0;
export class Linkifier {
/**
* The time to wait after a row is changed before it is linkified. This prevents
* the costly operation of searching every row multiple times, pntentially a
* huge aount of times.
* the costly operation of searching every row multiple times, potentially a
* huge amount of times.
*/
protected static TIME_BEFORE_LINKIFY = 200;
@@ -49,19 +49,32 @@ export class Linkifier {
private _rowTimeoutIds: number[];
private _nextLinkMatcherId = HYPERTEXT_LINK_MATCHER_ID;
constructor(document: Document, rows: HTMLElement[]) {
this._document = document;
this._rows = rows;
constructor() {
this._rowTimeoutIds = [];
this._linkMatchers = [];
this.registerLinkMatcher(strictUrlRegex, null, { matchIndex: 1 });
}
/**
* Attaches the linkifier to the DOM, enabling linkification.
* @param document The document object.
* @param rows The array of rows to apply links to.
*/
public attachToDom(document: Document, rows: HTMLElement[]) {
this._document = document;
this._rows = rows;
}
/**
* Queues a row for linkification.
* @param {number} rowIndex The index of the row to linkify.
*/
public linkifyRow(rowIndex: number): void {
// Don't attempt linkify if not yet attached to DOM
if (!this._document) {
return;
}
const timeoutId = this._rowTimeoutIds[rowIndex];
if (timeoutId) {
clearTimeout(timeoutId);
@@ -75,10 +88,19 @@ export class Linkifier {
* @param {LinkHandler} handler The handler to use, this can be cleared with
* null.
*/
public attachHypertextLinkHandler(handler: LinkMatcherHandler): void {
public setHypertextLinkHandler(handler: LinkMatcherHandler): void {
this._linkMatchers[HYPERTEXT_LINK_MATCHER_ID].handler = handler;
}
/**
* Attaches a validation callback for hypertext links.
* @param {LinkMatcherValidationCallback} callback The callback to use, this
* can be cleared with null.
*/
public setHypertextValidationCallback(callback: LinkMatcherValidationCallback): void {
this._linkMatchers[HYPERTEXT_LINK_MATCHER_ID].validationCallback = callback;
}
/**
* Registers a link matcher, allowing custom link patterns to be matched and
* handled.
@@ -155,16 +177,18 @@ export class Linkifier {
const text = row.textContent;
for (let i = 0; i < this._linkMatchers.length; i++) {
const matcher = this._linkMatchers[i];
const uri = this._findLinkMatch(text, matcher.regex, matcher.matchIndex);
if (uri) {
const linkElement = this._doLinkifyRow(rowIndex, uri, matcher.handler, matcher.id === HYPERTEXT_LINK_MATCHER_ID);
const linkElements = this._doLinkifyRow(row, matcher);
if (linkElements.length > 0) {
// Fire validation callback
if (linkElement && matcher.validationCallback) {
matcher.validationCallback(uri, isValid => {
if (!isValid) {
linkElement.classList.add(INVALID_LINK_CLASS);
}
});
if (matcher.validationCallback) {
for (let j = 0; j < linkElements.length; j++) {
const element = linkElements[j];
matcher.validationCallback(element.textContent, element, isValid => {
if (!isValid) {
element.classList.add(INVALID_LINK_CLASS);
}
});
}
}
// Only allow a single LinkMatcher to trigger on any given row.
return;
@@ -174,54 +198,61 @@ export class Linkifier {
/**
* Linkifies a row given a specific handler.
* @param {number} rowIndex The index of the row to linkify.
* @param {string} uri The uri that has been found.
* @param {handler} handler The handler to trigger when the link is triggered.
* @param {HTMLElement} row The row to linkify.
* @param {LinkMatcher} matcher The link matcher for this line.
* @return The link element if it was added, otherwise undefined.
*/
private _doLinkifyRow(rowIndex: number, uri: string, handler: LinkMatcherHandler, isHttpLinkMatcher: boolean): HTMLElement {
private _doLinkifyRow(row: HTMLElement, matcher: LinkMatcher): HTMLElement[] {
// Iterate over nodes as we want to consider text nodes
const nodes = this._rows[rowIndex].childNodes;
let result = [];
const isHttpLinkMatcher = matcher.id === HYPERTEXT_LINK_MATCHER_ID;
const nodes = row.childNodes;
// Find the first match
let match = row.textContent.match(matcher.regex);
if (!match || match.length === 0) {
return result;
}
let uri = match[typeof matcher.matchIndex !== 'number' ? 0 : matcher.matchIndex];
// Set the next searches start index
let rowStartIndex = match.index + uri.length;
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i];
const searchIndex = node.textContent.indexOf(uri);
if (searchIndex >= 0) {
const linkElement = this._createAnchorElement(uri, handler, isHttpLinkMatcher);
const linkElement = this._createAnchorElement(uri, matcher.handler, isHttpLinkMatcher);
if (node.textContent.length === uri.length) {
// Matches entire string
if (node.nodeType === 3 /*Node.TEXT_NODE*/) {
this._replaceNode(node, linkElement);
} else {
const element = (<HTMLElement>node);
if (element.nodeName === 'A') {
// This row has already been linkified
return;
return result;
}
element.innerHTML = '';
element.appendChild(linkElement);
}
} else {
// Matches part of string
this._replaceNodeSubstringWithNode(node, linkElement, uri, searchIndex);
const nodesAdded = this._replaceNodeSubstringWithNode(node, linkElement, uri, searchIndex);
// No need to consider the new nodes
i += nodesAdded;
}
return linkElement;
result.push(linkElement);
// Find the next match
match = row.textContent.substring(rowStartIndex).match(matcher.regex);
if (!match || match.length === 0) {
return result;
}
uri = match[typeof matcher.matchIndex !== 'number' ? 0 : matcher.matchIndex];
rowStartIndex += match.index + uri.length;
}
}
}
/**
* Finds a link match in a piece of text.
* @param {string} text The text to search.
* @param {number} matchIndex The regex match index of the link.
* @return {string} The matching URI or null if not found.
*/
private _findLinkMatch(text: string, regex: RegExp, matchIndex?: number): string {
const match = text.match(regex);
if (!match || match.length === 0) {
return null;
}
return match[typeof matchIndex !== 'number' ? 0 : matchIndex];
return result;
}
/**
@@ -232,6 +263,7 @@ export class Linkifier {
private _createAnchorElement(uri: string, handler: LinkMatcherHandler, isHypertextLinkHandler: boolean): HTMLAnchorElement {
const element = this._document.createElement('a');
element.textContent = uri;
element.draggable = false;
if (isHypertextLinkHandler) {
element.href = uri;
// Force link on another tab so work is not lost
@@ -273,8 +305,9 @@ export class Linkifier {
* @param {Node} newNode The new node to insert.
* @param {string} substring The substring to replace.
* @param {number} substringIndex The index of the substring within the string.
* @return The number of nodes to skip when searching for the next uri.
*/
private _replaceNodeSubstringWithNode(targetNode: Node, newNode: Node, substring: string, substringIndex: number): void {
private _replaceNodeSubstringWithNode(targetNode: Node, newNode: Node, substring: string, substringIndex: number): number {
let node = targetNode;
if (node.nodeType !== 3/*Node.TEXT_NODE*/) {
node = node.childNodes[0];
@@ -283,7 +316,7 @@ export class Linkifier {
// The targetNode will be either a text node or a <span>. The text node
// (targetNode or its only-child) needs to be replaced with newNode plus new
// text nodes potentially on either side.
if (node.childNodes.length === 0 && node.nodeType !== Node.TEXT_NODE) {
if (node.childNodes.length === 0 && node.nodeType !== 3/*Node.TEXT_NODE*/) {
throw new Error('targetNode must be a text node or only contain a single text node');
}
@@ -294,18 +327,23 @@ export class Linkifier {
const rightText = fullText.substring(substring.length);
const rightTextNode = this._document.createTextNode(rightText);
this._replaceNode(node, newNode, rightTextNode);
} else if (substringIndex === targetNode.textContent.length - substring.length) {
return 0;
}
if (substringIndex === targetNode.textContent.length - substring.length) {
// Replace with <textnode><newNode>
const leftText = fullText.substring(0, substringIndex);
const leftTextNode = this._document.createTextNode(leftText);
this._replaceNode(node, leftTextNode, newNode);
} else {
// Replace with <textnode><newNode><textnode>
const leftText = fullText.substring(0, substringIndex);
const leftTextNode = this._document.createTextNode(leftText);
const rightText = fullText.substring(substringIndex + substring.length);
const rightTextNode = this._document.createTextNode(rightText);
this._replaceNode(node, leftTextNode, newNode, rightTextNode);
return 0;
}
// Replace with <textnode><newNode><textnode>
const leftText = fullText.substring(0, substringIndex);
const leftTextNode = this._document.createTextNode(leftText);
const rightText = fullText.substring(substringIndex + substring.length);
const rightTextNode = this._document.createTextNode(rightText);
this._replaceNode(node, leftTextNode, newNode, rightTextNode);
return 1;
}
}
+10 -8
View File
@@ -34,7 +34,7 @@ export class Renderer {
// Figure out whether boldness affects
// the character width of monospace fonts.
if (brokenBold === null) {
brokenBold = checkBoldBroken((<any>this._terminal).document);
brokenBold = checkBoldBroken((<any>this._terminal).element);
}
// TODO: Pull more DOM interactions into Renderer.constructor, element for
@@ -291,14 +291,16 @@ export class Renderer {
// if bold is broken, we can't
// use it in the terminal.
function checkBoldBroken(document) {
const body = document.getElementsByTagName('body')[0];
function checkBoldBroken(terminal) {
const document = terminal.ownerDocument;
const el = document.createElement('span');
el.innerHTML = 'hello world';
body.appendChild(el);
const w1 = el.scrollWidth;
terminal.appendChild(el);
const w1 = el.offsetWidth;
const h1 = el.offsetHeight;
el.style.fontWeight = 'bold';
const w2 = el.scrollWidth;
body.removeChild(el);
return w1 !== w2;
const w2 = el.offsetWidth;
const h2 = el.offsetHeight;
terminal.removeChild(el);
return w1 !== w2 || h1 !== h2;
}
+1 -1
View File
@@ -11,4 +11,4 @@ export type LinkMatcher = {
priority?: number
};
export type LinkMatcherHandler = (event: MouseEvent, uri: string) => boolean | void;
export type LinkMatcherValidationCallback = (uri: string, callback: (isValid: boolean) => void) => void;
export type LinkMatcherValidationCallback = (uri: string, element: HTMLElement, callback: (isValid: boolean) => void) => void;
+1 -1
View File
@@ -56,7 +56,7 @@
subjectRow.innerHTML = 'W'; // Common character for measuring width, although on monospace
characterWidth = subjectRow.getBoundingClientRect().width;
subjectRow.style.display = ''; // Revert style before calculating height, since they differ.
characterHeight = parseInt(subjectRow.offsetHeight);
characterHeight = subjectRow.getBoundingClientRect().height;
subjectRow.innerHTML = contentBuffer;
rows = parseInt(availableHeight / characterHeight);
+2 -1
View File
@@ -21,7 +21,8 @@ describe('xterm.js', function() {
};
xterm.element = {
classList: {
toggle: function(){}
toggle: function(){},
remove: function(){}
}
};
});
+9 -16
View File
@@ -86,7 +86,7 @@
text-decoration: none;
}
.terminal:not(.xterm-cursor-style-underline):not(.xterm-cursor-style-bar) .terminal-cursor {
.terminal.focus:not(.xterm-cursor-style-underline):not(.xterm-cursor-style-bar) .terminal-cursor {
background-color: #fff;
color: #000;
}
@@ -97,16 +97,9 @@
background-color: transparent;
}
.terminal:not(.xterm-cursor-style-underline):not(.xterm-cursor-style-bar).focus.xterm-cursor-blink .terminal-cursor {
animation: xterm-cursor-blink 1.2s infinite step-end;
}
@keyframes xterm-cursor-blink {
0% { }
50% {
background-color: transparent;
color: inherit;
}
.terminal:not(.xterm-cursor-style-underline):not(.xterm-cursor-style-bar).focus.xterm-cursor-blink-on .terminal-cursor {
background-color: transparent;
color: inherit;
}
.terminal.xterm-cursor-style-bar .terminal-cursor,
@@ -132,13 +125,13 @@
right: 0;
height: 1px;
}
.terminal.xterm-cursor-style-bar.focus.xterm-cursor-blink.xterm-cursor-blink-on .terminal-cursor::before,
.terminal.xterm-cursor-style-underline.focus.xterm-cursor-blink.xterm-cursor-blink-on .terminal-cursor::before {
background-color: transparent;
}
.terminal.xterm-cursor-style-bar.focus.xterm-cursor-blink .terminal-cursor::before,
.terminal.xterm-cursor-style-underline.focus.xterm-cursor-blink .terminal-cursor::before {
animation: xterm-cursor-non-bar-blink 1.2s infinite step-end;
}
@keyframes xterm-cursor-non-bar-blink {
0% { background-color: #fff; }
50% { background-color: transparent; }
background-color: #fff;
}
.terminal .composition-view {
+79 -17
View File
@@ -52,6 +52,13 @@ var WRITE_BUFFER_PAUSE_THRESHOLD = 5;
*/
var WRITE_BATCH_SIZE = 300;
/**
* The time between cursor blinks. This is driven by JS rather than a CSS
* animation due to a bug in Chromium that causes it to use excessive CPU time.
* See https://github.com/Microsoft/vscode/issues/22900
*/
var CURSOR_BLINK_INTERVAL = 600;
/**
* Terminal
*/
@@ -159,6 +166,7 @@ function Terminal(options) {
this.scrollTop = 0;
this.scrollBottom = this.rows - 1;
this.customKeydownHandler = null;
this.cursorBlinkInterval = null;
// modes
this.applicationKeypad = false;
@@ -211,7 +219,7 @@ function Terminal(options) {
this.parser = new Parser(this.inputHandler, this);
// Reuse renderer if the Terminal is being recreated via a Terminal.reset call.
this.renderer = this.renderer || null;
this.linkifier = this.linkifier || null;;
this.linkifier = this.linkifier || new Linkifier();
// user input states
this.writeBuffer = [];
@@ -423,7 +431,7 @@ Terminal.prototype.setOption = function(key, value) {
this[key] = value;
this.options[key] = value;
switch (key) {
case 'cursorBlink': this.element.classList.toggle('xterm-cursor-blink', value); break;
case 'cursorBlink': this.setCursorBlinking(value); break;
case 'cursorStyle':
// Style 'block' applies with no class
this.element.classList.toggle(`xterm-cursor-style-underline`, value === 'underline');
@@ -433,6 +441,29 @@ Terminal.prototype.setOption = function(key, value) {
}
};
Terminal.prototype.restartCursorBlinking = function () {
this.setCursorBlinking(this.options.cursorBlink);
};
Terminal.prototype.setCursorBlinking = function (enabled) {
this.element.classList.toggle('xterm-cursor-blink', enabled);
this.clearCursorBlinkingInterval();
if (enabled) {
var self = this;
this.cursorBlinkInterval = setInterval(function () {
self.element.classList.toggle('xterm-cursor-blink-on');
}, CURSOR_BLINK_INTERVAL);
}
};
Terminal.prototype.clearCursorBlinkingInterval = function () {
this.element.classList.remove('xterm-cursor-blink-on');
if (this.cursorBlinkInterval) {
clearInterval(this.cursorBlinkInterval);
this.cursorBlinkInterval = null;
}
};
/**
* Binds the desired focus behavior on a given terminal object.
*
@@ -445,6 +476,7 @@ Terminal.bindFocus = function (term) {
}
term.element.classList.add('focus');
term.showCursor();
term.restartCursorBlinking.apply(term);
Terminal.focus = term;
term.emit('focus', {terminal: term});
});
@@ -469,6 +501,7 @@ Terminal.bindBlur = function (term) {
term.send(C0.ESC + '[O');
}
term.element.classList.remove('focus');
term.clearCursorBlinkingInterval.apply(term);
Terminal.focus = null;
term.emit('blur', {terminal: term});
});
@@ -574,8 +607,9 @@ Terminal.prototype.insertRow = function (row) {
* Opens the terminal within an element.
*
* @param {HTMLElement} parent The element to create the terminal within.
* @param {boolean} focus Focus the terminal, after it gets instantiated in the DOM
*/
Terminal.prototype.open = function(parent) {
Terminal.prototype.open = function(parent, focus) {
var self=this, i=0, div;
this.parent = parent || this.parent;
@@ -594,9 +628,9 @@ Terminal.prototype.open = function(parent) {
this.element.classList.add('terminal');
this.element.classList.add('xterm');
this.element.classList.add('xterm-theme-' + this.theme);
this.element.classList.toggle('xterm-cursor-blink', this.options.cursorBlink);
this.setCursorBlinking(this.options.cursorBlink);
this.element.style.height
this.element.style.height;
this.element.setAttribute('tabindex', 0);
this.viewportElement = document.createElement('div');
@@ -612,7 +646,7 @@ Terminal.prototype.open = function(parent) {
this.rowContainer.classList.add('xterm-rows');
this.element.appendChild(this.rowContainer);
this.children = [];
this.linkifier = new Linkifier(document, this.children);
this.linkifier.attachToDom(document, this.children);
// Create the container that will hold helpers like the textarea for
// capturing DOM Events. Then produce the helpers.
@@ -663,8 +697,23 @@ Terminal.prototype.open = function(parent) {
// need to be taken on the document.
this.initGlobal();
// Ensure there is a Terminal.focus.
this.focus();
/**
* Automatic focus functionality.
* TODO: Default to `false` starting with xterm.js 3.0.
*/
if (typeof focus == 'undefined') {
let message = 'You did not pass the `focus` argument in `Terminal.prototype.open()`.\n';
message += 'The `focus` argument now defaults to `true` but starting with xterm.js 3.0 ';
message += 'it will default to `false`.';
console.warn(message);
focus = true;
}
if (focus) {
this.focus();
}
on(this.element, 'click', function() {
var selection = document.getSelection(),
@@ -1286,11 +1335,26 @@ Terminal.prototype.attachCustomKeydownHandler = function(customKeydownHandler) {
* reconstructed. Calling this with null will remove the handler.
* @param {LinkHandler} handler The handler callback function.
*/
Terminal.prototype.attachHypertextLinkHandler = function(handler) {
Terminal.prototype.setHypertextLinkHandler = function(handler) {
if (!this.linkifier) {
throw new Error('Cannot attach a hypertext link handler before Terminal.open is called');
}
this.linkifier.attachHypertextLinkHandler(handler);
this.linkifier.setHypertextLinkHandler(handler);
// Refresh to force links to refresh
this.refresh(0, this.rows - 1);
}
/**
* Attaches a validation callback for hypertext links. This is useful to use
* validation logic or to do something with the link's element and url.
* @param {LinkMatcherValidationCallback} callback The callback to use, this can
* be cleared with null.
*/
Terminal.prototype.setHypertextValidationCallback = function(handler) {
if (!this.linkifier) {
throw new Error('Cannot attach a hypertext validation callback before Terminal.open is called');
}
this.linkifier.setHypertextValidationCallback(handler);
// Refresh to force links to refresh
this.refresh(0, this.rows - 1);
}
@@ -1336,6 +1400,8 @@ Terminal.prototype.keyDown = function(ev) {
return false;
}
this.restartCursorBlinking();
if (!this.compositionHelper.keydown.bind(this.compositionHelper)(ev)) {
if (this.ybase !== this.ydisp) {
this.scrollToBottom();
@@ -1805,14 +1871,8 @@ Terminal.prototype.resize = function(x, y) {
this.lines.get(i).push(ch);
}
}
} else { // (j > x)
i = this.lines.length;
while (i--) {
while (this.lines.get(i).length > x) {
this.lines.get(i).pop();
}
}
}
this.cols = x;
this.setupStops(this.cols);
@@ -2149,8 +2209,10 @@ Terminal.prototype.reset = function() {
this.options.rows = this.rows;
this.options.cols = this.cols;
var customKeydownHandler = this.customKeydownHandler;
var cursorBlinkInterval = this.cursorBlinkInterval;
Terminal.call(this, this.options);
this.customKeydownHandler = customKeydownHandler;
this.cursorBlinkInterval = cursorBlinkInterval;
this.refresh(0, this.rows - 1);
this.viewport.syncScrollArea();
};
+1 -8
View File
@@ -12,13 +12,6 @@
"src/**/*"
],
"exclude": [
"src/addons/**/*",
"build",
"demo",
"dist",
"out",
"test",
"node_modules",
"docs"
"src/addons/**/*"
]
}