flutter/examples/terminal/terminal.sky
Viet-Trung Luu 94d55d4003 A crappy "netcat"-type example (in Dart).
It's especially crappy since it doesn't actually do much/any error
handling.

Use:
$ sky/tools/skydb start out/Debug \
  'sky/examples/terminal/index.sky?url=mojo:netcat?host=localhost%26port=80'

(Note: We don't have a resolver yet, so the host either has to be
"localhost" or an IPv4 address in the form N1.N2.N3.N4.)

R=erg@chromium.org

Review URL: https://codereview.chromium.org/1032743002
2015-03-24 13:49:13 -07:00

219 lines
5.9 KiB
Plaintext

<!--
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-->
<import src="/sky/framework/elements/sky-element.sky" />
<sky-element>
<template>
<style>
#container {
height: -webkit-fill-available;
}
#input-box {
position: absolute;
left: 0;
top: 0;
z-index: 10;
width: 100%;
height: 100%;
opacity: 0;
}
#output-box {
position: absolute;
left: 0;
top: 0;
z-index: 0;
width: 100%;
height: 100%;
background-color: black;
color: rgb(255, 191, 0);
font-family: 'Courier', 'monospace';
font-size: small;
}
.line {
height: 1em;
white-space: nowrap;
}
</style>
<div id="container">
<div id="input-box" contenteditable />
<div id="output-box" />
</div>
</div>
</template>
<script>
import 'dart:async';
import 'dart:core';
import 'dart:sky';
import 'package:mojo/services/terminal/public/interfaces/terminal_client.mojom.dart' as terminal;
import 'package:sky/framework/embedder.dart';
import 'terminal_display.dart';
import 'terminal_file_impl.dart';
// Implements the <terminal> element, which implements a "terminal display". Has
// an |url| attribute, whose value should be a Mojo app that provides the
// |terminal.TerminalClient| service.
@Tagname('terminal')
class TerminalDisplayImpl extends SkyElement implements TerminalDisplay {
Element _inputBox;
Element _outputBox;
int _maxLines;
// Queue of unconsumed input (keystrokes), with the head at index 0.
// Keystrokes end up here if there's no reader (i.e., |getChar()|) pending,
// i.e., if |_readerQueue| is empty. Invariant: At most one of |_inputQueue|
// and |_readerQueue| may be nonempty at any given time.
List<int> _inputQueue;
// Queue of things waiting for input, with the head at index 0. If a keystroke
// is received and this is nonempty, the head is given that keystroke (and
// dequeued).
List<Completer<int>> _readerQueue;
TerminalDisplayImpl()
: _inputQueue = new List<int>(),
_readerQueue = new List<Completer<int>>() {
}
void shadowRootReady() {
_inputBox = shadowRoot.getElementById('input-box');
_inputBox.addEventListener('keydown', _handleKeyDown);
_inputBox.addEventListener('keypress', _handleKeyPress);
_inputBox.addEventListener('wheel', _handleWheel);
_outputBox = shadowRoot.getElementById('output-box');
// Hack to allow |_newLine()| to work.
_maxLines = 100;
// Initialize with the first line.
_newLine();
// Actually compute the maximum number of lines.
// TODO(vtl): Recompute on resize.
_maxLines = _outputBox.clientHeight ~/ _outputBox.firstChild.offsetHeight;
var url = getAttribute('url');
if (url != null) {
connect(url);
}
}
void _handleKeyDown(KeyboardEvent event) {
// TODO(vtl): In general, our key handling is a total hack (due in part to
// sky's keyboard support being incomplete) -- e.g., we shouldn't have to
// make our div contenteditable. We have to intercept backspace (^H) here,
// since we won't actually get a keypress event for it. (Possibly, we should
// only handle keydown instead of keypress, but then we'd have to handle
// shift, etc. ourselves.)
if (event.key == 8) {
_enqueueChar(8);
event.preventDefault();
}
}
void _handleKeyPress(KeyboardEvent event) {
if (event.charCode != 0) {
_enqueueChar(event.charCode);
}
event.preventDefault();
}
void _handleWheel(WheelEvent event) {
_outputBox.dispatchEvent(event);
}
void _enqueueChar(int charCode) {
// TODO(vtl): Add "echo" mode; do |putChar(event.charCode);| if echo is on.
if (_readerQueue.isEmpty) {
_inputQueue.add(charCode);
} else {
_readerQueue.removeAt(0).complete(charCode);
}
}
void _backspace() {
var oldText = _outputBox.lastChild.textContent;
if (oldText.length > 0) {
_outputBox.lastChild.textContent = oldText.substring(0,
oldText.length - 1);
}
}
void _newLine() {
var line = document.createElement('div');
line.setAttribute('class', 'line');
_outputBox.appendChild(line);
// Scroll if necessary.
var children = _outputBox.getChildNodes();
if (children.length > _maxLines) {
children = new List.from(children.skip(children.length - _maxLines));
_outputBox.setChildren(children);
}
}
void _clear() {
_outputBox.setChildren([]);
_newLine();
}
void connect(String url) {
var terminalClient = new terminal.TerminalClientProxy.unbound();
embedder.connectToService(url, terminalClient);
terminalClient.ptr.connectToTerminal(new TerminalFileImpl(this).stub);
terminalClient.close();
}
void putString(String s) {
for (var i = 0; i < s.length; i++) {
putChar(s.codeUnitAt(i));
}
}
// |TerminalDisplay| implementation:
@override
void putChar(int byte) {
// Fast-path for printable chars.
if (byte >= 32) {
_outputBox.lastChild.textContent += new String.fromCharCode(byte);
return;
}
switch (byte) {
case 8: // BS (^H).
_backspace();
break;
case 10: // LF ('\n').
_newLine(); // TODO(vtl): LF and CR should be separated.
break;
case 12: // FF (^L).
_clear();
break;
case 13: // CR ('\r').
_newLine(); // TODO(vtl): LF and CR should be separated.
break;
default:
// Should beep or something.
break;
}
}
@override
Future<int> getChar() async {
if (_inputQueue.isNotEmpty) {
return new Future.value(_inputQueue.removeAt(0));
}
var completer = new Completer<int>();
_readerQueue.add(completer);
return completer.future;
}
}
_init(script) => register(script, TerminalDisplayImpl);
</script>
</sky-element>