From 6674f05cc8eac07bde01c9a3185a475de835407c Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 11 May 2019 23:35:31 -0700 Subject: [PATCH 1/5] Implement setSelection API Fixes #1443 --- src/Terminal.ts | 10 ++++++++++ src/TestUtils.test.ts | 3 +++ src/Types.ts | 1 + src/public/Terminal.ts | 3 +++ typings/xterm.d.ts | 8 ++++++++ 5 files changed, 25 insertions(+) diff --git a/src/Terminal.ts b/src/Terminal.ts index 6da60458ec..56205d1e6a 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -1535,6 +1535,16 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II return this.selectionManager ? this.selectionManager.hasSelection : false; } + /** + * Selects text within the terminal. + * @param column The column the selection starts at.. + * @param row The row the selection starts at. + * @param length The length of the selection. + */ + public setSelection(column: number, row: number, length: number): void { + this.selectionManager.setSelection(column, row, length); + } + /** * Gets the terminal's current selection, this is useful for implementing copy * behavior outside of xterm.js. diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index 1d5008c8b3..f5008a4356 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -80,6 +80,9 @@ export class MockTerminal implements ITerminal { hasSelection(): boolean { throw new Error('Method not implemented.'); } + setSelection(column: number, row: number, length: number): void { + throw new Error('Method not implemented.'); + } getSelection(): string { throw new Error('Method not implemented.'); } diff --git a/src/Types.ts b/src/Types.ts index 5841626bab..238dbd416e 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -249,6 +249,7 @@ export interface IPublicTerminal extends IDisposable, IEventEmitter { deregisterCharacterJoiner(joinerId: number): void; addMarker(cursorYOffset: number): IMarker; hasSelection(): boolean; + setSelection(column: number, row: number, length: number): void; getSelection(): string; clearSelection(): void; selectAll(): void; diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts index 0c6a77457e..3f1cfd0cce 100644 --- a/src/public/Terminal.ts +++ b/src/public/Terminal.ts @@ -96,6 +96,9 @@ export class Terminal implements ITerminalApi { public hasSelection(): boolean { return this._core.hasSelection(); } + public setSelection(column: number, row: number, length: number): void { + this._core.setSelection(column, row, length); + } public getSelection(): string { return this._core.getSelection(); } diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 0714f55f0a..59eebc14f1 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -670,6 +670,14 @@ declare module 'xterm' { */ hasSelection(): boolean; + /** + * Selects text within the terminal. + * @param column The column the selection starts at.. + * @param row The row the selection starts at. + * @param length The length of the selection. + */ + setSelection(column: number, row: number, length: number): void; + /** * Gets the terminal's current selection, this is useful for implementing * copy behavior outside of xterm.js. From da7c39e5267cf4f198638b68713576ad57492d2c Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 11 May 2019 23:37:42 -0700 Subject: [PATCH 2/5] Add API test --- src/public/Terminal.api.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/public/Terminal.api.ts b/src/public/Terminal.api.ts index 031e03d54a..772ffdc35f 100644 --- a/src/public/Terminal.api.ts +++ b/src/public/Terminal.api.ts @@ -99,6 +99,9 @@ describe('API Integration Tests', () => { await page.evaluate(`window.term.clearSelection()`); assert.equal(await page.evaluate(`window.term.hasSelection()`), false); assert.equal(await page.evaluate(`window.term.getSelection()`), ''); + await page.evaluate(`window.term.setSelection(1, 2, 2)`) + assert.equal(await page.evaluate(`window.term.hasSelection()`), true); + assert.equal(await page.evaluate(`window.term.getSelection()`), 'oo'); }); it('focus, blur', async function(): Promise { From 5bbbbf7348e4f0e65afb271912a7def198769404 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 11 May 2019 23:52:38 -0700 Subject: [PATCH 3/5] Expose getSelectionPosition and select APIs instead --- src/Terminal.ts | 17 ++++++++++-- src/TestUtils.test.ts | 9 ++++-- src/Types.ts | 5 ++-- src/addons/search/SearchHelper.ts | 2 +- src/public/Terminal.api.ts | 2 +- src/public/Terminal.ts | 9 ++++-- typings/xterm.d.ts | 46 +++++++++++++++++++++++++------ 7 files changed, 70 insertions(+), 20 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 56205d1e6a..58e58b6ebd 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -43,7 +43,7 @@ import { DEFAULT_BELL_SOUND, SoundManager } from './SoundManager'; import { MouseZoneManager } from './MouseZoneManager'; import { AccessibilityManager } from './AccessibilityManager'; import { ScreenDprMonitor } from './ui/ScreenDprMonitor'; -import { ITheme, IMarker, IDisposable } from 'xterm'; +import { ITheme, IMarker, IDisposable, ISelectionPosition } from 'xterm'; import { removeTerminalFromCache } from './renderer/atlas/CharAtlasCache'; import { DomRenderer } from './renderer/dom/DomRenderer'; import { IKeyboardEvent } from './common/Types'; @@ -1541,7 +1541,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II * @param row The row the selection starts at. * @param length The length of the selection. */ - public setSelection(column: number, row: number, length: number): void { + public select(column: number, row: number, length: number): void { this.selectionManager.setSelection(column, row, length); } @@ -1553,6 +1553,19 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II return this.selectionManager ? this.selectionManager.selectionText : ''; } + public getSelectionPosition(): ISelectionPosition | undefined { + if (!this.selectionManager.hasSelection) { + return undefined; + } + + return { + startColumn: this.selectionManager.selectionStart[0], + startRow: this.selectionManager.selectionStart[1], + endColumn: this.selectionManager.selectionEnd[0], + endRow: this.selectionManager.selectionEnd[1] + }; + } + /** * Clears the current terminal selection. */ diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index f5008a4356..3f53c7d3a9 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -9,7 +9,7 @@ import { IBufferLine, ICellData, IAttributeData } from './core/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 { ITheme, IDisposable, IMarker, IEvent, ISelectionPosition } from 'xterm'; import { Terminal } from './Terminal'; import { AttributeData } from './core/buffer/BufferLine'; @@ -80,15 +80,18 @@ export class MockTerminal implements ITerminal { hasSelection(): boolean { throw new Error('Method not implemented.'); } - setSelection(column: number, row: number, length: number): void { + getSelection(): string { throw new Error('Method not implemented.'); } - getSelection(): string { + getSelectionPosition(): ISelectionPosition | undefined { throw new Error('Method not implemented.'); } clearSelection(): void { throw new Error('Method not implemented.'); } + select(column: number, row: number, length: number): void { + throw new Error('Method not implemented.'); + } selectAll(): void { throw new Error('Method not implemented.'); } diff --git a/src/Types.ts b/src/Types.ts index 238dbd416e..26a358146a 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { ITerminalOptions as IPublicTerminalOptions, IEventEmitter, IDisposable, IMarker } from 'xterm'; +import { ITerminalOptions as IPublicTerminalOptions, IEventEmitter, IDisposable, IMarker, ISelectionPosition } from 'xterm'; import { IColorSet, IRenderer } from './renderer/Types'; import { ICharset, IAttributeData, ICellData, IBufferLine, CharData } from './core/Types'; import { ICircularList } from './common/Types'; @@ -249,9 +249,10 @@ export interface IPublicTerminal extends IDisposable, IEventEmitter { deregisterCharacterJoiner(joinerId: number): void; addMarker(cursorYOffset: number): IMarker; hasSelection(): boolean; - setSelection(column: number, row: number, length: number): void; getSelection(): string; + getSelectionPosition(): ISelectionPosition | undefined; clearSelection(): void; + select(column: number, row: number, length: number): void; selectAll(): void; selectLines(start: number, end: number): void; dispose(): void; diff --git a/src/addons/search/SearchHelper.ts b/src/addons/search/SearchHelper.ts index 7db1ed4337..3dc9960334 100644 --- a/src/addons/search/SearchHelper.ts +++ b/src/addons/search/SearchHelper.ts @@ -341,7 +341,7 @@ export class SearchHelper implements ISearchHelper { this._terminal.clearSelection(); return false; } - this._terminal._core.selectionManager.setSelection(result.col, result.row, result.term.length); + this._terminal.select(result.col, result.row, result.term.length); this._terminal.scrollLines(result.row - this._terminal._core.buffer.ydisp); return true; } diff --git a/src/public/Terminal.api.ts b/src/public/Terminal.api.ts index 772ffdc35f..3f289f84cf 100644 --- a/src/public/Terminal.api.ts +++ b/src/public/Terminal.api.ts @@ -99,7 +99,7 @@ describe('API Integration Tests', () => { await page.evaluate(`window.term.clearSelection()`); assert.equal(await page.evaluate(`window.term.hasSelection()`), false); assert.equal(await page.evaluate(`window.term.getSelection()`), ''); - await page.evaluate(`window.term.setSelection(1, 2, 2)`) + await page.evaluate(`window.term.select(1, 2, 2)`) assert.equal(await page.evaluate(`window.term.hasSelection()`), true); assert.equal(await page.evaluate(`window.term.getSelection()`), 'oo'); }); diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts index 3f1cfd0cce..964069194b 100644 --- a/src/public/Terminal.ts +++ b/src/public/Terminal.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, IBuffer as IBufferApi, IBufferLine as IBufferLineApi, IBufferCell as IBufferCellApi } from 'xterm'; +import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, IBuffer as IBufferApi, IBufferLine as IBufferLineApi, IBufferCell as IBufferCellApi, ISelectionPosition } from 'xterm'; import { ITerminal, IBuffer } from '../Types'; import { IBufferLine } from '../core/Types'; import { Terminal as TerminalCore } from '../Terminal'; @@ -96,12 +96,15 @@ export class Terminal implements ITerminalApi { public hasSelection(): boolean { return this._core.hasSelection(); } - public setSelection(column: number, row: number, length: number): void { - this._core.setSelection(column, row, length); + public select(column: number, row: number, length: number): void { + this._core.select(column, row, length); } public getSelection(): string { return this._core.getSelection(); } + public getSelectionPosition(): ISelectionPosition | undefined { + return this._core.getSelectionPosition(); + } public clearSelection(): void { this._core.clearSelection(); } diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 59eebc14f1..466ddb6d6e 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -670,25 +670,30 @@ declare module 'xterm' { */ hasSelection(): boolean; - /** - * Selects text within the terminal. - * @param column The column the selection starts at.. - * @param row The row the selection starts at. - * @param length The length of the selection. - */ - setSelection(column: number, row: number, length: number): void; - /** * Gets the terminal's current selection, this is useful for implementing * copy behavior outside of xterm.js. */ getSelection(): string; + /** + * Gets the selection position or undefined if there is no selection. + */ + getSelectionPosition(): ISelectionPosition | undefined; + /** * Clears the current terminal selection. */ clearSelection(): void; + /** + * Selects text within the terminal. + * @param column The column the selection starts at.. + * @param row The row the selection starts at. + * @param length The length of the selection. + */ + select(column: number, row: number, length: number): void; + /** * Selects all text within the terminal. */ @@ -872,6 +877,31 @@ declare module 'xterm' { static applyAddon(addon: any): void; } + /** + * An object representing a selecrtion within the terminal. + */ + interface ISelectionPosition { + /** + * The start column of the selection. + */ + startColumn: number; + + /** + * The start row of the selection. + */ + startRow: number; + + /** + * The end column of the selection. + */ + endColumn: number; + + /** + * The end row of the selection. + */ + endRow: number; + } + interface IBuffer { /** * The y position of the cursor. This ranges between `0` (when the From 9ca9f7296efdedfc0db20a57007749a69609c29d Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 11 May 2019 23:56:34 -0700 Subject: [PATCH 4/5] Use new APIs in selection addon --- src/addons/search/Interfaces.ts | 1 - src/addons/search/SearchHelper.ts | 24 ++++++++++-------------- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/src/addons/search/Interfaces.ts b/src/addons/search/Interfaces.ts index a1f05895e7..27e3c4b3df 100644 --- a/src/addons/search/Interfaces.ts +++ b/src/addons/search/Interfaces.ts @@ -8,7 +8,6 @@ import { Terminal } from 'xterm'; // TODO: Don't rely on this private API export interface ITerminalCore { buffer: any; - selectionManager: any; } export interface ISearchAddonTerminal extends Terminal { diff --git a/src/addons/search/SearchHelper.ts b/src/addons/search/SearchHelper.ts index 3dc9960334..c42167fa5d 100644 --- a/src/addons/search/SearchHelper.ts +++ b/src/addons/search/SearchHelper.ts @@ -35,25 +35,23 @@ export class SearchHelper implements ISearchHelper { * @return Whether a result was found. */ public findNext(term: string, searchOptions?: ISearchOptions): boolean { - const selectionManager = this._terminal._core.selectionManager; const {incremental} = searchOptions; let result: ISearchResult; if (!term || term.length === 0) { - selectionManager.clearSelection(); + this._terminal.clearSelection(); return false; } let startCol: number = 0; let startRow = this._terminal._core.buffer.ydisp; - if (selectionManager.selectionEnd) { + if (this._terminal.hasSelection()) { // Start from the selection end if there is a selection // For incremental search, use existing row - if (this._terminal.getSelection().length !== 0) { - startRow = incremental ? selectionManager.selectionStart[1] : selectionManager.selectionEnd[1]; - startCol = incremental ? selectionManager.selectionStart[0] : selectionManager.selectionEnd[0]; - } + const currentSelection = this._terminal.getSelectionPosition(); + startRow = incremental ? currentSelection.startRow : currentSelection.endRow; + startCol = incremental ? currentSelection.startColumn : currentSelection.endColumn; } this._initLinesCache(); @@ -109,11 +107,10 @@ export class SearchHelper implements ISearchHelper { * @return Whether a result was found. */ public findPrevious(term: string, searchOptions?: ISearchOptions): boolean { - const selectionManager = this._terminal._core.selectionManager; let result: ISearchResult; if (!term || term.length === 0) { - selectionManager.clearSelection(); + this._terminal.clearSelection(); return false; } @@ -121,12 +118,11 @@ export class SearchHelper implements ISearchHelper { let startRow = this._terminal._core.buffer.ydisp + this._terminal.rows - 1; let startCol = this._terminal.cols; - if (selectionManager.selectionStart) { + if (this._terminal.hasSelection()) { // Start from the selection start if there is a selection - if (this._terminal.getSelection().length !== 0) { - startRow = selectionManager.selectionStart[1]; - startCol = selectionManager.selectionStart[0]; - } + const currentSelection = this._terminal.getSelectionPosition(); + startRow = currentSelection.startRow; + startCol = currentSelection.startColumn; } this._initLinesCache(); From a7fa58cc60073ef2ab23e3274d5e32eba5fea43b Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 11 May 2019 23:59:51 -0700 Subject: [PATCH 5/5] Add API test for getSelectionPosition --- src/public/Terminal.api.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/public/Terminal.api.ts b/src/public/Terminal.api.ts index 3f289f84cf..a16f2c62a5 100644 --- a/src/public/Terminal.api.ts +++ b/src/public/Terminal.api.ts @@ -89,19 +89,23 @@ describe('API Integration Tests', () => { it('selection', async function(): Promise { this.timeout(10000); - await openTerminal({ rows: 5 }); + await openTerminal({ rows: 5, cols: 5 }); await page.evaluate(`window.term.write('\\n\\nfoo\\n\\n\\rbar\\n\\n\\rbaz')`); assert.equal(await page.evaluate(`window.term.hasSelection()`), false); assert.equal(await page.evaluate(`window.term.getSelection()`), ''); + assert.deepEqual(await page.evaluate(`window.term.getSelectionPosition()`), undefined); await page.evaluate(`window.term.selectAll()`); assert.equal(await page.evaluate(`window.term.hasSelection()`), true); assert.equal(await page.evaluate(`window.term.getSelection()`), '\n\nfoo\n\nbar\n\nbaz'); + assert.deepEqual(await page.evaluate(`window.term.getSelectionPosition()`), { startColumn: 0, startRow: 0, endColumn: 5, endRow: 6 }); await page.evaluate(`window.term.clearSelection()`); assert.equal(await page.evaluate(`window.term.hasSelection()`), false); assert.equal(await page.evaluate(`window.term.getSelection()`), ''); - await page.evaluate(`window.term.select(1, 2, 2)`) + assert.deepEqual(await page.evaluate(`window.term.getSelectionPosition()`), undefined); + await page.evaluate(`window.term.select(1, 2, 2)`); assert.equal(await page.evaluate(`window.term.hasSelection()`), true); assert.equal(await page.evaluate(`window.term.getSelection()`), 'oo'); + assert.deepEqual(await page.evaluate(`window.term.getSelectionPosition()`), { startColumn: 1, startRow: 2, endColumn: 3, endRow: 2 }); }); it('focus, blur', async function(): Promise {