From a0ccc9b78b840227e52c07bcd6644c4293954a16 Mon Sep 17 00:00:00 2001 From: Michael Miller Date: Tue, 18 Jun 2019 15:28:41 -0700 Subject: [PATCH 01/13] Fix a type in the README "Perfomant" should be "Performant" --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4707f195f..fb6883f70 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Xterm.js is a front-end component written in TypeScript that lets applications b ## Features - **Terminal apps just work**: Xterm.js works with most terminal apps such as `bash`, `vim` and `tmux`, this includes support for curses-based apps and mouse event support. -- **Perfomant**: Xterm.js is *really* fast, it even includes a GPU-accelerated renderer. +- **Performant**: Xterm.js is *really* fast, it even includes a GPU-accelerated renderer. - **Rich unicode support**: Supports CJK, emojis and IMEs. - **Self-contained**: Requires zero dependencies to work. - **Accessible**: Screen reader support can be turned on using the `screenReaderMode` option. From 1b2811075cf1499fcb726c4afd9c4b8cc6ddfcd0 Mon Sep 17 00:00:00 2001 From: Max Risuhin Date: Fri, 21 Jun 2019 16:35:26 +0300 Subject: [PATCH 02/13] Define wordSeparator option --- src/SelectionManager.ts | 8 +------- src/common/services/OptionsService.ts | 4 +++- src/common/services/Services.d.ts | 2 ++ src/public/Terminal.ts | 4 ++-- typings/xterm.d.ts | 10 ++++++++-- 5 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index 0b77d1830..fb2caba89 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -38,12 +38,6 @@ const DRAG_SCROLL_INTERVAL = 50; */ const ALT_CLICK_MOVE_CURSOR_TIME = 500; -/** - * A string containing all characters that are considered word separated by the - * double click to select work logic. - */ -const WORD_SEPARATORS = ' ()[]{}\'"'; - const NON_BREAKING_SPACE_CHAR = String.fromCharCode(160); const ALL_NON_BREAKING_SPACE_REGEX = new RegExp(NON_BREAKING_SPACE_CHAR, 'g'); @@ -914,7 +908,7 @@ export class SelectionManager implements ISelectionManager { if (cell.getWidth() === 0) { return false; } - return WORD_SEPARATORS.indexOf(cell.getChars()) >= 0; + return this._terminal.optionsService.options.wordSeparator.indexOf(cell.getChars()) >= 0; } /** diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index 2d2a08f02..ab0414882 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -46,7 +46,8 @@ export const DEFAULT_OPTIONS: ITerminalOptions = Object.freeze({ screenKeys: false, debug: false, cancelEvents: false, - useFlowControl: false + useFlowControl: false, + wordSeparator: ' ()[]{}\'"' }); /** @@ -99,6 +100,7 @@ export class OptionsService implements IOptionsService { case 'fontWeight': case 'fontWeightBold': case 'rendererType': + case 'wordSeparator': if (!value) { value = DEFAULT_OPTIONS[key]; } diff --git a/src/common/services/Services.d.ts b/src/common/services/Services.d.ts index 602671ff7..b8276f515 100644 --- a/src/common/services/Services.d.ts +++ b/src/common/services/Services.d.ts @@ -56,6 +56,7 @@ export interface IPartialTerminalOptions { tabStopWidth?: number; theme?: ITheme; windowsMode?: boolean; + wordSeparator?: string; } export interface ITerminalOptions { @@ -91,6 +92,7 @@ export interface ITerminalOptions { screenKeys: boolean; termName: string; useFlowControl: boolean; + wordSeparator?: string; } export interface ITheme { diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts index 0dae4def8..4a19db9f1 100644 --- a/src/public/Terminal.ts +++ b/src/public/Terminal.ts @@ -125,7 +125,7 @@ export class Terminal implements ITerminalApi { public writeUtf8(data: Uint8Array): void { this._core.writeUtf8(data); } - public getOption(key: 'bellSound' | 'bellStyle' | 'cursorStyle' | 'fontFamily' | 'fontWeight' | 'fontWeightBold' | 'rendererType' | 'termName'): string; + public getOption(key: 'bellSound' | 'bellStyle' | 'cursorStyle' | 'fontFamily' | 'fontWeight' | 'fontWeightBold' | 'rendererType' | 'termName' | 'wordSeparator'): string; public getOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'debug' | 'disableStdin' | 'macOptionIsMeta' | 'rightClickSelectsWord' | 'popOnBell' | 'screenKeys' | 'useFlowControl' | 'visualBell'): boolean; public getOption(key: 'colors'): string[]; public getOption(key: 'cols' | 'fontSize' | 'letterSpacing' | 'lineHeight' | 'rows' | 'tabStopWidth' | 'scrollback'): number; @@ -134,7 +134,7 @@ export class Terminal implements ITerminalApi { public getOption(key: any): any { return this._core.optionsService.getOption(key); } - public setOption(key: 'bellSound' | 'fontFamily' | 'termName', value: string): void; + public setOption(key: 'bellSound' | 'fontFamily' | 'termName' | 'wordSeparator', value: string): void; public setOption(key: 'fontWeight' | 'fontWeightBold', value: 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900'): void; public setOption(key: 'bellStyle', value: 'none' | 'visual' | 'sound' | 'both'): void; public setOption(key: 'cursorStyle', value: 'block' | 'underline' | 'bar'): void; diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index f435367dd..be3018848 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -177,6 +177,12 @@ declare module 'xterm' { * not whitespace. */ windowsMode?: boolean; + + /** + * A string containing all characters that are considered word separated by the + * double click to select work logic. + */ + wordSeparator?: string; } /** @@ -673,7 +679,7 @@ declare module 'xterm' { * Retrieves an option's value from the terminal. * @param key The option key. */ - getOption(key: 'bellSound' | 'bellStyle' | 'cursorStyle' | 'fontFamily' | 'fontWeight' | 'fontWeightBold'| 'rendererType' | 'termName'): string; + getOption(key: 'bellSound' | 'bellStyle' | 'cursorStyle' | 'fontFamily' | 'fontWeight' | 'fontWeightBold'| 'rendererType' | 'termName' | 'wordSeparator'): string; /** * Retrieves an option's value from the terminal. * @param key The option key. @@ -705,7 +711,7 @@ declare module 'xterm' { * @param key The option key. * @param value The option value. */ - setOption(key: 'fontFamily' | 'termName' | 'bellSound', value: string): void; + setOption(key: 'fontFamily' | 'termName' | 'bellSound' | 'wordSeparator', value: string): void; /** * Sets an option on the terminal. * @param key The option key. From 62e2b00bd5bbb6cb29583ce2577469a3aac6a580 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 21 Jun 2019 19:54:11 -0700 Subject: [PATCH 03/13] Remove terminal dep in clipboard --- src/Clipboard.ts | 51 ++++++++++++++++++++++++------------------------ src/Terminal.ts | 10 +++++----- 2 files changed, 30 insertions(+), 31 deletions(-) diff --git a/src/Clipboard.ts b/src/Clipboard.ts index 9461afa1f..75b0da8e0 100644 --- a/src/Clipboard.ts +++ b/src/Clipboard.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { ITerminal, ISelectionManager } from './Types'; +import { ISelectionManager } from './Types'; /** * Prepares text to be pasted into the terminal by normalizing the line endings @@ -28,7 +28,7 @@ export function bracketTextForPaste(text: string, bracketedPasteMode: boolean): * Binds copy functionality to the given terminal. * @param ev The original copy event to be handled */ -export function copyHandler(ev: ClipboardEvent, term: ITerminal, selectionManager: ISelectionManager): void { +export function copyHandler(ev: ClipboardEvent, selectionManager: ISelectionManager): void { ev.clipboardData.setData('text/plain', selectionManager.selectionText); // Prevent or the original text will be copied. ev.preventDefault(); @@ -39,17 +39,16 @@ export function copyHandler(ev: ClipboardEvent, term: ITerminal, selectionManage * @param ev The original paste event to be handled * @param term The terminal on which to apply the handled paste event */ -export function pasteHandler(ev: ClipboardEvent, term: ITerminal): void { +export function pasteHandler(ev: ClipboardEvent, textarea: HTMLTextAreaElement, bracketedPasteMode: boolean, triggerUserInput: (data: string) => void): void { ev.stopPropagation(); let text: string; const dispatchPaste = function(text: string): void { text = prepareTextForTerminal(text); - text = bracketTextForPaste(text, term.bracketedPasteMode); - term.handler(text); - term.textarea.value = ''; - term.cancel(ev); + text = bracketTextForPaste(text, bracketedPasteMode); + triggerUserInput(text); + textarea.value = ''; }; if (ev.clipboardData) { @@ -63,32 +62,32 @@ export function pasteHandler(ev: ClipboardEvent, term: ITerminal): void { * @param ev The original right click event to be handled. * @param textarea The terminal's textarea. */ -export function moveTextAreaUnderMouseCursor(ev: MouseEvent, term: ITerminal): void { +export function moveTextAreaUnderMouseCursor(ev: MouseEvent, textarea: HTMLTextAreaElement, screenElement: HTMLElement): void { // Calculate textarea position relative to the screen element - const pos = term.screenElement.getBoundingClientRect(); + const pos = screenElement.getBoundingClientRect(); const left = ev.clientX - pos.left - 10; const top = ev.clientY - pos.top - 10; // Bring textarea at the cursor position - term.textarea.style.position = 'absolute'; - term.textarea.style.width = '20px'; - term.textarea.style.height = '20px'; - term.textarea.style.left = `${left}px`; - term.textarea.style.top = `${top}px`; - term.textarea.style.zIndex = '1000'; + textarea.style.position = 'absolute'; + textarea.style.width = '20px'; + textarea.style.height = '20px'; + textarea.style.left = `${left}px`; + textarea.style.top = `${top}px`; + textarea.style.zIndex = '1000'; - term.textarea.focus(); + textarea.focus(); // Reset the terminal textarea's styling // Timeout needs to be long enough for click event to be handled. setTimeout(() => { - term.textarea.style.position = null; - term.textarea.style.width = null; - term.textarea.style.height = null; - term.textarea.style.left = null; - term.textarea.style.top = null; - term.textarea.style.zIndex = null; + textarea.style.position = null; + textarea.style.width = null; + textarea.style.height = null; + textarea.style.left = null; + textarea.style.top = null; + textarea.style.zIndex = null; }, 200); } @@ -99,14 +98,14 @@ export function moveTextAreaUnderMouseCursor(ev: MouseEvent, term: ITerminal): v * @param selectionManager The terminal's selection manager. * @param shouldSelectWord If true and there is no selection the current word will be selected */ -export function rightClickHandler(ev: MouseEvent, term: ITerminal, selectionManager: ISelectionManager, shouldSelectWord: boolean): void { - moveTextAreaUnderMouseCursor(ev, term); +export function rightClickHandler(ev: MouseEvent, textarea: HTMLTextAreaElement, screenElement: HTMLElement, selectionManager: ISelectionManager, shouldSelectWord: boolean): void { + moveTextAreaUnderMouseCursor(ev, textarea, screenElement); if (shouldSelectWord && !selectionManager.isClickInSelection(ev)) { selectionManager.selectWordAtCursor(ev); } // Get textarea ready to copy from the context menu - term.textarea.value = selectionManager.selectionText; - term.textarea.select(); + textarea.value = selectionManager.selectionText; + textarea.select(); } diff --git a/src/Terminal.ts b/src/Terminal.ts index b28cfcfa0..310aebe93 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -478,9 +478,9 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp if (!this.hasSelection()) { return; } - copyHandler(event, this, this.selectionManager); + copyHandler(event, this.selectionManager); })); - const pasteHandlerWrapper = (event: ClipboardEvent) => pasteHandler(event, this); + const pasteHandlerWrapper = (event: ClipboardEvent) => pasteHandler(event, this.textarea, this.bracketedPasteMode, e => this.handler(e)); this.register(addDisposableDomListener(this.textarea, 'paste', pasteHandlerWrapper)); this.register(addDisposableDomListener(this.element, 'paste', pasteHandlerWrapper)); @@ -489,12 +489,12 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp // Firefox doesn't appear to fire the contextmenu event on right click this.register(addDisposableDomListener(this.element, 'mousedown', (event: MouseEvent) => { if (event.button === 2) { - rightClickHandler(event, this, this.selectionManager, this.options.rightClickSelectsWord); + rightClickHandler(event, this.textarea, this.screenElement, this.selectionManager, this.options.rightClickSelectsWord); } })); } else { this.register(addDisposableDomListener(this.element, 'contextmenu', (event: MouseEvent) => { - rightClickHandler(event, this, this.selectionManager, this.options.rightClickSelectsWord); + rightClickHandler(event, this.textarea, this.screenElement, this.selectionManager, this.options.rightClickSelectsWord); })); } @@ -506,7 +506,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp // that the regular click event doesn't fire for the middle mouse button. this.register(addDisposableDomListener(this.element, 'auxclick', (event: MouseEvent) => { if (event.button === 1) { - moveTextAreaUnderMouseCursor(event, this); + moveTextAreaUnderMouseCursor(event, this.textarea, this.screenElement); } })); } From 72f615da14a0e0d627db4cff81172b0e9ef98285 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 21 Jun 2019 20:08:58 -0700 Subject: [PATCH 04/13] Remove some of terminal dependency in SelectionManager --- src/SelectionManager.ts | 82 ++++++++++++++++++++--------------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index fb2caba89..4bd62cdcf 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -114,13 +114,13 @@ export class SelectionManager implements ISelectionManager { constructor( private readonly _terminal: ITerminal, private readonly _charSizeService: ICharSizeService, - readonly bufferService: IBufferService, + private readonly _bufferService: IBufferService, private readonly _mouseService: IMouseService ) { this._initListeners(); this.enable(); - this._model = new SelectionModel(bufferService); + this._model = new SelectionModel(this._bufferService); this._activeSelectionMode = SelectionMode.NORMAL; } @@ -128,10 +128,6 @@ export class SelectionManager implements ISelectionManager { this._removeMouseDownListeners(); } - private get _buffer(): IBuffer { - return this._terminal.buffers.active; - } - /** * Initializes listener variables. */ @@ -143,8 +139,8 @@ export class SelectionManager implements ISelectionManager { } public initBuffersListeners(): void { - this._trimListener = this._terminal.buffer.lines.onTrim(amount => this._onTrim(amount)); - this._terminal.buffers.onBufferActivate(e => this._onBufferActivate(e)); + this._trimListener = this._bufferService.buffer.lines.onTrim(amount => this._onTrim(amount)); + this._bufferService.buffers.onBufferActivate(e => this._onBufferActivate(e)); } /** @@ -188,6 +184,7 @@ export class SelectionManager implements ISelectionManager { return ''; } + const buffer = this._bufferService.buffer; const result: string[] = []; if (this._activeSelectionMode === SelectionMode.COLUMN) { @@ -197,18 +194,18 @@ export class SelectionManager implements ISelectionManager { } for (let i = start[1]; i <= end[1]; i++) { - const lineText = this._buffer.translateBufferLineToString(i, true, start[0], end[0]); + const lineText = buffer.translateBufferLineToString(i, true, start[0], end[0]); result.push(lineText); } } else { // Get first row const startRowEndCol = start[1] === end[1] ? end[0] : undefined; - result.push(this._buffer.translateBufferLineToString(start[1], true, start[0], startRowEndCol)); + result.push(buffer.translateBufferLineToString(start[1], true, start[0], startRowEndCol)); // Get middle rows for (let i = start[1] + 1; i <= end[1] - 1; i++) { - const bufferLine = this._buffer.lines.get(i); - const lineText = this._buffer.translateBufferLineToString(i, true); + const bufferLine = buffer.lines.get(i); + const lineText = buffer.translateBufferLineToString(i, true); if (bufferLine.isWrapped) { result[result.length - 1] += lineText; } else { @@ -218,8 +215,8 @@ export class SelectionManager implements ISelectionManager { // Get final row if (start[1] !== end[1]) { - const bufferLine = this._buffer.lines.get(end[1]); - const lineText = this._buffer.translateBufferLineToString(end[1], true, 0, end[0]); + const bufferLine = buffer.lines.get(end[1]); + const lineText = buffer.translateBufferLineToString(end[1], true, 0, end[0]); if (bufferLine.isWrapped) { result[result.length - 1] += lineText; } else { @@ -329,9 +326,9 @@ export class SelectionManager implements ISelectionManager { public selectLines(start: number, end: number): void { this._model.clearSelection(); start = Math.max(start, 0); - end = Math.min(end, this._terminal.buffer.lines.length - 1); + end = Math.min(end, this._bufferService.buffer.lines.length - 1); this._model.selectionStart = [0, start]; - this._model.selectionEnd = [this._terminal.cols, end]; + this._model.selectionEnd = [this._bufferService.cols, end]; this.refresh(); this._onSelectionChange.fire(); } @@ -362,7 +359,7 @@ export class SelectionManager implements ISelectionManager { coords[1]--; // Convert viewport coords to buffer coords - coords[1] += this._terminal.buffer.ydisp; + coords[1] += this._bufferService.buffer.ydisp; return coords; } @@ -499,7 +496,7 @@ export class SelectionManager implements ISelectionManager { this._model.selectionEnd = null; // Ensure the line exists - const line = this._buffer.lines.get(this._model.selectionStart[1]); + const line = this._bufferService.buffer.lines.get(this._model.selectionStart[1]); if (!line) { return; } @@ -576,7 +573,7 @@ export class SelectionManager implements ISelectionManager { if (this._model.selectionEnd[1] < this._model.selectionStart[1]) { this._model.selectionEnd[0] = 0; } else { - this._model.selectionEnd[0] = this._terminal.cols; + this._model.selectionEnd[0] = this._bufferService.cols; } } else if (this._activeSelectionMode === SelectionMode.WORD) { this._selectToWordAt(this._model.selectionEnd); @@ -590,7 +587,7 @@ export class SelectionManager implements ISelectionManager { // NOT in column select mode. if (this._activeSelectionMode !== SelectionMode.COLUMN) { if (this._dragScrollAmount > 0) { - this._model.selectionEnd[0] = this._terminal.cols; + this._model.selectionEnd[0] = this._bufferService.cols; } else if (this._dragScrollAmount < 0) { this._model.selectionEnd[0] = 0; } @@ -599,8 +596,9 @@ export class SelectionManager implements ISelectionManager { // If the character is a wide character include the cell to the right in the // selection. Note that selections at the very end of the line will never // have a character. - if (this._model.selectionEnd[1] < this._buffer.lines.length) { - if (this._buffer.lines.get(this._model.selectionEnd[1]).hasWidth(this._model.selectionEnd[0]) === 0) { + const buffer = this._bufferService.buffer; + if (this._model.selectionEnd[1] < buffer.lines.length) { + if (buffer.lines.get(this._model.selectionEnd[1]).hasWidth(this._model.selectionEnd[0]) === 0) { this._model.selectionEnd[0]++; } } @@ -624,16 +622,17 @@ export class SelectionManager implements ISelectionManager { // If the cursor was above or below the viewport, make sure it's at the // start or end of the viewport respectively. This should only happen when // NOT in column select mode. + const buffer = this._bufferService.buffer; if (this._dragScrollAmount > 0) { if (this._activeSelectionMode !== SelectionMode.COLUMN) { - this._model.selectionEnd[0] = this._terminal.cols; + this._model.selectionEnd[0] = this._bufferService.cols; } - this._model.selectionEnd[1] = Math.min(this._terminal.buffer.ydisp + this._terminal.rows, this._terminal.buffer.lines.length - 1); + this._model.selectionEnd[1] = Math.min(buffer.ydisp + this._bufferService.rows, buffer.lines.length - 1); } else { if (this._activeSelectionMode !== SelectionMode.COLUMN) { this._model.selectionEnd[0] = 0; } - this._model.selectionEnd[1] = this._terminal.buffer.ydisp; + this._model.selectionEnd[1] = buffer.ydisp; } this.refresh(); } @@ -704,16 +703,17 @@ export class SelectionManager implements ISelectionManager { */ private _getWordAt(coords: [number, number], allowWhitespaceOnlySelection: boolean, followWrappedLinesAbove: boolean = true, followWrappedLinesBelow: boolean = true): IWordPosition { // Ensure coords are within viewport (eg. not within scroll bar) - if (coords[0] >= this._terminal.cols) { + if (coords[0] >= this._bufferService.cols) { return null; } - const bufferLine = this._buffer.lines.get(coords[1]); + const buffer = this._bufferService.buffer; + const bufferLine = buffer.lines.get(coords[1]); if (!bufferLine) { return null; } - const line = this._buffer.translateBufferLineToString(coords[1], false); + const line = buffer.translateBufferLineToString(coords[1], false); // Get actual index, taking into consideration wide characters let startIndex = this._convertViewportColToCharacterIndex(bufferLine, coords); @@ -808,7 +808,7 @@ export class SelectionManager implements ISelectionManager { // Calculate the length in _columns_, converting the the string indexes back // to column coordinates. - let length = Math.min(this._terminal.cols, // Disallow lengths larger than the terminal cols + let length = Math.min(this._bufferService.cols, // Disallow lengths larger than the terminal cols endIndex // The index of the selection's end char in the line string - startIndex // The index of the selection's start char in the line string + leftWideCharCount // The number of wide chars left of the initial char @@ -823,11 +823,11 @@ export class SelectionManager implements ISelectionManager { // Recurse upwards if the line is wrapped and the word wraps to the above line if (followWrappedLinesAbove) { if (start === 0 && bufferLine.getCodePoint(0) !== 32 /*' '*/) { - const previousBufferLine = this._buffer.lines.get(coords[1] - 1); - if (previousBufferLine && bufferLine.isWrapped && previousBufferLine.getCodePoint(this._terminal.cols - 1) !== 32 /*' '*/) { - const previousLineWordPosition = this._getWordAt([this._terminal.cols - 1, coords[1] - 1], false, true, false); + const previousBufferLine = buffer.lines.get(coords[1] - 1); + if (previousBufferLine && bufferLine.isWrapped && previousBufferLine.getCodePoint(this._bufferService.cols - 1) !== 32 /*' '*/) { + const previousLineWordPosition = this._getWordAt([this._bufferService.cols - 1, coords[1] - 1], false, true, false); if (previousLineWordPosition) { - const offset = this._terminal.cols - previousLineWordPosition.start; + const offset = this._bufferService.cols - previousLineWordPosition.start; start -= offset; length += offset; } @@ -837,8 +837,8 @@ export class SelectionManager implements ISelectionManager { // Recurse downwards if the line is wrapped and the word wraps to the next line if (followWrappedLinesBelow) { - if (start + length === this._terminal.cols && bufferLine.getCodePoint(this._terminal.cols - 1) !== 32 /*' '*/) { - const nextBufferLine = this._buffer.lines.get(coords[1] + 1); + if (start + length === this._bufferService.cols && bufferLine.getCodePoint(this._bufferService.cols - 1) !== 32 /*' '*/) { + const nextBufferLine = buffer.lines.get(coords[1] + 1); if (nextBufferLine && nextBufferLine.isWrapped && nextBufferLine.getCodePoint(0) !== 32 /*' '*/) { const nextLineWordPosition = this._getWordAt([0, coords[1] + 1], false, false, true); if (nextLineWordPosition) { @@ -861,7 +861,7 @@ export class SelectionManager implements ISelectionManager { if (wordPosition) { // Adjust negative start value while (wordPosition.start < 0) { - wordPosition.start += this._terminal.cols; + wordPosition.start += this._bufferService.cols; coords[1]--; } this._model.selectionStart = [wordPosition.start, coords[1]]; @@ -880,15 +880,15 @@ export class SelectionManager implements ISelectionManager { // Adjust negative start value while (wordPosition.start < 0) { - wordPosition.start += this._terminal.cols; + wordPosition.start += this._bufferService.cols; endRow--; } // Adjust wrapped length value, this only needs to happen when values are reversed as in that // case we're interested in the start of the word, not the end if (!this._model.areSelectionValuesReversed()) { - while (wordPosition.start + wordPosition.length > this._terminal.cols) { - wordPosition.length -= this._terminal.cols; + while (wordPosition.start + wordPosition.length > this._bufferService.cols) { + wordPosition.length -= this._bufferService.cols; endRow++; } } @@ -916,9 +916,9 @@ export class SelectionManager implements ISelectionManager { * @param line The line index. */ protected _selectLineAt(line: number): void { - const wrappedRange = this._buffer.getWrappedRangeForLine(line); + const wrappedRange = this._bufferService.buffer.getWrappedRangeForLine(line); this._model.selectionStart = [0, wrappedRange.first]; - this._model.selectionEnd = [this._terminal.cols, wrappedRange.last]; + this._model.selectionEnd = [this._bufferService.cols, wrappedRange.last]; this._model.selectionStartLength = 0; } } From 0ba90a934951b08c46bc953c82b912bf76a40125 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 21 Jun 2019 20:12:17 -0700 Subject: [PATCH 05/13] Adopt options service in selection manager --- src/SelectionManager.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index 4bd62cdcf..060c03666 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -13,7 +13,7 @@ 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 } from 'common/services/Services'; +import { IBufferService, IOptionsService } from 'common/services/Services'; import { getCoordsRelativeToElement } from 'browser/input/Mouse'; /** @@ -115,7 +115,8 @@ export class SelectionManager implements ISelectionManager { private readonly _terminal: ITerminal, private readonly _charSizeService: ICharSizeService, private readonly _bufferService: IBufferService, - private readonly _mouseService: IMouseService + private readonly _mouseService: IMouseService, + private readonly _optionsService: IOptionsService ) { this._initListeners(); this.enable(); @@ -349,7 +350,7 @@ export class SelectionManager implements ISelectionManager { * @param event The mouse event. */ private _getMouseBufferCoords(event: MouseEvent): [number, number] { - const coords = this._mouseService.getCoords(event, this._terminal.screenElement, this._terminal.cols, this._terminal.rows, true); + const coords = this._mouseService.getCoords(event, this._terminal.screenElement, this._bufferService.cols, this._bufferService.rows, true); if (!coords) { return null; } @@ -370,7 +371,7 @@ export class SelectionManager implements ISelectionManager { */ private _getMouseEventScrollAmount(event: MouseEvent): number { let offset = getCoordsRelativeToElement(event, this._terminal.screenElement)[1]; - const terminalHeight = this._terminal.rows * Math.ceil(this._charSizeService.height * this._terminal.options.lineHeight); + const terminalHeight = this._bufferService.rows * Math.ceil(this._charSizeService.height * this._optionsService.options.lineHeight); if (offset >= 0 && offset <= terminalHeight) { return 0; } @@ -390,7 +391,7 @@ export class SelectionManager implements ISelectionManager { */ public shouldForceSelection(event: MouseEvent): boolean { if (Browser.isMac) { - return event.altKey && this._terminal.options.macOptionClickForcesSelection; + return event.altKey && this._optionsService.options.macOptionClickForcesSelection; } return event.shiftKey; @@ -543,7 +544,7 @@ export class SelectionManager implements ISelectionManager { * @param event the mouse or keyboard event */ public shouldColumnSelect(event: KeyboardEvent | MouseEvent): boolean { - return event.altKey && !(Browser.isMac && this._terminal.options.macOptionClickForcesSelection); + return event.altKey && !(Browser.isMac && this._optionsService.options.macOptionClickForcesSelection); } /** @@ -908,7 +909,7 @@ export class SelectionManager implements ISelectionManager { if (cell.getWidth() === 0) { return false; } - return this._terminal.optionsService.options.wordSeparator.indexOf(cell.getChars()) >= 0; + return this._optionsService.options.wordSeparator.indexOf(cell.getChars()) >= 0; } /** From 9174f60d94fe097f11da7b5f28e03ebb26e7c711 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 21 Jun 2019 20:20:48 -0700 Subject: [PATCH 06/13] Move screen element into ctor --- src/SelectionManager.test.ts | 15 +++++++++------ src/SelectionManager.ts | 15 ++++++++------- src/Terminal.ts | 2 +- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/SelectionManager.test.ts b/src/SelectionManager.test.ts index 0b82466d7..814cb5fe0 100644 --- a/src/SelectionManager.test.ts +++ b/src/SelectionManager.test.ts @@ -10,9 +10,9 @@ import { ITerminal } from './Types'; import { IBuffer } from 'common/buffer/Types'; import { IBufferLine } from 'common/Types'; import { MockTerminal } from './TestUtils.test'; -import { MockBufferService } from 'common/TestUtils.test'; +import { MockBufferService, MockOptionsService } from 'common/TestUtils.test'; import { BufferLine } from 'common/buffer/BufferLine'; -import { IBufferService } from 'common/services/Services'; +import { IBufferService, IOptionsService } from 'common/services/Services'; import { MockCharSizeService, MockMouseService } from 'browser/TestUtils.test'; import { CellData } from 'common/buffer/CellData'; @@ -23,9 +23,10 @@ class TestMockTerminal extends MockTerminal { class TestSelectionManager extends SelectionManager { constructor( terminal: ITerminal, - bufferService: IBufferService + bufferService: IBufferService, + optionsService: IOptionsService ) { - super(terminal, new MockCharSizeService(10, 10), bufferService, new MockMouseService()); + super(terminal, null, new MockCharSizeService(10, 10), bufferService, new MockMouseService(), optionsService); } public get model(): SelectionModel { return this._model; } @@ -46,17 +47,19 @@ describe('SelectionManager', () => { let terminal: ITerminal; let buffer: IBuffer; let bufferService: IBufferService; + let optionsService: IOptionsService; let selectionManager: TestSelectionManager; beforeEach(() => { terminal = new TestMockTerminal(); - bufferService = new MockBufferService(20, 20); + optionsService = new MockOptionsService(); + bufferService = new MockBufferService(20, 20, optionsService); terminal.buffers = bufferService.buffers; terminal.cols = 20; terminal.rows = 20; terminal.buffer = terminal.buffers.active; buffer = terminal.buffer; - selectionManager = new TestSelectionManager(terminal, bufferService); + selectionManager = new TestSelectionManager(terminal, bufferService, optionsService); }); function stringToRow(text: string): IBufferLine { diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index 060c03666..767cf1564 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -113,6 +113,7 @@ export class SelectionManager implements ISelectionManager { constructor( private readonly _terminal: ITerminal, + private readonly _screenElement: HTMLElement, private readonly _charSizeService: ICharSizeService, private readonly _bufferService: IBufferService, private readonly _mouseService: IMouseService, @@ -350,7 +351,7 @@ export class SelectionManager implements ISelectionManager { * @param event The mouse event. */ private _getMouseBufferCoords(event: MouseEvent): [number, number] { - const coords = this._mouseService.getCoords(event, this._terminal.screenElement, this._bufferService.cols, this._bufferService.rows, true); + const coords = this._mouseService.getCoords(event, this._screenElement, this._bufferService.cols, this._bufferService.rows, true); if (!coords) { return null; } @@ -370,7 +371,7 @@ export class SelectionManager implements ISelectionManager { * @param event The mouse event. */ private _getMouseEventScrollAmount(event: MouseEvent): number { - let offset = getCoordsRelativeToElement(event, this._terminal.screenElement)[1]; + let offset = getCoordsRelativeToElement(event, this._screenElement)[1]; const terminalHeight = this._bufferService.rows * Math.ceil(this._charSizeService.height * this._optionsService.options.lineHeight); if (offset >= 0 && offset <= terminalHeight) { return 0; @@ -451,8 +452,8 @@ export class SelectionManager implements ISelectionManager { */ private _addMouseDownListeners(): void { // Listen on the document so that dragging outside of viewport works - this._terminal.element.ownerDocument.addEventListener('mousemove', this._mouseMoveListener); - this._terminal.element.ownerDocument.addEventListener('mouseup', this._mouseUpListener); + this._screenElement.ownerDocument.addEventListener('mousemove', this._mouseMoveListener); + this._screenElement.ownerDocument.addEventListener('mouseup', this._mouseUpListener); this._dragScrollIntervalTimer = setInterval(() => this._dragScroll(), DRAG_SCROLL_INTERVAL); } @@ -460,9 +461,9 @@ export class SelectionManager implements ISelectionManager { * Removes the listeners that are registered when mousedown is triggered. */ private _removeMouseDownListeners(): void { - if (this._terminal.element.ownerDocument) { - this._terminal.element.ownerDocument.removeEventListener('mousemove', this._mouseMoveListener); - this._terminal.element.ownerDocument.removeEventListener('mouseup', this._mouseUpListener); + if (this._screenElement.ownerDocument) { + this._screenElement.ownerDocument.removeEventListener('mousemove', this._mouseMoveListener); + this._screenElement.ownerDocument.removeEventListener('mouseup', this._mouseUpListener); } clearInterval(this._dragScrollIntervalTimer); this._dragScrollIntervalTimer = null; diff --git a/src/Terminal.ts b/src/Terminal.ts index 310aebe93..72cecb199 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -640,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._mouseService); + this.selectionManager = new SelectionManager(this, this.screenElement, this._charSizeService, this._bufferService, this._mouseService, this.optionsService); 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))); From 00969508d9352da6156e7d924f2f1b2ce7f3959b Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 22 Jun 2019 09:40:11 -0700 Subject: [PATCH 07/13] 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 767cf1564..0a5daed25 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 334b78d8e..7b46d7450 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 08/13] 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 0a5daed25..483ea7cb6 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 7b46d7450..918ab60de 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 09/13] 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 483ea7cb6..344978cf4 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 918ab60de..8f8288a6e 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 10/13] 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 344978cf4..48a2fec8a 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 8f8288a6e..73e9b7295 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 11/13] 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 48a2fec8a..56d32b567 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 73e9b7295..406ec8075 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 12/13] 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 000000000..bc4012c04 --- /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 13/13] 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 75b0da8e0..1ee232eee 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 56d32b567..4133fcccc 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 07f917055..8394f5a78 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 5987d605e..c3957ab71 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 000000000..241731f1f --- /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; +}