Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
048b10ebf6 | ||
|
|
c882dcc353 | ||
|
|
cd06786d16 | ||
|
|
989f5873f5 | ||
|
|
307744be66 | ||
|
|
d4b75e4909 | ||
|
|
4b5de031ea | ||
|
|
b66b0aae18 | ||
|
|
36a1c5b922 | ||
|
|
63f711e634 | ||
|
|
516903e29e | ||
|
|
5cf9423699 | ||
|
|
dbb2320e83 | ||
|
|
0cb6138cd0 | ||
|
|
bf21097430 | ||
|
|
55d6daf8ee | ||
|
|
6e1e63e666 | ||
|
|
b27d5bb08d | ||
|
|
e63831febc | ||
|
|
4ab79c908a | ||
|
|
b85083c3e8 | ||
|
|
97f592ce8b | ||
|
|
70da707569 | ||
|
|
63f24be2b9 | ||
|
|
0d6be9e03e | ||
|
|
3cbb21a966 | ||
|
|
1fd7348938 | ||
|
|
ae32893732 | ||
|
|
4f82cf26f0 | ||
|
|
b67b65cd1e | ||
|
|
0eeec13653 | ||
|
|
5d1f744282 | ||
|
|
387d5b056d | ||
|
|
cbf7419601 | ||
|
|
5c1a4a7d01 | ||
|
|
cbc6d7ce32 | ||
|
|
b80832ac4c | ||
|
|
3716aad202 | ||
|
|
efbd542964 | ||
|
|
888070830d | ||
|
|
9905e2aa1d | ||
|
|
a59af6606b | ||
|
|
d1213e5656 | ||
|
|
0e14798d69 | ||
|
|
c8ab687fa0 | ||
|
|
e5f5c2baf2 | ||
|
|
2f861602c8 | ||
|
|
4f0cc01485 | ||
|
|
dd41ab38bb | ||
|
|
255cb5de3a | ||
|
|
b44a11c42e | ||
|
|
90b2f0e6a1 | ||
|
|
811aa9c413 | ||
|
|
842c7132ec | ||
|
|
efb67edc36 | ||
|
|
ed7741e006 | ||
|
|
cfe936a0b4 | ||
|
|
4ddf91dbbe | ||
|
|
59be054ca1 | ||
|
|
b794dc017d | ||
|
|
4443ed69c7 | ||
|
|
ab9b84898e | ||
|
|
a41e183b88 | ||
|
|
949239dbdc | ||
|
|
421ccc5695 | ||
|
|
5d68ed6e5e | ||
|
|
021f8c1ba0 | ||
|
|
f456b14b3f | ||
|
|
7a1ef19146 | ||
|
|
b9cbdf6a0d | ||
|
|
7639131c12 | ||
|
|
289b4eeb23 | ||
|
|
404a285eaf | ||
|
|
b20fe80f9b | ||
|
|
ff9814a40d | ||
|
|
855964fbe1 | ||
|
|
735dfb4710 | ||
|
|
ab69bfbb32 | ||
|
|
70771430b5 | ||
|
|
41f6ee4f6d | ||
|
|
ff6ef1b63a | ||
|
|
34034b849f | ||
|
|
e137530581 | ||
|
|
be7788dda6 | ||
|
|
18fb134910 | ||
|
|
454fd4bfb1 | ||
|
|
2e3f9e7094 | ||
|
|
bc35f28f51 | ||
|
|
97feb64e94 | ||
|
|
1114977a21 | ||
|
|
a8c5b159a9 | ||
|
|
a9d6686e14 | ||
|
|
99aa142cef | ||
|
|
f8f1355155 | ||
|
|
74a6dfb197 |
@@ -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>
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
Generated
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "xterm",
|
||||
"version": "3.1.0-master",
|
||||
"version": "3.2.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "xterm",
|
||||
"description": "Full xterm terminal, in your browser",
|
||||
"version": "3.2.0",
|
||||
"version": "3.3.0",
|
||||
"ignore": [
|
||||
"demo",
|
||||
"test",
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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
@@ -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],
|
||||
|
||||
@@ -17,7 +17,7 @@ describe('CompositionHelper', () => {
|
||||
compositionView = {
|
||||
classList: {
|
||||
add: () => {},
|
||||
remove: () => {},
|
||||
remove: () => {}
|
||||
},
|
||||
getBoundingClientRect: () => {
|
||||
return { width: 0 };
|
||||
|
||||
+61
-61
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -18,9 +18,6 @@ class TestSelectionModel extends SelectionModel {
|
||||
}
|
||||
|
||||
describe('SelectionManager', () => {
|
||||
let window: Window;
|
||||
let document: Document;
|
||||
|
||||
let terminal: ITerminal;
|
||||
let model: TestSelectionModel;
|
||||
|
||||
|
||||
+3
-3
@@ -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(',');
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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 = '';
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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}`;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 +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');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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
@@ -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
@@ -5,7 +5,8 @@
|
||||
"rootDir": "src",
|
||||
"outDir": "lib",
|
||||
"sourceMap": true,
|
||||
"removeComments": true
|
||||
"removeComments": true,
|
||||
"noUnusedLocals": true
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
|
||||
+12
@@ -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"
|
||||
|
||||
Vendored
+34
-1
@@ -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.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user