From 00969508d9352da6156e7d924f2f1b2ce7f3959b Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 22 Jun 2019 09:40:11 -0700 Subject: [PATCH 1/7] Remove some private member deps in AltClickHandler --- src/SelectionManager.ts | 2 +- src/handlers/AltClickHandler.ts | 172 ++++++++++++++++---------------- 2 files changed, 89 insertions(+), 85 deletions(-) diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index 767cf156..0a5daed2 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -650,7 +650,7 @@ export class SelectionManager implements ISelectionManager { this._removeMouseDownListeners(); if (this.selectionText.length <= 1 && timeElapsed < ALT_CLICK_MOVE_CURSOR_TIME) { - (new AltClickHandler(event, this._terminal, this._mouseService)).move(); + (new AltClickHandler(event, this._terminal, this._mouseService)).move(this._bufferService, this._terminal.applicationCursor); } else if (this.hasSelection) { this._onSelectionChange.fire(); } diff --git a/src/handlers/AltClickHandler.ts b/src/handlers/AltClickHandler.ts index 334b78d8..7b46d745 100644 --- a/src/handlers/AltClickHandler.ts +++ b/src/handlers/AltClickHandler.ts @@ -7,6 +7,7 @@ import { ITerminal } from '../Types'; import { IBufferLine, ICircularList } from 'common/Types'; import { C0 } from 'common/data/EscapeSequences'; import { IMouseService } from 'browser/services/Services'; +import { IBufferService } from 'common/services/Services'; const enum Direction { UP = 'A', @@ -49,9 +50,9 @@ export class AltClickHandler { /** * Writes the escape sequences of arrows to the terminal */ - public move(): void { + public move(bufferService: IBufferService, applicationCursor: boolean): void { if (this._mouseEvent.altKey && this._endCol !== undefined && this._endRow !== undefined) { - this._terminal.handler(this._arrowSequences()); + this._terminal.handler(this._arrowSequences(bufferService, applicationCursor)); } } @@ -60,14 +61,16 @@ export class AltClickHandler { * Resets the starting row to an unwrapped row, moves to the requested row, * then moves to requested col. */ - private _arrowSequences(): string { + private _arrowSequences(bufferService: IBufferService, applicationCursor: boolean): string { // The alt buffer should try to navigate between rows - if (!this._terminal.buffer.hasScrollback) { - return this._resetStartingRow() + this._moveToRequestedRow() + this._moveToRequestedCol(); + if (!bufferService.buffer.hasScrollback) { + return this._resetStartingRow(bufferService, applicationCursor) + + this._moveToRequestedRow(bufferService, applicationCursor) + + this._moveToRequestedCol(bufferService, applicationCursor); } // Only move horizontally for the normal buffer - return this._moveHorizontallyOnly(); + return this._moveHorizontallyOnly(bufferService, applicationCursor); } /** @@ -75,52 +78,52 @@ export class AltClickHandler { * cursor up to the first row that is not wrapped to have accurate vertical * positioning. */ - private _resetStartingRow(): string { - if (this._moveToRequestedRow().length === 0) { + private _resetStartingRow(bufferService: IBufferService, applicationCursor: boolean): string { + if (this._moveToRequestedRow(bufferService, applicationCursor).length === 0) { return ''; } - return repeat(this._bufferLine( + return repeat(bufferLine( this._startCol, this._startRow, this._startCol, - this._startRow - this._wrappedRowsForRow(this._startRow), false - ).length, this._sequence(Direction.LEFT)); + this._startRow - this._wrappedRowsForRow(bufferService, this._startRow), false, bufferService + ).length, sequence(Direction.LEFT, applicationCursor)); } /** * Using the reset starting and ending row, move to the requested row, * ignoring wrapped rows */ - private _moveToRequestedRow(): string { - const startRow = this._startRow - this._wrappedRowsForRow(this._startRow); - const endRow = this._endRow - this._wrappedRowsForRow(this._endRow); + private _moveToRequestedRow(bufferService: IBufferService, applicationCursor: boolean): string { + const startRow = this._startRow - this._wrappedRowsForRow(bufferService, this._startRow); + const endRow = this._endRow - this._wrappedRowsForRow(bufferService, this._endRow); - const rowsToMove = Math.abs(startRow - endRow) - this._wrappedRowsCount(); + const rowsToMove = Math.abs(startRow - endRow) - this._wrappedRowsCount(bufferService); - return repeat(rowsToMove, this._sequence(this._verticalDirection())); + return repeat(rowsToMove, sequence(this._verticalDirection(), applicationCursor)); } /** * Move to the requested col on the ending row */ - private _moveToRequestedCol(): string { + private _moveToRequestedCol(bufferService: IBufferService, applicationCursor: boolean): string { let startRow; - if (this._moveToRequestedRow().length > 0) { - startRow = this._endRow - this._wrappedRowsForRow(this._endRow); + if (this._moveToRequestedRow(bufferService, applicationCursor).length > 0) { + startRow = this._endRow - this._wrappedRowsForRow(bufferService, this._endRow); } else { startRow = this._startRow; } const endRow = this._endRow; - const direction = this._horizontalDirection(); + const direction = this._horizontalDirection(bufferService, applicationCursor); - return repeat(this._bufferLine( + return repeat(bufferLine( this._startCol, startRow, this._endCol, endRow, - direction === Direction.RIGHT - ).length, this._sequence(direction)); + direction === Direction.RIGHT, bufferService + ).length, sequence(direction, applicationCursor)); } - private _moveHorizontallyOnly(): string { - const direction = this._horizontalDirection(); - return repeat(Math.abs(this._startCol - this._endCol), this._sequence(direction)); + private _moveHorizontallyOnly(bufferService: IBufferService, applicationCursor: boolean): string { + const direction = this._horizontalDirection(bufferService, applicationCursor); + return repeat(Math.abs(this._startCol - this._endCol), sequence(direction, applicationCursor)); } /** @@ -131,10 +134,10 @@ export class AltClickHandler { * Calculates the number of wrapped rows between the unwrapped starting and * ending rows. These rows need to ignored since the cursor skips over them. */ - private _wrappedRowsCount(): number { + private _wrappedRowsCount(bufferService: IBufferService): number { let wrappedRows = 0; - const startRow = this._startRow - this._wrappedRowsForRow(this._startRow); - const endRow = this._endRow - this._wrappedRowsForRow(this._endRow); + const startRow = this._startRow - this._wrappedRowsForRow(bufferService, this._startRow); + const endRow = this._endRow - this._wrappedRowsForRow(bufferService, this._endRow); for (let i = 0; i < Math.abs(startRow - endRow); i++) { const direction = this._verticalDirection() === Direction.UP ? -1 : 1; @@ -151,14 +154,14 @@ export class AltClickHandler { * Calculates the number of wrapped rows that make up a given row. * @param currentRow The row to determine how many wrapped rows make it up */ - private _wrappedRowsForRow(currentRow: number): number { + private _wrappedRowsForRow(bufferService: IBufferService, currentRow: number): number { let rowCount = 0; - let lineWraps = this._lines.get(currentRow).isWrapped; + let lineWraps = bufferService.buffer.lines.get(currentRow).isWrapped; - while (lineWraps && currentRow >= 0 && currentRow < this._terminal.rows) { + while (lineWraps && currentRow >= 0 && currentRow < bufferService.rows) { rowCount++; currentRow--; - lineWraps = this._lines.get(currentRow).isWrapped; + lineWraps = bufferService.buffer.lines.get(currentRow).isWrapped; } return rowCount; @@ -171,10 +174,10 @@ export class AltClickHandler { /** * Determines if the right or left arrow is needed */ - private _horizontalDirection(): Direction { + private _horizontalDirection(bufferService: IBufferService, applicationCursor: boolean): Direction { let startRow; - if (this._moveToRequestedRow().length > 0) { - startRow = this._endRow - this._wrappedRowsForRow(this._endRow); + if (this._moveToRequestedRow(bufferService, applicationCursor).length > 0) { + startRow = this._endRow - this._wrappedRowsForRow(bufferService, this._endRow); } else { startRow = this._startRow; } @@ -197,60 +200,61 @@ export class AltClickHandler { } return Direction.DOWN; } +} - /** - * Constructs the string of chars in the buffer from a starting row and col - * to an ending row and col - * @param startCol The starting column position - * @param startRow The starting row position - * @param endCol The ending column position - * @param endRow The ending row position - * @param forward Direction to move - */ - private _bufferLine( - startCol: number, - startRow: number, - endCol: number, - endRow: number, - forward: boolean - ): string { - let currentCol = startCol; - let currentRow = startRow; - let bufferStr = ''; +/** + * Constructs the string of chars in the buffer from a starting row and col + * to an ending row and col + * @param startCol The starting column position + * @param startRow The starting row position + * @param endCol The ending column position + * @param endRow The ending row position + * @param forward Direction to move + */ +function bufferLine( + startCol: number, + startRow: number, + endCol: number, + endRow: number, + forward: boolean, + bufferService: IBufferService +): string { + let currentCol = startCol; + let currentRow = startRow; + let bufferStr = ''; - while (currentCol !== endCol || currentRow !== endRow) { - currentCol += forward ? 1 : -1; + while (currentCol !== endCol || currentRow !== endRow) { + currentCol += forward ? 1 : -1; - if (forward && currentCol > this._terminal.cols - 1) { - bufferStr += this._terminal.buffer.translateBufferLineToString( - currentRow, false, startCol, currentCol - ); - currentCol = 0; - startCol = 0; - currentRow++; - } else if (!forward && currentCol < 0) { - bufferStr += this._terminal.buffer.translateBufferLineToString( - currentRow, false, 0, startCol + 1 - ); - currentCol = this._terminal.cols - 1; - startCol = currentCol; - currentRow--; - } + if (forward && currentCol > bufferService.cols - 1) { + bufferStr += bufferService.buffer.translateBufferLineToString( + currentRow, false, startCol, currentCol + ); + currentCol = 0; + startCol = 0; + currentRow++; + } else if (!forward && currentCol < 0) { + bufferStr += bufferService.buffer.translateBufferLineToString( + currentRow, false, 0, startCol + 1 + ); + currentCol = bufferService.cols - 1; + startCol = currentCol; + currentRow--; } - - return bufferStr + this._terminal.buffer.translateBufferLineToString( - currentRow, false, startCol, currentCol - ); } - /** - * Constructs the escape sequence for clicking an arrow - * @param direction The direction to move - */ - private _sequence(direction: Direction): string { - const mod = this._terminal.applicationCursor ? 'O' : '['; - return C0.ESC + mod + direction; - } + return bufferStr + bufferService.buffer.translateBufferLineToString( + currentRow, false, startCol, currentCol + ); +} + +/** + * Constructs the escape sequence for clicking an arrow + * @param direction The direction to move + */ +function sequence(direction: Direction, applicationCursor: boolean): string { + const mod = applicationCursor ? 'O' : '['; + return C0.ESC + mod + direction; } /** From da1484ac47d7e40e2ba42f654346903112dea2b8 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 22 Jun 2019 09:44:25 -0700 Subject: [PATCH 2/7] Return a sequence from AltClickHandler --- src/SelectionManager.ts | 15 ++++++++++++++- src/handlers/AltClickHandler.ts | 27 +++++---------------------- 2 files changed, 19 insertions(+), 23 deletions(-) diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index 0a5daed2..483ea7cb 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -650,7 +650,20 @@ export class SelectionManager implements ISelectionManager { this._removeMouseDownListeners(); if (this.selectionText.length <= 1 && timeElapsed < ALT_CLICK_MOVE_CURSOR_TIME) { - (new AltClickHandler(event, this._terminal, this._mouseService)).move(this._bufferService, this._terminal.applicationCursor); + if (event.altKey) { + const coordinates = this._mouseService.getCoords( + event, + this._terminal.element, + this._bufferService.cols, + this._bufferService.rows, + false + ); + if (coordinates && coordinates[0] !== undefined && coordinates[1] !== undefined) { + this._terminal.handler( + (new AltClickHandler(this._terminal)).move(coordinates[0] - 1, coordinates[1] - 1, this._bufferService, this._terminal.applicationCursor) + ); + } + } } else if (this.hasSelection) { this._onSelectionChange.fire(); } diff --git a/src/handlers/AltClickHandler.ts b/src/handlers/AltClickHandler.ts index 7b46d745..918ab60d 100644 --- a/src/handlers/AltClickHandler.ts +++ b/src/handlers/AltClickHandler.ts @@ -6,7 +6,6 @@ import { ITerminal } from '../Types'; import { IBufferLine, ICircularList } from 'common/Types'; import { C0 } from 'common/data/EscapeSequences'; -import { IMouseService } from 'browser/services/Services'; import { IBufferService } from 'common/services/Services'; const enum Direction { @@ -24,36 +23,20 @@ export class AltClickHandler { private _lines: ICircularList; constructor( - private _mouseEvent: MouseEvent, - private _terminal: ITerminal, - private readonly _mouseService: IMouseService + private _terminal: ITerminal ) { this._lines = this._terminal.buffer.lines; this._startCol = this._terminal.buffer.x; this._startRow = this._terminal.buffer.y; - - const coordinates = this._mouseService.getCoords( - this._mouseEvent, - this._terminal.element, - this._terminal.cols, - this._terminal.rows, - false - ); - - if (coordinates) { - [this._endCol, this._endRow] = coordinates.map((coordinate: number) => { - return coordinate - 1; - }); - } } /** * Writes the escape sequences of arrows to the terminal */ - public move(bufferService: IBufferService, applicationCursor: boolean): void { - if (this._mouseEvent.altKey && this._endCol !== undefined && this._endRow !== undefined) { - this._terminal.handler(this._arrowSequences(bufferService, applicationCursor)); - } + public move(targetX: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): string { + this._endCol = targetX; + this._endRow = targetY; + return this._arrowSequences(bufferService, applicationCursor); } /** From 7454701c0b34459ed8f920bf76be775c5b686e06 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 22 Jun 2019 17:53:11 -0700 Subject: [PATCH 3/7] Remove AltHandler member usage --- src/SelectionManager.ts | 2 +- src/handlers/AltClickHandler.ts | 99 ++++++++++++++------------------- 2 files changed, 44 insertions(+), 57 deletions(-) diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index 483ea7cb..344978cf 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -660,7 +660,7 @@ export class SelectionManager implements ISelectionManager { ); if (coordinates && coordinates[0] !== undefined && coordinates[1] !== undefined) { this._terminal.handler( - (new AltClickHandler(this._terminal)).move(coordinates[0] - 1, coordinates[1] - 1, this._bufferService, this._terminal.applicationCursor) + (new AltClickHandler()).move(coordinates[0] - 1, coordinates[1] - 1, this._bufferService, this._terminal.applicationCursor) ); } } diff --git a/src/handlers/AltClickHandler.ts b/src/handlers/AltClickHandler.ts index 918ab60d..8f8288a6 100644 --- a/src/handlers/AltClickHandler.ts +++ b/src/handlers/AltClickHandler.ts @@ -3,8 +3,6 @@ * @license MIT */ -import { ITerminal } from '../Types'; -import { IBufferLine, ICircularList } from 'common/Types'; import { C0 } from 'common/data/EscapeSequences'; import { IBufferService } from 'common/services/Services'; @@ -16,27 +14,16 @@ const enum Direction { } export class AltClickHandler { - private _startRow: number; - private _startCol: number; - private _endRow: number; - private _endCol: number; - private _lines: ICircularList; constructor( - private _terminal: ITerminal ) { - this._lines = this._terminal.buffer.lines; - this._startCol = this._terminal.buffer.x; - this._startRow = this._terminal.buffer.y; } /** * Writes the escape sequences of arrows to the terminal */ public move(targetX: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): string { - this._endCol = targetX; - this._endRow = targetY; - return this._arrowSequences(bufferService, applicationCursor); + return this._arrowSequences(targetX, targetY, bufferService, applicationCursor); } /** @@ -44,16 +31,19 @@ export class AltClickHandler { * Resets the starting row to an unwrapped row, moves to the requested row, * then moves to requested col. */ - private _arrowSequences(bufferService: IBufferService, applicationCursor: boolean): string { + private _arrowSequences(targetX: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): string { + const startX = bufferService.buffer.x; + const startY = bufferService.buffer.y; + // The alt buffer should try to navigate between rows if (!bufferService.buffer.hasScrollback) { - return this._resetStartingRow(bufferService, applicationCursor) + - this._moveToRequestedRow(bufferService, applicationCursor) + - this._moveToRequestedCol(bufferService, applicationCursor); + return this._resetStartingRow(startX, startY, targetX, targetY, bufferService, applicationCursor) + + this._moveToRequestedRow(startY, targetY, bufferService, applicationCursor) + + this._moveToRequestedCol(startX, startY, targetX, targetY, bufferService, applicationCursor); } // Only move horizontally for the normal buffer - return this._moveHorizontallyOnly(bufferService, applicationCursor); + return this._moveHorizontallyOnly(startX, startY, targetX, targetY, bufferService, applicationCursor); } /** @@ -61,13 +51,13 @@ export class AltClickHandler { * cursor up to the first row that is not wrapped to have accurate vertical * positioning. */ - private _resetStartingRow(bufferService: IBufferService, applicationCursor: boolean): string { - if (this._moveToRequestedRow(bufferService, applicationCursor).length === 0) { + private _resetStartingRow(startX: number, startY: number, targetX: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): string { + if (this._moveToRequestedRow(startY, targetY, bufferService, applicationCursor).length === 0) { return ''; } return repeat(bufferLine( - this._startCol, this._startRow, this._startCol, - this._startRow - this._wrappedRowsForRow(bufferService, this._startRow), false, bufferService + startX, startY, startX, + startY - this._wrappedRowsForRow(bufferService, startY), false, bufferService ).length, sequence(Direction.LEFT, applicationCursor)); } @@ -75,38 +65,38 @@ export class AltClickHandler { * Using the reset starting and ending row, move to the requested row, * ignoring wrapped rows */ - private _moveToRequestedRow(bufferService: IBufferService, applicationCursor: boolean): string { - const startRow = this._startRow - this._wrappedRowsForRow(bufferService, this._startRow); - const endRow = this._endRow - this._wrappedRowsForRow(bufferService, this._endRow); + private _moveToRequestedRow(startY: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): string { + const startRow = startY - this._wrappedRowsForRow(bufferService, startY); + const endRow = targetY - this._wrappedRowsForRow(bufferService, targetY); - const rowsToMove = Math.abs(startRow - endRow) - this._wrappedRowsCount(bufferService); + const rowsToMove = Math.abs(startRow - endRow) - this._wrappedRowsCount(startY, targetY, bufferService); - return repeat(rowsToMove, sequence(this._verticalDirection(), applicationCursor)); + return repeat(rowsToMove, sequence(this._verticalDirection(startY, targetY), applicationCursor)); } /** * Move to the requested col on the ending row */ - private _moveToRequestedCol(bufferService: IBufferService, applicationCursor: boolean): string { + private _moveToRequestedCol(startX: number, startY: number, targetX: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): string { let startRow; - if (this._moveToRequestedRow(bufferService, applicationCursor).length > 0) { - startRow = this._endRow - this._wrappedRowsForRow(bufferService, this._endRow); + if (this._moveToRequestedRow(startY, targetY, bufferService, applicationCursor).length > 0) { + startRow = targetY - this._wrappedRowsForRow(bufferService, targetY); } else { - startRow = this._startRow; + startRow = startY; } - const endRow = this._endRow; - const direction = this._horizontalDirection(bufferService, applicationCursor); + const endRow = targetY; + const direction = this._horizontalDirection(startX, startY, targetX, targetY, bufferService, applicationCursor); return repeat(bufferLine( - this._startCol, startRow, this._endCol, endRow, + startX, startRow, targetX, endRow, direction === Direction.RIGHT, bufferService ).length, sequence(direction, applicationCursor)); } - private _moveHorizontallyOnly(bufferService: IBufferService, applicationCursor: boolean): string { - const direction = this._horizontalDirection(bufferService, applicationCursor); - return repeat(Math.abs(this._startCol - this._endCol), sequence(direction, applicationCursor)); + private _moveHorizontallyOnly(startX: number, startY: number, targetX: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): string { + const direction = this._horizontalDirection(startX, startY, targetX, targetY, bufferService, applicationCursor); + return repeat(Math.abs(startX - targetX), sequence(direction, applicationCursor)); } /** @@ -117,15 +107,15 @@ export class AltClickHandler { * Calculates the number of wrapped rows between the unwrapped starting and * ending rows. These rows need to ignored since the cursor skips over them. */ - private _wrappedRowsCount(bufferService: IBufferService): number { + private _wrappedRowsCount(startY: number, targetY: number, bufferService: IBufferService): number { let wrappedRows = 0; - const startRow = this._startRow - this._wrappedRowsForRow(bufferService, this._startRow); - const endRow = this._endRow - this._wrappedRowsForRow(bufferService, this._endRow); + const startRow = startY - this._wrappedRowsForRow(bufferService, startY); + const endRow = targetY - this._wrappedRowsForRow(bufferService, targetY); for (let i = 0; i < Math.abs(startRow - endRow); i++) { - const direction = this._verticalDirection() === Direction.UP ? -1 : 1; + const direction = this._verticalDirection(startY, targetY) === Direction.UP ? -1 : 1; - if (this._lines.get(startRow + (direction * i)).isWrapped) { + if (bufferService.buffer.lines.get(startRow + (direction * i)).isWrapped) { wrappedRows++; } } @@ -157,18 +147,18 @@ export class AltClickHandler { /** * Determines if the right or left arrow is needed */ - private _horizontalDirection(bufferService: IBufferService, applicationCursor: boolean): Direction { + private _horizontalDirection(startX: number, startY: number, targetX: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): Direction { let startRow; - if (this._moveToRequestedRow(bufferService, applicationCursor).length > 0) { - startRow = this._endRow - this._wrappedRowsForRow(bufferService, this._endRow); + if (this._moveToRequestedRow(targetX, targetY, bufferService, applicationCursor).length > 0) { + startRow = targetY - this._wrappedRowsForRow(bufferService, targetY); } else { - startRow = this._startRow; + startRow = startY; } - if ((this._startCol < this._endCol && - startRow <= this._endRow) || // down/right or same y/right - (this._startCol >= this._endCol && - startRow < this._endRow)) { // down/left or same y/left + if ((startX < targetX && + startRow <= targetY) || // down/right or same y/right + (startX >= targetX && + startRow < targetY)) { // down/left or same y/left return Direction.RIGHT; } return Direction.LEFT; @@ -177,11 +167,8 @@ export class AltClickHandler { /** * Determines if the up or down arrow is needed */ - private _verticalDirection(): Direction { - if (this._startRow > this._endRow) { - return Direction.UP; - } - return Direction.DOWN; + private _verticalDirection(startY: number, targetY: number): Direction { + return startY > targetY ? Direction.UP : Direction.DOWN; } } From 13a63efdab01e404e45b5302d045dd4fb1eda168 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 22 Jun 2019 17:55:59 -0700 Subject: [PATCH 4/7] Change AltClickHandler to be functional --- src/SelectionManager.ts | 7 +- src/handlers/AltClickHandler.ts | 266 +++++++++++++++----------------- 2 files changed, 129 insertions(+), 144 deletions(-) diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index 344978cf..48a2fec8 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -8,13 +8,13 @@ import { IBuffer } from 'common/buffer/Types'; import { IBufferLine } from 'common/Types'; import * as Browser from 'common/Platform'; import { SelectionModel } from 'browser/selection/SelectionModel'; -import { AltClickHandler } from './handlers/AltClickHandler'; import { CellData } from 'common/buffer/CellData'; import { IDisposable } from 'xterm'; import { EventEmitter, IEvent } from 'common/EventEmitter'; import { ICharSizeService, IMouseService } from 'browser/services/Services'; import { IBufferService, IOptionsService } from 'common/services/Services'; import { getCoordsRelativeToElement } from 'browser/input/Mouse'; +import { moveToCellSequence } from 'handlers/AltClickHandler'; /** * The number of pixels the mouse needs to be above or below the viewport in @@ -659,9 +659,8 @@ export class SelectionManager implements ISelectionManager { false ); if (coordinates && coordinates[0] !== undefined && coordinates[1] !== undefined) { - this._terminal.handler( - (new AltClickHandler()).move(coordinates[0] - 1, coordinates[1] - 1, this._bufferService, this._terminal.applicationCursor) - ); + const sequence = moveToCellSequence(coordinates[0] - 1, coordinates[1] - 1, this._bufferService, this._terminal.applicationCursor); + this._terminal.handler(sequence); } } } else if (this.hasSelection) { diff --git a/src/handlers/AltClickHandler.ts b/src/handlers/AltClickHandler.ts index 8f8288a6..73e9b729 100644 --- a/src/handlers/AltClickHandler.ts +++ b/src/handlers/AltClickHandler.ts @@ -13,163 +13,149 @@ const enum Direction { LEFT = 'D' } -export class AltClickHandler { +/** + * Concatenates all the arrow sequences together. + * Resets the starting row to an unwrapped row, moves to the requested row, + * then moves to requested col. + */ +export function moveToCellSequence(targetX: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): string { + const startX = bufferService.buffer.x; + const startY = bufferService.buffer.y; - constructor( - ) { + // The alt buffer should try to navigate between rows + if (!bufferService.buffer.hasScrollback) { + return resetStartingRow(startX, startY, targetX, targetY, bufferService, applicationCursor) + + moveToRequestedRow(startY, targetY, bufferService, applicationCursor) + + moveToRequestedCol(startX, startY, targetX, targetY, bufferService, applicationCursor); } - /** - * Writes the escape sequences of arrows to the terminal - */ - public move(targetX: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): string { - return this._arrowSequences(targetX, targetY, bufferService, applicationCursor); + // Only move horizontally for the normal buffer + return moveHorizontallyOnly(startX, startY, targetX, targetY, bufferService, applicationCursor); +} + +/** + * If the initial position of the cursor is on a row that is wrapped, move the + * cursor up to the first row that is not wrapped to have accurate vertical + * positioning. + */ +function resetStartingRow(startX: number, startY: number, targetX: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): string { + if (moveToRequestedRow(startY, targetY, bufferService, applicationCursor).length === 0) { + return ''; + } + return repeat(bufferLine( + startX, startY, startX, + startY - wrappedRowsForRow(bufferService, startY), false, bufferService + ).length, sequence(Direction.LEFT, applicationCursor)); +} + +/** + * Using the reset starting and ending row, move to the requested row, + * ignoring wrapped rows + */ +function moveToRequestedRow(startY: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): string { + const startRow = startY - wrappedRowsForRow(bufferService, startY); + const endRow = targetY - wrappedRowsForRow(bufferService, targetY); + + const rowsToMove = Math.abs(startRow - endRow) - wrappedRowsCount(startY, targetY, bufferService); + + return repeat(rowsToMove, sequence(verticalDirection(startY, targetY), applicationCursor)); +} + +/** + * Move to the requested col on the ending row + */ +function moveToRequestedCol(startX: number, startY: number, targetX: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): string { + let startRow; + if (moveToRequestedRow(startY, targetY, bufferService, applicationCursor).length > 0) { + startRow = targetY - wrappedRowsForRow(bufferService, targetY); + } else { + startRow = startY; } - /** - * Concatenates all the arrow sequences together. - * Resets the starting row to an unwrapped row, moves to the requested row, - * then moves to requested col. - */ - private _arrowSequences(targetX: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): string { - const startX = bufferService.buffer.x; - const startY = bufferService.buffer.y; + const endRow = targetY; + const direction = horizontalDirection(startX, startY, targetX, targetY, bufferService, applicationCursor); - // The alt buffer should try to navigate between rows - if (!bufferService.buffer.hasScrollback) { - return this._resetStartingRow(startX, startY, targetX, targetY, bufferService, applicationCursor) + - this._moveToRequestedRow(startY, targetY, bufferService, applicationCursor) + - this._moveToRequestedCol(startX, startY, targetX, targetY, bufferService, applicationCursor); + return repeat(bufferLine( + startX, startRow, targetX, endRow, + direction === Direction.RIGHT, bufferService + ).length, sequence(direction, applicationCursor)); +} + +function moveHorizontallyOnly(startX: number, startY: number, targetX: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): string { + const direction = horizontalDirection(startX, startY, targetX, targetY, bufferService, applicationCursor); + return repeat(Math.abs(startX - targetX), sequence(direction, applicationCursor)); +} + +/** + * Utility functions + */ + +/** + * Calculates the number of wrapped rows between the unwrapped starting and + * ending rows. These rows need to ignored since the cursor skips over them. + */ +function wrappedRowsCount(startY: number, targetY: number, bufferService: IBufferService): number { + let wrappedRows = 0; + const startRow = startY - wrappedRowsForRow(bufferService, startY); + const endRow = targetY - wrappedRowsForRow(bufferService, targetY); + + for (let i = 0; i < Math.abs(startRow - endRow); i++) { + const direction = verticalDirection(startY, targetY) === Direction.UP ? -1 : 1; + + if (bufferService.buffer.lines.get(startRow + (direction * i)).isWrapped) { + wrappedRows++; } - - // Only move horizontally for the normal buffer - return this._moveHorizontallyOnly(startX, startY, targetX, targetY, bufferService, applicationCursor); } - /** - * If the initial position of the cursor is on a row that is wrapped, move the - * cursor up to the first row that is not wrapped to have accurate vertical - * positioning. - */ - private _resetStartingRow(startX: number, startY: number, targetX: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): string { - if (this._moveToRequestedRow(startY, targetY, bufferService, applicationCursor).length === 0) { - return ''; - } - return repeat(bufferLine( - startX, startY, startX, - startY - this._wrappedRowsForRow(bufferService, startY), false, bufferService - ).length, sequence(Direction.LEFT, applicationCursor)); + return wrappedRows; +} + +/** + * Calculates the number of wrapped rows that make up a given row. + * @param currentRow The row to determine how many wrapped rows make it up + */ +function wrappedRowsForRow(bufferService: IBufferService, currentRow: number): number { + let rowCount = 0; + let lineWraps = bufferService.buffer.lines.get(currentRow).isWrapped; + + while (lineWraps && currentRow >= 0 && currentRow < bufferService.rows) { + rowCount++; + currentRow--; + lineWraps = bufferService.buffer.lines.get(currentRow).isWrapped; } - /** - * Using the reset starting and ending row, move to the requested row, - * ignoring wrapped rows - */ - private _moveToRequestedRow(startY: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): string { - const startRow = startY - this._wrappedRowsForRow(bufferService, startY); - const endRow = targetY - this._wrappedRowsForRow(bufferService, targetY); + return rowCount; +} - const rowsToMove = Math.abs(startRow - endRow) - this._wrappedRowsCount(startY, targetY, bufferService); +/** + * Direction determiners + */ - return repeat(rowsToMove, sequence(this._verticalDirection(startY, targetY), applicationCursor)); +/** + * Determines if the right or left arrow is needed + */ +function horizontalDirection(startX: number, startY: number, targetX: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): Direction { + let startRow; + if (moveToRequestedRow(targetX, targetY, bufferService, applicationCursor).length > 0) { + startRow = targetY - wrappedRowsForRow(bufferService, targetY); + } else { + startRow = startY; } - /** - * Move to the requested col on the ending row - */ - private _moveToRequestedCol(startX: number, startY: number, targetX: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): string { - let startRow; - if (this._moveToRequestedRow(startY, targetY, bufferService, applicationCursor).length > 0) { - startRow = targetY - this._wrappedRowsForRow(bufferService, targetY); - } else { - startRow = startY; - } - - const endRow = targetY; - const direction = this._horizontalDirection(startX, startY, targetX, targetY, bufferService, applicationCursor); - - return repeat(bufferLine( - startX, startRow, targetX, endRow, - direction === Direction.RIGHT, bufferService - ).length, sequence(direction, applicationCursor)); + if ((startX < targetX && + startRow <= targetY) || // down/right or same y/right + (startX >= targetX && + startRow < targetY)) { // down/left or same y/left + return Direction.RIGHT; } + return Direction.LEFT; +} - private _moveHorizontallyOnly(startX: number, startY: number, targetX: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): string { - const direction = this._horizontalDirection(startX, startY, targetX, targetY, bufferService, applicationCursor); - return repeat(Math.abs(startX - targetX), sequence(direction, applicationCursor)); - } - - /** - * Utility functions - */ - - /** - * Calculates the number of wrapped rows between the unwrapped starting and - * ending rows. These rows need to ignored since the cursor skips over them. - */ - private _wrappedRowsCount(startY: number, targetY: number, bufferService: IBufferService): number { - let wrappedRows = 0; - const startRow = startY - this._wrappedRowsForRow(bufferService, startY); - const endRow = targetY - this._wrappedRowsForRow(bufferService, targetY); - - for (let i = 0; i < Math.abs(startRow - endRow); i++) { - const direction = this._verticalDirection(startY, targetY) === Direction.UP ? -1 : 1; - - if (bufferService.buffer.lines.get(startRow + (direction * i)).isWrapped) { - wrappedRows++; - } - } - - return wrappedRows; - } - - /** - * Calculates the number of wrapped rows that make up a given row. - * @param currentRow The row to determine how many wrapped rows make it up - */ - private _wrappedRowsForRow(bufferService: IBufferService, currentRow: number): number { - let rowCount = 0; - let lineWraps = bufferService.buffer.lines.get(currentRow).isWrapped; - - while (lineWraps && currentRow >= 0 && currentRow < bufferService.rows) { - rowCount++; - currentRow--; - lineWraps = bufferService.buffer.lines.get(currentRow).isWrapped; - } - - return rowCount; - } - - /** - * Direction determiners - */ - - /** - * Determines if the right or left arrow is needed - */ - private _horizontalDirection(startX: number, startY: number, targetX: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): Direction { - let startRow; - if (this._moveToRequestedRow(targetX, targetY, bufferService, applicationCursor).length > 0) { - startRow = targetY - this._wrappedRowsForRow(bufferService, targetY); - } else { - startRow = startY; - } - - if ((startX < targetX && - startRow <= targetY) || // down/right or same y/right - (startX >= targetX && - startRow < targetY)) { // down/left or same y/left - return Direction.RIGHT; - } - return Direction.LEFT; - } - - /** - * Determines if the up or down arrow is needed - */ - private _verticalDirection(startY: number, targetY: number): Direction { - return startY > targetY ? Direction.UP : Direction.DOWN; - } +/** + * Determines if the up or down arrow is needed + */ +function verticalDirection(startY: number, targetY: number): Direction { + return startY > targetY ? Direction.UP : Direction.DOWN; } /** From 22a7e48f04ab014d79fc8d8e1e5aabdca0a1bfbc Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 22 Jun 2019 18:00:03 -0700 Subject: [PATCH 5/7] Move alt click into browser --- src/SelectionManager.ts | 2 +- .../input/MoveToCell.ts} | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) rename src/{handlers/AltClickHandler.ts => browser/input/MoveToCell.ts} (96%) diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index 48a2fec8..56d32b56 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -14,7 +14,7 @@ import { EventEmitter, IEvent } from 'common/EventEmitter'; import { ICharSizeService, IMouseService } from 'browser/services/Services'; import { IBufferService, IOptionsService } from 'common/services/Services'; import { getCoordsRelativeToElement } from 'browser/input/Mouse'; -import { moveToCellSequence } from 'handlers/AltClickHandler'; +import { moveToCellSequence } from 'browser/input/MoveToCell'; /** * The number of pixels the mouse needs to be above or below the viewport in diff --git a/src/handlers/AltClickHandler.ts b/src/browser/input/MoveToCell.ts similarity index 96% rename from src/handlers/AltClickHandler.ts rename to src/browser/input/MoveToCell.ts index 73e9b729..406ec807 100644 --- a/src/handlers/AltClickHandler.ts +++ b/src/browser/input/MoveToCell.ts @@ -101,8 +101,8 @@ function wrappedRowsCount(startY: number, targetY: number, bufferService: IBuffe for (let i = 0; i < Math.abs(startRow - endRow); i++) { const direction = verticalDirection(startY, targetY) === Direction.UP ? -1 : 1; - - if (bufferService.buffer.lines.get(startRow + (direction * i)).isWrapped) { + const line = bufferService.buffer.lines.get(startRow + (direction * i)); + if (line && line.isWrapped) { wrappedRows++; } } @@ -116,12 +116,13 @@ function wrappedRowsCount(startY: number, targetY: number, bufferService: IBuffe */ function wrappedRowsForRow(bufferService: IBufferService, currentRow: number): number { let rowCount = 0; - let lineWraps = bufferService.buffer.lines.get(currentRow).isWrapped; + let line = bufferService.buffer.lines.get(currentRow); + let lineWraps = line && line.isWrapped; while (lineWraps && currentRow >= 0 && currentRow < bufferService.rows) { rowCount++; - currentRow--; - lineWraps = bufferService.buffer.lines.get(currentRow).isWrapped; + line = bufferService.buffer.lines.get(--currentRow); + lineWraps = line && line.isWrapped; } return rowCount; From f903dbde4508c8c79f751b10667ee7b51fe8362d Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 22 Jun 2019 18:16:33 -0700 Subject: [PATCH 6/7] Add some tests for MoveToCell --- src/browser/input/MoveToCell.test.ts | 49 ++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 src/browser/input/MoveToCell.test.ts diff --git a/src/browser/input/MoveToCell.test.ts b/src/browser/input/MoveToCell.test.ts new file mode 100644 index 00000000..bc4012c0 --- /dev/null +++ b/src/browser/input/MoveToCell.test.ts @@ -0,0 +1,49 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { assert } from 'chai'; +import { IBufferService } from 'common/services/Services'; +import { MockBufferService } from 'common/TestUtils.test'; +import { moveToCellSequence } from './MoveToCell'; + +describe('MoveToCell', () => { + let bufferService: IBufferService; + + beforeEach(() => { + bufferService = new MockBufferService(5, 5); + bufferService.buffer.x = 3; + bufferService.buffer.y = 3; + }); + + describe('normal buffer', () => { + it('should use the right directional escape sequences', () => { + assert.equal(moveToCellSequence(2, 3, bufferService, false), '\x1b[D'); + assert.equal(moveToCellSequence(4, 3, bufferService, false), '\x1b[C'); + }); + it('should ignore the Y value', () => { + assert.equal(moveToCellSequence(1, 1, bufferService, false), '\x1b[D\x1b[D'); + assert.equal(moveToCellSequence(1, 2, bufferService, false), '\x1b[D\x1b[D'); + assert.equal(moveToCellSequence(1, 3, bufferService, false), '\x1b[D\x1b[D'); + assert.equal(moveToCellSequence(1, 4, bufferService, false), '\x1b[D\x1b[D'); + assert.equal(moveToCellSequence(1, 5, bufferService, false), '\x1b[D\x1b[D'); + }); + it('should use the correct character for application cursor', () => { + assert.equal(moveToCellSequence(2, 1, bufferService, false), '\x1b[D'); + assert.equal(moveToCellSequence(2, 1, bufferService, true), '\x1bOD'); + }); + }); + + describe('alt buffer', () => { + beforeEach(() => { + bufferService.buffers.activateAltBuffer(); + bufferService.buffer.x = 3; + bufferService.buffer.y = 3; + }); + + it('should move the cursor across rows', () => { + assert.equal(moveToCellSequence(4, 4, bufferService, false), '\x1b[B\x1b[C'); + }); + }); +}); From 87897a7ee540215a9730496c7c59b4bd2720e0c5 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 22 Jun 2019 18:27:25 -0700 Subject: [PATCH 7/7] Move selection manager types into browser --- src/Clipboard.ts | 2 +- src/SelectionManager.ts | 3 ++- src/TestUtils.test.ts | 3 ++- src/Types.d.ts | 19 +------------------ src/browser/selection/Types.d.ts | 22 ++++++++++++++++++++++ 5 files changed, 28 insertions(+), 21 deletions(-) create mode 100644 src/browser/selection/Types.d.ts diff --git a/src/Clipboard.ts b/src/Clipboard.ts index 75b0da8e..1ee232ee 100644 --- a/src/Clipboard.ts +++ b/src/Clipboard.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { ISelectionManager } from './Types'; +import { ISelectionManager } from 'browser/selection/Types'; /** * Prepares text to be pasted into the terminal by normalizing the line endings diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index 56d32b56..4133fccc 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -3,7 +3,8 @@ * @license MIT */ -import { ITerminal, ISelectionManager, ISelectionRedrawRequestEvent } from './Types'; +import { ITerminal } from './Types'; +import { ISelectionManager, ISelectionRedrawRequestEvent } from 'browser/selection/Types'; import { IBuffer } from 'common/buffer/Types'; import { IBufferLine } from 'common/Types'; import * as Browser from 'common/Platform'; diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index 07f91705..8394f5a7 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -4,7 +4,7 @@ */ import { IRenderer, IRenderDimensions, CharacterJoinerHandler } from 'browser/renderer/Types'; -import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminal, IBrowser, ISelectionManager, ITerminalOptions, ILinkifier, ILinkMatcherOptions } from './Types'; +import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminal, IBrowser, ITerminalOptions, ILinkifier, ILinkMatcherOptions } from './Types'; import { IBuffer, IBufferStringIterator, IBufferSet } from 'common/buffer/Types'; import { IBufferLine, ICellData, IAttributeData, ICircularList, XtermListener } from 'common/Types'; import { Buffer } from 'common/buffer/Buffer'; @@ -15,6 +15,7 @@ import { AttributeData } from 'common/buffer/AttributeData'; import { IColorManager, IColorSet } from 'browser/Types'; import { IOptionsService } from 'common/services/Services'; import { EventEmitter } from 'common/EventEmitter'; +import { ISelectionManager } from 'browser/selection/Types'; export class TestTerminal extends Terminal { writeSync(data: string): void { diff --git a/src/Types.d.ts b/src/Types.d.ts index 5987d605..c3957ab7 100644 --- a/src/Types.d.ts +++ b/src/Types.d.ts @@ -9,6 +9,7 @@ import { IEvent, IEventEmitter } from 'common/EventEmitter'; import { IColorSet } from 'browser/Types'; import { IOptionsService } from 'common/services/Services'; import { IBuffer, IBufferSet } from 'common/buffer/Types'; +import { ISelectionManager } from 'browser/selection/Types'; export type CustomKeyEventHandler = (event: KeyboardEvent) => boolean; @@ -296,24 +297,6 @@ export interface ITerminalOptions extends IPublicTerminalOptions { useFlowControl?: boolean; } -export interface ISelectionManager { - selectionText: string; - selectionStart: [number, number]; - selectionEnd: [number, number]; - - disable(): void; - enable(): void; - setSelection(row: number, col: number, length: number): void; - isClickInSelection(event: MouseEvent): boolean; - selectWordAtCursor(event: MouseEvent): void; -} - -export interface ISelectionRedrawRequestEvent { - start: [number, number]; - end: [number, number]; - columnSelectMode: boolean; -} - export interface ILinkifier { onLinkHover: IEvent; onLinkLeave: IEvent; diff --git a/src/browser/selection/Types.d.ts b/src/browser/selection/Types.d.ts new file mode 100644 index 00000000..241731f1 --- /dev/null +++ b/src/browser/selection/Types.d.ts @@ -0,0 +1,22 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +export interface ISelectionManager { + selectionText: string; + selectionStart: [number, number]; + selectionEnd: [number, number]; + + disable(): void; + enable(): void; + setSelection(row: number, col: number, length: number): void; + isClickInSelection(event: MouseEvent): boolean; + selectWordAtCursor(event: MouseEvent): void; +} + +export interface ISelectionRedrawRequestEvent { + start: [number, number]; + end: [number, number]; + columnSelectMode: boolean; +}