Compare commits

...
139 Commits
Author SHA1 Message Date
Paris Kasidiaris 8a146b5401 Bump version to 2.2.2
Signed-off-by: Paris Kasidiaris <paris@sourcelair.com>
2016-12-15 17:16:04 +00:00
Paris Kasidiaris 3a6368ba6c Merge pull request #411 from sourcelair/issue-#408-tslint
Add tslint
2016-12-15 12:13:23 +00:00
Paris Kasidiaris fbc314a74b Parallelise build 2016-12-15 14:05:37 +02:00
Paris Kasidiaris 850a238ca2 Include lint test into travis 2016-12-15 14:02:38 +02:00
Paris Kasidiaris 20b6f20908 Make linter happy 2016-12-15 13:57:25 +02:00
Paris Kasidiaris 33f7d7649a Add tslint
Fix #408
2016-12-15 13:53:26 +02:00
Paris Kasidiaris fb4d263211 Merge pull request #409 from sourcelair/issue-#405-firefox-paste
Fix linux firefox paste
2016-12-14 10:38:30 +00:00
Paris Kasidiaris ab87dece0b Fix weird paste issue: #405
Fix #405
2016-12-14 01:45:53 +02:00
Paris Kasidiaris 69e027394d 😱 Fix wrong event type and remove useless interface 2016-12-14 01:43:32 +02:00
Paris Kasidiaris 6639b2c731 Merge pull request #407 from sourcelair/build-ts-into-lib
Always build TypeScript in `lib/`
2016-12-13 23:32:49 +00:00
Paris Kasidiaris e23956cd77 Do not publish test files to npm 2016-12-13 23:28:33 +00:00
Paris Kasidiaris 56ca7743a8 Always build TypeScript in lib/ 2016-12-14 01:10:03 +02:00
Paris Kasidiaris fb17d57549 Merge pull request #398 from budde377/feat-npm
Add prepublish script and change main
2016-12-13 22:59:58 +00:00
Christian Budde Christensen e7045f2b8a Add prepublish script and change main
Building TS before publish and pointing the main entry to the builded files.
2016-12-13 20:34:19 +01:00
Paris Kasidiaris 852f587bd2 Merge pull request #400 from arcanis/patch-1
Use a fixed size to account for the scrollbar
2016-12-11 10:25:35 +00:00
Maël Nison e0e6f5e302 Use a fixed size to account for the scrollbar 2016-12-10 14:46:01 +01:00
Paris Kasidiaris 06a5ffdd1a Merge pull request #399 from bottleofwater/patch-1
Don't apply the 32 delta when in sgr mode - fix #394
2016-12-09 10:12:42 +00:00
bottleofwater a75bafc4ba Don't apply the 32 delta when in sgr mode - fix #394 2016-12-09 10:52:09 +01:00
Paris Kasidiaris 60a59be4d3 Bump version to 2.2.1
Signed-off-by: Paris Kasidiaris <paris@sourcelair.com>
2016-12-07 18:43:03 +00:00
Paris Kasidiaris b76b2d01eb Checkout add-ons 2016-12-07 18:42:40 +00:00
Paris Kasidiaris cd70d298fc Bump version to 2.2.0
Signed-off-by: Paris Kasidiaris <paris@sourcelair.com>
2016-12-07 18:41:37 +00:00
Paris Kasidiaris 70d0ed3ebf Merge pull request #393 from sourcelair/migrate-clipboard-to-typescript
Migrate Clipboard handler to TypeScript
2016-12-07 18:37:00 +00:00
Paris Kasidiaris 5a5c6ab621 Merge pull request #392 from sourcelair/fix-readme-addon-path
Update path to addons in README.md
2016-12-06 22:08:30 +00:00
Paris Kasidiaris a34bb9724f Improve interfaces in Clipboard.ts 2016-12-07 00:07:50 +02:00
Paris Kasidiaris 824a9c6dbf Migrate Clipboard handler to TypeScript
Also apply JavaScript rules in `.editorconfig` in TypeScript files as well.

Part of #335
2016-12-06 10:01:06 +00:00
Paris Kasidiaris c9eaae6bd5 Update path to addons in README.md 2016-12-06 10:55:40 +02:00
Daniel Imms 9f5790fcaf Merge pull request #391 from Tyriar/357_textarea_top_zero
Position the textarea at top:0
2016-12-05 11:47:55 -08:00
Paris Kasidiaris 6aeb0435ce Merge pull request #390 from sourcelair/issue-#375-bring-build-dir-back
Bring back the `build` directory for temporary builds
2016-12-05 19:35:08 +00:00
Paris Kasidiaris 70ec2496db Remove export from build script 2016-12-05 21:29:50 +02:00
Daniel Imms 55314e0fc7 Position the textarea at top:0
This prevents the browser from scrolling to the top of the page if the terminal
can be scrolled out of view.

