Merge branch 'master' into new_parser

This commit is contained in:
Jörg Breitbart
2018-04-29 19:30:35 +02:00
11 changed files with 162 additions and 122 deletions
+1 -1
View File
@@ -41,7 +41,7 @@ To start using xterm.js on your browser, add the `xterm.js` and `xterm.css` to t
<script>
var term = new Terminal();
term.open(document.getElementById('terminal'));
term.write('Hello from \033[1;3;31mxterm.js\033[0m $ ')
      term.write('Hello from \x1B[1;3;31mxterm.js\x1B[0m $ ')
</script>
</body>
</html>
+1
View File
@@ -67,6 +67,7 @@
"npm-run-all": "^4.1.2",
"sorcery": "^0.10.0",
"tslint": "^5.9.1",
"tslint-consistent-codestyle": "^1.13.0",
"typescript": "~2.7.1",
"vinyl-buffer": "^1.0.0",
"vinyl-source-stream": "^1.1.0",
+11 -11
View File
@@ -12,9 +12,9 @@ import { IDisposable } from 'xterm';
const MAX_ROWS_TO_READ = 20;
enum BoundaryPosition {
Top,
Bottom
const enum BoundaryPosition {
TOP,
BOTTOM
}
export class AccessibilityManager implements IDisposable {
@@ -54,8 +54,8 @@ export class AccessibilityManager implements IDisposable {
this._rowContainer.appendChild(this._rowElements[i]);
}
this._topBoundaryFocusListener = e => this._onBoundaryFocus(e, BoundaryPosition.Top);
this._bottomBoundaryFocusListener = e => this._onBoundaryFocus(e, BoundaryPosition.Bottom);
this._topBoundaryFocusListener = e => this._onBoundaryFocus(e, BoundaryPosition.TOP);
this._bottomBoundaryFocusListener = e => this._onBoundaryFocus(e, BoundaryPosition.BOTTOM);
this._rowElements[0].addEventListener('focus', this._topBoundaryFocusListener);
this._rowElements[this._rowElements.length - 1].addEventListener('focus', this._bottomBoundaryFocusListener);
@@ -101,11 +101,11 @@ export class AccessibilityManager implements IDisposable {
private _onBoundaryFocus(e: FocusEvent, position: BoundaryPosition): void {
const boundaryElement = <HTMLElement>e.target;
const beforeBoundaryElement = this._rowElements[position === BoundaryPosition.Top ? 1 : this._rowElements.length - 2];
const beforeBoundaryElement = this._rowElements[position === BoundaryPosition.TOP ? 1 : this._rowElements.length - 2];
// Don't scroll if the buffer top has reached the end in that direction
const posInSet = boundaryElement.getAttribute('aria-posinset');
const lastRowPos = position === BoundaryPosition.Top ? '1' : `${this._terminal.buffer.lines.length}`;
const lastRowPos = position === BoundaryPosition.TOP ? '1' : `${this._terminal.buffer.lines.length}`;
if (posInSet === lastRowPos) {
return;
}
@@ -119,7 +119,7 @@ export class AccessibilityManager implements IDisposable {
// Remove old boundary element from array
let topBoundaryElement: HTMLElement;
let bottomBoundaryElement: HTMLElement;
if (position === BoundaryPosition.Top) {
if (position === BoundaryPosition.TOP) {
topBoundaryElement = boundaryElement;
bottomBoundaryElement = this._rowElements.pop()!;
this._rowContainer.removeChild(bottomBoundaryElement);
@@ -134,7 +134,7 @@ export class AccessibilityManager implements IDisposable {
bottomBoundaryElement.removeEventListener('focus', this._bottomBoundaryFocusListener);
// Add new element to array/DOM
if (position === BoundaryPosition.Top) {
if (position === BoundaryPosition.TOP) {
const newElement = this._createAccessibilityTreeNode();
this._rowElements.unshift(newElement);
this._rowContainer.insertAdjacentElement('afterbegin', newElement);
@@ -149,10 +149,10 @@ export class AccessibilityManager implements IDisposable {
this._rowElements[this._rowElements.length - 1].addEventListener('focus', this._bottomBoundaryFocusListener);
// Scroll up
this._terminal.scrollLines(position === BoundaryPosition.Top ? -1 : 1);
this._terminal.scrollLines(position === BoundaryPosition.TOP ? -1 : 1);
// Focus new boundary before element
this._rowElements[position === BoundaryPosition.Top ? 1 : this._rowElements.length - 2].focus();
this._rowElements[position === BoundaryPosition.TOP ? 1 : this._rowElements.length - 2].focus();
// Prevent the standard behavior
e.preventDefault();
+3 -4
View File
@@ -92,11 +92,10 @@ export class CompositionHelper {
} else if (ev.keyCode === 16 || ev.keyCode === 17 || ev.keyCode === 18) {
// Continue composing if the keyCode is a modifier key
return false;
} else {
// Finish composition immediately. This is mainly here for the case where enter is
// pressed and the handler needs to be triggered before the command is executed.
this._finalizeComposition(false);
}
// Finish composition immediately. This is mainly here for the case where enter is
// pressed and the handler needs to be triggered before the command is executed.
this._finalizeComposition(false);
}
if (ev.keyCode === 229) {
+1 -1
View File
@@ -150,7 +150,7 @@ csiStateHandler['s'] = (handler, params) => handler.saveCursor(params);
csiStateHandler['u'] = (handler, params) => handler.restoreCursor(params);
csiStateHandler[C0.CAN] = (handler, params, prefix, postfix, parser) => parser.setState(ParserState.NORMAL);
export enum ParserState {
export const enum ParserState {
NORMAL = 0,
ESCAPED = 1,
CSI_PARAM = 2,
+1 -1
View File
@@ -54,7 +54,7 @@ interface IWordPosition {
/**
* A selection mode, this drives how the selection behaves on mouse move.
*/
enum SelectionMode {
const enum SelectionMode {
NORMAL,
WORD,
LINE
+1 -1
View File
@@ -17,7 +17,7 @@ export type LineData = CharData[];
export type LinkMatcherHandler = (event: MouseEvent, uri: string) => void;
export type LinkMatcherValidationCallback = (uri: string, callback: (isValid: boolean) => void) => void;
export enum LinkHoverEventTypes {
export const enum LinkHoverEventTypes {
HOVER = 'linkhover',
TOOLTIP = 'linktooltip',
LEAVE = 'linkleave'
+15 -18
View File
@@ -6,11 +6,11 @@
import { ITerminal, ICircularList, LineData } from '../Types';
import { C0 } from '../EscapeSequences';
enum Direction {
Up = 'A',
Down = 'B',
Right = 'C',
Left = 'D'
const enum Direction {
UP = 'A',
DOWN = 'B',
RIGHT = 'C',
LEFT = 'D'
}
export class AltClickHandler {
@@ -73,12 +73,11 @@ export class AltClickHandler {
private _resetStartingRow(): string {
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));
}
return repeat(this._bufferLine(
this._startCol, this._startRow, this._startCol,
this._startRow - this._wrappedRowsForRow(this._startRow), false
).length, this._sequence(Direction.LEFT));
}
/**
@@ -110,7 +109,7 @@ export class AltClickHandler {
return repeat(this._bufferLine(
this._startCol, startRow, this._endCol, endRow,
direction === Direction.Right
direction === Direction.RIGHT
).length, this._sequence(direction));
}
@@ -133,7 +132,7 @@ export class AltClickHandler {
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;
let direction = this._verticalDirection() === Direction.UP ? -1 : 1;
if ((<any>this._lines.get(startRow + (direction * i))).isWrapped) {
wrappedRows++;
@@ -179,10 +178,9 @@ export class AltClickHandler {
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;
return Direction.RIGHT;
}
return Direction.LEFT;
}
/**
@@ -190,10 +188,9 @@ export class AltClickHandler {
*/
private _verticalDirection(): Direction {
if (this._startRow > this._endRow) {
return Direction.Up;
} else {
return Direction.Down;
return Direction.UP;
}
return Direction.DOWN;
}
/**
+113 -84
View File
@@ -48,21 +48,24 @@ export class TextRenderLayer extends BaseRenderLayer {
this.clearAll();
}
public onGridChanged(terminal: ITerminal, startRow: number, endRow: number): void {
// Resize has not been called yet
if (this._state.cache.length === 0) {
return;
}
for (let y = startRow; y <= endRow; y++) {
private _forEachCell(
terminal: ITerminal,
firstRow: number,
lastRow: number,
callback: (
code: number,
char: string,
width: number,
x: number,
y: number,
fg: number,
bg: number,
flags: number
) => void
): void {
for (let y = firstRow; y <= lastRow; y++) {
const row = y + terminal.buffer.ydisp;
const line = terminal.buffer.lines.get(row);
this.clearCells(0, y, terminal.cols, 1);
// for (let x = 0; x < terminal.cols; x++) {
// this._state.cache[x][y] = null;
// }
for (let x = 0; x < terminal.cols; x++) {
const charData = line[x];
const code: number = <number>charData[CHAR_DATA_CODE_INDEX];
@@ -73,51 +76,12 @@ export class TextRenderLayer extends BaseRenderLayer {
// The character to the left is a wide character, drawing is owned by
// the char at x-1
if (width === 0) {
// this._state.cache[x][y] = null;
continue;
}
// If the character is a space and the character to the left is an
// overlapping character, skip the character and allow the overlapping
// char to take full control over this character's cell.
if (code === 32 /*' '*/) {
if (x > 0) {
const previousChar: CharData = line[x - 1];
if (this._isOverlapping(previousChar)) {
continue;
}
}
}
// Skip rendering if the character is identical
// const state = this._state.cache[x][y];
// if (state && state[CHAR_DATA_CHAR_INDEX] === char && state[CHAR_DATA_ATTR_INDEX] === attr) {
// // Skip render, contents are identical
// this._state.cache[x][y] = charData;
// continue;
// }
// Clear the old character was not a space with the default background
// const wasInverted = !!(state && state[CHAR_DATA_ATTR_INDEX] && state[CHAR_DATA_ATTR_INDEX] >> 18 & FLAGS.INVERSE);
// if (state && !(state[CHAR_DATA_CODE_INDEX] === 32 /*' '*/ && (state[CHAR_DATA_ATTR_INDEX] & 0x1ff) >= 256 && !wasInverted)) {
// this._clearChar(x, y);
// }
// this._state.cache[x][y] = charData;
const flags = attr >> 18;
let bg = attr & 0x1ff;
// Skip rendering if the character is invisible
const isDefaultBackground = bg >= 256;
const isInvisible = flags & FLAGS.INVISIBLE;
const isInverted = flags & FLAGS.INVERSE;
if (!code || (code === 32 /*' '*/ && isDefaultBackground && !isInverted) || isInvisible) {
continue;
}
// If the character is an overlapping char and the character to the right is a
// space, take ownership of the cell to the right.
if (width !== 0 && this._isOverlapping(charData)) {
if (this._isOverlapping(charData)) {
// If the character is overlapping, we want to force a re-render on every
// frame. This is specifically to work around the case where two
// overlaping chars `a` and `b` are adjacent, the cursor is moved to b and a
@@ -135,10 +99,12 @@ export class TextRenderLayer extends BaseRenderLayer {
}
}
const flags = attr >> 18;
let bg = attr & 0x1ff;
let fg = (attr >> 9) & 0x1ff;
// If inverse flag is on, the foreground should become the background.
if (isInverted) {
if (flags & FLAGS.INVERSE) {
const temp = bg;
bg = fg;
fg = temp;
@@ -150,47 +116,110 @@ export class TextRenderLayer extends BaseRenderLayer {
}
}
// Clear the cell next to this character if it's wide
if (width === 2) {
// this.clearCells(x + 1, y, 1, 1);
}
// Draw background
if (bg < 256) {
this._ctx.save();
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();
}
this._ctx.save();
if (flags & FLAGS.BOLD) {
this._ctx.font = this._getFont(terminal, true);
// Convert the FG color to the bold variant
if (fg < 8) {
fg += 8;
}
}
if (flags & FLAGS.UNDERLINE) {
if (fg === INVERTED_DEFAULT_COLOR) {
this._ctx.fillStyle = this._colors.background.css;
} else if (fg < 256) {
// 256 color support
this._ctx.fillStyle = this._colors.ansi[fg].css;
} else {
this._ctx.fillStyle = this._colors.foreground.css;
}
this.fillBottomLineAtCells(x, y);
}
this.drawChar(terminal, char, code, width, x, y, fg, bg, !!(flags & FLAGS.BOLD), !!(flags & FLAGS.DIM));
this._ctx.restore();
callback(code, char, width, x, y, fg, bg, flags);
}
}
}
/**
* Draws the background for a specified range of columns. Tries to batch adjacent cells of the
* same color together to reduce draw calls.
*/
private _drawBackground(terminal: ITerminal, firstRow: number, lastRow: number): void {
const ctx = this._ctx;
const cols = terminal.cols;
let startX: number = 0;
let startY: number = 0;
let prevFillStyle: string | null = null;
ctx.save();
this._forEachCell(terminal, firstRow, lastRow, (code, char, width, x, y, fg, bg, flags) => {
// libvte and xterm both draw the background (but not foreground) of invisible characters,
// so we should too.
let nextFillStyle = null; // null represents default background color
if (bg === INVERTED_DEFAULT_COLOR) {
nextFillStyle = this._colors.foreground.css;
} else if (bg < 256) {
nextFillStyle = this._colors.ansi[bg].css;
}
if (prevFillStyle === null) {
// This is either the first iteration, or the default background was set. Either way, we
// don't need to draw anything.
startX = x;
startY = y;
} if (y !== startY) {
// our row changed, draw the previous row
ctx.fillStyle = prevFillStyle;
this.fillCells(startX, startY, cols - startX, 1);
startX = x;
startY = y;
} else if (prevFillStyle !== nextFillStyle) {
// our color changed, draw the previous characters in this row
ctx.fillStyle = prevFillStyle;
this.fillCells(startX, startY, x - startX, 1);
startX = x;
startY = y;
}
prevFillStyle = nextFillStyle;
});
// flush the last color we encountered
if (prevFillStyle !== null) {
ctx.fillStyle = prevFillStyle;
this.fillCells(startX, startY, cols - startX, 1);
}
ctx.restore();
}
private _drawForeground(terminal: ITerminal, firstRow: number, lastRow: number): void {
this._forEachCell(terminal, firstRow, lastRow, (code, char, width, x, y, fg, bg, flags) => {
if (flags & FLAGS.INVISIBLE) {
return;
}
if (flags & FLAGS.UNDERLINE) {
this._ctx.save();
if (fg === INVERTED_DEFAULT_COLOR) {
this._ctx.fillStyle = this._colors.background.css;
} else if (fg < 256) {
// 256 color support
this._ctx.fillStyle = this._colors.ansi[fg].css;
} else {
this._ctx.fillStyle = this._colors.foreground.css;
}
this.fillBottomLineAtCells(x, y);
this._ctx.restore();
}
this.drawChar(
terminal, char, code,
width, x, y,
fg, bg,
!!(flags & FLAGS.BOLD), !!(flags & FLAGS.DIM)
);
});
}
public onGridChanged(terminal: ITerminal, firstRow: number, lastRow: number): void {
// Resize has not been called yet
if (this._state.cache.length === 0) {
return;
}
this.clearCells(0, firstRow, terminal.cols, lastRow - firstRow + 1);
this._drawBackground(terminal, firstRow, lastRow);
this._drawForeground(terminal, firstRow, lastRow);
}
public onOptionsChanged(terminal: ITerminal): void {
this.setTransparency(terminal, terminal.options.allowTransparency);
}
+1 -1
View File
@@ -10,7 +10,7 @@ import { IColorSet } from '../shared/Types';
/**
* Flags used to render terminal text properly.
*/
export enum FLAGS {
export const enum FLAGS {
BOLD = 1,
UNDERLINE = 2,
BLINK = 4,
+14
View File
@@ -1,4 +1,7 @@
{
"rulesDirectory": [
"tslint-consistent-codestyle"
],
"rules": {
"array-type": [
true,
@@ -86,6 +89,17 @@
"check-type",
"check-type-operator",
"check-preblock"
],
"naming-convention": [
true,
{"type": "property", "modifiers": ["public", "static", "const"], "format": "UPPER_CASE"}
],
"no-else-after-return": {
"options": "allow-else-if"
},
"prefer-const-enum": [
true
]
}
}