Merge branch 'master' into fix_1879

This commit is contained in:
Daniel Imms
2019-03-30 15:38:53 -07:00
committed by GitHub
35 changed files with 208 additions and 813 deletions
+5 -1
View File
@@ -211,7 +211,7 @@ export class Buffer implements IBuffer {
this.scrollBottom = newRows - 1;
if (this._hasScrollback) {
if (this._isReflowEnabled) {
this._reflow(newCols, newRows);
// Trim the end of the line off if cols shrunk
@@ -226,6 +226,10 @@ export class Buffer implements IBuffer {
this._rows = newRows;
}
private get _isReflowEnabled(): boolean {
return this._hasScrollback && !(this._terminal as any).isWinptyCompatEnabled;
}
private _reflow(newCols: number, newRows: number): void {
if (this._cols === newCols) {
return;
+23 -21
View File
@@ -64,10 +64,12 @@ const document = (typeof window !== 'undefined') ? window.document : null;
const WRITE_BUFFER_PAUSE_THRESHOLD = 5;
/**
* The number of writes to perform in a single batch before allowing the
* renderer to catch up with a 0ms setTimeout.
* The max number of ms to spend on writes before allowing the renderer to
* catch up with a 0ms setTimeout. A value of < 33 to keep us close to
* 30fps, and a value of < 16 to try to run at 60fps. Of course, the real FPS
* depends on the time it takes for the renderer to draw the frame.
*/
const WRITE_BATCH_SIZE = 300;
const WRITE_TIMEOUT_MS = 12;
const MINIMUM_COLS = 2; // Less than 2 can mess with wide chars
const MINIMUM_ROWS = 1;
@@ -341,7 +343,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
*/
public focus(): void {
if (this.textarea) {
this.textarea.focus();
this.textarea.focus({ preventScroll: true });
}
}
@@ -859,17 +861,11 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
if (ch > 127) ch = 127;
data.push(ch);
} else {
if (ch === 2047) {
data.push(0);
if (ch > 2047) {
data.push(2047);
return;
}
if (ch < 127) {
data.push(ch);
} else {
if (ch > 2047) ch = 2047;
data.push(0xC0 | (ch >> 6));
data.push(0x80 | (ch & 0x3F));
}
data.push(ch);
}
}
@@ -1348,19 +1344,20 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
}
}
protected _innerWrite(): void {
protected _innerWrite(bufferOffset: number = 0): void {
// Ensure the terminal isn't disposed
if (this._isDisposed) {
this.writeBuffer = [];
}
const writeBatch = this.writeBuffer.splice(0, WRITE_BATCH_SIZE);
while (writeBatch.length > 0) {
const data = writeBatch.shift();
const startTime = Date.now();
while (this.writeBuffer.length > bufferOffset) {
const data = this.writeBuffer[bufferOffset];
bufferOffset++;
// 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) {
// we reached the end of the writeBuffer to allow more data to come in.
if (this._xoffSentToCatchUp && this.writeBuffer.length === bufferOffset) {
this.handler(C0.DC1);
this._xoffSentToCatchUp = false;
}
@@ -1378,12 +1375,17 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
this.updateRange(this.buffer.y);
this.refresh(this._refreshStart, this._refreshEnd);
if (Date.now() - startTime >= WRITE_TIMEOUT_MS) {
break;
}
}
if (this.writeBuffer.length > 0) {
if (this.writeBuffer.length > bufferOffset) {
// Allow renderer to catch up before processing the next batch
setTimeout(() => this._innerWrite(), 0);
setTimeout(() => this._innerWrite(bufferOffset), 0);
} else {
this._writeInProgress = false;
this.writeBuffer = [];
}
}
+1 -2
View File
@@ -10,8 +10,7 @@
"outDir": "../../../lib/addons/attach/",
"sourceMap": true,
"removeComments": true,
"declaration": true,
"preserveWatchOutput": true
"declaration": true
},
"include": [
"**/*.ts",
-1
View File
@@ -11,7 +11,6 @@
"sourceMap": true,
"removeComments": true,
"declaration": true,
"preserveWatchOutput": true,
"types": [
"../../node_modules/@types/mocha"
]
-1
View File
@@ -11,7 +11,6 @@
"sourceMap": true,
"removeComments": true,
"declaration": true,
"preserveWatchOutput": true,
"types": [
"../../node_modules/@types/mocha"
]
-1
View File
@@ -11,7 +11,6 @@
"sourceMap": true,
"removeComments": true,
"declaration": true,
"preserveWatchOutput": true,
"types": [
"../../node_modules/@types/mocha"
]
-1
View File
@@ -10,7 +10,6 @@
"sourceMap": true,
"removeComments": true,
"declaration": true,
"preserveWatchOutput": true,
"types": [
"../../node_modules/@types/mocha"
]
-1
View File
@@ -11,7 +11,6 @@
"sourceMap": true,
"removeComments": true,
"declaration": true,
"preserveWatchOutput": true,
"types": [
"../../node_modules/@types/mocha"
]
-1
View File
@@ -10,7 +10,6 @@
"sourceMap": true,
"removeComments": true,
"declaration": true,
"preserveWatchOutput": true,
"types": [
"../../node_modules/@types/mocha"
]
+1 -5
View File
@@ -13,11 +13,7 @@ const WHITESPACE_CELL_CODE = 32;
export function winptyCompatInit(terminal: Terminal): void {
const addonTerminal = <IWinptyCompatAddonTerminal>terminal;
// Don't do anything when the platform is not Windows
const isWindows = ['Windows', 'Win16', 'Win32', 'WinCE'].indexOf(navigator.platform) >= 0;
if (!isWindows) {
return;
}
(addonTerminal._core as any).isWinptyCompatEnabled = true;
// Winpty does not support wraparound mode which means that lines will never
// be marked as wrapped. This causes issues for things like copying a line
-1
View File
@@ -10,7 +10,6 @@
"sourceMap": true,
"removeComments": true,
"declaration": true,
"preserveWatchOutput": true,
"types": [
"../../node_modules/@types/mocha"
]
+2 -12
View File
@@ -1,17 +1,7 @@
{
"extends": "../tsconfig-library-base",
"compilerOptions": {
"target": "es5",
"lib": [
"es5"
],
"rootDir": ".",
"noEmit": true,
"strict": true,
"pretty": true,
"types": [
"../../node_modules/@types/mocha",
"../../"
]
"outDir": "../../lib"
},
"include": [
"./**/*"
+6 -14
View File
@@ -1,20 +1,12 @@
{
"extends": "../tsconfig-library-base",
"compilerOptions": {
"target": "es5",
"lib": [
"es5"
],
"rootDir": ".",
"noEmit": true,
"strict": true,
"pretty": true,
"types": [
"../../node_modules/@types/mocha",
"../../"
]
"outDir": "../../lib"
},
"include": [
"./**/*",
"../common/**/*"
"./**/*"
],
"references": [
{ "path": "../common" }
]
}
+2 -2
View File
@@ -241,7 +241,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
this._ctx.fillText(
charData[CHAR_DATA_CHAR_INDEX],
x * this._scaledCellWidth + this._scaledCharLeft,
(y + 0.5) * this._scaledCellHeight + this._scaledCharTop);
y * this._scaledCellHeight + this._scaledCharTop + this._scaledCharHeight / 2);
}
/**
@@ -316,7 +316,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
this._ctx.fillText(
chars,
x * this._scaledCellWidth + this._scaledCharLeft,
(y + 0.5) * this._scaledCellHeight + this._scaledCharTop);
y * this._scaledCellHeight + this._scaledCharTop + this._scaledCharHeight / 2);
this._ctx.restore();
}
+1
View File
@@ -85,6 +85,7 @@ export class Renderer extends EventEmitter implements IRenderer {
this._isPaused = entry.intersectionRatio === 0;
if (!this._isPaused && this._needsFullRefresh) {
this._terminal.refresh(0, this._terminal.rows - 1);
this._needsFullRefresh = false;
}
}
+14 -3
View File
@@ -9,7 +9,7 @@ import { ITheme } from 'xterm';
import { EventEmitter } from '../../common/EventEmitter';
import { ColorManager } from '../ColorManager';
import { RenderDebouncer } from '../../ui/RenderDebouncer';
import { BOLD_CLASS, ITALIC_CLASS, CURSOR_CLASS, CURSOR_STYLE_BLOCK_CLASS, CURSOR_STYLE_BAR_CLASS, CURSOR_STYLE_UNDERLINE_CLASS, DomRendererRowFactory } from './DomRendererRowFactory';
import { BOLD_CLASS, ITALIC_CLASS, CURSOR_CLASS, CURSOR_STYLE_BLOCK_CLASS, CURSOR_BLINK_CLASS, CURSOR_STYLE_BAR_CLASS, CURSOR_STYLE_UNDERLINE_CLASS, DomRendererRowFactory } from './DomRendererRowFactory';
import { INVERTED_DEFAULT_COLOR } from '../atlas/Types';
const TERMINAL_CLASS_PREFIX = 'xterm-dom-renderer-owner-';
@@ -75,7 +75,7 @@ export class DomRenderer extends EventEmitter implements IRenderer {
this._updateDimensions();
this._renderDebouncer = new RenderDebouncer(this._terminal, this._renderRows.bind(this));
this._rowFactory = new DomRendererRowFactory(document);
this._rowFactory = new DomRendererRowFactory(_terminal.options, document);
this._terminal.element.classList.add(TERMINAL_CLASS_PREFIX + this._terminalClass);
this._terminal.screenElement.appendChild(this._rowContainer);
@@ -165,12 +165,22 @@ export class DomRenderer extends EventEmitter implements IRenderer {
`${this._terminalSelector} span.${ITALIC_CLASS} {` +
` font-style: italic;` +
`}`;
// Blink animation
styles +=
`@keyframes blink {` +
` 0 % { opacity: 1.0; }` +
` 50% { opacity: 0.0; }` +
` 100 % { opacity: 1.0; }` +
`}`;
// Cursor
styles +=
`${this._terminalSelector} .${ROW_CONTAINER_CLASS}:not(.${FOCUS_CLASS}) .${CURSOR_CLASS} {` +
` outline: 1px solid ${this.colorManager.colors.cursor.css};` +
` outline-offset: -1px;` +
`}` +
`${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${CURSOR_CLASS}.${CURSOR_BLINK_CLASS} {` +
` animation: blink 1s step-end infinite;` +
`}` +
`${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${CURSOR_CLASS}.${CURSOR_STYLE_BLOCK_CLASS} {` +
` background-color: ${this.colorManager.colors.cursor.css};` +
` color: ${this.colorManager.colors.cursorAccent.css};` +
@@ -328,6 +338,7 @@ export class DomRenderer extends EventEmitter implements IRenderer {
const cursorAbsoluteY = terminal.buffer.ybase + terminal.buffer.y;
const cursorX = this._terminal.buffer.x;
const cursorBlink = this._terminal.options.cursorBlink;
for (let y = start; y <= end; y++) {
const rowElement = this._rowElements[y];
@@ -336,7 +347,7 @@ export class DomRenderer extends EventEmitter implements IRenderer {
const row = y + terminal.buffer.ydisp;
const lineData = terminal.buffer.lines.get(row);
const cursorStyle = terminal.options.cursorStyle;
rowElement.appendChild(this._rowFactory.createRow(lineData, row === cursorAbsoluteY, cursorStyle, cursorX, this.dimensions.actualCellWidth, terminal.cols));
rowElement.appendChild(this._rowFactory.createRow(lineData, row === cursorAbsoluteY, cursorStyle, cursorX, cursorBlink, this.dimensions.actualCellWidth, terminal.cols));
}
this._terminal.emit('refresh', {start, end});
+26 -14
View File
@@ -9,23 +9,28 @@ import { DomRendererRowFactory } from './DomRendererRowFactory';
import { DEFAULT_ATTR, NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR } from '../../Buffer';
import { FLAGS } from '../Types';
import { BufferLine } from '../../BufferLine';
import { IBufferLine } from '../../Types';
import { IBufferLine, ITerminalOptions } from '../../Types';
import { DEFAULT_COLOR } from '../atlas/Types';
describe('DomRendererRowFactory', () => {
let dom: jsdom.JSDOM;
const options: ITerminalOptions = {};
let rowFactory: DomRendererRowFactory;
let lineData: IBufferLine;
beforeEach(() => {
dom = new jsdom.JSDOM('');
rowFactory = new DomRendererRowFactory(dom.window.document);
options.enableBold = true;
options.drawBoldTextInBrightColors = true;
rowFactory = new DomRendererRowFactory(options, dom.window.document);
lineData = createEmptyLineData(2);
});
describe('createRow', () => {
it('should not create anything for an empty row', () => {
const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20);
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
''
);
@@ -35,7 +40,7 @@ describe('DomRendererRowFactory', () => {
lineData.set(0, [DEFAULT_ATTR, '語', 2, '語'.charCodeAt(0)]);
// There should be no element for the following "empty" cell
lineData.set(1, [DEFAULT_ATTR, '', 0, undefined]);
const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20);
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
'<span style="width: 10px;">語</span>'
);
@@ -43,17 +48,24 @@ describe('DomRendererRowFactory', () => {
it('should add class for cursor and cursor style', () => {
for (const style of ['block', 'bar', 'underline']) {
const fragment = rowFactory.createRow(lineData, true, style, 0, 5, 20);
const fragment = rowFactory.createRow(lineData, true, style, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
`<span class="xterm-cursor xterm-cursor-${style}"> </span>`
);
}
});
it('should add class for cursor blink', () => {
const fragment = rowFactory.createRow(lineData, true, 'block', 0, true, 5, 20);
assert.equal(getFragmentHtml(fragment),
`<span class="xterm-cursor xterm-cursor-blink xterm-cursor-block"> </span>`
);
});
it('should not render cells that go beyond the terminal\'s columns', () => {
lineData.set(0, [DEFAULT_ATTR, 'a', 1, 'a'.charCodeAt(0)]);
lineData.set(1, [DEFAULT_ATTR, 'b', 1, 'b'.charCodeAt(0)]);
const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 1);
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 1);
assert.equal(getFragmentHtml(fragment),
'<span>a</span>'
);
@@ -62,7 +74,7 @@ describe('DomRendererRowFactory', () => {
describe('attributes', () => {
it('should add class for bold', () => {
lineData.set(0, [DEFAULT_ATTR | (FLAGS.BOLD << 18), 'a', 1, 'a'.charCodeAt(0)]);
const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20);
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
'<span class="xterm-bold">a</span>'
);
@@ -70,7 +82,7 @@ describe('DomRendererRowFactory', () => {
it('should add class for italic', () => {
lineData.set(0, [DEFAULT_ATTR | (FLAGS.ITALIC << 18), 'a', 1, 'a'.charCodeAt(0)]);
const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20);
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
'<span class="xterm-italic">a</span>'
);
@@ -80,7 +92,7 @@ describe('DomRendererRowFactory', () => {
const defaultAttrNoFgColor = (0 << 9) | (DEFAULT_COLOR << 0);
for (let i = 0; i < 256; i++) {
lineData.set(0, [defaultAttrNoFgColor | (i << 9), 'a', 1, 'a'.charCodeAt(0)]);
const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20);
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
`<span class="xterm-fg-${i}">a</span>`
);
@@ -91,7 +103,7 @@ describe('DomRendererRowFactory', () => {
const defaultAttrNoBgColor = (DEFAULT_ATTR << 9) | (0 << 0);
for (let i = 0; i < 256; i++) {
lineData.set(0, [defaultAttrNoBgColor | (i << 0), 'a', 1, 'a'.charCodeAt(0)]);
const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20);
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
`<span class="xterm-bg-${i}">a</span>`
);
@@ -100,7 +112,7 @@ describe('DomRendererRowFactory', () => {
it('should correctly invert colors', () => {
lineData.set(0, [(FLAGS.INVERSE << 18) | (2 << 9) | (1 << 0), 'a', 1, 'a'.charCodeAt(0)]);
const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20);
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
'<span class="xterm-fg-1 xterm-bg-2">a</span>'
);
@@ -108,7 +120,7 @@ describe('DomRendererRowFactory', () => {
it('should correctly invert default fg color', () => {
lineData.set(0, [(FLAGS.INVERSE << 18) | (DEFAULT_ATTR << 9) | (1 << 0), 'a', 1, 'a'.charCodeAt(0)]);
const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20);
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
'<span class="xterm-fg-1 xterm-bg-257">a</span>'
);
@@ -116,7 +128,7 @@ describe('DomRendererRowFactory', () => {
it('should correctly invert default bg color', () => {
lineData.set(0, [(FLAGS.INVERSE << 18) | (1 << 9) | (DEFAULT_COLOR << 0), 'a', 1, 'a'.charCodeAt(0)]);
const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20);
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
'<span class="xterm-fg-257 xterm-bg-1">a</span>'
);
@@ -125,7 +137,7 @@ describe('DomRendererRowFactory', () => {
it('should turn bold fg text bright', () => {
for (let i = 0; i < 8; i++) {
lineData.set(0, [(FLAGS.BOLD << 18) | (i << 9) | (DEFAULT_COLOR << 0), 'a', 1, 'a'.charCodeAt(0)]);
const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20);
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
`<span class="xterm-bold xterm-fg-${i + 8}">a</span>`
);
+10 -4
View File
@@ -5,23 +5,25 @@
import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_ATTR_INDEX, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CODE_INDEX, NULL_CELL_CODE, WHITESPACE_CELL_CHAR } from '../../Buffer';
import { FLAGS } from '../Types';
import { IBufferLine } from '../../Types';
import { IBufferLine, ITerminalOptions } from '../../Types';
import { DEFAULT_COLOR, INVERTED_DEFAULT_COLOR } from '../atlas/Types';
export const BOLD_CLASS = 'xterm-bold';
export const ITALIC_CLASS = 'xterm-italic';
export const CURSOR_CLASS = 'xterm-cursor';
export const CURSOR_BLINK_CLASS = 'xterm-cursor-blink';
export const CURSOR_STYLE_BLOCK_CLASS = 'xterm-cursor-block';
export const CURSOR_STYLE_BAR_CLASS = 'xterm-cursor-bar';
export const CURSOR_STYLE_UNDERLINE_CLASS = 'xterm-cursor-underline';
export class DomRendererRowFactory {
constructor(
private _terminalOptions: ITerminalOptions,
private _document: Document
) {
}
public createRow(lineData: IBufferLine, isCursorRow: boolean, cursorStyle: string | undefined, cursorX: number, cellWidth: number, cols: number): DocumentFragment {
public createRow(lineData: IBufferLine, isCursorRow: boolean, cursorStyle: string | undefined, cursorX: number, cursorBlink: boolean, cellWidth: number, cols: number): DocumentFragment {
const fragment = this._document.createDocumentFragment();
// Find the line length first, this prevents the need to output a bunch of
@@ -62,6 +64,10 @@ export class DomRendererRowFactory {
if (isCursorRow && x === cursorX) {
charElement.classList.add(CURSOR_CLASS);
if (cursorBlink) {
charElement.classList.add(CURSOR_BLINK_CLASS);
}
switch (cursorStyle) {
case 'bar':
charElement.classList.add(CURSOR_STYLE_BAR_CLASS);
@@ -88,10 +94,10 @@ export class DomRendererRowFactory {
}
}
if (flags & FLAGS.BOLD) {
if (flags & FLAGS.BOLD && this._terminalOptions.enableBold) {
// Convert the FG color to the bold variant. This should not happen when
// the fg is the inverse default color as there is no bold variant.
if (fg < 8) {
if (fg < 8 && this._terminalOptions.drawBoldTextInBrightColors) {
fg += 8;
}
charElement.classList.add(BOLD_CLASS);
+15
View File
@@ -0,0 +1,15 @@
{
"compilerOptions": {
"target": "es5",
"lib": [ "es5" ],
"rootDir": ".",
"sourceMap": true,
"removeComments": true,
"pretty": true,
"incremental": true,
"skipLibCheck": true
}
}
+11
View File
@@ -0,0 +1,11 @@
{
"extends": "./tsconfig-base.json",
"compilerOptions": {
"types": [
"../../node_modules/@types/mocha",
"../../"
],
"composite": true,
"strict": true
}
}
+16
View File
@@ -0,0 +1,16 @@
{
"files": [],
"include": [],
"references": [
{ "path": "." },
{ "path": "./addons/attach" },
{ "path": "./addons/fit" },
{ "path": "./addons/fullscreen" },
{ "path": "./addons/search" },
{ "path": "./addons/terminado" },
{ "path": "./addons/webLinks" },
{ "path": "./addons/winptyCompat" },
{ "path": "./addons/zmodem" }
]
}
+30
View File
@@ -0,0 +1,30 @@
{
"extends": "./tsconfig-base",
"compilerOptions": {
"module": "commonjs",
"lib": [
"dom",
"es5",
"es6",
"scripthost",
"es2015.promise"
],
"rootDir": ".",
"outDir": "../lib",
"noUnusedLocals": true,
"noImplicitAny": true
},
"include": [
"./**/*",
"../typings/xterm.d.ts"
],
"exclude": [
"./addons/**/*"
],
"references": [
{ "path": "./common" },
{ "path": "./core" }
]
}
+26 -2
View File
@@ -23,11 +23,13 @@ export class MouseZoneManager extends Disposable implements IMouseZoneManager {
private _areZonesActive: boolean = false;
private _mouseMoveListener: (e: MouseEvent) => any;
private _mouseLeaveListener: (e: MouseEvent) => any;
private _clickListener: (e: MouseEvent) => any;
private _tooltipTimeout: number = null;
private _currentZone: IMouseZone = null;
private _lastHoverCoords: [number, number] = [null, null];
private _initialSelectionLength: number;
constructor(
private _terminal: ITerminal
@@ -38,6 +40,7 @@ export class MouseZoneManager extends Disposable implements IMouseZoneManager {
// These events are expensive, only listen to it when mouse zones are active
this._mouseMoveListener = e => this._onMouseMove(e);
this._mouseLeaveListener = e => this._onMouseLeave(e);
this._clickListener = e => this._onClick(e);
}
@@ -89,6 +92,7 @@ export class MouseZoneManager extends Disposable implements IMouseZoneManager {
if (!this._areZonesActive) {
this._areZonesActive = true;
this._terminal.element.addEventListener('mousemove', this._mouseMoveListener);
this._terminal.element.addEventListener('mouseleave', this._mouseLeaveListener);
this._terminal.element.addEventListener('click', this._clickListener);
}
}
@@ -97,6 +101,7 @@ export class MouseZoneManager extends Disposable implements IMouseZoneManager {
if (this._areZonesActive) {
this._areZonesActive = false;
this._terminal.element.removeEventListener('mousemove', this._mouseMoveListener);
this._terminal.element.removeEventListener('mouseleave', this._mouseLeaveListener);
this._terminal.element.removeEventListener('click', this._clickListener);
}
}
@@ -153,6 +158,10 @@ export class MouseZoneManager extends Disposable implements IMouseZoneManager {
}
private _onMouseDown(e: MouseEvent): void {
// Store current terminal selection length, to check if we're performing
// a selection operation
this._initialSelectionLength = this._terminal.getSelection().length;
// Ignore the event if there are no zones active
if (!this._areZonesActive) {
return;
@@ -169,10 +178,25 @@ export class MouseZoneManager extends Disposable implements IMouseZoneManager {
}
}
private _onMouseLeave(e: MouseEvent): void {
// Fire the hover end callback and cancel any existing timer if the mouse
// leaves the terminal element
if (this._currentZone) {
this._currentZone.leaveCallback();
this._currentZone = null;
if (this._tooltipTimeout) {
clearTimeout(this._tooltipTimeout);
}
}
}
private _onClick(e: MouseEvent): void {
// Find the active zone and click it if found
// Find the active zone and click it if found and no selection was
// being performed
const zone = this._findZoneEventAt(e);
if (zone) {
const currentSelectionLength = this._terminal.getSelection().length;
if (zone && currentSelectionLength === this._initialSelectionLength) {
zone.clickCallback(e);
e.preventDefault();
e.stopImmediatePropagation();