Skip to content

Commit b639bd0

Browse files
committed
Rewrite terminal based on xterm.js; redirecting stdin, using dash
1 parent 4c2b9cf commit b639bd0

File tree

6 files changed

+91
-138
lines changed

6 files changed

+91
-138
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,6 @@
1-
<link rel="import" href="../../bower_components/polymer-ts/polymer-ts.html">
1+
<link rel="stylesheet" href="../../xterm/xterm.css" />
22

3-
<dom-module id="browsix-terminal">
4-
<template>
5-
<style>
6-
7-
#output, #input_container, #input {
8-
background: #171a1b;
9-
font-size: 24px;
10-
font-family: 'Hack', 'Roboto', 'Helvetica Neue', Helvetica, Arial, sans-serif;
11-
color: #eeeeec;
12-
-webkit-box-sizing: border-box;
13-
-moz-box-sizing: border-box;
14-
box-sizing: border-box;
15-
width: 100%;
16-
word-break: break-all;
17-
}
18-
19-
#output, #input_container {
20-
padding-left: 24px;
21-
padding-right: 24px;
22-
}
23-
24-
#output {
25-
padding-top: 24px;
26-
}
27-
28-
#output:empty {
29-
display: none;
30-
}
31-
32-
#input_container {
33-
padding-bottom: 24px;
34-
}
35-
36-
#output:empty + #input_container {
37-
padding-top: 24px;
38-
}
39-
40-
#input {
41-
display: inline;
42-
width: 90%;
43-
background: transparent;
44-
border: 0;
45-
outline: 0;
46-
color: #eeeeec;
47-
font-size: 24px;
48-
font-family: 'Hack', 'Roboto', 'Helvetica Neue', Helvetica, Arial, sans-serif;
49-
}
50-
51-
</style>
52-
53-
<div id="output"></div>
54-
55-
<div id="input_container">
56-
$
57-
<input id="input" autofocus/>
58-
</div>
59-
60-
</template>
61-
</dom-module>
3+
<div id="browsix-terminal"></div>
624

5+
<script type="text/javascript" src="../../xterm/xterm.js"></script>
636
<script type="text/javascript" src="browsix-terminal.js"></script>
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
/// <reference path="../../../bower_components/polymer-ts/polymer-ts.d.ts"/>
1+
/// <reference path="../../../node_modules/xterm/typings/xterm.d.ts"/>
2+
3+
//import { Terminal } from 'xterm';
24

