Move terminal example from //sky/examples to //examples
We're trying to clean up the //sky/examples directory to focus on mobile use cases for the fn framework. Terminal is both focused on desktop and uses the older custom-element framework. R=viettrungluu@chromium.org Review URL: https://codereview.chromium.org/1038813005
This commit is contained in:
parent
854f083f1b
commit
64d53aa0fa
@ -1,9 +0,0 @@
|
||||
Terminal
|
||||
========
|
||||
|
||||
This is a prototype "terminal" application that can connect to any Mojo
|
||||
application (providing the |terminal.TerminalClient| interface) and provide
|
||||
interactive terminal facilities via an implementation of |mojo.files.File|.
|
||||
I.e., once connected, the application can write to/read from the terminal by
|
||||
performing the corresponding operations on a "file" (thus replicating
|
||||
decades-old technology, poorly).
|
@ -1,26 +0,0 @@
|
||||
#!mojo mojo:sky_viewer
|
||||
<!--
|
||||
// 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="terminal.sky" />
|
||||
<!--
|
||||
<terminal id="terminal" url="mojo:echo_terminal" />
|
||||
-->
|
||||
<terminal id="terminal" />
|
||||
<script>
|
||||
import 'dart:core';
|
||||
import 'dart:sky';
|
||||
|
||||
main () {
|
||||
var terminal = document.getElementById('terminal');
|
||||
var params = Uri.parse(document.URL).queryParameters;
|
||||
if (params.containsKey('url')) {
|
||||
terminal.connect(params['url']);
|
||||
} else {
|
||||
terminal.putString('HALP: Add a "?url=<URL>" query.\n'
|
||||
'E.g., "?url=mojo:echo_terminal".\n');
|
||||
}
|
||||
}
|
||||
</script>
|
@ -1,218 +0,0 @@
|
||||
<!--
|
||||
// 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>
|
@ -1,16 +0,0 @@
|
||||
// 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 'dart:async';
|
||||
|
||||
// Interface for a terminal display, able to accept bytes (from the computer)
|
||||
// and typically displaying them (or possibly handling them as escape codes,
|
||||
// etc.) and able to get bytes from the "user".
|
||||
abstract class TerminalDisplay {
|
||||
void putChar(int byte);
|
||||
Future<int> getChar();
|
||||
|
||||
// TODO(vtl): Should probably also have facilities for putting many bytes at a
|
||||
// time or getting as many bytes as available.
|
||||
}
|
@ -1,124 +0,0 @@
|
||||
// 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 'dart:async';
|
||||
import 'dart:core';
|
||||
import 'package:mojo/public/dart/core.dart';
|
||||
import 'package:mojo/services/files/public/interfaces/file.mojom.dart' as files;
|
||||
import 'package:mojo/services/files/public/interfaces/types.mojom.dart' as files;
|
||||
|
||||
import 'terminal_display.dart';
|
||||
|
||||
// This implements a |mojo.files.File| that acts like a (pseudo)terminal. Bytes
|
||||
// written to the |File| will be read by this implementation and passed on to
|
||||
// the (Dart) |TerminalDisplay| (using |putChar()|). A read from the |File| will
|
||||
// be completed if/when |TerminalDisplay| makes a byte available (via
|
||||
// |getChar()|).
|
||||
// TODO(vtl): This implementation is very incomplete.
|
||||
class TerminalFileImpl implements files.File {
|
||||
final files.FileStub stub;
|
||||
final TerminalDisplay _display;
|
||||
|
||||
TerminalFileImpl(this._display) : stub = new files.FileStub.unbound() {
|
||||
stub.impl = this;
|
||||
}
|
||||
|
||||
// |files.File| implementation:
|
||||
|
||||
@override
|
||||
Future close(Function responseFactory) async {
|
||||
// TODO(vtl): We should probably do more than just say OK.
|
||||
return responseFactory(files.Error_OK);
|
||||
}
|
||||
|
||||
@override
|
||||
Future read(int numBytesToRead, int offset, int whence,
|
||||
Function responseFactory) async {
|
||||
if (numBytesToRead < 0) {
|
||||
return responseFactory(files.Error_INVALID_ARGUMENT, null);
|
||||
}
|
||||
|
||||
// TODO(vtl): Error if |offset|/|whence| not appropriate.
|
||||
|
||||
if (numBytesToRead == 0) {
|
||||
return responseFactory(files.Error_OK, []);
|
||||
}
|
||||
|
||||
return responseFactory(files.Error_OK, [await _display.getChar()]);
|
||||
}
|
||||
|
||||
@override
|
||||
Future write(List<int> bytesToWrite, int offset, int whence,
|
||||
Function responseFactory) async {
|
||||
// TODO(vtl): Error if |offset|/|whence| not appropriate.
|
||||
|
||||
for (var c in bytesToWrite) {
|
||||
_display.putChar(c);
|
||||
}
|
||||
return responseFactory(files.Error_OK, bytesToWrite.length);
|
||||
}
|
||||
|
||||
@override
|
||||
Future readToStream(MojoDataPipeProducer source, int offset, int whence,
|
||||
int numBytesToRead, Function responseFactory) async {
|
||||
// TODO(vtl)
|
||||
return responseFactory(files.Error_UNIMPLEMENTED);
|
||||
}
|
||||
|
||||
@override
|
||||
Future writeFromStream(MojoDataPipeConsumer sink, int offset, int whence,
|
||||
Function responseFactory) async {
|
||||
// TODO(vtl)
|
||||
return responseFactory(files.Error_UNIMPLEMENTED);
|
||||
}
|
||||
|
||||
@override
|
||||
Future tell(Function responseFactory) async {
|
||||
// TODO(vtl)
|
||||
return responseFactory(files.Error_UNIMPLEMENTED, 0);
|
||||
}
|
||||
|
||||
@override
|
||||
Future seek(int offset, int whence, Function responseFactory) async {
|
||||
// TODO(vtl)
|
||||
return responseFactory(files.Error_UNIMPLEMENTED, 0);
|
||||
}
|
||||
|
||||
@override
|
||||
Future stat(Function responseFactory) async {
|
||||
// TODO(vtl)
|
||||
return responseFactory(files.Error_UNIMPLEMENTED, null);
|
||||
}
|
||||
|
||||
@override
|
||||
Future truncate(int size, Function responseFactory) async {
|
||||
// TODO(vtl)
|
||||
return responseFactory(files.Error_UNIMPLEMENTED);
|
||||
}
|
||||
|
||||
@override
|
||||
Future touch(files.TimespecOrNow atime, files.TimespecOrNow mtime,
|
||||
Function responseFactory) async {
|
||||
// TODO(vtl)
|
||||
return responseFactory(files.Error_UNIMPLEMENTED);
|
||||
}
|
||||
|
||||
@override
|
||||
Future dup(Object file, Function responseFactory) async {
|
||||
// TODO(vtl)
|
||||
return responseFactory(files.Error_UNIMPLEMENTED);
|
||||
}
|
||||
|
||||
@override
|
||||
Future reopen(Object file, int openFlags, Function responseFactory) async {
|
||||
// TODO(vtl)
|
||||
return responseFactory(files.Error_UNIMPLEMENTED);
|
||||
}
|
||||
|
||||
@override
|
||||
Future asBuffer(Function responseFactory) async {
|
||||
// TODO(vtl)
|
||||
return responseFactory(files.Error_UNIMPLEMENTED, null);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user