Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(759)

Side by Side Diff: frog/tip/tip.dart

Issue 8889031: move tip to utils so it can be picked up in the SDK (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 9 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « frog/tip/tip.css ('k') | frog/tip/tip.html » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file.
4
5 /**
6 * This giant file has pieces for compiling dart in the browser, injecting
7 * scripts into pages and a bunch of classes to build a simple dart editor.
8 *
9 * TODO(jimhug): Separate these pieces cleanly.
10 */
11
12 #import('dart:dom');
13 #import('../lang.dart');
14 #import('../file_system_dom.dart');
15
16
17 void main() {
18 final systemPath = getRootPath(window) + '/..';
19 final userPath = getRootPath(window.parent);
20
21 if (window !== window.parent) {
22 // I'm in an iframe - frogify my surrounding code unless dart is native.
23 if (!document.implementation.hasFeature('dart', '')) {
24 // Suppress warnings to avoid diff-based tests from failing on them.
25 initialize(systemPath, userPath, ['--suppress_warnings']);
26 window.addEventListener('DOMContentLoaded',
27 (e) => frogify(window.parent),
28 false);
29 }
30 } else {
31 // I'm at the top level - run the tip shell.
32 shell = new Shell();
33 document.body.appendChild(shell._node);
34 initialize(systemPath, userPath);
35 world.messageHandler = shell.handleMessage;
36 }
37 }
38
39
40 /** The filename to use for Dart code compiled directly from the browser. */
41 final String DART_FILENAME = 'input.dart';
42
43 /** Helper to get my base url. */
44 String getRootPath(Window window) {
45 String url = window.location.href;
46 final tail = url.lastIndexOf('/', url.length);
47 final dir = url.substring(0, tail);
48 return dir;
49 }
50
51
52 /**
53 * Invoke this script in the given window's context. Use
54 * this frame's window by default.
55 */
56 void inject(String code, Window win, [String name = 'generated.js']) {
57 final doc = win.document;
58 var script = doc.createElement('script');
59 // TODO(vsm): Enable debugging of injected code. This sourceURL
60 // trick only appears to work for eval'ed code, not script injected
61 // code.
62 // Append sourceURL to enable debugging.
63 script.innerHTML = code + '\n//@ sourceURL=$name';
64 script.type = 'application/javascript';
65 doc.body.appendChild(script);
66 }
67
68
69 /**
70 * Compile all dart scripts in a window and inject/invoke the corresponding JS.
71 */
72 void frogify(Window win) {
73 var doc = win.document;
74 int n = doc.scripts.length;
75 // TODO(vsm): Implement foreach iteration on native DOM types. This
76 // should be for (var script in doc.scripts) { ... }.
77 for (int i = 0; i < n; ++i) {
78 final script = doc.scripts[i];
79 if (script.type == 'application/dart') {
80 final src = script.src;
81 var input;
82 var name;
83 if (src == '') {
84 input = script.innerHTML;
85 name = null;
86 } else {
87 input = world.files.readAll(src);
88 name = '$src.js';
89 }
90 world.files.writeString(DART_FILENAME, input);
91 world.reset();
92 var success = world.compile();
93
94 if (success) {
95 inject(world.getGeneratedCode(), win, name);
96 } else {
97 inject('window.alert("compilation failed");', win, name);
98 }
99 }
100 }
101 }
102
103 void initialize(String systemPath, String userPath,
104 [List<String> flags = const []]) {
105 DomFileSystem fs = new DomFileSystem(userPath);
106 // TODO(jimhug): Workaround lib path hack in frog_options.dart
107 final options = [null, null, '--libdir=$systemPath/lib'];
108 options.addAll(flags);
109 options.add(DART_FILENAME);
110 parseOptions(systemPath, options, fs);
111 initializeWorld(fs);
112 }
113
114 final int LINE_HEIGHT = 22; // TODO(jimhug): This constant sucks.
115 final int CHAR_WIDTH = 8; // TODO(jimhug): See above.
116
117 final String CODE = '''
118 #import("dart:dom");
119
120 // This is an interesting field;
121 final int y = 22;
122 String name;
123
124 /** This is my main method. */
125 void main() {
126 var element = document.createElement('div');
127 element.innerHTML = "Hello dom from Dart!";
128 document.body.appendChild(element);
129
130 HTMLCanvasElement canvas = document.createElement('canvas');
131 document.body.appendChild(canvas);
132
133 var context = canvas.getContext('2d');
134 context.setFillColor('purple');
135 context.fillRect(10, 10, 30, 30);
136 }
137
138 /**
139 * The usual method of computing factorial in the slowest possible way.
140 */
141 num fact(n) {
142 if (n == 0) return 1;
143 return n * fact(n - 1);
144 }
145
146 final x = 22;
147
148 ''';
149
150 final String HCODE = '''#import("dart:html");
151
152 void main() {
153 var element = document.createElement('div');
154 element.innerHTML = "Hello html from Dart!";
155 document.body.nodes.add(element);
156
157 CanvasElement canvas = document.createElement('canvas');
158 canvas.width = 100;
159 canvas.height = 100;
160 document.body.nodes.add(canvas);
161
162 var context = canvas.getContext('2d');
163 context.setFillColor('blue');
164 context.fillRect(10, 10, 30, 30);
165 }
166 ''';
167
168
169 class Message {
170 var _node;
171 String prefix, message;
172 SourceSpan span;
173
174 Message(this.prefix, this.message, this.span) {
175 var col = prefix.indexOf(':');
176 if (col != -1) {
177 prefix = prefix.substring(0, col);
178 }
179
180 _node = document.createElement('div');
181 _node.className = 'message ${prefix}';
182 _node.innerText = '$message at ${span.locationText}';
183
184 _node.addEventListener('click', click, false);
185 }
186
187 void click(MouseEvent event) {
188 shell.showSpan(span);
189 }
190 }
191
192 class MessageWindow {
193 var _node;
194 List<Message> messages;
195
196 MessageWindow() {
197 messages = [];
198 _node = document.createElement('div');
199 _node.className = 'errors';
200 }
201
202 addMessage(Message message) {
203 messages.add(message);
204 _node.appendChild(message._node);
205 }
206
207 clear() {
208 messages.length = 0;
209 _node.innerHTML = '';
210 }
211 }
212
213
214 // TODO(jimhug): Type is needed.
215 Shell shell;
216
217 class Shell {
218 var _textInputArea;
219 KeyBindings _bindings;
220
221 Cursor cursor;
222 Editor _editor;
223
224 var _node;
225 var _output;
226
227 var _repl;
228 var _errors;
229
230 Shell() {
231 _node = document.createElement('div');
232 _node.className = 'shell';
233 _editor = new Editor(this);
234
235 _editor._node.style.setProperty('height', '93%');
236 _node.appendChild(_editor._node);
237
238 _textInputArea = document.createElement('textarea');
239 _textInputArea.className = 'hiddenTextArea';
240
241 _node.appendChild(_textInputArea);
242
243 var outDiv = document.createElement('div');
244 outDiv.className = 'output';
245 outDiv.style.setProperty('height', '83%');
246
247
248 _output = document.createElement('iframe');
249 outDiv.appendChild(_output);
250 _node.appendChild(outDiv);
251
252 _repl = document.createElement('div');
253 _repl.className = 'repl';
254 _repl.style.setProperty('height', '5%');
255 _repl.innerHTML = '<div>REPL Under Construction...</div>';
256 _node.appendChild(_repl);
257
258 _errors = new MessageWindow();
259
260 //_errors.innerHTML = '<h3>Errors/Warnings Under Construction...</h3>';
261 _errors._node.style.setProperty('height', '15%');
262 _node.appendChild(_errors._node);
263
264 // TODO(jimhug): Ick!
265 window.setTimeout( () {
266 _editor.focus();
267 _output.contentDocument.head.innerHTML = '''
268 <style>body {
269 font-family: arial , sans-serif;
270 }
271 h3 {
272 text-align: center;
273 }
274 </style>''';
275 _output.contentDocument.body.innerHTML = '<h3>Output will go here</h3>';
276 }, .5);
277
278 // TODO(jimhug): These are hugely incomplete and Mac-centric.
279 var bindings = {
280 'Left': () {
281 cursor.clearSelection();
282 cursor._pos = cursor._pos.moveColumn(-1);
283 },
284 'Shift-Left': () {
285 if (cursor._toPos == null) {
286 cursor._toPos = cursor._pos;
287 }
288 cursor._pos = cursor._pos.moveColumn(-1);
289 },
290
291 'Right': () {
292 cursor.clearSelection();
293 cursor._pos = cursor._pos.moveColumn(+1);
294 },
295 'Shift-Right': () {
296 if (cursor._toPos == null) {
297 cursor._toPos = cursor._pos;
298 }
299 cursor._pos = cursor._pos.moveColumn(+1);
300 },
301 'Up': () {
302 cursor.clearSelection();
303 // TODO(jimhug): up and down lose column info on shorter lines.
304 cursor._pos = cursor._pos.moveLine(-1);
305 },
306 'Shift-Up': () {
307 if (cursor._toPos == null) {
308 cursor._toPos = cursor._pos;
309 }
310 cursor._pos = cursor._pos.moveLine(-1);
311 },
312 'Down': () {
313 cursor.clearSelection();
314 cursor._pos = cursor._pos.moveLine(+1);
315 },
316 'Shift-Down': () {
317 if (cursor._toPos == null) {
318 cursor._toPos = cursor._pos;
319 }
320 cursor._pos = cursor._pos.moveLine(+1);
321 },
322
323 'Meta-Up': () {
324 cursor.clearSelection();
325 cursor._pos = _editor._code.start;
326 },
327 'Meta-Shift-Up': () {
328 if (cursor._toPos == null) {
329 cursor._toPos = cursor._pos;
330 }
331 cursor._pos = _editor._code.start;
332 },
333
334 'Meta-Down': () {
335 cursor.clearSelection();
336 cursor._pos = _editor._code.end;
337 },
338 'Meta-Shift-Down': () {
339 if (cursor._toPos == null) {
340 cursor._toPos = cursor._pos;
341 }
342 cursor._pos = _editor._code.end;
343 },
344
345 'Delete': () {
346 //TODO(jimhug): go back to beginning of line when appropriate.
347 if (cursor._toPos == null) {
348 cursor._toPos = cursor._pos.moveColumn(-1);
349 }
350 cursor.deleteSelection();
351 },
352 'Control-D': () {
353 if (cursor._toPos == null) {
354 cursor._toPos = cursor._pos.moveColumn(+1);
355 }
356 cursor.deleteSelection();
357 },
358
359 'Meta-A': () {
360 cursor._pos = _editor._code.start;
361 cursor._toPos = _editor._code.end;
362 },
363
364 '.' : () {
365 // TODO(jimhug): complete hints here
366 cursor.write('.');
367 },
368
369 '}' : () {
370 // TODO(jimhug): Get indentation right.
371 cursor.write('}');
372 },
373
374
375 'Enter': () {
376 cursor.write('\n');
377 // TODO(jimhug): Indent on the new line appropriately...
378 },
379
380 'Tab': () {
381 // TODO(jimhug): force tab to always "properly" indent the line
382 cursor.write(' ');
383 },
384
385 'Space': () => cursor.write(' '),
386 // This seems to be a common typo, so just allow it.
387 'Shift-Space': () => cursor.write(' '),
388
389 'Meta-G': () => cursor.write(CODE),
390 'Meta-H': () => cursor.write(HCODE),
391
392 'Meta-P': () => cursor._pos.block.parse(),
393
394 'Shift-Enter': run,
395 'Meta-Enter': run,
396 };
397
398 _bindings = new KeyBindings(_textInputArea, bindings,
399 (String text) { cursor.write(text); },
400 (String key) {
401 // Use native bindings for cut and paste
402 if (key == 'Meta-V') return false;
403 if (key == 'Control-V') return false;
404
405 // TODO(jimhug): No good, very bad hack.
406 if (key == 'Meta-X' || key == 'Control-X') {
407 window.setTimeout(() {
408 cursor.deleteSelection();
409 _editor._redraw();
410 }, 0);
411 return false;
412 }
413
414 if (key == 'Meta-C') return false;
415 if (key == 'Control-C') return false;
416
417 // Use native bindings for dev tools
418 if (key == 'Alt-Meta-I') return false;
419 if (key == 'Control-Shift-I') return false;
420 window.console.log('Unbound key "$key"');
421 return true;
422 });
423 }
424
425 void handleMessage(String prefix, String message, SourceSpan span) {
426 var m = new Message(prefix, message, span);
427 _errors.addMessage(m);
428 }
429
430 void showSpan(SourceSpan span) {
431 // TODO(jimhug): Get code from correct file.
432 var code = _editor._code;
433 var p1 = new CodePosition(code, span.start);
434 var p2 = new CodePosition(code, span.end);
435 _editor._cursor._pos = p1.toLeaf();
436 _editor._cursor._toPos = p2.toLeaf();
437 _editor._redraw();
438 }
439
440 void run() {
441 _output.contentDocument.body.innerHTML =
442 '<h3 style="color:green">Compiling...</h3>';
443 _errors.clear();
444
445 window.setTimeout( () {
446 final sw = new Stopwatch();
447 sw.start();
448 var code = _editor.getCode();
449 world.files.writeString(DART_FILENAME, code);
450 options.enableAsserts = true;
451 options.enableTypeChecks = true;
452 world.reset();
453 var success = world.compile();
454 sw.stop();
455
456 if (success) {
457 _output.contentDocument.body.innerHTML = '';
458 inject(world.getGeneratedCode(), _output.contentWindow);
459 } else {
460 _output.contentDocument.body.innerHTML =
461 '<h3 style="color:red">Compilation failed</h3>';
462 }
463
464 print('compiled in ${sw.elapsedInMs()}msec');
465 }, 0);
466 }
467
468 void focusKeys(Editor editor) {
469 _editor = editor;
470 cursor = editor._cursor;
471 _textInputArea.focus();
472 }
473 }
474
475
476 class Editor {
477 Shell _shell;
478 Cursor _cursor;
479 CodeBlock _code;
480 var _node;
481
482 // Some temp state for mouse down and selections
483 bool _isSelecting;
484 int _lastClickTime;
485 bool _didDoubleClick;
486
487 Editor(this._shell) {
488 _node = document.createElement('div');
489 _node.className = 'editor';
490
491 _code = new BlockBlock(null, 0);
492 _code.text = CODE;
493 _code.top = 0;
494 _node.appendChild(_code._node);
495
496 _cursor = new Cursor(_code.start);
497 _node.appendChild(_cursor._node);
498
499 _node.addEventListener('mousedown', mousedown, false);
500 _node.addEventListener('mousemove', mousemove, false);
501 _node.addEventListener('mouseup', mouseup, false);
502
503 // TODO(jimhug): Final bit to make region selection clean.
504 //this.node.addEventListener('mouseout', this, false);
505
506 // TODO(jimhug): Lazy rendering should be triggered by this.
507 //_node.addEventListener('scroll', this, false);
508 }
509
510 String getCode() {
511 return _code.text;
512 }
513
514 void goto(int line, int column) {
515 _cursor._pos = _code.getPosition(line, column);
516 }
517
518 void mousedown(MouseEvent e) {
519 // for shift click, create selection region
520 if (e.shiftKey) {
521 _cursor._toPos = _cursor._pos;
522 } else {
523 _cursor.clearSelection();
524 }
525 _cursor._pos = _code.positionFromMouse(e);
526 focus();
527 e.preventDefault();
528
529 _isSelecting = true;
530 }
531
532 void mousemove(MouseEvent e) {
533 // TODO(jimhug): Would REALLY like to check that button is down here!
534 if (_isSelecting) {
535 if (_cursor._toPos == null) {
536 _cursor._toPos = _cursor._pos;
537 }
538 _cursor._pos = _code.positionFromMouse(e);
539 e.preventDefault();
540 _redraw();
541 }
542 }
543
544 void mouseup(MouseEvent e) {
545 _isSelecting = false;
546 if (_cursor.emptySelection) {
547 _cursor.clearSelection();
548 }
549 }
550
551 void scrollToVisible(CodePosition pos) {
552 var top = _node.scrollTop;
553 var height = _node.getBoundingClientRect().height;
554 var pt = pos.getPoint();
555
556 if (pt.y < top) {
557 _node.scrollTop = pt.y;
558 } else if (pt.y > top + height - LINE_HEIGHT) {
559 var H = LINE_HEIGHT * ((height ~/ LINE_HEIGHT) - 1);
560 _node.scrollTop = Math.max(pt.y - H, 0);
561 }
562 }
563
564 void _redraw() {
565 scrollToVisible(_cursor._pos);
566 _code._redraw();
567 _cursor._redraw();
568 }
569
570 void focus() {
571 _shell.focusKeys(this);
572 _cursor._visible = true;
573 _redraw();
574 }
575
576 void blur() {
577 _shell.blurKeys(this);
578 _cursor._visible = false;
579 _redraw();
580 }
581 }
582
583 class Point {
584 final int x, y;
585 const Point(this.x, this.y);
586 }
587
588 class LineColumn {
589 final int line, column;
590 LineColumn(this.line, this.column);
591 }
592
593 class Cursor {
594 CodePosition _pos;
595
596 CodePosition _toPos;
597 bool _visible = true;
598 var _node;
599
600 var _cursorNode;
601 var _selectionNode;
602
603 Cursor(this._pos) {
604 _node = document.createElement('div');
605 _node.className = 'cursorDiv';
606 }
607
608 bool get emptySelection() {
609 return _toPos == null ||
610 (_toPos.block == _pos.block && _toPos.offset == _pos.offset);
611 }
612
613 _redraw() {
614 // Approach is to kill and recreate everything on a redraw.
615 // There are lots of potential improvements if this proves costly.
616 // However: If we don't do this we need a different dance to make cursor
617 // blinking disabled when it is moving.
618
619 _node.innerHTML = '';
620 if (!_visible) return;
621
622 _cursorNode = document.createElement('div');
623 _cursorNode.className = 'cursor blink';
624 _cursorNode.style.setProperty('height', '${LINE_HEIGHT}px');
625
626 var p = _pos.getPoint();
627 _cursorNode.style.setProperty('left', '${p.x}px');
628 _cursorNode.style.setProperty('top', '${p.y}px');
629 _node.appendChild(_cursorNode);
630
631 if (_toPos == null) return;
632
633 void addDiv(top, left, height, width) {
634 var child = document.createElement('div');
635 child.className = 'selection';
636 child.style.setProperty('left', '${left}px');
637 child.style.setProperty('top', '${top}px');
638
639 child.style.setProperty('height', '${height}px');
640 if (width == null) {
641 child.style.setProperty('right', '0px');
642 } else {
643 child.style.setProperty('width', '${width}px');
644 }
645 _node.appendChild(child);
646 }
647
648 var toP = _toPos.getPoint();
649 // Same line - only one line to highlight
650 if (toP.y == p.y) {
651 if (toP.x < p.x) {
652 addDiv(p.y, toP.x, LINE_HEIGHT, p.x - toP.x);
653 } else {
654 addDiv(p.y, p.x, LINE_HEIGHT, toP.x - p.x);
655 }
656 } else {
657 if (toP.y < p.y) {
658 var tmp = toP; toP = p; p = tmp;
659 }
660 addDiv(p.y, p.x, LINE_HEIGHT, null);
661 if (toP.y > p.y + LINE_HEIGHT) {
662 addDiv(p.y + LINE_HEIGHT, 0, toP.y - p.y - LINE_HEIGHT, null);
663 }
664 addDiv(toP.y, 0, LINE_HEIGHT, toP.x);
665 }
666
667 // TODO(jimhug): separate out - this makes default copy/cut work
668 var p0 = _pos.toRoot();
669 var p1 = _toPos.toRoot();
670
671 var i0 = p0.offset;
672 var i1 = p1.offset;
673 if (i1 < i0) {
674 var tmp = i1; i1 = i0; i0 = tmp;
675 }
676 var text = p0.block.text.substring(i0, i1);
677 shell._textInputArea.value = text;
678 shell._textInputArea.select();
679 }
680
681 void clearSelection() {
682 _toPos = null;
683 }
684
685 void moveColumn(int delta) {
686 _pos = _pos.moveColumn(delta);
687 }
688
689 void moveLine(int delta) {
690 _pos = _pos.moveLine(delta);
691 }
692
693 void deleteSelection() {
694 if (_toPos == null) return;
695
696 var p0 = _pos;
697 var p1 = _toPos;
698
699 if (p0.block != p1.block) {
700 // move up to root and resolve there...
701 p0 = p0.toRoot();
702 p1 = p1.toRoot();
703 }
704 assert(p0.block == p1.block);
705
706 if (p1.offset < p0.offset) {
707 var tmp = p0; p0 = p1; p1 = tmp;
708 }
709 p0.block.delete(p0.offset, p1.offset);
710 _pos = p0.toLeaf();
711 _toPos = null;
712 }
713
714 void write(String text) {
715 // TODO(jimhug): combine insert and delete to optimize
716 deleteSelection();
717 _pos.block.insertText(_pos.offset, text);
718 _pos.block._redraw(); // TODO(jimhug): Egregious hack.
719 _pos = _pos.moveColumn(text.length);
720 }
721 }
722
723 class CodePosition {
724 final CodeBlock block;
725 final int offset;
726
727 CodePosition(this.block, this.offset);
728
729 CodePosition moveLine(int delta) {
730 if (delta == 0) return this;
731
732 var lineCol = getLineColumn();
733 return block.getPosition(lineCol.line + delta, lineCol.column);
734 }
735
736 CodePosition moveColumn(int delta) {
737 return block.moveToOffset(offset + delta);
738 }
739
740 Point getPoint() {
741 return block.offsetToPoint(offset);
742 }
743
744 LineColumn getLineColumn() {
745 return block.getLineColumn(offset);
746 }
747
748 CodePosition toRoot() {
749 if (block._parent === null) return this;
750
751 var ret = new CodePosition(block._parent,
752 block._parent.getOffset(block) + offset);
753 return ret.toRoot();
754 }
755
756 CodePosition toLeaf() {
757 return block.moveToOffset(offset);
758 }
759 }
760
761
762 /**
763 * Every [CodeBlock] must provide the following:
764 * - a node to render it - purely as vertical div
765 * - a height in real pixels and in code lines
766 * - proper interaction with CodePosition
767 * - accurate and working get/set for text property
768 * - appropriate integration into parsing
769 * - support to hold onto annotations of various sorts
770 */
771 class BlockChildren implements Iterable<CodeBlock> {
772 BlockBlock _parent;
773 BlockChildren(this._parent);
774
775 Iterator<CodeBlock> iterator() {
776 return new BlockChildrenIterator(_parent._firstChild);
777 }
778 }
779
780 class BlockChildrenIterator implements Iterator<CodeBlock> {
781 CodeBlock _child;
782 BlockChildrenIterator(this._child);
783
784 // TODO(jimhug): current + moveNext() is much more sane.
785 CodeBlock next() {
786 var ret = _child;
787 _child = _child._nextSibling;
788 return ret;
789 }
790
791 bool hasNext() => _child !== null;
792 }
793
794
795 /**
796 * Block block... first and last children are special except for top file.
797 */
798 class BlockBlock extends CodeBlock {
799 CodeBlock _firstChild;
800 CodeBlock _lastChild;
801
802 Iterable<CodeBlock> children;
803
804 BlockBlock(CodeBlock parent, int depth): super(parent, depth) {
805 text = '';
806 children = new BlockChildren(this);
807 }
808
809 CodePosition get start() => _firstChild.start;
810
811 CodePosition get end() => _lastChild.end;
812
813 int get size() {
814 var ret = 0;
815 for (var child in children) ret += child.size;
816 return ret;
817 }
818
819 int get lineCount() {
820 var ret = 0;
821 for (var child in children) ret += child.lineCount;
822 return ret;
823 }
824
825 // TODO(jimhug): This property is expensive here - should it be a prop?
826 String get text() {
827 var ret = new StringBuffer();
828 for (var child in children) ret.add(child.text);
829 return ret.toString();
830 }
831
832 int getOffset(CodeBlock forChild) {
833 var ret = 0;
834 for (var child in children) {
835 if (child == forChild) return ret;
836 ret += child.size;
837 }
838 throw "child missing";
839 }
840
841 int getLine(CodeBlock forChild) {
842 var ret = 0;
843 for (var child in children) {
844 if (child == forChild) return ret;
845 ret += child.lineCount;
846 }
847 throw "child missing";
848 }
849
850 _addChildAfter(CodeBlock addAfterChild, CodeBlock child) {
851 markDirty();
852 child._nextSibling = addAfterChild._nextSibling;
853 child._previousSibling = addAfterChild;
854 addAfterChild._nextSibling = child;
855
856 // Add child's node into our DOM tree.
857 if (child._nextSibling === null) {
858 _node.appendChild(child._node);
859 _lastChild = child;
860 } else {
861 _node.insertBefore(child._node, child._nextSibling._node);
862 }
863 }
864
865 _removeChild(CodeBlock child) {
866 markDirty();
867 if (child._previousSibling !== null) {
868 child._previousSibling._nextSibling = child._nextSibling;
869 } else {
870 _firstChild = child._nextSibling;
871 }
872 if (child._nextSibling !== null) {
873 child._nextSibling._previousSibling = child._previousSibling;
874 } else {
875 _lastChild = child._previousSibling;
876 }
877
878 // Remove child's node from our DOM tree.
879 _node.removeChild(child._node);
880 }
881
882 void set text(String newText) {
883 final sw = new Stopwatch();
884 sw.start();
885
886 _firstChild = _lastChild = null;
887
888 final src = new SourceFile('fake.dart', newText);
889 int start = 0;
890 while (start != -1) {
891 var child = new TextBlock(this, null, _depth + 1);
892 if (_lastChild === null) {
893 _firstChild = _lastChild = child;
894 _node.appendChild(child._node);
895 } else {
896 _addChildAfter(_lastChild, child);
897 }
898 start = child.tokenizeInto(src, start);
899 }
900
901 sw.stop();
902
903 print('create structure in ${sw.elapsedInMs()}msec');
904 }
905
906 void insertText(int offset, String newText) {
907 var index = 0;
908 for (var child in children) {
909 var childSize = child.size;
910 if (offset < childSize) {
911 child.insertText(offset, newText);
912 return;
913 } else if (offset == childSize) {
914 // TODO(jimhug): Nasty merging of text and block structure here.
915 var newChild = new TextBlock(this, newText, _depth + 1);
916 _addChildAfter(child, newChild);
917 return;
918 }
919 offset -= childSize;
920 index++;
921 }
922 // TODO: nesting at this level
923 throw "help";
924 }
925
926 void delete(int from, int to) {
927 assert(from <= to);
928 var keepChild = null;
929 for (var child = _firstChild; child !== null; child = child._nextSibling) {
930 var childSize = child.size;
931 if (keepChild !== null) {
932 _removeChild(child);
933 if (to <= childSize) {
934 // abstraction violation!!!
935 keepChild._text += child._text.substring(to);
936 return;
937 }
938 } else if (from <= childSize) {
939 if (to < childSize) {
940 child.delete(from, to);
941 return;
942 } else {
943 child.delete(from, childSize);
944 keepChild = child;
945 }
946 }
947 from -= childSize;
948 to -= childSize;
949 }
950 // TODO: nesting at this level
951 throw "help";
952 }
953
954 _redraw() {
955 if (!_dirty) return;
956 _dirty = false;
957
958 var childTop = 0;
959 for (var child = _firstChild; child !== null; child = child._nextSibling) {
960 // Note: Performance here relies on lazy setter in CodeBlock.
961 child.top = childTop;
962 child._redraw();
963 childTop += child.height;
964 }
965
966 height = childTop;
967 }
968
969 CodePosition moveToOffset(int offset) {
970 for (var child in children) {
971 var childSize = child.size;
972 if (offset < childSize) {
973 return child.moveToOffset(offset);
974 }
975 offset -= childSize;
976 }
977 // TODO: nesting at this level
978 return end;
979 }
980
981 CodePosition positionFromPoint(int x, int y) {
982 if (y < top) return start;
983
984 for (var child in children) {
985 if (child.top <= y && (child.top + child.height) >= y) {
986 return child.positionFromPoint(x, y - child.top);
987 }
988 }
989 // TODO: next level of nesting...
990 return end;
991 }
992
993 CodePosition getPosition(int line, int column) {
994 if (line < 0) return start;
995 for (var child in children) {
996 if (line < child.lineCount) return child.getPosition(line, column);
997
998 line -= child.lineCount;
999 }
1000 return end; // TODO
1001 }
1002
1003 // These are local line/column to this block??????
1004 LineColumn getLineColumn(int offset) {
1005 if (offset < 0) return new LineColumn(0, 0);
1006
1007 int childLine = 0;
1008 for (var child in children) {
1009 var childSize = child.size;
1010 if (offset < childSize) {
1011 // TODO: This needs modification!!!
1012 var ret = child.getLineColumn(offset);
1013 return new LineColumn(ret.line + childLine, ret.column);
1014 }
1015 offset -= childSize;
1016 childLine += child.lineCount;
1017 }
1018 // TODO: nesting at this level
1019 return new LineColumn(lineCount, 0); // ??? wrong end column
1020 }
1021
1022 Point offsetToPoint(int offset) {
1023 if (offset < 0) return new Point(0, 0);
1024
1025 for (var child in children) {
1026 var childSize = child.size;
1027 if (offset < childSize) {
1028 var ret = child.offsetToPoint(offset);
1029 return new Point(ret.x, ret.y + child.top);
1030 }
1031 offset -= childSize;
1032 }
1033 // TODO: nesting at this level
1034 return new Point(0, top + height);
1035 }
1036 }
1037
1038 /**
1039 * Pure text block - terminals.
1040 */
1041 class TextBlock extends CodeBlock {
1042 List<int> _lineStarts;
1043 String _text;
1044
1045 TextBlock(CodeBlock parent, this._text, int depth): super(parent, depth) {
1046 //window.console.warn('tb: "$_text"');
1047 }
1048
1049 int get size() => _text.length;
1050
1051 int get lineCount() => _lineStarts.length;
1052
1053 String get text() => _text;
1054
1055 void set text(String newText) {
1056 _text = newText;
1057 markDirty();
1058 }
1059
1060 void insertText(int offset, String newText) {
1061 _text = _text.substring(0, offset) + newText + _text.substring(offset);
1062 markDirty();
1063 }
1064
1065 void delete(int from, int to) {
1066 assert(from <= to);
1067 assert(to <= _text.length);
1068 markDirty();
1069
1070 if (to == _text.length) {
1071 if (from == 0) {
1072 _parent._removeChild(this);
1073 } else {
1074 _text = _text.substring(0, from);
1075 }
1076 } else {
1077 _text = _text.substring(0, from) + _text.substring(to);
1078 }
1079 }
1080
1081 int tokenizeInto(SourceFile src, int start) {
1082 _lineStarts = new List<int>();
1083 _lineStarts.add(start);
1084
1085 // classify my text and create siblings and parents as needed
1086 var html = new StringBuffer();
1087 Tokenizer tokenizer = new Tokenizer(src, /*skipWhitespace:*/false, start);
1088
1089 int depth = 0;
1090
1091 // TODO(jimhug): REALLY INEFFICIENT!
1092 void addLineStarts(Token token) {
1093 // TODO(jimhug): Should we just make the Tokenizer do this directly?
1094 final text = src.text;
1095 for (int index = token.start; index < token.end; index++) {
1096 if (text.charCodeAt(index) == 10/*'\n'*/) {
1097 _lineStarts.add(index - start + 1);
1098 }
1099 }
1100 }
1101
1102 // TODO(jimhug): Add whitespace blocks?
1103 while (true) {
1104 var token = tokenizer.next();
1105
1106 if (token.kind == TokenKind.END_OF_FILE) {
1107 _node.innerHTML = html.toString();
1108 if (start == 0) _text = src.text;
1109 else _text = src.text.substring(start);
1110 height = lineCount * LINE_HEIGHT;
1111 // update parent
1112 return -1;
1113 } else if (token.kind == TokenKind.WHITESPACE) {
1114 // TODO(jimhug): Special handling for pure whitespace divs?
1115 if (src.text.charCodeAt(token.end-1) == 10/*'\n'*/) {
1116 // Model 1 - create siblings at any "true" line break
1117 // -- set my from source
1118 _text = src.text.substring(start, token.end);
1119 _node.innerHTML = html.toString();
1120 height = lineCount * LINE_HEIGHT;
1121 // update parent...
1122 return token.end;
1123 }
1124 } else if (token.kind == TokenKind.COMMENT) {
1125 // TODO(jimhug): These may be the most fun blocks to handle...
1126 addLineStarts(token);
1127 } else if (token.kind == TokenKind.STRING) {
1128 addLineStarts(token);
1129 } else if (token.kind == TokenKind.STRING_PART) {
1130 addLineStarts(token);
1131 }
1132
1133 final kind = classify(token);
1134 final stringClass = '';
1135 final text = htmlEscape(token.text);
1136 if (kind != null) {
1137 html.add('<span class="$kind $stringClass">$text</span>');
1138 } else {
1139 html.add('<span>$text</span>');
1140 }
1141 }
1142 }
1143
1144 _redraw() {
1145 if (!_dirty) return;
1146 _dirty = false;
1147
1148 var initialText = _text;
1149 var end = tokenizeInto(new SourceFile('fake.dart', _text), 0);
1150 if (_text.length < initialText.length) {
1151 // ??? How to know we want a new block ???
1152 var extraText = initialText.substring(_text.length);
1153 _parent.insertText(_parent.getOffset(this) + _text.length, extraText);
1154 }
1155 }
1156
1157 CodePosition moveToOffset(int offset) {
1158 if (offset < 0 || offset >= _text.length) {
1159 return _parent.moveToOffset(_parent.getOffset(this) + offset);
1160 }
1161 return new CodePosition(this, offset);
1162 }
1163
1164 CodePosition positionFromPoint(int x, int y) {
1165 return getPosition((y / LINE_HEIGHT).floor(), (x / CHAR_WIDTH).round());
1166 }
1167
1168 CodePosition getPosition(int line, int column) {
1169 if (line < 0 || line >= lineCount) {
1170 return _parent.getPosition(_parent.getLine(this) + line, column);
1171 }
1172
1173 int maxOffset;
1174 if (line < _lineStarts.length - 1) {
1175 maxOffset = _lineStarts[line + 1] - 1;
1176 } else {
1177 maxOffset = _text.length - 1;
1178 }
1179
1180 final offset = Math.min(_lineStarts[line] + column, maxOffset);
1181
1182 return new CodePosition(this, offset);
1183 }
1184
1185 // These are local line/column to this block
1186 LineColumn getLineColumn(int offset) {
1187 if (_lineStarts === null) {
1188 return new LineColumn(0, 0);
1189 }
1190 // TODO(jimhug): Binary search would be faster but more complicated.
1191 int previousStart = 0;
1192 int line = 1;
1193 for (; line < _lineStarts.length; line++) {
1194 int start = _lineStarts[line];
1195 if (start > offset) {
1196 break;
1197 }
1198 previousStart = start;
1199 }
1200 return new LineColumn(line - 1, offset - previousStart);
1201 }
1202
1203 Point offsetToPoint(int offset) {
1204 LineColumn lc = getLineColumn(offset);
1205 return new Point(lc.column * CHAR_WIDTH, top + (lc.line * LINE_HEIGHT));
1206 }
1207 }
1208
1209
1210 class CodeBlock {
1211 CodeBlock _parent;
1212
1213 CodeBlock _previousSibling;
1214 CodeBlock _nextSibling;
1215
1216 int _depth = 0;
1217
1218 bool _dirty = true;
1219 var _node;
1220 int _top, _height;
1221
1222 CodeBlock(this._parent, this._depth) {
1223 _node = document.createElement('div');
1224 _node.className = 'code'; // TODO - different kinds of nodes
1225 }
1226
1227 abstract int size();
1228 abstract int get lineCount();
1229
1230 abstract String get text();
1231
1232 abstract void set text(String newText);
1233
1234 abstract CodePosition moveToOffset(int offset);
1235
1236 CodePosition get start() => new CodePosition(this, 0);
1237
1238 CodePosition get end() => new CodePosition(this, size);
1239
1240 int getOffset(CodeBlock forChild) {
1241 throw "child missing";
1242 }
1243
1244 int getLine(CodeBlock forChild) {
1245 throw "child missing";
1246 }
1247
1248 _removeChild(CodeBlock child) {
1249 throw "child missing";
1250 }
1251
1252 void parse() {
1253 final source = new SourceFile('fake.dart', text);
1254 var p = new Parser(source);
1255 var cu = p.compilationUnit();
1256 }
1257
1258 void markDirty() {
1259 if (!_dirty) {
1260 _dirty = true;
1261 if (_parent != null) {
1262 _parent._dirty = true;
1263 }
1264 }
1265 }
1266
1267 int get top() => _top;
1268
1269 void set top(int newTop) {
1270 if (newTop != _top) {
1271 _top = newTop;
1272 _node.style.setProperty('top', '${_top}px');
1273 }
1274 }
1275
1276 int get height() => _height;
1277
1278 void set height(int newHeight) {
1279 if (newHeight != _height) {
1280 _height = newHeight;
1281 _node.style.setProperty('height', '${_height}px');
1282 }
1283 }
1284
1285 abstract void insertText(int offset, String newText);
1286
1287 abstract void delete(int from, int to);
1288
1289 CodePosition positionFromMouse(MouseEvent p) {
1290 var box = _node.getBoundingClientRect();
1291 int y = p.clientY - box.top;
1292 int x = p.clientX - box.left;
1293
1294 return positionFromPoint(x, y);
1295 }
1296
1297 abstract CodePosition positionFromPoint(int x, int y);
1298
1299 abstract CodePosition getPosition(int line, int column);
1300
1301 // These are local line/column to this block
1302 abstract LineColumn getLineColumn(int offset);
1303
1304 abstract Point offsetToPoint(int offset);
1305
1306 abstract void _redraw();
1307 }
1308
1309
1310 class KeyBindings {
1311 static final Map _remap = const {
1312 'U+001B':'Esc', 'U+0008':'Delete', 'U+0009':'Tab', 'U+0020':'Space',
1313 'Shift':'', 'Control':'', 'Alt':'', 'Meta':''
1314 };
1315
1316 static String _getModifiers(event) {
1317 String ret = '';
1318 if (event.ctrlKey) { ret += 'Control-'; }
1319 if (event.altKey) { ret += 'Alt-'; }
1320 if (event.metaKey) { ret += 'Meta-'; }
1321 if (event.shiftKey) { ret += 'Shift-'; }
1322 return ret;
1323 }
1324
1325 // TODO(jimhug): Move this to base <= 36 and into shared code.
1326 static int _hexDigit(int c) {
1327 if(c >= 48/*0*/ && c <= 57/*9*/) {
1328 return c - 48;
1329 } else if (c >= 97/*a*/ && c <= 102/*f*/) {
1330 return c - 87;
1331 } else if (c >= 65/*A*/ && c <= 70/*F*/) {
1332 return c - 55;
1333 } else {
1334 return -1;
1335 }
1336 }
1337
1338 static int parseHex(String hex) {
1339 var result = 0;
1340
1341 for (int i=0; i < hex.length; i++) {
1342 var digit = _hexDigit(hex.charCodeAt(i));
1343 assert(digit != -1);
1344 result = (result << 4) + digit;
1345 }
1346
1347 return result;
1348 }
1349
1350 static String translate(event) {
1351 var ret = _remap[event.keyIdentifier];
1352 if (ret === null) ret = event.keyIdentifier;
1353
1354 if (ret == '') {
1355 return null;
1356 } else if (ret.startsWith('U+')) {
1357 // This method only reports "non-text" key presses
1358 if (event.ctrlKey || event.altKey || event.metaKey) {
1359 return _getModifiers(event) +
1360 new String.fromCharCodes([parseHex(ret.substring(2, re t.length))]);
1361 } else {
1362 return null;
1363 }
1364 } else {
1365 return _getModifiers(event) + ret;
1366 }
1367 }
1368
1369 var node;
1370 Map bindings;
1371 var handleText, handleUnknown;
1372
1373 KeyBindings(this.node, this.bindings, this.handleText, this.handleUnknown) {
1374 node.addEventListener('textInput', onTextInput, false);
1375 node.addEventListener('keydown', onKeydown, false);
1376 }
1377
1378 onTextInput(TextEvent event) {
1379 var text = event.data;
1380 var ret;
1381 if (bindings[text] !== null) {
1382 ret = bindings[text]();
1383 } else {
1384 ret = handleText(text);
1385 }
1386 // TODO(jimhug): Unfortunate coupling to shell.
1387 shell._editor._redraw();
1388 return ret;
1389 }
1390
1391 // TODO(jimhug): KeyboardEvent type is needed!
1392 onKeydown(KeyboardEvent event) {
1393 final key = translate(event);
1394 if (key !== null) {
1395 if (bindings[key] !== null) {
1396 bindings[key]();
1397 event.preventDefault();
1398 } else {
1399 if (handleUnknown(key)) {
1400 event.preventDefault();
1401 } else {
1402 event.stopPropagation();
1403 }
1404 }
1405 } else {
1406 event.stopPropagation();
1407 }
1408 // TODO(jimhug): Unfortunate coupling to shell.
1409 shell._editor._redraw();
1410 return false;
1411 }
1412 }
1413
1414
1415
1416 // TODO(jimhug): Copy, paste and then modified from dartdoc
1417 /**
1418 * Kinds of tokens that we care to highlight differently. The values of the
1419 * fields here will be used as CSS class names for the generated spans.
1420 */
1421 class Classification {
1422 static final NONE = null;
1423 static final ERROR = "e";
1424 static final COMMENT = "c";
1425 static final IDENTIFIER = "i";
1426 static final KEYWORD = "k";
1427 static final OPERATOR = "o";
1428 static final STRING = "s";
1429 static final NUMBER = "n";
1430 static final PUNCTUATION = "p";
1431
1432 // A few things that are nice to make different:
1433 static final TYPE_IDENTIFIER = "t";
1434
1435 // Between a keyword and an identifier
1436 static final SPECIAL_IDENTIFIER = "r";
1437
1438 static final ARROW_OPERATOR = "a";
1439
1440 static final STRING_INTERPOLATION = 'si';
1441 }
1442
1443 // TODO(rnystrom): should exist in standard lib somewhere
1444 String htmlEscape(String text) {
1445 return text.replaceAll('&', '&amp;').replaceAll(
1446 '>', '&gt;').replaceAll('<', '&lt;');
1447 }
1448
1449 bool _looksLikeType(String name) {
1450 // If the name looks like an UppercaseName, assume it's a type.
1451 return _looksLikePublicType(name) || _looksLikePrivateType(name);
1452 }
1453
1454 bool _looksLikePublicType(String name) {
1455 // If the name looks like an UppercaseName, assume it's a type.
1456 return name.length >= 2 && isUpper(name[0]) && isLower(name[1]);
1457 }
1458
1459 bool _looksLikePrivateType(String name) {
1460 // If the name looks like an _UppercaseName, assume it's a type.
1461 return (name.length >= 3 && name[0] == '_' && isUpper(name[1])
1462 && isLower(name[2]));
1463 }
1464
1465 // These ensure that they don't return "true" if the string only has symbols.
1466 bool isUpper(String s) => s.toLowerCase() != s;
1467 bool isLower(String s) => s.toUpperCase() != s;
1468
1469 String classify(Token token) {
1470 switch (token.kind) {
1471 case TokenKind.ERROR:
1472 return Classification.ERROR;
1473
1474 case TokenKind.IDENTIFIER:
1475 // Special case for names that look like types.
1476 if (_looksLikeType(token.text)
1477 || token.text == 'num'
1478 || token.text == 'bool'
1479 || token.text == 'int'
1480 || token.text == 'double') {
1481 return Classification.TYPE_IDENTIFIER;
1482 }
1483 return Classification.IDENTIFIER;
1484
1485 // Even though it's a reserved word, let's try coloring it like a type.
1486 case TokenKind.VOID:
1487 return Classification.TYPE_IDENTIFIER;
1488
1489 case TokenKind.THIS:
1490 case TokenKind.SUPER:
1491 return Classification.SPECIAL_IDENTIFIER;
1492
1493 case TokenKind.STRING:
1494 case TokenKind.STRING_PART:
1495 case TokenKind.INCOMPLETE_STRING:
1496 case TokenKind.INCOMPLETE_MULTILINE_STRING_DQ:
1497 case TokenKind.INCOMPLETE_MULTILINE_STRING_SQ:
1498 return Classification.STRING;
1499
1500 case TokenKind.INTEGER:
1501 case TokenKind.HEX_INTEGER:
1502 case TokenKind.DOUBLE:
1503 return Classification.NUMBER;
1504
1505 case TokenKind.COMMENT:
1506 case TokenKind.INCOMPLETE_COMMENT:
1507 return Classification.COMMENT;
1508
1509 // => is so awesome it is in a class of its own.
1510 case TokenKind.ARROW:
1511 return Classification.ARROW_OPERATOR;
1512
1513 case TokenKind.HASHBANG:
1514 case TokenKind.LPAREN:
1515 case TokenKind.RPAREN:
1516 case TokenKind.LBRACK:
1517 case TokenKind.RBRACK:
1518 case TokenKind.LBRACE:
1519 case TokenKind.RBRACE:
1520 case TokenKind.COLON:
1521 case TokenKind.SEMICOLON:
1522 case TokenKind.COMMA:
1523 case TokenKind.DOT:
1524 case TokenKind.ELLIPSIS:
1525 return Classification.PUNCTUATION;
1526
1527 case TokenKind.INCR:
1528 case TokenKind.DECR:
1529 case TokenKind.BIT_NOT:
1530 case TokenKind.NOT:
1531 case TokenKind.ASSIGN:
1532 case TokenKind.ASSIGN_OR:
1533 case TokenKind.ASSIGN_XOR:
1534 case TokenKind.ASSIGN_AND:
1535 case TokenKind.ASSIGN_SHL:
1536 case TokenKind.ASSIGN_SAR:
1537 case TokenKind.ASSIGN_SHR:
1538 case TokenKind.ASSIGN_ADD:
1539 case TokenKind.ASSIGN_SUB:
1540 case TokenKind.ASSIGN_MUL:
1541 case TokenKind.ASSIGN_DIV:
1542 case TokenKind.ASSIGN_TRUNCDIV:
1543 case TokenKind.ASSIGN_MOD:
1544 case TokenKind.CONDITIONAL:
1545 case TokenKind.OR:
1546 case TokenKind.AND:
1547 case TokenKind.BIT_OR:
1548 case TokenKind.BIT_XOR:
1549 case TokenKind.BIT_AND:
1550 case TokenKind.SHL:
1551 case TokenKind.SAR:
1552 case TokenKind.SHR:
1553 case TokenKind.ADD:
1554 case TokenKind.SUB:
1555 case TokenKind.MUL:
1556 case TokenKind.DIV:
1557 case TokenKind.TRUNCDIV:
1558 case TokenKind.MOD:
1559 case TokenKind.EQ:
1560 case TokenKind.NE:
1561 case TokenKind.EQ_STRICT:
1562 case TokenKind.NE_STRICT:
1563 case TokenKind.LT:
1564 case TokenKind.GT:
1565 case TokenKind.LTE:
1566 case TokenKind.GTE:
1567 case TokenKind.INDEX:
1568 case TokenKind.SETINDEX:
1569 return Classification.OPERATOR;
1570
1571 // Color this like a keyword
1572 case TokenKind.HASH:
1573
1574 case TokenKind.ABSTRACT:
1575 case TokenKind.ASSERT:
1576 case TokenKind.CLASS:
1577 case TokenKind.EXTENDS:
1578 case TokenKind.FACTORY:
1579 case TokenKind.GET:
1580 case TokenKind.IMPLEMENTS:
1581 case TokenKind.IMPORT:
1582 case TokenKind.INTERFACE:
1583 case TokenKind.LIBRARY:
1584 case TokenKind.NATIVE:
1585 case TokenKind.NEGATE:
1586 case TokenKind.OPERATOR:
1587 case TokenKind.SET:
1588 case TokenKind.SOURCE:
1589 case TokenKind.STATIC:
1590 case TokenKind.TYPEDEF:
1591 case TokenKind.BREAK:
1592 case TokenKind.CASE:
1593 case TokenKind.CATCH:
1594 case TokenKind.CONST:
1595 case TokenKind.CONTINUE:
1596 case TokenKind.DEFAULT:
1597 case TokenKind.DO:
1598 case TokenKind.ELSE:
1599 case TokenKind.FALSE:
1600 case TokenKind.FINALLY:
1601 case TokenKind.FOR:
1602 case TokenKind.IF:
1603 case TokenKind.IN:
1604 case TokenKind.IS:
1605 case TokenKind.NEW:
1606 case TokenKind.NULL:
1607 case TokenKind.RETURN:
1608 case TokenKind.SWITCH:
1609 case TokenKind.THROW:
1610 case TokenKind.TRUE:
1611 case TokenKind.TRY:
1612 case TokenKind.WHILE:
1613 case TokenKind.VAR:
1614 case TokenKind.FINAL:
1615 return Classification.KEYWORD;
1616
1617 case TokenKind.WHITESPACE:
1618 case TokenKind.END_OF_FILE:
1619 return Classification.NONE;
1620
1621 default:
1622 return Classification.NONE;
1623 }
1624 }
OLDNEW
« no previous file with comments | « frog/tip/tip.css ('k') | frog/tip/tip.html » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698