Merge remote-tracking branch 'origin/master' into 24_multi_line_links

This commit is contained in:
Daniel Imms
2018-03-08 10:23:05 -08:00
9 changed files with 323 additions and 261 deletions
+61 -61
View File
@@ -20,43 +20,43 @@ export class CompositionHelper {
* Whether input composition is currently happening, eg. via a mobile keyboard, speech input or
* IME. This variable determines whether the compositionText should be displayed on the UI.
*/
private isComposing: boolean;
private _isComposing: boolean;
/**
* The position within the input textarea's value of the current composition.
*/
private compositionPosition: IPosition;
private _compositionPosition: IPosition;
/**
* Whether a composition is in the process of being sent, setting this to false will cancel any
* in-progress composition.
*/
private isSendingComposition: boolean;
private _isSendingComposition: boolean;
/**
* Creates a new CompositionHelper.
* @param textarea The textarea that xterm uses for input.
* @param compositionView The element to display the in-progress composition in.
* @param terminal The Terminal to forward the finished composition to.
* @param _textarea The textarea that xterm uses for input.
* @param _compositionView The element to display the in-progress composition in.
* @param _terminal The Terminal to forward the finished composition to.
*/
constructor(
private textarea: HTMLTextAreaElement,
private compositionView: HTMLElement,
private terminal: ITerminal
private _textarea: HTMLTextAreaElement,
private _compositionView: HTMLElement,
private _terminal: ITerminal
) {
this.isComposing = false;
this.isSendingComposition = false;
this.compositionPosition = { start: null, end: null };
this._isComposing = false;
this._isSendingComposition = false;
this._compositionPosition = { start: null, end: null };
}
/**
* Handles the compositionstart event, activating the composition view.
*/
public compositionstart(): void {
this.isComposing = true;
this.compositionPosition.start = this.textarea.value.length;
this.compositionView.textContent = '';
this.compositionView.classList.add('active');
this._isComposing = true;
this._compositionPosition.start = this._textarea.value.length;
this._compositionView.textContent = '';
this._compositionView.classList.add('active');
}
/**
@@ -64,10 +64,10 @@ export class CompositionHelper {
* @param {CompositionEvent} ev The event.
*/
public compositionupdate(ev: CompositionEvent): void {
this.compositionView.textContent = ev.data;
this._compositionView.textContent = ev.data;
this.updateCompositionElements();
setTimeout(() => {
this.compositionPosition.end = this.textarea.value.length;
this._compositionPosition.end = this._textarea.value.length;
}, 0);
}
@@ -76,7 +76,7 @@ export class CompositionHelper {
* the handler.
*/
public compositionend(): void {
this.finalizeComposition(true);
this._finalizeComposition(true);
}
/**
@@ -85,7 +85,7 @@ export class CompositionHelper {
* @return Whether the Terminal should continue processing the keydown event.
*/
public keydown(ev: KeyboardEvent): boolean {
if (this.isComposing || this.isSendingComposition) {
if (this._isComposing || this._isSendingComposition) {
if (ev.keyCode === 229) {
// Continue composing if the keyCode is the "composition character"
return false;
@@ -95,14 +95,14 @@ export class CompositionHelper {
} else {
// Finish composition immediately. This is mainly here for the case where enter is
// pressed and the handler needs to be triggered before the command is executed.
this.finalizeComposition(false);
this._finalizeComposition(false);
}
}
if (ev.keyCode === 229) {
// If the "composition character" is used but gets to this point it means a non-composition
// character (eg. numbers and punctuation) was pressed when the IME was active.
this.handleAnyTextareaChanges();
this._handleAnyTextareaChanges();
return false;
}
@@ -117,22 +117,22 @@ export class CompositionHelper {
* compositionend event is triggered, such as enter, so that the composition is send before
* the command is executed.
*/
private finalizeComposition(waitForPropogation: boolean): void {
this.compositionView.classList.remove('active');
this.isComposing = false;
this.clearTextareaPosition();
private _finalizeComposition(waitForPropogation: boolean): void {
this._compositionView.classList.remove('active');
this._isComposing = false;
this._clearTextareaPosition();
if (!waitForPropogation) {
// Cancel any delayed composition send requests and send the input immediately.
this.isSendingComposition = false;
const input = this.textarea.value.substring(this.compositionPosition.start, this.compositionPosition.end);
this.terminal.handler(input);
this._isSendingComposition = false;
const input = this._textarea.value.substring(this._compositionPosition.start, this._compositionPosition.end);
this._terminal.handler(input);
} else {
// Make a deep copy of the composition position here as a new compositionstart event may
// fire before the setTimeout executes.
const currentCompositionPosition = {
start: this.compositionPosition.start,
end: this.compositionPosition.end,
start: this._compositionPosition.start,
end: this._compositionPosition.end,
};
// Since composition* events happen before the changes take place in the textarea on most
@@ -143,22 +143,22 @@ export class CompositionHelper {
// - The last compositionupdate event's data property does not always accurately describe
// the character, a counter example being Korean where an ending consonsant can move to
// the following character if the following input is a vowel.
this.isSendingComposition = true;
this._isSendingComposition = true;
setTimeout(() => {
// Ensure that the input has not already been sent
if (this.isSendingComposition) {
this.isSendingComposition = false;
if (this._isSendingComposition) {
this._isSendingComposition = false;
let input;
if (this.isComposing) {
if (this._isComposing) {
// Use the end position to get the string if a new composition has started.
input = this.textarea.value.substring(currentCompositionPosition.start, currentCompositionPosition.end);
input = this._textarea.value.substring(currentCompositionPosition.start, currentCompositionPosition.end);
} else {
// Don't use the end position here in order to pick up any characters after the
// composition has finished, for example when typing a non-composition character
// (eg. 2) after a composition character.
input = this.textarea.value.substring(currentCompositionPosition.start);
input = this._textarea.value.substring(currentCompositionPosition.start);
}
this.terminal.handler(input);
this._terminal.handler(input);
}
}, 0);
}
@@ -170,15 +170,15 @@ export class CompositionHelper {
* character" (229) is triggered, in order to allow non-composition text to be entered when an
* IME is active.
*/
private handleAnyTextareaChanges(): void {
const oldValue = this.textarea.value;
private _handleAnyTextareaChanges(): void {
const oldValue = this._textarea.value;
setTimeout(() => {
// Ignore if a composition has started since the timeout
if (!this.isComposing) {
const newValue = this.textarea.value;
if (!this._isComposing) {
const newValue = this._textarea.value;
const diff = newValue.replace(oldValue, '');
if (diff.length > 0) {
this.terminal.handler(diff);
this._terminal.handler(diff);
}
}
}, 0);
@@ -191,27 +191,27 @@ export class CompositionHelper {
* necessary as the IME events across browsers are not consistently triggered.
*/
public updateCompositionElements(dontRecurse?: boolean): void {
if (!this.isComposing) {
if (!this._isComposing) {
return;
}
if (this.terminal.buffer.isCursorInViewport) {
const cellHeight = Math.ceil(this.terminal.charMeasure.height * this.terminal.options.lineHeight);
const cursorTop = this.terminal.buffer.y * cellHeight;
const cursorLeft = this.terminal.buffer.x * this.terminal.charMeasure.width;
if (this._terminal.buffer.isCursorInViewport) {
const cellHeight = Math.ceil(this._terminal.charMeasure.height * this._terminal.options.lineHeight);
const cursorTop = this._terminal.buffer.y * cellHeight;
const cursorLeft = this._terminal.buffer.x * this._terminal.charMeasure.width;
this.compositionView.style.left = cursorLeft + 'px';
this.compositionView.style.top = cursorTop + 'px';
this.compositionView.style.height = cellHeight + 'px';
this.compositionView.style.lineHeight = cellHeight + 'px';
this._compositionView.style.left = cursorLeft + 'px';
this._compositionView.style.top = cursorTop + 'px';
this._compositionView.style.height = cellHeight + 'px';
this._compositionView.style.lineHeight = cellHeight + 'px';
// Sync the textarea to the exact position of the composition view so the IME knows where the
// text is.
const compositionViewBounds = this.compositionView.getBoundingClientRect();
this.textarea.style.left = cursorLeft + 'px';
this.textarea.style.top = cursorTop + 'px';
this.textarea.style.width = compositionViewBounds.width + 'px';
this.textarea.style.height = compositionViewBounds.height + 'px';
this.textarea.style.lineHeight = compositionViewBounds.height + 'px';
const compositionViewBounds = this._compositionView.getBoundingClientRect();
this._textarea.style.left = cursorLeft + 'px';
this._textarea.style.top = cursorTop + 'px';
this._textarea.style.width = compositionViewBounds.width + 'px';
this._textarea.style.height = compositionViewBounds.height + 'px';
this._textarea.style.lineHeight = compositionViewBounds.height + 'px';
}
if (!dontRecurse) {
@@ -223,8 +223,8 @@ export class CompositionHelper {
* Clears the textarea's position so that the cursor does not blink on IE.
* @private
*/
private clearTextareaPosition(): void {
this.textarea.style.left = '';
this.textarea.style.top = '';
private _clearTextareaPosition(): void {
this._textarea.style.left = '';
this._textarea.style.top = '';
}
}
+3 -3
View File
@@ -28,7 +28,7 @@ export class SoundManager implements ISoundManager {
if (this._audioContext) {
const bellAudioSource = this._audioContext.createBufferSource();
const context = this._audioContext;
this._audioContext.decodeAudioData(this.base64ToArrayBuffer(this.removeMimeType(this._terminal.options.bellSound)), (buffer) => {
this._audioContext.decodeAudioData(this._base64ToArrayBuffer(this._removeMimeType(this._terminal.options.bellSound)), (buffer) => {
bellAudioSource.buffer = buffer;
bellAudioSource.connect(context.destination);
bellAudioSource.start(0);
@@ -38,7 +38,7 @@ export class SoundManager implements ISoundManager {
}
}
private base64ToArrayBuffer(base64: string): ArrayBuffer {
private _base64ToArrayBuffer(base64: string): ArrayBuffer {
const binaryString = window.atob(base64);
const len = binaryString.length;
const bytes = new Uint8Array(len);
@@ -50,7 +50,7 @@ export class SoundManager implements ISoundManager {
return bytes.buffer;
}
private removeMimeType(dataURI: string): string {
private _removeMimeType(dataURI: string): string {
// Split the input to get the mime-type and the data itself
const splitUri = dataURI.split(',');
+1 -1
View File
@@ -125,7 +125,7 @@ if (os.platform() !== 'win32') {
// Perform a synchronous .write(data)
xterm.writeBuffer.push(fromPty);
xterm.innerWrite();
xterm._innerWrite();
let fromEmulator = terminalToString(xterm);
console.log = CONSOLE_LOG;
+2 -2
View File
@@ -29,11 +29,11 @@ describe('term.js addons', () => {
term.refresh = () => {};
(<any>term).renderer = new MockRenderer();
term.viewport = new MockViewport();
(<any>term).compositionHelper = new MockCompositionHelper();
(<any>term)._compositionHelper = new MockCompositionHelper();
// Force synchronous writes
term.write = (data) => {
term.writeBuffer.push(data);
(<any>term).innerWrite();
(<any>term)._innerWrite();
};
(<any>term).element = {
classList: {
+157 -137
View File
@@ -133,30 +133,30 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
/**
* The HTMLElement that the terminal is created in, set by Terminal.open.
*/
private parent: HTMLElement;
private context: Window;
private document: Document;
private body: HTMLBodyElement;
private viewportScrollArea: HTMLElement;
private viewportElement: HTMLElement;
private helperContainer: HTMLElement;
private compositionView: HTMLElement;
private charSizeStyleElement: HTMLStyleElement;
private _parent: HTMLElement;
private _context: Window;
private _document: Document;
private _body: HTMLBodyElement;
private _viewportScrollArea: HTMLElement;
private _viewportElement: HTMLElement;
private _helperContainer: HTMLElement;
private _compositionView: HTMLElement;
private _charSizeStyleElement: HTMLStyleElement;
private visualBellTimer: number;
private _visualBellTimer: number;
public browser: IBrowser = <any>Browser;
public options: ITerminalOptions;
private colors: any;
private _colors: any;
// TODO: This can be changed to an enum or boolean, 0 and 1 seem to be the only options
public cursorState: number;
public cursorHidden: boolean;
public convertEol: boolean;
private sendDataQueue: string;
private customKeyEventHandler: CustomKeyEventHandler;
private _sendDataQueue: string;
private _customKeyEventHandler: CustomKeyEventHandler;
// modes
public applicationKeypad: boolean;
@@ -174,10 +174,10 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
public charsets: ICharset[];
// mouse properties
private decLocator: boolean; // This is unstable and never set
private _decLocator: boolean; // This is unstable and never set
public x10Mouse: boolean;
public vt200Mouse: boolean;
private vt300Mouse: boolean; // This is unstable and never set
private _vt300Mouse: boolean; // This is unstable and never set
public normalMouse: boolean;
public mouseEvents: boolean;
public sendFocus: boolean;
@@ -186,13 +186,13 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
public urxvtMouse: boolean;
// misc
private refreshStart: number;
private refreshEnd: number;
private _refreshStart: number;
private _refreshEnd: number;
public savedCols: number;
// stream
private readable: boolean;
private writable: boolean;
private _readable: boolean;
private _writable: boolean;
public defAttr: number;
public curAttr: number;
@@ -204,7 +204,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
// user input states
public writeBuffer: string[];
private writeInProgress: boolean;
private _writeInProgress: boolean;
/**
* Whether _xterm.js_ sent XOFF in order to catch up with the pty process.
@@ -212,26 +212,26 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
* XOFF via ^S that it will not automatically resume when the writeBuffer goes
* below threshold.
*/
private xoffSentToCatchUp: boolean;
private _xoffSentToCatchUp: boolean;
/** Whether writing has been stopped as a result of XOFF */
private writeStopped: boolean;
private _writeStopped: boolean;
// leftover surrogate high from previous write invocation
private surrogateHigh: string;
private _surrogateHigh: string;
// Store if user went browsing history in scrollback
private userScrolling: boolean;
private _userScrolling: boolean;
private inputHandler: InputHandler;
private _inputHandler: InputHandler;
public soundManager: SoundManager;
private parser: Parser;
private _parser: Parser;
public renderer: IRenderer;
public selectionManager: SelectionManager;
public linkifier: ILinkifier;
public buffers: BufferSet;
public viewport: IViewport;
private compositionHelper: ICompositionHelper;
private _compositionHelper: ICompositionHelper;
public charMeasure: CharMeasure;
private _mouseZoneManager: IMouseZoneManager;
public mouseHelper: MouseHelper;
@@ -258,10 +258,10 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
) {
super();
this.options = options;
this.setup();
this._setup();
}
private setup(): void {
private _setup(): void {
Object.keys(DEFAULT_OPTIONS).forEach((key) => {
if (this.options[key] == null) {
this.options[key] = DEFAULT_OPTIONS[key];
@@ -273,7 +273,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
// this.context = options.context || window;
// this.document = options.document || document;
// TODO: WHy not document.body?
this.parent = document ? document.body : null;
this._parent = document ? document.body : null;
this.cols = this.options.cols;
this.rows = this.options.rows;
@@ -284,8 +284,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
this.cursorState = 0;
this.cursorHidden = false;
this.sendDataQueue = '';
this.customKeyEventHandler = null;
this._sendDataQueue = '';
this._customKeyEventHandler = null;
// modes
this.applicationKeypad = false;
@@ -302,8 +302,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
// TODO: Can this be just []?
this.charsets = [null];
this.readable = true;
this.writable = true;
this._readable = true;
this._writable = true;
this.defAttr = (0 << 18) | (257 << 9) | (256 << 0);
this.curAttr = (0 << 18) | (257 << 9) | (256 << 0);
@@ -315,15 +315,15 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
// user input states
this.writeBuffer = [];
this.writeInProgress = false;
this._writeInProgress = false;
this.xoffSentToCatchUp = false;
this.writeStopped = false;
this.surrogateHigh = '';
this.userScrolling = false;
this._xoffSentToCatchUp = false;
this._writeStopped = false;
this._surrogateHigh = '';
this._userScrolling = false;
this.inputHandler = new InputHandler(this);
this.parser = new Parser(this.inputHandler, this);
this._inputHandler = new InputHandler(this);
this._parser = new Parser(this._inputHandler, this);
// Reuse renderer if the Terminal is being recreated via a reset call.
this.renderer = this.renderer || null;
this.selectionManager = this.selectionManager || null;
@@ -538,8 +538,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
/**
* Initialize default behavior
*/
private initGlobal(): void {
this.bindKeys();
private _initGlobal(): void {
this._bindKeys();
// Bind clipboard functionality
on(this.element, 'copy', (event: ClipboardEvent) => {
@@ -585,7 +585,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
/**
* Apply key handling to the terminal
*/
private bindKeys(): void {
private _bindKeys(): void {
const self = this;
on(this.element, 'keydown', function (ev: KeyboardEvent): void {
if (document.activeElement !== this) {
@@ -609,11 +609,11 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
on(this.textarea, 'keydown', (ev: KeyboardEvent) => this._keyDown(ev), true);
on(this.textarea, 'keypress', (ev: KeyboardEvent) => this._keyPress(ev), true);
on(this.textarea, 'compositionstart', () => this.compositionHelper.compositionstart());
on(this.textarea, 'compositionupdate', (e: CompositionEvent) => this.compositionHelper.compositionupdate(e));
on(this.textarea, 'compositionend', () => this.compositionHelper.compositionend());
this.on('refresh', () => this.compositionHelper.updateCompositionElements());
this.on('refresh', (data) => this.queueLinkification(data.start, data.end));
on(this.textarea, 'compositionstart', () => this._compositionHelper.compositionstart());
on(this.textarea, 'compositionupdate', (e: CompositionEvent) => this._compositionHelper.compositionupdate(e));
on(this.textarea, 'compositionend', () => this._compositionHelper.compositionend());
this.on('refresh', () => this._compositionHelper.updateCompositionElements());
this.on('refresh', (data) => this._queueLinkification(data.start, data.end));
}
/**
@@ -625,44 +625,44 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
let i = 0;
let div;
this.parent = parent || this.parent;
this._parent = parent || this._parent;
if (!this.parent) {
if (!this._parent) {
throw new Error('Terminal requires a parent element.');
}
// Grab global elements
this.context = this.parent.ownerDocument.defaultView;
this.document = this.parent.ownerDocument;
this.body = <HTMLBodyElement>this.document.body;
this._context = this._parent.ownerDocument.defaultView;
this._document = this._parent.ownerDocument;
this._body = <HTMLBodyElement>this._document.body;
this._screenDprMonitor = new ScreenDprMonitor();
this._screenDprMonitor.setListener(() => this.emit('dprchange', window.devicePixelRatio));
// Create main element container
this.element = this.document.createElement('div');
this.element = this._document.createElement('div');
this.element.classList.add('terminal');
this.element.classList.add('xterm');
this.element.setAttribute('tabindex', '0');
this.parent.appendChild(this.element);
this._parent.appendChild(this.element);
// Performance: Use a document fragment to build the terminal
// viewport and helper elements detached from the DOM
const fragment = document.createDocumentFragment();
this.viewportElement = document.createElement('div');
this.viewportElement.classList.add('xterm-viewport');
fragment.appendChild(this.viewportElement);
this.viewportScrollArea = document.createElement('div');
this.viewportScrollArea.classList.add('xterm-scroll-area');
this.viewportElement.appendChild(this.viewportScrollArea);
this._viewportElement = document.createElement('div');
this._viewportElement.classList.add('xterm-viewport');
fragment.appendChild(this._viewportElement);
this._viewportScrollArea = document.createElement('div');
this._viewportScrollArea.classList.add('xterm-scroll-area');
this._viewportElement.appendChild(this._viewportScrollArea);
this.screenElement = document.createElement('div');
this.screenElement.classList.add('xterm-screen');
// Create the container that will hold helpers like the textarea for
// capturing DOM Events. Then produce the helpers.
this.helperContainer = document.createElement('div');
this.helperContainer.classList.add('xterm-helpers');
this.screenElement.appendChild(this.helperContainer);
this._helperContainer = document.createElement('div');
this._helperContainer.classList.add('xterm-helpers');
this.screenElement.appendChild(this._helperContainer);
fragment.appendChild(this.screenElement);
this._mouseZoneManager = new MouseZoneManager(this);
@@ -680,23 +680,23 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
this.textarea.tabIndex = 0;
this.textarea.addEventListener('focus', () => this._onTextAreaFocus());
this.textarea.addEventListener('blur', () => this._onTextAreaBlur());
this.helperContainer.appendChild(this.textarea);
this._helperContainer.appendChild(this.textarea);
this.compositionView = document.createElement('div');
this.compositionView.classList.add('composition-view');
this.compositionHelper = new CompositionHelper(this.textarea, this.compositionView, this);
this.helperContainer.appendChild(this.compositionView);
this._compositionView = document.createElement('div');
this._compositionView.classList.add('composition-view');
this._compositionHelper = new CompositionHelper(this.textarea, this._compositionView, this);
this._helperContainer.appendChild(this._compositionView);
this.charSizeStyleElement = document.createElement('style');
this.helperContainer.appendChild(this.charSizeStyleElement);
this.charMeasure = new CharMeasure(document, this.helperContainer);
this._charSizeStyleElement = document.createElement('style');
this._helperContainer.appendChild(this._charSizeStyleElement);
this.charMeasure = new CharMeasure(document, this._helperContainer);
// Performance: Add viewport and helper elements from the fragment
this.element.appendChild(fragment);
this.renderer = new Renderer(this, this.options.theme);
this.options.theme = null;
this.viewport = new Viewport(this, this.viewportElement, this.viewportScrollArea, this.charMeasure);
this.viewport = new Viewport(this, this._viewportElement, this._viewportScrollArea, this.charMeasure);
this.viewport.onThemeChanged(this.renderer.colorManager.colors);
this.on('cursormove', () => this.renderer.onCursorMove());
@@ -725,7 +725,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
this.viewport.syncScrollArea();
this.selectionManager.refresh();
});
this.viewportElement.addEventListener('scroll', () => this.selectionManager.refresh());
this._viewportElement.addEventListener('scroll', () => this.selectionManager.refresh());
this.mouseHelper = new MouseHelper(this.renderer);
@@ -742,7 +742,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
this.refresh(0, this.rows - 1);
// Initialize global actions that need to be taken on the document.
this.initGlobal();
this._initGlobal();
// Listen for mouse events and translate
// them into terminal mouse protocols.
@@ -869,7 +869,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
// button: button
// });
if (self.vt300Mouse) {
if (self._vt300Mouse) {
// NOTE: Unstable.
// http://www.vt100.net/docs/vt3xx-gp/chapter15.html
button &= 3;
@@ -886,7 +886,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
return;
}
if (self.decLocator) {
if (self._decLocator) {
// NOTE: Unstable.
button &= 3;
pos.x -= 32;
@@ -1029,19 +1029,19 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
}
// bind events
if (this.normalMouse) on(this.document, 'mousemove', sendMove);
if (this.normalMouse) on(this._document, 'mousemove', sendMove);
// x10 compatibility mode can't send button releases
if (!this.x10Mouse) {
const handler = (ev: MouseEvent) => {
sendButton(ev);
// TODO: Seems dangerous calling this on document?
if (this.normalMouse) off(this.document, 'mousemove', sendMove);
off(this.document, 'mouseup', handler);
if (this.normalMouse) off(this._document, 'mousemove', sendMove);
off(this._document, 'mouseup', handler);
return this.cancel(ev);
};
// TODO: Seems dangerous calling this on document?
on(this.document, 'mouseup', handler);
on(this._document, 'mouseup', handler);
}
return this.cancel(ev);
@@ -1052,8 +1052,28 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
// }
on(el, 'wheel', (ev: WheelEvent) => {
if (!this.mouseEvents) return;
if (this.x10Mouse || this.vt300Mouse || this.decLocator) return;
if (!this.mouseEvents) {
// Convert wheel events into up/down events when the buffer does not have scrollback, this
// enables scrolling in apps hosted in the alt buffer such as vim or tmux.
if (!this.buffer.hasScrollback) {
const amount = this.viewport.getLinesScrolled(ev);
// Do nothing if there's no vertical scroll
if (amount === 0) {
return;
}
// Construct and send sequences
const sequence = C0.ESC + (this.applicationCursor ? 'O' : '[') + ( ev.deltaY < 0 ? 'A' : 'B');
let data = '';
for (let i = 0; i < Math.abs(amount); i++) {
data += sequence;
}
this.send(data);
}
return;
}
if (this.x10Mouse || this._vt300Mouse || this._decLocator) return;
sendButton(ev);
ev.preventDefault();
});
@@ -1084,8 +1104,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
*/
public destroy(): void {
super.destroy();
this.readable = false;
this.writable = false;
this._readable = false;
this._writable = false;
this.handler = () => {};
this.write = () => {};
if (this.element && this.element.parentNode) {
@@ -1111,7 +1131,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
* @param {number} start The row to start from (between 0 and this.rows - 1).
* @param {number} end The row to end at (between start and this.rows - 1).
*/
private queueLinkification(start: number, end: number): void {
private _queueLinkification(start: number, end: number): void {
if (this.linkifier) {
this.linkifier.linkifyRows(start, end);
}
@@ -1151,13 +1171,13 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
if (!willBufferBeTrimmed) {
this.buffer.ybase++;
// Only scroll the ydisp with ybase if the user has not scrolled up
if (!this.userScrolling) {
if (!this._userScrolling) {
this.buffer.ydisp++;
}
} else {
// When the buffer is full and the user has scrolled up, keep the text
// stable unless ydisp is right at the top
if (this.userScrolling) {
if (this._userScrolling) {
this.buffer.ydisp = Math.max(this.buffer.ydisp - 1, 0);
}
}
@@ -1171,7 +1191,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
// Move the viewport to the bottom of the buffer unless the user is
// scrolling.
if (!this.userScrolling) {
if (!this._userScrolling) {
this.buffer.ydisp = this.buffer.ybase;
}
@@ -1200,9 +1220,9 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
if (this.buffer.ydisp === 0) {
return;
}
this.userScrolling = true;
this._userScrolling = true;
} else if (disp + this.buffer.ydisp >= this.buffer.ybase) {
this.userScrolling = false;
this._userScrolling = false;
}
const oldYdisp = this.buffer.ydisp;
@@ -1252,54 +1272,54 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
// Send XOFF to pause the pty process if the write buffer becomes too large so
// xterm.js can catch up before more data is sent. This is necessary in order
// to keep signals such as ^C responsive.
if (this.options.useFlowControl && !this.xoffSentToCatchUp && this.writeBuffer.length >= WRITE_BUFFER_PAUSE_THRESHOLD) {
if (this.options.useFlowControl && !this._xoffSentToCatchUp && this.writeBuffer.length >= WRITE_BUFFER_PAUSE_THRESHOLD) {
// XOFF - stop pty pipe
// XON will be triggered by emulator before processing data chunk
this.send(C0.DC3);
this.xoffSentToCatchUp = true;
this._xoffSentToCatchUp = true;
}
if (!this.writeInProgress && this.writeBuffer.length > 0) {
if (!this._writeInProgress && this.writeBuffer.length > 0) {
// Kick off a write which will write all data in sequence recursively
this.writeInProgress = true;
this._writeInProgress = true;
// Kick off an async innerWrite so more writes can come in while processing data
setTimeout(() => {
this.innerWrite();
this._innerWrite();
});
}
}
private innerWrite(): void {
private _innerWrite(): void {
const writeBatch = this.writeBuffer.splice(0, WRITE_BATCH_SIZE);
while (writeBatch.length > 0) {
const data = writeBatch.shift();
// If XOFF was sent in order to catch up with the pty process, resume it if
// the writeBuffer is empty to allow more data to come in.
if (this.xoffSentToCatchUp && writeBatch.length === 0 && this.writeBuffer.length === 0) {
if (this._xoffSentToCatchUp && writeBatch.length === 0 && this.writeBuffer.length === 0) {
this.send(C0.DC1);
this.xoffSentToCatchUp = false;
this._xoffSentToCatchUp = false;
}
this.refreshStart = this.buffer.y;
this.refreshEnd = this.buffer.y;
this._refreshStart = this.buffer.y;
this._refreshEnd = this.buffer.y;
// HACK: Set the parser state based on it's state at the time of return.
// This works around the bug #662 which saw the parser state reset in the
// middle of parsing escape sequence in two chunks. For some reason the
// state of the parser resets to 0 after exiting parser.parse. This change
// just sets the state back based on the correct return statement.
const state = this.parser.parse(data);
this.parser.setState(state);
const state = this._parser.parse(data);
this._parser.setState(state);
this.updateRange(this.buffer.y);
this.refresh(this.refreshStart, this.refreshEnd);
this.refresh(this._refreshStart, this._refreshEnd);
}
if (this.writeBuffer.length > 0) {
// Allow renderer to catch up before processing the next batch
setTimeout(() => this.innerWrite(), 0);
setTimeout(() => this._innerWrite(), 0);
} else {
this.writeInProgress = false;
this._writeInProgress = false;
}
}
@@ -1321,7 +1341,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
* the event should be processed by xterm.js.
*/
public attachCustomKeyEventHandler(customKeyEventHandler: CustomKeyEventHandler): void {
this.customKeyEventHandler = customKeyEventHandler;
this._customKeyEventHandler = customKeyEventHandler;
}
/**
@@ -1390,11 +1410,11 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
* @param {KeyboardEvent} ev The keydown event to be handled.
*/
protected _keyDown(ev: KeyboardEvent): boolean {
if (this.customKeyEventHandler && this.customKeyEventHandler(ev) === false) {
if (this._customKeyEventHandler && this._customKeyEventHandler(ev) === false) {
return false;
}
if (!this.compositionHelper.keydown(ev)) {
if (!this._compositionHelper.keydown(ev)) {
if (this.buffer.ybase !== this.buffer.ydisp) {
this.scrollToBottom();
}
@@ -1404,9 +1424,9 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
const result = this._evaluateKeyEscapeSequence(ev);
if (result.key === C0.DC3) { // XOFF
this.writeStopped = true;
this._writeStopped = true;
} else if (result.key === C0.DC1) { // XON
this.writeStopped = false;
this._writeStopped = false;
}
if (result.scrollLines) {
@@ -1801,7 +1821,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
protected _keyPress(ev: KeyboardEvent): boolean {
let key;
if (this.customKeyEventHandler && this.customKeyEventHandler(ev) === false) {
if (this._customKeyEventHandler && this._customKeyEventHandler(ev) === false) {
return false;
}
@@ -1838,14 +1858,14 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
* @param {string} data
*/
public send(data: string): void {
if (!this.sendDataQueue) {
if (!this._sendDataQueue) {
setTimeout(() => {
this.handler(this.sendDataQueue);
this.sendDataQueue = '';
this.handler(this._sendDataQueue);
this._sendDataQueue = '';
}, 1);
}
this.sendDataQueue += data;
this._sendDataQueue += data;
}
/**
@@ -1854,14 +1874,14 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
*/
public bell(): void {
this.emit('bell');
if (this.soundBell()) {
if (this._soundBell()) {
this.soundManager.playBellSound();
}
if (this.visualBell()) {
if (this._visualBell()) {
this.element.classList.add('visual-bell-active');
clearTimeout(this.visualBellTimer);
this.visualBellTimer = window.setTimeout(() => {
clearTimeout(this._visualBellTimer);
this._visualBellTimer = window.setTimeout(() => {
this.element.classList.remove('visual-bell-active');
}, 200);
}
@@ -1872,8 +1892,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
*/
public log(text: string, data?: any): void {
if (!this.options.debug) return;
if (!this.context.console || !this.context.console.log) return;
this.context.console.log(text, data);
if (!this._context.console || !this._context.console.log) return;
this._context.console.log(text, data);
}
/**
@@ -1881,8 +1901,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
*/
public error(text: string, data?: any): void {
if (!this.options.debug) return;
if (!this.context.console || !this.context.console.error) return;
this.context.console.error(text, data);
if (!this._context.console || !this._context.console.error) return;
this._context.console.error(text, data);
}
/**
@@ -1926,8 +1946,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
* @param {number} y The number of rows to refresh next.
*/
public updateRange(y: number): void {
if (y < this.refreshStart) this.refreshStart = y;
if (y > this.refreshEnd) this.refreshEnd = y;
if (y < this._refreshStart) this._refreshStart = y;
if (y > this._refreshEnd) this._refreshEnd = y;
// if (y > this.refreshEnd) {
// this.refreshEnd = y;
// if (y > this.rows - 1) {
@@ -1940,8 +1960,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
* Set the range of refreshing to the maximum value
*/
public maxRange(): void {
this.refreshStart = 0;
this.refreshEnd = this.rows - 1;
this._refreshStart = 0;
this._refreshEnd = this.rows - 1;
}
/**
@@ -2079,7 +2099,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
* Emit the 'title' event and populate the given title.
* @param {string} title The title to populate in the event.
*/
private handleTitle(title: string): void {
public handleTitle(title: string): void {
/**
* This event is emitted when the title of the terminal is changed
* from inside the terminal. The parameter is the new title.
@@ -2134,11 +2154,11 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
public reset(): void {
this.options.rows = this.rows;
this.options.cols = this.cols;
const customKeyEventHandler = this.customKeyEventHandler;
const inputHandler = this.inputHandler;
this.setup();
this.customKeyEventHandler = customKeyEventHandler;
this.inputHandler = inputHandler;
const customKeyEventHandler = this._customKeyEventHandler;
const inputHandler = this._inputHandler;
this._setup();
this._customKeyEventHandler = customKeyEventHandler;
this._inputHandler = inputHandler;
this.refresh(0, this.rows - 1);
this.viewport.syncScrollArea();
}
@@ -2166,13 +2186,13 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
return matchColor_(r1, g1, b1);
}
private visualBell(): boolean {
private _visualBell(): boolean {
return false;
// return this.options.bellStyle === 'visual' ||
// this.options.bellStyle === 'both';
}
private soundBell(): boolean {
private _soundBell(): boolean {
return this.options.bellStyle === 'sound';
// return this.options.bellStyle === 'sound' ||
// this.options.bellStyle === 'both';
+1
View File
@@ -89,6 +89,7 @@ export interface IInputHandlingTerminal extends IEventEmitter {
export interface IViewport {
scrollBarWidth: number;
syncScrollArea(): void;
getLinesScrolled(ev: WheelEvent): number;
onWheel(ev: WheelEvent): void;
onTouchStart(ev: TouchEvent): void;
onTouchMove(ev: TouchEvent): void;
+91 -53
View File
@@ -15,51 +15,56 @@ const FALLBACK_SCROLL_BAR_WIDTH = 15;
*/
export class Viewport implements IViewport {
public scrollBarWidth: number = 0;
private currentRowHeight: number = 0;
private lastRecordedBufferLength: number = 0;
private lastRecordedViewportHeight: number = 0;
private lastRecordedBufferHeight: number = 0;
private lastTouchY: number;
private _currentRowHeight: number = 0;
private _lastRecordedBufferLength: number = 0;
private _lastRecordedViewportHeight: number = 0;
private _lastRecordedBufferHeight: number = 0;
private _lastTouchY: number;
// Stores a partial line amount when scrolling, this is used to keep track of how much of a line
// is scrolled so we can "scroll" over partial lines and feel natural on touchpads. This is a
// quick fix and could have a more robust solution in place that reset the value when needed.
private _wheelPartialScroll: number = 0;
/**
* Creates a new Viewport.
* @param terminal The terminal this viewport belongs to.
* @param viewportElement The DOM element acting as the viewport.
* @param scrollArea The DOM element acting as the scroll area.
* @param charMeasure A DOM element used to measure the character size of. the terminal.
* @param _terminal The terminal this viewport belongs to.
* @param _viewportElement The DOM element acting as the viewport.
* @param _scrollArea The DOM element acting as the scroll area.
* @param _charMeasure A DOM element used to measure the character size of. the terminal.
*/
constructor(
private terminal: ITerminal,
private viewportElement: HTMLElement,
private scrollArea: HTMLElement,
private charMeasure: CharMeasure
private _terminal: ITerminal,
private _viewportElement: HTMLElement,
private _scrollArea: HTMLElement,
private _charMeasure: CharMeasure
) {
// Measure the width of the scrollbar. If it is 0 we can assume it's an OSX overlay scrollbar.
// Unfortunately the overlay scrollbar would be hidden underneath the screen element in that case,
// therefore we account for a standard amount to make it visible
this.scrollBarWidth = (this.viewportElement.offsetWidth - this.scrollArea.offsetWidth) || FALLBACK_SCROLL_BAR_WIDTH;
this.viewportElement.addEventListener('scroll', this.onScroll.bind(this));
this.scrollBarWidth = (this._viewportElement.offsetWidth - this._scrollArea.offsetWidth) || FALLBACK_SCROLL_BAR_WIDTH;
this._viewportElement.addEventListener('scroll', this._onScroll.bind(this));
// Perform this async to ensure the CharMeasure is ready.
setTimeout(() => this.syncScrollArea(), 0);
}
public onThemeChanged(colors: IColorSet): void {
this.viewportElement.style.backgroundColor = colors.background;
this._viewportElement.style.backgroundColor = colors.background;
}
/**
* Refreshes row height, setting line-height, viewport height and scroll area height if
* necessary.
*/
private refresh(): void {
if (this.charMeasure.height > 0) {
this.currentRowHeight = this.terminal.renderer.dimensions.scaledCellHeight / window.devicePixelRatio;
this.lastRecordedViewportHeight = this.viewportElement.offsetHeight;
const newBufferHeight = Math.round(this.currentRowHeight * this.lastRecordedBufferLength) + (this.lastRecordedViewportHeight - this.terminal.renderer.dimensions.canvasHeight);
if (this.lastRecordedBufferHeight !== newBufferHeight) {
this.lastRecordedBufferHeight = newBufferHeight;
this.scrollArea.style.height = this.lastRecordedBufferHeight + 'px';
private _refresh(): void {
if (this._charMeasure.height > 0) {
this._currentRowHeight = this._terminal.renderer.dimensions.scaledCellHeight / window.devicePixelRatio;
this._lastRecordedViewportHeight = this._viewportElement.offsetHeight;
const newBufferHeight = Math.round(this._currentRowHeight * this._lastRecordedBufferLength) + (this._lastRecordedViewportHeight - this._terminal.renderer.dimensions.canvasHeight);
if (this._lastRecordedBufferHeight !== newBufferHeight) {
this._lastRecordedBufferHeight = newBufferHeight;
this._scrollArea.style.height = this._lastRecordedBufferHeight + 'px';
}
}
}
@@ -68,24 +73,24 @@ export class Viewport implements IViewport {
* Updates dimensions and synchronizes the scroll area if necessary.
*/
public syncScrollArea(): void {
if (this.lastRecordedBufferLength !== this.terminal.buffer.lines.length) {
if (this._lastRecordedBufferLength !== this._terminal.buffer.lines.length) {
// If buffer height changed
this.lastRecordedBufferLength = this.terminal.buffer.lines.length;
this.refresh();
} else if (this.lastRecordedViewportHeight !== (<any>this.terminal).renderer.dimensions.canvasHeight) {
this._lastRecordedBufferLength = this._terminal.buffer.lines.length;
this._refresh();
} else if (this._lastRecordedViewportHeight !== (<any>this._terminal).renderer.dimensions.canvasHeight) {
// If viewport height changed
this.refresh();
this._refresh();
} else {
// If size has changed, refresh viewport
if (this.terminal.renderer.dimensions.scaledCellHeight / window.devicePixelRatio !== this.currentRowHeight) {
this.refresh();
if (this._terminal.renderer.dimensions.scaledCellHeight / window.devicePixelRatio !== this._currentRowHeight) {
this._refresh();
}
}
// Sync scrollTop
const scrollTop = this.terminal.buffer.ydisp * this.currentRowHeight;
if (this.viewportElement.scrollTop !== scrollTop) {
this.viewportElement.scrollTop = scrollTop;
const scrollTop = this._terminal.buffer.ydisp * this._currentRowHeight;
if (this._viewportElement.scrollTop !== scrollTop) {
this._viewportElement.scrollTop = scrollTop;
}
}
@@ -94,16 +99,16 @@ export class Viewport implements IViewport {
* terminal to scroll to it.
* @param ev The scroll event.
*/
private onScroll(ev: Event): void {
private _onScroll(ev: Event): void {
// Don't attempt to scroll if the element is not visible, otherwise scrollTop will be corrupt
// which causes the terminal to scroll the buffer to the top
if (!this.viewportElement.offsetParent) {
if (!this._viewportElement.offsetParent) {
return;
}
const newRow = Math.round(this.viewportElement.scrollTop / this.currentRowHeight);
const diff = newRow - this.terminal.buffer.ydisp;
this.terminal.scrollLines(diff, true);
const newRow = Math.round(this._viewportElement.scrollTop / this._currentRowHeight);
const diff = newRow - this._terminal.buffer.ydisp;
this._terminal.scrollLines(diff, true);
}
/**
@@ -113,28 +118,61 @@ export class Viewport implements IViewport {
* @param ev The mouse wheel event.
*/
public onWheel(ev: WheelEvent): void {
if (ev.deltaY === 0) {
// Do nothing if it's not a vertical scroll event
const amount = this._getPixelsScrolled(ev);
if (amount === 0) {
return;
}
// Fallback to WheelEvent.DOM_DELTA_PIXEL
let multiplier = 1;
if (ev.deltaMode === WheelEvent.DOM_DELTA_LINE) {
multiplier = this.currentRowHeight;
} else if (ev.deltaMode === WheelEvent.DOM_DELTA_PAGE) {
multiplier = this.currentRowHeight * this.terminal.rows;
}
this.viewportElement.scrollTop += ev.deltaY * multiplier;
this._viewportElement.scrollTop += amount;
// Prevent the page from scrolling when the terminal scrolls
ev.preventDefault();
}
private _getPixelsScrolled(ev: WheelEvent): number {
// Do nothing if it's not a vertical scroll event
if (ev.deltaY === 0) {
return 0;
}
// Fallback to WheelEvent.DOM_DELTA_PIXEL
let amount = ev.deltaY;
if (ev.deltaMode === WheelEvent.DOM_DELTA_LINE) {
amount *= this._currentRowHeight;
} else if (ev.deltaMode === WheelEvent.DOM_DELTA_PAGE) {
amount *= this._currentRowHeight * this._terminal.rows;
}
return amount;
}
/**
* Gets the number of pixels scrolled by the mouse event taking into account what type of delta
* is being used.
* @param ev The mouse wheel event.
*/
public getLinesScrolled(ev: WheelEvent): number {
// Do nothing if it's not a vertical scroll event
if (ev.deltaY === 0) {
return 0;
}
// Fallback to WheelEvent.DOM_DELTA_LINE
let amount = ev.deltaY;
if (ev.deltaMode === WheelEvent.DOM_DELTA_PIXEL) {
amount /= this._currentRowHeight + 0.0; // Prevent integer division
this._wheelPartialScroll += amount;
amount = Math.floor(Math.abs(this._wheelPartialScroll)) * (this._wheelPartialScroll > 0 ? 1 : -1);
this._wheelPartialScroll %= 1;
} else if (ev.deltaMode === WheelEvent.DOM_DELTA_PAGE) {
amount *= this._terminal.rows;
}
return amount;
}
/**
* Handles the touchstart event, recording the touch occurred.
* @param ev The touch event.
*/
public onTouchStart(ev: TouchEvent): void {
this.lastTouchY = ev.touches[0].pageY;
this._lastTouchY = ev.touches[0].pageY;
}
/**
@@ -142,12 +180,12 @@ export class Viewport implements IViewport {
* @param ev The touch event.
*/
public onTouchMove(ev: TouchEvent): void {
let deltaY = this.lastTouchY - ev.touches[0].pageY;
this.lastTouchY = ev.touches[0].pageY;
let deltaY = this._lastTouchY - ev.touches[0].pageY;
this._lastTouchY = ev.touches[0].pageY;
if (deltaY === 0) {
return;
}
this.viewportElement.scrollTop += deltaY;
this._viewportElement.scrollTop += deltaY;
ev.preventDefault();
}
}
+4 -4
View File
@@ -237,7 +237,7 @@ class CursorBlinkStateManager {
constructor(
terminal: ITerminal,
private renderCallback: () => void
private _renderCallback: () => void
) {
this.isCursorVisible = true;
if (terminal.isFocused) {
@@ -272,7 +272,7 @@ class CursorBlinkStateManager {
this.isCursorVisible = true;
if (!this._animationFrame) {
this._animationFrame = window.requestAnimationFrame(() => {
this.renderCallback();
this._renderCallback();
this._animationFrame = null;
});
}
@@ -303,7 +303,7 @@ class CursorBlinkStateManager {
// Hide the cursor
this.isCursorVisible = false;
this._animationFrame = window.requestAnimationFrame(() => {
this.renderCallback();
this._renderCallback();
this._animationFrame = null;
});
@@ -322,7 +322,7 @@ class CursorBlinkStateManager {
// Invert visibility and render
this.isCursorVisible = !this.isCursorVisible;
this._animationFrame = window.requestAnimationFrame(() => {
this.renderCallback();
this._renderCallback();
this._animationFrame = null;
});
}, BLINK_INTERVAL);
+3
View File
@@ -338,6 +338,9 @@ export class MockViewport implements IViewport {
throw new Error('Method not implemented.');
}
syncScrollArea(): void { }
getLinesScrolled(ev: WheelEvent): number {
throw new Error('Method not implemented.');
}
}
export class MockCompositionHelper implements ICompositionHelper {