]> git.proxmox.com Git - mirror_xterm.js.git/commitdiff
Clean up buffer clean up/fill logic
authorDaniel Imms <daimms@microsoft.com>
Sun, 6 Aug 2017 02:42:52 +0000 (19:42 -0700)
committerDaniel Imms <daimms@microsoft.com>
Sun, 6 Aug 2017 02:42:54 +0000 (19:42 -0700)
The alt buffer is now cleared immediated after activating the normal buffer and
is filled when switching to it. The tests were failing because the alt buffer
wasn't being cleared properly with the previous solution.

src/Buffer.ts
src/BufferSet.test.ts
src/BufferSet.ts
src/InputHandler.ts
src/Interfaces.ts
src/SelectionManager.test.ts
src/SelectionModel.test.ts
src/utils/CircularList.ts
src/utils/TestUtils.ts [new file with mode: 0644]
src/xterm.js

index bd3645e7f85c9d80f3de50aeffdf11d984cf12d9..714151d05d4ea68bd6036cacbf470851cc6a7b24 100644 (file)
@@ -2,7 +2,7 @@
  * @license MIT
  */
 
-import { ITerminal } from './Interfaces';
+import { ITerminal, IBuffer } from './Interfaces';
 import { CircularList } from './utils/CircularList';
 
 /**
@@ -12,9 +12,16 @@ import { CircularList } from './utils/CircularList';
  *   - cursor position
  *   - scroll position
  */
