Merge remote-tracking branch 'origin/master' into 1248_tslint_curly
This commit is contained in:
+1
-5
@@ -19,10 +19,6 @@ fixtures/typings-test/*.js
|
|||||||
# Directories needed for code coverage
|
# Directories needed for code coverage
|
||||||
/coverage/
|
/coverage/
|
||||||
|
|
||||||
# Keep legacy files out of the repo, this can be removed when we merge v3 into master
|
# Keep bundled code out of Git
|
||||||
dist/
|
dist/
|
||||||
src/utils/TestUtils.ts
|
|
||||||
src/xterm.js
|
|
||||||
|
|
||||||
# Keep the demo builds out of Git
|
|
||||||
demo/dist/
|
demo/dist/
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ before_install:
|
|||||||
- npm install -g npm@5.1.0
|
- npm install -g npm@5.1.0
|
||||||
env:
|
env:
|
||||||
matrix:
|
matrix:
|
||||||
|
- NPM_COMMAND=tsc
|
||||||
- NPM_COMMAND=lint
|
- NPM_COMMAND=lint
|
||||||
- NPM_COMMAND=test
|
- NPM_COMMAND=test
|
||||||
notifications:
|
notifications:
|
||||||
|
|||||||
@@ -125,6 +125,7 @@ computational environment for Jupyter, supporting interactive data science and s
|
|||||||
- [**rtty**](https://github.com/zhaojh329/rtty): A reverse proxy WebTTY. It is composed of the client and the server.
|
- [**rtty**](https://github.com/zhaojh329/rtty): A reverse proxy WebTTY. It is composed of the client and the server.
|
||||||
- [**Pisth**](https://github.com/ColdGrub1384/Pisth): An SFTP and SSH client for iOS
|
- [**Pisth**](https://github.com/ColdGrub1384/Pisth): An SFTP and SSH client for iOS
|
||||||
- [**abstruse**](https://github.com/bleenco/abstruse): Abstruse CI is a continuous integration platform based on Node.JS and Docker.
|
- [**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
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
|
|||||||
@@ -147,6 +147,7 @@ namespace methods_core {
|
|||||||
const r23: boolean = t.getOption('macOptionIsMeta');
|
const r23: boolean = t.getOption('macOptionIsMeta');
|
||||||
const r24: string = t.getOption('fontWeight');
|
const r24: string = t.getOption('fontWeight');
|
||||||
const r25: string = t.getOption('fontWeightBold');
|
const r25: string = t.getOption('fontWeightBold');
|
||||||
|
const r26: boolean = t.getOption('allowTransparency');
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
const t: Terminal = new Terminal();
|
const t: Terminal = new Terminal();
|
||||||
@@ -167,6 +168,7 @@ namespace methods_core {
|
|||||||
t.setOption('popOnBell', true);
|
t.setOption('popOnBell', true);
|
||||||
t.setOption('screenKeys', true);
|
t.setOption('screenKeys', true);
|
||||||
t.setOption('useFlowControl', true);
|
t.setOption('useFlowControl', true);
|
||||||
|
t.setOption('allowTransparency', true);
|
||||||
t.setOption('visualBell', true);
|
t.setOption('visualBell', true);
|
||||||
t.setOption('colors', ['a', 'b']);
|
t.setOption('colors', ['a', 'b']);
|
||||||
t.setOption('letterSpacing', 1);
|
t.setOption('letterSpacing', 1);
|
||||||
|
|||||||
@@ -82,6 +82,7 @@
|
|||||||
"lint": "tslint src/*.ts src/**/*.ts",
|
"lint": "tslint src/*.ts src/**/*.ts",
|
||||||
"test": "gulp test",
|
"test": "gulp test",
|
||||||
"build:docs": "jsdoc -c jsdoc.json",
|
"build:docs": "jsdoc -c jsdoc.json",
|
||||||
|
"tsc": "tsc",
|
||||||
"build": "gulp build",
|
"build": "gulp build",
|
||||||
"prepublish": "npm run build",
|
"prepublish": "npm run build",
|
||||||
"coveralls": "gulp coveralls",
|
"coveralls": "gulp coveralls",
|
||||||
|
|||||||
+4
-1
@@ -213,7 +213,10 @@ export class Buffer implements IBuffer {
|
|||||||
// needed here because some chars are 0 characters long (eg. after wide
|
// needed here because some chars are 0 characters long (eg. after wide
|
||||||
// chars) and some chars are longer than 1 characters long (eg. emojis).
|
// chars) and some chars are longer than 1 characters long (eg. emojis).
|
||||||
let startIndex = startCol;
|
let startIndex = startCol;
|
||||||
endCol = endCol || line.length;
|
// Only set endCol to the line length when it is null. 0 is a valid column.
|
||||||
|
if (endCol === null) {
|
||||||
|
endCol = line.length;
|
||||||
|
}
|
||||||
let endIndex = endCol;
|
let endIndex = endCol;
|
||||||
|
|
||||||
for (let i = 0; i < line.length; i++) {
|
for (let i = 0; i < line.length; i++) {
|
||||||
|
|||||||
+15
-1
@@ -11,6 +11,7 @@ import { CircularList } from './utils/CircularList';
|
|||||||
import { EventEmitter } from './EventEmitter';
|
import { EventEmitter } from './EventEmitter';
|
||||||
import { SelectionModel } from './SelectionModel';
|
import { SelectionModel } from './SelectionModel';
|
||||||
import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from './Buffer';
|
import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from './Buffer';
|
||||||
|
import { AltClickHandler } from './handlers/AltClickHandler';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The number of pixels the mouse needs to be above or below the viewport in
|
* The number of pixels the mouse needs to be above or below the viewport in
|
||||||
@@ -28,6 +29,12 @@ const DRAG_SCROLL_MAX_SPEED = 15;
|
|||||||
*/
|
*/
|
||||||
const DRAG_SCROLL_INTERVAL = 50;
|
const DRAG_SCROLL_INTERVAL = 50;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum amount of time that can have elapsed for an alt click to move the
|
||||||
|
* cursor.
|
||||||
|
*/
|
||||||
|
const ALT_CLICK_MOVE_CURSOR_TIME = 500;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A string containing all characters that are considered word separated by the
|
* A string containing all characters that are considered word separated by the
|
||||||
* double click to select work logic.
|
* double click to select work logic.
|
||||||
@@ -96,6 +103,8 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
|
|||||||
private _mouseUpListener: EventListener;
|
private _mouseUpListener: EventListener;
|
||||||
private _trimListener: (...args: any[]) => void;
|
private _trimListener: (...args: any[]) => void;
|
||||||
|
|
||||||
|
private _mouseDownTimeStamp: number;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private _terminal: ITerminal,
|
private _terminal: ITerminal,
|
||||||
private _charMeasure: CharMeasure
|
private _charMeasure: CharMeasure
|
||||||
@@ -317,6 +326,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
|
|||||||
* @param event The mousedown event.
|
* @param event The mousedown event.
|
||||||
*/
|
*/
|
||||||
public onMouseDown(event: MouseEvent): void {
|
public onMouseDown(event: MouseEvent): void {
|
||||||
|
this._mouseDownTimeStamp = event.timeStamp;
|
||||||
// If we have selection, we want the context menu on right click even if the
|
// If we have selection, we want the context menu on right click even if the
|
||||||
// terminal is in mouse mode.
|
// terminal is in mouse mode.
|
||||||
if (event.button === 2 && this.hasSelection) {
|
if (event.button === 2 && this.hasSelection) {
|
||||||
@@ -536,9 +546,13 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
|
|||||||
* @param event The mouseup event.
|
* @param event The mouseup event.
|
||||||
*/
|
*/
|
||||||
private _onMouseUp(event: MouseEvent): void {
|
private _onMouseUp(event: MouseEvent): void {
|
||||||
|
let timeElapsed = event.timeStamp - this._mouseDownTimeStamp;
|
||||||
|
|
||||||
this._removeMouseDownListeners();
|
this._removeMouseDownListeners();
|
||||||
|
|
||||||
if (this.hasSelection) {
|
if (this.selectionText.length <= 1 && timeElapsed < ALT_CLICK_MOVE_CURSOR_TIME) {
|
||||||
|
(new AltClickHandler(event, this._terminal)).move();
|
||||||
|
} else if (this.hasSelection) {
|
||||||
this._terminal.emit('selection');
|
this._terminal.emit('selection');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ const DEFAULT_OPTIONS: ITerminalOptions = {
|
|||||||
cancelEvents: false,
|
cancelEvents: false,
|
||||||
disableStdin: false,
|
disableStdin: false,
|
||||||
useFlowControl: false,
|
useFlowControl: false,
|
||||||
|
allowTransparency: false,
|
||||||
tabStopWidth: 8,
|
tabStopWidth: 8,
|
||||||
theme: null
|
theme: null
|
||||||
// programFeatures: false,
|
// programFeatures: false,
|
||||||
|
|||||||
@@ -188,12 +188,14 @@ export interface ITerminal extends PublicTerminal, IElementAccessor, IBufferAcce
|
|||||||
isFocused: boolean;
|
isFocused: boolean;
|
||||||
mouseHelper: IMouseHelper;
|
mouseHelper: IMouseHelper;
|
||||||
bracketedPasteMode: boolean;
|
bracketedPasteMode: boolean;
|
||||||
|
applicationCursor: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Emit the 'data' event and populate the given data.
|
* Emit the 'data' event and populate the given data.
|
||||||
* @param data The data to populate in the event.
|
* @param data The data to populate in the event.
|
||||||
*/
|
*/
|
||||||
handler(data: string): void;
|
handler(data: string): void;
|
||||||
|
send(data: string): void;
|
||||||
scrollLines(disp: number, suppressScrollEvent?: boolean): void;
|
scrollLines(disp: number, suppressScrollEvent?: boolean): void;
|
||||||
cancel(ev: Event, force?: boolean): boolean | void;
|
cancel(ev: Event, force?: boolean): boolean | void;
|
||||||
log(text: string): void;
|
log(text: string): void;
|
||||||
|
|||||||
@@ -0,0 +1,261 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
|
||||||
|
* @license MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { ITerminal, ICircularList, LineData } from '../Types';
|
||||||
|
import { C0 } from '../EscapeSequences';
|
||||||
|
|
||||||
|
enum Direction {
|
||||||
|
Up = 'A',
|
||||||
|
Down = 'B',
|
||||||
|
Right = 'C',
|
||||||
|
Left = 'D'
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AltClickHandler {
|
||||||
|
private _startRow: number;
|
||||||
|
private _startCol: number;
|
||||||
|
private _endRow: number;
|
||||||
|
private _endCol: number;
|
||||||
|
private _lines: ICircularList<LineData>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private _mouseEvent: MouseEvent,
|
||||||
|
private _terminal: ITerminal
|
||||||
|
) {
|
||||||
|
this._lines = this._terminal.buffer.lines;
|
||||||
|
this._startCol = this._terminal.buffer.x;
|
||||||
|
this._startRow = this._terminal.buffer.y;
|
||||||
|
|
||||||
|
[this._endCol, this._endRow] = this._terminal.mouseHelper.getCoords(
|
||||||
|
this._mouseEvent,
|
||||||
|
this._terminal.element,
|
||||||
|
this._terminal.charMeasure,
|
||||||
|
this._terminal.options.lineHeight,
|
||||||
|
this._terminal.cols,
|
||||||
|
this._terminal.rows,
|
||||||
|
false
|
||||||
|
).map((coordinate: number) => {
|
||||||
|
return coordinate - 1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes the escape sequences of arrows to the terminal
|
||||||
|
*/
|
||||||
|
public move(): void {
|
||||||
|
if (this._mouseEvent.altKey) {
|
||||||
|
this._terminal.send(this._arrowSequences());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Concatenates all the arrow sequences together.
|
||||||
|
* Resets the starting row to an unwrapped row, moves to the requested row,
|
||||||
|
* then moves to requested col.
|
||||||
|
*/
|
||||||
|
private _arrowSequences(): string {
|
||||||
|
return this._resetStartingRow() +
|
||||||
|
this._moveToRequestedRow() +
|
||||||
|
this._moveToRequestedCol();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the initial position of the cursor is on a row that is wrapped, move the
|
||||||
|
* cursor up to the first row that is not wrapped to have accurate vertical
|
||||||
|
* positioning.
|
||||||
|
*/
|
||||||
|
private _resetStartingRow(): string {
|
||||||
|
let startRow = this._endRow - this._wrappedRowsForRow(this._endRow);
|
||||||
|
let endRow = this._endRow;
|
||||||
|
|
||||||
|
if (this._moveToRequestedRow().length === 0) {
|
||||||
|
return '';
|
||||||
|
} else {
|
||||||
|
return repeat(this._bufferLine(
|
||||||
|
this._startCol, this._startRow, this._startCol,
|
||||||
|
this._startRow - this._wrappedRowsForRow(this._startRow), false
|
||||||
|
).length, this._sequence(Direction.Left));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Using the reset starting and ending row, move to the requested row,
|
||||||
|
* ignoring wrapped rows
|
||||||
|
*/
|
||||||
|
private _moveToRequestedRow(): string {
|
||||||
|
let startRow = this._startRow - this._wrappedRowsForRow(this._startRow);
|
||||||
|
let endRow = this._endRow - this._wrappedRowsForRow(this._endRow);
|
||||||
|
|
||||||
|
let rowsToMove = Math.abs(startRow - endRow) - this._wrappedRowsCount();
|
||||||
|
|
||||||
|
return repeat(rowsToMove, this._sequence(this._verticalDirection()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move to the requested col on the ending row
|
||||||
|
*/
|
||||||
|
private _moveToRequestedCol(): string {
|
||||||
|
let startRow;
|
||||||
|
if (this._moveToRequestedRow().length > 0) {
|
||||||
|
startRow = this._endRow - this._wrappedRowsForRow(this._endRow);
|
||||||
|
} else {
|
||||||
|
startRow = this._startRow;
|
||||||
|
}
|
||||||
|
|
||||||
|
let endRow = this._endRow;
|
||||||
|
let direction = this._horizontalDirection();
|
||||||
|
|
||||||
|
return repeat(this._bufferLine(
|
||||||
|
this._startCol, startRow, this._endCol, endRow,
|
||||||
|
direction === Direction.Right
|
||||||
|
).length, this._sequence(direction));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility functions
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the number of wrapped rows between the unwrapped starting and
|
||||||
|
* ending rows. These rows need to ignored since the cursor skips over them.
|
||||||
|
*/
|
||||||
|
private _wrappedRowsCount(): number {
|
||||||
|
let wrappedRows = 0;
|
||||||
|
let startRow = this._startRow - this._wrappedRowsForRow(this._startRow);
|
||||||
|
let endRow = this._endRow - this._wrappedRowsForRow(this._endRow);
|
||||||
|
|
||||||
|
for (let i = 0; i < Math.abs(startRow - endRow); i++) {
|
||||||
|
let direction = this._verticalDirection() === Direction.Up ? -1 : 1;
|
||||||
|
|
||||||
|
if ((<any>this._lines.get(startRow + (direction * i))).isWrapped) {
|
||||||
|
wrappedRows++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return wrappedRows;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the number of wrapped rows that make up a given row.
|
||||||
|
* @param currentRow The row to determine how many wrapped rows make it up
|
||||||
|
*/
|
||||||
|
private _wrappedRowsForRow(currentRow: number): number {
|
||||||
|
let rowCount = 0;
|
||||||
|
let lineWraps = (<any>this._lines.get(currentRow)).isWrapped;
|
||||||
|
|
||||||
|
while (lineWraps && currentRow >= 0 && currentRow < this._terminal.rows) {
|
||||||
|
rowCount++;
|
||||||
|
currentRow--;
|
||||||
|
lineWraps = (<any>this._lines.get(currentRow)).isWrapped;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rowCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Direction determiners
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if the right or left arrow is needed
|
||||||
|
*/
|
||||||
|
private _horizontalDirection(): Direction {
|
||||||
|
let startRow;
|
||||||
|
if (this._moveToRequestedRow().length > 0) {
|
||||||
|
startRow = this._endRow - this._wrappedRowsForRow(this._endRow);
|
||||||
|
} else {
|
||||||
|
startRow = this._startRow;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((this._startCol < this._endCol &&
|
||||||
|
startRow <= this._endRow) || // down/right or same y/right
|
||||||
|
(this._startCol >= this._endCol &&
|
||||||
|
startRow < this._endRow)) { // down/left or same y/left
|
||||||
|
return Direction.Right;
|
||||||
|
} else {
|
||||||
|
return Direction.Left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if the up or down arrow is needed
|
||||||
|
*/
|
||||||
|
private _verticalDirection(): Direction {
|
||||||
|
if (this._startRow > this._endRow) {
|
||||||
|
return Direction.Up;
|
||||||
|
} else {
|
||||||
|
return Direction.Down;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs the string of chars in the buffer from a starting row and col
|
||||||
|
* to an ending row and col
|
||||||
|
* @param startCol The starting column position
|
||||||
|
* @param startRow The starting row position
|
||||||
|
* @param endCol The ending column position
|
||||||
|
* @param endRow The ending row position
|
||||||
|
* @param forward Direction to move
|
||||||
|
*/
|
||||||
|
private _bufferLine(
|
||||||
|
startCol: number,
|
||||||
|
startRow: number,
|
||||||
|
endCol: number,
|
||||||
|
endRow: number,
|
||||||
|
forward: boolean
|
||||||
|
): string {
|
||||||
|
let currentCol = startCol;
|
||||||
|
let currentRow = startRow;
|
||||||
|
let bufferStr = '';
|
||||||
|
|
||||||
|
while (currentCol !== endCol || currentRow !== endRow) {
|
||||||
|
currentCol += forward ? 1 : -1;
|
||||||
|
|
||||||
|
if (forward && currentCol > this._terminal.cols - 1) {
|
||||||
|
bufferStr += this._terminal.buffer.translateBufferLineToString(
|
||||||
|
currentRow, false, startCol, currentCol
|
||||||
|
);
|
||||||
|
currentCol = 0;
|
||||||
|
startCol = 0;
|
||||||
|
currentRow++;
|
||||||
|
} else if (!forward && currentCol < 0) {
|
||||||
|
bufferStr += this._terminal.buffer.translateBufferLineToString(
|
||||||
|
currentRow, false, 0, startCol + 1
|
||||||
|
);
|
||||||
|
currentCol = this._terminal.cols - 1;
|
||||||
|
startCol = currentCol;
|
||||||
|
currentRow--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return bufferStr + this._terminal.buffer.translateBufferLineToString(
|
||||||
|
currentRow, false, startCol, currentCol
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs the escape sequence for clicking an arrow
|
||||||
|
* @param direction The direction to move
|
||||||
|
*/
|
||||||
|
private _sequence(direction: Direction): string {
|
||||||
|
const mod = this._terminal.applicationCursor ? 'O' : '[';
|
||||||
|
return C0.ESC + mod + direction;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a string repeated a given number of times
|
||||||
|
* Polyfill from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/repeat
|
||||||
|
* @param count The number of times to repeat the string
|
||||||
|
* @param string The string that is to be repeated
|
||||||
|
*/
|
||||||
|
function repeat(count: number, str: string): string {
|
||||||
|
count = Math.floor(count);
|
||||||
|
let rpt = '';
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
rpt += str;
|
||||||
|
}
|
||||||
|
return rpt;
|
||||||
|
}
|
||||||
@@ -100,6 +100,7 @@ export function moveTextAreaUnderMouseCursor(ev: MouseEvent, textarea: HTMLTextA
|
|||||||
textarea.focus();
|
textarea.focus();
|
||||||
|
|
||||||
// Reset the terminal textarea's styling
|
// Reset the terminal textarea's styling
|
||||||
|
// Timeout needs to be long enough for click event to be handled.
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
textarea.style.position = null;
|
textarea.style.position = null;
|
||||||
textarea.style.width = null;
|
textarea.style.width = null;
|
||||||
@@ -107,7 +108,7 @@ export function moveTextAreaUnderMouseCursor(ev: MouseEvent, textarea: HTMLTextA
|
|||||||
textarea.style.left = null;
|
textarea.style.left = null;
|
||||||
textarea.style.top = null;
|
textarea.style.top = null;
|
||||||
textarea.style.zIndex = null;
|
textarea.style.zIndex = null;
|
||||||
}, 4);
|
}, 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -24,22 +24,25 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
|||||||
private _charAtlas: HTMLCanvasElement | ImageBitmap;
|
private _charAtlas: HTMLCanvasElement | ImageBitmap;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
container: HTMLElement,
|
private _container: HTMLElement,
|
||||||
id: string,
|
id: string,
|
||||||
zIndex: number,
|
zIndex: number,
|
||||||
private _alpha: boolean,
|
private _alpha: boolean,
|
||||||
protected _colors: IColorSet
|
protected _colors: IColorSet
|
||||||
) {
|
) {
|
||||||
this._canvas = document.createElement('canvas');
|
this._canvas = document.createElement('canvas');
|
||||||
this._canvas.id = `xterm-${id}-layer`;
|
this._canvas.classList.add(`xterm-${id}-layer`);
|
||||||
this._canvas.style.zIndex = zIndex.toString();
|
this._canvas.style.zIndex = zIndex.toString();
|
||||||
this._ctx = this._canvas.getContext('2d', {alpha: _alpha});
|
this._initCanvas();
|
||||||
this._ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
|
this._container.appendChild(this._canvas);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _initCanvas(): void {
|
||||||
|
this._ctx = this._canvas.getContext('2d', {alpha: this._alpha});
|
||||||
// Draw the background if this is an opaque layer
|
// Draw the background if this is an opaque layer
|
||||||
if (!_alpha) {
|
if (!this._alpha) {
|
||||||
this.clearAll();
|
this.clearAll();
|
||||||
}
|
}
|
||||||
container.appendChild(this._canvas);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public onOptionsChanged(terminal: ITerminal): void {}
|
public onOptionsChanged(terminal: ITerminal): void {}
|
||||||
@@ -53,6 +56,25 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
|||||||
this._refreshCharAtlas(terminal, colorSet);
|
this._refreshCharAtlas(terminal, colorSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected setTransparency(terminal: ITerminal, alpha: boolean): void {
|
||||||
|
// Do nothing when alpha doesn't change
|
||||||
|
if (alpha === this._alpha) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new canvas and replace old one
|
||||||
|
const oldCanvas = this._canvas;
|
||||||
|
this._alpha = alpha;
|
||||||
|
// Cloning preserves properties
|
||||||
|
this._canvas = <HTMLCanvasElement>this._canvas.cloneNode();
|
||||||
|
this._initCanvas();
|
||||||
|
this._container.replaceChild(this._canvas, oldCanvas);
|
||||||
|
|
||||||
|
// Regenerate char atlas and force a full redraw
|
||||||
|
this._refreshCharAtlas(terminal, this._colors);
|
||||||
|
this.onGridChanged(terminal, 0, terminal.rows - 1);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Refreshes the char atlas, aquiring a new one if necessary.
|
* Refreshes the char atlas, aquiring a new one if necessary.
|
||||||
* @param terminal The terminal.
|
* @param terminal The terminal.
|
||||||
@@ -63,7 +85,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._charAtlas = null;
|
this._charAtlas = null;
|
||||||
const result = acquireCharAtlas(terminal, this._colors, this._scaledCharWidth, this._scaledCharHeight);
|
const result = acquireCharAtlas(terminal, colorSet, this._scaledCharWidth, this._scaledCharHeight);
|
||||||
if (result instanceof HTMLCanvasElement) {
|
if (result instanceof HTMLCanvasElement) {
|
||||||
this._charAtlas = result;
|
this._charAtlas = result;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ interface ICharAtlasConfig {
|
|||||||
fontWeightBold: string;
|
fontWeightBold: string;
|
||||||
scaledCharWidth: number;
|
scaledCharWidth: number;
|
||||||
scaledCharHeight: number;
|
scaledCharHeight: number;
|
||||||
|
allowTransparency: boolean;
|
||||||
colors: IColorSet;
|
colors: IColorSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,7 +84,8 @@ export function acquireCharAtlas(terminal: ITerminal, colors: IColorSet, scaledC
|
|||||||
background: colors.background,
|
background: colors.background,
|
||||||
foreground: colors.foreground,
|
foreground: colors.foreground,
|
||||||
ansiColors: colors.ansi,
|
ansiColors: colors.ansi,
|
||||||
devicePixelRatio: window.devicePixelRatio
|
devicePixelRatio: window.devicePixelRatio,
|
||||||
|
allowTransparency: terminal.options.allowTransparency
|
||||||
};
|
};
|
||||||
|
|
||||||
const newEntry: ICharAtlasCacheEntry = {
|
const newEntry: ICharAtlasCacheEntry = {
|
||||||
@@ -111,6 +113,7 @@ function generateConfig(scaledCharWidth: number, scaledCharHeight: number, termi
|
|||||||
fontSize: terminal.options.fontSize,
|
fontSize: terminal.options.fontSize,
|
||||||
fontWeight: terminal.options.fontWeight,
|
fontWeight: terminal.options.fontWeight,
|
||||||
fontWeightBold: terminal.options.fontWeightBold,
|
fontWeightBold: terminal.options.fontWeightBold,
|
||||||
|
allowTransparency: terminal.options.allowTransparency,
|
||||||
colors: clonedColors
|
colors: clonedColors
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -125,6 +128,7 @@ function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean {
|
|||||||
a.fontSize === b.fontSize &&
|
a.fontSize === b.fontSize &&
|
||||||
a.fontWeight === b.fontWeight &&
|
a.fontWeight === b.fontWeight &&
|
||||||
a.fontWeightBold === b.fontWeightBold &&
|
a.fontWeightBold === b.fontWeightBold &&
|
||||||
|
a.allowTransparency === b.allowTransparency &&
|
||||||
a.scaledCharWidth === b.scaledCharWidth &&
|
a.scaledCharWidth === b.scaledCharWidth &&
|
||||||
a.scaledCharHeight === b.scaledCharHeight &&
|
a.scaledCharHeight === b.scaledCharHeight &&
|
||||||
a.colors.foreground === b.colors.foreground &&
|
a.colors.foreground === b.colors.foreground &&
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ export class ColorManager implements IColorManager {
|
|||||||
*/
|
*/
|
||||||
public setTheme(theme: ITheme): void {
|
public setTheme(theme: ITheme): void {
|
||||||
this.colors.foreground = theme.foreground || DEFAULT_FOREGROUND;
|
this.colors.foreground = theme.foreground || DEFAULT_FOREGROUND;
|
||||||
this.colors.background = this._validateColor(theme.background, DEFAULT_BACKGROUND);
|
this.colors.background = theme.background || DEFAULT_BACKGROUND;
|
||||||
this.colors.cursor = theme.cursor || DEFAULT_CURSOR;
|
this.colors.cursor = theme.cursor || DEFAULT_CURSOR;
|
||||||
this.colors.cursorAccent = theme.cursorAccent || DEFAULT_CURSOR_ACCENT;
|
this.colors.cursorAccent = theme.cursorAccent || DEFAULT_CURSOR_ACCENT;
|
||||||
this.colors.selection = theme.selection || DEFAULT_SELECTION;
|
this.colors.selection = theme.selection || DEFAULT_SELECTION;
|
||||||
@@ -107,20 +107,4 @@ export class ColorManager implements IColorManager {
|
|||||||
this.colors.ansi[14] = theme.brightCyan || DEFAULT_ANSI_COLORS[14];
|
this.colors.ansi[14] = theme.brightCyan || DEFAULT_ANSI_COLORS[14];
|
||||||
this.colors.ansi[15] = theme.brightWhite || DEFAULT_ANSI_COLORS[15];
|
this.colors.ansi[15] = theme.brightWhite || DEFAULT_ANSI_COLORS[15];
|
||||||
}
|
}
|
||||||
|
|
||||||
private _validateColor(color: string, fallback: string): string {
|
|
||||||
if (!color) {
|
|
||||||
return fallback;
|
|
||||||
}
|
|
||||||
if (color.length === 7 && color.charAt(0) === '#') {
|
|
||||||
return color;
|
|
||||||
}
|
|
||||||
if (color.length === 4 && color.charAt(0) === '#') {
|
|
||||||
const r = color.charAt(1);
|
|
||||||
const g = color.charAt(2);
|
|
||||||
const b = color.charAt(3);
|
|
||||||
return `#${r}${r}${g}${g}${b}${b}`;
|
|
||||||
}
|
|
||||||
return fallback;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,8 +36,9 @@ export class Renderer extends EventEmitter implements IRenderer {
|
|||||||
if (theme) {
|
if (theme) {
|
||||||
this.colorManager.setTheme(theme);
|
this.colorManager.setTheme(theme);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._renderLayers = [
|
this._renderLayers = [
|
||||||
new TextRenderLayer(this._terminal.element, 0, this.colorManager.colors),
|
new TextRenderLayer(this._terminal.element, 0, this.colorManager.colors, this._terminal.options.allowTransparency),
|
||||||
new SelectionRenderLayer(this._terminal.element, 1, this.colorManager.colors),
|
new SelectionRenderLayer(this._terminal.element, 1, this.colorManager.colors),
|
||||||
new LinkRenderLayer(this._terminal.element, 2, this.colorManager.colors, this._terminal),
|
new LinkRenderLayer(this._terminal.element, 2, this.colorManager.colors, this._terminal),
|
||||||
new CursorRenderLayer(this._terminal.element, 3, this.colorManager.colors)
|
new CursorRenderLayer(this._terminal.element, 3, this.colorManager.colors)
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ export class TextRenderLayer extends BaseRenderLayer {
|
|||||||
private _characterFont: string;
|
private _characterFont: string;
|
||||||
private _characterOverlapCache: { [key: string]: boolean } = {};
|
private _characterOverlapCache: { [key: string]: boolean } = {};
|
||||||
|
|
||||||
constructor(container: HTMLElement, zIndex: number, colors: IColorSet) {
|
constructor(container: HTMLElement, zIndex: number, colors: IColorSet, alpha: boolean) {
|
||||||
super(container, 'text', zIndex, false, colors);
|
super(container, 'text', zIndex, alpha, colors);
|
||||||
this._state = new GridCache<CharData>();
|
this._state = new GridCache<CharData>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,6 +190,10 @@ export class TextRenderLayer extends BaseRenderLayer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public onOptionsChanged(terminal: ITerminal): void {
|
||||||
|
this.setTransparency(terminal, terminal.options.allowTransparency);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether a character is overlapping to the next cell.
|
* Whether a character is overlapping to the next cell.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ export interface ICharAtlasRequest {
|
|||||||
foreground: string;
|
foreground: string;
|
||||||
ansiColors: string[];
|
ansiColors: string[];
|
||||||
devicePixelRatio: number;
|
devicePixelRatio: number;
|
||||||
|
allowTransparency: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CHAR_ATLAS_CELL_SPACING = 1;
|
export const CHAR_ATLAS_CELL_SPACING = 1;
|
||||||
@@ -43,7 +44,7 @@ export function generateCharAtlas(context: Window, canvasFactory: (width: number
|
|||||||
/*255 ascii chars*/255 * cellWidth,
|
/*255 ascii chars*/255 * cellWidth,
|
||||||
(/*default+default bold*/2 + /*0-15*/16) * cellHeight
|
(/*default+default bold*/2 + /*0-15*/16) * cellHeight
|
||||||
);
|
);
|
||||||
const ctx = canvas.getContext('2d', {alpha: false});
|
const ctx = canvas.getContext('2d', {alpha: request.allowTransparency});
|
||||||
|
|
||||||
ctx.fillStyle = request.background;
|
ctx.fillStyle = request.background;
|
||||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||||
|
|||||||
@@ -70,6 +70,9 @@ export class MockTerminal implements ITerminal {
|
|||||||
write(data: string): void {
|
write(data: string): void {
|
||||||
throw new Error('Method not implemented.');
|
throw new Error('Method not implemented.');
|
||||||
}
|
}
|
||||||
|
send(data: string): void {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
bracketedPasteMode: boolean;
|
bracketedPasteMode: boolean;
|
||||||
mouseHelper: IMouseHelper;
|
mouseHelper: IMouseHelper;
|
||||||
renderer: IRenderer;
|
renderer: IRenderer;
|
||||||
@@ -93,6 +96,7 @@ export class MockTerminal implements ITerminal {
|
|||||||
scrollback: number;
|
scrollback: number;
|
||||||
buffers: IBufferSet;
|
buffers: IBufferSet;
|
||||||
buffer: IBuffer;
|
buffer: IBuffer;
|
||||||
|
applicationCursor: boolean;
|
||||||
handler(data: string): void {
|
handler(data: string): void {
|
||||||
throw new Error('Method not implemented.');
|
throw new Error('Method not implemented.');
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+8
-2
@@ -17,6 +17,12 @@ declare module 'xterm' {
|
|||||||
* An object containing start up options for the terminal.
|
* An object containing start up options for the terminal.
|
||||||
*/
|
*/
|
||||||
export interface ITerminalOptions {
|
export interface ITerminalOptions {
|
||||||
|
/**
|
||||||
|
* Whether background should support non-opaque color. It must be set before
|
||||||
|
* executing open() method and can't be changed later without excuting it again.
|
||||||
|
* Warning: Enabling this option can reduce performances somewhat.
|
||||||
|
*/
|
||||||
|
allowTransparency?: boolean;
|
||||||
/**
|
/**
|
||||||
* A data uri of the sound to use for the bell (needs bellStyle = 'sound').
|
* A data uri of the sound to use for the bell (needs bellStyle = 'sound').
|
||||||
*/
|
*/
|
||||||
@@ -421,7 +427,7 @@ declare module 'xterm' {
|
|||||||
* Retrieves an option's value from the terminal.
|
* Retrieves an option's value from the terminal.
|
||||||
* @param key The option key.
|
* @param key The option key.
|
||||||
*/
|
*/
|
||||||
getOption(key: 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'debug' | 'disableStdin' | 'enableBold' | 'macOptionIsMeta' | 'popOnBell' | 'screenKeys' | 'useFlowControl' | 'visualBell'): boolean;
|
getOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'debug' | 'disableStdin' | 'enableBold' | 'macOptionIsMeta' | 'popOnBell' | 'screenKeys' | 'useFlowControl' | 'visualBell'): boolean;
|
||||||
/**
|
/**
|
||||||
* Retrieves an option's value from the terminal.
|
* Retrieves an option's value from the terminal.
|
||||||
* @param key The option key.
|
* @param key The option key.
|
||||||
@@ -472,7 +478,7 @@ declare module 'xterm' {
|
|||||||
* @param key The option key.
|
* @param key The option key.
|
||||||
* @param value The option value.
|
* @param value The option value.
|
||||||
*/
|
*/
|
||||||
setOption(key: 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'debug' | 'disableStdin' | 'enableBold' | 'macOptionIsMeta' | 'popOnBell' | 'screenKeys' | 'useFlowControl' | 'visualBell', value: boolean): void;
|
setOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'debug' | 'disableStdin' | 'enableBold' | 'macOptionIsMeta' | 'popOnBell' | 'screenKeys' | 'useFlowControl' | 'visualBell', value: boolean): void;
|
||||||
/**
|
/**
|
||||||
* Sets an option on the terminal.
|
* Sets an option on the terminal.
|
||||||
* @param key The option key.
|
* @param key The option key.
|
||||||
|
|||||||
Reference in New Issue
Block a user