Compare commits

..
93 Commits
Author SHA1 Message Date
Paris c06b08df5b Bump version to 1.1.0
Signed-off-by: Paris <paris@sourcelair.com>
2016-08-22 11:22:21 +00:00
Daniel Imms fc16752cec Merge pull request #236 from Tyriar/235_fix_scroll_to_top_on_app_mode
Disable onScroll event during application mode
2016-08-21 00:58:47 -07:00
Daniel Imms 588d3b12e0 Disable onScroll event during application mode
Fixes #235
2016-08-19 12:52:13 -07:00
Paris Kasidiaris 6e8e78db57 Merge pull request #230 from ayapi/refresh-viewport-height
Refresh the height of the viewport when the line-height changed
2016-08-18 20:35:39 +03:00
ayapi dcc991bd5c refresh the height of the viewport when the line-height changed 2016-08-18 19:37:16 +09:00
Daniel Imms 04cb9f33ff Merge pull request #226 from Tyriar/208_fix_ime_popup_pos
Reposition textview after composition view
2016-08-17 08:50:41 -07:00
Paris Kasidiaris 9bd167c10d Merge pull request #231 from meefik/patch-1
Fix incorrect mouse position
2016-08-17 11:57:16 +03:00
Anton Skshidlevsky dc6aab88b0 Fix incorrect mouse position
Incorrect mouse position in Midnight Commander
2016-08-17 10:38:12 +03:00
Paris Kasidiaris 43fc6f1fa3 Merge pull request #228 from ayapi/restore-customkeydownhandler
restore customKeydownHandler on reset(RIS)
2016-08-16 17:20:40 +03:00
ayapi c2aa0d75ec restore customKeydownHandler on reset(RIS) 2016-08-13 22:44:56 +09:00
Daniel Imms c24ae84b00 Fix tests 2016-08-12 10:27:49 -07:00
Daniel Imms 92605a9734 Reposition textview after composition view
Call the function again via setTimeout to allow changes after composition
events. This prevents the IME windows jumping sometimes on thesecond key
press.

