Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor buffer in preparation for true color and buffer performance improvements #861

Merged
merged 2 commits into from
Aug 7, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions src/Buffer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import { ITerminal, IBuffer } from './Interfaces';
import { CircularList } from './utils/CircularList';
import { LineData, CharData } from './Types';

export const CHAR_DATA_CHAR_INDEX = 1;
export const CHAR_DATA_WIDTH_INDEX = 2;

/**
* This class represents a terminal buffer (an internal state of the terminal), where the
* following information is stored (in high-level):
Expand Down Expand Up @@ -146,4 +149,52 @@ export class Buffer implements IBuffer {
this.scrollTop = 0;
this.scrollBottom = newRows - 1;
}

/**
* Translates a buffer line to a string, with optional start and end columns.
* Wide characters will count as two columns in the resulting string. This
* function is useful for getting the actual text underneath the raw selection
* position.
* @param line The line being translated.
* @param trimRight Whether to trim whitespace to the right.
* @param startCol The column to start at.
* @param endCol The column to end at.
*/
public translateBufferLineToString(lineIndex: number, trimRight: boolean, startCol: number = 0, endCol: number = null): string {
// Get full line
let lineString = '';
let widthAdjustedStartCol = startCol;
let widthAdjustedEndCol = endCol;
const line = this.lines.get(lineIndex);
for (let i = 0; i < line.length; i++) {
const char = line[i];
lineString += char[CHAR_DATA_CHAR_INDEX];
// Adjust start and end cols for wide characters if they affect their
// column indexes
if (char[CHAR_DATA_WIDTH_INDEX] === 0) {
if (startCol >= i) {
widthAdjustedStartCol--;
}
if (endCol >= i) {
widthAdjustedEndCol--;
}
}
}

// Calculate the final end col by trimming whitespace on the right of the
// line if needed.
let finalEndCol = widthAdjustedEndCol || line.length;
if (trimRight) {
const rightWhitespaceIndex = lineString.search(/\s+$/);
if (rightWhitespaceIndex !== -1) {
finalEndCol = Math.min(finalEndCol, rightWhitespaceIndex);
}
// Return the empty string if only trimmed whitespace is selected
if (finalEndCol <= widthAdjustedStartCol) {
return '';
}
}

return lineString.substring(widthAdjustedStartCol, finalEndCol);
}
}
14 changes: 8 additions & 6 deletions src/InputHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { IInputHandler, ITerminal, IInputHandlingTerminal } from './Interfaces';
import { C0 } from './EscapeSequences';
import { DEFAULT_CHARSET } from './Charsets';
import { CharData } from './Types';
import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX } from './Buffer';

