Skip to content
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
14 changes: 14 additions & 0 deletions src/Interfaces.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* xterm.js: xterm, in the browser
* Copyright (c) 2014-2016, SourceLair Private Company (www.sourcelair.com (MIT License)
*/

export interface ITerminal {
rowContainer: HTMLElement;
ydisp: number;
lines: string[];
rows: number;

on(event: string, callback: () => void);
scrollDisp(disp: number, suppressScrollEvent: boolean);
}
114 changes: 0 additions & 114 deletions src/Viewport.js

This file was deleted.

38 changes: 16 additions & 22 deletions src/test/viewport-test.js → src/Viewport.test.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,29 @@
var assert = require('chai').assert;
var Terminal = require('../xterm');
import { assert } from 'chai';
import { Viewport } from './Viewport';

describe('Viewport', function () {
describe('Viewport', () => {
var terminal;
var viewportElement;
var charMeasureElement;
var viewport;
var scrollAreaElement;

var CHARACTER_HEIGHT = 10;
const CHARACTER_HEIGHT = 10;

beforeEach(function () {
beforeEach(() => {
terminal = {
lines: [],
rows: 0,
ydisp: 0,
on: function () {},
on: () => {},
rowContainer: {
style: {
lineHeight: 0
}
}
};
viewportElement = {
addEventListener: function () {},
addEventListener: () => {},
style: {
height: 0,
lineHeight: 0
Expand All @@ -35,46 +35,40 @@ describe('Viewport', function () {
}
};
charMeasureElement = {
getBoundingClientRect: function () {
getBoundingClientRect: () => {
return { width: null, height: CHARACTER_HEIGHT };
}
};
viewport = new Terminal.Viewport(terminal, viewportElement, scrollAreaElement, charMeasureElement);
viewport = new Viewport(terminal, viewportElement, scrollAreaElement, charMeasureElement);
});

describe('Public API', function () {
it('should define Viewport.prototype.onWheel', function () {
assert.isDefined(Terminal.Viewport.prototype.onWheel);
});
});

describe('refresh', function () {
it('should set the line-height of the terminal', function () {
describe('refresh', () => {
it('should set the line-height of the terminal', () => {
assert.equal(viewportElement.style.lineHeight, CHARACTER_HEIGHT + 'px');
assert.equal(terminal.rowContainer.style.lineHeight, CHARACTER_HEIGHT + 'px');
charMeasureElement.getBoundingClientRect = function () {
charMeasureElement.getBoundingClientRect = () => {
return { width: null, height: 1 };
};
viewport.refresh();
assert.equal(viewportElement.style.lineHeight, '1px');
assert.equal(terminal.rowContainer.style.lineHeight, '1px');
});
it('should set the height of the viewport when the line-height changed', function () {
it('should set the height of the viewport when the line-height changed', () => {
terminal.lines.push('');
terminal.lines.push('');
terminal.rows = 1;
viewport.refresh();
assert.equal(viewportElement.style.height, 1 * CHARACTER_HEIGHT + 'px');
charMeasureElement.getBoundingClientRect = function () {
charMeasureElement.getBoundingClientRect = () => {
return { width: null, height: 20 };
};
viewport.refresh();
assert.equal(viewportElement.style.height, 20 + 'px');
});
});

describe('syncScrollArea', function () {
it('should sync the scroll area', function () {
describe('syncScrollArea', () => {
it('should sync the scroll area', () => {
terminal.lines.push('');
terminal.rows = 1;
assert.equal(scrollAreaElement.style.height, 0 * CHARACTER_HEIGHT + 'px');
Expand Down
120 changes: 120 additions & 0 deletions src/Viewport.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/**
* xterm.js: xterm, in the browser
* Copyright (c) 2014-2016, SourceLair Private Company (www.sourcelair.com (MIT License)
*/

import { ITerminal } from './Interfaces';

/**
* Represents the viewport of a terminal, the visible area within the larger buffer of output.
* Logic for the virtual scroll bar is included in this object.
* @param viewportElement The DOM element acting as the viewport.
* @param scrollArea The DOM element acting as the scroll area.
* @param charMeasureElement A DOM element used to measure the character size of. the terminal.
*/
export class Viewport {
private currentRowHeight: number;
private lastRecordedBufferLength: number;
private lastRecordedViewportHeight: number;

constructor(
private terminal: ITerminal,
private viewportElement: HTMLElement,
private scrollArea: HTMLElement,
private charMeasureElement: HTMLElement
) {
this.currentRowHeight = 0;
this.lastRecordedBufferLength = 0;
this.lastRecordedViewportHeight = 0;

this.terminal.on('scroll', this.syncScrollArea.bind(this));
this.terminal.on('resize', this.syncScrollArea.bind(this));
this.viewportElement.addEventListener('scroll', this.onScroll.bind(this));

this.syncScrollArea();
}

/**
* Refreshes row height, setting line-height, viewport height and scroll area height if
* necessary.
* @param charSize A character size measurement bounding rect object, if it doesn't exist it will
* be created.
*/
private refresh(charSize?: ClientRect): void {
var size = charSize || this.charMeasureElement.getBoundingClientRect();
if (size.height > 0) {
var rowHeightChanged = size.height !== this.currentRowHeight;
if (rowHeightChanged) {
this.currentRowHeight = size.height;
this.viewportElement.style.lineHeight = size.height + 'px';
this.terminal.rowContainer.style.lineHeight = size.height + 'px';
}
var viewportHeightChanged = this.lastRecordedViewportHeight !== this.terminal.rows;
if (rowHeightChanged || viewportHeightChanged) {
this.lastRecordedViewportHeight = this.terminal.rows;
this.viewportElement.style.height = size.height * this.terminal.rows + 'px';
}
this.scrollArea.style.height = (size.height * this.lastRecordedBufferLength) + 'px';
}
}

/**
* Updates dimensions and synchronizes the scroll area if necessary.
*/
public syncScrollArea(): void {
if (this.lastRecordedBufferLength !== this.terminal.lines.length) {
// If buffer height changed
this.lastRecordedBufferLength = this.terminal.lines.length;
this.refresh();
} else if (this.lastRecordedViewportHeight !== this.terminal.rows) {
// If viewport height changed
this.refresh();
} else {
// If size has changed, refresh viewport
var size = this.charMeasureElement.getBoundingClientRect();
if (size.height !== this.currentRowHeight) {
this.refresh(size);
}
}

// Sync scrollTop
var scrollTop = this.terminal.ydisp * this.currentRowHeight;
if (this.viewportElement.scrollTop !== scrollTop) {
this.viewportElement.scrollTop = scrollTop;
}
}

/**
* Handles scroll events on the viewport, calculating the new viewport and requesting the
* terminal to scroll to it.
* @param ev The scroll event.
*/
private onScroll(ev: Event) {
var newRow = Math.round(this.viewportElement.scrollTop / this.currentRowHeight);
var diff = newRow - this.terminal.ydisp;
this.terminal.scrollDisp(diff, true);
}

/**
* Handles mouse wheel events by adjusting the viewport's scrollTop and delegating the actual
* scrolling to `onScroll`, this event needs to be attached manually by the consumer of
* `Viewport`.
* @param ev The mouse wheel event.
*/
public onWheel(ev: WheelEvent) {
if (ev.deltaY === 0) {
// Do nothing if it's not a vertical scroll event
return;
}
// Fallback to WheelEvent.DOM_DELTA_PIXEL
var multiplier = 1;
if (ev.deltaMode === WheelEvent.DOM_DELTA_LINE) {
multiplier = this.currentRowHeight;
} else if (ev.deltaMode === WheelEvent.DOM_DELTA_PAGE) {
multiplier = this.currentRowHeight * this.terminal.rows;
}
this.viewportElement.scrollTop += ev.deltaY * multiplier;
// Prevent the page from scrolling when the terminal scrolls
ev.preventDefault();
};
}