-export class Buffer {
-  public readonly lines: CircularList<[number, string, number][]>;
+export class Buffer implements IBuffer {
+  private _lines: CircularList<[number, string, number][]>;
 
+  public ydisp: number;
+  public ybase: number;
+  public y: number;
+  public x: number;
+  public scrollBottom: number;
+  public scrollTop: number;
+  public tabs: any;
   public savedY: number;
   public savedX: number;
 
@@ -27,34 +34,51 @@ export class Buffer {
    * @param {number} x - The cursor's x position after ybase
    */
   constructor(
-    private _terminal: ITerminal,
-    public ydisp: number = 0,
-    public ybase: number = 0,
-    public y: number = 0,
-    public x: number = 0,
-    public scrollBottom: number = 0,
-    public scrollTop: number = 0,
-    public tabs: any = {},
+    private _terminal: ITerminal
   ) {
-    this.lines = new CircularList<[number, string, number][]>(this._terminal.scrollback);
+    this.clear();
+  }
+
+  public get lines(): CircularList<[number, string, number][]> {
+    return this._lines;
+  }
+
+  public fillViewportRows(): void {
+    if (this._lines.length === 0) {
+      let i = this._terminal.rows;
+      while (i--) {
+        this.lines.push(this._terminal.blankLine());
+      }
+    }
+  }
+
+  public clear(): void {
+    this.ydisp = 0;
+    this.ybase = 0;
+    this.y = 0;
+    this.x = 0;
+    this.scrollBottom = 0;
+    this.scrollTop = 0;
+    this.tabs = {};
+    this._lines = new CircularList<[number, string, number][]>(this._terminal.scrollback);
     this.scrollBottom = this._terminal.rows - 1;
   }
 
   public resize(newCols: number, newRows: number): void {
     // Don't resize the buffer if it's empty and hasn't been used yet.
-    if (this.lines.length === 0) {
+    if (this._lines.length === 0) {
       return;
     }
 
     // Deal with columns increasing (we don't do anything when columns reduce)
     if (this._terminal.cols < newCols) {
       const ch: [number, string, number] = [this._terminal.defAttr, ' ', 1]; // does xterm use the default attr?
-      for (let i = 0; i < this.lines.length; i++) {
-        if (this.lines.get(i) === undefined) {
-          this.lines.set(i, this._terminal.blankLine());
+      for (let i = 0; i < this._lines.length; i++) {
+        if (this._lines.get(i) === undefined) {
+          this._lines.set(i, this._terminal.blankLine());
         }
-        while (this.lines.get(i).length < newCols) {
-          this.lines.get(i).push(ch);
+        while (this._lines.get(i).length < newCols) {
+          this._lines.get(i).push(ch);
         }
       }
     }
@@ -63,8 +87,8 @@ export class Buffer {
     let addToY = 0;
     if (this._terminal.rows < newRows) {
       for (let y = this._terminal.rows; y < newRows; y++) {
-        if (this.lines.length < newRows + this.ybase) {
-          if (this.ybase > 0 && this.lines.length <= this.ybase + this.y + addToY + 1) {
+        if (this._lines.length < newRows + this.ybase) {
+          if (this.ybase > 0 && this._lines.length <= this.ybase + this.y + addToY + 1) {
             // There is room above the buffer and there are no empty elements below the line,
             // scroll up
             this.ybase--;
@@ -76,16 +100,16 @@ export class Buffer {
           } else {
             // Add a blank line if there is no buffer left at the top to scroll to, or if there
             // are blank lines after the cursor
-            this.lines.push(this._terminal.blankLine());
+            this._lines.push(this._terminal.blankLine());
           }
         }
       }
     } else { // (this._terminal.rows >= newRows)
       for (let y = this._terminal.rows; y > newRows; y--) {
-        if (this.lines.length > newRows + this.ybase) {
-          if (this.lines.length > this.ybase + this.y + 1) {
+        if (this._lines.length > newRows + this.ybase) {
+          if (this._lines.length > this.ybase + this.y + 1) {
             // The line is a blank line below the cursor, remove it
-            this.lines.pop();
+            this._lines.pop();
           } else {
             // The line is the cursor, scroll down
             this.ybase++;
index 2101fbc1f48c033a29f702f102299c83207c94ea..ab814cee61144f5d8f5f57ade3c6dd20b4c5ea44 100644 (file)
@@ -5,17 +5,17 @@ import { assert } from 'chai';
 import { ITerminal } from './Interfaces';
 import { BufferSet } from './BufferSet';
 import { Buffer } from './Buffer';
+import { MockTerminal } from './utils/TestUtils';
 
 describe('BufferSet', () => {
   let terminal: ITerminal;
   let bufferSet: BufferSet;
 
   beforeEach(() => {
-    terminal = <any>{
-      cols: 80,
-      rows: 24,
-      scrollback: 1000
-    };
+    terminal = new MockTerminal();
+    terminal.cols = 80;
+    terminal.rows = 24;
+    terminal.scrollback = 1000;
     bufferSet = new BufferSet(terminal);
   });
 
index 4a65dbfe16e984aa3da41e29da225dea681aa8c4..345191cfa2116d3e5483ec0f4b1670478e427f1e 100644 (file)
@@ -22,6 +22,7 @@ export class BufferSet extends EventEmitter implements IBufferSet {
   constructor(private _terminal: ITerminal) {
     super();
     this._normal = new Buffer(this._terminal);
+    this._normal.fillViewportRows();
     this._alt = new Buffer(this._terminal);
     this._activeBuffer = this._normal;
   }
@@ -54,6 +55,11 @@ export class BufferSet extends EventEmitter implements IBufferSet {
    * Sets the normal Buffer of the BufferSet as its currently active Buffer
    */
   public activateNormalBuffer(): void {
+    // The alt buffer should always be cleared when we switch to the normal
+    // buffer. This frees up memory since the alt buffer should always be new
+    // when activated.
+    this._alt.clear();
+
     this._activeBuffer = this._normal;
     this.emit('activate', this._normal);
   }
@@ -62,6 +68,10 @@ export class BufferSet extends EventEmitter implements IBufferSet {
    * Sets the alt Buffer of the BufferSet as its currently active Buffer
    */
   public activateAltBuffer(): void {
+    // Since the alt buffer is always cleared when the normal buffer is
+    // activated, we want to fill it when switching to it.
+    this._alt.fillViewportRows();
+
     this._activeBuffer = this._alt;
     this.emit('activate', this._alt);
   }
index 64da3b3f152505a468c3d5603eae74e914dc24db..b915f2c0b942fc7053cfd152e36486a7eb610c4c 100644 (file)
@@ -954,7 +954,6 @@ export class InputHandler implements IInputHandler {
         case 47: // alt screen buffer
         case 1047: // alt screen buffer
           this._terminal.buffers.activateAltBuffer();
-          this._terminal.reset();
           this._terminal.viewport.syncScrollArea();
           this._terminal.showCursor();
           break;
index d463fcb795dbccd388e8da60f07bcbc71750bdb2..70a46d419ac3585e2f6cbc2ebb8afc1caf35a300 100644 (file)
@@ -93,8 +93,8 @@ export interface ILinkifier {
 export interface ICircularList<T> extends IEventEmitter {
   length: number;
   maxLength: number;
+  forEach: (callbackfn: (value: T, index: number) => void) => void;
 
-  forEach(callbackfn: (value: T, index: number, array: T[]) => void): void;
   get(index: number): T;
   set(index: number, value: T): void;
   push(value: T): void;
index c2173d77f47b2c8317c4ab5984e4453a8cf602ce..7beafd977fda939f39ca1d594da15aa011b10667 100644 (file)
@@ -9,6 +9,7 @@ import { CircularList } from './utils/CircularList';
 import { SelectionManager } from './SelectionManager';
 import { SelectionModel } from './SelectionModel';
 import { BufferSet } from './BufferSet';
+import { MockTerminal } from './utils/TestUtils';
 
 class TestSelectionManager extends SelectionManager {
   constructor(
@@ -46,7 +47,9 @@ describe('SelectionManager', () => {
     window = dom.window;
     document = window.document;
     rowContainer = document.createElement('div');
-    terminal = <any>{ cols: 80, rows: 2 };
+    terminal = new MockTerminal();
+    terminal.cols = 80;
+    terminal.rows = 2;
     terminal.scrollback = 100;
     terminal.buffers = new BufferSet(terminal);
     terminal.buffer = terminal.buffers.active;
@@ -64,7 +67,7 @@ describe('SelectionManager', () => {
 
   describe('_selectWordAt', () => {
     it('should expand selection for normal width chars', () => {
-      bufferLines.push(stringToRow('foo bar'));
+      bufferLines.set(0, stringToRow('foo bar'));
       selectionManager.selectWordAt([0, 0]);
       assert.equal(selectionManager.selectionText, 'foo');
       selectionManager.selectWordAt([1, 0]);
@@ -81,7 +84,7 @@ describe('SelectionManager', () => {
       assert.equal(selectionManager.selectionText, 'bar');
     });
     it('should expand selection for whitespace', () => {
-      bufferLines.push(stringToRow('a   b'));
+      bufferLines.set(0, stringToRow('a   b'));
       selectionManager.selectWordAt([0, 0]);
       assert.equal(selectionManager.selectionText, 'a');
       selectionManager.selectWordAt([1, 0]);
@@ -95,7 +98,7 @@ describe('SelectionManager', () => {
     });
     it('should expand selection for wide characters', () => {
       // Wide characters use a special format
-      bufferLines.push([
+      bufferLines.set(0, [
         [null, '中', 2],
         [null, '', 0],
         [null, '文', 2],
@@ -147,7 +150,7 @@ describe('SelectionManager', () => {
       assert.equal(selectionManager.selectionText, 'foo');
     });
     it('should select up to non-path characters that are commonly adjacent to paths', () => {
-      bufferLines.push(stringToRow('(cd)[ef]{gh}\'ij"'));
+      bufferLines.set(0, stringToRow('(cd)[ef]{gh}\'ij"'));
       selectionManager.selectWordAt([0, 0]);
       assert.equal(selectionManager.selectionText, '(cd');
       selectionManager.selectWordAt([1, 0]);
@@ -185,7 +188,7 @@ describe('SelectionManager', () => {
 
   describe('_selectLineAt', () => {
     it('should select the entire line', () => {
-      bufferLines.push(stringToRow('foo bar'));
+      bufferLines.set(0, stringToRow('foo bar'));
       selectionManager.selectLineAt(0);
       assert.equal(selectionManager.selectionText, 'foo bar', 'The selected text is correct');
       assert.deepEqual(selectionManager.model.finalSelectionStart, [0, 0]);
@@ -195,11 +198,12 @@ describe('SelectionManager', () => {
 
   describe('selectAll', () => {
     it('should select the entire buffer, beyond the viewport', () => {
-      bufferLines.push(stringToRow('1'));
-      bufferLines.push(stringToRow('2'));
-      bufferLines.push(stringToRow('3'));
-      bufferLines.push(stringToRow('4'));
-      bufferLines.push(stringToRow('5'));
+      bufferLines.length = 5;
+      bufferLines.set(0, stringToRow('1'));
+      bufferLines.set(1, stringToRow('2'));
+      bufferLines.set(2, stringToRow('3'));
+      bufferLines.set(3, stringToRow('4'));
+      bufferLines.set(4, stringToRow('5'));
       selectionManager.selectAll();
       terminal.buffer.ybase = bufferLines.length - terminal.rows;
       assert.equal(selectionManager.selectionText, '1\n2\n3\n4\n5');
index 6da3874bd40ed0e3ade296f0bdf3079bc1add443..b087944273a54da097fcbc9cc6f01c01245a2687 100644 (file)
@@ -5,6 +5,7 @@ import { assert } from 'chai';
 import { ITerminal } from './Interfaces';
 import { SelectionModel } from './SelectionModel';
 import {BufferSet} from './BufferSet';
+import { MockTerminal } from './utils/TestUtils';
 
 class TestSelectionModel extends SelectionModel {
   constructor(
@@ -22,7 +23,9 @@ describe('SelectionManager', () => {
   let model: TestSelectionModel;
 
   beforeEach(() => {
-    terminal = <any>{ cols: 80, rows: 2, ybase: 0 };
+    terminal = new MockTerminal();
+    terminal.cols = 80;
+    terminal.rows = 2;
     terminal.scrollback = 10;
     terminal.buffers = new BufferSet(terminal);
     terminal.buffer = terminal.buffers.active;
index d0b2f685a48031d79d87cd3de761de54e60148b1..54850ab7c3806c487a57143ef6ea6cce03bd0078 100644 (file)
@@ -5,8 +5,9 @@
  * @license MIT
  */
 import { EventEmitter } from '../EventEmitter';
+import { ICircularList } from '../Interfaces';
 
-export class CircularList<T> extends EventEmitter {
+export class CircularList<T> extends EventEmitter implements ICircularList<T> {
   private _array: T[];
   private _startIndex: number;
   private _length: number;
diff --git a/src/utils/TestUtils.ts b/src/utils/TestUtils.ts
new file mode 100644 (file)
index 0000000..fbb1726
--- /dev/null
@@ -0,0 +1,53 @@
+import { ITerminal, IBuffer, IBufferSet, IBrowser, ICharMeasure, ISelectionManager } from '../Interfaces';
+
+export class MockTerminal implements ITerminal {
+  public element: HTMLElement;
+  public rowContainer: HTMLElement;
+  public selectionContainer: HTMLElement;
+  public selectionManager: ISelectionManager;
+  public charMeasure: ICharMeasure;
+  public textarea: HTMLTextAreaElement;
+  public rows: number;
+  public cols: number;
+  public browser: IBrowser;
+  public writeBuffer: string[];
+  public children: HTMLElement[];
+  public cursorHidden: boolean;
+  public cursorState: number;
+  public defAttr: number;
+  public scrollback: number;
+  public buffers: IBufferSet;
+  public buffer: IBuffer;
+
+  handler(data: string) {
+    throw new Error('Method not implemented.');
+  }
+  on(event: string, callback: () => void) {
+    throw new Error('Method not implemented.');
+  }
+  scrollDisp(disp: number, suppressScrollEvent: boolean) {
+    throw new Error('Method not implemented.');
+  }
+  cancel(ev: Event, force?: boolean) {
+    throw new Error('Method not implemented.');
+  }
+  log(text: string): void {
+    throw new Error('Method not implemented.');
+  }
+  emit(event: string, data: any) {
+    throw new Error('Method not implemented.');
+  }
+  reset(): void {
+    throw new Error('Method not implemented.');
+  }
+  showCursor(): void {
+    throw new Error('Method not implemented.');
+  }
+  blankLine(cur?: boolean, isWrapped?: boolean) {
+    const line = [];
+    for (let i = 0; i < this.cols; i++) {
+      line.push([0, ' ', 1]);
+    }
+    return line;
+  }
+}
index 92fc0df83586c21c841e6497d076370d0edd7762..e9e3465e6b83b98255f67ef1b64dc30036cae1a3 100644 (file)
@@ -221,17 +221,12 @@ function Terminal(options) {
   this.surrogate_high = '';
 
   // Create the terminal's buffers and set the current buffer
-  this.buffers = this.buffers || new BufferSet(this);
+  this.buffers = new BufferSet(this);
   this.buffer = this.buffers.active;  // Convenience shortcut;
   this.buffers.on('activate', function (buffer) {
     this._terminal.buffer = buffer;
   });
 
-  var i = this.rows;
-
-  while (i--) {
-    this.buffer.lines.push(this.blankLine());
-  }
   // Ensure the selection manager has the correct buffer
   if (this.selectionManager) {
     this.selectionManager.setBuffer(this.buffer.lines);
@@ -2228,12 +2223,10 @@ Terminal.prototype.reset = function() {
   var customKeyEventHandler = this.customKeyEventHandler;
   var cursorBlinkInterval = this.cursorBlinkInterval;
   var inputHandler = this.inputHandler;
-  var buffers = this.buffers;
   Terminal.call(this, this.options);
   this.customKeyEventHandler = customKeyEventHandler;
   this.cursorBlinkInterval = cursorBlinkInterval;
   this.inputHandler = inputHandler;
-  this.buffers = buffers;
   this.refresh(0, this.rows - 1);
   this.viewport.syncScrollArea();
 };