Merge branch 'master' into 1103_search_wide_emoji
This commit is contained in:
@@ -121,11 +121,6 @@ export class Buffer implements IBuffer {
|
||||
if (this._terminal.cols < newCols) {
|
||||
const ch: CharData = [this._terminal.defAttr, ' ', 1, 32]; // does xterm use the default attr?
|
||||
for (let i = 0; i < this._lines.length; i++) {
|
||||
// TODO: This should be removed, with tests setup for the case that was
|
||||
// causing the underlying bug, see https://github.com/sourcelair/xterm.js/issues/824
|
||||
if (this._lines.get(i) === undefined) {
|
||||
this._lines.set(i, this._terminal.blankLine(undefined, undefined, newCols));
|
||||
}
|
||||
while (this._lines.get(i).length < newCols) {
|
||||
this._lines.get(i).push(ch);
|
||||
}
|
||||
|
||||
+14
-3
@@ -61,24 +61,35 @@ export class BufferSet extends EventEmitter implements IBufferSet {
|
||||
* Sets the normal Buffer of the BufferSet as its currently active Buffer
|
||||
*/
|
||||
public activateNormalBuffer(): void {
|
||||
if (this._activeBuffer === this._normal) {
|
||||
return;
|
||||
}
|
||||
// The alt buffer should always be cleared when we switch to the normal
|
||||
// buffer. This frees up memory since the alt buffer should always be new
|
||||
// when activated.
|
||||
this._alt.clear();
|
||||
|
||||
this._activeBuffer = this._normal;
|
||||
this.emit('activate', this._normal);
|
||||
this.emit('activate', {
|
||||
activeBuffer: this._normal,
|
||||
inactiveBuffer: this._alt
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the alt Buffer of the BufferSet as its currently active Buffer
|
||||
*/
|
||||
public activateAltBuffer(): void {
|
||||
if (this._activeBuffer === this._alt) {
|
||||
return;
|
||||
}
|
||||
// Since the alt buffer is always cleared when the normal buffer is
|
||||
// activated, we want to fill it when switching to it.
|
||||
this._alt.fillViewportRows();
|
||||
this._activeBuffer = this._alt;
|
||||
this.emit('activate', this._alt);
|
||||
this.emit('activate', {
|
||||
activeBuffer: this._alt,
|
||||
inactiveBuffer: this._normal
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+3
-3
@@ -3,19 +3,19 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { Charset } from './Types';
|
||||
import { ICharset } from './Interfaces';
|
||||
|
||||
/**
|
||||
* The character sets supported by the terminal. These enable several languages
|
||||
* to be represented within the terminal with only 8-bit encoding. See ISO 2022
|
||||
* for a discussion on character sets. Only VT100 character sets are supported.
|
||||
*/
|
||||
export const CHARSETS: { [key: string]: Charset } = {};
|
||||
export const CHARSETS: { [key: string]: ICharset } = {};
|
||||
|
||||
/**
|
||||
* The default character set, US.
|
||||
*/
|
||||
export const DEFAULT_CHARSET: Charset = CHARSETS['B'];
|
||||
export const DEFAULT_CHARSET: ICharset = CHARSETS['B'];
|
||||
|
||||
/**
|
||||
* DEC Special Character and Line Drawing Set.
|
||||
|
||||
@@ -217,7 +217,7 @@ export class CompositionHelper {
|
||||
if (!dontRecurse) {
|
||||
setTimeout(() => this.updateCompositionElements(true), 0);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the textarea's position so that the cursor does not blink on IE.
|
||||
@@ -226,5 +226,5 @@ export class CompositionHelper {
|
||||
private clearTextareaPosition(): void {
|
||||
this.textarea.style.left = '';
|
||||
this.textarea.style.top = '';
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,4 +76,4 @@ export namespace C0 {
|
||||
export const SP = '\x20';
|
||||
/** Delete (Caret = ^?) */
|
||||
export const DEL = '\x7f';
|
||||
};
|
||||
}
|
||||
|
||||
+7
-9
@@ -26,7 +26,7 @@ export class InputHandler implements IInputHandler {
|
||||
if (char >= ' ') {
|
||||
// calculate print space
|
||||
// expensive call, therefore we save width in line buffer
|
||||
const ch_width = wcwidth(code);
|
||||
const chWidth = wcwidth(code);
|
||||
|
||||
if (this._terminal.charset && this._terminal.charset[char]) {
|
||||
char = this._terminal.charset[char];
|
||||
@@ -36,7 +36,7 @@ export class InputHandler implements IInputHandler {
|
||||
|
||||
// insert combining char in last cell
|
||||
// FIXME: needs handling after cursor jumps
|
||||
if (!ch_width && this._terminal.buffer.x) {
|
||||
if (!chWidth && this._terminal.buffer.x) {
|
||||
// dont overflow left
|
||||
if (this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 1]) {
|
||||
if (!this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 1][CHAR_DATA_WIDTH_INDEX]) {
|
||||
@@ -56,7 +56,7 @@ export class InputHandler implements IInputHandler {
|
||||
|
||||
// goto next line if ch would overflow
|
||||
// TODO: needs a global min terminal width of 2
|
||||
if (this._terminal.buffer.x + ch_width - 1 >= this._terminal.cols) {
|
||||
if (this._terminal.buffer.x + chWidth - 1 >= this._terminal.cols) {
|
||||
// autowrap - DECAWM
|
||||
if (this._terminal.wraparoundMode) {
|
||||
this._terminal.buffer.x = 0;
|
||||
@@ -70,7 +70,7 @@ export class InputHandler implements IInputHandler {
|
||||
(<any>this._terminal.buffer.lines.get(this._terminal.buffer.y)).isWrapped = true;
|
||||
}
|
||||
} else {
|
||||
if (ch_width === 2) // FIXME: check for xterm behavior
|
||||
if (chWidth === 2) // FIXME: check for xterm behavior
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -79,7 +79,7 @@ export class InputHandler implements IInputHandler {
|
||||
// insert mode: move characters to right
|
||||
if (this._terminal.insertMode) {
|
||||
// do this twice for a fullwidth char
|
||||
for (let moves = 0; moves < ch_width; ++moves) {
|
||||
for (let moves = 0; moves < chWidth; ++moves) {
|
||||
// remove last cell, if it's width is 0
|
||||
// we have to adjust the second last cell as well
|
||||
const removed = this._terminal.buffer.lines.get(this._terminal.buffer.y + this._terminal.buffer.ybase).pop();
|
||||
@@ -94,12 +94,12 @@ export class InputHandler implements IInputHandler {
|
||||
}
|
||||
}
|
||||
|
||||
this._terminal.buffer.lines.get(row)[this._terminal.buffer.x] = [this._terminal.curAttr, char, ch_width, char.charCodeAt(0)];
|
||||
this._terminal.buffer.lines.get(row)[this._terminal.buffer.x] = [this._terminal.curAttr, char, chWidth, char.charCodeAt(0)];
|
||||
this._terminal.buffer.x++;
|
||||
this._terminal.updateRange(this._terminal.buffer.y);
|
||||
|
||||
// fullwidth char - set next cell width to zero and advance cursor
|
||||
if (ch_width === 2) {
|
||||
if (chWidth === 2) {
|
||||
this._terminal.buffer.lines.get(row)[this._terminal.buffer.x] = [this._terminal.curAttr, '', 0, undefined];
|
||||
this._terminal.buffer.x++;
|
||||
}
|
||||
@@ -928,7 +928,6 @@ export class InputHandler implements IInputHandler {
|
||||
case 47: // alt screen buffer
|
||||
case 1047: // alt screen buffer
|
||||
this._terminal.buffers.activateAltBuffer();
|
||||
this._terminal.selectionManager.setBuffer(this._terminal.buffer);
|
||||
this._terminal.viewport.syncScrollArea();
|
||||
this._terminal.showCursor();
|
||||
break;
|
||||
@@ -1100,7 +1099,6 @@ export class InputHandler implements IInputHandler {
|
||||
// if (params[0] === 1049) {
|
||||
// this.restoreCursor(params);
|
||||
// }
|
||||
this._terminal.selectionManager.setBuffer(this._terminal.buffer);
|
||||
this._terminal.refresh(0, this._terminal.rows - 1);
|
||||
this._terminal.viewport.syncScrollArea();
|
||||
this._terminal.showCursor();
|
||||
|
||||
+31
-9
@@ -3,8 +3,8 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { ILinkMatcherOptions } from './Interfaces';
|
||||
import { LinkMatcherHandler, LinkMatcherValidationCallback, Charset, LineData } from './Types';
|
||||
import { ICharset, ILinkMatcherOptions } from './Interfaces';
|
||||
import { LinkMatcherHandler, LinkMatcherValidationCallback, LineData, FontWeight } from './Types';
|
||||
import { IColorSet, IRenderer } from './renderer/Interfaces';
|
||||
import { IMouseZoneManager } from './input/Interfaces';
|
||||
|
||||
@@ -74,10 +74,10 @@ export interface IInputHandlingTerminal extends IEventEmitter {
|
||||
options: ITerminalOptions;
|
||||
cols: number;
|
||||
rows: number;
|
||||
charset: Charset;
|
||||
charset: ICharset;
|
||||
gcharset: number;
|
||||
glevel: number;
|
||||
charsets: Charset[];
|
||||
charsets: ICharset[];
|
||||
applicationKeypad: boolean;
|
||||
applicationCursor: boolean;
|
||||
originMode: boolean;
|
||||
@@ -116,7 +116,7 @@ export interface IInputHandlingTerminal extends IEventEmitter {
|
||||
blankLine(cur?: boolean, isWrapped?: boolean): LineData;
|
||||
is(term: string): boolean;
|
||||
send(data: string): void;
|
||||
setgCharset(g: number, charset: Charset): void;
|
||||
setgCharset(g: number, charset: ICharset): void;
|
||||
resize(x: number, y: number): void;
|
||||
log(text: string, data?: any): void;
|
||||
reset(): void;
|
||||
@@ -137,12 +137,14 @@ export interface ITerminalOptions {
|
||||
cursorStyle?: string;
|
||||
debug?: boolean;
|
||||
disableStdin?: boolean;
|
||||
enableBold?: boolean;
|
||||
fontSize?: number;
|
||||
fontFamily?: string;
|
||||
fontWeight?: FontWeight;
|
||||
fontWeightBold?: FontWeight;
|
||||
handler?: (data: string) => void;
|
||||
letterSpacing?: number;
|
||||
lineHeight?: number;
|
||||
macOptionIsMeta?: boolean;
|
||||
rows?: number;
|
||||
screenKeys?: boolean;
|
||||
scrollback?: number;
|
||||
@@ -169,7 +171,7 @@ export interface IBuffer {
|
||||
prevStop(x?: number): number;
|
||||
}
|
||||
|
||||
export interface IBufferSet {
|
||||
export interface IBufferSet extends IEventEmitter {
|
||||
alt: IBuffer;
|
||||
normal: IBuffer;
|
||||
active: IBuffer;
|
||||
@@ -198,7 +200,6 @@ export interface ISelectionManager {
|
||||
|
||||
disable(): void;
|
||||
enable(): void;
|
||||
setBuffer(buffer: IBuffer): void;
|
||||
setSelection(row: number, col: number, length: number): void;
|
||||
}
|
||||
|
||||
@@ -248,7 +249,7 @@ export interface IEventEmitter {
|
||||
export interface IListenerType {
|
||||
(data?: any): void;
|
||||
listener?: (data?: any) => void;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ILinkMatcherOptions {
|
||||
/**
|
||||
@@ -352,3 +353,24 @@ export interface ITheme {
|
||||
brightCyan?: string;
|
||||
brightWhite?: string;
|
||||
}
|
||||
|
||||
export interface ILinkMatcher {
|
||||
id: number;
|
||||
regex: RegExp;
|
||||
handler: LinkMatcherHandler;
|
||||
hoverTooltipCallback?: LinkMatcherHandler;
|
||||
hoverLeaveCallback?: () => void;
|
||||
matchIndex?: number;
|
||||
validationCallback?: LinkMatcherValidationCallback;
|
||||
priority?: number;
|
||||
}
|
||||
|
||||
export interface ICharset {
|
||||
[key: string]: string;
|
||||
}
|
||||
|
||||
export interface ILinkHoverEvent {
|
||||
x: number;
|
||||
y: number;
|
||||
length: number;
|
||||
}
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
*/
|
||||
|
||||
import { assert } from 'chai';
|
||||
import { ITerminal, ILinkifier, IBuffer, IBufferAccessor, IElementAccessor } from './Interfaces';
|
||||
import { ITerminal, ILinkifier, ILinkMatcher, IBuffer, IBufferAccessor, IElementAccessor } from './Interfaces';
|
||||
import { Linkifier } from './Linkifier';
|
||||
import { LinkMatcher, LineData } from './Types';
|
||||
import { LineData } from './Types';
|
||||
import { IMouseZoneManager, IMouseZone } from './input/Interfaces';
|
||||
import { MockBuffer } from './utils/TestUtils.test';
|
||||
import { CircularList } from './utils/CircularList';
|
||||
@@ -17,7 +17,7 @@ class TestLinkifier extends Linkifier {
|
||||
Linkifier.TIME_BEFORE_LINKIFY = 0;
|
||||
}
|
||||
|
||||
public get linkMatchers(): LinkMatcher[] { return this._linkMatchers; }
|
||||
public get linkMatchers(): ILinkMatcher[] { return this._linkMatchers; }
|
||||
public linkifyRows(): void { super.linkifyRows(0, this._terminal.buffer.lines.length - 1); }
|
||||
}
|
||||
|
||||
|
||||
+13
-13
@@ -3,8 +3,8 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { ILinkMatcherOptions, ITerminal, IBufferAccessor, ILinkifier, IElementAccessor } from './Interfaces';
|
||||
import { LinkMatcher, LinkMatcherHandler, LinkMatcherValidationCallback, LineData, LinkHoverEvent, LinkHoverEventTypes } from './Types';
|
||||
import { ILinkHoverEvent, ILinkMatcher, ILinkMatcherOptions, ITerminal, IBufferAccessor, ILinkifier, IElementAccessor } from './Interfaces';
|
||||
import { LinkMatcherHandler, LinkMatcherValidationCallback, LineData, LinkHoverEventTypes } from './Types';
|
||||
import { IMouseZoneManager } from './input/Interfaces';
|
||||
import { MouseZone } from './input/MouseZoneManager';
|
||||
import { EventEmitter } from './EventEmitter';
|
||||
@@ -44,7 +44,7 @@ export class Linkifier extends EventEmitter implements ILinkifier {
|
||||
*/
|
||||
protected static TIME_BEFORE_LINKIFY = 200;
|
||||
|
||||
protected _linkMatchers: LinkMatcher[] = [];
|
||||
protected _linkMatchers: ILinkMatcher[] = [];
|
||||
|
||||
private _mouseZoneManager: IMouseZoneManager;
|
||||
private _rowsTimeoutId: number;
|
||||
@@ -82,12 +82,12 @@ export class Linkifier extends EventEmitter implements ILinkifier {
|
||||
}
|
||||
|
||||
// Increase range to linkify
|
||||
if (!this._rowsToLinkify.start) {
|
||||
if (this._rowsToLinkify.start === null) {
|
||||
this._rowsToLinkify.start = start;
|
||||
this._rowsToLinkify.end = end;
|
||||
} else {
|
||||
this._rowsToLinkify.start = this._rowsToLinkify.start < start ? this._rowsToLinkify.start : start;
|
||||
this._rowsToLinkify.end = this._rowsToLinkify.end > end ? this._rowsToLinkify.end : end;
|
||||
this._rowsToLinkify.start = Math.min(this._rowsToLinkify.start, start);
|
||||
this._rowsToLinkify.end = Math.max(this._rowsToLinkify.end, end);
|
||||
}
|
||||
|
||||
// Clear out any existing links on this row range
|
||||
@@ -143,7 +143,7 @@ export class Linkifier extends EventEmitter implements ILinkifier {
|
||||
if (this._nextLinkMatcherId !== HYPERTEXT_LINK_MATCHER_ID && !handler) {
|
||||
throw new Error('handler must be defined');
|
||||
}
|
||||
const matcher: LinkMatcher = {
|
||||
const matcher: ILinkMatcher = {
|
||||
id: this._nextLinkMatcherId++,
|
||||
regex,
|
||||
handler,
|
||||
@@ -163,7 +163,7 @@ export class Linkifier extends EventEmitter implements ILinkifier {
|
||||
* considered after older link matchers.
|
||||
* @param matcher The link matcher to be added.
|
||||
*/
|
||||
private _addLinkMatcherToList(matcher: LinkMatcher): void {
|
||||
private _addLinkMatcherToList(matcher: ILinkMatcher): void {
|
||||
if (this._linkMatchers.length === 0) {
|
||||
this._linkMatchers.push(matcher);
|
||||
return;
|
||||
@@ -219,7 +219,7 @@ export class Linkifier extends EventEmitter implements ILinkifier {
|
||||
* @param offset The how much of the row has already been linkified.
|
||||
* @return The link element(s) that were added.
|
||||
*/
|
||||
private _doLinkifyRow(rowIndex: number, text: string, matcher: LinkMatcher, offset: number = 0): void {
|
||||
private _doLinkifyRow(rowIndex: number, text: string, matcher: ILinkMatcher, offset: number = 0): void {
|
||||
// Iterate over nodes as we want to consider text nodes
|
||||
let result = [];
|
||||
const isHttpLinkMatcher = matcher.id === HYPERTEXT_LINK_MATCHER_ID;
|
||||
@@ -264,7 +264,7 @@ export class Linkifier extends EventEmitter implements ILinkifier {
|
||||
* @param uri The URI of the link.
|
||||
* @param matcher The link matcher for the link.
|
||||
*/
|
||||
private _addLink(x: number, y: number, uri: string, matcher: LinkMatcher): void {
|
||||
private _addLink(x: number, y: number, uri: string, matcher: ILinkMatcher): void {
|
||||
this._mouseZoneManager.add(new MouseZone(
|
||||
x + 1,
|
||||
x + 1 + uri.length,
|
||||
@@ -276,17 +276,17 @@ export class Linkifier extends EventEmitter implements ILinkifier {
|
||||
window.open(uri, '_blank');
|
||||
},
|
||||
e => {
|
||||
this.emit(LinkHoverEventTypes.HOVER, <LinkHoverEvent>{ x, y, length: uri.length});
|
||||
this.emit(LinkHoverEventTypes.HOVER, <ILinkHoverEvent>{ x, y, length: uri.length});
|
||||
this._terminal.element.style.cursor = 'pointer';
|
||||
},
|
||||
e => {
|
||||
this.emit(LinkHoverEventTypes.TOOLTIP, <LinkHoverEvent>{ x, y, length: uri.length});
|
||||
this.emit(LinkHoverEventTypes.TOOLTIP, <ILinkHoverEvent>{ x, y, length: uri.length});
|
||||
if (matcher.hoverTooltipCallback) {
|
||||
matcher.hoverTooltipCallback(e, uri);
|
||||
}
|
||||
},
|
||||
() => {
|
||||
this.emit(LinkHoverEventTypes.LEAVE, <LinkHoverEvent>{ x, y, length: uri.length});
|
||||
this.emit(LinkHoverEventTypes.LEAVE, <ILinkHoverEvent>{ x, y, length: uri.length});
|
||||
this._terminal.element.style.cursor = '';
|
||||
if (matcher.hoverLeaveCallback) {
|
||||
matcher.hoverLeaveCallback();
|
||||
|
||||
@@ -21,10 +21,9 @@ class TestMockTerminal extends MockTerminal {
|
||||
class TestSelectionManager extends SelectionManager {
|
||||
constructor(
|
||||
terminal: ITerminal,
|
||||
buffer: IBuffer,
|
||||
charMeasure: CharMeasure
|
||||
) {
|
||||
super(terminal, buffer, charMeasure);
|
||||
super(terminal, charMeasure);
|
||||
}
|
||||
|
||||
public get model(): SelectionModel { return this._model; }
|
||||
@@ -59,7 +58,7 @@ describe('SelectionManager', () => {
|
||||
terminal.buffers = new BufferSet(terminal);
|
||||
terminal.buffer = terminal.buffers.active;
|
||||
buffer = terminal.buffer;
|
||||
selectionManager = new TestSelectionManager(terminal, buffer, null);
|
||||
selectionManager = new TestSelectionManager(terminal, null);
|
||||
});
|
||||
|
||||
function stringToRow(text: string): LineData {
|
||||
|
||||
+23
-17
@@ -8,7 +8,7 @@ import * as Browser from './utils/Browser';
|
||||
import { CharMeasure } from './utils/CharMeasure';
|
||||
import { CircularList } from './utils/CircularList';
|
||||
import { EventEmitter } from './EventEmitter';
|
||||
import { ITerminal, ICircularList, ISelectionManager, IBuffer } from './Interfaces';
|
||||
import { ITerminal, ICircularList, ISelectionManager, IBuffer, IListenerType } from './Interfaces';
|
||||
import { SelectionModel } from './SelectionModel';
|
||||
import { LineData, CharData } from './Types';
|
||||
import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from './Buffer';
|
||||
@@ -95,10 +95,10 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
|
||||
|
||||
private _mouseMoveListener: EventListener;
|
||||
private _mouseUpListener: EventListener;
|
||||
private _trimListener: IListenerType;
|
||||
|
||||
constructor(
|
||||
private _terminal: ITerminal,
|
||||
private _buffer: IBuffer,
|
||||
private _charMeasure: CharMeasure
|
||||
) {
|
||||
super();
|
||||
@@ -109,18 +109,24 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
|
||||
this._activeSelectionMode = SelectionMode.NORMAL;
|
||||
}
|
||||
|
||||
private get _buffer(): IBuffer {
|
||||
return this._terminal.buffers.active;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes listener variables.
|
||||
*/
|
||||
private _initListeners(): void {
|
||||
this._mouseMoveListener = event => this._onMouseMove(<MouseEvent>event);
|
||||
this._mouseUpListener = event => this._onMouseUp(<MouseEvent>event);
|
||||
this._trimListener = (amount: number) => this._onTrim(amount);
|
||||
|
||||
// Only adjust the selection on trim, shiftElements is rarely used (only in
|
||||
// reverseIndex) and delete in a splice is only ever used when the same
|
||||
// number of elements was just added. Given this is could actually be
|
||||
// beneficial to leave the selection as is for these cases.
|
||||
this._buffer.lines.on('trim', (amount: number) => this._onTrim(amount));
|
||||
this.initBuffersListeners();
|
||||
}
|
||||
|
||||
public initBuffersListeners(): void {
|
||||
this._terminal.buffer.lines.on('trim', this._trimListener);
|
||||
this._terminal.buffers.on('activate', e => this._onBufferActivate(e));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -139,16 +145,6 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
|
||||
this._enabled = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the active buffer, this should be called when the alt buffer is
|
||||
* switched in or out.
|
||||
* @param buffer The active buffer.
|
||||
*/
|
||||
public setBuffer(buffer: IBuffer): void {
|
||||
this._buffer = buffer;
|
||||
this.clearSelection();
|
||||
}
|
||||
|
||||
public get selectionStart(): [number, number] { return this._model.finalSelectionStart; }
|
||||
public get selectionEnd(): [number, number] { return this._model.finalSelectionEnd; }
|
||||
|
||||
@@ -547,6 +543,16 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
|
||||
this._terminal.emit('selection');
|
||||
}
|
||||
|
||||
private _onBufferActivate(e: {activeBuffer: IBuffer, inactiveBuffer: IBuffer}): void {
|
||||
this.clearSelection();
|
||||
// Only adjust the selection on trim, shiftElements is rarely used (only in
|
||||
// reverseIndex) and delete in a splice is only ever used when the same
|
||||
// number of elements was just added. Given this is could actually be
|
||||
// beneficial to leave the selection as is for these cases.
|
||||
e.inactiveBuffer.lines.off('trim', this._trimListener);
|
||||
e.activeBuffer.lines.on('trim', this._trimListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a viewport column to the character index on the buffer line, the
|
||||
* latter takes into account wide characters.
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { assert } from 'chai';
|
||||
import { ITerminal } from './Interfaces';
|
||||
import { SelectionModel } from './SelectionModel';
|
||||
import {BufferSet} from './BufferSet';
|
||||
import { BufferSet } from './BufferSet';
|
||||
import { MockTerminal } from './utils/TestUtils.test';
|
||||
|
||||
class TestSelectionModel extends SelectionModel {
|
||||
|
||||
+18
-18
@@ -15,17 +15,17 @@ import { assert } from 'chai';
|
||||
import { Terminal } from './Terminal';
|
||||
import { CHAR_DATA_CHAR_INDEX } from './Buffer';
|
||||
|
||||
let primitive_pty: any;
|
||||
let primitivePty: any;
|
||||
|
||||
// fake sychronous pty write - read
|
||||
// we just pipe the data from slave to master as a child program would do
|
||||
// pty.js opens pipe fds with O_NONBLOCK
|
||||
// just wait 10ms instead of setting fds to blocking mode
|
||||
function ptyWriteRead(data: string, cb: (result: string) => void): void {
|
||||
fs.writeSync(primitive_pty.slave, data);
|
||||
fs.writeSync(primitivePty.slave, data);
|
||||
setTimeout(() => {
|
||||
let b = new Buffer(64000);
|
||||
let bytes = fs.readSync(primitive_pty.master, b, 0, 64000, null);
|
||||
let bytes = fs.readSync(primitivePty.master, b, 0, 64000, null);
|
||||
cb(b.toString('utf8', 0, bytes));
|
||||
});
|
||||
}
|
||||
@@ -37,7 +37,7 @@ function ptyReset(cb: (result: string) => void): void {
|
||||
|
||||
/* debug helpers */
|
||||
// generate colorful noisy output to compare xterm and emulator cell states
|
||||
function formatError(in_: string, out_: string, expected: string): string {
|
||||
function formatError(input: string, output: string, expected: string): string {
|
||||
function addLineNumber(start: number, color: string): (s: string) => string {
|
||||
let counter = start || 0;
|
||||
return function(s: string): string {
|
||||
@@ -47,9 +47,9 @@ function formatError(in_: string, out_: string, expected: string): string {
|
||||
}
|
||||
let line80 = '12345678901234567890123456789012345678901234567890123456789012345678901234567890';
|
||||
let s = '';
|
||||
s += '\n\x1b[34m' + JSON.stringify(in_);
|
||||
s += '\n\x1b[34m' + JSON.stringify(input);
|
||||
s += '\n\x1b[33m ' + line80 + '\n';
|
||||
s += out_.split('\n').map(addLineNumber(0, '\x1b[31m')).join('\n');
|
||||
s += output.split('\n').map(addLineNumber(0, '\x1b[31m')).join('\n');
|
||||
s += '\n\x1b[33m ' + line80 + '\n';
|
||||
s += expected.split('\n').map(addLineNumber(0, '\x1b[32m')).join('\n');
|
||||
return s;
|
||||
@@ -58,15 +58,15 @@ function formatError(in_: string, out_: string, expected: string): string {
|
||||
// simple debug output of terminal cells
|
||||
function terminalToString(term: Terminal): string {
|
||||
let result = '';
|
||||
let line_s = '';
|
||||
let lineText = '';
|
||||
for (let line = term.buffer.ybase; line < term.buffer.ybase + term.rows; line++) {
|
||||
line_s = '';
|
||||
lineText = '';
|
||||
for (let cell = 0; cell < term.cols; ++cell) {
|
||||
line_s += term.buffer.lines.get(line)[cell][CHAR_DATA_CHAR_INDEX];
|
||||
lineText += term.buffer.lines.get(line)[cell][CHAR_DATA_CHAR_INDEX];
|
||||
}
|
||||
// rtrim empty cells as xterm does
|
||||
line_s = line_s.replace(/\s+$/, '');
|
||||
result += line_s;
|
||||
lineText = lineText.replace(/\s+$/, '');
|
||||
result += lineText;
|
||||
result += '\n';
|
||||
}
|
||||
return result;
|
||||
@@ -83,7 +83,7 @@ if (os.platform() !== 'win32') {
|
||||
/** some helpers for pty interaction */
|
||||
// we need a pty in between to get the termios decorations
|
||||
// for the basic test cases a raw pty device is enough
|
||||
primitive_pty = pty.native.open(COLS, ROWS);
|
||||
primitivePty = pty.native.open(COLS, ROWS);
|
||||
|
||||
/** tests */
|
||||
describe('xterm output comparison', () => {
|
||||
@@ -118,24 +118,24 @@ if (os.platform() !== 'win32') {
|
||||
((filename: string) => {
|
||||
it(filename.split('/').slice(-1)[0], done => {
|
||||
ptyReset(() => {
|
||||
let in_file = fs.readFileSync(filename, 'utf8');
|
||||
ptyWriteRead(in_file, from_pty => {
|
||||
let inFile = fs.readFileSync(filename, 'utf8');
|
||||
ptyWriteRead(inFile, fromPty => {
|
||||
// uncomment this to get log from terminal
|
||||
// console.log = function(){};
|
||||
|
||||
// Perform a synchronous .write(data)
|
||||
xterm.writeBuffer.push(from_pty);
|
||||
xterm.writeBuffer.push(fromPty);
|
||||
xterm.innerWrite();
|
||||
|
||||
let from_emulator = terminalToString(xterm);
|
||||
let fromEmulator = terminalToString(xterm);
|
||||
console.log = CONSOLE_LOG;
|
||||
let expected = fs.readFileSync(filename.split('.')[0] + '.text', 'utf8');
|
||||
// Some of the tests have whitespace on the right of lines, we trim all the linex
|
||||
// from xterm.js so ignore this for now at least.
|
||||
let expectedRightTrimmed = expected.split('\n').map(l => l.replace(/\s+$/, '')).join('\n');
|
||||
if (from_emulator !== expectedRightTrimmed) {
|
||||
if (fromEmulator !== expectedRightTrimmed) {
|
||||
// uncomment to get noisy output
|
||||
throw new Error(formatError(in_file, from_emulator, expected));
|
||||
throw new Error(formatError(inFile, fromEmulator, expected));
|
||||
// throw new Error('mismatch');
|
||||
}
|
||||
done();
|
||||
|
||||
@@ -503,6 +503,9 @@ describe('term.js addons', () => {
|
||||
it('should return \\x1b[5C for alt+right', () => {
|
||||
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 39 }).key, '\x1b[1;5C'); // CSI 5 C
|
||||
});
|
||||
it('should return \\x1ba for alt+a', () => {
|
||||
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 65 }).key, '\x1ba');
|
||||
});
|
||||
});
|
||||
|
||||
describe('On macOS platforms', () => {
|
||||
@@ -515,6 +518,19 @@ describe('term.js addons', () => {
|
||||
it('should return \\x1bf for alt+right', () => {
|
||||
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 39 }).key, '\x1bf'); // CSI 5 C
|
||||
});
|
||||
it('should return undefined for alt+a', () => {
|
||||
assert.strictEqual(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 65 }).key, undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with macOptionIsMeta', () => {
|
||||
beforeEach(() => {
|
||||
term.browser.isMac = true;
|
||||
term.setOption('macOptionIsMeta', true);
|
||||
});
|
||||
it('should return \\x1ba for alt+a', () => {
|
||||
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 65 }).key, '\x1ba');
|
||||
});
|
||||
});
|
||||
|
||||
it('should return \\x1b[5A for alt+up', () => {
|
||||
@@ -597,6 +613,22 @@ describe('term.js addons', () => {
|
||||
};
|
||||
});
|
||||
|
||||
describe('with macOptionIsMeta', () => {
|
||||
beforeEach(() => {
|
||||
term.browser.isMac = true;
|
||||
term.setOption('macOptionIsMeta', true);
|
||||
});
|
||||
|
||||
it('should interfere with the alt key on keyDown', () => {
|
||||
evKeyDown.altKey = true;
|
||||
evKeyDown.keyCode = 81;
|
||||
assert.equal(term.keyDown(evKeyDown), false);
|
||||
evKeyDown.altKey = true;
|
||||
evKeyDown.keyCode = 192;
|
||||
assert.equal(term.keyDown(evKeyDown), false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('On Mac OS', () => {
|
||||
beforeEach(() => {
|
||||
term.browser.isMac = true;
|
||||
|
||||
+60
-43
@@ -38,9 +38,9 @@ import { CharMeasure } from './utils/CharMeasure';
|
||||
import * as Browser from './utils/Browser';
|
||||
import { MouseHelper } from './utils/MouseHelper';
|
||||
import { CHARSETS } from './Charsets';
|
||||
import { CustomKeyEventHandler, Charset, LinkMatcherHandler, LinkMatcherValidationCallback, CharData, LineData } from './Types';
|
||||
import { ITerminal, IBrowser, ITerminalOptions, IInputHandlingTerminal, ILinkMatcherOptions, IViewport, ICompositionHelper, ITheme, ILinkifier } from './Interfaces';
|
||||
import { BellSound } from './utils/Sounds';
|
||||
import { CustomKeyEventHandler, LinkMatcherHandler, LinkMatcherValidationCallback, CharData, LineData } from './Types';
|
||||
import { ITerminal, IBrowser, ICharset, ITerminalOptions, IInputHandlingTerminal, ILinkMatcherOptions, IViewport, ICompositionHelper, ITheme, ILinkifier } from './Interfaces';
|
||||
import { BELL_SOUND } from './utils/Sounds';
|
||||
import { DEFAULT_ANSI_COLORS } from './renderer/ColorManager';
|
||||
import { IMouseZoneManager } from './input/Interfaces';
|
||||
import { MouseZoneManager } from './input/MouseZoneManager';
|
||||
@@ -70,16 +70,18 @@ const DEFAULT_OPTIONS: ITerminalOptions = {
|
||||
termName: 'xterm',
|
||||
cursorBlink: false,
|
||||
cursorStyle: 'block',
|
||||
bellSound: BellSound,
|
||||
bellSound: BELL_SOUND,
|
||||
bellStyle: 'none',
|
||||
enableBold: true,
|
||||
fontFamily: 'courier-new, courier, monospace',
|
||||
fontSize: 15,
|
||||
fontWeight: 'normal',
|
||||
fontWeightBold: 'bold',
|
||||
lineHeight: 1.0,
|
||||
letterSpacing: 0,
|
||||
scrollback: 1000,
|
||||
screenKeys: false,
|
||||
debug: false,
|
||||
macOptionIsMeta: false,
|
||||
cancelEvents: false,
|
||||
disableStdin: false,
|
||||
useFlowControl: false,
|
||||
@@ -131,10 +133,10 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
|
||||
// charset
|
||||
// The current charset
|
||||
public charset: Charset;
|
||||
public charset: ICharset;
|
||||
public gcharset: number;
|
||||
public glevel: number;
|
||||
public charsets: Charset[];
|
||||
public charsets: ICharset[];
|
||||
|
||||
// mouse properties
|
||||
private decLocator: boolean; // This is unstable and never set
|
||||
@@ -181,7 +183,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
private writeStopped: boolean;
|
||||
|
||||
// leftover surrogate high from previous write invocation
|
||||
private surrogate_high: string;
|
||||
private surrogateHigh: string;
|
||||
|
||||
// Store if user went browsing history in scrollback
|
||||
private userScrolling: boolean;
|
||||
@@ -192,7 +194,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
public selectionManager: SelectionManager;
|
||||
public linkifier: ILinkifier;
|
||||
public buffers: BufferSet;
|
||||
public buffer: Buffer;
|
||||
public viewport: IViewport;
|
||||
private compositionHelper: ICompositionHelper;
|
||||
public charMeasure: CharMeasure;
|
||||
@@ -280,7 +281,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
|
||||
this.xoffSentToCatchUp = false;
|
||||
this.writeStopped = false;
|
||||
this.surrogate_high = '';
|
||||
this.surrogateHigh = '';
|
||||
this.userScrolling = false;
|
||||
|
||||
this.inputHandler = new InputHandler(this);
|
||||
@@ -293,17 +294,19 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
|
||||
// Create the terminal's buffers and set the current buffer
|
||||
this.buffers = new BufferSet(this);
|
||||
this.buffer = this.buffers.active; // Convenience shortcut;
|
||||
this.buffers.on('activate', (buffer: Buffer) => {
|
||||
this.buffer = buffer;
|
||||
});
|
||||
|
||||
// Ensure the selection manager has the correct buffer
|
||||
if (this.selectionManager) {
|
||||
this.selectionManager.setBuffer(this.buffer);
|
||||
this.selectionManager.clearSelection();
|
||||
this.selectionManager.initBuffersListeners();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience property to active buffer.
|
||||
*/
|
||||
public get buffer(): Buffer {
|
||||
return this.buffers.active;
|
||||
}
|
||||
|
||||
/**
|
||||
* back_color_erase feature for xterm.
|
||||
*/
|
||||
@@ -361,6 +364,16 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
value = 'block';
|
||||
}
|
||||
break;
|
||||
case 'fontWeight':
|
||||
if (!value) {
|
||||
value = 'normal';
|
||||
}
|
||||
break;
|
||||
case 'fontWeightBold':
|
||||
if (!value) {
|
||||
value = 'bold';
|
||||
}
|
||||
break;
|
||||
case 'lineHeight':
|
||||
if (value < 1) {
|
||||
console.warn(`${key} cannot be less than 1, value: ${value}`);
|
||||
@@ -411,14 +424,16 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
this.renderer.clear();
|
||||
this.charMeasure.measure(this.options);
|
||||
break;
|
||||
case 'enableBold':
|
||||
case 'letterSpacing':
|
||||
case 'lineHeight':
|
||||
case 'fontWeight':
|
||||
case 'fontWeightBold':
|
||||
const didCharSizeChange = (key === 'fontWeight' || key === 'fontWeightBold');
|
||||
|
||||
// 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, false);
|
||||
this.renderer.onResize(this.cols, this.rows, didCharSizeChange);
|
||||
this.refresh(0, this.rows - 1);
|
||||
// this.charMeasure.measure(this.options);
|
||||
case 'scrollback':
|
||||
this.buffers.resize(this.cols, this.rows);
|
||||
this.viewport.syncScrollArea();
|
||||
@@ -443,7 +458,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
this.element.classList.add('focus');
|
||||
this.showCursor();
|
||||
this.emit('focus');
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Blur the terminal, calling the blur function on the terminal's underlying
|
||||
@@ -640,7 +655,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
this.charMeasure.on('charsizechanged', () => this.renderer.onResize(this.cols, this.rows, true));
|
||||
this.renderer.on('resize', (dimensions) => this.viewport.syncScrollArea());
|
||||
|
||||
this.selectionManager = new SelectionManager(this, this.buffer, this.charMeasure);
|
||||
this.selectionManager = new SelectionManager(this, this.charMeasure);
|
||||
this.element.addEventListener('mousedown', (e: MouseEvent) => this.selectionManager.onMouseDown(e));
|
||||
this.selectionManager.on('refresh', data => this.renderer.onSelectionChanged(data.start, data.end));
|
||||
this.selectionManager.on('newselection', text => {
|
||||
@@ -1373,7 +1388,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
return this.cancel(ev, true);
|
||||
}
|
||||
|
||||
if (isThirdLevelShift(this.browser, ev)) {
|
||||
if (this._isThirdLevelShift(this.browser, ev)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1394,6 +1409,19 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
return this.cancel(ev, true);
|
||||
}
|
||||
|
||||
private _isThirdLevelShift(browser: IBrowser, ev: KeyboardEvent): boolean {
|
||||
const thirdLevelKey =
|
||||
(browser.isMac && !this.options.macOptionIsMeta && ev.altKey && !ev.ctrlKey && !ev.metaKey) ||
|
||||
(browser.isMSWindows && ev.altKey && ev.ctrlKey && !ev.metaKey);
|
||||
|
||||
if (ev.type === 'keypress') {
|
||||
return thirdLevelKey;
|
||||
}
|
||||
|
||||
// Don't invoke for arrows, pageDown, home, backspace, etc. (on non-keypress events)
|
||||
return thirdLevelKey && (!ev.keyCode || ev.keyCode > 47);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an object that determines how a KeyboardEvent should be handled. The key of the
|
||||
* returned value is the new key code to pass to the PTY.
|
||||
@@ -1573,6 +1601,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
// page up
|
||||
if (ev.shiftKey) {
|
||||
result.scrollLines = -(this.rows - 1);
|
||||
} else if (modifiers) {
|
||||
result.key = C0.ESC + '[5;' + (modifiers + 1) + '~';
|
||||
} else {
|
||||
result.key = C0.ESC + '[5~';
|
||||
}
|
||||
@@ -1581,6 +1611,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
// page down
|
||||
if (ev.shiftKey) {
|
||||
result.scrollLines = this.rows - 1;
|
||||
} else if (modifiers) {
|
||||
result.key = C0.ESC + '[6;' + (modifiers + 1) + '~';
|
||||
} else {
|
||||
result.key = C0.ESC + '[6~';
|
||||
}
|
||||
@@ -1694,8 +1726,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
// ^] - Operating System Command (OSC)
|
||||
result.key = String.fromCharCode(29);
|
||||
}
|
||||
} else if (!this.browser.isMac && ev.altKey && !ev.ctrlKey && !ev.metaKey) {
|
||||
// On Mac this is a third level shift. Use <Esc> instead.
|
||||
} else if ((!this.browser.isMac || this.options.macOptionIsMeta) && ev.altKey && !ev.ctrlKey && !ev.metaKey) {
|
||||
// On macOS this is a third level shift when !macOptionIsMeta. Use <Esc> instead.
|
||||
if (ev.keyCode >= 65 && ev.keyCode <= 90) {
|
||||
result.key = C0.ESC + String.fromCharCode(ev.keyCode + 32);
|
||||
} else if (ev.keyCode === 192) {
|
||||
@@ -1728,7 +1760,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
* @param g
|
||||
* @param charset
|
||||
*/
|
||||
public setgCharset(g: number, charset: Charset): void {
|
||||
public setgCharset(g: number, charset: ICharset): void {
|
||||
this.charsets[g] = charset;
|
||||
if (this.glevel === g) {
|
||||
this.charset = charset;
|
||||
@@ -1761,7 +1793,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
}
|
||||
|
||||
if (!key || (
|
||||
(ev.altKey || ev.ctrlKey || ev.metaKey) && !isThirdLevelShift(this.browser, ev)
|
||||
(ev.altKey || ev.ctrlKey || ev.metaKey) && !this._isThirdLevelShift(this.browser, ev)
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
@@ -2075,11 +2107,9 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
this.options.cols = this.cols;
|
||||
const customKeyEventHandler = this.customKeyEventHandler;
|
||||
const inputHandler = this.inputHandler;
|
||||
const buffers = this.buffers;
|
||||
this.setup();
|
||||
this.customKeyEventHandler = customKeyEventHandler;
|
||||
this.inputHandler = inputHandler;
|
||||
this.buffers = buffers;
|
||||
this.refresh(0, this.rows - 1);
|
||||
this.viewport.syncScrollArea();
|
||||
}
|
||||
@@ -2155,19 +2185,6 @@ function off(el: any, type: string, handler: (event: Event) => any, capture: boo
|
||||
el.removeEventListener(type, handler, capture);
|
||||
}
|
||||
|
||||
function isThirdLevelShift(browser: IBrowser, ev: KeyboardEvent): boolean {
|
||||
const thirdLevelKey =
|
||||
(browser.isMac && ev.altKey && !ev.ctrlKey && !ev.metaKey) ||
|
||||
(browser.isMSWindows && ev.altKey && ev.ctrlKey && !ev.metaKey);
|
||||
|
||||
if (ev.type === 'keypress') {
|
||||
return thirdLevelKey;
|
||||
}
|
||||
|
||||
// Don't invoke for arrows, pageDown, home, backspace, etc. (on non-keypress events)
|
||||
return thirdLevelKey && (!ev.keyCode || ev.keyCode > 47);
|
||||
}
|
||||
|
||||
function wasMondifierKeyOnlyEvent(ev: KeyboardEvent): boolean {
|
||||
return ev.keyCode === 16 || // Shift
|
||||
ev.keyCode === 17 || // Ctrl
|
||||
@@ -2220,7 +2237,7 @@ function matchColorDistance(r1: number, g1: number, b1: number, r2: number, g2:
|
||||
return Math.pow(30 * (r1 - r2), 2)
|
||||
+ Math.pow(59 * (g1 - g2), 2)
|
||||
+ Math.pow(11 * (b1 - b2), 2);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
function matchColor_(r1: number, g1: number, b1: number): number {
|
||||
|
||||
+3
-18
@@ -3,33 +3,18 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
export type LinkMatcher = {
|
||||
id: number,
|
||||
regex: RegExp,
|
||||
handler: LinkMatcherHandler,
|
||||
hoverTooltipCallback?: LinkMatcherHandler,
|
||||
hoverLeaveCallback?: () => void,
|
||||
matchIndex?: number,
|
||||
validationCallback?: LinkMatcherValidationCallback,
|
||||
priority?: number
|
||||
};
|
||||
export type LinkMatcherHandler = (event: MouseEvent, uri: string) => boolean | void;
|
||||
export type LinkMatcherValidationCallback = (uri: string, callback: (isValid: boolean) => void) => void;
|
||||
|
||||
export type CustomKeyEventHandler = (event: KeyboardEvent) => boolean;
|
||||
export type Charset = {[key: string]: string};
|
||||
|
||||
export type CharData = [number, string, number, number];
|
||||
export type LineData = CharData[];
|
||||
|
||||
export type LinkHoverEvent = {
|
||||
x: number,
|
||||
y: number,
|
||||
length: number
|
||||
};
|
||||
|
||||
export enum LinkHoverEventTypes {
|
||||
HOVER = 'linkhover',
|
||||
TOOLTIP = 'linktooltip',
|
||||
LEAVE = 'linkleave'
|
||||
};
|
||||
}
|
||||
|
||||
export type FontWeight = 'normal' | 'bold' | 'bolder' | 'lighter' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900';
|
||||
|
||||
+3
-3
@@ -119,7 +119,7 @@ export class Viewport implements IViewport {
|
||||
this.viewportElement.scrollTop += ev.deltaY * multiplier;
|
||||
// Prevent the page from scrolling when the terminal scrolls
|
||||
ev.preventDefault();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the touchstart event, recording the touch occurred.
|
||||
@@ -127,7 +127,7 @@ export class Viewport implements IViewport {
|
||||
*/
|
||||
public onTouchStart(ev: TouchEvent): void {
|
||||
this.lastTouchY = ev.touches[0].pageY;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the touchmove event, scrolling the viewport if the position shifted.
|
||||
@@ -141,5 +141,5 @@ export class Viewport implements IViewport {
|
||||
}
|
||||
this.viewportElement.scrollTop += deltaY;
|
||||
ev.preventDefault();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*
|
||||
* Implements the attach method, that attaches the terminal to a WebSocket stream.
|
||||
*/
|
||||
|
||||
import { Terminal } from 'xterm';
|
||||
|
||||
export interface IAttachAddonTerminal extends Terminal {
|
||||
__socket?: WebSocket;
|
||||
__attachSocketBuffer?: string;
|
||||
|
||||
__getMessage?(ev: MessageEvent): void;
|
||||
__flushBuffer?(): void;
|
||||
__pushToBuffer?(data: string): void;
|
||||
__sendData?(data: string): void;
|
||||
}
|
||||
@@ -5,14 +5,14 @@
|
||||
|
||||
import { assert, expect } from 'chai';
|
||||
|
||||
import * as attach from './attach'
|
||||
import * as attach from './attach';
|
||||
|
||||
class MockTerminal {}
|
||||
|
||||
describe('attach addon', () => {
|
||||
describe('apply', () => {
|
||||
it('should do register the `attach` and `detach` methods', () => {
|
||||
attach.apply(MockTerminal);
|
||||
attach.apply(<any>MockTerminal);
|
||||
assert.equal(typeof (<any>MockTerminal).prototype.attach, 'function');
|
||||
assert.equal(typeof (<any>MockTerminal).prototype.detach, 'function');
|
||||
});
|
||||
|
||||
+55
-55
@@ -5,118 +5,118 @@
|
||||
* Implements the attach method, that attaches the terminal to a WebSocket stream.
|
||||
*/
|
||||
|
||||
/// <reference path="../../../typings/xterm.d.ts"/>
|
||||
|
||||
import { Terminal } from 'xterm';
|
||||
import { IAttachAddonTerminal } from './Intefaces';
|
||||
|
||||
/**
|
||||
* Attaches the given terminal to the given socket.
|
||||
*
|
||||
* @param {Terminal} term - The terminal to be attached to the given socket.
|
||||
* @param {WebSocket} socket - The socket to attach the current terminal.
|
||||
* @param {boolean} bidirectional - Whether the terminal should send data
|
||||
* to the socket as well.
|
||||
* @param {boolean} buffered - Whether the rendering of incoming data
|
||||
* should happen instantly or at a maximum
|
||||
* frequency of 1 rendering per 10ms.
|
||||
* @param term The terminal to be attached to the given socket.
|
||||
* @param socket The socket to attach the current terminal.
|
||||
* @param bidirectional Whether the terminal should send data to the socket as well.
|
||||
* @param buffered Whether the rendering of incoming data should happen instantly or at a maximum
|
||||
* frequency of 1 rendering per 10ms.
|
||||
*/
|
||||
export function attach(term, socket, bidirectional, buffered) {
|
||||
bidirectional = (typeof bidirectional == 'undefined') ? true : bidirectional;
|
||||
term.socket = socket;
|
||||
export function attach(term: Terminal, socket: WebSocket, bidirectional: boolean, buffered: boolean): void {
|
||||
const addonTerminal = <IAttachAddonTerminal>term;
|
||||
bidirectional = (typeof bidirectional === 'undefined') ? true : bidirectional;
|
||||
addonTerminal.__socket = socket;
|
||||
|
||||
term._flushBuffer = function() {
|
||||
term.write(term._attachSocketBuffer);
|
||||
term._attachSocketBuffer = null;
|
||||
addonTerminal.__flushBuffer = () => {
|
||||
addonTerminal.write(addonTerminal.__attachSocketBuffer);
|
||||
addonTerminal.__attachSocketBuffer = null;
|
||||
};
|
||||
|
||||
term._pushToBuffer = function(data) {
|
||||
if (term._attachSocketBuffer) {
|
||||
term._attachSocketBuffer += data;
|
||||
addonTerminal.__pushToBuffer = (data: string) => {
|
||||
if (addonTerminal.__attachSocketBuffer) {
|
||||
addonTerminal.__attachSocketBuffer += data;
|
||||
} else {
|
||||
term._attachSocketBuffer = data;
|
||||
setTimeout(term._flushBuffer, 10);
|
||||
addonTerminal.__attachSocketBuffer = data;
|
||||
setTimeout(addonTerminal.__flushBuffer, 10);
|
||||
}
|
||||
};
|
||||
|
||||
var myTextDecoder;
|
||||
let myTextDecoder;
|
||||
|
||||
term._getMessage = function(ev) {
|
||||
var str;
|
||||
if (typeof ev.data === "object") {
|
||||
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?";
|
||||
} else {
|
||||
throw 'TODO: handle Blob?';
|
||||
}
|
||||
}
|
||||
|
||||
if (buffered) {
|
||||
term._pushToBuffer(str || ev.data);
|
||||
addonTerminal.__pushToBuffer(str || ev.data);
|
||||
} else {
|
||||
term.write(str || ev.data);
|
||||
addonTerminal.write(str || ev.data);
|
||||
}
|
||||
};
|
||||
|
||||
term._sendData = function(data) {
|
||||
addonTerminal.__sendData = (data: string) => {
|
||||
if (socket.readyState !== 1) {
|
||||
return;
|
||||
}
|
||||
socket.send(data);
|
||||
};
|
||||
|
||||
socket.addEventListener('message', term._getMessage);
|
||||
socket.addEventListener('message', addonTerminal.__getMessage);
|
||||
|
||||
if (bidirectional) {
|
||||
term.on('data', term._sendData);
|
||||
addonTerminal.on('data', addonTerminal.__sendData);
|
||||
}
|
||||
|
||||
socket.addEventListener('close', term.detach.bind(term, socket));
|
||||
socket.addEventListener('error', term.detach.bind(term, socket));
|
||||
};
|
||||
socket.addEventListener('close', () => detach(addonTerminal, socket));
|
||||
socket.addEventListener('error', () => detach(addonTerminal, socket));
|
||||
}
|
||||
|
||||
/**
|
||||
* Detaches the given terminal from the given socket
|
||||
*
|
||||
* @param {Terminal} term - The terminal to be detached from the given socket.
|
||||
* @param {WebSocket} socket - The socket from which to detach the current
|
||||
* terminal.
|
||||
* @param term The terminal to be detached from the given socket.
|
||||
* @param socket The socket from which to detach the current terminal.
|
||||
*/
|
||||
export function detach(term, socket) {
|
||||
term.off('data', term._sendData);
|
||||
export function detach(term: Terminal, socket: WebSocket): void {
|
||||
const addonTerminal = <IAttachAddonTerminal>term;
|
||||
addonTerminal.off('data', addonTerminal.__sendData);
|
||||
|
||||
socket = (typeof socket == 'undefined') ? term.socket : socket;
|
||||
socket = (typeof socket === 'undefined') ? addonTerminal.__socket : socket;
|
||||
|
||||
if (socket) {
|
||||
socket.removeEventListener('message', term._getMessage);
|
||||
socket.removeEventListener('message', addonTerminal.__getMessage);
|
||||
}
|
||||
|
||||
delete term.socket;
|
||||
};
|
||||
delete addonTerminal.__socket;
|
||||
}
|
||||
|
||||
|
||||
export function apply(terminalConstructor) {
|
||||
export function apply(terminalConstructor: typeof Terminal): void {
|
||||
/**
|
||||
* Attaches the current terminal to the given socket
|
||||
*
|
||||
* @param {WebSocket} socket - The socket to attach the current terminal.
|
||||
* @param {boolean} bidirectional - Whether the terminal should send data
|
||||
* to the socket as well.
|
||||
* @param {boolean} buffered - Whether the rendering of incoming data
|
||||
* should happen instantly or at a maximum
|
||||
* frequency of 1 rendering per 10ms.
|
||||
* @param socket The socket to attach the current terminal.
|
||||
* @param bidirectional Whether the terminal should send data to the socket as well.
|
||||
* @param buffered Whether the rendering of incoming data should happen instantly or at a maximum
|
||||
* frequency of 1 rendering per 10ms.
|
||||
*/
|
||||
terminalConstructor.prototype.attach = function(socket, bidirectional, buffered) {
|
||||
return attach(this, socket, bidirectional, buffered);
|
||||
(<any>terminalConstructor.prototype).attach = function (socket: WebSocket, bidirectional: boolean, buffered: boolean): void {
|
||||
attach(this, socket, bidirectional, buffered);
|
||||
};
|
||||
|
||||
/**
|
||||
* Detaches the current terminal from the given socket.
|
||||
*
|
||||
* @param {WebSocket} socket - The socket from which to detach the current
|
||||
* terminal.
|
||||
* @param socket The socket from which to detach the current terminal.
|
||||
*/
|
||||
terminalConstructor.prototype.detach = function(socket) {
|
||||
return detach(this, socket);
|
||||
(<any>terminalConstructor.prototype).detach = function (socket: WebSocket): void {
|
||||
detach(this, socket);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"rootDir": ".",
|
||||
"outDir": "../../../lib/addons/attach/",
|
||||
"sourceMap": true,
|
||||
"removeComments": true
|
||||
"removeComments": true,
|
||||
"declaration": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,14 +5,14 @@
|
||||
|
||||
import { assert, expect } from 'chai';
|
||||
|
||||
import * as fit from './fit'
|
||||
import * as fit from './fit';
|
||||
|
||||
class MockTerminal {}
|
||||
|
||||
describe('fit addon', () => {
|
||||
describe('apply', () => {
|
||||
it('should do register the `proposeGeometry` and `fit` methods', () => {
|
||||
fit.apply(MockTerminal);
|
||||
fit.apply(<any>MockTerminal);
|
||||
assert.equal(typeof (<any>MockTerminal).prototype.proposeGeometry, 'function');
|
||||
assert.equal(typeof (<any>MockTerminal).prototype.fit, 'function');
|
||||
});
|
||||
|
||||
+35
-26
@@ -13,43 +13,52 @@
|
||||
* row and truncate its width with the current number of columns).
|
||||
*/
|
||||
|
||||
export function proposeGeometry(term) {
|
||||
/// <reference path="../../../typings/xterm.d.ts"/>
|
||||
|
||||
import { Terminal } from 'xterm';
|
||||
|
||||
export interface IGeometry {
|
||||
rows: number;
|
||||
cols: number;
|
||||
}
|
||||
|
||||
export function proposeGeometry(term: Terminal): IGeometry {
|
||||
if (!term.element.parentElement) {
|
||||
return null;
|
||||
}
|
||||
var parentElementStyle = window.getComputedStyle(term.element.parentElement);
|
||||
var parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height'));
|
||||
var parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width')) - 17);
|
||||
var elementStyle = window.getComputedStyle(term.element);
|
||||
var elementPaddingVer = parseInt(elementStyle.getPropertyValue('padding-top')) + parseInt(elementStyle.getPropertyValue('padding-bottom'));
|
||||
var elementPaddingHor = parseInt(elementStyle.getPropertyValue('padding-right')) + parseInt(elementStyle.getPropertyValue('padding-left'));
|
||||
var availableHeight = parentElementHeight - elementPaddingVer;
|
||||
var availableWidth = parentElementWidth - elementPaddingHor;
|
||||
var geometry = {
|
||||
cols: Math.floor(availableWidth / term.renderer.dimensions.actualCellWidth),
|
||||
rows: Math.floor(availableHeight / term.renderer.dimensions.actualCellHeight)
|
||||
const parentElementStyle = window.getComputedStyle(term.element.parentElement);
|
||||
const parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height'));
|
||||
const parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width')) - 17);
|
||||
const elementStyle = window.getComputedStyle(term.element);
|
||||
const elementPaddingVer = parseInt(elementStyle.getPropertyValue('padding-top')) + parseInt(elementStyle.getPropertyValue('padding-bottom'));
|
||||
const elementPaddingHor = parseInt(elementStyle.getPropertyValue('padding-right')) + parseInt(elementStyle.getPropertyValue('padding-left'));
|
||||
const availableHeight = parentElementHeight - elementPaddingVer;
|
||||
const availableWidth = parentElementWidth - elementPaddingHor;
|
||||
const geometry = {
|
||||
cols: Math.floor(availableWidth / (<any>term).renderer.dimensions.actualCellWidth),
|
||||
rows: Math.floor(availableHeight / (<any>term).renderer.dimensions.actualCellHeight)
|
||||
};
|
||||
|
||||
return geometry;
|
||||
};
|
||||
}
|
||||
|
||||
export function fit(term) {
|
||||
var geometry = proposeGeometry(term);
|
||||
export function fit(term: Terminal): void {
|
||||
const geometry = proposeGeometry(term);
|
||||
if (geometry) {
|
||||
// Force a full render
|
||||
if (term.rows !== geometry.rows || term.cols !== geometry.cols) {
|
||||
term.renderer.clear();
|
||||
(<any>term).renderer.clear();
|
||||
term.resize(geometry.cols, geometry.rows);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export function apply(terminalConstructor) {
|
||||
terminalConstructor.prototype.proposeGeometry = function() {
|
||||
return proposeGeometry(this);
|
||||
}
|
||||
|
||||
terminalConstructor.prototype.fit = function() {
|
||||
return fit(this);
|
||||
}
|
||||
}
|
||||
|
||||
export function apply(terminalConstructor: typeof Terminal): void {
|
||||
(<any>terminalConstructor.prototype).proposeGeometry = function (): IGeometry {
|
||||
return proposeGeometry(this);
|
||||
};
|
||||
|
||||
(<any>terminalConstructor.prototype).fit = function (): void {
|
||||
fit(this);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"rootDir": ".",
|
||||
"outDir": "../../../lib/addons/fit/",
|
||||
"sourceMap": true,
|
||||
"removeComments": true
|
||||
"removeComments": true,
|
||||
"declaration": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,14 +5,14 @@
|
||||
|
||||
import { assert, expect } from 'chai';
|
||||
|
||||
import * as fullscreen from './fullscreen'
|
||||
import * as fullscreen from './fullscreen';
|
||||
|
||||
class MockTerminal {}
|
||||
|
||||
describe('fullscreen addon', () => {
|
||||
describe('apply', () => {
|
||||
it('should do register the `toggleFullscreen` method', () => {
|
||||
fullscreen.apply(MockTerminal);
|
||||
fullscreen.apply(<any>MockTerminal);
|
||||
assert.equal(typeof (<any>MockTerminal).prototype.toggleFullScreen, 'function');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,15 +3,19 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
/// <reference path="../../../typings/xterm.d.ts"/>
|
||||
|
||||
import { Terminal } from 'xterm';
|
||||
|
||||
/**
|
||||
* Toggle the given terminal's fullscreen mode.
|
||||
* @param {Terminal} term - The terminal to toggle full screen mode
|
||||
* @param {boolean} fullscreen - Toggle fullscreen on (true) or off (false)
|
||||
* @param term The terminal to toggle full screen mode
|
||||
* @param fullscreen Toggle fullscreen on (true) or off (false)
|
||||
*/
|
||||
export function toggleFullScreen(term, fullscreen) {
|
||||
var fn;
|
||||
export function toggleFullScreen(term: Terminal, fullscreen: boolean): void {
|
||||
let fn: string;
|
||||
|
||||
if (typeof fullscreen == 'undefined') {
|
||||
if (typeof fullscreen === 'undefined') {
|
||||
fn = (term.element.classList.contains('fullscreen')) ? 'remove' : 'add';
|
||||
} else if (!fullscreen) {
|
||||
fn = 'remove';
|
||||
@@ -20,10 +24,10 @@ export function toggleFullScreen(term, fullscreen) {
|
||||
}
|
||||
|
||||
term.element.classList[fn]('fullscreen');
|
||||
};
|
||||
}
|
||||
|
||||
export function apply(terminalConstructor) {
|
||||
terminalConstructor.prototype.toggleFullScreen = function (fullscreen) {
|
||||
return toggleFullScreen(this, fullscreen);
|
||||
export function apply(terminalConstructor: typeof Terminal): void {
|
||||
(<any>terminalConstructor.prototype).toggleFullScreen = function (fullscreen: boolean): void {
|
||||
toggleFullScreen(this, fullscreen);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"rootDir": ".",
|
||||
"outDir": "../../../lib/addons/fullscreen/",
|
||||
"sourceMap": true,
|
||||
"removeComments": true
|
||||
"removeComments": true,
|
||||
"declaration": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { Terminal } from 'xterm';
|
||||
|
||||
export interface ISearchAddonTerminal extends Terminal {
|
||||
__searchHelper?: ISearchHelper;
|
||||
|
||||
// TODO: Reuse ITerminal from core
|
||||
buffer: any;
|
||||
selectionManager: any;
|
||||
}
|
||||
|
||||
export interface ISearchHelper {
|
||||
findNext(term: string): boolean;
|
||||
findPrevious(term: string): boolean;
|
||||
}
|
||||
@@ -3,8 +3,7 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
// import { ITerminal } from '../../Interfaces';
|
||||
// import { translateBufferLineToString } from '../../utils/BufferLine';
|
||||
import { ISearchHelper, ISearchAddonTerminal } from './Interfaces';
|
||||
|
||||
interface ISearchResult {
|
||||
term: string;
|
||||
@@ -15,8 +14,8 @@ interface ISearchResult {
|
||||
/**
|
||||
* A class that knows how to search the terminal and how to display the results.
|
||||
*/
|
||||
export class SearchHelper {
|
||||
constructor(private _terminal: any) {
|
||||
export class SearchHelper implements ISearchHelper {
|
||||
constructor(private _terminal: ISearchAddonTerminal) {
|
||||
// TODO: Search for multiple instances on 1 line
|
||||
// TODO: Don't use the actual selection, instead use a "find selection" so multiple instances can be highlighted
|
||||
// TODO: Highlight other instances in the viewport
|
||||
@@ -149,7 +148,7 @@ export class SearchHelper {
|
||||
return false;
|
||||
}
|
||||
this._terminal.selectionManager.setSelection(result.col, result.row, result.term.length);
|
||||
this._terminal.scrollLines(result.row - this._terminal.buffer.ydisp, false);
|
||||
this._terminal.scrollLines(result.row - this._terminal.buffer.ydisp);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
+25
-20
@@ -3,8 +3,11 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { SearchHelper } from './SearchHelper';
|
||||
/// <reference path="../../../typings/xterm.d.ts"/>
|
||||
|
||||
import { SearchHelper } from './SearchHelper';
|
||||
import { Terminal } from 'xterm';
|
||||
import { ISearchAddonTerminal } from './Interfaces';
|
||||
|
||||
/**
|
||||
* Find the next instance of the term, then scroll to and select it. If it
|
||||
@@ -12,12 +15,13 @@ import { SearchHelper } from './SearchHelper';
|
||||
* @param term Tne search term.
|
||||
* @return Whether a result was found.
|
||||
*/
|
||||
export function findNext(terminal: any, term: string): boolean {
|
||||
if (!terminal._searchHelper) {
|
||||
terminal.searchHelper = new SearchHelper(terminal);
|
||||
export function findNext(terminal: Terminal, term: string): boolean {
|
||||
const addonTerminal = <ISearchAddonTerminal>terminal;
|
||||
if (!addonTerminal.__searchHelper) {
|
||||
addonTerminal.__searchHelper = new SearchHelper(addonTerminal);
|
||||
}
|
||||
return (<SearchHelper>terminal.searchHelper).findNext(term);
|
||||
};
|
||||
return addonTerminal.__searchHelper.findNext(term);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the previous instance of the term, then scroll to and select it. If it
|
||||
@@ -25,19 +29,20 @@ export function findNext(terminal: any, term: string): boolean {
|
||||
* @param term Tne search term.
|
||||
* @return Whether a result was found.
|
||||
*/
|
||||
export function findPrevious(terminal: any, term: string): boolean {
|
||||
if (!terminal._searchHelper) {
|
||||
terminal.searchHelper = new SearchHelper(terminal);
|
||||
}
|
||||
return (<SearchHelper>terminal.searchHelper).findPrevious(term);
|
||||
};
|
||||
|
||||
export function apply(terminalConstructor) {
|
||||
terminalConstructor.prototype.findNext = function(term) {
|
||||
return findNext(this, term);
|
||||
}
|
||||
|
||||
terminalConstructor.prototype.findPrevious = function(term) {
|
||||
return findPrevious(this, term);
|
||||
export function findPrevious(terminal: Terminal, term: string): boolean {
|
||||
const addonTerminal = <ISearchAddonTerminal>terminal;
|
||||
if (!addonTerminal.__searchHelper) {
|
||||
addonTerminal.__searchHelper = new SearchHelper(addonTerminal);
|
||||
}
|
||||
return addonTerminal.__searchHelper.findPrevious(term);
|
||||
}
|
||||
|
||||
export function apply(terminalConstructor: typeof Terminal): void {
|
||||
(<any>terminalConstructor.prototype).findNext = function(term: string): boolean {
|
||||
return findNext(this, term);
|
||||
};
|
||||
|
||||
(<any>terminalConstructor.prototype).findPrevious = function(term: string): boolean {
|
||||
return findPrevious(this, term);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"rootDir": ".",
|
||||
"outDir": "../../../lib/addons/search/",
|
||||
"sourceMap": true,
|
||||
"removeComments": true
|
||||
"removeComments": true,
|
||||
"declaration": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*
|
||||
* Implements the attach method, that attaches the terminal to a WebSocket stream.
|
||||
*/
|
||||
|
||||
import { Terminal } from 'xterm';
|
||||
|
||||
export interface ITerminadoAddonTerminal extends Terminal {
|
||||
__socket?: WebSocket;
|
||||
__attachSocketBuffer?: string;
|
||||
|
||||
__getMessage?(ev: MessageEvent): void;
|
||||
__flushBuffer?(): void;
|
||||
__pushToBuffer?(data: string): void;
|
||||
__sendData?(data: string): void;
|
||||
__setSize?(size: {rows: number, cols: number}): void;
|
||||
}
|
||||
@@ -5,14 +5,14 @@
|
||||
|
||||
import { assert, expect } from 'chai';
|
||||
|
||||
import * as terminado from './terminado'
|
||||
import * as terminado from './terminado';
|
||||
|
||||
class MockTerminal {}
|
||||
|
||||
describe('terminado addon', () => {
|
||||
describe('apply', () => {
|
||||
it('should do register the `terminadoAttach` and `terminadoDetach` methods', () => {
|
||||
terminado.apply(MockTerminal);
|
||||
terminado.apply(<any>MockTerminal);
|
||||
assert.equal(typeof (<any>MockTerminal).prototype.terminadoAttach, 'function');
|
||||
assert.equal(typeof (<any>MockTerminal).prototype.terminadoDetach, 'function');
|
||||
});
|
||||
|
||||
@@ -6,106 +6,107 @@
|
||||
* WebSocket stream.
|
||||
*/
|
||||
|
||||
/// <reference path="../../../typings/xterm.d.ts"/>
|
||||
|
||||
import { Terminal } from 'xterm';
|
||||
import { ITerminadoAddonTerminal } from './Intefaces';
|
||||
|
||||
/**
|
||||
* Attaches the given terminal to the given socket.
|
||||
*
|
||||
* @param {Terminal} term - The terminal to be attached to the given socket.
|
||||
* @param {WebSocket} socket - The socket to attach the current terminal.
|
||||
* @param {boolean} bidirectional - Whether the terminal should send data
|
||||
* to the socket as well.
|
||||
* @param {boolean} buffered - Whether the rendering of incoming data
|
||||
* should happen instantly or at a maximum
|
||||
* frequency of 1 rendering per 10ms.
|
||||
* @param term The terminal to be attached to the given socket.
|
||||
* @param socket The socket to attach the current terminal.
|
||||
* @param bidirectional Whether the terminal should send data to the socket as well.
|
||||
* @param buffered Whether the rendering of incoming data should happen instantly or at a maximum
|
||||
* frequency of 1 rendering per 10ms.
|
||||
*/
|
||||
export function terminadoAttach(term, socket, bidirectional, buffered) {
|
||||
bidirectional = (typeof bidirectional == 'undefined') ? true : bidirectional;
|
||||
term.socket = socket;
|
||||
export function terminadoAttach(term: Terminal, socket: WebSocket, bidirectional: boolean, buffered: boolean): void {
|
||||
const addonTerminal = <ITerminadoAddonTerminal>term;
|
||||
bidirectional = (typeof bidirectional === 'undefined') ? true : bidirectional;
|
||||
addonTerminal.__socket = socket;
|
||||
|
||||
term._flushBuffer = function() {
|
||||
term.write(term._attachSocketBuffer);
|
||||
term._attachSocketBuffer = null;
|
||||
addonTerminal.__flushBuffer = () => {
|
||||
addonTerminal.write(addonTerminal.__attachSocketBuffer);
|
||||
addonTerminal.__attachSocketBuffer = null;
|
||||
};
|
||||
|
||||
term._pushToBuffer = function(data) {
|
||||
if (term._attachSocketBuffer) {
|
||||
term._attachSocketBuffer += data;
|
||||
addonTerminal.__pushToBuffer = (data: string) => {
|
||||
if (addonTerminal.__attachSocketBuffer) {
|
||||
addonTerminal.__attachSocketBuffer += data;
|
||||
} else {
|
||||
term._attachSocketBuffer = data;
|
||||
setTimeout(term._flushBuffer, 10);
|
||||
addonTerminal.__attachSocketBuffer = data;
|
||||
setTimeout(addonTerminal.__flushBuffer, 10);
|
||||
}
|
||||
};
|
||||
|
||||
term._getMessage = function(ev) {
|
||||
var data = JSON.parse(ev.data)
|
||||
if( data[0] == "stdout" ) {
|
||||
addonTerminal.__getMessage = (ev: MessageEvent) => {
|
||||
const data = JSON.parse(ev.data);
|
||||
if (data[0] === 'stdout') {
|
||||
if (buffered) {
|
||||
term._pushToBuffer(data[1]);
|
||||
addonTerminal.__pushToBuffer(data[1]);
|
||||
} else {
|
||||
term.write(data[1]);
|
||||
addonTerminal.write(data[1]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
term._sendData = function(data) {
|
||||
addonTerminal.__sendData = (data: string) => {
|
||||
socket.send(JSON.stringify(['stdin', data]));
|
||||
};
|
||||
|
||||
term._setSize = function(size) {
|
||||
addonTerminal.__setSize = (size: {rows: number, cols: number}) => {
|
||||
socket.send(JSON.stringify(['set_size', size.rows, size.cols]));
|
||||
};
|
||||
|
||||
socket.addEventListener('message', term._getMessage);
|
||||
socket.addEventListener('message', addonTerminal.__getMessage);
|
||||
|
||||
if (bidirectional) {
|
||||
term.on('data', term._sendData);
|
||||
addonTerminal.on('data', addonTerminal.__sendData);
|
||||
}
|
||||
term.on('resize', term._setSize);
|
||||
addonTerminal.on('resize', addonTerminal.__setSize);
|
||||
|
||||
socket.addEventListener('close', term.terminadoDetach.bind(term, socket));
|
||||
socket.addEventListener('error', term.terminadoDetach.bind(term, socket));
|
||||
};
|
||||
socket.addEventListener('close', () => terminadoDetach(addonTerminal, socket));
|
||||
socket.addEventListener('error', () => terminadoDetach(addonTerminal, socket));
|
||||
}
|
||||
|
||||
/**
|
||||
* Detaches the given terminal from the given socket
|
||||
*
|
||||
* @param {Xterm} term - The terminal to be detached from the given socket.
|
||||
* @param {WebSocket} socket - The socket from which to detach the current
|
||||
* terminal.
|
||||
* @param term The terminal to be detached from the given socket.
|
||||
* @param socket The socket from which to detach the current terminal.
|
||||
*/
|
||||
export function terminadoDetach(term, socket) {
|
||||
term.off('data', term._sendData);
|
||||
export function terminadoDetach(term: Terminal, socket: WebSocket): void {
|
||||
const addonTerminal = <ITerminadoAddonTerminal>term;
|
||||
addonTerminal.off('data', addonTerminal.__sendData);
|
||||
|
||||
socket = (typeof socket == 'undefined') ? term.socket : socket;
|
||||
socket = (typeof socket === 'undefined') ? addonTerminal.__socket : socket;
|
||||
|
||||
if (socket) {
|
||||
socket.removeEventListener('message', term._getMessage);
|
||||
socket.removeEventListener('message', addonTerminal.__getMessage);
|
||||
}
|
||||
|
||||
delete term.socket;
|
||||
};
|
||||
delete addonTerminal.__socket;
|
||||
}
|
||||
|
||||
export function apply(terminalConstructor) {
|
||||
export function apply(terminalConstructor: typeof Terminal): void {
|
||||
/**
|
||||
* Attaches the current terminal to the given socket
|
||||
*
|
||||
* @param {WebSocket} socket - The socket to attach the current terminal.
|
||||
* @param {boolean} bidirectional - Whether the terminal should send data
|
||||
* to the socket as well.
|
||||
* @param {boolean} buffered - Whether the rendering of incoming data
|
||||
* should happen instantly or at a maximum
|
||||
* frequency of 1 rendering per 10ms.
|
||||
* @param socket - The socket to attach the current terminal.
|
||||
* @param bidirectional - Whether the terminal should send data to the socket as well.
|
||||
* @param buffered - Whether the rendering of incoming data should happen instantly or at a
|
||||
* maximum frequency of 1 rendering per 10ms.
|
||||
*/
|
||||
terminalConstructor.prototype.terminadoAttach = function(socket, bidirectional, buffered) {
|
||||
(<any>terminalConstructor.prototype).terminadoAttach = function (socket: WebSocket, bidirectional: boolean, buffered: boolean): void {
|
||||
return terminadoAttach(this, socket, bidirectional, buffered);
|
||||
};
|
||||
|
||||
/**
|
||||
* Detaches the current terminal from the given socket.
|
||||
*
|
||||
* @param {WebSocket} socket - The socket from which to detach the current
|
||||
* terminal.
|
||||
* @param socket The socket from which to detach the current terminal.
|
||||
*/
|
||||
terminalConstructor.prototype.terminadoDetach = function(socket) {
|
||||
(<any>terminalConstructor.prototype).terminadoDetach = function (socket: WebSocket): void {
|
||||
return terminadoDetach(this, socket);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"rootDir": ".",
|
||||
"outDir": "../../../lib/addons/terminado/",
|
||||
"sourceMap": true,
|
||||
"removeComments": true
|
||||
"removeComments": true,
|
||||
"declaration": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { Terminal } from 'xterm';
|
||||
|
||||
export interface IWinptyCompatAddonTerminal extends Terminal {
|
||||
buffer: any;
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
"rootDir": ".",
|
||||
"outDir": "../../../lib/addons/winptyCompat/",
|
||||
"sourceMap": true,
|
||||
"removeComments": true
|
||||
"removeComments": true,
|
||||
"declaration": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,14 +5,14 @@
|
||||
|
||||
import { assert, expect } from 'chai';
|
||||
|
||||
import * as winptyCompat from './winptyCompat'
|
||||
import * as winptyCompat from './winptyCompat';
|
||||
|
||||
class MockTerminal {}
|
||||
|
||||
describe('winptyCompat addon', () => {
|
||||
describe('apply', () => {
|
||||
it('should do register the `winptyCompatInit` method', () => {
|
||||
winptyCompat.apply(MockTerminal);
|
||||
winptyCompat.apply(<any>MockTerminal);
|
||||
assert.equal(typeof (<any>MockTerminal).prototype.winptyCompatInit, 'function');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,36 +3,43 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
export function winptyCompatInit(terminal): void {
|
||||
// Don't do anything when the platform is not Windows
|
||||
const isWindows = ['Windows', 'Win16', 'Win32', 'WinCE'].indexOf(navigator.platform) >= 0;
|
||||
if (!isWindows) {
|
||||
return;
|
||||
/// <reference path="../../../typings/xterm.d.ts"/>
|
||||
|
||||
import { Terminal } from 'xterm';
|
||||
import { IWinptyCompatAddonTerminal } from './Interfaces';
|
||||
|
||||
export function winptyCompatInit(terminal: Terminal): void {
|
||||
const addonTerminal = <IWinptyCompatAddonTerminal>terminal;
|
||||
|
||||
// Don't do anything when the platform is not Windows
|
||||
const isWindows = ['Windows', 'Win16', 'Win32', 'WinCE'].indexOf(navigator.platform) >= 0;
|
||||
if (!isWindows) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Winpty does not support wraparound mode which means that lines will never
|
||||
// be marked as wrapped. This causes issues for things like copying a line
|
||||
// retaining the wrapped new line characters or if consumers are listening
|
||||
// in on the data stream.
|
||||
//
|
||||
// The workaround for this is to listen to every incoming line feed and mark
|
||||
// the line as wrapped if the last character in the previous line is not a
|
||||
// space. This is certainly not without its problems, but generally on
|
||||
// Windows when text reaches the end of the terminal it's likely going to be
|
||||
// wrapped.
|
||||
addonTerminal.on('linefeed', () => {
|
||||
const line = addonTerminal.buffer.lines.get(addonTerminal.buffer.ybase + addonTerminal.buffer.y - 1);
|
||||
const lastChar = line[addonTerminal.cols - 1];
|
||||
|
||||
if (lastChar[3] !== 32 /* ' ' */) {
|
||||
const nextLine = addonTerminal.buffer.lines.get(addonTerminal.buffer.ybase + addonTerminal.buffer.y);
|
||||
(<any>nextLine).isWrapped = true;
|
||||
}
|
||||
|
||||
// Winpty does not support wraparound mode which means that lines will never
|
||||
// be marked as wrapped. This causes issues for things like copying a line
|
||||
// retaining the wrapped new line characters or if consumers are listening
|
||||
// in on the data stream.
|
||||
//
|
||||
// The workaround for this is to listen to every incoming line feed and mark
|
||||
// the line as wrapped if the last character in the previous line is not a
|
||||
// space. This is certainly not without its problems, but generally on
|
||||
// Windows when text reaches the end of the terminal it's likely going to be
|
||||
// wrapped.
|
||||
terminal.on('linefeed', () => {
|
||||
const line = terminal.buffer.lines.get(terminal.buffer.ybase + terminal.buffer.y - 1);
|
||||
const lastChar = line[terminal.cols - 1];
|
||||
|
||||
if (lastChar[3] !== 32 /* ' ' */) {
|
||||
const nextLine = terminal.buffer.lines.get(terminal.buffer.ybase + terminal.buffer.y);
|
||||
(<any>nextLine).isWrapped = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function apply(terminalConstructor) {
|
||||
terminalConstructor.prototype.winptyCompatInit = function(): void {
|
||||
export function apply(terminalConstructor: typeof Terminal): void {
|
||||
(<any>terminalConstructor.prototype).winptyCompatInit = function (): void {
|
||||
winptyCompatInit(this);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"rootDir": ".",
|
||||
"outDir": "../../../lib/addons/zmodem/",
|
||||
"sourceMap": true,
|
||||
"removeComments": true
|
||||
"removeComments": true,
|
||||
"declaration": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,14 +5,14 @@
|
||||
|
||||
import { assert, expect } from 'chai';
|
||||
|
||||
import * as zmodem from './zmodem'
|
||||
import * as zmodem from './zmodem';
|
||||
|
||||
class MockTerminal {}
|
||||
|
||||
describe('zmodem addon', () => {
|
||||
describe('apply', () => {
|
||||
it('should do register the `zmodemAttach` method and `zmodemBrowser` attribute', () => {
|
||||
zmodem.apply(MockTerminal);
|
||||
zmodem.apply(<any>MockTerminal);
|
||||
assert.equal(typeof (<any>MockTerminal).prototype.zmodemAttach, 'function');
|
||||
assert.equal(typeof (<any>MockTerminal).prototype.zmodemBrowser, 'object');
|
||||
});
|
||||
|
||||
+31
-29
@@ -1,3 +1,12 @@
|
||||
/**
|
||||
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
/// <reference path="../../../typings/xterm.d.ts"/>
|
||||
|
||||
import { Terminal } from 'xterm';
|
||||
|
||||
/**
|
||||
*
|
||||
* Allow xterm.js to handle ZMODEM uploads and downloads.
|
||||
@@ -27,47 +36,40 @@
|
||||
* via `detach()` and a re-`attach()`.)
|
||||
*/
|
||||
|
||||
let Zmodem;
|
||||
let zmodem;
|
||||
|
||||
export function zmodemAttach(term, ws, opts) {
|
||||
if (!opts) opts = {};
|
||||
export interface IZModemOptions {
|
||||
noTerminalWriteOutsideSession?: boolean;
|
||||
}
|
||||
|
||||
var senderFunc = function _ws_sender_func(octets) {
|
||||
ws.send(new Uint8Array(octets));
|
||||
};
|
||||
export function zmodemAttach(term: Terminal, ws: WebSocket, opts: IZModemOptions = {}): void {
|
||||
const senderFunc = (octets: ArrayLike<number>) => ws.send(new Uint8Array(octets));
|
||||
|
||||
var zsentry;
|
||||
let zsentry;
|
||||
|
||||
function _shouldWrite() {
|
||||
function _shouldWrite(): boolean {
|
||||
return !!zsentry.get_confirmed_session() || !opts.noTerminalWriteOutsideSession;
|
||||
}
|
||||
|
||||
zsentry = new Zmodem.Sentry( {
|
||||
to_terminal: function _to_terminal(octets) {
|
||||
zsentry = new zmodem.Sentry({
|
||||
to_terminal: (octets: ArrayLike<number>) => {
|
||||
if (_shouldWrite()) {
|
||||
term.write(
|
||||
String.fromCharCode.apply(String, octets)
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
sender: senderFunc,
|
||||
on_retract: () => (<any>term).emit('zmodemRetract'),
|
||||
on_detect: (detection: any) => (<any>term).emit('zmodemDetect', detection)
|
||||
});
|
||||
|
||||
on_retract: function _on_retract() {
|
||||
term.emit('zmodemRetract');
|
||||
},
|
||||
function handleWSMessage(evt: MessageEvent): void {
|
||||
|
||||
on_detect: function _on_detect(detection) {
|
||||
term.emit('zmodemDetect', detection);
|
||||
},
|
||||
} );
|
||||
|
||||
function handleWSMessage(evt) {
|
||||
|
||||
//In testing with xterm.js’s demo the first message was
|
||||
//always text even if the rest were binary. While that
|
||||
//may be specific to xterm.js’s demo, ultimately we
|
||||
//should reject anything that isn’t binary.
|
||||
// In testing with xterm.js’s demo the first message was
|
||||
// always text even if the rest were binary. While that
|
||||
// may be specific to xterm.js’s demo, ultimately we
|
||||
// should reject anything that isn’t binary.
|
||||
if (typeof evt.data === 'string') {
|
||||
if (_shouldWrite()) {
|
||||
term.write(evt.data);
|
||||
@@ -82,9 +84,9 @@ export function zmodemAttach(term, ws, opts) {
|
||||
ws.addEventListener('message', handleWSMessage);
|
||||
}
|
||||
|
||||
export function apply(terminalConstructor) {
|
||||
Zmodem = (typeof window == 'object') ? (<any>window).ZModem : {Browser: null}; // Nullify browser for tests
|
||||
export function apply(terminalConstructor: typeof Terminal): void {
|
||||
zmodem = (typeof window === 'object') ? (<any>window).ZModem : {Browser: null}; // Nullify browser for tests
|
||||
|
||||
terminalConstructor.prototype.zmodemAttach = zmodemAttach.bind(this, this);
|
||||
terminalConstructor.prototype.zmodemBrowser = Zmodem.Browser
|
||||
(<any>terminalConstructor.prototype).zmodemAttach = zmodemAttach.bind(this, this);
|
||||
(<any>terminalConstructor.prototype).zmodemBrowser = zmodem.Browser;
|
||||
}
|
||||
|
||||
@@ -178,7 +178,7 @@ export class MouseZoneManager implements IMouseZoneManager {
|
||||
if (zone.y === coords[1] && zone.x1 <= coords[0] && zone.x2 > coords[0]) {
|
||||
return zone;
|
||||
}
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,7 +201,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
* @param color The color of the character.
|
||||
*/
|
||||
protected fillCharTrueColor(terminal: ITerminal, charData: CharData, x: number, y: number): void {
|
||||
this._ctx.font = `${terminal.options.fontSize * window.devicePixelRatio}px ${terminal.options.fontFamily}`;
|
||||
this._ctx.font = this._getFont(terminal, false);
|
||||
this._ctx.textBaseline = 'top';
|
||||
this._clipRow(terminal, y);
|
||||
this._ctx.fillText(
|
||||
@@ -230,7 +230,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
colorIndex = fg + 2;
|
||||
} else {
|
||||
// If default color and bold
|
||||
if (bold && terminal.options.enableBold) {
|
||||
if (bold) {
|
||||
colorIndex = 1;
|
||||
}
|
||||
}
|
||||
@@ -251,13 +251,6 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
this._ctx.globalAlpha = DIM_OPACITY;
|
||||
}
|
||||
|
||||
// Draw the non-bold version of the same color if bold is not enabled
|
||||
if (bold && !terminal.options.enableBold) {
|
||||
// Ignore default color as it's not touched above
|
||||
if (colorIndex > 1) {
|
||||
colorIndex -= 8;
|
||||
}
|
||||
}
|
||||
|
||||
this._ctx.drawImage(this._charAtlas,
|
||||
code * charAtlasCellWidth,
|
||||
@@ -289,10 +282,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
*/
|
||||
private _drawUncachedChar(terminal: ITerminal, char: string, width: number, fg: number, x: number, y: number, bold: boolean, dim: boolean): void {
|
||||
this._ctx.save();
|
||||
this._ctx.font = `${terminal.options.fontSize * window.devicePixelRatio}px ${terminal.options.fontFamily}`;
|
||||
if (bold && terminal.options.enableBold) {
|
||||
this._ctx.font = `bold ${this._ctx.font}`;
|
||||
}
|
||||
this._ctx.font = this._getFont(terminal, bold);
|
||||
this._ctx.textBaseline = 'top';
|
||||
|
||||
if (fg === INVERTED_DEFAULT_COLOR) {
|
||||
@@ -332,5 +322,16 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
this._scaledCellHeight);
|
||||
this._ctx.clip();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current font.
|
||||
* @param terminal The terminal.
|
||||
* @param isBold If we should use the bold fontWeight.
|
||||
*/
|
||||
protected _getFont(terminal: ITerminal, isBold: boolean): string {
|
||||
const fontWeight = isBold ? terminal.options.fontWeightBold : terminal.options.fontWeight;
|
||||
|
||||
return `${fontWeight} ${terminal.options.fontSize * window.devicePixelRatio}px ${terminal.options.fontFamily}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@ export const CHAR_ATLAS_CELL_SPACING = 1;
|
||||
interface ICharAtlasConfig {
|
||||
fontSize: number;
|
||||
fontFamily: string;
|
||||
fontWeight: string;
|
||||
fontWeightBold: string;
|
||||
scaledCharWidth: number;
|
||||
scaledCharHeight: number;
|
||||
colors: IColorSet;
|
||||
@@ -64,7 +66,7 @@ export function acquireCharAtlas(terminal: ITerminal, colors: IColorSet, scaledC
|
||||
}
|
||||
|
||||
const newEntry: ICharAtlasCacheEntry = {
|
||||
bitmap: generator.generate(scaledCharWidth, scaledCharHeight, terminal.options.fontSize, terminal.options.fontFamily, colors.background, colors.foreground, colors.ansi),
|
||||
bitmap: generator.generate(scaledCharWidth, scaledCharHeight, terminal.options.fontSize, terminal.options.fontFamily, terminal.options.fontWeight, terminal.options.fontWeightBold, colors.background, colors.foreground, colors.ansi),
|
||||
config: newConfig,
|
||||
ownedBy: [terminal]
|
||||
};
|
||||
@@ -86,6 +88,8 @@ function generateConfig(scaledCharWidth: number, scaledCharHeight: number, termi
|
||||
scaledCharHeight,
|
||||
fontFamily: terminal.options.fontFamily,
|
||||
fontSize: terminal.options.fontSize,
|
||||
fontWeight: terminal.options.fontWeight,
|
||||
fontWeightBold: terminal.options.fontWeightBold,
|
||||
colors: clonedColors
|
||||
};
|
||||
}
|
||||
@@ -98,6 +102,8 @@ function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean {
|
||||
}
|
||||
return a.fontFamily === b.fontFamily &&
|
||||
a.fontSize === b.fontSize &&
|
||||
a.fontWeight === b.fontWeight &&
|
||||
a.fontWeightBold === b.fontWeightBold &&
|
||||
a.scaledCharWidth === b.scaledCharWidth &&
|
||||
a.scaledCharHeight === b.scaledCharHeight &&
|
||||
a.colors.foreground === b.colors.foreground &&
|
||||
@@ -126,7 +132,7 @@ class CharAtlasGenerator {
|
||||
this._ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
|
||||
}
|
||||
|
||||
public generate(scaledCharWidth: number, scaledCharHeight: number, fontSize: number, fontFamily: string, background: string, foreground: string, ansiColors: string[]): HTMLCanvasElement | Promise<ImageBitmap> {
|
||||
public generate(scaledCharWidth: number, scaledCharHeight: number, fontSize: number, fontFamily: string, fontWeight: string, fontWeightBold: string, background: string, foreground: string, ansiColors: string[]): HTMLCanvasElement | Promise<ImageBitmap> {
|
||||
const cellWidth = scaledCharWidth + CHAR_ATLAS_CELL_SPACING;
|
||||
const cellHeight = scaledCharHeight + CHAR_ATLAS_CELL_SPACING;
|
||||
this._canvas.width = 255 * cellWidth;
|
||||
@@ -137,7 +143,7 @@ class CharAtlasGenerator {
|
||||
|
||||
this._ctx.save();
|
||||
this._ctx.fillStyle = foreground;
|
||||
this._ctx.font = `${fontSize * window.devicePixelRatio}px ${fontFamily}`;
|
||||
this._ctx.font = this._getFont(fontWeight, fontSize, fontFamily);
|
||||
this._ctx.textBaseline = 'top';
|
||||
|
||||
// Default color
|
||||
@@ -151,7 +157,7 @@ class CharAtlasGenerator {
|
||||
}
|
||||
// Default color bold
|
||||
this._ctx.save();
|
||||
this._ctx.font = `bold ${this._ctx.font}`;
|
||||
this._ctx.font = this._getFont(fontWeightBold, fontSize, fontFamily);
|
||||
for (let i = 0; i < 256; i++) {
|
||||
this._ctx.save();
|
||||
this._ctx.beginPath();
|
||||
@@ -163,11 +169,11 @@ class CharAtlasGenerator {
|
||||
this._ctx.restore();
|
||||
|
||||
// Colors 0-15
|
||||
this._ctx.font = `${fontSize * window.devicePixelRatio}px ${fontFamily}`;
|
||||
this._ctx.font = this._getFont(fontWeight, fontSize, fontFamily);
|
||||
for (let colorIndex = 0; colorIndex < 16; colorIndex++) {
|
||||
// colors 8-15 are bold
|
||||
if (colorIndex === 8) {
|
||||
this._ctx.font = `bold ${this._ctx.font}`;
|
||||
this._ctx.font = this._getFont(fontWeightBold, fontSize, fontFamily);
|
||||
}
|
||||
const y = (colorIndex + 2) * cellHeight;
|
||||
// Draw ascii characters
|
||||
@@ -219,4 +225,8 @@ class CharAtlasGenerator {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _getFont(fontWeight: string, fontSize: number, fontFamily: string): string {
|
||||
return `${fontWeight} ${fontSize * window.devicePixelRatio}px ${fontFamily}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import { FLAGS } from './Types';
|
||||
import { BaseRenderLayer } from './BaseRenderLayer';
|
||||
import { CharData } from '../Types';
|
||||
|
||||
interface CursorState {
|
||||
interface ICursorState {
|
||||
x: number;
|
||||
y: number;
|
||||
isFocused: boolean;
|
||||
@@ -25,7 +25,7 @@ interface CursorState {
|
||||
const BLINK_INTERVAL = 600;
|
||||
|
||||
export class CursorRenderLayer extends BaseRenderLayer {
|
||||
private _state: CursorState;
|
||||
private _state: ICursorState;
|
||||
private _cursorRenderers: {[key: string]: (terminal: ITerminal, x: number, y: number, charData: CharData) => void};
|
||||
private _cursorBlinkStateManager: CursorBlinkStateManager;
|
||||
private _isFocused: boolean;
|
||||
@@ -108,9 +108,10 @@ export class CursorRenderLayer extends BaseRenderLayer {
|
||||
}
|
||||
|
||||
public onGridChanged(terminal: ITerminal, startRow: number, endRow: number): void {
|
||||
// Only render if the animation frame is not active
|
||||
if (!this._cursorBlinkStateManager || this._cursorBlinkStateManager.isPaused) {
|
||||
this._render(terminal, false);
|
||||
} else {
|
||||
this._cursorBlinkStateManager.restartBlinkAnimation(terminal);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,20 +4,20 @@
|
||||
*/
|
||||
|
||||
import { IColorSet, IRenderDimensions } from './Interfaces';
|
||||
import { IBuffer, ICharMeasure, ITerminal, ILinkifierAccessor } from '../Interfaces';
|
||||
import { IBuffer, ICharMeasure, ILinkHoverEvent, ITerminal, ILinkifierAccessor } from '../Interfaces';
|
||||
import { CHAR_DATA_ATTR_INDEX } from '../Buffer';
|
||||
import { GridCache } from './GridCache';
|
||||
import { FLAGS } from './Types';
|
||||
import { BaseRenderLayer, INVERTED_DEFAULT_COLOR } from './BaseRenderLayer';
|
||||
import { LinkHoverEvent, LinkHoverEventTypes } from '../Types';
|
||||
import { LinkHoverEventTypes } from '../Types';
|
||||
|
||||
export class LinkRenderLayer extends BaseRenderLayer {
|
||||
private _state: LinkHoverEvent = null;
|
||||
private _state: ILinkHoverEvent = null;
|
||||
|
||||
constructor(container: HTMLElement, zIndex: number, colors: IColorSet, terminal: ILinkifierAccessor) {
|
||||
super(container, 'link', zIndex, true, colors);
|
||||
terminal.linkifier.on(LinkHoverEventTypes.HOVER, (e: LinkHoverEvent) => this._onLinkHover(e));
|
||||
terminal.linkifier.on(LinkHoverEventTypes.LEAVE, (e: LinkHoverEvent) => this._onLinkLeave(e));
|
||||
terminal.linkifier.on(LinkHoverEventTypes.HOVER, (e: ILinkHoverEvent) => this._onLinkHover(e));
|
||||
terminal.linkifier.on(LinkHoverEventTypes.LEAVE, (e: ILinkHoverEvent) => this._onLinkLeave(e));
|
||||
}
|
||||
|
||||
public resize(terminal: ITerminal, dim: IRenderDimensions, charSizeChanged: boolean): void {
|
||||
@@ -37,13 +37,13 @@ export class LinkRenderLayer extends BaseRenderLayer {
|
||||
}
|
||||
}
|
||||
|
||||
private _onLinkHover(e: LinkHoverEvent): void {
|
||||
private _onLinkHover(e: ILinkHoverEvent): void {
|
||||
this._ctx.fillStyle = this._colors.foreground;
|
||||
this.fillBottomLineAtCells(e.x, e.y, e.length);
|
||||
this._state = e;
|
||||
}
|
||||
|
||||
private _onLinkLeave(e: LinkHoverEvent): void {
|
||||
private _onLinkLeave(e: ILinkHoverEvent): void {
|
||||
this._clearCurrentLink();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,6 +57,7 @@ export class Renderer extends EventEmitter implements IRenderer {
|
||||
};
|
||||
this._devicePixelRatio = window.devicePixelRatio;
|
||||
this._updateDimensions();
|
||||
this.onOptionsChanged();
|
||||
|
||||
this._screenDprMonitor = new ScreenDprMonitor();
|
||||
this._screenDprMonitor.setListener(() => this.onWindowResize(window.devicePixelRatio));
|
||||
|
||||
@@ -33,7 +33,7 @@ export class TextRenderLayer extends BaseRenderLayer {
|
||||
super.resize(terminal, dim, charSizeChanged);
|
||||
|
||||
// Clear the character width cache if the font or width has changed
|
||||
const terminalFont = `${terminal.options.fontSize * window.devicePixelRatio}px ${terminal.options.fontFamily}`;
|
||||
const terminalFont = this._getFont(terminal, false);
|
||||
if (this._characterWidth !== dim.scaledCharWidth || this._characterFont !== terminalFont) {
|
||||
this._characterWidth = dim.scaledCharWidth;
|
||||
this._characterFont = terminalFont;
|
||||
@@ -166,7 +166,7 @@ export class TextRenderLayer extends BaseRenderLayer {
|
||||
|
||||
this._ctx.save();
|
||||
if (flags & FLAGS.BOLD) {
|
||||
this._ctx.font = `bold ${this._ctx.font}`;
|
||||
this._ctx.font = this._getFont(terminal, true);
|
||||
// Convert the FG color to the bold variant
|
||||
if (fg < 8) {
|
||||
fg += 8;
|
||||
@@ -192,9 +192,9 @@ export class TextRenderLayer extends BaseRenderLayer {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a character is overlapping to the next cell.
|
||||
*/
|
||||
/**
|
||||
* Whether a character is overlapping to the next cell.
|
||||
*/
|
||||
private _isOverlapping(charData: CharData): boolean {
|
||||
// Only single cell characters can be overlapping, rendering issues can
|
||||
// occur without this check
|
||||
|
||||
@@ -13,4 +13,4 @@ export enum FLAGS {
|
||||
INVERSE = 8,
|
||||
INVISIBLE = 16,
|
||||
DIM = 32
|
||||
};
|
||||
}
|
||||
|
||||
@@ -10,4 +10,4 @@
|
||||
*/
|
||||
export function contains(arr: any[], el: any): boolean {
|
||||
return arr.indexOf(el) >= 0;
|
||||
};
|
||||
}
|
||||
|
||||
+1
-1
@@ -7,4 +7,4 @@
|
||||
// This sound is released under the Creative Commons Attribution 3.0 Unported
|
||||
// (CC BY 3.0) license. It was created by 'altemark'. No modifications have been
|
||||
// made, apart from the conversion to base64.
|
||||
export const BellSound = 'data:audio/wav;base64,UklGRigBAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQQBAADpAFgCwAMlBZoG/wdmCcoKRAypDQ8PbRDBEQQTOxRtFYcWlBePGIUZXhoiG88bcBz7HHIdzh0WHlMeZx51HmkeUx4WHs8dah0AHXwc3hs9G4saxRnyGBIYGBcQFv8U4RPAEoYRQBACD70NWwwHC6gJOwjWBloF7gOBAhABkf8b/qv8R/ve+Xf4Ife79W/0JfPZ8Z/wde9N7ijtE+wU6xvqM+lb6H7nw+YX5mrlxuQz5Mzje+Ma49fioeKD4nXiYeJy4pHitOL04j/jn+MN5IPkFOWs5U3mDefM55/ogOl36m7rdOyE7abuyu8D8Unyj/Pg9D/2qfcb+Yn6/vuK/Qj/lAAlAg==';
|
||||
export const BELL_SOUND = 'data:audio/wav;base64,UklGRigBAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQQBAADpAFgCwAMlBZoG/wdmCcoKRAypDQ8PbRDBEQQTOxRtFYcWlBePGIUZXhoiG88bcBz7HHIdzh0WHlMeZx51HmkeUx4WHs8dah0AHXwc3hs9G4saxRnyGBIYGBcQFv8U4RPAEoYRQBACD70NWwwHC6gJOwjWBloF7gOBAhABkf8b/qv8R/ve+Xf4Ife79W/0JfPZ8Z/wde9N7ijtE+wU6xvqM+lb6H7nw+YX5mrlxuQz5Mzje+Ma49fioeKD4nXiYeJy4pHitOL04j/jn+MN5IPkFOWs5U3mDefM55/ogOl36m7rdOyE7abuyu8D8Unyj/Pg9D/2qfcb+Yn6/vuK/Qj/lAAlAg==';
|
||||
|
||||
Reference in New Issue
Block a user