Compare commits

...
95 Commits
Author SHA1 Message Date
Paris Kasidiaris 048b10ebf6 Bump version to 3.3.0
Signed-off-by: Paris Kasidiaris <paris@sourcelair.com>
2018-04-11 16:19:48 +00:00
Daniel Imms c882dcc353 Merge pull request #1381 from Tyriar/1379_viewport_throw
Fix exception when reset is called before open
2018-04-11 07:20:48 -07:00
Daniel Imms cd06786d16 Fix exception when reset is called before open
Fixes #1379
2018-04-09 06:13:32 -07:00
Daniel Imms 989f5873f5 Merge pull request #1366 from Tyriar/1363_fix_demo
Prevent falsy write values
2018-04-02 08:23:53 -07:00
Daniel Imms 307744be66 Prevent falsy write values
Fixes #1363
2018-04-02 08:22:22 -07:00
Daniel Imms d4b75e4909 Merge pull request #1346 from bgw/color-parsing
Allow theming with arbitrary CSS color strings
2018-03-30 07:57:19 -07:00
Daniel Imms 4b5de031ea Merge branch 'master' into color-parsing 2018-03-30 07:50:39 -07:00
Benjamin Woodruff b66b0aae18 Block transparent colors without allowTransparency
Fixes xterm/xterm.js#1357.
2018-03-29 22:04:24 -07:00
Paris Kasidiaris 36a1c5b922 Merge pull request #1355 from AlexandrePetit-Pas/patch-5
Handle blob in attach Addon
2018-03-28 11:09:29 +03:00
Paris Kasidiaris 63f711e634 Fix styling and make string messages to work again 2018-03-28 10:57:21 +03:00
Alexandre Petit-Pas 516903e29e Handle blob in attach Addon
I have added blob handler for the attach Addon.
This allows to handle Docker with ws (works on Docker API v1.35)
I have also added a callback to handle asynchronous FileReader.
2018-03-28 07:37:02 +00:00
Paris Kasidiaris 5cf9423699 Merge pull request #1356 from xtermjs/fix-linter
Make linter blissful again
2018-03-28 10:31:29 +03:00
Paris Kasidiaris dbb2320e83 Fix linting errors 2018-03-28 10:22:42 +03:00
Benjamin Woodruff 0cb6138cd0 Allow theming with arbitrary CSS color strings
This mostly worked before, but clearColor made the assumption that the
colors were always in #RRBBGG format. See the discussion here:
https://github.com/xtermjs/xterm.js/pull/1327#issuecomment-374812842

This changes the internal representation of a color so that we hold onto
into an object containing the original css string along with an RGBA
value encoded as a 32-bit uint. clearColor can then use that RGBA
representation.

Additionally, this deduplicates some code between ColorManager and
Terminal. Terminal was generating the 256 ansi colors the same way
ColorManager was, so this just makes Terminal use the same constant.
2018-03-26 09:47:25 -07:00
Daniel Imms bf21097430 Merge pull request #1326 from LucianBuzzo/1322-options-clone
Do not mutate options object used in constructor
2018-03-24 14:07:59 -07:00
Lucian Buzzo 55d6daf8ee Do not mutate options object used in constructor
Connects to #1322

change-type: patch
2018-03-24 16:25:02 +00:00
Daniel Imms 6e1e63e666 Merge pull request #1350 from FGasper/issue_1330_rtl_layout
Force LTR for the terminal element
2018-03-23 16:19:02 -07:00
Daniel Imms b27d5bb08d Use single quote to make tslint happy 2018-03-23 16:18:41 -07:00
Felipe Gasper e63831febc Force LTR for the terminal element
Issue #1330: This prevents RTL layouts from mishandlin the
intended layout of the terminal container and children.
2018-03-23 19:16:02 -04:00
Daniel Imms 4ab79c908a Merge pull request #1329 from Tyriar/1319_bang_operator
Use bang operator for array methods
2018-03-21 07:34:18 -07:00
Daniel Imms b85083c3e8 Merge pull request #1303 from Tyriar/24_multi_line_links
Get multi-line links working
2018-03-21 07:33:43 -07:00
Daniel Imms 97f592ce8b Merge remote-tracking branch 'origin/master' into 24_multi_line_links 2018-03-21 07:32:32 -07:00
Daniel Imms 70da707569 Merge pull request #1338 from Tyriar/1325_marker_api
Implement Markers API
2018-03-20 19:14:10 -07:00
Daniel Imms 63f24be2b9 Merge branch 'master' into 1325_marker_api 2018-03-20 19:02:52 -07:00
Daniel Imms 0d6be9e03e Merge pull request #1344 from Tyriar/trailing_comma
Disallow trailing commas
2018-03-20 19:01:04 -07:00
Daniel Imms 3cbb21a966 Merge pull request #1340 from Tyriar/unused_func
Remove unused function
2018-03-20 19:00:35 -07:00
Daniel Imms 1fd7348938 Merge branch 'master' into unused_func 2018-03-20 19:00:24 -07:00
Daniel Imms ae32893732 Disallow trailing commas 2018-03-20 09:10:48 -07:00
Daniel Imms 4f82cf26f0 Merge pull request #1336 from mareklibra/removeStyle
Replace inline-styles by external CSS
2018-03-20 09:07:06 -07:00
Marek Libra b67b65cd1e Replace inline-styles by external CSS
Selected inline-styles were externalized to xterm.css.

The `Terminal._charSizeStyleElement` is removed since it
was not used anymore.

Fixes: https://github.com/xtermjs/xterm.js/issues/1335
2018-03-20 13:45:08 +01:00
Daniel Imms 0eeec13653 Clean up 2018-03-19 11:01:26 -07:00
Daniel Imms 5d1f744282 Remove unused function 2018-03-19 08:20:28 -07:00
Daniel Imms 387d5b056d Add Terminal.scrollToLine tests 2018-03-17 11:46:37 -07:00
Daniel Imms cbf7419601 Flag markers and addMarker as experimental 2018-03-16 17:24:04 -07:00
Daniel Imms 5c1a4a7d01 Add tests for Buffer.addMarker 2018-03-16 17:23:17 -07:00
Daniel Imms cbc6d7ce32 Add selectLines test 2018-03-16 15:56:48 -07:00
Daniel Imms b80832ac4c Initial marker API implementation
Part of #1325
2018-03-16 12:53:55 -07:00
Daniel Imms 3716aad202 Backtrack row to be linkified if the first row is wrapped 2018-03-16 07:21:14 -07:00
Daniel Imms efbd542964 Merge branch 'master' into 24_multi_line_links 2018-03-16 07:14:20 -07:00
Daniel Imms 888070830d Merge pull request #1332 from xtermjs/Tyriar-patch-1
Add note about third party dependencies
2018-03-13 12:44:51 -07:00
Daniel Imms 9905e2aa1d Add note about third party dependencies
Fixes #1328
2018-03-13 11:19:51 -07:00
Daniel Imms a59af6606b Use bang operator for array methods
Part of #1319
2018-03-12 14:14:37 -07:00
Daniel Imms d1213e5656 Merge pull request #1320 from Tyriar/1319/AccessibilityManager
Fix strictNullChecks errors in AccessibilityManager
2018-03-12 08:51:32 -07:00
Daniel Imms 0e14798d69 Merge pull request #1321 from felixse/patch-1
Add Fluent Terminal to the list of real-world uses
2018-03-11 13:44:16 -07:00
Daniel Imms c8ab687fa0 Add tests 2018-03-11 12:34:27 -07:00
Daniel Imms e5f5c2baf2 Fix links that start on wrapped lines 2018-03-10 16:08:50 -08:00
Felix 2f861602c8 Add Fluent Terminal to the list of real-world uses 2018-03-10 23:27:59 +01:00
Daniel Imms 4f0cc01485 Fix strictNullChecks errors in AccessibilityManager
Part of #1319
2018-03-09 11:27:10 -08:00
Daniel Imms dd41ab38bb Fix imports after noUnusedLocals was merged in 2018-03-09 10:58:57 -08:00
Daniel Imms 255cb5de3a Merge branch 'master' into 24_multi_line_links 2018-03-09 10:57:04 -08:00
Daniel Imms b44a11c42e Remove TODOs, polish 2018-03-09 10:56:54 -08:00
Daniel Imms 90b2f0e6a1 Merge pull request #1316 from Tyriar/noUnusedLocals
Add noUnusedLocals tsconfig flag, fix issues
2018-03-09 08:43:40 -08:00
Daniel Imms 811aa9c413 Merge remote-tracking branch 'origin/master' into noUnusedLocals 2018-03-09 08:43:21 -08:00
Daniel Imms 842c7132ec Merge pull request #1318 from Tyriar/remove_unused_css
Remove unused CSS rule
2018-03-09 08:41:41 -08:00
Daniel Imms efb67edc36 Remove unused CSS rule
With #1316, this will resolve Microsoft/vscode#45145
2018-03-09 07:11:53 -08:00
Daniel Imms ed7741e006 Merge pull request #1317 from bgw/rm-char-atlas-request
Merge ICharAtlasRequest with ICharAtlasConfig
2018-03-09 06:11:05 -08:00
Benjamin Woodruff cfe936a0b4 Merge ICharAtlasRequest with ICharAtlasConfig
ICharAtlasRequest and ICharAtlasConfig need almost exactly the same set
of information, so it's simpler if we just merge the two types.

As an added bonus, this also adds devicePixelRatio to the config, which
helps guarantee that we won't ever accidentally end up with an atlas
using a different pixel ratio than we need.
2018-03-08 23:23:33 -08:00
Daniel Imms 4ddf91dbbe Fix tests 2018-03-08 11:20:42 -08:00
Daniel Imms 59be054ca1 Fix exception for links on bottom line 2018-03-08 11:12:59 -08:00
Daniel Imms b794dc017d Fix test 2018-03-08 11:08:51 -08:00
Daniel Imms 4443ed69c7 Add noUnusedLocals tsconfig flag, fix issues
Two parts were commented out:
- _writeStopped: This is because _xoffSentToCatchUp references this variable
  and the feature is sort of half implemented/disabled.
- _clearChar: This is because some comments still reference this, it's part
  of the work needed to do better line redrawing.
2018-03-08 10:56:10 -08:00
Daniel Imms ab9b84898e Merge remote-tracking branch 'origin/master' into 24_multi_line_links 2018-03-08 10:23:05 -08:00
Daniel Imms a41e183b88 Merge pull request #1310 from Tyriar/426_alt_scroll
Send arrow events on mouse wheel in alt buffer
2018-03-08 09:53:36 -08:00
Daniel Imms 949239dbdc Merge remote-tracking branch 'origin/master' into 426_alt_scroll 2018-03-08 09:28:02 -08:00
Daniel Imms 421ccc5695 Join lines that wrap over more than 2 lines 2018-03-08 09:25:08 -08:00
Daniel Imms 5d68ed6e5e Merge pull request #1315 from Tyriar/underscore_private
Rename all remaining private variables to use underscores
2018-03-08 09:01:33 -08:00
Daniel Imms 021f8c1ba0 Draw and clear link cells 2018-03-08 07:00:37 -08:00
Daniel Imms f456b14b3f Get multi-line link hover working 2018-03-08 06:42:40 -08:00
Daniel Imms 7a1ef19146 Merge remote-tracking branch 'origin/master' into 24_multi_line_links 2018-03-08 06:23:29 -08:00
Daniel Imms b9cbdf6a0d Fix tests 2018-03-08 06:19:17 -08:00
Daniel Imms 7639131c12 Ensure underscore is used for private vars
Variables changed based on regex: "private [^(_|get)]"
2018-03-08 06:09:46 -08:00
Daniel Imms 289b4eeb23 Merge branch 'master' into 426_alt_scroll 2018-03-08 05:37:16 -08:00
Daniel Imms 404a285eaf Merge pull request #1307 from bgw/split-char-atlas-files
Split CharAtlas types and utility functions off
2018-03-08 05:16:44 -08:00
Daniel Imms b20fe80f9b Merge pull request #1308 from bgw/rm-did-char-size-change
Remove didCharSizeChange logic from renderer
2018-03-08 05:10:53 -08:00
Daniel Imms ff9814a40d Merge pull request #1293 from nikonso/allow-named-theme-colors-1195
Validate colors
2018-03-08 05:02:50 -08:00
Daniel Imms 855964fbe1 Merge pull request #1296 from olsonpm/fix-paste-newline
Paste newlines as \r
2018-03-08 05:02:31 -08:00
Benjamin Woodruff 735dfb4710 Add copyright header to shared/atlas/Types.ts 2018-03-07 23:19:38 -08:00
Benjamin Woodruff ab69bfbb32 Move rest of atlas implementations to atlas dirs
Moves CharAtlas into src/renderer/atlas/ and CharAtlasGenerator into
src/shared/atlas/.
2018-03-07 22:23:19 -08:00
Benjamin Woodruff 70771430b5 Clean up char atlas constant imports/exports
- Ensures that everything is only defined in one place, and everything
  imports from that place.
- Moves CHAR_ATLAS_CELL_SPACING into a subdirectory of shared/ so that
  CharAtlasGenerator can pull from it.

This addresses the comments on
https://github.com/xtermjs/xterm.js/pull/1307/files/454fd4bfb1ab5c9ece
2018-03-07 22:13:41 -08:00
Bruno Ribeiro 41f6ee4f6d Merge branch 'master' into allow-named-theme-colors-1195 2018-03-06 20:15:32 +00:00
Thomas Zilz ff6ef1b63a Initialise _wheelPartialScroll to 0 to prevent NaN 2018-03-06 11:39:14 +01:00
Bruno Ribeiro 34034b849f Merge branch 'master' into allow-named-theme-colors-1195 2018-03-05 23:22:12 +00:00
Daniel Imms e137530581 Scroll by exact line amount in alt buffer, unify scroll logic
Fixes #1304
2018-03-05 14:22:20 -08:00
Daniel Imms be7788dda6 Merge remote-tracking branch 'origin/master' into 426_alt_scroll 2018-03-05 11:48:40 -08:00
Benjamin Woodruff 18fb134910 Remove didCharSizeChange logic from renderer
The only thing `didCharSizeChange` was used for was to figure out if we
need to refresh the character atlas or not. However, `acquireCharAtlas`
already inspects the atlas owned by this terminal, and if it matches, it
avoids generating a new atlas.

So if `didCharSizeChange` is true and it's not needed, it quickly bails
out as a no-op. But if `didCharSizeChange` is false, and it was needed
(possible given the complexity of testing all of these edge cases), it
could introduce a bug.

I think it makes sense to just get rid of `didCharSizeChange`, and just
always call `acquireCharAtlas`.
2018-03-04 20:55:42 -08:00
Benjamin Woodruff 454fd4bfb1 Split CharAtlas types and utility functions off
I'm starting to pull some changes off of my large WIP branch/commit that
adds an alternative dynamic character atlas. We're going to need to
support multiple CharAtlas implementations, and this should make that
easier.
2018-03-04 18:50:46 -08:00
Daniel Imms 2e3f9e7094 Multi-line links prototype
Part of #24
2018-02-27 08:37:16 -08:00
Philip Olson bc35f28f51 Paste newlines as \r
Fix issue where non-windows OS terminals don't paste newlines correctly
into misbehaving programs.  As noted in JetBrains/jediterm#136, the
most practical solution is for terminals to handle this in their paste
logic.  In a perfect world, all terminal programs would implement
bracketed paste mode properly, but that's a lot to ask.

