Move files not in ui project back to root
This commit is contained in:
@@ -1,54 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2016 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import jsdom = require('jsdom');
|
||||
import { ICharMeasure } from '../Types';
|
||||
import { assert } from 'chai';
|
||||
import { CharMeasure } from './CharMeasure';
|
||||
|
||||
describe('CharMeasure', () => {
|
||||
let dom: jsdom.JSDOM;
|
||||
let window: Window;
|
||||
let document: Document;
|
||||
let container: HTMLElement;
|
||||
let charMeasure: ICharMeasure;
|
||||
|
||||
beforeEach(() => {
|
||||
dom = new jsdom.JSDOM('');
|
||||
window = dom.window;
|
||||
document = window.document;
|
||||
container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
charMeasure = new CharMeasure(document, container);
|
||||
});
|
||||
|
||||
describe('measure', () => {
|
||||
it('should have _measureElement', () => {
|
||||
assert.isDefined((<any>charMeasure)._measureElement, 'new CharMeasure() should have created _measureElement');
|
||||
});
|
||||
|
||||
it('should be performed sync', () => {
|
||||
// Mock getBoundingClientRect since jsdom doesn't have a layout engine
|
||||
(<any>charMeasure)._measureElement.getBoundingClientRect = () => {
|
||||
return { width: 1, height: 1 };
|
||||
};
|
||||
charMeasure.measure({});
|
||||
assert.equal(charMeasure.height, 1);
|
||||
assert.equal(charMeasure.width, 1);
|
||||
});
|
||||
|
||||
it('should NOT do a measure when the parent is hidden', done => {
|
||||
charMeasure.measure({});
|
||||
setTimeout(() => {
|
||||
const firstWidth = charMeasure.width;
|
||||
container.style.display = 'none';
|
||||
container.style.fontSize = '2em';
|
||||
charMeasure.measure({});
|
||||
assert.equal(charMeasure.width, firstWidth);
|
||||
done();
|
||||
}, 0);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,58 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2016 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { ICharMeasure, ITerminalOptions } from '../Types';
|
||||
import { EventEmitter2, IEvent } from '../common/EventEmitter2';
|
||||
|
||||
/**
|
||||
* Utility class that measures the size of a character. Measurements are done in
|
||||
* the DOM rather than with a canvas context because support for extracting the
|
||||
* height of characters is patchy across browsers.
|
||||
*/
|
||||
export class CharMeasure implements ICharMeasure {
|
||||
private _document: Document;
|
||||
private _parentElement: HTMLElement;
|
||||
private _measureElement: HTMLElement;
|
||||
private _width: number;
|
||||
private _height: number;
|
||||
|
||||
private _onCharSizeChanged = new EventEmitter2<void>();
|
||||
public get onCharSizeChanged(): IEvent<void> { return this._onCharSizeChanged.event; }
|
||||
|
||||
constructor(document: Document, parentElement: HTMLElement) {
|
||||
this._document = document;
|
||||
this._parentElement = parentElement;
|
||||
this._measureElement = this._document.createElement('span');
|
||||
this._measureElement.classList.add('xterm-char-measure-element');
|
||||
this._measureElement.textContent = 'W';
|
||||
this._measureElement.setAttribute('aria-hidden', 'true');
|
||||
this._parentElement.appendChild(this._measureElement);
|
||||
}
|
||||
|
||||
public get width(): number {
|
||||
return this._width;
|
||||
}
|
||||
|
||||
public get height(): number {
|
||||
return this._height;
|
||||
}
|
||||
|
||||
public measure(options: ITerminalOptions): void {
|
||||
this._measureElement.style.fontFamily = options.fontFamily;
|
||||
this._measureElement.style.fontSize = `${options.fontSize}px`;
|
||||
const geometry = this._measureElement.getBoundingClientRect();
|
||||
// The element is likely currently display:none, we should retain the
|
||||
// previous value.
|
||||
if (geometry.width === 0 || geometry.height === 0) {
|
||||
return;
|
||||
}
|
||||
const adjustedHeight = Math.ceil(geometry.height);
|
||||
if (this._width !== geometry.width || this._height !== adjustedHeight) {
|
||||
this._width = geometry.width;
|
||||
this._height = adjustedHeight;
|
||||
this._onCharSizeChanged.fire();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2016 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { assert } from 'chai';
|
||||
import * as Clipboard from './Clipboard';
|
||||
|
||||
describe('evaluatePastedTextProcessing', () => {
|
||||
it('should replace carriage return and/or line feed with carriage return', () => {
|
||||
const pastedText = {
|
||||
unix: 'foo\nbar\n',
|
||||
windows: 'foo\r\nbar\r\n'
|
||||
};
|
||||
|
||||
const processedText = {
|
||||
unix: Clipboard.prepareTextForTerminal(pastedText.unix),
|
||||
windows: Clipboard.prepareTextForTerminal(pastedText.windows)
|
||||
};
|
||||
|
||||
assert.equal(processedText.unix, 'foo\rbar\r');
|
||||
assert.equal(processedText.windows, 'foo\rbar\r');
|
||||
});
|
||||
it('should bracket pasted text in bracketedPasteMode', () => {
|
||||
const pastedText = 'foo bar';
|
||||
const unbracketedText = Clipboard.bracketTextForPaste(pastedText, false);
|
||||
const bracketedText = Clipboard.bracketTextForPaste(pastedText, true);
|
||||
|
||||
assert.equal(unbracketedText, 'foo bar');
|
||||
assert.equal(bracketedText, '\x1b[200~foo bar\x1b[201~');
|
||||
});
|
||||
});
|
||||
@@ -1,134 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2016 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { ITerminal, ISelectionManager } from '../Types';
|
||||
|
||||
interface IWindow extends Window {
|
||||
clipboardData?: {
|
||||
getData(format: string): string;
|
||||
setData(format: string, data: string): void;
|
||||
};
|
||||
}
|
||||
|
||||
declare var window: IWindow;
|
||||
|
||||
/**
|
||||
* Prepares text to be pasted into the terminal by normalizing the line endings
|
||||
* @param text The pasted text that needs processing before inserting into the terminal
|
||||
*/
|
||||
export function prepareTextForTerminal(text: string): string {
|
||||
return text.replace(/\r?\n/g, '\r');
|
||||
}
|
||||
|
||||
/**
|
||||
* Bracket text for paste, if necessary, as per https://cirw.in/blog/bracketed-paste
|
||||
* @param text The pasted text to bracket
|
||||
*/
|
||||
export function bracketTextForPaste(text: string, bracketedPasteMode: boolean): string {
|
||||
if (bracketedPasteMode) {
|
||||
return '\x1b[200~' + text + '\x1b[201~';
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds copy functionality to the given terminal.
|
||||
* @param ev The original copy event to be handled
|
||||
*/
|
||||
export function copyHandler(ev: ClipboardEvent, term: ITerminal, selectionManager: ISelectionManager): void {
|
||||
if (term.browser.isMSIE) {
|
||||
window.clipboardData.setData('Text', selectionManager.selectionText);
|
||||
} else {
|
||||
ev.clipboardData.setData('text/plain', selectionManager.selectionText);
|
||||
}
|
||||
|
||||
// Prevent or the original text will be copied.
|
||||
ev.preventDefault();
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect the clipboard's data to the terminal's input handler.
|
||||
* @param ev The original paste event to be handled
|
||||
* @param term The terminal on which to apply the handled paste event
|
||||
*/
|
||||
export function pasteHandler(ev: ClipboardEvent, term: ITerminal): void {
|
||||
ev.stopPropagation();
|
||||
|
||||
let text: string;
|
||||
|
||||
const dispatchPaste = function(text: string): void {
|
||||
text = prepareTextForTerminal(text);
|
||||
text = bracketTextForPaste(text, term.bracketedPasteMode);
|
||||
term.handler(text);
|
||||
term.textarea.value = '';
|
||||
term.emit('paste', text);
|
||||
term.cancel(ev);
|
||||
};
|
||||
|
||||
if (term.browser.isMSIE) {
|
||||
if (window.clipboardData) {
|
||||
text = window.clipboardData.getData('Text');
|
||||
dispatchPaste(text);
|
||||
}
|
||||
} else {
|
||||
if (ev.clipboardData) {
|
||||
text = ev.clipboardData.getData('text/plain');
|
||||
dispatchPaste(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the textarea under the mouse cursor and focuses it.
|
||||
* @param ev The original right click event to be handled.
|
||||
* @param textarea The terminal's textarea.
|
||||
*/
|
||||
export function moveTextAreaUnderMouseCursor(ev: MouseEvent, term: ITerminal): void {
|
||||
|
||||
// Calculate textarea position relative to the screen element
|
||||
const pos = term.screenElement.getBoundingClientRect();
|
||||
const left = ev.clientX - pos.left - 10;
|
||||
const top = ev.clientY - pos.top - 10;
|
||||
|
||||
// Bring textarea at the cursor position
|
||||
term.textarea.style.position = 'absolute';
|
||||
term.textarea.style.width = '20px';
|
||||
term.textarea.style.height = '20px';
|
||||
term.textarea.style.left = `${left}px`;
|
||||
term.textarea.style.top = `${top}px`;
|
||||
term.textarea.style.zIndex = '1000';
|
||||
|
||||
term.textarea.focus();
|
||||
|
||||
// Reset the terminal textarea's styling
|
||||
// Timeout needs to be long enough for click event to be handled.
|
||||
setTimeout(() => {
|
||||
term.textarea.style.position = null;
|
||||
term.textarea.style.width = null;
|
||||
term.textarea.style.height = null;
|
||||
term.textarea.style.left = null;
|
||||
term.textarea.style.top = null;
|
||||
term.textarea.style.zIndex = null;
|
||||
}, 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind to right-click event and allow right-click copy and paste.
|
||||
* @param ev The original right click event to be handled.
|
||||
* @param textarea The terminal's textarea.
|
||||
* @param selectionManager The terminal's selection manager.
|
||||
* @param shouldSelectWord If true and there is no selection the current word will be selected
|
||||
*/
|
||||
export function rightClickHandler(ev: MouseEvent, term: ITerminal, selectionManager: ISelectionManager, shouldSelectWord: boolean): void {
|
||||
moveTextAreaUnderMouseCursor(ev, term);
|
||||
|
||||
if (shouldSelectWord && !selectionManager.isClickInSelection(ev)) {
|
||||
selectionManager.selectWordAtCursor(ev);
|
||||
}
|
||||
|
||||
// Get textarea ready to copy from the context menu
|
||||
term.textarea.value = selectionManager.selectionText;
|
||||
term.textarea.select();
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import jsdom = require('jsdom');
|
||||
import { assert } from 'chai';
|
||||
import { MouseHelper } from './MouseHelper';
|
||||
import { MockCharMeasure, MockRenderer } from './TestUtils.test';
|
||||
|
||||
const CHAR_WIDTH = 10;
|
||||
const CHAR_HEIGHT = 20;
|
||||
|
||||
describe('MouseHelper.getCoords', () => {
|
||||
let dom: jsdom.JSDOM;
|
||||
let window: Window;
|
||||
let document: Document;
|
||||
let mouseHelper: MouseHelper;
|
||||
|
||||
let charMeasure: MockCharMeasure;
|
||||
|
||||
beforeEach(() => {
|
||||
dom = new jsdom.JSDOM('');
|
||||
window = dom.window;
|
||||
document = window.document;
|
||||
charMeasure = new MockCharMeasure();
|
||||
charMeasure.width = CHAR_WIDTH;
|
||||
charMeasure.height = CHAR_HEIGHT;
|
||||
const renderer = new MockRenderer();
|
||||
renderer.dimensions = <any>{
|
||||
actualCellWidth: CHAR_WIDTH,
|
||||
actualCellHeight: CHAR_HEIGHT
|
||||
};
|
||||
mouseHelper = new MouseHelper(renderer);
|
||||
});
|
||||
|
||||
describe('when charMeasure is not initialized', () => {
|
||||
it('should return null', () => {
|
||||
charMeasure = new MockCharMeasure();
|
||||
assert.equal(mouseHelper.getCoords({ clientX: 0, clientY: 0 }, document.createElement('div'), charMeasure, 10, 10), null);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the cell that was clicked', () => {
|
||||
let coords: [number, number];
|
||||
coords = mouseHelper.getCoords({ clientX: CHAR_WIDTH / 2, clientY: CHAR_HEIGHT / 2 }, document.createElement('div'), charMeasure, 10, 10);
|
||||
assert.deepEqual(coords, [1, 1]);
|
||||
coords = mouseHelper.getCoords({ clientX: CHAR_WIDTH, clientY: CHAR_HEIGHT }, document.createElement('div'), charMeasure, 10, 10);
|
||||
assert.deepEqual(coords, [1, 1]);
|
||||
coords = mouseHelper.getCoords({ clientX: CHAR_WIDTH, clientY: CHAR_HEIGHT + 1 }, document.createElement('div'), charMeasure, 10, 10);
|
||||
assert.deepEqual(coords, [1, 2]);
|
||||
coords = mouseHelper.getCoords({ clientX: CHAR_WIDTH + 1, clientY: CHAR_HEIGHT }, document.createElement('div'), charMeasure, 10, 10);
|
||||
assert.deepEqual(coords, [2, 1]);
|
||||
});
|
||||
|
||||
it('should ensure the coordinates are returned within the terminal bounds', () => {
|
||||
let coords: [number, number];
|
||||
coords = mouseHelper.getCoords({ clientX: -1, clientY: -1 }, document.createElement('div'), charMeasure, 10, 10);
|
||||
assert.deepEqual(coords, [1, 1]);
|
||||
// Event are double the cols/rows
|
||||
coords = mouseHelper.getCoords({ clientX: CHAR_WIDTH * 20, clientY: CHAR_HEIGHT * 20 }, document.createElement('div'), charMeasure, 10, 10);
|
||||
assert.deepEqual(coords, [10, 10], 'coordinates should never come back as larger than the terminal');
|
||||
});
|
||||
});
|
||||
@@ -1,78 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { ICharMeasure, IMouseHelper } from '../Types';
|
||||
import { IRenderer } from '../renderer/Types';
|
||||
|
||||
export class MouseHelper implements IMouseHelper {
|
||||
constructor(private _renderer: IRenderer) {}
|
||||
|
||||
public setRenderer(renderer: IRenderer): void {
|
||||
this._renderer = renderer;
|
||||
}
|
||||
|
||||
public static getCoordsRelativeToElement(event: {clientX: number, clientY: number}, element: HTMLElement): [number, number] {
|
||||
const rect = element.getBoundingClientRect();
|
||||
return [event.clientX - rect.left, event.clientY - rect.top];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets coordinates within the terminal for a particular mouse event. The result
|
||||
* is returned as an array in the form [x, y] instead of an object as it's a
|
||||
* little faster and this function is used in some low level code.
|
||||
* @param event The mouse event.
|
||||
* @param element The terminal's container element.
|
||||
* @param charMeasure The char measure object used to determine character sizes.
|
||||
* @param colCount The number of columns in the terminal.
|
||||
* @param rowCount The number of rows n the terminal.
|
||||
* @param isSelection Whether the request is for the selection or not. This will
|
||||
* apply an offset to the x value such that the left half of the cell will
|
||||
* select that cell and the right half will select the next cell.
|
||||
*/
|
||||
public getCoords(event: {clientX: number, clientY: number}, element: HTMLElement, charMeasure: ICharMeasure, colCount: number, rowCount: number, isSelection?: boolean): [number, number] {
|
||||
// Coordinates cannot be measured if charMeasure has not been initialized
|
||||
if (!charMeasure.width || !charMeasure.height) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const coords = MouseHelper.getCoordsRelativeToElement(event, element);
|
||||
if (!coords) {
|
||||
return null;
|
||||
}
|
||||
|
||||
coords[0] = Math.ceil((coords[0] + (isSelection ? this._renderer.dimensions.actualCellWidth / 2 : 0)) / this._renderer.dimensions.actualCellWidth);
|
||||
coords[1] = Math.ceil(coords[1] / this._renderer.dimensions.actualCellHeight);
|
||||
|
||||
// Ensure coordinates are within the terminal viewport. Note that selections
|
||||
// need an addition point of precision to cover the end point (as characters
|
||||
// cover half of one char and half of the next).
|
||||
coords[0] = Math.min(Math.max(coords[0], 1), colCount + (isSelection ? 1 : 0));
|
||||
coords[1] = Math.min(Math.max(coords[1], 1), rowCount);
|
||||
|
||||
return coords;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets coordinates within the terminal for a particular mouse event, wrapping
|
||||
* them to the bounds of the terminal and adding 32 to both the x and y values
|
||||
* as expected by xterm.
|
||||
* @param event The mouse event.
|
||||
* @param element The terminal's container element.
|
||||
* @param charMeasure The char measure object used to determine character sizes.
|
||||
* @param colCount The number of columns in the terminal.
|
||||
* @param rowCount The number of rows in the terminal.
|
||||
*/
|
||||
public getRawByteCoords(event: MouseEvent, element: HTMLElement, charMeasure: ICharMeasure, colCount: number, rowCount: number): { x: number, y: number } {
|
||||
const coords = this.getCoords(event, element, charMeasure, colCount, rowCount);
|
||||
let x = coords[0];
|
||||
let y = coords[1];
|
||||
|
||||
// xterm sends raw bytes and starts at 32 (SP) for each.
|
||||
x += 32;
|
||||
y += 32;
|
||||
|
||||
return { x, y };
|
||||
}
|
||||
}
|
||||
@@ -1,246 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { ITerminal } from '../Types';
|
||||
import { IMouseZoneManager, IMouseZone } from './Types';
|
||||
import { Disposable } from '../common/Lifecycle';
|
||||
import { addDisposableDomListener } from './Lifecycle';
|
||||
|
||||
const HOVER_DURATION = 500;
|
||||
|
||||
/**
|
||||
* The MouseZoneManager allows components to register zones within the terminal
|
||||
* that trigger hover and click callbacks.
|
||||
*
|
||||
* This class was intentionally made not so robust initially as the only case it
|
||||
* needed to support was single-line links which never overlap. Improvements can
|
||||
* be made in the future.
|
||||
*/
|
||||
export class MouseZoneManager extends Disposable implements IMouseZoneManager {
|
||||
private _zones: IMouseZone[] = [];
|
||||
|
||||
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
|
||||
) {
|
||||
super();
|
||||
|
||||
this.register(addDisposableDomListener(this._terminal.element, 'mousedown', e => this._onMouseDown(e)));
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
this._deactivate();
|
||||
}
|
||||
|
||||
public add(zone: IMouseZone): void {
|
||||
this._zones.push(zone);
|
||||
if (this._zones.length === 1) {
|
||||
this._activate();
|
||||
}
|
||||
}
|
||||
|
||||
public clearAll(start?: number, end?: number): void {
|
||||
// Exit if there's nothing to clear
|
||||
if (this._zones.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear all if start/end weren't set
|
||||
if (!end) {
|
||||
start = 0;
|
||||
end = this._terminal.rows - 1;
|
||||
}
|
||||
|
||||
// Iterate through zones and clear them out if they're within the range
|
||||
for (let i = 0; i < this._zones.length; i++) {
|
||||
const zone = this._zones[i];
|
||||
if ((zone.y1 > start && zone.y1 <= end + 1) ||
|
||||
(zone.y2 > start && zone.y2 <= end + 1) ||
|
||||
(zone.y1 < start && zone.y2 > end + 1)) {
|
||||
if (this._currentZone && this._currentZone === zone) {
|
||||
this._currentZone.leaveCallback();
|
||||
this._currentZone = null;
|
||||
}
|
||||
this._zones.splice(i--, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Deactivate the mouse zone manager if all the zones have been removed
|
||||
if (this._zones.length === 0) {
|
||||
this._deactivate();
|
||||
}
|
||||
}
|
||||
|
||||
private _activate(): void {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
private _deactivate(): void {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
private _onMouseMove(e: MouseEvent): void {
|
||||
// TODO: Ideally this would only clear the hover state when the mouse moves
|
||||
// outside of the mouse zone
|
||||
if (this._lastHoverCoords[0] !== e.pageX || this._lastHoverCoords[1] !== e.pageY) {
|
||||
this._onHover(e);
|
||||
// Record the current coordinates
|
||||
this._lastHoverCoords = [e.pageX, e.pageY];
|
||||
}
|
||||
}
|
||||
|
||||
private _onHover(e: MouseEvent): void {
|
||||
const zone = this._findZoneEventAt(e);
|
||||
|
||||
// Do nothing if the zone is the same
|
||||
if (zone === this._currentZone) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Fire the hover end callback and cancel any existing timer if a new zone
|
||||
// is being hovered
|
||||
if (this._currentZone) {
|
||||
this._currentZone.leaveCallback();
|
||||
this._currentZone = null;
|
||||
if (this._tooltipTimeout) {
|
||||
clearTimeout(this._tooltipTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
// Exit if there is not zone
|
||||
if (!zone) {
|
||||
return;
|
||||
}
|
||||
this._currentZone = zone;
|
||||
|
||||
// Trigger the hover callback
|
||||
if (zone.hoverCallback) {
|
||||
zone.hoverCallback(e);
|
||||
}
|
||||
|
||||
// Restart the tooltip timeout
|
||||
this._tooltipTimeout = <number><any>setTimeout(() => this._onTooltip(e), HOVER_DURATION);
|
||||
}
|
||||
|
||||
private _onTooltip(e: MouseEvent): void {
|
||||
this._tooltipTimeout = null;
|
||||
const zone = this._findZoneEventAt(e);
|
||||
if (zone && zone.tooltipCallback) {
|
||||
zone.tooltipCallback(e);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Find the active zone, prevent event propagation if found to prevent other
|
||||
// components from handling the mouse event.
|
||||
const zone = this._findZoneEventAt(e);
|
||||
if (zone) {
|
||||
if (zone.willLinkActivate(e)) {
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 and no selection was
|
||||
// being performed
|
||||
const zone = this._findZoneEventAt(e);
|
||||
const currentSelectionLength = this._terminal.getSelection().length;
|
||||
|
||||
if (zone && currentSelectionLength === this._initialSelectionLength) {
|
||||
zone.clickCallback(e);
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
}
|
||||
}
|
||||
|
||||
private _findZoneEventAt(e: MouseEvent): IMouseZone {
|
||||
const coords = this._terminal.mouseHelper.getCoords(e, this._terminal.screenElement, this._terminal.charMeasure, this._terminal.cols, this._terminal.rows);
|
||||
if (!coords) {
|
||||
return null;
|
||||
}
|
||||
const x = coords[0];
|
||||
const y = coords[1];
|
||||
for (let i = 0; i < this._zones.length; i++) {
|
||||
const zone = this._zones[i];
|
||||
if (zone.y1 === zone.y2) {
|
||||
// Single line link
|
||||
if (y === zone.y1 && x >= zone.x1 && x < zone.x2) {
|
||||
return zone;
|
||||
}
|
||||
} else {
|
||||
// Multi-line link
|
||||
if ((y === zone.y1 && x >= zone.x1) ||
|
||||
(y === zone.y2 && x < zone.x2) ||
|
||||
(y > zone.y1 && y < zone.y2)) {
|
||||
return zone;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export class MouseZone implements IMouseZone {
|
||||
constructor(
|
||||
public x1: number,
|
||||
public y1: number,
|
||||
public x2: number,
|
||||
public y2: number,
|
||||
public clickCallback: (e: MouseEvent) => any,
|
||||
public hoverCallback: (e: MouseEvent) => any,
|
||||
public tooltipCallback: (e: MouseEvent) => any,
|
||||
public leaveCallback: () => void,
|
||||
public willLinkActivate: (e: MouseEvent) => boolean
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -1,430 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { IColorSet, IRenderer, IRenderDimensions, IColorManager } from '../renderer/Types';
|
||||
import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminal, IBuffer, IBufferSet, IBrowser, ICharMeasure, ISelectionManager, ITerminalOptions, ILinkifier, IMouseHelper, ILinkMatcherOptions, CharacterJoinerHandler, IBufferLine, IBufferStringIterator, ICellData, IAttributeData } from '../Types';
|
||||
import { ICircularList, XtermListener } from '../common/Types';
|
||||
import { Buffer } from '../Buffer';
|
||||
import * as Browser from '../common/Platform';
|
||||
import { ITheme, IDisposable, IMarker, IEvent } from 'xterm';
|
||||
import { Terminal } from '../Terminal';
|
||||
import { AttributeData } from '../BufferLine';
|
||||
|
||||
export class TestTerminal extends Terminal {
|
||||
writeSync(data: string): void {
|
||||
this.writeBuffer.push(data);
|
||||
this._innerWrite();
|
||||
}
|
||||
}
|
||||
|
||||
export class MockTerminal implements ITerminal {
|
||||
onCursorMove: IEvent<void>;
|
||||
onLineFeed: IEvent<void>;
|
||||
onSelectionChange: IEvent<void>;
|
||||
onData: IEvent<string>;
|
||||
onTitleChange: IEvent<string>;
|
||||
onScroll: IEvent<number>;
|
||||
onKey: IEvent<{ key: string; domEvent: KeyboardEvent; }>;
|
||||
onRender: IEvent<{ start: number; end: number; }>;
|
||||
onResize: IEvent<{ cols: number; rows: number; }>;
|
||||
markers: IMarker[];
|
||||
addMarker(cursorYOffset: number): IMarker {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
selectLines(start: number, end: number): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
scrollToLine(line: number): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
static string: any;
|
||||
getOption(key: any): any {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
setOption(key: any, value: any): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
blur(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
focus(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
resize(columns: number, rows: number): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
writeln(data: string): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
open(parent: HTMLElement): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): IDisposable {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => boolean | void, options?: ILinkMatcherOptions): number {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
deregisterLinkMatcher(matcherId: number): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
hasSelection(): boolean {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
getSelection(): string {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
clearSelection(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
selectAll(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
dispose(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
destroy(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
scrollPages(pageCount: number): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
scrollToTop(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
scrollToBottom(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
clear(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
write(data: string): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
bracketedPasteMode: boolean;
|
||||
mouseHelper: IMouseHelper;
|
||||
renderer: IRenderer;
|
||||
linkifier: ILinkifier;
|
||||
isFocused: boolean;
|
||||
options: ITerminalOptions = {};
|
||||
element: HTMLElement;
|
||||
screenElement: HTMLElement;
|
||||
rowContainer: HTMLElement;
|
||||
selectionContainer: HTMLElement;
|
||||
selectionManager: ISelectionManager;
|
||||
charMeasure: ICharMeasure;
|
||||
textarea: HTMLTextAreaElement;
|
||||
rows: number;
|
||||
cols: number;
|
||||
browser: IBrowser = <any>Browser;
|
||||
writeBuffer: string[];
|
||||
children: HTMLElement[];
|
||||
cursorHidden: boolean;
|
||||
cursorState: number;
|
||||
scrollback: number;
|
||||
buffers: IBufferSet;
|
||||
buffer: IBuffer;
|
||||
viewport: IViewport;
|
||||
applicationCursor: boolean;
|
||||
handler(data: string): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
on(event: string, callback: (...args: any[]) => void): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
off(type: string, listener: XtermListener): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
addDisposableListener(type: string, handler: XtermListener): IDisposable {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
scrollLines(disp: number, suppressScrollEvent: boolean): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
scrollToRow(absoluteRow: number): number {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
cancel(ev: Event, force?: boolean): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
log(text: string): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
emit(event: string, data: any): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
reset(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
showCursor(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
refresh(start: number, end: number): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
registerCharacterJoiner(handler: CharacterJoinerHandler): number { return 0; }
|
||||
deregisterCharacterJoiner(joinerId: number): void { }
|
||||
}
|
||||
|
||||
export class MockCharMeasure implements ICharMeasure {
|
||||
onCharSizeChanged: IEvent<void>;
|
||||
width: number;
|
||||
height: number;
|
||||
measure(options: ITerminalOptions): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
}
|
||||
|
||||
export class MockInputHandlingTerminal implements IInputHandlingTerminal {
|
||||
element: HTMLElement;
|
||||
options: ITerminalOptions = {};
|
||||
cols: number;
|
||||
rows: number;
|
||||
charset: { [key: string]: string; };
|
||||
gcharset: number;
|
||||
glevel: number;
|
||||
charsets: { [key: string]: string; }[];
|
||||
applicationKeypad: boolean;
|
||||
applicationCursor: boolean;
|
||||
originMode: boolean;
|
||||
insertMode: boolean;
|
||||
wraparoundMode: boolean;
|
||||
bracketedPasteMode: boolean;
|
||||
curAttrData = new AttributeData();
|
||||
savedCols: number;
|
||||
x10Mouse: boolean;
|
||||
vt200Mouse: boolean;
|
||||
normalMouse: boolean;
|
||||
mouseEvents: boolean;
|
||||
sendFocus: boolean;
|
||||
utfMouse: boolean;
|
||||
sgrMouse: boolean;
|
||||
urxvtMouse: boolean;
|
||||
cursorHidden: boolean;
|
||||
buffers: IBufferSet;
|
||||
buffer: IBuffer = new MockBuffer();
|
||||
viewport: IViewport;
|
||||
selectionManager: ISelectionManager;
|
||||
focus(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
convertEol: boolean;
|
||||
bell(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
updateRange(y: number): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
scroll(isWrapped?: boolean): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
nextStop(x?: number): number {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
setgLevel(g: number): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
eraseAttrData(): IAttributeData {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
eraseRight(x: number, y: number): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
eraseLine(y: number): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
eraseLeft(x: number, y: number): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
prevStop(x?: number): number {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
is(term: string): boolean {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
setgCharset(g: number, charset: { [key: string]: string; }): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
resize(x: number, y: number): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
log(text: string, data?: any): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
reset(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
showCursor(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
refresh(start: number, end: number): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
matchColor(r1: number, g1: number, b1: number): number {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
error(text: string, data?: any): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
setOption(key: string, value: any): void {
|
||||
(<any>this.options)[key] = value;
|
||||
}
|
||||
on(type: string, listener: XtermListener): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
off(type: string, listener: XtermListener): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
emit(type: string, data?: any): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
addDisposableListener(type: string, handler: XtermListener): IDisposable {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
tabSet(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
handler(data: string): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
handleTitle(title: string): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
index(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
reverseIndex(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
}
|
||||
|
||||
export class MockBuffer implements IBuffer {
|
||||
isCursorInViewport: boolean;
|
||||
lines: ICircularList<IBufferLine>;
|
||||
ydisp: number;
|
||||
ybase: number;
|
||||
hasScrollback: boolean;
|
||||
y: number;
|
||||
x: number;
|
||||
tabs: any;
|
||||
scrollBottom: number;
|
||||
scrollTop: number;
|
||||
savedY: number;
|
||||
savedX: number;
|
||||
savedCurAttrData = new AttributeData();
|
||||
translateBufferLineToString(lineIndex: number, trimRight: boolean, startCol?: number, endCol?: number): string {
|
||||
return Buffer.prototype.translateBufferLineToString.apply(this, arguments);
|
||||
}
|
||||
getWrappedRangeForLine(y: number): { first: number; last: number; } {
|
||||
return Buffer.prototype.getWrappedRangeForLine.apply(this, arguments);
|
||||
}
|
||||
nextStop(x?: number): number {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
prevStop(x?: number): number {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
setLines(lines: ICircularList<IBufferLine>): void {
|
||||
this.lines = lines;
|
||||
}
|
||||
getBlankLine(attr: IAttributeData, isWrapped?: boolean): IBufferLine {
|
||||
return Buffer.prototype.getBlankLine.apply(this, arguments);
|
||||
}
|
||||
stringIndexToBufferIndex(lineIndex: number, stringIndex: number): number[] {
|
||||
return Buffer.prototype.stringIndexToBufferIndex.apply(this, arguments);
|
||||
}
|
||||
iterator(trimRight: boolean, startIndex?: number, endIndex?: number): IBufferStringIterator {
|
||||
return Buffer.prototype.iterator.apply(this, arguments);
|
||||
}
|
||||
getNullCell(attr?: IAttributeData): ICellData {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
getWhitespaceCell(attr?: IAttributeData): ICellData {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
}
|
||||
|
||||
export class MockRenderer implements IRenderer {
|
||||
onCanvasResize: IEvent<{ width: number; height: number; }>;
|
||||
onRender: IEvent<{ start: number; end: number; }>;
|
||||
dispose(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
colorManager: IColorManager;
|
||||
on(type: string, listener: XtermListener): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
off(type: string, listener: XtermListener): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
emit(type: string, data?: any): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
addDisposableListener(type: string, handler: XtermListener): IDisposable {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
dimensions: IRenderDimensions;
|
||||
setTheme(theme: ITheme): IColorSet { return <IColorSet>{}; }
|
||||
onResize(cols: number, rows: number): void {}
|
||||
onCharSizeChanged(): void {}
|
||||
onBlur(): void {}
|
||||
onFocus(): void {}
|
||||
onSelectionChanged(start: [number, number], end: [number, number]): void {}
|
||||
onCursorMove(): void {}
|
||||
onOptionsChanged(): void {}
|
||||
onWindowResize(devicePixelRatio: number): void {}
|
||||
clear(): void {}
|
||||
refreshRows(start: number, end: number): void {}
|
||||
registerCharacterJoiner(handler: CharacterJoinerHandler): number { return 0; }
|
||||
deregisterCharacterJoiner(): boolean { return true; }
|
||||
}
|
||||
|
||||
export class MockViewport implements IViewport {
|
||||
dispose(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
scrollBarWidth: number = 0;
|
||||
onThemeChanged(colors: IColorSet): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
onWheel(ev: WheelEvent): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
onTouchStart(ev: TouchEvent): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
onTouchMove(ev: TouchEvent): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
syncScrollArea(): void { }
|
||||
getLinesScrolled(ev: WheelEvent): number {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
}
|
||||
|
||||
export class MockCompositionHelper implements ICompositionHelper {
|
||||
compositionstart(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
compositionupdate(ev: CompositionEvent): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
compositionend(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
updateCompositionElements(dontRecurse?: boolean): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
keydown(ev: KeyboardEvent): boolean {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { IDisposable } from 'xterm';
|
||||
|
||||
export interface IMouseZoneManager extends IDisposable {
|
||||
add(zone: IMouseZone): void;
|
||||
clearAll(start?: number, end?: number): void;
|
||||
}
|
||||
|
||||
export interface IMouseZone {
|
||||
x1: number;
|
||||
x2: number;
|
||||
y1: number;
|
||||
y2: number;
|
||||
clickCallback: (e: MouseEvent) => any;
|
||||
hoverCallback: (e: MouseEvent) => any | undefined;
|
||||
tooltipCallback: (e: MouseEvent) => any | undefined;
|
||||
leaveCallback: () => any | undefined;
|
||||
willLinkActivate: (e: MouseEvent) => boolean;
|
||||
}
|
||||
Reference in New Issue
Block a user