Fixes #357
2016-12-05 11:19:36 -08:00
Paris Kasidiaris 243d76617b Create the build directory if it does not exist already 2016-12-05 13:34:10 +02:00
Paris Kasidiaris 6cc66a0b20 Bring back the build directory for temporary builds
Fix #375
2016-12-05 13:17:13 +02:00
Paris Kasidiaris 1e928df531 Merge pull request #388 from sourcelair/issue-#366
Update documentation with cleaner license declaration
2016-12-01 19:41:58 +02:00
Daniel Imms db9bec02f5 Merge pull request #382 from sourcelair/Tyriar-patch-1
Add link to TravisCI badge
2016-12-01 09:19:06 -08:00
Daniel Imms 0dc033692f Merge pull request #383 from Tyriar/381_clean_out_on_build
Clean out/* on build in case src/* files were deleted
2016-12-01 09:18:56 -08:00
Paris Kasidiaris 1d30091188 Update documentation with cleaner license declaration
Fix #366
2016-12-01 13:37:20 +00:00
Daniel Imms 64f80f8623 Merge pull request #380 from Tyriar/378_handle_keypress_scroll
Scroll to bottom whenever handler is called
2016-11-28 15:55:27 -08:00
Daniel Imms 83b06c6bb0 Clean out/* on build in case src/* files were deleted
If files in src/ were compiled and then deleted they will hang around in out/
until deleted. This can potentially cause difficult to diagnose build/test
errors. This commit removes all files from out/ but keeps the folder to
prevent editor file watchers from seeing the folder was deleted and shifting
the file explorer around.

Fixed #381
2016-11-28 10:52:03 -08:00
Daniel Imms 5cbc2f0ea5 Add link to TravisCI badge 2016-11-28 10:46:19 -08:00
Daniel Imms 2bc8adee8c Scroll to bottom whenever handler is called
This catches all current and future cases for correctly scrolling to the bottom
on input by handling it in the handler immediately before emitting the data
event.

Fixes #378
2016-11-28 10:41:05 -08:00
Daniel Imms c6eb7f136c Merge pull request #371 from Tyriar/335_CompositionHelper_ts
Convert CompositionHelper to TypeScript
2016-11-28 08:21:12 -08:00
Daniel Imms 48b87c2f40 Merge remote-tracking branch 'upstream/master' into 335_CompositionHelper_ts 2016-11-28 08:16:38 -08:00
Daniel Imms ca818fbf04 Merge pull request #379 from Tyriar/378_cannot_scroll_up_1plus_pages
Only scroll to bottom during composition or result.key is evaluated
2016-11-28 08:07:00 -08:00
Daniel Imms 264fe4edc7 Merge pull request #372 from Tyriar/335_viewport_tidy
Clean up viewport more after TS conversion
2016-11-28 08:06:50 -08:00
Daniel Imms 317c0ec2de Merge pull request #377 from Tyriar/376_ctrl_backslash_sigquit
Implement ^\ String Terminator
2016-11-28 08:06:42 -08:00
Daniel Imms 3b2e89d878 Only scroll to bottom during composition or result.key is evaluated
Fixes #378
2016-11-27 01:49:45 -08:00
Daniel Imms 15a94240eb Implement ^\ String Terminator
Fixes #376
2016-11-26 14:59:53 -08:00
Daniel Imms f75b7db90e Move CompositionHelper header comment to class 2016-11-22 11:54:42 -08:00
Daniel Imms 81dc48ef00 Clean up viewport more after TS conversion
Part of #335
2016-11-22 11:53:45 -08:00
Daniel Imms 3638ef808b tsify CompositionHelper test 2016-11-22 11:50:08 -08:00
Daniel Imms f24cba722a Rename composition-helper-test.js -> CompositionHelper.test.ts 2016-11-22 11:48:16 -08:00
Daniel Imms 30fcdd6ccb tsify CompositionHelper 2016-11-22 11:47:18 -08:00
Daniel Imms b0df236dea Rename CompositionHelper.js -> CompositionHelper.ts 2016-11-22 11:36:34 -08:00
Daniel Imms d91e33ff39 Merge pull request #369 from Tyriar/368_custom_handler_before_scroll
Evaluate the custom keydown event handler before scrolling down
2016-11-22 10:29:43 -08:00
Daniel Imms 1ee316ca83 Merge remote-tracking branch 'upstream/master' into 368_custom_handler_before_scroll 2016-11-22 10:23:04 -08:00
Daniel Imms dcbf014402 Merge pull request #367 from Tyriar/363_modifier_keys_no_scroll
Prevent keyboard modifier events from scrolling down
2016-11-22 08:00:23 -08:00
Daniel Imms 20b0d4fdcc Merge pull request #370 from Tyriar/335_Viewport_ts
Convert Viewport to TypeScript
2016-11-22 07:59:46 -08:00
Daniel Imms 2da1255c26 Merge pull request #355 from Tyriar/354_fix_sourcemaps
Set up source maps
2016-11-21 15:27:38 -08:00
Daniel Imms 5bddb93745 Adopt more TS syntax in Viewport.test.ts 2016-11-21 14:44:10 -08:00
Daniel Imms 51932161af Move viewport-test.js to Viewport.test.ts 2016-11-21 14:39:58 -08:00
Daniel Imms e0d9871195 Convert Viewport to a TS class 2016-11-21 14:39:08 -08:00
Daniel Imms 634d4af4dd Rename Viewport.js -> Viewport.ts
Part of #335
2016-11-21 14:27:41 -08:00
Daniel Imms a507e4f002 Add test for not scrolling on custom handler 2016-11-21 14:23:28 -08:00
Daniel Imms c15fed382d Evaluate the custom keydown event handler before scrolling down
Fixes #368
2016-11-21 14:04:13 -08:00
Daniel Imms 9937d54410 Prevent keyboard modifier events from scrolling down
Fixes #363
2016-11-21 14:01:20 -08:00
Daniel Imms 64f6610f11 Merge pull request #353 from Tyriar/352_fix_demo_after_ts
Point to correct assets after TS build
2016-11-16 09:56:35 -08:00
Daniel Imms e6f21f0bfc Make all dist/ references in index.html absolute 2016-11-14 10:35:20 -08:00
Daniel Imms c63732ef5f Set up source maps
Fixes #354
2016-11-12 14:24:57 -08:00
Daniel Imms 99496de242 Point to correct assets after TS build 2016-11-12 14:01:38 -08:00
Daniel Imms 29cb576737 Merge pull request #296 from Tyriar/typescript_build_2
Move to TypeScript build
2016-11-12 13:51:12 -08:00
Daniel Imms bf4ef599ad Merge remote-tracking branch 'upstream/master' into typescript_build_2 2016-11-12 13:46:59 -08:00
Daniel Imms 5ddb33419e Merge pull request #1 from Tyriar/typescript_build_3
Refactor TS build and fix testing
2016-11-12 11:39:24 -08:00
Paris Kasidiaris bc10fbdfae Merge pull request #351 from yuvipanda/docsome
Add doc comments for *some* events
2016-11-12 12:53:02 +02:00
YuviPanda 6dcf7267a5 Add doc comment for scroll event 2016-11-11 16:28:25 -08:00
YuviPanda 5712365cac Add docstring for the open event 2016-11-11 16:27:10 -08:00
YuviPanda 1fc5a9aada Add docstring for the title event 2016-11-11 16:27:01 -08:00
Daniel Imms 38cd1bd6d7 Merge pull request #350 from sourcelair/Tyriar-patch-1
Make ttyd link bold like the others
2016-11-11 08:31:44 -08:00
Daniel Imms 771642d17d Make ttyd link bold like the others 2016-11-09 19:34:36 -08:00
Paris Kasidiaris bb19b2040b Merge pull request #347 from tsl0922/update-readme
Add ttyd to the real-world uses section of README
2016-11-09 14:52:56 +02:00
Shuanglei Tao 12840dae6c Add ttyd to the real-world uses section of README 2016-11-09 20:23:36 +08:00
Paris Kasidiaris 521d7dbdaa Bump version to 2.1.0
Signed-off-by: Paris Kasidiaris <paris@sourcelair.com>
2016-11-08 22:51:33 +00:00
Paris Kasidiaris 2cba2ff3d4 Merge pull request #345 from hiro-su/fix-ie11-right-click-paste
Fix the right click paste in IE11
2016-11-08 13:21:12 +02:00
hiro-su 547db92693 Fix the right click paste in IE11 2016-11-08 14:56:57 +09:00
Paris Kasidiaris bf334238b7 Merge pull request #339 from hiro-su/fix-ie11-copy-event
Fixed copy event in IE11
2016-11-05 15:27:50 +00:00
hiro-su 5808de648b Fixed copy event in IE11 2016-11-05 23:55:34 +09:00
Paris Kasidiaris 9ab0455b05 Merge pull request #342 from sourcelair/issue-#310-cross-browser-right-click
Make right-click work on all browsers
2016-11-04 20:40:53 +00:00
Paris Kasidiaris 55ab36662a Merge pull request #336 from sourcelair/issue-#216-no-auto-scroll-on-output
Stop auto scrolling to bottom on output
2016-11-04 19:55:07 +00:00
Paris Kasidiaris b1ad564b0d Actually fix tests 2016-11-04 21:48:53 +02:00
Paris Kasidiaris bc70b3b37d Fix tests 2016-11-04 19:36:30 +02:00
Paris Kasidiaris d426c99866 Move all browser/platform detecting in utils/Browser.js 2016-11-04 18:41:00 +02:00
Paris Kasidiaris 356377975b Fix right-click for Firefox and IE
Fix #310
2016-11-04 18:33:04 +02:00
Paris Kasidiaris 0de3d839e6 Improve experience and add test
- Scroll to bottom on keydown (when scrolled up)
- Add test to ensure above behavior
2016-11-04 12:29:17 +00:00
Paris Kasidiaris 0fcd6b1d22 Merge pull request #337 from IanLewis/master
Add main property to bower.json
2016-11-04 09:35:24 +00:00
Ian Lewis 99b5464392 Add main property to bower.json
This patch adds the main property to the project's bower.json for better
interoperability with tools like wiredep etc.
2016-11-03 17:56:58 +05:30
Paris Kasidiaris 70965a8fd6 Merge pull request #334 from hiro-su/fix-ie11-paste-key
Fixed paste key in IE11
2016-11-02 09:19:18 +00:00
hiro-su 77ca15497c Fixed paste key in IE11 2016-11-02 11:17:37 +09:00
Daniel Imms 56ecc77dfc Refactor TS build and fix testing
This change is largely just moving files with minor tweaks to them to fix,
the rest of the commit is build process changes:

- The addons/ and test/ dirs have been moved to src/
- The build directory has been removed
- TypeScript builds are output in out/, this is where tests are run
- The demo now relies on the dist/ build which is performed as part of ./bin/build
- Addons are now shipped under the ./build directory
2016-10-31 21:22:21 -07:00
Daniel Imms a995b610cf Remove typings (not needed in TS 2+) 2016-10-31 19:30:35 -07:00
Daniel Imms 332bc6be76 Merge remote-tracking branch 'upstream/master' into typescript_build_2 2016-10-31 19:29:36 -07:00
Paris Kasidiaris 26de9fe4c5 Merge pull request #332 from sourcelair/issue-#331-fix-no-selection-range
Consider click not on selection, when s.rangeCount is 0
2016-10-31 18:52:48 +00:00
Paris Kasidiaris 00dcb4f6e3 Consider click not on selection, when s.rangeCount is 0 2016-10-31 15:06:00 +02:00
Daniel Imms 996c641a79 Merge pull request #328 from Tyriar/327_fix_random_line_wrap
Force nowrap on each line
2016-10-27 07:52:48 -07:00
Daniel Imms a077860464 Force nowrap on each line
Fixes #327
2016-10-26 18:07:55 -07:00
Mikko Karvonen 5e68acfc76 Quick fix to prevent terminal scrolling when user is looking into scrollback. 2016-10-25 13:54:57 +03:00
Daniel Imms 269540bfed Merge pull request #323 from Tyriar/322_ime_bad_position
Fix the textarea in the composition view's pos when composing
2016-10-24 14:58:41 -07:00
Daniel Imms 868f15173d Fix the textarea in the composition view's pos when composing
Fixes #322
2016-10-24 11:06:25 -07:00
Paris Kasidiaris 06a93344bb Merge pull request #321 from sourcelair/issue-#313-document-terminal-constructor
Issue #313 document terminal constructor
2016-10-24 19:01:37 +03:00
Paris Kasidiaris e8e7870be2 Remove geometry option from terminal consttructor 2016-10-23 17:55:37 +03:00
Paris Kasidiaris a9417c6878 Fix term.geometry and document it in constructor options
Set `term.geometry` on terminal creation and on resize
2016-10-23 14:22:00 +00:00
Paris Kasidiaris 605d61b64f Ignore .env files that set the environment 2016-10-23 14:22:00 +00:00
Daniel Imms 8feaacf57e Merge pull request #320 from Tyriar/299_keep_textarea_in_viewport
Correctly size and position the composition view
2016-10-23 07:16:28 -07:00
Daniel Imms 553c651166 Correctly size and position the composition view 2016-10-22 16:27:17 -07:00
Daniel Imms 1ec0bac9c6 Keep composition textarea within viewport
Part of #299
2016-10-22 15:41:55 -07:00
Paris Kasidiaris b1c6ae8a06 Merge pull request #303 from staticfloat/sf/terminado
Add `Terminado` example
2016-10-17 21:03:49 +03:00
Daniel Imms 8a124d0b35 Merge pull request #308 from Tyriar/294_scroll
Implement scrollPages, scrollToTop, scrollToBottom and add scroll tests
2016-10-10 08:33:09 -07:00
Daniel Imms 0ad02a4afa Add note that negative scrolls up on scrollPages 2016-10-10 08:25:01 -07:00
Daniel Imms 9389f899d7 Merge pull request #309 from Tyriar/fix_indentation
Fix jsdoc indentation
2016-10-10 08:20:56 -07:00
Paris Kasidiaris 050a34921b Merge pull request #304 from sourcelair/update-addons-paths
Make add-ons load Terminal from dist
2016-10-10 10:12:50 +03:00
Daniel Imms 32e878db81 Fix jsdoc indentation 2016-10-09 13:23:22 -07:00
Daniel Imms 0bf7bf56a5 Add jsdoc to scrollTo functions 2016-10-09 13:20:33 -07:00
Daniel Imms e5d130b698 Add scrollToBottom 2016-10-09 13:19:11 -07:00
Daniel Imms 941e57a218 Add tests for scrollPages 2016-10-09 13:10:18 -07:00
Daniel Imms c02eea6ad3 Add tests for scrollDisp 2016-10-09 13:08:08 -07:00
Daniel Imms fe0d878b23 Implement scrollPages 2016-10-09 12:51:58 -07:00
Paris Kasidiaris 763f148fa1 Remove __dirname usage from loadAddon 2016-10-09 13:17:17 +03:00
Elliot Saba 8f0ada708f Make naming a little more self-consistent 2016-10-08 20:09:34 -07:00
Paris Kasidiaris 8117999ab3 Fix tests 2016-10-08 12:22:18 +03:00
Elliot Saba 10dd22a3d4 Rename Terminado attach and detach functions 2016-10-08 00:36:08 -07:00
Daniel Imms 738f37e86c Set travis to node 6 2016-10-07 23:01:20 -07:00
Daniel Imms 729c0abf04 Explicitly indicate test locations 2016-10-07 22:56:02 -07:00
Elliot Saba fe54da34f8 Add terminado.js, an addon for connecting to Terminado backends 2016-10-07 13:33:10 -07:00
Daniel Imms 09de0f31fa Merge pull request #301 from Tyriar/300_top_textarea_helper
Add top to textarea helper
2016-10-05 09:33:08 -07:00
Paris Kasidiaris 6ee524864e Make add-ons load the latest built version 2016-10-05 13:23:45 +03:00
Elliot Saba 8fd350f0fc Add Terminado example 2016-10-05 00:32:39 -07:00
Daniel Imms ebbeeefd86 Add top to textarea helper
Fixes #300
2016-10-04 19:03:15 -07:00
Paris Kasidiaris 76a890aa74 Bump version to 2.0.1
Signed-off-by: Paris Kasidiaris <paris@sourcelair.com>
2016-10-03 12:39:39 +00:00
Paris Kasidiaris 5ce522a401 Add built files 2016-10-03 12:39:14 +00:00
Daniel Imms bd994f9e57 Install typings on build and add to git ignore 2016-10-01 12:22:57 -07:00
Daniel Imms 441009d359 Move to TypeScript build 2016-10-01 00:29:33 -07:00
203 changed files with 9070 additions and 666 deletions
-3
View File
@@ -1,3 +0,0 @@
{
"presets": ["es2015"]
}
+1 -1
View File
@@ -6,7 +6,7 @@ indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.js]
[*.{j,t}s]
max_line_length = 100
[*.css]
+3 -2
View File
@@ -1,8 +1,7 @@
node_modules/
*.swp
build/*
.lock-wscript
out/
lib/
Makefile.gyp
*.Makefile
*.target.gyp.mk
@@ -11,3 +10,5 @@ example/*.log
docs/
npm-debug.log
/.idea/
.env
build/
+16
View File
@@ -0,0 +1,16 @@
node_modules/
*.swp
.lock-wscript
lib/*.test.js
lib/*.test.js.map
lib/test/
Makefile.gyp
*.Makefile
*.target.gyp.mk
*.node
example/*.log
docs/
npm-debug.log
/.idea/
.env
build/
+7 -2
View File
@@ -1,8 +1,12 @@
language: node_js
node_js:
- 4
- 6
env:
- CXX=g++-4.8
global:
- CXX=g++-4.8
matrix:
- NPM_COMMAND=lint
- NPM_COMMAND=test
addons:
apt:
sources:
@@ -11,3 +15,4 @@ addons:
- g++-4.8
notifications:
email: false
script: npm run $NPM_COMMAND
+9
View File
@@ -8,18 +8,27 @@ Anton Yurovskykh <anton.yurovskykh@gmail.com>
Austin Robertson <austinrobertson@gmail.com>
ayapi <colors.aya@gmail.com>
Benjamin Fischer <benjamin.fischer@rwth-aachen.de>
bottleofwater <nison.mael+bottleofwater@gmail.com>
Carson Anderson <carson@betterservers.com>
Christian Budde Christensen <budde377@gmail.com>
Christopher Jeffrey <chjjeffrey@gmail.com>
Daniel Imms <daimms@microsoft.com>
Daniel Risacher <drisacher@gmail.com>
Dan Kaplun <dbkaplun@twitch.tv>
Darin Morrison <freebroccolo@users.noreply.github.com>
Elliot Saba <staticfloat@gmail.com>
hiro-su <h.sugipon@gmail.com>
Ian Lewis <ianlewis@google.com>
imoses <ido@twiggle.com>
Jean Bruenn <himself@jeanbruenn.info>
Jörg Breitbart <jerch@rockborn.de>
Maël Nison <nison.mael@gmail.com>
Mikko Karvonen <mikko.karvonen@arm.com>
Paris Kasidiaris <pariskasidiaris@gmail.com>
Paris Kasidiaris <paris@sourcelair.com>
runarberg <runar@greenqloud.com>
Shuanglei Tao <tsl0922@gmail.com>
Steven Silvester <steven.silvester@ieee.org>
Thanasis Daglis <thanasis@sourcelair.com>
Tine Jozelj <tine.jozelj@outlook.com>
YuviPanda <yuvipanda@gmail.com>
+4 -3
View File
@@ -1,6 +1,6 @@
# xterm.js
![xterm.js build status](https://api.travis-ci.org/sourcelair/xterm.js.svg) [![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)
[![xterm.js build status](https://api.travis-ci.org/sourcelair/xterm.js.svg)](https://travis-ci.org/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)
Xterm.js is a terminal front-end component written in JavaScript that works in the browser.
@@ -24,6 +24,7 @@ Xterm.js is used in several world-class applications to provide great terminal e
- [**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 and powerful open source code editor that provides an integrated terminal based on xterm.js
- [**ttyd**](https://github.com/tsl0922/ttyd): A command-line tool for sharing terminal over the web, with fully-featured terminal emulation 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.
@@ -53,13 +54,13 @@ Then open http://0.0.0.0:3000 in a web browser (use http://127.0.0.1:3000 if run
## Addons
Addons are JavaScript modules that attach functions to the `Terminal` prototype to extend its functionality. There are a handful available in the main repository in the `addons` directory, you can even write your own (though they may break when the internals of xterm.js change across versions).
Addons are JavaScript modules that attach functions to the `Terminal` prototype to extend its functionality. There are a handful available in the main repository in the `dist/addons` directory, you can even write your own (though they may break when the internals of xterm.js change across versions).
To use an addon, just include the JavaScript file after xterm.js and before the `Terminal` object has been instantiated. The function should then be exposed on the `Terminal` object:
```html
<script src="node_modules/dist/xterm.js"></script>
<script src="node_modules/addons/fit/fit.js"></script>
<script src="node_modules/dist/addons/fit/fit.js"></script>
```
```js
+25 -28
View File
@@ -1,38 +1,35 @@
#! /usr/bin/env node
#! /usr/bin/env bash
var child_process = require('child_process');
var fs = require('fs');
set -e
var buildDir = process.env.BUILD_DIR || 'build';
# $BUILD_DIR should default to "build"
BUILD_DIR=${BUILD_DIR:=build}
if (!fs.existsSync(buildDir)){
fs.mkdirSync(buildDir);
}
# Create the build directory
mkdir -p $BUILD_DIR
// Add `node_modules/.bin` to PATH
process.env.PATH = process.cwd() + '/node_modules/.bin:' + process.env.PATH;
console.log('Building xterm.js into ' + buildDir);
# Clean lib/* to prevent confusion if files were deleted in src/
rm -rf lib/*
// Build ES2015 modules into ES5 form, then concatenate them,
// then remove unused require calls and save in output file with source map.
console.log(' - Building ' + buildDir + '/xterm.js...');
# Build all TypeScript files (including tests) to lib/
tsc
var jsBuildCmd = 'browserify src/xterm.js -s Terminal -t [ babelify --presets [ es2015 ] ] --debug | ';
jsBuildCmd += 'derequire | exorcist ' + buildDir + '/xterm.js.map > ' + buildDir + '/xterm.js';
# Concat all xterm.js files into a single file and output as a UMD to $BUILD_DIR/xterm.js
browserify ./lib/xterm.js --standalone Terminal --debug --outfile ./$BUILD_DIR/xterm.js
cat ./$BUILD_DIR/xterm.js | exorcist ./$BUILD_DIR/xterm.js.map -b ./$BUILD_DIR > ./$BUILD_DIR/xterm.temp.js
rm ./$BUILD_DIR/xterm.js
mv ./$BUILD_DIR/xterm.temp.js ./$BUILD_DIR/xterm.js
var jsBuildProcess = child_process.execSync(jsBuildCmd);
# Resolve the chain of sourcemaps so that ./$BUILD_DIR/xterm.js.map points at ./src
sorcery -i $BUILD_DIR/xterm.js
if (jsBuildProcess.status) {
console.log(jsBuildProcess.error);
}
# Copy all CSS files from src/ to $BUILD_DIR/
cd src
find . -name '*.css' | cpio -pdm ../$BUILD_DIR
cd ..
console.log(' OK.');
// Copy CSS into $BUILD_DIR
console.log(' - Building ' + buildDir + '/xterm.css...');
fs.createReadStream('src/xterm.css').pipe(
fs.createWriteStream(buildDir + '/xterm.css')
);
console.log(' OK.');
# Copy addons from lib/ to $BUILD_DIR/
cd lib/addons
find . -name '*.js' | cpio -pdm ../../$BUILD_DIR/addons
cd ../..
+6 -2
View File
@@ -1,5 +1,9 @@
{
"name": "xterm.js",
"version": "2.0.0",
"ignore": ["demo", "test", ".gitignore"]
"version": "2.2.2",
"ignore": ["demo", "test", ".gitignore"],
"main": [
"dist/xterm.js",
"dist/xterm.css"
]
}
-1
View File
@@ -8,7 +8,6 @@ var terminals = {},
logs = {};
app.use('/build', express.static(__dirname + '/../build'));
app.use('/addons', express.static(__dirname + '/../addons'));
app.get('/', function(req, res){
res.sendFile(__dirname + '/index.html');
+6 -6
View File
@@ -2,14 +2,14 @@
<html>
<head>
<title>xterm.js demo</title>
<link rel="stylesheet" href="../build/xterm.css" />
<link rel="stylesheet" href="../addons/fullscreen/fullscreen.css" />
<link rel="stylesheet" href="/build/xterm.css" />
<link rel="stylesheet" href="/build/addons/fullscreen/fullscreen.css" />
<link rel="stylesheet" href="style.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/fetch/1.0.0/fetch.min.js"></script>
<script src="../build/xterm.js" ></script>
<script src="../addons/attach/attach.js" ></script>
<script src="../addons/fit/fit.js" ></script>
<script src="../addons/fullscreen/fullscreen.js" ></script>
<script src="/build/xterm.js" ></script>
<script src="/build/addons/attach/attach.js" ></script>
<script src="/build/addons/fit/fit.js" ></script>
<script src="/build/addons/fullscreen/fullscreen.js" ></script>
</head>
<body>
<h1>xterm.js: xterm, in the browser</h1>
+114
View File
@@ -0,0 +1,114 @@
/**
* Implements the attach method, that attaches the terminal to a WebSocket stream.
* @module xterm/addons/attach/attach
* @license MIT
*/
(function (attach) {
if (typeof exports === 'object' && typeof module === 'object') {
/*
* CommonJS environment
*/
module.exports = attach(require('../../xterm'));
}
else if (typeof define == 'function') {
/*
* Require.js is available
*/
define(['../../xterm'], attach);
}
else {
/*
* Plain browser environment
*/
attach(window.Terminal);
}
})(function (Xterm) {
'use strict';
var exports = {};
/**
* Attaches the given terminal to the given socket.
*
* @param {Xterm} term - The terminal to be attached to the given socket.
* @param {WebSocket} socket - The socket to attach the current terminal.
* @param {boolean} bidirectional - Whether the terminal should send data
* to the socket as well.
* @param {boolean} buffered - Whether the rendering of incoming data
* should happen instantly or at a maximum
* frequency of 1 rendering per 10ms.
*/
exports.attach = function (term, socket, bidirectional, buffered) {
bidirectional = (typeof bidirectional == 'undefined') ? true : bidirectional;
term.socket = socket;
term._flushBuffer = function () {
term.write(term._attachSocketBuffer);
term._attachSocketBuffer = null;
clearTimeout(term._attachSocketBufferTimer);
term._attachSocketBufferTimer = null;
};
term._pushToBuffer = function (data) {
if (term._attachSocketBuffer) {
term._attachSocketBuffer += data;
}
else {
term._attachSocketBuffer = data;
setTimeout(term._flushBuffer, 10);
}
};
term._getMessage = function (ev) {
if (buffered) {
term._pushToBuffer(ev.data);
}
else {
term.write(ev.data);
}
};
term._sendData = function (data) {
socket.send(data);
};
socket.addEventListener('message', term._getMessage);
if (bidirectional) {
term.on('data', term._sendData);
}
socket.addEventListener('close', term.detach.bind(term, socket));
socket.addEventListener('error', term.detach.bind(term, socket));
};
/**
* Detaches the given terminal from the given socket
*
* @param {Xterm} term - The terminal to be detached from the given socket.
* @param {WebSocket} socket - The socket from which to detach the current
* terminal.
*/
exports.detach = function (term, socket) {
term.off('data', term._sendData);
socket = (typeof socket == 'undefined') ? term.socket : socket;
if (socket) {
socket.removeEventListener('message', term._getMessage);
}
delete term.socket;
};
/**
* Attaches the current terminal to the given socket
*
* @param {WebSocket} socket - The socket to attach the current terminal.
* @param {boolean} bidirectional - Whether the terminal should send data
* to the socket as well.
* @param {boolean} buffered - Whether the rendering of incoming data
* should happen instantly or at a maximum
* frequency of 1 rendering per 10ms.
*/
Xterm.prototype.attach = function (socket, bidirectional, buffered) {
return exports.attach(this, socket, bidirectional, buffered);
};
/**
* Detaches the current terminal from the given socket.
*
* @param {WebSocket} socket - The socket from which to detach the current
* terminal.
*/
Xterm.prototype.detach = function (socket) {
return exports.detach(this, socket);
};
return exports;
});
//# sourceMappingURL=attach.js.map
+59
View File
@@ -0,0 +1,59 @@
/**
* Fit terminal columns and rows to the dimensions of its DOM element.
*
* ## Approach
* - Rows: Truncate the division of the terminal parent element height by the terminal row height.
*
* - Columns: Truncate the division of the terminal parent element width by the terminal character
* width (apply display: inline at the terminal row and truncate its width with the current
* number of columns).
* @module xterm/addons/fit/fit
* @license MIT
*/
(function (fit) {
if (typeof exports === 'object' && typeof module === 'object') {
/*
* CommonJS environment
*/
module.exports = fit(require('../../xterm'));
}
else if (typeof define == 'function') {
/*
* Require.js is available
*/
define(['../../xterm'], fit);
}
else {
/*
* Plain browser environment
*/
fit(window.Terminal);
}
})(function (Xterm) {
var exports = {};
exports.proposeGeometry = function (term) {
var parentElementStyle = window.getComputedStyle(term.element.parentElement), parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height')), parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width')) - 17), elementStyle = window.getComputedStyle(term.element), elementPaddingVer = parseInt(elementStyle.getPropertyValue('padding-top')) + parseInt(elementStyle.getPropertyValue('padding-bottom')), elementPaddingHor = parseInt(elementStyle.getPropertyValue('padding-right')) + parseInt(elementStyle.getPropertyValue('padding-left')), availableHeight = parentElementHeight - elementPaddingVer, availableWidth = parentElementWidth - elementPaddingHor, container = term.rowContainer, subjectRow = term.rowContainer.firstElementChild, contentBuffer = subjectRow.innerHTML, characterHeight, rows, characterWidth, cols, geometry;
subjectRow.style.display = 'inline';
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);
subjectRow.innerHTML = contentBuffer;
rows = parseInt(availableHeight / characterHeight);
cols = parseInt(availableWidth / characterWidth);
geometry = { cols: cols, rows: rows };
return geometry;
};
exports.fit = function (term) {
var geometry = exports.proposeGeometry(term);
term.resize(geometry.cols, geometry.rows);
};
Xterm.prototype.proposeGeometry = function () {
return exports.proposeGeometry(this);
};
Xterm.prototype.fit = function () {
return exports.fit(this);
};
return exports;
});
//# sourceMappingURL=fit.js.map
+50
View File
@@ -0,0 +1,50 @@
/**
* Fullscreen addon for xterm.js
* @module xterm/addons/fullscreen/fullscreen
* @license MIT
*/
(function (fullscreen) {
if (typeof exports === 'object' && typeof module === 'object') {
/*
* CommonJS environment
*/
module.exports = fullscreen(require('../../xterm'));
}
else if (typeof define == 'function') {
/*
* Require.js is available
*/
define(['../../xterm'], fullscreen);
}
else {
/*
* Plain browser environment
*/
fullscreen(window.Terminal);
}
})(function (Xterm) {
var exports = {};
/**
* Toggle the given terminal's fullscreen mode.
* @param {Xterm} term - The terminal to toggle full screen mode
* @param {boolean} fullscreen - Toggle fullscreen on (true) or off (false)
*/
exports.toggleFullScreen = function (term, fullscreen) {
var fn;
if (typeof fullscreen == 'undefined') {
fn = (term.element.classList.contains('fullscreen')) ? 'remove' : 'add';
}
else if (!fullscreen) {
fn = 'remove';
}
else {
fn = 'add';
}
term.element.classList[fn]('fullscreen');
};
Xterm.prototype.toggleFullscreen = function (fullscreen) {
exports.toggleFullScreen(this, fullscreen);
};
return exports;
});
//# sourceMappingURL=fullscreen.js.map
+165
View File
@@ -0,0 +1,165 @@
/**
* Methods for turning URL subscrings in the terminal's content into links (`a` DOM elements).
* @module xterm/addons/linkify/linkify
* @license MIT
*/
(function (linkify) {
if (typeof exports === 'object' && typeof module === 'object') {
/*
* CommonJS environment
*/
module.exports = linkify(require('../../xterm'));
}
else if (typeof define == 'function') {
/*
* Require.js is available
*/
define(['../../xterm'], linkify);
}
else {
/*
* Plain browser environment
*/
linkify(window.Terminal);
}
})(function (Xterm) {
'use strict';
var exports = {}, protocolClause = '(https?:\\/\\/)', domainCharacterSet = '[\\da-z\\.-]+', negatedDomainCharacterSet = '[^\\da-z\\.-]+', domainBodyClause = '(' + domainCharacterSet + ')', tldClause = '([a-z\\.]{2,6})', ipClause = '((\\d{1,3}\\.){3}\\d{1,3})', portClause = '(:\\d{1,5})', hostClause = '((' + domainBodyClause + '\\.' + tldClause + ')|' + ipClause + ')' + portClause + '?', pathClause = '(\\/[\\/\\w\\.-]*)*', negatedPathCharacterSet = '[^\\/\\w\\.-]+', bodyClause = hostClause + pathClause, start = '(?:^|' + negatedDomainCharacterSet + ')(', end = ')($|' + negatedPathCharacterSet + ')', lenientUrlClause = start + protocolClause + '?' + bodyClause + end, strictUrlClause = start + protocolClause + bodyClause + end, lenientUrlRegex = new RegExp(lenientUrlClause), strictUrlRegex = new RegExp(strictUrlClause);
/**
* Converts all valid URLs found in the given terminal line into
* hyperlinks. The terminal line can be either the HTML element itself
* or the index of the termina line in the children of the terminal
* rows container.
*
* @param {Xterm} terminal - The terminal that owns the given line.
* @param {number|HTMLDivElement} line - The terminal line 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.linkifyTerminalLine = function (terminal, line, lenient, target) {
if (typeof line == 'number') {
line = terminal.rowContainer.children[line];
}
else if (!(line instanceof HTMLDivElement)) {
var message = 'The "line" argument should be either a number';
message += ' or an HTMLDivElement';
throw new TypeError(message);
}
if (typeof target === 'undefined') {
target = '';
}
else {
target = 'target="' + target + '"';
}
var buffer = document.createElement('span'), nodes = line.childNodes;
for (var j = 0; j < nodes.length; j++) {
var node = nodes[j], match;
/**
* Since we cannot access the TextNode's HTML representation
* from the instance itself, we assign its data as textContent
* to a dummy buffer span, in order to retrieve the TextNode's
* HTML representation from the buffer's innerHTML.
*/
buffer.textContent = node.data;
var nodeHTML = buffer.innerHTML;
/**
* Apply function only on TextNodes
*/
if (node.nodeType != node.TEXT_NODE) {
continue;
}
var url = exports.findLinkMatch(node.data, lenient);
if (!url) {
continue;
}
var startsWithProtocol = new RegExp('^' + protocolClause), urlHasProtocol = url.match(startsWithProtocol), href = (urlHasProtocol) ? url : 'http://' + url, link = '<a href="' + href + '" ' + target + '>' + url + '</a>', newHTML = nodeHTML.replace(url, link);
line.innerHTML = line.innerHTML.replace(nodeHTML, newHTML);
}
/**
* This event gets emitted when conversion of all URL susbtrings
* to HTML anchor elements (links) has finished, for a specific
* line of the current Xterm instance.
*
* @event linkify:line
*/
terminal.emit('linkify:line', line);
};
/**
* Finds a link within a block of text.
*
* @param {string} text - The text to search .
* @param {boolean} lenient - Whether to use the lenient search.
* @return {string} A URL.
*/
exports.findLinkMatch = function (text, lenient) {
var match = text.match(lenient ? lenientUrlRegex : strictUrlRegex);
if (!match || match.length === 0) {
return null;
}
return match[1];
};
/**
* Converts all valid URLs found in the terminal view into hyperlinks.
*
* @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, 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, target);
}
/**
* This event gets emitted when conversion of all URL substrings to
* HTML anchor elements (links) has finished for the current Xterm
* instance's view.
*
* @event linkify
*/
terminal.emit('linkify');
};
/**
* Extend Xterm prototype.
*/
/**
* Converts all valid URLs found in the current terminal linte into
* hyperlinks.
*
* @memberof Xterm
* @param {number|HTMLDivElement} line - The terminal line 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
*/
Xterm.prototype.linkifyTerminalLine = function (line, lenient, target) {
return exports.linkifyTerminalLine(this, line, lenient, target);
};
/**
* Converts all valid URLs found in the current terminal into hyperlinks.
*
* @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, target) {
return exports.linkify(this, lenient, target);
};
return exports;
});
//# sourceMappingURL=linkify.js.map
+122
View File
@@ -0,0 +1,122 @@
/**
* This module provides methods for attaching a terminal to a terminado WebSocket stream.
*
* @module xterm/addons/terminado/terminado
* @license MIT
*/
(function (attach) {
if (typeof exports === 'object' && typeof module === 'object') {
/*
* CommonJS environment
*/
module.exports = attach(require('../../xterm'));
}
else if (typeof define == 'function') {
/*
* Require.js is available
*/
define(['../../xterm'], attach);
}
else {
/*
* Plain browser environment
*/
attach(window.Terminal);
}
})(function (Xterm) {
'use strict';
var exports = {};
/**
* Attaches the given terminal to the given socket.
*
* @param {Xterm} term - The terminal to be attached to the given socket.
* @param {WebSocket} socket - The socket to attach the current terminal.
* @param {boolean} bidirectional - Whether the terminal should send data
* to the socket as well.
* @param {boolean} buffered - Whether the rendering of incoming data
* should happen instantly or at a maximum
* frequency of 1 rendering per 10ms.
*/
exports.terminadoAttach = function (term, socket, bidirectional, buffered) {
bidirectional = (typeof bidirectional == 'undefined') ? true : bidirectional;
term.socket = socket;
term._flushBuffer = function () {
term.write(term._attachSocketBuffer);
term._attachSocketBuffer = null;
clearTimeout(term._attachSocketBufferTimer);
term._attachSocketBufferTimer = null;
};
term._pushToBuffer = function (data) {
if (term._attachSocketBuffer) {
term._attachSocketBuffer += data;
}
else {
term._attachSocketBuffer = data;
setTimeout(term._flushBuffer, 10);
}
};
term._getMessage = function (ev) {
var data = JSON.parse(ev.data);
if (data[0] == "stdout") {
if (buffered) {
term._pushToBuffer(data[1]);
}
else {
term.write(data[1]);
}
}
};
term._sendData = function (data) {
socket.send(JSON.stringify(['stdin', data]));
};
term._setSize = function (size) {
socket.send(JSON.stringify(['set_size', size.rows, size.cols]));
};
socket.addEventListener('message', term._getMessage);
if (bidirectional) {
term.on('data', term._sendData);
}
term.on('resize', term._setSize);
socket.addEventListener('close', term.terminadoDetach.bind(term, socket));
socket.addEventListener('error', term.terminadoDetach.bind(term, socket));
};
/**
* Detaches the given terminal from the given socket
*
* @param {Xterm} term - The terminal to be detached from the given socket.
* @param {WebSocket} socket - The socket from which to detach the current
* terminal.
*/
exports.terminadoDetach = function (term, socket) {
term.off('data', term._sendData);
socket = (typeof socket == 'undefined') ? term.socket : socket;
if (socket) {
socket.removeEventListener('message', term._getMessage);
}
delete term.socket;
};
/**
* Attaches the current terminal to the given socket
*
* @param {WebSocket} socket - The socket to attach the current terminal.
* @param {boolean} bidirectional - Whether the terminal should send data
* to the socket as well.
* @param {boolean} buffered - Whether the rendering of incoming data
* should happen instantly or at a maximum
* frequency of 1 rendering per 10ms.
*/
Xterm.prototype.terminadoAttach = function (socket, bidirectional, buffered) {
return exports.terminadoAttach(this, socket, bidirectional, buffered);
};
/**
* Detaches the current terminal from the given socket.
*
* @param {WebSocket} socket - The socket from which to detach the current
* terminal.
*/
Xterm.prototype.terminadoDetach = function (socket) {
return exports.terminadoDetach(this, socket);
};
return exports;
});
//# sourceMappingURL=terminado.js.map
+2206
View File
File diff suppressed because it is too large Load Diff
+5160
View File
File diff suppressed because it is too large Load Diff
+1
View File
File diff suppressed because one or more lines are too long
+6 -7
View File
@@ -1,13 +1,12 @@
{
"source": {
"include": [
"src/xterm.js",
"src/handlers/Clipboard.js",
"addons/attach/attach.js",
"addons/fit/fit.js",
"addons/fullscreen/fullscreen.js",
"addons/linkify/linkify.js"
]
"src/"
],
"exclude": [
"src/test/"
],
"excludePattern": "src/.+\\.test\\.(js|ts)$"
},
"opts": {
"readme": "README.md",
+18 -11
View File
@@ -1,36 +1,43 @@
{
"name": "xterm",
"version": "2.0.0",
"version": "2.2.2",
"ignore": [
"demo",
"test",
".gitignore"
],
"main": "dist/xterm.js",
"main": "lib/xterm.js",
"repository": "https://github.com/sourcelair/xterm.js",
"license": "MIT",
"files": [
"lib"
],
"devDependencies": {
"babel-core": "6.14.0",
"babel-preset-es2015": "6.14.0",
"babelify": "^7.3.0",
"@types/chai": "^3.4.34",
"@types/mocha": "^2.2.33",
"@types/node": "^6.0.41",
"browserify": "^13.1.0",
"chai": "3.5.0",
"derequire": "^2.0.3",
"docdash": "0.4.0",
"exorcist": "^0.4.0",
"express": "4.13.4",
"express-ws": "2.0.0-rc.1",
"glob": "^7.0.5",
"jsdoc": "3.4.0",
"jsdoc": "3.4.3",
"mocha": "2.5.3",
"nodemon": "1.10.2",
"pty.js": "0.3.1",
"sleep": "^3.0.1"
"sleep": "^3.0.1",
"sorcery": "^0.10.0",
"tslint": "^4.0.2",
"typescript": "^2.0.3"
},
"scripts": {
"start": "nodemon --watch src --watch addons --exec bash -c './bin/build && node demo/app'",
"test": "mocha --recursive --compilers js:babel-core/register",
"start": "nodemon --watch src --watch addons --watch demo --exec bash -c './bin/build && node demo/app'",
"lint": "tslint src/**/*.ts",
"test": "mocha --recursive ./lib",
"build:docs": "jsdoc -c jsdoc.json",
"build": "./bin/build"
"build": "./bin/build",
"prepublish": "tsc"
}
}
-201
View File
@@ -1,201 +0,0 @@
/**
* xterm.js: xterm, in the browser
* Copyright (c) 2014-2016, SourceLair Private Company (www.sourcelair.com (MIT License)
*/
/**
* Encapsulates the logic for handling compositionstart, compositionupdate and compositionend
* events, displaying the in-progress composition to the UI and forwarding the final composition
* to the handler.
* @param {HTMLTextAreaElement} textarea The textarea that xterm uses for input.
* @param {HTMLElement} compositionView The element to display the in-progress composition in.
* @param {Terminal} terminal The Terminal to forward the finished composition to.
*/
function CompositionHelper(textarea, compositionView, terminal) {
this.textarea = textarea;
this.compositionView = compositionView;
this.terminal = terminal;
// Whether input composition is currently happening, eg. via a mobile keyboard, speech input
// or IME. This variable determines whether the compositionText should be displayed on the UI.
this.isComposing = false;
// The input currently being composed, eg. via a mobile keyboard, speech input or IME.
this.compositionText = null;
// The position within the input textarea's value of the current composition.
this.compositionPosition = { start: null, end: null };
// Whether a composition is in the process of being sent, setting this to false will cancel
// any in-progress composition.
this.isSendingComposition = false;
}
/**
* Handles the compositionstart event, activating the composition view.
*/
CompositionHelper.prototype.compositionstart = function() {
this.isComposing = true;
this.compositionPosition.start = this.textarea.value.length;
this.compositionView.textContent = '';
this.compositionView.classList.add('active');
};
/**
* Handles the compositionupdate event, updating the composition view.
* @param {CompositionEvent} ev The event.
*/
CompositionHelper.prototype.compositionupdate = function(ev) {
this.compositionView.textContent = ev.data;
this.updateCompositionElements();
var self = this;
setTimeout(function() {
self.compositionPosition.end = self.textarea.value.length;
}, 0);
};
/**
* Handles the compositionend event, hiding the composition view and sending the composition to
* the handler.
*/
CompositionHelper.prototype.compositionend = function() {
this.finalizeComposition(true);
};
/**
* Handles the keydown event, routing any necessary events to the CompositionHelper functions.
* @return Whether the Terminal should continue processing the keydown event.
*/
CompositionHelper.prototype.keydown = function(ev) {
if (this.isComposing || this.isSendingComposition) {
if (ev.keyCode === 229) {
// Continue composing if the keyCode is the "composition character"
return false;
} else if (ev.keyCode === 16 || ev.keyCode === 17 || ev.keyCode === 18) {
// Continue composing if the keyCode is a modifier key
return false;
} else {
// Finish composition immediately. This is mainly here for the case where enter is
// pressed and the handler needs to be triggered before the command is executed.
this.finalizeComposition(false);
}
}
if (ev.keyCode === 229) {
// If the "composition character" is used but gets to this point it means a non-composition
// character (eg. numbers and punctuation) was pressed when the IME was active.
this.handleAnyTextareaChanges();
return false;
}
return true;
};
/**
* Finalizes the composition, resuming regular input actions. This is called when a composition
* is ending.
* @param {boolean} waitForPropogation Whether to wait for events to propogate before sending
* the input. This should be false if a non-composition keystroke is entered before the
* compositionend event is triggered, such as enter, so that the composition is send before
* the command is executed.
*/
CompositionHelper.prototype.finalizeComposition = function(waitForPropogation) {
this.compositionView.classList.remove('active');
this.isComposing = false;
this.clearTextareaPosition();
if (!waitForPropogation) {
// Cancel any delayed composition send requests and send the input immediately.
this.isSendingComposition = false;
var input = this.textarea.value.substring(this.compositionPosition.start, this.compositionPosition.end);
this.terminal.handler(input);
} else {
// Make a deep copy of the composition position here as a new compositionstart event may
// fire before the setTimeout executes.
var currentCompositionPosition = {
start: this.compositionPosition.start,
end: this.compositionPosition.end,
}
// Since composition* events happen before the changes take place in the textarea on most
// browsers, use a setTimeout with 0ms time to allow the native compositionend event to
// complete. This ensures the correct character is retrieved, this solution was used
// because:
// - The compositionend event's data property is unreliable, at least on Chromium
// - The last compositionupdate event's data property does not always accurately describe
// the character, a counter example being Korean where an ending consonsant can move to
// the following character if the following input is a vowel.
var self = this;
this.isSendingComposition = true;
setTimeout(function () {
// Ensure that the input has not already been sent
if (self.isSendingComposition) {
self.isSendingComposition = false;
var input;
if (self.isComposing) {
// Use the end position to get the string if a new composition has started.
input = self.textarea.value.substring(currentCompositionPosition.start, currentCompositionPosition.end);
} else {
// Don't use the end position here in order to pick up any characters after the
// composition has finished, for example when typing a non-composition character
// (eg. 2) after a composition character.
input = self.textarea.value.substring(currentCompositionPosition.start);
}
self.terminal.handler(input);
}
}, 0);
}
};
/**
* Apply any changes made to the textarea after the current event chain is allowed to complete.
* This should be called when not currently composing but a keydown event with the "composition
* character" (229) is triggered, in order to allow non-composition text to be entered when an
* IME is active.
*/
CompositionHelper.prototype.handleAnyTextareaChanges = function() {
var oldValue = this.textarea.value;
var self = this;
setTimeout(function() {
// Ignore if a composition has started since the timeout
if (!self.isComposing) {
var newValue = self.textarea.value;
var diff = newValue.replace(oldValue, '');
if (diff.length > 0) {
self.terminal.handler(diff);
}
}
}, 0);
};
/**
* Positions the composition view on top of the cursor and the textarea just below it (so the
* IME helper dialog is positioned correctly).
*/
CompositionHelper.prototype.updateCompositionElements = function(dontRecurse) {
if (!this.isComposing) {
return;
}
var cursor = this.terminal.element.querySelector('.terminal-cursor');
if (cursor) {
this.compositionView.style.left = cursor.offsetLeft + 'px';
this.compositionView.style.top = cursor.offsetTop + 'px';
var compositionViewBounds = this.compositionView.getBoundingClientRect();
this.textarea.style.left = cursor.offsetLeft + compositionViewBounds.width + 'px';
this.textarea.style.top = (cursor.offsetTop + cursor.offsetHeight) + 'px';
}
if (!dontRecurse) {
setTimeout(this.updateCompositionElements.bind(this, true), 0);
}
};
/**
* Clears the textarea's position so that the cursor does not blink on IE.
* @private
*/
CompositionHelper.prototype.clearTextareaPosition = function() {
this.textarea.style.left = '';
this.textarea.style.top = '';
};
export { CompositionHelper };
@@ -1,20 +1,20 @@
var assert = require('chai').assert;
var Terminal = require('../src/xterm');
import { assert } from 'chai';
import { CompositionHelper } from './CompositionHelper';
describe('CompositionHelper', function () {
describe('CompositionHelper', () => {
var terminal;
var compositionHelper;
var compositionView;
var textarea;
var handledText;
beforeEach(function () {
beforeEach(() => {
compositionView = {
classList: {
add: function () {},
remove: function () {},
add: () => {},
remove: () => {},
},
getBoundingClientRect: function () {
getBoundingClientRect: () => {
return { width: 0 };
},
style: {
@@ -32,7 +32,7 @@ describe('CompositionHelper', function () {
};
terminal = {
element: {
querySelector: function () {
querySelector: () => {
return { offsetLeft: 0, offsetTop: 0 };
}
},
@@ -41,53 +41,26 @@ describe('CompositionHelper', function () {
}
};
handledText = '';
compositionHelper = new Terminal.CompositionHelper(textarea, compositionView, terminal);
compositionHelper = new 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 () {
describe('Input', () => {
it('Should insert simple characters', function (done) {
// First character 'ㅇ'
compositionHelper.compositionstart();
compositionHelper.compositionupdate({ data: 'ㅇ' });
textarea.value = 'ㅇ';
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionend();
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
assert.equal(handledText, 'ㅇ');
// Second character 'ㅇ'
compositionHelper.compositionstart();
compositionHelper.compositionupdate({ data: 'ㅇ' });
textarea.value = 'ㅇㅇ';
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionend();
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
assert.equal(handledText, 'ㅇㅇ');
done();
}, 0);
@@ -101,29 +74,29 @@ describe('CompositionHelper', function () {
compositionHelper.compositionstart();
compositionHelper.compositionupdate({ data: 'ㅇ' });
textarea.value = 'ㅇ';
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionupdate({ data: '아' });
textarea.value = '아';
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionupdate({ data: '앙' });
textarea.value = '앙';
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionend();
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
assert.equal(handledText, '앙');
// Second character '앙'
compositionHelper.compositionstart();
compositionHelper.compositionupdate({ data: 'ㅇ' });
textarea.value = '앙ㅇ';
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionupdate({ data: '아' });
textarea.value = '앙아';
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionupdate({ data: '앙' });
textarea.value = '앙앙';
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionend();
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
assert.equal(handledText, '앙앙');
done();
}, 0);
@@ -141,21 +114,21 @@ describe('CompositionHelper', function () {
compositionHelper.compositionstart();
compositionHelper.compositionupdate({ data: 'ㅇ' });
textarea.value = 'ㅇ';
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionupdate({ data: '아' });
textarea.value = '아';
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
// Start second character '아' in first character
compositionHelper.compositionupdate({ data: '앙' });
textarea.value = '앙';
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionend();
compositionHelper.compositionstart();
compositionHelper.compositionupdate({ data: '아' });
textarea.value = '아아'
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionend();
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
assert.equal(handledText, '아아');
done();
}, 0);
@@ -170,16 +143,16 @@ describe('CompositionHelper', function () {
compositionHelper.compositionstart();
compositionHelper.compositionupdate({ data: 'd' });
textarea.value = 'd';
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionupdate({ data: 'だ' });
textarea.value = 'だ';
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
// Second character 'あ'
compositionHelper.compositionupdate({ data: 'だあ' });
textarea.value = 'だあ';
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionend();
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
assert.equal(handledText, 'だあ');
done();
}, 0);
@@ -193,20 +166,20 @@ describe('CompositionHelper', function () {
compositionHelper.compositionstart();
compositionHelper.compositionupdate({ data: 'd' });
textarea.value = 'd';
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionupdate({ data: 'だ' });
textarea.value = 'だ';
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
// Second character 'ー'
compositionHelper.compositionupdate({ data: 'だー' });
textarea.value = 'だー';
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
// Convert to katakana 'ダー'
compositionHelper.compositionupdate({ data: 'ダー' });
textarea.value = 'ダー';
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionend();
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
assert.equal(handledText, 'ダー');
done();
}, 0);
@@ -221,20 +194,20 @@ describe('CompositionHelper', function () {
compositionHelper.compositionstart();
compositionHelper.compositionupdate({ data: 'い' });
textarea.value = 'い';
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
// Second character 'ま'
compositionHelper.compositionupdate({ data: 'いm' });
textarea.value = 'いm';
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionupdate({ data: 'いま' });
textarea.value = 'いま';
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
// Convert to kanji '今'
compositionHelper.compositionupdate({ data: '今' });
textarea.value = '今';
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionend();
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
assert.equal(handledText, '今');
done();
}, 0);
@@ -249,11 +222,11 @@ describe('CompositionHelper', function () {
compositionHelper.compositionstart();
compositionHelper.compositionupdate({ data: 'ㅇ' });
textarea.value = 'ㅇ';
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionend();
// Second character '1' (a non-composition character)
textarea.value = 'ㅇ1';
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
assert.equal(handledText, 'ㅇ1');
done();
}, 0);
+232
View File
@@ -0,0 +1,232 @@
/**
* @license MIT
*/
import { ITerminal } from './Interfaces';
interface IPosition {
start: number;
end: number;
}
/**
* Encapsulates the logic for handling compositionstart, compositionupdate and compositionend
* events, displaying the in-progress composition to the UI and forwarding the final composition
* to the handler.
*/
export class CompositionHelper {
/**
* Whether input composition is currently happening, eg. via a mobile keyboard, speech input or
* IME. This variable determines whether the compositionText should be displayed on the UI.
*/
private isComposing: boolean;
/**
* The position within the input textarea's value of the current composition.
*/
private compositionPosition: IPosition;;
/**
* Whether a composition is in the process of being sent, setting this to false will cancel any
* in-progress composition.
*/
private isSendingComposition: boolean;
/**
* Creates a new CompositionHelper.
* @param textarea The textarea that xterm uses for input.
* @param compositionView The element to display the in-progress composition in.
* @param terminal The Terminal to forward the finished composition to.
*/
constructor(
private textarea: HTMLTextAreaElement,
private compositionView: HTMLElement,
private terminal: ITerminal
) {
this.isComposing = false;
this.isSendingComposition = false;
this.compositionPosition = { start: null, end: null };
}
/**
* Handles the compositionstart event, activating the composition view.
*/
public compositionstart() {
this.isComposing = true;
this.compositionPosition.start = this.textarea.value.length;
this.compositionView.textContent = '';
this.compositionView.classList.add('active');
}
/**
* Handles the compositionupdate event, updating the composition view.
* @param {CompositionEvent} ev The event.
*/
public compositionupdate(ev: CompositionEvent) {
this.compositionView.textContent = ev.data;
this.updateCompositionElements();
var self = this;
setTimeout(function() {
self.compositionPosition.end = self.textarea.value.length;
}, 0);
}
/**
* Handles the compositionend event, hiding the composition view and sending the composition to
* the handler.
*/
public compositionend() {
this.finalizeComposition(true);
}
/**
* Handles the keydown event, routing any necessary events to the CompositionHelper functions.
* @param ev The keydown event.
* @return Whether the Terminal should continue processing the keydown event.
*/
public keydown(ev: KeyboardEvent) {
if (this.isComposing || this.isSendingComposition) {
if (ev.keyCode === 229) {
// Continue composing if the keyCode is the "composition character"
return false;
} else if (ev.keyCode === 16 || ev.keyCode === 17 || ev.keyCode === 18) {
// Continue composing if the keyCode is a modifier key
return false;
} else {
// Finish composition immediately. This is mainly here for the case where enter is
// pressed and the handler needs to be triggered before the command is executed.
this.finalizeComposition(false);
}
}
if (ev.keyCode === 229) {
// If the "composition character" is used but gets to this point it means a non-composition
// character (eg. numbers and punctuation) was pressed when the IME was active.
this.handleAnyTextareaChanges();
return false;
}
return true;
}
/**
* Finalizes the composition, resuming regular input actions. This is called when a composition
* is ending.
* @param waitForPropogation Whether to wait for events to propogate before sending
* the input. This should be false if a non-composition keystroke is entered before the
* compositionend event is triggered, such as enter, so that the composition is send before
* the command is executed.
*/
private finalizeComposition(waitForPropogation: boolean) {
this.compositionView.classList.remove('active');
this.isComposing = false;
this.clearTextareaPosition();
if (!waitForPropogation) {
// Cancel any delayed composition send requests and send the input immediately.
this.isSendingComposition = false;
var input = this.textarea.value.substring(this.compositionPosition.start, this.compositionPosition.end);
this.terminal.handler(input);
} else {
// Make a deep copy of the composition position here as a new compositionstart event may
// fire before the setTimeout executes.
var currentCompositionPosition = {
start: this.compositionPosition.start,
end: this.compositionPosition.end,
}
// Since composition* events happen before the changes take place in the textarea on most
// browsers, use a setTimeout with 0ms time to allow the native compositionend event to
// complete. This ensures the correct character is retrieved, this solution was used
// because:
// - The compositionend event's data property is unreliable, at least on Chromium
// - The last compositionupdate event's data property does not always accurately describe
// the character, a counter example being Korean where an ending consonsant can move to
// the following character if the following input is a vowel.
var self = this;
this.isSendingComposition = true;
setTimeout(function () {
// Ensure that the input has not already been sent
if (self.isSendingComposition) {
self.isSendingComposition = false;
var input;
if (self.isComposing) {
// Use the end position to get the string if a new composition has started.
input = self.textarea.value.substring(currentCompositionPosition.start, currentCompositionPosition.end);
} else {
// Don't use the end position here in order to pick up any characters after the
// composition has finished, for example when typing a non-composition character
// (eg. 2) after a composition character.
input = self.textarea.value.substring(currentCompositionPosition.start);
}
self.terminal.handler(input);
}
}, 0);
}
}
/**
* Apply any changes made to the textarea after the current event chain is allowed to complete.
* This should be called when not currently composing but a keydown event with the "composition
* character" (229) is triggered, in order to allow non-composition text to be entered when an
* IME is active.
*/
private handleAnyTextareaChanges() {
var oldValue = this.textarea.value;
var self = this;
setTimeout(function() {
// Ignore if a composition has started since the timeout
if (!self.isComposing) {
var newValue = self.textarea.value;
var diff = newValue.replace(oldValue, '');
if (diff.length > 0) {
self.terminal.handler(diff);
}
}
}, 0);
}
/**
* Positions the composition view on top of the cursor and the textarea just below it (so the
* IME helper dialog is positioned correctly).
* @param dontRecurse Whether to use setTimeout to recursively trigger another update, this is
* necessary as the IME events across browsers are not consistently triggered.
*/
public updateCompositionElements(dontRecurse?: boolean) {
if (!this.isComposing) {
return;
}
var cursor = <HTMLElement>this.terminal.element.querySelector('.terminal-cursor');
if (cursor) {
// Take .xterm-rows offsetTop into account as well in case it's positioned absolutely within
// the .xterm element.
var xtermRows = <HTMLElement>this.terminal.element.querySelector('.xterm-rows');
var cursorTop = xtermRows.offsetTop + cursor.offsetTop;
this.compositionView.style.left = cursor.offsetLeft + 'px';
this.compositionView.style.top = cursorTop + 'px';
this.compositionView.style.height = cursor.offsetHeight + 'px';
this.compositionView.style.lineHeight = cursor.offsetHeight + 'px';
// Sync the textarea to the exact position of the composition view so the IME knows where the
// text is.
var compositionViewBounds = this.compositionView.getBoundingClientRect();
this.textarea.style.left = cursor.offsetLeft + 'px';
this.textarea.style.top = cursorTop + 'px';
this.textarea.style.width = compositionViewBounds.width + 'px';
this.textarea.style.height = compositionViewBounds.height + 'px';
this.textarea.style.lineHeight = compositionViewBounds.height + 'px';
}
if (!dontRecurse) {
setTimeout(this.updateCompositionElements.bind(this, true), 0);
}
};
/**
* Clears the textarea's position so that the cursor does not blink on IE.
* @private
*/
private clearTextareaPosition() {
this.textarea.style.left = '';
this.textarea.style.top = '';
};
}
+1 -2
View File
@@ -1,6 +1,5 @@
/**
* xterm.js: xterm, in the browser
* Copyright (c) 2014-2016, SourceLair Private Company (www.sourcelair.com (MIT License)
* @license MIT
*/
function EventEmitter() {
+34
View File
@@ -0,0 +1,34 @@
/**
* @license MIT
*/
export interface IBrowser {
isNode: boolean;
userAgent: string;
platform: string;
isFirefox: boolean;
isMSIE: boolean;
isMac: boolean;
isIpad: boolean;
isIphone: boolean;
isMSWindows: boolean;
}
export interface ITerminal {
element: HTMLElement;
rowContainer: HTMLElement;
textarea: HTMLTextAreaElement;
ydisp: number;
lines: string[];
rows: number;
browser: IBrowser;
/**
* Emit the 'data' event and populate the given data.
* @param data The data to populate in the event.
*/
handler(data: string);
on(event: string, callback: () => void);
scrollDisp(disp: number, suppressScrollEvent: boolean);
cancel(ev: Event, force?: boolean);
}
-114
View File
@@ -1,114 +0,0 @@
/**
* xterm.js: xterm, in the browser
* Copyright (c) 2014-2016, SourceLair Private Company (www.sourcelair.com (MIT License)
*/
/**
* Represents the viewport of a terminal, the visible area within the larger buffer of output.
* Logic for the virtual scroll bar is included in this object.
* @param {Terminal} terminal The Terminal object.
* @param {HTMLElement} viewportElement The DOM element acting as the viewport
* @param {HTMLElement} charMeasureElement A DOM element used to measure the character size of
* the terminal.
*/
function Viewport(terminal, viewportElement, scrollArea, charMeasureElement) {
this.terminal = terminal;
this.viewportElement = viewportElement;
this.scrollArea = scrollArea;
this.charMeasureElement = charMeasureElement;
this.currentRowHeight = 0;
this.lastRecordedBufferLength = 0;
this.lastRecordedViewportHeight = 0;
this.terminal.on('scroll', this.syncScrollArea.bind(this));
this.terminal.on('resize', this.syncScrollArea.bind(this));
this.viewportElement.addEventListener('scroll', this.onScroll.bind(this));
this.syncScrollArea();
}
/**
* Refreshes row height, setting line-height, viewport height and scroll area height if
* necessary.
* @param {number|undefined} charSize A character size measurement bounding rect object, if it
* doesn't exist it will be created.
*/
Viewport.prototype.refresh = function(charSize) {
var size = charSize || this.charMeasureElement.getBoundingClientRect();
if (size.height > 0) {
var rowHeightChanged = size.height !== this.currentRowHeight;
if (rowHeightChanged) {
this.currentRowHeight = size.height;
this.viewportElement.style.lineHeight = size.height + 'px';
this.terminal.rowContainer.style.lineHeight = size.height + 'px';
}
var viewportHeightChanged = this.lastRecordedViewportHeight !== this.terminal.rows;
if (rowHeightChanged || viewportHeightChanged) {
this.lastRecordedViewportHeight = this.terminal.rows;
this.viewportElement.style.height = size.height * this.terminal.rows + 'px';
}
this.scrollArea.style.height = (size.height * this.lastRecordedBufferLength) + 'px';
}
};
/**
* Updates dimensions and synchronizes the scroll area if necessary.
*/
Viewport.prototype.syncScrollArea = function() {
if (this.lastRecordedBufferLength !== this.terminal.lines.length) {
// If buffer height changed
this.lastRecordedBufferLength = this.terminal.lines.length;
this.refresh();
} else if (this.lastRecordedViewportHeight !== this.terminal.rows) {
// If viewport height changed
this.refresh();
} else {
// If size has changed, refresh viewport
var size = this.charMeasureElement.getBoundingClientRect();
if (size.height !== this.currentRowHeight) {
this.refresh(size);
}
}
// Sync scrollTop
var scrollTop = this.terminal.ydisp * this.currentRowHeight;
if (this.viewportElement.scrollTop !== scrollTop) {
this.viewportElement.scrollTop = scrollTop;
}
};
/**
* Handles scroll events on the viewport, calculating the new viewport and requesting the
* terminal to scroll to it.
* @param {Event} ev The scroll event.
*/
Viewport.prototype.onScroll = function(ev) {
var newRow = Math.round(this.viewportElement.scrollTop / this.currentRowHeight);
var diff = newRow - this.terminal.ydisp;
this.terminal.scrollDisp(diff, true);
};
/**
* Handles mouse wheel events by adjusting the viewport's scrollTop and delegating the actual
* scrolling to `onScroll`, this event needs to be attached manually by the consumer of
* `Viewport`.
* @param {WheelEvent} ev The mouse wheel event.
*/
Viewport.prototype.onWheel = function(ev) {
if (ev.deltaY === 0) {
// Do nothing if it's not a vertical scroll event
return;
}
// Fallback to WheelEvent.DOM_DELTA_PIXEL
var multiplier = 1;
if (ev.deltaMode === WheelEvent.DOM_DELTA_LINE) {
multiplier = this.currentRowHeight;
} else if (ev.deltaMode === WheelEvent.DOM_DELTA_PAGE) {
multiplier = this.currentRowHeight * this.terminal.rows;
}
this.viewportElement.scrollTop += ev.deltaY * multiplier;
// Prevent the page from scrolling when the terminal scrolls
ev.preventDefault();
};
export { Viewport };
+16 -22
View File
@@ -1,21 +1,21 @@
var assert = require('chai').assert;
var Terminal = require('../src/xterm');
import { assert } from 'chai';
import { Viewport } from './Viewport';
describe('Viewport', function () {
describe('Viewport', () => {
var terminal;
var viewportElement;
var charMeasureElement;
var viewport;
var scrollAreaElement;
var CHARACTER_HEIGHT = 10;
const CHARACTER_HEIGHT = 10;
beforeEach(function () {
beforeEach(() => {
terminal = {
lines: [],
rows: 0,
ydisp: 0,
on: function () {},
on: () => {},
rowContainer: {
style: {
lineHeight: 0
@@ -23,7 +23,7 @@ describe('Viewport', function () {
}
};
viewportElement = {
addEventListener: function () {},
addEventListener: () => {},
style: {
height: 0,
lineHeight: 0
@@ -35,37 +35,31 @@ describe('Viewport', function () {
}
};
charMeasureElement = {
getBoundingClientRect: function () {
getBoundingClientRect: () => {
return { width: null, height: CHARACTER_HEIGHT };
}
};
viewport = new Terminal.Viewport(terminal, viewportElement, scrollAreaElement, charMeasureElement);
viewport = new Viewport(terminal, viewportElement, scrollAreaElement, charMeasureElement);
});
describe('Public API', function () {
it('should define Viewport.prototype.onWheel', function () {
assert.isDefined(Terminal.Viewport.prototype.onWheel);
});
});
describe('refresh', function () {
it('should set the line-height of the terminal', function () {
describe('refresh', () => {
it('should set the line-height of the terminal', () => {
assert.equal(viewportElement.style.lineHeight, CHARACTER_HEIGHT + 'px');
assert.equal(terminal.rowContainer.style.lineHeight, CHARACTER_HEIGHT + 'px');
charMeasureElement.getBoundingClientRect = function () {
charMeasureElement.getBoundingClientRect = () => {
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 () {
it('should set the height of the viewport when the line-height changed', () => {
terminal.lines.push('');
terminal.lines.push('');
terminal.rows = 1;
viewport.refresh();
assert.equal(viewportElement.style.height, 1 * CHARACTER_HEIGHT + 'px');
charMeasureElement.getBoundingClientRect = function () {
charMeasureElement.getBoundingClientRect = () => {
return { width: null, height: 20 };
};
viewport.refresh();
@@ -73,8 +67,8 @@ describe('Viewport', function () {
});
});
describe('syncScrollArea', function () {
it('should sync the scroll area', function () {
describe('syncScrollArea', () => {
it('should sync the scroll area', () => {
terminal.lines.push('');
terminal.rows = 1;
assert.equal(scrollAreaElement.style.height, 0 * CHARACTER_HEIGHT + 'px');
+123
View File
@@ -0,0 +1,123 @@
/**
* @license MIT
*/
import { ITerminal } from './Interfaces';
/**
* Represents the viewport of a terminal, the visible area within the larger buffer of output.
* Logic for the virtual scroll bar is included in this object.
*/
export class Viewport {
private currentRowHeight: number;
private lastRecordedBufferLength: number;
private lastRecordedViewportHeight: number;
/**
* Creates a new Viewport.
* @param terminal The terminal this viewport belongs to.
* @param viewportElement The DOM element acting as the viewport.
* @param scrollArea The DOM element acting as the scroll area.
* @param charMeasureElement A DOM element used to measure the character size of. the terminal.
*/
constructor(
private terminal: ITerminal,
private viewportElement: HTMLElement,
private scrollArea: HTMLElement,
private charMeasureElement: HTMLElement
) {
this.currentRowHeight = 0;
this.lastRecordedBufferLength = 0;
this.lastRecordedViewportHeight = 0;
this.terminal.on('scroll', this.syncScrollArea.bind(this));
this.terminal.on('resize', this.syncScrollArea.bind(this));
this.viewportElement.addEventListener('scroll', this.onScroll.bind(this));
this.syncScrollArea();
}
/**
* Refreshes row height, setting line-height, viewport height and scroll area height if
* necessary.
* @param charSize A character size measurement bounding rect object, if it doesn't exist it will
* be created.
*/
private refresh(charSize?: ClientRect): void {
var size = charSize || this.charMeasureElement.getBoundingClientRect();
if (size.height > 0) {
var rowHeightChanged = size.height !== this.currentRowHeight;
if (rowHeightChanged) {
this.currentRowHeight = size.height;
this.viewportElement.style.lineHeight = size.height + 'px';
this.terminal.rowContainer.style.lineHeight = size.height + 'px';
}
var viewportHeightChanged = this.lastRecordedViewportHeight !== this.terminal.rows;
if (rowHeightChanged || viewportHeightChanged) {
this.lastRecordedViewportHeight = this.terminal.rows;
this.viewportElement.style.height = size.height * this.terminal.rows + 'px';
}
this.scrollArea.style.height = (size.height * this.lastRecordedBufferLength) + 'px';
}
}
/**
* Updates dimensions and synchronizes the scroll area if necessary.
*/
public syncScrollArea(): void {
if (this.lastRecordedBufferLength !== this.terminal.lines.length) {
// If buffer height changed
this.lastRecordedBufferLength = this.terminal.lines.length;
this.refresh();
} else if (this.lastRecordedViewportHeight !== this.terminal.rows) {
// If viewport height changed
this.refresh();
} else {
// If size has changed, refresh viewport
var size = this.charMeasureElement.getBoundingClientRect();
if (size.height !== this.currentRowHeight) {
this.refresh(size);
}
}
// Sync scrollTop
var scrollTop = this.terminal.ydisp * this.currentRowHeight;
if (this.viewportElement.scrollTop !== scrollTop) {
this.viewportElement.scrollTop = scrollTop;
}
}
/**
* Handles scroll events on the viewport, calculating the new viewport and requesting the
* terminal to scroll to it.
* @param ev The scroll event.
*/
private onScroll(ev: Event) {
var newRow = Math.round(this.viewportElement.scrollTop / this.currentRowHeight);
var diff = newRow - this.terminal.ydisp;
this.terminal.scrollDisp(diff, true);
}
/**
* Handles mouse wheel events by adjusting the viewport's scrollTop and delegating the actual
* scrolling to `onScroll`, this event needs to be attached manually by the consumer of
* `Viewport`.
* @param ev The mouse wheel event.
*/
public onWheel(ev: WheelEvent) {
if (ev.deltaY === 0) {
// Do nothing if it's not a vertical scroll event
return;
}
// Fallback to WheelEvent.DOM_DELTA_PIXEL
var multiplier = 1;
if (ev.deltaMode === WheelEvent.DOM_DELTA_LINE) {
multiplier = this.currentRowHeight;
} else if (ev.deltaMode === WheelEvent.DOM_DELTA_PAGE) {
multiplier = this.currentRowHeight * this.terminal.rows;
}
this.viewportElement.scrollTop += ev.deltaY * multiplier;
// Prevent the page from scrolling when the terminal scrolls
ev.preventDefault();
};
}
@@ -1,9 +1,7 @@
/*
* Implements the attach method, that
* attaches the terminal to a WebSocket stream.
*
* The bidirectional argument indicates, whether the terminal should
* send data to the socket as well and is true, by default.
/**
* Implements the attach method, that attaches the terminal to a WebSocket stream.
* @module xterm/addons/attach/attach
* @license MIT
*/
(function (attach) {
@@ -11,12 +9,12 @@
/*
* CommonJS environment
*/
module.exports = attach(require('../../src/xterm'));
module.exports = attach(require('../../xterm'));
} else if (typeof define == 'function') {
/*
* Require.js is available
*/
define(['../../src/xterm'], attach);
define(['../../xterm'], attach);
} else {
/*
* Plain browser environment
@@ -26,12 +24,6 @@
})(function (Xterm) {
'use strict';
/**
* This module provides methods for attaching a terminal to a WebSocket
* stream.
*
* @module xterm/addons/attach/attach
*/
var exports = {};
/**
+14 -19
View File
@@ -1,27 +1,27 @@
/*
* Fit terminal columns and rows to the dimensions of its
* DOM element.
/**
* Fit terminal columns and rows to the dimensions of its DOM element.
*
* Approach:
* - Rows: Truncate the division of the terminal parent element height
* by the terminal row height
* ## Approach
* - Rows: Truncate the division of the terminal parent element height by the terminal row height.
*
* - Columns: Truncate the division of the terminal parent element width by
* the terminal character width (apply display: inline at the
* terminal row and truncate its width with the current number
* of columns)
* - Columns: Truncate the division of the terminal parent element width by the terminal character
* width (apply display: inline at the terminal row and truncate its width with the current
* number of columns).
* @module xterm/addons/fit/fit
* @license MIT
*/
(function (fit) {
if (typeof exports === 'object' && typeof module === 'object') {
/*
* CommonJS environment
*/
module.exports = fit(require('../../src/xterm'));
module.exports = fit(require('../../xterm'));
} else if (typeof define == 'function') {
/*
* Require.js is available
*/
define(['../../src/xterm'], fit);
define(['../../xterm'], fit);
} else {
/*
* Plain browser environment
@@ -29,17 +29,12 @@
fit(window.Terminal);
}
})(function (Xterm) {
/**
* This module provides methods for fitting a terminal's size to a parent container.
*
* @module xterm/addons/fit/fit
*/
var exports = {};
exports.proposeGeometry = function (term) {
var parentElementStyle = window.getComputedStyle(term.element.parentElement),
parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height')),
parentElementWidth = parseInt(parentElementStyle.getPropertyValue('width')),
parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width')) - 17),
elementStyle = window.getComputedStyle(term.element),
elementPaddingVer = parseInt(elementStyle.getPropertyValue('padding-top')) + parseInt(elementStyle.getPropertyValue('padding-bottom')),
elementPaddingHor = parseInt(elementStyle.getPropertyValue('padding-right')) + parseInt(elementStyle.getPropertyValue('padding-left')),
@@ -62,7 +57,7 @@
subjectRow.innerHTML = contentBuffer;
rows = parseInt(availableHeight / characterHeight);
cols = parseInt(availableWidth / characterWidth) - 1;
cols = parseInt(availableWidth / characterWidth);
geometry = {cols: cols, rows: rows};
return geometry;
+10
View File
@@ -0,0 +1,10 @@
.xterm.fullscreen {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
width: auto;
height: auto;
z-index: 255;
}
@@ -1,26 +1,19 @@
/*
/**
* Fullscreen addon for xterm.js
*
* Implements the toggleFullscreen function.
*
* If the `fullscreen` argument has been supplied, then
* if it is true, the fullscreen mode gets turned on,
* if it is false or null, the fullscreen mode gets turned off.
*
* If the `fullscreen` argument has not been supplied, the
* fullscreen mode is being toggled.
* @module xterm/addons/fullscreen/fullscreen
* @license MIT
*/
(function (fullscreen) {
if (typeof exports === 'object' && typeof module === 'object') {
/*
* CommonJS environment
*/
module.exports = fullscreen(require('../../src/xterm'));
module.exports = fullscreen(require('../../xterm'));
} else if (typeof define == 'function') {
/*
* Require.js is available
*/
define(['../../src/xterm'], fullscreen);
define(['../../xterm'], fullscreen);
} else {
/*
* Plain browser environment
@@ -30,6 +23,11 @@
})(function (Xterm) {
var exports = {};
/**
* Toggle the given terminal's fullscreen mode.
* @param {Xterm} term - The terminal to toggle full screen mode
* @param {boolean} fullscreen - Toggle fullscreen on (true) or off (false)
*/
exports.toggleFullScreen = function (term, fullscreen) {
var fn;
@@ -1,14 +1,20 @@
/**
* Methods for turning URL subscrings in the terminal's content into links (`a` DOM elements).
* @module xterm/addons/linkify/linkify
* @license MIT
*/
(function (linkify) {
if (typeof exports === 'object' && typeof module === 'object') {
/*
* CommonJS environment
*/
module.exports = linkify(require('../../src/xterm'));
module.exports = linkify(require('../../xterm'));
} else if (typeof define == 'function') {
/*
* Require.js is available
*/
define(['../../src/xterm'], linkify);
define(['../../xterm'], linkify);
} else {
/*
* Plain browser environment
@@ -18,12 +24,6 @@
})(function (Xterm) {
'use strict';
/**
* This module provides methods for convertings valid URL substrings
* into HTML anchor elements (links), inside a terminal view.
*
* @module xterm/addons/linkify/linkify
*/
var exports = {},
protocolClause = '(https?:\\/\\/)',
domainCharacterSet = '[\\da-z\\.-]+',
+5
View File
@@ -0,0 +1,5 @@
{
"name": "xterm.terminado",
"main": "terminado.js",
"private": true
}
+135
View File
@@ -0,0 +1,135 @@
/**
* This module provides methods for attaching a terminal to a terminado WebSocket stream.
*
* @module xterm/addons/terminado/terminado
* @license MIT
*/
(function (attach) {
if (typeof exports === 'object' && typeof module === 'object') {
/*
* CommonJS environment
*/
module.exports = attach(require('../../xterm'));
} else if (typeof define == 'function') {
/*
* Require.js is available
*/
define(['../../xterm'], attach);
} else {
/*
* Plain browser environment
*/
attach(window.Terminal);
}
})(function (Xterm) {
'use strict';
var exports = {};
/**
* Attaches the given terminal to the given socket.
*
* @param {Xterm} term - The terminal to be attached to the given socket.
* @param {WebSocket} socket - The socket to attach the current terminal.
* @param {boolean} bidirectional - Whether the terminal should send data
* to the socket as well.
* @param {boolean} buffered - Whether the rendering of incoming data
* should happen instantly or at a maximum
* frequency of 1 rendering per 10ms.
*/
exports.terminadoAttach = function (term, socket, bidirectional, buffered) {
bidirectional = (typeof bidirectional == 'undefined') ? true : bidirectional;
term.socket = socket;
term._flushBuffer = function () {
term.write(term._attachSocketBuffer);
term._attachSocketBuffer = null;
clearTimeout(term._attachSocketBufferTimer);
term._attachSocketBufferTimer = null;
};
term._pushToBuffer = function (data) {
if (term._attachSocketBuffer) {
term._attachSocketBuffer += data;
} else {
term._attachSocketBuffer = data;
setTimeout(term._flushBuffer, 10);
}
};
term._getMessage = function (ev) {
var data = JSON.parse(ev.data)
if( data[0] == "stdout" ) {
if (buffered) {
term._pushToBuffer(data[1]);
} else {
term.write(data[1]);
}
}
};
term._sendData = function (data) {
socket.send(JSON.stringify(['stdin', data]));
};
term._setSize = function (size) {
socket.send(JSON.stringify(['set_size', size.rows, size.cols]));
};
socket.addEventListener('message', term._getMessage);
if (bidirectional) {
term.on('data', term._sendData);
}
term.on('resize', term._setSize);
socket.addEventListener('close', term.terminadoDetach.bind(term, socket));
socket.addEventListener('error', term.terminadoDetach.bind(term, socket));
};
/**
* Detaches the given terminal from the given socket
*
* @param {Xterm} term - The terminal to be detached from the given socket.
* @param {WebSocket} socket - The socket from which to detach the current
* terminal.
*/
exports.terminadoDetach = function (term, socket) {
term.off('data', term._sendData);
socket = (typeof socket == 'undefined') ? term.socket : socket;
if (socket) {
socket.removeEventListener('message', term._getMessage);
}
delete term.socket;
};
/**
* Attaches the current terminal to the given socket
*
* @param {WebSocket} socket - The socket to attach the current terminal.
* @param {boolean} bidirectional - Whether the terminal should send data
* to the socket as well.
* @param {boolean} buffered - Whether the rendering of incoming data
* should happen instantly or at a maximum
* frequency of 1 rendering per 10ms.
*/
Xterm.prototype.terminadoAttach = function (socket, bidirectional, buffered) {
return exports.terminadoAttach(this, socket, bidirectional, buffered);
};
/**
* Detaches the current terminal from the given socket.
*
* @param {WebSocket} socket - The socket from which to detach the current
* terminal.
*/
Xterm.prototype.terminadoDetach = function (socket) {
return exports.terminadoDetach(this, socket);
};
return exports;
});
@@ -1,11 +1,11 @@
var assert = require('chai').assert;
var Terminal = require('../src/xterm');
var Clipboard = require('../src/handlers/Clipboard');
import { assert } from 'chai';
import * as Terminal from '../xterm';
import * as Clipboard from './Clipboard';
describe('evaluateCopiedTextProcessing', function () {
it('should strip trailing whitespaces and replace nbsps with spaces', function () {
var nonBreakingSpace = String.fromCharCode(160),
let nonBreakingSpace = String.fromCharCode(160),
copiedText = 'echo' + nonBreakingSpace + 'hello' + nonBreakingSpace,
processedText = Clipboard.prepareTextForClipboard(copiedText);
@@ -1,13 +1,20 @@
/**
* xterm.js: xterm, in the browser
* Copyright (c) 2016, SourceLair Private Company <www.sourcelair.com> (MIT License)
* Clipboard handler module: exports methods for handling all clipboard-related events in the
* terminal.
* @module xterm/handlers/Clipboard
* @license MIT
*/
/**
* Clipboard handler module. This module contains methods for handling all
* clipboard-related events appropriately in the terminal.
* @module xterm/handlers/Clipboard
*/
import { ITerminal } from '../Interfaces';
interface IWindow extends Window {
clipboardData?: {
getData(format: string): string;
setData(format: string, data: string);
};
}
declare var window: IWindow;
/**
* Prepares text copied from terminal selection, to be saved in the clipboard by:
@@ -16,14 +23,14 @@
* @param {string} text The copied text that needs processing for storing in clipboard
* @returns {string}
*/
function prepareTextForClipboard(text) {
var space = String.fromCharCode(32),
export function prepareTextForClipboard(text: string): string {
let space = String.fromCharCode(32),
nonBreakingSpace = String.fromCharCode(160),
allNonBreakingSpaces = new RegExp(nonBreakingSpace, 'g'),
processedText = text.split('\n').map(function (line) {
// Strip all trailing white spaces and convert all non-breaking spaces
// to regular spaces.
var processedLine = line.replace(/\s+$/g, '').replace(allNonBreakingSpaces, space);
let processedLine = line.replace(/\s+$/g, '').replace(allNonBreakingSpaces, space);
return processedLine;
}).join('\n');
@@ -35,11 +42,18 @@ function prepareTextForClipboard(text) {
* Binds copy functionality to the given terminal.
* @param {ClipboardEvent} ev The original copy event to be handled
*/
function copyHandler (ev) {
var copiedText = window.getSelection().toString(),
export function copyHandler(ev: ClipboardEvent, term: ITerminal) {
// We cast `window` to `any` type, because TypeScript has not declared the `clipboardData`
// property that we use below for Internet Explorer.
let copiedText = window.getSelection().toString(),
text = prepareTextForClipboard(copiedText);
ev.clipboardData.setData('text/plain', text);
if (term.browser.isMSIE) {
window.clipboardData.setData('Text', text);
} else {
ev.clipboardData.setData('text/plain', text);
}
ev.preventDefault(); // Prevent or the original text will be copied.
}
@@ -48,13 +62,27 @@ function copyHandler (ev) {
* @param {ClipboardEvent} ev The original paste event to be handled
* @param {Terminal} term The terminal on which to apply the handled paste event
*/
function pasteHandler(ev, term) {
export function pasteHandler(ev: ClipboardEvent, term: ITerminal) {
ev.stopPropagation();
if (ev.clipboardData) {
var text = ev.clipboardData.getData('text/plain');
let text: string;
let dispatchPaste = function(text) {
term.handler(text);
term.textarea.value = '';
return term.cancel(ev);
};
if (term.browser.isMSIE) {
if (window.clipboardData) {
text = window.clipboardData.getData('Text');
dispatchPaste(text);
}
} else {
if (ev.clipboardData) {
text = ev.clipboardData.getData('text/plain');
dispatchPaste(text);
}
}
}
@@ -68,45 +96,49 @@ function pasteHandler(ev, term) {
* area, then bring the terminal's input below the cursor, in order to
* trigger the event on the textarea and allow-right click paste, without
* caring about disappearing selection.
* @param {ClipboardEvent} ev The original paste event to be handled
* @param {MouseEvent} ev The original right click event to be handled
* @param {Terminal} term The terminal on which to apply the handled paste event
*/
function rightClickHandler(ev, term) {
var s = document.getSelection(),
sText = prepareTextForClipboard(s.toString()),
r = s.getRangeAt(0);
var x = ev.clientX,
export function rightClickHandler(ev: MouseEvent, term: ITerminal) {
let s = document.getSelection(),
selectedText = prepareTextForClipboard(s.toString()),
clickIsOnSelection = false,
x = ev.clientX,
y = ev.clientY;
var cr = r.getClientRects(),
clickIsOnSelection = false,
i, rect;
if (s.rangeCount) {
let r = s.getRangeAt(0),
cr = r.getClientRects();
for (i=0; i<cr.length; i++) {
rect = cr[i];
clickIsOnSelection = (
(x > rect.left) && (x < rect.right) &&
(y > rect.top) && (y < rect.bottom)
);
for (let i = 0; i < cr.length; i++) {
let rect = cr[i];
clickIsOnSelection = (
(x > rect.left) && (x < rect.right) &&
(y > rect.top) && (y < rect.bottom)
);
if (clickIsOnSelection) {
break;
}
}
// If we clicked on selection and selection is not a single space,
// then mark the right click as copy-only. We check for the single
// space selection, as this can happen when clicking on an &nbsp;
// and there is not much pointing in copying a single space.
// Single space is char
if (clickIsOnSelection && (sText !== ' ')) {
break;
if (selectedText.match(/^\s$/) || !selectedText.length) {
clickIsOnSelection = false;
}
}
// Bring textarea at the cursor position
if (!clickIsOnSelection) {
term.textarea.style.position = 'fixed';
term.textarea.style.width = '10px';
term.textarea.style.height = '10px';
term.textarea.style.left = x + 'px';
term.textarea.style.top = y + 'px';
term.textarea.style.zIndex = 1000;
term.textarea.style.width = '20px';
term.textarea.style.height = '20px';
term.textarea.style.left = (x - 10) + 'px';
term.textarea.style.top = (y - 10) + 'px';
term.textarea.style.zIndex = '1000';
term.textarea.focus();
// Reset the terminal textarea's styling
@@ -117,10 +149,6 @@ function rightClickHandler(ev, term) {
term.textarea.style.left = null;
term.textarea.style.top = null;
term.textarea.style.zIndex = null;
}, 1);
}, 4);
}
}
export {
prepareTextForClipboard, copyHandler, pasteHandler, rightClickHandler
};
@@ -1,5 +1,5 @@
var assert = require('chai').assert;
var Terminal = require('../../src/xterm');
var Terminal = require('../../xterm');
var linkify = require('../../addons/linkify/linkify');
describe('linkify addon', function () {
@@ -1,10 +1,10 @@
var assert = require('chai').assert;
var Terminal = require('../../src/xterm');
var Terminal = require('../../xterm');
describe('xterm.js addons', function() {
it('should load addons with Terminal.loadAddon', function () {
Terminal.loadAddon('attach');
// Test that function was loaded successfully
// Test that addon was loaded successfully
assert.equal(typeof Terminal.prototype.attach, 'function');
});
});
@@ -2,7 +2,7 @@ var glob = require('glob');
var fs = require('fs');
var pty = require('pty.js');
var sleep = require('sleep');
var Terminal = require('../src/xterm');
var Terminal = require('../xterm');
var CONSOLE_LOG = console.log;
@@ -80,9 +80,10 @@ describe('xterm output comparison', function() {
// omit stack trace for escape sequence files
Error.stackTraceLimit = 0;
var files = glob.sync('test/escape_sequence_files/*.in');
var files = glob.sync('**/escape_sequence_files/*.in');
// only successful tests for now
var successful = [0, 2, 6, 12, 13, 18, 20, 22, 27, 28];
console.log(files);
for (var a in successful) {
var i = successful[a];
(function(filename){

Some files were not shown because too many files have changed in this diff Show More