parent
731aecd556
commit
12c0894079
@ -0,0 +1,95 @@ |
||||
/** |
||||
* Copyright (c) 2017 The xterm.js authors. All rights reserved. |
||||
* @license MIT |
||||
*/ |
||||
|
||||
import { Terminal, ITerminalAddon } from 'xterm'; |
||||
|
||||
|
||||
interface ITerminalDimensions { |
||||
/** |
||||
* The number of rows in the terminal. |
||||
*/ |
||||
rows: number; |
||||
|
||||
/** |
||||
* The number of columns in the terminal. |
||||
*/ |
||||
cols: number; |
||||
} |
||||
|
||||
const MINIMUM_COLS = 2; |
||||
const MINIMUM_ROWS = 1; |
||||
|
||||
export class FitAddon implements ITerminalAddon { |
||||
private _terminal: Terminal | undefined; |
||||
|
||||
constructor() {} |
||||
|
||||
public activate(terminal: Terminal): void { |
||||
console.log('FitAddon activate') |
||||
this._terminal = terminal; |
||||
} |
||||
|
||||
public dispose(): void {} |
||||
|
||||
public fit(): void { |
||||
const dims = this.proposeDimensions(); |
||||
console.log('FitAddon fit', dims) |
||||
if (!dims || !this._terminal || isNaN(dims.cols) || isNaN(dims.rows)) { |
||||
return; |
||||
} |
||||
|
||||
// TODO: Remove reliance on private API
|
||||
const core = (this._terminal as any)._core; |
||||
|
||||
// Force a full render
|
||||
if (this._terminal.rows !== dims.rows || this._terminal.cols !== dims.cols) { |
||||
core._renderService.clear(); |
||||
this._terminal.resize(dims.cols, dims.rows); |
||||
} |
||||
} |
||||
|
||||
public proposeDimensions(): ITerminalDimensions | undefined { |
||||
if (!this._terminal) { |
||||
return undefined; |
||||
} |
||||
|
||||
if (!this._terminal.element || !this._terminal.element.parentElement) { |
||||
return undefined; |
||||
} |
||||
|
||||
// TODO: Remove reliance on private API
|
||||
const core = (this._terminal as any)._core; |
||||
const dims = core._renderService.dimensions; |
||||
|
||||
if (dims.css.cell.width === 0 || dims.css.cell.height === 0) { |
||||
return undefined; |
||||
} |
||||
|
||||
const scrollbarWidth = this._terminal.options.scrollback === 0 ? |
||||
0 : core.viewport.scrollBarWidth; |
||||
|
||||
console.log('parentElementStyle', this._terminal.element.parentElement) |
||||
|
||||
const parentElementStyle = window.getComputedStyle(this._terminal.element.parentElement); |
||||
const parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height')); |
||||
const parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width'))); |
||||
const elementStyle = window.getComputedStyle(this._terminal.element); |
||||
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 - scrollbarWidth; |
||||
const geometry = { |
||||
cols: Math.max(MINIMUM_COLS, Math.floor(availableWidth / dims.css.cell.width)), |
||||
rows: Math.max(MINIMUM_ROWS, Math.floor(availableHeight / dims.css.cell.height)) |
||||
}; |
||||
return geometry; |
||||
} |
||||
} |
@ -0,0 +1,240 @@ |
||||
import * as React from 'react' |
||||
import PropTypes from 'prop-types' |
||||
|
||||
import 'xterm/css/xterm.css' |
||||
|
||||
// We are using these as types.
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import { Terminal, ITerminalOptions, ITerminalAddon } from 'xterm' |
||||
|
||||
interface IProps { |
||||
/** |
||||
* Class name to add to the terminal container. |
||||
*/ |
||||
className?: string |
||||
|
||||
/** |
||||
* Options to initialize the terminal with. |
||||
*/ |
||||
options?: ITerminalOptions |
||||
|
||||
/** |
||||
* An array of XTerm addons to load along with the terminal. |
||||
*/ |
||||
addons?: Array<ITerminalAddon> |
||||
|
||||
/** |
||||
* Adds an event listener for when a binary event fires. This is used to |
||||
* enable non UTF-8 conformant binary messages to be sent to the backend. |
||||
* Currently this is only used for a certain type of mouse reports that |
||||
* happen to be not UTF-8 compatible. |
||||
* The event value is a JS string, pass it to the underlying pty as |
||||
* binary data, e.g. `pty.write(Buffer.from(data, 'binary'))`. |
||||
*/ |
||||
onBinary?(data: string): void |
||||
|
||||
/** |
||||
* Adds an event listener for the cursor moves. |
||||
*/ |
||||
onCursorMove?(): void |
||||
|
||||
/** |
||||
* Adds an event listener for when a data event fires. This happens for |
||||
* example when the user types or pastes into the terminal. The event value |
||||
* is whatever `string` results, in a typical setup, this should be passed |
||||
* on to the backing pty. |
||||
*/ |
||||
onData?(data: string): void |
||||
|
||||
/** |
||||
* Adds an event listener for when a key is pressed. The event value contains the |
||||
* string that will be sent in the data event as well as the DOM event that |
||||
* triggered it. |
||||
*/ |
||||
onKey?(event: { key: string; domEvent: KeyboardEvent }): void |
||||
|
||||
/** |
||||
* Adds an event listener for when a line feed is added. |
||||
*/ |
||||
onLineFeed?(): void |
||||
|
||||
/** |
||||
* Adds an event listener for when a scroll occurs. The event value is the |
||||
* new position of the viewport. |
||||
* @returns an `IDisposable` to stop listening. |
||||
*/ |
||||
onScroll?(newPosition: number): void |
||||
|
||||
/** |
||||
* Adds an event listener for when a selection change occurs. |
||||
*/ |
||||
onSelectionChange?(): void |
||||
|
||||
/** |
||||
* Adds an event listener for when rows are rendered. The event value |
||||
* contains the start row and end rows of the rendered area (ranges from `0` |
||||
* to `Terminal.rows - 1`). |
||||
*/ |
||||
onRender?(event: { start: number; end: number }): void |
||||
|
||||
/** |
||||
* Adds an event listener for when the terminal is resized. The event value |
||||
* contains the new size. |
||||
*/ |
||||
onResize?(event: { cols: number; rows: number }): void |
||||
|
||||
/** |
||||
* Adds an event listener for when an OSC 0 or OSC 2 title change occurs. |
||||
* The event value is the new title. |
||||
*/ |
||||
onTitleChange?(newTitle: string): void |
||||
|
||||
/** |
||||
* Attaches a custom key event handler which is run before keys are |
||||
* processed, giving consumers of xterm.js ultimate control as to what keys |
||||
* should be processed by the terminal and what keys should not. |
||||
* |
||||
* @param event The custom KeyboardEvent handler to attach. |
||||
* This is a function that takes a KeyboardEvent, allowing consumers to stop |
||||
* propagation and/or prevent the default action. The function returns |
||||
* whether the event should be processed by xterm.js. |
||||
*/ |
||||
customKeyEventHandler?(event: KeyboardEvent): boolean |
||||
} |
||||
|
||||
export class Xterm extends React.Component<IProps> { |
||||
/** |
||||
* The ref for the containing element. |
||||
*/ |
||||
terminalRef: React.RefObject<HTMLDivElement> |
||||
|
||||
/** |
||||
* XTerm.js Terminal object. |
||||
*/ |
||||
terminal!: Terminal // This is assigned in the setupTerminal() which is called from the constructor
|
||||
|
||||
static propTypes = { |
||||
className: PropTypes.string, |
||||
options: PropTypes.object, |
||||
addons: PropTypes.array, |
||||
onBinary: PropTypes.func, |
||||
onCursorMove: PropTypes.func, |
||||
onData: PropTypes.func, |
||||
onKey: PropTypes.func, |
||||
onLineFeed: PropTypes.func, |
||||
onScroll: PropTypes.func, |
||||
onSelectionChange: PropTypes.func, |
||||
onRender: PropTypes.func, |
||||
onResize: PropTypes.func, |
||||
onTitleChange: PropTypes.func, |
||||
customKeyEventHandler: PropTypes.func, |
||||
} |
||||
|
||||
constructor(props: IProps) { |
||||
super(props) |
||||
|
||||
console.log('Xterm constructor') |
||||
|
||||
this.terminalRef = React.createRef() |
||||
|
||||
// Bind Methods
|
||||
this.onData = this.onData.bind(this) |
||||
this.onCursorMove = this.onCursorMove.bind(this) |
||||
this.onKey = this.onKey.bind(this) |
||||
this.onBinary = this.onBinary.bind(this) |
||||
this.onLineFeed = this.onLineFeed.bind(this) |
||||
this.onScroll = this.onScroll.bind(this) |
||||
this.onSelectionChange = this.onSelectionChange.bind(this) |
||||
this.onRender = this.onRender.bind(this) |
||||
this.onResize = this.onResize.bind(this) |
||||
this.onTitleChange = this.onTitleChange.bind(this) |
||||
|
||||
this.setupTerminal() |
||||
} |
||||
|
||||
setupTerminal() { |
||||
// Setup the XTerm terminal.
|
||||
this.terminal = new Terminal(this.props.options) |
||||
|
||||
// Load addons if the prop exists.
|
||||
if (this.props.addons) { |
||||
this.props.addons.forEach((addon) => { |
||||
console.log('addon', addon) |
||||
this.terminal.loadAddon(addon) |
||||
}) |
||||
} |
||||
|
||||
// Create Listeners
|
||||
this.terminal.onBinary(this.onBinary) |
||||
this.terminal.onCursorMove(this.onCursorMove) |
||||
this.terminal.onData(this.onData) |
||||
this.terminal.onKey(this.onKey) |
||||
this.terminal.onLineFeed(this.onLineFeed) |
||||
this.terminal.onScroll(this.onScroll) |
||||
this.terminal.onSelectionChange(this.onSelectionChange) |
||||
this.terminal.onRender(this.onRender) |
||||
this.terminal.onResize(this.onResize) |
||||
this.terminal.onTitleChange(this.onTitleChange) |
||||
|
||||
// Add Custom Key Event Handler
|
||||
if (this.props.customKeyEventHandler) { |
||||
this.terminal.attachCustomKeyEventHandler(this.props.customKeyEventHandler) |
||||
} |
||||
} |
||||
|
||||
componentDidMount() { |
||||
if (this.terminalRef.current) { |
||||
// Creates the terminal within the container element.
|
||||
this.terminal.open(this.terminalRef.current) |
||||
} |
||||
} |
||||
|
||||
componentWillUnmount() { |
||||
// When the component unmounts dispose of the terminal and all of its listeners.
|
||||
this.terminal.dispose() |
||||
} |
||||
|
||||
private onBinary(data: string) { |
||||
if (this.props.onBinary) this.props.onBinary(data) |
||||
} |
||||
|
||||
private onCursorMove() { |
||||
if (this.props.onCursorMove) this.props.onCursorMove() |
||||
} |
||||
|
||||
private onData(data: string) { |
||||
if (this.props.onData) this.props.onData(data) |
||||
} |
||||
|
||||
private onKey(event: { key: string; domEvent: KeyboardEvent }) { |
||||
if (this.props.onKey) this.props.onKey(event) |
||||
} |
||||
|
||||
private onLineFeed() { |
||||
if (this.props.onLineFeed) this.props.onLineFeed() |
||||
} |
||||
|
||||
private onScroll(newPosition: number) { |
||||
if (this.props.onScroll) this.props.onScroll(newPosition) |
||||
} |
||||
|
||||
private onSelectionChange() { |
||||
if (this.props.onSelectionChange) this.props.onSelectionChange() |
||||
} |
||||
|
||||
private onRender(event: { start: number; end: number }) { |
||||
if (this.props.onRender) this.props.onRender(event) |
||||
} |
||||
|
||||
private onResize(event: { cols: number; rows: number }) { |
||||
if (this.props.onResize) this.props.onResize(event) |
||||
} |
||||
|
||||
private onTitleChange(newTitle: string) { |
||||
if (this.props.onTitleChange) this.props.onTitleChange(newTitle) |
||||
} |
||||
|
||||
render() { |
||||
return <div className={this.props.className} ref={this.terminalRef} /> |
||||
} |
||||
} |
Loading…
Reference in new issue