Compare commits

..
314 Commits
Author SHA1 Message Date
Paris Kasidiaris 8dd11f5587 Bump version to 2.3.0
Signed-off-by: Paris Kasidiaris <paris@sourcelair.com>
2017-02-08 22:24:56 +00:00
Daniel Imms 795cdb52dc Merge pull request #530 from Tyriar/fix_tests_on_windows
Disable escape sequence tests on windows
2017-02-08 10:41:06 -08:00
Daniel Imms e79e67c3ce Disable escape sequence tests on windows 2017-02-07 21:17:47 -08:00
Daniel Imms e0639e1b62 Merge pull request #526 from sourcelair/Tyriar-patch-1
Add description to package.json
2017-02-06 16:40:29 -08:00
Daniel Imms 56b541b44f Add description to package.json
This should fix the ugly broken markdown at the top of npm
2017-02-06 13:56:52 -08:00
Paris Kasidiaris 03e46fbdee Merge pull request #525 from LucianBuzzo/resize
Fix issue when resizing hidden terminals
2017-02-05 17:59:54 +02:00
Lucian Buzzo ee0d6b4533 Fixed issue where terminal would become unusable if NaN values are sent
to resize method.
2017-02-05 12:45:55 +00:00
Daniel Imms 937a9bf433 Merge pull request #523 from sourcelair/Tyriar-patch-1
Add null check on Terminal.element
2017-02-02 08:53:40 -08:00
Paris Kasidiaris 3bcdc4a354 Merge pull request #524 from billchurch/master
Adding WebSSH2 to real-world uses
2017-02-01 23:51:23 +02:00
Bill Church 382cd0bbf4 Merge pull request #1 from billchurch/billchurch-patch-1
Update README.md
2017-02-01 16:42:39 -05:00
Bill Church e58a6975da Update README.md
Adding WebSSH2 to real-word uses
2017-02-01 16:41:47 -05:00
Daniel Imms 833eb5216b Add null check on Terminal.element
Since the Terminal can be used now before being attached, we should check element on destroy.

