diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index 9cfa530..7f21c2b 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -45,6 +45,11 @@ const LINE_DATA_WIDTH_INDEX = 2; const NON_BREAKING_SPACE_CHAR = String.fromCharCode(160); const ALL_NON_BREAKING_SPACE_REGEX = new RegExp(NON_BREAKING_SPACE_CHAR, 'g'); +interface IWordPosition { + start: number; + length: number; +} + /** * A class that manages the selection of the terminal. With help from * SelectionModel, SelectionManager handles with all logic associated with @@ -84,6 +89,11 @@ export class SelectionManager extends EventEmitter { */ private _isLineSelectModeActive: boolean; + /** + * Whether word select mode is active, this occurs after a double click. + */ + private _isWordSelectModeActive: boolean; + /** * A setInterval timer that is active while the mouse is down whose callback * scrolls the viewport when necessary. @@ -113,6 +123,7 @@ export class SelectionManager extends EventEmitter { this._model = new SelectionModel(_terminal); this._lastMouseDownTime = 0; this._isLineSelectModeActive = false; + this._isWordSelectModeActive = false; } /** @@ -412,6 +423,7 @@ export class SelectionManager extends EventEmitter { this._model.selectionStartLength = 0; this._model.isSelectAllActive = false; this._isLineSelectModeActive = false; + this._isWordSelectModeActive = false; this._model.selectionStart = this._getMouseBufferCoords(event); if (this._model.selectionStart) { this._model.selectionEnd = null; @@ -431,6 +443,7 @@ export class SelectionManager extends EventEmitter { private _onDoubleClick(event: MouseEvent): void { const coords = this._getMouseBufferCoords(event); if (coords) { + this._isWordSelectModeActive = true; this._selectWordAt(coords); } } @@ -496,6 +509,10 @@ export class SelectionManager extends EventEmitter { } } + if (this._isWordSelectModeActive) { + this._selectToWordAt(this._model.selectionEnd); + } + // Determine the amount of scrolling that will happen. this._dragScrollAmount = this._getMouseEventScrollAmount(event); @@ -567,11 +584,10 @@ export class SelectionManager extends EventEmitter { } /** - * Selects the word at the coordinates specified. Words are defined as all - * non-whitespace characters. + * Gets positional information for the word at the coordinated specified. * @param coords The coordinates to get the word at. */ - protected _selectWordAt(coords: [number, number]): void { + private _getWordAt(coords: [number, number]): IWordPosition { const bufferLine = this._buffer.get(coords[1]); const line = this._translateBufferLineToString(bufferLine, false); @@ -630,9 +646,28 @@ export class SelectionManager extends EventEmitter { } } - // Record the resulting selection - this._model.selectionStart = [startIndex + charOffset - leftWideCharCount, coords[1]]; - this._model.selectionStartLength = Math.min(endIndex - startIndex + leftWideCharCount + rightWideCharCount + 1/*include endIndex char*/, this._terminal.cols); + const start = startIndex + charOffset - leftWideCharCount; + const length = Math.min(endIndex - startIndex + leftWideCharCount + rightWideCharCount + 1/*include endIndex char*/, this._terminal.cols); + return {start, length}; + } + + /** + * Selects the word at the coordinates specified. + * @param coords The coordinates to get the word at. + */ + protected _selectWordAt(coords: [number, number]): void { + const wordPosition = this._getWordAt(coords); + this._model.selectionStart = [wordPosition.start, coords[1]]; + this._model.selectionStartLength = wordPosition.length; + } + + /** + * Sets the selection end to the word at the coordinated specified. + * @param coords The coordinates to get the word at. + */ + private _selectToWordAt(coords: [number, number]): void { + const wordPosition = this._getWordAt(coords); + this._model.selectionEnd = [this._model.areSelectionValuesReversed() ? wordPosition.start : (wordPosition.start + wordPosition.length), coords[1]]; } /** diff --git a/src/SelectionModel.test.ts b/src/SelectionModel.test.ts index e862959..ab22b77 100644 --- a/src/SelectionModel.test.ts +++ b/src/SelectionModel.test.ts @@ -11,8 +11,6 @@ class TestSelectionModel extends SelectionModel { ) { super(terminal); } - - public areSelectionValuesReversed(): boolean { return this._areSelectionValuesReversed(); } } describe('SelectionManager', () => { @@ -39,7 +37,7 @@ describe('SelectionManager', () => { }); }); - describe('_areSelectionValuesReversed', () => { + describe('areSelectionValuesReversed', () => { it('should return true when the selection end is before selection start', () => { model.selectionStart = [1, 0]; model.selectionEnd = [0, 0]; diff --git a/src/SelectionModel.ts b/src/SelectionModel.ts index 403f42e..410af3b 100644 --- a/src/SelectionModel.ts +++ b/src/SelectionModel.ts @@ -59,7 +59,7 @@ export class SelectionModel { return this.selectionStart; } - return this._areSelectionValuesReversed() ? this.selectionEnd : this.selectionStart; + return this.areSelectionValuesReversed() ? this.selectionEnd : this.selectionStart; } /** @@ -76,7 +76,7 @@ export class SelectionModel { } // Use the selection start if the end doesn't exist or they're reversed - if (!this.selectionEnd || this._areSelectionValuesReversed()) { + if (!this.selectionEnd || this.areSelectionValuesReversed()) { return [this.selectionStart[0] + this.selectionStartLength, this.selectionStart[1]]; } @@ -93,7 +93,7 @@ export class SelectionModel { /** * Returns whether the selection start and end are reversed. */ - protected _areSelectionValuesReversed(): boolean { + public areSelectionValuesReversed(): boolean { const start = this.selectionStart; const end = this.selectionEnd; return start[1] > end[1] || (start[1] === end[1] && start[0] > end[0]);