| OLD | NEW |
| (Empty) |
| 1 <!-- | |
| 2 // Copyright 2015 The Chromium Authors. All rights reserved. | |
| 3 // Use of this source code is governed by a BSD-style license that can be | |
| 4 // found in the LICENSE file. | |
| 5 --> | |
| 6 <import src="/sky/framework/elements/sky-element.sky" /> | |
| 7 <sky-element> | |
| 8 <template> | |
| 9 <style> | |
| 10 #container { | |
| 11 height: -webkit-fill-available; | |
| 12 } | |
| 13 #input-box { | |
| 14 position: absolute; | |
| 15 left: 0; | |
| 16 top: 0; | |
| 17 z-index: 10; | |
| 18 width: 100%; | |
| 19 height: 100%; | |
| 20 opacity: 0; | |
| 21 } | |
| 22 #output-box { | |
| 23 position: absolute; | |
| 24 left: 0; | |
| 25 top: 0; | |
| 26 z-index: 0; | |
| 27 width: 100%; | |
| 28 height: 100%; | |
| 29 background-color: black; | |
| 30 color: rgb(255, 191, 0); | |
| 31 font-family: 'Courier', 'monospace'; | |
| 32 font-size: small; | |
| 33 } | |
| 34 .line { | |
| 35 height: 1em; | |
| 36 white-space: nowrap; | |
| 37 } | |
| 38 </style> | |
| 39 <div id="container"> | |
| 40 <div id="input-box" contenteditable /> | |
| 41 <div id="output-box" /> | |
| 42 </div> | |
| 43 </div> | |
| 44 </template> | |
| 45 <script> | |
| 46 import 'dart:async'; | |
| 47 import 'dart:core'; | |
| 48 import 'dart:sky'; | |
| 49 import 'package:mojo/services/terminal/public/interfaces/terminal_client.mojom.d
art' as terminal; | |
| 50 import 'package:sky/framework/embedder.dart'; | |
| 51 import 'terminal_display.dart'; | |
| 52 import 'terminal_file_impl.dart'; | |
| 53 | |
| 54 // Implements the <terminal> element, which implements a "terminal display". Has | |
| 55 // an |url| attribute, whose value should be a Mojo app that provides the | |
| 56 // |terminal.TerminalClient| service. | |
| 57 @Tagname('terminal') | |
| 58 class TerminalDisplayImpl extends SkyElement implements TerminalDisplay { | |
| 59 Element _inputBox; | |
| 60 Element _outputBox; | |
| 61 int _maxLines; | |
| 62 | |
| 63 // Queue of unconsumed input (keystrokes), with the head at index 0. | |
| 64 // Keystrokes end up here if there's no reader (i.e., |getChar()|) pending, | |
| 65 // i.e., if |_readerQueue| is empty. Invariant: At most one of |_inputQueue| | |
| 66 // and |_readerQueue| may be nonempty at any given time. | |
| 67 List<int> _inputQueue; | |
| 68 | |
| 69 // Queue of things waiting for input, with the head at index 0. If a keystroke | |
| 70 // is received and this is nonempty, the head is given that keystroke (and | |
| 71 // dequeued). | |
| 72 List<Completer<int>> _readerQueue; | |
| 73 | |
| 74 TerminalDisplayImpl() | |
| 75 : _inputQueue = new List<int>(), | |
| 76 _readerQueue = new List<Completer<int>>() { | |
| 77 } | |
| 78 | |
| 79 void shadowRootReady() { | |
| 80 _inputBox = shadowRoot.getElementById('input-box'); | |
| 81 _inputBox.addEventListener('keydown', _handleKeyDown); | |
| 82 _inputBox.addEventListener('keypress', _handleKeyPress); | |
| 83 _inputBox.addEventListener('wheel', _handleWheel); | |
| 84 _outputBox = shadowRoot.getElementById('output-box'); | |
| 85 | |
| 86 // Hack to allow |_newLine()| to work. | |
| 87 _maxLines = 100; | |
| 88 | |
| 89 // Initialize with the first line. | |
| 90 _newLine(); | |
| 91 | |
| 92 // Actually compute the maximum number of lines. | |
| 93 // TODO(vtl): Recompute on resize. | |
| 94 _maxLines = _outputBox.clientHeight ~/ _outputBox.firstChild.offsetHeight; | |
| 95 | |
| 96 var url = getAttribute('url'); | |
| 97 if (url != null) { | |
| 98 connect(url); | |
| 99 } | |
| 100 } | |
| 101 | |
| 102 void _handleKeyDown(KeyboardEvent event) { | |
| 103 // TODO(vtl): In general, our key handling is a total hack (due in part to | |
| 104 // sky's keyboard support being incomplete) -- e.g., we shouldn't have to | |
| 105 // make our div contenteditable. We have to intercept backspace (^H) here, | |
| 106 // since we won't actually get a keypress event for it. (Possibly, we should | |
| 107 // only handle keydown instead of keypress, but then we'd have to handle | |
| 108 // shift, etc. ourselves.) | |
| 109 if (event.key == 8) { | |
| 110 _enqueueChar(8); | |
| 111 event.preventDefault(); | |
| 112 } | |
| 113 } | |
| 114 | |
| 115 void _handleKeyPress(KeyboardEvent event) { | |
| 116 if (event.charCode != 0) { | |
| 117 _enqueueChar(event.charCode); | |
| 118 } | |
| 119 event.preventDefault(); | |
| 120 } | |
| 121 | |
| 122 void _handleWheel(WheelEvent event) { | |
| 123 _outputBox.dispatchEvent(event); | |
| 124 } | |
| 125 | |
| 126 void _enqueueChar(int charCode) { | |
| 127 // TODO(vtl): Add "echo" mode; do |putChar(event.charCode);| if echo is on. | |
| 128 | |
| 129 if (_readerQueue.isEmpty) { | |
| 130 _inputQueue.add(charCode); | |
| 131 } else { | |
| 132 _readerQueue.removeAt(0).complete(charCode); | |
| 133 } | |
| 134 } | |
| 135 | |
| 136 void _backspace() { | |
| 137 var oldText = _outputBox.lastChild.textContent; | |
| 138 if (oldText.length > 0) { | |
| 139 _outputBox.lastChild.textContent = oldText.substring(0, | |
| 140 oldText.length - 1); | |
| 141 } | |
| 142 } | |
| 143 | |
| 144 void _newLine() { | |
| 145 var line = document.createElement('div'); | |
| 146 line.setAttribute('class', 'line'); | |
| 147 _outputBox.appendChild(line); | |
| 148 | |
| 149 // Scroll if necessary. | |
| 150 var children = _outputBox.getChildNodes(); | |
| 151 if (children.length > _maxLines) { | |
| 152 children = new List.from(children.skip(children.length - _maxLines)); | |
| 153 _outputBox.setChildren(children); | |
| 154 } | |
| 155 } | |
| 156 | |
| 157 void _clear() { | |
| 158 _outputBox.setChildren([]); | |
| 159 _newLine(); | |
| 160 } | |
| 161 | |
| 162 void connect(String url) { | |
| 163 var terminalClient = new terminal.TerminalClientProxy.unbound(); | |
| 164 embedder.connectToService(url, terminalClient); | |
| 165 terminalClient.ptr.connectToTerminal(new TerminalFileImpl(this).stub); | |
| 166 terminalClient.close(); | |
| 167 } | |
| 168 | |
| 169 void putString(String s) { | |
| 170 for (var i = 0; i < s.length; i++) { | |
| 171 putChar(s.codeUnitAt(i)); | |
| 172 } | |
| 173 } | |
| 174 | |
| 175 // |TerminalDisplay| implementation: | |
| 176 | |
| 177 @override | |
| 178 void putChar(int byte) { | |
| 179 // Fast-path for printable chars. | |
| 180 if (byte >= 32) { | |
| 181 _outputBox.lastChild.textContent += new String.fromCharCode(byte); | |
| 182 return; | |
| 183 } | |
| 184 | |
| 185 switch (byte) { | |
| 186 case 8: // BS (^H). | |
| 187 _backspace(); | |
| 188 break; | |
| 189 case 10: // LF ('\n'). | |
| 190 _newLine(); // TODO(vtl): LF and CR should be separated. | |
| 191 break; | |
| 192 case 12: // FF (^L). | |
| 193 _clear(); | |
| 194 break; | |
| 195 case 13: // CR ('\r'). | |
| 196 _newLine(); // TODO(vtl): LF and CR should be separated. | |
| 197 break; | |
| 198 default: | |
| 199 // Should beep or something. | |
| 200 break; | |
| 201 } | |
| 202 } | |
| 203 | |
| 204 @override | |
| 205 Future<int> getChar() async { | |
| 206 if (_inputQueue.isNotEmpty) { | |
| 207 return new Future.value(_inputQueue.removeAt(0)); | |
| 208 } | |
| 209 | |
| 210 var completer = new Completer<int>(); | |
| 211 _readerQueue.add(completer); | |
| 212 return completer.future; | |
| 213 } | |
| 214 } | |
| 215 | |
| 216 _init(script) => register(script, TerminalDisplayImpl); | |
| 217 </script> | |
| 218 </sky-element> | |
| OLD | NEW |