Related to #266
2017-02-01 11:50:52 -08:00
Daniel Imms 742d28fcc3 Merge pull request #522 from Tyriar/266_pull_renderer_out
Pull renderer out of xterm.js
2017-02-01 11:33:52 -08:00
Daniel Imms cc78fdd63c Hold on to renderer if a reset occurs 2017-02-01 11:19:54 -08:00
Daniel Imms 20f22fb658 Convert flags to enum 2017-02-01 11:18:44 -08:00
Daniel Imms 3c635f3ce5 Remove Terminal.queueRefresh, call refresh directly 2017-02-01 11:13:00 -08:00
Daniel Imms 9d8f6c9ae7 Polish 2017-02-01 10:30:58 -08:00
Daniel Imms cc6893d0f2 Merge remote-tracking branch 'upstream/master' into 266_pull_renderer_out 2017-02-01 10:29:11 -08:00
Daniel Imms ca4e99e810 Add license 2017-02-01 10:21:43 -08:00
Daniel Imms 8d7df670b4 Merge pull request #521 from Tyriar/517_dont_loop_animation_frame
Don't requeue animation frame if not needed
2017-01-31 23:51:51 -08:00
Daniel Imms 4de7ed46bf Merge pull request #520 from Tyriar/519_erase_null_check
Add null checks in eraseRight and eraseLeft
2017-01-31 23:51:40 -08:00
Daniel Imms f9aac1bcc6 Fix tests 2017-01-31 23:46:58 -08:00
Daniel Imms 92068f36f2 Pull renderer out of xterm.js 2017-01-31 22:45:28 -08:00
Daniel Imms 047729ee9e Don't requeue animation frame if not needed
Fixes #517
2017-01-31 21:53:29 -08:00
Daniel Imms 637a5bd9cc Don't requeue animation frame if not needed
Fixes #517
2017-01-31 21:23:21 -08:00
Daniel Imms 6cd5c2b846 Add null checks in eraseRight and eraseLeft
Fixes #519
2017-01-31 21:03:13 -08:00
Daniel Imms 365a9f949c Merge pull request #513 from Tyriar/511_useFlowControl_option
Add useFlowControl option
2017-01-27 08:23:13 -08:00
Paris Kasidiaris d6de156cd8 Merge pull request #514 from LucianBuzzo/master
[issue #164] Fix bug that can occur when setting a scroll region
2017-01-27 12:58:57 +00:00
Lucian Buzzo 23c9fca088 [issue #164] Fix bug that can occur when setting a scroll region 2017-01-27 11:25:12 +00:00
Daniel Imms c2a69721f3 Add useFlowControl option
Fixes #511
2017-01-26 21:14:07 -08:00
Daniel Imms d1ad8a7831 Merge pull request #500 from Tyriar/fix_vt_tests
Fix many vt tests
2017-01-26 08:55:42 -08:00
Daniel Imms cee2285818 Merge pull request #509 from Tyriar/vscode_debugger_support
Support debugging mocha tests in vscode
2017-01-26 08:55:22 -08:00
Daniel Imms ca19da16e0 Merge pull request #499 from Tyriar/481_character_sets
Add support for rest of VT100 character sets
2017-01-25 13:00:49 -08:00
Daniel Imms 5850cbcd05 Add comments explaining actions preventing x from wrapping 2017-01-25 10:34:43 -08:00
Daniel Imms b789a7e85b Fix failing test given new auto wraparound behavior 2017-01-25 10:32:20 -08:00
Daniel Imms 9f0dbdd5c7 Support debugging mocha tests from vscode 2017-01-25 10:25:28 -08:00
Daniel Imms 98614f26ef Merge pull request #508 from Tyriar/507_fix_lint_in_src_root
Run lint on src/*.ts and fix errors
2017-01-25 07:30:09 -08:00
Daniel Imms 3b16696672 Run lint on src/*.ts and fix errors
Fixes #507
2017-01-24 13:34:47 -08:00
Paris Kasidiaris 0219ad2ed8 Merge pull request #505 from vincentwoo/patch-1
Add CoderPad to real world use cases
2017-01-24 07:48:37 +00:00
Vincent Woo e771483041 Add CoderPad to real world use cases
Hi everyone,

I'm very pleased to say I have cut CoderPad.io over to using xterm.js (replacing term.js). I've very pleased with the work you've done on it. You've implemented the many terrible hacks on top of term.js I had to write, except you've actually implemented stuff the right way.

I look forward to your continued progress on the project!

Cheers,
Vincent
2017-01-23 21:32:14 -08:00
Daniel Imms 4a7c3320fb Merge pull request #504 from Tyriar/fix_mode_recursive_call
Use InputHandler.setMode/resetMode, not Terminal
2017-01-23 12:41:19 -08:00
Daniel Imms 9359dca0f3 Use InputHandler.setMode/resetMode, not Terminal 2017-01-23 11:35:33 -08:00
Paris Kasidiaris c8ccc411d4 Merge pull request #502 from vincentwoo/master
Bundle top-level .js and .json files in NPM
2017-01-23 12:38:35 +02:00
Vincent Woo 537c8bad10 Bundle top-level JSON with npm 2017-01-22 22:28:46 -08:00
Vincent Woo 1a55367fc9 Bundle top-level JS with npm 2017-01-22 22:26:40 -08:00
Daniel Imms 4f7ee7dab0 Skip broken tests 2017-01-20 23:38:56 -08:00
Daniel Imms da16dd7cae Mock viewport.syncScrollArea 2017-01-20 23:22:04 -08:00
Daniel Imms 2aa7608ac0 Fix VPR behavior
Don't wrap cursor to column 0 after a VPR, fixes 1 test
2017-01-20 23:14:08 -08:00
Daniel Imms 74c3a4a6f7 Fix CUD behavior
Don't wrap cursor to column 0 after a CUD, fixes 1 test
2017-01-20 23:12:54 -08:00
Daniel Imms d30fb89fde Fix CUB behavior
Don't wrap cursor to column 0 after a CUB, fixes 3 tests
2017-01-20 23:11:22 -08:00
Daniel Imms 1b344ce789 Implement CAN (Cancel, ^X)
This helps partially pass t0014-CAN.in, it won't work 100% however
due to the very complex parsing logic of vanilla xterm that
gnome-terminal doesn't even seem to match. xterm's source seems to
have multiple ignore cases for a set of characters such that an
escape sequence like \e!!!!!! prints nothing as the ! continually
gets ignored. Our current logic is we ignore the escape sequence
immediately in this case and print the !.
2017-01-20 23:03:38 -08:00
Daniel Imms 99fbcd3d65 Fix LF/VT/FF behavior
Don't wrap cursor to column 0 after an index, fixes 2 tests
2017-01-20 22:14:18 -08:00
Daniel Imms 72063329fe Fix index behavior
Don't wrap cursor to column 0 after an index, fixes 2 tests
2017-01-20 21:49:39 -08:00
Daniel Imms adaf604ce0 Right trim expected output, we don't care for now
This fixes 2 tests
2017-01-20 21:39:27 -08:00
Daniel Imms d5f0fff523 Enable other tests and fix viewport check 2017-01-20 21:22:16 -08:00
Daniel Imms 43c3349494 Prevent wraparound mode from overriding last character 2017-01-20 21:20:11 -08:00
Daniel Imms b5d16ed7d7 Use block comment for SCLD 2017-01-20 20:14:17 -08:00
Daniel Imms aab67c2ed9 Reference CHARSETS using codes 2017-01-20 20:12:47 -08:00
Daniel Imms 0461a822c2 Add character sets for most languages
Part of #481
2017-01-20 19:05:13 -08:00
Paris Kasidiaris 7b87e78ed3 Merge pull request #497 from sourcelair/issue-#488-tabstopwidth
Implement configurable tab stop width
2017-01-21 01:08:23 +02:00
Paris Kasidiaris 6e1628bc3b Fixes on demo based on configurable tabStopWidth 2017-01-21 00:32:39 +02:00
Daniel Imms 370c5a971d Merge pull request #498 from Tyriar/259_fix_setupStops_in_resize
Fix setupStops call in resize
2017-01-20 14:29:13 -08:00
Daniel Imms 9a86e4e109 Fix setupStops call in resize
Fixes #259
2017-01-20 14:23:45 -08:00
Paris Kasidiaris 8cb46f2726 Move tabStopWidth option callback into setOption 2017-01-20 16:19:19 +00:00
Paris Kasidiaris f4293a6dfa Fix #488: Implement configurable tab width 2017-01-20 16:18:19 +00:00
Daniel Imms 1c759f1d6f Merge pull request #495 from Tyriar/494_display_none_resize_fix
Only keep > 0 geometry in CharMeasure
2017-01-20 07:26:46 -08:00
Daniel Imms 6918e83787 Merge pull request #492 from Tyriar/480_cursor_style
Implement CSI Ps SP q Set cursor style
2017-01-20 07:26:36 -08:00
Daniel Imms 9626b1e39d Only keep > 0 geometry in CharMeasure
Fixes #494
2017-01-19 18:02:09 -08:00
Daniel Imms 9227aed906 Add InputHandler.setCursorStyle test 2017-01-18 10:30:35 -08:00
Daniel Imms 0bd469f505 Implement CSI Ps SP q Set cursor style
Fixes #480
2017-01-18 10:06:57 -08:00
Daniel Imms 5bd803382a Merge pull request #462 from Tyriar/459_parser__on_460
Parser refactor and optimization
2017-01-18 10:02:03 -08:00
Daniel Imms ef60e50e1d Add documentation 2017-01-18 09:35:27 -08:00
Daniel Imms ed1ba14c6b Merge pull request #490 from Tyriar/489_namespace_cursor_blink
Namespace cursor-blink CSS animations
2017-01-18 08:18:23 -08:00
Daniel Imms a4080d3706 Namespace cursor-blink CSS animations
Fixes #489
2017-01-18 08:06:41 -08:00
Daniel Imms e2b2f98011 Merge remote-tracking branch 'upstream/master' into 459_parser__on_460 2017-01-16 15:17:35 -08:00
Daniel Imms ed13218f90 Merge pull request #485 from Tyriar/remove_comments_in_build
Remove comments in build
2017-01-16 15:14:42 -08:00
Daniel Imms 1cdd2e00ab Remove parser states from xterm.js 2017-01-16 14:11:00 -08:00
Daniel Imms 94eb08fb73 Remove comments in build 2017-01-16 14:02:55 -08:00
Daniel Imms 5e1ccb358b Merge remote-tracking branch 'upstream/master' into 459_parser__on_460 2017-01-16 13:53:19 -08:00
Daniel Imms 259338b8c2 Merge pull request #482 from Tyriar/348_keep_selection_on_mod_key
Don't drop selection on modifier keyup
2017-01-16 03:49:19 -08:00
Daniel Imms a05bb50230 Don't drop selection on modifier keyup
Fixes #348
2017-01-15 21:41:42 -08:00
Daniel Imms 8a07b8c798 Merge pull request #442 from Tyriar/441_windows_support
Use node-pty for Windows support
2017-01-15 02:38:16 -08:00
Daniel Imms 4d5046b5e7 Merge pull request #479 from Tyriar/478_cursorBlink_refresh
Apply cursorBlink via setOption
2017-01-15 02:35:30 -08:00
Daniel Imms 12160a50cb Fake xterm.element in tests 2017-01-15 02:31:54 -08:00
Daniel Imms 375ccbd9e2 Merge remote-tracking branch 'upstream/master' into 478_cursorBlink_refresh 2017-01-15 02:22:45 -08:00
Daniel Imms cc0f0d94f6 Merge remote-tracking branch 'upstream/master' into 441_windows_support 2017-01-15 02:12:17 -08:00
Paris Kasidiaris 6309752208 Merge pull request #435 from sourcelair/issue-#359-gulp
Introduce build system based on Gulp
2017-01-15 11:49:32 +02:00
Daniel Imms 4448ebe391 Use charsets to map 2017-01-14 22:06:30 -08:00
Daniel Imms d1dac57f7a Move cursor blink style to class on .terminal 2017-01-14 21:26:49 -08:00
Daniel Imms 3422f84e00 Merge pull request #477 from Tyriar/476_scrollback_update
Apply scrollback via setOption
2017-01-14 10:46:54 -08:00
Paris Kasidiaris ac6faf3dd8 Stick gulp-sourcemaps to 1.9.1
Paths became absolute by default at https://github.com/floridoo/gulp-sourcemaps/commit/fb4027a496898b8b7992ea5af9b3d707f1d82d38
2017-01-14 19:52:07 +02:00
Daniel Imms 7679475bfe Apply cursorBlink via setOption
Fixes #478
2017-01-13 21:31:31 -08:00
Daniel Imms 5a932b2a20 Apply scrollback via setOption
Fixes #476
2017-01-13 21:27:19 -08:00
Daniel Imms b1ef934aea Merge pull request #474 from Tyriar/473_refresh_null_checks
Add null checks to refresh line and character fetches
2017-01-13 07:09:12 -08:00
Daniel Imms a2ab0ffc46 Merge pull request #471 from Tyriar/470_CharMeasure_in_helper
Move CharMeasure element to the helper container
2017-01-13 07:08:45 -08:00
Daniel Imms ecd7e69f1a Start adding ESC state handler 2017-01-12 22:36:48 -08:00
Daniel Imms fa3484cd14 Pull character add char code to input handler 2017-01-12 22:17:36 -08:00
Daniel Imms f4846aa154 Finish CSI codes 2017-01-12 21:58:50 -08:00
Daniel Imms 9b66208029 Add more CSI codes 2017-01-12 21:33:28 -08:00
Daniel Imms c43c3b411a Add more CSI codes 2017-01-12 20:51:15 -08:00
Daniel Imms f9a286a851 Convert more CSI codes 2017-01-12 20:32:15 -08:00
Daniel Imms 3de3912b96 Add another null check on children[y] 2017-01-12 11:23:17 -08:00
Daniel Imms 23169e89c8 Add null checks to refresh line and character fetches
Fixes #473
2017-01-12 11:14:14 -08:00
Daniel Imms 0f5f34e8f5 Move CharMeasure element to the helper container
Fixes #470
2017-01-12 10:47:57 -08:00
Daniel Imms 342e862d69 Merge remote-tracking branch 'upstream/master' into 459_parser__on_460 2017-01-11 13:43:33 -08:00
Paris Kasidiaris 081fe3f301 Fix #359 - Introduce build system based on Gulp
- Add Gulp and new dependencies to `package.json`
- Add `gulpfile.js` with four tasks:
    - `tsc`: For building TypeScript sources
    - `bundle`: For bundling JavaScript modules in a monolith
    - `sorcery`: For resolving the source map chains back to the original TypeScript files
    - `build` (`default`): Runs the whole `tsc` → `bundle` → `sorcery` chain
- Clean up `Dockerfile`, since `cpio` is not needed any more
- Clean up not needed dependencies from `package.json`
- Remove `bin/build`
- Update `bin/release` to use `npm run build` instead of `./bin/build`
2017-01-11 08:11:45 +00:00
Daniel Imms fc528f6c0c Merge pull request #447 from Tyriar/425_xon_xoff_on_280
Sync with pty process using XOFF and XON
2017-01-10 21:20:58 -08:00
Daniel Imms 411c974960 Merge pull request #446 from Tyriar/335_viewport_var
Remove var usage from Viewport
2017-01-10 20:24:22 -08:00
Daniel Imms 0ec125557a Merge remote-tracking branch 'upstream/master' into 425_xon_xoff_on_280 2017-01-10 20:24:07 -08:00
Daniel Imms 0b977fc35f Merge pull request #461 from Tyriar/460_escape_sequence_c0
Pull C0 escape sequences into its own file
2017-01-10 20:06:36 -08:00
Daniel Imms 0739a4be06 Merge remote-tracking branch 'upstream/master' into 425_xon_xoff_on_280 2017-01-10 20:04:26 -08:00
Daniel Imms c4de07135f Merge pull request #466 from Tyriar/465_async_CharMeasure_measure
Perform CharMeasure.measure async
2017-01-10 20:03:59 -08:00
Daniel Imms d52b53889e Merge remote-tracking branch 'upstream/master' into 335_viewport_var 2017-01-10 20:03:19 -08:00
Daniel Imms d9682bd6d6 Perform CharMeasure.measure async
This fixes the edge case where getBoundingClientRect was returning a width and
height of 0,0.

Fixes #465
2017-01-10 11:36:22 -08:00
Daniel Imms 411b80cdc0 Add CSI @, E, F, G 2017-01-09 23:06:36 -08:00
Daniel Imms db81c28bd7 Support more CSI codes, remove unused CSI helper functions 2017-01-09 22:57:38 -08:00
Daniel Imms c7fa2d55b4 Handle remaining CSI_PARAM cases and introduce the state 2017-01-09 16:07:27 -08:00
Daniel Imms 7572dd5fa6 Remove IInputHandler from CSI_PARAM lambdas 2017-01-09 15:39:56 -08:00
Daniel Imms 9942477bff Start converting CSI codes 2017-01-09 15:38:54 -08:00
Daniel Imms 635a4d7682 Handle CSI postfix 2017-01-09 15:19:29 -08:00
Daniel Imms 672dc1a251 Start pulling CSI handlers out 2017-01-09 15:17:15 -08:00
Daniel Imms 6deaaa8ef8 Move all parser state handling into the parser 2017-01-09 15:00:37 -08:00
Daniel Imms f4267c4f74 Merge remote-tracking branch 'upstream/master' into 459_parser__on_460 2017-01-09 14:50:28 -08:00
Daniel Imms 1848fff4c9 Pull charsets out of xterm.js 2017-01-09 14:21:59 -08:00
Daniel Imms 991b2843d3 Fix references to Terminaal and fix TS lint warnings 2017-01-09 13:57:58 -08:00
Daniel Imms 940bd2699e Fix wcwidth TS errors 2017-01-09 13:48:52 -08:00
Daniel Imms 4a27b9a53f Move wcwidth into Parser.ts 2017-01-09 13:46:08 -08:00
Daniel Imms a31921ae73 Move parsing logic into Parser.ts 2017-01-09 13:45:55 -08:00
Daniel Imms 27a7f1504c Merge pull request #453 from Tyriar/452_disable_stdin_option
Add disableStdin option
2017-01-09 09:53:22 -08:00
Paris Kasidiaris aee8ddeafb Merge pull request #464 from TylerJewell/patch-1
Add Codenvy to the list of xTerm supporters
2017-01-09 19:17:50 +02:00
Tyler Jewell 6c29ccfbd1 Add Codenvy to the list of xTerm supporters 2017-01-08 17:41:02 -08:00
Daniel Imms da7ea104de Merge remote-tracking branch 'upstream/master' into 460_escape_sequence_c0 2017-01-08 17:13:28 -08:00
Daniel Imms 665a11acc0 Add C0.BS handler back 2017-01-08 17:12:35 -08:00
Daniel Imms dddc91fbcc Merge remote-tracking branch 'upstream/master' into 425_xon_xoff_on_280 2017-01-08 17:05:19 -08:00
Daniel Imms ecb892d995 Merge remote-tracking branch 'upstream/master' into 425_xon_xoff_on_280 2017-01-08 17:05:10 -08:00
Daniel Imms ada96a8373 Merge remote-tracking branch 'upstream/master' into 335_viewport_var 2017-01-08 17:04:18 -08:00
Daniel Imms cabe23b782 Merge pull request #436 from Tyriar/434_tmux_scroll_fix
Fix scroll in tmux with max scrollback
2017-01-08 17:02:44 -08:00
Daniel Imms b25bf9562e Merge pull request #457 from Tyriar/451_fix_events_after_reset
Keep previous terminal events after a reset
2017-01-08 17:00:55 -08:00
Daniel Imms e9da2a6330 Merge pull request #440 from Tyriar/439_wide_char_class
Style wide characters at charWidth*2 in DOM
2017-01-08 17:00:30 -08:00
Daniel Imms 9385b9309d Merge remote-tracking branch 'upstream/master' into 439_wide_char_class 2017-01-08 16:56:47 -08:00
Daniel Imms facc2659e7 Merge pull request #463 from sourcelair/revert-445-444_rate_limit_viewport_refresh
Revert "Rate limit Viewport.refresh"
2017-01-08 16:51:27 -08:00
Daniel Imms 06852cd904 Revert "Rate limit Viewport.refresh" 2017-01-08 16:46:23 -08:00
Daniel Imms a1ee8aefbb Merge remote-tracking branch 'upstream/master' into 439_wide_char_class 2017-01-08 16:32:51 -08:00
Daniel Imms eb3b3fa107 Merge pull request #437 from Tyriar/335_TS_Generic_Browser
Convert browser and generic to TS
2017-01-08 16:25:30 -08:00
Daniel Imms 3457b73ac8 Merge pull request #445 from Tyriar/444_rate_limit_viewport_refresh
Rate limit Viewport.refresh
2017-01-08 16:24:27 -08:00
Daniel Imms 28b38dc76d Merge pull request #438 from Tyriar/280_improve_refresh_queue
Improve refresh queue
2017-01-08 16:24:16 -08:00
Daniel Imms 659fb90c1c Handle C0 codes via handler map in Parser 2017-01-08 16:21:51 -08:00
Daniel Imms 04b1ebf1a7 Create InputHandler which performs C0 actions
Part of #459
2017-01-08 16:11:35 -08:00
Daniel Imms 1685cf2d8f Use namespace and const for C0 constants 2017-01-08 16:02:47 -08:00
Paris Kasidiaris 43d4d4f2bb Merge pull request #458 from TylerJewell/patch-1
Add additional products that use xTerm
2017-01-08 23:49:46 +00:00
Daniel Imms 3de3c0c6b6 Pull C0 escape sequences into its own file
Part of #460
2017-01-08 15:49:19 -08:00
Daniel Imms bebe43e5e1 Use node-pty as it's been published 2017-01-08 13:33:51 -08:00
Tyler Jewell 520f2bc9d6 Add additional products that use xTerm
Hi xTerm team. Over the past 6 months, we have been studying xTerm as a replacement for Eclipse Che's term.js. It's taken us a bit of an effort, but the work is now done!  You can expect that Eclipse Che engineers (abot 60 contributors) will make direct contributions back to xTerm ongoing now that we are fully integrated in. We integrate xTerm alongside GWT.  As we more natively integrate the solution, we'll make additional PRs directly back to the project.

As background on the research and integration work that we did, these are the postings from our engineering team's research from the past six months. Some of these issues have already been resolved, or in the process of being resolved.

We also plan a couple of blog posts to our forums (~250K followers) about xTerm integration later in Q1 after we make our 5.0.0 announcements. I could also add Codenvy to this PR as well since it has been released with the version based upon xTerm, but with Sourcelair being the initiators we didn't want to make it seem like we were trying to be competitive. This PR is about the promotion and success of xTerm, for which we are fully committed to.

## Xtermjs 
This document is result of investigation using xtemjs ui terminal instead of current term.js. It contains analyze pluses and minuses using xtermjs (release version 2.2.3) and technical problems.  Investigation issue: https://github.com/eclipse/che/issues/3210

## Description
For now we are using our own fork of the https://github.com/chjj/term.js for user interface websocket-terminal. But actually this project is no longer maintained. So we can not get new releases from this project and we need support this script on our own. Original project contains link for a maintained fork https://github.com/sourcelair/xterm.js. This fork uses MIT license and community intensive develops this project. It has such users like: Microsoft Visual Studio Code, SourceLair, ttyd. xterm.js had already done 46 releases https://github.com/sourcelair/xterm.js/releases.

## Technical Advantages of xTerm:
* xterm.js has js tests. We have not any test for old term.js.
* We can periodically update xtem.js by new release.
* Added parameter to cancel browser events.
* Default 256 colours moved from js to css.
* Added ability to set terminal theme.
* Improved resize mehanizm (added resize event).
* Added fit.ts script to fits terminal size to original height and width of parent div.
* Fixed lost text selection from current active line(when blink is enabled).
* Improved copy/paster mehanizm.
* Linkify URL feature.
* Implement moving back and forward across words with "Alt + ←" and "Alt + →" respectively.
* Improved special key handling.
* Added addons to exdens xterm.js
* Improved mehanizm copy/paster from clipboard.
* Fixed cross platform input problems(For IPad, Iphone, MacIntel, MacPPC and so on).
* Did some work to support UTF-8 symbols.
* Implemented scrollbar.
* Fixed incorrect mouse position for application with  pseudo-graphic user interface (for example Midnight Commander).
* Fix to prevent terminal scrolling when user is looking into scrollback(similar to gnome-terminal, if the user is scrolling up to look at past output and the currently running program adds output to the terminal, the viewport of xterm.js should not scroll and interrupt what they are looking at.)
* Make right-click work on all browsers.

##  Broken Changes: 
We can move on our changes to realize copy/paste by hotkeys Ctrl + C/Ctrl +V. But xterm.js support hotkeys more common for terminal: Ctrl + Insert and Shift + Insert and maybe that's enough.

Drop support for old mouse wheel APIs: all browsers have supported the WheelEvent (onwheel) for sometime now, since Firefox does not support onmousewheel which is also non-standard but works with the standard interface, it makes sense to drop support now.

## Browser Support
Since xterm.js is typically implemented as a developer tool, only modern browsers are supported officially. Here is a list of the versions to support:
Chrome 48+
Edge 13+
Firefox 44+
Internet Explorer 11+
Opera 35+
Safari 8+

Xterm.js works seamlessly in Electron apps and may even work on earlier versions of the browsers but these are the browsers xterm.js developers strive to keep working.

## Known major bugs:
https://github.com/sourcelair/xterm.js/issues/307
https://github.com/sourcelair/xterm.js/issues/362
https://github.com/sourcelair/xterm.js/issues/348
https://github.com/sourcelair/xterm.js/issues/325 Data loss when resizing terminal (but this bug is exist in the current terminal ui in the CHE too). Community has pull request to fix this issue for xtermjs https://github.com/sourcelair/xterm.js/pull/404 .
 
Additional information: 

Code base xterm.js consist of files written on native javascript and typescript. For manage js dependency and configuration used npm, typings, bower and node. Development tendency: rewrite xtemr.js completely on the Typescript(information about this included to the release notes 2.2.3).
2017-01-08 09:44:14 -08:00
Daniel Imms 996c25a364 Keep previous terminal events after a reset
This broke in the EventEmitter ts conversion, just reverts this line to what
it used to be.

Fixes #451
2017-01-07 16:04:49 -08:00
Daniel Imms d9d60063d7 Add disableStdin option
Fixes #452
2017-01-04 12:43:21 -08:00
Daniel Imms ff523bfc79 Fix tests 2017-01-04 10:30:04 -08:00
Daniel Imms 94c01ec378 Fix tests 2017-01-04 08:08:59 -08:00
Daniel Imms 2b8820fdac Further tweaks, add write batching 2017-01-03 15:52:45 -08:00
Daniel Imms 0ec7b661f2 Tweak config values 2017-01-03 15:38:14 -08:00
Daniel Imms dc5efa886a Make innerWrite iterative 2017-01-03 15:29:23 -08:00
Daniel Imms e66b1c5704 Add write buffer pause and refresh frame skip 2017-01-03 15:26:41 -08:00
Daniel Imms 6b8c43ed7f Send \x13 when a write buffer threadhold is reached 2017-01-03 14:57:13 -08:00
Daniel Imms b9d374affa Add XON/XOFF and eparate write from processing
Part of #425
2017-01-03 14:57:04 -08:00
Daniel Imms d2ef19dffe Remove var usage from Viewport
Part of #335
2017-01-03 11:26:59 -08:00
Daniel Imms efdf37b887 jsdoc 2017-01-03 11:23:36 -08:00
Daniel Imms 6ffb8f545b Rate limit Viewport.refresh
This prevents 1000 scroll events from firing when the buffer is not full

Fixes #444
2017-01-03 11:18:38 -08:00
Daniel Imms 2e4147b4fa Use Tyriar/pty.js for Windows support
Fixes #441
2017-01-01 16:24:55 -08:00
Daniel Imms 07a8b8f4f7 Fix tests 2016-12-31 18:06:49 -08:00
Daniel Imms 74483fb294 Use CharMeasure in Viewport and to style wide chars
Fixes #439
2016-12-31 15:56:57 -08:00
Daniel Imms 4f18d842f9 Add CharMeasure util class 2016-12-31 15:46:17 -08:00
Daniel Imms 188e197e20 Wrap wide chars in a span/class
Part of #439
2016-12-31 15:19:54 -08:00
Daniel Imms 7234bfb6eb Move row evaluation into refreshLoop
This allows the refresh public API to still function
2016-12-31 14:58:17 -08:00
Daniel Imms 97feb3321c Improve refresh queue
Use requestAnimationFrame in addition to a queue to refresh every animation
frame but only when a refresh is needed.

Fixes #280
Fixes #290
2016-12-31 14:14:02 -08:00
Daniel Imms 3e1a607179 Convert browser and generic to TS
Part of #335
2016-12-31 07:36:56 -08:00
Daniel Imms c3f46e4a6b Fix scroll in tmux with max scrollback
I believe this is related to when scrollBottom is not the last row in the viewport

Fixes #434
2016-12-31 07:18:50 -08:00
Daniel Imms 1fbd3c8f65 Merge pull request #432 from Tyriar/335_TS_EventEmitter
Convert EventEmitter to TypeScript and add tests
2016-12-31 07:14:47 -08:00
Daniel Imms 965ebde22f Remove remaining removeListener ref 2016-12-31 07:11:47 -08:00
Daniel Imms 0d9d3aca1a Remove addListener/removeListener 2016-12-31 07:10:45 -08:00
Daniel Imms 937cf43e09 Remove redundant assignment in constructor 2016-12-31 07:07:45 -08:00
Daniel Imms 23472e6fb4 Merge pull request #431 from Tyriar/427_refresh_scroll_on_buffer_change
Sync scroll bar when alt/normal buffer is switched in
2016-12-30 04:42:36 -08:00
Antonis Kalipetis 445728b2a2 Merge pull request #403 from akalipetis/docker-enhancement
Improve the Docker image
2016-12-30 11:24:22 +02:00
Antonis Kalipetis 61c213f9e3 Ignore all the build directories in Docker
This allows for reproducible builds.
Also, fix Dockerfile to first build and then run tests and a typo in the entrypoint.

Thanks @BenHall for reporting the issue with the Docker build!
2016-12-29 16:13:58 +02:00
Daniel Imms dba432d1b6 Add tests 2016-12-29 05:09:47 -08:00
Daniel Imms 2f416371bd Convert EventEmitter to TS
Pat of #335
2016-12-29 04:53:57 -08:00
Daniel Imms 2bfbdb1c06 Sync scroll bar when alt/normal buffer is switched in
Fixes #427
2016-12-29 03:55:36 -08:00
Antonis Kalipetis a4c958c54f Fix EOL character in .editorconfig and update docker-compose 2016-12-28 18:49:36 +02:00
Antonis Kalipetis b2e257bf37 Remove entrypoint
Signed-off-by: Antonis Kalipetis <akalipetis@gmail.com>
2016-12-28 18:45:21 +02:00
Antonis Kalipetis 6b6314227e Improve the Docker image
1. Bump Node version to 6.9 - the latest LTS
2. Include the `cpio` binary, used during building
3. Ignore node_modules and .git directories for faster builds
4. Run the tests and build during the image build, to make sure the image is not built if these break
5. Make npm run dev the default command
6. Add an entrypoint to automatically install Node modules if they do nott exist

Signed-off-by: Antonis Kalipetis <akalipetis@gmail.com>
2016-12-28 18:45:18 +02:00
Antonis Kalipetis 1e0eb3784e Merge pull request #402 from akalipetis/hotfix-npm-scripts
Improve the package.json scripts
2016-12-28 18:44:20 +02:00
Antonis Kalipetis f00ea4bfac Add a Procfile with the npm start command
Also, update the Procfile.dev with the updated `npm run dev` command.

Signed-off-by: Antonis Kalipetis <akalipetis@gmail.com>
2016-12-28 18:12:23 +02:00
Antonis Kalipetis 0da4c9d90d Improve the package.json scripts
1. Include a `prestart` script, which runs the build
2. Make the `start` script just start the demo server
3. Add a new `dev` script, which contains the nodemon logic
4. nodemon now watches for changes in `*.ts` files and runs the start command

Signed-off-by: Antonis Kalipetis <akalipetis@gmail.com>
2016-12-28 18:12:20 +02:00
Daniel Imms df83298c54 Merge pull request #429 from Tyriar/428_scrollback_exception
Ensure ydisp does not drop below 0 in refresh
2016-12-28 05:59:43 -08:00
Daniel Imms 5d29d4ea22 Remove log 2016-12-28 03:41:48 -08:00
Daniel Imms a9e1ed5a72 Ensure ydisp does not drop below 0 in refresh
Fixes #428
Related to #385
2016-12-28 03:41:12 -08:00
Daniel Imms e6ad8d8b50 Merge pull request #422 from Tyriar/361_circular_list_scrollback
Use a circular list to manage buffer
2016-12-28 03:10:31 -08:00
Daniel Imms 50cfe2b21a Improve header comment 2016-12-27 13:40:52 -08:00
Daniel Imms d7f2ec89d7 Polish 2016-12-24 03:17:30 -08:00
Daniel Imms cc5ae8193b Fix insertLines as max buffer size 2016-12-24 00:26:43 -08:00
Daniel Imms 30a1f1ccb5 Fix issue with max buffer size after vim fix 2016-12-24 00:14:30 -08:00
Daniel Imms 6f7cb990ca Fix issue with vim scrolling 2016-12-24 00:04:31 -08:00
Paris Kasidiaris 06f5746c86 Merge pull request #423 from katacoda/katacoda-integration
Katacoda xterm.js integration is live
2016-12-22 23:30:00 +02:00
Ben Hall cd44fe7bcd Katacoda xterm.js integration is live 2016-12-22 20:10:33 +00:00
Ben Hall bb3d6ee72c Katacoda xterm.js integration is live 2016-12-22 12:01:44 +00:00
Daniel Imms bf16fdc01d Have reverseIndex use CircularList.shiftElements 🎉 2016-12-21 03:26:40 -08:00
Daniel Imms 969a2e953c Implement CircularList.shiftElements 2016-12-21 03:18:30 -08:00
Daniel Imms bfaf9cd6eb Doc and testing 2016-12-21 02:39:21 -08:00
Daniel Imms be2e4b5d85 Add missing return type 2016-12-21 02:11:14 -08:00
Daniel Imms cc0fc953cf Remove logs 2016-12-22 02:04:31 -08:00
Daniel Imms 3b35d12e60 Fix issue with the git log
The old code was assuming that the buffer was not going to change, this is not
true with the current impl though where the list is shifted and ybase and ydisp
need to be compensated for that.
2016-12-22 02:02:05 -08:00
Daniel Imms 4bc679b0b3 Merge remote-tracking branch 'upstream/master' into 361_circular_list_scrollback 2016-12-21 15:43:18 -08:00
Daniel Imms be56c72b5f Fix when scrollback limit is reached 2016-12-21 15:43:04 -08:00
Paris Kasidiaris db92840ffc Merge pull request #415 from saswatds/saswatds-patch-1
Update README.md
2016-12-19 15:47:19 +02:00
Paris Kasidiaris 593a4ec0b0 Merge pull request #417 from BobReid/os_x_bash_arrow_fix
changed escape sequence for alt-arrow to work on bash on os x
2016-12-19 15:45:57 +02:00
Saswat Das d018a547a2 Update README.md
Added Getting started content from the xtermjs.org website
2016-12-18 04:48:19 +05:30
Paris Kasidiaris a246200675 Merge pull request #419 from blink1073/clean-up-npm-publish
Clean up npm publish
2016-12-16 20:46:27 +00:00
Steven Silvester 1cb6517812 Revert changes to other published files 2016-12-16 14:41:29 -06:00
Bob Reid 2824da371f Added macOS check to escape sequences and updated tests 2016-12-16 15:03:49 -05:00
Steven Silvester 22eb86089f Clean up npm publish 2016-12-16 13:28:10 -06:00
Bob Reid 2e17366bce changed escape sequence for alt-arrow to work on bash on os x 2016-12-16 10:29:42 -05:00
Paris Kasidiaris ef407ae93a Bump version to 2.2.3
Signed-off-by: Paris Kasidiaris <paris@sourcelair.com>
2016-12-15 21:56:32 +00:00
Paris Kasidiaris 9032ce99d3 Merge pull request #416 from sourcelair/issue-#414-css-files-in-npm
Include appropriate files in npm.
2016-12-15 21:53:19 +00:00
Paris Kasidiaris adaaeb6f36 Include appropriate files in npm.
Fix #414
2016-12-15 23:49:43 +02:00
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
Saswat Das 4ee1e00808 Update README.md
Adding a getting started instruction for adding xterm to a parent div and listening for data output from xterm
2016-12-15 21:26:04 +05:30
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
Daniel Imms fd25bd7ca2 Merge remote-tracking branch 'upstream/master' into 361_circular_list_scrollback 2016-12-12 15:05:19 -08: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 607c819109 Progress 2016-11-27 03:27:36 -08:00
Daniel Imms 61fbbb0054 Add CiruclarList pop and splice 2016-11-27 02:20:53 -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 49b76baf3f Add CircularList and basic tests
Part of #361
2016-11-26 16:57:15 -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
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
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
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
226 changed files with 9656 additions and 6298 deletions
-3
View File
@@ -1,3 +0,0 @@
{
"presets": ["es2015"]
}
+16
View File
@@ -0,0 +1,16 @@
node_modules/
*.swp
.lock-wscript
lib/
Makefile.gyp
*.Makefile
*.target.gyp.mk
*.node
example/*.log
docs/
npm-debug.log
/.idea/
.env
build/
.vscode/
.git/
+2 -1
View File
@@ -5,8 +5,9 @@ indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
end_of_line = lf
[*.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
@@ -12,3 +11,5 @@ docs/
npm-debug.log
/.idea/
.env
build/
.vscode/
+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
+23
View File
@@ -0,0 +1,23 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Mocha Tests",
"cwd": "${workspaceRoot}",
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/mocha",
"windows": {
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/mocha.cmd"
},
"runtimeArgs": [
"--colors",
"--recursive",
"${workspaceRoot}/lib"
],
"sourceMaps": true,
"outFiles": [ "${workspaceRoot}/lib/**/*.js" ],
"internalConsoleOptions": "openOnSessionStart"
}
]
}
+12
View File
@@ -7,8 +7,13 @@ Anton Skshidlevsky <meefik@gmail.com>
Anton Yurovskykh <anton.yurovskykh@gmail.com>
Austin Robertson <austinrobertson@gmail.com>
ayapi <colors.aya@gmail.com>
Ben Hall <ben@benhall.me.uk>
Benjamin Fischer <benjamin.fischer@rwth-aachen.de>
Bill Church <billchurch@users.noreply.github.com>
Bob Reid <bobreid@Bobs-MacBook-Pro.local>
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>
@@ -20,10 +25,17 @@ Ian Lewis <ianlewis@google.com>
imoses <ido@twiggle.com>
Jean Bruenn <himself@jeanbruenn.info>
Jörg Breitbart <jerch@rockborn.de>
Lucian Buzzo <lucian.buzzo@gmail.com>
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>
Saswat Das <saswatds@users.noreply.github.com>
Shuanglei Tao <tsl0922@gmail.com>
Steven Silvester <steven.silvester@ieee.org>
Thanasis Daglis <thanasis@sourcelair.com>
Tine Jozelj <tine.jozelj@outlook.com>
Tyler Jewell <tjewell@codenvy.com>
Vincent Woo <me@vincentwoo.com>
YuviPanda <yuvipanda@gmail.com>
+17 -2
View File
@@ -1,4 +1,19 @@
FROM node:4-onbuild
FROM node:6.9
MAINTAINER Paris Kasidiaris <paris@sourcelair.com>
EXPOSE 3000
# Set the working directory
WORKDIR /usr/src/app
# Set an entrypoint, to automatically install node modules
ENTRYPOINT ["/bin/bash", "-c", "if [[ ! -d node_modules ]]; then npm install; fi; exec \"${@:0}\";"]
CMD ["npm", "run", "dev"]
# First, install dependencies to improve layer caching
COPY package.json /usr/src/app/
RUN npm install
# Add the code
COPY . /usr/src/app
# Run the tests and build, to make sure everything is working nicely
RUN npm run build && npm run test
+1
View File
@@ -0,0 +1 @@
web: npm start
+1 -1
View File
@@ -1 +1 @@
web: npm start
web: npm run dev
+39 -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,12 @@ 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
- [**Katacoda**](https://www.katacoda.com/): Katacoda is an Interactive Learning Platform for software developers, covering the latest Cloud Native technologies.
- [**Eclipse Che**](http://www.eclipse.org/che): Developer workspace server, cloud IDE, and Eclipse next-generation IDE.
- [**Codenvy**](http://www.codenvy.com): Cloud workspaces for development teams.
- [**CoderPad**](https://coderpad.io): Online interviewing platform for programmers. Run code in many programming languages, with results displayed by `xterm.js`.
- [**WebSSH2**](https://github.com/billchurch/WebSSH2): A web based SSH2 client using `xterm.js`, socket.io, and ssh2.
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.
@@ -51,15 +57,37 @@ npm start
Then open http://0.0.0.0:3000 in a web browser (use http://127.0.0.1:3000 if running under Windows).
## Getting Started
To start using xterm.js on your browser, add the `xterm.js` and `xterm.css` to the head of your html page. Then create a `<div id="terminal"></div>` onto which xterm can attach itself.
```html
<!doctype html>
<html>
<head>
<link rel="stylesheet" href="bower_components/xterm.js/dist/xterm.css" />
<script src="bower_components/xterm.js/dist/xterm.js"></script>
</head>
<body>
<div id="terminal"></div>
<script>
var term = new Terminal();
term.open(document.getElementById('#terminal'));
term.write('Hello from \033[1;3;31mxterm.js\033[0m $ ')
</script>
</body>
</html>
```
Finally instantiate the `Terminal` object and then call the `open` function with the DOM object of the `div`.
## 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
@@ -92,6 +120,14 @@ Visit https://lair.io/sourcelair/xterm and follow the instructions. All developm
[Download Visual Studio Code](http://code.visualstudio.com/Download), clone xterm.js and you are all set.
#### [Eclipse Che](http://www.eclipse.org/che)
You can start Eclipse Che with `docker run eclipse/che start`.
#### [Codenvy](http://www.codenvy.io)
You can create a trial account or install an enterprise version with `docker run codenvy/cli start`.
## License Agreement
If you contribute code to this project, you are implicitly allowing your code to be distributed under the MIT license. You are also implicitly verifying that all code is your original work.
-38
View File
@@ -1,38 +0,0 @@
#! /usr/bin/env node
var child_process = require('child_process');
var fs = require('fs');
var buildDir = process.env.BUILD_DIR || 'build';
if (!fs.existsSync(buildDir)){
fs.mkdirSync(buildDir);
}
// Add `node_modules/.bin` to PATH
process.env.PATH = process.cwd() + '/node_modules/.bin:' + process.env.PATH;
console.log('Building xterm.js into ' + buildDir);
// 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...');
var jsBuildCmd = 'browserify src/xterm.js -s Terminal -t [ babelify --presets [ es2015 ] ] --debug | ';
jsBuildCmd += 'derequire | exorcist ' + buildDir + '/xterm.js.map > ' + buildDir + '/xterm.js';
var jsBuildProcess = child_process.execSync(jsBuildCmd);
if (jsBuildProcess.status) {
console.log(jsBuildProcess.error);
}
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.');
+1 -1
View File
@@ -21,7 +21,7 @@ CURRENT_BOWER_JSON_VERSION=$(cat bower.json \
# Build xterm.js into `dist`
export BUILD_DIR=dist
./bin/build
npm run build
# Update AUTHORS file
sh bin/generate-authors
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "xterm.js",
"version": "2.1.0",
"version": "2.3.0",
"ignore": ["demo", "test", ".gitignore"],
"main": [
"dist/xterm.js",
+1 -2
View File
@@ -2,13 +2,12 @@ var express = require('express');
var app = express();
var expressWs = require('express-ws')(app);
var os = require('os');
var pty = require('pty.js');
var pty = require('node-pty');
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');
+15 -7
View File
@@ -2,21 +2,29 @@
<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>
<div id="terminal-container"></div>
<div>
<h2>Options</h2>
<label><input type="checkbox" id="option-cursor-blink"> cursorBlink</label>
<p>
<label><input type="checkbox" id="option-cursor-blink"> cursorBlink</label>
</p>
<p>
<label>scrollback <input type="number" id="option-scrollback" value="1000" /></label>
</p>
<p>
<label>tabStopWidth <input type="number" id="option-tabstopwidth" value="8" /></label>
</p>
<div>
<h3>Size</h3>
<div>
+17 -6
View File
@@ -8,14 +8,16 @@ var term,
var terminalContainer = document.getElementById('terminal-container'),
optionElements = {
cursorBlink: document.querySelector('#option-cursor-blink')
cursorBlink: document.querySelector('#option-cursor-blink'),
scrollback: document.querySelector('#option-scrollback'),
tabstopwidth: document.querySelector('#option-tabstopwidth')
},
colsElement = document.getElementById('cols'),
rowsElement = document.getElementById('rows');
function setTerminalSize () {
var cols = parseInt(colsElement.value),
rows = parseInt(rowsElement.value),
var cols = parseInt(colsElement.value, 10),
rows = parseInt(rowsElement.value, 10),
width = (cols * charWidth).toString() + 'px',
height = (rows * charHeight).toString() + 'px';
@@ -27,7 +29,15 @@ function setTerminalSize () {
colsElement.addEventListener('change', setTerminalSize);
rowsElement.addEventListener('change', setTerminalSize);
optionElements.cursorBlink.addEventListener('change', createTerminal);
optionElements.cursorBlink.addEventListener('change', function () {
term.setOption('cursorBlink', optionElements.cursorBlink.checked);
});
optionElements.scrollback.addEventListener('change', function () {
term.setOption('scrollback', parseInt(optionElements.scrollback.value, 10));
});
optionElements.tabstopwidth.addEventListener('change', function () {
term.setOption('tabStopWidth', parseInt(optionElements.tabstopwidth.value, 10));
});
createTerminal();
@@ -37,7 +47,9 @@ function createTerminal() {
terminalContainer.removeChild(terminalContainer.children[0]);
}
term = new Terminal({
cursorBlink: optionElements.cursorBlink.checked
cursorBlink: optionElements.cursorBlink.checked,
scrollback: parseInt(optionElements.scrollback.value, 10),
tabStopWidth: parseInt(optionElements.tabstopwidth.value, 10)
});
term.on('resize', function (size) {
if (!pid) {
@@ -78,7 +90,6 @@ function createTerminal() {
});
}
function runRealTerminal() {
term.attach(socket);
term._initialized = true;
-4
View File
@@ -20,7 +20,3 @@ h1 {
color: #fafafa;
padding: 2px;
}
#terminal-container .terminal:focus .terminal-cursor {
background-color: #fafafa;
}
+6 -14
View File
@@ -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('../../dist/xterm'));
module.exports = attach(require('../../xterm'));
} else if (typeof define == 'function') {
/*
* Require.js is available
*/
define(['../../dist/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('../../dist/xterm'));
module.exports = fit(require('../../xterm'));
} else if (typeof define == 'function') {
/*
* Require.js is available
*/
define(['../../dist/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;
@@ -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('../../dist/xterm'));
module.exports = fullscreen(require('../../xterm'));
} else if (typeof define == 'function') {
/*
* Require.js is available
*/
define(['../../dist/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('../../dist/xterm'));
module.exports = linkify(require('../../xterm'));
} else if (typeof define == 'function') {
/*
* Require.js is available
*/
define(['../../dist/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\\.-]+',
@@ -1,9 +1,8 @@
/*
* Implements the attach method that
* attaches the terminal to a Terminado WebSocket stream.
/**
* This module provides methods for attaching a terminal to a terminado WebSocket stream.
*
* The bidirectional argument indicates, whether the terminal should
* send data to the socket as well and is true, by default.
* @module xterm/addons/terminado/terminado
* @license MIT
*/
(function (attach) {
@@ -11,12 +10,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 +25,6 @@
})(function (Xterm) {
'use strict';
/**
* This module provides methods for attaching a terminal to a WebSocket
* stream.
*
* @module xterm/addons/attach/attach
*/
var exports = {};
/**
+41 -5
View File
@@ -61,7 +61,7 @@
position: absolute;
opacity: 0;
left: -9999em;
top: -9999em;
top: 0;
width: 0;
height: 0;
z-index: -10;
@@ -71,7 +71,7 @@
resize: none;
}
.terminal .terminal-cursor {
.terminal:not(.xterm-cursor-style-underline):not(.xterm-cursor-style-bar) .terminal-cursor {
background-color: #fff;
color: #000;
}
@@ -82,11 +82,11 @@
background-color: transparent;
}
.terminal.focus .terminal-cursor.blinking {
animation: blink-cursor 1.2s infinite step-end;
.terminal:not(.xterm-cursor-style-underline):not(.xterm-cursor-style-bar).focus.xterm-cursor-blink .terminal-cursor {
animation: xterm-cursor-blink 1.2s infinite step-end;
}
@keyframes blink-cursor {
@keyframes xterm-cursor-blink {
0% {
background-color: #fff;
color: #000;
@@ -97,6 +97,38 @@
}
}
.terminal.xterm-cursor-style-bar .terminal-cursor,
.terminal.xterm-cursor-style-underline .terminal-cursor {
position: relative;
}
.terminal.xterm-cursor-style-bar .terminal-cursor::before,
.terminal.xterm-cursor-style-underline .terminal-cursor::before {
content: "";
display: block;
position: absolute;
background-color: #fff;
}
.terminal.xterm-cursor-style-bar .terminal-cursor::before {
top: 0;
bottom: 0;
left: 0;
width: 1px;
}
.terminal.xterm-cursor-style-underline .terminal-cursor::before {
bottom: 0;
left: 0;
right: 0;
height: 1px;
}
.terminal.xterm-cursor-style-bar.focus.xterm-cursor-blink .terminal-cursor::before,
.terminal.xterm-cursor-style-underline.focus.xterm-cursor-blink .terminal-cursor::before {
animation: xterm-cursor-non-bar-blink 1.2s infinite step-end;
}
@keyframes xterm-cursor-non-bar-blink {
0% { background-color: #fff; }
50% { background-color: transparent; }
}
.terminal .composition-view {
background: #000;
color: #FFF;
@@ -116,6 +148,10 @@
overflow-y: scroll;
}
.terminal .xterm-wide-char {
display: inline-block;
}
.terminal .xterm-rows {
position: absolute;
left: 0;
+3835 -2200
View File
File diff suppressed because it is too large Load Diff
+1 -27
View File
File diff suppressed because one or more lines are too long
+9
View File
@@ -0,0 +1,9 @@
version: '2'
services:
web:
build: ./
volumes:
- ./:/usr/src/app
ports:
- 3000:3000
+87
View File
@@ -0,0 +1,87 @@
const browserify = require('browserify');
const buffer = require('vinyl-buffer');
const fs = require('fs-extra');
const gulp = require('gulp');
const merge = require('merge-stream');
const sorcery = require('sorcery');
const source = require('vinyl-source-stream');
const sourcemaps = require('gulp-sourcemaps');
const ts = require('gulp-typescript');
const tsify = require('tsify');
let buildDir = process.env.BUILD_DIR || 'build';
/**
* Compile TypeScript sources to JavaScript files and create a source map file for each TypeScript
* file compiled.
*/
gulp.task('tsc', function () {
// Remove the lib/ directory to prevent confusion if files were deleted in src/
fs.emptyDirSync('lib');
// Build all TypeScript files (including tests) to lib/, based on the configuration defined in
// `tsconfig.json`.
let tsProject = ts.createProject('tsconfig.json');
let tsResult = tsProject.src().pipe(sourcemaps.init()).pipe(tsProject());
let tsc = tsResult.js.pipe(sourcemaps.write('.', {includeContent: false, sourceRoot: ''})).pipe(gulp.dest('lib'));
// Copy all addons from src/ to lib/
let copyAddons = gulp.src('src/addons/**/*').pipe(gulp.dest('lib/addons'));
// Copy stylesheets from src/ to lib/
let copyStylesheets = gulp.src('src/**/*.css').pipe(gulp.dest('lib'));
return merge(tsc, copyAddons, copyStylesheets);
});
/**
* Bundle JavaScript files produced by the `tsc` task, into a single file named `xterm.js` with
* Browserify.
*/
gulp.task('browserify', ['tsc'], function() {
// Ensure that the build directory exists
fs.ensureDirSync(buildDir);
let browserifyOptions = {
basedir: buildDir,
debug: true,
entries: ['../lib/xterm.js'],
standalone: 'Terminal',
cache: {},
packageCache: {}
};
let bundleStream = browserify(browserifyOptions)
.plugin(tsify)
.bundle()
.pipe(source('xterm.js'))
.pipe(buffer())
.pipe(sourcemaps.init({loadMaps: true, sourceRoot: '..'}))
.pipe(sourcemaps.write('./'))
.pipe(gulp.dest(buildDir));
// Copy all add-ons from lib/ to buildDir
let copyAddons = gulp.src('lib/addons/**/*').pipe(gulp.dest(`${buildDir}/addons`));
// Copy stylesheets from src/ to lib/
let copyStylesheets = gulp.src('lib/**/*.css').pipe(gulp.dest(buildDir));
return merge(bundleStream, copyAddons, copyStylesheets);
});
/**
* Use `sorcery` to resolve the source map chain and point back to the TypeScript files.
* (Without this task the source maps produced for the JavaScript bundle points into the
* compiled JavaScript files in lib/).
*/
gulp.task('sorcery', ['browserify'], function () {
var chain = sorcery.loadSync(`${buildDir}/xterm.js`);
var map = chain.apply();
chain.writeSync();
});
gulp.task('build', ['sorcery']);
gulp.task('default', ['build']);
+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",
+52 -13
View File
@@ -1,36 +1,75 @@
{
"name": "xterm",
"version": "2.1.0",
"description": "Full xterm terminal, in your browser",
"version": "2.3.0",
"ignore": [
"demo",
"test",
".gitignore"
],
"main": "dist/xterm.js",
"main": "lib/xterm.js",
"repository": "https://github.com/sourcelair/xterm.js",
"license": "MIT",
"files": [
"*.js",
"*.json",
"dist/*.css",
"dist/**/*.css",
"dist/*.js",
"dist/*.js.map",
"dist/**/*.js",
"dist/**/*.js.map",
"lib/*.css",
"lib/**/*.css",
"lib/*.js",
"lib/*.js.map",
"lib/**/*.js",
"lib/**/*.js.map",
"src/*.css",
"src/**/*.css",
"src/*.js",
"src/*.js.map",
"src/*.ts",
"src/**/*.js",
"src/**/*.js.map",
"src/**/*.ts"
],
"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",
"fs-extra": "^1.0.0",
"glob": "^7.0.5",
"jsdoc": "3.4.0",
"gulp": "^3.9.1",
"gulp-cli": "^1.2.2",
"gulp-sourcemaps": "1.9.1",
"gulp-typescript": "^3.1.3",
"jsdoc": "3.4.3",
"merge-stream": "^1.0.1",
"mocha": "2.5.3",
"node-pty": "^0.4.1",
"nodemon": "1.10.2",
"pty.js": "0.3.1",
"sleep": "^3.0.1"
"sleep": "^3.0.1",
"sorcery": "^0.10.0",
"tsify": "^3.0.0",
"tslint": "^4.0.2",
"typescript": "^2.0.3",
"vinyl-buffer": "^1.0.0",
"vinyl-source-stream": "^1.1.0"
},
"scripts": {
"start": "nodemon --watch src --watch addons --exec bash -c './bin/build && node demo/app'",
"test": "mocha --recursive --compilers js:babel-core/register",
"prestart": "npm run build",
"start": "node demo/app",
"dev": "nodemon -e js,ts --watch src --watch demo --exec npm start",
"lint": "tslint src/*.ts src/**/*.ts",
"test": "mocha --recursive ./lib",
"build:docs": "jsdoc -c jsdoc.json",
"build": "./bin/build"
"build": "gulp build",
"prepublish": "npm run build"
}
}
+252
View File
@@ -0,0 +1,252 @@
/**
* @license MIT
*/
/**
* The character sets supported by the terminal. These enable several languages
* to be represented within the terminal with only 8-bit encoding. See ISO 2022
* for a discussion on character sets. Only VT100 character sets are supported.
*/
export const CHARSETS: {[key: string]: {[key: string]: string}} = {};
/**
* The default character set, US.
*/
export const DEFAULT_CHARSET = CHARSETS['B'];
/**
* DEC Special Character and Line Drawing Set.
* Reference: http://vt100.net/docs/vt102-ug/table5-13.html
* A lot of curses apps use this if they see TERM=xterm.
* testing: echo -e '\e(0a\e(B'
* The xterm output sometimes seems to conflict with the
* reference above. xterm seems in line with the reference
* when running vttest however.
* The table below now uses xterm's output from vttest.
*/
CHARSETS['0'] = {
'`': '\u25c6', // '◆'
'a': '\u2592', // '▒'
'b': '\u0009', // '\t'
'c': '\u000c', // '\f'
'd': '\u000d', // '\r'
'e': '\u000a', // '\n'
'f': '\u00b0', // '°'
'g': '\u00b1', // '±'
'h': '\u2424', // '\u2424' (NL)
'i': '\u000b', // '\v'
'j': '\u2518', // '┘'
'k': '\u2510', // '┐'
'l': '\u250c', // '┌'
'm': '\u2514', // '└'
'n': '\u253c', // '┼'
'o': '\u23ba', // '⎺'
'p': '\u23bb', // '⎻'
'q': '\u2500', // '─'
'r': '\u23bc', // '⎼'
's': '\u23bd', // '⎽'
't': '\u251c', // '├'
'u': '\u2524', // '┤'
'v': '\u2534', // '┴'
'w': '\u252c', // '┬'
'x': '\u2502', // '│'
'y': '\u2264', // '≤'
'z': '\u2265', // '≥'
'{': '\u03c0', // 'π'
'|': '\u2260', // '≠'
'}': '\u00a3', // '£'
'~': '\u00b7' // '·'
};
/**
* British character set
* ESC (A
* Reference: http://vt100.net/docs/vt220-rm/table2-5.html
*/
CHARSETS['A'] = {
'#': '£'
};
/**
* United States character set
* ESC (B
*/
CHARSETS['B'] = null;
/**
* Dutch character set
* ESC (4
* Reference: http://vt100.net/docs/vt220-rm/table2-6.html
*/
CHARSETS['4'] = {
'#': '£',
'@': '¾',
'[': 'ij',
'\\': '½',
']': '|',
'{': '¨',
'|': 'f',
'}': '¼',
'~': '´'
};
/**
* Finnish character set
* ESC (C or ESC (5
* Reference: http://vt100.net/docs/vt220-rm/table2-7.html
*/
CHARSETS['C'] =
CHARSETS['5'] = {
'[': 'Ä',
'\\': 'Ö',
']': 'Å',
'^': 'Ü',
'`': 'é',
'{': 'ä',
'|': 'ö',
'}': 'å',
'~': 'ü'
};
/**
* French character set
* ESC (R
* Reference: http://vt100.net/docs/vt220-rm/table2-8.html
*/
CHARSETS['R'] = {
'#': '£',
'@': 'à',
'[': '°',
'\\': 'ç',
']': '§',
'{': 'é',
'|': 'ù',
'}': 'è',
'~': '¨'
};
/**
* French Canadian character set
* ESC (Q
* Reference: http://vt100.net/docs/vt220-rm/table2-9.html
*/
CHARSETS['Q'] = {
'@': 'à',
'[': 'â',
'\\': 'ç',
']': 'ê',
'^': 'î',
'`': 'ô',
'{': 'é',
'|': 'ù',
'}': 'è',
'~': 'û'
};
/**
* German character set
* ESC (K
* Reference: http://vt100.net/docs/vt220-rm/table2-10.html
*/
CHARSETS['K'] = {
'@': '§',
'[': 'Ä',
'\\': 'Ö',
']': 'Ü',
'{': 'ä',
'|': 'ö',
'}': 'ü',
'~': 'ß'
};
/**
* Italian character set
* ESC (Y
* Reference: http://vt100.net/docs/vt220-rm/table2-11.html
*/
CHARSETS['Y'] = {
'#': '£',
'@': '§',
'[': '°',
'\\': 'ç',
']': 'é',
'`': 'ù',
'{': 'à',
'|': 'ò',
'}': 'è',
'~': 'ì'
};
/**
* Norwegian/Danish character set
* ESC (E or ESC (6
* Reference: http://vt100.net/docs/vt220-rm/table2-12.html
*/
CHARSETS['E'] =
CHARSETS['6'] = {
'@': 'Ä',
'[': 'Æ',
'\\': 'Ø',
']': 'Å',
'^': 'Ü',
'`': 'ä',
'{': 'æ',
'|': 'ø',
'}': 'å',
'~': 'ü'
};
/**
* Spanish character set
* ESC (Z
* Reference: http://vt100.net/docs/vt220-rm/table2-13.html
*/
CHARSETS['Z'] = {
'#': '£',
'@': '§',
'[': '¡',
'\\': 'Ñ',
']': '¿',
'{': '°',
'|': 'ñ',
'}': 'ç'
};
/**
* Swedish character set
* ESC (H or ESC (7
* Reference: http://vt100.net/docs/vt220-rm/table2-14.html
*/
CHARSETS['H'] =
CHARSETS['7'] = {
'@': 'É',
'[': 'Ä',
'\\': 'Ö',
']': 'Å',
'^': 'Ü',
'`': 'é',
'{': 'ä',
'|': 'ö',
'}': 'å',
'~': 'ü'
};
/**
* Swiss character set
* ESC (=
* Reference: http://vt100.net/docs/vt220-rm/table2-15.html
*/
CHARSETS['='] = {
'#': 'ù',
'@': 'à',
'[': 'é',
'\\': 'ç',
']': 'ê',
'^': 'î',
'_': 'è',
'`': 'ô',
'{': 'ä',
'|': 'ö',
'}': 'ü',
'~': 'û'
};
-213
View File
@@ -1,213 +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) {
// Take .xterm-rows offsetTop into account as well in case it's positioned absolutely within
// the .xterm element.
var xtermRows = 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
*/
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 () {
var terminal;
var compositionHelper;
var compositionView;
var textarea;
var handledText;
describe('CompositionHelper', () => {
let terminal;
let compositionHelper;
let compositionView;
let textarea;
let 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
textarea.value = '아아';
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);
@@ -214,27 +187,27 @@ describe('CompositionHelper', function () {
}, 0);
}, 0);
}, 0);
})
});
it('Should insert multi-character compositions that are converted to other characters with different lengths', function (done) {
// First character 'い'
compositionHelper.compositionstart();
compositionHelper.compositionupdate({ data: 'い' });
textarea.value = 'い';
setTimeout(function() { // wait for any textarea updates
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);
+229
View File
@@ -0,0 +1,229 @@
/**
* @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();
setTimeout(() => {
this.compositionPosition.end = this.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;
const 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.
const 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.
this.isSendingComposition = true;
setTimeout(() => {
// Ensure that the input has not already been sent
if (this.isSendingComposition) {
this.isSendingComposition = false;
let input;
if (this.isComposing) {
// Use the end position to get the string if a new composition has started.
input = this.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 = this.textarea.value.substring(currentCompositionPosition.start);
}
this.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() {
const oldValue = this.textarea.value;
setTimeout(() => {
// Ignore if a composition has started since the timeout
if (!this.isComposing) {
const newValue = this.textarea.value;
const diff = newValue.replace(oldValue, '');
if (diff.length > 0) {
this.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;
}
const 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.
const xtermRows = <HTMLElement>this.terminal.element.querySelector('.xterm-rows');
const 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.
const 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(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 = '';
};
}
+74
View File
@@ -0,0 +1,74 @@
/**
* C0 control codes
* See = https://en.wikipedia.org/wiki/C0_and_C1_control_codes
*/
export namespace C0 {
/** Null (Caret = ^@, C = \0) */
export const NUL = '\x00';
/** Start of Heading (Caret = ^A) */
export const SOH = '\x01';
/** Start of Text (Caret = ^B) */
export const STX = '\x02';
/** End of Text (Caret = ^C) */
export const ETX = '\x03';
/** End of Transmission (Caret = ^D) */
export const EOT = '\x04';
/** Enquiry (Caret = ^E) */
export const ENQ = '\x05';
/** Acknowledge (Caret = ^F) */
export const ACK = '\x06';
/** Bell (Caret = ^G, C = \a) */
export const BEL = '\x07';
/** Backspace (Caret = ^H, C = \b) */
export const BS = '\x08';
/** Character Tabulation, Horizontal Tabulation (Caret = ^I, C = \t) */
export const HT = '\x09';
/** Line Feed (Caret = ^J, C = \n) */
export const LF = '\x0a';
/** Line Tabulation, Vertical Tabulation (Caret = ^K, C = \v) */
export const VT = '\x0b';
/** Form Feed (Caret = ^L, C = \f) */
export const FF = '\x0c';
/** Carriage Return (Caret = ^M, C = \r) */
export const CR = '\x0d';
/** Shift Out (Caret = ^N) */
export const SO = '\x0e';
/** Shift In (Caret = ^O) */
export const SI = '\x0f';
/** Data Link Escape (Caret = ^P) */
export const DLE = '\x10';
/** Device Control One (XON) (Caret = ^Q) */
export const DC1 = '\x11';
/** Device Control Two (Caret = ^R) */
export const DC2 = '\x12';
/** Device Control Three (XOFF) (Caret = ^S) */
export const DC3 = '\x13';
/** Device Control Four (Caret = ^T) */
export const DC4 = '\x14';
/** Negative Acknowledge (Caret = ^U) */
export const NAK = '\x15';
/** Synchronous Idle (Caret = ^V) */
export const SYN = '\x16';
/** End of Transmission Block (Caret = ^W) */
export const ETB = '\x17';
/** Cancel (Caret = ^X) */
export const CAN = '\x18';
/** End of Medium (Caret = ^Y) */
export const EM = '\x19';
/** Substitute (Caret = ^Z) */
export const SUB = '\x1a';
/** Escape (Caret = ^[, C = \e) */
export const ESC = '\x1b';
/** File Separator (Caret = ^\) */
export const FS = '\x1c';
/** Group Separator (Caret = ^]) */
export const GS = '\x1d';
/** Record Separator (Caret = ^^) */
export const RS = '\x1e';
/** Unit Separator (Caret = ^_) */
export const US = '\x1f';
/** Space */
export const SP = '\x20';
/** Delete (Caret = ^?) */
export const DEL = '\x7f';
};
-65
View File
@@ -1,65 +0,0 @@
/**
* xterm.js: xterm, in the browser
* Copyright (c) 2014-2016, SourceLair Private Company (www.sourcelair.com (MIT License)
*/
function EventEmitter() {
this._events = this._events || {};
}
EventEmitter.prototype.addListener = function(type, listener) {
this._events[type] = this._events[type] || [];
this._events[type].push(listener);
};
EventEmitter.prototype.on = EventEmitter.prototype.addListener;
EventEmitter.prototype.removeListener = function(type, listener) {
if (!this._events[type]) return;
var obj = this._events[type]
, i = obj.length;
while (i--) {
if (obj[i] === listener || obj[i].listener === listener) {
obj.splice(i, 1);
return;
}
}
};
EventEmitter.prototype.off = EventEmitter.prototype.removeListener;
EventEmitter.prototype.removeAllListeners = function(type) {
if (this._events[type]) delete this._events[type];
};
EventEmitter.prototype.once = function(type, listener) {
var self = this;
function on() {
var args = Array.prototype.slice.call(arguments);
this.removeListener(type, on);
return listener.apply(this, args);
}
on.listener = listener;
return this.on(type, on);
};
EventEmitter.prototype.emit = function(type) {
if (!this._events[type]) return;
var args = Array.prototype.slice.call(arguments, 1)
, obj = this._events[type]
, l = obj.length
, i = 0;
for (; i < l; i++) {
obj[i].apply(this, args);
}
};
EventEmitter.prototype.listeners = function(type) {
return this._events[type] = this._events[type] || [];
};
export { EventEmitter };
+84
View File
@@ -0,0 +1,84 @@
import { assert } from 'chai';
import { EventEmitter } from './EventEmitter';
describe('EventEmitter', () => {
let eventEmitter: EventEmitter;
beforeEach(() => {
eventEmitter = new EventEmitter();
});
describe('once', () => {
it('should trigger the listener only once', () => {
let count = 0;
const listener = () => count++;
eventEmitter.once('test', listener);
eventEmitter.emit('test');
assert.equal(count, 1);
eventEmitter.emit('test');
assert.equal(count, 1);
});
});
describe('emit', () => {
it('should emit events to listeners', () => {
let count1 = 0;
let count2 = 0;
const listener1 = () => count1++;
const listener2 = () => count2++;
eventEmitter.on('test', listener1);
eventEmitter.on('test', listener2);
eventEmitter.emit('test');
assert.equal(count1, 1);
assert.equal(count2, 1);
eventEmitter.emit('test');
assert.equal(count1, 2);
assert.equal(count2, 2);
});
it('should manage multiple listener types', () => {
let count1 = 0;
let count2 = 0;
const listener1 = () => count1++;
const listener2 = () => count2++;
eventEmitter.on('test', listener1);
eventEmitter.on('foo', listener2);
eventEmitter.emit('test');
assert.equal(count1, 1);
assert.equal(count2, 0);
eventEmitter.emit('foo');
assert.equal(count1, 1);
assert.equal(count2, 1);
});
});
describe('listeners', () => {
it('should return listeners for the type requested', () => {
assert.equal(eventEmitter.listeners('test').length, 0);
const listener = () => {};
eventEmitter.on('test', listener);
assert.deepEqual(eventEmitter.listeners('test'), [listener]);
});
});
describe('off', () => {
it('should remove the specific listener', () => {
const listener1 = () => {};
const listener2 = () => {};
eventEmitter.on('foo', listener1);
eventEmitter.on('foo', listener2);
assert.equal(eventEmitter.listeners('foo').length, 2);
eventEmitter.off('foo', listener1);
assert.deepEqual(eventEmitter.listeners('foo'), [listener2]);
});
});
describe('removeAllListeners', () => {
it('should clear all listeners', () => {
eventEmitter.on('foo', () => {});
assert.equal(eventEmitter.listeners('foo').length, 1);
eventEmitter.removeAllListeners('foo');
assert.equal(eventEmitter.listeners('foo').length, 0);
});
});
});
+72
View File
@@ -0,0 +1,72 @@
/**
* @license MIT
*/
interface ListenerType {
(): void;
listener?: () => void;
};
export class EventEmitter {
private _events: {[type: string]: ListenerType[]};
constructor() {
// Restore the previous events if available, this will happen if the
// constructor is called multiple times on the same object (terminal reset).
this._events = this._events || {};
}
public on(type, listener): void {
this._events[type] = this._events[type] || [];
this._events[type].push(listener);
}
public off(type, listener): void {
if (!this._events[type]) {
return;
}
let obj = this._events[type];
let i = obj.length;
while (i--) {
if (obj[i] === listener || obj[i].listener === listener) {
obj.splice(i, 1);
return;
}
}
}
public removeAllListeners(type): void {
if (this._events[type]) {
delete this._events[type];
}
}
public once(type, listener): any {
function on() {
let args = Array.prototype.slice.call(arguments);
this.off(type, on);
return listener.apply(this, args);
}
(<any>on).listener = listener;
return this.on(type, on);
}
public emit(type): void {
if (!this._events[type]) {
return;
}
let args = Array.prototype.slice.call(arguments, 1);
let obj = this._events[type];
for (let i = 0; i < obj.length; i++) {
obj[i].apply(this, args);
}
}
public listeners(type): ListenerType[] {
return this._events[type] || [];
}
}
+49
View File
@@ -0,0 +1,49 @@
import { assert } from 'chai';
import { InputHandler } from './InputHandler';
describe('InputHandler', () => {
describe('setCursorStyle', () => {
it('should call Terminal.setOption with correct params', () => {
let options = {};
let terminal = {
setOption: (option, value) => options[option] = value
};
let inputHandler = new InputHandler(terminal);
inputHandler.setCursorStyle([0]);
assert.equal(options['cursorStyle'], 'block');
assert.equal(options['cursorBlink'], true);
options = {};
inputHandler.setCursorStyle([1]);
assert.equal(options['cursorStyle'], 'block');
assert.equal(options['cursorBlink'], true);
options = {};
inputHandler.setCursorStyle([2]);
assert.equal(options['cursorStyle'], 'block');
assert.equal(options['cursorBlink'], false);
options = {};
inputHandler.setCursorStyle([3]);
assert.equal(options['cursorStyle'], 'underline');
assert.equal(options['cursorBlink'], true);
options = {};
inputHandler.setCursorStyle([4]);
assert.equal(options['cursorStyle'], 'underline');
assert.equal(options['cursorBlink'], false);
options = {};
inputHandler.setCursorStyle([5]);
assert.equal(options['cursorStyle'], 'bar');
assert.equal(options['cursorBlink'], true);
options = {};
inputHandler.setCursorStyle([6]);
assert.equal(options['cursorStyle'], 'bar');
assert.equal(options['cursorBlink'], false);
});
});
});
+1578
View File
File diff suppressed because it is too large Load Diff
+111
View File
@@ -0,0 +1,111 @@
/**
* @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;
ybase: number;
ydisp: number;
lines: ICircularList<string>;
rows: number;
cols: number;
browser: IBrowser;
writeBuffer: string[];
children: HTMLElement[];
cursorHidden: boolean;
cursorState: number;
x: number;
y: number;
defAttr: number;
/**
* 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);
log(text: string): void;
emit(event: string, data: any);
}
interface ICircularList<T> {
length: number;
maxLength: number;
forEach(callbackfn: (value: T, index: number, array: T[]) => void): void;
get(index: number): T;
set(index: number, value: T): void;
push(value: T): void;
pop(): T;
splice(start: number, deleteCount: number, ...items: T[]): void;
trimStart(count: number): void;
shiftElements(start: number, count: number, offset: number): void;
}
/**
* Handles actions generated by the parser.
*/
export interface IInputHandler {
addChar(char: string, code: number): void;
/** C0 BEL */ bell(): void;
/** C0 LF */ lineFeed(): void;
/** C0 CR */ carriageReturn(): void;
/** C0 BS */ backspace(): void;
/** C0 HT */ tab(): void;
/** C0 SO */ shiftOut(): void;
/** C0 SI */ shiftIn(): void;
/** CSI @ */ insertChars(params?: number[]): void;
/** CSI A */ cursorUp(params?: number[]): void;
/** CSI B */ cursorDown(params?: number[]): void;
/** CSI C */ cursorForward(params?: number[]): void;
/** CSI D */ cursorBackward(params?: number[]): void;
/** CSI E */ cursorNextLine(params?: number[]): void;
/** CSI F */ cursorPrecedingLine(params?: number[]): void;
/** CSI G */ cursorCharAbsolute(params?: number[]): void;
/** CSI H */ cursorPosition(params?: number[]): void;
/** CSI I */ cursorForwardTab(params?: number[]): void;
/** CSI J */ eraseInDisplay(params?: number[]): void;
/** CSI K */ eraseInLine(params?: number[]): void;
/** CSI L */ insertLines(params?: number[]): void;
/** CSI M */ deleteLines(params?: number[]): void;
/** CSI P */ deleteChars(params?: number[]): void;
/** CSI S */ scrollUp(params?: number[]): void;
/** CSI T */ scrollDown(params?: number[]): void;
/** CSI X */ eraseChars(params?: number[]): void;
/** CSI Z */ cursorBackwardTab(params?: number[]): void;
/** CSI ` */ charPosAbsolute(params?: number[]): void;
/** CSI a */ HPositionRelative(params?: number[]): void;
/** CSI b */ repeatPrecedingCharacter(params?: number[]): void;
/** CSI c */ sendDeviceAttributes(params?: number[]): void;
/** CSI d */ linePosAbsolute(params?: number[]): void;
/** CSI e */ VPositionRelative(params?: number[]): void;
/** CSI f */ HVPosition(params?: number[]): void;
/** CSI g */ tabClear(params?: number[]): void;
/** CSI h */ setMode(params?: number[]): void;
/** CSI l */ resetMode(params?: number[]): void;
/** CSI m */ charAttributes(params?: number[]): void;
/** CSI n */ deviceStatus(params?: number[]): void;
/** CSI p */ softReset(params?: number[]): void;
/** CSI q */ setCursorStyle(params?: number[]): void;
/** CSI r */ setScrollRegion(params?: number[]): void;
/** CSI s */ saveCursor(params?: number[]): void;
/** CSI u */ restoreCursor(params?: number[]): void;
}
+636
View File
@@ -0,0 +1,636 @@
/**
* @license MIT
*/
import { C0 } from './EscapeSequences';
import { IInputHandler } from './Interfaces';
import { CHARSETS, DEFAULT_CHARSET } from './Charsets';
const normalStateHandler: {[key: string]: (parser: Parser, handler: IInputHandler) => void} = {};
normalStateHandler[C0.BEL] = (parser, handler) => handler.bell();
normalStateHandler[C0.LF] = (parser, handler) => handler.lineFeed();
normalStateHandler[C0.VT] = normalStateHandler[C0.LF];
normalStateHandler[C0.FF] = normalStateHandler[C0.LF];
normalStateHandler[C0.CR] = (parser, handler) => handler.carriageReturn();
normalStateHandler[C0.BS] = (parser, handler) => handler.backspace();
normalStateHandler[C0.HT] = (parser, handler) => handler.tab();
normalStateHandler[C0.SO] = (parser, handler) => handler.shiftOut();
normalStateHandler[C0.SI] = (parser, handler) => handler.shiftIn();
normalStateHandler[C0.ESC] = (parser, handler) => parser.setState(ParserState.ESCAPED);
// TODO: Remove terminal when parser owns params and currentParam
const escapedStateHandler: {[key: string]: (parser: Parser, terminal: any) => void} = {};
escapedStateHandler['['] = (parser, terminal) => {
// ESC [ Control Sequence Introducer (CSI is 0x9b)
terminal.params = [];
terminal.currentParam = 0;
parser.setState(ParserState.CSI_PARAM);
};
escapedStateHandler[']'] = (parser, terminal) => {
// ESC ] Operating System Command (OSC is 0x9d)
terminal.params = [];
terminal.currentParam = 0;
parser.setState(ParserState.OSC);
};
escapedStateHandler['P'] = (parser, terminal) => {
// ESC P Device Control String (DCS is 0x90)
terminal.params = [];
terminal.currentParam = 0;
parser.setState(ParserState.DCS);
};
escapedStateHandler['_'] = (parser, terminal) => {
// ESC _ Application Program Command ( APC is 0x9f).
parser.setState(ParserState.IGNORE);
};
escapedStateHandler['^'] = (parser, terminal) => {
// ESC ^ Privacy Message ( PM is 0x9e).
parser.setState(ParserState.IGNORE);
};
escapedStateHandler['c'] = (parser, terminal) => {
// ESC c Full Reset (RIS).
terminal.reset();
};
escapedStateHandler['E'] = (parser, terminal) => {
// ESC E Next Line ( NEL is 0x85).
terminal.x = 0;
terminal.index();
parser.setState(ParserState.NORMAL);
};
escapedStateHandler['D'] = (parser, terminal) => {
// ESC D Index ( IND is 0x84).
terminal.index();
parser.setState(ParserState.NORMAL);
};
escapedStateHandler['M'] = (parser, terminal) => {
// ESC M Reverse Index ( RI is 0x8d).
terminal.reverseIndex();
parser.setState(ParserState.NORMAL);
};
escapedStateHandler['%'] = (parser, terminal) => {
// ESC % Select default/utf-8 character set.
// @ = default, G = utf-8
terminal.setgLevel(0);
terminal.setgCharset(0, DEFAULT_CHARSET); // US (default)
parser.setState(ParserState.NORMAL);
parser.skipNextChar();
};
escapedStateHandler[C0.CAN] = (parser) => parser.setState(ParserState.NORMAL);
const csiParamStateHandler: {[key: string]: (parser: Parser) => void} = {};
csiParamStateHandler['?'] = (parser) => parser.setPrefix('?');
csiParamStateHandler['>'] = (parser) => parser.setPrefix('>');
csiParamStateHandler['!'] = (parser) => parser.setPrefix('!');
csiParamStateHandler['0'] = (parser) => parser.setParam(parser.getParam() * 10);
csiParamStateHandler['1'] = (parser) => parser.setParam(parser.getParam() * 10 + 1);
csiParamStateHandler['2'] = (parser) => parser.setParam(parser.getParam() * 10 + 2);
csiParamStateHandler['3'] = (parser) => parser.setParam(parser.getParam() * 10 + 3);
csiParamStateHandler['4'] = (parser) => parser.setParam(parser.getParam() * 10 + 4);
csiParamStateHandler['5'] = (parser) => parser.setParam(parser.getParam() * 10 + 5);
csiParamStateHandler['6'] = (parser) => parser.setParam(parser.getParam() * 10 + 6);
csiParamStateHandler['7'] = (parser) => parser.setParam(parser.getParam() * 10 + 7);
csiParamStateHandler['8'] = (parser) => parser.setParam(parser.getParam() * 10 + 8);
csiParamStateHandler['9'] = (parser) => parser.setParam(parser.getParam() * 10 + 9);
csiParamStateHandler['$'] = (parser) => parser.setPostfix('$');
csiParamStateHandler['"'] = (parser) => parser.setPostfix('"');
csiParamStateHandler[' '] = (parser) => parser.setPostfix(' ');
csiParamStateHandler['\''] = (parser) => parser.setPostfix('\'');
csiParamStateHandler[';'] = (parser) => parser.finalizeParam();
csiParamStateHandler[C0.CAN] = (parser) => parser.setState(ParserState.NORMAL);
const csiStateHandler: {[key: string]: (handler: IInputHandler, params: number[], prefix: string, postfix: string, parser: Parser) => void} = {};
csiStateHandler['@'] = (handler, params, prefix) => handler.insertChars(params);
csiStateHandler['A'] = (handler, params, prefix) => handler.cursorUp(params);
csiStateHandler['B'] = (handler, params, prefix) => handler.cursorDown(params);
csiStateHandler['C'] = (handler, params, prefix) => handler.cursorForward(params);
csiStateHandler['D'] = (handler, params, prefix) => handler.cursorBackward(params);
csiStateHandler['E'] = (handler, params, prefix) => handler.cursorNextLine(params);
csiStateHandler['F'] = (handler, params, prefix) => handler.cursorPrecedingLine(params);
csiStateHandler['G'] = (handler, params, prefix) => handler.cursorCharAbsolute(params);
csiStateHandler['H'] = (handler, params, prefix) => handler.cursorPosition(params);
csiStateHandler['I'] = (handler, params, prefix) => handler.cursorForwardTab(params);
csiStateHandler['J'] = (handler, params, prefix) => handler.eraseInDisplay(params);
csiStateHandler['K'] = (handler, params, prefix) => handler.eraseInLine(params);
csiStateHandler['L'] = (handler, params, prefix) => handler.insertLines(params);
csiStateHandler['M'] = (handler, params, prefix) => handler.deleteLines(params);
csiStateHandler['P'] = (handler, params, prefix) => handler.deleteChars(params);
csiStateHandler['S'] = (handler, params, prefix) => handler.scrollUp(params);
csiStateHandler['T'] = (handler, params, prefix) => {
if (params.length < 2 && !prefix) {
handler.scrollDown(params);
}
};
csiStateHandler['X'] = (handler, params, prefix) => handler.eraseChars(params);
csiStateHandler['Z'] = (handler, params, prefix) => handler.cursorBackwardTab(params);
csiStateHandler['`'] = (handler, params, prefix) => handler.charPosAbsolute(params);
csiStateHandler['a'] = (handler, params, prefix) => handler.HPositionRelative(params);
csiStateHandler['b'] = (handler, params, prefix) => handler.repeatPrecedingCharacter(params);
csiStateHandler['c'] = (handler, params, prefix) => handler.sendDeviceAttributes(params);
csiStateHandler['d'] = (handler, params, prefix) => handler.linePosAbsolute(params);
csiStateHandler['e'] = (handler, params, prefix) => handler.VPositionRelative(params);
csiStateHandler['f'] = (handler, params, prefix) => handler.HVPosition(params);
csiStateHandler['g'] = (handler, params, prefix) => handler.tabClear(params);
csiStateHandler['h'] = (handler, params, prefix) => handler.setMode(params);
csiStateHandler['l'] = (handler, params, prefix) => handler.resetMode(params);
csiStateHandler['m'] = (handler, params, prefix) => handler.charAttributes(params);
csiStateHandler['n'] = (handler, params, prefix) => handler.deviceStatus(params);
csiStateHandler['p'] = (handler, params, prefix) => {
switch (prefix) {
case '!': handler.softReset(params); break;
}
};
csiStateHandler['q'] = (handler, params, prefix, postfix) => {
if (postfix === ' ') {
handler.setCursorStyle(params);
}
};
csiStateHandler['r'] = (handler, params) => handler.setScrollRegion(params);
csiStateHandler['s'] = (handler, params) => handler.saveCursor(params);
csiStateHandler['u'] = (handler, params) => handler.restoreCursor(params);
csiStateHandler[C0.CAN] = (handler, params, prefix, postfix, parser) => parser.setState(ParserState.NORMAL);
enum ParserState {
NORMAL = 0,
ESCAPED = 1,
CSI_PARAM = 2,
CSI = 3,
OSC = 4,
CHARSET = 5,
DCS = 6,
IGNORE = 7
}
/**
* The terminal's parser, all input into the terminal goes through the parser
* which parses and defers the actual input handling the the IInputHandler
* specified in the constructor.
*/
export class Parser {
private _state: ParserState;
private _position: number;
// TODO: Remove terminal when handler can do everything
constructor(
private _inputHandler: IInputHandler,
private _terminal: any
) {
this._state = ParserState.NORMAL;
}
/**
* Parse and handle data.
*
* @param data The data to parse.
*/
public parse(data: string) {
let l = data.length, j, cs, ch, code, low;
this._position = 0;
// apply leftover surrogate high from last write
if (this._terminal.surrogate_high) {
data = this._terminal.surrogate_high + data;
this._terminal.surrogate_high = '';
}
for (; this._position < l; this._position++) {
ch = data[this._position];
// FIXME: higher chars than 0xa0 are not allowed in escape sequences
// --> maybe move to default
code = data.charCodeAt(this._position);
if (0xD800 <= code && code <= 0xDBFF) {
// we got a surrogate high
// get surrogate low (next 2 bytes)
low = data.charCodeAt(this._position + 1);
if (isNaN(low)) {
// end of data stream, save surrogate high
this._terminal.surrogate_high = ch;
continue;
}
code = ((code - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000;
ch += data.charAt(this._position + 1);
}
// surrogate low - already handled above
if (0xDC00 <= code && code <= 0xDFFF)
continue;
switch (this._state) {
case ParserState.NORMAL:
if (ch in normalStateHandler) {
normalStateHandler[ch](this, this._inputHandler);
} else {
this._inputHandler.addChar(ch, code);
}
break;
case ParserState.ESCAPED:
if (ch in escapedStateHandler) {
escapedStateHandler[ch](this, this._terminal);
// Skip switch as it was just handled
break;
}
switch (ch) {
// ESC (,),*,+,-,. Designate G0-G2 Character Set.
case '(': // <-- this seems to get all the attention
case ')':
case '*':
case '+':
case '-':
case '.':
switch (ch) {
case '(':
this._terminal.gcharset = 0;
break;
case ')':
this._terminal.gcharset = 1;
break;
case '*':
this._terminal.gcharset = 2;
break;
case '+':
this._terminal.gcharset = 3;
break;
case '-':
this._terminal.gcharset = 1;
break;
case '.':
this._terminal.gcharset = 2;
break;
}
this._state = ParserState.CHARSET;
break;
// Designate G3 Character Set (VT300).
// A = ISO Latin-1 Supplemental.
// Not implemented.
case '/':
this._terminal.gcharset = 3;
this._state = ParserState.CHARSET;
this._position--;
break;
// ESC N
// Single Shift Select of G2 Character Set
// ( SS2 is 0x8e). This affects next character only.
case 'N':
break;
// ESC O
// Single Shift Select of G3 Character Set
// ( SS3 is 0x8f). This affects next character only.
case 'O':
break;
// ESC n
// Invoke the G2 Character Set as GL (LS2).
case 'n':
this._terminal.setgLevel(2);
break;
// ESC o
// Invoke the G3 Character Set as GL (LS3).
case 'o':
this._terminal.setgLevel(3);
break;
// ESC |
// Invoke the G3 Character Set as GR (LS3R).
case '|':
this._terminal.setgLevel(3);
break;
// ESC }
// Invoke the G2 Character Set as GR (LS2R).
case '}':
this._terminal.setgLevel(2);
break;
// ESC ~
// Invoke the G1 Character Set as GR (LS1R).
case '~':
this._terminal.setgLevel(1);
break;
// ESC 7 Save Cursor (DECSC).
case '7':
this._inputHandler.saveCursor();
this._state = ParserState.NORMAL;
break;
// ESC 8 Restore Cursor (DECRC).
case '8':
this._inputHandler.restoreCursor();
this._state = ParserState.NORMAL;
break;
// ESC # 3 DEC line height/width
case '#':
this._state = ParserState.NORMAL;
this._position++;
break;
// ESC H Tab Set (HTS is 0x88).
case 'H':
this._terminal.tabSet();
this._state = ParserState.NORMAL;
break;
// ESC = Application Keypad (DECKPAM).
case '=':
this._terminal.log('Serial port requested application keypad.');
this._terminal.applicationKeypad = true;
this._terminal.viewport.syncScrollArea();
this._state = ParserState.NORMAL;
break;
// ESC > Normal Keypad (DECKPNM).
case '>':
this._terminal.log('Switching back to normal keypad.');
this._terminal.applicationKeypad = false;
this._terminal.viewport.syncScrollArea();
this._state = ParserState.NORMAL;
break;
default:
this._state = ParserState.NORMAL;
this._terminal.error('Unknown ESC control: %s.', ch);
break;
}
break;
case ParserState.CHARSET:
if (ch in CHARSETS) {
cs = CHARSETS[ch];
if (ch === '/') { // ISOLatin is actually /A
this.skipNextChar();
}
} else {
cs = DEFAULT_CHARSET;
}
this._terminal.setgCharset(this._terminal.gcharset, cs);
this._terminal.gcharset = null;
this._state = ParserState.NORMAL;
break;
case ParserState.OSC:
// OSC Ps ; Pt ST
// OSC Ps ; Pt BEL
// Set Text Parameters.
if (ch === C0.ESC || ch === C0.BEL) {
if (ch === C0.ESC) this._position++;
this._terminal.params.push(this._terminal.currentParam);
switch (this._terminal.params[0]) {
case 0:
case 1:
case 2:
if (this._terminal.params[1]) {
this._terminal.title = this._terminal.params[1];
this._terminal.handleTitle(this._terminal.title);
}
break;
case 3:
// set X property
break;
case 4:
case 5:
// change dynamic colors
break;
case 10:
case 11:
case 12:
case 13:
case 14:
case 15:
case 16:
case 17:
case 18:
case 19:
// change dynamic ui colors
break;
case 46:
// change log file
break;
case 50:
// dynamic font
break;
case 51:
// emacs shell
break;
case 52:
// manipulate selection data
break;
case 104:
case 105:
case 110:
case 111:
case 112:
case 113:
case 114:
case 115:
case 116:
case 117:
case 118:
// reset colors
break;
}
this._terminal.params = [];
this._terminal.currentParam = 0;
this._state = ParserState.NORMAL;
} else {
if (!this._terminal.params.length) {
if (ch >= '0' && ch <= '9') {
this._terminal.currentParam =
this._terminal.currentParam * 10 + ch.charCodeAt(0) - 48;
} else if (ch === ';') {
this._terminal.params.push(this._terminal.currentParam);
this._terminal.currentParam = '';
}
} else {
this._terminal.currentParam += ch;
}
}
break;
case ParserState.CSI_PARAM:
if (ch in csiParamStateHandler) {
csiParamStateHandler[ch](this);
break;
}
this.finalizeParam();
// Fall through the CSI as this character should be the CSI code.
this._state = ParserState.CSI;
case ParserState.CSI:
if (ch in csiStateHandler) {
csiStateHandler[ch](this._inputHandler, this._terminal.params, this._terminal.prefix, this._terminal.postfix, this);
} else {
this._terminal.error('Unknown CSI code: %s.', ch);
}
this._state = ParserState.NORMAL;
this._terminal.prefix = '';
this._terminal.postfix = '';
break;
case ParserState.DCS:
if (ch === C0.ESC || ch === C0.BEL) {
if (ch === C0.ESC) this._position++;
switch (this._terminal.prefix) {
// User-Defined Keys (DECUDK).
case '':
break;
// Request Status String (DECRQSS).
// test: echo -e '\eP$q"p\e\\'
case '$q':
let pt = this._terminal.currentParam
, valid = false;
switch (pt) {
// DECSCA
case '"q':
pt = '0"q';
break;
// DECSCL
case '"p':
pt = '61"p';
break;
// DECSTBM
case 'r':
pt = ''
+ (this._terminal.scrollTop + 1)
+ ';'
+ (this._terminal.scrollBottom + 1)
+ 'r';
break;
// SGR
case 'm':
pt = '0m';
break;
default:
this._terminal.error('Unknown DCS Pt: %s.', pt);
pt = '';
break;
}
this._terminal.send(C0.ESC + 'P' + +valid + '$r' + pt + C0.ESC + '\\');
break;
// Set Termcap/Terminfo Data (xterm, experimental).
case '+p':
break;
// Request Termcap/Terminfo String (xterm, experimental)
// Regular xterm does not even respond to this sequence.
// This can cause a small glitch in vim.
// test: echo -ne '\eP+q6b64\e\\'
case '+q':
// TODO: Don't declare pt twice
/*let*/ pt = this._terminal.currentParam
, valid = false;
this._terminal.send(C0.ESC + 'P' + +valid + '+r' + pt + C0.ESC + '\\');
break;
default:
this._terminal.error('Unknown DCS prefix: %s.', this._terminal.prefix);
break;
}
this._terminal.currentParam = 0;
this._terminal.prefix = '';
this._state = ParserState.NORMAL;
} else if (!this._terminal.currentParam) {
if (!this._terminal.prefix && ch !== '$' && ch !== '+') {
this._terminal.currentParam = ch;
} else if (this._terminal.prefix.length === 2) {
this._terminal.currentParam = ch;
} else {
this._terminal.prefix += ch;
}
} else {
this._terminal.currentParam += ch;
}
break;
case ParserState.IGNORE:
// For PM and APC.
if (ch === C0.ESC || ch === C0.BEL) {
if (ch === C0.ESC) this._position++;
this._state = ParserState.NORMAL;
}
break;
}
}
}
/**
* Set the parser's current parsing state.
*
* @param state The new state.
*/
public setState(state: ParserState): void {
this._state = state;
}
/**
* Sets the parsier's current prefix. CSI codes can have prefixes of '?', '>'
* or '!'.
*
* @param prefix The prefix.
*/
public setPrefix(prefix: string): void {
this._terminal.prefix = prefix;
}
/**
* Sets the parsier's current prefix. CSI codes can have postfixes of '$',
* '"', ' ', '\''.
*
* @param postfix The postfix.
*/
public setPostfix(postfix: string): void {
this._terminal.postfix = postfix;
}
/**
* Sets the parser's current parameter.
*
* @param param the parameter.
*/
public setParam(param: number) {
this._terminal.currentParam = param;
}
/**
* Gets the parser's current parameter.
*/
public getParam(): number {
return this._terminal.currentParam;
}
/**
* Finalizes the parser's current parameter, adding it to the list of
* parameters and setting the new current parameter to 0.
*/
public finalizeParam(): void {
this._terminal.params.push(this._terminal.currentParam);
this._terminal.currentParam = 0;
}
/**
* Tell the parser to skip the next character.
*/
public skipNextChar(): void {
this._position++;
}
/**
* Tell the parser to repeat parsing the current character (for example if it
* needs parsing using a different state.
*/
// public repeatChar(): void {
// this._position--;
// }
}
+304
View File
@@ -0,0 +1,304 @@
/**
* @license MIT
*/
import { ITerminal } from './Interfaces';
/**
* The maximum number of refresh frames to skip when the write buffer is non-
* empty. Note that these frames may be intermingled with frames that are
* skipped via requestAnimationFrame's mechanism.
*/
const MAX_REFRESH_FRAME_SKIP = 5;
/**
* Flags used to render terminal text properly.
*/
enum FLAGS {
BOLD = 1,
UNDERLINE = 2,
BLINK = 4,
INVERSE = 8,
INVISIBLE = 16
};
let brokenBold: boolean = null;
export class Renderer {
/** A queue of the rows to be refreshed */
private _refreshRowsQueue: {start: number, end: number}[] = [];
private _refreshFramesSkipped = 0;
private _refreshAnimationFrame = null;
constructor(private _terminal: ITerminal) {
// Figure out whether boldness affects
// the character width of monospace fonts.
if (brokenBold === null) {
brokenBold = checkBoldBroken((<any>this._terminal).document);
}
// TODO: Pull more DOM interactions into Renderer.constructor, element for
// example should be owned by Renderer (and also exposed by Terminal due to
// to established public API).
}
/**
* Queues a refresh between two rows (inclusive), to be done on next animation
* frame.
* @param {number} start The start row.
* @param {number} end The end row.
*/
public queueRefresh(start: number, end: number): void {
this._refreshRowsQueue.push({ start: start, end: end });
if (!this._refreshAnimationFrame) {
this._refreshAnimationFrame = window.requestAnimationFrame(this._refreshLoop.bind(this));
}
}
/**
* Performs the refresh loop callback, calling refresh only if a refresh is
* necessary before queueing up the next one.
*/
private _refreshLoop(): void {
// Skip MAX_REFRESH_FRAME_SKIP frames if the writeBuffer is non-empty as it
// will need to be immediately refreshed anyway. This saves a lot of
// rendering time as the viewport DOM does not need to be refreshed, no
// scroll events, no layouts, etc.
const skipFrame = this._terminal.writeBuffer.length > 0 && this._refreshFramesSkipped++ <= MAX_REFRESH_FRAME_SKIP;
if (skipFrame) {
this._refreshAnimationFrame = window.requestAnimationFrame(this._refreshLoop.bind(this));
return;
}
this._refreshFramesSkipped = 0;
let start;
let end;
if (this._refreshRowsQueue.length > 4) {
// Just do a full refresh when 5+ refreshes are queued
start = 0;
end = this._terminal.rows - 1;
} else {
// Get start and end rows that need refreshing
start = this._refreshRowsQueue[0].start;
end = this._refreshRowsQueue[0].end;
for (let i = 1; i < this._refreshRowsQueue.length; i++) {
if (this._refreshRowsQueue[i].start < start) {
start = this._refreshRowsQueue[i].start;
}
if (this._refreshRowsQueue[i].end > end) {
end = this._refreshRowsQueue[i].end;
}
}
}
this._refreshRowsQueue = [];
this._refreshAnimationFrame = null;
this._refresh(start, end);
}
/**
* Refreshes (re-renders) terminal content within two rows (inclusive)
*
* Rendering Engine:
*
* In the screen buffer, each character is stored as a an array with a character
* and a 32-bit integer:
* - First value: a utf-16 character.
* - Second value:
* - Next 9 bits: background color (0-511).
* - Next 9 bits: foreground color (0-511).
* - Next 14 bits: a mask for misc. flags:
* - 1=bold
* - 2=underline
* - 4=blink
* - 8=inverse
* - 16=invisible
*
* @param {number} start The row to start from (between 0 and terminal's height terminal - 1)
* @param {number} end The row to end at (between fromRow and terminal's height terminal - 1)
*/
private _refresh(start: number, end: number): void {
let x, y, i, line, out, ch, ch_width, width, data, attr, bg, fg, flags, row, parent, focused = document.activeElement;
// If this is a big refresh, remove the terminal rows from the DOM for faster calculations
if (end - start >= this._terminal.rows / 2) {
parent = this._terminal.element.parentNode;
if (parent) {
this._terminal.element.removeChild(this._terminal.rowContainer);
}
}
width = this._terminal.cols;
y = start;
if (end >= this._terminal.rows) {
this._terminal.log('`end` is too large. Most likely a bad CSR.');
end = this._terminal.rows - 1;
}
for (; y <= end; y++) {
row = y + this._terminal.ydisp;
line = this._terminal.lines.get(row);
if (!line || !this._terminal.children[y]) {
// Continue if the line is not available, this means a resize is currently in progress
continue;
}
out = '';
if (this._terminal.y === y - (this._terminal.ybase - this._terminal.ydisp)
&& this._terminal.cursorState
&& !this._terminal.cursorHidden) {
x = this._terminal.x;
} else {
x = -1;
}
attr = this._terminal.defAttr;
i = 0;
for (; i < width; i++) {
if (!line[i]) {
// Continue if the character is not available, this means a resize is currently in progress
continue;
}
data = line[i][0];
ch = line[i][1];
ch_width = line[i][2];
if (!ch_width)
continue;
if (i === x) data = -1;
if (data !== attr) {
if (attr !== this._terminal.defAttr) {
out += '</span>';
}
if (data !== this._terminal.defAttr) {
if (data === -1) {
out += '<span class="reverse-video terminal-cursor">';
} else {
let classNames = [];
bg = data & 0x1ff;
fg = (data >> 9) & 0x1ff;
flags = data >> 18;
if (flags & FLAGS.BOLD) {
if (!brokenBold) {
classNames.push('xterm-bold');
}
// See: XTerm*boldColors
if (fg < 8) fg += 8;
}
if (flags & FLAGS.UNDERLINE) {
classNames.push('xterm-underline');
}
if (flags & FLAGS.BLINK) {
classNames.push('xterm-blink');
}
// If inverse flag is on, then swap the foreground and background variables.
if (flags & FLAGS.INVERSE) {
/* One-line variable swap in JavaScript: http://stackoverflow.com/a/16201730 */
bg = [fg, fg = bg][0];
// Should inverse just be before the
// above boldColors effect instead?
if ((flags & 1) && fg < 8) fg += 8;
}
if (flags & FLAGS.INVISIBLE) {
classNames.push('xterm-hidden');
}
/**
* Weird situation: Invert flag used black foreground and white background results
* in invalid background color, positioned at the 256 index of the 256 terminal
* color map. Pin the colors manually in such a case.
*
* Source: https://github.com/sourcelair/xterm.js/issues/57
*/
if (flags & FLAGS.INVERSE) {
if (bg === 257) {
bg = 15;
}
if (fg === 256) {
fg = 0;
}
}
if (bg < 256) {
classNames.push('xterm-bg-color-' + bg);
}
if (fg < 256) {
classNames.push('xterm-color-' + fg);
}
out += '<span';
if (classNames.length) {
out += ' class="' + classNames.join(' ') + '"';
}
out += '>';
}
}
}
if (ch_width === 2) {
out += '<span class="xterm-wide-char">';
}
switch (ch) {
case '&':
out += '&amp;';
break;
case '<':
out += '&lt;';
break;
case '>':
out += '&gt;';
break;
default:
if (ch <= ' ') {
out += '&nbsp;';
} else {
out += ch;
}
break;
}
if (ch_width === 2) {
out += '</span>';
}
attr = data;
}
if (attr !== this._terminal.defAttr) {
out += '</span>';
}
this._terminal.children[y].innerHTML = out;
}
if (parent) {
this._terminal.element.appendChild(this._terminal.rowContainer);
}
this._terminal.emit('refresh', {element: this._terminal.element, start: start, end: end});
};
}
// if bold is broken, we can't
// use it in the terminal.
function checkBoldBroken(document) {
const body = document.getElementsByTagName('body')[0];
const el = document.createElement('span');
el.innerHTML = 'hello world';
body.appendChild(el);
const w1 = el.scrollWidth;
el.style.fontWeight = 'bold';
const w2 = el.scrollWidth;
body.removeChild(el);
return w1 !== w2;
}
-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 };
+22 -34
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 () {
var terminal;
var viewportElement;
var charMeasureElement;
var viewport;
var scrollAreaElement;
describe('Viewport', () => {
let terminal;
let viewportElement;
let charMeasure;
let viewport;
let 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
@@ -34,47 +34,35 @@ describe('Viewport', function () {
height: 0
}
};
charMeasureElement = {
getBoundingClientRect: function () {
return { width: null, height: CHARACTER_HEIGHT };
}
charMeasure = {
height: CHARACTER_HEIGHT
};
viewport = new Terminal.Viewport(terminal, viewportElement, scrollAreaElement, charMeasureElement);
viewport = new Viewport(terminal, viewportElement, scrollAreaElement, charMeasure);
});
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 () {
return { width: null, height: 1 };
};
charMeasure.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 () {
return { width: null, height: 20 };
};
charMeasure.height = 20;
viewport.refresh();
assert.equal(viewportElement.style.height, 20 + 'px');
});
});
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');
+122
View File
@@ -0,0 +1,122 @@
/**
* @license MIT
*/
import { ITerminal } from './Interfaces';
import { CharMeasure } from './utils/CharMeasure';
/**
* 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 charMeasure: CharMeasure
) {
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(): void {
if (this.charMeasure.height > 0) {
const rowHeightChanged = this.charMeasure.height !== this.currentRowHeight;
if (rowHeightChanged) {
this.currentRowHeight = this.charMeasure.height;
this.viewportElement.style.lineHeight = this.charMeasure.height + 'px';
this.terminal.rowContainer.style.lineHeight = this.charMeasure.height + 'px';
}
const viewportHeightChanged = this.lastRecordedViewportHeight !== this.terminal.rows;
if (rowHeightChanged || viewportHeightChanged) {
this.lastRecordedViewportHeight = this.terminal.rows;
this.viewportElement.style.height = this.charMeasure.height * this.terminal.rows + 'px';
}
this.scrollArea.style.height = (this.charMeasure.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
if (this.charMeasure.height !== this.currentRowHeight) {
this.refresh();
}
}
// Sync scrollTop
const 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) {
const newRow = Math.round(this.viewportElement.scrollTop / this.currentRowHeight);
const 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
let 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();
};
}
+126
View File
@@ -0,0 +1,126 @@
/**
* 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;
});
+81
View File
@@ -0,0 +1,81 @@
/**
* 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;
});
+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;
}
+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;
});
+207
View File
@@ -0,0 +1,207 @@
/**
* 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;
});
+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,8 +42,10 @@ function prepareTextForClipboard(text) {
* Binds copy functionality to the given terminal.
* @param {ClipboardEvent} ev The original copy event to be handled
*/
function copyHandler(ev, term) {
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);
if (term.browser.isMSIE) {
@@ -53,10 +62,12 @@ function copyHandler(ev, term) {
* @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();
var dispatchPaste = function(text) {
let text: string;
let dispatchPaste = function(text) {
term.handler(text);
term.textarea.value = '';
return term.cancel(ev);
@@ -64,12 +75,12 @@ function pasteHandler(ev, term) {
if (term.browser.isMSIE) {
if (window.clipboardData) {
var text = window.clipboardData.getData('Text');
text = window.clipboardData.getData('Text');
dispatchPaste(text);
}
} else {
if (ev.clipboardData) {
var text = ev.clipboardData.getData('text/plain');
text = ev.clipboardData.getData('text/plain');
dispatchPaste(text);
}
}
@@ -85,23 +96,23 @@ 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(),
export function rightClickHandler(ev: MouseEvent, term: ITerminal) {
let s = document.getSelection(),
selectedText = prepareTextForClipboard(s.toString()),
clickIsOnSelection = false;
clickIsOnSelection = false,
x = ev.clientX,
y = ev.clientY;
if (s.rangeCount) {
var r = s.getRangeAt(0),
cr = r.getClientRects(),
x = ev.clientX,
y = ev.clientY,
i, rect;
let r = s.getRangeAt(0),
cr = r.getClientRects();
for (let i = 0; i < cr.length; i++) {
let rect = cr[i];
for (i=0; i<cr.length; i++) {
rect = cr[i];
clickIsOnSelection = (
(x > rect.left) && (x < rect.right) &&
(y > rect.top) && (y < rect.bottom)
@@ -127,7 +138,7 @@ function rightClickHandler(ev, term) {
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.style.zIndex = '1000';
term.textarea.focus();
// Reset the terminal textarea's styling
@@ -141,7 +152,3 @@ function rightClickHandler(ev, term) {
}, 4);
}
}
export {
prepareTextForClipboard, copyHandler, pasteHandler, rightClickHandler
};
@@ -1,5 +1,5 @@
var assert = require('chai').assert;
var Terminal = require('../../dist/xterm');
var Terminal = require('../../xterm');
var linkify = require('../../addons/linkify/linkify');
describe('linkify addon', function () {
@@ -1,11 +1,10 @@
var assert = require('chai').assert;
var Terminal = require('../../src/xterm');
var distTerminal = require('../../dist/xterm');
var Terminal = require('../../xterm');
describe('xterm.js addons', function() {
it('should load addons with Terminal.loadAddon', function () {
Terminal.loadAddon('attach');
// Test that addon was loaded successfully
assert.equal(typeof distTerminal.prototype.attach, 'function');
assert.equal(typeof Terminal.prototype.attach, 'function');
});
});
@@ -1,8 +1,14 @@
var glob = require('glob');
var fs = require('fs');
var pty = require('pty.js');
var os = require('os');
var pty = require('node-pty');
var sleep = require('sleep');
var Terminal = require('../src/xterm');
var Terminal = require('../xterm');
if (os.platform() === 'win32') {
// Skip tests on Windows since pty.open isn't supported
return;
}
var CONSOLE_LOG = console.log;
@@ -56,10 +62,10 @@ function formatError(in_, out_, expected) {
function terminalToString(term) {
var result = '';
var line_s = '';
for (var line=0; line<term.rows; ++line) {
for (var line = term.ybase; line < term.ybase + term.rows; line++) {
line_s = '';
for (var cell=0; cell<term.cols; ++cell) {
line_s += term.lines[line][cell][1];
line_s += term.lines.get(line)[cell][1];
}
// rtrim empty cells as xterm does
line_s = line_s.replace(/\s+$/, '');
@@ -76,30 +82,49 @@ describe('xterm output comparison', function() {
beforeEach(function () {
xterm = new Terminal(COLS, ROWS);
xterm.refresh = function() {};
xterm.viewport = {
syncScrollArea: 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];
for (var a in successful) {
var i = successful[a];
(function(filename){
var skip = [
10, 16, 17, 19, 32, 33, 34, 35, 36, 39,
40, 42, 43, 44, 45, 46, 47, 48, 49, 50,
51, 52, 54, 55, 56, 57, 58, 59, 60, 61,
63, 68
];
for (var i = 0; i < files.length; i++) {
if (skip.indexOf(i) >= 0) {
continue;
}
(function(filename) {
it(filename.split('/').slice(-1)[0], function () {
pty_reset();
var in_file = fs.readFileSync(filename, 'utf8');
var from_pty = pty_write_read(in_file);
// uncomment this to get log from terminal
console.log = function(){};
xterm.write(from_pty);
//console.log = function(){};
// Perform a synchronous .write(data)
xterm.writeBuffer.push(from_pty);
xterm.innerWrite();
var from_emulator = terminalToString(xterm);
console.log = CONSOLE_LOG;
var expected = fs.readFileSync(filename.split('.')[0] + '.text', 'utf8');
if (from_emulator != expected) {
// Some of the tests have whitespace on the right of lines, we trim all the linex
// from xterm.js so ignore this for now at least.
var expectedRightTrimmed = expected.split('\n').map(function (l) {
return l.replace(/\s+$/, '');
}).join('\n');
if (from_emulator != expectedRightTrimmed) {
// uncomment to get noisy output
//throw new Error(formatError(in_file, from_emulator, expected));
throw new Error('mismatch');
throw new Error(formatError(in_file, from_emulator, expected));
// throw new Error('mismatch');
}
});
})(files[i]);

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