- translate newlines to \r which is what terminal programs expect
2018-02-22 23:52:45 -06:00
Bruno Ribeiro 97feb64e94 Merge branch 'master' into allow-named-theme-colors-1195 2018-02-21 23:51:36 +00:00
Bruno Ribeito 1114977a21 Fix tests 2018-02-21 23:35:37 +00:00
Bruno Ribeiro a8c5b159a9 Merge branch 'master' into allow-named-theme-colors-1195 2018-02-21 23:17:00 +00:00
Bruno Ribeito a9d6686e14 Regex is not needed for color validation 2018-02-20 20:38:12 +00:00
Bruno Ribeito 99aa142cef Fix test -> wrong Regex flag 2018-02-19 23:55:25 +00:00
Bruno Ribeito f8f1355155 Validate colors 2018-02-19 22:54:09 +00:00
Daniel Imms 74a6dfb197 Send arrow events on mouse wheel in alt buffer
Fixes #426
2018-01-29 11:28:48 -08:00
57 changed files with 1635 additions and 1033 deletions
+5
View File
@@ -5,6 +5,7 @@ Aleksandr Andrienko <aandrienko@codenvy.com>
Aleksandr Andriienko <oandriie@redhat.com>
Alessandro Nadalin <alessandro.nadalin@gmail.com>
Alexander Olsson <noseglid@gmail.com>
Alexandre Petit-Pas <petitpasalexandre@gmail.com>
Alexey Kontsevoy <alexey@gravitational.com>
Andres Mejia <amejia004@gmail.com>
Anish Athalye <me@anishathalye.com>
@@ -18,6 +19,7 @@ Austin Robertson <austinrobertson@gmail.com>
ayapi <colors.aya@gmail.com>
Ben Hall <ben@benhall.me.uk>
Benjamin Fischer <benjamin.fischer@rwth-aachen.de>
Benjamin Woodruff <github@benjam.info>
Bill Church <billchurch@users.noreply.github.com>
Bob Reid <bobreid@Bobs-MacBook-Pro.local>
bottleofwater <nison.mael+bottleofwater@gmail.com>
@@ -44,6 +46,7 @@ Edgar Andrés Margffoy Tuay <andfoy@gmail.com>
Elliot Saba <staticfloat@gmail.com>
Exile <exile@chamber.ee>
Felipe Gasper <felipe@felipegasper.com>
Felix <30559812+felixse@users.noreply.github.com>
ficristo <ficristo.work@gmail.com>
Gary Ritchie <gary@rstudio.com>
hiro-su <h.sugipon@gmail.com>
@@ -70,6 +73,7 @@ Lucian Buzzo <lucian.buzzo@gmail.com>
Lukas Drgon <lukas.drgon@gmail.com>
Maël Nison <nison.mael@gmail.com>
Marc Dumais <marc.dumais@ericsson.com>
Marek Libra <mlibra@redhat.com>
Markus F.X.J. Oberhumer <markus@oberhumer.com>
Martin Chloride <i@martincl2.me>
Martin Koppehel <martin.koppehel@st.ovgu.de>
@@ -85,6 +89,7 @@ Oleksandr Andriienko <oandriie@redhat.com>
Paris Kasidiaris <pariskasidiaris@gmail.com>
Paris Kasidiaris <paris@sourcelair.com>
Peter Baumgarten <me@peterbaumgarten.com>
Philip Olson <philip.olson@protonmail.ch>
Rick Baker <rick@ricktbaker.com>
runarberg <runar@greenqloud.com>
Saad Malik <simfox3@gmail.com>
+4
View File
@@ -50,3 +50,7 @@ By contributing code to xterm.js you
holder has explicitly granted the right to use it like this,
through a compatible open source license or through a direct
agreement with you.)
### Third party dependencies
We prefer to not include any non-dev third party dependencies in order to keep our code minimal, performant and secure. If you plan on adding a dependency on a third party library it's a good idea to discuss the need in an issue with the maintainers first.
+1
View File
@@ -127,6 +127,7 @@ computational environment for Jupyter, supporting interactive data science and s
- [**abstruse**](https://github.com/bleenco/abstruse): Abstruse CI is a continuous integration platform based on Node.JS and Docker.
- [**Microsoft SQL Operations Studio**](https://github.com/Microsoft/sqlopsstudio): A data management tool that enables working with SQL Server, Azure SQL DB and SQL DW from Windows, macOS and Linux
- [**FreeMAN**](https://github.com/matthew-matvei/freeman): A free, cross-platform file manager for power users
- [**Fluent Terminal**](https://github.com/felixse/FluentTerminal): A terminal emulator based on UWP and web technologies.
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.
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "xterm",
"version": "3.1.0-master",
"version": "3.2.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "xterm",
"description": "Full xterm terminal, in your browser",
"version": "3.2.0",
"version": "3.3.0",
"ignore": [
"demo",
"test",
+9 -14
View File
@@ -11,7 +11,6 @@ import { addDisposableListener } from './utils/Dom';
import { IDisposable } from 'xterm';
const MAX_ROWS_TO_READ = 20;
const ACTIVE_ITEM_ID_PREFIX = 'xterm-active-item-';
enum BoundaryPosition {
Top,
@@ -21,7 +20,7 @@ enum BoundaryPosition {
export class AccessibilityManager implements IDisposable {
private _accessibilityTreeRoot: HTMLElement;
private _rowContainer: HTMLElement;
private _rowElements: HTMLElement[] = [];
private _rowElements: HTMLElement[];
private _liveRegion: HTMLElement;
private _liveRegionLineCount: number = 0;
@@ -49,6 +48,7 @@ export class AccessibilityManager implements IDisposable {
this._rowContainer = document.createElement('div');
this._rowContainer.classList.add('xterm-accessibility-tree');
this._rowElements = [];
for (let i = 0; i < this._terminal.rows; i++) {
this._rowElements[i] = this._createAccessibilityTreeNode();
this._rowContainer.appendChild(this._rowElements[i]);
@@ -93,14 +93,10 @@ export class AccessibilityManager implements IDisposable {
}
public dispose(): void {
this._terminal.element.removeChild(this._accessibilityTreeRoot);
this._disposables.forEach(d => d.dispose());
this._disposables = null;
this._accessibilityTreeRoot = null;
this._rowContainer = null;
this._liveRegion = null;
this._rowContainer = null;
this._rowElements = null;
this._disposables.length = 0;
this._terminal.element.removeChild(this._accessibilityTreeRoot);
this._rowElements.length = 0;
}
private _onBoundaryFocus(e: FocusEvent, position: BoundaryPosition): void {
@@ -125,10 +121,10 @@ export class AccessibilityManager implements IDisposable {
let bottomBoundaryElement: HTMLElement;
if (position === BoundaryPosition.Top) {
topBoundaryElement = boundaryElement;
bottomBoundaryElement = this._rowElements.pop();
bottomBoundaryElement = this._rowElements.pop()!;
this._rowContainer.removeChild(bottomBoundaryElement);
} else {
topBoundaryElement = this._rowElements.shift();
topBoundaryElement = this._rowElements.shift()!;
bottomBoundaryElement = boundaryElement;
this._rowContainer.removeChild(topBoundaryElement);
}
@@ -174,7 +170,7 @@ export class AccessibilityManager implements IDisposable {
}
// Shrink rows as required
while (this._rowElements.length > rows) {
this._rowContainer.removeChild(this._rowElements.pop());
this._rowContainer.removeChild(this._rowElements.pop()!);
}
// Add bottom boundary listener
@@ -218,7 +214,7 @@ export class AccessibilityManager implements IDisposable {
// Only detach/attach on mac as otherwise messages can go unaccounced
if (isMac) {
if (this._liveRegion.textContent.length > 0 && !this._liveRegion.parentNode) {
if (this._liveRegion.textContent && this._liveRegion.textContent.length > 0 && !this._liveRegion.parentNode) {
setTimeout(() => {
this._accessibilityTreeRoot.appendChild(this._liveRegion);
}, 0);
@@ -265,7 +261,6 @@ export class AccessibilityManager implements IDisposable {
if (!this._terminal.renderer.dimensions.actualCellHeight) {
return;
}
const buffer: IBuffer = this._terminal.buffer;
for (let i = 0; i < this._terminal.rows; i++) {
this._refreshRowDimensions(this._rowElements[i]);
}
+24
View File
@@ -188,4 +188,28 @@ describe('Buffer', () => {
assert.equal(buffer.lines.maxLength, INIT_ROWS / 2);
});
});
describe('addMarker', () => {
it('should adjust a marker line when the buffer is trimmed', () => {
terminal.options.scrollback = 0;
buffer = new Buffer(terminal, true);
buffer.fillViewportRows();
const marker = buffer.addMarker(buffer.lines.length - 1);
assert.equal(marker.line, buffer.lines.length - 1);
buffer.lines.emit('trim', 1);
assert.equal(marker.line, buffer.lines.length - 2);
});
it('should dispose of a marker if it is trimmed off the buffer', () => {
terminal.options.scrollback = 0;
buffer = new Buffer(terminal, true);
buffer.fillViewportRows();
assert.equal(buffer.markers.length, 0);
const marker = buffer.addMarker(0);
assert.equal(marker.isDisposed, false);
assert.equal(buffer.markers.length, 1);
buffer.lines.emit('trim', 1);
assert.equal(marker.isDisposed, true);
assert.equal(buffer.markers.length, 0);
});
});
});
+48
View File
@@ -5,6 +5,8 @@
import { CircularList } from './utils/CircularList';
import { LineData, CharData, ITerminal, IBuffer } from './Types';
import { EventEmitter } from './EventEmitter';
import { IDisposable, IMarker } from 'xterm';
export const CHAR_DATA_ATTR_INDEX = 0;
export const CHAR_DATA_CHAR_INDEX = 1;
@@ -31,6 +33,7 @@ export class Buffer implements IBuffer {
public tabs: any;
public savedY: number;
public savedX: number;
public markers: Marker[] = [];
/**
* Create a new Buffer.
@@ -303,4 +306,49 @@ export class Buffer implements IBuffer {
while (!this.tabs[++x] && x < this._terminal.cols);
return x >= this._terminal.cols ? this._terminal.cols - 1 : x < 0 ? 0 : x;
}
public addMarker(y: number): Marker {
const marker = new Marker(y);
this.markers.push(marker);
marker.disposables.push(this._lines.addDisposableListener('trim', amount => {
marker.line -= amount;
// The marker should be disposed when the line is trimmed from the buffer
if (marker.line < 0) {
marker.dispose();
}
}));
marker.on('dispose', () => this._removeMarker(marker));
return marker;
}
private _removeMarker(marker: Marker): void {
// TODO: This could probably be optimized by relying on sort order and trimming the array using .length
this.markers.splice(this.markers.indexOf(marker), 1);
}
}
export class Marker extends EventEmitter implements IMarker {
private static NEXT_ID = 1;
private _id: number = Marker.NEXT_ID++;
public isDisposed: boolean = false;
public disposables: IDisposable[] = [];
public get id(): number { return this._id; }
constructor(
public line: number
) {
super();
}
public dispose(): void {
if (this.isDisposed) {
return;
}
this.isDisposed = true;
this.disposables.forEach(d => d.dispose());
this.disposables.length = 0;
this.emit('dispose');
}
}
+1 -1
View File
@@ -49,7 +49,7 @@ export const wcwidth = (function(opts: {nul: number, control: number}): (ucs: nu
[0x206A, 0x206F], [0x20D0, 0x20EF], [0x302A, 0x302F],
[0x3099, 0x309A], [0xA806, 0xA806], [0xA80B, 0xA80B],
[0xA825, 0xA826], [0xFB1E, 0xFB1E], [0xFE00, 0xFE0F],
[0xFE20, 0xFE23], [0xFEFF, 0xFEFF], [0xFFF9, 0xFFFB],
[0xFE20, 0xFE23], [0xFEFF, 0xFEFF], [0xFFF9, 0xFFFB]
];
const COMBINING_HIGH = [
[0x10A01, 0x10A03], [0x10A05, 0x10A06], [0x10A0C, 0x10A0F],
+1 -1
View File
@@ -17,7 +17,7 @@ describe('CompositionHelper', () => {
compositionView = {
classList: {
add: () => {},
remove: () => {},
remove: () => {}
},
getBoundingClientRect: () => {
return { width: 0 };
+61 -61
View File
@@ -20,43 +20,43 @@ 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;
private _isComposing: boolean;
/**
* The position within the input textarea's value of the current composition.
*/
private compositionPosition: IPosition;
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;
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.
* @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
private _textarea: HTMLTextAreaElement,
private _compositionView: HTMLElement,
private _terminal: ITerminal
) {
this.isComposing = false;
this.isSendingComposition = false;
this.compositionPosition = { start: null, end: null };
this._isComposing = false;
this._isSendingComposition = false;
this._compositionPosition = { start: null, end: null };
}
/**
* Handles the compositionstart event, activating the composition view.
*/
public compositionstart(): void {
this.isComposing = true;
this.compositionPosition.start = this.textarea.value.length;
this.compositionView.textContent = '';
this.compositionView.classList.add('active');
this._isComposing = true;
this._compositionPosition.start = this._textarea.value.length;
this._compositionView.textContent = '';
this._compositionView.classList.add('active');
}
/**
@@ -64,10 +64,10 @@ export class CompositionHelper {
* @param {CompositionEvent} ev The event.
*/
public compositionupdate(ev: CompositionEvent): void {
this.compositionView.textContent = ev.data;
this._compositionView.textContent = ev.data;
this.updateCompositionElements();
setTimeout(() => {
this.compositionPosition.end = this.textarea.value.length;
this._compositionPosition.end = this._textarea.value.length;
}, 0);
}
@@ -76,7 +76,7 @@ export class CompositionHelper {
* the handler.
*/
public compositionend(): void {
this.finalizeComposition(true);
this._finalizeComposition(true);
}
/**
@@ -85,7 +85,7 @@ export class CompositionHelper {
* @return Whether the Terminal should continue processing the keydown event.
*/
public keydown(ev: KeyboardEvent): boolean {
if (this.isComposing || this.isSendingComposition) {
if (this._isComposing || this._isSendingComposition) {
if (ev.keyCode === 229) {
// Continue composing if the keyCode is the "composition character"
return false;
@@ -95,14 +95,14 @@ export class CompositionHelper {
} 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);
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();
this._handleAnyTextareaChanges();
return false;
}
@@ -117,22 +117,22 @@ export class CompositionHelper {
* compositionend event is triggered, such as enter, so that the composition is send before
* the command is executed.
*/
private finalizeComposition(waitForPropogation: boolean): void {
this.compositionView.classList.remove('active');
this.isComposing = false;
this.clearTextareaPosition();
private _finalizeComposition(waitForPropogation: boolean): void {
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);
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,
start: this._compositionPosition.start,
end: this._compositionPosition.end
};
// Since composition* events happen before the changes take place in the textarea on most
@@ -143,22 +143,22 @@ export class CompositionHelper {
// - 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;
this._isSendingComposition = true;
setTimeout(() => {
// Ensure that the input has not already been sent
if (this.isSendingComposition) {
this.isSendingComposition = false;
if (this._isSendingComposition) {
this._isSendingComposition = false;
let input;
if (this.isComposing) {
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);
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);
input = this._textarea.value.substring(currentCompositionPosition.start);
}
this.terminal.handler(input);
this._terminal.handler(input);
}
}, 0);
}
@@ -170,15 +170,15 @@ export class CompositionHelper {
* character" (229) is triggered, in order to allow non-composition text to be entered when an
* IME is active.
*/
private handleAnyTextareaChanges(): void {
const oldValue = this.textarea.value;
private _handleAnyTextareaChanges(): void {
const oldValue = this._textarea.value;
setTimeout(() => {
// Ignore if a composition has started since the timeout
if (!this.isComposing) {
const newValue = this.textarea.value;
if (!this._isComposing) {
const newValue = this._textarea.value;
const diff = newValue.replace(oldValue, '');
if (diff.length > 0) {
this.terminal.handler(diff);
this._terminal.handler(diff);
}
}
}, 0);
@@ -191,27 +191,27 @@ export class CompositionHelper {
* necessary as the IME events across browsers are not consistently triggered.
*/
public updateCompositionElements(dontRecurse?: boolean): void {
if (!this.isComposing) {
if (!this._isComposing) {
return;
}
if (this.terminal.buffer.isCursorInViewport) {
const cellHeight = Math.ceil(this.terminal.charMeasure.height * this.terminal.options.lineHeight);
const cursorTop = this.terminal.buffer.y * cellHeight;
const cursorLeft = this.terminal.buffer.x * this.terminal.charMeasure.width;
if (this._terminal.buffer.isCursorInViewport) {
const cellHeight = Math.ceil(this._terminal.charMeasure.height * this._terminal.options.lineHeight);
const cursorTop = this._terminal.buffer.y * cellHeight;
const cursorLeft = this._terminal.buffer.x * this._terminal.charMeasure.width;
this.compositionView.style.left = cursorLeft + 'px';
this.compositionView.style.top = cursorTop + 'px';
this.compositionView.style.height = cellHeight + 'px';
this.compositionView.style.lineHeight = cellHeight + 'px';
this._compositionView.style.left = cursorLeft + 'px';
this._compositionView.style.top = cursorTop + 'px';
this._compositionView.style.height = cellHeight + 'px';
this._compositionView.style.lineHeight = cellHeight + '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 = cursorLeft + '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';
const compositionViewBounds = this._compositionView.getBoundingClientRect();
this._textarea.style.left = cursorLeft + '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) {
@@ -223,8 +223,8 @@ export class CompositionHelper {
* Clears the textarea's position so that the cursor does not blink on IE.
* @private
*/
private clearTextareaPosition(): void {
this.textarea.style.left = '';
this.textarea.style.top = '';
private _clearTextareaPosition(): void {
this._textarea.style.left = '';
this._textarea.style.top = '';
}
}
+1 -1
View File
@@ -4,7 +4,7 @@
* @license MIT
*/
import { CharData, IInputHandler, IInputHandlingTerminal, ITerminal } from './Types';
import { CharData, IInputHandler, IInputHandlingTerminal } from './Types';
import { C0 } from './EscapeSequences';
import { DEFAULT_CHARSET } from './Charsets';
import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX } from './Buffer';
+46 -21
View File
@@ -5,13 +5,13 @@
import { assert } from 'chai';
import { IMouseZoneManager, IMouseZone } from './input/Types';
import { ILinkMatcher, LineData, ITerminal, ILinkifier, IBuffer, IBufferAccessor, IElementAccessor } from './Types';
import { ILinkMatcher, LineData, ITerminal } from './Types';
import { Linkifier } from './Linkifier';
import { MockBuffer } from './utils/TestUtils.test';
import { MockBuffer, MockTerminal } from './utils/TestUtils.test';
import { CircularList } from './utils/CircularList';
class TestLinkifier extends Linkifier {
constructor(_terminal: IBufferAccessor & IElementAccessor) {
constructor(_terminal: ITerminal) {
super(_terminal);
Linkifier.TIME_BEFORE_LINKIFY = 0;
}
@@ -32,15 +32,14 @@ class TestMouseZoneManager implements IMouseZoneManager {
}
describe('Linkifier', () => {
let terminal: IBufferAccessor & IElementAccessor;
let terminal: ITerminal;
let linkifier: TestLinkifier;
let mouseZoneManager: TestMouseZoneManager;
beforeEach(() => {
terminal = {
buffer: new MockBuffer(),
element: <HTMLElement>{}
};
terminal = new MockTerminal();
terminal.cols = 100;
terminal.buffer = new MockBuffer();
terminal.buffer.lines = new CircularList<LineData>(20);
terminal.buffer.ydisp = 0;
linkifier = new TestLinkifier(terminal);
@@ -59,17 +58,6 @@ describe('Linkifier', () => {
terminal.buffer.lines.push(stringToRow(text));
}
function assertLinkifiesEntireRow(uri: string, done: MochaDone): void {
addRow(uri);
linkifier.linkifyRows();
setTimeout(() => {
assert.equal(mouseZoneManager.zones[0].x1, 1);
assert.equal(mouseZoneManager.zones[0].x2, uri.length + 1);
assert.equal(mouseZoneManager.zones[0].y, terminal.buffer.lines.length);
done();
}, 0);
}
function assertLinkifiesRow(rowText: string, linkMatcherRegex: RegExp, links: {x: number, length: number}[], done: MochaDone): void {
addRow(rowText);
linkifier.registerLinkMatcher(linkMatcherRegex, () => {});
@@ -80,7 +68,25 @@ describe('Linkifier', () => {
links.forEach((l, i) => {
assert.equal(mouseZoneManager.zones[i].x1, l.x + 1);
assert.equal(mouseZoneManager.zones[i].x2, l.x + l.length + 1);
assert.equal(mouseZoneManager.zones[i].y, terminal.buffer.lines.length);
assert.equal(mouseZoneManager.zones[i].y1, terminal.buffer.lines.length);
assert.equal(mouseZoneManager.zones[i].y2, terminal.buffer.lines.length);
});
done();
}, 0);
}
function assertLinkifiesMultiLineLink(rowText: string, linkMatcherRegex: RegExp, links: {x1: number, y1: number, x2: number, y2: number}[], done: MochaDone): void {
addRow(rowText);
linkifier.registerLinkMatcher(linkMatcherRegex, () => {});
linkifier.linkifyRows();
// Allow linkify to happen
setTimeout(() => {
assert.equal(mouseZoneManager.zones.length, links.length);
links.forEach((l, i) => {
assert.equal(mouseZoneManager.zones[i].x1, l.x1 + 1);
assert.equal(mouseZoneManager.zones[i].x2, l.x2 + 1);
assert.equal(mouseZoneManager.zones[i].y1, l.y1 + 1);
assert.equal(mouseZoneManager.zones[i].y2, l.y2 + 1);
});
done();
}, 0);
@@ -129,6 +135,24 @@ describe('Linkifier', () => {
// character (U+1F537) which caused the path to be duplicated. See #642.
assertLinkifiesRow('echo \'🔷foo\'', /foo/, [{x: 8, length: 3}], done);
});
describe('multi-line links', () => {
it('should match links that start on line 1/2 of a wrapped line and end on the last character of line 1/2', done => {
terminal.cols = 4;
assertLinkifiesMultiLineLink('12345', /1234/, [{x1: 0, x2: 4, y1: 0, y2: 0}], done);
});
it('should match links that start on line 1/2 of a wrapped line and wrap to line 2/2', done => {
terminal.cols = 4;
assertLinkifiesMultiLineLink('12345', /12345/, [{x1: 0, x2: 1, y1: 0, y2: 1}], done);
});
it('should match links that start and end on line 2/2 of a wrapped line', done => {
terminal.cols = 4;
assertLinkifiesMultiLineLink('12345678', /5678/, [{x1: 0, x2: 4, y1: 1, y2: 1}], done);
});
it('should match links that start on line 2/3 of a wrapped line and wrap to line 3/3', done => {
terminal.cols = 4;
assertLinkifiesMultiLineLink('123456789', /56789/, [{x1: 0, x2: 1, y1: 1, y2: 2}], done);
});
});
});
describe('validationCallback', () => {
@@ -141,7 +165,8 @@ describe('Linkifier', () => {
assert.equal(mouseZoneManager.zones.length, 1);
assert.equal(mouseZoneManager.zones[0].x1, 1);
assert.equal(mouseZoneManager.zones[0].x2, 5);
assert.equal(mouseZoneManager.zones[0].y, 1);
assert.equal(mouseZoneManager.zones[0].y1, 1);
assert.equal(mouseZoneManager.zones[0].y2, 1);
// Fires done()
mouseZoneManager.zones[0].clickCallback(<any>{});
}
+47 -15
View File
@@ -4,7 +4,7 @@
*/
import { IMouseZoneManager } from './input/Types';
import { ILinkHoverEvent, ILinkMatcher, LinkMatcherHandler, LinkMatcherValidationCallback, LineData, LinkHoverEventTypes, ILinkMatcherOptions, ITerminal, IBufferAccessor, ILinkifier, IElementAccessor } from './Types';
import { ILinkHoverEvent, ILinkMatcher, LinkMatcherHandler, LinkHoverEventTypes, ILinkMatcherOptions, ILinkifier, ITerminal } from './Types';
import { MouseZone } from './input/MouseZoneManager';
import { EventEmitter } from './EventEmitter';
@@ -27,7 +27,7 @@ export class Linkifier extends EventEmitter implements ILinkifier {
private _rowsToLinkify: {start: number, end: number};
constructor(
protected _terminal: IBufferAccessor & IElementAccessor
protected _terminal: ITerminal
) {
super();
this._rowsToLinkify = {
@@ -157,11 +157,32 @@ export class Linkifier extends EventEmitter implements ILinkifier {
* @param rowIndex The index of the row to linkify.
*/
private _linkifyRow(rowIndex: number): void {
const absoluteRowIndex = this._terminal.buffer.ydisp + rowIndex;
// Ensure the row exists
let absoluteRowIndex = this._terminal.buffer.ydisp + rowIndex;
if (absoluteRowIndex >= this._terminal.buffer.lines.length) {
return;
}
const text = this._terminal.buffer.translateBufferLineToString(absoluteRowIndex, false);
if ((<any>this._terminal.buffer.lines.get(absoluteRowIndex)).isWrapped) {
// Only attempt to linkify rows that start in the viewport
if (rowIndex !== 0) {
return;
}
// If the first row is wrapped, backtrack to find the origin row and linkify that
do {
rowIndex--;
absoluteRowIndex--;
} while ((<any>this._terminal.buffer.lines.get(absoluteRowIndex)).isWrapped);
}
// Construct full unwrapped line text
let text = this._terminal.buffer.translateBufferLineToString(absoluteRowIndex, false);
let currentIndex = absoluteRowIndex + 1;
while (currentIndex < this._terminal.buffer.lines.length &&
(<any>this._terminal.buffer.lines.get(currentIndex)).isWrapped) {
text += this._terminal.buffer.translateBufferLineToString(currentIndex++, false);
}
for (let i = 0; i < this._linkMatchers.length; i++) {
this._doLinkifyRow(rowIndex, text, this._linkMatchers[i]);
}
@@ -177,9 +198,6 @@ export class Linkifier extends EventEmitter implements ILinkifier {
* @return The link element(s) that were added.
*/
private _doLinkifyRow(rowIndex: number, text: string, matcher: ILinkMatcher, offset: number = 0): void {
// Iterate over nodes as we want to consider text nodes
let result = [];
// Find the first match
let match = text.match(matcher.regex);
if (!match || match.length === 0) {
@@ -221,10 +239,20 @@ export class Linkifier extends EventEmitter implements ILinkifier {
* @param matcher The link matcher for the link.
*/
private _addLink(x: number, y: number, uri: string, matcher: ILinkMatcher): void {
const x1 = x % this._terminal.cols;
const y1 = y + Math.floor(x / this._terminal.cols);
let x2 = (x1 + uri.length) % this._terminal.cols;
let y2 = y1 + Math.floor((x1 + uri.length) / this._terminal.cols);
if (x2 === 0) {
x2 = this._terminal.cols;
y2--;
}
this._mouseZoneManager.add(new MouseZone(
x + 1,
x + 1 + uri.length,
y + 1,
x1 + 1,
y1 + 1,
x2 + 1,
y2 + 1,
e => {
if (matcher.handler) {
return matcher.handler(e, uri);
@@ -232,18 +260,18 @@ export class Linkifier extends EventEmitter implements ILinkifier {
window.open(uri, '_blank');
},
e => {
this.emit(LinkHoverEventTypes.HOVER, <ILinkHoverEvent>{ x, y, length: uri.length});
this._terminal.element.style.cursor = 'pointer';
this.emit(LinkHoverEventTypes.HOVER, this._createLinkHoverEvent(x1, y1, x2, y2));
this._terminal.element.classList.add('xterm-cursor-pointer');
},
e => {
this.emit(LinkHoverEventTypes.TOOLTIP, <ILinkHoverEvent>{ x, y, length: uri.length});
this.emit(LinkHoverEventTypes.TOOLTIP, this._createLinkHoverEvent(x1, y1, x2, y2));
if (matcher.hoverTooltipCallback) {
matcher.hoverTooltipCallback(e, uri);
}
},
() => {
this.emit(LinkHoverEventTypes.LEAVE, <ILinkHoverEvent>{ x, y, length: uri.length});
this._terminal.element.style.cursor = '';
this.emit(LinkHoverEventTypes.LEAVE, this._createLinkHoverEvent(x1, y1, x2, y2));
this._terminal.element.classList.remove('xterm-cursor-pointer');
if (matcher.hoverLeaveCallback) {
matcher.hoverLeaveCallback();
}
@@ -256,4 +284,8 @@ export class Linkifier extends EventEmitter implements ILinkifier {
}
));
}
private _createLinkHoverEvent(x1: number, y1: number, x2: number, y2: number): ILinkHoverEvent {
return { x1, y1, x2, y2, cols: this._terminal.cols };
}
}
+2 -3
View File
@@ -5,7 +5,7 @@
*/
import { C0 } from './EscapeSequences';
import { IInputHandler } from './Types';
import { IInputHandler, IInputHandlingTerminal } from './Types';
import { CHARSETS, DEFAULT_CHARSET } from './Charsets';
const normalStateHandler: {[key: string]: (parser: Parser, handler: IInputHandler) => void} = {};
@@ -185,7 +185,6 @@ export class Parser {
*/
public parse(data: string): ParserState {
const l = data.length;
let j;
let cs;
let ch;
let code;
@@ -346,7 +345,7 @@ export class Parser {
// ESC H Tab Set (HTS is 0x88).
case 'H':
this._terminal.tabSet();
(<IInputHandlingTerminal>this._terminal).tabSet();
this._state = ParserState.NORMAL;
break;
+40 -11
View File
@@ -3,14 +3,12 @@
* @license MIT
*/
import jsdom = require('jsdom');
import { assert } from 'chai';
import { CharMeasure } from './utils/CharMeasure';
import { CircularList } from './utils/CircularList';
import { SelectionManager } from './SelectionManager';
import { SelectionModel } from './SelectionModel';
import { BufferSet } from './BufferSet';
import { LineData, CharData, ITerminal, ICircularList, IBuffer } from './Types';
import { LineData, CharData, ITerminal, IBuffer } from './Types';
import { MockTerminal } from './utils/TestUtils.test';
class TestMockTerminal extends MockTerminal {
@@ -37,19 +35,11 @@ class TestSelectionManager extends SelectionManager {
}
describe('SelectionManager', () => {
let dom: jsdom.JSDOM;
let window: Window;
let document: Document;
let terminal: ITerminal;
let buffer: IBuffer;
let rowContainer: HTMLElement;
let selectionManager: TestSelectionManager;
beforeEach(() => {
dom = new jsdom.JSDOM('');
window = dom.window;
document = window.document;
terminal = new TestMockTerminal();
terminal.cols = 80;
terminal.rows = 2;
@@ -324,6 +314,45 @@ describe('SelectionManager', () => {
});
});
describe('selectLines', () => {
it('should select a single line', () => {
buffer.lines.length = 3;
buffer.lines.set(0, stringToRow('1'));
buffer.lines.set(1, stringToRow('2'));
buffer.lines.set(2, stringToRow('3'));
selectionManager.selectLines(1, 1);
assert.deepEqual(selectionManager.model.finalSelectionStart, [0, 1]);
assert.deepEqual(selectionManager.model.finalSelectionEnd, [terminal.cols, 1]);
});
it('should select multiple lines', () => {
buffer.lines.length = 5;
buffer.lines.set(0, stringToRow('1'));
buffer.lines.set(1, stringToRow('2'));
buffer.lines.set(2, stringToRow('3'));
buffer.lines.set(3, stringToRow('4'));
buffer.lines.set(4, stringToRow('5'));
selectionManager.selectLines(1, 3);
assert.deepEqual(selectionManager.model.finalSelectionStart, [0, 1]);
assert.deepEqual(selectionManager.model.finalSelectionEnd, [terminal.cols, 3]);
});
it('should select the to the start when requesting a negative row', () => {
buffer.lines.length = 2;
buffer.lines.set(0, stringToRow('1'));
buffer.lines.set(1, stringToRow('2'));
selectionManager.selectLines(-1, 0);
assert.deepEqual(selectionManager.model.finalSelectionStart, [0, 0]);
assert.deepEqual(selectionManager.model.finalSelectionEnd, [terminal.cols, 0]);
});
it('should select the to the end when requesting beyond the final row', () => {
buffer.lines.length = 2;
buffer.lines.set(0, stringToRow('1'));
buffer.lines.set(1, stringToRow('2'));
selectionManager.selectLines(1, 2);
assert.deepEqual(selectionManager.model.finalSelectionStart, [0, 1]);
assert.deepEqual(selectionManager.model.finalSelectionEnd, [terminal.cols, 1]);
});
});
describe('hasSelection', () => {
it('should return whether there is a selection', () => {
selectionManager.model.selectionStart = [0, 0];
+11 -2
View File
@@ -3,11 +3,10 @@
* @license MIT
*/
import { ITerminal, ICircularList, ISelectionManager, IBuffer, LineData, CharData, XtermListener } from './Types';
import { ITerminal, ISelectionManager, IBuffer, CharData, XtermListener } from './Types';
import { MouseHelper } from './utils/MouseHelper';
import * as Browser from './shared/utils/Browser';
import { CharMeasure } from './utils/CharMeasure';
import { CircularList } from './utils/CircularList';
import { EventEmitter } from './EventEmitter';
import { SelectionModel } from './SelectionModel';
import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from './Buffer';
@@ -293,6 +292,16 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
this._terminal.emit('selection');
}
public selectLines(start: number, end: number): void {
this._model.clearSelection();
start = Math.max(start, 0);
end = Math.min(end, this._terminal.buffer.lines.length - 1);
this._model.selectionStart = [0, start];
this._model.selectionEnd = [this._terminal.cols, end];
this.refresh();
this._terminal.emit('selection');
}
/**
* Handle the buffer being trimmed, adjust the selection position.
* @param amount The amount the buffer is being trimmed.
-3
View File
@@ -18,9 +18,6 @@ class TestSelectionModel extends SelectionModel {
}
describe('SelectionManager', () => {
let window: Window;
let document: Document;
let terminal: ITerminal;
let model: TestSelectionModel;
+3 -3
View File
@@ -28,7 +28,7 @@ export class SoundManager implements ISoundManager {
if (this._audioContext) {
const bellAudioSource = this._audioContext.createBufferSource();
const context = this._audioContext;
this._audioContext.decodeAudioData(this.base64ToArrayBuffer(this.removeMimeType(this._terminal.options.bellSound)), (buffer) => {
this._audioContext.decodeAudioData(this._base64ToArrayBuffer(this._removeMimeType(this._terminal.options.bellSound)), (buffer) => {
bellAudioSource.buffer = buffer;
bellAudioSource.connect(context.destination);
bellAudioSource.start(0);
@@ -38,7 +38,7 @@ export class SoundManager implements ISoundManager {
}
}
private base64ToArrayBuffer(base64: string): ArrayBuffer {
private _base64ToArrayBuffer(base64: string): ArrayBuffer {
const binaryString = window.atob(base64);
const len = binaryString.length;
const bytes = new Uint8Array(len);
@@ -50,7 +50,7 @@ export class SoundManager implements ISoundManager {
return bytes.buffer;
}
private removeMimeType(dataURI: string): string {
private _removeMimeType(dataURI: string): string {
// Split the input to get the mime-type and the data itself
const splitUri = dataURI.split(',');
+1 -1
View File
@@ -125,7 +125,7 @@ if (os.platform() !== 'win32') {
// Perform a synchronous .write(data)
xterm.writeBuffer.push(fromPty);
xterm.innerWrite();
xterm._innerWrite();
let fromEmulator = terminalToString(xterm);
console.log = CONSOLE_LOG;
+44 -6
View File
@@ -20,20 +20,21 @@ class TestTerminal extends Terminal {
describe('term.js addons', () => {
let term: TestTerminal;
const termOptions = {
cols: INIT_COLS,
rows: INIT_ROWS
};
beforeEach(() => {
term = new TestTerminal({
cols: INIT_COLS,
rows: INIT_ROWS
});
term = new TestTerminal(termOptions);
term.refresh = () => {};
(<any>term).renderer = new MockRenderer();
term.viewport = new MockViewport();
(<any>term).compositionHelper = new MockCompositionHelper();
(<any>term)._compositionHelper = new MockCompositionHelper();
// Force synchronous writes
term.write = (data) => {
term.writeBuffer.push(data);
(<any>term).innerWrite();
(<any>term)._innerWrite();
};
(<any>term).element = {
classList: {
@@ -43,6 +44,15 @@ describe('term.js addons', () => {
};
});
it('should not mutate the options parameter', () => {
term.setOption('cols', 1000);
assert.deepEqual(termOptions, {
cols: INIT_COLS,
rows: INIT_ROWS
});
});
it('should apply addons with Terminal.applyAddon', () => {
Terminal.applyAddon(attach);
// Test that addon was applied successfully, adding attach to Terminal's
@@ -258,6 +268,34 @@ describe('term.js addons', () => {
});
});
describe('scrollToLine', () => {
let startYDisp;
beforeEach(() => {
for (let i = 0; i < term.rows * 3; i++) {
term.writeln('test');
}
startYDisp = (term.rows * 2) + 1;
});
it('should scroll to requested line', () => {
assert.equal(term.buffer.ydisp, startYDisp);
term.scrollToLine(0);
assert.equal(term.buffer.ydisp, 0);
term.scrollToLine(10);
assert.equal(term.buffer.ydisp, 10);
term.scrollToLine(startYDisp);
assert.equal(term.buffer.ydisp, startYDisp);
term.scrollToLine(20);
assert.equal(term.buffer.ydisp, 20);
});
it('should not scroll beyond boundary lines', () => {
assert.equal(term.buffer.ydisp, startYDisp);
term.scrollToLine(-1);
assert.equal(term.buffer.ydisp, 0);
term.scrollToLine(startYDisp + 1);
assert.equal(term.buffer.ydisp, startYDisp);
});
});
describe('keyDown', () => {
it('should scroll down, when a key is pressed and terminal is scrolled up', () => {
// Override _evaluateKeyEscapeSequence to return cancel code
+196 -198
View File
@@ -21,7 +21,7 @@
* http://linux.die.net/man/7/urxvt
*/
import { ICharset, IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminalOptions, ITerminal, IBrowser, ILinkifier, ILinkMatcherOptions, CustomKeyEventHandler, LinkMatcherHandler, LinkMatcherValidationCallback, CharData, LineData } from './Types';
import { ICharset, IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminalOptions, ITerminal, IBrowser, ILinkifier, ILinkMatcherOptions, CustomKeyEventHandler, LinkMatcherHandler, CharData, LineData } from './Types';
import { IMouseZoneManager } from './input/Types';
import { IRenderer } from './renderer/Types';
import { BufferSet } from './BufferSet';
@@ -30,7 +30,6 @@ import { CompositionHelper } from './CompositionHelper';
import { EventEmitter } from './EventEmitter';
import { Viewport } from './Viewport';
import { rightClickHandler, moveTextAreaUnderMouseCursor, pasteHandler, copyHandler } from './handlers/Clipboard';
import { CircularList } from './utils/CircularList';
import { C0 } from './EscapeSequences';
import { InputHandler } from './InputHandler';
import { Parser } from './Parser';
@@ -41,13 +40,13 @@ import { CharMeasure } from './utils/CharMeasure';
import * as Browser from './shared/utils/Browser';
import * as Strings from './Strings';
import { MouseHelper } from './utils/MouseHelper';
import { CHARSETS } from './Charsets';
import { clone } from './utils/Clone';
import { DEFAULT_BELL_SOUND, SoundManager } from './SoundManager';
import { DEFAULT_ANSI_COLORS } from './renderer/ColorManager';
import { MouseZoneManager } from './input/MouseZoneManager';
import { AccessibilityManager } from './AccessibilityManager';
import { ScreenDprMonitor } from './utils/ScreenDprMonitor';
import { ITheme, ILocalizableStrings } from 'xterm';
import { ITheme, ILocalizableStrings, IMarker } from 'xterm';
// reg + shift key mappings for digits and special chars
const KEYCODE_KEY_MAPPINGS = {
@@ -133,30 +132,27 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
/**
* The HTMLElement that the terminal is created in, set by Terminal.open.
*/
private parent: HTMLElement;
private context: Window;
private document: Document;
private body: HTMLBodyElement;
private viewportScrollArea: HTMLElement;
private viewportElement: HTMLElement;
private helperContainer: HTMLElement;
private compositionView: HTMLElement;
private charSizeStyleElement: HTMLStyleElement;
private _parent: HTMLElement;
private _context: Window;
private _document: Document;
private _viewportScrollArea: HTMLElement;
private _viewportElement: HTMLElement;
private _helperContainer: HTMLElement;
private _compositionView: HTMLElement;
private visualBellTimer: number;
private _visualBellTimer: number;
public browser: IBrowser = <any>Browser;
public options: ITerminalOptions;
private colors: any;
// TODO: This can be changed to an enum or boolean, 0 and 1 seem to be the only options
public cursorState: number;
public cursorHidden: boolean;
public convertEol: boolean;
private sendDataQueue: string;
private customKeyEventHandler: CustomKeyEventHandler;
private _sendDataQueue: string;
private _customKeyEventHandler: CustomKeyEventHandler;
// modes
public applicationKeypad: boolean;
@@ -174,10 +170,10 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
public charsets: ICharset[];
// mouse properties
private decLocator: boolean; // This is unstable and never set
private _decLocator: boolean; // This is unstable and never set
public x10Mouse: boolean;
public vt200Mouse: boolean;
private vt300Mouse: boolean; // This is unstable and never set
private _vt300Mouse: boolean; // This is unstable and never set
public normalMouse: boolean;
public mouseEvents: boolean;
public sendFocus: boolean;
@@ -186,14 +182,10 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
public urxvtMouse: boolean;
// misc
private refreshStart: number;
private refreshEnd: number;
private _refreshStart: number;
private _refreshEnd: number;
public savedCols: number;
// stream
private readable: boolean;
private writable: boolean;
public defAttr: number;
public curAttr: number;
@@ -204,7 +196,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
// user input states
public writeBuffer: string[];
private writeInProgress: boolean;
private _writeInProgress: boolean;
/**
* Whether _xterm.js_ sent XOFF in order to catch up with the pty process.
@@ -212,26 +204,23 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
* XOFF via ^S that it will not automatically resume when the writeBuffer goes
* below threshold.
*/
private xoffSentToCatchUp: boolean;
private _xoffSentToCatchUp: boolean;
/** Whether writing has been stopped as a result of XOFF */
private writeStopped: boolean;
// leftover surrogate high from previous write invocation
private surrogateHigh: string;
// private _writeStopped: boolean;
// Store if user went browsing history in scrollback
private userScrolling: boolean;
private _userScrolling: boolean;
private inputHandler: InputHandler;
private _inputHandler: InputHandler;
public soundManager: SoundManager;
private parser: Parser;
private _parser: Parser;
public renderer: IRenderer;
public selectionManager: SelectionManager;
public linkifier: ILinkifier;
public buffers: BufferSet;
public viewport: IViewport;
private compositionHelper: ICompositionHelper;
private _compositionHelper: ICompositionHelper;
public charMeasure: CharMeasure;
private _mouseZoneManager: IMouseZoneManager;
public mouseHelper: MouseHelper;
@@ -257,11 +246,11 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
options: ITerminalOptions = {}
) {
super();
this.options = options;
this.setup();
this.options = clone(options);
this._setup();
}
private setup(): void {
private _setup(): void {
Object.keys(DEFAULT_OPTIONS).forEach((key) => {
if (this.options[key] == null) {
this.options[key] = DEFAULT_OPTIONS[key];
@@ -273,7 +262,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
// this.context = options.context || window;
// this.document = options.document || document;
// TODO: WHy not document.body?
this.parent = document ? document.body : null;
this._parent = document ? document.body : null;
this.cols = this.options.cols;
this.rows = this.options.rows;
@@ -284,8 +273,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
this.cursorState = 0;
this.cursorHidden = false;
this.sendDataQueue = '';
this.customKeyEventHandler = null;
this._sendDataQueue = '';
this._customKeyEventHandler = null;
// modes
this.applicationKeypad = false;
@@ -302,9 +291,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
// TODO: Can this be just []?
this.charsets = [null];
this.readable = true;
this.writable = true;
this.defAttr = (0 << 18) | (257 << 9) | (256 << 0);
this.curAttr = (0 << 18) | (257 << 9) | (256 << 0);
@@ -315,15 +301,14 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
// user input states
this.writeBuffer = [];
this.writeInProgress = false;
this._writeInProgress = false;
this.xoffSentToCatchUp = false;
this.writeStopped = false;
this.surrogateHigh = '';
this.userScrolling = false;
this._xoffSentToCatchUp = false;
// this._writeStopped = false;
this._userScrolling = false;
this.inputHandler = new InputHandler(this);
this.parser = new Parser(this.inputHandler, this);
this._inputHandler = new InputHandler(this);
this._parser = new Parser(this._inputHandler, this);
// Reuse renderer if the Terminal is being recreated via a reset call.
this.renderer = this.renderer || null;
this.selectionManager = this.selectionManager || null;
@@ -472,11 +457,9 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
case 'lineHeight':
case 'fontWeight':
case 'fontWeightBold':
const didCharSizeChange = (key === 'fontWeight' || key === 'fontWeightBold' || key === 'enableBold');
// When the font changes the size of the cells may change which requires a renderer clear
this.renderer.clear();
this.renderer.onResize(this.cols, this.rows, didCharSizeChange);
this.renderer.onResize(this.cols, this.rows);
this.refresh(0, this.rows - 1);
case 'scrollback':
this.buffers.resize(this.cols, this.rows);
@@ -540,8 +523,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
/**
* Initialize default behavior
*/
private initGlobal(): void {
this.bindKeys();
private _initGlobal(): void {
this._bindKeys();
// Bind clipboard functionality
on(this.element, 'copy', (event: ClipboardEvent) => {
@@ -587,7 +570,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
/**
* Apply key handling to the terminal
*/
private bindKeys(): void {
private _bindKeys(): void {
const self = this;
on(this.element, 'keydown', function (ev: KeyboardEvent): void {
if (document.activeElement !== this) {
@@ -611,11 +594,11 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
on(this.textarea, 'keydown', (ev: KeyboardEvent) => this._keyDown(ev), true);
on(this.textarea, 'keypress', (ev: KeyboardEvent) => this._keyPress(ev), true);
on(this.textarea, 'compositionstart', () => this.compositionHelper.compositionstart());
on(this.textarea, 'compositionupdate', (e: CompositionEvent) => this.compositionHelper.compositionupdate(e));
on(this.textarea, 'compositionend', () => this.compositionHelper.compositionend());
this.on('refresh', () => this.compositionHelper.updateCompositionElements());
this.on('refresh', (data) => this.queueLinkification(data.start, data.end));
on(this.textarea, 'compositionstart', () => this._compositionHelper.compositionstart());
on(this.textarea, 'compositionupdate', (e: CompositionEvent) => this._compositionHelper.compositionupdate(e));
on(this.textarea, 'compositionend', () => this._compositionHelper.compositionend());
this.on('refresh', () => this._compositionHelper.updateCompositionElements());
this.on('refresh', (data) => this._queueLinkification(data.start, data.end));
}
/**
@@ -624,47 +607,44 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
* @param {HTMLElement} parent The element to create the terminal within.
*/
public open(parent: HTMLElement): void {
let i = 0;
let div;
this._parent = parent || this._parent;
this.parent = parent || this.parent;
if (!this.parent) {
if (!this._parent) {
throw new Error('Terminal requires a parent element.');
}
// Grab global elements
this.context = this.parent.ownerDocument.defaultView;
this.document = this.parent.ownerDocument;
this.body = <HTMLBodyElement>this.document.body;
this._context = this._parent.ownerDocument.defaultView;
this._document = this._parent.ownerDocument;
this._screenDprMonitor = new ScreenDprMonitor();
this._screenDprMonitor.setListener(() => this.emit('dprchange', window.devicePixelRatio));
// Create main element container
this.element = this.document.createElement('div');
this.element = this._document.createElement('div');
this.element.dir = 'ltr'; // xterm.css assumes LTR
this.element.classList.add('terminal');
this.element.classList.add('xterm');
this.element.setAttribute('tabindex', '0');
this.parent.appendChild(this.element);
this._parent.appendChild(this.element);
// Performance: Use a document fragment to build the terminal
// viewport and helper elements detached from the DOM
const fragment = document.createDocumentFragment();
this.viewportElement = document.createElement('div');
this.viewportElement.classList.add('xterm-viewport');
fragment.appendChild(this.viewportElement);
this.viewportScrollArea = document.createElement('div');
this.viewportScrollArea.classList.add('xterm-scroll-area');
this.viewportElement.appendChild(this.viewportScrollArea);
this._viewportElement = document.createElement('div');
this._viewportElement.classList.add('xterm-viewport');
fragment.appendChild(this._viewportElement);
this._viewportScrollArea = document.createElement('div');
this._viewportScrollArea.classList.add('xterm-scroll-area');
this._viewportElement.appendChild(this._viewportScrollArea);
this.screenElement = document.createElement('div');
this.screenElement.classList.add('xterm-screen');
// Create the container that will hold helpers like the textarea for
// capturing DOM Events. Then produce the helpers.
this.helperContainer = document.createElement('div');
this.helperContainer.classList.add('xterm-helpers');
this.screenElement.appendChild(this.helperContainer);
this._helperContainer = document.createElement('div');
this._helperContainer.classList.add('xterm-helpers');
this.screenElement.appendChild(this._helperContainer);
fragment.appendChild(this.screenElement);
this._mouseZoneManager = new MouseZoneManager(this);
@@ -682,34 +662,32 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
this.textarea.tabIndex = 0;
this.textarea.addEventListener('focus', () => this._onTextAreaFocus());
this.textarea.addEventListener('blur', () => this._onTextAreaBlur());
this.helperContainer.appendChild(this.textarea);
this._helperContainer.appendChild(this.textarea);
this.compositionView = document.createElement('div');
this.compositionView.classList.add('composition-view');
this.compositionHelper = new CompositionHelper(this.textarea, this.compositionView, this);
this.helperContainer.appendChild(this.compositionView);
this._compositionView = document.createElement('div');
this._compositionView.classList.add('composition-view');
this._compositionHelper = new CompositionHelper(this.textarea, this._compositionView, this);
this._helperContainer.appendChild(this._compositionView);
this.charSizeStyleElement = document.createElement('style');
this.helperContainer.appendChild(this.charSizeStyleElement);
this.charMeasure = new CharMeasure(document, this.helperContainer);
this.charMeasure = new CharMeasure(document, this._helperContainer);
// Performance: Add viewport and helper elements from the fragment
this.element.appendChild(fragment);
this.renderer = new Renderer(this, this.options.theme);
this.options.theme = null;
this.viewport = new Viewport(this, this.viewportElement, this.viewportScrollArea, this.charMeasure);
this.viewport = new Viewport(this, this._viewportElement, this._viewportScrollArea, this.charMeasure);
this.viewport.onThemeChanged(this.renderer.colorManager.colors);
this.on('cursormove', () => this.renderer.onCursorMove());
this.on('resize', () => this.renderer.onResize(this.cols, this.rows, false));
this.on('resize', () => this.renderer.onResize(this.cols, this.rows));
this.on('blur', () => this.renderer.onBlur());
this.on('focus', () => this.renderer.onFocus());
this.on('dprchange', () => this.renderer.onWindowResize(window.devicePixelRatio));
// dprchange should handle this case, we need this as well for browsers that don't support the
// matchMedia query.
window.addEventListener('resize', () => this.renderer.onWindowResize(window.devicePixelRatio));
this.charMeasure.on('charsizechanged', () => this.renderer.onResize(this.cols, this.rows, true));
this.charMeasure.on('charsizechanged', () => this.renderer.onResize(this.cols, this.rows));
this.renderer.on('resize', (dimensions) => this.viewport.syncScrollArea());
this.selectionManager = new SelectionManager(this, this.charMeasure);
@@ -727,7 +705,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
this.viewport.syncScrollArea();
this.selectionManager.refresh();
});
this.viewportElement.addEventListener('scroll', () => this.selectionManager.refresh());
this._viewportElement.addEventListener('scroll', () => this.selectionManager.refresh());
this.mouseHelper = new MouseHelper(this.renderer);
@@ -744,7 +722,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
this.refresh(0, this.rows - 1);
// Initialize global actions that need to be taken on the document.
this.initGlobal();
this._initGlobal();
// Listen for mouse events and translate
// them into terminal mouse protocols.
@@ -871,7 +849,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
// button: button
// });
if (self.vt300Mouse) {
if (self._vt300Mouse) {
// NOTE: Unstable.
// http://www.vt100.net/docs/vt3xx-gp/chapter15.html
button &= 3;
@@ -888,7 +866,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
return;
}
if (self.decLocator) {
if (self._decLocator) {
// NOTE: Unstable.
button &= 3;
pos.x -= 32;
@@ -1031,19 +1009,19 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
}
// bind events
if (this.normalMouse) on(this.document, 'mousemove', sendMove);
if (this.normalMouse) on(this._document, 'mousemove', sendMove);
// x10 compatibility mode can't send button releases
if (!this.x10Mouse) {
const handler = (ev: MouseEvent) => {
sendButton(ev);
// TODO: Seems dangerous calling this on document?
if (this.normalMouse) off(this.document, 'mousemove', sendMove);
off(this.document, 'mouseup', handler);
if (this.normalMouse) off(this._document, 'mousemove', sendMove);
off(this._document, 'mouseup', handler);
return this.cancel(ev);
};
// TODO: Seems dangerous calling this on document?
on(this.document, 'mouseup', handler);
on(this._document, 'mouseup', handler);
}
return this.cancel(ev);
@@ -1054,8 +1032,28 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
// }
on(el, 'wheel', (ev: WheelEvent) => {
if (!this.mouseEvents) return;
if (this.x10Mouse || this.vt300Mouse || this.decLocator) return;
if (!this.mouseEvents) {
// Convert wheel events into up/down events when the buffer does not have scrollback, this
// enables scrolling in apps hosted in the alt buffer such as vim or tmux.
if (!this.buffer.hasScrollback) {
const amount = this.viewport.getLinesScrolled(ev);
// Do nothing if there's no vertical scroll
if (amount === 0) {
return;
}
// Construct and send sequences
const sequence = C0.ESC + (this.applicationCursor ? 'O' : '[') + ( ev.deltaY < 0 ? 'A' : 'B');
let data = '';
for (let i = 0; i < Math.abs(amount); i++) {
data += sequence;
}
this.send(data);
}
return;
}
if (this.x10Mouse || this._vt300Mouse || this._decLocator) return;
sendButton(ev);
ev.preventDefault();
});
@@ -1086,8 +1084,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
*/
public destroy(): void {
super.destroy();
this.readable = false;
this.writable = false;
this.handler = () => {};
this.write = () => {};
if (this.element && this.element.parentNode) {
@@ -1113,7 +1109,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
* @param {number} start The row to start from (between 0 and this.rows - 1).
* @param {number} end The row to end at (between start and this.rows - 1).
*/
private queueLinkification(start: number, end: number): void {
private _queueLinkification(start: number, end: number): void {
if (this.linkifier) {
this.linkifier.linkifyRows(start, end);
}
@@ -1153,13 +1149,13 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
if (!willBufferBeTrimmed) {
this.buffer.ybase++;
// Only scroll the ydisp with ybase if the user has not scrolled up
if (!this.userScrolling) {
if (!this._userScrolling) {
this.buffer.ydisp++;
}
} else {
// When the buffer is full and the user has scrolled up, keep the text
// stable unless ydisp is right at the top
if (this.userScrolling) {
if (this._userScrolling) {
this.buffer.ydisp = Math.max(this.buffer.ydisp - 1, 0);
}
}
@@ -1173,7 +1169,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
// Move the viewport to the bottom of the buffer unless the user is
// scrolling.
if (!this.userScrolling) {
if (!this._userScrolling) {
this.buffer.ydisp = this.buffer.ybase;
}
@@ -1202,9 +1198,9 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
if (this.buffer.ydisp === 0) {
return;
}
this.userScrolling = true;
this._userScrolling = true;
} else if (disp + this.buffer.ydisp >= this.buffer.ybase) {
this.userScrolling = false;
this._userScrolling = false;
}
const oldYdisp = this.buffer.ydisp;
@@ -1244,64 +1240,76 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
this.scrollLines(this.buffer.ybase - this.buffer.ydisp);
}
public scrollToLine(line: number): void {
const scrollAmount = line - this.buffer.ydisp;
if (scrollAmount !== 0) {
this.scrollLines(scrollAmount);
}
}
/**
* Writes text to the terminal.
* @param {string} data The text to write to the terminal.
*/
public write(data: string): void {
// Ignore falsy data values (including the empty string)
if (!data) {
return;
}
this.writeBuffer.push(data);
// Send XOFF to pause the pty process if the write buffer becomes too large so
// xterm.js can catch up before more data is sent. This is necessary in order
// to keep signals such as ^C responsive.
if (this.options.useFlowControl && !this.xoffSentToCatchUp && this.writeBuffer.length >= WRITE_BUFFER_PAUSE_THRESHOLD) {
if (this.options.useFlowControl && !this._xoffSentToCatchUp && this.writeBuffer.length >= WRITE_BUFFER_PAUSE_THRESHOLD) {
// XOFF - stop pty pipe
// XON will be triggered by emulator before processing data chunk
this.send(C0.DC3);
this.xoffSentToCatchUp = true;
this._xoffSentToCatchUp = true;
}
if (!this.writeInProgress && this.writeBuffer.length > 0) {
if (!this._writeInProgress && this.writeBuffer.length > 0) {
// Kick off a write which will write all data in sequence recursively
this.writeInProgress = true;
this._writeInProgress = true;
// Kick off an async innerWrite so more writes can come in while processing data
setTimeout(() => {
this.innerWrite();
this._innerWrite();
});
}
}
private innerWrite(): void {
private _innerWrite(): void {
const writeBatch = this.writeBuffer.splice(0, WRITE_BATCH_SIZE);
while (writeBatch.length > 0) {
const data = writeBatch.shift();
// If XOFF was sent in order to catch up with the pty process, resume it if
// the writeBuffer is empty to allow more data to come in.
if (this.xoffSentToCatchUp && writeBatch.length === 0 && this.writeBuffer.length === 0) {
if (this._xoffSentToCatchUp && writeBatch.length === 0 && this.writeBuffer.length === 0) {
this.send(C0.DC1);
this.xoffSentToCatchUp = false;
this._xoffSentToCatchUp = false;
}
this.refreshStart = this.buffer.y;
this.refreshEnd = this.buffer.y;
this._refreshStart = this.buffer.y;
this._refreshEnd = this.buffer.y;
// HACK: Set the parser state based on it's state at the time of return.
// This works around the bug #662 which saw the parser state reset in the
// middle of parsing escape sequence in two chunks. For some reason the
// state of the parser resets to 0 after exiting parser.parse. This change
// just sets the state back based on the correct return statement.
const state = this.parser.parse(data);
this.parser.setState(state);
const state = this._parser.parse(data);
this._parser.setState(state);
this.updateRange(this.buffer.y);
this.refresh(this.refreshStart, this.refreshEnd);
this.refresh(this._refreshStart, this._refreshEnd);
}
if (this.writeBuffer.length > 0) {
// Allow renderer to catch up before processing the next batch
setTimeout(() => this.innerWrite(), 0);
setTimeout(() => this._innerWrite(), 0);
} else {
this.writeInProgress = false;
this._writeInProgress = false;
}
}
@@ -1323,7 +1331,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
* the event should be processed by xterm.js.
*/
public attachCustomKeyEventHandler(customKeyEventHandler: CustomKeyEventHandler): void {
this.customKeyEventHandler = customKeyEventHandler;
this._customKeyEventHandler = customKeyEventHandler;
}
/**
@@ -1352,6 +1360,19 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
}
}
public get markers(): IMarker[] {
return this.buffer.markers;
}
public addMarker(cursorYOffset: number): IMarker {
// Disallow markers on the alt buffer
if (this.buffer !== this.buffers.normal) {
return;
}
return this.buffer.addMarker(this.buffer.ybase + this.buffer.y + cursorYOffset);
}
/**
* Gets whether the terminal has an active selection.
*/
@@ -1385,6 +1406,12 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
}
}
public selectLines(start: number, end: number): void {
if (this.selectionManager) {
this.selectionManager.selectLines(start, end);
}
}
/**
* Handle a keydown event
* Key Resources:
@@ -1392,11 +1419,11 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
* @param {KeyboardEvent} ev The keydown event to be handled.
*/
protected _keyDown(ev: KeyboardEvent): boolean {
if (this.customKeyEventHandler && this.customKeyEventHandler(ev) === false) {
if (this._customKeyEventHandler && this._customKeyEventHandler(ev) === false) {
return false;
}
if (!this.compositionHelper.keydown(ev)) {
if (!this._compositionHelper.keydown(ev)) {
if (this.buffer.ybase !== this.buffer.ydisp) {
this.scrollToBottom();
}
@@ -1405,11 +1432,11 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
const result = this._evaluateKeyEscapeSequence(ev);
if (result.key === C0.DC3) { // XOFF
this.writeStopped = true;
} else if (result.key === C0.DC1) { // XON
this.writeStopped = false;
}
// if (result.key === C0.DC3) { // XOFF
// this._writeStopped = true;
// } else if (result.key === C0.DC1) { // XON
// this._writeStopped = false;
// }
if (result.scrollLines) {
this.scrollLines(result.scrollLines);
@@ -1803,7 +1830,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
protected _keyPress(ev: KeyboardEvent): boolean {
let key;
if (this.customKeyEventHandler && this.customKeyEventHandler(ev) === false) {
if (this._customKeyEventHandler && this._customKeyEventHandler(ev) === false) {
return false;
}
@@ -1840,14 +1867,14 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
* @param {string} data
*/
public send(data: string): void {
if (!this.sendDataQueue) {
if (!this._sendDataQueue) {
setTimeout(() => {
this.handler(this.sendDataQueue);
this.sendDataQueue = '';
this.handler(this._sendDataQueue);
this._sendDataQueue = '';
}, 1);
}
this.sendDataQueue += data;
this._sendDataQueue += data;
}
/**
@@ -1856,14 +1883,14 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
*/
public bell(): void {
this.emit('bell');
if (this.soundBell()) {
if (this._soundBell()) {
this.soundManager.playBellSound();
}
if (this.visualBell()) {
if (this._visualBell()) {
this.element.classList.add('visual-bell-active');
clearTimeout(this.visualBellTimer);
this.visualBellTimer = window.setTimeout(() => {
clearTimeout(this._visualBellTimer);
this._visualBellTimer = window.setTimeout(() => {
this.element.classList.remove('visual-bell-active');
}, 200);
}
@@ -1874,8 +1901,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
*/
public log(text: string, data?: any): void {
if (!this.options.debug) return;
if (!this.context.console || !this.context.console.log) return;
this.context.console.log(text, data);
if (!this._context.console || !this._context.console.log) return;
this._context.console.log(text, data);
}
/**
@@ -1883,8 +1910,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
*/
public error(text: string, data?: any): void {
if (!this.options.debug) return;
if (!this.context.console || !this.context.console.error) return;
this.context.console.error(text, data);
if (!this._context.console || !this._context.console.error) return;
this._context.console.error(text, data);
}
/**
@@ -1928,8 +1955,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
* @param {number} y The number of rows to refresh next.
*/
public updateRange(y: number): void {
if (y < this.refreshStart) this.refreshStart = y;
if (y > this.refreshEnd) this.refreshEnd = y;
if (y < this._refreshStart) this._refreshStart = y;
if (y > this._refreshEnd) this._refreshEnd = y;
// if (y > this.refreshEnd) {
// this.refreshEnd = y;
// if (y > this.rows - 1) {
@@ -1942,8 +1969,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
* Set the range of refreshing to the maximum value
*/
public maxRange(): void {
this.refreshStart = 0;
this.refreshEnd = this.rows - 1;
this._refreshStart = 0;
this._refreshEnd = this.rows - 1;
}
/**
@@ -2081,7 +2108,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
* Emit the 'title' event and populate the given title.
* @param {string} title The title to populate in the event.
*/
private handleTitle(title: string): void {
public handleTitle(title: string): void {
/**
* This event is emitted when the title of the terminal is changed
* from inside the terminal. The parameter is the new title.
@@ -2136,20 +2163,22 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
public reset(): void {
this.options.rows = this.rows;
this.options.cols = this.cols;
const customKeyEventHandler = this.customKeyEventHandler;
const inputHandler = this.inputHandler;
this.setup();
this.customKeyEventHandler = customKeyEventHandler;
this.inputHandler = inputHandler;
const customKeyEventHandler = this._customKeyEventHandler;
const inputHandler = this._inputHandler;
this._setup();
this._customKeyEventHandler = customKeyEventHandler;
this._inputHandler = inputHandler;
this.refresh(0, this.rows - 1);
this.viewport.syncScrollArea();
if (this.viewport) {
this.viewport.syncScrollArea();
}
}
/**
* ESC H Tab Set (HTS is 0x88).
*/
private tabSet(): void {
public tabSet(): void {
this.buffer.tabs[this.buffer.x] = true;
}
@@ -2168,13 +2197,13 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
return matchColor_(r1, g1, b1);
}
private visualBell(): boolean {
private _visualBell(): boolean {
return false;
// return this.options.bellStyle === 'visual' ||
// this.options.bellStyle === 'both';
}
private soundBell(): boolean {
private _soundBell(): boolean {
return this.options.bellStyle === 'sound';
// return this.options.bellStyle === 'sound' ||
// this.options.bellStyle === 'both';
@@ -2213,38 +2242,6 @@ function wasMondifierKeyOnlyEvent(ev: KeyboardEvent): boolean {
* ANSI color code.
*/
// Colors 0-15 + 16-255
// Much thanks to TooTallNate for writing this.
const vcolors: number[][] = (function(): number[][] {
const result = DEFAULT_ANSI_COLORS.map(c => {
c = c.substring(1);
return [
parseInt(c.substring(0, 2), 16),
parseInt(c.substring(2, 4), 16),
parseInt(c.substring(4, 6), 16)
];
});
const r = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff];
// 16-231
for (let i = 0; i < 216; i++) {
result.push([
r[(i / 36) % 6 | 0],
r[(i / 6) % 6 | 0],
r[i % 6]
]);
}
// 232-255 (grey)
let c: number;
for (let i = 0; i < 24; i++) {
c = 8 + i * 10;
result.push([c, c, c]);
}
return result;
})();
const matchColorCache: {[colorRGBHash: number]: number} = {};
// http://stackoverflow.com/questions/1633828
@@ -2265,17 +2262,18 @@ function matchColor_(r1: number, g1: number, b1: number): number {
let ldiff = Infinity;
let li = -1;
let i = 0;
let c: number[];
let c: number;
let r2: number;
let g2: number;
let b2: number;
let diff: number;
for (; i < vcolors.length; i++) {
c = vcolors[i];
r2 = c[0];
g2 = c[1];
b2 = c[2];
for (; i < DEFAULT_ANSI_COLORS.length; i++) {
c = DEFAULT_ANSI_COLORS[i].rgba;
r2 = c >>> 24;
g2 = c >>> 16 & 0xFF;
b2 = c >>> 8 & 0xFF;
// assume that alpha is 0xFF
diff = matchColorDistance(r1, g1, b1, r2, g2, b2);
+8 -5
View File
@@ -3,7 +3,7 @@
* @license MIT
*/
import { Terminal as PublicTerminal, ITerminalOptions as IPublicTerminalOptions, IEventEmitter as IPublicEventEmitter, IEventEmitter } from 'xterm';
import { Terminal as PublicTerminal, ITerminalOptions as IPublicTerminalOptions, IEventEmitter } from 'xterm';
import { IColorSet, IRenderer } from './renderer/Types';
import { IMouseZoneManager } from './input/Types';
@@ -84,11 +84,13 @@ export interface IInputHandlingTerminal extends IEventEmitter {
matchColor(r1: number, g1: number, b1: number): number;
error(text: string, data?: any): void;
setOption(key: string, value: any): void;
tabSet(): void;
}
export interface IViewport {
scrollBarWidth: number;
syncScrollArea(): void;
getLinesScrolled(ev: WheelEvent): number;
onWheel(ev: WheelEvent): void;
onTouchStart(ev: TouchEvent): void;
onTouchMove(ev: TouchEvent): void;
@@ -172,9 +174,11 @@ export interface ICharset {
}
export interface ILinkHoverEvent {
x: number;
y: number;
length: number;
x1: number;
y1: number;
x2: number;
y2: number;
cols: number;
}
export interface ITerminal extends PublicTerminal, IElementAccessor, IBufferAccessor, ILinkifierAccessor {
@@ -273,7 +277,6 @@ export interface IBufferSet extends IEventEmitter {
export interface ICircularList<T> extends IEventEmitter {
length: number;
maxLength: number;
forEach: (callbackfn: (value: T, index: number) => void) => void;
get(index: number): T;
set(index: number, value: T): void;
+91 -53
View File
@@ -15,51 +15,56 @@ const FALLBACK_SCROLL_BAR_WIDTH = 15;
*/
export class Viewport implements IViewport {
public scrollBarWidth: number = 0;
private currentRowHeight: number = 0;
private lastRecordedBufferLength: number = 0;
private lastRecordedViewportHeight: number = 0;
private lastRecordedBufferHeight: number = 0;
private lastTouchY: number;
private _currentRowHeight: number = 0;
private _lastRecordedBufferLength: number = 0;
private _lastRecordedViewportHeight: number = 0;
private _lastRecordedBufferHeight: number = 0;
private _lastTouchY: number;
// Stores a partial line amount when scrolling, this is used to keep track of how much of a line
// is scrolled so we can "scroll" over partial lines and feel natural on touchpads. This is a
// quick fix and could have a more robust solution in place that reset the value when needed.
private _wheelPartialScroll: number = 0;
/**
* 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 charMeasure A DOM element used to measure the character size of. the terminal.
* @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 _charMeasure 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
private _terminal: ITerminal,
private _viewportElement: HTMLElement,
private _scrollArea: HTMLElement,
private _charMeasure: CharMeasure
) {
// Measure the width of the scrollbar. If it is 0 we can assume it's an OSX overlay scrollbar.
// Unfortunately the overlay scrollbar would be hidden underneath the screen element in that case,
// therefore we account for a standard amount to make it visible
this.scrollBarWidth = (this.viewportElement.offsetWidth - this.scrollArea.offsetWidth) || FALLBACK_SCROLL_BAR_WIDTH;
this.viewportElement.addEventListener('scroll', this.onScroll.bind(this));
this.scrollBarWidth = (this._viewportElement.offsetWidth - this._scrollArea.offsetWidth) || FALLBACK_SCROLL_BAR_WIDTH;
this._viewportElement.addEventListener('scroll', this._onScroll.bind(this));
// Perform this async to ensure the CharMeasure is ready.
setTimeout(() => this.syncScrollArea(), 0);
}
public onThemeChanged(colors: IColorSet): void {
this.viewportElement.style.backgroundColor = colors.background;
this._viewportElement.style.backgroundColor = colors.background.css;
}
/**
* Refreshes row height, setting line-height, viewport height and scroll area height if
* necessary.
*/
private refresh(): void {
if (this.charMeasure.height > 0) {
this.currentRowHeight = this.terminal.renderer.dimensions.scaledCellHeight / window.devicePixelRatio;
this.lastRecordedViewportHeight = this.viewportElement.offsetHeight;
const newBufferHeight = Math.round(this.currentRowHeight * this.lastRecordedBufferLength) + (this.lastRecordedViewportHeight - this.terminal.renderer.dimensions.canvasHeight);
if (this.lastRecordedBufferHeight !== newBufferHeight) {
this.lastRecordedBufferHeight = newBufferHeight;
this.scrollArea.style.height = this.lastRecordedBufferHeight + 'px';
private _refresh(): void {
if (this._charMeasure.height > 0) {
this._currentRowHeight = this._terminal.renderer.dimensions.scaledCellHeight / window.devicePixelRatio;
this._lastRecordedViewportHeight = this._viewportElement.offsetHeight;
const newBufferHeight = Math.round(this._currentRowHeight * this._lastRecordedBufferLength) + (this._lastRecordedViewportHeight - this._terminal.renderer.dimensions.canvasHeight);
if (this._lastRecordedBufferHeight !== newBufferHeight) {
this._lastRecordedBufferHeight = newBufferHeight;
this._scrollArea.style.height = this._lastRecordedBufferHeight + 'px';
}
}
}
@@ -68,24 +73,24 @@ export class Viewport implements IViewport {
* Updates dimensions and synchronizes the scroll area if necessary.
*/
public syncScrollArea(): void {
if (this.lastRecordedBufferLength !== this.terminal.buffer.lines.length) {
if (this._lastRecordedBufferLength !== this._terminal.buffer.lines.length) {
// If buffer height changed
this.lastRecordedBufferLength = this.terminal.buffer.lines.length;
this.refresh();
} else if (this.lastRecordedViewportHeight !== (<any>this.terminal).renderer.dimensions.canvasHeight) {
this._lastRecordedBufferLength = this._terminal.buffer.lines.length;
this._refresh();
} else if (this._lastRecordedViewportHeight !== (<any>this._terminal).renderer.dimensions.canvasHeight) {
// If viewport height changed
this.refresh();
this._refresh();
} else {
// If size has changed, refresh viewport
if (this.terminal.renderer.dimensions.scaledCellHeight / window.devicePixelRatio !== this.currentRowHeight) {
this.refresh();
if (this._terminal.renderer.dimensions.scaledCellHeight / window.devicePixelRatio !== this._currentRowHeight) {
this._refresh();
}
}
// Sync scrollTop
const scrollTop = this.terminal.buffer.ydisp * this.currentRowHeight;
if (this.viewportElement.scrollTop !== scrollTop) {
this.viewportElement.scrollTop = scrollTop;
const scrollTop = this._terminal.buffer.ydisp * this._currentRowHeight;
if (this._viewportElement.scrollTop !== scrollTop) {
this._viewportElement.scrollTop = scrollTop;
}
}
@@ -94,16 +99,16 @@ export class Viewport implements IViewport {
* terminal to scroll to it.
* @param ev The scroll event.
*/
private onScroll(ev: Event): void {
private _onScroll(ev: Event): void {
// Don't attempt to scroll if the element is not visible, otherwise scrollTop will be corrupt
// which causes the terminal to scroll the buffer to the top
if (!this.viewportElement.offsetParent) {
if (!this._viewportElement.offsetParent) {
return;
}
const newRow = Math.round(this.viewportElement.scrollTop / this.currentRowHeight);
const diff = newRow - this.terminal.buffer.ydisp;
this.terminal.scrollLines(diff, true);
const newRow = Math.round(this._viewportElement.scrollTop / this._currentRowHeight);
const diff = newRow - this._terminal.buffer.ydisp;
this._terminal.scrollLines(diff, true);
}
/**
@@ -113,28 +118,61 @@ export class Viewport implements IViewport {
* @param ev The mouse wheel event.
*/
public onWheel(ev: WheelEvent): void {
if (ev.deltaY === 0) {
// Do nothing if it's not a vertical scroll event
const amount = this._getPixelsScrolled(ev);
if (amount === 0) {
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;
this._viewportElement.scrollTop += amount;
// Prevent the page from scrolling when the terminal scrolls
ev.preventDefault();
}
private _getPixelsScrolled(ev: WheelEvent): number {
// Do nothing if it's not a vertical scroll event
if (ev.deltaY === 0) {
return 0;
}
// Fallback to WheelEvent.DOM_DELTA_PIXEL
let amount = ev.deltaY;
if (ev.deltaMode === WheelEvent.DOM_DELTA_LINE) {
amount *= this._currentRowHeight;
} else if (ev.deltaMode === WheelEvent.DOM_DELTA_PAGE) {
amount *= this._currentRowHeight * this._terminal.rows;
}
return amount;
}
/**
* Gets the number of pixels scrolled by the mouse event taking into account what type of delta
* is being used.
* @param ev The mouse wheel event.
*/
public getLinesScrolled(ev: WheelEvent): number {
// Do nothing if it's not a vertical scroll event
if (ev.deltaY === 0) {
return 0;
}
// Fallback to WheelEvent.DOM_DELTA_LINE
let amount = ev.deltaY;
if (ev.deltaMode === WheelEvent.DOM_DELTA_PIXEL) {
amount /= this._currentRowHeight + 0.0; // Prevent integer division
this._wheelPartialScroll += amount;
amount = Math.floor(Math.abs(this._wheelPartialScroll)) * (this._wheelPartialScroll > 0 ? 1 : -1);
this._wheelPartialScroll %= 1;
} else if (ev.deltaMode === WheelEvent.DOM_DELTA_PAGE) {
amount *= this._terminal.rows;
}
return amount;
}
/**
* Handles the touchstart event, recording the touch occurred.
* @param ev The touch event.
*/
public onTouchStart(ev: TouchEvent): void {
this.lastTouchY = ev.touches[0].pageY;
this._lastTouchY = ev.touches[0].pageY;
}
/**
@@ -142,12 +180,12 @@ export class Viewport implements IViewport {
* @param ev The touch event.
*/
public onTouchMove(ev: TouchEvent): void {
let deltaY = this.lastTouchY - ev.touches[0].pageY;
this.lastTouchY = ev.touches[0].pageY;
let deltaY = this._lastTouchY - ev.touches[0].pageY;
this._lastTouchY = ev.touches[0].pageY;
if (deltaY === 0) {
return;
}
this.viewportElement.scrollTop += deltaY;
this._viewportElement.scrollTop += deltaY;
ev.preventDefault();
}
}
+32 -12
View File
@@ -42,25 +42,45 @@ export function attach(term: Terminal, socket: WebSocket, bidirectional: boolean
addonTerminal.__getMessage = function(ev: MessageEvent): void {
let str;
if (typeof ev.data === 'object') {
if (ev.data instanceof ArrayBuffer) {
if (!myTextDecoder) {
myTextDecoder = new TextDecoder();
}
str = myTextDecoder.decode( ev.data );
} else {
throw 'TODO: handle Blob?';
if (typeof ev.data == 'object') {
if (!myTextDecoder) {
myTextDecoder = new TextDecoder();
}
}
if (ev.data instanceof ArrayBuffer) {
str = myTextDecoder.decode(ev.data);
displayData(str);
} else {
let fileReader = new FileReader();
if (buffered) {
addonTerminal.__pushToBuffer(str || ev.data);
fileReader.addEventListener('load', function() {
str = myTextDecoder.decode(this.result);
displayData(str);
});
fileReader.readAsArrayBuffer(ev.data);
}
} else if (typeof ev.data == 'string') {
displayData(ev.data)
} else {
addonTerminal.write(str || ev.data);
throw Error(`Cannot handle "${typeof ev.data}" websocket message.`);
}
};
/**
* Push data to buffer or write it in the terminal.
* This is used as a callback for FileReader.onload.
*
* @param str String decoded by FileReader.
* @param data The data of the EventMessage.
*/
function displayData(str?: string, data?: string) {
if (buffered) {
addonTerminal.__pushToBuffer(str || data);
} else {
addonTerminal.write(str || data);
}
}
addonTerminal.__sendData = (data: string) => {
if (socket.readyState !== 1) {
return;
-3
View File
@@ -71,9 +71,6 @@ export class AltClickHandler {
* positioning.
*/
private _resetStartingRow(): string {
let startRow = this._endRow - this._wrappedRowsForRow(this._endRow);
let endRow = this._endRow;
if (this._moveToRequestedRow().length === 0) {
return '';
} else {
+12 -7
View File
@@ -4,17 +4,22 @@
*/
import { assert } from 'chai';
import * as Terminal from '../Terminal';
import * as Clipboard from './Clipboard';
describe('evaluatePastedTextProcessing', () => {
it('should replace carriage return + line feed with line feed on windows', () => {
const pastedText = 'foo\r\nbar\r\n';
const processedText = Clipboard.prepareTextForTerminal(pastedText, false);
const windowsProcessedText = Clipboard.prepareTextForTerminal(pastedText, true);
it('should replace carriage return and/or line feed with carriage return', () => {
const pastedText = {
unix: 'foo\nbar\n',
windows: 'foo\r\nbar\r\n'
};
assert.equal(processedText, 'foo\r\nbar\r\n');
assert.equal(windowsProcessedText, 'foo\rbar\r');
const processedText = {
unix: Clipboard.prepareTextForTerminal(pastedText.unix),
windows: Clipboard.prepareTextForTerminal(pastedText.windows)
};
assert.equal(processedText.unix, 'foo\rbar\r');
assert.equal(processedText.windows, 'foo\rbar\r');
});
it('should bracket pasted text in bracketedPasteMode', () => {
const pastedText = 'foo bar';
+3 -6
View File
@@ -18,11 +18,8 @@ declare var window: IWindow;
* Prepares text to be pasted into the terminal by normalizing the line endings
* @param text The pasted text that needs processing before inserting into the terminal
*/
export function prepareTextForTerminal(text: string, isMSWindows: boolean): string {
if (isMSWindows) {
return text.replace(/\r?\n/g, '\r');
}
return text;
export function prepareTextForTerminal(text: string): string {
return text.replace(/\r?\n/g, '\r');
}
/**
@@ -62,7 +59,7 @@ export function pasteHandler(ev: ClipboardEvent, term: ITerminal): void {
let text: string;
let dispatchPaste = function(text: string): void {
text = prepareTextForTerminal(text, term.browser.isMSWindows);
text = prepareTextForTerminal(text);
text = bracketTextForPaste(text, term.bracketedPasteMode);
term.handler(text);
term.textarea.value = '';
+19 -4
View File
@@ -59,7 +59,9 @@ export class MouseZoneManager implements IMouseZoneManager {
// Iterate through zones and clear them out if they're within the range
for (let i = 0; i < this._zones.length; i++) {
const zone = this._zones[i];
if (zone.y > start && zone.y <= end + 1) {
if ((zone.y1 > start && zone.y1 <= end + 1) ||
(zone.y2 > start && zone.y2 <= end + 1) ||
(zone.y1 < start && zone.y2 > end + 1)) {
if (this._currentZone && this._currentZone === zone) {
this._currentZone.leaveCallback();
this._currentZone = null;
@@ -173,10 +175,22 @@ export class MouseZoneManager implements IMouseZoneManager {
if (!coords) {
return null;
}
const x = coords[0];
const y = coords[1];
for (let i = 0; i < this._zones.length; i++) {
const zone = this._zones[i];
if (zone.y === coords[1] && zone.x1 <= coords[0] && zone.x2 > coords[0]) {
return zone;
if (zone.y1 === zone.y2) {
// Single line link
if (y === zone.y1 && x >= zone.x1 && x < zone.x2) {
return zone;
}
} else {
// Multi-line link
if ((y === zone.y1 && x >= zone.x1) ||
(y === zone.y2 && x < zone.x2) ||
(y > zone.y1 && y < zone.y2)) {
return zone;
}
}
}
return null;
@@ -186,8 +200,9 @@ export class MouseZoneManager implements IMouseZoneManager {
export class MouseZone implements IMouseZone {
constructor(
public x1: number,
public y1: number,
public x2: number,
public y: number,
public y2: number,
public clickCallback: (e: MouseEvent) => any,
public hoverCallback: (e: MouseEvent) => any,
public tooltipCallback: (e: MouseEvent) => any,
+2 -1
View File
@@ -11,7 +11,8 @@ export interface IMouseZoneManager {
export interface IMouseZone {
x1: number;
x2: number;
y: number;
y1: number;
y2: number;
clickCallback: (e: MouseEvent) => any;
hoverCallback: (e: MouseEvent) => any | undefined;
tooltipCallback: (e: MouseEvent) => any | undefined;
+12 -15
View File
@@ -4,12 +4,11 @@
*/
import { IRenderLayer, IColorSet, IRenderDimensions } from './Types';
import { CharData, ITerminal, ITerminalOptions } from '../Types';
import { acquireCharAtlas, CHAR_ATLAS_CELL_SPACING } from './CharAtlas';
import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from '../Buffer';
export const INVERTED_DEFAULT_COLOR = -1;
const DIM_OPACITY = 0.5;
import { CharData, ITerminal } from '../Types';
import { DIM_OPACITY, INVERTED_DEFAULT_COLOR } from './atlas/Types';
import { CHAR_ATLAS_CELL_SPACING } from '../shared/atlas/Types';
import { acquireCharAtlas } from './atlas/CharAtlas';
import { CHAR_DATA_CHAR_INDEX } from '../Buffer';
export abstract class BaseRenderLayer implements IRenderLayer {
private _canvas: HTMLCanvasElement;
@@ -93,7 +92,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
}
}
public resize(terminal: ITerminal, dim: IRenderDimensions, charSizeChanged: boolean): void {
public resize(terminal: ITerminal, dim: IRenderDimensions): void {
this._scaledCellWidth = dim.scaledCellWidth;
this._scaledCellHeight = dim.scaledCellHeight;
this._scaledCharWidth = dim.scaledCharWidth;
@@ -110,9 +109,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
this.clearAll();
}
if (charSizeChanged) {
this._refreshCharAtlas(terminal, this._colors);
}
this._refreshCharAtlas(terminal, this._colors);
}
public abstract reset(terminal: ITerminal): void;
@@ -182,7 +179,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
if (this._alpha) {
this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);
} else {
this._ctx.fillStyle = this._colors.background;
this._ctx.fillStyle = this._colors.background.css;
this._ctx.fillRect(0, 0, this._canvas.width, this._canvas.height);
}
}
@@ -202,7 +199,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
width * this._scaledCellWidth,
height * this._scaledCellHeight);
} else {
this._ctx.fillStyle = this._colors.background;
this._ctx.fillStyle = this._colors.background.css;
this._ctx.fillRect(
x * this._scaledCellWidth,
y * this._scaledCellHeight,
@@ -314,12 +311,12 @@ export abstract class BaseRenderLayer implements IRenderLayer {
this._ctx.textBaseline = 'top';
if (fg === INVERTED_DEFAULT_COLOR) {
this._ctx.fillStyle = this._colors.background;
this._ctx.fillStyle = this._colors.background.css;
} else if (fg < 256) {
// 256 color support
this._ctx.fillStyle = this._colors.ansi[fg];
this._ctx.fillStyle = this._colors.ansi[fg].css;
} else {
this._ctx.fillStyle = this._colors.foreground;
this._ctx.fillStyle = this._colors.foreground.css;
}
this._clipRow(terminal, y);
-136
View File
@@ -1,136 +0,0 @@
/**
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { ITerminal } from '../Types';
import { IColorSet } from './Types';
import { isFirefox } from '../shared/utils/Browser';
import { generateCharAtlas, ICharAtlasRequest } from '../shared/CharAtlasGenerator';
export const CHAR_ATLAS_CELL_SPACING = 1;
interface ICharAtlasConfig {
fontSize: number;
fontFamily: string;
fontWeight: string;
fontWeightBold: string;
scaledCharWidth: number;
scaledCharHeight: number;
allowTransparency: boolean;
colors: IColorSet;
}
interface ICharAtlasCacheEntry {
bitmap: HTMLCanvasElement | Promise<ImageBitmap>;
config: ICharAtlasConfig;
ownedBy: ITerminal[];
}
let charAtlasCache: ICharAtlasCacheEntry[] = [];
/**
* Acquires a char atlas, either generating a new one or returning an existing
* one that is in use by another terminal.
* @param terminal The terminal.
* @param colors The colors to use.
*/
export function acquireCharAtlas(terminal: ITerminal, colors: IColorSet, scaledCharWidth: number, scaledCharHeight: number): HTMLCanvasElement | Promise<ImageBitmap> {
const newConfig = generateConfig(scaledCharWidth, scaledCharHeight, terminal, colors);
// Check to see if the terminal already owns this config
for (let i = 0; i < charAtlasCache.length; i++) {
const entry = charAtlasCache[i];
const ownedByIndex = entry.ownedBy.indexOf(terminal);
if (ownedByIndex >= 0) {
if (configEquals(entry.config, newConfig)) {
return entry.bitmap;
} else {
// The configs differ, release the terminal from the entry
if (entry.ownedBy.length === 1) {
charAtlasCache.splice(i, 1);
} else {
entry.ownedBy.splice(ownedByIndex, 1);
}
break;
}
}
}
// Try match a char atlas from the cache
for (let i = 0; i < charAtlasCache.length; i++) {
const entry = charAtlasCache[i];
if (configEquals(entry.config, newConfig)) {
// Add the terminal to the cache entry and return
entry.ownedBy.push(terminal);
return entry.bitmap;
}
}
const canvasFactory = (width: number, height: number) => {
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
return canvas;
};
const charAtlasConfig: ICharAtlasRequest = {
scaledCharWidth,
scaledCharHeight,
fontSize: terminal.options.fontSize,
fontFamily: terminal.options.fontFamily,
fontWeight: terminal.options.fontWeight,
fontWeightBold: terminal.options.fontWeightBold,
background: colors.background,
foreground: colors.foreground,
ansiColors: colors.ansi,
devicePixelRatio: window.devicePixelRatio,
allowTransparency: terminal.options.allowTransparency
};
const newEntry: ICharAtlasCacheEntry = {
bitmap: generateCharAtlas(window, canvasFactory, charAtlasConfig),
config: newConfig,
ownedBy: [terminal]
};
charAtlasCache.push(newEntry);
return newEntry.bitmap;
}
function generateConfig(scaledCharWidth: number, scaledCharHeight: number, terminal: ITerminal, colors: IColorSet): ICharAtlasConfig {
const clonedColors = {
foreground: colors.foreground,
background: colors.background,
cursor: null,
cursorAccent: null,
selection: null,
ansi: colors.ansi.slice(0, 16)
};
return {
scaledCharWidth,
scaledCharHeight,
fontFamily: terminal.options.fontFamily,
fontSize: terminal.options.fontSize,
fontWeight: terminal.options.fontWeight,
fontWeightBold: terminal.options.fontWeightBold,
allowTransparency: terminal.options.allowTransparency,
colors: clonedColors
};
}
function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean {
for (let i = 0; i < a.colors.ansi.length; i++) {
if (a.colors.ansi[i] !== b.colors.ansi[i]) {
return false;
}
}
return a.fontFamily === b.fontFamily &&
a.fontSize === b.fontSize &&
a.fontWeight === b.fontWeight &&
a.fontWeightBold === b.fontWeightBold &&
a.allowTransparency === b.allowTransparency &&
a.scaledCharWidth === b.scaledCharWidth &&
a.scaledCharHeight === b.scaledCharHeight &&
a.colors.foreground === b.colors.foreground &&
a.colors.background === b.colors.background;
}
+268 -250
View File
@@ -3,268 +3,286 @@
* @license MIT
*/
import jsdom = require('jsdom');
import { assert } from 'chai';
import { ColorManager } from './ColorManager';
describe('ColorManager', () => {
let cm: ColorManager;
let dom: jsdom.JSDOM;
let document: Document;
let window: Window;
beforeEach(() => {
cm = new ColorManager();
dom = new jsdom.JSDOM('');
window = dom.window;
document = window.document;
(<any>window).HTMLCanvasElement.prototype.getContext = () => ({
createLinearGradient(): any {
return null;
},
fillRect(): void { },
getImageData(): any {
return {data: [0, 0, 0, 0xFF]};
}
});
cm = new ColorManager(document, false);
});
describe('constructor', () => {
it('should fill all colors with values', () => {
for (let key in cm.colors) {
if (typeof key === 'string') {
for (let key of Object.keys(cm.colors)) {
if (key !== 'ansi') {
// A #rrggbb or rgba(...)
assert.ok(cm.colors[key].length >= 7);
assert.ok(cm.colors[key].css.length >= 7);
}
}
assert.equal(cm.colors.ansi.length, 256);
});
it('should fill 240 colors with expected values', () => {
assert.equal(cm.colors.ansi[16], '#000000');
assert.equal(cm.colors.ansi[17], '#00005f');
assert.equal(cm.colors.ansi[18], '#000087');
assert.equal(cm.colors.ansi[19], '#0000af');
assert.equal(cm.colors.ansi[20], '#0000d7');
assert.equal(cm.colors.ansi[21], '#0000ff');
assert.equal(cm.colors.ansi[22], '#005f00');
assert.equal(cm.colors.ansi[23], '#005f5f');
assert.equal(cm.colors.ansi[24], '#005f87');
assert.equal(cm.colors.ansi[25], '#005faf');
assert.equal(cm.colors.ansi[26], '#005fd7');
assert.equal(cm.colors.ansi[27], '#005fff');
assert.equal(cm.colors.ansi[28], '#008700');
assert.equal(cm.colors.ansi[29], '#00875f');
assert.equal(cm.colors.ansi[30], '#008787');
assert.equal(cm.colors.ansi[31], '#0087af');
assert.equal(cm.colors.ansi[32], '#0087d7');
assert.equal(cm.colors.ansi[33], '#0087ff');
assert.equal(cm.colors.ansi[34], '#00af00');
assert.equal(cm.colors.ansi[35], '#00af5f');
assert.equal(cm.colors.ansi[36], '#00af87');
assert.equal(cm.colors.ansi[37], '#00afaf');
assert.equal(cm.colors.ansi[38], '#00afd7');
assert.equal(cm.colors.ansi[39], '#00afff');
assert.equal(cm.colors.ansi[40], '#00d700');
assert.equal(cm.colors.ansi[41], '#00d75f');
assert.equal(cm.colors.ansi[42], '#00d787');
assert.equal(cm.colors.ansi[43], '#00d7af');
assert.equal(cm.colors.ansi[44], '#00d7d7');
assert.equal(cm.colors.ansi[45], '#00d7ff');
assert.equal(cm.colors.ansi[46], '#00ff00');
assert.equal(cm.colors.ansi[47], '#00ff5f');
assert.equal(cm.colors.ansi[48], '#00ff87');
assert.equal(cm.colors.ansi[49], '#00ffaf');
assert.equal(cm.colors.ansi[50], '#00ffd7');
assert.equal(cm.colors.ansi[51], '#00ffff');
assert.equal(cm.colors.ansi[52], '#5f0000');
assert.equal(cm.colors.ansi[53], '#5f005f');
assert.equal(cm.colors.ansi[54], '#5f0087');
assert.equal(cm.colors.ansi[55], '#5f00af');
assert.equal(cm.colors.ansi[56], '#5f00d7');
assert.equal(cm.colors.ansi[57], '#5f00ff');
assert.equal(cm.colors.ansi[58], '#5f5f00');
assert.equal(cm.colors.ansi[59], '#5f5f5f');
assert.equal(cm.colors.ansi[60], '#5f5f87');
assert.equal(cm.colors.ansi[61], '#5f5faf');
assert.equal(cm.colors.ansi[62], '#5f5fd7');
assert.equal(cm.colors.ansi[63], '#5f5fff');
assert.equal(cm.colors.ansi[64], '#5f8700');
assert.equal(cm.colors.ansi[65], '#5f875f');
assert.equal(cm.colors.ansi[66], '#5f8787');
assert.equal(cm.colors.ansi[67], '#5f87af');
assert.equal(cm.colors.ansi[68], '#5f87d7');
assert.equal(cm.colors.ansi[69], '#5f87ff');
assert.equal(cm.colors.ansi[70], '#5faf00');
assert.equal(cm.colors.ansi[71], '#5faf5f');
assert.equal(cm.colors.ansi[72], '#5faf87');
assert.equal(cm.colors.ansi[73], '#5fafaf');
assert.equal(cm.colors.ansi[74], '#5fafd7');
assert.equal(cm.colors.ansi[75], '#5fafff');
assert.equal(cm.colors.ansi[76], '#5fd700');
assert.equal(cm.colors.ansi[77], '#5fd75f');
assert.equal(cm.colors.ansi[78], '#5fd787');
assert.equal(cm.colors.ansi[79], '#5fd7af');
assert.equal(cm.colors.ansi[80], '#5fd7d7');
assert.equal(cm.colors.ansi[81], '#5fd7ff');
assert.equal(cm.colors.ansi[82], '#5fff00');
assert.equal(cm.colors.ansi[83], '#5fff5f');
assert.equal(cm.colors.ansi[84], '#5fff87');
assert.equal(cm.colors.ansi[85], '#5fffaf');
assert.equal(cm.colors.ansi[86], '#5fffd7');
assert.equal(cm.colors.ansi[87], '#5fffff');
assert.equal(cm.colors.ansi[88], '#870000');
assert.equal(cm.colors.ansi[89], '#87005f');
assert.equal(cm.colors.ansi[90], '#870087');
assert.equal(cm.colors.ansi[91], '#8700af');
assert.equal(cm.colors.ansi[92], '#8700d7');
assert.equal(cm.colors.ansi[93], '#8700ff');
assert.equal(cm.colors.ansi[94], '#875f00');
assert.equal(cm.colors.ansi[95], '#875f5f');
assert.equal(cm.colors.ansi[96], '#875f87');
assert.equal(cm.colors.ansi[97], '#875faf');
assert.equal(cm.colors.ansi[98], '#875fd7');
assert.equal(cm.colors.ansi[99], '#875fff');
assert.equal(cm.colors.ansi[100], '#878700');
assert.equal(cm.colors.ansi[101], '#87875f');
assert.equal(cm.colors.ansi[102], '#878787');
assert.equal(cm.colors.ansi[103], '#8787af');
assert.equal(cm.colors.ansi[104], '#8787d7');
assert.equal(cm.colors.ansi[105], '#8787ff');
assert.equal(cm.colors.ansi[106], '#87af00');
assert.equal(cm.colors.ansi[107], '#87af5f');
assert.equal(cm.colors.ansi[108], '#87af87');
assert.equal(cm.colors.ansi[109], '#87afaf');
assert.equal(cm.colors.ansi[110], '#87afd7');
assert.equal(cm.colors.ansi[111], '#87afff');
assert.equal(cm.colors.ansi[112], '#87d700');
assert.equal(cm.colors.ansi[113], '#87d75f');
assert.equal(cm.colors.ansi[114], '#87d787');
assert.equal(cm.colors.ansi[115], '#87d7af');
assert.equal(cm.colors.ansi[116], '#87d7d7');
assert.equal(cm.colors.ansi[117], '#87d7ff');
assert.equal(cm.colors.ansi[118], '#87ff00');
assert.equal(cm.colors.ansi[119], '#87ff5f');
assert.equal(cm.colors.ansi[120], '#87ff87');
assert.equal(cm.colors.ansi[121], '#87ffaf');
assert.equal(cm.colors.ansi[122], '#87ffd7');
assert.equal(cm.colors.ansi[123], '#87ffff');
assert.equal(cm.colors.ansi[124], '#af0000');
assert.equal(cm.colors.ansi[125], '#af005f');
assert.equal(cm.colors.ansi[126], '#af0087');
assert.equal(cm.colors.ansi[127], '#af00af');
assert.equal(cm.colors.ansi[128], '#af00d7');
assert.equal(cm.colors.ansi[129], '#af00ff');
assert.equal(cm.colors.ansi[130], '#af5f00');
assert.equal(cm.colors.ansi[131], '#af5f5f');
assert.equal(cm.colors.ansi[132], '#af5f87');
assert.equal(cm.colors.ansi[133], '#af5faf');
assert.equal(cm.colors.ansi[134], '#af5fd7');
assert.equal(cm.colors.ansi[135], '#af5fff');
assert.equal(cm.colors.ansi[136], '#af8700');
assert.equal(cm.colors.ansi[137], '#af875f');
assert.equal(cm.colors.ansi[138], '#af8787');
assert.equal(cm.colors.ansi[139], '#af87af');
assert.equal(cm.colors.ansi[140], '#af87d7');
assert.equal(cm.colors.ansi[141], '#af87ff');
assert.equal(cm.colors.ansi[142], '#afaf00');
assert.equal(cm.colors.ansi[143], '#afaf5f');
assert.equal(cm.colors.ansi[144], '#afaf87');
assert.equal(cm.colors.ansi[145], '#afafaf');
assert.equal(cm.colors.ansi[146], '#afafd7');
assert.equal(cm.colors.ansi[147], '#afafff');
assert.equal(cm.colors.ansi[148], '#afd700');
assert.equal(cm.colors.ansi[149], '#afd75f');
assert.equal(cm.colors.ansi[150], '#afd787');
assert.equal(cm.colors.ansi[151], '#afd7af');
assert.equal(cm.colors.ansi[152], '#afd7d7');
assert.equal(cm.colors.ansi[153], '#afd7ff');
assert.equal(cm.colors.ansi[154], '#afff00');
assert.equal(cm.colors.ansi[155], '#afff5f');
assert.equal(cm.colors.ansi[156], '#afff87');
assert.equal(cm.colors.ansi[157], '#afffaf');
assert.equal(cm.colors.ansi[158], '#afffd7');
assert.equal(cm.colors.ansi[159], '#afffff');
assert.equal(cm.colors.ansi[160], '#d70000');
assert.equal(cm.colors.ansi[161], '#d7005f');
assert.equal(cm.colors.ansi[162], '#d70087');
assert.equal(cm.colors.ansi[163], '#d700af');
assert.equal(cm.colors.ansi[164], '#d700d7');
assert.equal(cm.colors.ansi[165], '#d700ff');
assert.equal(cm.colors.ansi[166], '#d75f00');
assert.equal(cm.colors.ansi[167], '#d75f5f');
assert.equal(cm.colors.ansi[168], '#d75f87');
assert.equal(cm.colors.ansi[169], '#d75faf');
assert.equal(cm.colors.ansi[170], '#d75fd7');
assert.equal(cm.colors.ansi[171], '#d75fff');
assert.equal(cm.colors.ansi[172], '#d78700');
assert.equal(cm.colors.ansi[173], '#d7875f');
assert.equal(cm.colors.ansi[174], '#d78787');
assert.equal(cm.colors.ansi[175], '#d787af');
assert.equal(cm.colors.ansi[176], '#d787d7');
assert.equal(cm.colors.ansi[177], '#d787ff');
assert.equal(cm.colors.ansi[178], '#d7af00');
assert.equal(cm.colors.ansi[179], '#d7af5f');
assert.equal(cm.colors.ansi[180], '#d7af87');
assert.equal(cm.colors.ansi[181], '#d7afaf');
assert.equal(cm.colors.ansi[182], '#d7afd7');
assert.equal(cm.colors.ansi[183], '#d7afff');
assert.equal(cm.colors.ansi[184], '#d7d700');
assert.equal(cm.colors.ansi[185], '#d7d75f');
assert.equal(cm.colors.ansi[186], '#d7d787');
assert.equal(cm.colors.ansi[187], '#d7d7af');
assert.equal(cm.colors.ansi[188], '#d7d7d7');
assert.equal(cm.colors.ansi[189], '#d7d7ff');
assert.equal(cm.colors.ansi[190], '#d7ff00');
assert.equal(cm.colors.ansi[191], '#d7ff5f');
assert.equal(cm.colors.ansi[192], '#d7ff87');
assert.equal(cm.colors.ansi[193], '#d7ffaf');
assert.equal(cm.colors.ansi[194], '#d7ffd7');
assert.equal(cm.colors.ansi[195], '#d7ffff');
assert.equal(cm.colors.ansi[196], '#ff0000');
assert.equal(cm.colors.ansi[197], '#ff005f');
assert.equal(cm.colors.ansi[198], '#ff0087');
assert.equal(cm.colors.ansi[199], '#ff00af');
assert.equal(cm.colors.ansi[200], '#ff00d7');
assert.equal(cm.colors.ansi[201], '#ff00ff');
assert.equal(cm.colors.ansi[202], '#ff5f00');
assert.equal(cm.colors.ansi[203], '#ff5f5f');
assert.equal(cm.colors.ansi[204], '#ff5f87');
assert.equal(cm.colors.ansi[205], '#ff5faf');
assert.equal(cm.colors.ansi[206], '#ff5fd7');
assert.equal(cm.colors.ansi[207], '#ff5fff');
assert.equal(cm.colors.ansi[208], '#ff8700');
assert.equal(cm.colors.ansi[209], '#ff875f');
assert.equal(cm.colors.ansi[210], '#ff8787');
assert.equal(cm.colors.ansi[211], '#ff87af');
assert.equal(cm.colors.ansi[212], '#ff87d7');
assert.equal(cm.colors.ansi[213], '#ff87ff');
assert.equal(cm.colors.ansi[214], '#ffaf00');
assert.equal(cm.colors.ansi[215], '#ffaf5f');
assert.equal(cm.colors.ansi[216], '#ffaf87');
assert.equal(cm.colors.ansi[217], '#ffafaf');
assert.equal(cm.colors.ansi[218], '#ffafd7');
assert.equal(cm.colors.ansi[219], '#ffafff');
assert.equal(cm.colors.ansi[220], '#ffd700');
assert.equal(cm.colors.ansi[221], '#ffd75f');
assert.equal(cm.colors.ansi[222], '#ffd787');
assert.equal(cm.colors.ansi[223], '#ffd7af');
assert.equal(cm.colors.ansi[224], '#ffd7d7');
assert.equal(cm.colors.ansi[225], '#ffd7ff');
assert.equal(cm.colors.ansi[226], '#ffff00');
assert.equal(cm.colors.ansi[227], '#ffff5f');
assert.equal(cm.colors.ansi[228], '#ffff87');
assert.equal(cm.colors.ansi[229], '#ffffaf');
assert.equal(cm.colors.ansi[230], '#ffffd7');
assert.equal(cm.colors.ansi[231], '#ffffff');
assert.equal(cm.colors.ansi[232], '#080808');
assert.equal(cm.colors.ansi[233], '#121212');
assert.equal(cm.colors.ansi[234], '#1c1c1c');
assert.equal(cm.colors.ansi[235], '#262626');
assert.equal(cm.colors.ansi[236], '#303030');
assert.equal(cm.colors.ansi[237], '#3a3a3a');
assert.equal(cm.colors.ansi[238], '#444444');
assert.equal(cm.colors.ansi[239], '#4e4e4e');
assert.equal(cm.colors.ansi[240], '#585858');
assert.equal(cm.colors.ansi[241], '#626262');
assert.equal(cm.colors.ansi[242], '#6c6c6c');
assert.equal(cm.colors.ansi[243], '#767676');
assert.equal(cm.colors.ansi[244], '#808080');
assert.equal(cm.colors.ansi[245], '#8a8a8a');
assert.equal(cm.colors.ansi[246], '#949494');
assert.equal(cm.colors.ansi[247], '#9e9e9e');
assert.equal(cm.colors.ansi[248], '#a8a8a8');
assert.equal(cm.colors.ansi[249], '#b2b2b2');
assert.equal(cm.colors.ansi[250], '#bcbcbc');
assert.equal(cm.colors.ansi[251], '#c6c6c6');
assert.equal(cm.colors.ansi[252], '#d0d0d0');
assert.equal(cm.colors.ansi[253], '#dadada');
assert.equal(cm.colors.ansi[254], '#e4e4e4');
assert.equal(cm.colors.ansi[255], '#eeeeee');
assert.equal(cm.colors.ansi[16].css, '#000000');
assert.equal(cm.colors.ansi[17].css, '#00005f');
assert.equal(cm.colors.ansi[18].css, '#000087');
assert.equal(cm.colors.ansi[19].css, '#0000af');
assert.equal(cm.colors.ansi[20].css, '#0000d7');
assert.equal(cm.colors.ansi[21].css, '#0000ff');
assert.equal(cm.colors.ansi[22].css, '#005f00');
assert.equal(cm.colors.ansi[23].css, '#005f5f');
assert.equal(cm.colors.ansi[24].css, '#005f87');
assert.equal(cm.colors.ansi[25].css, '#005faf');
assert.equal(cm.colors.ansi[26].css, '#005fd7');
assert.equal(cm.colors.ansi[27].css, '#005fff');
assert.equal(cm.colors.ansi[28].css, '#008700');
assert.equal(cm.colors.ansi[29].css, '#00875f');
assert.equal(cm.colors.ansi[30].css, '#008787');
assert.equal(cm.colors.ansi[31].css, '#0087af');
assert.equal(cm.colors.ansi[32].css, '#0087d7');
assert.equal(cm.colors.ansi[33].css, '#0087ff');
assert.equal(cm.colors.ansi[34].css, '#00af00');
assert.equal(cm.colors.ansi[35].css, '#00af5f');
assert.equal(cm.colors.ansi[36].css, '#00af87');
assert.equal(cm.colors.ansi[37].css, '#00afaf');
assert.equal(cm.colors.ansi[38].css, '#00afd7');
assert.equal(cm.colors.ansi[39].css, '#00afff');
assert.equal(cm.colors.ansi[40].css, '#00d700');
assert.equal(cm.colors.ansi[41].css, '#00d75f');
assert.equal(cm.colors.ansi[42].css, '#00d787');
assert.equal(cm.colors.ansi[43].css, '#00d7af');
assert.equal(cm.colors.ansi[44].css, '#00d7d7');
assert.equal(cm.colors.ansi[45].css, '#00d7ff');
assert.equal(cm.colors.ansi[46].css, '#00ff00');
assert.equal(cm.colors.ansi[47].css, '#00ff5f');
assert.equal(cm.colors.ansi[48].css, '#00ff87');
assert.equal(cm.colors.ansi[49].css, '#00ffaf');
assert.equal(cm.colors.ansi[50].css, '#00ffd7');
assert.equal(cm.colors.ansi[51].css, '#00ffff');
assert.equal(cm.colors.ansi[52].css, '#5f0000');
assert.equal(cm.colors.ansi[53].css, '#5f005f');
assert.equal(cm.colors.ansi[54].css, '#5f0087');
assert.equal(cm.colors.ansi[55].css, '#5f00af');
assert.equal(cm.colors.ansi[56].css, '#5f00d7');
assert.equal(cm.colors.ansi[57].css, '#5f00ff');
assert.equal(cm.colors.ansi[58].css, '#5f5f00');
assert.equal(cm.colors.ansi[59].css, '#5f5f5f');
assert.equal(cm.colors.ansi[60].css, '#5f5f87');
assert.equal(cm.colors.ansi[61].css, '#5f5faf');
assert.equal(cm.colors.ansi[62].css, '#5f5fd7');
assert.equal(cm.colors.ansi[63].css, '#5f5fff');
assert.equal(cm.colors.ansi[64].css, '#5f8700');
assert.equal(cm.colors.ansi[65].css, '#5f875f');
assert.equal(cm.colors.ansi[66].css, '#5f8787');
assert.equal(cm.colors.ansi[67].css, '#5f87af');
assert.equal(cm.colors.ansi[68].css, '#5f87d7');
assert.equal(cm.colors.ansi[69].css, '#5f87ff');
assert.equal(cm.colors.ansi[70].css, '#5faf00');
assert.equal(cm.colors.ansi[71].css, '#5faf5f');
assert.equal(cm.colors.ansi[72].css, '#5faf87');
assert.equal(cm.colors.ansi[73].css, '#5fafaf');
assert.equal(cm.colors.ansi[74].css, '#5fafd7');
assert.equal(cm.colors.ansi[75].css, '#5fafff');
assert.equal(cm.colors.ansi[76].css, '#5fd700');
assert.equal(cm.colors.ansi[77].css, '#5fd75f');
assert.equal(cm.colors.ansi[78].css, '#5fd787');
assert.equal(cm.colors.ansi[79].css, '#5fd7af');
assert.equal(cm.colors.ansi[80].css, '#5fd7d7');
assert.equal(cm.colors.ansi[81].css, '#5fd7ff');
assert.equal(cm.colors.ansi[82].css, '#5fff00');
assert.equal(cm.colors.ansi[83].css, '#5fff5f');
assert.equal(cm.colors.ansi[84].css, '#5fff87');
assert.equal(cm.colors.ansi[85].css, '#5fffaf');
assert.equal(cm.colors.ansi[86].css, '#5fffd7');
assert.equal(cm.colors.ansi[87].css, '#5fffff');
assert.equal(cm.colors.ansi[88].css, '#870000');
assert.equal(cm.colors.ansi[89].css, '#87005f');
assert.equal(cm.colors.ansi[90].css, '#870087');
assert.equal(cm.colors.ansi[91].css, '#8700af');
assert.equal(cm.colors.ansi[92].css, '#8700d7');
assert.equal(cm.colors.ansi[93].css, '#8700ff');
assert.equal(cm.colors.ansi[94].css, '#875f00');
assert.equal(cm.colors.ansi[95].css, '#875f5f');
assert.equal(cm.colors.ansi[96].css, '#875f87');
assert.equal(cm.colors.ansi[97].css, '#875faf');
assert.equal(cm.colors.ansi[98].css, '#875fd7');
assert.equal(cm.colors.ansi[99].css, '#875fff');
assert.equal(cm.colors.ansi[100].css, '#878700');
assert.equal(cm.colors.ansi[101].css, '#87875f');
assert.equal(cm.colors.ansi[102].css, '#878787');
assert.equal(cm.colors.ansi[103].css, '#8787af');
assert.equal(cm.colors.ansi[104].css, '#8787d7');
assert.equal(cm.colors.ansi[105].css, '#8787ff');
assert.equal(cm.colors.ansi[106].css, '#87af00');
assert.equal(cm.colors.ansi[107].css, '#87af5f');
assert.equal(cm.colors.ansi[108].css, '#87af87');
assert.equal(cm.colors.ansi[109].css, '#87afaf');
assert.equal(cm.colors.ansi[110].css, '#87afd7');
assert.equal(cm.colors.ansi[111].css, '#87afff');
assert.equal(cm.colors.ansi[112].css, '#87d700');
assert.equal(cm.colors.ansi[113].css, '#87d75f');
assert.equal(cm.colors.ansi[114].css, '#87d787');
assert.equal(cm.colors.ansi[115].css, '#87d7af');
assert.equal(cm.colors.ansi[116].css, '#87d7d7');
assert.equal(cm.colors.ansi[117].css, '#87d7ff');
assert.equal(cm.colors.ansi[118].css, '#87ff00');
assert.equal(cm.colors.ansi[119].css, '#87ff5f');
assert.equal(cm.colors.ansi[120].css, '#87ff87');
assert.equal(cm.colors.ansi[121].css, '#87ffaf');
assert.equal(cm.colors.ansi[122].css, '#87ffd7');
assert.equal(cm.colors.ansi[123].css, '#87ffff');
assert.equal(cm.colors.ansi[124].css, '#af0000');
assert.equal(cm.colors.ansi[125].css, '#af005f');
assert.equal(cm.colors.ansi[126].css, '#af0087');
assert.equal(cm.colors.ansi[127].css, '#af00af');
assert.equal(cm.colors.ansi[128].css, '#af00d7');
assert.equal(cm.colors.ansi[129].css, '#af00ff');
assert.equal(cm.colors.ansi[130].css, '#af5f00');
assert.equal(cm.colors.ansi[131].css, '#af5f5f');
assert.equal(cm.colors.ansi[132].css, '#af5f87');
assert.equal(cm.colors.ansi[133].css, '#af5faf');
assert.equal(cm.colors.ansi[134].css, '#af5fd7');
assert.equal(cm.colors.ansi[135].css, '#af5fff');
assert.equal(cm.colors.ansi[136].css, '#af8700');
assert.equal(cm.colors.ansi[137].css, '#af875f');
assert.equal(cm.colors.ansi[138].css, '#af8787');
assert.equal(cm.colors.ansi[139].css, '#af87af');
assert.equal(cm.colors.ansi[140].css, '#af87d7');
assert.equal(cm.colors.ansi[141].css, '#af87ff');
assert.equal(cm.colors.ansi[142].css, '#afaf00');
assert.equal(cm.colors.ansi[143].css, '#afaf5f');
assert.equal(cm.colors.ansi[144].css, '#afaf87');
assert.equal(cm.colors.ansi[145].css, '#afafaf');
assert.equal(cm.colors.ansi[146].css, '#afafd7');
assert.equal(cm.colors.ansi[147].css, '#afafff');
assert.equal(cm.colors.ansi[148].css, '#afd700');
assert.equal(cm.colors.ansi[149].css, '#afd75f');
assert.equal(cm.colors.ansi[150].css, '#afd787');
assert.equal(cm.colors.ansi[151].css, '#afd7af');
assert.equal(cm.colors.ansi[152].css, '#afd7d7');
assert.equal(cm.colors.ansi[153].css, '#afd7ff');
assert.equal(cm.colors.ansi[154].css, '#afff00');
assert.equal(cm.colors.ansi[155].css, '#afff5f');
assert.equal(cm.colors.ansi[156].css, '#afff87');
assert.equal(cm.colors.ansi[157].css, '#afffaf');
assert.equal(cm.colors.ansi[158].css, '#afffd7');
assert.equal(cm.colors.ansi[159].css, '#afffff');
assert.equal(cm.colors.ansi[160].css, '#d70000');
assert.equal(cm.colors.ansi[161].css, '#d7005f');
assert.equal(cm.colors.ansi[162].css, '#d70087');
assert.equal(cm.colors.ansi[163].css, '#d700af');
assert.equal(cm.colors.ansi[164].css, '#d700d7');
assert.equal(cm.colors.ansi[165].css, '#d700ff');
assert.equal(cm.colors.ansi[166].css, '#d75f00');
assert.equal(cm.colors.ansi[167].css, '#d75f5f');
assert.equal(cm.colors.ansi[168].css, '#d75f87');
assert.equal(cm.colors.ansi[169].css, '#d75faf');
assert.equal(cm.colors.ansi[170].css, '#d75fd7');
assert.equal(cm.colors.ansi[171].css, '#d75fff');
assert.equal(cm.colors.ansi[172].css, '#d78700');
assert.equal(cm.colors.ansi[173].css, '#d7875f');
assert.equal(cm.colors.ansi[174].css, '#d78787');
assert.equal(cm.colors.ansi[175].css, '#d787af');
assert.equal(cm.colors.ansi[176].css, '#d787d7');
assert.equal(cm.colors.ansi[177].css, '#d787ff');
assert.equal(cm.colors.ansi[178].css, '#d7af00');
assert.equal(cm.colors.ansi[179].css, '#d7af5f');
assert.equal(cm.colors.ansi[180].css, '#d7af87');
assert.equal(cm.colors.ansi[181].css, '#d7afaf');
assert.equal(cm.colors.ansi[182].css, '#d7afd7');
assert.equal(cm.colors.ansi[183].css, '#d7afff');
assert.equal(cm.colors.ansi[184].css, '#d7d700');
assert.equal(cm.colors.ansi[185].css, '#d7d75f');
assert.equal(cm.colors.ansi[186].css, '#d7d787');
assert.equal(cm.colors.ansi[187].css, '#d7d7af');
assert.equal(cm.colors.ansi[188].css, '#d7d7d7');
assert.equal(cm.colors.ansi[189].css, '#d7d7ff');
assert.equal(cm.colors.ansi[190].css, '#d7ff00');
assert.equal(cm.colors.ansi[191].css, '#d7ff5f');
assert.equal(cm.colors.ansi[192].css, '#d7ff87');
assert.equal(cm.colors.ansi[193].css, '#d7ffaf');
assert.equal(cm.colors.ansi[194].css, '#d7ffd7');
assert.equal(cm.colors.ansi[195].css, '#d7ffff');
assert.equal(cm.colors.ansi[196].css, '#ff0000');
assert.equal(cm.colors.ansi[197].css, '#ff005f');
assert.equal(cm.colors.ansi[198].css, '#ff0087');
assert.equal(cm.colors.ansi[199].css, '#ff00af');
assert.equal(cm.colors.ansi[200].css, '#ff00d7');
assert.equal(cm.colors.ansi[201].css, '#ff00ff');
assert.equal(cm.colors.ansi[202].css, '#ff5f00');
assert.equal(cm.colors.ansi[203].css, '#ff5f5f');
assert.equal(cm.colors.ansi[204].css, '#ff5f87');
assert.equal(cm.colors.ansi[205].css, '#ff5faf');
assert.equal(cm.colors.ansi[206].css, '#ff5fd7');
assert.equal(cm.colors.ansi[207].css, '#ff5fff');
assert.equal(cm.colors.ansi[208].css, '#ff8700');
assert.equal(cm.colors.ansi[209].css, '#ff875f');
assert.equal(cm.colors.ansi[210].css, '#ff8787');
assert.equal(cm.colors.ansi[211].css, '#ff87af');
assert.equal(cm.colors.ansi[212].css, '#ff87d7');
assert.equal(cm.colors.ansi[213].css, '#ff87ff');
assert.equal(cm.colors.ansi[214].css, '#ffaf00');
assert.equal(cm.colors.ansi[215].css, '#ffaf5f');
assert.equal(cm.colors.ansi[216].css, '#ffaf87');
assert.equal(cm.colors.ansi[217].css, '#ffafaf');
assert.equal(cm.colors.ansi[218].css, '#ffafd7');
assert.equal(cm.colors.ansi[219].css, '#ffafff');
assert.equal(cm.colors.ansi[220].css, '#ffd700');
assert.equal(cm.colors.ansi[221].css, '#ffd75f');
assert.equal(cm.colors.ansi[222].css, '#ffd787');
assert.equal(cm.colors.ansi[223].css, '#ffd7af');
assert.equal(cm.colors.ansi[224].css, '#ffd7d7');
assert.equal(cm.colors.ansi[225].css, '#ffd7ff');
assert.equal(cm.colors.ansi[226].css, '#ffff00');
assert.equal(cm.colors.ansi[227].css, '#ffff5f');
assert.equal(cm.colors.ansi[228].css, '#ffff87');
assert.equal(cm.colors.ansi[229].css, '#ffffaf');
assert.equal(cm.colors.ansi[230].css, '#ffffd7');
assert.equal(cm.colors.ansi[231].css, '#ffffff');
assert.equal(cm.colors.ansi[232].css, '#080808');
assert.equal(cm.colors.ansi[233].css, '#121212');
assert.equal(cm.colors.ansi[234].css, '#1c1c1c');
assert.equal(cm.colors.ansi[235].css, '#262626');
assert.equal(cm.colors.ansi[236].css, '#303030');
assert.equal(cm.colors.ansi[237].css, '#3a3a3a');
assert.equal(cm.colors.ansi[238].css, '#444444');
assert.equal(cm.colors.ansi[239].css, '#4e4e4e');
assert.equal(cm.colors.ansi[240].css, '#585858');
assert.equal(cm.colors.ansi[241].css, '#626262');
assert.equal(cm.colors.ansi[242].css, '#6c6c6c');
assert.equal(cm.colors.ansi[243].css, '#767676');
assert.equal(cm.colors.ansi[244].css, '#808080');
assert.equal(cm.colors.ansi[245].css, '#8a8a8a');
assert.equal(cm.colors.ansi[246].css, '#949494');
assert.equal(cm.colors.ansi[247].css, '#9e9e9e');
assert.equal(cm.colors.ansi[248].css, '#a8a8a8');
assert.equal(cm.colors.ansi[249].css, '#b2b2b2');
assert.equal(cm.colors.ansi[250].css, '#bcbcbc');
assert.equal(cm.colors.ansi[251].css, '#c6c6c6');
assert.equal(cm.colors.ansi[252].css, '#d0d0d0');
assert.equal(cm.colors.ansi[253].css, '#dadada');
assert.equal(cm.colors.ansi[254].css, '#e4e4e4');
assert.equal(cm.colors.ansi[255].css, '#eeeeee');
});
});
@@ -276,20 +294,20 @@ describe('ColorManager', () => {
});
it('should set a partial set of colors, using the default if not present', () => {
assert.equal(cm.colors.background, '#000000');
assert.equal(cm.colors.foreground, '#ffffff');
assert.equal(cm.colors.background.css, '#000000');
assert.equal(cm.colors.foreground.css, '#ffffff');
cm.setTheme({
background: '#FF0000',
foreground: '#00FF00'
});
assert.equal(cm.colors.background, '#FF0000');
assert.equal(cm.colors.foreground, '#00FF00');
assert.equal(cm.colors.background.css, '#FF0000');
assert.equal(cm.colors.foreground.css, '#00FF00');
cm.setTheme({
background: '#0000FF'
});
assert.equal(cm.colors.background, '#0000FF');
assert.equal(cm.colors.background.css, '#0000FF');
// FG reverts back to default
assert.equal(cm.colors.foreground, '#ffffff');
assert.equal(cm.colors.foreground.css, '#ffffff');
});
});
});
+138 -61
View File
@@ -3,58 +3,75 @@
* @license MIT
*/
import { IColorSet, IColorManager } from './Types';
import { IColorManager } from './Types';
import { IColor, IColorSet } from '../shared/Types';
import { ITheme } from 'xterm';
const DEFAULT_FOREGROUND = '#ffffff';
const DEFAULT_BACKGROUND = '#000000';
const DEFAULT_CURSOR = '#ffffff';
const DEFAULT_CURSOR_ACCENT = '#000000';
const DEFAULT_SELECTION = 'rgba(255, 255, 255, 0.3)';
export const DEFAULT_ANSI_COLORS = [
// dark:
'#2e3436',
'#cc0000',
'#4e9a06',
'#c4a000',
'#3465a4',
'#75507b',
'#06989a',
'#d3d7cf',
// bright:
'#555753',
'#ef2929',
'#8ae234',
'#fce94f',
'#729fcf',
'#ad7fa8',
'#34e2e2',
'#eeeeec'
];
const DEFAULT_FOREGROUND = fromHex('#ffffff');
const DEFAULT_BACKGROUND = fromHex('#000000');
const DEFAULT_CURSOR = fromHex('#ffffff');
const DEFAULT_CURSOR_ACCENT = fromHex('#000000');
const DEFAULT_SELECTION = {
css: 'rgba(255, 255, 255, 0.3)',
rgba: 0xFFFFFF77
};
/**
* Fills an existing 16 length string with the remaining 240 ANSI colors.
* @param first16Colors The first 16 ANSI colors.
*/
function generate256Colors(first16Colors: string[]): string[] {
let colors = first16Colors.slice();
// An IIFE to generate DEFAULT_ANSI_COLORS. Do not mutate DEFAULT_ANSI_COLORS, instead make a copy
// and mutate that.
export const DEFAULT_ANSI_COLORS = (() => {
const colors = [
// dark:
fromHex('#2e3436'),
fromHex('#cc0000'),
fromHex('#4e9a06'),
fromHex('#c4a000'),
fromHex('#3465a4'),
fromHex('#75507b'),
fromHex('#06989a'),
fromHex('#d3d7cf'),
// bright:
fromHex('#555753'),
fromHex('#ef2929'),
fromHex('#8ae234'),
fromHex('#fce94f'),
fromHex('#729fcf'),
fromHex('#ad7fa8'),
fromHex('#34e2e2'),
fromHex('#eeeeec')
];
// Fill in the remaining 240 ANSI colors.
// Generate colors (16-231)
let v = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff];
for (let i = 0; i < 216; i++) {
const r = toPaddedHex(v[(i / 36) % 6 | 0]);
const g = toPaddedHex(v[(i / 6) % 6 | 0]);
const b = toPaddedHex(v[i % 6]);
colors.push(`#${r}${g}${b}`);
const r = v[(i / 36) % 6 | 0];
const g = v[(i / 6) % 6 | 0];
const b = v[i % 6];
colors.push({
css: `#${toPaddedHex(r)}${toPaddedHex(g)}${toPaddedHex(b)}`,
// Use >>> 0 to force a conversion to an unsigned int
rgba: ((r << 24) | (g << 16) | (b << 8) | 0xFF) >>> 0
});
}
// Generate greys (232-255)
for (let i = 0; i < 24; i++) {
const c = toPaddedHex(8 + i * 10);
colors.push(`#${c}${c}${c}`);
const c = 8 + i * 10;
const ch = toPaddedHex(c);
colors.push({
css: `#${ch}${ch}${ch}`,
rgba: ((c << 24) | (c << 16) | (c << 8) | 0xFF) >>> 0
});
}
return colors;
})();
function fromHex(css: string): IColor {
return {
css,
rgba: parseInt(css.slice(1), 16) << 8 | 0xFF
};
}
function toPaddedHex(c: number): string {
@@ -67,15 +84,23 @@ function toPaddedHex(c: number): string {
*/
export class ColorManager implements IColorManager {
public colors: IColorSet;
private _ctx: CanvasRenderingContext2D;
private _litmusColor: CanvasGradient;
constructor() {
constructor(document: Document, public allowTransparency: boolean) {
const canvas = document.createElement('canvas');
canvas.width = 1;
canvas.height = 1;
this._ctx = canvas.getContext('2d');
this._ctx.globalCompositeOperation = 'copy';
this._litmusColor = this._ctx.createLinearGradient(0, 0, 1, 1);
this.colors = {
foreground: DEFAULT_FOREGROUND,
background: DEFAULT_BACKGROUND,
cursor: DEFAULT_CURSOR,
cursorAccent: DEFAULT_CURSOR_ACCENT,
selection: DEFAULT_SELECTION,
ansi: generate256Colors(DEFAULT_ANSI_COLORS)
ansi: DEFAULT_ANSI_COLORS.slice()
};
}
@@ -85,26 +110,78 @@ export class ColorManager implements IColorManager {
* colors will be used where colors are not defined.
*/
public setTheme(theme: ITheme): void {
this.colors.foreground = theme.foreground || DEFAULT_FOREGROUND;
this.colors.background = theme.background || DEFAULT_BACKGROUND;
this.colors.cursor = theme.cursor || DEFAULT_CURSOR;
this.colors.cursorAccent = theme.cursorAccent || DEFAULT_CURSOR_ACCENT;
this.colors.selection = theme.selection || DEFAULT_SELECTION;
this.colors.ansi[0] = theme.black || DEFAULT_ANSI_COLORS[0];
this.colors.ansi[1] = theme.red || DEFAULT_ANSI_COLORS[1];
this.colors.ansi[2] = theme.green || DEFAULT_ANSI_COLORS[2];
this.colors.ansi[3] = theme.yellow || DEFAULT_ANSI_COLORS[3];
this.colors.ansi[4] = theme.blue || DEFAULT_ANSI_COLORS[4];
this.colors.ansi[5] = theme.magenta || DEFAULT_ANSI_COLORS[5];
this.colors.ansi[6] = theme.cyan || DEFAULT_ANSI_COLORS[6];
this.colors.ansi[7] = theme.white || DEFAULT_ANSI_COLORS[7];
this.colors.ansi[8] = theme.brightBlack || DEFAULT_ANSI_COLORS[8];
this.colors.ansi[9] = theme.brightRed || DEFAULT_ANSI_COLORS[9];
this.colors.ansi[10] = theme.brightGreen || DEFAULT_ANSI_COLORS[10];
this.colors.ansi[11] = theme.brightYellow || DEFAULT_ANSI_COLORS[11];
this.colors.ansi[12] = theme.brightBlue || DEFAULT_ANSI_COLORS[12];
this.colors.ansi[13] = theme.brightMagenta || DEFAULT_ANSI_COLORS[13];
this.colors.ansi[14] = theme.brightCyan || DEFAULT_ANSI_COLORS[14];
this.colors.ansi[15] = theme.brightWhite || DEFAULT_ANSI_COLORS[15];
this.colors.foreground = this._parseColor(theme.foreground, DEFAULT_FOREGROUND);
this.colors.background = this._parseColor(theme.background, DEFAULT_BACKGROUND);
this.colors.cursor = this._parseColor(theme.cursor, DEFAULT_CURSOR, true);
this.colors.cursorAccent = this._parseColor(theme.cursorAccent, DEFAULT_CURSOR_ACCENT, true);
this.colors.selection = this._parseColor(theme.selection, DEFAULT_SELECTION, true);
this.colors.ansi[0] = this._parseColor(theme.black, DEFAULT_ANSI_COLORS[0]);
this.colors.ansi[1] = this._parseColor(theme.red, DEFAULT_ANSI_COLORS[1]);
this.colors.ansi[2] = this._parseColor(theme.green, DEFAULT_ANSI_COLORS[2]);
this.colors.ansi[3] = this._parseColor(theme.yellow, DEFAULT_ANSI_COLORS[3]);
this.colors.ansi[4] = this._parseColor(theme.blue, DEFAULT_ANSI_COLORS[4]);
this.colors.ansi[5] = this._parseColor(theme.magenta, DEFAULT_ANSI_COLORS[5]);
this.colors.ansi[6] = this._parseColor(theme.cyan, DEFAULT_ANSI_COLORS[6]);
this.colors.ansi[7] = this._parseColor(theme.white, DEFAULT_ANSI_COLORS[7]);
this.colors.ansi[8] = this._parseColor(theme.brightBlack, DEFAULT_ANSI_COLORS[8]);
this.colors.ansi[9] = this._parseColor(theme.brightRed, DEFAULT_ANSI_COLORS[9]);
this.colors.ansi[10] = this._parseColor(theme.brightGreen, DEFAULT_ANSI_COLORS[10]);
this.colors.ansi[11] = this._parseColor(theme.brightYellow, DEFAULT_ANSI_COLORS[11]);
this.colors.ansi[12] = this._parseColor(theme.brightBlue, DEFAULT_ANSI_COLORS[12]);
this.colors.ansi[13] = this._parseColor(theme.brightMagenta, DEFAULT_ANSI_COLORS[13]);
this.colors.ansi[14] = this._parseColor(theme.brightCyan, DEFAULT_ANSI_COLORS[14]);
this.colors.ansi[15] = this._parseColor(theme.brightWhite, DEFAULT_ANSI_COLORS[15]);
}
private _parseColor(
css: string,
fallback: IColor,
allowTransparency: boolean = this.allowTransparency
): IColor {
if (!css) {
return fallback;
}
// If parsing the value results in failure, then it must be ignored, and the attribute must
// retain its previous value.
// -- https://html.spec.whatwg.org/multipage/canvas.html#fill-and-stroke-styles
this._ctx.fillStyle = this._litmusColor;
this._ctx.fillStyle = css;
if (typeof this._ctx.fillStyle !== 'string') {
console.warn(`Color: ${css} is invalid using fallback ${fallback.css}`);
return fallback;
}
this._ctx.fillRect(0, 0, 1, 1);
const data = this._ctx.getImageData(0, 0, 1, 1).data;
if (!allowTransparency && data[3] !== 0xFF) {
// Ideally we'd just ignore the alpha channel, but...
//
// Browsers may not give back exactly the same RGB values we put in, because most/all
// convert the color to a pre-multiplied representation. getImageData converts that back to
// a un-premultipled representation, but the precision loss may make the RGB channels unuable
// on their own.
//
// E.g. In Chrome #12345610 turns into #10305010, and in the extreme case, 0xFFFFFF00 turns
// into 0x00000000.
//
// "Note: Due to the lossy nature of converting to and from premultiplied alpha color values,
// pixels that have just been set using putImageData() might be returned to an equivalent
// getImageData() as different values."
// -- https://html.spec.whatwg.org/multipage/canvas.html#pixel-manipulation
//
// So let's just use the fallback color in this case instead.
console.warn(
`Color: ${css} is using transparency, but allowTransparency is false. ` +
`Using fallback ${fallback.css}.`
);
return fallback;
}
return {
css,
rgba: (data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3]) >>> 0
};
}
}
+18 -20
View File
@@ -3,11 +3,10 @@
* @license MIT
*/
import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_CHAR_INDEX } from '../Buffer';
import { GridCache } from './GridCache';
import { FLAGS, IColorSet, IRenderDimensions } from './Types';
import { CHAR_DATA_WIDTH_INDEX } from '../Buffer';
import { IColorSet, IRenderDimensions } from './Types';
import { BaseRenderLayer } from './BaseRenderLayer';
import { CharData, IBuffer, ICharMeasure, ITerminal, ITerminalOptions } from '../Types';
import { CharData, ITerminal } from '../Types';
interface ICursorState {
x: number;
@@ -26,7 +25,6 @@ export class CursorRenderLayer extends BaseRenderLayer {
private _state: ICursorState;
private _cursorRenderers: {[key: string]: (terminal: ITerminal, x: number, y: number, charData: CharData) => void};
private _cursorBlinkStateManager: CursorBlinkStateManager;
private _isFocused: boolean;
constructor(container: HTMLElement, zIndex: number, colors: IColorSet) {
super(container, 'cursor', zIndex, true, colors);
@@ -35,7 +33,7 @@ export class CursorRenderLayer extends BaseRenderLayer {
y: null,
isFocused: null,
style: null,
width: null,
width: null
};
this._cursorRenderers = {
'bar': this._renderBarCursor.bind(this),
@@ -45,15 +43,15 @@ export class CursorRenderLayer extends BaseRenderLayer {
// TODO: Consider initial options? Maybe onOptionsChanged should be called at the end of open?
}
public resize(terminal: ITerminal, dim: IRenderDimensions, charSizeChanged: boolean): void {
super.resize(terminal, dim, charSizeChanged);
public resize(terminal: ITerminal, dim: IRenderDimensions): void {
super.resize(terminal, dim);
// Resizing the canvas discards the contents of the canvas so clear state
this._state = {
x: null,
y: null,
isFocused: null,
style: null,
width: null,
width: null
};
}
@@ -137,7 +135,7 @@ export class CursorRenderLayer extends BaseRenderLayer {
if (!terminal.isFocused) {
this._clearCursor();
this._ctx.save();
this._ctx.fillStyle = this._colors.cursor;
this._ctx.fillStyle = this._colors.cursor.css;
this._renderBlurCursor(terminal, terminal.buffer.x, viewportRelativeCursorY, charData);
this._ctx.restore();
this._state.x = terminal.buffer.x;
@@ -185,37 +183,37 @@ export class CursorRenderLayer extends BaseRenderLayer {
y: null,
isFocused: null,
style: null,
width: null,
width: null
};
}
}
private _renderBarCursor(terminal: ITerminal, x: number, y: number, charData: CharData): void {
this._ctx.save();
this._ctx.fillStyle = this._colors.cursor;
this._ctx.fillStyle = this._colors.cursor.css;
this.fillLeftLineAtCell(x, y);
this._ctx.restore();
}
private _renderBlockCursor(terminal: ITerminal, x: number, y: number, charData: CharData): void {
this._ctx.save();
this._ctx.fillStyle = this._colors.cursor;
this._ctx.fillStyle = this._colors.cursor.css;
this.fillCells(x, y, charData[CHAR_DATA_WIDTH_INDEX], 1);
this._ctx.fillStyle = this._colors.cursorAccent;
this._ctx.fillStyle = this._colors.cursorAccent.css;
this.fillCharTrueColor(terminal, charData, x, y);
this._ctx.restore();
}
private _renderUnderlineCursor(terminal: ITerminal, x: number, y: number, charData: CharData): void {
this._ctx.save();
this._ctx.fillStyle = this._colors.cursor;
this._ctx.fillStyle = this._colors.cursor.css;
this.fillBottomLineAtCells(x, y);
this._ctx.restore();
}
private _renderBlurCursor(terminal: ITerminal, x: number, y: number, charData: CharData): void {
this._ctx.save();
this._ctx.strokeStyle = this._colors.cursor;
this._ctx.strokeStyle = this._colors.cursor.css;
this.strokeRectAtCell(x, y, charData[CHAR_DATA_WIDTH_INDEX], 1);
this._ctx.restore();
}
@@ -237,7 +235,7 @@ class CursorBlinkStateManager {
constructor(
terminal: ITerminal,
private renderCallback: () => void
private _renderCallback: () => void
) {
this.isCursorVisible = true;
if (terminal.isFocused) {
@@ -272,7 +270,7 @@ class CursorBlinkStateManager {
this.isCursorVisible = true;
if (!this._animationFrame) {
this._animationFrame = window.requestAnimationFrame(() => {
this.renderCallback();
this._renderCallback();
this._animationFrame = null;
});
}
@@ -303,7 +301,7 @@ class CursorBlinkStateManager {
// Hide the cursor
this.isCursorVisible = false;
this._animationFrame = window.requestAnimationFrame(() => {
this.renderCallback();
this._renderCallback();
this._animationFrame = null;
});
@@ -322,7 +320,7 @@ class CursorBlinkStateManager {
// Invert visibility and render
this.isCursorVisible = !this.isCursorVisible;
this._animationFrame = window.requestAnimationFrame(() => {
this.renderCallback();
this._renderCallback();
this._animationFrame = null;
});
}, BLINK_INTERVAL);
+23 -10
View File
@@ -3,11 +3,9 @@
* @license MIT
*/
import { ILinkHoverEvent, ITerminal, ILinkifierAccessor, IBuffer, ICharMeasure, LinkHoverEventTypes } from '../Types';
import { CHAR_DATA_ATTR_INDEX } from '../Buffer';
import { GridCache } from './GridCache';
import { FLAGS, IColorSet, IRenderDimensions } from './Types';
import { BaseRenderLayer, INVERTED_DEFAULT_COLOR } from './BaseRenderLayer';
import { ILinkHoverEvent, ITerminal, ILinkifierAccessor, LinkHoverEventTypes } from '../Types';
import { IColorSet, IRenderDimensions } from './Types';
import { BaseRenderLayer } from './BaseRenderLayer';
export class LinkRenderLayer extends BaseRenderLayer {
private _state: ILinkHoverEvent = null;
@@ -18,8 +16,8 @@ export class LinkRenderLayer extends BaseRenderLayer {
terminal.linkifier.on(LinkHoverEventTypes.LEAVE, (e: ILinkHoverEvent) => this._onLinkLeave(e));
}
public resize(terminal: ITerminal, dim: IRenderDimensions, charSizeChanged: boolean): void {
super.resize(terminal, dim, charSizeChanged);
public resize(terminal: ITerminal, dim: IRenderDimensions): void {
super.resize(terminal, dim);
// Resizing the canvas discards the contents of the canvas so clear state
this._state = null;
}
@@ -30,14 +28,29 @@ export class LinkRenderLayer extends BaseRenderLayer {
private _clearCurrentLink(): void {
if (this._state) {
this.clearCells(this._state.x, this._state.y, this._state.length, 1);
this.clearCells(this._state.x1, this._state.y1, this._state.cols - this._state.x1, 1);
const middleRowCount = this._state.y2 - this._state.y1 - 1;
if (middleRowCount > 0) {
this.clearCells(0, this._state.y1 + 1, this._state.cols, middleRowCount);
}
this.clearCells(0, this._state.y2, this._state.x2, 1);
this._state = null;
}
}
private _onLinkHover(e: ILinkHoverEvent): void {
this._ctx.fillStyle = this._colors.foreground;
this.fillBottomLineAtCells(e.x, e.y, e.length);
this._ctx.fillStyle = this._colors.foreground.css;
if (e.y1 === e.y2) {
// Single line link
this.fillBottomLineAtCells(e.x1, e.y1, e.x2 - e.x1);
} else {
// Multi-line link
this.fillBottomLineAtCells(e.x1, e.y1, e.cols - e.x1);
for (let y = e.y1 + 1; y < e.y2; y++) {
this.fillBottomLineAtCells(0, y, e.cols);
}
this.fillBottomLineAtCells(0, e.y2, e.x2);
}
this._state = e;
}
+7 -8
View File
@@ -3,12 +3,10 @@
* @license MIT
*/
import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from '../Buffer';
import { TextRenderLayer } from './TextRenderLayer';
import { SelectionRenderLayer } from './SelectionRenderLayer';
import { CursorRenderLayer } from './CursorRenderLayer';
import { ColorManager } from './ColorManager';
import { BaseRenderLayer } from './BaseRenderLayer';
import { IRenderLayer, IColorSet, IRenderer, IRenderDimensions } from './Types';
import { ITerminal } from '../Types';
import { LinkRenderLayer } from './LinkRenderLayer';
@@ -31,13 +29,14 @@ export class Renderer extends EventEmitter implements IRenderer {
constructor(private _terminal: ITerminal, theme: ITheme) {
super();
this.colorManager = new ColorManager();
const allowTransparency = this._terminal.options.allowTransparency;
this.colorManager = new ColorManager(document, allowTransparency);
if (theme) {
this.colorManager.setTheme(theme);
}
this._renderLayers = [
new TextRenderLayer(this._terminal.screenElement, 0, this.colorManager.colors, this._terminal.options.allowTransparency),
new TextRenderLayer(this._terminal.screenElement, 0, this.colorManager.colors, allowTransparency),
new SelectionRenderLayer(this._terminal.screenElement, 1, this.colorManager.colors),
new LinkRenderLayer(this._terminal.screenElement, 2, this.colorManager.colors, this._terminal),
new CursorRenderLayer(this._terminal.screenElement, 3, this.colorManager.colors)
@@ -84,7 +83,7 @@ export class Renderer extends EventEmitter implements IRenderer {
// and the terminal needs to refreshed
if (this._devicePixelRatio !== devicePixelRatio) {
this._devicePixelRatio = devicePixelRatio;
this.onResize(this._terminal.cols, this._terminal.rows, true);
this.onResize(this._terminal.cols, this._terminal.rows);
}
}
@@ -106,12 +105,12 @@ export class Renderer extends EventEmitter implements IRenderer {
return this.colorManager.colors;
}
public onResize(cols: number, rows: number, didCharSizeChange: boolean): void {
public onResize(cols: number, rows: number): void {
// Update character and canvas dimensions
this._updateDimensions();
// Resize all render layers
this._renderLayers.forEach(l => l.resize(this._terminal, this.dimensions, didCharSizeChange));
this._renderLayers.forEach(l => l.resize(this._terminal, this.dimensions));
// Force a refresh
if (this._isPaused) {
@@ -131,7 +130,7 @@ export class Renderer extends EventEmitter implements IRenderer {
}
public onCharSizeChanged(): void {
this.onResize(this._terminal.cols, this._terminal.rows, true);
this.onResize(this._terminal.cols, this._terminal.rows);
}
public onBlur(): void {
+5 -7
View File
@@ -3,10 +3,8 @@
* @license MIT
*/
import { IBuffer, ICharMeasure, ITerminal } from '../Types';
import { CHAR_DATA_ATTR_INDEX } from '../Buffer';
import { GridCache } from './GridCache';
import { FLAGS, IColorSet, IRenderDimensions } from './Types';
import { ITerminal } from '../Types';
import { IColorSet, IRenderDimensions } from './Types';
import { BaseRenderLayer } from './BaseRenderLayer';
export class SelectionRenderLayer extends BaseRenderLayer {
@@ -20,8 +18,8 @@ export class SelectionRenderLayer extends BaseRenderLayer {
};
}
public resize(terminal: ITerminal, dim: IRenderDimensions, charSizeChanged: boolean): void {
super.resize(terminal, dim, charSizeChanged);
public resize(terminal: ITerminal, dim: IRenderDimensions): void {
super.resize(terminal, dim);
// Resizing the canvas discards the contents of the canvas so clear state
this._state = {
start: null,
@@ -67,7 +65,7 @@ export class SelectionRenderLayer extends BaseRenderLayer {
// Draw first row
const startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0;
const startRowEndCol = viewportCappedStartRow === viewportCappedEndRow ? end[0] : terminal.cols;
this._ctx.fillStyle = this._colors.selection;
this._ctx.fillStyle = this._colors.selection.css;
this.fillCells(startCol, viewportCappedStartRow, startRowEndCol - startCol, 1);
// Draw middle rows
+19 -18
View File
@@ -5,16 +5,17 @@
import { CHAR_DATA_ATTR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX } from '../Buffer';
import { FLAGS, IColorSet, IRenderDimensions } from './Types';
import { CharData, IBuffer, ICharMeasure, ITerminal } from '../Types';
import { CharData, ITerminal } from '../Types';
import { INVERTED_DEFAULT_COLOR } from './atlas/Types';
import { GridCache } from './GridCache';
import { BaseRenderLayer, INVERTED_DEFAULT_COLOR } from './BaseRenderLayer';
import { BaseRenderLayer } from './BaseRenderLayer';
/**
* This CharData looks like a null character, which will forc a clear and render
* when the character changes (a regular space ' ' character may not as it's
* drawn state is a cleared cell).
*/
const OVERLAP_OWNED_CHAR_DATA: CharData = [null, '', 0, -1];
// const OVERLAP_OWNED_CHAR_DATA: CharData = [null, '', 0, -1];
export class TextRenderLayer extends BaseRenderLayer {
private _state: GridCache<CharData>;
@@ -27,8 +28,8 @@ export class TextRenderLayer extends BaseRenderLayer {
this._state = new GridCache<CharData>();
}
public resize(terminal: ITerminal, dim: IRenderDimensions, charSizeChanged: boolean): void {
super.resize(terminal, dim, charSizeChanged);
public resize(terminal: ITerminal, dim: IRenderDimensions): void {
super.resize(terminal, dim);
// Clear the character width cache if the font or width has changed
const terminalFont = this._getFont(terminal, false);
@@ -157,7 +158,7 @@ export class TextRenderLayer extends BaseRenderLayer {
// Draw background
if (bg < 256) {
this._ctx.save();
this._ctx.fillStyle = (bg === INVERTED_DEFAULT_COLOR ? this._colors.foreground : this._colors.ansi[bg]);
this._ctx.fillStyle = (bg === INVERTED_DEFAULT_COLOR ? this._colors.foreground.css : this._colors.ansi[bg].css);
this.fillCells(x, y, width, 1);
this._ctx.restore();
}
@@ -173,12 +174,12 @@ export class TextRenderLayer extends BaseRenderLayer {
if (flags & FLAGS.UNDERLINE) {
if (fg === INVERTED_DEFAULT_COLOR) {
this._ctx.fillStyle = this._colors.background;
this._ctx.fillStyle = this._colors.background.css;
} else if (fg < 256) {
// 256 color support
this._ctx.fillStyle = this._colors.ansi[fg];
this._ctx.fillStyle = this._colors.ansi[fg].css;
} else {
this._ctx.fillStyle = this._colors.foreground;
this._ctx.fillStyle = this._colors.foreground.css;
}
this.fillBottomLineAtCells(x, y);
}
@@ -238,13 +239,13 @@ export class TextRenderLayer extends BaseRenderLayer {
* @param x The column of the char.
* @param y The row of the char.
*/
private _clearChar(x: number, y: number): void {
let colsToClear = 1;
// Clear the adjacent character if it was wide
const state = this._state.cache[x][y];
if (state && state[CHAR_DATA_WIDTH_INDEX] === 2) {
colsToClear = 2;
}
this.clearCells(x, y, colsToClear, 1);
}
// private _clearChar(x: number, y: number): void {
// let colsToClear = 1;
// // Clear the adjacent character if it was wide
// const state = this._state.cache[x][y];
// if (state && state[CHAR_DATA_WIDTH_INDEX] === 2) {
// colsToClear = 2;
// }
// this.clearCells(x, y, colsToClear, 1);
// }
}
+5 -10
View File
@@ -5,6 +5,7 @@
import { ITerminal } from '../Types';
import { IEventEmitter, ITheme } from 'xterm';
import { IColorSet } from '../shared/Types';
/**
* Flags used to render terminal text properly.
@@ -24,7 +25,7 @@ export interface IRenderer extends IEventEmitter {
setTheme(theme: ITheme): IColorSet;
onWindowResize(devicePixelRatio: number): void;
onResize(cols: number, rows: number, didCharSizeChange: boolean): void;
onResize(cols: number, rows: number): void;
onCharSizeChanged(): void;
onBlur(): void;
onFocus(): void;
@@ -39,14 +40,8 @@ export interface IColorManager {
colors: IColorSet;
}
export interface IColorSet {
foreground: string;
background: string;
cursor: string;
cursorAccent: string;
selection: string;
ansi: string[];
}
// TODO: We should probably rewrite the imports for IColorSet, but there's a lot of them
export { IColorSet };
export interface IRenderDimensions {
scaledCharWidth: number;
@@ -103,7 +98,7 @@ export interface IRenderLayer {
/**
* Resize the render layer.
*/
resize(terminal: ITerminal, dim: IRenderDimensions, charSizeChanged: boolean): void;
resize(terminal: ITerminal, dim: IRenderDimensions): void;
/**
* Clear the state of the render layer.
+72
View File
@@ -0,0 +1,72 @@
/**
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { ITerminal } from '../../Types';
import { IColorSet } from '../Types';
import { ICharAtlasConfig } from '../../shared/atlas/Types';
import { generateCharAtlas } from '../../shared/atlas/CharAtlasGenerator';
import { generateConfig, configEquals } from './CharAtlasUtils';
interface ICharAtlasCacheEntry {
bitmap: HTMLCanvasElement | Promise<ImageBitmap>;
config: ICharAtlasConfig;
ownedBy: ITerminal[];
}
let charAtlasCache: ICharAtlasCacheEntry[] = [];
/**
* Acquires a char atlas, either generating a new one or returning an existing
* one that is in use by another terminal.
* @param terminal The terminal.
* @param colors The colors to use.
*/
export function acquireCharAtlas(terminal: ITerminal, colors: IColorSet, scaledCharWidth: number, scaledCharHeight: number): HTMLCanvasElement | Promise<ImageBitmap> {
const newConfig = generateConfig(scaledCharWidth, scaledCharHeight, terminal, colors);
// Check to see if the terminal already owns this config
for (let i = 0; i < charAtlasCache.length; i++) {
const entry = charAtlasCache[i];
const ownedByIndex = entry.ownedBy.indexOf(terminal);
if (ownedByIndex >= 0) {
if (configEquals(entry.config, newConfig)) {
return entry.bitmap;
} else {
// The configs differ, release the terminal from the entry
if (entry.ownedBy.length === 1) {
charAtlasCache.splice(i, 1);
} else {
entry.ownedBy.splice(ownedByIndex, 1);
}
break;
}
}
}
// Try match a char atlas from the cache
for (let i = 0; i < charAtlasCache.length; i++) {
const entry = charAtlasCache[i];
if (configEquals(entry.config, newConfig)) {
// Add the terminal to the cache entry and return
entry.ownedBy.push(terminal);
return entry.bitmap;
}
}
const canvasFactory = (width: number, height: number) => {
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
return canvas;
};
const newEntry: ICharAtlasCacheEntry = {
bitmap: generateCharAtlas(window, canvasFactory, newConfig),
config: newConfig,
ownedBy: [terminal]
};
charAtlasCache.push(newEntry);
return newEntry.bitmap;
}
+48
View File
@@ -0,0 +1,48 @@
/**
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { ITerminal } from '../../Types';
import { IColorSet } from '../Types';
import { ICharAtlasConfig } from '../../shared/atlas/Types';
export function generateConfig(scaledCharWidth: number, scaledCharHeight: number, terminal: ITerminal, colors: IColorSet): ICharAtlasConfig {
const clonedColors = {
foreground: colors.foreground,
background: colors.background,
cursor: null,
cursorAccent: null,
selection: null,
ansi: colors.ansi.slice(0, 16)
};
return {
devicePixelRatio: window.devicePixelRatio,
scaledCharWidth,
scaledCharHeight,
fontFamily: terminal.options.fontFamily,
fontSize: terminal.options.fontSize,
fontWeight: terminal.options.fontWeight,
fontWeightBold: terminal.options.fontWeightBold,
allowTransparency: terminal.options.allowTransparency,
colors: clonedColors
};
}
export function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean {
for (let i = 0; i < a.colors.ansi.length; i++) {
if (a.colors.ansi[i].rgba !== b.colors.ansi[i].rgba) {
return false;
}
}
return a.devicePixelRatio === b.devicePixelRatio &&
a.fontFamily === b.fontFamily &&
a.fontSize === b.fontSize &&
a.fontWeight === b.fontWeight &&
a.fontWeightBold === b.fontWeightBold &&
a.allowTransparency === b.allowTransparency &&
a.scaledCharWidth === b.scaledCharWidth &&
a.scaledCharHeight === b.scaledCharHeight &&
a.colors.foreground === b.colors.foreground &&
a.colors.background === b.colors.background;
}
+7
View File
@@ -0,0 +1,7 @@
/**
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
* @license MIT
*/
export const INVERTED_DEFAULT_COLOR = -1;
export const DIM_OPACITY = 0.5;
+18
View File
@@ -0,0 +1,18 @@
/**
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
* @license MIT
*/
export interface IColor {
css: string;
rgba: number; // 32-bit int with rgba in each byte
}
export interface IColorSet {
foreground: IColor;
background: IColor;
cursor: IColor;
cursorAccent: IColor;
selection: IColor;
ansi: IColor[];
}
@@ -4,7 +4,8 @@
*/
import { FontWeight } from 'xterm';
import { isFirefox } from './utils/Browser';
import { CHAR_ATLAS_CELL_SPACING, ICharAtlasConfig } from './Types';
import { isFirefox } from '../utils/Browser';
declare const Promise: any;
@@ -15,43 +16,27 @@ export interface IOffscreenCanvas {
transferToImageBitmap(): ImageBitmap;
}
export interface ICharAtlasRequest {
scaledCharWidth: number;
scaledCharHeight: number;
fontSize: number;
fontFamily: string;
fontWeight: FontWeight;
fontWeightBold: FontWeight;
background: string;
foreground: string;
ansiColors: string[];
devicePixelRatio: number;
allowTransparency: boolean;
}
export const CHAR_ATLAS_CELL_SPACING = 1;
/**
* Generates a char atlas.
* @param context The window or worker context.
* @param canvasFactory A function to generate a canvas with a width or height.
* @param request The config for the new char atlas.
*/
export function generateCharAtlas(context: Window, canvasFactory: (width: number, height: number) => HTMLCanvasElement | IOffscreenCanvas, request: ICharAtlasRequest): HTMLCanvasElement | Promise<ImageBitmap> {
const cellWidth = request.scaledCharWidth + CHAR_ATLAS_CELL_SPACING;
const cellHeight = request.scaledCharHeight + CHAR_ATLAS_CELL_SPACING;
export function generateCharAtlas(context: Window, canvasFactory: (width: number, height: number) => HTMLCanvasElement | IOffscreenCanvas, config: ICharAtlasConfig): HTMLCanvasElement | Promise<ImageBitmap> {
const cellWidth = config.scaledCharWidth + CHAR_ATLAS_CELL_SPACING;
const cellHeight = config.scaledCharHeight + CHAR_ATLAS_CELL_SPACING;
const canvas = canvasFactory(
/*255 ascii chars*/255 * cellWidth,
(/*default+default bold*/2 + /*0-15*/16) * cellHeight
);
const ctx = canvas.getContext('2d', {alpha: request.allowTransparency});
const ctx = canvas.getContext('2d', {alpha: config.allowTransparency});
ctx.fillStyle = request.background;
ctx.fillStyle = config.colors.background.css;
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.save();
ctx.fillStyle = request.foreground;
ctx.font = getFont(request.fontWeight, request);
ctx.fillStyle = config.colors.foreground.css;
ctx.font = getFont(config.fontWeight, config);
ctx.textBaseline = 'top';
// Default color
@@ -65,7 +50,7 @@ export function generateCharAtlas(context: Window, canvasFactory: (width: number
}
// Default color bold
ctx.save();
ctx.font = getFont(request.fontWeightBold, request);
ctx.font = getFont(config.fontWeightBold, config);
for (let i = 0; i < 256; i++) {
ctx.save();
ctx.beginPath();
@@ -77,11 +62,11 @@ export function generateCharAtlas(context: Window, canvasFactory: (width: number
ctx.restore();
// Colors 0-15
ctx.font = getFont(request.fontWeight, request);
ctx.font = getFont(config.fontWeight, config);
for (let colorIndex = 0; colorIndex < 16; colorIndex++) {
// colors 8-15 are bold
if (colorIndex === 8) {
ctx.font = getFont(request.fontWeightBold, request);
ctx.font = getFont(config.fontWeightBold, config);
}
const y = (colorIndex + 2) * cellHeight;
// Draw ascii characters
@@ -90,7 +75,7 @@ export function generateCharAtlas(context: Window, canvasFactory: (width: number
ctx.beginPath();
ctx.rect(i * cellWidth, y, cellWidth, cellHeight);
ctx.clip();
ctx.fillStyle = request.ansiColors[colorIndex];
ctx.fillStyle = config.colors.ansi[colorIndex].css;
ctx.fillText(String.fromCharCode(i), i * cellWidth, y);
ctx.restore();
}
@@ -115,9 +100,9 @@ export function generateCharAtlas(context: Window, canvasFactory: (width: number
const charAtlasImageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
// Remove the background color from the image so characters may overlap
const r = parseInt(request.background.substr(1, 2), 16);
const g = parseInt(request.background.substr(3, 2), 16);
const b = parseInt(request.background.substr(5, 2), 16);
const r = config.colors.background.rgba >>> 24;
const g = config.colors.background.rgba >>> 16 & 0xFF;
const b = config.colors.background.rgba >>> 8 & 0xFF;
clearColor(charAtlasImageData, r, g, b);
return context.createImageBitmap(charAtlasImageData);
@@ -136,6 +121,6 @@ function clearColor(imageData: ImageData, r: number, g: number, b: number): void
}
}
function getFont(fontWeight: FontWeight, request: ICharAtlasRequest): string {
return `${fontWeight} ${request.fontSize * request.devicePixelRatio}px ${request.fontFamily}`;
function getFont(fontWeight: FontWeight, config: ICharAtlasConfig): string {
return `${fontWeight} ${config.fontSize * config.devicePixelRatio}px ${config.fontFamily}`;
}
+21
View File
@@ -0,0 +1,21 @@
/**
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { FontWeight } from 'xterm';
import { IColorSet } from '../Types';
export const CHAR_ATLAS_CELL_SPACING = 1;
export interface ICharAtlasConfig {
devicePixelRatio: number;
fontSize: number;
fontFamily: string;
fontWeight: FontWeight;
fontWeightBold: FontWeight;
scaledCharWidth: number;
scaledCharHeight: number;
allowTransparency: boolean;
colors: IColorSet;
}
+1 -1
View File
@@ -4,7 +4,7 @@
*/
import jsdom = require('jsdom');
import { ICharMeasure, ITerminal } from '../Types';
import { ICharMeasure } from '../Types';
import { assert } from 'chai';
import { CharMeasure } from './CharMeasure';
+3 -7
View File
@@ -3,7 +3,7 @@
* @license MIT
*/
import { ICharMeasure, ITerminal, ITerminalOptions } from '../Types';
import { ICharMeasure, ITerminalOptions } from '../Types';
import { EventEmitter } from '../EventEmitter';
/**
@@ -23,10 +23,7 @@ export class CharMeasure extends EventEmitter implements ICharMeasure {
this._document = document;
this._parentElement = parentElement;
this._measureElement = this._document.createElement('span');
this._measureElement.style.position = 'absolute';
this._measureElement.style.top = '0';
this._measureElement.style.left = '-9999em';
this._measureElement.style.lineHeight = 'normal';
this._measureElement.classList.add('xterm-char-measure-element');
this._measureElement.textContent = 'W';
this._measureElement.setAttribute('aria-hidden', 'true');
this._parentElement.appendChild(this._measureElement);
@@ -41,7 +38,7 @@ export class CharMeasure extends EventEmitter implements ICharMeasure {
}
public measure(options: ITerminalOptions): void {
this._measureElement.style.fontFamily = options.fontFamily;
this._measureElement.style.fontFamily = options.fontFamily;
this._measureElement.style.fontSize = `${options.fontSize}px`;
const geometry = this._measureElement.getBoundingClientRect();
// The element is likely currently display:none, we should retain the
@@ -55,5 +52,4 @@ export class CharMeasure extends EventEmitter implements ICharMeasure {
this.emit('charsizechanged');
}
}
}
-4
View File
@@ -6,10 +6,6 @@
import { assert } from 'chai';
import { CircularList } from './CircularList';
class TestCircularList<T> extends CircularList<T> {
public get array(): T[] { return this._array; }
}
describe('CircularList', () => {
describe('push', () => {
it('should push values onto the array', () => {
-10
View File
@@ -58,16 +58,6 @@ export class CircularList<T> extends EventEmitter implements ICircularList<T> {
this._length = newLength;
}
public get forEach(): (callbackfn: (value: T, index: number) => void) => void {
return (callbackfn: (value: T, index: number) => void) => {
let i = 0;
let length = this.length;
for (let i = 0; i < length; i++) {
callbackfn(this.get(i), i);
}
};
}
/**
* Gets the value at an index.
*
+137
View File
@@ -0,0 +1,137 @@
/**
* Copyright (c) 2016 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { assert, expect } from 'chai';
import { clone } from './Clone';
describe('clone', () => {
it('should clone simple objects', () => {
const test = {
a: 1,
b: 2
};
assert.deepEqual(clone(test), { a: 1, b: 2 });
});
it('should clone nested objects', () => {
const test = {
bar: {
a: 1,
b: 2,
c: {
foo: 'bar'
}
}
};
assert.deepEqual(clone(test), {
bar: {
a: 1,
b: 2,
c: {
foo: 'bar'
}
}
});
});
it('should clone null values', () => {
const test = {
a: null
};
assert.deepEqual(clone(test), { a: null });
});
it('should clone array values', () => {
const test = {
a: [1, 2, 3],
b: [1, null, 'test', { foo: 'bar' }]
};
assert.deepEqual(clone(test), {
a: [1, 2, 3],
b: [1, null, 'test', { foo: 'bar' }]
});
});
it('should stop mutation from occuring on the original object', () => {
const test = {
a: 1,
b: 2,
c: {
foo: 'bar'
}
};
const cloned = clone(test);
test.a = 5;
test.c.foo = 'barbaz';
assert.deepEqual(cloned, {
a: 1,
b: 2,
c: {
foo: 'bar'
}
});
});
it('should clone to a maximum depth of 5 by default', () => {
const test = {
a: {
b: {
c: {
d: {
e: {
f: 'foo'
}
}
}
}
}
};
const cloned = clone(test);
test.a.b.c.d.e.f = 'bar';
// The values at a greater depth then 5 should not be cloned
assert.equal(cloned.a.b.c.d.e.f, 'bar');
});
it('should allow an optional maximum depth to be set', () => {
const test = {
a: {
b: {
c: 'foo'
}
}
};
const cloned = clone(test, 2);
test.a.b.c = 'bar';
// The values at a greater depth then 2 should not be cloned
assert.equal(cloned.a.b.c, 'bar');
});
it('should not throw when cloning a recursive reference', () => {
const test = {
a: {
b: {
c: {}
}
}
};
test.a.b.c = test;
expect(() => clone(test)).to.not.throw();
});
});
+28
View File
@@ -0,0 +1,28 @@
/**
* Copyright (c) 2016 The xterm.js authors. All rights reserved.
* @license MIT
*/
/*
* A simple utility for cloning values
*/
export const clone = <T>(val: T, depth: number = 5): T => {
if (typeof val !== 'object') {
return val;
}
// cloning null always returns null
if (val === null) {
return null;
}
// If we're cloning an array, use an array as the base, otherwise use an object
const clonedObject: any = Array.isArray(val) ? [] : {};
for (const key in val) {
// Recursively clone eack item unless we're at the maximum depth
clonedObject[key] = depth <= 1 ? val[key] : clone(val[key], depth - 1);
}
return clonedObject as T;
};
+18 -2
View File
@@ -7,9 +7,19 @@ import { IColorSet, IRenderer, IRenderDimensions, IColorManager } from '../rende
import { LineData, IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminal, IBuffer, IBufferSet, IBrowser, ICharMeasure, ISelectionManager, ITerminalOptions, ICircularList, ILinkifier, IMouseHelper, ILinkMatcherOptions, XtermListener } from '../Types';
import { Buffer } from '../Buffer';
import * as Browser from '../shared/utils/Browser';
import { ITheme, IDisposable } from 'xterm';
import { ITheme, IDisposable, IMarker } from 'xterm';
export class MockTerminal implements ITerminal {
markers: IMarker[];
addMarker(cursorYOffset: number): IMarker {
throw new Error('Method not implemented.');
}
selectLines(start: number, end: number): void {
throw new Error('Method not implemented.');
}
scrollToLine(line: number): void {
throw new Error('Method not implemented.');
}
static string: any;
getOption(key: any): any {
throw new Error('Method not implemented.');
@@ -269,6 +279,9 @@ export class MockInputHandlingTerminal implements IInputHandlingTerminal {
addDisposableListener(type: string, handler: XtermListener): IDisposable {
throw new Error('Method not implemented.');
}
tabSet(): void {
throw new Error('Method not implemented.');
}
}
export class MockBuffer implements IBuffer {
@@ -311,7 +324,7 @@ export class MockRenderer implements IRenderer {
}
dimensions: IRenderDimensions;
setTheme(theme: ITheme): IColorSet { return <IColorSet>{}; }
onResize(cols: number, rows: number, didCharSizeChange: boolean): void {}
onResize(cols: number, rows: number): void {}
onCharSizeChanged(): void {}
onBlur(): void {}
onFocus(): void {}
@@ -338,6 +351,9 @@ export class MockViewport implements IViewport {
throw new Error('Method not implemented.');
}
syncScrollArea(): void { }
getLinesScrolled(ev: WheelEvent): number {
throw new Error('Method not implemented.');
}
}
export class MockCompositionHelper implements ICompositionHelper {
+7 -5
View File
@@ -117,11 +117,13 @@
visibility: hidden;
}
.xterm .xterm-char-measure-element {
.xterm-char-measure-element {
display: inline-block;
visibility: hidden;
position: absolute;
top: 0;
left: -9999em;
line-height: normal;
}
.xterm.enable-mouse-events {
@@ -144,10 +146,6 @@
color: transparent;
}
.xterm .xterm-accessibility-tree:focus [id^="xterm-active-item-"] {
outline: 1px solid #F80;
}
.xterm .live-region {
position: absolute;
left: -9999px;
@@ -155,3 +153,7 @@
height: 1px;
overflow: hidden;
}
.xterm-cursor-pointer {
cursor: pointer;
}
+2 -1
View File
@@ -5,7 +5,8 @@
"rootDir": "src",
"outDir": "lib",
"sourceMap": true,
"removeComments": true
"removeComments": true,
"noUnusedLocals": true
},
"include": [
"src/**/*"
+12
View File
@@ -43,6 +43,18 @@
true,
"always"
],
"trailing-comma": [
true,
{
"multiline": {
"objects": "never",
"arrays": "never",
"functions": "never",
"typeLiterals": "ignore"
},
"esSpecCompliant": true
}
],
"triple-equals": [
true,
"allow-null-check"
+34 -1
View File
@@ -23,6 +23,7 @@ declare module 'xterm' {
* Warning: Enabling this option can reduce performances somewhat.
*/
allowTransparency?: boolean;
/**
* A data uri of the sound to use for the bell (needs bellStyle = 'sound').
*/
@@ -55,7 +56,7 @@ declare module 'xterm' {
/**
* Whether to enable the rendering of bold text.
*
*
* @deprecated Use fontWeight and fontWeightBold instead.
*/
enableBold?: boolean;
@@ -235,6 +236,12 @@ declare module 'xterm' {
dispose(): void;
}
export interface IMarker extends IDisposable {
readonly id: number;
readonly isDisposed: boolean;
readonly line: number;
}
export interface ILocalizableStrings {
blankLine: string;
promptLabel: string;
@@ -265,6 +272,12 @@ declare module 'xterm' {
*/
cols: number;
/**
* (EXPERIMENTAL) Get all markers registered against the buffer. If the alt
* buffer is active this will always return [].
*/
markers: IMarker[];
/**
* Natural language strings that can be localized.
*/
@@ -403,6 +416,13 @@ declare module 'xterm' {
*/
deregisterLinkMatcher(matcherId: number): void;
/**
* (EXPERIMENTAL) Adds a marker to the normal buffer and returns it. If the
* alt buffer is active, undefined is returned.
* @param cursorYOffset The y position offset of the marker from the cursor.
*/
addMarker(cursorYOffset: number): IMarker;
/**
* Gets whether the terminal has an active selection.
*/
@@ -424,6 +444,13 @@ declare module 'xterm' {
*/
selectAll(): void;
/**
* Selects text in the buffer between 2 lines.
* @param start The 0-based line index to select from (inclusive).
* @param end The 0-based line index to select to (inclusive).
*/
selectLines(start: number, end: number): void;
/**
* Destroys the terminal and detaches it from the DOM.
*/
@@ -451,6 +478,12 @@ declare module 'xterm' {
*/
scrollToBottom(): void;
/**
* Scrolls to a line within the buffer.
* @param line The 0-based line index to scroll to.
*/
scrollToLine(line: number): void;
/**
* Clear the entire buffer, making the prompt line the new first line.
*/