inspec/www/demo/app/xterm-terminal/xterm-terminal.component.ts

231 lines
6 KiB
TypeScript
Raw Normal View History

2016-09-19 08:32:23 +00:00
import {
Component,
OnInit,
OnChanges,
Input,
Output,
EventEmitter,
SimpleChange
} from '@angular/core';
2016-08-29 21:12:31 +00:00
declare var Terminal: any;
@Component({
selector: 'xterm-terminal',
templateUrl: 'app/xterm-terminal/xterm-terminal.component.html',
styleUrls: ['app/xterm-terminal/xterm-terminal.component.css']
})
2016-09-19 08:32:23 +00:00
export class XtermTerminalComponent implements OnInit, OnChanges{
// This Xterm implementation emits all commands, once the users presses 'Enter'
@Output() stdin: EventEmitter<string> = new EventEmitter<string>();
// waits for events that should be printed on the terminal
@Input() stdout: EventEmitter<string>;
// sets the shell prompt
@Input() prompt: string;
// handle up/down command history functionality
history: any = [];
historyIndex: number;
last: number;
2016-09-19 08:32:23 +00:00
// caches stdin, until the user presses Enter
buffer: string = '';
// xterm variables
2016-08-29 21:12:31 +00:00
terminalContainer: any;
term: any;
optionElements: any;
cols: string;
rows: string;
2016-08-29 21:12:31 +00:00
2016-09-19 08:32:23 +00:00
// we cannot use \33[2K\r since strict mode recognize it as ocal number
clearLine = "\u001b[2K\r"
ngOnInit() {
// set up Xterm terminal (https://github.com/sourcelair/xterm.js)
2016-09-02 19:13:07 +00:00
this.terminalContainer = document.getElementById('terminal-container');
2016-09-19 08:32:23 +00:00
this.initializeTerminal();
}
ngOnChanges(changes: {[propKey: string]: SimpleChange}) {
let log: string[] = [];
for (let propName in changes) {
let changedProp = changes[propName];
// reprint prompt
if (propName == 'prompt' && this.term != null) {
this.term.write(this.clearLine+this.prompt)
}
}
}
2016-09-19 08:32:23 +00:00
onResize(event) {
this.updateTerminalSize()
}
updateTerminalSize() {
// recalculate cols and rows
2016-09-26 12:32:27 +00:00
let charwidth = 30;
2016-09-19 08:32:23 +00:00
// read size from parent wrapping element
let cols = 80
let rows = Math.floor(this.terminalContainer.parentElement.clientHeight / charwidth)
this.term.resize(cols, rows);
}
// create Xterm terminal (https://github.com/sourcelair/xterm.js)
2016-09-19 08:32:23 +00:00
initializeTerminal() {
2016-08-29 21:12:31 +00:00
while (this.terminalContainer.children.length) {
this.terminalContainer.removeChild(this.terminalContainer.children[0]);
}
this.term = new Terminal({
2016-09-19 08:32:23 +00:00
cursorBlink: true,
2016-08-29 21:12:31 +00:00
});
this.term.open(this.terminalContainer);
this.term.fit();
2016-09-19 08:32:23 +00:00
// start registering the event listener, only need to be done the first time
2016-08-29 21:12:31 +00:00
if (this.term._initialized) {
return;
}
this.term._initialized = true;
2016-09-19 08:32:23 +00:00
this.writePrompt();
let self = this
// raw data
this.term.on('data', function(data){
self.writeBuffer(data)
})
// overwrite keydown handler to be able to customize cursor handling
this.term.attachCustomKeydownHandler(function(ev){
return self.keydown(ev);
})
// handle stream events from the app
this.stdout.subscribe(function(data) {
self.onStdout(data)
}, function(err) {
console.error(err)
})
2016-09-26 12:32:27 +00:00
// determine rows for terminal
this.updateTerminalSize()
// TODO: hack, find a better way
// this allows outside tiggers via
// window.dispatchEvent(new Event('resizeTerminal'));
window.addEventListener('resizeTerminal', function (e) {
self.updateTerminalSize();
}, false);
2016-08-29 21:12:31 +00:00
}
2016-09-19 08:32:23 +00:00
// this writes the raw buffer and includes invisible charaters
writeBuffer(data) {
this.term.write(data);
this.buffer += data;
}
2016-09-19 08:32:23 +00:00
// reset the command buffer
resetBuffer() {
this.buffer = ''
}
2016-09-19 08:32:23 +00:00
// print out stdout data
onStdout(data) {
let res = data
this.term.write(res);
}
2016-09-19 08:32:23 +00:00
// print out error
onStderr(response) {
this.term.writeln(response);
}
2016-09-19 08:32:23 +00:00
// writes the command prompt, a prompt is not part of the input buffer
writePrompt() {
this.term.write(this.prompt);
2016-08-29 21:12:31 +00:00
}
2016-09-19 08:32:23 +00:00
// stores a command in commmand history
storeInHistory(cmd) {
// remove cariage returns from history commands
2016-10-06 02:44:39 +00:00
cmd = cmd.replace(/\r/,'');
2016-09-19 08:32:23 +00:00
this.history.push(cmd);
this.historyIndex = this.history.length
}
// prints the current selected command at history index
historyCmdtoStdout() {
this.buffer = this.history[this.historyIndex]
this.reprintCommand(this.buffer )
}
// reprint a command in the current line
reprintCommand(cmd){
2016-10-06 02:44:39 +00:00
// strip out any extra \r characters
cmd = cmd.replace(/\r/g,'');
this.term.write(this.clearLine + this.prompt + cmd);
2016-09-19 08:32:23 +00:00
}
// resets the terminal
clear() {
2016-10-06 02:44:39 +00:00
this.term.reset();
this.writePrompt();
2016-09-19 08:32:23 +00:00
}
// terminal event handler
// be aware that we need to decouple the event loops, therefore we need to
// use setTimeout to reliable print content on the terminal
keydown(ev) {
// console.log(ev);
let self = this
if (ev.keyCode === 13) {
let cmd = self.buffer
// special handling for clear and cls
if (/^\s*clear|cls\s*/.exec(cmd) != null) {
// reset terminal
setTimeout(function(){
self.clear()
}, 0);
} else {
2016-09-19 08:32:23 +00:00
// decouple from event loop, write a new line
setTimeout(function(){
self.term.writeln('')
self.stdin.emit(cmd);
self.storeInHistory(cmd)
}, 0);
2016-08-29 21:12:31 +00:00
}
2016-09-19 08:32:23 +00:00
// reset index to last item +1
self.resetBuffer()
}
2016-08-29 21:12:31 +00:00
// on backspace, pop characters from buffer
else if (ev.keyCode == 8) {
2016-09-19 08:32:23 +00:00
self.buffer = self.buffer.substr(0, self.buffer.length-1);
setTimeout(function(){
self.reprintCommand(self.buffer)
}, 0);
}
2016-09-19 08:32:23 +00:00
// press the up key, find previous item in cmd history
2016-09-02 19:13:07 +00:00
else if (ev.keyCode === 40) {
2016-09-19 08:32:23 +00:00
setTimeout(function(){
if (self.historyIndex < self.history.length - 1) {
self.historyIndex += 1
self.historyCmdtoStdout()
}
}, 0);
2016-09-02 19:13:07 +00:00
}
2016-09-19 08:32:23 +00:00
// press the down key, find next item in cmd history
else if (ev.keyCode === 38) {
setTimeout(function(){
if (self.historyIndex >= 1) {
self.historyIndex -= 1
self.historyCmdtoStdout()
}
}, 0);
2016-08-29 21:12:31 +00:00
}
2016-09-19 08:32:23 +00:00
// this forces xterm not to handle the key events with its own event handler
return false;
2016-08-29 21:12:31 +00:00
}
}