Fixes #208
2016-08-12 10:21:41 -07:00
Daniel Imms b32a34b97c Merge pull request #224 from sourcelair/Tyriar-patch-2
Sync scroll bar height if the viewport height changes
2016-08-09 09:03:16 -07:00
Daniel Imms 12923a6649 Sync scroll bar height if the viewport height changes 2016-08-08 14:41:39 -07:00
Daniel Imms e7c7160f85 Merge pull request #218 from Tyriar/77_scroll_bar
Implement scroll bar
2016-08-08 12:52:12 -07:00
Paris Kasidiaris 2a0962183e Merge pull request #223 from sourcelair/Tyriar-patch-1
By more explicit in security warning
2016-08-08 20:14:14 +01:00
Daniel Imms 95d9ad46cf By more explicit in security warning
Calling out that the insecurity is due to the barebones implementation
of the demo will prevent people thinking that the library is inherently
insecure.
2016-08-08 10:38:01 -07:00
Daniel Imms e60513f554 Fix outline showing up when clicking terminal 2016-08-08 10:22:34 -07:00
Daniel Imms a557a82b40 Fix scrollbar being transparent on OS X 2016-08-08 10:22:19 -07:00
Paris Kasidiaris 3dd6f368d6 Merge pull request #220 from sourcelair/improve-demo
Introduce resizing option in demo
2016-08-08 14:35:02 +01:00
Paris 5ce012bdac Add notice about security risks 2016-08-08 16:28:29 +03:00
Paris Kasidiaris a2338c49b9 Merge pull request #222 from sourcelair/signed-releases
Require release commits to be signed
2016-08-05 14:56:00 +01:00
Paris Kasidiaris dd6886f358 Merge pull request #221 from odino/patch-1
Update pty.js
2016-08-05 14:55:14 +01:00
Paris 5ddf6301c2 Add Sign-Off flag as well 2016-08-05 16:49:04 +03:00
Paris 14dd2c0afb Require release commits to be signed 2016-08-05 11:21:27 +00:00
Alessandro Nadalin fc2a0d3e65 Update pty.js 2016-08-05 13:59:21 +04:00
Paris 0f8fc63a5e First functional resizing demo
Fix #211
2016-08-05 12:28:22 +03:00
Paris f57a40eeb3 First batch of improvements to demo
- Used fetch tentatively
- Implemented resize endpoint
- Create terminals with the appropriate size
2016-08-05 08:46:49 +00:00
Daniel Imms 2c9c95f531 Add another test 2016-08-04 18:56:33 -07:00
Daniel Imms 7048f6edf7 Add some tests, fix app keypad mode bug 2016-08-04 18:34:04 -07:00
Paris Kasidiaris c333cfa073 Merge pull request #214 from mentos1386/mentos1386-linkify
Make linkify links open in new tab/window
2016-08-04 14:31:29 +01:00
Tine Jozelj 64d77d2491 Making it more human readable 2016-08-04 15:22:45 +02:00
Tine Jozelj 9604674c65 Default without target atribute 2016-08-04 15:05:09 +02:00
Tine Jozelj d9294779a6 Add target as option
You can now provide target argument if you want to modify target attribute on links. It defaults to `_self` to keep current behavior.
2016-08-04 14:58:42 +02:00
Paris Kasidiaris 3117fe9465 Merge pull request #219 from sourcelair/issue/198
Update README and remove screenshot
2016-08-04 11:07:20 +01:00
Paris 0a74b9aa3b Fix typos 2016-08-04 13:00:47 +03:00
Paris 7e90576391 Update README and remove screenshot 2016-08-04 12:01:37 +03:00
Daniel Imms 0a34885fbc Fix line-height after rowContainer moved out of viewport 2016-08-03 18:27:43 -07:00
Daniel Imms 61201525a8 Remaining jsdoc 2016-08-03 18:27:19 -07:00
Daniel Imms 1b886a4420 Disable the scroll bar when in application keypad mode 2016-08-03 18:12:44 -07:00
Daniel Imms 6f70984ab6 Drop support for old mouse wheel APIs
All browsers have supported the WheelEvent (onwheel) for sometime now, since
Firefox does not support onmousewheel which is also non-standard but works with
the standard interface, it makes sense to drop support now.
2016-08-03 13:41:55 -07:00
Daniel Imms 38fa2d78da Support DOMMouseScroll event (Firefox) 2016-08-03 13:00:07 -07:00
Daniel Imms fe905bace5 Prevent the page from scrolling when the terminal scrolls 2016-08-03 12:49:12 -07:00
Daniel Imms e7e1434293 Clean up, refresh sizes when font-size changes 2016-08-03 12:47:36 -07:00
Daniel Imms 363c647a5f Sync scroll bar on more sensible events 2016-08-03 11:27:16 -07:00
Daniel Imms aac60eb029 Add support for WheelEvent.deltaMode 2016-08-03 10:16:08 -07:00
Daniel Imms 06ca03ae9c Get it working 2016-08-02 18:36:24 -07:00
Daniel Imms ff927b8e79 Implement basic scroll bar 2016-08-02 16:59:50 -07:00
Daniel Imms 670b0d58c7 Add base viewport, set line height
Fixes #149
2016-08-02 12:42:13 -07:00
Tine Jozelj 3113cdf0ea Make linkify links open in new tab/window
I believe this behavior is more appropriate.
2016-08-02 15:57:15 +02:00
Paris Kasidiaris d2cef6f29d Merge pull request #210 from Tyriar/209_hide_textarea_when_not_composing
Hide textarea off-screen when not composing
2016-08-01 11:35:06 +03:00
Daniel Imms d62bd5f748 Get rid of rogue whitespace 2016-07-28 11:15:20 -07:00
Daniel Imms e6fd1381ce Hide textarea off-screen when not composing
Fixes #209
2016-07-28 11:13:24 -07:00
Daniel Imms 65b2be7831 Merge pull request #186 from Tyriar/118_support_custom_keydown_handler
Allow custom keydown handler to be attached
2016-07-22 05:21:28 -07:00
Daniel Imms e2d0de2686 Fix test 2016-07-22 05:18:28 -07:00
Daniel Imms 495b2d7fbb Merge remote-tracking branch 'upstream/master' into 118_support_custom_keydown_handler 2016-07-22 05:12:52 -07:00
Paris Kasidiaris 5e662d0eb7 Merge pull request #203 from Tyriar/124_fix_focus_blur
Correct blur/focus delegation
2016-07-22 14:26:48 +03:00
Daniel Imms 892db98c96 Correct blur/focus delegation
Focus and blur events on the Terminal object are delegated to the textarea
which handles everything.
2016-07-22 04:14:05 -07:00
Paris Kasidiaris 10fbf0dccc Merge pull request #175 from Tyriar/124_add_textarea_back
Add textarea back and support IMEs
2016-07-22 11:50:23 +03:00
Daniel Imms 4595a18197 Tidy up blur/focus handlers 2016-07-21 10:51:43 -07:00
Daniel Imms 577a3d81e5 Use explicit boolean comparison 2016-07-21 10:32:20 -07:00
Paris Kasidiaris 55fb5dbcb5 Merge pull request #200 from jerch/fix_193
fixing arrow key sequences
2016-07-21 15:00:17 +03:00
Jörg Breitbart 8e7eeff45c fixing test cases 2016-07-21 12:47:19 +02:00
Jörg Breitbart ec7130b46f fixing arrow key sequences 2016-07-21 12:39:36 +02:00
Daniel Imms 0010a5a109 Explicitly return from keydown 2016-07-15 16:33:30 -07:00
Daniel Imms 9cb5b005bb Add tests 2016-07-15 16:31:57 -07:00
Daniel Imms 06a3aa6cf8 Merge remote-tracking branch 'upstream/master' into 118_support_custom_keydown_handler 2016-07-15 16:21:06 -07:00
Daniel Imms d4e9d34d2d Allow custom keydown handler to be attached
Fixes #118
2016-07-15 16:16:37 -07:00
Daniel Imms a3a7017f6b Fix tests 2016-07-15 10:22:31 -07:00
Daniel Imms fedc1fd325 Fix keydown conditional 2016-07-15 10:17:29 -07:00
Daniel Imms 11d5f51e67 Merge remote-tracking branch 'upstream/master' into 124_add_textarea_back 2016-07-15 10:13:56 -07:00
Daniel Imms 534a9e7f5a Big clean up
Move keydown event to CompositionHelper
Move call in refresh to be handled with an event
2016-07-13 21:50:00 -07:00
Daniel Imms 14a62c0f8b Add test cases for Japanese 2016-07-13 21:22:27 -07:00
Daniel Imms 89d29bbc10 Fix non-composition char input after composition char + test 2016-07-13 21:02:43 -07:00
Daniel Imms ac8db6e8ec jsdoc handleAnyTextareaChanges 2016-07-13 20:52:55 -07:00
Daniel Imms 0a7c1bfd98 Position the textarea below the cursor during a composition
This will position any IME helper in the correct position
2016-07-13 20:46:43 -07:00
Daniel Imms 4efb8d24ac Don't wrap multi-character compositions 2016-07-13 20:34:55 -07:00
Daniel Imms 38d08873bc Don't finalize composition on modifier key presses 2016-07-13 20:34:43 -07:00
Daniel Imms 8074d754d3 Only position composition view if a cursor is visible 2016-07-13 20:12:49 -07:00
Daniel Imms e1c1b07a0b Add test cases for Korean 2016-07-13 08:37:24 -07:00
Daniel Imms c18794f202 Merge remote-tracking branch 'upstream/master' into 124_add_textarea_back 2016-07-13 07:14:14 -07:00
Daniel Imms 8b46e84263 Use correct .terminal-cursor for composition view 2016-07-12 21:42:10 -07:00
Daniel Imms 237e6819bf Fix tests 2016-07-12 20:55:24 -07:00
Daniel Imms 8faea59e31 Handle non-composition input when IME is active 2016-07-12 20:51:55 -07:00
Daniel Imms cab79c473f jsdoc and tidy up 2016-07-12 20:26:18 -07:00
Daniel Imms c656ed04b0 Use handler not write, fix other edge cases 2016-07-12 20:19:26 -07:00
Daniel Imms 29000fb79e Refactor into CompositionHelper object 2016-07-12 19:14:56 -07:00
Daniel Imms 03fa017d4a Re-hide the textarea 2016-07-12 18:49:37 -07:00
Daniel Imms 26af6ffd4d Get IMEs working 2016-07-12 18:48:08 -07:00
Daniel Imms 7d8e28a7dd Merge remote-tracking branch 'upstream/master' into 124_add_textarea_back 2016-07-12 17:02:11 -07:00
Daniel Imms a52b7e7a4e Add starting point for IME support 2016-07-12 16:50:07 -07:00
Daniel Imms aaedcfc700 Support focus class on the .xterm element 2016-07-12 16:18:05 -07:00
Daniel Imms fc7b22dc48 Manually revert textarea changes 2016-07-12 16:03:31 -07:00
16 changed files with 1162 additions and 290 deletions
+4
View File
@@ -1,8 +1,11 @@
List of xterm.js contributors. Updated before every release.
Alessandro Nadalin <alessandro.nadalin@gmail.com>
Alexander Olsson <noseglid@gmail.com>
Antonis Kalipetis <akalipetis@sourcelair.com>
Anton Skshidlevsky <meefik@gmail.com>
Austin Robertson <austinrobertson@gmail.com>
ayapi <colors.aya@gmail.com>
Benjamin Fischer <benjamin.fischer@rwth-aachen.de>
Carson Anderson <carson@betterservers.com>
Christopher Jeffrey <chjjeffrey@gmail.com>
@@ -17,3 +20,4 @@ Paris Kasidiaris <paris@sourcelair.com>
runarberg <runar@greenqloud.com>
Steven Silvester <steven.silvester@ieee.org>
Thanasis Daglis <thanasis@sourcelair.com>
Tine Jozelj <tine.jozelj@outlook.com>
+31 -6
View File
@@ -2,17 +2,34 @@
![xterm.js build status](https://api.travis-ci.org/sourcelair/xterm.js.svg)
Xterm.js is a full xterm clone, written in JavaScript.
Xterm.js is a terminal front-end component written in JavaScript that works in the browser.
It is used at [SourceLair](https://www.sourcelair.com/home) to help people develop their applications in their browsers.
It enables applications to provide fully featured terminals to their users and create great development experiences.
Xterm.js supplies a modular, event-based interface that lets developers build addons and themes on top of it.
## Features
- **Text-based application support**: Use xterm.js to work with applications like `bash`, `git` etc.
- **Curses-based application support**: Use xterm.js to work with applications like `vim`, `tmux` etc.
- **Mouse events support**: Xterm.js captures mouse events like click and scroll and passes them to the terminal's back-end controlling process
- **CJK (Chinese, Japanese, Korean) character support**: Xterm.js renders CJK characters seamlessly
- **IME support**: Insert international (including CJK) characters using IME input with your keyboard
- **Self-contained library**: Xterm.js works on its own. It does not require any external libraries like jQuery or React to work
- **Modular, event-based API**: Lets you build addons and themes with ease
![xterm.js screenshot](xtermjs.png)
## What xterm.js is not
- Xterm.js is not a terminal application that you can download and use on your computer
- Xterm.js is not `bash`. Xterm.js can be connected to processes like `bash` and let you interact with them (provide input, receive output)
## Real-world uses
Xterm.js is used in several world-class applications to provide great terminal experiences.
- [**SourceLair**](https://www.sourcelair.com/): In-browser IDE that provides its users with fully-featured Linux terminals based on xterm.js
- [**Microsoft Visual Studio Code**](http://code.visualstudio.com/): Modern, versatile, powerful and open source code editor that provides integrated terminal based on xterm.js
Do you use xterm.js in your application as well? Please [open a Pull Request](https://github.com/sourcelair/xterm.js/pulls) to include it here. We would love to have it in our list.
## Browser Support
Since xterm.js is typically implemented as a developer tool, only modern browsers are supported officially. Here is a list of the versions we aim to support:
Since xterm.js is typically implemented as a developer tool, only modern browsers are supported officially. Here is a list of the versions we aim to support:
- Chrome 48+
- Edge 13+
@@ -21,7 +38,7 @@ Since xterm.js is typically implemented as a developer tool, only modern browser
- Opera 35+
- Safari 8+
xterm.js may work on earlier versions of the browsers but these are the browsers we strive to keep working.
Xterm.js works seamlessly in Electron apps and may even work on earlier versions of the browsers but these are the browsers we strive to keep working.
## Demo
@@ -51,8 +68,16 @@ var xterm = new Terminal();
xterm.fit();
```
## Releases
Xterm.js follows a monthly release cycle roughly.
The existing releases are available at this GitHub repo's [Releases](https://github.com/sourcelair/xterm.js/releases), while the roadmap is available as [Milestones](https://github.com/sourcelair/xterm.js/milestones).
## Development and Contribution
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).
+22 -8
View File
@@ -54,10 +54,12 @@
* "linkified".
* @param {boolean} lenient - The regex type that will be used to identify links. If lenient is
* false, the regex requires a protocol clause. Defaults to true.
* @param {string} target - Sets target="" attribute with value provided to links.
* Default doesn't set target attribute
* @emits linkify
* @emits linkify:line
*/
exports.linkifyTerminalLine = function (terminal, line, lenient) {
exports.linkifyTerminalLine = function (terminal, line, lenient, target) {
if (typeof line == 'number') {
line = terminal.rowContainer.children[line];
} else if (! (line instanceof HTMLDivElement)) {
@@ -67,6 +69,12 @@
throw new TypeError(message);
}
if (typeof target === 'undefined') {
target = '';
} else {
target = 'target="' + target + '"';
}
var buffer = document.createElement('span'),
nodes = line.childNodes;
@@ -100,7 +108,7 @@
var startsWithProtocol = new RegExp('^' + protocolClause),
urlHasProtocol = url.match(startsWithProtocol),
href = (urlHasProtocol) ? url : 'http://' + url,
link = '<a href="' + href + '" >' + url + '</a>',
link = '<a href="' + href + '" ' + target + '>' + url + '</a>',
newHTML = nodeHTML.replace(url, link);
line.innerHTML = line.innerHTML.replace(nodeHTML, newHTML);
@@ -137,17 +145,19 @@
* @param {Xterm} terminal - The terminal that should get "linkified".
* @param {boolean} lenient - The regex type that will be used to identify links. If lenient is
* false, the regex requires a protocol clause. Defaults to true.
* @param {string} target - Sets target="" attribute with value provided to links.
* Default doesn't set target attribute
* @emits linkify
* @emits linkify:line
*/
exports.linkify = function (terminal, lenient) {
exports.linkify = function (terminal, lenient, target) {
var rows = terminal.rowContainer.children;
lenient = (typeof lenient == "boolean") ? lenient : true;
for (var i=0; i<rows.length; i++) {
var line = rows[i];
exports.linkifyTerminalLine(terminal, line, lenient);
exports.linkifyTerminalLine(terminal, line, lenient, target);
}
/**
@@ -173,9 +183,11 @@
* "linkified".
* @param {boolean} lenient - The regex type that will be used to identify links. If lenient is
* false, the regex requires a protocol clause. Defaults to true.
* @param {string} target - Sets target="" attribute with value provided to links.
* Default doesn't set target attribute
*/
Xterm.prototype.linkifyTerminalLine = function (line, lenient) {
return exports.linkifyTerminalLine(this, line, lenient);
Xterm.prototype.linkifyTerminalLine = function (line, lenient, target) {
return exports.linkifyTerminalLine(this, line, lenient, target);
};
/**
@@ -184,9 +196,11 @@
* @memberof Xterm
* @param {boolean} lenient - The regex type that will be used to identify links. If lenient is
* false, the regex requires a protocol clause. Defaults to true.
* @param {string} target - Sets target="" attribute with value provided to links.
* Default doesn't set target attribute
*/
Xterm.prototype.linkify = function (lenient) {
return exports.linkify(this, lenient);
Xterm.prototype.linkify = function (lenient, target) {
return exports.linkify(this, lenient, target);
};
return exports;
+1 -1
View File
@@ -24,4 +24,4 @@ sed -i "s/\"version\": \"$CURRENT_BOWER_JSON_VERSION\"/\"version\": \"$NEW_VERSI
# Update AUTHORS file
sh bin/generate-authors
git commit -a -m "Bump version to $NEW_VERSION"
git commit -S -s -a -m "Bump version to $NEW_VERSION"
+1 -1
View File
@@ -1,5 +1,5 @@
{
"name": "xterm.js",
"version": "1.0.0",
"version": "1.1.0",
"ignore": ["demo", "test", ".gitignore"]
}
+43 -11
View File
@@ -4,6 +4,9 @@ var expressWs = require('express-ws')(app);
var os = require('os');
var pty = require('pty.js');
var terminals = {},
logs = {};
app.use('/src', express.static(__dirname + '/../src'));
app.use('/addons', express.static(__dirname + '/../addons'));
@@ -19,17 +22,43 @@ app.get('/main.js', function(req, res){
res.sendFile(__dirname + '/main.js');
});
app.ws('/bash', function(ws, req) {
/**
* Open bash terminal and attach it
*/
var term = pty.spawn(process.platform === 'win32' ? 'cmd.exe' : 'bash', [], {
name: 'xterm-color',
cols: 80,
rows: 24,
cwd: process.env.PWD,
env: process.env
app.post('/terminals', function (req, res) {
var cols = parseInt(req.query.cols),
rows = parseInt(req.query.rows),
term = pty.spawn(process.platform === 'win32' ? 'cmd.exe' : 'bash', [], {
name: 'xterm-color',
cols: cols || 80,
rows: rows || 24,
cwd: process.env.PWD,
env: process.env
});
console.log('Created terminal with PID: ' + term.pid);
terminals[term.pid] = term;
logs[term.pid] = '';
term.on('data', function(data) {
logs[term.pid] += data;
});
res.send(term.pid.toString());
res.end();
});
app.post('/terminals/:pid/size', function (req, res) {
var pid = parseInt(req.params.pid),
cols = parseInt(req.query.cols),
rows = parseInt(req.query.rows),
term = terminals[pid];
term.resize(cols, rows);
console.log('Resized terminal ' + pid + ' to ' + cols + ' cols and ' + rows + ' rows.');
res.end();
});
app.ws('/terminals/:pid', function (ws, req) {
var term = terminals[parseInt(req.params.pid)];
console.log('Connected to terminal ' + term.pid);
ws.send(logs[term.pid]);
term.on('data', function(data) {
try {
ws.send(data);
@@ -41,8 +70,11 @@ app.ws('/bash', function(ws, req) {
term.write(msg);
});
ws.on('close', function () {
console.log('close');
process.kill(term.pid);
console.log('Closed terminal ' + term.pid);
// Clean things up
delete terminals[term.pid];
delete logs[term.pid];
});
});
+16 -3
View File
@@ -5,20 +5,33 @@
<link rel="stylesheet" href="../src/xterm.css" />
<link rel="stylesheet" href="../addons/fullscreen/fullscreen.css" />
<link rel="stylesheet" href="style.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/fetch/1.0.0/fetch.min.js"></script>
<script src="../src/xterm.js" ></script>
<script src="../addons/attach/attach.js" ></script>
<script src="../addons/fit/fit.js" ></script>
<script src="../addons/fullscreen/fullscreen.js" ></script>
</head>
<body>
<h1>
xterm.js: xterm, in the browser
</h1>
<h1>xterm.js: xterm, in the browser</h1>
<div id="terminal-container"></div>
<div>
<h2>Options</h2>
<label><input type="checkbox" id="option-cursor-blink"> cursorBlink</label>
<div>
<h3>Size</h3>
<div>
<div style="display: inline-block; margin-right: 16px;">
<label for="cols">Columns</label>
<input type="number" id="cols" />
</div>
<div style="display: inline-block; margin-right: 16px;">
<label for="rows">Rows</label>
<input type="number" id="rows" />
</div>
</div>
</div>
</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>
<script src="main.js" defer ></script>
</body>
</html>
+58 -13
View File
@@ -1,34 +1,81 @@
var term,
protocol,
socketURL,
socket;
socket,
pid,
charWidth,
charHeight;
var terminalContainer = document.getElementById('terminal-container');
var optionElements = {
cursorBlink: document.querySelector('#option-cursor-blink')
};
var terminalContainer = document.getElementById('terminal-container'),
optionElements = {
cursorBlink: document.querySelector('#option-cursor-blink')
},
colsElement = document.getElementById('cols'),
rowsElement = document.getElementById('rows');
function setTerminalSize () {
var cols = parseInt(colsElement.value),
rows = parseInt(rowsElement.value),
width = (cols * charWidth).toString() + 'px',
height = (rows * charHeight).toString() + 'px';
terminalContainer.style.width = width;
terminalContainer.style.height = height;
term.resize(cols, rows);
}
colsElement.addEventListener('change', setTerminalSize);
rowsElement.addEventListener('change', setTerminalSize);
optionElements.cursorBlink.addEventListener('change', createTerminal);
createTerminal();
function createTerminal() {
// Clean terminal
while (terminalContainer.children.length) {
terminalContainer.removeChild(terminalContainer.children[0]);
}
term = new Terminal({
cursorBlink: optionElements.cursorBlink.checked
});
term.on('resize', function (size) {
if (!pid) {
return;
}
var cols = size.cols,
rows = size.rows,
url = '/terminals/' + pid + '/size?cols=' + cols + '&rows=' + rows;
fetch(url, {method: 'POST'});
});
protocol = (location.protocol === 'https:') ? 'wss://' : 'ws://';
socketURL = protocol + location.hostname + ((location.port) ? (':' + location.port) : '') + '/bash';
socket = new WebSocket(socketURL);
socketURL = protocol + location.hostname + ((location.port) ? (':' + location.port) : '') + '/terminals/';
term.open(terminalContainer);
term.fit();
socket.onopen = runRealTerminal;
socket.onclose = runFakeTerminal;
socket.onerror = runFakeTerminal;
var initialGeometry = term.proposeGeometry(),
cols = initialGeometry.cols,
rows = initialGeometry.rows;
colsElement.value = cols;
rowsElement.value = rows;
fetch('/terminals?cols=' + cols + '&rows=' + rows, {method: 'POST'}).then(function (res) {
charWidth = Math.ceil(term.element.offsetWidth / cols);
charHeight = Math.ceil(term.element.offsetHeight / rows);
res.text().then(function (pid) {
window.pid = pid;
socketURL += pid;
socket = new WebSocket(socketURL);
socket.onopen = runRealTerminal;
socket.onclose = runFakeTerminal;
socket.onerror = runFakeTerminal;
});
});
}
@@ -64,9 +111,7 @@ function runFakeTerminal() {
if (ev.keyCode == 13) {
term.prompt();
} else if (ev.keyCode == 8) {
/*
* Do not delete the prompt
*/
// Do not delete the prompt
if (term.x > 2) {
term.write('\b \b');
}
+2 -2
View File
@@ -9,8 +9,8 @@ h1 {
}
#terminal-container {
width: 960px;
height: 600px;
width: 800px;
height: 450px;
margin: 0 auto;
padding: 2px;
}
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "xterm",
"version": "1.0.0",
"version": "1.1.0",
"ignore": [
"demo",
"test",
@@ -17,7 +17,7 @@
"glob": "^7.0.5",
"jsdoc": "3.4.0",
"mocha": "2.5.3",
"pty.js": "0.3.0",
"pty.js": "0.3.1",
"sleep": "^3.0.1"
},
"scripts": {
+56 -10
View File
@@ -43,22 +43,41 @@
position: relative;
}
.terminal.focus,
.terminal:focus {
outline: none;
}
.terminal .xterm-helpers {
position: absolute;
top: 0;
}
.terminal .xterm-helper-textarea {
position: absolute;
/*
* HACK: to fix IE's blinking cursor
* Move textarea out of the screen to the far left, so that the cursor is not visible.
*/
left: -9999em;
opacity: 0;
width: 0;
height: 0;
z-index: -10;
}
.terminal .terminal-cursor {
background-color: #fff;
color: #000;
}
.terminal:not(:focus) .terminal-cursor {
.terminal:not(.focus) .terminal-cursor {
outline: 1px solid #fff;
outline-offset: -1px;
background-color: transparent;
}
.terminal:focus .terminal-cursor.blinking {
.terminal.focus .terminal-cursor.blinking {
animation: blink-cursor 1.2s infinite step-end;
}
@@ -73,6 +92,41 @@
}
}
.terminal .composition-view {
background: #000;
color: #FFF;
display: none;
position: absolute;
white-space: nowrap;
}
.terminal .composition-view.active {
display: block;
}
.terminal .xterm-viewport {
/* On OS X this is required in order for the scroll bar to appear fully opaque */
background-color: #000;
overflow-y: scroll;
}
.terminal .xterm-rows {
position: absolute;
left: 0;
top: 0;
}
.terminal .xterm-scroll-area {
visibility: hidden;
}
.terminal .xterm-char-measure-element {
display: inline-block;
visibility: hidden;
position: absolute;
left: -9999em;
}
/*
* Determine default colors for xterm.js
*/
@@ -2139,11 +2193,3 @@
.terminal .xterm-bg-color-255 {
background-color: #eeeeee;
}
/**
* All terminal rows should have explicitly declared height,
* in order to allow child elements to adjust.
*/
.terminal .xterm-rows > div {
line-height: normal;
}
+503 -229
View File
File diff suppressed because it is too large Load Diff
+263
View File
@@ -0,0 +1,263 @@
var assert = require('chai').assert;
var Terminal = require('../src/xterm');
describe('CompositionHelper', function () {
var terminal;
var compositionHelper;
var compositionView;
var textarea;
var handledText;
beforeEach(function () {
compositionView = {
classList: {
add: function () {},
remove: function () {},
},
getBoundingClientRect: function () {
return { width: 0 }
},
style: {
left: 0,
top: 0
},
textContent: ''
};
textarea = {
value: '',
style: {
left: 0,
top: 0
}
};
terminal = {
element: {
querySelector: function () {
return { offsetLeft: 0, offsetTop: 0 };
}
},
handler: function (text) {
handledText += text;
}
};
handledText = '';
compositionHelper = new Terminal.CompositionHelper(textarea, compositionView, terminal);
});
describe('Public API', function () {
it('should define CompositionHelper.prototype.compositionstart', function () {
assert.isDefined(Terminal.CompositionHelper.prototype.compositionstart);
});
it('should define CompositionHelper.prototype.compositionupdate', function () {
assert.isDefined(Terminal.CompositionHelper.prototype.compositionupdate);
});
it('should define CompositionHelper.prototype.compositionend', function () {
assert.isDefined(Terminal.CompositionHelper.prototype.compositionend);
});
it('should define CompositionHelper.prototype.finalizeComposition', function () {
assert.isDefined(Terminal.CompositionHelper.prototype.finalizeComposition);
});
it('should define CompositionHelper.prototype.handleAnyTextareaChanges', function () {
assert.isDefined(Terminal.CompositionHelper.prototype.handleAnyTextareaChanges);
});
it('should define CompositionHelper.prototype.updateCompositionElements', function () {
assert.isDefined(Terminal.CompositionHelper.prototype.updateCompositionElements);
});
it('should define CompositionHelper.isComposing', function () {
assert.isDefined(compositionHelper.isComposing);
});
it('should define CompositionHelper.isSendingComposition', function () {
assert.isDefined(compositionHelper.isSendingComposition);
});
});
describe('Input', function () {
it('Should insert simple characters', function (done) {
// First character 'ㅇ'
compositionHelper.compositionstart();
compositionHelper.compositionupdate({ data: 'ㅇ' });
textarea.value = 'ㅇ';
setTimeout(function() { // wait for any textarea updates
compositionHelper.compositionend();
setTimeout(function() { // wait for any textarea updates
assert.equal(handledText, 'ㅇ');
// Second character 'ㅇ'
compositionHelper.compositionstart();
compositionHelper.compositionupdate({ data: 'ㅇ' });
textarea.value = 'ㅇㅇ';
setTimeout(function() { // wait for any textarea updates
compositionHelper.compositionend();
setTimeout(function() { // wait for any textarea updates
assert.equal(handledText, 'ㅇㅇ');
done();
}, 0);
}, 0);
}, 0);
}, 0);
});
it('Should insert complex characters', function (done) {
// First character '앙'
compositionHelper.compositionstart();
compositionHelper.compositionupdate({ data: 'ㅇ' });
textarea.value = 'ㅇ';
setTimeout(function() { // wait for any textarea updates
compositionHelper.compositionupdate({ data: '아' });
textarea.value = '아';
setTimeout(function() { // wait for any textarea updates
compositionHelper.compositionupdate({ data: '앙' });
textarea.value = '앙';
setTimeout(function() { // wait for any textarea updates
compositionHelper.compositionend();
setTimeout(function() { // wait for any textarea updates
assert.equal(handledText, '앙');
// Second character '앙'
compositionHelper.compositionstart();
compositionHelper.compositionupdate({ data: 'ㅇ' });
textarea.value = '앙ㅇ';
setTimeout(function() { // wait for any textarea updates
compositionHelper.compositionupdate({ data: '아' });
textarea.value = '앙아';
setTimeout(function() { // wait for any textarea updates
compositionHelper.compositionupdate({ data: '앙' });
textarea.value = '앙앙';
setTimeout(function() { // wait for any textarea updates
compositionHelper.compositionend();
setTimeout(function() { // wait for any textarea updates
assert.equal(handledText, '앙앙');
done();
}, 0);
}, 0);
}, 0);
}, 0);
}, 0);
}, 0);
}, 0);
}, 0);
});
it('Should insert complex characters that change with following character', function (done) {
// First character '아'
compositionHelper.compositionstart();
compositionHelper.compositionupdate({ data: 'ㅇ' });
textarea.value = 'ㅇ';
setTimeout(function() { // wait for any textarea updates
compositionHelper.compositionupdate({ data: '아' });
textarea.value = '아';
setTimeout(function() { // wait for any textarea updates
// Start second character '아' in first character
compositionHelper.compositionupdate({ data: '앙' });
textarea.value = '앙';
setTimeout(function() { // wait for any textarea updates
compositionHelper.compositionend();
compositionHelper.compositionstart();
compositionHelper.compositionupdate({ data: '아' });
textarea.value = '아아'
setTimeout(function() { // wait for any textarea updates
compositionHelper.compositionend();
setTimeout(function() { // wait for any textarea updates
assert.equal(handledText, '아아');
done();
}, 0);
}, 0);
}, 0);
}, 0);
}, 0);
});
it('Should insert multi-characters compositions', function (done) {
// First character 'だ'
compositionHelper.compositionstart();
compositionHelper.compositionupdate({ data: 'd' });
textarea.value = 'd';
setTimeout(function() { // wait for any textarea updates
compositionHelper.compositionupdate({ data: 'だ' });
textarea.value = 'だ';
setTimeout(function() { // wait for any textarea updates
// Second character 'あ'
compositionHelper.compositionupdate({ data: 'だあ' });
textarea.value = 'だあ';
setTimeout(function() { // wait for any textarea updates
compositionHelper.compositionend();
setTimeout(function() { // wait for any textarea updates
assert.equal(handledText, 'だあ');
done();
}, 0);
}, 0);
}, 0);
}, 0);
});
it('Should insert multi-character compositions that are converted to other characters with the same length', function (done) {
// First character 'だ'
compositionHelper.compositionstart();
compositionHelper.compositionupdate({ data: 'd' });
textarea.value = 'd';
setTimeout(function() { // wait for any textarea updates
compositionHelper.compositionupdate({ data: 'だ' });
textarea.value = 'だ';
setTimeout(function() { // wait for any textarea updates
// Second character 'ー'
compositionHelper.compositionupdate({ data: 'だー' });
textarea.value = 'だー';
setTimeout(function() { // wait for any textarea updates
// Convert to katakana 'ダー'
compositionHelper.compositionupdate({ data: 'ダー' });
textarea.value = 'ダー';
setTimeout(function() { // wait for any textarea updates
compositionHelper.compositionend();
setTimeout(function() { // wait for any textarea updates
assert.equal(handledText, 'ダー');
done();
}, 0);
}, 0);
}, 0);
}, 0);
}, 0);
})
it('Should insert multi-character compositions that are converted to other characters with different lengths', function (done) {
// First character 'い'
compositionHelper.compositionstart();
compositionHelper.compositionupdate({ data: 'い' });
textarea.value = 'い';
setTimeout(function() { // wait for any textarea updates
// Second character 'ま'
compositionHelper.compositionupdate({ data: 'いm' });
textarea.value = 'いm';
setTimeout(function() { // wait for any textarea updates
compositionHelper.compositionupdate({ data: 'いま' });
textarea.value = 'いま';
setTimeout(function() { // wait for any textarea updates
// Convert to kanji '今'
compositionHelper.compositionupdate({ data: '今' });
textarea.value = '今';
setTimeout(function() { // wait for any textarea updates
compositionHelper.compositionend();
setTimeout(function() { // wait for any textarea updates
assert.equal(handledText, '今');
done();
}, 0);
}, 0);
}, 0);
}, 0);
}, 0);
});
it('Should insert non-composition characters input immediately after composition characters', function (done) {
// First character 'ㅇ'
compositionHelper.compositionstart();
compositionHelper.compositionupdate({ data: 'ㅇ' });
textarea.value = 'ㅇ';
setTimeout(function() { // wait for any textarea updates
compositionHelper.compositionend();
// Second character '1' (a non-composition character)
textarea.value = 'ㅇ1';
setTimeout(function() { // wait for any textarea updates
assert.equal(handledText, 'ㅇ1');
done();
}, 0);
}, 0);
});
});
});
+54 -4
View File
@@ -24,8 +24,8 @@ describe('xterm.js', function() {
assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 33 }).key, '\x1b[5~'); // CSI 5 ~
assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 34 }).key, '\x1b[6~'); // CSI 6 ~
// End, Home
assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 35 }).key, '\x1bOF'); // SS3 F
assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 36 }).key, '\x1bOH'); // SS3 H
assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 35 }).key, '\x1b[F'); // SS3 F
assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 36 }).key, '\x1b[H'); // SS3 H
// Left, up, right, down arrows
assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 37 }).key, '\x1b[D'); // CSI D
assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 38 }).key, '\x1b[A'); // CSI A
@@ -50,10 +50,52 @@ describe('xterm.js', function() {
assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 123 }).key, '\x1b[24~'); // CSI 2 4 ~
});
it('should return \\x1b[5D for ctrl+left', function() {
assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 37 }).key, '\x1b[5D'); // CSI 5 D
assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 37 }).key, '\x1b[1;5D'); // CSI 5 D
});
it('should return \\x1b[5C for ctrl+right', function() {
assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 39 }).key, '\x1b[5C'); // CSI 5 C
assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 39 }).key, '\x1b[1;5C'); // CSI 5 C
});
});
describe('attachCustomEventHandler', function () {
var evKeyDown = {
preventDefault: function() {},
stopPropagation: function() {},
type: 'keydown'
}
beforeEach(function() {
xterm.handler = function() {};
xterm.showCursor = function() {};
xterm.clearSelection = function() {};
xterm.compositionHelper = {
keydown: {
bind: function() {
return function () { return true; }
}
}
}
});
it('should process the keydown event based on what the handler returns', function () {
assert.equal(xterm.keyDown(Object.assign({}, evKeyDown, { keyCode: 77 })), true);
xterm.attachCustomKeydownHandler(function (ev) {
return ev.keyCode === 77;
});
assert.equal(xterm.keyDown(Object.assign({}, evKeyDown, { keyCode: 77 })), true);
xterm.attachCustomKeydownHandler(function (ev) {
return ev.keyCode !== 77;
});
assert.equal(xterm.keyDown(Object.assign({}, evKeyDown, { keyCode: 77 })), false);
});
it('should alive after reset(ESC c Full Reset (RIS))', function () {
xterm.attachCustomKeydownHandler(function (ev) {
return ev.keyCode !== 77;
});
assert.equal(xterm.keyDown(Object.assign({}, evKeyDown, { keyCode: 77 })), false);
xterm.reset();
assert.equal(xterm.keyDown(Object.assign({}, evKeyDown, { keyCode: 77 })), false);
});
});
@@ -87,6 +129,14 @@ describe('xterm.js', function() {
xterm.handler = function() {};
xterm.showCursor = function() {};
xterm.clearSelection = function() {};
xterm.compositionHelper = {
isComposing: false,
keydown: {
bind: function() {
return function() { return true; };
}
}
};
});
describe('On Mac OS', function() {
+106
View File
@@ -0,0 +1,106 @@
var assert = require('chai').assert;
var Terminal = require('../src/xterm');
describe('Viewport', function () {
var terminal;
var viewportElement;
var charMeasureElement;
var viewport;
var CHARACTER_HEIGHT = 10;
beforeEach(function () {
terminal = {
lines: [],
rows: 0,
ydisp: 0,
on: function () {},
rowContainer: {
style: {
lineHeight: 0
}
}
};
viewportElement = {
addEventListener: function () {},
style: {
height: 0,
lineHeight: 0
}
};
scrollAreaElement = {
style: {
height: 0
}
};
charMeasureElement = {
getBoundingClientRect: function () {
return { width: null, height: CHARACTER_HEIGHT };
}
};
viewport = new Terminal.Viewport(terminal, viewportElement, scrollAreaElement, charMeasureElement);
});
describe('Public API', function () {
it('should define Viewport.prototype.onWheel', function () {
assert.isDefined(Terminal.Viewport.prototype.onWheel);
});
it('should define Viewport.prototype.setApplicationMode', function () {
assert.isDefined(Terminal.Viewport.prototype.setApplicationMode);
});
});
describe('setApplicationMode', function () {
it('should restrict the scroll area to the viewport', function () {
terminal.lines.push('');
terminal.lines.push('');
terminal.rows = 1;
viewport.syncScrollArea();
assert.equal(scrollAreaElement.style.height, 2 * CHARACTER_HEIGHT + 'px');
viewport.setApplicationMode(true);
assert.equal(scrollAreaElement.style.height, CHARACTER_HEIGHT + 'px');
viewport.setApplicationMode(false);
assert.equal(scrollAreaElement.style.height, 2 * CHARACTER_HEIGHT + 'px');
});
});
describe('refresh', function () {
it('should set the line-height of the terminal', function () {
assert.equal(viewportElement.style.lineHeight, CHARACTER_HEIGHT + 'px');
assert.equal(terminal.rowContainer.style.lineHeight, CHARACTER_HEIGHT + 'px');
charMeasureElement.getBoundingClientRect = function () {
return { width: null, height: 1 };
};
viewport.refresh();
assert.equal(viewportElement.style.lineHeight, '1px');
assert.equal(terminal.rowContainer.style.lineHeight, '1px');
});
it('should set the height of the viewport when the line-height changed', function () {
terminal.lines.push('');
terminal.lines.push('');
terminal.rows = 1;
viewport.refresh();
assert.equal(viewportElement.style.height, 1 * CHARACTER_HEIGHT + 'px');
charMeasureElement.getBoundingClientRect = function () {
return { width: null, height: 20 };
};
viewport.refresh();
assert.equal(viewportElement.style.height, 20 + 'px');
});
});
describe('syncScrollArea', function () {
it('should sync the scroll area', function () {
terminal.lines.push('');
terminal.rows = 1;
assert.equal(scrollAreaElement.style.height, 0 * CHARACTER_HEIGHT + 'px');
viewport.syncScrollArea();
assert.equal(viewportElement.style.height, 1 * CHARACTER_HEIGHT + 'px');
assert.equal(scrollAreaElement.style.height, 1 * CHARACTER_HEIGHT + 'px');
terminal.lines.push('');
viewport.syncScrollArea();
assert.equal(viewportElement.style.height, 1 * CHARACTER_HEIGHT + 'px');
assert.equal(scrollAreaElement.style.height, 2 * CHARACTER_HEIGHT + 'px');
});
});
});
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 212 KiB