Skip to content

Commit

Permalink
Rewrite terminal based on xterm.js; redirecting stdin, using dash
Browse files Browse the repository at this point in the history
  • Loading branch information
ids1024 committed Apr 22, 2019
1 parent 4c2b9cf commit b639bd0
Show file tree
Hide file tree
Showing 6 changed files with 91 additions and 138 deletions.
63 changes: 3 additions & 60 deletions app/elements/browsix-terminal/browsix-terminal.html
Original file line number Diff line number Diff line change
@@ -1,63 +1,6 @@
<link rel="import" href="../../bower_components/polymer-ts/polymer-ts.html">
<link rel="stylesheet" href="../../xterm/xterm.css" />

<dom-module id="browsix-terminal">
<template>
<style>

#output, #input_container, #input {
background: #171a1b;
font-size: 24px;
font-family: 'Hack', 'Roboto', 'Helvetica Neue', Helvetica, Arial, sans-serif;
color: #eeeeec;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
width: 100%;
word-break: break-all;
}

#output, #input_container {
padding-left: 24px;
padding-right: 24px;
}

#output {
padding-top: 24px;
}

#output:empty {
display: none;
}

#input_container {
padding-bottom: 24px;
}

#output:empty + #input_container {
padding-top: 24px;
}

#input {
display: inline;
width: 90%;
background: transparent;
border: 0;
outline: 0;
color: #eeeeec;
font-size: 24px;
font-family: 'Hack', 'Roboto', 'Helvetica Neue', Helvetica, Arial, sans-serif;
}

</style>

<div id="output"></div>

<div id="input_container">
$
<input id="input" autofocus/>
</div>

</template>
</dom-module>
<div id="browsix-terminal"></div>

<script type="text/javascript" src="../../xterm/xterm.js"></script>
<script type="text/javascript" src="browsix-terminal.js"></script>
141 changes: 70 additions & 71 deletions app/elements/browsix-terminal/browsix-terminal.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
/// <reference path="../../../bower_components/polymer-ts/polymer-ts.d.ts"/>
/// <reference path="../../../node_modules/xterm/typings/xterm.d.ts"/>

//import { Terminal } from 'xterm';

interface ExitCallback {
(pid: number, code: number): void;
Expand All @@ -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();
(<any>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));
(<any>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 + '<br>';
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('<br>');
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 {
(<any>window).scrollTo(0, document.documentElement.scrollHeight
|| document.body.scrollHeight);
}
}

Terminal.register();
var terminal = new BrowsixTerminal(document.getElementById('browsix-terminal'));
}
6 changes: 5 additions & 1 deletion gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'}));
});

Expand Down Expand Up @@ -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: [],
}
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand All @@ -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"
Expand Down
9 changes: 6 additions & 3 deletions src/kernel/kernel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -1610,9 +1610,12 @@ export class Kernel implements IKernel {
let t = this.tasks[pid];
t.onExit = onExit;

let stdin = <PipeFile>t.files[0];
let stdout = <PipeFile>t.files[1];
let stderr = <PipeFile>t.files[2];

onHaveStdin(stdin);

stdout.addEventListener('write', onStdout);
stderr.addEventListener('write', onStderr);
});
Expand Down Expand Up @@ -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();
}
Expand Down
6 changes: 5 additions & 1 deletion src/kernel/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down

0 comments on commit b639bd0

Please sign in to comment.