35
interface ExitCallback {
46
(pid: number, code: number): void;
@@ -14,98 +16,95 @@ interface Kernel {
1416
kill(pid: number): void;
1517
}
1618

17-
namespace Terminal {
19+
namespace BrowsixTerminal {
1820
'use strict';
1921

2022
const ERROR = 'FLAGRANT SYSTEM ERROR';
2123

22-
@component('browsix-terminal')
23-
class Terminal extends polymer.Base {
24-
@property({type: Object})
25-
kernel: any;
26-
27-
@property({type: String})
28-
ps1: string = '$ ';
24+
class BrowsixTerminal {
25+
kernel: Kernel;
26+
stdin: any;
27+
terminal: Terminal;
28+
line: string = "";
29+
lineidx: number = 0;
30+
31+
constructor(element: HTMLElement) {
32+
this.terminal = new Terminal({
33+
// According to xterm.js docs, unnecessary with pty
34+
"convertEol": true
35+
});
36+
this.terminal.open(element);
37+
this.terminal.on('key', (key: string, ev: KeyboardEvent) => this.keyCallback(key, ev));
2938

30-
constructor() {
31-
super();
3239
(<any>window).Boot(
3340
'XmlHttpRequest',
3441
['index.json', 'fs', true],
3542
(err: any, k: Kernel) => {
3643
if (err) {
3744
console.log(err);
38-
this.$.output.innerHTML = ERROR;
45+
this.terminal.clear();
46+
this.terminal.writeln(ERROR);
3947
throw new Error(err);
4048
}
4149
this.kernel = k;
42-
},
43-
{readOnly: false});
44-
}
4550

46-
attached(): void {
47-
this.$.input.addEventListener('keypress', this.onInput.bind(this));
48-
(<any>document).body.addEventListener('click', this.focus.bind(this));
49-
}
50-
51-
onInput(ev: any): void {
52-
// If key pressed is not Return/Enter, skip
53-
if (ev.keyCode !== 13) return;
54-
55-
let cmd = this.$.input.value;
56-
this.$.output.innerHTML += this.ps1 + cmd + '<br>';
57-
if (cmd === '') {
58-
this.scrollBottom();
59-
return;
60-
}
61-
this.setEditable(false);
62-
let bg = cmd[cmd.length - 1] === '&';
63-
if (bg) {
64-
cmd = cmd.slice(0, -1).trim();
65-
setTimeout(() => { this.setEditable(true); }, 0);
66-
}
51+
let completed = (pid: number, code: number) => {
52+
this.stdin = null;
53+
this.terminal.writeln("'sh' exited with status " + code);
54+
};
6755

68-
let completed = (pid: number, code: number) => {
69-
this.setEditable(true);
70-
this.$.input.value = '';
71-
this.focus();
72-
this.scrollBottom();
73-
};
56+
let onInput = (pid: number, out: string ) => {
57+
this.terminal.write(out);
58+
};
7459

75-
let onInput = (pid: number, out: string) => {
76-
// Replace all LF with HTML breaks
77-
out = out.split('\n').join('<br>');
78-
this.$.output.innerHTML += out;
79-
this.scrollBottom();
80-
};
60+
let onHaveStdin = (stdin: any) => {
61+
this.stdin = stdin;
62+
}
8163

82-
this.kernel.system(cmd, completed, onInput, onInput);
64+
this.kernel.system("sh", completed, onInput, onInput, onHaveStdin);
65+
},
66+
{readOnly: false});
8367
}
8468

85-
@observe('kernel')
86-
kernelChanged(_: Kernel, oldKernel: Kernel): void {
87-
// we expect this to be called once, after
88-
// we've booted the kernel.
89-
if (oldKernel) {
90-
console.log('unexpected kernel change');
91-
return;
69+
keyCallback(key: string, ev: KeyboardEvent): void {
70+
// Newline
71+
if (ev.keyCode == 13) {
72+
this.terminal.writeln("");
73+
this.line += '\n'
74+
if (this.stdin !== null) {
75+
this.stdin.write(new Buffer(this.line), -1, (error: any) => {});
76+
}
77+
this.line = '';
78+
this.lineidx = 0;
79+
// Backspace
80+
} else if (ev.keyCode == 8) {
81+
const previous = this.line.slice(0, this.lineidx - 1);
82+
const rest = this.line.slice(this.lineidx);
83+
this.terminal.write('\b' + rest + ' ' + '\b'.repeat(rest.length + 1));
84+
this.line = previous + rest;
85+
this.lineidx--;
86+
// Up and down arrows
87+
} else if (ev.keyCode == 38 || ev.keyCode == 40) {
88+
// Left arrow
89+
} else if (ev.keyCode == 37) {
90+
this.terminal.write(key);
91+
this.lineidx--;
92+
if (this.lineidx == -1) {
93+
this.lineidx = 0;
94+
}
95+
// Right arrow
96+
} else if (ev.keyCode == 39) {
97+
if (this.lineidx < this.line.length) {
98+
this.lineidx++;
99+
this.terminal.write(key);
100+
}
101+
} else {
102+
this.terminal.write(key);
103+
this.line += key;
104+
this.lineidx++;
92105
}
93106
}
94-
95-
focus(): void {
96-
this.$.input.focus();
97-
}
98-
99-
setEditable(editable: boolean): void {
100-
// Hide input if not editable
101-
this.$.input_container.style.visibility = (editable) ? '' : 'hidden';
102-
}
103-
104-
scrollBottom(): void {
105-
(<any>window).scrollTo(0, document.documentElement.scrollHeight
106-
|| document.body.scrollHeight);
107-
}
108107
}
109108

110-
Terminal.register();
109+
var terminal = new BrowsixTerminal(document.getElementById('browsix-terminal'));
111110
}

gulpfile.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -436,7 +436,10 @@ gulp.task('app:copy', ['index-fs'], function () {
436436
var fs = gulp.src(['fs/**/*'])
437437
.pipe(gulp.dest('dist/fs'));
438438

439-
return merge(app, bower, elements, vulcanized, swBootstrap, swToolbox, fs)
439+
var xterm = gulp.src(['node_modules/xterm/dist/**/*'])
440+
.pipe(gulp.dest('dist/xterm'));
441+
442+
return merge(app, bower, elements, vulcanized, swBootstrap, swToolbox, fs, xterm)
440443
.pipe($.size({title: 'copy'}));
441444
});
442445

@@ -506,6 +509,7 @@ gulp.task('serve', ['app:build', 'app:styles', 'app:elements', 'app:images'], fu
506509
'/bower_components': 'bower_components',
507510
'/fs': 'fs',
508511
'/benchfs': 'benchfs',
512+
'/xterm': 'node_modules/xterm/dist',
509513
},
510514
middleware: [],
511515
}

package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
"mocha": "^3.4.2",
5656
"through2": "^2.0.3",
5757
"tslint": "^5.5.0",
58-
"typescript": "^2.4.1",
58+
"typescript": "^3.4.3",
5959
"vinyl-buffer": "^1.0.0",
6060
"vinyl-source-stream": "^1.1.0"
6161
},
@@ -65,7 +65,7 @@
6565
"@types/filesystem": "0.0.28",
6666
"@types/mocha": "^2.2.41",
6767
"node-binary-marshal": "^0.4.2",
68-
"term.js": "github:bpowers/term.js"
68+
"xterm": "^3.12.2"
6969
},
7070
"engines": {
7171
"node": ">=4.3.0"

src/kernel/kernel.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { now } from './ipc';
99
import { Pipe, PipeFile, isPipe } from './pipe';
1010
import { SocketFile, isSocket } from './socket';
1111
import { DirFile, RegularFile, NullFile, resolve } from './file';
12-
import { ExitCallback, OutputCallback, SyscallContext, SyscallResult,
12+
import { ExitCallback, OutputCallback, StdinCallback, SyscallContext, SyscallResult,
1313
Syscall, ConnectCallback, IKernel, ITask, IFile, Environment } from './types';
1414

1515
import { HTTPParser } from './http_parser';
@@ -1570,7 +1570,7 @@ export class Kernel implements IKernel {
15701570
this.portWaiters[port] = cb;
15711571
}
15721572

1573-
system(cmd: string, onExit: ExitCallback, onStdout: OutputCallback, onStderr: OutputCallback): void {
1573+
system(cmd: string, onExit: ExitCallback, onStdout: OutputCallback, onStderr: OutputCallback, onHaveStdin: StdinCallback): void {
15741574
let splitParts: string[] = cmd.split(' ');
15751575
let parts: string[];
15761576
// only check for an = in the first part of a command,
@@ -1610,9 +1610,12 @@ export class Kernel implements IKernel {
16101610
let t = this.tasks[pid];
16111611
t.onExit = onExit;
16121612

1613+
let stdin = <PipeFile>t.files[0];
16131614
let stdout = <PipeFile>t.files[1];
16141615
let stderr = <PipeFile>t.files[2];
16151616

1617+
onHaveStdin(stdin);
1618+
16161619
stdout.addEventListener('write', onStdout);
16171620
stderr.addEventListener('write', onStderr);
16181621
});
@@ -1860,7 +1863,7 @@ export class Kernel implements IKernel {
18601863
files[i].ref();
18611864
}
18621865
} else {
1863-
files[0] = new NullFile();
1866+
files[0] = new PipeFile();
18641867
files[1] = new PipeFile();
18651868
files[2] = new PipeFile();
18661869
}

src/kernel/types.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ export interface RWCallback {
1717
(err: number, len?: number): void;
1818
}
1919

20+
export interface StdinCallback {
21+
(stdin: IFile): void;
22+
}
23+
2024
export interface SyscallResult {
2125
id: number;
2226
name: string;
@@ -33,7 +37,7 @@ export interface IKernel {
3337
nCPUs: number;
3438
debug: boolean;
3539

36-
system(cmd: string, onExit: ExitCallback, onStdout: OutputCallback, onStderr: OutputCallback): void;
40+
system(cmd: string, onExit: ExitCallback, onStdout: OutputCallback, onStderr: OutputCallback, onHaveStdin: StdinCallback): void;
3741
exit(task: ITask, code: number): void;
3842
wait(pid: number): void;
3943
doSyscall(syscall: Syscall): void;

0 commit comments

Comments
 (0)