Merge branch 'master' into 1117
This commit is contained in:
@@ -122,6 +122,9 @@ computational environment for Jupyter, supporting interactive data science and s
|
||||
- [**Kubebox**](https://github.com/astefanutti/kubebox): Terminal console for Kubernetes clusters.
|
||||
- [**Azure Cloud Shell**](https://shell.azure.com): Azure Cloud Shell is a Microsoft-managed admin machine built on Azure, for Azure.
|
||||
- [**atom-xterm**](https://atom.io/packages/atom-xterm): Atom plugin for providing terminals inside your Atom workspace.
|
||||
- [**rtty**](https://github.com/zhaojh329/rtty): A reverse proxy WebTTY. It is composed of the client and the server.
|
||||
- [**Pisth**](https://github.com/ColdGrub1384/Pisth): An SFTP and SSH client for iOS
|
||||
- [**abstruse**](https://github.com/bleenco/abstruse): Abstruse CI is a continuous integration platform based on Node.JS and Docker.
|
||||
|
||||
Do you use xterm.js in your application as well? Please [open a Pull Request](https://github.com/sourcelair/xterm.js/pulls) to include it here. We would love to have it in our list.
|
||||
|
||||
|
||||
@@ -23,6 +23,9 @@
|
||||
<p>
|
||||
<label><input type="checkbox" id="option-cursor-blink"> cursorBlink</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><input type="checkbox" id="option-mac-option-is-meta"> macOptionIsMeta</label>
|
||||
</p>
|
||||
<p>
|
||||
<label>
|
||||
cursorStyle
|
||||
|
||||
@@ -27,6 +27,7 @@ var terminalContainer = document.getElementById('terminal-container'),
|
||||
optionElements = {
|
||||
cursorBlink: document.querySelector('#option-cursor-blink'),
|
||||
cursorStyle: document.querySelector('#option-cursor-style'),
|
||||
macOptionIsMeta: document.querySelector('#option-mac-option-is-meta'),
|
||||
scrollback: document.querySelector('#option-scrollback'),
|
||||
tabstopwidth: document.querySelector('#option-tabstopwidth'),
|
||||
bellStyle: document.querySelector('#option-bell-style')
|
||||
@@ -72,6 +73,9 @@ optionElements.cursorStyle.addEventListener('change', function () {
|
||||
optionElements.bellStyle.addEventListener('change', function () {
|
||||
term.setOption('bellStyle', optionElements.bellStyle.value);
|
||||
});
|
||||
optionElements.macOptionIsMeta.addEventListener('change', function () {
|
||||
term.setOption('macOptionIsMeta', optionElements.macOptionIsMeta.checked);
|
||||
});
|
||||
optionElements.scrollback.addEventListener('change', function () {
|
||||
term.setOption('scrollback', parseInt(optionElements.scrollback.value, 10));
|
||||
});
|
||||
@@ -87,6 +91,7 @@ function createTerminal() {
|
||||
terminalContainer.removeChild(terminalContainer.children[0]);
|
||||
}
|
||||
term = new Terminal({
|
||||
macOptionIsMeta: optionElements.macOptionIsMeta.enabled,
|
||||
cursorBlink: optionElements.cursorBlink.checked,
|
||||
scrollback: parseInt(optionElements.scrollback.value, 10),
|
||||
tabStopWidth: parseInt(optionElements.tabstopwidth.value, 10)
|
||||
|
||||
@@ -142,9 +142,11 @@ namespace methods_core {
|
||||
const r18: (data: string) => void = t.getOption('handler');
|
||||
const r19: string = t.getOption('bellSound');
|
||||
const r20: string = t.getOption('bellStyle');
|
||||
const r21: number = t.getOption('letterSpacing');
|
||||
const r22: number = t.getOption('fontWeight');
|
||||
const r23: number = t.getOption('fontWeightBold');
|
||||
const r21: boolean = t.getOption('enableBold');
|
||||
const r22: number = t.getOption('letterSpacing');
|
||||
const r23: boolean = t.getOption('macOptionIsMeta');
|
||||
const r24: number = t.getOption('fontWeight');
|
||||
const r25: number = t.getOption('fontWeightBold');
|
||||
}
|
||||
{
|
||||
const t: Terminal = new Terminal();
|
||||
@@ -179,6 +181,7 @@ namespace methods_core {
|
||||
t.setOption('lineHeight', 1);
|
||||
t.setOption('fontFamily', 'foo');
|
||||
t.setOption('theme', {background: '#ff0000'});
|
||||
t.setOption('macOptionIsMeta', true);
|
||||
}
|
||||
}
|
||||
namespace scrolling {
|
||||
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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();
|
||||
|
||||
+2
-2
@@ -144,6 +144,7 @@ export interface ITerminalOptions {
|
||||
handler?: (data: string) => void;
|
||||
letterSpacing?: number;
|
||||
lineHeight?: number;
|
||||
macOptionIsMeta?: boolean;
|
||||
rows?: number;
|
||||
screenKeys?: boolean;
|
||||
scrollback?: number;
|
||||
@@ -170,7 +171,7 @@ export interface IBuffer {
|
||||
prevStop(x?: number): number;
|
||||
}
|
||||
|
||||
export interface IBufferSet {
|
||||
export interface IBufferSet extends IEventEmitter {
|
||||
alt: IBuffer;
|
||||
normal: IBuffer;
|
||||
active: IBuffer;
|
||||
@@ -199,7 +200,6 @@ export interface ISelectionManager {
|
||||
|
||||
disable(): void;
|
||||
enable(): void;
|
||||
setBuffer(buffer: IBuffer): void;
|
||||
setSelection(row: number, col: number, length: number): void;
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
+28
-28
@@ -81,6 +81,7 @@ const DEFAULT_OPTIONS: ITerminalOptions = {
|
||||
scrollback: 1000,
|
||||
screenKeys: false,
|
||||
debug: false,
|
||||
macOptionIsMeta: false,
|
||||
cancelEvents: false,
|
||||
disableStdin: false,
|
||||
useFlowControl: false,
|
||||
@@ -193,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;
|
||||
@@ -294,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.
|
||||
*/
|
||||
@@ -653,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 => {
|
||||
@@ -1386,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;
|
||||
}
|
||||
|
||||
@@ -1407,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.
|
||||
@@ -1711,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) {
|
||||
@@ -1778,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;
|
||||
}
|
||||
@@ -2092,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();
|
||||
}
|
||||
@@ -2172,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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -12,7 +12,7 @@ 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');
|
||||
});
|
||||
|
||||
+45
-44
@@ -5,38 +5,42 @@
|
||||
* 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: any, socket: WebSocket, bidirectional: boolean, buffered: boolean): void {
|
||||
export function attach(term: Terminal, socket: WebSocket, bidirectional: boolean, buffered: boolean): void {
|
||||
const addonTerminal = <IAttachAddonTerminal>term;
|
||||
bidirectional = (typeof bidirectional === 'undefined') ? true : bidirectional;
|
||||
term.socket = socket;
|
||||
addonTerminal.__socket = socket;
|
||||
|
||||
term._flushBuffer = () => {
|
||||
term.write(term._attachSocketBuffer);
|
||||
term._attachSocketBuffer = null;
|
||||
addonTerminal.__flushBuffer = () => {
|
||||
addonTerminal.write(addonTerminal.__attachSocketBuffer);
|
||||
addonTerminal.__attachSocketBuffer = null;
|
||||
};
|
||||
|
||||
term._pushToBuffer = (data: string) => {
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
let myTextDecoder;
|
||||
|
||||
term._getMessage = function(ev: MessageEvent): void {
|
||||
addonTerminal.__getMessage = function(ev: MessageEvent): void {
|
||||
let str;
|
||||
if (typeof ev.data === 'object') {
|
||||
if (ev.data instanceof ArrayBuffer) {
|
||||
@@ -51,71 +55,68 @@ export function attach(term: any, socket: WebSocket, bidirectional: boolean, buf
|
||||
}
|
||||
|
||||
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 = (data: string) => {
|
||||
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: any, socket: WebSocket): void {
|
||||
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: any): void {
|
||||
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: WebSocket, bidirectional: boolean, buffered: boolean): void {
|
||||
(<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: WebSocket): void {
|
||||
(<any>terminalConstructor.prototype).detach = function (socket: WebSocket): void {
|
||||
detach(this, socket);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ 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');
|
||||
});
|
||||
|
||||
+12
-8
@@ -13,12 +13,16 @@
|
||||
* row and truncate its width with the current number of columns).
|
||||
*/
|
||||
|
||||
/// <reference path="../../../typings/xterm.d.ts"/>
|
||||
|
||||
import { Terminal } from 'xterm';
|
||||
|
||||
export interface IGeometry {
|
||||
rows: number;
|
||||
cols: number;
|
||||
}
|
||||
|
||||
export function proposeGeometry(term: any): IGeometry {
|
||||
export function proposeGeometry(term: Terminal): IGeometry {
|
||||
if (!term.element.parentElement) {
|
||||
return null;
|
||||
}
|
||||
@@ -31,30 +35,30 @@ export function proposeGeometry(term: any): IGeometry {
|
||||
const availableHeight = parentElementHeight - elementPaddingVer;
|
||||
const availableWidth = parentElementWidth - elementPaddingHor;
|
||||
const geometry = {
|
||||
cols: Math.floor(availableWidth / term.renderer.dimensions.actualCellWidth),
|
||||
rows: Math.floor(availableHeight / term.renderer.dimensions.actualCellHeight)
|
||||
cols: Math.floor(availableWidth / (<any>term).renderer.dimensions.actualCellWidth),
|
||||
rows: Math.floor(availableHeight / (<any>term).renderer.dimensions.actualCellHeight)
|
||||
};
|
||||
|
||||
return geometry;
|
||||
}
|
||||
|
||||
export function fit(term: any): void {
|
||||
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: any): void {
|
||||
terminalConstructor.prototype.proposeGeometry = function (): IGeometry {
|
||||
export function apply(terminalConstructor: typeof Terminal): void {
|
||||
(<any>terminalConstructor.prototype).proposeGeometry = function (): IGeometry {
|
||||
return proposeGeometry(this);
|
||||
};
|
||||
|
||||
terminalConstructor.prototype.fit = function (): void {
|
||||
(<any>terminalConstructor.prototype).fit = function (): void {
|
||||
fit(this);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ 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,13 +3,17 @@
|
||||
* @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: any, fullscreen: boolean): void {
|
||||
let fn;
|
||||
export function toggleFullScreen(term: Terminal, fullscreen: boolean): void {
|
||||
let fn: string;
|
||||
|
||||
if (typeof fullscreen === 'undefined') {
|
||||
fn = (term.element.classList.contains('fullscreen')) ? 'remove' : 'add';
|
||||
@@ -22,8 +26,8 @@ export function toggleFullScreen(term: any, fullscreen: boolean): void {
|
||||
term.element.classList[fn]('fullscreen');
|
||||
}
|
||||
|
||||
export function apply(terminalConstructor: any): void {
|
||||
terminalConstructor.prototype.toggleFullScreen = function (fullscreen: boolean): void {
|
||||
export function apply(terminalConstructor: typeof Terminal): void {
|
||||
(<any>terminalConstructor.prototype).toggleFullScreen = function (fullscreen: boolean): void {
|
||||
toggleFullScreen(this, fullscreen);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -134,7 +133,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;
|
||||
}
|
||||
}
|
||||
|
||||
+17
-12
@@ -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,11 +15,12 @@ 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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -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);
|
||||
export function findPrevious(terminal: Terminal, term: string): boolean {
|
||||
const addonTerminal = <ISearchAddonTerminal>terminal;
|
||||
if (!addonTerminal.__searchHelper) {
|
||||
addonTerminal.__searchHelper = new SearchHelper(addonTerminal);
|
||||
}
|
||||
return (<SearchHelper>terminal.searchHelper).findPrevious(term);
|
||||
return addonTerminal.__searchHelper.findPrevious(term);
|
||||
}
|
||||
|
||||
export function apply(terminalConstructor: any): void {
|
||||
terminalConstructor.prototype.findNext = function(term: any): boolean {
|
||||
export function apply(terminalConstructor: typeof Terminal): void {
|
||||
(<any>terminalConstructor.prototype).findNext = function(term: string): boolean {
|
||||
return findNext(this, term);
|
||||
};
|
||||
|
||||
terminalConstructor.prototype.findPrevious = function(term: any): boolean {
|
||||
(<any>terminalConstructor.prototype).findPrevious = function(term: string): boolean {
|
||||
return findPrevious(this, term);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -12,7 +12,7 @@ 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: any, socket: WebSocket, bidirectional: boolean, buffered: boolean): void {
|
||||
export function terminadoAttach(term: Terminal, socket: WebSocket, bidirectional: boolean, buffered: boolean): void {
|
||||
const addonTerminal = <ITerminadoAddonTerminal>term;
|
||||
bidirectional = (typeof bidirectional === 'undefined') ? true : bidirectional;
|
||||
term.socket = socket;
|
||||
addonTerminal.__socket = socket;
|
||||
|
||||
term._flushBuffer = () => {
|
||||
term.write(term._attachSocketBuffer);
|
||||
term._attachSocketBuffer = null;
|
||||
addonTerminal.__flushBuffer = () => {
|
||||
addonTerminal.write(addonTerminal.__attachSocketBuffer);
|
||||
addonTerminal.__attachSocketBuffer = null;
|
||||
};
|
||||
|
||||
term._pushToBuffer = (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 = (ev: MessageEvent) => {
|
||||
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 = (data: string) => {
|
||||
addonTerminal.__sendData = (data: string) => {
|
||||
socket.send(JSON.stringify(['stdin', data]));
|
||||
};
|
||||
|
||||
term._setSize = (size: {rows: number, cols: number}) => {
|
||||
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: any, socket: WebSocket): void {
|
||||
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: any): void {
|
||||
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: WebSocket, bidirectional: boolean, buffered: boolean): void {
|
||||
(<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: WebSocket): void {
|
||||
(<any>terminalConstructor.prototype).terminadoDetach = function (socket: WebSocket): void {
|
||||
return terminadoDetach(this, socket);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -12,7 +12,7 @@ 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: any): 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: any): void {
|
||||
terminalConstructor.prototype.winptyCompatInit = function(): void {
|
||||
export function apply(terminalConstructor: typeof Terminal): void {
|
||||
(<any>terminalConstructor.prototype).winptyCompatInit = function (): void {
|
||||
winptyCompatInit(this);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ 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');
|
||||
});
|
||||
|
||||
@@ -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.
|
||||
@@ -33,7 +42,7 @@ export interface IZModemOptions {
|
||||
noTerminalWriteOutsideSession?: boolean;
|
||||
}
|
||||
|
||||
export function zmodemAttach(term: any, ws: WebSocket, opts: IZModemOptions = {}): void {
|
||||
export function zmodemAttach(term: Terminal, ws: WebSocket, opts: IZModemOptions = {}): void {
|
||||
const senderFunc = (octets: ArrayLike<number>) => ws.send(new Uint8Array(octets));
|
||||
|
||||
let zsentry;
|
||||
@@ -51,8 +60,8 @@ export function zmodemAttach(term: any, ws: WebSocket, opts: IZModemOptions = {}
|
||||
}
|
||||
},
|
||||
sender: senderFunc,
|
||||
on_retract: () => term.emit('zmodemRetract'),
|
||||
on_detect: (detection: any) => term.emit('zmodemDetect', detection)
|
||||
on_retract: () => (<any>term).emit('zmodemRetract'),
|
||||
on_detect: (detection: any) => (<any>term).emit('zmodemDetect', detection)
|
||||
});
|
||||
|
||||
function handleWSMessage(evt: MessageEvent): void {
|
||||
@@ -75,9 +84,9 @@ export function zmodemAttach(term: any, ws: WebSocket, opts: IZModemOptions = {}
|
||||
ws.addEventListener('message', handleWSMessage);
|
||||
}
|
||||
|
||||
export function apply(terminalConstructor: any): void {
|
||||
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;
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -460,7 +460,7 @@ declare module 'xterm' {
|
||||
* @param key The option key.
|
||||
* @param value The option value.
|
||||
*/
|
||||
setOption(key: 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'debug' | 'disableStdin' | 'enableBold' | 'popOnBell' | 'screenKeys' | 'useFlowControl' | 'visualBell', value: boolean): void;
|
||||
setOption(key: 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'debug' | 'disableStdin' | 'enableBold' | 'macOptionIsMeta' | 'popOnBell' | 'screenKeys' | 'useFlowControl' | 'visualBell', value: boolean): void;
|
||||
/**
|
||||
* Sets an option on the terminal.
|
||||
* @param key The option key.
|
||||
|
||||
Reference in New Issue
Block a user