diff --git a/src/Buffer.ts b/src/Buffer.ts index 7e34a23d..e4c4468e 100644 --- a/src/Buffer.ts +++ b/src/Buffer.ts @@ -5,6 +5,8 @@ import { CircularList } from './utils/CircularList'; import { LineData, CharData, ITerminal, IBuffer } from './Types'; +import { EventEmitter } from './EventEmitter'; +import { IDisposable, IMarker } from 'xterm'; export const CHAR_DATA_ATTR_INDEX = 0; export const CHAR_DATA_CHAR_INDEX = 1; @@ -31,6 +33,7 @@ export class Buffer implements IBuffer { public tabs: any; public savedY: number; public savedX: number; + public markers: Marker[] = []; /** * Create a new Buffer. @@ -303,4 +306,50 @@ export class Buffer implements IBuffer { while (!this.tabs[++x] && x < this._terminal.cols); return x >= this._terminal.cols ? this._terminal.cols - 1 : x < 0 ? 0 : x; } + + public addMarker(y: number): Marker { + const marker = new Marker(y); + this.markers.push(marker); + marker.disposables.push(this._lines.addDisposableListener('trim', amount => { + marker.line -= amount; + // The marker should be disposed when the line is trimmed from the buffer + if (marker.line < 0) { + marker.dispose(); + } + // TODO: handle splice? + })); + marker.on('dispose', () => this._removeMarker(marker)); + return marker; + } + + private _removeMarker(marker: Marker): void { + // TODO: This could probably be optimized by relying on sort order and trimming the array using .length + this.markers = this.markers.splice(this.markers.indexOf(marker), 1); + } +} + +export class Marker extends EventEmitter implements IMarker { + private static NEXT_ID = 1; + + private _id: number = Marker.NEXT_ID++; + public isDisposed: boolean = false; + public disposables: IDisposable[] = []; + + public get id(): number { return this._id; } + + constructor( + public line: number + ) { + super(); + } + + public dispose(): void { + if (this.isDisposed) { + return; + } + this.isDisposed = true; + this.disposables.forEach(d => d.dispose()); + this.disposables.length = 0; + this.emit('dispose'); + } } diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index 506e87d4..a50203bd 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -292,6 +292,16 @@ export class SelectionManager extends EventEmitter implements ISelectionManager this._terminal.emit('selection'); } + 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); + this._model.selectionStart = [0, start]; + this._model.selectionEnd = [this._terminal.cols, end]; + this.refresh(); + this._terminal.emit('selection'); + } + /** * Handle the buffer being trimmed, adjust the selection position. * @param amount The amount the buffer is being trimmed. diff --git a/src/Terminal.ts b/src/Terminal.ts index 924260ca..22aafdb4 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -45,7 +45,7 @@ import { DEFAULT_ANSI_COLORS } from './renderer/ColorManager'; import { MouseZoneManager } from './input/MouseZoneManager'; import { AccessibilityManager } from './AccessibilityManager'; import { ScreenDprMonitor } from './utils/ScreenDprMonitor'; -import { ITheme, ILocalizableStrings } from 'xterm'; +import { ITheme, ILocalizableStrings, IMarker } from 'xterm'; // reg + shift key mappings for digits and special chars const KEYCODE_KEY_MAPPINGS = { @@ -1241,6 +1241,14 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.scrollLines(this.buffer.ybase - this.buffer.ydisp); } + public scrollToLine(line: number): void { + const scrollAmount = line - this.buffer.ydisp; + console.log('scrollAmount', scrollAmount); + if (scrollAmount !== 0) { + this.scrollLines(scrollAmount); + } + } + /** * Writes text to the terminal. * @param {string} data The text to write to the terminal. @@ -1349,6 +1357,19 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT } } + public get markers(): IMarker[] { + return this.buffer.markers; + } + + public addMarker(cursorYOffset: number): IMarker { + // Disallow markers on the alt buffer + if (this.buffer !== this.buffers.normal) { + return; + } + + return this.buffer.addMarker(this.buffer.ybase + this.buffer.y + cursorYOffset); + } + /** * Gets whether the terminal has an active selection. */ @@ -1382,6 +1403,12 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT } } + public selectLines(start: number, end: number): void { + if (this.selectionManager) { + this.selectionManager.selectLines(start, end); + } + } + /** * Handle a keydown event * Key Resources: diff --git a/src/utils/TestUtils.test.ts b/src/utils/TestUtils.test.ts index 3c60d695..5f3a8482 100644 --- a/src/utils/TestUtils.test.ts +++ b/src/utils/TestUtils.test.ts @@ -7,9 +7,19 @@ import { IColorSet, IRenderer, IRenderDimensions, IColorManager } from '../rende import { LineData, IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminal, IBuffer, IBufferSet, IBrowser, ICharMeasure, ISelectionManager, ITerminalOptions, ICircularList, ILinkifier, IMouseHelper, ILinkMatcherOptions, XtermListener } from '../Types'; import { Buffer } from '../Buffer'; import * as Browser from '../shared/utils/Browser'; -import { ITheme, IDisposable } from 'xterm'; +import { ITheme, IDisposable, IMarker } from 'xterm'; export class MockTerminal implements ITerminal { + markers: IMarker[]; + addMarker(cursorYOffset: number): IMarker { + throw new Error('Method not implemented.'); + } + selectLines(start: number, end: number): void { + throw new Error('Method not implemented.'); + } + scrollToLine(line: number): void { + throw new Error('Method not implemented.'); + } static string: any; getOption(key: any): any { throw new Error('Method not implemented.'); diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 0e8fdcc7..0d6e422d 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -235,6 +235,12 @@ declare module 'xterm' { dispose(): void; } + export interface IMarker extends IDisposable { + readonly id: number; + readonly isDisposed: boolean; + readonly line: number; + } + export interface ILocalizableStrings { blankLine: string; promptLabel: string; @@ -265,6 +271,12 @@ declare module 'xterm' { */ cols: number; + /** + * Get all markers registered against the buffer. If the alt buffer is + * active this will always return []. + */ + markers: IMarker[]; + /** * Natural language strings that can be localized. */ @@ -403,6 +415,13 @@ declare module 'xterm' { */ deregisterLinkMatcher(matcherId: number): void; + /** + * Adds a marker to the normal buffer and returns it. If the alt buffer is + * active, undefined is returned. + * @param cursorYOffset The y position offset of the marker from the cursor. + */ + addMarker(cursorYOffset: number): IMarker; + /** * Gets whether the terminal has an active selection. */ @@ -424,6 +443,13 @@ declare module 'xterm' { */ selectAll(): void; + /** + * Selects text in the buffer between 2 lines. + * @param start The 0-based line index to select from (inclusive). + * @param end The 0-based line index to select to (inclusive). + */ + selectLines(start: number, end: number): void; + /** * Destroys the terminal and detaches it from the DOM. */ @@ -451,6 +477,12 @@ declare module 'xterm' { */ scrollToBottom(): void; + /** + * Scrolls to a line within the buffer. + * @param line The 0-based line index to scroll to. + */ + scrollToLine(line: number): void; + /** * Clear the entire buffer, making the prompt line the new first line. */