Merge remote-tracking branch 'origin/master' into 24_multi_line_links
This commit is contained in:
+61
-61
@@ -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
@@ -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(',');
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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';
|
||||
|
||||
@@ -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
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user