/**
* The terminal's standard implementation of IInputHandler, this handles all
Expand Down Expand Up @@ -34,14 +35,14 @@ export class InputHandler implements IInputHandler {
if (!ch_width && this._terminal.buffer.x) {
// dont overflow left
if (this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 1]) {
if (!this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 1][2]) {
if (!this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 1][CHAR_DATA_WIDTH_INDEX]) {

// found empty cell after fullwidth, need to go 2 cells back
if (this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 2])
this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 2][1] += char;
this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 2][CHAR_DATA_CHAR_INDEX] += char;

} else {
this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 1][1] += char;
this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 1][CHAR_DATA_CHAR_INDEX] += char;
}
this._terminal.updateRange(this._terminal.buffer.y);
}
Expand Down Expand Up @@ -77,9 +78,9 @@ export class InputHandler implements IInputHandler {
// remove last cell, if it's width is 0
// we have to adjust the second last cell as well
const removed = this._terminal.buffer.lines.get(this._terminal.buffer.y + this._terminal.buffer.ybase).pop();
if (removed[2] === 0
if (removed[CHAR_DATA_WIDTH_INDEX] === 0
&& this._terminal.buffer.lines.get(row)[this._terminal.cols - 2]
&& this._terminal.buffer.lines.get(row)[this._terminal.cols - 2][2] === 2) {
&& this._terminal.buffer.lines.get(row)[this._terminal.cols - 2][CHAR_DATA_WIDTH_INDEX] === 2) {
this._terminal.buffer.lines.get(row)[this._terminal.cols - 2] = [this._terminal.curAttr, ' ', 1];
}

Expand Down Expand Up @@ -945,6 +946,7 @@ export class InputHandler implements IInputHandler {
case 47: // alt screen buffer
case 1047: // alt screen buffer
this._terminal.buffers.activateAltBuffer();
this._terminal.selectionManager.setBuffer(this._terminal.buffer);
this._terminal.viewport.syncScrollArea();
this._terminal.showCursor();
break;
Expand Down Expand Up @@ -1113,7 +1115,7 @@ export class InputHandler implements IInputHandler {
// if (params[0] === 1049) {
// this.restoreCursor(params);
// }
this._terminal.selectionManager.setBuffer(this._terminal.buffer.lines);
this._terminal.selectionManager.setBuffer(this._terminal.buffer);
this._terminal.refresh(0, this._terminal.rows - 1);
this._terminal.viewport.syncScrollArea();
this._terminal.showCursor();
Expand Down
3 changes: 2 additions & 1 deletion src/Interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ export interface IBuffer {
scrollTop: number;
savedY: number;
savedX: number;
translateBufferLineToString(lineIndex: number, trimRight: boolean, startCol?: number, endCol?: number): string;
}

export interface IBufferSet {
Expand All @@ -166,7 +167,7 @@ export interface ISelectionManager {

disable(): void;
enable(): void;
setBuffer(buffer: ICircularList<LineData>): void;
setBuffer(buffer: IBuffer): void;
setSelection(row: number, col: number, length: number): void;
}

Expand Down
5 changes: 3 additions & 2 deletions src/Renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import { ITerminal } from './Interfaces';
import { DomElementObjectPool } from './utils/DomElementObjectPool';
import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from './Buffer';

/**
* The maximum number of refresh frames to skip when the write buffer is non-
Expand Down Expand Up @@ -168,8 +169,8 @@ export class Renderer {
for (let i = 0; i < width; i++) {
// TODO: Could data be a more specific type?
let data: any = line[i][0];
const ch = line[i][1];
const ch_width: any = line[i][2];
const ch = line[i][CHAR_DATA_CHAR_INDEX];
const ch_width: any = line[i][CHAR_DATA_WIDTH_INDEX];
const isCursor: boolean = i === x;
if (!ch_width) {
continue;
Expand Down
34 changes: 17 additions & 17 deletions src/SelectionManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import jsdom = require('jsdom');
import { assert } from 'chai';
import { ITerminal, ICircularList } from './Interfaces';
import { ITerminal, ICircularList, IBuffer } from './Interfaces';
import { CharMeasure } from './utils/CharMeasure';
import { CircularList } from './utils/CircularList';
import { SelectionManager } from './SelectionManager';
Expand All @@ -16,7 +16,7 @@ import { LineData } from './Types';
class TestSelectionManager extends SelectionManager {
constructor(
terminal: ITerminal,
buffer: ICircularList<LineData>,
buffer: IBuffer,
rowContainer: HTMLElement,
charMeasure: CharMeasure
) {
Expand All @@ -40,7 +40,7 @@ describe('SelectionManager', () => {
let document: Document;

let terminal: ITerminal;
let bufferLines: ICircularList<LineData>;
let buffer: IBuffer;
let rowContainer: HTMLElement;
let selectionManager: TestSelectionManager;

Expand All @@ -55,8 +55,8 @@ describe('SelectionManager', () => {
terminal.options.scrollback = 100;
terminal.buffers = new BufferSet(terminal);
terminal.buffer = terminal.buffers.active;
bufferLines = terminal.buffer.lines;
selectionManager = new TestSelectionManager(terminal, bufferLines, rowContainer, null);
buffer = terminal.buffer;
selectionManager = new TestSelectionManager(terminal, buffer, rowContainer, null);
});

function stringToRow(text: string): LineData {
Expand All @@ -69,7 +69,7 @@ describe('SelectionManager', () => {

describe('_selectWordAt', () => {
it('should expand selection for normal width chars', () => {
bufferLines.set(0, stringToRow('foo bar'));
buffer.lines.set(0, stringToRow('foo bar'));
selectionManager.selectWordAt([0, 0]);
assert.equal(selectionManager.selectionText, 'foo');
selectionManager.selectWordAt([1, 0]);
Expand All @@ -86,7 +86,7 @@ describe('SelectionManager', () => {
assert.equal(selectionManager.selectionText, 'bar');
});
it('should expand selection for whitespace', () => {
bufferLines.set(0, stringToRow('a b'));
buffer.lines.set(0, stringToRow('a b'));
selectionManager.selectWordAt([0, 0]);
assert.equal(selectionManager.selectionText, 'a');
selectionManager.selectWordAt([1, 0]);
Expand All @@ -100,7 +100,7 @@ describe('SelectionManager', () => {
});
it('should expand selection for wide characters', () => {
// Wide characters use a special format
bufferLines.set(0, [
buffer.lines.set(0, [
[null, '中', 2],
[null, '', 0],
[null, '文', 2],
Expand Down Expand Up @@ -152,7 +152,7 @@ describe('SelectionManager', () => {
assert.equal(selectionManager.selectionText, 'foo');
});
it('should select up to non-path characters that are commonly adjacent to paths', () => {
bufferLines.set(0, stringToRow('(cd)[ef]{gh}\'ij"'));
buffer.lines.set(0, stringToRow('(cd)[ef]{gh}\'ij"'));
selectionManager.selectWordAt([0, 0]);
assert.equal(selectionManager.selectionText, '(cd');
selectionManager.selectWordAt([1, 0]);
Expand Down Expand Up @@ -190,7 +190,7 @@ describe('SelectionManager', () => {

describe('_selectLineAt', () => {
it('should select the entire line', () => {
bufferLines.set(0, stringToRow('foo bar'));
buffer.lines.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]);
Expand All @@ -200,14 +200,14 @@ describe('SelectionManager', () => {

describe('selectAll', () => {
it('should select the entire buffer, beyond the viewport', () => {
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'));
buffer.lines.length = 5;
buffer.lines.set(0, stringToRow('1'));
buffer.lines.set(1, stringToRow('2'));
buffer.lines.set(2, stringToRow('3'));
buffer.lines.set(3, stringToRow('4'));
buffer.lines.set(4, stringToRow('5'));
selectionManager.selectAll();
terminal.buffer.ybase = bufferLines.length - terminal.rows;
terminal.buffer.ybase = buffer.lines.length - terminal.rows;
assert.equal(selectionManager.selectionText, '1\n2\n3\n4\n5');
});
});
Expand Down
Loading