Merge remote-tracking branch 'upstream/master' into 460_escape_sequence_c0
This commit is contained in:
+3
-1
@@ -11,7 +11,9 @@ export class EventEmitter {
|
||||
private _events: {[type: string]: ListenerType[]};
|
||||
|
||||
constructor() {
|
||||
this._events = {};
|
||||
// Restore the previous events if available, this will happen if the
|
||||
// constructor is called multiple times on the same object (terminal reset).
|
||||
this._events = this._events || {};
|
||||
}
|
||||
|
||||
public on(type, listener): void {
|
||||
|
||||
+10
-16
@@ -2,11 +2,11 @@ import { assert } from 'chai';
|
||||
import { Viewport } from './Viewport';
|
||||
|
||||
describe('Viewport', () => {
|
||||
var terminal;
|
||||
var viewportElement;
|
||||
var charMeasureElement;
|
||||
var viewport;
|
||||
var scrollAreaElement;
|
||||
let terminal;
|
||||
let viewportElement;
|
||||
let charMeasure;
|
||||
let viewport;
|
||||
let scrollAreaElement;
|
||||
|
||||
const CHARACTER_HEIGHT = 10;
|
||||
|
||||
@@ -34,21 +34,17 @@ describe('Viewport', () => {
|
||||
height: 0
|
||||
}
|
||||
};
|
||||
charMeasureElement = {
|
||||
getBoundingClientRect: () => {
|
||||
return { width: null, height: CHARACTER_HEIGHT };
|
||||
}
|
||||
charMeasure = {
|
||||
height: CHARACTER_HEIGHT
|
||||
};
|
||||
viewport = new Viewport(terminal, viewportElement, scrollAreaElement, charMeasureElement);
|
||||
viewport = new Viewport(terminal, viewportElement, scrollAreaElement, charMeasure);
|
||||
});
|
||||
|
||||
describe('refresh', () => {
|
||||
it('should set the line-height of the terminal', () => {
|
||||
assert.equal(viewportElement.style.lineHeight, CHARACTER_HEIGHT + 'px');
|
||||
assert.equal(terminal.rowContainer.style.lineHeight, CHARACTER_HEIGHT + 'px');
|
||||
charMeasureElement.getBoundingClientRect = () => {
|
||||
return { width: null, height: 1 };
|
||||
};
|
||||
charMeasure.height = 1;
|
||||
viewport.refresh();
|
||||
assert.equal(viewportElement.style.lineHeight, '1px');
|
||||
assert.equal(terminal.rowContainer.style.lineHeight, '1px');
|
||||
@@ -59,9 +55,7 @@ describe('Viewport', () => {
|
||||
terminal.rows = 1;
|
||||
viewport.refresh();
|
||||
assert.equal(viewportElement.style.height, 1 * CHARACTER_HEIGHT + 'px');
|
||||
charMeasureElement.getBoundingClientRect = () => {
|
||||
return { width: null, height: 20 };
|
||||
};
|
||||
charMeasure.height = 20;
|
||||
viewport.refresh();
|
||||
assert.equal(viewportElement.style.height, 20 + 'px');
|
||||
});
|
||||
|
||||
+12
-13
@@ -3,6 +3,7 @@
|
||||
*/
|
||||
|
||||
import { ITerminal } from './Interfaces';
|
||||
import { CharMeasure } from './utils/CharMeasure';
|
||||
|
||||
/**
|
||||
* Represents the viewport of a terminal, the visible area within the larger buffer of output.
|
||||
@@ -24,7 +25,7 @@ export class Viewport {
|
||||
private terminal: ITerminal,
|
||||
private viewportElement: HTMLElement,
|
||||
private scrollArea: HTMLElement,
|
||||
private charMeasureElement: HTMLElement
|
||||
private charMeasure: CharMeasure
|
||||
) {
|
||||
this.currentRowHeight = 0;
|
||||
this.lastRecordedBufferLength = 0;
|
||||
@@ -43,21 +44,20 @@ export class Viewport {
|
||||
* @param charSize A character size measurement bounding rect object, if it doesn't exist it will
|
||||
* be created.
|
||||
*/
|
||||
private refresh(charSize?: ClientRect): void {
|
||||
var size = charSize || this.charMeasureElement.getBoundingClientRect();
|
||||
if (size.height > 0) {
|
||||
var rowHeightChanged = size.height !== this.currentRowHeight;
|
||||
private refresh(): void {
|
||||
if (this.charMeasure.height > 0) {
|
||||
var rowHeightChanged = this.charMeasure.height !== this.currentRowHeight;
|
||||
if (rowHeightChanged) {
|
||||
this.currentRowHeight = size.height;
|
||||
this.viewportElement.style.lineHeight = size.height + 'px';
|
||||
this.terminal.rowContainer.style.lineHeight = size.height + 'px';
|
||||
this.currentRowHeight = this.charMeasure.height;
|
||||
this.viewportElement.style.lineHeight = this.charMeasure.height + 'px';
|
||||
this.terminal.rowContainer.style.lineHeight = this.charMeasure.height + 'px';
|
||||
}
|
||||
var viewportHeightChanged = this.lastRecordedViewportHeight !== this.terminal.rows;
|
||||
if (rowHeightChanged || viewportHeightChanged) {
|
||||
this.lastRecordedViewportHeight = this.terminal.rows;
|
||||
this.viewportElement.style.height = size.height * this.terminal.rows + 'px';
|
||||
this.viewportElement.style.height = this.charMeasure.height * this.terminal.rows + 'px';
|
||||
}
|
||||
this.scrollArea.style.height = (size.height * this.lastRecordedBufferLength) + 'px';
|
||||
this.scrollArea.style.height = (this.charMeasure.height * this.lastRecordedBufferLength) + 'px';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,9 +74,8 @@ export class Viewport {
|
||||
this.refresh();
|
||||
} else {
|
||||
// If size has changed, refresh viewport
|
||||
var size = this.charMeasureElement.getBoundingClientRect();
|
||||
if (size.height !== this.currentRowHeight) {
|
||||
this.refresh(size);
|
||||
if (this.charMeasure.height !== this.currentRowHeight) {
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
/**
|
||||
* Attributes and methods to help with identifying the current browser and platform.
|
||||
* @module xterm/utils/Browser
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { contains } from './Generic.js';
|
||||
|
||||
let isNode = (typeof navigator == 'undefined') ? true : false;
|
||||
let userAgent = (isNode) ? 'node' : navigator.userAgent;
|
||||
let platform = (isNode) ? 'node' : navigator.platform;
|
||||
|
||||
export let isFirefox = !!~userAgent.indexOf('Firefox');
|
||||
export let isMSIE = !!~userAgent.indexOf('MSIE') || !!~userAgent.indexOf('Trident');
|
||||
|
||||
// Find the users platform. We use this to interpret the meta key
|
||||
// and ISO third level shifts.
|
||||
// http://stackoverflow.com/q/19877924/577598
|
||||
export let isMac = contains(['Macintosh', 'MacIntel', 'MacPPC', 'Mac68K'], platform);
|
||||
export let isIpad = platform === 'iPad';
|
||||
export let isIphone = platform === 'iPhone';
|
||||
export let isMSWindows = contains(['Windows', 'Win16', 'Win32', 'WinCE'], platform);
|
||||
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Attributes and methods to help with identifying the current browser and platform.
|
||||
* @module xterm/utils/Browser
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { contains } from './Generic';
|
||||
|
||||
const isNode = (typeof navigator === 'undefined') ? true : false;
|
||||
const userAgent = (isNode) ? 'node' : navigator.userAgent;
|
||||
const platform = (isNode) ? 'node' : navigator.platform;
|
||||
|
||||
export const isFirefox = !!~userAgent.indexOf('Firefox');
|
||||
export const isMSIE = !!~userAgent.indexOf('MSIE') || !!~userAgent.indexOf('Trident');
|
||||
|
||||
// Find the users platform. We use this to interpret the meta key
|
||||
// and ISO third level shifts.
|
||||
// http://stackoverflow.com/q/19877924/577598
|
||||
export const isMac = contains(['Macintosh', 'MacIntel', 'MacPPC', 'Mac68K'], platform);
|
||||
export const isIpad = platform === 'iPad';
|
||||
export const isIphone = platform === 'iPhone';
|
||||
export const isMSWindows = contains(['Windows', 'Win16', 'Win32', 'WinCE'], platform);
|
||||
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* @module xterm/utils/CharMeasure
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { EventEmitter } from '../EventEmitter.js';
|
||||
|
||||
/**
|
||||
* Utility class that measures the size of a character.
|
||||
*/
|
||||
export class CharMeasure extends EventEmitter {
|
||||
private _parentElement: HTMLElement;
|
||||
private _measureElement: HTMLElement;
|
||||
private _width: number;
|
||||
private _height: number;
|
||||
|
||||
constructor(parentElement: HTMLElement) {
|
||||
super();
|
||||
this._parentElement = parentElement;
|
||||
}
|
||||
|
||||
public get width(): number {
|
||||
return this._width;
|
||||
}
|
||||
|
||||
public get height(): number {
|
||||
return this._height;
|
||||
}
|
||||
|
||||
public measure(): void {
|
||||
const oldWidth = this._width;
|
||||
const oldHeight = this._height;
|
||||
|
||||
if (!this._measureElement) {
|
||||
this._measureElement = document.createElement('span');
|
||||
this._measureElement.style.position = 'absolute';
|
||||
this._measureElement.style.top = '0';
|
||||
this._measureElement.style.left = '-9999em';
|
||||
this._measureElement.textContent = 'W';
|
||||
}
|
||||
|
||||
this._parentElement.appendChild(this._measureElement);
|
||||
const geometry = this._measureElement.getBoundingClientRect();
|
||||
this._width = geometry.width;
|
||||
this._height = geometry.height;
|
||||
this._parentElement.removeChild(this._measureElement);
|
||||
|
||||
if (this._width !== oldWidth || this._height !== oldHeight) {
|
||||
this.emit('charsizechanged');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,6 @@
|
||||
* @param {Array} array The array to search for the given element.
|
||||
* @param {Object} el The element to look for into the array
|
||||
*/
|
||||
export let contains = function(arr, el) {
|
||||
export function contains(arr: any[], el: any) {
|
||||
return arr.indexOf(el) >= 0;
|
||||
};
|
||||
@@ -116,6 +116,10 @@
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.terminal .xterm-wide-char {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.terminal .xterm-rows {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
|
||||
+90
-68
@@ -16,6 +16,7 @@ import { Viewport } from './Viewport.js';
|
||||
import { rightClickHandler, pasteHandler, copyHandler } from './handlers/Clipboard.js';
|
||||
import { CircularList } from './utils/CircularList.js';
|
||||
import { C0 } from './EscapeSequences';
|
||||
import { CharMeasure } from './utils/CharMeasure.js';
|
||||
import * as Browser from './utils/Browser';
|
||||
import * as Keyboard from './utils/Keyboard';
|
||||
|
||||
@@ -138,14 +139,8 @@ function Terminal(options) {
|
||||
*/
|
||||
this.y = 0;
|
||||
|
||||
/**
|
||||
* Used to debounce the refresh function
|
||||
*/
|
||||
this.isRefreshing = false;
|
||||
|
||||
/**
|
||||
* Whether there is a full terminal refresh queued
|
||||
*/
|
||||
/** A queue of the rows to be refreshed */
|
||||
this.refreshRowsQueue = [];
|
||||
|
||||
this.cursorState = 0;
|
||||
this.cursorHidden = false;
|
||||
@@ -408,7 +403,7 @@ Terminal.prototype.blur = function() {
|
||||
*/
|
||||
Terminal.bindBlur = function (term) {
|
||||
on(term.textarea, 'blur', function (ev) {
|
||||
term.refresh(term.y, term.y);
|
||||
term.queueRefresh(term.y, term.y);
|
||||
if (term.sendFocus) {
|
||||
term.send(C0.ESC + '[O');
|
||||
}
|
||||
@@ -574,20 +569,25 @@ Terminal.prototype.open = function(parent) {
|
||||
this.compositionHelper = new CompositionHelper(this.textarea, this.compositionView, this);
|
||||
this.helperContainer.appendChild(this.compositionView);
|
||||
|
||||
this.charMeasureElement = document.createElement('div');
|
||||
this.charMeasureElement.classList.add('xterm-char-measure-element');
|
||||
this.charMeasureElement.innerHTML = 'W';
|
||||
this.helperContainer.appendChild(this.charMeasureElement);
|
||||
this.charSizeStyleElement = document.createElement('style');
|
||||
this.helperContainer.appendChild(this.charSizeStyleElement);
|
||||
|
||||
for (; i < this.rows; i++) {
|
||||
this.insertRow();
|
||||
}
|
||||
this.parent.appendChild(this.element);
|
||||
|
||||
this.viewport = new Viewport(this, this.viewportElement, this.viewportScrollArea, this.charMeasureElement);
|
||||
this.charMeasure = new CharMeasure(this.rowContainer);
|
||||
this.charMeasure.on('charsizechanged', function () {
|
||||
self.updateCharSizeCSS();
|
||||
});
|
||||
this.charMeasure.measure();
|
||||
|
||||
// Draw the screen.
|
||||
this.refresh(0, this.rows - 1);
|
||||
this.viewport = new Viewport(this, this.viewportElement, this.viewportScrollArea, this.charMeasure);
|
||||
|
||||
// Setup loop that draws to screen
|
||||
this.queueRefresh(0, this.rows - 1);
|
||||
this.refreshLoop();
|
||||
|
||||
// Initialize global actions that
|
||||
// need to be taken on the document.
|
||||
@@ -642,6 +642,13 @@ Terminal.loadAddon = function(addon, callback) {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the helper CSS class with any changes necessary after the terminal's
|
||||
* character width has been changed.
|
||||
*/
|
||||
Terminal.prototype.updateCharSizeCSS = function() {
|
||||
this.charSizeStyleElement.textContent = '.xterm-wide-char{width:' + (this.charMeasure.width * 2) + 'px;}';
|
||||
}
|
||||
|
||||
/**
|
||||
* XTerm mouse events
|
||||
@@ -998,6 +1005,48 @@ Terminal.flags = {
|
||||
INVISIBLE: 16
|
||||
}
|
||||
|
||||
/**
|
||||
* Queues a refresh between two rows (inclusive), to be done on next animation
|
||||
* frame.
|
||||
* @param {number} start The start row.
|
||||
* @param {number} end The end row.
|
||||
*/
|
||||
Terminal.prototype.queueRefresh = function(start, end) {
|
||||
this.refreshRowsQueue.push({ start: start, end: end });
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the refresh loop callback, calling refresh only if a refresh is
|
||||
* necessary before queueing up the next one.
|
||||
*/
|
||||
Terminal.prototype.refreshLoop = function() {
|
||||
// Don't refresh if there were no row changes
|
||||
if (this.refreshRowsQueue.length > 0) {
|
||||
var start;
|
||||
var end;
|
||||
if (this.refreshRowsQueue.length > 4) {
|
||||
// Just do a full refresh when 5+ refreshes are queued
|
||||
start = 0;
|
||||
end = this.rows - 1;
|
||||
} else {
|
||||
// Get start and end rows that need refreshing
|
||||
start = this.refreshRowsQueue[0].start;
|
||||
end = this.refreshRowsQueue[0].end;
|
||||
for (var i = 1; i < this.refreshRowsQueue.length; i++) {
|
||||
if (this.refreshRowsQueue[i].start < start) {
|
||||
start = this.refreshRowsQueue[i].start;
|
||||
}
|
||||
if (this.refreshRowsQueue[i].end > end) {
|
||||
end = this.refreshRowsQueue[i].end;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.refreshRowsQueue = [];
|
||||
this.refresh(start, end);
|
||||
}
|
||||
window.requestAnimationFrame(this.refreshLoop.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes (re-renders) terminal content within two rows (inclusive)
|
||||
*
|
||||
@@ -1018,47 +1067,10 @@ Terminal.flags = {
|
||||
*
|
||||
* @param {number} start The row to start from (between 0 and terminal's height terminal - 1)
|
||||
* @param {number} end The row to end at (between fromRow and terminal's height terminal - 1)
|
||||
* @param {boolean} queue Whether the refresh should ran right now or be queued
|
||||
*/
|
||||
Terminal.prototype.refresh = function(start, end, queue) {
|
||||
Terminal.prototype.refresh = function(start, end) {
|
||||
var self = this;
|
||||
|
||||
// queue defaults to true
|
||||
queue = (typeof queue == 'undefined') ? true : queue;
|
||||
|
||||
/**
|
||||
* The refresh queue allows refresh to execute only approximately 30 times a second. For
|
||||
* commands that pass a significant amount of output to the write function, this prevents the
|
||||
* terminal from maxing out the CPU and making the UI unresponsive. While commands can still
|
||||
* run beyond what they do on the terminal, it is far better with a debounce in place as
|
||||
* every single terminal manipulation does not need to be constructed in the DOM.
|
||||
*
|
||||
* A side-effect of this is that it makes ^C to interrupt a process seem more responsive.
|
||||
*/
|
||||
if (queue) {
|
||||
// If refresh should be queued, order the refresh and return.
|
||||
if (this._refreshIsQueued) {
|
||||
// If a refresh has already been queued, just order a full refresh next
|
||||
this._fullRefreshNext = true;
|
||||
} else {
|
||||
setTimeout(function () {
|
||||
self.refresh(start, end, false);
|
||||
}, 34)
|
||||
this._refreshIsQueued = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// If refresh should be run right now (not be queued), release the lock
|
||||
this._refreshIsQueued = false;
|
||||
|
||||
// If multiple refreshes were requested, make a full refresh.
|
||||
if (this._fullRefreshNext) {
|
||||
start = 0;
|
||||
end = this.rows - 1;
|
||||
this._fullRefreshNext = false // reset lock
|
||||
}
|
||||
|
||||
var x, y, i, line, out, ch, ch_width, width, data, attr, bg, fg, flags, row, parent, focused = document.activeElement;
|
||||
|
||||
// If this is a big refresh, remove the terminal rows from the DOM for faster calculations
|
||||
@@ -1183,6 +1195,9 @@ Terminal.prototype.refresh = function(start, end, queue) {
|
||||
}
|
||||
}
|
||||
|
||||
if (ch_width === 2) {
|
||||
out += '<span class="xterm-wide-char">';
|
||||
}
|
||||
switch (ch) {
|
||||
case '&':
|
||||
out += '&';
|
||||
@@ -1201,6 +1216,9 @@ Terminal.prototype.refresh = function(start, end, queue) {
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (ch_width === 2) {
|
||||
out += '</span>';
|
||||
}
|
||||
|
||||
attr = data;
|
||||
}
|
||||
@@ -1225,7 +1243,7 @@ Terminal.prototype.refresh = function(start, end, queue) {
|
||||
Terminal.prototype.showCursor = function() {
|
||||
if (!this.cursorState) {
|
||||
this.cursorState = 1;
|
||||
this.refresh(this.y, this.y);
|
||||
this.queueRefresh(this.y, this.y);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1235,6 +1253,15 @@ Terminal.prototype.showCursor = function() {
|
||||
Terminal.prototype.scroll = function() {
|
||||
var row;
|
||||
|
||||
// Make room for the new row in lines
|
||||
if (this.lines.length === this.lines.maxLength) {
|
||||
this.lines.trimStart(1);
|
||||
this.ybase--;
|
||||
if (this.ydisp !== 0) {
|
||||
this.ydisp--;
|
||||
}
|
||||
}
|
||||
|
||||
this.ybase++;
|
||||
|
||||
// TODO: Why is this done twice?
|
||||
@@ -1249,13 +1276,6 @@ Terminal.prototype.scroll = function() {
|
||||
row -= this.rows - 1 - this.scrollBottom;
|
||||
|
||||
if (row === this.lines.length) {
|
||||
// Compensate ybase and ydisp if lines has hit the maximum buffer size
|
||||
if (this.lines.length === this.lines.maxLength) {
|
||||
this.ybase--;
|
||||
if (this.ydisp !== 0) {
|
||||
this.ydisp--;
|
||||
}
|
||||
}
|
||||
// Optimization: pushing is faster than splicing when they amount to the same behavior
|
||||
this.lines.push(this.blankLine());
|
||||
} else {
|
||||
@@ -1312,7 +1332,7 @@ Terminal.prototype.scrollDisp = function(disp, suppressScrollEvent) {
|
||||
this.emit('scroll', this.ydisp);
|
||||
}
|
||||
|
||||
this.refresh(0, this.rows - 1);
|
||||
this.queueRefresh(0, this.rows - 1);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -2379,7 +2399,7 @@ Terminal.prototype.write = function(data) {
|
||||
}
|
||||
|
||||
this.updateRange(this.y);
|
||||
this.refresh(this.refreshStart, this.refreshEnd);
|
||||
this.queueRefresh(this.refreshStart, this.refreshEnd);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -2945,7 +2965,9 @@ Terminal.prototype.resize = function(x, y) {
|
||||
this.scrollTop = 0;
|
||||
this.scrollBottom = y - 1;
|
||||
|
||||
this.refresh(0, this.rows - 1);
|
||||
this.charMeasure.measure();
|
||||
|
||||
this.queueRefresh(0, this.rows - 1);
|
||||
|
||||
this.normal = null;
|
||||
|
||||
@@ -3074,7 +3096,7 @@ Terminal.prototype.clear = function() {
|
||||
for (var i = 1; i < this.rows; i++) {
|
||||
this.lines.push(this.blankLine());
|
||||
}
|
||||
this.refresh(0, this.rows - 1);
|
||||
this.queueRefresh(0, this.rows - 1);
|
||||
this.emit('scroll', this.ydisp);
|
||||
};
|
||||
|
||||
@@ -3205,7 +3227,7 @@ Terminal.prototype.reset = function() {
|
||||
var customKeydownHandler = this.customKeydownHandler;
|
||||
Terminal.call(this, this.options);
|
||||
this.customKeydownHandler = customKeydownHandler;
|
||||
this.refresh(0, this.rows - 1);
|
||||
this.queueRefresh(0, this.rows - 1);
|
||||
this.viewport.syncScrollArea();
|
||||
};
|
||||
|
||||
@@ -4324,7 +4346,7 @@ Terminal.prototype.resetMode = function(params) {
|
||||
// this.x = this.savedX;
|
||||
// this.y = this.savedY;
|
||||
// }
|
||||
this.refresh(0, this.rows - 1);
|
||||
this.queueRefresh(0, this.rows - 1);
|
||||
this.viewport.syncScrollArea();
|
||||
this.showCursor();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user