From 3fa4b4941fbbbda6178e65eddc0e944c49839b3e Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 16 Jan 2018 11:01:45 -0800 Subject: [PATCH] Support nav mode pgup/pgdown/end/home, check boundaries --- src/AccessibilityManager.ts | 63 +++++++++++++++++++++++-------------- src/Interfaces.ts | 1 + src/Terminal.ts | 22 +++++++++++++ src/utils/TestUtils.test.ts | 3 ++ 4 files changed, 66 insertions(+), 23 deletions(-) diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index 930ae9f0d..20da65e25 100644 --- a/src/AccessibilityManager.ts +++ b/src/AccessibilityManager.ts @@ -211,7 +211,7 @@ export class AccessibilityManager implements IDisposable { class NavigationMode implements IDisposable { private _activeItemId: string; private _isNavigationModeActive: boolean = false; - private _navigationModeFocusedRow: number; + private _absoluteFocusedRow: number; private _disposables: IDisposable[] = []; @@ -243,13 +243,14 @@ class NavigationMode implements IDisposable { } public enter(): void { + // TODO: Should entering navigation mode send ydisp to ybase? this._isNavigationModeActive = true; this._accessibilityManager.announce('Entered line navigation mode'); this._rowContainer.tabIndex = 0; this._rowContainer.setAttribute('role', 'menu'); this._rowContainer.setAttribute('aria-activedescendant', this._activeItemId); this._rowContainer.focus(); - this._navigateToElement(this._terminal.buffer.y); + this._navigateToElement(this._terminal.buffer.ydisp + this._terminal.buffer.y); } public leave(): void { @@ -280,10 +281,16 @@ class NavigationMode implements IDisposable { public onKeyUp(e: KeyboardEvent): boolean { return this._onKey(e, e => { - switch (e.keyCode) { - case 27: return this._onEscape(e); - case 38: return this._onArrowUp(e); - case 40: return this._onArrowDown(e); + if (this._isNavigationModeActive) { + switch (e.keyCode) { + case 27: return this._onEscape(e); + case 33: return this._onPageUp(e); + case 34: return this._onPageDown(e); + case 35: return this._onEnd(e); + case 36: return this._onHome(e); + case 38: return this._onArrowUp(e); + case 40: return this._onArrowDown(e); + } } return false; }); @@ -302,35 +309,45 @@ class NavigationMode implements IDisposable { } private _onArrowUp(e: KeyboardEvent): boolean { - // TODO: Jump up/down to next non-blank row - this._navigateToElement(this._navigationModeFocusedRow - 1); - this._rowContainer.focus(); - return true; + return this._focusRow(this._absoluteFocusedRow - 1); } private _onArrowDown(e: KeyboardEvent): boolean { - this._navigateToElement(this._navigationModeFocusedRow + 1); + return this._focusRow(this._absoluteFocusedRow + 1); + } + + private _onPageUp(e: KeyboardEvent): boolean { + return this._focusRow(this._absoluteFocusedRow - this._terminal.rows); + } + + private _onPageDown(e: KeyboardEvent): boolean { + return this._focusRow(this._absoluteFocusedRow + this._terminal.rows); + } + + private _onHome(e: KeyboardEvent): boolean { + return this._focusRow(0); + } + + private _onEnd(e: KeyboardEvent): boolean { + return this._focusRow(this._terminal.buffer.lines.length - 1); + } + + private _focusRow(row: number): boolean { + this._navigateToElement(row); this._rowContainer.focus(); return true; } - private _navigateToElement(row: number): void { - if (row < 0) { - if (this._terminal.buffer.ydisp > 0) { - this._terminal.scrollLines(-1); - row = 0; - // TODO: This doesn't reliably read the element, maybe a new row needs to be inserted at the top? - // Exit early since the same element is focused - return; - } - } + private _navigateToElement(absoluteRow: number): void { + absoluteRow = this._terminal.scrollToRow(absoluteRow); + // TODO: Store this state const selected = document.querySelector('#' + this._activeItemId); if (selected) { selected.removeAttribute('id'); } - this._navigationModeFocusedRow = row; - const selectedElement = this._rowElements[row]; + this._absoluteFocusedRow = absoluteRow; + const selectedElement = this._rowElements[absoluteRow - this._terminal.buffer.ydisp]; selectedElement.id = this._activeItemId; } } diff --git a/src/Interfaces.ts b/src/Interfaces.ts index 780a6b4e6..4dc572d1e 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -56,6 +56,7 @@ export interface ITerminal extends ILinkifierAccessor, IBufferAccessor, IElement */ handler(data: string): void; scrollLines(disp: number, suppressScrollEvent?: boolean): void; + scrollToRow(row: number): number; cancel(ev: Event, force?: boolean): boolean | void; log(text: string): void; reset(): void; diff --git a/src/Terminal.ts b/src/Terminal.ts index 62dcab8d9..92ae794ff 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -1169,6 +1169,28 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.refresh(0, this.rows - 1); } + /** + * Scroll the viewport to an absolute row in the buffer. + * @param absoluteRow The absolute row in the buffer to scroll to. + * @returns The actual absolute row that was scrolled to (including boundary checked). + */ + public scrollToRow(absoluteRow: number): number { + // Ensure value is valid + absoluteRow = Math.max(Math.min(absoluteRow, this.buffer.lines.length - 1), 0); + + // Move viewport as necessary + const relativeRow = absoluteRow - this.buffer.ydisp; + let scrollAmount = 0; + if (relativeRow < 0) { + scrollAmount = relativeRow; + } else if (relativeRow >= this.rows) { + scrollAmount = relativeRow - this.rows + 1; + } + this.scrollLines(scrollAmount); + + return absoluteRow; + } + /** * Scroll the display of the terminal by a number of pages. * @param {number} pageCount The number of pages to scroll (negative scrolls up). diff --git a/src/utils/TestUtils.test.ts b/src/utils/TestUtils.test.ts index 89b03530b..69adf3a20 100644 --- a/src/utils/TestUtils.test.ts +++ b/src/utils/TestUtils.test.ts @@ -48,6 +48,9 @@ export class MockTerminal implements ITerminal { scrollLines(disp: number, suppressScrollEvent: boolean): void { throw new Error('Method not implemented.'); } + scrollToRow(absoluteRow: number): number { + throw new Error('Method not implemented.'); + } cancel(ev: Event, force?: boolean): void { throw new Error('Method not implemented.'); }