diff --git a/app/elements/browsix-terminal/browsix-terminal.html b/app/elements/browsix-terminal/browsix-terminal.html index 5b0fcb5..c75d20b 100644 --- a/app/elements/browsix-terminal/browsix-terminal.html +++ b/app/elements/browsix-terminal/browsix-terminal.html @@ -1,63 +1,6 @@ - + - - - +
+ diff --git a/app/elements/browsix-terminal/browsix-terminal.ts b/app/elements/browsix-terminal/browsix-terminal.ts index 3189c9f..f18d6b0 100644 --- a/app/elements/browsix-terminal/browsix-terminal.ts +++ b/app/elements/browsix-terminal/browsix-terminal.ts @@ -1,4 +1,6 @@ -/// +/// + +//import { Terminal } from 'xterm'; interface ExitCallback { (pid: number, code: number): void; @@ -14,98 +16,95 @@ interface Kernel { kill(pid: number): void; } -namespace Terminal { +namespace BrowsixTerminal { 'use strict'; const ERROR = 'FLAGRANT SYSTEM ERROR'; - @component('browsix-terminal') - class Terminal extends polymer.Base { - @property({type: Object}) - kernel: any; - - @property({type: String}) - ps1: string = '$ '; + class BrowsixTerminal { + kernel: Kernel; + stdin: any; + terminal: Terminal; + line: string = ""; + lineidx: number = 0; + + constructor(element: HTMLElement) { + this.terminal = new Terminal({ + // According to xterm.js docs, unnecessary with pty + "convertEol": true + }); + this.terminal.open(element); + this.terminal.on('key', (key: string, ev: KeyboardEvent) => this.keyCallback(key, ev)); - constructor() { - super(); (window).Boot( 'XmlHttpRequest', ['index.json', 'fs', true], (err: any, k: Kernel) => { if (err) { console.log(err); - this.$.output.innerHTML = ERROR; + this.terminal.clear(); + this.terminal.writeln(ERROR); throw new Error(err); } this.kernel = k; - }, - {readOnly: false}); - } - attached(): void { - this.$.input.addEventListener('keypress', this.onInput.bind(this)); - (document).body.addEventListener('click', this.focus.bind(this)); - } - - onInput(ev: any): void { - // If key pressed is not Return/Enter, skip - if (ev.keyCode !== 13) return; - - let cmd = this.$.input.value; - this.$.output.innerHTML += this.ps1 + cmd + '
'; - if (cmd === '') { - this.scrollBottom(); - return; - } - this.setEditable(false); - let bg = cmd[cmd.length - 1] === '&'; - if (bg) { - cmd = cmd.slice(0, -1).trim(); - setTimeout(() => { this.setEditable(true); }, 0); - } + let completed = (pid: number, code: number) => { + this.stdin = null; + this.terminal.writeln("'sh' exited with status " + code); + }; - let completed = (pid: number, code: number) => { - this.setEditable(true); - this.$.input.value = ''; - this.focus(); - this.scrollBottom(); - }; + let onInput = (pid: number, out: string ) => { + this.terminal.write(out); + }; - let onInput = (pid: number, out: string) => { - // Replace all LF with HTML breaks - out = out.split('\n').join('
'); - this.$.output.innerHTML += out; - this.scrollBottom(); - }; + let onHaveStdin = (stdin: any) => { + this.stdin = stdin; + } - this.kernel.system(cmd, completed, onInput, onInput); + this.kernel.system("sh", completed, onInput, onInput, onHaveStdin); + }, + {readOnly: false}); } - @observe('kernel') - kernelChanged(_: Kernel, oldKernel: Kernel): void { - // we expect this to be called once, after - // we've booted the kernel. - if (oldKernel) { - console.log('unexpected kernel change'); - return; + keyCallback(key: string, ev: KeyboardEvent): void { + // Newline + if (ev.keyCode == 13) { + this.terminal.writeln(""); + this.line += '\n' + if (this.stdin !== null) { + this.stdin.write(new Buffer(this.line), -1, (error: any) => {}); + } + this.line = ''; + this.lineidx = 0; + // Backspace + } else if (ev.keyCode == 8) { + const previous = this.line.slice(0, this.lineidx - 1); + const rest = this.line.slice(this.lineidx); + this.terminal.write('\b' + rest + ' ' + '\b'.repeat(rest.length + 1)); + this.line = previous + rest; + this.lineidx--; + // Up and down arrows + } else if (ev.keyCode == 38 || ev.keyCode == 40) { + // Left arrow + } else if (ev.keyCode == 37) { + this.terminal.write(key); + this.lineidx--; + if (this.lineidx == -1) { + this.lineidx = 0; + } + // Right arrow + } else if (ev.keyCode == 39) { + if (this.lineidx < this.line.length) { + this.lineidx++; + this.terminal.write(key); + } + } else { + this.terminal.write(key); + this.line += key; + this.lineidx++; } } - - focus(): void { - this.$.input.focus(); - } - - setEditable(editable: boolean): void { - // Hide input if not editable - this.$.input_container.style.visibility = (editable) ? '' : 'hidden'; - } - - scrollBottom(): void { - (window).scrollTo(0, document.documentElement.scrollHeight - || document.body.scrollHeight); - } } - Terminal.register(); + var terminal = new BrowsixTerminal(document.getElementById('browsix-terminal')); } diff --git a/gulpfile.js b/gulpfile.js index 3cc9a0d..5569a90 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -436,7 +436,10 @@ gulp.task('app:copy', ['index-fs'], function () { var fs = gulp.src(['fs/**/*']) .pipe(gulp.dest('dist/fs')); - return merge(app, bower, elements, vulcanized, swBootstrap, swToolbox, fs) + var xterm = gulp.src(['node_modules/xterm/dist/**/*']) + .pipe(gulp.dest('dist/xterm')); + + return merge(app, bower, elements, vulcanized, swBootstrap, swToolbox, fs, xterm) .pipe($.size({title: 'copy'})); }); @@ -506,6 +509,7 @@ gulp.task('serve', ['app:build', 'app:styles', 'app:elements', 'app:images'], fu '/bower_components': 'bower_components', '/fs': 'fs', '/benchfs': 'benchfs', + '/xterm': 'node_modules/xterm/dist', }, middleware: [], } diff --git a/package.json b/package.json index ddde688..daad3e3 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,7 @@ "mocha": "^3.4.2", "through2": "^2.0.3", "tslint": "^5.5.0", - "typescript": "^2.4.1", + "typescript": "^3.4.3", "vinyl-buffer": "^1.0.0", "vinyl-source-stream": "^1.1.0" }, @@ -65,7 +65,7 @@ "@types/filesystem": "0.0.28", "@types/mocha": "^2.2.41", "node-binary-marshal": "^0.4.2", - "term.js": "github:bpowers/term.js" + "xterm": "^3.12.2" }, "engines": { "node": ">=4.3.0" diff --git a/src/kernel/kernel.ts b/src/kernel/kernel.ts index 32ebfe8..c948f3d 100644 --- a/src/kernel/kernel.ts +++ b/src/kernel/kernel.ts @@ -9,7 +9,7 @@ import { now } from './ipc'; import { Pipe, PipeFile, isPipe } from './pipe'; import { SocketFile, isSocket } from './socket'; import { DirFile, RegularFile, NullFile, resolve } from './file'; -import { ExitCallback, OutputCallback, SyscallContext, SyscallResult, +import { ExitCallback, OutputCallback, StdinCallback, SyscallContext, SyscallResult, Syscall, ConnectCallback, IKernel, ITask, IFile, Environment } from './types'; import { HTTPParser } from './http_parser'; @@ -1570,7 +1570,7 @@ export class Kernel implements IKernel { this.portWaiters[port] = cb; } - system(cmd: string, onExit: ExitCallback, onStdout: OutputCallback, onStderr: OutputCallback): void { + system(cmd: string, onExit: ExitCallback, onStdout: OutputCallback, onStderr: OutputCallback, onHaveStdin: StdinCallback): void { let splitParts: string[] = cmd.split(' '); let parts: string[]; // only check for an = in the first part of a command, @@ -1610,9 +1610,12 @@ export class Kernel implements IKernel { let t = this.tasks[pid]; t.onExit = onExit; + let stdin = t.files[0]; let stdout = t.files[1]; let stderr = t.files[2]; + onHaveStdin(stdin); + stdout.addEventListener('write', onStdout); stderr.addEventListener('write', onStderr); }); @@ -1860,7 +1863,7 @@ export class Kernel implements IKernel { files[i].ref(); } } else { - files[0] = new NullFile(); + files[0] = new PipeFile(); files[1] = new PipeFile(); files[2] = new PipeFile(); } diff --git a/src/kernel/types.ts b/src/kernel/types.ts index b801689..2a09ee0 100644 --- a/src/kernel/types.ts +++ b/src/kernel/types.ts @@ -17,6 +17,10 @@ export interface RWCallback { (err: number, len?: number): void; } +export interface StdinCallback { + (stdin: IFile): void; +} + export interface SyscallResult { id: number; name: string; @@ -33,7 +37,7 @@ export interface IKernel { nCPUs: number; debug: boolean; - system(cmd: string, onExit: ExitCallback, onStdout: OutputCallback, onStderr: OutputCallback): void; + system(cmd: string, onExit: ExitCallback, onStdout: OutputCallback, onStderr: OutputCallback, onHaveStdin: StdinCallback): void; exit(task: ITask, code: number): void; wait(pid: number): void; doSyscall(syscall: Syscall): void;