diff --git a/src/Buffer.test.ts b/src/Buffer.test.ts index 101912bb2..d0d098054 100644 --- a/src/Buffer.test.ts +++ b/src/Buffer.test.ts @@ -7,28 +7,30 @@ import { assert, expect } from 'chai'; import { ITerminal } from './Types'; import { Buffer } from './Buffer'; import { CircularList } from 'common/CircularList'; -import { MockTerminal, TestTerminal } from './TestUtils.test'; +import { MockTerminal, TestTerminal, MockOptionsService } from './TestUtils.test'; import { BufferLine, CellData, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; const INIT_COLS = 80; const INIT_ROWS = 24; +const INIT_SCROLLBACK = 1000; describe('Buffer', () => { let terminal: ITerminal; + let optionsService: MockOptionsService; let buffer: Buffer; beforeEach(() => { terminal = new MockTerminal(); (terminal as any).cols = INIT_COLS; (terminal as any).rows = INIT_ROWS; - terminal.options.scrollback = 1000; - buffer = new Buffer(terminal, true); + optionsService = new MockOptionsService({ scrollback: INIT_SCROLLBACK }); + buffer = new Buffer(terminal, true, optionsService); }); describe('constructor', () => { it('should create a CircularList with max length equal to rows + scrollback, for its lines', () => { assert.instanceOf(buffer.lines, CircularList); - assert.equal(buffer.lines.maxLength, terminal.rows + terminal.options.scrollback); + assert.equal(buffer.lines.maxLength, terminal.rows + INIT_SCROLLBACK); }); it('should set the Buffer\'s scrollBottom value equal to the terminal\'s rows -1', () => { assert.equal(buffer.scrollBottom, terminal.rows - 1); @@ -150,8 +152,7 @@ describe('Buffer', () => { describe('no scrollback', () => { it('should trim from the top of the buffer when the cursor reaches the bottom', () => { - terminal.options.scrollback = 0; - buffer = new Buffer(terminal, true); + buffer = new Buffer(terminal, true, new MockOptionsService({ scrollback: 0 })); assert.equal(buffer.lines.maxLength, INIT_ROWS); buffer.y = INIT_ROWS - 1; buffer.fillViewportRows(); @@ -295,7 +296,7 @@ describe('Buffer', () => { }); it('should discard parts of wrapped lines that go out of the scrollback', () => { buffer.fillViewportRows(); - terminal.options.scrollback = 1; + optionsService.options.scrollback = 1; buffer.resize(10, 5); const lastLine = buffer.lines.get(3); for (let i = 0; i < 10; i++) { @@ -462,7 +463,7 @@ describe('Buffer', () => { }); it('should dispose markers whose rows are trimmed during a reflow', () => { buffer.fillViewportRows(); - terminal.options.scrollback = 1; + optionsService.options.scrollback = 1; buffer.resize(10, 11); for (let i = 0; i < 10; i++) { const code = 'a'.charCodeAt(0) + i; @@ -788,7 +789,7 @@ describe('Buffer', () => { // ybase === 0 doesn't make sense here as scrollback=0 isn't really supported describe('ybase !== 0', () => { beforeEach(() => { - terminal.options.scrollback = 10; + optionsService.options.scrollback = 10; // Add 10 empty rows to start for (let i = 0; i < 10; i++) { buffer.lines.splice(0, 0, buffer.getBlankLine(DEFAULT_ATTR_DATA)); @@ -986,7 +987,7 @@ describe('Buffer', () => { // ybase === 0 doesn't make sense here as scrollback=0 isn't really supported describe('ybase !== 0', () => { beforeEach(() => { - terminal.options.scrollback = 10; + optionsService.options.scrollback = 10; // Add 10 empty rows to start for (let i = 0; i < 10; i++) { buffer.lines.splice(0, 0, buffer.getBlankLine(DEFAULT_ATTR_DATA)); @@ -1053,9 +1054,8 @@ describe('Buffer', () => { describe('buffer marked to have no scrollback', () => { it('should always have a scrollback of 0', () => { - assert.equal(terminal.options.scrollback, 1000); // Test size on initialization - buffer = new Buffer(terminal, false); + buffer = new Buffer(terminal, false, new MockOptionsService({ scrollback: 1000 })); buffer.fillViewportRows(); assert.equal(buffer.lines.maxLength, INIT_ROWS); // Test size on buffer increase @@ -1069,8 +1069,7 @@ describe('Buffer', () => { describe('addMarker', () => { it('should adjust a marker line when the buffer is trimmed', () => { - terminal.options.scrollback = 0; - buffer = new Buffer(terminal, true); + buffer = new Buffer(terminal, true, new MockOptionsService({ scrollback: 0 })); buffer.fillViewportRows(); const marker = buffer.addMarker(buffer.lines.length - 1); assert.equal(marker.line, buffer.lines.length - 1); @@ -1078,8 +1077,7 @@ describe('Buffer', () => { assert.equal(marker.line, buffer.lines.length - 2); }); it('should dispose of a marker if it is trimmed off the buffer', () => { - terminal.options.scrollback = 0; - buffer = new Buffer(terminal, true); + buffer = new Buffer(terminal, true, new MockOptionsService({ scrollback: 0 })); buffer.fillViewportRows(); assert.equal(buffer.markers.length, 0); const marker = buffer.addMarker(0); @@ -1366,7 +1364,7 @@ describe('Buffer', () => { const input = '\thttps://google.de'; terminal.writeSync(input); const s = terminal.buffer.iterator(true).next().content; - assert.equal(s, Array(terminal.options.tabStopWidth + 1).join(' ') + 'https://google.de'); + assert.equal(s, Array(optionsService.options.tabStopWidth + 1).join(' ') + 'https://google.de'); }); }); describe('BufferStringIterator', function(): void { diff --git a/src/BufferSet.test.ts b/src/BufferSet.test.ts index cdc220b3f..fb70e9543 100644 --- a/src/BufferSet.test.ts +++ b/src/BufferSet.test.ts @@ -7,7 +7,7 @@ import { assert } from 'chai'; import { ITerminal } from './Types'; import { BufferSet } from './BufferSet'; import { Buffer } from './Buffer'; -import { MockTerminal } from './TestUtils.test'; +import { MockTerminal, MockOptionsService } from './TestUtils.test'; describe('BufferSet', () => { let terminal: ITerminal; @@ -17,8 +17,7 @@ describe('BufferSet', () => { terminal = new MockTerminal(); (terminal as any).cols = 80; (terminal as any).rows = 24; - terminal.options.scrollback = 1000; - bufferSet = new BufferSet(terminal); + bufferSet = new BufferSet(terminal, new MockOptionsService({ scrollback: 1000 })); }); describe('constructor', () => { diff --git a/src/BufferSet.ts b/src/BufferSet.ts index 1d4ae2c61..f5df2073e 100644 --- a/src/BufferSet.ts +++ b/src/BufferSet.ts @@ -7,6 +7,7 @@ import { ITerminal, IBufferSet, IBuffer } from './Types'; import { IAttributeData } from 'common/Types'; import { Buffer } from './Buffer'; import { EventEmitter2, IEvent } from 'common/EventEmitter2'; +import { IOptionsService } from 'common/services/Services'; /** * The BufferSet represents the set of two buffers used by xterm terminals (normal and alt) and @@ -25,13 +26,16 @@ export class BufferSet implements IBufferSet { * Create a new BufferSet for the given terminal. * @param _terminal - The terminal the BufferSet will belong to */ - constructor(private _terminal: ITerminal) { - this._normal = new Buffer(this._terminal, true); + constructor( + private _terminal: ITerminal, + readonly optionsService: IOptionsService + ) { + this._normal = new Buffer(this._terminal, true, optionsService); this._normal.fillViewportRows(); // The alt buffer should never have scrollback. // See http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-The-Alternate-Screen-Buffer - this._alt = new Buffer(this._terminal, false); + this._alt = new Buffer(this._terminal, false, optionsService); this._activeBuffer = this._normal; this.setupTabStops(); diff --git a/src/SelectionManager.test.ts b/src/SelectionManager.test.ts index 90d70d79f..970a8e9bf 100644 --- a/src/SelectionManager.test.ts +++ b/src/SelectionManager.test.ts @@ -9,7 +9,7 @@ import { SelectionModel } from './SelectionModel'; import { BufferSet } from './BufferSet'; import { ITerminal, IBuffer } from './Types'; import { IBufferLine } from 'common/Types'; -import { MockTerminal, MockCharSizeService } from './TestUtils.test'; +import { MockTerminal, MockCharSizeService, MockOptionsService } from './TestUtils.test'; import { BufferLine, CellData } from 'common/buffer/BufferLine'; class TestMockTerminal extends MockTerminal { @@ -46,8 +46,7 @@ describe('SelectionManager', () => { terminal = new TestMockTerminal(); (terminal as any).cols = 80; (terminal as any).rows = 2; - terminal.options.scrollback = 100; - terminal.buffers = new BufferSet(terminal); + terminal.buffers = new BufferSet(terminal, new MockOptionsService({ scrollback: 100 })); terminal.buffer = terminal.buffers.active; buffer = terminal.buffer; selectionManager = new TestSelectionManager(terminal); diff --git a/src/SelectionModel.test.ts b/src/SelectionModel.test.ts index c2b261a98..05444aac6 100644 --- a/src/SelectionModel.test.ts +++ b/src/SelectionModel.test.ts @@ -7,7 +7,7 @@ import { assert } from 'chai'; import { ITerminal } from './Types'; import { SelectionModel } from './SelectionModel'; import { BufferSet } from './BufferSet'; -import { MockTerminal } from './TestUtils.test'; +import { MockTerminal, MockOptionsService } from './TestUtils.test'; class TestSelectionModel extends SelectionModel { constructor( @@ -25,8 +25,7 @@ describe('SelectionManager', () => { terminal = new MockTerminal(); (terminal as any).cols = 80; (terminal as any).rows = 2; - terminal.options.scrollback = 10; - terminal.buffers = new BufferSet(terminal); + terminal.buffers = new BufferSet(terminal, new MockOptionsService({ scrollback: 10 })); terminal.buffer = terminal.buffers.active; model = new TestSelectionModel(terminal); diff --git a/src/Terminal.test.ts b/src/Terminal.test.ts index db4742695..a3cff41f8 100644 --- a/src/Terminal.test.ts +++ b/src/Terminal.test.ts @@ -379,10 +379,10 @@ describe('Terminal', () => { describe('scrollLines', () => { let startYDisp: number; beforeEach(() => { - for (let i = 0; i < term.rows * 2; i++) { + for (let i = 0; i < INIT_ROWS * 2; i++) { term.writeln('test'); } - startYDisp = term.rows + 1; + startYDisp = INIT_ROWS + 1; }); it('should scroll a single line', () => { assert.equal(term.buffer.ydisp, startYDisp); diff --git a/src/Terminal.ts b/src/Terminal.ts index a5348568e..29a0478f8 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -313,7 +313,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this.soundManager = this.soundManager || new SoundManager(this); // Create the terminal's buffers and set the current buffer - this.buffers = new BufferSet(this); + this.buffers = new BufferSet(this, this.optionsService); if (this.selectionManager) { this.selectionManager.clearSelection(); this.selectionManager.initBuffersListeners(); diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index e8fdeeba2..5ac908e4f 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -4,7 +4,7 @@ */ import { IRenderer, IRenderDimensions } from './renderer/Types'; -import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminal, IBuffer, IBufferSet, IBrowser, ISelectionManager, ITerminalOptions, ILinkifier, IMouseHelper, ILinkMatcherOptions, CharacterJoinerHandler, IBufferStringIterator } from './Types'; +import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminal, IBuffer, IBufferSet, IBrowser, ISelectionManager, ITerminalOptions as IInternalTerminalOptions, ILinkifier, IMouseHelper, ILinkMatcherOptions, CharacterJoinerHandler, IBufferStringIterator } from './Types'; import { IBufferLine, ICellData, IAttributeData, ICircularList, XtermListener } from 'common/Types'; import { Buffer } from './Buffer'; import * as Browser from 'common/Platform'; @@ -12,8 +12,10 @@ import { IDisposable, IMarker, IEvent, ISelectionPosition } from 'xterm'; import { Terminal } from './Terminal'; import { AttributeData } from 'common/buffer/BufferLine'; import { IColorManager, IColorSet } from 'browser/Types'; -import { IOptionsService } from 'common/services/Services'; +import { IOptionsService, IPartialTerminalOptions, ITerminalOptions } from 'common/services/Services'; import { ICharSizeService } from 'browser/services/Services'; +import { DEFAULT_OPTIONS } from 'common/services/OptionsService'; +import { clone } from 'common/Clone'; export class TestTerminal extends Terminal { writeSync(data: string): void { @@ -122,7 +124,7 @@ export class MockTerminal implements ITerminal { renderer: IRenderer; linkifier: ILinkifier; isFocused: boolean; - options: ITerminalOptions = {}; + options: IInternalTerminalOptions = {}; element: HTMLElement; screenElement: HTMLElement; rowContainer: HTMLElement; @@ -183,7 +185,7 @@ export class MockTerminal implements ITerminal { export class MockInputHandlingTerminal implements IInputHandlingTerminal { element: HTMLElement; - options: ITerminalOptions = {}; + options: IInternalTerminalOptions = {}; cols: number; rows: number; charset: { [key: string]: string; }; @@ -435,3 +437,17 @@ export class MockCharSizeService implements ICharSizeService { constructor(public width: number, public height: number) {} measure(): void {} } + +export class MockOptionsService implements IOptionsService { + options: ITerminalOptions = clone(DEFAULT_OPTIONS); + onOptionChange: IEvent; + constructor(testOptions: IPartialTerminalOptions) { + Object.keys(testOptions).forEach(key => this.options[key] = (testOptions)[key]); + } + setOption(key: string, value: T): void { + throw new Error('Method not implemented.'); + } + getOption(key: string): T { + throw new Error('Method not implemented.'); + } +} diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index 0fce405d7..63ad04a73 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -15,7 +15,7 @@ import { clone } from 'common/Clone'; export const DEFAULT_BELL_SOUND = 'data:audio/wav;base64,UklGRigBAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQQBAADpAFgCwAMlBZoG/wdmCcoKRAypDQ8PbRDBEQQTOxRtFYcWlBePGIUZXhoiG88bcBz7HHIdzh0WHlMeZx51HmkeUx4WHs8dah0AHXwc3hs9G4saxRnyGBIYGBcQFv8U4RPAEoYRQBACD70NWwwHC6gJOwjWBloF7gOBAhABkf8b/qv8R/ve+Xf4Ife79W/0JfPZ8Z/wde9N7ijtE+wU6xvqM+lb6H7nw+YX5mrlxuQz5Mzje+Ma49fioeKD4nXiYeJy4pHitOL04j/jn+MN5IPkFOWs5U3mDefM55/ogOl36m7rdOyE7abuyu8D8Unyj/Pg9D/2qfcb+Yn6/vuK/Qj/lAAlAg=='; // TODO: Freeze? -const DEFAULT_OPTIONS: ITerminalOptions = { +export const DEFAULT_OPTIONS: ITerminalOptions = { cols: 80, rows: 24, cursorBlink: false, diff --git a/tslint.json b/tslint.json index d2daef092..f6f1f46c6 100644 --- a/tslint.json +++ b/tslint.json @@ -95,7 +95,7 @@ {"type": "default", "format": "camelCase", "leadingUnderscore": "forbid"}, {"type": "type", "format": "PascalCase"}, {"type": "class", "format": "PascalCase"}, - {"type": "property", "modifiers": ["const"], "format": "UPPER_CASE"}, + {"type": "property", "modifiers": ["const"], "format": ["camelCase", "UPPER_CASE"]}, {"type": "member", "modifiers": ["protected"], "format": "camelCase", "leadingUnderscore": "allow"}, // TODO: Change allow to require when there aren't many PRs out // {"type": "member", "modifiers": ["protected"], "format": "camelCase", "leadingUnderscore": "require"},