Support setting padding on the .xterm element (#1208)

* Support padding on the .xterm element
This commit is contained in:
Thomas Zilz
2018-02-06 12:31:17 +01:00
committed by GitHub
parent 1ec7b91a98
commit dc759a93bc
11 changed files with 72 additions and 33 deletions
+4
View File
@@ -64,6 +64,10 @@
<label for="rows">Rows</label> <label for="rows">Rows</label>
<input type="number" id="rows" /> <input type="number" id="rows" />
</div> </div>
<div style="display: inline-block; margin-right: 16px;">
<label for="padding">Padding</label>
<input type="number" id="padding" />
</div>
</div> </div>
</div> </div>
</div> </div>
+11 -6
View File
@@ -33,23 +33,27 @@ var terminalContainer = document.getElementById('terminal-container'),
bellStyle: document.querySelector('#option-bell-style') bellStyle: document.querySelector('#option-bell-style')
}, },
colsElement = document.getElementById('cols'), colsElement = document.getElementById('cols'),
rowsElement = document.getElementById('rows'); rowsElement = document.getElementById('rows'),
paddingElement = document.getElementById('padding');
function setTerminalSize() { function setTerminalSize() {
var cols = parseInt(colsElement.value, 10); var cols = parseInt(colsElement.value, 10);
var rows = parseInt(rowsElement.value, 10); var rows = parseInt(rowsElement.value, 10);
var viewportElement = document.querySelector('.xterm-viewport'); var width = (cols * term.renderer.dimensions.actualCellWidth + term.viewport.scrollBarWidth).toString() + 'px';
var scrollBarWidth = viewportElement.offsetWidth - viewportElement.clientWidth;
var width = (cols * term.renderer.dimensions.actualCellWidth + 20 /*room for scrollbar*/).toString() + 'px';
var height = (rows * term.renderer.dimensions.actualCellHeight).toString() + 'px'; var height = (rows * term.renderer.dimensions.actualCellHeight).toString() + 'px';
terminalContainer.style.width = width; terminalContainer.style.width = width;
terminalContainer.style.height = height; 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); colsElement.addEventListener('change', setTerminalSize);
rowsElement.addEventListener('change', setTerminalSize); rowsElement.addEventListener('change', setTerminalSize);
paddingElement.addEventListener('change', setPadding);
actionElements.findNext.addEventListener('keypress', function (e) { actionElements.findNext.addEventListener('keypress', function (e) {
if (e.key === "Enter") { if (e.key === "Enter") {
@@ -119,6 +123,7 @@ function createTerminal() {
setTimeout(function () { setTimeout(function () {
colsElement.value = term.cols; colsElement.value = term.cols;
rowsElement.value = term.rows; rowsElement.value = term.rows;
paddingElement.value = 0;
// Set terminal size again to set the specific dimensions on the demo // Set terminal size again to set the specific dimensions on the demo
setTerminalSize(); setTerminalSize();
+2 -2
View File
@@ -309,7 +309,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
* @param event The mouse event. * @param event The mouse event.
*/ */
private _getMouseBufferCoords(event: MouseEvent): [number, number] { 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) { if (!coords) {
return null; return null;
} }
@@ -329,7 +329,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
* @param event The mouse event. * @param event The mouse event.
*/ */
private _getMouseEventScrollAmount(event: MouseEvent): number { 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); const terminalHeight = this._terminal.rows * Math.ceil(this._charMeasure.height * this._terminal.options.lineHeight);
if (offset >= 0 && offset <= terminalHeight) { if (offset >= 0 && offset <= terminalHeight) {
return 0; return 0;
+11 -7
View File
@@ -96,6 +96,7 @@ const DEFAULT_OPTIONS: ITerminalOptions = {
export class Terminal extends EventEmitter implements ITerminal, IInputHandlingTerminal { export class Terminal extends EventEmitter implements ITerminal, IInputHandlingTerminal {
public textarea: HTMLTextAreaElement; public textarea: HTMLTextAreaElement;
public element: HTMLElement; public element: HTMLElement;
public screenElement: HTMLElement;
/** /**
* The HTMLElement that the terminal is created in, set by Terminal.open. * 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.viewportScrollArea.classList.add('xterm-scroll-area');
this.viewportElement.appendChild(this.viewportScrollArea); this.viewportElement.appendChild(this.viewportScrollArea);
this._mouseZoneManager = new MouseZoneManager(this); this.screenElement = document.createElement('div');
this.on('scroll', () => this._mouseZoneManager.clearAll()); this.screenElement.classList.add('xterm-screen');
this.linkifier.attachToDom(this._mouseZoneManager);
// Create the container that will hold helpers like the textarea for // Create the container that will hold helpers like the textarea for
// capturing DOM Events. Then produce the helpers. // capturing DOM Events. Then produce the helpers.
this.helperContainer = document.createElement('div'); this.helperContainer = document.createElement('div');
this.helperContainer.classList.add('xterm-helpers'); 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 = document.createElement('textarea');
this.textarea.classList.add('xterm-helper-textarea'); this.textarea.classList.add('xterm-helper-textarea');
@@ -735,7 +739,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
button = getButton(ev); button = getButton(ev);
// get mouse coordinates // 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; if (!pos) return;
sendEvent(button, pos); 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< // ^[[M 3<^[[M@4<^[[M@5<^[[M@6<^[[M@7<^[[M#7<
function sendMove(ev: MouseEvent): void { function sendMove(ev: MouseEvent): void {
let button = pressed; 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; if (!pos) return;
// buttons marked as motions // buttons marked as motions
+3
View File
@@ -85,6 +85,7 @@ export interface IInputHandlingTerminal extends IEventEmitter {
} }
export interface IViewport { export interface IViewport {
scrollBarWidth: number;
syncScrollArea(): void; syncScrollArea(): void;
onWheel(ev: WheelEvent): void; onWheel(ev: WheelEvent): void;
onTouchStart(ev: TouchEvent): void; onTouchStart(ev: TouchEvent): void;
@@ -175,6 +176,7 @@ export interface ILinkHoverEvent {
} }
export interface ITerminal extends PublicTerminal, IElementAccessor, IBufferAccessor, ILinkifierAccessor { export interface ITerminal extends PublicTerminal, IElementAccessor, IBufferAccessor, ILinkifierAccessor {
screenElement: HTMLElement;
selectionManager: ISelectionManager; selectionManager: ISelectionManager;
charMeasure: ICharMeasure; charMeasure: ICharMeasure;
renderer: IRenderer; renderer: IRenderer;
@@ -188,6 +190,7 @@ export interface ITerminal extends PublicTerminal, IElementAccessor, IBufferAcce
buffers: IBufferSet; buffers: IBufferSet;
isFocused: boolean; isFocused: boolean;
mouseHelper: IMouseHelper; mouseHelper: IMouseHelper;
viewport: IViewport;
bracketedPasteMode: boolean; bracketedPasteMode: boolean;
applicationCursor: boolean; applicationCursor: boolean;
+9 -7
View File
@@ -7,11 +7,14 @@ import { IColorSet } from './renderer/Types';
import { ITerminal, IViewport } from './Types'; import { ITerminal, IViewport } from './Types';
import { CharMeasure } from './utils/CharMeasure'; 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. * 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. * Logic for the virtual scroll bar is included in this object.
*/ */
export class Viewport implements IViewport { export class Viewport implements IViewport {
public scrollBarWidth: number = 0;
private currentRowHeight: number = 0; private currentRowHeight: number = 0;
private lastRecordedBufferLength: number = 0; private lastRecordedBufferLength: number = 0;
private lastRecordedViewportHeight: number = 0; private lastRecordedViewportHeight: number = 0;
@@ -31,6 +34,10 @@ export class Viewport implements IViewport {
private scrollArea: HTMLElement, private scrollArea: HTMLElement,
private charMeasure: CharMeasure 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)); this.viewportElement.addEventListener('scroll', this.onScroll.bind(this));
// Perform this async to ensure the CharMeasure is ready. // Perform this async to ensure the CharMeasure is ready.
@@ -48,13 +55,8 @@ export class Viewport implements IViewport {
private refresh(): void { private refresh(): void {
if (this.charMeasure.height > 0) { if (this.charMeasure.height > 0) {
this.currentRowHeight = this.terminal.renderer.dimensions.scaledCellHeight / window.devicePixelRatio; this.currentRowHeight = this.terminal.renderer.dimensions.scaledCellHeight / window.devicePixelRatio;
this.lastRecordedViewportHeight = this.viewportElement.offsetHeight;
if (this.lastRecordedViewportHeight !== this.terminal.renderer.dimensions.canvasHeight) { const newBufferHeight = Math.round(this.currentRowHeight * this.lastRecordedBufferLength) + (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);
if (this.lastRecordedBufferHeight !== newBufferHeight) { if (this.lastRecordedBufferHeight !== newBufferHeight) {
this.lastRecordedBufferHeight = newBufferHeight; this.lastRecordedBufferHeight = newBufferHeight;
this.scrollArea.style.height = this.lastRecordedBufferHeight + 'px'; this.scrollArea.style.height = this.lastRecordedBufferHeight + 'px';
+10 -5
View File
@@ -28,17 +28,22 @@ export function proposeGeometry(term: Terminal): IGeometry {
} }
const parentElementStyle = window.getComputedStyle(term.element.parentElement); const parentElementStyle = window.getComputedStyle(term.element.parentElement);
const parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height')); 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 elementStyle = window.getComputedStyle(term.element);
const elementPaddingVer = parseInt(elementStyle.getPropertyValue('padding-top')) + parseInt(elementStyle.getPropertyValue('padding-bottom')); const elementPadding = {
const elementPaddingHor = parseInt(elementStyle.getPropertyValue('padding-right')) + parseInt(elementStyle.getPropertyValue('padding-left')); 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 availableHeight = parentElementHeight - elementPaddingVer;
const availableWidth = parentElementWidth - elementPaddingHor; const availableWidth = parentElementWidth - elementPaddingHor - (<any>term).viewport.scrollBarWidth;
const geometry = { const geometry = {
cols: Math.floor(availableWidth / (<any>term).renderer.dimensions.actualCellWidth), cols: Math.floor(availableWidth / (<any>term).renderer.dimensions.actualCellWidth),
rows: Math.floor(availableHeight / (<any>term).renderer.dimensions.actualCellHeight) rows: Math.floor(availableHeight / (<any>term).renderer.dimensions.actualCellHeight)
}; };
return geometry; return geometry;
} }
+1 -1
View File
@@ -169,7 +169,7 @@ export class MouseZoneManager implements IMouseZoneManager {
} }
private _findZoneEventAt(e: MouseEvent): IMouseZone { 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) { if (!coords) {
return null; return null;
} }
+8 -4
View File
@@ -38,10 +38,10 @@ export class Renderer extends EventEmitter implements IRenderer {
} }
this._renderLayers = [ this._renderLayers = [
new TextRenderLayer(this._terminal.element, 0, this.colorManager.colors, this._terminal.options.allowTransparency), new TextRenderLayer(this._terminal.screenElement, 0, this.colorManager.colors, this._terminal.options.allowTransparency),
new SelectionRenderLayer(this._terminal.element, 1, this.colorManager.colors), new SelectionRenderLayer(this._terminal.screenElement, 1, this.colorManager.colors),
new LinkRenderLayer(this._terminal.element, 2, this.colorManager.colors, this._terminal), new LinkRenderLayer(this._terminal.screenElement, 2, this.colorManager.colors, this._terminal),
new CursorRenderLayer(this._terminal.element, 3, this.colorManager.colors) new CursorRenderLayer(this._terminal.screenElement, 3, this.colorManager.colors)
]; ];
this.dimensions = { this.dimensions = {
scaledCharWidth: null, scaledCharWidth: null,
@@ -120,6 +120,10 @@ export class Renderer extends EventEmitter implements IRenderer {
this._terminal.refresh(0, this._terminal.rows - 1); 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', { this.emit('resize', {
width: this.dimensions.canvasWidth, width: this.dimensions.canvasWidth,
height: this.dimensions.canvasHeight height: this.dimensions.canvasHeight
+3
View File
@@ -80,6 +80,7 @@ export class MockTerminal implements ITerminal {
isFocused: boolean; isFocused: boolean;
options: ITerminalOptions = {}; options: ITerminalOptions = {};
element: HTMLElement; element: HTMLElement;
screenElement: HTMLElement;
rowContainer: HTMLElement; rowContainer: HTMLElement;
selectionContainer: HTMLElement; selectionContainer: HTMLElement;
selectionManager: ISelectionManager; selectionManager: ISelectionManager;
@@ -96,6 +97,7 @@ export class MockTerminal implements ITerminal {
scrollback: number; scrollback: number;
buffers: IBufferSet; buffers: IBufferSet;
buffer: IBuffer; buffer: IBuffer;
viewport: IViewport;
applicationCursor: boolean; applicationCursor: boolean;
handler(data: string): void { handler(data: string): void {
throw new Error('Method not implemented.'); throw new Error('Method not implemented.');
@@ -308,6 +310,7 @@ export class MockRenderer implements IRenderer {
} }
export class MockViewport implements IViewport { export class MockViewport implements IViewport {
scrollBarWidth: number = 0;
onThemeChanged(colors: IColorSet): void { onThemeChanged(colors: IColorSet): void {
throw new Error('Method not implemented.'); throw new Error('Method not implemented.');
} }
+10 -1
View File
@@ -96,9 +96,18 @@
background-color: #000; background-color: #000;
overflow-y: scroll; overflow-y: scroll;
cursor: default; 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; position: absolute;
left: 0; left: 0;
top: 0; top: 0;