diff --git a/demo/index.html b/demo/index.html
index b6d0ff61..a62cff0e 100644
--- a/demo/index.html
+++ b/demo/index.html
@@ -64,6 +64,10 @@
+
+
+
+
diff --git a/demo/main.js b/demo/main.js
index 1faf77d8..264a205d 100644
--- a/demo/main.js
+++ b/demo/main.js
@@ -33,23 +33,27 @@ var terminalContainer = document.getElementById('terminal-container'),
bellStyle: document.querySelector('#option-bell-style')
},
colsElement = document.getElementById('cols'),
- rowsElement = document.getElementById('rows');
+ rowsElement = document.getElementById('rows'),
+ paddingElement = document.getElementById('padding');
function setTerminalSize() {
var cols = parseInt(colsElement.value, 10);
var rows = parseInt(rowsElement.value, 10);
- var viewportElement = document.querySelector('.xterm-viewport');
- var scrollBarWidth = viewportElement.offsetWidth - viewportElement.clientWidth;
- var width = (cols * term.renderer.dimensions.actualCellWidth + 20 /*room for scrollbar*/).toString() + 'px';
+ var width = (cols * term.renderer.dimensions.actualCellWidth + term.viewport.scrollBarWidth).toString() + 'px';
var height = (rows * term.renderer.dimensions.actualCellHeight).toString() + 'px';
-
terminalContainer.style.width = width;
terminalContainer.style.height = height;
- term.resize(cols, rows);
+ term.fit();
+}
+
+function setPadding() {
+ term.element.style.padding = parseInt(paddingElement.value, 10).toString() + 'px';
+ term.fit();
}
colsElement.addEventListener('change', setTerminalSize);
rowsElement.addEventListener('change', setTerminalSize);
+paddingElement.addEventListener('change', setPadding);
actionElements.findNext.addEventListener('keypress', function (e) {
if (e.key === "Enter") {
@@ -119,6 +123,7 @@ function createTerminal() {
setTimeout(function () {
colsElement.value = term.cols;
rowsElement.value = term.rows;
+ paddingElement.value = 0;
// Set terminal size again to set the specific dimensions on the demo
setTerminalSize();
diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts
index 11856a92..dc1d0682 100644
--- a/src/SelectionManager.ts
+++ b/src/SelectionManager.ts
@@ -309,7 +309,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
* @param event The mouse event.
*/
private _getMouseBufferCoords(event: MouseEvent): [number, number] {
- const coords = this._terminal.mouseHelper.getCoords(event, this._terminal.element, this._charMeasure, this._terminal.options.lineHeight, this._terminal.cols, this._terminal.rows, true);
+ const coords = this._terminal.mouseHelper.getCoords(event, this._terminal.screenElement, this._charMeasure, this._terminal.options.lineHeight, this._terminal.cols, this._terminal.rows, true);
if (!coords) {
return null;
}
@@ -329,7 +329,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
* @param event The mouse event.
*/
private _getMouseEventScrollAmount(event: MouseEvent): number {
- let offset = MouseHelper.getCoordsRelativeToElement(event, this._terminal.element)[1];
+ let offset = MouseHelper.getCoordsRelativeToElement(event, this._terminal.screenElement)[1];
const terminalHeight = this._terminal.rows * Math.ceil(this._charMeasure.height * this._terminal.options.lineHeight);
if (offset >= 0 && offset <= terminalHeight) {
return 0;
diff --git a/src/Terminal.ts b/src/Terminal.ts
index c55dd924..1bc9cfbb 100644
--- a/src/Terminal.ts
+++ b/src/Terminal.ts
@@ -96,6 +96,7 @@ const DEFAULT_OPTIONS: ITerminalOptions = {
export class Terminal extends EventEmitter implements ITerminal, IInputHandlingTerminal {
public textarea: HTMLTextAreaElement;
public element: HTMLElement;
+ public screenElement: HTMLElement;
/**
* The HTMLElement that the terminal is created in, set by Terminal.open.
@@ -609,15 +610,18 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
this.viewportScrollArea.classList.add('xterm-scroll-area');
this.viewportElement.appendChild(this.viewportScrollArea);
- this._mouseZoneManager = new MouseZoneManager(this);
- this.on('scroll', () => this._mouseZoneManager.clearAll());
- this.linkifier.attachToDom(this._mouseZoneManager);
-
+ this.screenElement = document.createElement('div');
+ this.screenElement.classList.add('xterm-screen');
// Create the container that will hold helpers like the textarea for
// capturing DOM Events. Then produce the helpers.
this.helperContainer = document.createElement('div');
this.helperContainer.classList.add('xterm-helpers');
- fragment.appendChild(this.helperContainer);
+ this.screenElement.appendChild(this.helperContainer);
+ fragment.appendChild(this.screenElement);
+
+ this._mouseZoneManager = new MouseZoneManager(this);
+ this.on('scroll', () => this._mouseZoneManager.clearAll());
+ this.linkifier.attachToDom(this._mouseZoneManager);
this.textarea = document.createElement('textarea');
this.textarea.classList.add('xterm-helper-textarea');
@@ -735,7 +739,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
button = getButton(ev);
// get mouse coordinates
- pos = self.mouseHelper.getRawByteCoords(ev, self.element, self.charMeasure, self.options.lineHeight, self.cols, self.rows);
+ pos = self.mouseHelper.getRawByteCoords(ev, self.screenElement, self.charMeasure, self.options.lineHeight, self.cols, self.rows);
if (!pos) return;
sendEvent(button, pos);
@@ -761,7 +765,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
// ^[[M 3<^[[M@4<^[[M@5<^[[M@6<^[[M@7<^[[M#7<
function sendMove(ev: MouseEvent): void {
let button = pressed;
- let pos = self.mouseHelper.getRawByteCoords(ev, self.element, self.charMeasure, self.options.lineHeight, self.cols, self.rows);
+ let pos = self.mouseHelper.getRawByteCoords(ev, self.screenElement, self.charMeasure, self.options.lineHeight, self.cols, self.rows);
if (!pos) return;
// buttons marked as motions
diff --git a/src/Types.ts b/src/Types.ts
index 599299e3..3ea9fce6 100644
--- a/src/Types.ts
+++ b/src/Types.ts
@@ -85,6 +85,7 @@ export interface IInputHandlingTerminal extends IEventEmitter {
}
export interface IViewport {
+ scrollBarWidth: number;
syncScrollArea(): void;
onWheel(ev: WheelEvent): void;
onTouchStart(ev: TouchEvent): void;
@@ -175,6 +176,7 @@ export interface ILinkHoverEvent {
}
export interface ITerminal extends PublicTerminal, IElementAccessor, IBufferAccessor, ILinkifierAccessor {
+ screenElement: HTMLElement;
selectionManager: ISelectionManager;
charMeasure: ICharMeasure;
renderer: IRenderer;
@@ -188,6 +190,7 @@ export interface ITerminal extends PublicTerminal, IElementAccessor, IBufferAcce
buffers: IBufferSet;
isFocused: boolean;
mouseHelper: IMouseHelper;
+ viewport: IViewport;
bracketedPasteMode: boolean;
applicationCursor: boolean;
diff --git a/src/Viewport.ts b/src/Viewport.ts
index 59dc304d..35a2aa68 100644
--- a/src/Viewport.ts
+++ b/src/Viewport.ts
@@ -7,11 +7,14 @@ import { IColorSet } from './renderer/Types';
import { ITerminal, IViewport } from './Types';
import { CharMeasure } from './utils/CharMeasure';
+const FALLBACK_SCROLL_BAR_WIDTH = 15;
+
/**
* 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.
*/
export class Viewport implements IViewport {
+ public scrollBarWidth: number = 0;
private currentRowHeight: number = 0;
private lastRecordedBufferLength: number = 0;
private lastRecordedViewportHeight: number = 0;
@@ -31,6 +34,10 @@ export class Viewport implements IViewport {
private scrollArea: HTMLElement,
private charMeasure: CharMeasure
) {
+ // Measure the width of the scrollbar. If it is 0 we can assume it's an OSX overlay scrollbar.
+ // Unfortunately the overlay scrollbar would be hidden underneath the screen element in that case,
+ // therefore we account for a standard amount to make it visible
+ this.scrollBarWidth = (this.viewportElement.offsetWidth - this.scrollArea.offsetWidth) || FALLBACK_SCROLL_BAR_WIDTH;
this.viewportElement.addEventListener('scroll', this.onScroll.bind(this));
// Perform this async to ensure the CharMeasure is ready.
@@ -48,13 +55,8 @@ export class Viewport implements IViewport {
private refresh(): void {
if (this.charMeasure.height > 0) {
this.currentRowHeight = this.terminal.renderer.dimensions.scaledCellHeight / window.devicePixelRatio;
-
- if (this.lastRecordedViewportHeight !== this.terminal.renderer.dimensions.canvasHeight) {
- this.lastRecordedViewportHeight = this.terminal.renderer.dimensions.canvasHeight;
- this.viewportElement.style.height = this.lastRecordedViewportHeight + 'px';
- }
-
- const newBufferHeight = Math.round(this.currentRowHeight * this.lastRecordedBufferLength);
+ this.lastRecordedViewportHeight = this.viewportElement.offsetHeight;
+ const newBufferHeight = Math.round(this.currentRowHeight * this.lastRecordedBufferLength) + (this.lastRecordedViewportHeight - this.terminal.renderer.dimensions.canvasHeight);
if (this.lastRecordedBufferHeight !== newBufferHeight) {
this.lastRecordedBufferHeight = newBufferHeight;
this.scrollArea.style.height = this.lastRecordedBufferHeight + 'px';
diff --git a/src/addons/fit/fit.ts b/src/addons/fit/fit.ts
index 75e854d9..f6f593dc 100644
--- a/src/addons/fit/fit.ts
+++ b/src/addons/fit/fit.ts
@@ -28,17 +28,22 @@ export function proposeGeometry(term: Terminal): IGeometry {
}
const parentElementStyle = window.getComputedStyle(term.element.parentElement);
const parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height'));
- const parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width')) - 17);
+ const parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width')));
const elementStyle = window.getComputedStyle(term.element);
- const elementPaddingVer = parseInt(elementStyle.getPropertyValue('padding-top')) + parseInt(elementStyle.getPropertyValue('padding-bottom'));
- const elementPaddingHor = parseInt(elementStyle.getPropertyValue('padding-right')) + parseInt(elementStyle.getPropertyValue('padding-left'));
+ const elementPadding = {
+ top: parseInt(elementStyle.getPropertyValue('padding-top')),
+ bottom: parseInt(elementStyle.getPropertyValue('padding-bottom')),
+ right: parseInt(elementStyle.getPropertyValue('padding-right')),
+ left: parseInt(elementStyle.getPropertyValue('padding-left'))
+ };
+ const elementPaddingVer = elementPadding.top + elementPadding.bottom;
+ const elementPaddingHor = elementPadding.right + elementPadding.left;
const availableHeight = parentElementHeight - elementPaddingVer;
- const availableWidth = parentElementWidth - elementPaddingHor;
+ const availableWidth = parentElementWidth - elementPaddingHor - (term).viewport.scrollBarWidth;
const geometry = {
cols: Math.floor(availableWidth / (term).renderer.dimensions.actualCellWidth),
rows: Math.floor(availableHeight / (term).renderer.dimensions.actualCellHeight)
};
-
return geometry;
}
diff --git a/src/input/MouseZoneManager.ts b/src/input/MouseZoneManager.ts
index 79c02b5f..3ab86e7c 100644
--- a/src/input/MouseZoneManager.ts
+++ b/src/input/MouseZoneManager.ts
@@ -169,7 +169,7 @@ export class MouseZoneManager implements IMouseZoneManager {
}
private _findZoneEventAt(e: MouseEvent): IMouseZone {
- const coords = this._terminal.mouseHelper.getCoords(e, this._terminal.element, this._terminal.charMeasure, this._terminal.options.lineHeight, this._terminal.cols, this._terminal.rows);
+ const coords = this._terminal.mouseHelper.getCoords(e, this._terminal.screenElement, this._terminal.charMeasure, this._terminal.options.lineHeight, this._terminal.cols, this._terminal.rows);
if (!coords) {
return null;
}
diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts
index 11c24733..3792a9da 100644
--- a/src/renderer/Renderer.ts
+++ b/src/renderer/Renderer.ts
@@ -38,10 +38,10 @@ export class Renderer extends EventEmitter implements IRenderer {
}
this._renderLayers = [
- new TextRenderLayer(this._terminal.element, 0, this.colorManager.colors, this._terminal.options.allowTransparency),
- new SelectionRenderLayer(this._terminal.element, 1, this.colorManager.colors),
- new LinkRenderLayer(this._terminal.element, 2, this.colorManager.colors, this._terminal),
- new CursorRenderLayer(this._terminal.element, 3, this.colorManager.colors)
+ new TextRenderLayer(this._terminal.screenElement, 0, this.colorManager.colors, this._terminal.options.allowTransparency),
+ new SelectionRenderLayer(this._terminal.screenElement, 1, this.colorManager.colors),
+ new LinkRenderLayer(this._terminal.screenElement, 2, this.colorManager.colors, this._terminal),
+ new CursorRenderLayer(this._terminal.screenElement, 3, this.colorManager.colors)
];
this.dimensions = {
scaledCharWidth: null,
@@ -120,6 +120,10 @@ export class Renderer extends EventEmitter implements IRenderer {
this._terminal.refresh(0, this._terminal.rows - 1);
}
+ // Resize the screen
+ this._terminal.screenElement.style.width = `${this.dimensions.canvasWidth + this._terminal.viewport.scrollBarWidth}px`;
+ this._terminal.screenElement.style.height = `${this.dimensions.canvasHeight}px`;
+
this.emit('resize', {
width: this.dimensions.canvasWidth,
height: this.dimensions.canvasHeight
diff --git a/src/utils/TestUtils.test.ts b/src/utils/TestUtils.test.ts
index d53c8c58..d48ad12c 100644
--- a/src/utils/TestUtils.test.ts
+++ b/src/utils/TestUtils.test.ts
@@ -80,6 +80,7 @@ export class MockTerminal implements ITerminal {
isFocused: boolean;
options: ITerminalOptions = {};
element: HTMLElement;
+ screenElement: HTMLElement;
rowContainer: HTMLElement;
selectionContainer: HTMLElement;
selectionManager: ISelectionManager;
@@ -96,6 +97,7 @@ export class MockTerminal implements ITerminal {
scrollback: number;
buffers: IBufferSet;
buffer: IBuffer;
+ viewport: IViewport;
applicationCursor: boolean;
handler(data: string): void {
throw new Error('Method not implemented.');
@@ -308,6 +310,7 @@ export class MockRenderer implements IRenderer {
}
export class MockViewport implements IViewport {
+ scrollBarWidth: number = 0;
onThemeChanged(colors: IColorSet): void {
throw new Error('Method not implemented.');
}
diff --git a/src/xterm.css b/src/xterm.css
index b3ee50f0..3edaf3ff 100644
--- a/src/xterm.css
+++ b/src/xterm.css
@@ -96,9 +96,18 @@
background-color: #000;
overflow-y: scroll;
cursor: default;
+ position: absolute;
+ right: 0;
+ left: 0;
+ top: 0;
+ bottom: 0;
}
-.xterm canvas {
+.xterm .xterm-screen {
+ position: relative;
+}
+
+.xterm .xterm-screen canvas {
position: absolute;
left: 0;
top: 0;