diff --git a/src/MouseZoneManager.ts b/src/MouseZoneManager.ts index b2ee9b14..de724b88 100644 --- a/src/MouseZoneManager.ts +++ b/src/MouseZoneManager.ts @@ -6,6 +6,7 @@ import { ITerminal, IMouseZoneManager, IMouseZone } from './Types'; import { Disposable } from 'common/Lifecycle'; import { addDisposableDomListener } from 'browser/Lifecycle'; +import { IMouseService } from 'browser/services/Services'; const HOVER_DURATION = 500; @@ -31,7 +32,8 @@ export class MouseZoneManager extends Disposable implements IMouseZoneManager { private _initialSelectionLength: number; constructor( - private _terminal: ITerminal + private _terminal: ITerminal, + private _mouseService: IMouseService ) { super(); @@ -203,7 +205,7 @@ export class MouseZoneManager extends Disposable implements IMouseZoneManager { } private _findZoneEventAt(e: MouseEvent): IMouseZone { - const coords = this._terminal.mouseHelper.getCoords(e, this._terminal.screenElement, this._terminal.cols, this._terminal.rows); + const coords = this._mouseService.getCoords(e, this._terminal.screenElement, this._terminal.cols, this._terminal.rows); if (!coords) { return null; } diff --git a/src/SelectionManager.test.ts b/src/SelectionManager.test.ts index f58815f0..8499497a 100644 --- a/src/SelectionManager.test.ts +++ b/src/SelectionManager.test.ts @@ -14,7 +14,7 @@ import { MockTerminal } from './TestUtils.test'; import { MockOptionsService, MockBufferService } from 'common/TestUtils.test'; import { BufferLine } from 'common/buffer/BufferLine'; import { IBufferService } from 'common/services/Services'; -import { MockCharSizeService } from 'browser/TestUtils.test'; +import { MockCharSizeService, MockMouseService } from 'browser/TestUtils.test'; import { CellData } from 'common/buffer/CellData'; class TestMockTerminal extends MockTerminal { @@ -26,7 +26,7 @@ class TestSelectionManager extends SelectionManager { terminal: ITerminal, bufferService: IBufferService ) { - super(terminal, new MockCharSizeService(10, 10), bufferService); + super(terminal, new MockCharSizeService(10, 10), bufferService, new MockMouseService()); } public get model(): SelectionModel { return this._model; } diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index 05beaeab..aa388d8a 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -6,15 +6,15 @@ import { ITerminal, ISelectionManager, ISelectionRedrawRequestEvent } from './Types'; import { IBuffer } from 'common/buffer/Types'; import { IBufferLine } from 'common/Types'; -import { MouseHelper } from 'browser/input/MouseHelper'; import * as Browser from 'common/Platform'; import { SelectionModel } from './SelectionModel'; import { AltClickHandler } from './handlers/AltClickHandler'; import { CellData } from 'common/buffer/CellData'; import { IDisposable } from 'xterm'; import { EventEmitter, IEvent } from 'common/EventEmitter'; -import { ICharSizeService } from 'browser/services/Services'; +import { ICharSizeService, IMouseService } from 'browser/services/Services'; import { IBufferService } from 'common/services/Services'; +import { getCoordsRelativeToElement } from 'browser/input/Mouse'; /** * The number of pixels the mouse needs to be above or below the viewport in @@ -118,9 +118,10 @@ export class SelectionManager implements ISelectionManager { public get onSelectionChange(): IEvent { return this._onSelectionChange.event; } constructor( - private _terminal: ITerminal, - private _charSizeService: ICharSizeService, - bufferService: IBufferService + private readonly _terminal: ITerminal, + private readonly _charSizeService: ICharSizeService, + readonly bufferService: IBufferService, + private readonly _mouseService: IMouseService ) { this._initListeners(); this.enable(); @@ -357,7 +358,7 @@ export class SelectionManager implements ISelectionManager { * @param event The mouse event. */ private _getMouseBufferCoords(event: MouseEvent): [number, number] { - const coords = this._terminal.mouseHelper.getCoords(event, this._terminal.screenElement, this._terminal.cols, this._terminal.rows, true); + const coords = this._mouseService.getCoords(event, this._terminal.screenElement, this._terminal.cols, this._terminal.rows, true); if (!coords) { return null; } @@ -377,7 +378,7 @@ export class SelectionManager implements ISelectionManager { * @param event The mouse event. */ private _getMouseEventScrollAmount(event: MouseEvent): number { - let offset = MouseHelper.getCoordsRelativeToElement(event, this._terminal.screenElement)[1]; + let offset = getCoordsRelativeToElement(event, this._terminal.screenElement)[1]; const terminalHeight = this._terminal.rows * Math.ceil(this._charSizeService.height * this._terminal.options.lineHeight); if (offset >= 0 && offset <= terminalHeight) { return 0; @@ -654,7 +655,7 @@ export class SelectionManager implements ISelectionManager { this._removeMouseDownListeners(); if (this.selectionText.length <= 1 && timeElapsed < ALT_CLICK_MOVE_CURSOR_TIME) { - (new AltClickHandler(event, this._terminal)).move(); + (new AltClickHandler(event, this._terminal, this._mouseService)).move(); } else if (this.hasSelection) { this._onSelectionChange.fire(); } diff --git a/src/Terminal.ts b/src/Terminal.ts index 42cacf6f..59346505 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -34,7 +34,6 @@ import { SelectionManager } from './SelectionManager'; import * as Browser from 'common/Platform'; import { addDisposableDomListener } from 'browser/Lifecycle'; import * as Strings from './browser/LocalizableStrings'; -import { MouseHelper } from 'browser/input/MouseHelper'; import { SoundManager } from './SoundManager'; import { MouseZoneManager } from './MouseZoneManager'; import { AccessibilityManager } from './AccessibilityManager'; @@ -50,12 +49,13 @@ import { ColorManager } from 'browser/ColorManager'; import { RenderService } from 'browser/services/RenderService'; import { IOptionsService, IBufferService } from 'common/services/Services'; import { OptionsService } from 'common/services/OptionsService'; -import { ICharSizeService } from 'browser/services/Services'; +import { ICharSizeService, IRenderService, IMouseService } from 'browser/services/Services'; import { CharSizeService } from 'browser/services/CharSizeService'; import { BufferService, MINIMUM_COLS, MINIMUM_ROWS } from 'common/services/BufferService'; import { Disposable } from 'common/Lifecycle'; import { IBufferSet, IBuffer } from 'common/buffer/Types'; import { Attributes } from 'common/buffer/Constants'; +import { MouseService } from 'browser/services/MouseService'; // Let it work inside Node.js for automated testing purposes. const document = (typeof window !== 'undefined') ? window.document : null; @@ -111,7 +111,8 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp // browser services private _charSizeService: ICharSizeService; - private _renderService: RenderService; + private _renderService: IRenderService; + private _mouseService: IMouseService; // modes public applicationKeypad: boolean; @@ -177,7 +178,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp public viewport: IViewport; private _compositionHelper: ICompositionHelper; private _mouseZoneManager: IMouseZoneManager; - public mouseHelper: MouseHelper; private _accessibilityManager: AccessibilityManager; private _colorManager: ColorManager; private _theme: ITheme; @@ -591,11 +591,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this.screenElement.appendChild(this._helperContainer); fragment.appendChild(this.screenElement); - this._mouseZoneManager = new MouseZoneManager(this); - this.register(this._mouseZoneManager); - this.register(this.onScroll(() => this._mouseZoneManager.clearAll())); - this.linkifier.attachToDom(this._mouseZoneManager); - this.textarea = document.createElement('textarea'); this.textarea.classList.add('xterm-helper-textarea'); this.textarea.setAttribute('aria-label', Strings.promptLabel); @@ -628,6 +623,13 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this._renderService.onRender(e => this._onRender.fire(e)); this.onResize(e => this._renderService.resize(e.cols, e.rows)); + this._mouseService = new MouseService(this._renderService, this._charSizeService); + + this._mouseZoneManager = new MouseZoneManager(this, this._mouseService); + this.register(this._mouseZoneManager); + this.register(this.onScroll(() => this._mouseZoneManager.clearAll())); + this.linkifier.attachToDom(this._mouseZoneManager); + this.viewport = new Viewport(this, this._viewportElement, this._viewportScrollArea, this._renderService.dimensions, this._charSizeService); this.viewport.onThemeChange(this._colorManager.colors); this.register(this.viewport); @@ -638,7 +640,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this.register(this.onFocus(() => this._renderService.onFocus())); this.register(this._renderService.onDimensionsChange(() => this.viewport.syncScrollArea())); - this.selectionManager = new SelectionManager(this, this._charSizeService, this._bufferService); + this.selectionManager = new SelectionManager(this, this._charSizeService, this._bufferService, this._mouseService); this.register(this.selectionManager.onSelectionChange(() => this._onSelectionChange.fire())); this.register(addDisposableDomListener(this.element, 'mousedown', (e: MouseEvent) => this.selectionManager.onMouseDown(e))); this.register(this.selectionManager.onRedrawRequest(e => this._renderService.onSelectionChanged(e.start, e.end, e.columnSelectMode))); @@ -656,7 +658,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp })); this.register(addDisposableDomListener(this._viewportElement, 'scroll', () => this.selectionManager.refresh())); - this.mouseHelper = new MouseHelper(this._renderService, this._charSizeService); // apply mouse event classes set by escape codes before terminal was attached this.element.classList.toggle('enable-mouse-events', this.mouseEvents); if (this.mouseEvents) { @@ -738,7 +739,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp button = getButton(ev); // get mouse coordinates - pos = self.mouseHelper.getRawByteCoords(ev, self.screenElement, self.cols, self.rows); + pos = self._mouseService.getRawByteCoords(ev, self.screenElement, self.cols, self.rows); if (!pos) return; sendEvent(button, pos); @@ -764,7 +765,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp // ^[[M 3<^[[M@4<^[[M@5<^[[M@6<^[[M@7<^[[M#7< function sendMove(ev: MouseEvent): void { let button = pressed; - const pos = self.mouseHelper.getRawByteCoords(ev, self.screenElement, self.cols, self.rows); + const pos = self._mouseService.getRawByteCoords(ev, self.screenElement, self.cols, self.rows); if (!pos) return; // buttons marked as motions diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index 257babe8..07f91705 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -12,7 +12,7 @@ import * as Browser from 'common/Platform'; import { IDisposable, IMarker, IEvent, ISelectionPosition } from 'xterm'; import { Terminal } from './Terminal'; import { AttributeData } from 'common/buffer/AttributeData'; -import { IColorManager, IColorSet, IMouseHelper } from 'browser/Types'; +import { IColorManager, IColorSet } from 'browser/Types'; import { IOptionsService } from 'common/services/Services'; import { EventEmitter } from 'common/EventEmitter'; @@ -122,7 +122,6 @@ export class MockTerminal implements ITerminal { throw new Error('Method not implemented.'); } bracketedPasteMode: boolean; - mouseHelper: IMouseHelper; renderer: IRenderer; linkifier: ILinkifier; isFocused: boolean; diff --git a/src/Types.d.ts b/src/Types.d.ts index 6f587931..5987d605 100644 --- a/src/Types.d.ts +++ b/src/Types.d.ts @@ -6,7 +6,7 @@ import { ITerminalOptions as IPublicTerminalOptions, IDisposable, IMarker, ISelectionPosition } from 'xterm'; import { ICharset, IAttributeData, CharData } from 'common/Types'; import { IEvent, IEventEmitter } from 'common/EventEmitter'; -import { IColorSet, IMouseHelper } from 'browser/Types'; +import { IColorSet } from 'browser/Types'; import { IOptionsService } from 'common/services/Services'; import { IBuffer, IBufferSet } from 'common/buffer/Types'; @@ -204,7 +204,6 @@ export interface ITerminal extends IPublicTerminal, IElementAccessor, IBufferAcc buffer: IBuffer; buffers: IBufferSet; isFocused: boolean; - mouseHelper: IMouseHelper; viewport: IViewport; bracketedPasteMode: boolean; applicationCursor: boolean; diff --git a/src/browser/TestUtils.test.ts b/src/browser/TestUtils.test.ts index b286295b..d89dc391 100644 --- a/src/browser/TestUtils.test.ts +++ b/src/browser/TestUtils.test.ts @@ -4,7 +4,7 @@ */ import { IEvent, EventEmitter } from 'common/EventEmitter'; -import { ICharSizeService } from 'browser/services/Services'; +import { ICharSizeService, IMouseService } from 'browser/services/Services'; export class MockCharSizeService implements ICharSizeService { get hasValidSize(): boolean { return this.width > 0 && this.height > 0; } @@ -12,3 +12,13 @@ export class MockCharSizeService implements ICharSizeService { constructor(public width: number, public height: number) {} measure(): void {} } + +export class MockMouseService implements IMouseService { + public getCoords(event: {clientX: number, clientY: number}, element: HTMLElement, colCount: number, rowCount: number, isSelection?: boolean): [number, number] | undefined { + throw new Error('Not implemented'); + } + + public getRawByteCoords(event: MouseEvent, element: HTMLElement, colCount: number, rowCount: number): { x: number, y: number } | undefined { + throw new Error('Not implemented'); + } +} diff --git a/src/browser/Types.d.ts b/src/browser/Types.d.ts index a1ea662c..ef725ba6 100644 --- a/src/browser/Types.d.ts +++ b/src/browser/Types.d.ts @@ -20,8 +20,3 @@ export interface IColorSet { selection: IColor; ansi: IColor[]; } - -export interface IMouseHelper { - getCoords(event: { clientX: number, clientY: number }, element: HTMLElement, colCount: number, rowCount: number, isSelection?: boolean): [number, number] | undefined; - getRawByteCoords(event: MouseEvent, element: HTMLElement, colCount: number, rowCount: number): { x: number | undefined, y: number | undefined }; -} diff --git a/src/browser/input/Mouse.test.ts b/src/browser/input/Mouse.test.ts new file mode 100644 index 00000000..6a908499 --- /dev/null +++ b/src/browser/input/Mouse.test.ts @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import jsdom = require('jsdom'); +import { assert } from 'chai'; +import { getCoords } from 'browser/input/Mouse'; + +const CHAR_WIDTH = 10; +const CHAR_HEIGHT = 20; + +describe('Mouse getCoords', () => { + let document: Document; + + beforeEach(() => { + document = new jsdom.JSDOM('').window.document; + }); + + it('should return the cell that was clicked', () => { + let coords: [number, number] | undefined; + coords = getCoords({ clientX: CHAR_WIDTH / 2, clientY: CHAR_HEIGHT / 2 }, document.createElement('div'), 10, 10, true, CHAR_WIDTH, CHAR_HEIGHT); + assert.deepEqual(coords, [1, 1]); + coords = getCoords({ clientX: CHAR_WIDTH, clientY: CHAR_HEIGHT }, document.createElement('div'), 10, 10, true, CHAR_WIDTH, CHAR_HEIGHT); + assert.deepEqual(coords, [1, 1]); + coords = getCoords({ clientX: CHAR_WIDTH, clientY: CHAR_HEIGHT + 1 }, document.createElement('div'), 10, 10, true, CHAR_WIDTH, CHAR_HEIGHT); + assert.deepEqual(coords, [1, 2]); + coords = getCoords({ clientX: CHAR_WIDTH + 1, clientY: CHAR_HEIGHT }, document.createElement('div'), 10, 10, true, CHAR_WIDTH, CHAR_HEIGHT); + assert.deepEqual(coords, [2, 1]); + }); + + it('should ensure the coordinates are returned within the terminal bounds', () => { + let coords: [number, number] | undefined; + coords = getCoords({ clientX: -1, clientY: -1 }, document.createElement('div'), 10, 10, true, CHAR_WIDTH, CHAR_HEIGHT); + assert.deepEqual(coords, [1, 1]); + // Event are double the cols/rows + coords = getCoords({ clientX: CHAR_WIDTH * 20, clientY: CHAR_HEIGHT * 20 }, document.createElement('div'), 10, 10, true, CHAR_WIDTH, CHAR_HEIGHT); + assert.deepEqual(coords, [10, 10], 'coordinates should never come back as larger than the terminal'); + }); +}); diff --git a/src/browser/input/Mouse.ts b/src/browser/input/Mouse.ts new file mode 100644 index 00000000..2986fb3c --- /dev/null +++ b/src/browser/input/Mouse.ts @@ -0,0 +1,58 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +export function 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 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. + */ +export function getCoords(event: {clientX: number, clientY: number}, element: HTMLElement, colCount: number, rowCount: number, hasValidCharSize: boolean, actualCellWidth: number, actualCellHeight: number, isSelection?: boolean): [number, number] | undefined { + // Coordinates cannot be measured if there are no valid + if (!hasValidCharSize) { + return undefined; + } + + const coords = getCoordsRelativeToElement(event, element); + if (!coords) { + return undefined; + } + + coords[0] = Math.ceil((coords[0] + (isSelection ? actualCellWidth / 2 : 0)) / actualCellWidth); + coords[1] = Math.ceil(coords[1] / 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. + */ +export function getRawByteCoords(coords: [number, number] | undefined): { x: number, y: number } | undefined { + if (!coords) { + return undefined; + } + + // xterm sends raw bytes and starts at 32 (SP) for each. + return { x: coords[0] + 32, y: coords[1] + 32 }; +} diff --git a/src/browser/input/MouseHelper.test.ts b/src/browser/input/MouseHelper.test.ts deleted file mode 100644 index 5d4b567c..00000000 --- a/src/browser/input/MouseHelper.test.ts +++ /dev/null @@ -1,49 +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 'browser/input/MouseHelper'; -import { MockCharSizeService } from 'browser/TestUtils.test'; - -const CHAR_WIDTH = 10; -const CHAR_HEIGHT = 20; - -describe('MouseHelper.getCoords', () => { - let document: Document; - let mouseHelper: MouseHelper; - - beforeEach(() => { - document = new jsdom.JSDOM('').window.document; - const mockRenderService = { - dimensions: { - actualCellWidth: CHAR_WIDTH, - actualCellHeight: CHAR_HEIGHT - } - }; - mouseHelper = new MouseHelper(mockRenderService as any, new MockCharSizeService(CHAR_WIDTH, CHAR_HEIGHT)); - }); - - it('should return the cell that was clicked', () => { - let coords: [number, number] | undefined; - coords = mouseHelper.getCoords({ clientX: CHAR_WIDTH / 2, clientY: CHAR_HEIGHT / 2 }, document.createElement('div'), 10, 10); - assert.deepEqual(coords, [1, 1]); - coords = mouseHelper.getCoords({ clientX: CHAR_WIDTH, clientY: CHAR_HEIGHT }, document.createElement('div'), 10, 10); - assert.deepEqual(coords, [1, 1]); - coords = mouseHelper.getCoords({ clientX: CHAR_WIDTH, clientY: CHAR_HEIGHT + 1 }, document.createElement('div'), 10, 10); - assert.deepEqual(coords, [1, 2]); - coords = mouseHelper.getCoords({ clientX: CHAR_WIDTH + 1, clientY: CHAR_HEIGHT }, document.createElement('div'), 10, 10); - assert.deepEqual(coords, [2, 1]); - }); - - it('should ensure the coordinates are returned within the terminal bounds', () => { - let coords: [number, number] | undefined; - coords = mouseHelper.getCoords({ clientX: -1, clientY: -1 }, document.createElement('div'), 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'), 10, 10); - assert.deepEqual(coords, [10, 10], 'coordinates should never come back as larger than the terminal'); - }); -}); diff --git a/src/browser/input/MouseHelper.ts b/src/browser/input/MouseHelper.ts deleted file mode 100644 index b99757db..00000000 --- a/src/browser/input/MouseHelper.ts +++ /dev/null @@ -1,75 +0,0 @@ -/** - * Copyright (c) 2017 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { IMouseHelper } from 'browser/Types'; -import { RenderService } from 'browser/services/RenderService'; -import { ICharSizeService } from 'browser/services/Services'; - -export class MouseHelper implements IMouseHelper { - constructor( - private _renderService: RenderService, - private _charSizeService: ICharSizeService - ) { - } - - 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 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, colCount: number, rowCount: number, isSelection?: boolean): [number, number] | undefined { - // Coordinates cannot be measured if there are no valid - if (!this._charSizeService.hasValidSize) { - return undefined; - } - - const coords = MouseHelper.getCoordsRelativeToElement(event, element); - if (!coords) { - return undefined; - } - - coords[0] = Math.ceil((coords[0] + (isSelection ? this._renderService.dimensions.actualCellWidth / 2 : 0)) / this._renderService.dimensions.actualCellWidth); - coords[1] = Math.ceil(coords[1] / this._renderService.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 colCount The number of columns in the terminal. - * @param rowCount The number of rows in the terminal. - */ - public getRawByteCoords(event: MouseEvent, element: HTMLElement, colCount: number, rowCount: number): { x: number | undefined, y: number | undefined } { - const coords = this.getCoords(event, element, colCount, rowCount); - - // xterm sends raw bytes and starts at 32 (SP) for each. - const x = coords ? coords[0] + 32 : undefined; - const y = coords ? coords[1] + 32 : undefined; - - return { x, y }; - } -} diff --git a/src/browser/services/MouseService.ts b/src/browser/services/MouseService.ts new file mode 100644 index 00000000..76968698 --- /dev/null +++ b/src/browser/services/MouseService.ts @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { ICharSizeService, IRenderService, IMouseService } from './Services'; +import { getCoords, getRawByteCoords } from 'browser/input/Mouse'; + +export class MouseService implements IMouseService { + constructor( + private readonly _renderService: IRenderService, + private readonly _charSizeService: ICharSizeService + ) { + } + + public getCoords(event: {clientX: number, clientY: number}, element: HTMLElement, colCount: number, rowCount: number, isSelection?: boolean): [number, number] | undefined { + return getCoords( + event, + element, + colCount, + rowCount, + this._charSizeService.hasValidSize, + this._renderService.dimensions.actualCellWidth, + this._renderService.dimensions.actualCellHeight, + isSelection + ); + } + + public getRawByteCoords(event: MouseEvent, element: HTMLElement, colCount: number, rowCount: number): { x: number, y: number } | undefined { + const coords = this.getCoords(event, element, colCount, rowCount); + return getRawByteCoords(coords); + } +} diff --git a/src/browser/services/Services.d.ts b/src/browser/services/Services.d.ts index 1916572c..64539b95 100644 --- a/src/browser/services/Services.d.ts +++ b/src/browser/services/Services.d.ts @@ -17,6 +17,11 @@ export interface ICharSizeService { measure(): void; } +export interface IMouseService { + getCoords(event: {clientX: number, clientY: number}, element: HTMLElement, colCount: number, rowCount: number, isSelection?: boolean): [number, number] | undefined; + getRawByteCoords(event: MouseEvent, element: HTMLElement, colCount: number, rowCount: number): { x: number, y: number } | undefined; +} + export interface IRenderService { onDimensionsChange: IEvent; onRender: IEvent<{ start: number, end: number }>; diff --git a/src/handlers/AltClickHandler.ts b/src/handlers/AltClickHandler.ts index 506e6ee1..334b78d8 100644 --- a/src/handlers/AltClickHandler.ts +++ b/src/handlers/AltClickHandler.ts @@ -6,6 +6,7 @@ import { ITerminal } from '../Types'; import { IBufferLine, ICircularList } from 'common/Types'; import { C0 } from 'common/data/EscapeSequences'; +import { IMouseService } from 'browser/services/Services'; const enum Direction { UP = 'A', @@ -23,13 +24,14 @@ export class AltClickHandler { constructor( private _mouseEvent: MouseEvent, - private _terminal: ITerminal + private _terminal: ITerminal, + private readonly _mouseService: IMouseService ) { this._lines = this._terminal.buffer.lines; this._startCol = this._terminal.buffer.x; this._startRow = this._terminal.buffer.y; - const coordinates = this._terminal.mouseHelper.getCoords( + const coordinates = this._mouseService.getCoords( this._mouseEvent, this._terminal.element, this._terminal.cols,